use alloc::vec::Vec;
use super::{DirtySet, EditReport, tally};
use crate::chunk::ChunkLayout;
use crate::cube::{corner_offset, is_inside};
use crate::marching_tetrahedra::table::{TET_COUNT, TET_EDGE_COUNT, tet_edge_corners};
use crate::subgrid::roots::{edge_point, edge_step, sample_parameter};
use crate::{Real, Sdf};
const CHANGED: u8 = 1;
const INSIDE_BEFORE: u8 = 2;
const INSIDE_AFTER: u8 = 4;
const SHOWED_CHANGE: u8 = 1;
const SHOWED_SIGN: u8 = 2;
const CROSSED_BEFORE: u8 = 4;
const CROSSED_AFTER: u8 = 8;
#[derive(Clone, Copy, Default)]
struct EdgeSpec {
lo: [usize; 3],
hi: [usize; 3],
direction: usize,
through_z: bool,
}
fn cell_edges() -> ([EdgeSpec; TET_COUNT * TET_EDGE_COUNT], usize) {
let mut specs = [EdgeSpec::default(); TET_COUNT * TET_EDGE_COUNT];
let mut seen = [[false; 8]; 8];
let mut count = 0usize;
for tet in 0..TET_COUNT {
for edge in 0..TET_EDGE_COUNT {
let [lo, hi] = tet_edge_corners(tet, edge);
let (l, h) = (usize::from(lo), usize::from(hi));
if seen[l][h] {
continue;
}
seen[l][h] = true;
let a = corner_offset(lo);
let b = corner_offset(hi);
let step = lo ^ hi;
let through_z = step >= 4;
specs[count] = EdgeSpec {
lo: [a[0] as usize, a[1] as usize, a[2] as usize],
hi: [b[0] as usize, b[1] as usize, b[2] as usize],
direction: usize::from(if through_z { step - 4 } else { step - 1 }),
through_z,
};
count += 1;
}
}
(specs, count)
}
struct Plane {
corner: Vec<u8>,
along: [Vec<u8>; 3],
}
struct Slab {
along: [Vec<u8>; 4],
}
struct Probes<'a, R: Real, A, B> {
layout: &'a ChunkLayout<R>,
before: &'a A,
after: &'a B,
min_cell: [i64; 3],
extent: [usize; 3],
samples: u32,
step: R,
}
impl<R, A, B> Probes<'_, R, A, B>
where
R: Real,
A: Sdf<Scalar = R>,
B: Sdf<Scalar = R>,
{
fn interior(&self) -> usize {
self.samples as usize - 1
}
fn position(&self, x: usize, y: usize, z: usize) -> [R; 3] {
self.layout.world_of_sample([
self.min_cell[0] + x as i64,
self.min_cell[1] + y as i64,
self.min_cell[2] + z as i64,
])
}
fn flags(&self, p: [R; 3]) -> u8 {
let a = self.before.sample(p);
let b = self.after.sample(p);
let mut f = 0;
if a.total_cmp(&b) != core::cmp::Ordering::Equal {
f |= CHANGED;
}
if is_inside(a) {
f |= INSIDE_BEFORE;
}
if is_inside(b) {
f |= INSIDE_AFTER;
}
f
}
fn new_plane(&self) -> Plane {
let n = self.extent[0] * self.extent[1];
let m = n * self.interior();
Plane {
corner: alloc::vec![0; n],
along: [alloc::vec![0; m], alloc::vec![0; m], alloc::vec![0; m]],
}
}
fn new_slab(&self) -> Slab {
let m = self.extent[0] * self.extent[1] * self.interior();
Slab {
along: [
alloc::vec![0; m],
alloc::vec![0; m],
alloc::vec![0; m],
alloc::vec![0; m],
],
}
}
fn fill_plane(&self, out: &mut Plane, z: usize) {
let [ex, ey, _] = self.extent;
let interior = self.interior();
for y in 0..ey {
for x in 0..ex {
out.corner[y * ex + x] = self.flags(self.position(x, y, z));
}
}
for (direction, (dx, dy)) in [(1, 0), (0, 1), (1, 1)].into_iter().enumerate() {
for y in 0..ey.saturating_sub(dy) {
for x in 0..ex.saturating_sub(dx) {
let from = self.position(x, y, z);
let to = self.position(x + dx, y + dy, z);
let slot = (y * ex + x) * interior;
for k in 1..self.samples {
let t = sample_parameter(k, self.samples, self.step);
out.along[direction][slot + k as usize - 1] =
self.flags(edge_point(from, to, t));
}
}
}
}
}
fn fill_slab(&self, out: &mut Slab, z: usize) {
let [ex, ey, _] = self.extent;
let interior = self.interior();
for (direction, (dx, dy)) in [(0, 0), (1, 0), (0, 1), (1, 1)].into_iter().enumerate() {
for y in 0..ey.saturating_sub(dy) {
for x in 0..ex.saturating_sub(dx) {
let from = self.position(x, y, z);
let to = self.position(x + dx, y + dy, z + 1);
let slot = (y * ex + x) * interior;
for k in 1..self.samples {
let t = sample_parameter(k, self.samples, self.step);
out.along[direction][slot + k as usize - 1] =
self.flags(edge_point(from, to, t));
}
}
}
}
}
}
#[inline]
fn scan_edge(first: u8, inner: &[u8], last: u8) -> u8 {
let point = |f: u8| (f & CHANGED) | ((((f >> 1) ^ (f >> 2)) & 1) << 1);
let mut shown = point(first);
let mut prev = first;
for &f in inner.iter().chain(core::iter::once(&last)) {
shown |= point(f);
shown |= ((prev ^ f) & (INSIDE_BEFORE | INSIDE_AFTER)) << 1;
prev = f;
}
shown
}
pub fn mark_edit_subgrid<R, A, B>(
layout: &ChunkLayout<R>,
before: &A,
after: &B,
min_cell: [i64; 3],
max_cell: [i64; 3],
samples: u32,
dirty: &mut DirtySet,
) -> crate::Result<EditReport>
where
R: Real,
A: Sdf<Scalar = R>,
B: Sdf<Scalar = R>,
{
if samples == 0 {
return Err(crate::Error::InvalidCellSize { value: 0.0 });
}
let mut report = EditReport::default();
for axis in 0..3 {
if max_cell[axis] < min_cell[axis] {
return Ok(report);
}
}
let mut lattice = 1u64;
let mut extent = [0usize; 3];
for axis in 0..3 {
let planes = (max_cell[axis].wrapping_sub(min_cell[axis]) as u64).saturating_add(2);
lattice = lattice.saturating_mul(planes);
extent[axis] = planes as usize;
}
let probes = lattice.saturating_mul(1 + 7 * u64::from(samples - 1));
if probes > u64::from(u32::MAX) {
return Err(crate::Error::IndexSpaceExhausted { needed: probes });
}
let grid = Probes {
layout,
before,
after,
min_cell,
extent,
samples,
step: edge_step::<R>(samples),
};
let interior = grid.interior();
let [ex, ey, ez] = extent;
let (edges, edge_count) = cell_edges();
let edges = &edges[..edge_count];
let mut low = grid.new_plane();
let mut high = grid.new_plane();
let mut slab = grid.new_slab();
grid.fill_plane(&mut low, 0);
let mut dirtied = DirtySet::new();
let mut region_chunks = DirtySet::new();
for cz in 0..ez - 1 {
grid.fill_plane(&mut high, cz + 1);
grid.fill_slab(&mut slab, cz);
for cy in 0..ey - 1 {
for cx in 0..ex - 1 {
let mut shown = 0u8;
for e in edges {
let a = (cy + e.lo[1]) * ex + cx + e.lo[0];
let b = (cy + e.hi[1]) * ex + cx + e.hi[0];
let slot = a * interior;
shown |= if e.through_z {
scan_edge(
low.corner[a],
&slab.along[e.direction][slot..slot + interior],
high.corner[b],
)
} else {
let plane = if e.lo[2] == 0 { &low } else { &high };
scan_edge(
plane.corner[a],
&plane.along[e.direction][slot..slot + interior],
plane.corner[b],
)
};
}
let cell = [
min_cell[0] + cx as i64,
min_cell[1] + cy as i64,
min_cell[2] + cz as i64,
];
let (owner, _) = layout.local_sample(cell);
region_chunks.insert(owner);
if tally(
&mut report,
shown & SHOWED_CHANGE != 0,
shown & SHOWED_SIGN != 0,
shown & CROSSED_BEFORE != 0,
shown & CROSSED_AFTER != 0,
) {
dirtied.insert(owner);
}
}
}
core::mem::swap(&mut low, &mut high);
}
report.region_chunks = region_chunks.len() as u64;
report.dirty_chunks = dirtied.len() as u64;
for id in dirtied.iter() {
dirty.insert(id);
}
Ok(report)
}
#[cfg(test)]
mod tests;