Fixed issue #793. Operations work incorrect with elliptical arc.

--HG--
branch : develop
This commit is contained in:
Roman Telezhynskyi 2018-01-05 19:41:09 +02:00
parent 7a77ad0e19
commit b25f971758
6 changed files with 185 additions and 71 deletions

View file

@ -72,7 +72,7 @@ public:
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;

View file

@ -124,59 +124,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::FlippingMatrix(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;
}
@ -208,7 +206,7 @@ qreal VEllipticalArc::GetLength() const
*/
QPointF VEllipticalArc::GetP1() const
{
return GetPoints().first();
return GetTransform().map(GetP(VAbstractArc::GetStartAngle()));
}
//---------------------------------------------------------------------------------------------------------------------
@ -218,7 +216,29 @@ QPointF VEllipticalArc::GetP1() const
*/
QPointF VEllipticalArc::GetP2 () const
{
return GetPoints().last();
return GetTransform().map(GetP(VAbstractArc::GetEndAngle()));
}
//---------------------------------------------------------------------------------------------------------------------
QTransform VEllipticalArc::GetTransform() const
{
return d->m_transform;
}
//---------------------------------------------------------------------------------------------------------------------
void VEllipticalArc::SetTransform(const QTransform &matrix, bool combine)
{
d->m_transform = combine ? d->m_transform * matrix : matrix;
}
//---------------------------------------------------------------------------------------------------------------------
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;
}
//---------------------------------------------------------------------------------------------------------------------
@ -228,27 +248,14 @@ QPointF VEllipticalArc::GetP2 () const
*/
QVector<QPointF> VEllipticalArc::GetPoints() const
{
QRectF box(GetCenter().x() - d->radius1, GetCenter().y() - d->radius2, d->radius1*2,
d->radius2*2);
const QPointF center = VAbstractArc::GetCenter().toQPointF();
QRectF box(center.x() - d->radius1, center.y() - d->radius2, d->radius1*2, d->radius2*2);
qreal startAngle = 0;
qreal endAngle = 0;
if (not IsFlipped())
{
startAngle = GetStartAngle();
endAngle = GetEndAngle();
}
else
{
startAngle = GetEndAngle();
endAngle = GetStartAngle();
}
QLineF startLine(GetCenter().x(), GetCenter().y(), GetCenter().x() + d->radius1, GetCenter().y());
QLineF startLine(center.x(), center.y(), center.x() + d->radius1, center.y());
QLineF endLine = startLine;
startLine.setAngle(startAngle);
endLine.setAngle(endAngle);
startLine.setAngle(VAbstractArc::GetStartAngle());
endLine.setAngle(VAbstractArc::GetEndAngle());
qreal sweepAngle = startLine.angleTo(endLine);
if (qFuzzyIsNull(sweepAngle))
@ -257,12 +264,12 @@ QVector<QPointF> VEllipticalArc::GetPoints() const
}
QPainterPath path;
path.arcTo(box, startAngle, sweepAngle);
path.arcTo(box, VAbstractArc::GetStartAngle(), sweepAngle);
QTransform t;
t.translate(GetCenter().x(), GetCenter().y());
QTransform t = d->m_transform;
t.translate(center.x(), center.y());
t.rotate(-GetRotationAngle());
t.translate(-GetCenter().x(), -GetCenter().y());
t.translate(-center.x(), -center.y());
path = t.map(path);
@ -280,6 +287,18 @@ QVector<QPointF> VEllipticalArc::GetPoints() const
return polygon;
}
//---------------------------------------------------------------------------------------------------------------------
qreal VEllipticalArc::GetStartAngle() const
{
return QLineF(GetCenter().toQPointF(), GetP1()).angle() - GetRotationAngle();
}
//---------------------------------------------------------------------------------------------------------------------
qreal VEllipticalArc::GetEndAngle() const
{
return QLineF(GetCenter().toQPointF(), GetP2()).angle() - GetRotationAngle();
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief CutArc cut arc into two arcs.
@ -367,7 +386,7 @@ void VEllipticalArc::FindF2(qreal length)
}
while (length > MaxLength())
{
length = length - MaxLength();
length = MaxLength();
}
// We need to calculate the second angle
@ -383,7 +402,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)
{
@ -416,6 +435,24 @@ qreal VEllipticalArc::MaxLength() const
return ellipseLength;
}
//---------------------------------------------------------------------------------------------------------------------
QPointF VEllipticalArc::GetP(qreal angle) const
{
QLineF line(0, 0, 100, 0);
line.setAngle(angle);
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);
QLineF line2(QPointF(), p);
SCASSERT(VFuzzyComparePossibleNulls(line2.angle(), line.angle()))
line2.setAngle(line2.angle() + GetRotationAngle());
return line2.p2() + VAbstractArc::GetCenter().toQPointF();
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetFormulaRadius1 return formula for major radius.

