diff --git a/src/libs/vgeometry/vellipticalarc.cpp b/src/libs/vgeometry/vellipticalarc.cpp index 8190f5792..bf9330fe8 100644 --- a/src/libs/vgeometry/vellipticalarc.cpp +++ b/src/libs/vgeometry/vellipticalarc.cpp @@ -47,6 +47,10 @@ namespace { constexpr qreal tolerance = accuracyPointOnLine/8; + +// Because of overflow we cannot generate arcs more than maxRadius +constexpr int maxRadius = 10000; + //--------------------------------------------------------------------------------------------------------------------- auto VLen(fpm::fixed_16_16 x, fpm::fixed_16_16 y) -> fpm::fixed_16_16 { @@ -201,6 +205,27 @@ auto EllipticArcPoints(QPointF c, qreal radius1, qreal radius2, qreal astart, qr return arc; } + +//--------------------------------------------------------------------------------------------------------------------- +auto JoinVectors(const QVector &v1, const QVector &v2) -> QVector +{ + QVector v; + v.reserve(v1.size() + v2.size()); + + v = v1; + + constexpr qreal accuracy = (0.0001/*mm*/ / 25.4) * PrintDPI; + + for (auto p : v2) + { + if (not VFuzzyComparePoints(ConstLast(v), p, accuracy)) + { + v.append(p); + } + } + + return v; +} } // namespace //--------------------------------------------------------------------------------------------------------------------- @@ -326,6 +351,7 @@ auto VEllipticalArc::Rotate(QPointF originPoint, qreal degrees, const QString &p elArc.SetPenStyle(GetPenStyle()); elArc.SetFlipped(IsFlipped()); elArc.SetTransform(t); + elArc.SetApproximationScale(GetApproximationScale()); return elArc; } @@ -345,6 +371,7 @@ auto VEllipticalArc::Flip(const QLineF &axis, const QString &prefix) const -> VE elArc.SetPenStyle(GetPenStyle()); elArc.SetFlipped(not IsFlipped()); elArc.SetTransform(d->m_transform * VGObject::FlippingMatrix(d->m_transform.inverted().map(axis))); + elArc.SetApproximationScale(GetApproximationScale()); return elArc; } @@ -373,6 +400,7 @@ auto VEllipticalArc::Move(qreal length, qreal angle, const QString &prefix) cons elArc.SetPenStyle(GetPenStyle()); elArc.SetFlipped(IsFlipped()); elArc.SetTransform(t); + elArc.SetApproximationScale(GetApproximationScale()); return elArc; } @@ -448,12 +476,32 @@ auto VEllipticalArc::GetPoints() const -> QVector { const QPointF center = VAbstractArc::GetCenter().toQPointF(); + // Don't work with 0 radius. Always make it bigger than 0. + constexpr qreal threshold = ToPixel(0.001, Unit::Mm); + qreal radius1 = qMax(d->radius1, threshold); + qreal radius2 = qMax(d->radius2, threshold); + qreal max = qMax(d->radius1, d->radius2); + qreal scale = 1; + + if (max > maxRadius) + { + scale = max / maxRadius; + radius1 /= scale; + radius2 /= scale; + } + // Generate complete ellipse because angles are not correct and have to be fixed manually - QVector points = EllipticArcPoints(center, d->radius1, d->radius2, 0.0, M_2PI, GetApproximationScale()); + QVector points = EllipticArcPoints(center, radius1, radius2, 0.0, M_2PI, GetApproximationScale()); points = ArcPoints(points); QTransform t = d->m_transform; t.translate(center.x(), center.y()); + if (not VFuzzyComparePossibleNulls(scale, 1)) + { + // Because fixed 16.16 type has limitations it is very easy to get overflow error. + // To avoid this we calculate an arc for scaled radiuses and then scale up to original size. + t.scale(scale, scale); + } t.rotate(-GetRotationAngle()); t.translate(-center.x(), -center.y()); @@ -693,12 +741,12 @@ auto VEllipticalArc::GetP(qreal angle) const -> QPointF //--------------------------------------------------------------------------------------------------------------------- auto VEllipticalArc::ArcPoints(QVector points) const -> QVector { - if (points.size() < 2 || VFuzzyComparePossibleNulls(VAbstractArc::GetStartAngle(), VAbstractArc::GetEndAngle())) + if (points.size() < 2 || (qFuzzyIsNull(d->radius1) && qFuzzyIsNull(d->radius2))) { return points; } - QPointF center = static_cast(GetCenter()); + QPointF center = VAbstractArc::GetCenter().toQPointF(); qreal radius = qMax(d->radius1, d->radius2) * 2; QLineF start(center.x(), center.y(), center.x() + radius, center.y()); @@ -707,22 +755,18 @@ auto VEllipticalArc::ArcPoints(QVector points) const -> QVector end.angle()) + if (start.angle() >= end.angle()) { for (int i=0; i < points.size()-1; ++i) { @@ -736,8 +780,16 @@ auto VEllipticalArc::ArcPoints(QVector points) const -> QVector head = points.mid(0, i+1); QVector tail = points.mid(i+1, -1); - tail.prepend(p); - points = tail + head; + + tail = JoinVectors({p}, tail); + points = JoinVectors(tail, head); + points = JoinVectors(points, {p}); + + if (VFuzzyComparePossibleNulls(start.angle(), end.angle())) + { + return points; + } + begin = false; break; } diff --git a/src/test/ValentinaTest/tst_vellipticalarc.cpp b/src/test/ValentinaTest/tst_vellipticalarc.cpp index ab6bf8168..80f068f60 100644 --- a/src/test/ValentinaTest/tst_vellipticalarc.cpp +++ b/src/test/ValentinaTest/tst_vellipticalarc.cpp @@ -282,6 +282,7 @@ void TST_VEllipticalArc::TestGetPoints1() const VPointF center; VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle); + arc.SetApproximationScale(maxCurveApproximationScale); QVector points = arc.GetPoints(); if (qFuzzyIsNull(rotationAngle)) @@ -313,6 +314,7 @@ void TST_VEllipticalArc::TestGetPoints2() const VPointF center; VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle); + arc.SetApproximationScale(maxCurveApproximationScale); QVector points = arc.GetPoints(); const qreal c = qSqrt(qAbs(radius2*radius2 - radius1*radius1)); @@ -385,6 +387,7 @@ void TST_VEllipticalArc::TestGetPoints3() const VPointF center; VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle); + arc.SetApproximationScale(maxCurveApproximationScale); QVector points = arc.GetPoints(); if (VFuzzyComparePossibleNulls(arc.AngleArc(), 360.0)) @@ -413,12 +416,13 @@ void TST_VEllipticalArc::TestGetPoints4() const VPointF center; VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle); + arc.SetApproximationScale(maxCurveApproximationScale); if (VFuzzyComparePossibleNulls(arc.AngleArc(), 360.0)) {// calculated full ellipse length const qreal h = ((radius1-radius2)*(radius1-radius2))/((radius1+radius2)*(radius1+radius2)); const qreal ellipseLength = M_PI*(radius1+radius2)*(1+3*h/(10+qSqrt(4-3*h))); - const qreal epsLength = ellipseLength*0.5/100; // computing error + const qreal epsLength = ToPixel(1, Unit::Mm); // computing error const qreal arcLength = VEllipticalArc(center, radius1, radius2, 0, 360, 0).GetLength(); const qreal diffLength = qAbs(arcLength - ellipseLength); // cppcheck-suppress unreadVariable @@ -446,6 +450,7 @@ void TST_VEllipticalArc::TestGetPoints5() const VPointF center; VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle); + arc.SetApproximationScale(maxCurveApproximationScale); const qreal stAngle = VEllipticalArc::OptimizeAngle(arc.GetStartAngle()+arc.GetRotationAngle()); const qreal enAngle = VEllipticalArc::OptimizeAngle(arc.GetEndAngle()+arc.GetRotationAngle()); @@ -471,7 +476,7 @@ void TST_VEllipticalArc::TestGetPoints5() if (points.size() > 2 && qFuzzyIsNull(rotationAngle)) { - const qreal testAccuracy = (1.5/*mm*/ / 25.4) * PrintDPI; + const qreal testAccuracy = ToPixel(1.5, Unit::Mm); Comparison(arc.GetP1(), ConstFirst(points), testAccuracy); Comparison(arc.GetP2(), ConstLast(points), testAccuracy); @@ -607,8 +612,9 @@ void TST_VEllipticalArc::EmptyArc() QFETCH(qreal, length); VEllipticalArc empty; + empty.SetApproximationScale(maxCurveApproximationScale); empty.SetRadius1(radius1); empty.SetRadius2(radius2); - QCOMPARE(empty.GetLength(), length); + QVERIFY(qAbs(empty.GetLength() - length) <= ToPixel(1, Unit::Mm)); }