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_with_token(&impl_a, &impl_b, op, token);
360 CsgLeafNode::new(result)
361}
362
363struct MeshEntry(CsgLeafNode, u64);
373
374impl PartialEq for MeshEntry {
375 fn eq(&self, other: &Self) -> bool {
376 self.cmp(other) == Ordering::Equal
377 }
378}
379impl Eq for MeshEntry {}
380
381impl PartialOrd for MeshEntry {
382 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
383 Some(self.cmp(other))
384 }
385}
386impl Ord for MeshEntry {
387 fn cmp(&self, other: &Self) -> Ordering {
388 self.0
392 .num_vert()
393 .cmp(&other.0.num_vert())
394 .then(self.1.cmp(&other.1))
395 }
396}
397
398fn batch_boolean(
399 op: OpType,
400 children: &mut Vec<CsgLeafNode>,
401 token: Option<&CancelToken>,
402) -> CsgLeafNode {
403 if children.is_empty() {
404 return CsgLeafNode::empty();
405 }
406 if children.len() == 1 {
407 return children.remove(0);
408 }
409 if children.len() == 2 {
410 let b = children.pop().unwrap();
411 let a = children.pop().unwrap();
412 return simple_boolean(&a, &b, op, token);
413 }
414
415 let mut heap: BinaryHeap<MeshEntry> = BinaryHeap::new();
416 let mut next_serial = children.len() as u64;
417 for (i, child) in children.drain(..).enumerate() {
418 heap.push(MeshEntry(child, i as u64));
419 }
420
421 let mut tmp: Vec<MeshEntry> = Vec::new();
425 while heap.len() > 1 {
426 if is_cancelled(token) {
429 return CsgLeafNode::cancelled();
430 }
431 for _ in 0..4 {
432 if heap.len() <= 1 {
433 break;
434 }
435 let a = heap.pop().unwrap();
436 let b = heap.pop().unwrap();
437 let result = simple_boolean(&a.0, &b.0, op, token);
438 tmp.push(MeshEntry(result, next_serial));
439 next_serial += 1;
440 }
441 for entry in tmp.drain(..) {
442 heap.push(entry);
443 }
444 }
445
446 heap.pop().unwrap().0
447}
448
449const K_MAX_UNION_SIZE: usize = 1000;
455
456fn batch_union(children: &mut Vec<CsgLeafNode>, token: Option<&CancelToken>) -> CsgLeafNode {
457 if children.is_empty() {
458 return CsgLeafNode::empty();
459 }
460 if children.len() == 1 {
461 return children.remove(0);
462 }
463
464 while children.len() > 1 {
466 if is_cancelled(token) {
468 return CsgLeafNode::cancelled();
469 }
470 let chunk_size = children.len().min(K_MAX_UNION_SIZE);
471 let chunk_start = children.len() - chunk_size;
472
473 let boxes: Vec<BBox> = children[chunk_start..]
475 .iter()
476 .map(|c| c.get_bounding_box())
477 .collect();
478
479 let mut sets: Vec<Vec<usize>> = Vec::new(); for i in 0..chunk_size {
482 let mut found_set = false;
483 for set in &mut sets {
484 let overlaps = set.iter().any(|&j| boxes[i].does_overlap_box(&boxes[j]));
485 if !overlaps {
486 set.push(i);
487 found_set = true;
488 break;
489 }
490 }
491 if !found_set {
492 sets.push(vec![i]);
493 }
494 }
495
496 let chunk: Vec<CsgLeafNode> = children.drain(chunk_start..).collect();
498 let mut results: Vec<CsgLeafNode> = Vec::new();
499
500 for set in &sets {
501 if set.len() == 1 {
502 results.push(chunk[set[0]].clone());
503 } else {
504 let meshes: Vec<ManifoldImpl> = set.iter()
506 .map(|&i| chunk[i].get_impl())
507 .collect();
508 let composed = boolean3::compose_meshes(&meshes);
509 results.push(CsgLeafNode::new(composed));
510 }
511 }
512
513 let result = batch_boolean(OpType::Add, &mut results, token);
517 children.push(result);
518 let last = children.len() - 1;
519 children.swap(0, last);
520 }
521
522 children.remove(0)
523}
524
525#[cfg(test)]
530mod tests {
531 use super::*;
532 use crate::linalg::{mat4_to_mat3x4, translation_matrix, Vec3};
533
534 #[test]
535 fn test_csg_tree_union_disjoint() {
536 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
537 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0))));
538 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
539 let result = tree.evaluate();
540 assert_eq!(result.num_tri(), 24);
541 }
542
543 #[test]
544 fn test_csg_tree_union_overlapping() {
545 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
546 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
547 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
548 let result = tree.evaluate();
549 assert!(result.num_tri() > 0, "Overlapping union should produce non-empty mesh");
550 }
551
552 #[test]
553 fn test_csg_tree_intersection() {
554 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
555 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
556 let tree = CsgNode::op(OpType::Intersect, CsgNode::leaf(a), CsgNode::leaf(b));
557 let result = tree.evaluate();
558 assert!(result.num_tri() > 0, "Overlapping intersection should produce non-empty mesh");
559 }
560
561 #[test]
562 fn test_csg_tree_subtract() {
563 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
564 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
565 let tree = CsgNode::op(OpType::Subtract, CsgNode::leaf(a), CsgNode::leaf(b));
566 let result = tree.evaluate();
567 assert!(result.num_tri() > 0, "Subtraction should produce non-empty mesh");
568 }
569
570 #[test]
571 fn test_batch_boolean_three_cubes() {
572 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
573 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0)))));
574 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(1.0, 0.0, 0.0)))));
575 let mut children = vec![a, b, c];
576 let result = batch_boolean(OpType::Add, &mut children, None);
577 let mesh = result.get_impl();
578 assert!(mesh.num_tri() > 0, "BatchBoolean of 3 overlapping cubes should produce non-empty mesh");
579 }
580
581 #[test]
582 fn test_batch_union_disjoint() {
583 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
584 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0)))));
585 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(6.0, 0.0, 0.0)))));
586 let mut children = vec![a, b, c];
587 let result = batch_union(&mut children, None);
588 let mesh = result.get_impl();
589 assert_eq!(mesh.num_tri(), 36, "BatchUnion of 3 disjoint cubes should have 36 tris");
591 }
592
593 #[test]
594 fn test_csg_n_ary_union() {
595 let nodes: Vec<CsgNode> = (0..4).map(|i| {
597 CsgNode::leaf(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(
598 Vec3::new(i as f64 * 3.0, 0.0, 0.0)
599 ))))
600 }).collect();
601 let tree = CsgNode::op_n(OpType::Add, nodes);
602 let result = tree.evaluate();
603 assert_eq!(result.num_tri(), 48, "N-ary union of 4 disjoint cubes should have 48 tris");
604 }
605
606 #[test]
607 fn test_lazy_leaf_transform_applied_on_evaluate() {
608 let cube = ManifoldImpl::cube(&Mat3x4::identity());
613 let a = CsgLeafNode::new(cube.clone());
614 let b = CsgLeafNode::new(cube).apply_transform(
615 mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0))),
616 );
617 let bbox = b.get_impl().bbox;
618 assert!(
619 bbox.min.x >= 2.9 && bbox.max.x <= 4.1,
620 "lazy transform not applied by get_impl: bbox.x = [{}, {}]",
621 bbox.min.x,
622 bbox.max.x
623 );
624 let tree = CsgNode::op(
625 OpType::Add,
626 CsgNode::leaf_node(a),
627 CsgNode::leaf_node(b),
628 );
629 assert_eq!(tree.evaluate().num_tri(), 24);
630 }
631
632 #[test]
633 fn test_tree_transforms() {
634 let a = ManifoldImpl::cube(&Mat3x4::identity());
636 let leaf = CsgLeafNode::new(a);
637 let translated = leaf.apply_transform(
638 mat4_to_mat3x4(translation_matrix(Vec3::new(5.0, 0.0, 0.0)))
639 );
640 let bbox = translated.get_bounding_box();
641 assert!(bbox.min.x > 4.0, "Translated bbox min.x should be > 4.0, got {}", bbox.min.x);
642 assert!(bbox.max.x < 6.5, "Translated bbox max.x should be < 6.5, got {}", bbox.max.x);
643 }
644}