Skip to content

Commit

Permalink
Merge pull request FashionFreedom#571 from FashionFreedom/issue#376-e…
Browse files Browse the repository at this point in the history
…lliptical-arcs-do-not-mirror-correctly

Fix: issue FashionFreedom#376 elliptical arcs do not mirror
  • Loading branch information
slspencer authored Jan 10, 2022
2 parents ab767b5 + 3e520c8 commit 5a5ea83
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 298 deletions.
2 changes: 1 addition & 1 deletion src/libs/vgeometry/vabstractarc.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class VAbstractArc : public VAbstractCurve
void SetFormulaF2 (const QString &formula, qreal value);
virtual qreal GetEndAngle () const Q_DECL_OVERRIDE;

VPointF GetCenter () const;
virtual VPointF GetCenter () const;
void SetCenter (const VPointF &point);

QString GetFormulaLength () const;
Expand Down
286 changes: 95 additions & 191 deletions src/libs/vgeometry/vellipticalarc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include <QLineF>
#include <QPoint>
#include <QPainterPath>

#include "../vmisc/def.h"
#include "../vmisc/vmath.h"
Expand Down Expand Up @@ -131,59 +132,57 @@ VEllipticalArc &VEllipticalArc::operator =(const VEllipticalArc &arc)
}

//---------------------------------------------------------------------------------------------------------------------
VEllipticalArc VEllipticalArc::Rotate(const QPointF &originPoint, qreal degrees, const QString &prefix) const
VEllipticalArc VEllipticalArc::Rotate(QPointF originPoint, qreal degrees, const QString &prefix) const
{
const VPointF center = GetCenter().Rotate(originPoint, degrees);
originPoint = d->m_transform.inverted().map(originPoint);

const QPointF p1 = VPointF::RotatePF(originPoint, GetP1(), degrees);
const QPointF p2 = VPointF::RotatePF(originPoint, GetP2(), degrees);
QTransform t = d->m_transform;
t.translate(originPoint.x(), originPoint.y());
t.rotate(-degrees);
t.translate(-originPoint.x(), -originPoint.y());

const qreal f1 = QLineF(static_cast<QPointF>(center), p1).angle() - GetRotationAngle();
const qreal f2 = QLineF(static_cast<QPointF>(center), p2).angle() - GetRotationAngle();

VEllipticalArc elArc(center, GetRadius1(), GetRadius2(), f1, f2, GetRotationAngle());
VEllipticalArc elArc(VAbstractArc::GetCenter(), GetRadius1(), GetRadius2(), VAbstractArc::GetStartAngle(),
VAbstractArc::GetEndAngle(), GetRotationAngle());
elArc.setName(name() + prefix);
elArc.SetColor(GetColor());
elArc.SetPenStyle(GetPenStyle());
elArc.SetFlipped(IsFlipped());
elArc.setTransform(t);
return elArc;
}

//---------------------------------------------------------------------------------------------------------------------
VEllipticalArc VEllipticalArc::Flip(const QLineF &axis, const QString &prefix) const
{
const VPointF center = GetCenter().Flip(axis);

const QPointF p1 = VPointF::FlipPF(axis, GetP1());
const QPointF p2 = VPointF::FlipPF(axis, GetP2());

const qreal f1 = QLineF(static_cast<QPointF>(center), p1).angle() - GetRotationAngle();
const qreal f2 = QLineF(static_cast<QPointF>(center), p2).angle() - GetRotationAngle();

VEllipticalArc elArc(center, GetRadius1(), GetRadius2(), f1, f2, GetRotationAngle());
VEllipticalArc elArc(VAbstractArc::GetCenter(), GetRadius1(), GetRadius2(), VAbstractArc::GetStartAngle(),
VAbstractArc::GetEndAngle(), GetRotationAngle());
elArc.setName(name() + prefix);
elArc.SetColor(GetColor());
elArc.SetPenStyle(GetPenStyle());
elArc.SetFlipped(not IsFlipped());
elArc.setTransform(d->m_transform * VGObject::flipTransform(d->m_transform.inverted().map(axis)));
return elArc;
}

//---------------------------------------------------------------------------------------------------------------------
VEllipticalArc VEllipticalArc::Move(qreal length, qreal angle, const QString &prefix) const
{
const VPointF center = GetCenter().Move(length, angle);
const VPointF oldCenter = VAbstractArc::GetCenter();
const VPointF center = oldCenter.Move(length, angle);

const QPointF p1 = VPointF::MovePF(GetP1(), length, angle);
const QPointF p2 = VPointF::MovePF(GetP2(), length, angle);
const QPointF position = d->m_transform.inverted().map(center.toQPointF()) -
d->m_transform.inverted().map(oldCenter.toQPointF());

const qreal f1 = QLineF(static_cast<QPointF>(center), p1).angle() - GetRotationAngle();
const qreal f2 = QLineF(static_cast<QPointF>(center), p2).angle() - GetRotationAngle();
QTransform t = d->m_transform;
t.translate(position.x(), position.y());

VEllipticalArc elArc(center, GetRadius1(), GetRadius2(), f1, f2, GetRotationAngle());
VEllipticalArc elArc(oldCenter, GetRadius1(), GetRadius2(), VAbstractArc::GetStartAngle(),
VAbstractArc::GetEndAngle(), GetRotationAngle());
elArc.setName(name() + prefix);
elArc.SetColor(GetColor());
elArc.SetPenStyle(GetPenStyle());
elArc.SetFlipped(IsFlipped());
elArc.setTransform(t);
return elArc;
}

