use std::collections::{HashSet, VecDeque};
use crate::ids::{FaceId, HalfEdgeId};
use crate::storage::MeshStorage;
use crate::traversal::FaceHalfEdges;
pub fn are_normals_consistent(mesh: &MeshStorage) -> bool {
for he in mesh.halfedge_ids() {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => continue,
};
let twin = match h.twin {
Some(t) => t,
None => continue,
};
let twin_data = match mesh.get_halfedge(twin) {
Some(t) => t,
None => continue,
};
if h.vertex == twin_data.vertex {
return false;
}
}
true
}
pub fn is_orientable(mesh: &MeshStorage) -> bool {
let mut visited: HashSet<FaceId> = HashSet::new();
for seed in mesh.face_ids() {
if visited.contains(&seed) {
continue;
}
let mut queue = VecDeque::new();
visited.insert(seed);
queue.push_back((seed, true)); while let Some((f, _orientation)) = queue.pop_front() {
for he in FaceHalfEdges::new(mesh, f) {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => continue,
};
let twin = match h.twin {
Some(t) => t,
None => continue,
};
let adj_f = match mesh.get_halfedge(twin).and_then(|t| t.face) {
Some(f) => f,
None => continue,
};
if visited.contains(&adj_f) {
continue;
}
let twin_data = mesh.get_halfedge(twin);
if let Some(td) = twin_data
&& h.vertex == td.vertex
{
return false; }
visited.insert(adj_f);
queue.push_back((adj_f, true));
}
}
}
true
}
fn flip_face_orientation(mesh: &mut MeshStorage, face: FaceId) {
let he_ids: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, face).collect();
if he_ids.is_empty() {
return;
}
let he_data: Vec<_> = he_ids
.iter()
.map(|&id| mesh.get_halfedge(id).unwrap().clone())
.collect();
for &id in &he_ids {
let h = mesh.get_halfedge_mut(id).unwrap();
let old_next = h.next;
let old_prev = h.prev;
h.next = old_prev;
h.prev = old_next;
}
if he_ids.len() == 3 {
let v0 = he_data[0].vertex;
let v1 = he_data[1].vertex;
let v2 = he_data[2].vertex;
mesh.get_halfedge_mut(he_ids[0]).unwrap().vertex = v2;
mesh.get_halfedge_mut(he_ids[1]).unwrap().vertex = v0;
mesh.get_halfedge_mut(he_ids[2]).unwrap().vertex = v1;
for &id in &he_ids {
let h = mesh.get_halfedge_mut(id).unwrap();
let old_next = h.next;
let old_prev = h.prev;
h.next = old_prev;
h.prev = old_next;
}
}
}
pub fn fix_orientations(mesh: &mut MeshStorage) -> usize {
let mut visited: HashSet<FaceId> = HashSet::new();
let mut flipped = 0usize;
for seed in mesh.face_ids().collect::<Vec<_>>() {
if visited.contains(&seed) {
continue;
}
let mut queue = VecDeque::new();
visited.insert(seed);
queue.push_back(seed);
while let Some(f) = queue.pop_front() {
for he in FaceHalfEdges::new(mesh, f).collect::<Vec<_>>() {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => continue,
};
let twin = match h.twin {
Some(t) => t,
None => continue,
};
let adj_f = match mesh.get_halfedge(twin).and_then(|t| t.face) {
Some(f) => f,
None => continue,
};
if visited.contains(&adj_f) {
continue;
}
let twin_data = mesh.get_halfedge(twin);
let need_flip = twin_data.is_some_and(|td| h.vertex == td.vertex);
if need_flip {
flip_face_orientation(mesh, adj_f);
flipped += 1;
}
visited.insert(adj_f);
queue.push_back(adj_f);
}
}
}
flipped
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn icosphere_has_consistent_normals() {
let mesh = crate::test_util::build_icosphere(1);
assert!(are_normals_consistent(&mesh));
assert!(is_orientable(&mesh));
}
#[test]
fn fix_orientations_noop_on_icosphere() {
let mut mesh = crate::test_util::build_icosphere(1);
let flipped = fix_orientations(&mut mesh);
assert_eq!(flipped, 0);
}
}