use crate::data::{CellArray, Points, PolyData};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BooleanOp {
Union,
Intersection,
Difference,
}
pub fn boolean(a: &PolyData, b: &PolyData, op: BooleanOp) -> PolyData {
let tris_a = collect_triangles(a);
let tris_b = collect_triangles(b);
let a_inside_b: Vec<bool> = tris_a.iter().map(|tri| {
let c = centroid(tri);
is_inside(&tris_b, c)
}).collect();
let b_inside_a: Vec<bool> = tris_b.iter().map(|tri| {
let c = centroid(tri);
is_inside(&tris_a, c)
}).collect();
let mut out_points = Points::<f64>::new();
let mut out_polys = CellArray::new();
match op {
BooleanOp::Union => {
for (i, tri) in tris_a.iter().enumerate() {
if !a_inside_b[i] {
add_triangle(&mut out_points, &mut out_polys, tri, false);
}
}
for (i, tri) in tris_b.iter().enumerate() {
if !b_inside_a[i] {
add_triangle(&mut out_points, &mut out_polys, tri, false);
}
}
}
BooleanOp::Intersection => {
for (i, tri) in tris_a.iter().enumerate() {
if a_inside_b[i] {
add_triangle(&mut out_points, &mut out_polys, tri, false);
}
}
for (i, tri) in tris_b.iter().enumerate() {
if b_inside_a[i] {
add_triangle(&mut out_points, &mut out_polys, tri, false);
}
}
}
BooleanOp::Difference => {
for (i, tri) in tris_a.iter().enumerate() {
if !a_inside_b[i] {
add_triangle(&mut out_points, &mut out_polys, tri, false);
}
}
for (i, tri) in tris_b.iter().enumerate() {
if b_inside_a[i] {
add_triangle(&mut out_points, &mut out_polys, tri, true);
}
}
}
}
let mut pd = PolyData::new();
pd.points = out_points;
pd.polys = out_polys;
pd
}
type Tri = [[f64; 3]; 3];
fn collect_triangles(pd: &PolyData) -> Vec<Tri> {
let mut tris = Vec::new();
for cell in pd.polys.iter() {
if cell.len() < 3 {
continue;
}
let p0 = pd.points.get(cell[0] as usize);
for i in 1..cell.len() - 1 {
let p1 = pd.points.get(cell[i] as usize);
let p2 = pd.points.get(cell[i + 1] as usize);
tris.push([
[p0[0], p0[1], p0[2]],
[p1[0], p1[1], p1[2]],
[p2[0], p2[1], p2[2]],
]);
}
}
tris
}
fn centroid(tri: &Tri) -> [f64; 3] {
[
(tri[0][0] + tri[1][0] + tri[2][0]) / 3.0,
(tri[0][1] + tri[1][1] + tri[2][1]) / 3.0,
(tri[0][2] + tri[1][2] + tri[2][2]) / 3.0,
]
}
fn is_inside(tris: &[Tri], point: [f64; 3]) -> bool {
let mut count = 0u32;
for tri in tris {
if ray_hits_triangle(point, tri) {
count += 1;
}
}
count % 2 == 1
}
fn ray_hits_triangle(origin: [f64; 3], tri: &Tri) -> bool {
let e1 = sub(tri[1], tri[0]);
let e2 = sub(tri[2], tri[0]);
let dir = [1.0, 0.0, 0.0];
let h = cross(dir, e2);
let a = dot(e1, h);
if a.abs() < 1e-12 {
return false;
}
let f = 1.0 / a;
let s = sub(origin, tri[0]);
let u = f * dot(s, h);
if !(0.0..=1.0).contains(&u) {
return false;
}
let q = cross(s, e1);
let v = f * dot(dir, q);
if v < 0.0 || u + v > 1.0 {
return false;
}
let t = f * dot(e2, q);
t > 1e-12
}
fn add_triangle(
points: &mut Points<f64>,
polys: &mut CellArray,
tri: &Tri,
reverse: bool,
) {
let base = points.len() as i64;
points.push(tri[0]);
points.push(tri[1]);
points.push(tri[2]);
if reverse {
polys.push_cell(&[base, base + 2, base + 1]);
} else {
polys.push_cell(&[base, base + 1, base + 2]);
}
}
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[cfg(test)]
mod tests {
use super::*;
fn make_box(min: [f64; 3], max: [f64; 3]) -> PolyData {
let mut pd = PolyData::new();
let corners = [
[min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], max[1], min[2]], [min[0], max[1], min[2]], [min[0], min[1], max[2]], [max[0], min[1], max[2]], [max[0], max[1], max[2]], [min[0], max[1], max[2]], ];
for c in &corners {
pd.points.push(*c);
}
let faces: [[usize; 4]; 6] = [
[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [2, 3, 7, 6], [0, 4, 7, 3], [1, 2, 6, 5], ];
for face in &faces {
pd.polys.push_cell(&[
face[0] as i64, face[1] as i64, face[2] as i64,
]);
pd.polys.push_cell(&[
face[0] as i64, face[2] as i64, face[3] as i64,
]);
}
pd
}
#[test]
fn union_disjoint() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([3.0, 0.0, 0.0], [4.0, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Union);
assert_eq!(result.polys.num_cells(), 24); }
#[test]
fn intersection_disjoint() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([3.0, 0.0, 0.0], [4.0, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Intersection);
assert_eq!(result.polys.num_cells(), 0);
}
#[test]
fn difference_disjoint() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([3.0, 0.0, 0.0], [4.0, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Difference);
assert_eq!(result.polys.num_cells(), 12);
}
#[test]
fn union_overlapping() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Union);
assert!(result.polys.num_cells() > 0);
assert!(result.polys.num_cells() < 24);
}
#[test]
fn intersection_overlapping() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Intersection);
assert!(result.polys.num_cells() > 0);
}
#[test]
fn difference_overlapping() {
let a = make_box([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let b = make_box([0.5, 0.0, 0.0], [1.5, 1.0, 1.0]);
let result = boolean(&a, &b, BooleanOp::Difference);
assert!(result.polys.num_cells() > 0);
}
#[test]
fn contained_intersection() {
let a = make_box([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
let b = make_box([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]);
let result = boolean(&a, &b, BooleanOp::Intersection);
assert_eq!(result.polys.num_cells(), 12); }
#[test]
fn contained_union() {
let a = make_box([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
let b = make_box([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]);
let result = boolean(&a, &b, BooleanOp::Union);
assert_eq!(result.polys.num_cells(), 12); }
}