microcad_core/geo3d/
collection.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! 3D Geometry collection
5
6use std::rc::Rc;
7
8use derive_more::{Deref, DerefMut};
9
10use crate::{
11    geo3d::{CalcBounds3D, bounds::Bounds3D},
12    traits::{DistributeGrid, TotalMemory, VertexCount},
13    *,
14};
15
16/// 3D geometry collection.
17#[derive(Debug, Clone, Default, Deref, DerefMut)]
18pub struct Geometries3D(Vec<Rc<Geometry3D>>);
19
20impl Geometries3D {
21    /// New geometry collection.
22    pub fn new(geometries: Vec<Geometry3D>) -> Self {
23        Self(geometries.into_iter().map(Rc::new).collect())
24    }
25
26    /// Append another geometry collection.
27    pub fn append(&mut self, mut geometries: Geometries3D) {
28        self.0.append(&mut geometries.0)
29    }
30
31    /// Apply boolean operation on collection and render to manifold.
32    pub fn boolean_op(&self, op: &BooleanOp) -> Rc<Manifold> {
33        let manifold_list: Vec<_> = self
34            .0
35            .iter()
36            // Render each geometry into a multipolygon and filter out empty ones
37            .filter_map(|geo| {
38                let manifold: Rc<Manifold> = geo.as_ref().clone().into();
39                if manifold.is_empty() {
40                    None
41                } else {
42                    Some(manifold)
43                }
44            })
45            .collect();
46
47        if manifold_list.is_empty() {
48            return Rc::new(Manifold::empty());
49        }
50
51        manifold_list[1..]
52            .iter()
53            .fold(manifold_list[0].clone(), |acc, other| {
54                Rc::new(acc.boolean_op(other, op.into()))
55            })
56    }
57}
58
59impl FromIterator<Rc<Geometry3D>> for Geometries3D {
60    fn from_iter<T: IntoIterator<Item = Rc<Geometry3D>>>(iter: T) -> Self {
61        Geometries3D(iter.into_iter().collect())
62    }
63}
64
65impl CalcBounds3D for Geometries3D {
66    fn calc_bounds_3d(&self) -> Bounds3D {
67        self.0.iter().fold(Bounds3D::default(), |bounds, geometry| {
68            bounds.extend(geometry.calc_bounds_3d())
69        })
70    }
71}
72
73impl Transformed3D for Geometries3D {
74    fn transformed_3d(&self, mat: &Mat4) -> Self {
75        Self(
76            self.iter()
77                .map(|geometry| Rc::new(geometry.transformed_3d(mat)))
78                .collect::<Vec<_>>(),
79        )
80    }
81}
82
83impl DistributeGrid for Geometries3D {
84    fn distribute_grid(&self, rect: Rect, rows: Integer, columns: Integer) -> Self {
85        Geometries3D(
86            GridCells::new(rect, rows, columns)
87                .zip(self.0.iter())
88                .map(|(cell, geo)| {
89                    let bounds = geo.calc_bounds_3d();
90                    let center = bounds.center();
91                    let cell_center: Vec2 = cell.center().x_y().into();
92                    let d = center - cell_center.extend(bounds.min.z + center.z);
93                    Rc::new(geo.transformed_3d(&Mat4::from_translation(d)))
94                })
95                .collect(),
96        )
97    }
98}
99
100impl TotalMemory for Geometries3D {
101    fn heap_memory(&self) -> usize {
102        self.iter().map(|g| g.heap_memory()).sum()
103    }
104}
105
106impl VertexCount for Geometries3D {
107    fn vertex_count(&self) -> usize {
108        self.iter().map(|g| g.vertex_count()).sum()
109    }
110}