use super::geom::{mesh_is_closed_exact, mesh_point, mesh_signed_volume, point_inside_mesh};
use crate::{Mesh, Point3};
pub(super) fn cut_changed_mesh(result: &Mesh, tris_before: usize, vol_before: f64) -> bool {
if result.triangle_count() != tris_before {
return true;
}
let vol_after = mesh_signed_volume(result).abs();
let vol_before = vol_before.abs();
(vol_after - vol_before).abs() > vol_before.max(1.0e-9) * 1.0e-3
}
const SWEEP_COST_BUDGET: usize = 2_000_000;
fn point_mesh_distance_exceeds(mesh: &Mesh, p: &Point3<f64>, tol: f64) -> bool {
let tol2 = tol * tol;
for tri in mesh.indices.chunks_exact(3) {
let (Some(a), Some(b), Some(c)) = (
mesh_point(mesh, tri[0]),
mesh_point(mesh, tri[1]),
mesh_point(mesh, tri[2]),
) else {
continue;
};
let ab = b - a;
let ac = c - a;
let ap = p - a;
let d1 = ab.dot(&ap);
let d2 = ac.dot(&ap);
let q = if d1 <= 0.0 && d2 <= 0.0 {
a
} else {
let bp = p - b;
let d3 = ab.dot(&bp);
let d4 = ac.dot(&bp);
if d3 >= 0.0 && d4 <= d3 {
b
} else {
let vc = d1 * d4 - d3 * d2;
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
a + ab * (d1 / (d1 - d3))
} else {
let cp = p - c;
let d5 = ab.dot(&cp);
let d6 = ac.dot(&cp);
if d6 >= 0.0 && d5 <= d6 {
c
} else {
let vb = d5 * d2 - d1 * d6;
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
a + ac * (d2 / (d2 - d6))
} else {
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
b + (c - b) * ((d4 - d3) / ((d4 - d3) + (d5 - d6)))
} else {
let denom = 1.0 / (va + vb + vc);
a + ab * (vb * denom) + ac * (vc * denom)
}
}
}
}
}
};
if (p - q).norm_squared() <= tol2 {
return false;
}
}
true
}
fn overlapping_components(mesh: &Mesh) -> bool {
use std::collections::HashMap;
let nverts = mesh.positions.len() / 3;
let mut parent: Vec<u32> = (0..nverts as u32).collect();
fn find(parent: &mut [u32], mut x: u32) -> u32 {
while parent[x as usize] != x {
parent[x as usize] = parent[parent[x as usize] as usize];
x = parent[x as usize];
}
x
}
let mut by_pos: HashMap<(u32, u32, u32), u32> = HashMap::new();
for i in 0..nverts {
let b = i * 3;
let key = (
mesh.positions[b].to_bits(),
mesh.positions[b + 1].to_bits(),
mesh.positions[b + 2].to_bits(),
);
match by_pos.get(&key) {
Some(&j) => {
let (a, b2) = (find(&mut parent, i as u32), find(&mut parent, j));
parent[a as usize] = b2;
}
None => {
by_pos.insert(key, i as u32);
}
}
}
for tri in mesh.indices.chunks_exact(3) {
let a = find(&mut parent, tri[0]);
let b = find(&mut parent, tri[1]);
parent[a as usize] = b;
let b = find(&mut parent, tri[1]);
let c = find(&mut parent, tri[2]);
parent[b as usize] = c;
}
let mut boxes: HashMap<u32, ([f32; 3], [f32; 3])> = HashMap::new();
for tri in mesh.indices.chunks_exact(3) {
let root = find(&mut parent, tri[0]);
let entry = boxes
.entry(root)
.or_insert(([f32::INFINITY; 3], [f32::NEG_INFINITY; 3]));
for &vi in tri {
let b = vi as usize * 3;
for k in 0..3 {
entry.0[k] = entry.0[k].min(mesh.positions[b + k]);
entry.1[k] = entry.1[k].max(mesh.positions[b + k]);
}
}
}
if boxes.len() < 2 {
return false;
}
let list: Vec<&([f32; 3], [f32; 3])> = boxes.values().collect();
for i in 0..list.len() {
for j in (i + 1)..list.len() {
let (amn, amx) = list[i];
let (bmn, bmx) = list[j];
if (0..3).all(|k| amn[k] <= bmx[k] && bmn[k] <= amx[k]) {
return true;
}
}
}
false
}
pub(super) fn drop_faces_outside_host(result: Mesh, original_host: &Mesh) -> Mesh {
if result.indices.is_empty() || original_host.indices.is_empty() {
return result;
}
let tri_count = result.indices.len() / 3;
let vert_count = result.positions.len() / 3;
if result.indices.iter().any(|&i| i as usize >= vert_count) {
return result;
}
if tri_count.saturating_mul(original_host.indices.len() / 3) > SWEEP_COST_BUDGET {
return result;
}
let reference_host = original_host.welded_by_position(1.0e-6);
if !mesh_is_closed_exact(&reference_host) || overlapping_components(&reference_host) {
return result;
}
let reference_host = &reference_host;
const EPS: f64 = 5.0e-5;
let mut keep: Vec<bool> = Vec::with_capacity(tri_count);
let mut dropped = 0usize;
for tri in result.indices.chunks_exact(3) {
let (Some(a), Some(b), Some(c)) = (
mesh_point(&result, tri[0]),
mesh_point(&result, tri[1]),
mesh_point(&result, tri[2]),
) else {
keep.push(true);
continue;
};
let centroid = Point3::new(
(a.x + b.x + c.x) / 3.0,
(a.y + b.y + c.y) / 3.0,
(a.z + b.z + c.z) / 3.0,
);
let n = (b - a).cross(&(c - a));
let len = n.norm();
if len <= 0.0 {
keep.push(true); continue;
}
let off = n * (EPS / len);
const VERTEX_CLEARANCE: f64 = 1.0e-3;
let centroid_out = !point_inside_mesh(reference_host, centroid + off)
&& !point_inside_mesh(reference_host, centroid - off);
let vertex_out = [a, b, c].iter().any(|&v| {
!point_inside_mesh(reference_host, v)
&& point_mesh_distance_exceeds(reference_host, &v, VERTEX_CLEARANCE)
});
let kept = !(centroid_out || vertex_out);
if !kept {
dropped += 1;
}
keep.push(kept);
}
if dropped == 0 || dropped == tri_count {
return result;
}
let mut remap: Vec<u32> = vec![u32::MAX; vert_count];
let mut out = result.clone();
out.positions.clear();
out.normals.clear();
out.indices.clear();
let has_normals = result.normals.len() == result.positions.len();
for (i, tri) in result.indices.chunks_exact(3).enumerate() {
if !keep[i] {
continue;
}
for &vi in tri {
let v = vi as usize;
if remap[v] == u32::MAX {
remap[v] = (out.positions.len() / 3) as u32;
out.positions
.extend_from_slice(&result.positions[v * 3..v * 3 + 3]);
if has_normals {
out.normals
.extend_from_slice(&result.normals[v * 3..v * 3 + 3]);
}
}
out.indices.push(remap[v]);
}
}
out
}