File indexing completed on 2024-12-15 04:01:13

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "ellipse_solver.hpp"
0008 
0009 #include "bezier/bezier.hpp"
0010 #include "vector.hpp"
0011 #include "math.hpp"
0012 
0013 using namespace glaxnimate;
0014 
0015 
0016 math::EllipseSolver::EllipseSolver(const QPointF& center, const QPointF& radii, qreal xrot)
0017 : center(center),
0018     radii(radii),
0019     xrot(xrot)
0020 {}
0021 
0022 QPointF math::EllipseSolver::point(qreal t) const
0023 {
0024     return QPointF(
0025         center.x()
0026         + radii.x() * qCos(xrot) * qCos(t)
0027         - radii.y() * qSin(xrot) * qSin(t),
0028 
0029         center.y()
0030         + radii.x() * qSin(xrot) * qCos(t)
0031         + radii.y() * qCos(xrot) * qSin(t)
0032     );
0033 }
0034 
0035 QPointF math::EllipseSolver::derivative(qreal t) const
0036 {
0037     return QPointF(
0038         - radii.x() * qCos(xrot) * qSin(t)
0039         - radii.y() * qSin(xrot) * qCos(t),
0040 
0041         - radii.x() * qSin(xrot) * qSin(t)
0042         + radii.y() * qCos(xrot) * qCos(t)
0043     );
0044 }
0045 
0046 math::bezier::Bezier math::EllipseSolver::to_bezier(qreal anglestart, qreal angle_delta)
0047 {
0048     bezier::Bezier points;
0049     qreal angle1 = anglestart;
0050     qreal angle_left = qAbs(angle_delta);
0051     qreal step = math::pi / 2;
0052     qreal sign = anglestart+angle_delta < angle1 ? -1 : 1;
0053 
0054     // We need to fix the first handle
0055     qreal firststep = qMin(angle_left, step) * sign;
0056     qreal alpha = _alpha(firststep);
0057     QPointF q1 = derivative(angle1) * alpha;
0058     points.push_back(bezier::Point::from_relative(point(angle1), QPointF(0, 0), q1, math::bezier::Symmetrical));
0059 
0060     // Then we iterate until the angle has been completed
0061     qreal tolerance = step / 2;
0062     do
0063     {
0064         qreal lstep = qMin(angle_left, step);
0065         qreal step_sign = lstep * sign;
0066         qreal angle2 = angle1 + step_sign;
0067         angle_left -= abs(lstep);
0068 
0069         alpha = _alpha(step_sign);
0070         QPointF p2 = point(angle2);
0071         QPointF q2 = derivative(angle2) * alpha;
0072 
0073         points.push_back(bezier::Point::from_relative(p2, -q2, q2, math::bezier::Symmetrical));
0074         angle1 = angle2;
0075     }
0076     while ( angle_left > tolerance );
0077 
0078     if ( points.size() > 1 && qFuzzyCompare(angle_delta, math::tau)  )
0079     {
0080         points.close();
0081         points[0].tan_in = points.back().tan_in;
0082         points.points().pop_back();
0083     }
0084     return points;
0085 }
0086 
0087 math::bezier::Bezier math::EllipseSolver::from_svg_arc(
0088     QPointF start, qreal rx, qreal ry, qreal xrot,
0089     bool large, bool sweep, QPointF dest
0090 )
0091 {
0092     rx = qAbs(rx);
0093     ry = qAbs(ry);
0094 
0095     qreal x1 = start.x();
0096     qreal y1 = start.y();
0097     qreal x2 = dest.x();
0098     qreal y2 = dest.y();
0099     qreal phi = pi * xrot / 180;
0100 
0101     QPointF p1 = _matrix_mul(phi, (start-dest)/2, -1);
0102     qreal x1p = p1.x();
0103     qreal y1p = p1.y();
0104 
0105     qreal cr = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
0106     if ( cr > 1 )
0107     {
0108         qreal s = qSqrt(cr);
0109         rx *= s;
0110         ry *= s;
0111     }
0112 
0113     qreal dq = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
0114     qreal pq = (rx * rx * ry * ry - dq) / dq;
0115     qreal cpm = qSqrt(qMax(0., pq));
0116     if ( large == sweep )
0117         cpm = -cpm;
0118     QPointF cp(cpm * rx * y1p / ry, -cpm * ry * x1p / rx);
0119     QPointF c = _matrix_mul(phi, cp) + QPointF((x1+x2)/2, (y1+y2)/2);
0120     qreal theta1 = _angle(QPointF(1, 0), QPointF((x1p - cp.x()) / rx, (y1p - cp.y()) / ry));
0121     qreal deltatheta = std::fmod(_angle(
0122         QPointF((x1p - cp.x()) / rx, (y1p - cp.y()) / ry),
0123         QPointF((-x1p - cp.x()) / rx, (-y1p - cp.y()) / ry)
0124     ), 2*pi);
0125 
0126     if ( !sweep && deltatheta > 0 )
0127         deltatheta -= 2*pi;
0128     else if ( sweep && deltatheta < 0 )
0129         deltatheta += 2*pi;
0130 
0131     return EllipseSolver(c, QPointF(rx, ry), phi).to_bezier(theta1, deltatheta);
0132 }
0133 
0134 qreal math::EllipseSolver::_alpha(qreal step)
0135 {
0136     return qSin(step) * (qSqrt(4+3*qPow(qTan(step/2), 2)) - 1) / 3;
0137 }
0138 
0139 QPointF math::EllipseSolver::_matrix_mul(qreal phi, const QPointF p, qreal sin_mul)
0140 {
0141     qreal c = qCos(phi);
0142     qreal  s = qSin(phi) * sin_mul;
0143 
0144     qreal xr = c * p.x() - s * p.y();
0145     qreal yr = s * p.x() + c * p.y();
0146     return QPointF(xr, yr);
0147 
0148 }
0149 qreal math::EllipseSolver::_angle(const QPointF& u, const QPointF& v)
0150 {
0151     qreal arg = qAcos(qMax(-1., qMin(1., QPointF::dotProduct(u, v) / (length(u) * length(v)))));
0152     if ( u.x() * v.y() - u.y() * v.x() < 0 )
0153         return -arg;
0154     return arg;
0155 }
0156 
0157