use super::{dot, norm, normalize, scale, sub, V3};
use crate::mesh::Mesh;
use rustc_hash::FxHashMap;
pub(super) fn directed_closed(mesh: &Mesh) -> bool {
let key = |i: u32| -> (i64, i64, i64) {
let b = i as usize * 3;
let q = |v: f32| (v as f64 / 1.0e-4).round() as i64;
(
q(mesh.positions[b]),
q(mesh.positions[b + 1]),
q(mesh.positions[b + 2]),
)
};
let mut edges: FxHashMap<((i64, i64, i64), (i64, i64, i64)), i64> = FxHashMap::default();
for tri in mesh.indices.chunks_exact(3) {
let (ka, kb, kc) = (key(tri[0]), key(tri[1]), key(tri[2]));
if ka == kb || kb == kc || kc == ka {
continue;
}
for (x, y) in [(ka, kb), (kb, kc), (kc, ka)] {
*edges.entry((x, y)).or_insert(0) += 1;
*edges.entry((y, x)).or_insert(0) -= 1;
}
}
!edges.is_empty() && edges.values().all(|&c| c == 0)
}
pub(super) fn closed_or_hairline(mesh: &Mesh) -> bool {
type K = (i64, i64, i64);
let key = |i: u32| -> K {
let b = i as usize * 3;
let q = |v: f32| (v as f64 / 1.0e-4).round() as i64;
(
q(mesh.positions[b]),
q(mesh.positions[b + 1]),
q(mesh.positions[b + 2]),
)
};
let mut edges: FxHashMap<(K, K), i64> = FxHashMap::default();
for tri in mesh.indices.chunks_exact(3) {
let (ka, kb, kc) = (key(tri[0]), key(tri[1]), key(tri[2]));
if ka == kb || kb == kc || kc == ka {
continue;
}
for (x, y) in [(ka, kb), (kb, kc), (kc, ka)] {
*edges.entry((x, y)).or_insert(0) += 1;
*edges.entry((y, x)).or_insert(0) -= 1;
}
}
if edges.is_empty() {
return false;
}
let mut bad: Vec<(K, K, i64)> = Vec::new();
for (&(a, b), &c) in edges.iter() {
if c > 0 {
bad.push((a, b, c));
}
}
bad.sort_unstable_by_key(|&(a, b, _)| (a, b));
if bad.is_empty() {
return true;
}
if bad.len() > 64 {
return false; }
let p = |k: K| [k.0 as f64, k.1 as f64, k.2 as f64];
const COLLINEAR_TOL: f64 = 2.0; const T_EPS: f64 = 1.0e-6;
struct Seg {
a: V3,
b: V3,
m: i64,
}
let segs: Vec<Seg> = bad
.iter()
.map(|&(a, b, c)| Seg {
a: p(a),
b: p(b),
m: c,
})
.collect();
let perp = |x: V3, o: V3, dir: V3| -> f64 {
let w = sub(x, o);
let along = dot(w, dir);
norm(sub(w, scale(dir, along)))
};
struct Line {
o: V3,
dir: V3,
members: Vec<usize>,
}
let mut order: Vec<usize> = (0..segs.len()).collect();
order.sort_by(|&i, &j| {
let li = dot(sub(segs[i].b, segs[i].a), sub(segs[i].b, segs[i].a));
let lj = dot(sub(segs[j].b, segs[j].a), sub(segs[j].b, segs[j].a));
lj.total_cmp(&li).then(i.cmp(&j))
});
let mut lines: Vec<Line> = Vec::new();
'seg: for si in order {
let s = &segs[si];
let Some(sdir) = normalize(sub(s.b, s.a)) else {
return false;
};
for line in lines.iter_mut() {
if perp(s.a, line.o, line.dir) <= COLLINEAR_TOL
&& perp(s.b, line.o, line.dir) <= COLLINEAR_TOL
{
line.members.push(si);
continue 'seg;
}
}
lines.push(Line {
o: s.a,
dir: sdir,
members: vec![si],
});
}
const GAP_TOL: f64 = COLLINEAR_TOL; for line in &lines {
let mut ints: Vec<(f64, f64, i64)> = Vec::with_capacity(line.members.len());
let mut breaks: Vec<f64> = Vec::with_capacity(line.members.len() * 2);
for &si in &line.members {
let s = &segs[si];
let ta = dot(sub(s.a, line.o), line.dir);
let tb = dot(sub(s.b, line.o), line.dir);
let sign = if tb >= ta { 1 } else { -1 };
ints.push((ta.min(tb), ta.max(tb), sign * s.m));
breaks.push(ta);
breaks.push(tb);
}
breaks.sort_by(|x, y| x.total_cmp(y));
let mut run = 0.0_f64;
for w in breaks.windows(2) {
let (lo, hi) = (w[0], w[1]);
let len = hi - lo;
if len <= T_EPS {
continue;
}
let mid = 0.5 * (lo + hi);
let mut sum = 0i64;
for &(ilo, ihi, sm) in &ints {
if ilo - T_EPS <= mid && mid <= ihi + T_EPS {
sum += sm;
}
}
if sum != 0 {
run += len;
if run > GAP_TOL {
return false;
}
} else {
run = 0.0;
}
}
}
true
}