[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.
This commit is contained in:
@@ -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<SymbolSpacing>();
|
||||
// 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<SymbolAvoidEdges>() && layout.get<SymbolPlacement>() != SymbolPlacementType::Line;
|
||||
const float textPadding = layout.get<TextPadding>() * tilePixelRatio;
|
||||
const float iconPadding = layout.get<IconPadding>() * tilePixelRatio;
|
||||
const float textMaxAngle = layout.get<TextMaxAngle>() * 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;
|
||||
const bool anchorInsideTile = anchor.point.x >= 0 && anchor.point.x < util::EXTENT && anchor.point.y >= 0 && anchor.point.y < util::EXTENT;
|
||||
|
||||
if (avoidEdges && !inside) return;
|
||||
|
||||
if (mode == MapMode::Tile || withinPlus0) {
|
||||
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,
|
||||
|
||||
@@ -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<float> topLeft = projectPoint(posMatrix, { 0, 0 });
|
||||
Point<float> 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<bool,bool> CollisionIndex::placeFeature(CollisionFeature& feature,
|
||||
@@ -73,7 +88,8 @@ std::pair<bool,bool> CollisionIndex::placeFeature(CollisionFeature& feature,
|
||||
const float fontSize,
|
||||
const bool allowOverlap,
|
||||
const bool pitchWithMap,
|
||||
const bool collisionDebug) {
|
||||
const bool collisionDebug,
|
||||
const optional<CollisionTileBoundaries>& avoidEdges) {
|
||||
if (!feature.alongLine) {
|
||||
CollisionBox& box = feature.boxes.front();
|
||||
const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor);
|
||||
@@ -82,15 +98,17 @@ std::pair<bool,bool> 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<bool,bool> CollisionIndex::placeLineFeature(CollisionFeature& feature,
|
||||
const float fontSize,
|
||||
const bool allowOverlap,
|
||||
const bool pitchWithMap,
|
||||
const bool collisionDebug) {
|
||||
const bool collisionDebug,
|
||||
const optional<CollisionTileBoundaries>& avoidEdges) {
|
||||
|
||||
const auto tileUnitAnchorPoint = symbol.anchorPoint;
|
||||
const auto projectedAnchor = projectAnchor(posMatrix, tileUnitAnchorPoint);
|
||||
@@ -202,15 +221,14 @@ std::pair<bool,bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
#include <mbgl/geometry/feature_index.hpp>
|
||||
#include <mbgl/text/collision_feature.hpp>
|
||||
#include <mbgl/util/grid_index.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class PlacedSymbol;
|
||||
|
||||
struct TileDistance;
|
||||
|
||||
using CollisionTileBoundaries = std::array<float,4>;
|
||||
|
||||
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<CollisionTileBoundaries>& avoidEdges);
|
||||
|
||||
void insertFeature(CollisionFeature& feature, bool ignorePlacement, uint32_t bucketInstanceId);
|
||||
|
||||
std::unordered_map<uint32_t, std::vector<IndexedSubfeature>> 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<bool,bool> 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<CollisionTileBoundaries>& avoidEdges);
|
||||
|
||||
float approximateTileDistance(const TileDistance& tileDistance, const float lastSegmentAngle, const float pixelsToTileUnits, const float cameraToAnchorDistance, const bool pitchWithMap);
|
||||
|
||||
|
||||
@@ -110,6 +110,13 @@ void Placement::placeLayerBucket(
|
||||
auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom());
|
||||
auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom());
|
||||
|
||||
optional<CollisionTileBoundaries> avoidEdges;
|
||||
if (mapMode == MapMode::Tile &&
|
||||
(bucket.layout.get<style::SymbolAvoidEdges>() ||
|
||||
bucket.layout.get<style::SymbolPlacement>() == 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<style::TextAllowOverlap>(),
|
||||
bucket.layout.get<style::TextPitchAlignment>() == 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<style::IconAllowOverlap>(),
|
||||
bucket.layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map,
|
||||
showCollisionBoxes);
|
||||
showCollisionBoxes, avoidEdges);
|
||||
placeIcon = placed.first;
|
||||
offscreen &= placed.second;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user