pub mod table;
#[cfg(test)]
mod tests;
use alloc::vec::Vec;
use crate::cube::{corner_offset, edge_offset, is_inside, place};
use crate::vec3;
use crate::{MeshSink, Real, Sdf, Shape3};
use table::{TET_CASES, TET_COUNT, TETS, tet_edge_corners};
const STEPS: usize = 7;
#[derive(Debug)]
pub struct MarchingTetrahedra<R: Real> {
values: Vec<R>,
edge_vertices: Vec<u32>,
}
impl<R: Real> MarchingTetrahedra<R> {
#[must_use]
pub const fn new() -> Self {
Self {
values: Vec::new(),
edge_vertices: Vec::new(),
}
}
pub fn extract<S, M>(
&mut self,
sdf: &S,
shape: &impl Shape3,
origin: [R; 3],
cell_size: R,
out: &mut M,
) -> crate::Result<()>
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
{
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
let sample_count = shape.element_count();
let bound = STEPS as u64 * sample_count as u64;
if bound > u64::from(u32::MAX) {
return Err(crate::Error::IndexSpaceExhausted { needed: bound });
}
self.values.clear();
self.values.reserve(sample_count);
crate::sdf::sample_grid(
sdf,
size,
origin,
cell_size,
size[0] as usize,
&mut self.values,
);
self.edge_vertices.clear();
self.edge_vertices.resize(sample_count * STEPS, u32::MAX);
for z in 0..size[2] - 1 {
for y in 0..size[1] - 1 {
for x in 0..size[0] - 1 {
let base = [x, y, z];
let mut corner_value = [R::ZERO; 8];
for (c, slot) in corner_value.iter_mut().enumerate() {
let s = corner_sample(shape, base, c as u8);
*slot = self.values[s as usize];
}
for t in 0..TET_COUNT {
let mut case = 0u8;
for i in 0..4 {
if is_inside(corner_value[TETS[t][i] as usize]) {
case |= 1 << i;
}
}
let entry = &TET_CASES[t][case as usize];
for tri in &entry.triangles[..entry.count as usize] {
let mut idx = [0u32; 3];
for (k, &edge) in tri.iter().enumerate() {
idx[k] = self.vertex_on_edge(
sdf,
shape,
base,
t,
edge as usize,
&corner_value,
origin,
cell_size,
out,
);
}
out.triangle(idx[0], idx[1], idx[2]);
}
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn vertex_on_edge<S, M>(
&mut self,
sdf: &S,
shape: &impl Shape3,
base: [u32; 3],
tet: usize,
edge: usize,
corner_value: &[R; 8],
origin: [R; 3],
cell_size: R,
out: &mut M,
) -> u32
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
{
let [lo_corner, hi_corner] = tet_edge_corners(tet, edge);
let lo = corner_offset(lo_corner);
let hi = corner_offset(hi_corner);
let step = (hi[0] - lo[0]) + 2 * (hi[1] - lo[1]) + 4 * (hi[2] - lo[2]);
debug_assert!(
(1..=7).contains(&step),
"tetrahedron edge is not a 0/1 step"
);
let lo_sample = corner_sample(shape, base, lo_corner);
let key = lo_sample as usize * STEPS + (step - 1) as usize;
let cached = self.edge_vertices[key];
if cached != u32::MAX {
return cached;
}
let a = corner_value[lo_corner as usize];
let b = corner_value[hi_corner as usize];
debug_assert!(is_inside(a) != is_inside(b));
let d = edge_offset(a, b);
let lo_pos = corner_position(base, lo_corner, origin, cell_size);
let hi_pos = corner_position(base, hi_corner, origin, cell_size);
let position = [
place(lo_pos[0], hi_pos[0], d),
place(lo_pos[1], hi_pos[1], d),
place(lo_pos[2], hi_pos[2], d),
];
let g = sdf.gradient(position);
let len = vec3::length(g);
debug_assert!(len > R::ZERO, "zero gradient at a surface crossing");
let normal = vec3::scale(g, len.recip());
let index = out.vertex(position, normal);
self.edge_vertices[key] = index;
index
}
}
impl<R: Real> Default for MarchingTetrahedra<R> {
fn default() -> Self {
Self::new()
}
}
#[inline]
fn corner_sample(shape: &impl Shape3, base: [u32; 3], corner: u8) -> u32 {
let o = corner_offset(corner);
shape.linearize([base[0] + o[0], base[1] + o[1], base[2] + o[2]])
}
#[inline]
fn corner_position<R: Real>(base: [u32; 3], corner: u8, origin: [R; 3], cell_size: R) -> [R; 3] {
let o = corner_offset(corner);
[
origin[0] + cell_size * R::from_f64(f64::from(base[0] + o[0])),
origin[1] + cell_size * R::from_f64(f64::from(base[1] + o[1])),
origin[2] + cell_size * R::from_f64(f64::from(base[2] + o[2])),
]
}