use super::ray_parity::point_enclosed;
use crate::mesh::Mesh;
use rustc_hash::FxHashMap;
const OUTER_OPEN_EDGE_RATIO_MAX: f64 = 0.05;
const AREA_RATIO_MAX: f64 = 0.25;
const MAX_CANDIDATES: usize = 10_000;
const MAX_PARITY_RAY_TRIS: u64 = 30_000_000;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct CavityStats {
pub components_dropped: u32,
pub triangles_dropped: u32,
}
pub(crate) fn drop_enclosed_cavities(mesh: &mut Mesh, weld_eps: f32) -> CavityStats {
let n_verts = mesh.positions.len() / 3;
let n_tris = mesh.indices.len() / 3;
if n_verts == 0 || n_tris < 8 {
return CavityStats::default();
}
let pos_scale = 1.0 / weld_eps.max(f32::MIN_POSITIVE);
let q = |v: f32| -> i64 { (v * pos_scale).round() as i64 };
let mut canonical: FxHashMap<[i64; 3], u32> = FxHashMap::default();
let mut weld: Vec<u32> = Vec::with_capacity(n_verts);
let mut n_welded: u32 = 0;
for i in 0..n_verts {
let key = [
q(mesh.positions[i * 3]),
q(mesh.positions[i * 3 + 1]),
q(mesh.positions[i * 3 + 2]),
];
weld.push(*canonical.entry(key).or_insert_with(|| {
let id = n_welded;
n_welded += 1;
id
}));
}
let mut uf = UnionFind::new(n_welded as usize);
let welded_tri = |tri: &[u32]| -> Option<[u32; 3]> {
if (tri[0] as usize) >= n_verts
|| (tri[1] as usize) >= n_verts
|| (tri[2] as usize) >= n_verts
{
return None;
}
Some([
weld[tri[0] as usize],
weld[tri[1] as usize],
weld[tri[2] as usize],
])
};
for tri in mesh.indices.chunks_exact(3) {
if let Some([a, b, c]) = welded_tri(tri) {
uf.union(a, b);
uf.union(b, c);
}
}
#[derive(Clone)]
struct Comp {
area: f64,
min: [f64; 3],
max: [f64; 3],
tris: u32,
edges: u64,
open_edges: u64,
ext: [[f64; 3]; 6],
}
let mut comp_of_root: FxHashMap<u32, usize> = FxHashMap::default();
let mut comps: Vec<Comp> = Vec::new();
let mut comp_of_tri: Vec<usize> = Vec::with_capacity(n_tris);
let p = |i: u32| -> [f64; 3] {
let i = i as usize * 3;
[
mesh.positions[i] as f64,
mesh.positions[i + 1] as f64,
mesh.positions[i + 2] as f64,
]
};
for tri in mesh.indices.chunks_exact(3) {
let Some([wa, _, _]) = welded_tri(tri) else {
comp_of_tri.push(usize::MAX);
continue;
};
let root = uf.find(wa);
let next = comps.len();
let ci = *comp_of_root.entry(root).or_insert(next);
if ci == comps.len() {
comps.push(Comp {
area: 0.0,
min: [f64::INFINITY; 3],
max: [f64::NEG_INFINITY; 3],
tris: 0,
edges: 0,
open_edges: 0,
ext: [[0.0; 3]; 6],
});
}
let (a, b, c) = (p(tri[0]), p(tri[1]), p(tri[2]));
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let cr = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
let comp = &mut comps[ci];
comp.area += 0.5 * (cr[0] * cr[0] + cr[1] * cr[1] + cr[2] * cr[2]).sqrt();
comp.tris += 1;
for vert in [a, b, c] {
for k in 0..3 {
if vert[k] < comp.min[k] {
comp.min[k] = vert[k];
comp.ext[k] = vert;
}
if vert[k] > comp.max[k] {
comp.max[k] = vert[k];
comp.ext[3 + k] = vert;
}
}
}
comp_of_tri.push(ci);
}
if comps.len() < 2 {
return CavityStats::default();
}
if comps.len() - 1 > MAX_CANDIDATES {
return CavityStats::default();
}
let mut edge_use: FxHashMap<u64, u32> = FxHashMap::default();
for tri in mesh.indices.chunks_exact(3) {
let Some([wa, wb, wc]) = welded_tri(tri) else {
continue;
};
for (x, y) in [(wa, wb), (wb, wc), (wc, wa)] {
if x == y {
continue; }
let key = ((x.min(y) as u64) << 32) | (x.max(y) as u64);
*edge_use.entry(key).or_insert(0) += 1;
}
}
for (&key, &count) in &edge_use {
let root = uf.find((key >> 32) as u32);
if let Some(&ci) = comp_of_root.get(&root) {
comps[ci].edges += 1;
if count == 1 {
comps[ci].open_edges += 1;
}
}
}
let outer = comps
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.area.total_cmp(&b.area))
.map(|(i, _)| i)
.unwrap();
let outer_comp = comps[outer].clone();
if !(outer_comp.area > 0.0) || outer_comp.edges == 0 {
return CavityStats::default();
}
if outer_comp.open_edges as f64 / outer_comp.edges as f64 > OUTER_OPEN_EDGE_RATIO_MAX {
return CavityStats::default();
}
let diag = ((outer_comp.max[0] - outer_comp.min[0]).powi(2)
+ (outer_comp.max[1] - outer_comp.min[1]).powi(2)
+ (outer_comp.max[2] - outer_comp.min[2]).powi(2))
.sqrt();
if !(diag > 0.0) || !diag.is_finite() {
return CavityStats::default();
}
let outer_tris: Vec<[[f64; 3]; 3]> = mesh
.indices
.chunks_exact(3)
.zip(comp_of_tri.iter())
.filter(|(_, &ci)| ci == outer)
.map(|(tri, _)| [p(tri[0]), p(tri[1]), p(tri[2])])
.collect();
let aabb_pad = 1e-9 * diag; let mut candidates: Vec<(usize, Vec<[f64; 3]>)> = Vec::new();
for (ci, comp) in comps.iter().enumerate() {
if ci == outer || comp.tris == 0 {
continue;
}
let inside_aabb = (0..3).all(|k| {
comp.min[k] >= outer_comp.min[k] - aabb_pad
&& comp.max[k] <= outer_comp.max[k] + aabb_pad
});
if !inside_aabb {
continue;
}
if comp.area > AREA_RATIO_MAX * outer_comp.area {
continue;
}
let center = [
0.5 * (comp.min[0] + comp.max[0]),
0.5 * (comp.min[1] + comp.max[1]),
0.5 * (comp.min[2] + comp.max[2]),
];
let mut points: Vec<[f64; 3]> = Vec::with_capacity(7);
points.push(center);
for e in comp.ext {
if !points.contains(&e) {
points.push(e);
}
}
candidates.push((ci, points));
}
let total_points: u64 = candidates.iter().map(|(_, pts)| pts.len() as u64).sum();
if total_points
.saturating_mul(3)
.saturating_mul(outer_tris.len() as u64)
> MAX_PARITY_RAY_TRIS
{
return CavityStats::default();
}
let mut drop: Vec<bool> = vec![false; comps.len()];
let mut components_dropped = 0u32;
for (ci, points) in candidates {
if points
.iter()
.all(|&pt| point_enclosed(pt, diag, &outer_tris))
{
drop[ci] = true;
components_dropped += 1;
}
}
if components_dropped == 0 {
return CavityStats::default();
}
let mut kept: Vec<u32> = Vec::with_capacity(mesh.indices.len());
let mut triangles_dropped = 0u32;
for (tri, &ci) in mesh.indices.chunks_exact(3).zip(comp_of_tri.iter()) {
if ci != usize::MAX && drop[ci] {
triangles_dropped += 1;
} else {
kept.extend_from_slice(tri);
}
}
mesh.indices = kept;
CavityStats {
components_dropped,
triangles_dropped,
}
}
struct UnionFind {
parent: Vec<u32>,
}
impl UnionFind {
fn new(n: usize) -> Self {
Self {
parent: (0..n as u32).collect(),
}
}
fn find(&mut self, mut x: u32) -> u32 {
while self.parent[x as usize] != x {
let grand = self.parent[self.parent[x as usize] as usize];
self.parent[x as usize] = grand;
x = grand;
}
x
}
fn union(&mut self, a: u32, b: u32) {
let (ra, rb) = (self.find(a), self.find(b));
if ra != rb {
self.parent[rb as usize] = ra;
}
}
}