1 // line.cpp: Line 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 L. Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 03/22/2011 Created this file
12 // JLH 04/11/2011 Fixed attached dimensions to stay at correct length when
13 // "Fixed Length" button is down
14 // JLH 04/27/2011 Fixed attached dimension to stay a correct length when
15 // "Fixed Length" button is *not* down ;-)
21 #include "dimension.h"
23 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
24 dragging(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
25 length(Vector::Magnitude(p2, p1))
33 /*virtual*/ void Line::Draw(QPainter * painter)
35 if (state == OSSelected)
36 painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
38 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
40 // if (draggingHandle1)
41 if (state == OSSelected)
42 painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
44 // if (draggingHandle2)
45 if (state == OSSelected)
46 painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
48 if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
50 Vector point1 = (draggingHandle1 ? endpoint : position);
51 Vector point2 = (draggingHandle1 ? position : endpoint);
53 Vector current(point2 - point1);
54 Vector v = current.Unit() * length;
55 Vector v2 = point1 + v;
56 painter->drawLine((int)point1.x, (int)point1.y, (int)v2.x, (int)v2.y);
58 if (current.Magnitude() > length)
60 painter->setPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
61 painter->drawLine((int)v2.x, (int)v2.y, (int)point2.x, (int)point2.y);
65 painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
68 /*virtual*/ Vector Line::Center(void)
70 // Technically, this is the midpoint but who are we to quibble? :-)
71 Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
75 /*virtual*/ bool Line::Collided(Vector point)
77 objectWasDragged = false;
78 Vector lineSegment = endpoint - position;
79 Vector v1 = point - position;
80 Vector v2 = point - endpoint;
81 double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
83 // Geometric interpretation:
84 // The paremeterized point on the vector ls is where the perpendicular intersects ls.
85 // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
86 // then the perpendicular lies beyond the 2nd endpoint.
88 if (parameterizedPoint < 0.0)
89 distance = v1.Magnitude();
90 else if (parameterizedPoint > lineSegment.Magnitude())
91 distance = v2.Magnitude();
92 else // distance = ?Det?(ls, v1) / |ls|
93 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
95 // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
96 // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
97 // {e-s}.{e-p} are both non-negative. Perpendicular distance from the point to the segment is
98 // computed by first computing the area of the triangle the three points form, then dividing by the
99 // length of the segment. Distances are done just by the Pythagorean theorem. Twice the area of the
100 // triangle formed by three points is the determinant of the following matrix:
106 // By translating the start point to the origin, this can be rewritten as:
107 // By subtracting row 1 from all rows, you get the following:
108 // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
109 // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
112 // (ex - sx) (ey - sy) 0 ==> ex ey 0
113 // (px - sx) (py - sy) 0 px py 0
115 // which greatly simplifies the calculation of the determinant.
117 if (state == OSInactive)
119 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
120 //printf(" v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
121 //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);
123 //How to translate this into pixels from Document space???
124 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
125 //the caller knows about the zoom factor and all that good kinda crap
126 if (v1.Magnitude() < 10.0)
130 oldPoint = position; //maybe "position"?
131 draggingHandle1 = true;
134 else if (v2.Magnitude() < 10.0)
138 oldPoint = endpoint; //maybe "position"?
139 draggingHandle2 = true;
142 else if (distance < 2.0)
151 else if (state == OSSelected)
153 // Here we test for collision with handles as well! (SOON!)
156 if (v1.Magnitude() < 2.0) // Handle #1
157 else if (v2.Magnitude() < 2.0) // Handle #2
162 // state = OSInactive;
173 /*virtual*/ void Line::PointerMoved(Vector point)
175 // We know this is true because mouse move messages don't come here unless
176 // the object was actually clicked on--therefore we *know* we're being
178 objectWasDragged = true;
182 // Here we need to check whether or not we're dragging a handle or the object itself...
183 Vector delta = point - oldPoint;
191 else if (draggingHandle1)
193 Vector delta = point - oldPoint;
200 else if (draggingHandle2)
202 Vector delta = point - oldPoint;
214 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
215 Vector point1 = (draggingHandle1 ? endpoint : position);
216 Vector point2 = (draggingHandle1 ? position : endpoint);
218 Vector current(point2, point1);
219 Vector v = current.Unit() * length;
220 Vector v2 = point1 + v;
223 if (!Object::fixedLength)
227 dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
230 dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
234 /*virtual*/ void Line::PointerReleased(void)
236 if (draggingHandle1 || draggingHandle2)
238 // Set the length (in case the global state was set to fixed (or not))
239 if (Object::fixedLength)
241 if (draggingHandle1) // startpoint
243 Vector v = Vector(position - endpoint).Unit() * length;
244 position = endpoint + v;
248 // Vector v1 = endpoint - position;
249 Vector v = Vector(endpoint - position).Unit() * length;
250 endpoint = position + v;
255 // Otherwise, we calculate the new length, just in case on the next move
256 // it turns out to have a fixed length. :-)
257 length = Vector(endpoint - position).Magnitude();
262 draggingHandle1 = false;
263 draggingHandle2 = false;
265 // Here we check for just a click: If object was clicked and dragged, then
266 // revert to the old state (OSInactive). Otherwise, keep the new state that
268 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
269 about keeping track of old states...
271 if (objectWasDragged)
275 void Line::SetDimensionOnPoint1(Dimension * dimension)
277 dimPoint1 = dimension;
280 dimension->SetPoint1(position);
283 void Line::SetDimensionOnPoint2(Dimension * dimension)
285 dimPoint2 = dimension;
288 dimension->SetPoint2(endpoint);
292 Intersection of two lines:
294 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
296 When they intersect, we can set the equations equal to one another:
298 i + j + t (3i - j) = -i + s (j)
300 Equating coefficients:
301 1 + 3t = -1 and 1 - t = s
302 So t = -2/3 and s = 5/3
304 The position vector of the intersection point is therefore given by putting t = -2/3 or s = 5/3 into one of the above equations. This gives -i +5j/3 .
307 so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
308 and v2(p2x, p2y), v3(p3x, p3y) for l2.
310 d1 = v1 - v0, d2 = v3 - v2
312 Our parametric equations for the line then are:
317 Set r1 = r2, thus we have:
319 v0 + t(d1) = v2 + s(d2)
321 Taking coefficients, we have:
323 p0x + t(d1x) = p2x + s(d2x)
324 p0y + t(d1y) = p2y + s(d2y)
328 t(d1x) - s(d2x) = p2x - p0x
329 t(d1y) - s(d2y) = p2y - p0y
331 Determinant D is ad - bc where the matrix look like:
336 so D = (d1x)(d2y) - (d2x)(d1y)
337 if D = 0, the lines are parallel.
338 Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
339 Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
342 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
344 ---------------------------------------------------------------------------------------------------
346 The first and most preferred method for intersection calculation is the perp-product calculation. There are two vectors, v1 and v2. Create a third vector vector between the starting points of these vectors, and calculate the perp product of v2 and the two other vectors. These two scalars have to be divided to get the mulitplication ratio of v1 to reach intersection point. So:
352 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
359 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
360 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
363 crossing vector = v1 * ratio;
367 -----------------------------------
369 So... to code this, let's say we have two Lines: l1 & l2.
371 Vector v1 = l1.endpoint - l1.position;
372 Vector v2 = l2.endpoint - l2.position;
375 Vector normal1(-v1.y, v1.x);
376 Vector normal3(-v3.y, v3.x);
378 double dotProduct1 = v2.Dot(normal1);
379 double dotProduct2 = v2.Dot(normal3);
381 if (dotProduct2 == 0)
382 return ParallelLines;
385 // I think we'd still have to add the intersection to the position point to get the intersection...
386 Point intersection = v1 * (dotProduct1 / dotProduct2);