use super::arrangement::{
boolean, boolean_with_conformity, difference_all, difference_all_lenient, BoolOp,
Tri,
};
use super::signed_volume::signed_volume6;
use crate::mesh::Mesh;
pub(crate) const SNAP_GRID: f64 = 1.0 / 65536.0;
#[inline]
fn snap(c: f64) -> f64 {
(c / SNAP_GRID).round() * SNAP_GRID
}
use super::plane_weld::{promote_cutter_verts_onto_host_faces, promote_operands_mutually};
pub fn mesh_to_tris(m: &Mesh) -> Vec<Tri> {
let vertex = |i: u32| -> Option<[f64; 3]> {
let b = (i as usize) * 3;
let c = [
*m.positions.get(b)? as f64,
*m.positions.get(b + 1)? as f64,
*m.positions.get(b + 2)? as f64,
];
if !c.iter().all(|v| v.is_finite()) {
return None;
}
Some([snap(c[0]), snap(c[1]), snap(c[2])])
};
m.indices
.chunks_exact(3)
.filter_map(|c| Some([vertex(c[0])?, vertex(c[1])?, vertex(c[2])?]))
.collect()
}
fn face_normal(t: &Tri) -> [f32; 3] {
let e1 = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]];
let e2 = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]];
let n = [
e1[1] * e2[2] - e1[2] * e2[1],
e1[2] * e2[0] - e1[0] * e2[2],
e1[0] * e2[1] - e1[1] * e2[0],
];
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len > 0.0 {
[(n[0] / len) as f32, (n[1] / len) as f32, (n[2] / len) as f32]
} else {
[0.0, 0.0, 1.0]
}
}
pub fn tris_to_mesh(tris: &[Tri]) -> Mesh {
let mut m = Mesh::with_capacity(tris.len() * 3, tris.len() * 3);
for t in tris {
let n = face_normal(t);
let base = (m.positions.len() / 3) as u32;
for p in t {
m.positions
.extend_from_slice(&[p[0] as f32, p[1] as f32, p[2] as f32]);
m.normals.extend_from_slice(&n);
}
m.indices.extend_from_slice(&[base, base + 1, base + 2]);
}
m
}
pub(crate) fn orient_outward(mut tris: Vec<Tri>) -> Vec<Tri> {
if signed_volume6(&tris) < 0.0 {
for t in &mut tris {
t.swap(1, 2);
}
}
tris
}
pub fn subtract(host: &Mesh, cutter: &Mesh) -> Mesh {
#[cfg(feature = "csg_capture")]
crate::csg_capture::record_single(host, cutter);
let h = orient_outward(mesh_to_tris(host));
let mut c = mesh_to_tris(cutter);
promote_cutter_verts_onto_host_faces(&mut c, &h);
let c = orient_outward(c);
tris_to_mesh(&boolean(&h, &c, BoolOp::Difference))
}
pub fn subtract_many(host: &Mesh, cutters: &[&Mesh]) -> Option<Mesh> {
#[cfg(feature = "csg_capture")]
crate::csg_capture::record_many(host, cutters);
let h = orient_outward(mesh_to_tris(host));
let comp_tris: Vec<Vec<Tri>> = cutters
.iter()
.map(|m| {
let mut c = mesh_to_tris(m);
promote_cutter_verts_onto_host_faces(&mut c, &h);
orient_outward(c)
})
.collect();
let refs: Vec<&[Tri]> = comp_tris.iter().map(|c| c.as_slice()).collect();
if let Some(r) = difference_all(&h, &refs) {
return Some(tris_to_mesh(&r));
}
let batch = difference_all_lenient(&h, &refs);
let budget_snap = super::budget::snapshot_counters();
let mut inter_sum = 0.0f64;
let mut oracle_tripped = false;
for c in &comp_tris {
super::budget::begin();
let i = boolean(&h, c, BoolOp::Intersection);
if super::budget::tripped() {
oracle_tripped = true;
break;
}
inter_sum += signed_volume6(&i).abs();
}
super::budget::restore_counters(budget_snap);
if oracle_tripped {
return None;
}
let host_v = signed_volume6(&h).abs();
let batch_removed = host_v - signed_volume6(&batch).abs();
let tol = inter_sum.abs().max(1.0e-9) * 0.01;
if (batch_removed - inter_sum).abs() <= tol {
Some(tris_to_mesh(&batch))
} else {
None
}
}
pub fn union(a: &Mesh, b: &Mesh) -> Mesh {
union_with_conformity(a, b).0
}
pub fn union_with_conformity(a: &Mesh, b: &Mesh) -> (Mesh, bool) {
super::budget::begin();
let mut operands = [mesh_to_tris(a), mesh_to_tris(b)];
promote_operands_mutually(&mut operands);
let [ta, tb] = operands;
let (a, b) = (orient_outward(ta), orient_outward(tb));
let (out, conforming) = boolean_with_conformity(&a, &b, BoolOp::Union);
if super::budget::tripped() {
return (Mesh::new(), conforming);
}
(tris_to_mesh(&out), conforming)
}
#[path = "nary_union.rs"]
mod nary_union;
pub use nary_union::union_many;
pub(crate) use nary_union::union_many_preserving_coordinates;
pub fn intersection_tris(a: &Mesh, b: &Mesh) -> Vec<Tri> {
super::budget::begin();
let a = orient_outward(mesh_to_tris(a));
let b = orient_outward(mesh_to_tris(b));
let out = boolean(&a, &b, BoolOp::Intersection);
if super::budget::tripped() {
return Vec::new();
}
out
}
pub fn intersection(a: &Mesh, b: &Mesh) -> Mesh {
tris_to_mesh(&intersection_tris(a, b))
}
#[cfg(test)]
#[path = "mesh_bridge_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "issue_3913_sweep_tests.rs"]
mod issue_3913_sweep_tests;