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