Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] implement symbol-avoid-edges
Browse files Browse the repository at this point in the history
ref #3582

if `symbol-avoid-edges` is true, this prevents symbols from colliding
with tile edges.
  • Loading branch information
ansis committed Jan 21, 2016
1 parent f9c9ee9 commit 0fda834
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 42 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
],
"devDependencies": {
"aws-sdk": "^2.2.21",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#0fea16f7d1e0e0922f00da793e21a2420f6b8748",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#bc0272cdde41d027a5df1ca8fb3aa1bc49aa8149",
"node-gyp": "^3.2.1",
"request": "^2.67.0",
"tape": "^4.2.2"
Expand Down
12 changes: 8 additions & 4 deletions src/mbgl/renderer/symbol_bucket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,14 @@ void SymbolBucket::placeFeatures(CollisionTile& collisionTile) {

// Calculate the scales at which the text and icon can be placed without collision.

float glyphScale = hasText && !layout.text.allowOverlap ?
collisionTile.placeFeature(symbolInstance.textCollisionFeature) : collisionTile.minScale;
float iconScale = hasIcon && !layout.icon.allowOverlap ?
collisionTile.placeFeature(symbolInstance.iconCollisionFeature) : collisionTile.minScale;
float glyphScale = hasText ?
collisionTile.placeFeature(symbolInstance.textCollisionFeature,
layout.text.allowOverlap, layout.avoidEdges) :
collisionTile.minScale;
float iconScale = hasIcon ?
collisionTile.placeFeature(symbolInstance.iconCollisionFeature,
layout.icon.allowOverlap, layout.avoidEdges) :
collisionTile.minScale;


// Combine the scales for icons and text.
Expand Down
119 changes: 83 additions & 36 deletions src/mbgl/text/collision_tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@

namespace mbgl {

CollisionTile::CollisionTile(PlacementConfig config_) : config(config_) {
auto infinity = std::numeric_limits<float>::infinity();

CollisionTile::CollisionTile(PlacementConfig config_) : config(config_),
edges({{
// left
CollisionBox(vec2<float>(0, 0), 0, -infinity, 0, infinity, infinity),
// right
CollisionBox(vec2<float>(extent, 0), 0, -infinity, 0, infinity, infinity),
// top
CollisionBox(vec2<float>(0, 0), -infinity, 0, infinity, 0, infinity),
// bottom
CollisionBox(vec2<float>(0, extent), -infinity, 0, infinity, 0, infinity),
}}) {
tree.clear();

// Compute the transformation matrix.
const float angle_sin = std::sin(config.angle);
const float angle_cos = std::cos(config.angle);
rotationMatrix = { { angle_cos, -angle_sin, angle_sin, angle_cos } };
reverseRotationMatrix = { { angle_cos, angle_sin, -angle_sin, angle_cos } };

// Stretch boxes in y direction to account for the map tilt.
const float _yStretch = 1.0f / std::cos(config.pitch);
Expand All @@ -19,54 +32,88 @@ CollisionTile::CollisionTile(PlacementConfig config_) : config(config_) {
yStretch = std::pow(_yStretch, 1.3);
}

float CollisionTile::placeFeature(const CollisionFeature &feature) {

float minPlacementScale = minScale;
float CollisionTile::findPlacementScale(float minPlacementScale, const vec2<float>& anchor,
const CollisionBox& box, const vec2<float>& blockingAnchor, const CollisionBox& blocking) {

for (auto& box : feature.boxes) {
const auto anchor = box.anchor.matMul(rotationMatrix);

std::vector<CollisionTreeBox> blockingBoxes;
tree.query(bgi::intersects(getTreeBox(anchor, box)), std::back_inserter(blockingBoxes));
// Find the lowest scale at which the two boxes can fit side by side without overlapping.
// Original algorithm:
float s1 = (blocking.x1 - box.x2) / (anchor.x - blockingAnchor.x); // scale at which new box is to the left of old box
float s2 = (blocking.x2 - box.x1) / (anchor.x - blockingAnchor.x); // scale at which new box is to the right of old box
float s3 = (blocking.y1 - box.y2) * yStretch / (anchor.y - blockingAnchor.y); // scale at which new box is to the top of old box
float s4 = (blocking.y2 - box.y1) * yStretch / (anchor.y - blockingAnchor.y); // scale at which new box is to the bottom of old box

for (auto& blockingTreeBox : blockingBoxes) {
const auto& blocking = std::get<1>(blockingTreeBox);
auto blockingAnchor = blocking.anchor.matMul(rotationMatrix);
if (std::isnan(s1) || std::isnan(s2)) s1 = s2 = 1;
if (std::isnan(s3) || std::isnan(s4)) s3 = s4 = 1;

// Find the lowest scale at which the two boxes can fit side by side without overlapping.
// Original algorithm:
float s1 = (blocking.x1 - box.x2) / (anchor.x - blockingAnchor.x); // scale at which new box is to the left of old box
float s2 = (blocking.x2 - box.x1) / (anchor.x - blockingAnchor.x); // scale at which new box is to the right of old box
float s3 = (blocking.y1 - box.y2) * yStretch / (anchor.y - blockingAnchor.y); // scale at which new box is to the top of old box
float s4 = (blocking.y2 - box.y1) * yStretch / (anchor.y - blockingAnchor.y); // scale at which new box is to the bottom of old box
float collisionFreeScale = ::fmin(::fmax(s1, s2), ::fmax(s3, s4));

if (collisionFreeScale > blocking.maxScale) {
// After a box's maxScale the label has shrunk enough that the box is no longer needed to cover it,
// so unblock the new box at the scale that the old box disappears.
collisionFreeScale = blocking.maxScale;
}

if (std::isnan(s1) || std::isnan(s2)) s1 = s2 = 1;
if (std::isnan(s3) || std::isnan(s4)) s3 = s4 = 1;
if (collisionFreeScale > box.maxScale) {
// If the box can only be shown after it is visible, then the box can never be shown.
// But the label can be shown after this box is not visible.
collisionFreeScale = box.maxScale;
}

float collisionFreeScale = ::fmin(::fmax(s1, s2), ::fmax(s3, s4));
if (collisionFreeScale > minPlacementScale &&
collisionFreeScale >= blocking.placementScale) {
// If this collision occurs at a lower scale than previously found collisions
// and the collision occurs while the other label is visible

if (collisionFreeScale > blocking.maxScale) {
// After a box's maxScale the label has shrunk enough that the box is no longer needed to cover it,
// so unblock the new box at the scale that the old box disappears.
collisionFreeScale = blocking.maxScale;
}
// this this is the lowest scale at which the label won't collide with anything
minPlacementScale = collisionFreeScale;
}

if (collisionFreeScale > box.maxScale) {
// If the box can only be shown after it is visible, then the box can never be shown.
// But the label can be shown after this box is not visible.
collisionFreeScale = box.maxScale;
}
return minPlacementScale;
}

float CollisionTile::placeFeature(const CollisionFeature &feature, const bool allowOverlap, const bool avoidEdges) {

float minPlacementScale = minScale;

for (auto& box : feature.boxes) {
const auto anchor = box.anchor.matMul(rotationMatrix);

if (!allowOverlap) {
std::vector<CollisionTreeBox> blockingBoxes;
tree.query(bgi::intersects(getTreeBox(anchor, box)), std::back_inserter(blockingBoxes));

if (collisionFreeScale > minPlacementScale &&
collisionFreeScale >= blocking.placementScale) {
// If this collision occurs at a lower scale than previously found collisions
// and the collision occurs while the other label is visible
for (auto& blockingTreeBox : blockingBoxes) {
const auto& blocking = std::get<1>(blockingTreeBox);
auto blockingAnchor = blocking.anchor.matMul(rotationMatrix);

// this this is the lowest scale at which the label won't collide with anything
minPlacementScale = collisionFreeScale;
minPlacementScale = findPlacementScale(minPlacementScale, anchor, box, blockingAnchor, blocking);
if (minPlacementScale >= maxScale) return minPlacementScale;
}
}

if (minPlacementScale >= maxScale) return minPlacementScale;
if (avoidEdges) {
const vec2<float> tl = { box.x1, box.y1 };
const vec2<float> tr = { box.x2, box.y1 };
const vec2<float> bl = { box.x1, box.y2 };
const vec2<float> br = { box.x2, box.y2 };
const vec2<float> rtl = tl.matMul(reverseRotationMatrix);
const vec2<float> rtr = tr.matMul(reverseRotationMatrix);
const vec2<float> rbl = bl.matMul(reverseRotationMatrix);
const vec2<float> rbr = br.matMul(reverseRotationMatrix);
CollisionBox rotatedBox(box.anchor,
::fmin(::fmin(rtl.x, rtr.x), ::fmin(rbl.x, rbr.x)),
::fmin(::fmin(rtl.y, rtr.y), ::fmin(rbl.y, rbr.y)),
::fmax(::fmax(rtl.x, rtr.x), ::fmax(rbl.x, rbr.x)),
::fmax(::fmax(rtl.y, rtr.y), ::fmax(rbl.y, rbr.y)),
box.maxScale);

for (auto& blocking : edges) {
minPlacementScale = findPlacementScale(minPlacementScale, box.anchor, rotatedBox, blocking.anchor, blocking);

if (minPlacementScale >= maxScale) return minPlacementScale;
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/mbgl/text/collision_tile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CollisionTile {
public:
explicit CollisionTile(PlacementConfig);

float placeFeature(const CollisionFeature& feature);
float placeFeature(const CollisionFeature& feature, const bool allowOverlap, const bool avoidEdges);
void insertFeature(CollisionFeature& feature, const float minPlacementScale);

const PlacementConfig config;
Expand All @@ -47,10 +47,16 @@ class CollisionTile {
float yStretch;

private:
float findPlacementScale(float minPlacementScale,
const vec2<float>& anchor, const CollisionBox& box,
const vec2<float>& blockingAnchor, const CollisionBox& blocking);
Box getTreeBox(const vec2<float>& anchor, const CollisionBox& box);

Tree tree;
std::array<float, 4> rotationMatrix;
std::array<float, 4> reverseRotationMatrix;
const float extent = 4096;
std::array<CollisionBox, 4> edges;
};

} // namespace mbgl
Expand Down

0 comments on commit 0fda834

Please sign in to comment.