#![allow(dead_code)]
fn dist3(a: [f32; 3], b: [f32; 3]) -> f32 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 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],
]
}
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn len3(v: [f32; 3]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
pub fn triangle_normal(v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) -> [f32; 3] {
let e1 = sub3(v1, v0);
let e2 = sub3(v2, v0);
let n = cross3(e1, e2);
let l = len3(n);
if l < 1e-12 {
return [0.0, 1.0, 0.0];
}
[n[0] / l, n[1] / l, n[2] / l]
}
pub fn signed_distance_to_triangle(
query: [f32; 3],
v0: [f32; 3],
v1: [f32; 3],
v2: [f32; 3],
) -> f32 {
let centroid = [
(v0[0] + v1[0] + v2[0]) / 3.0,
(v0[1] + v1[1] + v2[1]) / 3.0,
(v0[2] + v1[2] + v2[2]) / 3.0,
];
let normal = triangle_normal(v0, v1, v2);
let d_vec = sub3(query, centroid);
let sign = if dot3(d_vec, normal) >= 0.0 {
1.0f32
} else {
-1.0
};
sign * dist3(query, centroid)
}
pub fn signed_distance_to_mesh(
query: [f32; 3],
verts: &[[f32; 3]],
tris: &[[u32; 3]],
) -> Option<f32> {
if tris.is_empty() || verts.is_empty() {
return None;
}
let mut best = f32::MAX;
for tri in tris {
let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
if i0 >= verts.len() || i1 >= verts.len() || i2 >= verts.len() {
continue;
}
let sd = signed_distance_to_triangle(query, verts[i0], verts[i1], verts[i2]);
if sd.abs() < best.abs() {
best = sd;
}
}
Some(best)
}
pub fn sample_sdf_grid(
verts: &[[f32; 3]],
tris: &[[u32; 3]],
min: [f32; 3],
max: [f32; 3],
resolution: usize,
) -> Vec<f32> {
let mut values = Vec::with_capacity(resolution * resolution * resolution);
for iz in 0..resolution {
for iy in 0..resolution {
for ix in 0..resolution {
let t = |i: usize, lo: f32, hi: f32| {
lo + (i as f32 / (resolution - 1).max(1) as f32) * (hi - lo)
};
let query = [
t(ix, min[0], max[0]),
t(iy, min[1], max[1]),
t(iz, min[2], max[2]),
];
let sd = signed_distance_to_mesh(query, verts, tris).unwrap_or(f32::MAX);
values.push(sd);
}
}
}
values
}
pub fn is_inside_mesh(query: [f32; 3], verts: &[[f32; 3]], tris: &[[u32; 3]]) -> bool {
signed_distance_to_mesh(query, verts, tris).is_some_and(|sd| sd < 0.0)
}
pub fn sdf_gradient(query: [f32; 3], verts: &[[f32; 3]], tris: &[[u32; 3]], eps: f32) -> [f32; 3] {
let sdf = |q: [f32; 3]| signed_distance_to_mesh(q, verts, tris).unwrap_or(0.0);
let dx = (sdf([query[0] + eps, query[1], query[2]])
- sdf([query[0] - eps, query[1], query[2]]))
/ (2.0 * eps);
let dy = (sdf([query[0], query[1] + eps, query[2]])
- sdf([query[0], query[1] - eps, query[2]]))
/ (2.0 * eps);
let dz = (sdf([query[0], query[1], query[2] + eps])
- sdf([query[0], query[1], query[2] - eps]))
/ (2.0 * eps);
[dx, dy, dz]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triangle_normal_unit() {
let n = triangle_normal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((len3(n) - 1.0).abs() < 1e-5 );
}
#[test]
fn test_signed_distance_above_positive() {
let sd = signed_distance_to_triangle(
[0.33, 0.33, 1.0],
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
);
assert!(sd > 0.0 );
}
#[test]
fn test_signed_distance_below_negative() {
let sd = signed_distance_to_triangle(
[0.33, 0.33, -1.0],
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
);
assert!(sd < 0.0 );
}
#[test]
fn test_signed_distance_to_mesh_some() {
let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tris = vec![[0u32, 1, 2]];
let sd = signed_distance_to_mesh([0.33, 0.33, 0.5], &verts, &tris);
assert!(sd.is_some() );
}
#[test]
fn test_signed_distance_to_mesh_empty() {
let sd = signed_distance_to_mesh([0.0; 3], &[], &[]);
assert!(sd.is_none() );
}
#[test]
fn test_sample_sdf_grid_size() {
let verts = vec![[0.0f32; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tris = vec![[0u32, 1, 2]];
let grid = sample_sdf_grid(&verts, &tris, [0.0; 3], [1.0; 3], 2);
assert_eq!(grid.len(), 8 );
}
#[test]
fn test_is_inside_returns_false_for_above() {
let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tris = vec![[0u32, 1, 2]];
let inside = is_inside_mesh([0.33, 0.33, 5.0], &verts, &tris);
assert!(!inside );
}
#[test]
fn test_sdf_gradient_nonzero() {
let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tris = vec![[0u32, 1, 2]];
let grad = sdf_gradient([0.33, 0.33, 1.0], &verts, &tris, 0.01);
let mag = (grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]).sqrt();
assert!(mag > 0.0 );
}
}