From ad8cfb37cfe139280f3e920a474f6331222471b3 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Mon, 30 Jul 2018 15:22:28 -0700 Subject: [core, node] Re-implement "avoid edges" behavior for MapMode::Tile - Fixes issue #12461. - Only implement "avoid edges" in MapMode::Tile since it's no longer relevant in Static or Continuous mode. - New: Force "avoid edges" to "true" for line labels, since in tile mode they'll always clip poorly at tile boundaries. - Remove unused "withinPlus0/inside" logic. --- src/mbgl/layout/symbol_layout.cpp | 32 +++++++--------------------- src/mbgl/text/collision_index.cpp | 44 +++++++++++++++++++++++++++------------ src/mbgl/text/collision_index.hpp | 14 +++++++++++-- src/mbgl/text/placement.cpp | 11 ++++++++-- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 41469f293d..ab718351ab 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -258,11 +258,6 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const float textMaxBoxScale = tilePixelRatio * textMaxSize / glyphSize; const float iconBoxScale = tilePixelRatio * layoutIconSize; const float symbolSpacing = tilePixelRatio * layout.get(); - // CJL: I'm not sure why SymbolPlacementType::Line -> avoidEdges = false. It seems redundant since - // getAnchors will already avoid generating anchors outside the tile bounds. - // However, SymbolPlacementType::LineCenter allows anchors outside tile boundaries, so its behavior - // here should match SymbolPlacement::Point - const bool avoidEdges = layout.get() && layout.get() != SymbolPlacementType::Line; const float textPadding = layout.get() * tilePixelRatio; const float iconPadding = layout.get() * tilePixelRatio; const float textMaxAngle = layout.get() * util::DEG2RAD; @@ -274,25 +269,14 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketLeaderID, symbolInstances.size()); auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) { - // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers - // +-------------------+ Symbols with anchors located on tile edges - // |(0,0) || are duplicated on neighbor tiles. - // | || - // | || In continuous mode, to avoid overdraw we - // | || skip symbols located on the extent edges. - // | Tile || In still mode, we include the features in - // | || the buffers for both tiles and clip them - // | || at draw time. - // | || - // +-------------------| In this scenario, the inner bounding box - // +-------------------+ is called 'withinPlus0', and the outer - // (extent,extent) is called 'inside'. - const bool withinPlus0 = anchor.point.x >= 0 && anchor.point.x < util::EXTENT && anchor.point.y >= 0 && anchor.point.y < util::EXTENT; - const bool inside = withinPlus0 || anchor.point.x == util::EXTENT || anchor.point.y == util::EXTENT; - - if (avoidEdges && !inside) return; - - if (mode == MapMode::Tile || withinPlus0) { + const bool anchorInsideTile = anchor.point.x >= 0 && anchor.point.x < util::EXTENT && anchor.point.y >= 0 && anchor.point.y < util::EXTENT; + + if (mode == MapMode::Tile || anchorInsideTile) { + // For static/continuous rendering, only add symbols anchored within this tile: + // neighboring symbols will be added as part of the neighboring tiles. + // In tiled rendering mode, add all symbols in the buffers so that we can: + // (1) render symbols that overlap into this tile + // (2) approximate collision detection effects from neighboring symbols symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout.evaluate(zoom, feature), layoutTextSize, textBoxScale, textPadding, textPlacement, textOffset, diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 091840a371..dae789a196 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -62,6 +62,21 @@ bool CollisionIndex::isOffscreen(const CollisionBox& box) const { bool CollisionIndex::isInsideGrid(const CollisionBox& box) const { return box.px2 >= 0 && box.px1 < gridRightBoundary && box.py2 >= 0 && box.py1 < gridBottomBoundary; } + +CollisionTileBoundaries CollisionIndex::projectTileBoundaries(const mat4& posMatrix) const { + Point topLeft = projectPoint(posMatrix, { 0, 0 }); + Point bottomRight = projectPoint(posMatrix, { util::EXTENT, util::EXTENT }); + + return {{ topLeft.x, topLeft.y, bottomRight.x, bottomRight.y }}; + +} + +bool CollisionIndex::isInsideTile(const CollisionBox& box, const CollisionTileBoundaries& tileBoundaries) const { + // This check is only well defined when the tile boundaries are axis-aligned + // We are relying on it only being used in MapMode::Tile, where that is always the case + + return box.px1 >= tileBoundaries[0] && box.py1 >= tileBoundaries[1] && box.px2 < tileBoundaries[2] && box.py2 < tileBoundaries[3]; +} std::pair CollisionIndex::placeFeature(CollisionFeature& feature, @@ -73,7 +88,8 @@ std::pair CollisionIndex::placeFeature(CollisionFeature& feature, const float fontSize, const bool allowOverlap, const bool pitchWithMap, - const bool collisionDebug) { + const bool collisionDebug, + const optional& avoidEdges) { if (!feature.alongLine) { CollisionBox& box = feature.boxes.front(); const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor); @@ -82,15 +98,17 @@ std::pair CollisionIndex::placeFeature(CollisionFeature& feature, box.py1 = box.y1 * tileToViewport + projectedPoint.first.y; box.px2 = box.x2 * tileToViewport + projectedPoint.first.x; box.py2 = box.y2 * tileToViewport + projectedPoint.first.y; + - if (!isInsideGrid(box) || + if ((avoidEdges && !isInsideTile(box, *avoidEdges)) || + !isInsideGrid(box) || (!allowOverlap && collisionGrid.hitTest({{ box.px1, box.py1 }, { box.px2, box.py2 }}))) { return { false, false }; } return {true, isOffscreen(box)}; } else { - return placeLineFeature(feature, posMatrix, labelPlaneMatrix, textPixelRatio, symbol, scale, fontSize, allowOverlap, pitchWithMap, collisionDebug); + return placeLineFeature(feature, posMatrix, labelPlaneMatrix, textPixelRatio, symbol, scale, fontSize, allowOverlap, pitchWithMap, collisionDebug, avoidEdges); } } @@ -103,7 +121,8 @@ std::pair CollisionIndex::placeLineFeature(CollisionFeature& feature, const float fontSize, const bool allowOverlap, const bool pitchWithMap, - const bool collisionDebug) { + const bool collisionDebug, + const optional& avoidEdges) { const auto tileUnitAnchorPoint = symbol.anchorPoint; const auto projectedAnchor = projectAnchor(posMatrix, tileUnitAnchorPoint); @@ -202,15 +221,14 @@ std::pair CollisionIndex::placeLineFeature(CollisionFeature& feature, entirelyOffscreen &= isOffscreen(circle); inGrid |= isInsideGrid(circle); - if (!allowOverlap) { - if (collisionGrid.hitTest({{circle.px, circle.py}, circle.radius})) { - if (!collisionDebug) { - return {false, false}; - } else { - // Don't early exit if we're showing the debug circles because we still want to calculate - // which circles are in use - collisionDetected = true; - } + if ((avoidEdges && !isInsideTile(circle, *avoidEdges)) || + (!allowOverlap && collisionGrid.hitTest({{circle.px, circle.py}, circle.radius}))) { + if (!collisionDebug) { + return {false, false}; + } else { + // Don't early exit if we're showing the debug circles because we still want to calculate + // which circles are in use + collisionDetected = true; } } } diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index b2be4c6ade..78782fe61c 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -3,13 +3,18 @@ #include #include #include +#include #include +#include + namespace mbgl { class PlacedSymbol; struct TileDistance; + +using CollisionTileBoundaries = std::array; class CollisionIndex { public: @@ -26,15 +31,19 @@ public: const float fontSize, const bool allowOverlap, const bool pitchWithMap, - const bool collisionDebug); + const bool collisionDebug, + const optional& avoidEdges); void insertFeature(CollisionFeature& feature, bool ignorePlacement, uint32_t bucketInstanceId); std::unordered_map> queryRenderedSymbols(const ScreenLineString&) const; + + CollisionTileBoundaries projectTileBoundaries(const mat4& posMatrix) const; private: bool isOffscreen(const CollisionBox&) const; bool isInsideGrid(const CollisionBox&) const; + bool isInsideTile(const CollisionBox&, const CollisionTileBoundaries& tileBoundaries) const; std::pair placeLineFeature(CollisionFeature& feature, const mat4& posMatrix, @@ -45,7 +54,8 @@ private: const float fontSize, const bool allowOverlap, const bool pitchWithMap, - const bool collisionDebug); + const bool collisionDebug, + const optional& avoidEdges); float approximateTileDistance(const TileDistance& tileDistance, const float lastSegmentAngle, const float pixelsToTileUnits, const float cameraToAnchorDistance, const bool pitchWithMap); diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index a050be4648..0747133bd2 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -110,6 +110,13 @@ void Placement::placeLayerBucket( auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom()); auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom()); + optional avoidEdges; + if (mapMode == MapMode::Tile && + (bucket.layout.get() || + bucket.layout.get() == style::SymbolPlacementType::Line)) { + avoidEdges = collisionIndex.projectTileBoundaries(posMatrix); + } + for (auto& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { @@ -133,7 +140,7 @@ void Placement::placeLayerBucket( placedSymbol, scale, fontSize, bucket.layout.get(), bucket.layout.get() == style::AlignmentType::Map, - showCollisionBoxes); + showCollisionBoxes, avoidEdges); placeText = placed.first; offscreen &= placed.second; } @@ -147,7 +154,7 @@ void Placement::placeLayerBucket( placedSymbol, scale, fontSize, bucket.layout.get(), bucket.layout.get() == style::AlignmentType::Map, - showCollisionBoxes); + showCollisionBoxes, avoidEdges); placeIcon = placed.first; offscreen &= placed.second; } -- cgit v1.2.1