summaryrefslogtreecommitdiff
path: root/src/renderer/painter_symbol.cpp
blob: a2238a35449e622cff2397980314809c9eaa43d8 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <mbgl/renderer/painter.hpp>
#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/util/math.hpp>
#include <cmath>

namespace mbgl {

void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc,
                           const Tile::ID &/*id*/) {
    // Abort early.
    if (pass == Opaque) {
        return;
    }

    const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>();


    if (bucket.hasTextData()) {
        mat4 exMatrix;
        matrix::copy(exMatrix, projMatrix);
        if (bucket.properties.placement == PlacementType::Line) {
            matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
        }

        // If layerStyle.size > bucket.info.fontSize then labels may collide
        float fontSize = std::fmin(properties.text.size, bucket.properties.text.max_size);
        matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);

        useProgram(textShader->program);
        textShader->setMatrix(matrix);
        textShader->setExtrudeMatrix(exMatrix);

        GlyphAtlas &glyphAtlas = *map.getGlyphAtlas();
        glyphAtlas.bind();
        textShader->setTextureSize(
            {{static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height)}});

        // Convert the -pi..pi to an int8 range.
        float angle = std::round((map.getState().getAngle()) / M_PI * 128);

        // adjust min/max zooms for variable font sies
        float zoomAdjust = log(fontSize / bucket.properties.text.max_size) / log(2);

        textShader->setAngle((int32_t)(angle + 256) % 256);
        textShader->setFlip(bucket.properties.placement == PlacementType::Line ? 1 : 0);
        textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) *
                            10); // current zoom level

        // Label fading
        const timestamp duration = 300_milliseconds;
        const timestamp currentTime = util::now();

        std::deque<FrameSnapshot> &history = frameHistory.history;

        // Remove frames until only one is outside the duration, or until there are only three
        while (history.size() > 3 && history[1].t + duration < currentTime) {
            history.pop_front();
        }

        if (history[1].t + duration < currentTime) {
            history[0].z = history[1].z;
        }

        assert("there should never be less than three frames in the history" &&
               (history.size() >= 3));

        // Find the range of zoom levels we want to fade between
        float startingZ = history.front().z;
        const FrameSnapshot lastFrame = history.back();
        float endingZ = lastFrame.z;
        float lowZ = std::fmin(startingZ, endingZ);
        float highZ = std::fmax(startingZ, endingZ);

        // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one
        // duration
        float zoomDiff = endingZ - history[1].z, timeDiff = lastFrame.t - history[1].t;
        float fadedist = zoomDiff / (timeDiff / duration);

