]> Shamusworld >> Repos - architektonas/blob - src/line.cpp
c40277da748b75b36ccec9c10b70dc2e740aa695
[architektonas] / src / line.cpp
1 // line.cpp: Line 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 L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
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
17 //
18
19 #include "line.h"
20
21 #include <QtGui>
22 #include "dimension.h"
23
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)
27 {
28 }
29
30 Line::~Line()
31 {
32 }
33
34 /*virtual*/ void Line::Draw(QPainter * painter)
35 {
36         painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
37
38         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
39                 painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
40
41         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
42                 painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
43
44         if ((state == OSInactive) && !hitLine)
45                 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
46
47         if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
48         {
49                 Vector point1 = (draggingHandle1 ? endpoint : position);
50                 Vector point2 = (draggingHandle1 ? position : endpoint);
51
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);
56
57                 if (current.Magnitude() > length)
58                 {
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);
61                 }
62         }
63         else
64                 painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
65 }
66
67 /*virtual*/ Vector Line::Center(void)
68 {
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);
71         return endpoint + v;
72 }
73
74 /*virtual*/ bool Line::Collided(Vector point)
75 {
76 // Can't assume this!
77 // Actually, we can, since this is a mouse down event here.
78         objectWasDragged = false;
79         HitTest(point);
80
81 /*
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...
85
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...
88 */
89         // Is the dimension tool active? Let's use it:
90         if (dimensionActive)
91         {
92                 // User clicked on the line itself (endpoint checks should preceed this one):
93                 // (Priorities are taken care of in HitTest()...)
94                 if (hitLine)
95                 {
96                         // If there's one already there, tell it to flip sides...
97                         if (dimPoint1 && (dimPoint1 == dimPoint2))
98                         {
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...
102 #if 1
103                                 // Hm, why do we have to do both here???
104                                 dimPoint1->FlipSides();
105                                 //dis don't do shit
106 /*                              Vector temp = dimPoint1;
107                                 dimPoint1 = dimPoint2;
108                                 dimPoint2 = temp;//*/
109 #else
110 // It doesn't work correctly anyhow, not until you move an endpoint.
111                                 Vector temp = position;
112                                 position = endpoint;
113                                 endpoint = temp;
114 #endif
115                         }
116                         else if ((dimPoint1 == NULL) && (dimPoint2 == NULL))
117                         {
118                                 // How to get this object into the top level container???
119 /*
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... :-/
124 */
125                                 dimPoint1 = new Dimension(position, endpoint, this);
126                                 dimPoint2 = dimPoint1;
127
128                                 if (parent != NULL)
129                                         parent->Add(dimPoint1);
130                         }
131
132                         return true;
133                 }
134         }
135
136
137         if (state == OSInactive)
138         {
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);
142 //printf("      \n", );
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
146                 if (hitPoint1)
147                 {
148                         oldState = state;
149                         state = OSSelected;
150                         oldPoint = position; //maybe "position"?
151                         draggingHandle1 = true;
152                         return true;
153                 }
154                 else if (hitPoint2)
155                 {
156                         oldState = state;
157                         state = OSSelected;
158                         oldPoint = endpoint; //maybe "position"?
159                         draggingHandle2 = true;
160                         return true;
161                 }
162                 else if (hitLine)
163                 {
164                         oldState = state;
165                         state = OSSelected;
166                         oldPoint = point;
167                         dragging = true;
168                         return true;
169                 }
170         }
171         else if (state == OSSelected)
172         {
173                 // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
174 /*
175 Like so:
176                 if (v1.Magnitude() < 2.0) // Handle #1
177                 else if (v2.Magnitude() < 2.0) // Handle #2
178 */
179                 if (hitLine)
180                 {
181                         oldState = state;
182 //                      state = OSInactive;
183                         oldPoint = point;
184                         dragging = true;
185                         return true;
186                 }
187         }
188
189         state = OSInactive;
190         return false;
191 }
192
193 /*virtual*/ void Line::PointerMoved(Vector point)
194 {
195         needUpdate = HitTest(point);
196
197         objectWasDragged = (dragging | draggingHandle1 | draggingHandle2);
198
199         if (objectWasDragged)
200         {
201                 Vector delta = point - oldPoint;
202
203                 if (draggingHandle1 || dragging)
204                         position += delta;
205
206                 if (draggingHandle2 || dragging)
207                         endpoint += delta;
208
209                 oldPoint = point;
210                 needUpdate = true;
211         }
212
213 /*
214 We can't count on any coupling between the dimension object, so how do we do this???
215 */
216         if (needUpdate)
217         {
218 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
219                 Vector point1 = (draggingHandle1 ? endpoint : position);
220                 Vector point2 = (draggingHandle1 ? position : endpoint);
221
222                 Vector current(point2, point1);
223                 Vector v = current.Unit() * length;
224                 Vector v2 = point1 + v;
225
226                 //bleh
227                 if (!Object::fixedLength)
228                         v2 = point2;
229
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???
234                 if (dimPoint1)
235                         dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
236                 
237                 if (dimPoint2)
238                         dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
239         }
240 }
241
242 /*virtual*/ void Line::PointerReleased(void)
243 {
244         if (draggingHandle1 || draggingHandle2)
245         {
246                 // Set the length (in case the global state was set to fixed (or not))
247                 if (Object::fixedLength)
248                 {
249                         if (draggingHandle1)    // startpoint
250                         {
251                                 Vector v = Vector(position - endpoint).Unit() * length;
252                                 position = endpoint + v;
253                         }
254                         else                                    // endpoint
255                         {
256 //                              Vector v1 = endpoint - position;
257                                 Vector v = Vector(endpoint - position).Unit() * length;
258                                 endpoint = position + v;
259                         }
260                 }
261                 else
262                 {
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();
266                 }
267         }
268
269         dragging = false;
270         draggingHandle1 = false;
271         draggingHandle2 = false;
272
273 //      hitPoint1 = hitPoint2 = hitLine = false;
274
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
277         // we set.
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...
280 */
281         if (objectWasDragged)
282                 state = oldState;
283 }
284
285 void Line::SetDimensionOnPoint1(Dimension * dimension)
286 {
287         dimPoint1 = dimension;
288
289         if (dimension)
290                 dimension->SetPoint1(position);
291 }
292
293 void Line::SetDimensionOnPoint2(Dimension * dimension)
294 {
295         dimPoint2 = dimension;
296
297         if (dimension)
298                 dimension->SetPoint2(endpoint);
299 }
300
301 bool Line::HitTest(Point point)
302 {
303         SaveState();
304
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;
310
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.
315
316         if (parameterizedPoint < 0.0)
317                 distance = v1.Magnitude();
318         else if (parameterizedPoint > lineSegment.Magnitude())
319                 distance = v2.Magnitude();
320         else
321                 // distance = ?Det?(ls, v1) / |ls|
322                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
323
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:
333         //
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
337         //
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.
341
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)
345                 hitPoint1 = true;
346         else if (v2.Magnitude() < 8.0)
347                 hitPoint2 = true;
348         else if (distance < 5.0)
349                 hitLine = true;
350
351         return StateChanged();
352 }
353
354 void Line::SaveState(void)
355 {
356         oldHitPoint1 = hitPoint1;
357         oldHitPoint2 = hitPoint2;
358         oldHitLine = hitLine;
359 }
360
361 bool Line::StateChanged(void)
362 {
363         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
364                 return true;
365
366         return false;
367 }
368
369 /*
370 Intersection of two lines:
371
372 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
373
374 When they intersect, we can set the equations equal to one another:
375
376 i + j + t (3i - j) = -i + s (j)
377
378 Equating coefficients:
379 1 + 3t = -1 and 1 - t = s
380 So t = -2/3 and s = 5/3
381
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 .
383
384
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.
387
388 d1 = v1 - v0, d2 = v3 - v2
389
390 Our parametric equations for the line then are:
391
392 r1 = v0 + t(d1)
393 r2 = v2 + s(d2)
394
395 Set r1 = r2, thus we have:
396
397 v0 + t(d1) = v2 + s(d2)
398
399 Taking coefficients, we have:
400
401 p0x + t(d1x) = p2x + s(d2x)
402 p0y + t(d1y) = p2y + s(d2y)
403
404 rearranging we get:
405
406 t(d1x) - s(d2x) = p2x - p0x
407 t(d1y) - s(d2y) = p2y - p0y
408
409 Determinant D is ad - bc where the matrix look like:
410
411 a b
412 c d
413
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)
418 t = Dx/D, s = Dy/D
419
420 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
421
422 ---------------------------------------------------------------------------------------------------
423
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:
425
426 v1 ( bx1 , by1 );
427 v2 ( bx2 , by2 );
428 v3 ( bx3 , by3 );
429
430 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
431
432 n1 ( -by1 , bx1 );
433 n3 ( -by3 , bx3 );
434
435 Dot products:
436
437 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
438 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
439
440 ratio = dp1 / dp2;
441 crossing vector = v1 * ratio;
442
443 And that's it.
444
445 -----------------------------------
446
447 So... to code this, let's say we have two Lines: l1 & l2.
448
449 Vector v1 = l1.endpoint - l1.position;
450 Vector v2 = l2.endpoint - l2.position;
451 Vector v3 = v2 - v1;
452
453 Vector normal1(-v1.y, v1.x);
454 Vector normal3(-v3.y, v3.x);
455
456 double dotProduct1 = v2.Dot(normal1);
457 double dotProduct2 = v2.Dot(normal3);
458
459 if (dotProduct2 == 0)
460         return ParallelLines;
461 else
462 {
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);
465         return intersection;
466 }
467 */
468