summaryrefslogtreecommitdiff
path: root/vcl/opengl/LineRenderUtils.cxx
blob: e130fb93bb22d11a06f33036bcf138d27fabe95f (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */

#include <opengl/LineRenderUtils.hxx>
#include <opengl/VertexUtils.hxx>

namespace vcl
{

LineBuilder::LineBuilder(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices,
                Color nColor, GLfloat fTransparency,
                GLfloat fLineWidth, bool bUseAA)
    : mrVertices(rVertices)
    , mrIndices(rIndices)
    , mR(nColor.GetRed())
    , mG(nColor.GetGreen())
    , mB(nColor.GetBlue())
    , mA((1.0f - fTransparency) * 255.0f)
    , mfLineWidth(fLineWidth)
    , mfLineWidthAndAA(bUseAA ? fLineWidth : -fLineWidth)
    , mnInitialIndexSize(rIndices.size())
    , mbIncomplete(false)
{
}

void LineBuilder::appendLineSegment(const glm::vec2& rPoint1, const glm::vec2& rNormal1, GLfloat aExtrusion1,
                                    const glm::vec2& rPoint2, const glm::vec2& rNormal2, GLfloat aExtrusion2)
{
    GLuint zero = mrVertices.size();

    mrVertices.insert(mrVertices.end(), {
        {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal1.x, -rNormal1.y, -aExtrusion1, mfLineWidthAndAA}},
        {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal1.x,  rNormal1.y,  aExtrusion1, mfLineWidthAndAA}},
        {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal2.x, -rNormal2.y, -aExtrusion2, mfLineWidthAndAA}},
        {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal2.x,  rNormal2.y,  aExtrusion2, mfLineWidthAndAA}},
    });

    mrIndices.insert(mrIndices.end(), {
        zero + 0, zero + 1, zero + 2,
        zero + 2, zero + 1, zero + 3
    });

}

void LineBuilder::appendLine(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
{
    glm::vec2 aLineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
    glm::vec2 aNormal = vcl::vertex::perpendicular(aLineVector);

    appendLineSegment(rPoint1, aNormal, 1.0f,
                      rPoint2, aNormal, 1.0f);
}

void LineBuilder::appendAndConnectLinePoint(const glm::vec2& rPoint, const glm::vec2& aNormal, GLfloat aExtrusion)
{
    GLuint zero = mrVertices.size();

    mrVertices.insert(mrVertices.end(), {
        {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{-aNormal.x, -aNormal.y, -aExtrusion, mfLineWidthAndAA}},
        {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{ aNormal.x,  aNormal.y,  aExtrusion, mfLineWidthAndAA}},
    });

    if (mnInitialIndexSize == mrIndices.size())
    {
        mrIndices.insert(mrIndices.end(), {
            zero + 0, zero + 1
        });
        mbIncomplete = true;
    }
    else
    {
        if (mbIncomplete)
        {
            mrIndices.insert(mrIndices.end(), {
                                    zero + 0,
                zero + 0, zero - 1, zero + 1
            });
            mbIncomplete = false;
        }
        else
        {
            mrIndices.insert(mrIndices.end(), {
                zero - 2, zero - 1, zero + 0,
                zero + 0, zero - 1, zero + 1
            });
        }
    }
}

void LineBuilder::appendMiterJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
                                   glm::vec2 const& nextLineVector)
{
    // With miter join we calculate the extrusion vector by adding normals of
    // previous and next line segment. The vector shows the way but we also
    // need the length (otherwise the line will be deformed). Length factor is
    // calculated as dot product of extrusion vector and one of the normals.
    // The value we get is the inverse length (used in the shader):
    // length = line_width / dot(extrusionVector, normal)

    glm::vec2 normal(-prevLineVector.y, prevLineVector.x);

    glm::vec2 tangent = vcl::vertex::normalize(nextLineVector + prevLineVector);
    glm::vec2 extrusionVector(-tangent.y, tangent.x);
    GLfloat length = glm::dot(extrusionVector, normal);

    appendAndConnectLinePoint(point, extrusionVector, length);
}

void LineBuilder::appendBevelJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
                                   const glm::vec2& nextLineVector)
{
    // For bevel join we just add 2 additional vertices and use previous
    // line segment normal and next line segment normal as extrusion vector.
    // All the magic is done by the fact that we draw triangle strips, so we
    // cover the joins correctly.

    glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x);
    glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x);

    appendAndConnectLinePoint(point, prevNormal, 1.0f);
    appendAndConnectLinePoint(point, nextNormal, 1.0f);
}

void LineBuilder::appendRoundJoint(glm::vec2 const& point, const glm::vec2& prevLineVector,
                                   const glm::vec2& nextLineVector)
{
    // For round join we do a similar thing as in bevel, we add more intermediate
    // vertices and add normals to get extrusion vectors in the between the
    // both normals.

    // 3 additional extrusion vectors + normals are enough to make most
    // line joins look round. Ideally the number of vectors could be
    // calculated.

    glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x);
    glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x);

    glm::vec2 middle = vcl::vertex::normalize(prevNormal + nextNormal);
    glm::vec2 middleLeft  = vcl::vertex::normalize(prevNormal + middle);
    glm::vec2 middleRight = vcl::vertex::normalize(middle + nextNormal);

    appendAndConnectLinePoint(point, prevNormal, 1.0f);
    appendAndConnectLinePoint(point, middleLeft, 1.0f);
    appendAndConnectLinePoint(point, middle, 1.0f);
    appendAndConnectLinePoint(point, middleRight, 1.0f);
    appendAndConnectLinePoint(point, nextNormal, 1.0f);
}

void LineBuilder::appendRoundLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
{
    constexpr int nRoundCapIteration = 12;

    glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
    glm::vec2 normal(-lineVector.y, lineVector.x);
    glm::vec2 previousRoundNormal = normal;

    for (int nFactor = 1; nFactor <= nRoundCapIteration; nFactor++)
    {
        float angle = float(nFactor) * (M_PI / float(nRoundCapIteration));
        glm::vec2 roundNormal(normal.x * glm::cos(angle) - normal.y * glm::sin(angle),
                              normal.x * glm::sin(angle) + normal.y * glm::cos(angle));

        appendLineSegment(rPoint1, previousRoundNormal, 1.0f,
                          rPoint1, roundNormal, 1.0f);
        previousRoundNormal = roundNormal;
    }
}

void LineBuilder::appendSquareLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2)
{
    glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1);
    glm::vec2 normal(-lineVector.y, lineVector.x);

    glm::vec2 extrudedPoint = rPoint1 + -lineVector * (mfLineWidth / 2.0f);

    appendLineSegment(extrudedPoint, normal, 1.0f,
                      rPoint1,       normal, 1.0f);
}

} // end vcl

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */