update symbol placement to be more like js

This commit is contained in:
Konstantin Käfer
2014-08-04 18:24:22 +02:00
parent 9dd50a29e1
commit 36181b0922
18 changed files with 576 additions and 573 deletions

View File

@@ -48,24 +48,26 @@ Collision::Collision(float zoom, float tileExtent, float tileSize, float placeme
// We don't want to place labels all the way to 25.5. This lets too many
// glyphs be placed, slowing down collision checking. Only place labels if
// they will show up within the intended zoom range of the tile.
maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom)))
{
maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom))) {
const float m = 4096;
const float edge = m * tilePixelRatio * 2;
// Hack to prevent cross-tile labels
insert(
{{GlyphBox{
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}}, 0},
GlyphBox{
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}}, 0}}},
CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2);
insert({GlyphBox{/* box */ CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{0, edge}},
/* minScale */ 0,
/* padding */ 2},
GlyphBox{/* box */ CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{edge, 0}},
/* minScale */ 0,
/* padding */ 2}},
CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false);
insert({{GlyphBox{
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}}, 0},
GlyphBox{
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}}, 0}}},
CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2);
insert({GlyphBox{/* box*/ CollisionRect{CollisionPoint{-edge, 0}, CollisionPoint{edge, edge}},
/* minScale */ 0,
/* padding */ 2},
GlyphBox{/* box */ CollisionRect{CollisionPoint{0, -edge}, CollisionPoint{edge, edge}},
/* minScale */ 0,
/* padding */ 2}},
CollisionAnchor(m, m), 1, {{M_PI * 2, 0}}, false);
}
GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) {
@@ -81,99 +83,51 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor)
box.tl.y = util::min(box.tl.y, gbox.tl.y);
box.br.x = util::max(box.br.x, gbox.br.x);
box.br.y = util::max(box.br.y, gbox.br.y);
mergedGlyphs.minScale =
util::max(mergedGlyphs.minScale, glyph.minScale);
mergedGlyphs.minScale = util::max(mergedGlyphs.minScale, glyph.minScale);
}
return mergedGlyphs;
}
PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAnchor &anchor,
float minPlacementScale, float maxPlacementScale, float padding,
bool horizontal, bool allowOverlap, bool ignorePlacement) {
float minScale = std::numeric_limits<float>::infinity();
for (const GlyphBox &glyphBox : boxes) {
minScale = util::min(minScale, glyphBox.minScale);
}
minPlacementScale = util::max(minPlacementScale, minScale);
Box getBox(const CollisionAnchor &anchor, const CollisionRect &bbox, float minScale,
float maxScale) {
return Box{
Point{
anchor.x + util::min(bbox.tl.x / minScale, bbox.tl.x / maxScale),
anchor.y + util::min(bbox.tl.y / minScale, bbox.tl.y / maxScale),
},
Point{
anchor.x + util::max(bbox.br.x / minScale, bbox.br.x / maxScale),
anchor.y + util::max(bbox.br.y / minScale, bbox.br.y / maxScale),
},
};
};
GlyphBoxes glyphs;
if (horizontal) {
// Collision checks between rotating and fixed labels are relatively expensive,
// so we use one box per label, not per glyph for horizontal labels.
glyphs.push_back(getMergedGlyphs(boxes, anchor));
// for all horizontal labels, calculate bbox covering all rotated positions
const CollisionRect &box = glyphs.front().box;
float x12 = box.tl.x * box.tl.x,
y12 = box.tl.y * box.tl.y,
x22 = box.br.x * box.br.x,
y22 = box.br.y * box.br.y,
diag = std::sqrt(util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22));
glyphs.front().hBox = CollisionRect{
{-diag, -diag},
{diag, diag}
};
}
// Calculate the minimum scale the entire label can be shown without
// collisions
float scale =
allowOverlap ? minPlacementScale : getPlacementScale(glyphs, minPlacementScale,
maxPlacementScale, padding);
// Return if the label can never be placed without collision
if (scale < 0 || scale == std::numeric_limits<float>::infinity()) {
return PlacementProperty{};
}
// Calculate the range it is safe to rotate all glyphs
PlacementRange rotationRange = allowOverlap ? PlacementRange{{2 * M_PI, 0.0f}}
: getPlacementRange(glyphs, scale, horizontal);
if (!ignorePlacement) insert(glyphs, anchor, scale, rotationRange, horizontal, padding);
float zoom = log(scale) / log(2);
return PlacementProperty{zoom, rotationRange};
}
float Collision::getPlacementScale(const GlyphBoxes &glyphs,
float minPlacementScale,
float maxPlacementScale, float pad) {
float Collision::getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale) {
for (const GlyphBox &glyph : glyphs) {
const CollisionRect &box = glyph.box;
const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
const CollisionAnchor &anchor = glyph.anchor;
const float pad = glyph.padding;
if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 ||
anchor.y > 4096) {
if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096) {
return -1;
}
float minScale = std::fmax(minPlacementScale, glyph.minScale);
float maxScale = glyph.maxScale;
float maxScale = glyph.maxScale || std::numeric_limits<float>::infinity();
if (minScale >= maxScale) {
continue;
}
// Compute the scaled bounding box of the unrotated glyph
float minPlacedX = anchor.x + bbox.tl.x / minScale;
float minPlacedY = anchor.y + bbox.tl.y / minScale;
float maxPlacedX = anchor.x + bbox.br.x / minScale;
float maxPlacedY = anchor.y + bbox.br.y / minScale;
Box query_box{Point{minPlacedX, minPlacedY},
Point{maxPlacedX, maxPlacedY}};
const Box searchBox = getBox(anchor, bbox, minScale, maxScale);
std::vector<PlacementValue> blocking;
((Tree *)cTree)
->query(bgi::intersects(query_box), std::back_inserter(blocking));
((Tree *)hTree)
->query(bgi::intersects(query_box), std::back_inserter(blocking));
((Tree *)cTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking));
((Tree *)hTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking));
if (blocking.size()) {
const CollisionAnchor &na = anchor; // new anchor
@@ -195,10 +149,14 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
float padding = std::fmax(pad, placement.padding) * 8.0f;
// Original algorithm:
float s1 = (ob.tl.x - nb.br.x - padding) / (na.x - oa.x); // scale at which new box is to the left of old box
float s2 = (ob.br.x - nb.tl.x + padding) / (na.x - oa.x); // scale at which new box is to the right of old box
float s3 = (ob.tl.y - nb.br.y - padding) / (na.y - oa.y); // scale at which new box is to the top of old box
float s4 = (ob.br.y - nb.tl.y + padding) / (na.y - oa.y); // scale at which new box is to the bottom of old box
float s1 = (ob.tl.x - nb.br.x - padding) /
(na.x - oa.x); // scale at which new box is to the left of old box
float s2 = (ob.br.x - nb.tl.x + padding) /
(na.x - oa.x); // scale at which new box is to the right of old box
float s3 = (ob.tl.y - nb.br.y - padding) /
(na.y - oa.y); // scale at which new box is to the top of old box
float s4 = (ob.br.y - nb.tl.y + padding) /
(na.y - oa.y); // scale at which new box is to the bottom of old box
if (isnan(s1) || isnan(s2)) {
s1 = s2 = 1;
@@ -211,10 +169,8 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
// Only update label's min scale if the glyph was
// restricted by a collision
if (collisionFreeScale > minPlacementScale &&
collisionFreeScale > minScale &&
collisionFreeScale < maxScale &&
collisionFreeScale < placement.maxScale) {
if (collisionFreeScale > minPlacementScale && collisionFreeScale > minScale &&
collisionFreeScale < maxScale && collisionFreeScale < placement.maxScale) {
minPlacementScale = collisionFreeScale;
}
@@ -228,8 +184,7 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
return minPlacementScale;
}
PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs,
float placementScale,
PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, float placementScale,
bool horizontal) {
PlacementRange placementRange = {{2.0f * M_PI, 0}};
@@ -242,16 +197,13 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs,
float maxPlacedX = anchor.x + bbox.br.x / placementScale;
float maxPlacedY = anchor.y + bbox.br.y / placementScale;
Box query_box{Point{minPlacedX, minPlacedY},
Point{maxPlacedX, maxPlacedY}};
Box query_box{Point{minPlacedX, minPlacedY}, Point{maxPlacedX, maxPlacedY}};
std::vector<PlacementValue> blocking;
((Tree *)hTree)
->query(bgi::intersects(query_box), std::back_inserter(blocking));
((Tree *)hTree)->query(bgi::intersects(query_box), std::back_inserter(blocking));
if (horizontal) {
((Tree *)cTree)
->query(bgi::intersects(query_box), std::back_inserter(blocking));
((Tree *)cTree)->query(bgi::intersects(query_box), std::back_inserter(blocking));
}
for (const PlacementValue &value : blocking) {
@@ -275,10 +227,8 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs,
x2 = anchor.x + bbox.br.x / b.placementScale;
y2 = anchor.y + bbox.br.y / b.placementScale;
intersectX = x1 < s.max_corner().get<0>() &&
x2 > s.min_corner().get<0>();
intersectY = y1 < s.max_corner().get<1>() &&
y2 > s.min_corner().get<1>();
intersectX = x1 < s.max_corner().get<0>() && x2 > s.min_corner().get<0>();
intersectY = y1 < s.max_corner().get<1>() && y2 > s.min_corner().get<1>();
}
// If they can't intersect, skip more expensive rotation calculation
@@ -298,24 +248,22 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs,
};
void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
float placementScale,
const PlacementRange &placementRange, bool horizontal,
float padding) {
float placementScale, const PlacementRange &placementRange,
bool horizontal) {
assert(placementScale != std::numeric_limits<float>::infinity());
std::vector<PlacementValue> result;
result.reserve(glyphs.size());
std::vector<PlacementValue> allBounds;
allBounds.reserve(glyphs.size());
for (const GlyphBox &glyph : glyphs) {
const CollisionRect &box = glyph.box;
const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
float minScale = std::fmax(placementScale, glyph.minScale);
const float minScale = util::max(placementScale, glyph.minScale);
const float maxScale = glyph.maxScale || std::numeric_limits<float>::infinity();
Box bounds{Point{anchor.x + bbox.tl.x / minScale,
anchor.y + bbox.tl.y / minScale},
Point{anchor.x + bbox.br.x / minScale,
anchor.y + bbox.br.y / minScale}};
Box bounds{Point{anchor.x + bbox.tl.x / minScale, anchor.y + bbox.tl.y / minScale},
Point{anchor.x + bbox.br.x / minScale, anchor.y + bbox.br.y / minScale}};
PlacementBox placement;
placement.anchor = anchor;
@@ -325,19 +273,19 @@ void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
}
placement.placementRange = placementRange;
placement.placementScale = minScale;
placement.maxScale = glyph.maxScale;
placement.padding = padding;
placement.maxScale = maxScale;
placement.padding = glyph.padding;
assert(!isnan(bounds.min_corner().get<0>()) && !isnan(bounds.min_corner().get<1>()));
assert(!isnan(bounds.max_corner().get<0>()) && !isnan(bounds.max_corner().get<1>()));
result.emplace_back(bounds, placement);
allBounds.emplace_back(bounds, placement);
}
// Bulk-insert all glyph boxes
if (horizontal) {
((Tree *)hTree)->insert(result.begin(), result.end());
((Tree *)hTree)->insert(allBounds.begin(), allBounds.end());
} else {
((Tree *)cTree)->insert(result.begin(), result.end());
((Tree *)cTree)->insert(allBounds.begin(), allBounds.end());
}
}