From 7f3a6b11585376eecd80979ec3da2346c5314d88 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Mon, 19 May 2014 13:36:53 -0500 Subject: [PATCH] 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. :-) --- architektonas.pro | 2 + res/architektonas.qrc | 1 + res/atns-icon.png | Bin 23419 -> 9865 bytes res/triangulate-tool.png | Bin 0 -> 525 bytes src/applicationwindow.cpp | 17 ++ src/applicationwindow.h | 2 + src/arc.cpp | 22 +++ src/circle.cpp | 14 ++ src/container.cpp | 5 +- src/container.h | 1 + src/dimension.cpp | 11 +- src/drawingview.cpp | 93 +++++++++-- src/geometry.cpp | 125 +++++++++++++- src/geometry.h | 2 + src/line.cpp | 110 +++---------- src/object.cpp | 2 + src/object.h | 2 + src/triangulateaction.cpp | 335 ++++++++++++++++++++++++++++++++++++++ src/triangulateaction.h | 34 ++++ src/trimaction.cpp | 8 +- src/vector.cpp | 11 +- src/vector.h | 8 +- 22 files changed, 699 insertions(+), 106 deletions(-) create mode 100644 res/triangulate-tool.png create mode 100644 src/triangulateaction.cpp create mode 100644 src/triangulateaction.h 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 b98b5b473cd50124fd49675149e2c4bbb92baa50..9084dd2c80d609c8e60cf5d18883efbc8afbb258 100644 GIT binary patch literal 9865 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�w$K{plH^9-(4s_}4r7&86l5hxb)%J8{ zY;Khy&GWOjqz~sFGBy}1DO;CwSp4-kx`_YSZKg48u4|+Pb7p)h8V-)#nQLID4d9sn z_Za3~etYKa!pZwJl z#hX%@8MXCYa^UPKhZE5>>_ksz=VO3v&jJA2nztXLlwsrxKVYzMo-7#xbfrb$;{D>I z?*IMeCr_`{4ekBoOq25Lk&@?3oBznsCpO_yTB-6I`PQUSSuYg_BSMKWVozCo`nTmRu z#S0sl1t6up;BWNwzmKfk(nPE3y5^^If?&vJ67kp46a|3-q$~$>#+uevkS!4XaSzlk zsRU*+SulZE??{cFuLOD?A!F%czWq^okJNA525T@r#ujLQ;Zy-ET3}KU0AU4c=*=dT zLIOJhrr9M1pjo{8s(@t4@8|I>mLO

0HoM)~Eq|z;m<%=#BR=d28?3YXTK&00n?Z zSxFde{pl9X(u|!T(Ap&iK-x)a5%ITex=jp^r=4Bpmz9KR@y7OU+u=Ay`~UANH5=FK z-tBcymqblPq)@z-JijuBTt0=CxZSM?HEC#Z2^CvtmqSdcx}9|#FSg$(&w-i5oQsOPDcoWtmc8F)aZnR7X;NuW#lj|xpdtt;+nM6lp0uok%b>CpJISN4tHw;ir9qlE zV98+s<^og!AP5K)Ap@X`1)Jf{{f~A5|FMJ&g{c4z(u}zPO#)B~2q{93_bk`J9i!hV z3Y}eL0OS=T$^evmxvA-WJ?9mF5^%wvqxb(uPtl{^GmUxARJ?$%4=$F%*;!AzJ7&(+ v`+Tm9=KedSBPx2PyLac&sEo>}>_hUu_h_kLsN(tR00000NkvXXu0mjfO(5Wl literal 23419 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 diff --git a/res/triangulate-tool.png b/res/triangulate-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..7af66bb69151871efcd55873efa380ab7c4c94a9 GIT binary patch literal 525 zcmV+o0`mQdP)WFU8GbZ8()Nlj2>E@cM*00D7HL_t(|+U;1&4#F@D zn^p0&wtqRNMnr8zp4l1E3|~%X#H6D8k*iJRA$$6b}R2V)5Bn z`JdeUkjl1&_x>Oc3R`J6@$m^cJjuXI=(S2W^(|er%AnHRNFEWKAOkj@iA1=Ymz8O7 z)D7l0ycXl|QxQ^-UoGrOQv!wlVn@BuYB3|v7E`Cd?tP)$kdes^DiO#c!r$3JlpV#B zA`saPW)4W_1~GORPl`aaNc?QT;F!e_r-XM)>Mlaao%tm2YBU;+M#BwnbXQqTxl(my P00000NkvXXu0mjfWR}s( literal 0 HcmV?d00001 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); -- 2.37.2