]> Shamusworld >> Repos - architektonas/commitdiff
Added new triangulation tool, ability to snap to endpoints/intersections.
authorShamus Hammons <jlhamm@acm.org>
Mon, 19 May 2014 18:36:53 +0000 (13:36 -0500)
committerShamus Hammons <jlhamm@acm.org>
Mon, 19 May 2014 18:36:53 +0000 (13:36 -0500)
Note that the new snap functionality is not yet complete; there are still
gaps in functionality for different entities. It's almost in a usuable
state--just have to tweak a few things here and there and I can put out a
1.0.0 for general consumption. :-)

22 files changed:
architektonas.pro
res/architektonas.qrc
res/atns-icon.png
res/triangulate-tool.png [new file with mode: 0644]
src/applicationwindow.cpp
src/applicationwindow.h
src/arc.cpp
src/circle.cpp
src/container.cpp
src/container.h
src/dimension.cpp
src/drawingview.cpp
src/geometry.cpp
src/geometry.h
src/line.cpp
src/object.cpp
src/object.h
src/triangulateaction.cpp [new file with mode: 0644]
src/triangulateaction.h [new file with mode: 0644]
src/trimaction.cpp
src/vector.cpp
src/vector.h

index ce6ce577d30253b8953dc8ffcbd9aa8949b8edcb..53c36b2ac9563a652b396ed0d6c8358968e73982 100644 (file)
@@ -80,6 +80,7 @@ HEADERS = \
        src/settingsdialog.h \
        src/spline.h \
        src/text.h \
+       src/triangulateaction.h \
        src/trimaction.h \
        src/vector.h
 
@@ -117,6 +118,7 @@ SOURCES = \
        src/settingsdialog.cpp \
        src/spline.cpp \
        src/text.cpp \
+       src/triangulateaction.cpp \
        src/trimaction.cpp \
        src/vector.cpp
 
index c8e8f908ce1855fede0bb84fe11948110e028ad0..afe168c82d60b7abe76af56774d16da96470b058 100644 (file)
@@ -17,6 +17,7 @@
                <file>snap-to-grid-tool.png</file>
                <file>splash.png</file>
                <file>trim-tool.png</file>
+               <file>triangulate-tool.png</file>
                <file>quit.png</file>
                <file>zoom-in.png</file>
                <file>zoom-out.png</file>
index b98b5b473cd50124fd49675149e2c4bbb92baa50..9084dd2c80d609c8e60cf5d18883efbc8afbb258 100644 (file)
Binary files a/res/atns-icon.png and b/res/atns-icon.png differ
diff --git a/res/triangulate-tool.png b/res/triangulate-tool.png
new file mode 100644 (file)
index 0000000..7af66bb
Binary files /dev/null and b/res/triangulate-tool.png differ
index 23c865427f64ecc2971695db553567908cb870ff..ad3bc8d9ec0b4c209087f7d58d39e8c79c427fc2 100644 (file)
@@ -46,6 +46,7 @@
 #include "painter.h"
 #include "rotateaction.h"
 #include "settingsdialog.h"
+#include "triangulateaction.h"
 #include "trimaction.h"
 
 
@@ -276,6 +277,13 @@ void ApplicationWindow::TrimTool(void)
 }
 
 
+void ApplicationWindow::TriangulateTool(void)
+{
+       ClearUIToolStatesExcept(triangulateAct);
+       SetInternalToolStates();
+}
+
+
 void ApplicationWindow::AddLineTool(void)
 {
        ClearUIToolStatesExcept(addLineAct);
@@ -419,6 +427,9 @@ void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
 
        if (exception != trimAct)
                trimAct->setChecked(false);
+
+       if (exception != triangulateAct)
+               triangulateAct->setChecked(false);
 }
 
 
@@ -445,6 +456,7 @@ void ApplicationWindow::SetInternalToolStates(void)
        drawing->SetToolActive(mirrorAct->isChecked() ? new MirrorAction() : NULL);
        drawing->SetToolActive(rotateAct->isChecked() ? new RotateAction() : NULL);
        drawing->SetToolActive(trimAct->isChecked() ? new TrimAction() : NULL);
+       drawing->SetToolActive(triangulateAct->isChecked() ? new TriangulateAction() : NULL);
 
        if (drawing->toolAction)
                Object::ignoreClicks = true;
@@ -730,6 +742,9 @@ void ApplicationWindow::CreateActions(void)
        trimAct = CreateAction(tr("&Trim"), tr("Trim"), tr("Trim extraneous lines from selected objects."), QIcon(":/res/trim-tool.png"), QKeySequence("t,r"), true);
        connect(trimAct, SIGNAL(triggered()), this, SLOT(TrimTool()));
 
+       triangulateAct = CreateAction(tr("&Triangulate"), tr("Triangulate"), tr("Make triangles from selected lines, preserving their lengths."), QIcon(":/res/triangulate-tool.png"), QKeySequence("t,g"), true);
+       connect(triangulateAct, SIGNAL(triggered()), this, SLOT(TriangulateTool()));
+
 
 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
@@ -801,6 +816,7 @@ void ApplicationWindow::CreateMenus(void)
        menu->addAction(rotateAct);
        menu->addAction(mirrorAct);
        menu->addAction(trimAct);
+       menu->addAction(triangulateAct);
        menu->addAction(connectAct);
        menu->addAction(disconnectAct);
        menu->addSeparator();
@@ -851,6 +867,7 @@ void ApplicationWindow::CreateToolbars(void)
        toolbar->addAction(rotateAct);
        toolbar->addAction(mirrorAct);
        toolbar->addAction(trimAct);
