Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquickshapecurverenderer.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
8
9#include <QtGui/qvector2d.h>
10#include <QtGui/qvector4d.h>
11#include <QtGui/private/qtriangulator_p.h>
12#include <QtGui/private/qtriangulatingstroker_p.h>
13#include <QtGui/private/qrhi_p.h>
14
15#include <QtQuick/qsgmaterial.h>
16
17#include <QThread>
18
20
21Q_LOGGING_CATEGORY(lcShapeCurveRenderer, "qt.shape.curverenderer");
22
23#if !defined(QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN)
24# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f / 32.0f)
25#endif
26
27namespace {
28
29
30
31class QQuickShapeWireFrameMaterialShader : public QSGMaterialShader
32{
33public:
34 QQuickShapeWireFrameMaterialShader()
35 {
36 setShaderFileName(VertexStage,
37 QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"));
38 setShaderFileName(FragmentStage,
39 QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"));
40 }
41
42 bool updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *) override
43 {
44 bool changed = false;
45 QByteArray *buf = state.uniformData();
46 Q_ASSERT(buf->size() >= 64);
47
48 if (state.isMatrixDirty()) {
49 const QMatrix4x4 m = state.combinedMatrix();
50
51 memcpy(buf->data(), m.constData(), 64);
52 changed = true;
53 }
54
55 return changed;
56 }
57};
58
59class QQuickShapeWireFrameMaterial : public QSGMaterial
60{
61public:
62 QQuickShapeWireFrameMaterial()
63 {
64 setFlag(Blending, true);
65 }
66
67 int compare(const QSGMaterial *other) const override
68 {
69 return (type() - other->type());
70 }
71
72protected:
73 QSGMaterialType *type() const override
74 {
75 static QSGMaterialType t;
76 return &t;
77 }
79 {
80 return new QQuickShapeWireFrameMaterialShader;
81 }
82
83};
84
85class QQuickShapeWireFrameNode : public QSGGeometryNode
86{
87public:
88 struct WireFrameVertex
89 {
90 float x, y, u, v, w;
91 };
92
93 QQuickShapeWireFrameNode()
94 {
95 setFlag(OwnsGeometry, true);
96 setGeometry(new QSGGeometry(attributes(), 0, 0));
97 activateMaterial();
98 }
99
100 void activateMaterial()
101 {
102 m_material.reset(new QQuickShapeWireFrameMaterial);
103 setMaterial(m_material.data());
104 }
105
106 static const QSGGeometry::AttributeSet &attributes()
107 {
108 static QSGGeometry::Attribute data[] = {
111 };
112 static QSGGeometry::AttributeSet attrs = { 2, sizeof(WireFrameVertex), data };
113 return attrs;
114 }
115
116protected:
118};
119}
120
122{
123 if (isLine()) {
124 return sp + t * (ep - sp);
125 } else {
126 const float r = 1 - t;
127 return (r * r * sp) + (2 * t * r * cp) + (t * t * ep);
128 }
129}
130
132{
133 // TBD: cache this value if we start using it a lot
134 QVector2D min(qMin(sp.x(), ep.x()), qMin(sp.y(), ep.y()));
135 QVector2D max(qMax(sp.x(), ep.x()), qMax(sp.y(), ep.y()));
136 if (!isLine()) {
137 min = QVector2D(qMin(min.x(), cp.x()), qMin(min.y(), cp.y()));
138 max = QVector2D(qMax(max.x(), cp.x()), qMax(max.y(), cp.y()));
139 }
140 return (max - min).length();
141}
142
143// Returns the number of intersections between element and a horizontal line at y.
144// The t values of max 2 intersection(s) are stored in the fractions array
145int QuadPath::Element::intersectionsAtY(float y, float *fractions) const
146{
147 const float y0 = startPoint().y() - y;
148 const float y1 = controlPoint().y() - y;
149 const float y2 = endPoint().y() - y;
150
151 int numRoots = 0;
152 const float a = y0 - (2 * y1) + y2;
153 if (a) {
154 const float b = (y1 * y1) - (y0 * y2);
155 if (b >= 0) {
156 const float sqr = qSqrt(b);
157 const float root1 = -(-y0 + y1 + sqr) / a;
158 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
159 fractions[numRoots++] = root1;
160 const float root2 = (y0 - y1 + sqr) / a;
161 if (qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
162 fractions[numRoots++] = root2;
163 }
164 } else if (y1 != y2) {
165 const float root1 = (y2 - (2 * y1)) / (2 * (y2 - y1));
166 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
167 fractions[numRoots++] = root1;
168 }
169
170 return numRoots;
172
173static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
174{
175 QVector2D v1 = ep - p;
176 QVector2D v2 = p - sp;
177 return (v2.x() * v1.y()) - (v2.y() * v1.x());
178}
179
180bool QuadPath::isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
181{
182 // Use cross product to compare directions of base vector and vector from start to p
183 return crossProduct(sp, p, ep) >= 0.0f;
184}
185
186bool QuadPath::isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
187{
188 return qFuzzyIsNull(crossProduct(p, sp, ep));
189}
190
191// Assumes sp != ep
192bool QuadPath::isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
193{
194 // epsilon is max length of p-to-baseline relative to length of baseline. So 0.01 means that
195 // the distance from p to the baseline must be less than 1% of the length of the baseline.
196 constexpr float epsilon = 0.01f;
197 QVector2D bv = ep - sp;
198 float bl2 = QVector2D::dotProduct(bv, bv);
199 float t = QVector2D::dotProduct(p - sp, bv) / bl2;
200 QVector2D pv = p - (sp + t * bv);
201 return (QVector2D::dotProduct(pv, pv) / bl2) < (epsilon * epsilon);
202}
203
204bool QuadPath::isControlPointOnLeft(const QuadPath::Element &element)
205{
206 return isPointOnLeft(element.cp, element.sp, element.ep);
207}
208
209// NOTE: it is assumed that subpaths are closed
210bool QuadPath::contains(const QVector2D &pt) const
211{
212 // if (!controlPointRect().contains(pt) : good opt when we add cpr caching
213 // return false;
214
215 int winding_number = 0;
216 for (const Element &e : m_elements) {
217 int dir = 1;
218 float y1 = e.startPoint().y();
219 float y2 = e.endPoint().y();
220 if (y2 < y1) {
221 qSwap(y1, y2);
222 dir = -1;
223 }
224 if (e.m_isLine) {
225 if (pt.y() < y1 || pt.y() >= y2 || y1 == y2)
226 continue;
227 const float t = (pt.y() - e.startPoint().y()) / (e.endPoint().y() - e.startPoint().y());
228 const float x = e.startPoint().x() + t * (e.endPoint().x() - e.startPoint().x());
229 if (x <= pt.x())
230 winding_number += dir;
231 } else {
232 y1 = qMin(y1, e.controlPoint().y());
233 y2 = qMax(y2, e.controlPoint().y());
234 if (pt.y() < y1 || pt.y() >= y2)
235 continue;
236 float ts[2];
237 const int numRoots = e.intersectionsAtY(pt.y(), ts);
238 // Count if there is exactly one intersection to the left
239 bool oneHit = false;
240 float tForHit = -1;
241 for (int i = 0; i < numRoots; i++) {
242 if (e.pointAtFraction(ts[i]).x() <= pt.x()) {
243 oneHit = !oneHit;
244 tForHit = ts[i];
245 }
246 }
247 if (oneHit) {
248 dir = e.tangentAtFraction(tForHit).y() < 0 ? -1 : 1;
249 winding_number += dir;
250 }
251 }
252 };
253
254 return (fillRule() == Qt::WindingFill ? (winding_number != 0) : ((winding_number % 2) != 0));
255}
256
257void QuadPath::addElement(const QVector2D &control, const QVector2D &endPoint, bool isLine)
258{
259 if (qFuzzyCompare(currentPoint, endPoint))
260 return; // 0 length element, skip
261
262 isLine = isLine || isPointNearLine(control, currentPoint, endPoint); // Turn flat quad into line
263
264 m_elements.resize(m_elements.size() + 1);
265 Element &elem = m_elements.last();
266 elem.sp = currentPoint;
267 elem.cp = isLine ? (0.5f * (currentPoint + endPoint)) : control;
268 elem.ep = endPoint;
269 elem.m_isLine = isLine;
270 elem.m_isSubpathStart = subPathToStart;
271 subPathToStart = false;
272 currentPoint = endPoint;
273}
274
275QuadPath::Element::CurvatureFlags QuadPath::coordinateOrderOfElement(const QuadPath::Element &element) const
276{
277 QVector2D baseLine = element.endPoint() - element.startPoint();
278 QVector2D midPoint = element.midPoint();
279 // At the midpoint, the tangent of a quad is parallel to the baseline
280 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
281 float delta = qMin(element.extent() / 100, QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN);
282 QVector2D justRightOfMid = midPoint + (normal * delta);
283 bool pathContainsPoint = contains(justRightOfMid);
284 return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
285}
286
287QVector2D QuadPath::closestPointOnLine(const QVector2D &start,
288 const QVector2D &end,
289 const QVector2D &p)
290{
293 return start + qBound(0.0f, t, 1.0f) * line;
294}
295
297{
299 res.reserve(path.elementCount());
300 res.setFillRule(path.fillRule());
301
302 QPolygonF quads;
303 QPointF sp;
304 for (int i = 0; i < path.elementCount(); ++i) {
305 QPainterPath::Element element = path.elementAt(i);
306
307 QPointF ep(element);
308 switch (element.type) {
310 res.moveTo(QVector2D(ep));
311 break;
313 res.lineTo(QVector2D(ep));
314 break;
316 QPointF cp1 = ep;
317 QPointF cp2(path.elementAt(++i));
318 ep = path.elementAt(++i);
319 QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
320#ifndef USE_TOQUADRATICS_IN_QBEZIER
321 qt_toQuadratics(b, &quads);
322#else
323 quads = b.toQuadratics();
324#endif
325 for (int i = 1; i < quads.size(); i += 2) {
326 QVector2D cp(quads[i]);
327 QVector2D ep(quads[i + 1]);
328 res.quadTo(cp, ep);
329 }
330 break;
331 }
332 default:
333 Q_UNREACHABLE();
334 break;
335 }
336 sp = ep;
337 }
338
339 return res;
340}
341
343{
344 // We use the convention that the inside of a curve is on the *right* side of the
345 // direction of the baseline.Thus, as long as this is true: if the control point is
346 // on the left side of the baseline, the curve is convex and otherwise it is
347 // concave. The paths we get can be arbitrary order, but each subpath will have a
348 // consistent order. Therefore, for the first curve element in a subpath, we can
349 // determine if the direction already follows the convention or not, and then we
350 // can easily detect curvature of all subsequent elements in the subpath.
351
352 static bool checkAnomaly = qEnvironmentVariableIntValue("QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
353
354 Element::CurvatureFlags flags = Element::CurvatureUndetermined;
355 for (QuadPath::Element &element : m_elements) {
356 Q_ASSERT(element.childCount() == 0);
357 if (element.isSubpathStart()) {
358 flags = coordinateOrderOfElement(element);
359 } else if (checkAnomaly) {
360 Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
361 if (flags != newFlags) {
362 qDebug() << "Curvature anomaly detected:" << element
363 << "Subpath fill on right:" << (flags & Element::FillOnRight)
364 << "Element fill on right:" << (newFlags & Element::FillOnRight);
365 flags = newFlags;
366 }
367 }
368
369 if (element.isLine()) {
370 element.m_curvatureFlags = flags;
371
372 // Set the control point to an arbitrary point on the inside side of the line
373 // (doesn't need to actually be inside the shape: it just makes our calculations
374 // easier later if it is at the same side as the fill).
375 const QVector2D &sp = element.sp;
376 const QVector2D &ep = element.ep;
377 QVector2D v = ep - sp;
378 element.cp = flags & Element::FillOnRight ? sp + QVector2D(-v.y(), v.x()) : sp + QVector2D(v.y(), -v.x());
379 } else {
380 bool controlPointOnLeft = isControlPointOnLeft(element);
381 bool isFillOnRight = flags & Element::FillOnRight;
382 bool isConvex = controlPointOnLeft == isFillOnRight;
383
384 if (isConvex)
385 element.m_curvatureFlags = Element::CurvatureFlags(flags | Element::Convex);
386 else
387 element.m_curvatureFlags = flags;
388 }
389 }
390}
391
393{
394 QRectF res;
395 if (elementCount()) {
396 QVector2D min, max;
397 min = max = m_elements.constFirst().sp;
398 // No need to recurse, as split curve's controlpoints are within the parent curve's
399 for (const QuadPath::Element &e : std::as_const(m_elements)) {
400 min.setX(std::min({ min.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
401 min.setY(std::min({ min.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
402 max.setX(std::max({ max.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
403 max.setY(std::max({ max.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
404 }
405 res = QRectF(min.toPointF(), max.toPointF());
406 }
407 return res;
408}
409
410// Count leaf elements
412{
413 qsizetype count = 0;
414 iterateElements([&](const QuadPath::Element &) { count++; });
415 return count;
416}
417
419{
420 // Currently only converts the main, unsplit path; no need for the split path identified
422 res.reserve(elementCount());
423 res.setFillRule(fillRule());
424 for (const Element &element : m_elements) {
425 if (element.m_isSubpathStart)
426 res.moveTo(element.startPoint().toPointF());
427 if (element.m_isLine)
428 res.lineTo(element.endPoint().toPointF());
429 else
430 res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
431 };
432 return res;
433}
434
435// Returns a new path since doing it inline would probably be less efficient
436// (technically changing it from O(n) to O(n^2))
437// Note that this function should be called before splitting any elements,
438// so we can assume that the structure is a list and not a tree
440{
441 QuadPath res = *this;
442 res.m_elements = {};
443 res.m_elements.reserve(elementCount());
444 qsizetype subStart = -1;
445 qsizetype prevElement = -1;
446 for (qsizetype i = 0; i < elementCount(); i++) {
447 const auto &element = m_elements.at(i);
448 if (element.m_isSubpathStart) {
449 if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
450 res.currentPoint = m_elements[i - 1].ep;
451 res.lineTo(m_elements[subStart].sp);
452 auto &endElement = res.m_elements.last();
453 endElement.m_isSubpathEnd = true;
454 // lineTo() can bail out if the points are too close.
455 // In that case, just change the end point to be equal to the start
456 // (No need to test because the assignment is a no-op otherwise).
457 endElement.ep = m_elements[subStart].sp;
458 } else if (prevElement >= 0) {
459 res.m_elements[prevElement].m_isSubpathEnd = true;
460 }
461 subStart = i;
462 }
463 res.m_elements.append(element);
464 prevElement = res.m_elements.size() - 1;
465 }
466
467 if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
468 res.currentPoint = m_elements.last().ep;
469 res.lineTo(m_elements[subStart].sp);
470 }
471 if (!res.m_elements.isEmpty()) {
472 auto &endElement = res.m_elements.last();
473 endElement.m_isSubpathEnd = true;
474 endElement.ep = m_elements[subStart].sp;
475 }
476
477 // ### Workaround for triangulator issue: Avoid 3-element paths
478 if (res.elementCount() == 3) {
479 res.splitElementAt(2);
480 res = res.flattened();
481 Q_ASSERT(res.elementCount() == 4);
482 }
483
484 return res;
485}
486
488{
490 res.reserve(elementCountRecursive());
491 iterateElements([&](const QuadPath::Element &element) { res.m_elements.append(element); });
492 return res;
493}
494
496{
497public:
499 : m_element(element)
500 {
501 m_currentPoint = m_element.startPoint();
502 if (m_element.isLine())
503 m_lineLength = (m_element.endPoint() - m_element.startPoint()).length();
504 else
505 fillLUT();
506 }
507
508 bool consume(float length)
509 {
510 m_lastT = m_currentT;
511 m_lastPoint = m_currentPoint;
512 float nextBreak = m_consumed + length;
513 float breakT = m_element.isLine() ? nextBreak / m_lineLength : tForLength(nextBreak);
514 if (breakT < 1) {
515 m_currentT = breakT;
516 m_currentPoint = m_element.pointAtFraction(m_currentT);
517 m_consumed = nextBreak;
518 return true;
519 } else {
520 m_currentT = 1;
521 m_currentPoint = m_element.endPoint();
522 return false;
523 }
524 }
525
527 {
528 return m_currentPoint;
529 }
530
532 {
533 Q_ASSERT(!m_element.isLine());
534 // Split curve right at lastT, yields { lastPoint, rcp, endPoint } quad segment
535 QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
536 // Split that left at currentT, yields { lastPoint, lcp, currentPoint } quad segment
537 float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
538 QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
539 return lcp;
540 }
541
543 {
544 float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
545 return elemLength - m_consumed;
546 }
547
548private:
549 void fillLUT()
550 {
551 Q_ASSERT(!m_element.isLine());
552 QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
553 QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
554 float A = 4 * QVector2D::dotProduct(ap, ap);
555 float B = 4 * QVector2D::dotProduct(ap, bp);
556 float C = QVector2D::dotProduct(bp, bp);
557 float b = B / (2 * A);
558 float c = C / A;
559 float k = c - (b * b);
560 float l2 = b * std::sqrt(b * b + k);
561 float lnom = b + std::sqrt(b * b + k);
562 float l0 = 0.5f * std::sqrt(A);
563
564 m_lut.resize(LUTSize, 0);
565 for (int i = 1; i < LUTSize; i++) {
566 float t = float(i) / (LUTSize - 1);
567 float u = t + b;
568 float w = std::sqrt(u * u + k);
569 float l1 = u * w;
570 float lden = u + w;
571 float l3 = k * std::log(std::fabs(lden / lnom));
572 float res = l0 * (l1 - l2 + l3);
573 m_lut[i] = res;
574 }
575 }
576
577 float tForLength(float length)
578 {
579 Q_ASSERT(!m_element.isLine());
580 Q_ASSERT(!m_lut.isEmpty());
581
582 float res = 2; // I.e. invalid, outside [0, 1] range
583 auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(), length);
584 if (it != m_lut.cend()) {
585 float nextLength = *it--;
586 float prevLength = *it;
587 int prevIndex = std::distance(m_lut.cbegin(), it);
588 float fraction = (length - prevLength) / (nextLength - prevLength);
589 res = (prevIndex + fraction) / (LUTSize - 1);
590 }
591 return res;
592 }
593
594 const QuadPath::Element &m_element;
595 float m_lastT = 0;
596 float m_currentT = 0;
597 QVector2D m_lastPoint;
598 QVector2D m_currentPoint;
599 float m_consumed = 0;
600 // For line elements:
601 float m_lineLength;
602 // For quadratic curve elements:
603 static constexpr int LUTSize = 21;
605};
606
607QuadPath QuadPath::dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset) const
608{
610 float patternLength = 0;
611 for (int i = 0; i < 2 * (dashPattern.length() / 2); i++) {
612 float dashLength = qMax(lineWidth * dashPattern[i], qreal(0));
613 pattern.append(dashLength);
614 patternLength += dashLength;
615 }
616 if (patternLength == 0)
617 return {};
618
619 int startIndex = 0;
620 float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
621 if (startOffset < 0)
622 startOffset += patternLength;
623 for (float dashLength : pattern) {
624 if (dashLength > startOffset)
625 break;
626 startIndex++;
627 startOffset -= dashLength;
628 }
629
630 int dashIndex = startIndex;
631 float offset = startOffset;
633 for (int i = 0; i < elementCount(); i++) {
634 const Element &element = elementAt(i);
635 if (element.isSubpathStart()) {
636 res.moveTo(element.startPoint());
637 dashIndex = startIndex;
638 offset = startOffset;
639 }
640 ElementCutter cutter(element);
641 while (true) {
642 bool gotAll = cutter.consume(pattern.at(dashIndex) - offset);
643 QVector2D nextPoint = cutter.currentCutPoint();
644 if (dashIndex & 1)
645 res.moveTo(nextPoint); // gap
646 else if (element.isLine())
647 res.lineTo(nextPoint); // dash in line
648 else
649 res.quadTo(cutter.currentControlPoint(), nextPoint); // dash in curve
650 if (gotAll) {
651 offset = 0;
652 dashIndex = (dashIndex + 1) % pattern.size();
653 } else {
654 offset += cutter.lastLength();
655 break;
656 }
657 }
658 }
659 return res;
660}
661
663{
664 const qsizetype newChildIndex = m_childElements.size();
665 m_childElements.resize(newChildIndex + 2);
667 parent.m_numChildren = 2;
668 parent.m_firstChildIndex = newChildIndex;
669
670 Element &quad1 = m_childElements[newChildIndex];
671 const QVector2D mp = parent.midPoint();
672 quad1.sp = parent.sp;
673 quad1.cp = 0.5f * (parent.sp + parent.cp);
674 quad1.ep = mp;
675 quad1.m_isSubpathStart = parent.m_isSubpathStart;
676 quad1.m_isSubpathEnd = false;
677 quad1.m_curvatureFlags = parent.m_curvatureFlags;
678 quad1.m_isLine = parent.m_isLine; //### || isPointNearLine(quad1.cp, quad1.sp, quad1.ep);
679
680 Element &quad2 = m_childElements[newChildIndex + 1];
681 quad2.sp = mp;
682 quad2.cp = 0.5f * (parent.ep + parent.cp);
683 quad2.ep = parent.ep;
684 quad2.m_isSubpathStart = false;
685 quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
686 quad2.m_curvatureFlags = parent.m_curvatureFlags;
687 quad2.m_isLine = parent.m_isLine; //### || isPointNearLine(quad2.cp, quad2.sp, quad2.ep);
688
689#ifndef QT_NO_DEBUG
690 if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
691 qDebug() << "###FIXME: quad splitting has yielded ~null quad.";
692#endif
693}
694
695static void printElement(QDebug stream, const QuadPath::Element &element)
696{
697 auto printPoint = [&](QVector2D p) { stream << "(" << p.x() << ", " << p.y() << ") "; };
698 stream << "{ ";
699 printPoint(element.startPoint());
700 printPoint(element.controlPoint());
701 printPoint(element.endPoint());
702 stream << "} " << (element.isLine() ? "L " : "C ") << (element.isConvex() ? "X " : "O ")
703 << (element.isSubpathStart() ? "S" : element.isSubpathEnd() ? "E" : "");
704}
705
707{
709 stream.nospace();
710 stream << "QuadPath::Element( ";
711 printElement(stream, element);
712 stream << " )";
713 return stream;
714}
715
717{
719 stream.nospace();
720 stream << "QuadPath(" << path.elementCount() << " main elements, "
721 << path.elementCountRecursive() << " leaf elements, "
722 << (path.fillRule() == Qt::OddEvenFill ? "OddEven" : "Winding") << Qt::endl;
723 qsizetype count = 0;
724 path.iterateElements([&](const QuadPath::Element &e) {
725 stream << " " << count++ << (e.isSubpathStart() ? " >" : " ");
727 stream << Qt::endl;
728 });
729 stream << ")";
730 return stream;
731}
732
734
735void QQuickShapeCurveRenderer::beginSync(int totalCount, bool *countChanged)
736{
737 if (countChanged != nullptr && totalCount != m_paths.size())
738 *countChanged = true;
739 m_paths.resize(totalCount);
740}
741
743{
744 auto &pathData = m_paths[index];
745 pathData.originalPath = path->path();
746 pathData.m_dirty |= PathDirty;
747}
748
750{
751 auto &pathData = m_paths[index];
752 const bool wasVisible = pathData.isStrokeVisible();
753 pathData.pen.setColor(color);
754 if (pathData.isStrokeVisible() != wasVisible)
755 pathData.m_dirty |= StrokeDirty;
756 else
757 pathData.m_dirty |= UniformsDirty;
758}
759
761{
762 auto &pathData = m_paths[index];
763 if (w > 0) {
764 pathData.validPenWidth = true;
765 pathData.pen.setWidthF(w);
766 } else {
767 pathData.validPenWidth = false;
768 }
769 pathData.m_dirty |= StrokeDirty;
770}
771
773{
774 auto &pathData = m_paths[index];
775 const bool wasVisible = pathData.isFillVisible();
776 pathData.fillColor = color;
777 if (pathData.isFillVisible() != wasVisible)
778 pathData.m_dirty |= FillDirty;
779 else
780 pathData.m_dirty |= UniformsDirty;
781}
782
784{
785 auto &pathData = m_paths[index];
786 pathData.fillRule = Qt::FillRule(fillRule);
787 pathData.m_dirty |= PathDirty;
788}
789
792 int miterLimit)
793{
794 auto &pathData = m_paths[index];
795 pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
796 pathData.pen.setMiterLimit(miterLimit);
797 pathData.m_dirty |= StrokeDirty;
798}
799
801{
802 auto &pathData = m_paths[index];
803 pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
804 pathData.m_dirty |= StrokeDirty;
805}
806
809 qreal dashOffset,
810 const QVector<qreal> &dashPattern)
811{
812 auto &pathData = m_paths[index];
813 pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
814 if (strokeStyle == QQuickShapePath::DashLine) {
815 pathData.pen.setDashPattern(dashPattern);
816 pathData.pen.setDashOffset(dashOffset);
817 }
818 pathData.m_dirty |= StrokeDirty;
819}
820
822{
823 PathData &pd(m_paths[index]);
824 pd.gradientType = NoGradient;
825 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) {
826 pd.gradientType = LinearGradient;
827 pd.gradient.stops = gradient->gradientStops();
828 pd.gradient.spread = gradient->spread();
829 pd.gradient.a = QPointF(g->x1(), g->y1());
830 pd.gradient.b = QPointF(g->x2(), g->y2());
831 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) {
832 pd.gradientType = RadialGradient;
833 pd.gradient.a = QPointF(g->centerX(), g->centerY());
834 pd.gradient.b = QPointF(g->focalX(), g->focalY());
835 pd.gradient.v0 = g->centerRadius();
836 pd.gradient.v1 = g->focalRadius();
837 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(gradient)) {
838 pd.gradientType = ConicalGradient;
839 pd.gradient.a = QPointF(g->centerX(), g->centerY());
840 pd.gradient.v0 = g->angle();
841 } else
842 if (gradient != nullptr) {
843 static bool warned = false;
844 if (!warned) {
845 warned = true;
846 qCWarning(lcShapeCurveRenderer) << "Unsupported gradient fill";
847 }
848 }
849
850 if (pd.gradientType != NoGradient) {
851 pd.gradient.stops = gradient->gradientStops();
852 pd.gradient.spread = gradient->spread();
853 }
854
855 pd.m_dirty |= FillDirty;
856}
857
858void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data)
859{
860 qCWarning(lcShapeCurveRenderer) << "Asynchronous creation not supported by CurveRenderer";
861 Q_UNUSED(callback);
862 Q_UNUSED(data);
863}
864
866{
867 Q_UNUSED(async);
868}
869
871{
872 if (!m_rootNode)
873 return;
874 static const bool doOverlapSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
875 static const bool useTriangulatingStroker = qEnvironmentVariableIntValue("QT_QUICKSHAPES_TRIANGULATING_STROKER");
876 static const bool simplifyPath = qEnvironmentVariableIntValue("QT_QUICKSHAPES_SIMPLIFY_PATHS");
877
878 for (PathData &pathData : m_paths) {
879 int dirtyFlags = pathData.m_dirty;
880
881 if (dirtyFlags & PathDirty) {
882 if (simplifyPath)
883 pathData.path = QuadPath::fromPainterPath(pathData.originalPath.simplified());
884 else
885 pathData.path = QuadPath::fromPainterPath(pathData.originalPath);
886 pathData.path.setFillRule(pathData.fillRule);
887 pathData.fillPath = {};
888 dirtyFlags |= (FillDirty | StrokeDirty);
889 }
890
891 if (dirtyFlags & FillDirty) {
892 deleteAndClear(&pathData.fillNodes);
893 deleteAndClear(&pathData.fillDebugNodes);
894 if (pathData.isFillVisible()) {
895 if (pathData.fillPath.isEmpty()) {
896 pathData.fillPath = pathData.path.subPathsClosed();
897 pathData.fillPath.addCurvatureData();
898 if (doOverlapSolving)
899 solveOverlaps(pathData.fillPath);
900 }
901 pathData.fillNodes = addFillNodes(pathData, &pathData.fillDebugNodes);
902 dirtyFlags |= StrokeDirty;
903 }
904 }
905
906 if (dirtyFlags & StrokeDirty) {
907 deleteAndClear(&pathData.strokeNodes);
908 deleteAndClear(&pathData.strokeDebugNodes);
909 if (pathData.isStrokeVisible()) {
910 const QPen &pen = pathData.pen;
911 if (pen.style() == Qt::SolidLine)
912 pathData.strokePath = pathData.path;
913 else
914 pathData.strokePath = pathData.path.dashed(pen.widthF(), pen.dashPattern(), pen.dashOffset());
915
916 if (useTriangulatingStroker)
917 pathData.strokeNodes = addTriangulatingStrokerNodes(pathData, &pathData.strokeDebugNodes);
918 else
919 pathData.strokeNodes = addCurveStrokeNodes(pathData, &pathData.strokeDebugNodes);
920 }
921 }
922
923 if (dirtyFlags & UniformsDirty) {
924 if (!(dirtyFlags & FillDirty)) {
925 for (auto &pathNode : std::as_const(pathData.fillNodes))
926 static_cast<QQuickShapeCurveNode *>(pathNode)->setColor(pathData.fillColor);
927 }
928 if (!(dirtyFlags & StrokeDirty)) {
929 for (auto &strokeNode : std::as_const(pathData.strokeNodes))
930 static_cast<QQuickShapeCurveNode *>(strokeNode)->setColor(pathData.pen.color());
931 }
932 }
933
934 pathData.m_dirty &= ~(PathDirty | FillDirty | StrokeDirty | UniformsDirty);
935 }
936}
937
938// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
939// 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)
941{
942 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
943
944 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
945 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
946
947 return {u, v};
948}
949
950// Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
951// also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
953{
954 QVector2D v1 = 2 * (p1 - p0);
955 QVector2D v2 = p2 - v1 - p0;
956 return uvForPoint(v1, v2, p - p0);
957}
958
960{
961 auto uv = curveUv(sp, cp, ep, p);
962 if (m_isLine)
963 return { uv.x(), uv.y(), 0.0f };
964 else
965 return { uv.x(), uv.y(), (m_curvatureFlags & Convex) ? -1.0f : 1.0f };
966}
967
969{
970 auto v = b - a;
971 return {v.y(), -v.x()};
972}
973
974// The sign of the return value indicates which side of the line defined by a and n the point p falls
976{
977 float dot = QVector2D::dotProduct(p - a, n);
978 return dot;
979};
980
981template<typename Func>
982void iteratePath(const QuadPath &path, int index, Func &&lambda)
983{
984 const auto &element = path.elementAt(index);
985 if (element.childCount() == 0) {
986 lambda(element, index);
987 } else {
988 for (int i = 0; i < element.childCount(); ++i)
989 iteratePath(path, element.indexOfChild(i), lambda);
990 }
991}
992QVector<QSGGeometryNode *> QQuickShapeCurveRenderer::addFillNodes(const PathData &pathData, NodeList *debugNodes)
993{
994 //qDebug() << "========= STARTING ===========" << pathData.path;
995 auto *node = new QQuickShapeCurveNode;
996 node->setGradientType(pathData.gradientType);
997
999 const QColor &color = pathData.fillColor;
1000 QPainterPath internalHull;
1001 internalHull.setFillRule(pathData.fillPath.fillRule());
1002
1003
1004 bool visualizeDebug = debugVisualization() & DebugCurves;
1005 const float dbg = visualizeDebug ? 0.5f : 0.0f;
1007
1008 QHash<QPair<float, float>, int> linePointHash;
1009 QHash<QPair<float, float>, int> concaveControlPointHash;
1010 QHash<QPair<float, float>, int> convexPointHash;
1011
1012
1013 auto toRoundedPair = [](const QPointF &p) -> QPair<float, float> {
1014 return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
1015 };
1016
1017 auto toRoundedVec2D = [](const QPointF &p) -> QVector2D {
1018 return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
1019 };
1020
1021 auto roundVec2D = [](const QVector2D &p) -> QVector2D {
1022 return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
1023 };
1024
1025 auto addCurveTriangle = [&](const QuadPath::Element &element, const QVector2D &sp, const QVector2D &ep, const QVector2D &cp) {
1026 float r = 0.0f, g = 0.0f, b = 0.0f;
1027 if (element.isLine()) {
1028 g = b = 1.0f;
1029 } else if (element.isConvex()) {
1030 r = 1.0;
1031 } else { // concave
1032 b = 1.0;
1033 }
1034
1035 QVector4D dbgColor(r, g, b, dbg);
1036
1037 node->appendTriangle(sp, cp, ep,
1038 [&element](QVector2D v) { return element.uvForPoint(v); },
1039 dbgColor, dbgColor, dbgColor);
1040
1041 wfVertices.append({sp.x(), sp.y(), 1.0f, 0.0f, 0.0f}); // 0
1042 wfVertices.append({cp.x(), cp.y(), 0.0f, 1.0f, 0.0f}); // 1
1043 wfVertices.append({ep.x(), ep.y(), 0.0f, 0.0f, 1.0f}); // 2
1044 };
1045
1046 // Find a point on the other side of the line
1047 auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){
1048
1049 QVector2D baseLine = endPoint - startPoint;
1050 QVector2D insideVector = referencePoint - startPoint;
1051 //QVector2D midPoint = (endPoint + startPoint) / 2; // ??? Should we use midPoint instead of startPoint??
1052 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle
1053
1054 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
1055
1056 return swap ? startPoint + normal : startPoint - normal;
1057 };
1058
1059 auto addLineTriangle = [&](const QuadPath::Element &element, const QVector2D &sp, const QVector2D &ep, const QVector2D &cp){
1060 addCurveTriangle(element, sp, ep, cp);
1061 // Add a triangle on the outer side of the line to get some more AA
1062 // The new point replaces cp
1063 QVector2D op = findPointOtherSide(sp, ep, cp); //sp - (cp - ep);
1064 addCurveTriangle(element, sp, op, ep);
1065 };
1066
1067 auto addConvexTriangle = [&](const QuadPath::Element &element, const QVector2D &sp, const QVector2D &ep, const QVector2D &cp){
1068 addCurveTriangle(element, sp, ep, cp);
1069 // Add two triangles on the outer side to get some more AA
1070
1071 // First triangle on the line sp-cp, replacing ep
1072 {
1073 QVector2D op = findPointOtherSide(sp, cp, ep); //sp - (ep - cp);
1074 addCurveTriangle(element, sp, cp, op);
1075 }
1076
1077 // Second triangle on the line ep-cp, replacing sp
1078 {
1079 QVector2D op = findPointOtherSide(ep, cp, sp); //ep - (sp - cp);
1080 addCurveTriangle(element, op, cp, ep);
1081 }
1082
1083 };
1084
1085
1086 // This is guaranteed to be in safe space (the curve will never enter the triangle)
1087 // ### This is the opposite of what we really want: it's going to be extremely thin when we need it,
1088 // and big when it's completely pointless, but a thicker triangle could be going into negative space
1089 auto oppositePoint = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &controlPoint) -> QVector2D {
1090 return startPoint + 2 * (endPoint - controlPoint);
1091 };
1092
1093 // Identical to addLineTriangle, except for how op is calculated
1094 auto addConcaveTriangle = [&](const QuadPath::Element &element, const QVector2D &sp, const QVector2D &ep, const QVector2D &cp){
1095 addCurveTriangle(element, sp, ep, cp);
1096 // Add an outer triangle to give extra AA for very flat curves
1097 QVector2D op = oppositePoint(sp, ep, cp);
1098 // The new point replaces cp
1099 addCurveTriangle(element, sp, op, ep);
1100 };
1101
1102 auto addFillTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3){
1103 constexpr QVector3D uv(0.0, 1.0, -1.0);
1104 QVector4D dbgColor(0.0f, 1.0f, 0.0f, dbg);
1105 node->appendTriangle(p1, p2, p3,
1106 [&uv](QVector2D) { return uv; },
1107 dbgColor, dbgColor, dbgColor);
1108
1109 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); // 0
1110 wfVertices.append({p3.x(), p3.y(), 0.0f, 1.0f, 0.0f}); // 1
1111 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f}); // 2
1112 };
1113
1114
1115
1116 for (int i = 0; i < pathData.fillPath.elementCount(); ++i)
1117 iteratePath(pathData.fillPath, i, [&](const QuadPath::Element &element, int index){
1118 QPointF sp(element.startPoint().toPointF()); //### to much conversion to and from pointF
1119 QPointF cp(element.controlPoint().toPointF());
1120 QPointF ep(element.endPoint().toPointF());
1121 if (element.isSubpathStart())
1122 internalHull.moveTo(sp);
1123 if (element.isLine()) {
1124 internalHull.lineTo(ep);
1125 //lineSegments.append(QLineF(sp, ep));
1126 linePointHash.insert(toRoundedPair(sp), index);
1127 } else {
1128 if (element.isConvex()) {
1129 internalHull.lineTo(ep);
1130 addConvexTriangle(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
1131 convexPointHash.insert(toRoundedPair(sp), index);
1132 } else {
1133 internalHull.lineTo(cp);
1134 internalHull.lineTo(ep);
1135 addConcaveTriangle(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
1136 concaveControlPointHash.insert(toRoundedPair(cp), index);
1137 }
1138 }
1139 });
1140 //qDebug() << "Curves: i" << indices.size() << "v" << vertexBuffer.size() << "w" << wfVertices.size();
1141
1142 auto makeHashable = [](const QVector2D &p) -> QPair<float, float> {
1143 return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
1144 };
1145 // Points in p are already rounded do 1/32
1146 // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
1147
1148 // TODO: Does not handle ambiguous vertices that are on multiple unrelated lines/curves
1149
1150 auto onSameSideOfLine = [](const QVector2D &p1, const QVector2D &p2, const QVector2D &linePoint, const QVector2D &lineNormal) {
1151 float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
1152 float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
1153 return side1 * side2 >= 0;
1154 };
1155
1156 auto pointInSafeSpace = [&](const QVector2D &p, const QuadPath::Element &element) -> bool {
1157 const QVector2D a = element.startPoint();
1158 const QVector2D b = element.endPoint();
1159 const QVector2D c = element.controlPoint();
1160 // There are "safe" areas of the curve also across the baseline: the curve can never cross:
1161 // line1: the line through A and B'
1162 // line2: the line through B and A'
1163 // Where A' = A "mirrored" through C and B' = B "mirrored" through C
1164 const QVector2D n1 = calcNormalVector(a, c + (c - b));
1165 const QVector2D n2 = calcNormalVector(b, c + (c - a));
1166 bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
1167 bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
1168 return safeSideOf1 && safeSideOf2;
1169 };
1170
1171 auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
1172 int lineElementIndex = -1;
1173 int concaveElementIndex = -1;
1174 int convexElementIndex = -1;
1175
1176 bool foundElement = false;
1177 int si = -1;
1178 int ei = -1;
1179 for (int i = 0; i < 3; ++i) {
1180 if (auto found = linePointHash.constFind(makeHashable(p[i])); found != linePointHash.constEnd()) {
1181 // check if this triangle is on a line, i.e. if one point is the sp and another is the ep of the same path element
1182 const auto &element = pathData.fillPath.elementAt(*found);
1183 //qDebug() << " " << element;
1184 for (int j = 0; j < 3; ++j) {
1185 if (i != j && roundVec2D(element.endPoint()) == p[j]) {
1186 if (foundElement)
1187 return false; // More than one edge on path: must split
1188 lineElementIndex = *found;
1189 si = i;
1190 ei = j;
1191 //qDebug() << "FOUND IT!!!!" << p[i] << p[j] << lineElementIndex;
1192 foundElement = true;
1193 }
1194 }
1195 } else if (auto found = concaveControlPointHash.constFind(makeHashable(p[i])); found != concaveControlPointHash.constEnd()) {
1196 // check if this triangle is on the tangent line of a concave curve,
1197 // i.e if one point is the cp, and the other is sp or ep
1198 // TODO: clean up duplicated code (almost the same as the lineElement path above)
1199 const auto &element = pathData.fillPath.elementAt(*found);
1200 for (int j = 0; j < 3; ++j) {
1201 if (i == j)
1202 continue;
1203 if (roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j]) {
1204 if (foundElement)
1205 return false; // More than one edge on path: must split
1206 concaveElementIndex = *found;
1207 // The tangent line is p[i] - p[j]
1208 si = i; // we may not need these
1209 ei = j;
1210 //qDebug() << "FOUND IT!!!!" << p[i] << p[j] << lineElementIndex;
1211 foundElement = true;
1212 }
1213 }
1214 } else if (auto found = convexPointHash.constFind(makeHashable(p[i])); found != convexPointHash.constEnd()) {
1215 // check if this triangle is on a curve, i.e. if one point is the sp and another is the ep of the same path element
1216 const auto &element = pathData.fillPath.elementAt(*found);
1217 for (int j = 0; j < 3; ++j) {
1218 if (i != j && roundVec2D(element.endPoint()) == p[j]) {
1219 if (foundElement)
1220 return false; // More than one edge on path: must split
1221 convexElementIndex = *found;
1222 si = i;
1223 ei = j;
1224 //qDebug() << "FOUND IT!!!!" << p[i] << p[j] << convexElementIndex;
1225 foundElement = true;
1226 }
1227 }
1228 }
1229 }
1230 if (lineElementIndex != -1) {
1231 int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
1232 addLineTriangle(pathData.fillPath.elementAt(lineElementIndex), p[si], p[ei], p[ci]);
1233 } else if (concaveElementIndex != -1) {
1234 addCurveTriangle(pathData.fillPath.elementAt(concaveElementIndex), p[0], p[1], p[2]);
1235 } else if (convexElementIndex != -1) {
1236 int oi = (6 - si - ei) % 3;
1237 const auto &otherPoint = p[oi];
1238 const auto &element = pathData.fillPath.elementAt(convexElementIndex);
1239 // We have to test whether the triangle can cross the line TODO: use the toplevel element's safe space
1240 bool safeSpace = pointInSafeSpace(otherPoint, element);
1241 if (safeSpace) {
1242 addCurveTriangle(element, p[0], p[1], p[2]);
1243 } else {
1244 //qDebug() << "Point" << otherPoint << "element" << element << "safe" << safeSpace;
1245 // Find a point inside the triangle that's also in the safe space
1246 QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
1247 // We should calculate the point directly, but just do a lazy implementation for now:
1248 for (int i = 0; i < 7; ++i) {
1249 safeSpace = pointInSafeSpace(newPoint, element);
1250 //qDebug() << "UNSAFE space round" << i << "checking" << newPoint << safeSpace;
1251 if (safeSpace)
1252 break;
1253 newPoint = (p[si] + p[ei] + newPoint) / 3;
1254 }
1255 if (safeSpace) {
1256 // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
1257 // Curve triangle is (sp, ep, np)
1258 addCurveTriangle(element, p[si], p[ei], newPoint);
1259 // The other two are (sp, op, np) and (ep, op, np)
1260 addFillTriangle(p[si], p[oi], newPoint);
1261 addFillTriangle(p[ei], p[oi], newPoint);
1262 } else {
1263 // fallback to fill if we can't find a point in safe space
1264 addFillTriangle(p[0], p[1], p[2]);
1265 }
1266 }
1267
1268 } else {
1269 addFillTriangle(p[0], p[1], p[2]);
1270 }
1271 return true;
1272 };
1273
1274 QTriangleSet triangles = qTriangulate(internalHull);
1275
1276 const quint32 *idxTable = static_cast<const quint32 *>(triangles.indices.data());
1277 for (int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
1278 const quint32 *idx = &idxTable[triangle * 3];
1279
1280 QVector2D p[3];
1281 for (int i = 0; i < 3; ++i) {
1282 p[i] = toRoundedVec2D(QPointF(triangles.vertices.at(idx[i]*2), triangles.vertices.at(idx[i]*2 + 1)));
1283 }
1284 bool needsSplit = !handleTriangle(p);
1285 if (needsSplit) {
1286 QVector2D c = (p[0] + p[1] + p[2]) / 3;
1287 //qDebug() << "Split!!! New point" << c;
1288 for (int i = 0; i < 3; ++i) {
1289 qSwap(c, p[i]);
1290 //qDebug() << "Adding split triangle" << p[0] << p[1] << p[2];
1291 handleTriangle(p);
1292 qSwap(c, p[i]);
1293 }
1294 }
1295 }
1296
1297 QVector<quint32> indices = node->uncookedIndexes();
1298 if (indices.size() > 0) {
1299 node->setColor(color);
1300 node->setFillGradient(pathData.gradient);
1301
1302 node->cookGeometry();
1303 m_rootNode->appendChildNode(node);
1304 ret.append(node);
1305 }
1306
1307
1308 const bool wireFrame = debugVisualization() & DebugWireframe;
1309 if (wireFrame) {
1310 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
1311
1312 //QVarLengthArray<quint32> indices;
1313
1314 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
1315 wfVertices.size(),
1316 indices.size(),
1318 wfNode->setGeometry(wfg);
1319
1321 memcpy(wfg->indexData(),
1322 indices.data(),
1323 indices.size() * wfg->sizeOfIndex());
1324 memcpy(wfg->vertexData(),
1325 wfVertices.data(),
1326 wfg->vertexCount() * wfg->sizeOfVertex());
1327
1328 m_rootNode->appendChildNode(wfNode);
1329 debugNodes->append(wfNode);
1330 }
1331
1332 return ret;
1333}
1334
1335QVector<QSGGeometryNode *> QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const PathData &pathData, NodeList *debugNodes)
1336{
1338 const QColor &color = pathData.pen.color();
1339
1340 bool visualizeDebug = debugVisualization() & DebugCurves;
1341 const float dbg = visualizeDebug ? 0.5f : 0.0f;
1343
1344 QTriangulatingStroker stroker;
1345 const auto painterPath = pathData.strokePath.toPainterPath();
1346 const QVectorPath &vp = qtVectorPathForPath(painterPath);
1347 QPen pen = pathData.pen;
1348 stroker.process(vp, pen, {}, {});
1349
1350 auto *node = new QQuickShapeCurveNode;
1351 node->setGradientType(pathData.gradientType);
1352
1353 auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){
1354
1355 QVector2D baseLine = endPoint - startPoint;
1356 QVector2D insideVector = referencePoint - startPoint;
1357 //QVector2D midPoint = (endPoint + startPoint) / 2; // ??? Should we use midPoint instead of startPoint??
1358 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle
1359
1360 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
1361
1362 return swap ? startPoint + normal : startPoint - normal;
1363 };
1364
1365 static bool disableExtraTriangles = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
1366
1367 auto addStrokeTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, bool){
1368 //constexpr QVector3D uv(0.0, 1.0, -1.0);
1369
1370
1371 if (p1 == p2 || p2 == p3) {
1372 //qDebug() << "Skipping triangle" << p1 << p2 << p3;
1373 return;
1374 }
1375
1376 auto uvForPoint = [&p1, &p2, &p3](QVector2D p) {
1377 auto uv = curveUv(p1, p2, p3, p);
1378 return QVector3D(uv.x(), uv.y(), 0.0f); // Line
1379 };
1380
1381 node->appendTriangle(p1, p2, p3,
1382 uvForPoint,
1383 QVector4D(1.0f, 0.0, 0.0, dbg),
1384 QVector4D(0.0f, 1.0, 0.0, dbg),
1385 QVector4D(0.0f, 0.0, 1.0, dbg));
1386
1387
1388 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); // 0
1389 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.1f, 0.0f}); // 1
1390 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f}); // 2
1391
1392 if (!disableExtraTriangles) {
1393 // Add a triangle on the outer side of the line to get some more AA
1394 // The new point replaces p2 (currentVertex+1)
1395 QVector2D op = findPointOtherSide(p1, p3, p2);
1396 node->appendTriangle(p1, op, p3,
1397 uvForPoint,
1398 QVector4D(1.0f, 0.0, 0.0, dbg),
1399 QVector4D(1.0f, 1.0, 0.0, dbg),
1400 QVector4D(0.0f, 0.0, 1.0, dbg));
1401
1402 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f});
1403 wfVertices.append({op.x(), op.y(), 0.0f, 1.0f, 0.0f}); // replacing p2
1404 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f});
1405 }
1406 };
1407
1408 const int vertCount = stroker.vertexCount() / 2;
1409 const float *verts = stroker.vertices();
1410 for (int i = 0; i < vertCount - 2; ++i) {
1411 QVector2D p[3];
1412 for (int j = 0; j < 3; ++j) {
1413 p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
1414 }
1415 bool isOdd = i % 2;
1416 addStrokeTriangle(p[0], p[1], p[2], isOdd);
1417 }
1418
1419 QVector<quint32> indices = node->uncookedIndexes();
1420 if (indices.size() > 0) {
1421 node->setColor(color);
1422 node->setFillGradient(pathData.gradient);
1423
1424 node->cookGeometry();
1425 m_rootNode->appendChildNode(node);
1426 ret.append(node);
1427 }
1428 const bool wireFrame = debugVisualization() & DebugWireframe;
1429 if (wireFrame) {
1430 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
1431
1432 //QVarLengthArray<quint32> indices;
1433
1434 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
1435 wfVertices.size(),
1436 indices.size(),
1438 wfNode->setGeometry(wfg);
1439
1441 memcpy(wfg->indexData(),
1442 indices.data(),
1443 indices.size() * wfg->sizeOfIndex());
1444 memcpy(wfg->vertexData(),
1445 wfVertices.data(),
1446 wfg->vertexCount() * wfg->sizeOfVertex());
1447
1448 m_rootNode->appendChildNode(wfNode);
1449 debugNodes->append(wfNode);
1450 }
1451
1452 return ret;
1453}
1454
1456{
1457 m_rootNode = node;
1458}
1459
1460int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
1461
1463{
1464 static const int envFlags = qEnvironmentVariableIntValue("QT_QUICKSHAPES_DEBUG");
1465 return debugVisualizationFlags | envFlags;
1466}
1467
1469{
1470 if (debugVisualizationFlags == options)
1471 return;
1472 debugVisualizationFlags = options;
1473}
1474
1475void QQuickShapeCurveRenderer::deleteAndClear(NodeList *nodeList)
1476{
1477 for (QSGNode *node : std::as_const(*nodeList))
1478 delete node;
1479 nodeList->clear();
1480}
1481
1482namespace {
1483
1484/*
1485 Clever triangle overlap algorithm. Stack Overflow says:
1486
1487 You can prove that the two triangles do not collide by finding an edge (out of the total 6
1488 edges that make up the two triangles) that acts as a separating line where all the vertices
1489 of one triangle lie on one side and the vertices of the other triangle lie on the other side.
1490 If you can find such an edge then it means that the triangles do not intersect otherwise the
1491 triangles are colliding.
1492*/
1493
1494// The sign of the determinant tells the winding order: positive means counter-clockwise
1495static inline double determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
1496{
1497 return p1.x() * (p2.y() - p3.y())
1498 + p2.x() * (p3.y() - p1.y())
1499 + p3.x() * (p1.y() - p2.y());
1500}
1501
1502// Fix the triangle so that the determinant is positive
1503static void fixWinding(QVector2D &p1, QVector2D &p2, QVector2D &p3)
1504{
1505 double det = determinant(p1, p2, p3);
1506 if (det < 0.0) {
1507 qSwap(p1, p2);
1508 }
1509}
1510
1511// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
1512// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
1513// a positive determinant].
1514bool checkEdge(QVector2D &p1, QVector2D &p2, QVector2D &p, float epsilon)
1515{
1516 return determinant(p1, p2, p) <= epsilon;
1517}
1518
1519bool checkTriangleOverlap(QVector2D *triangle1, QVector2D *triangle2, float epsilon = 1.0/32)
1520{
1521 // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
1522 fixWinding(triangle1[0], triangle1[1], triangle1[2]);
1523 for (int i = 0; i < 3; i++) {
1524 int ni = (i + 1) % 3;
1525 if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
1526 checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
1527 checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
1528 return false;
1529 }
1530
1531 // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
1532 fixWinding(triangle2[0], triangle2[1], triangle2[2]);
1533 for (int i = 0; i < 3; i++) {
1534 int ni = (i + 1) % 3;
1535
1536 if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
1537 checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
1538 checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
1539 return false;
1540 }
1541
1542 return true;
1543}
1544
1545bool checkLineTriangleOverlap(QVector2D *triangle, QVector2D *line, float epsilon = 1.0/32)
1546{
1547 // See if all vertices of the triangle are on the same side of the line
1548 bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
1549 auto s2 = determinant(line[0], line[1], triangle[1]) < 0;
1550 auto s3 = determinant(line[0], line[1], triangle[2]) < 0;
1551 // If all determinants have the same sign, then there is no overlap
1552 if (s1 == s2 && s2 == s3) {
1553 return false;
1554 }
1555 // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
1556 fixWinding(triangle[0], triangle[1], triangle[2]);
1557 for (int i = 0; i < 3; i++) {
1558 int ni = (i + 1) % 3;
1559 if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
1560 checkEdge(triangle[i], triangle[ni], line[1], epsilon))
1561 return false;
1562 }
1563
1564 return true;
1565}
1566
1567// We could slightly optimize this if we did fixWinding in advance
1568bool checkTriangleContains (QVector2D pt, QVector2D v1, QVector2D v2, QVector2D v3, float epsilon = 1.0/32)
1569{
1570 float d1, d2, d3;
1571 d1 = determinant(pt, v1, v2);
1572 d2 = determinant(pt, v2, v3);
1573 d3 = determinant(pt, v3, v1);
1574
1575 bool allNegative = d1 < epsilon && d2 < epsilon && d3 < epsilon;
1576 bool allPositive = d1 > epsilon && d2 > epsilon && d3 > epsilon;
1577
1578 return allNegative || allPositive;
1579}
1580
1581// e1 is always a concave curve. e2 can be curve or line
1582static bool isOverlap(const QuadPath &path, qsizetype e1, qsizetype e2)
1583{
1584 const QuadPath::Element &element1 = path.elementAt(e1);
1585 const QuadPath::Element &element2 = path.elementAt(e2);
1586
1587 QVector2D t1[3] = { element1.startPoint(), element1.controlPoint(), element1.endPoint() };
1588
1589 if (element2.isLine()) {
1590 QVector2D line[2] = { element2.startPoint(), element2.endPoint() };
1591 return checkLineTriangleOverlap(t1, line);
1592 } else {
1593 QVector2D t2[3] = { element2.startPoint(), element2.controlPoint(), element2.endPoint() };
1594 return checkTriangleOverlap(t1, t2);
1595 }
1596
1597 return false;
1598}
1599
1600static bool isOverlap(const QuadPath &path, qsizetype index, const QVector2D &vertex)
1601{
1602 const QuadPath::Element &elem = path.elementAt(index);
1603 return checkTriangleContains(vertex, elem.startPoint(), elem.controlPoint(), elem.endPoint());
1604}
1605
1606struct TriangleData
1607{
1608 QVector2D points[3];
1609 int pathElementIndex;
1610 QVector3D debugColor;
1611 bool specialDebug = false; // Quick way of debugging special cases without having to change debugColor
1612};
1613
1614// Returns a vector that is normal to baseLine, pointing to the right
1615QVector2D normalVector(QVector2D baseLine)
1616{
1617 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
1618 return normal;
1619}
1620
1621// Returns a vector that is normal to the path and pointing to the right. If endSide is false
1622// the vector is normal to the start point, otherwise to the end point
1623QVector2D normalVector(const QuadPath::Element &element, bool endSide = false)
1624{
1625 if (element.isLine())
1626 return normalVector(element.endPoint() - element.startPoint());
1627 else if (!endSide)
1628 return normalVector(element.controlPoint() - element.startPoint());
1629 else
1630 return normalVector(element.endPoint() - element.controlPoint());
1631}
1632
1633// Returns a vector that is parallel to the path. If endSide is false
1634// the vector starts at the start point and points forward,
1635// otherwise it starts at the end point and points backward
1636QVector2D tangentVector(const QuadPath::Element &element, bool endSide = false)
1637{
1638 if (element.isLine()) {
1639 if (!endSide)
1640 return element.endPoint() - element.startPoint();
1641 else
1642 return element.startPoint() - element.endPoint();
1643 } else {
1644 if (!endSide)
1645 return element.controlPoint() - element.startPoint();
1646 else
1647 return element.controlPoint() - element.endPoint();
1648 }
1649}
1650
1651
1652QVector2D miterBisector(const QuadPath::Element &element1, const QuadPath::Element &element2, float strokeMargin, float inverseMiterLimit,
1653 bool *ok = nullptr, bool *pointingRight = nullptr)
1654{
1655 Q_ASSERT(element1.endPoint() == element2.startPoint());
1656
1657 const auto p1 = element1.isLine() ? element1.startPoint() : element1.controlPoint();
1658 const auto p2 = element1.endPoint();
1659 const auto p3 = element2.isLine() ? element2.endPoint() : element2.controlPoint();
1660
1661 const auto v1 = (p1 - p2).normalized();
1662 const auto v2 = (p3 - p2).normalized();
1663 const auto bisector = v1 + v2;
1664
1665 if (qFuzzyIsNull(bisector.x()) && qFuzzyIsNull(bisector.y())) {
1666 // v1 and v2 are almost parallel, and pointing in opposite directions
1667 // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
1668 QVector2D n1(-v1.y(), v1.x());
1669 QVector2D n2(-v2.y(), v2.x());
1670 if (ok)
1671 *ok = true;
1672 if (pointingRight)
1673 *pointingRight = true;
1674 return (n2 - n1).normalized() * strokeMargin;
1675 } else {
1676 // We need to increase the length, so that the result covers the whole miter
1677 // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
1678 // dot product of two unit vectors is the cosine of the angle between them
1679 // The length of the miter is w/sin(x/2) where x is the angle between the two elements
1680
1681 float cos2x = QVector2D::dotProduct(v1, v2);
1682 cos2x = qMin(1.0f, cos2x); // Allow for float inaccuracy
1683 float sine = sqrt((1.0f - cos2x) / 2);
1684 if (ok)
1685 *ok = sine >= inverseMiterLimit / 2.0f;
1686 if (pointingRight)
1687 *pointingRight = determinant(p1, p2, p3) > 0;
1688 sine = qMax(sine, 0.01f); // Avoid divide by zero
1689 return bisector.normalized() * strokeMargin / sine;
1690 }
1691}
1692
1693// Really simplistic O(n^2) triangulator - only intended for five points
1694QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, int elementIndex)
1695{
1696 int count = pts.size();
1697 Q_ASSERT(count >= 3);
1699
1700 ret.append({{pts[0], pts[1], pts[2]}, elementIndex, {1, 0, 0}});
1701
1702 // hull is always in positive determinant winding order
1703 QList<QVector2D> hull;
1704 float det1 = determinant(pts[0], pts[1], pts[2]);
1705 if (det1 > 0)
1706 hull << pts[0] << pts[1] << pts[2];
1707 else
1708 hull << pts[2] << pts[1] << pts[0];
1709 auto connectableInHull = [&](const QVector2D &pt) -> QList<int> {
1710 QList<int> r;
1711 const int n = hull.size();
1712 for (int i = 0; i < n; ++i) {
1713 const auto &p1 = hull.at(i);
1714 const auto &p2 = hull.at((i+1) % n);
1715 if (determinant(p1, p2, pt) < 0.0f)
1716 r << i;
1717 }
1718 return r;
1719 };
1720 for (int i = 3; i < count; ++i) {
1721 const auto &p = pts[i];
1722 auto visible = connectableInHull(p);
1723 if (visible.isEmpty())
1724 continue;
1725 int visCount = visible.count();
1726 int hullCount = hull.count();
1727 // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
1728 // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
1729 int boundaryStart = visible[0];
1730 for (int j = 0; j < visCount - 1; ++j) {
1731 if ((visible[j] + 1) % hullCount != visible[j+1]) {
1732 boundaryStart = visible[j + 1];
1733 break;
1734 }
1735 }
1736 // Now add those triangles
1737 for (int j = 0; j < visCount; ++j) {
1738 const auto &p1 = hull.at((boundaryStart + j) % hullCount);
1739 const auto &p2 = hull.at((boundaryStart + j+1) % hullCount);
1740 ret.append({{p1, p2, p}, elementIndex, {1,1,0}});
1741 }
1742 // Finally replace the points that are now inside the hull
1743 // We insert p after boundaryStart, and before boundaryStart + visCount (modulo...)
1744 // and remove the points inbetween
1745 int pointsToKeep = hullCount - visCount + 1;
1746 QList<QVector2D> newHull;
1747 newHull << p;
1748 for (int j = 0; j < pointsToKeep; ++j) {
1749 newHull << hull.at((j + boundaryStart + visCount) % hullCount);
1750 }
1751 hull = newHull;
1752 }
1753 return ret;
1754}
1755
1756
1757static QList<TriangleData> customTriangulator2(const QuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
1758{
1759 const bool bevelJoin = joinStyle == Qt::BevelJoin;
1760 const bool roundJoin = joinStyle == Qt::RoundJoin;
1761 const bool miterJoin = !bevelJoin && !roundJoin;
1762
1763 const bool roundCap = capStyle == Qt::RoundCap;
1764 const bool squareCap = capStyle == Qt::SquareCap;
1765
1766 Q_ASSERT(miterLimit > 0 || !miterJoin);
1767 float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
1768
1769
1770 static const int additionalSpace = qEnvironmentVariableIntValue("QT_QUICKSHAPES_EXTRA_SPACE");
1771
1772 const float extraFactor = roundJoin && additionalSpace ? (penWidth + additionalSpace) / penWidth : 2.0;
1773
1775 int count = path.elementCount();
1776 int subStart = 0;
1777 while (subStart < count) {
1778 int subEnd = subStart;
1779 for (int i = subStart + 1; i < count; ++i) {
1780 const auto &e = path.elementAt(i);
1781 if (e.isSubpathStart()) {
1782 subEnd = i - 1;
1783 break;
1784 }
1785 if (i == count - 1) {
1786 subEnd = i;
1787 break;
1788 }
1789 }
1790 bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
1791 const int subCount = subEnd - subStart + 1;
1792
1793 auto elementAt = [&](int idx, int delta) -> const QuadPath::Element * {
1794 int subIdx = idx - subStart;
1795 if (closed) {
1796 subIdx = (subIdx + subCount + delta) % subCount;
1797 return &path.elementAt(subStart + subIdx);
1798 }
1799 subIdx += delta;
1800 if (subIdx >= 0 && subIdx < subCount)
1801 return &path.elementAt(subStart + subIdx);
1802 return nullptr;
1803 };
1804
1805 for (int i = subStart; i <= subEnd; ++i) {
1806 const auto &element = path.elementAt(i);
1807 const auto *nextElement = elementAt(i, +1);
1808 const auto *prevElement = elementAt(i, -1);
1809
1810 const auto &s = element.startPoint();
1811 const auto &c = element.controlPoint();
1812 const auto &e = element.endPoint();
1813
1814 // Normals point to the right
1815 QVector2D startNormal = normalVector(element).normalized() * (penWidth / 2);
1816 QVector2D endNormal = normalVector(element, true).normalized() * (penWidth / 2);
1817
1818 // Bisectors point to the inside of the curve: make them point the same way as normals
1819 bool startBisectorPointsRight = true;
1820 bool startBisectorWithinMiterLimit = true;
1821 QVector2D startBisector = prevElement ? miterBisector(*prevElement, element, penWidth / 2, inverseMiterLimit, &startBisectorWithinMiterLimit, &startBisectorPointsRight) : startNormal;
1822 if (!startBisectorPointsRight)
1823 startBisector = -startBisector;
1824
1825 bool endBisectorPointsRight = true;
1826 bool endBisectorWithinMiterLimit = true;
1827 QVector2D endBisector = nextElement ? miterBisector(element, *nextElement, penWidth / 2, inverseMiterLimit, &endBisectorWithinMiterLimit, &endBisectorPointsRight) : endNormal;
1828 if (!endBisectorPointsRight)
1829 endBisector = -endBisector;
1830
1831 // We can't use the simple miter for miter joins, since the shader currently only supports round joins
1832 bool simpleMiter = joinStyle == Qt::RoundJoin;
1833
1834 // TODO: miterLimit
1835 bool startMiter = simpleMiter && startBisectorWithinMiterLimit;
1836 bool endMiter = simpleMiter && endBisectorWithinMiterLimit;
1837
1838 QVector2D p1, p2, p3, p4;
1839 if (startMiter) {
1840 //### bisector on inside can extend further than element: must limit it somehow
1841 p1 = s + startBisector * extraFactor;
1842 p2 = s - startBisector * extraFactor;
1843 } else {
1844 // TODO: remove the overdraw by using the intersection point on the inside (for lines, this is the bisector, but
1845 // it's more complex for non-smooth curve joins)
1846
1847 // For now, simple bevel: just use normals and live with overdraw
1848 p1 = s + startNormal * extraFactor;
1849 p2 = s - startNormal * extraFactor;
1850 }
1851 // repeat logic above for the other end:
1852 if (endMiter) {
1853 p3 = e + endBisector * extraFactor;
1854 p4 = e - endBisector * extraFactor;
1855 } else {
1856 p3 = e + endNormal * extraFactor;
1857 p4 = e - endNormal * extraFactor;
1858 }
1859
1860 // End caps
1861
1862 if (!prevElement) {
1863 QVector2D capSpace = tangentVector(element).normalized() * -penWidth;
1864 if (roundCap) {
1865 p1 += capSpace;
1866 p2 += capSpace;
1867 } else if (squareCap) {
1868 QVector2D c1 = p1 + capSpace;
1869 QVector2D c2 = p2 + capSpace;
1870 ret.append({{p1, s, c1}, -1, {1, 1, 0}, true});
1871 ret.append({{c1, s, c2}, -1, {1, 1, 0}, true});
1872 ret.append({{p2, s, c2}, -1, {1, 1, 0}, true});
1873 }
1874 }
1875 if (!nextElement) {
1876 QVector2D capSpace = tangentVector(element, true).normalized() * -penWidth;
1877 if (roundCap) {
1878 p3 += capSpace;
1879 p4 += capSpace;
1880 } else if (squareCap) {
1881 QVector2D c3 = p3 + capSpace;
1882 QVector2D c4 = p4 + capSpace;
1883 ret.append({{p3, e, c3}, -1, {1, 1, 0}, true});
1884 ret.append({{c3, e, c4}, -1, {1, 1, 0}, true});
1885 ret.append({{p4, e, c4}, -1, {1, 1, 0}, true});
1886 }
1887 }
1888
1889 if (element.isLine()) {
1890 ret.append({{p1, p2, p3}, i, {0,1,0}, false});
1891 ret.append({{p2, p3, p4}, i, {0.5,1,0}, false});
1892 } else {
1893 bool controlPointOnRight = determinant(s, c, e) > 0;
1894 QVector2D controlPointOffset = (startNormal + endNormal).normalized() * penWidth;
1895 QVector2D p5 = controlPointOnRight ? c - controlPointOffset : c + controlPointOffset;
1896 ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, i));
1897 }
1898
1899 if (!endMiter && nextElement) {
1900 // inside of join (opposite of bevel) is defined by
1901 // triangle s, e, next.e
1902 QVector2D outer1, outer2;
1903 const auto &np = nextElement->isLine() ? nextElement->endPoint() : nextElement->controlPoint();
1904 bool innerOnRight = endBisectorPointsRight;
1905 auto nextNormal = calcNormalVector(e, np).normalized() * penWidth / 2;
1906
1907 if (innerOnRight) {
1908 outer1 = e - 2 * endNormal;
1909 outer2 = e + 2 * nextNormal;
1910 } else {
1911 outer1 = e + 2 * endNormal;
1912 outer2 = e - 2 * nextNormal;
1913 }
1914
1915 if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
1916 ret.append({{outer1, e, outer2}, -1, {1,1,0}, false});
1917 } else if (roundJoin) {
1918 ret.append({{outer1, e, outer2}, i, {1,1,0}, false});
1919 QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penWidth / 2;
1920 if (!innerOnRight)
1921 nn = -nn;
1922 ret.append({{outer1, outer1 + nn, outer2}, i, {1,1,0}, false});
1923 ret.append({{outer1 + nn, outer2, outer2 + nn}, i, {1,1,0}, false});
1924
1925 } else if (miterJoin) {
1926 QVector2D outer = innerOnRight ? e - endBisector * 2 : e + endBisector * 2;
1927 ret.append({{outer1, e, outer}, -2, {1,1,0}, false});
1928 ret.append({{outer, e, outer2}, -2, {1,1,0}, false});
1929 }
1930 }
1931 }
1932 subStart = subEnd + 1;
1933 }
1934 return ret;
1935}
1936
1937};
1938
1939QVector<QSGGeometryNode *> QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData, NodeList *debugNodes)
1940{
1942 const QColor &color = pathData.pen.color();
1943
1944 auto *node = new QQuickShapeStrokeNode;
1945
1947
1948 float miterLimit = pathData.pen.miterLimit();
1949 float penWidth = pathData.pen.widthF();
1950
1951 auto thePath = pathData.strokePath;
1952 auto triangles = customTriangulator2(thePath, penWidth, pathData.pen.joinStyle(), pathData.pen.capStyle(), miterLimit);
1953
1954 auto addCurveTriangle = [&](const QuadPath::Element &element, const QVector2D &p0, const QVector2D &p1, const QVector2D &p2){
1955
1956 if (element.isLine()) {
1957 node->appendTriangle(p0, p1, p2,
1958 element.startPoint(), element.endPoint());
1959 } else {
1960 node->appendTriangle(p0, p1, p2,
1961 element.startPoint(), element.controlPoint(), element.endPoint());
1962 }
1963 wfVertices.append({p0.x(), p0.y(), 1.0f, 0.0f, 0.0f});
1964 wfVertices.append({p1.x(), p1.y(), 0.0f, 1.0f, 0.0f});
1965 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f});
1966 };
1967
1968 // TESTING
1969 auto addBevelTriangle = [&](const QVector2D &p0, const QVector2D &p1, const QVector2D &p2)
1970 {
1971 QVector2D fp1 = p1 + (p0 - p1) / 2;
1972 QVector2D fp2 = p1 + (p2 - p1) / 2;
1973 QVector2D fcp = p1; // does not matter for line
1974
1975 // That describes a path that passes through those points. We want the stroke
1976 // edge, so we need to shift everything down by the stroke offset
1977
1978 QVector2D nn = calcNormalVector(p0, p2);
1979 if (determinant(p0, p1, p2) < 0)
1980 nn = -nn;
1981 float delta = penWidth / 2;
1982
1983 QVector2D offset = nn.normalized() * delta;
1984 fp1 += offset;
1985 fp2 += offset;
1986 fcp += offset;
1987
1988 node->appendTriangle(p0, p1, p2, fp1, fp2);
1989
1990 wfVertices.append({p0.x(), p0.y(), 1.0f, 0.0f, 0.0f});
1991 wfVertices.append({p1.x(), p1.y(), 0.0f, 1.0f, 0.0f});
1992 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f});
1993 //qDebug() << "bev" << p1 << p2 << p3;
1994 };
1995
1996 for (const auto &triangle : triangles) {
1997 if (triangle.pathElementIndex < 0) {
1998 // TODO: improve bevel logic
1999 addBevelTriangle(triangle.points[0], triangle.points[1], triangle.points[2]);
2000 continue;
2001 }
2002 const auto &element = thePath.elementAt(triangle.pathElementIndex);
2003 addCurveTriangle(element, triangle.points[0], triangle.points[1], triangle.points[2]);
2004 }
2005
2006 auto indexCopy = node->uncookedIndexes(); // uncookedIndexes get delete on cooking
2007
2008 node->setColor(color);
2009 node->setStrokeWidth(pathData.pen.widthF());
2010 node->cookGeometry();
2011 m_rootNode->appendChildNode(node);
2012 ret.append(node);
2013
2014
2015 const bool wireFrame = debugVisualization() & DebugWireframe;
2016 if (wireFrame) {
2017 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
2018
2019 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
2020 wfVertices.size(),
2021 indexCopy.size(),
2023 wfNode->setGeometry(wfg);
2024
2026 memcpy(wfg->indexData(),
2027 indexCopy.data(),
2028 indexCopy.size() * wfg->sizeOfIndex());
2029 memcpy(wfg->vertexData(),
2030 wfVertices.data(),
2031 wfg->vertexCount() * wfg->sizeOfVertex());
2032
2033 m_rootNode->appendChildNode(wfNode);
2034 debugNodes->append(wfNode);
2035 }
2036
2037
2038 return ret;
2039}
2040
2041//TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
2042// elements
2043static void handleOverlap(QuadPath &path, qsizetype e1, qsizetype e2, int recursionLevel = 0)
2044{
2045 if (!isOverlap(path, e1, e2)) {
2046 return;
2047 }
2048
2049 if (recursionLevel > 8) {
2050 qDebug() << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
2051 return;
2052 }
2053
2054 if (path.elementAt(e1).childCount() > 1) {
2055 // qDebug() << "Case 1, recursion level" << recursionLevel;
2056 auto e11 = path.indexOfChildAt(e1, 0);
2057 auto e12 = path.indexOfChildAt(e1, 1);
2058 handleOverlap(path, e11, e2, recursionLevel + 1);
2059 handleOverlap(path, e12, e2, recursionLevel + 1);
2060 } else if (path.elementAt(e2).childCount() > 1) {
2061 // qDebug() << "Case 2, recursion level" << recursionLevel;
2062 auto e21 = path.indexOfChildAt(e2, 0);
2063 auto e22 = path.indexOfChildAt(e2, 1);
2064 handleOverlap(path, e1, e21, recursionLevel + 1);
2065 handleOverlap(path, e1, e22, recursionLevel + 1);
2066 } else {
2067 path.splitElementAt(e1);
2068 auto e11 = path.indexOfChildAt(e1, 0);
2069 auto e12 = path.indexOfChildAt(e1, 1);
2070 bool overlap1 = isOverlap(path, e11, e2);
2071 bool overlap2 = isOverlap(path, e12, e2);
2072 // qDebug() << "actual split, recursion level" << recursionLevel << "new overlaps" <<
2073 // overlap1 << overlap2;
2074
2075 if (!overlap1 && !overlap2)
2076 return; // no more overlap: success!
2077
2078 // We need to split more:
2079 if (path.elementAt(e2).isLine()) {
2080 // Splitting a line won't help, so we just split e1 further
2081 if (overlap1)
2082 handleOverlap(path, e11, e2, recursionLevel + 1);
2083 if (overlap2)
2084 handleOverlap(path, e12, e2, recursionLevel + 1);
2085 } else {
2086 // See if splitting e2 works:
2087 path.splitElementAt(e2);
2088 auto e21 = path.indexOfChildAt(e2, 0);
2089 auto e22 = path.indexOfChildAt(e2, 1);
2090 if (overlap1) {
2091 handleOverlap(path, e11, e21, recursionLevel + 1);
2092 handleOverlap(path, e11, e22, recursionLevel + 1);
2093 }
2094 if (overlap2) {
2095 handleOverlap(path, e12, e21, recursionLevel + 1);
2096 handleOverlap(path, e12, e22, recursionLevel + 1);
2097 }
2098 }
2099 }
2100}
2101
2102// Test if element contains a start point of another element
2103static void handleOverlap(QuadPath &path, qsizetype e1, const QVector2D vertex,
2104 int recursionLevel = 0)
2105{
2106 // First of all: Ignore the next element: it trivially overlaps (maybe not necessary: we do check for strict containment)
2107 if (vertex == path.elementAt(e1).endPoint() || !isOverlap(path, e1, vertex))
2108 return;
2109 if (recursionLevel > 8) {
2110 qDebug() << "Vertex overlap: recursion level" << recursionLevel << "aborting!";
2111 return;
2112 }
2113
2114 // Don't split if we're already split
2115 if (path.elementAt(e1).childCount() == 0)
2116 path.splitElementAt(e1);
2117
2118 handleOverlap(path, path.indexOfChildAt(e1, 0), vertex, recursionLevel + 1);
2119 handleOverlap(path, path.indexOfChildAt(e1, 1), vertex, recursionLevel + 1);
2120}
2121
2122void QQuickShapeCurveRenderer::solveOverlaps(QuadPath &path)
2123{
2124 for (qsizetype i = 0; i < path.elementCount(); i++) {
2125 auto &element = path.elementAt(i);
2126 // only concave curve overlap is problematic, as long as we don't allow self-intersecting curves
2127 if (element.isLine() || element.isConvex())
2128 continue;
2129
2130 for (qsizetype j = 0; j < path.elementCount(); j++) {
2131 if (i == j)
2132 continue; // Would be silly to test overlap with self
2133 auto &other = path.elementAt(j);
2134 if (!other.isConvex() && !other.isLine() && j < i)
2135 continue; // We have already tested this combination, so no need to test again
2136 handleOverlap(path, i, j);
2137 }
2138 }
2139
2140 static const int handleConcaveJoint = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_CONCAVE_JOINT");
2141 if (handleConcaveJoint) {
2142 // Note that the joint between two non-concave elements can also be concave, so we have to
2143 // test all convex elements to see if there is a vertex in any of them. We could do it the other way
2144 // by identifying concave joints, but then we would have to know which side is the inside
2145 // TODO: optimization potential! Maybe do that at the same time as we identify concave curves?
2146
2147 // We do this in a separate loop, since the triangle/triangle test above is more expensive, and
2148 // if we did this first, there would be more triangles to test
2149 for (qsizetype i = 0; i < path.elementCount(); i++) {
2150 auto &element = path.elementAt(i);
2151 if (!element.isConvex())
2152 continue;
2153
2154 for (qsizetype j = 0; j < path.elementCount(); j++) {
2155 // We only need to check one point per element, since all subpaths are closed
2156 // Could do smartness to skip elements that cannot overlap, but let's do it the easy way first
2157 if (i == j)
2158 continue;
2159 const auto &other = path.elementAt(j);
2160 handleOverlap(path, i, other.startPoint());
2161 }
2162 }
2163 }
2164}
2165
bool consume(float length)
ElementCutter(const QuadPath::Element &element)
static QBezier fromPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
Definition qbezier_p.h:34
\inmodule QtCore
Definition qbytearray.h:57
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
Definition qhash.h:818
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1279
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1209
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
qsizetype length() const noexcept
Definition qlist.h:388
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
qsizetype count() const noexcept
Definition qlist.h:387
pointer data()
Definition qlist.h:414
void append(parameter_type t)
Definition qlist.h:441
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
\inmodule QtGui
\inmodule QtGui
void setFillRule(Qt::FillRule fillRule)
Sets the fill rule of the painter path to the given fillRule.
\inmodule QtGui
Definition qpen.h:25
qreal widthF() const
Returns the pen width with floating point precision.
Definition qpen.cpp:598
QList< qreal > dashPattern() const
Returns the dash pattern of this pen.
Definition qpen.cpp:420
qreal dashOffset() const
Returns the dash offset for the pen.
Definition qpen.cpp:506
Qt::PenStyle style() const
Returns the pen style.
Definition qpen.cpp:385
\inmodule QtCore\reentrant
Definition qpoint.h:214
The QPolygonF class provides a list of points using floating point precision.
Definition qpolygon.h:96
QGradientStops gradientStops() const
void setGradientType(QQuickAbstractPathRenderer::FillGradientType type)
void beginSync(int totalCount, bool *countChanged) override
static Q_QUICKSHAPES_PRIVATE_EXPORT int debugVisualization()
static Q_QUICKSHAPES_PRIVATE_EXPORT void setDebugVisualization(int options)
void setFillGradient(int index, QQuickShapeGradient *gradient) override
void endSync(bool async) override
void setStrokeWidth(int index, qreal w) override
void setCapStyle(int index, QQuickShapePath::CapStyle capStyle) override
void setStrokeColor(int index, const QColor &color) override
void setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit) override
void setFillColor(int index, const QColor &color) override
void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle, qreal dashOffset, const QVector< qreal > &dashPattern) override
void setPath(int index, const QQuickPath *path) override
void setAsyncCallback(void(*)(void *), void *) override
void setFillRule(int index, QQuickShapePath::FillRule fillRule) override
\inmodule QtCore\reentrant
Definition qrect.h:483
void setGeometry(QSGGeometry *geometry)
Sets the geometry of this node to geometry.
Definition qsgnode.cpp:762
The QSGGeometryNode class is used for all rendered content in the scene graph.
Definition qsgnode.h:191
void setMaterial(QSGMaterial *material)
Sets the material of this geometry node to material.
Definition qsgnode.cpp:925
The QSGGeometry class provides low-level storage for graphics primitives in the \l{Qt Quick Scene Gra...
Definition qsggeometry.h:15
void setDrawingMode(unsigned int mode)
Sets the mode to be used for drawing this geometry.
void * indexData()
Returns a pointer to the raw index data of this geometry object.
int sizeOfVertex() const
Returns the size in bytes of one vertex.
void * vertexData()
Returns a pointer to the raw vertex data of this geometry object.
int sizeOfIndex() const
Returns the byte size of the index type.
int vertexCount() const
Returns the number of vertices in this geometry object.
The QSGMaterialShader class represents a graphics API independent shader program.
virtual bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
void setShaderFileName(Stage stage, const QString &filename)
Sets the filename for the shader for the specified stage.
The QSGMaterial class encapsulates rendering state for a shader program.
Definition qsgmaterial.h:15
virtual QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const =0
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
virtual int compare(const QSGMaterial *other) const
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
virtual QSGMaterialType * type() const =0
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
void setFlag(Flags flags, bool on=true)
Sets the flags flags on this material if on is true; otherwise clears the attribute.
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
void setFlag(Flag, bool=true)
Sets the flag f on this node if enabled is true; otherwise clears the flag.
Definition qsgnode.cpp:584
RenderMode
\value RenderMode2D Normal 2D rendering \value RenderMode2DNoDepthBuffer Normal 2D rendering with dep...
\inmodule QtCore
const_iterator cend() const noexcept
Definition qset.h:142
const float * vertices() const
void process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints hints)
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:502
QVector2D normalized() const noexcept
Returns the normalized unit vector form of this vector.
Definition qvectornd.h:529
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:501
static constexpr float dotProduct(QVector2D v1, QVector2D v2) noexcept
Returns the dot product of v1 and v2.
Definition qvectornd.h:604
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
Definition qvectornd.h:505
constexpr QPointF toPointF() const noexcept
Returns the QPointF form of this 2D vector.
Definition qvectornd.h:628
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
Definition qvectornd.h:504
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:330
QVector2D pointAtFraction(float t) const
qsizetype indexOfChild(qsizetype childNumber) const
QVector3D uvForPoint(QVector2D p) const
qsizetype elementCount() const
static QuadPath fromPainterPath(const QPainterPath &path)
QuadPath flattened() const
void splitElementAt(qsizetype index)
qsizetype elementCountRecursive() const
QPainterPath toPainterPath() const
QuadPath subPathsClosed() const
Qt::FillRule fillRule() const
QuadPath dashed(qreal lineWidth, const QList< qreal > &dashPattern, qreal dashOffset=0) const
friend QDebug operator<<(QDebug, const QuadPath &)
QRectF controlPointRect() const
void iterateElements(Func &&lambda)
bool contains(const QVector2D &v) const
Element & elementAt(qsizetype i)
QPixmap p2
QPixmap p1
[0]
qSwap(pi, e)
double e
QSet< QString >::iterator it
else opt state
[0]
Combined button and popup list for selecting options.
@ SolidLine
PenJoinStyle
@ BevelJoin
@ RoundJoin
@ WindingFill
@ OddEvenFill
QTextStream & endl(QTextStream &stream)
Writes '\n' to the stream and flushes the stream.
PenCapStyle
@ SquareCap
@ RoundCap
std::pair< T1, T2 > QPair
static struct AttrInfo attrs[]
EGLStreamKHR stream
bool qIsFinite(qfloat16 f) noexcept
Definition qfloat16.h:239
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:243
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
#define qDebug
[1]
Definition qlogging.h:160
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
n varying highp vec2 A
GLint GLfloat GLfloat GLfloat v2
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLint GLint GLint GLint GLint x
[0]
const GLfloat * m
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint divisor
GLuint GLfloat GLfloat GLfloat GLfloat y1
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLint GLfloat GLfloat v1
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint start
GLenum GLuint GLintptr offset
GLboolean GLboolean g
GLint GLfloat GLfloat GLfloat GLfloat v3
GLfloat n
GLuint GLfloat GLfloat y0
GLsizei GLenum const void * indices
GLint y
GLfixed GLfixed GLint GLint GLfixed points
GLuint res
const GLubyte * c
GLfixed GLfixed GLfixed y2
GLdouble GLdouble t
Definition qopenglext.h:243
GLsizei const GLchar *const * path
GLint GLenum GLboolean normalized
Definition qopenglext.h:752
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
GLubyte * pattern
const QVectorPath & qtVectorPathForPath(const QPainterPath &path)
constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
static qreal dot(const QPointF &a, const QPointF &b)
static bool isLine(const QBezier &bezier)
static const qreal epsilon
static void printElement(QDebug stream, const QuadPath::Element &element)
static QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
static QVector2D calcNormalVector(QVector2D a, QVector2D b)
static void handleOverlap(QuadPath &path, qsizetype e1, qsizetype e2, int recursionLevel=0)
static QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
#define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN
static float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
void iteratePath(const QuadPath &path, int index, Func &&lambda)
Q_QUICKSHAPES_PRIVATE_EXPORT void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit=0.01)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static const struct TessellationModeTab triangles[]
#define QStringLiteral(str)
#define s3
#define s2
#define sp
#define t2
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
Q_GUI_EXPORT QTriangleSet qTriangulate(const qreal *polygon, int count, uint hint, const QTransform &matrix, bool allowUintIndices)
unsigned int quint32
Definition qtypes.h:45
ptrdiff_t qsizetype
Definition qtypes.h:70
double qreal
Definition qtypes.h:92
MyCustomStruct c2
QDate d1(1995, 5, 17)
[0]
QDate d2(1995, 5, 20)
QSharedPointer< T > other(t)
[5]
this swap(other)
QString dir
[11]
The QSGGeometry::AttributeSet describes how the vertices in a QSGGeometry are built up.
Definition qsggeometry.h:73
The QSGGeometry::Attribute describes a single vertex attribute in a QSGGeometry.
Definition qsggeometry.h:58
static Attribute createWithAttributeType(int pos, int tupleSize, int primitiveType, AttributeType attributeType)
Creates a new QSGGeometry::Attribute for attribute register pos with tupleSize.
The QSGMaterialType class is used as a unique type token in combination with QSGMaterial.
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent