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