valentina/src/libs/vgeometry/vspline.cpp
2024-02-20 09:17:20 +02:00

624 lines
22 KiB
C++

/************************************************************************
**
** @file vspline.cpp
** @author Roman Telezhynskyi <dismine(at)gmail.com>
** @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
** <https://gitlab.com/smart-pattern/valentina> 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 <http://www.gnu.org/licenses/>.
**
*************************************************************************/
#include "vspline.h"
#include <QJsonObject>
#include <QLineF>
#include "../vmisc/vmath.h"
#include "vabstractcurve.h"
#include "vspline_p.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
#include "../vmisc/compatibility.h"
#endif
using namespace Qt::Literals::StringLiterals;
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief VSpline default constructor
*/
VSpline::VSpline()
: VAbstractCubicBezier(GOType::Spline),
d(new VSplineData)
{
}
//---------------------------------------------------------------------------------------------------------------------
COPY_CONSTRUCTOR_IMPL_2(VSpline, VAbstractCubicBezier)
//---------------------------------------------------------------------------------------------------------------------
/**
* @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();
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::Rotate(const QPointF &originPoint, qreal degrees, const QString &prefix) const -> VSpline
{
const VPointF p1 = GetP1().Rotate(originPoint, degrees);
const VPointF p4 = GetP4().Rotate(originPoint, degrees);
const QPointF p2 = VPointF::RotatePF(originPoint, static_cast<QPointF>(GetP2()), degrees);
const QPointF p3 = VPointF::RotatePF(originPoint, static_cast<QPointF>(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;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::Flip(const QLineF &axis, const QString &prefix) const -> VSpline
{
const VPointF p1 = GetP1().Flip(axis);
const VPointF p4 = GetP4().Flip(axis);
const QPointF p2 = VPointF::FlipPF(axis, static_cast<QPointF>(GetP2()));
const QPointF p3 = VPointF::FlipPF(axis, static_cast<QPointF>(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;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::Move(qreal length, qreal angle, const QString &prefix) const -> VSpline
{
const VPointF p1 = GetP1().Move(length, angle);
const VPointF p4 = GetP4().Move(length, angle);
const QPointF p2 = VPointF::MovePF(static_cast<QPointF>(GetP2()), length, angle);
const QPointF p3 = VPointF::MovePF(static_cast<QPointF>(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() = default;
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetLength return length of spline.
* @return length.
*/
auto VSpline::GetLength() const -> qreal
{
return LengthBezier(static_cast<QPointF>(GetP1()), static_cast<QPointF>(GetP2()), static_cast<QPointF>(GetP3()),
static_cast<QPointF>(GetP4()), GetApproximationScale());
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::CutSpline(qreal length, VSpline &spl1, VSpline &spl2, const QString &pointName) const -> QPointF
{
QPointF spl1p2;
QPointF spl1p3;
QPointF spl2p2;
QPointF spl2p3;
const QPointF cutPoint = CutSpline(length, spl1p2, spl1p3, spl2p2, spl2p3, pointName);
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.
*/
auto VSpline::GetPoints() const -> QVector<QPointF>
{
return GetCubicBezierPoints(static_cast<QPointF>(GetP1()), static_cast<QPointF>(GetP2()),
static_cast<QPointF>(GetP3()), static_cast<QPointF>(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
auto VSpline::SplinePoints(const QPointF &p1, const QPointF &p4, qreal angle1, qreal angle2, qreal kAsm1, qreal kAsm2,
qreal kCurve, qreal approximationScale) -> QVector<QPointF>
{
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 const p2 = p1p2.p2();
QPointF const p3 = p4p3.p2();
return GetCubicBezierPoints(p1, p2, p3, p4, approximationScale);
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::operator=(const VSpline &spline) -> VSpline &
{
if (&spline == this)
{
return *this;
}
VAbstractCubicBezier::operator=(spline);
d = spline.d;
return *this;
}
//---------------------------------------------------------------------------------------------------------------------
VSpline::VSpline(VSpline &&spline) noexcept
: VAbstractCubicBezier(std::move(spline)),
d(std::move(spline.d)) // NOLINT(bugprone-use-after-move)
{
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::operator=(VSpline &&spline) noexcept -> VSpline &
{
VAbstractCubicBezier::operator=(spline);
std::swap(d, spline.d);
return *this;
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetP1 return first spline point.
* @return first point.
*/
auto VSpline::GetP1() const -> VPointF
{
return d->p1;
}
//---------------------------------------------------------------------------------------------------------------------
void VSpline::SetP1(const VPointF &p)
{
d->p1 = p;
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetP2 return first control point.
* @return first control point.
*/
auto VSpline::GetP2() const -> VPointF
{
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.
*/
auto VSpline::GetP3() const -> VPointF
{
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 остання точка сплайну.
*/
auto VSpline::GetP4() const -> VPointF
{
return d->p4;
}
//---------------------------------------------------------------------------------------------------------------------
void VSpline::SetP4(const VPointF &p)
{
d->p4 = p;
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetAngle1 return first angle control line.
* @return angle.
*/
auto VSpline::GetStartAngle() const -> qreal
{
return d->angle1;
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetAngle2 return second angle control line.
* @return angle.
*/
auto VSpline::GetEndAngle() const -> qreal
{
return d->angle2;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetStartAngleFormula() const -> QString
{
return d->angle1F;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetEndAngleFormula() const -> QString
{
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;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetC1Length() const -> qreal
{
return d->c1Length;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetC2Length() const -> qreal
{
return d->c2Length;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetC1LengthFormula() const -> QString
{
return d->c1LengthF;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetC2LengthFormula() const -> QString
{
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.
*/
auto VSpline::GetKasm1() const -> qreal
{
return QLineF(static_cast<QPointF>(d->p1), static_cast<QPointF>(GetP2())).length() /
VSplineData::GetL(static_cast<QPointF>(d->p1), static_cast<QPointF>(d->p4), d->kCurve);
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetKasm2 return coefficient of length second control line.
* @return coefficient.
*/
auto VSpline::GetKasm2() const -> qreal
{
return QLineF(static_cast<QPointF>(d->p4), static_cast<QPointF>(GetP3())).length() /
VSplineData::GetL(static_cast<QPointF>(d->p1), static_cast<QPointF>(d->p4), d->kCurve);
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief GetKcurve return coefficient of curvature spline.
* @return coefficient
*/
auto VSpline::GetKcurve() const -> qreal
{
return d->kCurve;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::Sign(long double ld) -> int
{
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).
*/
auto VSpline::Cubic(QVector<qreal> &x, qreal a, qreal b, qreal c) -> qint32
{
// 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);
}
// 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);
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::CalcT(qreal curveCoord1, qreal curveCoord2, qreal curveCoord3, qreal curveCoord4, qreal pointCoord)
-> QVector<qreal>
{
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<qreal> t = QVector<qreal>(3, -1);
Cubic(t, b / a, c / a, d / a);
QVector<qreal> retT;
retT.reserve(t.size());
for (auto i : qAsConst(t))
{
if (i >= 0 && i <= 1)
{
retT.append(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.
*/
auto VSpline::ParamT(const QPointF &pBt) const -> qreal
{
QVector<qreal> 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<QPointF>(GetP1());
const QPointF p1 = static_cast<QPointF>(GetP2());
const QPointF p2 = static_cast<QPointF>(GetP3());
const QPointF p3 = static_cast<QPointF>(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;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::ToJson() const -> QJsonObject
{
QJsonObject object = VAbstractCubicBezier::ToJson();
object["aScale"_L1] = GetApproximationScale();
object["p1"_L1] = GetP1().ToJson();
object["p4"_L1] = GetP4().ToJson();
object["angle1"_L1] = GetStartAngle();
object["angle1Formula"_L1] = GetStartAngleFormula();
object["angle2"_L1] = GetEndAngle();
object["angle2Formula"_L1] = GetEndAngleFormula();
object["c1Length"_L1] = GetC1Length();
object["c1LengthFormula"_L1] = GetC1LengthFormula();
object["c2Length"_L1] = GetC2Length();
object["c2LengthFormula"_L1] = GetC2LengthFormula();
return object;
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetControlPoint1() const -> QPointF
{
return static_cast<QPointF>(GetP2());
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetControlPoint2() const -> QPointF
{
return static_cast<QPointF>(GetP3());
}
//---------------------------------------------------------------------------------------------------------------------
auto VSpline::GetRealLength() const -> qreal
{
return LengthBezier(static_cast<QPointF>(GetP1()), static_cast<QPointF>(GetP2()), static_cast<QPointF>(GetP3()),
static_cast<QPointF>(GetP4()), maxCurveApproximationScale);
}