use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use crate::mesh::MeshBuffer;
use crate::real::Real;
use crate::vec3;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OrientReport {
pub components: u64,
pub triangles_flipped: u64,
pub non_orientable_components: u64,
pub low_confidence_seeds: u64,
}
impl OrientReport {
#[must_use]
pub const fn is_orientable(&self) -> bool {
self.non_orientable_components == 0
}
#[must_use]
pub const fn is_noop(&self) -> bool {
self.triangles_flipped == 0
}
}
fn undirected(a: u32, b: u32) -> (u32, u32) {
if a <= b { (a, b) } else { (b, a) }
}
pub fn orient<R: Real>(mesh: &mut MeshBuffer<R>) -> crate::Result<OrientReport> {
let vertices = mesh.positions.len();
let faces = mesh.indices.len() / 3;
for (at, &index) in mesh.indices.iter().enumerate() {
if index as usize >= vertices {
return Err(crate::Error::IndexOutOfRange {
at: at as u64,
index,
vertices: vertices as u64,
});
}
}
if faces == 0 {
return Ok(OrientReport::default());
}
let mut incident: BTreeMap<(u32, u32), Vec<u32>> = BTreeMap::new();
for face in 0..faces {
let tri = &mesh.indices[face * 3..face * 3 + 3];
for k in 0..3 {
incident
.entry(undirected(tri[k], tri[(k + 1) % 3]))
.or_default()
.push(face as u32);
}
}
let confidence: Vec<R> = (0..faces).map(|face| confidence_of(mesh, face)).collect();
let mut visited = vec![false; faces];
let mut report = OrientReport::default();
let mut queue: Vec<u32> = Vec::new();
let mut order: Vec<usize> = (0..faces).collect();
order.sort_by(|&a, &b| {
confidence[b]
.total_cmp(&confidence[a])
.then_with(|| a.cmp(&b))
});
for seed in order {
if visited[seed] {
continue;
}
report.components += 1;
if confidence[seed] <= R::ZERO {
report.low_confidence_seeds += 1;
}
if faces_outward(mesh, seed) == Some(false) {
flip(mesh, seed);
report.triangles_flipped += 1;
}
visited[seed] = true;
queue.clear();
queue.push(seed as u32);
let mut disagreed = false;
while let Some(face) = queue.pop() {
let tri = face_indices(mesh, face as usize);
for k in 0..3 {
let (from, to) = (tri[k], tri[(k + 1) % 3]);
let Some(neighbours) = incident.get(&undirected(from, to)) else {
continue;
};
if neighbours.len() > 2 {
continue;
}
for &other in neighbours {
if other == face {
continue;
}
let agrees = !traverses(mesh, other, from, to);
if visited[other as usize] {
disagreed |= !agrees;
continue;
}
visited[other as usize] = true;
if !agrees {
flip(mesh, other as usize);
report.triangles_flipped += 1;
}
queue.push(other);
}
}
}
if disagreed {
report.non_orientable_components += 1;
}
}
Ok(report)
}
fn traverses<R: Real>(mesh: &MeshBuffer<R>, face: u32, from: u32, to: u32) -> bool {
let tri = face_indices(mesh, face as usize);
(0..3).any(|k| tri[k] == from && tri[(k + 1) % 3] == to)
}
fn faces_outward<R: Real>(mesh: &MeshBuffer<R>, face: usize) -> Option<bool> {
let tri = face_indices(mesh, face);
let geometric = geometric_normal(mesh, tri);
let field = field_direction(mesh, tri);
let vote = vec3::dot(geometric, field);
if vote > R::ZERO {
Some(true)
} else if vote < R::ZERO {
Some(false)
} else {
None
}
}
fn flip<R: Real>(mesh: &mut MeshBuffer<R>, face: usize) {
mesh.indices.swap(face * 3 + 1, face * 3 + 2);
}
fn field_direction<R: Real>(mesh: &MeshBuffer<R>, tri: [u32; 3]) -> [R; 3] {
let mut sum = [R::ZERO; 3];
for index in tri {
let Some(n) = mesh.normals.get(index as usize) else {
return [R::ZERO; 3];
};
for axis in 0..3 {
sum[axis] += n[axis];
}
}
sum
}
fn confidence_of<R: Real>(mesh: &MeshBuffer<R>, face: usize) -> R {
let tri = face_indices(mesh, face);
let geometric = geometric_normal(mesh, tri);
let length = vec3::length(geometric);
let field = field_direction(mesh, tri);
let field_length = vec3::length(field);
if !length.is_finite() || !field_length.is_finite() {
return R::ZERO;
}
if length <= R::ZERO || field_length <= R::ZERO {
return R::ZERO;
}
vec3::dot(
vec3::scale(geometric, length.recip()),
vec3::scale(field, field_length.recip()),
)
.abs()
}
fn face_indices<R: Real>(mesh: &MeshBuffer<R>, face: usize) -> [u32; 3] {
[0, 1, 2].map(|k| mesh.indices[face * 3 + k])
}
fn geometric_normal<R: Real>(mesh: &MeshBuffer<R>, tri: [u32; 3]) -> [R; 3] {
let p = tri.map(|i| mesh.positions[i as usize]);
vec3::cross(vec3::sub(p[1], p[0]), vec3::sub(p[2], p[0]))
}
#[cfg(test)]
mod tests;