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