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