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