use axiolid_core::{Point3, Vec3};
use axiolid_guarantees::Sign;
use axiolid_mesh::TriMesh;
use axiolid_predicates::orient3d;
pub fn winding_number(mesh: &TriMesh, point: Point3) -> Option<i32> {
winding_number_along(mesh, point, default_direction())
}
pub fn contains(mesh: &TriMesh, point: Point3) -> Option<bool> {
winding_number(mesh, point).map(|w| w != 0)
}
pub fn winding_number_along(mesh: &TriMesh, point: Point3, direction: Vec3) -> Option<i32> {
let mut winding = 0;
for triangle in mesh.indices.chunks_exact(3) {
let a = mesh.positions[triangle[0] as usize];
let b = mesh.positions[triangle[1] as usize];
let c = mesh.positions[triangle[2] as usize];
winding += crossing_sign([a, b, c], point, direction)?;
}
Some(winding)
}
fn crossing_sign(triangle: [Point3; 3], origin: Point3, direction: Vec3) -> Option<i32> {
let [a, b, c] = triangle;
let reach = (a - origin)
.length()
.max((b - origin).length())
.max((c - origin).length());
let far = origin + direction * (reach / direction.length() + 1.0);
let near_side = sign_of(orient3d(a, b, c, origin).sign()?)?;
let far_side = sign_of(orient3d(a, b, c, far).sign()?)?;
if near_side == 0 {
return None;
}
if near_side == far_side {
return Some(0);
}
let e0 = sign_of(orient3d(origin, far, a, b).sign()?)?;
let e1 = sign_of(orient3d(origin, far, b, c).sign()?)?;
let e2 = sign_of(orient3d(origin, far, c, a).sign()?)?;
if e0 == 0 || e1 == 0 || e2 == 0 {
return None;
}
if e0 != e1 || e1 != e2 {
return Some(0);
}
Some(if near_side > 0 { 1 } else { -1 })
}
fn sign_of(sign: Sign) -> Option<i32> {
match sign {
Sign::Positive => Some(1),
Sign::Negative => Some(-1),
Sign::Zero => Some(0),
_ => None,
}
}
fn default_direction() -> Vec3 {
Vec3::new(0.573_215_664_9, 0.311_029_995_7, 0.144_729_885_8)
}
pub(crate) fn ray_hits_triangle(
triangle: [Point3; 3],
origin: Point3,
direction: Vec3,
) -> Option<bool> {
match crossing_sign(triangle, origin, direction) {
Some(sign) => Some(sign != 0),
None => Some(false),
}
}