]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Added new triangulation tool, ability to snap to endpoints/intersections.
[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 (snapPointIsValid)
268                 point = snapPoint;
269
270         if (hitPoint1)
271         {
272                 oldState = state;
273                 state = OSSelected;
274                 oldPoint = position;
275                 draggingHandle1 = true;
276                 return true;
277         }
278         else if (hitPoint2)
279         {
280                 oldState = state;
281                 state = OSSelected;
282                 oldPoint = endpoint;
283                 draggingHandle2 = true;
284                 return true;
285         }
286         else if (hitFlipSwitch)
287         {
288                 FlipSides();
289                 hitFlipSwitch = hitLine = false;
290 //              state = OSInactive;
291 //              return true;
292         }
293         else if (hitChangeSwitch1)
294         {
295                 // There are three cases here: aligned, horizontal, & vertical. Aligned
296                 // and horizontal do the same thing, vertical goes back to linear.
297                 if (dimensionType == DTLinearVert)
298                         dimensionType = DTLinear;
299                 else
300                         dimensionType = DTLinearVert;
301
302                 hitFlipSwitch = hitLine = false;
303         }
304         else if (hitChangeSwitch2)
305         {
306                 // There are three cases here: aligned, horizontal, & vertical. Aligned
307                 // and vertical do the same thing, horizontal goes back to linear.
308                 if (dimensionType == DTLinearHorz)
309                         dimensionType = DTLinear;
310                 else
311                         dimensionType = DTLinearHorz;
312
313                 hitFlipSwitch = hitLine = false;
314         }
315
316         state = OSInactive;
317         return false;
318 }
319
320
321 /*virtual*/ bool Dimension::PointerMoved(Vector point)
322 {
323         if (selectionInProgress)
324         {
325                 // Check for whether or not the rect contains this line
326                 if (selection.contains(position.x, position.y)
327                         && selection.contains(endpoint.x, endpoint.y))
328                         state = OSSelected;
329                 else
330                         state = OSInactive;
331
332                 return false;
333         }
334
335         // Hit test tells us what we hit (if anything) through boolean variables. (It
336         // also tells us whether or not the state changed. --not any more)
337         SaveHitState();
338         bool hovered = HitTest(point);
339         needUpdate = HitStateChanged();
340
341         if (snapToGrid)
342                 point = SnapPointToGrid(point);
343
344         if (snapPointIsValid)
345                 point = snapPoint;
346
347         objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
348
349         if (objectWasDragged)
350         {
351                 Vector delta = point - oldPoint;
352
353                 if (draggingHandle1)// || draggingLine)
354                         position += delta;
355
356                 if (draggingHandle2)// || draggingLine)
357                         endpoint += delta;
358
359                 oldPoint = point;
360                 needUpdate = true;
361         }
362
363         return hovered;
364 }
365
366
367 /*virtual*/ void Dimension::PointerReleased(void)
368 {
369 /*      if (draggingHandle1 || draggingHandle2)
370         {
371                 // Set the length (in case the global state was set to fixed (or not))
372                 if (Object::fixedLength)
373                 {
374
375                         if (draggingHandle1)    // startpoint
376                         {
377                                 Vector v = Vector(endpoint, position).Unit() * length;
378                                 position = endpoint + v;
379                         }
380                         else                                    // endpoint
381                         {
382                                 Vector v = Vector(position, endpoint).Unit() * length;
383                                 endpoint = position + v;
384                         }
385                 }
386                 else*/
387                 {
388                         // Otherwise, we calculate the new length, just in case on the next move
389                         // it turns out to have a fixed length. :-)
390                         length = Vector(endpoint - position).Magnitude();
391                 }
392 /*      }*/
393
394         dragging = false;
395         draggingHandle1 = false;
396         draggingHandle2 = false;
397
398         // Here we check for just a click: If object was clicked and dragged, then
399         // revert to the old state (OSInactive). Otherwise, keep the new state that
400         // we set.
401 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
402 about keeping track of old states...
403 */
404         if (objectWasDragged)
405                 state = oldState;
406 }
407
408
409 /*virtual*/ bool Dimension::HitTest(Point point)
410 {
411 //      Vector orthogonal = Vector::Normal(position, endpoint);
412         Vector orthogonal = Vector::Normal(linePt1, linePt2);
413         // Get our line parallel to our points
414 #if 0
415         Point p1 = position + (orthogonal * 10.0 * size);
416         Point p2 = endpoint + (orthogonal * 10.0 * size);
417 #else
418         Point p1 = linePt1 + (orthogonal * 10.0 * size);
419         Point p2 = linePt2 + (orthogonal * 10.0 * size);
420 #endif
421         Point p3(p1, point);
422
423         hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = hitChangeSwitch1
424                 = hitChangeSwitch2 = false;
425         Vector v1(position, point);
426         Vector v2(endpoint, point);
427 //      Vector lineSegment(position, endpoint);
428         Vector lineSegment(p1, p2);
429 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
430         double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
431         double distance;
432         Point midpoint = (p1 + p2) / 2.0;
433         Point hFSPoint = Point(midpoint, point);
434         Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
435         Point hCS2Point = Point((midpoint + p2) / 2.0, point);
436
437         if (t < 0.0)
438                 distance = v1.Magnitude();
439         else if (t > 1.0)
440                 distance = v2.Magnitude();
441         else
442                 // distance = ?Det?(ls, v1) / |ls|
443 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
444                 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
445                         / lineSegment.Magnitude());
446
447         if ((v1.Magnitude() * Painter::zoom) < 8.0)
448                 hitPoint1 = true;
449         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
450                 hitPoint2 = true;
451         else if ((distance * Painter::zoom) < 5.0)
452                 hitLine = true;
453
454         if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
455                 hitFlipSwitch = true;
456         else if ((hCS1Point.Magnitude() * Painter::zoom) < 8.0)
457                 hitChangeSwitch1 = true;
458         else if ((hCS2Point.Magnitude() * Painter::zoom) < 8.0)
459                 hitChangeSwitch2 = true;
460
461         return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
462 }
463
464
465 void Dimension::SaveHitState(void)
466 {
467         oldHitPoint1 = hitPoint1;
468         oldHitPoint2 = hitPoint2;
469         oldHitLine = hitLine;
470         oldHitFlipSwitch = hitFlipSwitch;
471         oldHitChangeSwitch1 = hitChangeSwitch1;
472         oldHitChangeSwitch2 = hitChangeSwitch2;
473 }
474
475
476 bool Dimension::HitStateChanged(void)
477 {
478         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2)
479                 || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch)
480                 || (hitChangeSwitch1 != oldHitChangeSwitch1)
481                 || (hitChangeSwitch2 != oldHitChangeSwitch2))
482                 return true;
483
484         return false;
485 }
486
487
488 /*virtual*/ void Dimension::Translate(Vector amount)
489 {
490         position += amount;
491         endpoint += amount;
492 }
493
494
495 /*virtual*/ void Dimension::Rotate(Point point, double angle)
496 {
497         Point l1 = Geometry::RotatePointAroundPoint(position, point, angle);
498         Point l2 = Geometry::RotatePointAroundPoint(endpoint, point, angle);
499         position = l1;
500         endpoint = l2;
501 }
502
503
504 /*virtual*/ void Dimension::Mirror(Point p1, Point p2)
505 {
506         Point l1 = Geometry::MirrorPointAroundLine(position, p1, p2);
507         Point l2 = Geometry::MirrorPointAroundLine(endpoint, p1, p2);
508         position = l1;
509         endpoint = l2;
510 }
511
512
513 /*virtual*/ void Dimension::Save(void)
514 {
515         Object::Save();
516         oldEndpoint = endpoint;
517 }
518
519
520 /*virtual*/ void Dimension::Restore(void)
521 {
522         Object::Restore();
523         endpoint = oldEndpoint;
524 }
525
526
527 /*virtual*/ void Dimension::Enumerate(FILE * file)
528 {
529         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
530 }
531
532
533 /*virtual*/ Object * Dimension::Copy(void)
534 {
535 #warning "!!! This doesn't take care of attached Dimensions !!!"
536 /*
537 This is a real problem. While having a pointer in the Dimension to this line's points
538 is fast & easy, it creates a huge problem when trying to replicate an object like this.
539
540 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
541 way, if you copy them, ... you might still have problems. Because you can't be sure if
542 a copy will be persistant or not, you then *definitely* do not want them to have the
543 same reference number.
544 */
545
546         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
547         d->size = size;
548         return d;
549 }
550
551
552 // Dimensions are special: they contain exactly *two* points. Here, we check
553 // only for zero/non-zero in returning the correct points.
554 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
555 {
556         if (parameter == 0)
557                 return position;
558
559         return endpoint;
560 }
561
562
563 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
564 {
565         if (parameter == 0)
566                 position += v;
567         else if (parameter == 1.0)
568                 endpoint += v;
569         else
570                 {} // Not sure how to handle this case :-P
571 }
572
573 #if 0
574 /*virtual*/ void Dimension::Connect(Object * obj, double param)
575 {
576         // There are four possibilities here...
577         // The param is only looking for 0 or 1 here.
578         if (point1.object == NULL && point2.object == NULL)
579         {
580                 point1.object = obj;
581                 point1.t = param;
582         }
583         else if (point1.object == NULL && point2.object != NULL)
584         {
585                 if (point2.t == param)
586                         point2.object = obj;
587                 else
588                 {
589                         point1.object = obj;
590                         point1.t = param;
591                 }
592         }
593         else if (point1.object != NULL && point2.object == NULL)
594         {
595                 if (point1.t == param)
596                         point1.object = obj;
597                 else
598                 {
599                         point2.object = obj;
600                         point2.t = param;
601                 }
602         }
603         else if (point1.object != NULL && point2.object != NULL)
604         {
605                 if (point1.t == param)
606                         point1.object = obj;
607                 else
608                         point2.object = obj;
609         }
610 }
611
612
613 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
614 {
615         if (point1.object == obj && point1.t == param)
616                 point1.object = NULL;
617         else if (point2.object == obj && point2.t == param)
618                 point2.object = NULL;
619 }
620
621
622 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
623 {
624         if (point1.object == obj)
625                 point1.object = NULL;
626
627         if (point2.object == obj)
628                 point2.object = NULL;
629 }
630 #endif
631
632 /*virtual*/ QRectF Dimension::Extents(void)
633 {
634         Point p1 = position;
635         Point p2 = endpoint;
636
637 //      if (point1.object)
638 //              p1 = point1.object->GetPointAtParameter(point1.t);
639 //
640 //      if (point2.object)
641 //              p2 = point2.object->GetPointAtParameter(point2.t);
642
643         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
644 }
645
646
647 #if 0
648 /*virtual*/ ObjectType Dimension::Type(void)
649 {
650         return OTDimension;
651 }
652 #endif
653
654
655 void Dimension::FlipSides(void)
656 {
657 #if 1
658         Vector tmp = position;
659         position = endpoint;
660         endpoint = tmp;
661 //Not sure this matters...
662 //#warning "!!! May need to swap parameter values on connected objects !!!"
663 #else
664         Connection tmp = point1;
665         point1 = point2;
666         point2 = tmp;
667 //      double tmp = point1.t;
668 //      point1.t = point2.t;
669 //      point2.t = tmp;
670 //      Object * tmp = point1.object;
671 //      point1.object = point2.object;
672 //      point2.object = tmp;
673 #endif
674         needUpdate = true;
675 }
676