1use axiolid_core::{Point2, Tolerance};
43use axiolid_exact::{certify, Arith, SignExpr};
44use axiolid_guarantees::Sign;
45
46use crate::arc::ArcRing;
47use crate::arrangement::ArcArrangement;
48use crate::region::Region;
49use crate::{validate_ring, OverlayError, Polygon, Ring};
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum MinkowskiError {
55 NotConvex,
58 Overlay(OverlayError),
60}
61
62impl From<OverlayError> for MinkowskiError {
63 fn from(error: OverlayError) -> Self {
64 Self::Overlay(error)
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[non_exhaustive]
71pub enum BoundSide {
72 Inner,
75 Outer,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct MorphologyBound {
83 pub side: BoundSide,
85 pub deviation: f64,
89}
90
91struct Orient {
93 a: Point2,
94 b: Point2,
95 c: Point2,
96}
97
98impl SignExpr for Orient {
99 fn sign_in<T: Arith>(&self) -> Option<Sign> {
100 let f = T::from_f64;
101 let (ux, uy) = (f(self.b.x).sub(&f(self.a.x)), f(self.b.y).sub(&f(self.a.y)));
102 let (vx, vy) = (f(self.c.x).sub(&f(self.a.x)), f(self.c.y).sub(&f(self.a.y)));
103 ux.mul(&vy).sub(&uy.mul(&vx)).sign()
104 }
105}
106
107fn orient(a: Point2, b: Point2, c: Point2) -> Sign {
108 certify(&Orient { a, b, c }).unwrap_or(Sign::Zero)
109}
110
111fn convex(ring: &Ring, tolerance: Tolerance) -> Result<Vec<Point2>, MinkowskiError> {
113 validate_ring(ring, tolerance)?;
114 let hull = hull(ring.points.clone());
115 let on_hull = ring.points.iter().all(|p| {
117 hull.contains(p) || {
118 (0..hull.len()).any(|i| {
120 let (a, b) = (hull[i], hull[(i + 1) % hull.len()]);
121 orient(a, b, *p) == Sign::Zero
122 && p.x >= a.x.min(b.x)
123 && p.x <= a.x.max(b.x)
124 && p.y >= a.y.min(b.y)
125 && p.y <= a.y.max(b.y)
126 })
127 }
128 });
129 if !on_hull || hull.len() < 3 {
130 return Err(MinkowskiError::NotConvex);
131 }
132 Ok(hull)
133}
134
135fn hull(mut points: Vec<Point2>) -> Vec<Point2> {
138 points.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
139 points.dedup();
140 if points.len() < 3 {
141 return points;
142 }
143 let mut lower: Vec<Point2> = Vec::new();
144 for &p in &points {
145 while lower.len() >= 2
146 && orient(lower[lower.len() - 2], lower[lower.len() - 1], p) != Sign::Positive
147 {
148 lower.pop();
149 }
150 lower.push(p);
151 }
152 let mut upper: Vec<Point2> = Vec::new();
153 for &p in points.iter().rev() {
154 while upper.len() >= 2
155 && orient(upper[upper.len() - 2], upper[upper.len() - 1], p) != Sign::Positive
156 {
157 upper.pop();
158 }
159 upper.push(p);
160 }
161 lower.pop();
162 upper.pop();
163 lower.extend(upper);
164 lower
165}
166
167fn rings_of(polygons: &[Polygon]) -> Vec<Vec<Point2>> {
170 polygons
171 .iter()
172 .flat_map(|p| std::iter::once(&p.outer).chain(&p.holes))
173 .map(|r| r.points.clone())
174 .collect()
175}
176
177fn member(polygons: &[usize], flags: &[bool]) -> bool {
180 let mut at = 0;
181 for &holes in polygons {
182 let inside = flags[at] && !flags[at + 1..at + 1 + holes].iter().any(|&f| f);
183 if inside {
184 return true;
185 }
186 at += 1 + holes;
187 }
188 false
189}
190
191fn tidy(points: Vec<Point2>, tolerance: Tolerance) -> Option<ArcRing> {
194 let mut out: Vec<Point2> = Vec::with_capacity(points.len());
195 for p in points {
196 if out
197 .last()
198 .is_none_or(|q: &Point2| (p - *q).length() > tolerance.linear())
199 {
200 out.push(p);
201 }
202 }
203 while out.len() > 1 && (out[0] - out[out.len() - 1]).length() <= tolerance.linear() {
204 out.pop();
205 }
206 (out.len() >= 3).then(|| ArcRing::from_points(&out))
207}
208
209fn sum_rings(
213 rings: &[Vec<Point2>],
214 k: &[Point2],
215 tolerance: Tolerance,
216) -> (Vec<ArcRing>, Vec<ArcRing>) {
217 let shift = |p: Point2, by: Point2| Point2::new(p.x + by.x, p.y + by.y);
218 let translated = rings
219 .iter()
220 .map(|r| ArcRing::from_points(&r.iter().map(|&p| shift(p, k[0])).collect::<Vec<_>>()))
221 .collect();
222 let mut pieces = Vec::new();
223 for ring in rings {
224 for i in 0..ring.len() {
225 let (a, b) = (ring[i], ring[(i + 1) % ring.len()]);
226 let points: Vec<Point2> = k.iter().flat_map(|&q| [shift(a, q), shift(b, q)]).collect();
227 if let Some(piece) = tidy(hull(points), tolerance) {
228 pieces.push(piece);
229 }
230 }
231 }
232 (translated, pieces)
233}
234
235pub(crate) fn region_of(
237 arrangement: &ArcArrangement,
238 inside: impl Fn(&[bool]) -> bool,
239 had_input: bool,
240 tolerance: Tolerance,
241) -> Result<Region, OverlayError> {
242 let straight = |ring: ArcRing| Ring {
243 points: ring.vertices.iter().map(|v| v.point).collect(),
244 };
245 let mut polygons = Vec::new();
246 for region in arrangement.regions(inside)? {
247 let Some(outer) = crate::arc_overlay::presented(arrangement.ring(®ion.outer), tolerance)
248 else {
249 continue;
250 };
251 let holes = region
252 .holes
253 .iter()
254 .filter_map(|h| crate::arc_overlay::presented(arrangement.ring(h), tolerance))
255 .map(straight)
256 .collect();
257 polygons.push(Polygon {
258 outer: straight(outer),
259 holes,
260 });
261 }
262 Ok(Region::from_valid_polygons(
263 crate::settle::settle(polygons, tolerance),
264 had_input,
265 ))
266}
267
268impl Region {
269 pub fn minkowski_sum(
280 &self,
281 convex_ring: &Ring,
282 tolerance: Tolerance,
283 ) -> Result<Self, MinkowskiError> {
284 let k = convex(convex_ring, tolerance)?;
285 Ok(self.sum_with(&k, tolerance)?)
286 }
287
288 pub fn minkowski_erosion(
295 &self,
296 convex_ring: &Ring,
297 tolerance: Tolerance,
298 ) -> Result<Self, MinkowskiError> {
299 let k = convex(convex_ring, tolerance)?;
300 Ok(self.erode_with(&k, tolerance)?)
301 }
302
303 fn sum_with(&self, k: &[Point2], tolerance: Tolerance) -> Result<Self, OverlayError> {
304 if self.is_empty() {
305 return Ok(Self::empty());
306 }
307 let own = rings_of(self.polygons());
308 let shape: Vec<usize> = self.polygons().iter().map(|p| p.holes.len()).collect();
309 let (translated, pieces) = sum_rings(&own, k, tolerance);
310 let n = translated.len();
311 let mut rings = translated;
312 rings.extend(pieces);
313 let arrangement = ArcArrangement::new(&rings, tolerance)?;
314 region_of(
315 &arrangement,
316 |flags| member(&shape, &flags[..n]) || flags[n..].iter().any(|&f| f),
317 true,
318 tolerance,
319 )
320 }
321
322 fn erode_with(&self, k: &[Point2], tolerance: Tolerance) -> Result<Self, OverlayError> {
323 if self.is_empty() {
324 return Ok(Self::empty());
325 }
326 let own = rings_of(self.polygons());
327 let shape: Vec<usize> = self.polygons().iter().map(|p| p.holes.len()).collect();
328 let (mut lo, mut hi) = (own[0][0], own[0][0]);
334 for p in own.iter().flatten() {
335 lo = lo.min(*p);
336 hi = hi.max(*p);
337 }
338 let reach = k
339 .iter()
340 .fold(0.0_f64, |m, q| m.max(q.x.abs()).max(q.y.abs()));
341 let pad = 2.0 * reach + (hi - lo).max_element() + 1.0;
342 let frame = vec![
343 Point2::new(lo.x - pad, lo.y - pad),
344 Point2::new(hi.x + pad, lo.y - pad),
345 Point2::new(hi.x + pad, hi.y + pad),
346 Point2::new(lo.x - pad, hi.y + pad),
347 ];
348 let mut outside = vec![frame];
349 outside.extend(own.iter().cloned());
350 let flipped: Vec<Point2> = k.iter().map(|q| Point2::new(-q.x, -q.y)).collect();
351 let (translated, pieces) = sum_rings(&outside, &flipped, tolerance);
352 let m = own.len();
353 let t = translated.len();
354 let mut rings: Vec<ArcRing> = own.iter().map(|r| ArcRing::from_points(r)).collect();
355 rings.extend(translated);
356 rings.extend(pieces);
357 let arrangement = ArcArrangement::new(&rings, tolerance)?;
358 let in_outside = |flags: &[bool]| flags[0] && !member(&shape, &flags[1..]);
359 region_of(
360 &arrangement,
361 |flags| {
362 member(&shape, &flags[..m])
363 && !(in_outside(&flags[m..m + t]) || flags[m + t..].iter().any(|&f| f))
364 },
365 true,
366 tolerance,
367 )
368 }
369
370 pub fn dilate_inner(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
378 self.disc(radius, BoundSide::Inner, false, tolerance)
379 }
380
381 pub fn dilate_outer(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
388 self.disc(radius, BoundSide::Outer, false, tolerance)
389 }
390
391 pub fn erode_inner(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
399 self.disc(radius, BoundSide::Inner, true, tolerance)
400 }
401
402 pub fn erode_outer(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
410 self.disc(radius, BoundSide::Outer, true, tolerance)
411 }
412
413 fn disc(
414 &self,
415 radius: f64,
416 side: BoundSide,
417 erode: bool,
418 tolerance: Tolerance,
419 ) -> Result<Self, OverlayError> {
420 if !radius.is_finite() || radius < 0.0 {
421 return Err(OverlayError::InvalidOffsetDistance);
422 }
423 if self.is_empty() || radius == 0.0 {
424 let mut same = Self::from_valid_polygons(self.polygons().to_vec(), false);
425 same.set_bound(MorphologyBound {
426 side,
427 deviation: 0.0,
428 });
429 return Ok(same);
430 }
431 let n = 64;
434 let half = core::f64::consts::PI / n as f64;
435 let size = self
440 .polygons()
441 .iter()
442 .flat_map(|p| std::iter::once(&p.outer).chain(&p.holes))
443 .flat_map(|r| r.points.iter())
444 .fold(0.0_f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
445 let margin =
446 2.0 * tolerance.linear() + 1e-12 * radius + 64.0 * f64::EPSILON * (size + radius);
447 let inscribed = (side == BoundSide::Inner) != erode;
450 let (reach, deviation) = if inscribed {
451 let reach = radius - margin;
452 (reach, radius - reach * half.cos())
453 } else {
454 let reach = (radius + margin) / half.cos();
455 (reach, reach - radius)
456 };
457 if reach <= 0.0 {
458 let mut same = Self::from_valid_polygons(self.polygons().to_vec(), true);
462 same.set_bound(MorphologyBound {
463 side,
464 deviation: radius + margin,
465 });
466 return Ok(same);
467 }
468 let k: Vec<Point2> = (0..n)
469 .map(|i| {
470 let t = 2.0 * half * i as f64;
471 Point2::new(reach * t.cos(), reach * t.sin())
472 })
473 .collect();
474 let mut out = if erode {
475 self.erode_with(&k, tolerance)?
476 } else {
477 self.sum_with(&k, tolerance)?
478 };
479 out.set_bound(MorphologyBound { side, deviation });
480 Ok(out)
481 }
482}