use uzor::types::Rect;
pub const DEFAULT_CELL_PX: f64 = 4.0;
pub struct OccupancyBitmap {
origin: (f64, f64),
cell_px: f64,
cols: usize,
rows: usize,
words_per_row: usize,
words: Vec<u64>,
}
impl OccupancyBitmap {
pub fn new(rect: Rect, cell_px: f64) -> Self {
let cell_px = cell_px.max(1.0);
let cols = ((rect.width / cell_px).ceil() as usize).max(1);
let rows = ((rect.height / cell_px).ceil() as usize).max(1);
let words_per_row = cols.div_ceil(64);
Self { origin: (rect.x, rect.y), cell_px, cols, rows, words_per_row, words: vec![0u64; words_per_row * rows] }
}
fn fully_within_bounds(&self, rect: Rect) -> bool {
let grid_w = self.cols as f64 * self.cell_px;
let grid_h = self.rows as f64 * self.cell_px;
let local_x0 = rect.x - self.origin.0;
let local_y0 = rect.y - self.origin.1;
rect.width > 0.0
&& rect.height > 0.0
&& local_x0 >= 0.0
&& local_y0 >= 0.0
&& local_x0 + rect.width <= grid_w
&& local_y0 + rect.height <= grid_h
}
fn cell_range(&self, rect: Rect) -> Option<(usize, usize, usize, usize)> {
let grid_w = self.cols as f64 * self.cell_px;
let grid_h = self.rows as f64 * self.cell_px;
let local_x0 = rect.x - self.origin.0;
let local_y0 = rect.y - self.origin.1;
let local_x1 = local_x0 + rect.width;
let local_y1 = local_y0 + rect.height;
if local_x1 <= 0.0 || local_y1 <= 0.0 || local_x0 >= grid_w || local_y0 >= grid_h || rect.width <= 0.0 || rect.height <= 0.0 {
return None;
}
let c0 = (local_x0.max(0.0) / self.cell_px).floor() as usize;
let c1 = (((local_x1.min(grid_w)) / self.cell_px).ceil() as usize).max(c0 + 1).min(self.cols);
let r0 = (local_y0.max(0.0) / self.cell_px).floor() as usize;
let r1 = (((local_y1.min(grid_h)) / self.cell_px).ceil() as usize).max(r0 + 1).min(self.rows);
Some((c0, c1, r0, r1))
}
pub fn is_free(&self, rect: Rect) -> bool {
if !self.fully_within_bounds(rect) {
return false;
}
let Some((c0, c1, r0, r1)) = self.cell_range(rect) else { return false };
for row in r0..r1 {
let base = row * self.words_per_row;
let mut col = c0;
while col < c1 {
let word_idx = col / 64;
let bit_start = col % 64;
let bit_end = (c1 - word_idx * 64).min(64);
let mask = word_mask(bit_start, bit_end);
if self.words[base + word_idx] & mask != 0 {
return false;
}
col = word_idx * 64 + bit_end;
}
}
true
}
pub fn mark(&mut self, rect: Rect) {
let Some((c0, c1, r0, r1)) = self.cell_range(rect) else { return };
for row in r0..r1 {
let base = row * self.words_per_row;
let mut col = c0;
while col < c1 {
let word_idx = col / 64;
let bit_start = col % 64;
let bit_end = (c1 - word_idx * 64).min(64);
let mask = word_mask(bit_start, bit_end);
self.words[base + word_idx] |= mask;
col = word_idx * 64 + bit_end;
}
}
}
}
fn word_mask(bit_start: usize, bit_end: usize) -> u64 {
if bit_start >= bit_end {
return 0;
}
if bit_end - bit_start >= 64 {
return u64::MAX;
}
((1u64 << (bit_end - bit_start)) - 1) << bit_start
}
pub fn place_labels(occupancy: &mut OccupancyBitmap, rects: &[Rect], candidates_fn: impl Fn(usize, Rect) -> Vec<Rect>) -> Vec<Option<Rect>> {
rects
.iter()
.enumerate()
.map(|(i, &natural)| {
for candidate in candidates_fn(i, natural) {
if occupancy.is_free(candidate) {
occupancy.mark(candidate);
return Some(candidate);
}
}
None
})
.collect()
}
pub fn anchor_candidates(anchor: (f64, f64), label_size: (f64, f64), radius: f64, gap: f64) -> [Rect; 8] {
let (ax, ay) = anchor;
let (w, h) = label_size;
let off = radius + gap;
[
Rect::new(ax + off, ay - h / 2.0, w, h), Rect::new(ax - off - w, ay - h / 2.0, w, h), Rect::new(ax - w / 2.0, ay - off - h, w, h), Rect::new(ax - w / 2.0, ay + off, w, h), Rect::new(ax + off, ay - off - h, w, h), Rect::new(ax + off, ay + off, w, h), Rect::new(ax - off - w, ay - off - h, w, h), Rect::new(ax - off - w, ay + off, w, h), ]
}
#[cfg(test)]
mod tests {
use super::*;
fn plot_rect() -> Rect {
Rect::new(0.0, 0.0, 200.0, 200.0)
}
#[test]
fn fresh_bitmap_is_free_everywhere() {
let occ = OccupancyBitmap::new(plot_rect(), DEFAULT_CELL_PX);
assert!(occ.is_free(Rect::new(10.0, 10.0, 20.0, 20.0)));
assert!(occ.is_free(Rect::new(180.0, 180.0, 20.0, 20.0)));
}
#[test]
fn mark_then_is_free_reports_occupied_and_word_boundary_is_handled() {
let mut occ = OccupancyBitmap::new(Rect::new(0.0, 0.0, 2000.0, 100.0), 4.0);
let r = Rect::new(500.0, 0.0, 40.0, 20.0); assert!(occ.is_free(r));
occ.mark(r);
assert!(!occ.is_free(r));
assert!(occ.is_free(Rect::new(600.0, 0.0, 20.0, 20.0)));
assert!(!occ.is_free(Rect::new(530.0, 0.0, 20.0, 20.0)));
}
#[test]
fn out_of_bounds_rect_is_never_free_and_marking_it_is_a_safe_no_op() {
let mut occ = OccupancyBitmap::new(plot_rect(), DEFAULT_CELL_PX);
let outside = Rect::new(-100.0, -100.0, 10.0, 10.0);
assert!(!occ.is_free(outside), "a candidate entirely outside the trackable grid must never be reported free");
occ.mark(outside); assert!(occ.is_free(Rect::new(0.0, 0.0, 5.0, 5.0)));
let clipping = Rect::new(-5.0, 10.0, 20.0, 10.0);
assert!(!occ.is_free(clipping), "a candidate clipping the grid's own edge must never be reported free");
}
#[test]
fn two_overlapping_at_default_labels_get_separated_to_different_candidates() {
let mut occ = OccupancyBitmap::new(plot_rect(), DEFAULT_CELL_PX);
let natural = Rect::new(50.0, 50.0, 30.0, 12.0);
let rects = [natural, natural];
let candidates_fn = |_i: usize, r: Rect| vec![r, Rect::new(r.x + 40.0, r.y, r.width, r.height), Rect::new(r.x, r.y + 20.0, r.width, r.height)];
let placed = place_labels(&mut occ, &rects, candidates_fn);
let first = placed[0].expect("first label must place at its own natural rect (grid starts empty)");
let second = placed[1].expect("second label must find a free alternate candidate");
assert_eq!(first, natural, "first label wins the contested natural position");
assert_ne!(second, natural, "second label must NOT land on the same rect as the first");
assert!(!rects_overlap(first, second));
}
#[test]
fn full_region_saturation_degrades_to_skip_never_overlap() {
let region = Rect::new(0.0, 0.0, 40.0, 40.0); let mut occ = OccupancyBitmap::new(region, DEFAULT_CELL_PX);
occ.mark(region);
let rects = [Rect::new(5.0, 5.0, 10.0, 10.0); 3];
let candidates_fn = |_i: usize, r: Rect| vec![r, Rect::new(r.x + 5.0, r.y, r.width, r.height), Rect::new(r.x, r.y + 5.0, r.width, r.height)];
let placed = place_labels(&mut occ, &rects, candidates_fn);
assert!(placed.iter().all(Option::is_none), "every label must degrade to skip on a fully saturated region: {placed:?}");
}
#[test]
fn place_labels_is_deterministic_across_repeated_runs() {
let make_occ = || OccupancyBitmap::new(plot_rect(), DEFAULT_CELL_PX);
let rects = [Rect::new(10.0, 10.0, 20.0, 10.0), Rect::new(15.0, 10.0, 20.0, 10.0), Rect::new(20.0, 10.0, 20.0, 10.0)];
let candidates_fn = |_i: usize, r: Rect| {
anchor_candidates((r.x, r.y), (r.width, r.height), 0.0, 2.0).to_vec()
};
let mut occ_a = make_occ();
let placed_a = place_labels(&mut occ_a, &rects, candidates_fn);
let mut occ_b = make_occ();
let placed_b = place_labels(&mut occ_b, &rects, candidates_fn);
assert_eq!(placed_a, placed_b);
}
#[test]
fn anchor_candidates_are_ordered_right_left_above_below_then_diagonals() {
let candidates = anchor_candidates((100.0, 100.0), (20.0, 10.0), 5.0, 2.0);
assert!(candidates[0].x > 100.0);
assert!(candidates[1].right() <= 100.0 - 5.0);
assert!(candidates[2].bottom() <= 100.0 - 5.0);
assert!(candidates[3].y >= 100.0 + 5.0);
}
fn rects_overlap(a: Rect, b: Rect) -> bool {
a.x < b.right() && b.x < a.right() && a.y < b.bottom() && b.y < a.bottom()
}
}