#![allow(dead_code)]
use std::f32::consts::PI;
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
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 len3(v: [f32; 3]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
pub fn solid_angle_triangle(query: [f32; 3], v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) -> f32 {
let a = sub3(v0, query);
let b = sub3(v1, query);
let c = sub3(v2, query);
let la = len3(a);
let lb = len3(b);
let lc = len3(c);
if la < 1e-12 || lb < 1e-12 || lc < 1e-12 {
return 0.0;
}
let num = dot3(a, cross3(b, c));
let denom = la * lb * lc + dot3(a, b) * lc + dot3(b, c) * la + dot3(a, c) * lb;
2.0 * num.atan2(denom)
}
pub fn winding_number(query: [f32; 3], verts: &[[f32; 3]], tris: &[[u32; 3]]) -> f32 {
let mut total = 0.0f32;
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;
}
total += solid_angle_triangle(query, verts[i0], verts[i1], verts[i2]);
}
total / (4.0 * PI)
}
pub fn is_inside_winding(query: [f32; 3], verts: &[[f32; 3]], tris: &[[u32; 3]]) -> bool {
(winding_number(query, verts, tris).abs() - 1.0).abs() < 0.5
}
pub fn winding_number_batch(
queries: &[[f32; 3]],
verts: &[[f32; 3]],
tris: &[[u32; 3]],
) -> Vec<f32> {
queries
.iter()
.map(|&q| winding_number(q, verts, tris))
.collect()
}
pub fn count_inside_winding(queries: &[[f32; 3]], verts: &[[f32; 3]], tris: &[[u32; 3]]) -> usize {
queries
.iter()
.filter(|&&q| is_inside_winding(q, verts, tris))
.count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_solid_angle_zero_empty() {
let sa = solid_angle_triangle([0.0; 3], [0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!(sa.abs() < 0.1 );
}
#[test]
fn test_winding_number_empty_mesh() {
let w = winding_number([0.5; 3], &[], &[]);
assert_eq!(w, 0.0 );
}
#[test]
fn test_winding_batch_same_length() {
let queries = vec![[0.0f32; 3], [1.0, 1.0, 1.0]];
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 wns = winding_number_batch(&queries, &verts, &tris);
assert_eq!(wns.len(), 2 );
}
#[test]
fn test_winding_outside_near_zero() {
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 w = winding_number([100.0, 100.0, 100.0], &verts, &tris);
assert!(w.abs() < 0.1 );
}
#[test]
fn test_is_inside_outside_mesh() {
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]];
assert!(!is_inside_winding([100.0, 100.0, 100.0], &verts, &tris));
}
#[test]
fn test_count_inside_all_outside() {
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 queries = vec![[100.0f32; 3], [200.0; 3]];
assert_eq!(
count_inside_winding(&queries, &verts, &tris),
0
);
}
#[test]
fn test_solid_angle_finite() {
let sa = solid_angle_triangle(
[2.0, 2.0, 2.0],
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
);
assert!(sa.is_finite() );
}
#[test]
fn test_winding_batch_empty_queries() {
let wns = winding_number_batch(&[], &[], &[]);
assert!(wns.is_empty() );
}
}