1 // dimension.cpp: Dimension object
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 04/04/2011 Created this file, basic rendering
12 // JLH 03/14/2013 Updated to new connection system
15 #include "dimension.h"
18 #include "mathconstants.h"
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)
27 // We set the size to 1/4 base unit. Could be anything.
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)
41 Dimension::~Dimension()
46 /*virtual*/ void Dimension::Draw(Painter * painter)
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.
51 position = point1.object->GetPointAtParameter(point1.t);
54 endpoint = point2.object->GetPointAtParameter(point2.t);
56 if (state == OSSelected)
57 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
59 // painter->SetPen(QPen(Qt::blue, 1.0, Qt::SolidLine));
60 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
62 painter->SetBrush(QBrush(QColor(Qt::blue)));
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();
71 // Point p1 = head - (unit * 9.0 * size);
72 // Point p2 = p1 + (orthogonal * 3.0 * size);
73 // Point p3 = p1 - (orthogonal * 3.0 * size);
76 The numbers hardcoded into here, what are they?
77 I believe they are pixels.
80 // Get our line parallel to our points
81 Point p1 = position + (orthogonal * 10.0 * size);
82 Point p2 = endpoint + (orthogonal * 10.0 * size);
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);
89 // Draw extension lines
90 painter->DrawLine(p3, p5);
91 painter->DrawLine(p4, p6);
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);
100 // On the screen, it's acting like this is actually 58%...
101 // This is correct, we want it to happen at > 50%
104 // Draw main dimension line + arrowheads
105 painter->DrawLine(p1, p2);
106 painter->DrawArrowhead(p1, p2, size);
107 painter->DrawArrowhead(p2, p1, size);
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));
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);
126 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
129 double length = Vector(endpoint - position).Magnitude();
132 dimText = QString("%1\"").arg(length);
135 double feet = (double)((int)length / 12);
136 double inches = length - (feet * 12.0);
139 dimText = QString("%1'").arg(feet);
141 dimText = QString("%1' %2\"").arg(feet).arg(inches);
145 painter->DrawAngledText(ctr, angle, dimText, size);
149 /*virtual*/ Vector Dimension::Center(void)
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);
157 /*virtual*/ bool Dimension::Collided(Vector /*point*/)
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;
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.
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());
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:
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!]
195 // (ex - sx) (ey - sy) 0 ==> ex ey 0
196 // (px - sx) (py - sy) 0 px py 0
198 // which greatly simplifies the calculation of the determinant.
200 if (state == OSInactive)
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);
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)
213 oldPoint = position; //maybe "position"?
214 draggingHandle1 = true;
217 else if (v2.Magnitude() < 10.0)
221 oldPoint = endpoint; //maybe "position"?
222 draggingHandle2 = true;
225 else if (distance < 2.0)
234 else if (state == OSSelected)
236 // Here we test for collision with handles as well! (SOON!)
239 if (v1.Magnitude() < 2.0) // Handle #1
240 else if (v2.Magnitude() < 2.0) // Handle #2
245 // state = OSInactive;
258 /*virtual*/ void Dimension::PointerMoved(Vector point)
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
263 objectWasDragged = true;
267 // Here we need to check whether or not we're dragging a handle or the object itself...
268 Vector delta = point - oldPoint;
276 else if (draggingHandle1)
278 Vector delta = point - oldPoint;
285 else if (draggingHandle2)
287 Vector delta = point - oldPoint;
299 /*virtual*/ void Dimension::PointerReleased(void)
301 if (draggingHandle1 || draggingHandle2)
303 // Set the length (in case the global state was set to fixed (or not))
304 if (Object::fixedLength)
307 if (draggingHandle1) // startpoint
309 Vector v = Vector(position - endpoint).Unit() * length;
310 position = endpoint + v;
314 // Vector v1 = endpoint - position;
315 Vector v = Vector(endpoint - position).Unit() * length;
316 endpoint = position + v;
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();
328 draggingHandle1 = false;
329 draggingHandle2 = false;
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
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...
337 if (objectWasDragged)
342 /*virtual*/ void Dimension::Enumerate(FILE * file)
344 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
348 /*virtual*/ Object * Dimension::Copy(void)
350 #warning "!!! This doesn't take care of attached Dimensions !!!"
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.
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.
361 Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
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)
378 /*virtual*/ void Dimension::Connect(Object * obj, double param)
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)
387 else if (point1.object == NULL && point2.object != NULL)
389 if (point2.t == param)
397 else if (point1.object != NULL && point2.object == NULL)
399 if (point1.t == param)
407 else if (point1.object != NULL && point2.object != NULL)
409 if (point1.t == param)
417 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
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;
426 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
428 if (point1.object == obj)
429 point1.object = NULL;
431 if (point2.object == obj)
432 point2.object = NULL;
436 /*virtual*/ QRectF Dimension::Extents(void)
442 p1 = point1.object->GetPointAtParameter(point1.t);
445 p2 = point2.object->GetPointAtParameter(point2.t);
447 return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
452 /*virtual*/ ObjectType Dimension::Type(void)
459 void Dimension::FlipSides(void)
462 Vector tmp = position;
466 Connection tmp = point1;
469 // double tmp = point1.t;
470 // point1.t = point2.t;
472 // Object * tmp = point1.object;
473 // point1.object = point2.object;
474 // point2.object = tmp;