iron_shapes/traits.rs
1// Copyright (c) 2018-2020 Thomas Kramer.
2// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Common traits for geometrical objects.
7
8use crate::point::Point;
9use crate::rect::Rect;
10use crate::vector::Vector;
11
12use crate::types::Angle;
13use num_traits::cast::NumCast;
14use num_traits::Zero;
15use std::ops::{Add, Mul, Sub};
16
17/// Calculation of the 'bounding box', i.e. the smallest rectangle that contains the geometrical object.
18pub trait BoundingBox<T> {
19 /// Return the bounding box of this geometry.
20 fn bounding_box(&self) -> Rect<T>;
21}
22
23/// Try the calculation of the 'bounding box', i.e. the smallest rectangle that contains the geometrical object.
24/// In some cases this is not always possible, so the try might fail.
25/// For instance a set of polygons does not have a bounding box if the set is empty.
26pub trait TryBoundingBox<T> {
27 /// Return the bounding box of this geometry if a bounding box is defined.
28 fn try_bounding_box(&self) -> Option<Rect<T>>;
29}
30
31/// Try to compute the bounding box while consuming the data.
32/// This is intended to be used for computing bounding boxes over iterators.
33pub trait TryIntoBoundingBox<T> {
34 /// Return the bounding box of this geometry if such bounding box is defined.
35 fn try_into_bounding_box(self) -> Option<Rect<T>>;
36}
37
38/// Compute the bounding box of many objects that may have a bounding box.
39impl<'a, I, B, T> TryIntoBoundingBox<T> for I
40where
41 I: Iterator<Item = &'a B>,
42 B: TryBoundingBox<T> + 'a,
43 T: Copy + PartialOrd,
44{
45 fn try_into_bounding_box(self) -> Option<Rect<T>> {
46 self.filter_map(|p| p.try_bounding_box())
47 .reduce(|acc, b| acc.add_rect(&b))
48 }
49}
50
51/// Calculate the doubled oriented area of a geometry.
52/// Using the doubled area allows to compute the area without using fractions. This is especially
53/// helpful when computing in integer coordinates.
54///
55/// * `A`: Area type.
56pub trait DoubledOrientedArea<A> {
57 /// Calculate doubled oriented area of this geometry.
58 /// Using the doubled area allows to compute the area without using fractions.
59 fn area_doubled_oriented(&self) -> A;
60}
61
62/// Calculate the area of a geometry.
63pub trait Area<F> {
64 /// Compute the area of a geometrical shape by first computing
65 /// doubled oriented area and then taking absolute value of the half.
66 fn area(&self) -> F;
67}
68
69// impl<T, F: Float + NumCast, DA: DoubledOrientedArea<T>> Area<F> for DA {
70// fn area(&self) -> F {
71// F::abs(F::from(self.area_doubled_oriented()).unwrap() / (F::one() + F::one()))
72// }
73// }
74
75/// Translate the geometrical object by a vector.
76pub trait Translate<T> {
77 /// Translate the geometrical object by a vector `v`.
78 fn translate(&self, v: Vector<T>) -> Self;
79}
80
81/// Scale the geometrical shape. Scaling center is the origin `(0, 0)`.
82pub trait Scale<T> {
83 /// Scale the geometrical shape. Scaling center is the origin `(0, 0)`.
84 fn scale(&self, factor: T) -> Self;
85}
86
87/// Transform the geometrical object by transforming each point of it.
88pub trait MapPointwise<T> {
89 /// Point wise transformation.
90 fn transform<F>(&self, transformation: F) -> Self
91 where
92 F: Fn(Point<T>) -> Point<T>;
93}
94
95impl<S, T> Scale<T> for S
96where
97 T: Copy + Mul<Output = T>,
98 S: MapPointwise<T>,
99{
100 fn scale(&self, factor: T) -> S {
101 self.transform(|p: Point<T>| p * factor)
102 }
103}
104
105impl<S, T> Translate<T> for S
106where
107 T: Copy + Add<Output = T>,
108 S: MapPointwise<T>,
109{
110 fn translate(&self, v: Vector<T>) -> S {
111 self.transform(|p: Point<T>| p + v)
112 }
113}
114
115/// Compute the winding number of a geometrical object around a point.
116/// The winding number is used to check if a point is contained in a shape.
117pub trait WindingNumber<T> {
118 /// Calculate the winding number of the polygon around this point.
119 ///
120 /// TODO: Define how point on edges and vertices is handled.
121 ///
122 /// See: <http://geomalgorithms.com/a03-_inclusion.html>
123 fn winding_number(&self, point: Point<T>) -> isize;
124
125 /// Check if `point` is inside the polygon, i.e. the polygons winds around the point
126 /// a non-zero number of times.
127 ///
128 /// For points on edges the following convention is used:
129 /// Points on left or bottom edges are inside, points on right or top edges outside.
130 fn contains_point_non_oriented(&self, point: Point<T>) -> bool {
131 self.winding_number(point) != 0
132 }
133
134 /// Check if `point` is inside the polygon, i.e. the polygon winds around the point
135 /// an odd number of times.
136 ///
137 /// For points on edges the following convention is used:
138 /// Points on left or bottom edges are inside, points on right or top edges outside.
139 fn contains_point(&self, point: Point<T>) -> bool {
140 (self.winding_number(point) % 2 + 2) % 2 == 1
141 }
142}
143
144/// Rotate by a integer multiple of 90 degrees.
145pub trait RotateOrtho<T> {
146 /// Rotate the geometrical shape by a multiple of 90 degrees.
147 fn rotate_ortho(&self, a: Angle) -> Self;
148}
149
150impl<S, T> RotateOrtho<T> for S
151where
152 T: Copy + Zero + Sub<Output = T>,
153 S: MapPointwise<T>,
154{
155 fn rotate_ortho(&self, a: Angle) -> S {
156 self.transform(|p: Point<T>| match a {
157 Angle::R0 => p,
158 Angle::R90 => Point::new(T::zero() - p.y, p.x),
159 Angle::R180 => Point::zero() - p.v(),
160 Angle::R270 => Point::new(p.y, T::zero() - p.x),
161 })
162 }
163}
164
165/// Mirror at the x or y axis.
166pub trait Mirror<T> {
167 /// Mirror this shape at the `x`-axis.
168 fn mirror_x(&self) -> Self;
169 /// Mirror this shape at the `y`-axis.
170 fn mirror_y(&self) -> Self;
171}
172
173impl<S, T> Mirror<T> for S
174where
175 T: Copy + Zero + Sub<Output = T>,
176 S: MapPointwise<T>,
177{
178 /// Return the geometrical object mirrored at the `x` axis.
179 fn mirror_x(&self) -> S {
180 self.transform(|p| Point::new(p.x, T::zero() - p.y))
181 }
182
183 /// Return the geometrical object mirrored at the `y` axis.
184 fn mirror_y(&self) -> S {
185 self.transform(|p| Point::new(T::zero() - p.x, p.y))
186 }
187}
188
189//
190//pub trait EachPoint<'a, T: 'a>
191// where T: CoordinateType {
192// type EachPoints: Iterator<Item=&'a Point<T>>;
193// fn each_point(&self) -> Self::EachPoints;
194//}
195
196/// This trait defines the type-casting of the coordinate types for geometrical objects.
197pub trait TryCastCoord<Src, Dst>
198where
199 Src: NumCast,
200 Dst: NumCast,
201{
202 /// Output type of the cast. This is likely the same geometrical type just with other
203 /// coordinate types.
204 type Output;
205
206 /// Try to cast to target data type.
207 ///
208 /// Conversion from float to int can fail and will return `None`.
209 /// Float values like infinity or non-a-number
210 /// have no integer representation.
211 fn try_cast(&self) -> Option<Self::Output>;
212
213 /// Cast to target data type.
214 ///
215 /// Conversion from float to int can fail and panic because float values
216 /// like infinity or non-a-number have no integer representation.
217 ///
218 /// # Panics
219 /// Panics if casting of the coordinate values fails. For instance when trying to cast a 'NaN' float into a integer.
220 /// Use `try_cast` to detect and handle this failure.
221 fn cast(&self) -> Self::Output {
222 self.try_cast().unwrap()
223 }
224}