]> Shamusworld >> Repos - architektonas/blobdiff - src/line.cpp
Beginnings of visual feedback for editing shapes.
[architektonas] / src / line.cpp
index b78a9dc6cd4f578a4e8d02cfe82adfe588797168..c40277da748b75b36ccec9c10b70dc2e740aa695 100644 (file)
@@ -9,15 +9,21 @@
 // WHO  WHEN        WHAT
 // ---  ----------  ------------------------------------------------------------
 // JLH  03/22/2011  Created this file
+// JLH  04/11/2011  Fixed attached dimensions to stay at correct length when
+//                  "Fixed Length" button is down
+// JLH  04/27/2011  Fixed attached dimension to stay a correct length when
+//                  "Fixed Length" button is *not* down ;-)
+// JLH  05/29/2011  Added mouseover hints
 //
 
 #include "line.h"
 
 #include <QtGui>
+#include "dimension.h"
 
 Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
        dragging(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
-       length(p2.Magnitude())
+       length(Vector::Magnitude(p2, p1)), hitPoint1(false), hitPoint2(false), hitLine(false)
 {
 }
 
@@ -27,19 +33,17 @@ Line::~Line()
 
 /*virtual*/ void Line::Draw(QPainter * painter)
 {
-       if (state == OSSelected)
-               painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
-       else
-               painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
+       painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
 
-//     if (draggingHandle1)
-       if (state == OSSelected)
+       if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
                painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
 
-//     if (draggingHandle2)
-       if (state == OSSelected)
+       if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
                painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
 
+       if ((state == OSInactive) && !hitLine)
+               painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
+
        if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
        {
                Vector point1 = (draggingHandle1 ? endpoint : position);
@@ -69,45 +73,66 @@ Line::~Line()
 
 /*virtual*/ bool Line::Collided(Vector point)
 {
+// Can't assume this!
+// Actually, we can, since this is a mouse down event here.
        objectWasDragged = false;
-       Vector lineSegment = endpoint - position;
-       Vector v1 = point - position;
-       Vector v2 = point - endpoint;
-       double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
+       HitTest(point);
 
-       // Geometric interpretation:
-       // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
-       // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
-       // then the perpendicular lies beyond the 2nd endpoint.
+/*
+There's a small problem here with the implementation: You can have a dimension tied
+to only one point while at the same time you can have a dimension sitting on this line.
+Since there's only *one* dimPoint for each point, this can be problematic...
 
-       if (parameterizedPoint < 0.0)
-               distance = v1.Magnitude();
-       else if (parameterizedPoint > lineSegment.Magnitude())
-               distance = v2.Magnitude();
-       else                                    // distance = ?Det?(ls, v1) / |ls|
-               distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
+Also: It would be nice to have a preview of the dimension being drawn, with a modifier
+key to make it draw/show on the other side...
+*/
+       // Is the dimension tool active? Let's use it:
+       if (dimensionActive)
+       {
+               // User clicked on the line itself (endpoint checks should preceed this one):
+               // (Priorities are taken care of in HitTest()...)
+               if (hitLine)
+               {
+                       // If there's one already there, tell it to flip sides...
+                       if (dimPoint1 && (dimPoint1 == dimPoint2))
+                       {
+// Here's an interesting problem... When swapping points we can either do it
+// on the dimension or directly here... I think swapping endpoint & position is
+// probably the wrong way to do things here...
+#if 1
+                               // Hm, why do we have to do both here???
+                               dimPoint1->FlipSides();
+                               //dis don't do shit
+/*                             Vector temp = dimPoint1;
+                               dimPoint1 = dimPoint2;
+                               dimPoint2 = temp;//*/
+#else
+// It doesn't work correctly anyhow, not until you move an endpoint.
+                               Vector temp = position;
+                               position = endpoint;
+                               endpoint = temp;
+#endif
+                       }
+                       else if ((dimPoint1 == NULL) && (dimPoint2 == NULL))
+                       {
+                               // How to get this object into the top level container???
+/*
+The real question is do we care. I think so, because if this isn't in the top
+level container, it won't get drawn...
+But we can fix that by making this object call any attached object's (like
+a dimension only) Draw() function... :-/
+*/
+                               dimPoint1 = new Dimension(position, endpoint, this);
+                               dimPoint2 = dimPoint1;
+
+                               if (parent != NULL)
+                                       parent->Add(dimPoint1);
+                       }
+
+                       return true;
+               }
+       }
 
-       // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
-       // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
-       // {e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
-       // computed by first computing the area of the triangle the three points form, then dividing by the
-       // length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
-       // triangle formed by three points is the determinant of the following matrix:
-       //
-       // sx sy 1
-       // ex ey 1
-       // px py 1
-       //
-       // By translating the start point to the origin, this can be rewritten as:
-       // By subtracting row 1 from all rows, you get the following:
-       // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
-       // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
-       //
-       // 0         0         0        0  0  0
-       // (ex - sx) (ey - sy) 0   ==>  ex ey 0
-       // (px - sx) (py - sy) 0        px py 0
-       //
-       // which greatly simplifies the calculation of the determinant.
 
        if (state == OSInactive)
        {
@@ -118,7 +143,7 @@ Line::~Line()
 //How to translate this into pixels from Document space???
 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
 //the caller knows about the zoom factor and all that good kinda crap
-               if (v1.Magnitude() < 10.0)
+               if (hitPoint1)
                {
                        oldState = state;
                        state = OSSelected;
@@ -126,7 +151,7 @@ Line::~Line()
                        draggingHandle1 = true;
                        return true;
                }
-               else if (v2.Magnitude() < 10.0)
+               else if (hitPoint2)
                {
                        oldState = state;
                        state = OSSelected;
@@ -134,7 +159,7 @@ Line::~Line()
                        draggingHandle2 = true;
                        return true;
                }
-               else if (distance < 2.0)
+               else if (hitLine)
                {
                        oldState = state;
                        state = OSSelected;
@@ -145,13 +170,13 @@ Line::~Line()
        }
        else if (state == OSSelected)
        {
-               // Here we test for collision with handles as well! (SOON!)
+               // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
 /*
 Like so:
                if (v1.Magnitude() < 2.0) // Handle #1
                else if (v2.Magnitude() < 2.0) // Handle #2
 */
-               if (distance < 2.0)
+               if (hitLine)
                {
                        oldState = state;
 //                     state = OSInactive;
@@ -167,42 +192,51 @@ Like so:
 
 /*virtual*/ void Line::PointerMoved(Vector point)
 {
-       // We know this is true because mouse move messages don't come here unless
-       // the object was actually clicked on--therefore we *know* we're being
-       // dragged...
-       objectWasDragged = true;
+       needUpdate = HitTest(point);
 
-       if (dragging)
-       {
-               // Here we need to check whether or not we're dragging a handle or the object itself...
-               Vector delta = point - oldPoint;
-
-               position += delta;
-               endpoint += delta;
+       objectWasDragged = (dragging | draggingHandle1 | draggingHandle2);
 
-               oldPoint = point;
-               needUpdate = true;
-       }
-       else if (draggingHandle1)
+       if (objectWasDragged)
        {
                Vector delta = point - oldPoint;
 
-               position += delta;
+               if (draggingHandle1 || dragging)
+                       position += delta;
+
+               if (draggingHandle2 || dragging)
+                       endpoint += delta;
 
                oldPoint = point;
                needUpdate = true;
        }
-       else if (draggingHandle2)
+
+/*
+We can't count on any coupling between the dimension object, so how do we do this???
+*/
+       if (needUpdate)
        {
-               Vector delta = point - oldPoint;
+// should only do this if "Fixed Length" is set... !!! FIX !!! [DONE]
+               Vector point1 = (draggingHandle1 ? endpoint : position);
+               Vector point2 = (draggingHandle1 ? position : endpoint);
 
-               endpoint += delta;
+               Vector current(point2, point1);
+               Vector v = current.Unit() * length;
+               Vector v2 = point1 + v;
 
-               oldPoint = point;
-               needUpdate = true;
+               //bleh
+               if (!Object::fixedLength)
+                       v2 = point2;
+
+//If we tell the dimension to flip sides, this is no longer a valid
+//assumption. !!! FIX !!!
+//Ideally, we should just send the point that's changing to the Dimension object
+//and have it figure out which point needs to move... Or is it???
+               if (dimPoint1)
+                       dimPoint1->SetPoint1(draggingHandle1 ? v2 : position);
+               
+               if (dimPoint2)
+                       dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
        }
-       else
-               needUpdate = false;
 }
 
 /*virtual*/ void Line::PointerReleased(void)
@@ -212,7 +246,6 @@ Like so:
                // Set the length (in case the global state was set to fixed (or not))
                if (Object::fixedLength)
                {
-
                        if (draggingHandle1)    // startpoint
                        {
                                Vector v = Vector(position - endpoint).Unit() * length;
@@ -237,6 +270,8 @@ Like so:
        draggingHandle1 = false;
        draggingHandle2 = false;
 
+//     hitPoint1 = hitPoint2 = hitLine = false;
+
        // Here we check for just a click: If object was clicked and dragged, then
        // revert to the old state (OSInactive). Otherwise, keep the new state that
        // we set.
@@ -247,9 +282,187 @@ about keeping track of old states...
                state = oldState;
 }
 
-#if 0
-/*virtual*/ bool Line::NeedsUpdate(void)
+void Line::SetDimensionOnPoint1(Dimension * dimension)
 {
-       return needUpdate;
+       dimPoint1 = dimension;
+
+       if (dimension)
+               dimension->SetPoint1(position);
 }
-#endif
+
+void Line::SetDimensionOnPoint2(Dimension * dimension)
+{
+       dimPoint2 = dimension;
+
+       if (dimension)
+               dimension->SetPoint2(endpoint);
+}
+
+bool Line::HitTest(Point point)
+{
+       SaveState();
+
+       hitPoint1 = hitPoint2 = hitLine = false;
+       Vector lineSegment = endpoint - position;
+       Vector v1 = point - position;
+       Vector v2 = point - endpoint;
+       double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
+
+       // Geometric interpretation:
+       // The parameterized point on the vector lineSegment is where the perpendicular
+       // intersects lineSegment. If pp < 0, then the perpendicular lies beyond the 1st
+       // endpoint. If pp > length of ls, then the perpendicular lies beyond the 2nd endpoint.
+
+       if (parameterizedPoint < 0.0)
+               distance = v1.Magnitude();
+       else if (parameterizedPoint > lineSegment.Magnitude())
+               distance = v2.Magnitude();
+       else
+               // distance = ?Det?(ls, v1) / |ls|
+               distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
+
+       // Geometric interpretation of the above:
+       // If the segment endpoints are s and e, and the point is p, then the test
+       // for the perpendicular intercepting the segment is equivalent to insisting
+       // that the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
+       // Perpendicular distance from the point to the segment is computed by first
+       // computing the area of the triangle the three points form, then dividing by
+       // the length of the segment.  Distances are done just by the Pythagorean
+       // theorem. Twice the area of the triangle formed by three points is the
+       // determinant of the following matrix:
+       //
+       // sx sy 1       0  0  1       0  0  0
+       // ex ey 1  ==>  ex ey 1  ==>  ex ey 0
+       // px py 1       px py 1       px py 0
+       //
+       // By translating the start point to the origin, and subtracting row 1 from
+       // all other rows, we end up with the matrix on the right which greatly
+       // simplifies the calculation of the determinant.
+
+//How do we determine distance here? Especially if zoomed in or out???
+#warning "!!! Distances tested for may not be valid if zoomed in or out !!!"
+       if (v1.Magnitude() < 8.0)
+               hitPoint1 = true;
+       else if (v2.Magnitude() < 8.0)
+               hitPoint2 = true;
+       else if (distance < 5.0)
+               hitLine = true;
+
+       return StateChanged();
+}
+
+void Line::SaveState(void)
+{
+       oldHitPoint1 = hitPoint1;
+       oldHitPoint2 = hitPoint2;
+       oldHitLine = hitLine;
+}
+
+bool Line::StateChanged(void)
+{
+       if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
+               return true;
+
+       return false;
+}
+
+/*
+Intersection of two lines:
+
+Find where the lines with equations r = i + j + t (3i - j) and r = -i + s (j) intersect.
+
+When they intersect, we can set the equations equal to one another:
+
+i + j + t (3i - j) = -i + s (j)
+
+Equating coefficients:
+1 + 3t = -1 and 1 - t = s
+So t = -2/3 and s = 5/3
+
+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 .
+
+
+so, let's say we have two lines, l1 and l2. Points are v0(p0x, p0y), v1(p1x, p1y) for l1
+and v2(p2x, p2y), v3(p3x, p3y) for l2.
+
+d1 = v1 - v0, d2 = v3 - v2
+
+Our parametric equations for the line then are:
+
+r1 = v0 + t(d1)
+r2 = v2 + s(d2)
+
+Set r1 = r2, thus we have:
+
+v0 + t(d1) = v2 + s(d2)
+
+Taking coefficients, we have:
+
+p0x + t(d1x) = p2x + s(d2x)
+p0y + t(d1y) = p2y + s(d2y)
+
+rearranging we get:
+
+t(d1x) - s(d2x) = p2x - p0x
+t(d1y) - s(d2y) = p2y - p0y
+
+Determinant D is ad - bc where the matrix look like:
+
+a b
+c d
+
+so D = (d1x)(d2y) - (d2x)(d1y)
+if D = 0, the lines are parallel.
+Dx = (p2x - p0x)(d2y) - (d2x)(p2y - p0y)
+Dy = (d1x)(p2y - p0y) - (p2x - p0x)(d1y)
+t = Dx/D, s = Dy/D
+
+We only need to calculate t, as we can then multiply it by d1 to get the intersection point.
+
+---------------------------------------------------------------------------------------------------
+
+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:
+
+v1 ( bx1 , by1 );
+v2 ( bx2 , by2 );
+v3 ( bx3 , by3 );
+
+Perp product is equal with dot product of normal of first vector and the second vector, so we need normals:
+
+n1 ( -by1 , bx1 );
+n3 ( -by3 , bx3 );
+
+Dot products:
+
+dp1 = n3 . v2 = -by3 * bx2 + bx3 * by2;
+dp2 = n1 . v2 = -by1 * bx2 + bx1 * by2;
+
+ratio = dp1 / dp2;
+crossing vector = v1 * ratio;
+
+And that's it.
+
+-----------------------------------
+
+So... to code this, let's say we have two Lines: l1 & l2.
+
+Vector v1 = l1.endpoint - l1.position;
+Vector v2 = l2.endpoint - l2.position;
+Vector v3 = v2 - v1;
+
+Vector normal1(-v1.y, v1.x);
+Vector normal3(-v3.y, v3.x);
+
+double dotProduct1 = v2.Dot(normal1);
+double dotProduct2 = v2.Dot(normal3);
+
+if (dotProduct2 == 0)
+       return ParallelLines;
+else
+{
+       // I think we'd still have to add the intersection to the position point to get the intersection...
+       Point intersection = v1 * (dotProduct1 / dotProduct2);
+       return intersection;
+}
+*/
+