Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgcurveprocessor.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QtGui/private/qtriangulator_p.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qhash.h>
10
12
13Q_LOGGING_CATEGORY(lcSGCurveProcessor, "qt.quick.curveprocessor");
14Q_STATIC_LOGGING_CATEGORY(lcSGCurveIntersectionSolver, "qt.quick.curveprocessor.intersections");
15
16namespace {
17// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
18// v1 maps to [1,0], v2 maps to [0,1]. p is the point to be mapped to uv in this space (i.e. vector from p0)
19static inline QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
20{
21 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
22
23 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
24 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
25
26 return {u, v};
27}
28
29// Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
30// also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
31static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
32{
33 QVector2D v1 = 2 * (p1 - p0);
34 QVector2D v2 = p2 - v1 - p0;
35 return uvForPoint(v1, v2, p - p0);
36}
37
38static QVector3D elementUvForPoint(const QQuadPath::Element& e, QVector2D p)
39{
40 auto uv = curveUv(e.startPoint(), e.referencePoint(), e.endPoint(), p);
41 if (e.isLine())
42 return { uv.x(), uv.y(), 0.0f };
43 else
44 return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
45}
46
47static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
48{
49 auto v = b - a;
50 return {v.y(), -v.x()};
51}
52
53// The sign of the return value indicates which side of the line defined by a and n the point p falls
54static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
55{
56 float dot = QVector2D::dotProduct(p - a, n);
57 return dot;
58};
59
60static inline float determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
61{
62 return p1.x() * (p2.y() - p3.y())
63 + p2.x() * (p3.y() - p1.y())
64 + p3.x() * (p1.y() - p2.y());
65}
66
67/*
68 Clever triangle overlap algorithm. Stack Overflow says:
69
70 You can prove that the two triangles do not collide by finding an edge (out of the total 6
71 edges that make up the two triangles) that acts as a separating line where all the vertices
72 of one triangle lie on one side and the vertices of the other triangle lie on the other side.
73 If you can find such an edge then it means that the triangles do not intersect otherwise the
74 triangles are colliding.
75*/
76using TrianglePoints = std::array<QVector2D, 3>;
77using LinePoints = std::array<QVector2D, 2>;
78
79// The sign of the determinant tells the winding order: positive means counter-clockwise
80
81static inline double determinant(const TrianglePoints &p)
82{
83 return determinant(p[0], p[1], p[2]);
84}
85
86// Fix the triangle so that the determinant is positive
87static void fixWinding(TrianglePoints &p)
88{
89 double det = determinant(p);
90 if (det < 0.0) {
91 qSwap(p[0], p[1]);
92 }
93}
94
95// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
96// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
97// a positive determinant].
98bool checkEdge(const QVector2D &p1, const QVector2D &p2, const QVector2D &p, float epsilon)
99{
100 return determinant(p1, p2, p) <= epsilon;
101}
102
103// Check if lines l1 and l2 are intersecting and return the respective value. Solutions are stored to
104// the optional pointer solution.
105bool lineIntersection(const LinePoints &l1, const LinePoints &l2, QList<std::pair<float, float>> *solution = nullptr)
106{
107 constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
108
109 // see https://www.wolframalpha.com/input?i=solve%28A+%2B+t+*+B+%3D+C+%2B+s*D%3B+E+%2B+t+*+F+%3D+G+%2B+s+*+H+for+s+and+t%29
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();
118
119 float det = D * F - B * H;
120
121 if (det == 0)
122 return false;
123
124 float s = (F * (A - C) - B * (E - G)) / det;
125 float t = (H * (A - C) - D * (E - G)) / det;
126
127 // Intersections at 0 count. Intersections at 1 do not.
128 bool intersecting = (s >= 0 && s <= 1. - eps2 && t >= 0 && t <= 1. - eps2);
129
130 if (solution && intersecting)
131 solution->append(std::pair<float, float>(t, s));
132
133 return intersecting;
134}
135
136
137bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2, float epsilon = 1.0/32)
138{
139 // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
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))
146 return false;
147 }
148
149 // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
150 fixWinding(triangle2);
151 for (int i = 0; i < 3; i++) {
152 int ni = (i + 1) % 3;
153
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))
157 return false;
158 }
159
160 return true;
161}
162
163bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line, float epsilon = 1.0/32)
164{
165 // See if all vertices of the triangle are on the same side of the line
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;
169 // If all determinants have the same sign, then there is no overlap
170 if (s1 == s2 && s2 == s3) {
171 return false;
172 }
173 // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
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))
179 return false;
180 }
181
182 return true;
183}
184
185static bool isOverlap(const QQuadPath &path, int e1, int e2)
186{
187 const QQuadPath::Element &element1 = path.elementAt(e1);
188 const QQuadPath::Element &element2 = path.elementAt(e2);
189
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);
195 } else {
196 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
197 return checkLineTriangleOverlap(t2, line1);
198 }
199 } else {
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);
204 } else {
205 TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
206 return checkTriangleOverlap(t1, t2);
207 }
208 }
209
210 return false;
211}
212
213static float angleBetween(const QVector2D v1, const QVector2D v2)
214{
215 float dot = v1.x() * v2.x() + v1.y() * v2.y();
216 float cross = v1.x() * v2.y() - v1.y() * v2.x();
217 //TODO: Optimization: Maybe we don't need the atan2 here.
218 return atan2(cross, dot);
219}
220
221static bool isIntersecting(const TrianglePoints &t1, const TrianglePoints &t2, QList<std::pair<float, float>> *solutions = nullptr)
222{
223 constexpr double eps = 1e-5; // Epsilon for coordinate space x-y
224 constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
225 constexpr int maxIterations = 7; // Maximum iterations allowed for Newton
226
227 // Convert to double to get better accuracy.
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() };
230
231 // F = P1(t1) - P2(t2) where P1 and P2 are bezier curve functions.
232 // F = (0, 0) at the intersection.
233 // t is the vector of bezier curve parameters for curves P1 and P2
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();};
237
238 // J is the Jacobi Matrix dF/dt where F and t are both vectors of dimension 2.
239 // Storing in a QLineF for simplicity.
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());};
245
246 // solve the equation A(as 2x2 matrix)*x = b. Returns x.
247 auto solve = [](QLineF A, QPointF b) {
248 // invert A
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);
251 // return A^-1 * b
252 return QPointF(Ainv.x1() * b.x() + Ainv.y1() * b.y(),
253 Ainv.x2() * b.x() + Ainv.y2() * b.y());
254 };
255
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];
259#endif
260
261 // TODO: Try to figure out reasonable starting points to reach all 4 possible intersections.
262 // This works but is kinda brute forcing it.
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} };
266
267 for (auto t : tref) {
268 double err = 1;
269 QPointF fval = F(t);
270 int i = 0;
271
272 // TODO: Try to abort sooner, e.g. when falling out of the interval [0-1]?
273 while (err > eps && i < maxIterations) { // && t.x() >= 0 && t.x() <= 1 && t.y() >= 0 && t.y() <= 1) {
274 t = t - solve(J(t), fval);
275 fval = F(t);
276 err = qAbs(fval.x()) + qAbs(fval.y()); // Using the Manhatten length as an error indicator.
277 i++;
278#ifdef INTERSECTION_EXTRA_DEBUG
279 qCDebug(lcSGCurveIntersectionSolver) << " Newton iteration" << i << "t =" << t << "F =" << fval << "Error =" << err;
280#endif
281 }
282 // Intersections at 0 count. Intersections at 1 do not.
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) << ")";
286#endif
287 if (solutions) {
288 bool append = true;
289 for (auto solution : *solutions) {
290 if (qAbs(solution.first - t.x()) < 10 * eps2 && qAbs(solution.second - t.y()) < 10 * eps2) {
291 append = false;
292 break;
293 }
294 }
295 if (append)
296 solutions->append({t.x(), t.y()});
297 }
298 else
299 return true;
300 }
301 }
302 if (solutions)
303 return solutions->size() > 0;
304 else
305 return false;
306}
307
308static bool isIntersecting(const QQuadPath &path, int e1, int e2, QList<std::pair<float, float>> *solutions = nullptr)
309{
310
311 const QQuadPath::Element &elem1 = path.elementAt(e1);
312 const QQuadPath::Element &elem2 = path.elementAt(e2);
313
314 if (elem1.isLine() && elem2.isLine()) {
315 return lineIntersection(LinePoints {elem1.startPoint(), elem1.endPoint() },
316 LinePoints {elem2.startPoint(), elem2.endPoint() },
317 solutions);
318 } else {
319 return isIntersecting(TrianglePoints { elem1.startPoint(), elem1.controlPoint(), elem1.endPoint() },
320 TrianglePoints { elem2.startPoint(), elem2.controlPoint(), elem2.endPoint() },
321 solutions);
322 }
323}
324
325struct TriangleData
326{
327 TrianglePoints points;
328 TrianglePoints normals;
329 std::array<float, 3> extrusions;
330 int pathElementIndex = std::numeric_limits<int>::min();
331
332private:
333#ifndef QT_NO_DEBUG_STREAM
334 friend QDebug operator<<(QDebug, const TriangleData &);
335#endif
336};
337
338#ifndef QT_NO_DEBUG_STREAM
339QDebug operator<<(QDebug stream, const TriangleData &td)
340{
341 QDebugStateSaver saver(stream);
342 stream.nospace();
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()
352 << "])";
353 return stream;
354}
355#endif
356
357// Returns a normalized vector that is perpendicular to baseLine, pointing to the right
358inline QVector2D normalVector(QVector2D baseLine)
359{
360 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
361 return normal;
362}
363
364/*! \internal
365 Returns a vector that is normal to the path and pointing to the right. If \a endSide
366 is \c false, the vector is normal to the start point;
367 if \c true, the vector is normal to the end point.
368*/
369QVector2D normalVector(const QQuadPath::Element &element, bool endSide = false)
370{
371 if (element.isLine())
372 return normalVector(element.endPoint() - element.startPoint());
373 else if (!endSide)
374 return normalVector(element.controlPoint() - element.startPoint());
375 else
376 return normalVector(element.endPoint() - element.controlPoint());
377}
378
379/*! \internal
380 Returns a vector that is parallel to the path and pointing inward. If \a endSide
381 is \c false, it starts at the start point and points forward;
382 if \c true, it starts at the end point and points backward.
383*/
384QVector2D tangentVector(const QQuadPath::Element &element, bool endSide = false)
385{
386 if (element.isLine()) {
387 if (!endSide)
388 return element.endPoint() - element.startPoint();
389 else
390 return element.startPoint() - element.endPoint();
391 } else {
392 if (!endSide)
393 return element.controlPoint() - element.startPoint();
394 else
395 return element.controlPoint() - element.endPoint();
396 }
397}
398
399// Really simplistic O(n^2) triangulator - only intended for five points
400QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, const QList<QVector2D> &normals, int elementIndex)
401{
402 int count = pts.size();
403 Q_ASSERT(count >= 3);
404 Q_ASSERT(normals.size() == count);
405
406 // First we find the convex hull: it's always in positive determinant winding order
407 QList<int> hull;
408 float det1 = determinant(pts[0], pts[1], pts[2]);
409 if (det1 > 0)
410 hull << 0 << 1 << 2;
411 else
412 hull << 2 << 1 << 0;
413 auto connectableInHull = [&](int idx) -> QList<int> {
414 QList<int> r;
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)
421 r << i;
422 }
423 return r;
424 };
425 for (int i = 3; i < count; ++i) {
426 auto visible = connectableInHull(i);
427 if (visible.isEmpty())
428 continue;
429 int visCount = visible.count();
430 int hullCount = hull.count();
431 // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
432 // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
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];
437 break;
438 }
439 }
440 // Finally replace the points that are now inside the hull
441 // We insert the new point after boundaryStart, and before boundaryStart + visCount (modulo...)
442 // and remove the points in between
443 int pointsToKeep = hullCount - visCount + 1;
444 QList<int> newHull;
445 newHull << i;
446 for (int j = 0; j < pointsToKeep; ++j) {
447 newHull << hull.at((j + boundaryStart + visCount) % hullCount);
448 }
449 hull = newHull;
450 }
451
452 // Now that we have a convex hull, we can trivially triangulate it
453 QList<TriangleData> ret;
454 for (int i = 1; i < hull.size() - 1; ++i) {
455 int i0 = hull[0];
456 int i1 = hull[i];
457 int i2 = hull[i+1];
458 ret.append({{pts[i0], pts[i1], pts[i2]}, {normals[i0], normals[i1], normals[i2]},
459 QSGCurveStrokeNode::defaultExtrusions(), elementIndex});
460 }
461 return ret;
462}
463
464
465inline bool needsSplit(const QQuadPath::Element &el)
466{
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());
471 return cos < 0.9;
472}
473
474
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);
480 }
481}
482
483static QQuadPath subdivide(const QQuadPath &path, int subdivisions)
484{
485 QQuadPath newPath = path;
486 newPath.iterateElements([&](QQuadPath::Element &e, int index) {
487 if (!e.isLine())
488 splitElementIfNecessary(&newPath, index, subdivisions);
489 });
490
491 return newPath;
492}
493
494/*! \internal
495 Returns {inner1, inner2, outer1, outer2, outerMiter}
496 where inner1 and outer1 are for the end of \a element1,
497 where inner2 and outer2 are for the start of \a element2,
498 and inner1 == inner2 unless we had to give up finding a decent point.
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)
503{
504 outerBisectorWithinMiterLimit = true;
505 innerIsRight = true;
506 giveUp = false;
507 if (!element1) {
508 Q_ASSERT(element2);
509 QVector2D n = normalVector(*element2);
510 return {n, n, -n, -n, -n};
511 }
512 if (!element2) {
513 Q_ASSERT(element1);
514 QVector2D n = normalVector(*element1, true);
515 return {n, n, -n, -n, -n};
516 }
517
518 Q_ASSERT(element1->endPoint() == element2->startPoint());
519
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();
523
524 const auto v1 = (p1 - p2).normalized();
525 const auto v2 = (p3 - p2).normalized();
526 const auto b = (v1 + v2);
527
528 constexpr float epsilon = 1.0f / 32.0f;
529 const bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
530
531 if (smoothJoin) {
532 // v1 and v2 are almost parallel and pointing in opposite directions
533 // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
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};
538 }
539
540 // Calculate the length of the bisector, so it will cover the entire miter.
541 // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
542 // dot product of two unit vectors is the cosine of the angle between them
543 // The length of the miter is w/sin(x/2) where x is the angle between the two elements
544 const auto bisector = b.normalized();
545 const float cos2x = qMin(1.0f, QVector2D::dotProduct(v1, v2)); // Allow for float inaccuracy
546 const float sine = qMax(sqrt((1.0f - cos2x) / 2), 0.01f); // Avoid divide by zero
547 const float length = penFactor / sine;
548 innerIsRight = determinant(p1, p2, p3) > 0;
549
550 // Check if bisector is longer than one of the lines it's trying to bisect
551 auto tooLong = [penFactor, length](QVector2D p1, QVector2D p2, QVector2D n) -> bool {
552 auto v = p2 - p1;
553 // It's too long if the projection onto the bisector is longer than the bisector
554 // and half the projection (v - proj) onto the normal to the bisector
555 // (minus AA margin) is shorter than the pen factor.
556 // (We're also adding a 10% safety margin to length to make room for AA: not exact.)
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;
561 };
562
563 // The angle bisector of the tangent lines is not correct for curved lines. We could fix this by calculating
564 // the exact intersection point, but for now just give up and use the normals.
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;
569
570 if (simpleJoin)
571 return {bn, bn, -bn, -bn, -bn}; // We only have one inner and one outer point TODO: change inner point when conflict/curve
572 const QVector2D n1 = normalVector(*element1, true);
573 const QVector2D n2 = normalVector(*element2);
574 if (giveUp) {
575 if (innerIsRight)
576 return {n1, n2, -n1, -n2, -bn};
577 else
578 return {-n1, -n2, n1, n2, -bn};
579
580 } else {
581 if (innerIsRight)
582 return {bn, bn, -n1, -n2, -bn};
583 else
584 return {bn, bn, n1, n2, -bn};
585 }
586};
587
588static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
589{
590 const bool bevelJoin = joinStyle == Qt::BevelJoin;
591 const bool roundJoin = joinStyle == Qt::RoundJoin;
592 const bool miterJoin = !bevelJoin && !roundJoin;
593
594 const bool roundCap = capStyle == Qt::RoundCap;
595 const bool squareCap = capStyle == Qt::SquareCap;
596 // We can't use the simple miter for miter joins, since the shader currently only supports round joins
597 const bool simpleMiter = joinStyle == Qt::RoundJoin;
598
599 Q_ASSERT(miterLimit > 0 || !miterJoin);
600 float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
601
602 const float penFactor = penWidth / 2;
603
604 QList<TriangleData> ret;
605
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)
609 {
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();
615 // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
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; // This is too simplistic
623 TrianglePoints t1{p1, p2, p5};
624 TrianglePoints t2{p3, p4, p5};
625 bool simpleCase = !checkTriangleOverlap(t1, t2);
626
627 if (simpleCase) {
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});
632 } else {
633 ret.append({{p2, p4, p5}, {n2, n4, controlPointNormal}, {}, idx});
634 }
635 } else {
636 ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
637 }
638 };
639
640 // Each element is calculated independently, so we don't have to special-case closed sub-paths.
641 // Take care so the end points of one element are precisely equal to the start points of the next.
642 // Any additional triangles needed for joining are added at the end of the current element.
643
644 int count = path.elementCount();
645 int subStart = 0;
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()) {
651 subEnd = i - 1;
652 break;
653 }
654 if (i == count - 1) {
655 subEnd = i;
656 break;
657 }
658 }
659 bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
660 const int subCount = subEnd - subStart + 1;
661
662 auto addIdx = [&](int idx, int delta) -> int {
663 int subIdx = idx - subStart;
664 if (closed)
665 subIdx = (subIdx + subCount + delta) % subCount;
666 else
667 subIdx += delta;
668 return subStart + subIdx;
669 };
670 auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
671 int subIdx = idx - subStart;
672 if (closed) {
673 subIdx = (subIdx + subCount + delta) % subCount;
674 return &path.elementAt(subStart + subIdx);
675 }
676 subIdx += delta;
677 if (subIdx >= 0 && subIdx < subCount)
678 return &path.elementAt(subStart + subIdx);
679 return nullptr;
680 };
681
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);
686
687 const auto &s = element.startPoint();
688 const auto &e = element.endPoint();
689
690 bool startInnerIsRight;
691 bool startBisectorWithinMiterLimit; // Not used
692 bool giveUpOnStartJoin; // Not used
693 auto startJoin = calculateJoin(prevElement, &element,
694 penFactor, inverseMiterLimit, simpleMiter,
695 startBisectorWithinMiterLimit, startInnerIsRight,
696 giveUpOnStartJoin);
697 const QVector2D &startInner = startJoin[1];
698 const QVector2D &startOuter = startJoin[3];
699
700 bool endInnerIsRight;
701 bool endBisectorWithinMiterLimit;
702 bool giveUpOnEndJoin;
703 auto endJoin = calculateJoin(&element, nextElement,
704 penFactor, inverseMiterLimit, simpleMiter,
705 endBisectorWithinMiterLimit, endInnerIsRight,
706 giveUpOnEndJoin);
707 QVector2D endInner = endJoin[0];
708 QVector2D endOuter = endJoin[2];
709 QVector2D nextOuter = endJoin[3];
710 QVector2D outerB = endJoin[4];
711
712 QVector2D p1, p2, p3, p4;
713 QVector2D n1, n2, n3, n4;
714
715 if (startInnerIsRight) {
716 n1 = startInner;
717 n2 = startOuter;
718 } else {
719 n1 = startOuter;
720 n2 = startInner;
721 }
722
723 p1 = s + n1 * penFactor;
724 p2 = s + n2 * penFactor;
725
726 // repeat logic above for the other end:
727 if (endInnerIsRight) {
728 n3 = endInner;
729 n4 = endOuter;
730 } else {
731 n3 = endOuter;
732 n4 = endInner;
733 }
734
735 p3 = e + n3 * penFactor;
736 p4 = e + n4 * penFactor;
737
738 // End caps
739
740 if (!prevElement) {
741 QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
742 if (roundCap) {
743 p1 += capSpace;
744 p2 += capSpace;
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});
751 }
752 }
753 if (!nextElement) {
754 QVector2D capSpace = tangentVector(element, true).normalized() * -penFactor;
755 if (roundCap) {
756 p3 += capSpace;
757 p4 += capSpace;
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});
764 }
765 }
766
767 if (element.isLine()) {
768 ret.append({{p1, p2, p3}, {n1, n2, n3}, {}, i});
769 ret.append({{p2, p3, p4}, {n2, n3, n4}, {}, i});
770 } else {
771 triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
772 }
773
774 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
775 if (!trivialJoin && nextElement) {
776 // inside of join (opposite of bevel) is defined by
777 // triangle s, e, next.e
778 bool innerOnRight = endInnerIsRight;
779
780 const auto outer1 = e + endOuter * penFactor;
781 const auto outer2 = e + nextOuter * penFactor;
782 //const auto inner = e + endInner * penFactor;
783
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;
789 if (!innerOnRight)
790 nn = -nn;
791 ret.append({{outer1, outer1 + nn, outer2}, {}, {}, i});
792 ret.append({{outer1 + nn, outer2, outer2 + nn}, {}, {}, i});
793
794 } else if (miterJoin) {
795 QVector2D outer = e + outerB * penFactor;
796 ret.append({{outer1, e, outer}, {}, {}, -2});
797 ret.append({{outer, e, outer2}, {}, {}, -2});
798 }
799
800 if (!giveUpOnEndJoin) {
801 QVector2D inner = e + endInner * penFactor;
802 ret.append({{inner, e, outer1}, {endInner, {}, endOuter}, {}, i});
803 // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
804 int nextIdx = addIdx(i, +1);
805 ret.append({{inner, e, outer2}, {endInner, {}, nextOuter}, {}, nextIdx});
806 }
807 }
808 }
809 subStart = subEnd + 1;
810 }
811 return ret;
812}
813
814// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
815// elements
816// Returns true if a change was made
817static bool handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
818{
819 // Splitting lines is not going to help with overlap, since we assume that lines don't intersect
820 if (path.elementAt(e1).isLine() && path.elementAt(e1).isLine())
821 return false;
822
823 if (!isOverlap(path, e1, e2)) {
824 return false;
825 }
826
827 if (recursionLevel > 8) {
828 qCDebug(lcSGCurveProcessor) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
829 return false;
830 }
831
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);
842 } else {
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)
849 return true; // no more overlap: success!
850
851 // We need to split more:
852 if (path.elementAt(e2).isLine()) {
853 // Splitting a line won't help, so we just split e1 further
854 if (overlap1)
855 handleOverlap(path, e11, e2, recursionLevel + 1);
856 if (overlap2)
857 handleOverlap(path, e12, e2, recursionLevel + 1);
858 } else {
859 // See if splitting e2 works:
860 path.splitElementAt(e2);
861 auto e21 = path.indexOfChildAt(e2, 0);
862 auto e22 = path.indexOfChildAt(e2, 1);
863 if (overlap1) {
864 handleOverlap(path, e11, e21, recursionLevel + 1);
865 handleOverlap(path, e11, e22, recursionLevel + 1);
866 }
867 if (overlap2) {
868 handleOverlap(path, e12, e21, recursionLevel + 1);
869 handleOverlap(path, e12, e22, recursionLevel + 1);
870 }
871 }
872 }
873 return true;
874}
875}
876
877// Returns true if the path was changed
878bool QSGCurveProcessor::solveOverlaps(QQuadPath &path)
879{
880 bool changed = false;
881 if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
882 return false;
883
884 const auto candidates = findOverlappingCandidates(path);
885 for (auto candidate : candidates)
886 changed = handleOverlap(path, candidate.first, candidate.second) || changed;
887
888 path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
889 return changed;
890}
891
892// A fast algorithm to find path elements that might overlap. We will only check the overlap of the
893// triangles that define the elements.
894// We will order the elements first and then pool them depending on their x-values. This should
895// reduce the complexity to O(n log n), where n is the number of elements in the path.
896QList<std::pair<int, int>> QSGCurveProcessor::findOverlappingCandidates(const QQuadPath &path)
897{
898 struct BRect { float xmin; float xmax; float ymin; float ymax; };
899
900 // Calculate all bounding rectangles
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);
907
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);
914 }
915
916 // Sort the bounding rectangles by x-startpoint and x-endpoint
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);
922
923 QList<int> bRpool;
924 QList<std::pair<int, int>> overlappingBB;
925
926 // Start from x = xmin and move towards xmax. Add a rectangle to the pool and check for
927 // intersections with all other rectangles in the pool. If a rectangles xmax is smaller
928 // than the new xmin, it can be removed from the pool.
929 int firstElementEnd = 0;
930 for (const int addIndex : std::as_const(elementStarts)) {
931 const BRect &newR = boundingRects.at(addIndex);
932 // First remove elements from the pool that cannot touch the new one
933 // because xmax is too small
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);
938 firstElementEnd++;
939 } else {
940 break;
941 }
942 }
943
944 // Now compare the new element with all elements in the pool.
945 for (int j = 0; j < bRpool.size(); j++) {
946 int i = bRpool.at(j);
947 const BRect &r1 = boundingRects.at(i);
948 // We don't have to check for x because the pooling takes care of it.
949 //if (r1.xmax <= newR.xmin || newR.xmax <= r1.xmin)
950 // continue;
951
952 bool isNeighbor = false;
953 if (i - addIndex == 1) {
954 if (!path.elementAt(addIndex).isSubpathEnd())
955 isNeighbor = true;
956 } else if (addIndex - i == 1) {
957 if (!path.elementAt(i).isSubpathEnd())
958 isNeighbor = true;
959 }
960 // Neighbors need to be completely different (otherwise they just share a point)
961 if (isNeighbor && (r1.ymax <= newR.ymin || newR.ymax <= r1.ymin))
962 continue;
963 // Non-neighbors can also just touch
964 if (!isNeighbor && (r1.ymax < newR.ymin || newR.ymax < r1.ymin))
965 continue;
966 // If the bounding boxes are overlapping it is a candidate for an intersection.
967 overlappingBB.append(std::pair<int, int>(i, addIndex));
968 }
969 bRpool.append(addIndex); //Add the new element to the pool.
970 }
971 return overlappingBB;
972}
973
974// Remove paths that are nested inside another path and not required to fill the path correctly
975bool QSGCurveProcessor::removeNestedSubpaths(QQuadPath &path)
976{
977 // Ensure that the path is not intersecting first
978 Q_ASSERT(path.testHint(QQuadPath::PathNonIntersecting));
979
980 if (path.fillRule() != Qt::WindingFill) {
981 // If the fillingRule is odd-even, all internal subpaths matter
982 return false;
983 }
984
985 // Store the starting and end elements of the subpaths to be able
986 // to jump quickly between them.
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);
994 }
995 }
996 const int subPathCount = subPathStartPoints.size();
997
998 // If there is only one subpath, none have to be removed
999 if (subPathStartPoints.size() < 2)
1000 return false;
1001
1002 // We set up a matrix that tells us which path is nested in which other path.
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++) {
1008 if (i == j) {
1009 isInside.append(false);
1010 } else {
1011 isInside.append(path.contains(path.elementAt(subPathStartPoints.at(i)).startPoint(),
1012 subPathStartPoints.at(j), subPathEndPoints.at(j)));
1013 if (isInside.last())
1014 isAnyInside = true;
1015 }
1016 }
1017 }
1018
1019 // If no nested subpaths are present we can return early.
1020 if (!isAnyInside)
1021 return false;
1022
1023 // To find out which paths are filled and which not, we first calculate the
1024 // rotation direction (clockwise - counterclockwise).
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());
1033 }
1034 clockwise.append(sumProduct > 0);
1035 }
1036
1037 // Set up a list that tells us which paths create filling and which path create holes.
1038 // Holes in Holes and fillings in fillings can then be removed.
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;
1046 }
1047 isFilled.append(crossings != 0);
1048 }
1049
1050 // A helper function to find the most inner subpath that is around a subpath.
1051 // Returns -1 if the subpath is a toplevel subpath.
1052 auto findClosestOuterSubpath = [&](int subPath) {
1053 // All paths that contain the current subPath are candidates.
1054 QList<int> candidates;
1055 for (int i = 0; i < subPathStartPoints.size(); i++) {
1056 if (isInside.at(subPath * subPathCount + i))
1057 candidates.append(i);
1058 }
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))) {
1065 nestingLevel++;
1066 }
1067 }
1068 if (nestingLevel > maxNestingLevel) {
1069 maxNestingLevel = nestingLevel;
1070 maxNestingLevelIndex = candidates.at(i);
1071 }
1072 }
1073 return maxNestingLevelIndex;
1074 };
1075
1076 bool pathChanged = false;
1077 QQuadPath fixedPath;
1078 fixedPath.setPathHints(path.pathHints());
1079
1080 // Go through all subpaths and find the closest surrounding subpath.
1081 // If it is doing the same (create filling or create hole) we can remove it.
1082 for (int i = 0; i < subPathCount; i++) {
1083 int j = findClosestOuterSubpath(i);
1084 if (j >= 0 && isFilled.at(i) == isFilled.at(j)) {
1085 pathChanged = true;
1086 } else {
1087 for (int k = subPathStartPoints.at(i); k <= subPathEndPoints.at(i); k++)
1088 fixedPath.addElement(path.elementAt(k));
1089 }
1090 }
1091
1092 if (pathChanged)
1093 path = fixedPath;
1094 return pathChanged;
1095}
1096
1097// Returns true if the path was changed
1098bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool removeNestedPaths)
1099{
1100 if (path.testHint(QQuadPath::PathNonIntersecting)) {
1101 if (removeNestedPaths)
1102 return removeNestedSubpaths(path);
1103 else
1104 return false;
1105 }
1106
1107 if (path.elementCount() < 2) {
1108 path.setHint(QQuadPath::PathNonIntersecting);
1109 return false;
1110 }
1111
1112 struct IntersectionData { int e1; int e2; float t1; float t2; bool in1 = false, in2 = false, out1 = false, out2 = false; };
1113 QList<IntersectionData> intersections;
1114
1115 // Helper function to mark an intersection as handled when the
1116 // path i is processed moving forward/backward
1117 auto markIntersectionAsHandled = [=](IntersectionData *data, int i, bool forward) {
1118 if (data->e1 == i) {
1119 if (forward)
1120 data->out1 = true;
1121 else
1122 data->in1 = true;
1123 } else if (data->e2 == i){
1124 if (forward)
1125 data->out2 = true;
1126 else
1127 data->in2 = true;
1128 } else {
1129 Q_UNREACHABLE();
1130 }
1131 };
1132
1133 // First make a O(n log n) search for candidates.
1134 const QList<std::pair<int, int>> candidates = findOverlappingCandidates(path);
1135 // Then check the candidates for actual intersections.
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});
1143 }
1144 }
1145
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;
1153 }
1154 }
1155
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);
1161 } else {
1162 qCDebug(lcSGCurveIntersectionSolver) << "Returning the path unchanged.";
1163 return false;
1164 }
1165 }
1166
1167
1168 // Store the starting and end elements of the subpaths to be able
1169 // to jump quickly between them. Also keep a list of handled paths,
1170 // so we know if we need to come back to a subpath or if it
1171 // was already united with another subpath due to an intersection.
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);
1181 }
1182 }
1183
1184 // A small helper to find the subPath of an element with index
1185 auto subPathIndex = [&](int index) {
1186 for (int i = 0; i < subPathStartPoints.size(); i++) {
1187 if (index >= subPathStartPoints.at(i) && index <= subPathEndPoints.at(i))
1188 return i;
1189 }
1190 return -1;
1191 };
1192
1193 // Helper to ensure that index i and position t are valid:
1194 auto ensureInBounds = [&](int *i, float *t, float deltaT) {
1195 if (*t <= 0.f) {
1196 if (path.elementAt(*i).isSubpathStart())
1197 *i = subPathEndPoints.at(subPathIndex(*i));
1198 else
1199 *i = *i - 1;
1200 *t = 1.f - deltaT;
1201 } else if (*t >= 1.f) {
1202 if (path.elementAt(*i).isSubpathEnd())
1203 *i = subPathStartPoints.at(subPathIndex(*i));
1204 else
1205 *i = *i + 1;
1206 *t = deltaT;
1207 }
1208 };
1209
1210 // Helper function to find a siutable starting point between start and end.
1211 // A suitable starting point is where right is inside and left is outside
1212 // If left is inside and right is outside it works too, just move in the
1213 // other direction (forward = false).
1214 auto findStart = [=](QQuadPath &path, int start, int end, int *result, bool *forward) {
1215 for (int i = start; i < end; i++) {
1216 int adjecent;
1217 if (subPathStartPoints.contains(i))
1218 adjecent = subPathEndPoints.at(subPathStartPoints.indexOf(i));
1219 else
1220 adjecent = i - 1;
1221
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;
1226 if (rightInside) {
1227 *result = i;
1228 *forward = true;
1229 return true;
1230 } else if (leftInside) {
1231 *result = adjecent;
1232 *forward = false;
1233 return true;
1234 }
1235 }
1236 return false;
1237 };
1238
1239 // Also store handledElements (handled is when we touch the start point).
1240 // This is used to identify and abort on errors.
1241 QVarLengthArray<bool> handledElements(path.elementCount(), false);
1242 // Only store handledElements when it is not touched due to an intersection.
1243 bool regularVisit = true;
1244
1245 QQuadPath fixedPath;
1246 fixedPath.setFillRule(path.fillRule());
1247
1248 int i1 = 0;
1249 float t1 = 0;
1250
1251 int i2 = 0;
1252 float t2 = 0;
1253
1254 float t = 0;
1255 bool forward = true;
1256
1257 int startedAtIndex = -1;
1258 float startedAtT = -1;
1259
1260 if (!findStart(path, 0, path.elementCount(), &i1, &forward)) {
1261 qCDebug(lcSGCurveIntersectionSolver) << "No suitable start found. This should not happen. Returning the path unchanged.";
1262 return false;
1263 }
1264
1265 // Helper function to start a new subpath and update temporary variables.
1266 auto startNewSubPath = [&](int i, bool forward) {
1267 if (forward) {
1268 fixedPath.moveTo(path.elementAt(i).startPoint());
1269 t = startedAtT = 0;
1270 } else {
1271 fixedPath.moveTo(path.elementAt(i).endPoint());
1272 t = startedAtT = 1;
1273 }
1274 startedAtIndex = i;
1275 subPathHandled[subPathIndex(i)] = true;
1276 };
1277 startNewSubPath(i1, forward);
1278
1279 // If not all interactions where found correctly, we might end up in an infinite loop.
1280 // Therefore we count the total number of iterations and bail out at some point.
1281 int totalIterations = 0;
1282
1283 // We need to store the last intersection so we don't jump back and forward immediately.
1284 int prevIntersection = -1;
1285
1286 do {
1287 // Sanity check: Make sure that we do not process the same corner point more than once.
1288 if (regularVisit && (t == 0 || t == 1)) {
1289 int nextIndex = i1;
1290 if (t == 1 && path.elementAt(i1).isSubpathEnd()) {
1291 nextIndex = subPathStartPoints.at(subPathIndex(i1));
1292 } else if (t == 1) {
1293 nextIndex = nextIndex + 1;
1294 }
1295 if (handledElements[nextIndex]) {
1296 qCDebug(lcSGCurveIntersectionSolver) << "Revisiting an element when trying to solve intersections. This should not happen. Returning the path unchanged.";
1297 return false;
1298 }
1299 handledElements[nextIndex] = true;
1300 }
1301 // Sanity check: Keep an eye on total iterations
1302 totalIterations++;
1303
1304 qCDebug(lcSGCurveIntersectionSolver) << "Checking section" << i1 << "from" << t << "going" << (forward ? "forward" : "backward");
1305
1306 // Find the next intersection that is as close as possible to t but in direction of processing (forward or !forward).
1307 int iC = -1; //intersection candidate
1308 t1 = forward? 1 : -1; //intersection candidate t-value
1309 for (int j = 0; j < intersections.size(); j++) {
1310 if (j == prevIntersection)
1311 continue;
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)) {
1315 iC = j;
1316 t1 = intersections[j].t1;
1317 i2 = intersections[j].e2;
1318 t2 = intersections[j].t2;
1319 }
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)) {
1323 iC = j;
1324 t1 = intersections[j].t2;
1325 i2 = intersections[j].e1;
1326 t2 = intersections[j].t1;
1327 }
1328 }
1329 prevIntersection = iC;
1330
1331 if (iC < 0) {
1332 qCDebug(lcSGCurveIntersectionSolver) << " No intersection found on my way. Adding the rest of the segment " << i1;
1333 regularVisit = true;
1334 // If no intersection with the current element was found, just add the rest of the element
1335 // to the fixed path and go on.
1336 // If we reached the end (going forward) or start (going backward) of a subpath, we have
1337 // to wrap aroud. Abort condition for the loop comes separately later.
1338 if (forward) {
1339 if (path.elementAt(i1).isLine()) {
1340 fixedPath.lineTo(path.elementAt(i1).endPoint());
1341 } else {
1342 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(t, 1);
1343 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1344 }
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);
1349 } else {
1350 i1++;
1351 }
1352 t = 0;
1353 } else {
1354 if (path.elementAt(i1).isLine()) {
1355 fixedPath.lineTo(path.elementAt(i1).startPoint());
1356 } else {
1357 const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(0, t).reversed();
1358 fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
1359 }
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);
1364 } else {
1365 i1--;
1366 }
1367 t = 1;
1368 }
1369 } else { // Here comes the part where we actually handle intersections.
1370 qCDebug(lcSGCurveIntersectionSolver) << " Found an intersection at" << t1 << "with" << i2 << "at" << t2;
1371
1372 // Mark the subpath we intersected with as visisted. We do not have to come here explicitly again.
1373 subPathHandled[subPathIndex(i2)] = true;
1374
1375 // Mark the path that lead us to this intersection as handled on the intersection level.
1376 // Note the ! in front of forward. This is required because we move towards the intersection.
1377 markIntersectionAsHandled(&intersections[iC], i1, !forward);
1378
1379 // Split the path from the last point to the newly found intersection.
1380 // Add the part of the current segment to the fixedPath.
1381 const QQuadPath::Element &elem1 = path.elementAt(i1);
1382 if (elem1.isLine()) {
1383 fixedPath.lineTo(elem1.pointAtFraction(t1));
1384 } else {
1385 QQuadPath::Element partUntilIntersection;
1386 if (forward) {
1387 partUntilIntersection = elem1.segmentFromTo(t, t1);
1388 } else {
1389 partUntilIntersection = elem1.segmentFromTo(t1, t).reversed();
1390 }
1391 fixedPath.quadTo(partUntilIntersection.controlPoint(), partUntilIntersection.endPoint());
1392 }
1393
1394 // If only one unhandled path is left the decision how to proceed is trivial
1395 if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && !intersections[iC].out2) {
1396 i1 = intersections[iC].e2;
1397 t = intersections[iC].t2;
1398 forward = true;
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;
1402 forward = true;
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;
1406 forward = false;
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;
1410 forward = false;
1411 } else {
1412 // If no trivial path is left, calculate the intersection angle to decide which path to move forward.
1413 // For winding fill we take the left most path forward, so the inside stays on the right side
1414 // For odd even fill we take the right most path forward so we cut of the smallest area.
1415 // We come back at the intersection and add the missing pieces as subpaths later on.
1416 if (t1 !=0 && t1 != 1 && t2 != 0 && t2 != 1) {
1417 QVector2D tangent1 = elem1.tangentAtFraction(t1);
1418 if (!forward)
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;
1424 // A small angle. Everything smaller is interpreted as tangent
1425 constexpr float deltaAngle = 1e-3f;
1426 if ((angle > deltaAngle && path.fillRule() == Qt::WindingFill) || (angle < -deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
1427 forward = true;
1428 i1 = i2;
1429 t = t2;
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)) {
1432 forward = false;
1433 i1 = i2;
1434 t = t2;
1435 qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
1436 } else { // this is basically a tangential touch and and no crossing. So stay on the current path, keep direction
1437 qCDebug(lcSGCurveIntersectionSolver) << " Found tangent. Staying on element";
1438 }
1439 } else {
1440 // If we are intersecting exactly at a corner, the trick with the angle does not help.
1441 // Therefore we have to rely on finding the next path by looking forward and see if the
1442 // path there is valid. This is more expensive than the method above and is therefore
1443 // just used as a fallback for corner cases.
1444 constexpr float deltaT = 1e-4f;
1445 int i2after = i2;
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) {
1450 forward = true;
1451 i1 = i2;
1452 t = t2;
1453 qCDebug(lcSGCurveIntersectionSolver) << " Next going forward from" << t << "on" << i1;
1454 } else {
1455 int i2before = i2;
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) {
1460 forward = false;
1461 i1 = i2;
1462 t = t2;
1463 qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
1464 } else {
1465 qCDebug(lcSGCurveIntersectionSolver) << " Staying on element.";
1466 }
1467 }
1468 }
1469 }
1470
1471 // Mark the path that takes us away from this intersection as handled on the intersection level.
1472 if (!(i1 == startedAtIndex && t == startedAtT))
1473 markIntersectionAsHandled(&intersections[iC], i1, forward);
1474
1475 // If we took all paths from an intersection it can be deleted.
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;
1480 }
1481 regularVisit = false;
1482 }
1483
1484 if (i1 == startedAtIndex && t == startedAtT) {
1485 // We reached the point on the subpath where we started. This subpath is done.
1486 // We have to find an unhandled subpath or a new subpath that was generated by cuts/intersections.
1487 qCDebug(lcSGCurveIntersectionSolver) << "Reached my starting point and try to find a new subpath.";
1488
1489 // Search for the next subpath to handle.
1490 int nextUnhandled = -1;
1491 for (int i = 0; i < subPathHandled.size(); i++) {
1492 if (!subPathHandled.at(i)) {
1493
1494 // Not necessarily handled (if findStart() returns false); but if we find no starting
1495 // point, we cannot/don't need to handle it anyway. So just mark it as handled.
1496 subPathHandled[i] = true;
1497
1498 if (findStart(path, subPathStartPoints.at(i), subPathEndPoints.at(i), &i1, &forward)) {
1499 nextUnhandled = i;
1500 qCDebug(lcSGCurveIntersectionSolver) << "Found a new subpath" << i << "to be processed.";
1501 startNewSubPath(i1, forward);
1502 regularVisit = true;
1503 break;
1504 }
1505 }
1506 }
1507
1508 // If no valid subpath is left, we have to go back to the unhandled intersections.
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);
1514 path = fixedPath;
1515 return true;
1516 }
1517
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;
1524
1525 // Searching for the correct direction to go forward.
1526 // That requires that the intersection + small delta (here 1e-4)
1527 // is a valid starting point (filling only on one side)
1528 auto lookForwardOnIntersection = [&](bool *handledPath, int nextE, float nextT, bool nextForward) {
1529 if (*handledPath)
1530 return false;
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;
1543 return true;
1544 }
1545 return false;
1546 };
1547
1548 if (lookForwardOnIntersection(&unhandledIntersec.in1, unhandledIntersec.e1, unhandledIntersec.t1, false))
1549 break;
1550 if (lookForwardOnIntersection(&unhandledIntersec.in2, unhandledIntersec.e2, unhandledIntersec.t2, false))
1551 break;
1552 if (lookForwardOnIntersection(&unhandledIntersec.out1, unhandledIntersec.e1, unhandledIntersec.t1, true))
1553 break;
1554 if (lookForwardOnIntersection(&unhandledIntersec.out2, unhandledIntersec.e2, unhandledIntersec.t2, true))
1555 break;
1556
1557 intersections.removeFirst();
1558 qCDebug(lcSGCurveIntersectionSolver) << "Found no way to move forward at this intersection and removed it.";
1559 }
1560 }
1561
1562 } while (totalIterations < path.elementCount() * 50);
1563 // Check the totalIterations as a sanity check. Should never be triggered.
1564
1565 qCDebug(lcSGCurveIntersectionSolver) << "Could not solve intersections of path. This should not happen. Returning the path unchanged.";
1566
1567 return false;
1568}
1569
1570
1571void QSGCurveProcessor::processStroke(const QQuadPath &strokePath,
1572 float miterLimit,
1573 float penWidth, bool cosmetic,
1574 Qt::PenJoinStyle joinStyle,
1575 Qt::PenCapStyle capStyle,
1576 addStrokeTriangleCallback addTriangle,
1577 int subdivisions)
1578{
1579 const bool expandingInVertexShader = cosmetic || QSGCurveStrokeNode::expandingStrokeEnabled();
1580 auto thePath = subdivide(strokePath, subdivisions).flattened(); // TODO: don't flatten, but handle it in the triangulator
1581
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);
1589 };
1590
1591 if (!expandingInVertexShader) {
1592 // this block is outdented to preserve git line history
1593 // TODO remove customTriangulator2 in a future version
1594 auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
1595 qCDebug(lcSGCurveProcessor) << thePath << "->" << triangles;
1596
1597 auto addBevelTriangle = [&](const TrianglePoints &p, QSGCurveStrokeNode::TriangleFlags flags)
1598 {
1599 QVector2D fp1 = p[0];
1600 QVector2D fp2 = p[2];
1601
1602 // That describes a path that passes through those points. We want the stroke
1603 // edge, so we need to shift everything down by the stroke offset
1604
1605 QVector2D nn = calcNormalVector(p[0], p[2]);
1606 if (determinant(p) < 0)
1607 nn = -nn;
1608 float delta = penWidth / 2;
1609
1610 QVector2D offset = nn.normalized() * delta;
1611 fp1 += offset;
1612 fp2 += offset;
1613
1614 TrianglePoints n;
1615 // p1 is inside, so n[1] is {0,0}
1616 n[0] = (p[0] - p[1]).normalized();
1617 n[2] = (p[2] - p[1]).normalized();
1618
1619 flags.setFlag(QSGCurveStrokeNode::TriangleFlag::Line);
1620 addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, {}, flags);
1621 };
1622
1623 for (const auto &triangle : std::as_const(triangles)) {
1624 if (triangle.pathElementIndex < 0) {
1625 addBevelTriangle(triangle.points, {});
1626 continue;
1627 }
1628 const auto &element = thePath.elementAt(triangle.pathElementIndex);
1629 addCurveTriangle(element, triangle);
1630 }
1631
1632 return;
1633 } // legacy customTriangulator2 if we aren't using the expanding vertex shader
1634
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);
1638 };
1639
1640 const bool bevelJoin = joinStyle == Qt::BevelJoin;
1641 const bool roundJoin = joinStyle == Qt::RoundJoin;
1642 const bool miterJoin = !bevelJoin && !roundJoin;
1643
1644 // Whether to allow a miter to simply be two adjacent triangle-pair stroke
1645 // quads coming together at an angle: so far, it only works for round joins
1646 // (and even then, only for suitably obtuse angles), because we need
1647 // synthetic line-equation coefficients to get straight-edged joins.
1648 // When we allow the user-provided endpoints to be visible inside the
1649 // triangles, the fragment shader makes round endcaps.
1650 const bool simpleMiter = joinStyle == Qt::RoundJoin;
1651
1652 Q_ASSERT(miterLimit > 0 || !miterJoin);
1653 const float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
1654 const float penFactor = penWidth / 2;
1655
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)
1658 {
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();
1664 // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
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);
1674 if (simpleCase) {
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});
1682 } else {
1683 addCurveTriangle(element, {{p2, p4, c}, {n2, n4, controlPointNormal},
1684 QSGCurveStrokeNode::defaultExtrusions(), idx});
1685 }
1686 } else {
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);
1690 }
1691 };
1692
1693 // Each element is calculated independently, so we don't have to special-case closed sub-paths.
1694 // Take care so the end points of one element are precisely equal to the start points of the next.
1695 // Any additional triangles needed for joining are added at the end of the current element.
1696
1697 const int count = thePath.elementCount();
1698 int subStart = 0;
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()) {
1704 subEnd = i - 1;
1705 break;
1706 }
1707 if (i == count - 1) {
1708 subEnd = i;
1709 break;
1710 }
1711 }
1712 bool closed = thePath.elementAt(subStart).startPoint() == thePath.elementAt(subEnd).endPoint();
1713 const int subCount = subEnd - subStart + 1;
1714
1715 auto addIdx = [&](int idx, int delta) -> int {
1716 int subIdx = idx - subStart;
1717 if (closed)
1718 subIdx = (subIdx + subCount + delta) % subCount;
1719 else
1720 subIdx += delta;
1721 return subStart + subIdx;
1722 };
1723 auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
1724 int subIdx = idx - subStart;
1725 if (closed) {
1726 subIdx = (subIdx + subCount + delta) % subCount;
1727 return &thePath.elementAt(subStart + subIdx);
1728 }
1729 subIdx += delta;
1730 if (subIdx >= 0 && subIdx < subCount)
1731 return &thePath.elementAt(subStart + subIdx);
1732 return nullptr;
1733 };
1734
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);
1739
1740 const auto &s = element.startPoint();
1741 const auto &e = element.endPoint();
1742
1743 bool startInnerIsRight;
1744 bool startBisectorWithinMiterLimit; // Not used
1745 bool giveUpOnStartJoin; // Not used
1746 auto startJoin = calculateJoin(prevElement, &element,
1747 penFactor, inverseMiterLimit, simpleMiter,
1748 startBisectorWithinMiterLimit, startInnerIsRight,
1749 giveUpOnStartJoin);
1750 const QVector2D &startInner = startJoin[1];
1751 const QVector2D &startOuter = startJoin[3];
1752
1753 bool endInnerIsRight;
1754 bool endBisectorWithinMiterLimit;
1755 bool giveUpOnEndJoin;
1756 auto endJoin = calculateJoin(&element, nextElement,
1757 penFactor, inverseMiterLimit, simpleMiter,
1758 endBisectorWithinMiterLimit, endInnerIsRight,
1759 giveUpOnEndJoin);
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();
1766
1767 QVector2D n1, n2, n3, n4;
1768
1769 if (startInnerIsRight) {
1770 n1 = startInner;
1771 n2 = startOuter;
1772 } else {
1773 n1 = startOuter;
1774 n2 = startInner;
1775 }
1776
1777 // repeat logic above for the other end:
1778 if (endInnerIsRight) {
1779 n3 = endInner;
1780 n4 = endOuter;
1781 } else {
1782 n3 = endOuter;
1783 n4 = endInner;
1784 }
1785
1786 // When we fill triangles that make up square caps with fake lines,
1787 // we need the line equations to extend way beyond the triangles,
1788 // so that corner roundoff won't occur at any reasonable zoom level.
1789 // But if the virtual lines are too long, AA quality suffers.
1790 // Multiplying by 50 seems to get it to behave reasonably at zoom levels from 0.01 to 50.
1791 static const float artificialLineExtension = 50;
1792
1793 // End cap triangles
1794 if (capStyle != Qt::FlatCap) {
1795 const QVector2D capNormalNone(0, 0);
1796 // a cap is rendered in 3 triangles: in all cases below, the order is outer, middle, outer
1797 if (!prevElement) {
1798 // If the cap happens to be left-facing, "up" means in the -y direction,
1799 // "down" is in the +y direction and so on; otherwise they are all rotated together
1800 const QVector2D capNormalUp(startTangent.y(), -startTangent.x());
1801 const QVector2D capNormalDown = -capNormalUp;
1802 // hypoteneuses of triangles made from normalized vectors are longer: not re-normalized
1803 const QVector2D capNormalUpOut = (capNormalUp - startTangent);
1804 const QVector2D capNormalDownOut = (capNormalDown - startTangent); // not in the magic kingdom
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});
1813 } else { // SquareCap
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});
1822 }
1823 }
1824 if (!nextElement) {
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});
1837 } else { // SquareCap
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});
1845 }
1846 }
1847 }
1848
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});
1852 } else {
1853 triangulateCurve(i, s, s, e, e, n1, n2, n3, n4);
1854 }
1855
1856 bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
1857 if (!trivialJoin && nextElement) {
1858 // inside of join (opposite of bevel) is defined by triangle s, e, next.e
1859 bool innerOnRight = endInnerIsRight;
1860
1861 const auto outer1 = e + endOuter;
1862 const auto outer2 = e + nextOuter;
1863 QVector2D nn = calcNormalVector(outer1, outer2).normalized();
1864 if (!innerOnRight)
1865 nn = -nn;
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();
1870
1871 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
1872 const float cosTheta = QVector2D::dotProduct(endOuterN, nextOuterN); // divided by magnitudes, which are 1s
1873 const float cosHalf = cos(acos(cosTheta) / 2);
1874 // draw a line from the end of element to the beginning of the next:
1875 // slope is the average of the two tangents
1876 const auto tan1 = endTangent * penWidth * artificialLineExtension;
1877 const auto tan2 = tangentVector(*nextElement, false).normalized() * penWidth * artificialLineExtension;
1878 const auto bevelTan = (tan2 - tan1) / 2;
1879 // element.endPoint() is not the centerline of the bevel's stroke if full width, so
1880 // we use extrusion (normalExt.z) in the vertex shader to adjust the stroke width so that its edge
1881 // falls in the right place to draw a clean bevel within this triangle: i.e.
1882 // compensate for the triangle becoming smaller as the join angle becomes more acute.
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});
1895 }
1896
1897 if (!giveUpOnEndJoin) {
1898 addCurveTriangle(element, {{e, e, e}, {endInner, {}, endOuter}, QSGCurveStrokeNode::defaultExtrusions(), i});
1899 // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
1900 int nextIdx = addIdx(i, +1);
1901 addCurveTriangle(*nextElement, {{e, e, e}, {endInner, {}, nextOuter}, QSGCurveStrokeNode::defaultExtrusions(), nextIdx});
1902 }
1903 }
1904 }
1905 subStart = subEnd + 1;
1906 }
1907}
1908
1909// 2x variant of qHash(float)
1910inline size_t qHash(QVector2D key, size_t seed = 0) noexcept
1911{
1912 Q_STATIC_ASSERT(sizeof(QVector2D) == sizeof(quint64));
1913 // ensure -0 gets mapped to 0
1914 key[0] += 0.0f;
1915 key[1] += 0.0f;
1916 quint64 k;
1917 memcpy(&k, &key, sizeof(QVector2D));
1918 return QHashPrivate::hash(k, seed);
1919}
1920
1921void QSGCurveProcessor::processFill(const QQuadPath &fillPath,
1922 Qt::FillRule fillRule,
1923 addTriangleCallback addTriangle)
1924{
1925 QPainterPath internalHull;
1926 internalHull.setFillRule(fillRule);
1927
1928 QMultiHash<QVector2D, int> pointHash;
1929
1930 auto roundVec2D = [](const QVector2D &p) -> QVector2D {
1931 return { qRound64(p.x() * 32.0f) / 32.0f, qRound64(p.y() * 32.0f) / 32.0f };
1932 };
1933
1934 auto addCurveTriangle = [&](const QQuadPath::Element &element,
1935 const QVector2D &sp,
1936 const QVector2D &ep,
1937 const QVector2D &cp) {
1938 addTriangle({ sp, cp, ep },
1939 {},
1940 [&element](QVector2D v) { return elementUvForPoint(element, v); });
1941 };
1942
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); });
1947 };
1948
1949 auto outsideNormal = [](const QVector2D &startPoint,
1950 const QVector2D &endPoint,
1951 const QVector2D &insidePoint) {
1952
1953 QVector2D baseLine = endPoint - startPoint;
1954 QVector2D insideVector = insidePoint - startPoint;
1955 QVector2D normal = normalVector(baseLine);
1956
1957 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
1958
1959 return swap ? normal : -normal;
1960 };
1961
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);
1967
1968 // Add triangles on the outer side to make room for AA
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});
1973 };
1974
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);
1980 };
1981
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);
1987 // Add two triangles on the outer side to get some more AA
1988
1989 constexpr QVector2D null;
1990 // First triangle on the line sp-cp, replacing ep
1991 {
1992 const QVector2D normal = outsideNormal(sp, cp, ep);
1993 addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
1994 }
1995
1996 // Second triangle on the line ep-cp, replacing sp
1997 {
1998 const QVector2D normal = outsideNormal(ep, cp, sp);
1999 addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
2000 }
2001 };
2002
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 },
2006 {},
2007 [&uv](QVector2D) { return uv; });
2008 };
2009
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);
2015
2016 if (element.isSubpathStart())
2017 internalHull.moveTo(sp.toPointF());
2018 if (element.isLine()) {
2019 internalHull.lineTo(ep.toPointF());
2020 pointHash.insert(rsp, index);
2021 } else {
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);
2028 } else {
2029 internalHull.lineTo(cp.toPointF());
2030 internalHull.lineTo(ep.toPointF());
2031 addTriangleForConcave(element, rsp, rep, rcp);
2032 pointHash.insert(rcp, index);
2033 }
2034 }
2035 });
2036
2037 // Points in p are already rounded do 1/32
2038 // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
2039 // (Does not handle ambiguous vertices that are on multiple unrelated lines/curves)
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;
2047 };
2048
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();
2053 // There are "safe" areas of the curve also across the baseline: the curve can never cross:
2054 // line1: the line through A and B'
2055 // line2: the line through B and A'
2056 // Where A' = A "mirrored" through C and B' = B "mirrored" through C
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;
2062 };
2063
2064 // Returns false if the triangle belongs to multiple elements and need to be split.
2065 // Otherwise adds the triangle, optionally splitting it to avoid "unsafe space"
2066 auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
2067 bool isLine = false;
2068 bool isConcave = false;
2069 bool isConvex = false;
2070 int elementIndex = -1;
2071
2072 bool foundElement = false;
2073 int si = -1;
2074 int ei = -1;
2075
2076 for (int i = 0; i < 3; ++i) {
2077 auto pointFoundRange = std::as_const(pointHash).equal_range(roundVec2D(p[i]));
2078
2079 if (pointFoundRange.first == pointHash.constEnd())
2080 continue;
2081
2082 // This point is on some element, now find the element
2083 int testIndex = *pointFoundRange.first;
2084 bool ambiguous = std::next(pointFoundRange.first) != pointFoundRange.second;
2085 if (ambiguous) {
2086 // The triangle should be on the inside of exactly one of the elements
2087 // We're doing the test for each of the points, which maybe duplicates some effort,
2088 // but optimize for simplicity for now.
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());
2094 // Check if the triangle is on the inside of el; i.e. each point is either sp, ep, or on the inside.
2095 auto pointInside = [&](const QVector2D &p) {
2096 return p == sp || p == ep
2097 || QQuadPath::isPointOnLeft(p, el.startPoint(), el.endPoint()) == fillOnLeft;
2098 };
2099 if (pointInside(p[0]) && pointInside(p[1]) && pointInside(p[2])) {
2100 testIndex = *it;
2101 break;
2102 }
2103 }
2104 }
2105
2106 const auto &element = fillPath.elementAt(testIndex);
2107 // Now we check if p[i] -> p[j] is on the element for some j
2108 // For a line, the relevant line is sp-ep
2109 // For concave it's cp-sp/ep
2110 // For convex it's sp-ep again
2111 bool onElement = false;
2112 for (int j = 0; j < 3; ++j) {
2113 if (i == j)
2114 continue;
2115 if (element.isConvex() || element.isLine())
2116 onElement = roundVec2D(element.endPoint()) == p[j];
2117 else // concave
2118 onElement = roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j];
2119 if (onElement) {
2120 if (foundElement)
2121 return false; // Triangle already on some other element: must split
2122 si = i;
2123 ei = j;
2124 foundElement = true;
2125 elementIndex = testIndex;
2126 isConvex = element.isConvex();
2127 isLine = element.isLine();
2128 isConcave = !isLine && !isConvex;
2129 break;
2130 }
2131 }
2132 }
2133
2134 if (isLine) {
2135 int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
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);
2143 // We have to test whether the triangle can cross the line
2144 // TODO: use the toplevel element's safe space
2145 bool safeSpace = pointInSafeSpace(otherPoint, element);
2146 if (safeSpace) {
2147 addCurveTriangle(element, p[0], p[1], p[2]);
2148 } else {
2149 // Find a point inside the triangle that's also in the safe space
2150 QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
2151 // We should calculate the point directly, but just do a lazy implementation for now:
2152 for (int i = 0; i < 7; ++i) {
2153 safeSpace = pointInSafeSpace(newPoint, element);
2154 if (safeSpace)
2155 break;
2156 newPoint = (p[si] + p[ei] + newPoint) / 3;
2157 }
2158 if (safeSpace) {
2159 // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
2160 // Curve triangle is (sp, ep, np)
2161 addCurveTriangle(element, p[si], p[ei], newPoint);
2162 // The other two are (sp, op, np) and (ep, op, np)
2163 addFillTriangle(p[si], p[oi], newPoint);
2164 addFillTriangle(p[ei], p[oi], newPoint);
2165 } else {
2166 // fallback to fill if we can't find a point in safe space
2167 addFillTriangle(p[0], p[1], p[2]);
2168 }
2169 }
2170
2171 } else {
2172 addFillTriangle(p[0], p[1], p[2]);
2173 }
2174 return true;
2175 };
2176
2177 QTriangleSet triangles = qTriangulate(internalHull);
2178 // Workaround issue in qTriangulate() for single-triangle path
2179 if (triangles.indices.size() == 3)
2180 triangles.indices.setDataUint({ 0, 1, 2 });
2181
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];
2185
2186 QVector2D p[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))));
2190 }
2191 if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
2192 continue; // Skip degenerate triangles
2193 bool needsSplit = !handleTriangle(p);
2194 if (needsSplit) {
2195 QVector2D c = (p[0] + p[1] + p[2]) / 3;
2196 for (int i = 0; i < 3; ++i) {
2197 qSwap(c, p[i]);
2198 handleTriangle(p);
2199 qSwap(c, p[i]);
2200 }
2201 }
2202 }
2203}
2204
2205
2206QT_END_NAMESPACE
Combined button and popup list for selecting options.
size_t qHash(QByteArrayView key, size_t seed) noexcept
Definition qhash.cpp:876
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)