3bb657ea1a
--HG-- branch : develop
580 lines
18 KiB
C++
580 lines
18 KiB
C++
/************************************************************************
|
||
**
|
||
** @file vabstractcubicbezier.cpp
|
||
** @author Roman Telezhynskyi <dismine(at)gmail.com>
|
||
** @date 8 3, 2016
|
||
**
|
||
** @brief
|
||
** @copyright
|
||
** This source code is part of the Valentine project, a pattern making
|
||
** program, whose allow create and modeling patterns of clothing.
|
||
** Copyright (C) 2016 Valentina project
|
||
** <https://bitbucket.org/dismine/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 "vabstractcubicbezier.h"
|
||
|
||
#include <QLineF>
|
||
#include <QMessageLogger>
|
||
#include <QPoint>
|
||
#include <QtDebug>
|
||
|
||
#include "../vmisc/def.h"
|
||
#include "../vmisc/vmath.h"
|
||
#include "../vgeometry/vpointf.h"
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
VAbstractCubicBezier::VAbstractCubicBezier(const GOType &type, const quint32 &idObject, const Draw &mode)
|
||
: VAbstractBezier(type, idObject, mode)
|
||
{
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
VAbstractCubicBezier::VAbstractCubicBezier(const VAbstractCubicBezier &curve)
|
||
: VAbstractBezier(curve)
|
||
{
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
VAbstractCubicBezier &VAbstractCubicBezier::operator=(const VAbstractCubicBezier &curve)
|
||
{
|
||
if ( &curve == this )
|
||
{
|
||
return *this;
|
||
}
|
||
VAbstractBezier::operator=(curve);
|
||
return *this;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
VAbstractCubicBezier::~VAbstractCubicBezier()
|
||
{
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* @brief CutSpline cut spline.
|
||
* @param length length first spline
|
||
* @param spl1p2 second point of first spline
|
||
* @param spl1p3 third point of first spline
|
||
* @param spl2p2 second point of second spline
|
||
* @param spl2p3 third point of second spline
|
||
* @return point of cutting. This point is forth point of first spline and first point of second spline.
|
||
*/
|
||
QPointF VAbstractCubicBezier::CutSpline(qreal length, QPointF &spl1p2, QPointF &spl1p3, QPointF &spl2p2,
|
||
QPointF &spl2p3) const
|
||
{
|
||
//Always need return two splines, so we must correct wrong length.
|
||
const qreal minLength = ToPixel(1, Unit::Mm);
|
||
const qreal fullLength = GetLength();
|
||
|
||
if (fullLength <= minLength)
|
||
{
|
||
spl1p2 = spl1p3 = spl2p2 = spl2p3 = QPointF();
|
||
return QPointF();
|
||
}
|
||
|
||
const qreal maxLength = fullLength - minLength;
|
||
|
||
if (length < minLength)
|
||
{
|
||
length = minLength;
|
||
}
|
||
else if (length > maxLength)
|
||
{
|
||
length = maxLength;
|
||
}
|
||
|
||
const qreal parT = GetParmT(length);
|
||
|
||
QLineF seg1_2 ( GetP1 (), GetControlPoint1 () );
|
||
seg1_2.setLength(seg1_2.length () * parT);
|
||
const QPointF p12 = seg1_2.p2();
|
||
|
||
QLineF seg2_3 ( GetControlPoint1(), GetControlPoint2 () );
|
||
seg2_3.setLength(seg2_3.length () * parT);
|
||
const QPointF p23 = seg2_3.p2();
|
||
|
||
QLineF seg12_23 ( p12, p23 );
|
||
seg12_23.setLength(seg12_23.length () * parT);
|
||
const QPointF p123 = seg12_23.p2();
|
||
|
||
QLineF seg3_4 ( GetControlPoint2 (), GetP4 () );
|
||
seg3_4.setLength(seg3_4.length () * parT);
|
||
const QPointF p34 = seg3_4.p2();
|
||
|
||
QLineF seg23_34 ( p23, p34 );
|
||
seg23_34.setLength(seg23_34.length () * parT);
|
||
const QPointF p234 = seg23_34.p2();
|
||
|
||
QLineF seg123_234 ( p123, p234 );
|
||
seg123_234.setLength(seg123_234.length () * parT);
|
||
const QPointF p1234 = seg123_234.p2();
|
||
|
||
spl1p2 = p12;
|
||
spl1p3 = p123;
|
||
spl2p2 = p234;
|
||
spl2p3 = p34;
|
||
return p1234;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
QString VAbstractCubicBezier::NameForHistory(const QString &toolName) const
|
||
{
|
||
QString name = toolName + QString(" %1_%2").arg(GetP1().name()).arg(GetP4().name());
|
||
if (GetDuplicate() > 0)
|
||
{
|
||
name += QString("_%1").arg(GetDuplicate());
|
||
}
|
||
return name;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
qreal VAbstractCubicBezier::GetParmT(qreal length) const
|
||
{
|
||
if (length < 0)
|
||
{
|
||
return 0;
|
||
}
|
||
else if (length > GetLength())
|
||
{
|
||
length = GetLength();
|
||
}
|
||
|
||
const qreal eps = 0.001 * length;
|
||
qreal parT = 0.5;
|
||
qreal step = parT;
|
||
qreal splLength = LengthT(parT);
|
||
|
||
while (qAbs(splLength - length) > eps)
|
||
{
|
||
step /= 2.0;
|
||
splLength > length ? parT -= step : parT += step;
|
||
splLength = LengthT(parT);
|
||
}
|
||
return parT;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
void VAbstractCubicBezier::CreateName()
|
||
{
|
||
QString name = SPL_ + QString("%1_%2").arg(GetP1().name()).arg(GetP4().name());
|
||
if (GetDuplicate() > 0)
|
||
{
|
||
name += QString("_%1").arg(GetDuplicate());
|
||
}
|
||
|
||
setName(name);
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* @brief CalcSqDistance calculate squared distance.
|
||
* @param x1 х coordinate first point.
|
||
* @param y1 у coordinate first point.
|
||
* @param x2 х coordinate second point.
|
||
* @param y2 у coordinate second point.
|
||
* @return squared length.
|
||
*/
|
||
qreal VAbstractCubicBezier::CalcSqDistance(qreal x1, qreal y1, qreal x2, qreal y2)
|
||
{
|
||
const qreal dx = x2 - x1;
|
||
const qreal dy = y2 - y1;
|
||
return dx * dx + dy * dy;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* @brief PointBezier_r find spline point using four point of spline.
|
||
* @param x1 х coordinate first point.
|
||
* @param y1 у coordinate first point.
|
||
* @param x2 х coordinate first control point.
|
||
* @param y2 у coordinate first control point.
|
||
* @param x3 х coordinate second control point.
|
||
* @param y3 у coordinate second control point.
|
||
* @param x4 х coordinate last point.
|
||
* @param y4 у coordinate last point.
|
||
* @param level level of recursion. In the begin 0.
|
||
* @param px list х coordinat spline points.
|
||
* @param py list у coordinat spline points.
|
||
*/
|
||
void VAbstractCubicBezier::PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4,
|
||
qint16 level, QVector<qreal> &px, QVector<qreal> &py)
|
||
{
|
||
if (px.size() >= 2)
|
||
{
|
||
for (int i=1; i < px.size(); ++i)
|
||
{
|
||
if (QPointF(px.at(i-1), py.at(i-1)) == QPointF(px.at(i), py.at(i)))
|
||
{
|
||
qDebug("All neighbors points in path must be unique.");
|
||
}
|
||
}
|
||
}
|
||
|
||
const double curve_collinearity_epsilon = 1e-30;
|
||
const double curve_angle_tolerance_epsilon = 0.01;
|
||
const double m_angle_tolerance = 0.0;
|
||
enum curve_recursion_limit_e { curve_recursion_limit = 32 };
|
||
const double m_cusp_limit = 0.0;
|
||
double m_approximation_scale = 1.0;
|
||
double m_distance_tolerance_square;
|
||
|
||
m_distance_tolerance_square = 0.5 / m_approximation_scale;
|
||
m_distance_tolerance_square *= m_distance_tolerance_square;
|
||
|
||
if (level > curve_recursion_limit)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Calculate all the mid-points of the line segments
|
||
//----------------------
|
||
const double x12 = (x1 + x2) / 2;
|
||
const double y12 = (y1 + y2) / 2;
|
||
const double x23 = (x2 + x3) / 2;
|
||
const double y23 = (y2 + y3) / 2;
|
||
const double x34 = (x3 + x4) / 2;
|
||
const double y34 = (y3 + y4) / 2;
|
||
const double x123 = (x12 + x23) / 2;
|
||
const double y123 = (y12 + y23) / 2;
|
||
const double x234 = (x23 + x34) / 2;
|
||
const double y234 = (y23 + y34) / 2;
|
||
const double x1234 = (x123 + x234) / 2;
|
||
const double y1234 = (y123 + y234) / 2;
|
||
|
||
|
||
// Try to approximate the full cubic curve by a single straight line
|
||
//------------------
|
||
const double dx = x4-x1;
|
||
const double dy = y4-y1;
|
||
|
||
double d2 = fabs((x2 - x4) * dy - (y2 - y4) * dx);
|
||
double d3 = fabs((x3 - x4) * dy - (y3 - y4) * dx);
|
||
|
||
switch ((static_cast<int>(d2 > curve_collinearity_epsilon) << 1) +
|
||
static_cast<int>(d3 > curve_collinearity_epsilon))
|
||
{
|
||
case 0:
|
||
{
|
||
// All collinear OR p1==p4
|
||
//----------------------
|
||
double k = dx*dx + dy*dy;
|
||
if (k < 0.000000001)
|
||
{
|
||
d2 = CalcSqDistance(x1, y1, x2, y2);
|
||
d3 = CalcSqDistance(x4, y4, x3, y3);
|
||
}
|
||
else
|
||
{
|
||
k = 1 / k;
|
||
{
|
||
const double da1 = x2 - x1;
|
||
const double da2 = y2 - y1;
|
||
d2 = k * (da1*dx + da2*dy);
|
||
}
|
||
{
|
||
const double da1 = x3 - x1;
|
||
const double da2 = y3 - y1;
|
||
d3 = k * (da1*dx + da2*dy);
|
||
}
|
||
if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1)
|
||
{
|
||
// Simple collinear case, 1---2---3---4
|
||
// We can leave just two endpoints
|
||
return;
|
||
}
|
||
if (d2 <= 0)
|
||
{
|
||
d2 = CalcSqDistance(x2, y2, x1, y1);
|
||
}
|
||
else if (d2 >= 1)
|
||
{
|
||
d2 = CalcSqDistance(x2, y2, x4, y4);
|
||
}
|
||
else
|
||
{
|
||
d2 = CalcSqDistance(x2, y2, x1 + d2*dx, y1 + d2*dy);
|
||
}
|
||
|
||
if (d3 <= 0)
|
||
{
|
||
d3 = CalcSqDistance(x3, y3, x1, y1);
|
||
}
|
||
else if (d3 >= 1)
|
||
{
|
||
d3 = CalcSqDistance(x3, y3, x4, y4);
|
||
}
|
||
else
|
||
{
|
||
d3 = CalcSqDistance(x3, y3, x1 + d3*dx, y1 + d3*dy);
|
||
}
|
||
}
|
||
if (d2 > d3)
|
||
{
|
||
if (d2 < m_distance_tolerance_square)
|
||
{
|
||
px.append(x2);
|
||
py.append(y2);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (d3 < m_distance_tolerance_square)
|
||
{
|
||
px.append(x3);
|
||
py.append(y3);
|
||
return;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
// p1,p2,p4 are collinear, p3 is significant
|
||
//----------------------
|
||
if (d3 * d3 <= m_distance_tolerance_square * (dx*dx + dy*dy))
|
||
{
|
||
if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||
{
|
||
px.append(x23);
|
||
py.append(y23);
|
||
return;
|
||
}
|
||
|
||
// Angle Condition
|
||
//----------------------
|
||
double da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
|
||
if (da1 >= M_PI)
|
||
{
|
||
da1 = M_2PI - da1;
|
||
}
|
||
|
||
if (da1 < m_angle_tolerance)
|
||
{
|
||
px.append(x2);
|
||
py.append(y2);
|
||
|
||
px.append(x3);
|
||
py.append(y3);
|
||
return;
|
||
}
|
||
|
||
if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
|
||
{
|
||
if (da1 > m_cusp_limit)
|
||
{
|
||
px.append(x3);
|
||
py.append(y3);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case 2:
|
||
{
|
||
// p1,p3,p4 are collinear, p2 is significant
|
||
//----------------------
|
||
if (d2 * d2 <= m_distance_tolerance_square * (dx*dx + dy*dy))
|
||
{
|
||
if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||
{
|
||
px.append(x23);
|
||
py.append(y23);
|
||
return;
|
||
}
|
||
|
||
// Angle Condition
|
||
//----------------------
|
||
double da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
|
||
if (da1 >= M_PI)
|
||
{
|
||
da1 = M_2PI - da1;
|
||
}
|
||
|
||
if (da1 < m_angle_tolerance)
|
||
{
|
||
px.append(x2);
|
||
py.append(y2);
|
||
|
||
px.append(x3);
|
||
py.append(y3);
|
||
return;
|
||
}
|
||
|
||
if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
|
||
{
|
||
if (da1 > m_cusp_limit)
|
||
{
|
||
px.append(x2);
|
||
py.append(y2);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case 3:
|
||
{
|
||
// Regular case
|
||
//-----------------
|
||
if ((d2 + d3)*(d2 + d3) <= m_distance_tolerance_square * (dx*dx + dy*dy))
|
||
{
|
||
// If the curvature doesn't exceed the distance_tolerance value
|
||
// we tend to finish subdivisions.
|
||
//----------------------
|
||
if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||
{
|
||
px.append(x23);
|
||
py.append(y23);
|
||
return;
|
||
}
|
||
|
||
// Angle & Cusp Condition
|
||
//----------------------
|
||
const double k = atan2(y3 - y2, x3 - x2);
|
||
double da1 = fabs(k - atan2(y2 - y1, x2 - x1));
|
||
double da2 = fabs(atan2(y4 - y3, x4 - x3) - k);
|
||
if (da1 >= M_PI)
|
||
{
|
||
da1 = M_2PI - da1;
|
||
}
|
||
if (da2 >= M_PI)
|
||
{
|
||
da2 = M_2PI - da2;
|
||
}
|
||
|
||
if (da1 + da2 < m_angle_tolerance)
|
||
{
|
||
// Finally we can stop the recursion
|
||
//----------------------
|
||
|
||
px.append(x23);
|
||
py.append(y23);
|
||
return;
|
||
}
|
||
|
||
if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
|
||
{
|
||
if (da1 > m_cusp_limit)
|
||
{
|
||
px.append(x2);
|
||
py.append(y2);
|
||
return;
|
||
}
|
||
|
||
if (da2 > m_cusp_limit)
|
||
{
|
||
px.append(x3);
|
||
py.append(y3);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Continue subdivision
|
||
//----------------------
|
||
PointBezier_r(x1, y1, x12, y12, x123, y123, x1234, y1234, static_cast<qint16>(level + 1), px, py);
|
||
PointBezier_r(x1234, y1234, x234, y234, x34, y34, x4, y4, static_cast<qint16>(level + 1), px, py);
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* @brief GetCubicBezierPoints return list with cubic bezier curve points.
|
||
* @param p1 first spline point.
|
||
* @param p2 first control point.
|
||
* @param p3 second control point.
|
||
* @param p4 last spline point.
|
||
* @return list of points.
|
||
*/
|
||
QVector<QPointF> VAbstractCubicBezier::GetCubicBezierPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3,
|
||
const QPointF &p4)
|
||
{
|
||
QVector<QPointF> pvector;
|
||
QVector<qreal> x;
|
||
QVector<qreal> y;
|
||
QVector<qreal>& wx = x;
|
||
QVector<qreal>& wy = y;
|
||
x.append ( p1.x () );
|
||
y.append ( p1.y () );
|
||
PointBezier_r ( p1.x (), p1.y (), p2.x (), p2.y (),
|
||
p3.x (), p3.y (), p4.x (), p4.y (), 0, wx, wy );
|
||
x.append ( p4.x () );
|
||
y.append ( p4.y () );
|
||
for ( qint32 i = 0; i < x.count(); ++i )
|
||
{
|
||
pvector.append( QPointF ( x.at(i), y.at(i)) );
|
||
}
|
||
return pvector;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* @brief LengthBezier return spline length using 4 spline point.
|
||
* @param p1 first spline point
|
||
* @param p2 first control point.
|
||
* @param p3 second control point.
|
||
* @param p4 last spline point.
|
||
* @return length.
|
||
*/
|
||
qreal VAbstractCubicBezier::LengthBezier(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
|
||
{
|
||
return PathLength(GetCubicBezierPoints(p1, p2, p3, p4));
|
||
}
|
||
|
||
//---------------------------------------------------------------------------------------------------------------------
|
||
qreal VAbstractCubicBezier::LengthT(qreal t) const
|
||
{
|
||
if (t < 0 || t > 1)
|
||
{
|
||
qDebug()<<"Wrong value t.";
|
||
return 0;
|
||
}
|
||
QLineF seg1_2 ( GetP1 (), GetControlPoint1 () );
|
||
seg1_2.setLength(seg1_2.length () * t);
|
||
const QPointF p12 = seg1_2.p2();
|
||
|
||
QLineF seg2_3 ( GetControlPoint1 (), GetControlPoint2 () );
|
||
seg2_3.setLength(seg2_3.length () * t);
|
||
const QPointF p23 = seg2_3.p2();
|
||
|
||
QLineF seg12_23 ( p12, p23 );
|
||
seg12_23.setLength(seg12_23.length () * t);
|
||
const QPointF p123 = seg12_23.p2();
|
||
|
||
QLineF seg3_4 ( GetControlPoint2 (), GetP4 () );
|
||
seg3_4.setLength(seg3_4.length () * t);
|
||
const QPointF p34 = seg3_4.p2();
|
||
|
||
QLineF seg23_34 ( p23, p34 );
|
||
seg23_34.setLength(seg23_34.length () * t);
|
||
const QPointF p234 = seg23_34.p2();
|
||
|
||
QLineF seg123_234 ( p123, p234 );
|
||
seg123_234.setLength(seg123_234.length () * t);
|
||
const QPointF p1234 = seg123_234.p2();
|
||
|
||
return LengthBezier ( GetP1(), p12, p123, p1234);
|
||
}
|