6#include <QtGui/private/qtriangulator_p.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qhash.h>
12Q_LOGGING_CATEGORY(lcSGCurveProcessor,
"qt.quick.curveprocessor");
18static inline QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
20 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
22 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
23 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
30static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
32 QVector2D v1 = 2 * (p1 - p0);
33 QVector2D v2 = p2 - v1 - p0;
34 return uvForPoint(v1, v2, p - p0);
37static QVector3D elementUvForPoint(
const QQuadPath::Element& e, QVector2D p)
39 auto uv = curveUv(e.startPoint(), e.referencePoint(), e.endPoint(), p);
41 return { uv.x(), uv.y(), 0.0f };
43 return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
46static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
49 return {v.y(), -v.x()};
53static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
55 float dot = QVector2D::dotProduct(p - a, n);
59static inline float determinant(
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3)
61 return p1.x() * (p2.y() - p3.y())
62 + p2.x() * (p3.y() - p1.y())
63 + p3.x() * (p1.y() - p2.y());
67
68
69
70
71
72
73
74
75using TrianglePoints = std::array<QVector2D, 3>;
76using LinePoints = std::array<QVector2D, 2>;
80static inline double determinant(
const TrianglePoints &p)
82 return determinant(p[0], p[1], p[2]);
86static void fixWinding(TrianglePoints &p)
88 double det = determinant(p);
97bool checkEdge(
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p,
float epsilon)
99 return determinant(p1, p2, p) <= epsilon;
104bool lineIntersection(
const LinePoints &l1,
const LinePoints &l2, QList<std::pair<
float,
float>> *solution =
nullptr)
106 constexpr double eps2 = 1e-5;
109 const float A = l1[0].x();
110 const float B = l1[1].x() - l1[0].x();
111 const float C = l2[0].x();
112 const float D = l2[1].x() - l2[0].x();
113 const float E = l1[0].y();
114 const float F = l1[1].y() - l1[0].y();
115 const float G = l2[0].y();
116 const float H = l2[1].y() - l2[0].y();
118 float det = D * F - B * H;
123 float s = (F * (A - C) - B * (E - G)) / det;
124 float t = (H * (A - C) - D * (E - G)) / det;
127 bool intersecting = (s >= 0 && s <= 1. - eps2 && t >= 0 && t <= 1. - eps2);
129 if (solution && intersecting)
130 solution->append(std::pair<
float,
float>(t, s));
136bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2,
float epsilon = 1.0/32)
139 fixWinding(triangle1);
140 for (
int i = 0; i < 3; i++) {
141 int ni = (i + 1) % 3;
142 if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
143 checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
144 checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
149 fixWinding(triangle2);
150 for (
int i = 0; i < 3; i++) {
151 int ni = (i + 1) % 3;
153 if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
154 checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
155 checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
162bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line,
float epsilon = 1.0/32)
165 bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
166 bool s2 = determinant(line[0], line[1], triangle[1]) < 0;
167 bool s3 = determinant(line[0], line[1], triangle[2]) < 0;
169 if (s1 == s2 && s2 == s3) {
173 fixWinding(triangle);
174 for (
int i = 0; i < 3; i++) {
175 int ni = (i + 1) % 3;
176 if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
177 checkEdge(triangle[i], triangle[ni], line[1], epsilon))
184static bool isOverlap(
const QQuadPath &path,
int e1,
int e2)
186 const QQuadPath::Element &element1 = path.elementAt(e1);
187 const QQuadPath::Element &element2 = path.elementAt(e2);
189 if (element1.isLine()) {
190 LinePoints line1{ element1.startPoint(), element1.endPoint() };
191 if (element2.isLine()) {
192 LinePoints line2{ element2.startPoint(), element2.endPoint() };
193 return lineIntersection(line1, line2);
195 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
196 return checkLineTriangleOverlap(t2, line1);
199 TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
200 if (element2.isLine()) {
201 LinePoints line{ element2.startPoint(), element2.endPoint() };
202 return checkLineTriangleOverlap(t1, line);
204 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
205 return checkTriangleOverlap(t1, t2);
212static float angleBetween(
const QVector2D v1,
const QVector2D v2)
214 float dot = v1.x() * v2.x() + v1.y() * v2.y();
215 float cross = v1.x() * v2.y() - v1.y() * v2.x();
217 return atan2(cross, dot);
220static bool isIntersecting(
const TrianglePoints &t1,
const TrianglePoints &t2, QList<std::pair<
float,
float>> *solutions =
nullptr)
222 constexpr double eps = 1e-5;
223 constexpr double eps2 = 1e-5;
224 constexpr int maxIterations = 7;
227 std::array<QPointF, 3> td1 = { t1[0].toPointF(), t1[1].toPointF(), t1[2].toPointF() };
228 std::array<QPointF, 3> td2 = { t2[0].toPointF(), t2[1].toPointF(), t2[2].toPointF() };
233 auto F = [=](QPointF t) {
return
234 td1[0] * (1 - t.x()) * (1. - t.x()) + 2 * td1[1] * (1. - t.x()) * t.x() + td1[2] * t.x() * t.x() -
235 td2[0] * (1 - t.y()) * (1. - t.y()) - 2 * td2[1] * (1. - t.y()) * t.y() - td2[2] * t.y() * t.y();};
239 auto J = [=](QPointF t) {
return QLineF(
240 td1[0].x() * (-2 * (1-t.x())) + 2 * td1[1].x() * (1 - 2 * t.x()) + td1[2].x() * 2 * t.x(),
241 -td2[0].x() * (-2 * (1-t.y())) - 2 * td2[1].x() * (1 - 2 * t.y()) - td2[2].x() * 2 * t.y(),
242 td1[0].y() * (-2 * (1-t.x())) + 2 * td1[1].y() * (1 - 2 * t.x()) + td1[2].y() * 2 * t.x(),
243 -td2[0].y() * (-2 * (1-t.y())) - 2 * td2[1].y() * (1 - 2 * t.y()) - td2[2].y() * 2 * t.y());};
246 auto solve = [](QLineF A, QPointF b) {
248 const double det = A.x1() * A.y2() - A.y1() * A.x2();
249 QLineF Ainv(A.y2() / det, -A.y1() / det, -A.x2() / det, A.x1() / det);
251 return QPointF(Ainv.x1() * b.x() + Ainv.y1() * b.y(),
252 Ainv.x2() * b.x() + Ainv.y2() * b.y());
255#ifdef INTERSECTION_EXTRA_DEBUG
256 qCDebug(lcSGCurveIntersectionSolver) <<
"Checking" << t1[0] << t1[1] << t1[2];
257 qCDebug(lcSGCurveIntersectionSolver) <<
" vs" << t2[0] << t2[1] << t2[2];
262 constexpr std::array tref = { QPointF{0.0, 0.0}, QPointF{0.5, 0.0}, QPointF{1.0, 0.0},
263 QPointF{0.0, 0.5}, QPointF{0.5, 0.5}, QPointF{1.0, 0.5},
264 QPointF{0.0, 1.0}, QPointF{0.5, 1.0}, QPointF{1.0, 1.0} };
266 for (
auto t : tref) {
272 while (err > eps && i < maxIterations) {
273 t = t - solve(J(t), fval);
275 err = qAbs(fval.x()) + qAbs(fval.y());
277#ifdef INTERSECTION_EXTRA_DEBUG
278 qCDebug(lcSGCurveIntersectionSolver) <<
" Newton iteration" << i <<
"t =" << t <<
"F =" << fval <<
"Error =" << err;
282 if (err < eps && t.x() >=0 && t.x() <= 1. - 10 * eps2 && t.y() >= 0 && t.y() <= 1. - 10 * eps2) {
283#ifdef INTERSECTION_EXTRA_DEBUG
284 qCDebug(lcSGCurveIntersectionSolver) <<
" Newton solution (after" << i <<
")=" << t <<
"(" << F(t) <<
")";
288 for (
auto solution : *solutions) {
289 if (qAbs(solution.first - t.x()) < 10 * eps2 && qAbs(solution.second - t.y()) < 10 * eps2) {
295 solutions->append({t.x(), t.y()});
302 return solutions->size() > 0;
307static bool isIntersecting(
const QQuadPath &path,
int e1,
int e2, QList<std::pair<
float,
float>> *solutions =
nullptr)
310 const QQuadPath::Element &elem1 = path.elementAt(e1);
311 const QQuadPath::Element &elem2 = path.elementAt(e2);
313 if (elem1.isLine() && elem2.isLine()) {
314 return lineIntersection(LinePoints {elem1.startPoint(), elem1.endPoint() },
315 LinePoints {elem2.startPoint(), elem2.endPoint() },
318 return isIntersecting(TrianglePoints { elem1.startPoint(), elem1.controlPoint(), elem1.endPoint() },
319 TrianglePoints { elem2.startPoint(), elem2.controlPoint(), elem2.endPoint() },
326 TrianglePoints points;
327 TrianglePoints normals;
328 std::array<
float, 3> extrusions;
329 int pathElementIndex = std::numeric_limits<
int>::min();
332#ifndef QT_NO_DEBUG_STREAM
333 friend QDebug operator<<(QDebug,
const TriangleData &);
337#ifndef QT_NO_DEBUG_STREAM
338QDebug operator<<(QDebug stream,
const TriangleData &td)
340 QDebugStateSaver saver(stream);
342 stream <<
"TriangleData(";
343 if (td.pathElementIndex != std::numeric_limits<
int>::min())
344 stream <<
"idx " << td.pathElementIndex;
345 stream <<
" [" << td.points.at(0).x() <<
", " << td.points.at(0).y()
346 <<
"; " << td.points.at(1).x() <<
", " << td.points.at(1).y()
347 <<
"; " << td.points.at(2).x() <<
", " << td.points.at(2).y()
348 <<
"] normals [" << td.normals.at(0).x() <<
", " << td.points.at(0).y()
349 <<
"; " << td.normals.at(1).x() <<
", " << td.normals.at(1).y()
350 <<
"; " << td.normals.at(2).x() <<
", " << td.normals.at(2).y()
357inline QVector2D normalVector(QVector2D baseLine)
359 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
364
365
366
367
368QVector2D normalVector(
const QQuadPath::Element &element,
bool endSide =
false)
370 if (element.isLine())
371 return normalVector(element.endPoint() - element.startPoint());
373 return normalVector(element.controlPoint() - element.startPoint());
375 return normalVector(element.endPoint() - element.controlPoint());
379
380
381
382
383QVector2D tangentVector(
const QQuadPath::Element &element,
bool endSide =
false)
385 if (element.isLine()) {
387 return element.endPoint() - element.startPoint();
389 return element.startPoint() - element.endPoint();
392 return element.controlPoint() - element.startPoint();
394 return element.controlPoint() - element.endPoint();
399QList<TriangleData> simplePointTriangulator(
const QList<QVector2D> &pts,
const QList<QVector2D> &normals,
int elementIndex)
401 int count = pts.size();
402 Q_ASSERT(count >= 3);
403 Q_ASSERT(normals.size() == count);
407 float det1 = determinant(pts[0], pts[1], pts[2]);
412 auto connectableInHull = [&](
int idx) -> QList<
int> {
414 const int n = hull.size();
415 const auto &pt = pts[idx];
416 for (
int i = 0; i < n; ++i) {
417 const auto &i1 = hull.at(i);
418 const auto &i2 = hull.at((i+1) % n);
419 if (determinant(pts[i1], pts[i2], pt) < 0.0f)
424 for (
int i = 3; i < count; ++i) {
425 auto visible = connectableInHull(i);
426 if (visible.isEmpty())
428 int visCount = visible.count();
429 int hullCount = hull.count();
432 int boundaryStart = visible[0];
433 for (
int j = 0; j < visCount - 1; ++j) {
434 if ((visible[j] + 1) % hullCount != visible[j+1]) {
435 boundaryStart = visible[j + 1];
442 int pointsToKeep = hullCount - visCount + 1;
445 for (
int j = 0; j < pointsToKeep; ++j) {
446 newHull << hull.at((j + boundaryStart + visCount) % hullCount);
452 QList<TriangleData> ret;
453 for (
int i = 1; i < hull.size() - 1; ++i) {
457 ret.append({{pts[i0], pts[i1], pts[i2]}, {normals[i0], normals[i1], normals[i2]},
458 QSGCurveStrokeNode::defaultExtrusions(), elementIndex});
464inline bool needsSplit(
const QQuadPath::Element &el)
466 Q_ASSERT(!el.isLine());
467 const auto v1 = el.controlPoint() - el.startPoint();
468 const auto v2 = el.endPoint() - el.controlPoint();
469 float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
474inline void splitElementIfNecessary(QQuadPath *path,
int index,
int level) {
475 if (level > 0 && needsSplit(path->elementAt(index))) {
476 path->splitElementAt(index);
477 splitElementIfNecessary(path, path->indexOfChildAt(index, 0), level - 1);
478 splitElementIfNecessary(path, path->indexOfChildAt(index, 1), level - 1);
482static QQuadPath subdivide(
const QQuadPath &path,
int subdivisions)
484 QQuadPath newPath = path;
485 newPath.iterateElements([&](QQuadPath::Element &e,
int index) {
487 splitElementIfNecessary(&newPath, index, subdivisions);
494
495
496
497
498
499static std::array<QVector2D, 5> calculateJoin(
const QQuadPath::Element *element1,
const QQuadPath::Element *element2,
500 float penFactor,
float inverseMiterLimit,
bool simpleMiter,
501 bool &outerBisectorWithinMiterLimit,
bool &innerIsRight,
bool &giveUp)
503 outerBisectorWithinMiterLimit =
true;
508 QVector2D n = normalVector(*element2);
509 return {n, n, -n, -n, -n};
513 QVector2D n = normalVector(*element1,
true);
514 return {n, n, -n, -n, -n};
517 Q_ASSERT(element1->endPoint() == element2->startPoint());
519 const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
520 const auto p2 = element1->endPoint();
521 const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
523 const auto v1 = (p1 - p2).normalized();
524 const auto v2 = (p3 - p2).normalized();
525 const auto b = (v1 + v2);
527 constexpr float epsilon = 1.0f / 32.0f;
528 const bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
533 QVector2D n1(-v1.y(), v1.x());
534 QVector2D n2(-v2.y(), v2.x());
535 QVector2D n = (n2 - n1).normalized();
536 return {n, n, -n, -n, -n};
543 const auto bisector = b.normalized();
544 const float cos2x = qMin(1.0f, QVector2D::dotProduct(v1, v2));
545 const float sine = qMax(sqrt((1.0f - cos2x) / 2), 0.01f);
546 const float length = penFactor / sine;
547 innerIsRight = determinant(p1, p2, p3) > 0;
550 auto tooLong = [penFactor, length](QVector2D p1, QVector2D p2, QVector2D n) ->
bool {
556 auto projLen = QVector2D::dotProduct(v, n);
557 return projLen * 0.9f < length &&
558 (QSGCurveStrokeNode::expandingStrokeEnabled() ? ((v - n * projLen).length() - 2.0) * 0.5
559 : (v - n * projLen).length() * 0.9) < penFactor;
564 giveUp = !element1->isLine() || !element2->isLine() || tooLong(p1, p2, bisector) || tooLong(p3, p2, bisector);
565 outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
566 bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
567 const QVector2D bn = bisector / sine;
570 return {bn, bn, -bn, -bn, -bn};
571 const QVector2D n1 = normalVector(*element1,
true);
572 const QVector2D n2 = normalVector(*element2);
575 return {n1, n2, -n1, -n2, -bn};
577 return {-n1, -n2, n1, n2, -bn};
581 return {bn, bn, -n1, -n2, -bn};
583 return {bn, bn, n1, n2, -bn};
587static QList<TriangleData> customTriangulator2(
const QQuadPath &path,
float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle,
float miterLimit)
589 const bool bevelJoin = joinStyle == Qt::BevelJoin;
590 const bool roundJoin = joinStyle == Qt::RoundJoin;
591 const bool miterJoin = !bevelJoin && !roundJoin;
593 const bool roundCap = capStyle == Qt::RoundCap;
594 const bool squareCap = capStyle == Qt::SquareCap;
596 const bool simpleMiter = joinStyle == Qt::RoundJoin;
598 Q_ASSERT(miterLimit > 0 || !miterJoin);
599 float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
601 const float penFactor = penWidth / 2;
603 QList<TriangleData> ret;
605 auto triangulateCurve = [&ret, &path, penFactor]
606 (
int idx,
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3,
const QVector2D &p4,
607 const QVector2D &n1,
const QVector2D &n2,
const QVector2D &n3,
const QVector2D &n4)
609 const auto &element = path.elementAt(idx);
610 Q_ASSERT(!element.isLine());
611 const auto &s = element.startPoint();
612 const auto &c = element.controlPoint();
613 const auto &e = element.endPoint();
615 bool controlPointOnRight = determinant(s, c, e) > 0;
616 QVector2D startNormal = normalVector(element);
617 QVector2D endNormal = normalVector(element,
true);
618 QVector2D controlPointNormal = (startNormal + endNormal).normalized();
619 if (controlPointOnRight)
620 controlPointNormal = -controlPointNormal;
621 QVector2D p5 = c + controlPointNormal * penFactor;
622 TrianglePoints t1{p1, p2, p5};
623 TrianglePoints t2{p3, p4, p5};
624 bool simpleCase = !checkTriangleOverlap(t1, t2);
627 ret.append({{p1, p2, p5}, {n1, n2, controlPointNormal}, {}, idx});
628 ret.append({{p3, p4, p5}, {n3, n4, controlPointNormal}, {}, idx});
629 if (controlPointOnRight) {
630 ret.append({{p1, p3, p5}, {n1, n3, controlPointNormal}, {}, idx});
632 ret.append({{p2, p4, p5}, {n2, n4, controlPointNormal}, {}, idx});
635 ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
643 int count = path.elementCount();
645 while (subStart < count) {
646 int subEnd = subStart;
647 for (
int i = subStart + 1; i < count; ++i) {
648 const auto &e = path.elementAt(i);
649 if (e.isSubpathStart()) {
653 if (i == count - 1) {
658 bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
659 const int subCount = subEnd - subStart + 1;
661 auto addIdx = [&](
int idx,
int delta) ->
int {
662 int subIdx = idx - subStart;
664 subIdx = (subIdx + subCount + delta) % subCount;
667 return subStart + subIdx;
669 auto elementAt = [&](
int idx,
int delta) ->
const QQuadPath::Element * {
670 int subIdx = idx - subStart;
672 subIdx = (subIdx + subCount + delta) % subCount;
673 return &path.elementAt(subStart + subIdx);
676 if (subIdx >= 0 && subIdx < subCount)
677 return &path.elementAt(subStart + subIdx);
681 for (
int i = subStart; i <= subEnd; ++i) {
682 const auto &element = path.elementAt(i);
683 const auto *nextElement = elementAt(i, +1);
684 const auto *prevElement = elementAt(i, -1);
686 const auto &s = element.startPoint();
687 const auto &e = element.endPoint();
689 bool startInnerIsRight;
690 bool startBisectorWithinMiterLimit;
691 bool giveUpOnStartJoin;
692 auto startJoin = calculateJoin(prevElement, &element,
693 penFactor, inverseMiterLimit, simpleMiter,
694 startBisectorWithinMiterLimit, startInnerIsRight,
696 const QVector2D &startInner = startJoin[1];
697 const QVector2D &startOuter = startJoin[3];
699 bool endInnerIsRight;
700 bool endBisectorWithinMiterLimit;
701 bool giveUpOnEndJoin;
702 auto endJoin = calculateJoin(&element, nextElement,
703 penFactor, inverseMiterLimit, simpleMiter,
704 endBisectorWithinMiterLimit, endInnerIsRight,
706 QVector2D endInner = endJoin[0];
707 QVector2D endOuter = endJoin[2];
708 QVector2D nextOuter = endJoin[3];
709 QVector2D outerB = endJoin[4];
711 QVector2D p1, p2, p3, p4;
712 QVector2D n1, n2, n3, n4;
714 if (startInnerIsRight) {
722 p1 = s + n1 * penFactor;
723 p2 = s + n2 * penFactor;
726 if (endInnerIsRight) {
734 p3 = e + n3 * penFactor;
735 p4 = e + n4 * penFactor;
740 QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
744 }
else if (squareCap) {
745 QVector2D c1 = p1 + capSpace;
746 QVector2D c2 = p2 + capSpace;
747 ret.append({{p1, s, c1}, {}, {}, -1});
748 ret.append({{c1, s, c2}, {}, {}, -1});
749 ret.append({{p2, s, c2}, {}, {}, -1});
753 QVector2D capSpace = tangentVector(element,
true).normalized() * -penFactor;
757 }
else if (squareCap) {
758 QVector2D c3 = p3 + capSpace;
759 QVector2D c4 = p4 + capSpace;
760 ret.append({{p3, e, c3}, {}, {}, -1});
761 ret.append({{c3, e, c4}, {}, {}, -1});
762 ret.append({{p4, e, c4}, {}, {}, -1});
766 if (element.isLine()) {
767 ret.append({{p1, p2, p3}, {n1, n2, n3}, {}, i});
768 ret.append({{p2, p3, p4}, {n2, n3, n4}, {}, i});
770 triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
773 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
774 if (!trivialJoin && nextElement) {
777 bool innerOnRight = endInnerIsRight;
779 const auto outer1 = e + endOuter * penFactor;
780 const auto outer2 = e + nextOuter * penFactor;
783 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
784 ret.append({{outer1, e, outer2}, {}, {}, -1});
785 }
else if (roundJoin) {
786 ret.append({{outer1, e, outer2}, {}, {}, i});
787 QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
790 ret.append({{outer1, outer1 + nn, outer2}, {}, {}, i});
791 ret.append({{outer1 + nn, outer2, outer2 + nn}, {}, {}, i});
793 }
else if (miterJoin) {
794 QVector2D outer = e + outerB * penFactor;
795 ret.append({{outer1, e, outer}, {}, {}, -2});
796 ret.append({{outer, e, outer2}, {}, {}, -2});
799 if (!giveUpOnEndJoin) {
800 QVector2D inner = e + endInner * penFactor;
801 ret.append({{inner, e, outer1}, {endInner, {}, endOuter}, {}, i});
803 int nextIdx = addIdx(i, +1);
804 ret.append({{inner, e, outer2}, {endInner, {}, nextOuter}, {}, nextIdx});
808 subStart = subEnd + 1;
816static bool handleOverlap(QQuadPath &path,
int e1,
int e2,
int recursionLevel = 0)
819 if (path.elementAt(e1).isLine() && path.elementAt(e1).isLine())
822 if (!isOverlap(path, e1, e2)) {
826 if (recursionLevel > 8) {
827 qCDebug(lcSGCurveProcessor) <<
"Triangle overlap: recursion level" << recursionLevel <<
"aborting!";
831 if (path.elementAt(e1).childCount() > 1) {
832 auto e11 = path.indexOfChildAt(e1, 0);
833 auto e12 = path.indexOfChildAt(e1, 1);
834 handleOverlap(path, e11, e2, recursionLevel + 1);
835 handleOverlap(path, e12, e2, recursionLevel + 1);
836 }
else if (path.elementAt(e2).childCount() > 1) {
837 auto e21 = path.indexOfChildAt(e2, 0);
838 auto e22 = path.indexOfChildAt(e2, 1);
839 handleOverlap(path, e1, e21, recursionLevel + 1);
840 handleOverlap(path, e1, e22, recursionLevel + 1);
842 path.splitElementAt(e1);
843 auto e11 = path.indexOfChildAt(e1, 0);
844 auto e12 = path.indexOfChildAt(e1, 1);
845 bool overlap1 = isOverlap(path, e11, e2);
846 bool overlap2 = isOverlap(path, e12, e2);
847 if (!overlap1 && !overlap2)
851 if (path.elementAt(e2).isLine()) {
854 handleOverlap(path, e11, e2, recursionLevel + 1);
856 handleOverlap(path, e12, e2, recursionLevel + 1);
859 path.splitElementAt(e2);
860 auto e21 = path.indexOfChildAt(e2, 0);
861 auto e22 = path.indexOfChildAt(e2, 1);
863 handleOverlap(path, e11, e21, recursionLevel + 1);
864 handleOverlap(path, e11, e22, recursionLevel + 1);
867 handleOverlap(path, e12, e21, recursionLevel + 1);
868 handleOverlap(path, e12, e22, recursionLevel + 1);
877bool QSGCurveProcessor::solveOverlaps(QQuadPath &path)
879 bool changed =
false;
880 if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
883 const auto candidates = findOverlappingCandidates(path);
884 for (
auto candidate : candidates)
885 changed = handleOverlap(path, candidate.first, candidate.second) || changed;
887 path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
895QList<std::pair<
int,
int>> QSGCurveProcessor::findOverlappingCandidates(
const QQuadPath &path)
897 struct BRect {
float xmin;
float xmax;
float ymin;
float ymax; };
900 QVarLengthArray<
int, 64> elementStarts, elementEnds;
901 QVarLengthArray<BRect, 64> boundingRects;
902 elementStarts.reserve(path.elementCount());
903 boundingRects.reserve(path.elementCount());
904 for (
int i = 0; i < path.elementCount(); i++) {
905 QQuadPath::Element e = path.elementAt(i);
907 BRect bR{qMin(qMin(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
908 qMax(qMax(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
909 qMin(qMin(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y()),
910 qMax(qMax(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y())};
911 boundingRects.append(bR);
912 elementStarts.append(i);
916 auto compareXmin = [&](
int i,
int j){
return boundingRects.at(i).xmin < boundingRects.at(j).xmin;};
917 auto compareXmax = [&](
int i,
int j){
return boundingRects.at(i).xmax < boundingRects.at(j).xmax;};
918 std::sort(elementStarts.begin(), elementStarts.end(), compareXmin);
919 elementEnds = elementStarts;
920 std::sort(elementEnds.begin(), elementEnds.end(), compareXmax);
923 QList<std::pair<
int,
int>> overlappingBB;
928 int firstElementEnd = 0;
929 for (
const int addIndex : std::as_const(elementStarts)) {
930 const BRect &newR = boundingRects.at(addIndex);
933 while (bRpool.size() && firstElementEnd < elementEnds.size()) {
934 int removeIndex = elementEnds.at(firstElementEnd);
935 if (bRpool.contains(removeIndex) && newR.xmin > boundingRects.at(removeIndex).xmax) {
936 bRpool.removeOne(removeIndex);
944 for (
int j = 0; j < bRpool.size(); j++) {
945 int i = bRpool.at(j);
946 const BRect &r1 = boundingRects.at(i);
951 bool isNeighbor =
false;
952 if (i - addIndex == 1) {
953 if (!path.elementAt(addIndex).isSubpathEnd())
955 }
else if (addIndex - i == 1) {
956 if (!path.elementAt(i).isSubpathEnd())
960 if (isNeighbor && (r1.ymax <= newR.ymin || newR.ymax <= r1.ymin))
963 if (!isNeighbor && (r1.ymax < newR.ymin || newR.ymax < r1.ymin))
966 overlappingBB.append(std::pair<
int,
int>(i, addIndex));
968 bRpool.append(addIndex);
970 return overlappingBB;
974bool QSGCurveProcessor::removeNestedSubpaths(QQuadPath &path)
977 Q_ASSERT(path.testHint(QQuadPath::PathNonIntersecting));
979 if (path.fillRule() != Qt::WindingFill) {
986 QList<
int> subPathStartPoints;
987 QList<
int> subPathEndPoints;
988 for (
int i = 0; i < path.elementCount(); i++) {
989 if (path.elementAt(i).isSubpathStart())
990 subPathStartPoints.append(i);
991 if (path.elementAt(i).isSubpathEnd()) {
992 subPathEndPoints.append(i);
995 const int subPathCount = subPathStartPoints.size();
998 if (subPathStartPoints.size() < 2)
1002 QList<
bool> isInside;
1003 bool isAnyInside =
false;
1004 isInside.reserve(subPathStartPoints.size() * subPathStartPoints.size());
1005 for (
int i = 0; i < subPathCount; i++) {
1006 for (
int j = 0; j < subPathCount; j++) {
1008 isInside.append(
false);
1010 isInside.append(path.contains(path.elementAt(subPathStartPoints.at(i)).startPoint(),
1011 subPathStartPoints.at(j), subPathEndPoints.at(j)));
1012 if (isInside.last())
1024 QList<
bool> clockwise;
1025 clockwise.reserve(subPathCount);
1026 for (
int i = 0; i < subPathCount; i++) {
1027 float sumProduct = 0;
1028 for (
int j = subPathStartPoints.at(i); j <= subPathEndPoints.at(i); j++) {
1029 const QVector2D startPoint = path.elementAt(j).startPoint();
1030 const QVector2D endPoint = path.elementAt(j).endPoint();
1031 sumProduct += (endPoint.x() - startPoint.x()) * (endPoint.y() + startPoint.y());
1033 clockwise.append(sumProduct > 0);
1038 QList<
bool> isFilled;
1039 isFilled.reserve(subPathStartPoints.size() );
1040 for (
int i = 0; i < subPathCount; i++) {
1041 int crossings = clockwise.at(i) ? 1 : -1;
1042 for (
int j = 0; j < subPathStartPoints.size(); j++) {
1043 if (isInside.at(i * subPathCount + j))
1044 crossings += clockwise.at(j) ? 1 : -1;
1046 isFilled.append(crossings != 0);
1051 auto findClosestOuterSubpath = [&](
int subPath) {
1053 QList<
int> candidates;
1054 for (
int i = 0; i < subPathStartPoints.size(); i++) {
1055 if (isInside.at(subPath * subPathCount + i))
1056 candidates.append(i);
1058 int maxNestingLevel = -1;
1059 int maxNestingLevelIndex = -1;
1060 for (
int i = 0; i < candidates.size(); i++) {
1061 int nestingLevel = 0;
1062 for (
int j = 0; j < candidates.size(); j++) {
1063 if (isInside.at(candidates.at(i) * subPathCount + candidates.at(j))) {
1067 if (nestingLevel > maxNestingLevel) {
1068 maxNestingLevel = nestingLevel;
1069 maxNestingLevelIndex = candidates.at(i);
1072 return maxNestingLevelIndex;
1075 bool pathChanged =
false;
1076 QQuadPath fixedPath;
1077 fixedPath.setPathHints(path.pathHints());
1081 for (
int i = 0; i < subPathCount; i++) {
1082 int j = findClosestOuterSubpath(i);
1083 if (j >= 0 && isFilled.at(i) == isFilled.at(j)) {
1086 for (
int k = subPathStartPoints.at(i); k <= subPathEndPoints.at(i); k++)
1087 fixedPath.addElement(path.elementAt(k));
1097bool QSGCurveProcessor::solveIntersections(QQuadPath &path,
bool removeNestedPaths)
1099 if (path.testHint(QQuadPath::PathNonIntersecting)) {
1100 if (removeNestedPaths)
1101 return removeNestedSubpaths(path);
1106 if (path.elementCount() < 2) {
1107 path.setHint(QQuadPath::PathNonIntersecting);
1111 struct IntersectionData {
int e1;
int e2;
float t1;
float t2;
bool in1 =
false, in2 =
false, out1 =
false, out2 =
false; };
1112 QList<IntersectionData> intersections;
1116 auto markIntersectionAsHandled = [=](IntersectionData *data,
int i,
bool forward) {
1117 if (data->e1 == i) {
1122 }
else if (data->e2 == i){
1133 const QList<std::pair<
int,
int>> candidates = findOverlappingCandidates(path);
1135 for (
const auto &candidate : candidates) {
1136 QList<std::pair<
float,
float>> res;
1137 int e1 = candidate.first;
1138 int e2 = candidate.second;
1139 if (isIntersecting(path, e1, e2, &res)) {
1140 for (
const auto &r : res)
1141 intersections.append({e1, e2, r.first, r.second});
1145 qCDebug(lcSGCurveIntersectionSolver) <<
"----- Checking for Intersections -----";
1146 qCDebug(lcSGCurveIntersectionSolver) <<
"Found" << intersections.length() <<
"intersections";
1147 if (lcSGCurveIntersectionSolver().isDebugEnabled()) {
1148 for (
const auto &i : intersections) {
1149 auto p1 = path.elementAt(i.e1).pointAtFraction(i.t1);
1150 auto p2 = path.elementAt(i.e2).pointAtFraction(i.t2);
1151 qCDebug(lcSGCurveIntersectionSolver) <<
" between" << i.e1 <<
"and" << i.e2 <<
"at" << i.t1 <<
"/" << i.t2 <<
"->" << p1 <<
"/" << p2;
1155 if (intersections.isEmpty()) {
1156 path.setHint(QQuadPath::PathNonIntersecting);
1157 if (removeNestedPaths) {
1158 qCDebug(lcSGCurveIntersectionSolver) <<
"No Intersections found. Looking for enclosed subpaths.";
1159 return removeNestedSubpaths(path);
1161 qCDebug(lcSGCurveIntersectionSolver) <<
"Returning the path unchanged.";
1171 QList<
int> subPathStartPoints;
1172 QList<
int> subPathEndPoints;
1173 QList<
bool> subPathHandled;
1174 for (
int i = 0; i < path.elementCount(); i++) {
1175 if (path.elementAt(i).isSubpathStart())
1176 subPathStartPoints.append(i);
1177 if (path.elementAt(i).isSubpathEnd()) {
1178 subPathEndPoints.append(i);
1179 subPathHandled.append(
false);
1184 auto subPathIndex = [&](
int index) {
1185 for (
int i = 0; i < subPathStartPoints.size(); i++) {
1186 if (index >= subPathStartPoints.at(i) && index <= subPathEndPoints.at(i))
1193 auto ensureInBounds = [&](
int *i,
float *t,
float deltaT) {
1195 if (path.elementAt(*i).isSubpathStart())
1196 *i = subPathEndPoints.at(subPathIndex(*i));
1200 }
else if (*t >= 1.f) {
1201 if (path.elementAt(*i).isSubpathEnd())
1202 *i = subPathStartPoints.at(subPathIndex(*i));
1213 auto findStart = [=](QQuadPath &path,
int start,
int end,
int *result,
bool *forward) {
1214 for (
int i = start; i < end; i++) {
1216 if (subPathStartPoints.contains(i))
1217 adjecent = subPathEndPoints.at(subPathStartPoints.indexOf(i));
1221 QQuadPath::Element::FillSide fillSide = path.fillSideOf(i, 1e-4f);
1222 const bool leftInside = fillSide == QQuadPath::Element::FillSideLeft;
1223 const bool rightInside = fillSide == QQuadPath::Element::FillSideRight;
1224 qCDebug(lcSGCurveIntersectionSolver) <<
"Element" << i <<
"/" << adjecent <<
"meeting point is left/right inside:" << leftInside <<
"/" << rightInside;
1229 }
else if (leftInside) {
1240 QVarLengthArray<
bool> handledElements(path.elementCount(),
false);
1242 bool regularVisit =
true;
1244 QQuadPath fixedPath;
1245 fixedPath.setFillRule(path.fillRule());
1254 bool forward =
true;
1256 int startedAtIndex = -1;
1257 float startedAtT = -1;
1259 if (!findStart(path, 0, path.elementCount(), &i1, &forward)) {
1260 qCDebug(lcSGCurveIntersectionSolver) <<
"No suitable start found. This should not happen. Returning the path unchanged.";
1265 auto startNewSubPath = [&](
int i,
bool forward) {
1267 fixedPath.moveTo(path.elementAt(i).startPoint());
1270 fixedPath.moveTo(path.elementAt(i).endPoint());
1274 subPathHandled[subPathIndex(i)] =
true;
1276 startNewSubPath(i1, forward);
1280 int totalIterations = 0;
1283 int prevIntersection = -1;
1287 if (regularVisit && (t == 0 || t == 1)) {
1289 if (t == 1 && path.elementAt(i1).isSubpathEnd()) {
1290 nextIndex = subPathStartPoints.at(subPathIndex(i1));
1291 }
else if (t == 1) {
1292 nextIndex = nextIndex + 1;
1294 if (handledElements[nextIndex]) {
1295 qCDebug(lcSGCurveIntersectionSolver) <<
"Revisiting an element when trying to solve intersections. This should not happen. Returning the path unchanged.";
1298 handledElements[nextIndex] =
true;
1303 qCDebug(lcSGCurveIntersectionSolver) <<
"Checking section" << i1 <<
"from" << t <<
"going" << (forward ?
"forward" :
"backward");
1307 t1 = forward? 1 : -1;
1308 for (
int j = 0; j < intersections.size(); j++) {
1309 if (j == prevIntersection)
1311 if (i1 == intersections[j].e1 &&
1312 intersections[j].t1 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
1313 intersections[j].t1 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
1315 t1 = intersections[j].t1;
1316 i2 = intersections[j].e2;
1317 t2 = intersections[j].t2;
1319 if (i1 == intersections[j].e2 &&
1320 intersections[j].t2 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
1321 intersections[j].t2 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
1323 t1 = intersections[j].t2;
1324 i2 = intersections[j].e1;
1325 t2 = intersections[j].t1;
1328 prevIntersection = iC;
1331 qCDebug(lcSGCurveIntersectionSolver) <<
" No intersection found on my way. Adding the rest of the segment " << i1;
1332 regularVisit =
true;
1338 if (path.elementAt(i1).isLine()) {
1339 fixedPath.lineTo(path.elementAt(i1).endPoint());
1341 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(t, 1);
1342 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1344 if (path.elementAt(i1).isSubpathEnd()) {
1345 int index = subPathEndPoints.indexOf(i1);
1346 qCDebug(lcSGCurveIntersectionSolver) <<
" Going back to the start of subPath" << index;
1347 i1 = subPathStartPoints.at(index);
1353 if (path.elementAt(i1).isLine()) {
1354 fixedPath.lineTo(path.elementAt(i1).startPoint());
1356 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(0, t).reversed();
1357 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1359 if (path.elementAt(i1).isSubpathStart()) {
1360 int index = subPathStartPoints.indexOf(i1);
1361 qCDebug(lcSGCurveIntersectionSolver) <<
" Going back to the end of subPath" << index;
1362 i1 = subPathEndPoints.at(index);
1369 qCDebug(lcSGCurveIntersectionSolver) <<
" Found an intersection at" << t1 <<
"with" << i2 <<
"at" << t2;
1372 subPathHandled[subPathIndex(i2)] =
true;
1376 markIntersectionAsHandled(&intersections[iC], i1, !forward);
1380 const QQuadPath::Element &elem1 = path.elementAt(i1);
1381 if (elem1.isLine()) {
1382 fixedPath.lineTo(elem1.pointAtFraction(t1));
1384 QQuadPath::Element partUntilIntersection;
1386 partUntilIntersection = elem1.segmentFromTo(t, t1);
1388 partUntilIntersection = elem1.segmentFromTo(t1, t).reversed();
1390 fixedPath.quadTo(partUntilIntersection.controlPoint(), partUntilIntersection.endPoint());
1394 if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && !intersections[iC].out2) {
1395 i1 = intersections[iC].e2;
1396 t = intersections[iC].t2;
1398 }
else if (intersections[iC].in1 && intersections[iC].in2 && !intersections[iC].out1 && intersections[iC].out2) {
1399 i1 = intersections[iC].e1;
1400 t = intersections[iC].t1;
1402 }
else if (intersections[iC].in1 && !intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1403 i1 = intersections[iC].e2;
1404 t = intersections[iC].t2;
1406 }
else if (!intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1407 i1 = intersections[iC].e1;
1408 t = intersections[iC].t1;
1415 if (t1 !=0 && t1 != 1 && t2 != 0 && t2 != 1) {
1416 QVector2D tangent1 = elem1.tangentAtFraction(t1);
1418 tangent1 = -tangent1;
1419 const QQuadPath::Element &elem2 = path.elementAt(i2);
1420 const QVector2D tangent2 = elem2.tangentAtFraction(t2);
1421 const float angle = angleBetween(-tangent1, tangent2);
1422 qCDebug(lcSGCurveIntersectionSolver) <<
" Angle at intersection is" << angle;
1424 constexpr float deltaAngle = 1e-3f;
1425 if ((angle > deltaAngle && path.fillRule() == Qt::WindingFill) || (angle < -deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
1429 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going forward from" << t <<
"on" << i1;
1430 }
else if ((angle < -deltaAngle && path.fillRule() == Qt::WindingFill) || (angle > deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
1434 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going backward from" << t <<
"on" << i1;
1436 qCDebug(lcSGCurveIntersectionSolver) <<
" Found tangent. Staying on element";
1443 constexpr float deltaT = 1e-4f;
1445 float t2after = t2 + deltaT;
1446 ensureInBounds(&i2after, &t2after, deltaT);
1447 QQuadPath::Element::FillSide fillSideForwardNew = path.fillSideOf(i2after, t2after);
1448 if (fillSideForwardNew == QQuadPath::Element::FillSideRight) {
1452 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going forward from" << t <<
"on" << i1;
1455 float t2before = t2 - deltaT;
1456 ensureInBounds(&i2before, &t2before, deltaT);
1457 QQuadPath::Element::FillSide fillSideBackwardNew = path.fillSideOf(i2before, t2before);
1458 if (fillSideBackwardNew == QQuadPath::Element::FillSideLeft) {
1462 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going backward from" << t <<
"on" << i1;
1464 qCDebug(lcSGCurveIntersectionSolver) <<
" Staying on element.";
1471 if (!(i1 == startedAtIndex && t == startedAtT))
1472 markIntersectionAsHandled(&intersections[iC], i1, forward);
1475 if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1476 qCDebug(lcSGCurveIntersectionSolver) <<
" This intersection was processed completely and will be removed";
1477 intersections.removeAt(iC);
1478 prevIntersection = -1;
1480 regularVisit =
false;
1483 if (i1 == startedAtIndex && t == startedAtT) {
1486 qCDebug(lcSGCurveIntersectionSolver) <<
"Reached my starting point and try to find a new subpath.";
1489 int nextUnhandled = -1;
1490 for (
int i = 0; i < subPathHandled.size(); i++) {
1491 if (!subPathHandled.at(i)) {
1495 subPathHandled[i] =
true;
1497 if (findStart(path, subPathStartPoints.at(i), subPathEndPoints.at(i), &i1, &forward)) {
1499 qCDebug(lcSGCurveIntersectionSolver) <<
"Found a new subpath" << i <<
"to be processed.";
1500 startNewSubPath(i1, forward);
1501 regularVisit =
true;
1508 while (nextUnhandled < 0) {
1509 qCDebug(lcSGCurveIntersectionSolver) <<
"All subpaths handled. Looking for unhandled intersections.";
1510 if (intersections.isEmpty()) {
1511 qCDebug(lcSGCurveIntersectionSolver) <<
"All intersections handled. I am done.";
1512 fixedPath.setHint(QQuadPath::PathNonIntersecting);
1517 IntersectionData &unhandledIntersec = intersections[0];
1518 prevIntersection = 0;
1519 regularVisit =
false;
1520 qCDebug(lcSGCurveIntersectionSolver) <<
"Revisiting intersection of" << unhandledIntersec.e1 <<
"with" << unhandledIntersec.e2;
1521 qCDebug(lcSGCurveIntersectionSolver) <<
"Handled are" << unhandledIntersec.e1 <<
"in:" << unhandledIntersec.in1 <<
"out:" << unhandledIntersec.out1
1522 <<
"/" << unhandledIntersec.e2 <<
"in:" << unhandledIntersec.in2 <<
"out:" << unhandledIntersec.out2;
1527 auto lookForwardOnIntersection = [&](
bool *handledPath,
int nextE,
float nextT,
bool nextForward) {
1530 constexpr float deltaT = 1e-4f;
1531 int eForward = nextE;
1532 float tForward = nextT + (nextForward ? deltaT : -deltaT);
1533 ensureInBounds(&eForward, &tForward, deltaT);
1534 QQuadPath::Element::FillSide fillSide = path.fillSideOf(eForward, tForward);
1535 if ((nextForward && fillSide == QQuadPath::Element::FillSideRight) ||
1536 (!nextForward && fillSide == QQuadPath::Element::FillSideLeft)) {
1537 fixedPath.moveTo(path.elementAt(nextE).pointAtFraction(nextT));
1538 i1 = startedAtIndex = nextE;
1539 t = startedAtT = nextT;
1540 forward = nextForward;
1541 *handledPath =
true;
1547 if (lookForwardOnIntersection(&unhandledIntersec.in1, unhandledIntersec.e1, unhandledIntersec.t1,
false))
1549 if (lookForwardOnIntersection(&unhandledIntersec.in2, unhandledIntersec.e2, unhandledIntersec.t2,
false))
1551 if (lookForwardOnIntersection(&unhandledIntersec.out1, unhandledIntersec.e1, unhandledIntersec.t1,
true))
1553 if (lookForwardOnIntersection(&unhandledIntersec.out2, unhandledIntersec.e2, unhandledIntersec.t2,
true))
1556 intersections.removeFirst();
1557 qCDebug(lcSGCurveIntersectionSolver) <<
"Found no way to move forward at this intersection and removed it.";
1561 }
while (totalIterations < path.elementCount() * 50);
1564 qCDebug(lcSGCurveIntersectionSolver) <<
"Could not solve intersections of path. This should not happen. Returning the path unchanged.";
1570void QSGCurveProcessor::processStroke(
const QQuadPath &strokePath,
1572 float penWidth,
bool cosmetic,
1573 Qt::PenJoinStyle joinStyle,
1574 Qt::PenCapStyle capStyle,
1575 addStrokeTriangleCallback addTriangle,
1578 const bool expandingInVertexShader = cosmetic || QSGCurveStrokeNode::expandingStrokeEnabled();
1579 auto thePath = subdivide(strokePath, subdivisions).flattened();
1581 auto addCurveTriangle = [&](
const QQuadPath::Element &element,
const TriangleData &t) {
1582 qCDebug(lcSGCurveProcessor) << element <<
"->" << t;
1583 QSGCurveStrokeNode::TriangleFlags flags;
1584 flags.setFlag(QSGCurveStrokeNode::TriangleFlag::Line, element.isLine());
1585 addTriangle(t.points,
1586 { element.startPoint(), element.controlPoint(), element.endPoint() },
1587 t.normals, t.extrusions, flags);
1590 if (!expandingInVertexShader) {
1593 auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
1594 qCDebug(lcSGCurveProcessor) << thePath <<
"->" << triangles;
1596 auto addBevelTriangle = [&](
const TrianglePoints &p, QSGCurveStrokeNode::TriangleFlags flags)
1598 QVector2D fp1 = p[0];
1599 QVector2D fp2 = p[2];
1604 QVector2D nn = calcNormalVector(p[0], p[2]);
1605 if (determinant(p) < 0)
1607 float delta = penWidth / 2;
1609 QVector2D offset = nn.normalized() * delta;
1615 n[0] = (p[0] - p[1]).normalized();
1616 n[2] = (p[2] - p[1]).normalized();
1618 flags.setFlag(QSGCurveStrokeNode::TriangleFlag::Line);
1619 addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, {}, flags);
1622 for (
const auto &triangle : std::as_const(triangles)) {
1623 if (triangle.pathElementIndex < 0) {
1624 addBevelTriangle(triangle.points, {});
1627 const auto &element = thePath.elementAt(triangle.pathElementIndex);
1628 addCurveTriangle(element, triangle);
1634 auto addEdgeTriangle = [&](QVector2D start, QVector2D end,
const TriangleData &t) {
1635 qCDebug(lcSGCurveProcessor) <<
"line from" << start <<
"to" << end <<
"->" << t;
1636 addTriangle(t.points, { start, start, end }, t.normals, t.extrusions, QSGCurveStrokeNode::TriangleFlag::Line);
1639 const bool bevelJoin = joinStyle == Qt::BevelJoin;
1640 const bool roundJoin = joinStyle == Qt::RoundJoin;
1641 const bool miterJoin = !bevelJoin && !roundJoin;
1649 const bool simpleMiter = joinStyle == Qt::RoundJoin;
1651 Q_ASSERT(miterLimit > 0 || !miterJoin);
1652 const float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
1653 const float penFactor = penWidth / 2;
1655 auto triangulateCurve = [&](
int idx,
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3,
const QVector2D &p4,
1656 const QVector2D &n1,
const QVector2D &n2,
const QVector2D &n3,
const QVector2D &n4)
1658 const auto &element = thePath.elementAt(idx);
1659 Q_ASSERT(!element.isLine());
1660 const auto &s = element.startPoint();
1661 const auto &c = element.controlPoint();
1662 const auto &e = element.endPoint();
1664 bool controlPointOnRight = determinant(s, c, e) > 0;
1665 QVector2D startNormal = normalVector(element);
1666 QVector2D endNormal = normalVector(element,
true);
1667 QVector2D controlPointNormal = (startNormal + endNormal).normalized();
1668 if (controlPointOnRight)
1669 controlPointNormal = -controlPointNormal;
1670 TrianglePoints t1{p1, p2, c};
1671 TrianglePoints t2{p3, p4, c};
1672 bool simpleCase = !checkTriangleOverlap(t1, t2);
1674 addCurveTriangle(element, {{p1, p2, c}, {n1, n2, controlPointNormal},
1675 QSGCurveStrokeNode::defaultExtrusions(), idx});
1676 addCurveTriangle(element, {{p3, p4, c}, {n3, n4, controlPointNormal},
1677 QSGCurveStrokeNode::defaultExtrusions(), idx});
1678 if (controlPointOnRight) {
1679 addCurveTriangle(element, {{p1, p3, c}, {n1, n3, controlPointNormal},
1680 QSGCurveStrokeNode::defaultExtrusions(), idx});
1682 addCurveTriangle(element, {{p2, p4, c}, {n2, n4, controlPointNormal},
1683 QSGCurveStrokeNode::defaultExtrusions(), idx});
1686 const auto &triangles = simplePointTriangulator({p1, p2, c, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx);
1687 for (
const auto &triangle : triangles)
1688 addCurveTriangle(element, triangle);
1696 const int count = thePath.elementCount();
1698 while (subStart < count) {
1699 int subEnd = subStart;
1700 for (
int i = subStart + 1; i < count; ++i) {
1701 const auto &e = thePath.elementAt(i);
1702 if (e.isSubpathStart()) {
1706 if (i == count - 1) {
1711 bool closed = thePath.elementAt(subStart).startPoint() == thePath.elementAt(subEnd).endPoint();
1712 const int subCount = subEnd - subStart + 1;
1714 auto addIdx = [&](
int idx,
int delta) ->
int {
1715 int subIdx = idx - subStart;
1717 subIdx = (subIdx + subCount + delta) % subCount;
1720 return subStart + subIdx;
1722 auto elementAt = [&](
int idx,
int delta) ->
const QQuadPath::Element * {
1723 int subIdx = idx - subStart;
1725 subIdx = (subIdx + subCount + delta) % subCount;
1726 return &thePath.elementAt(subStart + subIdx);
1729 if (subIdx >= 0 && subIdx < subCount)
1730 return &thePath.elementAt(subStart + subIdx);
1734 for (
int i = subStart; i <= subEnd; ++i) {
1735 const auto &element = thePath.elementAt(i);
1736 const auto *nextElement = elementAt(i, +1);
1737 const auto *prevElement = elementAt(i, -1);
1739 const auto &s = element.startPoint();
1740 const auto &e = element.endPoint();
1742 bool startInnerIsRight;
1743 bool startBisectorWithinMiterLimit;
1744 bool giveUpOnStartJoin;
1745 auto startJoin = calculateJoin(prevElement, &element,
1746 penFactor, inverseMiterLimit, simpleMiter,
1747 startBisectorWithinMiterLimit, startInnerIsRight,
1749 const QVector2D &startInner = startJoin[1];
1750 const QVector2D &startOuter = startJoin[3];
1752 bool endInnerIsRight;
1753 bool endBisectorWithinMiterLimit;
1754 bool giveUpOnEndJoin;
1755 auto endJoin = calculateJoin(&element, nextElement,
1756 penFactor, inverseMiterLimit, simpleMiter,
1757 endBisectorWithinMiterLimit, endInnerIsRight,
1759 const QVector2D endInner = endJoin[0];
1760 const QVector2D endOuter = endJoin[2];
1761 const QVector2D nextOuter = endJoin[3];
1762 const QVector2D outerBisector = endJoin[4];
1763 const QVector2D startTangent = tangentVector(element,
false).normalized();
1764 const QVector2D endTangent = tangentVector(element,
true).normalized();
1766 QVector2D n1, n2, n3, n4;
1768 if (startInnerIsRight) {
1777 if (endInnerIsRight) {
1790 static const float artificialLineExtension = 50;
1793 if (capStyle != Qt::FlatCap) {
1794 const QVector2D capNormalNone(0, 0);
1799 const QVector2D capNormalUp(startTangent.y(), -startTangent.x());
1800 const QVector2D capNormalDown = -capNormalUp;
1802 const QVector2D capNormalUpOut = (capNormalUp - startTangent);
1803 const QVector2D capNormalDownOut = (capNormalDown - startTangent);
1804 Q_ASSERT(capNormalUpOut.length() == capNormalDownOut.length());
1805 if (capStyle == Qt::RoundCap) {
1806 addCurveTriangle(element, {{s, s, s}, {capNormalUp, capNormalNone, capNormalUpOut},
1807 QSGCurveStrokeNode::defaultExtrusions(), i});
1808 addCurveTriangle(element, {{s, s, s}, {capNormalUpOut, capNormalNone, capNormalDownOut},
1809 QSGCurveStrokeNode::defaultExtrusions(), i});
1810 addCurveTriangle(element, {{s, s, s}, {capNormalDown, capNormalNone, capNormalDownOut},
1811 QSGCurveStrokeNode::defaultExtrusions(), i});
1813 addEdgeTriangle(element.startPoint(), element.startPoint() - startTangent * penWidth * artificialLineExtension,
1814 {{s, s, s}, {capNormalUp, capNormalNone, capNormalUpOut},
1815 QSGCurveStrokeNode::defaultExtrusions(), i});
1816 const auto norm = normalVector(element,
false).normalized() * penWidth * artificialLineExtension;
1817 addEdgeTriangle(element.startPoint() - norm, element.startPoint() + norm,
1818 {{s, s, s}, {capNormalUpOut, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1819 addEdgeTriangle(element.startPoint(), element.startPoint() - startTangent * penWidth * artificialLineExtension,
1820 {{s, s, s}, {capNormalDown, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1824 const QVector2D capNormalUp(endTangent.y(), -endTangent.x());
1825 const QVector2D capNormalDown = -capNormalUp;
1826 const QVector2D capNormalUpOut = (capNormalUp - endTangent);
1827 const QVector2D capNormalDownOut = (capNormalDown - endTangent);
1828 Q_ASSERT(capNormalUpOut.length() == capNormalDownOut.length());
1829 if (capStyle == Qt::RoundCap) {
1830 addCurveTriangle(element, {{e, e, e}, {capNormalDown, capNormalNone, capNormalDownOut},
1831 QSGCurveStrokeNode::defaultExtrusions(), i});
1832 addCurveTriangle(element, {{e, e, e}, {capNormalUpOut, capNormalNone, capNormalDownOut},
1833 QSGCurveStrokeNode::defaultExtrusions(), i});
1834 addCurveTriangle(element, {{e, e, e}, {capNormalUp, capNormalNone, capNormalUpOut},
1835 QSGCurveStrokeNode::defaultExtrusions(), i});
1837 addEdgeTriangle(element.endPoint() - endTangent * penWidth * artificialLineExtension, element.endPoint(),
1838 {{e, e, e}, {capNormalDown, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1839 const auto norm = normalVector(element,
true).normalized() * penWidth * artificialLineExtension;
1840 addEdgeTriangle(element.endPoint() - norm, element.endPoint() + norm,
1841 {{e, e, e}, {capNormalUpOut, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1842 addEdgeTriangle(element.endPoint() - endTangent * penWidth * artificialLineExtension, element.endPoint(),
1843 {{e, e, e}, {capNormalUp, capNormalNone, capNormalUpOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1848 if (element.isLine()) {
1849 addCurveTriangle(element, {{s, s, e}, {n1, n2, n3}, QSGCurveStrokeNode::defaultExtrusions(), i});
1850 addCurveTriangle(element, {{s, e, e}, {n2, n3, n4}, QSGCurveStrokeNode::defaultExtrusions(), i});
1852 triangulateCurve(i, s, s, e, e, n1, n2, n3, n4);
1855 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
1856 if (!trivialJoin && nextElement) {
1858 bool innerOnRight = endInnerIsRight;
1860 const auto outer1 = e + endOuter;
1861 const auto outer2 = e + nextOuter;
1862 QVector2D nn = calcNormalVector(outer1, outer2).normalized();
1865 const QVector2D endOuterN = (outer1 - e).normalized();
1866 const QVector2D nextOuterN = (outer2 - e).normalized();
1867 const QVector2D endOuterBisectorN = (endOuterN + nn.normalized()).normalized();
1868 const QVector2D nextOuterBisectorN = (nextOuterN + nn.normalized()).normalized();
1870 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
1871 const float cosTheta = QVector2D::dotProduct(endOuterN, nextOuterN);
1872 const float cosHalf = cos(acos(cosTheta) / 2);
1875 const auto tan1 = endTangent * penWidth * artificialLineExtension;
1876 const auto tan2 = tangentVector(*nextElement,
false).normalized() * penWidth * artificialLineExtension;
1877 const auto bevelTan = (tan2 - tan1) / 2;
1882 addEdgeTriangle(element.endPoint() - bevelTan, element.endPoint() + bevelTan,
1883 {{e, e, e}, {endOuterN, {}, nextOuterN}, {cosHalf, cosHalf, cosHalf}, i});
1884 }
else if (roundJoin) {
1885 addCurveTriangle(element, {{outer1, e, outer2}, {endOuterN, {}, nextOuterN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1886 addCurveTriangle(element, {{outer1, outer1 + nn, outer2}, {endOuterN, endOuterBisectorN, nextOuterN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1887 addCurveTriangle(element, {{outer1 + nn, outer2, outer2 + nn}, {endOuterBisectorN, nextOuterN, nextOuterBisectorN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1888 }
else if (miterJoin) {
1889 addEdgeTriangle(element.endPoint(), element.endPoint() - endTangent * penWidth * artificialLineExtension,
1890 {{e, e, e}, {endOuterN, {}, outerBisector}, QSGCurveStrokeNode::defaultExtrusions(), i});
1891 addEdgeTriangle(nextElement->startPoint(),
1892 nextElement->startPoint() - tangentVector(*nextElement,
false).normalized() * penWidth * artificialLineExtension,
1893 {{e, e, e}, {nextOuterN, {}, outerBisector}, QSGCurveStrokeNode::defaultExtrusions(), i});
1896 if (!giveUpOnEndJoin) {
1897 addCurveTriangle(element, {{e, e, e}, {endInner, {}, endOuter}, QSGCurveStrokeNode::defaultExtrusions(), i});
1899 int nextIdx = addIdx(i, +1);
1900 addCurveTriangle(*nextElement, {{e, e, e}, {endInner, {}, nextOuter}, QSGCurveStrokeNode::defaultExtrusions(), nextIdx});
1904 subStart = subEnd + 1;
1911 Q_STATIC_ASSERT(
sizeof(QVector2D) ==
sizeof(quint64));
1916 memcpy(&k, &key,
sizeof(QVector2D));
1917 return QHashPrivate::hash(k, seed);
1920void QSGCurveProcessor::processFill(
const QQuadPath &fillPath,
1921 Qt::FillRule fillRule,
1922 addTriangleCallback addTriangle)
1924 QPainterPath internalHull;
1925 internalHull.setFillRule(fillRule);
1927 QMultiHash<QVector2D,
int> pointHash;
1929 auto roundVec2D = [](
const QVector2D &p) -> QVector2D {
1930 return { qRound64(p.x() * 32.0f) / 32.0f, qRound64(p.y() * 32.0f) / 32.0f };
1933 auto addCurveTriangle = [&](
const QQuadPath::Element &element,
1934 const QVector2D &sp,
1935 const QVector2D &ep,
1936 const QVector2D &cp) {
1937 addTriangle({ sp, cp, ep },
1939 [&element](QVector2D v) {
return elementUvForPoint(element, v); });
1942 auto addCurveTriangleWithNormals = [&](
const QQuadPath::Element &element,
1943 const std::array<QVector2D, 3> &v,
1944 const std::array<QVector2D, 3> &n) {
1945 addTriangle(v, n, [&element](QVector2D v) {
return elementUvForPoint(element, v); });
1948 auto outsideNormal = [](
const QVector2D &startPoint,
1949 const QVector2D &endPoint,
1950 const QVector2D &insidePoint) {
1952 QVector2D baseLine = endPoint - startPoint;
1953 QVector2D insideVector = insidePoint - startPoint;
1954 QVector2D normal = normalVector(baseLine);
1956 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
1958 return swap ? normal : -normal;
1961 auto addTriangleForLine = [&](
const QQuadPath::Element &element,
1962 const QVector2D &sp,
1963 const QVector2D &ep,
1964 const QVector2D &cp) {
1965 addCurveTriangle(element, sp, ep, cp);
1968 const QVector2D normal = outsideNormal(sp, ep, cp);
1969 constexpr QVector2D null;
1970 addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
1971 addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
1974 auto addTriangleForConcave = [&](
const QQuadPath::Element &element,
1975 const QVector2D &sp,
1976 const QVector2D &ep,
1977 const QVector2D &cp) {
1978 addTriangleForLine(element, sp, ep, cp);
1981 auto addTriangleForConvex = [&](
const QQuadPath::Element &element,
1982 const QVector2D &sp,
1983 const QVector2D &ep,
1984 const QVector2D &cp) {
1985 addCurveTriangle(element, sp, ep, cp);
1988 constexpr QVector2D null;
1991 const QVector2D normal = outsideNormal(sp, cp, ep);
1992 addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
1997 const QVector2D normal = outsideNormal(ep, cp, sp);
1998 addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
2002 auto addFillTriangle = [&](
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3) {
2003 constexpr QVector3D uv(0.0, 1.0, -1.0);
2004 addTriangle({ p1, p2, p3 },
2006 [&uv](QVector2D) {
return uv; });
2009 fillPath.iterateElements([&](
const QQuadPath::Element &element,
int index) {
2010 QVector2D sp(element.startPoint());
2011 QVector2D cp(element.controlPoint());
2012 QVector2D ep(element.endPoint());
2013 QVector2D rsp = roundVec2D(sp);
2015 if (element.isSubpathStart())
2016 internalHull.moveTo(sp.toPointF());
2017 if (element.isLine()) {
2018 internalHull.lineTo(ep.toPointF());
2019 pointHash.insert(rsp, index);
2021 QVector2D rep = roundVec2D(ep);
2022 QVector2D rcp = roundVec2D(cp);
2023 if (element.isConvex()) {
2024 internalHull.lineTo(ep.toPointF());
2025 addTriangleForConvex(element, rsp, rep, rcp);
2026 pointHash.insert(rsp, index);
2028 internalHull.lineTo(cp.toPointF());
2029 internalHull.lineTo(ep.toPointF());
2030 addTriangleForConcave(element, rsp, rep, rcp);
2031 pointHash.insert(rcp, index);
2039 auto onSameSideOfLine = [](
const QVector2D &p1,
2040 const QVector2D &p2,
2041 const QVector2D &linePoint,
2042 const QVector2D &lineNormal) {
2043 float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
2044 float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
2045 return side1 * side2 >= 0;
2048 auto pointInSafeSpace = [&](
const QVector2D &p,
const QQuadPath::Element &element) ->
bool {
2049 const QVector2D a = element.startPoint();
2050 const QVector2D b = element.endPoint();
2051 const QVector2D c = element.controlPoint();
2056 const QVector2D n1 = calcNormalVector(a, c + (c - b));
2057 const QVector2D n2 = calcNormalVector(b, c + (c - a));
2058 bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
2059 bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
2060 return safeSideOf1 && safeSideOf2;
2065 auto handleTriangle = [&](
const QVector2D (&p)[3]) ->
bool {
2066 bool isLine =
false;
2067 bool isConcave =
false;
2068 bool isConvex =
false;
2069 int elementIndex = -1;
2071 bool foundElement =
false;
2075 for (
int i = 0; i < 3; ++i) {
2076 auto pointFoundRange = std::as_const(pointHash).equal_range(roundVec2D(p[i]));
2078 if (pointFoundRange.first == pointHash.constEnd())
2082 int testIndex = *pointFoundRange.first;
2083 bool ambiguous = std::next(pointFoundRange.first) != pointFoundRange.second;
2088 for (
auto it = pointFoundRange.first; it != pointFoundRange.second; ++it) {
2089 auto &el = fillPath.elementAt(*it);
2090 bool fillOnLeft = !el.isFillOnRight();
2091 auto sp = roundVec2D(el.startPoint());
2092 auto ep = roundVec2D(el.endPoint());
2094 auto pointInside = [&](
const QVector2D &p) {
2095 return p == sp || p == ep
2096 || QQuadPath::isPointOnLeft(p, el.startPoint(), el.endPoint()) == fillOnLeft;
2098 if (pointInside(p[0]) && pointInside(p[1]) && pointInside(p[2])) {
2105 const auto &element = fillPath.elementAt(testIndex);
2110 bool onElement =
false;
2111 for (
int j = 0; j < 3; ++j) {
2114 if (element.isConvex() || element.isLine())
2115 onElement = roundVec2D(element.endPoint()) == p[j];
2117 onElement = roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j];
2123 foundElement =
true;
2124 elementIndex = testIndex;
2125 isConvex = element.isConvex();
2126 isLine = element.isLine();
2127 isConcave = !isLine && !isConvex;
2134 int ci = (6 - si - ei) % 3;
2135 addTriangleForLine(fillPath.elementAt(elementIndex), p[si], p[ei], p[ci]);
2136 }
else if (isConcave) {
2137 addCurveTriangle(fillPath.elementAt(elementIndex), p[0], p[1], p[2]);
2138 }
else if (isConvex) {
2139 int oi = (6 - si - ei) % 3;
2140 const auto &otherPoint = p[oi];
2141 const auto &element = fillPath.elementAt(elementIndex);
2144 bool safeSpace = pointInSafeSpace(otherPoint, element);
2146 addCurveTriangle(element, p[0], p[1], p[2]);
2149 QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
2151 for (
int i = 0; i < 7; ++i) {
2152 safeSpace = pointInSafeSpace(newPoint, element);
2155 newPoint = (p[si] + p[ei] + newPoint) / 3;
2160 addCurveTriangle(element, p[si], p[ei], newPoint);
2162 addFillTriangle(p[si], p[oi], newPoint);
2163 addFillTriangle(p[ei], p[oi], newPoint);
2166 addFillTriangle(p[0], p[1], p[2]);
2171 addFillTriangle(p[0], p[1], p[2]);
2176 QTriangleSet triangles = qTriangulate(internalHull);
2178 if (triangles.indices.size() == 3)
2179 triangles.indices.setDataUint({ 0, 1, 2 });
2181 const quint32 *idxTable =
static_cast<
const quint32 *>(triangles.indices.data());
2182 for (
int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
2183 const quint32 *idx = &idxTable[triangle * 3];
2186 for (
int i = 0; i < 3; ++i) {
2187 p[i] = roundVec2D(QVector2D(
float(triangles.vertices.at(idx[i] * 2)),
2188 float(triangles.vertices.at(idx[i] * 2 + 1))));
2190 if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
2192 bool needsSplit = !handleTriangle(p);
2194 QVector2D c = (p[0] + p[1] + p[2]) / 3;
2195 for (
int i = 0; i < 3; ++i) {
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
size_t qHash(QByteArrayView key, size_t seed) noexcept