1use axiolid_core::{Point2, Tolerance};
104use axiolid_exact::{certify, Arith, SignExpr};
105use axiolid_guarantees::Sign;
106
107use crate::arc::ArcRing;
108use crate::arrangement::ArcArrangement;
109use crate::region::Region;
110use crate::{validate_ring, OverlayError, Polygon, Ring};
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114#[non_exhaustive]
115pub enum MinkowskiError {
116 NotConvex,
120 Overlay(OverlayError),
122 EmptyStructuring,
124}
125
126impl From<OverlayError> for MinkowskiError {
127 fn from(error: OverlayError) -> Self {
128 Self::Overlay(error)
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum BoundSide {
136 Inner,
139 Outer,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Default)]
151#[non_exhaustive]
152pub enum MorphologyMode {
153 #[default]
158 Exact,
159 Fast {
167 budget: f64,
169 },
170}
171
172impl MorphologyMode {
173 #[must_use]
175 pub const fn fast(budget: f64) -> Self {
176 Self::Fast { budget }
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq)]
182pub struct MorphologyBound {
183 pub side: BoundSide,
185 pub deviation: f64,
189}
190
191struct Orient {
193 a: Point2,
194 b: Point2,
195 c: Point2,
196}
197
198impl SignExpr for Orient {
199 fn sign_in<T: Arith>(&self) -> Option<Sign> {
200 let f = T::from_f64;
201 let (ux, uy) = (f(self.b.x).sub(&f(self.a.x)), f(self.b.y).sub(&f(self.a.y)));
202 let (vx, vy) = (f(self.c.x).sub(&f(self.a.x)), f(self.c.y).sub(&f(self.a.y)));
203 ux.mul(&vy).sub(&uy.mul(&vx)).sign()
204 }
205}
206
207pub(crate) fn orient(a: Point2, b: Point2, c: Point2) -> Sign {
208 certify(&Orient { a, b, c }).unwrap_or(Sign::Zero)
209}
210
211fn convex(ring: &Ring, tolerance: Tolerance) -> Result<Vec<Point2>, MinkowskiError> {
213 validate_ring(ring, tolerance)?;
214 let hull = hull(ring.points.clone());
215 let on_hull = ring.points.iter().all(|p| {
217 hull.contains(p) || {
218 (0..hull.len()).any(|i| {
220 let (a, b) = (hull[i], hull[(i + 1) % hull.len()]);
221 orient(a, b, *p) == Sign::Zero
222 && p.x >= a.x.min(b.x)
223 && p.x <= a.x.max(b.x)
224 && p.y >= a.y.min(b.y)
225 && p.y <= a.y.max(b.y)
226 })
227 }
228 });
229 if !on_hull || hull.len() < 3 {
230 return Err(MinkowskiError::NotConvex);
231 }
232 Ok(hull)
233}
234
235fn hull(mut points: Vec<Point2>) -> Vec<Point2> {
238 points.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
239 points.dedup();
240 if points.len() < 3 {
241 return points;
242 }
243 let mut lower: Vec<Point2> = Vec::new();
244 for &p in &points {
245 while lower.len() >= 2
246 && orient(lower[lower.len() - 2], lower[lower.len() - 1], p) != Sign::Positive
247 {
248 lower.pop();
249 }
250 lower.push(p);
251 }
252 let mut upper: Vec<Point2> = Vec::new();
253 for &p in points.iter().rev() {
254 while upper.len() >= 2
255 && orient(upper[upper.len() - 2], upper[upper.len() - 1], p) != Sign::Positive
256 {
257 upper.pop();
258 }
259 upper.push(p);
260 }
261 lower.pop();
262 upper.pop();
263 lower.extend(upper);
264 lower
265}
266
267fn rings_of(polygons: &[Polygon]) -> Vec<Vec<Point2>> {
270 polygons
271 .iter()
272 .flat_map(|p| std::iter::once(&p.outer).chain(&p.holes))
273 .map(|r| r.points.clone())
274 .collect()
275}
276
277#[derive(Debug, Clone)]
280struct Layout {
281 starts: Vec<usize>,
283 len: usize,
285}
286
287impl Layout {
288 fn new(polygons: &[Polygon]) -> Self {
289 let mut starts = Vec::with_capacity(polygons.len());
290 let mut len = 0;
291 for polygon in polygons {
292 starts.push(len);
293 len += 1 + polygon.holes.len();
294 }
295 Self { starts, len }
296 }
297
298 fn single(holes: usize) -> Self {
300 Self {
301 starts: vec![0],
302 len: 1 + holes,
303 }
304 }
305
306 fn member(&self, rings: &[usize], offset: usize) -> bool {
314 rings.iter().enumerate().any(|(i, &ring)| {
315 let ring = ring - offset;
316 let polygon = self.starts.partition_point(|&s| s <= ring) - 1;
317 let end = self.starts.get(polygon + 1).copied().unwrap_or(self.len);
318 ring == self.starts[polygon] && rings.get(i + 1).is_none_or(|&r| r - offset >= end)
319 })
320 }
321}
322
323fn from(rings: &[usize], first: usize) -> &[usize] {
325 &rings[rings.partition_point(|&r| r < first)..]
326}
327
328fn tidy(points: Vec<Point2>, tolerance: Tolerance) -> Option<ArcRing> {
331 tidy_points(points, tolerance).map(|out| ArcRing::from_points(&out))
332}
333
334fn tidy_points(points: Vec<Point2>, tolerance: Tolerance) -> Option<Vec<Point2>> {
336 let mut out: Vec<Point2> = Vec::with_capacity(points.len());
337 for p in points {
338 if out
339 .last()
340 .is_none_or(|q: &Point2| (p - *q).length() > tolerance.linear())
341 {
342 out.push(p);
343 }
344 }
345 while out.len() > 1 && (out[0] - out[out.len() - 1]).length() <= tolerance.linear() {
346 out.pop();
347 }
348 (out.len() >= 3).then_some(out)
349}
350
351#[derive(Debug, Clone)]
353enum EdgeTerm {
354 Whole(Vec<Point2>),
356 Trimmed(Vec<Point2>),
358 Nothing,
361}
362
363#[derive(Debug, Clone)]
365pub(crate) enum Part {
366 Pieces(Vec<Vec<Point2>>),
368 Rings(Vec<Vec<Point2>>),
371}
372
373impl Part {
374 fn flipped(&self) -> Self {
376 let flip = |v: &Vec<Point2>| v.iter().map(|q| Point2::new(-q.x, -q.y)).collect();
377 match self {
378 Self::Pieces(p) => Self::Pieces(p.iter().map(flip).collect()),
379 Self::Rings(r) => Self::Rings(r.iter().map(flip).collect()),
380 }
381 }
382
383 fn anchor(&self) -> Point2 {
387 match self {
388 _ if self.holds_origin() => Point2::new(0.0, 0.0),
389 Self::Pieces(p) => p[0][0],
390 Self::Rings(r) => r[0][0],
391 }
392 }
393
394 fn points(&self) -> impl Iterator<Item = &Point2> {
395 match self {
396 Self::Pieces(p) | Self::Rings(p) => p.iter().flatten(),
397 }
398 }
399
400 fn holds_origin(&self) -> bool {
403 let o = Point2::new(0.0, 0.0);
404 match self {
405 Self::Pieces(pieces) => pieces.iter().any(|piece| {
406 let h = hull(piece.clone());
407 h.len() >= 3
408 && (0..h.len()).all(|i| orient(h[i], h[(i + 1) % h.len()], o) != Sign::Negative)
409 }),
410 Self::Rings(_) => false,
411 }
412 }
413}
414
415fn polygon_part(
418 outer: &Ring,
419 holes: &[Ring],
420 tolerance: Tolerance,
421) -> Result<Part, MinkowskiError> {
422 if holes.is_empty() {
423 match convex(outer, tolerance) {
424 Ok(k) => return Ok(Part::Pieces(vec![k])),
425 Err(MinkowskiError::NotConvex) => {}
426 Err(other) => return Err(other),
427 }
428 }
429 let holes: Vec<Vec<Point2>> = holes.iter().map(|h| h.points.clone()).collect();
430 Ok(
431 match crate::convex_parts::convex_parts(&outer.points, &holes) {
432 Some(pieces) => Part::Pieces(pieces),
433 None => Part::Rings(std::iter::once(outer.points.clone()).chain(holes).collect()),
434 },
435 )
436}
437
438#[derive(Debug, Clone)]
440enum Set {
441 Region(Layout),
443 Outside(Layout),
445}
446
447impl Set {
448 fn sides(&self, rings: &[Vec<Point2>]) -> Vec<Sign> {
454 let (offset, shape, outer_inside) = match self {
455 Self::Region(shape) => (0, shape, true),
456 Self::Outside(shape) => (1, shape, false),
457 };
458 rings
459 .iter()
460 .enumerate()
461 .map(|(i, ring)| {
462 let inside = i < offset
463 || (shape.starts.binary_search(&(i - offset)).is_ok() == outer_inside);
464 let turn = crate::orientation(ring);
465 if inside {
466 turn
467 } else {
468 turn.flip()
469 }
470 })
471 .collect()
472 }
473
474 fn span(&self) -> usize {
476 match self {
477 Self::Region(shape) => shape.len,
478 Self::Outside(shape) => 1 + shape.len,
479 }
480 }
481
482 fn holds(&self, rings: &[usize], offset: usize) -> bool {
485 match self {
486 Self::Region(shape) => shape.member(rings, offset),
487 Self::Outside(shape) => {
488 rings.first() == Some(&offset) && !shape.member(&rings[1..], offset + 1)
489 }
490 }
491 }
492}
493
494struct Terms {
497 rings: Vec<ArcRing>,
498 sets: Vec<(usize, Set)>,
501 pieces: usize,
503}
504
505impl Terms {
506 fn holds(&self, rings: &[usize], offset: usize) -> bool {
514 if rings.last().is_some_and(|&r| r - offset >= self.pieces) {
515 return true;
516 }
517 let mut i = 0;
518 while i < rings.len() {
519 let at = rings[i] - offset;
520 let (first, set) = &self.sets[self.sets.partition_point(|(s, _)| *s <= at) - 1];
521 let end = offset + first + set.span();
522 let next = i + rings[i..].partition_point(|&r| r < end);
523 if set.holds(&rings[i..next], offset + first) {
524 return true;
525 }
526 i = next;
527 }
528 false
529 }
530}
531
532enum Rule {
535 Sum(Terms),
537 Erosion {
541 own: usize,
543 anchor: usize,
546 shape: Layout,
547 terms: Terms,
548 },
549}
550
551impl Rule {
552 fn holds(&self, rings: &[usize]) -> bool {
553 match self {
554 Self::Sum(terms) => terms.holds(rings, 0),
555 Self::Erosion {
556 own,
557 anchor,
558 shape,
559 terms,
560 } => {
561 shape.member(from(rings, *anchor), *anchor) && !terms.holds(from(rings, *own), *own)
562 }
563 }
564 }
565}
566
567fn sum_terms(
578 rings: &[Vec<Point2>],
579 set: &Set,
580 parts: &[Part],
581 covers: bool,
582 tolerance: Tolerance,
583) -> Terms {
584 let shift = |p: Point2, by: Point2| Point2::new(p.x + by.x, p.y + by.y);
585 let moved = |r: &[Point2], by: Point2| {
586 ArcRing::from_points(&r.iter().map(|&p| shift(p, by)).collect::<Vec<_>>())
587 };
588 let mut out = Vec::new();
589 let mut sets = Vec::new();
590 for part in parts {
591 sets.push((out.len(), set.clone()));
592 out.extend(rings.iter().map(|r| moved(r, part.anchor())));
593 if let Part::Rings(own) = part {
594 let shape = Layout::single(own.len() - 1);
595 for ring in rings {
596 sets.push((out.len(), Set::Region(shape.clone())));
597 out.extend(own.iter().map(|r| moved(r, ring[0])));
598 }
599 }
600 }
601 let pieces = out.len();
602 let convex: Vec<&Vec<Point2>> = parts
605 .iter()
606 .filter_map(|part| match part {
607 Part::Pieces(p) => Some(p),
608 Part::Rings(_) => None,
609 })
610 .flatten()
611 .collect();
612 let depth = parts
613 .iter()
614 .flat_map(Part::points)
615 .fold(0.0_f64, |m, q| m.max(q.x.abs()).max(q.y.abs()))
616 / 16.0;
617 let sweep = |k: &[Point2], a: Point2, b: Point2| -> Option<Vec<Point2>> {
618 let points: Vec<Point2> = k.iter().flat_map(|&q| [shift(a, q), shift(b, q)]).collect();
619 tidy_points(hull(points), tolerance)
620 };
621 let index = crate::cover::Index::new(rings);
622 let sides = set.sides(rings);
623 let degenerate = |points: &[Point2]| crate::zero_area(points, tolerance.linear());
625 for (r, ring) in rings.iter().enumerate() {
626 let boundary = crate::cover::merged(ring);
628 let edges = &boundary.points;
629 let n = edges.len();
630 let edge = |i: usize, k: &[Point2]| sweep(k, edges[i], edges[(i + 1) % n]);
631 let mut spanned: Vec<Option<Vec<EdgeTerm>>> = vec![None; n];
634 if covers && !convex.is_empty() {
635 for chord in crate::cover::chords(r, &boundary, sides[r], depth, &index) {
636 let ends = (edges[chord.from], edges[(chord.from + chord.span) % n]);
637 let hulls: Vec<Option<Vec<Point2>>> =
638 convex.iter().map(|k| sweep(k, ends.0, ends.1)).collect();
639 let terms: Vec<Vec<EdgeTerm>> = (0..chord.span)
640 .map(|step| {
641 let i = (chord.from + step) % n;
642 convex
643 .iter()
644 .zip(&hulls)
645 .map(|(k, g)| match (edge(i, k), g) {
646 (None, _) => EdgeTerm::Nothing,
647 (Some(h), None) => EdgeTerm::Whole(h),
648 (Some(h), Some(g)) => crate::cover::trim(h, g, ends, |points| {
649 tidy_points(points, tolerance)
650 })
651 .map_or(EdgeTerm::Nothing, EdgeTerm::Trimmed),
652 })
653 .collect()
654 })
655 .collect();
656 if hulls.iter().flatten().any(|g| degenerate(g))
659 || terms
660 .iter()
661 .flatten()
662 .any(|term| matches!(term, EdgeTerm::Trimmed(kept) if degenerate(kept)))
663 {
664 continue;
665 }
666 out.extend(hulls.iter().flatten().map(|h| ArcRing::from_points(h)));
667 for (step, terms) in terms.into_iter().enumerate() {
668 spanned[(chord.from + step) % n] = Some(terms);
669 }
670 }
671 }
672 let mut given: std::collections::HashSet<Vec<(u64, u64)>> =
675 std::collections::HashSet::new();
676 for i in 0..n {
677 let (a, b) = (edges[i], edges[(i + 1) % n]);
678 let terms = spanned[i].take().unwrap_or_else(|| {
679 convex
680 .iter()
681 .map(|k| edge(i, k).map_or(EdgeTerm::Nothing, EdgeTerm::Whole))
682 .collect()
683 });
684 for term in terms {
685 match term {
686 EdgeTerm::Whole(h) => out.push(ArcRing::from_points(&h)),
687 EdgeTerm::Trimmed(kept) => {
688 if given.insert(
689 kept.iter()
690 .map(|p| (p.x.to_bits(), p.y.to_bits()))
691 .collect(),
692 ) {
693 out.push(ArcRing::from_points(&kept));
694 }
695 }
696 EdgeTerm::Nothing => {}
697 }
698 }
699 for part in parts {
700 if let Part::Rings(own) = part {
701 for r in own {
702 for j in 0..r.len() {
703 let (p, q) = (r[j], r[(j + 1) % r.len()]);
704 let points = vec![shift(a, p), shift(a, q), shift(b, p), shift(b, q)];
705 out.extend(tidy(hull(points), tolerance));
706 }
707 }
708 }
709 }
710 }
711 }
712 Terms {
713 rings: out,
714 sets,
715 pieces,
716 }
717}
718
719fn parts_of(region: &Region, tolerance: Tolerance) -> Result<Vec<Part>, MinkowskiError> {
721 region
722 .polygons()
723 .iter()
724 .map(|p| polygon_part(&p.outer, &p.holes, tolerance))
725 .collect()
726}
727
728pub(crate) fn region_of(
732 arrangement: &ArcArrangement,
733 inside: impl Fn(&[usize]) -> bool,
734 had_input: bool,
735 tolerance: Tolerance,
736) -> Result<Region, OverlayError> {
737 let straight = |ring: ArcRing| Ring {
738 points: ring.vertices.iter().map(|v| v.point).collect(),
739 };
740 let mut polygons = Vec::new();
741 for region in arrangement.regions_holding(inside)? {
742 let Some(outer) = crate::arc_overlay::presented(arrangement.ring(®ion.outer), tolerance)
743 else {
744 continue;
745 };
746 let holes = region
747 .holes
748 .iter()
749 .filter_map(|h| crate::arc_overlay::presented(arrangement.ring(h), tolerance))
750 .map(straight)
751 .collect();
752 polygons.push(Polygon {
753 outer: straight(outer),
754 holes,
755 });
756 }
757 let (polygons, validated) = crate::settle::settle_validated(polygons, tolerance);
759 Ok(Region::from_valid_polygons(polygons, had_input).with_validated(validated))
760}
761
762impl Region {
763 pub fn minkowski_sum(&self, ring: &Ring, tolerance: Tolerance) -> Result<Self, MinkowskiError> {
777 let part = polygon_part(ring, &[], tolerance)?;
778 Ok(self.sum_parts(&[part], tolerance)?)
779 }
780
781 pub fn minkowski_erosion(
788 &self,
789 ring: &Ring,
790 tolerance: Tolerance,
791 ) -> Result<Self, MinkowskiError> {
792 let part = polygon_part(ring, &[], tolerance)?;
793 Ok(self.erode_parts(&[part], tolerance)?)
794 }
795
796 pub fn minkowski_sum_region(
808 &self,
809 other: &Self,
810 tolerance: Tolerance,
811 ) -> Result<Self, MinkowskiError> {
812 if self.is_empty() || other.is_empty() {
813 return Ok(Self::empty());
814 }
815 let size = |r: &Self| {
816 r.polygons()
817 .iter()
818 .flat_map(|p| std::iter::once(&p.outer).chain(&p.holes))
819 .map(|r| r.points.len())
820 .sum::<usize>()
821 };
822 let (base, cut) = if size(self) < size(other) {
823 (other, self)
824 } else {
825 (self, other)
826 };
827 let parts = parts_of(cut, tolerance)?;
828 Ok(base.sum_parts(&parts, tolerance)?)
829 }
830
831 pub fn minkowski_erosion_region(
839 &self,
840 other: &Self,
841 tolerance: Tolerance,
842 ) -> Result<Self, MinkowskiError> {
843 if other.is_empty() {
844 return Err(MinkowskiError::EmptyStructuring);
845 }
846 let parts = parts_of(other, tolerance)?;
847 Ok(self.erode_parts(&parts, tolerance)?)
848 }
849
850 fn sum_with(&self, k: &[Point2], tolerance: Tolerance) -> Result<Self, OverlayError> {
851 self.sum_parts(&[Part::Pieces(vec![k.to_vec()])], tolerance)
852 }
853
854 fn erode_with(&self, k: &[Point2], tolerance: Tolerance) -> Result<Self, OverlayError> {
855 self.erode_parts(&[Part::Pieces(vec![k.to_vec()])], tolerance)
856 }
857
858 pub(crate) fn sum_parts(
859 &self,
860 parts: &[Part],
861 tolerance: Tolerance,
862 ) -> Result<Self, OverlayError> {
863 if self.is_empty() {
864 return Ok(Self::empty());
865 }
866 let (rings, rule) = self.sum_rule(parts, true, tolerance);
867 let arrangement = ArcArrangement::new(&rings, tolerance)?;
868 region_of(&arrangement, |rings| rule.holds(rings), true, tolerance)
869 }
870
871 fn sum_rule(&self, parts: &[Part], covers: bool, tolerance: Tolerance) -> (Vec<ArcRing>, Rule) {
875 let own = rings_of(self.polygons());
876 let shape = Layout::new(self.polygons());
877 sum_rule_of(&own, shape, parts, covers, tolerance)
878 }
879
880 pub(crate) fn erode_parts(
881 &self,
882 parts: &[Part],
883 tolerance: Tolerance,
884 ) -> Result<Self, OverlayError> {
885 if self.is_empty() {
886 return Ok(Self::empty());
887 }
888 let (rings, rule) = self.erosion_rule(parts, true, tolerance);
889 let arrangement = ArcArrangement::new(&rings, tolerance)?;
890 region_of(&arrangement, |rings| rule.holds(rings), true, tolerance)
891 }
892
893 fn erosion_rule(
897 &self,
898 parts: &[Part],
899 covers: bool,
900 tolerance: Tolerance,
901 ) -> (Vec<ArcRing>, Rule) {
902 let own = rings_of(self.polygons());
903 let shape = Layout::new(self.polygons());
904 erosion_rule_of(own, shape, parts, covers, tolerance)
905 }
906}
907
908fn sum_rule_of(
911 own: &[Vec<Point2>],
912 shape: Layout,
913 parts: &[Part],
914 covers: bool,
915 tolerance: Tolerance,
916) -> (Vec<ArcRing>, Rule) {
917 let mut terms = sum_terms(own, &Set::Region(shape), parts, covers, tolerance);
918 (std::mem::take(&mut terms.rings), Rule::Sum(terms))
919}
920
921fn erosion_rule_of(
924 own: Vec<Vec<Point2>>,
925 shape: Layout,
926 parts: &[Part],
927 covers: bool,
928 tolerance: Tolerance,
929) -> (Vec<ArcRing>, Rule) {
930 let (mut lo, mut hi) = (own[0][0], own[0][0]);
936 for p in own.iter().flatten() {
937 lo = lo.min(*p);
938 hi = hi.max(*p);
939 }
940 let reach = parts
941 .iter()
942 .flat_map(Part::points)
943 .fold(0.0_f64, |m, q| m.max(q.x.abs()).max(q.y.abs()));
944 let pad = 2.0 * reach + (hi - lo).max_element() + 1.0;
945 let frame = vec![
946 Point2::new(lo.x - pad, lo.y - pad),
947 Point2::new(hi.x + pad, lo.y - pad),
948 Point2::new(hi.x + pad, hi.y + pad),
949 Point2::new(lo.x - pad, hi.y + pad),
950 ];
951 let mut outside = vec![frame];
952 outside.extend(own.iter().cloned());
953 let flipped: Vec<Part> = parts.iter().map(Part::flipped).collect();
954 let mut terms = sum_terms(
955 &outside,
956 &Set::Outside(shape.clone()),
957 &flipped,
958 covers,
959 tolerance,
960 );
961 let m = own.len();
962 let mut rings: Vec<ArcRing> = own.iter().map(|r| ArcRing::from_points(r)).collect();
963 rings.append(&mut terms.rings);
964 let origin = parts.iter().any(Part::holds_origin);
970 let first = m + terms.sets[0].0 + 1;
971 (
972 rings,
973 Rule::Erosion {
974 anchor: if origin { 0 } else { first },
975 own: m,
976 shape,
977 terms,
978 },
979 )
980}
981
982impl Region {
983 pub fn dilate_inner(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
991 self.disc(
992 radius,
993 BoundSide::Inner,
994 false,
995 MorphologyMode::Exact,
996 tolerance,
997 )
998 }
999
1000 pub fn dilate_outer(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
1007 self.disc(
1008 radius,
1009 BoundSide::Outer,
1010 false,
1011 MorphologyMode::Exact,
1012 tolerance,
1013 )
1014 }
1015
1016 pub fn erode_inner(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
1024 self.disc(
1025 radius,
1026 BoundSide::Inner,
1027 true,
1028 MorphologyMode::Exact,
1029 tolerance,
1030 )
1031 }
1032
1033 pub fn erode_outer(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
1041 self.disc(
1042 radius,
1043 BoundSide::Outer,
1044 true,
1045 MorphologyMode::Exact,
1046 tolerance,
1047 )
1048 }
1049
1050 pub fn dilate_inner_with(
1060 &self,
1061 radius: f64,
1062 tolerance: Tolerance,
1063 mode: MorphologyMode,
1064 ) -> Result<Self, OverlayError> {
1065 self.disc(radius, BoundSide::Inner, false, mode, tolerance)
1066 }
1067
1068 pub fn dilate_outer_with(
1075 &self,
1076 radius: f64,
1077 tolerance: Tolerance,
1078 mode: MorphologyMode,
1079 ) -> Result<Self, OverlayError> {
1080 self.disc(radius, BoundSide::Outer, false, mode, tolerance)
1081 }
1082
1083 pub fn erode_inner_with(
1090 &self,
1091 radius: f64,
1092 tolerance: Tolerance,
1093 mode: MorphologyMode,
1094 ) -> Result<Self, OverlayError> {
1095 self.disc(radius, BoundSide::Inner, true, mode, tolerance)
1096 }
1097
1098 pub fn erode_outer_with(
1105 &self,
1106 radius: f64,
1107 tolerance: Tolerance,
1108 mode: MorphologyMode,
1109 ) -> Result<Self, OverlayError> {
1110 self.disc(radius, BoundSide::Outer, true, mode, tolerance)
1111 }
1112
1113 fn disc(
1114 &self,
1115 radius: f64,
1116 side: BoundSide,
1117 erode: bool,
1118 mode: MorphologyMode,
1119 tolerance: Tolerance,
1120 ) -> Result<Self, OverlayError> {
1121 if !radius.is_finite() || radius < 0.0 {
1122 return Err(OverlayError::InvalidOffsetDistance);
1123 }
1124 if let MorphologyMode::Fast { budget } = mode {
1125 if !budget.is_finite() || budget < 0.0 {
1126 return Err(OverlayError::InvalidOffsetDistance);
1127 }
1128 }
1129 if self.is_empty() || radius == 0.0 {
1130 let mut same = Self::from_valid_polygons(self.polygons().to_vec(), false)
1131 .with_validated(self.validated());
1132 same.set_bound(MorphologyBound {
1133 side,
1134 deviation: 0.0,
1135 });
1136 return Ok(same);
1137 }
1138 let n = 64;
1141 let half = core::f64::consts::PI / n as f64;
1142 let size = self
1147 .polygons()
1148 .iter()
1149 .flat_map(|p| std::iter::once(&p.outer).chain(&p.holes))
1150 .flat_map(|r| r.points.iter())
1151 .fold(0.0_f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
1152 let margin =
1153 2.0 * tolerance.linear() + 1e-12 * radius + 64.0 * f64::EPSILON * (size + radius);
1154 let inscribed = (side == BoundSide::Inner) != erode;
1157 let (mut reach, mut deviation) = if inscribed {
1158 let reach = radius - margin;
1159 (reach, radius - reach * half.cos())
1160 } else {
1161 let reach = (radius + margin) / half.cos();
1162 (reach, reach - radius)
1163 };
1164 if reach <= 0.0 {
1165 let mut same = Self::from_valid_polygons(self.polygons().to_vec(), true)
1169 .with_validated(self.validated());
1170 same.set_bound(MorphologyBound {
1171 side,
1172 deviation: radius + margin,
1173 });
1174 return Ok(same);
1175 }
1176 let filled = match mode {
1181 MorphologyMode::Exact => None,
1182 MorphologyMode::Fast { budget } => {
1183 let own = rings_of(self.polygons());
1184 let shape = Layout::new(self.polygons());
1185 let mut sides = Set::Region(shape.clone()).sides(&own);
1186 if erode {
1187 for s in &mut sides {
1189 *s = s.flip();
1190 }
1191 }
1192 let t = 0.5 * (radius - margin);
1193 let filled = crate::fill::fill(&own, &sides, t, budget.min(t), tolerance.linear());
1194 if inscribed {
1195 reach -= filled.excess;
1196 deviation = radius - reach * half.cos();
1197 } else {
1198 deviation += filled.excess;
1199 }
1200 Some((filled, shape))
1201 }
1202 };
1203 let k: Vec<Point2> = (0..n)
1204 .map(|i| {
1205 let t = 2.0 * half * i as f64;
1206 Point2::new(reach * t.cos(), reach * t.sin())
1207 })
1208 .collect();
1209 let mut out = match filled {
1210 None if erode => self.erode_with(&k, tolerance)?,
1211 None => self.sum_with(&k, tolerance)?,
1212 Some((filled, shape)) => {
1213 let parts = [Part::Pieces(vec![k])];
1214 let (rings, rule) = if erode {
1215 erosion_rule_of(filled.rings, shape, &parts, true, tolerance)
1216 } else {
1217 sum_rule_of(&filled.rings, shape, &parts, true, tolerance)
1218 };
1219 let arrangement = ArcArrangement::new(&rings, tolerance)?;
1220 let mut out = region_of(&arrangement, |r| rule.holds(r), true, tolerance)?;
1221 out.set_excess(filled.excess);
1222 out
1223 }
1224 };
1225 out.set_bound(MorphologyBound { side, deviation });
1226 Ok(out)
1227 }
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232 use super::*;
1233 use crate::Polygon;
1234
1235 fn ring(points: &[(f64, f64)]) -> Ring {
1236 Ring {
1237 points: points.iter().map(|&(x, y)| Point2::new(x, y)).collect(),
1238 }
1239 }
1240
1241 fn u_with_hole() -> Region {
1242 Region::new(
1243 vec![Polygon {
1244 outer: ring(&[
1245 (0.0, 0.0),
1246 (6.0, 0.0),
1247 (6.0, 5.0),
1248 (4.0, 5.0),
1249 (4.0, 2.0),
1250 (2.0, 2.0),
1251 (2.0, 5.0),
1252 (0.0, 5.0),
1253 ]),
1254 holes: vec![ring(&[(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)])],
1255 }],
1256 Tolerance::METRE,
1257 )
1258 .unwrap()
1259 }
1260
1261 fn shapes() -> Vec<(Ring, Vec<Ring>)> {
1262 vec![
1263 (
1265 ring(&[
1266 (0., 0.),
1267 (0.8, 0.),
1268 (0.8, 0.4),
1269 (0.4, 0.4),
1270 (0.4, 0.8),
1271 (0., 0.8),
1272 ]),
1273 vec![],
1274 ),
1275 (
1277 ring(&[(0.6, 0.4), (0.3, 1.0), (1.5, 0.4), (0.3, -0.2)]),
1278 vec![],
1279 ),
1280 (
1282 ring(&[(-0.6, -0.6), (0.6, -0.6), (0.6, 0.6), (-0.6, 0.6)]),
1283 vec![ring(&[(-0.3, -0.3), (0.3, -0.3), (0.3, 0.3), (-0.3, 0.3)])],
1284 ),
1285 ]
1286 }
1287
1288 fn apart(a: &Region, b: &Region) -> f64 {
1289 let t = Tolerance::METRE;
1290 a.difference(b, t).unwrap().area() + b.difference(a, t).unwrap().area()
1291 }
1292
1293 fn dense_member(shape: &Layout, flags: &[bool]) -> bool {
1297 (0..shape.starts.len()).any(|p| {
1298 let (at, end) = (
1299 shape.starts[p],
1300 shape.starts.get(p + 1).copied().unwrap_or(shape.len),
1301 );
1302 flags[at] && !flags[at + 1..end].iter().any(|&f| f)
1303 })
1304 }
1305
1306 fn dense_terms(terms: &Terms, flags: &[bool]) -> bool {
1307 terms.sets.iter().any(|(at, set)| match set {
1308 Set::Region(shape) => dense_member(shape, &flags[*at..]),
1309 Set::Outside(shape) => flags[*at] && !dense_member(shape, &flags[at + 1..]),
1310 }) || flags[terms.pieces..].iter().any(|&f| f)
1311 }
1312
1313 fn dense_rule(rule: &Rule, flags: &[bool]) -> bool {
1314 match rule {
1315 Rule::Sum(terms) => dense_terms(terms, flags),
1316 Rule::Erosion {
1317 own,
1318 anchor,
1319 shape,
1320 terms,
1321 } => dense_member(shape, &flags[*anchor..]) && !dense_terms(terms, &flags[*own..]),
1322 }
1323 }
1324
1325 #[test]
1326 fn the_sparse_rule_reads_every_piece_as_the_flags_did() {
1327 let t = Tolerance::METRE;
1332 let square =
1333 |x: f64, y: f64, s: f64| ring(&[(x, y), (x + s, y), (x + s, y + s), (x, y + s)]);
1334 let two_holes = Region::new(
1335 vec![Polygon {
1336 outer: square(8.0, 0.0, 4.0),
1337 holes: vec![square(8.5, 0.5, 1.0), square(10.0, 2.0, 1.5)],
1338 }],
1339 t,
1340 )
1341 .unwrap();
1342 let region = u_with_hole()
1343 .union(&two_holes, t)
1344 .unwrap()
1345 .union(
1346 &Region::new(
1347 vec![Polygon {
1348 outer: square(3.0, 3.0, 0.1),
1349 holes: vec![],
1350 }],
1351 t,
1352 )
1353 .unwrap(),
1354 t,
1355 )
1356 .unwrap();
1357 assert_eq!(region.polygons().len(), 3);
1358 let mut parts = vec![Part::Pieces(vec![vec![
1359 Point2::new(-0.3, -0.3),
1360 Point2::new(0.3, -0.3),
1361 Point2::new(0.3, 0.3),
1362 Point2::new(-0.3, 0.3),
1363 ]])];
1364 for (outer, holes) in shapes() {
1365 parts.push(polygon_part(&outer, &holes, t).unwrap());
1366 parts.push(Part::Rings(
1367 std::iter::once(outer.points.clone())
1368 .chain(holes.iter().map(|h| h.points.clone()))
1369 .collect(),
1370 ));
1371 }
1372 let mut sides = 0;
1373 for part in &parts {
1374 for (rings, rule) in [
1375 region.sum_rule(std::slice::from_ref(part), true, t),
1376 region.erosion_rule(std::slice::from_ref(part), true, t),
1377 ] {
1378 let arrangement = ArcArrangement::new(&rings, t).unwrap();
1379 let mut held = 0;
1380 for edge in arrangement.edges() {
1381 for left in [true, false] {
1382 let flags: Vec<bool> = (0..rings.len())
1383 .map(|r| {
1384 if left {
1385 edge.inside_left(r)
1386 } else {
1387 edge.inside_right(r)
1388 }
1389 })
1390 .collect();
1391 let holding: Vec<usize> = (0..rings.len()).filter(|&r| flags[r]).collect();
1392 let mut listed = Vec::new();
1393 edge.holding(left, &mut listed);
1394 assert_eq!(listed, holding);
1395 let dense = dense_rule(&rule, &flags);
1396 assert_eq!(rule.holds(&holding), dense, "{holding:?}");
1397 held += usize::from(dense);
1398 sides += 1;
1399 }
1400 }
1401 assert!(held > 0);
1402 }
1403 }
1404 assert!(sides > 10_000, "{sides}");
1405 }
1406
1407 #[test]
1408 fn non_convex_polygons_are_cut_into_few_convex_pieces() {
1409 let pieces: Vec<usize> = shapes()
1410 .iter()
1411 .map(
1412 |(outer, holes)| match polygon_part(outer, holes, Tolerance::METRE).unwrap() {
1413 Part::Pieces(p) => p.len(),
1414 Part::Rings(_) => panic!("not decomposed"),
1415 },
1416 )
1417 .collect();
1418 assert_eq!(pieces[0], 2);
1419 assert_eq!(pieces[1], 2);
1420 assert!(pieces[2] <= 6, "{pieces:?}");
1421 }
1422
1423 #[test]
1424 fn the_edge_pair_route_agrees_with_the_convex_pieces() {
1425 let t = Tolerance::METRE;
1429 let small = Region::new(
1430 vec![Polygon {
1431 outer: ring(&[(3.0, 3.0), (3.1, 3.0), (3.1, 3.1), (3.0, 3.1)]),
1432 holes: vec![],
1433 }],
1434 t,
1435 )
1436 .unwrap();
1437 let r = u_with_hole().union(&small, t).unwrap();
1438 for (outer, holes) in shapes() {
1439 let cut = polygon_part(&outer, &holes, t).unwrap();
1440 let whole = Part::Rings(
1441 std::iter::once(outer.points.clone())
1442 .chain(holes.iter().map(|h| h.points.clone()))
1443 .collect(),
1444 );
1445 let (a, b) = (
1446 r.sum_parts(std::slice::from_ref(&cut), t).unwrap(),
1447 r.sum_parts(std::slice::from_ref(&whole), t).unwrap(),
1448 );
1449 assert!(!a.is_empty() && apart(&a, &b) < 1e-9, "{}", apart(&a, &b));
1450 let (a, b) = (
1451 r.erode_parts(std::slice::from_ref(&cut), t).unwrap(),
1452 r.erode_parts(std::slice::from_ref(&whole), t).unwrap(),
1453 );
1454 assert!(!a.is_empty() && apart(&a, &b) < 1e-9, "{}", apart(&a, &b));
1455 }
1456 }
1457
1458 fn teeth(a: (f64, f64), b: (f64, f64), teeth: usize, depth: f64) -> Vec<(f64, f64)> {
1461 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
1462 let len = dx.hypot(dy);
1463 let (nx, ny) = (-dy / len, dx / len);
1464 (0..2 * teeth)
1465 .map(|k| {
1466 let t = k as f64 / (2 * teeth) as f64;
1467 let d = if k % 2 == 1 { depth } else { 0.0 };
1468 (a.0 + t * dx + d * nx, a.1 + t * dy + d * ny)
1469 })
1470 .collect()
1471 }
1472
1473 fn walls() -> Vec<(&'static str, Region)> {
1476 let t = Tolerance::new(1e-9, 1e-9).unwrap();
1477 let region = |rings: Vec<Vec<(f64, f64)>>| {
1478 Region::new(
1479 vec![Polygon {
1480 outer: ring(&rings[0]),
1481 holes: rings[1..].iter().map(|h| ring(h)).collect(),
1482 }],
1483 t,
1484 )
1485 .unwrap()
1486 };
1487 let room = |depth: f64| {
1488 let mut outer = teeth((0.0, 0.0), (4.0, 0.0), 30, depth);
1489 outer.extend(teeth((4.0, 0.0), (4.0, 3.0), 2, 0.0));
1490 outer.extend(teeth((4.0, 3.0), (0.0, 3.0), 30, depth));
1491 outer.push((0.0, 3.0));
1492 outer
1493 };
1494 let notch = {
1495 let mut outer = vec![(0.0, 0.0), (4.0, 0.0)];
1497 outer.extend(teeth((4.0, 1.5), (2.0, 1.5), 20, 5e-4));
1498 outer.extend(teeth((2.0, 1.5), (2.0, 3.0), 15, 5e-4));
1499 outer.extend([(2.0, 3.0), (0.0, 3.0)]);
1500 outer
1501 };
1502 let column: Vec<(f64, f64)> = (0..40)
1503 .map(|i| {
1504 let a = -(i as f64) * std::f64::consts::TAU / 40.0;
1505 (2.0 + 0.4 * a.cos(), 1.5 + 0.4 * a.sin())
1506 })
1507 .collect();
1508 let mut pillar = teeth((1.0, 1.0), (1.0, 2.0), 10, -5e-4);
1509 pillar.extend(teeth((1.0, 2.0), (1.4, 2.0), 4, -5e-4));
1510 pillar.extend(teeth((1.4, 2.0), (1.4, 1.0), 10, -5e-4));
1511 pillar.extend(teeth((1.4, 1.0), (1.0, 1.0), 4, -5e-4));
1512 let (s, c) = 2.3f64.to_radians().sin_cos();
1513 let placed: Vec<(f64, f64)> = room(5e-4)
1514 .into_iter()
1515 .map(|(x, y)| (640_000.0 + c * x - s * y, 5_650_000.0 + s * x + c * y))
1516 .collect();
1517 let peak = 4.0 * 21.0 / 60.0;
1520 let island = Region::new(
1521 vec![Polygon {
1522 outer: ring(&[
1523 (peak, -1e-4),
1524 (peak + 2e-4, 0.0),
1525 (peak, 1e-4),
1526 (peak - 2e-4, 0.0),
1527 ]),
1528 holes: vec![],
1529 }],
1530 t,
1531 )
1532 .unwrap();
1533 vec![
1534 ("teeth in", region(vec![room(5e-4)])),
1535 ("teeth out", region(vec![room(-5e-4)])),
1536 ("collinear", region(vec![room(0.0)])),
1537 ("notch", region(vec![notch])),
1538 (
1539 "column and pillar",
1540 region(vec![room(5e-4), column, pillar]),
1541 ),
1542 ("placed", region(vec![placed])),
1543 (
1544 "island",
1545 region(vec![room(5e-4)]).union(&island, t).unwrap(),
1546 ),
1547 ]
1548 }
1549
1550 #[test]
1551 fn chord_covers_leave_sums_and_erosions_unchanged() {
1552 let t = Tolerance::new(1e-9, 1e-9).unwrap();
1556 let disc: Vec<Point2> = (0..64)
1557 .map(|i| {
1558 let a = i as f64 * std::f64::consts::TAU / 64.0;
1559 Point2::new(0.3 * a.cos(), 0.3 * a.sin())
1560 })
1561 .collect();
1562 let l_shape = polygon_part(&shapes()[0].0, &[], t).unwrap();
1563 let size = |rings: &[ArcRing]| rings.iter().map(|r| r.vertices.len()).sum::<usize>();
1564 for (name, region) in walls() {
1565 for (round, part) in [
1566 (true, Part::Pieces(vec![disc.clone()])),
1567 (false, l_shape.clone()),
1568 ] {
1569 let parts = std::slice::from_ref(&part);
1570 for erosion in [false, true] {
1571 let build = |covers: bool| {
1572 let (rings, rule) = if erosion {
1573 region.erosion_rule(parts, covers, t)
1574 } else {
1575 region.sum_rule(parts, covers, t)
1576 };
1577 let arrangement = ArcArrangement::new(&rings, t).unwrap();
1578 let out = region_of(&arrangement, |r| rule.holds(r), true, t).unwrap();
1579 (size(&rings), out)
1580 };
1581 let ((trimmed, a), (whole, b)) = (build(true), build(false));
1582 assert!(!a.is_empty(), "{name}");
1583 assert_eq!(a.polygons(), b.polygons(), "{name}, erosion {erosion}");
1584 match name {
1587 "collinear" => {}
1588 _ if round && name != "island" => {
1589 assert!(2 * trimmed < whole, "{name}: {trimmed} of {whole}");
1590 }
1591 _ => assert!(trimmed < whole, "{name}: {trimmed} of {whole}"),
1592 }
1593 }
1594 }
1595 }
1596 }
1597
1598 #[test]
1599 fn exactly_collinear_corners_cost_and_give_what_the_plain_room_does() {
1600 let t = Tolerance::new(1e-9, 1e-9).unwrap();
1603 let walls = walls();
1604 let collinear = &walls.iter().find(|(n, _)| *n == "collinear").unwrap().1;
1605 let plain = Region::new(
1606 vec![Polygon {
1607 outer: ring(&[(0.0, 0.0), (4.0, 0.0), (4.0, 3.0), (0.0, 3.0)]),
1608 holes: vec![],
1609 }],
1610 t,
1611 )
1612 .unwrap();
1613 for radius in [0.3, 0.75] {
1614 for (a, b) in [
1615 (
1616 collinear.erode_inner(radius, t),
1617 plain.erode_inner(radius, t),
1618 ),
1619 (
1620 collinear.erode_outer(radius, t),
1621 plain.erode_outer(radius, t),
1622 ),
1623 (
1624 collinear.dilate_inner(radius, t),
1625 plain.dilate_inner(radius, t),
1626 ),
1627 (
1628 collinear.dilate_outer(radius, t),
1629 plain.dilate_outer(radius, t),
1630 ),
1631 ] {
1632 assert_eq!(a.unwrap().polygons(), b.unwrap().polygons());
1633 }
1634 }
1635 }
1636}