1use itertools::Itertools;
2use log::{debug, info, warn};
3use ordered_float::OrderedFloat;
4use serde::{Deserialize, Serialize};
5use std::cmp::Ordering;
6
7use crate::geometry::geo_traits::CollidesWith;
8use crate::geometry::primitives::Edge;
9use crate::geometry::primitives::Point;
10use crate::geometry::primitives::SPolygon;
11
12use crate::io::ext_repr::ExtSPolygon;
13use crate::io::import;
14use anyhow::{Result, bail};
15
16#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub enum ShapeModifyMode {
20 Inflate,
22 Deflate,
24}
25
26#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq)]
27pub struct ShapeModifyConfig {
28 pub simplify_tolerance: Option<f32>,
32 pub offset: Option<f32>,
36}
37
38pub fn simplify_shape(
44 shape: &SPolygon,
45 mode: ShapeModifyMode,
46 max_area_change_ratio: f32,
47) -> SPolygon {
48 let original_area = shape.area;
49
50 let mut ref_points = shape.vertices.clone();
51
52 for _ in 0..shape.n_vertices() {
53 let n_points = ref_points.len() as isize;
54 if n_points < 4 {
55 break;
57 }
58
59 let mut corners = (0..n_points)
60 .map(|i| {
61 let i_prev = (i - 1).rem_euclid(n_points);
62 let i_next = (i + 1).rem_euclid(n_points);
63 Corner(i_prev as usize, i as usize, i_next as usize)
64 })
65 .collect_vec();
66
67 if mode == ShapeModifyMode::Deflate {
68 corners.reverse();
71 corners.iter_mut().for_each(|c| c.flip());
73 }
74
75 let mut candidates = vec![];
76
77 let mut prev_corner = corners.last().expect("corners is empty");
78 let mut prev_corner_type = CornerType::from(prev_corner.to_points(&ref_points));
79
80 for corner in corners.iter() {
82 let corner_type = CornerType::from(corner.to_points(&ref_points));
83
84 match (&corner_type, &prev_corner_type) {
86 (CornerType::Concave, _) => candidates.push(Candidate::Concave(*corner)),
87 (CornerType::Collinear, _) => candidates.push(Candidate::Collinear(*corner)),
88 (CornerType::Convex, CornerType::Convex) => {
89 candidates.push(Candidate::ConvexConvex(*prev_corner, *corner))
90 }
91 (_, _) => {}
92 };
93 (prev_corner, prev_corner_type) = (corner, corner_type);
94 }
95
96 let best_candidate = candidates
98 .iter()
99 .sorted_by_cached_key(|c| {
100 OrderedFloat(calculate_area_delta(&ref_points, c).unwrap_or(f32::INFINITY))
101 })
102 .find(|c| candidate_is_valid(&ref_points, c));
103
104 if let Some(best_candidate) = best_candidate {
106 let new_shape = execute_candidate(&ref_points, best_candidate);
107 let new_shape_area = SPolygon::calculate_area(&new_shape);
108 let area_delta = (new_shape_area - original_area).abs() / original_area;
109 if area_delta <= max_area_change_ratio {
110 debug!(
111 "Simplified {:?} causing {:.2}% area change",
112 best_candidate,
113 area_delta * 100.0
114 );
115 ref_points = new_shape;
116 } else {
117 break; }
119 } else {
120 break; }
122 }
123
124 let simpl_shape = SPolygon::new(ref_points).unwrap();
126
127 if simpl_shape.n_vertices() < shape.n_vertices() {
128 info!(
129 "[PS] simplified from {} to {} edges with {:.3}% area difference",
130 shape.n_vertices(),
131 simpl_shape.n_vertices(),
132 (simpl_shape.area - shape.area) / shape.area * 100.0
133 );
134 } else {
135 info!("[PS] no simplification possible within area change constraints");
136 }
137
138 simpl_shape
139}
140
141fn calculate_area_delta(shape: &[Point], candidate: &Candidate) -> Result<f32, InvalidCandidate> {
142 let area = match candidate {
144 Candidate::Collinear(_) => 0.0,
145 Candidate::Concave(c) => {
146 let Point(x0, y0) = shape[c.0];
148 let Point(x1, y1) = shape[c.1];
149 let Point(x2, y2) = shape[c.2];
150
151 let area = (x0 * y1 + x1 * y2 + x2 * y0 - x0 * y2 - x1 * y0 - x2 * y1) / 2.0;
152
153 area.abs()
154 }
155 Candidate::ConvexConvex(c1, c2) => {
156 let replacing_vertex = replacing_vertex_convex_convex_candidate(shape, (*c1, *c2))?;
157
158 let Point(x0, y0) = shape[c1.1];
160 let Point(x1, y1) = replacing_vertex;
161 let Point(x2, y2) = shape[c2.1];
162
163 let area = (x0 * y1 + x1 * y2 + x2 * y0 - x0 * y2 - x1 * y0 - x2 * y1) / 2.0;
164
165 area.abs()
166 }
167 };
168 Ok(area)
169}
170
171fn candidate_is_valid(shape: &[Point], candidate: &Candidate) -> bool {
172 match candidate {
174 Candidate::Collinear(_) => true,
175 Candidate::Concave(c) => {
176 let new_edge = Edge::try_new(shape[c.0], shape[c.2]).unwrap();
177 let affected_points = [shape[c.0], shape[c.1], shape[c.2]];
178
179 edge_iter(shape)
181 .filter(|l| !affected_points.contains(&l.start))
182 .filter(|l| !affected_points.contains(&l.end))
183 .all(|l| !l.collides_with(&new_edge))
184 }
185 Candidate::ConvexConvex(c1, c2) => {
186 match replacing_vertex_convex_convex_candidate(shape, (*c1, *c2)) {
187 Err(_) => false,
188 Ok(new_vertex) => {
189 let new_edge_1 = Edge::try_new(shape[c1.0], new_vertex).unwrap();
190 let new_edge_2 = Edge::try_new(new_vertex, shape[c2.2]).unwrap();
191
192 let affected_points = [shape[c1.1], shape[c1.0], shape[c2.1], shape[c2.2]];
193
194 edge_iter(shape)
196 .filter(|l| !affected_points.contains(&l.start))
197 .filter(|l| !affected_points.contains(&l.end))
198 .all(|l| !l.collides_with(&new_edge_1) && !l.collides_with(&new_edge_2))
199 }
200 }
201 }
202 }
203}
204
205fn edge_iter(points: &[Point]) -> impl Iterator<Item = Edge> + '_ {
206 let n_points = points.len();
207 (0..n_points).map(move |i| {
208 let j = (i + 1) % n_points;
209 Edge::try_new(points[i], points[j]).unwrap()
210 })
211}
212
213fn execute_candidate(shape: &[Point], candidate: &Candidate) -> Vec<Point> {
214 let mut points = shape.iter().cloned().collect_vec();
215 match candidate {
216 Candidate::Collinear(c) | Candidate::Concave(c) => {
217 points.remove(c.1);
218 }
219 Candidate::ConvexConvex(c1, c2) => {
220 let replacing_vertex = replacing_vertex_convex_convex_candidate(shape, (*c1, *c2))
221 .expect("invalid candidate cannot be executed");
222 points.remove(c1.1);
223 let other_index = if c1.1 < c2.1 { c2.1 - 1 } else { c2.1 };
224 points.remove(other_index);
225 points.insert(other_index, replacing_vertex);
226 }
227 }
228 points
229}
230
231fn replacing_vertex_convex_convex_candidate(
232 shape: &[Point],
233 (c1, c2): (Corner, Corner),
234) -> Result<Point, InvalidCandidate> {
235 assert_eq!(c1.2, c2.1, "non-consecutive corners {c1:?},{c2:?}");
236 assert_eq!(c1.1, c2.0, "non-consecutive corners {c1:?},{c2:?}");
237
238 let edge_prev = Edge::try_new(shape[c1.0], shape[c1.1]).unwrap();
239 let edge_next = Edge::try_new(shape[c2.2], shape[c2.1]).unwrap();
240
241 calculate_intersection_in_front(&edge_prev, &edge_next).ok_or(InvalidCandidate)
242}
243
244fn calculate_intersection_in_front(l1: &Edge, l2: &Edge) -> Option<Point> {
245 let Point(x1, y1) = l1.start;
250 let Point(x2, y2) = l1.end;
251 let Point(x3, y3) = l2.start;
252 let Point(x4, y4) = l2.end;
253
254 let t_nom = (x2 - x4) * (y4 - y3) - (y2 - y4) * (x4 - x3);
258 let t_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
259
260 let u_nom = (x2 - x4) * (y2 - y1) - (y2 - y4) * (x2 - x1);
261 let u_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
262
263 let t = if t_denom != 0.0 { t_nom / t_denom } else { 0.0 };
264
265 let u = if u_denom != 0.0 { u_nom / u_denom } else { 0.0 };
266
267 if t < 0.0 && u < 0.0 {
268 Some(Point(x2 + t * (x1 - x2), y2 + t * (y1 - y2)))
270 } else {
271 None
273 }
274}
275
276#[derive(Debug, Clone)]
277struct InvalidCandidate;
278
279#[derive(Clone, Debug, PartialEq)]
280enum Candidate {
281 Concave(Corner),
282 ConvexConvex(Corner, Corner),
283 Collinear(Corner),
284}
285
286#[derive(Clone, Copy, Debug, PartialEq)]
287struct Corner(pub usize, pub usize, pub usize);
289
290impl Corner {
291 pub fn flip(&mut self) {
292 std::mem::swap(&mut self.0, &mut self.2);
293 }
294
295 pub fn to_points(self, points: &[Point]) -> [Point; 3] {
296 [points[self.0], points[self.1], points[self.2]]
297 }
298}
299
300#[derive(Clone, Copy, Debug, PartialEq)]
301enum CornerType {
302 Concave,
303 Convex,
304 Collinear,
305}
306
307impl CornerType {
308 pub fn from([p1, p2, p3]: [Point; 3]) -> Self {
309 let p1p2 = (p2.0 - p1.0, p2.1 - p1.1);
313 let p1p3 = (p3.0 - p1.0, p3.1 - p1.1);
314 let cross_prod = p1p2.0 * p1p3.1 - p1p2.1 * p1p3.0;
315
316 match cross_prod.partial_cmp(&0.0).expect("cross product is NaN") {
318 Ordering::Less => CornerType::Concave,
319 Ordering::Equal => CornerType::Collinear,
320 Ordering::Greater => CornerType::Convex,
321 }
322 }
323}
324
325pub fn offset_shape(sp: &SPolygon, mode: ShapeModifyMode, distance: f32) -> Result<SPolygon> {
328 let offset = match mode {
329 ShapeModifyMode::Deflate => -distance,
330 ShapeModifyMode::Inflate => distance,
331 };
332
333 let geo_poly = geo_types::Polygon::new(
335 sp.vertices
336 .iter()
337 .map(|p| (p.0 as f64, p.1 as f64))
338 .collect(),
339 vec![],
340 );
341
342 let geo_poly_offsets = geo_buffer::buffer_polygon_rounded(&geo_poly, offset as f64).0;
344
345 let geo_poly_offset = match geo_poly_offsets.len() {
346 0 => bail!("Offset resulted in an empty polygon"),
347 1 => &geo_poly_offsets[0],
348 _ => {
349 warn!("Offset resulted in multiple polygons, taking the first one.");
352 &geo_poly_offsets[0]
353 }
354 };
355
356 let ext_s_polygon = ExtSPolygon(
358 geo_poly_offset
359 .exterior()
360 .points()
361 .map(|p| (p.x() as f32, p.y() as f32))
362 .collect_vec(),
363 );
364
365 import::import_simple_polygon(&ext_s_polygon)
366}