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