+       toolbar->addAction(triangulateAct);
        toolbar->addAction(deleteAct);
        toolbar->addAction(connectAct);
        toolbar->addAction(disconnectAct);
index b695c1ed06059ac72c6cc618b946e03d4ec466a0..e2e49345bf2015f0f30af62894e3f83195a55153 100644 (file)
@@ -35,6 +35,7 @@ class ApplicationWindow: public QMainWindow
                void RotateTool(void);
                void MirrorTool(void);
                void TrimTool(void);
+               void TriangulateTool(void);
                void AddLineTool(void);
                void AddCircleTool(void);
                void AddArcTool(void);
@@ -98,6 +99,7 @@ class ApplicationWindow: public QMainWindow
                QAction * disconnectAct;
                QAction * mirrorAct;
                QAction * trimAct;
+               QAction * triangulateAct;
 
        // Class variables
        public:
index d4d747b7df80d1a3a7dd7cb8c4e1010609e82b7a..f5078694d9a27a425b93ab98aca044997cf4248d 100644 (file)
@@ -168,6 +168,9 @@ Also: should put the snap logic into the Object base class (as a static method).
        if (snapToGrid)
                point = SnapPointToGrid(point);
 
+       if (snapPointIsValid)
+               point = snapPoint;
+
 /*
 State Management:
 We want the arc to go into OSSelected mode if we click on it but don't drag.
@@ -241,6 +244,13 @@ so let's do like this:
        SaveHitState();
        bool hovered = HitTest(point);
        needUpdate = HitStateChanged();
+
+       if (snapToGrid)
+               point = SnapPointToGrid(point);
+
+       if (snapPointIsValid)
+               point = snapPoint;
+
        objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
 
        if (objectWasDragged)
@@ -324,14 +334,26 @@ point (to get the correct position).
                hitArc = true;
 #else
        if ((length * Painter::zoom) < 8.0)
+       {
                hitCenter = true;
+               snapPoint = position;
+               snapPointIsValid = true;
+       }
        else if (((fabs(length - radius) * Painter::zoom) < 2.0)
                && AngleInArcSpan(pointerAngle))
                hitArc = true;
        else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
+       {
                hitRotate = true;
+               snapPoint = handle2;
+               snapPointIsValid = true;
+       }
        else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
+       {
                hitSpan = true;
+               snapPoint = handle3;
+               snapPointIsValid = true;
+       }
 #endif
 
        return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
index 94a9ec8f807b47b5e867bb3e5fab79b951c46c3a..093f53395834bd3472a05a3242ad148bd1cc8759 100644 (file)
@@ -100,6 +100,9 @@ Circle::~Circle()
        if (snapToGrid)
                point = SnapPointToGrid(point);
 
+       if (snapPointIsValid)
+               point = snapPoint;
+
        draggingCenter = hitCenter;
        draggingEdge = hitCircle;
 
@@ -137,6 +140,13 @@ Circle::~Circle()
        SaveHitState();
        bool hovered = HitTest(point);
        needUpdate = HitStateChanged();
+
+       if (snapToGrid)
+               point = SnapPointToGrid(point);
+
+       if (snapPointIsValid)
+               point = snapPoint;
+
        objectWasDragged = (draggingEdge | draggingCenter);
 
        if (objectWasDragged)
@@ -186,7 +196,11 @@ the radius will be 5.0. By multiplying the length by the zoom factor, we align o
 pointed at length with our on screen length.
 */
        if ((length * Painter::zoom) < 8.0)
+       {
                hitCenter = true;
+               snapPoint = position;
+               snapPointIsValid = true;
+       }
 //wrong:       else if ((length < (radius + 2.0)) && (length > (radius - 2.0)))
 /*NB: The following should be identical to what we have down below, but it doesn't work out that way... :-P */
 //close, but no        else if (((length * Painter::zoom) < ((radius * Painter::zoom) + 2.0)) && ((length * Painter::zoom) > ((radius * Painter::zoom) - 2.0)))
index 2eca9f4573473c0c772a4efd96060836e6bc15ec..9c3d403bf74b8864af6cfea0b0a4a0f2e036c520 100644 (file)
@@ -204,7 +204,7 @@ class so that we can leverage that stuff here as well.
 /*virtual*/ bool Container::PointerMoved(Vector point)
 {
        std::vector<Object *>::iterator i;
-       lastObjectHovered = NULL;
+       lastObjectHovered = penultimateObjectHovered = NULL;
 
        if (!isTopLevelContainer)
        {
@@ -261,7 +261,10 @@ class so that we can leverage that stuff here as well.
        {
 //             if (objects[i]->GetState() == OSSelected)
                if ((*i)->PointerMoved(point))
+               {
+                       penultimateObjectHovered = lastObjectHovered;
                        lastObjectHovered = *i;
+               }
        }
 
        // Generic container doesn't need this???
index 7de5d8918c175e2e08a221eae603005ceed5d001..7d5ef4cf8e01f4873347ca8325d982793b60b616 100644 (file)
@@ -50,6 +50,7 @@ class Container: public Object
                bool isTopLevelContainer;
                Object * lastObjectClicked;
                Object * lastObjectHovered;
+               Object * penultimateObjectHovered;
        private:
                bool dragging;
                bool draggingHandle1;
index e5a747842a32b8f6baa696c70f0dbf8219cdd9d8..1e9b785e0be7d2981a30c892d2d05360627378ba 100644 (file)
@@ -264,6 +264,9 @@ I believe they are pixels.
        if (snapToGrid)
                point = SnapPointToGrid(point);
 
+       if (snapPointIsValid)
+               point = snapPoint;
+
        if (hitPoint1)
        {
                oldState = state;
@@ -335,6 +338,12 @@ I believe they are pixels.
        bool hovered = HitTest(point);
        needUpdate = HitStateChanged();
 
+       if (snapToGrid)
+               point = SnapPointToGrid(point);
+
+       if (snapPointIsValid)
+               point = snapPoint;
+
        objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
 
        if (objectWasDragged)
@@ -517,7 +526,7 @@ bool Dimension::HitStateChanged(void)
 
 /*virtual*/ void Dimension::Enumerate(FILE * file)
 {
-       fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
+       fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
 }
 
 
index acfad409c2f22e685e49c646de30c355039684df..fc30ac8e259ce61a092b35210f2ab6280ce2b457 100644 (file)
@@ -35,6 +35,7 @@
 #include "arc.h"
 #include "circle.h"
 #include "dimension.h"
+#include "geometry.h"
 #include "line.h"
 #include "painter.h"
 
@@ -343,6 +344,11 @@ void DrawingView::mousePressEvent(QMouseEvent * event)
                        if (Object::snapToGrid)
                                point = Object::SnapPointToGrid(point);
 
+                       // We always snap to object points, and they take precendence over
+                       // grid points...
+                       if (Object::snapPointIsValid)
+                               point = Object::snapPoint;
+
                        toolAction->MouseDown(point);
                }
 
@@ -368,13 +374,15 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event)
 {
        Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
        Object::selection.setBottomRight(QPointF(point.x, point.y));
+       // Only needs to be done here, as mouse down is always preceded by movement
+       Object::snapPointIsValid = false;
 
+       // Scrolling...
        if (event->buttons() & Qt::MiddleButton)
        {
                point = Vector(event->x(), event->y());
                // Since we're using Qt coords for scrolling, we have to adjust them here to
                // conform to Cartesian coords, since the origin is using Cartesian. :-)
-//             Vector delta(point, oldPoint);
                Vector delta(oldPoint, point);
                delta /= Painter::zoom;
                delta.y = -delta.y;
@@ -386,31 +394,96 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event)
                return;
        }
 
-       // Grid processing...
+#if 0
+       // Grid processing... (only snap here is left button is down)
        if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
        {
                point = Object::SnapPointToGrid(point);
        }
 
-       oldPoint = point;
+       // Snap points on objects always take precedence over the grid, whether
+       // dragging an object or not...
+//thisnowok
+       if (Object::snapPointIsValid)
+       {
+// Uncommenting this causes the cursor to become unresponsive after the first
+// object is added.
+//             point = Object::snapPoint;
+       }
+#endif
+
+//     oldPoint = point;
 //we should keep track of the last point here and only pass this down *if* the point
 //changed...
-       document.PointerMoved(point);
 
-       if (document.NeedsUpdate() || Object::selectionInProgress)
-               update();
+       // This returns true if we've moved over an object...
+       if (document.PointerMoved(point))
+       {
+               // Do object snapping here. Grid snapping on mouse down is done in the
+               // objects themselves, only because we have to hit test the raw point,
+               // not the snapped point. There has to be a better way...!
+               if (document.penultimateObjectHovered)
+               {
+                       // Two objects are hovered, see if we have an intersection point
+                       if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
+                       {
+                               double t;
+                               int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
+
+                               if (n == 1)
+                               {
+                                       Object::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
+                                       Object::snapPointIsValid = true;
+                               }
+                       }
+                       else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
+                       {
+                               Point p1, p2;
+                               int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
+
+                               if (n == 1)
+                               {
+                                       Object::snapPoint = p1;
+                                       Object::snapPointIsValid = true;
+                               }
+                               else if (n == 2)
+                               {
+                                       double d1 = Vector(point, p1).Magnitude();
+                                       double d2 = Vector(point, p2).Magnitude();
+
+                                       if (d1 < d2)
+                                               Object::snapPoint = p1;
+                                       else
+                                               Object::snapPoint = p2;
+
+                                       Object::snapPointIsValid = true;
+                               }
+                       }
+               }
+//             else
+//             {
+                       // Otherwise, it was a single object hovered...
+//             }
+       }
 
        if (toolAction)
        {
                if (Object::snapToGrid)
-               {
                        point = Object::SnapPointToGrid(point);
-                       oldPoint = point;
-               }
+
+               // We always snap to object points, and they take precendence over
+               // grid points...
+               if (Object::snapPointIsValid)
+                       point = Object::snapPoint;
 
                toolAction->MouseMoved(point);
-               update();
        }
+
+       // This is used to draw the tool crosshair...
+       oldPoint = point;
+
+       if (document.NeedsUpdate() || Object::selectionInProgress || toolAction)
+               update();
 }
 
 
index 058dbfa14c06895ce659db9310ad778e57f7c925..1ec93db7e1430b2752cc4e77a2b2d9d30ce61aeb 100644 (file)
@@ -18,6 +18,7 @@
 #include "circle.h"
 #include "dimension.h"
 #include "line.h"
+#include "mathconstants.h"
 
 
 Point Geometry::IntersectionOfLineAndLine(Point p1, Point p2, Point p3, Point p4)
@@ -215,7 +216,7 @@ int Geometry::Intersects(Line * l1, Dimension * d1, double * tp/*= 0*/, double *
 }
 
 
