jagua_rs/geometry/primitives/
simple_polygon.rs1use 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::Point;
16use crate::geometry::primitives::Rect;
17use crate::util::FPA;
18use anyhow::{Result, bail};
19
20#[derive(Clone, Debug)]
24pub struct SPolygon {
25 pub vertices: Vec<Point>,
27 pub bbox: Rect,
29 pub area: f32,
31 pub diameter: f32,
33 pub poi: Circle,
35 pub surrogate: Option<SPSurrogate>,
37}
38
39impl SPolygon {
40 pub fn new(mut points: Vec<Point>) -> Result<Self> {
42 if points.len() < 3 {
43 bail!("Simple polygon must have at least 3 points: {points:?}");
44 }
45 if points.iter().unique().count() != points.len() {
46 bail!("Simple polygon should not contain duplicate points: {points:?}");
47 }
48 if let Some((e1_idx, e2_idx)) = SPolygon::find_self_intersection(&points) {
49 bail!("Simple polygon contains intersecting edges {e1_idx} and {e2_idx}: {points:?}");
50 }
51
52 let area = match SPolygon::calculate_area(&points) {
53 0.0 => bail!("Simple polygon has no area: {points:?}"),
54 area if area < 0.0 => {
55 points.reverse();
57 -area
58 }
59 area => area,
60 };
61
62 let diameter = SPolygon::calculate_diameter(points.clone());
63 let bbox = SPolygon::generate_bounding_box(&points);
64 let poi = SPolygon::calculate_poi(&points, diameter)?;
65
66 Ok(SPolygon {
67 vertices: points,
68 bbox,
69 area,
70 diameter,
71 poi,
72 surrogate: None,
73 })
74 }
75
76 pub fn generate_surrogate(&mut self, config: SPSurrogateConfig) -> Result<()> {
77 match &self.surrogate {
79 Some(surrogate) if surrogate.config == config => {}
80 _ => self.surrogate = Some(SPSurrogate::new(self, config)?),
81 }
82 Ok(())
83 }
84
85 #[must_use]
86 pub fn vertex(&self, i: usize) -> Point {
87 self.vertices[i]
88 }
89
90 #[must_use]
91 pub fn edge(&self, i: usize) -> Edge {
92 assert!(i < self.n_vertices(), "index out of bounds");
93 let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
94 Edge {
95 start: self.vertices[i],
96 end: self.vertices[j],
97 }
98 }
99
100 pub fn edge_iter(&self) -> impl Iterator<Item = Edge> + '_ {
101 (0..self.n_vertices()).map(move |i| self.edge(i))
102 }
103
104 #[must_use]
105 pub fn n_vertices(&self) -> usize {
106 self.vertices.len()
107 }
108
109 #[must_use]
110 pub fn surrogate(&self) -> &SPSurrogate {
111 self.surrogate.as_ref().expect("surrogate not generated")
112 }
113
114 #[must_use]
115 pub fn calculate_diameter(points: Vec<Point>) -> f32 {
116 let ch = convex_hull_from_points(points);
118
119 let sq_diam = ch
121 .iter()
122 .array_combinations::<2>()
123 .map(|[p1, p2]| p1.sq_distance_to(p2))
124 .max_by_key(|sq_d| NotNan::new(*sq_d).unwrap())
125 .expect("convex hull is empty");
126
127 sq_diam.sqrt()
128 }
129
130 #[must_use]
131 pub fn generate_bounding_box(points: &[Point]) -> Rect {
132 let (mut x_min, mut y_min) = (f32::MAX, f32::MAX);
133 let (mut x_max, mut y_max) = (f32::MIN, f32::MIN);
134
135 for point in points {
136 x_min = x_min.min(point.0);
137 y_min = y_min.min(point.1);
138 x_max = x_max.max(point.0);
139 y_max = y_max.max(point.1);
140 }
141 Rect::try_new(x_min, y_min, x_max, y_max).unwrap()
142 }
143
144 #[must_use]
147 pub fn calculate_area(points: &[Point]) -> f32 {
148 let mut sigma: f32 = 0.0;
149 for i in 0..points.len() {
150 let j = (i + 1) % points.len();
152
153 let (x_i, y_i) = points[i].into();
154 let (x_j, y_j) = points[j].into();
155
156 sigma += (y_i + y_j) * (x_i - x_j);
157 }
158
159 0.5 * sigma
160 }
161
162 pub fn calculate_poi(points: &[Point], diameter: f32) -> Result<Circle> {
163 let dummy_sp = {
166 let bbox = SPolygon::generate_bounding_box(points);
167 let area = SPolygon::calculate_area(points);
168 let dummy_poi = Circle::try_new(Point(f32::MAX, f32::MAX), f32::MAX).unwrap();
169
170 SPolygon {
171 vertices: points.to_vec(),
172 bbox,
173 area,
174 diameter,
175 poi: dummy_poi,
176 surrogate: None,
177 }
178 };
179
180 compute_pole(&dummy_sp, &[])
181 }
182
183 #[must_use]
184 pub fn centroid(&self) -> Point {
185 let area = self.area;
188 let mut c_x = 0.0;
189 let mut c_y = 0.0;
190
191 for i in 0..self.n_vertices() {
192 let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
193 let Point(x_i, y_i) = self.vertex(i);
194 let Point(x_j, y_j) = self.vertex(j);
195 c_x += (x_i + x_j) * (x_i * y_j - x_j * y_i);
196 c_y += (y_i + y_j) * (x_i * y_j - x_j * y_i);
197 }
198
199 c_x /= 6.0 * area;
200 c_y /= 6.0 * area;
201
202 (c_x, c_y).into()
203 }
204
205 fn find_self_intersection(points: &[Point]) -> Option<(usize, usize)> {
206 let edge = |i| Edge {
207 start: points[i],
208 end: points[(i + 1) % points.len()],
209 };
210 let are_neighboring_edges = |i, j| i + 1 == j || (i == 0 && j == points.len() - 1);
211
212 (0..points.len()).combinations(2).find_map(|pair| {
213 let (i, j) = (pair[0], pair[1]);
214 (!are_neighboring_edges(i, j) && edge(i).collides_with(&edge(j))).then_some((i, j))
215 })
216 }
217}
218
219impl Transformable for SPolygon {
220 fn transform(&mut self, t: &Transformation) -> &mut Self {
221 let SPolygon {
223 vertices: points,
224 bbox,
225 area: _,
226 diameter: _,
227 poi,
228 surrogate,
229 } = self;
230
231 for p in points.iter_mut() {
233 p.transform(t);
234 }
235
236 poi.transform(t);
237
238 if let Some(surrogate) = surrogate.as_mut() {
240 surrogate.transform(t);
241 }
242
243 *bbox = SPolygon::generate_bounding_box(points);
245
246 self
247 }
248}
249
250impl TransformableFrom for SPolygon {
251 fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
252 let SPolygon {
254 vertices: points,
255 bbox,
256 area: _,
257 diameter: _,
258 poi,
259 surrogate,
260 } = self;
261
262 for (p, ref_p) in points.iter_mut().zip(&reference.vertices) {
263 p.transform_from(ref_p, t);
264 }
265
266 poi.transform_from(&reference.poi, t);
267
268 if let Some(surrogate) = surrogate.as_mut() {
270 surrogate.transform_from(reference.surrogate(), t);
271 }
272 *bbox = SPolygon::generate_bounding_box(points);
274
275 self
276 }
277}
278
279impl CollidesWith<Point> for SPolygon {
280 fn collides_with(&self, point: &Point) -> bool {
281 if self.bbox.collides_with(point) {
283 let point_outside = Point(self.bbox.x_max + self.bbox.width(), point.1);
286 let ray = Edge {
287 start: *point,
288 end: point_outside,
289 };
290
291 let mut n_intersections = 0;
292 for edge in self.edge_iter() {
293 let (s_x, s_y) = (FPA(edge.start.0), FPA(edge.start.1));
297 let (e_x, e_y) = (FPA(edge.end.0), FPA(edge.end.1));
298 let (p_x, p_y) = (FPA(point.0), FPA(point.1));
299
300 if (s_y == p_y && s_x > p_x) || (e_y == p_y && e_x > p_x) {
301 if s_y < p_y || e_y < p_y {
304 n_intersections += 1;
305 }
306 } else if ray.collides_with(&edge) {
307 n_intersections += 1;
308 }
309 }
310 n_intersections % 2 == 1
311 } else {
312 false
313 }
314 }
315}
316
317impl DistanceTo<Point> for SPolygon {
318 fn distance_to(&self, point: &Point) -> f32 {
319 self.sq_distance_to(point).sqrt()
320 }
321 fn sq_distance_to(&self, point: &Point) -> f32 {
322 if self.collides_with(point) {
323 0.0
324 } else {
325 self.edge_iter()
326 .map(|edge| edge.sq_distance_to(point))
327 .min_by(|a, b| a.partial_cmp(b).unwrap())
328 .unwrap()
329 }
330 }
331}
332
333impl SeparationDistance<Point> for SPolygon {
334 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
335 let (position, sq_distance) = self.sq_separation_distance(point);
336 (position, sq_distance.sqrt())
337 }
338
339 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
340 let distance_to_closest_edge = self
341 .edge_iter()
342 .map(|edge| edge.sq_distance_to(point))
343 .min_by_key(|sq_d| OrderedFloat(*sq_d))
344 .unwrap();
345
346 if self.collides_with(point) {
347 (GeoPosition::Interior, distance_to_closest_edge)
348 } else {
349 (GeoPosition::Exterior, distance_to_closest_edge)
350 }
351 }
352}
353
354impl<T> From<T> for SPolygon
355where
356 T: Borrow<Rect>,
357{
358 fn from(r: T) -> Self {
359 let r = r.borrow();
360 SPolygon::new(vec![
361 (r.x_min, r.y_min).into(),
362 (r.x_max, r.y_min).into(),
363 (r.x_max, r.y_max).into(),
364 (r.x_min, r.y_max).into(),
365 ])
366 .unwrap()
367 }
368}