pub const LEVEL_LIFT: f32 = 0.01;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Ground {
pub corners: [(f32, f32); 4],
pub area: f32,
pub paint: i8,
}
const EPSILON: f32 = 1e-4;
const MAX_CELLS_PER_PIECE: i64 = 256;
thread_local! {
static OVERLAP_TESTS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
pub fn overlap_tests_for_test() -> usize {
OVERLAP_TESTS.with(std::cell::Cell::get)
}
fn bounds(corners: &[(f32, f32); 4]) -> (f32, f32, f32, f32) {
corners.iter().fold(
(f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
|(x0, y0, x1, y1), &(x, y)| (x0.min(x), y0.min(y), x1.max(x), y1.max(y)),
)
}
fn cell_size(order: &[usize], ground: &[Option<Ground>]) -> f32 {
let mut span = 0.0f32;
let mut n = 0usize;
for &i in order {
if let Some(g) = ground[i].as_ref() {
let (x0, y0, x1, y1) = bounds(&g.corners);
span += (x1 - x0).max(y1 - y0);
n += 1;
}
}
let mean = if n == 0 { 0.0 } else { span / n as f32 };
if mean.is_finite() && mean > 0.0 {
mean
} else {
1.0
}
}
fn cell_range(
corners: &[(f32, f32); 4],
cell: f32,
) -> Option<(i32, i32, i32, i32)> {
let (x0, y0, x1, y1) = bounds(corners);
if !(x0.is_finite() && y0.is_finite() && x1.is_finite() && y1.is_finite()) {
return None;
}
let (cx0, cy0) = ((x0 / cell).floor(), (y0 / cell).floor());
let (cx1, cy1) = ((x1 / cell).floor(), (y1 / cell).floor());
let span = ((cx1 - cx0 + 1.0) * (cy1 - cy0 + 1.0)) as f64;
if !(span.is_finite() && span >= 1.0 && span <= MAX_CELLS_PER_PIECE as f64) {
return None;
}
Some((cx0 as i32, cy0 as i32, cx1 as i32, cy1 as i32))
}
pub fn draw_levels(ground: &[Option<Ground>]) -> Vec<i8> {
let mut levels = vec![0i8; ground.len()];
let mut order: Vec<usize> = (0..ground.len()).filter(|&i| ground[i].is_some()).collect();
order.sort_by(|&a, &b| {
let (ga, gb) = (ground[a].as_ref(), ground[b].as_ref());
match (ga, gb) {
(Some(ga), Some(gb)) => ga
.paint
.cmp(&gb.paint)
.then(gb.area.total_cmp(&ga.area))
.then(a.cmp(&b)),
_ => a.cmp(&b),
}
});
let cell = cell_size(&order, ground);
let mut cells: std::collections::HashMap<(i32, i32), Vec<usize>> =
std::collections::HashMap::new();
let mut aside: Vec<usize> = Vec::new();
let mut seen = vec![0u64; order.len()];
let mut candidates: Vec<usize> = Vec::new();
for (k, &i) in order.iter().enumerate() {
let gi = ground[i].as_ref().expect("filtered to footprints");
let stamp = k as u64 + 1;
candidates.clear();
let range = cell_range(&gi.corners, cell);
match range {
Some((cx0, cy0, cx1, cy1)) => {
candidates.extend_from_slice(&aside);
for cx in cx0..=cx1 {
for cy in cy0..=cy1 {
if let Some(bucket) = cells.get(&(cx, cy)) {
candidates.extend_from_slice(bucket);
}
}
}
}
None => candidates.extend(0..k),
}
let mut level = 0i8;
for j in candidates.drain(..) {
if seen[j] == stamp {
continue;
}
seen[j] = stamp;
let gj = ground[order[j]].as_ref().expect("filtered to footprints");
if overlaps(&gi.corners, &gj.corners) {
level = level.max(levels[order[j]].saturating_add(1));
}
}
levels[i] = level;
match range {
Some((cx0, cy0, cx1, cy1)) => {
for cx in cx0..=cx1 {
for cy in cy0..=cy1 {
cells.entry((cx, cy)).or_default().push(k);
}
}
}
None => aside.push(k),
}
}
levels
}
fn overlaps(a: &[(f32, f32); 4], b: &[(f32, f32); 4]) -> bool {
OVERLAP_TESTS.with(|n| n.set(n.get() + 1));
!separated(a, b) && !separated(b, a)
}
fn separated(poly: &[(f32, f32); 4], other: &[(f32, f32); 4]) -> bool {
(0..4).any(|i| {
let (x0, y0) = poly[i];
let (x1, y1) = poly[(i + 1) % 4];
let (nx, ny) = (y1 - y0, x0 - x1);
let (pmin, pmax) = project(poly, nx, ny);
let (omin, omax) = project(other, nx, ny);
omax <= pmin + EPSILON || pmax <= omin + EPSILON
})
}
fn project(points: &[(f32, f32); 4], nx: f32, ny: f32) -> (f32, f32) {
points
.iter()
.fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), &(x, y)| {
let d = x * nx + y * ny;
(lo.min(d), hi.max(d))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn square(cx: f32, cy: f32, half: f32) -> [(f32, f32); 4] {
[
(cx - half, cy - half),
(cx + half, cy - half),
(cx + half, cy + half),
(cx - half, cy + half),
]
}
fn piece(corners: [(f32, f32); 4], area: f32, paint: i8) -> Option<Ground> {
Some(Ground {
corners,
area,
paint,
})
}
#[test]
fn disjoint_pieces_are_both_level_zero() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 1.0), 4.0, 0),
piece(square(10.0, 0.0, 1.0), 4.0, 0),
]);
assert_eq!(levels, vec![0, 0]);
}
#[test]
fn edge_contact_is_not_overlap() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 1.0), 4.0, 0),
piece(square(2.0, 0.0, 1.0), 4.0, 0),
]);
assert_eq!(levels, vec![0, 0]);
}
#[test]
fn the_smaller_piece_draws_on_top_of_the_larger() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 6.0), 144.0, 0),
piece(square(0.0, 0.0, 2.0), 16.0, 0),
]);
assert!(
levels[1] > levels[0],
"smaller piece must be higher: {levels:?}"
);
}
#[test]
fn equal_area_later_placement_draws_on_top() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 1.0), 4.0, 0),
piece(square(0.0, 0.0, 1.0), 4.0, 0),
]);
assert!(
levels[1] > levels[0],
"later placement must be higher: {levels:?}"
);
}
#[test]
fn authored_paint_beats_area() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 6.0), 144.0, 1),
piece(square(0.0, 0.0, 2.0), 16.0, 0),
]);
assert!(
levels[0] > levels[1],
"painted piece must be higher: {levels:?}"
);
}
#[test]
fn a_chain_of_overlaps_stacks_levels() {
let levels = draw_levels(&[
piece(square(0.0, 0.0, 6.0), 144.0, 0),
piece(square(0.0, 0.0, 4.0), 64.0, 0),
piece(square(0.0, 0.0, 2.0), 16.0, 0),
]);
assert_eq!(levels, vec![0, 1, 2]);
}
#[test]
fn a_piece_without_footprint_is_ignored() {
let levels = draw_levels(&[None, piece(square(0.0, 0.0, 1.0), 4.0, 0)]);
assert_eq!(levels, vec![0, 0]);
}
}