]> Shamusworld >> Repos - architektonas/commitdiff
Preliminary support for Polylines.
authorShamus Hammons <jlhamm@acm.org>
Wed, 12 Jan 2022 20:02:28 +0000 (14:02 -0600)
committerShamus Hammons <jlhamm@acm.org>
Wed, 12 Jan 2022 20:02:28 +0000 (14:02 -0600)
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.

13 files changed:
src/drawingview.cpp
src/fileio.cpp
src/geometry.cpp
src/geometry.h
src/global.cpp
src/global.h
src/painter.cpp
src/painter.h
src/rect.cpp
src/rect.h
src/structs.h
src/vector.cpp
src/vector.h

index 3b6f92c6663c363bba9b1cf68e006793f101a7e0..39196a753646a9eb7929e8d699b9e21f89c8fce3 100644 (file)
@@ -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<numAxes; i++)
+                       axis[(quadStart + i) % 4] = box[(quadStart + i) % 4];
+
+               // The rect is constructed the same way we traverse the axes: TLBR
+               Rect r2(axis[0], axis[1], axis[2], axis[3]);
+
+               rect |= r2;
+#else
                // Adjust the bounds depending on which axes are crossed
                if ((start < QTR_TAU) && (end > 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<numAxes; i++)
+                               bounds[(quadStart + i) % 4] = box[(quadStart + i) % 4];
+#else
                        // Adjust the bounds depending on which axes are crossed
                        if ((start < QTR_TAU) && (end > 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;
index 3ce447040014fe9ea7e53bb5d4be90bda16f0521..ad303f469cdcb0713a9afcb4e67533c1b428c3f3 100644 (file)
@@ -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; i<size; i++)
+               {
+                       Object * po = new Object();
+                       fscanf(file, "(%lf,%lf,%lf)\n", &po->p[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;
index 055c581a652a7505e9a42a97c220649385ccfe30..6b3935db57895998a63026dcb592c7b604229260 100644 (file)
@@ -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 <x0, y0> 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);
+}
index b5e6c6bf8623c3d9c7aa361ebbc60632d1a593df..0a05b373460224f2a1e12d39463ccacbc12a57a6 100644 (file)
@@ -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__
index 624c94f8b136fdd4d33cefec48032ff94fb0bc72..a60fe8c3c5fe083f5ccf4762f8c6f16ee62b4e00 100644 (file)
@@ -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;
index 597ceadf0fa5762cef44c0cb7ff33adb8a68548c..6418b7f92587bdcec5730ed0fc811f73a38d7dff 100644 (file)
@@ -7,7 +7,7 @@
 #include <stdint.h>
 #include <string>
 #include <vector>
-#include <QRectF>
+#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;
index 853d3502690a494afdeb3635925d275fa087e6c8..2314a8559b4de310848a8af737096b0e829b3712 100644 (file)
@@ -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)
index e89f045b7d55a16eb852eb38996e90e2a9b3d777..d460ebcc2c9c349dd3782989150ada6b2cab1fdc 100644 (file)
@@ -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);
index 54f44ddf73abcb404c95d1b6d397674a90a16b8c..c38c0741e08d2d08b0568afbd4e4034c45151c97 100644 (file)
 // JLH  11/10/2016  Created this file
 //
 
+
 #include "rect.h"
-//#include <stdio.h>
+#include <math.h>
 
 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);
index 0098a8d54cd32066d2a0a5e6b3a969f4422206ec..23b79fff0a1ec041bbc3b97b94cab7ca7b4840bb 100644 (file)
@@ -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);
index 47d8d85a50795519943efebfea49879502c303e2..8250a002c8dab39c8ff820b210e70ea60613bac0 100644 (file)
@@ -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 {
index e62e5800ce5528314f24da40216a65eb8e9b5550..8b2dda2a22301372e956b8bad7fcb2da5fd56d37 100644 (file)
@@ -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);
+}
index c3d6a4df89e4d750c42e91f3661ea4739c52d785..97985c50aeb5286f15cf80efcff444c51c3e00dc 100644 (file)
@@ -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;