diff options
author | Mikhail Pozdnyakov <mikhail.pozdnyakov@mapbox.com> | 2019-03-14 10:25:53 +0200 |
---|---|---|
committer | Mikhail Pozdnyakov <mikhail.pozdnyakov@mapbox.com> | 2019-04-01 20:42:09 +0300 |
commit | 476b6af436e6c65e3d8270cf801e99a776490020 (patch) | |
tree | 2bebe41d018fbaeb0cfea1108874e98c6730c994 | |
parent | 3e6ab2236affbdf7d3e0cb892e504bad3c92c586 (diff) | |
download | qtlocation-mapboxgl-476b6af436e6c65e3d8270cf801e99a776490020.tar.gz |
[core] Introduce variable text placement for point labels - Placement part
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/programs/collision_box_program.hpp | 18 | ||||
-rw-r--r-- | src/mbgl/renderer/layers/render_symbol_layer.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/renderer/paint_parameters.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/renderer/paint_parameters.hpp | 5 | ||||
-rw-r--r-- | src/mbgl/renderer/renderer_impl.cpp | 16 | ||||
-rw-r--r-- | src/mbgl/text/collision_index.cpp | 9 | ||||
-rw-r--r-- | src/mbgl/text/collision_index.hpp | 1 | ||||
-rw-r--r-- | src/mbgl/text/placement.cpp | 263 | ||||
-rw-r--r-- | src/mbgl/text/placement.hpp | 18 |
10 files changed, 267 insertions, 73 deletions
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 52ec2aa843..f691d406f1 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -739,7 +739,7 @@ void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) { // Dynamic vertices are initialized so that the vertex count always agrees with // the layout vertex buffer, but they will always be updated before rendering happens - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(false, false); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(false, false, {}); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index 7b81752a94..9f1da81a1a 100644 --- a/src/mbgl/programs/collision_box_program.hpp +++ b/src/mbgl/programs/collision_box_program.hpp @@ -13,10 +13,9 @@ namespace mbgl { using CollisionBoxLayoutAttributes = TypeList< attributes::a_pos, attributes::a_anchor_pos, - attributes::a_extrude, - attributes::a_shift>; + attributes::a_extrude>; -using CollisionBoxDynamicAttributes = TypeList<attributes::a_placed>; +using CollisionBoxDynamicAttributes = TypeList<attributes::a_placed, attributes::a_shift>; class CollisionBoxProgram : public Program< CollisionBoxProgram, @@ -45,17 +44,14 @@ public: {{ static_cast<int16_t>(::round(o.x)), static_cast<int16_t>(::round(o.y)) - }}, - {{ - 0.0f, - 0.0f }} }; } - static gfx::Vertex<CollisionBoxDynamicAttributes> dynamicVertex(bool placed, bool notUsed) { + static gfx::Vertex<CollisionBoxDynamicAttributes> dynamicVertex(bool placed, bool notUsed, Point<float> shift) { return { - {{ static_cast<uint8_t>(placed), static_cast<uint8_t>(notUsed) }} + {{ static_cast<uint8_t>(placed), static_cast<uint8_t>(notUsed) }}, + {{ shift.x, shift.y }} }; } @@ -139,10 +135,6 @@ public: {{ static_cast<int16_t>(::round(o.x)), static_cast<int16_t>(::round(o.y)) - }}, - {{ - 0.0f, - 0.0f }} }; } diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index ef18a21005..ff035ab04b 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -258,8 +258,8 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { for (const PlacedSymbol& symbol : bucket.text.placedSymbols) { optional<VariableOffset> variableOffset; if (!symbol.hidden && symbol.crossTileID != 0u) { - auto it = parameters.variableOffsets.find(symbol.crossTileID); - if (it != parameters.variableOffsets.end()) { + auto it = parameters.variableOffsets.get().find(symbol.crossTileID); + if (it != parameters.variableOffsets.get().end()) { variableOffset = it->second; hasVariablePacement |= true; } diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 2237b016d2..1f858ca105 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -13,7 +13,8 @@ PaintParameters::PaintParameters(gl::Context& context_, const EvaluatedLight& evaluatedLight_, RenderStaticData& staticData_, ImageManager& imageManager_, - LineAtlas& lineAtlas_) + LineAtlas& lineAtlas_, + Placement::VariableOffsets variableOffsets_) : context(context_), backend(backend_), state(updateParameters.transformState), @@ -26,6 +27,7 @@ PaintParameters::PaintParameters(gl::Context& context_, contextMode(contextMode_), timePoint(updateParameters.timePoint), pixelRatio(pixelRatio_), + variableOffsets(variableOffsets_), #ifndef NDEBUG programs((debugOptions & MapDebugOptions::Overdraw) ? staticData_.overdrawPrograms : staticData_.programs) #else diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 0b729d102b..553b4f94bf 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -38,7 +38,8 @@ public: const EvaluatedLight&, RenderStaticData&, ImageManager&, - LineAtlas&); + LineAtlas&, + Placement::VariableOffsets); gl::Context& context; RendererBackend& backend; @@ -57,7 +58,7 @@ public: TimePoint timePoint; float pixelRatio; - std::unordered_map<uint32_t, VariableOffset> variableOffsets; + Placement::VariableOffsets variableOffsets; std::array<float, 2> pixelsToGLUnits; algorithm::ClipIDGenerator clipIDGenerator; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 471536cf40..19635c7784 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -262,7 +262,8 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { renderLight.getEvaluated(), *staticData, *imageManager, - *lineAtlas + *lineAtlas, + placement->getVariableOffsets() }; bool loaded = updateParameters.styleLoaded && isLoaded(); @@ -340,26 +341,25 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { bool symbolBucketsChanged = false; const bool placementChanged = !placement->stillRecent(parameters.timePoint); - std::unique_ptr<Placement> newPlacement; std::set<std::string> usedSymbolLayers; if (placementChanged) { - newPlacement = std::make_unique<Placement>(parameters.state, parameters.mapMode, updateParameters.transitionOptions, updateParameters.crossSourceCollisions); + placement = std::make_unique<Placement>(parameters.state, parameters.mapMode, updateParameters.transitionOptions, updateParameters.crossSourceCollisions, std::move(placement)); } for (const auto& item : renderItemsWithSymbols) { if (crossTileSymbolIndex.addLayer(*item.layer.getSymbolInterface(), parameters.state.getLatLng().longitude())) symbolBucketsChanged = true; - if (newPlacement) { + if (placementChanged) { usedSymbolLayers.insert(item.layer.getID()); - newPlacement->placeLayer(*item.layer.getSymbolInterface(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); + placement->placeLayer(*item.layer.getSymbolInterface(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); } } - if (newPlacement) { - newPlacement->commit(*placement, parameters.timePoint); + if (placementChanged) { + placement->commit(parameters.timePoint); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); - placement = std::move(newPlacement); + parameters.variableOffsets = placement->getVariableOffsets(); updateFadingTiles(); } else { placement->setStale(); diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 90acb2b441..88e59bf51c 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -80,6 +80,7 @@ bool CollisionIndex::isInsideTile(const CollisionBox& box, const CollisionTileBo std::pair<bool,bool> CollisionIndex::placeFeature(CollisionFeature& feature, + Point<float> shift, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, @@ -95,10 +96,10 @@ std::pair<bool,bool> CollisionIndex::placeFeature(CollisionFeature& feature, CollisionBox& box = feature.boxes.front(); const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor); const float tileToViewport = textPixelRatio * projectedPoint.second; - box.px1 = box.x1 * tileToViewport + projectedPoint.first.x; - box.py1 = box.y1 * tileToViewport + projectedPoint.first.y; - box.px2 = box.x2 * tileToViewport + projectedPoint.first.x; - box.py2 = box.y2 * tileToViewport + projectedPoint.first.y; + box.px1 = (box.x1 + shift.x) * tileToViewport + projectedPoint.first.x; + box.py1 = (box.y1 + shift.y) * tileToViewport + projectedPoint.first.y; + box.px2 = (box.x2 + shift.x) * tileToViewport + projectedPoint.first.x; + box.py2 = (box.y2 + shift.y) * tileToViewport + projectedPoint.first.y; if ((avoidEdges && !isInsideTile(box, *avoidEdges)) || diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index dac0aa0bf7..dd160c945c 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -23,6 +23,7 @@ public: explicit CollisionIndex(const TransformState&); std::pair<bool,bool> placeFeature(CollisionFeature& feature, + Point<float> shift, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index bb7e2d1a6c..9d0fcf7b32 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -1,10 +1,13 @@ #include <mbgl/text/placement.hpp> + +#include <mbgl/layout/symbol_layout.hpp> #include <mbgl/renderer/render_layer.hpp> #include <mbgl/renderer/layers/render_layer_symbol_interface.hpp> #include <mbgl/renderer/render_tile.hpp> #include <mbgl/tile/geometry_tile.hpp> #include <mbgl/renderer/buckets/symbol_bucket.hpp> #include <mbgl/renderer/bucket.hpp> +#include <mbgl/util/math.hpp> namespace mbgl { @@ -55,13 +58,18 @@ const CollisionGroups::CollisionGroup& CollisionGroups::get(const std::string& s } } -Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions) +Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions, std::unique_ptr<Placement> prevPlacement_) : collisionIndex(state_) , state(state_) , mapMode(mapMode_) , transitionOptions(transitionOptions_) , collisionGroups(crossSourceCollisions) -{} + , prevPlacement(std::move(prevPlacement_)) +{ + if (prevPlacement) { + prevPlacement->prevPlacement.reset(); // Only hold on to one placement back + } +} void Placement::placeLayer(const RenderLayerSymbolInterface& symbolInterface, const mat4& projMatrix, bool showCollisionBoxes) { @@ -121,6 +129,19 @@ void Placement::placeLayer(const RenderLayerSymbolInterface& symbolInterface, co } } +namespace { +Point<float> calculateVariableLayoutOffset(style::SymbolAnchorType anchor, float width, float height, float radialOffset, float textBoxScale) { + AnchorAlignment alignment = AnchorAlignment::getAnchorAlignment(anchor); + float shiftX = -(alignment.horizontalAlign - 0.5f) * width; + float shiftY = -(alignment.verticalAlign - 0.5f) * height; + Point<float> offset = SymbolLayout::evaluateRadialOffset(anchor, radialOffset); + return Point<float>( + shiftX + offset.x * textBoxScale, + shiftY + offset.y * textBoxScale + ); +} +} // namespace + void Placement::placeLayerBucket( SymbolBucket& bucket, const mat4& posMatrix, @@ -161,8 +182,11 @@ void Placement::placeLayerBucket( // See https://github.com/mapbox/mapbox-gl-native/issues/12683 const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get<style::IconOptional>()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get<style::TextOptional>()); + std::vector<style::TextVariableAnchorType> variableTextAnchors = bucket.layout.get<style::TextVariableAnchor>(); + const bool rotateWithMap = bucket.layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map; + const bool pitchWithMap = bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map; - for (auto& symbolInstance : bucket.symbolInstances) { + for (SymbolInstance& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { if (holdingForFade) { @@ -175,30 +199,111 @@ void Placement::placeLayerBucket( bool placeText = false; bool placeIcon = false; bool offscreen = true; - - if (symbolInstance.placedRightTextIndex) { - PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); + optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex(); + if (horizontalTextIndex) { + CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature; + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); - - auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, - posMatrix, textLabelPlaneMatrix, textPixelRatio, - placedSymbol, scale, fontSize, - bucket.layout.get<style::TextAllowOverlap>(), - bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map, - showCollisionBoxes, avoidEdges, collisionGroup.second); - placeText = placed.first; - offscreen &= placed.second; + if (variableTextAnchors.empty()) { + auto placed = collisionIndex.placeFeature(textCollisionFeature, {}, + posMatrix, textLabelPlaneMatrix, textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get<style::TextAllowOverlap>(), + pitchWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second); + placeText = placed.first; + offscreen &= placed.second; + } else if (!textCollisionFeature.alongLine && !textCollisionFeature.boxes.empty()) { + const CollisionBox& textBox = symbolInstance.textCollisionFeature.boxes[0]; + const float width = textBox.x2 - textBox.x1; + const float height = textBox.y2 - textBox.y1; + const float textBoxScale = symbolInstance.textBoxScale; + + // If this symbol was in the last placement, shift the previously used + // anchor to the front of the anchor list. + if (prevPlacement) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end() && + variableTextAnchors.front() != prevOffset->second.anchor) { + std::vector<style::TextVariableAnchorType> filtered; + filtered.reserve(variableTextAnchors.size()); + filtered.push_back(prevOffset->second.anchor); + for (auto anchor : variableTextAnchors) { + if (anchor != prevOffset->second.anchor) { + filtered.push_back(anchor); + } + } + variableTextAnchors = std::move(filtered); + } + } + + for (auto anchor : variableTextAnchors) { + Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale); + if (rotateWithMap) { + float angle = pitchWithMap ? state.getBearing() : -state.getBearing(); + shift = util::rotate(shift, angle); + } + + auto placed = collisionIndex.placeFeature(textCollisionFeature, shift, + posMatrix, mat4(), textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get<style::TextAllowOverlap>(), + pitchWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second); + + if (placed.first) { + assert(symbolInstance.crossTileID != 0u); + optional<style::TextVariableAnchorType> prevAnchor; + + // If this label was placed in the previous placement, record the anchor position + // to allow us to animate the transition + if (prevPlacement) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end() && + prevPlacements != prevPlacement->placements.end() && + prevPlacements->second.text) { + prevAnchor = prevOffset->second.anchor; + } + } + + variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{ + symbolInstance.radialTextOffset, + width, + height, + anchor, + textBoxScale, + prevAnchor + })); + markUsedJustification(bucket, anchor, symbolInstance); + + placeText = placed.first; + offscreen &= placed.second; + break; + } + } + + // If we didn't get placed, we still need to copy our position from the last placement for + // fade animations + if (prevPlacement && variableOffsets.find(symbolInstance.crossTileID) == variableOffsets.end()) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end()) { + variableOffsets[symbolInstance.crossTileID] = prevOffset->second; + markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance); + } + } + } } if (symbolInstance.placedIconIndex) { PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol); - auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, + auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {}, posMatrix, iconLabelPlaneMatrix, textPixelRatio, placedSymbol, scale, fontSize, bucket.layout.get<style::IconAllowOverlap>(), - bucket.layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map, + pitchWithMap, showCollisionBoxes, avoidEdges, collisionGroup.second); placeIcon = placed.first; offscreen &= placed.second; @@ -240,7 +345,8 @@ void Placement::placeLayerBucket( bucket.justReloaded = false; } -void Placement::commit(const Placement& prevPlacement, TimePoint now) { +void Placement::commit(TimePoint now) { + assert(prevPlacement); commitTime = now; bool placementChanged = false; @@ -248,13 +354,13 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { float increment = mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0) ? - std::chrono::duration<float>(commitTime - prevPlacement.commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) : + std::chrono::duration<float>(commitTime - prevPlacement->commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) : 1.0; // add the opacities from the current placement, and copy their current values from the previous placement for (auto& jointPlacement : placements) { - auto prevOpacity = prevPlacement.opacities.find(jointPlacement.first); - if (prevOpacity != prevPlacement.opacities.end()) { + auto prevOpacity = prevPlacement->opacities.find(jointPlacement.first); + if (prevOpacity != prevPlacement->opacities.end()) { opacities.emplace(jointPlacement.first, JointOpacityState(prevOpacity->second, increment, jointPlacement.second.text, jointPlacement.second.icon)); placementChanged = placementChanged || jointPlacement.second.icon != prevOpacity->second.icon.placed || @@ -266,7 +372,7 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { } // copy and update values from the previous placement that aren't in the current placement but haven't finished fading - for (auto& prevOpacity : prevPlacement.opacities) { + for (auto& prevOpacity : prevPlacement->opacities) { if (opacities.find(prevOpacity.first) == opacities.end()) { JointOpacityState jointOpacity(prevOpacity.second, increment, false, false); if (!jointOpacity.isHidden()) { @@ -276,7 +382,16 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { } } - fadeStartTime = placementChanged ? commitTime : prevPlacement.fadeStartTime; + for (auto& prevOffset : prevPlacement->variableOffsets) { + const uint32_t crossTileID = prevOffset.first; + auto foundOffset = variableOffsets.find(crossTileID); + auto foundOpacity = opacities.find(crossTileID); + if (foundOffset == variableOffsets.end() && foundOpacity != opacities.end() && !foundOpacity->second.isHidden()) { + variableOffsets[prevOffset.first] = prevOffset.second; + } + } + + fadeStartTime = placementChanged ? commitTime : prevPlacement->fadeStartTime; } void Placement::updateLayerOpacities(const RenderLayerSymbolInterface& symbolInterface) { @@ -310,7 +425,10 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& const bool textAllowOverlap = bucket.layout.get<style::TextAllowOverlap>(); const bool iconAllowOverlap = bucket.layout.get<style::IconAllowOverlap>(); - + const bool variablePlacement = !bucket.layout.get<style::TextVariableAnchor>().empty(); + const bool rotateWithMap = bucket.layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map; + const bool pitchWithMap = bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map; + // If allow-overlap is true, we can show symbols before placement runs on them // But we have to wait for placement if we potentially depend on a paired icon/text // with allow-overlap: false. @@ -339,19 +457,38 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& if (symbolInstance.hasText) { auto opacityVertex = SymbolSDFTextProgram::opacityVertex(opacityState.text.placed, opacityState.text.opacity); - for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { - bucket.text.opacityVertices.emplace_back(opacityVertex); - } - for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { - bucket.text.opacityVertices.emplace_back(opacityVertex); - } if (symbolInstance.placedRightTextIndex) { + for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedRightTextIndex]; placed.hidden = opacityState.isHidden(); } + if (symbolInstance.placedCenterTextIndex) { + for (size_t i = 0; i < symbolInstance.centerJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedCenterTextIndex]; + placed.hidden = opacityState.isHidden(); + } + if (symbolInstance.placedLeftTextIndex) { + for (size_t i = 0; i < symbolInstance.leftJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedLeftTextIndex]; + placed.hidden = opacityState.isHidden(); + } if (symbolInstance.placedVerticalTextIndex) { + for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden(); } + + auto prevOffset = variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != variableOffsets.end()) { + markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance); + } } if (symbolInstance.hasIcon) { auto opacityVertex = SymbolIconProgram::opacityVertex(opacityState.icon.placed, opacityState.icon.opacity); @@ -370,7 +507,42 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& if (feature.alongLine) { return; } - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false, {}); + for (size_t i = 0; i < feature.boxes.size() * 4; i++) { + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + } + }; + + auto updateCollisionTextBox = [this, &bucket, &symbolInstance, variablePlacement, rotateWithMap, pitchWithMap](const auto& feature, const bool placed) { + if (feature.alongLine) { + return; + } + Point<float> 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.radialOffset, + variableOffset.textBoxScale); + if (rotateWithMap) { + shift = util::rotate(shift, pitchWithMap ? state.getBearing() : -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 dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !used, shift); for (size_t i = 0; i < feature.boxes.size() * 4; i++) { bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); } @@ -381,7 +553,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& return; } for (const CollisionBox& box : feature.boxes) { - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !box.used); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !box.used, {}); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); @@ -390,7 +562,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& }; if (bucket.hasCollisionBoxData()) { - updateCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed); + updateCollisionTextBox(symbolInstance.textCollisionFeature, opacityState.text.placed); updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed); } if (bucket.hasCollisionCircleData()) { @@ -407,6 +579,31 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& } } +void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance) { + std::map<style::TextJustifyType, optional<size_t>> justificationToIndex { + {style::TextJustifyType::Right, symbolInstance.placedRightTextIndex}, + {style::TextJustifyType::Center, symbolInstance.placedCenterTextIndex}, + {style::TextJustifyType::Left, symbolInstance.placedLeftTextIndex}, + }; + style::TextJustifyType justify = getAnchorJustification(placedAnchor); + assert(justify == style::TextJustifyType::Right || justify == style::TextJustifyType::Center || justify == style::TextJustifyType::Left); + const optional<size_t> autoIndex = justificationToIndex[justify]; + + for (auto& pair : justificationToIndex) { + const optional<size_t> index = pair.second; + if (index) { + assert(bucket.text.placedSymbols.size() > *index); + if (autoIndex && *index != *autoIndex) { + // There are multiple justifications and this one isn't it: shift offscreen + bucket.text.placedSymbols.at(*index).crossTileID = 0u; + } else { + // Either this is the chosen justification or the justification is hardwired: use this one + bucket.text.placedSymbols.at(*index).crossTileID = symbolInstance.crossTileID; + } + } + } +} + float Placement::symbolFadeChange(TimePoint now) const { if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) { diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 673ea59c24..3f2a7b8a03 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -12,6 +12,7 @@ namespace mbgl { class RenderLayerSymbolInterface; class SymbolBucket; +class SymbolInstance; class OpacityState { public: @@ -90,9 +91,9 @@ private: class Placement { public: - Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions); + Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions, std::unique_ptr<Placement> prevPlacementOrNull = nullptr); void placeLayer(const RenderLayerSymbolInterface&, const mat4&, bool showCollisionBoxes); - void commit(const Placement& prevPlacement, TimePoint); + void commit(TimePoint); void updateLayerOpacities(const RenderLayerSymbolInterface&); float symbolFadeChange(TimePoint now) const; bool hasTransitions(TimePoint now) const; @@ -104,8 +105,10 @@ public: void setStale(); const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const; -private: + using VariableOffsets = std::reference_wrapper<const std::unordered_map<uint32_t, VariableOffset>>; + VariableOffsets getVariableOffsets() const { return std::cref(variableOffsets); } +private: void placeLayerBucket( SymbolBucket&, const mat4& posMatrix, @@ -119,6 +122,7 @@ private: const CollisionGroups::CollisionGroup& collisionGroup); void updateBucketOpacities(SymbolBucket&, std::set<uint32_t>&); + void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, SymbolInstance&); CollisionIndex collisionIndex; @@ -131,17 +135,13 @@ private: std::unordered_map<uint32_t, JointPlacement> placements; std::unordered_map<uint32_t, JointOpacityState> opacities; + std::unordered_map<uint32_t, VariableOffset> variableOffsets; bool stale = false; std::unordered_map<uint32_t, RetainedQueryData> retainedQueryData; CollisionGroups collisionGroups; + std::unique_ptr<Placement> prevPlacement; }; -Point<float> calculateVariableLayoutOffset(style::SymbolAnchorType anchor, - float width, - float height, - float radialOffset, - float textBoxScale); - } // namespace mbgl |