#![allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BooleanIntersectionResult {
pub vertices: Vec<[f32; 3]>,
pub triangles: Vec<[u32; 3]>,
}
#[derive(Debug, Clone)]
pub struct BooleanIntersectionConfig {
pub tolerance: f32,
pub max_iterations: usize,
}
impl Default for BooleanIntersectionConfig {
fn default() -> Self {
Self {
tolerance: 1e-6,
max_iterations: 64,
}
}
}
use crate::mesh::MeshBuffers;
use crate::mesh_sdf::{combined_aabb, compute_sdf_on_bounds, sdf_intersection, sdf_to_mesh};
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_intersection(
verts_a: &[[f32; 3]],
tris_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
tris_b: &[[u32; 3]],
_cfg: &BooleanIntersectionConfig,
) -> BooleanIntersectionResult {
if verts_a.is_empty()
|| tris_a.is_empty()
|| verts_b.is_empty()
|| tris_b.is_empty()
|| !aabbs_overlap(verts_a, verts_b)
{
return BooleanIntersectionResult::default();
}
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_intersection(&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();
BooleanIntersectionResult {
vertices: mesh_out.positions,
triangles,
}
}
pub fn aabbs_overlap(va: &[[f32; 3]], vb: &[[f32; 3]]) -> bool {
if va.is_empty() || vb.is_empty() {
return false;
}
let mut amin = [f32::MAX; 3];
let mut amax = [f32::MIN; 3];
for v in va {
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 = [f32::MAX; 3];
let mut bmax = [f32::MIN; 3];
for v in vb {
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])
}
#[allow(clippy::needless_range_loop)]
pub fn triangles_inside_aabb(
verts: &[[f32; 3]],
tris: &[[u32; 3]],
aabb_min: [f32; 3],
aabb_max: [f32; 3],
) -> Vec<[u32; 3]> {
let mut result = Vec::new();
for tri in tris {
let mut centroid = [0.0f32; 3];
for i in 0..3 {
let vi = tri[i] as usize;
if vi >= verts.len() {
continue;
}
for k in 0..3 {
centroid[k] += verts[vi][k];
}
}
for k in 0..3 {
centroid[k] /= 3.0;
}
let inside = (0..3).all(|k| (aabb_min[k]..=aabb_max[k]).contains(¢roid[k]));
if inside {
result.push(*tri);
}
}
result
}
pub fn count_shared_edge_candidates(tris_a: &[[u32; 3]], tris_b: &[[u32; 3]]) -> usize {
tris_a.len().min(tris_b.len())
}
pub fn intersection_aabb_volume(va: &[[f32; 3]], vb: &[[f32; 3]]) -> f32 {
if !aabbs_overlap(va, vb) {
return 0.0;
}
let mut amin = [f32::MAX; 3];
let mut amax = [f32::MIN; 3];
for v in va {
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 = [f32::MAX; 3];
let mut bmax = [f32::MIN; 3];
for v in vb {
for k in 0..3 {
if v[k] < bmin[k] {
bmin[k] = v[k];
}
if v[k] > bmax[k] {
bmax[k] = v[k];
}
}
}
let mut vol = 1.0f32;
for k in 0..3 {
let lo = amin[k].max(bmin[k]);
let hi = amax[k].min(bmax[k]);
vol *= (hi - lo).max(0.0);
}
vol
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = BooleanIntersectionConfig::default();
assert!(cfg.tolerance > 0.0 );
}
#[test]
fn test_intersection_empty_returns_empty() {
let cfg = BooleanIntersectionConfig::default();
let result = mesh_boolean_intersection(&[], &[], &[], &[], &cfg);
assert!(result.vertices.is_empty() );
}
#[test]
fn test_aabbs_overlap_true() {
let va = vec![[0.0f32, 0.0, 0.0], [2.0, 2.0, 2.0]];
let vb = vec![[1.0f32, 1.0, 1.0], [3.0, 3.0, 3.0]];
assert!(aabbs_overlap(&va, &vb) );
}
#[test]
fn test_aabbs_no_overlap() {
let va = vec![[0.0f32, 0.0, 0.0], [1.0, 1.0, 1.0]];
let vb = vec![[5.0f32, 5.0, 5.0], [6.0, 6.0, 6.0]];
assert!(!aabbs_overlap(&va, &vb) );
}
#[test]
fn test_aabbs_empty_input() {
assert!(!aabbs_overlap(&[], &[[0.0f32, 0.0, 0.0]]) );
}
#[test]
fn test_triangles_inside_aabb() {
let verts = vec![[0.5f32, 0.5, 0.5], [0.6, 0.5, 0.5], [0.5, 0.6, 0.5]];
let tris = vec![[0u32, 1, 2]];
let inside = triangles_inside_aabb(&verts, &tris, [0.0; 3], [1.0; 3]);
assert_eq!(inside.len(), 1 );
}
#[test]
fn test_triangles_outside_aabb() {
let verts = vec![[5.0f32, 5.0, 5.0], [6.0, 5.0, 5.0], [5.0, 6.0, 5.0]];
let tris = vec![[0u32, 1, 2]];
let inside = triangles_inside_aabb(&verts, &tris, [0.0; 3], [1.0; 3]);
assert_eq!(inside.len(), 0 );
}
#[test]
fn test_count_shared_edge_candidates() {
let ta = vec![[0u32, 1, 2], [1, 2, 3]];
let tb = vec![[0u32, 1, 2]];
assert_eq!(
count_shared_edge_candidates(&ta, &tb),
1
);
}
#[test]
fn test_intersection_aabb_volume() {
let va = vec![[0.0f32, 0.0, 0.0], [2.0, 2.0, 2.0]];
let vb = vec![[1.0f32, 1.0, 1.0], [3.0, 3.0, 3.0]];
let vol = intersection_aabb_volume(&va, &vb);
assert!((vol - 1.0).abs() < 1e-4 );
}
#[test]
fn test_intersection_aabb_volume_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_eq!(
intersection_aabb_volume(&va, &vb),
0.0
);
}
fn box_at(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 intersection_of_overlapping_boxes_is_non_empty() {
let cfg = BooleanIntersectionConfig::default();
let (va, ta) = box_at([0.0, 0.0, 0.0]);
let (vb, tb) = box_at([0.25, 0.25, 0.25]); let result = mesh_boolean_intersection(&va, &ta, &vb, &tb, &cfg);
assert!(
!result.vertices.is_empty(),
"overlapping boxes must have a non-empty intersection"
);
}
#[test]
fn intersection_of_non_overlapping_boxes_is_empty() {
let cfg = BooleanIntersectionConfig::default();
let (va, ta) = box_at([0.0, 0.0, 0.0]);
let (vb, tb) = box_at([5.0, 5.0, 5.0]); let result = mesh_boolean_intersection(&va, &ta, &vb, &tb, &cfg);
assert_eq!(
result.vertices.len(),
0,
"disjoint boxes must have an empty intersection"
);
}
}