use alloc::vec::Vec;
use crate::{
Face, Vertex,
core::{AABB, EdgeMap, Float, MeshError, MeshValidationReport},
};
#[cfg(feature = "rayon")]
use rayon::prelude::*;
pub fn check_intersecting<T: Float>(vertices: &[Vertex<T>], faces: &[Face]) -> bool {
let compute_aabb_and_normal = |f: &Face| {
let v0 = &vertices[f.0];
let v1 = &vertices[f.1];
let v2 = &vertices[f.2];
let aabb = AABB::from_triangle(v0, v1, v2);
let n = (v2.sub(v0)).cross(&v1.sub(v0));
let n_sq = n.0 * n.0 + n.1 * n.1 + n.2 * n.2;
let normal = if n_sq < T::from(1e-12).unwrap() {
None
} else {
let inv_n_sq = T::one() / n_sq;
Some(Vertex(n.0 * inv_n_sq, n.1 * inv_n_sq, n.2 * inv_n_sq))
};
(aabb, normal)
};
#[cfg(feature = "rayon")]
let (aabbs, normals): (Vec<_>, Vec<_>) = faces.par_iter().map(compute_aabb_and_normal).unzip();
#[cfg(not(feature = "rayon"))]
let (aabbs, normals): (Vec<_>, Vec<_>) = faces.iter().map(compute_aabb_and_normal).unzip();
check_intersecting_internal(vertices, faces, &aabbs, &normals)
}
#[inline]
fn check_intersecting_internal<T: Float>(
vertices: &[Vertex<T>],
faces: &[Face],
aabbs: &[AABB<T>],
normals: &[Option<Vertex<T>>],
) -> bool {
let num_faces = faces.len();
let check_face_pair = |i: usize| {
let aabb1 = &aabbs[i];
for j in (i + 1)..num_faces {
let aabb2 = &aabbs[j];
if !aabb1.intersects(aabb2) {
continue;
}
let f1 = &faces[i];
let f2 = &faces[j];
if f1.0 == f2.0
|| f1.0 == f2.1
|| f1.0 == f2.2
|| f1.1 == f2.0
|| f1.1 == f2.1
|| f1.1 == f2.2
|| f1.2 == f2.0
|| f1.2 == f2.1
|| f1.2 == f2.2
{
continue;
}
let t1 = [&vertices[f1.0], &vertices[f1.1], &vertices[f1.2]];
let t2 = [&vertices[f2.0], &vertices[f2.1], &vertices[f2.2]];
let n1 = &normals[i];
let n2 = &normals[j];
if let Some(n2) = n2
&& triangle_intersects_facet(t1, t2, n2)
{
return true;
}
if let Some(n1) = n1
&& triangle_intersects_facet(t2, t1, n1)
{
return true;
}
}
false
};
#[cfg(feature = "rayon")]
{
(0..num_faces).into_par_iter().any(check_face_pair)
}
#[cfg(not(feature = "rayon"))]
{
(0..num_faces).any(check_face_pair)
}
}
#[inline]
fn triangle_intersects_facet<T: Float>(
t1: [&Vertex<T>; 3],
t2: [&Vertex<T>; 3],
n2: &Vertex<T>,
) -> bool {
let eps = T::from(1e-6).unwrap();
let d0 = n2.dot(&t1[0].sub(t2[2]));
let d1 = n2.dot(&t1[1].sub(t2[2]));
let d2 = n2.dot(&t1[2].sub(t2[2]));
if (d0 > eps && d1 > eps && d2 > eps) || (d0 < -eps && d1 < -eps && d2 < -eps) {
return false;
}
check_segment_against_facet(t1[0], t1[1], d0, d1, t2, eps)
|| check_segment_against_facet(t1[1], t1[2], d1, d2, t2, eps)
|| check_segment_against_facet(t1[2], t1[0], d2, d0, t2, eps)
}
#[inline]
fn check_segment_against_facet<T: Float>(
s0: &Vertex<T>,
s1: &Vertex<T>,
d0: T,
d1: T,
t2: [&Vertex<T>; 3],
eps: T,
) -> bool {
if d0 * d1 > T::zero() {
return false;
}
if d0.abs() <= eps || d1.abs() <= eps {
return false;
}
let s0_sub_s1 = s0.sub(s1);
let t2_sub_s1 = [t2[0].sub(s1), t2[1].sub(s1), t2[2].sub(s1)];
let mut v = [T::zero(); 3];
for (i, j) in [(0, 1), (1, 2), (2, 0)] {
let sv = t2_sub_s1[i].dot(&t2_sub_s1[j].cross(&s0_sub_s1));
if sv.abs() < eps {
return false;
}
v[i] = sv;
}
(v[0] > T::zero() && v[1] > T::zero() && v[2] > T::zero())
|| (v[0] < T::zero() && v[1] < T::zero() && v[2] < T::zero())
}
pub fn check_zero_area_faces<T: Float>(vertices: &[Vertex<T>], faces: &[Face], atol: T) -> bool {
let atol_sq = atol * atol;
let check_face = |face: &Face| {
let v0 = &vertices[face.0];
let v1 = &vertices[face.1];
let v2 = &vertices[face.2];
let cross = v1.sub(v0).cross(&v2.sub(v0));
let area_sq = cross.0 * cross.0 + cross.1 * cross.1 + cross.2 * cross.2;
area_sq < atol_sq
};
#[cfg(feature = "rayon")]
{
faces.par_iter().any(check_face)
}
#[cfg(not(feature = "rayon"))]
{
faces.iter().any(check_face)
}
}
pub fn check_manifold(map: &EdgeMap) -> Result<(), MeshError> {
let check_counts = |(&(_v1, _v2), &count): (&(usize, usize), &u8)| {
if count > 2 {
return Some(Err(MeshError::NonManifold));
}
if count == 1 {
return Some(Err(MeshError::OpenEdges));
}
None
};
#[cfg(feature = "rayon")]
let result = map.counts.par_iter().find_map_any(check_counts);
#[cfg(not(feature = "rayon"))]
let result = map.counts.iter().find_map(check_counts);
result.unwrap_or(Ok(()))
}
#[inline]
pub fn check_consistent_normals(map: &EdgeMap) -> bool {
let check_dir = |((_v1, _v2), &count): (&(usize, usize), &u8)| count <= 1;
#[cfg(feature = "rayon")]
{
map.directions.par_iter().all(check_dir)
}
#[cfg(not(feature = "rayon"))]
{
map.directions.iter().all(check_dir)
}
}
pub fn check_inward_orientation<T: Float>(vertices: &[Vertex<T>], faces: &[Face]) -> bool {
let calc_vol = |face: &Face| {
let v0 = &vertices[face.0];
let v1 = &vertices[face.1];
let v2 = &vertices[face.2];
let cross = v1.sub(v0).cross(&v2.sub(v0));
v0.dot(&cross)
};
#[cfg(feature = "rayon")]
let total_vol: T = faces.par_iter().map(calc_vol).sum();
#[cfg(not(feature = "rayon"))]
let total_vol: T = faces.iter().map(calc_vol).sum();
total_vol < T::zero()
}
pub fn check_mesh<T: Float>(
vertices: &[Vertex<T>],
faces: &[Face],
atol: T,
) -> MeshValidationReport {
let mut report = MeshValidationReport::default();
let atol_sq = atol * atol;
let compute_all = |f: &Face| {
let v0 = &vertices[f.0];
let v1 = &vertices[f.1];
let v2 = &vertices[f.2];
let cross = v1.sub(v0).cross(&v2.sub(v0));
let norm_sq = cross.0 * cross.0 + cross.1 * cross.1 + cross.2 * cross.2;
let aabb = AABB::from_triangle(v0, v1, v2);
let normal = if norm_sq < atol_sq {
None
} else {
let inv_norm_sq = T::one() / norm_sq;
Some(Vertex(
cross.0 * inv_norm_sq,
cross.1 * inv_norm_sq,
cross.2 * inv_norm_sq,
))
};
let vol = v0.dot(&cross);
let is_zero = norm_sq < atol_sq;
(aabb, normal, vol, is_zero)
};
#[cfg(feature = "rayon")]
let (aabbs, (normals, (volumes, zero_area_flags))): (Vec<_>, (Vec<_>, (Vec<_>, Vec<_>))) =
faces
.par_iter()
.map(compute_all)
.map(|(a, n, v, z)| (a, (n, (v, z))))
.unzip();
#[cfg(not(feature = "rayon"))]
let (aabbs, (normals, (volumes, zero_area_flags))): (Vec<_>, (Vec<_>, (Vec<_>, Vec<_>))) =
faces
.iter()
.map(compute_all)
.map(|(a, n, v, z)| (a, (n, (v, z))))
.unzip();
let total_vol: T = volumes.into_iter().sum();
report.inward_normals = total_vol < T::zero();
report.zero_area_faces = zero_area_flags.into_iter().filter(|&b| b).count();
let edge_map = EdgeMap::from_faces(faces);
for &count in edge_map.counts.values() {
if count > 2 {
report.non_manifold += 1;
} else if count == 1 {
report.open_edges += 1;
}
}
report.inconsistent_normals = edge_map.directions.values().any(|&count| count > 1);
report.self_intersecting = check_intersecting_internal(vertices, faces, &aabbs, &normals);
report
}
pub fn validate_mesh<T: Float>(
vertices: &[Vertex<T>],
faces: &[Face],
atol: T,
) -> Result<(), MeshError> {
if check_zero_area_faces(vertices, faces, atol) {
return Err(MeshError::ZeroAreaFace);
}
let edge_map = EdgeMap::from_faces(faces);
check_manifold(&edge_map)?;
if !check_consistent_normals(&edge_map) {
return Err(MeshError::InconsistentNormals);
}
if check_inward_orientation(vertices, faces) {
return Err(MeshError::InwardNormals);
}
if check_intersecting(vertices, faces) {
return Err(MeshError::SelfIntersecting);
}
Ok(())
}