summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Pozdnyakov <mikhail.pozdnyakov@mapbox.com>2020-04-01 15:20:21 +0300
committerMikhail Pozdnyakov <mikhail.pozdnyakov@mapbox.com>2020-04-08 11:19:37 +0300
commita32672b9e6f3b667b22edced59300e26e17f7bde (patch)
tree696008428430d94bd21b49fb9d6adffc8ba1647c
parent42dc1e997f4e35be1dc0f89f2e78116b6c449a74 (diff)
downloadqtlocation-mapboxgl-a32672b9e6f3b667b22edced59300e26e17f7bde.tar.gz
[core] Introduce PlacementContext
Make `placeSymbol()` a method and introduce copiable `PlacementContext`.
-rw-r--r--src/mbgl/text/placement.cpp796
-rw-r--r--src/mbgl/text/placement.hpp9
2 files changed, 434 insertions, 371 deletions
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index 3cba1b8e16..cf73c0c5a7 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -59,6 +59,93 @@ const CollisionGroups::CollisionGroup& CollisionGroups::get(const std::string& s
}
}
+using namespace style;
+
+// PlacementContext implemenation
+class PlacementContext {
+ std::reference_wrapper<const SymbolBucket> bucket;
+ std::reference_wrapper<const RenderTile> renderTile;
+ std::reference_wrapper<const TransformState> state;
+
+public:
+ PlacementContext(const SymbolBucket& bucket_,
+ const RenderTile& renderTile_,
+ const TransformState& state_,
+ float placementZoom,
+ const CollisionGroups::CollisionGroup& collisionGroup_,
+ optional<CollisionBoundaries> avoidEdges_ = nullopt)
+ : bucket(bucket_),
+ renderTile(renderTile_),
+ state(state_),
+ pixelsToTileUnits(renderTile_.id.pixelsToTileUnits(1, placementZoom)),
+ scale(std::pow(2, placementZoom - getOverscaledID().overscaledZ)),
+ pixelRatio(util::tileSize * getOverscaledID().overscaleFactor() / util::EXTENT),
+ collisionGroup(collisionGroup_),
+ partiallyEvaluatedTextSize(bucket_.textSizeBinder->evaluateForZoom(placementZoom)),
+ partiallyEvaluatedIconSize(bucket_.iconSizeBinder->evaluateForZoom(placementZoom)),
+ avoidEdges(std::move(avoidEdges_)) {}
+
+ const SymbolBucket& getBucket() const { return bucket.get(); }
+ const style::SymbolLayoutProperties::PossiblyEvaluated& getLayout() const { return *getBucket().layout; }
+ const RenderTile& getRenderTile() const { return renderTile.get(); }
+
+ const OverscaledTileID& getOverscaledID() const { return renderTile.get().getOverscaledTileID(); }
+
+ const TransformState& getTransformState() const { return state; }
+
+ const std::vector<style::TextVariableAnchorType>& getVariableTextAnchors() const {
+ return getLayout().get<style::TextVariableAnchor>();
+ }
+
+ const float pixelsToTileUnits;
+ const float scale;
+ const float pixelRatio;
+
+ const bool rotateTextWithMap = getLayout().get<TextRotationAlignment>() == AlignmentType::Map;
+ const bool pitchTextWithMap = getLayout().get<TextPitchAlignment>() == AlignmentType::Map;
+ const bool rotateIconWithMap = getLayout().get<IconRotationAlignment>() == AlignmentType::Map;
+ const bool pitchIconWithMap = getLayout().get<IconPitchAlignment>() == AlignmentType::Map;
+ const SymbolPlacementType placementType = getLayout().get<SymbolPlacement>();
+
+ const mat4 textLabelPlaneMatrix =
+ getLabelPlaneMatrix(renderTile.get().matrix, pitchTextWithMap, rotateTextWithMap, state, pixelsToTileUnits);
+ const mat4 iconLabelPlaneMatrix =
+ (rotateTextWithMap == rotateIconWithMap && pitchTextWithMap == pitchIconWithMap)
+ ? textLabelPlaneMatrix
+ : getLabelPlaneMatrix(
+ renderTile.get().matrix, pitchIconWithMap, rotateIconWithMap, state, pixelsToTileUnits);
+
+ const CollisionGroups::CollisionGroup collisionGroup;
+ const ZoomEvaluatedSize partiallyEvaluatedTextSize;
+ const ZoomEvaluatedSize partiallyEvaluatedIconSize;
+
+ const bool textAllowOverlap = getLayout().get<style::TextAllowOverlap>();
+ const bool iconAllowOverlap = getLayout().get<style::IconAllowOverlap>();
+ // This logic is similar to the "defaultOpacityState" logic below in updateBucketOpacities
+ // If we know a symbol is always supposed to show, force it to be marked visible even if
+ // it wasn't placed into the collision index (because some or all of it was outside the range
+ // of the collision grid).
+ // There is a subtle edge case here we're accepting:
+ // Symbol A has text-allow-overlap: true, icon-allow-overlap: true, icon-optional: false
+ // A's icon is outside the grid, so doesn't get placed
+ // A's text would be inside grid, but doesn't get placed because of icon-optional: false
+ // We still show A because of the allow-overlap settings.
+ // Symbol B has allow-overlap: false, and gets placed where A's text would be
+ // On panning in, there is a short period when Symbol B and Symbol A will overlap
+ // This is the reverse of our normal policy of "fade in on pan", but should look like any other
+ // collision and hopefully not be too noticeable.
+ // See https://github.com/mapbox/mapbox-gl-native/issues/12683
+ const bool alwaysShowText =
+ textAllowOverlap && (iconAllowOverlap || !(getBucket().hasIconData() || getBucket().hasSdfIconData()) ||
+ getLayout().get<style::IconOptional>());
+ const bool alwaysShowIcon =
+ iconAllowOverlap && (textAllowOverlap || !getBucket().hasTextData() || getLayout().get<style::TextOptional>());
+
+ const bool hasIconTextFit = getLayout().get<IconTextFit>() != IconTextFitType::None;
+
+ const optional<CollisionBoundaries> avoidEdges;
+};
+
// PlacementController implemenation
PlacementController::PlacementController() : placement(makeMutable<Placement>()) {}
@@ -143,419 +230,388 @@ Point<float> calculateVariableLayoutOffset(style::SymbolAnchorType anchor,
void Placement::placeSymbolBucket(const BucketPlacementData& params, std::set<uint32_t>& seenCrossTileIDs) {
assert(updateParameters);
- const auto& bucket = static_cast<const SymbolBucket&>(params.bucket.get());
- const auto& layout = *bucket.layout;
+ const SymbolBucket& symbolBucket = static_cast<const SymbolBucket&>(params.bucket.get());
const RenderTile& renderTile = params.tile;
- const auto& state = collisionIndex.getTransformState();
- const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, placementZoom);
- const OverscaledTileID& overscaledID = renderTile.getOverscaledTileID();
- const float scale = std::pow(2, placementZoom - overscaledID.overscaledZ);
- const float pixelRatio = (util::tileSize * overscaledID.overscaleFactor()) / util::EXTENT;
-
- const bool rotateTextWithMap = layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map;
- const bool pitchTextWithMap = layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map;
-
- const bool rotateIconWithMap = layout.get<style::IconRotationAlignment>() == style::AlignmentType::Map;
- const bool pitchIconWithMap = layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map;
- const style::SymbolPlacementType placementType = layout.get<style::SymbolPlacement>();
- const mat4& posMatrix = renderTile.matrix;
+ PlacementContext ctx{symbolBucket,
+ params.tile,
+ collisionIndex.getTransformState(),
+ placementZoom,
+ collisionGroups.get(params.sourceId),
+ getAvoidEdges(symbolBucket, renderTile.matrix)};
+ for (const SymbolInstance& symbol : getSortedSymbols(params, ctx.pixelRatio)) {
+ placeSymbol(symbol, ctx, seenCrossTileIDs);
+ }
- mat4 textLabelPlaneMatrix =
- getLabelPlaneMatrix(posMatrix, pitchTextWithMap, rotateTextWithMap, state, pixelsToTileUnits);
+ // As long as this placement lives, we have to hold onto this bucket's
+ // matching FeatureIndex/data for querying purposes
+ retainedQueryData.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(symbolBucket.bucketInstanceId),
+ std::forward_as_tuple(symbolBucket.bucketInstanceId, params.featureIndex, ctx.getOverscaledID()));
+}
- mat4 iconLabelPlaneMatrix;
- if (rotateTextWithMap == rotateIconWithMap && pitchTextWithMap == pitchIconWithMap) {
- iconLabelPlaneMatrix = textLabelPlaneMatrix;
- } else {
- iconLabelPlaneMatrix =
- getLabelPlaneMatrix(posMatrix, pitchIconWithMap, rotateIconWithMap, state, pixelsToTileUnits);
+void Placement::placeSymbol(const SymbolInstance& symbolInstance,
+ const PlacementContext& ctx,
+ std::set<uint32_t>& seenCrossTileIDs) {
+ if (symbolInstance.crossTileID == SymbolInstance::invalidCrossTileID() ||
+ seenCrossTileIDs.count(symbolInstance.crossTileID) != 0u)
+ return;
+
+ if (ctx.getRenderTile().holdForFade()) {
+ // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
+ // know yet if we have a duplicate in a parent tile that _should_ be placed.
+ placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
+ return;
}
+ const SymbolBucket& bucket = ctx.getBucket();
+ const mat4& posMatrix = ctx.getRenderTile().matrix;
+ const auto& collisionGroup = ctx.collisionGroup;
+ auto variableTextAnchors = ctx.getVariableTextAnchors();
+ textBoxes.clear();
+ iconBoxes.clear();
+
+ bool placeText = false;
+ bool placeIcon = false;
+ bool offscreen = true;
+ std::pair<bool, bool> placed{false, false};
+ std::pair<bool, bool> placedVerticalText{false, false};
+ std::pair<bool, bool> placedVerticalIcon{false, false};
+ Point<float> shift{0.0f, 0.0f};
+ optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex();
+ if (horizontalTextIndex) {
+ const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex);
+ const float fontSize = evaluateSizeForFeature(ctx.partiallyEvaluatedTextSize, placedSymbol);
+
+ const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
+ if (bucket.allowVerticalPlacement && !isPlaced && getPrevPlacement()) {
+ auto prevOrientation = getPrevPlacement()->placedOrientations.find(symbolInstance.crossTileID);
+ if (prevOrientation != getPrevPlacement()->placedOrientations.end()) {
+ placedOrientations[symbolInstance.crossTileID] = prevOrientation->second;
+ }
+ }
+ };
- const auto& collisionGroup = collisionGroups.get(params.sourceId);
- auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(placementZoom);
- auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(placementZoom);
-
- optional<CollisionBoundaries> avoidEdges = getAvoidEdges(bucket, posMatrix);
- const bool textAllowOverlap = layout.get<style::TextAllowOverlap>();
- const bool iconAllowOverlap = layout.get<style::IconAllowOverlap>();
- // This logic is similar to the "defaultOpacityState" logic below in updateBucketOpacities
- // If we know a symbol is always supposed to show, force it to be marked visible even if
- // it wasn't placed into the collision index (because some or all of it was outside the range
- // of the collision grid).
- // There is a subtle edge case here we're accepting:
- // Symbol A has text-allow-overlap: true, icon-allow-overlap: true, icon-optional: false
- // A's icon is outside the grid, so doesn't get placed
- // A's text would be inside grid, but doesn't get placed because of icon-optional: false
- // We still show A because of the allow-overlap settings.
- // Symbol B has allow-overlap: false, and gets placed where A's text would be
- // On panning in, there is a short period when Symbol B and Symbol A will overlap
- // This is the reverse of our normal policy of "fade in on pan", but should look like any other
- // collision and hopefully not be too noticeable.
- // See https://github.com/mapbox/mapbox-gl-native/issues/12683
- const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !(bucket.hasIconData() || bucket.hasSdfIconData()) || layout.get<style::IconOptional>());
- const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get<style::TextOptional>());
- std::vector<style::TextVariableAnchorType> variableTextAnchors = layout.get<style::TextVariableAnchor>();
+ const auto placeTextForPlacementModes = [&](auto& placeHorizontalFn, auto& placeVerticalFn) {
+ if (bucket.allowVerticalPlacement && symbolInstance.writingModes & WritingModeType::Vertical) {
+ assert(!bucket.placementModes.empty());
+ for (auto& placementMode : bucket.placementModes) {
+ if (placementMode == TextWritingModeType::Vertical) {
+ placedVerticalText = placed = placeVerticalFn();
+ } else {
+ placed = placeHorizontalFn();
+ }
- const bool hasIconTextFit = layout.get<style::IconTextFit>() != style::IconTextFitType::None;
+ if (placed.first) {
+ break;
+ }
+ }
+ } else {
+ placed = placeHorizontalFn();
+ }
+ };
- std::vector<ProjectedCollisionBox> textBoxes;
- std::vector<ProjectedCollisionBox> iconBoxes;
+ // Line or point label placement
+ if (variableTextAnchors.empty()) {
+ const auto placeFeature = [&](const CollisionFeature& collisionFeature,
+ style::TextWritingModeType orientation) {
+ textBoxes.clear();
+ auto placedFeature = collisionIndex.placeFeature(collisionFeature,
+ {},
+ posMatrix,
+ ctx.textLabelPlaneMatrix,
+ ctx.pixelRatio,
+ placedSymbol,
+ ctx.scale,
+ fontSize,
+ ctx.textAllowOverlap,
+ ctx.pitchTextWithMap,
+ showCollisionBoxes,
+ ctx.avoidEdges,
+ collisionGroup.second,
+ textBoxes);
+ if (placedFeature.first) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
+ }
+ return placedFeature;
+ };
- auto placeSymbol = [&](const SymbolInstance& symbolInstance) {
- if (symbolInstance.crossTileID == SymbolInstance::invalidCrossTileID() ||
- seenCrossTileIDs.count(symbolInstance.crossTileID) != 0u)
- return;
+ const auto placeHorizontal = [&] {
+ return placeFeature(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ };
- if (renderTile.holdForFade()) {
- // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
- // know yet if we have a duplicate in a parent tile that _should_ be placed.
- placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
- return;
- }
- textBoxes.clear();
- iconBoxes.clear();
-
- bool placeText = false;
- bool placeIcon = false;
- bool offscreen = true;
- std::pair<bool, bool> placed{ false, false };
- std::pair<bool, bool> placedVerticalText{ false, false };
- std::pair<bool, bool> placedVerticalIcon{ false, false };
- Point<float> shift{0.0f, 0.0f};
- optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex();
- if (horizontalTextIndex) {
- const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex);
- const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);
-
- const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
- if (bucket.allowVerticalPlacement && !isPlaced && getPrevPlacement()) {
- auto prevOrientation = getPrevPlacement()->placedOrientations.find(symbolInstance.crossTileID);
- if (prevOrientation != getPrevPlacement()->placedOrientations.end()) {
- placedOrientations[symbolInstance.crossTileID] = prevOrientation->second;
- }
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeature(*symbolInstance.verticalTextCollisionFeature,
+ style::TextWritingModeType::Vertical);
}
+ return std::pair<bool, bool>{false, false};
};
- const auto placeTextForPlacementModes = [&](auto& placeHorizontalFn, auto& placeVerticalFn) {
- if (bucket.allowVerticalPlacement && symbolInstance.writingModes & WritingModeType::Vertical) {
- assert(!bucket.placementModes.empty());
- for (auto& placementMode : bucket.placementModes) {
- if (placementMode == style::TextWritingModeType::Vertical) {
- placedVerticalText = placed = placeVerticalFn();
- } else {
- placed = placeHorizontalFn();
- }
-
- if (placed.first) {
- break;
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
+ updatePreviousOrientationIfNotPlaced(placed.first);
+
+ placeText = placed.first;
+ offscreen &= placed.second;
+ } else if (!symbolInstance.textCollisionFeature.alongLine &&
+ !symbolInstance.textCollisionFeature.boxes.empty()) {
+ // If this symbol was in the last placement, shift the previously used
+ // anchor to the front of the anchor list, only if the previous anchor
+ // is still in the anchor list.
+ if (getPrevPlacement()) {
+ auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
+ if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
+ const auto prevAnchor = prevOffset->second.anchor;
+ auto found = std::find(variableTextAnchors.begin(), variableTextAnchors.end(), prevAnchor);
+ if (found != variableTextAnchors.begin() && found != variableTextAnchors.end()) {
+ std::vector<style::TextVariableAnchorType> filtered{prevAnchor};
+ if (!isTiltedView()) {
+ for (auto anchor : variableTextAnchors) {
+ if (anchor != prevAnchor) {
+ filtered.push_back(anchor);
+ }
+ }
}
+ variableTextAnchors = std::move(filtered);
}
- } else {
- placed = placeHorizontalFn();
}
- };
+ }
- // Line or point label placement
- if (variableTextAnchors.empty()) {
- const auto placeFeature = [&](const CollisionFeature& collisionFeature,
- style::TextWritingModeType orientation) {
+ const bool doVariableIconPlacement =
+ ctx.hasIconTextFit && !ctx.iconAllowOverlap && symbolInstance.placedIconIndex;
+ const auto placeFeatureForVariableAnchors = [&](const CollisionFeature& textCollisionFeature,
+ style::TextWritingModeType orientation,
+ const CollisionFeature& iconCollisionFeature) {
+ const CollisionBox& textBox = textCollisionFeature.boxes[0];
+ const float width = textBox.x2 - textBox.x1;
+ const float height = textBox.y2 - textBox.y1;
+ const float textBoxScale = symbolInstance.textBoxScale;
+ std::pair<bool, bool> placedFeature = {false, false};
+ const size_t anchorsSize = variableTextAnchors.size();
+ const size_t placementAttempts = ctx.textAllowOverlap ? anchorsSize * 2 : anchorsSize;
+ for (size_t i = 0u; i < placementAttempts; ++i) {
+ auto anchor = variableTextAnchors[i % anchorsSize];
+ const bool isFirstAnchor = (anchor == variableTextAnchors.front());
+ const bool allowOverlap = (i >= anchorsSize);
+ shift = calculateVariableLayoutOffset(anchor,
+ width,
+ height,
+ symbolInstance.variableTextOffset,
+ textBoxScale,
+ ctx.rotateTextWithMap,
+ ctx.pitchTextWithMap,
+ ctx.getTransformState().getBearing());
textBoxes.clear();
- auto placedFeature = collisionIndex.placeFeature(collisionFeature,
- {},
- posMatrix,
- textLabelPlaneMatrix,
- pixelRatio,
- placedSymbol,
- scale,
- fontSize,
- textAllowOverlap,
- pitchTextWithMap,
- showCollisionBoxes,
- avoidEdges,
- collisionGroup.second,
- textBoxes);
- if (placedFeature.first) {
- placedOrientations.emplace(symbolInstance.crossTileID, orientation);
+ if (!isFirstAnchor &&
+ stickToFirstVariableAnchor(
+ symbolInstance.textCollisionFeature.boxes.front(), shift, posMatrix, ctx.pixelRatio)) {
+ continue;
}
- return placedFeature;
- };
-
- const auto placeHorizontal = [&] {
- return placeFeature(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
- };
- const auto placeVertical = [&] {
- if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
- return placeFeature(*symbolInstance.verticalTextCollisionFeature,
- style::TextWritingModeType::Vertical);
+ placedFeature = collisionIndex.placeFeature(textCollisionFeature,
+ shift,
+ posMatrix,
+ mat4(),
+ ctx.pixelRatio,
+ placedSymbol,
+ ctx.scale,
+ fontSize,
+ allowOverlap,
+ ctx.pitchTextWithMap,
+ showCollisionBoxes,
+ ctx.avoidEdges,
+ collisionGroup.second,
+ textBoxes);
+
+ if (doVariableIconPlacement) {
+ auto placedIconFeature =
+ collisionIndex.placeFeature(iconCollisionFeature,
+ shift,
+ posMatrix,
+ ctx.iconLabelPlaneMatrix,
+ ctx.pixelRatio,
+ placedSymbol,
+ ctx.scale,
+ fontSize,
+ ctx.iconAllowOverlap,
+ ctx.pitchTextWithMap, // TODO: shall it be pitchIconWithMap?
+ showCollisionBoxes,
+ ctx.avoidEdges,
+ collisionGroup.second,
+ iconBoxes);
+ iconBoxes.clear();
+ if (!placedIconFeature.first) continue;
}
- return std::pair<bool, bool>{false, false};
- };
-
- placeTextForPlacementModes(placeHorizontal, placeVertical);
- updatePreviousOrientationIfNotPlaced(placed.first);
-
- placeText = placed.first;
- offscreen &= placed.second;
- } else if (!symbolInstance.textCollisionFeature.alongLine &&
- !symbolInstance.textCollisionFeature.boxes.empty()) {
- // If this symbol was in the last placement, shift the previously used
- // anchor to the front of the anchor list, only if the previous anchor
- // is still in the anchor list.
- if (getPrevPlacement()) {
- auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
- if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
- const auto prevAnchor = prevOffset->second.anchor;
- auto found = std::find(variableTextAnchors.begin(), variableTextAnchors.end(), prevAnchor);
- if (found != variableTextAnchors.begin() && found != variableTextAnchors.end()) {
- std::vector<style::TextVariableAnchorType> filtered{prevAnchor};
- if (!isTiltedView()) {
- for (auto anchor : variableTextAnchors) {
- if (anchor != prevAnchor) {
- filtered.push_back(anchor);
- }
- }
+
+ if (placedFeature.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 (getPrevPlacement()) {
+ auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
+ auto prevPlacements = getPrevPlacement()->placements.find(symbolInstance.crossTileID);
+ if (prevOffset != getPrevPlacement()->variableOffsets.end() &&
+ prevPlacements != getPrevPlacement()->placements.end() && prevPlacements->second.text) {
+ // TODO: The prevAnchor seems to be unused, needs to be fixed.
+ prevAnchor = prevOffset->second.anchor;
}
- variableTextAnchors = std::move(filtered);
}
- }
- }
- const bool doVariableIconPlacement =
- hasIconTextFit && !iconAllowOverlap && symbolInstance.placedIconIndex;
- const auto placeFeatureForVariableAnchors = [&](const CollisionFeature& textCollisionFeature,
- style::TextWritingModeType orientation,
- const CollisionFeature& iconCollisionFeature) {
- const CollisionBox& textBox = textCollisionFeature.boxes[0];
- const float width = textBox.x2 - textBox.x1;
- const float height = textBox.y2 - textBox.y1;
- const float textBoxScale = symbolInstance.textBoxScale;
- std::pair<bool, bool> placedFeature = {false, false};
- const size_t anchorsSize = variableTextAnchors.size();
- const size_t placementAttempts = textAllowOverlap ? anchorsSize * 2 : anchorsSize;
- for (size_t i = 0u; i < placementAttempts; ++i) {
- auto anchor = variableTextAnchors[i % anchorsSize];
- const bool isFirstAnchor = (anchor == variableTextAnchors.front());
- const bool allowOverlap = (i >= anchorsSize);
- shift = calculateVariableLayoutOffset(anchor,
- width,
- height,
- symbolInstance.variableTextOffset,
- textBoxScale,
- rotateTextWithMap,
- pitchTextWithMap,
- state.getBearing());
- textBoxes.clear();
- if (!isFirstAnchor &&
- stickToFirstVariableAnchor(
- symbolInstance.textCollisionFeature.boxes.front(), shift, posMatrix, pixelRatio)) {
- continue;
- }
+ variableOffsets.insert(std::make_pair(
+ symbolInstance.crossTileID,
+ VariableOffset{
+ symbolInstance.variableTextOffset, width, height, anchor, textBoxScale, prevAnchor}));
- placedFeature = collisionIndex.placeFeature(textCollisionFeature,
- shift,
- posMatrix,
- mat4(),
- pixelRatio,
- placedSymbol,
- scale,
- fontSize,
- allowOverlap,
- pitchTextWithMap,
- showCollisionBoxes,
- avoidEdges,
- collisionGroup.second,
- textBoxes);
-
- if (doVariableIconPlacement) {
- auto placedIconFeature =
- collisionIndex.placeFeature(iconCollisionFeature,
- shift,
- posMatrix,
- iconLabelPlaneMatrix,
- pixelRatio,
- placedSymbol,
- scale,
- fontSize,
- iconAllowOverlap,
- pitchTextWithMap, // TODO: shall it be pitchIconWithMap?
- showCollisionBoxes,
- avoidEdges,
- collisionGroup.second,
- iconBoxes);
- iconBoxes.clear();
- if (!placedIconFeature.first) continue;
+ if (bucket.allowVerticalPlacement) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
}
+ break;
+ }
+ }
- if (placedFeature.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 (getPrevPlacement()) {
- auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
- auto prevPlacements = getPrevPlacement()->placements.find(symbolInstance.crossTileID);
- if (prevOffset != getPrevPlacement()->variableOffsets.end() &&
- prevPlacements != getPrevPlacement()->placements.end() &&
- prevPlacements->second.text) {
- // TODO: The prevAnchor seems to be unused, needs to be fixed.
- prevAnchor = prevOffset->second.anchor;
- }
- }
+ return placedFeature;
+ };
- variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
- symbolInstance.variableTextOffset,
- width,
- height,
- anchor,
- textBoxScale,
- prevAnchor
- }));
-
- if (bucket.allowVerticalPlacement) {
- placedOrientations.emplace(symbolInstance.crossTileID, orientation);
- }
- break;
- }
- }
+ const auto placeHorizontal = [&] {
+ return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature,
+ style::TextWritingModeType::Horizontal,
+ symbolInstance.iconCollisionFeature);
+ };
- return placedFeature;
- };
-
- const auto placeHorizontal = [&] {
- return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature,
- style::TextWritingModeType::Horizontal,
- symbolInstance.iconCollisionFeature);
- };
-
- const auto placeVertical = [&] {
- if (bucket.allowVerticalPlacement && !placed.first && symbolInstance.verticalTextCollisionFeature) {
- return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature,
- style::TextWritingModeType::Vertical,
- symbolInstance.verticalIconCollisionFeature
- ? *symbolInstance.verticalIconCollisionFeature
- : symbolInstance.iconCollisionFeature);
- }
- return std::pair<bool, bool>{false, false};
- };
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && !placed.first && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature,
+ style::TextWritingModeType::Vertical,
+ symbolInstance.verticalIconCollisionFeature
+ ? *symbolInstance.verticalIconCollisionFeature
+ : symbolInstance.iconCollisionFeature);
+ }
+ return std::pair<bool, bool>{false, false};
+ };
- placeTextForPlacementModes(placeHorizontal, placeVertical);
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
- placeText = placed.first;
- offscreen &= placed.second;
+ placeText = placed.first;
+ offscreen &= placed.second;
- updatePreviousOrientationIfNotPlaced(placed.first);
+ updatePreviousOrientationIfNotPlaced(placed.first);
- // If we didn't get placed, we still need to copy our position from the last placement for
- // fade animations
- if (!placeText && getPrevPlacement()) {
- auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
- if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
- variableOffsets[symbolInstance.crossTileID] = prevOffset->second;
- }
+ // If we didn't get placed, we still need to copy our position from the last placement for
+ // fade animations
+ if (!placeText && getPrevPlacement()) {
+ auto prevOffset = getPrevPlacement()->variableOffsets.find(symbolInstance.crossTileID);
+ if (prevOffset != getPrevPlacement()->variableOffsets.end()) {
+ variableOffsets[symbolInstance.crossTileID] = prevOffset->second;
}
}
}
+ }
- if (symbolInstance.placedIconIndex) {
- if (!hasIconTextFit || !placeText || variableTextAnchors.empty()) {
- shift = {0.0f, 0.0f};
- }
-
- const auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon;
- const PlacedSymbol& placedSymbol = iconBuffer.placedSymbols.at(*symbolInstance.placedIconIndex);
- const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol);
- const auto& placeIconFeature = [&](const CollisionFeature& collisionFeature) {
- return collisionIndex.placeFeature(collisionFeature,
- shift,
- posMatrix,
- iconLabelPlaneMatrix,
- pixelRatio,
- placedSymbol,
- scale,
- fontSize,
- iconAllowOverlap,
- pitchTextWithMap,
- showCollisionBoxes,
- avoidEdges,
- collisionGroup.second,
- iconBoxes);
- };
-
- std::pair<bool, bool> placedIcon = {false, false};
- if (placedVerticalText.first && symbolInstance.verticalIconCollisionFeature) {
- placedIcon = placedVerticalIcon = placeIconFeature(*symbolInstance.verticalIconCollisionFeature);
- } else {
- placedIcon = placeIconFeature(symbolInstance.iconCollisionFeature);
- }
- placeIcon = placedIcon.first;
- offscreen &= placedIcon.second;
+ if (symbolInstance.placedIconIndex) {
+ if (!ctx.hasIconTextFit || !placeText || variableTextAnchors.empty()) {
+ shift = {0.0f, 0.0f};
}
- const bool iconWithoutText = !symbolInstance.hasText() || layout.get<style::TextOptional>();
- const bool textWithoutIcon = !symbolInstance.hasIcon() || layout.get<style::IconOptional>();
+ const auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon;
+ const PlacedSymbol& placedSymbol = iconBuffer.placedSymbols.at(*symbolInstance.placedIconIndex);
+ const float fontSize = evaluateSizeForFeature(ctx.partiallyEvaluatedIconSize, placedSymbol);
+ const auto& placeIconFeature = [&](const CollisionFeature& collisionFeature) {
+ return collisionIndex.placeFeature(collisionFeature,
+ shift,
+ posMatrix,
+ ctx.iconLabelPlaneMatrix,
+ ctx.pixelRatio,
+ placedSymbol,
+ ctx.scale,
+ fontSize,
+ ctx.iconAllowOverlap,
+ ctx.pitchTextWithMap,
+ showCollisionBoxes,
+ ctx.avoidEdges,
+ collisionGroup.second,
+ iconBoxes);
+ };
- // combine placements for icon and text
- if (!iconWithoutText && !textWithoutIcon) {
- placeText = placeIcon = placeText && placeIcon;
- } else if (!textWithoutIcon) {
- placeText = placeText && placeIcon;
- } else if (!iconWithoutText) {
- placeIcon = placeText && placeIcon;
+ std::pair<bool, bool> placedIcon = {false, false};
+ if (placedVerticalText.first && symbolInstance.verticalIconCollisionFeature) {
+ placedIcon = placedVerticalIcon = placeIconFeature(*symbolInstance.verticalIconCollisionFeature);
+ } else {
+ placedIcon = placeIconFeature(symbolInstance.iconCollisionFeature);
}
+ placeIcon = placedIcon.first;
+ offscreen &= placedIcon.second;
+ }
- if (placeText) {
- if (placedVerticalText.first && symbolInstance.verticalTextCollisionFeature) {
- collisionIndex.insertFeature(*symbolInstance.verticalTextCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
- } else {
- collisionIndex.insertFeature(symbolInstance.textCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
- }
- }
+ const bool iconWithoutText = !symbolInstance.hasText() || ctx.getLayout().get<TextOptional>();
+ const bool textWithoutIcon = !symbolInstance.hasIcon() || ctx.getLayout().get<IconOptional>();
- if (placeIcon) {
- if (placedVerticalIcon.first && symbolInstance.verticalIconCollisionFeature) {
- collisionIndex.insertFeature(*symbolInstance.verticalIconCollisionFeature, iconBoxes, layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
- } else {
- collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, iconBoxes, layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
- }
- }
-
- const bool hasIconCollisionCircleData = bucket.hasIconCollisionCircleData();
- const bool hasTextCollisionCircleData = bucket.hasTextCollisionCircleData();
+ // combine placements for icon and text
+ if (!iconWithoutText && !textWithoutIcon) {
+ placeText = placeIcon = placeText && placeIcon;
+ } else if (!textWithoutIcon) {
+ placeText = placeText && placeIcon;
+ } else if (!iconWithoutText) {
+ placeIcon = placeText && placeIcon;
+ }
- if (hasIconCollisionCircleData && symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) {
- collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes;
+ if (placeText) {
+ if (placedVerticalText.first && symbolInstance.verticalTextCollisionFeature) {
+ collisionIndex.insertFeature(*symbolInstance.verticalTextCollisionFeature,
+ textBoxes,
+ ctx.getLayout().get<TextIgnorePlacement>(),
+ bucket.bucketInstanceId,
+ collisionGroup.first);
+ } else {
+ collisionIndex.insertFeature(symbolInstance.textCollisionFeature,
+ textBoxes,
+ ctx.getLayout().get<TextIgnorePlacement>(),
+ bucket.bucketInstanceId,
+ collisionGroup.first);
}
- if (hasTextCollisionCircleData && symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) {
- collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes;
+ }
+
+ if (placeIcon) {
+ if (placedVerticalIcon.first && symbolInstance.verticalIconCollisionFeature) {
+ collisionIndex.insertFeature(*symbolInstance.verticalIconCollisionFeature,
+ iconBoxes,
+ ctx.getLayout().get<IconIgnorePlacement>(),
+ bucket.bucketInstanceId,
+ collisionGroup.first);
+ } else {
+ collisionIndex.insertFeature(symbolInstance.iconCollisionFeature,
+ iconBoxes,
+ ctx.getLayout().get<IconIgnorePlacement>(),
+ bucket.bucketInstanceId,
+ collisionGroup.first);
}
+ }
- assert(symbolInstance.crossTileID != 0);
+ const bool hasIconCollisionCircleData = bucket.hasIconCollisionCircleData();
+ const bool hasTextCollisionCircleData = bucket.hasTextCollisionCircleData();
- if (placements.find(symbolInstance.crossTileID) != placements.end()) {
- // If there's a previous placement with this ID, it comes from a tile that's fading out
- // Erase it so that the placement result from the non-fading tile supersedes it
- placements.erase(symbolInstance.crossTileID);
- }
+ if (hasIconCollisionCircleData && symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) {
+ collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes;
+ }
+ if (hasTextCollisionCircleData && symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) {
+ collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes;
+ }
- auto pair = placements.emplace(
- symbolInstance.crossTileID,
- JointPlacement(placeText || alwaysShowText, placeIcon || alwaysShowIcon, offscreen || bucket.justReloaded));
- assert(pair.second);
- newSymbolPlaced(symbolInstance, pair.first->second, placementType, textBoxes, iconBoxes);
- seenCrossTileIDs.insert(symbolInstance.crossTileID);
- };
+ assert(symbolInstance.crossTileID != 0);
- for (const SymbolInstance& symbol : getSortedSymbols(params, pixelRatio)) {
- placeSymbol(symbol);
+ if (placements.find(symbolInstance.crossTileID) != placements.end()) {
+ // If there's a previous placement with this ID, it comes from a tile that's fading out
+ // Erase it so that the placement result from the non-fading tile supersedes it
+ placements.erase(symbolInstance.crossTileID);
}
- // As long as this placement lives, we have to hold onto this bucket's
- // matching FeatureIndex/data for querying purposes
- retainedQueryData.emplace(std::piecewise_construct,
- std::forward_as_tuple(bucket.bucketInstanceId),
- std::forward_as_tuple(bucket.bucketInstanceId, params.featureIndex, overscaledID));
+ auto pair = placements.emplace(
+ symbolInstance.crossTileID,
+ JointPlacement(
+ placeText || ctx.alwaysShowText, placeIcon || ctx.alwaysShowIcon, offscreen || bucket.justReloaded));
+ assert(pair.second);
+ newSymbolPlaced(symbolInstance, pair.first->second, ctx.placementType, textBoxes, iconBoxes);
+ seenCrossTileIDs.insert(symbolInstance.crossTileID);
}
namespace {
@@ -1363,21 +1419,21 @@ bool TilePlacement::stickToFirstVariableAnchor(const CollisionBox& box,
void TilePlacement::newSymbolPlaced(const SymbolInstance& symbol,
const JointPlacement& placement,
style::SymbolPlacementType placementType,
- const std::vector<ProjectedCollisionBox>& textBoxes,
- const std::vector<ProjectedCollisionBox>& iconBoxes) {
+ const std::vector<ProjectedCollisionBox>& textCollisionBoxes,
+ const std::vector<ProjectedCollisionBox>& iconCollisionBoxes) {
if (!collectData || placementType != style::SymbolPlacementType::Point) return;
optional<mapbox::geometry::box<float>> textCollisionBox;
- if (!textBoxes.empty()) {
- assert(textBoxes.size() == 1u);
- auto& box = textBoxes.front();
+ if (!textCollisionBoxes.empty()) {
+ assert(textCollisionBoxes.size() == 1u);
+ auto& box = textCollisionBoxes.front();
assert(box.isBox());
textCollisionBox = box.box();
}
optional<mapbox::geometry::box<float>> iconCollisionBox;
- if (!iconBoxes.empty()) {
- assert(iconBoxes.size() == 1u);
- auto& box = iconBoxes.front();
+ if (!iconCollisionBoxes.empty()) {
+ assert(iconCollisionBoxes.size() == 1u);
+ auto& box = iconCollisionBoxes.front();
assert(box.isBox());
iconCollisionBox = box.box();
}
diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp
index 7fb5f74599..87d1e981df 100644
--- a/src/mbgl/text/placement.hpp
+++ b/src/mbgl/text/placement.hpp
@@ -91,7 +91,7 @@ private:
};
class Placement;
-
+class PlacementContext;
class PlacementController {
public:
PlacementController();
@@ -142,6 +142,9 @@ public:
protected:
friend SymbolBucket;
void placeSymbolBucket(const BucketPlacementData&, std::set<uint32_t>& seenCrossTileIDs);
+ void placeSymbol(const SymbolInstance& symbolInstance,
+ const PlacementContext&,
+ std::set<uint32_t>& seenCrossTileIDs);
void placeLayer(const RenderLayer&, std::set<uint32_t>&);
virtual void commit();
virtual void newSymbolPlaced(const SymbolInstance&,
@@ -191,6 +194,10 @@ protected:
CollisionGroups collisionGroups;
mutable optional<Immutable<Placement>> prevPlacement;
bool showCollisionBoxes = false;
+
+ // Cache being used by placeSymbol()
+ std::vector<ProjectedCollisionBox> textBoxes;
+ std::vector<ProjectedCollisionBox> iconBoxes;
// Used for debug purposes.
std::unordered_map<const CollisionFeature*, std::vector<ProjectedCollisionBox>> collisionCircles;
};