linesweeper/
topology.rs

1//! Utilities for computing topological properties of closed paths.
2//!
3//! This consumes the output of the sweep-line algorithm and does things
4//! like winding number computations and boolean operations.
5
6use 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
20/// An abstraction over winding numbers.
21///
22/// The windings numbers of a set can just be represented by integers. But if
23/// you're doing boolean operations over two sets, you need both winding numbers
24/// to determine whether a point is inside or outside the output set. Since we
25/// also want to support more complicated situations (ternary operations, non-closed
26/// paths for which winding numbers aren't defined, etc.), our topologies use generic
27/// winding numbers: anything that implements this trait can be used.
28pub trait WindingNumber:
29    Copy + std::fmt::Debug + std::ops::Add<Output = Self> + std::ops::AddAssign + Default + Eq
30{
31    /// A tag for categorizing segments.
32    ///
33    /// When building a topology, you can assign to each segment a tag, which can then be
34    /// used to figure out that segment's winding number contribution.
35    #[cfg(not(test))]
36    type Tag: Copy + std::fmt::Debug + Eq;
37
38    /// A tag for categorizing segments.
39    ///
40    /// When building a topology, you can assign to each segment a tag, which can then be
41    /// used to figure out that segment's winding number contribution.
42    #[cfg(test)]
43    type Tag: Copy + std::fmt::Debug + Eq + serde::Serialize;
44
45    /// What is the winding number of a simple curve with tag `tag`?
46    fn single(tag: Self::Tag, positive: bool) -> Self;
47}
48
49/// Winding numbers for binary set operations.
50///
51/// This is just two integers, one for each set. Segments are tagged by
52/// booleans, where `true` means that the segment is part of the first set.
53#[cfg_attr(test, derive(serde::Serialize))]
54#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)]
55pub struct BinaryWindingNumber {
56    /// The winding number of the first shape.
57    pub shape_a: i32,
58    /// The winding number of the second shape.
59    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/// For a segment, we store two winding numbers (one on each side of the segment).
117///
118/// For simple segments, the winding numbers on two sides only differ by one. Once
119/// we merge segments, they can differ by more.
120#[cfg_attr(test, derive(serde::Serialize))]
121#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)]
122pub struct HalfSegmentWindingNumbers<W: WindingNumber> {
123    /// This half-segment is incident to a point. Imagine you're standing at
124    /// that point, looking out along the segment. This is the winding number of
125    /// the area just counter-clockwise (to the left, from your point of view)
126    /// of the segment.
127    pub counter_clockwise: W,
128    /// This half-segment is incident to a point. Imagine you're standing at
129    /// that point, looking out along the segment. This is the winding number of
130    /// the area just clockwise (to the right, from your point of view) of the segment.
131    pub clockwise: W,
132}
133
134impl<W: WindingNumber> HalfSegmentWindingNumbers<W> {
135    /// A half-segment's winding numbers are trivial if they're the same on both sides.
136    /// In this case, the segment is invisible to the topology of the sets.
137    fn is_trivial(&self) -> bool {
138        self.counter_clockwise == self.clockwise
139    }
140
141    /// Returns the winding numbers of our opposite half-segment.
142    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/// An index into the set of output segments.
161///
162/// There's no compile-time magic preventing misuse of this index, but you
163/// should only use this to index into the [`Topology`] that you got it from.
164#[cfg_attr(test, derive(serde::Serialize))]
165#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
166pub struct OutputSegIdx(usize);
167
168impl OutputSegIdx {
169    /// Returns an index to the first half of this output segment.
170    pub fn first_half(self) -> HalfOutputSegIdx {
171        HalfOutputSegIdx {
172            idx: self,
173            first_half: true,
174        }
175    }
176
177    /// Returns an index to the second half of this output segment.
178    pub fn second_half(self) -> HalfOutputSegIdx {
179        HalfOutputSegIdx {
180            idx: self,
181            first_half: false,
182        }
183    }
184}
185
186/// A vector indexed by output segments.
187///
188/// See [`OutputSegIdx`] for more about output segments.
189#[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/// An index that refers to one end of an output segment.
198///
199/// The two ends of an output segment are sweep-line ordered: the "first" half
200/// has a smaller `y` coordinate (or smaller `x` coordinate if the `y`s are
201/// tied) than the "second" half.
202#[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    /// Returns the index pointing to the other end of this output segment.
211    pub fn other_half(self) -> Self {
212        Self {
213            idx: self.idx,
214            first_half: !self.first_half,
215        }
216    }
217
218    /// Do we point to the first half of the output segment?
219    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/// A vector indexed by half-output segments.
235#[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    /// Creates a new vector that can store `cap` output segments without re-allocating.
244    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    /// Creates a new vector with `size` output segments.
254    ///
255    /// Both halves of each output segment are initialized to their default values.
256    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/// An index into the set of points.
336#[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/// Consumes sweep-line output and computes topology.
362#[cfg_attr(test, derive(serde::Serialize))]
363#[derive(Clone, Debug)]
364pub struct Topology<W: WindingNumber> {
365    eps: f64,
366    /// The user-provided tags for each segment.
367    tag: SegVec<W::Tag>,
368    /// For each input segment, this is the list of output segments that we've started
369    /// recording but haven't finished with. There can be up to three of them, because
370    /// consider a segment that passes through a sweep-line like this:
371    ///
372    /// ```text
373    ///           /
374    ///          /
375    /// (*) /---/
376    ///    /
377    ///   /
378    /// ```
379    ///
380    /// When we come to process the sweep-line at height (*), we'll already have the
381    /// unfinished output segment coming from above. But before dealing with it, we'll
382    /// first encounter the output segment pointing down and add an unfinished segment
383    /// for that. Then we'll add an output segment for the horizontal line and so
384    /// at that point there will be three unfinished output segments.
385    open_segs: SegVec<VecDeque<OutputSegIdx>>,
386    /// Winding numbers of each segment.
387    ///
388    /// This is sort of logically indexed by `HalfOutputSegIdx`, because we can look at the
389    /// `HalfSegmentWindingNumbers` for each `HalfOutputSegIdx`. But since the two halves of
390    /// the winding numbers are determined by one another, we only store the winding numbers
391    /// for the start half of the output segment.
392    winding: OutputSegVec<HalfSegmentWindingNumbers<W>>,
393    /// The output points.
394    points: PointVec<Point>,
395    /// The segment endpoints, as indices into `points`.
396    point_idx: HalfOutputSegVec<PointIdx>,
397    /// For each output half-segment, its neighboring segments are the ones that share a point with it.
398    point_neighbors: HalfOutputSegVec<PointNeighbors>,
399    /// Marks the output segments that have been deleted due to merges of coindident segments.
400    deleted: OutputSegVec<bool>,
401    /// For each non-horizontal output segment, if we go just a tiny bit south of its starting
402    /// position then which output segment is to the west?
403    ///
404    /// The map from a segment to its scan-west neighbor is always strictly decreasing (in the
405    /// index). This ensures that a scan will always terminate, and it also means that we can
406    /// build the contours in increasing `OutputSegIdx` order.
407    scan_west: OutputSegVec<Option<OutputSegIdx>>,
408    /// For each non-horizontal output segment that's at the east edge of its sweep-line-range,
409    /// this is the segment to the east... but *only* if the segment to the east continues through
410    /// the sweep-line where this output segment started.
411    ///
412    /// That is, in this situation (where the `->` points to our sweep-line) there will be no
413    /// "scan_east" relation from s1 to s2:
414    ///
415    /// ```text
416    /// \            /
417    ///  \          /
418    ///   \        /
419    /// -> ◦      ◦
420    ///    |      |
421    ///    |      |
422    ///    |      |
423    ///  (s1)   (s2)
424    /// ```
425    ///
426    /// However, in this situation there will be a "scan_east" relation from s1 to s2.
427    ///
428    /// ```text
429    /// \         |
430    ///  \        |
431    ///   \       |
432    /// -> ◦      |
433    ///    |      |
434    ///    |      |
435    ///    |      |
436    ///   (s1)   (s2)
437    /// ```
438    ///
439    /// The reason behind this seemingly-arbitrary choice is that in the former situation
440    /// it's harder to build up `scan_east` (because the output segment `s2` hasn't bee
441    /// created when we process `s1`) and it's redundant with the `scan_west` relation from
442    /// `s2` to `s1`.
443    scan_east: OutputSegVec<Option<OutputSegIdx>>,
444    /// This stores all the situations where two segments become scan-line
445    /// neighbors because something in between dropped out. For example:
446    ///
447    /// ```text
448    ///  |    \  |      |
449    ///  |     \ |      |
450    ///  |      \|      |
451    ///  |       ◦      | <- y
452    ///  |              |
453    ///  |              |
454    ///  |              |
455    /// (s1)           (s2)
456    /// ```
457    ///
458    /// Here we would add an entry `(y, s1, s2)` to `scan_after`. If `s2`
459    /// weren't there (and there was nothing else further right), we'd add `(y,
460    /// s1, None)` to denote that `s1` has no scan-east neighbor after `y`.
461    scan_after: Vec<(f64, Option<OutputSegIdx>, Option<OutputSegIdx>)>,
462    /// For each output segment, the input segment that it came from. Will probably become
463    /// private at some point (TODO)
464    pub orig_seg: OutputSegVec<SegIdx>,
465    // TODO: probably don't have this owned
466    /// The collection of segments used to build this topology. Will probably become private
467    /// at some point (TODO)
468    #[cfg_attr(test, serde(skip))]
469    pub segments: Segments,
470    /// Does the sweep-line orientation of each output segment agree with its original
471    /// orientation from the input?
472    ///
473    /// In principle, this is redundant with the winding numbers. But since the winding
474    /// numbers are generic, that information isn't available to us.
475    positively_oriented: OutputSegVec<bool>,
476}
477
478impl<W: WindingNumber> Topology<W> {
479    /// Construct a new topology from a collection of paths and tags.
480    ///
481    /// See [`WindingNumber`] about the tags are for.
482    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            // We have at least as many output segments as input segments, so preallocate enough for them.
504            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        //dbg!(&segments);
520        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    /// We're working on building up a list of half-segments that all meet at a point.
547    /// Maybe we've done a few already, but there's a region where we may add more.
548    /// Something like this:
549    ///
550    /// ```text
551    ///         \
552    ///          \  ?
553    ///           \  ?
554    ///       -----o  ?
555    ///           /|  ?
556    ///          / |?
557    ///         /  |
558    /// ```
559    ///
560    /// `first_seg` is the most-counter-clockwise segment we've added so far
561    /// (the one pointing down in the picture above, and `last_seg` is the
562    /// most-clockwise one (pointing up-left in the picture above). These can be
563    /// `None` if we haven't actually added any segments left.
564    ///
565    /// This method adds more segments to the picture, starting at `last_seg`
566    /// and working clockwise. It works only with segment indices, so it's the
567    /// caller's responsibility to ensure that the geometry is correct, and that
568    /// the provided segments actually go in clockwise order (relative to each
569    /// other, and also relative to the segments we've already placed).
570    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    /// Like `add_segs_clockwise`, but adds them on the other side.
595    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    /// Takes some segments where we've already placed the first half, and
620    /// gets ready to place the second half.
621    ///
622    /// The state-tracking is subtle and should be re-considered. The basic
623    /// issue is that (as discussed in the documentation for `open_segs`) a
624    /// single segment index can have three open half-segments at any one time,
625    /// so how do we know which one is ready for its second half? The short
626    /// answer is that we use a double-ended queue, and see `new_half_seg`
627    /// for how we use it.
628    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    /// Creates a new half-segment.
641    ///
642    /// This needs to update the open segment state to be compatible with `second_half_segs`.
643    ///
644    /// The key is that we know the order that the segments are processed: any
645    /// horizontal segments will be closed first, followed by segments coming
646    /// from an earlier sweep-line, followed by segments extending down from
647    /// this sweep-line (which won't be closed until we move on to the next
648    /// sweep-line). Therefore, we push horizontal half-segments to the front
649    /// of the queue so that they can be taken next. We push non-horizontal
650    /// half-segments to the back of the queue, so that the older ones (coming
651    /// from the previous sweep-line) will get taken before the new ones.
652    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            // TODO: maybe an option instead of this weird sentinel
670            .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        // A re-usable buffer for holding temporary lists of segment indices. We
699        // need to collect the output of second_half_segments because it holds
700        // a &mut self reference.
701        let mut seg_buf = Vec::new();
702        let mut connected_segs = SegmentsConnectedAtX::default();
703        // The last segment we saw that points down from this sweep line.
704        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            // The first segment at our current point, in clockwise order.
710            let mut first_seg = None;
711            // The last segment at our current point, in clockwise order.
712            let mut last_seg = None;
713
714            // Close off the horizontal segments from the previous point in this sweep-line.
715            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            // Find all the segments that are connected to something above this sweep-line at next_x.
721            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            // Then: gather the output segments from half-segments starting here and moving
727            // to later sweep-lines. Allocate new output segments for them.
728            // Also, calculate their winding numbers and update `winding`.
729            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            // Bump the current x position, which will get rid of horizontals ending here
753            // and add any horizontals starting here.
754            pos.increase_x();
755
756            // Gather the output segments from horizontal segments starting
757            // here. Allocate new output segments for them and calculate their
758            // winding numbers.
759            let hsegs = pos.active_horizontals_and_orientations();
760
761            // We don't want to update our "global" winding number state because that's supposed
762            // to keep track of the winding number below the current sweep line.
763            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 something was connected up but nothing was connected down, let's remember that
794        // the scan-line order has changed.
795        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    /// After generating the topology, there's a good chance we end up with
827    /// coincident output segments. This method removes coincident segments. If
828    /// a collection of coincident segments has a net winding number of zero,
829    /// this method just removes them all. Otherwise, they are replaced by a
830    /// single segment.
831    ///
832    /// In principle, we could do this as we build the topology. The thing that
833    /// makes it a little bit tricky is that (except for horizontal segments)
834    /// we don't know whether two segments are coincident until we've processed
835    /// their second endpoint.
836    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                // We've found two segments with the same starting and ending points, but
845                // that doesn't mean they're coincident!
846                // TODO: we could use the comparison cache here, if we could keep it around somewhere.
847                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                // All output segments are in sweep line order, so if they're
864                // coincident then they'd better both be first halves.
865                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    /// Iterates over indices of all output segments.
877    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    /// Returns the winding numbers of an output half-segment.
884    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    /// Returns the endpoint of an output half-segment.
893    pub fn point(&self, idx: HalfOutputSegIdx) -> &Point {
894        &self.points[self.point_idx[idx]]
895    }
896
897    /// The segments in `segs` form a closed path, and each one is the ending half
898    /// of its segment.
899    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                // skip(1) leaves off the initial MoveTo, which is unnecessary
910                // because this path starts where the last one ended.
911                // TODO: avoid the allocation in reverse_subpaths
912                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    /// Returns the contours of some set defined by this topology.
923    ///
924    /// The callback function `inside` takes a winding number and returns `true`
925    /// if a point with that winding number should be in the resulting set. For example,
926    /// to compute a boolean "and" using the non-zero winding rule, `inside` should be
927    /// `|w| w.shape_a != 0 && w.shape_b != 0`.
928    pub fn contours(&self, inside: impl Fn(W) -> bool) -> Contours {
929        // We walk contours in sweep-line order of their smallest point. This mostly ensures
930        // that we visit outer contours before we visit their children. However, when the inner
931        // and outer contours share a point, we run into a problem. For example:
932        //
933        // /---------o--------\
934        // |        / \       |
935        // |       /   \      |
936        // \       \   /     /
937        //  \       \_/     /
938        //   \             /
939        //    -------------
940        // (where the top-middle point is supposed to have 4 segments coming out of it; it's
941        // a hard to draw it in ASCII). In this case, we'll "notice" the inner contour when
942        // we realize that we've visited a point twice. At that point, we extract the inner part
943        // into a separate contour and mark it as a child of the outer one. This requires some
944        // slightly sketch indexing, because we need to refer to the outer contour even though
945        // we haven't finished generating it. We solve this by reserving a slot for the unfinished
946        // outer contour as soon as we start walking it.
947        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        // Keep track of the points that were visited on this walk, so that if we re-visit a
957        // point we can split out an additional contour.
958        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            // We found a boundary segment. Let's start by scanning left to figure out where we
970            // are relative to existing contours.
971            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                    // Is the thing just to our left inside or outside the output set?
984                    let outside = !inside(self.winding(west.first_half()).counter_clockwise);
985                    if outside == ret.contours[west_contour.0].outer {
986                        // They're an outer contour, and there's exterior between us and them,
987                        // or they're an inner contour and there's interior between us.
988                        // That means they're our sibling.
989                        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            // Reserve a space for the unfinished outer contour, as described above.
1001            ret.contours.push(contour);
1002
1003            // First, arrange the orientation so that the interior is on our
1004            // left as we walk.
1005            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            // `segs` collects the endpoint of each segment as we walk along it.
1011            // "endpoint" here means "as we walk the contour;" it could be either a first_half
1012            // or a second_half as far as `HalfOutputSegIdx` is concerned.
1013            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                // Walk clockwise around the point until we find the next segment
1024                // that's on the boundary.
1025                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                    // We don't clean up `last_visit` when we finish walking a
1039                    // contour because it could be expensive if there are many
1040                    // small contours. This means that `last_visit[p]` could be
1041                    // a false positive from an earlier contours. We handle this
1042                    // by double-checking that it was actually a time when this
1043                    // contours visited `p`.
1044                    .filter(|&idx| idx < segs.len() && self.point_idx[segs[idx].other_half()] == p);
1045                if let Some(seg_idx) = last_visit_idx {
1046                    // We repeated a point, meaning that we've found an inner contour. Extract
1047                    // it and remove it from the current contour.
1048
1049                    // seg_idx should point to the end of a segment whose start is at p.
1050                    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                    // In principle, we should also be unsetting `last_visit`
1063                    // for all points in the contour we just removed. I *think*
1064                    // we don't need to, because it's impossible for the outer
1065                    // contour to visit any of them anyway. Should check this
1066                    // more carefully.
1067                } 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    /// Returns a rectangle bounding all of our segments.
1083    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    // Doesn't account for deleted segments. I mean, they're still present in the output.
1097    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        // This slightly funny iteration order ensures that we push everything
1104        // in increasing y. `scan_west` (resp. east) contains the *first* west
1105        // (resp east) neighbor of an output segment, so that's the one we
1106        // should push into the west_map (resp east_map) first.
1107
1108        // Filter out horizontal segments, because we don't care about them when
1109        // doing scan line orders (they're present in scan_west because we do
1110        // care about them when doing topology).
1111        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                // Double-check that we're inserting in order.
1123                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                // Double-check that we're inserting in order.
1135                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        // Account for the scan-line changes that were triggered by segments
1143        // ending and revealing other things behind them. It would be nice if
1144        // we could process these in sweep-line order, to avoid the need to sort
1145        // and dedup...
1146        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    /// Computes paths for all the output segments.
1170    ///
1171    /// The `usize` return value tells which segment (if any) in the returned
1172    /// path was the one that was "far" from any other paths. This is really
1173    /// only interesting for diagnosis/visualization so the API should probably
1174    /// be refined somehow to make it optional. (TODO)
1175    ///
1176    /// TODO: We should allow passing in an "inside" callback and then only do
1177    /// positioning for the segments that are on the boundary.
1178    pub fn compute_positions(&self) -> OutputSegVec<(BezPath, Option<usize>)> {
1179        // TODO: reuse the cache from the sweep-line
1180        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            // The sweep-line guarantees that any two segments coming within
1195            // eps / 2 will get noticed by the sweep-line. That means we
1196            // can perturb things by eps / 4 without causing any unexpected
1197            // collisions.
1198            self.eps / 4.0,
1199        )
1200    }
1201
1202    #[cfg(feature = "slow-asserts")]
1203    fn check_invariants(&self) {
1204        // Check that the winding numbers are locally consistent around every point.
1205        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        // Check the continuity of contours.
1223        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        // For each segment, figure out the first and last points of its output-segment-polyline,
1228        // in that segment's natural orientation.
1229        let mut realized_endpoints = Vec::new();
1230        for (in_seg, out_segs) in out_segs.iter_mut() {
1231            // Sort the out segments so that they're in the same order that the segment will
1232            // visit them if it's traversed in sweep-line order. This is almost the same as
1233            // sorting the out segments by sweep-line order, but there's one little wrinkle
1234            // for horizontal segments: in a situation like this:
1235            //
1236            //            /
1237            //           /
1238            //    o--o--o
1239            //   /
1240            //  /
1241            //
1242            // where there are multiple horizontal out segments on the same sweep line, we
1243            // need to sort those segments relative to one another depending on the direction
1244            // in which the input segment traverses that sweep line.
1245            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                // Compare the y positions first, so that horizontal segments will get sorted
1254                // after segments coming from above and before segments going down, no matter the x positions.
1255                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                    // We can create multiple horizontal segments for a segment in the same
1259                    // sweep line, but currently we don't create any backtracking: all the
1260                    // horizontal segments we create have the same orientation.
1261                    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                // When walking the output segments in sweep-line order, the last endpoint of
1288                // the previous one should be the first endpoint of this one.
1289                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    /// Renders out our state as an svg.
1313    #[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    /// Construct a new topology from a single path.
1377    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    /// Creates a new `Topology` for two collections of polylines and a given tolerance.
1384    ///
1385    /// Each "set" is a collection of sequences of points; each sequence of
1386    /// points is interpreted as a closed polyline (i.e. the last point will be
1387    /// connected back to the first one).
1388    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    /// Creates a new `Topology` from two Bézier paths.
1403    ///
1404    /// The two Bézier paths represent two different sets for the purpose of boolean set operations.
1405    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/// One direction of a `ScanLineOrder`.
1415#[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        // TODO: maybe binary search, if this might get big?
1424        self.iter(seg)
1425            .take_while(|(y0, _, _)| *y0 <= y)
1426            .find(|(_, y1, _)| *y1 > y)
1427            .and_then(|(_, _, idx)| idx)
1428    }
1429
1430    /// Returns an iterator over `(y0, y1, maybe_seg)`.
1431    ///
1432    /// Each item in the iterator tells us that `maybe_seg` is `seg`'s neighbor
1433    /// between heights `y0` and `y1`. If `maybe_seg` is `None`, it means `seg`
1434    /// has no neighbor between heights `y0` and `y1`. The y intervals in this
1435    /// iterator are guaranteed to be in increasing order, which each `y0` equal to
1436    /// the previous `y1`. The last `y1` will be `f64::INFINITY`.
1437    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/// A summary of all the east-west ordering relations between non-horizontal
1482/// output segments.
1483#[cfg_attr(test, derive(serde::Serialize))]
1484#[derive(Clone, Debug)]
1485pub struct ScanLineOrder {
1486    /// Each entry is a list of `(y, west_neighbor)`: starting at `y`, `west_neighbor`
1487    /// is the segment to my west. The `y`s are in increasing order.
1488    west_map: HalfScanLineOrder,
1489    /// Each entry is a list of `(y, east_neighbor)`: starting at `y`, `east_neighbor`
1490    /// is the segment to my east. The `y`s are in increasing order.
1491    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    /// Returns the neighbor to the west of `seg` just after height `y`.
1506    pub fn west_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option<OutputSegIdx> {
1507        self.west_map.neighbor_after(seg, y)
1508    }
1509
1510    /// Returns the neighbor to the east of `seg` just after height `y`.
1511    pub fn east_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option<OutputSegIdx> {
1512        self.east_map.neighbor_after(seg, y)
1513    }
1514
1515    /// Returns the next height (greater than or equal to `y`) at which `seg` has a close
1516    /// neighbor to the east.
1517    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    /// Returns the next height (greater than or equal to `y`) at which `seg` has a close
1530    /// neighbor to the west.
1531    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/// An index for a [`Contour`] within [`Contours`].
1567#[cfg_attr(test, derive(serde::Serialize))]
1568#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1569pub struct ContourIdx(pub usize);
1570
1571/// A simple, closed path.
1572///
1573/// A contour has no repeated points, and its segments do not intersect.
1574#[cfg_attr(test, derive(serde::Serialize))]
1575#[derive(Clone, Debug)]
1576pub struct Contour {
1577    /// The contour's path, which is simple and closed.
1578    pub path: BezPath,
1579
1580    /// A contour can have a parent, so that sets with holes can be represented as nested contours.
1581    /// For example, the shaded set below:
1582    ///
1583    /// ```text
1584    ///   ----------------------
1585    ///   |xxxxxxxxxxxxxxxxxxxx|
1586    ///   |xxxxxxxxxxxxxxxxxxxx|
1587    ///   |xxxxxxxxx/\xxxxxxxxx|
1588    ///   |xxxxxxxx/  \xxxxxxxx|
1589    ///   |xxxxxxx/    \xxxxxxx|
1590    ///   |xxxxxxx\    /xxxxxxx|
1591    ///   |xxxxxxxx\  /xxxxxxxx|
1592    ///   |xxxxxxxxx\/xxxxxxxxx|
1593    ///   |xxxxxxxxxxxxxxxxxxxx|
1594    ///   |xxxxxxxxxxxxxxxxxxxx|
1595    ///   ----------------------
1596    /// ```
1597    ///
1598    /// is represented as a square contour with no parent, and a diamond contour with the square
1599    /// as its parent.
1600    ///
1601    /// A contour can share at most one point with its parent. For example, if you translate the
1602    /// diamond above upwards until it touches the top of the square, it will share that top point
1603    /// with its parent. You can't make them share two points, though: if you try to translate the
1604    /// diamond to a corner...
1605    ///
1606    /// ```text
1607    ///   ----------------------
1608    ///   |xx/\xxxxxxxxxxxxxxxx|
1609    ///   |x/  \xxxxxxxxxxxxxxx|
1610    ///   |/    \xxxxxxxxxxxxxx|
1611    ///   |\    /xxxxxxxxxxxxxx|
1612    ///   |x\  /xxxxxxxxxxxxxxx|
1613    ///   |xx\/xxxxxxxxxxxxxxxx|
1614    ///   |xxxxxxxxxxxxxxxxxxxx|
1615    ///   |xxxxxxxxxxxxxxxxxxxx|
1616    ///   |xxxxxxxxxxxxxxxxxxxx|
1617    ///   |xxxxxxxxxxxxxxxxxxxx|
1618    ///   ----------------------
1619    /// ```
1620    ///
1621    /// ...then it will be interpreted as two contours without a parent/child relationship:
1622    ///
1623    /// ```text
1624    ///   ----
1625    ///   |xx/
1626    ///   |x/
1627    ///   |/
1628    /// ```
1629    ///
1630    /// and
1631    ///
1632    /// ```text
1633    ///       ------------------
1634    ///       \xxxxxxxxxxxxxxxx|
1635    ///        \xxxxxxxxxxxxxxx|
1636    ///         \xxxxxxxxxxxxxx|
1637    ///   |\    /xxxxxxxxxxxxxx|
1638    ///   |x\  /xxxxxxxxxxxxxxx|
1639    ///   |xx\/xxxxxxxxxxxxxxxx|
1640    ///   |xxxxxxxxxxxxxxxxxxxx|
1641    ///   |xxxxxxxxxxxxxxxxxxxx|
1642    ///   |xxxxxxxxxxxxxxxxxxxx|
1643    ///   |xxxxxxxxxxxxxxxxxxxx|
1644    ///   ----------------------
1645    /// ```
1646    ///
1647    pub parent: Option<ContourIdx>,
1648
1649    /// Whether this contour is "outer" or not. A contour with no parent is "outer", and
1650    /// then they alternate: a contour is "outer" if and only if its parent isn't.
1651    ///
1652    /// As you walk along a contour, the "occupied" part of the set it represents is
1653    /// on your left. This means that outer contours wind counter-clockwise and inner
1654    /// contours wind clockwise.
1655    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/// A collection of [`Contour`]s.
1669///
1670/// Can be indexed with a [`ContourIdx`].
1671#[cfg_attr(test, derive(serde::Serialize))]
1672#[derive(Clone, Debug, Default)]
1673pub struct Contours {
1674    contours: Vec<Contour>,
1675}
1676
1677impl Contours {
1678    /// Returns all of the contour indices, grouped by containment.
1679    ///
1680    /// For each of the inner vecs, the first element is an outer contour with
1681    /// no parent. All of the other contours in that inner vec lie inside that
1682    /// outer contour.
1683    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    /// Iterates over all of the contours.
1711    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        //check_intersections(&top);
1751
1752        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        //check_intersections(&top);
1761
1762        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        //check_intersections(&top);
1772
1773        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        //check_intersections(&top);
1790
1791        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        // Maybe this is a little surprising, but it's what the current implementation does.
1825        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        // The output segs coming from the left side of the big square.
1857        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    // Checks that all output segments intersect one another only at endpoints.
1916    // fn check_intersections(top: &Topology) {
1917    //     for i in 0..top.winding.inner.len() {
1918    //         for j in (i + 1)..top.winding.inner.len() {
1919    //             let i = OutputSegIdx(i);
1920    //             let j = OutputSegIdx(j);
1921
1922    //             let p0 = top.point(i.first_half()).clone();
1923    //             let p1 = top.point(i.second_half()).clone();
1924    //             let q0 = top.point(j.first_half()).clone();
1925    //             let q1 = top.point(j.second_half()).clone();
1926
1927    //             let s = Segment::new(p0.clone().min(p1.clone()), p1.max(p0)).to_exact();
1928    //             let t = Segment::new(q0.clone().min(q1.clone()), q1.max(q0)).to_exact();
1929
1930    //             if s.end.y >= t.start.y && t.end.y >= s.start.y {
1931    //                 // FIXME
1932    //                 // if let Some(y) = s.exact_intersection_y(&t) {
1933    //                 //     dbg!(y);
1934    //                 //     assert!(
1935    //                 //         s.start == t.start
1936    //                 //             || s.start == t.end
1937    //                 //             || s.end == t.start
1938    //                 //             || s.end == t.end
1939    //                 //     );
1940    //                 // }
1941    //             }
1942    //         }
1943    //     }
1944    // }
1945
1946    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        //check_intersections(&top);
1962    }
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}