Skip to main content

geometry_strategy/
intersects.rs

1//! Per-CS strategy for the `intersects` set-relation algorithm.
2//!
3//! Mirrors `boost::geometry::intersects(g1, g2)` from
4//! `boost/geometry/algorithms/intersects.hpp` together with the
5//! per-pair dispatch tables it pulls in from
6//! `algorithms/detail/intersects/{interface,implementation}.hpp` and,
7//! transitively, the Cartesian segment-segment / point-on-segment
8//! kernels in `strategy/cartesian/`. Two geometries `intersects` iff
9//! they are *not* `disjoint` — Boost's interface header is literally
10//! `intersects(a, b) == !disjoint(a, b)`
11//! (`algorithms/detail/intersects/interface.hpp:64-78`). The Rust port
12//! flips that around: `intersects` is the primary kernel here and
13//! [`crate::disjoint`] is the negation, because every constructive
14//! per-pair test is naturally an "is there a shared point?" question.
15//!
16//! ## Coherence note
17//!
18//! Rust's trait system cannot prove a downstream type does not implement
19//! several geometry traits at once, so two open blankets on one strategy
20//! struct would collide (E0119). The port reproduces Boost's per-pair
21//! tag dispatch instead: one **per-ordered-pair strategy struct**
22//! ([`IxPointPoint`], [`IxLinestringPolygon`], …) carries a single
23//! concept-pair-bounded `IntersectsStrategy` impl — distinct `Self`, so
24//! no overlap — and the tag-keyed [`IntersectsPairStrategy`] picker
25//! routes `(A::Kind, B::Kind)` to the right struct. Because it keys on
26//! the tags, a concept-adapted foreign type resolves through the same
27//! path as the equivalent `geometry-model` value.
28//! [`CartesianIntersects`] remains as a thin
29//! facade that routes through the picker, so `disjoint` / `is_simple` and
30//! the `Reversed` symmetry adapter keep resolving unchanged.
31//!
32//! ## Symmetry
33//!
34//! `intersects` is symmetric: `intersects(a, b) == intersects(b, a)`.
35//! Each pair appears in exactly one canonical direction here and the
36//! [`Reversed`] blanket at the bottom lifts every
37//! `IntersectsStrategy<A, B>` to an `IntersectsStrategy<B, A>` for
38//! free — mirroring `boost::geometry::reverse_dispatch` at
39//! `core/reverse_dispatch.hpp`.
40
41extern crate alloc;
42
43use geometry_coords::CoordinateScalar;
44use geometry_cs::{CartesianFamily, CoordinateSystem};
45use geometry_tag::{LinestringTag, PointTag, PolygonTag, SameAs, SegmentTag};
46use geometry_trait::{
47    Geometry, Linestring as LinestringTrait, Point as PointTrait, Polygon as PolygonTrait,
48    Ring as RingTrait, Segment as SegmentTrait, segment_end, segment_start,
49};
50
51use crate::within::{WithinPoly, WithinStrategy};
52
53pub use crate::reversal::Reversed;
54
55/// A strategy for "do these two geometries share at least one point?".
56///
57/// Mirrors `boost::geometry::intersects(g1, g2)` from
58/// `boost/geometry/algorithms/intersects.hpp`. The Boost API takes the
59/// strategy implicitly through the algorithm's per-pair dispatch
60/// table; the Rust analogue collapses dispatch and strategy onto one
61/// trait keyed on the geometry pair.
62pub trait IntersectsStrategy<A, B> {
63    /// `true` iff `a` and `b` share at least one point.
64    fn intersects(&self, a: &A, b: &B) -> bool;
65}
66
67/// The Cartesian intersection facade — Boost's default for the Cartesian
68/// coordinate system.
69///
70/// Routes `(A, B)` through the tag-keyed [`IntersectsPairStrategy`] picker
71/// to the matching per-pair strategy struct. Kept as a single type so
72/// consumers that name it directly ([`crate::disjoint`],
73/// `is_simple`, [`Reversed`]) resolve unchanged; the per-pair *bodies*
74/// live on the [`IxPointPoint`] … structs below.
75#[derive(Debug, Default, Clone, Copy)]
76pub struct CartesianIntersects;
77
78impl<A, B> IntersectsStrategy<A, B> for CartesianIntersects
79where
80    A: Geometry,
81    B: Geometry,
82    A::Kind: IntersectsPairStrategy<B::Kind>,
83    <A::Kind as IntersectsPairStrategy<B::Kind>>::S: IntersectsStrategy<A, B>,
84{
85    #[inline]
86    fn intersects(&self, a: &A, b: &B) -> bool {
87        <<A::Kind as IntersectsPairStrategy<B::Kind>>::S as Default>::default().intersects(a, b)
88    }
89}
90
91// ---- Per-pair strategy structs --------------------------------------
92//
93// Each struct carries a single concept-pair-bounded `IntersectsStrategy`
94// impl; distinct structs never overlap. Bodies are the existing kernels,
95// re-bound on the open concepts. The four `covered_by` cross-strategy
96// calls go through the open [`WithinPoly`] strategy (not the algorithm
97// free fn — that would be an upward crate dependency).
98
99/// Point × Point. See the [module docs](self).
100#[derive(Debug, Default, Clone, Copy)]
101pub struct IxPointPoint;
102/// Point × Segment. See the [module docs](self).
103#[derive(Debug, Default, Clone, Copy)]
104pub struct IxPointSegment;
105/// Segment × Segment. See the [module docs](self).
106#[derive(Debug, Default, Clone, Copy)]
107pub struct IxSegmentSegment;
108/// Linestring × Segment. See the [module docs](self).
109#[derive(Debug, Default, Clone, Copy)]
110pub struct IxLinestringSegment;
111/// Linestring × Linestring. See the [module docs](self).
112#[derive(Debug, Default, Clone, Copy)]
113pub struct IxLinestringLinestring;
114/// Point × Polygon. See the [module docs](self).
115#[derive(Debug, Default, Clone, Copy)]
116pub struct IxPointPolygon;
117/// Linestring × Polygon. See the [module docs](self).
118#[derive(Debug, Default, Clone, Copy)]
119pub struct IxLinestringPolygon;
120/// Polygon × Polygon. See the [module docs](self).
121#[derive(Debug, Default, Clone, Copy)]
122pub struct IxPolygonPolygon;
123/// Segment × Point (reverse of [`IxPointSegment`]). See the [module docs](self).
124#[derive(Debug, Default, Clone, Copy)]
125pub struct IxSegmentPoint;
126/// Segment × Linestring (reverse of [`IxLinestringSegment`]). See the
127/// [module docs](self).
128#[derive(Debug, Default, Clone, Copy)]
129pub struct IxSegmentLinestring;
130/// Polygon × Point (reverse of [`IxPointPolygon`]). See the [module docs](self).
131#[derive(Debug, Default, Clone, Copy)]
132pub struct IxPolygonPoint;
133/// Polygon × Linestring (reverse of [`IxLinestringPolygon`]). See the
134/// [module docs](self).
135#[derive(Debug, Default, Clone, Copy)]
136pub struct IxPolygonLinestring;
137
138// ---- Point × Point ---------------------------------------------------
139//
140// Two points "intersect" iff they are coordinate-wise equal. Mirrors
141// the pointlike/pointlike arm at
142// `algorithms/detail/disjoint/point_point.hpp:36-66`.
143
144impl<A, B> IntersectsStrategy<A, B> for IxPointPoint
145where
146    A: PointTrait,
147    B: PointTrait<Scalar = A::Scalar>,
148    <A::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
149{
150    #[inline]
151    fn intersects(&self, a: &A, b: &B) -> bool {
152        points_equal::<A, B>(a, b, A::DIM)
153    }
154}
155
156// ---- Point × Segment -------------------------------------------------
157//
158// A point lies on a segment iff it is collinear with the two
159// endpoints and inside the bounding box of those endpoints.
160
161impl<P, S> IntersectsStrategy<P, S> for IxPointSegment
162where
163    P: PointTrait,
164    S: SegmentTrait<Point = P>,
165    P: geometry_trait::PointMut + Default,
166    P::Scalar: CoordinateScalar,
167    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
168{
169    #[inline]
170    fn intersects(&self, p: &P, s: &S) -> bool {
171        point_on_segment(p, &segment_start(s), &segment_end(s))
172    }
173}
174
175// ---- Segment × Segment -----------------------------------------------
176
177impl<A, B, P> IntersectsStrategy<A, B> for IxSegmentSegment
178where
179    A: SegmentTrait<Point = P>,
180    B: SegmentTrait<Point = P>,
181    P: PointTrait + geometry_trait::PointMut + Default,
182    P::Scalar: CoordinateScalar,
183    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
184{
185    #[inline]
186    fn intersects(&self, a: &A, b: &B) -> bool {
187        segments_intersect(
188            &segment_start(a),
189            &segment_end(a),
190            &segment_start(b),
191            &segment_end(b),
192        )
193    }
194}
195
196// ---- Linestring × Segment --------------------------------------------
197
198impl<L, S, P> IntersectsStrategy<L, S> for IxLinestringSegment
199where
200    L: LinestringTrait<Point = P>,
201    S: SegmentTrait<Point = P>,
202    P: PointTrait + geometry_trait::PointMut + Default,
203    P::Scalar: CoordinateScalar,
204    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
205{
206    #[inline]
207    fn intersects(&self, ls: &L, s: &S) -> bool {
208        let s1 = segment_start(s);
209        let s2 = segment_end(s);
210        let mut it = ls.points();
211        let Some(mut prev) = it.next() else {
212            return false;
213        };
214        for curr in it {
215            if segments_intersect(prev, curr, &s1, &s2) {
216                return true;
217            }
218            prev = curr;
219        }
220        false
221    }
222}
223
224// ---- Linestring × Linestring -----------------------------------------
225
226impl<A, B, P> IntersectsStrategy<A, B> for IxLinestringLinestring
227where
228    A: LinestringTrait<Point = P>,
229    B: LinestringTrait<Point = P>,
230    P: PointTrait,
231    P::Scalar: CoordinateScalar,
232    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
233{
234    #[inline]
235    fn intersects(&self, a: &A, b: &B) -> bool {
236        let mut ia = a.points();
237        let Some(mut pa) = ia.next() else {
238            return false;
239        };
240        for qa in ia {
241            let mut ib = b.points();
242            let Some(mut pb) = ib.next() else {
243                return false;
244            };
245            for qb in ib {
246                if segments_intersect(pa, qa, pb, qb) {
247                    return true;
248                }
249                pb = qb;
250            }
251            pa = qa;
252        }
253        false
254    }
255}
256
257// ---- Point × Polygon -------------------------------------------------
258//
259// A point intersects a polygon iff it is covered_by (interior or
260// boundary). Goes through the open `WithinPoly` strategy.
261
262impl<P, G> IntersectsStrategy<P, G> for IxPointPolygon
263where
264    P: PointTrait,
265    G: PolygonTrait<Point = P>,
266    P::Scalar: CoordinateScalar,
267    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
268{
269    #[inline]
270    fn intersects(&self, p: &P, pg: &G) -> bool {
271        WithinPoly.covered_by(p, pg)
272    }
273}
274
275// ---- Linestring × Polygon --------------------------------------------
276
277impl<L, G, P> IntersectsStrategy<L, G> for IxLinestringPolygon
278where
279    L: LinestringTrait<Point = P>,
280    G: PolygonTrait<Point = P>,
281    P: PointTrait,
282    P::Scalar: CoordinateScalar,
283    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
284{
285    fn intersects(&self, ls: &L, pg: &G) -> bool {
286        // A connected line with no polygon-boundary crossing cannot change
287        // between polygon material and its complement, so one representative
288        // point is sufficient for containment.
289        let Some(first) = ls.points().next() else {
290            return false;
291        };
292        if WithinPoly.covered_by(first, pg) {
293            return true;
294        }
295        // Any sub-segment crossing any ring sub-segment.
296        if linestring_crosses_ring(ls, pg.exterior()) {
297            return true;
298        }
299        for hole in pg.interiors() {
300            if linestring_crosses_ring(ls, hole) {
301                return true;
302            }
303        }
304        false
305    }
306}
307
308// ---- Polygon × Polygon -----------------------------------------------
309
310impl<A, B, P> IntersectsStrategy<A, B> for IxPolygonPolygon
311where
312    A: PolygonTrait<Point = P>,
313    B: PolygonTrait<Point = P>,
314    P: PointTrait,
315    P::Scalar: CoordinateScalar,
316    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
317{
318    fn intersects(&self, a: &A, b: &B) -> bool {
319        // Containment fast path: a vertex of A inside B, or vice versa.
320        if let Some(v) = a.exterior().points().next() {
321            if WithinPoly.covered_by(v, b) {
322                return true;
323            }
324        }
325        if let Some(v) = b.exterior().points().next() {
326            if WithinPoly.covered_by(v, a) {
327                return true;
328            }
329        }
330        // Any ring-edge crossing.
331        if rings_cross(a.exterior(), b.exterior()) {
332            return true;
333        }
334        for hole in a.interiors() {
335            if rings_cross(hole, b.exterior()) {
336                return true;
337            }
338        }
339        for hole in b.interiors() {
340            if rings_cross(a.exterior(), hole) {
341                return true;
342            }
343        }
344        false
345    }
346}
347
348// ---- Reverse pairs ---------------------------------------------------
349//
350// Each asymmetric pair gets its own struct that swaps and delegates to
351// the forward struct, so the per-pair picker can cover each ordered pair
352// directly.
353
354impl<S, P> IntersectsStrategy<S, P> for IxSegmentPoint
355where
356    S: SegmentTrait<Point = P>,
357    P: PointTrait + geometry_trait::PointMut + Default,
358    P::Scalar: CoordinateScalar,
359    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
360{
361    #[inline]
362    fn intersects(&self, s: &S, p: &P) -> bool {
363        IxPointSegment.intersects(p, s)
364    }
365}
366
367impl<S, L, P> IntersectsStrategy<S, L> for IxSegmentLinestring
368where
369    S: SegmentTrait<Point = P>,
370    L: LinestringTrait<Point = P>,
371    P: PointTrait + geometry_trait::PointMut + Default,
372    P::Scalar: CoordinateScalar,
373    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
374{
375    #[inline]
376    fn intersects(&self, s: &S, ls: &L) -> bool {
377        IxLinestringSegment.intersects(ls, s)
378    }
379}
380
381impl<G, P> IntersectsStrategy<G, P> for IxPolygonPoint
382where
383    G: PolygonTrait<Point = P>,
384    P: PointTrait,
385    P::Scalar: CoordinateScalar,
386    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
387{
388    #[inline]
389    fn intersects(&self, pg: &G, p: &P) -> bool {
390        IxPointPolygon.intersects(p, pg)
391    }
392}
393
394impl<G, L, P> IntersectsStrategy<G, L> for IxPolygonLinestring
395where
396    G: PolygonTrait<Point = P>,
397    L: LinestringTrait<Point = P>,
398    P: PointTrait,
399    P::Scalar: CoordinateScalar,
400    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
401{
402    #[inline]
403    fn intersects(&self, pg: &G, ls: &L) -> bool {
404        IxLinestringPolygon.intersects(ls, pg)
405    }
406}
407
408/// Type-level "which `IntersectsStrategy` struct does this ordered pair
409/// of geometry *kinds* use". A trait parameterised by the second tag
410/// `K2`, keyed on the first tag `Self` — disjoint on the pair, so no
411/// overlap. One entry per implemented ordered pair. The
412/// [`crate::intersects`] free function routes `(A::Kind, B::Kind)`
413/// through this trait.
414#[doc(hidden)]
415pub trait IntersectsPairStrategy<K2> {
416    /// The per-pair [`IntersectsStrategy`] struct this tag pair uses.
417    type S: Default;
418}
419
420impl IntersectsPairStrategy<PointTag> for PointTag {
421    type S = IxPointPoint;
422}
423impl IntersectsPairStrategy<SegmentTag> for PointTag {
424    type S = IxPointSegment;
425}
426impl IntersectsPairStrategy<SegmentTag> for SegmentTag {
427    type S = IxSegmentSegment;
428}
429impl IntersectsPairStrategy<SegmentTag> for LinestringTag {
430    type S = IxLinestringSegment;
431}
432impl IntersectsPairStrategy<LinestringTag> for LinestringTag {
433    type S = IxLinestringLinestring;
434}
435impl IntersectsPairStrategy<PolygonTag> for PointTag {
436    type S = IxPointPolygon;
437}
438impl IntersectsPairStrategy<PolygonTag> for LinestringTag {
439    type S = IxLinestringPolygon;
440}
441impl IntersectsPairStrategy<PolygonTag> for PolygonTag {
442    type S = IxPolygonPolygon;
443}
444impl IntersectsPairStrategy<PointTag> for SegmentTag {
445    type S = IxSegmentPoint;
446}
447impl IntersectsPairStrategy<LinestringTag> for SegmentTag {
448    type S = IxSegmentLinestring;
449}
450impl IntersectsPairStrategy<PointTag> for PolygonTag {
451    type S = IxPolygonPoint;
452}
453impl IntersectsPairStrategy<LinestringTag> for PolygonTag {
454    type S = IxPolygonLinestring;
455}
456
457// ---- Kernels ---------------------------------------------------------
458
459/// Coordinate-wise point equality across the first `dim` dimensions.
460#[inline]
461fn points_equal<A, B>(a: &A, b: &B, dim: usize) -> bool
462where
463    A: PointTrait,
464    B: PointTrait<Scalar = A::Scalar>,
465{
466    let mut i = 0;
467    while i < dim {
468        // const-generic indexed access — match on every dimension supported
469        // by geometry_trait's stable-Rust dimension walkers.
470        let eq = match i {
471            0 => a.get::<0>() == b.get::<0>(),
472            1 => a.get::<1>() == b.get::<1>(),
473            2 => a.get::<2>() == b.get::<2>(),
474            3 => a.get::<3>() == b.get::<3>(),
475            _ => panic!("points_equal: dimension exceeds MAX_DIM (4)"),
476        };
477        if !eq {
478            return false;
479        }
480        i += 1;
481    }
482    true
483}
484
485/// Point-on-segment test in 2D. Mirrors the per-segment short-circuit
486/// in `cartesian_winding_base::apply` at
487/// `strategy/cartesian/point_in_poly_winding.hpp:91-131`: the point
488/// lies on `s1->s2` iff the side cross product is zero **and** the
489/// point's parameter along the segment lies in `[0, 1]`.
490fn point_on_segment<P>(p: &P, s1: &P, s2: &P) -> bool
491where
492    P: PointTrait,
493    P::Scalar: CoordinateScalar,
494{
495    let px = p.get::<0>();
496    let py = p.get::<1>();
497    let ax = s1.get::<0>();
498    let ay = s1.get::<1>();
499    let bx = s2.get::<0>();
500    let by = s2.get::<1>();
501    let cross = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
502    if cross != P::Scalar::ZERO {
503        return false;
504    }
505    let (xlo, xhi) = if ax <= bx { (ax, bx) } else { (bx, ax) };
506    let (ylo, yhi) = if ay <= by { (ay, by) } else { (by, ay) };
507    xlo <= px && px <= xhi && ylo <= py && py <= yhi
508}
509
510/// 2D segment-segment intersection test. Returns `true` iff the two
511/// closed segments share at least one point — proper crossing,
512/// endpoint touch, or collinear overlap. Mirrors the boolean
513/// projection of `strategy/cartesian/intersection.hpp:139-260`.
514fn segments_intersect<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> bool
515where
516    P: PointTrait,
517    P::Scalar: CoordinateScalar,
518{
519    let x1 = p1.get::<0>();
520    let y1 = p1.get::<1>();
521    let x2 = p2.get::<0>();
522    let y2 = p2.get::<1>();
523    let x3 = p3.get::<0>();
524    let y3 = p3.get::<1>();
525    let x4 = p4.get::<0>();
526    let y4 = p4.get::<1>();
527
528    let d1 = side_sign((x3, y3), (x4, y4), (x1, y1));
529    let d2 = side_sign((x3, y3), (x4, y4), (x2, y2));
530    let d3 = side_sign((x1, y1), (x2, y2), (x3, y3));
531    let d4 = side_sign((x1, y1), (x2, y2), (x4, y4));
532
533    // Proper crossing — the endpoints of each segment lie on
534    // opposite sides of the other segment's line.
535    if ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) {
536        return true;
537    }
538
539    // Endpoint-on-segment / collinear cases: a `side_sign` of zero
540    // means the third point lies on the other segment's line; we
541    // still have to check the bounding-box containment.
542    if d1 == 0 && point_on_segment(p1, p3, p4) {
543        return true;
544    }
545    if d2 == 0 && point_on_segment(p2, p3, p4) {
546        return true;
547    }
548    if d3 == 0 && point_on_segment(p3, p1, p2) {
549        return true;
550    }
551    if d4 == 0 && point_on_segment(p4, p1, p2) {
552        return true;
553    }
554    false
555}
556
557/// Sign of the side cross product `(b - a) × (c - a)`. `+1` left,
558/// `-1` right, `0` collinear. Mirrors `side_by_triangle::side_value`
559/// at `strategy/cartesian/side_by_triangle.hpp:178-200`.
560#[inline]
561fn side_sign<T: CoordinateScalar>(a: (T, T), b: (T, T), c: (T, T)) -> i32 {
562    let v = (b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0);
563    if v > T::ZERO {
564        1
565    } else if v < T::ZERO {
566        -1
567    } else {
568        0
569    }
570}
571
572/// Whether the closed axis-aligned bounds of two segments are disjoint.
573///
574/// Comparisons are written without `min`/`max` so a coordinate that is not
575/// ordered (for example, `NaN`) falls through to the exact segment predicate.
576#[inline]
577fn segment_bounds_disjoint<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> bool
578where
579    P: PointTrait,
580{
581    let x1 = p1.get::<0>();
582    let y1 = p1.get::<1>();
583    let x2 = p2.get::<0>();
584    let y2 = p2.get::<1>();
585    let x3 = p3.get::<0>();
586    let y3 = p3.get::<1>();
587    let x4 = p4.get::<0>();
588    let y4 = p4.get::<1>();
589
590    (x1 < x3 && x1 < x4 && x2 < x3 && x2 < x4)
591        || (x3 < x1 && x3 < x2 && x4 < x1 && x4 < x2)
592        || (y1 < y3 && y1 < y4 && y2 < y3 && y2 < y4)
593        || (y3 < y1 && y3 < y2 && y4 < y1 && y4 < y2)
594}
595
596/// Does any sub-segment of `ls` cross any sub-segment of `r` (with
597/// `r`'s closing edge added explicitly if the ring is open)?
598fn linestring_crosses_ring<L, R, P>(ls: &L, r: &R) -> bool
599where
600    L: LinestringTrait<Point = P>,
601    R: RingTrait<Point = P>,
602    P: PointTrait,
603    P::Scalar: CoordinateScalar,
604{
605    let mut ils = ls.points();
606    let Some(mut pls) = ils.next() else {
607        return false;
608    };
609    for qls in ils {
610        if ring_edge_crosses_segment(r, pls, qls) {
611            return true;
612        }
613        pls = qls;
614    }
615    false
616}
617
618/// Does the segment `pls -> qls` cross any edge of the ring `r`?
619fn ring_edge_crosses_segment<R, P>(r: &R, pls: &P, qls: &P) -> bool
620where
621    R: RingTrait<Point = P>,
622    P: PointTrait,
623    P::Scalar: CoordinateScalar,
624{
625    let mut ir = r.points();
626    let Some(mut pr) = ir.next() else {
627        return false;
628    };
629    let first = pr;
630    let mut has_edge = false;
631    for qr in ir {
632        has_edge = true;
633        if !segment_bounds_disjoint(pls, qls, pr, qr) && segments_intersect(pls, qls, pr, qr) {
634            return true;
635        }
636        pr = qr;
637    }
638    if has_edge && pr.get::<0>() == first.get::<0>() && pr.get::<1>() == first.get::<1>() {
639        return false;
640    }
641    // Close an open coordinate sequence explicitly. A one-point ring keeps
642    // its degenerate edge so its established point-like behavior is intact.
643    !segment_bounds_disjoint(pls, qls, pr, first) && segments_intersect(pls, qls, pr, first)
644}
645
646/// Does any edge of ring `a` cross any edge of ring `b`? Both rings
647/// are walked with an explicit closing edge appended for open rings;
648/// for closed rings the closing edge degenerates to zero length and
649/// contributes nothing.
650fn rings_cross<Ra, Rb, P>(a: &Ra, b: &Rb) -> bool
651where
652    Ra: RingTrait<Point = P>,
653    Rb: RingTrait<Point = P>,
654    P: PointTrait,
655    P::Scalar: CoordinateScalar,
656{
657    let edges_a = ring_edges(a);
658    let edges_b = ring_edges(b);
659    for (pa, qa) in &edges_a {
660        for (pb, qb) in &edges_b {
661            if segments_intersect(*pa, *qa, *pb, *qb) {
662                return true;
663            }
664        }
665    }
666    false
667}
668
669/// Materialise every edge of `r` as a `(prev, curr)` pair of point
670/// references. The closing edge is appended explicitly so callers do
671/// not need to special-case open vs. closed rings.
672fn ring_edges<R>(r: &R) -> alloc::vec::Vec<(&R::Point, &R::Point)>
673where
674    R: RingTrait,
675    R::Point: PointTrait,
676{
677    let pts: alloc::vec::Vec<&R::Point> = r.points().collect();
678    let mut out = alloc::vec::Vec::with_capacity(pts.len());
679    if pts.len() < 2 {
680        return out;
681    }
682    for w in pts.windows(2) {
683        out.push((w[0], w[1]));
684    }
685    out.push((*pts.last().unwrap(), *pts.first().unwrap()));
686    out
687}
688
689#[cfg(test)]
690mod tests {
691    //! Reference values from
692    //! `geometry/test/algorithms/intersects/intersects.cpp:38-79`.
693    //! Each test cites the C++ line it mirrors.
694
695    use super::{CartesianIntersects, IntersectsStrategy, Reversed, linestring_crosses_ring};
696    use geometry_cs::Cartesian;
697    use geometry_model::{Point2D, Polygon, Ring, Segment, linestring, polygon};
698
699    type P = Point2D<f64, Cartesian>;
700
701    fn pt(x: f64, y: f64) -> P {
702        Point2D::new(x, y)
703    }
704
705    use geometry_model::Linestring;
706    type LS = Linestring<P>;
707
708    /// `intersects.cpp:38` — linestring crosses segment.
709    #[test]
710    fn ls_crosses_segment() {
711        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0), (2.0, 5.0)];
712        let s = Segment::new(pt(2.0, 0.0), pt(2.0, 6.0));
713        assert!(CartesianIntersects.intersects(&ls, &s));
714    }
715
716    /// `intersects.cpp:39` — linestring touches segment endpoint.
717    #[test]
718    fn ls_touches_segment_endpoint() {
719        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
720        let s = Segment::new(pt(1.0, 0.0), pt(1.0, 1.0));
721        assert!(CartesianIntersects.intersects(&ls, &s));
722    }
723
724    /// `intersects.cpp:41` — linestring disjoint from segment.
725    #[test]
726    fn ls_disjoint_from_segment() {
727        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
728        let s = Segment::new(pt(3.0, 0.0), pt(4.0, 1.0));
729        assert!(!CartesianIntersects.intersects(&ls, &s));
730    }
731
732    /// `intersects.cpp:50` — linestring crosses linestring.
733    #[test]
734    fn ls_crosses_ls() {
735        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
736        let b: LS = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
737        assert!(CartesianIntersects.intersects(&a, &b));
738    }
739
740    /// `intersects.cpp:55` — collinear overlap.
741    #[test]
742    fn ls_overlap_collinear() {
743        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
744        let b: LS = linestring![(1.0, 0.0), (4.0, 0.0), (5.0, 0.0)];
745        assert!(CartesianIntersects.intersects(&a, &b));
746    }
747
748    /// `intersects.cpp:69` — linestring inside polygon.
749    #[test]
750    fn ls_inside_polygon() {
751        let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
752        let p: Polygon<P> = polygon![[
753            (0.0, 0.0),
754            (10.0, 0.0),
755            (10.0, 10.0),
756            (0.0, 10.0),
757            (0.0, 0.0)
758        ]];
759        assert!(CartesianIntersects.intersects(&ls, &p));
760    }
761
762    /// `intersects.cpp:71` — linestring outside polygon.
763    #[test]
764    fn ls_outside_polygon() {
765        let ls: LS = linestring![(11.0, 0.0), (12.0, 12.0)];
766        let p: Polygon<P> = polygon![[
767            (0.0, 0.0),
768            (10.0, 0.0),
769            (10.0, 10.0),
770            (0.0, 10.0),
771            (0.0, 0.0)
772        ]];
773        assert!(!CartesianIntersects.intersects(&ls, &p));
774    }
775
776    /// The public dispatcher rejects empty inputs before reaching the
777    /// linestring/ring edge helper, so guard its private empty-iterator
778    /// contract directly.
779    #[test]
780    fn empty_linestring_has_no_ring_crossing() {
781        let ls = Linestring::<P>::new();
782        let ring: Ring<P> = Ring::from_vec(vec![
783            pt(0.0, 0.0),
784            pt(0.0, 1.0),
785            pt(1.0, 1.0),
786            pt(1.0, 0.0),
787            pt(0.0, 0.0),
788        ]);
789
790        assert!(!linestring_crosses_ring(&ls, &ring));
791    }
792
793    /// `Reversed<S>` swaps the arguments transparently.
794    #[test]
795    fn reversed_pair_compiles_and_agrees() {
796        let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
797        let p: Polygon<P> = polygon![[
798            (0.0, 0.0),
799            (10.0, 0.0),
800            (10.0, 10.0),
801            (0.0, 10.0),
802            (0.0, 0.0)
803        ]];
804        let forward = CartesianIntersects.intersects(&ls, &p);
805        let reversed = Reversed(CartesianIntersects).intersects(&p, &ls);
806        assert_eq!(forward, reversed);
807    }
808
809    // KC1.T2 witness: proves this strategy accepts read-only `Point`
810    // operands (that need not implement `PointMut`). If it compiles,
811    // the read-only bound is locked.
812    fn _accepts_readonly_point<A, B, S>(s: &S, a: &A, b: &B) -> bool
813    where
814        A: geometry_trait::Point,
815        B: geometry_trait::Point,
816        S: IntersectsStrategy<A, B>,
817    {
818        s.intersects(a, b)
819    }
820
821    use super::IxPointPoint;
822
823    /// The read-only-point witness compiles *and* returns the correct
824    /// membership when actually invoked.
825    #[test]
826    #[allow(
827        clippy::used_underscore_items,
828        reason = "the test exists to run the compile-time witness's body"
829    )]
830    fn readonly_point_witness_computes_equality() {
831        assert!(_accepts_readonly_point(
832            &IxPointPoint,
833            &pt(1.0, 1.0),
834            &pt(1.0, 1.0)
835        ));
836        assert!(!_accepts_readonly_point(
837            &IxPointPoint,
838            &pt(1.0, 1.0),
839            &pt(2.0, 2.0)
840        ));
841    }
842}