1use geometry_coords::CoordinateScalar;
28use geometry_trait::{Point, PointMut, Segment as SegmentTrait, segment_end, segment_start};
29
30use super::orientation::{Sign, orientation_2d};
31use super::range_guard::coordinate_in_range;
32
33#[derive(Debug, Clone, Copy, PartialEq)]
43pub enum SegmentIntersection<P> {
44 Disjoint,
46 Single(P),
48 Collinear {
52 from: P,
54 to: P,
56 },
57 OutOfRange,
61}
62
63#[must_use]
99pub fn segment_intersection<S, P>(a: &S, b: &S) -> SegmentIntersection<P>
100where
101 S: SegmentTrait<Point = P>,
102 P: PointMut + Default,
103 P::Scalar: CoordinateScalar + Into<f64>,
104{
105 let p1 = segment_start(a);
106 let p2 = segment_end(a);
107 let p3 = segment_start(b);
108 let p4 = segment_end(b);
109
110 if !(coordinate_in_range(&p1)
111 && coordinate_in_range(&p2)
112 && coordinate_in_range(&p3)
113 && coordinate_in_range(&p4))
114 {
115 return SegmentIntersection::OutOfRange;
116 }
117
118 let d1 = orientation_2d(&p3, &p4, &p1);
122 let d2 = orientation_2d(&p3, &p4, &p2);
123 let d3 = orientation_2d(&p1, &p2, &p3);
124 let d4 = orientation_2d(&p1, &p2, &p4);
125
126 if straddles(d1, d2) && straddles(d3, d4) {
128 return SegmentIntersection::Single(line_cross_point(&p1, &p2, &p3, &p4));
129 }
130
131 if d1 == Sign::Collinear
135 && d2 == Sign::Collinear
136 && d3 == Sign::Collinear
137 && d4 == Sign::Collinear
138 {
139 return collinear_overlap(&p1, &p2, &p3, &p4);
140 }
141
142 if d1 == Sign::Collinear && on_segment(&p1, &p3, &p4) {
144 return SegmentIntersection::Single(clone_point(&p1));
145 }
146 if d2 == Sign::Collinear && on_segment(&p2, &p3, &p4) {
147 return SegmentIntersection::Single(clone_point(&p2));
148 }
149 if d3 == Sign::Collinear && on_segment(&p3, &p1, &p2) {
150 return SegmentIntersection::Single(clone_point(&p3));
151 }
152 if d4 == Sign::Collinear && on_segment(&p4, &p1, &p2) {
153 return SegmentIntersection::Single(clone_point(&p4));
154 }
155
156 SegmentIntersection::Disjoint
157}
158
159fn straddles(a: Sign, b: Sign) -> bool {
161 matches!(
162 (a, b),
163 (Sign::Positive, Sign::Negative) | (Sign::Negative, Sign::Positive)
164 )
165}
166
167fn make_point<P>(x: P::Scalar, y: P::Scalar) -> P
169where
170 P: PointMut + Default,
171{
172 let mut p = P::default();
173 p.set::<0>(x);
174 p.set::<1>(y);
175 p
176}
177
178fn clone_point<P>(src: &P) -> P
181where
182 P: PointMut + Default,
183{
184 make_point::<P>(src.get::<0>(), src.get::<1>())
185}
186
187fn line_cross_point<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> P
191where
192 P: PointMut + Default,
193 P::Scalar: CoordinateScalar,
194{
195 let x1 = p1.get::<0>();
196 let y1 = p1.get::<1>();
197 let x2 = p2.get::<0>();
198 let y2 = p2.get::<1>();
199 let x3 = p3.get::<0>();
200 let y3 = p3.get::<1>();
201 let x4 = p4.get::<0>();
202 let y4 = p4.get::<1>();
203
204 let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
206 let a = x1 * y2 - y1 * x2;
207 let b = x3 * y4 - y3 * x4;
208 let px = (a * (x3 - x4) - (x1 - x2) * b) / denom;
209 let py = (a * (y3 - y4) - (y1 - y2) * b) / denom;
210 make_point::<P>(px, py)
211}
212
213fn on_segment<P>(p: &P, s1: &P, s2: &P) -> bool
217where
218 P: Point,
219 P::Scalar: CoordinateScalar,
220{
221 let (px, py) = (p.get::<0>(), p.get::<1>());
222 let (ax, ay) = (s1.get::<0>(), s1.get::<1>());
223 let (bx, by) = (s2.get::<0>(), s2.get::<1>());
224 min(ax, bx) <= px && px <= max(ax, bx) && min(ay, by) <= py && py <= max(ay, by)
225}
226
227fn collinear_overlap<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> SegmentIntersection<P>
234where
235 P: PointMut + Default,
236 P::Scalar: CoordinateScalar,
237{
238 let spread_x = (p1.get::<0>() - p2.get::<0>()).abs();
241 let spread_y = (p1.get::<1>() - p2.get::<1>()).abs();
242 let project_on_x = spread_x >= spread_y;
243
244 let key = |p: &P| -> P::Scalar {
245 if project_on_x {
246 p.get::<0>()
247 } else {
248 p.get::<1>()
249 }
250 };
251
252 let (a_lo, a_hi) = ordered(p1, p2, &key);
255 let (b_lo, b_hi) = ordered(p3, p4, &key);
256
257 let lo = if key(a_lo) >= key(b_lo) { a_lo } else { b_lo };
258 let hi = if key(a_hi) <= key(b_hi) { a_hi } else { b_hi };
259
260 if key(lo) > key(hi) {
261 SegmentIntersection::Disjoint
262 } else if key(lo) == key(hi) {
263 SegmentIntersection::Single(clone_point(lo))
264 } else {
265 SegmentIntersection::Collinear {
266 from: clone_point(lo),
267 to: clone_point(hi),
268 }
269 }
270}
271
272fn ordered<'p, P, F>(a: &'p P, b: &'p P, key: &F) -> (&'p P, &'p P)
274where
275 P: Point,
276 F: Fn(&P) -> P::Scalar,
277{
278 if key(a) <= key(b) { (a, b) } else { (b, a) }
279}
280
281fn min<T: CoordinateScalar>(a: T, b: T) -> T {
282 if a <= b { a } else { b }
283}
284
285fn max<T: CoordinateScalar>(a: T, b: T) -> T {
286 if a >= b { a } else { b }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::{SegmentIntersection, segment_intersection};
299 use geometry_cs::Cartesian;
300 use geometry_model::{Point2D, Segment};
301 use geometry_trait::Point as _;
302
303 type P = Point2D<f64, Cartesian>;
304 type Seg = Segment<P>;
305
306 fn coords(p: &P) -> (f64, f64) {
307 (p.get::<0>(), p.get::<1>())
308 }
309
310 #[test]
311 fn proper_crossing() {
312 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
313 let b = Seg::new(P::new(0.0, 2.0), P::new(2.0, 0.0));
314 match segment_intersection::<Seg, P>(&a, &b) {
315 SegmentIntersection::Single(p) => assert_eq!(coords(&p), (1.0, 1.0)),
316 other => panic!("{other:?}"),
317 }
318 }
319
320 #[test]
321 fn t_junction_at_endpoint() {
322 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
324 let b = Seg::new(P::new(2.0, 0.0), P::new(2.0, 3.0));
325 match segment_intersection::<Seg, P>(&a, &b) {
326 SegmentIntersection::Single(p) => assert_eq!(coords(&p), (2.0, 0.0)),
327 other => panic!("{other:?}"),
328 }
329 }
330
331 #[test]
332 fn collinear_overlap() {
333 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
334 let b = Seg::new(P::new(2.0, 0.0), P::new(6.0, 0.0));
335 match segment_intersection::<Seg, P>(&a, &b) {
336 SegmentIntersection::Collinear { from, to } => {
337 let (mut lo, mut hi) = (coords(&from), coords(&to));
338 if lo.0 > hi.0 {
339 core::mem::swap(&mut lo, &mut hi);
340 }
341 assert_eq!(lo, (2.0, 0.0));
342 assert_eq!(hi, (4.0, 0.0));
343 }
344 other => panic!("{other:?}"),
345 }
346 }
347
348 #[test]
349 fn collinear_touching_at_one_point() {
350 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
352 let b = Seg::new(P::new(4.0, 0.0), P::new(8.0, 0.0));
353 match segment_intersection::<Seg, P>(&a, &b) {
354 SegmentIntersection::Single(p) => assert_eq!(coords(&p), (4.0, 0.0)),
355 other => panic!("{other:?}"),
356 }
357 }
358
359 #[test]
360 fn collinear_disjoint() {
361 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 0.0));
362 let b = Seg::new(P::new(5.0, 0.0), P::new(7.0, 0.0));
363 assert_eq!(
364 segment_intersection::<Seg, P>(&a, &b),
365 SegmentIntersection::Disjoint
366 );
367 }
368
369 #[test]
370 fn parallel_disjoint() {
371 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
372 let b = Seg::new(P::new(0.0, 1.0), P::new(4.0, 1.0));
373 assert_eq!(
374 segment_intersection::<Seg, P>(&a, &b),
375 SegmentIntersection::Disjoint
376 );
377 }
378
379 #[test]
380 fn skew_disjoint() {
381 let a = Seg::new(P::new(0.0, 0.0), P::new(1.0, 0.0));
383 let b = Seg::new(P::new(3.0, 1.0), P::new(3.0, 2.0));
384 assert_eq!(
385 segment_intersection::<Seg, P>(&a, &b),
386 SegmentIntersection::Disjoint
387 );
388 }
389
390 #[test]
391 fn out_of_range_endpoint_refused() {
392 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
393 let b = Seg::new(P::new(0.0, 2.0), P::new(2.0e9, 0.0));
394 assert_eq!(
395 segment_intersection::<Seg, P>(&a, &b),
396 SegmentIntersection::OutOfRange
397 );
398 }
399
400 #[test]
401 fn off_center_crossing_point() {
402 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 4.0));
404 let b = Seg::new(P::new(0.0, 4.0), P::new(4.0, 0.0));
405 match segment_intersection::<Seg, P>(&a, &b) {
406 SegmentIntersection::Single(p) => assert_eq!(coords(&p), (2.0, 2.0)),
407 other => panic!("{other:?}"),
408 }
409 }
410}