1use std::collections::VecDeque;
7
8use kurbo::{BezPath, Rect, Shape};
9
10use crate::{
11 curve::{y_subsegment, Order},
12 geom::Point,
13 order::ComparisonCache,
14 segments::{NonClosedPath, SegIdx, SegVec, Segments},
15 sweep::{
16 SegmentsConnectedAtX, SweepLineBuffers, SweepLineRange, SweepLineRangeBuffers, Sweeper,
17 },
18};
19
20pub trait WindingNumber:
29 Copy + std::fmt::Debug + std::ops::Add<Output = Self> + std::ops::AddAssign + Default + Eq
30{
31 #[cfg(not(test))]
36 type Tag: Copy + std::fmt::Debug + Eq;
37
38 #[cfg(test)]
43 type Tag: Copy + std::fmt::Debug + Eq + serde::Serialize;
44
45 fn single(tag: Self::Tag, positive: bool) -> Self;
47}
48
49#[cfg_attr(test, derive(serde::Serialize))]
54#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)]
55pub struct BinaryWindingNumber {
56 pub shape_a: i32,
58 pub shape_b: i32,
60}
61
62impl WindingNumber for BinaryWindingNumber {
63 type Tag = bool;
64
65 fn single(tag: Self::Tag, positive: bool) -> Self {
66 let sign = if positive { 1 } else { -1 };
67 if tag {
68 Self {
69 shape_a: sign,
70 shape_b: 0,
71 }
72 } else {
73 Self {
74 shape_a: 0,
75 shape_b: sign,
76 }
77 }
78 }
79}
80
81impl std::ops::Add for BinaryWindingNumber {
82 type Output = Self;
83
84 fn add(self, rhs: Self) -> Self::Output {
85 Self {
86 shape_a: self.shape_a + rhs.shape_a,
87 shape_b: self.shape_b + rhs.shape_b,
88 }
89 }
90}
91
92impl std::ops::AddAssign for BinaryWindingNumber {
93 fn add_assign(&mut self, rhs: Self) {
94 *self = *self + rhs
95 }
96}
97
98impl std::fmt::Debug for BinaryWindingNumber {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 write!(f, "{}a + {}b", self.shape_a, self.shape_b)
101 }
102}
103
104impl WindingNumber for i32 {
105 type Tag = ();
106
107 fn single((): Self::Tag, positive: bool) -> Self {
108 if positive {
109 1
110 } else {
111 -1
112 }
113 }
114}
115
116#[cfg_attr(test, derive(serde::Serialize))]
121#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)]
122pub struct HalfSegmentWindingNumbers<W: WindingNumber> {
123 pub counter_clockwise: W,
128 pub clockwise: W,
132}
133
134impl<W: WindingNumber> HalfSegmentWindingNumbers<W> {
135 fn is_trivial(&self) -> bool {
138 self.counter_clockwise == self.clockwise
139 }
140
141 fn flipped(self) -> Self {
143 Self {
144 counter_clockwise: self.clockwise,
145 clockwise: self.counter_clockwise,
146 }
147 }
148}
149
150impl<W: WindingNumber> std::fmt::Debug for HalfSegmentWindingNumbers<W> {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 write!(
153 f,
154 "cw {:?} | cc {:?}",
155 self.clockwise, self.counter_clockwise
156 )
157 }
158}
159
160#[cfg_attr(test, derive(serde::Serialize))]
165#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
166pub struct OutputSegIdx(usize);
167
168impl OutputSegIdx {
169 pub fn first_half(self) -> HalfOutputSegIdx {
171 HalfOutputSegIdx {
172 idx: self,
173 first_half: true,
174 }
175 }
176
177 pub fn second_half(self) -> HalfOutputSegIdx {
179 HalfOutputSegIdx {
180 idx: self,
181 first_half: false,
182 }
183 }
184}
185
186#[cfg_attr(test, derive(serde::Serialize))]
190#[derive(Clone, Hash, PartialEq, Eq)]
191pub struct OutputSegVec<T> {
192 inner: Vec<T>,
193}
194
195impl_typed_vec!(OutputSegVec, OutputSegIdx, "o");
196
197#[cfg_attr(test, derive(serde::Serialize))]
203#[derive(Clone, Copy, Hash, PartialEq, Eq)]
204pub struct HalfOutputSegIdx {
205 idx: OutputSegIdx,
206 first_half: bool,
207}
208
209impl HalfOutputSegIdx {
210 pub fn other_half(self) -> Self {
212 Self {
213 idx: self.idx,
214 first_half: !self.first_half,
215 }
216 }
217
218 pub fn is_first_half(self) -> bool {
220 self.first_half
221 }
222}
223
224impl std::fmt::Debug for HalfOutputSegIdx {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 if self.first_half {
227 write!(f, "{:?}->", self.idx)
228 } else {
229 write!(f, "->{:?}", self.idx)
230 }
231 }
232}
233
234#[cfg_attr(test, derive(serde::Serialize))]
236#[derive(Clone, Hash, PartialEq, Eq)]
237pub struct HalfOutputSegVec<T> {
238 start: Vec<T>,
239 end: Vec<T>,
240}
241
242impl<T> HalfOutputSegVec<T> {
243 pub fn with_capacity(cap: usize) -> Self {
245 Self {
246 start: Vec::with_capacity(cap),
247 end: Vec::with_capacity(cap),
248 }
249 }
250}
251
252impl<T: Default> HalfOutputSegVec<T> {
253 pub fn with_size(size: usize) -> Self {
257 Self {
258 start: std::iter::from_fn(|| Some(T::default()))
259 .take(size)
260 .collect(),
261 end: std::iter::from_fn(|| Some(T::default()))
262 .take(size)
263 .collect(),
264 }
265 }
266}
267
268impl<T: std::fmt::Debug> std::fmt::Debug for HalfOutputSegVec<T> {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 struct Entry<'a, T> {
271 idx: usize,
272 start: &'a T,
273 end: &'a T,
274 }
275
276 impl<T: std::fmt::Debug> std::fmt::Debug for Entry<'_, T> {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 write!(
279 f,
280 "{idx:4}: {start:?} -> {end:?}",
281 idx = self.idx,
282 start = self.start,
283 end = self.end
284 )
285 }
286 }
287
288 let mut list = f.debug_list();
289 for (idx, (start, end)) in self.start.iter().zip(&self.end).enumerate() {
290 list.entry(&Entry { idx, start, end });
291 }
292 list.finish()
293 }
294}
295
296impl<T> Default for HalfOutputSegVec<T> {
297 fn default() -> Self {
298 Self {
299 start: Vec::new(),
300 end: Vec::new(),
301 }
302 }
303}
304
305impl<T> std::ops::Index<HalfOutputSegIdx> for HalfOutputSegVec<T> {
306 type Output = T;
307
308 fn index(&self, index: HalfOutputSegIdx) -> &Self::Output {
309 if index.first_half {
310 &self.start[index.idx.0]
311 } else {
312 &self.end[index.idx.0]
313 }
314 }
315}
316
317impl<T> std::ops::IndexMut<HalfOutputSegIdx> for HalfOutputSegVec<T> {
318 fn index_mut(&mut self, index: HalfOutputSegIdx) -> &mut T {
319 if index.first_half {
320 &mut self.start[index.idx.0]
321 } else {
322 &mut self.end[index.idx.0]
323 }
324 }
325}
326
327impl<T> std::ops::Index<HalfOutputSegIdx> for OutputSegVec<T> {
328 type Output = T;
329
330 fn index(&self, index: HalfOutputSegIdx) -> &Self::Output {
331 &self.inner[index.idx.0]
332 }
333}
334
335#[cfg_attr(test, derive(serde::Serialize))]
337#[derive(Clone, Copy, Hash, PartialEq, Eq)]
338pub struct PointIdx(usize);
339
340#[cfg_attr(test, derive(serde::Serialize))]
341#[derive(Clone, Hash, PartialEq, Eq)]
342struct PointVec<T> {
343 inner: Vec<T>,
344}
345
346impl_typed_vec!(PointVec, PointIdx, "p");
347
348#[cfg_attr(test, derive(serde::Serialize))]
349#[derive(Clone, Copy, Hash, PartialEq, Eq)]
350struct PointNeighbors {
351 clockwise: HalfOutputSegIdx,
352 counter_clockwise: HalfOutputSegIdx,
353}
354
355impl std::fmt::Debug for PointNeighbors {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 write!(f, "{:?} o {:?}", self.counter_clockwise, self.clockwise)
358 }
359}
360
361#[cfg_attr(test, derive(serde::Serialize))]
363#[derive(Clone, Debug)]
364pub struct Topology<W: WindingNumber> {
365 eps: f64,
366 tag: SegVec<W::Tag>,
368 open_segs: SegVec<VecDeque<OutputSegIdx>>,
386 winding: OutputSegVec<HalfSegmentWindingNumbers<W>>,
393 points: PointVec<Point>,
395 point_idx: HalfOutputSegVec<PointIdx>,
397 point_neighbors: HalfOutputSegVec<PointNeighbors>,
399 deleted: OutputSegVec<bool>,
401 scan_west: OutputSegVec<Option<OutputSegIdx>>,
408 scan_east: OutputSegVec<Option<OutputSegIdx>>,
444 scan_after: Vec<(f64, Option<OutputSegIdx>, Option<OutputSegIdx>)>,
462 pub orig_seg: OutputSegVec<SegIdx>,
465 #[cfg_attr(test, serde(skip))]
469 pub segments: Segments,
470 positively_oriented: OutputSegVec<bool>,
476}
477
478impl<W: WindingNumber> Topology<W> {
479 pub fn from_paths<'a, Iter>(paths: Iter, eps: f64) -> Result<Self, NonClosedPath>
483 where
484 Iter: IntoIterator<Item = (&'a BezPath, W::Tag)>,
485 {
486 let mut segments = Segments::default();
487 let mut tag = Vec::new();
488 for (p, t) in paths {
489 segments.add_path_without_updating_enter_exit(p, true)?;
490 tag.resize(segments.len(), t);
491 }
492 segments.update_enter_exit(0);
493 segments.check_invariants();
494 Ok(Self::from_segments(segments, SegVec::from_vec(tag), eps))
495 }
496
497 fn from_segments(segments: Segments, tag: SegVec<W::Tag>, eps: f64) -> Self {
498 let mut ret = Self {
499 eps,
500 tag,
501 open_segs: SegVec::with_size(segments.len()),
502
503 winding: OutputSegVec::with_capacity(segments.len()),
505 points: PointVec::with_capacity(segments.len()),
506 point_idx: HalfOutputSegVec::with_capacity(segments.len()),
507 point_neighbors: HalfOutputSegVec::with_capacity(segments.len()),
508 deleted: OutputSegVec::with_capacity(segments.len()),
509 scan_west: OutputSegVec::with_capacity(segments.len()),
510 scan_east: OutputSegVec::with_capacity(segments.len()),
511 scan_after: Vec::new(),
512 orig_seg: OutputSegVec::with_capacity(segments.len()),
513 positively_oriented: OutputSegVec::with_capacity(segments.len()),
514 segments: Segments::default(),
515 };
516 let mut sweep_state = Sweeper::new(&segments, eps);
517 let mut range_bufs = SweepLineRangeBuffers::default();
518 let mut line_bufs = SweepLineBuffers::default();
519 while let Some(mut line) = sweep_state.next_line(&mut line_bufs) {
521 while let Some(positions) = line.next_range(&mut range_bufs, &segments) {
522 let range = positions.seg_range();
523 let scan_west_seg = if range.segs.start == 0 {
524 None
525 } else {
526 let prev_seg = positions.line().line_segment(range.segs.start - 1).unwrap();
527 debug_assert!(!ret.open_segs[prev_seg].is_empty());
528 ret.open_segs[prev_seg].front().copied()
529 };
530 ret.process_sweep_line_range(positions, &segments, scan_west_seg);
531 }
532 }
533 ret.segments = segments;
534
535 #[cfg(feature = "slow-asserts")]
536 ret.check_invariants();
537
538 ret.merge_coincident();
539
540 #[cfg(feature = "slow-asserts")]
541 ret.check_invariants();
542
543 ret
544 }
545
546 fn add_segs_clockwise(
571 &mut self,
572 first_seg: &mut Option<HalfOutputSegIdx>,
573 last_seg: &mut Option<HalfOutputSegIdx>,
574 segs: impl Iterator<Item = HalfOutputSegIdx>,
575 p: PointIdx,
576 ) {
577 for seg in segs {
578 self.point_idx[seg] = p;
579 if first_seg.is_none() {
580 *first_seg = Some(seg);
581 }
582 if let Some(last) = last_seg {
583 self.point_neighbors[*last].clockwise = seg;
584 self.point_neighbors[seg].counter_clockwise = *last;
585 }
586 *last_seg = Some(seg);
587 }
588 if let Some((first, last)) = first_seg.zip(*last_seg) {
589 self.point_neighbors[last].clockwise = first;
590 self.point_neighbors[first].counter_clockwise = last;
591 }
592 }
593
594 fn add_segs_counter_clockwise(
596 &mut self,
597 first_seg: &mut Option<HalfOutputSegIdx>,
598 last_seg: &mut Option<HalfOutputSegIdx>,
599 segs: impl Iterator<Item = HalfOutputSegIdx>,
600 p: PointIdx,
601 ) {
602 for seg in segs {
603 self.point_idx[seg] = p;
604 if last_seg.is_none() {
605 *last_seg = Some(seg);
606 }
607 if let Some(first) = first_seg {
608 self.point_neighbors[*first].counter_clockwise = seg;
609 self.point_neighbors[seg].clockwise = *first;
610 }
611 *first_seg = Some(seg);
612 }
613 if let Some((first, last)) = first_seg.zip(*last_seg) {
614 self.point_neighbors[last].clockwise = first;
615 self.point_neighbors[first].counter_clockwise = last;
616 }
617 }
618
619 fn second_half_segs<'a, 'slf: 'a>(
629 &'slf mut self,
630 segs: impl Iterator<Item = SegIdx> + 'a,
631 ) -> impl Iterator<Item = HalfOutputSegIdx> + 'a {
632 segs.map(|s| {
633 self.open_segs[s]
634 .pop_front()
635 .expect("should be open")
636 .second_half()
637 })
638 }
639
640 fn new_half_seg(
653 &mut self,
654 idx: SegIdx,
655 p: PointIdx,
656 winding: HalfSegmentWindingNumbers<W>,
657 horizontal: bool,
658 positively_oriented: bool,
659 ) -> OutputSegIdx {
660 let out_idx = OutputSegIdx(self.winding.inner.len());
661 if horizontal {
662 self.open_segs[idx].push_front(out_idx);
663 } else {
664 self.open_segs[idx].push_back(out_idx);
665 }
666 self.point_idx.start.push(p);
667 self.point_idx
668 .end
669 .push(PointIdx(usize::MAX));
671
672 let no_nbrs = PointNeighbors {
673 clockwise: out_idx.first_half(),
674 counter_clockwise: out_idx.first_half(),
675 };
676 self.point_neighbors.start.push(no_nbrs);
677 self.point_neighbors.end.push(no_nbrs);
678 self.winding.inner.push(winding);
679 self.deleted.inner.push(false);
680 self.scan_west.inner.push(None);
681 self.scan_east.inner.push(None);
682 self.orig_seg.inner.push(idx);
683 self.positively_oriented.inner.push(positively_oriented);
684 out_idx
685 }
686
687 fn process_sweep_line_range(
688 &mut self,
689 mut pos: SweepLineRange,
690 segments: &Segments,
691 mut scan_west: Option<OutputSegIdx>,
692 ) {
693 let y = pos.line().y();
694 let mut winding = scan_west
695 .map(|idx| self.winding[idx].counter_clockwise)
696 .unwrap_or_default();
697
698 let mut seg_buf = Vec::new();
702 let mut connected_segs = SegmentsConnectedAtX::default();
703 let mut last_connected_down_seg: Option<OutputSegIdx> = None;
705
706 while let Some(next_x) = pos.x() {
707 let p = PointIdx(self.points.inner.len());
708 self.points.inner.push(Point::new(next_x, y));
709 let mut first_seg = None;
711 let mut last_seg = None;
713
714 let hsegs = pos.active_horizontals();
716 seg_buf.clear();
717 seg_buf.extend(self.second_half_segs(hsegs));
718 self.add_segs_clockwise(&mut first_seg, &mut last_seg, seg_buf.iter().copied(), p);
719
720 pos.update_segments_at_x(&mut connected_segs);
722 seg_buf.clear();
723 seg_buf.extend(self.second_half_segs(connected_segs.connected_up()));
724 self.add_segs_clockwise(&mut first_seg, &mut last_seg, seg_buf.iter().copied(), p);
725
726 seg_buf.clear();
730 for new_seg in connected_segs.connected_down() {
731 let prev_winding = winding;
732 let orientation = segments.positively_oriented(new_seg);
733 winding += W::single(self.tag[new_seg], orientation);
734 let windings = HalfSegmentWindingNumbers {
735 clockwise: prev_winding,
736 counter_clockwise: winding,
737 };
738 let half_seg = self.new_half_seg(new_seg, p, windings, false, orientation);
739 self.scan_west[half_seg] = scan_west;
740 scan_west = Some(half_seg);
741 seg_buf.push(half_seg.first_half());
742
743 last_connected_down_seg = Some(half_seg);
744 }
745 self.add_segs_counter_clockwise(
746 &mut first_seg,
747 &mut last_seg,
748 seg_buf.iter().copied(),
749 p,
750 );
751
752 pos.increase_x();
755
756 let hsegs = pos.active_horizontals_and_orientations();
760
761 let mut w = winding;
764 seg_buf.clear();
765 for (new_seg, same_orientation) in hsegs {
766 let prev_w = w;
767 let orientation = same_orientation == segments.positively_oriented(new_seg);
768 w += W::single(self.tag[new_seg], orientation);
769 let windings = HalfSegmentWindingNumbers {
770 counter_clockwise: w,
771 clockwise: prev_w,
772 };
773 let half_seg = self.new_half_seg(new_seg, p, windings, true, orientation);
774 self.scan_west[half_seg] = scan_west;
775 seg_buf.push(half_seg.first_half());
776 }
777 self.add_segs_counter_clockwise(
778 &mut first_seg,
779 &mut last_seg,
780 seg_buf.iter().copied(),
781 p,
782 );
783 }
784
785 if let Some(seg) = last_connected_down_seg {
786 if let Some(east_nbr) = pos.line().line_entry(pos.seg_range().segs.end) {
787 if !east_nbr.is_in_changed_interval() {
788 self.scan_east[seg] = self.open_segs[east_nbr.seg].front().copied()
789 }
790 }
791 }
792
793 if last_connected_down_seg.is_none() {
796 let seg_range = pos.seg_range().segs;
797 let west_nbr = seg_range
798 .start
799 .checked_sub(1)
800 .and_then(|idx| pos.line().line_segment(idx));
801 let east_nbr = pos.line().line_segment(pos.seg_range().segs.end);
802 let west = west_nbr.map(|idx| *self.open_segs[idx].front().unwrap());
803 let east = east_nbr.map(|idx| *self.open_segs[idx].front().unwrap());
804 if west.is_some() || east.is_some() {
805 self.scan_after.push((y, west, east));
806 }
807 }
808 }
809
810 fn delete_half(&mut self, half_seg: HalfOutputSegIdx) {
811 let nbr = self.point_neighbors[half_seg];
812 self.point_neighbors[nbr.clockwise].counter_clockwise = nbr.counter_clockwise;
813 self.point_neighbors[nbr.counter_clockwise].clockwise = nbr.clockwise;
814 }
815
816 fn delete(&mut self, seg: OutputSegIdx) {
817 self.deleted[seg] = true;
818 self.delete_half(seg.first_half());
819 self.delete_half(seg.second_half());
820 }
821
822 fn is_horizontal(&self, seg: OutputSegIdx) -> bool {
823 self.point(seg.first_half()).y == self.point(seg.second_half()).y
824 }
825
826 fn merge_coincident(&mut self) {
837 for idx in 0..self.winding.inner.len() {
838 let idx = OutputSegIdx(idx);
839 if self.deleted[idx] {
840 continue;
841 }
842 let cc_nbr = self.point_neighbors[idx.first_half()].clockwise;
843 if self.point_idx[idx.second_half()] == self.point_idx[cc_nbr.other_half()] {
844 let y0 = self.point(idx.first_half()).y;
848 let y1 = self.point(idx.second_half()).y;
849 if y0 != y1 {
850 let s1 = self.orig_seg[idx];
851 let s2 = self.orig_seg[cc_nbr];
852 let s1 = y_subsegment(self.segments[s1].to_kurbo_cubic(), y0, y1);
853 let s2 = y_subsegment(self.segments[s2].to_kurbo_cubic(), y0, y1);
854 if (s1.p0 - s2.p0).hypot() > self.eps
855 || (s1.p1 - s2.p1).hypot() > self.eps
856 || (s1.p2 - s2.p2).hypot() > self.eps
857 || (s1.p3 - s2.p3).hypot() > self.eps
858 {
859 continue;
860 }
861 }
862
863 debug_assert!(cc_nbr.first_half);
866 self.delete(idx);
867 self.winding[cc_nbr.idx].counter_clockwise = self.winding[idx].counter_clockwise;
868
869 if self.winding[cc_nbr.idx].is_trivial() {
870 self.delete(cc_nbr.idx);
871 }
872 }
873 }
874 }
875
876 pub fn segment_indices(&self) -> impl Iterator<Item = OutputSegIdx> + '_ {
878 (0..self.winding.inner.len())
879 .map(OutputSegIdx)
880 .filter(|i| !self.deleted[*i])
881 }
882
883 pub fn winding(&self, idx: HalfOutputSegIdx) -> HalfSegmentWindingNumbers<W> {
885 if idx.first_half {
886 self.winding[idx.idx]
887 } else {
888 self.winding[idx.idx].flipped()
889 }
890 }
891
892 pub fn point(&self, idx: HalfOutputSegIdx) -> &Point {
894 &self.points[self.point_idx[idx]]
895 }
896
897 fn segs_to_path(
900 &self,
901 segs: &[HalfOutputSegIdx],
902 positions: &OutputSegVec<(BezPath, Option<usize>)>,
903 ) -> BezPath {
904 let mut ret = BezPath::default();
905 ret.move_to(self.point(segs[0].other_half()).to_kurbo());
906 for seg in segs {
907 let path = &positions[seg.idx];
908 if seg.is_first_half() {
909 ret.extend(path.0.reverse_subpaths().iter().skip(1));
913 } else {
914 ret.extend(path.0.iter().skip(1));
915 }
916 }
917
918 ret.close_path();
919 ret
920 }
921
922 pub fn contours(&self, inside: impl Fn(W) -> bool) -> Contours {
929 let mut ret = Contours::default();
948 let mut seg_contour: Vec<Option<ContourIdx>> = vec![None; self.winding.inner.len()];
949 let positions = self.compute_positions();
950
951 let bdy = |idx: OutputSegIdx| -> bool {
952 inside(self.winding[idx].clockwise) != inside(self.winding[idx].counter_clockwise)
953 };
954
955 let mut visited = vec![false; self.winding.inner.len()];
956 let mut last_visit = PointVec::with_capacity(self.points.inner.len());
959 last_visit.inner.resize(self.points.inner.len(), None);
960 for idx in self.segment_indices() {
961 if visited[idx.0] {
962 continue;
963 }
964
965 if !bdy(idx) {
966 continue;
967 }
968
969 let contour_idx = ContourIdx(ret.contours.len());
972 let mut contour = Contour::default();
973 let mut west_seg = self.scan_west[idx];
974 while let Some(left) = west_seg {
975 if self.deleted[left] || !bdy(left) {
976 west_seg = self.scan_west[left];
977 } else {
978 break;
979 }
980 }
981 if let Some(west) = west_seg {
982 if let Some(west_contour) = seg_contour[west.0] {
983 let outside = !inside(self.winding(west.first_half()).counter_clockwise);
985 if outside == ret.contours[west_contour.0].outer {
986 contour.parent = ret.contours[west_contour.0].parent;
990 contour.outer = outside;
991 debug_assert!(outside || contour.parent.is_some());
992 } else {
993 contour.parent = Some(west_contour);
994 contour.outer = !ret.contours[west_contour.0].outer;
995 }
996 } else {
997 panic!("I'm {idx:?}, west is {west:?}. Y u no have contour?");
998 }
999 };
1000 ret.contours.push(contour);
1002
1003 let (start, mut next) = if inside(self.winding[idx].counter_clockwise) {
1006 (idx.first_half(), idx.second_half())
1007 } else {
1008 (idx.second_half(), idx.first_half())
1009 };
1010 let mut segs = Vec::new();
1014 last_visit[self.point_idx[start]] = Some(0);
1015
1016 debug_assert!(inside(self.winding(start).counter_clockwise));
1017 loop {
1018 visited[next.idx.0] = true;
1019
1020 debug_assert!(inside(self.winding(next).clockwise));
1021 debug_assert!(!inside(self.winding(next).counter_clockwise));
1022
1023 let mut nbr = self.point_neighbors[next].clockwise;
1026 debug_assert!(inside(self.winding(nbr).counter_clockwise));
1027 while inside(self.winding(nbr).clockwise) {
1028 nbr = self.point_neighbors[nbr].clockwise;
1029 }
1030
1031 segs.push(next);
1032 if nbr == start {
1033 break;
1034 }
1035
1036 let p = self.point_idx[nbr];
1037 let last_visit_idx = last_visit[p]
1038 .filter(|&idx| idx < segs.len() && self.point_idx[segs[idx].other_half()] == p);
1045 if let Some(seg_idx) = last_visit_idx {
1046 debug_assert_eq!(self.point_idx[segs[seg_idx].other_half()], p);
1051
1052 let loop_contour_idx = ContourIdx(ret.contours.len());
1053 for &seg in &segs[seg_idx..] {
1054 seg_contour[seg.idx.0] = Some(loop_contour_idx);
1055 }
1056 ret.contours.push(Contour {
1057 path: self.segs_to_path(&segs[seg_idx..], &positions),
1058 parent: Some(contour_idx),
1059 outer: !ret.contours[contour_idx.0].outer,
1060 });
1061 segs.truncate(seg_idx);
1062 } else {
1068 last_visit[p] = Some(segs.len());
1069 }
1070
1071 next = nbr.other_half();
1072 }
1073 for &seg in &segs {
1074 seg_contour[seg.idx.0] = Some(contour_idx);
1075 }
1076 ret.contours[contour_idx.0].path = self.segs_to_path(&segs, &positions);
1077 }
1078
1079 ret
1080 }
1081
1082 pub fn bounding_box(&self) -> kurbo::Rect {
1084 let mut rect = Rect::new(
1085 f64::INFINITY,
1086 f64::INFINITY,
1087 f64::NEG_INFINITY,
1088 f64::NEG_INFINITY,
1089 );
1090 for seg in self.segments.segments() {
1091 rect = rect.union(seg.to_kurbo_cubic().bounding_box());
1092 }
1093 rect
1094 }
1095
1096 fn build_scan_line_orders(&self) -> ScanLineOrder {
1098 let mut west_map: OutputSegVec<Vec<(f64, Option<OutputSegIdx>)>> =
1099 OutputSegVec::with_size(self.winding.len());
1100 let mut east_map: OutputSegVec<Vec<(f64, Option<OutputSegIdx>)>> =
1101 OutputSegVec::with_size(self.winding.len());
1102
1103 for idx in self.scan_west.indices().filter(|i| !self.is_horizontal(*i)) {
1112 let y = self.point(idx.first_half()).y;
1113 if let Some(west) = self.scan_west[idx] {
1114 west_map[idx].push((y, Some(west)));
1115 }
1116 }
1117
1118 for idx in self.scan_east.indices() {
1119 let y = self.point(idx.first_half()).y;
1120 if let Some(east) = self.scan_east[idx] {
1121 east_map[idx].push((y, Some(east)));
1122 if let Some((last_y, _)) = west_map[east].last() {
1124 debug_assert!(y > *last_y);
1125 }
1126
1127 west_map[east].push((y, Some(idx)));
1128 }
1129 }
1130
1131 for idx in self.scan_west.indices().filter(|i| !self.is_horizontal(*i)) {
1132 let y = self.point(idx.first_half()).y;
1133 if let Some(west) = self.scan_west[idx] {
1134 if let Some((last_y, _)) = east_map[west].last() {
1136 debug_assert!(y > *last_y);
1137 }
1138 east_map[west].push((y, Some(idx)));
1139 }
1140 }
1141
1142 for &(y, west, east) in &self.scan_after {
1147 if let Some(east) = east {
1148 west_map[east].push((y, west));
1149 }
1150 if let Some(west) = west {
1151 east_map[west].push((y, east));
1152 }
1153 }
1154 for vec in &mut west_map.inner {
1155 vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
1156 vec.dedup();
1157 }
1158 for vec in &mut east_map.inner {
1159 vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
1160 vec.dedup();
1161 }
1162
1163 let ret = ScanLineOrder::new(west_map, east_map);
1164 #[cfg(feature = "slow-asserts")]
1165 ret.check_invariants(&self.orig_seg, &self.segments);
1166 ret
1167 }
1168
1169 pub fn compute_positions(&self) -> OutputSegVec<(BezPath, Option<usize>)> {
1179 let mut cmp = ComparisonCache::new(self.eps, self.eps / 2.0);
1181 let mut endpoints = HalfOutputSegVec::with_size(self.orig_seg.len());
1182 for idx in self.orig_seg.indices() {
1183 endpoints[idx.first_half()] = self.points[self.point_idx[idx.first_half()]].to_kurbo();
1184 endpoints[idx.second_half()] =
1185 self.points[self.point_idx[idx.second_half()]].to_kurbo();
1186 }
1187
1188 crate::position::compute_positions(
1189 &self.segments,
1190 &self.orig_seg,
1191 &mut cmp,
1192 &endpoints,
1193 &self.build_scan_line_orders(),
1194 self.eps / 4.0,
1199 )
1200 }
1201
1202 #[cfg(feature = "slow-asserts")]
1203 fn check_invariants(&self) {
1204 for out_idx in self.segment_indices() {
1206 if !self.deleted[out_idx] {
1207 for half in [out_idx.first_half(), out_idx.second_half()] {
1208 let cw_nbr = self.point_neighbors[half].clockwise;
1209 if self.winding(half).clockwise != self.winding(cw_nbr).counter_clockwise {
1210 #[cfg(feature = "debug-svg")]
1211 {
1212 let svg = self.dump_svg(|_| "black".to_owned());
1213 svg::save("out.svg", &svg).unwrap();
1214 }
1215 dbg!(half, cw_nbr);
1216 panic!();
1217 }
1218 }
1219 }
1220 }
1221
1222 let mut out_segs = SegVec::<Vec<OutputSegIdx>>::with_size(self.segments.len());
1224 for out_seg in self.winding.indices() {
1225 out_segs[self.orig_seg[out_seg]].push(out_seg);
1226 }
1227 let mut realized_endpoints = Vec::new();
1230 for (in_seg, out_segs) in out_segs.iter_mut() {
1231 out_segs.sort_by(|&o1, &o2| {
1246 let p11 = self.point(o1.first_half());
1247 let p12 = self.point(o1.second_half());
1248 let p21 = self.point(o2.first_half());
1249 let p22 = self.point(o2.second_half());
1250 let horiz1 = p11.y == p12.y;
1251 let horiz2 = p21.y == p22.y;
1252
1253 let cmp = (p11.y, p12.y).partial_cmp(&(p21.y, p22.y)).unwrap();
1256 let cmp = cmp.then((p11.x, p12.x).partial_cmp(&(p21.x, p22.x)).unwrap());
1257 if horiz1 && horiz2 {
1258 debug_assert_eq!(self.positively_oriented[o1], self.positively_oriented[o2]);
1262 if self.positively_oriented[o1] == self.segments.positively_oriented(in_seg) {
1263 cmp
1264 } else {
1265 cmp.reverse()
1266 }
1267 } else {
1268 cmp
1269 }
1270 });
1271
1272 let mut first = None;
1273 let mut last = None;
1274
1275 for &out_seg in &*out_segs {
1276 let same_orientation =
1277 self.positively_oriented[out_seg] == self.segments.positively_oriented(in_seg);
1278 let (first_endpoint, second_endpoint) = if same_orientation {
1279 (out_seg.first_half(), out_seg.second_half())
1280 } else {
1281 (out_seg.second_half(), out_seg.first_half())
1282 };
1283 if first.is_none() {
1284 first = Some(first_endpoint);
1285 }
1286
1287 if let Some(last) = last {
1290 assert_eq!(self.point_idx[first_endpoint], self.point_idx[last]);
1291 }
1292 last = Some(second_endpoint);
1293 }
1294
1295 let first = first.unwrap();
1296 let last = last.unwrap();
1297 let (first, last) = if self.segments.positively_oriented(in_seg) {
1298 (first, last)
1299 } else {
1300 (last, first)
1301 };
1302 realized_endpoints.push((self.point_idx[first], self.point_idx[last]));
1303 }
1304
1305 for seg in self.segments.indices() {
1306 if let Some(prev) = self.segments.contour_prev(seg) {
1307 assert_eq!(realized_endpoints[prev.0].1, realized_endpoints[seg.0].0);
1308 }
1309 }
1310 }
1311
1312 #[cfg(feature = "debug-svg")]
1314 pub fn dump_svg(&self, tag_color: impl Fn(W::Tag) -> String) -> svg::Document {
1315 let mut bbox = Rect::new(
1316 f64::INFINITY,
1317 f64::INFINITY,
1318 f64::NEG_INFINITY,
1319 f64::NEG_INFINITY,
1320 );
1321 let mut document = svg::Document::new();
1322 let stroke_width = 1.0;
1323 let point_radius = 1.5;
1324 for seg in self.segment_indices() {
1325 let mut data = svg::node::element::path::Data::new();
1326 let p = |point: Point| (point.x, point.y);
1327 let p0 = p(*self.point(seg.first_half()));
1328 let p1 = p(*self.point(seg.second_half()));
1329 bbox = bbox.union_pt(p0.into());
1330 bbox = bbox.union_pt(p1.into());
1331 data = data.move_to(p0);
1332 data = data.line_to(p1);
1333 let color = tag_color(self.tag[self.orig_seg[seg]]);
1334 let path = svg::node::element::Path::new()
1335 .set("id", format!("{seg:?}"))
1336 .set("class", format!("{:?}", self.orig_seg[seg]))
1337 .set("stroke", color)
1338 .set("stroke-width", stroke_width)
1339 .set("stroke-linecap", "round")
1340 .set("stroke-linejoin", "round")
1341 .set("opacity", 0.2)
1342 .set("fill", "none")
1343 .set("d", data);
1344 document = document.add(path);
1345
1346 let text = svg::node::element::Text::new(format!("{seg:?}",))
1347 .set("font-size", 8.0)
1348 .set("text-anchor", "middle")
1349 .set("x", (p0.0 + p1.0) / 2.0)
1350 .set("y", (p0.1 + p1.1) / 2.0);
1351 document = document.add(text);
1352 }
1353
1354 for p_idx in self.points.indices() {
1355 let p = self.points[p_idx];
1356 let c = svg::node::element::Circle::new()
1357 .set("id", format!("{p_idx:?}"))
1358 .set("cx", p.x)
1359 .set("cy", p.y)
1360 .set("r", point_radius)
1361 .set("stroke", "none")
1362 .set("fill", "black");
1363 document = document.add(c);
1364 }
1365
1366 bbox = bbox.inset(2.0);
1367 document = document.set(
1368 "viewBox",
1369 (bbox.min_x(), bbox.min_y(), bbox.width(), bbox.height()),
1370 );
1371 document
1372 }
1373}
1374
1375impl Topology<i32> {
1376 pub fn from_path(path: &BezPath, eps: f64) -> Result<Self, NonClosedPath> {
1378 Self::from_paths(std::iter::once((path, ())), eps)
1379 }
1380}
1381
1382impl Topology<BinaryWindingNumber> {
1383 pub fn from_polylines_binary(
1389 set_a: impl IntoIterator<Item = impl IntoIterator<Item = Point>>,
1390 set_b: impl IntoIterator<Item = impl IntoIterator<Item = Point>>,
1391 eps: f64,
1392 ) -> Self {
1393 let mut segments = Segments::default();
1394 let mut shape_a = Vec::new();
1395 segments.add_closed_polylines(set_a);
1396 shape_a.resize(segments.len(), true);
1397 segments.add_closed_polylines(set_b);
1398 shape_a.resize(segments.len(), false);
1399 Self::from_segments(segments, SegVec::from_vec(shape_a), eps)
1400 }
1401
1402 pub fn from_paths_binary(
1406 set_a: &BezPath,
1407 set_b: &BezPath,
1408 eps: f64,
1409 ) -> Result<Self, NonClosedPath> {
1410 Self::from_paths([(set_a, true), (set_b, false)], eps)
1411 }
1412}
1413
1414#[cfg_attr(test, derive(serde::Serialize))]
1416#[derive(Clone, Debug)]
1417struct HalfScanLineOrder {
1418 inner: OutputSegVec<Vec<(f64, Option<OutputSegIdx>)>>,
1419}
1420
1421impl HalfScanLineOrder {
1422 fn neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option<OutputSegIdx> {
1423 self.iter(seg)
1425 .take_while(|(y0, _, _)| *y0 <= y)
1426 .find(|(_, y1, _)| *y1 > y)
1427 .and_then(|(_, _, idx)| idx)
1428 }
1429
1430 fn iter(
1438 &self,
1439 seg: OutputSegIdx,
1440 ) -> impl Iterator<Item = (f64, f64, Option<OutputSegIdx>)> + '_ {
1441 let ends = self.inner[seg]
1442 .iter()
1443 .map(|(y0, _)| *y0)
1444 .skip(1)
1445 .chain(std::iter::once(f64::INFINITY));
1446 self.inner[seg]
1447 .iter()
1448 .zip(ends)
1449 .map(|((y0, maybe_seg), y1)| (*y0, y1, *maybe_seg))
1450 }
1451
1452 fn close_neighbor_height_after(
1453 &self,
1454 seg: OutputSegIdx,
1455 y: f64,
1456 orig_seg: &OutputSegVec<SegIdx>,
1457 segs: &Segments,
1458 cmp: &mut ComparisonCache,
1459 ) -> Option<f64> {
1460 for (_, y1, other_seg) in self.iter(seg) {
1461 if y1 <= y {
1462 continue;
1463 }
1464
1465 if let Some(other_seg) = other_seg {
1466 let order = cmp.compare_segments(segs, orig_seg[seg], orig_seg[other_seg]);
1467 let next_ish = order
1468 .iter()
1469 .take_while(|(order_y0, _, _)| *order_y0 < y1)
1470 .filter(|(_, _, order)| *order == Order::Ish)
1471 .find(|(order_y0, _, _)| *order_y0 >= y);
1472 if let Some((order_y0, _, _)) = next_ish {
1473 return Some(order_y0);
1474 }
1475 }
1476 }
1477 None
1478 }
1479}
1480
1481#[cfg_attr(test, derive(serde::Serialize))]
1484#[derive(Clone, Debug)]
1485pub struct ScanLineOrder {
1486 west_map: HalfScanLineOrder,
1489 east_map: HalfScanLineOrder,
1492}
1493
1494impl ScanLineOrder {
1495 fn new(
1496 west: OutputSegVec<Vec<(f64, Option<OutputSegIdx>)>>,
1497 east: OutputSegVec<Vec<(f64, Option<OutputSegIdx>)>>,
1498 ) -> Self {
1499 Self {
1500 west_map: HalfScanLineOrder { inner: west },
1501 east_map: HalfScanLineOrder { inner: east },
1502 }
1503 }
1504
1505 pub fn west_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option<OutputSegIdx> {
1507 self.west_map.neighbor_after(seg, y)
1508 }
1509
1510 pub fn east_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option<OutputSegIdx> {
1512 self.east_map.neighbor_after(seg, y)
1513 }
1514
1515 pub fn close_east_neighbor_height_after(
1518 &self,
1519 seg: OutputSegIdx,
1520 y: f64,
1521 orig_seg: &OutputSegVec<SegIdx>,
1522 segs: &Segments,
1523 cmp: &mut ComparisonCache,
1524 ) -> Option<f64> {
1525 self.east_map
1526 .close_neighbor_height_after(seg, y, orig_seg, segs, cmp)
1527 }
1528
1529 pub fn close_west_neighbor_height_after(
1532 &self,
1533 seg: OutputSegIdx,
1534 y: f64,
1535 orig_seg: &OutputSegVec<SegIdx>,
1536 segs: &Segments,
1537 cmp: &mut ComparisonCache,
1538 ) -> Option<f64> {
1539 self.west_map
1540 .close_neighbor_height_after(seg, y, orig_seg, segs, cmp)
1541 }
1542
1543 #[cfg(feature = "slow-asserts")]
1544 fn check_invariants(&self, orig_seg: &OutputSegVec<SegIdx>, segs: &Segments) {
1545 for idx in self.east_map.inner.indices() {
1546 for &(y, east_idx) in &self.east_map.inner[idx] {
1547 if let Some(east_idx) = east_idx {
1548 let seg = &segs[orig_seg[idx]];
1549 let east_seg = &segs[orig_seg[east_idx]];
1550 assert!(y >= seg.start().y && y >= east_seg.start().y);
1551 assert!(y < seg.end().y && y < east_seg.end().y);
1552 }
1553 }
1554 for &(y, west_idx) in &self.west_map.inner[idx] {
1555 if let Some(west_idx) = west_idx {
1556 let seg = &segs[orig_seg[idx]];
1557 let west_seg = &segs[orig_seg[west_idx]];
1558 assert!(y >= seg.start().y && y >= west_seg.start().y);
1559 assert!(y < seg.end().y && y < west_seg.end().y);
1560 }
1561 }
1562 }
1563 }
1564}
1565
1566#[cfg_attr(test, derive(serde::Serialize))]
1568#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1569pub struct ContourIdx(pub usize);
1570
1571#[cfg_attr(test, derive(serde::Serialize))]
1575#[derive(Clone, Debug)]
1576pub struct Contour {
1577 pub path: BezPath,
1579
1580 pub parent: Option<ContourIdx>,
1648
1649 pub outer: bool,
1656}
1657
1658impl Default for Contour {
1659 fn default() -> Self {
1660 Self {
1661 path: BezPath::default(),
1662 outer: true,
1663 parent: None,
1664 }
1665 }
1666}
1667
1668#[cfg_attr(test, derive(serde::Serialize))]
1672#[derive(Clone, Debug, Default)]
1673pub struct Contours {
1674 contours: Vec<Contour>,
1675}
1676
1677impl Contours {
1678 pub fn grouped(&self) -> Vec<Vec<ContourIdx>> {
1684 let mut children = vec![Vec::new(); self.contours.len()];
1685 let mut top_level = Vec::new();
1686 for i in 0..self.contours.len() {
1687 if let Some(parent) = self.contours[i].parent {
1688 children[parent.0].push(ContourIdx(i));
1689 } else {
1690 top_level.push(ContourIdx(i));
1691 }
1692 }
1693
1694 let mut ret = Vec::with_capacity(top_level.len());
1695 for top in top_level {
1696 let mut tree = Vec::new();
1697 fn visit(idx: ContourIdx, children: &[Vec<ContourIdx>], acc: &mut Vec<ContourIdx>) {
1698 acc.push(idx);
1699 for &child in &children[idx.0] {
1700 visit(child, children, acc);
1701 }
1702 }
1703 visit(top, &children, &mut tree);
1704 ret.push(tree);
1705 }
1706
1707 ret
1708 }
1709
1710 pub fn contours(&self) -> impl Iterator<Item = &Contour> + '_ {
1712 self.contours.iter()
1713 }
1714}
1715
1716impl std::ops::Index<ContourIdx> for Contours {
1717 type Output = Contour;
1718
1719 fn index(&self, index: ContourIdx) -> &Self::Output {
1720 &self.contours[index.0]
1721 }
1722}
1723
1724#[cfg(test)]
1725mod tests {
1726 use proptest::prelude::*;
1727
1728 use crate::{
1729 geom::Point,
1730 order::ComparisonCache,
1731 perturbation::{
1732 f64_perturbation, perturbation, realize_perturbation, F64Perturbation, Perturbation,
1733 },
1734 SegIdx,
1735 };
1736
1737 use super::Topology;
1738
1739 fn p(x: f64, y: f64) -> Point {
1740 Point::new(x, y)
1741 }
1742
1743 const EMPTY: [[Point; 0]; 0] = [];
1744
1745 #[test]
1746 fn square() {
1747 let segs = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]];
1748 let eps = 0.01;
1749 let top = Topology::from_polylines_binary(segs, EMPTY, eps);
1750 insta::assert_ron_snapshot!(top);
1753 }
1754
1755 #[test]
1756 fn diamond() {
1757 let segs = [[p(0.0, 0.0), p(1.0, 1.0), p(0.0, 2.0), p(-1.0, 1.0)]];
1758 let eps = 0.01;
1759 let top = Topology::from_polylines_binary(segs, EMPTY, eps);
1760 insta::assert_ron_snapshot!(top);
1763 }
1764
1765 #[test]
1766 fn square_and_diamond() {
1767 let square = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]];
1768 let diamond = [[p(0.0, 0.0), p(1.0, 1.0), p(0.0, 2.0), p(-1.0, 1.0)]];
1769 let eps = 0.01;
1770 let top = Topology::from_polylines_binary(square, diamond, eps);
1771 insta::assert_ron_snapshot!(top);
1774 }
1775
1776 #[test]
1777 fn square_with_double_back() {
1778 let segs = [[
1779 p(0.0, 0.0),
1780 p(0.5, 0.0),
1781 p(0.5, 0.5),
1782 p(0.5, 0.0),
1783 p(1.0, 0.0),
1784 p(1.0, 1.0),
1785 p(0.0, 1.0),
1786 ]];
1787 let eps = 0.01;
1788 let top = Topology::from_polylines_binary(segs, EMPTY, eps);
1789 insta::assert_ron_snapshot!(top);
1792 }
1793
1794 #[test]
1795 fn nested_squares() {
1796 let outer = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]];
1797 let inner = [[p(-1.0, -1.0), p(1.0, -1.0), p(1.0, 1.0), p(-1.0, 1.0)]];
1798 let eps = 0.01;
1799 let top = Topology::from_polylines_binary(outer, inner, eps);
1800 let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0);
1801
1802 insta::assert_ron_snapshot!((&top, contours, top.build_scan_line_orders()));
1803
1804 let out_idx = top.orig_seg.indices().collect::<Vec<_>>();
1805 let orders = top.build_scan_line_orders();
1806 assert_eq!(orders.west_neighbor_after(out_idx[0], -2.0), None);
1807 assert_eq!(orders.east_neighbor_after(out_idx[0], -2.2), None);
1808 assert_eq!(
1809 orders.east_neighbor_after(out_idx[0], -2.0),
1810 Some(out_idx[2])
1811 );
1812 assert_eq!(
1813 orders.east_neighbor_after(out_idx[0], -1.5),
1814 Some(out_idx[2])
1815 );
1816 assert_eq!(
1817 orders.east_neighbor_after(out_idx[0], -1.0),
1818 Some(out_idx[3])
1819 );
1820 assert_eq!(
1821 orders.east_neighbor_after(out_idx[0], 1.0),
1822 Some(out_idx[2])
1823 );
1824 assert_eq!(
1826 orders.east_neighbor_after(out_idx[0], 2.0),
1827 Some(out_idx[2])
1828 );
1829 }
1830
1831 #[test]
1832 fn squares_with_gaps() {
1833 let mid = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]];
1834 let left_right = [
1835 [p(-4.0, -1.0), p(-3.0, -1.0), p(-3.0, 1.0), p(-4.0, 1.0)],
1836 [p(4.0, -1.0), p(3.0, -1.0), p(3.0, -0.5), p(4.0, -0.5)],
1837 [p(4.0, 1.0), p(3.0, 1.0), p(3.0, 0.5), p(4.0, 0.5)],
1838 ];
1839 let eps = 0.01;
1840 let top = Topology::from_polylines_binary(mid, left_right, eps);
1841 let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0);
1842
1843 insta::assert_ron_snapshot!((&top, contours, top.build_scan_line_orders()));
1844 }
1845
1846 #[test]
1847 fn close_neighbor_height() {
1848 let big = [[p(0.0, 0.0), p(0.0, 10.0), p(1.0, 10.0), p(1.0, 0.0)]];
1849 let left = [
1850 [p(-10.0, 0.5), p(-0.25, 2.0), p(-10.0, 10.0)],
1851 [p(-10.0, 0.5), p(-0.25, 10.0), p(-10.0, 10.0)],
1852 ];
1853 let eps = 0.5;
1854 let top = Topology::from_polylines_binary(big, left, eps);
1855
1856 let indices: Vec<_> = top
1858 .orig_seg
1859 .indices()
1860 .filter(|i| top.orig_seg[*i] == SegIdx(0))
1861 .collect();
1862
1863 assert_eq!(indices.len(), 2);
1864 let orders = top.build_scan_line_orders();
1865 let mut cmp = ComparisonCache::new(eps, eps / 2.0);
1866
1867 let h = orders
1868 .close_west_neighbor_height_after(
1869 indices[0],
1870 0.0,
1871 &top.orig_seg,
1872 &top.segments,
1873 &mut cmp,
1874 )
1875 .unwrap();
1876 assert!((0.5..=2.0).contains(&h));
1877
1878 let h = orders
1879 .close_west_neighbor_height_after(
1880 indices[0],
1881 0.6,
1882 &top.orig_seg,
1883 &top.segments,
1884 &mut cmp,
1885 )
1886 .unwrap();
1887 assert!((0.5..=2.0).contains(&h));
1888
1889 let h = orders
1890 .close_west_neighbor_height_after(
1891 indices[1],
1892 5.0,
1893 &top.orig_seg,
1894 &top.segments,
1895 &mut cmp,
1896 )
1897 .unwrap();
1898 assert!((8.0..=10.0).contains(&h));
1899 }
1900
1901 #[test]
1902 fn inner_loop() {
1903 let outer = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]];
1904 let inners = [
1905 [p(-1.5, -1.0), p(0.0, 2.0), p(1.5, -1.0)],
1906 [p(-0.1, 0.0), p(0.0, 2.0), p(0.1, 0.0)],
1907 ];
1908 let eps = 0.01;
1909 let top = Topology::from_polylines_binary(outer, inners, eps);
1910 let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0);
1911
1912 insta::assert_ron_snapshot!((top, contours));
1913 }
1914
1915 fn run_perturbation(ps: Vec<Perturbation<F64Perturbation>>) {
1947 let base = vec![vec![
1948 p(0.0, 0.0),
1949 p(1.0, 1.0),
1950 p(1.0, -1.0),
1951 p(2.0, 0.0),
1952 p(1.0, 1.0),
1953 p(1.0, -1.0),
1954 ]];
1955 let perturbed_polylines = ps
1956 .iter()
1957 .map(|p| realize_perturbation(&base, p))
1958 .collect::<Vec<_>>();
1959 let eps = 0.1;
1960 let _top = Topology::from_polylines_binary(perturbed_polylines, EMPTY, eps);
1961 }
1963
1964 proptest! {
1965 #[test]
1966 fn perturbation_test_f64(perturbations in prop::collection::vec(perturbation(f64_perturbation(0.1)), 1..5)) {
1967 run_perturbation(perturbations);
1968 }
1969 }
1970}