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