1use std::sync::Arc;
26use std::collections::BinaryHeap;
27use std::cmp::Ordering;
28
29use crate::boolean3;
30use crate::impl_mesh::ManifoldImpl;
31use crate::linalg::{Mat3x4, Vec3, mat3x4_to_mat4, mat4_to_mat3x4};
32use crate::types::{Box as BBox, OpType};
33
34#[derive(Clone)]
39pub struct CsgLeafNode {
40 pub p_impl: Arc<ManifoldImpl>,
41 pub transform: Mat3x4,
42}
43
44impl CsgLeafNode {
45 pub fn new(mesh: ManifoldImpl) -> Self {
47 Self {
48 p_impl: Arc::new(mesh),
49 transform: Mat3x4::identity(),
50 }
51 }
52
53 pub fn with_transform(mesh: ManifoldImpl, transform: Mat3x4) -> Self {
55 Self {
56 p_impl: Arc::new(mesh),
57 transform,
58 }
59 }
60
61 pub fn empty() -> Self {
63 Self {
64 p_impl: Arc::new(ManifoldImpl::new()),
65 transform: Mat3x4::identity(),
66 }
67 }
68
69 pub fn get_impl(&self) -> ManifoldImpl {
72 if self.transform == Mat3x4::identity() {
73 (*self.p_impl).clone()
74 } else {
75 let mut result = (*self.p_impl).clone();
76 result.transform(&self.transform);
77 result
78 }
79 }
80
81 pub fn apply_transform(&self, m: Mat3x4) -> Self {
84 let new_transform = mat4_to_mat3x4(
85 mat3x4_to_mat4(m) * mat3x4_to_mat4(self.transform)
86 );
87 Self {
88 p_impl: Arc::clone(&self.p_impl),
89 transform: new_transform,
90 }
91 }
92
93 pub fn get_bounding_box(&self) -> BBox {
97 let impl_bbox = self.p_impl.bbox;
98 if self.transform == Mat3x4::identity() {
99 return impl_bbox;
100 }
101 let center = (impl_bbox.min + impl_bbox.max) * 0.5;
103 let half = (impl_bbox.max - impl_bbox.min) * 0.5;
104
105 let mat = self.transform;
107 let new_center = Vec3::new(
108 mat[0].x * center.x + mat[1].x * center.y + mat[2].x * center.z + mat[3].x,
109 mat[0].y * center.x + mat[1].y * center.y + mat[2].y * center.z + mat[3].y,
110 mat[0].z * center.x + mat[1].z * center.y + mat[2].z * center.z + mat[3].z,
111 );
112
113 let new_half = Vec3::new(
115 mat[0].x.abs() * half.x + mat[1].x.abs() * half.y + mat[2].x.abs() * half.z,
116 mat[0].y.abs() * half.x + mat[1].y.abs() * half.y + mat[2].y.abs() * half.z,
117 mat[0].z.abs() * half.x + mat[1].z.abs() * half.y + mat[2].z.abs() * half.z,
118 );
119
120 BBox {
121 min: new_center - new_half,
122 max: new_center + new_half,
123 }
124 }
125
126 pub fn num_vert(&self) -> usize {
128 self.p_impl.num_vert()
129 }
130}
131
132#[derive(Clone)]
137pub enum CsgNode {
138 Leaf(CsgLeafNode),
139 Op {
140 op: OpType,
141 children: Vec<CsgNode>,
142 transform: Mat3x4,
143 },
144}
145
146impl CsgNode {
147 pub fn leaf(mesh: ManifoldImpl) -> Self {
148 Self::Leaf(CsgLeafNode::new(mesh))
149 }
150
151 pub fn leaf_node(node: CsgLeafNode) -> Self {
152 Self::Leaf(node)
153 }
154
155 pub fn op(op: OpType, left: CsgNode, right: CsgNode) -> Self {
156 Self::Op {
157 op,
158 children: vec![left, right],
159 transform: Mat3x4::identity(),
160 }
161 }
162
163 pub fn op_n(op: OpType, children: Vec<CsgNode>) -> Self {
164 Self::Op {
165 op,
166 children,
167 transform: Mat3x4::identity(),
168 }
169 }
170
171 pub fn evaluate(&self) -> ManifoldImpl {
175 let leaf = self.to_leaf_node(Mat3x4::identity());
176 leaf.get_impl()
177 }
178
179 fn to_leaf_node(&self, parent_transform: Mat3x4) -> CsgLeafNode {
181 match self {
182 CsgNode::Leaf(leaf) => leaf.apply_transform(parent_transform),
183 CsgNode::Op { op, children, transform } => {
184 let combined = mat4_to_mat3x4(
186 mat3x4_to_mat4(parent_transform) * mat3x4_to_mat4(*transform)
187 );
188
189 let mut positive: Vec<CsgLeafNode> = Vec::new();
191 let mut negative: Vec<CsgLeafNode> = Vec::new();
192
193 self.collect_children(*op, combined, children, &mut positive, &mut negative);
194
195 match op {
197 OpType::Add => {
198 batch_union(&mut positive)
200 }
201 OpType::Intersect => {
202 batch_boolean(OpType::Intersect, &mut positive)
204 }
205 OpType::Subtract => {
206 if positive.is_empty() {
208 return CsgLeafNode::empty();
209 }
210 let pos_result = batch_union(&mut positive);
211 if negative.is_empty() {
212 return pos_result;
213 }
214 let neg_result = batch_union(&mut negative);
215 simple_boolean(&pos_result, &neg_result, OpType::Subtract)
216 }
217 }
218 }
219 }
220 }
221
222 fn collect_children(
225 &self,
226 parent_op: OpType,
227 transform: Mat3x4,
228 children: &[CsgNode],
229 positive: &mut Vec<CsgLeafNode>,
230 negative: &mut Vec<CsgLeafNode>,
231 ) {
232 for (i, child) in children.iter().enumerate() {
233 match child {
234 CsgNode::Leaf(leaf) => {
235 let transformed = leaf.apply_transform(transform);
236 if parent_op == OpType::Subtract && i > 0 {
237 negative.push(transformed);
238 } else {
239 positive.push(transformed);
240 }
241 }
242 CsgNode::Op { op: child_op, children: grandchildren, transform: child_transform } => {
243 let combined = mat4_to_mat3x4(
244 mat3x4_to_mat4(transform) * mat3x4_to_mat4(*child_transform)
245 );
246
247 let can_collapse = match (parent_op, child_op) {
249 (OpType::Add, OpType::Add) => true,
251 (OpType::Intersect, OpType::Intersect) => true,
253 (OpType::Subtract, OpType::Subtract) if i == 0 => true,
255 _ => false,
256 };
257
258 if can_collapse {
259 if parent_op == OpType::Subtract && *child_op == OpType::Subtract && i == 0 {
261 for (gi, gc) in grandchildren.iter().enumerate() {
263 let leaf = gc.to_leaf_node_inner(combined);
264 if gi == 0 {
265 positive.push(leaf);
266 } else {
267 negative.push(leaf);
268 }
269 }
270 } else {
271 for gc in grandchildren {
272 let leaf = gc.to_leaf_node_inner(combined);
273 if parent_op == OpType::Subtract && i > 0 {
274 negative.push(leaf);
275 } else {
276 positive.push(leaf);
277 }
278 }
279 }
280 } else {
281 let result = child.to_leaf_node(combined);
283 if parent_op == OpType::Subtract && i > 0 {
284 negative.push(result);
285 } else {
286 positive.push(result);
287 }
288 }
289 }
290 }
291 }
292 }
293
294 fn to_leaf_node_inner(&self, transform: Mat3x4) -> CsgLeafNode {
296 match self {
297 CsgNode::Leaf(leaf) => leaf.apply_transform(transform),
298 CsgNode::Op { .. } => self.to_leaf_node(transform),
299 }
300 }
301}
302
303fn simple_boolean(a: &CsgLeafNode, b: &CsgLeafNode, op: OpType) -> CsgLeafNode {
309 let impl_a = a.get_impl();
310 let impl_b = b.get_impl();
311 let result = boolean3::boolean(&impl_a, &impl_b, op);
312 CsgLeafNode::new(result)
313}
314
315struct MeshEntry(CsgLeafNode, u64);
325
326impl PartialEq for MeshEntry {
327 fn eq(&self, other: &Self) -> bool {
328 self.cmp(other) == Ordering::Equal
329 }
330}
331impl Eq for MeshEntry {}
332
333impl PartialOrd for MeshEntry {
334 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
335 Some(self.cmp(other))
336 }
337}
338impl Ord for MeshEntry {
339 fn cmp(&self, other: &Self) -> Ordering {
340 self.0
344 .num_vert()
345 .cmp(&other.0.num_vert())
346 .then(self.1.cmp(&other.1))
347 }
348}
349
350fn batch_boolean(op: OpType, children: &mut Vec<CsgLeafNode>) -> CsgLeafNode {
351 if children.is_empty() {
352 return CsgLeafNode::empty();
353 }
354 if children.len() == 1 {
355 return children.remove(0);
356 }
357 if children.len() == 2 {
358 let b = children.pop().unwrap();
359 let a = children.pop().unwrap();
360 return simple_boolean(&a, &b, op);
361 }
362
363 let mut heap: BinaryHeap<MeshEntry> = BinaryHeap::new();
364 let mut next_serial = children.len() as u64;
365 for (i, child) in children.drain(..).enumerate() {
366 heap.push(MeshEntry(child, i as u64));
367 }
368
369 let mut tmp: Vec<MeshEntry> = Vec::new();
373 while heap.len() > 1 {
374 for _ in 0..4 {
375 if heap.len() <= 1 {
376 break;
377 }
378 let a = heap.pop().unwrap();
379 let b = heap.pop().unwrap();
380 let result = simple_boolean(&a.0, &b.0, op);
381 tmp.push(MeshEntry(result, next_serial));
382 next_serial += 1;
383 }
384 for entry in tmp.drain(..) {
385 heap.push(entry);
386 }
387 }
388
389 heap.pop().unwrap().0
390}
391
392const K_MAX_UNION_SIZE: usize = 1000;
398
399fn batch_union(children: &mut Vec<CsgLeafNode>) -> CsgLeafNode {
400 if children.is_empty() {
401 return CsgLeafNode::empty();
402 }
403 if children.len() == 1 {
404 return children.remove(0);
405 }
406
407 while children.len() > 1 {
409 let chunk_size = children.len().min(K_MAX_UNION_SIZE);
410 let chunk_start = children.len() - chunk_size;
411
412 let boxes: Vec<BBox> = children[chunk_start..]
414 .iter()
415 .map(|c| c.get_bounding_box())
416 .collect();
417
418 let mut sets: Vec<Vec<usize>> = Vec::new(); for i in 0..chunk_size {
421 let mut found_set = false;
422 for set in &mut sets {
423 let overlaps = set.iter().any(|&j| boxes[i].does_overlap_box(&boxes[j]));
424 if !overlaps {
425 set.push(i);
426 found_set = true;
427 break;
428 }
429 }
430 if !found_set {
431 sets.push(vec![i]);
432 }
433 }
434
435 let chunk: Vec<CsgLeafNode> = children.drain(chunk_start..).collect();
437 let mut results: Vec<CsgLeafNode> = Vec::new();
438
439 for set in &sets {
440 if set.len() == 1 {
441 results.push(chunk[set[0]].clone());
442 } else {
443 let meshes: Vec<ManifoldImpl> = set.iter()
445 .map(|&i| chunk[i].get_impl())
446 .collect();
447 let composed = boolean3::compose_meshes(&meshes);
448 results.push(CsgLeafNode::new(composed));
449 }
450 }
451
452 let result = batch_boolean(OpType::Add, &mut results);
456 children.push(result);
457 let last = children.len() - 1;
458 children.swap(0, last);
459 }
460
461 children.remove(0)
462}
463
464#[cfg(test)]
469mod tests {
470 use super::*;
471 use crate::linalg::{mat4_to_mat3x4, translation_matrix, Vec3};
472
473 #[test]
474 fn test_csg_tree_union_disjoint() {
475 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
476 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0))));
477 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
478 let result = tree.evaluate();
479 assert_eq!(result.num_tri(), 24);
480 }
481
482 #[test]
483 fn test_csg_tree_union_overlapping() {
484 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
485 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
486 let tree = CsgNode::op(OpType::Add, CsgNode::leaf(a), CsgNode::leaf(b));
487 let result = tree.evaluate();
488 assert!(result.num_tri() > 0, "Overlapping union should produce non-empty mesh");
489 }
490
491 #[test]
492 fn test_csg_tree_intersection() {
493 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
494 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
495 let tree = CsgNode::op(OpType::Intersect, CsgNode::leaf(a), CsgNode::leaf(b));
496 let result = tree.evaluate();
497 assert!(result.num_tri() > 0, "Overlapping intersection should produce non-empty mesh");
498 }
499
500 #[test]
501 fn test_csg_tree_subtract() {
502 let a = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0))));
503 let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0))));
504 let tree = CsgNode::op(OpType::Subtract, CsgNode::leaf(a), CsgNode::leaf(b));
505 let result = tree.evaluate();
506 assert!(result.num_tri() > 0, "Subtraction should produce non-empty mesh");
507 }
508
509 #[test]
510 fn test_batch_boolean_three_cubes() {
511 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
512 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.5, 0.0, 0.0)))));
513 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(1.0, 0.0, 0.0)))));
514 let mut children = vec![a, b, c];
515 let result = batch_boolean(OpType::Add, &mut children);
516 let mesh = result.get_impl();
517 assert!(mesh.num_tri() > 0, "BatchBoolean of 3 overlapping cubes should produce non-empty mesh");
518 }
519
520 #[test]
521 fn test_batch_union_disjoint() {
522 let a = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(0.0, 0.0, 0.0)))));
523 let b = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(3.0, 0.0, 0.0)))));
524 let c = CsgLeafNode::new(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(6.0, 0.0, 0.0)))));
525 let mut children = vec![a, b, c];
526 let result = batch_union(&mut children);
527 let mesh = result.get_impl();
528 assert_eq!(mesh.num_tri(), 36, "BatchUnion of 3 disjoint cubes should have 36 tris");
530 }
531
532 #[test]
533 fn test_csg_n_ary_union() {
534 let nodes: Vec<CsgNode> = (0..4).map(|i| {
536 CsgNode::leaf(ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(
537 Vec3::new(i as f64 * 3.0, 0.0, 0.0)
538 ))))
539 }).collect();
540 let tree = CsgNode::op_n(OpType::Add, nodes);
541 let result = tree.evaluate();
542 assert_eq!(result.num_tri(), 48, "N-ary union of 4 disjoint cubes should have 48 tris");
543 }
544
545 #[test]
546 fn test_tree_transforms() {
547 let a = ManifoldImpl::cube(&Mat3x4::identity());
549 let leaf = CsgLeafNode::new(a);
550 let translated = leaf.apply_transform(
551 mat4_to_mat3x4(translation_matrix(Vec3::new(5.0, 0.0, 0.0)))
552 );
553 let bbox = translated.get_bounding_box();
554 assert!(bbox.min.x > 4.0, "Translated bbox min.x should be > 4.0, got {}", bbox.min.x);
555 assert!(bbox.max.x < 6.5, "Translated bbox max.x should be < 6.5, got {}", bbox.max.x);
556 }
557}