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 }