From 2040d4d6f56ccb6b626af078fe752e1459565c6b Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Wed, 4 Dec 2019 17:05:11 +0200 Subject: [core] In tile mode, place labels from tile borders first --- src/mbgl/text/placement.cpp | 141 +++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 53 deletions(-) (limited to 'src/mbgl/text/placement.cpp') diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 5e59e91465..32def0b611 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -142,10 +142,9 @@ Point calculateVariableLayoutOffset(style::SymbolAnchorType anchor, } } // namespace -void Placement::placeBucket( - const SymbolBucket& bucket, - const BucketPlacementParameters& params, - std::set& seenCrossTileIDs) { +void Placement::placeBucket(const SymbolBucket& bucket, + const BucketPlacementParameters& params, + std::set& seenCrossTileIDs) { const auto& layout = *bucket.layout; const auto& renderTile = params.tile; const auto& state = collisionIndex.getTransformState(); @@ -158,11 +157,12 @@ void Placement::placeBucket( state.matrixFor(posMatrix, renderTile.id); matrix::multiply(posMatrix, params.projMatrix, posMatrix); - mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, - layout.get() == style::AlignmentType::Map, - layout.get() == style::AlignmentType::Map, - state, - pixelsToTileUnits); + mat4 textLabelPlaneMatrix = + getLabelPlaneMatrix(posMatrix, + layout.get() == style::AlignmentType::Map, + layout.get() == style::AlignmentType::Map, + state, + pixelsToTileUnits); mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, layout.get() == style::AlignmentType::Map, @@ -182,6 +182,7 @@ void Placement::placeBucket( layout.get() == style::SymbolPlacementType::Line)) { avoidEdges = tileBorders; } + const bool stickToFirstAnchor = tileBorders && !avoidEdges; const bool textAllowOverlap = layout.get(); const bool iconAllowOverlap = layout.get(); @@ -243,7 +244,7 @@ void Placement::placeBucket( } }; - const auto placeTextForPlacementModes = [&] (auto& placeHorizontalFn, auto& placeVerticalFn) { + const auto placeTextForPlacementModes = [&](auto& placeHorizontalFn, auto& placeVerticalFn) { if (bucket.allowVerticalPlacement && symbolInstance.writingModes & WritingModeType::Vertical) { assert(!bucket.placementModes.empty()); for (auto& placementMode : bucket.placementModes) { @@ -293,7 +294,8 @@ void Placement::placeBucket( const auto placeVertical = [&] { if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) { - return placeFeature(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical); + return placeFeature(*symbolInstance.verticalTextCollisionFeature, + style::TextWritingModeType::Vertical); } return std::pair{false, false}; }; @@ -340,8 +342,6 @@ void Placement::placeBucket( std::pair placedFeature = {false, false}; const size_t anchorsSize = variableTextAnchors.size(); const size_t placementAttempts = textAllowOverlap ? anchorsSize * 2 : anchorsSize; - const bool stickToFirstAnchor = tileBorders && !avoidEdges; - for (size_t i = 0u; i < placementAttempts; ++i) { auto anchor = variableTextAnchors[i % anchorsSize]; const auto& avoidEdgesForVariableAnchor = @@ -553,6 +553,44 @@ void Placement::placeBucket( for (auto it = sortedSymbols.rbegin(); it != sortedSymbols.rend(); ++it) { placeSymbol(*it); } + } else if (stickToFirstAnchor) { + // In this case we first try to place symbols, which intersects the tile borders, so that + // those symbols will remain even if each tile is handled independently. + assert(mapMode == MapMode::Tile); + const auto& symbolInstances = bucket.symbolInstances; + std::vector> sorted(symbolInstances.begin(), + symbolInstances.end()); + optional variableAnchor; + if (!variableTextAnchors.empty()) variableAnchor = variableTextAnchors.front(); + + auto intersectsTileBorder = [&](const SymbolInstance& symbol) -> bool { + if (symbol.textCollisionFeature.boxes.empty()) return false; + Point offset{}; + if (variableAnchor) { + const CollisionBox& textBox = symbol.textCollisionFeature.boxes.front(); + float width = textBox.x2 - textBox.x1; + float height = textBox.y2 - textBox.y1; + offset = calculateVariableLayoutOffset(*variableAnchor, + width, + height, + symbol.variableTextOffset, + symbol.textBoxScale, + rotateWithMap, + pitchWithMap, + state.getBearing()); + } + return collisionIndex.featureIntersectsTileBorders( + symbol.textCollisionFeature, offset, posMatrix, pixelRatio, *tileBorders); + }; + + std::stable_sort(sorted.begin(), sorted.end(), [&](const SymbolInstance& a, const SymbolInstance& b) { + return intersectsTileBorder(a) && !intersectsTileBorder(b); + }); + + for (const SymbolInstance& symbol : sorted) { + placeSymbol(symbol); + } + } else { for (const SymbolInstance& symbol : bucket.symbolInstances) { placeSymbol(symbol); @@ -891,7 +929,8 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, } } if (symbolInstance.hasIcon()) { - const auto& opacityVertex = SymbolIconProgram::opacityVertex(opacityState.icon.placed, opacityState.icon.opacity); + const auto& opacityVertex = + SymbolIconProgram::opacityVertex(opacityState.icon.placed, opacityState.icon.opacity); auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon; if (symbolInstance.placedIconIndex) { @@ -904,7 +943,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, iconBuffer.placedSymbols[*symbolInstance.placedVerticalIconIndex].hidden = opacityState.isHidden(); } } - + auto updateIconCollisionBox = [&](const auto& feature, const bool placed, const Point& shift) { if (feature.alongLine) { return; @@ -913,45 +952,41 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, bucket.iconCollisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); }; - auto updateTextCollisionBox = [this, - &bucket, - &symbolInstance, - &state, - variablePlacement, - rotateWithMap, - pitchWithMap](const auto& feature, const bool placed) { - Point shift{0.0f, 0.0f}; - if (feature.alongLine) { - return shift; - } - bool used = true; - if (variablePlacement) { - auto foundOffset = variableOffsets.find(symbolInstance.crossTileID); - if (foundOffset != variableOffsets.end()) { - const VariableOffset& variableOffset = foundOffset->second; - // This will show either the currently placed position or the last - // successfully placed position (so you can visualize what collision - // just made the symbol disappear, and the most likely place for the - // symbol to come back) - shift = calculateVariableLayoutOffset(variableOffset.anchor, - variableOffset.width, - variableOffset.height, - variableOffset.offset, - variableOffset.textBoxScale, - rotateWithMap, - pitchWithMap, - state.getBearing()); - } else { - // No offset -> this symbol hasn't been placed since coming on-screen - // No single box is particularly meaningful and all of them would be too noisy - // Use the center box just to show something's there, but mark it "not used" - used = false; + auto updateTextCollisionBox = + [this, &bucket, &symbolInstance, &state, variablePlacement, rotateWithMap, pitchWithMap]( + const auto& feature, const bool placed) { + Point shift{0.0f, 0.0f}; + if (feature.alongLine) { + return shift; } - } - const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !used, shift); - bucket.textCollisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); - return shift; - }; + bool used = true; + if (variablePlacement) { + auto foundOffset = variableOffsets.find(symbolInstance.crossTileID); + if (foundOffset != variableOffsets.end()) { + const VariableOffset& variableOffset = foundOffset->second; + // This will show either the currently placed position or the last + // successfully placed position (so you can visualize what collision + // just made the symbol disappear, and the most likely place for the + // symbol to come back) + shift = calculateVariableLayoutOffset(variableOffset.anchor, + variableOffset.width, + variableOffset.height, + variableOffset.offset, + variableOffset.textBoxScale, + rotateWithMap, + pitchWithMap, + state.getBearing()); + } else { + // No offset -> this symbol hasn't been placed since coming on-screen + // No single box is particularly meaningful and all of them would be too noisy + // Use the center box just to show something's there, but mark it "not used" + used = false; + } + } + const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !used, shift); + bucket.textCollisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); + return shift; + }; auto updateCollisionCircles = [&](const auto& feature, const bool placed, bool isText) { if (!feature.alongLine) { -- cgit v1.2.1