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