View file

@ -61,7 +61,7 @@ public:
VEllipticalArc (qreal length, const VPointF &center, qreal radius1, qreal radius2, qreal f1, qreal rotationAngle);
VEllipticalArc(const VEllipticalArc &arc);
VEllipticalArc Rotate(const QPointF &originPoint, qreal degrees, const QString &prefix = QString()) const;
VEllipticalArc Rotate(QPointF originPoint, qreal degrees, const QString &prefix = QString()) const;
VEllipticalArc Flip(const QLineF &axis, const QString &prefix = QString()) const;
VEllipticalArc Move(qreal length, qreal angle, const QString &prefix = QString()) const;
@ -92,10 +92,18 @@ public:
QPointF GetP1() const;
QPointF GetP2() const;
QTransform GetTransform() const;
void SetTransform(const QTransform &matrix, bool combine = false);
virtual VPointF GetCenter () const Q_DECL_OVERRIDE;
virtual QVector<QPointF> GetPoints () const Q_DECL_OVERRIDE;
virtual qreal GetStartAngle () const Q_DECL_OVERRIDE;
virtual qreal GetEndAngle () const Q_DECL_OVERRIDE;
QPointF CutArc (const qreal &length, VEllipticalArc &arc1, VEllipticalArc &arc2) const;
QPointF CutArc (const qreal &length) const;
static qreal OptimizeAngle(qreal angle);
protected:
virtual void CreateName() Q_DECL_OVERRIDE;
virtual void FindF2(qreal length) Q_DECL_OVERRIDE;
@ -103,9 +111,17 @@ private:
QSharedDataPointer<VEllipticalArcData> d;
qreal MaxLength() const;
QPointF GetP(qreal angle) const;
};
Q_DECLARE_METATYPE(VEllipticalArc)
Q_DECLARE_TYPEINFO(VEllipticalArc, Q_MOVABLE_TYPE);
//---------------------------------------------------------------------------------------------------------------------
inline qreal VEllipticalArc::OptimizeAngle(qreal angle)
{
return angle - 360.*qFloor(angle/360.);
}
#endif // VELLIPTICALARC_H

View file

