From f44484a364ac789d2765c686393b4e360dc85b59 Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Fri, 16 Nov 2018 17:44:54 +0200 Subject: [PATCH] Optimization for method PointBezier_r(). --HG-- branch : develop --- src/libs/vgeometry/vabstractcubicbezier.cpp | 679 ++++++++++---------- src/libs/vgeometry/vabstractcubicbezier.h | 4 - src/libs/vlayout/vabstractpiece.cpp | 6 +- 3 files changed, 343 insertions(+), 346 deletions(-) diff --git a/src/libs/vgeometry/vabstractcubicbezier.cpp b/src/libs/vgeometry/vabstractcubicbezier.cpp index 223f851fe..3446eb452 100644 --- a/src/libs/vgeometry/vabstractcubicbezier.cpp +++ b/src/libs/vgeometry/vabstractcubicbezier.cpp @@ -28,16 +28,350 @@ #include "vabstractcubicbezier.h" +#include #include #include #include #include +#include #include "../vmisc/def.h" #include "../vmisc/vmath.h" #include "../vgeometry/vpointf.h" #include "../vmisc/vabstractapplication.h" +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +/** + * @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. + */ +inline qreal 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 points spline points coordinates. + * @param approximationScale curve approximation scale. + */ +QVector PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4, + qint16 level, QVector points, qreal approximationScale) +{ + if (points.size() >= 2) + { + for (int i=1; i < points.size(); ++i) + { + if (points.at(i-1) == points.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 = approximationScale; + if(m_approximation_scale < minCurveApproximationScale || m_approximation_scale > maxCurveApproximationScale) + { + m_approximation_scale = qApp->Settings()->GetCurveApproximationScale(); + } + + 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 points; + } + + // 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(d2 > curve_collinearity_epsilon) << 1) + + static_cast(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 points; + } + 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) + { + points.append(QPointF(x2, y2)); + return points; + } + } + else + { + if (d3 < m_distance_tolerance_square) + { + points.append(QPointF(x3, y3)); + return points; + } + } + 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) + { + points.append(QPointF(x23, y23)); + return points; + } + + // 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) + { + points.append(QPointF(x2, y2)); + points.append(QPointF(x3, y3)); + return points; + } + + if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0) + { + if (da1 > m_cusp_limit) + { + points.append(QPointF(x3, y3)); + return points; + } + } + } + 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) + { + points.append(QPointF(x23, y23)); + return points; + } + + // 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) + { + points.append(QPointF(x2, y2)); + + points.append(QPointF(x3, y3)); + return points; + } + + if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0) + { + if (da1 > m_cusp_limit) + { + points.append(QPointF(x2, y2)); + return points; + } + } + } + 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) + { + points.append(QPointF(x23, y23)); + return points; + } + + // 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 + //---------------------- + + points.append(QPointF(x23, y23)); + return points; + } + + if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0) + { + if (da1 > m_cusp_limit) + { + points.append(QPointF(x2, y2)); + return points; + } + + if (da2 > m_cusp_limit) + { + points.append(QPointF(x3, y3)); + return points; + } + } + } + break; + } + default: + break; + } + + // Continue subdivision + //---------------------- + auto BezierTailPoints = [x1234, y1234, x234, y234, x34, y34, x4, y4, level, approximationScale]() + { + QVector tail; + return PointBezier_r(x1234, y1234, x234, y234, x34, y34, x4, y4, static_cast(level + 1), tail, + approximationScale); + }; + + auto BezierPoints = [x1, y1, x12, y12, x123, y123, x1234, y1234, level, points, approximationScale]() + { + return PointBezier_r(x1, y1, x12, y12, x123, y123, x1234, y1234, static_cast(level + 1), points, + approximationScale); + }; + + if (level < 1) + { + QFuture> futureBezier = QtConcurrent::run(BezierPoints); + const QVector tail = BezierTailPoints(); + return futureBezier.result() + tail; + } + else + { + return BezierPoints() + BezierTailPoints(); + } +} +} + //--------------------------------------------------------------------------------------------------------------------- VAbstractCubicBezier::VAbstractCubicBezier(const GOType &type, const quint32 &idObject, const Draw &mode) : VAbstractBezier(type, idObject, mode) @@ -182,333 +516,6 @@ void VAbstractCubicBezier::CreateName() 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. - * @param approximationScale curve approximation scale. - */ -void VAbstractCubicBezier::PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4, - qint16 level, QVector &px, QVector &py, qreal approximationScale) -{ - 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 = approximationScale; - if(m_approximation_scale < minCurveApproximationScale || m_approximation_scale > maxCurveApproximationScale) - { - m_approximation_scale = qApp->Settings()->GetCurveApproximationScale(); - } - - 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(d2 > curve_collinearity_epsilon) << 1) + - static_cast(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(level + 1), px, py, - approximationScale); - PointBezier_r(x1234, y1234, x234, y234, x34, y34, x4, y4, static_cast(level + 1), px, py, - approximationScale); -} - //--------------------------------------------------------------------------------------------------------------------- /** * @brief GetCubicBezierPoints return list with cubic bezier curve points. @@ -523,20 +530,10 @@ QVector VAbstractCubicBezier::GetCubicBezierPoints(const QPointF &p1, c const QPointF &p4, qreal approximationScale) { QVector pvector; - QVector x; - QVector y; - QVector& wx = x; - QVector& 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, approximationScale ); - 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)) ); - } + pvector.append(p1); + pvector = PointBezier_r(p1.x(), p1.y(), p2.x(), p2.y(), p3.x(), p3.y(), p4.x(), p4.y(), 0, pvector, + approximationScale); + pvector.append(p4); return pvector; } diff --git a/src/libs/vgeometry/vabstractcubicbezier.h b/src/libs/vgeometry/vabstractcubicbezier.h index 723625248..c33ec0c13 100644 --- a/src/libs/vgeometry/vabstractcubicbezier.h +++ b/src/libs/vgeometry/vabstractcubicbezier.h @@ -64,10 +64,6 @@ public: protected: virtual void CreateName() override; - static qreal CalcSqDistance(qreal x1, qreal y1, qreal x2, qreal y2); - static void PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, - qreal y4, qint16 level, QVector &px, QVector &py, - qreal approximationScale); static QVector GetCubicBezierPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4, qreal approximationScale); static qreal LengthBezier(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4, diff --git a/src/libs/vlayout/vabstractpiece.cpp b/src/libs/vlayout/vabstractpiece.cpp index bd2477bbe..c4bf720ab 100644 --- a/src/libs/vlayout/vabstractpiece.cpp +++ b/src/libs/vlayout/vabstractpiece.cpp @@ -971,6 +971,10 @@ QVector VAbstractPiece::CheckLoops(const QVector &points) const bool pathClosed = (points.first() == points.last()); QVector ekvPoints; + ekvPoints.reserve(points.size()); + + QVector uniqueVertices; + uniqueVertices.reserve(4); qint32 i, j, jNext = 0; for (i = 0; i < count; ++i) @@ -1000,7 +1004,7 @@ QVector VAbstractPiece::CheckLoops(const QVector &points) continue; } - QVector uniqueVertices; + uniqueVertices.clear(); auto AddUniqueIndex = [&uniqueVertices](qint32 i) {