/************************************************************************ ** ** @file vspline.cpp ** @author Roman Telezhynskyi ** @date November 15, 2013 ** ** @brief ** @copyright ** This source code is part of the Valentina project, a pattern making ** program, whose allow create and modeling patterns of clothing. ** Copyright (C) 2013-2015 Valentina project ** All Rights Reserved. ** ** Valentina is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** Valentina is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Valentina. If not, see . ** *************************************************************************/ #include "vspline.h" #include #include #include "vabstractcurve.h" #include "vspline_p.h" #include "../vmisc/vmath.h" //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline default constructor */ VSpline::VSpline() :VAbstractCubicBezier(GOType::Spline), d(new VSplineData) {} //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline constructor. * @param spline spline from which the copy. */ VSpline::VSpline ( const VSpline & spline ) :VAbstractCubicBezier(spline), d(spline.d) {} //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline constructor. * @param p1 first point spline. * @param p4 last point spline. * @param angle1 angle from first point to first control point. * @param angle2 angle from second point to second control point. * @param kCurve coefficient of curvature spline. * @param kAsm1 coefficient of length first control line. * @param kAsm2 coefficient of length second control line. */ VSpline::VSpline (const VPointF &p1, const VPointF &p4, qreal angle1, qreal angle2, qreal kAsm1, qreal kAsm2, qreal kCurve, quint32 idObject, Draw mode) : VAbstractCubicBezier(GOType::Spline, idObject, mode), d(new VSplineData(p1, p4, angle1, angle2, kAsm1, kAsm2, kCurve)) { CreateName(); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline constructor. * @param p1 first point spline. * @param p2 first control point. * @param p3 second control point. * @param p4 second point spline. */ VSpline::VSpline (const VPointF &p1, const QPointF &p2, const QPointF &p3, const VPointF &p4, quint32 idObject, Draw mode) :VAbstractCubicBezier(GOType::Spline, idObject, mode), d(new VSplineData(p1, p2, p3, p4)) { CreateName(); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline constructor * @param p1 first point spline. * @param p4 first control point. * @param angle1 angle from first point to first control point. * @param angle1Formula formula angle from first point to first control point. * @param angle2 angle from second point to second control point. * @param angle2Formula formula angle from second point to second control point. * @param c1Length length from first point to first control point. * @param c1LengthFormula formula length from first point to first control point. * @param c2Length length from second point to first control point. * @param c2LengthFormula formula length from second point to first control point. */ VSpline::VSpline(const VPointF &p1, const VPointF &p4, qreal angle1, const QString &angle1Formula, qreal angle2, const QString &angle2Formula, qreal c1Length, const QString &c1LengthFormula, qreal c2Length, const QString &c2LengthFormula, quint32 idObject, Draw mode) : VAbstractCubicBezier(GOType::Spline, idObject, mode), d(new VSplineData(p1, p4, angle1, angle1Formula, angle2, angle2Formula, c1Length, c1LengthFormula, c2Length, c2LengthFormula)) { CreateName(); } //--------------------------------------------------------------------------------------------------------------------- VSpline VSpline::Rotate(const QPointF &originPoint, qreal degrees, const QString &prefix) const { const VPointF p1 = GetP1().Rotate(originPoint, degrees); const VPointF p4 = GetP4().Rotate(originPoint, degrees); const QPointF p2 = VPointF::RotatePF(originPoint, static_cast(GetP2()), degrees); const QPointF p3 = VPointF::RotatePF(originPoint, static_cast(GetP3()), degrees); VSpline spl(p1, p2, p3, p4); spl.setName(name() + prefix); if (not GetAliasSuffix().isEmpty()) { spl.SetAliasSuffix(GetAliasSuffix() + prefix); } spl.SetColor(GetColor()); spl.SetPenStyle(GetPenStyle()); spl.SetApproximationScale(GetApproximationScale()); return spl; } //--------------------------------------------------------------------------------------------------------------------- VSpline VSpline::Flip(const QLineF &axis, const QString &prefix) const { const VPointF p1 = GetP1().Flip(axis); const VPointF p4 = GetP4().Flip(axis); const QPointF p2 = VPointF::FlipPF(axis, static_cast(GetP2())); const QPointF p3 = VPointF::FlipPF(axis, static_cast(GetP3())); VSpline spl(p1, p2, p3, p4); spl.setName(name() + prefix); if (not GetAliasSuffix().isEmpty()) { spl.SetAliasSuffix(GetAliasSuffix() + prefix); } spl.SetColor(GetColor()); spl.SetPenStyle(GetPenStyle()); spl.SetApproximationScale(GetApproximationScale()); return spl; } //--------------------------------------------------------------------------------------------------------------------- VSpline VSpline::Move(qreal length, qreal angle, const QString &prefix) const { const VPointF p1 = GetP1().Move(length, angle); const VPointF p4 = GetP4().Move(length, angle); const QPointF p2 = VPointF::MovePF(static_cast(GetP2()), length, angle); const QPointF p3 = VPointF::MovePF(static_cast(GetP3()), length, angle); VSpline spl(p1, p2, p3, p4); spl.setName(name() + prefix); if (not GetAliasSuffix().isEmpty()) { spl.SetAliasSuffix(GetAliasSuffix() + prefix); } spl.SetColor(GetColor()); spl.SetPenStyle(GetPenStyle()); spl.SetApproximationScale(GetApproximationScale()); return spl; } //--------------------------------------------------------------------------------------------------------------------- VSpline::~VSpline() {} //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetLength return length of spline. * @return length. */ qreal VSpline::GetLength () const { return LengthBezier ( static_cast(GetP1()), static_cast(GetP2()), static_cast(GetP3()), static_cast(GetP4()), GetApproximationScale()); } //--------------------------------------------------------------------------------------------------------------------- QPointF VSpline::CutSpline(qreal length, VSpline &spl1, VSpline &spl2) const { QPointF spl1p2; QPointF spl1p3; QPointF spl2p2; QPointF spl2p3; const QPointF cutPoint = CutSpline (length, spl1p2, spl1p3, spl2p2, spl2p3 ); spl1 = VSpline(GetP1(), spl1p2, spl1p3, VPointF(cutPoint)); spl1.SetApproximationScale(GetApproximationScale()); spl2 = VSpline(VPointF(cutPoint), spl2p2, spl2p3, GetP4()); spl2.SetApproximationScale(GetApproximationScale()); return cutPoint; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetPoints return list with spline points. * @return list of points. */ QVector VSpline::GetPoints () const { return GetCubicBezierPoints(static_cast(GetP1()), static_cast(GetP2()), static_cast(GetP3()), static_cast(GetP4()), GetApproximationScale()); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief SplinePoints return list with spline points. * @param p1 first spline point. * @param p4 last spline point. * @param angle1 angle from first point to first control point. * @param angle2 angle from second point to second control point. * @param kAsm1 coefficient of length first control line. * @param kAsm2 coefficient of length second control line. * @param kCurve coefficient of curvature spline. * @return list with spline points. */ // cppcheck-suppress unusedFunction QVector VSpline::SplinePoints(const QPointF &p1, const QPointF &p4, qreal angle1, qreal angle2, qreal kAsm1, qreal kAsm2, qreal kCurve, qreal approximationScale) { QLineF p1pX(p1.x(), p1.y(), p1.x() + 100, p1.y()); p1pX.setAngle( angle1 ); qreal L = 0, radius = 0, angle = 90; radius = QLineF(QPointF(p1.x(), p4.y()), p4).length(); L = kCurve * radius * 4 / 3 * tan( angle * M_PI_4 / 180.0 ); QLineF p1p2(p1.x(), p1.y(), p1.x() + L * kAsm1, p1.y()); p1p2.setAngle(angle1); QLineF p4p3(p4.x(), p4.y(), p4.x() + L * kAsm2, p4.y()); p4p3.setAngle(angle2); QPointF p2 = p1p2.p2(); QPointF p3 = p4p3.p2(); return GetCubicBezierPoints(p1, p2, p3, p4, approximationScale); } //--------------------------------------------------------------------------------------------------------------------- VSpline &VSpline::operator =(const VSpline &spline) { if ( &spline == this ) { return *this; } VAbstractCubicBezier::operator=(spline); d = spline.d; return *this; } #ifdef Q_COMPILER_RVALUE_REFS //--------------------------------------------------------------------------------------------------------------------- VSpline::VSpline(const VSpline &&spline) Q_DECL_NOTHROW :VAbstractCubicBezier(spline), d(spline.d) {} //--------------------------------------------------------------------------------------------------------------------- VSpline &VSpline::operator=(VSpline &&spline) Q_DECL_NOTHROW { VAbstractCubicBezier::operator=(spline); std::swap(d, spline.d); return *this; } #endif //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetP1 return first spline point. * @return first point. */ VPointF VSpline::GetP1() const { return d->p1; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetP1(const VPointF &p) { d->p1 = p; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetP2 return first control point. * @return first control point. */ VPointF VSpline::GetP2() const { QLineF p1p2(d->p1.x(), d->p1.y(), d->p1.x() + d->c1Length, d->p1.y()); p1p2.setAngle(d->angle1); return VPointF(p1p2.p2()); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetP3 return second control point. * @return second control point. */ VPointF VSpline::GetP3() const { QLineF p4p3(d->p4.x(), d->p4.y(), d->p4.x() + d->c2Length, d->p4.y()); p4p3.setAngle(d->angle2); return VPointF(p4p3.p2()); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetP4 return last spline point. * @return остання точка сплайну. */ VPointF VSpline::GetP4() const { return d->p4; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetP4(const VPointF &p) { d->p4 = p; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetAngle1 return first angle control line. * @return angle. */ qreal VSpline::GetStartAngle() const { return d->angle1; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetAngle2 return second angle control line. * @return angle. */ qreal VSpline::GetEndAngle() const { return d->angle2; } //--------------------------------------------------------------------------------------------------------------------- QString VSpline::GetStartAngleFormula() const { return d->angle1F; } //--------------------------------------------------------------------------------------------------------------------- QString VSpline::GetEndAngleFormula() const { return d->angle2F; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetStartAngle(qreal angle, const QString &formula) { d->angle1 = angle; d->angle1F = formula; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetEndAngle(qreal angle, const QString &formula) { d->angle2 = angle; d->angle2F = formula; } //--------------------------------------------------------------------------------------------------------------------- qreal VSpline::GetC1Length() const { return d->c1Length; } //--------------------------------------------------------------------------------------------------------------------- qreal VSpline::GetC2Length() const { return d->c2Length; } //--------------------------------------------------------------------------------------------------------------------- QString VSpline::GetC1LengthFormula() const { return d->c1LengthF; } //--------------------------------------------------------------------------------------------------------------------- QString VSpline::GetC2LengthFormula() const { return d->c2LengthF; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetC1Length(qreal length, const QString &formula) { d->c1Length = length; d->c1LengthF = formula; } //--------------------------------------------------------------------------------------------------------------------- void VSpline::SetC2Length(qreal length, const QString &formula) { d->c2Length = length; d->c2LengthF = formula; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetKasm1 return coefficient of length first control line. * @return coefficient. */ qreal VSpline::GetKasm1() const { return QLineF(static_cast(d->p1), static_cast(GetP2())).length() / VSplineData::GetL(static_cast(d->p1), static_cast(d->p4), d->kCurve); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetKasm2 return coefficient of length second control line. * @return coefficient. */ qreal VSpline::GetKasm2() const { return QLineF(static_cast(d->p4), static_cast(GetP3())).length() / VSplineData::GetL(static_cast(d->p1), static_cast(d->p4), d->kCurve); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetKcurve return coefficient of curvature spline. * @return coefficient */ qreal VSpline::GetKcurve() const { return d->kCurve; } //--------------------------------------------------------------------------------------------------------------------- int VSpline::Sign(long double ld) { if (qAbs(ld)<0.00000000001) { return 0; } return (ld>0) ? 1 : -1; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief Cubic Cubic equation solution. Real coefficients case. * * This method use method Vieta-Cardano for eval cubic equations. * Cubic equation write in form x3+a*x2+b*x+c=0. * * Output: * 3 real roots -> then x is filled with them; * 1 real + 2 complex -> x[0] is real, x[1] is real part of complex roots, x[2] - non-negative imaginary part. * * @param x solution array (size 3). * @param a coefficient * @param b coefficient * @param c coefficient * @return 3 - 3 real roots; * 1 - 1 real root + 2 complex; * 2 - 1 real root + complex roots imaginary part is zero (i.e. 2 real roots). */ qint32 VSpline::Cubic(QVector &x, qreal a, qreal b, qreal c) { //To find cubic equation roots in the case of real coefficients, calculated at the beginning const qreal q = (pow(a, 2) - 3*b)/9.; const qreal r = (2*pow(a, 3) - 9*a*b + 27.*c)/54.; if (pow(r, 2) < pow(q, 3)) { // equation has three real roots, use formula Vieta const qreal t = acos(r/sqrt(pow(q, 3)))/3.; x.insert(0, -2.*sqrt(q)*cos(t)-a/3); x.insert(1, -2.*sqrt(q)*cos(t + (2*M_2PI/3.)) - a/3.); x.insert(2, -2.*sqrt(q)*cos(t - (2*M_2PI/3.)) - a/3.); return(3); } else { // 1 real root + 2 complex //Formula Cardano const qreal aa = -Sign(r)*pow(fabs(r)+sqrt(pow(r, 2)-pow(q, 3)), 1./3.); const qreal bb = Sign(aa) == 0 ? 0 : q/aa; x.insert(0, aa+bb-a/3.); // Real root x.insert(1, (-0.5)*(aa+bb)-a/3.); //Complex root x.insert(2, (sqrt(3.)*0.5)*fabs(aa-bb)); // Complex root if (qFuzzyIsNull(x.at(2))) { return(2); } return(1); } } //--------------------------------------------------------------------------------------------------------------------- QVector VSpline::CalcT (qreal curveCoord1, qreal curveCoord2, qreal curveCoord3, qreal curveCoord4, qreal pointCoord) const { const qreal a = -curveCoord1 + 3*curveCoord2 - 3*curveCoord3 + curveCoord4; const qreal b = 3*curveCoord1 - 6*curveCoord2 + 3*curveCoord3; const qreal c = -3*curveCoord1 + 3*curveCoord2; const qreal d = -pointCoord + curveCoord1; QVector t = QVector(3, -1); Cubic(t, b/a, c/a, d/a); QVector retT; for (int i=0; i < t.size(); ++i) { if ( t.at(i) >= 0 && t.at(i) <= 1 ) { retT.append(t.at(i)); } } return retT; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VSpline::ParamT calculate t coeffient that reprezent point on curve. * * Each point that belongs to Cubic Bézier curve can be shown by coefficient in interval [0; 1]. * * @param pBt point on curve * @return t coeffient that reprezent this point on curve. Return -1 if point doesn't belongs to curve. */ qreal VSpline::ParamT (const QPointF &pBt) const { QVector ts; // Calculate t coefficient for each axis ts += CalcT (GetP1().x(), GetP2().x(), GetP3().x(), GetP4().x(), pBt.x()); ts += CalcT (GetP1().y(), GetP2().y(), GetP3().y(), GetP4().y(), pBt.y()); if (ts.isEmpty()) { return -1; // We don't have candidates } qreal tx = -1; qreal eps = 3; // Error calculation // In morst case we will have 6 result in interval [0; 1]. // Here we try find closest to our point. for (auto t : qAsConst(ts)) { const QPointF p0 = static_cast(GetP1()); const QPointF p1 = static_cast(GetP2()); const QPointF p2 = static_cast(GetP3()); const QPointF p3 = static_cast(GetP4()); //The explicit form of the Cubic Bézier curve const qreal pointX = pow(1-t, 3)*p0.x() + 3*pow(1-t, 2)*t*p1.x() + 3*(1-t)*pow(t, 2)*p2.x() + pow(t, 3)*p3.x(); const qreal pointY = pow(1-t, 3)*p0.y() + 3*pow(1-t, 2)*t*p1.y() + 3*(1-t)*pow(t, 2)*p2.y() + pow(t, 3)*p3.y(); const QLineF line(pBt, QPointF(pointX, pointY)); if (line.length() <= eps) { tx = t; eps = line.length(); //Next point should be even closest } } return tx; } //--------------------------------------------------------------------------------------------------------------------- QJsonObject VSpline::ToJson() const { QJsonObject object = VAbstractCubicBezier::ToJson(); object[QLatin1String("aScale")] = GetApproximationScale(); object[QLatin1String("p1")] = GetP1().ToJson(); object[QLatin1String("p4")] = GetP4().ToJson(); object[QLatin1String("angle1")] = GetStartAngle(); object[QLatin1String("angle1Formula")] = GetStartAngleFormula(); object[QLatin1String("angle2")] = GetEndAngle(); object[QLatin1String("angle2Formula")] = GetEndAngleFormula(); object[QLatin1String("c1Length")] = GetC1Length(); object[QLatin1String("c1LengthFormula")] = GetC1LengthFormula(); object[QLatin1String("c2Length")] = GetC2Length(); object[QLatin1String("c2LengthFormula")] = GetC2LengthFormula(); return object; } //--------------------------------------------------------------------------------------------------------------------- QPointF VSpline::GetControlPoint1() const { return static_cast(GetP2 ()); } //--------------------------------------------------------------------------------------------------------------------- QPointF VSpline::GetControlPoint2() const { return static_cast(GetP3 ()); }