#![allow(dead_code)]
use crate::mesh::MeshBuffers;
use crate::mesh_sdf::{combined_aabb, compute_sdf_on_bounds, sdf_to_mesh, sdf_union};
#[derive(Debug, Clone)]
pub struct BooleanUnionConfig {
pub tolerance: f32,
pub max_iterations: usize,
pub weld_threshold: f32,
}
impl Default for BooleanUnionConfig {
fn default() -> Self {
Self {
tolerance: 1e-6,
max_iterations: 128,
weld_threshold: 1e-5,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BooleanUnionResult {
pub vertices: Vec<[f32; 3]>,
pub triangles: Vec<[u32; 3]>,
pub vertex_count: usize,
pub triangle_count: usize,
}
impl BooleanUnionResult {
pub fn new(vertices: Vec<[f32; 3]>, triangles: Vec<[u32; 3]>) -> Self {
let vertex_count = vertices.len();
let triangle_count = triangles.len();
Self {
vertices,
triangles,
vertex_count,
triangle_count,
}
}
}
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,
}
}
fn mesh_to_result(mesh: MeshBuffers) -> BooleanUnionResult {
let triangles: Vec<[u32; 3]> = mesh
.indices
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
BooleanUnionResult::new(mesh.positions, triangles)
}
pub fn mesh_boolean_union(
verts_a: &[[f32; 3]],
tris_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
tris_b: &[[u32; 3]],
_cfg: &BooleanUnionConfig,
) -> BooleanUnionResult {
if verts_a.is_empty() && verts_b.is_empty() {
return BooleanUnionResult::default();
}
if verts_a.is_empty() {
return BooleanUnionResult::new(
verts_b.to_vec(),
tris_b.to_vec(),
);
}
if verts_b.is_empty() {
return BooleanUnionResult::new(
verts_a.to_vec(),
tris_a.to_vec(),
);
}
if !meshes_aabb_overlap(verts_a, verts_b) {
let offset = verts_a.len() as u32;
let mut vertices = verts_a.to_vec();
vertices.extend_from_slice(verts_b);
let mut triangles = tris_a.to_vec();
for tri in tris_b {
triangles.push([tri[0] + offset, tri[1] + offset, tri[2] + offset]);
}
return BooleanUnionResult::new(vertices, triangles);
}
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_union(&sdf_a, &sdf_b).unwrap_or(sdf_a);
let mesh_out = sdf_to_mesh(&sdf_result, 0.0);
mesh_to_result(mesh_out)
}
pub fn validate_mesh_for_boolean(verts: &[[f32; 3]], tris: &[[u32; 3]]) -> bool {
let n = verts.len() as u32;
tris.iter().all(|t| t[0] < n && t[1] < n && t[2] < n)
}
pub fn estimate_union_vertex_count(a_count: usize, b_count: usize) -> usize {
a_count + b_count
}
pub fn meshes_aabb_overlap(verts_a: &[[f32; 3]], verts_b: &[[f32; 3]]) -> bool {
if verts_a.is_empty() || verts_b.is_empty() {
return false;
}
let (mut amin, mut amax) = ([f32::MAX; 3], [f32::MIN; 3]);
for v in verts_a {
for k in 0..3 {
if v[k] < amin[k] {
amin[k] = v[k];
}
if v[k] > amax[k] {
amax[k] = v[k];
}
}
}
let (mut bmin, mut bmax) = ([f32::MAX; 3], [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];
}
}
}
(0..3).all(|k| amin[k] <= bmax[k] && bmin[k] <= amax[k])
}
pub fn weld_union_vertices(vertices: &[[f32; 3]], threshold: f32) -> Vec<[f32; 3]> {
let mut result: Vec<[f32; 3]> = Vec::new();
'outer: for &v in vertices {
for existing in &result {
let dx = v[0] - existing[0];
let dy = v[1] - existing[1];
let dz = v[2] - existing[2];
if (dx * dx + dy * dy + dz * dz).sqrt() < threshold {
continue 'outer;
}
}
result.push(v);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = BooleanUnionConfig::default();
assert!(cfg.tolerance > 0.0 );
assert!(cfg.max_iterations > 0 );
}
#[test]
fn test_union_empty_meshes() {
let cfg = BooleanUnionConfig::default();
let result = mesh_boolean_union(&[], &[], &[], &[], &cfg);
assert_eq!(
result.vertex_count,
0
);
assert_eq!(
result.triangle_count,
0
);
}
#[test]
fn test_union_concatenates_vertices() {
let cfg = BooleanUnionConfig::default();
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 vb = vec![[2.0f32, 0.0, 0.0], [3.0, 0.0, 0.0], [2.0, 1.0, 0.0]];
let tb = vec![[0u32, 1, 2]];
let result = mesh_boolean_union(&va, &ta, &vb, &tb, &cfg);
assert_eq!(result.vertex_count, 6 );
assert_eq!(result.triangle_count, 2 );
}
#[test]
fn test_union_index_offset() {
let cfg = BooleanUnionConfig::default();
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 vb = vec![[5.0f32, 0.0, 0.0], [6.0, 0.0, 0.0], [5.0, 1.0, 0.0]];
let tb = vec![[0u32, 1, 2]];
let result = mesh_boolean_union(&va, &ta, &vb, &tb, &cfg);
assert_eq!(result.triangles[1], [3, 4, 5]);
}
#[test]
fn test_validate_mesh_valid() {
let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tris = vec![[0u32, 1, 2]];
assert!(validate_mesh_for_boolean(&verts, &tris) );
}
#[test]
fn test_validate_mesh_invalid_index() {
let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0]];
let tris = vec![[0u32, 1, 5]];
assert!(!validate_mesh_for_boolean(&verts, &tris));
}
#[test]
fn test_estimate_vertex_count() {
assert_eq!(
estimate_union_vertex_count(10, 20),
30
);
}
#[test]
fn test_aabb_overlap_true() {
let va = vec![[0.0f32, 0.0, 0.0], [1.0, 1.0, 1.0]];
let vb = vec![[0.5f32, 0.5, 0.5], [1.5, 1.5, 1.5]];
assert!(meshes_aabb_overlap(&va, &vb) );
}
#[test]
fn test_aabb_no_overlap() {
let va = vec![[0.0f32, 0.0, 0.0], [1.0, 1.0, 1.0]];
let vb = vec![[2.0f32, 2.0, 2.0], [3.0, 3.0, 3.0]];
assert!(!meshes_aabb_overlap(&va, &vb) );
}
#[test]
fn test_weld_removes_duplicates() {
let verts = vec![[0.0f32, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
let welded = weld_union_vertices(&verts, 1e-4);
assert_eq!(welded.len(), 2 );
}
fn overlapping_box_mesh(offset: [f32; 3]) -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let mesh = crate::shapes::box_mesh([0.5, 0.5, 0.5]);
let verts: Vec<[f32; 3]> = mesh
.positions
.iter()
.map(|p| [p[0] + offset[0], p[1] + offset[1], p[2] + offset[2]])
.collect();
let tris: Vec<[u32; 3]> = mesh
.indices
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
(verts, tris)
}
#[test]
fn union_produces_more_volume_than_either() {
let cfg = BooleanUnionConfig::default();
let (va, ta) = overlapping_box_mesh([0.0, 0.0, 0.0]);
let (vb, tb) = overlapping_box_mesh([0.25, 0.25, 0.25]); let result = mesh_boolean_union(&va, &ta, &vb, &tb, &cfg);
assert!(
result.triangle_count > 0,
"union of overlapping unit boxes must produce triangles, got {}",
result.triangle_count
);
}
}