use alloc::vec::Vec;
use core::fmt;
use crate::{Real, Shape3};
const NONE: u32 = u32::MAX;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Repair {
pub dirty: u64,
pub already_air: u64,
pub relabels: u64,
pub merges: u64,
}
impl Repair {
#[must_use]
pub fn relabels_per_dirty(&self) -> f64 {
if self.dirty == 0 {
0.0
} else {
self.relabels as f64 / self.dirty as f64
}
}
}
impl fmt::Display for Repair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"dirty {} (+{} already air), {} relabels ({:.2}/dirty), {} merges",
self.dirty,
self.already_air,
self.relabels,
self.relabels_per_dirty(),
self.merges
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Fill {
pub dirty: u64,
pub already_solid: u64,
pub seeds: u64,
pub visited: u64,
pub splits: u64,
pub shed: u64,
pub vanished: u64,
pub pending: u64,
}
impl fmt::Display for Fill {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"dirty {} (+{} already solid), {} seeds, {} visited, \
{} splits shedding {}, {} vanished, {} pending",
self.dirty,
self.already_solid,
self.seeds,
self.visited,
self.splits,
self.shed,
self.vanished,
self.pending
)
}
}
#[derive(Clone, Debug)]
pub struct Air {
air: Vec<bool>,
label: Vec<u32>,
size: Vec<u32>,
area: Vec<u32>,
free: Vec<u32>,
live: u64,
pending: Vec<(u32, Vec<u32>)>,
stamp: Vec<u32>,
epoch: u32,
claim: Vec<u32>,
dims: [u32; 3],
queue: Vec<usize>,
scratch: Vec<u32>,
}
impl Air {
pub fn build<R: Real>(values: &[R], shape: &impl Shape3) -> crate::Result<(Self, Repair)> {
let dims = shape.size();
let count = shape.element_count();
if values.len() != count {
return Err(crate::Error::ShapeOverflow {
size: dims,
product: values.len() as u64,
});
}
let mut me = Self {
air: values.iter().map(|v| !crate::cube::is_inside(*v)).collect(),
label: alloc::vec![NONE; count],
size: Vec::new(),
area: Vec::new(),
free: Vec::new(),
live: 0,
pending: Vec::new(),
stamp: alloc::vec![0u32; count],
epoch: 0,
claim: alloc::vec![NONE; count],
dims,
queue: Vec::new(),
scratch: Vec::new(),
};
let mut repair = Repair::default();
for start in 0..count {
if me.air.get(start) != Some(&true) || me.label.get(start) != Some(&NONE) {
continue;
}
let l = me.take_label();
let grown = me.flood(start, l);
me.set_size(l, grown);
repair.dirty += grown as u64;
repair.relabels += grown as u64;
}
for i in 0..count {
if me.air.get(i) != Some(&true) {
continue;
}
let Some(&l) = me.label.get(i) else { continue };
if l == NONE {
continue;
}
let solid = me.solid_faces(i);
me.area_add(l, solid);
}
Ok((me, repair))
}
pub fn dig<B: FnMut() -> bool>(&mut self, samples: &[[u32; 3]], mut spend: B) -> Repair {
let mut repair = Repair::default();
let mut fresh = core::mem::take(&mut self.scratch);
fresh.clear();
for s in samples {
if !self.in_range(*s) {
continue;
}
let i = self.index(*s);
match self.air.get_mut(i) {
Some(a) if *a => repair.already_air += 1,
Some(a) => {
*a = true;
repair.dirty += 1;
fresh.push(i as u32);
}
None => {}
}
}
fresh.sort_unstable();
fresh.dedup();
for k in 0..fresh.len() {
let Some(&i) = fresh.get(k) else { continue };
let i = i as usize;
if self.label.get(i) != Some(&NONE) {
continue;
}
if !spend() {
break;
}
self.grow_from(i, &mut repair);
}
self.scratch = fresh;
repair
}
pub fn fill<B: FnMut() -> bool>(&mut self, samples: &[[u32; 3]], mut spend: B) -> Fill {
let mut out = Fill::default();
let mut gone = core::mem::take(&mut self.scratch);
gone.clear();
for s in samples {
if !self.in_range(*s) {
continue;
}
let i = self.index(*s);
match self.air.get_mut(i) {
Some(a) if *a => {
*a = false;
out.dirty += 1;
gone.push(i as u32);
}
Some(_) => out.already_solid += 1,
None => {}
}
}
gone.sort_unstable();
gone.dedup();
let mut nb_area = [0usize; 6];
for &g in &gone {
let i = g as usize;
let Some(&l) = self.label.get(i) else {
continue;
};
if l == NONE {
continue;
}
let used = self.neighbours(i, &mut nb_area);
for &n in nb_area.iter().take(used) {
if self.air.get(n) == Some(&true) {
if let Some(&ln) = self.label.get(n)
&& ln != NONE
{
self.area_add(ln, 1);
}
} else if gone.binary_search(&(n as u32)).is_err() {
self.area_sub(l, 1);
}
}
self.area_sub(l, 6 - used as u32);
if let Some(slot) = self.label.get_mut(i) {
*slot = NONE;
}
let remaining = self
.size
.get(l as usize)
.copied()
.unwrap_or(0)
.saturating_sub(1);
if let Some(slot) = self.size.get_mut(l as usize) {
*slot = remaining;
}
if remaining == 0 {
self.retire(l);
out.vanished += 1;
}
}
let mut nb = [0usize; 6];
for &g in &gone {
let used = self.neighbours(g as usize, &mut nb);
for &j in nb.iter().take(used) {
if self.air.get(j) != Some(&true) {
continue;
}
let Some(&l) = self.label.get(j) else {
continue;
};
if l == NONE {
continue;
}
out.seeds += 1;
match self.pending.iter_mut().find(|(p, _)| *p == l) {
Some((_, seeds)) => {
if !seeds.contains(&(j as u32)) {
seeds.push(j as u32);
}
}
None => self.pending.push((l, alloc::vec![j as u32])),
}
}
}
self.scratch = gone;
let drained = self.repair(&mut spend);
out.visited += drained.visited;
out.splits += drained.splits;
out.shed += drained.shed;
out.pending = self.pending.len() as u64;
out
}
pub fn repair<B: FnMut() -> bool>(&mut self, spend: &mut B) -> Fill {
let mut out = Fill::default();
while !self.pending.is_empty() {
if !spend() {
break;
}
let (l, seeds) = self.pending.remove(0);
self.search(l, &seeds, &mut out);
}
out.pending = self.pending.len() as u64;
out
}
#[must_use]
pub fn connected(&self, a: [u32; 3], b: [u32; 3]) -> bool {
if !self.in_range(a) || !self.in_range(b) {
return false;
}
let (ia, ib) = (self.index(a), self.index(b));
match (self.label.get(ia), self.label.get(ib)) {
(Some(&x), Some(&y)) => x != NONE && x == y,
_ => false,
}
}
#[must_use]
pub fn label_of(&self, p: [u32; 3]) -> Option<u32> {
if !self.in_range(p) {
return None;
}
match self.label.get(self.index(p)) {
Some(&l) if l != NONE => Some(l),
_ => None,
}
}
#[must_use]
pub fn components(&self) -> u64 {
self.live
}
#[must_use]
pub fn air_samples(&self) -> u64 {
self.air.iter().filter(|a| **a).count() as u64
}
#[must_use]
pub fn pending(&self) -> u64 {
self.pending.len() as u64
}
#[must_use]
pub fn label_count(&self) -> usize {
self.size.len()
}
#[must_use]
pub fn component_size(&self, label: u32) -> u32 {
self.size_of(label)
}
#[must_use]
pub fn component_area(&self, label: u32) -> u32 {
self.area.get(label as usize).copied().unwrap_or(0)
}
fn in_range(&self, p: [u32; 3]) -> bool {
p[0] < self.dims[0] && p[1] < self.dims[1] && p[2] < self.dims[2]
}
fn index(&self, p: [u32; 3]) -> usize {
(p[2] as usize * self.dims[1] as usize + p[1] as usize) * self.dims[0] as usize
+ p[0] as usize
}
fn neighbours(&self, i: usize, out: &mut [usize; 6]) -> usize {
let (nx, ny) = (self.dims[0] as usize, self.dims[1] as usize);
let nz = self.dims[2] as usize;
if nx == 0 || ny == 0 || nz == 0 {
return 0;
}
let x = i % nx;
let y = (i / nx) % ny;
let z = i / (nx * ny);
let mut count = 0;
let mut push = |v: usize| {
if let Some(slot) = out.get_mut(count) {
*slot = v;
count += 1;
}
};
if x > 0 {
push(i - 1);
}
if x + 1 < nx {
push(i + 1);
}
if y > 0 {
push(i - nx);
}
if y + 1 < ny {
push(i + nx);
}
if z > 0 {
push(i - nx * ny);
}
if z + 1 < nz {
push(i + nx * ny);
}
count
}
fn take_label(&mut self) -> u32 {
self.live += 1;
match self.free.pop() {
Some(l) => l,
None => {
self.size.push(0);
self.area.push(0);
(self.size.len() - 1) as u32
}
}
}
fn retire(&mut self, l: u32) {
self.live = self.live.saturating_sub(1);
if let Some(slot) = self.size.get_mut(l as usize) {
*slot = 0;
}
if let Some(slot) = self.area.get_mut(l as usize) {
*slot = 0;
}
self.free.push(l);
self.pending.retain(|(p, _)| *p != l);
}
fn set_size(&mut self, l: u32, n: u32) {
if let Some(slot) = self.size.get_mut(l as usize) {
*slot = n;
}
}
fn size_of(&self, l: u32) -> u32 {
self.size.get(l as usize).copied().unwrap_or(0)
}
fn area_add(&mut self, l: u32, n: u32) {
if let Some(slot) = self.area.get_mut(l as usize) {
*slot = slot.saturating_add(n);
}
}
fn area_sub(&mut self, l: u32, n: u32) {
if let Some(slot) = self.area.get_mut(l as usize) {
*slot = slot.saturating_sub(n);
}
}
fn solid_faces(&self, i: usize) -> u32 {
let mut nb = [0usize; 6];
let used = self.neighbours(i, &mut nb);
let air = nb
.iter()
.take(used)
.filter(|&&j| self.air.get(j) == Some(&true))
.count();
6 - air as u32
}
fn flood(&mut self, start: usize, l: u32) -> u32 {
let mut queue = core::mem::take(&mut self.queue);
queue.clear();
queue.push(start);
if let Some(slot) = self.label.get_mut(start) {
*slot = l;
}
let mut seen = 0u32;
let mut head = 0;
let mut nb = [0usize; 6];
while let Some(&i) = queue.get(head) {
head += 1;
seen += 1;
let used = self.neighbours(i, &mut nb);
for &j in nb.iter().take(used) {
if self.air.get(j) != Some(&true) || self.label.get(j) != Some(&NONE) {
continue;
}
if let Some(slot) = self.label.get_mut(j) {
*slot = l;
}
queue.push(j);
}
}
self.queue = queue;
seen
}
fn recolour(&mut self, start: usize, from: u32, to: u32) -> u32 {
if from == to {
return 0;
}
let mut queue = core::mem::take(&mut self.queue);
queue.clear();
queue.push(start);
if let Some(slot) = self.label.get_mut(start) {
*slot = to;
}
let mut seen = 0u32;
let mut head = 0;
let mut nb = [0usize; 6];
while let Some(&i) = queue.get(head) {
head += 1;
seen += 1;
let used = self.neighbours(i, &mut nb);
for &j in nb.iter().take(used) {
if self.label.get(j) != Some(&from) {
continue;
}
if let Some(slot) = self.label.get_mut(j) {
*slot = to;
}
queue.push(j);
}
}
self.queue = queue;
seen
}
fn grow_from(&mut self, i: usize, repair: &mut Repair) {
let provisional = self.take_label();
let mut touched: Vec<(u32, usize)> = Vec::new();
let mut queue = core::mem::take(&mut self.queue);
queue.clear();
queue.push(i);
if let Some(slot) = self.label.get_mut(i) {
*slot = provisional;
}
let mut grown = 0u32;
let mut blob_faces = 0u32;
let mut head = 0;
let mut nb = [0usize; 6];
while let Some(&at) = queue.get(head) {
head += 1;
grown += 1;
blob_faces += self.solid_faces(at);
let used = self.neighbours(at, &mut nb);
for &j in nb.iter().take(used) {
if self.air.get(j) != Some(&true) {
continue;
}
match self.label.get(j).copied() {
Some(NONE) => {
if let Some(slot) = self.label.get_mut(j) {
*slot = provisional;
}
queue.push(j);
}
Some(l) if l != provisional => {
self.area_sub(l, 1);
if !touched.iter().any(|(t, _)| *t == l) {
touched.push((l, j));
}
}
_ => {}
}
}
}
self.queue = queue;
self.set_size(provisional, grown);
self.area_add(provisional, blob_faces);
repair.relabels += u64::from(grown);
let mut best = provisional;
for &(l, _) in &touched {
let (sl, sb) = (self.size_of(l), self.size_of(best));
if sl > sb || (sl == sb && l < best) {
best = l;
}
}
let mut merged = 0u32;
for &(l, member) in &touched {
if l == best {
continue;
}
let moved = self.recolour(member, l, best);
repair.relabels += u64::from(moved);
repair.merges += 1;
merged += moved;
let transfer = self.component_area(l);
self.area_sub(l, transfer);
self.area_add(best, transfer);
self.retire(l);
}
if best != provisional {
let moved = self.recolour(i, provisional, best);
repair.relabels += u64::from(moved);
repair.merges += 1;
merged += moved;
let transfer = self.component_area(provisional);
self.area_sub(provisional, transfer);
self.area_add(best, transfer);
self.retire(provisional);
}
let total = self.size_of(best) + merged;
self.set_size(best, total);
}
fn search(&mut self, l: u32, seeds: &[u32], out: &mut Fill) {
if seeds.len() < 2 {
return;
}
self.epoch = self.epoch.wrapping_add(1);
if self.epoch == 0 {
self.stamp.iter_mut().for_each(|s| *s = 0);
self.epoch = 1;
}
let epoch = self.epoch;
let mut queues: Vec<Vec<usize>> = Vec::new();
let mut seen: Vec<Vec<usize>> = Vec::new();
let mut into: Vec<u32> = Vec::new();
let mut done: Vec<bool> = Vec::new();
for (f, &s) in seeds.iter().enumerate() {
let s = s as usize;
if self.label.get(s) != Some(&l) || self.stamp.get(s) == Some(&epoch) {
queues.push(Vec::new());
seen.push(Vec::new());
into.push(f as u32);
done.push(true);
continue;
}
if let Some(slot) = self.stamp.get_mut(s) {
*slot = epoch;
}
if let Some(slot) = self.claim.get_mut(s) {
*slot = f as u32;
}
queues.push(alloc::vec![s]);
seen.push(alloc::vec![s]);
into.push(f as u32);
done.push(false);
}
fn resolve(into: &[u32], mut f: u32) -> u32 {
while into.get(f as usize).copied().unwrap_or(f) != f {
f = into.get(f as usize).copied().unwrap_or(f);
}
f
}
let mut nb = [0usize; 6];
loop {
let active: Vec<u32> = (0..queues.len() as u32)
.filter(|&f| resolve(&into, f) == f && done.get(f as usize) != Some(&true))
.collect();
if active.len() <= 1 {
break;
}
for &f in &active {
if resolve(&into, f) != f || done.get(f as usize) == Some(&true) {
continue;
}
let Some(q) = queues.get_mut(f as usize) else {
continue;
};
let Some(at) = q.pop() else {
if let Some(slot) = done.get_mut(f as usize) {
*slot = true;
}
continue;
};
out.visited += 1;
let used = self.neighbours(at, &mut nb);
for &j in nb.iter().take(used) {
if self.label.get(j) != Some(&l) {
continue;
}
if self.stamp.get(j) == Some(&epoch) {
let g = self.claim.get(j).copied().unwrap_or(NONE);
if g == NONE {
continue;
}
let (rf, rg) = (resolve(&into, f), resolve(&into, g));
if rf == rg {
continue;
}
let (keep, drop_) = if rf < rg { (rf, rg) } else { (rg, rf) };
if let Some(slot) = into.get_mut(drop_ as usize) {
*slot = keep;
}
let moved_q = queues.get(drop_ as usize).cloned().unwrap_or_default();
let moved_s = seen.get(drop_ as usize).cloned().unwrap_or_default();
if let Some(dst) = queues.get_mut(keep as usize) {
dst.extend(moved_q);
}
if let Some(dst) = seen.get_mut(keep as usize) {
dst.extend(moved_s);
}
if let Some(slot) = queues.get_mut(drop_ as usize) {
slot.clear();
}
if let Some(slot) = seen.get_mut(drop_ as usize) {
slot.clear();
}
let revived = done.get(drop_ as usize) == Some(&true);
if revived && let Some(slot) = done.get_mut(keep as usize) {
*slot = false;
}
continue;
}
if let Some(slot) = self.stamp.get_mut(j) {
*slot = epoch;
}
let root = resolve(&into, f);
if let Some(slot) = self.claim.get_mut(j) {
*slot = root;
}
if let Some(q) = queues.get_mut(root as usize) {
q.push(j);
}
if let Some(sset) = seen.get_mut(root as usize) {
sset.push(j);
}
}
}
}
let roots: Vec<u32> = (0..queues.len() as u32)
.filter(|&f| {
resolve(&into, f) == f && seen.get(f as usize).is_some_and(|m| !m.is_empty())
})
.collect();
if roots.len() < 2 {
return;
}
let unfinished: Vec<u32> = roots
.iter()
.copied()
.filter(|&f| done.get(f as usize) != Some(&true))
.collect();
let keeper = match unfinished.first() {
Some(&f) if unfinished.len() == 1 => f,
_ => {
let mut best = *roots.first().unwrap_or(&0);
for &f in &roots {
let (a, b) = (
seen.get(f as usize).map_or(0, Vec::len),
seen.get(best as usize).map_or(0, Vec::len),
);
if a > b {
best = f;
}
}
best
}
};
let mut split = false;
for &f in &roots {
if f == keeper {
continue;
}
let Some(members) = seen.get(f as usize).cloned() else {
continue;
};
if members.is_empty() {
continue;
}
let fresh = self.take_label();
let mut moved_faces = 0u32;
for &i in &members {
if let Some(slot) = self.label.get_mut(i) {
*slot = fresh;
}
moved_faces += self.solid_faces(i);
}
self.set_size(fresh, members.len() as u32);
self.area_sub(l, moved_faces);
self.area_add(fresh, moved_faces);
let left = self.size_of(l).saturating_sub(members.len() as u32);
self.set_size(l, left);
out.shed += 1;
split = true;
}
if split {
out.splits += 1;
}
}
}
mod world;
pub use world::{AirWorld, Seams};
#[cfg(test)]
mod tests;