Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_algebra_2d.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "kis_algebra_2d.h"
8
9#include <QTransform>
10#include <QPainterPath>
11#include <kis_debug.h>
12
13#include <boost/accumulators/accumulators.hpp>
14#include <boost/accumulators/statistics/stats.hpp>
15#include <boost/accumulators/statistics/min.hpp>
16#include <boost/accumulators/statistics/max.hpp>
17
18#include <array>
19#include <QVector2D>
20#include <QVector3D>
21
22#include <QtMath>
23
24#include <config-gsl.h>
25
26#ifdef HAVE_GSL
27#include <gsl/gsl_multimin.h>
28#endif /*HAVE_GSL*/
29
30#include <Eigen/Eigenvalues>
31#include <KisBezierUtils.h>
32
33#define SANITY_CHECKS
34
35namespace KisAlgebra2D {
36
37void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
38{
39 const int numPoints = poly.size();
40 for (int i = 0; i < numPoints; i++) {
41 int nextI = i + 1;
42 if (nextI >= numPoints) {
43 nextI = 0;
44 }
45
46 const QPointF &p0 = poly[i];
47 const QPointF &p1 = poly[nextI];
48
49 QPointF edge = p1 - p0;
50
51 qreal cross = crossProduct(edge, *pt - p0)
52 / (0.5 * edge.manhattanLength());
53
54 if (cross < 1.0 &&
55 isInRange(pt->x(), p0.x(), p1.x()) &&
56 isInRange(pt->y(), p0.y(), p1.y())) {
57
58 QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection);
59
60 QPointF adjustedPoint = *pt + salt;
61
62 // in case the polygon is self-intersecting, polygon direction
63 // might not help
64 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
65 adjustedPoint = *pt - salt;
66
67#ifdef SANITY_CHECKS
68 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
69 dbgKrita << ppVar(*pt);
70 dbgKrita << ppVar(adjustedPoint);
71 dbgKrita << ppVar(QLineF(p0, p1));
73
74 dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill));
75
76 dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1)));
77 dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1)));
78 }
79
80 *pt = adjustedPoint;
81
82 KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4);
83#endif /* SANITY_CHECKS */
84 }
85 }
86 }
87}
88
89QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) {
90 qreal len1 = norm(base1);
91 if (len1 < 1e-5) return pt;
92 qreal sin1 = base1.y() / len1;
93 qreal cos1 = base1.x() / len1;
94
95 qreal len2 = norm(base2);
96 if (len2 < 1e-5) return QPointF();
97 qreal sin2 = base2.y() / len2;
98 qreal cos2 = base2.x() / len2;
99
100 qreal sinD = sin2 * cos1 - cos2 * sin1;
101 qreal cosD = cos1 * cos2 + sin1 * sin2;
102 qreal scaleD = len2 / len1;
103
104 QPointF result;
105 result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD);
106 result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD);
107
108 return result;
109}
110
111qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
112{
113 qreal a1 = std::atan2(v1.y(), v1.x());
114 qreal a2 = std::atan2(v2.y(), v2.x());
115
116 return a2 - a1;
117}
118
119qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
120{
121 if (fuzzyPointCompare(p1, p2)) {
122 return defaultAngle;
123 }
124
125 const QVector2D diff(p2 - p1);
126 return std::atan2(diff.y(), diff.x());
127}
128
129QPainterPath smallArrow()
130{
131 QPainterPath p;
132
133 p.moveTo(5, 2);
134 p.lineTo(-3, 8);
135 p.lineTo(-5, 5);
136 p.lineTo( 2, 0);
137 p.lineTo(-5,-5);
138 p.lineTo(-3,-8);
139 p.lineTo( 5,-2);
140 p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
141
142 return p;
143}
144
145template <class Point, class Rect>
146inline Point ensureInRectImpl(Point pt, const Rect &bounds)
147{
148 if (pt.x() > bounds.right()) {
149 pt.rx() = bounds.right();
150 } else if (pt.x() < bounds.left()) {
151 pt.rx() = bounds.left();
152 }
153
154 if (pt.y() > bounds.bottom()) {
155 pt.ry() = bounds.bottom();
156 } else if (pt.y() < bounds.top()) {
157 pt.ry() = bounds.top();
158 }
159
160 return pt;
161}
162
163QPoint ensureInRect(QPoint pt, const QRect &bounds)
164{
165 return ensureInRectImpl(pt, bounds);
166}
167
168QPointF ensureInRect(QPointF pt, const QRectF &bounds)
169{
170 return ensureInRectImpl(pt, bounds);
171}
172
173bool intersectLineRect(QLineF &line, const QRect rect, bool extend)
174{
175 return intersectLineRect(line, rect, extend, extend);
176}
177
178bool intersectLineRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
179{
180 // using Liang-Barsky algorithm
181 // using parametric equation for a line:
182 // X = x1 + t(x2-x1) = x1 + t*p2
183 // Y = y1 + t(y2-y1) = y1 + t*p4
184 // note that we have a line *segment* here, not a line
185 // t1 = 0, t2 = 1, no matter what points we got
186
187 double p1 = -(line.x2() - line.x1());
188 double p2 = -p1;
189 double p3 = -(line.y2() - line.y1());
190 double p4 = -p3;
191
193 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()});
194
195 float tmin = extendFirst ? -std::numeric_limits<float>::infinity() : 0; // line.p1() - first point, or infinity
196 float tmax = extendSecond ? std::numeric_limits<float>::infinity() : 1; // line.p2() - second point, or infinity
197
198 for (int i = 0; i < p.length(); i++) {
199 // now clipping
200 if (p[i] == 0 && q[i] < 0) {
201 // line is parallel to the boundary and outside of it
202 return false;
203 } else if (p[i] < 0) {
204 // line entry point (should be tmin)
205 double t = q[i]/p[i];
206 if (t > tmax) {
207 if (extendSecond) {
208 tmax = t;
209 } else {
210 return false;
211 }
212 }
213 if (t > tmin) {
214 tmin = t;
215 }
216
217
218 } else if (p[i] > 0) {
219 // line leaving point (should be tmax)
220 double t = q[i]/p[i];
221 if (t < tmin) {
222 if (extendFirst) {
223 tmin = t;
224 } else {
225 return false;
226 }
227 }
228 if (t < tmax) {
229 tmax = t;
230 }
231 }
232 }
233
234 QPointF pt1 = line.p1();
235 QPointF pt2 = line.p2();
236
237 if (extendFirst || tmin > 0) {
238 pt1 = QPointF(line.x1() + tmin*(line.x2() - line.x1()), line.y1() + tmin*(line.y2() - line.y1()));
239 }
240 if (extendSecond || tmax < 1) {
241 pt2 = QPointF(line.x1() + tmax*(line.x2() - line.x1()), line.y1() + tmax*(line.y2() - line.y1()));
242 }
243
244 line.setP1(pt1);
245 line.setP2(pt2);
246
247 return true;
248
249}
250
251bool intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
252{
253 qreal epsilon = 1e-07;
254
255 if (polygon.size() == 0) {
256 return false;
257 }
258
259 // trivial case: no extends, all points inside the polygon
260 if (!extendFirst && !extendSecond && polygon.containsPoint(line.p1(), Qt::WindingFill) && polygon.containsPoint(line.p2(), Qt::WindingFill)) {
261 return true;
262 }
263
264 // Cyrus-Beck algorithm: https://en.wikipedia.org/wiki/Cyrus%E2%80%93Beck_algorithm
265
266 // parametric equation for the line:
267 // p(t) = t*P1 + (1-t)*P2
268
269 // we can't use infinity here, because the points would all end up as (+/- inf, +/- inf)
270 // which would be useless if we have a very specific direction
271 // hence we use just "a big number"
272 float aBigNumber = 100000; // that will keep t*Px still on the line // this is LIMITATION of the algorithm
273 float tmin = extendFirst ? -aBigNumber : 0; // line.p1() - first point, or infinity
274 float tmax = extendSecond ? aBigNumber : 1; // line.p2() - second point, or infinity
275
276
277
278 QList<QLineF> clippingLines;
279 QList<QPointF> normals;
280
281 bool clockwise = false;
282 {
283 // only temporary values to check whether the polygon is clockwise or counterclockwise
284 QPointF vec1 = polygon[1] - polygon[0];
285 QPointF vec2 = polygon[2] - polygon[0];
286
287 QLineF line1(QPointF(0, 0), vec1);
288 QLineF line2(QPointF(0, 0), vec2);
289
290 qreal angle = line1.angleTo(line2); // range: <0, 360) // <0, 180) means counter-clockwise
291 if (angle >= 180) {
292 clockwise = true;
293 }
294 }
295
296
297 for (int i = 0; i < polygon.size() - 1; i++) {
298 clippingLines.append(QLineF(polygon[i], polygon[i + 1]));
299 float xdiff = polygon[i].x() - polygon[i + 1].x();
300 float ydiff = polygon[i].y() - polygon[i + 1].y();
301
302 if (!clockwise) {
303 normals.append(QPointF(ydiff, -xdiff));
304 } else {
305 normals.append(QPointF(-ydiff, xdiff));
306 }
307
308 }
309
310 if (!polygon.isClosed()) {
311 int i = polygon.size() - 1;
312 clippingLines.append(QLineF(polygon[i], polygon[0]));
313 float xdiff = polygon[i].x() - polygon[0].x();
314 float ydiff = polygon[i].y() - polygon[0].y();
315
316 if (!clockwise) {
317 normals.append(QPointF(ydiff, -xdiff));
318 } else {
319 normals.append(QPointF(-ydiff, xdiff));
320 }
321
322 }
323
324 QPointF lineVec = line.p2() - line.p1();
325 // ax + by + c = 0
326 // c = -ax - by
327// qreal cFromStandardEquation = -lineVec.x()*line.p1().x() - lineVec.y()*line.p1().y();
328
329
330 QPointF p1 = line.p1();
331 QPointF p2 = line.p2();
332
333 qreal pA, pB, pC; // standard equation for the line: ax + by + c = 0
334 if (qAbs(lineVec.x()) < epsilon) {
335 // x1 ~= x2, line looks like: x = -pC
336 pB = 0;
337 pA = 1;
338 pC = -p1.x();
339 } else {
340 pB = 1; // let b = 1 to simplify
341 qreal tmp = (p1.y() - p2.y())/(p1.x() - p2.x());
342 pA = -tmp;
343 pC = tmp * p1.x() - p1.y();
344 }
345
346
347 int pEFound = 0;
348
349
350 for (int i = 0; i < clippingLines.size(); i++) {
351
352 // is the clipping line parallel to the line?
353 QPointF clipVec = clippingLines[i].p2() - clippingLines[i].p1();
354
355 if (qFuzzyCompare(clipVec.x()*lineVec.y(), clipVec.y()*lineVec.x())) {
356
357 // vectors are parallel
358 // 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
359
360 qreal distanceBetweenLines = qAbs(pA*clippingLines[i].p1().x() + pB*clippingLines[i].p1().y() + pC)
361 /(sqrt(pA*pA + pB*pB));
362
363
364 if (qAbs(distanceBetweenLines) < epsilon) {
365 // they are the same line
366
367 qreal t1;
368 qreal t2;
369
370 if (qAbs(p2.x() - p1.x()) > epsilon) {
371 t1 = (clippingLines[i].p1().x() - p1.x())/(p2.x() - p1.x());
372 t2 = (clippingLines[i].p2().x() - p1.x())/(p2.x() - p1.x());
373 } else {
374 t1 = (clippingLines[i].p1().y() - p1.y())/(p2.y() - p1.y());
375 t2 = (clippingLines[i].p2().y() - p1.y())/(p2.y() - p1.y());
376 }
377
378 qreal tmin1 = qMin(t1, t2);
379 qreal tmax2 = qMax(t1, t2);
380
381
382
383 if (tmin < tmin1) {
384 tmin = tmin1;
385 }
386 if (tmax > tmax2) {
387 tmax = tmax2;
388 }
389 continue;
390 }
391 else {
392 // if clipping line points are P1 and P2, and some other point is P(t) (given by parametric equation),
393 // and N is the normal vector
394 // and one of the points of the line we're intersecting is K,
395 // then we have a following equation:
396 // K = P(t) + a * N
397 // where t and a are unknown.
398 // after simplifying we get:
399 // t*(p2 - p1) + a*(n) = k - p1
400 // and since we have two axis, we get a linear system of two equations
401 // A * [t; a] = B
402
403 Eigen::Matrix2f A;
404 Eigen::Vector2f b;
405 A << p2.x() - p1.x(), normals[i].x(),
406 p2.y() - p1.y(), normals[i].y();
407 b << clippingLines[i].p1().x() - p1.x(),
408 clippingLines[i].p1().y() - p1.y();
409
410 Eigen::Vector2f ta = A.colPivHouseholderQr().solve(b);
411
412 qreal tt = ta(1);
413 if (tt < 0) {
414 return false;
415 }
416 }
417
418 }
419
420
421 boost::optional<QPointF> pEOptional = intersectLines(clippingLines[i], line); // bounded, unbounded
422
423
424 if (pEOptional) {
425 pEFound++;
426
427 QPointF pE = pEOptional.value();
428 QPointF n = normals[i];
429
430 QPointF A = line.p2() - line.p1();
431 QPointF B = line.p1() - pE;
432
433 qreal over = (n.x()*B.x() + n.y()*B.y());
434 qreal under = (n.x()*A.x() + n.y()*A.y());
435 qreal t = -over/under;
436
437// if (pE.x() != p2.x()) {
438// qreal maybet = (pE.x() - p1.x())/(p2.x() - p1.x());
439// }
440
441 qreal tminvalue = tmin * under + over;
442 qreal tmaxvalue = tmax * under + over;
443
444 if (tminvalue > 0 && tmaxvalue > 0) {
445 // both outside of the edge
446 return false;
447 }
448 if (tminvalue <= 0 && tmaxvalue <= 0) {
449 // noop, both inside
450 }
451 if (tminvalue*tmaxvalue < 0) {
452
453 // on two different sides
454 if (tminvalue > 0) {
455 // tmin outside, tmax inside
456 if (t > tmin) {
457 tmin = t;
458 }
459 } else {
460 if (t < tmax) {
461 tmax = t;
462 }
463 }
464 }
465
466
467
468 if (tmax < tmin) {
469 return false;
470 }
471 }
472 else {
473 }
474
475 }
476
477 if (pEFound == 0) {
478 return false;
479 }
480
481 QLineF response = QLineF(tmin*line.p2() + (1-tmin)*line.p1(), tmax*line.p2() + (1-tmax)*line.p1());
482 line = response;
483 return true;
484}
485
486 template <class Rect, class Point>
488 {
489 QVector<Point> points;
490
491 Point m1 = 0.5 * (rect.topLeft() + rect.topRight());
492 Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight());
493
494 points << rect.topLeft();
495 points << m1;
496 points << rect.topRight();
497
498 points << 0.5 * (rect.topLeft() + rect.bottomLeft());
499 points << 0.5 * (m1 + m2);
500 points << 0.5 * (rect.topRight() + rect.bottomRight());
501
502 points << rect.bottomLeft();
503 points << m2;
504 points << rect.bottomRight();
505
506 return points;
507 }
508
510 {
511 return sampleRectWithPoints<QRect, QPoint>(rect);
512 }
513
515 {
516 return sampleRectWithPoints<QRectF, QPointF>(rect);
517 }
518
519
520 template <class Rect, class Point, bool alignPixels>
522 {
523 using namespace boost::accumulators;
524 accumulator_set<qreal, stats<tag::min, tag::max > > accX;
525 accumulator_set<qreal, stats<tag::min, tag::max > > accY;
526
527 Q_FOREACH (const Point &pt, points) {
528 accX(pt.x());
529 accY(pt.y());
530 }
531
532 Rect resultRect;
533
534 if (alignPixels) {
535 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
536 std::ceil(max(accX)), std::ceil(max(accY)));
537 } else {
538 resultRect.setCoords(min(accX), min(accY),
539 max(accX), max(accY));
540 }
541
542 return resultRect;
543 }
544
546 {
547 return approximateRectFromPointsImpl<QRect, QPoint, true>(points);
548 }
549
551 {
552 return approximateRectFromPointsImpl<QRectF, QPointF, false>(points);
553 }
554
555 QRect approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func)
556 {
558
559 using namespace boost::accumulators;
560 accumulator_set<qreal, stats<tag::min, tag::max > > accX;
561 accumulator_set<qreal, stats<tag::min, tag::max > > accY;
562
563 Q_FOREACH (const QPoint &pt, points) {
564 QPointF dstPt = func(pt);
565
566 accX(dstPt.x());
567 accY(dstPt.y());
568 }
569
570 QRect resultRect;
571 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
572 std::ceil(max(accX)), std::ceil(max(accY)));
573
574 return resultRect;
575 }
576
577
578QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
579{
580 QVector<QPointF> points;
581
582 const QLineF cutLine = p.getLine();
583
584 points << rc.topLeft();
585 points << rc.topRight();
586 points << rc.bottomRight();
587 points << rc.bottomLeft();
588
589 QPointF p1 = points[3];
590 bool p1Valid = p.pos(p1) >= 0;
591
592 QVector<QPointF> resultPoints;
593
594 for (int i = 0; i < 4; i++) {
595 const QPointF p2 = points[i];
596 const bool p2Valid = p.pos(p2) >= 0;
597
598 if (p1Valid != p2Valid) {
599 QPointF intersection;
600 cutLine.intersects(QLineF(p1, p2), &intersection);
601 resultPoints << intersection;
602 }
603
604 if (p2Valid) {
605 resultPoints << p2;
606 }
607
608 p1 = p2;
609 p1Valid = p2Valid;
610 }
611
612 return approximateRectFromPoints(resultPoints);
613}
614
615int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
616{
617 int numSolutions = 0;
618
619 const qreal D = pow2(b) - 4 * a * c;
620 const qreal eps = 1e-14;
621
622 if (qAbs(D) <= eps) {
623 *x1 = -b / (2 * a);
624 numSolutions = 1;
625 } else if (D < 0) {
626 return 0;
627 } else {
628 const qreal sqrt_D = std::sqrt(D);
629
630 *x1 = (-b + sqrt_D) / (2 * a);
631 *x2 = (-b - sqrt_D) / (2 * a);
632 numSolutions = 2;
633 }
634
635 return numSolutions;
636}
637
638QVector<QPointF> intersectTwoCircles(const QPointF &center1, qreal r1,
639 const QPointF &center2, qreal r2)
640{
641 QVector<QPointF> points;
642
643 const QPointF diff = (center2 - center1);
644 const QPointF c1;
645 const QPointF c2 = diff;
646
647 const qreal centerDistance = norm(diff);
648
649 if (centerDistance > r1 + r2) return points;
650 if (centerDistance < qAbs(r1 - r2)) return points;
651
652 if (centerDistance < qAbs(r1 - r2) + 0.001) {
653 dbgKrita << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2));
654 return points;
655 }
656
657 const qreal x_kp1 = diff.x();
658 const qreal y_kp1 = diff.y();
659
660 const qreal F2 =
661 0.5 * (pow2(x_kp1) +
662 pow2(y_kp1) + pow2(r1) - pow2(r2));
663
664 const qreal eps = 1e-6;
665
666 if (qAbs(diff.y()) < eps) {
667 qreal x = F2 / diff.x();
668 qreal y1, y2;
670 1, 0,
671 pow2(x) - pow2(r2),
672 &y1, &y2);
673
674 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
675
676 if (result == 1) {
677 points << QPointF(x, y1);
678 } else if (result == 2) {
680
681 QPointF p1(x, y1);
682 QPointF p2(x, y2);
683
684 if (p.pos(p1) >= 0) {
685 points << p1;
686 points << p2;
687 } else {
688 points << p2;
689 points << p1;
690 }
691 }
692 } else {
693 const qreal A = diff.x() / diff.y();
694 const qreal C = F2 / diff.y();
695
696 qreal x1, x2;
698 1 + pow2(A), -2 * A * C,
699 pow2(C) - pow2(r1),
700 &x1, &x2);
701
702 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
703
704 if (result == 1) {
705 points << QPointF(x1, C - x1 * A);
706 } else if (result == 2) {
708
709 QPointF p1(x1, C - x1 * A);
710 QPointF p2(x2, C - x2 * A);
711
712 if (p.pos(p1) >= 0) {
713 points << p1;
714 points << p2;
715 } else {
716 points << p2;
717 points << p1;
718 }
719 }
720 }
721
722 for (int i = 0; i < points.size(); i++) {
723 points[i] = center1 + points[i];
724 }
725
726 return points;
727}
728
729QTransform mapToRect(const QRectF &rect)
730{
731 return
732 QTransform(rect.width(), 0, 0, rect.height(),
733 rect.x(), rect.y());
734}
735
736QTransform mapToRectInverse(const QRectF &rect)
737{
738 return
739 QTransform::fromTranslate(-rect.x(), -rect.y()) *
740 QTransform::fromScale(rect.width() != 0 ? 1.0 / rect.width() : 0.0,
741 rect.height() != 0 ? 1.0 / rect.height() : 0.0);
742}
743
744bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) {
745 return
746 qAbs(t1.m11() - t2.m11()) < delta &&
747 qAbs(t1.m12() - t2.m12()) < delta &&
748 qAbs(t1.m13() - t2.m13()) < delta &&
749 qAbs(t1.m21() - t2.m21()) < delta &&
750 qAbs(t1.m22() - t2.m22()) < delta &&
751 qAbs(t1.m23() - t2.m23()) < delta &&
752 qAbs(t1.m31() - t2.m31()) < delta &&
753 qAbs(t1.m32() - t2.m32()) < delta &&
754 qAbs(t1.m33() - t2.m33()) < delta;
755}
756
757bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
758{
759 return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y());
760}
761
762
763bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta)
764{
765 return qAbs(p1.x() - p2.x()) < delta && qAbs(p1.y() - p2.y()) < delta;
766}
767
768
769inline QTransform toQTransformStraight(const Eigen::Matrix3d &m)
770{
771 return QTransform(m(0,0), m(0,1), m(0,2),
772 m(1,0), m(1,1), m(1,2),
773 m(2,0), m(2,1), m(2,2));
774}
775
776inline Eigen::Matrix3d fromQTransformStraight(const QTransform &t)
777{
778 Eigen::Matrix3d m;
779
780 m << t.m11() , t.m12() , t.m13()
781 ,t.m21() , t.m22() , t.m23()
782 ,t.m31() , t.m32() , t.m33();
783
784 return m;
785}
786
787/********************************************************/
788/* DecomposedMatrix */
789/********************************************************/
790
794
796{
797 QTransform t(t0);
798
799 QTransform projMatrix;
800
801 if (t.m33() == 0.0 || t0.determinant() == 0.0) {
802 qWarning() << "Cannot decompose matrix!" << t;
803 valid = false;
804 return;
805 }
806
807 if (t.type() == QTransform::TxProject) {
808 QTransform affineTransform(
809 t.m11(), t.m12(), 0,
810 t.m21(), t.m22(), 0,
811 t.m31(), t.m32(), 1
812 );
813 projMatrix = affineTransform.inverted() * t;
814
815 t = affineTransform;
816 proj[0] = projMatrix.m13();
817 proj[1] = projMatrix.m23();
818 proj[2] = projMatrix.m33();
819 }
820
821 // can't use QVector3D, because they have too little accuracy for ellipse in perspective calculations
822 std::array<Eigen::Vector3d, 3> rows;
823
824
825 // << t.m11() << t.m12() << t.m13())
826 rows[0] = Eigen::Vector3d(t.m11(), t.m12(), t.m13());
827 rows[1] = Eigen::Vector3d(t.m21(), t.m22(), t.m23());
828 rows[2] = Eigen::Vector3d(t.m31(), t.m32(), t.m33());
829
830 if (!qFuzzyCompare(t.m33(), 1.0)) {
831 const qreal invM33 = 1.0 / t.m33();
832
833 for (auto &row : rows) {
834 row *= invM33;
835 }
836 }
837
838 dx = rows[2].x();
839 dy = rows[2].y();
840
841 rows[2] = Eigen::Vector3d(0,0,1);
842
843
844 scaleX = rows[0].norm();
845 rows[0] *= 1.0 / scaleX;
846
847 shearXY = rows[0].dot(rows[1]);
848 rows[1] = rows[1] - shearXY * rows[0];
849
850 scaleY = rows[1].norm();
851 rows[1] *= 1.0 / scaleY;
852 shearXY *= 1.0 / scaleY;
853
854 // If determinant is negative, one axis was flipped.
855 qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x();
856 if (determinant < 0) {
857 // Flip axis with minimum unit vector dot product.
858 if (rows[0].x() < rows[1].y()) {
859 scaleX = -scaleX;
860 rows[0] = -rows[0];
861 } else {
862 scaleY = -scaleY;
863 rows[1] = -rows[1];
864 }
865 shearXY = - shearXY;
866 }
867
868 angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x()));
869
870 if (angle != 0.0) {
871 // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
872 // = [row0x, -row0y, row0y, row0x]
873 // Thanks to the normalization above.
874
875 qreal sn = -rows[0].y();
876 qreal cs = rows[0].x();
877 qreal m11 = rows[0].x();
878 qreal m12 = rows[0].y();
879 qreal m21 = rows[1].x();
880 qreal m22 = rows[1].y();
881 rows[0][0] = (cs * m11 + sn * m21);
882 rows[0][1] = (cs * m12 + sn * m22);
883 rows[1][0] = (-sn * m11 + cs * m21);
884 rows[1][1] = (-sn * m12 + cs * m22);
885 }
886
887 QTransform leftOver(
888 rows[0].x(), rows[0].y(), rows[0].z(),
889 rows[1].x(), rows[1].y(), rows[1].z(),
890 rows[2].x(), rows[2].y(), rows[2].z());
891
892 if (/*true || */!fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)) {
893 // what's wrong?
894 ENTER_FUNCTION() << "FAILING THE ASSERT BELOW!";
895 ENTER_FUNCTION() << ppVar(leftOver);
896 ENTER_FUNCTION() << "matrix to decompose was: " << ppVar(t0);
897 ENTER_FUNCTION() << ppVar(t.m33()) << ppVar(t0.determinant());
898 Eigen::Matrix3d mat1 = fromQTransformStraight(QTransform());
899 Eigen::Matrix3d mat2 = fromQTransformStraight(leftOver);
900 Eigen::Matrix3d mat3 = mat2 - mat1;
901 ENTER_FUNCTION() << mat3(0, 0) << mat3(0, 1) << mat3(0, 2);
902 ENTER_FUNCTION() << mat3(1, 0) << mat3(1, 1) << mat3(1, 2);
903 ENTER_FUNCTION() << mat3(2, 0) << mat3(2, 1) << mat3(2, 2);
904 //ENTER_FUNCTION() << ppVar(mat1 - mat2);
905 }
906
907
908 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4));
909 KIS_ASSERT(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4));
910}
911
912
913std::pair<QPointF, QTransform> transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal)
914{
915 KisAlgebra2D::DecomposedMatrix decomposed(fullLocalToGlobal);
916 const QTransform localToGlobal =
917 decomposed.scaleTransform() *
918 decomposed.shearTransform() *
919
920 /* decomposed.projectTransform() * */
921 /*decomposed.translateTransform() * */
922
923 decomposed.rotateTransform();
924
925 const QTransform localEllipse = QTransform(1.0 / pow2(axes.x()), 0.0, 0.0,
926 0.0, 1.0 / pow2(axes.y()), 0.0,
927 0.0, 0.0, 1.0);
928
929
930 const QTransform globalToLocal = localToGlobal.inverted();
931
932 Eigen::Matrix3d eqM =
933 fromQTransformStraight(globalToLocal *
934 localEllipse *
935 globalToLocal.transposed());
936
937// std::cout << "eqM:" << std::endl << eqM << std::endl;
938
939 Eigen::EigenSolver<Eigen::Matrix3d> eigenSolver(eqM);
940
941 const Eigen::Matrix3d T = eigenSolver.eigenvalues().real().asDiagonal();
942 const Eigen::Matrix3d U = eigenSolver.eigenvectors().real();
943
944 const Eigen::Matrix3d Ti = eigenSolver.eigenvalues().imag().asDiagonal();
945 const Eigen::Matrix3d Ui = eigenSolver.eigenvectors().imag();
946
947 KIS_SAFE_ASSERT_RECOVER_NOOP(Ti.isZero());
949 KIS_SAFE_ASSERT_RECOVER_NOOP((U * U.transpose()).isIdentity());
950
951// std::cout << "T:" << std::endl << T << std::endl;
952// std::cout << "U:" << std::endl << U << std::endl;
953
954// std::cout << "Ti:" << std::endl << Ti << std::endl;
955// std::cout << "Ui:" << std::endl << Ui << std::endl;
956
957// std::cout << "UTU':" << std::endl << U * T * U.transpose() << std::endl;
958
959 const qreal newA = 1.0 / std::sqrt(T(0,0) * T(2,2));
960 const qreal newB = 1.0 / std::sqrt(T(1,1) * T(2,2));
961
962 const QTransform newGlobalToLocal = toQTransformStraight(U);
963 const QTransform newLocalToGlobal = QTransform::fromScale(-1,-1) *
964 newGlobalToLocal.inverted() *
965 decomposed.translateTransform();
966
967 return std::make_pair(QPointF(newA, newB), newLocalToGlobal);
968}
969
970QPointF alignForZoom(const QPointF &pt, qreal zoom)
971{
972 return QPointF((pt * zoom).toPoint()) / zoom;
973}
974
975boost::optional<QPointF> intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine)
976{
977 const QPointF B1 = unboundedLine.p1();
978 const QPointF A1 = unboundedLine.p2() - unboundedLine.p1();
979
980 const QPointF B2 = boundedLine.p1();
981 const QPointF A2 = boundedLine.p2() - boundedLine.p1();
982
983 Eigen::Matrix<float, 2, 2> A;
984 A << A1.x(), -A2.x(),
985 A1.y(), -A2.y();
986
987 Eigen::Matrix<float, 2, 1> B;
988 B << B2.x() - B1.x(),
989 B2.y() - B1.y();
990
991 if (qFuzzyIsNull(A.determinant())) {
992 return boost::none;
993 }
994
995 Eigen::Matrix<float, 2, 1> T;
996
997 T = A.inverse() * B;
998
999 const qreal t2 = T(1,0);
1000 double epsilon = 1e-06; // to avoid precision issues
1001
1002 if (t2 < 0.0 || t2 > 1.0) {
1003 if (qAbs(t2) > epsilon && qAbs(t2 - 1.0) > epsilon) {
1004 return boost::none;
1005 }
1006 }
1007
1008 return t2 * A2 + B2;
1009}
1010
1011boost::optional<QPointF> intersectLines(const QPointF &p1, const QPointF &p2,
1012 const QPointF &q1, const QPointF &q2)
1013{
1014 return intersectLines(QLineF(p1, p2), QLineF(q1, q2));
1015}
1016
1017QVector<QPointF> findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b)
1018{
1019 using KisAlgebra2D::norm;
1021
1022 QVector<QPointF> result;
1023
1024 const QPointF p = p2 - p1;
1025
1026 const qreal pSq = dotProduct(p, p);
1027
1028 const qreal T = 0.5 * (-pow2(b) + pow2(a) + pSq);
1029
1030 if (p.isNull()) return result;
1031
1032 if (qAbs(p.x()) > qAbs(p.y())) {
1033 const qreal A = 1.0;
1034 const qreal B2 = -T * p.y() / pSq;
1035 const qreal C = pow2(T) / pSq - pow2(a * p.x()) / pSq;
1036
1037 const qreal D4 = pow2(B2) - A * C;
1038
1039 if (D4 > 0 || qFuzzyIsNull(D4)) {
1040 if (qFuzzyIsNull(D4)) {
1041 const qreal y = -B2 / A;
1042 const qreal x = (T - y * p.y()) / p.x();
1043 result << p1 + QPointF(x, y);
1044 } else {
1045 const qreal y1 = (-B2 + std::sqrt(D4)) / A;
1046 const qreal x1 = (T - y1 * p.y()) / p.x();
1047 result << p1 + QPointF(x1, y1);
1048
1049 const qreal y2 = (-B2 - std::sqrt(D4)) / A;
1050 const qreal x2 = (T - y2 * p.y()) / p.x();
1051 result << p1 + QPointF(x2, y2);
1052 }
1053 }
1054 } else {
1055 const qreal A = 1.0;
1056 const qreal B2 = -T * p.x() / pSq;
1057 const qreal C = pow2(T) / pSq - pow2(a * p.y()) / pSq;
1058
1059 const qreal D4 = pow2(B2) - A * C;
1060
1061 if (D4 > 0 || qFuzzyIsNull(D4)) {
1062 if (qFuzzyIsNull(D4)) {
1063 const qreal x = -B2 / A;
1064 const qreal y = (T - x * p.x()) / p.y();
1065 result << p1 + QPointF(x, y);
1066 } else {
1067 const qreal x1 = (-B2 + std::sqrt(D4)) / A;
1068 const qreal y1 = (T - x1 * p.x()) / p.y();
1069 result << p1 + QPointF(x1, y1);
1070
1071 const qreal x2 = (-B2 - std::sqrt(D4)) / A;
1072 const qreal y2 = (T - x2 * p.x()) / p.y();
1073 result << p1 + QPointF(x2, y2);
1074 }
1075 }
1076 }
1077
1078 return result;
1079}
1080
1081boost::optional<QPointF> findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest)
1082{
1083 const QVector<QPointF> points = findTrianglePoint(p1, p2, a, b);
1084
1085 boost::optional<QPointF> result;
1086
1087 if (points.size() == 1) {
1088 result = points.first();
1089 } else if (points.size() > 1) {
1090 result = kisDistance(points.first(), nearest) < kisDistance(points.last(), nearest) ? points.first() : points.last();
1091 }
1092
1093 return result;
1094}
1095
1096QPointF moveElasticPoint(const QPointF &pt, const QPointF &base, const QPointF &newBase, const QPointF &wingA, const QPointF &wingB)
1097{
1098 using KisAlgebra2D::norm;
1101
1102 const QPointF vecL = base - pt;
1103 const QPointF vecLa = wingA - pt;
1104 const QPointF vecLb = wingB - pt;
1105
1106 const qreal L = norm(vecL);
1107 const qreal La = norm(vecLa);
1108 const qreal Lb = norm(vecLb);
1109
1110 const qreal sinMuA = crossProduct(vecLa, vecL) / (La * L);
1111 const qreal sinMuB = crossProduct(vecL, vecLb) / (Lb * L);
1112
1113 const qreal cosMuA = dotProduct(vecLa, vecL) / (La * L);
1114 const qreal cosMuB = dotProduct(vecLb, vecL) / (Lb * L);
1115
1116 const qreal dL = dotProduct(newBase - base, -vecL) / L;
1117
1118
1119 const qreal divB = (cosMuB + La / Lb * sinMuB * cosMuA / sinMuA) / L +
1120 (cosMuB + sinMuB * cosMuA / sinMuA) / Lb;
1121 const qreal dLb = dL / ( L * divB);
1122
1123 const qreal divA = (cosMuA + Lb / La * sinMuA * cosMuB / sinMuB) / L +
1124 (cosMuA + sinMuA * cosMuB / sinMuB) / La;
1125 const qreal dLa = dL / ( L * divA);
1126
1127 boost::optional<QPointF> result =
1128 KisAlgebra2D::findTrianglePointNearest(wingA, wingB, La + dLa, Lb + dLb, pt);
1129
1130 const QPointF resultPoint = result ? *result : pt;
1131
1132 return resultPoint;
1133}
1134
1135#ifdef HAVE_GSL
1136
1137struct ElasticMotionData
1138{
1139 QPointF oldBasePos;
1140 QPointF newBasePos;
1141 QVector<QPointF> anchorPoints;
1142
1143 QPointF oldResultPoint;
1144};
1145
1146double elasticMotionError(const gsl_vector * x, void *paramsPtr)
1147{
1148 using KisAlgebra2D::norm;
1151
1152 const QPointF newResultPoint(gsl_vector_get(x, 0), gsl_vector_get(x, 1));
1153
1154 const ElasticMotionData *p = static_cast<const ElasticMotionData*>(paramsPtr);
1155
1156 const QPointF vecL = newResultPoint - p->newBasePos;
1157 const qreal L = norm(vecL);
1158
1159 const qreal deltaL = L - kisDistance(p->oldBasePos, p->oldResultPoint);
1160
1161 QVector<qreal> deltaLi;
1162 QVector<qreal> Li;
1163 QVector<qreal> cosMuI;
1164 QVector<qreal> sinMuI;
1165 Q_FOREACH (const QPointF &anchorPoint, p->anchorPoints) {
1166 const QPointF vecLi = newResultPoint - anchorPoint;
1167 const qreal _Li = norm(vecLi);
1168
1169 Li << _Li;
1170 deltaLi << _Li - kisDistance(p->oldResultPoint, anchorPoint);
1171 cosMuI << dotProduct(vecLi, vecL) / (_Li * L);
1172 sinMuI << crossProduct(vecL, vecLi) / (_Li * L);
1173 }
1174
1175 qreal finalError = 0;
1176
1177 qreal tangentialForceSum = 0;
1178 for (int i = 0; i < p->anchorPoints.size(); i++) {
1179 const qreal sum = deltaLi[i] * sinMuI[i] / Li[i];
1180 tangentialForceSum += sum;
1181 }
1182
1183 finalError += pow2(tangentialForceSum);
1184
1185 qreal normalForceSum = 0;
1186 {
1187 qreal sum = 0;
1188 for (int i = 0; i < p->anchorPoints.size(); i++) {
1189 sum += deltaLi[i] * cosMuI[i] / Li[i];
1190 }
1191 normalForceSum = (-deltaL) / L - sum;
1192 }
1193
1194 finalError += pow2(normalForceSum);
1195
1196 return finalError;
1197}
1198
1199
1200#endif /* HAVE_GSL */
1201
1202QPointF moveElasticPoint(const QPointF &pt,
1203 const QPointF &base, const QPointF &newBase,
1204 const QVector<QPointF> &anchorPoints)
1205{
1206 const QPointF offset = newBase - base;
1207
1208 QPointF newResultPoint = pt + offset;
1209
1210#ifdef HAVE_GSL
1211
1212 ElasticMotionData data;
1213 data.newBasePos = newBase;
1214 data.oldBasePos = base;
1215 data.anchorPoints = anchorPoints;
1216 data.oldResultPoint = pt;
1217
1218 const gsl_multimin_fminimizer_type *T =
1219 gsl_multimin_fminimizer_nmsimplex2;
1220 gsl_multimin_fminimizer *s = 0;
1221 gsl_vector *ss, *x;
1222 gsl_multimin_function minex_func;
1223
1224 size_t iter = 0;
1225 int status;
1226 double size;
1227
1228 /* Starting point */
1229 x = gsl_vector_alloc (2);
1230 gsl_vector_set (x, 0, newResultPoint.x());
1231 gsl_vector_set (x, 1, newResultPoint.y());
1232
1233 /* Set initial step sizes to 0.1 */
1234 ss = gsl_vector_alloc (2);
1235 gsl_vector_set (ss, 0, 0.1 * offset.x());
1236 gsl_vector_set (ss, 1, 0.1 * offset.y());
1237
1238 /* Initialize method and iterate */
1239 minex_func.n = 2;
1240 minex_func.f = elasticMotionError;
1241 minex_func.params = (void*)&data;
1242
1243 s = gsl_multimin_fminimizer_alloc (T, 2);
1244 gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
1245
1246 do
1247 {
1248 iter++;
1249 status = gsl_multimin_fminimizer_iterate(s);
1250
1251 if (status)
1252 break;
1253
1254 size = gsl_multimin_fminimizer_size (s);
1255 status = gsl_multimin_test_size (size, 1e-6);
1256
1262 if (status == GSL_SUCCESS && elasticMotionError(s->x, &data) > 0.5) {
1263 status = GSL_CONTINUE;
1264 }
1265
1266 if (status == GSL_SUCCESS)
1267 {
1268// qDebug() << "*******Converged to minimum";
1269// qDebug() << gsl_vector_get (s->x, 0)
1270// << gsl_vector_get (s->x, 1)
1271// << "|" << s->fval << size;
1272// qDebug() << ppVar(iter);
1273
1274 newResultPoint.rx() = gsl_vector_get (s->x, 0);
1275 newResultPoint.ry() = gsl_vector_get (s->x, 1);
1276 }
1277 }
1278 while (status == GSL_CONTINUE && iter < 10000);
1279
1280 if (status != GSL_SUCCESS) {
1281 ENTER_FUNCTION() << "failed to find point" << ppVar(pt) << ppVar(base) << ppVar(newBase);
1282 }
1283
1284 gsl_vector_free(x);
1285 gsl_vector_free(ss);
1286 gsl_multimin_fminimizer_free (s);
1287#endif
1288
1289 return newResultPoint;
1290}
1291
1292void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
1293{
1294 bool intersects = intersectLineRect(line, rect, extendFirst, extendSecond);
1295 if (!intersects) {
1296 line = QLineF(); // empty line to help with drawing
1297 }
1298}
1299
1300void cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
1301{
1302 bool intersects = intersectLineConvexPolygon(line, polygon, extendFirst, extendSecond);
1303 if (!intersects) {
1304 line = QLineF(); // empty line to help with drawing
1305 }
1306}
1307
1308int lineSideForPoint(const QLineF &line, const QPointF &point)
1309{
1310 // TODO: do we really neede these explicit checks? Do they help with
1311 // precision?
1312
1313 if (fuzzyPointCompare(point, line.p1())) {
1314 return 0;
1315 }
1316 if (fuzzyPointCompare(point, line.p2())) {
1317 return 0;
1318 }
1319 if (qFuzzyCompare(line.length(), 0)) {
1320 return 0;
1321 }
1322
1323 qreal whichSide = KisAlgebra2D::crossProduct(line.p2() - line.p1(), point - line.p1());
1324 return qFuzzyIsNull(whichSide) ? 0 : (whichSide > 0 ? 1 : -1);
1325}
1326
1327QPolygonF combineConvexHullParts(const QPolygonF &leftPolygon, QPolygonF &rightPolygon, bool triangular) {
1328 // resulting polygon should start at p1, go through all the points, go to p2, then to p1 again
1329 // triangular: whether the last point of right is equal to the first point in left or not
1330
1331 QPolygonF left = leftPolygon;
1332 QPolygonF right = rightPolygon;
1333
1334 left.removeLast(); // remove p1 from the end (but not the beginning)
1335 left.removeLast(); // remove nextPoint as well, since it's present in rightPolygon (twice)
1336
1337 right.removeLast(); // remove nextPoint from the end (but not the beginning)
1338
1339
1340 QPolygonF result;
1341 Q_FOREACH(QPointF point, left) {
1342 result << point;
1343 }
1344
1345 Q_FOREACH(QPointF point, right) {
1346 result << point;
1347 }
1348
1349 if (triangular) {
1350 result << left[0];
1351 }
1352
1353 return result;
1354}
1355
1356QPolygonF calculateConvexHullFromPointsOverTheLine(const QPolygonF &points, const QLineF &line)
1357{
1358 // all the points should be on the correct side already, no need to check it
1359 if (points.count() < 1) {
1360 QPolygonF result;
1361 result << line.p1() << line.p2() << line.p1();
1362 return result;
1363 }
1364 if (points.count() == 1) {
1365 QList<QPointF> list = points.toList();
1366
1367 QPolygonF result;
1368 result << line.p1();
1369 result << list[0];
1370 result << line.p2();
1371 result << line.p1();
1372
1373 return result;
1374 }
1375
1376 double maxDistance = 0;
1377 QPointF nextPoint = points[0];
1378 Q_FOREACH(QPointF point, points) {
1379 double distance = kisSquareDistanceToLine(point, line);
1380 if (distance > maxDistance) {
1381 maxDistance = distance;
1382 nextPoint = point;
1383 }
1384 }
1385
1386 QPolygonF left;
1387 QLineF lineForLeft = QLineF(line.p1(), nextPoint);
1388 QPolygonF right;
1389 QLineF lineForRight = QLineF(nextPoint, line.p2());
1390 QPolygonF triangle;
1391 triangle << line.p1() << line.p2() << nextPoint << line.p1();
1392 Q_FOREACH(QPointF point, points) {
1393 if (triangle.containsPoint(point, Qt::WindingFill)) {
1394 continue;
1395 }
1396 if (lineSideForPoint(lineForLeft, point) > 0) {
1397 left << point;
1398 } else if (lineSideForPoint(lineForRight, point) < 0) {
1399 right << point;
1400 }
1401 }
1402
1403 QPolygonF leftPolygon = calculateConvexHullFromPointsOverTheLine(left, lineForLeft);
1404 QPolygonF rightPolygon = calculateConvexHullFromPointsOverTheLine(right, lineForRight);
1405
1406 QPolygonF result = combineConvexHullParts(leftPolygon, rightPolygon, true);
1407
1408 return result;
1409}
1410
1411
1412QPolygonF calculateConvexHull(const QPolygonF &polygon)
1413{
1414 if (polygon.count() < 4) {
1415 return polygon;
1416 }
1417 QPointF leftPoint = polygon[0];
1418 QPointF rightPoint = polygon[1];
1419 if (leftPoint.x() > rightPoint.x()) { // swap them around
1420 leftPoint = polygon[1];
1421 rightPoint = polygon[0];
1422 }
1423
1424 Q_FOREACH(QPointF point, polygon) {
1425 if (point.x() < leftPoint.x()) {
1426 leftPoint = point;
1427 } else if (point.x() > rightPoint.x()) {
1428 rightPoint = point;
1429 }
1430 }
1431 QLineF line(leftPoint, rightPoint);
1432 QLineF lineOther(rightPoint, leftPoint);
1433 QPolygonF left;
1434 QPolygonF right;
1435
1436 Q_FOREACH(QPointF point, polygon) {
1437 qCritical() << "Checking point " << point << "and line" << line << ": " << lineSideForPoint(line, point);
1438 if (lineSideForPoint(line, point) > 0) {
1439 left << point;
1440 } else if (lineSideForPoint(line, point) < 0) {
1441 right << point;
1442 }
1443 }
1444
1445 qCritical() << "Using line: " << line << "for points: " << left;
1446 qCritical() << "Using line: " << lineOther << "for points: " << right;
1447
1449 right = calculateConvexHullFromPointsOverTheLine(right, lineOther);
1450
1451 qCritical() << "Then the result: " << left << right;
1452
1453 QPolygonF result = combineConvexHullParts(left, right, false);
1454
1455 return result;
1456
1457}
1458
1459QList<QLineF> intersectLineConcavePolygon(const QPolygonF polygon, const QLineF& line, bool extendFirst, bool extendSecond) {
1460
1461 // should use kis_convex_hull instead, that one uses boost
1462 QPolygonF convexHull = calculateConvexHull(polygon);
1463 if (convexHull.count() == polygon.count()) {
1464 QLineF resultLine = line;
1465 bool result = intersectLineConvexPolygon(resultLine, polygon, extendFirst, extendSecond);
1466 if (result) {
1467 return QList<QLineF>() << resultLine;
1468 } else {
1469 return QList<QLineF>();
1470 }
1471 }
1472
1473 // it was a concave polygon
1474 // intersectLines
1475
1476 // should be as easy as cutting every line, then sorting the points and creating the lines, except gotta take care of exceptions
1477
1478 KIS_ASSERT(false && "Not implemented yet");
1479
1480 return QList<QLineF>();
1481}
1482
1483
1484qreal findMinimumGoldenSection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100)
1485{
1486 // requirements:
1487 // only one local minimum between xA and xB
1488
1489 int i = 0;
1490 const double phi = 0.618033988749894; // 1/(golden ratio)
1491 qreal a = xA;
1492 qreal b = xB;
1493 qreal c = b - (b - a)*phi;
1494 qreal d = a + (b - a)*phi;
1495
1496 while (qAbs(b - a) > eps) {
1497 if (f(c) < f(d)) {
1498 b = d;
1499 } else {
1500 a = c;
1501 }
1502
1503 // Wikipedia says to recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop
1504 c = b - (b - a) * phi;
1505 d = a + (b - a) * phi;
1506
1507 i++;
1508 if (i > maxIter) {
1509 break;
1510 }
1511
1512 }
1513
1514 return (b + a)/2;
1515}
1516
1517qreal findMinimumTernarySection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100)
1518{
1519 // requirements:
1520 // only one local minimum between xA and xB
1521
1522 int i = 0;
1523 qreal l = qMin(xA, xB);
1524 qreal r = qMax(xA, xB);
1525
1526
1527 qreal m1 = l + (r - l)/3;
1528 qreal m2 = r - (r - l)/3;
1529
1530 while ((r - l) > eps) {
1531 qreal f1 = f(m1);
1532 qreal f2 = f(m2);
1533
1534 if (f1 > f2) {
1535 l = m1;
1536 } else {
1537 r = m2;
1538 }
1539
1540 // 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
1541 // so it's probably a good idea to do it here too
1542 m1 = l + (r - l)/3;
1543 m2 = r - (r - l)/3;
1544
1545 i++;
1546
1547 if (i > maxIter) {
1548 break;
1549 }
1550 }
1551
1552 return (l + r)/2;
1553}
1554
1555qreal pointToLineDistSquared(const QPointF &pt, const QLineF &line)
1556{
1557 // distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1|
1558
1559 // magnitude of (p2 - p1) x (p1 - pt)
1560 const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x()));
1561
1562 return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy());
1563}
1564
1565bool tryMergePoints(QPainterPath &path,
1566 const QPointF &startPoint,
1567 const QPointF &endPoint,
1568 qreal &distance,
1569 qreal distanceThreshold,
1570 bool lastSegment)
1571{
1572 qreal length = (endPoint - startPoint).manhattanLength();
1573
1574 if (lastSegment || length > distanceThreshold) {
1575 if (lastSegment) {
1576 qreal wrappedLength =
1577 (endPoint - QPointF(path.elementAt(0))).manhattanLength();
1578
1579 if (length < distanceThreshold ||
1580 wrappedLength < distanceThreshold) {
1581
1582 return true;
1583 }
1584 }
1585
1586 distance = 0;
1587 return false;
1588 }
1589
1590 distance += length;
1591
1592 if (distance > distanceThreshold) {
1593 path.lineTo(endPoint);
1594 distance = 0;
1595 }
1596
1597 return true;
1598}
1599
1600QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
1601{
1602 QPainterPath newPath;
1603 QPointF startPoint;
1604 qreal distance = 0;
1605
1606 int count = path.elementCount();
1607 for (int i = 0; i < count; i++) {
1608 QPainterPath::Element e = path.elementAt(i);
1609 QPointF endPoint = QPointF(e.x, e.y);
1610
1611 switch (e.type) {
1612 case QPainterPath::MoveToElement:
1613 newPath.moveTo(endPoint);
1614 break;
1615 case QPainterPath::LineToElement:
1616 if (!tryMergePoints(newPath, startPoint, endPoint,
1617 distance, lengthThreshold, i == count - 1)) {
1618
1619 newPath.lineTo(endPoint);
1620 }
1621 break;
1622 case QPainterPath::CurveToElement: {
1623 Q_ASSERT(i + 2 < count);
1624
1625 if (!tryMergePoints(newPath, startPoint, endPoint,
1626 distance, lengthThreshold, i == count - 1)) {
1627
1628 e = path.elementAt(i + 1);
1629 Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
1630 QPointF ctrl1 = QPointF(e.x, e.y);
1631 e = path.elementAt(i + 2);
1632 Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
1633 QPointF ctrl2 = QPointF(e.x, e.y);
1634 newPath.cubicTo(ctrl1, ctrl2, endPoint);
1635 }
1636
1637 i += 2;
1638 }
1639 default:
1640 ;
1641 }
1642 startPoint = endPoint;
1643 }
1644
1645 return newPath;
1646}
1647
1648
1649QList<QLineF> getParallelLines(const QLineF& line, const qreal distance) {
1650
1651 QPointF lineNormalVector = QPointF(line.dy(), -line.dx());
1652 QLineF line1 = QLineF(movePointInTheDirection(line.p1(), lineNormalVector, distance), movePointInTheDirection(line.p2(), lineNormalVector, distance));
1653 QLineF line2 = QLineF(movePointInTheDirection(line.p1(), -lineNormalVector, distance), movePointInTheDirection(line.p2(), -lineNormalVector, distance));
1654
1655 if (line1.isNull() || line2.isNull()) {
1656 return QList<QLineF>();
1657 }
1658 return {line1, line2};
1659}
1660
1661QPainterPath getOnePathFromRectangleCutThrough(const QList<QPointF> &points, const QLineF &line, bool left) {
1662
1663 auto onTheLine = [](QPointF first, QPointF second) {
1664 float delta = 0.1f;
1665 return qAbs(first.x() - second.x()) < delta || qAbs(first.y() - second.y()) < delta;
1666 };
1667
1668 bool started = false;
1669 QPainterPath path;
1670
1671 path.moveTo(line.p1());
1672 path.lineTo(line.p2());
1673
1674 QList<QPointF> availablePoints;
1675 int maxRectPointsNumber = 4; // in case the list has five points to count the first one twice
1676 for(int i = 0; i < maxRectPointsNumber; i++) {
1677 qreal whichSide = KisAlgebra2D::crossProduct(line.p2() - line.p1(), line.p2() - points[i]);
1678 if (whichSide*(left ? 1 : -1) >= 0) {
1679 availablePoints << points[i];
1680 }
1681 }
1682
1683 int startValue, increment;
1684 if (!left) {
1685 startValue = 2*availablePoints.length();
1686 increment = -1;
1687 } else {
1688 startValue = 0;
1689 increment = 1;
1690 }
1691
1692 auto stopFor = [](int index, int max, bool left) {
1693 if (!left) {
1694 return index <= 0;
1695 } else {
1696 return index >= max;
1697 }
1698 };
1699
1700 for (int i = startValue; !stopFor(i, 2*availablePoints.length(), left); i += increment) {
1701 int index = KisAlgebra2D::wrapValue(i, 0, (int)availablePoints.length());
1702
1703 if (onTheLine(availablePoints[index], path.currentPosition())) {
1704 if (!started) {
1705 started = true;
1706
1707 // TODO: if it's not the same point?
1708 path.lineTo(availablePoints[index]);
1709 if (onTheLine(availablePoints[index], line.p1())) {
1710 break;
1711 }
1712
1713 } else {
1714 // usually would add, unless it's the ending
1715 if (onTheLine(availablePoints[index], line.p1())) {
1716 path.lineTo(availablePoints[index]);
1717 break;
1718 }
1719 path.lineTo(availablePoints[index]);
1720 }
1721 }
1722 }
1723
1724
1725 path.lineTo(line.p1());
1726
1727 return path;
1728}
1729
1730QList<QPainterPath> getPathsFromRectangleCutThrough(const QRectF &rect, const QLineF &leftLine, const QLineF &rightLine) {
1731
1732
1733 QPainterPath left;
1734 QPainterPath right;
1735
1736 left.moveTo(leftLine.p1());
1737 left.lineTo(leftLine.p2());
1738
1739 right.moveTo(rightLine.p1());
1740 right.lineTo(rightLine.p2());
1741
1742 QList<QPointF> rectPoints;
1743 rectPoints << rect.topLeft() << rect.bottomLeft() << rect.bottomRight() << rect.topRight();
1744
1745 left = getOnePathFromRectangleCutThrough(rectPoints, leftLine, true);
1746 right = getOnePathFromRectangleCutThrough(rectPoints, rightLine, false);
1747
1748 QList<QPainterPath> paths;
1749 paths << left << right;
1750
1751
1752 return paths;
1753}
1754
1755QPointF findNearestPointOnLine(const QPointF &point, const QLineF &line, bool unbounded)
1756{
1757 // TODO: can we reuse (or deduplicate with) KisBezierUtils.cpp:nearestPoint()?
1758
1759 if (line.length() == 0) {
1760 return point;
1761 }
1762
1763 QPointF lineVector = line.p2() - line.p1();
1764 QPointF lineNormalVector = QPointF(-lineVector.y(), lineVector.x());
1765
1766 QLineF pointToLineNormalLine = QLineF(point, point + lineNormalVector);
1767 QPointF result;
1768 QLineF::IntersectionType intersectionType = pointToLineNormalLine.intersects(line, &result);
1769 if (unbounded || intersectionType == QLineF::IntersectionType::BoundedIntersection) {
1770 return result;
1771 }
1772 qreal distance1 = kisDistance(line.p1(), result);
1773 qreal distance2 = kisDistance(line.p2(), result);
1774 if (distance1 + distance2 <= line.length() || qFuzzyCompare(distance1 + distance2, line.length())) {
1775 return result; // it's still on the line
1776 }
1777 if (distance1 < distance2) {
1778 return line.p1();
1779 }
1780 return line.p2();
1781}
1782
1783QPointF movePointInTheDirection(const QPointF &point, const QPointF &direction, qreal distance)
1784{
1785 QPointF response = point;
1786 QLineF line = QLineF(QPointF(0, 0), direction);
1787 return response + distance*direction/line.length();
1788}
1789
1790QPointF movePointAlongTheLine(const QPointF &point, const QLineF &direction, qreal distance, bool ensureOnLine)
1791{
1792 QPointF response = point;
1793 if (ensureOnLine) {
1794 response = findNearestPointOnLine(point, direction);
1795 }
1796 return movePointInTheDirection(response, direction.p2() - direction.p1(), distance);
1797}
1798
1799
1800
1801QLineF getLineFromElements(const QPainterPath &shape, int index)
1802{
1803 QPainterPath::Element element1 = shape.elementAt(wrapValue(index, 0, shape.elementCount() - 1));
1804 QPainterPath::Element element2 = shape.elementAt(wrapValue(index + 1, 0, shape.elementCount() - 1));
1805 // it doesn't handle isCurveTo, just assumes it's a line... sorry!
1806
1807 return QLineF(element1, element2);
1808}
1809
1810// local
1811QLineF reverseDirection(const QLineF& line)
1812{
1813 return QLineF(line.p2(), line.p1());
1814}
1815
1816// local
1817QPainterPath removeGutterOneEndSmart(const QPainterPath &shape1, int index1, const QPainterPath &shape2, int index2, QLineF middleLine, bool reverseFirstPoly, bool reverseSecondPoly)
1818{
1819 // moving from shape1.index1.p1 to shape1.index2.p2
1820
1821 auto reverseIfNeeded = [] (const QLineF &line, bool reverse) {
1822 if (reverse) {
1823 return reverseDirection(line);
1824 }
1825 return line;
1826 };
1827
1828 QLineF line1 = reverseIfNeeded(getLineFromElements(shape1, index1), reverseFirstPoly);
1829 QLineF line2 = reverseIfNeeded(getLineFromElements(shape2, index2), reverseSecondPoly);
1830
1831 QLineF leftLine;
1832 QLineF rightLine;
1833
1834 int indexMultiplierFirst = reverseFirstPoly ? -1 : 1;
1835 int indexMultiplierSecond = reverseSecondPoly ? -1 : 1;
1836
1837 leftLine = reverseIfNeeded(getLineFromElements(shape1, index1 - indexMultiplierFirst*1), reverseFirstPoly);
1838 rightLine = reverseIfNeeded(getLineFromElements(shape2, index2 + indexMultiplierSecond*1), reverseSecondPoly);
1839
1840 qreal leftPointDistanceFromMiddle;
1841 qreal rightPointDistanceFromMiddle;
1842
1843 QPointF leftPoint = line1.p1();
1844 QPointF rightPoint = line2.p2();
1845
1846 leftPointDistanceFromMiddle = kisDistanceToLine(leftPoint, middleLine);
1847 rightPointDistanceFromMiddle = kisDistanceToLine(rightPoint, middleLine);
1848
1849 QPainterPath simpleResult;
1850 simpleResult.moveTo(leftPoint);
1851 simpleResult.lineTo(rightPoint);
1852
1853 auto makeTrianglePath = [leftPoint, rightPoint] (QPointF middle) {
1854 QPainterPath result;
1855 result.moveTo(leftPoint);
1856 result.lineTo(middle);
1857 result.lineTo(rightPoint);
1858 return result;
1859 };
1860
1861 QPointF intersectionPoint;
1862 QLineF::IntersectionType intersectionType = leftLine.intersects(rightLine, &intersectionPoint);
1863
1864 qreal distanceToMiddle = 0;
1865 if (intersectionType != QLineF::NoIntersection) {
1866 distanceToMiddle = kisDistanceToLine(intersectionPoint, middleLine);
1867 }
1868
1869
1870 if (intersectionType == QLineF::NoIntersection || distanceToMiddle > qMax(leftPointDistanceFromMiddle, rightPointDistanceFromMiddle)) {
1871
1872 // first arg: bounded line, second: unbounded
1873 boost::optional<QPointF> leftIntersection = intersectLines(line2, leftLine);
1874 boost::optional<QPointF> rightIntersection = intersectLines(line1, rightLine);
1875
1876 if (leftIntersection.has_value()) {
1877 return makeTrianglePath(leftIntersection.value());
1878 } else if (rightIntersection.has_value()) {
1879 return makeTrianglePath(rightIntersection.value());
1880 } else {
1881 return simpleResult;
1882 }
1883
1884 }
1885
1886 QPainterPath betterResult = makeTrianglePath(intersectionPoint);
1887
1888 return betterResult;
1889
1890}
1891
1892QPainterPath simplifyShape(const QPainterPath& path) {
1893
1894 VectorPath vector(path);
1895 return vector.trulySimplified().asPainterPath();
1896}
1897
1898VectorPath mergeShapesWithGutter(const VectorPath& shape1, const VectorPath& shape2, const VectorPath& oneEnd, const VectorPath& otherEnd, int index1, int index2, bool reverseSecondPoly, bool isSameShape)
1899{
1900 using VPoint = VectorPath::VectorPathPoint;
1901 QList<VPoint> result;
1902
1903 result.append(shape1.pointAt(0));
1904
1905 auto appendFrom = [] (QList<VPoint>& res, int start, int end, const VectorPath& shape) {
1906 start = qBound(0, start, shape.segmentsCount() - 1);
1907 end = qBound(0, end, shape.segmentsCount() - 1);
1908
1909 for (int i = start; i < end; i++) {
1910 QList<VPoint> segment = shape.segmentAt(i);
1911 KIS_SAFE_ASSERT_RECOVER_RETURN(segment.length() == 2);
1912 res.append(segment[1]);
1913 }
1914 };
1915
1916 VectorPath reversedShape2 = reverseSecondPoly ? shape2.reversed() : VectorPath(shape2);
1917 int reversedIndex2 = reverseSecondPoly ? (shape2.segmentsCount() - index2 - 1) : index2;
1918
1919 if (isSameShape) {
1920 int minIndex = qMin(index1, index2);
1921 int maxIndex = qMax(index1, index2);
1922
1923 // the indexes define a way to cut the shape into two shapes
1924 // and one of them is kind of more outer than the other
1925 // (they could intersect but... that's the user problem at that point, they can fix it point by point)
1926 // in normal situation, it's like an outer ring of edges and inner ring of edges (which might be just one edge, which is the most common case)
1927 // and we can find that out by checking whether the ends of the selected edges are inside or outside of the shape
1928 // and if they're inside a shape, we're assuming that's the resulting shape (and the other is discarded)
1929 // no islands in comic panels on my watch!
1930
1931 QList<VPoint> result1;
1932 // min index to max index
1933 if (minIndex < 0 || maxIndex >= shape1.segmentsCount()) {
1934 return shape1;
1935 }
1936
1937 result1 << VPoint::moveTo(shape1.segmentAt(minIndex)[1].endPoint);
1938 appendFrom(result1, minIndex + 1, maxIndex, shape1);
1939
1940 QList<VPoint> result2;
1941 result2 << VPoint::moveTo(shape1.segmentAt(maxIndex)[1].endPoint);
1942 appendFrom(result2, maxIndex + 1, shape1.segmentsCount(), shape1);
1943 appendFrom(result2, 0, minIndex, shape1);
1944
1945
1946 VectorPath minEnd = index1 > index2 ? oneEnd : otherEnd;
1947 VectorPath maxEnd = index1 > index2 ? otherEnd : oneEnd;
1948
1949 appendFrom(result1, 0, minEnd.segmentsCount(), minEnd);
1950 appendFrom(result2, 0, maxEnd.segmentsCount(), maxEnd);
1951
1952 VectorPath result1Vec(result1);
1953 VectorPath result2Vec(result2);
1954
1955
1956 // TODO: assumes winding fill
1957 if (isInsideShape(result2Vec, shape1.segmentAt(minIndex)[1].endPoint)) {
1958 // could check all
1959 return result2Vec;
1960 }
1961 return result1Vec;
1962
1963 } else {
1964 appendFrom(result, 0, index1, shape1);
1965 appendFrom(result, 0, oneEnd.segmentsCount(), oneEnd);
1966 appendFrom(result, reversedIndex2 + 1, reversedShape2.segmentsCount(), reversedShape2);
1967 appendFrom(result, 0, reversedIndex2, reversedShape2);
1968 appendFrom(result, 0, otherEnd.segmentsCount(), otherEnd);
1969 appendFrom(result, index1 + 1, shape1.segmentsCount(), shape1);
1970 }
1971
1972 return VectorPath(result);
1973
1974}
1975
1976
1977QPainterPath removeGutterSmart(const QPainterPath &shape1, int index1, const QPainterPath &shape2, int index2, bool isSameShape)
1978{
1979 if (index1 + 1 >= shape1.elementCount() || index2 + 1 >= shape2.elementCount()) {
1980 return QPainterPath();
1981 }
1982
1983 QLineF line1 = getLineFromElements(shape1, index1);
1984 QLineF line2 = getLineFromElements(shape2, index2);
1985
1986 QVector<QPointF> poly1 = QVector<QPointF>() << line1.p1() << line1.p2() << line2.p1();
1987 QVector<QPointF> poly2 = QVector<QPointF>() << line1.p2() << line2.p1() << line2.p2();
1988 bool reverseSecondPoly = false;
1989 if (polygonDirection(poly1) != polygonDirection(poly2)) {
1990 line2 = QLineF(line2.p2(), line2.p1()); // quick fix to ensure that the shape will be correct (convex)
1991 reverseSecondPoly = true;
1992 }
1993
1994 QLineF middleLine = QLineF((line1.p1() + line2.p2())/2, (line1.p2() + line2.p1())/2);
1995
1996 // more difficult version:
1997 QPainterPath oneEnd = removeGutterOneEndSmart(shape1, index1, shape2, index2, middleLine, false, reverseSecondPoly);
1998 QPainterPath otherEnd = removeGutterOneEndSmart(shape2, index2, shape1, index1, middleLine, reverseSecondPoly, false);
1999
2000 VectorPath vectorShape1 = VectorPath(shape1);
2001 VectorPath vectorShape2 = VectorPath(shape2);
2002 VectorPath vectorOneEnd = VectorPath(oneEnd);
2003 VectorPath vectorOtherEnd = VectorPath(otherEnd);
2004
2005 VectorPath vectorResultHere = mergeShapesWithGutter(vectorShape1, vectorShape2, vectorOneEnd, vectorOtherEnd, vectorShape1.pathIndexToSegmentIndex(index1), vectorShape2.pathIndexToSegmentIndex(index2), reverseSecondPoly, isSameShape);
2006 return vectorResultHere.trulySimplified(1e-02).asPainterPath();
2007
2008
2009
2010}
2011
2012
2013QList<int> getLineSegmentCrossingLineIndexes(const QLineF &line, const QPainterPath &shape)
2014{
2015 VectorPath path(shape);
2016 QList<int> indexes;
2017
2018 for (int i = 0; i < path.segmentsCount(); i++) {
2019 QList<VectorPath::VectorPathPoint> points = path.segmentAt(i);
2020 if (points.count() < 2)
2021 continue;
2022 if (points[1].type == VectorPath::VectorPathPoint::BezierTo) {
2023
2024 qreal eps = 1e-5;
2025 QVector<qreal> intersections = KisBezierUtils::intersectWithLine(points[0].endPoint, points[1].controlPoint1, points[1].controlPoint2, points[1].endPoint, line, eps);
2026 for (int i = 0; i < intersections.length(); i++) {
2027 //
2028 QPointF p = KisBezierUtils::bezierCurve(points[0].endPoint, points[1].controlPoint1, points[1].controlPoint2, points[1].endPoint, intersections[i]);
2029
2030 bool onLine = isOnLine(line, p, eps, true, true, true);
2031 if (onLine) {
2032 indexes << path.segmentIndexToPathIndex(i);
2033 break; // we need just one
2034 }
2035 }
2036
2037 } else {
2038
2039 QLineF lineSegment = path.segmentAtAsLine(i);
2040 QPointF intersection;
2041 QLineF::IntersectionType type = lineSegment.intersects(line, &intersection);
2042 if (type == QLineF::IntersectionType::BoundedIntersection) {
2043 indexes << path.segmentIndexToPathIndex(i);
2044 }
2045 }
2046 }
2047 return indexes;
2048}
2049
2050VectorPath::VectorPath(const QPainterPath &path)
2051{
2052 using VPoint = VectorPath::VectorPathPoint;
2053
2055 for (int i = 0; i < path.elementCount(); i++) {
2056 QPainterPath::Element el = path.elementAt(i);
2057 switch(el.type) {
2058 case QPainterPath::MoveToElement:
2059 m_points.append(VPoint(VPoint::MoveTo, el));
2060 break;
2061 case QPainterPath::LineToElement:
2062 m_points.append(VPoint(VPoint::LineTo, el));
2063 break;
2064 case QPainterPath::CurveToDataElement:
2066 break;
2067 case QPainterPath::CurveToElement:
2068 KIS_SAFE_ASSERT_RECOVER(i + 2 < path.elementCount()) { continue; }
2069 KIS_SAFE_ASSERT_RECOVER(path.elementAt(i + 1).type == QPainterPath::CurveToDataElement) { continue; }
2070 KIS_SAFE_ASSERT_RECOVER(path.elementAt(i + 2).type == QPainterPath::CurveToDataElement) { continue; }
2071 // Qt saves the data this way: [control point 1, CurveToElement] [control point 2, CurveToDataElement] [end point, CurveToDataElement]
2072 m_points.append(VPoint(VPoint::BezierTo, path.elementAt(i + 2), el, path.elementAt(i + 1)));
2073 i += 2; // skip next two points
2074 break;
2075 }
2076 }
2077}
2078
2080{
2081 m_points = path;
2082}
2083
2085{
2086 return m_points.length();
2087}
2088
2090{
2091 return m_points[i];
2092}
2093
2095{
2096 return m_points.length() - 1 >= 0 ? m_points.length() - 1 : 0;
2097}
2098
2100{
2102 if (i < 0 || i >= segmentsCount()) {
2103 return response;
2104 }
2105 response << m_points[i] << m_points[i + 1];
2106 return response;
2107}
2108
2109std::optional<VectorPath::Segment> VectorPath::segmentAtAsSegment(int i) const
2110{
2112 if (segment.count() == 2) {
2113 return VectorPath::Segment(segment[0], segment[1]);
2114 }
2115 return std::nullopt;
2116}
2117
2119{
2121 if (points.length() < 1) {
2122 return QLineF();
2123 }
2124 return QLineF(points[0].endPoint, points[1].endPoint);
2125}
2126
2128{
2129 qreal eps = 1e-5;
2131 QLineF segLine = QLineF(segment.startPoint, segment.endPoint);
2132 QPointF intersection;
2133 if (segLine.intersects(line, &intersection) == QLineF::BoundedIntersection) {
2134 return QList<QPointF> {intersection};
2135 } else {
2136 return QList<QPointF>();
2137 }
2138
2139 } else {
2140 QList<QPointF> result;
2141 // check for intersections
2142 QVector<qreal> intersections = KisBezierUtils::intersectWithLine(segment.startPoint, segment.controlPoint1, segment.controlPoint2, segment.endPoint, line, eps);
2143 for (int i = 0; i < intersections.count(); i++) {
2144 QPointF p = KisBezierUtils::bezierCurve(segment.startPoint, segment.controlPoint1, segment.controlPoint2, segment.endPoint, intersections[i]);
2145 bool onLine = isOnLine(line, p, eps, true, true, true);
2146 if (onLine) {
2147 result << p;
2148 }
2149 }
2150
2151 return result;
2152 }
2153
2154}
2155
2160
2162{
2163 // first should be MoveTo
2164 int pathIndex = 0;
2165 for (int i = 1; i < m_points.length(); i++) {
2166 if (index < pathIndex) {
2167 // it was the previous segment, it's just a control point
2168 return i - 1;
2169 }
2170 if (index == pathIndex) {
2171 return i - 1;
2172 }
2173 if (m_points[i].type == VectorPathPoint::BezierTo) {
2174 pathIndex += 2; // add space for control points
2175 }
2176 pathIndex++;
2177 }
2178 return m_points.length() - 1;
2179}
2180
2182{
2183 // first should be MoveTo
2184 int pathIndex = 0;
2185 for (int i = 1; i < m_points.length(); i++) {
2186 if (i - 1 == index) {
2187 if (m_points[i - 1].type == VectorPathPoint::BezierTo) {
2188 return pathIndex - 2;
2189 }
2190 return pathIndex;
2191 }
2192 if (m_points[i].type == VectorPathPoint::BezierTo) {
2193 pathIndex += 2; // add space for control points
2194 }
2195 pathIndex++;
2196 }
2197 if (m_points.length() > 0 && m_points[m_points.length() - 1].type == VectorPathPoint::BezierTo) {
2198 return pathIndex - 2;
2199 }
2200 return pathIndex;
2201}
2202
2204{
2205 qreal eps = kisDegreesToRadians(epsDegrees);
2206
2207
2208 using VPoint = VectorPath::VectorPathPoint;
2209
2210 if (m_points.length() < 1) {
2212 }
2213
2214 QList<VPoint> response;
2215 VPoint lineBeginPoint = VPoint(VPoint::MoveTo, QPointF(0, 0));
2216 VPoint previousPoint = m_points[0];
2217
2218
2219 //qreal eps = 1e-05;
2220 auto onTheLine = [eps] (QPointF start, QPointF middle, QPointF end) {
2221 QLineF line1(start, end);
2222 QLineF line2(start, middle);
2223
2224 return (qAbs(crossProduct(end - start, middle - start)/line1.length()/line2.length()) < eps);
2225 };
2226
2227 for (int i = 1; i < m_points.length(); i++) {
2228 if (m_points[i].type == VPoint::MoveTo) {
2229 if (previousPoint.type == VPoint::MoveTo) {
2230 previousPoint = m_points[i]; // let's skip the previous point since they're both just moving
2231 continue;
2232 } else { // LineTo or BezierTo, both need to be added
2233 response << previousPoint;
2234 previousPoint = m_points[i];
2235 continue;
2236 }
2237 } else if (m_points[i].type == VPoint::LineTo) {
2238 if (previousPoint.type == VPoint::LineTo) {
2239 // check whether they're parallel
2240 if (!onTheLine(lineBeginPoint.endPoint, previousPoint.endPoint, m_points[i].endPoint)) {
2241 response << previousPoint;
2242 lineBeginPoint = previousPoint;
2243 }
2244 previousPoint = m_points[i]; // let's skip the previous point since they're parallel
2245 continue;
2246 } else { // MoveTo or BezierTo, both need to be added
2247 response << previousPoint;
2248 lineBeginPoint = previousPoint;
2249 previousPoint = m_points[i];
2250 continue;
2251 }
2252 } else { // currently in BezierTo
2253 response << previousPoint;
2254 previousPoint = m_points[i];
2255 continue;
2256 }
2257 }
2258 response << previousPoint;
2259
2260 // now eliminate additional point at the beginning of the shape if it's in the middle of a line
2261 // and wasn't simplified only because it's at the beginning
2262
2263 if (response.length() < 3) {
2264 return VectorPath(response);
2265 }
2266
2267 VPoint start = response[0];
2268 VPoint second = response[1];
2269
2270 if (start.type == VPoint::MoveTo) {
2271 if (fuzzyPointCompare(previousPoint.endPoint, start.endPoint)) {
2272 // they are the same point, now whether they're on the line
2273 if (second.type == VPoint::LineTo && onTheLine(response[response.length()-2].endPoint, start.endPoint, second.endPoint)) {
2274 response.pop_front();
2275 response[0].type = VPoint::MoveTo;
2276 response[response.length() - 1].endPoint = second.endPoint;
2277 }
2278 }
2279
2280 } else if (response[0].type == VPoint::LineTo) { // first point is then QPointF(0, 0)
2281 if (qFuzzyIsNull(previousPoint.endPoint.x()) && qFuzzyIsNull(previousPoint.endPoint.y())) {
2282 // they are the same point, now whether they're on the line
2283 if (second.type == VPoint::LineTo && onTheLine(previousPoint.endPoint, QPointF(0, 0), start.endPoint)) {
2284 // note: leaving this debug for future reference
2285 qCritical() << "We're in this case";
2286 //response.pop_front();
2287 //response[0].type = VPoint::MoveTo;
2288 //response[response.length() - 1].endPoint = start.endPoint;
2289 }
2290 }
2291 }
2292
2293
2294
2295
2296 return VectorPath(response);
2297}
2298
2300{
2301 using VPoint = VectorPath::VectorPathPoint;
2302 QList<VPoint> response;
2303 response.append(VPoint(VPoint::MoveTo, m_points[m_points.length() - 1].endPoint));
2304 for (int i = m_points.length() - 1; i > 0; i--) {
2305 // to reverse a segment, we take the end VPoint and change the .endPoint to the start VPoint coords
2306 VPoint point = m_points[i];
2307 point.endPoint = m_points[i - 1].endPoint;
2308 response.append(point);
2309 }
2310 return VectorPath(response);
2311}
2312
2314{
2315 using VPoint = VectorPath::VectorPathPoint;
2316
2317 if (segmentsCount() != path.segmentsCount()) {
2318 return false;
2319 }
2320
2321 bool fuzzy = qFuzzyIsNull(eps);
2322
2323 auto comparePoints = [fuzzy, eps] (QPointF left, QPointF right) {
2324 if (fuzzy) {
2325 return fuzzyPointCompare(left, right);
2326 } else {
2327 return fuzzyPointCompare(left, right, eps);
2328 }
2329 };
2330
2331
2332 VPoint leftStart = path.pointAt(0);
2333 QList<int> rightStartingPoints;
2334 for (int i = 0; i < path.pointsCount(); i++) {
2335 if (comparePoints(leftStart.endPoint, path.pointAt(i).endPoint)) {
2336 rightStartingPoints << i;
2337 }
2338 }
2339
2340 Q_FOREACH(int startId, rightStartingPoints) {
2341 bool isEqual = true;
2342 for (int i = 0; i < pointsCount(); i++) {
2343 int rightId = wrapValue(i + startId, 0, pointsCount());
2344 if (!comparePoints(pointAt(i).endPoint, path.pointAt(rightId).endPoint)) {
2345 isEqual = false;
2346 break;
2347 }
2348 }
2349 if (isEqual) {
2350 return true;
2351 }
2352 for (int i = 0; i < pointsCount(); i++) {
2353 int rightId = wrapValue(startId - i, 0, pointsCount());
2354 if (!comparePoints(pointAt(i).endPoint, path.pointAt(rightId).endPoint)) {
2355 isEqual = false;
2356 break;
2357 }
2358 }
2359 if (isEqual) {
2360 return true;
2361 }
2362 }
2363
2364 return false;
2365}
2366
2367QPainterPath VectorPath::asPainterPath() const
2368{
2369 using VPoint = VectorPath::VectorPathPoint;
2370 if (m_originalPath.elementCount() > 0) {
2371 return m_originalPath;
2372 }
2373 QPainterPath response;
2374 for (int i = 0; i < pointsCount(); i++) {
2375 switch(m_points[i].type) {
2376 case VPoint::MoveTo:
2377 response.moveTo(m_points[i].endPoint);
2378 break;
2379 case VPoint::LineTo:
2380 response.lineTo(m_points[i].endPoint);
2381 break;
2382 case VPoint::BezierTo:
2383 response.cubicTo(m_points[i].controlPoint1, m_points[i].controlPoint2, m_points[i].endPoint);
2384 break;
2385 }
2386 }
2387 response.closeSubpath();
2388 return response;
2389}
2390
2391QDebug operator<<(QDebug debug, const VectorPath &path)
2392{
2393 debug << path.asPainterPath();
2394 return debug;
2395}
2396
2397QDebug operator<<(QDebug debug, const VectorPath::VectorPathPoint &point)
2398{
2399 bool autoInsertSpaces = debug.autoInsertSpaces();
2400 debug.setAutoInsertSpaces(false);
2401 debug << (point.type == VectorPath::VectorPathPoint::MoveTo ? "(move " : (point.type == VectorPath::VectorPathPoint::BezierTo ? "(curve " : "(line "));
2402 debug << "(" << point.endPoint.x() << ", " << point.endPoint.y() << ")";
2404 debug << ": (";
2405 debug << "(" << point.controlPoint1.x() << ", " << point.controlPoint1.y() << ")";
2406 debug << ", ";
2407 debug << "(" << point.controlPoint2.x() << ", " << point.controlPoint2.y() << ")";
2408 debug << ")";
2409 }
2410 debug << ")";
2411 debug.setAutoInsertSpaces(autoInsertSpaces);
2412 return debug;
2413}
2414
2415bool isInsideShape(const VectorPath &path, const QPointF &point)
2416{
2417 if (path.pointsCount() == 0) {
2418 return false;
2419 }
2420
2421 QRectF boundRect;
2422 accumulateBounds(path.pointAt(0).endPoint, &boundRect);
2423
2424 bool isPolygon = true;
2425
2426 for (int i = 0; i < path.segmentsCount(); i++) {
2427 QList<VectorPath::VectorPathPoint> points = path.segmentAt(i);
2428 accumulateBounds(points[0].endPoint, &boundRect);
2429 accumulateBounds(points[1].endPoint, &boundRect);
2430
2431 if (points[1].type == VectorPath::VectorPathPoint::BezierTo) {
2432 accumulateBounds(points[1].controlPoint1, &boundRect);
2433 accumulateBounds(points[1].controlPoint2, &boundRect);
2434 isPolygon = false;
2435 }
2436 }
2437
2438 if (!boundRect.contains(point)) {
2439 return false;
2440 }
2441
2442 if (isPolygon) {
2443 return path.asPainterPath().toFillPolygon().containsPoint(point, Qt::WindingFill);
2444 }
2445
2446 boundRect = kisGrowRect(boundRect, 5); // just safety margins
2447
2448 // NOTE: it would be way faster to do it taking into account the fact that the lines are vertical and horizontal
2449 // except for the last one maybe
2450 // but let's wait until it proves to be a problem
2451 QList<QLineF> lines;
2452 lines << QLineF(point, QPointF(boundRect.left(), point.y()));
2453 lines << QLineF(point, QPointF(boundRect.right(), point.y()));
2454 lines << QLineF(point, QPointF(point.x(), boundRect.top()));
2455 lines << QLineF(point, QPointF(point.x(), boundRect.bottom()));
2456 lines << QLineF(point, boundRect.topLeft()); // last resort, diagonal
2457
2458 int intersectionsCount = 0;
2459
2460 for (int k = 0; k < lines.length(); k++) {
2461 int intersections = 0;
2462 QLineF line = lines[k];
2463 bool checkNext = false;
2464
2465 for (int i = 0; i < path.segmentsCount(); i++) {
2466 std::optional<VectorPath::Segment> segmentOpt = path.segmentAtAsSegment(i);
2467 KIS_SAFE_ASSERT_RECOVER(segmentOpt.has_value()) {continue;}
2468 VectorPath::Segment segment = segmentOpt.value();
2469
2470 QList<QPointF> intersectionsHere = VectorPath::intersectSegmentWithLineBounded(line, segment);
2471 qreal eps = 1e-4;
2472
2473 for (int i = 0; i < intersectionsHere.count(); i++) {
2474 if (fuzzyPointCompare(intersectionsHere[i], segment.startPoint, eps) || fuzzyPointCompare(intersectionsHere[i], segment.endPoint, eps)) {
2475 checkNext = true;
2476 break;
2477 }
2478 }
2479
2480 if (checkNext) {
2481 break;
2482 }
2483
2484 intersections += intersectionsHere.count();
2485 }
2486
2487 intersectionsCount = intersections;
2488 if (!checkNext) {
2489 break;
2490 }
2491 }
2492
2493 return intersectionsCount%2 == 1;
2494}
2495
2496bool isInsideShape(const QPainterPath &path, const QPointF &point)
2497{
2498 for (int i = 0; i < path.elementCount(); i++) {
2499 if (path.elementAt(i).type == QPainterPath::CurveToDataElement) {
2500 // contains control points
2501 return isInsideShape(VectorPath(path), point);
2502 }
2503 }
2504
2505 return path.toFillPolygon().containsPoint(point, Qt::WindingFill);
2506}
2507
2508bool isOnLine(const QLineF &line, const QPointF &point, const qreal eps, bool boundedStart, bool boundedEnd, bool includeEnds)
2509{
2510 if (fuzzyPointCompare(point, line.p1(), eps)) {
2511 return includeEnds || !boundedStart;
2512 }
2513 if (fuzzyPointCompare(point, line.p2(), eps)) {
2514 return includeEnds || !boundedEnd;
2515 }
2516
2517 if (kisDistanceToLine(point, line) > eps) {
2518 return false;
2519 }
2520
2521 QPointF lineVector = line.p2() - line.p1();
2522 QPointF pointVector = point - line.p1();
2523 qreal t = -1;
2524 if (qAbs(pointVector.x()) < eps) {
2525 // use y
2526 t = pointVector.y()/lineVector.y();
2527 } else {
2528 t = pointVector.x()/lineVector.x();
2529 }
2530
2531 if (t < 0) {
2532 return !boundedStart;
2533 }
2534 if (t > 1.0) {
2535 return !boundedEnd;
2536 }
2537
2538 return true;
2539}
2540
2541
2542} // namespace
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
QPointF r2
QPointF q1
const Params2D p
QPointF p0
QPointF r1
QPointF p2
QPointF q2
QPointF p3
QPointF p1
static qreal B2(qreal u)
static qreal B1(qreal u)
qreal distance(const QPointF &p1, const QPointF &p2)
qreal D(qreal t, const QPointF &P0, const QPointF &P1, const QPointF &P2, const QPointF &P3, const QPointF &p)
#define C(i, j)
QList< VectorPathPoint > segmentAt(int i) const
VectorPath reversed() const
VectorPath(const QPainterPath &path)
int pathIndexToSegmentIndex(int index)
static QList< QPointF > intersectSegmentWithLineBounded(const QLineF &line, const Segment &segment)
QPainterPath asPainterPath() const
bool fuzzyComparePointsCyclic(const VectorPath &path, qreal eps=0.0f) const
QList< VectorPathPoint > m_points
std::optional< Segment > segmentAtAsSegment(int i) const
VectorPathPoint pointAt(int i) const
VectorPath trulySimplified(qreal epsDegrees=0.5) const
int segmentIndexToPathIndex(int index)
QLineF segmentAtAsLine(int i) const
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
const qreal eps
#define dbgKrita
Definition kis_debug.h:45
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
T kisRadiansToDegrees(T radians)
Definition kis_global.h:181
T pow2(const T &x)
Definition kis_global.h:166
qreal kisSquareDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:256
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
QPainterPath removeGutterOneEndSmart(const QPainterPath &shape1, int index1, const QPainterPath &shape2, int index2, QLineF middleLine, bool reverseFirstPoly, bool reverseSecondPoly)
boost::optional< QPointF > findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest)
QVector< QPointF > intersectTwoCircles(const QPointF &center1, qreal r1, const QPointF &center2, qreal r2)
QLineF getLineFromElements(const QPainterPath &shape, int index)
bool tryMergePoints(QPainterPath &path, const QPointF &startPoint, const QPointF &endPoint, qreal &distance, qreal distanceThreshold, bool lastSegment)
Point ensureInRectImpl(Point pt, const Rect &bounds)
QTransform toQTransformStraight(const Eigen::Matrix3d &m)
QVector< Point > sampleRectWithPoints(const Rect &rect)
QPointF movePointInTheDirection(const QPointF &point, const QPointF &direction, qreal distance)
movePointAlongTheLine moves the point a particular distance in the specified direction
T wrapValue(T value, T wrapBounds)
QRect approximateRectFromPoints(const QVector< QPoint > &points)
QList< QPainterPath > getPathsFromRectangleCutThrough(const QRectF &rect, const QLineF &leftLine, const QLineF &rightLine)
getPathsFromRectangleCutThrough get paths defining both sides of a rectangle cut through using two (s...
void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
QPainterPath simplifyShape(const QPainterPath &path)
VectorPath mergeShapesWithGutter(const VectorPath &shape1, const VectorPath &shape2, const VectorPath &oneEnd, const VectorPath &otherEnd, int index1, int index2, bool reverseSecondPoly, bool isSameShape)
mergeShapesWithGutter merges two shapes with a gutter shape (defined as two paths)
std::pair< QPointF, QTransform > transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal)
qreal findMinimumGoldenSection(std::function< qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter=100)
QPolygonF calculateConvexHull(const QPolygonF &polygon)
calculateConvexHull Calculate the convex hull of the polygon using the QuickHull
bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta)
void accumulateBounds(const Point &pt, Rect *bounds)
qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
void cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
QTransform mapToRectInverse(const QRectF &rect)
QPointF moveElasticPoint(const QPointF &pt, const QPointF &base, const QPointF &newBase, const QPointF &wingA, const QPointF &wingB)
moveElasticPoint moves point pt based on the model of elasticity
QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
trySimplifyPath Tries to simplify a QPainterPath
qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
qreal pointToLineDistSquared(const QPointF &pt, const QLineF &line)
QPoint ensureInRect(QPoint pt, const QRect &bounds)
int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
QPointF alignForZoom(const QPointF &pt, qreal zoom)
QLineF reverseDirection(const QLineF &line)
void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
Crop line to rect; if it doesn't intersect, just return an empty line (QLineF()).
QPainterPath removeGutterSmart(const QPainterPath &shape1, int index1, const QPainterPath &shape2, int index2, bool isSameShape)
removeGutterSmart
bool isInRange(T x, T a, T b)
QDebug operator<<(QDebug debug, const VectorPath &path)
QList< QLineF > intersectLineConcavePolygon(const QPolygonF polygon, const QLineF &line, bool extendFirst, bool extendSecond)
QPainterPath smallArrow()
qreal norm(const T &a)
QPointF movePointAlongTheLine(const QPointF &point, const QLineF &direction, qreal distance, bool ensureOnLine)
movePointAlongTheLine moves the point a particular distance in the specified direction
QPolygonF combineConvexHullParts(const QPolygonF &leftPolygon, QPolygonF &rightPolygon, bool triangular)
QVector< QPointF > findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b)
QPointF findNearestPointOnLine(const QPointF &point, const QLineF &line, bool unbounded)
bool isInsideShape(const VectorPath &path, const QPointF &point)
PointTypeTraits< T >::value_type dotProduct(const T &a, const T &b)
QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
QPolygonF calculateConvexHullFromPointsOverTheLine(const QPolygonF &points, const QLineF &line)
qreal findMinimumTernarySection(std::function< qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter=100)
boost::optional< QPointF > intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine)
PointTypeTraits< T >::value_type crossProduct(const T &a, const T &b)
QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2)
int polygonDirection(const QVector< T > &polygon)
T inwardUnitNormal(const T &a, int polygonDirection)
int lineSideForPoint(const QLineF &line, const QPointF &point)
Rect approximateRectFromPointsImpl(const QVector< Point > &points)
Eigen::Matrix3d fromQTransformStraight(const QTransform &t)
bool intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
bool isOnLine(const QLineF &line, const QPointF &point, const qreal eps, bool boundedStart, bool boundedEnd, bool includeEnds)
QList< QLineF > getParallelLines(const QLineF &line, const qreal distance)
QRect approximateRectWithPointTransform(const QRect &rect, std::function< QPointF(QPointF)> func)
QList< int > getLineSegmentCrossingLineIndexes(const QLineF &line, const QPainterPath &shape)
bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
bool intersectLineRect(QLineF &line, const QRect rect, bool extend)
QTransform mapToRect(const QRectF &rect)
QPainterPath getOnePathFromRectangleCutThrough(const QList< QPointF > &points, const QLineF &line, bool left)
QVector< qreal > intersectWithLine(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, qreal eps)
QPointF bezierCurve(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)
const unsigned char salt[256][256]
Definition rand_salt.h:30
QTransform shearTransform() const
QTransform rotateTransform() const
QTransform scaleTransform() const
QTransform translateTransform() const
static VectorPathPoint moveTo(QPointF _endPoint)
static VectorPathPoint lineTo(QPointF _endPoint)