use crate::cube::{edge_offset, is_inside, place};
use crate::marching_cubes::table::NO_EDGE;
use crate::vec3;
use crate::{MeshSink, Real, Sdf};
use super::table::{
AMBIGUOUS_FACES, EDGE_COUNT, EDGE_SAMPLES, SAMPLE_COUNT, is_half_resolution, transition_links,
};
#[derive(Clone, Copy, Debug)]
pub struct TransitionCell<R: Real> {
pub value: [R; SAMPLE_COUNT],
pub position: [[R; 3]; SAMPLE_COUNT],
pub back: [[R; 3]; 4],
}
#[inline]
#[must_use]
pub const fn back_slot(sample: u8) -> Option<usize> {
match sample {
0 => Some(0),
2 => Some(1),
6 => Some(2),
8 => Some(3),
_ => None,
}
}
impl<R: Real> TransitionCell<R> {
pub fn sample<S: Sdf<Scalar = R>>(
sdf: &S,
origin: [R; 3],
step: R,
base: [i64; 3],
u: usize,
v: usize,
width: R,
) -> Self {
debug_assert!(u < 3 && v < 3 && u != v, "u and v must be distinct axes");
let normal = 3 - u - v;
let mut value = [R::ZERO; SAMPLE_COUNT];
let mut position = [[R::ZERO; 3]; SAMPLE_COUNT];
let mut back = [[R::ZERO; 3]; 4];
for s in 0..SAMPLE_COUNT {
let mut index = base;
index[u] += (s % 3) as i64;
index[v] += (s / 3) as i64;
let mut p = [R::ZERO; 3];
for (axis, slot) in p.iter_mut().enumerate() {
*slot = origin[axis] + step * R::from_f64(index[axis] as f64);
}
position[s] = p;
value[s] = sdf.sample(p);
if let Some(k) = back_slot(s as u8) {
let mut q = p;
q[normal] += width;
back[k] = q;
}
}
Self {
value,
position,
back,
}
}
fn endpoint(&self, edge: u8, sample: u8) -> [R; 3] {
if is_half_resolution(edge) {
if let Some(k) = back_slot(sample) {
return self.back[k];
}
debug_assert!(false, "a half-resolution edge joins two corners");
}
self.position[sample as usize]
}
#[must_use]
pub fn is_cut(&self, edge: u8) -> bool {
let [a, b] = EDGE_SAMPLES[edge as usize];
is_inside(self.value[a as usize]) != is_inside(self.value[b as usize])
}
#[must_use]
pub fn crossing(&self, edge: u8) -> Option<[R; 3]> {
if !self.is_cut(edge) {
return None;
}
let [lo, hi] = EDGE_SAMPLES[edge as usize];
let (a, b) = (self.value[lo as usize], self.value[hi as usize]);
let d = edge_offset(a, b);
let (lo_pos, hi_pos) = (self.endpoint(edge, lo), self.endpoint(edge, hi));
Some([
place(lo_pos[0], hi_pos[0], d),
place(lo_pos[1], hi_pos[1], d),
place(lo_pos[2], hi_pos[2], d),
])
}
pub fn crossings(&self) -> impl Iterator<Item = (u8, [R; 3])> + '_ {
(0..EDGE_COUNT as u8).filter_map(|e| self.crossing(e).map(|p| (e, p)))
}
#[must_use]
pub fn case(&self) -> u16 {
let mut case = 0u16;
for (s, v) in self.value.iter().enumerate() {
if is_inside(*v) {
case |= 1 << s;
}
}
case
}
#[must_use]
pub fn centroid(&self) -> Option<[R; 3]> {
let mut sum = [R::ZERO; 3];
let mut n = 0u32;
for (_, p) in self.crossings() {
for (axis, slot) in sum.iter_mut().enumerate() {
*slot += p[axis];
}
n += 1;
}
if n == 0 {
return None;
}
Some(vec3::scale(sum, R::from_f64(f64::from(n)).recip()))
}
pub fn emit<S, M>(&self, sdf: &S, joined: u16, out: &mut M)
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
{
let case = self.case();
debug_assert_eq!(
joined & !AMBIGUOUS_FACES[case as usize],
0,
"a pairing was chosen for a face that is not ambiguous"
);
let next = transition_links(case, joined);
let e_u = vec3::sub(self.position[1], self.position[0]);
let e_v = vec3::sub(self.position[3], self.position[0]);
let towards_back = vec3::sub(self.back[0], self.position[0]);
let reversed = vec3::dot(vec3::cross(e_u, e_v), towards_back) < R::ZERO;
let mut visited = 0u16;
for start in 0..EDGE_COUNT as u8 {
if next[start as usize] == NO_EDGE || visited & (1 << start) != 0 {
continue;
}
let mut cycle = [[R::ZERO; 3]; EDGE_COUNT];
let mut len = 0usize;
let mut current = start;
while visited & (1 << current) == 0 {
visited |= 1 << current;
let Some(p) = self.crossing(current) else {
debug_assert!(false, "a linked edge must be cut");
break;
};
cycle[len] = p;
len += 1;
current = next[current as usize];
}
if len < 3 {
debug_assert!(
false,
"a cycle closes across faces and has three or more edges"
);
continue;
}
let mut sum = [R::ZERO; 3];
for p in &cycle[..len] {
for (axis, slot) in sum.iter_mut().enumerate() {
*slot += p[axis];
}
}
let centre = vec3::scale(sum, R::from_f64(len as f64).recip());
let hub = out.vertex(centre, unit_gradient(sdf, centre));
let mut spoke = [0u32; EDGE_COUNT];
for (k, p) in cycle[..len].iter().enumerate() {
spoke[k] = out.vertex(*p, unit_gradient(sdf, *p));
}
for k in 0..len {
let (b, c) = (spoke[k], spoke[(k + 1) % len]);
if reversed {
out.triangle(hub, c, b);
} else {
out.triangle(hub, b, c);
}
}
}
}
}
fn unit_gradient<R: Real, S: Sdf<Scalar = R>>(sdf: &S, p: [R; 3]) -> [R; 3] {
let g = sdf.gradient(p);
let length = vec3::length(g);
debug_assert!(length > R::ZERO, "zero gradient at a surface vertex");
vec3::scale(g, length.recip())
}
#[cfg(test)]
mod tests;