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
49 let area = match SPolygon::calculate_area(&points) {
50 0.0 => bail!("Simple polygon has no area: {points:?}"),
51 area if area < 0.0 => {
52 points.reverse();
54 -area
55 }
56 area => area,
57 };
58
59 let diameter = SPolygon::calculate_diameter(points.clone());
60 let bbox = SPolygon::generate_bounding_box(&points);
61 let poi = SPolygon::calculate_poi(&points, diameter)?;
62
63 Ok(SPolygon {
64 vertices: points,
65 bbox,
66 area,
67 diameter,
68 poi,
69 surrogate: None,
70 })
71 }
72
73 pub fn generate_surrogate(&mut self, config: SPSurrogateConfig) -> Result<()> {
74 match &self.surrogate {
76 Some(surrogate) if surrogate.config == config => {}
77 _ => self.surrogate = Some(SPSurrogate::new(self, config)?),
78 }
79 Ok(())
80 }
81
82 pub fn vertex(&self, i: usize) -> Point {
83 self.vertices[i]
84 }
85
86 pub fn edge(&self, i: usize) -> Edge {
87 assert!(i < self.n_vertices(), "index out of bounds");
88 let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
89 Edge {
90 start: self.vertices[i],
91 end: self.vertices[j],
92 }
93 }
94
95 pub fn edge_iter(&self) -> impl Iterator<Item = Edge> + '_ {
96 (0..self.n_vertices()).map(move |i| self.edge(i))
97 }
98
99 pub fn n_vertices(&self) -> usize {
100 self.vertices.len()
101 }
102
103 pub fn surrogate(&self) -> &SPSurrogate {
104 self.surrogate.as_ref().expect("surrogate not generated")
105 }
106
107 pub fn calculate_diameter(points: Vec<Point>) -> f32 {
108 let ch = convex_hull_from_points(points);
110
111 let sq_diam = ch
113 .iter()
114 .tuple_combinations()
115 .map(|(p1, p2)| p1.sq_distance_to(p2))
116 .max_by_key(|sq_d| NotNan::new(*sq_d).unwrap())
117 .expect("convex hull is empty");
118
119 sq_diam.sqrt()
120 }
121
122 pub fn generate_bounding_box(points: &[Point]) -> Rect {
123 let (mut x_min, mut y_min) = (f32::MAX, f32::MAX);
124 let (mut x_max, mut y_max) = (f32::MIN, f32::MIN);
125
126 for point in points.iter() {
127 x_min = x_min.min(point.0);
128 y_min = y_min.min(point.1);
129 x_max = x_max.max(point.0);
130 y_max = y_max.max(point.1);
131 }
132 Rect::try_new(x_min, y_min, x_max, y_max).unwrap()
133 }
134
135 pub fn calculate_area(points: &[Point]) -> f32 {
138 let mut sigma: f32 = 0.0;
139 for i in 0..points.len() {
140 let j = (i + 1) % points.len();
142
143 let (x_i, y_i) = points[i].into();
144 let (x_j, y_j) = points[j].into();
145
146 sigma += (y_i + y_j) * (x_i - x_j)
147 }
148
149 0.5 * sigma
150 }
151
152 pub fn calculate_poi(points: &[Point], diameter: f32) -> Result<Circle> {
153 let dummy_sp = {
156 let bbox = SPolygon::generate_bounding_box(points);
157 let area = SPolygon::calculate_area(points);
158 let dummy_poi = Circle::try_new(Point(f32::MAX, f32::MAX), f32::MAX).unwrap();
159
160 SPolygon {
161 vertices: points.to_vec(),
162 bbox,
163 area,
164 diameter,
165 poi: dummy_poi,
166 surrogate: None,
167 }
168 };
169
170 compute_pole(&dummy_sp, &[])
171 }
172
173 pub fn centroid(&self) -> Point {
174 let area = self.area;
177 let mut c_x = 0.0;
178 let mut c_y = 0.0;
179
180 for i in 0..self.n_vertices() {
181 let j = if i == self.n_vertices() - 1 { 0 } else { i + 1 };
182 let Point(x_i, y_i) = self.vertex(i);
183 let Point(x_j, y_j) = self.vertex(j);
184 c_x += (x_i + x_j) * (x_i * y_j - x_j * y_i);
185 c_y += (y_i + y_j) * (x_i * y_j - x_j * y_i);
186 }
187
188 c_x /= 6.0 * area;
189 c_y /= 6.0 * area;
190
191 (c_x, c_y).into()
192 }
193}
194
195impl Transformable for SPolygon {
196 fn transform(&mut self, t: &Transformation) -> &mut Self {
197 let SPolygon {
199 vertices: points,
200 bbox,
201 area: _,
202 diameter: _,
203 poi,
204 surrogate,
205 } = self;
206
207 points.iter_mut().for_each(|p| {
209 p.transform(t);
210 });
211
212 poi.transform(t);
213
214 if let Some(surrogate) = surrogate.as_mut() {
216 surrogate.transform(t);
217 }
218
219 *bbox = SPolygon::generate_bounding_box(points);
221
222 self
223 }
224}
225
226impl TransformableFrom for SPolygon {
227 fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
228 let SPolygon {
230 vertices: points,
231 bbox,
232 area: _,
233 diameter: _,
234 poi,
235 surrogate,
236 } = self;
237
238 for (p, ref_p) in points.iter_mut().zip(&reference.vertices) {
239 p.transform_from(ref_p, t);
240 }
241
242 poi.transform_from(&reference.poi, t);
243
244 if let Some(surrogate) = surrogate.as_mut() {
246 surrogate.transform_from(reference.surrogate(), t);
247 }
248 *bbox = SPolygon::generate_bounding_box(points);
250
251 self
252 }
253}
254
255impl CollidesWith<Point> for SPolygon {
256 fn collides_with(&self, point: &Point) -> bool {
257 match self.bbox.collides_with(point) {
259 false => false,
260 true => {
261 let point_outside = Point(self.bbox.x_max + self.bbox.width(), point.1);
264 let ray = Edge {
265 start: *point,
266 end: point_outside,
267 };
268
269 let mut n_intersections = 0;
270 for edge in self.edge_iter() {
271 let (s_x, s_y) = (FPA(edge.start.0), FPA(edge.start.1));
275 let (e_x, e_y) = (FPA(edge.end.0), FPA(edge.end.1));
276 let (p_x, p_y) = (FPA(point.0), FPA(point.1));
277
278 if (s_y == p_y && s_x > p_x) || (e_y == p_y && e_x > p_x) {
279 if s_y < p_y || e_y < p_y {
282 n_intersections += 1;
283 }
284 } else if ray.collides_with(&edge) {
285 n_intersections += 1;
286 }
287 }
288 n_intersections % 2 == 1
289 }
290 }
291 }
292}
293
294impl DistanceTo<Point> for SPolygon {
295 fn distance_to(&self, point: &Point) -> f32 {
296 self.sq_distance_to(point).sqrt()
297 }
298 fn sq_distance_to(&self, point: &Point) -> f32 {
299 match self.collides_with(point) {
300 true => 0.0,
301 false => self
302 .edge_iter()
303 .map(|edge| edge.sq_distance_to(point))
304 .min_by(|a, b| a.partial_cmp(b).unwrap())
305 .unwrap(),
306 }
307 }
308}
309
310impl SeparationDistance<Point> for SPolygon {
311 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
312 let (position, sq_distance) = self.sq_separation_distance(point);
313 (position, sq_distance.sqrt())
314 }
315
316 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
317 let distance_to_closest_edge = self
318 .edge_iter()
319 .map(|edge| edge.sq_distance_to(point))
320 .min_by_key(|sq_d| OrderedFloat(*sq_d))
321 .unwrap();
322
323 match self.collides_with(point) {
324 true => (GeoPosition::Interior, distance_to_closest_edge),
325 false => (GeoPosition::Exterior, distance_to_closest_edge),
326 }
327 }
328}
329
330impl<T> From<T> for SPolygon
331where
332 T: Borrow<Rect>,
333{
334 fn from(r: T) -> Self {
335 let r = r.borrow();
336 SPolygon::new(vec![
337 (r.x_min, r.y_min).into(),
338 (r.x_max, r.y_min).into(),
339 (r.x_max, r.y_max).into(),
340 (r.x_min, r.y_max).into(),
341 ])
342 .unwrap()
343 }
344}