aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgcurvestrokenode.cpp
blob: be92c888380b66ab70662c6f8a16e40e42326d65 (plain)
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