]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
27823b39135fb8f7f7ed87f4d482476672a42b19
[architektonas] / src / dimension.cpp
1 // dimension.cpp: Dimension object
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  04/04/2011  Created this file, basic rendering
12 // JLH  03/14/2013  Updated to new connection system
13 //
14
15 #include "dimension.h"
16
17 #include <QtGui>
18 #include "geometry.h"
19 #include "mathconstants.h"
20 #include "painter.h"
21
22
23 Dimension::Dimension(Vector p1, Vector p2, DimensionType dt/*= DTLinear*/, Object * p/*= NULL*/):
24         Object(p1, p), endpoint(p2),
25         dragging(false), draggingHandle1(false), draggingHandle2(false),
26         length(p2.Magnitude()), dimensionType(dt), size(0.25), point1(NULL), point2(NULL)
27 {
28         // We set the size to 1/4 base unit. Could be anything.
29         type = OTDimension;
30 }
31
32
33 // This is bad, p1 & p2 could be NULL, causing much consternation...
34 Dimension::Dimension(Connection p1, Connection p2, DimensionType dt/*= DTLinear*/, Object * p/*= NULL*/):
35         dragging(false), draggingHandle1(false), draggingHandle2(false),
36         length(0), dimensionType(dt), size(0.25), point1(p1), point2(p2)
37 {
38         type = OTDimension;
39 }
40
41
42 Dimension::~Dimension()
43 {
44 }
45
46
47 /*
48 The approach used below creates a hierarchy: Dimension is subservient to Line.
49
50 Does this solve our problem of connected objects? Maybe, partially. Let's think this
51 through. It only works for endpoints, not points in the middle...
52
53 Also: this is bad, depending on the Draw() function to update the internal
54       position(s) of the data of the object! (is it though?)
55
56 How to move: click once moves only the object/point clicked on, all connected
57 objects deform themselves accordingly. click twice selects ALL connected objects;
58 all objects move as a unified whole.
59
60 */
61
62 /*virtual*/ void Dimension::Draw(Painter * painter)
63 {
64 #if 0
65         // If there are valid Vector pointers in here, use them to update the internal
66         // positions. Otherwise, we just use the internal positions by default.
67         if (point1.object)
68                 position = point1.object->GetPointAtParameter(point1.t);
69
70         if (point2.object)
71                 endpoint = point2.object->GetPointAtParameter(point2.t);
72 #endif
73
74         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
75
76         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
77                 painter->DrawHandle(position);
78
79         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
80                 painter->DrawHandle(endpoint);
81 #if 1
82         if (state == OSSelected)
83                 painter->SetPen(QPen(Qt::cyan, 1.0 * Painter::zoom * size, Qt::SolidLine));
84         else
85 //              painter->SetPen(QPen(Qt::blue, 1.0, Qt::SolidLine));
86 #endif
87                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
88
89         painter->SetBrush(QBrush(QColor(Qt::blue)));
90
91         // Draw an aligned dimension line
92         double angle = Vector(endpoint - position).Angle();
93         double orthoAngle = angle + (PI / 2.0);
94         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
95         Vector unit = Vector(endpoint - position).Unit();
96
97 // Arrowhead:
98 //      Point p1 = head - (unit * 9.0 * size);
99 //      Point p2 = p1 + (orthogonal * 3.0 * size);
100 //      Point p3 = p1 - (orthogonal * 3.0 * size);
101
102 /*
103 The numbers hardcoded into here, what are they?
104 I believe they are pixels.
105 */
106
107         // Get our line parallel to our points
108         Point p1 = position + (orthogonal * 10.0 * size);
109         Point p2 = endpoint + (orthogonal * 10.0 * size);
110
111         Point p3 = position + (orthogonal * 16.0 * size);
112         Point p4 = endpoint + (orthogonal * 16.0 * size);
113         Point p5 = position + (orthogonal * 4.0 * size);
114         Point p6 = endpoint + (orthogonal * 4.0 * size);
115
116         // Draw extension lines
117         painter->DrawLine(p3, p5);
118         painter->DrawLine(p4, p6);
119
120         // Calculate whether or not the arrowheads are too crowded to put inside
121         // the extension lines. 9.0 is the length of the arrowhead.
122 //      double t = Vector::Parameter(position, endpoint, endpoint - (unit * 9.0 * size));
123         double t = Geometry::ParameterOfLineAndPoint(position, endpoint, endpoint - (unit * 9.0 * size));
124 //printf("Dimension::Draw(): t = %lf\n", t);
125
126 // On the screen, it's acting like this is actually 58%...
127 // This is correct, we want it to happen at > 50%
128         if (t > 0.58)
129         {
130                 // Draw main dimension line + arrowheads
131                 painter->DrawLine(p1, p2);
132                 painter->DrawArrowhead(p1, p2, size);
133                 painter->DrawArrowhead(p2, p1, size);
134         }
135         else
136         {
137                 // Draw outside arrowheads
138                 Point p7 = p1 - (unit * 9.0 * size);
139                 Point p8 = p2 + (unit * 9.0 * size);
140                 painter->DrawArrowhead(p1, p7, size);
141                 painter->DrawArrowhead(p2, p8, size);
142                 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
143                 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
144         }
145
146         // Draw length of dimension line...
147         painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
148         Vector v1((p1.x - p2.x) / 2.0, (p1.y - p2.y) / 2.0);
149         Point ctr = p2 + v1;
150
151 #if 0
152         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
153 #else
154         QString dimText;
155         double length = Vector(endpoint - position).Magnitude();
156
157         if (length < 12.0)
158                 dimText = QString("%1\"").arg(length);
159         else
160         {
161                 double feet = (double)((int)length / 12);
162                 double inches = length - (feet * 12.0);
163
164                 if (inches == 0)
165                         dimText = QString("%1'").arg(feet);
166                 else
167                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
168         }
169 #endif
170
171         painter->DrawAngledText(ctr, angle, dimText, size);
172 }
173
174
175 /*virtual*/ Vector Dimension::Center(void)
176 {
177         // Technically, this is the midpoint but who are we to quibble? :-)
178         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
179         return endpoint + v;
180 }
181
182
183 /*virtual*/ bool Dimension::Collided(Vector point)
184 {
185         // Someone told us to fuck off, so we'll fuck off. :-)
186         if (ignoreClicks)
187                 return false;
188
189         // We can assume this, since this is a mouse down event here.
190         objectWasDragged = false;
191         HitTest(point);
192
193         // Now that we've done our hit testing on the non-snapped point, snap it if
194         // necessary...
195         if (snapToGrid)
196                 point = SnapPointToGrid(point);
197
198         if (hitPoint1)
199         {
200                 oldState = state;
201                 state = OSSelected;
202                 oldPoint = position;
203                 draggingHandle1 = true;
204                 return true;
205         }
206         else if (hitPoint2)
207         {
208                 oldState = state;
209                 state = OSSelected;
210                 oldPoint = endpoint;
211                 draggingHandle2 = true;
212                 return true;
213         }
214
215         state = OSInactive;
216         return false;
217 }
218
219
220 /*virtual*/ void Dimension::PointerMoved(Vector point)
221 {
222         if (selectionInProgress)
223         {
224                 // Check for whether or not the rect contains this line
225                 if (selection.contains(position.x, position.y)
226                         && selection.contains(endpoint.x, endpoint.y))
227                         state = OSSelected;
228                 else
229                         state = OSInactive;
230
231                 return;
232         }
233
234         // Hit test tells us what we hit (if anything) through boolean variables. (It
235         // also tells us whether or not the state changed. --not any more)
236         SaveHitState();
237         HitTest(point);
238         needUpdate = HitStateChanged();
239
240         objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
241
242         if (objectWasDragged)
243         {
244                 Vector delta = point - oldPoint;
245
246                 if (draggingHandle1)// || draggingLine)
247                         position += delta;
248
249                 if (draggingHandle2)// || draggingLine)
250                         endpoint += delta;
251
252                 oldPoint = point;
253                 needUpdate = true;
254         }
255 }
256
257
258 /*virtual*/ void Dimension::PointerReleased(void)
259 {
260 /*      if (draggingHandle1 || draggingHandle2)
261         {
262                 // Set the length (in case the global state was set to fixed (or not))
263                 if (Object::fixedLength)
264                 {
265
266                         if (draggingHandle1)    // startpoint
267                         {
268                                 Vector v = Vector(endpoint, position).Unit() * length;
269                                 position = endpoint + v;
270                         }
271                         else                                    // endpoint
272                         {
273                                 Vector v = Vector(position, endpoint).Unit() * length;
274                                 endpoint = position + v;
275                         }
276                 }
277                 else*/
278                 {
279                         // Otherwise, we calculate the new length, just in case on the next move
280                         // it turns out to have a fixed length. :-)
281                         length = Vector(endpoint - position).Magnitude();
282                 }
283 /*      }*/
284
285         dragging = false;
286         draggingHandle1 = false;
287         draggingHandle2 = false;
288
289         // Here we check for just a click: If object was clicked and dragged, then
290         // revert to the old state (OSInactive). Otherwise, keep the new state that
291         // we set.
292 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
293 about keeping track of old states...
294 */
295         if (objectWasDragged)
296                 state = oldState;
297 }
298
299
300 /*virtual*/ bool Dimension::HitTest(Point point)
301 {
302         hitPoint1 = hitPoint2 = false;
303 //      Vector lineSegment(position, endpoint);
304         Vector v1(position, point);
305         Vector v2(endpoint, point);
306 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
307 //      double distance;
308
309 //      if (t < 0.0)
310 //              distance = v1.Magnitude();
311 //      else if (t > 1.0)
312 //              distance = v2.Magnitude();
313 //      else
314                 // distance = ?Det?(ls, v1) / |ls|
315 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
316 //                      / lineSegment.Magnitude());
317
318         if ((v1.Magnitude() * Painter::zoom) < 8.0)
319                 hitPoint1 = true;
320         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
321                 hitPoint2 = true;
322
323         return (hitPoint1 || hitPoint2 ? true : false);
324 }
325
326
327 void Dimension::SaveHitState(void)
328 {
329         oldHitPoint1 = hitPoint1;
330         oldHitPoint2 = hitPoint2;
331 //      oldHitLine = hitLine;
332 }
333
334
335 bool Dimension::HitStateChanged(void)
336 {
337         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2))
338                 return true;
339
340         return false;
341 }
342
343
344 /*virtual*/ void Dimension::Enumerate(FILE * file)
345 {
346         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
347 }
348
349
350 /*virtual*/ Object * Dimension::Copy(void)
351 {
352 #warning "!!! This doesn't take care of attached Dimensions !!!"
353 /*
354 This is a real problem. While having a pointer in the Dimension to this line's points
355 is fast & easy, it creates a huge problem when trying to replicate an object like this.
356
357 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
358 way, if you copy them, ... you might still have problems. Because you can't be sure if
359 a copy will be persistant or not, you then *definitely* do not want them to have the
360 same reference number.
361 */
362
363         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
364         d->size = size;
365         return d;
366 }
367
368
369 // Dimensions are special: they contain exactly *two* points. Here, we check
370 // only for zero/non-zero in returning the correct points.
371 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
372 {
373         if (parameter == 0)
374                 return position;
375
376         return endpoint;
377 }
378
379
380 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
381 {
382         if (parameter == 0)
383                 position += v;
384         else if (parameter == 1.0)
385                 endpoint += v;
386         else
387                 {} // Not sure how to handle this case :-P
388 }
389
390
391 /*virtual*/ void Dimension::Connect(Object * obj, double param)
392 {
393         // There are four possibilities here...
394         // The param is only looking for 0 or 1 here.
395         if (point1.object == NULL && point2.object == NULL)
396         {
397                 point1.object = obj;
398                 point1.t = param;
399         }
400         else if (point1.object == NULL && point2.object != NULL)
401         {
402                 if (point2.t == param)
403                         point2.object = obj;
404                 else
405                 {
406                         point1.object = obj;
407                         point1.t = param;
408                 }
409         }
410         else if (point1.object != NULL && point2.object == NULL)
411         {
412                 if (point1.t == param)
413                         point1.object = obj;
414                 else
415                 {
416                         point2.object = obj;
417                         point2.t = param;
418                 }
419         }
420         else if (point1.object != NULL && point2.object != NULL)
421         {
422                 if (point1.t == param)
423                         point1.object = obj;
424                 else
425                         point2.object = obj;
426         }
427 }
428
429
430 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
431 {
432         if (point1.object == obj && point1.t == param)
433                 point1.object = NULL;
434         else if (point2.object == obj && point2.t == param)
435                 point2.object = NULL;
436 }
437
438
439 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
440 {
441         if (point1.object == obj)
442                 point1.object = NULL;
443
444         if (point2.object == obj)
445                 point2.object = NULL;
446 }
447
448
449 /*virtual*/ QRectF Dimension::Extents(void)
450 {
451         Point p1 = position;
452         Point p2 = endpoint;
453
454         if (point1.object)
455                 p1 = point1.object->GetPointAtParameter(point1.t);
456
457         if (point2.object)
458                 p2 = point2.object->GetPointAtParameter(point2.t);
459
460         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
461 }
462
463
464 #if 0
465 /*virtual*/ ObjectType Dimension::Type(void)
466 {
467         return OTDimension;
468 }
469 #endif
470
471
472 void Dimension::FlipSides(void)
473 {
474 #if 0
475         Vector tmp = position;
476         position = endpoint;
477         endpoint = tmp;
478 #else
479         Connection tmp = point1;
480         point1 = point2;
481         point2 = tmp;
482 //      double tmp = point1.t;
483 //      point1.t = point2.t;
484 //      point2.t = tmp;
485 //      Object * tmp = point1.object;
486 //      point1.object = point2.object;
487 //      point2.object = tmp;
488 #endif
489         needUpdate = true;
490 }
491