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