7#include <QtGui/private/qtriangulator_p.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qhash.h>
13Q_LOGGING_CATEGORY(lcSGCurveProcessor,
"qt.quick.curveprocessor");
19static inline QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
21 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
23 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
24 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
31static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
33 QVector2D v1 = 2 * (p1 - p0);
34 QVector2D v2 = p2 - v1 - p0;
35 return uvForPoint(v1, v2, p - p0);
38static QVector3D elementUvForPoint(
const QQuadPath::Element& e, QVector2D p)
40 auto uv = curveUv(e.startPoint(), e.referencePoint(), e.endPoint(), p);
42 return { uv.x(), uv.y(), 0.0f };
44 return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
47static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
50 return {v.y(), -v.x()};
54static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
56 float dot = QVector2D::dotProduct(p - a, n);
60static inline float determinant(
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3)
62 return p1.x() * (p2.y() - p3.y())
63 + p2.x() * (p3.y() - p1.y())
64 + p3.x() * (p1.y() - p2.y());
68
69
70
71
72
73
74
75
76using TrianglePoints = std::array<QVector2D, 3>;
77using LinePoints = std::array<QVector2D, 2>;
81static inline double determinant(
const TrianglePoints &p)
83 return determinant(p[0], p[1], p[2]);
87static void fixWinding(TrianglePoints &p)
89 double det = determinant(p);
98bool checkEdge(
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p,
float epsilon)
100 return determinant(p1, p2, p) <= epsilon;
105bool lineIntersection(
const LinePoints &l1,
const LinePoints &l2, QList<std::pair<
float,
float>> *solution =
nullptr)
107 constexpr double eps2 = 1e-5;
110 const float A = l1[0].x();
111 const float B = l1[1].x() - l1[0].x();
112 const float C = l2[0].x();
113 const float D = l2[1].x() - l2[0].x();
114 const float E = l1[0].y();
115 const float F = l1[1].y() - l1[0].y();
116 const float G = l2[0].y();
117 const float H = l2[1].y() - l2[0].y();
119 float det = D * F - B * H;
124 float s = (F * (A - C) - B * (E - G)) / det;
125 float t = (H * (A - C) - D * (E - G)) / det;
128 bool intersecting = (s >= 0 && s <= 1. - eps2 && t >= 0 && t <= 1. - eps2);
130 if (solution && intersecting)
131 solution->append(std::pair<
float,
float>(t, s));
137bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2,
float epsilon = 1.0/32)
140 fixWinding(triangle1);
141 for (
int i = 0; i < 3; i++) {
142 int ni = (i + 1) % 3;
143 if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
144 checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
145 checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
150 fixWinding(triangle2);
151 for (
int i = 0; i < 3; i++) {
152 int ni = (i + 1) % 3;
154 if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
155 checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
156 checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
163bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line,
float epsilon = 1.0/32)
166 bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
167 bool s2 = determinant(line[0], line[1], triangle[1]) < 0;
168 bool s3 = determinant(line[0], line[1], triangle[2]) < 0;
170 if (s1 == s2 && s2 == s3) {
174 fixWinding(triangle);
175 for (
int i = 0; i < 3; i++) {
176 int ni = (i + 1) % 3;
177 if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
178 checkEdge(triangle[i], triangle[ni], line[1], epsilon))
185static bool isOverlap(
const QQuadPath &path,
int e1,
int e2)
187 const QQuadPath::Element &element1 = path.elementAt(e1);
188 const QQuadPath::Element &element2 = path.elementAt(e2);
190 if (element1.isLine()) {
191 LinePoints line1{ element1.startPoint(), element1.endPoint() };
192 if (element2.isLine()) {
193 LinePoints line2{ element2.startPoint(), element2.endPoint() };
194 return lineIntersection(line1, line2);
196 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
197 return checkLineTriangleOverlap(t2, line1);
200 TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
201 if (element2.isLine()) {
202 LinePoints line{ element2.startPoint(), element2.endPoint() };
203 return checkLineTriangleOverlap(t1, line);
205 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
206 return checkTriangleOverlap(t1, t2);
213static float angleBetween(
const QVector2D v1,
const QVector2D v2)
215 float dot = v1.x() * v2.x() + v1.y() * v2.y();
216 float cross = v1.x() * v2.y() - v1.y() * v2.x();
218 return atan2(cross, dot);
221static bool isIntersecting(
const TrianglePoints &t1,
const TrianglePoints &t2, QList<std::pair<
float,
float>> *solutions =
nullptr)
223 constexpr double eps = 1e-5;
224 constexpr double eps2 = 1e-5;
225 constexpr int maxIterations = 7;
228 std::array<QPointF, 3> td1 = { t1[0].toPointF(), t1[1].toPointF(), t1[2].toPointF() };
229 std::array<QPointF, 3> td2 = { t2[0].toPointF(), t2[1].toPointF(), t2[2].toPointF() };
234 auto F = [=](QPointF t) {
return
235 td1[0] * (1 - t.x()) * (1. - t.x()) + 2 * td1[1] * (1. - t.x()) * t.x() + td1[2] * t.x() * t.x() -
236 td2[0] * (1 - t.y()) * (1. - t.y()) - 2 * td2[1] * (1. - t.y()) * t.y() - td2[2] * t.y() * t.y();};
240 auto J = [=](QPointF t) {
return QLineF(
241 td1[0].x() * (-2 * (1-t.x())) + 2 * td1[1].x() * (1 - 2 * t.x()) + td1[2].x() * 2 * t.x(),
242 -td2[0].x() * (-2 * (1-t.y())) - 2 * td2[1].x() * (1 - 2 * t.y()) - td2[2].x() * 2 * t.y(),
243 td1[0].y() * (-2 * (1-t.x())) + 2 * td1[1].y() * (1 - 2 * t.x()) + td1[2].y() * 2 * t.x(),
244 -td2[0].y() * (-2 * (1-t.y())) - 2 * td2[1].y() * (1 - 2 * t.y()) - td2[2].y() * 2 * t.y());};
247 auto solve = [](QLineF A, QPointF b) {
249 const double det = A.x1() * A.y2() - A.y1() * A.x2();
250 QLineF Ainv(A.y2() / det, -A.y1() / det, -A.x2() / det, A.x1() / det);
252 return QPointF(Ainv.x1() * b.x() + Ainv.y1() * b.y(),
253 Ainv.x2() * b.x() + Ainv.y2() * b.y());
256#ifdef INTERSECTION_EXTRA_DEBUG
257 qCDebug(lcSGCurveIntersectionSolver) <<
"Checking" << t1[0] << t1[1] << t1[2];
258 qCDebug(lcSGCurveIntersectionSolver) <<
" vs" << t2[0] << t2[1] << t2[2];
263 constexpr std::array tref = { QPointF{0.0, 0.0}, QPointF{0.5, 0.0}, QPointF{1.0, 0.0},
264 QPointF{0.0, 0.5}, QPointF{0.5, 0.5}, QPointF{1.0, 0.5},
265 QPointF{0.0, 1.0}, QPointF{0.5, 1.0}, QPointF{1.0, 1.0} };
267 for (
auto t : tref) {
273 while (err > eps && i < maxIterations) {
274 t = t - solve(J(t), fval);
276 err = qAbs(fval.x()) + qAbs(fval.y());
278#ifdef INTERSECTION_EXTRA_DEBUG
279 qCDebug(lcSGCurveIntersectionSolver) <<
" Newton iteration" << i <<
"t =" << t <<
"F =" << fval <<
"Error =" << err;
283 if (err < eps && t.x() >=0 && t.x() <= 1. - 10 * eps2 && t.y() >= 0 && t.y() <= 1. - 10 * eps2) {
284#ifdef INTERSECTION_EXTRA_DEBUG
285 qCDebug(lcSGCurveIntersectionSolver) <<
" Newton solution (after" << i <<
")=" << t <<
"(" << F(t) <<
")";
289 for (
auto solution : *solutions) {
290 if (qAbs(solution.first - t.x()) < 10 * eps2 && qAbs(solution.second - t.y()) < 10 * eps2) {
296 solutions->append({t.x(), t.y()});
303 return solutions->size() > 0;
308static bool isIntersecting(
const QQuadPath &path,
int e1,
int e2, QList<std::pair<
float,
float>> *solutions =
nullptr)
311 const QQuadPath::Element &elem1 = path.elementAt(e1);
312 const QQuadPath::Element &elem2 = path.elementAt(e2);
314 if (elem1.isLine() && elem2.isLine()) {
315 return lineIntersection(LinePoints {elem1.startPoint(), elem1.endPoint() },
316 LinePoints {elem2.startPoint(), elem2.endPoint() },
319 return isIntersecting(TrianglePoints { elem1.startPoint(), elem1.controlPoint(), elem1.endPoint() },
320 TrianglePoints { elem2.startPoint(), elem2.controlPoint(), elem2.endPoint() },
327 TrianglePoints points;
328 TrianglePoints normals;
329 std::array<
float, 3> extrusions;
330 int pathElementIndex = std::numeric_limits<
int>::min();
333#ifndef QT_NO_DEBUG_STREAM
334 friend QDebug operator<<(QDebug,
const TriangleData &);
338#ifndef QT_NO_DEBUG_STREAM
339QDebug operator<<(QDebug stream,
const TriangleData &td)
341 QDebugStateSaver saver(stream);
343 stream <<
"TriangleData(";
344 if (td.pathElementIndex != std::numeric_limits<
int>::min())
345 stream <<
"idx " << td.pathElementIndex;
346 stream <<
" [" << td.points.at(0).x() <<
", " << td.points.at(0).y()
347 <<
"; " << td.points.at(1).x() <<
", " << td.points.at(1).y()
348 <<
"; " << td.points.at(2).x() <<
", " << td.points.at(2).y()
349 <<
"] normals [" << td.normals.at(0).x() <<
", " << td.points.at(0).y()
350 <<
"; " << td.normals.at(1).x() <<
", " << td.normals.at(1).y()
351 <<
"; " << td.normals.at(2).x() <<
", " << td.normals.at(2).y()
358inline QVector2D normalVector(QVector2D baseLine)
360 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
365
366
367
368
369QVector2D normalVector(
const QQuadPath::Element &element,
bool endSide =
false)
371 if (element.isLine())
372 return normalVector(element.endPoint() - element.startPoint());
374 return normalVector(element.controlPoint() - element.startPoint());
376 return normalVector(element.endPoint() - element.controlPoint());
380
381
382
383
384QVector2D tangentVector(
const QQuadPath::Element &element,
bool endSide =
false)
386 if (element.isLine()) {
388 return element.endPoint() - element.startPoint();
390 return element.startPoint() - element.endPoint();
393 return element.controlPoint() - element.startPoint();
395 return element.controlPoint() - element.endPoint();
400QList<TriangleData> simplePointTriangulator(
const QList<QVector2D> &pts,
const QList<QVector2D> &normals,
int elementIndex)
402 int count = pts.size();
403 Q_ASSERT(count >= 3);
404 Q_ASSERT(normals.size() == count);
408 float det1 = determinant(pts[0], pts[1], pts[2]);
413 auto connectableInHull = [&](
int idx) -> QList<
int> {
415 const int n = hull.size();
416 const auto &pt = pts[idx];
417 for (
int i = 0; i < n; ++i) {
418 const auto &i1 = hull.at(i);
419 const auto &i2 = hull.at((i+1) % n);
420 if (determinant(pts[i1], pts[i2], pt) < 0.0f)
425 for (
int i = 3; i < count; ++i) {
426 auto visible = connectableInHull(i);
427 if (visible.isEmpty())
429 int visCount = visible.count();
430 int hullCount = hull.count();
433 int boundaryStart = visible[0];
434 for (
int j = 0; j < visCount - 1; ++j) {
435 if ((visible[j] + 1) % hullCount != visible[j+1]) {
436 boundaryStart = visible[j + 1];
443 int pointsToKeep = hullCount - visCount + 1;
446 for (
int j = 0; j < pointsToKeep; ++j) {
447 newHull << hull.at((j + boundaryStart + visCount) % hullCount);
453 QList<TriangleData> ret;
454 for (
int i = 1; i < hull.size() - 1; ++i) {
458 ret.append({{pts[i0], pts[i1], pts[i2]}, {normals[i0], normals[i1], normals[i2]},
459 QSGCurveStrokeNode::defaultExtrusions(), elementIndex});
465inline bool needsSplit(
const QQuadPath::Element &el)
467 Q_ASSERT(!el.isLine());
468 const auto v1 = el.controlPoint() - el.startPoint();
469 const auto v2 = el.endPoint() - el.controlPoint();
470 float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
475inline void splitElementIfNecessary(QQuadPath *path,
int index,
int level) {
476 if (level > 0 && needsSplit(path->elementAt(index))) {
477 path->splitElementAt(index);
478 splitElementIfNecessary(path, path->indexOfChildAt(index, 0), level - 1);
479 splitElementIfNecessary(path, path->indexOfChildAt(index, 1), level - 1);
483static QQuadPath subdivide(
const QQuadPath &path,
int subdivisions)
485 QQuadPath newPath = path;
486 newPath.iterateElements([&](QQuadPath::Element &e,
int index) {
488 splitElementIfNecessary(&newPath, index, subdivisions);
495
496
497
498
499
500static std::array<QVector2D, 5> calculateJoin(
const QQuadPath::Element *element1,
const QQuadPath::Element *element2,
501 float penFactor,
float inverseMiterLimit,
bool simpleMiter,
502 bool &outerBisectorWithinMiterLimit,
bool &innerIsRight,
bool &giveUp)
504 outerBisectorWithinMiterLimit =
true;
509 QVector2D n = normalVector(*element2);
510 return {n, n, -n, -n, -n};
514 QVector2D n = normalVector(*element1,
true);
515 return {n, n, -n, -n, -n};
518 Q_ASSERT(element1->endPoint() == element2->startPoint());
520 const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
521 const auto p2 = element1->endPoint();
522 const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
524 const auto v1 = (p1 - p2).normalized();
525 const auto v2 = (p3 - p2).normalized();
526 const auto b = (v1 + v2);
528 constexpr float epsilon = 1.0f / 32.0f;
529 const bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
534 QVector2D n1(-v1.y(), v1.x());
535 QVector2D n2(-v2.y(), v2.x());
536 QVector2D n = (n2 - n1).normalized();
537 return {n, n, -n, -n, -n};
544 const auto bisector = b.normalized();
545 const float cos2x = qMin(1.0f, QVector2D::dotProduct(v1, v2));
546 const float sine = qMax(sqrt((1.0f - cos2x) / 2), 0.01f);
547 const float length = penFactor / sine;
548 innerIsRight = determinant(p1, p2, p3) > 0;
551 auto tooLong = [penFactor, length](QVector2D p1, QVector2D p2, QVector2D n) ->
bool {
557 auto projLen = QVector2D::dotProduct(v, n);
558 return projLen * 0.9f < length &&
559 (QSGCurveStrokeNode::expandingStrokeEnabled() ? ((v - n * projLen).length() - 2.0) * 0.5
560 : (v - n * projLen).length() * 0.9) < penFactor;
565 giveUp = !element1->isLine() || !element2->isLine() || tooLong(p1, p2, bisector) || tooLong(p3, p2, bisector);
566 outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
567 bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
568 const QVector2D bn = bisector / sine;
571 return {bn, bn, -bn, -bn, -bn};
572 const QVector2D n1 = normalVector(*element1,
true);
573 const QVector2D n2 = normalVector(*element2);
576 return {n1, n2, -n1, -n2, -bn};
578 return {-n1, -n2, n1, n2, -bn};
582 return {bn, bn, -n1, -n2, -bn};
584 return {bn, bn, n1, n2, -bn};
588static QList<TriangleData> customTriangulator2(
const QQuadPath &path,
float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle,
float miterLimit)
590 const bool bevelJoin = joinStyle == Qt::BevelJoin;
591 const bool roundJoin = joinStyle == Qt::RoundJoin;
592 const bool miterJoin = !bevelJoin && !roundJoin;
594 const bool roundCap = capStyle == Qt::RoundCap;
595 const bool squareCap = capStyle == Qt::SquareCap;
597 const bool simpleMiter = joinStyle == Qt::RoundJoin;
599 Q_ASSERT(miterLimit > 0 || !miterJoin);
600 float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
602 const float penFactor = penWidth / 2;
604 QList<TriangleData> ret;
606 auto triangulateCurve = [&ret, &path, penFactor]
607 (
int idx,
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3,
const QVector2D &p4,
608 const QVector2D &n1,
const QVector2D &n2,
const QVector2D &n3,
const QVector2D &n4)
610 const auto &element = path.elementAt(idx);
611 Q_ASSERT(!element.isLine());
612 const auto &s = element.startPoint();
613 const auto &c = element.controlPoint();
614 const auto &e = element.endPoint();
616 bool controlPointOnRight = determinant(s, c, e) > 0;
617 QVector2D startNormal = normalVector(element);
618 QVector2D endNormal = normalVector(element,
true);
619 QVector2D controlPointNormal = (startNormal + endNormal).normalized();
620 if (controlPointOnRight)
621 controlPointNormal = -controlPointNormal;
622 QVector2D p5 = c + controlPointNormal * penFactor;
623 TrianglePoints t1{p1, p2, p5};
624 TrianglePoints t2{p3, p4, p5};
625 bool simpleCase = !checkTriangleOverlap(t1, t2);
628 ret.append({{p1, p2, p5}, {n1, n2, controlPointNormal}, {}, idx});
629 ret.append({{p3, p4, p5}, {n3, n4, controlPointNormal}, {}, idx});
630 if (controlPointOnRight) {
631 ret.append({{p1, p3, p5}, {n1, n3, controlPointNormal}, {}, idx});
633 ret.append({{p2, p4, p5}, {n2, n4, controlPointNormal}, {}, idx});
636 ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
644 int count = path.elementCount();
646 while (subStart < count) {
647 int subEnd = subStart;
648 for (
int i = subStart + 1; i < count; ++i) {
649 const auto &e = path.elementAt(i);
650 if (e.isSubpathStart()) {
654 if (i == count - 1) {
659 bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
660 const int subCount = subEnd - subStart + 1;
662 auto addIdx = [&](
int idx,
int delta) ->
int {
663 int subIdx = idx - subStart;
665 subIdx = (subIdx + subCount + delta) % subCount;
668 return subStart + subIdx;
670 auto elementAt = [&](
int idx,
int delta) ->
const QQuadPath::Element * {
671 int subIdx = idx - subStart;
673 subIdx = (subIdx + subCount + delta) % subCount;
674 return &path.elementAt(subStart + subIdx);
677 if (subIdx >= 0 && subIdx < subCount)
678 return &path.elementAt(subStart + subIdx);
682 for (
int i = subStart; i <= subEnd; ++i) {
683 const auto &element = path.elementAt(i);
684 const auto *nextElement = elementAt(i, +1);
685 const auto *prevElement = elementAt(i, -1);
687 const auto &s = element.startPoint();
688 const auto &e = element.endPoint();
690 bool startInnerIsRight;
691 bool startBisectorWithinMiterLimit;
692 bool giveUpOnStartJoin;
693 auto startJoin = calculateJoin(prevElement, &element,
694 penFactor, inverseMiterLimit, simpleMiter,
695 startBisectorWithinMiterLimit, startInnerIsRight,
697 const QVector2D &startInner = startJoin[1];
698 const QVector2D &startOuter = startJoin[3];
700 bool endInnerIsRight;
701 bool endBisectorWithinMiterLimit;
702 bool giveUpOnEndJoin;
703 auto endJoin = calculateJoin(&element, nextElement,
704 penFactor, inverseMiterLimit, simpleMiter,
705 endBisectorWithinMiterLimit, endInnerIsRight,
707 QVector2D endInner = endJoin[0];
708 QVector2D endOuter = endJoin[2];
709 QVector2D nextOuter = endJoin[3];
710 QVector2D outerB = endJoin[4];
712 QVector2D p1, p2, p3, p4;
713 QVector2D n1, n2, n3, n4;
715 if (startInnerIsRight) {
723 p1 = s + n1 * penFactor;
724 p2 = s + n2 * penFactor;
727 if (endInnerIsRight) {
735 p3 = e + n3 * penFactor;
736 p4 = e + n4 * penFactor;
741 QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
745 }
else if (squareCap) {
746 QVector2D c1 = p1 + capSpace;
747 QVector2D c2 = p2 + capSpace;
748 ret.append({{p1, s, c1}, {}, {}, -1});
749 ret.append({{c1, s, c2}, {}, {}, -1});
750 ret.append({{p2, s, c2}, {}, {}, -1});
754 QVector2D capSpace = tangentVector(element,
true).normalized() * -penFactor;
758 }
else if (squareCap) {
759 QVector2D c3 = p3 + capSpace;
760 QVector2D c4 = p4 + capSpace;
761 ret.append({{p3, e, c3}, {}, {}, -1});
762 ret.append({{c3, e, c4}, {}, {}, -1});
763 ret.append({{p4, e, c4}, {}, {}, -1});
767 if (element.isLine()) {
768 ret.append({{p1, p2, p3}, {n1, n2, n3}, {}, i});
769 ret.append({{p2, p3, p4}, {n2, n3, n4}, {}, i});
771 triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
774 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
775 if (!trivialJoin && nextElement) {
778 bool innerOnRight = endInnerIsRight;
780 const auto outer1 = e + endOuter * penFactor;
781 const auto outer2 = e + nextOuter * penFactor;
784 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
785 ret.append({{outer1, e, outer2}, {}, {}, -1});
786 }
else if (roundJoin) {
787 ret.append({{outer1, e, outer2}, {}, {}, i});
788 QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
791 ret.append({{outer1, outer1 + nn, outer2}, {}, {}, i});
792 ret.append({{outer1 + nn, outer2, outer2 + nn}, {}, {}, i});
794 }
else if (miterJoin) {
795 QVector2D outer = e + outerB * penFactor;
796 ret.append({{outer1, e, outer}, {}, {}, -2});
797 ret.append({{outer, e, outer2}, {}, {}, -2});
800 if (!giveUpOnEndJoin) {
801 QVector2D inner = e + endInner * penFactor;
802 ret.append({{inner, e, outer1}, {endInner, {}, endOuter}, {}, i});
804 int nextIdx = addIdx(i, +1);
805 ret.append({{inner, e, outer2}, {endInner, {}, nextOuter}, {}, nextIdx});
809 subStart = subEnd + 1;
817static bool handleOverlap(QQuadPath &path,
int e1,
int e2,
int recursionLevel = 0)
820 if (path.elementAt(e1).isLine() && path.elementAt(e1).isLine())
823 if (!isOverlap(path, e1, e2)) {
827 if (recursionLevel > 8) {
828 qCDebug(lcSGCurveProcessor) <<
"Triangle overlap: recursion level" << recursionLevel <<
"aborting!";
832 if (path.elementAt(e1).childCount() > 1) {
833 auto e11 = path.indexOfChildAt(e1, 0);
834 auto e12 = path.indexOfChildAt(e1, 1);
835 handleOverlap(path, e11, e2, recursionLevel + 1);
836 handleOverlap(path, e12, e2, recursionLevel + 1);
837 }
else if (path.elementAt(e2).childCount() > 1) {
838 auto e21 = path.indexOfChildAt(e2, 0);
839 auto e22 = path.indexOfChildAt(e2, 1);
840 handleOverlap(path, e1, e21, recursionLevel + 1);
841 handleOverlap(path, e1, e22, recursionLevel + 1);
843 path.splitElementAt(e1);
844 auto e11 = path.indexOfChildAt(e1, 0);
845 auto e12 = path.indexOfChildAt(e1, 1);
846 bool overlap1 = isOverlap(path, e11, e2);
847 bool overlap2 = isOverlap(path, e12, e2);
848 if (!overlap1 && !overlap2)
852 if (path.elementAt(e2).isLine()) {
855 handleOverlap(path, e11, e2, recursionLevel + 1);
857 handleOverlap(path, e12, e2, recursionLevel + 1);
860 path.splitElementAt(e2);
861 auto e21 = path.indexOfChildAt(e2, 0);
862 auto e22 = path.indexOfChildAt(e2, 1);
864 handleOverlap(path, e11, e21, recursionLevel + 1);
865 handleOverlap(path, e11, e22, recursionLevel + 1);
868 handleOverlap(path, e12, e21, recursionLevel + 1);
869 handleOverlap(path, e12, e22, recursionLevel + 1);
878bool QSGCurveProcessor::solveOverlaps(QQuadPath &path)
880 bool changed =
false;
881 if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
884 const auto candidates = findOverlappingCandidates(path);
885 for (
auto candidate : candidates)
886 changed = handleOverlap(path, candidate.first, candidate.second) || changed;
888 path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
896QList<std::pair<
int,
int>> QSGCurveProcessor::findOverlappingCandidates(
const QQuadPath &path)
898 struct BRect {
float xmin;
float xmax;
float ymin;
float ymax; };
901 QVarLengthArray<
int, 64> elementStarts, elementEnds;
902 QVarLengthArray<BRect, 64> boundingRects;
903 elementStarts.reserve(path.elementCount());
904 boundingRects.reserve(path.elementCount());
905 for (
int i = 0; i < path.elementCount(); i++) {
906 QQuadPath::Element e = path.elementAt(i);
908 BRect bR{qMin(qMin(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
909 qMax(qMax(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
910 qMin(qMin(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y()),
911 qMax(qMax(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y())};
912 boundingRects.append(bR);
913 elementStarts.append(i);
917 auto compareXmin = [&](
int i,
int j){
return boundingRects.at(i).xmin < boundingRects.at(j).xmin;};
918 auto compareXmax = [&](
int i,
int j){
return boundingRects.at(i).xmax < boundingRects.at(j).xmax;};
919 std::sort(elementStarts.begin(), elementStarts.end(), compareXmin);
920 elementEnds = elementStarts;
921 std::sort(elementEnds.begin(), elementEnds.end(), compareXmax);
924 QList<std::pair<
int,
int>> overlappingBB;
929 int firstElementEnd = 0;
930 for (
const int addIndex : std::as_const(elementStarts)) {
931 const BRect &newR = boundingRects.at(addIndex);
934 while (bRpool.size() && firstElementEnd < elementEnds.size()) {
935 int removeIndex = elementEnds.at(firstElementEnd);
936 if (bRpool.contains(removeIndex) && newR.xmin > boundingRects.at(removeIndex).xmax) {
937 bRpool.removeOne(removeIndex);
945 for (
int j = 0; j < bRpool.size(); j++) {
946 int i = bRpool.at(j);
947 const BRect &r1 = boundingRects.at(i);
952 bool isNeighbor =
false;
953 if (i - addIndex == 1) {
954 if (!path.elementAt(addIndex).isSubpathEnd())
956 }
else if (addIndex - i == 1) {
957 if (!path.elementAt(i).isSubpathEnd())
961 if (isNeighbor && (r1.ymax <= newR.ymin || newR.ymax <= r1.ymin))
964 if (!isNeighbor && (r1.ymax < newR.ymin || newR.ymax < r1.ymin))
967 overlappingBB.append(std::pair<
int,
int>(i, addIndex));
969 bRpool.append(addIndex);
971 return overlappingBB;
975bool QSGCurveProcessor::removeNestedSubpaths(QQuadPath &path)
978 Q_ASSERT(path.testHint(QQuadPath::PathNonIntersecting));
980 if (path.fillRule() != Qt::WindingFill) {
987 QList<
int> subPathStartPoints;
988 QList<
int> subPathEndPoints;
989 for (
int i = 0; i < path.elementCount(); i++) {
990 if (path.elementAt(i).isSubpathStart())
991 subPathStartPoints.append(i);
992 if (path.elementAt(i).isSubpathEnd()) {
993 subPathEndPoints.append(i);
996 const int subPathCount = subPathStartPoints.size();
999 if (subPathStartPoints.size() < 2)
1003 QList<
bool> isInside;
1004 bool isAnyInside =
false;
1005 isInside.reserve(subPathStartPoints.size() * subPathStartPoints.size());
1006 for (
int i = 0; i < subPathCount; i++) {
1007 for (
int j = 0; j < subPathCount; j++) {
1009 isInside.append(
false);
1011 isInside.append(path.contains(path.elementAt(subPathStartPoints.at(i)).startPoint(),
1012 subPathStartPoints.at(j), subPathEndPoints.at(j)));
1013 if (isInside.last())
1025 QList<
bool> clockwise;
1026 clockwise.reserve(subPathCount);
1027 for (
int i = 0; i < subPathCount; i++) {
1028 float sumProduct = 0;
1029 for (
int j = subPathStartPoints.at(i); j <= subPathEndPoints.at(i); j++) {
1030 const QVector2D startPoint = path.elementAt(j).startPoint();
1031 const QVector2D endPoint = path.elementAt(j).endPoint();
1032 sumProduct += (endPoint.x() - startPoint.x()) * (endPoint.y() + startPoint.y());
1034 clockwise.append(sumProduct > 0);
1039 QList<
bool> isFilled;
1040 isFilled.reserve(subPathStartPoints.size() );
1041 for (
int i = 0; i < subPathCount; i++) {
1042 int crossings = clockwise.at(i) ? 1 : -1;
1043 for (
int j = 0; j < subPathStartPoints.size(); j++) {
1044 if (isInside.at(i * subPathCount + j))
1045 crossings += clockwise.at(j) ? 1 : -1;
1047 isFilled.append(crossings != 0);
1052 auto findClosestOuterSubpath = [&](
int subPath) {
1054 QList<
int> candidates;
1055 for (
int i = 0; i < subPathStartPoints.size(); i++) {
1056 if (isInside.at(subPath * subPathCount + i))
1057 candidates.append(i);
1059 int maxNestingLevel = -1;
1060 int maxNestingLevelIndex = -1;
1061 for (
int i = 0; i < candidates.size(); i++) {
1062 int nestingLevel = 0;
1063 for (
int j = 0; j < candidates.size(); j++) {
1064 if (isInside.at(candidates.at(i) * subPathCount + candidates.at(j))) {
1068 if (nestingLevel > maxNestingLevel) {
1069 maxNestingLevel = nestingLevel;
1070 maxNestingLevelIndex = candidates.at(i);
1073 return maxNestingLevelIndex;
1076 bool pathChanged =
false;
1077 QQuadPath fixedPath;
1078 fixedPath.setPathHints(path.pathHints());
1082 for (
int i = 0; i < subPathCount; i++) {
1083 int j = findClosestOuterSubpath(i);
1084 if (j >= 0 && isFilled.at(i) == isFilled.at(j)) {
1087 for (
int k = subPathStartPoints.at(i); k <= subPathEndPoints.at(i); k++)
1088 fixedPath.addElement(path.elementAt(k));
1098bool QSGCurveProcessor::solveIntersections(QQuadPath &path,
bool removeNestedPaths)
1100 if (path.testHint(QQuadPath::PathNonIntersecting)) {
1101 if (removeNestedPaths)
1102 return removeNestedSubpaths(path);
1107 if (path.elementCount() < 2) {
1108 path.setHint(QQuadPath::PathNonIntersecting);
1112 struct IntersectionData {
int e1;
int e2;
float t1;
float t2;
bool in1 =
false, in2 =
false, out1 =
false, out2 =
false; };
1113 QList<IntersectionData> intersections;
1117 auto markIntersectionAsHandled = [=](IntersectionData *data,
int i,
bool forward) {
1118 if (data->e1 == i) {
1123 }
else if (data->e2 == i){
1134 const QList<std::pair<
int,
int>> candidates = findOverlappingCandidates(path);
1136 for (
const auto &candidate : candidates) {
1137 QList<std::pair<
float,
float>> res;
1138 int e1 = candidate.first;
1139 int e2 = candidate.second;
1140 if (isIntersecting(path, e1, e2, &res)) {
1141 for (
const auto &r : res)
1142 intersections.append({e1, e2, r.first, r.second});
1146 qCDebug(lcSGCurveIntersectionSolver) <<
"----- Checking for Intersections -----";
1147 qCDebug(lcSGCurveIntersectionSolver) <<
"Found" << intersections.length() <<
"intersections";
1148 if (lcSGCurveIntersectionSolver().isDebugEnabled()) {
1149 for (
const auto &i : intersections) {
1150 auto p1 = path.elementAt(i.e1).pointAtFraction(i.t1);
1151 auto p2 = path.elementAt(i.e2).pointAtFraction(i.t2);
1152 qCDebug(lcSGCurveIntersectionSolver) <<
" between" << i.e1 <<
"and" << i.e2 <<
"at" << i.t1 <<
"/" << i.t2 <<
"->" << p1 <<
"/" << p2;
1156 if (intersections.isEmpty()) {
1157 path.setHint(QQuadPath::PathNonIntersecting);
1158 if (removeNestedPaths) {
1159 qCDebug(lcSGCurveIntersectionSolver) <<
"No Intersections found. Looking for enclosed subpaths.";
1160 return removeNestedSubpaths(path);
1162 qCDebug(lcSGCurveIntersectionSolver) <<
"Returning the path unchanged.";
1172 QList<
int> subPathStartPoints;
1173 QList<
int> subPathEndPoints;
1174 QList<
bool> subPathHandled;
1175 for (
int i = 0; i < path.elementCount(); i++) {
1176 if (path.elementAt(i).isSubpathStart())
1177 subPathStartPoints.append(i);
1178 if (path.elementAt(i).isSubpathEnd()) {
1179 subPathEndPoints.append(i);
1180 subPathHandled.append(
false);
1185 auto subPathIndex = [&](
int index) {
1186 for (
int i = 0; i < subPathStartPoints.size(); i++) {
1187 if (index >= subPathStartPoints.at(i) && index <= subPathEndPoints.at(i))
1194 auto ensureInBounds = [&](
int *i,
float *t,
float deltaT) {
1196 if (path.elementAt(*i).isSubpathStart())
1197 *i = subPathEndPoints.at(subPathIndex(*i));
1201 }
else if (*t >= 1.f) {
1202 if (path.elementAt(*i).isSubpathEnd())
1203 *i = subPathStartPoints.at(subPathIndex(*i));
1214 auto findStart = [=](QQuadPath &path,
int start,
int end,
int *result,
bool *forward) {
1215 for (
int i = start; i < end; i++) {
1217 if (subPathStartPoints.contains(i))
1218 adjecent = subPathEndPoints.at(subPathStartPoints.indexOf(i));
1222 QQuadPath::Element::FillSide fillSide = path.fillSideOf(i, 1e-4f);
1223 const bool leftInside = fillSide == QQuadPath::Element::FillSideLeft;
1224 const bool rightInside = fillSide == QQuadPath::Element::FillSideRight;
1225 qCDebug(lcSGCurveIntersectionSolver) <<
"Element" << i <<
"/" << adjecent <<
"meeting point is left/right inside:" << leftInside <<
"/" << rightInside;
1230 }
else if (leftInside) {
1241 QVarLengthArray<
bool> handledElements(path.elementCount(),
false);
1243 bool regularVisit =
true;
1245 QQuadPath fixedPath;
1246 fixedPath.setFillRule(path.fillRule());
1255 bool forward =
true;
1257 int startedAtIndex = -1;
1258 float startedAtT = -1;
1260 if (!findStart(path, 0, path.elementCount(), &i1, &forward)) {
1261 qCDebug(lcSGCurveIntersectionSolver) <<
"No suitable start found. This should not happen. Returning the path unchanged.";
1266 auto startNewSubPath = [&](
int i,
bool forward) {
1268 fixedPath.moveTo(path.elementAt(i).startPoint());
1271 fixedPath.moveTo(path.elementAt(i).endPoint());
1275 subPathHandled[subPathIndex(i)] =
true;
1277 startNewSubPath(i1, forward);
1281 int totalIterations = 0;
1284 int prevIntersection = -1;
1288 if (regularVisit && (t == 0 || t == 1)) {
1290 if (t == 1 && path.elementAt(i1).isSubpathEnd()) {
1291 nextIndex = subPathStartPoints.at(subPathIndex(i1));
1292 }
else if (t == 1) {
1293 nextIndex = nextIndex + 1;
1295 if (handledElements[nextIndex]) {
1296 qCDebug(lcSGCurveIntersectionSolver) <<
"Revisiting an element when trying to solve intersections. This should not happen. Returning the path unchanged.";
1299 handledElements[nextIndex] =
true;
1304 qCDebug(lcSGCurveIntersectionSolver) <<
"Checking section" << i1 <<
"from" << t <<
"going" << (forward ?
"forward" :
"backward");
1308 t1 = forward? 1 : -1;
1309 for (
int j = 0; j < intersections.size(); j++) {
1310 if (j == prevIntersection)
1312 if (i1 == intersections[j].e1 &&
1313 intersections[j].t1 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
1314 intersections[j].t1 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
1316 t1 = intersections[j].t1;
1317 i2 = intersections[j].e2;
1318 t2 = intersections[j].t2;
1320 if (i1 == intersections[j].e2 &&
1321 intersections[j].t2 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
1322 intersections[j].t2 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
1324 t1 = intersections[j].t2;
1325 i2 = intersections[j].e1;
1326 t2 = intersections[j].t1;
1329 prevIntersection = iC;
1332 qCDebug(lcSGCurveIntersectionSolver) <<
" No intersection found on my way. Adding the rest of the segment " << i1;
1333 regularVisit =
true;
1339 if (path.elementAt(i1).isLine()) {
1340 fixedPath.lineTo(path.elementAt(i1).endPoint());
1342 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(t, 1);
1343 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1345 if (path.elementAt(i1).isSubpathEnd()) {
1346 int index = subPathEndPoints.indexOf(i1);
1347 qCDebug(lcSGCurveIntersectionSolver) <<
" Going back to the start of subPath" << index;
1348 i1 = subPathStartPoints.at(index);
1354 if (path.elementAt(i1).isLine()) {
1355 fixedPath.lineTo(path.elementAt(i1).startPoint());
1357 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(0, t).reversed();
1358 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1360 if (path.elementAt(i1).isSubpathStart()) {
1361 int index = subPathStartPoints.indexOf(i1);
1362 qCDebug(lcSGCurveIntersectionSolver) <<
" Going back to the end of subPath" << index;
1363 i1 = subPathEndPoints.at(index);
1370 qCDebug(lcSGCurveIntersectionSolver) <<
" Found an intersection at" << t1 <<
"with" << i2 <<
"at" << t2;
1373 subPathHandled[subPathIndex(i2)] =
true;
1377 markIntersectionAsHandled(&intersections[iC], i1, !forward);
1381 const QQuadPath::Element &elem1 = path.elementAt(i1);
1382 if (elem1.isLine()) {
1383 fixedPath.lineTo(elem1.pointAtFraction(t1));
1385 QQuadPath::Element partUntilIntersection;
1387 partUntilIntersection = elem1.segmentFromTo(t, t1);
1389 partUntilIntersection = elem1.segmentFromTo(t1, t).reversed();
1391 fixedPath.quadTo(partUntilIntersection.controlPoint(), partUntilIntersection.endPoint());
1395 if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && !intersections[iC].out2) {
1396 i1 = intersections[iC].e2;
1397 t = intersections[iC].t2;
1399 }
else if (intersections[iC].in1 && intersections[iC].in2 && !intersections[iC].out1 && intersections[iC].out2) {
1400 i1 = intersections[iC].e1;
1401 t = intersections[iC].t1;
1403 }
else if (intersections[iC].in1 && !intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1404 i1 = intersections[iC].e2;
1405 t = intersections[iC].t2;
1407 }
else if (!intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1408 i1 = intersections[iC].e1;
1409 t = intersections[iC].t1;
1416 if (t1 !=0 && t1 != 1 && t2 != 0 && t2 != 1) {
1417 QVector2D tangent1 = elem1.tangentAtFraction(t1);
1419 tangent1 = -tangent1;
1420 const QQuadPath::Element &elem2 = path.elementAt(i2);
1421 const QVector2D tangent2 = elem2.tangentAtFraction(t2);
1422 const float angle = angleBetween(-tangent1, tangent2);
1423 qCDebug(lcSGCurveIntersectionSolver) <<
" Angle at intersection is" << angle;
1425 constexpr float deltaAngle = 1e-3f;
1426 if ((angle > deltaAngle && path.fillRule() == Qt::WindingFill) || (angle < -deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
1430 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going forward from" << t <<
"on" << i1;
1431 }
else if ((angle < -deltaAngle && path.fillRule() == Qt::WindingFill) || (angle > deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
1435 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going backward from" << t <<
"on" << i1;
1437 qCDebug(lcSGCurveIntersectionSolver) <<
" Found tangent. Staying on element";
1444 constexpr float deltaT = 1e-4f;
1446 float t2after = t2 + deltaT;
1447 ensureInBounds(&i2after, &t2after, deltaT);
1448 QQuadPath::Element::FillSide fillSideForwardNew = path.fillSideOf(i2after, t2after);
1449 if (fillSideForwardNew == QQuadPath::Element::FillSideRight) {
1453 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going forward from" << t <<
"on" << i1;
1456 float t2before = t2 - deltaT;
1457 ensureInBounds(&i2before, &t2before, deltaT);
1458 QQuadPath::Element::FillSide fillSideBackwardNew = path.fillSideOf(i2before, t2before);
1459 if (fillSideBackwardNew == QQuadPath::Element::FillSideLeft) {
1463 qCDebug(lcSGCurveIntersectionSolver) <<
" Next going backward from" << t <<
"on" << i1;
1465 qCDebug(lcSGCurveIntersectionSolver) <<
" Staying on element.";
1472 if (!(i1 == startedAtIndex && t == startedAtT))
1473 markIntersectionAsHandled(&intersections[iC], i1, forward);
1476 if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
1477 qCDebug(lcSGCurveIntersectionSolver) <<
" This intersection was processed completely and will be removed";
1478 intersections.removeAt(iC);
1479 prevIntersection = -1;
1481 regularVisit =
false;
1484 if (i1 == startedAtIndex && t == startedAtT) {
1487 qCDebug(lcSGCurveIntersectionSolver) <<
"Reached my starting point and try to find a new subpath.";
1490 int nextUnhandled = -1;
1491 for (
int i = 0; i < subPathHandled.size(); i++) {
1492 if (!subPathHandled.at(i)) {
1496 subPathHandled[i] =
true;
1498 if (findStart(path, subPathStartPoints.at(i), subPathEndPoints.at(i), &i1, &forward)) {
1500 qCDebug(lcSGCurveIntersectionSolver) <<
"Found a new subpath" << i <<
"to be processed.";
1501 startNewSubPath(i1, forward);
1502 regularVisit =
true;
1509 while (nextUnhandled < 0) {
1510 qCDebug(lcSGCurveIntersectionSolver) <<
"All subpaths handled. Looking for unhandled intersections.";
1511 if (intersections.isEmpty()) {
1512 qCDebug(lcSGCurveIntersectionSolver) <<
"All intersections handled. I am done.";
1513 fixedPath.setHint(QQuadPath::PathNonIntersecting);
1518 IntersectionData &unhandledIntersec = intersections[0];
1519 prevIntersection = 0;
1520 regularVisit =
false;
1521 qCDebug(lcSGCurveIntersectionSolver) <<
"Revisiting intersection of" << unhandledIntersec.e1 <<
"with" << unhandledIntersec.e2;
1522 qCDebug(lcSGCurveIntersectionSolver) <<
"Handled are" << unhandledIntersec.e1 <<
"in:" << unhandledIntersec.in1 <<
"out:" << unhandledIntersec.out1
1523 <<
"/" << unhandledIntersec.e2 <<
"in:" << unhandledIntersec.in2 <<
"out:" << unhandledIntersec.out2;
1528 auto lookForwardOnIntersection = [&](
bool *handledPath,
int nextE,
float nextT,
bool nextForward) {
1531 constexpr float deltaT = 1e-4f;
1532 int eForward = nextE;
1533 float tForward = nextT + (nextForward ? deltaT : -deltaT);
1534 ensureInBounds(&eForward, &tForward, deltaT);
1535 QQuadPath::Element::FillSide fillSide = path.fillSideOf(eForward, tForward);
1536 if ((nextForward && fillSide == QQuadPath::Element::FillSideRight) ||
1537 (!nextForward && fillSide == QQuadPath::Element::FillSideLeft)) {
1538 fixedPath.moveTo(path.elementAt(nextE).pointAtFraction(nextT));
1539 i1 = startedAtIndex = nextE;
1540 t = startedAtT = nextT;
1541 forward = nextForward;
1542 *handledPath =
true;
1548 if (lookForwardOnIntersection(&unhandledIntersec.in1, unhandledIntersec.e1, unhandledIntersec.t1,
false))
1550 if (lookForwardOnIntersection(&unhandledIntersec.in2, unhandledIntersec.e2, unhandledIntersec.t2,
false))
1552 if (lookForwardOnIntersection(&unhandledIntersec.out1, unhandledIntersec.e1, unhandledIntersec.t1,
true))
1554 if (lookForwardOnIntersection(&unhandledIntersec.out2, unhandledIntersec.e2, unhandledIntersec.t2,
true))
1557 intersections.removeFirst();
1558 qCDebug(lcSGCurveIntersectionSolver) <<
"Found no way to move forward at this intersection and removed it.";
1562 }
while (totalIterations < path.elementCount() * 50);
1565 qCDebug(lcSGCurveIntersectionSolver) <<
"Could not solve intersections of path. This should not happen. Returning the path unchanged.";
1571void QSGCurveProcessor::processStroke(
const QQuadPath &strokePath,
1573 float penWidth,
bool cosmetic,
1574 Qt::PenJoinStyle joinStyle,
1575 Qt::PenCapStyle capStyle,
1576 addStrokeTriangleCallback addTriangle,
1579 const bool expandingInVertexShader = cosmetic || QSGCurveStrokeNode::expandingStrokeEnabled();
1580 auto thePath = subdivide(strokePath, subdivisions).flattened();
1582 auto addCurveTriangle = [&](
const QQuadPath::Element &element,
const TriangleData &t) {
1583 qCDebug(lcSGCurveProcessor) << element <<
"->" << t;
1584 QSGCurveStrokeNode::TriangleFlags flags;
1585 flags.setFlag(QSGCurveStrokeNode::TriangleFlag::Line, element.isLine());
1586 addTriangle(t.points,
1587 { element.startPoint(), element.controlPoint(), element.endPoint() },
1588 t.normals, t.extrusions, flags);
1591 if (!expandingInVertexShader) {
1594 auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
1595 qCDebug(lcSGCurveProcessor) << thePath <<
"->" << triangles;
1597 auto addBevelTriangle = [&](
const TrianglePoints &p, QSGCurveStrokeNode::TriangleFlags flags)
1599 QVector2D fp1 = p[0];
1600 QVector2D fp2 = p[2];
1605 QVector2D nn = calcNormalVector(p[0], p[2]);
1606 if (determinant(p) < 0)
1608 float delta = penWidth / 2;
1610 QVector2D offset = nn.normalized() * delta;
1616 n[0] = (p[0] - p[1]).normalized();
1617 n[2] = (p[2] - p[1]).normalized();
1619 flags.setFlag(QSGCurveStrokeNode::TriangleFlag::Line);
1620 addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, {}, flags);
1623 for (
const auto &triangle : std::as_const(triangles)) {
1624 if (triangle.pathElementIndex < 0) {
1625 addBevelTriangle(triangle.points, {});
1628 const auto &element = thePath.elementAt(triangle.pathElementIndex);
1629 addCurveTriangle(element, triangle);
1635 auto addEdgeTriangle = [&](QVector2D start, QVector2D end,
const TriangleData &t) {
1636 qCDebug(lcSGCurveProcessor) <<
"line from" << start <<
"to" << end <<
"->" << t;
1637 addTriangle(t.points, { start, start, end }, t.normals, t.extrusions, QSGCurveStrokeNode::TriangleFlag::Line);
1640 const bool bevelJoin = joinStyle == Qt::BevelJoin;
1641 const bool roundJoin = joinStyle == Qt::RoundJoin;
1642 const bool miterJoin = !bevelJoin && !roundJoin;
1650 const bool simpleMiter = joinStyle == Qt::RoundJoin;
1652 Q_ASSERT(miterLimit > 0 || !miterJoin);
1653 const float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
1654 const float penFactor = penWidth / 2;
1656 auto triangulateCurve = [&](
int idx,
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3,
const QVector2D &p4,
1657 const QVector2D &n1,
const QVector2D &n2,
const QVector2D &n3,
const QVector2D &n4)
1659 const auto &element = thePath.elementAt(idx);
1660 Q_ASSERT(!element.isLine());
1661 const auto &s = element.startPoint();
1662 const auto &c = element.controlPoint();
1663 const auto &e = element.endPoint();
1665 bool controlPointOnRight = determinant(s, c, e) > 0;
1666 QVector2D startNormal = normalVector(element);
1667 QVector2D endNormal = normalVector(element,
true);
1668 QVector2D controlPointNormal = (startNormal + endNormal).normalized();
1669 if (controlPointOnRight)
1670 controlPointNormal = -controlPointNormal;
1671 TrianglePoints t1{p1, p2, c};
1672 TrianglePoints t2{p3, p4, c};
1673 bool simpleCase = !checkTriangleOverlap(t1, t2);
1675 addCurveTriangle(element, {{p1, p2, c}, {n1, n2, controlPointNormal},
1676 QSGCurveStrokeNode::defaultExtrusions(), idx});
1677 addCurveTriangle(element, {{p3, p4, c}, {n3, n4, controlPointNormal},
1678 QSGCurveStrokeNode::defaultExtrusions(), idx});
1679 if (controlPointOnRight) {
1680 addCurveTriangle(element, {{p1, p3, c}, {n1, n3, controlPointNormal},
1681 QSGCurveStrokeNode::defaultExtrusions(), idx});
1683 addCurveTriangle(element, {{p2, p4, c}, {n2, n4, controlPointNormal},
1684 QSGCurveStrokeNode::defaultExtrusions(), idx});
1687 const auto &triangles = simplePointTriangulator({p1, p2, c, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx);
1688 for (
const auto &triangle : triangles)
1689 addCurveTriangle(element, triangle);
1697 const int count = thePath.elementCount();
1699 while (subStart < count) {
1700 int subEnd = subStart;
1701 for (
int i = subStart + 1; i < count; ++i) {
1702 const auto &e = thePath.elementAt(i);
1703 if (e.isSubpathStart()) {
1707 if (i == count - 1) {
1712 bool closed = thePath.elementAt(subStart).startPoint() == thePath.elementAt(subEnd).endPoint();
1713 const int subCount = subEnd - subStart + 1;
1715 auto addIdx = [&](
int idx,
int delta) ->
int {
1716 int subIdx = idx - subStart;
1718 subIdx = (subIdx + subCount + delta) % subCount;
1721 return subStart + subIdx;
1723 auto elementAt = [&](
int idx,
int delta) ->
const QQuadPath::Element * {
1724 int subIdx = idx - subStart;
1726 subIdx = (subIdx + subCount + delta) % subCount;
1727 return &thePath.elementAt(subStart + subIdx);
1730 if (subIdx >= 0 && subIdx < subCount)
1731 return &thePath.elementAt(subStart + subIdx);
1735 for (
int i = subStart; i <= subEnd; ++i) {
1736 const auto &element = thePath.elementAt(i);
1737 const auto *nextElement = elementAt(i, +1);
1738 const auto *prevElement = elementAt(i, -1);
1740 const auto &s = element.startPoint();
1741 const auto &e = element.endPoint();
1743 bool startInnerIsRight;
1744 bool startBisectorWithinMiterLimit;
1745 bool giveUpOnStartJoin;
1746 auto startJoin = calculateJoin(prevElement, &element,
1747 penFactor, inverseMiterLimit, simpleMiter,
1748 startBisectorWithinMiterLimit, startInnerIsRight,
1750 const QVector2D &startInner = startJoin[1];
1751 const QVector2D &startOuter = startJoin[3];
1753 bool endInnerIsRight;
1754 bool endBisectorWithinMiterLimit;
1755 bool giveUpOnEndJoin;
1756 auto endJoin = calculateJoin(&element, nextElement,
1757 penFactor, inverseMiterLimit, simpleMiter,
1758 endBisectorWithinMiterLimit, endInnerIsRight,
1760 const QVector2D endInner = endJoin[0];
1761 const QVector2D endOuter = endJoin[2];
1762 const QVector2D nextOuter = endJoin[3];
1763 const QVector2D outerBisector = endJoin[4];
1764 const QVector2D startTangent = tangentVector(element,
false).normalized();
1765 const QVector2D endTangent = tangentVector(element,
true).normalized();
1767 QVector2D n1, n2, n3, n4;
1769 if (startInnerIsRight) {
1778 if (endInnerIsRight) {
1791 static const float artificialLineExtension = 50;
1794 if (capStyle != Qt::FlatCap) {
1795 const QVector2D capNormalNone(0, 0);
1800 const QVector2D capNormalUp(startTangent.y(), -startTangent.x());
1801 const QVector2D capNormalDown = -capNormalUp;
1803 const QVector2D capNormalUpOut = (capNormalUp - startTangent);
1804 const QVector2D capNormalDownOut = (capNormalDown - startTangent);
1805 Q_ASSERT(capNormalUpOut.length() == capNormalDownOut.length());
1806 if (capStyle == Qt::RoundCap) {
1807 addCurveTriangle(element, {{s, s, s}, {capNormalUp, capNormalNone, capNormalUpOut},
1808 QSGCurveStrokeNode::defaultExtrusions(), i});
1809 addCurveTriangle(element, {{s, s, s}, {capNormalUpOut, capNormalNone, capNormalDownOut},
1810 QSGCurveStrokeNode::defaultExtrusions(), i});
1811 addCurveTriangle(element, {{s, s, s}, {capNormalDown, capNormalNone, capNormalDownOut},
1812 QSGCurveStrokeNode::defaultExtrusions(), i});
1814 addEdgeTriangle(element.startPoint(), element.startPoint() - startTangent * penWidth * artificialLineExtension,
1815 {{s, s, s}, {capNormalUp, capNormalNone, capNormalUpOut},
1816 QSGCurveStrokeNode::defaultExtrusions(), i});
1817 const auto norm = normalVector(element,
false).normalized() * penWidth * artificialLineExtension;
1818 addEdgeTriangle(element.startPoint() - norm, element.startPoint() + norm,
1819 {{s, s, s}, {capNormalUpOut, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1820 addEdgeTriangle(element.startPoint(), element.startPoint() - startTangent * penWidth * artificialLineExtension,
1821 {{s, s, s}, {capNormalDown, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1825 const QVector2D capNormalUp(endTangent.y(), -endTangent.x());
1826 const QVector2D capNormalDown = -capNormalUp;
1827 const QVector2D capNormalUpOut = (capNormalUp - endTangent);
1828 const QVector2D capNormalDownOut = (capNormalDown - endTangent);
1829 Q_ASSERT(capNormalUpOut.length() == capNormalDownOut.length());
1830 if (capStyle == Qt::RoundCap) {
1831 addCurveTriangle(element, {{e, e, e}, {capNormalDown, capNormalNone, capNormalDownOut},
1832 QSGCurveStrokeNode::defaultExtrusions(), i});
1833 addCurveTriangle(element, {{e, e, e}, {capNormalUpOut, capNormalNone, capNormalDownOut},
1834 QSGCurveStrokeNode::defaultExtrusions(), i});
1835 addCurveTriangle(element, {{e, e, e}, {capNormalUp, capNormalNone, capNormalUpOut},
1836 QSGCurveStrokeNode::defaultExtrusions(), i});
1838 addEdgeTriangle(element.endPoint() - endTangent * penWidth * artificialLineExtension, element.endPoint(),
1839 {{e, e, e}, {capNormalDown, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1840 const auto norm = normalVector(element,
true).normalized() * penWidth * artificialLineExtension;
1841 addEdgeTriangle(element.endPoint() - norm, element.endPoint() + norm,
1842 {{e, e, e}, {capNormalUpOut, capNormalNone, capNormalDownOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1843 addEdgeTriangle(element.endPoint() - endTangent * penWidth * artificialLineExtension, element.endPoint(),
1844 {{e, e, e}, {capNormalUp, capNormalNone, capNormalUpOut}, QSGCurveStrokeNode::defaultExtrusions(), i});
1849 if (element.isLine()) {
1850 addCurveTriangle(element, {{s, s, e}, {n1, n2, n3}, QSGCurveStrokeNode::defaultExtrusions(), i});
1851 addCurveTriangle(element, {{s, e, e}, {n2, n3, n4}, QSGCurveStrokeNode::defaultExtrusions(), i});
1853 triangulateCurve(i, s, s, e, e, n1, n2, n3, n4);
1856 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
1857 if (!trivialJoin && nextElement) {
1859 bool innerOnRight = endInnerIsRight;
1861 const auto outer1 = e + endOuter;
1862 const auto outer2 = e + nextOuter;
1863 QVector2D nn = calcNormalVector(outer1, outer2).normalized();
1866 const QVector2D endOuterN = (outer1 - e).normalized();
1867 const QVector2D nextOuterN = (outer2 - e).normalized();
1868 const QVector2D endOuterBisectorN = (endOuterN + nn.normalized()).normalized();
1869 const QVector2D nextOuterBisectorN = (nextOuterN + nn.normalized()).normalized();
1871 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
1872 const float cosTheta = QVector2D::dotProduct(endOuterN, nextOuterN);
1873 const float cosHalf = cos(acos(cosTheta) / 2);
1876 const auto tan1 = endTangent * penWidth * artificialLineExtension;
1877 const auto tan2 = tangentVector(*nextElement,
false).normalized() * penWidth * artificialLineExtension;
1878 const auto bevelTan = (tan2 - tan1) / 2;
1883 addEdgeTriangle(element.endPoint() - bevelTan, element.endPoint() + bevelTan,
1884 {{e, e, e}, {endOuterN, {}, nextOuterN}, {cosHalf, cosHalf, cosHalf}, i});
1885 }
else if (roundJoin) {
1886 addCurveTriangle(element, {{outer1, e, outer2}, {endOuterN, {}, nextOuterN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1887 addCurveTriangle(element, {{outer1, outer1 + nn, outer2}, {endOuterN, endOuterBisectorN, nextOuterN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1888 addCurveTriangle(element, {{outer1 + nn, outer2, outer2 + nn}, {endOuterBisectorN, nextOuterN, nextOuterBisectorN}, QSGCurveStrokeNode::defaultExtrusions(), i});
1889 }
else if (miterJoin) {
1890 addEdgeTriangle(element.endPoint(), element.endPoint() - endTangent * penWidth * artificialLineExtension,
1891 {{e, e, e}, {endOuterN, {}, outerBisector}, QSGCurveStrokeNode::defaultExtrusions(), i});
1892 addEdgeTriangle(nextElement->startPoint(),
1893 nextElement->startPoint() - tangentVector(*nextElement,
false).normalized() * penWidth * artificialLineExtension,
1894 {{e, e, e}, {nextOuterN, {}, outerBisector}, QSGCurveStrokeNode::defaultExtrusions(), i});
1897 if (!giveUpOnEndJoin) {
1898 addCurveTriangle(element, {{e, e, e}, {endInner, {}, endOuter}, QSGCurveStrokeNode::defaultExtrusions(), i});
1900 int nextIdx = addIdx(i, +1);
1901 addCurveTriangle(*nextElement, {{e, e, e}, {endInner, {}, nextOuter}, QSGCurveStrokeNode::defaultExtrusions(), nextIdx});
1905 subStart = subEnd + 1;
1912 Q_STATIC_ASSERT(
sizeof(QVector2D) ==
sizeof(quint64));
1917 memcpy(&k, &key,
sizeof(QVector2D));
1918 return QHashPrivate::hash(k, seed);
1921void QSGCurveProcessor::processFill(
const QQuadPath &fillPath,
1922 Qt::FillRule fillRule,
1923 addTriangleCallback addTriangle)
1925 QPainterPath internalHull;
1926 internalHull.setFillRule(fillRule);
1928 QMultiHash<QVector2D,
int> pointHash;
1930 auto roundVec2D = [](
const QVector2D &p) -> QVector2D {
1931 return { qRound64(p.x() * 32.0f) / 32.0f, qRound64(p.y() * 32.0f) / 32.0f };
1934 auto addCurveTriangle = [&](
const QQuadPath::Element &element,
1935 const QVector2D &sp,
1936 const QVector2D &ep,
1937 const QVector2D &cp) {
1938 addTriangle({ sp, cp, ep },
1940 [&element](QVector2D v) {
return elementUvForPoint(element, v); });
1943 auto addCurveTriangleWithNormals = [&](
const QQuadPath::Element &element,
1944 const std::array<QVector2D, 3> &v,
1945 const std::array<QVector2D, 3> &n) {
1946 addTriangle(v, n, [&element](QVector2D v) {
return elementUvForPoint(element, v); });
1949 auto outsideNormal = [](
const QVector2D &startPoint,
1950 const QVector2D &endPoint,
1951 const QVector2D &insidePoint) {
1953 QVector2D baseLine = endPoint - startPoint;
1954 QVector2D insideVector = insidePoint - startPoint;
1955 QVector2D normal = normalVector(baseLine);
1957 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
1959 return swap ? normal : -normal;
1962 auto addTriangleForLine = [&](
const QQuadPath::Element &element,
1963 const QVector2D &sp,
1964 const QVector2D &ep,
1965 const QVector2D &cp) {
1966 addCurveTriangle(element, sp, ep, cp);
1969 const QVector2D normal = outsideNormal(sp, ep, cp);
1970 constexpr QVector2D null;
1971 addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
1972 addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
1975 auto addTriangleForConcave = [&](
const QQuadPath::Element &element,
1976 const QVector2D &sp,
1977 const QVector2D &ep,
1978 const QVector2D &cp) {
1979 addTriangleForLine(element, sp, ep, cp);
1982 auto addTriangleForConvex = [&](
const QQuadPath::Element &element,
1983 const QVector2D &sp,
1984 const QVector2D &ep,
1985 const QVector2D &cp) {
1986 addCurveTriangle(element, sp, ep, cp);
1989 constexpr QVector2D null;
1992 const QVector2D normal = outsideNormal(sp, cp, ep);
1993 addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
1998 const QVector2D normal = outsideNormal(ep, cp, sp);
1999 addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
2003 auto addFillTriangle = [&](
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3) {
2004 constexpr QVector3D uv(0.0, 1.0, -1.0);
2005 addTriangle({ p1, p2, p3 },
2007 [&uv](QVector2D) {
return uv; });
2010 fillPath.iterateElements([&](
const QQuadPath::Element &element,
int index) {
2011 QVector2D sp(element.startPoint());
2012 QVector2D cp(element.controlPoint());
2013 QVector2D ep(element.endPoint());
2014 QVector2D rsp = roundVec2D(sp);
2016 if (element.isSubpathStart())
2017 internalHull.moveTo(sp.toPointF());
2018 if (element.isLine()) {
2019 internalHull.lineTo(ep.toPointF());
2020 pointHash.insert(rsp, index);
2022 QVector2D rep = roundVec2D(ep);
2023 QVector2D rcp = roundVec2D(cp);
2024 if (element.isConvex()) {
2025 internalHull.lineTo(ep.toPointF());
2026 addTriangleForConvex(element, rsp, rep, rcp);
2027 pointHash.insert(rsp, index);
2029 internalHull.lineTo(cp.toPointF());
2030 internalHull.lineTo(ep.toPointF());
2031 addTriangleForConcave(element, rsp, rep, rcp);
2032 pointHash.insert(rcp, index);
2040 auto onSameSideOfLine = [](
const QVector2D &p1,
2041 const QVector2D &p2,
2042 const QVector2D &linePoint,
2043 const QVector2D &lineNormal) {
2044 float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
2045 float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
2046 return side1 * side2 >= 0;
2049 auto pointInSafeSpace = [&](
const QVector2D &p,
const QQuadPath::Element &element) ->
bool {
2050 const QVector2D a = element.startPoint();
2051 const QVector2D b = element.endPoint();
2052 const QVector2D c = element.controlPoint();
2057 const QVector2D n1 = calcNormalVector(a, c + (c - b));
2058 const QVector2D n2 = calcNormalVector(b, c + (c - a));
2059 bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
2060 bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
2061 return safeSideOf1 && safeSideOf2;
2066 auto handleTriangle = [&](
const QVector2D (&p)[3]) ->
bool {
2067 bool isLine =
false;
2068 bool isConcave =
false;
2069 bool isConvex =
false;
2070 int elementIndex = -1;
2072 bool foundElement =
false;
2076 for (
int i = 0; i < 3; ++i) {
2077 auto pointFoundRange = std::as_const(pointHash).equal_range(roundVec2D(p[i]));
2079 if (pointFoundRange.first == pointHash.constEnd())
2083 int testIndex = *pointFoundRange.first;
2084 bool ambiguous = std::next(pointFoundRange.first) != pointFoundRange.second;
2089 for (
auto it = pointFoundRange.first; it != pointFoundRange.second; ++it) {
2090 auto &el = fillPath.elementAt(*it);
2091 bool fillOnLeft = !el.isFillOnRight();
2092 auto sp = roundVec2D(el.startPoint());
2093 auto ep = roundVec2D(el.endPoint());
2095 auto pointInside = [&](
const QVector2D &p) {
2096 return p == sp || p == ep
2097 || QQuadPath::isPointOnLeft(p, el.startPoint(), el.endPoint()) == fillOnLeft;
2099 if (pointInside(p[0]) && pointInside(p[1]) && pointInside(p[2])) {
2106 const auto &element = fillPath.elementAt(testIndex);
2111 bool onElement =
false;
2112 for (
int j = 0; j < 3; ++j) {
2115 if (element.isConvex() || element.isLine())
2116 onElement = roundVec2D(element.endPoint()) == p[j];
2118 onElement = roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j];
2124 foundElement =
true;
2125 elementIndex = testIndex;
2126 isConvex = element.isConvex();
2127 isLine = element.isLine();
2128 isConcave = !isLine && !isConvex;
2135 int ci = (6 - si - ei) % 3;
2136 addTriangleForLine(fillPath.elementAt(elementIndex), p[si], p[ei], p[ci]);
2137 }
else if (isConcave) {
2138 addCurveTriangle(fillPath.elementAt(elementIndex), p[0], p[1], p[2]);
2139 }
else if (isConvex) {
2140 int oi = (6 - si - ei) % 3;
2141 const auto &otherPoint = p[oi];
2142 const auto &element = fillPath.elementAt(elementIndex);
2145 bool safeSpace = pointInSafeSpace(otherPoint, element);
2147 addCurveTriangle(element, p[0], p[1], p[2]);
2150 QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
2152 for (
int i = 0; i < 7; ++i) {
2153 safeSpace = pointInSafeSpace(newPoint, element);
2156 newPoint = (p[si] + p[ei] + newPoint) / 3;
2161 addCurveTriangle(element, p[si], p[ei], newPoint);
2163 addFillTriangle(p[si], p[oi], newPoint);
2164 addFillTriangle(p[ei], p[oi], newPoint);
2167 addFillTriangle(p[0], p[1], p[2]);
2172 addFillTriangle(p[0], p[1], p[2]);
2177 QTriangleSet triangles = qTriangulate(internalHull);
2179 if (triangles.indices.size() == 3)
2180 triangles.indices.setDataUint({ 0, 1, 2 });
2182 const quint32 *idxTable =
static_cast<
const quint32 *>(triangles.indices.data());
2183 for (
int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
2184 const quint32 *idx = &idxTable[triangle * 3];
2187 for (
int i = 0; i < 3; ++i) {
2188 p[i] = roundVec2D(QVector2D(
float(triangles.vertices.at(idx[i] * 2)),
2189 float(triangles.vertices.at(idx[i] * 2 + 1))));
2191 if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
2193 bool needsSplit = !handleTriangle(p);
2195 QVector2D c = (p[0] + p[1] + p[2]) / 3;
2196 for (
int i = 0; i < 3; ++i) {
Combined button and popup list for selecting options.
size_t qHash(QByteArrayView key, size_t seed) noexcept
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)