1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsgcurvestrokenode_p.h"
#include "qsgcurvestrokenode_p_p.h"
QT_BEGIN_NAMESPACE
/*!
\class QSGCurveStrokeNode
\inmodule QtQuick
\internal
*/
QSGCurveStrokeNode::QSGCurveStrokeNode()
{
setFlag(OwnsGeometry, true);
qsgnode_set_description(this, QLatin1StringView("curve stroke"));
setGeometry(new QSGGeometry(attributes(), 0, 0));
// defer updateMaterial() until later
}
void QSGCurveStrokeNode::QSGCurveStrokeNode::updateMaterial()
{
const bool expandingInVertexShader = m_cosmetic || expandingStrokeEnabled();
m_material.reset(new QSGCurveStrokeMaterial(this, expandingInVertexShader));
setMaterial(m_material.data());
}
// Take the start, control and end point of a curve and return the points A, B, C
// representing the curve as Q(s) = A*s*s + B*s + C
std::array<QVector2D, 3> QSGCurveStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
{
QVector2D a = p[0] - 2*p[1] + p[2];
QVector2D b = 2*p[1] - 2*p[0];
QVector2D c = p[0];
return {a, b, c};
}
/*!
Append a triangle with \a vtx corners within which the fragment shader will
draw the visible part of a quadratic curve from ctl[0] to ctl[2] with
control point ctl[1] (AKA a quadratic Bézier curve with 3 control points).
The \a normal vectors are used in the vertex shader to expand the triangle
according to its stroke width: therefore, it's ok for the triangle to be
degenerate, and get expanded to size in the vertex shader. Normals are
usually unit vectors, but it's also ok for some to have larger magnitudes,
to handle the case when miter corners need to be extended proportionally
farther as stroke width increases.
*/
void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &vtx,
const std::array<QVector2D, 3> &ctl,
const std::array<QVector2D, 3> &normal,
const std::array<float, 3> &extrusions)
{
auto abc = curveABC(ctl);
int currentVertex = m_uncookedVertexes.count();
for (int i = 0; i < 3; ++i) {
m_uncookedVertexes.append( { vtx[i].x(), vtx[i].y(),
abc[0].x(), abc[0].y(), abc[1].x(), abc[1].y(), abc[2].x(), abc[2].y(),
normal[i].x(), normal[i].y(), extrusions[i] } );
}
m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
}
/*!
Append a triangle with \a vtx corners within which the fragment shader will
draw the visible part of a line from ctl[0] to ctl[2].
The \a normal vectors are used in the vertex shader to expand the triangle
according to its stroke width: therefore, it's ok for the triangle to be
degenerate, and get expanded to size in the vertex shader. Normals are
usually unit vectors, but it's also ok for some to have larger magnitudes,
to handle the case when miter corners need to be extended proportionally
farther as stroke width increases.
*/
void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &vtx,
const std::array<QVector2D, 2> &ctl,
const std::array<QVector2D, 3> &normal,
const std::array<float, 3> &extrusions)
{
// We could reduce this to a linear equation by setting A to (0,0).
// However, then we cannot use the cubic solution and need an additional
// code path in the shader. The following formulation looks more complicated
// but allows to always use the cubic solution.
auto A = ctl[1] - ctl[0];
auto B = QVector2D(0., 0.);
auto C = ctl[0];
int currentVertex = m_uncookedVertexes.count();
for (int i = 0; i < 3; ++i) {
m_uncookedVertexes.append( { vtx[i].x(), vtx[i].y(),
A.x(), A.y(), B.x(), B.y(), C.x(), C.y(),
normal[i].x(), normal[i].y(), extrusions[i] } );
}
m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
}
void QSGCurveStrokeNode::cookGeometry()
{
updateMaterial(); // by now, setCosmeticStroke has been called if necessary
QSGGeometry *g = geometry();
if (g->indexType() != QSGGeometry::UnsignedIntType) {
g = new QSGGeometry(attributes(),
m_uncookedVertexes.size(),
m_uncookedIndexes.size(),
QSGGeometry::UnsignedIntType);
setGeometry(g);
} else {
g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
}
g->setDrawingMode(QSGGeometry::DrawTriangles);
memcpy(g->vertexData(),
m_uncookedVertexes.constData(),
g->vertexCount() * g->sizeOfVertex());
memcpy(g->indexData(),
m_uncookedIndexes.constData(),
g->indexCount() * g->sizeOfIndex());
m_uncookedIndexes.clear();
m_uncookedIndexes.squeeze();
m_uncookedVertexes.clear();
m_uncookedVertexes.squeeze();
}
const QSGGeometry::AttributeSet &QSGCurveStrokeNode::attributes()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), //vertexCoord
QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //A
QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //B
QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //C
QSGGeometry::Attribute::createWithAttributeType(4, 3, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //normalExt
};
static QSGGeometry::AttributeSet attrs = { 5, sizeof(StrokeVertex), data };
return attrs;
}
// TODO remove when we consider the expanding-stroke shader to be stable for full-time use
bool QSGCurveStrokeNode::expandingStrokeEnabled()
{
static const bool ret = qEnvironmentVariableIntValue("QT_QUICKSHAPES_STROKE_EXPANDING");
return ret;
}
QT_END_NAMESPACE
|