-// Finds the intesection(s) between a line and a circle (if any)
+// Finds the intersection(s) between a line and a circle (if any)
 int Geometry::Intersects(Line * l, Circle * c, double * tp/*= 0*/, double * up/*= 0*/, double * vp/*= 0*/, double * wp/*= 0*/)
 {
 #if 0
@@ -263,7 +264,16 @@ I'm thinking a better approach to this might be as follows:
        // Now we have to check for intersection points.
        // Tangent case: (needs to return something)
        if ((distance == c->radius) && (t >= 0.0) && (t <= 1.0))
+       {
+               // Need to set tp & up to something... !!! FIX !!!
+               if (tp)
+                       *tp = t;
+
+               if (up)
+                       *up = Vector(c->position, p).Angle();
+
                return 1;
+       }
 
        // The line intersects the circle in two points (possibly). Use Pythagorus
        // to find them for testing.
@@ -300,3 +310,116 @@ I'm thinking a better approach to this might be as follows:
 }
 
 
+// Finds the intersection(s) between a circle and a circle (if any)
+// There can be 0, 1, or 2 intersections.
+// Returns the angles of the points of intersection in tp thru wp, with the
+// angles returned as c1, c2, c1, c2 (if applicable--in the 1 intersection case,
+// only the first two angles are returned: c1, c2).
+int Geometry::Intersects(Circle * c1, Circle * c2, double * tp/*= 0*/, double * up/*= 0*/, double * vp/*= 0*/, double * wp/*= 0*/, Point * p1/*= 0*/, Point * p2/*= 0*/)
+{
+       // Get the distance between centers. If the distance plus the radius of the
+       // smaller circle is less than the radius of the larger circle, there is no
+       // intersection. If the distance is greater than the sum of the radii,
+       // there is no intersection. If the distance is equal to the sum of the
+       // radii, they are tangent and intersect at one point. Otherwise, they
+       // intersect at two points.
+       Vector centerLine(c1->position, c2->position);
+       double d = centerLine.Magnitude();
+//printf("Circle #1: pos=<%lf, %lf>, r=%lf\n", c1->position.x, c1->position.y, c1->radius);
+//printf("Circle #2: pos=<%lf, %lf>, r=%lf\n", c2->position.x, c2->position.y, c2->radius);
+//printf("Distance between #1 & #2: %lf\n", d);
+
+       // Check to see if we actually have an intersection, and return failure if not
+       if ((fabs(c1->radius - c2->radius) > d) || ((c1->radius + c2->radius) < d))
+               return 0;
+
+       // There are *two* tangent cases!
+       if (((c1->radius + c2->radius) == d) || (fabs(c1->radius - c2->radius) == d))
+       {
+               // Need to return something in tp & up!! !!! FIX !!! [DONE]
+               if (tp)
+                       *tp = centerLine.Angle();
+
+               if (up)
+                       *up = centerLine.Angle() + PI;
+
+               return 1;
+       }
+
+       // Find the distance from the center of c1 to the perpendicular chord
+       // (which contains the points of intersection)
+       double x = ((d * d) - (c2->radius * c2->radius) + (c1->radius * c1->radius))
+               / (2.0 * d);
+       // Find the the length of the perpendicular chord
+// Not needed...!
+       double a = sqrt((-d + c2->radius - c1->radius) * (-d - c2->radius + c1->radius) * (-d + c2->radius + c1->radius) * (d + c2->radius + c1->radius)) / d;
+
+       // Now, you can use pythagorus to find the length of the hypotenuse, but we
+       // already know that length, it's the radius! :-P
+       // What's needed is the angle of the center line and the radial line. Since
+       // there's two intersection points, there's also four angles (two for each
+       // circle)!
+       // We can use the arccos to find the angle using just the radius and the
+       // distance to the perpendicular chord...!
+       double angleC1 = acos(x / c1->radius);
+       double angleC2 = acos((d - x) / c2->radius);
+
+       if (tp)
+               *tp = centerLine.Angle() - angleC1;
+
+       if (up)
+               *up = (centerLine.Angle() + PI) - angleC2;
+
+       if (vp)
+               *vp =  centerLine.Angle() + angleC1;
+
+       if (wp)
+               *wp = (centerLine.Angle() + PI) + angleC2;
+
+       if (p1)
+               *p1 = c1->position + (centerLine.Unit() * x) + (Vector::Normal(Vector(), centerLine) * (a / 2.0));
+
+       if (p2)
+               *p2 = c1->position + (centerLine.Unit() * x) - (Vector::Normal(Vector(), centerLine) * (a / 2.0));
+
+       return 2;
+}
+
+
+// should we just do common trig solves, like AAS, ASA, SAS, SSA?
+// Law of Cosines:
+// c^2 = a^2 + b^2 -2ab*cos(C)
+// Solving for C:
+// cos(C) = (c^2 - a^2 - b^2) / -2ab = (a^2 + b^2 - c^2) / 2ab
+// Law of Sines:
+// a / sin A = b / sin B = c / sin C
+
+// Solve the angles of the triangle given the sides. Angles returned are
+// opposite of the given sides (so a1 consists of sides s2 & s3, and so on).
+void Geometry::FindAnglesForSides(double s1, double s2, double s3, double * a1, double * a2, double * a3)
+{
+       // Use law of cosines to find 1st angle
+       double cosine1 = ((s2 * s2) + (s3 * s3) - (s1 * s1)) / (2.0 * s2 * s3);
+
+       // Check for a valid triangle
+       if ((cosine1 < -1.0) || (cosine1 > 1.0))
+               return;
+
+       double angle1 = acos(cosine1);
+
+       // Use law of sines to find 2nd & 3rd angles
+// sin A / a = sin B / b
+// sin B = (sin A / a) * b
+       double angle2 = asin(s2 * (sin(angle1) / s1));
+       double angle3 = asin(s3 * (sin(angle1) / s1));
+
+       if (a1)
+               *a1 = angle1;
+
+       if (a2)
+               *a2 = angle2;
+
+       if (a3)
+               *a3 = angle3;
+}
+
index d19d48c8c1735fdd81d2f89494dff85c3abc0a95..410c7e05e501435098045ce154638876a3ee4316 100644 (file)
@@ -19,6 +19,8 @@ class Geometry
                static int Intersects(Line *, Line *, double * tp = 0, double * up = 0);
                static int Intersects(Line *, Dimension *, double * tp = 0, double * up = 0);
                static int Intersects(Line * l, Circle * c, double * tp = 0, double * up = 0, double * vp = 0, double * wp = 0);
