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