use alloc::vec::Vec;
use super::{Air, Fill, Repair};
use crate::Real;
use crate::chunk::{ChunkId, ChunkLayout};
const NONE: u32 = u32::MAX;
#[derive(Debug)]
struct Loaded {
id: ChunkId,
air: Air,
global: Vec<u32>,
}
#[derive(Debug)]
struct Seam {
lo: ChunkId,
hi: ChunkId,
pairs: Vec<(u32, u32)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Seams {
pub rescanned: u64,
pub plane_samples: u64,
pub nodes: u64,
pub pairs: u64,
}
#[derive(Debug)]
pub struct AirWorld<R: Real> {
layout: ChunkLayout<R>,
chunks: Vec<Loaded>,
seams: Vec<Seam>,
components: u64,
last: Seams,
}
impl<R: Real> AirWorld<R> {
#[must_use]
pub fn new(layout: ChunkLayout<R>) -> Self {
Self {
layout,
chunks: Vec::new(),
seams: Vec::new(),
components: 0,
last: Seams::default(),
}
}
pub fn load(&mut self, id: ChunkId, values: &[R]) -> crate::Result<Repair> {
let shape = self.layout.sample_shape()?;
let (air, repair) = Air::build(values, &shape)?;
let global = alloc::vec![NONE; air.label_count()];
let entry = Loaded { id, air, global };
match self.chunks.binary_search_by(|c| c.id.cmp(&id)) {
Ok(at) => {
if let Some(slot) = self.chunks.get_mut(at) {
*slot = entry;
}
}
Err(at) => self.chunks.insert(at, entry),
}
self.rescan_around(id);
self.restitch();
Ok(repair)
}
pub fn dig<B: FnMut() -> bool>(
&mut self,
id: ChunkId,
samples: &[[u32; 3]],
spend: B,
) -> Option<Repair> {
let at = self.chunks.binary_search_by(|c| c.id.cmp(&id)).ok()?;
let repair = self.chunks.get_mut(at)?.air.dig(samples, spend);
self.resize_global(at);
self.rescan_around(id);
self.restitch();
Some(repair)
}
pub fn fill<B: FnMut() -> bool>(
&mut self,
id: ChunkId,
samples: &[[u32; 3]],
spend: B,
) -> Option<Fill> {
let at = self.chunks.binary_search_by(|c| c.id.cmp(&id)).ok()?;
let out = self.chunks.get_mut(at)?.air.fill(samples, spend);
self.resize_global(at);
self.rescan_around(id);
self.restitch();
Some(out)
}
#[must_use]
pub fn connected(&self, a: [i64; 3], b: [i64; 3]) -> bool {
match (self.component_at(a), self.component_at(b)) {
(Some(x), Some(y)) => x == y,
_ => false,
}
}
#[must_use]
pub fn component_at(&self, sample: [i64; 3]) -> Option<u32> {
let (id, local) = self.layout.local_sample(sample);
let at = self.chunks.binary_search_by(|c| c.id.cmp(&id)).ok()?;
let entry = self.chunks.get(at)?;
let label = entry.air.label_of(local)?;
match entry.global.get(label as usize) {
Some(&g) if g != NONE => Some(g),
_ => None,
}
}
#[must_use]
pub fn components(&self) -> u64 {
self.components
}
#[must_use]
pub fn chunk(&self, id: ChunkId) -> Option<&Air> {
let at = self.chunks.binary_search_by(|c| c.id.cmp(&id)).ok()?;
self.chunks.get(at).map(|c| &c.air)
}
#[must_use]
pub fn loaded(&self) -> usize {
self.chunks.len()
}
#[must_use]
pub fn last_seams(&self) -> Seams {
self.last
}
fn resize_global(&mut self, at: usize) {
let Some(entry) = self.chunks.get_mut(at) else {
return;
};
let want = entry.air.label_count();
if entry.global.len() < want {
entry.global.resize(want, NONE);
}
}
fn rescan_around(&mut self, id: ChunkId) {
let mut touched: Vec<(ChunkId, ChunkId)> = Vec::new();
for axis in 0..3 {
for step in [-1i32, 1] {
let Some(other) = neighbour(id, axis, step) else {
continue;
};
if self.chunks.binary_search_by(|c| c.id.cmp(&other)).is_err() {
continue;
}
let pair = if step > 0 { (id, other) } else { (other, id) };
touched.push(pair);
}
}
for (lo, hi) in touched {
let pairs = self.scan_seam(lo, hi);
match self.seams.iter_mut().find(|s| s.lo == lo && s.hi == hi) {
Some(seam) => seam.pairs = pairs,
None => self.seams.push(Seam { lo, hi, pairs }),
}
}
self.seams.retain(|s| {
self.chunks.binary_search_by(|c| c.id.cmp(&s.lo)).is_ok()
&& self.chunks.binary_search_by(|c| c.id.cmp(&s.hi)).is_ok()
});
}
fn scan_seam(&mut self, lo: ChunkId, hi: ChunkId) -> Vec<(u32, u32)> {
let mut pairs: Vec<(u32, u32)> = Vec::new();
let axis = match (0..3).find(|&a| lo.coords[a] != hi.coords[a]) {
Some(a) => a,
None => return pairs,
};
let (Ok(a_at), Ok(b_at)) = (
self.chunks.binary_search_by(|c| c.id.cmp(&lo)),
self.chunks.binary_search_by(|c| c.id.cmp(&hi)),
) else {
return pairs;
};
let cells = self.layout.cells();
let (u, v) = match axis {
0 => (1, 2),
1 => (0, 2),
_ => (0, 1),
};
let mut samples = 0u64;
for a in 0..=cells {
for b in 0..=cells {
let mut lo_local = [0u32; 3];
lo_local[axis] = cells;
lo_local[u] = a;
lo_local[v] = b;
let mut hi_local = [0u32; 3];
hi_local[axis] = 0;
hi_local[u] = a;
hi_local[v] = b;
samples += 1;
let la = self.chunks.get(a_at).and_then(|c| c.air.label_of(lo_local));
let lb = self.chunks.get(b_at).and_then(|c| c.air.label_of(hi_local));
if let (Some(la), Some(lb)) = (la, lb)
&& !pairs.contains(&(la, lb))
{
pairs.push((la, lb));
}
}
}
self.last.rescanned += 1;
self.last.plane_samples += samples;
pairs
}
fn restitch(&mut self) {
let (rescanned, plane_samples) = (self.last.rescanned, self.last.plane_samples);
self.last = Seams {
rescanned,
plane_samples,
..Seams::default()
};
let mut offset = Vec::with_capacity(self.chunks.len());
let mut total = 0usize;
for c in &self.chunks {
offset.push(total);
total += c.air.label_count();
}
self.last.nodes = total as u64;
let mut parent: Vec<u32> = (0..total as u32).collect();
fn find(parent: &mut [u32], mut i: u32) -> u32 {
while parent.get(i as usize).copied().unwrap_or(i) != i {
let p = parent.get(i as usize).copied().unwrap_or(i);
let grand = parent.get(p as usize).copied().unwrap_or(p);
if let Some(slot) = parent.get_mut(i as usize) {
*slot = grand;
}
i = grand;
}
i
}
let mut unions = 0u64;
for seam in &self.seams {
let (Ok(a_at), Ok(b_at)) = (
self.chunks.binary_search_by(|c| c.id.cmp(&seam.lo)),
self.chunks.binary_search_by(|c| c.id.cmp(&seam.hi)),
) else {
continue;
};
let (Some(&ao), Some(&bo)) = (offset.get(a_at), offset.get(b_at)) else {
continue;
};
for &(la, lb) in &seam.pairs {
let x = find(&mut parent, ao as u32 + la);
let y = find(&mut parent, bo as u32 + lb);
unions += 1;
if x != y
&& let Some(slot) = parent.get_mut(y as usize)
{
*slot = x;
}
}
}
self.last.pairs = unions;
let mut world_of: Vec<u32> = alloc::vec![NONE; total];
let mut next = 0u32;
for (k, c) in self.chunks.iter().enumerate() {
let Some(&off) = offset.get(k) else { continue };
for l in 0..c.air.label_count() {
if c.air.component_size(l as u32) == 0 {
continue;
}
let root = find(&mut parent, (off + l) as u32);
if world_of.get(root as usize) == Some(&NONE) {
if let Some(slot) = world_of.get_mut(root as usize) {
*slot = next;
}
next += 1;
}
}
}
self.components = u64::from(next);
for (k, c) in self.chunks.iter_mut().enumerate() {
let Some(&off) = offset.get(k) else { continue };
c.global.clear();
c.global.resize(c.air.label_count(), NONE);
for l in 0..c.air.label_count() {
if c.air.component_size(l as u32) == 0 {
continue;
}
let root = find(&mut parent, (off + l) as u32);
let g = world_of.get(root as usize).copied().unwrap_or(NONE);
if let Some(slot) = c.global.get_mut(l) {
*slot = g;
}
}
}
}
}
fn neighbour(id: ChunkId, axis: usize, step: i32) -> Option<ChunkId> {
let mut coords = id.coords;
let slot = coords.get_mut(axis)?;
*slot = slot.checked_add(step)?;
Some(ChunkId::new(coords))
}
#[cfg(test)]
mod tests;