#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BooleanOpType {
Union,
Intersect,
Subtract,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BooleanConfig {
pub tolerance: f32,
pub repair_manifold: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BooleanResult {
pub vertices: Vec<[f32; 3]>,
pub faces: Vec<[u32; 3]>,
pub is_manifold: bool,
}
#[allow(dead_code)]
pub fn default_boolean_config() -> BooleanConfig {
BooleanConfig {
tolerance: 1e-5,
repair_manifold: true,
}
}
#[allow(dead_code)]
pub fn aabb_from_verts(verts: &[[f32; 3]]) -> ([f32; 3], [f32; 3]) {
if verts.is_empty() {
return ([0.0; 3], [0.0; 3]);
}
let mut mn = verts[0];
let mut mx = verts[0];
for v in verts.iter().skip(1) {
for k in 0..3 {
if v[k] < mn[k] {
mn[k] = v[k];
}
if v[k] > mx[k] {
mx[k] = v[k];
}
}
}
(mn, mx)
}
#[allow(dead_code)]
pub fn point_in_aabb(p: [f32; 3], aabb_min: [f32; 3], aabb_max: [f32; 3]) -> bool {
p[0] > aabb_min[0]
&& p[0] < aabb_max[0]
&& p[1] > aabb_min[1]
&& p[1] < aabb_max[1]
&& p[2] > aabb_min[2]
&& p[2] < aabb_max[2]
}
fn tri_centroid(
verts: &[[f32; 3]],
face: [u32; 3],
) -> [f32; 3] {
let a = verts[face[0] as usize];
let b = verts[face[1] as usize];
let c = verts[face[2] as usize];
[
(a[0] + b[0] + c[0]) / 3.0,
(a[1] + b[1] + c[1]) / 3.0,
(a[2] + b[2] + c[2]) / 3.0,
]
}
#[allow(dead_code)]
pub fn boolean_op(
verts_a: &[[f32; 3]],
faces_a: &[[u32; 3]],
verts_b: &[[f32; 3]],
faces_b: &[[u32; 3]],
op: BooleanOpType,
_cfg: &BooleanConfig,
) -> BooleanResult {
use super::mesh_boolean_difference::{
mesh_boolean_difference, BooleanDifferenceConfig,
};
use super::mesh_boolean_intersection::{
mesh_boolean_intersection, BooleanIntersectionConfig,
};
use super::mesh_boolean_union::{mesh_boolean_union, BooleanUnionConfig};
let (out_verts, out_faces) = match op {
BooleanOpType::Union => {
let r = mesh_boolean_union(
verts_a,
faces_a,
verts_b,
faces_b,
&BooleanUnionConfig::default(),
);
(r.vertices, r.triangles)
}
BooleanOpType::Intersect => {
let r = mesh_boolean_intersection(
verts_a,
faces_a,
verts_b,
faces_b,
&BooleanIntersectionConfig::default(),
);
(r.vertices, r.triangles)
}
BooleanOpType::Subtract => {
let r = mesh_boolean_difference(
verts_a,
faces_a,
verts_b,
faces_b,
&BooleanDifferenceConfig::default(),
);
(r.vertices, r.triangles)
}
};
let is_manifold = check_manifold(&out_faces, out_verts.len());
BooleanResult {
vertices: out_verts,
faces: out_faces,
is_manifold,
}
}
fn check_manifold(faces: &[[u32; 3]], _vert_count: usize) -> bool {
use std::collections::HashMap;
let mut edge_count: HashMap<(u32, u32), u32> = HashMap::new();
for face in faces {
let edges = [
(face[0].min(face[1]), face[0].max(face[1])),
(face[1].min(face[2]), face[1].max(face[2])),
(face[0].min(face[2]), face[0].max(face[2])),
];
for e in &edges {
*edge_count.entry(*e).or_insert(0) += 1;
}
}
edge_count.values().all(|&c| c <= 2)
}
#[allow(dead_code)]
pub fn boolean_result_vertex_count(result: &BooleanResult) -> usize {
result.vertices.len()
}
#[allow(dead_code)]
pub fn boolean_result_face_count(result: &BooleanResult) -> usize {
result.faces.len()
}
#[allow(dead_code)]
pub fn boolean_op_name(op: BooleanOpType) -> &'static str {
match op {
BooleanOpType::Union => "union",
BooleanOpType::Intersect => "intersect",
BooleanOpType::Subtract => "subtract",
}
}
#[allow(dead_code)]
pub fn boolean_result_is_manifold(result: &BooleanResult) -> bool {
result.is_manifold
}
#[cfg(test)]
mod tests {
use super::*;
fn unit_box_mesh() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let verts = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let faces = vec![[0, 1, 2], [0, 2, 3]];
(verts, faces)
}
fn offset_box_mesh() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let verts = vec![
[0.5, 0.5, 0.0],
[1.5, 0.5, 0.0],
[1.5, 1.5, 0.0],
[0.5, 1.5, 0.0],
];
let faces = vec![[0, 1, 2], [0, 2, 3]];
(verts, faces)
}
#[test]
fn test_default_config() {
let cfg = default_boolean_config();
assert!(cfg.tolerance > 0.0);
assert!(cfg.repair_manifold);
}
#[test]
fn test_boolean_op_name() {
assert_eq!(boolean_op_name(BooleanOpType::Union), "union");
assert_eq!(boolean_op_name(BooleanOpType::Intersect), "intersect");
assert_eq!(boolean_op_name(BooleanOpType::Subtract), "subtract");
}
#[test]
fn test_aabb_from_verts() {
let (verts, _) = unit_box_mesh();
let (mn, mx) = aabb_from_verts(&verts);
assert_eq!(mn, [0.0, 0.0, 0.0]);
assert_eq!(mx, [1.0, 1.0, 0.0]);
}
#[test]
fn test_aabb_from_verts_empty() {
let (mn, mx) = aabb_from_verts(&[]);
assert_eq!(mn, [0.0, 0.0, 0.0]);
assert_eq!(mx, [0.0, 0.0, 0.0]);
}
#[test]
fn test_point_in_aabb() {
assert!(point_in_aabb(
[0.5, 0.5, 0.5],
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0]
));
assert!(!point_in_aabb(
[1.5, 0.5, 0.5],
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0]
));
}
#[test]
fn test_subtract_removes_overlap() {
let (va, fa) = unit_box_mesh();
let (vb, fb) = offset_box_mesh();
let cfg = default_boolean_config();
let result = boolean_op(&va, &fa, &vb, &fb, BooleanOpType::Subtract, &cfg);
let _vc = boolean_result_vertex_count(&result);
let fc = boolean_result_face_count(&result);
assert!(fc <= fa.len(), "subtract should not add faces");
}
#[test]
fn test_union_has_all_b_faces() {
let (va, fa) = unit_box_mesh();
let (vb, fb) = offset_box_mesh();
let cfg = default_boolean_config();
let result = boolean_op(&va, &fa, &vb, &fb, BooleanOpType::Union, &cfg);
assert!(
boolean_result_face_count(&result) >= fb.len(),
"union face count={}",
boolean_result_face_count(&result)
);
}
#[test]
fn test_intersect_empty_when_no_overlap() {
let (va, fa) = unit_box_mesh();
let vb = vec![
[10.0, 10.0, 0.0],
[11.0, 10.0, 0.0],
[11.0, 11.0, 0.0],
[10.0, 11.0, 0.0],
];
let fb = vec![[0u32, 1, 2], [0, 2, 3]];
let cfg = default_boolean_config();
let result = boolean_op(&va, &fa, &vb, &fb, BooleanOpType::Intersect, &cfg);
assert_eq!(
boolean_result_face_count(&result),
0,
"no intersection expected"
);
}
#[test]
fn test_manifold_check() {
let (va, fa) = unit_box_mesh();
let (vb, fb) = offset_box_mesh();
let cfg = default_boolean_config();
let result = boolean_op(&va, &fa, &vb, &fb, BooleanOpType::Subtract, &cfg);
let _m = boolean_result_is_manifold(&result);
}
}