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();
let n = he_ids.len();
if n == 0 {
return;
}
let old_v: Vec<_> = he_ids
.iter()
.map(|&id| {
mesh.get_halfedge(id)
.expect("halfedge exists in mesh")
.vertex
})
.collect();
for i in 0..n {
let h = mesh
.get_halfedge_mut(he_ids[i])
.expect("halfedge exists in mesh");
h.vertex = old_v[(i + n - 1) % n];
h.next = Some(he_ids[(i + n - 1) % n]);
h.prev = Some(he_ids[(i + 1) % n]);
}
}
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::*;
use crate::io::build_mesh_from_polygons;
use crate::storage::Vertex;
use crate::topology_ops::add_triangle;
use crate::traversal::FaceHalfEdges;
#[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);
}
#[test]
fn empty_mesh_is_consistent() {
let mesh = crate::storage::MeshStorage::new();
assert!(are_normals_consistent(&mesh));
assert!(is_orientable(&mesh));
let mut mesh2 = crate::storage::MeshStorage::new();
assert_eq!(fix_orientations(&mut mesh2), 0);
}
#[test]
fn single_triangle_is_consistent() {
let mut mesh = crate::storage::MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let v2 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
add_triangle(&mut mesh, v0, v1, v2).unwrap();
assert!(are_normals_consistent(&mesh));
assert!(is_orientable(&mesh));
}
fn face_vertex_ring(mesh: &MeshStorage, face: FaceId) -> Vec<crate::ids::VertexId> {
FaceHalfEdges::new(mesh, face)
.filter_map(|he| mesh.get_halfedge(he))
.map(|h| h.vertex)
.collect()
}
#[test]
fn flip_triangle_reverses_winding() {
let mut mesh = crate::storage::MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let v2 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
let face = add_triangle(&mut mesh, v0, v1, v2).unwrap();
let original = face_vertex_ring(&mesh, face);
assert_eq!(original, vec![v1, v2, v0]);
flip_face_orientation(&mut mesh, face);
let flipped = face_vertex_ring(&mesh, face);
assert_eq!(flipped, vec![v0, v2, v1]);
let he_ids: Vec<_> = FaceHalfEdges::new(&mesh, face).collect();
for (i, &he) in he_ids.iter().enumerate() {
let h = mesh.get_halfedge(he).unwrap();
let next_id = he_ids[(i + 1) % he_ids.len()];
let prev_id = he_ids[(i + he_ids.len() - 1) % he_ids.len()];
assert_eq!(h.next, Some(next_id), "he[{i}].next broken");
assert_eq!(h.prev, Some(prev_id), "he[{i}].prev broken");
}
flip_face_orientation(&mut mesh, face);
let restored = face_vertex_ring(&mesh, face);
assert_eq!(restored, original);
}
#[test]
fn flip_quad_reverses_winding() {
let verts = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[2.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let faces: Vec<Vec<u32>> = vec![vec![0, 1, 4, 5], vec![1, 2, 3, 4]];
let mut mesh = build_mesh_from_polygons(&verts, &faces).unwrap();
let f_ids: Vec<_> = mesh.face_ids().collect();
let face0 = f_ids[0];
let original = face_vertex_ring(&mesh, face0);
assert_eq!(original.len(), 4, "quad face should have 4 halfedges");
flip_face_orientation(&mut mesh, face0);
let flipped = face_vertex_ring(&mesh, face0);
for i in 0..4 {
assert_eq!(
flipped[i],
original[(4 - 1 - i) % 4],
"flipped[{i}] should equal original[{}]",
(4 - 1 - i) % 4
);
}
let he_ids: Vec<_> = FaceHalfEdges::new(&mesh, face0).collect();
for (i, &he) in he_ids.iter().enumerate() {
let h = mesh.get_halfedge(he).unwrap();
assert_eq!(
h.next,
Some(he_ids[(i + 1) % 4]),
"he[{i}].next broken after quad flip"
);
assert_eq!(
h.prev,
Some(he_ids[(i + 3) % 4]),
"he[{i}].prev broken after quad flip"
);
}
flip_face_orientation(&mut mesh, face0);
let restored = face_vertex_ring(&mesh, face0);
assert_eq!(restored, original);
}
#[test]
fn fix_orientations_corrects_flipped_face() {
let verts = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let faces = vec![[0u32, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]];
let mut mesh = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
assert!(are_normals_consistent(&mesh));
let first_face = mesh.face_ids().next().unwrap();
flip_face_orientation(&mut mesh, first_face);
assert!(
!are_normals_consistent(&mesh),
"mesh should be inconsistent after flipping one face"
);
let flipped = fix_orientations(&mut mesh);
assert!(flipped > 0, "should flip at least one face");
assert!(
are_normals_consistent(&mesh),
"mesh should be consistent after fix_orientations"
);
}
#[test]
fn disconnected_components_each_fixed() {
let mut mesh = crate::storage::MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let v2 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
let v3 = mesh.add_vertex(Vertex::new([2.0, 0.0, 0.0]));
let v4 = mesh.add_vertex(Vertex::new([3.0, 0.0, 0.0]));
let v5 = mesh.add_vertex(Vertex::new([2.0, 1.0, 0.0]));
let f1 = add_triangle(&mut mesh, v0, v1, v2).unwrap();
let _f2 = add_triangle(&mut mesh, v3, v4, v5).unwrap();
assert!(are_normals_consistent(&mesh));
assert_eq!(fix_orientations(&mut mesh), 0);
flip_face_orientation(&mut mesh, f1);
}
}