#[cfg(test)]
mod tests;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::Sdf;
use crate::real::Real;
use crate::shape::Shape3;
use super::from_mesh::{MeshField, cross, dot, norm, sub};
const APEX_DISTANCE: f64 = 64.0;
const PERTURB: [f64; 2] = [1.234_567e-5, 7.654_321e-5];
fn solid_angle<R: Real>(q: [R; 3], a: [R; 3], b: [R; 3], c: [R; 3]) -> R {
let a = sub(a, q);
let b = sub(b, q);
let c = sub(c, q);
let (la, lb, lc) = (norm(a), norm(b), norm(c));
if la == R::ZERO || lb == R::ZERO || lc == R::ZERO {
return R::ZERO;
}
let numer = dot(a, cross(b, c));
let denom = la * lb * lc + dot(a, b) * lc + dot(a, c) * lb + dot(b, c) * la;
R::TWO * numer.atan2(denom)
}
fn boundary_edges(indices: &[u32]) -> Vec<([u32; 2], i32)> {
let mut net: BTreeMap<(u32, u32), i32> = BTreeMap::new();
for tri in indices.chunks_exact(3) {
for (u, v) in [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])] {
if u == v {
continue;
}
let (key, delta) = if u < v { ((u, v), 1) } else { ((v, u), -1) };
*net.entry(key).or_insert(0) += delta;
}
}
net.into_iter()
.filter(|&(_, n)| n != 0)
.map(|((u, v), n)| ([u, v], n))
.collect()
}
struct Hit<R> {
x: R,
sign: i32,
}
pub fn winding_numbers<R: Real>(
positions: &[[R; 3]],
indices: &[u32],
shape: &impl Shape3,
origin: [R; 3],
cell_size: R,
) -> crate::Result<Vec<R>> {
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
if !indices.len().is_multiple_of(3) {
return Err(crate::Error::ShapeOverflow {
size,
product: indices.len() as u64,
});
}
if let Some(&bad) = indices.iter().find(|&&i| i as usize >= positions.len()) {
return Err(crate::Error::ShapeOverflow {
size,
product: u64::from(bad),
});
}
let boundary = boundary_edges(indices);
let triangles = indices.len() / 3;
let mut lo = origin;
let mut hi = origin;
for p in positions {
for axis in 0..3 {
if p[axis] < lo[axis] {
lo[axis] = p[axis];
}
if p[axis] > hi[axis] {
hi[axis] = p[axis];
}
}
}
let diagonal = norm(sub(hi, lo));
let back = if diagonal == R::ZERO {
R::ONE
} else {
diagonal
} * R::from_f64(APEX_DISTANCE);
let (nx, ny, nz) = (size[0], size[1], size[2]);
let mut out = alloc::vec![R::ZERO; shape.element_count()];
let mut hits: Vec<Hit<R>> = Vec::new();
let quarter = R::ONE / (R::from_f64(4.0) * R::from_f64(core::f64::consts::PI));
for z in 0..nz {
for y in 0..ny {
let py = origin[1] + cell_size * R::from_f64(f64::from(y));
let pz = origin[2] + cell_size * R::from_f64(f64::from(z));
let ry = py + cell_size * R::from_f64(PERTURB[0]);
let rz = pz + cell_size * R::from_f64(PERTURB[1]);
hits.clear();
for t in 0..triangles {
let tri = &indices[t * 3..t * 3 + 3];
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
if let Some(hit) = intersect_x_ray(a, b, c, ry, rz) {
hits.push(hit);
}
}
for x in 0..nx {
let px = origin[0] + cell_size * R::from_f64(f64::from(x));
let mut chi = 0i32;
for h in &hits {
if h.x > px {
chi += h.sign;
}
}
let q = [px, ry, rz];
let apex = [px - back, ry, rz];
let mut omega = R::ZERO;
for &([u, v], n) in &boundary {
let (first, second) = if n > 0 {
(positions[v as usize], positions[u as usize])
} else {
(positions[u as usize], positions[v as usize])
};
let one = solid_angle(q, first, second, apex);
omega += one * R::from_f64(f64::from(n.abs()));
}
let i = ((z * ny + y) * nx + x) as usize;
out[i] = R::from_f64(f64::from(chi)) - omega * quarter;
}
}
}
Ok(out)
}
fn intersect_x_ray<R: Real>(a: [R; 3], b: [R; 3], c: [R; 3], py: R, pz: R) -> Option<Hit<R>> {
let ab = sub(b, a);
let ac = sub(c, a);
let h = [R::ZERO, -ac[2], ac[1]];
let det = dot(ab, h);
if det.abs() < R::EPSILON {
return None;
}
let inv = R::ONE / det;
let s = [R::ZERO, py - a[1], pz - a[2]];
let u = inv * dot(s, h);
if u < R::ZERO || u > R::ONE {
return None;
}
let q = cross(s, ab);
let v = inv * q[0];
if v < R::ZERO || u + v > R::ONE {
return None;
}
let t = inv * dot(ac, q);
let n_x = ab[1] * ac[2] - ab[2] * ac[1];
Some(Hit {
x: a[0] + t,
sign: if n_x < R::ZERO { -1 } else { 1 },
})
}
pub fn signed_distance_from_mesh_winding<R: Real>(
positions: &[[R; 3]],
indices: &[u32],
shape: &impl Shape3,
origin: [R; 3],
cell_size: R,
threshold: R,
) -> crate::Result<Vec<R>> {
let winding = winding_numbers(positions, indices, shape, origin, cell_size)?;
let field = MeshField::new(positions, indices)?;
let size = shape.size();
let (nx, ny) = (size[0] as usize, size[1] as usize);
let plane = nx.checked_mul(ny).ok_or(crate::Error::ShapeOverflow {
size,
product: winding.len() as u64,
})?;
Ok(winding
.iter()
.enumerate()
.map(|(i, &w)| {
let z = i / plane;
let rest = i % plane;
let p = [
origin[0] + cell_size * R::from_f64((rest % nx) as f64),
origin[1] + cell_size * R::from_f64((rest / nx) as f64),
origin[2] + cell_size * R::from_f64(z as f64),
];
let magnitude = field.sample(p).abs();
if w > threshold { -magnitude } else { magnitude }
})
.collect())
}