]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Fixed loading code, added "Base Unit" dialog.
[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                 Point p7 = p1 - (unit * 9.0 * size);
112                 Point p8 = p2 + (unit * 9.0 * size);
113                 painter->DrawArrowhead(p1, p7, size);
114                 painter->DrawArrowhead(p2, p8, size);
115                 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
116                 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
117         }
118
119         // Draw length of dimension line...
120         painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
121         Vector v1((p1.x - p2.x) / 2.0, (p1.y - p2.y) / 2.0);
122         Point ctr = p2 + v1;
123         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
124         painter->DrawAngledText(ctr, angle, dimText, size);
125 }
126
127
128 /*virtual*/ Vector Dimension::Center(void)
129 {
130         // Technically, this is the midpoint but who are we to quibble? :-)
131         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
132         return endpoint + v;
133 }
134
135
136 /*virtual*/ bool Dimension::Collided(Vector /*point*/)
137 {
138 #if 0
139         objectWasDragged = false;
140         Vector lineSegment = endpoint - position;
141         Vector v1 = point - position;
142         Vector v2 = point - endpoint;
143         double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
144
145         // Geometric interpretation:
146         // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
147         // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
148         // then the perpendicular lies beyond the 2nd endpoint.
149
150         if (parameterizedPoint < 0.0)
151                 distance = v1.Magnitude();
152         else if (parameterizedPoint > lineSegment.Magnitude())
153                 distance = v2.Magnitude();
154         else                                    // distance = ?Det?(ls, v1) / |ls|
155                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
156
157         // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
158         // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
159         // {e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
160         // computed by first computing the area of the triangle the three points form, then dividing by the
161         // length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
162         // triangle formed by three points is the determinant of the following matrix:
163         //
164         // sx sy 1
165         // ex ey 1
166         // px py 1
167         //
168         // By translating the start point to the origin, this can be rewritten as:
169         // By subtracting row 1 from all rows, you get the following:
170         // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
171         // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
172         //
173         // 0         0         0        0  0  0
174         // (ex - sx) (ey - sy) 0   ==>  ex ey 0
175         // (px - sx) (py - sy) 0        px py 0
176         //
177         // which greatly simplifies the calculation of the determinant.
178
179         if (state == OSInactive)
180         {
181 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
182 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
183 //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);
184 //printf("      \n", );
185 //How to translate this into pixels from Document space???
186 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
187 //the caller knows about the zoom factor and all that good kinda crap
188                 if (v1.Magnitude() < 10.0)
189                 {
190                         oldState = state;
191                         state = OSSelected;
192                         oldPoint = position; //maybe "position"?
193                         draggingHandle1 = true;
194                         return true;
195                 }
196                 else if (v2.Magnitude() < 10.0)
197                 {
198                         oldState = state;
199                         state = OSSelected;
200                         oldPoint = endpoint; //maybe "position"?
201                         draggingHandle2 = true;
202                         return true;
203                 }
204                 else if (distance < 2.0)
205                 {
206                         oldState = state;
207                         state = OSSelected;
208                         oldPoint = point;
209                         dragging = true;
210                         return true;
211                 }
212         }
213         else if (state == OSSelected)
214         {
215                 // Here we test for collision with handles as well! (SOON!)
216 /*
217 Like so:
218                 if (v1.Magnitude() < 2.0) // Handle #1
219                 else if (v2.Magnitude() < 2.0) // Handle #2
220 */
221                 if (distance < 2.0)
222                 {
223                         oldState = state;
224 //                      state = OSInactive;
225                         oldPoint = point;
226                         dragging = true;
227                         return true;
228                 }
229         }
230 #endif
231
232         state = OSInactive;
233         return false;
234 }
235
236
237 /*virtual*/ void Dimension::PointerMoved(Vector point)
238 {
239         // We know this is true because mouse move messages don't come here unless
240         // the object was actually clicked on--therefore we *know* we're being
241         // dragged...
242         objectWasDragged = true;
243
244         if (dragging)
245         {
246                 // Here we need to check whether or not we're dragging a handle or the object itself...
247                 Vector delta = point - oldPoint;
248
249                 position += delta;
250                 endpoint += delta;
251
252                 oldPoint = point;
253                 needUpdate = true;
254         }
255         else if (draggingHandle1)
256         {
257                 Vector delta = point - oldPoint;
258
259                 position += delta;
260
261                 oldPoint = point;
262                 needUpdate = true;
263         }
264         else if (draggingHandle2)
265         {
266                 Vector delta = point - oldPoint;
267
268                 endpoint += delta;
269
270                 oldPoint = point;
271                 needUpdate = true;
272         }
273         else
274                 needUpdate = false;
275 }
276
277
278 /*virtual*/ void Dimension::PointerReleased(void)
279 {
280         if (draggingHandle1 || draggingHandle2)
281         {
282                 // Set the length (in case the global state was set to fixed (or not))
283                 if (Object::fixedLength)
284                 {
285
286                         if (draggingHandle1)    // startpoint
287                         {
288                                 Vector v = Vector(position - endpoint).Unit() * length;
289                                 position = endpoint + v;
290                         }
291                         else                                    // endpoint
292                         {
293 //                              Vector v1 = endpoint - position;
294                                 Vector v = Vector(endpoint - position).Unit() * length;
295                                 endpoint = position + v;
296                         }
297                 }
298                 else
299                 {
300                         // Otherwise, we calculate the new length, just in case on the next move
301                         // it turns out to have a fixed length. :-)
302                         length = Vector(endpoint - position).Magnitude();
303                 }
304         }
305
306         dragging = false;
307         draggingHandle1 = false;
308         draggingHandle2 = false;
309
310         // Here we check for just a click: If object was clicked and dragged, then
311         // revert to the old state (OSInactive). Otherwise, keep the new state that
312         // we set.
313 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
314 about keeping track of old states...
315 */
316         if (objectWasDragged)
317                 state = oldState;
318 }
319
320
321 /*virtual*/ void Dimension::Enumerate(FILE * file)
322 {
323         fprintf(file, "DIMENSION (%lf,%lf) (%lf,%lf) %i\n", position.x, position.y, endpoint.x, endpoint.y, type);
324 }
325
326
327 /*virtual*/ Object * Dimension::Copy(void)
328 {
329 #warning "!!! This doesn't take care of attached Dimensions !!!"
330 /*
331 This is a real problem. While having a pointer in the Dimension to this line's points
332 is fast & easy, it creates a huge problem when trying to replicate an object like this.
333
334 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
335 way, if you copy them, ... you might still have problems. Because you can't be sure if
336 a copy will be persistant or not, you then *definitely* do not want them to have the
337 same reference number.
338 */
339
340         Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
341         d->size = size;
342         return d;
343 }
344
345
346 // Dimensions are special: they contain exactly *two* points. Here, we check
347 // only for zero/non-zero in returning the correct points.
348 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
349 {
350         if (parameter == 0)
351                 return position;
352
353         return endpoint;
354 }
355
356
357 /*virtual*/ void Dimension::Connect(Object * obj, double param)
358 {
359         // There are four possibilities here...
360         // The param is only looking for 0 or 1 here.
361         if (point1.object == NULL && point2.object == NULL)
362         {
363                 point1.object = obj;
364                 point1.t = param;
365         }
366         else if (point1.object == NULL && point2.object != NULL)
367         {
368                 if (point2.t == param)
369                         point2.object = obj;
370                 else
371                 {
372                         point1.object = obj;
373                         point1.t = param;
374                 }
375         }
376         else if (point1.object != NULL && point2.object == NULL)
377         {
378                 if (point1.t == param)
379                         point1.object = obj;
380                 else
381                 {
382                         point2.object = obj;
383                         point2.t = param;
384                 }
385         }
386         else if (point1.object != NULL && point2.object != NULL)
387         {
388                 if (point1.t == param)
389                         point1.object = obj;
390                 else
391                         point2.object = obj;
392         }
393 }
394
395
396 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
397 {
398         if (point1.object == obj && point1.t == param)
399                 point1.object = NULL;
400         else if (point2.object == obj && point2.t == param)
401                 point2.object = NULL;
402 }
403
404
405 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
406 {
407         if (point1.object == obj)
408                 point1.object = NULL;
409
410         if (point2.object == obj)
411                 point2.object = NULL;
412 }
413
414
415 /*virtual*/ QRectF Dimension::Extents(void)
416 {
417         Point p1 = position;
418         Point p2 = endpoint;
419
420         if (point1.object)
421                 p1 = point1.object->GetPointAtParameter(point1.t);
422
423         if (point2.object)
424                 p2 = point2.object->GetPointAtParameter(point2.t);
425
426         return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
427 }
428
429
430 #if 0
431 /*virtual*/ ObjectType Dimension::Type(void)
432 {
433         return OTDimension;
434 }
435 #endif
436
437
438 void Dimension::FlipSides(void)
439 {
440 #if 0
441         Vector tmp = position;
442         position = endpoint;
443         endpoint = tmp;
444 #else
445         Connection tmp = point1;
446         point1 = point2;
447         point2 = tmp;
448 //      double tmp = point1.t;
449 //      point1.t = point2.t;
450 //      point2.t = tmp;
451 //      Object * tmp = point1.object;
452 //      point1.object = point2.object;
453 //      point2.object = tmp;
454 #endif
455         needUpdate = true;
456 }
457