use alloc::vec::Vec;
use core::fmt;
use crate::{Real, Shape3};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Repair {
pub dirty: u64,
pub already_air: u64,
pub unions: u64,
pub merges: u64,
}
impl Repair {
#[must_use]
pub fn unions_per_dirty(&self) -> f64 {
if self.dirty == 0 {
0.0
} else {
self.unions 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), {} unions ({:.2}/dirty), {} merges",
self.dirty,
self.already_air,
self.unions,
self.unions_per_dirty(),
self.merges
)
}
}
#[derive(Clone, Debug)]
pub struct Air {
air: Vec<bool>,
parent: Vec<u32>,
size: Vec<u32>,
dims: [u32; 3],
}
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(),
parent: (0..count as u32).collect(),
size: alloc::vec![1u32; count],
dims,
};
let mut repair = Repair::default();
for z in 0..dims[2] {
for y in 0..dims[1] {
for x in 0..dims[0] {
let i = me.index([x, y, z]);
if me.air.get(i) != Some(&true) {
continue;
}
repair.dirty += 1;
me.link_forward([x, y, z], &mut repair);
}
}
}
Ok((me, repair))
}
pub fn dig(&mut self, samples: &[[u32; 3]]) -> Repair {
let mut repair = Repair::default();
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;
}
None => {}
}
}
for s in samples {
if !self.in_range(*s) {
continue;
}
self.link_all(*s, &mut repair);
}
repair
}
pub fn connected(&mut 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));
if self.air.get(ia) != Some(&true) || self.air.get(ib) != Some(&true) {
return false;
}
self.find(ia) == self.find(ib)
}
pub fn components(&mut self) -> u64 {
let mut n = 0;
for i in 0..self.air.len() {
if self.air.get(i) == Some(&true) && self.find(i) == i {
n += 1;
}
}
n
}
#[must_use]
pub fn air_samples(&self) -> u64 {
self.air.iter().filter(|a| **a).count() as u64
}
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 link_forward(&mut self, p: [u32; 3], repair: &mut Repair) {
for axis in 0..3 {
let mut q = p;
q[axis] += 1;
self.try_link(p, q, repair);
}
}
fn link_all(&mut self, p: [u32; 3], repair: &mut Repair) {
for axis in 0..3 {
let mut q = p;
q[axis] += 1;
self.try_link(p, q, repair);
let mut q = p;
let Some(lower) = q[axis].checked_sub(1) else {
continue;
};
q[axis] = lower;
self.try_link(p, q, repair);
}
}
fn try_link(&mut self, p: [u32; 3], q: [u32; 3], repair: &mut Repair) {
if !self.in_range(q) {
return;
}
let (i, j) = (self.index(p), self.index(q));
if self.air.get(j) != Some(&true) {
return;
}
repair.unions += 1;
if self.union(i, j) {
repair.merges += 1;
}
}
fn find(&mut self, mut i: usize) -> usize {
while self.parent.get(i).copied().map(|p| p as usize) != Some(i) {
let Some(p) = self.parent.get(i).copied() else {
return i;
};
let grand = self.parent.get(p as usize).copied().unwrap_or(p);
if let Some(slot) = self.parent.get_mut(i) {
*slot = grand;
}
i = grand as usize;
}
i
}
fn union(&mut self, a: usize, b: usize) -> bool {
let (mut ra, mut rb) = (self.find(a), self.find(b));
if ra == rb {
return false;
}
if self.size.get(ra).copied().unwrap_or(0) < self.size.get(rb).copied().unwrap_or(0) {
core::mem::swap(&mut ra, &mut rb);
}
if let Some(slot) = self.parent.get_mut(rb) {
*slot = ra as u32;
}
let grew = self.size.get(rb).copied().unwrap_or(0);
if let Some(slot) = self.size.get_mut(ra) {
*slot += grew;
}
true
}
}
#[cfg(test)]
mod tests;