+               static int Intersects(Circle * c1, Circle * c2, double * tp = 0, double * up = 0, double * vp = 0, double * wp = 0, Point * p1 = 0, Point * p2 = 0);
+               static void FindAnglesForSides(double s1, double s2, double s3, double * a1, double * a2, double * a3);
 };
 
 #endif         // __GEOMETRY_H__
index 46a6d85860c8d598b8f76497f9ea4ace005bb20d..7841fa8a823e43720d327985e16bf44370eb0ce2 100644 (file)
@@ -131,7 +131,6 @@ Actually, this is done here to keep tools from selecting stuff inadvertantly...
 
        // Someone told us to fuck off, so we'll fuck off. :-)
        if (ignoreClicks)
-//             return false;
                return hit;
 
        // Now that we've done our hit testing on the non-snapped point, snap it if
@@ -139,6 +138,9 @@ Actually, this is done here to keep tools from selecting stuff inadvertantly...
        if (snapToGrid)
                point = SnapPointToGrid(point);
 
+       if (snapPointIsValid)
+               point = snapPoint;
+
 // this is shite. this should be checked for in the Container, not here!
 #warning "!!! This should be checked for in Container, not here !!!"
        // If we're part of a non-top-level container, send this signal to it
@@ -149,87 +151,6 @@ Actually, this is done here to keep tools from selecting stuff inadvertantly...
                return true;
        }
 
-/*
-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...
-
-We solve this by allowing only *one* Dimension object to be attached to the Line,
-Arc, etc. and by giving the Dimension object a pointer to our endpoints.
-
-Problem still arises when we delete this object; The attached Dimension object will
-then have bad pointers! What it *should* do is delete the object if and only if this
-line is not attached to any other object. If it is, then one of those attachment
-points should be sent to the dimension object (done for position & endpoint).
-
-NOTE: The STL vector<T> *does not* take ownership of pointers, therefore is suitable
-      for our purposes
-
-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...
-
-TODO: Make Dimension preview with modifier keys for showing on other side
-*/
-/*
-
-N.B.: This no longer works, as the DrawDimension object takes precedence over this code.
-      THIS DOES NOTHING ANYMORE!!!
-
-*/
-#if 0
-       // 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 0
-                       if (attachedDimension == 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... :-/
-*/
-                               attachedDimension = new Dimension(&position, &endpoint, DTLinear, this);
-
-                               if (parent != NULL)
-                                       parent->Add(attachedDimension);
-                       }
-                       else
-                       {
-                               // If there's one already there, tell it to flip sides...
-                               attachedDimension->FlipSides();
-                       }
-#else
-                       // New approach here: We look for connected objects.
-                       Object * attachedDimension = FindAttachedDimension();
-
-                       if (attachedDimension)
-                       {
-                               // If there's an attached Dimension, tell it to switch sides...
-                               ((Dimension *)attachedDimension)->FlipSides();
-                       }
-                       else
-                       {
-                               // Otherwise, we make a new one and attach it here.
-                               attachedDimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
-                               connected.push_back(Connection(attachedDimension, 0));
-                               connected.push_back(Connection(attachedDimension, 1.0));
-
-                               if (parent != NULL)
-                                       parent->Add(attachedDimension);
-                       }
-#endif
-
-                       return true;
-               }
-       }
-#endif
-
        if (state == OSInactive)
        {
 //How to translate this into pixels from Document space???
@@ -313,12 +234,17 @@ a dimension only) Draw() function... :-/
                return false;
        }
 
-       // Hit test tells us what we hit (if anything) through boolean variables. (It
-       // also tells us whether or not the state changed. --not any more)
+       // Hit test tells us what we hit (if anything) through boolean variables.
        SaveHitState();
        bool hovered = HitTest(point);
        needUpdate = HitStateChanged();
 
+       if (snapToGrid)
+               point = SnapPointToGrid(point);
+
+       if (snapPointIsValid)
+               point = snapPoint;
+
        objectWasDragged = (draggingLine | draggingHandle1 | draggingHandle2);
 
        if (objectWasDragged)
@@ -469,6 +395,7 @@ the horizontal line or vertical line that intersects from the current mouse posi
        Vector lineSegment = endpoint - position;
        Vector v1 = point - position;
        Vector v2 = point - endpoint;
+       Vector v3 = point - Center();   // Midpoint, for snapping...
        double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
        double distance;
 
@@ -500,12 +427,27 @@ the horizontal line or vertical line that intersects from the current mouse posi
                        / lineSegment.Magnitude());
 
        if ((v1.Magnitude() * Painter::zoom) < 8.0)
+       {
                hitPoint1 = true;
+               snapPoint = position;
+               snapPointIsValid = true;
+       }
        else if ((v2.Magnitude() * Painter::zoom) < 8.0)
+       {
                hitPoint2 = true;
+               snapPoint = endpoint;
+               snapPointIsValid = true;
+       }
        else if ((distance * Painter::zoom) < 5.0)
                hitLine = true;
 
