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