]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Added visual feedback to switch side handle.
[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
212                 if (hitFlipSwitch)
213                 {
214                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
215                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
216                         painter->DrawArrowHandle(hp1, ortho.Angle() + PI);
217                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
218                 }
219
220                 painter->DrawHandle(hp1);
221         }
222 }
223
224
225 /*virtual*/ Vector Dimension::Center(void)
226 {
227         // Technically, this is the midpoint but who are we to quibble? :-)
228         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
229         return endpoint + v;
230 }
231
232
233 /*virtual*/ bool Dimension::Collided(Vector point)
234 {
235         // Someone told us to fuck off, so we'll fuck off. :-)
236         if (ignoreClicks)
237                 return false;
238
239         // We can assume this, since this is a mouse down event here.
240         objectWasDragged = false;
241         HitTest(point);
242
243         // Now that we've done our hit testing on the non-snapped point, snap it if
244         // necessary...
245         if (snapToGrid)
246                 point = SnapPointToGrid(point);
247
248         if (hitPoint1)
249         {
250                 oldState = state;
251                 state = OSSelected;
252                 oldPoint = position;
253                 draggingHandle1 = true;
254                 return true;
255         }
256         else if (hitPoint2)
257         {
258                 oldState = state;
259                 state = OSSelected;
260                 oldPoint = endpoint;
261                 draggingHandle2 = true;
262                 return true;
263         }
264         else if (hitFlipSwitch)
265         {
266                 FlipSides();
267                 hitFlipSwitch = hitLine = false;
268 //              state = OSInactive;
269 //              return true;
270         }
271
272         state = OSInactive;
273         return false;
274 }
275
276
277 /*virtual*/ bool Dimension::PointerMoved(Vector point)
278 {
279         if (selectionInProgress)
280         {
281                 // Check for whether or not the rect contains this line
282                 if (selection.contains(position.x, position.y)
283                         && selection.contains(endpoint.x, endpoint.y))
284                         state = OSSelected;
285                 else
286                         state = OSInactive;
287
288                 return false;
289         }
290
291         // Hit test tells us what we hit (if anything) through boolean variables. (It
292         // also tells us whether or not the state changed. --not any more)
293         SaveHitState();
294         bool hovered = HitTest(point);
295         needUpdate = HitStateChanged();
296
297         objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
298
299         if (objectWasDragged)
300         {
301                 Vector delta = point - oldPoint;
302
303                 if (draggingHandle1)// || draggingLine)
304                         position += delta;
305
306                 if (draggingHandle2)// || draggingLine)
307                         endpoint += delta;
308
309                 oldPoint = point;
310                 needUpdate = true;
311         }
312
313         return hovered;
314 }
315
316
317 /*virtual*/ void Dimension::PointerReleased(void)
318 {
319 /*      if (draggingHandle1 || draggingHandle2)
320         {
321                 // Set the length (in case the global state was set to fixed (or not))
322                 if (Object::fixedLength)
323                 {
324
325                         if (draggingHandle1)    // startpoint
326                         {
327                                 Vector v = Vector(endpoint, position).Unit() * length;
328                                 position = endpoint + v;
329                         }
330                         else                                    // endpoint
331                         {
332                                 Vector v = Vector(position, endpoint).Unit() * length;
333                                 endpoint = position + v;
334                         }
335                 }
336                 else*/
337                 {
338                         // Otherwise, we calculate the new length, just in case on the next move
339                         // it turns out to have a fixed length. :-)
340                         length = Vector(endpoint - position).Magnitude();
341                 }
342 /*      }*/
343
344         dragging = false;
345         draggingHandle1 = false;
346         draggingHandle2 = false;
347
348         // Here we check for just a click: If object was clicked and dragged, then
349         // revert to the old state (OSInactive). Otherwise, keep the new state that
350         // we set.
351 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
352 about keeping track of old states...
353 */
354         if (objectWasDragged)
355                 state = oldState;
356 }
357
358
359 /*virtual*/ bool Dimension::HitTest(Point point)
360 {
361 //      Vector orthogonal = Vector::Normal(position, endpoint);
362         Vector orthogonal = Vector::Normal(linePt1, linePt2);
363         // Get our line parallel to our points
364 #if 0
365         Point p1 = position + (orthogonal * 10.0 * size);
366         Point p2 = endpoint + (orthogonal * 10.0 * size);
367 #else
368         Point p1 = linePt1 + (orthogonal * 10.0 * size);
369         Point p2 = linePt2 + (orthogonal * 10.0 * size);
370 #endif
371         Point p3(p1, point);
372
373         hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = false;
374         Vector v1(position, point);
375         Vector v2(endpoint, point);
376 //      Vector lineSegment(position, endpoint);
377         Vector lineSegment(p1, p2);
378 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
379         double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
380         double distance;
381         Point hFSPoint = Point((p1 + p2) / 2.0, point);
382
383         if (t < 0.0)
384                 distance = v1.Magnitude();
385         else if (t > 1.0)
386                 distance = v2.Magnitude();
387         else
388                 // distance = ?Det?(ls, v1) / |ls|
389 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
390                 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
391                         / lineSegment.Magnitude());
392
393         if ((v1.Magnitude() * Painter::zoom) < 8.0)
394                 hitPoint1 = true;
395         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
396                 hitPoint2 = true;
397         else if ((distance * Painter::zoom) < 5.0)
398                 hitLine = true;
399
400         if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
401                 hitFlipSwitch = true;
402
403         return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch ? true : false);
404 }
405
406
407 void Dimension::SaveHitState(void)
408 {
409         oldHitPoint1 = hitPoint1;
410         oldHitPoint2 = hitPoint2;
411         oldHitLine = hitLine;
412         oldHitFlipSwitch = hitFlipSwitch;
413 }
414
415
416 bool Dimension::HitStateChanged(void)
417 {
418         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch))
419                 return true;
420
421         return false;
422 }
423
424
425 /*virtual*/ void Dimension::Enumerate(FILE * file)
426 {
427         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
428 }
429
430
431 /*virtual*/ Object * Dimension::Copy(void)
432 {
433 #warning "!!! This doesn't take care of attached Dimensions !!!"
434 /*
435 This is a real problem. While having a pointer in the Dimension to this line's points
436 is fast & easy, it creates a huge problem when trying to replicate an object like this.
437
438 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
439 way, if you copy them, ... you might still have problems. Because you can't be sure if
440 a copy will be persistant or not, you then *definitely* do not want them to have the
441 same reference number.
442 */
443
444         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
445         d->size = size;
446         return d;
447 }
448
449
450 // Dimensions are special: they contain exactly *two* points. Here, we check
451 // only for zero/non-zero in returning the correct points.
452 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
453 {
454         if (parameter == 0)
455                 return position;
456
457         return endpoint;
458 }
459
460
461 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
462 {
463         if (parameter == 0)
464                 position += v;
465         else if (parameter == 1.0)
466                 endpoint += v;
467         else
468                 {} // Not sure how to handle this case :-P
469 }
470
471 #if 0
472 /*virtual*/ void Dimension::Connect(Object * obj, double param)
473 {
474         // There are four possibilities here...
475         // The param is only looking for 0 or 1 here.
476         if (point1.object == NULL && point2.object == NULL)
477         {
478                 point1.object = obj;
479                 point1.t = param;
480         }
481         else if (point1.object == NULL && point2.object != NULL)
482         {
483                 if (point2.t == param)
484                         point2.object = obj;
485                 else
486                 {
487                         point1.object = obj;
488                         point1.t = param;
489                 }
490         }
491         else if (point1.object != NULL && point2.object == NULL)
492         {
493                 if (point1.t == param)
494                         point1.object = obj;
495                 else
496                 {
497                         point2.object = obj;
498                         point2.t = param;
499                 }
500         }
501         else if (point1.object != NULL && point2.object != NULL)
502         {
503                 if (point1.t == param)
504                         point1.object = obj;
505                 else
506                         point2.object = obj;
507         }
508 }
509
510
511 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
512 {
513         if (point1.object == obj && point1.t == param)
514                 point1.object = NULL;
515         else if (point2.object == obj && point2.t == param)
516                 point2.object = NULL;
517 }
518
519
520 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
521 {
522         if (point1.object == obj)
523                 point1.object = NULL;
524
525         if (point2.object == obj)
526                 point2.object = NULL;
527 }
528 #endif
529
530 /*virtual*/ QRectF Dimension::Extents(void)
531 {
532         Point p1 = position;
533         Point p2 = endpoint;
534
535 //      if (point1.object)
536 //              p1 = point1.object->GetPointAtParameter(point1.t);
537 //
538 //      if (point2.object)
539 //              p2 = point2.object->GetPointAtParameter(point2.t);
540
541         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
542 }
543
544
545 #if 0
546 /*virtual*/ ObjectType Dimension::Type(void)
547 {
548         return OTDimension;
549 }
550 #endif
551
552
553 void Dimension::FlipSides(void)
554 {
555 #if 1
556         Vector tmp = position;
557         position = endpoint;
558         endpoint = tmp;
559 //Not sure this matters...
560 //#warning "!!! May need to swap parameter values on connected objects !!!"
561 #else
562         Connection tmp = point1;
563         point1 = point2;
564         point2 = tmp;
565 //      double tmp = point1.t;
566 //      point1.t = point2.t;
567 //      point2.t = tmp;
568 //      Object * tmp = point1.object;
569 //      point1.object = point2.object;
570 //      point2.object = tmp;
571 #endif
572         needUpdate = true;
573 }
574