1use threecrate_core::{Point3f, Result, TriangleMesh, Vector3f};
15
16const EPSILON: f32 = 1e-5;
18
19#[derive(Debug, Clone)]
25struct Plane {
26 normal: Vector3f,
27 w: f32,
28}
29
30impl Plane {
31 fn from_points(a: &Point3f, b: &Point3f, c: &Point3f) -> Option<Self> {
32 let n = (b - a).cross(&(c - a));
33 let len = n.magnitude();
34 if len < EPSILON * EPSILON {
35 return None; }
37 let normal = n / len;
38 let w = normal.dot(&a.coords);
39 Some(Plane { normal, w })
40 }
41
42 fn flip(&mut self) {
43 self.normal = -self.normal;
44 self.w = -self.w;
45 }
46
47 #[inline]
48 fn signed_distance(&self, p: &Point3f) -> f32 {
49 self.normal.dot(&p.coords) - self.w
50 }
51
52 fn split_polygon(
55 &self,
56 poly: &Polygon,
57 coplanar_front: &mut Vec<Polygon>,
58 coplanar_back: &mut Vec<Polygon>,
59 front: &mut Vec<Polygon>,
60 back: &mut Vec<Polygon>,
61 ) {
62 let verts = &poly.vertices;
63 let n = verts.len();
64
65 let dists: Vec<f32> = verts.iter().map(|v| self.signed_distance(v)).collect();
67 let mut poly_mask: u8 = 0;
68 for &d in &dists {
69 if d > EPSILON {
70 poly_mask |= 1; } else if d < -EPSILON {
72 poly_mask |= 2; }
74 }
76
77 match poly_mask {
78 0 => {
79 if self.normal.dot(&poly.plane.normal) > 0.0 {
81 coplanar_front.push(poly.clone());
82 } else {
83 coplanar_back.push(poly.clone());
84 }
85 }
86 1 => front.push(poly.clone()),
87 2 => back.push(poly.clone()),
88 _ => {
89 let mut f_verts: Vec<Point3f> = Vec::new();
91 let mut b_verts: Vec<Point3f> = Vec::new();
92
93 for i in 0..n {
94 let j = (i + 1) % n;
95 let di = dists[i];
96 let dj = dists[j];
97 let vi = verts[i];
98 let vj = verts[j];
99
100 let i_front = di > EPSILON;
101 let i_back = di < -EPSILON;
102 let j_front = dj > EPSILON;
103 let j_back = dj < -EPSILON;
104
105 if !i_back {
106 f_verts.push(vi);
107 }
108 if !i_front {
109 b_verts.push(vi);
110 }
111
112 if (i_front && j_back) || (i_back && j_front) {
114 let t = di / (di - dj);
115 let mid = vi + (vj - vi) * t;
116 f_verts.push(mid);
117 b_verts.push(mid);
118 }
119 }
120
121 front.extend(Polygon::fan_triangulate(f_verts));
122 back.extend(Polygon::fan_triangulate(b_verts));
123 }
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
130struct Polygon {
131 vertices: Vec<Point3f>,
132 plane: Plane,
133}
134
135impl Polygon {
136 fn new(vertices: Vec<Point3f>) -> Option<Self> {
137 if vertices.len() < 3 {
138 return None;
139 }
140 let plane = Plane::from_points(&vertices[0], &vertices[1], &vertices[2])?;
141 Some(Polygon { vertices, plane })
142 }
143
144 fn flip(&mut self) {
145 self.vertices.reverse();
146 self.plane.flip();
147 }
148
149 fn fan_triangulate(verts: Vec<Point3f>) -> Vec<Polygon> {
151 if verts.len() < 3 {
152 return Vec::new();
153 }
154 let mut out = Vec::with_capacity(verts.len() - 2);
155 for i in 1..verts.len() - 1 {
156 if let Some(p) = Polygon::new(vec![verts[0], verts[i], verts[i + 1]]) {
157 out.push(p);
158 }
159 }
160 out
161 }
162}
163
164struct BspNode {
169 plane: Option<Plane>,
170 front: Option<Box<BspNode>>,
171 back: Option<Box<BspNode>>,
172 polygons: Vec<Polygon>,
173}
174
175impl BspNode {
176 fn new() -> Self {
177 BspNode {
178 plane: None,
179 front: None,
180 back: None,
181 polygons: Vec::new(),
182 }
183 }
184
185 fn invert(&mut self) {
186 for p in &mut self.polygons {
187 p.flip();
188 }
189 if let Some(ref mut plane) = self.plane {
190 plane.flip();
191 }
192 if let Some(ref mut f) = self.front {
193 f.invert();
194 }
195 if let Some(ref mut b) = self.back {
196 b.invert();
197 }
198 std::mem::swap(&mut self.front, &mut self.back);
199 }
200
201 fn clip_polygons(&self, polygons: Vec<Polygon>) -> Vec<Polygon> {
203 let plane = match &self.plane {
204 Some(p) => p,
205 None => return polygons,
206 };
207
208 let mut f: Vec<Polygon> = Vec::new();
209 let mut b: Vec<Polygon> = Vec::new();
210
211 for poly in polygons {
212 let mut cf = Vec::new();
213 let mut cb = Vec::new();
214 let mut pf = Vec::new();
215 let mut pb = Vec::new();
216 plane.split_polygon(&poly, &mut cf, &mut cb, &mut pf, &mut pb);
217 f.extend(cf);
218 f.extend(pf);
219 b.extend(cb);
220 b.extend(pb);
221 }
222
223 let mut result = match &self.front {
224 Some(node) => node.clip_polygons(f),
225 None => f,
226 };
227 let back_result = match &self.back {
228 Some(node) => node.clip_polygons(b),
229 None => Vec::new(), };
231 result.extend(back_result);
232 result
233 }
234
235 fn clip_to(&mut self, other: &BspNode) {
237 self.polygons = other.clip_polygons(std::mem::take(&mut self.polygons));
238 if let Some(ref mut f) = self.front {
239 f.clip_to(other);
240 }
241 if let Some(ref mut b) = self.back {
242 b.clip_to(other);
243 }
244 }
245
246 fn all_polygons(&self) -> Vec<Polygon> {
248 let mut out = self.polygons.clone();
249 if let Some(ref f) = self.front {
250 out.extend(f.all_polygons());
251 }
252 if let Some(ref b) = self.back {
253 out.extend(b.all_polygons());
254 }
255 out
256 }
257
258 fn build(&mut self, polygons: Vec<Polygon>) {
260 if polygons.is_empty() {
261 return;
262 }
263
264 if self.plane.is_none() {
265 self.plane = Some(polygons[0].plane.clone());
266 }
267 let plane = self.plane.clone().unwrap();
268
269 let mut front = Vec::new();
270 let mut back = Vec::new();
271
272 for poly in polygons {
273 let mut cf = Vec::new();
274 let mut cb = Vec::new();
275 let mut pf = Vec::new();
276 let mut pb = Vec::new();
277 plane.split_polygon(&poly, &mut cf, &mut cb, &mut pf, &mut pb);
278 self.polygons.extend(cf);
279 self.polygons.extend(cb);
280 front.extend(pf);
281 back.extend(pb);
282 }
283
284 if !front.is_empty() {
285 if self.front.is_none() {
286 self.front = Some(Box::new(BspNode::new()));
287 }
288 self.front.as_mut().unwrap().build(front);
289 }
290 if !back.is_empty() {
291 if self.back.is_none() {
292 self.back = Some(Box::new(BspNode::new()));
293 }
294 self.back.as_mut().unwrap().build(back);
295 }
296 }
297}
298
299fn mesh_to_polygons(mesh: &TriangleMesh) -> Vec<Polygon> {
304 mesh.faces
305 .iter()
306 .filter_map(|face| {
307 Polygon::new(vec![
308 mesh.vertices[face[0]],
309 mesh.vertices[face[1]],
310 mesh.vertices[face[2]],
311 ])
312 })
313 .collect()
314}
315
316fn polygons_to_mesh(polygons: Vec<Polygon>) -> TriangleMesh {
317 let capacity = polygons
320 .iter()
321 .map(|p| p.vertices.len().saturating_sub(2))
322 .sum::<usize>();
323 let mut vertices = Vec::with_capacity(capacity * 3);
324 let mut faces = Vec::with_capacity(capacity);
325
326 for poly in &polygons {
327 let n = poly.vertices.len();
328 if n < 3 {
329 continue;
330 }
331 for i in 1..n - 1 {
333 let base = vertices.len();
334 vertices.push(poly.vertices[0]);
335 vertices.push(poly.vertices[i]);
336 vertices.push(poly.vertices[i + 1]);
337 faces.push([base, base + 1, base + 2]);
338 }
339 }
340
341 TriangleMesh::from_vertices_and_faces(vertices, faces)
342}
343
344fn build_bsp(mesh: &TriangleMesh) -> BspNode {
345 let mut node = BspNode::new();
346 node.build(mesh_to_polygons(mesh));
347 node
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub enum BooleanOp {
357 Union,
359 Intersection,
361 Difference,
363}
364
365pub fn mesh_boolean(a: &TriangleMesh, b: &TriangleMesh, op: BooleanOp) -> Result<TriangleMesh> {
375 match op {
376 BooleanOp::Union => mesh_union(a, b),
377 BooleanOp::Intersection => mesh_intersection(a, b),
378 BooleanOp::Difference => mesh_difference(a, b),
379 }
380}
381
382pub fn mesh_union(a: &TriangleMesh, b: &TriangleMesh) -> Result<TriangleMesh> {
399 if a.is_empty() {
400 return Ok(b.clone());
401 }
402 if b.is_empty() {
403 return Ok(a.clone());
404 }
405
406 let mut a_bsp = build_bsp(a);
407 let mut b_bsp = build_bsp(b);
408
409 a_bsp.clip_to(&b_bsp);
410 b_bsp.clip_to(&a_bsp);
411 b_bsp.invert();
412 b_bsp.clip_to(&a_bsp);
413 b_bsp.invert();
414 a_bsp.build(b_bsp.all_polygons());
415
416 Ok(polygons_to_mesh(a_bsp.all_polygons()))
417}
418
419pub fn mesh_intersection(a: &TriangleMesh, b: &TriangleMesh) -> Result<TriangleMesh> {
436 if a.is_empty() || b.is_empty() {
437 return Ok(TriangleMesh::new());
438 }
439
440 let mut a_bsp = build_bsp(a);
441 let mut b_bsp = build_bsp(b);
442
443 a_bsp.invert();
444 b_bsp.clip_to(&a_bsp);
445 b_bsp.invert();
446 a_bsp.clip_to(&b_bsp);
447 b_bsp.clip_to(&a_bsp);
448 a_bsp.build(b_bsp.all_polygons());
449 a_bsp.invert();
450
451 Ok(polygons_to_mesh(a_bsp.all_polygons()))
452}
453
454pub fn mesh_difference(a: &TriangleMesh, b: &TriangleMesh) -> Result<TriangleMesh> {
471 if a.is_empty() {
472 return Ok(TriangleMesh::new());
473 }
474 if b.is_empty() {
475 return Ok(a.clone());
476 }
477
478 let mut a_bsp = build_bsp(a);
479 let mut b_bsp = build_bsp(b);
480
481 a_bsp.invert();
482 a_bsp.clip_to(&b_bsp);
483 b_bsp.clip_to(&a_bsp);
484 b_bsp.invert();
485 b_bsp.clip_to(&a_bsp);
486 b_bsp.invert();
487 a_bsp.build(b_bsp.all_polygons());
488 a_bsp.invert();
489
490 Ok(polygons_to_mesh(a_bsp.all_polygons()))
491}
492
493#[cfg(test)]
498mod tests {
499 use super::*;
500
501 fn make_box(min: Point3f, max: Point3f) -> TriangleMesh {
504 let (x0, y0, z0) = (min.x, min.y, min.z);
505 let (x1, y1, z1) = (max.x, max.y, max.z);
506 let verts = vec![
507 Point3f::new(x0, y0, z0), Point3f::new(x1, y0, z0), Point3f::new(x1, y1, z0), Point3f::new(x0, y1, z0), Point3f::new(x0, y0, z1), Point3f::new(x1, y0, z1), Point3f::new(x1, y1, z1), Point3f::new(x0, y1, z1), ];
516 let faces = vec![
518 [0, 2, 1],
520 [0, 3, 2],
521 [4, 5, 6],
523 [4, 6, 7],
524 [0, 1, 5],
526 [0, 5, 4],
527 [3, 7, 6],
529 [3, 6, 2],
530 [0, 4, 7],
532 [0, 7, 3],
533 [1, 2, 6],
535 [1, 6, 5],
536 ];
537 TriangleMesh::from_vertices_and_faces(verts, faces)
538 }
539
540 #[test]
543 fn test_union_empty_a() {
544 let empty = TriangleMesh::new();
545 let b = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
546 let result = mesh_union(&empty, &b).unwrap();
547 assert!(!result.is_empty());
548 }
549
550 #[test]
551 fn test_union_empty_b() {
552 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
553 let empty = TriangleMesh::new();
554 let result = mesh_union(&a, &empty).unwrap();
555 assert!(!result.is_empty());
556 }
557
558 #[test]
559 fn test_intersection_empty() {
560 let empty = TriangleMesh::new();
561 let b = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
562 let result = mesh_intersection(&empty, &b).unwrap();
563 assert!(result.is_empty());
564 }
565
566 #[test]
567 fn test_difference_empty_a() {
568 let empty = TriangleMesh::new();
569 let b = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
570 let result = mesh_difference(&empty, &b).unwrap();
571 assert!(result.is_empty());
572 }
573
574 #[test]
575 fn test_difference_empty_b() {
576 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
577 let empty = TriangleMesh::new();
578 let result = mesh_difference(&a, &empty).unwrap();
579 assert!(!result.is_empty());
580 }
581
582 #[test]
585 fn test_union_non_overlapping() {
586 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
588 let b = make_box(Point3f::new(5.0, 0.0, 0.0), Point3f::new(6.0, 1.0, 1.0));
589 let result = mesh_union(&a, &b).unwrap();
590 assert!(!result.is_empty());
592 assert!(
593 result.face_count() >= 24,
594 "expected ≥24 faces, got {}",
595 result.face_count()
596 );
597 }
598
599 #[test]
600 fn test_intersection_non_overlapping() {
601 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
602 let b = make_box(Point3f::new(5.0, 0.0, 0.0), Point3f::new(6.0, 1.0, 1.0));
603 let result = mesh_intersection(&a, &b).unwrap();
604 assert!(
606 result.is_empty(),
607 "non-overlapping intersection should be empty, got {} faces",
608 result.face_count()
609 );
610 }
611
612 #[test]
613 fn test_difference_non_overlapping() {
614 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(1.0, 1.0, 1.0));
615 let b = make_box(Point3f::new(5.0, 0.0, 0.0), Point3f::new(6.0, 1.0, 1.0));
616 let result = mesh_difference(&a, &b).unwrap();
617 assert!(!result.is_empty());
619 assert!(
620 result.face_count() >= 12,
621 "expected ≥12 faces, got {}",
622 result.face_count()
623 );
624 }
625
626 #[test]
629 fn test_union_overlapping() {
630 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(2.0, 2.0, 2.0));
632 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(3.0, 3.0, 3.0));
633 let result = mesh_union(&a, &b).unwrap();
634 assert!(
635 !result.is_empty(),
636 "union of overlapping boxes must not be empty"
637 );
638 }
639
640 #[test]
641 fn test_intersection_overlapping() {
642 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(2.0, 2.0, 2.0));
643 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(3.0, 3.0, 3.0));
644 let result = mesh_intersection(&a, &b).unwrap();
645 assert!(
646 !result.is_empty(),
647 "intersection of overlapping boxes must not be empty"
648 );
649 assert!(result.face_count() <= a.face_count() + b.face_count() + 50);
651 }
652
653 #[test]
654 fn test_difference_overlapping() {
655 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(2.0, 2.0, 2.0));
656 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(3.0, 3.0, 3.0));
657 let result = mesh_difference(&a, &b).unwrap();
658 assert!(
659 !result.is_empty(),
660 "A minus partially-overlapping B must not be empty"
661 );
662 }
663
664 #[test]
665 fn test_difference_a_fully_contains_b() {
666 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(4.0, 4.0, 4.0));
668 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(2.0, 2.0, 2.0));
669 let result = mesh_difference(&a, &b).unwrap();
670 assert!(
671 !result.is_empty(),
672 "difference with interior hole must not be empty"
673 );
674 }
675
676 #[test]
677 fn test_intersection_a_fully_contains_b() {
678 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(4.0, 4.0, 4.0));
680 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(2.0, 2.0, 2.0));
681 let result = mesh_intersection(&a, &b).unwrap();
682 assert!(
683 !result.is_empty(),
684 "intersection when B inside A must not be empty"
685 );
686 }
687
688 #[test]
691 fn test_mesh_boolean_dispatch() {
692 let a = make_box(Point3f::new(0.0, 0.0, 0.0), Point3f::new(2.0, 2.0, 2.0));
693 let b = make_box(Point3f::new(1.0, 1.0, 1.0), Point3f::new(3.0, 3.0, 3.0));
694
695 let u = mesh_boolean(&a, &b, BooleanOp::Union).unwrap();
696 let i = mesh_boolean(&a, &b, BooleanOp::Intersection).unwrap();
697 let d = mesh_boolean(&a, &b, BooleanOp::Difference).unwrap();
698
699 assert!(!u.is_empty());
700 assert!(!i.is_empty());
701 assert!(!d.is_empty());
702 }
703}