summaryrefslogtreecommitdiff
path: root/src/renderer/painter_text.cpp
blob: d9db72d66b3549cf680c9a552ad265ad24057b8b (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
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/text_bucket.hpp>
#include <llmr/style/style_layer.hpp>
#include <llmr/map/map.hpp>
#include <cmath>

using namespace llmr;

void Painter::renderText(TextBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) {
    // Abort early.
    if (pass == Opaque) return;
    if (!bucket.hasData()) return;

    const TextProperties &properties = layer_desc->getProperties<TextProperties>();
    if (!properties.enabled) return;

    mat4 exMatrix;
    matrix::copy(exMatrix, projMatrix);
    if (bucket.properties.path == TextPathType::Curve) {
        matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
    }

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

// TODO: figure out whether we actually need to account for this while painting; we might already have
// done this during label placement.
//    const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);

    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.max_size) / log(2);

    textShader->setAngle((int32_t)(angle + 256) % 256);
    textShader->setFlip(bucket.properties.path == TextPathType::Curve ? 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);

    // We're drawing in the translucent pass which is bottom-to-top, so we need
    // to draw the halo first.
    if (properties.halo_color[3] > 0.0f) {
        // TODO: Get rid of the 2.4 magic value. It is currently 24 / 10, with 24 being the font size
        // of the SDF glyphs.
        textShader->setGamma(properties.halo_blur * 2.4f / fontSize / map.getState().getPixelRatio());
        if (properties.opacity < 1.0f) {
            Color color = properties.halo_color;
            color[0] *= properties.opacity;
            color[1] *= properties.opacity;
            color[2] *= properties.opacity;
            color[3] *= properties.opacity;
            textShader->setColor(color);
        } else {
            textShader->setColor(properties.halo_color);
        }
        textShader->setBuffer(properties.halo_width);
        glDepthRange(strata, 1.0f);
        bucket.drawGlyphs(*textShader);
    }

    if (properties.color[3] > 0.0f) {
        // Then, we draw the text over the halo
        // TODO: Get rid of the 2.4 magic value. It is currently 24 / 10, with 24 being the font size
        // of the SDF glyphs.
        textShader->setGamma(2.4f / fontSize / map.getState().getPixelRatio());
        if (properties.opacity < 1.0f) {
            Color color = properties.color;
            color[0] *= properties.opacity;
            color[1] *= properties.opacity;
            color[2] *= properties.opacity;
            color[3] *= properties.opacity;
            textShader->setColor(color);
        } else {
            textShader->setColor(properties.color);
        }
        textShader->setBuffer((256.0f - 64.0f) / 256.0f);
        glDepthRange(strata + strata_epsilon, 1.0f);
        bucket.drawGlyphs(*textShader);
    }
}