]> Shamusworld >> Repos - architektonas/blob - src/line.cpp
Fixed Arc to give continuous feedback like Line and Circle do.
[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 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 (some) mouseover hints
17 //
18
19 #include "line.h"
20
21 #include <QtGui>
22 #include "container.h"
23 #include "dimension.h"
24 #include "mathconstants.h"
25 #include "painter.h"
26
27
28 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p),
29         /*type(OTLine),*/ endpoint(p2),
30         draggingLine(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
31         length(Vector::Magnitude(p2, p1)), angle(Vector(endpoint - position).Unit()),
32         hitPoint1(false), hitPoint2(false), hitLine(false)
33 {
34         type = OTLine;
35 }
36
37
38 Line::~Line()
39 {
40 // Taking care of connections should be done by the Container, as we don't know
41 // anything about any other object connected to this one.
42 #if 0
43         // If there are any attached Dimensions, we must set the attachment points
44         // to NULL since they will no longer be valid.
45         if (attachedDimension)
46         {
47                 attachedDimension->SetPoint1(NULL);
48                 attachedDimension->SetPoint2(NULL);
49         }
50         // IT WOULD BE NICE to have any object points attached to this line automagically
51         // connect to this dimension object at this point, instead of just becoming
52         // detached.
53 #endif
54 }
55
56
57 /*virtual*/ void Line::Draw(Painter * painter)
58 {
59         painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
60
61         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
62                 painter->DrawHandle(position);
63
64         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
65                 painter->DrawHandle(endpoint);
66
67         if ((state == OSInactive) && !hitLine)
68                 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
69
70         if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
71         {
72                 Vector point1 = (draggingHandle1 ? endpoint : position);
73                 Vector point2 = (draggingHandle1 ? position : endpoint);
74
75                 Vector current(point2 - point1);
76                 Vector v = current.Unit() * length;
77                 Vector v2 = point1 + v;
78                 painter->DrawLine(point1, v2);
79
80                 if (current.Magnitude() > length)
81                 {
82                         painter->SetPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
83                         painter->DrawLine(v2, point2);
84                 }
85         }
86         else
87                 painter->DrawLine(position, endpoint);
88
89         // If we're dragging an endpoint, draw an information panel showing both
90         // the length and angle being set.
91         if (draggingHandle1 || draggingHandle2)
92         {
93                 double absAngle = (Vector(endpoint - position).Angle()) * RADIANS_TO_DEGREES;
94                 double absLength = Vector(position - endpoint).Magnitude();
95
96                 QString text;
97
98                 text = QObject::tr("Length: %1 in.\n") + QChar(0x2221) + QObject::tr(": %2");
99                 text = text.arg(absLength).arg(absAngle);
100
101                 QPen pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
102                 painter->SetPen(pen);
103                 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
104                 QRectF textRect(10.0, 10.0, 270.0, 70.0);       // x, y, w, h (in Qt coords)
105                 painter->DrawRoundedRect(textRect, 7.0, 7.0);
106
107                 textRect.setLeft(textRect.left() + 14);
108                 painter->SetFont(*Object::font);
109                 pen = QPen(QColor(0x00, 0x5F, 0xDF));
110                 painter->SetPen(pen);
111                 painter->DrawText(textRect, Qt::AlignVCenter, text);
112         }
113 }
114
115 /*virtual*/ Vector Line::Center(void)
116 {
117         // Technically, this is the midpoint but who are we to quibble? :-)
118         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
119         return endpoint + v;
120 }
121
122 /*virtual*/ bool Line::Collided(Vector point)
123 {
124         // We can assume this, since this is a mouse down event here.
125         objectWasDragged = false;
126 //      SaveHitState();
127         HitTest(point);
128 //      return HitStateChanged();
129
130 // this is shite. this should be checked for in the Container, not here!
131         // If we're part of a non-top-level container, send this signal to it
132         if (parent->type == OTContainer && !((Container *)parent)->isTopLevelContainer
133                 && (hitLine || hitPoint1 || hitPoint2))
134         {
135                 parent->state = OSSelected;
136                 return true;
137         }
138
139 /*
140 There's a small problem here with the implementation: You can have a dimension tied
141 to only one point while at the same time you can have a dimension sitting on this line.
142 Since there's only *one* dimPoint for each point, this can be problematic...
143
144 We solve this by allowing only *one* Dimension object to be attached to the Line,
145 Arc, etc. and by giving the Dimension object a pointer to our endpoints.
146
147 Problem still arises when we delete this object; The attached Dimension object will
148 then have bad pointers! What it *should* do is delete the object if and only if this
149 line is not attached to any other object. If it is, then one of those attachment
150 points should be sent to the dimension object (done for position & endpoint).
151
152 NOTE: The STL vector<T> *does not* take ownership of pointers, therefore is suitable
153       for our purposes
154
155 Also: It would be nice to have a preview of the dimension being drawn, with a modifier
156 key to make it draw/show on the other side...
157
158 TODO: Make Dimension preview with modifier keys for showing on other side
159 */
160         // Is the dimension tool active? Let's use it:
161         if (dimensionActive)
162         {
163                 // User clicked on the line itself (endpoint checks should preceed this one):
164                 // (Priorities are taken care of in HitTest()...)
165                 if (hitLine)
166                 {
167 #if 0
168                         if (attachedDimension == NULL)
169                         {
170                                 // How to get this object into the top level container???
171 /*
172 The real question is do we care. I think so, because if this isn't in the top
173 level container, it won't get drawn...
174 But we can fix that by making this object call any attached object's (like
175 a dimension only) Draw() function... :-/
176 */
177                                 attachedDimension = new Dimension(&position, &endpoint, DTLinear, this);
178
179                                 if (parent != NULL)
180                                         parent->Add(attachedDimension);
181                         }
182                         else
183                         {
184                                 // If there's one already there, tell it to flip sides...
185                                 attachedDimension->FlipSides();
186                         }
187 #else
188                         // New approach here: We look for connected objects.
189                         Object * attachedDimension = FindAttachedDimension();
190
191                         if (attachedDimension)
192                         {
193                                 // If there's an attached Dimension, tell it to switch sides...
194                                 ((Dimension *)attachedDimension)->FlipSides();
195                         }
196                         else
197                         {
198                                 // Otherwise, we make a new one and attach it here.
199                                 attachedDimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
200                                 connected.push_back(Connection(attachedDimension, 0));
201                                 connected.push_back(Connection(attachedDimension, 1.0));
202
203                                 if (parent != NULL)
204                                         parent->Add(attachedDimension);
205                         }
206 #endif
207
208                         return true;
209                 }
210         }
211
212
213         if (state == OSInactive)
214         {
215 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
216 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
217 //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);
218 //printf("      \n", );
219 //How to translate this into pixels from Document space???
220 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
221 //the caller knows about the zoom factor and all that good kinda crap
222 //I think what's needed is an Object class variable/method that can be changed by the TLC and
223 //called in derived classes to properly scale the location to the current zoom level. That *should* work.
224
225 // ALSO: Need to code a global (read: Object class) variable that tells use whether a modifier
226 //       key was pressed in addition to the mouse click, so we can do stuff like, say, hold
227 //       down CTRL and be able to do multiple selecting of objects (in that case, we would
228 //       keep the Object state from changing).
229                 if (hitPoint1)
230                 {
231                         oldState = state;
232                         state = OSSelected;
233                         oldPoint = position; //maybe "position"?
234                         draggingHandle1 = true;
235                         return true;
236                 }
237                 else if (hitPoint2)
238                 {
239                         oldState = state;
240                         state = OSSelected;
241                         oldPoint = endpoint; //maybe "position"?
242                         draggingHandle2 = true;
243                         return true;
244                 }
245                 else if (hitLine)
246                 {
247                         oldState = state;
248                         state = OSSelected;
249                         oldPoint = point;
250                         draggingLine = true;
251                         return true;
252                 }
253         }
254         else if (state == OSSelected)
255         {
256                 // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
257 /*
258 Like so:
259                 if (v1.Magnitude() < 2.0) // Handle #1
260                 else if (v2.Magnitude() < 2.0) // Handle #2
261 */
262                 if (hitLine)
263                 {
264                         oldState = state;
265 //                      state = OSInactive;
266                         oldPoint = point;
267                         draggingLine = true;
268
269                         // Toggle selected state if CTRL held
270                         if (qApp->keyboardModifiers() == Qt::ControlModifier)
271                                 state = OSInactive;
272
273                         return true;
274                 }
275         }
276
277         // If CTRL is held, then we bypass the "turn off" code. Still didn't hit
278         // *this* object though. :-)
279         if (qApp->keyboardModifiers() == Qt::ControlModifier)
280                 return false;
281
282         // If we got here, we clicked on nothing, so set the object to inactive.
283         // (Once we can read key modifiers, we can override this to allow multiple selection.)
284         state = OSInactive;
285         return false;
286 }
287
288
289 /*virtual*/ void Line::PointerMoved(Vector point)
290 {
291         if (selectionInProgress)
292         {
293                 // Check for whether or not the rect contains this line
294 #if 0
295                 if (selection.normalized().contains(Extents()))
296 #else
297 //              if (selection.normalized().contains(position.x, position.y)
298 //                      && selection.normalized().contains(endpoint.x, endpoint.y))
299                 if (selection.contains(position.x, position.y)
300                         && selection.contains(endpoint.x, endpoint.y))
301 #endif
302                         state = OSSelected;
303                 else
304                         state = OSInactive;
305
306                 return;
307         }
308
309         // Hit test tells us what we hit (if anything) through boolean variables. (It
310         // also tells us whether or not the state changed. --not any more)
311         SaveHitState();
312         HitTest(point);
313         needUpdate = HitStateChanged();
314
315         objectWasDragged = (draggingLine | draggingHandle1 | draggingHandle2);
316
317         if (objectWasDragged)
318         {
319                 Vector delta = point - oldPoint;
320
321                 if (draggingHandle1 || draggingLine)
322                         position += delta;
323
324                 if (draggingHandle2 || draggingLine)
325                         endpoint += delta;
326
327                 oldPoint = point;
328                 needUpdate = true;
329
330 //doesn't work          QMainWindow::statusBar()->setText("You are manipulating a line");
331         }
332
333 /*
334 We can't count on any coupling between the dimension object and us, so how do we do this???
335 Also, there may be more than one Dimension object connected to a single endpoint!
336
337 Ugly ways to do it:
338  - Keep track of the state of the connected dimension
339  - Pass the Dimension the point that's being changed and the delta
340
341 More elegant ways:
342  - Pass the point in a notification function (how?)
343  - Pass the point as a reference to the class instance object (&endpoint). This
344    way, the line doesn't have to care about keeping track of Dimensions
345    connected to it. But still have to care about other connected entities
346    (other Lines, Circles, Arcs, Splines, Texts, etc). I think I'd be OK with
347    this. Since the Dimension has a pointer to our object, all we have to do is
348    update our coordinates and the Dimension object will adjust itself on the
349    next repaint. Problem solved, and we don't have to know anything about how
350    many Dimensions are connected to us, or where! \o/
351    The question then becomes, how do we do this kind of coupling???
352
353 We need to know about connected entities so that we can have them either move
354 in expected ways or constrain the movement of this Line object. This is how we
355 will be a cut above all other CAD software currently out there: the GUI will
356 try to do the right thing, most of the time. :-)
357 */
358         if (needUpdate)
359         {
360 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
361                 Vector point1 = (draggingHandle1 ? endpoint : position);
362                 Vector point2 = (draggingHandle1 ? position : endpoint);
363
364                 if (Object::fixedAngle)
365                 {
366                         // Here we calculate the component of the current vector along the fixed angle.
367                         // A_compB = (A . Bu) * Bu
368                         double magnitudeAlongB = Vector::Dot(Vector(point2 - point1), angle);
369 /*
370 Actually, this isn't quite right. What we want to do is look for the intersection along either
371 the horizontal line or vertical line that intersects from the current mouse position.
372 */
373
374                         if (draggingHandle1)
375                                 position = endpoint + (angle * magnitudeAlongB);
376
377                         if (draggingHandle2)
378                                 endpoint = position + (angle * magnitudeAlongB);
379                 }
380 //              else
381 //                      v2 = point2;
382
383 //If we tell the dimension to flip sides, this is no longer a valid
384 //assumption. !!! FIX !!!
385 //Ideally, we should just send the point that's changing to the Dimension object
386 //and have it figure out which point needs to move... Or is it???
387 // Ideally, we shouldn't have to fuck around with this shit. We need to fix the rendering code
388 // so that we don't have to wait until the dragging is done to correct the position of the
389 // point in question, but we'd need another variable tho.
390 #if 0
391                 if (dimPoint1)
392                         dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
393                 
394                 if (dimPoint2)
395                         dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
396 #endif
397         }
398 }
399
400
401 /*virtual*/ void Line::PointerReleased(void)
402 {
403         if (draggingHandle1 || draggingHandle2)
404         {
405                 // Set the length (in case the global state was set to fixed (or not))
406                 if (Object::fixedLength)
407                 {
408                         if (draggingHandle1)    // startpoint
409                         {
410                                 Vector v = Vector(position - endpoint).Unit() * length;
411                                 position = endpoint + v;
412                         }
413                         else                                    // endpoint
414                         {
415                                 Vector v = Vector(endpoint - position).Unit() * length;
416                                 endpoint = position + v;
417                         }
418                 }
419                 else
420                 {
421                         // Otherwise, we calculate the new length, just in case on the next
422                         // move it turns out to have a fixed length. :-)
423                         length = Vector(endpoint - position).Magnitude();
424                 }
425
426                 if (!Object::fixedAngle)
427                 {
428                         // Calculate the new angle, just in case on the next move it turns
429                         // out to be fixed. :-)
430                         angle = Vector(endpoint - position).Unit();
431                 }
432         }
433
434         draggingLine = false;
435         draggingHandle1 = false;
436         draggingHandle2 = false;
437
438         if (objectWasDragged)
439                 state = oldState;
440 }
441
442
443 /*virtual*/ bool Line::HitTest(Point point)
444 {
445 //      SaveHitState();
446
447         hitPoint1 = hitPoint2 = hitLine = false;
448         Vector lineSegment = endpoint - position;
449         Vector v1 = point - position;
450         Vector v2 = point - endpoint;
451         double t = Vector::Parameter(position, endpoint, point);
452         double distance;
453
454         // Geometric interpretation:
455         // The parameter "t" on the vector lineSegment is where the normal of
456         // lineSegment coincides with point. If t < 0, the normal lies beyond the
457         // 1st endpoint. If t > 1, then the normal lies beyond the 2nd endpoint. We
458         // only calculate the length of the normal between the point and the
459         // lineSegment when the parameter is between 0 and 1.
460
461         // Geometric interpretation of "distance = ?Det?(ls, v1) / |ls|":
462         // If the segment endpoints are s and e, and the point is p, then the test
463         // for the perpendicular intercepting the segment is equivalent to insisting
464         // that the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
465         // Perpendicular distance from the point to the segment is computed by first
466         // computing the area of the triangle the three points form, then dividing by
467         // the length of the segment.  Distances are done just by the Pythagorean
468         // theorem. Twice the area of the triangle formed by three points is the
469         // determinant of the following matrix:
470         //
471         // sx sy 1       0  0  1       0  0  0
472         // ex ey 1  ==>  ex ey 1  ==>  ex ey 0
473         // px py 1       px py 1       px py 0
474         //
475         // By translating the start point to the origin, and subtracting row 1 from
476         // all other rows, we end up with the matrix on the right which greatly
477         // simplifies the calculation of the determinant.
478
479         if (t < 0.0)
480                 distance = v1.Magnitude();
481         else if (t > 1.0)
482                 distance = v2.Magnitude();
483         else
484                 // distance = ?Det?(ls, v1) / |ls|
485                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
486                         / lineSegment.Magnitude());
487
488         if ((v1.Magnitude() * Painter::zoom) < 8.0)
489                 hitPoint1 = true;
490         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
491                 hitPoint2 = true;
492         else if ((distance * Painter::zoom) < 5.0)
493                 hitLine = true;
494
495         return (hitPoint1 || hitPoint2 || hitLine ? true : false);
496 //      return HitStateChanged();
497 }
498
499
500 // Check to see if the point passed in coincides with any we have. If so, return a
501 // pointer to it; otherwise, return NULL.
502 /*virtual*/ Vector * Line::GetPointAt(Vector v)
503 {
504         if (v == position)
505                 return &position;
506         else if (v == endpoint)
507                 return &endpoint;
508
509         return 0;
510 }
511
512
513 /*virtual*/ void Line::Enumerate(FILE * file)
514 {
515         fprintf(file, "LINE (%lf,%lf) (%lf,%lf)\n", position.x, position.y, endpoint.x, endpoint.y);
516 }
517
518
519 /*virtual*/ Object * Line::Copy(void)
520 {
521 #warning "!!! This doesn't take care of attached Dimensions !!!"
522 /*
523 This is a real problem. While having a pointer in the Dimension to this line's points
524 is fast & easy, it creates a huge problem when trying to replicate an object like this.
525
526 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
527 way, if you copy them, ... you might still have problems. Because you can't be sure if
528 a copy will be persistant or not, you then *definitely* do not want them to have the
529 same reference number.
530 */
531         return new Line(position, endpoint, parent);
532 }
533
534
535 /*virtual*/ Vector Line::GetPointAtParameter(double parameter)
536 {
537         if (parameter <= 0)
538                 return position;
539         else if (parameter >= 1.0)
540                 return endpoint;
541
542         // Our parameter lies between zero and one, so calculate it!
543         Vector v(endpoint, position);
544         double length = v.Magnitude();
545         // We scale the magnitude of v so that it lies between 0 and 1...
546         // By multiplying the parameter by the magnitude, we obtain the point we
547         // want. No scaling necessary as it's inherent in the approach!
548         double spotOnLength = length * parameter;
549
550         // To get our point, we use the initial point of the line and add in our
551         // scaled point.
552         Vector result = position + (v * spotOnLength);
553         return result;
554 }
555
556
557 /*virtual*/ QRectF Line::Extents(void)
558 {
559         QRectF rect(QPointF(position.x, position.y), QPointF(endpoint.x, endpoint.y));
560         return rect.normalized();
561 }
562
563
564 /*virtual*/ void Line::Translate(Vector amount)
565 {
566         position += amount;
567         endpoint += amount;
568 }
569
570
571 /*virtual*/ void Line::Rotate(Vector point, double angle)
572 {
573 }
574
575
576 /*virtual*/ void Line::Scale(Vector point, double amount)
577 {
578 }
579
580
581 void Line::SetDimensionOnLine(Dimension * dimension/*=NULL*/)
582 {
583         // If they don't pass one in, create it for the caller.
584         if (dimension == NULL)
585         {
586 //printf("Line::SetDimensionOnLine(): Creating new dimension...\n");
587 //              dimension = new Dimension(position, endpoint, DTLinear, this);
588                 dimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
589
590                 if (parent)
591 //{
592 //printf("Line::SetDimensionOnLine(): Adding to parent...\n");
593                         parent->Add(dimension);
594 //}
595         }
596         else
597         {
598                 dimension->Connect(this, 0);
599                 dimension->Connect(this, 1.0);
600         }
601
602         // Make sure the Dimension is connected to us...
603         Connect(dimension, 0);
604         Connect(dimension, 1.0);
605 }
606
607
608 Object * Line::FindAttachedDimension(void)
609 {
610         // Is there anything connected to this line? If not, return NULL
611         if (connected.size() < 2)
612                 return NULL;
613
614         // Otherwise, we have to search our objects to see if there's a likely
615         // candidate. In this case, we're looking for a pointer to the same object
616         // with a parameter of 0 and 1 respectively. This is O((n^2)/2).
617         for(uint i=0; i<connected.size(); i++)
618         {
619                 for(uint j=i+1; j<connected.size(); j++)
620                 {
621 //printf("Line: connected[i]=%X, connected[j]=%X, connected[i].t=%lf, connected[j].t=%lf\n", connected[i].object, connected[j].object, connected[i].t, connected[j].t);
622                         if ((connected[i].object == connected[j].object)
623                                 && ((connected[i].t == 0 && connected[j].t == 1.0)
624                                 || (connected[i].t == 1.0 && connected[j].t == 0)))
625                                 return connected[i].object;
626                 }
627         }
628
629         // Didn't find anything, so return NULL
630         return NULL;
631 }
632
633
634 void Line::SaveHitState(void)
635 {
636         oldHitPoint1 = hitPoint1;
637         oldHitPoint2 = hitPoint2;
638         oldHitLine = hitLine;
639 }
640
641
642 bool Line::HitStateChanged(void)
643 {
644         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
645                 return true;
646
647         return false;
648 }
649
650
651 /*
652 Intersection of two lines:
653
654 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
655
656 When they intersect, we can set the equations equal to one another:
657
658 i + j + t (3i - j) = -i + s (j)
659
660 Equating coefficients:
661 1 + 3t = -1 and 1 - t = s
662 So t = -2/3 and s = 5/3
663
664 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 .
665
666
667 so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
668 and v2(p2x, p2y), v3(p3x, p3y) for l2.
669
670 d1 = v1 - v0, d2 = v3 - v2
671
672 Our parametric equations for the line then are:
673
674 r1 = v0 + t(d1)
675 r2 = v2 + s(d2)
676
677 Set r1 = r2, thus we have:
678
679 v0 + t(d1) = v2 + s(d2)
680
681 Taking coefficients, we have:
682
683 p0x + t(d1x) = p2x + s(d2x)
684 p0y + t(d1y) = p2y + s(d2y)
685
686 rearranging we get:
687
688 t(d1x) - s(d2x) = p2x - p0x
689 t(d1y) - s(d2y) = p2y - p0y
690
691 Determinant D is ad - bc where the matrix looks like:
692
693 a b
694 c d
695
696 so D = (d1x)(d2y) - (d2x)(d1y)
697 if D = 0, the lines are parallel.
698 Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
699 Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
700 t = Dx/D, s = Dy/D
701
702 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
703
704 ---------------------------------------------------------------------------------------------------
705
706 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:
707
708 v1 ( bx1 , by1 );
709 v2 ( bx2 , by2 );
710 v3 ( bx3 , by3 );
711
712 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
713
714 n1 ( -by1 , bx1 );
715 n3 ( -by3 , bx3 );
716
717 Dot products:
718
719 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
720 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
721
722 ratio = dp1 / dp2;
723 crossing vector = v1 * ratio;
724
725 And that's it.
726
727 -----------------------------------
728
729 So... to code this, let's say we have two Lines: l1 & l2.
730
731 Vector v1 = l1.endpoint - l1.position;
732 Vector v2 = l2.endpoint - l2.position;
733 Vector v3 = v2 - v1;
734
735 Vector normal1(-v1.y, v1.x);
736 Vector normal3(-v3.y, v3.x);
737
738 double dotProduct1 = v2.Dot(normal1);
739 double dotProduct2 = v2.Dot(normal3);
740
741 if (dotProduct2 == 0)
742         return ParallelLines;
743 else
744 {
745         // I think we'd still have to add the intersection to the position point to get the intersection...
746         Point intersection = v1 * (dotProduct1 / dotProduct2);
747         return intersection;
748 }
749 */
750