]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
c9777e50bd673688bc5d06dee646c6a4753cd19d
[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 there are valid Vector pointers in here, use them to update the internal
65         // positions. Otherwise, we just use the internal positions by default.
66         if (point1.object)
67                 position = point1.object->GetPointAtParameter(point1.t);
68
69         if (point2.object)
70                 endpoint = point2.object->GetPointAtParameter(point2.t);
71
72         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
73
74         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
75                 painter->DrawHandle(position);
76
77         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
78                 painter->DrawHandle(endpoint);
79 #if 0
80         if (state == OSSelected)
81                 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
82         else
83 //              painter->SetPen(QPen(Qt::blue, 1.0, Qt::SolidLine));
84 #endif
85                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
86
87         painter->SetBrush(QBrush(QColor(Qt::blue)));
88
89         // Draw an aligned dimension line
90         double angle = Vector(endpoint - position).Angle();
91         double orthoAngle = angle + (PI / 2.0);
92         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
93         Vector unit = Vector(endpoint - position).Unit();
94
95 // Arrowhead:
96 //      Point p1 = head - (unit * 9.0 * size);
97 //      Point p2 = p1 + (orthogonal * 3.0 * size);
98 //      Point p3 = p1 - (orthogonal * 3.0 * size);
99
100 /*
101 The numbers hardcoded into here, what are they?
102 I believe they are pixels.
103 */
104
105         // Get our line parallel to our points
106         Point p1 = position + (orthogonal * 10.0 * size);
107         Point p2 = endpoint + (orthogonal * 10.0 * size);
108
109         Point p3 = position + (orthogonal * 16.0 * size);
110         Point p4 = endpoint + (orthogonal * 16.0 * size);
111         Point p5 = position + (orthogonal * 4.0 * size);
112         Point p6 = endpoint + (orthogonal * 4.0 * size);
113
114         // Draw extension lines
115         painter->DrawLine(p3, p5);
116         painter->DrawLine(p4, p6);
117
118         // Calculate whether or not the arrowheads are too crowded to put inside
119         // the extension lines. 9.0 is the length of the arrowhead.
120 //      double t = Vector::Parameter(position, endpoint, endpoint - (unit * 9.0 * size));
121         double t = Geometry::ParameterOfLineAndPoint(position, endpoint, endpoint - (unit * 9.0 * size));
122 //printf("Dimension::Draw(): t = %lf\n", t);
123
124 // On the screen, it's acting like this is actually 58%...
125 // This is correct, we want it to happen at > 50%
126         if (t > 0.58)
127         {
128                 // Draw main dimension line + arrowheads
129                 painter->DrawLine(p1, p2);
130                 painter->DrawArrowhead(p1, p2, size);
131                 painter->DrawArrowhead(p2, p1, size);
132         }
133         else
134         {
135                 // Draw outside arrowheads
136                 Point p7 = p1 - (unit * 9.0 * size);
137                 Point p8 = p2 + (unit * 9.0 * size);
138                 painter->DrawArrowhead(p1, p7, size);
139                 painter->DrawArrowhead(p2, p8, size);
140                 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
141                 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
142         }
143
144         // Draw length of dimension line...
145         painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
146         Vector v1((p1.x - p2.x) / 2.0, (p1.y - p2.y) / 2.0);
147         Point ctr = p2 + v1;
148
149 #if 0
150         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
151 #else
152         QString dimText;
153         double length = Vector(endpoint - position).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
172
173 /*virtual*/ Vector Dimension::Center(void)
174 {
175         // Technically, this is the midpoint but who are we to quibble? :-)
176         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
177         return endpoint + v;
178 }
179
180
181 /*virtual*/ bool Dimension::Collided(Vector point)
182 {
183         // Someone told us to fuck off, so we'll fuck off. :-)
184         if (ignoreClicks)
185                 return false;
186
187         // We can assume this, since this is a mouse down event here.
188         objectWasDragged = false;
189         HitTest(point);
190
191         // Now that we've done our hit testing on the non-snapped point, snap it if
192         // necessary...
193         if (snapToGrid)
194                 point = SnapPointToGrid(point);
195
196         if (hitPoint1)
197         {
198                 oldState = state;
199                 state = OSSelected;
200                 oldPoint = position;
201                 draggingHandle1 = true;
202                 return true;
203         }
204         else if (hitPoint2)
205         {
206                 oldState = state;
207                 state = OSSelected;
208                 oldPoint = endpoint;
209                 draggingHandle2 = true;
210                 return true;
211         }
212
213         state = OSInactive;
214         return false;
215 }
216
217
218 /*virtual*/ void Dimension::PointerMoved(Vector point)
219 {
220 #if 0
221         // We know this is true because mouse move messages don't come here unless
222         // the object was actually clicked on--therefore we *know* we're being
223         // dragged...
224         objectWasDragged = true;
225
226 /*      if (dragging)
227         {
228                 // Here we need to check whether or not we're dragging a handle or the object itself...
229                 Vector delta = point - oldPoint;
230
231                 position += delta;
232                 endpoint += delta;
233
234                 oldPoint = point;
235                 needUpdate = true;
236         }
237         else*/ if (draggingHandle1)
238         {
239                 Vector delta = point - oldPoint;
240
241                 position += delta;
242
243                 oldPoint = point;
244                 needUpdate = true;
245         }
246         else if (draggingHandle2)
247         {
248                 Vector delta = point - oldPoint;
249
250                 endpoint += delta;
251
252                 oldPoint = point;
253                 needUpdate = true;
254         }
255         else
256                 needUpdate = false;
257 #else
258         // Hit test tells us what we hit (if anything) through boolean variables. (It
259         // also tells us whether or not the state changed. --not any more)
260         SaveHitState();
261         HitTest(point);
262         needUpdate = HitStateChanged();
263
264         objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
265
266         if (objectWasDragged)
267         {
268                 Vector delta = point - oldPoint;
269
270                 if (draggingHandle1)// || draggingLine)
271                         position += delta;
272
273                 if (draggingHandle2)// || draggingLine)
274                         endpoint += delta;
275
276                 oldPoint = point;
277                 needUpdate = true;
278         }
279 #endif
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         hitPoint1 = hitPoint2 = false;
328 //      Vector lineSegment(position, endpoint);
329         Vector v1(position, point);
330         Vector v2(endpoint, point);
331 //      double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
332 //      double distance;
333
334 //      if (t < 0.0)
335 //              distance = v1.Magnitude();
336 //      else if (t > 1.0)
337 //              distance = v2.Magnitude();
338 //      else
339                 // distance = ?Det?(ls, v1) / |ls|
340 //              distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
341 //                      / lineSegment.Magnitude());
342
343         if ((v1.Magnitude() * Painter::zoom) < 8.0)
344                 hitPoint1 = true;
345         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
346                 hitPoint2 = true;
347
348         return (hitPoint1 || hitPoint2 ? true : false);
349 }
350
351
352 void Dimension::SaveHitState(void)
353 {
354         oldHitPoint1 = hitPoint1;
355         oldHitPoint2 = hitPoint2;
356 //      oldHitLine = hitLine;
357 }
358
359
360 bool Dimension::HitStateChanged(void)
361 {
362         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2))
363                 return true;
364
365         return false;
366 }
367
368
369 /*virtual*/ void Dimension::Enumerate(FILE * file)
370 {
371         fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
372 }
373
374
375 /*virtual*/ Object * Dimension::Copy(void)
376 {
377 #warning "!!! This doesn't take care of attached Dimensions !!!"
378 /*
379 This is a real problem. While having a pointer in the Dimension to this line's points
380 is fast & easy, it creates a huge problem when trying to replicate an object like this.
381
382 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
383 way, if you copy them, ... you might still have problems. Because you can't be sure if
384 a copy will be persistant or not, you then *definitely* do not want them to have the
385 same reference number.
386 */
387
388         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
389         d->size = size;
390         return d;
391 }
392
393
394 // Dimensions are special: they contain exactly *two* points. Here, we check
395 // only for zero/non-zero in returning the correct points.
396 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
397 {
398         if (parameter == 0)
399                 return position;
400
401         return endpoint;
402 }
403
404
405 /*virtual*/ void Dimension::Connect(Object * obj, double param)
406 {
407         // There are four possibilities here...
408         // The param is only looking for 0 or 1 here.
409         if (point1.object == NULL && point2.object == NULL)
410         {
411                 point1.object = obj;
412                 point1.t = param;
413         }
414         else if (point1.object == NULL && point2.object != NULL)
415         {
416                 if (point2.t == param)
417                         point2.object = obj;
418                 else
419                 {
420                         point1.object = obj;
421                         point1.t = param;
422                 }
423         }
424         else if (point1.object != NULL && point2.object == NULL)
425         {
426                 if (point1.t == param)
427                         point1.object = obj;
428                 else
429                 {
430                         point2.object = obj;
431                         point2.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                         point2.object = obj;
440         }
441 }
442
443
444 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
445 {
446         if (point1.object == obj && point1.t == param)
447                 point1.object = NULL;
448         else if (point2.object == obj && point2.t == param)
449                 point2.object = NULL;
450 }
451
452
453 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
454 {
455         if (point1.object == obj)
456                 point1.object = NULL;
457
458         if (point2.object == obj)
459                 point2.object = NULL;
460 }
461
462
463 /*virtual*/ QRectF Dimension::Extents(void)
464 {
465         Point p1 = position;
466         Point p2 = endpoint;
467
468         if (point1.object)
469                 p1 = point1.object->GetPointAtParameter(point1.t);
470
471         if (point2.object)
472                 p2 = point2.object->GetPointAtParameter(point2.t);
473
474         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
475 }
476
477
478 #if 0
479 /*virtual*/ ObjectType Dimension::Type(void)
480 {
481         return OTDimension;
482 }
483 #endif
484
485
486 void Dimension::FlipSides(void)
487 {
488 #if 0
489         Vector tmp = position;
490         position = endpoint;
491         endpoint = tmp;
492 #else
493         Connection tmp = point1;
494         point1 = point2;
495         point2 = tmp;
496 //      double tmp = point1.t;
497 //      point1.t = point2.t;
498 //      point2.t = tmp;
499 //      Object * tmp = point1.object;
500 //      point1.object = point2.object;
501 //      point2.object = tmp;
502 #endif
503         needUpdate = true;
504 }
505