use crate::Mesh;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
#[derive(Clone, Copy, Default)]
struct EdgeInc {
tris: [usize; 2],
count: u32,
}
impl EdgeInc {
#[inline]
fn push(&mut self, t: usize) {
if (self.count as usize) < 2 {
self.tris[self.count as usize] = t;
}
self.count += 1;
}
#[inline]
fn incident(&self) -> &[usize] {
&self.tris[..(self.count as usize).min(2)]
}
}
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: FxHashMap<(u32, u32), EdgeInc>,
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 q = |v: f32| (v as f64 * WELD_SCALE).round() as i64;
for &idx in &mesh.indices {
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
});
corner.push(vid);
}
let tv = |t: usize| [corner[3 * t], corner[3 * t + 1], corner[3 * t + 2]];
edge_tris.clear();
edge_tris.reserve(ntri * 2);
for t in 0..ntri {
let v = tv(t);
for &(a, b) in &[(v[0], v[1]), (v[1], v[2]), (v[2], v[0])] {
if a == b {
continue; }
let key = if a < b { (a, b) } else { (b, a) };
edge_tris.entry(key).or_default().push(t);
}
}
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 &(a, b) in &dirs {
if a == b {
continue;
}
let key = if a < b { (a, b) } else { (b, a) };
let inc = &edge_tris[&key];
if inc.count != 2 {
closed = false; }
if inc.count > 2 {
continue; }
for &nb in inc.incident() {
if 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;