use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::cube::corner_offset;
use crate::marching_tetrahedra::table::{TET_EDGE_COUNT, TET_EDGES, TETS};
use crate::mesh::MeshSink;
use crate::real::Real;
use crate::sdf::Sdf;
use crate::shape::Shape3;
use crate::vec3;
use super::curves::FacePoint;
use super::roots::all_roots;
use super::surface::{TetCrossings, TetPatch, Unfilled, fill};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum CrossingKey {
OnEdge {
lo: [u32; 3],
hi: [u32; 3],
index: u32,
},
OnGridPoint { at: [u32; 3] },
}
fn corners_on_surface<R: Real, S: Sdf<Scalar = R>>(sdf: &S, corners: &[[R; 3]; 4]) -> [bool; 4] {
corners.map(|c| {
matches!(
sdf.sample(c).partial_cmp(&R::ZERO),
Some(core::cmp::Ordering::Equal)
)
})
}
impl CrossingKey {
fn edge_grid_points(cell: [u32; 3], tet: usize, edge: u8) -> [[u32; 3]; 2] {
let [a, b] = TET_EDGES[edge as usize];
[a, b].map(|corner| {
let offset = corner_offset(TETS[tet][corner as usize]);
[
cell[0] + offset[0],
cell[1] + offset[1],
cell[2] + offset[2],
]
})
}
fn of(cell: [u32; 3], tet: usize, point: FacePoint, endpoint: Option<u8>) -> Self {
let [lo, hi] = Self::edge_grid_points(cell, tet, point.edge);
match endpoint {
Some(0) => Self::OnGridPoint { at: lo },
Some(_) => Self::OnGridPoint { at: hi },
None => Self::OnEdge {
lo,
hi,
index: point.index,
},
}
}
}
#[derive(Clone, Debug)]
pub struct SubgridMarchingTetrahedra<R: Real> {
samples: u32,
lipschitz: Option<R>,
along: [Vec<R>; TET_EDGE_COUNT],
patch: TetPatch<R>,
index: Vec<u32>,
shared: BTreeMap<CrossingKey, u32>,
}
impl<R: Real> SubgridMarchingTetrahedra<R> {
pub fn new(samples: u32) -> crate::Result<Self> {
if samples == 0 {
return Err(crate::Error::InvalidCellSize { value: 0.0 });
}
Ok(Self {
samples,
lipschitz: None,
along: Default::default(),
patch: TetPatch::new(),
index: Vec::new(),
shared: BTreeMap::new(),
})
}
#[must_use]
pub const fn samples(&self) -> u32 {
self.samples
}
pub fn set_lipschitz(&mut self, l: Option<R>) {
self.lipschitz = l;
}
fn cell_is_provably_empty<S>(
&self,
sdf: &S,
origin: [R; 3],
cell_size: R,
cell: [u32; 3],
) -> bool
where
S: Sdf<Scalar = R>,
{
let Some(l) = self.lipschitz else {
return false;
};
let half = cell_size * R::HALF;
let centre = [
origin[0] + cell_size * R::from_f64(f64::from(cell[0])) + half,
origin[1] + cell_size * R::from_f64(f64::from(cell[1])) + half,
origin[2] + cell_size * R::from_f64(f64::from(cell[2])) + half,
];
let radius = half * R::from_f64(1.732_050_807_568_877_2);
sdf.sample(centre).abs() > l * radius
}
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 });
}
self.shared.clear();
let mut vertices: u64 = 0;
for z in 0..size[2] - 1 {
for y in 0..size[1] - 1 {
for x in 0..size[0] - 1 {
let cell = [x, y, z];
if self.cell_is_provably_empty(sdf, origin, cell_size, cell) {
continue;
}
for t in 0..TETS.len() {
self.cell_tet(sdf, origin, cell_size, cell, t, &mut vertices, out)?;
}
}
}
}
Ok(())
}
fn endpoint_of(&self, point: FacePoint, on_surface: &[bool; 4]) -> Option<u8> {
let [a, b] = TET_EDGES[point.edge as usize];
let roots = &self.along[point.edge as usize];
let count = roots.len() as u32;
if count == 0 {
return None;
}
let step = R::ONE / R::from_f64(f64::from(self.samples));
let last_interval_start = R::from_f64(f64::from(self.samples - 1)) * step;
let t = *roots.get(point.index as usize)?;
let at_lo = on_surface[a as usize] && point.index == 0 && t <= step;
let at_hi = on_surface[b as usize] && point.index == count - 1 && t >= last_interval_start;
match (at_lo, at_hi) {
(true, true) => Some(u8::from(t + t >= R::ONE)),
(true, false) => Some(0),
(false, true) => Some(1),
(false, false) => None,
}
}
#[allow(clippy::too_many_arguments)]
fn cell_tet<S, M>(
&mut self,
sdf: &S,
origin: [R; 3],
cell_size: R,
cell: [u32; 3],
t: usize,
vertices: &mut u64,
out: &mut M,
) -> crate::Result<()>
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
{
let mut corners = [[R::ZERO; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(TETS[t][c]);
for axis in 0..3 {
let index = f64::from(cell[axis]) + f64::from(offset[axis]);
slot[axis] = origin[axis] + cell_size * R::from_f64(index);
}
}
let mut total = 0usize;
for (e, slot) in self.along.iter_mut().enumerate() {
slot.clear();
let [lo, hi] = TET_EDGES[e];
all_roots(
corners[lo as usize],
corners[hi as usize],
sdf,
self.samples,
slot,
);
total += slot.len();
}
if total == 0 {
return Ok(());
}
let mut borrowed: [&[R]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(self.along.iter()) {
*slot = v.as_slice();
}
let crossings = TetCrossings {
corners,
along: borrowed,
};
let unfilled = fill(&crossings, &mut self.patch).map_err(|_| {
crate::Error::SubgridUnfilled {
cell,
tet: t as u8,
reason: "crossings rejected as malformed",
}
})?;
if unfilled != Unfilled::None {
return Err(crate::Error::SubgridUnfilled {
cell,
tet: t as u8,
reason: match unfilled {
Unfilled::SingleLoop => "single loop",
Unfilled::Subdivision => "subdivision",
Unfilled::NonNormalLoop => "non-normal loop",
Unfilled::NoPattern => "residual is not a (d1, d2) pattern",
Unfilled::Inconsistent => "curves disagree with the crossings",
Unfilled::None => unreachable!(),
},
});
}
let on_surface = corners_on_surface(sdf, &corners);
self.index.clear();
self.index.reserve(self.patch.positions.len());
for (at, position) in self.patch.positions.iter().enumerate() {
let crossing = self.patch.crossings.get(at).copied();
let endpoint = crossing.and_then(|point| self.endpoint_of(point, &on_surface));
let key = crossing.map(|point| CrossingKey::of(cell, t, point, endpoint));
if let Some(existing) = key.and_then(|k| self.shared.get(&k)) {
self.index.push(*existing);
continue;
}
let position = &match (crossing, endpoint) {
(Some(point), Some(end)) => {
let [a, b] = TET_EDGES[point.edge as usize];
corners[if end == 0 { a } else { b } as usize]
}
_ => *position,
};
let g = sdf.gradient(*position);
let length = vec3::length(g);
if !length.is_finite() || length <= R::ZERO {
return Err(crate::Error::DegenerateNormal { vertex: at as u64 });
}
let normal = vec3::scale(g, length.recip());
if *vertices >= u64::from(u32::MAX) {
return Err(crate::Error::IndexSpaceExhausted {
needed: *vertices + 1,
});
}
let emitted = out.vertex(*position, normal);
*vertices += 1;
if let Some(key) = key {
self.shared.insert(key, emitted);
}
self.index.push(emitted);
}
for tri in &self.patch.triangles {
let (a, b, c) = (
self.index.get(tri[0] as usize),
self.index.get(tri[1] as usize),
self.index.get(tri[2] as usize),
);
let (Some(a), Some(b), Some(c)) = (a, b, c) else {
return Err(crate::Error::SubgridUnfilled {
cell,
tet: t as u8,
reason: "a triangle indexed a vertex the patch does not have",
});
};
if a == b || b == c || a == c {
continue;
}
let (pa, pb, pc) = (
self.patch.positions[tri[0] as usize],
self.patch.positions[tri[1] as usize],
self.patch.positions[tri[2] as usize],
);
let face = vec3::cross(vec3::sub(pb, pa), vec3::sub(pc, pa));
let third = R::ONE / R::from_f64(3.0);
let centroid = [
(pa[0] + pb[0] + pc[0]) * third,
(pa[1] + pb[1] + pc[1]) * third,
(pa[2] + pb[2] + pc[2]) * third,
];
let outward = vec3::dot(face, sdf.gradient(centroid));
if outward < R::ZERO {
out.triangle(*a, *c, *b);
} else {
out.triangle(*a, *b, *c);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests;