use std::collections::BTreeMap;
use crate::disjoint_sets::DisjointSets;
use crate::impl_mesh::ManifoldImpl;
use crate::linalg::Vec3;
use crate::types::Halfedge;
use super::exact::predicates::tri_normal_r;
use super::exact::rational::R3;
use super::ray_shoot::{piece_centroid, winding_off_surface};
pub struct RepairPlan {
pub flip: Vec<bool>,
pub num_shells: usize,
pub flipped_shells: usize,
}
impl RepairPlan {
pub fn is_noop(&self) -> bool {
self.flipped_shells == 0
}
}
const MAX_CANDIDATE_FACES: usize = 16;
fn pos_key(v: Vec3) -> (u64, u64, u64) {
let norm = |x: f64| if x == 0.0 { 0.0f64 } else { x }.to_bits();
(norm(v.x), norm(v.y), norm(v.z))
}
fn connected_shells(tris: &[[Vec3; 3]]) -> (Vec<usize>, usize) {
let mut weld: BTreeMap<(u64, u64, u64), u32> = BTreeMap::new();
let mut vert_id = |v: Vec3, next: &mut u32| -> u32 {
*weld.entry(pos_key(v)).or_insert_with(|| {
let id = *next;
*next += 1;
id
})
};
let mut next = 0u32;
let tri_verts: Vec<[u32; 3]> = tris
.iter()
.map(|t| [
vert_id(t[0], &mut next),
vert_id(t[1], &mut next),
vert_id(t[2], &mut next),
])
.collect();
let ds = DisjointSets::new(tris.len().max(1) as u32);
let mut by_edge: Vec<((u32, u32), u32)> = Vec::with_capacity(3 * tris.len());
for (t, tv) in tri_verts.iter().enumerate() {
for e in 0..3 {
let (a, b) = (tv[e], tv[(e + 1) % 3]);
if a != b {
by_edge.push(((a.min(b), a.max(b)), t as u32));
}
}
}
by_edge.sort_unstable();
for w in by_edge.windows(2) {
if w[0].0 == w[1].0 {
ds.unite(w[0].1, w[1].1);
}
}
let mut remap: BTreeMap<u32, usize> = BTreeMap::new();
let mut shell = Vec::with_capacity(tris.len());
for t in 0..tris.len() {
let root = ds.find(t as u32);
let next_id = remap.len();
shell.push(*remap.entry(root).or_insert(next_id));
}
let count = remap.len();
(shell, count)
}
struct ShellGeom {
tris_f64: Vec<[Vec3; 3]>,
tris_r: Vec<[R3; 3]>,
boxes: Vec<crate::types::Box>,
}
impl ShellGeom {
fn new(tris: &[[Vec3; 3]], members: &[usize]) -> Self {
let tris_f64: Vec<[Vec3; 3]> = members.iter().map(|&t| tris[t]).collect();
let tris_r = tris_f64
.iter()
.map(|t| [R3::from_vec3(t[0]), R3::from_vec3(t[1]), R3::from_vec3(t[2])])
.collect();
let boxes = tris_f64
.iter()
.map(|t| {
let mut b = crate::types::Box::from_points(t[0], t[1]);
b.union_point(t[2]);
b
})
.collect();
ShellGeom {
tris_f64,
tris_r,
boxes,
}
}
}
struct Classified {
sign: i32,
probe: R3,
exterior: R3,
}
fn classify_shell(geom: &ShellGeom) -> Option<Classified> {
for t in geom.tris_r.iter().take(MAX_CANDIDATE_FACES) {
let n = tri_normal_r(&t[0], &t[1], &t[2]);
if n.is_zero() {
continue; }
let probe = piece_centroid([&t[0], &t[1], &t[2]]);
let w_out = winding_off_surface(&probe, &n, &geom.tris_r, &geom.tris_f64, &geom.boxes);
let neg = R3::new(-&n.x, -&n.y, -&n.z);
let w_in = winding_off_surface(&probe, &neg, &geom.tris_r, &geom.tris_f64, &geom.boxes);
match (w_out, w_in) {
(0, 1) => {
return Some(Classified {
sign: 1,
probe,
exterior: n,
})
}
(-1, 0) => {
return Some(Classified {
sign: -1,
probe,
exterior: neg,
})
}
_ => continue,
}
}
None
}
struct Analysis {
shell_of: Vec<usize>,
num_shells: usize,
classified: Vec<Option<Classified>>,
containers: Vec<Vec<usize>>,
}
fn analyze(tris: &[[Vec3; 3]]) -> Analysis {
let (shell_of, num_shells) = connected_shells(tris);
let mut members: Vec<Vec<usize>> = vec![Vec::new(); num_shells];
for (t, &s) in shell_of.iter().enumerate() {
members[s].push(t);
}
let geoms: Vec<ShellGeom> = members.iter().map(|m| ShellGeom::new(tris, m)).collect();
let classified: Vec<Option<Classified>> = geoms.iter().map(classify_shell).collect();
let mut containers: Vec<Vec<usize>> = vec![Vec::new(); num_shells];
for s in 0..num_shells {
let Some(c) = &classified[s] else { continue };
containers[s] = (0..num_shells)
.filter(|&o| o != s)
.filter(|&o| {
let g = &geoms[o];
winding_off_surface(&c.probe, &c.exterior, &g.tris_r, &g.tris_f64, &g.boxes) != 0
})
.collect();
}
Analysis {
shell_of,
num_shells,
classified,
containers,
}
}
pub fn shells_well_nested(tris: &[[Vec3; 3]]) -> bool {
let a = analyze(tris);
(0..a.num_shells).all(|s| match &a.classified[s] {
Some(c) => c.sign == if a.containers[s].len() % 2 == 0 { 1 } else { -1 },
None => false,
})
}
pub fn plan_repair(tris: &[[Vec3; 3]]) -> RepairPlan {
let Analysis {
shell_of,
num_shells,
classified,
containers,
} = analyze(tris);
let depth = |s: usize| containers[s].len();
let mut flip_shell = vec![false; num_shells];
let mut flipped_shells = 0usize;
for s in 0..num_shells {
let Some(c) = &classified[s] else { continue };
let target = if depth(s) % 2 == 0 { 1 } else { -1 };
if c.sign == target {
continue;
}
if c.sign == 1 {
let mirrored_stack = containers[s].iter().any(|&o| {
classified[o].as_ref().is_some_and(|co| co.sign == -1) && depth(o) % 2 == 0
});
if !mirrored_stack {
continue;
}
}
flip_shell[s] = true;
flipped_shells += 1;
}
RepairPlan {
flip: shell_of.iter().map(|&s| flip_shell[s]).collect(),
num_shells,
flipped_shells,
}
}
pub fn apply_flips(imp: &mut ManifoldImpl, flip: &[bool]) {
debug_assert_eq!(imp.num_tri(), flip.len());
let new_slot = |he: usize| -> usize {
let (t, e) = (he / 3, he % 3);
if flip[t] {
3 * t + [2, 1, 0][e]
} else {
he
}
};
let old = imp.halfedge.clone();
for t in 0..flip.len() {
if !flip[t] {
continue;
}
let (e0, e1, e2) = (old[3 * t].clone(), old[3 * t + 1].clone(), old[3 * t + 2].clone());
imp.halfedge[3 * t] = Halfedge {
start_vert: e0.start_vert,
end_vert: e2.start_vert,
paired_halfedge: -1,
prop_vert: e0.prop_vert,
};
imp.halfedge[3 * t + 1] = Halfedge {
start_vert: e2.start_vert,
end_vert: e1.start_vert,
paired_halfedge: -1,
prop_vert: e2.prop_vert,
};
imp.halfedge[3 * t + 2] = Halfedge {
start_vert: e1.start_vert,
end_vert: e0.start_vert,
paired_halfedge: -1,
prop_vert: e1.prop_vert,
};
}
for i in 0..old.len() {
let p = old[i].paired_halfedge;
imp.halfedge[new_slot(i)].paired_halfedge = if p < 0 {
p
} else {
new_slot(p as usize) as i32
};
}
if imp.is_soup {
for (t, &f) in flip.iter().enumerate() {
if f && t < imp.face_normal.len() {
imp.face_normal[t] = -imp.face_normal[t];
}
}
imp.vert_normal.clear();
} else {
imp.set_normals_and_coplanar();
}
}
#[cfg(test)]
#[path = "repair_tests.rs"]
mod tests;