diff options
Diffstat (limited to 'src/mbgl')
-rw-r--r-- | src/mbgl/text/placement.cpp | 796 | ||||
-rw-r--r-- | src/mbgl/text/placement.hpp | 9 |
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; }; |