]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Added placeholder icons for File menu, changed Dimension rendering.
[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 "mathconstants.h"
19 #include "painter.h"
20
21
22 Dimension::Dimension(Vector p1, Vector p2, DimensionType dt/*= DTLinear*/ ,Object * p/*= NULL*/):
23         Object(p1, p), endpoint(p2),
24         dragging(false), draggingHandle1(false), draggingHandle2(false),
25         length(p2.Magnitude()), dimensionType(dt), size(0.25), point1(NULL), point2(NULL)
26 {
27         // We set the size to 1/4 base unit. Could be anything.
28         type = OTDimension;
29 }
30
31
32 // This is bad, p1 & p2 could be NULL, causing much consternation...
33 Dimension::Dimension(Connection p1, Connection p2, DimensionType dt/*= DTLinear*/ , Object * p/*= NULL*/):
34         dragging(false), draggingHandle1(false), draggingHandle2(false),
35         length(0), dimensionType(dt), size(0.25), point1(p1), point2(p2)
36 {
37         type = OTDimension;
38 }
39
40
41 Dimension::~Dimension()
42 {
43 }
44
45
46 /*virtual*/ void Dimension::Draw(Painter * painter)
47 {
48         // If there are valid Vector pointers in here, use them to update the internal
49         // positions. Otherwise, we just use the internal positions by default.
50         if (point1.object)
51                 position = point1.object->GetPointAtParameter(point1.t);
52
53         if (point2.object)
54                 endpoint = point2.object->GetPointAtParameter(point2.t);
55
56         if (state == OSSelected)
57                 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
58         else
59 //              painter->SetPen(QPen(Qt::blue, 1.0, Qt::SolidLine));
60                 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
61
62         painter->SetBrush(QBrush(QColor(Qt::blue)));
63
64         // Draw an aligned dimension line
65         double angle = Vector(endpoint - position).Angle();
66         double orthoAngle = angle + (PI / 2.0);
67         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
68         Vector unit = Vector(endpoint - position).Unit();
69
70 // Arrowhead:
71 //      Point p1 = head - (unit * 9.0 * size);
72 //      Point p2 = p1 + (orthogonal * 3.0 * size);
73 //      Point p3 = p1 - (orthogonal * 3.0 * size);
74
75 /*
76 The numbers hardcoded into here, what are they?
77 I believe they are pixels.
78 */
79
80         // Get our line parallel to our points
81         Point p1 = position + (orthogonal * 10.0 * size);
82         Point p2 = endpoint + (orthogonal * 10.0 * size);
83
84         Point p3 = position + (orthogonal * 16.0 * size);
85         Point p4 = endpoint + (orthogonal * 16.0 * size);
86         Point p5 = position + (orthogonal * 4.0 * size);
87         Point p6 = endpoint + (orthogonal * 4.0 * size);
88
89         // Draw extension lines
90         painter->DrawLine(p3, p5);
91         painter->DrawLine(p4, p6);
92
93         // Calculate whether or not the arrowheads are too crowded to put inside
94         // the extension lines. 9.0 is the length of the arrowhead.
95 //      double t = Vector::Parameter(position, endpoint, endpoint - (unit * 9.0 * size));
96 //      double t = Vector::Parameter(position, endpoint, position + (unit * 9.0 * size));
97         double t = Vector::Parameter(endpoint, position, position + (unit * 9.0 * size));
98 //printf("Dimension::Draw(): t = %lf\n", t);
99
100 // On the screen, it's acting like this is actually 58%...
101 // This is correct, we want it to happen at > 50%
102         if (t > 0.58)
103         {
104                 // Draw main dimension line + arrowheads
105                 painter->DrawLine(p1, p2);
106                 painter->DrawArrowhead(p1, p2, size);
107                 painter->DrawArrowhead(p2, p1, size);
108         }
109         else
110         {
111                 // Draw outside arrowheads
112                 Point p7 = p1 - (unit * 9.0 * size);
113                 Point p8 = p2 + (unit * 9.0 * size);
114                 painter->DrawArrowhead(p1, p7, size);
115                 painter->DrawArrowhead(p2, p8, size);
116                 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
117                 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
118         }
119
120         // Draw length of dimension line...
121         painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
122         Vector v1((p1.x - p2.x) / 2.0, (p1.y - p2.y) / 2.0);
123         Point ctr = p2 + v1;
124
125 #if 0
126         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
127 #else
128         QString dimText;
129         double length = Vector(endpoint - position).Magnitude();
130
131         if (length < 12.0)
132                 dimText = QString("%1\"").arg(length);
133         else
134         {
135                 double feet = (double)((int)length / 12);
136                 double inches = length - (feet * 12.0);
137
138                 if (inches == 0)
139                         dimText = QString("%1'").arg(feet);
140                 else
141                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
142         }
143 #endif
144
145         painter->DrawAngledText(ctr, angle, dimText, size);
146 }
147
148
149 /*virtual*/ Vector Dimension::Center(void)
150 {
151         // Technically, this is the midpoint but who are we to quibble? :-)
152         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
153         return endpoint + v;
154 }
155
156
157 /*virtual*/ bool Dimension::Collided(Vector /*point*/)
158 {
159 #if 0
160         objectWasDragged = false;
161         Vector lineSegment = endpoint - position;
162         Vector v1 = point - position;
163         Vector v2 = point - endpoint;
164         double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
165
166         // Geometric interpretation:
167         // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
168         // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
169         // then the perpendicular lies beyond the 2nd endpoint.
170
171         if (parameterizedPoint < 0.0)
172                 distance = v1.Magnitude();
173         else if (parameterizedPoint > lineSegment.Magnitude())
174                 distance = v2.Magnitude();
175         else                                    // distance = ?Det?(ls, v1) / |ls|
176                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
177
178         // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
179         // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
180         // {e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
181         // computed by first computing the area of the triangle the three points form, then dividing by the
182         // length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
183         // triangle formed by three points is the determinant of the following matrix:
184         //
185         // sx sy 1
186         // ex ey 1
187         // px py 1
188         //
189         // By translating the start point to the origin, this can be rewritten as:
190         // By subtracting row 1 from all rows, you get the following:
191         // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
192         // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
193         //
194         // 0         0         0        0  0  0
195         // (ex - sx) (ey - sy) 0   ==>  ex ey 0
196         // (px - sx) (py - sy) 0        px py 0
197         //
198         // which greatly simplifies the calculation of the determinant.
199
200         if (state == OSInactive)
201         {
202 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
203 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
204 //printf("      point = %lf,%lf,%lf; p1 = %lf,%lf,%lf; p2 = %lf,%lf,%lf\n", point.x, point.y, point.z, position.x, position.y, position.z, endpoint.x, endpoint.y, endpoint.z);
205 //printf("      \n", );
206 //How to translate this into pixels from Document space???
207 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
208 //the caller knows about the zoom factor and all that good kinda crap
209                 if (v1.Magnitude() < 10.0)
210                 {
211                         oldState = state;
212                         state = OSSelected;
213                         oldPoint = position; //maybe "position"?
214                         draggingHandle1 = true;
215                         return true;
216                 }
217                 else if (v2.Magnitude() < 10.0)
218                 {
219                         oldState = state;
220                         state = OSSelected;
221                         oldPoint = endpoint; //maybe "position"?
222                         draggingHandle2 = true;
223                         return true;
224                 }
225                 else if (distance < 2.0)
226                 {
227                         oldState = state;
228                         state = OSSelected;
229                         oldPoint = point;
230                         dragging = true;
231                         return true;
232                 }
233         }
234         else if (state == OSSelected)
235         {
236                 // Here we test for collision with handles as well! (SOON!)
237 /*
238 Like so:
239                 if (v1.Magnitude() < 2.0) // Handle #1
240                 else if (v2.Magnitude() < 2.0) // Handle #2
241 */
242                 if (distance < 2.0)
243                 {
244                         oldState = state;
245 //                      state = OSInactive;
246                         oldPoint = point;
247                         dragging = true;
248                         return true;
249                 }
250         }
251 #endif
252
253         state = OSInactive;
254         return false;
255 }
256
257
258 /*virtual*/ void Dimension::PointerMoved(Vector point)
259 {
260         // We know this is true because mouse move messages don't come here unless
261         // the object was actually clicked on--therefore we *know* we're being
262         // dragged...
263         objectWasDragged = true;
264
265         if (dragging)
266         {
267                 // Here we need to check whether or not we're dragging a handle or the object itself...
268                 Vector delta = point - oldPoint;
269
270                 position += delta;
271                 endpoint += delta;
272
273                 oldPoint = point;
274                 needUpdate = true;
275         }
276         else if (draggingHandle1)
277         {
278                 Vector delta = point - oldPoint;
279
280                 position += delta;
281
282                 oldPoint = point;
283                 needUpdate = true;
284         }
285         else if (draggingHandle2)
286         {
287                 Vector delta = point - oldPoint;
288
289                 endpoint += delta;
290
291                 oldPoint = point;
292                 needUpdate = true;
293         }
294         else
295                 needUpdate = false;
296 }
297
298
299 /*virtual*/ void Dimension::PointerReleased(void)
300 {
301         if (draggingHandle1 || draggingHandle2)
302         {
303                 // Set the length (in case the global state was set to fixed (or not))
304                 if (Object::fixedLength)
305                 {
306
307                         if (draggingHandle1)    // startpoint
308                         {
309                                 Vector v = Vector(position - endpoint).Unit() * length;
310                                 position = endpoint + v;
311                         }
312                         else                                    // endpoint
313                         {
314 //                              Vector v1 = endpoint - position;
315                                 Vector v = Vector(endpoint - position).Unit() * length;
316                                 endpoint = position + v;
317                         }
318                 }
319                 else
320                 {
321                         // Otherwise, we calculate the new length, just in case on the next move
322                         // it turns out to have a fixed length. :-)
323                         length = Vector(endpoint - position).Magnitude();
324                 }
325         }
326
327         dragging = false;
328         draggingHandle1 = false;
329         draggingHandle2 = false;
330
331         // Here we check for just a click: If object was clicked and dragged, then
332         // revert to the old state (OSInactive). Otherwise, keep the new state that
333         // we set.
334 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
335 about keeping track of old states...
336 */
337         if (objectWasDragged)
338                 state = oldState;
339 }
340
341
342 /*virtual*/ void Dimension::Enumerate(FILE * file)
343 {
344         fprintf(file, "DIMENSION (%lf,%lf) (%lf,%lf) %i\n", position.x, position.y, endpoint.x, endpoint.y, type);
345 }
346
347
348 /*virtual*/ Object * Dimension::Copy(void)
349 {
350 #warning "!!! This doesn't take care of attached Dimensions !!!"
351 /*
352 This is a real problem. While having a pointer in the Dimension to this line's points
353 is fast & easy, it creates a huge problem when trying to replicate an object like this.
354
355 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
356 way, if you copy them, ... you might still have problems. Because you can't be sure if
357 a copy will be persistant or not, you then *definitely* do not want them to have the
358 same reference number.
359 */
360
361         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
362         d->size = size;
363         return d;
364 }
365
366
367 // Dimensions are special: they contain exactly *two* points. Here, we check
368 // only for zero/non-zero in returning the correct points.
369 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
370 {
371         if (parameter == 0)
372                 return position;
373
374         return endpoint;
375 }
376
377
378 /*virtual*/ void Dimension::Connect(Object * obj, double param)
379 {
380         // There are four possibilities here...
381         // The param is only looking for 0 or 1 here.
382         if (point1.object == NULL && point2.object == NULL)
383         {
384                 point1.object = obj;
385                 point1.t = param;
386         }
387         else if (point1.object == NULL && point2.object != NULL)
388         {
389                 if (point2.t == param)
390                         point2.object = obj;
391                 else
392                 {
393                         point1.object = obj;
394                         point1.t = param;
395                 }
396         }
397         else if (point1.object != NULL && point2.object == NULL)
398         {
399                 if (point1.t == param)
400                         point1.object = obj;
401                 else
402                 {
403                         point2.object = obj;
404                         point2.t = param;
405                 }
406         }
407         else if (point1.object != NULL && point2.object != NULL)
408         {
409                 if (point1.t == param)
410                         point1.object = obj;
411                 else
412                         point2.object = obj;
413         }
414 }
415
416
417 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
418 {
419         if (point1.object == obj && point1.t == param)
420                 point1.object = NULL;
421         else if (point2.object == obj && point2.t == param)
422                 point2.object = NULL;
423 }
424
425
426 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
427 {
428         if (point1.object == obj)
429                 point1.object = NULL;
430
431         if (point2.object == obj)
432                 point2.object = NULL;
433 }
434
435
436 /*virtual*/ QRectF Dimension::Extents(void)
437 {
438         Point p1 = position;
439         Point p2 = endpoint;
440
441         if (point1.object)
442                 p1 = point1.object->GetPointAtParameter(point1.t);
443
444         if (point2.object)
445                 p2 = point2.object->GetPointAtParameter(point2.t);
446
447         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
448 }
449
450
451 #if 0
452 /*virtual*/ ObjectType Dimension::Type(void)
453 {
454         return OTDimension;
455 }
456 #endif
457
458
459 void Dimension::FlipSides(void)
460 {
461 #if 0
462         Vector tmp = position;
463         position = endpoint;
464         endpoint = tmp;
465 #else
466         Connection tmp = point1;
467         point1 = point2;
468         point2 = tmp;
469 //      double tmp = point1.t;
470 //      point1.t = point2.t;
471 //      point2.t = tmp;
472 //      Object * tmp = point1.object;
473 //      point1.object = point2.object;
474 //      point2.object = tmp;
475 #endif
476         needUpdate = true;
477 }
478