From 5d8c9e52606315fbfe857f2715b8f051b4f97491 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Mon, 29 Nov 2021 15:53:15 -0600 Subject: [PATCH] Miscellaneous fixes/updates: - Added default copy constructor to Vector class, to stop spurious compiler warnings - Added Polyline object to structs definition - Added tangent detection (point-circle, circle-circle) - Added initial line to circle tangent attachment - Added beginning of parallel tool (UI hookup done, tool code needs work) - Fixed intersection detection - Added new command line entry UI --- TODO | 14 ++++- architektonas.pro | 8 ++- res/architektonas.qrc | 1 + res/parallel-tool.png | Bin 0 -> 883 bytes src/applicationwindow.cpp | 21 +++++++ src/applicationwindow.h | 3 +- src/consolewidget.cpp | 58 ++++++++++++++++++ src/consolewidget.h | 27 ++++++++ src/drawingview.cpp | 123 ++++++++++++++++++++++++++++++++++--- src/drawingview.h | 3 +- src/geometry.cpp | 125 +++++++++++++++++++++++++++++++++----- src/geometry.h | 4 +- src/promptlineedit.cpp | 80 ++++++++++++++++++++++++ src/promptlineedit.h | 30 +++++++++ src/structs.h | 17 +++++- src/vector.cpp | 35 ++--------- src/vector.h | 7 ++- 17 files changed, 490 insertions(+), 66 deletions(-) create mode 100644 res/parallel-tool.png create mode 100644 src/consolewidget.cpp create mode 100644 src/consolewidget.h create mode 100644 src/promptlineedit.cpp create mode 100644 src/promptlineedit.h diff --git a/TODO b/TODO index eb6bfb8..23a36b3 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,8 @@ Stuff To Be Implemented/Fixed remove it and it only, or to cut the entity at other entities crossing) - Make Architektonas an MDI application - Parallel tool (be able to make copy of object that's parallel to original) - - Pen marker doesn't set attributes on the highlighted part of the group. + - Add command line tool + - Be able to show dimensions in pure inches as well as feet & inches Stuff That's Done @@ -52,3 +53,14 @@ Things to Ponder ---------------- Grouping items on different layers currently makes the items *not* on the current layer disappear. Not sure how this should be handled... Maybe we should honor the top level Container's attributes, and not the attributes of the contained objects? + + +Bugs +---- + + - Add new dimension doesn't honor the scale parameter set by user + - Dimension object prevents system from finding intersections when near or on + object intersection + - Pen marker doesn't set attributes on the highlighted part of the group. + - Can't move objects to other layers + - Dimension lines extents aren't properly taken into account when printing diff --git a/architektonas.pro b/architektonas.pro index 2af6bc7..4a13c0a 100644 --- a/architektonas.pro +++ b/architektonas.pro @@ -52,6 +52,7 @@ HEADERS = \ src/baseunittab.h \ src/blockitemwidget.h \ src/blockwidget.h \ + src/consolewidget.h \ src/drawingview.h \ src/fileio.h \ src/generaltab.h \ @@ -62,8 +63,9 @@ HEADERS = \ src/main.h \ src/mathconstants.h \ src/objectwidget.h \ - src/penwidget.h \ src/painter.h \ + src/penwidget.h \ + src/promptlineedit.h \ src/rect.h \ src/settingsdialog.h \ src/structs.h \ @@ -76,6 +78,7 @@ SOURCES = \ src/baseunittab.cpp \ src/blockitemwidget.cpp \ src/blockwidget.cpp \ + src/consolewidget.cpp \ src/drawingview.cpp \ src/fileio.cpp \ src/generaltab.cpp \ @@ -85,8 +88,9 @@ SOURCES = \ src/layeritemwidget.cpp \ src/main.cpp \ src/objectwidget.cpp \ - src/penwidget.cpp \ src/painter.cpp \ + src/penwidget.cpp \ + src/promptlineedit.cpp \ src/rect.cpp \ src/settingsdialog.cpp \ src/utils.cpp \ diff --git a/res/architektonas.qrc b/res/architektonas.qrc index 0929beb..83fbfc4 100644 --- a/res/architektonas.qrc +++ b/res/architektonas.qrc @@ -48,5 +48,6 @@ cursor-marker.png cursor-dropper.png print-preview.png + parallel-tool.png diff --git a/res/parallel-tool.png b/res/parallel-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..ed0fe2fdd0be331ddcaac166427c08bcb511c35b GIT binary patch literal 883 zcmV-(1C0EMP)EX>4Tx04R}tkv&MmKpe$iQ>9WX4i*$~$j~}j5EXIMDionYsTEpvFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UwP&La) zC*oo@w<-o+(ThO{h$1L4Q=dzvlJFc~_we!cF2=LG&;2?2l)T9RpGZ8*bi*RvAfDc| zbk6(4VOEk9;&b9LgDyz?$aUG}H_kh$D)sQNECM zS>e3JS*_Gq>z@3Dp}e-T%ypV0NMI35NI`^*8p^1^LX1|86ccIMk9+us9e;{kGP%lN zeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00B!$L_t(|+U=W33d1lAMJ17(B=Kf1y)=~sycAto73TEixmMWRuk5&V^ z2LYhmk3_&}VJ!tfIESbT&=J$B1|jFg@AB^!0h!$|0u%($8hjX40Qikh54JIZ)vch;_(hKS-QX7yj3bN zF#alOCFnVdUIZcU`lH_mz#!{7_ltmw>V|N?2skN#+p!m&fbM|+H@Cgs2rv^?GP{pL zfT^%&1qk6j0s-b}HDz=iWmENkfy@NJwA4BbC9gw8Kt)CJcmpQgVKJI|pg;fs002ov JPDHLkV1oQKan=9; literal 0 HcmV?d00001 diff --git a/src/applicationwindow.cpp b/src/applicationwindow.cpp index b80130e..2f78dfc 100644 --- a/src/applicationwindow.cpp +++ b/src/applicationwindow.cpp @@ -31,6 +31,7 @@ #include #include "about.h" #include "blockwidget.h" +#include "consolewidget.h" #include "drawingview.h" #include "fileio.h" #include "generaltab.h" @@ -80,10 +81,16 @@ ApplicationWindow::ApplicationWindow(): ObjectWidget * ow = new ObjectWidget; dock3->setWidget(ow); addDockWidget(Qt::RightDockWidgetArea, dock3); + QDockWidget * dock4 = new QDockWidget(tr("Command"), this); + ConsoleWidget * cw = new ConsoleWidget; + dock4->setWidget(cw); + addDockWidget(Qt::BottomDockWidgetArea, dock4); + // Needed for saveState() dock1->setObjectName("Layers"); dock2->setObjectName("Blocks"); dock3->setObjectName("Object"); + dock4->setObjectName("Commands"); // Create status bar zoomIndicator = new QLabel("Zoom: 100% Grid: 12.0\" BU: Inch"); @@ -300,6 +307,7 @@ void ApplicationWindow::HandlePrintRequest(QPrinter * printer) void ApplicationWindow::contextMenuEvent(QContextMenuEvent * event) { + // Proof of concept, still need to code up the real thing QMenu menu(this); menu.addAction(mirrorAct); menu.addAction(rotateAct); @@ -374,6 +382,12 @@ void ApplicationWindow::TrimTool(void) SetInternalToolStates(); } +void ApplicationWindow::ParallelTool(void) +{ + ClearUIToolStatesExcept(parallelAct); + SetInternalToolStates(); +} + void ApplicationWindow::TriangulateTool(void) { ClearUIToolStatesExcept(triangulateAct); @@ -516,6 +530,8 @@ void ApplicationWindow::SetInternalToolStates(void) Global::tool = TTRotate; else if (trimAct->isChecked()) Global::tool = TTTrim; + else if (parallelAct->isChecked()) + Global::tool = TTParallel; else if (triangulateAct->isChecked()) Global::tool = TTTriangulate; else @@ -846,6 +862,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())); + parallelAct = CreateAction(tr("&Parallel"), tr("Parallel"), tr("Create copies of objects parallel to the original."), QIcon(":/res/parallel-tool.png"), QKeySequence("p,a"), true); + connect(parallelAct, SIGNAL(triggered()), this, SLOT(ParallelTool())); + 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())); @@ -934,6 +953,7 @@ void ApplicationWindow::CreateMenus(void) menu->addAction(rotateAct); menu->addAction(mirrorAct); menu->addAction(trimAct); + menu->addAction(parallelAct); menu->addAction(triangulateAct); menu->addAction(connectAct); menu->addAction(disconnectAct); @@ -988,6 +1008,7 @@ void ApplicationWindow::CreateToolbars(void) toolbar->addAction(rotateAct); toolbar->addAction(mirrorAct); toolbar->addAction(trimAct); + toolbar->addAction(parallelAct); toolbar->addAction(triangulateAct); toolbar->addAction(editCutAct); toolbar->addAction(editCopyAct); diff --git a/src/applicationwindow.h b/src/applicationwindow.h index 00c7917..a83e937 100644 --- a/src/applicationwindow.h +++ b/src/applicationwindow.h @@ -37,6 +37,7 @@ class ApplicationWindow: public QMainWindow void RotateTool(void); void MirrorTool(void); void TrimTool(void); + void ParallelTool(void); void TriangulateTool(void); void AddLineTool(void); void AddCircleTool(void); @@ -109,6 +110,7 @@ class ApplicationWindow: public QMainWindow QAction * disconnectAct; QAction * mirrorAct; QAction * trimAct; + QAction * parallelAct; QAction * triangulateAct; QAction * editCutAct; QAction * editCopyAct; @@ -124,4 +126,3 @@ class ApplicationWindow: public QMainWindow }; #endif // __APPLICATIONWINDOW_H__ - diff --git a/src/consolewidget.cpp b/src/consolewidget.cpp new file mode 100644 index 0000000..ce55cc4 --- /dev/null +++ b/src/consolewidget.cpp @@ -0,0 +1,58 @@ +// +// consolewidget.cpp: Command line widget +// +// Part of the Architektonas Project +// (C) 2021 Underground Software +// See the README and GPLv3 files for licensing and warranty information +// +// JLH = James Hammons +// +// WHO WHEN WHAT +// --- ---------- ------------------------------------------------------------ +// JLH 11/20/2021 Created this file +// + +#include "consolewidget.h" + +ConsoleWidget::ConsoleWidget(QWidget * parent/*= NULL*/): QWidget(parent) +{ + cmdline = new PromptLineEdit(this); + cmdline->setFrame(false); + + screen = new QTextEdit; + screen->setAlignment(Qt::AlignBottom | Qt::AlignLeft); + screen->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + screen->setReadOnly(true); + + QVBoxLayout * mainLayout = new QVBoxLayout; + mainLayout->addWidget(screen); + mainLayout->addWidget(cmdline); + + setLayout(mainLayout); + + connect(cmdline, SIGNAL(returnPressed()), this, SLOT(Execute())); + + QScrollBar * scrollbar = screen->verticalScrollBar(); + connect(scrollbar, SIGNAL(rangeChanged(int, int)), this, SLOT(MoveScrollBarToBottom(int, int))); +} + +ConsoleWidget::~ConsoleWidget() +{ +} + +void ConsoleWidget::Execute(void) +{ + screen->append(cmdline->text()); + screen->append("Error: don't know how to '" + cmdline->text() + "'"); + cmdline->clear(); +} + +void ConsoleWidget::MoveScrollBarToBottom(int min, int max) +{ + Q_UNUSED(min); + screen->verticalScrollBar()->setValue(max); +} + +void ConsoleWidget::paintEvent(QPaintEvent * /*event*/) +{ +} diff --git a/src/consolewidget.h b/src/consolewidget.h new file mode 100644 index 0000000..147b597 --- /dev/null +++ b/src/consolewidget.h @@ -0,0 +1,27 @@ +#ifndef __CONSOLEWIDGET_H__ +#define __CONSOLEWIDGET_H__ + +#include +#include "promptlineedit.h" + +class ConsoleWidget: public QWidget +{ + Q_OBJECT + + public: + ConsoleWidget(QWidget * parent = NULL); + ~ConsoleWidget(); + + public slots: + void Execute(void); + void MoveScrollBarToBottom(int min, int max); + + protected: + void paintEvent(QPaintEvent * event); + + public: + PromptLineEdit * cmdline; + QTextEdit * screen; +}; + +#endif // __CONSOLEWIDGET_H__ diff --git a/src/drawingview.cpp b/src/drawingview.cpp index 911ca67..9acf00c 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -686,6 +686,11 @@ Where is the text offset? It looks like it's drawing in the center, but obvious break; } + case OTPolyline: + { + break; + } + case OTContainer: { // Containers require recursive rendering... @@ -739,14 +744,30 @@ VPVector DrawingView::GetSelection(void) return v; } -VPVector DrawingView::GetHovered(void) +// +// When testing for hovered intersections, we need to be able to exclude some +// objects which have funky characteristics or handles; so we allow for that +// here. +// +VPVector DrawingView::GetHovered(bool exclude/*= false*/) { VPVector v; for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++) { - if (((Object *)(*i))->hovered) + Object * obj = (Object *)(*i); + + if (obj->hovered) + { + if (exclude + && ((obj->type == OTDimension) + || ((obj->type == OTCircle) && (obj->hitPoint[0] == true)) + || ((obj->type == OTArc) && (obj->hitPoint[0] == true)) + || (draggingObject && (obj == dragged)))) + continue; + v.push_back(*i); + } } return v; @@ -946,6 +967,19 @@ void DrawingView::LineHandler(int mode, Point p) switch (mode) { case ToolMouseDown: +/* toolObj[0] = NULL; + + // Check to see if we can do a circle tangent snap + if (numHovered == 1) + { + VPVector hover = GetHovered(); + Object * obj = (Object *)hover[0]; + + // Save for later if the object clicked was a circle (need to check that it wasn't the center clicked on, because that will fuck up connecting centers of circles with lines... and now we do! :-) + if ((obj->type == OTCircle) && (obj->hitPoint[0] == false)) + toolObj[0] = obj; + }*/ + if (Global::toolState == TSNone) toolPoint[0] = p; else @@ -957,7 +991,54 @@ void DrawingView::LineHandler(int mode, Point p) if (Global::toolState == TSNone) toolPoint[0] = p; else + { toolPoint[1] = p; +/* bool isCircle = false; + + if (numHovered == 1) + { + VPVector hover = GetHovered(); + Object * obj = (Object *)hover[0]; + + if ((obj->type == OTCircle) && (obj->hitPoint[0] == false)) + { + isCircle = true; + toolObj[1] = obj; + } + } + + // Adjust initial point if it's on a circle (tangent point) + if (toolObj[0] != NULL) + { + if (isCircle) + { + Geometry::FindTangents(toolObj[0], toolObj[1]); + + if (Global::numIntersectPoints > 0) + { + toolPoint[0] = Global::intersectPoint[0]; + toolPoint[1] = Global::intersectPoint[1]; + } + } + else + { + Geometry::FindTangents(toolObj[0], p); + + if (Global::numIntersectPoints > 0) + toolPoint[0] = Global::intersectPoint[0]; + } + } + else + { + if (isCircle) + { + Geometry::FindTangents(toolObj[1], toolPoint[0]); + + if (Global::numIntersectPoints > 0) + toolPoint[1] = Global::intersectPoint[0]; + } + }*/ + } break; @@ -1506,7 +1587,8 @@ void DrawingView::TriangulateHandler(int mode, Point/*p*/) void DrawingView::TrimHandler(int mode, Point p) { /* -n.b.: this code is lifted straight out of the old oo code. needs to be updated. +N.B.: this code is lifted straight out of the old oo code. needs to be updated. + Also: trim tool should ignore snap. */ switch (mode) { @@ -1837,7 +1919,7 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) // Do object hit testing... bool needUpdate = HitTestObjects(point); - VPVector hover2 = GetHovered(); + VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...) #if 0 { if (needUpdate) @@ -1851,9 +1933,9 @@ if (needUpdate) #endif // Check for multi-hover... - if (numHovered > 1) + if (hover2.size() > 1) { -//need to check for case where hover is over 2 circles and a 3rd's center... +//need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)... Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1]; Geometry::Intersects(obj1, obj2); @@ -1892,7 +1974,7 @@ if (needUpdate) intersectionPoint = v1; } } - else if (numHovered == 1) + else if (hover2.size() == 1) { Object * obj = (Object *)hover2[0]; @@ -1900,7 +1982,6 @@ if (needUpdate) { /* Not sure that this is the best way to handle this, but it works(TM)... -Except when lines are overlapping, then it doesn't work... !!! FIX !!! */ Point midpoint = Geometry::Midpoint((Line *)obj); Vector v1 = Vector::Magnitude(midpoint, point); @@ -1914,6 +1995,30 @@ Except when lines are overlapping, then it doesn't work... !!! FIX !!! needUpdate = true; } } + else if (obj->type == OTCircle) + { + if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1])) + { + Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]); + Geometry::FindTangents(obj, p); + + if (Global::numIntersectPoints > 0) + { + hoveringIntersection = true; + intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]); + } + } + else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2)) + { + Geometry::FindTangents(obj, toolPoint[0]); + + if (Global::numIntersectPoints > 0) + { + hoveringIntersection = true; + intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]); + } + } + } } // Handle object movement (left button down & over an object) @@ -2254,7 +2359,7 @@ void DrawingView::CheckObjectBounds(void) switch (obj->type) { case OTLine: - case OTDimension: + case OTDimension: // N.B.: We don't check this properly... { Line * l = (Line *)obj; diff --git a/src/drawingview.h b/src/drawingview.h index 704b79d..344428a 100644 --- a/src/drawingview.h +++ b/src/drawingview.h @@ -24,7 +24,7 @@ class DrawingView: public QWidget void RenderObjects(Painter *, VPVector &, int, bool ignoreLayer = false); void AddHoveredToSelection(void); VPVector GetSelection(void); - VPVector GetHovered(void); + VPVector GetHovered(bool exclude = false); void ToolHandler(int, Point); void ToolDraw(Painter *); void LineHandler(int, Point); @@ -122,4 +122,3 @@ class DrawingView: public QWidget }; #endif // __DRAWINGVIEW_H__ - diff --git a/src/geometry.cpp b/src/geometry.cpp index 95d12c6..86d1906 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -1,3 +1,4 @@ +// // geometry.cpp: Algebraic geometry helper functions // // Part of the Architektonas Project @@ -19,7 +20,6 @@ #include "global.h" #include "mathconstants.h" - // Returns the parameter of a point in space to this vector. If the parameter // is between 0 and 1, the normal of the vector to the point is on the vector. // Note: lp1 is the tail, lp2 is the head of the line (vector). @@ -35,10 +35,10 @@ double Geometry::ParameterOfLineAndPoint(Point tail, Point head, Point point) double magnitude = lineSegment.Magnitude(); Vector pointSegment = point - tail; double t = lineSegment.Dot(pointSegment) / (magnitude * magnitude); + return t; } - double Geometry::DistanceToLineFromPoint(Point tail, Point head, Point point) { // Interpretation: given a line in the form x = a + tu, where u is the @@ -61,7 +61,6 @@ double Geometry::DistanceToLineFromPoint(Point tail, Point head, Point point) return dist.Magnitude() * (angle < HALF_TAU ? +1.0 : -1.0); } - Point Geometry::MirrorPointAroundLine(Point point, Point tail, Point head) { // Get the vector of the intersection of the line and the normal on the @@ -81,7 +80,6 @@ Point Geometry::MirrorPointAroundLine(Point point, Point tail, Point head) return mirroredPoint; } - // // point: The point we're rotating // rotationPoint: The point we're rotating around @@ -95,13 +93,11 @@ Point Geometry::RotatePointAroundPoint(Point point, Point rotationPoint, double return Vector(rotationPoint.x + px, rotationPoint.y + py, 0); } - double Geometry::Determinant(Point p1, Point p2) { return (p1.x * p2.y) - (p2.x * p1.y); } - void Geometry::Intersects(Object * obj1, Object * obj2) { Global::numIntersectPoints = Global::numIntersectParams = 0; @@ -116,7 +112,6 @@ void Geometry::Intersects(Object * obj1, Object * obj2) CheckLineToCircleIntersection(obj2, obj1); } - /* Intersecting line segments: An easier way: @@ -182,7 +177,6 @@ void Geometry::CheckLineToLineIntersection(Object * l1, Object * l2) Global::numIntersectParams = 1; } - void Geometry::CheckCircleToCircleIntersection(Object * c1, Object * c2) { // Set up global vars @@ -236,7 +230,6 @@ void Geometry::CheckCircleToCircleIntersection(Object * c1, Object * c2) Global::numIntersectPoints = 2; } - // // N.B.: l is the line, c is the circle // @@ -300,7 +293,6 @@ void Geometry::CheckLineToCircleIntersection(Object * l, Object * c) } } - // should we just do common trig solves, like AAS, ASA, SAS, SSA? // Law of Cosines: // c² = a² + b² - 2ab * cos(C) @@ -349,7 +341,6 @@ hmm... dunno... *a3 = angle3; } - Point Geometry::GetPointForParameter(Object * obj, double t) { if (obj->type == OTLine) @@ -363,22 +354,126 @@ Point Geometry::GetPointForParameter(Object * obj, double t) return Point(0, 0); } - Point Geometry::Midpoint(Line * line) { return Point((line->p[0].x + line->p[1].x) / 2.0, (line->p[0].y + line->p[1].y) / 2.0); } - /* How to find the tangent of a point off a circle: - • Calculate the midpoint on the point and the center of the circle - • Get the length of the line segment from the and the center divided by two + • Calculate the midpoint of the point and the center of the circle + • Get the length of the line segment from point and the center divided by two • Use that length to construct a circle with the point at the center and the radius equal to that length • The intersection of the two circles are the tangent points +Another way: + + • Find angle between radius and line between point and center + • Angle +/- from line (point to center) are the tangent points + +*/ +void Geometry::FindTangents(Object * c, Point p) +{ + // Set up global vars + Global::numIntersectPoints = Global::numIntersectParams = 0; + + // Get the distance between the point and the center of the circle, the + // length, and the angle. + Vector centerLine(c->p[0], p); + double d = centerLine.Magnitude(); + double clAngle = centerLine.Angle(); + + // If the point is on or inside the circle, there are no tangents. + if (d <= c->radius[0]) + return; + + // Use 'cos(a) = adjacent / hypotenuse' to get the tangent angle + double a = acos(c->radius[0] / d); + + // Finally, find the points of intersection by using +/- the angle found + // from the centerline's angle + Global::intersectPoint[0].x = c->p[0].x + (cos(clAngle + a) * c->radius[0]); + Global::intersectPoint[0].y = c->p[0].y + (sin(clAngle + a) * c->radius[0]); + Global::intersectPoint[1].x = c->p[0].x + (cos(clAngle - a) * c->radius[0]); + Global::intersectPoint[1].y = c->p[0].y + (sin(clAngle - a) * c->radius[0]); + Global::numIntersectPoints = 2; +} + +/* +The extangents can be found by collapsing the circle of smaller radius to a point, and diminishing the larger circle by the radius of the smaller, then treating it like the point-circle case (you have to translate the tangent back out by the length of the radius of the smaller circle once found). + +Not sure what the analogous method for finding the intangents are... :-/ +Looks like the intangent can be found by augmenting the larger circle by the smaller radius, and doing the center of the smaller as the point-circle case again, only translating the line back by the radius of the smaller. */ +void Geometry::FindTangents(Object * c1, Object * c2) +{ + // Set up global vars + Global::numIntersectPoints = Global::numIntersectParams = 0; + + // Find the larger and smaller of the two: + Object * cLg = c1, * cSm = c2; + + if (c2->radius[0] > c1->radius[0]) + cLg = c2, cSm = c1; + + // Get the distance between the point and the center of the circle, the + // length, and the angle. + Vector centerLine(cLg->p[0], cSm->p[0]); + double d = centerLine.Magnitude(); + double clAngle = centerLine.Angle(); + + // If one circle is completely inside the other, there are no tangents. + if ((d + cSm->radius[0]) <= cLg->radius[0]) + return; + // Subtract the radius of the smaller from the larger, and use the point-circle method to find the extangent: + double a = acos((cLg->radius[0] - cSm->radius[0]) / d); + + // Finally, find the points of intersection by using +/- the angle found + // from the centerline's angle + Global::intersectPoint[0].x = cLg->p[0].x + (cos(clAngle + a) * cLg->radius[0]); + Global::intersectPoint[0].y = cLg->p[0].y + (sin(clAngle + a) * cLg->radius[0]); + Global::intersectPoint[1].x = cSm->p[0].x + (cos(clAngle + a) * cSm->radius[0]); + Global::intersectPoint[1].y = cSm->p[0].y + (sin(clAngle + a) * cSm->radius[0]); + + Global::intersectPoint[2].x = cLg->p[0].x + (cos(clAngle - a) * cLg->radius[0]); + Global::intersectPoint[2].y = cLg->p[0].y + (sin(clAngle - a) * cLg->radius[0]); + Global::intersectPoint[3].x = cSm->p[0].x + (cos(clAngle - a) * cSm->radius[0]); + Global::intersectPoint[3].y = cSm->p[0].y + (sin(clAngle - a) * cSm->radius[0]); + Global::numIntersectPoints = 4; + + // If the circles overlap, there are no intangents. + if (d <= (cLg->radius[0] + cSm->radius[0])) + return; + + // Add the radius of the smaller from the larger, and use the point-circle method to find the intangent: + a = acos((cLg->radius[0] + cSm->radius[0]) / d); + + // Finally, find the points of intersection by using +/- the angle found + // from the centerline's angle + Global::intersectPoint[4].x = cLg->p[0].x + (cos(clAngle + a) * cLg->radius[0]); + Global::intersectPoint[4].y = cLg->p[0].y + (sin(clAngle + a) * cLg->radius[0]); + Global::intersectPoint[5].x = cSm->p[0].x + (cos(clAngle - a) * cSm->radius[0]); + Global::intersectPoint[5].y = cSm->p[0].y + (sin(clAngle - a) * cSm->radius[0]); + + Global::intersectPoint[6].x = cLg->p[0].x + (cos(clAngle - a) * cLg->radius[0]); + Global::intersectPoint[6].y = cLg->p[0].y + (sin(clAngle - a) * cLg->radius[0]); + Global::intersectPoint[7].x = cSm->p[0].x + (cos(clAngle + a) * cSm->radius[0]); + Global::intersectPoint[7].y = cSm->p[0].y + (sin(clAngle + a) * cSm->radius[0]); + Global::numIntersectPoints = 8; +} + +// +// Parameter 1: point in question +// Parameter 2, 3: points we are comparing to +// +Point Geometry::NearestTo(Point point, Point p1, Point p2) +{ + double l1 = Vector::Magnitude(point, p1); + double l2 = Vector::Magnitude(point, p2); + + return (l1 < l2 ? p1 : p2); +} diff --git a/src/geometry.h b/src/geometry.h index bc532da..58252b3 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -20,7 +20,9 @@ class Geometry static void FindAnglesForSides(double s1, double s2, double s3, double * a1, double * a2, double * a3); static Point GetPointForParameter(Object *, double); static Point Midpoint(Line *); + static void FindTangents(Object *, Point); + static void FindTangents(Object *, Object *); + static Point NearestTo(Point, Point, Point); }; #endif // __GEOMETRY_H__ - diff --git a/src/promptlineedit.cpp b/src/promptlineedit.cpp new file mode 100644 index 0000000..f79878d --- /dev/null +++ b/src/promptlineedit.cpp @@ -0,0 +1,80 @@ +// +// promptlineedit.cpp: QLineEdit widget with uneditable (by user) prompt +// +// Part of the Architektonas Project +// (C) 2021 Underground Software +// See the README and GPLv3 files for licensing and warranty information +// +// JLH = James Hammons +// +// WHO WHEN WHAT +// --- ---------- ------------------------------------------------------------ +// JLH 11/27/2021 Created this file +// + +#include "promptlineedit.h" + +PromptLineEdit::PromptLineEdit(QWidget * parent/*= NULL*/): QLineEdit(parent) +{ + connect(this, SIGNAL(cursorPositionChanged(int, int)), this, SLOT(HandleCursorMoved(int, int))); + connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(HandleTextChanged(const QString &))); + connect(this, SIGNAL(selectionChanged(void)), this, SLOT(HandleSelectionChanged(void))); + + SetPrompt("ATNS"); +} + +PromptLineEdit::~PromptLineEdit() +{ +} + +void PromptLineEdit::HandleCursorMoved(int /*old*/, int neu) +{ + if (neu < prompt.length()) + setCursorPosition(prompt.length()); +} + +void PromptLineEdit::HandleTextChanged(const QString & s) +{ + if (s.length() == 0) + setText(prompt); +} + +void PromptLineEdit::HandleSelectionChanged(void) +{ + int selStart = selectionStart(); + int selEnd = selectionEnd(); + int promptLen = prompt.length(); + + if (selStart == -1) + return; + + if (selStart < promptLen) + setSelection(selEnd, -(selEnd - promptLen)); +} + +void PromptLineEdit::keyPressEvent(QKeyEvent * event) +{ + if (event->key() == Qt::Key_Backspace) + { + if (cursorPosition() <= prompt.length()) + { + event->accept(); + return; + } + } + + QLineEdit::keyPressEvent(event); +} + +QString PromptLineEdit::text(void) const +{ + QString s = QLineEdit::text(); + + return s.right(s.length() - prompt.length()); +} + +void PromptLineEdit::SetPrompt(QString s) +{ + prompt = s + ": "; + setText(prompt); +} diff --git a/src/promptlineedit.h b/src/promptlineedit.h new file mode 100644 index 0000000..2c9b054 --- /dev/null +++ b/src/promptlineedit.h @@ -0,0 +1,30 @@ +#ifndef __PROMPTLINEEDIT_H__ +#define __PROMPTLINEEDIT_H__ + +#include + +class PromptLineEdit: public QLineEdit +{ + Q_OBJECT + + public: + PromptLineEdit(QWidget * parent = NULL); + ~PromptLineEdit(); + + public slots: + void HandleCursorMoved(int, int); + void HandleTextChanged(const QString &); + void HandleSelectionChanged(void); + + protected: + void keyPressEvent(QKeyEvent *); + + public: + QString text(void) const; + void SetPrompt(QString); + + private: + QString prompt; +}; + +#endif // __PROMPTLINEEDIT_H__ diff --git a/src/structs.h b/src/structs.h index bef3c52..0d16ff8 100644 --- a/src/structs.h +++ b/src/structs.h @@ -8,7 +8,7 @@ #include "rect.h" #include "vector.h" -enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer, OTCount }; +enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer, OTPolyline, OTCount }; enum DimensionType { DTLinear = 0, DTLinearVert, DTLinearHorz, DTRadial, DTDiametric, DTCircumferential, DTAngular, DTLeader, DTCount }; @@ -37,7 +37,7 @@ const char dimName[DTCount][32] = { bool hitPoint[5]; \ bool hitObject; \ Point p[5]; \ - double angle[2]; \ + double angle[3]; \ double radius[2]; \ double length; @@ -116,10 +116,22 @@ struct Text { struct Polygon { OBJECT_COMMON; + int sides; Polygon(): type(OTPolygon), id(Global::objectID++) {} }; +struct Polyline { + OBJECT_COMMON; + VPVector objects; + bool closed; + Object * clicked; + + Polyline(): type(OTPolyline), id(Global::objectID++), selected(false), hovered(false), hitObject(false), clicked(NULL) {} + void Add(void * obj) { objects.push_back(obj); } + void Add(VPVector objs) { objects.insert(objects.end(), objs.begin(), objs.end()); } +}; + struct Spline { OBJECT_COMMON; @@ -154,4 +166,3 @@ struct Container { }; #endif // __STRUCTS_H__ - diff --git a/src/vector.cpp b/src/vector.cpp index 57f49fb..89b9148 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -25,11 +25,13 @@ Vector::Vector(double xx/*= 0*/, double yy/*= 0*/, double zz/*= 0*/): x(xx), y(y { } - Vector::Vector(Vector tail, Vector head): x(head.x - tail.x), y(head.y - tail.y), z(head.z - tail.z) { } +Vector::Vector(const Vector &v): x(v.x), y(v.y), z(v.z) +{ +} // Create vector from angle + length (2D; z is set to zero) void Vector::SetAngleAndLength(double angle, double length) @@ -39,7 +41,6 @@ void Vector::SetAngleAndLength(double angle, double length) z = 0; } - Vector Vector::operator=(Vector const v) { x = v.x, y = v.y, z = v.z; @@ -47,19 +48,16 @@ Vector Vector::operator=(Vector const v) return *this; } - Vector Vector::operator+(Vector const v) { return Vector(x + v.x, y + v.y, z + v.z); } - Vector Vector::operator-(Vector const v) { return Vector(x - v.x, y - v.y, z - v.z); } - // Unary negation Vector Vector::operator-(void) @@ -67,7 +65,6 @@ Vector Vector::operator-(void) return Vector(-x, -y, -z); } - // Vector x constant Vector Vector::operator*(double const v) @@ -75,7 +72,6 @@ Vector Vector::operator*(double const v) return Vector(x * v, y * v, z * v); } - // Vector x constant Vector Vector::operator*(float const v) @@ -83,7 +79,6 @@ Vector Vector::operator*(float const v) return Vector(x * v, y * v, z * v); } - // Vector / constant Vector Vector::operator/(double const v) @@ -91,7 +86,6 @@ Vector Vector::operator/(double const v) return Vector(x / v, y / v, z / v); } - // Vector / constant Vector Vector::operator/(float const v) @@ -99,7 +93,6 @@ Vector Vector::operator/(float const v) return Vector(x / v, y / v, z / v); } - // Vector (cross) product Vector Vector::operator*(Vector const v) @@ -108,7 +101,6 @@ Vector Vector::operator*(Vector const v) return Vector((y * v.z) - (z * v.y), (z * v.x) - (x * v.z), (x * v.y) - (y * v.x)); } - // Dot product double Vector::Dot(Vector const v) @@ -116,7 +108,6 @@ double Vector::Dot(Vector const v) return (x * v.x) + (y * v.y) + (z * v.z); } - // Vector x constant, self assigned Vector& Vector::operator*=(double const v) @@ -126,7 +117,6 @@ Vector& Vector::operator*=(double const v) return *this; } - // Vector / constant, self assigned Vector& Vector::operator/=(double const v) @@ -145,7 +135,6 @@ Vector& Vector::operator+=(Vector const v) return *this; } - // Vector + constant, self assigned Vector& Vector::operator+=(double const v) @@ -155,7 +144,6 @@ Vector& Vector::operator+=(double const v) return *this; } - // Vector - vector, self assigned Vector& Vector::operator-=(Vector const v) @@ -165,7 +153,6 @@ Vector& Vector::operator-=(Vector const v) return *this; } - // Vector - constant, self assigned Vector& Vector::operator-=(double const v) @@ -175,21 +162,18 @@ Vector& Vector::operator-=(double const v) return *this; } - // Check for equality bool Vector::operator==(Vector const v) { return (x == v.x && y == v.y && z == v.z ? true : false); } - // Check for inequality bool Vector::operator!=(Vector const v) { return (x != v.x || y != v.y || z != v.z ? true : false); } - Vector Vector::Unit(void) { double mag = Magnitude(); @@ -202,13 +186,11 @@ Vector Vector::Unit(void) return Vector(x / mag, y / mag, z / mag); } - double Vector::Magnitude(void) { return sqrt((x * x) + (y * y) + (z * z)); } - double Vector::Angle(void) { // acos returns a value between zero and TAU/2, which means we don't know @@ -220,13 +202,11 @@ double Vector::Angle(void) return correctedAngle; } - bool Vector::isZero(double epsilon/*= 1e-6*/) { return (fabs(x) < epsilon && fabs(y) < epsilon && fabs(z) < epsilon ? true : false); } - // Class methods /*static*/ double Vector::Dot(Vector v1, Vector v2) @@ -234,16 +214,15 @@ bool Vector::isZero(double epsilon/*= 1e-6*/) return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); } - /*static*/ double Vector::Magnitude(Vector v1, Vector v2) { double xx = v1.x - v2.x; double yy = v1.y - v2.y; double zz = v1.z - v2.z; + return sqrt((xx * xx) + (yy * yy) + (zz * zz)); } - // // Convenience function // @@ -252,7 +231,6 @@ bool Vector::isZero(double epsilon/*= 1e-6*/) return Vector(p1, p2).Angle(); } - // Returns the parameter of a point in space to this vector. If the parameter // is between 0 and 1, the normal of the vector to the point is on the vector. // Note: v1 is the tail, v2 is the head of the line (vector). @@ -268,19 +246,19 @@ bool Vector::isZero(double epsilon/*= 1e-6*/) double magnitude = lineSegment.Magnitude(); Vector pointSegment = p - tail; double t = lineSegment.Dot(pointSegment) / (magnitude * magnitude); + return t; } - // Return the 2D normal to the linesegment formed by the passed in points. // The normal thus calculated should rotate anti-clockwise. /*static*/ Vector Vector::Normal(Vector tail, Vector head) { Vector v = (head - tail).Unit(); + return Vector(-v.y, v.x); } - /*static*/ double Vector::AngleBetween(Vector a, Vector b) { // This is done using the following formula: @@ -295,4 +273,3 @@ bool Vector::isZero(double epsilon/*= 1e-6*/) return acos(a.Dot(b) / (a.Magnitude() * b.Magnitude())); } - diff --git a/src/vector.h b/src/vector.h index 5ef0a92..67d9aa9 100644 --- a/src/vector.h +++ b/src/vector.h @@ -20,7 +20,8 @@ class Vector { public: Vector(double xx = 0, double yy = 0, double zz = 0); - Vector(Vector tail, Vector head); // Create vector from two points + Vector(Vector tail, Vector head); // Create vector from two points + Vector(const Vector &v); void SetAngleAndLength(double angle, double length); Vector operator=(Vector const v); Vector operator+(Vector const v); @@ -40,8 +41,8 @@ class Vector Vector& operator-=(Vector const v); // Vector minus Vector self-assignment Vector& operator-=(double const v); // Vector minus constant self-assignment - bool operator==(Vector const v); // Check for equality - bool operator!=(Vector const v); // Check for inequality + bool operator==(Vector const v); // Check for equality + bool operator!=(Vector const v); // Check for inequality Vector Unit(void); double Magnitude(void); -- 2.37.2