#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct NonManifoldFixResult {
pub non_manifold_edges_found: usize,
pub faces_removed: usize,
pub edges_total: usize,
}
pub fn edge_key(a: u32, b: u32) -> (u32, u32) {
if a < b {
(a, b)
} else {
(b, a)
}
}
pub fn edge_face_counts(indices: &[u32]) -> HashMap<(u32, u32), Vec<usize>> {
let n = indices.len() / 3;
let mut map: HashMap<(u32, u32), Vec<usize>> = HashMap::new();
for i in 0..n {
let ia = indices[i * 3];
let ib = indices[i * 3 + 1];
let ic = indices[i * 3 + 2];
for (a, b) in [(ia, ib), (ib, ic), (ic, ia)] {
map.entry(edge_key(a, b)).or_default().push(i);
}
}
map
}
pub fn find_non_manifold_edges_nm(indices: &[u32]) -> Vec<(u32, u32)> {
edge_face_counts(indices)
.into_iter()
.filter(|(_, faces)| faces.len() > 2)
.map(|(k, _)| k)
.collect()
}
pub fn non_manifold_face_indices(indices: &[u32]) -> Vec<usize> {
let map = edge_face_counts(indices);
let mut bad: std::collections::HashSet<usize> = std::collections::HashSet::new();
for faces in map.values() {
if faces.len() > 2 {
for &fi in faces {
bad.insert(fi);
}
}
}
let mut v: Vec<usize> = bad.into_iter().collect();
v.sort_unstable();
v
}
pub fn fix_non_manifold(indices: &[u32]) -> (Vec<u32>, NonManifoldFixResult) {
let n = indices.len() / 3;
let bad: std::collections::HashSet<usize> =
non_manifold_face_indices(indices).into_iter().collect();
let mut out = Vec::with_capacity(indices.len());
for i in 0..n {
if !bad.contains(&i) {
out.push(indices[i * 3]);
out.push(indices[i * 3 + 1]);
out.push(indices[i * 3 + 2]);
}
}
let nm_edges = find_non_manifold_edges_nm(indices).len();
let result = NonManifoldFixResult {
non_manifold_edges_found: nm_edges,
faces_removed: bad.len(),
edges_total: n * 3,
};
(out, result)
}
pub fn is_manifold_mesh_nm(indices: &[u32]) -> bool {
find_non_manifold_edges_nm(indices).is_empty()
}
pub fn manifold_edge_stats(indices: &[u32]) -> (usize, usize) {
let map = edge_face_counts(indices);
let nm = map.values().filter(|v| v.len() > 2).count();
(map.len().saturating_sub(nm), nm)
}
#[cfg(test)]
mod tests {
use super::*;
fn two_manifold_tris() -> Vec<u32> {
vec![0u32, 1, 2, 3, 2, 1]
}
fn nm_mesh() -> Vec<u32> {
vec![0u32, 1, 2, 3, 2, 1, 4, 1, 2]
}
#[test]
fn manifold_mesh_has_no_nm_edges() {
let idx = two_manifold_tris();
assert!(is_manifold_mesh_nm(&idx));
}
#[test]
fn non_manifold_mesh_detected() {
let idx = nm_mesh();
assert!(!is_manifold_mesh_nm(&idx));
}
#[test]
fn find_nm_edges_counts_correct() {
let idx = nm_mesh();
let nme = find_non_manifold_edges_nm(&idx);
assert_eq!(nme.len(), 1);
}
#[test]
fn fix_removes_correct_faces() {
let idx = nm_mesh();
let (out, stats) = fix_non_manifold(&idx);
assert!(stats.faces_removed > 0);
assert!(is_manifold_mesh_nm(&out));
}
#[test]
fn edge_key_canonical() {
assert_eq!(edge_key(3, 1), edge_key(1, 3));
}
#[test]
fn manifold_stats_nm_zero() {
let idx = two_manifold_tris();
let (_, nm) = manifold_edge_stats(&idx);
assert_eq!(nm, 0);
}
#[test]
fn manifold_stats_nm_one() {
let idx = nm_mesh();
let (_, nm) = manifold_edge_stats(&idx);
assert_eq!(nm, 1);
}
#[test]
fn empty_mesh_is_manifold() {
let idx: Vec<u32> = vec![];
assert!(is_manifold_mesh_nm(&idx));
}
#[test]
fn face_indices_sorted() {
let idx = nm_mesh();
let faces = non_manifold_face_indices(&idx);
for w in faces.windows(2) {
assert!(w[0] < w[1]);
}
}
}