[core] Introduce PlacementContext
Make `placeSymbol()` a method and introduce copiable `PlacementContext`.
This commit is contained in:
@@ -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;
|
||||
|
||||
mat4 textLabelPlaneMatrix =
|
||||
getLabelPlaneMatrix(posMatrix, pitchTextWithMap, rotateTextWithMap, state, pixelsToTileUnits);
|
||||
|
||||
mat4 iconLabelPlaneMatrix;
|
||||
if (rotateTextWithMap == rotateIconWithMap && pitchTextWithMap == pitchIconWithMap) {
|
||||
iconLabelPlaneMatrix = textLabelPlaneMatrix;
|
||||
} else {
|
||||
iconLabelPlaneMatrix =
|
||||
getLabelPlaneMatrix(posMatrix, pitchIconWithMap, rotateIconWithMap, state, pixelsToTileUnits);
|
||||
}
|
||||
|
||||
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 bool hasIconTextFit = layout.get<style::IconTextFit>() != style::IconTextFitType::None;
|
||||
|
||||
std::vector<ProjectedCollisionBox> textBoxes;
|
||||
std::vector<ProjectedCollisionBox> iconBoxes;
|
||||
|
||||
auto placeSymbol = [&](const SymbolInstance& symbolInstance) {
|
||||
if (symbolInstance.crossTileID == SymbolInstance::invalidCrossTileID() ||
|
||||
seenCrossTileIDs.count(symbolInstance.crossTileID) != 0u)
|
||||
return;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placed = placeHorizontalFn();
|
||||
}
|
||||
};
|
||||
|
||||
// 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,
|
||||
textLabelPlaneMatrix,
|
||||
pixelRatio,
|
||||
placedSymbol,
|
||||
scale,
|
||||
fontSize,
|
||||
textAllowOverlap,
|
||||
pitchTextWithMap,
|
||||
showCollisionBoxes,
|
||||
avoidEdges,
|
||||
collisionGroup.second,
|
||||
textBoxes);
|
||||
if (placedFeature.first) {
|
||||
placedOrientations.emplace(symbolInstance.crossTileID, orientation);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
}
|
||||
|
||||
variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
|
||||
symbolInstance.variableTextOffset,
|
||||
width,
|
||||
height,
|
||||
anchor,
|
||||
textBoxScale,
|
||||
prevAnchor
|
||||
}));
|
||||
|
||||
if (bucket.allowVerticalPlacement) {
|
||||
placedOrientations.emplace(symbolInstance.crossTileID, orientation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
placeTextForPlacementModes(placeHorizontal, placeVertical);
|
||||
|
||||
placeText = placed.first;
|
||||
offscreen &= placed.second;
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
const bool iconWithoutText = !symbolInstance.hasText() || layout.get<style::TextOptional>();
|
||||
const bool textWithoutIcon = !symbolInstance.hasIcon() || layout.get<style::IconOptional>();
|
||||
|
||||
// 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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (hasIconCollisionCircleData && symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) {
|
||||
collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes;
|
||||
}
|
||||
if (hasTextCollisionCircleData && symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) {
|
||||
collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes;
|
||||
}
|
||||
|
||||
assert(symbolInstance.crossTileID != 0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
for (const SymbolInstance& symbol : getSortedSymbols(params, pixelRatio)) {
|
||||
placeSymbol(symbol);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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));
|
||||
retainedQueryData.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(symbolBucket.bucketInstanceId),
|
||||
std::forward_as_tuple(symbolBucket.bucketInstanceId, params.featureIndex, ctx.getOverscaledID()));
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
if (placed.first) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placed = placeHorizontalFn();
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
variableTextAnchors = std::move(filtered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (!isFirstAnchor &&
|
||||
stickToFirstVariableAnchor(
|
||||
symbolInstance.textCollisionFeature.boxes.front(), shift, posMatrix, ctx.pixelRatio)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
variableOffsets.insert(std::make_pair(
|
||||
symbolInstance.crossTileID,
|
||||
VariableOffset{
|
||||
symbolInstance.variableTextOffset, width, height, anchor, textBoxScale, prevAnchor}));
|
||||
|
||||
if (bucket.allowVerticalPlacement) {
|
||||
placedOrientations.emplace(symbolInstance.crossTileID, orientation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
placeTextForPlacementModes(placeHorizontal, placeVertical);
|
||||
|
||||
placeText = placed.first;
|
||||
offscreen &= placed.second;
|
||||
|
||||
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 (symbolInstance.placedIconIndex) {
|
||||
if (!ctx.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(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);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const bool iconWithoutText = !symbolInstance.hasText() || ctx.getLayout().get<TextOptional>();
|
||||
const bool textWithoutIcon = !symbolInstance.hasIcon() || ctx.getLayout().get<IconOptional>();
|
||||
|
||||
// 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 (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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
const bool hasIconCollisionCircleData = bucket.hasIconCollisionCircleData();
|
||||
const bool hasTextCollisionCircleData = bucket.hasTextCollisionCircleData();
|
||||
|
||||
if (hasIconCollisionCircleData && symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) {
|
||||
collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes;
|
||||
}
|
||||
if (hasTextCollisionCircleData && symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) {
|
||||
collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes;
|
||||
}
|
||||
|
||||
assert(symbolInstance.crossTileID != 0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user