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 #ifndef __KIS_ALGEBRA_2D_H 0008 #define __KIS_ALGEBRA_2D_H 0009 0010 #include <QPoint> 0011 #include <QPointF> 0012 #include <QVector> 0013 #include <QPolygonF> 0014 #include <QTransform> 0015 #include <cmath> 0016 #include <kis_global.h> 0017 #include <kritaglobal_export.h> 0018 #include <functional> 0019 #include <boost/optional.hpp> 0020 0021 class QPainterPath; 0022 class QTransform; 0023 0024 namespace KisAlgebra2D { 0025 0026 template <class T> 0027 struct PointTypeTraits 0028 { 0029 }; 0030 0031 template <> 0032 struct PointTypeTraits<QPoint> 0033 { 0034 typedef int value_type; 0035 typedef qreal calculation_type; 0036 typedef QRect rect_type; 0037 }; 0038 0039 template <> 0040 struct PointTypeTraits<QPointF> 0041 { 0042 typedef qreal value_type; 0043 typedef qreal calculation_type; 0044 typedef QRectF rect_type; 0045 }; 0046 0047 0048 template <class T> 0049 typename PointTypeTraits<T>::value_type dotProduct(const T &a, const T &b) 0050 { 0051 return a.x() * b.x() + a.y() * b.y(); 0052 } 0053 0054 template <class T> 0055 typename PointTypeTraits<T>::value_type crossProduct(const T &a, const T &b) 0056 { 0057 return a.x() * b.y() - a.y() * b.x(); 0058 } 0059 0060 template <class T> 0061 qreal norm(const T &a) 0062 { 0063 return std::sqrt(pow2(a.x()) + pow2(a.y())); 0064 } 0065 0066 template <class Point> 0067 Point normalize(const Point &a) 0068 { 0069 const qreal length = norm(a); 0070 return (1.0 / length) * a; 0071 } 0072 0073 template <typename Point> 0074 Point lerp(const Point &pt1, const Point &pt2, qreal t) 0075 { 0076 return pt1 + (pt2 - pt1) * t; 0077 } 0078 0079 /** 0080 * Usual sign() function with positive zero 0081 */ 0082 template <typename T> 0083 T signPZ(T x) { 0084 return x >= T(0) ? T(1) : T(-1); 0085 } 0086 0087 /** 0088 * Usual sign() function with zero returning zero 0089 */ 0090 template <typename T> 0091 T signZZ(T x) { 0092 return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1); 0093 } 0094 0095 /** 0096 * Copies the sign of \p y into \p x and returns the result 0097 */ 0098 template <typename T> 0099 inline T copysign(T x, T y) { 0100 T strippedX = qAbs(x); 0101 return y >= T(0) ? strippedX : -strippedX; 0102 } 0103 0104 template<class T> 0105 typename std::enable_if<std::is_integral<T>::value, T>::type 0106 divideFloor(T a, T b) 0107 { 0108 const bool a_neg = a < T(0); 0109 const bool b_neg = b < T(0); 0110 0111 if (a == T(0)) { 0112 return 0; 0113 } else if (a_neg == b_neg) { 0114 return a / b; 0115 } else { 0116 const T a_abs = qAbs(a); 0117 const T b_abs = qAbs(b); 0118 0119 return - 1 - (a_abs - T(1)) / b_abs; 0120 } 0121 } 0122 0123 template <class T> 0124 T leftUnitNormal(const T &a) 0125 { 0126 T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0); 0127 qreal length = norm(result); 0128 result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length; 0129 0130 return -result; 0131 } 0132 0133 template <class T> 0134 T rightUnitNormal(const T &a) 0135 { 0136 return -leftUnitNormal(a); 0137 } 0138 0139 template <class T> 0140 T inwardUnitNormal(const T &a, int polygonDirection) 0141 { 0142 return polygonDirection * leftUnitNormal(a); 0143 } 0144 0145 /** 0146 * \return 1 if the polygon is counterclockwise 0147 * -1 if the polygon is clockwise 0148 * 0149 * Note: the sign is flipped because our 0y axis 0150 * is reversed 0151 */ 0152 template <class T> 0153 int polygonDirection(const QVector<T> &polygon) { 0154 0155 typename PointTypeTraits<T>::value_type doubleSum = 0; 0156 0157 const int numPoints = polygon.size(); 0158 for (int i = 1; i <= numPoints; i++) { 0159 int prev = i - 1; 0160 int next = i == numPoints ? 0 : i; 0161 0162 doubleSum += 0163 (polygon[next].x() - polygon[prev].x()) * 0164 (polygon[next].y() + polygon[prev].y()); 0165 } 0166 0167 return doubleSum >= 0 ? 1 : -1; 0168 } 0169 0170 template <typename T> 0171 bool isInRange(T x, T a, T b) { 0172 T length = qAbs(a - b); 0173 return qAbs(x - a) <= length && qAbs(x - b) <= length; 0174 } 0175 0176 void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt); 0177 0178 /** 0179 * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled 0180 * and then rotated into \p base2 using transformation matrix S * 0181 * R. The function applies the same transformation to \pt and returns 0182 * the result. 0183 **/ 0184 QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2); 0185 0186 qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2); 0187 0188 /** 0189 * Computes an angle indicating the direction from p1 to p2. If p1 and p2 are too close together to 0190 * compute an angle, defaultAngle is returned. 0191 */ 0192 qreal KRITAGLOBAL_EXPORT directionBetweenPoints(const QPointF &p1, const QPointF &p2, 0193 qreal defaultAngle); 0194 0195 namespace Private { 0196 inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) { 0197 *rc = QRect(pt, QSize(1, 1)); 0198 } 0199 0200 inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) { 0201 static const qreal eps = 1e-10; 0202 *rc = QRectF(pt, QSizeF(eps, eps)); 0203 } 0204 } 0205 0206 template <class Point, class Rect> 0207 inline void accumulateBounds(const Point &pt, Rect *bounds) 0208 { 0209 if (bounds->isEmpty()) { 0210 Private::resetEmptyRectangle(pt, bounds); 0211 } 0212 0213 if (pt.x() > bounds->right()) { 0214 bounds->setRight(pt.x()); 0215 } 0216 0217 if (pt.x() < bounds->left()) { 0218 bounds->setLeft(pt.x()); 0219 } 0220 0221 if (pt.y() > bounds->bottom()) { 0222 bounds->setBottom(pt.y()); 0223 } 0224 0225 if (pt.y() < bounds->top()) { 0226 bounds->setTop(pt.y()); 0227 } 0228 } 0229 0230 template <template <class T> class Container, class Point, class Rect> 0231 inline void accumulateBounds(const Container<Point> &points, Rect *bounds) 0232 { 0233 Q_FOREACH (const Point &pt, points) { 0234 accumulateBounds(pt, bounds); 0235 } 0236 } 0237 0238 template <template <class T> class Container, class Point> 0239 inline typename PointTypeTraits<Point>::rect_type 0240 accumulateBounds(const Container<Point> &points) 0241 { 0242 typename PointTypeTraits<Point>::rect_type result; 0243 0244 Q_FOREACH (const Point &pt, points) { 0245 accumulateBounds(pt, &result); 0246 } 0247 0248 return result; 0249 } 0250 0251 template <class Point, class Rect> 0252 inline Point clampPoint(Point pt, const Rect &bounds) 0253 { 0254 if (pt.x() > bounds.right()) { 0255 pt.rx() = bounds.right(); 0256 } 0257 0258 if (pt.x() < bounds.left()) { 0259 pt.rx() = bounds.left(); 0260 } 0261 0262 if (pt.y() > bounds.bottom()) { 0263 pt.ry() = bounds.bottom(); 0264 } 0265 0266 if (pt.y() < bounds.top()) { 0267 pt.ry() = bounds.top(); 0268 } 0269 0270 return pt; 0271 } 0272 0273 template <class Size> 0274 auto maxDimension(Size size) -> decltype(size.width()) { 0275 return qMax(size.width(), size.height()); 0276 } 0277 0278 template <class Size> 0279 auto minDimension(Size size) -> decltype(size.width()) { 0280 return qMin(size.width(), size.height()); 0281 } 0282 0283 QPainterPath KRITAGLOBAL_EXPORT smallArrow(); 0284 0285 /** 0286 * Multiply width and height of \p rect by \p coeff keeping the 0287 * center of the rectangle pinned 0288 */ 0289 template <class Rect> 0290 Rect blowRect(const Rect &rect, qreal coeff) 0291 { 0292 typedef decltype(rect.x()) CoordType; 0293 0294 CoordType w = rect.width() * coeff; 0295 CoordType h = rect.height() * coeff; 0296 0297 return rect.adjusted(-w, -h, w, h); 0298 } 0299 0300 QPoint KRITAGLOBAL_EXPORT ensureInRect(QPoint pt, const QRect &bounds); 0301 QPointF KRITAGLOBAL_EXPORT ensureInRect(QPointF pt, const QRectF &bounds); 0302 0303 template <class Rect> 0304 Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size) 0305 { 0306 typedef decltype(Rect().size()) Size; 0307 typedef decltype(Rect().top()) ValueType; 0308 0309 if (rc.width() < size.width() || 0310 rc.height() < size.height()) { 0311 0312 ValueType width = qMax(rc.width(), size.width()); 0313 ValueType height = qMax(rc.height(), size.height()); 0314 0315 rc = Rect(rc.topLeft(), Size(width, height)); 0316 } 0317 0318 return rc; 0319 } 0320 0321 template <class Size> 0322 Size ensureSizeNotSmaller(const Size &size, const Size &bounds) 0323 { 0324 Size result = size; 0325 0326 const auto widthBound = qAbs(bounds.width()); 0327 auto width = result.width(); 0328 if (qAbs(width) < widthBound) { 0329 width = copysign(widthBound, width); 0330 result.setWidth(width); 0331 } 0332 0333 const auto heightBound = qAbs(bounds.height()); 0334 auto height = result.height(); 0335 if (qAbs(height) < heightBound) { 0336 height = copysign(heightBound, height); 0337 result.setHeight(height); 0338 } 0339 0340 return result; 0341 } 0342 0343 /** 0344 * Attempt to intersect a line to the area of the a rectangle. 0345 * 0346 * If the line intersects the rectangle, it will be modified to represent the intersecting line segment and true is returned. 0347 * If the line does not intersect the area, it remains unmodified and false will be returned. 0348 * If the line is fully inside the rectangle, it's considered "intersecting" so true will be returned. 0349 * 0350 * @param line line segment 0351 * @param rect area 0352 * @param extend extend the line to the edges of the rect area even if the line segment fits inside the rectangle 0353 * (so, consider the line to be a line defined by those two points, not a line segment) 0354 * @return true if successful 0355 */ 0356 bool KRITAGLOBAL_EXPORT intersectLineRect(QLineF &line, const QRect rect, bool extend); 0357 0358 /** 0359 * Attempt to intersect a line to the area of the a rectangle. 0360 * 0361 * If the line intersects the rectangle, it will be modified to represent the intersecting line segment and true is returned. 0362 * If the line does not intersect the area, it remains unmodified and false will be returned. 0363 * If the line is fully inside the rectangle, it's considered "intersecting" so true will be returned. 0364 * 0365 * extendFirst and extendSecond parameters allow one to use this function in case of unbounded lines (if both are true), 0366 * line segments (if both are false) or half-lines/rays (if one is true and another is false). 0367 * Note that which point is the "first" and which is the "second" is determined by which is the p1() and which is p2() in QLineF. 0368 * 0369 * @param line line segment 0370 * @param rect area 0371 * @param extendFirst extend the line to the edge of the rect area even if the first point of the line segment lies inside the rectangle 0372 * @param extendSecond extend the line to the edge of the rect area even if the second point of the line segment lies inside the rectangle 0373 * @return true if successful 0374 */ 0375 bool KRITAGLOBAL_EXPORT intersectLineRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond); 0376 0377 // the same but with a convex polygon; uses Cyrus-Beck algorithm 0378 bool KRITAGLOBAL_EXPORT intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond); 0379 0380 /** 0381 * Crop line to rect; if it doesn't intersect, just return an empty line (QLineF()). 0382 * 0383 * This is using intersectLineRect, but with the difference that it doesn't require the user to check the return value. 0384 * It's useful for drawing code, since it let the developer not use `if` before drawing. 0385 * 0386 * If the line intersects the rectangle, it will be modified to represent the intersecting line segment. 0387 * If the line does not intersect the area, it will return an empty one-point line. 0388 * 0389 * extendFirst and extendSecond parameters allow one to use this function in case of unbounded lines (if both are true), 0390 * line segments (if both are false) or half-lines/rays (if one is true and another is false). 0391 * Note that which point is the "first" and which is the "second" is determined by which is the p1() and which is p2() in QLineF. 0392 * 0393 * @param line line segment 0394 * @param rect area 0395 * @param extendFirst extend the line to the edge of the rect area even if the first point of the line segment lies inside the rectangle 0396 * @param extendSecond extend the line to the edge of the rect area even if the second point of the line segment lies inside the rectangle 0397 */ 0398 void KRITAGLOBAL_EXPORT cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond); 0399 0400 void KRITAGLOBAL_EXPORT cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond); 0401 0402 0403 0404 0405 template <class Point> 0406 inline Point abs(const Point &pt) { 0407 return Point(qAbs(pt.x()), qAbs(pt.y())); 0408 } 0409 0410 template<typename T, typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr> 0411 inline T wrapValue(T value, T wrapBounds) { 0412 value %= wrapBounds; 0413 if (value < 0) { 0414 value += wrapBounds; 0415 } 0416 return value; 0417 } 0418 0419 template<typename T, typename std::enable_if<std::is_floating_point<T>::value, T>::type* = nullptr> 0420 inline T wrapValue(T value, T wrapBounds) { 0421 value = std::fmod(value, wrapBounds); 0422 if (value < 0) { 0423 value += wrapBounds; 0424 } 0425 return value; 0426 } 0427 0428 template<typename T, typename std::enable_if<std::is_same<decltype(T().x()), decltype(T().y())>::value, T>::type* = nullptr> 0429 inline T wrapValue(T value, T wrapBounds) { 0430 value.rx() = wrapValue(value.x(), wrapBounds.x()); 0431 value.ry() = wrapValue(value.y(), wrapBounds.y()); 0432 return value; 0433 } 0434 0435 template<typename T> 0436 inline T wrapValue(T value, T min, T max) { 0437 return wrapValue(value - min, max - min) + min; 0438 } 0439 0440 class RightHalfPlane { 0441 public: 0442 0443 RightHalfPlane(const QPointF &a, const QPointF &b) 0444 : m_a(a), m_p(b - a), m_norm_p_inv(1.0 / norm(m_p)) 0445 { 0446 } 0447 0448 RightHalfPlane(const QLineF &line) 0449 : RightHalfPlane(line.p1(), line.p2()) 0450 { 0451 } 0452 0453 qreal valueSq(const QPointF &pt) const { 0454 const qreal val = value(pt); 0455 return signZZ(val) * pow2(val); 0456 } 0457 0458 qreal value(const QPointF &pt) const { 0459 return crossProduct(m_p, pt - m_a) * m_norm_p_inv; 0460 } 0461 0462 int pos(const QPointF &pt) const { 0463 return signZZ(value(pt)); 0464 } 0465 0466 QLineF getLine() const { 0467 return QLineF(m_a, m_a + m_p); 0468 } 0469 0470 private: 0471 const QPointF m_a; 0472 const QPointF m_p; 0473 const qreal m_norm_p_inv; 0474 }; 0475 0476 class OuterCircle { 0477 public: 0478 0479 OuterCircle(const QPointF &c, qreal radius) 0480 : m_c(c), 0481 m_radius(radius), 0482 m_radius_sq(pow2(radius)), 0483 m_fadeCoeff(1.0 / (pow2(radius + 1.0) - m_radius_sq)) 0484 { 0485 } 0486 0487 qreal valueSq(const QPointF &pt) const { 0488 const qreal val = value(pt); 0489 0490 return signZZ(val) * pow2(val); 0491 } 0492 0493 qreal value(const QPointF &pt) const { 0494 return kisDistance(pt, m_c) - m_radius; 0495 } 0496 0497 int pos(const QPointF &pt) const { 0498 return signZZ(valueSq(pt)); 0499 } 0500 0501 qreal fadeSq(const QPointF &pt) const { 0502 const qreal valSq = kisSquareDistance(pt, m_c); 0503 return (valSq - m_radius_sq) * m_fadeCoeff; 0504 } 0505 0506 private: 0507 const QPointF m_c; 0508 const qreal m_radius; 0509 const qreal m_radius_sq; 0510 const qreal m_fadeCoeff; 0511 }; 0512 0513 QVector<QPoint> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRect &rect); 0514 QVector<QPointF> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRectF &rect); 0515 0516 QRect KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPoint> &points); 0517 QRectF KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPointF> &points); 0518 0519 QRect KRITAGLOBAL_EXPORT approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func); 0520 0521 0522 /** 0523 * Cuts off a portion of a rect \p rc defined by a half-plane \p p 0524 * \return the bounding rect of the resulting polygon 0525 */ 0526 KRITAGLOBAL_EXPORT 0527 QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p); 0528 0529 0530 /** 0531 * Solves a quadratic equation in a form: 0532 * 0533 * a * x^2 + b * x + c = 0 0534 * 0535 * WARNING: Please note that \p a *must* be nonzero! Otherwise the 0536 * equation is not quadratic! And this function doesn't check that! 0537 * 0538 * \return the number of solutions. It can be 0, 1 or 2. 0539 * 0540 * \p x1, \p x2 --- the found solution. The variables are filled with 0541 * data iff the corresponding solution is found. That 0542 * is: 0 solutions --- variabled are not touched, 1 0543 * solution --- x1 is filled with the result, 2 0544 * solutions --- x1 and x2 are filled. 0545 */ 0546 KRITAGLOBAL_EXPORT 0547 int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2); 0548 0549 /** 0550 * Finds the points of intersections between two circles 0551 * \return the found circles, the result can have 0, 1 or 2 points 0552 */ 0553 KRITAGLOBAL_EXPORT 0554 QVector<QPointF> intersectTwoCircles(const QPointF &c1, qreal r1, 0555 const QPointF &c2, qreal r2); 0556 0557 KRITAGLOBAL_EXPORT 0558 QTransform mapToRect(const QRectF &rect); 0559 0560 KRITAGLOBAL_EXPORT 0561 QTransform mapToRectInverse(const QRectF &rect); 0562 0563 /** 0564 * Scale the relative point \pt into the bounds of \p rc. The point might be 0565 * outside the rectangle. 0566 */ 0567 inline QPointF relativeToAbsolute(const QPointF &pt, const QRectF &rc) { 0568 return rc.topLeft() + QPointF(pt.x() * rc.width(), pt.y() * rc.height()); 0569 } 0570 0571 /** 0572 * Get the relative position of \p pt inside rectangle \p rc. The point can be 0573 * outside the rectangle. 0574 */ 0575 inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) { 0576 const QPointF rel = pt - rc.topLeft(); 0577 return QPointF(rc.width() > 0 ? rel.x() / rc.width() : 0, 0578 rc.height() > 0 ? rel.y() / rc.height() : 0); 0579 0580 } 0581 0582 /** 0583 * Scales relative isotropic value from relative to absolute coordinate system 0584 * using SVG 1.1 rules (see chapter 7.10) 0585 */ 0586 inline qreal relativeToAbsolute(qreal value, const QRectF &rc) { 0587 const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0); 0588 return value * coeff; 0589 } 0590 0591 /** 0592 * Scales absolute isotropic value from absolute to relative coordinate system 0593 * using SVG 1.1 rules (see chapter 7.10) 0594 */ 0595 inline qreal absoluteToRelative(const qreal value, const QRectF &rc) { 0596 const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0); 0597 return coeff != 0 ? value / coeff : 0; 0598 } 0599 0600 /** 0601 * Scales relative isotropic value from relative to absolute coordinate system 0602 * using SVG 1.1 rules (see chapter 7.10) 0603 */ 0604 inline QRectF relativeToAbsolute(const QRectF &rel, const QRectF &rc) { 0605 return QRectF(relativeToAbsolute(rel.topLeft(), rc), relativeToAbsolute(rel.bottomRight(), rc)); 0606 } 0607 0608 /** 0609 * Scales absolute isotropic value from absolute to relative coordinate system 0610 * using SVG 1.1 rules (see chapter 7.10) 0611 */ 0612 inline QRectF absoluteToRelative(const QRectF &rel, const QRectF &rc) { 0613 return QRectF(absoluteToRelative(rel.topLeft(), rc), absoluteToRelative(rel.bottomRight(), rc)); 0614 } 0615 0616 /** 0617 * Compare the matrices with tolerance \p delta 0618 */ 0619 bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta); 0620 0621 /** 0622 * Returns true if the two points are equal within some tolerance, where the tolerance is determined 0623 * by Qt's built-in fuzzy comparison functions. 0624 */ 0625 bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2); 0626 0627 /** 0628 * Returns true if the two points are equal within the specified tolerance 0629 */ 0630 bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta); 0631 0632 /** 0633 * Returns true if points in two containers are equal with specified tolerance 0634 */ 0635 template <template<typename> class Cont, class Point> 0636 bool fuzzyPointCompare(const Cont<Point> &c1, const Cont<Point> &c2, qreal delta) 0637 { 0638 if (c1.size() != c2.size()) return false; 0639 0640 const qreal eps = delta; 0641 0642 return std::mismatch(c1.cbegin(), 0643 c1.cend(), 0644 c2.cbegin(), 0645 [eps] (const QPointF &pt1, const QPointF &pt2) { 0646 return fuzzyPointCompare(pt1, pt2, eps); 0647 }) 0648 .first == c1.cend(); 0649 } 0650 0651 /** 0652 * Compare two rectangles with tolerance \p tolerance. The tolerance means that the 0653 * coordinates of top left and bottom right corners should not differ more than \p tolerance 0654 * pixels. 0655 */ 0656 template<class Rect, typename Difference = decltype(Rect::width())> 0657 bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance) { 0658 typedef decltype(r1.topLeft()) Point; 0659 0660 const Point d1 = abs(r1.topLeft() - r2.topLeft()); 0661 const Point d2 = abs(r1.bottomRight() - r2.bottomRight()); 0662 0663 const Difference maxError = std::max({d1.x(), d1.y(), d2.x(), d2.y()}); 0664 return maxError < tolerance; 0665 } 0666 0667 struct KRITAGLOBAL_EXPORT DecomposedMatix { 0668 DecomposedMatix(); 0669 0670 DecomposedMatix(const QTransform &t0); 0671 0672 inline QTransform scaleTransform() const 0673 { 0674 return QTransform::fromScale(scaleX, scaleY); 0675 } 0676 0677 inline QTransform shearTransform() const 0678 { 0679 QTransform t; 0680 t.shear(shearXY, 0); 0681 return t; 0682 } 0683 0684 inline QTransform rotateTransform() const 0685 { 0686 QTransform t; 0687 t.rotate(angle); 0688 return t; 0689 } 0690 0691 inline QTransform translateTransform() const 0692 { 0693 return QTransform::fromTranslate(dx, dy); 0694 } 0695 0696 inline QTransform projectTransform() const 0697 { 0698 return 0699 QTransform( 0700 1,0,proj[0], 0701 0,1,proj[1], 0702 0,0,proj[2]); 0703 } 0704 0705 inline QTransform transform() const { 0706 return 0707 scaleTransform() * 0708 shearTransform() * 0709 rotateTransform() * 0710 translateTransform() * 0711 projectTransform(); 0712 } 0713 0714 inline bool isValid() const { 0715 return valid; 0716 } 0717 0718 qreal scaleX = 1.0; 0719 qreal scaleY = 1.0; 0720 qreal shearXY = 0.0; 0721 qreal angle = 0.0; 0722 qreal dx = 0.0; 0723 qreal dy = 0.0; 0724 qreal proj[3] = {0.0, 0.0, 1.0}; 0725 0726 private: 0727 bool valid = true; 0728 }; 0729 0730 // NOTE: tiar: this seems to ignore perspective transformation, be wary and use the class in PerspectiveEllipseAssistant.cpp instead 0731 // this is only good for rotation, translation and possibly shear 0732 std::pair<QPointF, QTransform> KRITAGLOBAL_EXPORT transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal); 0733 0734 QPointF KRITAGLOBAL_EXPORT alignForZoom(const QPointF &pt, qreal zoom); 0735 0736 0737 /** 0738 * Linearly reshape function \p x so that in range [x0, x1] 0739 * it would cross points (x0, y0) and (x1, y1). 0740 */ 0741 template <typename T> 0742 inline T linearReshapeFunc(T x, T x0, T x1, T y0, T y1) 0743 { 0744 return y0 + (y1 - y0) * (x - x0) / (x1 - x0); 0745 } 0746 0747 0748 /** 0749 * Find intersection of a bounded line \p boundedLine with unbounded 0750 * line \p unboundedLine (if an intersection exists) 0751 */ 0752 KRITAGLOBAL_EXPORT 0753 boost::optional<QPointF> intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine); 0754 0755 0756 /** 0757 * Find intersection of a bounded line \p p1, \p p2 with unbounded 0758 * line \p q1, \p q2 (if an intersection exists) 0759 */ 0760 KRITAGLOBAL_EXPORT 0761 boost::optional<QPointF> intersectLines(const QPointF &p1, const QPointF &p2, 0762 const QPointF &q1, const QPointF &q2); 0763 0764 /** 0765 * Find possible positions for point p3, such that points \p1, \p2 and p3 form 0766 * a triangle, such that the distance between p1 ad p3 is \p a and the distance 0767 * between p2 and p3 is b. There might be 0, 1 or 2 such positions. 0768 */ 0769 QVector<QPointF> KRITAGLOBAL_EXPORT findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b); 0770 0771 /** 0772 * Find a point p3 that forms a triangle with \p1 and \p2 and is the nearest 0773 * possible point to \p nearest 0774 * 0775 * \see findTrianglePoint 0776 */ 0777 boost::optional<QPointF> KRITAGLOBAL_EXPORT findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest); 0778 0779 /** 0780 * @brief moveElasticPoint moves point \p pt based on the model of elasticity 0781 * @param pt point in question, tied to points \p base, \p wingA and \p wingB 0782 * using springs 0783 * @param base initial position of the dragged point 0784 * @param newBase final position of tht dragged point 0785 * @param wingA first anchor point 0786 * @param wingB second anchor point 0787 * @return the new position of \p pt 0788 * 0789 * The function requires (\p newBase - \p base) be much smaller than any 0790 * of the distances between other points. If larger distances are necessary, 0791 * then use integration. 0792 */ 0793 QPointF KRITAGLOBAL_EXPORT moveElasticPoint(const QPointF &pt, 0794 const QPointF &base, const QPointF &newBase, 0795 const QPointF &wingA, const QPointF &wingB); 0796 0797 /** 0798 * @brief moveElasticPoint moves point \p pt based on the model of elasticity 0799 * @param pt point in question, tied to points \p base, \p anchorPoints 0800 * using springs 0801 * @param base initial position of the dragged point 0802 * @param newBase final position of tht dragged point 0803 * @param anchorPoints anchor points 0804 * @return the new position of \p pt 0805 * 0806 * The function expects (\p newBase - \p base) be much smaller than any 0807 * of the distances between other points. If larger distances are necessary, 0808 * then use integration. 0809 */ 0810 QPointF KRITAGLOBAL_EXPORT moveElasticPoint(const QPointF &pt, 0811 const QPointF &base, const QPointF &newBase, 0812 const QVector<QPointF> &anchorPoints); 0813 0814 0815 /** 0816 * @brief a simple class to generate Halton sequence 0817 * 0818 * This sequence of numbers can be used to sample areas 0819 * in somewhat uniform way. See Wikipedia for more info: 0820 * 0821 * https://en.wikipedia.org/wiki/Halton_sequence 0822 */ 0823 0824 class HaltonSequenceGenerator 0825 { 0826 public: 0827 HaltonSequenceGenerator(int base) 0828 : m_base(base) 0829 { 0830 } 0831 0832 int generate(int maxRange) { 0833 generationStep(); 0834 return (m_n * maxRange + m_d / 2) / m_d; 0835 } 0836 0837 qreal generate() { 0838 generationStep(); 0839 return qreal(m_n) / m_d; 0840 } 0841 0842 private: 0843 inline void generationStep() { 0844 int x = m_d - m_n; 0845 0846 if (x == 1) { 0847 m_n = 1; 0848 m_d *= m_base; 0849 } else { 0850 int y = m_d / m_base; 0851 while (x <= y) { 0852 y /= m_base; 0853 } 0854 m_n = (m_base + 1) * y - x; 0855 } 0856 } 0857 0858 private: 0859 int m_n = 0; 0860 int m_d = 1; 0861 const int m_base = 0; 0862 }; 0863 0864 0865 // find minimum of the function f(x) between points xA and xB, with eps precision, using Golden Ratio Section 0866 // requirements: only one local minimum between xA and xB 0867 // Golden Section is supposed to be usually faster than Ternary Section 0868 // NOTE: tiar: this function was debugged and should be working correctly but is not used anywhere any longer 0869 qreal findMinimumGoldenSection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter); 0870 0871 // find minimum of the function f(x) between points xA and xB, with eps precision, using Ternary Section 0872 // requirements: only one local minimum between xA and xB 0873 // Golden Section is supposed to be usually faster than Ternary Section 0874 // NOTE: tiar: this function was debugged and should be working correctly but is not used anywhere any longer 0875 qreal findMinimumTernarySection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter); 0876 0877 0878 } 0879 0880 0881 #endif /* __KIS_ALGEBRA_2D_H */