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::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#[derive(Clone, Debug)]
26pub struct SPolygon {
27 pub(crate) vertices: Vec<Point>,
29 pub(crate) bbox: Rect,
31 pub(crate) area: f32,
33 pub(crate) diameter: f32,
35 pub(crate) poi: Circle,
37 pub(crate) surrogate: Option<SPSurrogate>,
39}
40
41impl SPolygon {
42 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 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 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 let ch = convex_hull_from_points(points);
124
125 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 #[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 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 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 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 #[must_use]
226 pub fn vertices(&self) -> &[Point] {
227 &self.vertices
228 }
229
230 #[must_use]
232 pub fn bbox(&self) -> Rect {
233 self.bbox
234 }
235
236 #[must_use]
238 pub fn area(&self) -> f32 {
239 self.area
240 }
241
242 #[must_use]
244 pub fn diameter(&self) -> f32 {
245 self.diameter
246 }
247
248 #[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 let SPolygon {
260 vertices: points,
261 bbox,
262 area: _,
263 diameter: _,
264 poi,
265 surrogate,
266 } = self;
267
268 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 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 *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 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 if let Some(surrogate) = surrogate.as_mut() {
321 let ref_surrogate = reference.surrogate();
322 surrogate.transform_from(ref_surrogate, t);
323 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 *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 if self.bbox.collides_with(point) {
346 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#[inline(always)]
368pub(crate) fn ray_crosses(edge: &Edge, point: &Point, ray: &Edge) -> bool {
369 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 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}