summaryrefslogtreecommitdiff
path: root/src/mbgl/text/collision.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/text/collision.cpp')
-rw-r--r--src/mbgl/text/collision.cpp297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/mbgl/text/collision.cpp b/src/mbgl/text/collision.cpp
new file mode 100644
index 0000000000..2e0ec6dce2
--- /dev/null
+++ b/src/mbgl/text/collision.cpp
@@ -0,0 +1,297 @@
+#include <mbgl/text/collision.hpp>
+#include <mbgl/text/rotation_range.hpp>
+#include <mbgl/util/math.hpp>
+
+
+using namespace mbgl;
+
+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),
+ },
+ };
+};
+
+Collision::Collision(float zoom_, float tileExtent, float tileSize, float placementDepth)
+ // tile pixels per screen pixels at the tile's zoom level
+ : tilePixelRatio(tileExtent / tileSize),
+
+ zoom(zoom_),
+
+ // Calculate the maximum scale we can go down in our fake-3d rtree so that
+ // placement still makes sense. This is calculated so that the minimum
+ // placement zoom can be at most 25.5 (we use an unsigned integer x10 to
+ // store the minimum zoom).
+ //
+ // 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_))) {
+ const float m = 4096;
+ const float edge = m * tilePixelRatio * 2;
+
+
+ PlacementBox left;
+ left.anchor = CollisionAnchor{ 0.0f, 0.0f };
+ left.box = CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{0, edge}};
+ left.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ left.placementScale = 0.5f;
+ left.maxScale = std::numeric_limits<float>::infinity();
+ left.padding = 0.0f;
+ leftEdge = PlacementValue{
+ getBox(left.anchor, left.box, left.placementScale, left.maxScale),
+ left};
+
+ PlacementBox top;
+ top.anchor = CollisionAnchor{ 0.0f, 0.0f };
+ top.box = CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{edge, 0}};
+ top.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ top.placementScale = 0.5f;
+ top.maxScale = std::numeric_limits<float>::infinity();
+ top.padding = 0.0f;
+ topEdge = PlacementValue{
+ getBox(top.anchor, top.box, top.placementScale, top.maxScale),
+ top};
+
+ PlacementBox bottom;
+ bottom.anchor = CollisionAnchor{ m, m };
+ bottom.box = CollisionRect{CollisionPoint{-edge, 0}, CollisionPoint{edge, edge}};
+ bottom.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ bottom.placementScale = 0.5f;
+ bottom.maxScale = std::numeric_limits<float>::infinity();
+ bottom.padding = 0.0f;
+ bottomEdge = PlacementValue{
+ getBox(bottom.anchor, bottom.box, bottom.placementScale, bottom.maxScale),
+ bottom};
+
+ PlacementBox right;
+ right.anchor = CollisionAnchor{ m, m };
+ right.box = CollisionRect{CollisionPoint{0, -edge}, CollisionPoint{edge, edge}};
+ right.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ right.placementScale = 0.5f;
+ right.maxScale = std::numeric_limits<float>::infinity();
+ right.padding = 0.0f;
+ rightEdge = PlacementValue{
+ getBox(right.anchor, right.box, right.placementScale, right.maxScale),
+ right};
+
+}
+
+GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) {
+ GlyphBox mergedGlyphs;
+ const float inf = std::numeric_limits<float>::infinity();
+ mergedGlyphs.box = CollisionRect{{inf, inf}, {-inf, -inf}};
+ mergedGlyphs.anchor = anchor;
+
+ CollisionRect &box = mergedGlyphs.box;
+ for (const GlyphBox &glyph : boxes) {
+ const CollisionRect &gbox = glyph.box;
+ box.tl.x = util::min(box.tl.x, gbox.tl.x);
+ 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);
+ }
+
+ return mergedGlyphs;
+}
+
+float Collision::getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale, bool avoidEdges) {
+
+ 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) {
+ return 0;
+ }
+
+ float minScale = std::fmax(minPlacementScale, glyph.minScale);
+ float maxScale = glyph.maxScale != 0 ? glyph.maxScale : std::numeric_limits<float>::infinity();
+
+ if (minScale >= maxScale) {
+ continue;
+ }
+
+ // Compute the scaled bounding box of the unrotated glyph
+ const Box searchBox = getBox(anchor, bbox, minScale, maxScale);
+
+ std::vector<PlacementValue> blocking;
+ hTree.query(bgi::intersects(searchBox), std::back_inserter(blocking));
+ cTree.query(bgi::intersects(searchBox), std::back_inserter(blocking));
+
+ if (avoidEdges) {
+ if (searchBox.min_corner().get<0>() < 0) blocking.emplace_back(leftEdge);
+ if (searchBox.min_corner().get<1>() < 0) blocking.emplace_back(topEdge);
+ if (searchBox.max_corner().get<0>() >= 4096) blocking.emplace_back(rightEdge);
+ if (searchBox.max_corner().get<1>() >= 4096) blocking.emplace_back(bottomEdge);
+ }
+
+ if (blocking.size()) {
+ const CollisionAnchor &na = anchor; // new anchor
+ const CollisionRect &nb = box; // new box
+
+ for (const PlacementValue &value : blocking) {
+ const PlacementBox &placement = std::get<1>(value);
+ const CollisionAnchor &oa = placement.anchor; // old anchor
+ const CollisionRect &ob = placement.box; // old box
+
+ // If anchors are identical, we're going to skip the label.
+ // NOTE: this isn't right because there can be glyphs with
+ // the same anchor but differing box offsets.
+ if (na == oa) {
+ return 0;
+ }
+
+ // todo: unhardcode the 8 = tileExtent/tileSize
+ 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
+
+ if (std::isnan(s1) || std::isnan(s2)) {
+ s1 = s2 = 1;
+ }
+ if (std::isnan(s3) || std::isnan(s4)) {
+ s3 = s4 = 1;
+ }
+
+ const float collisionFreeScale = std::fmin(std::fmax(s1, s2), std::fmax(s3, s4));
+
+ // Only update label's min scale if the glyph was
+ // restricted by a collision
+ if (collisionFreeScale > minPlacementScale &&
+ collisionFreeScale > minScale &&
+ collisionFreeScale < maxScale &&
+ collisionFreeScale < placement.maxScale) {
+ minPlacementScale = collisionFreeScale;
+ }
+
+ if (minPlacementScale > maxPlacementScale) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return minPlacementScale;
+}
+
+PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, float placementScale,
+ bool horizontal) {
+ PlacementRange placementRange = {{2.0f * M_PI, 0}};
+
+ for (const GlyphBox &glyph : glyphs) {
+ const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
+ const CollisionAnchor &anchor = glyph.anchor;
+
+ float minPlacedX = anchor.x + bbox.tl.x / placementScale;
+ float minPlacedY = anchor.y + bbox.tl.y / placementScale;
+ 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}};
+
+ std::vector<PlacementValue> blocking;
+ hTree.query(bgi::intersects(query_box), std::back_inserter(blocking));
+
+ if (horizontal) {
+ cTree.query(bgi::intersects(query_box), std::back_inserter(blocking));
+ }
+
+ for (const PlacementValue &value : blocking) {
+ const Box &s = std::get<0>(value);
+ const PlacementBox &b = std::get<1>(value);
+ const CollisionRect &bbox2 = b.hBox ? b.hBox.get() : b.box;
+
+ float x1, x2, y1, y2, intersectX, intersectY;
+
+ // Adjust and compare bboxes to see if the glyphs might intersect
+ if (placementScale > b.placementScale) {
+ x1 = b.anchor.x + bbox2.tl.x / placementScale;
+ y1 = b.anchor.y + bbox2.tl.y / placementScale;
+ x2 = b.anchor.x + bbox2.br.x / placementScale;
+ y2 = b.anchor.y + bbox2.br.y / placementScale;
+ intersectX = x1 < maxPlacedX && x2 > minPlacedX;
+ intersectY = y1 < maxPlacedY && y2 > minPlacedY;
+ } else {
+ x1 = anchor.x + bbox.tl.x / b.placementScale;
+ y1 = anchor.y + bbox.tl.y / b.placementScale;
+ 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>();
+ }
+
+ // If they can't intersect, skip more expensive rotation calculation
+ if (!(intersectX && intersectY))
+ continue;
+
+ float scale = std::fmax(placementScale, b.placementScale);
+ // TODO? glyph.box or glyph.bbox?
+ CollisionRange range = rotationRange(glyph, b, scale);
+
+ placementRange[0] = std::fmin(placementRange[0], range[0]);
+ placementRange[1] = std::fmax(placementRange[1], range[1]);
+ }
+ }
+
+ return placementRange;
+};
+
+void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
+ float placementScale, const PlacementRange &placementRange,
+ bool horizontal) {
+ assert(placementScale != std::numeric_limits<float>::infinity());
+
+ 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;
+
+ const float minScale = util::max(placementScale, glyph.minScale);
+ const float maxScale = glyph.maxScale != 0 ? glyph.maxScale : std::numeric_limits<float>::infinity();
+
+ const Box bounds = getBox(anchor, bbox, minScale, maxScale);
+
+ PlacementBox placement;
+ placement.anchor = anchor;
+ placement.box = box;
+ if (glyph.hBox) {
+ placement.hBox = bbox;
+ }
+ placement.placementRange = placementRange;
+ placement.placementScale = minScale;
+ placement.maxScale = maxScale;
+ placement.padding = glyph.padding;
+
+ allBounds.emplace_back(bounds, placement);
+ }
+
+ // Bulk-insert all glyph boxes
+ if (horizontal) {
+ hTree.insert(allBounds.begin(), allBounds.end());
+ } else {
+ cTree.insert(allBounds.begin(), allBounds.end());
+ }
+}