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}