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