]> Shamusworld >> Repos - architektonas/blob - src/line.cpp
3f4b8d4aebae8a60557a5d1660fd0c5e748b4081
[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 //
17
18 #include "line.h"
19
20 #include <QtGui>
21 #include "dimension.h"
22
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))
26 {
27 }
28
29 Line::~Line()
30 {
31 }
32
33 /*virtual*/ void Line::Draw(QPainter * painter)
34 {
35         if (state == OSSelected)
36                 painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
37         else
38                 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
39
40 //      if (draggingHandle1)
41         if (state == OSSelected)
42                 painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
43
44 //      if (draggingHandle2)
45         if (state == OSSelected)
46                 painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
47
48         if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
49         {
50                 Vector point1 = (draggingHandle1 ? endpoint : position);
51                 Vector point2 = (draggingHandle1 ? position : endpoint);
52
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);
57
58                 if (current.Magnitude() > length)
59                 {
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);
62                 }
63         }
64         else
65                 painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
66 }
67
68 /*virtual*/ Vector Line::Center(void)
69 {
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);
72         return endpoint + v;
73 }
74
75 /*virtual*/ bool Line::Collided(Vector point)
76 {
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;
82
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.
87
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());
94
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:
101         //
102         // sx sy 1
103         // ex ey 1
104         // px py 1
105         //
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!]
110         //
111         // 0         0         0        0  0  0
112         // (ex - sx) (ey - sy) 0   ==>  ex ey 0
113         // (px - sx) (py - sy) 0        px py 0
114         //
115         // which greatly simplifies the calculation of the determinant.
116
117         if (state == OSInactive)
118         {
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);
122 //printf("      \n", );
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)
127                 {
128                         oldState = state;
129                         state = OSSelected;
130                         oldPoint = position; //maybe "position"?
131                         draggingHandle1 = true;
132                         return true;
133                 }
134                 else if (v2.Magnitude() < 10.0)
135                 {
136                         oldState = state;
137                         state = OSSelected;
138                         oldPoint = endpoint; //maybe "position"?
139                         draggingHandle2 = true;
140                         return true;
141                 }
142                 else if (distance < 2.0)
143                 {
144                         oldState = state;
145                         state = OSSelected;
146                         oldPoint = point;
147                         dragging = true;
148                         return true;
149                 }
150         }
151         else if (state == OSSelected)
152         {
153                 // Here we test for collision with handles as well! (SOON!)
154 /*
155 Like so:
156                 if (v1.Magnitude() < 2.0) // Handle #1
157                 else if (v2.Magnitude() < 2.0) // Handle #2
158 */
159                 if (distance < 2.0)
160                 {
161                         oldState = state;
162 //                      state = OSInactive;
163                         oldPoint = point;
164                         dragging = true;
165                         return true;
166                 }
167         }
168
169         state = OSInactive;
170         return false;
171 }
172
173 /*virtual*/ void Line::PointerMoved(Vector point)
174 {
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
177         // dragged...
178         objectWasDragged = true;
179
180         if (dragging)
181         {
182                 // Here we need to check whether or not we're dragging a handle or the object itself...
183                 Vector delta = point - oldPoint;
184
185                 position += delta;
186                 endpoint += delta;
187
188                 oldPoint = point;
189                 needUpdate = true;
190         }
191         else if (draggingHandle1)
192         {
193                 Vector delta = point - oldPoint;
194
195                 position += delta;
196
197                 oldPoint = point;
198                 needUpdate = true;
199         }
200         else if (draggingHandle2)
201         {
202                 Vector delta = point - oldPoint;
203
204                 endpoint += delta;
205
206                 oldPoint = point;
207                 needUpdate = true;
208         }
209         else
210                 needUpdate = false;
211
212         if (needUpdate)
213         {
214 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
215                 Vector point1 = (draggingHandle1 ? endpoint : position);
216                 Vector point2 = (draggingHandle1 ? position : endpoint);
217
218                 Vector current(point2, point1);
219                 Vector v = current.Unit() * length;
220                 Vector v2 = point1 + v;
221
222                 //bleh
223                 if (!Object::fixedLength)
224                         v2 = point2;
225
226                 if (dimPoint1)
227                         dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
228                 
229                 if (dimPoint2)
230                         dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
231         }
232 }
233
234 /*virtual*/ void Line::PointerReleased(void)
235 {
236         if (draggingHandle1 || draggingHandle2)
237         {
238                 // Set the length (in case the global state was set to fixed (or not))
239                 if (Object::fixedLength)
240                 {
241                         if (draggingHandle1)    // startpoint
242                         {
243                                 Vector v = Vector(position - endpoint).Unit() * length;
244                                 position = endpoint + v;
245                         }
246                         else                                    // endpoint
247                         {
248 //                              Vector v1 = endpoint - position;
249                                 Vector v = Vector(endpoint - position).Unit() * length;
250                                 endpoint = position + v;
251                         }
252                 }
253                 else
254                 {
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();
258                 }
259         }
260
261         dragging = false;
262         draggingHandle1 = false;
263         draggingHandle2 = false;
264
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
267         // we set.
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...
270 */
271         if (objectWasDragged)
272                 state = oldState;
273 }
274
275 void Line::SetDimensionOnPoint1(Dimension * dimension)
276 {
277         dimPoint1 = dimension;
278
279         if (dimension)
280                 dimension->SetPoint1(position);
281 }
282
283 void Line::SetDimensionOnPoint2(Dimension * dimension)
284 {
285         dimPoint2 = dimension;
286
287         if (dimension)
288                 dimension->SetPoint2(endpoint);
289 }
290
291 /*
292 Intersection of two lines:
293
294 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
295
296 When they intersect, we can set the equations equal to one another:
297
298 i + j + t (3i - j) = -i + s (j)
299
300 Equating coefficients:
301 1 + 3t = -1 and 1 - t = s
302 So t = -2/3 and s = 5/3
303
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 .
305
306
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.
309
310 d1 = v1 - v0, d2 = v3 - v2
311
312 Our parametric equations for the line then are:
313
314 r1 = v0 + t(d1)
315 r2 = v2 + s(d2)
316
317 Set r1 = r2, thus we have:
318
319 v0 + t(d1) = v2 + s(d2)
320
321 Taking coefficients, we have:
322
323 p0x + t(d1x) = p2x + s(d2x)
324 p0y + t(d1y) = p2y + s(d2y)
325
326 rearranging we get:
327
328 t(d1x) - s(d2x) = p2x - p0x
329 t(d1y) - s(d2y) = p2y - p0y
330
331 Determinant D is ad - bc where the matrix look like:
332
333 a b
334 c d
335
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)
340 t = Dx/D, s = Dy/D
341
342 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
343
344 ---------------------------------------------------------------------------------------------------
345
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:
347
348 v1 ( bx1 , by1 );
349 v2 ( bx2 , by2 );
350 v3 ( bx3 , by3 );
351
352 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
353
354 n1 ( -by1 , bx1 );
355 n3 ( -by3 , bx3 );
356
357 Dot products:
358
359 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
360 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
361
362 ratio = dp1 / dp2;
363 crossing vector = v1 * ratio;
364
365 And that's it.
366
367 -----------------------------------
368
369 So... to code this, let's say we have two Lines: l1 & l2.
370
371 Vector v1 = l1.endpoint - l1.position;
372 Vector v2 = l2.endpoint - l2.position;
373 Vector v3 = v2 - v1;
374
375 Vector normal1(-v1.y, v1.x);
376 Vector normal3(-v3.y, v3.x);
377
378 double dotProduct1 = v2.Dot(normal1);
379 double dotProduct2 = v2.Dot(normal3);
380
381 if (dotProduct2 == 0)
382         return ParallelLines;
383 else
384 {
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);
387         return intersection;
388 }
389 */
390