]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Trim tool now works for Lines, but inaccurate.
[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*/ bool 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 false;
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         bool hovered = 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         return hovered;
283 }
284
285
286 /*virtual*/ void Dimension::PointerReleased(void)
287 {
288 /*      if (draggingHandle1 || draggingHandle2)
289         {
290                 // Set the length (in case the global state was set to fixed (or not))
291                 if (Object::fixedLength)
292                 {
293
294                         if (draggingHandle1)    // startpoint
295                         {
296                                 Vector v = Vector(endpoint, position).Unit() * length;
297                                 position = endpoint + v;
298                         }
299                         else                                    // endpoint
300                         {
301                                 Vector v = Vector(position, endpoint).Unit() * length;
302                                 endpoint = position + v;
303                         }
304                 }
305                 else*/
306                 {
307                         // Otherwise, we calculate the new length, just in case on the next move
308                         // it turns out to have a fixed length. :-)
309                         length = Vector(endpoint - position).Magnitude();
310                 }
311 /*      }*/
312
313         dragging = false;
314         draggingHandle1 = false;
315         draggingHandle2 = false;
316
317         // Here we check for just a click: If object was clicked and dragged, then
318         // revert to the old state (OSInactive). Otherwise, keep the new state that
319         // we set.
320 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
321 about keeping track of old states...
322 */
323         if (objectWasDragged)
324                 state = oldState;
325 }
326
327
328 /*virtual*/ bool Dimension::HitTest(Point point)
329 {
330         Vector orthogonal = Vector::Normal(position, endpoint);
331         // Get our line parallel to our points
332 #if 0
333         Point p1 = position + (orthogonal * 10.0 * size);
334         Point p2 = endpoint + (orthogonal * 10.0 * size);
335 #else
336         Point p1 = linePt1 + (orthogonal * 10.0 * size);
337         Point p2 = linePt2 + (orthogonal * 10.0 * size);
338 #endif
339         Point p3(p1, point);
340
341         hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = false;
342         Vector v1(position, point);
343         Vector v2(endpoint, point);
344 //      Vector lineSegment(position, endpoint);
345         Vector lineSegment(p1, p2);
346 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
347         double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
348         double distance;
349
350         if (t < 0.0)
351                 distance = v1.Magnitude();
352         else if (t > 1.0)
353                 distance = v2.Magnitude();
354         else
355                 // distance = ?Det?(ls, v1) / |ls|
356 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
357                 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
358                         / lineSegment.Magnitude());
359
360         if ((v1.Magnitude() * Painter::zoom) < 8.0)
361                 hitPoint1 = true;
362         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
363                 hitPoint2 = true;
364         else if ((distance * Painter::zoom) < 5.0)
365                 hitLine = true;
366
367         return (hitPoint1 || hitPoint2 || hitLine ? true : false);
368 }
369
370
371 void Dimension::SaveHitState(void)
372 {
373         oldHitPoint1 = hitPoint1;
374         oldHitPoint2 = hitPoint2;
375         oldHitLine = hitLine;
376 }
377
378
379 bool Dimension::HitStateChanged(void)
380 {
381         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
382                 return true;
383
384         return false;
385 }
386
387
388 /*virtual*/ void Dimension::Enumerate(FILE * file)
389 {
390         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
391 }
392
393
394 /*virtual*/ Object * Dimension::Copy(void)
395 {
396 #warning "!!! This doesn't take care of attached Dimensions !!!"
397 /*
398 This is a real problem. While having a pointer in the Dimension to this line's points
399 is fast & easy, it creates a huge problem when trying to replicate an object like this.
400
401 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
402 way, if you copy them, ... you might still have problems. Because you can't be sure if
403 a copy will be persistant or not, you then *definitely* do not want them to have the
404 same reference number.
405 */
406
407         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
408         d->size = size;
409         return d;
410 }
411
412
413 // Dimensions are special: they contain exactly *two* points. Here, we check
414 // only for zero/non-zero in returning the correct points.
415 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
416 {
417         if (parameter == 0)
418                 return position;
419
420         return endpoint;
421 }
422
423
424 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
425 {
426         if (parameter == 0)
427                 position += v;
428         else if (parameter == 1.0)
429                 endpoint += v;
430         else
431                 {} // Not sure how to handle this case :-P
432 }
433
434 #if 0
435 /*virtual*/ void Dimension::Connect(Object * obj, double param)
436 {
437         // There are four possibilities here...
438         // The param is only looking for 0 or 1 here.
439         if (point1.object == NULL && point2.object == NULL)
440         {
441                 point1.object = obj;
442                 point1.t = param;
443         }
444         else if (point1.object == NULL && point2.object != NULL)
445         {
446                 if (point2.t == param)
447                         point2.object = obj;
448                 else
449                 {
450                         point1.object = obj;
451                         point1.t = param;
452                 }
453         }
454         else if (point1.object != NULL && point2.object == NULL)
455         {
456                 if (point1.t == param)
457                         point1.object = obj;
458                 else
459                 {
460                         point2.object = obj;
461                         point2.t = param;
462                 }
463         }
464         else if (point1.object != NULL && point2.object != NULL)
465         {
466                 if (point1.t == param)
467                         point1.object = obj;
468                 else
469                         point2.object = obj;
470         }
471 }
472
473
474 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
475 {
476         if (point1.object == obj && point1.t == param)
477                 point1.object = NULL;
478         else if (point2.object == obj && point2.t == param)
479                 point2.object = NULL;
480 }
481
482
483 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
484 {
485         if (point1.object == obj)
486                 point1.object = NULL;
487
488         if (point2.object == obj)
489                 point2.object = NULL;
490 }
491 #endif
492
493 /*virtual*/ QRectF Dimension::Extents(void)
494 {
495         Point p1 = position;
496         Point p2 = endpoint;
497
498 //      if (point1.object)
499 //              p1 = point1.object->GetPointAtParameter(point1.t);
500 //
501 //      if (point2.object)
502 //              p2 = point2.object->GetPointAtParameter(point2.t);
503
504         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
505 }
506
507
508 #if 0
509 /*virtual*/ ObjectType Dimension::Type(void)
510 {
511         return OTDimension;
512 }
513 #endif
514
515
516 void Dimension::FlipSides(void)
517 {
518 #if 1
519         Vector tmp = position;
520         position = endpoint;
521         endpoint = tmp;
522 //Not sure this matters...
523 //#warning "!!! May need to swap parameter values on connected objects !!!"
524 #else
525         Connection tmp = point1;
526         point1 = point2;
527         point2 = tmp;
528 //      double tmp = point1.t;
529 //      point1.t = point2.t;
530 //      point2.t = tmp;
531 //      Object * tmp = point1.object;
532 //      point1.object = point2.object;
533 //      point2.object = tmp;
534 #endif
535         needUpdate = true;
536 }
537