use std::collections::{HashMap, HashSet};
pub const DEDUP_QUANTUM: i64 = 4;
pub const COLLISION_CELL_PX: f32 = 32.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Aabb {
pub min_x: f32,
pub min_y: f32,
pub max_x: f32,
pub max_y: f32,
}
impl Aabb {
pub fn inflate(self, pad: f32) -> Aabb {
Aabb {
min_x: self.min_x - pad,
min_y: self.min_y - pad,
max_x: self.max_x + pad,
max_y: self.max_y + pad,
}
}
pub fn intersects(&self, o: &Aabb) -> bool {
self.min_x < o.max_x && o.min_x < self.max_x && self.min_y < o.max_y && o.min_y < self.max_y
}
}
#[derive(Debug)]
pub struct Grid {
cell: f32,
cells: HashMap<(i32, i32), Vec<usize>>,
boxes: Vec<Aabb>,
}
impl Grid {
pub fn new(cell_px: f32) -> Grid {
Grid {
cell: cell_px.max(1.0),
cells: HashMap::new(),
boxes: Vec::new(),
}
}
fn cell_span(&self, b: &Aabb) -> (i32, i32, i32, i32) {
let lo_x = (b.min_x / self.cell).floor() as i32;
let hi_x = (b.max_x / self.cell).floor() as i32;
let lo_y = (b.min_y / self.cell).floor() as i32;
let hi_y = (b.max_y / self.cell).floor() as i32;
(lo_x, hi_x, lo_y, hi_y)
}
pub fn intersects_any(&self, b: &Aabb) -> bool {
let (lo_x, hi_x, lo_y, hi_y) = self.cell_span(b);
for cy in lo_y..=hi_y {
for cx in lo_x..=hi_x {
if let Some(ids) = self.cells.get(&(cx, cy)) {
if ids.iter().any(|&i| self.boxes[i].intersects(b)) {
return true;
}
}
}
}
false
}
pub fn insert(&mut self, b: Aabb) {
let id = self.boxes.len();
let (lo_x, hi_x, lo_y, hi_y) = self.cell_span(&b);
self.boxes.push(b);
for cy in lo_y..=hi_y {
for cx in lo_x..=hi_x {
self.cells.entry((cx, cy)).or_default().push(id);
}
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct PlaceRank {
pub tile: (i64, i64),
pub feature: u32,
pub symbol: u32,
}
#[derive(Debug, Clone)]
pub struct LabelCandidate {
pub sort_key: f64,
pub rank: PlaceRank,
pub world_ax: i64,
pub world_ay: i64,
pub text: String,
pub style_id: u64,
pub variants: Vec<Vec<Aabb>>,
pub anchor_x: f32,
pub anchor_y: f32,
pub repeat_px: f32,
pub allow_overlap: bool,
pub ignore_placement: bool,
}
impl LabelCandidate {
fn quant(&self) -> (i64, i64) {
(
self.world_ax.div_euclid(DEDUP_QUANTUM),
self.world_ay.div_euclid(DEDUP_QUANTUM),
)
}
}
type RepeatAnchors<'a> = HashMap<(usize, &'a str, u64), Vec<(f32, f32)>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Placement {
pub cand: usize,
pub variant: usize,
}
pub fn place_layers(layers: &[&[LabelCandidate]], cell_px: f32) -> Vec<Vec<Placement>> {
let mut order: Vec<(usize, usize)> = layers
.iter()
.enumerate()
.flat_map(|(li, cands)| (0..cands.len()).map(move |i| (li, i)))
.collect();
let at = |(li, i): (usize, usize)| -> &LabelCandidate { &layers[li][i] };
order.sort_by(|&a, &b| {
let (ca, cb) = (at(a), at(b));
a.0.cmp(&b.0)
.then_with(|| ca.sort_key.total_cmp(&cb.sort_key))
.then_with(|| ca.rank.cmp(&cb.rank))
.then_with(|| ca.quant().1.cmp(&cb.quant().1))
.then_with(|| ca.quant().0.cmp(&cb.quant().0))
.then_with(|| ca.text.cmp(&cb.text))
.then_with(|| ca.style_id.cmp(&cb.style_id))
});
let mut grid = Grid::new(cell_px);
let mut seen: HashSet<(usize, i64, i64, &str, u64)> = HashSet::new();
let mut anchors: RepeatAnchors<'_> = HashMap::new();
let mut placed: Vec<Vec<Placement>> = vec![Vec::new(); layers.len()];
for (li, i) in order {
let c = at((li, i));
let (qx, qy) = c.quant();
if !seen.insert((li, qx, qy, c.text.as_str(), c.style_id)) {
continue;
}
if c.repeat_px > 0.0 {
let taken = anchors
.entry((li, c.text.as_str(), c.style_id))
.or_default();
let r2 = c.repeat_px * c.repeat_px;
if taken.iter().any(|&(x, y)| {
let (dx, dy) = (c.anchor_x - x, c.anchor_y - y);
dx * dx + dy * dy < r2
}) {
continue;
}
taken.push((c.anchor_x, c.anchor_y));
}
let variant = if c.allow_overlap {
Some(0)
} else {
c.variants
.iter()
.position(|boxes| boxes.iter().all(|b| !grid.intersects_any(b)))
};
let Some(variant) = variant else { continue };
placed[li].push(Placement { cand: i, variant });
if !c.ignore_placement {
for b in c.variants.get(variant).into_iter().flatten() {
grid.insert(*b);
}
}
}
placed
}
pub fn place(candidates: &[LabelCandidate], cell_px: f32) -> Vec<Placement> {
place_layers(&[candidates], cell_px)
.pop()
.expect("one layer in, one out")
}
#[cfg(test)]
mod tests {
use super::*;
fn boxed(x: f32, y: f32, half: f32) -> Aabb {
Aabb {
min_x: x - half,
min_y: y - half,
max_x: x + half,
max_y: y + half,
}
}
fn cand(sort_key: f64, ax: i64, ay: i64, text: &str, x: f32, y: f32) -> LabelCandidate {
LabelCandidate {
sort_key,
rank: PlaceRank::default(),
world_ax: ax,
world_ay: ay,
text: text.into(),
style_id: 0,
variants: vec![vec![boxed(x, y, 10.0)]],
anchor_x: x,
anchor_y: y,
repeat_px: 0.0,
allow_overlap: false,
ignore_placement: false,
}
}
fn idxs(placed: &[Placement]) -> Vec<usize> {
placed.iter().map(|p| p.cand).collect()
}
#[test]
fn aabb_intersects_excludes_touching() {
let a = boxed(0.0, 0.0, 5.0);
assert!(a.intersects(&boxed(8.0, 0.0, 5.0))); assert!(!a.intersects(&boxed(10.0, 0.0, 5.0))); assert!(!a.intersects(&boxed(20.0, 0.0, 5.0))); }
#[test]
fn lower_sort_key_wins_overlap() {
let a = {
let mut c = cand(5.0, 100, 0, "a", 0.0, 0.0);
c.world_ax = 100;
c
};
let b = cand(1.0, 200, 0, "b", 5.0, 0.0); let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(idxs(&placed), vec![1]); }
#[test]
fn deterministic_tiebreak_on_equal_sort_key() {
let a = cand(0.0, 0, 40, "z", 0.0, 10.0); let b = cand(0.0, 0, 0, "a", 0.0, 0.0); let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(idxs(&placed), vec![1]);
}
#[test]
fn feature_order_outranks_the_anchor_tie_break() {
let mut a = cand(0.0, 0, 40, "z", 0.0, 10.0);
a.rank.feature = 0;
let mut b = cand(0.0, 0, 0, "a", 0.0, 0.0); b.rank.feature = 1;
assert_eq!(idxs(&place(&[a, b], COLLISION_CELL_PX)), vec![0]);
let mut a = cand(5.0, 0, 40, "z", 0.0, 10.0);
a.rank.feature = 0;
let mut b = cand(1.0, 0, 0, "a", 0.0, 0.0);
b.rank.feature = 1;
assert_eq!(idxs(&place(&[a, b], COLLISION_CELL_PX)), vec![1]);
}
#[test]
fn allow_overlap_draws_both() {
let mut a = cand(0.0, 0, 0, "a", 0.0, 0.0);
let mut b = cand(1.0, 40, 0, "b", 5.0, 0.0);
a.allow_overlap = true;
b.allow_overlap = true;
let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(placed.len(), 2);
}
#[test]
fn ignore_placement_does_not_block_later() {
let mut a = cand(0.0, 0, 0, "a", 0.0, 0.0);
a.ignore_placement = true;
let b = cand(1.0, 40, 0, "b", 5.0, 0.0); let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(idxs(&placed), vec![0, 1]);
}
#[test]
fn plain_collision_still_blocks() {
let a = cand(0.0, 0, 0, "a", 0.0, 0.0);
let b = cand(1.0, 40, 0, "b", 5.0, 0.0);
let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(idxs(&placed), vec![0]);
}
#[test]
fn dedup_keeps_one_per_key() {
let a = cand(0.0, 1000, 500, "town", 0.0, 0.0);
let mut dup = cand(0.0, 1001, 501, "town", 0.0, 0.0); dup.text = "town".into();
let placed = place(&[a, dup], COLLISION_CELL_PX);
assert_eq!(placed.len(), 1);
}
#[test]
fn distinct_text_same_cell_not_deduped() {
let a = cand(0.0, 1000, 500, "aaa", 0.0, 0.0);
let mut b = cand(1.0, 1001, 501, "bbb", 100.0, 100.0);
b.allow_overlap = true; let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(placed.len(), 2);
}
#[test]
fn variable_anchor_falls_back_on_collision() {
let a = cand(0.0, 0, 0, "a", 0.0, 0.0);
let mut b = cand(1.0, 40, 0, "b", 5.0, 0.0); b.variants.push(vec![boxed(100.0, 0.0, 10.0)]); let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(placed.len(), 2);
let b_placed = placed.iter().find(|p| p.cand == 1).unwrap();
assert_eq!(b_placed.variant, 1, "b should place at its fallback anchor");
}
#[test]
fn variable_anchor_reserves_the_chosen_box() {
let a = cand(0.0, 0, 0, "a", 0.0, 0.0);
let mut b = cand(1.0, 40, 0, "b", 5.0, 0.0);
b.variants.push(vec![boxed(100.0, 0.0, 10.0)]);
let c = cand(2.0, 80, 0, "c", 100.0, 0.0); let placed = place(&[a, b, c], COLLISION_CELL_PX);
assert_eq!(
idxs(&placed),
vec![0, 1],
"c collides with b's fallback box"
);
}
#[test]
fn variable_anchor_drops_when_every_box_blocked() {
let a = cand(0.0, 0, 0, "a", 0.0, 0.0);
let mut b = cand(1.0, 40, 0, "b", 5.0, 0.0);
b.variants.push(vec![boxed(8.0, 0.0, 10.0)]); let placed = place(&[a, b], COLLISION_CELL_PX);
assert_eq!(idxs(&placed), vec![0]);
}
fn line_cand(text: &str, x: f32, y: f32, repeat_px: f32) -> LabelCandidate {
LabelCandidate {
sort_key: 0.0,
rank: PlaceRank::default(),
world_ax: x as i64,
world_ay: y as i64,
text: text.into(),
style_id: 0,
variants: vec![vec![boxed(x, y, 5.0)]],
anchor_x: x,
anchor_y: y,
repeat_px,
allow_overlap: true,
ignore_placement: true,
}
}
#[test]
fn repeat_distance_drops_a_nearby_copy_of_the_same_label() {
let cands = [
line_cand("Main St", 0.0, 0.0, 125.0),
line_cand("Main St", 60.0, 0.0, 125.0),
line_cand("Main St", 180.0, 0.0, 125.0),
];
assert_eq!(idxs(&place(&cands, COLLISION_CELL_PX)), vec![0, 2]);
let mixed = [
line_cand("Main St", 0.0, 0.0, 125.0),
line_cand("Elm St", 60.0, 0.0, 125.0),
];
assert_eq!(idxs(&place(&mixed, COLLISION_CELL_PX)), vec![0, 1]);
let all = [
line_cand("Main St", 0.0, 0.0, 0.0),
line_cand("Main St", 60.0, 0.0, 0.0),
];
assert_eq!(idxs(&place(&all, COLLISION_CELL_PX)), vec![0, 1]);
let top = [line_cand("Main St", 0.0, 0.0, 125.0)];
let below = [line_cand("Main St", 60.0, 0.0, 125.0)];
let placed = place_layers(&[&top, &below], COLLISION_CELL_PX);
assert_eq!((idxs(&placed[0]), idxs(&placed[1])), (vec![0], vec![0]));
}
#[test]
fn repeat_distance_is_consumed_by_a_blocked_candidate() {
let blocker = {
let mut c = line_cand("blocker", 0.0, 0.0, 0.0);
c.allow_overlap = false;
c.ignore_placement = false;
c.sort_key = -1.0;
c
};
let mut a = line_cand("Main St", 0.0, 0.0, 125.0);
a.allow_overlap = false;
let mut b = line_cand("Main St", 60.0, 0.0, 125.0);
b.allow_overlap = false;
assert_eq!(idxs(&place(&[blocker, a, b], COLLISION_CELL_PX)), vec![0]);
}
#[test]
fn all_glyph_boxes_of_a_line_label_must_be_free() {
let mut long = line_cand("Main St", 0.0, 0.0, 0.0);
long.allow_overlap = false;
long.ignore_placement = false;
long.variants = vec![vec![boxed(0.0, 0.0, 5.0), boxed(40.0, 0.0, 5.0)]];
let mut blocker = cand(-1.0, -100, 0, "poi", 40.0, 0.0);
blocker.world_ay = -100;
let placed = place(&[long.clone(), blocker.clone()], COLLISION_CELL_PX);
assert_eq!(
idxs(&placed),
vec![1],
"the blocker takes the second glyph's cell, so the line label drops"
);
let late = cand(2.0, 200, 0, "late", 40.0, 0.0);
let placed = place(&[long, late], COLLISION_CELL_PX);
assert_eq!(
idxs(&placed),
vec![0],
"the reserved glyph box blocks `late`"
);
}
#[test]
fn earlier_layer_in_priority_order_wins() {
let top = [cand(100.0, 40, 0, "poi", 5.0, 0.0)];
let below = [cand(-100.0, 0, 0, "road", 0.0, 0.0)]; let placed = place_layers(&[&top, &below], COLLISION_CELL_PX);
assert_eq!((idxs(&placed[0]), idxs(&placed[1])), (vec![0], vec![]));
let placed = place_layers(&[&below, &top], COLLISION_CELL_PX);
assert_eq!((idxs(&placed[0]), idxs(&placed[1])), (vec![0], vec![]));
}
#[test]
fn layers_dedup_separately() {
let mut a = cand(0.0, 1000, 500, "Shibuya", 0.0, 0.0);
a.allow_overlap = true;
let b = [a.clone()];
let a = [a];
let placed = place_layers(&[&a, &b], COLLISION_CELL_PX);
assert_eq!(placed.iter().map(Vec::len).sum::<usize>(), 2);
}
#[test]
fn order_is_frame_independent() {
let win = |shift: i64| {
let a = cand(2.0, 100 + shift, 0, "a", 0.0, 0.0);
let b = cand(1.0, 100 + shift, 0, "b", 3.0, 0.0);
place(&[a, b], COLLISION_CELL_PX)
};
assert_eq!(win(0), win(4096));
}
}