diff --git a/include/mbgl/geometry/icon_buffer.hpp b/include/mbgl/geometry/icon_buffer.hpp index 346b3b5f0..bc9901afb 100644 --- a/include/mbgl/geometry/icon_buffer.hpp +++ b/include/mbgl/geometry/icon_buffer.hpp @@ -6,13 +6,13 @@ namespace mbgl { class IconVertexBuffer : public Buffer< - 4 + // int16 x/y coordinates per vertex (== 4 bytes) - 4 // uint16 x/y coordinates of icon in sprite (== 4 bytes) + 20 > { public: - typedef int16_t vertex_type; + static const double angleFactor; + + size_t add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array range, float maxzoom, float labelminzoom); - void add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty); }; } diff --git a/include/mbgl/renderer/symbol_bucket.hpp b/include/mbgl/renderer/symbol_bucket.hpp index b1917a93b..c2a8b14f3 100644 --- a/include/mbgl/renderer/symbol_bucket.hpp +++ b/include/mbgl/renderer/symbol_bucket.hpp @@ -4,6 +4,8 @@ #include "bucket.hpp" #include #include +#include +#include #include #include #include @@ -16,9 +18,6 @@ namespace mbgl { class Style; -class TextVertexBuffer; -class IconVertexBuffer; -class TriangleElementsBuffer; class TextShader; class IconShader; class DotShader; @@ -29,9 +28,14 @@ class GlyphAtlas; class GlyphStore; class FontStack; -class SymbolBucket : public Bucket { - typedef ElementGroup triangle_group_type; +class SymbolFeature { +public: + pbf geometry; + std::u32string label; + std::string sprite; +}; +class SymbolBucket : public Bucket { public: SymbolBucket( TextVertexBuffer &textVertexBuffer, @@ -57,6 +61,14 @@ public: void drawIcons(DotShader& shader); private: + + std::vector processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite); + void addFeature(const pbf &geom_pbf, const Shaping &shaping, const Rect &image); + void addFeature(const std::vector &line, const Shaping &shaping, const Rect &image); + + + + void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face); void addFeature(const pbf &geom_pbf, const GlyphPositions &face, const Shaping &shaping); @@ -65,18 +77,20 @@ public: const StyleBucketSymbol &properties; private: - TextVertexBuffer& textVertexBuffer; - IconVertexBuffer& iconVertexBuffer; - TriangleElementsBuffer& triangleElementsBuffer; Placement &placement; - const size_t text_vertex_start; - const size_t icon_vertex_start; - const size_t triangle_elements_start; - size_t icon_vertex_end = 0; - VertexArrayObject array; + struct { + TextVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector groups; + } text; + + struct { + IconVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector groups; + } icon; - std::vector triangleGroups; }; } diff --git a/include/mbgl/shader/icon_shader.hpp b/include/mbgl/shader/icon_shader.hpp index b97b3c2be..eab484387 100644 --- a/include/mbgl/shader/icon_shader.hpp +++ b/include/mbgl/shader/icon_shader.hpp @@ -11,30 +11,53 @@ public: void bind(char *offset); - void setImage(int32_t image); - void setColor(const std::array& color); - void setDimension(const std::array& dimension); - void setSize(float size); - void setRatio(float ratio); + void setExtrudeMatrix(const std::array& exmatrix); + void setAngle(float angle); + void setZoom(float zoom); + void setFlip(float flip); + void setFadeDist(float fadedist); + void setMinFadeZoom(float minfadezoom); + void setMaxFadeZoom(float maxfadezoom); + void setFadeZoom(float fadezoom); + void setOpacity(float opacity); private: int32_t a_pos = -1; + int32_t a_offset = -1; int32_t a_tex = -1; + int32_t a_angle = -1; + int32_t a_minzoom = -1; + int32_t a_maxzoom = -1; + int32_t a_rangeend = -1; + int32_t a_rangestart = -1; + int32_t a_labelminzoom = -1; - int32_t image = -1; - int32_t u_image = -1; + std::array exmatrix = {{}}; + int32_t u_exmatrix = -1; - std::array color = {{}}; - int32_t u_color = -1; + float angle = 0; + int32_t u_angle = -1; - std::array dimension = {{}}; - int32_t u_dimension = -1; + float zoom = 0; + int32_t u_zoom = -1; - float size = 0; - int32_t u_size = -1; + float flip = 0; + int32_t u_flip = -1; - float ratio = 0; - int32_t u_ratio = -1; + float fadedist = 0; + int32_t u_fadedist = -1; + + float minfadezoom = 0; + int32_t u_minfadezoom = -1; + + float maxfadezoom = 0; + int32_t u_maxfadezoom = -1; + + float fadezoom = 0; + int32_t u_fadezoom = -1; + + float opacity = 0; + int32_t u_opacity = -1; }; } diff --git a/include/mbgl/text/collision.hpp b/include/mbgl/text/collision.hpp index 31103a743..8911b3b4c 100644 --- a/include/mbgl/text/collision.hpp +++ b/include/mbgl/text/collision.hpp @@ -8,13 +8,12 @@ namespace mbgl { class Collision { public: - Collision(); + Collision(float zoom, float tileExtent, float tileSize, float placementDepth = 1); ~Collision(); - PlacementProperty place(const GlyphBoxes &boxes, - const CollisionAnchor &anchor, - float minPlacementScale, float maxPlacementScale, - float padding, bool horizontal, bool alwaysVisible); + PlacementProperty place(const GlyphBoxes &boxes, const CollisionAnchor &anchor, + float minPlacementScale, float maxPlacementScale, float padding, + bool horizontal, bool allowOverlap, bool ignorePlacement); float getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale, float maxPlacementScale, float pad); PlacementRange getPlacementRange(const GlyphBoxes &glyphs, @@ -24,8 +23,11 @@ public: bool horizontal, float padding); private: - void *cTree; void *hTree; + void *cTree; + const float tilePixelRatio; + const float zoom; + const float maxPlacementScale; }; } diff --git a/include/mbgl/util/rect.hpp b/include/mbgl/util/rect.hpp index a216b8e14..4e524b415 100644 --- a/include/mbgl/util/rect.hpp +++ b/include/mbgl/util/rect.hpp @@ -5,7 +5,8 @@ namespace mbgl { template struct Rect { - explicit Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {} + inline Rect() {} + inline Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {} T x = 0, y = 0; T w = 0, h = 0; diff --git a/src/geometry/icon_buffer.cpp b/src/geometry/icon_buffer.cpp index a25b2de06..c571dfa69 100644 --- a/src/geometry/icon_buffer.cpp +++ b/src/geometry/icon_buffer.cpp @@ -1,18 +1,35 @@ #include #include +#include #include -using namespace mbgl; +namespace mbgl { -void IconVertexBuffer::add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty) { +const double IconVertexBuffer::angleFactor = 128.0 / M_PI; + +size_t IconVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array range, float maxzoom, float labelminzoom) { + const size_t idx = index(); void *data = addElement(); - vertex_type *vertices = static_cast(data); - vertices[0] = x; - vertices[1] = y; + int16_t *shorts = static_cast(data); + shorts[0] /* pos */ = x; + shorts[1] /* pos */ = y; + shorts[2] /* offset */ = std::round(ox * 64); // use 1/64 pixels for placement + shorts[3] /* offset */ = std::round(oy * 64); + + uint8_t *ubytes = static_cast(data); + ubytes[8] /* labelminzoom */ = labelminzoom * 10; + ubytes[9] /* minzoom */ = minzoom * 10; // 1/10 zoom levels: z16 == 160. + ubytes[10] /* maxzoom */ = std::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. + ubytes[11] /* angle */ = (int16_t)std::round(angle * angleFactor) % 256; + ubytes[12] /* rangeend */ = util::max((int16_t)std::round(range[0] * angleFactor), (int16_t)0) % 256; + ubytes[13] /* rangestart */ = util::min((int16_t)std::round(range[1] * angleFactor), (int16_t)255) % 256; + + shorts[8] /* tex */ = tx; + shorts[9] /* tex */ = ty; + + return idx; +} - uint16_t *texture = static_cast(data); - texture[2] = tx; - texture[3] = ty; } diff --git a/src/renderer/symbol_bucket.cpp b/src/renderer/symbol_bucket.cpp index f09f87da0..03e5c91f2 100644 --- a/src/renderer/symbol_bucket.cpp +++ b/src/renderer/symbol_bucket.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -20,13 +21,8 @@ SymbolBucket::SymbolBucket(TextVertexBuffer &textVertexBuffer, IconVertexBuffer TriangleElementsBuffer &triangleElementsBuffer, const StyleBucketSymbol &properties, Placement &placement) : properties(properties), - textVertexBuffer(textVertexBuffer), - iconVertexBuffer(iconVertexBuffer), - triangleElementsBuffer(triangleElementsBuffer), - placement(placement), - text_vertex_start(textVertexBuffer.index()), - icon_vertex_start(iconVertexBuffer.index()), - triangle_elements_start(triangleElementsBuffer.index()) {} + placement(placement) + {} void SymbolBucket::render(Painter &painter, std::shared_ptr layer_desc, const Tile::ID &id) { @@ -39,11 +35,11 @@ bool SymbolBucket::hasData() const { bool SymbolBucket::hasTextData() const { - return !triangleGroups.empty(); + return !text.groups.empty(); } bool SymbolBucket::hasIconData() const { - return icon_vertex_end > 0; + return !icon.groups.empty() > 0; } void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname, @@ -61,74 +57,72 @@ void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname, } } -void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, - const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, GlyphAtlas &glyphAtlas, - GlyphStore &glyphStore) { +std::vector SymbolBucket::processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite) { const bool text = properties.text.field.size(); const bool icon = properties.icon.image.size(); + std::vector features; + + if (!text && !icon) { + return features; + } + util::utf8_to_utf32 ucs4conv; - std::vector> labels; - // Determine and load glyph ranges - { - std::set ranges; + std::set ranges; - FilteredVectorTileLayer filtered_layer(layer, filter); - for (const pbf &feature_pbf : filtered_layer) { - VectorTileFeature feature {feature_pbf, layer}; + FilteredVectorTileLayer filtered_layer(layer, filter); + for (const pbf &feature_pbf : filtered_layer) { + const VectorTileFeature feature {feature_pbf, layer}; - if (text) { - std::string u8string = util::replaceTokens(properties.text.field, feature.properties); + SymbolFeature ft; - auto& convert = std::use_facet>(std::locale()); - if (properties.text.transform == TextTransformType::Uppercase) { - convert.toupper(&u8string[0], &u8string[0] + u8string.size()); - } else if (properties.text.transform == TextTransformType::Lowercase) { - convert.tolower(&u8string[0], &u8string[0] + u8string.size()); - } + if (text) { + std::string u8string = util::replaceTokens(properties.text.field, feature.properties); - std::u32string string = ucs4conv.convert(u8string); - - if (string.size()) { - // Loop through all characters of this text and collect unique codepoints. - for (char32_t chr : string) { - ranges.insert(getGlyphRange(chr)); - } - - labels.emplace_back(string, feature.geometry); - } + auto& convert = std::use_facet>(std::locale()); + if (properties.text.transform == TextTransformType::Uppercase) { + convert.toupper(&u8string[0], &u8string[0] + u8string.size()); + } else if (properties.text.transform == TextTransformType::Lowercase) { + convert.tolower(&u8string[0], &u8string[0] + u8string.size()); } - // TODO: refactor for simultaneous placement - // TODO: refactor to use quads instead of GL_POINTS - if (icon) { - std::string field = util::replaceTokens(properties.icon.image, feature.properties); + ft.label = ucs4conv.convert(u8string); - const Rect rect = spriteAtlas.waitForImage(field, sprite); - const uint16_t tx = rect.x + rect.w / 2; - const uint16_t ty = rect.y + rect.h / 2; - - Geometry::command cmd; - pbf geom = feature.geometry; - Geometry geometry(geom); - int32_t x, y; - while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - iconVertexBuffer.add(x, y, tx, ty); - } else { - Log::Warning(Event::ParseTile, "other command than move_to in icon geometry"); - } + if (ft.label.size()) { + // Loop through all characters of this text and collect unique codepoints. + for (char32_t chr : ft.label) { + ranges.insert(getGlyphRange(chr)); } - - icon_vertex_end = iconVertexBuffer.index(); } } - glyphStore.waitForGlyphRanges(properties.text.font, ranges); + if (icon) { + ft.sprite = util::replaceTokens(properties.icon.image, feature.properties); + } + + if (ft.label.length() || ft.sprite.length()) { + ft.geometry = feature.geometry; + features.push_back(std::move(ft)); + } } + glyphStore.waitForGlyphRanges(properties.text.font, ranges); + sprite.waitUntilLoaded(); + + return features; +} + + + + +void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, + const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, GlyphAtlas &glyphAtlas, + GlyphStore &glyphStore) { + + const std::vector features = processFeatures(layer, filter, glyphStore, sprite); + float horizontalAlign = 0.5; if (properties.text.horizontal_align == TextHorizontalAlignType::Right) horizontalAlign = 1; @@ -138,28 +132,86 @@ void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpress if (properties.text.vertical_align == TextVerticalAlignType::Bottom) verticalAlign = 1; else if (properties.text.vertical_align == TextVerticalAlignType::Top) verticalAlign = 0; - - - // Create a copy! const FontStack &fontStack = glyphStore.getFontStack(properties.text.font); - GlyphPositions face; + for (const SymbolFeature &feature : features) { + Shaping shaping; + Rect image; - // Shape and place all labels. - for (const std::pair &label : labels) { + // if feature has text, shape the text + if (feature.label.length()) { + shaping = fontStack.getShaping(feature.label, properties.text.max_width, properties.text.line_height, horizontalAlign, verticalAlign, properties.text.letter_spacing); + } - // Shape labels. - const Shaping shaping = fontStack.getShaping(label.first, properties.text.max_width, - properties.text.line_height, horizontalAlign, - verticalAlign, properties.text.letter_spacing); + // if feature has icon, get sprite atlas position + if (feature.sprite.length()) { + image = spriteAtlas.waitForImage(feature.sprite, sprite); + } - addGlyph(id.to_uint64(), properties.text.font, label.first, fontStack, glyphAtlas, face); - - // Place labels. - addFeature(label.second, face, shaping); + // if either shaping or icon position is present, add the feature + if (shaping.size() || image) { + addFeature(feature.geometry, shaping, image); + } } } +void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, const Rect &image) { + // Decode all lines. + std::vector line; + Geometry::command cmd; + + Coordinate coord; + pbf geom(geom_pbf); + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (!line.empty()) { + addFeature(line, shaping, image); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addFeature(line, shaping, image); + } +} + +void SymbolBucket::addFeature(const std::vector &line, const Shaping &shaping, const Rect &image) { + const float minScale = 0.5f; + const float glyphSize = 24.0f; + + + const bool horizontalText = properties.text.rotation_alignment == RotationAlignmentType::Viewport; + const bool horizontalIcon = properties.icon.rotation_alignment == RotationAlignmentType::Viewport; + const float fontScale = properties.text.max_size / glyphSize; + const float textBoxScale = placement.collision.tilePixelRatio * fontScale, + iconBoxScale = collision.tilePixelRatio * info['icon-max-size'], + iconWithoutText = info['text-optional'] || !shaping, + textWithoutIcon = info['icon-optional'] || !image; +} + + +// +// GlyphPositions face; +// +// +// // Shape and place all labels. +// for (const std::pair &label : labels) { +// +// // Shape labels. +// const Shaping shaping = fontStack.getShaping(label.first, properties.text.max_width, +// properties.text.line_height, horizontalAlign, +// verticalAlign, properties.text.letter_spacing); +// +// addGlyph(id.to_uint64(), properties.text.font, label.first, fontStack, glyphAtlas, face); +// +// // Place labels. +// addFeature(label.second, face, shaping); +// } +} + void SymbolBucket::addFeature(const pbf &geom_pbf, const GlyphPositions &face, const Shaping &shaping) { // Decode all lines. @@ -184,6 +236,79 @@ void SymbolBucket::addFeature(const pbf &geom_pbf, const GlyphPositions &face, } } + +class Symbol { +public: + vec2 tl, tr, bl, br; + Rect tex; + float angle; + float minScale = 0.0f; + float maxScale = std::numeric_limits::infinity(); + CollisionAnchor anchor; +}; + +typedef std::vector Symbols; + +template +void SymbolBucket::addSymbols(Buffer &buffer, const Symbols &symbols, float placementZoom, PlacementRange placementRange, float zoom) { + placementZoom += zoom; + + for (const Symbol &symbol : symbols) { + const auto &tl = symbol.tl; + const auto &tr = symbol.tr; + const auto &bl = symbol.bl; + const auto &br = symbol.br; + const auto &tex = symbol.tex; + const auto &angle = symbol.angle; + + float minZoom = util::max(static_cast(zoom + log(symbol.minScale) / log(2)), + placementZoom); + float maxZoom = + util::min(static_cast(zoom + log(symbol.maxScale) / log(2)), 25.0f); + const auto &glyphAnchor = symbol.anchor; + + if (maxZoom <= minZoom) + continue; + + // Lower min zoom so that while fading out the label + // it can be shown outside of collision-free zoom levels + if (minZoom == placementZoom) { + minZoom = 0; + } + + const int glyph_vertex_length = 4; + + if (!triangleGroups.size() || + (triangleGroups.back().vertex_length + glyph_vertex_length > 65535)) { + // Move to a new group because the old one can't hold the geometry. + triangleGroups.emplace_back(); + } + + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + triangle_group_type &triangleGroup = triangleGroups.back(); + uint32_t triangleIndex = triangleGroup.vertex_length; + + // coordinates (2 triangles) + buffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, + placementRange, maxZoom, placementZoom); + buffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, + angle, minZoom, placementRange, maxZoom, placementZoom); + + // add the two triangles, referencing the four coordinates we just inserted. + triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); + triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + + triangleGroup.vertex_length += glyph_vertex_length; + triangleGroup.elements_length += 2; + } +} + + void SymbolBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange, float zoom) { placementZoom += zoom; diff --git a/src/shader/icon.fragment.glsl b/src/shader/icon.fragment.glsl index 52c9e65aa..beaee7654 100644 --- a/src/shader/icon.fragment.glsl +++ b/src/shader/icon.fragment.glsl @@ -1,11 +1,10 @@ -uniform sampler2D u_image; -uniform vec2 u_dimension; -uniform vec4 u_color; -uniform float u_size; +uniform sampler2D u_texture; varying vec2 v_tex; +varying float v_alpha; void main() { - vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension; - gl_FragColor = u_color * texture2D(u_image, pos); + gl_FragColor = texture2D(u_texture, v_tex); + gl_FragColor.a *= v_alpha; + gl_FragColor.rgb *= gl_FragColor.a; } diff --git a/src/shader/icon.vertex.glsl b/src/shader/icon.vertex.glsl index d0ffe4b33..d273b733d 100644 --- a/src/shader/icon.vertex.glsl +++ b/src/shader/icon.vertex.glsl @@ -1,14 +1,73 @@ attribute vec2 a_pos; +attribute vec2 a_offset; attribute vec2 a_tex; +attribute float a_angle; +attribute float a_minzoom; +attribute float a_maxzoom; +attribute float a_rangeend; +attribute float a_rangestart; +attribute float a_labelminzoom; -uniform mat4 u_matrix; -uniform float u_size; -uniform float u_ratio; + +// posmatrix is for the vertex position, exmatrix is for rotating and projecting +// the extrusion vector. +uniform mat4 u_posmatrix; +uniform mat4 u_exmatrix; +uniform float u_angle; +uniform float u_zoom; +uniform float u_flip; +uniform float u_fadedist; +uniform float u_minfadezoom; +uniform float u_maxfadezoom; +uniform float u_fadezoom; +uniform float u_opacity; + +uniform vec2 u_texsize; varying vec2 v_tex; +varying float v_alpha; void main() { - gl_Position = u_matrix * vec4(a_pos, 0, 1); - gl_PointSize = u_size; - v_tex = a_tex * u_ratio; + + float a_fadedist = 10.0; + float rev = 0.0; + + // u_angle is angle of the map, -128..128 representing 0..2PI + // a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text + float rotated = mod(a_angle + u_angle, 256.0); + // if the label rotates with the map, and if the rotated label is upside down, hide it + if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0; + + // If the label should be invisible, we move the vertex outside + // of the view plane so that the triangle gets clipped. This makes it easier + // for us to create degenerate triangle strips. + // u_zoom is the current zoom level adjusted for the change in font size + float z = 2.0 - step(a_minzoom, u_zoom) - (1.0 - step(a_maxzoom, u_zoom)) + rev; + + // fade out labels + float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0); + + if (u_fadedist >= 0.0) { + v_alpha = alpha; + } else { + v_alpha = 1.0 - alpha; + } + if (u_maxfadezoom < a_labelminzoom) { + v_alpha = 0.0; + } + if (u_minfadezoom >= a_labelminzoom) { + v_alpha = 1.0; + } + + // if label has been faded out, clip it + z += step(v_alpha, 0.0); + + // all the angles are 0..256 representing 0..2PI + // hide if (angle >= a_rangeend && angle < rangestart) + z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle)); + + gl_Position = u_posmatrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0); + v_tex = a_tex / u_texsize; + + v_alpha *= u_opacity; } diff --git a/src/shader/icon_shader.cpp b/src/shader/icon_shader.cpp index b3ac9272b..435b813fa 100644 --- a/src/shader/icon_shader.cpp +++ b/src/shader/icon_shader.cpp @@ -17,62 +17,130 @@ IconShader::IconShader() } a_pos = glGetAttribLocation(program, "a_pos"); + a_offset = glGetAttribLocation(program, "a_offset"); a_tex = glGetAttribLocation(program, "a_tex"); + a_angle = glGetAttribLocation(program, "a_angle"); + a_minzoom = glGetAttribLocation(program, "a_minzoom"); + a_maxzoom = glGetAttribLocation(program, "a_maxzoom"); + a_rangeend = glGetAttribLocation(program, "a_rangeend"); + a_rangestart = glGetAttribLocation(program, "a_rangestart"); + a_labelminzoom = glGetAttribLocation(program, "a_labelminzoom"); u_matrix = glGetUniformLocation(program, "u_matrix"); - u_color = glGetUniformLocation(program, "u_color"); - u_size = glGetUniformLocation(program, "u_size"); - u_ratio = glGetUniformLocation(program, "u_ratio"); - u_dimension = glGetUniformLocation(program, "u_dimension"); + u_exmatrix = glGetUniformLocation(program, "u_exmatrix"); + u_angle = glGetUniformLocation(program, "u_angle"); + u_zoom = glGetUniformLocation(program, "u_zoom"); + u_flip = glGetUniformLocation(program, "u_flip"); + u_fadedist = glGetUniformLocation(program, "u_fadedist"); + u_minfadezoom = glGetUniformLocation(program, "u_minfadezoom"); + u_maxfadezoom = glGetUniformLocation(program, "u_maxfadezoom"); + u_fadezoom = glGetUniformLocation(program, "u_fadezoom"); + u_opacity = glGetUniformLocation(program, "u_opacity"); // fprintf(stderr, "IconShader:\n"); // fprintf(stderr, " - u_matrix: %d\n", u_matrix); - // fprintf(stderr, " - u_color: %d\n", u_color); - // fprintf(stderr, " - u_size: %d\n", u_size); - // fprintf(stderr, " - u_ratio: %d\n", u_ratio); - // fprintf(stderr, " - u_dimension: %d\n", u_dimension); - // fprintf(stderr, " - u_image: %d\n", u_image); + // fprintf(stderr, " - u_exmatrix: %d\n", u_exmatrix); + // fprintf(stderr, " - u_angle: %d\n", u_angle); + // fprintf(stderr, " - u_zoom: %d\n", u_zoom); + // fprintf(stderr, " - u_flip: %d\n", u_flip); + // fprintf(stderr, " - u_fadedist: %d\n", u_fadedist); + // fprintf(stderr, " - u_minfadezoom: %d\n", u_minfadezoom); + // fprintf(stderr, " - u_maxfadezoom: %d\n", u_maxfadezoom); + // fprintf(stderr, " - u_fadezoom: %d\n", u_fadezoom); + // fprintf(stderr, " - u_opacity: %d\n", u_opacity); } void IconShader::bind(char *offset) { + const int stride = 20; + glEnableVertexAttribArray(a_pos); - glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset); + glVertexAttribPointer(a_pos, 2, GL_SHORT, false, stride, offset + 0); + + glEnableVertexAttribArray(a_offset); + glVertexAttribPointer(a_offset, 2, GL_SHORT, false, stride, offset + 4); + + glEnableVertexAttribArray(a_labelminzoom); + glVertexAttribPointer(a_labelminzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 8); + + glEnableVertexAttribArray(a_minzoom); + glVertexAttribPointer(a_minzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 9); + + glEnableVertexAttribArray(a_maxzoom); + glVertexAttribPointer(a_maxzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 10); + + glEnableVertexAttribArray(a_angle); + glVertexAttribPointer(a_angle, 1, GL_UNSIGNED_BYTE, false, stride, offset + 11); + + glEnableVertexAttribArray(a_rangeend); + glVertexAttribPointer(a_rangeend, 1, GL_UNSIGNED_BYTE, false, stride, offset + 12); + + glEnableVertexAttribArray(a_rangestart); + glVertexAttribPointer(a_rangestart, 1, GL_UNSIGNED_BYTE, false, stride, offset + 13); glEnableVertexAttribArray(a_tex); - glVertexAttribPointer(a_tex, 2, GL_UNSIGNED_SHORT, false, 8, offset + 4); + glVertexAttribPointer(a_tex, 2, GL_SHORT, false, stride, offset + 16); } -void IconShader::setImage(int32_t new_image) { - if (image != new_image) { - glUniform1i(u_image, new_image); - image = new_image; +void IconShader::setExtrudeMatrix(const std::array& new_exmatrix) { + if (exmatrix != new_exmatrix) { + glUniformMatrix4fv(u_exmatrix, 1, GL_FALSE, new_exmatrix.data()); + exmatrix = new_exmatrix; } } -void IconShader::setColor(const std::array& new_color) { - if (color != new_color) { - glUniform4fv(u_color, 1, new_color.data()); - color = new_color; +void IconShader::setAngle(float new_angle) { + if (angle != new_angle) { + glUniform1f(u_angle, new_angle); + angle = new_angle; } } -void IconShader::setSize(float new_size) { - if (size != new_size) { - glUniform1f(u_size, new_size); - size = new_size; +void IconShader::setZoom(float new_zoom) { + if (zoom != new_zoom) { + glUniform1f(u_zoom, new_zoom); + zoom = new_zoom; } } -void IconShader::setRatio(float new_ratio) { - if (ratio != new_ratio) { - glUniform1f(u_ratio, new_ratio); - ratio = new_ratio; +void IconShader::setFlip(float new_flip) { + if (flip != new_flip) { + glUniform1f(u_flip, new_flip); + flip = new_flip; } } -void IconShader::setDimension(const std::array& new_dimension) { - if (dimension != new_dimension) { - glUniform2fv(u_dimension, 1, new_dimension.data()); - dimension = new_dimension; +void IconShader::setFadeDist(float new_fadedist) { + if (fadedist != new_fadedist) { + glUniform1f(u_fadedist, new_fadedist); + fadedist = new_fadedist; } } + +void IconShader::setMinFadeZoom(float new_minfadezoom) { + if (minfadezoom != new_minfadezoom) { + glUniform1f(u_minfadezoom, new_minfadezoom); + minfadezoom = new_minfadezoom; + } +} + +void IconShader::setMaxFadeZoom(float new_maxfadezoom) { + if (maxfadezoom != new_maxfadezoom) { + glUniform1f(u_maxfadezoom, new_maxfadezoom); + maxfadezoom = new_maxfadezoom; + } +} + +void IconShader::setFadeZoom(float new_fadezoom) { + if (fadezoom != new_fadezoom) { + glUniform1f(u_fadezoom, new_fadezoom); + fadezoom = new_fadezoom; + } +} + +void IconShader::setOpacity(float new_opacity) { + if (opacity != new_opacity) { + glUniform1f(u_opacity, new_opacity); + opacity = new_opacity; + } +} + diff --git a/src/text/collision.cpp b/src/text/collision.cpp index 18cf56ca0..362ac92e2 100644 --- a/src/text/collision.cpp +++ b/src/text/collision.cpp @@ -31,24 +31,40 @@ Collision::~Collision() { delete ((Tree *)hTree); } -Collision::Collision() : cTree(new Tree()), hTree(new Tree()) { +Collision::Collision(float zoom, float tileExtent, float tileSize, float placementDepth) + : hTree(new Tree()), // tree for horizontal labels + cTree(new Tree()), // tree for glyphs from curved labels + + // tile pixels per screen pixels at the tile's zoom level + tilePixelRatio(tileExtent / tileSize), + + zoom(zoom), + + // Calculate the maximum scale we can go down in our fake-3d rtree so that + // placement still makes sense. This is calculated so that the minimum + // placement zoom can be at most 25.5 (we use an unsigned integer x10 to + // store the minimum zoom). + // + // We don't want to place labels all the way to 25.5. This lets too many + // glyphs be placed, slowing down collision checking. Only place labels if + // they will show up within the intended zoom range of the tile. + maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom))) +{ const float m = 4096; + const float edge = m * tilePixelRatio * 2; // Hack to prevent cross-tile labels insert( {{GlyphBox{ CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}}, 0}, GlyphBox{ - CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}}, - 0}}}, + CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}}, 0}}}, CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2); insert({{GlyphBox{ - CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}}, - 0}, + CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}}, 0}, GlyphBox{ - CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}}, - 0}}}, + CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}}, 0}}}, CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2); } @@ -74,7 +90,7 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAnchor &anchor, float minPlacementScale, float maxPlacementScale, float padding, - bool horizontal, bool alwaysVisible) { + bool horizontal, bool allowOverlap, bool ignorePlacement) { float minScale = std::numeric_limits::infinity(); for (const GlyphBox &glyphBox : boxes) { minScale = util::min(minScale, glyphBox.minScale); @@ -95,17 +111,17 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAncho y22 = box.br.y * box.br.y, diag = std::sqrt(util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22)); - glyphs.front().hBox = CollisionRect{{-diag, -diag}, {diag, diag}}; - - } else { - glyphs = boxes; + glyphs.front().hBox = CollisionRect{ + {-diag, -diag}, + {diag, diag} + }; } // Calculate the minimum scale the entire label can be shown without // collisions - float scale = alwaysVisible ? minPlacementScale - : getPlacementScale(glyphs, minPlacementScale, - maxPlacementScale, padding); + float scale = + allowOverlap ? minPlacementScale : getPlacementScale(glyphs, minPlacementScale, + maxPlacementScale, padding); // Return if the label can never be placed without collision if (scale < 0 || scale == std::numeric_limits::infinity()) { @@ -113,8 +129,10 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAncho } // Calculate the range it is safe to rotate all glyphs - PlacementRange rotationRange = getPlacementRange(glyphs, scale, horizontal); - insert(glyphs, anchor, scale, rotationRange, horizontal, padding); + PlacementRange rotationRange = allowOverlap ? PlacementRange{{2 * M_PI, 0.0f}} + : getPlacementRange(glyphs, scale, horizontal); + + if (!ignorePlacement) insert(glyphs, anchor, scale, rotationRange, horizontal, padding); float zoom = log(scale) / log(2); @@ -177,18 +195,10 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs, float padding = std::fmax(pad, placement.padding) * 8.0f; // Original algorithm: - float s1 = (ob.tl.x - nb.br.x - padding) / - (na.x - oa.x); // scale at which new box is to the - // left of old box - float s2 = (ob.br.x - nb.tl.x + padding) / - (na.x - oa.x); // scale at which new box is to the - // right of old box - float s3 = (ob.tl.y - nb.br.y - padding) / - (na.y - oa.y); // scale at which new box is to the - // top of old box - float s4 = (ob.br.y - nb.tl.y + padding) / - (na.y - oa.y); // scale at which new box is to the - // bottom of old box + float s1 = (ob.tl.x - nb.br.x - padding) / (na.x - oa.x); // scale at which new box is to the left of old box + float s2 = (ob.br.x - nb.tl.x + padding) / (na.x - oa.x); // scale at which new box is to the right of old box + float s3 = (ob.tl.y - nb.br.y - padding) / (na.y - oa.y); // scale at which new box is to the top of old box + float s4 = (ob.br.y - nb.tl.y + padding) / (na.y - oa.y); // scale at which new box is to the bottom of old box if (isnan(s1) || isnan(s2)) { s1 = s2 = 1; diff --git a/src/text/placement.cpp b/src/text/placement.cpp index ae5bf46bf..75988cc99 100644 --- a/src/text/placement.cpp +++ b/src/text/placement.cpp @@ -272,10 +272,11 @@ void Placement::addFeature(SymbolBucket &bucket, const std::vector & PlacedGlyphs glyphs; GlyphBoxes boxes; - getGlyphs(glyphs, boxes, anchor, info.text.offset, shaping, face, fontScale, horizontal, line, - info.text.max_angle_delta * (M_PI/180), info.text.rotate); - PlacementProperty place = collision.place(boxes, anchor, anchor.scale, maxPlacementScale, - info.text.padding, horizontal, info.text.ignore_placement); + getGlyphs(glyphs, boxes, anchor, info.text.offset, shaping, face, fontScale, horizontal, + line, info.text.max_angle_delta * (M_PI / 180), info.text.rotate); + PlacementProperty place = + collision.place(boxes, anchor, anchor.scale, maxPlacementScale, info.text.padding, + horizontal, info.text.allow_overlap, info.text.ignore_placement); if (place) { bucket.addGlyphs(glyphs, place.zoom, place.rotationRange, zoom - zOffset); }