1 /* 2 * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 */ 18 19 #ifndef __KIS_ALGEBRA_2D_H 20 #define __KIS_ALGEBRA_2D_H 21 22 #include <QPoint> 23 #include <QPointF> 24 #include <QVector> 25 #include <QPolygonF> 26 #include <QTransform> 27 #include <cmath> 28 #include <kis_global.h> 29 #include <kritaglobal_export.h> 30 #include <functional> 31 #include <boost/optional.hpp> 32 33 class QPainterPath; 34 class QTransform; 35 36 namespace KisAlgebra2D { 37 38 template <class T> 39 struct PointTypeTraits 40 { 41 }; 42 43 template <> 44 struct PointTypeTraits<QPoint> 45 { 46 typedef int value_type; 47 typedef qreal calculation_type; 48 typedef QRect rect_type; 49 }; 50 51 template <> 52 struct PointTypeTraits<QPointF> 53 { 54 typedef qreal value_type; 55 typedef qreal calculation_type; 56 typedef QRectF rect_type; 57 }; 58 59 60 template <class T> 61 typename PointTypeTraits<T>::value_type dotProduct(const T &a, const T &b) 62 { 63 return a.x() * b.x() + a.y() * b.y(); 64 } 65 66 template <class T> 67 typename PointTypeTraits<T>::value_type crossProduct(const T &a, const T &b) 68 { 69 return a.x() * b.y() - a.y() * b.x(); 70 } 71 72 template <class T> 73 qreal norm(const T &a) 74 { 75 return std::sqrt(pow2(a.x()) + pow2(a.y())); 76 } 77 78 template <class Point> 79 Point normalize(const Point &a) 80 { 81 const qreal length = norm(a); 82 return (1.0 / length) * a; 83 } 84 85 template <typename Point> 86 Point lerp(const Point &pt1, const Point &pt2, qreal t) 87 { 88 return pt1 + (pt2 - pt1) * t; 89 } 90 91 /** 92 * Usual sign() function with positive zero 93 */ 94 template <typename T> 95 T signPZ(T x) { 96 return x >= T(0) ? T(1) : T(-1); 97 } 98 99 /** 100 * Usual sign() function with zero returning zero 101 */ 102 template <typename T> 103 T signZZ(T x) { 104 return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1); 105 } 106 107 /** 108 * Copies the sign of \p y into \p x and returns the result 109 */ 110 template <typename T> 111 inline T copysign(T x, T y) { 112 T strippedX = qAbs(x); 113 return y >= T(0) ? strippedX : -strippedX; 114 } 115 116 template<class T> 117 typename std::enable_if<std::is_integral<T>::value, T>::type 118 divideFloor(T a, T b) 119 { 120 const bool a_neg = a < T(0); 121 const bool b_neg = b < T(0); 122 123 if (a == T(0)) { 124 return 0; 125 } else if (a_neg == b_neg) { 126 return a / b; 127 } else { 128 const T a_abs = qAbs(a); 129 const T b_abs = qAbs(b); 130 131 return - 1 - (a_abs - T(1)) / b_abs; 132 } 133 } 134 135 template <class T> 136 T leftUnitNormal(const T &a) 137 { 138 T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0); 139 qreal length = norm(result); 140 result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length; 141 142 return -result; 143 } 144 145 template <class T> 146 T rightUnitNormal(const T &a) 147 { 148 return -leftUnitNormal(a); 149 } 150 151 template <class T> 152 T inwardUnitNormal(const T &a, int polygonDirection) 153 { 154 return polygonDirection * leftUnitNormal(a); 155 } 156 157 /** 158 * \return 1 if the polygon is counterclockwise 159 * -1 if the polygon is clockwise 160 * 161 * Note: the sign is flipped because our 0y axis 162 * is reversed 163 */ 164 template <class T> 165 int polygonDirection(const QVector<T> &polygon) { 166 167 typename PointTypeTraits<T>::value_type doubleSum = 0; 168 169 const int numPoints = polygon.size(); 170 for (int i = 1; i <= numPoints; i++) { 171 int prev = i - 1; 172 int next = i == numPoints ? 0 : i; 173 174 doubleSum += 175 (polygon[next].x() - polygon[prev].x()) * 176 (polygon[next].y() + polygon[prev].y()); 177 } 178 179 return doubleSum >= 0 ? 1 : -1; 180 } 181 182 template <typename T> 183 bool isInRange(T x, T a, T b) { 184 T length = qAbs(a - b); 185 return qAbs(x - a) <= length && qAbs(x - b) <= length; 186 } 187 188 void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt); 189 190 /** 191 * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled 192 * and then rotated into \p base2 using transformation matrix S * 193 * R. The function applies the same transformation to \pt and returns 194 * the result. 195 **/ 196 QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2); 197 198 qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2); 199 200 /** 201 * Computes an angle indicating the direction from p1 to p2. If p1 and p2 are too close together to 202 * compute an angle, defaultAngle is returned. 203 */ 204 qreal KRITAGLOBAL_EXPORT directionBetweenPoints(const QPointF &p1, const QPointF &p2, 205 qreal defaultAngle); 206 207 namespace Private { 208 inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) { 209 *rc = QRect(pt, QSize(1, 1)); 210 } 211 212 inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) { 213 static const qreal eps = 1e-10; 214 *rc = QRectF(pt, QSizeF(eps, eps)); 215 } 216 } 217 218 template <class Point, class Rect> 219 inline void accumulateBounds(const Point &pt, Rect *bounds) 220 { 221 if (bounds->isEmpty()) { 222 Private::resetEmptyRectangle(pt, bounds); 223 } 224 225 if (pt.x() > bounds->right()) { 226 bounds->setRight(pt.x()); 227 } 228 229 if (pt.x() < bounds->left()) { 230 bounds->setLeft(pt.x()); 231 } 232 233 if (pt.y() > bounds->bottom()) { 234 bounds->setBottom(pt.y()); 235 } 236 237 if (pt.y() < bounds->top()) { 238 bounds->setTop(pt.y()); 239 } 240 } 241 242 template <template <class T> class Container, class Point, class Rect> 243 inline void accumulateBounds(const Container<Point> &points, Rect *bounds) 244 { 245 Q_FOREACH (const Point &pt, points) { 246 accumulateBounds(pt, bounds); 247 } 248 } 249 250 template <template <class T> class Container, class Point> 251 inline typename PointTypeTraits<Point>::rect_type 252 accumulateBounds(const Container<Point> &points) 253 { 254 typename PointTypeTraits<Point>::rect_type result; 255 256 Q_FOREACH (const Point &pt, points) { 257 accumulateBounds(pt, &result); 258 } 259 260 return result; 261 } 262 263 template <class Point, class Rect> 264 inline Point clampPoint(Point pt, const Rect &bounds) 265 { 266 if (pt.x() > bounds.right()) { 267 pt.rx() = bounds.right(); 268 } 269 270 if (pt.x() < bounds.left()) { 271 pt.rx() = bounds.left(); 272 } 273 274 if (pt.y() > bounds.bottom()) { 275 pt.ry() = bounds.bottom(); 276 } 277 278 if (pt.y() < bounds.top()) { 279 pt.ry() = bounds.top(); 280 } 281 282 return pt; 283 } 284 285 template <class Size> 286 auto maxDimension(Size size) -> decltype(size.width()) { 287 return qMax(size.width(), size.height()); 288 } 289 290 template <class Size> 291 auto minDimension(Size size) -> decltype(size.width()) { 292 return qMin(size.width(), size.height()); 293 } 294 295 QPainterPath KRITAGLOBAL_EXPORT smallArrow(); 296 297 /** 298 * Multiply width and height of \p rect by \p coeff keeping the 299 * center of the rectangle pinned 300 */ 301 template <class Rect> 302 Rect blowRect(const Rect &rect, qreal coeff) 303 { 304 typedef decltype(rect.x()) CoordType; 305 306 CoordType w = rect.width() * coeff; 307 CoordType h = rect.height() * coeff; 308 309 return rect.adjusted(-w, -h, w, h); 310 } 311 312 QPoint KRITAGLOBAL_EXPORT ensureInRect(QPoint pt, const QRect &bounds); 313 QPointF KRITAGLOBAL_EXPORT ensureInRect(QPointF pt, const QRectF &bounds); 314 315 template <class Rect> 316 Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size) 317 { 318 typedef decltype(Rect().size()) Size; 319 typedef decltype(Rect().top()) ValueType; 320 321 if (rc.width() < size.width() || 322 rc.height() < size.height()) { 323 324 ValueType width = qMax(rc.width(), size.width()); 325 ValueType height = qMax(rc.height(), size.height()); 326 327 rc = Rect(rc.topLeft(), Size(width, height)); 328 } 329 330 return rc; 331 } 332 333 template <class Size> 334 Size ensureSizeNotSmaller(const Size &size, const Size &bounds) 335 { 336 Size result = size; 337 338 const auto widthBound = qAbs(bounds.width()); 339 auto width = result.width(); 340 if (qAbs(width) < widthBound) { 341 width = copysign(widthBound, width); 342 result.setWidth(width); 343 } 344 345 const auto heightBound = qAbs(bounds.height()); 346 auto height = result.height(); 347 if (qAbs(height) < heightBound) { 348 height = copysign(heightBound, height); 349 result.setHeight(height); 350 } 351 352 return result; 353 } 354 355 356 /** 357 * Attempt to intersect a line to the area of the a rectangle. 358 * 359 * If the line intersects the rectangle, it will be modified to represent the intersecting line segment and true is returned. 360 * If the line does not intersect the area, it remains unmodified and false will be returned. 361 * 362 * @param segment 363 * @param area 364 * @return true if successful 365 */ 366 bool KRITAGLOBAL_EXPORT intersectLineRect(QLineF &line, const QRect rect); 367 368 369 template <class Point> 370 inline Point abs(const Point &pt) { 371 return Point(qAbs(pt.x()), qAbs(pt.y())); 372 } 373 374 template<typename T, typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr> 375 inline T wrapValue(T value, T wrapBounds) { 376 value %= wrapBounds; 377 if (value < 0) { 378 value += wrapBounds; 379 } 380 return value; 381 } 382 383 template<typename T, typename std::enable_if<std::is_floating_point<T>::value, T>::type* = nullptr> 384 inline T wrapValue(T value, T wrapBounds) { 385 value = std::fmod(value, wrapBounds); 386 if (value < 0) { 387 value += wrapBounds; 388 } 389 return value; 390 } 391 392 template<typename T, typename std::enable_if<std::is_same<decltype(T().x()), decltype(T().y())>::value, T>::type* = nullptr> 393 inline T wrapValue(T value, T wrapBounds) { 394 value.rx() = wrapValue(value.x(), wrapBounds.x()); 395 value.ry() = wrapValue(value.y(), wrapBounds.y()); 396 return value; 397 } 398 399 class RightHalfPlane { 400 public: 401 402 RightHalfPlane(const QPointF &a, const QPointF &b) 403 : m_a(a), m_p(b - a), m_norm_p_inv(1.0 / norm(m_p)) 404 { 405 } 406 407 RightHalfPlane(const QLineF &line) 408 : RightHalfPlane(line.p1(), line.p2()) 409 { 410 } 411 412 qreal valueSq(const QPointF &pt) const { 413 const qreal val = value(pt); 414 return signZZ(val) * pow2(val); 415 } 416 417 qreal value(const QPointF &pt) const { 418 return crossProduct(m_p, pt - m_a) * m_norm_p_inv; 419 } 420 421 int pos(const QPointF &pt) const { 422 return signZZ(value(pt)); 423 } 424 425 QLineF getLine() const { 426 return QLineF(m_a, m_a + m_p); 427 } 428 429 private: 430 const QPointF m_a; 431 const QPointF m_p; 432 const qreal m_norm_p_inv; 433 }; 434 435 class OuterCircle { 436 public: 437 438 OuterCircle(const QPointF &c, qreal radius) 439 : m_c(c), 440 m_radius(radius), 441 m_radius_sq(pow2(radius)), 442 m_fadeCoeff(1.0 / (pow2(radius + 1.0) - m_radius_sq)) 443 { 444 } 445 446 qreal valueSq(const QPointF &pt) const { 447 const qreal val = value(pt); 448 449 return signZZ(val) * pow2(val); 450 } 451 452 qreal value(const QPointF &pt) const { 453 return kisDistance(pt, m_c) - m_radius; 454 } 455 456 int pos(const QPointF &pt) const { 457 return signZZ(valueSq(pt)); 458 } 459 460 qreal fadeSq(const QPointF &pt) const { 461 const qreal valSq = kisSquareDistance(pt, m_c); 462 return (valSq - m_radius_sq) * m_fadeCoeff; 463 } 464 465 private: 466 const QPointF m_c; 467 const qreal m_radius; 468 const qreal m_radius_sq; 469 const qreal m_fadeCoeff; 470 }; 471 472 QVector<QPoint> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRect &rect); 473 QVector<QPointF> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRectF &rect); 474 475 QRect KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPoint> &points); 476 QRectF KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPointF> &points); 477 478 QRect KRITAGLOBAL_EXPORT approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func); 479 480 481 /** 482 * Cuts off a portion of a rect \p rc defined by a half-plane \p p 483 * \return the bounding rect of the resulting polygon 484 */ 485 KRITAGLOBAL_EXPORT 486 QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p); 487 488 489 /** 490 * Solves a quadratic equation in a form: 491 * 492 * a * x^2 + b * x + c = 0 493 * 494 * WARNING: Please note that \p a *must* be nonzero! Otherwise the 495 * equation is not quadratic! And this function doesn't check that! 496 * 497 * \return the number of solutions. It can be 0, 1 or 2. 498 * 499 * \p x1, \p x2 --- the found solution. The variables are filled with 500 * data iff the corresponding solution is found. That 501 * is: 0 solutions --- variabled are not touched, 1 502 * solution --- x1 is filled with the result, 2 503 * solutions --- x1 and x2 are filled. 504 */ 505 KRITAGLOBAL_EXPORT 506 int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2); 507 508 /** 509 * Finds the points of intersections between two circles 510 * \return the found circles, the result can have 0, 1 or 2 points 511 */ 512 KRITAGLOBAL_EXPORT 513 QVector<QPointF> intersectTwoCircles(const QPointF &c1, qreal r1, 514 const QPointF &c2, qreal r2); 515 516 KRITAGLOBAL_EXPORT 517 QTransform mapToRect(const QRectF &rect); 518 519 KRITAGLOBAL_EXPORT 520 QTransform mapToRectInverse(const QRectF &rect); 521 522 /** 523 * Scale the relative point \pt into the bounds of \p rc. The point might be 524 * outside the rectangle. 525 */ 526 inline QPointF relativeToAbsolute(const QPointF &pt, const QRectF &rc) { 527 return rc.topLeft() + QPointF(pt.x() * rc.width(), pt.y() * rc.height()); 528 } 529 530 /** 531 * Get the relative position of \p pt inside rectangle \p rc. The point can be 532 * outside the rectangle. 533 */ 534 inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) { 535 const QPointF rel = pt - rc.topLeft(); 536 return QPointF(rc.width() > 0 ? rel.x() / rc.width() : 0, 537 rc.height() > 0 ? rel.y() / rc.height() : 0); 538 539 } 540 541 /** 542 * Scales relative isotropic value from relative to absolute coordinate system 543 * using SVG 1.1 rules (see chapter 7.10) 544 */ 545 inline qreal relativeToAbsolute(qreal value, const QRectF &rc) { 546 const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0); 547 return value * coeff; 548 } 549 550 /** 551 * Scales absolute isotropic value from absolute to relative coordinate system 552 * using SVG 1.1 rules (see chapter 7.10) 553 */ 554 inline qreal absoluteToRelative(const qreal value, const QRectF &rc) { 555 const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0); 556 return coeff != 0 ? value / coeff : 0; 557 } 558 559 /** 560 * Scales relative isotropic value from relative to absolute coordinate system 561 * using SVG 1.1 rules (see chapter 7.10) 562 */ 563 inline QRectF relativeToAbsolute(const QRectF &rel, const QRectF &rc) { 564 return QRectF(relativeToAbsolute(rel.topLeft(), rc), relativeToAbsolute(rel.bottomRight(), rc)); 565 } 566 567 /** 568 * Scales absolute isotropic value from absolute to relative coordinate system 569 * using SVG 1.1 rules (see chapter 7.10) 570 */ 571 inline QRectF absoluteToRelative(const QRectF &rel, const QRectF &rc) { 572 return QRectF(absoluteToRelative(rel.topLeft(), rc), absoluteToRelative(rel.bottomRight(), rc)); 573 } 574 575 /** 576 * Compare the matrices with tolerance \p delta 577 */ 578 bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta); 579 580 /** 581 * Returns true if the two points are equal within some tolerance, where the tolerance is determined 582 * by Qt's built-in fuzzy comparison functions. 583 */ 584 bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2); 585 586 /** 587 * Returns true if the two points are equal within the specified tolerance 588 */ 589 bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta); 590 591 /** 592 * Returns true if points in two containers are equal with specified tolerance 593 */ 594 template <template<typename> class Cont, class Point> 595 bool fuzzyPointCompare(const Cont<Point> &c1, const Cont<Point> &c2, qreal delta) 596 { 597 if (c1.size() != c2.size()) return false; 598 599 const qreal eps = delta; 600 601 return std::mismatch(c1.cbegin(), 602 c1.cend(), 603 c2.cbegin(), 604 [eps] (const QPointF &pt1, const QPointF &pt2) { 605 return fuzzyPointCompare(pt1, pt2, eps); 606 }) 607 .first == c1.cend(); 608 } 609 610 /** 611 * Compare two rectangles with tolerance \p tolerance. The tolerance means that the 612 * coordinates of top left and bottom right corners should not differ more than \p tolerance 613 * pixels. 614 */ 615 template<class Rect, typename Difference = decltype(Rect::width())> 616 bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance) { 617 typedef decltype(r1.topLeft()) Point; 618 619 const Point d1 = abs(r1.topLeft() - r2.topLeft()); 620 const Point d2 = abs(r1.bottomRight() - r2.bottomRight()); 621 622 const Difference maxError = std::max({d1.x(), d1.y(), d2.x(), d2.y()}); 623 return maxError < tolerance; 624 } 625 626 struct KRITAGLOBAL_EXPORT DecomposedMatix { 627 DecomposedMatix(); 628 629 DecomposedMatix(const QTransform &t0); 630 631 inline QTransform scaleTransform() const 632 { 633 return QTransform::fromScale(scaleX, scaleY); 634 } 635 636 inline QTransform shearTransform() const 637 { 638 QTransform t; 639 t.shear(shearXY, 0); 640 return t; 641 } 642 643 inline QTransform rotateTransform() const 644 { 645 QTransform t; 646 t.rotate(angle); 647 return t; 648 } 649 650 inline QTransform translateTransform() const 651 { 652 return QTransform::fromTranslate(dx, dy); 653 } 654 655 inline QTransform projectTransform() const 656 { 657 return 658 QTransform( 659 1,0,proj[0], 660 0,1,proj[1], 661 0,0,proj[2]); 662 } 663 664 inline QTransform transform() const { 665 return 666 scaleTransform() * 667 shearTransform() * 668 rotateTransform() * 669 translateTransform() * 670 projectTransform(); 671 } 672 673 inline bool isValid() const { 674 return valid; 675 } 676 677 qreal scaleX = 1.0; 678 qreal scaleY = 1.0; 679 qreal shearXY = 0.0; 680 qreal angle = 0.0; 681 qreal dx = 0.0; 682 qreal dy = 0.0; 683 qreal proj[3] = {0.0, 0.0, 1.0}; 684 685 private: 686 bool valid = true; 687 }; 688 689 std::pair<QPointF, QTransform> KRITAGLOBAL_EXPORT transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal); 690 691 QPointF KRITAGLOBAL_EXPORT alignForZoom(const QPointF &pt, qreal zoom); 692 693 694 /** 695 * Linearly reshape function \p x so that in range [x0, x1] 696 * it would cross points (x0, y0) and (x1, y1). 697 */ 698 template <typename T> 699 inline T linearReshapeFunc(T x, T x0, T x1, T y0, T y1) 700 { 701 return y0 + (y1 - y0) * (x - x0) / (x1 - x0); 702 } 703 704 705 /** 706 * Find intersection of a bounded line \p boundedLine with unbounded 707 * line \p unboundedLine (if an intersection exists) 708 */ 709 KRITAGLOBAL_EXPORT 710 boost::optional<QPointF> intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine); 711 712 713 /** 714 * Find intersection of a bounded line \p p1, \p p2 with unbounded 715 * line \p q1, \p q2 (if an intersection exists) 716 */ 717 KRITAGLOBAL_EXPORT 718 boost::optional<QPointF> intersectLines(const QPointF &p1, const QPointF &p2, 719 const QPointF &q1, const QPointF &q2); 720 721 /** 722 * Find possible positions for point p3, such that points \p1, \p2 and p3 form 723 * a triangle, such that the distance between p1 ad p3 is \p a and the distance 724 * between p2 and p3 is b. There might be 0, 1 or 2 such positions. 725 */ 726 QVector<QPointF> KRITAGLOBAL_EXPORT findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b); 727 728 /** 729 * Find a point p3 that forms a triangle with \p1 and \p2 and is the nearest 730 * possible point to \p nearest 731 * 732 * \see findTrianglePoint 733 */ 734 boost::optional<QPointF> KRITAGLOBAL_EXPORT findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest); 735 736 /** 737 * @brief moveElasticPoint moves point \p pt based on the model of elasticity 738 * @param pt point in question, tied to points \p base, \p wingA and \p wingB 739 * using springs 740 * @param base initial position of the dragged point 741 * @param newBase final position of tht dragged point 742 * @param wingA first anchor point 743 * @param wingB second anchor point 744 * @return the new position of \p pt 745 * 746 * The function requires (\p newBase - \p base) be much smaller than any 747 * of the distances between other points. If larger distances are necessary, 748 * then use integration. 749 */ 750 QPointF KRITAGLOBAL_EXPORT moveElasticPoint(const QPointF &pt, 751 const QPointF &base, const QPointF &newBase, 752 const QPointF &wingA, const QPointF &wingB); 753 754 /** 755 * @brief moveElasticPoint moves point \p pt based on the model of elasticity 756 * @param pt point in question, tied to points \p base, \p anchorPoints 757 * using springs 758 * @param base initial position of the dragged point 759 * @param newBase final position of tht dragged point 760 * @param anchorPoints anchor points 761 * @return the new position of \p pt 762 * 763 * The function expects (\p newBase - \p base) be much smaller than any 764 * of the distances between other points. If larger distances are necessary, 765 * then use integration. 766 */ 767 QPointF KRITAGLOBAL_EXPORT moveElasticPoint(const QPointF &pt, 768 const QPointF &base, const QPointF &newBase, 769 const QVector<QPointF> &anchorPoints); 770 771 } 772 773 774 #endif /* __KIS_ALGEBRA_2D_H */ 775