File indexing completed on 2024-12-22 04:14:22
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "Ellipse.h" 0008 #include <cmath> 0009 0010 #include <iostream> 0011 0012 Ellipse::Ellipse() : a(-1), b(-1) 0013 { 0014 } 0015 Ellipse::Ellipse(const QPointF& _p1, const QPointF& _p2, const QPointF& _p3) : p1(_p1), p2(_p2), p3(_p3) { 0016 changeMajor(); 0017 } 0018 0019 Ellipse::~Ellipse() 0020 { 0021 } 0022 0023 bool Ellipse::set(const QPointF& m1, const QPointF& m2, const QPointF& p) 0024 { 0025 bool changedMajor = m1 != p1 || m2 != p2, 0026 changedMinor = !changedMajor && p != p3; 0027 p1 = m1; 0028 p2 = m2; 0029 p3 = p; 0030 if (changedMajor) { 0031 return changeMajor(); 0032 } else if (changedMinor) { 0033 return changeMinor(); 0034 } else { 0035 return a > 0 && b > 0; 0036 } 0037 } 0038 0039 QPointF Ellipse::project(const QPointF& pt) const 0040 { 0041 if (a <= 0 || b <= 0) return pt; // not a valid ellipse 0042 QPointF p = matrix.map(pt); 0043 /* 0044 * intersect line from (0,0) to p with the ellipse in canonical position 0045 * the equation of the line is y = py/px x 0046 * the equation of the ellipse is x^2/a^2 + y^2/b^2 = 1 0047 * x=(a*b*px)/sqrt(a^2*py^2+b^2*px^2) 0048 * y=(a*b*py)/sqrt(a^2*py^2+b^2*px^2) 0049 */ 0050 const qreal divisor = sqrt(a * a * p.y() * p.y() + b * b * p.x() * p.x()); 0051 if (divisor <= 0) return inverse.map(QPointF(a, 0)); // give up 0052 const qreal ab = a * b, factor = 1.0 / divisor; 0053 QPointF ep(ab * p.x() * factor, ab * p.y() * factor); 0054 return inverse.map(ep); 0055 /* return inverse.map(closest(matrix.map(pt)));*/ 0056 } 0057 0058 inline QPointF rotate90(const QPointF& p) { 0059 return QPointF(p.y(), -p.x()); 0060 } 0061 0062 QRectF Ellipse::boundingRect() const 0063 { 0064 const QPointF d = rotate90((p2 - p1) * 0.5 * b / a); 0065 const QPointF pts[4] = { 0066 p1 + d, 0067 p1 - d, 0068 p2 + d, 0069 p2 - d 0070 }; 0071 QRectF ret; 0072 for (int i = 0; i < 4; ++i) { 0073 ret = ret.united(QRectF(pts[i], QSizeF(0.0001, 0.0001))); 0074 } 0075 return ret; 0076 } 0077 0078 inline qreal sqrlength(const QPointF& vec) 0079 { 0080 return vec.x() * vec.x() + vec.y() * vec.y(); 0081 } 0082 inline qreal length(const QPointF& vec) 0083 { 0084 return sqrt(vec.x() * vec.x() + vec.y() * vec.y()); 0085 } 0086 0087 bool Ellipse::changeMajor() 0088 { 0089 a = length(p1 - p2) * 0.5; 0090 0091 /* 0092 * calculate transformation matrix 0093 * x' = m11*x + m21*y + dx 0094 * y' = m22*y + m12*x + dy 0095 * m11 = m22, m12 = -m21 (rotations and translations only) 0096 * x' = m11*x - m12*y + dx 0097 * y' = m11*y + m12*x + dy 0098 * 0099 * then, transforming (x1, y1) to (x1', y1') and (x2, y2) to (x2', y2'): 0100 * 0101 * m11 = (y2*y2' + y1 * (y1'-y2') - y2*y1' + x2*x'2 - x1*x'2 + (x1-x2)*x1') 0102 * ------------------------------------------------------------------ 0103 * (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2) 0104 * m12 = -(x1*(y2'-y1') - x2*y2' + x2*y1' + x2'*y2 - x1'*y2 + (x1'-x2')*y1) 0105 * ------------------------------------------------------------------ 0106 * (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2) 0107 * dx = (x1*(-y2*y2' + y2*y1' - x2*x2') + y1*( x2*y2' - x2*y1' - x2'*y2 - x1'*y2) + x2'*y1^2 + x1^2*x2' + x1'*(y2^2 + x2^2 - x1*x2)) 0108 * ---------------------------------------------------------------------------------------------------------------------------- 0109 * (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2) 0110 * dy = (x1*(-x2*y2' - x2*y1' + x2'*y2) + y1*(-y2*y2' - y2*y1' - x2*x2' + x2*x1') + y2'*y1^2 + x1^2*y2' + y1'(y2^2 + x2^2) - x1*x1'*y2) 0111 * ------------------------------------------------------------------------------------------------------------------------------- 0112 * (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2) 0113 * 0114 * in our usage, to move the ellipse into canonical position: 0115 * 0116 * (x1, y1) = p1 0117 * (x2, y2) = p2 0118 * (x1', y1') = (-a, 0) 0119 * (x2', y2') = (a, 0) 0120 */ 0121 0122 const qreal 0123 x1 = p1.x(), 0124 x2 = p2.x(), 0125 y1 = p1.y(), 0126 y2 = p2.y(), 0127 x1p = -a, 0128 x2p = a, 0129 x1sqr = x1 * x1, 0130 x2sqr = x2 * x2, 0131 y1sqr = y1 * y1, 0132 y2sqr = y2 * y2, 0133 factor = 1.0 / (x1sqr + y1sqr + x2sqr + y2sqr - 2.0 * y1 * y2 - 2.0 * x1 * x2), 0134 m11 = (x2*x2p - x1*x2p + (x1-x2)*x1p) * factor, 0135 m12 = -(x2p*y2 - x1p*y2 + (x1p-x2p)*y1) * factor, 0136 dx = (x1*(-x2*x2p) + y1*(-x2p*y2 - x1p*y2) + x2p*y1sqr + x1sqr*x2p + x1p*(y2sqr + x2sqr - x1*x2)) * factor, 0137 dy = (x1*(x2p*y2) + y1*(-x2*x2p + x2*x1p) - x1*x1p*y2) * factor; 0138 0139 matrix = QTransform(m11, m12, -m12, m11, dx, dy); 0140 inverse = matrix.inverted(); 0141 0142 return changeMinor(); 0143 } 0144 0145 bool Ellipse::changeMinor() 0146 { 0147 QPointF p = matrix.map(p3); 0148 0149 /* 0150 * ellipse formula: 0151 * x^2/a^2 + y^2/b^2 = 1 0152 * b = sqrt(y^2 / (1 - x^2/a^2)) 0153 */ 0154 const qreal 0155 asqr = a * a, 0156 xsqr = p.x() * p.x(), 0157 ysqr = p.y() * p.y(), 0158 divisor = (1.0 - xsqr / asqr); 0159 if (divisor <= 0) { 0160 // division by zero! 0161 b = -1; 0162 return false; 0163 } 0164 b = sqrt(ysqr / divisor); 0165 return true; 0166 } 0167 0168 bool Ellipse::setMajor1(const QPointF& p) 0169 { 0170 p1 = p; 0171 return changeMajor(); 0172 } 0173 bool Ellipse::setMajor2(const QPointF& p) { 0174 p2 = p; 0175 return changeMajor(); 0176 } 0177 bool Ellipse::setPoint(const QPointF& p) 0178 { 0179 p3 = p; 0180 return changeMinor(); 0181 }