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