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::{FetchBounds3D, bounds::Bounds3D},
12    *,
13};
14
15/// 3D geometry collection.
16#[derive(Debug, Clone, Default, Deref, DerefMut)]
17pub struct Geometries3D(Vec<Rc<Geometry3D>>);
18
19impl Geometries3D {
20    /// New geometry collection.
21    pub fn new(geometries: Vec<Geometry3D>) -> Self {
22        Self(geometries.into_iter().map(Rc::new).collect())
23    }
24
25    /// Append another geometry collection.
26    pub fn append(&mut self, mut geometries: Geometries3D) {
27        self.0.append(&mut geometries.0)
28    }
29
30    /// Apply boolean operation on collection and render to manifold.
31    pub fn boolean_op(&self, resolution: &RenderResolution, op: &BooleanOp) -> Rc<Manifold> {
32        let manifold_list: Vec<_> = self
33            .0
34            .iter()
35            // Render each geometry into a multipolygon and filter out empty ones
36            .filter_map(|geo| {
37                let manifold = geo.render_to_manifold(resolution);
38                if manifold.is_empty() {
39                    None
40                } else {
41                    Some(manifold)
42                }
43            })
44            .collect();
45
46        if manifold_list.is_empty() {
47            return Rc::new(Manifold::empty());
48        }
49
50        manifold_list[1..]
51            .iter()
52            .fold(manifold_list[0].clone(), |acc, other| {
53                Rc::new(acc.boolean_op(other, op.into()))
54            })
55    }
56
57    /// Convex hull.
58    pub fn hull(&self, resolution: &RenderResolution) -> Manifold {
59        self.render_to_manifold(resolution).hull()
60    }
61}
62
63impl FromIterator<Rc<Geometry3D>> for Geometries3D {
64    fn from_iter<T: IntoIterator<Item = Rc<Geometry3D>>>(iter: T) -> Self {
65        Geometries3D(iter.into_iter().collect())
66    }
67}
68
69impl FetchBounds3D for Geometries3D {
70    fn fetch_bounds_3d(&self) -> Bounds3D {
71        self.0.iter().fold(Bounds3D::default(), |bounds, geometry| {
72            bounds.extend(geometry.fetch_bounds_3d())
73        })
74    }
75}
76
77impl Transformed3D for Geometries3D {
78    fn transformed_3d(&self, render_resolution: &RenderResolution, mat: &Mat4) -> Self {
79        Self(
80            self.iter()
81                .map(|geometry| Rc::new(geometry.transformed_3d(render_resolution, mat)))
82                .collect::<Vec<_>>(),
83        )
84    }
85}
86
87impl RenderToMesh for Geometries3D {
88    fn render_to_manifold(&self, resolution: &RenderResolution) -> std::rc::Rc<Manifold> {
89        self.boolean_op(resolution, &BooleanOp::Union)
90    }
91}