#if defined(DEBUG)
//        if (std::isnan(fadedist))
//            fprintf(stderr, "fadedist should never be NaN\n");
#endif

        // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
        // bump is how much farther it would have been if it had continued zooming at the same rate
        float bump = (currentTime - lastFrame.t) / duration * fadedist;

        textShader->setFadeDist(fadedist * 10);
        textShader->setMinFadeZoom(std::floor(lowZ * 10));
        textShader->setMaxFadeZoom(std::floor(highZ * 10));
        textShader->setFadeZoom((map.getState().getNormalizedZoom() + bump) * 10);

        // This defines the gamma around the SDF cutoff value.
        const float sdfGamma = 1.0f / 10.0f;

        // Our signed distance fields are scaled so that 1 pixel is scaled to 32 pixels.
        // Our cutoff between positive and negative values is hard coded to 64 (== 2 pixels).
        // This means that our 6/8 of the value range lies outside the glyph outline.
        const float sdfOffset = (256.0f - 64.0f) / 32.0f;

        // Currently, all of our fonts are rendered with a font size of 24px.
        const float sdfFontSize = 24.0f;

        // The default gamma value has to be adjust for the current pixelratio so that we're not
        // drawing
        // blurry font on retina screens.
        const float gamma = sdfGamma * sdfFontSize / fontSize / map.getState().getPixelRatio();

        // We're drawing in the translucent pass which is bottom-to-top, so we need
        // to draw the halo first.
        if (properties.text.halo_color[3] > 0.0f) {
            const float haloWidth = util::clamp(
                (sdfOffset - properties.text.halo_width / (fontSize / sdfFontSize)) / 8.0f, 0.0f,
                1.0f);

            if (properties.text.halo_blur != 0.0f) {
                // We are converting the halo_blur value to current screen pixels.
                // Then we're dividing it by two because the gamma value is added/subtracted into
                // both
                // directions in the shader, but the halo_blur describes the entire width of the
                // blur.
                // Note that this does *not* have to be adjusted for retina screens, because we want
                // the
                // same blur width when we explicitly specify one.
                textShader->setGamma((properties.text.halo_blur / (fontSize / sdfFontSize)) / 8.0f /
                                     2.0f);
            } else {
                textShader->setGamma(sdfGamma);
            }

            if (properties.text.opacity < 1.0f) {
                Color color = properties.text.halo_color;
                color[0] *= properties.text.opacity;
                color[1] *= properties.text.opacity;
                color[2] *= properties.text.opacity;
                color[3] *= properties.text.opacity;
                textShader->setColor(color);
            } else {
                textShader->setColor(properties.text.halo_color);
            }
            textShader->setBuffer(haloWidth);
            glDepthRange(strata, 1.0f);
            bucket.drawGlyphs(*textShader);
        }

        if (properties.text.color[3] > 0.0f) {
            // Then, we draw the text over the halo
            textShader->setGamma(gamma);
            if (properties.text.opacity < 1.0f) {
                Color color = properties.text.color;
                color[0] *= properties.text.opacity;
                color[1] *= properties.text.opacity;
                color[2] *= properties.text.opacity;
                color[3] *= properties.text.opacity;
                textShader->setColor(color);
            } else {
                textShader->setColor(properties.text.color);
            }
            textShader->setBuffer((256.0f - 64.0f) / 256.0f);
            glDepthRange(strata + strata_epsilon, 1.0f);
            bucket.drawGlyphs(*textShader);
        }
    }

    if (bucket.hasIconData()) {
        mat4 exMatrix;
        matrix::copy(exMatrix, projMatrix);

        const float angleOffset =
            bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map
                ? map.getState().getAngle()
                : 0;

        if (angleOffset) {
            matrix::rotate_z(exMatrix, exMatrix, angleOffset);
        }

        // If layerStyle.size > bucket.info.fontSize then labels may collide
        const float fontSize = properties.icon.size != 0 ? properties.icon.size : bucket.properties.icon.max_size;
        const float fontScale = fontSize / 1.0f;
        matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);

        useProgram(iconShader->program);
        iconShader->setMatrix(matrix);
        iconShader->setExtrudeMatrix(exMatrix);

        SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
        spriteAtlas.bind(map.getState().isChanging() || bucket.properties.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1);
        iconShader->setTextureSize(
            {{static_cast<float>(spriteAtlas.getWidth()), static_cast<float>(spriteAtlas.getHeight())}});

        // Convert the -pi..pi to an int8 range.
        const float angle = std::round((map.getState().getAngle()) / M_PI * 128);

        // adjust min/max zooms for variable font sies
        float zoomAdjust = log(fontSize / bucket.properties.icon.max_size) / log(2);

        iconShader->setAngle((int32_t)(angle + 256) % 256);
        iconShader->setFlip(bucket.properties.placement == PlacementType::Line ? 1 : 0);
        iconShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) *
                            10); // current zoom level

        iconShader->setFadeDist(0 * 10);
        iconShader->setMinFadeZoom(map.getState().getNormalizedZoom() * 10);
        iconShader->setMaxFadeZoom(map.getState().getNormalizedZoom() * 10);
        iconShader->setFadeZoom(map.getState().getNormalizedZoom() * 10);
        iconShader->setOpacity(properties.icon.opacity);

        glDepthRange(strata, 1.0f);
        bucket.drawIcons(*iconShader);
    }
}
}