galileo_types/cartesian/traits/
contour.rs

1use std::cmp::Ordering;
2use std::fmt::Debug;
3
4use num_traits::{One, Zero};
5use serde::{Deserialize, Serialize};
6
7use crate::cartesian::traits::cartesian_point::CartesianPoint2d;
8use crate::contour::{ClosedContour, Contour};
9
10/// Methods specific to closed contours in 2d cartesian space. This trait is auto-implemented for all types implementing
11/// [`ClosedContour`] trait and consist of [`CartesianPoint2d`].
12pub trait CartesianClosedContour {
13    /// Type of the contour points.
14    type Point: CartesianPoint2d;
15
16    /// [Signed area](https://en.wikipedia.org/wiki/Signed_area) of the contour.
17    fn area_signed(&self) -> <Self::Point as CartesianPoint2d>::Num
18    where
19        Self: Sized;
20
21    /// Winding direction of the contour.
22    fn winding(&self) -> Winding
23    where
24        Self: Sized;
25}
26
27impl<P, T> CartesianClosedContour for T
28where
29    P: CartesianPoint2d + Copy,
30    T: ClosedContour<Point = P>,
31{
32    type Point = P;
33
34    fn area_signed(&self) -> P::Num
35    where
36        Self: Sized,
37    {
38        let mut prev;
39        let mut iter = self.iter_points_closing();
40        if let Some(p) = iter.next() {
41            prev = p;
42        } else {
43            return P::Num::zero();
44        }
45
46        let mut aggr = P::Num::zero();
47
48        for p in iter {
49            aggr = aggr + prev.x() * p.y() - p.x() * prev.y();
50            prev = p;
51        }
52
53        aggr / (P::Num::one() + P::Num::one())
54    }
55
56    fn winding(&self) -> Winding
57    where
58        Self: Sized,
59    {
60        if self.area_signed() <= P::Num::zero() {
61            Winding::Clockwise
62        } else {
63            Winding::CounterClockwise
64        }
65    }
66}
67
68/// [Winding](https://en.wikipedia.org/wiki/Winding_number) direction of the contour.
69#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash, Deserialize, Serialize)]
70pub enum Winding {
71    /// Positive winding.
72    Clockwise,
73    /// Negative winding.
74    CounterClockwise,
75}
76
77/// Methods for contours in 2d cartesian space. This trait is auto-implemented if applicable.
78pub trait CartesianContour<P: CartesianPoint2d + Copy>: Contour<Point = P> {
79    /// Squared distance from the point to the closest segment of the contour.
80    fn distance_to_point_sq<Point>(&self, point: &Point) -> Option<P::Num>
81    where
82        Self: Sized,
83        Point: CartesianPoint2d<Num = P::Num>,
84    {
85        self.iter_segments()
86            .map(|v| v.distance_to_point_sq(point))
87            .min_by(move |a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
88    }
89}
90
91impl<T: Contour<Point = P>, P: CartesianPoint2d + Copy> CartesianContour<P> for T {}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::cartesian::impls::Point2;
97    use crate::contour::Contour;
98    use crate::impls::ClosedContour;
99    use crate::segment::Segment;
100
101    #[test]
102    fn iter_points_closing() {
103        let contour =
104            crate::impls::Contour::open(vec![Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)]);
105        assert_eq!(contour.iter_points_closing().count(), 2);
106        assert_eq!(
107            contour.iter_points_closing().last().unwrap(),
108            Point2::new(1.0, 1.0)
109        );
110
111        let contour = ClosedContour {
112            points: vec![Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)],
113        };
114        assert_eq!(contour.iter_points_closing().count(), 3);
115        assert_eq!(
116            contour.iter_points_closing().last().unwrap(),
117            Point2::new(0.0, 0.0)
118        );
119    }
120
121    #[test]
122    fn iter_segments() {
123        let contour = crate::impls::Contour::open(vec![Point2::new(0.0, 0.0)]);
124        assert_eq!(contour.iter_segments().count(), 0);
125
126        let contour =
127            crate::impls::Contour::open(vec![Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)]);
128        assert_eq!(contour.iter_segments().count(), 1);
129        assert_eq!(
130            contour.iter_segments().last().unwrap(),
131            Segment(Point2::new(0.0, 0.0), Point2::new(1.0, 1.0))
132        );
133
134        let contour = ClosedContour {
135            points: vec![Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)],
136        };
137        assert_eq!(contour.iter_segments().count(), 2);
138        assert_eq!(
139            contour.iter_segments().last().unwrap(),
140            Segment(Point2::new(1.0, 1.0), Point2::new(0.0, 0.0))
141        );
142    }
143
144    #[test]
145    fn distance_to_point() {
146        let contour = ClosedContour {
147            points: vec![
148                Point2::new(0.0, 0.0),
149                Point2::new(1.0, 1.0),
150                Point2::new(1.0, 0.0),
151            ],
152        };
153
154        assert_eq!(
155            contour.distance_to_point_sq(&Point2::new(0.0, 0.0)),
156            Some(0.0)
157        );
158        assert_eq!(
159            contour.distance_to_point_sq(&Point2::new(0.5, 0.0)),
160            Some(0.0)
161        );
162        assert_eq!(
163            contour.distance_to_point_sq(&Point2::new(0.5, 0.5)),
164            Some(0.0)
165        );
166        assert_eq!(
167            contour.distance_to_point_sq(&Point2::new(0.0, 1.0)),
168            Some(0.5)
169        );
170        assert_eq!(
171            contour.distance_to_point_sq(&Point2::new(2.0, 2.0)),
172            Some(2.0)
173        );
174        assert_eq!(
175            contour.distance_to_point_sq(&Point2::new(-2.0, -2.0)),
176            Some(8.0)
177        );
178    }
179
180    #[test]
181    fn area() {
182        let contour = ClosedContour::new(vec![
183            Point2::new(0.0, 0.0),
184            Point2::new(0.0, 1.0),
185            Point2::new(1.0, 0.0),
186        ]);
187
188        assert_eq!(contour.area_signed(), -0.5);
189
190        let contour = ClosedContour::new(vec![
191            Point2::new(0.0, 0.0),
192            Point2::new(1.0, 0.0),
193            Point2::new(0.0, 1.0),
194        ]);
195
196        assert_eq!(contour.area_signed(), 0.5);
197    }
198
199    #[test]
200    fn winding() {
201        let contour = ClosedContour::new(vec![
202            Point2::new(0.0, 0.0),
203            Point2::new(0.0, 1.0),
204            Point2::new(1.0, 0.0),
205        ]);
206
207        assert_eq!(contour.winding(), Winding::Clockwise);
208
209        let contour = ClosedContour::new(vec![
210            Point2::new(0.0, 0.0),
211            Point2::new(1.0, 0.0),
212            Point2::new(0.0, 1.0),
213        ]);
214
215        assert_eq!(contour.winding(), Winding::CounterClockwise);
216    }
217}