File indexing completed on 2024-05-12 15:56:56
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_algebra_2d.h" 0008 0009 #include <QTransform> 0010 #include <QPainterPath> 0011 #include <kis_debug.h> 0012 0013 #include <boost/accumulators/accumulators.hpp> 0014 #include <boost/accumulators/statistics/stats.hpp> 0015 #include <boost/accumulators/statistics/min.hpp> 0016 #include <boost/accumulators/statistics/max.hpp> 0017 0018 #include <array> 0019 #include <QVector2D> 0020 #include <QVector3D> 0021 0022 #include <QtMath> 0023 0024 #include <config-gsl.h> 0025 0026 #ifdef HAVE_GSL 0027 #include <gsl/gsl_multimin.h> 0028 #endif /*HAVE_GSL*/ 0029 0030 #include <Eigen/Eigenvalues> 0031 0032 #define SANITY_CHECKS 0033 0034 namespace KisAlgebra2D { 0035 0036 void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt) 0037 { 0038 const int numPoints = poly.size(); 0039 for (int i = 0; i < numPoints; i++) { 0040 int nextI = i + 1; 0041 if (nextI >= numPoints) { 0042 nextI = 0; 0043 } 0044 0045 const QPointF &p0 = poly[i]; 0046 const QPointF &p1 = poly[nextI]; 0047 0048 QPointF edge = p1 - p0; 0049 0050 qreal cross = crossProduct(edge, *pt - p0) 0051 / (0.5 * edge.manhattanLength()); 0052 0053 if (cross < 1.0 && 0054 isInRange(pt->x(), p0.x(), p1.x()) && 0055 isInRange(pt->y(), p0.y(), p1.y())) { 0056 0057 QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection); 0058 0059 QPointF adjustedPoint = *pt + salt; 0060 0061 // in case the polygon is self-intersecting, polygon direction 0062 // might not help 0063 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { 0064 adjustedPoint = *pt - salt; 0065 0066 #ifdef SANITY_CHECKS 0067 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { 0068 dbgKrita << ppVar(*pt); 0069 dbgKrita << ppVar(adjustedPoint); 0070 dbgKrita << ppVar(QLineF(p0, p1)); 0071 dbgKrita << ppVar(salt); 0072 0073 dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill)); 0074 0075 dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1))); 0076 dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1))); 0077 } 0078 0079 *pt = adjustedPoint; 0080 0081 KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4); 0082 #endif /* SANITY_CHECKS */ 0083 } 0084 } 0085 } 0086 } 0087 0088 QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) { 0089 qreal len1 = norm(base1); 0090 if (len1 < 1e-5) return pt; 0091 qreal sin1 = base1.y() / len1; 0092 qreal cos1 = base1.x() / len1; 0093 0094 qreal len2 = norm(base2); 0095 if (len2 < 1e-5) return QPointF(); 0096 qreal sin2 = base2.y() / len2; 0097 qreal cos2 = base2.x() / len2; 0098 0099 qreal sinD = sin2 * cos1 - cos2 * sin1; 0100 qreal cosD = cos1 * cos2 + sin1 * sin2; 0101 qreal scaleD = len2 / len1; 0102 0103 QPointF result; 0104 result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD); 0105 result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD); 0106 0107 return result; 0108 } 0109 0110 qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2) 0111 { 0112 qreal a1 = std::atan2(v1.y(), v1.x()); 0113 qreal a2 = std::atan2(v2.y(), v2.x()); 0114 0115 return a2 - a1; 0116 } 0117 0118 qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle) 0119 { 0120 if (fuzzyPointCompare(p1, p2)) { 0121 return defaultAngle; 0122 } 0123 0124 const QVector2D diff(p2 - p1); 0125 return std::atan2(diff.y(), diff.x()); 0126 } 0127 0128 QPainterPath smallArrow() 0129 { 0130 QPainterPath p; 0131 0132 p.moveTo(5, 2); 0133 p.lineTo(-3, 8); 0134 p.lineTo(-5, 5); 0135 p.lineTo( 2, 0); 0136 p.lineTo(-5,-5); 0137 p.lineTo(-3,-8); 0138 p.lineTo( 5,-2); 0139 p.arcTo(QRectF(3, -2, 4, 4), 90, -180); 0140 0141 return p; 0142 } 0143 0144 template <class Point, class Rect> 0145 inline Point ensureInRectImpl(Point pt, const Rect &bounds) 0146 { 0147 if (pt.x() > bounds.right()) { 0148 pt.rx() = bounds.right(); 0149 } else if (pt.x() < bounds.left()) { 0150 pt.rx() = bounds.left(); 0151 } 0152 0153 if (pt.y() > bounds.bottom()) { 0154 pt.ry() = bounds.bottom(); 0155 } else if (pt.y() < bounds.top()) { 0156 pt.ry() = bounds.top(); 0157 } 0158 0159 return pt; 0160 } 0161 0162 QPoint ensureInRect(QPoint pt, const QRect &bounds) 0163 { 0164 return ensureInRectImpl(pt, bounds); 0165 } 0166 0167 QPointF ensureInRect(QPointF pt, const QRectF &bounds) 0168 { 0169 return ensureInRectImpl(pt, bounds); 0170 } 0171 0172 bool intersectLineRect(QLineF &line, const QRect rect, bool extend) 0173 { 0174 return intersectLineRect(line, rect, extend, extend); 0175 } 0176 0177 bool intersectLineRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond) 0178 { 0179 // using Liang-Barsky algorithm 0180 // using parametric equation for a line: 0181 // X = x1 + t(x2-x1) = x1 + t*p2 0182 // Y = y1 + t(y2-y1) = y1 + t*p4 0183 // note that we have a line *segment* here, not a line 0184 // t1 = 0, t2 = 1, no matter what points we got 0185 0186 double p1 = -(line.x2() - line.x1()); 0187 double p2 = -p1; 0188 double p3 = -(line.y2() - line.y1()); 0189 double p4 = -p3; 0190 0191 QVector<double> p = QVector<double>({p1, p2, p3, p4}); 0192 QVector<double> q = QVector<double>({line.x1() - rect.x(), rect.x() + rect.width() - line.x1(), line.y1() - rect.y(), rect.y() + rect.height() - line.y1()}); 0193 0194 float tmin = extendFirst ? -std::numeric_limits<float>::infinity() : 0; // line.p1() - first point, or infinity 0195 float tmax = extendSecond ? std::numeric_limits<float>::infinity() : 1; // line.p2() - second point, or infinity 0196 0197 for (int i = 0; i < p.length(); i++) { 0198 // now clipping 0199 if (p[i] == 0 && q[i] < 0) { 0200 // line is parallel to the boundary and outside of it 0201 return false; 0202 } else if (p[i] < 0) { 0203 // line entry point (should be tmin) 0204 double t = q[i]/p[i]; 0205 if (t > tmax) { 0206 if (extendSecond) { 0207 tmax = t; 0208 } else { 0209 return false; 0210 } 0211 } 0212 if (t > tmin) { 0213 tmin = t; 0214 } 0215 0216 0217 } else if (p[i] > 0) { 0218 // line leaving point (should be tmax) 0219 double t = q[i]/p[i]; 0220 if (t < tmin) { 0221 if (extendFirst) { 0222 tmin = t; 0223 } else { 0224 return false; 0225 } 0226 } 0227 if (t < tmax) { 0228 tmax = t; 0229 } 0230 } 0231 } 0232 0233 QPointF pt1 = line.p1(); 0234 QPointF pt2 = line.p2(); 0235 0236 if (extendFirst || tmin > 0) { 0237 pt1 = QPointF(line.x1() + tmin*(line.x2() - line.x1()), line.y1() + tmin*(line.y2() - line.y1())); 0238 } 0239 if (extendSecond || tmax < 1) { 0240 pt2 = QPointF(line.x1() + tmax*(line.x2() - line.x1()), line.y1() + tmax*(line.y2() - line.y1())); 0241 } 0242 0243 line.setP1(pt1); 0244 line.setP2(pt2); 0245 0246 return true; 0247 0248 } 0249 0250 bool intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond) 0251 { 0252 qreal epsilon = 1e-07; 0253 0254 if (polygon.size() == 0) { 0255 return false; 0256 } 0257 0258 // trivial case: no extends, all points inside the polygon 0259 if (!extendFirst && !extendSecond && polygon.containsPoint(line.p1(), Qt::WindingFill) && polygon.containsPoint(line.p2(), Qt::WindingFill)) { 0260 return true; 0261 } 0262 0263 // Cyrus-Beck algorithm: https://en.wikipedia.org/wiki/Cyrus%E2%80%93Beck_algorithm 0264 0265 // parametric equaltion for the line: 0266 // p(t) = t*P1 + (1-t)*P2 0267 0268 // we can't use infinity here, because the points would all end up as (+/- inf, +/- inf) 0269 // which would be useless if we have a very specific direction 0270 // hence we use just "a big number" 0271 float aBigNumber = 100000; // that will keep t*Px still on the line // this is LIMITATION of the algorithm 0272 float tmin = extendFirst ? -aBigNumber : 0; // line.p1() - first point, or infinity 0273 float tmax = extendSecond ? aBigNumber : 1; // line.p2() - second point, or infinity 0274 0275 0276 0277 QList<QLineF> clippingLines; 0278 QList<QPointF> normals; 0279 0280 bool clockwise = false; 0281 { 0282 // only temporary values to check whether the polygon is clockwise or counterclockwise 0283 QPointF vec1 = polygon[1] - polygon[0]; 0284 QPointF vec2 = polygon[2] - polygon[0]; 0285 0286 QLineF line1(QPointF(0, 0), vec1); 0287 QLineF line2(QPointF(0, 0), vec2); 0288 0289 qreal angle = line1.angleTo(line2); // range: <0, 360) // <0, 180) means counter-clockwise 0290 if (angle >= 180) { 0291 clockwise = true; 0292 } 0293 } 0294 0295 0296 for (int i = 0; i < polygon.size() - 1; i++) { 0297 clippingLines.append(QLineF(polygon[i], polygon[i + 1])); 0298 float xdiff = polygon[i].x() - polygon[i + 1].x(); 0299 float ydiff = polygon[i].y() - polygon[i + 1].y(); 0300 0301 if (!clockwise) { 0302 normals.append(QPointF(ydiff, -xdiff)); 0303 } else { 0304 normals.append(QPointF(-ydiff, xdiff)); 0305 } 0306 0307 } 0308 0309 if (!polygon.isClosed()) { 0310 int i = polygon.size() - 1; 0311 clippingLines.append(QLineF(polygon[i], polygon[0])); 0312 float xdiff = polygon[i].x() - polygon[0].x(); 0313 float ydiff = polygon[i].y() - polygon[0].y(); 0314 0315 if (!clockwise) { 0316 normals.append(QPointF(ydiff, -xdiff)); 0317 } else { 0318 normals.append(QPointF(-ydiff, xdiff)); 0319 } 0320 0321 } 0322 0323 QPointF lineVec = line.p2() - line.p1(); 0324 // ax + by + c = 0 0325 // c = -ax - by 0326 // qreal cFromStandardEquation = -lineVec.x()*line.p1().x() - lineVec.y()*line.p1().y(); 0327 0328 0329 QPointF p1 = line.p1(); 0330 QPointF p2 = line.p2(); 0331 0332 qreal pA, pB, pC; // standard equation for the line: ax + by + c = 0 0333 if (qAbs(lineVec.x()) < epsilon) { 0334 // x1 ~= x2, line looks like: x = -pC 0335 pB = 0; 0336 pA = 1; 0337 pC = -p1.x(); 0338 } else { 0339 pB = 1; // let b = 1 to simplify 0340 qreal tmp = (p1.y() - p2.y())/(p1.x() - p2.x()); 0341 pA = -tmp; 0342 pC = tmp * p1.x() - p1.y(); 0343 } 0344 0345 0346 int pEFound = 0; 0347 0348 0349 for (int i = 0; i < clippingLines.size(); i++) { 0350 0351 // is the clipping line parallel to the line? 0352 QPointF clipVec = clippingLines[i].p2() - clippingLines[i].p1(); 0353 0354 if (qFuzzyCompare(clipVec.x()*lineVec.y(), clipVec.y()*lineVec.x())) { 0355 0356 // vectors are parallel 0357 // let's check if one of the clipping points is on the line (extended) to see if it's the same line; if not, proceed normally 0358 0359 qreal distanceBetweenLines = qAbs(pA*clippingLines[i].p1().x() + pB*clippingLines[i].p1().y() + pC) 0360 /(sqrt(pA*pA + pB*pB)); 0361 0362 0363 if (qAbs(distanceBetweenLines) < epsilon) { 0364 // they are the same line 0365 0366 qreal t1; 0367 qreal t2; 0368 0369 if (qAbs(p2.x() - p1.x()) > epsilon) { 0370 t1 = (clippingLines[i].p1().x() - p1.x())/(p2.x() - p1.x()); 0371 t2 = (clippingLines[i].p2().x() - p1.x())/(p2.x() - p1.x()); 0372 } else { 0373 t1 = (clippingLines[i].p1().y() - p1.y())/(p2.y() - p1.y()); 0374 t2 = (clippingLines[i].p2().y() - p1.y())/(p2.y() - p1.y()); 0375 } 0376 0377 qreal tmin1 = qMin(t1, t2); 0378 qreal tmax2 = qMax(t1, t2); 0379 0380 0381 0382 if (tmin < tmin1) { 0383 tmin = tmin1; 0384 } 0385 if (tmax > tmax2) { 0386 tmax = tmax2; 0387 } 0388 continue; 0389 } 0390 else { 0391 // if clipping line points are P1 and P2, and some other point is P(t) (given by parametric equation), 0392 // and N is the normal vector 0393 // and one of the points of the line we're intersecting is K, 0394 // then we have a following equation: 0395 // K = P(t) + a * N 0396 // where t and a are unknown. 0397 // after simplifying we get: 0398 // t*(p2 - p1) + a*(n) = k - p1 0399 // and since we have two axis, we get a linear system of two equations 0400 // A * [t; a] = B 0401 0402 Eigen::Matrix2f A; 0403 Eigen::Vector2f b; 0404 A << p2.x() - p1.x(), normals[i].x(), 0405 p2.y() - p1.y(), normals[i].y(); 0406 b << clippingLines[i].p1().x() - p1.x(), 0407 clippingLines[i].p1().y() - p1.y(); 0408 0409 Eigen::Vector2f ta = A.colPivHouseholderQr().solve(b); 0410 0411 qreal tt = ta(1); 0412 if (tt < 0) { 0413 return false; 0414 } 0415 } 0416 0417 } 0418 0419 0420 boost::optional<QPointF> pEOptional = intersectLines(clippingLines[i], line); // bounded, unbounded 0421 0422 0423 if (pEOptional) { 0424 pEFound++; 0425 0426 QPointF pE = pEOptional.value(); 0427 QPointF n = normals[i]; 0428 0429 QPointF A = line.p2() - line.p1(); 0430 QPointF B = line.p1() - pE; 0431 0432 qreal over = (n.x()*B.x() + n.y()*B.y()); 0433 qreal under = (n.x()*A.x() + n.y()*A.y()); 0434 qreal t = -over/under; 0435 0436 // if (pE.x() != p2.x()) { 0437 // qreal maybet = (pE.x() - p1.x())/(p2.x() - p1.x()); 0438 // } 0439 0440 qreal tminvalue = tmin * under + over; 0441 qreal tmaxvalue = tmax * under + over; 0442 0443 if (tminvalue > 0 && tmaxvalue > 0) { 0444 // both outside of the edge 0445 return false; 0446 } 0447 if (tminvalue <= 0 && tmaxvalue <= 0) { 0448 // noop, both inside 0449 } 0450 if (tminvalue*tmaxvalue < 0) { 0451 0452 // on two different sides 0453 if (tminvalue > 0) { 0454 // tmin outside, tmax inside 0455 if (t > tmin) { 0456 tmin = t; 0457 } 0458 } else { 0459 if (t < tmax) { 0460 tmax = t; 0461 } 0462 } 0463 } 0464 0465 0466 0467 if (tmax < tmin) { 0468 return false; 0469 } 0470 } 0471 else { 0472 } 0473 0474 } 0475 0476 if (pEFound == 0) { 0477 return false; 0478 } 0479 0480 QLineF response = QLineF(tmin*line.p2() + (1-tmin)*line.p1(), tmax*line.p2() + (1-tmax)*line.p1()); 0481 line = response; 0482 return true; 0483 } 0484 0485 template <class Rect, class Point> 0486 QVector<Point> sampleRectWithPoints(const Rect &rect) 0487 { 0488 QVector<Point> points; 0489 0490 Point m1 = 0.5 * (rect.topLeft() + rect.topRight()); 0491 Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight()); 0492 0493 points << rect.topLeft(); 0494 points << m1; 0495 points << rect.topRight(); 0496 0497 points << 0.5 * (rect.topLeft() + rect.bottomLeft()); 0498 points << 0.5 * (m1 + m2); 0499 points << 0.5 * (rect.topRight() + rect.bottomRight()); 0500 0501 points << rect.bottomLeft(); 0502 points << m2; 0503 points << rect.bottomRight(); 0504 0505 return points; 0506 } 0507 0508 QVector<QPoint> sampleRectWithPoints(const QRect &rect) 0509 { 0510 return sampleRectWithPoints<QRect, QPoint>(rect); 0511 } 0512 0513 QVector<QPointF> sampleRectWithPoints(const QRectF &rect) 0514 { 0515 return sampleRectWithPoints<QRectF, QPointF>(rect); 0516 } 0517 0518 0519 template <class Rect, class Point, bool alignPixels> 0520 Rect approximateRectFromPointsImpl(const QVector<Point> &points) 0521 { 0522 using namespace boost::accumulators; 0523 accumulator_set<qreal, stats<tag::min, tag::max > > accX; 0524 accumulator_set<qreal, stats<tag::min, tag::max > > accY; 0525 0526 Q_FOREACH (const Point &pt, points) { 0527 accX(pt.x()); 0528 accY(pt.y()); 0529 } 0530 0531 Rect resultRect; 0532 0533 if (alignPixels) { 0534 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), 0535 std::ceil(max(accX)), std::ceil(max(accY))); 0536 } else { 0537 resultRect.setCoords(min(accX), min(accY), 0538 max(accX), max(accY)); 0539 } 0540 0541 return resultRect; 0542 } 0543 0544 QRect approximateRectFromPoints(const QVector<QPoint> &points) 0545 { 0546 return approximateRectFromPointsImpl<QRect, QPoint, true>(points); 0547 } 0548 0549 QRectF approximateRectFromPoints(const QVector<QPointF> &points) 0550 { 0551 return approximateRectFromPointsImpl<QRectF, QPointF, false>(points); 0552 } 0553 0554 QRect approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func) 0555 { 0556 QVector<QPoint> points = sampleRectWithPoints(rect); 0557 0558 using namespace boost::accumulators; 0559 accumulator_set<qreal, stats<tag::min, tag::max > > accX; 0560 accumulator_set<qreal, stats<tag::min, tag::max > > accY; 0561 0562 Q_FOREACH (const QPoint &pt, points) { 0563 QPointF dstPt = func(pt); 0564 0565 accX(dstPt.x()); 0566 accY(dstPt.y()); 0567 } 0568 0569 QRect resultRect; 0570 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), 0571 std::ceil(max(accX)), std::ceil(max(accY))); 0572 0573 return resultRect; 0574 } 0575 0576 0577 QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p) 0578 { 0579 QVector<QPointF> points; 0580 0581 const QLineF cutLine = p.getLine(); 0582 0583 points << rc.topLeft(); 0584 points << rc.topRight(); 0585 points << rc.bottomRight(); 0586 points << rc.bottomLeft(); 0587 0588 QPointF p1 = points[3]; 0589 bool p1Valid = p.pos(p1) >= 0; 0590 0591 QVector<QPointF> resultPoints; 0592 0593 for (int i = 0; i < 4; i++) { 0594 const QPointF p2 = points[i]; 0595 const bool p2Valid = p.pos(p2) >= 0; 0596 0597 if (p1Valid != p2Valid) { 0598 QPointF intersection; 0599 cutLine.intersect(QLineF(p1, p2), &intersection); 0600 resultPoints << intersection; 0601 } 0602 0603 if (p2Valid) { 0604 resultPoints << p2; 0605 } 0606 0607 p1 = p2; 0608 p1Valid = p2Valid; 0609 } 0610 0611 return approximateRectFromPoints(resultPoints); 0612 } 0613 0614 int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2) 0615 { 0616 int numSolutions = 0; 0617 0618 const qreal D = pow2(b) - 4 * a * c; 0619 const qreal eps = 1e-14; 0620 0621 if (qAbs(D) <= eps) { 0622 *x1 = -b / (2 * a); 0623 numSolutions = 1; 0624 } else if (D < 0) { 0625 return 0; 0626 } else { 0627 const qreal sqrt_D = std::sqrt(D); 0628 0629 *x1 = (-b + sqrt_D) / (2 * a); 0630 *x2 = (-b - sqrt_D) / (2 * a); 0631 numSolutions = 2; 0632 } 0633 0634 return numSolutions; 0635 } 0636 0637 QVector<QPointF> intersectTwoCircles(const QPointF ¢er1, qreal r1, 0638 const QPointF ¢er2, qreal r2) 0639 { 0640 QVector<QPointF> points; 0641 0642 const QPointF diff = (center2 - center1); 0643 const QPointF c1; 0644 const QPointF c2 = diff; 0645 0646 const qreal centerDistance = norm(diff); 0647 0648 if (centerDistance > r1 + r2) return points; 0649 if (centerDistance < qAbs(r1 - r2)) return points; 0650 0651 if (centerDistance < qAbs(r1 - r2) + 0.001) { 0652 dbgKrita << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2)); 0653 return points; 0654 } 0655 0656 const qreal x_kp1 = diff.x(); 0657 const qreal y_kp1 = diff.y(); 0658 0659 const qreal F2 = 0660 0.5 * (pow2(x_kp1) + 0661 pow2(y_kp1) + pow2(r1) - pow2(r2)); 0662 0663 const qreal eps = 1e-6; 0664 0665 if (qAbs(diff.y()) < eps) { 0666 qreal x = F2 / diff.x(); 0667 qreal y1, y2; 0668 int result = KisAlgebra2D::quadraticEquation( 0669 1, 0, 0670 pow2(x) - pow2(r2), 0671 &y1, &y2); 0672 0673 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } 0674 0675 if (result == 1) { 0676 points << QPointF(x, y1); 0677 } else if (result == 2) { 0678 KisAlgebra2D::RightHalfPlane p(c1, c2); 0679 0680 QPointF p1(x, y1); 0681 QPointF p2(x, y2); 0682 0683 if (p.pos(p1) >= 0) { 0684 points << p1; 0685 points << p2; 0686 } else { 0687 points << p2; 0688 points << p1; 0689 } 0690 } 0691 } else { 0692 const qreal A = diff.x() / diff.y(); 0693 const qreal C = F2 / diff.y(); 0694 0695 qreal x1, x2; 0696 int result = KisAlgebra2D::quadraticEquation( 0697 1 + pow2(A), -2 * A * C, 0698 pow2(C) - pow2(r1), 0699 &x1, &x2); 0700 0701 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } 0702 0703 if (result == 1) { 0704 points << QPointF(x1, C - x1 * A); 0705 } else if (result == 2) { 0706 KisAlgebra2D::RightHalfPlane p(c1, c2); 0707 0708 QPointF p1(x1, C - x1 * A); 0709 QPointF p2(x2, C - x2 * A); 0710 0711 if (p.pos(p1) >= 0) { 0712 points << p1; 0713 points << p2; 0714 } else { 0715 points << p2; 0716 points << p1; 0717 } 0718 } 0719 } 0720 0721 for (int i = 0; i < points.size(); i++) { 0722 points[i] = center1 + points[i]; 0723 } 0724 0725 return points; 0726 } 0727 0728 QTransform mapToRect(const QRectF &rect) 0729 { 0730 return 0731 QTransform(rect.width(), 0, 0, rect.height(), 0732 rect.x(), rect.y()); 0733 } 0734 0735 QTransform mapToRectInverse(const QRectF &rect) 0736 { 0737 return 0738 QTransform::fromTranslate(-rect.x(), -rect.y()) * 0739 QTransform::fromScale(rect.width() != 0 ? 1.0 / rect.width() : 0.0, 0740 rect.height() != 0 ? 1.0 / rect.height() : 0.0); 0741 } 0742 0743 bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) { 0744 return 0745 qAbs(t1.m11() - t2.m11()) < delta && 0746 qAbs(t1.m12() - t2.m12()) < delta && 0747 qAbs(t1.m13() - t2.m13()) < delta && 0748 qAbs(t1.m21() - t2.m21()) < delta && 0749 qAbs(t1.m22() - t2.m22()) < delta && 0750 qAbs(t1.m23() - t2.m23()) < delta && 0751 qAbs(t1.m31() - t2.m31()) < delta && 0752 qAbs(t1.m32() - t2.m32()) < delta && 0753 qAbs(t1.m33() - t2.m33()) < delta; 0754 } 0755 0756 bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2) 0757 { 0758 return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y()); 0759 } 0760 0761 0762 bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta) 0763 { 0764 return qAbs(p1.x() - p2.x()) < delta && qAbs(p1.y() - p2.y()) < delta; 0765 } 0766 0767 0768 inline QTransform toQTransformStraight(const Eigen::Matrix3d &m) 0769 { 0770 return QTransform(m(0,0), m(0,1), m(0,2), 0771 m(1,0), m(1,1), m(1,2), 0772 m(2,0), m(2,1), m(2,2)); 0773 } 0774 0775 inline Eigen::Matrix3d fromQTransformStraight(const QTransform &t) 0776 { 0777 Eigen::Matrix3d m; 0778 0779 m << t.m11() , t.m12() , t.m13() 0780 ,t.m21() , t.m22() , t.m23() 0781 ,t.m31() , t.m32() , t.m33(); 0782 0783 return m; 0784 } 0785 0786 /********************************************************/ 0787 /* DecomposedMatix */ 0788 /********************************************************/ 0789 0790 DecomposedMatix::DecomposedMatix() 0791 { 0792 } 0793 0794 DecomposedMatix::DecomposedMatix(const QTransform &t0) 0795 { 0796 QTransform t(t0); 0797 0798 QTransform projMatrix; 0799 0800 if (t.m33() == 0.0 || t0.determinant() == 0.0) { 0801 qWarning() << "Cannot decompose matrix!" << t; 0802 valid = false; 0803 return; 0804 } 0805 0806 if (t.type() == QTransform::TxProject) { 0807 QTransform affineTransform(t.toAffine()); 0808 projMatrix = affineTransform.inverted() * t; 0809 0810 t = affineTransform; 0811 proj[0] = projMatrix.m13(); 0812 proj[1] = projMatrix.m23(); 0813 proj[2] = projMatrix.m33(); 0814 } 0815 0816 // can't use QVector3D, because they have too little accuracy for ellipse in perspective calculations 0817 std::array<Eigen::Vector3d, 3> rows; 0818 0819 0820 // << t.m11() << t.m12() << t.m13()) 0821 rows[0] = Eigen::Vector3d(t.m11(), t.m12(), t.m13()); 0822 rows[1] = Eigen::Vector3d(t.m21(), t.m22(), t.m23()); 0823 rows[2] = Eigen::Vector3d(t.m31(), t.m32(), t.m33()); 0824 0825 if (!qFuzzyCompare(t.m33(), 1.0)) { 0826 const qreal invM33 = 1.0 / t.m33(); 0827 0828 for (auto &row : rows) { 0829 row *= invM33; 0830 } 0831 } 0832 0833 dx = rows[2].x(); 0834 dy = rows[2].y(); 0835 0836 rows[2] = Eigen::Vector3d(0,0,1); 0837 0838 0839 scaleX = rows[0].norm(); 0840 rows[0] *= 1.0 / scaleX; 0841 0842 shearXY = rows[0].dot(rows[1]); 0843 rows[1] = rows[1] - shearXY * rows[0]; 0844 0845 scaleY = rows[1].norm(); 0846 rows[1] *= 1.0 / scaleY; 0847 shearXY *= 1.0 / scaleY; 0848 0849 // If determinant is negative, one axis was flipped. 0850 qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x(); 0851 if (determinant < 0) { 0852 // Flip axis with minimum unit vector dot product. 0853 if (rows[0].x() < rows[1].y()) { 0854 scaleX = -scaleX; 0855 rows[0] = -rows[0]; 0856 } else { 0857 scaleY = -scaleY; 0858 rows[1] = -rows[1]; 0859 } 0860 shearXY = - shearXY; 0861 } 0862 0863 angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x())); 0864 0865 if (angle != 0.0) { 0866 // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] 0867 // = [row0x, -row0y, row0y, row0x] 0868 // Thanks to the normalization above. 0869 0870 qreal sn = -rows[0].y(); 0871 qreal cs = rows[0].x(); 0872 qreal m11 = rows[0].x(); 0873 qreal m12 = rows[0].y(); 0874 qreal m21 = rows[1].x(); 0875 qreal m22 = rows[1].y(); 0876 rows[0][0] = (cs * m11 + sn * m21); 0877 rows[0][1] = (cs * m12 + sn * m22); 0878 rows[1][0] = (-sn * m11 + cs * m21); 0879 rows[1][1] = (-sn * m12 + cs * m22); 0880 } 0881 0882 QTransform leftOver( 0883 rows[0].x(), rows[0].y(), rows[0].z(), 0884 rows[1].x(), rows[1].y(), rows[1].z(), 0885 rows[2].x(), rows[2].y(), rows[2].z()); 0886 0887 if (/*true || */!fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)) { 0888 // what's wrong? 0889 ENTER_FUNCTION() << "FAILING THE ASSERT BELOW!"; 0890 ENTER_FUNCTION() << ppVar(leftOver); 0891 ENTER_FUNCTION() << "matrix to decompose was: " << ppVar(t0); 0892 ENTER_FUNCTION() << ppVar(t.m33()) << ppVar(t0.determinant()); 0893 Eigen::Matrix3d mat1 = fromQTransformStraight(QTransform()); 0894 Eigen::Matrix3d mat2 = fromQTransformStraight(leftOver); 0895 Eigen::Matrix3d mat3 = mat2 - mat1; 0896 ENTER_FUNCTION() << mat3(0, 0) << mat3(0, 1) << mat3(0, 2); 0897 ENTER_FUNCTION() << mat3(1, 0) << mat3(1, 1) << mat3(1, 2); 0898 ENTER_FUNCTION() << mat3(2, 0) << mat3(2, 1) << mat3(2, 2); 0899 //ENTER_FUNCTION() << ppVar(mat1 - mat2); 0900 } 0901 0902 0903 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)); 0904 KIS_ASSERT(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)); 0905 } 0906 0907 0908 std::pair<QPointF, QTransform> transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal) 0909 { 0910 KisAlgebra2D::DecomposedMatix decomposed(fullLocalToGlobal); 0911 const QTransform localToGlobal = 0912 decomposed.scaleTransform() * 0913 decomposed.shearTransform() * 0914 0915 /* decomposed.projectTransform() * */ 0916 /*decomposed.translateTransform() * */ 0917 0918 decomposed.rotateTransform(); 0919 0920 const QTransform localEllipse = QTransform(1.0 / pow2(axes.x()), 0.0, 0.0, 0921 0.0, 1.0 / pow2(axes.y()), 0.0, 0922 0.0, 0.0, 1.0); 0923 0924 0925 const QTransform globalToLocal = localToGlobal.inverted(); 0926 0927 Eigen::Matrix3d eqM = 0928 fromQTransformStraight(globalToLocal * 0929 localEllipse * 0930 globalToLocal.transposed()); 0931 0932 // std::cout << "eqM:" << std::endl << eqM << std::endl; 0933 0934 Eigen::EigenSolver<Eigen::Matrix3d> eigenSolver(eqM); 0935 0936 const Eigen::Matrix3d T = eigenSolver.eigenvalues().real().asDiagonal(); 0937 const Eigen::Matrix3d U = eigenSolver.eigenvectors().real(); 0938 0939 const Eigen::Matrix3d Ti = eigenSolver.eigenvalues().imag().asDiagonal(); 0940 const Eigen::Matrix3d Ui = eigenSolver.eigenvectors().imag(); 0941 0942 KIS_SAFE_ASSERT_RECOVER_NOOP(Ti.isZero()); 0943 KIS_SAFE_ASSERT_RECOVER_NOOP(Ui.isZero()); 0944 KIS_SAFE_ASSERT_RECOVER_NOOP((U * U.transpose()).isIdentity()); 0945 0946 // std::cout << "T:" << std::endl << T << std::endl; 0947 // std::cout << "U:" << std::endl << U << std::endl; 0948 0949 // std::cout << "Ti:" << std::endl << Ti << std::endl; 0950 // std::cout << "Ui:" << std::endl << Ui << std::endl; 0951 0952 // std::cout << "UTU':" << std::endl << U * T * U.transpose() << std::endl; 0953 0954 const qreal newA = 1.0 / std::sqrt(T(0,0) * T(2,2)); 0955 const qreal newB = 1.0 / std::sqrt(T(1,1) * T(2,2)); 0956 0957 const QTransform newGlobalToLocal = toQTransformStraight(U); 0958 const QTransform newLocalToGlobal = QTransform::fromScale(-1,-1) * 0959 newGlobalToLocal.inverted() * 0960 decomposed.translateTransform(); 0961 0962 return std::make_pair(QPointF(newA, newB), newLocalToGlobal); 0963 } 0964 0965 QPointF alignForZoom(const QPointF &pt, qreal zoom) 0966 { 0967 return QPointF((pt * zoom).toPoint()) / zoom; 0968 } 0969 0970 boost::optional<QPointF> intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine) 0971 { 0972 const QPointF B1 = unboundedLine.p1(); 0973 const QPointF A1 = unboundedLine.p2() - unboundedLine.p1(); 0974 0975 const QPointF B2 = boundedLine.p1(); 0976 const QPointF A2 = boundedLine.p2() - boundedLine.p1(); 0977 0978 Eigen::Matrix<float, 2, 2> A; 0979 A << A1.x(), -A2.x(), 0980 A1.y(), -A2.y(); 0981 0982 Eigen::Matrix<float, 2, 1> B; 0983 B << B2.x() - B1.x(), 0984 B2.y() - B1.y(); 0985 0986 if (qFuzzyIsNull(A.determinant())) { 0987 return boost::none; 0988 } 0989 0990 Eigen::Matrix<float, 2, 1> T; 0991 0992 T = A.inverse() * B; 0993 0994 const qreal t2 = T(1,0); 0995 double epsilon = 1e-06; // to avoid precision issues 0996 0997 if (t2 < 0.0 || t2 > 1.0) { 0998 if (qAbs(t2) > epsilon && qAbs(t2 - 1.0) > epsilon) { 0999 return boost::none; 1000 } 1001 } 1002 1003 return t2 * A2 + B2; 1004 } 1005 1006 boost::optional<QPointF> intersectLines(const QPointF &p1, const QPointF &p2, 1007 const QPointF &q1, const QPointF &q2) 1008 { 1009 return intersectLines(QLineF(p1, p2), QLineF(q1, q2)); 1010 } 1011 1012 QVector<QPointF> findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b) 1013 { 1014 using KisAlgebra2D::norm; 1015 using KisAlgebra2D::dotProduct; 1016 1017 QVector<QPointF> result; 1018 1019 const QPointF p = p2 - p1; 1020 1021 const qreal pSq = dotProduct(p, p); 1022 1023 const qreal T = 0.5 * (-pow2(b) + pow2(a) + pSq); 1024 1025 if (p.isNull()) return result; 1026 1027 if (qAbs(p.x()) > qAbs(p.y())) { 1028 const qreal A = 1.0; 1029 const qreal B2 = -T * p.y() / pSq; 1030 const qreal C = pow2(T) / pSq - pow2(a * p.x()) / pSq; 1031 1032 const qreal D4 = pow2(B2) - A * C; 1033 1034 if (D4 > 0 || qFuzzyIsNull(D4)) { 1035 if (qFuzzyIsNull(D4)) { 1036 const qreal y = -B2 / A; 1037 const qreal x = (T - y * p.y()) / p.x(); 1038 result << p1 + QPointF(x, y); 1039 } else { 1040 const qreal y1 = (-B2 + std::sqrt(D4)) / A; 1041 const qreal x1 = (T - y1 * p.y()) / p.x(); 1042 result << p1 + QPointF(x1, y1); 1043 1044 const qreal y2 = (-B2 - std::sqrt(D4)) / A; 1045 const qreal x2 = (T - y2 * p.y()) / p.x(); 1046 result << p1 + QPointF(x2, y2); 1047 } 1048 } 1049 } else { 1050 const qreal A = 1.0; 1051 const qreal B2 = -T * p.x() / pSq; 1052 const qreal C = pow2(T) / pSq - pow2(a * p.y()) / pSq; 1053 1054 const qreal D4 = pow2(B2) - A * C; 1055 1056 if (D4 > 0 || qFuzzyIsNull(D4)) { 1057 if (qFuzzyIsNull(D4)) { 1058 const qreal x = -B2 / A; 1059 const qreal y = (T - x * p.x()) / p.y(); 1060 result << p1 + QPointF(x, y); 1061 } else { 1062 const qreal x1 = (-B2 + std::sqrt(D4)) / A; 1063 const qreal y1 = (T - x1 * p.x()) / p.y(); 1064 result << p1 + QPointF(x1, y1); 1065 1066 const qreal x2 = (-B2 - std::sqrt(D4)) / A; 1067 const qreal y2 = (T - x2 * p.x()) / p.y(); 1068 result << p1 + QPointF(x2, y2); 1069 } 1070 } 1071 } 1072 1073 return result; 1074 } 1075 1076 boost::optional<QPointF> findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest) 1077 { 1078 const QVector<QPointF> points = findTrianglePoint(p1, p2, a, b); 1079 1080 boost::optional<QPointF> result; 1081 1082 if (points.size() == 1) { 1083 result = points.first(); 1084 } else if (points.size() > 1) { 1085 result = kisDistance(points.first(), nearest) < kisDistance(points.last(), nearest) ? points.first() : points.last(); 1086 } 1087 1088 return result; 1089 } 1090 1091 QPointF moveElasticPoint(const QPointF &pt, const QPointF &base, const QPointF &newBase, const QPointF &wingA, const QPointF &wingB) 1092 { 1093 using KisAlgebra2D::norm; 1094 using KisAlgebra2D::dotProduct; 1095 using KisAlgebra2D::crossProduct; 1096 1097 const QPointF vecL = base - pt; 1098 const QPointF vecLa = wingA - pt; 1099 const QPointF vecLb = wingB - pt; 1100 1101 const qreal L = norm(vecL); 1102 const qreal La = norm(vecLa); 1103 const qreal Lb = norm(vecLb); 1104 1105 const qreal sinMuA = crossProduct(vecLa, vecL) / (La * L); 1106 const qreal sinMuB = crossProduct(vecL, vecLb) / (Lb * L); 1107 1108 const qreal cosMuA = dotProduct(vecLa, vecL) / (La * L); 1109 const qreal cosMuB = dotProduct(vecLb, vecL) / (Lb * L); 1110 1111 const qreal dL = dotProduct(newBase - base, -vecL) / L; 1112 1113 1114 const qreal divB = (cosMuB + La / Lb * sinMuB * cosMuA / sinMuA) / L + 1115 (cosMuB + sinMuB * cosMuA / sinMuA) / Lb; 1116 const qreal dLb = dL / ( L * divB); 1117 1118 const qreal divA = (cosMuA + Lb / La * sinMuA * cosMuB / sinMuB) / L + 1119 (cosMuA + sinMuA * cosMuB / sinMuB) / La; 1120 const qreal dLa = dL / ( L * divA); 1121 1122 boost::optional<QPointF> result = 1123 KisAlgebra2D::findTrianglePointNearest(wingA, wingB, La + dLa, Lb + dLb, pt); 1124 1125 const QPointF resultPoint = result ? *result : pt; 1126 1127 return resultPoint; 1128 } 1129 1130 #ifdef HAVE_GSL 1131 1132 struct ElasticMotionData 1133 { 1134 QPointF oldBasePos; 1135 QPointF newBasePos; 1136 QVector<QPointF> anchorPoints; 1137 1138 QPointF oldResultPoint; 1139 }; 1140 1141 double elasticMotionError(const gsl_vector * x, void *paramsPtr) 1142 { 1143 using KisAlgebra2D::norm; 1144 using KisAlgebra2D::dotProduct; 1145 using KisAlgebra2D::crossProduct; 1146 1147 const QPointF newResultPoint(gsl_vector_get(x, 0), gsl_vector_get(x, 1)); 1148 1149 const ElasticMotionData *p = static_cast<const ElasticMotionData*>(paramsPtr); 1150 1151 const QPointF vecL = newResultPoint - p->newBasePos; 1152 const qreal L = norm(vecL); 1153 1154 const qreal deltaL = L - kisDistance(p->oldBasePos, p->oldResultPoint); 1155 1156 QVector<qreal> deltaLi; 1157 QVector<qreal> Li; 1158 QVector<qreal> cosMuI; 1159 QVector<qreal> sinMuI; 1160 Q_FOREACH (const QPointF &anchorPoint, p->anchorPoints) { 1161 const QPointF vecLi = newResultPoint - anchorPoint; 1162 const qreal _Li = norm(vecLi); 1163 1164 Li << _Li; 1165 deltaLi << _Li - kisDistance(p->oldResultPoint, anchorPoint); 1166 cosMuI << dotProduct(vecLi, vecL) / (_Li * L); 1167 sinMuI << crossProduct(vecL, vecLi) / (_Li * L); 1168 } 1169 1170 qreal finalError = 0; 1171 1172 qreal tangentialForceSum = 0; 1173 for (int i = 0; i < p->anchorPoints.size(); i++) { 1174 const qreal sum = deltaLi[i] * sinMuI[i] / Li[i]; 1175 tangentialForceSum += sum; 1176 } 1177 1178 finalError += pow2(tangentialForceSum); 1179 1180 qreal normalForceSum = 0; 1181 { 1182 qreal sum = 0; 1183 for (int i = 0; i < p->anchorPoints.size(); i++) { 1184 sum += deltaLi[i] * cosMuI[i] / Li[i]; 1185 } 1186 normalForceSum = (-deltaL) / L - sum; 1187 } 1188 1189 finalError += pow2(normalForceSum); 1190 1191 return finalError; 1192 } 1193 1194 1195 #endif /* HAVE_GSL */ 1196 1197 QPointF moveElasticPoint(const QPointF &pt, 1198 const QPointF &base, const QPointF &newBase, 1199 const QVector<QPointF> &anchorPoints) 1200 { 1201 const QPointF offset = newBase - base; 1202 1203 QPointF newResultPoint = pt + offset; 1204 1205 #ifdef HAVE_GSL 1206 1207 ElasticMotionData data; 1208 data.newBasePos = newBase; 1209 data.oldBasePos = base; 1210 data.anchorPoints = anchorPoints; 1211 data.oldResultPoint = pt; 1212 1213 const gsl_multimin_fminimizer_type *T = 1214 gsl_multimin_fminimizer_nmsimplex2; 1215 gsl_multimin_fminimizer *s = 0; 1216 gsl_vector *ss, *x; 1217 gsl_multimin_function minex_func; 1218 1219 size_t iter = 0; 1220 int status; 1221 double size; 1222 1223 /* Starting point */ 1224 x = gsl_vector_alloc (2); 1225 gsl_vector_set (x, 0, newResultPoint.x()); 1226 gsl_vector_set (x, 1, newResultPoint.y()); 1227 1228 /* Set initial step sizes to 0.1 */ 1229 ss = gsl_vector_alloc (2); 1230 gsl_vector_set (ss, 0, 0.1 * offset.x()); 1231 gsl_vector_set (ss, 1, 0.1 * offset.y()); 1232 1233 /* Initialize method and iterate */ 1234 minex_func.n = 2; 1235 minex_func.f = elasticMotionError; 1236 minex_func.params = (void*)&data; 1237 1238 s = gsl_multimin_fminimizer_alloc (T, 2); 1239 gsl_multimin_fminimizer_set (s, &minex_func, x, ss); 1240 1241 do 1242 { 1243 iter++; 1244 status = gsl_multimin_fminimizer_iterate(s); 1245 1246 if (status) 1247 break; 1248 1249 size = gsl_multimin_fminimizer_size (s); 1250 status = gsl_multimin_test_size (size, 1e-6); 1251 1252 /** 1253 * Sometimes the algorithm may converge to a wrond point, 1254 * then just try to force it search better or return invalid 1255 * result. 1256 */ 1257 if (status == GSL_SUCCESS && elasticMotionError(s->x, &data) > 0.5) { 1258 status = GSL_CONTINUE; 1259 } 1260 1261 if (status == GSL_SUCCESS) 1262 { 1263 // qDebug() << "*******Converged to minimum"; 1264 // qDebug() << gsl_vector_get (s->x, 0) 1265 // << gsl_vector_get (s->x, 1) 1266 // << "|" << s->fval << size; 1267 // qDebug() << ppVar(iter); 1268 1269 newResultPoint.rx() = gsl_vector_get (s->x, 0); 1270 newResultPoint.ry() = gsl_vector_get (s->x, 1); 1271 } 1272 } 1273 while (status == GSL_CONTINUE && iter < 10000); 1274 1275 if (status != GSL_SUCCESS) { 1276 ENTER_FUNCTION() << "failed to find point" << ppVar(pt) << ppVar(base) << ppVar(newBase); 1277 } 1278 1279 gsl_vector_free(x); 1280 gsl_vector_free(ss); 1281 gsl_multimin_fminimizer_free (s); 1282 #endif 1283 1284 return newResultPoint; 1285 } 1286 1287 void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond) 1288 { 1289 bool intersects = intersectLineRect(line, rect, extendFirst, extendSecond); 1290 if (!intersects) { 1291 line = QLineF(); // empty line to help with drawing 1292 } 1293 } 1294 1295 void cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond) 1296 { 1297 bool intersects = intersectLineConvexPolygon(line, polygon, extendFirst, extendSecond); 1298 if (!intersects) { 1299 line = QLineF(); // empty line to help with drawing 1300 } 1301 } 1302 1303 1304 qreal findMinimumGoldenSection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100) 1305 { 1306 // requirements: 1307 // only one local minimum between xA and xB 1308 1309 int i = 0; 1310 const double phi = 0.618033988749894; // 1/(golden ratio) 1311 qreal a = xA; 1312 qreal b = xB; 1313 qreal c = b - (b - a)*phi; 1314 qreal d = a + (b - a)*phi; 1315 1316 while (qAbs(b - a) > eps) { 1317 if (f(c) < f(d)) { 1318 b = d; 1319 } else { 1320 a = c; 1321 } 1322 1323 // Wikipedia says to recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop 1324 c = b - (b - a) * phi; 1325 d = a + (b - a) * phi; 1326 1327 i++; 1328 if (i > maxIter) { 1329 break; 1330 } 1331 1332 } 1333 1334 return (b + a)/2; 1335 } 1336 1337 qreal findMinimumTernarySection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100) 1338 { 1339 // requirements: 1340 // only one local minimum between xA and xB 1341 1342 int i = 0; 1343 qreal l = qMin(xA, xB); 1344 qreal r = qMax(xA, xB); 1345 1346 1347 qreal m1 = l + (r - l)/3; 1348 qreal m2 = r - (r - l)/3; 1349 1350 while ((r - l) > eps) { 1351 qreal f1 = f(m1); 1352 qreal f2 = f(m2); 1353 1354 if (f1 > f2) { 1355 l = m1; 1356 } else { 1357 r = m2; 1358 } 1359 1360 // In Golden Section, Wikipedia says to recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop 1361 // so it's probably a good idea to do it here too 1362 m1 = l + (r - l)/3; 1363 m2 = r - (r - l)/3; 1364 1365 i++; 1366 1367 if (i > maxIter) { 1368 break; 1369 } 1370 } 1371 1372 return (l + r)/2; 1373 } 1374 1375 }