+       // Not a manipulation point, but a snapping point:
+       if ((v3.Magnitude() * Painter::zoom) < 8.0)
+       {
+               snapPoint = Center();
+               snapPointIsValid = true;
+       }
+
        return (hitPoint1 || hitPoint2 || hitLine ? true : false);
 }
 
index 2e85bf2d3e57c0b9666099dfb81a5a796d2692ac..4cc846028a669576f12e8545214b54f69c1a4103 100644 (file)
@@ -34,6 +34,8 @@ bool Object::selectionInProgress = false;
 QRectF Object::selection;
 double Object::gridSpacing;
 int Object::currentLayer = 0;
+Point Object::snapPoint;
+bool Object::snapPointIsValid = false;
 
 
 Object::Object(): position(Vector(0, 0)), parent(0), type(OTObject),
index b83adaf02399fcb9e3f89db4dfaea9ebb3b2809f..ff29e1f7618328b5fa7689efc95c4ec6a6221b51 100644 (file)
@@ -97,6 +97,8 @@ class Object
                static QRectF selection;
                static double gridSpacing;                      // Grid spacing in base units
                static int currentLayer;
+               static Point snapPoint;
+               static bool snapPointIsValid;
 };
 
 #endif // __OBJECT_H__
diff --git a/src/triangulateaction.cpp b/src/triangulateaction.cpp
new file mode 100644 (file)
index 0000000..1f6a949
--- /dev/null
@@ -0,0 +1,335 @@
+// triangulateaction.cpp: Action class for creating triangles from lines
+//
+// Part of the Architektonas Project
+// (C) 2014 Underground Software
+// See the README and GPLv3 files for licensing and warranty information
+//
+// JLH = James Hammons <jlhamm@acm.org>
+//
+// WHO  WHEN        WHAT
+// ---  ----------  ------------------------------------------------------------
+// JLH  05/07/2014  Created this file
+//
+
+#include "triangulateaction.h"
+#include "applicationwindow.h"
+#include "circle.h"
+#include "container.h"
+#include "drawingview.h"
+#include "geometry.h"
+#include "line.h"
+#include "mathconstants.h"
+#include "painter.h"
+
+
+enum { FIRST_LINE, SECOND_LINE, THIRD_LINE };
+
+
+TriangulateAction::TriangulateAction(): state(FIRST_LINE),// t(0), u(1.0),
+       line1(NULL), line2(NULL), line3(NULL),
+       doc(&(ApplicationWindow::drawing->document))
+//, line(NULL),
+//     shiftWasPressedOnNextPoint(false), ctrlWasPressed(false),
+//     mirror(new Container(Vector()))
+{
+//     ApplicationWindow::drawing->document.CopySelectedContentsTo(mirror);
+//     mirror->Save();
+}
+
+
+TriangulateAction::~TriangulateAction()
+{
+}
+
+
+/*virtual*/ void TriangulateAction::Draw(Painter * painter)
+{
+       Object * obj = doc->lastObjectHovered;
+
+       if (obj == NULL)
+               return;
+
+       // This assumes a Line, but it might not be!
+       painter->SetPen(QPen(Qt::blue, 2.0, Qt::DotLine));
+//     Vector v(((Line *)obj)->position, ((Line *)obj)->endpoint);
+       Point p1 = ((Line *)obj)->position;
+       Point p2 = ((Line *)obj)->endpoint;
+       painter->DrawLine(p1, p2);
+#if 0
+       if (state == FIRST_POINT)
+       {
+               painter->DrawHandle(p1);
+       }
+       else
+       {
+               Vector reflectedP2 = -(p2 - p1);
+               Point newP2 = p1 + reflectedP2;
+               painter->DrawLine(newP2, p2);
+               painter->DrawHandle(p1);
+
+               double absAngle = (Vector(p2 - p1).Angle()) * RADIANS_TO_DEGREES;
+
+               // Keep the angle between 0 and 180 degrees
+               if (absAngle > 180.0)
+                       absAngle -= 180.0;
+
+               QString text = QChar(0x2221) + QObject::tr(": %1");
+               text = text.arg(absAngle);
+
+               if (ctrlWasPressed)
+                       text += " (Copy)";
+
+               painter->DrawInformativeText(text);
+
+               // Draw the mirror only if there's been a line to mirror around
+               if (p1 != p2)
+                       mirror->Draw(painter);
+       }
+#endif
+}
+
+
+/*virtual*/ void TriangulateAction::MouseDown(Vector /*point*/)
+{
+#if 0
+// this is not accurate enough. need to use the actual intersection point, not
+// just the parameter(s).
+       Object * toTrim = doc->lastObjectHovered;
+
+       if (toTrim == NULL)
+               return;
+
+//it would be nice to do it like this, but if we bisect the object, we have to
+//create an extra one...
+//     toTrim->Trim(t, u);
+
+       Vector v(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint);
+
+       // Check to see which case we have...
+       // We're trimming point #1...
+       if (t == 0)
+       {
+               ((Line *)toTrim)->position = ((Line *)toTrim)->position + (v * u);
+//             u = 1.0;
+       }
+       else if (u == 1.0)
+       {
+               ((Line *)toTrim)->endpoint = ((Line *)toTrim)->position + (v * t);
+//             t = 0;
+       }
+       else
+       {
+               Point p1 = ((Line *)toTrim)->position + (v * t);
+               Point p2 = ((Line *)toTrim)->position + (v * u);
+               Point p3 = ((Line *)toTrim)->endpoint;
+               ((Line *)toTrim)->endpoint = p1;
+               Line * line = new Line(p2, p3);
+               emit ObjectReady(line);
+//             t = 0, u = 1.0;
+       }
+
+       doc->lastObjectHovered = NULL;
+#endif
+}
+
+
+/*virtual*/ void TriangulateAction::MouseMoved(Vector point)
+{
+#if 0
+       if (state == FIRST_POINT)
+               p1 = point;
+//     else
+//     {
+//             p2 = point;
+//             mirror->Restore();
+//             mirror->Mirror(p1, p2);
+//     }
+#endif
+
+#if 0
+//     Container & doc = ApplicationWindow::drawing->document;
+//     int items = doc.ItemsSelected();
+       Object * toTrim = doc->lastObjectHovered;
+//     double closestPt1 = 0, closestPt2 = 1.0;
+       t = 0, u = 1.0;
+
+       if (toTrim == NULL)
+               return;
+
+       if (toTrim->type != OTLine)
+               return;
+
+       double pointHoveredT = Geometry::ParameterOfLineAndPoint(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint, point);
+
+       std::vector<Object *>::iterator i;
+
+       for(i=doc->objects.begin(); i!=doc->objects.end(); i++)
+       {
+               // Can't trim against yourself... :-P
+               if (*i == toTrim)
+                       continue;
+
+               Object * trimAgainst = *i;
+               double t1;//, u1;
+
+               if ((toTrim->type != OTLine) || (trimAgainst->type != OTLine))
+                       continue;
+
+               int intersects = Geometry::Intersects((Line *)toTrim, (Line *)trimAgainst, &t1);//, &u1);
+
+               if (intersects)
+               {
+                       // Now what? We don't know which side to trim!
+                       // ... now we do, we know which side of the Line we're on!
+                       if ((t1 > t) && (t1 < pointHoveredT))
+                               t = t1;
+
+                       if ((t1 < u) && (t1 > pointHoveredT))
+                               u = t1;
+               }
+       }
+#endif
+}
+
+
+/*virtual*/ void TriangulateAction::MouseReleased(void)
+{
+#if 0
+       if (state == FIRST_POINT)
+       {
+               p2 = p1;
+               state = NEXT_POINT;
+       }
+       else if (state == NEXT_POINT)
+       {
+               if (!ctrlWasPressed)
+               {
+                       state = FIRST_POINT;
+                       ApplicationWindow::drawing->document.MirrorSelected(p1, p2);
+
+                       mirror->Clear();
+                       ApplicationWindow::drawing->document.CopySelectedContentsTo(mirror);
+                       mirror->Save();
+               }
+               else
+               {
+                       mirror->CopyContentsTo(&(ApplicationWindow::drawing->document));
+               }
+       }
+#endif
+       Object * obj = doc->lastObjectHovered;
+
+       if (obj == NULL)
+               return;
+
+       if (obj->type != OTLine)
+               return;
+
+       if (state == FIRST_LINE)
+       {
+               line1 = (Line *)obj;
+               line1->state = OSSelected;
+               state = SECOND_LINE;
+       }
+       else if (state == SECOND_LINE)
+       {
+               line2 = (Line *)obj;
+               line2->state = OSSelected;
+               state = THIRD_LINE;
+       }
+       else if (state == THIRD_LINE)
+       {
+               line3 = (Line *)obj;
+               state = FIRST_LINE;
+               Triangulate();
+       }
+}
+
+
+/*virtual*/ void TriangulateAction::KeyDown(int /*key*/)
+{
+#if 0
+       if ((key == Qt::Key_Shift) && (state == NEXT_POINT))
+       {
+               shiftWasPressedOnNextPoint = true;
+               p1Save = p1;
+               p1 = p2;
+               state = FIRST_POINT;
+               emit NeedRefresh();
+       }
+       else if (key == Qt::Key_Control)
+       {
+               ctrlWasPressed = true;
+               emit NeedRefresh();
+       }
+#endif
+#if 0
+       if ((t == 0) && (u == 1.0))
+               return;
+
+       t = 0, u = 1.0;
+#endif
+}
+
+
+/*virtual*/ void TriangulateAction::KeyReleased(int /*key*/)
+{
+#if 0
+       if ((key == Qt::Key_Shift) && shiftWasPressedOnNextPoint)
+       {
+               shiftWasPressedOnNextPoint = false;
+               p2 = p1;
+               p1 = p1Save;
+               state = NEXT_POINT;
+               emit NeedRefresh();
+       }
+       else if (key == Qt::Key_Control)
+       {
+               ctrlWasPressed = false;
+               emit NeedRefresh();
+       }
+#endif
+}
+
+
+void TriangulateAction::Triangulate(void)
+{
+// N.B.: Should connect the line segments together, once made into a triangle...
+       double length2 = Vector(line2->position, line2->endpoint).Magnitude();
+       double length3 = Vector(line3->position, line3->endpoint).Magnitude();
+#if 0
+       double angle1, angle2, angle3;
+       double length1 = Vector(line1->position, line1->endpoint).Magnitude();
+       Geometry::FindAnglesForSides(length1, length2, length3, &angle1, &angle2, &angle3);
+printf("Triangulate: l1=%lf, l2=%lf, l3=%lf, a1=%lf, a2=%lf, a3=%lf\n", length1, length2, length3, angle1, angle2, angle3);
+
+       // We use line1 as the base. Move the other lines to it.
+       double line1Angle = Vector(line1->position, line1->endpoint).Angle();
+       double newLine2Angle = line1Angle + angle3;
+       Vector v;
+       v.SetAngleAndLength(newLine2Angle, length2);
+printf("             line1Angle=%lf, newLine2Angle=%lf\n", line1Angle, newLine2Angle);
+
+       line2->position = line1->endpoint;
+       line2->endpoint = line1->endpoint - v;
+
+//     double line2Angle = Vector(line2->position, line2->endpoint).Angle();
+//     double newLine3Angle = line2Angle + angle1;
+//     Vector v2;
+//     v2.SetAngleAndLength(newLine3Angle, length3);
+#else
+       Circle c1(line1->position, length2), c2(line1->endpoint, length3);
+       Point p1, p2;
+       int n = Geometry::Intersects(&c1, &c2, 0, 0, 0, 0, &p1, &p2);
+//printf("Circle-circle intersections: n=%i, <%lf, %lf, %lf>, <%lf, %lf, %lf>\n", n, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
+#endif
+       line2->position = line1->endpoint;
+       line2->endpoint = p1;
+
+       line3->position = line2->endpoint;
+       line3->endpoint = line1->position;
+
+       line1->state = OSInactive;
+       line2->state = OSInactive;
+}
+
diff --git a/src/triangulateaction.h b/src/triangulateaction.h
new file mode 100644 (file)
index 0000000..754954a
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __TRIANGULATEACTION_H__
+#define __TRIANGULATEACTION_H__
+
+#include "action.h"
+
+class Container;
+class Line;
+
+class TriangulateAction: public Action
+{
+       public:
+               TriangulateAction();
+               ~TriangulateAction();
+
+               virtual void Draw(Painter *);
+               virtual void MouseDown(Vector);
+               virtual void MouseMoved(Vector);
+               virtual void MouseReleased(void);
+               virtual void KeyDown(int);
+               virtual void KeyReleased(int);
+
+       private:
+               void Triangulate(void);
+
+       private:
+               int state;
+               Line * line1, * line2, * line3;
+//             Vector p1, p2, p1Save;
+//             double t, u;
+               Container * doc;
+};
+
+#endif // __TRIANGULATEACTION_H__
+
index 40ae9fe9b61388848f795d81e85c8053022733c4..a72594aa0762fcaaec1623862d96477cf36b0efb 100644 (file)
@@ -1,4 +1,4 @@
-// TrimAction.cpp: Action class for mirroring selected objects
+// trimaction.cpp: Action class for mirroring selected objects
 //
 // Part of the Architektonas Project
 // (C) 2011 Underground Software
