1use std::sync::Arc;
26use std::collections::BinaryHeap;
27use std::cmp::Ordering;
28
29use crate::boolean3;
30use crate::cancel::{is_cancelled, CancelToken};
31use crate::impl_mesh::ManifoldImpl;
32use crate::linalg::{Mat3x4, Vec3, mat3x4_to_mat4, mat4_to_mat3x4};
33use crate::types::{Box as BBox, Error, OpType};
34
35#[derive(Clone)]
40pub struct CsgLeafNode {
41 pub p_impl: Arc<ManifoldImpl>,
42 pub transform: Mat3x4,
43}
44
45impl CsgLeafNode {
46 pub fn new(mesh: ManifoldImpl) -> Self {
48 Self {
49 p_impl: Arc::new(mesh),
50 transform: Mat3x4::identity(),
51 }
52 }
53
54 pub fn with_transform(mesh: ManifoldImpl, transform: Mat3x4) -> Self {
56 Self {
57 p_impl: Arc::new(mesh),
58 transform,
59 }
60 }
61
62 pub fn empty() -> Self {
64 Self {
65 p_impl: Arc::new(ManifoldImpl::new()),
66 transform: Mat3x4::identity(),
67 }
68 }
69
70 fn cancelled() -> Self {
74 let mut imp = ManifoldImpl::new();
75 imp.make_empty(Error::Cancelled);
76 Self {
77 p_impl: Arc::new(imp),
78 transform: Mat3x4::identity(),
79 }
80 }
81
82 pub fn get_impl(&self) -> ManifoldImpl {
85 if self.transform == Mat3x4::identity() {
86 (*self.p_impl).clone()
87 } else {
88 self.p_impl.transform(&self.transform)
92 }
93 }
94
95 pub fn apply_transform(&self, m: Mat3x4) -> Self {
98 let new_transform = mat4_to_mat3x4(
99 mat3x4_to_mat4(m) * mat3x4_to_mat4(self.transform)
100 );
101 Self {
102 p_impl: Arc::clone(&self.p_impl),
103 transform: new_transform,
104 }
105 }
106
107 pub fn get_bounding_box(&self) -> BBox {
111 let impl_bbox = self.p_impl.bbox;
112 if self.transform == Mat3x4::identity() {
113 return impl_bbox;
114 }
115 let center = (impl_bbox.min + impl_bbox.max) * 0.5;
117 let half = (impl_bbox.max - impl_bbox.min) * 0.5;
118
119 let mat = self.transform;
121 let new_center = Vec3::new(
122 mat[0].x * center.x + mat[1].x * center.y + mat[2].x * center.z + mat[3].x,
123 mat[0].y * center.x + mat[1].y * center.y + mat[2].y * center.z + mat[3].y,
124 mat[0].z * center.x + mat[1].z * center.y + mat[2].z * center.z + mat[3].z,
125 );
126
127 let new_half = Vec3::new(
129 mat[0].x.abs() * half.x + mat[1].x.abs() * half.y + mat[2].x.abs() * half.z,
130 mat[0].y.abs() * half.x + mat[1].y.abs() * half.y + mat[2].y.abs() * half.z,
131 mat[0].z.abs() * half.x + mat[1].z.abs() * half.y + mat[2].z.abs() * half.z,
132 );
133
134 BBox {
135 min: new_center - new_half,
136 max: new_center + new_half,
137 }
138 }
139
140 pub fn num_vert(&self) -> usize {
142 self.p_impl.num_vert()
143 }
144}
145
146#[derive(Clone)]
151pub enum CsgNode {
152 Leaf(CsgLeafNode),
153 Op {
154 op: OpType,
155 children: Vec<CsgNode>,
156 transform: Mat3x4,
157 },
158}
159
160impl CsgNode {
161 pub fn leaf(mesh: ManifoldImpl) -> Self {
162 Self::Leaf(CsgLeafNode::new(mesh))
163 }
164
165 pub fn leaf_node(node: CsgLeafNode) -> Self {
166 Self::Leaf(node)
167 }
168
169 pub fn op(op: OpType, left: CsgNode, right: CsgNode) -> Self {
170 Self::Op {
171 op,
172 children: vec![left, right],
173 transform: Mat3x4::identity(),
174 }
175 }
176
177 pub fn op_n(op: OpType, children: Vec<CsgNode>) -> Self {
178 Self::Op {
179 op,
180 children,
181 transform: Mat3x4::identity(),
182 }
183 }
184
185 pub fn evaluate(&self) -> ManifoldImpl {
189 self.evaluate_with_token(None)
190 }
191
192 pub fn evaluate_with_token(&self, token: Option<&CancelToken>) -> ManifoldImpl {
199 let leaf = self.to_leaf_node(Mat3x4::identity(), token);
200 leaf.get_impl()
201 }
202
203 fn to_leaf_node(&self, parent_transform: Mat3x4, token: Option<&CancelToken>) -> CsgLeafNode {
205 if is_cancelled(token) {
209 return CsgLeafNode::cancelled();
210 }
211 match self {
212 CsgNode::Leaf(leaf) => leaf.apply_transform(parent_transform),
213 CsgNode::Op { op, children, transform } => {
214 let combined = mat4_to_mat3x4(
216 mat3x4_to_mat4(parent_transform) * mat3x4_to_mat4(*transform)
217 );
218
219 let mut positive: Vec<CsgLeafNode> = Vec::new();
221 let mut negative: Vec<CsgLeafNode> = Vec::new();
222
223 self.collect_children(*op, combined, children, &mut positive, &mut negative, token);
224
225 match op {
227 OpType::Add => {
228 batch_union(&mut positive, token)
230 }
231 OpType::Intersect => {
232 batch_boolean(OpType::Intersect, &mut positive, token)
234 }
235 OpType::Subtract => {
236 if positive.is_empty() {
238 return if is_cancelled(token) {
242 CsgLeafNode::cancelled()
243 } else {
244 CsgLeafNode::empty()
245 };
246 }
247 let pos_result = batch_union(&mut positive, token);
248 if negative.is_empty() {
249 return pos_result;
250 }
251 let neg_result = batch_union(&mut negative, token);
252 simple_boolean(&pos_result, &neg_result, OpType::Subtract, token)
253 }
254 }
255 }
256 }
257 }
258
259 fn collect_children(
262 &self,
263 parent_op: OpType,
264 transform: Mat3x4,
265 children: &[CsgNode],
266 positive: &mut Vec<CsgLeafNode>,
267 negative: &mut Vec<CsgLeafNode>,
268 token: Option<&CancelToken>,
269 ) {
270 for (i, child) in children.iter().enumerate() {
271 match child {
272 CsgNode::Leaf(leaf) => {
273 let transformed = leaf.apply_transform(transform);
274 if parent_op == OpType::Subtract && i > 0 {
275 negative.push(transformed);
276 } else {
277 positive.push(transformed);
278 }
279 }
280 CsgNode::Op { op: child_op, children: grandchildren, transform: child_transform } => {
281 let combined = mat4_to_mat3x4(
282 mat3x4_to_mat4(transform) * mat3x4_to_mat4(*child_transform)
283 );
284
285 let can_collapse = match (parent_op, child_op) {
287 (OpType::Add, OpType::Add) => true,
289 (OpType::Intersect, OpType::Intersect) => true,
291 (OpType::Subtract, OpType::Subtract) if i == 0 => true,
293 _ => false,
294 };
295
296 if can_collapse {
297 if parent_op == OpType::Subtract && *child_op == OpType::Subtract && i == 0 {
299 for (gi, gc) in grandchildren.iter().enumerate() {
301 let leaf = gc.to_leaf_node_inner(combined, token);
302 if gi == 0 {
303 positive.push(leaf);
304 } else {
305 negative.push(leaf);
306 }
307 }
308 } else {
309 for gc in grandchildren {
310 let leaf = gc.to_leaf_node_inner(combined, token);
311 if parent_op == OpType::Subtract && i > 0 {
312 negative.push(leaf);
313 } else {
314 positive.push(leaf);
315 }
316 }
317 }
318 } else {
319 let result = child.to_leaf_node(combined, token);
321 if parent_op == OpType::Subtract && i > 0 {
322 negative.push(result);
323 } else {
324 positive.push(result);
325 }
326 }
327 }
328 }
329 }
330 }
331
332 fn to_leaf_node_inner(&self, transform: Mat3x4, token: Option<&CancelToken>) -> CsgLeafNode {
334 match self {
335 CsgNode::Leaf(leaf) => leaf.apply_transform(transform),
336 CsgNode::Op { .. } => self.to_leaf_node(transform, token),
337 }
338 }
339}
340
341fn simple_boolean(
347 a: &CsgLeafNode,
348 b: &CsgLeafNode,
349 op: OpType,
350 token: Option<&CancelToken>,
351) -> CsgLeafNode {
352 if is_cancelled(token) {
355 return CsgLeafNode::cancelled();
356 }
357 let impl_a = a.get_impl();
358 let impl_b = b.get_impl();
359 let result = boolean3::boolean_dispatch(
364 &impl_a,
365 &impl_b,
366 op,
367 crate::types::BooleanConfig::default_engine(),
368 token,
369 );
370 CsgLeafNode::new(result)
371}
372
373struct MeshEntry(CsgLeafNode, u64);
383
384impl PartialEq for MeshEntry {
385 fn eq(&self, other: &Self) -> bool {
386 self.cmp(other) == Ordering::Equal
387 }
388}
389impl Eq for MeshEntry {}
390
391impl PartialOrd for MeshEntry {
392 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
393 Some(self.cmp(other))
394 }
395}
396impl Ord for MeshEntry {
397 fn cmp(&self, other: &Self) -> Ordering {
398 self.0
402 .num_vert()
403 .cmp(&other.0.num_vert())
404 .then(self.1.cmp(&other.1))
405 }
406}
407
408fn batch_boolean(
409 op: OpType,
410 children: &mut Vec<CsgLeafNode>,
411 token: Option<&CancelToken>,
412) -> CsgLeafNode {
413 if children.is_empty() {
414 return CsgLeafNode::empty();
415 }
416 if children.len() == 1 {
417 return children.remove(0);
418 }
419 if children.len() == 2 {
420 let b = children.pop().unwrap();
421 let a = children.pop().unwrap();
422 return simple_boolean(&a, &b, op, token);
423 }
424
425 let mut heap: BinaryHeap<MeshEntry> = BinaryHeap::new();
426 let mut next_serial = children.len() as u64;
427 for (i, child) in children.drain(..).enumerate() {
428 heap.push(MeshEntry(child, i as u64));
429 }
430
431 let mut tmp: Vec<MeshEntry> = Vec::new();
435 while heap.len() > 1 {
436 if is_cancelled(token) {
439 return CsgLeafNode::cancelled();
440 }
441 for _ in 0..4 {
442 if heap.len() <= 1 {
443 break;
444 }
445 let a = heap.pop().unwrap();
446 let b = heap.pop().unwrap();
447 let result = simple_boolean(&a.0, &b.0, op, token);
448 tmp.push(MeshEntry(result, next_serial));
449 next_serial += 1;
450 }
451 for entry in tmp.drain(..) {
452 heap.push(entry);
453 }
454 }
455
456 heap.pop().unwrap().0
457}
458
459const K_MAX_UNION_SIZE: usize = 1000;
465
466fn batch_union(children: &mut Vec<CsgLeafNode>, token: Option<&CancelToken>) -> CsgLeafNode {
467 if children.is_empty() {
468 return CsgLeafNode::empty();
469 }
470 if children.len() == 1 {
471 return children.remove(0);
472 }
473
474 while children.len() > 1 {
476 if is_cancelled(token) {
478 return CsgLeafNode::cancelled();
479 }
480 let chunk_size = children.len().min(K_MAX_UNION_SIZE);
481 let chunk_start = children.len() - chunk_size;
482
483 let boxes: Vec<BBox> = children[chunk_start..]
485 .iter()
486 .map(|c| c.get_bounding_box())
487 .collect();
488
489 let mut sets: Vec<Vec<usize>> = Vec::new(); for i in 0..chunk_size {
492 let mut found_set = false;
493 for set in &mut sets {
494 let overlaps = set.iter().any(|&j| boxes[i].does_overlap_box(&boxes[j]));
495 if !overlaps {
496 set.push(i);
497 found_set = true;
498 break;
499 }
500 }
501 if !found_set {
502 sets.push(vec![i]);
503 }
504 }
505
506 let chunk: Vec<CsgLeafNode> = children.drain(chunk_start..).collect();
508 let mut results: Vec<CsgLeafNode> = Vec::new();
509
510 for set in &sets {
511 if set.len() == 1 {
512 results.push(chunk[set[0]].clone());
513 } else {
514 let meshes: Vec<ManifoldImpl> = set.iter()
516 .map(|&i| chunk[i].get_impl())
517 .collect();
518 let composed = boolean3::compose_meshes(&meshes);
519 results.push(CsgLeafNode::new(composed));
520 }
521 }
522
523 let result = batch_boolean(OpType::Add, &mut results, token);
527 children.push(result);
528 let last = children.len() - 1;
529 children.swap(0, last);
530 }
531
532 children.remove(0)
533}
534
535#[cfg(test)]
540mod tests {
541 use super::*;
542 use crate::linalg::{mat4_to_mat3x4, translation_matrix, Vec3};
543
544 #[test]
545 fn test_csg_tree_union_disjoint() {
546 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
547 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0))));
548 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
549 let result = tree.evaluate();
550 assert_eq!(result.num_tri(), 24);
551 }
552
553 #[test]
554 fn test_csg_tree_union_overlapping() {
555 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
556 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
557 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
558 let result = tree.evaluate();
559 assert!(result.num_tri() > 0, "Overlapping union should produce non-empty mesh");
560 }
561
562 #[test]
563 fn test_csg_tree_intersection() {
564 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
565 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
566 let tree = CsgNode::op(OpType::Intersect, CsgNode::leaf(a), CsgNode::leaf(b));
567 let result = tree.evaluate();
568 assert!(result.num_tri() > 0, "Overlapping intersection should produce non-empty mesh");
569 }
570
571 #[test]
572 fn test_csg_tree_subtract() {
573 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
574 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
575 let tree = CsgNode::op(OpType::Subtract, CsgNode::leaf(a), CsgNode::leaf(b));
576 let result = tree.evaluate();
577 assert!(result.num_tri() > 0, "Subtraction should produce non-empty mesh");
578 }
579
580 #[test]
581 fn test_batch_boolean_three_cubes() {
582 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
583 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0)))));
584 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(1.0, 0.0, 0.0)))));
585 let mut children = vec![a, b, c];
586 let result = batch_boolean(OpType::Add, &mut children, None);
587 let mesh = result.get_impl();
588 assert!(mesh.num_tri() > 0, "BatchBoolean of 3 overlapping cubes should produce non-empty mesh");
589 }
590
591 #[test]
592 fn test_batch_union_disjoint() {
593 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
594 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0)))));
595 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(6.0, 0.0, 0.0)))));
596 let mut children = vec![a, b, c];
597 let result = batch_union(&mut children, None);
598 let mesh = result.get_impl();
599 assert_eq!(mesh.num_tri(), 36, "BatchUnion of 3 disjoint cubes should have 36 tris");
601 }
602
603 #[test]
604 fn test_csg_n_ary_union() {
605 let nodes: Vec<CsgNode> = (0..4).map(|i| {
607 CsgNode::leaf(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(
608 Vec3::new(i as f64 * 3.0, 0.0, 0.0)
609 ))))
610 }).collect();
611 let tree = CsgNode::op_n(OpType::Add, nodes);
612 let result = tree.evaluate();
613 assert_eq!(result.num_tri(), 48, "N-ary union of 4 disjoint cubes should have 48 tris");
614 }
615
616 #[test]
617 fn test_lazy_leaf_transform_applied_on_evaluate() {
618 let cube = ManifoldImpl::cube(&Mat3x4::identity());
623 let a = CsgLeafNode::new(cube.clone());
624 let b = CsgLeafNode::new(cube).apply_transform(
625 mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0))),
626 );
627 let bbox = b.get_impl().bbox;
628 assert!(
629 bbox.min.x >= 2.9 && bbox.max.x <= 4.1,
630 "lazy transform not applied by get_impl: bbox.x = [{}, {}]",
631 bbox.min.x,
632 bbox.max.x
633 );
634 let tree = CsgNode::op(
635 OpType::Add,
636 CsgNode::leaf_node(a),
637 CsgNode::leaf_node(b),
638 );
639 assert_eq!(tree.evaluate().num_tri(), 24);
640 }
641
642 #[test]
643 fn test_tree_transforms() {
644 let a = ManifoldImpl::cube(&Mat3x4::identity());
646 let leaf = CsgLeafNode::new(a);
647 let translated = leaf.apply_transform(
648 mat4_to_mat3x4(translation_matrix(Vec3::new(5.0, 0.0, 0.0)))
649 );
650 let bbox = translated.get_bounding_box();
651 assert!(bbox.min.x > 4.0, "Translated bbox min.x should be > 4.0, got {}", bbox.min.x);
652 assert!(bbox.max.x < 6.5, "Translated bbox max.x should be < 6.5, got {}", bbox.max.x);
653 }
654}