microcad_core/geo2d/
geometry.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::traits::Align;
5
6use super::*;
7
8use geo::ConvexHull;
9use strum::IntoStaticStr;
10
11/// Geometry
12#[derive(IntoStaticStr, Clone, Debug)]
13pub enum Geometry2D {
14    /// Line string.
15    LineString(LineString),
16    /// Multiple line strings.
17    MultiLineString(MultiLineString),
18    /// Polygon.
19    Polygon(Polygon),
20    /// Multiple polygons.
21    MultiPolygon(MultiPolygon),
22    /// Rectangle.
23    Rect(Rect),
24    /// Circle.
25    Circle(Circle),
26    /// Line.
27    Line(Line),
28    /// Collection,
29    Collection(Geometries2D),
30}
31
32impl Geometry2D {
33    /// Return name of geometry.
34    pub fn name(&self) -> &'static str {
35        self.into()
36    }
37
38    /// Apply boolean operation.
39    pub fn boolean_op(
40        &self,
41        resolution: &RenderResolution,
42        other: &Self,
43        op: &BooleanOp,
44    ) -> geo2d::MultiPolygon {
45        use geo::BooleanOps;
46        self.render_to_multi_polygon(resolution)
47            .boolean_op(&other.render_to_multi_polygon(resolution), op.into())
48    }
49
50    /// Apply hull operation.
51    pub fn hull(&self, resolution: &RenderResolution) -> Self {
52        match self {
53            Geometry2D::LineString(line_string) => Geometry2D::Polygon(line_string.convex_hull()),
54            Geometry2D::MultiLineString(multi_line_string) => {
55                Geometry2D::Polygon(multi_line_string.convex_hull())
56            }
57            Geometry2D::Polygon(polygon) => Geometry2D::Polygon(polygon.convex_hull()),
58            Geometry2D::MultiPolygon(multi_polygon) => {
59                Geometry2D::Polygon(multi_polygon.convex_hull())
60            }
61            Geometry2D::Rect(rect) => Geometry2D::Rect(*rect),
62            Geometry2D::Circle(circle) => Geometry2D::Circle(circle.clone()),
63            Geometry2D::Line(line) => Geometry2D::Polygon(
64                LineString::new(vec![line.0.into(), line.1.into()]).convex_hull(),
65            ),
66            Geometry2D::Collection(collection) => Geometry2D::Polygon(collection.hull(resolution)),
67        }
68    }
69
70    /// Returns true if this geometry fills an area (e.g. like a polygon or circle).
71    pub fn is_areal(&self) -> bool {
72        !matches!(
73            self,
74            Geometry2D::LineString(_)
75                | Geometry2D::MultiLineString(_)
76                | Geometry2D::Line(_)
77                | Geometry2D::Collection(_)
78        )
79    }
80}
81
82impl FetchBounds2D for MultiPolygon {
83    fn fetch_bounds_2d(&self) -> Bounds2D {
84        use geo::BoundingRect;
85        self.bounding_rect().into()
86    }
87}
88
89impl FetchBounds2D for Geometry2D {
90    fn fetch_bounds_2d(&self) -> Bounds2D {
91        use geo::BoundingRect;
92
93        match &self {
94            Geometry2D::LineString(line_string) => line_string.bounding_rect().into(),
95            Geometry2D::MultiLineString(multi_line_string) => {
96                multi_line_string.bounding_rect().into()
97            }
98            Geometry2D::Polygon(polygon) => polygon.bounding_rect().into(),
99            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.fetch_bounds_2d(),
100            Geometry2D::Rect(rect) => Some(*rect).into(),
101            Geometry2D::Circle(circle) => circle.fetch_bounds_2d(),
102            Geometry2D::Line(line) => line.fetch_bounds_2d(),
103            Geometry2D::Collection(collection) => collection.fetch_bounds_2d(),
104        }
105    }
106}
107
108impl Transformed2D for Geometry2D {
109    fn transformed_2d(&self, resolution: &RenderResolution, mat: &Mat3) -> Self {
110        if self.is_areal() {
111            Self::MultiPolygon(
112                self.render_to_multi_polygon(resolution)
113                    .transformed_2d(resolution, mat),
114            )
115        } else {
116            match self {
117                Geometry2D::LineString(line_string) => {
118                    Self::LineString(line_string.transformed_2d(resolution, mat))
119                }
120                Geometry2D::MultiLineString(multi_line_string) => {
121                    Self::MultiLineString(multi_line_string.transformed_2d(resolution, mat))
122                }
123                Geometry2D::Line(line) => Self::Line(line.transformed_2d(resolution, mat)),
124                Geometry2D::Collection(geometries) => {
125                    Self::Collection(geometries.transformed_2d(resolution, mat))
126                }
127                _ => unreachable!("Geometry type not supported"),
128            }
129        }
130    }
131}
132
133impl Align for Geometry2D {
134    fn align(&self, resolution: &RenderResolution) -> Self {
135        if let Some(bounds) = self.fetch_bounds_2d().rect() {
136            let d: Vec2 = bounds.center().x_y().into();
137            self.transformed_2d(resolution, &Mat3::from_translation(-d))
138        } else {
139            self.clone()
140        }
141    }
142}
143
144impl RenderToMultiPolygon for Geometry2D {
145    fn render_to_existing_multi_polygon(
146        &self,
147        resolution: &RenderResolution,
148        polygons: &mut MultiPolygon,
149    ) {
150        match self {
151            Geometry2D::Polygon(polygon) => polygons.0.push(polygon.clone()),
152            Geometry2D::MultiPolygon(multi_polygon) => {
153                polygons.0.append(&mut multi_polygon.0.clone())
154            }
155            Geometry2D::Rect(rect) => polygons
156                .0
157                .push(rect.render_to_polygon(resolution).expect("Polygon")),
158            Geometry2D::Circle(circle) => polygons
159                .0
160                .push(circle.render_to_polygon(resolution).expect("Polygon")),
161            Geometry2D::Collection(geometries) => {
162                geometries.render_to_existing_multi_polygon(resolution, polygons);
163            }
164            _ => {}
165        }
166    }
167}