From 3c890e51a9763ffcee49e15753453a7da248272b Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Wed, 12 Jan 2022 14:02:28 -0600 Subject: [PATCH] Preliminary support for Polylines. So, as of this commit, there is Polyline support in the file format; which means that Polylines can be loaded and saved. They are also displayed properly in the GUI, but you can't interact with them as of yet other than selecting them with a selection rectangle and deleting them. The precision of the file format has been expanded from 8 decimal places to 16; also, the adding of Polylines did not necessitate bumping the format's version. --- src/drawingview.cpp | 180 ++++++++++++++++++++++++++++++++------------ src/fileio.cpp | 39 ++++++++-- src/geometry.cpp | 97 ++++++++++++++++++++++++ src/geometry.h | 3 + src/global.cpp | 2 +- src/global.h | 4 +- src/painter.cpp | 27 +++++-- src/painter.h | 7 +- src/rect.cpp | 57 +++++++++++++- src/rect.h | 6 +- src/structs.h | 10 ++- src/vector.cpp | 5 ++ src/vector.h | 1 + 13 files changed, 359 insertions(+), 79 deletions(-) diff --git a/src/drawingview.cpp b/src/drawingview.cpp index 3b6f92c..39196a7 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -133,7 +133,7 @@ void DrawingView::DrawBackground(Painter * painter) painter->SetBrush(0xF0F0F0); painter->SetPen(0xF0F0F0, 1, 1); - painter->DrawRect(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y))); + painter->DrawRect(Rect(ul, br)); double spacing = Global::gridSpacing; @@ -455,6 +455,41 @@ void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool break; + case OTPolyline: + { + painter->SetBrush(QBrush(Qt::NoBrush)); + Polyline * pl = (Polyline *)obj; + Point lastp; + double lastbump; + + for(VPVectorIter i=pl->points.begin(); i!=pl->points.end(); i++) + { + if (i != pl->points.begin()) + { + Point p = ((Object *)(*i))->p[0]; + double bump = ((Object *)(*i))->length; + + if (lastbump == 0) + painter->DrawLine(lastp, p); + else + { + Arc a = Geometry::Unpack(lastp, p, lastbump); + painter->DrawArc(a.p[0], a.radius[0], a.angle[0], a.angle[1]); + } + + lastp = p; + lastbump = bump; + } + else + { + lastp = ((Object *)(*i))->p[0]; + lastbump = ((Object *)(*i))->length; + } + } + + break; + } + case OTDimension: { Dimension * d = (Dimension *)obj; @@ -632,15 +667,12 @@ Where is the text offset? It looks like it's drawing in the center, but obvious break; } +#if 0 case OTPolygon: { break; } - - case OTPolyline: - { - break; - } +#endif case OTContainer: { @@ -1975,8 +2007,8 @@ void DrawingView::mousePressEvent(QMouseEvent * event) // Didn't hit any object and not using a tool, so do a selection // rectangle Global::selectionInProgress = true; - Global::selection.setTopLeft(QPointF(point.x, point.y)); - Global::selection.setBottomRight(QPointF(point.x, point.y)); + Global::selection.l = Global::selection.r = point.x; + Global::selection.t = Global::selection.b = point.y; select = GetSelection(); } else if (event->button() == Qt::MiddleButton) @@ -1998,7 +2030,8 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) } Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y())); - Global::selection.setBottomRight(QPointF(point.x, point.y)); + Global::selection.r = point.x; + Global::selection.b = point.y; // Only needs to be done here, as mouse down is always preceded by movement Global::snapPointIsValid = false; hoveringIntersection = false; @@ -2411,12 +2444,40 @@ Rect DrawingView::GetObjectExtents(Object * obj) double start = a->angle[0]; double end = start + a->angle[1]; + + // Swap 'em if the span is negative... + if (a->angle[1] < 0) + { + end = a->angle[0]; + start = end + a->angle[1]; + } + rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end))); // If the end of the arc is before the beginning, add 360 degrees to it if (end < start) end += TAU; +/* +Find which quadrant the start angle is in (consider the beginning of the 90° angle to be in the quadrant, the end to be in the next quadrant). Then, divide the span into 90° segments. The integer portion is the definite axis crossings; the remainder needs more scrutiny. There will be an additional axis crossing if the the sum of the start angle and the remainder is > 90°. +*/ +#if 1 + int quadStart = (int)(a->angle[0] / QTR_TAU); + double qsRemain = a->angle[0] - ((double)quadStart * QTR_TAU); + int numAxes = (int)((a->angle[1] + qsRemain) / QTR_TAU); + + double axis[4] = { 0, 0, 0, 0 }; + axis[0] = rect.t, axis[1] = rect.l, axis[2] = rect.b, axis[3] = rect.r; + double box[4] = { 1.0, -1.0, -1.0, 1.0 }; + + for(int i=0; i QTR_TAU)) rect.t = 1.0; @@ -2438,6 +2499,7 @@ Rect DrawingView::GetObjectExtents(Object * obj) if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU))) rect.b = -1.0; +#endif rect *= a->radius[0]; rect.Translate(a->p[0]); @@ -2477,6 +2539,8 @@ void DrawingView::CheckObjectBounds(void) { Object * obj = (Object *)(*i); obj->selected = false; + Rect selection = Global::selection; + selection.Normalize(); switch (obj->type) { @@ -2485,7 +2549,7 @@ void DrawingView::CheckObjectBounds(void) { Line * l = (Line *)obj; - if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y)) + if (selection.Contains(l->p[0]) && selection.Contains(l->p[1])) l->selected = true; break; @@ -2494,8 +2558,9 @@ void DrawingView::CheckObjectBounds(void) case OTCircle: { Circle * c = (Circle *)obj; + Vector radVec(c->radius[0], c->radius[0]); - if (Global::selection.contains(c->p[0].x - c->radius[0], c->p[0].y - c->radius[0]) && Global::selection.contains(c->p[0].x + c->radius[0], c->p[0].y + c->radius[0])) + if (selection.Contains(c->p[0] - radVec) && selection.Contains(c->p[0] + radVec)) c->selected = true; break; @@ -2507,73 +2572,85 @@ void DrawingView::CheckObjectBounds(void) double start = a->angle[0]; double end = start + a->angle[1]; - QPointF p1(cos(start), sin(start)); - QPointF p2(cos(end), sin(end)); - QRectF bounds(p1, p2); -#if 1 - // Swap X/Y coordinates if they're backwards... - if (bounds.left() > bounds.right()) - { - double temp = bounds.left(); - bounds.setLeft(bounds.right()); - bounds.setRight(temp); - } - - if (bounds.bottom() > bounds.top()) + // Swap 'em if the span is negative... + if (a->angle[1] < 0) { - double temp = bounds.bottom(); - bounds.setBottom(bounds.top()); - bounds.setTop(temp); + end = a->angle[0]; + start = end + a->angle[1]; } -#else - // Doesn't work as advertised! For shame! - bounds = bounds.normalized(); -#endif // If the end of the arc is before the beginning, add 360 degrees // to it if (end < start) end += TAU; +#if 1 + int quadStart = (int)(a->angle[0] / QTR_TAU); + double qsRemain = a->angle[0] - ((double)quadStart * QTR_TAU); + int numAxes = (int)((a->angle[1] + qsRemain) / QTR_TAU); + + Rect bounds(sin(start), cos(start), sin(end), cos(end)); + const double box[4] = { 1.0, -1.0, -1.0, 1.0 }; + + for(int i=0; i QTR_TAU)) - bounds.setTop(1.0); + bounds.t = 1.0; if ((start < HALF_TAU) && (end > HALF_TAU)) - bounds.setLeft(-1.0); + bounds.l = -1.0; if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU)) - bounds.setBottom(-1.0); + bounds.b = -1.0; if ((start < TAU) && (end > TAU)) - bounds.setRight(1.0); + bounds.r = 1.0; if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU))) - bounds.setTop(1.0); + bounds.t = 1.0; if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU))) - bounds.setLeft(-1.0); + bounds.l = -1.0; if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU))) - bounds.setBottom(-1.0); + bounds.b = -1.0; +#endif - bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0])); - bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0])); - bounds.translate(a->p[0].x, a->p[0].y); + bounds *= a->radius[0]; + bounds.Translate(a->p[0]); - if (Global::selection.contains(bounds)) + if (selection.Contains(bounds)) a->selected = true; break; } + case OTPolyline: + { + Polyline * pl = (Polyline *)obj; + Rect r(((Object *)(pl->points[0]))->p[0]); + + for(int i=0; i<(pl->points.size()-1); i++) + { + r += ((Object *)(pl->points[i]))->p[0]; + r += ((Object *)(pl->points[i + 1]))->p[0]; + } + + if (selection.Contains(r)) + pl->selected = true; + + break; + } + case OTText: { Text * t = (Text *)obj; Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height())); - if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b)) + if (selection.Contains(r)) t->selected = true; break; @@ -2583,7 +2660,7 @@ void DrawingView::CheckObjectBounds(void) { Container * c = (Container *)obj; - if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y)) + if (selection.Contains(c->p[0]) && selection.Contains(c->p[1])) c->selected = true; break; @@ -3049,14 +3126,14 @@ N.B.: Mixing fixed length with fixed angle (and in this order) is probably *not* obj->angle[1] += TAU; QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0); - informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2); + informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'f', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'f', 2); return; } double angle = Vector::Angle(obj->p[0], point); obj->angle[0] = angle; QString text = QObject::tr("Start angle: %1") + QChar(0x00B0); - informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4); + informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 4); } else if (obj->hitPoint[2]) { @@ -3070,7 +3147,7 @@ N.B.: Mixing fixed length with fixed angle (and in this order) is probably *not* obj->angle[1] += TAU; QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0); - informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2); + informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'f', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'f', 2); return; } @@ -3080,8 +3157,13 @@ N.B.: Mixing fixed length with fixed angle (and in this order) is probably *not* if (obj->angle[0] < 0) obj->angle[0] += TAU; + double endAngle = obj->angle[0] + obj->angle[1]; + + if (endAngle > TAU) + endAngle -= TAU; + QString text = QObject::tr("End angle: %1") + QChar(0x00B0); - informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4); + informativeText = text.arg(endAngle * RADIANS_TO_DEGREES, 0, 'f', 4); } else if (obj->hitObject) { @@ -3092,7 +3174,7 @@ N.B.: Mixing fixed length with fixed angle (and in this order) is probably *not* obj->radius[0] = Vector::Magnitude(obj->p[0], point); QString text = QObject::tr("Radius: %1"); - informativeText = text.arg(obj->radius[0], 0, 'd', 4); + informativeText = text.arg(obj->radius[0], 0, 'f', 4); } break; diff --git a/src/fileio.cpp b/src/fileio.cpp index 3ce4470..ad303f4 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -410,6 +410,20 @@ if (errno) fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2); obj = (Object *)new Arc(p, r, a1, a2); } + else if (strcmp(buffer, "POLYLINE") == 0) + { + long int size; + obj = (Object *)new Polyline(); + fscanf(file, "(%li)", &size); + fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style); + + for(int i=0; ip[0].x, &po->p[0].y, &po->length); + ((Polyline *)obj)->Add(po); + } + } else if (strcmp(buffer, "TEXT") == 0) { Point p; @@ -448,7 +462,7 @@ if (errno) { obj->layer = foundLayer; - if (extended && (obj->type != OTContainer)) + if (extended && (obj->type != OTContainer) && (obj->type != OTPolyline)) { fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style); } @@ -466,20 +480,31 @@ if (errno) switch (obj->type) { case OTLine: - fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y); + fprintf(file, "LINE %i (%.16lf,%.16lf) (%.16lf,%.16lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y); break; case OTCircle: - fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]); + fprintf(file, "CIRCLE %i (%.16lf,%.16lf) %.16lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]); break; case OTEllipse: break; case OTArc: - fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]); + fprintf(file, "ARC %i (%.16lf,%.16lf) %.16lf, %.16lf, %.16lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]); break; - case OTPolygon: + case OTPolyline: + { + Polyline * p = (Polyline *)obj; + fprintf(file, "POLYLINE %i (%li)", p->layer, p->points.size()); + fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style); + + for(VPVectorIter i=p->points.begin(); i!=p->points.end(); i++) + { + Object * po = (Object *)(*i); + fprintf(file, "(%.16lf,%.16lf,%.16lf)\n", po->p[0].x, po->p[0].y, po->length); + } + } break; case OTDimension: - fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype, ((Dimension *)obj)->offset); + fprintf(file, "DIMENSION %i (%.16lf,%.16lf) (%.16lf,%.16lf) %i %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype, ((Dimension *)obj)->offset); break; case OTSpline: break; @@ -507,7 +532,7 @@ if (errno) break; } - if (obj->type != OTContainer) + if ((obj->type != OTContainer) && (obj->type != OTPolyline)) fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style); return true; diff --git a/src/geometry.cpp b/src/geometry.cpp index 055c581..6b3935d 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -524,3 +524,100 @@ Vector Geometry::GetNormalOfPointAndLine(Point p, Line * l) return normal; } + +Circle Geometry::FindCircleForThreePoints(Point p1, Point p2, Point p3) +{ + // We use matrices and determinants to find the center and radius of the + // circle, given the three points passed in: + // + // [(x^2 + y^2) x y 1 ] + // [(x1^2 + y1^2) x1 y1 1 ] + // [(x2^2 + y2^2) x2 y2 1 ] + // [(x3^2 + y3^2) x3 y3 1 ] + // + // Then, center is found by: + // + // x0 = 1/2 • M12/M11, y0 = -1/2 • M13/M11 + // + // The radius can be found with the center and any of the points via + // Pythagoras, or with: + // + // r^2 = x0^2 + y0^2 + M14/M11 + // + // If M11 = 0, then the three points are colinear and there is no circle. + // (M## is the minor determinant of the 4x4 matrix, where a 3x3 matrix is + // created by deleting the row and column of the 4x4 with the indices + // given.) + + Circle c; + double m[3][4]; + + m[0][0] = (p1.x * p1.x) + (p1.y * p1.y); + m[0][1] = p1.x; + m[0][2] = p1.y; + m[0][3] = 1.0; + m[1][0] = (p2.x * p2.x) + (p2.y * p2.y); + m[1][1] = p2.x; + m[1][2] = p2.y; + m[1][3] = 1.0; + m[2][0] = (p3.x * p3.x) + (p3.y * p3.y); + m[2][1] = p3.x; + m[2][2] = p3.y; + m[2][3] = 1.0; + + double minor11 = Determinant3x3(m[0][1], m[0][2], m[0][3], m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3]); + + // Sanity check: if M11 is zero, the points are colinear. + if (minor11 == 0) + return c; + + double minor12 = Determinant3x3(m[0][0], m[0][2], m[0][3], m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], m[2][3]); + double minor13 = Determinant3x3(m[0][0], m[0][1], m[0][3], m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], m[2][3]); + double minor14 = Determinant3x3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2]); + + c.p[0].x = 0.5 * (minor12 / minor11); + c.p[0].y = -0.5 * (minor13 / minor11); + c.radius[0] = sqrt((c.p[0].x * c.p[0].x) + (c.p[0].y * c.p[0].y) + (minor14 / minor11)); + + return c; +} + +double Geometry::Determinant3x3(double a11, double a12, double a13, double a21, double a22, double a23, double a31, double a32, double a33) +{ + return (a11 * ((a22 * a33) - (a32 * a23))) + - (a12 * ((a21 * a33) - (a31 * a23))) + + (a13 * ((a21 * a32) - (a31 * a22))); +} + +Arc Geometry::Unpack(Point tail, Point head, double bump) +{ + double length = Vector::Magnitude(tail, head) / 2.0; + double bumpLen = length * fabs(bump); + Point midpoint = Vector::Midpoint(tail, head); + Vector mpNormal = Vector::Normal(tail, head); // Normal points to the left + + // Flip the normal if the bump is pointing left + if (bump < 0) + mpNormal = -mpNormal; + + // N.B.: The radius can also be found with r = (a² + h²) / 2h where a is + // the 1/2 length of the line segment and h is the bump length. +// double radius = (length + (1.0 / length)) / 2.0; + double radius = 0.5 * (((length * length) + (bumpLen * bumpLen)) / bumpLen); + Vector ctrVec = mpNormal * (radius - bumpLen); + Point center = midpoint + ctrVec; + + // Find arc endpoints... + double angle1 = Vector::Angle(center, tail); + double angle2 = Vector::Angle(center, head); + double span = angle2 - angle1; + + // Fix span depending on which way the arc is being drawn... + if (bump > 0 && span < 0) + span += TAU; + + if (bump < 0 && span > 0) + span -= TAU; + + return Arc(center, radius, angle1, span); +} diff --git a/src/geometry.h b/src/geometry.h index b5e6c6b..0a05b37 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -24,6 +24,9 @@ class Geometry static void FindTangents(Object *, Object *); static Point NearestTo(Point, Point, Point); static Vector GetNormalOfPointAndLine(Point, Line *); + static Circle FindCircleForThreePoints(Point, Point, Point); + static double Determinant3x3(double, double, double, double, double, double, double, double, double); + static Arc Unpack(Point, Point, double); }; #endif // __GEOMETRY_H__ diff --git a/src/global.cpp b/src/global.cpp index 624c94f..a60fe8c 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -21,7 +21,7 @@ bool Global::snapToGrid = true; bool Global::ignoreClicks = false; bool Global::dontMove = false; bool Global::selectionInProgress = false; -QRectF Global::selection; +Rect Global::selection; int Global::tool = TTNone; int Global::toolState = TSNone; diff --git a/src/global.h b/src/global.h index 597cead..6418b7f 100644 --- a/src/global.h +++ b/src/global.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include "rect.h" #include "vector.h" class QFont; @@ -24,7 +24,7 @@ class Global public: static double gridSpacing; static bool selectionInProgress; - static QRectF selection; + static Rect selection; static QFont * font; static Point snapPoint; static bool snapPointIsValid; diff --git a/src/painter.cpp b/src/painter.cpp index 853d350..2314a85 100644 --- a/src/painter.cpp +++ b/src/painter.cpp @@ -372,6 +372,16 @@ void Painter::DrawPoint(int x, int y) painter->drawPoint(v.x, v.y); } +// The rect passed in is in Qt coordinates... (?) +void Painter::DrawRoundedRect(Rect rect, double radiusX, double radiusY) +{ + if (!painter) + return; + + QRectF r(QPointF(rect.l, rect.t), QPointF(rect.r, rect.b)); + painter->drawRoundedRect(r, radiusX, radiusY); +} + // The rect passed in is in Qt coordinates... void Painter::DrawRoundedRect(QRectF rect, double radiusX, double radiusY) { @@ -383,35 +393,36 @@ void Painter::DrawRoundedRect(QRectF rect, double radiusX, double radiusY) // The rect passed in is in Cartesian but we want to pad it by a set number of // pixels (currently set at 8), so the pad looks the same regardless of zoom. -void Painter::DrawPaddedRect(QRectF rect) +void Painter::DrawPaddedRect(Rect rect) { if (!painter) return; - Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y())); - Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom())); + Vector v1 = CartesianToQtCoords(rect.TopLeft()); + Vector v2 = CartesianToQtCoords(rect.BottomRight()); QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y)); screenRect.adjust(-8, 8, 8, -8); // Left/top, right/bottom painter->drawRect(screenRect); } -void Painter::DrawRect(QRectF rect) +void Painter::DrawRect(Rect rect) { if (!painter) return; - Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y())); - Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom())); + Vector v1 = CartesianToQtCoords(rect.TopLeft()); + Vector v2 = CartesianToQtCoords(rect.BottomRight()); QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y)); painter->drawRect(screenRect); } -void Painter::DrawText(QRectF rect, int type, QString text) +void Painter::DrawText(Rect rect, int type, QString text) { if (!painter) return; - painter->drawText(rect, (Qt::AlignmentFlag)type, text); + QRectF r(QPointF(rect.l, rect.t), QPointF(rect.r, rect.b)); + painter->drawText(r, (Qt::AlignmentFlag)type, text); } void Painter::DrawArrowhead(Vector head, Vector tail, double size) diff --git a/src/painter.h b/src/painter.h index e89f045..d460ebc 100644 --- a/src/painter.h +++ b/src/painter.h @@ -36,10 +36,11 @@ class Painter void DrawHLine(double); void DrawVLine(double); void DrawPoint(int, int); + void DrawRoundedRect(Rect, double, double); void DrawRoundedRect(QRectF, double, double); - void DrawPaddedRect(QRectF); - void DrawRect(QRectF); - void DrawText(QRectF, int, QString); + void DrawPaddedRect(Rect); + void DrawRect(Rect); + void DrawText(Rect, int, QString); void DrawArrowhead(Vector, Vector, double); void DrawCrosshair(Vector); void DrawInformativeText(QString); diff --git a/src/rect.cpp b/src/rect.cpp index 54f44dd..c38c074 100644 --- a/src/rect.cpp +++ b/src/rect.cpp @@ -12,14 +12,15 @@ // JLH 11/10/2016 Created this file // + #include "rect.h" -//#include +#include Rect::Rect(): l(0), r(0), t(0), b(0) { } -Rect::Rect(double ll, double rr, double tt, double bb): +Rect::Rect(double tt, double ll, double bb, double rr): l(ll), r(rr), t(tt), b(bb) { Normalize(); @@ -30,6 +31,10 @@ Rect::Rect(Point tl, Point br): l(tl.x), r(br.x), t(tl.y), b(br.y) Normalize(); } +Rect::Rect(Point p): l(p.x), r(p.x), t(p.y), b(p.y) +{ +} + Rect & Rect::operator*=(double scale) { l *= scale; @@ -58,6 +63,45 @@ Rect & Rect::operator|=(Rect r2) return *this; } +Rect & Rect::operator+=(Point p) +{ + if (p.x < l) + l = p.x; + + if (p.x > r) + r = p.x; + + if (p.y < b) + b = p.y; + + if (p.y > t) + t = p.y; + + return *this; +} + +// +// We use this to give a rect an array-like access, which allows access of the +// rect in TLBR order (from 0 to 3). Also, values greater than 3 are treated +// as mod 4. +// +double & Rect::operator[](int idx) +{ + idx = idx % 4; + + switch (idx) + { + case 0: + return t; + case 1: + return l; + case 2: + return b; + } + + return r; +} + void Rect::Normalize(void) { if (l > r) @@ -93,12 +137,12 @@ void Rect::Expand(double amt) double Rect::Width(void) { - return r - l; + return fabs(r - l); } double Rect::Height(void) { - return t - b; + return fabs(t - b); } bool Rect::Contains(Point p) @@ -106,6 +150,11 @@ bool Rect::Contains(Point p) return ((p.x >= l) && (p.x <= r) && (p.y >= b) && (p.y <= t) ? true : false); } +bool Rect::Contains(Rect rect) +{ + return ((rect.l >= l) && (rect.r <= r) && (rect.b >= b) && (rect.t <= t) ? true : false); +} + Point Rect::TopLeft(void) { return Point(l, t); diff --git a/src/rect.h b/src/rect.h index 0098a8d..23b79ff 100644 --- a/src/rect.h +++ b/src/rect.h @@ -13,16 +13,20 @@ struct Rect double l, r, t, b; Rect(); - Rect(double ll, double rr, double tt, double bb); + Rect(double tt, double ll, double bb, double rr); Rect(Point tl, Point br); + Rect(Point); Rect & operator*=(double scale); Rect & operator|=(Rect x); + Rect & operator+=(Point p); + double & operator[](int); void Normalize(void); void Translate(Point p); void Expand(double amt); double Width(void); double Height(void); bool Contains(Point p); + bool Contains(Rect r); Point TopLeft(void); Point TopRight(void); Point BottomLeft(void); diff --git a/src/structs.h b/src/structs.h index 47d8d85..8250a00 100644 --- a/src/structs.h +++ b/src/structs.h @@ -125,6 +125,7 @@ struct Text { measured(false), s(str) { p[0] = pt1; angle[0] = 0; } }; +//prolly don't need this, as this just a special case of a polyline... struct Polygon { OBJECT_COMMON; int sides; @@ -134,13 +135,14 @@ struct Polygon { struct Polyline { OBJECT_COMMON; - VPVector objects; - bool closed; + VPVector points; +//need this? could just repeat the endpoint as well... +// 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()); } + void Add(void * obj) { points.push_back(obj); } + void Add(VPVector objs) { points.insert(points.end(), objs.begin(), objs.end()); } }; struct Spline { diff --git a/src/vector.cpp b/src/vector.cpp index e62e580..8b2dda2 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -281,3 +281,8 @@ bool Vector::isZero(double epsilon/*= 1e-6*/) return acos(a.Dot(b) / (a.Magnitude() * b.Magnitude())); } + +/*static*/ Point Vector::Midpoint(Point p1, Point p2) +{ + return Point((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0); +} diff --git a/src/vector.h b/src/vector.h index c3d6a4d..97985c5 100644 --- a/src/vector.h +++ b/src/vector.h @@ -58,6 +58,7 @@ class Vector static double Parameter(Vector v1, Vector v2, Vector p); static Vector Normal(Vector v1, Vector v2); static double AngleBetween(Vector a, Vector b); + static Point Midpoint(Point p1, Point p2); public: double x, y, z; -- 2.37.2