Skip to main content

jagua_rs/geometry/primitives/
simple_polygon.rs

1use std::borrow::Borrow;
2
3use itertools::Itertools;
4use ordered_float::{NotNan, OrderedFloat};
5
6use crate::geometry::Transformation;
7use crate::geometry::convex_hull::convex_hull_from_points;
8use crate::geometry::fail_fast::{SPSurrogate, SPSurrogateConfig, compute_pole};
9use crate::geometry::geo_enums::GeoPosition;
10use crate::geometry::geo_traits::{
11    CollidesWith, DistanceTo, SeparationDistance, Transformable, TransformableFrom,
12};
13use crate::geometry::primitives::Circle;
14use crate::geometry::primitives::Edge;
15use crate::geometry::primitives::GeometryError;
16use crate::geometry::primitives::Point;
17use crate::geometry::primitives::Rect;
18use crate::util::FPA;
19use crate::util::assertions;
20use anyhow::{Result, bail};
21
22/// A Simple Polygon is a polygon that does not intersect itself and contains no holes.
23/// It is a closed shape with a finite number of vertices and edges.
24/// [read more](https://en.wikipedia.org/wiki/Simple_polygon)
25#[derive(Clone, Debug)]
26pub struct SPolygon {
27    /// Vertices in counterclockwise order, including after reflection.
28    pub(crate) vertices: Vec<Point>,
29    /// Bounding box
30    pub(crate) bbox: Rect,
31    /// Area of its interior
32    pub(crate) area: f32,
33    /// Maximum distance between any two points in the polygon
34    pub(crate) diameter: f32,
35    /// [Pole of inaccessibility](https://en.wikipedia.org/wiki/Pole_of_inaccessibility) represented as a circle
36    pub(crate) poi: Circle,
37    /// Optional surrogate representation of the polygon (subset of the original)
38    pub(crate) surrogate: Option<SPSurrogate>,
39}
40
41impl SPolygon {
42    /// Create a new simple polygon from a set of points, expensive operations are performed here! Use [`Self::clone()`] or [`Self::transform()`] to avoid recomputation.
43    pub fn new(mut points: Vec<Point>) -> Result<Self> {
44        if points.len() < 3 {
45            bail!(GeometryError::TooFewPoints(points.len()));
46        }
47        if points.iter().unique().count() != points.len() {
48            let [i, j] = (0..points.len())
49                .array_combinations()
50                .find(|&[i, j]| points[i] == points[j])
51                .expect("a duplicate point exists");
52            bail!(GeometryError::DuplicatePoints(i, j));
53        }
54        if let Some((e1_idx, e2_idx)) = SPolygon::find_self_intersection(&points) {
55            bail!(GeometryError::IntersectingEdges(e1_idx, e2_idx));
56        }
57
58        let area = match SPolygon::calculate_area(&points) {
59            0.0 => bail!(GeometryError::NoArea),
60            area if area < 0.0 => {
61                //edges should always be ordered counterclockwise (positive area)
62                points.reverse();
63                -area
64            }
65            area => area,
66        };
67
68        let diameter = SPolygon::calculate_diameter(points.clone());
69        let bbox = SPolygon::generate_bounding_box(&points);
70        let poi = SPolygon::calculate_poi(&points, diameter)?;
71
72        Ok(SPolygon {
73            vertices: points,
74            bbox,
75            area,
76            diameter,
77            poi,
78            surrogate: None,
79        })
80    }
81
82    pub fn generate_surrogate(&mut self, config: SPSurrogateConfig) -> Result<()> {
83        //regenerate the surrogate if it is not present or if the config has changed
84        match &self.surrogate {
85            Some(surrogate) if surrogate.config == config => {}
86            _ => self.surrogate = Some(SPSurrogate::new(self, config)?),
87        }
88        Ok(())
89    }
90
91    #[must_use]
92    pub fn vertex(&self, i: usize) -> Point {
93        self.vertices[i]
94    }
95
96    #[must_use]
97    pub fn edge(&self, i: usize) -> Edge {
98        assert!(i < self.n_vertices(), "index out of bounds");
99        let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
100        Edge {
101            start: self.vertices[i],
102            end: self.vertices[j],
103        }
104    }
105
106    pub fn edge_iter(&self) -> impl Iterator<Item = Edge> + '_ {
107        (0..self.n_vertices()).map(move |i| self.edge(i))
108    }
109
110    #[must_use]
111    pub fn n_vertices(&self) -> usize {
112        self.vertices.len()
113    }
114
115    #[must_use]
116    pub fn surrogate(&self) -> &SPSurrogate {
117        self.surrogate.as_ref().expect("surrogate not generated")
118    }
119
120    #[must_use]
121    fn calculate_diameter(points: Vec<Point>) -> f32 {
122        //The two points furthest apart must be part of the convex hull
123        let ch = convex_hull_from_points(points);
124
125        //go through all pairs of points and find the pair with the largest distance
126        let sq_diam = ch
127            .iter()
128            .array_combinations::<2>()
129            .map(|[p1, p2]| p1.sq_distance_to(p2))
130            .max_by_key(|sq_d| NotNan::new(*sq_d).unwrap())
131            .expect("convex hull is empty");
132
133        sq_diam.sqrt()
134    }
135
136    #[must_use]
137    pub fn generate_bounding_box(points: &[Point]) -> Rect {
138        let (mut x_min, mut y_min) = (f32::MAX, f32::MAX);
139        let (mut x_max, mut y_max) = (f32::MIN, f32::MIN);
140
141        for point in points {
142            x_min = x_min.min(point.0);
143            y_min = y_min.min(point.1);
144            x_max = x_max.max(point.0);
145            y_max = y_max.max(point.1);
146        }
147        Rect::try_new(x_min, y_min, x_max, y_max).unwrap()
148    }
149
150    //https://en.wikipedia.org/wiki/Shoelace_formula
151    //counterclockwise = positive area, clockwise = negative area
152    #[must_use]
153    pub fn calculate_area(points: &[Point]) -> f32 {
154        let mut sigma: f32 = 0.0;
155        for i in 0..points.len() {
156            //next point
157            let j = (i + 1) % points.len();
158
159            let (x_i, y_i) = points[i].into();
160            let (x_j, y_j) = points[j].into();
161
162            sigma += (y_i + y_j) * (x_i - x_j);
163        }
164
165        0.5 * sigma
166    }
167
168    fn calculate_poi(points: &[Point], diameter: f32) -> Result<Circle> {
169        //need to make a dummy simple polygon, because the pole generation algorithm
170        //relies on many of the methods provided by the simple polygon struct
171        let dummy_sp = {
172            let bbox = SPolygon::generate_bounding_box(points);
173            let area = SPolygon::calculate_area(points);
174            let dummy_poi = Circle::try_new(Point(f32::MAX, f32::MAX), f32::MAX).unwrap();
175
176            SPolygon {
177                vertices: points.to_vec(),
178                bbox,
179                area,
180                diameter,
181                poi: dummy_poi,
182                surrogate: None,
183            }
184        };
185
186        compute_pole(&dummy_sp, &[])
187    }
188
189    #[must_use]
190    pub fn centroid(&self) -> Point {
191        //based on: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
192
193        let area = self.area;
194        let mut c_x = 0.0;
195        let mut c_y = 0.0;
196
197        for i in 0..self.n_vertices() {
198            let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
199            let Point(x_i, y_i) = self.vertex(i);
200            let Point(x_j, y_j) = self.vertex(j);
201            c_x += (x_i + x_j) * (x_i * y_j - x_j * y_i);
202            c_y += (y_i + y_j) * (x_i * y_j - x_j * y_i);
203        }
204
205        c_x /= 6.0 * area;
206        c_y /= 6.0 * area;
207
208        (c_x, c_y).into()
209    }
210
211    fn find_self_intersection(points: &[Point]) -> Option<(usize, usize)> {
212        let edge = |i| Edge {
213            start: points[i],
214            end: points[(i + 1) % points.len()],
215        };
216        let are_neighboring_edges = |i, j| i + 1 == j || (i == 0 && j == points.len() - 1);
217
218        (0..points.len()).combinations(2).find_map(|pair| {
219            let (i, j) = (pair[0], pair[1]);
220            (!are_neighboring_edges(i, j) && edge(i).collides_with(&edge(j))).then_some((i, j))
221        })
222    }
223
224    /// Vertices in counterclockwise order, including after reflection.
225    #[must_use]
226    pub fn vertices(&self) -> &[Point] {
227        &self.vertices
228    }
229
230    /// Bounding box
231    #[must_use]
232    pub fn bbox(&self) -> Rect {
233        self.bbox
234    }
235
236    /// Area of its interior
237    #[must_use]
238    pub fn area(&self) -> f32 {
239        self.area
240    }
241
242    /// Maximum distance between any two points in the polygon
243    #[must_use]
244    pub fn diameter(&self) -> f32 {
245        self.diameter
246    }
247
248    /// [Pole of inaccessibility](https://en.wikipedia.org/wiki/Pole_of_inaccessibility) represented as a circle
249    #[must_use]
250    pub fn poi(&self) -> Circle {
251        self.poi
252    }
253}
254
255impl Transformable for SPolygon {
256    fn transform(&mut self, t: &Transformation) -> &mut Self {
257        let reflected = t.is_reflected();
258        //destructuring pattern to ensure that the code is updated when the struct changes
259        let SPolygon {
260            vertices: points,
261            bbox,
262            area: _,
263            diameter: _,
264            poi,
265            surrogate,
266        } = self;
267
268        //transform all points of the simple poly
269        for p in points.iter_mut() {
270            p.transform(t);
271        }
272        if reflected {
273            points.reverse();
274        }
275
276        poi.transform(t);
277
278        //transform the surrogate
279        if let Some(surrogate) = surrogate.as_mut() {
280            surrogate.transform(t);
281            if reflected {
282                surrogate.reverse_vertex_order(points.len());
283            }
284        }
285
286        //regenerate bounding box
287        *bbox = SPolygon::generate_bounding_box(points);
288
289        self
290    }
291}
292
293impl TransformableFrom for SPolygon {
294    fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
295        let reflected = t.is_reflected();
296        assert_eq!(self.vertices.len(), reference.vertices.len());
297        //destructuring pattern to ensure that the code is updated when the struct changes
298        let SPolygon {
299            vertices: points,
300            bbox,
301            area: _,
302            diameter: _,
303            poi,
304            surrogate,
305        } = self;
306
307        if reflected {
308            for (p, ref_p) in points.iter_mut().zip(reference.vertices.iter().rev()) {
309                p.transform_from(ref_p, t);
310            }
311        } else {
312            for (p, ref_p) in points.iter_mut().zip(&reference.vertices) {
313                p.transform_from(ref_p, t);
314            }
315        }
316
317        poi.transform_from(&reference.poi, t);
318
319        //transform the surrogate
320        if let Some(surrogate) = surrogate.as_mut() {
321            let ref_surrogate = reference.surrogate();
322            surrogate.transform_from(ref_surrogate, t);
323            // The destination was cloned from `reference`, so its hull indices only
324            // need remapping when the vertex storage orientation changes.
325            if surrogate.hull_reversed() != (ref_surrogate.hull_reversed() ^ reflected) {
326                surrogate.reverse_vertex_order(points.len());
327            }
328            debug_assert!(assertions::hull_indices_match_reference(
329                surrogate,
330                ref_surrogate,
331                reflected,
332                points.len()
333            ));
334        }
335        //regenerate bounding box
336        *bbox = SPolygon::generate_bounding_box(points);
337
338        self
339    }
340}
341
342impl CollidesWith<Point> for SPolygon {
343    fn collides_with(&self, point: &Point) -> bool {
344        //based on the ray casting algorithm: https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
345        if self.bbox.collides_with(point) {
346            //horizontal ray shot to the right.
347            //Starting from the point to another point that is certainly outside the shape
348            let point_outside = Point(self.bbox.x_max + self.bbox.width(), point.1);
349            let ray = Edge {
350                start: *point,
351                end: point_outside,
352            };
353
354            let n_intersections = self
355                .edge_iter()
356                .filter(|edge| ray_crosses(edge, point, &ray))
357                .count();
358            n_intersections % 2 == 1
359        } else {
360            false
361        }
362    }
363}
364
365/// Whether `edge` counts as a crossing of `ray`, shot horizontally to the right from `point`,
366/// in the ray casting test of [`SPolygon::collides_with`].
367#[inline(always)]
368pub(crate) fn ray_crosses(edge: &Edge, point: &Point, ray: &Edge) -> bool {
369    //Check if the ray does not go through (or almost through) a vertex
370    //This can result in funky behaviour, which could incorrect results
371    //Therefore we handle this case
372    let (s_x, s_y) = (FPA(edge.start.0), FPA(edge.start.1));
373    let (e_x, e_y) = (FPA(edge.end.0), FPA(edge.end.1));
374    let (p_x, p_y) = (FPA(point.0), FPA(point.1));
375
376    if (s_y == p_y && s_x > p_x) || (e_y == p_y && e_x > p_x) {
377        //in this case, the ray passes through (or dangerously close to) a vertex
378        //We handle this case by only counting an intersection if the edge is below the ray
379        s_y < p_y || e_y < p_y
380    } else {
381        ray.collides_with(edge)
382    }
383}
384
385impl DistanceTo<Point> for SPolygon {
386    fn distance_to(&self, point: &Point) -> f32 {
387        self.sq_distance_to(point).sqrt()
388    }
389    fn sq_distance_to(&self, point: &Point) -> f32 {
390        if self.collides_with(point) {
391            0.0
392        } else {
393            self.edge_iter()
394                .map(|edge| edge.sq_distance_to(point))
395                .min_by(|a, b| a.partial_cmp(b).unwrap())
396                .unwrap()
397        }
398    }
399}
400
401impl SeparationDistance<Point> for SPolygon {
402    fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
403        let (position, sq_distance) = self.sq_separation_distance(point);
404        (position, sq_distance.sqrt())
405    }
406
407    fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
408        let distance_to_closest_edge = self
409            .edge_iter()
410            .map(|edge| edge.sq_distance_to(point))
411            .min_by_key(|sq_d| OrderedFloat(*sq_d))
412            .unwrap();
413
414        if self.collides_with(point) {
415            (GeoPosition::Interior, distance_to_closest_edge)
416        } else {
417            (GeoPosition::Exterior, distance_to_closest_edge)
418        }
419    }
420}
421
422impl<T> From<T> for SPolygon
423where
424    T: Borrow<Rect>,
425{
426    fn from(r: T) -> Self {
427        let r = r.borrow();
428        SPolygon::new(vec![
429            (r.x_min, r.y_min).into(),
430            (r.x_max, r.y_min).into(),
431            (r.x_max, r.y_max).into(),
432            (r.x_min, r.y_max).into(),
433        ])
434        .unwrap()
435    }
436}