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