]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Fixed Dimension class to rotate, translate & mirror correctly.
[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 //      dimensionType = DTLinearHorz;
31 }
32
33
34 Dimension::~Dimension()
35 {
36 }
37
38
39 /*
40 How to move: click once moves only the object/point clicked on, all connected
41 objects deform themselves accordingly. click twice selects ALL connected objects;
42 all objects move as a unified whole.
43
44 */
45
46 /*virtual*/ void Dimension::Draw(Painter * painter)
47 {
48         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
49
50         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
51                 painter->DrawHandle(position);
52
53         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
54                 painter->DrawHandle(endpoint);
55
56         if (state == OSSelected)
57                 painter->SetPen(QPen(Qt::cyan, 1.0 * Painter::zoom * size, Qt::SolidLine));
58         else
59                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
60
61         painter->SetBrush(QBrush(QColor(Qt::blue)));
62
63         // Draw an aligned dimension line
64         Vector v(position, endpoint);
65         double angle = v.Angle();
66 //      Vector orthogonal = Vector::Normal(position, endpoint);
67         Vector unit = v.Unit();
68         linePt1 = position, linePt2 = endpoint;
69
70 // Horizontally aligned display
71 #if 1
72         Vector ortho;
73         double x1, y1, length;
74
75         if (dimensionType == DTLinearVert)
76         {
77                 if ((angle < 0) || (angle > PI))
78                 {
79                         x1 = (position.x > endpoint.x ? position.x : endpoint.x);
80                         y1 = (position.y > endpoint.y ? position.y : endpoint.y);
81                         ortho = Vector(1.0, 0);
82                         angle = PI3_OVER_2;
83                 }
84                 else
85                 {
86                         x1 = (position.x > endpoint.x ? endpoint.x : position.x);
87                         y1 = (position.y > endpoint.y ? endpoint.y : position.y);
88                         ortho = Vector(-1.0, 0);
89                         angle = PI_OVER_2;
90                 }
91
92                 linePt1.x = linePt2.x = x1;
93                 length = fabs(position.y - endpoint.y);
94         }
95         else if (dimensionType == DTLinearHorz)
96         {
97                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
98                 {
99                         x1 = (position.x > endpoint.x ? position.x : endpoint.x);
100                         y1 = (position.y > endpoint.y ? position.y : endpoint.y);
101                         ortho = Vector(0, 1.0);
102                         angle = 0;
103                 }
104                 else
105                 {
106                         x1 = (position.x > endpoint.x ? endpoint.x : position.x);
107                         y1 = (position.y > endpoint.y ? endpoint.y : position.y);
108                         ortho = Vector(0, -1.0);
109                         angle = PI;
110                 }
111
112                 linePt1.y = linePt2.y = y1;
113                 length = fabs(position.x - endpoint.x);
114         }
115         else if (dimensionType == DTLinear)
116         {
117                 angle = Vector(linePt1, linePt2).Angle();
118                 ortho = Vector::Normal(linePt1, linePt2);
119                 length = v.Magnitude();
120         }
121
122         unit = Vector(linePt1, linePt2).Unit();
123 //      angle = Vector(linePt1, linePt2).Angle();
124 //      ortho = Vector::Normal(linePt1, linePt2);
125
126         Point p1 = linePt1 + (ortho * 10.0 * size);
127         Point p2 = linePt2 + (ortho * 10.0 * size);
128         Point p3 = linePt1 + (ortho * 16.0 * size);
129         Point p4 = linePt2 + (ortho * 16.0 * size);
130         Point p5 = position + (ortho * 4.0 * size);
131         Point p6 = endpoint + (ortho * 4.0 * size);
132 #endif
133 /*
134 The numbers hardcoded into here, what are they?
135 I believe they are pixels.
136 */
137 #if 0
138         // Get our line parallel to our points
139         Point p1 = position + (orthogonal * 10.0 * size);
140         Point p2 = endpoint + (orthogonal * 10.0 * size);
141
142         Point p3 = position + (orthogonal * 16.0 * size);
143         Point p4 = endpoint + (orthogonal * 16.0 * size);
144         Point p5 = position + (orthogonal * 4.0 * size);
145         Point p6 = endpoint + (orthogonal * 4.0 * size);
146 #endif
147         // Draw extension lines (if certain type)
148         painter->DrawLine(p3, p5);
149         painter->DrawLine(p4, p6);
150
151         // Calculate whether or not the arrowheads are too crowded to put inside
152         // the extension lines. 9.0 is the length of the arrowhead.
153 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, endpoint - (unit * 9.0 * size));
154 //      double t = Geometry::ParameterOfLineAndPoint(pos, endp, endp - (unit * 9.0 * size));
155         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * size));
156 //printf("Dimension::Draw(): t = %lf\n", t);
157
158 // On the screen, it's acting like this is actually 58%...
159 // This is correct, we want it to happen at > 50%
160         if (t > 0.58)
161         {
162                 // Draw main dimension line + arrowheads
163                 painter->DrawLine(p1, p2);
164                 painter->DrawArrowhead(p1, p2, size);
165                 painter->DrawArrowhead(p2, p1, size);
166         }
167         else
168         {
169                 // Draw outside arrowheads
170                 Point p7 = p1 - (unit * 9.0 * size);
171                 Point p8 = p2 + (unit * 9.0 * size);
172                 painter->DrawArrowhead(p1, p7, size);
173                 painter->DrawArrowhead(p2, p8, size);
174                 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
175                 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
176         }
177
178         // Draw length of dimension line...
179         painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
180         Point ctr = p2 + (Vector(p2, p1) / 2.0);
181
182 #if 0
183         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
184 #else
185         QString dimText;
186
187         if (length < 12.0)
188                 dimText = QString("%1\"").arg(length);
189         else
190         {
191                 double feet = (double)((int)length / 12);
192                 double inches = length - (feet * 12.0);
193
194                 if (inches == 0)
195                         dimText = QString("%1'").arg(feet);
196                 else
197                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
198         }
199 #endif
200
201         painter->DrawAngledText(ctr, angle, dimText, size);
202
203         if (hitLine)
204         {
205                 Point hp1 = (p1 + p2) / 2.0;
206                 Point hp2 = (p1 + hp1) / 2.0;
207                 Point hp3 = (hp1 + p2) / 2.0;
208
209                 if (hitFlipSwitch)
210                 {
211                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
212                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
213                         painter->DrawArrowHandle(hp1, ortho.Angle() + PI);
214                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
215                 }
216
217                 painter->DrawHandle(hp1);
218                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
219
220                 if (hitChangeSwitch1)
221                 {
222                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
223                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
224                         painter->DrawArrowToLineHandle(hp2, (dimensionType == DTLinearVert ? v.Angle() - PI_OVER_2 : (v.Angle() < PI ? PI : 0)));
225                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
226                 }
227
228                 painter->DrawHandle(hp2);
229                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
230
231                 if (hitChangeSwitch2)
232                 {
233                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
234                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
235                         painter->DrawArrowToLineHandle(hp3, (dimensionType == DTLinearHorz ? v.Angle() - PI_OVER_2 : (v.Angle() > PI_OVER_2 && v.Angle() < PI3_OVER_2 ? PI3_OVER_2 : PI_OVER_2)));
236                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
237                 }
238
239                 painter->DrawHandle(hp3);
240         }
241 }
242
243
244 /*virtual*/ Vector Dimension::Center(void)
245 {
246         // Technically, this is the midpoint but who are we to quibble? :-)
247         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
248         return endpoint + v;
249 }
250
251
252 /*virtual*/ bool Dimension::Collided(Vector point)
253 {
254         // Someone told us to fuck off, so we'll fuck off. :-)
255         if (ignoreClicks)
256                 return false;
257
258         // We can assume this, since this is a mouse down event here.
259         objectWasDragged = false;
260         HitTest(point);
261
262         // Now that we've done our hit testing on the non-snapped point, snap it if
263         // necessary...
264         if (snapToGrid)
265                 point = SnapPointToGrid(point);
266
267         if (hitPoint1)
268         {
269                 oldState = state;
270                 state = OSSelected;
271                 oldPoint = position;
272                 draggingHandle1 = true;
273                 return true;
274         }
275         else if (hitPoint2)
276         {
277                 oldState = state;
278                 state = OSSelected;
279                 oldPoint = endpoint;
280                 draggingHandle2 = true;
281                 return true;
282         }
283         else if (hitFlipSwitch)
284         {
285                 FlipSides();
286                 hitFlipSwitch = hitLine = false;
287 //              state = OSInactive;
288 //              return true;
289         }
290         else if (hitChangeSwitch1)
291         {
292                 // There are three cases here: aligned, horizontal, & vertical. Aligned
293                 // and horizontal do the same thing, vertical goes back to linear.
294                 if (dimensionType == DTLinearVert)
295                         dimensionType = DTLinear;
296                 else
297                         dimensionType = DTLinearVert;
298
299                 hitFlipSwitch = hitLine = false;
300         }
301         else if (hitChangeSwitch2)
302         {
303                 // There are three cases here: aligned, horizontal, & vertical. Aligned
304                 // and vertical do the same thing, horizontal goes back to linear.
305                 if (dimensionType == DTLinearHorz)
306                         dimensionType = DTLinear;
307                 else
308                         dimensionType = DTLinearHorz;
309
310                 hitFlipSwitch = hitLine = false;
311         }
312
313         state = OSInactive;
314         return false;
315 }
316
317
318 /*virtual*/ bool Dimension::PointerMoved(Vector point)
319 {
320         if (selectionInProgress)
321         {
322                 // Check for whether or not the rect contains this line
323                 if (selection.contains(position.x, position.y)
324                         && selection.contains(endpoint.x, endpoint.y))
325                         state = OSSelected;
326                 else
327                         state = OSInactive;
328
329                 return false;
330         }
331
332         // Hit test tells us what we hit (if anything) through boolean variables. (It
333         // also tells us whether or not the state changed. --not any more)
334         SaveHitState();
335         bool hovered = HitTest(point);
336         needUpdate = HitStateChanged();
337
338         objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
339
340         if (objectWasDragged)
341         {
342                 Vector delta = point - oldPoint;
343
344                 if (draggingHandle1)// || draggingLine)
345                         position += delta;
346
347                 if (draggingHandle2)// || draggingLine)
348                         endpoint += delta;
349
350                 oldPoint = point;
351                 needUpdate = true;
352         }
353
354         return hovered;
355 }
356
357
358 /*virtual*/ void Dimension::PointerReleased(void)
359 {
360 /*      if (draggingHandle1 || draggingHandle2)
361         {
362                 // Set the length (in case the global state was set to fixed (or not))
363                 if (Object::fixedLength)
364                 {
365
366                         if (draggingHandle1)    // startpoint
367                         {
368                                 Vector v = Vector(endpoint, position).Unit() * length;
369                                 position = endpoint + v;
370                         }
371                         else                                    // endpoint
372                         {
373                                 Vector v = Vector(position, endpoint).Unit() * length;
374                                 endpoint = position + v;
375                         }
376                 }
377                 else*/
378                 {
379                         // Otherwise, we calculate the new length, just in case on the next move
380                         // it turns out to have a fixed length. :-)
381                         length = Vector(endpoint - position).Magnitude();
382                 }
383 /*      }*/
384
385         dragging = false;
386         draggingHandle1 = false;
387         draggingHandle2 = false;
388
389         // Here we check for just a click: If object was clicked and dragged, then
390         // revert to the old state (OSInactive). Otherwise, keep the new state that
391         // we set.
392 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
393 about keeping track of old states...
394 */
395         if (objectWasDragged)
396                 state = oldState;
397 }
398
399
400 /*virtual*/ bool Dimension::HitTest(Point point)
401 {
402 //      Vector orthogonal = Vector::Normal(position, endpoint);
403         Vector orthogonal = Vector::Normal(linePt1, linePt2);
404         // Get our line parallel to our points
405 #if 0
406         Point p1 = position + (orthogonal * 10.0 * size);
407         Point p2 = endpoint + (orthogonal * 10.0 * size);
408 #else
409         Point p1 = linePt1 + (orthogonal * 10.0 * size);
410         Point p2 = linePt2 + (orthogonal * 10.0 * size);
411 #endif
412         Point p3(p1, point);
413
414         hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = hitChangeSwitch1
415                 = hitChangeSwitch2 = false;
416         Vector v1(position, point);
417         Vector v2(endpoint, point);
418 //      Vector lineSegment(position, endpoint);
419         Vector lineSegment(p1, p2);
420 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
421         double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
422         double distance;
423         Point midpoint = (p1 + p2) / 2.0;
424         Point hFSPoint = Point(midpoint, point);
425         Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
426         Point hCS2Point = Point((midpoint + p2) / 2.0, point);
427
428         if (t < 0.0)
429                 distance = v1.Magnitude();
430         else if (t > 1.0)
431                 distance = v2.Magnitude();
432         else
433                 // distance = ?Det?(ls, v1) / |ls|
434 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
435                 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
436                         / lineSegment.Magnitude());
437
438         if ((v1.Magnitude() * Painter::zoom) < 8.0)
439                 hitPoint1 = true;
440         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
441                 hitPoint2 = true;
442         else if ((distance * Painter::zoom) < 5.0)
443                 hitLine = true;
444
445         if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
446                 hitFlipSwitch = true;
447         else if ((hCS1Point.Magnitude() * Painter::zoom) < 8.0)
448                 hitChangeSwitch1 = true;
449         else if ((hCS2Point.Magnitude() * Painter::zoom) < 8.0)
450                 hitChangeSwitch2 = true;
451
452         return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
453 }
454
455
456 void Dimension::SaveHitState(void)
457 {
458         oldHitPoint1 = hitPoint1;
459         oldHitPoint2 = hitPoint2;
460         oldHitLine = hitLine;
461         oldHitFlipSwitch = hitFlipSwitch;
462         oldHitChangeSwitch1 = hitChangeSwitch1;
463         oldHitChangeSwitch2 = hitChangeSwitch2;
464 }
465
466
467 bool Dimension::HitStateChanged(void)
468 {
469         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2)
470                 || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch)
471                 || (hitChangeSwitch1 != oldHitChangeSwitch1)
472                 || (hitChangeSwitch2 != oldHitChangeSwitch2))
473                 return true;
474
475         return false;
476 }
477
478
479 /*virtual*/ void Dimension::Translate(Vector amount)
480 {
481         position += amount;
482         endpoint += amount;
483 }
484
485
486 /*virtual*/ void Dimension::Rotate(Point point, double angle)
487 {
488         Point l1 = Geometry::RotatePointAroundPoint(position, point, angle);
489         Point l2 = Geometry::RotatePointAroundPoint(endpoint, point, angle);
490         position = l1;
491         endpoint = l2;
492 }
493
494
495 /*virtual*/ void Dimension::Mirror(Point p1, Point p2)
496 {
497         Point l1 = Geometry::MirrorPointAroundLine(position, p1, p2);
498         Point l2 = Geometry::MirrorPointAroundLine(endpoint, p1, p2);
499         position = l1;
500         endpoint = l2;
501 }
502
503
504 /*virtual*/ void Dimension::Save(void)
505 {
506         Object::Save();
507         oldEndpoint = endpoint;
508 }
509
510
511 /*virtual*/ void Dimension::Restore(void)
512 {
513         Object::Restore();
514         endpoint = oldEndpoint;
515 }
516
517
518 /*virtual*/ void Dimension::Enumerate(FILE * file)
519 {
520         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
521 }
522
523
524 /*virtual*/ Object * Dimension::Copy(void)
525 {
526 #warning "!!! This doesn't take care of attached Dimensions !!!"
527 /*
528 This is a real problem. While having a pointer in the Dimension to this line's points
529 is fast & easy, it creates a huge problem when trying to replicate an object like this.
530
531 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
532 way, if you copy them, ... you might still have problems. Because you can't be sure if
533 a copy will be persistant or not, you then *definitely* do not want them to have the
534 same reference number.
535 */
536
537         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
538         d->size = size;
539         return d;
540 }
541
542
543 // Dimensions are special: they contain exactly *two* points. Here, we check
544 // only for zero/non-zero in returning the correct points.
545 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
546 {
547         if (parameter == 0)
548                 return position;
549
550         return endpoint;
551 }
552
553
554 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
555 {
556         if (parameter == 0)
557                 position += v;
558         else if (parameter == 1.0)
559                 endpoint += v;
560         else
561                 {} // Not sure how to handle this case :-P
562 }
563
564 #if 0
565 /*virtual*/ void Dimension::Connect(Object * obj, double param)
566 {
567         // There are four possibilities here...
568         // The param is only looking for 0 or 1 here.
569         if (point1.object == NULL && point2.object == NULL)
570         {
571                 point1.object = obj;
572                 point1.t = param;
573         }
574         else if (point1.object == NULL && point2.object != NULL)
575         {
576                 if (point2.t == param)
577                         point2.object = obj;
578                 else
579                 {
580                         point1.object = obj;
581                         point1.t = param;
582                 }
583         }
584         else if (point1.object != NULL && point2.object == NULL)
585         {
586                 if (point1.t == param)
587                         point1.object = obj;
588                 else
589                 {
590                         point2.object = obj;
591                         point2.t = param;
592                 }
593         }
594         else if (point1.object != NULL && point2.object != NULL)
595         {
596                 if (point1.t == param)
597                         point1.object = obj;
598                 else
599                         point2.object = obj;
600         }
601 }
602
603
604 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
605 {
606         if (point1.object == obj && point1.t == param)
607                 point1.object = NULL;
608         else if (point2.object == obj && point2.t == param)
609                 point2.object = NULL;
610 }
611
612
613 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
614 {
615         if (point1.object == obj)
616                 point1.object = NULL;
617
618         if (point2.object == obj)
619                 point2.object = NULL;
620 }
621 #endif
622
623 /*virtual*/ QRectF Dimension::Extents(void)
624 {
625         Point p1 = position;
626         Point p2 = endpoint;
627
628 //      if (point1.object)
629 //              p1 = point1.object->GetPointAtParameter(point1.t);
630 //
631 //      if (point2.object)
632 //              p2 = point2.object->GetPointAtParameter(point2.t);
633
634         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
635 }
636
637
638 #if 0
639 /*virtual*/ ObjectType Dimension::Type(void)
640 {
641         return OTDimension;
642 }
643 #endif
644
645
646 void Dimension::FlipSides(void)
647 {
648 #if 1
649         Vector tmp = position;
650         position = endpoint;
651         endpoint = tmp;
652 //Not sure this matters...
653 //#warning "!!! May need to swap parameter values on connected objects !!!"
654 #else
655         Connection tmp = point1;
656         point1 = point2;
657         point2 = tmp;
658 //      double tmp = point1.t;
659 //      point1.t = point2.t;
660 //      point2.t = tmp;
661 //      Object * tmp = point1.object;
662 //      point1.object = point2.object;
663 //      point2.object = tmp;
664 #endif
665         needUpdate = true;
666 }
667