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 ;-)
16 // JLH 05/29/2011 Added mouseover hints
22 #include "dimension.h"
24 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
25 dragging(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
26 length(Vector::Magnitude(p2, p1)), hitPoint1(false), hitPoint2(false), hitLine(false)
34 /*virtual*/ void Line::Draw(QPainter * painter)
36 painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
38 if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
39 painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
41 if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
42 painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
44 if ((state == OSInactive) && !hitLine)
45 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
47 if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
49 Vector point1 = (draggingHandle1 ? endpoint : position);
50 Vector point2 = (draggingHandle1 ? position : endpoint);
52 Vector current(point2 - point1);
53 Vector v = current.Unit() * length;
54 Vector v2 = point1 + v;
55 painter->drawLine((int)point1.x, (int)point1.y, (int)v2.x, (int)v2.y);
57 if (current.Magnitude() > length)
59 painter->setPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
60 painter->drawLine((int)v2.x, (int)v2.y, (int)point2.x, (int)point2.y);
64 painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
67 /*virtual*/ Vector Line::Center(void)
69 // Technically, this is the midpoint but who are we to quibble? :-)
70 Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
74 /*virtual*/ bool Line::Collided(Vector point)
77 // Actually, we can, since this is a mouse down event here.
78 objectWasDragged = false;
82 There's a small problem here with the implementation: You can have a dimension tied
83 to only one point while at the same time you can have a dimension sitting on this line.
84 Since there's only *one* dimPoint for each point, this can be problematic...
86 Also: It would be nice to have a preview of the dimension being drawn, with a modifier
87 key to make it draw/show on the other side...
89 // Is the dimension tool active? Let's use it:
92 // User clicked on the line itself (endpoint checks should preceed this one):
93 // (Priorities are taken care of in HitTest()...)
96 // If there's one already there, tell it to flip sides...
97 if (dimPoint1 && (dimPoint1 == dimPoint2))
99 // Here's an interesting problem... When swapping points we can either do it
100 // on the dimension or directly here... I think swapping endpoint & position is
101 // probably the wrong way to do things here...
103 // Hm, why do we have to do both here???
104 dimPoint1->FlipSides();
106 /* Vector temp = dimPoint1;
107 dimPoint1 = dimPoint2;
108 dimPoint2 = temp;//*/
110 // It doesn't work correctly anyhow, not until you move an endpoint.
111 Vector temp = position;
116 else if ((dimPoint1 == NULL) && (dimPoint2 == NULL))
118 // How to get this object into the top level container???
120 The real question is do we care. I think so, because if this isn't in the top
121 level container, it won't get drawn...
122 But we can fix that by making this object call any attached object's (like
123 a dimension only) Draw() function... :-/
125 dimPoint1 = new Dimension(position, endpoint, this);
126 dimPoint2 = dimPoint1;
129 parent->Add(dimPoint1);
137 if (state == OSInactive)
139 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
140 //printf(" v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
141 //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);
143 //How to translate this into pixels from Document space???
144 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
145 //the caller knows about the zoom factor and all that good kinda crap
150 oldPoint = position; //maybe "position"?
151 draggingHandle1 = true;
158 oldPoint = endpoint; //maybe "position"?
159 draggingHandle2 = true;
171 else if (state == OSSelected)
173 // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
176 if (v1.Magnitude() < 2.0) // Handle #1
177 else if (v2.Magnitude() < 2.0) // Handle #2
182 // state = OSInactive;
193 /*virtual*/ void Line::PointerMoved(Vector point)
195 needUpdate = HitTest(point);
197 objectWasDragged = (dragging | draggingHandle1 | draggingHandle2);
199 if (objectWasDragged)
201 Vector delta = point - oldPoint;
203 if (draggingHandle1 || dragging)
206 if (draggingHandle2 || dragging)
214 We can't count on any coupling between the dimension object, so how do we do this???
218 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
219 Vector point1 = (draggingHandle1 ? endpoint : position);
220 Vector point2 = (draggingHandle1 ? position : endpoint);
222 Vector current(point2, point1);
223 Vector v = current.Unit() * length;
224 Vector v2 = point1 + v;
227 if (!Object::fixedLength)
230 //If we tell the dimension to flip sides, this is no longer a valid
231 //assumption. !!! FIX !!!
232 //Ideally, we should just send the point that's changing to the Dimension object
233 //and have it figure out which point needs to move... Or is it???
235 dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
238 dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
242 /*virtual*/ void Line::PointerReleased(void)
244 if (draggingHandle1 || draggingHandle2)
246 // Set the length (in case the global state was set to fixed (or not))
247 if (Object::fixedLength)
249 if (draggingHandle1) // startpoint
251 Vector v = Vector(position - endpoint).Unit() * length;
252 position = endpoint + v;
256 // Vector v1 = endpoint - position;
257 Vector v = Vector(endpoint - position).Unit() * length;
258 endpoint = position + v;
263 // Otherwise, we calculate the new length, just in case on the next move
264 // it turns out to have a fixed length. :-)
265 length = Vector(endpoint - position).Magnitude();
270 draggingHandle1 = false;
271 draggingHandle2 = false;
273 // hitPoint1 = hitPoint2 = hitLine = false;
275 // Here we check for just a click: If object was clicked and dragged, then
276 // revert to the old state (OSInactive). Otherwise, keep the new state that
278 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
279 about keeping track of old states...
281 if (objectWasDragged)
285 void Line::SetDimensionOnPoint1(Dimension * dimension)
287 dimPoint1 = dimension;
290 dimension->SetPoint1(position);
293 void Line::SetDimensionOnPoint2(Dimension * dimension)
295 dimPoint2 = dimension;
298 dimension->SetPoint2(endpoint);
301 bool Line::HitTest(Point point)
305 hitPoint1 = hitPoint2 = hitLine = false;
306 Vector lineSegment = endpoint - position;
307 Vector v1 = point - position;
308 Vector v2 = point - endpoint;
309 double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
311 // Geometric interpretation:
312 // The parameterized point on the vector lineSegment is where the perpendicular
313 // intersects lineSegment. If pp < 0, then the perpendicular lies beyond the 1st
314 // endpoint. If pp > length of ls, then the perpendicular lies beyond the 2nd endpoint.
316 if (parameterizedPoint < 0.0)
317 distance = v1.Magnitude();
318 else if (parameterizedPoint > lineSegment.Magnitude())
319 distance = v2.Magnitude();
321 // distance = ?Det?(ls, v1) / |ls|
322 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
324 // Geometric interpretation of the above:
325 // If the segment endpoints are s and e, and the point is p, then the test
326 // for the perpendicular intercepting the segment is equivalent to insisting
327 // that the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
328 // Perpendicular distance from the point to the segment is computed by first
329 // computing the area of the triangle the three points form, then dividing by
330 // the length of the segment. Distances are done just by the Pythagorean
331 // theorem. Twice the area of the triangle formed by three points is the
332 // determinant of the following matrix:
334 // sx sy 1 0 0 1 0 0 0
335 // ex ey 1 ==> ex ey 1 ==> ex ey 0
336 // px py 1 px py 1 px py 0
338 // By translating the start point to the origin, and subtracting row 1 from
339 // all other rows, we end up with the matrix on the right which greatly
340 // simplifies the calculation of the determinant.
342 //How do we determine distance here? Especially if zoomed in or out???
343 #warning "!!! Distances tested for may not be valid if zoomed in or out !!!"
344 if (v1.Magnitude() < 8.0)
346 else if (v2.Magnitude() < 8.0)
348 else if (distance < 5.0)
351 return StateChanged();
354 void Line::SaveState(void)
356 oldHitPoint1 = hitPoint1;
357 oldHitPoint2 = hitPoint2;
358 oldHitLine = hitLine;
361 bool Line::StateChanged(void)
363 if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
370 Intersection of two lines:
372 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
374 When they intersect, we can set the equations equal to one another:
376 i + j + t (3i - j) = -i + s (j)
378 Equating coefficients:
379 1 + 3t = -1 and 1 - t = s
380 So t = -2/3 and s = 5/3
382 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 .
385 so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
386 and v2(p2x, p2y), v3(p3x, p3y) for l2.
388 d1 = v1 - v0, d2 = v3 - v2
390 Our parametric equations for the line then are:
395 Set r1 = r2, thus we have:
397 v0 + t(d1) = v2 + s(d2)
399 Taking coefficients, we have:
401 p0x + t(d1x) = p2x + s(d2x)
402 p0y + t(d1y) = p2y + s(d2y)
406 t(d1x) - s(d2x) = p2x - p0x
407 t(d1y) - s(d2y) = p2y - p0y
409 Determinant D is ad - bc where the matrix look like:
414 so D = (d1x)(d2y) - (d2x)(d1y)
415 if D = 0, the lines are parallel.
416 Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
417 Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
420 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
422 ---------------------------------------------------------------------------------------------------
424 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:
430 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
437 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
438 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
441 crossing vector = v1 * ratio;
445 -----------------------------------
447 So... to code this, let's say we have two Lines: l1 & l2.
449 Vector v1 = l1.endpoint - l1.position;
450 Vector v2 = l2.endpoint - l2.position;
453 Vector normal1(-v1.y, v1.x);
454 Vector normal3(-v3.y, v3.x);
456 double dotProduct1 = v2.Dot(normal1);
457 double dotProduct2 = v2.Dot(normal3);
459 if (dotProduct2 == 0)
460 return ParallelLines;
463 // I think we'd still have to add the intersection to the position point to get the intersection...
464 Point intersection = v1 * (dotProduct1 / dotProduct2);