#[cfg(test)]
mod tests;
use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use crate::real::Real;
use crate::sdf::Sdf;
use crate::shape::Shape3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Feature {
Face,
Edge(u8),
Vertex(u8),
}
pub(super) fn sub<R: Real>(a: [R; 3], b: [R; 3]) -> [R; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
pub(super) fn add<R: Real>(a: [R; 3], b: [R; 3]) -> [R; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
pub(super) fn scale<R: Real>(a: [R; 3], s: R) -> [R; 3] {
[a[0] * s, a[1] * s, a[2] * s]
}
pub(super) fn dot<R: Real>(a: [R; 3], b: [R; 3]) -> R {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
pub(super) fn cross<R: Real>(a: [R; 3], b: [R; 3]) -> [R; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub(super) fn norm<R: Real>(a: [R; 3]) -> R {
dot(a, a).sqrt()
}
pub(super) fn closest_on_triangle<R: Real>(
p: [R; 3],
a: [R; 3],
b: [R; 3],
c: [R; 3],
) -> ([R; 3], Feature) {
let ab = sub(b, a);
let ac = sub(c, a);
let ap = sub(p, a);
let d1 = dot(ab, ap);
let d2 = dot(ac, ap);
if d1 <= R::ZERO && d2 <= R::ZERO {
return (a, Feature::Vertex(0));
}
let bp = sub(p, b);
let d3 = dot(ab, bp);
let d4 = dot(ac, bp);
if d3 >= R::ZERO && d4 <= d3 {
return (b, Feature::Vertex(1));
}
let vc = d1 * d4 - d3 * d2;
if vc <= R::ZERO && d1 >= R::ZERO && d3 <= R::ZERO {
let denom = d1 - d3;
let v = if denom == R::ZERO {
R::ZERO
} else {
d1 / denom
};
return (add(a, scale(ab, v)), Feature::Edge(0));
}
let cp = sub(p, c);
let d5 = dot(ab, cp);
let d6 = dot(ac, cp);
if d6 >= R::ZERO && d5 <= d6 {
return (c, Feature::Vertex(2));
}
let vb = d5 * d2 - d1 * d6;
if vb <= R::ZERO && d2 >= R::ZERO && d6 <= R::ZERO {
let denom = d2 - d6;
let w = if denom == R::ZERO {
R::ZERO
} else {
d2 / denom
};
return (add(a, scale(ac, w)), Feature::Edge(2));
}
let va = d3 * d6 - d5 * d4;
if va <= R::ZERO && (d4 - d3) >= R::ZERO && (d5 - d6) >= R::ZERO {
let denom = (d4 - d3) + (d5 - d6);
let w = if denom == R::ZERO {
R::ZERO
} else {
(d4 - d3) / denom
};
return (add(b, scale(sub(c, b), w)), Feature::Edge(1));
}
let denom = va + vb + vc;
if denom == R::ZERO {
return (a, Feature::Vertex(0));
}
let inv = R::ONE / denom;
let v = vb * inv;
let w = vc * inv;
(add(add(a, scale(ab, v)), scale(ac, w)), Feature::Face)
}
fn corner_angle<R: Real>(x: [R; 3], y: [R; 3], z: [R; 3]) -> R {
let u = sub(y, x);
let v = sub(z, x);
let lu = norm(u);
let lv = norm(v);
if lu == R::ZERO || lv == R::ZERO {
return R::ZERO;
}
let mut c = dot(u, v) / (lu * lv);
if c > R::ONE {
c = R::ONE;
}
if c < -R::ONE {
c = -R::ONE;
}
c.acos()
}
#[derive(Debug)]
struct Pseudonormals<R: Real> {
face: Vec<[R; 3]>,
edge: BTreeMap<(u32, u32), [R; 3]>,
vertex: Vec<[R; 3]>,
}
impl<R: Real> Pseudonormals<R> {
fn build(positions: &[[R; 3]], indices: &[u32]) -> Self {
let mut face = Vec::with_capacity(indices.len() / 3);
let mut edge: BTreeMap<(u32, u32), [R; 3]> = BTreeMap::new();
let mut vertex = vec![[R::ZERO; 3]; positions.len()];
for tri in indices.chunks_exact(3) {
let (ia, ib, ic) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
let (a, b, c) = (positions[ia], positions[ib], positions[ic]);
let n = cross(sub(b, a), sub(c, a));
let len = norm(n);
let unit = if len == R::ZERO {
[R::ZERO; 3]
} else {
scale(n, R::ONE / len)
};
face.push(unit);
for (x, y, z) in [(ia, ib, ic), (ib, ic, ia), (ic, ia, ib)] {
let angle = corner_angle(positions[x], positions[y], positions[z]);
vertex[x] = add(vertex[x], scale(unit, angle));
}
for (u, v) in [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])] {
let key = if u < v { (u, v) } else { (v, u) };
let slot = edge.entry(key).or_insert([R::ZERO; 3]);
*slot = add(*slot, unit);
}
}
Self { face, edge, vertex }
}
fn at(&self, indices: &[u32], t: usize, feature: Feature) -> [R; 3] {
let tri = &indices[t * 3..t * 3 + 3];
match feature {
Feature::Face => self.face[t],
Feature::Vertex(i) => self.vertex[tri[i as usize] as usize],
Feature::Edge(i) => {
let u = tri[i as usize];
let v = tri[(i as usize + 1) % 3];
let key = if u < v { (u, v) } else { (v, u) };
self.edge.get(&key).copied().unwrap_or(self.face[t])
}
}
}
}
const BLOCK: usize = 64;
#[derive(Debug)]
struct Bounds<R: Real> {
tri: Vec<[[R; 3]; 2]>,
block: Vec<[[R; 3]; 2]>,
}
fn box_distance_sq<R: Real>(p: [R; 3], b: &[[R; 3]; 2]) -> R {
let mut total = R::ZERO;
for axis in 0..3 {
let d = if p[axis] < b[0][axis] {
b[0][axis] - p[axis]
} else if p[axis] > b[1][axis] {
p[axis] - b[1][axis]
} else {
R::ZERO
};
total += d * d;
}
total
}
impl<R: Real> Bounds<R> {
fn build(positions: &[[R; 3]], indices: &[u32]) -> Self {
let triangles = indices.len() / 3;
let mut tri = Vec::with_capacity(triangles);
for t in 0..triangles {
let v = &indices[t * 3..t * 3 + 3];
let mut lo = positions[v[0] as usize];
let mut hi = lo;
for &i in &v[1..] {
let p = positions[i as usize];
for axis in 0..3 {
if p[axis] < lo[axis] {
lo[axis] = p[axis];
}
if p[axis] > hi[axis] {
hi[axis] = p[axis];
}
}
}
tri.push([lo, hi]);
}
let mut block = Vec::with_capacity(tri.len().div_ceil(BLOCK));
for chunk in tri.chunks(BLOCK) {
let mut lo = chunk[0][0];
let mut hi = chunk[0][1];
for b in &chunk[1..] {
for axis in 0..3 {
if b[0][axis] < lo[axis] {
lo[axis] = b[0][axis];
}
if b[1][axis] > hi[axis] {
hi[axis] = b[1][axis];
}
}
}
block.push([lo, hi]);
}
Self { tri, block }
}
}
pub fn signed_distance_from_mesh<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 field = MeshField::new(positions, indices)?;
let count = shape.element_count();
let (nx, ny, nz) = (size[0], size[1], size[2]);
let mut out = Vec::with_capacity(count);
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
out.push(field.sample([
origin[0] + cell_size * R::from_f64(f64::from(x)),
origin[1] + cell_size * R::from_f64(f64::from(y)),
origin[2] + cell_size * R::from_f64(f64::from(z)),
]));
}
}
}
Ok(out)
}
#[derive(Debug)]
pub struct MeshField<'a, R: Real> {
positions: &'a [[R; 3]],
indices: &'a [u32],
normals: Pseudonormals<R>,
bounds: Bounds<R>,
}
impl<'a, R: Real> MeshField<'a, R> {
pub fn new(positions: &'a [[R; 3]], indices: &'a [u32]) -> crate::Result<Self> {
if let Some((at, &index)) = indices
.iter()
.enumerate()
.find(|&(_, &i)| i as usize >= positions.len())
{
return Err(crate::Error::IndexOutOfRange {
at: at as u64,
index,
vertices: positions.len() as u64,
});
}
let whole = indices.len() - indices.len() % 3;
let indices = &indices[..whole];
Ok(Self {
positions,
indices,
normals: Pseudonormals::build(positions, indices),
bounds: Bounds::build(positions, indices),
})
}
#[must_use]
pub const fn triangles(&self) -> usize {
self.indices.len() / 3
}
}
impl<R: Real> Sdf for MeshField<'_, R> {
type Scalar = R;
fn sample(&self, p: [R; 3]) -> R {
let triangles = self.triangles();
let mut best_sq = super::far::<R>();
let mut best_sign = R::ONE;
for (b, bbox) in self.bounds.block.iter().enumerate() {
if box_distance_sq(p, bbox) >= best_sq {
continue;
}
let lo = b * BLOCK;
let hi = (lo + BLOCK).min(triangles);
for t in lo..hi {
if box_distance_sq(p, &self.bounds.tri[t]) >= best_sq {
continue;
}
let tri = &self.indices[t * 3..t * 3 + 3];
let (c, feature) = closest_on_triangle(
p,
self.positions[tri[0] as usize],
self.positions[tri[1] as usize],
self.positions[tri[2] as usize],
);
let r = sub(p, c);
let d_sq = dot(r, r);
if d_sq < best_sq {
best_sq = d_sq;
let n = self.normals.at(self.indices, t, feature);
best_sign = if dot(n, r) < R::ZERO { -R::ONE } else { R::ONE };
}
}
}
best_sq.sqrt() * best_sign
}
}