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 std::rc::Rc;
5
6use crate::traits::Align;
7
8use super::*;
9
10use geo::{ConvexHull, MultiPolygon};
11use strum::IntoStaticStr;
12
13/// A 2D Geometry which is independent from resolution.
14#[derive(IntoStaticStr, Clone, Debug)]
15pub enum Geometry2D {
16    /// Line string.
17    LineString(LineString),
18    /// Multiple line strings.
19    MultiLineString(MultiLineString),
20    /// Polygon.
21    Polygon(Polygon),
22    /// Multiple polygons.
23    MultiPolygon(MultiPolygon),
24    /// Rectangle.
25    Rect(Rect),
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(self, other: Self, op: &BooleanOp) -> geo2d::MultiPolygon {
40        use geo::BooleanOps;
41        self.to_multi_polygon()
42            .boolean_op(&other.to_multi_polygon(), op.into())
43    }
44
45    /// Convert geometry to a multi_polygon.
46    pub fn to_multi_polygon(&self) -> MultiPolygon {
47        match self {
48            Geometry2D::Line(_) | Geometry2D::LineString(_) | Geometry2D::MultiLineString(_) => {
49                MultiPolygon::empty()
50            }
51            Geometry2D::Polygon(polygon) => MultiPolygon(vec![polygon.clone()]),
52            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.clone(),
53            Geometry2D::Rect(rect) => MultiPolygon(vec![rect.to_polygon()]),
54            Geometry2D::Collection(collection) => collection.to_multi_polygon(),
55        }
56    }
57
58    /// Apply hull operation.
59    pub fn hull(&self) -> Self {
60        match self {
61            Geometry2D::LineString(line_string) => Geometry2D::Polygon(line_string.convex_hull()),
62            Geometry2D::MultiLineString(multi_line_string) => {
63                Geometry2D::Polygon(multi_line_string.convex_hull())
64            }
65            Geometry2D::Polygon(polygon) => Geometry2D::Polygon(polygon.convex_hull()),
66            Geometry2D::MultiPolygon(multi_polygon) => {
67                Geometry2D::Polygon(multi_polygon.convex_hull())
68            }
69            Geometry2D::Rect(rect) => Geometry2D::Rect(*rect),
70            Geometry2D::Line(line) => Geometry2D::Polygon(
71                LineString::new(vec![line.0.into(), line.1.into()]).convex_hull(),
72            ),
73            Geometry2D::Collection(collection) => Geometry2D::Polygon(collection.hull()),
74        }
75    }
76
77    /// Returns true if this geometry fills an area (e.g. like a polygon or circle).
78    pub fn is_areal(&self) -> bool {
79        !matches!(
80            self,
81            Geometry2D::LineString(_)
82                | Geometry2D::MultiLineString(_)
83                | Geometry2D::Line(_)
84                | Geometry2D::Collection(_)
85        )
86    }
87
88    /// Return this geometry with calculated bounds.
89    pub fn with_bounds(self) -> WithBounds2D<Geometry2D> {
90        let bounds = self.calc_bounds_2d();
91        WithBounds2D {
92            bounds,
93            inner: self,
94        }
95    }
96}
97
98impl CalcBounds2D for MultiPolygon {
99    fn calc_bounds_2d(&self) -> Bounds2D {
100        use geo::BoundingRect;
101        self.bounding_rect().into()
102    }
103}
104
105impl CalcBounds2D for Geometry2D {
106    fn calc_bounds_2d(&self) -> Bounds2D {
107        use geo::BoundingRect;
108
109        match &self {
110            Geometry2D::LineString(line_string) => line_string.bounding_rect().into(),
111            Geometry2D::MultiLineString(multi_line_string) => {
112                multi_line_string.bounding_rect().into()
113            }
114            Geometry2D::Polygon(polygon) => polygon.bounding_rect().into(),
115            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.calc_bounds_2d(),
116            Geometry2D::Rect(rect) => Some(*rect).into(),
117            Geometry2D::Line(line) => line.calc_bounds_2d(),
118            Geometry2D::Collection(collection) => collection.calc_bounds_2d(),
119        }
120    }
121}
122
123impl Transformed2D for Geometry2D {
124    fn transformed_2d(&self, mat: &Mat3) -> Self {
125        if self.is_areal() {
126            let multi_polygon: MultiPolygon = self.clone().into();
127            Self::MultiPolygon(multi_polygon.transformed_2d(mat))
128        } else {
129            match self {
130                Geometry2D::LineString(line_string) => {
131                    Self::LineString(line_string.transformed_2d(mat))
132                }
133                Geometry2D::MultiLineString(multi_line_string) => {
134                    Self::MultiLineString(multi_line_string.transformed_2d(mat))
135                }
136                Geometry2D::Line(line) => Self::Line(line.transformed_2d(mat)),
137                Geometry2D::Collection(geometries) => {
138                    Self::Collection(geometries.transformed_2d(mat))
139                }
140                _ => unreachable!("Geometry type not supported"),
141            }
142        }
143    }
144}
145
146impl Align for Geometry2D {
147    fn align(&self) -> Self {
148        if let Some(bounds) = self.calc_bounds_2d().rect() {
149            let d: Vec2 = bounds.center().x_y().into();
150            self.transformed_2d(&Mat3::from_translation(-d))
151        } else {
152            self.clone()
153        }
154    }
155}
156
157impl From<Geometry2D> for MultiPolygon {
158    fn from(geo: Geometry2D) -> Self {
159        match geo {
160            Geometry2D::Polygon(polygon) => polygon.into(),
161            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon,
162            Geometry2D::Rect(rect) => MultiPolygon(vec![rect.to_polygon()]),
163            Geometry2D::Collection(collection) => collection.into(),
164            _ => MultiPolygon::empty(),
165        }
166    }
167}
168
169/// Something that can rendered into a 2D geometry with a certain resolution.
170pub trait RenderToGeometry2D {
171    /// Render self into some Geometry with a certain render resolution
172    ///
173    /// Note: We might want to have [`RenderCache`] as argument here, hence we return an `Rc`.
174    fn render_to_geometry(&self, resolution: &RenderResolution) -> Rc<Geometry2D>;
175}
176
177impl RenderToGeometry2D for Rc<Geometry2D> {
178    fn render_to_geometry(&self, _: &RenderResolution) -> Rc<Geometry2D> {
179        self.clone()
180    }
181}