@ -33,6 +33,7 @@ public:
qreal rotationAngle;
/** @brief formulaRotationAngle formula for rotationAngle. */
QString formulaRotationAngle;
QTransform m_transform;
private:
VEllipticalArcData &operator=(const VEllipticalArcData &) Q_DECL_EQ_DELETE;
@ -45,7 +46,8 @@ VEllipticalArcData::VEllipticalArcData()
formulaRadius1(),
formulaRadius2(),
rotationAngle(0),
formulaRotationAngle()
formulaRotationAngle(),
m_transform()
{}
//---------------------------------------------------------------------------------------------------------------------
@ -57,7 +59,8 @@ VEllipticalArcData::VEllipticalArcData(qreal radius1, qreal radius2, const QStri
formulaRadius1(formulaRadius1),
formulaRadius2(formulaRadius2),
rotationAngle(rotationAngle),
formulaRotationAngle(formulaRotationAngle)
formulaRotationAngle(formulaRotationAngle),
m_transform()
{}
//---------------------------------------------------------------------------------------------------------------------
@ -67,7 +70,8 @@ VEllipticalArcData::VEllipticalArcData(qreal radius1, qreal radius2, qreal rotat
formulaRadius1(QString().number(qApp->fromPixel(radius1))),
formulaRadius2(QString().number(qApp->fromPixel(radius2))),
rotationAngle(rotationAngle),
formulaRotationAngle(QString().number(qApp->fromPixel(rotationAngle)))
formulaRotationAngle(QString().number(qApp->fromPixel(rotationAngle))),
m_transform()
{}
//---------------------------------------------------------------------------------------------------------------------
@ -78,7 +82,8 @@ VEllipticalArcData::VEllipticalArcData(const VEllipticalArcData &arc)
formulaRadius1(arc.formulaRadius1),
formulaRadius2(arc.formulaRadius2),
rotationAngle(arc.rotationAngle),
formulaRotationAngle(arc.formulaRotationAngle)
formulaRotationAngle(arc.formulaRotationAngle),
m_transform(arc.m_transform)
{}
//---------------------------------------------------------------------------------------------------------------------

View file

@ -48,7 +48,7 @@ void TST_VEllipticalArc::CompareTwoWays_data()
QTest::addColumn<qreal>("f2");
QTest::addColumn<qreal>("rotationAngle");
//QTest::newRow("Test case 1") << QPointF() << 100. << 200. << 0. << 90.0 << 0.;
QTest::newRow("Test case 1") << QPointF() << 100. << 200. << 0. << 90.0 << 0.;
QTest::newRow("Test case 2") << QPointF() << 100. << 200. << 0. << 180.0 << 0.;
QTest::newRow("Test case 3") << QPointF() << 100. << 200. << 0. << 270.0 << 0.;
QTest::newRow("Test case 4") << QPointF() << 100. << 200. << 0. << 360.0 << 0.;
@ -86,18 +86,19 @@ void TST_VEllipticalArc::CompareTwoWays()
const qreal lengthEps = ToPixel(0.4, Unit::Mm); // computing error
const QString errorLengthMsg =
QString("Difference between real and computing lengthes bigger than eps = %1.").number(lengthEps);
QVERIFY2(qAbs(arc1.GetLength() - length) <= lengthEps, qUtf8Printable(errorLengthMsg));
QVERIFY2(qAbs(arc2.GetLength() - length) <= lengthEps, qUtf8Printable(errorLengthMsg));
QVERIFY2(qAbs(arc1.GetLength() - arc2.GetLength()) <= lengthEps, qUtf8Printable(errorLengthMsg));
QString("Difference between real and computing lengthes bigger than eps = %1. l1 = %2; l2 = %3");
QVERIFY2(qAbs(arc2.GetLength() - length) <= lengthEps,
qUtf8Printable(errorLengthMsg.arg(lengthEps).arg(arc2.GetLength()).arg(length)));
QVERIFY2(qAbs(arc1.GetLength() - arc2.GetLength()) <= lengthEps,
qUtf8Printable(errorLengthMsg.arg(lengthEps).arg(arc2.GetLength()).arg(arc2.GetLength())));
const qreal angleEps = 0.4;
const QString errorAngleMsg =
QString("Difference between real and computing angles bigger than eps = %1.").number(angleEps);
QString("Difference between real and computing angles bigger than eps = %1. f1 = %2; f2 = %3");
// compare angles
QVERIFY2(qAbs(arc1.GetEndAngle() - arc2.GetEndAngle()) <= angleEps, qUtf8Printable(errorAngleMsg));
QVERIFY2(qAbs(arc1.GetEndAngle() - f2) <= angleEps, qUtf8Printable(errorAngleMsg));
QVERIFY2(qAbs(arc1.GetEndAngle() - f2) <= angleEps, qUtf8Printable(errorAngleMsg));
const qreal diff = qAbs(arc1.GetEndAngle() - arc2.GetEndAngle());
QVERIFY2(qAbs(diff - 360.0*(diff/360.0)) <= angleEps,
qUtf8Printable(errorAngleMsg.arg(angleEps).arg(arc1.GetEndAngle()).arg(arc2.GetEndAngle())));
}
//---------------------------------------------------------------------------------------------------------------------
@ -158,12 +159,22 @@ void TST_VEllipticalArc::TestData()
QTest::addColumn<qreal>("endAngle");
QTest::addColumn<qreal>("rotationAngle");
QTest::newRow("Full circle: radiuses 10, 20") << 10.0 << 20.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200") << 150.0 << 200.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200, rotation 30") << 150.0 << 200.0 << 0.0 << 360.0 << 30.0;
QTest::newRow("Full circle: radiuses 1500, 1000") << 1500.0 << 1000.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 1500, 1000, rotation 50") << 1500.0 << 1000.0 << 0.0 << 360.0 << 50.0;
QTest::newRow("Full circle: radiuses 90000, 80000, rotation 90") << 90000.0 << 80000.0 << 0.0 << 360.0 << 90.0;
QTest::newRow("Full circle: radiuses 10, 20; start 0") << 10.0 << 20.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200; start 0") << 150.0 << 200.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200, rotation 30; start 0") << 150.0 << 200.0 << 0.0 << 360.0 << 30.0;
QTest::newRow("Full circle: radiuses 1500, 1000; start 0") << 1500.0 << 1000.0 << 0.0 << 360.0 << 0.0;
QTest::newRow("Full circle: radiuses 1500, 1000, rotation 50; start 0") << 1500.0 << 1000.0 << 0.0 << 360.0 << 50.0;
QTest::newRow("Full circle: radiuses 90000, 80000, rotation 90; start 0") << 90000.0 << 80000.0 << 0.0 << 360.0
<< 90.0;
QTest::newRow("Full circle: radiuses 10, 20; start 90") << 10.0 << 20.0 << 90.0 << 90.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200; start 90") << 150.0 << 200.0 << 90.0 << 90.0 << 0.0;
QTest::newRow("Full circle: radiuses 150, 200, rotation 30; start 90") << 150.0 << 200.0 << 90.0 << 90.0 << 30.0;
QTest::newRow("Full circle: radiuses 1500, 1000; start 90") << 1500.0 << 1000.0 << 90.0 << 90.0 << 0.0;
QTest::newRow("Full circle: radiuses 1500, 1000, rotation 50; start 90") << 1500.0 << 1000.0 << 90.0 << 90.0
<< 50.0;
QTest::newRow("Full circle: radiuses 90000, 80000, rotation 90; start 90") << 90000.0 << 80000.0 << 90.0 << 90.0
<< 90.0;
QTest::newRow("Arc less than 45 degree, radiuses 100, 50") << 100.0 << 50.0 << 0.0 << 10.5 << 0.0;
QTest::newRow("Arc less than 45 degree, radiuses 150, 50, rotation 180") << 150.0 << 50.0 << 0.0 << 10.5 << 180.0;
@ -408,6 +419,46 @@ void TST_VEllipticalArc::TestGetPoints4()
}
}
//---------------------------------------------------------------------------------------------------------------------
void TST_VEllipticalArc::TestGetPoints5_data()
{
TestData();
}
//---------------------------------------------------------------------------------------------------------------------
void TST_VEllipticalArc::TestGetPoints5()
{
// Test if first and last point still have same angle
QFETCH(qreal, radius1);
QFETCH(qreal, radius2);
QFETCH(qreal, startAngle);
QFETCH(qreal, endAngle);
QFETCH(qreal, rotationAngle);
const VPointF center;
VEllipticalArc arc(center, radius1, radius2, startAngle, endAngle, rotationAngle);
const qreal stAngle = VEllipticalArc::OptimizeAngle(arc.GetStartAngle()+arc.GetRotationAngle());
const qreal enAngle = VEllipticalArc::OptimizeAngle(arc.GetEndAngle()+arc.GetRotationAngle());
qreal f1 = QLineF(static_cast<QPointF>(center), arc.GetP1()).angle();
if ((qFuzzyIsNull(f1) && VFuzzyComparePossibleNulls(360, stAngle)) ||
(VFuzzyComparePossibleNulls(360, f1) && qFuzzyIsNull(stAngle)))
{
f1 = stAngle;
}
qreal f2 = QLineF(static_cast<QPointF>(center), arc.GetP2()).angle();
if ((qFuzzyIsNull(f2) && VFuzzyComparePossibleNulls(360, enAngle)) ||
(VFuzzyComparePossibleNulls(360, f2) && qFuzzyIsNull(enAngle)))
{
f2 = enAngle;
}
QCOMPARE(f1, stAngle);
QCOMPARE(f2, enAngle);
}
//---------------------------------------------------------------------------------------------------------------------
void TST_VEllipticalArc::TestRotation_data()
{
@ -423,6 +474,8 @@ void TST_VEllipticalArc::TestRotation_data()
QTest::newRow("Test el arc 1") << QPointF() << 10. << 20.0 << 1. << 91. << 0.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 2") << QPointF() << 10. << 20.0 << 0. << 90. << 0.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 3.2") << QPointF(10, 10) << 10. << 20.0 << 0. << 90. << 0.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 3.1") << QPointF(10, 10) << 10. << 20.0 << 1. << 91. << 0.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 3") << QPointF(10, 10) << 10. << 20.0 << 1. << 91. << 90.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 4") << QPointF(10, 10) << 10. << 20.0 << 0. << 90. << 90.<< QPointF() << 90. << "_r";
QTest::newRow("Test el arc 5") << QPointF(10, 10) << 10. << 20.0 << 0. << 180. << 90.<< QPointF() << 90. << "_r";
@ -448,7 +501,8 @@ void TST_VEllipticalArc::TestRotation()
const VEllipticalArc arcOrigin(VPointF(center), radius1, radius2, startAngle, endAngle, rotationAngle);
const VEllipticalArc rotatedArc = arcOrigin.Rotate(rotatePoint, degrees, prefix);
QVERIFY(qAbs(arcOrigin.AngleArc() - rotatedArc.AngleArc()) <= 1.6);
QVERIFY2(qAbs(arcOrigin.AngleArc() - rotatedArc.AngleArc()) <= 1.6,
qUtf8Printable(QString("a1 = %1, a2 - %2").arg(arcOrigin.AngleArc()).arg(rotatedArc.AngleArc())));
QVERIFY(qAbs(arcOrigin.GetLength() - rotatedArc.GetLength()) <= ToPixel(1, Unit::Mm));
QCOMPARE(arcOrigin.GetRadius1(), rotatedArc.GetRadius1());
QCOMPARE(arcOrigin.GetRadius2(), rotatedArc.GetRadius2());

View file

@ -49,6 +49,8 @@ private slots:
void TestGetPoints2();
void TestGetPoints3();
void TestGetPoints4();
void TestGetPoints5_data();
void TestGetPoints5();
void TestRotation_data();
void TestRotation();
void TestFlip_data();