From: Shamus Hammons Date: Mon, 19 May 2014 18:36:53 +0000 (-0500) Subject: Added new triangulation tool, ability to snap to endpoints/intersections. X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7f3a6b11585376eecd80979ec3da2346c5314d88;hp=c85958d34fac175452fe420ff24ea82f26d6f1f3;p=architektonas Added new triangulation tool, ability to snap to endpoints/intersections. 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. :-) --- diff --git a/architektonas.pro b/architektonas.pro index ce6ce57..53c36b2 100644 --- a/architektonas.pro +++ b/architektonas.pro @@ -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 diff --git a/res/architektonas.qrc b/res/architektonas.qrc index c8e8f90..afe168c 100644 --- a/res/architektonas.qrc +++ b/res/architektonas.qrc @@ -17,6 +17,7 @@ snap-to-grid-tool.png splash.png trim-tool.png + triangulate-tool.png quit.png zoom-in.png zoom-out.png diff --git a/res/atns-icon.png b/res/atns-icon.png index b98b5b4..9084dd2 100644 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 index 0000000..7af66bb Binary files /dev/null and b/res/triangulate-tool.png differ diff --git a/src/applicationwindow.cpp b/src/applicationwindow.cpp index 23c8654..ad3bc8d 100644 --- a/src/applicationwindow.cpp +++ b/src/applicationwindow.cpp @@ -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); diff --git a/src/applicationwindow.h b/src/applicationwindow.h index b695c1e..e2e4934 100644 --- a/src/applicationwindow.h +++ b/src/applicationwindow.h @@ -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: diff --git a/src/arc.cpp b/src/arc.cpp index d4d747b..f507869 100644 --- a/src/arc.cpp +++ b/src/arc.cpp @@ -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); diff --git a/src/circle.cpp b/src/circle.cpp index 94a9ec8..093f533 100644 --- a/src/circle.cpp +++ b/src/circle.cpp @@ -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))) diff --git a/src/container.cpp b/src/container.cpp index 2eca9f4..9c3d403 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -204,7 +204,7 @@ class so that we can leverage that stuff here as well. /*virtual*/ bool Container::PointerMoved(Vector point) { std::vector::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??? diff --git a/src/container.h b/src/container.h index 7de5d89..7d5ef4c 100644 --- a/src/container.h +++ b/src/container.h @@ -50,6 +50,7 @@ class Container: public Object bool isTopLevelContainer; Object * lastObjectClicked; Object * lastObjectHovered; + Object * penultimateObjectHovered; private: bool dragging; bool draggingHandle1; diff --git a/src/dimension.cpp b/src/dimension.cpp index e5a7478..1e9b785 100644 --- a/src/dimension.cpp +++ b/src/dimension.cpp @@ -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); } diff --git a/src/drawingview.cpp b/src/drawingview.cpp index acfad40..fc30ac8 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -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(); } diff --git a/src/geometry.cpp b/src/geometry.cpp index 058dbfa..1ec93db 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -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; +} + diff --git a/src/geometry.h b/src/geometry.h index d19d48c..410c7e0 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -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__ diff --git a/src/line.cpp b/src/line.cpp index 46a6d85..7841fa8 100644 --- a/src/line.cpp +++ b/src/line.cpp @@ -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 *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); } diff --git a/src/object.cpp b/src/object.cpp index 2e85bf2..4cc8460 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -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), diff --git a/src/object.h b/src/object.h index b83adaf..ff29e1f 100644 --- a/src/object.h +++ b/src/object.h @@ -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 index 0000000..1f6a949 --- /dev/null +++ b/src/triangulateaction.cpp @@ -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 +// +// 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::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 index 0000000..754954a --- /dev/null +++ b/src/triangulateaction.h @@ -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__ + diff --git a/src/trimaction.cpp b/src/trimaction.cpp index 40ae9fe..a72594a 100644 --- a/src/trimaction.cpp +++ b/src/trimaction.cpp @@ -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) diff --git a/src/vector.cpp b/src/vector.cpp index 11ddb64..6147c6d 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -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); } diff --git a/src/vector.h b/src/vector.h index c7f2771..bbc3aa7 100644 --- a/src/vector.h +++ b/src/vector.h @@ -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);