From de3fd89b2cb259c965629aa9aea17729511229f7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 29 May 2015 12:58:06 -0400 Subject: [PATCH] sort overlapping symbols in the y direction fixes #333 fixes #988 -js: 6514de77be2ff3df4267785f42f5e53c63233cac --- src/mbgl/renderer/painter_symbol.cpp | 44 ++++++++++++++++------------ src/mbgl/renderer/symbol_bucket.cpp | 42 ++++++++++++++++++++++---- src/mbgl/renderer/symbol_bucket.hpp | 10 ++++--- 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 6ef39e2853b..a4c5fa1d6ae 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -119,10 +119,35 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c const auto &properties = layer_desc.getProperties(); const auto &layout = bucket.layout; - config.stencilTest = false; + const bool drawAcrossEdges = !(layout.text.allow_overlap || layout.icon.allow_overlap || + layout.text.ignore_placement || layout.icon.ignore_placement); + + // Disable the stencil test so that labels aren't clipped to tile boundaries. + // + // Layers with features that may be drawn overlapping aren't clipped. These + // layers are sorted in the y direction, and to draw the correct ordering near + // tile edges the icons are included in both tiles and clipped when drawing. + config.stencilTest = drawAcrossEdges ? false : true; config.depthTest = true; config.depthMask = GL_FALSE; + if (bucket.hasCollisionBoxData() && ( + (bucket.hasIconData() && properties.icon.opacity) || + (bucket.hasTextData() && properties.text.opacity))) { + config.stencilTest = true; + + useProgram(collisionBoxShader->program); + collisionBoxShader->u_matrix = matrix; + collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z); + collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10; + collisionBoxShader->u_maxzoom = (id.z + 1) * 10; + lineWidth(3.0f); + + config.depthRange = { strata, 1.0f }; + bucket.drawCollisionBoxes(*collisionBoxShader); + + } + if (bucket.hasIconData()) { bool sdf = bucket.sdfIcons; @@ -193,21 +218,4 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c &SymbolBucket::drawGlyphs); } - if (bucket.hasCollisionBoxData() && ( - (bucket.hasIconData() && properties.icon.opacity) || - (bucket.hasTextData() && properties.text.opacity))) { - config.stencilTest = true; - - useProgram(collisionBoxShader->program); - collisionBoxShader->u_matrix = matrix; - collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z); - collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10; - collisionBoxShader->u_maxzoom = (id.z + 1) * 10; - lineWidth(3.0f); - - config.depthRange = { strata, 1.0f }; - bucket.drawCollisionBoxes(*collisionBoxShader); - - } - } diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index b0b8a6db4f3..5df02dd5a97 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -32,20 +32,22 @@ namespace mbgl { SymbolInstance::SymbolInstance(Anchor &anchor, const std::vector &line, const Shaping &shapedText, const PositionedIcon &shapedIcon, - const StyleLayoutSymbol &layout, const bool inside, + const StyleLayoutSymbol &layout, const bool addToBuffers, const float textBoxScale, const float textPadding, const float textAlongLine, const float iconBoxScale, const float iconPadding, const float iconAlongLine, const GlyphPositions &face) : + x(anchor.x), + y(anchor.y), hasText(shapedText), hasIcon(shapedIcon), // Create the quads used for rendering the glyphs. - glyphQuads(inside && shapedText ? + glyphQuads(addToBuffers && shapedText ? getGlyphQuads(anchor, shapedText, textBoxScale, line, layout, textAlongLine, face) : SymbolQuads()), // Create the quad used for rendering the icon. - iconQuads(inside && shapedIcon ? + iconQuads(addToBuffers && shapedIcon ? getIconQuads(anchor, shapedIcon, line, layout, iconAlongLine) : SymbolQuads()), @@ -288,6 +290,8 @@ void SymbolBucket::addFeature(const std::vector> &lines, const bool iconAlongLine = layout.icon.rotation_alignment == RotationAlignmentType::Map && layout.placement == PlacementType::Line; + const bool mayOverlap = layout.text.allow_overlap || layout.icon.allow_overlap || + layout.text.ignore_placement || layout.icon.ignore_placement; auto& clippedLines = layout.placement == PlacementType::Line ? util::clipLines(lines, 0, 0, 4096, 4096) : @@ -309,7 +313,18 @@ void SymbolBucket::addFeature(const std::vector> &lines, if (avoidEdges && !inside) continue; - symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, inside, + // Normally symbol layers are drawn across tile boundaries. Only symbols + // with their anchors within the tile boundaries are added to the buffers + // to prevent symbols from being drawn twice. + // + // Symbols in layers with overlap are sorted in the y direction so that + // symbols lower on the canvas are drawn on top of symbols near the top. + // To preserve this order across tile boundaries these symbols can't + // be drawn across tile boundaries. Instead they need to be included in + // the buffers for both tiles and clipped to tile boundaries at draw time. + const bool addToBuffers = inside || mayOverlap; + + symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, textBoxScale, textPadding, textAlongLine, iconBoxScale, iconPadding, iconAlongLine, face); @@ -335,6 +350,24 @@ void SymbolBucket::placeFeatures(bool swapImmediately) { layout.icon.rotation_alignment == RotationAlignmentType::Map && layout.placement == PlacementType::Line; + const bool mayOverlap = layout.text.allow_overlap || layout.icon.allow_overlap || + layout.text.ignore_placement || layout.icon.ignore_placement; + + // Sort symbols by their y position on the canvas so that they lower symbols + // are drawn on top of higher symbols. + // Don't sort symbols that won't overlap because it isn't necessary and + // because it causes more labels to pop in and out when rotating. + if (mayOverlap) { + float sin = std::sin(collision.angle); + float cos = std::cos(collision.angle); + + std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { + const float aRotated = sin * a.x + cos * a.y; + const float bRotated = sin * b.x + cos * b.y; + return aRotated < bRotated; + }); + } + for (SymbolInstance &symbolInstance : symbolInstances) { const bool hasText = symbolInstance.hasText; @@ -343,7 +376,6 @@ void SymbolBucket::placeFeatures(bool swapImmediately) { const bool iconWithoutText = layout.text.optional || !hasText; const bool textWithoutIcon = layout.icon.optional || !hasIcon; - // Calculate the scales at which the text and icon can be placed without collision. float glyphScale = hasText && !layout.text.allow_overlap ? diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp index 8fac9641b77..bebbedb3952 100644 --- a/src/mbgl/renderer/symbol_bucket.hpp +++ b/src/mbgl/renderer/symbol_bucket.hpp @@ -48,10 +48,12 @@ class SymbolInstance { const float textBoxScale, const float textPadding, const float textAlongLine, const float iconBoxScale, const float iconPadding, const float iconAlongLine, const GlyphPositions &face); - const bool hasText; - const bool hasIcon; - const SymbolQuads glyphQuads; - const SymbolQuads iconQuads; + float x; + float y; + bool hasText; + bool hasIcon; + SymbolQuads glyphQuads; + SymbolQuads iconQuads; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; };