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