iron_shapes/concepts.rs
1// SPDX-FileCopyrightText: 2022 Thomas Kramer
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5//! Abstraction concepts over geometric primitives.
6//!
7//! # References
8//! * Inspired by [the Boost polygon library](https://www.boost.org/doc/libs/1_78_0/libs/polygon/doc/gtl_design_overview.htm),
9//! [archived](https://web.archive.org/web/20220524201016/https://www.boost.org/doc/libs/1_78_0/libs/polygon/doc/gtl_design_overview.htm)
10//! * and ["Geometry Template Library for STL-like 2D Operations"](https://www.boost.org/doc/libs/1_44_0/libs/polygon/doc/GTL_boostcon2009.pdf),
11//! [archived](https://web.archive.org/web/20220524201159/https://www.boost.org/doc/libs/1_44_0/libs/polygon/doc/GTL_boostcon2009.pdf)
12
13use crate::isotropy::*;
14use num_traits::{Float, Num, Signed};
15
16/// Define a type used for coordinates.
17pub trait CoordinateBase {
18 /// Base coordinate type used for x and y coordinates.
19 type Coord: Num + Signed + Copy + PartialEq + PartialOrd;
20}
21
22impl<T> CoordinateBase for T
23where
24 T: Num + Signed + Copy + PartialEq + PartialOrd,
25{
26 type Coord = T;
27}
28
29/// Define the coordinate concept.
30/// This will be used to parametrize all other geometric concepts.
31pub trait CoordinateConcept: CoordinateBase {
32 /// Type used for area. This is typically a floating point number or a rational number.
33 type Area: Num + Signed + Copy + PartialEq + PartialOrd;
34 /// Type used for area which can be expressed without fractions. The datatype usually has a bigger range than `Coord` to avoid overflows during multiplications.
35 /// For example when using `i32` as `Coord`, a `i64` is recommended as area type.
36 type ManhattanArea: Num + Copy + PartialEq + PartialOrd;
37 /// Type for unsigned area.
38 type UnsignedArea: Num + Copy + PartialEq + PartialOrd;
39 /// Type for difference between coordinates. Typically the same type as `Coord` when `Coord` is signed.
40 type CoordinateDifference: Num + Signed + Copy + PartialEq + From<Self::Coord> + PartialOrd;
41 /// Type for distances. Typically a floating point type because distances cannot be represented in integers nor rationals.
42 type CoordinateDistance: Num
43 + Copy
44 + PartialEq
45 + From<Self::CoordinateDifference>
46 + Float
47 + PartialOrd;
48}
49
50impl CoordinateConcept for i32 {
51 type Area = f64;
52 type ManhattanArea = i64;
53 type UnsignedArea = f64;
54 type CoordinateDifference = i32;
55 type CoordinateDistance = f64;
56}
57
58impl CoordinateConcept for f32 {
59 type Area = f32;
60 type ManhattanArea = f32;
61 type UnsignedArea = f32;
62 type CoordinateDifference = f32;
63 type CoordinateDistance = f32;
64}
65
66impl CoordinateConcept for f64 {
67 type Area = f64;
68 type ManhattanArea = f64;
69 type UnsignedArea = f64;
70 type CoordinateDifference = f64;
71 type CoordinateDistance = f64;
72}
73
74// macro_rules! crd {
75// ($t:ty) => {<<Self as $t>::Coord as CoordinateConcept>}
76// }
77
78/// Basic traits to get and set Kartesian coordinates of a point in the two-dimensional plane.
79pub trait PointBase<C: CoordinateBase> {
80 /// Construct a new point.
81 fn new(x: C::Coord, y: C::Coord) -> Self;
82
83 /// Get a coordinate value.
84 fn get(&self, orient: Orientation2D) -> C::Coord;
85
86 /// Set a coordinate value.
87 fn set(&mut self, orient: Orientation2D, value: C::Coord);
88
89 /// Get the x-coordinate value.
90 fn x(&self) -> C::Coord {
91 self.get(Orientation2D::Horizontal)
92 }
93
94 /// Get the y-coordinate value.
95 fn y(&self) -> C::Coord {
96 self.get(Orientation2D::Vertical)
97 }
98}
99
100/// Concept of a point in the Euclidean plane.
101pub trait PointConcept<C: CoordinateConcept>: PointBase<C> {
102 /// Compute the x or y component of the vector from the point to the `other` point.
103 fn projected_distance(&self, other: &Self, orient: Orientation2D) -> C::CoordinateDifference {
104 let a: C::CoordinateDifference = self.get(orient).into();
105 let b: C::CoordinateDifference = other.get(orient).into();
106 b - a
107 }
108
109 /// Compute the 1-norm of the vector pointing from the point to the other.
110 fn manhattan_distance(&self, other: &Self) -> C::CoordinateDifference {
111 self.projected_distance(other, Orientation2D::Horizontal)
112 + self.projected_distance(other, Orientation2D::Vertical)
113 }
114
115 /// Squared euclidean distance.
116 fn distance_squared(&self, other: &Self) -> C::CoordinateDistance {
117 let d_horiz: C::CoordinateDistance = self
118 .projected_distance(other, Orientation2D::Horizontal)
119 .into();
120 let d_vert: C::CoordinateDistance = self
121 .projected_distance(other, Orientation2D::Vertical)
122 .into();
123
124 d_horiz * d_horiz + d_vert * d_vert
125 }
126
127 /// Euclidean distance, i.e. 2-norm of the vector from the point to the other.
128 fn euclidian_distance(&self, other: &Self) -> C::CoordinateDistance {
129 self.distance_squared(other).sqrt()
130 }
131}
132
133/// A polygon edge.
134pub trait Segment<C: CoordinateConcept> {
135 /// Type used to represent the end points of the segment.
136 type Point: PointConcept<C>;
137
138 /// Get the start (LOW) or end (HIGH) point of the segment.
139 fn get_point(&self, dir: Direction1D) -> Self::Point;
140
141 /// Shortcut to get the 'low' end of the segment.
142 fn start(&self) -> Self::Point {
143 self.get_point(Direction1D::Low)
144 }
145
146 /// Shortcut to get the 'high' end of the segment.
147 fn end(&self) -> Self::Point {
148 self.get_point(Direction1D::High)
149 }
150}
151
152/// Convert a polygon into an iterator over polygon segments.
153pub trait IntoSegments<C: CoordinateConcept> {
154 /// Type which represents the segments.
155 type Segment: Segment<C>;
156 /// Iterator over segments.
157 type SegmentIter: Iterator<Item = Self::Segment>;
158
159 /// Iterate over segments/edges of a polygon.
160 fn into_segments(self) -> Self::SegmentIter;
161}
162
163/// Loop over points/vertices.
164pub trait IntoPoints<C: CoordinateConcept> {
165 /// Type of the points.
166 type Point: PointConcept<C>;
167 /// Iterator over points.
168 type PointIter: Iterator<Item = Self::Point>;
169
170 /// Iterate over points.
171 fn into_points(self) -> Self::PointIter;
172}
173
174/// Describe an interval of coordinates by a start value and an end value.
175pub trait Interval<Coord>
176where
177 Coord: Copy,
178{
179 /// Get the low or high end.
180 fn get(&self, d: Direction1D) -> Coord;
181
182 /// Get the low end.
183 fn start(&self) -> Coord {
184 self.get(Direction1D::Low)
185 }
186
187 /// Get the high end.
188 fn end(&self) -> Coord {
189 self.get(Direction1D::High)
190 }
191}
192
193/// Concept of an axis-aligned rectangle.
194pub trait Rectangle<C: CoordinateConcept>: Polygon90<C> {
195 /// Type used for representing a one-dimensional interval.
196 type Interval: Interval<C::Coord>;
197
198 /// Get the interval which is spanned by the rectangle in the given orientation.
199 fn get(&self, orientation: Orientation2D) -> Self::Interval;
200
201 // fn lower_left(&self) -> <Self as PolygonSet>::Point;
202 // fn upper_right(&self) -> <Self as PolygonSet>::Point;
203 //
204 // fn lower_right(&self) -> <Self as PolygonSet>::Point {
205 // let ll = self.lower_left();
206 // let ur = self.upper_right();
207 //
208 // <Self as PolygonSet>::Point::new(ur.x(), ll.y())
209 // }
210}
211
212/// Concept of a polygon with axis-aligned edges. The polygon consists of a single closed loop of vertices, i.e. has no holes.
213pub trait Polygon90<C: CoordinateConcept>: Polygon<C> + Polygon90WithHoles<C> {
214 /// Iterator over alternating x/y coordinates of the points. Starts with an x coordinate.
215 type CompactIterator: Iterator<Item = C::Coord>;
216
217 /// Iterate over alternating x/y coordinates of the polygon vertices. Start with an x coordinate.
218 fn compact_iter(&self) -> Self::CompactIterator;
219
220 // /// Set the points from a compact iterator.
221 // fn set_from_compact(&mut self, iter: Self::CompactIterator) {
222 // self.set(iter);
223 // todo!();
224 // }
225}
226
227/// Concept of a polygon which consists of a single closed loop of vertices.
228pub trait Polygon<C: CoordinateConcept>: PolygonWithHoles<C> + IntoPoints<C> {
229 /// Set points from an iterator.
230 fn set(&mut self, iter: impl Iterator<Item = <Self as PolygonSet<C>>::Point>);
231}
232
233/// Concept of a polygon with axis-aligned edges and holes.
234pub trait Polygon90WithHoles<C: CoordinateConcept>: PolygonWithHoles<C> + Polygon90Set<C> {}
235
236/// Concept of a polygon with holes.
237pub trait PolygonWithHoles<C: CoordinateConcept>: PolygonSet<C> {
238 /// Get the number of holes.
239 fn num_holes(&self) -> usize;
240}
241
242/// Set of multiple polygons with axis-aligned edges.
243pub trait Polygon90Set<C: CoordinateConcept>: PolygonSet<C> {}
244
245/// Set of multiple polygons edges.
246pub trait PolygonSet<C: CoordinateConcept>: IntoSegments<C> {
247 /// Point type used for the vertices.
248 type Point: PointConcept<C>;
249 /// Type used for the polygon segments.
250 type Segment: Segment<C, Point = Self::Point>;
251 // type Polygon: PolygonWithHoles;
252 // type PolygonIter: Iterator<Item=Self::Polygon>;
253 /// Iterator over all points.
254 type AllPoints: Iterator<Item = Self::Point>;
255
256 /// Get number of polygons.
257 fn num_polygons(&self) -> usize;
258
259 /// Add the point `p` to all vertices.
260 fn convolved(self, p: &Self::Point) -> Self;
261
262 /// Add the point `p` to all vertices.
263 fn convolve(&mut self, p: &Self::Point);
264
265 /// Multiply all vertices with a factor.
266 fn scaled(self, scale: C::Coord) -> Self;
267
268 /// Multiply all vertices with a factor.
269 fn scale(&mut self, scale: C::Coord);
270
271 /// Iterate over all vertices.
272 fn all_points(&self) -> Self::AllPoints;
273}