@@ -87,7 +87,7 @@ TrimAction::~TrimAction()
 }
 
 
-/*virtual*/ void TrimAction::MouseDown(Vector point)
+/*virtual*/ void TrimAction::MouseDown(Vector /*point*/)
 {
 // this is not accurate enough. need to use the actual intersection point, not
 // just the parameter(s).
@@ -213,7 +213,7 @@ TrimAction::~TrimAction()
 }
 
 
-/*virtual*/ void TrimAction::KeyDown(int key)
+/*virtual*/ void TrimAction::KeyDown(int /*key*/)
 {
 #if 0
        if ((key == Qt::Key_Shift) && (state == NEXT_POINT))
@@ -237,7 +237,7 @@ TrimAction::~TrimAction()
 }
 
 
-/*virtual*/ void TrimAction::KeyReleased(int key)
+/*virtual*/ void TrimAction::KeyReleased(int /*key*/)
 {
 #if 0
        if ((key == Qt::Key_Shift) && shiftWasPressedOnNextPoint)
index 11ddb6494962b724e8166b05502baa27612c0295..6147c6d71d4021c0e10744b482e2b7796bb93c72 100644 (file)
@@ -31,6 +31,15 @@ Vector::Vector(Vector tail, Vector head): x(head.x - tail.x), y(head.y - tail.y)
 }
 
 
