microcad_core/geo2d/
collection.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! 2D Geometry collection
5
6use derive_more::{Deref, DerefMut};
7use geo::{CoordsIter, HasDimensions, LineString, Polygon};
8use std::rc::Rc;
9
10use crate::{
11    geo2d::{FetchBounds2D, bounds::Bounds2D},
12    *,
13};
14
15/// 2D geometry collection.
16#[derive(Debug, Clone, Default, Deref, DerefMut)]
17pub struct Geometries2D(Vec<Rc<Geometry2D>>);
18
19impl Geometries2D {
20    /// New geometry collection.
21    pub fn new(geometries: Vec<Geometry2D>) -> 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: Geometries2D) {
27        self.0.append(&mut geometries.0)
28    }
29
30    /// Apply boolean operation to render into MultiPolygon.
31    pub fn boolean_op(&self, resolution: &RenderResolution, op: &BooleanOp) -> geo2d::MultiPolygon {
32        let multi_polygon_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 multi_polygon = geo.render_to_multi_polygon(resolution);
38                if multi_polygon.is_empty() {
39                    None
40                } else {
41                    Some(multi_polygon)
42                }
43            })
44            .collect();
45
46        if multi_polygon_list.is_empty() {
47            return geo2d::MultiPolygon::empty();
48        }
49
50        multi_polygon_list[1..]
51            .iter()
52            .fold(multi_polygon_list[0].clone(), |acc, geo| {
53                use geo::BooleanOps;
54                acc.boolean_op(geo, op.into())
55            })
56    }
57
58    /// Apply contex hull operation to geometries.
59    pub fn hull(&self, resolution: &RenderResolution) -> geo2d::Polygon {
60        let mut coords = self.iter().fold(Vec::new(), |mut coords, geo| {
61            match geo.as_ref() {
62                Geometry2D::LineString(line_string) => {
63                    coords.append(&mut line_string.coords_iter().collect())
64                }
65                Geometry2D::MultiLineString(multi_line_string) => {
66                    coords.append(&mut multi_line_string.coords_iter().collect())
67                }
68                Geometry2D::Polygon(polygon) => {
69                    coords.append(&mut polygon.exterior_coords_iter().collect())
70                }
71                Geometry2D::MultiPolygon(multi_polygon) => {
72                    coords.append(&mut multi_polygon.exterior_coords_iter().collect())
73                }
74                Geometry2D::Rect(rect) => {
75                    let mut rect_corners: Vec<_> = rect.coords_iter().collect();
76                    coords.append(&mut rect_corners)
77                }
78                Geometry2D::Circle(circle) => coords.append(
79                    &mut circle
80                        .clone()
81                        .render_to_polygon(resolution)
82                        .unwrap_or(Polygon::new(LineString(vec![]), vec![]))
83                        .exterior_coords_iter()
84                        .collect(),
85                ),
86                Geometry2D::Line(line) => {
87                    coords.push(line.0.into());
88                    coords.push(line.1.into());
89                }
90                Geometry2D::Collection(collection) => {
91                    coords.append(&mut collection.hull(resolution).exterior_coords_iter().collect())
92                }
93            }
94            coords
95        });
96
97        geo2d::Polygon::new(
98            geo::algorithm::convex_hull::qhull::quick_hull(&mut coords),
99            vec![],
100        )
101    }
102}
103
104impl FromIterator<Rc<Geometry2D>> for Geometries2D {
105    fn from_iter<T: IntoIterator<Item = Rc<Geometry2D>>>(iter: T) -> Self {
106        Geometries2D(iter.into_iter().collect())
107    }
108}
109
110impl FetchBounds2D for Geometries2D {
111    fn fetch_bounds_2d(&self) -> Bounds2D {
112        self.0.iter().fold(Bounds2D::default(), |bounds, geometry| {
113            bounds.extend(geometry.fetch_bounds_2d())
114        })
115    }
116}
117
118impl Transformed2D for Geometries2D {
119    fn transformed_2d(&self, render_resolution: &RenderResolution, mat: &Mat3) -> Self {
120        Self(
121            self.iter()
122                .map(|geometry| Rc::new(geometry.transformed_2d(render_resolution, mat)))
123                .collect::<Vec<_>>(),
124        )
125    }
126}
127
128impl RenderToMultiPolygon for Geometries2D {
129    fn render_to_existing_multi_polygon(
130        &self,
131        resolution: &RenderResolution,
132        polygons: &mut geo2d::MultiPolygon,
133    ) {
134        self.iter().for_each(|geometry| {
135            geometry.render_to_existing_multi_polygon(resolution, polygons);
136        });
137    }
138}