use crate::scissor::Scissor;
pub const MAX_PIECES: usize = 32;
pub fn remainder(inside: Scissor, covered: &[Scissor]) -> Option<Vec<Scissor>> {
if inside.is_empty() {
return Some(Vec::new());
}
let mut blockers: Vec<Scissor> = covered
.iter()
.map(|c| c.intersect(inside))
.filter(|c| !c.is_empty())
.collect();
if blockers.is_empty() {
return Some(vec![inside]);
}
if blockers.contains(&inside) {
return Some(Vec::new());
}
let mut edges: Vec<u32> = Vec::with_capacity(blockers.len() * 2 + 2);
edges.push(inside.y);
edges.push(inside.bottom());
for b in &blockers {
edges.push(b.y);
edges.push(b.bottom());
}
edges.sort_unstable();
edges.dedup();
blockers.sort_unstable_by_key(|b| b.y);
let ceiling = MAX_PIECES * 8;
let mut out: Vec<Scissor> = Vec::new();
let mut spans: Vec<(u32, u32)> = Vec::new();
for pair in edges.windows(2) {
let (top, bottom) = (pair[0], pair[1]);
if bottom <= top {
continue;
}
spans.clear();
for b in &blockers {
if b.y > top {
break;
}
if b.bottom() >= bottom {
spans.push((b.x, b.right()));
}
}
spans.sort_unstable();
let mut x = inside.x;
for &(start, end) in &spans {
if start > x {
out.push(Scissor::new(x, top, start - x, bottom - top));
if out.len() > ceiling {
return None;
}
}
x = x.max(end);
}
if x < inside.right() {
out.push(Scissor::new(x, top, inside.right() - x, bottom - top));
if out.len() > ceiling {
return None;
}
}
}
out.sort_unstable_by_key(|r| (r.x, r.width, r.y));
let mut merged: Vec<Scissor> = Vec::with_capacity(out.len());
for piece in out {
match merged.last_mut() {
Some(prev)
if prev.x == piece.x && prev.width == piece.width && prev.bottom() == piece.y =>
{
prev.height += piece.height;
}
_ => merged.push(piece),
}
}
(merged.len() <= MAX_PIECES).then_some(merged)
}
#[cfg(test)]
mod tests {
use super::*;
const WHOLE: Scissor = Scissor::new(0, 0, 100, 80);
fn area(rects: &[Scissor]) -> u64 {
rects
.iter()
.map(|r| u64::from(r.width) * u64::from(r.height))
.sum()
}
fn covers_exactly(inside: Scissor, blockers: &[Scissor], out: &[Scissor]) {
for y in inside.y..inside.bottom() {
for x in inside.x..inside.right() {
let blocked = blockers
.iter()
.any(|b| x >= b.x && x < b.right() && y >= b.y && y < b.bottom());
let hits = out
.iter()
.filter(|r| x >= r.x && x < r.right() && y >= r.y && y < r.bottom())
.count();
let want = usize::from(!blocked);
assert_eq!(
hits, want,
"pixel ({x}, {y}) is in {hits} pieces and should be in {want}; \
blocked = {blocked}"
);
}
}
}
#[test]
fn nothing_covered_gives_the_whole_rectangle_back() {
assert_eq!(remainder(WHOLE, &[]), Some(vec![WHOLE]));
let elsewhere = Scissor::new(200, 200, 10, 10);
assert_eq!(remainder(WHOLE, &[elsewhere]), Some(vec![WHOLE]));
assert_eq!(remainder(WHOLE, &[Scissor::EMPTY]), Some(vec![WHOLE]));
}
#[test]
fn a_blocker_covering_everything_leaves_nothing() {
assert_eq!(remainder(WHOLE, &[WHOLE]), Some(Vec::new()));
let larger = Scissor::new(0, 0, 500, 500);
assert_eq!(remainder(WHOLE, &[larger]), Some(Vec::new()));
let top = Scissor::new(0, 0, 100, 40);
let bottom = Scissor::new(0, 40, 100, 40);
assert_eq!(remainder(WHOLE, &[top, bottom]), Some(Vec::new()));
}
#[test]
fn an_empty_input_has_no_remainder() {
assert_eq!(remainder(Scissor::EMPTY, &[WHOLE]), Some(Vec::new()));
}
#[test]
fn a_bar_across_the_top_leaves_the_rest() {
let bar = Scissor::new(0, 0, 100, 20);
let out = remainder(WHOLE, &[bar]).expect("under the cap");
assert_eq!(out, vec![Scissor::new(0, 20, 100, 60)]);
covers_exactly(WHOLE, &[bar], &out);
}
#[test]
fn a_bar_and_a_sidebar_do_not_double_count_their_corner() {
let bar = Scissor::new(0, 0, 100, 20);
let sidebar = Scissor::new(0, 0, 25, 80);
let out = remainder(WHOLE, &[bar, sidebar]).expect("under the cap");
assert_eq!(area(&out), 100 * 80 - (100 * 20 + 25 * 80 - 25 * 20));
covers_exactly(WHOLE, &[bar, sidebar], &out);
}
#[test]
fn a_block_in_the_middle_leaves_a_ring() {
let middle = Scissor::new(40, 30, 20, 20);
let out = remainder(WHOLE, &[middle]).expect("under the cap");
assert_eq!(area(&out), 100 * 80 - 20 * 20);
covers_exactly(WHOLE, &[middle], &out);
}
#[test]
fn overlapping_blockers_are_counted_once() {
let a = Scissor::new(10, 10, 40, 40);
let b = Scissor::new(30, 20, 40, 40);
let out = remainder(WHOLE, &[a, b]).expect("under the cap");
covers_exactly(WHOLE, &[a, b], &out);
}
#[test]
fn touching_blockers_leave_no_seam_between_them() {
let a = Scissor::new(0, 0, 50, 80);
let b = Scissor::new(50, 0, 50, 80);
assert_eq!(remainder(WHOLE, &[a, b]), Some(Vec::new()));
}
#[test]
fn too_many_pieces_declines_rather_than_returning_some_of_them() {
let many: Vec<Scissor> = (0..12).map(|i| Scissor::new(i * 8, i * 6, 4, 3)).collect();
assert_eq!(remainder(WHOLE, &many), None);
}
#[test]
fn every_pair_from_a_grid_decomposes_exactly() {
const TARGET: Scissor = Scissor::new(0, 0, 12, 10);
let candidates: Vec<Scissor> = [0u32, 3, 6, 9]
.iter()
.flat_map(|&x| {
[3u32, 6].iter().flat_map(move |&w| {
[0u32, 2, 5, 8].iter().flat_map(move |&y| {
[2u32, 5].iter().map(move |&h| Scissor::new(x, y, w, h))
})
})
})
.collect();
assert_eq!(candidates.len(), 64);
let mut decomposed = 0usize;
let mut declined = 0usize;
for a in &candidates {
for b in &candidates {
let blockers = [*a, *b];
match remainder(TARGET, &blockers) {
Some(out) => {
covers_exactly(TARGET, &blockers, &out);
decomposed += 1;
}
None => declined += 1,
}
}
}
assert_eq!(decomposed + declined, 64 * 64);
assert_eq!(declined, 0, "the cap refused a two-blocker case");
}
#[test]
fn every_triple_from_a_smaller_grid_decomposes_exactly() {
const TARGET: Scissor = Scissor::new(0, 0, 8, 6);
let candidates: Vec<Scissor> = [0u32, 2, 4, 6]
.iter()
.flat_map(|&x| {
[2u32, 4]
.iter()
.flat_map(move |&w| [0u32, 3].iter().map(move |&y| Scissor::new(x, y, w, 3)))
})
.collect();
assert_eq!(candidates.len(), 16);
let mut checked = 0usize;
for a in &candidates {
for b in &candidates {
for c in &candidates {
let blockers = [*a, *b, *c];
if let Some(out) = remainder(TARGET, &blockers) {
covers_exactly(TARGET, &blockers, &out);
checked += 1;
}
}
}
}
assert_eq!(checked, 16 * 16 * 16);
}
#[test]
fn an_offset_input_keeps_its_own_bounds() {
let inside = Scissor::new(20, 10, 50, 40);
let blocker = Scissor::new(30, 20, 10, 10);
let out = remainder(inside, &[blocker]).expect("under the cap");
for r in &out {
assert!(r.x >= inside.x && r.right() <= inside.right(), "{r:?}");
assert!(r.y >= inside.y && r.bottom() <= inside.bottom(), "{r:?}");
}
covers_exactly(inside, &[blocker], &out);
}
}