]> Shamusworld >> Repos - architektonas/blob - src/line.cpp
Fixes to accomodate object connections.
[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 "dimension.h"
23 #include "painter.h"
24
25
26 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
27         draggingLine(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
28         length(Vector::Magnitude(p2, p1)), angle(Vector(endpoint - position).Unit()),
29         hitPoint1(false), hitPoint2(false), hitLine(false)
30 {
31 }
32
33 Line::~Line()
34 {
35 // Taking care of connections should be done by the Container, as we don't know
36 // anything about any other object connected to this one.
37 #if 0
38         // If there are any attached Dimensions, we must set the attachment points
39         // to NULL since they will no longer be valid.
40         if (attachedDimension)
41         {
42                 attachedDimension->SetPoint1(NULL);
43                 attachedDimension->SetPoint2(NULL);
44         }
45         // IT WOULD BE NICE to have any object points attached to this line automagically
46         // connect to this dimension object at this point, instead of just becoming
47         // detached.
48 #endif
49 }
50
51 /*virtual*/ void Line::Draw(Painter * painter)
52 {
53         painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
54
55         if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
56                 painter->DrawHandle(position);
57
58         if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
59                 painter->DrawHandle(endpoint);
60
61         if ((state == OSInactive) && !hitLine)
62                 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
63
64         if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
65         {
66                 Vector point1 = (draggingHandle1 ? endpoint : position);
67                 Vector point2 = (draggingHandle1 ? position : endpoint);
68
69                 Vector current(point2 - point1);
70                 Vector v = current.Unit() * length;
71                 Vector v2 = point1 + v;
72 //              painter->DrawLine((int)point1.x, (int)point1.y, (int)v2.x, (int)v2.y);
73                 painter->DrawLine(point1, v2);
74
75                 if (current.Magnitude() > length)
76                 {
77                         painter->SetPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
78 //                      painter->DrawLine((int)v2.x, (int)v2.y, (int)point2.x, (int)point2.y);
79                         painter->DrawLine(v2, point2);
80                 }
81         }
82 // Problem: when drawing at large zoom levels, this throws away precision thus
83 //          causing the line to rendered too short. !!! FIX !!! [DONE]
84         else
85 //              painter->DrawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
86                 painter->DrawLine(position, endpoint);
87 }
88
89 /*virtual*/ Vector Line::Center(void)
90 {
91         // Technically, this is the midpoint but who are we to quibble? :-)
92         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
93         return endpoint + v;
94 }
95
96 /*virtual*/ bool Line::Collided(Vector point)
97 {
98         // We can assume this, since this is a mouse down event here.
99         objectWasDragged = false;
100         HitTest(point);
101
102 /*
103 There's a small problem here with the implementation: You can have a dimension tied
104 to only one point while at the same time you can have a dimension sitting on this line.
105 Since there's only *one* dimPoint for each point, this can be problematic...
106
107 We solve this by allowing only *one* Dimension object to be attached to the Line,
108 Arc, etc. and by giving the Dimension object a pointer to our endpoints.
109
110 Problem still arises when we delete this object; The attached Dimension object will
111 then have bad pointers! What it *should* do is delete the object if and only if this
112 line is not attached to any other object. If it is, then one of those attachment
113 points should be sent to the dimension object (done for position & endpoint).
114
115 NOTE: The STL vector<T> *does not* take ownership of pointers, therefore is suitable
116       for our purposes
117
118 Also: It would be nice to have a preview of the dimension being drawn, with a modifier
119 key to make it draw/show on the other side...
120
121 TODO: Make Dimension preview with modifier keys for showing on other side
122 */
123         // Is the dimension tool active? Let's use it:
124         if (dimensionActive)
125         {
126                 // User clicked on the line itself (endpoint checks should preceed this one):
127                 // (Priorities are taken care of in HitTest()...)
128                 if (hitLine)
129                 {
130 #if 0
131                         if (attachedDimension == NULL)
132                         {
133                                 // How to get this object into the top level container???
134 /*
135 The real question is do we care. I think so, because if this isn't in the top
136 level container, it won't get drawn...
137 But we can fix that by making this object call any attached object's (like
138 a dimension only) Draw() function... :-/
139 */
140                                 attachedDimension = new Dimension(&position, &endpoint, DTLinear, this);
141
142                                 if (parent != NULL)
143                                         parent->Add(attachedDimension);
144                         }
145                         else
146                         {
147                                 // If there's one already there, tell it to flip sides...
148                                 attachedDimension->FlipSides();
149                         }
150 #else
151                         // New approach here: We look for connected objects.
152                         Object * attachedDimension = FindAttachedDimension();
153
154                         if (attachedDimension)
155                         {
156                                 // If there's an attached Dimension, tell it to switch sides...
157                                 ((Dimension *)attachedDimension)->FlipSides();
158                         }
159                         else
160                         {
161                                 // Otherwise, we make a new one and attach it here.
162                                 attachedDimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
163                                 connected.push_back(Connection(attachedDimension, 0));
164                                 connected.push_back(Connection(attachedDimension, 1.0));
165
166                                 if (parent != NULL)
167                                         parent->Add(attachedDimension);
168                         }
169 #endif
170
171                         return true;
172                 }
173         }
174
175
176         if (state == OSInactive)
177         {
178 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
179 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
180 //printf("      point = %lf,%lf,%lf; p1 = %lf,%lf,%lf; p2 = %lf,%lf,%lf\n", point.x, point.y, point.z, position.x, position.y, position.z, endpoint.x, endpoint.y, endpoint.z);
181 //printf("      \n", );
182 //How to translate this into pixels from Document space???
183 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
184 //the caller knows about the zoom factor and all that good kinda crap
185 //I think what's needed is an Object class variable/method that can be changed by the TLC and
186 //called in derived classes to properly scale the location to the current zoom level. That *should* work.
187
188 // ALSO: Need to code a global (read: Object class) variable that tells use whether a modifier
189 //       key was pressed in addition to the mouse click, so we can do stuff like, say, hold
190 //       down CTRL and be able to do multiple selecting of objects (in that case, we would
191 //       keep the Object state from changing).
192                 if (hitPoint1)
193                 {
194                         oldState = state;
195                         state = OSSelected;
196                         oldPoint = position; //maybe "position"?
197                         draggingHandle1 = true;
198                         return true;
199                 }
200                 else if (hitPoint2)
201                 {
202                         oldState = state;
203                         state = OSSelected;
204                         oldPoint = endpoint; //maybe "position"?
205                         draggingHandle2 = true;
206                         return true;
207                 }
208                 else if (hitLine)
209                 {
210                         oldState = state;
211                         state = OSSelected;
212                         oldPoint = point;
213                         draggingLine = true;
214                         return true;
215                 }
216         }
217         else if (state == OSSelected)
218         {
219                 // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
220 /*
221 Like so:
222                 if (v1.Magnitude() < 2.0) // Handle #1
223                 else if (v2.Magnitude() < 2.0) // Handle #2
224 */
225                 if (hitLine)
226                 {
227                         oldState = state;
228 //                      state = OSInactive;
229                         oldPoint = point;
230                         draggingLine = true;
231                         return true;
232                 }
233         }
234
235         // If we got here, we clicked on nothing, so set the object to inactive.
236         // (Once we can read key modifiers, we can override this to allow multiple selection.)
237         state = OSInactive;
238         return false;
239 }
240
241 /*virtual*/ void Line::PointerMoved(Vector point)
242 {
243         // Hit test tells us what we hit (if anything) through boolean variables. It
244         // also tells us whether or not the state changed.
245         needUpdate = HitTest(point);
246
247         objectWasDragged = (draggingLine | draggingHandle1 | draggingHandle2);
248
249         if (objectWasDragged)
250         {
251                 Vector delta = point - oldPoint;
252
253                 if (draggingHandle1 || draggingLine)
254                         position += delta;
255
256                 if (draggingHandle2 || draggingLine)
257                         endpoint += delta;
258
259                 oldPoint = point;
260                 needUpdate = true;
261         }
262
263 /*
264 We can't count on any coupling between the dimension object and us, so how do we do this???
265 Also, there may be more than one Dimension object connected to a single endpoint!
266
267 Ugly ways to do it:
268  - Keep track of the state of the connected dimension
269  - Pass the Dimension the point that's being changed and the delta
270
271 More elegant ways:
272  - Pass the point in a notification function (how?)
273  - Pass the point as a reference to the class instance object (&endpoint). This way, the line
274    doesn't have to care about keeping track of Dimensions connected to it. But still have to
275    care about other connected entities (other Lines, Circles, Arcs, Splines, Texts, etc). I
276    think I'd be OK with this.
277    Since the Dimension has a pointer to our object, all we have to do is update our coordinates
278    and the Dimension object will adjust itself on the next repaint. Problem solved, and we don't
279    have to know anything about how many Dimensions are connected to us, or where! \o/
280    The question then becomes, how do we do this kind of coupling???
281
282 We need to know about connected entities so that we can have them either move in expected ways
283 or constrain the movement of this Line object. This is how we will be a cut above all other CAD
284 software currently out there: the GUI will try to do the right thing, most of the time. :-)
285 */
286         if (needUpdate)
287         {
288 // should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
289                 Vector point1 = (draggingHandle1 ? endpoint : position);
290                 Vector point2 = (draggingHandle1 ? position : endpoint);
291
292 #if 0
293                 Vector current(point2, point1);
294                 Vector v = current.Unit() * length;
295                 Vector v2 = point1 + v;
296
297                 //bleh
298                 if (!Object::fixedLength)
299                         v2 = point2;
300 #endif
301
302                 if (Object::fixedAngle)
303                 {
304                         // Here we calculate the component of the current vector along the fixed angle.
305                         // A_compB = (A . Bu) * Bu
306                         double magnitudeAlongB = Vector::Dot(Vector(point2 - point1), angle);
307 /*
308 Actually, this isn't quite right. What we want to do is look for the intersection along either
309 the horizontal line or vertical line that intersects from the current mouse position.
310 */
311
312                         if (draggingHandle1)
313                                 position = endpoint + (angle * magnitudeAlongB);
314
315                         if (draggingHandle2)
316                                 endpoint = position + (angle * magnitudeAlongB);
317                 }
318 //              else
319 //                      v2 = point2;
320
321 //If we tell the dimension to flip sides, this is no longer a valid
322 //assumption. !!! FIX !!!
323 //Ideally, we should just send the point that's changing to the Dimension object
324 //and have it figure out which point needs to move... Or is it???
325 // Ideally, we shouldn't have to fuck around with this shit. We need to fix the rendering code
326 // so that we don't have to wait until the dragging is done to correct the position of the
327 // point in question, but we'd need another variable tho.
328 #if 0
329                 if (dimPoint1)
330                         dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
331                 
332                 if (dimPoint2)
333                         dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
334 #endif
335         }
336 }
337
338 /*virtual*/ void Line::PointerReleased(void)
339 {
340         if (draggingHandle1 || draggingHandle2)
341         {
342                 // Set the length (in case the global state was set to fixed (or not))
343                 if (Object::fixedLength)
344                 {
345                         if (draggingHandle1)    // startpoint
346                         {
347                                 Vector v = Vector(position - endpoint).Unit() * length;
348                                 position = endpoint + v;
349                         }
350                         else                                    // endpoint
351                         {
352 //                              Vector v1 = endpoint - position;
353                                 Vector v = Vector(endpoint - position).Unit() * length;
354                                 endpoint = position + v;
355                         }
356                 }
357                 else
358                 {
359                         // Otherwise, we calculate the new length, just in case on the next move
360                         // it turns out to have a fixed length. :-)
361                         length = Vector(endpoint - position).Magnitude();
362                 }
363
364                 if (!Object::fixedAngle)
365                 {
366                         // Calculate the new angle, just in case on the next move it turns out to
367                         // be fixed. :-)
368                         angle = Vector(endpoint - position).Unit();
369                 }
370         }
371
372         draggingLine = false;
373         draggingHandle1 = false;
374         draggingHandle2 = false;
375
376 //      hitPoint1 = hitPoint2 = hitLine = false;
377
378         // Here we check for just a click: If object was clicked and dragged, then
379         // revert to the old state (OSInactive). Otherwise, keep the new state that
380         // we set.
381 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
382 about keeping track of old states...
383 */
384         if (objectWasDragged)
385                 state = oldState;
386 }
387
388 // Check to see if the point passed in coincides with any we have. If so, return a
389 // pointer to it; otherwise, return NULL.
390 /*virtual*/ Vector * Line::GetPointAt(Vector v)
391 {
392         if (v == position)
393                 return &position;
394         else if (v == endpoint)
395                 return &endpoint;
396
397         return 0;
398 }
399
400
401 #if 0
402 void Line::SetDimensionOnPoint1(Dimension * dimension)
403 {
404         dimPoint1 = dimension;
405
406         if (dimension)
407                 dimension->SetPoint1(position);
408 }
409
410 void Line::SetDimensionOnPoint2(Dimension * dimension)
411 {
412         dimPoint2 = dimension;
413
414         if (dimension)
415                 dimension->SetPoint2(endpoint);
416 }
417 #else
418 void Line::SetDimensionOnLine(Dimension * dimension/*=NULL*/)
419 {
420         // If they don't pass one in, create it for the caller.
421         if (dimension == NULL)
422         {
423 printf("Line::SetDimensionOnLine(): Creating new dimension...\n");
424 //              dimension = new Dimension(position, endpoint, DTLinear, this);
425                 dimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
426
427                 if (parent)
428 {
429 printf("Line::SetDimensionOnLine(): Adding to parent...\n");
430                         parent->Add(dimension);
431 }
432         }
433         else
434         {
435                 dimension->Connect(this, 0);
436                 dimension->Connect(this, 1.0);
437         }
438
439         // Make sure the Dimension is connected to us...
440 #if 0
441         connected.push_back(Connection(dimension, 0));
442         connected.push_back(Connection(dimension, 1.0));
443 #else
444         Connect(dimension, 0);
445         Connect(dimension, 1.0);
446 #endif
447
448 //      attachedDimension = dimension;
449
450 #if 0
451         // After we set the points here, we don't have to care about them anymore.
452         if (dimension)
453         {
454                 dimension->SetPoint1(&position);
455                 dimension->SetPoint2(&endpoint);
456         }
457 #endif
458 }
459 #endif
460
461
462 Object * Line::FindAttachedDimension(void)
463 {
464         // Is there anything connected to this line? If not, return NULL
465         if (connected.size() < 2)
466                 return NULL;
467
468         // Otherwise, we have to search our objects to see if there's a likely
469         // candidate. In this case, we're looking for a pointer to the same object
470         // with a parameter of 0 and 1 respectively. This is O((n^2)/2).
471         for(uint i=0; i<connected.size(); i++)
472         {
473                 for(uint j=i+1; j<connected.size(); j++)
474                 {
475 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);
476                         if ((connected[i].object == connected[j].object)
477                                 && ((connected[i].t == 0 && connected[j].t == 1.0)
478                                 || (connected[i].t == 1.0 && connected[j].t == 0)))
479                                 return connected[i].object;
480                 }
481         }
482
483         // Didn't find anything, so return NULL
484         return NULL;
485 }
486
487
488 bool Line::HitTest(Point point)
489 {
490         SaveState();
491
492         hitPoint1 = hitPoint2 = hitLine = false;
493         Vector lineSegment = endpoint - position;
494         Vector v1 = point - position;
495         Vector v2 = point - endpoint;
496         double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
497
498         // Geometric interpretation:
499         // The parameterized point on the vector lineSegment is where the perpendicular
500         // intersects lineSegment. If pp < 0, then the perpendicular lies beyond the 1st
501         // endpoint. If pp > length of ls, then the perpendicular lies beyond the 2nd endpoint.
502
503         if (parameterizedPoint < 0.0)
504                 distance = v1.Magnitude();
505         else if (parameterizedPoint > lineSegment.Magnitude())
506                 distance = v2.Magnitude();
507         else
508                 // distance = ?Det?(ls, v1) / |ls|
509                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
510
511         // Geometric interpretation of the above:
512         // If the segment endpoints are s and e, and the point is p, then the test
513         // for the perpendicular intercepting the segment is equivalent to insisting
514         // that the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
515         // Perpendicular distance from the point to the segment is computed by first
516         // computing the area of the triangle the three points form, then dividing by
517         // the length of the segment.  Distances are done just by the Pythagorean
518         // theorem. Twice the area of the triangle formed by three points is the
519         // determinant of the following matrix:
520         //
521         // sx sy 1       0  0  1       0  0  0
522         // ex ey 1  ==>  ex ey 1  ==>  ex ey 0
523         // px py 1       px py 1       px py 0
524         //
525         // By translating the start point to the origin, and subtracting row 1 from
526         // all other rows, we end up with the matrix on the right which greatly
527         // simplifies the calculation of the determinant.
528
529 //How do we determine distance here? Especially if zoomed in or out???
530 //#warning "!!! Distances tested for may not be valid if zoomed in or out !!!"
531 // [FIXED]
532         if ((v1.Magnitude() * Painter::zoom) < 8.0)
533                 hitPoint1 = true;
534         else if ((v2.Magnitude() * Painter::zoom) < 8.0)
535                 hitPoint2 = true;
536         else if ((distance * Painter::zoom) < 5.0)
537                 hitLine = true;
538
539         return StateChanged();
540 }
541
542 void Line::SaveState(void)
543 {
544         oldHitPoint1 = hitPoint1;
545         oldHitPoint2 = hitPoint2;
546         oldHitLine = hitLine;
547 }
548
549 bool Line::StateChanged(void)
550 {
551         if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
552                 return true;
553
554         return false;
555 }
556
557 /*
558 Intersection of two lines:
559
560 Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
561
562 When they intersect, we can set the equations equal to one another:
563
564 i + j + t (3i - j) = -i + s (j)
565
566 Equating coefficients:
567 1 + 3t = -1 and 1 - t = s
568 So t = -2/3 and s = 5/3
569
570 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 .
571
572
573 so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
574 and v2(p2x, p2y), v3(p3x, p3y) for l2.
575
576 d1 = v1 - v0, d2 = v3 - v2
577
578 Our parametric equations for the line then are:
579
580 r1 = v0 + t(d1)
581 r2 = v2 + s(d2)
582
583 Set r1 = r2, thus we have:
584
585 v0 + t(d1) = v2 + s(d2)
586
587 Taking coefficients, we have:
588
589 p0x + t(d1x) = p2x + s(d2x)
590 p0y + t(d1y) = p2y + s(d2y)
591
592 rearranging we get:
593
594 t(d1x) - s(d2x) = p2x - p0x
595 t(d1y) - s(d2y) = p2y - p0y
596
597 Determinant D is ad - bc where the matrix looks like:
598
599 a b
600 c d
601
602 so D = (d1x)(d2y) - (d2x)(d1y)
603 if D = 0, the lines are parallel.
604 Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
605 Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
606 t = Dx/D, s = Dy/D
607
608 We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
609
610 ---------------------------------------------------------------------------------------------------
611
612 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:
613
614 v1 ( bx1 , by1 );
615 v2 ( bx2 , by2 );
616 v3 ( bx3 , by3 );
617
618 Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
619
620 n1 ( -by1 , bx1 );
621 n3 ( -by3 , bx3 );
622
623 Dot products:
624
625 dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
626 dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
627
628 ratio = dp1 / dp2;
629 crossing vector = v1 * ratio;
630
631 And that's it.
632
633 -----------------------------------
634
635 So... to code this, let's say we have two Lines: l1 & l2.
636
637 Vector v1 = l1.endpoint - l1.position;
638 Vector v2 = l2.endpoint - l2.position;
639 Vector v3 = v2 - v1;
640
641 Vector normal1(-v1.y, v1.x);
642 Vector normal3(-v3.y, v3.x);
643
644 double dotProduct1 = v2.Dot(normal1);
645 double dotProduct2 = v2.Dot(normal3);
646
647 if (dotProduct2 == 0)
648         return ParallelLines;
649 else
650 {
651         // I think we'd still have to add the intersection to the position point to get the intersection...
652         Point intersection = v1 * (dotProduct1 / dotProduct2);
653         return intersection;
654 }
655 */
656
657 /*virtual*/ void Line::Enumerate(FILE * file)
658 {
659         fprintf(file, "LINE (%lf,%lf) (%lf,%lf)\n", position.x, position.y, endpoint.x, endpoint.y);
660 }
661
662
663 /*virtual*/ Object * Line::Copy(void)
664 {
665 #warning "!!! This doesn't take care of attached Dimensions !!!"
666 /*
667 This is a real problem. While having a pointer in the Dimension to this line's points is fast & easy,
668 it creates a huge problem when trying to replicate an object like this.
669
670 Maybe a way to fix that then, is to have reference numbers instead of pointers. That way, if you copy
671 them, ... you might still have problems. Because you can't be sure if a copy will be persistant or not,
672 you then *definitely* do not want them to have the same reference number.
673 */
674         return new Line(position, endpoint, parent);
675 }
676
677
678 /*virtual*/ Vector Line::GetPointAtParameter(double parameter)
679 {
680         if (parameter <= 0)
681                 return position;
682         else if (parameter >= 1.0)
683                 return endpoint;
684
685         // Our parameter lies between zero and one, so calculate it!
686         Vector v(endpoint, position);
687         double length = v.Magnitude();
688         // We scale the magnitude of v so that it lies between 0 and 1...
689         // By multiplying the parameter by the magnitude, we obtain the point we
690         // want. No scaling necessary as it's inherent in the approach!
691         double spotOnLength = length * parameter;
692
693         // To get our point, we use the initial point of the line and add in our
694         // scaled point.
695         Vector result = position + (v * spotOnLength);
696         return result;
697 }
698