+// Create vector from angle + length (2D; z is set to zero)
+void Vector::SetAngleAndLength(double angle, double length)
+{
+       x = cos(angle) * length;
+       y = sin(angle) * length;
+       z = 0;
+}
+
+
 Vector Vector::operator=(Vector const v)
 {
        x = v.x, y = v.y, z = v.z;
@@ -257,10 +266,8 @@ double Vector::Parameter(Vector tail, Vector head, Vector p)
 // (Not sure which is head or tail, or which hand the normal lies)
 // [v1 should be the tail, v2 should be the head, in which case the normal should
 //  rotate anti-clockwise.]
-///*static*/ Vector Vector::Normal(Vector v1, Vector v2)
 /*static*/ Vector Vector::Normal(Vector tail, Vector head)
 {
-//     Vector v = (v1 - v2).Unit();
        Vector v = (head - tail).Unit();
        return Vector(-v.y, v.x);
 }
index c7f2771ce001a45e47dd9eebc35f14a82b5bb68d..bbc3aa7df88077682ddea1e82022a1dd0aeea738 100644 (file)
@@ -3,20 +3,22 @@
 //
 // Various structures used for 3 dimensional imaging
 //
-// by James L. Hammons
-// (C) 2001 Underground Software
+// by James Hammons
+// (C) 2001, 2014 Underground Software
 //
 
 #ifndef __VECTOR_H__
 #define __VECTOR_H__
 
-// What we'll do here is create the vector type and use typedef to alias Point to it. Yeah, that's it.
+// What we'll do here is create the vector type and use typedef to alias Point
+// to it. Yeah, that's it.
 
 class Vector
 {
        public:
                Vector(double xx = 0, double yy = 0, double zz = 0);
                Vector(Vector tail, Vector head);               // Create vector from two points
+               void SetAngleAndLength(double angle, double length);
                Vector operator=(Vector const v);
                Vector operator+(Vector const v);
                Vector operator-(Vector const v);