From 6533354910fbf76d9747deeae02b2e910ef9aa48 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Fri, 10 Feb 2017 16:09:24 -0600 Subject: [PATCH] Added angle snap to whole degrees, ability to manipulate Dimensions. --- src/drawingview.cpp | 239 +++++++++++++++++++++++++++++++++++++++++--- src/drawingview.h | 3 + src/geometry.cpp | 12 +++ src/structs.h | 10 +- 4 files changed, 246 insertions(+), 18 deletions(-) diff --git a/src/drawingview.cpp b/src/drawingview.cpp index 9fb0332..943a9b9 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -49,7 +49,7 @@ DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent), gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE), scale(1.0), offsetX(-10), offsetY(-10), document(true), gridPixels(0), collided(false), hoveringIntersection(false), - dragged(NULL), draggingObject(false) + dragged(NULL), draggingObject(false), angleSnap(false) { //wtf? doesn't work except in c++11??? document = { 0 }; setBackgroundRole(QPalette::Base); @@ -444,7 +444,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int Vector v(d->p[0], d->p[1]); double angle = v.Angle(); Vector unit = v.Unit(); - Vector linePt1 = d->p[0], linePt2 = d->p[1]; + d->lp[0] = d->p[0], d->lp[1] = d->p[1]; Vector ortho; double x1, y1, length; @@ -465,7 +465,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int angle = QTR_TAU; } - linePt1.x = linePt2.x = x1; + d->lp[0].x = d->lp[1].x = x1; length = fabs(d->p[0].y - d->p[1].y); } else if (d->subtype == DTLinearHorz) @@ -485,22 +485,22 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int angle = HALF_TAU; } - linePt1.y = linePt2.y = y1; + d->lp[0].y = d->lp[1].y = y1; length = fabs(d->p[0].x - d->p[1].x); } else if (d->subtype == DTLinear) { - angle = Vector(linePt1, linePt2).Angle(); - ortho = Vector::Normal(linePt1, linePt2); + angle = Vector(d->lp[0], d->lp[1]).Angle(); + ortho = Vector::Normal(d->lp[0], d->lp[1]); length = v.Magnitude(); } - unit = Vector(linePt1, linePt2).Unit(); + unit = Vector(d->lp[0], d->lp[1]).Unit(); - Point p1 = linePt1 + (ortho * 10.0 * scaledThickness); - Point p2 = linePt2 + (ortho * 10.0 * scaledThickness); - Point p3 = linePt1 + (ortho * 16.0 * scaledThickness); - Point p4 = linePt2 + (ortho * 16.0 * scaledThickness); + Point p1 = d->lp[0] + (ortho * 10.0 * scaledThickness); + Point p2 = d->lp[1] + (ortho * 10.0 * scaledThickness); + Point p3 = d->lp[0] + (ortho * 16.0 * scaledThickness); + Point p4 = d->lp[1] + (ortho * 16.0 * scaledThickness); Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness); Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness); @@ -514,7 +514,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int // Calculate whether or not the arrowheads are too crowded to put // inside the extension lines. 9.0 is the length of the arrowhead. - double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness)); + double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness)); // On the screen, it's acting like this is actually 58%... // This is correct, we want it to happen at > 50% @@ -557,6 +557,51 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int painter->DrawAngledText(ctr, angle, dimText, scaledThickness); + if (d->hitObject) + { + Point hp1 = (p1 + p2) / 2.0; + Point hp2 = (p1 + hp1) / 2.0; + Point hp3 = (hp1 + p2) / 2.0; + + if (d->hitPoint[2]) + { + painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine)); + painter->SetBrush(QBrush(QColor(Qt::magenta))); + painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU); + painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine)); + } + + painter->DrawHandle(hp1); + painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine)); + + if (d->hitPoint[3]) + { + painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine)); + painter->SetBrush(QBrush(QColor(Qt::magenta))); + painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0))); + painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine)); + } + + painter->DrawHandle(hp2); + painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine)); + + if (d->hitPoint[4]) + { + painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine)); + painter->SetBrush(QBrush(QColor(Qt::magenta))); + painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU))); + painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine)); + } + + painter->DrawHandle(hp3); + } + + if (obj->hitPoint[0]) + painter->DrawHandle(obj->p[0]); + + if (obj->hitPoint[1]) + painter->DrawHandle(obj->p[1]); + break; } case OTText: @@ -646,6 +691,9 @@ void DrawingView::resizeEvent(QResizeEvent * /*event*/) void DrawingView::ToolHandler(int mode, Point p) { + // Drop angle snap until it's needed + angleSnap = false; + if (Global::tool == TTLine) LineHandler(mode, p); else if (Global::tool == TTCircle) @@ -904,9 +952,15 @@ void DrawingView::ArcHandler(int mode, Point p) else if (Global::toolState == TSPoint2) toolPoint[1] = p; else if (Global::toolState == TSPoint3) + { toolPoint[2] = p; + angleSnap = true; + } else + { toolPoint[3] = p; + angleSnap = true; + } break; case ToolMouseUp: @@ -920,7 +974,7 @@ void DrawingView::ArcHandler(int mode, Point p) { if (shiftDown) { - // Key override is telling us to start circle at new center, not + // Key override is telling us to start arc at new center, not // continue the current one. toolPoint[0] = toolPoint[1]; return; @@ -980,6 +1034,7 @@ void DrawingView::RotateHandler(int mode, Point p) if (shiftDown) return; + angleSnap = true; double angle = Vector(toolPoint[0], toolPoint[1]).Angle(); std::vector::iterator j = select.begin(); std::vector::iterator i = toolScratch.begin(); @@ -1082,6 +1137,7 @@ void DrawingView::MirrorHandler(int mode, Point p) if (shiftDown) return; + angleSnap = true; double angle = Vector(toolPoint[0], toolPoint[1]).Angle(); std::vector::iterator j = select.begin(); std::vector::iterator i = toolScratch.begin(); @@ -1197,6 +1253,14 @@ void DrawingView::mousePressEvent(QMouseEvent * event) dragged = (Object *)hover[0]; draggingObject = true; + // See if anything is using just a straight click on a handle + if (HandleObjectClicked()) + { + draggingObject = false; + update(); + return; + } + // Needed for grab & moving objects // We do it *after*... why? (doesn't seem to confer any advantage...) if (hoveringIntersection) @@ -1328,7 +1392,12 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) else if (hoverPointValid) point = hoverPoint; else if (Global::snapToGrid) - point = SnapPointToGrid(point); + { + if (angleSnap) + point = SnapPointToAngle(point); + else + point = SnapPointToGrid(point); + } ToolHandler(ToolMouseMove, point); } @@ -1501,6 +1570,24 @@ Point DrawingView::SnapPointToGrid(Point point) } +Point DrawingView::SnapPointToAngle(Point point) +{ + // Snap to a single digit angle (using toolpoint #1 as the center) + double angle = Vector::Angle(toolPoint[0], point); + double length = Vector::Magnitude(toolPoint[0], point); + + // Convert from radians to degrees + double degAngle = angle * RADIANS_TO_DEGREES; + double snapAngle = (double)((int)(degAngle + 0.5)); + + Vector v; + v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length); + point = toolPoint[0] + v; + + return point; +} + + Rect DrawingView::GetObjectExtents(Object * obj) { // Default to empty rect, if object checks below fail for some reason @@ -1796,9 +1883,17 @@ bool DrawingView::HitTest(Object * obj, Point point) if ((length * Global::zoom) < 8.0) obj->hitPoint[0] = true; else if ((length2 * Global::zoom) < 8.0) + { obj->hitPoint[1] = true; + hoverPoint = handle1; + hoverPointValid = true; + } else if ((length3 * Global::zoom) < 8.0) + { obj->hitPoint[2] = true; + hoverPoint = handle2; + hoverPointValid = true; + } else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1])) obj->hitObject = true; @@ -1809,6 +1904,62 @@ bool DrawingView::HitTest(Object * obj, Point point) break; } + case OTDimension: + { + bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject; + obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false; + + Dimension * d = (Dimension *)obj; + + Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]); + // Get our line parallel to our points + float scaledThickness = Global::scale * obj->thickness; + Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness); + Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness); + Point p3(p1, point); + + Vector v1(d->p[0], point); + Vector v2(d->p[1], point); + Vector lineSegment(p1, p2); + double t = Geometry::ParameterOfLineAndPoint(p1, p2, point); + double distance; + Point midpoint = (p1 + p2) / 2.0; + Point hFSPoint = Point(midpoint, point); + Point hCS1Point = Point((p1 + midpoint) / 2.0, point); + Point hCS2Point = Point((midpoint + p2) / 2.0, point); + + if (t < 0.0) + distance = v1.Magnitude(); + else if (t > 1.0) + distance = v2.Magnitude(); + else + // distance = ?Det?(ls, v1) / |ls| + distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y) + / lineSegment.Magnitude()); + + if ((v1.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[0] = true; + else if ((v2.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[1] = true; + else if ((distance * Global::zoom) < 5.0) + obj->hitObject = true; + + if ((hFSPoint.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[2] = true; + else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[3] = true; + else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[4] = true; + +// return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false); + obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false); + + if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject)) + needUpdate = true; + + + break; + } case OTContainer: { // Containers must be recursively tested... @@ -1841,6 +1992,50 @@ bool DrawingView::HitTest(Object * obj, Point point) } +bool DrawingView::HandleObjectClicked(void) +{ + if (dragged->type == OTDimension) + { + Dimension * d = (Dimension *)dragged; + + if (d->hitPoint[2]) + { + // Hit the "flip sides" switch, so flip 'em + Point temp = d->p[0]; + d->p[0] = d->p[1]; + d->p[1] = temp; + return true; + } + else if (d->hitPoint[3]) + { + // There are three cases here: aligned, horizontal, & vertical. + // Aligned and horizontal do the same thing, vertical goes back to + // linear. + if (d->subtype == DTLinearVert) + d->subtype = DTLinear; + else + d->subtype = DTLinearVert; + + return true; + } + else if (d->hitPoint[4]) + { + // There are three cases here: aligned, horizontal, & vertical. + // Aligned and vertical do the same thing, horizontal goes back to + // linear. + if (d->subtype == DTLinearHorz) + d->subtype = DTLinear; + else + d->subtype = DTLinearHorz; + + return true; + } + } + + return false; +} + + void DrawingView::HandleObjectMovement(Point point) { Point delta = point - oldPoint; @@ -1864,6 +2059,7 @@ void DrawingView::HandleObjectMovement(Point point) } break; + case OTCircle: if (obj->hitPoint[0]) obj->p[0] = point; @@ -1878,6 +2074,7 @@ void DrawingView::HandleObjectMovement(Point point) } break; + case OTArc: if (obj->hitPoint[0]) obj->p[0] = point; @@ -1946,6 +2143,20 @@ void DrawingView::HandleObjectMovement(Point point) } break; + + case OTDimension: + if (obj->hitPoint[0]) + obj->p[0] = point; + else if (obj->hitPoint[1]) + obj->p[1] = point; + else if (obj->hitObject) + { + obj->p[0] += delta; + obj->p[1] += delta; + } + + break; + case OTContainer: // This is shitty, but works for now until I can code up something // nicer :-) diff --git a/src/drawingview.h b/src/drawingview.h index 8e7357e..92ac72a 100644 --- a/src/drawingview.h +++ b/src/drawingview.h @@ -21,6 +21,7 @@ class DrawingView: public QWidget void SetGridSize(uint32_t); void UpdateGridBackground(void); Point SnapPointToGrid(Point); + Point SnapPointToAngle(Point); void RenderObjects(Painter *, std::vector &, int, bool ignoreLayer = false); void AddHoveredToSelection(void); void GetSelection(std::vector &); @@ -36,6 +37,7 @@ class DrawingView: public QWidget void CheckObjectBounds(void); bool HitTestObjects(Point); bool HitTest(Object *, Point); + bool HandleObjectClicked(void); void HandleObjectMovement(Point); public slots: @@ -92,6 +94,7 @@ class DrawingView: public QWidget bool hoveringIntersection; Object * dragged; bool draggingObject; + bool angleSnap; }; #endif // __DRAWINGVIEW_H__ diff --git a/src/geometry.cpp b/src/geometry.cpp index e5a6af2..b52ff7e 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -340,3 +340,15 @@ Point Geometry::GetPointForParameter(Object * obj, double t) return Point(0, 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 + • 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 + +*/ + diff --git a/src/structs.h b/src/structs.h index bc68adf..54e2f51 100644 --- a/src/structs.h +++ b/src/structs.h @@ -24,9 +24,9 @@ enum ToolState { TSNone, TSPoint1, TSPoint2, TSPoint3, TSPoint4, TSDone }; int style; \ bool selected; \ bool hovered; \ - bool hitPoint[4]; \ + bool hitPoint[5]; \ bool hitObject; \ - Point p[4]; \ + Point p[5]; \ double angle[2]; \ double radius[2]; @@ -73,11 +73,13 @@ struct Dimension { OBJECT_COMMON; int subtype; double offset; + Point lp[2]; // Line point, the actual dimension line Dimension(): type(OTDimension), id(Global::objectID++) {} Dimension(Vector pt1, Vector pt2, DimensionType dt = DTLinear, float th = 1.0, uint32_t c = 0x0000FF, int l = LSSolid): - type(OTDimension), id(Global::objectID++), layer(0), color(c), thickness(th), - style(l), selected(false), hovered(false), hitObject(false), subtype(dt) { p[0] = pt1; p[1] = pt2; } + type(OTDimension), id(Global::objectID++), layer(0), color(c), + thickness(th), style(l), selected(false), hovered(false), + hitObject(false), subtype(dt), offset(0) { p[0] = pt1; p[1] = pt2; } }; struct Text { -- 2.37.2