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