Expand Down Expand Up @@ -215,7 +214,7 @@ qreal VEllipticalArc::GetLength() const
*/
QPointF VEllipticalArc::GetP1() const
{
return GetPoint(GetStartAngle());
return getTransform().map(getPoint(VAbstractArc::GetStartAngle()));
}

//---------------------------------------------------------------------------------------------------------------------
Expand All @@ -225,169 +224,53 @@ QPointF VEllipticalArc::GetP1() const
*/
QPointF VEllipticalArc::GetP2 () const
{
return GetPoint(GetEndAngle());
return getTransform().map(getPoint(VAbstractArc::GetEndAngle()));
}

//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetPoint return point associated with angle.
* @return point.
*/
QPointF VEllipticalArc::GetPoint (qreal angle) const
{
// Original idea http://alex-black.ru/article.php?content=109#head_3
if (angle > 360 || angle < 0)
{// Filter incorect value of angle
QLineF dummy(0, 0, 100, 0);
dummy.setAngle(angle);
angle = dummy.angle();
}

// p - point without rotation
qreal x = 0;
qreal y = 0;

qreal angleRad = qDegreesToRadians(angle);
const int n = GetQuadransRad(angleRad);
if (VFuzzyComparePossibleNulls(angleRad, 0) || VFuzzyComparePossibleNulls(angleRad, M_2PI) ||
VFuzzyComparePossibleNulls(angleRad, -M_2PI))
{ // 0 (360, -360) degress
x = d->radius1;
y = 0;
}
else if (VFuzzyComparePossibleNulls(angleRad, M_PI_2) || VFuzzyComparePossibleNulls(angleRad, -3 * M_PI_2))
{ // 90 (-270) degress
x = 0;
y = d->radius2;
}
else if (VFuzzyComparePossibleNulls(angleRad, M_PI) || VFuzzyComparePossibleNulls(angleRad, -M_PI))
{ // 180 (-180) degress
x = -d->radius1;
y = 0;
}
else if (VFuzzyComparePossibleNulls(angleRad, 3 * M_PI_2) || VFuzzyComparePossibleNulls(angleRad, -M_PI_2))
{ // 270 (-90) degress
x = 0;
y = -d->radius2;
}
else
{ // cases between
const qreal r1Pow = qPow(d->radius1, 2);
const qreal r2Pow = qPow(d->radius2, 2);
const qreal angleTan = qTan(angleRad);
const qreal angleTan2 = qPow(angleTan, 2);
x = qSqrt((r1Pow * r2Pow) / (r1Pow * angleTan2 + r2Pow));
y = angleTan * x;
}

switch (n)
{
case 1:
x = +x;
y = +y;
break;
case 2:
x = -x;
y = +y;
break;
case 3:
x = -x;
y = -y;
break;
case 4:
x = +x;
y = -y;
break;
default:
break;
}

QPointF p (GetCenter().x() + x, GetCenter().y() + y);
// rotation of point
QLineF line(static_cast<QPointF>(GetCenter()), p);
line.setAngle(line.angle() + GetRotationAngle());

return line.p2();
QTransform VEllipticalArc::getTransform() const
{
return d->m_transform;
}

//---------------------------------------------------------------------------------------------------------------------
int VEllipticalArc::GetQuadransRad(qreal &rad)
void VEllipticalArc::setTransform(const QTransform &matrix, bool combine)
{
if (rad > M_PI)
{
rad = rad - M_2PI;
}

if (rad < -M_PI)
{
rad = rad + M_2PI;
}
d->m_transform = combine ? d->m_transform * matrix : matrix;
}

int n = 0;
if (rad >= 0)
{
if (rad <= M_PI_2)
{
n = 1;
rad = -rad;
}
else if (rad > M_PI_2 && rad <= M_PI)
{
n = 2;
rad = M_PI+rad;
}
}
else
{
if (rad >= -M_PI_2)
{
n = 4;
}
else if (rad < -M_PI_2 && rad >= -M_PI)
{
n = 3;
rad = M_PI-rad;
}
}
return n;
//---------------------------------------------------------------------------------------------------------------------
VPointF VEllipticalArc::GetCenter() const
{
VPointF center = VAbstractArc::GetCenter();
const QPointF p = d->m_transform.map(center.toQPointF());
center.setX(p.x());
center.setY(p.y());
return center;
}


//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetAngles return list of angles needed for drawing arc.
* @return list of angles
* @brief GetPoint return point associated with angle.
* @return point.
*/
QVector<qreal> VEllipticalArc::GetAngles() const
//---------------------------------------------------------------------------------------------------------------------
QPointF VEllipticalArc::getPoint(qreal angle) const
{
QVector<qreal> sectionAngle;
qreal angle = AngleArc();
QLineF line(0, 0, 100, 0);
line.setAngle(angle);

if (qFuzzyIsNull(angle))
{// Return the array that includes one angle
sectionAngle.append(GetStartAngle());
return sectionAngle;
}
const qreal a = line.p2().x() / GetRadius1();
const qreal b = line.p2().y() / GetRadius2();
const qreal k = qSqrt(a*a + b*b);
QPointF p(line.p2().x() / k, line.p2().y() / k);

if (angle > 360 || angle < 0)
{// Filter incorect value of angle
QLineF dummy(0,0, 100, 0);
dummy.setAngle(angle);
angle = dummy.angle();
}
QLineF line2(QPointF(), p);
SCASSERT(VFuzzyComparePossibleNulls(line2.angle(), line.angle()))

const qreal angleInterpolation = 45; //degree
const int sections = qFloor(angle / angleInterpolation);
for (int i = 0; i < sections; ++i)
{
sectionAngle.append(angleInterpolation);
}

const qreal tail = angle - sections * angleInterpolation;
if (tail > 0)
{
sectionAngle.append(tail);
}
return sectionAngle;
line2.setAngle(line2.angle() + GetRotationAngle());
return line2.p2() + VAbstractArc::GetCenter().toQPointF();
}

//---------------------------------------------------------------------------------------------------------------------
Expand All @@ -397,34 +280,55 @@ QVector<qreal> VEllipticalArc::GetAngles() const
*/
QVector<QPointF> VEllipticalArc::GetPoints() const
{
QVector<QPointF> points;
QVector<qreal> sectionAngle = GetAngles();
const QPointF center = VAbstractArc::GetCenter().toQPointF();
QRectF box(center.x() - d->radius1, center.y() - d->radius2, d->radius1*2, d->radius2*2);

QLineF startLine(center.x(), center.y(), center.x() + d->radius1, center.y());
QLineF endLine = startLine;

qreal currentAngle;
IsFlipped() ? currentAngle = GetEndAngle() : currentAngle = GetStartAngle();
for (int i = 0; i < sectionAngle.size(); ++i)
startLine.setAngle(VAbstractArc::GetStartAngle());
endLine.setAngle(VAbstractArc::GetEndAngle());
qreal sweepAngle = startLine.angleTo(endLine);

if (qFuzzyIsNull(sweepAngle))
{
QPointF startPoint = GetPoint(currentAngle);
QPointF ellipsePoint2 = GetPoint(currentAngle + sectionAngle.at(i)/3);
QPointF ellipsePoint3 = GetPoint(currentAngle + 2*sectionAngle.at(i)/3);
QPointF lastPoint = GetPoint(currentAngle + sectionAngle.at(i));
// four points that are on ellipse
sweepAngle = 360;
}

QPointF bezierPoint1 = ( -5*startPoint + 18*ellipsePoint2 -9*ellipsePoint3 + 2*lastPoint )/6;
QPointF bezierPoint2 = ( 2*startPoint - 9*ellipsePoint2 + 18*ellipsePoint3 - 5*lastPoint )/6;
QPainterPath path;
path.arcTo(box, VAbstractArc::GetStartAngle(), sweepAngle);

VSpline spl(VPointF(startPoint), bezierPoint1, bezierPoint2, VPointF(lastPoint), 1.0);
QTransform t = d->m_transform;
t.translate(center.x(), center.y());
t.rotate(-GetRotationAngle());
t.translate(-center.x(), -center.y());

QVector<QPointF> splPoints = spl.GetPoints();
path = t.map(path);

if (not splPoints.isEmpty() && i != sectionAngle.size() - 1)
QPolygonF polygon;
const QList<QPolygonF> subpath = path.toSubpathPolygons();
if (not subpath.isEmpty())
{
polygon = path.toSubpathPolygons().first();
if (not polygon.isEmpty())
{
splPoints.removeLast();
polygon.removeFirst(); // remove point (0;0)
}
points << splPoints;
currentAngle += sectionAngle.at(i);
}
return points;

return std::move(polygon);
}

//---------------------------------------------------------------------------------------------------------------------
qreal VEllipticalArc::GetStartAngle() const
{
return QLineF(GetCenter().toQPointF(), GetP1()).angle() - GetRotationAngle();
}

//---------------------------------------------------------------------------------------------------------------------
qreal VEllipticalArc::GetEndAngle() const
{
return QLineF(GetCenter().toQPointF(), GetP2()).angle() - GetRotationAngle();
}

//---------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -514,7 +418,7 @@ void VEllipticalArc::FindF2(qreal length)
}
while (length > MaxLength())
{
length = length - MaxLength();
length = MaxLength();
}

// We need to calculate the second angle
Expand All @@ -527,7 +431,7 @@ void VEllipticalArc::FindF2(qreal length)

qreal lenBez = GetLength(); // first approximation of length

const qreal eps = ToPixel(0.1, Unit::Mm);
const qreal eps = ToPixel(0.001, Unit::Mm);

while (qAbs(lenBez - length) > eps)
{
Expand Down
Loading

0 comments on commit 5a5ea83

Please sign in to comment.