geo_booleanop/boolean/
compare_segments.rs

1use super::helper::Float;
2use super::segment_intersection::{intersection, LineIntersection};
3use super::signed_area::signed_area;
4use super::sweep_event::SweepEvent;
5use std::cmp::Ordering;
6use std::rc::Rc;
7
8use super::helper;
9
10pub fn compare_segments<F>(se1_l: &Rc<SweepEvent<F>>, se2_l: &Rc<SweepEvent<F>>) -> Ordering
11where
12    F: Float,
13{
14    debug_assert!(
15        se1_l.is_left(),
16        "compare_segments requires left-events, got a right-event."
17    );
18    debug_assert!(
19        se2_l.is_left(),
20        "compare_segments requires left-events, got a right-event."
21    );
22    debug_assert!(
23        se1_l.get_other_event().is_some(),
24        "missing right-event in compare_segments"
25    );
26    debug_assert!(
27        se2_l.get_other_event().is_some(),
28        "missing right-event in compare_segments"
29    );
30
31    if Rc::ptr_eq(&se1_l, &se2_l) {
32        return Ordering::Equal;
33    }
34
35    // The main logic of compare segments is to check the orientation of the later/older
36    // SweepEvent w.r.t. the segment of the earlier/newer one. The logic is easier to
37    // express by swapping them here according to their temporal order. In case we have
38    // to swap, the result function must be inverted accordingly.
39    let (se_old_l, se_new_l, less_if) = if se1_l.is_before(&se2_l) {
40        (se1_l, se2_l, helper::less_if as fn(bool) -> Ordering)
41    } else {
42        (se2_l, se1_l, helper::less_if_inversed as fn(bool) -> Ordering)
43    };
44
45    if let (Some(se_old_r), Some(se_new_r)) = (se_old_l.get_other_event(), se_new_l.get_other_event()) {
46        let sa_l = signed_area(se_old_l.point, se_old_r.point, se_new_l.point);
47        let sa_r = signed_area(se_old_l.point, se_old_r.point, se_new_r.point);
48        if sa_l != 0. || sa_r != 0. {
49            // Segments are not collinear
50
51            // Left endpoints exactly identical? Use the right endpoint to sort
52            if se_old_l.point == se_new_l.point {
53                return less_if(se_old_l.is_below(se_new_r.point));
54            }
55
56            // Left endpoints identical in x, but different in y? Sort by y
57            if se_old_l.point.x == se_new_l.point.x {
58                return less_if(se_old_l.point.y < se_new_l.point.y);
59            }
60
61            // If `l` and `r` lie on the same side of the reference segment,
62            // no intersection check is necessary.
63            if (sa_l > 0.) == (sa_r > 0.) {
64                return less_if(sa_l > 0.);
65            }
66
67            // If `l` lies on the reference segment, compare based on `r`.
68            if sa_l == 0. {
69                return less_if(sa_r > 0.);
70            }
71
72            // According to the signed-area values the segments cross. Verify if
73            // we can get an intersection point whic is truely different from `l`.
74            let inter = intersection(se_old_l.point, se_old_r.point, se_new_l.point, se_new_r.point);
75            match inter {
76                LineIntersection::None => return less_if(sa_l > 0.),
77                LineIntersection::Point(p) => {
78                    if p == se_new_l.point {
79                        return less_if(sa_r > 0.);
80                    } else {
81                        return less_if(sa_l > 0.);
82                    }
83                }
84                _ => {} // go into collinear logic below
85            }
86        }
87
88        // Segments are collinear
89        if se_old_l.is_subject == se_new_l.is_subject {
90            if se_old_l.point == se_new_l.point {
91                // Previously this was returning Ordering::Equal if the segments had identical
92                // left and right endpoints. I think in order to properly support self-overlapping
93                // segments we must return Ordering::Equal if and only if segments are the same
94                // by identity (the Rc::ptr_eq above).
95                less_if(se_old_l.contour_id < se_new_l.contour_id)
96            } else {
97                // Fallback to purely temporal-based comparison. Since `less_if` already
98                // encodes "earlier-is-less" semantics, no comparison is needed.
99                less_if(true)
100            }
101        } else {
102            less_if(se_old_l.is_subject)
103        }
104    } else {
105        debug_assert!(false, "Other events should always be defined in compare_segment.");
106        less_if(true)
107    }
108}
109
110#[cfg(test)]
111mod test {
112    use super::super::sweep_event::SweepEvent;
113    use super::compare_segments;
114    use crate::splay::SplaySet;
115    use geo_types::Coordinate;
116    use std::cmp::Ordering;
117    use std::rc::{Rc, Weak};
118
119    macro_rules! assert_ordering {
120        ($se1:expr, $se2:expr, $ordering:expr) => {
121            let inverse_ordering = match $ordering {
122                Ordering::Less => Ordering::Greater,
123                Ordering::Greater => Ordering::Less,
124                _ => Ordering::Equal,
125            };
126            assert_eq!(
127                compare_segments(&$se1, &$se2),
128                $ordering,
129                "Comparing se1/se2 with expected value {:?}",
130                $ordering
131            );
132            assert_eq!(
133                compare_segments(&$se2, &$se1),
134                inverse_ordering,
135                "Comparing se2/se1 with expected value {:?}",
136                inverse_ordering
137            );
138        };
139    }
140
141    fn make_simple(
142        contour_id: u32,
143        x: f64,
144        y: f64,
145        other_x: f64,
146        other_y: f64,
147        is_subject: bool,
148    ) -> (Rc<SweepEvent<f64>>, Rc<SweepEvent<f64>>) {
149        let other = SweepEvent::new_rc(
150            contour_id,
151            Coordinate { x: other_x, y: other_y },
152            false,
153            Weak::new(),
154            is_subject,
155            true,
156        );
157        let event = SweepEvent::new_rc(
158            contour_id,
159            Coordinate { x, y },
160            true,
161            Rc::downgrade(&other),
162            is_subject,
163            true,
164        );
165        // Make sure test cases fulfill the invariant of left/right relationship.
166        assert!(event.is_before(&other));
167
168        (event, other)
169    }
170
171    #[test]
172    fn not_collinear_shared_left_right_first() {
173        let (se1, _other1) = make_simple(0, 0.0, 0.0, 1.0, 1.0, false);
174        let (se2, _other2) = make_simple(0, 0.0, 0.0, 2.0, 3.0, false);
175
176        let mut tree = SplaySet::new(compare_segments);
177
178        tree.insert(se1);
179        tree.insert(se2);
180
181        let min_other = tree.min().unwrap().get_other_event().unwrap();
182        let max_other = tree.max().unwrap().get_other_event().unwrap();
183
184        assert_eq!(max_other.point, Coordinate { x: 2.0, y: 3.0 });
185        assert_eq!(min_other.point, Coordinate { x: 1.0, y: 1.0 });
186    }
187
188    #[test]
189    fn not_collinear_different_left_point_right_sort_y() {
190        let (se1, _other1) = make_simple(0, 0.0, 1.0, 1.0, 1.0, false);
191        let (se2, _other2) = make_simple(0, 0.0, 2.0, 2.0, 3.0, false);
192
193        let mut tree = SplaySet::new(compare_segments);
194
195        tree.insert(se1);
196        tree.insert(se2);
197
198        let min_other = tree.min().unwrap().get_other_event().unwrap();
199        let max_other = tree.max().unwrap().get_other_event().unwrap();
200
201        assert_eq!(min_other.point, Coordinate { x: 1.0, y: 1.0 });
202        assert_eq!(max_other.point, Coordinate { x: 2.0, y: 3.0 });
203    }
204
205    #[test]
206    fn not_collinear_order_in_sweep_line() {
207        let (se1, _other1) = make_simple(0, 0.0, 1.0, 2.0, 1.0, false);
208        let (se2, _other2) = make_simple(0, -1.0, 0.0, 2.0, 3.0, false);
209        let (se3, _other3) = make_simple(0, 0.0, 1.0, 3.0, 4.0, false);
210        let (se4, _other4) = make_simple(0, -1.0, 0.0, 3.0, 1.0, false);
211
212        assert_eq!(se1.cmp(&se2), Ordering::Less);
213        assert!(!se2.is_below(se1.point));
214        assert!(se2.is_above(se1.point));
215
216        assert_ordering!(se1, se2, Ordering::Less);
217
218        assert_eq!(se3.cmp(&se4), Ordering::Less);
219        assert!(!se4.is_above(se3.point));
220    }
221
222    #[test]
223    fn not_collinear_first_point_is_below() {
224        let (se2, _other2) = make_simple(0, 1.0, 1.0, 5.0, 1.0, false);
225        let (se1, _other1) = make_simple(0, -1.0, 0.0, 2.0, 3.0, false);
226
227        assert!(!se1.is_below(se2.point));
228        assert_ordering!(se1, se2, Ordering::Greater);
229    }
230
231    #[test]
232    fn collinear_segments() {
233        let (se1, _other1) = make_simple(0, 1.0, 1.0, 5.0, 1.0, true);
234        let (se2, _other2) = make_simple(0, 2.0, 01.0, 3.0, 1.0, false);
235
236        assert_ne!(se1.is_subject, se2.is_subject);
237        assert_ordering!(se1, se2, Ordering::Less);
238    }
239
240    #[test]
241    fn collinear_shared_left_point() {
242        {
243            let (se1, _other2) = make_simple(1, 0.0, 1.0, 5.0, 1.0, false);
244            let (se2, _other1) = make_simple(2, 0.0, 1.0, 3.0, 1.0, false);
245
246            assert_eq!(se1.is_subject, se2.is_subject);
247            assert_eq!(se1.point, se2.point);
248
249            assert_ordering!(se1, se2, Ordering::Less);
250        }
251        {
252            let (se1, _other2) = make_simple(2, 0.0, 1.0, 5.0, 1.0, false);
253            let (se2, _other1) = make_simple(1, 0.0, 1.0, 3.0, 1.0, false);
254
255            assert_ordering!(se1, se2, Ordering::Greater);
256        }
257    }
258
259    #[test]
260    fn collinear_same_polygon_different_left() {
261        let (se1, _other2) = make_simple(0, 1.0, 1.0, 5.0, 1.0, true);
262        let (se2, _other1) = make_simple(0, 2.0, 1.0, 3.0, 1.0, true);
263
264        assert_eq!(se1.is_subject, se2.is_subject);
265        assert_ne!(se1.point, se2.point);
266        assert_ordering!(se1, se2, Ordering::Less);
267    }
268
269    #[test]
270    fn t_shaped_cases() {
271        // shape:  /
272        //        /\
273        let (se1, _other1) = make_simple(0, 0.0, 0.0, 1.0, 1.0, true);
274        let (se2, _other2) = make_simple(0, 0.5, 0.5, 1.0, 0.0, true);
275        assert_ordering!(se1, se2, Ordering::Greater);
276
277        // shape: \/
278        //         \
279        let (se1, _other1) = make_simple(0, 0.0, 1.0, 1.0, 0.0, true);
280        let (se2, _other2) = make_simple(0, 0.5, 0.5, 1.0, 1.0, true);
281        assert_ordering!(se1, se2, Ordering::Less);
282
283        // shape: T
284        let (se1, _other1) = make_simple(0, 0.0, 1.0, 1.0, 1.0, true);
285        let (se2, _other2) = make_simple(0, 0.5, 0.0, 0.5, 1.0, true);
286        assert_ordering!(se1, se2, Ordering::Greater);
287
288        // shape: T upside down
289        let (se1, _other1) = make_simple(0, 0.0, 0.0, 1.0, 0.0, true);
290        let (se2, _other2) = make_simple(0, 0.5, 0.0, 0.5, 1.0, true);
291        assert_ordering!(se1, se2, Ordering::Less);
292    }
293
294    #[test]
295    fn vertical_segment() {
296        // vertical reference segment at x = 0, expanding from y = -1 to +1.
297        let (se1, _other1) = make_simple(0, 0.0, -1.0, 0.0, 1.0, true);
298
299        // "above" cases
300        let (se2, _other2) = make_simple(0, -1.0, 1.0, 0.0, 1.0, true);
301        assert_ordering!(se1, se2, Ordering::Less);
302        let (se2, _other2) = make_simple(0, 0.0, 1.0, 1.0, 1.0, true);
303        assert_ordering!(se1, se2, Ordering::Less);
304        let (se2, _other2) = make_simple(0, -1.0, 2.0, 0.0, 2.0, true);
305        assert_ordering!(se1, se2, Ordering::Less);
306        let (se2, _other2) = make_simple(0, 0.0, 2.0, 1.0, 2.0, true);
307        assert_ordering!(se1, se2, Ordering::Less);
308        let (se2, _other2) = make_simple(0, 0.0, 1.0, 0.0, 2.0, true);
309        assert_ordering!(se1, se2, Ordering::Less);
310
311        // "below" cases
312        let (se2, _other2) = make_simple(0, -1.0, -1.0, 0.0, -1.0, true);
313        assert_ordering!(se1, se2, Ordering::Greater);
314        let (se2, _other2) = make_simple(0, 0.0, -1.0, 1.0, -1.0, true);
315        assert_ordering!(se1, se2, Ordering::Greater);
316        let (se2, _other2) = make_simple(0, -1.0, -2.0, 0.0, -2.0, true);
317        assert_ordering!(se1, se2, Ordering::Greater);
318        let (se2, _other2) = make_simple(0, 0.0, -2.0, 1.0, -2.0, true);
319        assert_ordering!(se1, se2, Ordering::Greater);
320        let (se2, _other2) = make_simple(0, 0.0, -2.0, 0.0, -1.0, true);
321        assert_ordering!(se1, se2, Ordering::Greater);
322
323        // overlaps
324        let (se2, _other2) = make_simple(0, 0.0, -0.5, 0.0, 0.5, true);
325        assert_ordering!(se1, se2, Ordering::Less);
326        // When left endpoints are identical, the ordering is no longer anti-symmetric.
327        // TODO: Decide if this is a problem.
328        // let (se2, _other2) = make_simple(0, 0.0, -1.0, 0.0, 0.0, true);
329        // assert_ordering!(se1, se2, Ordering::Less); // fails because of its not anti-symmetric.
330    }
331}