]> Shamusworld >> Repos - architektonas/blob - src/line.cpp
Fixed Line rendering to keep attache Dimensions correct length w/o 'Fix Len'
[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 //
15
16 #include "line.h"
17
18 #include <QtGui>
19 #include "dimension.h"
20
21 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
22         dragging(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
23         length(p2.Magnitude())
24 {
25 }
26
27 Line::~Line()
28 {
29 }
30
31 /*virtual*/ void Line::Draw(QPainter * painter)
32 {
33         if (state == OSSelected)
34                 painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
35         else
36                 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
37
38 //      if (draggingHandle1)
39         if (state == OSSelected)
40                 painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
41
42 //      if (draggingHandle2)
43         if (state == OSSelected)
44                 painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
45
46         if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
47         {
48                 Vector point1 = (draggingHandle1 ? endpoint : position);
49                 Vector point2 = (draggingHandle1 ? position : endpoint);
50
51                 Vector current(point2 - point1);
52                 Vector v = current.Unit() * length;
53                 Vector v2 = point1 + v;
54                 painter->drawLine((int)point1.x, (int)point1.y, (int)v2.x, (int)v2.y);
55
56                 if (current.Magnitude() > length)
57                 {
58                         painter->setPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
59                         painter->drawLine((int)v2.x, (int)v2.y, (int)point2.x, (int)point2.y);
60                 }
61         }
62         else
63                 painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
64 }
65
66 /*virtual*/ Vector Line::Center(void)
67 {
68         // Technically, this is the midpoint but who are we to quibble? :-)
69         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
70         return endpoint + v;
71 }
72
73 /*virtual*/ bool Line::Collided(Vector point)
74 {
75         objectWasDragged = false;
76         Vector lineSegment = endpoint - position;
77         Vector v1 = point - position;
78         Vector v2 = point - endpoint;
79         double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
80
81         // Geometric interpretation:
82         // The paremeterized point on the vector ls is where the perpendicular intersects ls.
83         // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
84         // then the perpendicular lies beyond the 2nd endpoint.
85
86         if (parameterizedPoint < 0.0)
87                 distance = v1.Magnitude();
88         else if (parameterizedPoint > lineSegment.Magnitude())
89                 distance = v2.Magnitude();
90         else                                    // distance = ?Det?(ls, v1) / |ls|
91                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
92
93         // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
94         // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
95         // {e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
96         // computed by first computing the area of the triangle the three points form, then dividing by the
97         // length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
98         // triangle formed by three points is the determinant of the following matrix:
99         //
100         // sx sy 1
101         // ex ey 1
102         // px py 1
103         //
104         // By translating the start point to the origin, this can be rewritten as:
105         // By subtracting row 1 from all rows, you get the following:
106         // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
107         // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
108         //
109         // 0         0         0        0  0  0
110         // (ex - sx) (ey - sy) 0   ==>  ex ey 0
111         // (px - sx) (py - sy) 0        px py 0
112         //
113         // which greatly simplifies the calculation of the determinant.
114
115         if (state == OSInactive)
116         {
117 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
118 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
119 //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);
120 //printf("      \n", );
121 //How to translate this into pixels from Document space???
122 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
123 //the caller knows about the zoom factor and all that good kinda crap
124                 if (v1.Magnitude() < 10.0)
125                 {
126                         oldState = state;
127                         state = OSSelected;
128                         oldPoint = position; //maybe "position"?
129                         draggingHandle1 = true;
130                         return true;
131                 }
132                 else if (v2.Magnitude() < 10.0)
133                 {
134                         oldState = state;
135                         state = OSSelected;
136                         oldPoint = endpoint; //maybe "position"?
137                         draggingHandle2 = true;
138                         return true;
139                 }
140                 else if (distance < 2.0)
141                 {
142                         oldState = state;
143                         state = OSSelected;
144                         oldPoint = point;
145                         dragging = true;
146                         return true;
147                 }
148         }
149         else if (state == OSSelected)
150         {
151                 // Here we test for collision with handles as well! (SOON!)
152 /*
153 Like so:
154                 if (v1.Magnitude() < 2.0) // Handle #1
155                 else if (v2.Magnitude() < 2.0) // Handle #2
156 */
157                 if (distance < 2.0)
158                 {
159                         oldState = state;
160 //                      state = OSInactive;
161                         oldPoint = point;
162                         dragging = true;
163                         return true;
164                 }
165         }
166
167         state = OSInactive;
168         return false;
169 }
170
171 /*virtual*/ void Line::PointerMoved(Vector point)
172 {
173         // We know this is true because mouse move messages don't come here unless
174         // the object was actually clicked on--therefore we *know* we're being
175         // dragged...
176         objectWasDragged = true;
177
178         if (dragging)
179         {
180                 // Here we need to check whether or not we're dragging a handle or the object itself...
181                 Vector delta = point - oldPoint;
182
183                 position += delta;
184                 endpoint += delta;
185
186                 oldPoint = point;
187                 needUpdate = true;
188         }
189         else if (draggingHandle1)
190         {
191                 Vector delta = point - oldPoint;
192
193                 position += delta;
194
195                 oldPoint = point;
196                 needUpdate = true;
197         }
198         else if (draggingHandle2)
199         {
200                 Vector delta = point - oldPoint;
201
202                 endpoint += delta;
203
204                 oldPoint = point;
205                 needUpdate = true;
206         }
207         else
208                 needUpdate = false;
209
210         if (needUpdate)
211         {
212 // should only do this if "Fixed Length" is set... !!! FIX !!!
213                 Vector point1 = (draggingHandle1 ? endpoint : position);
214                 Vector point2 = (draggingHandle1 ? position : endpoint);
215
216                 Vector current(point2 - point1);
217                 Vector v = current.Unit() * length;
218                 Vector v2 = point1 + v;
219
220                 //bleh
221                 if (!Object::fixedLength)
222                         v2 = point2;
223
224                 if (dimPoint1)
225                         dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
226                 
227                 if (dimPoint2)
228                         dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
229         }
230 }
231
232 /*virtual*/ void Line::PointerReleased(void)
233 {
234         if (draggingHandle1 || draggingHandle2)
235         {
236                 // Set the length (in case the global state was set to fixed (or not))
237                 if (Object::fixedLength)
238                 {
239                         if (draggingHandle1)    // startpoint
240                         {
241                                 Vector v = Vector(position - endpoint).Unit() * length;
242                                 position = endpoint + v;
243                         }
244                         else                                    // endpoint
245                         {
246 //                              Vector v1 = endpoint - position;
247                                 Vector v = Vector(endpoint - position).Unit() * length;
248                                 endpoint = position + v;
249                         }
250                 }
251                 else
252                 {
253                         // Otherwise, we calculate the new length, just in case on the next move
254                         // it turns out to have a fixed length. :-)
255                         length = Vector(endpoint - position).Magnitude();
256                 }
257         }
258
259         dragging = false;
260         draggingHandle1 = false;
261         draggingHandle2 = false;
262
263         // Here we check for just a click: If object was clicked and dragged, then
264         // revert to the old state (OSInactive). Otherwise, keep the new state that
265         // we set.
266 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
267 about keeping track of old states...
268 */
269         if (objectWasDragged)
270                 state = oldState;
271 }
272
273 void Line::SetDimensionOnPoint1(Dimension * dimension)
274 {
275         dimPoint1 = dimension;
276
277         if (dimension)
278                 dimension->SetPoint1(position);
279 }
280
281 void Line::SetDimensionOnPoint2(Dimension * dimension)
282 {
283         dimPoint2 = dimension;
284
285         if (dimension)
286                 dimension->SetPoint2(endpoint);
287 }
288
289 /*
290 Intersection of two lines:
291
292 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
293
294 When they intersect, we can set the equations equal to one another:
295
296 i + j + t (3i - j) = -i + s (j)
297
298 Equating coefficients:
299 1 + 3t = -1 and 1 - t = s
300 So t = -2/3 and s = 5/3
301
302 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 .
303
304
305 so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
306 and v2(p2x, p2y), v3(p3x, p3y) for l2.
307
308 d1 = v1 - v0, d2 = v3 - v2
309
310 Our parametric equations for the line then are:
311
312 r1 = v0 + t(d1)
313 r2 = v2 + s(d2)
314
315 Set r1 = r2, thus we have:
316
317 v0 + t(d1) = v2 + s(d2)
318
319 Taking coefficients, we have:
320
321 p0x + t(d1x) = p2x + s(d2x)
322 p0y + t(d1y) = p2y + s(d2y)
323
324 rearranging we get:
325
326 t(d1x) - s(d2x) = p2x - p0x
327 t(d1y) - s(d2y) = p2y - p0y
328
329 Determinant D is ad - bc where the matrix look like:
330
331 a b
332 c d
333
334 so D = (d1x)(d2y) - (d2x)(d1y)
335 if D = 0, the lines are parallel.
336 Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
337 Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
338 t = Dx/D, s = Dy/D
339
340 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
341
342 ---------------------------------------------------------------------------------------------------
343
344 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:
345
346 v1 ( bx1 , by1 );
347 v2 ( bx2 , by2 );
348 v3 ( bx3 , by3 );
349
350 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
351
352 n1 ( -by1 , bx1 );
353 n3 ( -by3 , bx3 );
354
355 Dot products:
356
357 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
358 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
359
360 ratio = dp1 / dp2;
361 crossing vector = v1 * rat;
362
363 And that's it.
364
365 -----------------------------------
366
367 So... to code this, let's say we have two Lines: l1 & l2.
368
369 Vector v1 = l1.endpoint - l1.position;
370 Vector v2 = l2.endpoint - l2.position;
371 Vector v3 = v2 - v1;
372
373 Vector normal1(-v1.y, v1.x);
374 Vector normal3(-v3.y, v3.x);
375
376 double dotProduct1 = v2.Dot(normal1);
377 double dotProduct2 = v2.Dot(normal3);
378
379 if (dotProduct2 == 0)
380         return ParallelLines;
381 else
382 {
383         // I think we'd still have to add the intersection to the position point to get the intersection...
384         Point intersection = v1 * (dotProduct1 / dotProduct2);
385         return intersection;
386 }
387 */
388