#![allow(dead_code)]
use crate::mesh::MeshBuffers;
use crate::mesh_sdf::{combined_aabb, compute_sdf_on_bounds, sdf_subtraction, sdf_to_mesh};
#[derive(Debug, Clone, Default)]
pub struct BooleanDifferenceResult {
pub vertices: Vec<[f32; 3]>,
pub triangles: Vec<[u32; 3]>,
pub removed_triangle_count: usize,
}
#[derive(Debug, Clone)]
pub struct BooleanDifferenceConfig {
pub tolerance: f32,
pub flip_normals_on_b: bool,
}
impl Default for BooleanDifferenceConfig {
fn default() -> Self {
Self {
tolerance: 1e-6,
flip_normals_on_b: true,
}
}
}
fn build_mesh_buffers(verts: &[[f32; 3]], tris: &[[u32; 3]]) -> MeshBuffers {
let n = verts.len();
MeshBuffers {
positions: verts.to_vec(),
indices: tris.iter().flat_map(|t| [t[0], t[1], t[2]]).collect(),
normals: vec![[0.0f32, 1.0, 0.0]; n],
tangents: vec![[1.0f32, 0.0, 0.0, 1.0]; n],
uvs: vec![[0.0f32; 2]; n],
colors: None,
has_suit: false,
}
}
pub fn mesh_boolean_difference(
verts_a: &[[f32; 3]],
tris_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
tris_b: &[[u32; 3]],
_cfg: &BooleanDifferenceConfig,
) -> BooleanDifferenceResult {
if verts_b.is_empty() || tris_b.is_empty() {
return BooleanDifferenceResult {
vertices: verts_a.to_vec(),
triangles: tris_a.to_vec(),
removed_triangle_count: 0,
};
}
if verts_a.is_empty() || tris_a.is_empty() {
return BooleanDifferenceResult::default();
}
let original_tri_count = tris_a.len();
let mesh_a = build_mesh_buffers(verts_a, tris_a);
let mesh_b = build_mesh_buffers(verts_b, tris_b);
let (mn, mx) = combined_aabb(&mesh_a, &mesh_b, 0.1);
let res = 32usize;
let sdf_a = compute_sdf_on_bounds(&mesh_a, mn, mx, res, true);
let sdf_b = compute_sdf_on_bounds(&mesh_b, mn, mx, res, true);
let sdf_result = sdf_subtraction(&sdf_a, &sdf_b).unwrap_or(sdf_a);
let mesh_out = sdf_to_mesh(&sdf_result, 0.0);
let triangles: Vec<[u32; 3]> = mesh_out
.indices
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
let result_tri_count = triangles.len();
let removed_triangle_count = original_tri_count.saturating_sub(result_tri_count);
BooleanDifferenceResult {
vertices: mesh_out.positions,
triangles,
removed_triangle_count,
}
}
pub fn flip_triangle_winding(tris: &[[u32; 3]]) -> Vec<[u32; 3]> {
tris.iter().map(|t| [t[2], t[1], t[0]]).collect()
}
pub fn estimate_removed_triangles(
verts_a: &[[f32; 3]],
tris_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
) -> usize {
if verts_b.is_empty() {
return 0;
}
let mut bmin = [f32::MAX; 3];
let mut bmax = [f32::MIN; 3];
for v in verts_b {
for k in 0..3 {
if v[k] < bmin[k] {
bmin[k] = v[k];
}
if v[k] > bmax[k] {
bmax[k] = v[k];
}
}
}
tris_a
.iter()
.filter(|tri| {
let mut c = [0.0f32; 3];
for idx in tri.iter() {
let vi = *idx as usize;
if vi < verts_a.len() {
for k in 0..3 {
c[k] += verts_a[vi][k];
}
}
}
c.iter_mut().for_each(|x| *x /= 3.0);
(0..3).all(|k| (bmin[k]..=bmax[k]).contains(&c[k]))
})
.count()
}
pub fn build_keep_mask(
verts_a: &[[f32; 3]],
tris_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
) -> Vec<bool> {
if verts_b.is_empty() {
return vec![true; tris_a.len()];
}
let mut bmin = [f32::MAX; 3];
let mut bmax = [f32::MIN; 3];
for v in verts_b {
for k in 0..3 {
if v[k] < bmin[k] {
bmin[k] = v[k];
}
if v[k] > bmax[k] {
bmax[k] = v[k];
}
}
}
tris_a
.iter()
.map(|tri| {
let mut c = [0.0f32; 3];
for idx in tri.iter() {
let vi = *idx as usize;
if vi < verts_a.len() {
for k in 0..3 {
c[k] += verts_a[vi][k];
}
}
}
c.iter_mut().for_each(|x| *x /= 3.0);
!(0..3).all(|k| (bmin[k]..=bmax[k]).contains(&c[k]))
})
.collect()
}
pub fn apply_keep_mask(tris: &[[u32; 3]], mask: &[bool]) -> Vec<[u32; 3]> {
tris.iter()
.zip(mask.iter())
.filter_map(|(t, &keep)| if keep { Some(*t) } else { None })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_difference_empty_b_returns_a() {
let va = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let ta = vec![[0u32, 1, 2]];
let cfg = BooleanDifferenceConfig::default();
let result = mesh_boolean_difference(&va, &ta, &[], &[], &cfg);
assert_eq!(result.vertices.len(), 3 );
assert_eq!(result.triangles.len(), 1 );
}
#[test]
fn test_flip_winding() {
let tris = vec![[0u32, 1, 2]];
let flipped = flip_triangle_winding(&tris);
assert_eq!(flipped[0], [2, 1, 0] );
}
#[test]
fn test_flip_winding_multiple() {
let tris = vec![[0u32, 1, 2], [3, 4, 5]];
let flipped = flip_triangle_winding(&tris);
assert_eq!(flipped.len(), 2 );
assert_eq!(flipped[1], [5, 4, 3]);
}
#[test]
fn test_estimate_removed_empty_b() {
let va = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let ta = vec![[0u32, 1, 2]];
assert_eq!(estimate_removed_triangles(&va, &ta, &[]), 0 );
}
#[test]
fn test_keep_mask_empty_b() {
let va = vec![[0.0f32; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let ta = vec![[0u32, 1, 2]];
let mask = build_keep_mask(&va, &ta, &[]);
assert!(mask.iter().all(|&k| k) );
}
#[test]
fn test_apply_keep_mask_all_true() {
let tris = vec![[0u32, 1, 2], [3, 4, 5]];
let mask = vec![true, true];
let result = apply_keep_mask(&tris, &mask);
assert_eq!(result.len(), 2 );
}
#[test]
fn test_apply_keep_mask_mixed() {
let tris = vec![[0u32, 1, 2], [3, 4, 5]];
let mask = vec![true, false];
let result = apply_keep_mask(&tris, &mask);
assert_eq!(result.len(), 1 );
}
#[test]
fn test_default_config_flip() {
let cfg = BooleanDifferenceConfig::default();
assert!(cfg.flip_normals_on_b );
}
#[test]
fn test_difference_result_removed_count() {
let va = vec![[0.0f32; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let ta = vec![[0u32, 1, 2]];
let cfg = BooleanDifferenceConfig::default();
let result = mesh_boolean_difference(&va, &ta, &[], &[], &cfg);
assert_eq!(result.removed_triangle_count, 0 );
}
fn unit_box_half() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let mesh = crate::shapes::box_mesh([0.5, 0.5, 0.5]);
let tris: Vec<[u32; 3]> = mesh
.indices
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
(mesh.positions, tris)
}
#[test]
fn difference_a_minus_a_near_empty() {
let cfg = BooleanDifferenceConfig::default();
let (va, ta) = unit_box_half();
let (vb, tb) = unit_box_half(); let result = mesh_boolean_difference(&va, &ta, &vb, &tb, &cfg);
assert!(
result.triangles.len() <= 4,
"A minus A should be near-empty, got {} triangles",
result.triangles.len()
);
}
}