Skip to main content

geometry_overlay/
line_intersection.rs

1//! Public segment-intersection result with topology classification.
2//!
3//! The predicate kernel computes zero, one, or two intersection points. This
4//! entry adds the proper-crossing distinction used by the turn classifier and
5//! converts an arithmetic range refusal into the public overlay error.
6
7use geometry_coords::CoordinateScalar;
8use geometry_model::Segment;
9use geometry_trait::{Point, PointMut, Segment as SegmentTrait, segment_end, segment_start};
10
11use crate::operation::OverlayError;
12use crate::predicate::{SegmentIntersection, segment_intersection};
13use crate::turn::{Method, classify::refine_touch};
14
15/// A non-empty intersection between two line segments.
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum LineIntersection<P: Point> {
18    /// The segments meet at one point.
19    SinglePoint {
20        /// The intersection coordinate.
21        intersection: P,
22        /// `true` only when the point lies in the interior of both segments.
23        is_proper: bool,
24    },
25    /// The segments overlap along a collinear segment.
26    Collinear {
27        /// The shared closed segment.
28        intersection: Segment<P>,
29    },
30}
31
32/// Intersect two segments and report proper, touch, and collinear cases.
33///
34/// This is the public counterpart of the Cartesian intersection strategy in
35/// `strategy/cartesian/intersection.hpp`. It preserves this port's explicit
36/// range refusal instead of returning a potentially unreliable coordinate.
37///
38/// # Errors
39///
40/// Returns [`OverlayError::Unsupported`] when an endpoint lies outside the
41/// exact-predicate range.
42#[inline]
43#[must_use = "line intersection can fail and its result should be used"]
44pub fn line_intersection<S, P>(
45    first: &S,
46    second: &S,
47) -> Result<Option<LineIntersection<P>>, OverlayError>
48where
49    S: SegmentTrait<Point = P>,
50    P: PointMut + Default,
51    P::Scalar: CoordinateScalar + Into<f64> + PartialEq,
52{
53    match segment_intersection(first, second) {
54        SegmentIntersection::Disjoint => Ok(None),
55        SegmentIntersection::OutOfRange => Err(OverlayError::Unsupported),
56        SegmentIntersection::Single(intersection) => {
57            let first_start = segment_start(first);
58            let first_end = segment_end(first);
59            let second_start = segment_start(second);
60            let second_end = segment_end(second);
61            let is_proper = refine_touch(
62                &intersection,
63                &first_start,
64                &first_end,
65                &second_start,
66                &second_end,
67            ) == Method::Crosses;
68            Ok(Some(LineIntersection::SinglePoint {
69                intersection,
70                is_proper,
71            }))
72        }
73        SegmentIntersection::Collinear { from, to } => Ok(Some(LineIntersection::Collinear {
74            intersection: Segment::new(from, to),
75        })),
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::{LineIntersection, line_intersection};
82    use geometry_cs::Cartesian;
83    use geometry_model::{Point2D, Segment};
84
85    type P = Point2D<f64, Cartesian>;
86
87    #[test]
88    fn distinguishes_proper_crossing_from_endpoint_touch() {
89        let diagonal = Segment::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
90        let crossing = Segment::new(P::new(0.0, 2.0), P::new(2.0, 0.0));
91        let touching = Segment::new(P::new(2.0, 2.0), P::new(3.0, 2.0));
92
93        assert_eq!(
94            line_intersection(&diagonal, &crossing),
95            Ok(Some(LineIntersection::SinglePoint {
96                intersection: P::new(1.0, 1.0),
97                is_proper: true,
98            }))
99        );
100        assert_eq!(
101            line_intersection(&diagonal, &touching),
102            Ok(Some(LineIntersection::SinglePoint {
103                intersection: P::new(2.0, 2.0),
104                is_proper: false,
105            }))
106        );
107    }
108}