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