use crate::Mesh;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
#[path = "mesh_orient_adjacency.rs"]
mod adjacency;
use adjacency::EdgeAdjacency;
#[cfg(test)]
use adjacency::EdgeInc;
const WELD_SCALE: f64 = 1.0e5;
#[derive(Default)]
struct OrientScratch {
vid_of: FxHashMap<(i64, i64, i64), u32>,
vpos: Vec<[f64; 3]>,
corner: Vec<u32>,
edge_tris: EdgeAdjacency,
flip: Vec<bool>,
visited: Vec<bool>,
comp: Vec<usize>,
stack: Vec<usize>,
}
thread_local! {
static ORIENT_SCRATCH: RefCell<Option<OrientScratch>> = const { RefCell::new(None) };
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct OrientVerdict {
pub flipped: bool,
pub all_closed: bool,
pub all_orientable: bool,
pub components: u32,
}
impl OrientVerdict {
pub const INDETERMINATE: Self = Self {
flipped: false,
all_closed: false,
all_orientable: false,
components: 0,
};
#[inline]
pub fn is_single_closed_solid(&self) -> bool {
self.components == 1 && self.all_closed && self.all_orientable
}
}
pub fn orient_mesh_outward(mesh: &mut Mesh) -> bool {
orient_mesh_outward_verdict(mesh).flipped
}
pub fn orient_mesh_outward_verdict(mesh: &mut Mesh) -> OrientVerdict {
let ntri = mesh.indices.len() / 3;
if ntri < 2 {
return OrientVerdict::INDETERMINATE;
}
let vertex_count = mesh.positions.len() / 3;
if !mesh.positions.len().is_multiple_of(3)
|| mesh.indices.iter().any(|&idx| idx as usize >= vertex_count)
{
return OrientVerdict::INDETERMINATE;
}
let mut scratch = ORIENT_SCRATCH.with(|c| c.borrow_mut().take()).unwrap_or_default();
let OrientScratch { vid_of, vpos, corner, edge_tris, flip, visited, comp, stack } =
&mut scratch;
vid_of.clear();
vid_of.reserve(vertex_count);
vpos.clear();
corner.clear();
corner.reserve(mesh.indices.len());
let indexed = vertex_count < mesh.indices.len();
if indexed { corner.resize(vertex_count, u32::MAX); }
let q = |v: f32| (v as f64 * WELD_SCALE).round() as i64;
for &idx in &mesh.indices {
if indexed && corner[idx as usize] != u32::MAX { continue; }
let b = idx as usize * 3;
let key = (
q(mesh.positions[b]),
q(mesh.positions[b + 1]),
q(mesh.positions[b + 2]),
);
let vid = *vid_of.entry(key).or_insert_with(|| {
let id = vpos.len() as u32;
vpos.push([
key.0 as f64 / WELD_SCALE,
key.1 as f64 / WELD_SCALE,
key.2 as f64 / WELD_SCALE,
]);
id
});
if indexed { corner[idx as usize] = vid; } else { corner.push(vid); }
}
let tv = |t: usize| std::array::from_fn::<_, 3, _>(|k| {
corner[if indexed { mesh.indices[3 * t + k] as usize } else { 3 * t + k }]
});
edge_tris.reset(ntri);
for t in 0..ntri {
let v = tv(t);
for (slot, &(a, b)) in [(v[0], v[1]), (v[1], v[2]), (v[2], v[0])].iter().enumerate() {
if a == b {
continue; }
let key = if a < b { (a, b) } else { (b, a) };
edge_tris.push(key, t * 3 + slot);
}
}
flip.clear();
flip.resize(ntri, false);
visited.clear();
visited.resize(ntri, false);
let mut any_flip = false;
let mut verdict = OrientVerdict {
flipped: false,
all_closed: true,
all_orientable: true,
components: 0,
};
for seed in 0..ntri {
if visited[seed] {
continue;
}
verdict.components += 1;
comp.clear();
stack.clear();
stack.push(seed);
visited[seed] = true;
let mut orientable = true;
let mut closed = true;
while let Some(t) = stack.pop() {
comp.push(t);
let v = tv(t);
let dirs = if flip[t] {
[(v[0], v[2]), (v[2], v[1]), (v[1], v[0])]
} else {
[(v[0], v[1]), (v[1], v[2]), (v[2], v[0])]
};
for (slot, &(a, b)) in dirs.iter().enumerate() {
if a == b {
continue;
}
let key = if a < b { (a, b) } else { (b, a) };
let original_slot = if flip[t] { 2 - slot } else { slot };
let (closed_edge, neighbors) = edge_tris.neighbors(key, t * 3 + original_slot);
if !closed_edge { closed = false; }
for nb in neighbors {
if nb == usize::MAX || nb == t { continue; }
let nv = tv(nb);
let need_flip =
[(nv[0], nv[1]), (nv[1], nv[2]), (nv[2], nv[0])].contains(&(a, b));
if !visited[nb] {
visited[nb] = true;
flip[nb] = need_flip;
stack.push(nb);
} else if flip[nb] != need_flip {
orientable = false; }
}
}
}
verdict.all_closed &= closed;
verdict.all_orientable &= orientable;
if !orientable || !closed {
for &t in comp.iter() {
flip[t] = false; }
continue;
}
let mut vol6 = 0.0f64;
for &t in comp.iter() {
let v = tv(t);
let (i0, i1, i2) = if flip[t] {
(v[0], v[2], v[1])
} else {
(v[0], v[1], v[2])
};
let (a, b, c) = (vpos[i0 as usize], vpos[i1 as usize], vpos[i2 as usize]);
vol6 += a[0] * (b[1] * c[2] - b[2] * c[1]) + a[1] * (b[2] * c[0] - b[0] * c[2])
+ a[2] * (b[0] * c[1] - b[1] * c[0]);
}
if vol6 < 0.0 {
for &t in comp.iter() {
flip[t] = !flip[t];
}
}
}
for t in 0..ntri {
if flip[t] {
mesh.indices.swap(3 * t + 1, 3 * t + 2);
any_flip = true;
}
}
verdict.flipped = any_flip;
ORIENT_SCRATCH.with(|c| *c.borrow_mut() = Some(scratch));
verdict
}
#[cfg(test)]
#[path = "mesh_orient_tests.rs"]
mod tests;