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
302 type P = Point2D<f64, Cartesian>;
303 type Seg = Segment<P>;
304
305 #[test]
306 fn proper_crossing() {
307 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
308 let b = Seg::new(P::new(0.0, 2.0), P::new(2.0, 0.0));
309 assert_eq!(
310 segment_intersection::<Seg, P>(&a, &b),
311 SegmentIntersection::Single(P::new(1.0, 1.0))
312 );
313 }
314
315 #[test]
316 fn t_junction_at_endpoint() {
317 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
319 let b = Seg::new(P::new(2.0, 0.0), P::new(2.0, 3.0));
320 assert_eq!(
321 segment_intersection::<Seg, P>(&a, &b),
322 SegmentIntersection::Single(P::new(2.0, 0.0))
323 );
324 }
325
326 #[test]
327 fn collinear_overlap() {
328 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
329 let b = Seg::new(P::new(2.0, 0.0), P::new(6.0, 0.0));
330 assert_eq!(
331 segment_intersection::<Seg, P>(&a, &b),
332 SegmentIntersection::Collinear {
333 from: P::new(2.0, 0.0),
334 to: P::new(4.0, 0.0),
335 }
336 );
337 }
338
339 #[test]
340 fn collinear_touching_at_one_point() {
341 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
343 let b = Seg::new(P::new(4.0, 0.0), P::new(8.0, 0.0));
344 assert_eq!(
345 segment_intersection::<Seg, P>(&a, &b),
346 SegmentIntersection::Single(P::new(4.0, 0.0))
347 );
348 }
349
350 #[test]
351 fn collinear_disjoint() {
352 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 0.0));
353 let b = Seg::new(P::new(5.0, 0.0), P::new(7.0, 0.0));
354 assert_eq!(
355 segment_intersection::<Seg, P>(&a, &b),
356 SegmentIntersection::Disjoint
357 );
358 }
359
360 #[test]
361 fn parallel_disjoint() {
362 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
363 let b = Seg::new(P::new(0.0, 1.0), P::new(4.0, 1.0));
364 assert_eq!(
365 segment_intersection::<Seg, P>(&a, &b),
366 SegmentIntersection::Disjoint
367 );
368 }
369
370 #[test]
371 fn skew_disjoint() {
372 let a = Seg::new(P::new(0.0, 0.0), P::new(1.0, 0.0));
374 let b = Seg::new(P::new(3.0, 1.0), P::new(3.0, 2.0));
375 assert_eq!(
376 segment_intersection::<Seg, P>(&a, &b),
377 SegmentIntersection::Disjoint
378 );
379 }
380
381 #[test]
382 fn out_of_range_endpoint_refused() {
383 let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
384 let b = Seg::new(P::new(0.0, 2.0), P::new(2.0e9, 0.0));
385 assert_eq!(
386 segment_intersection::<Seg, P>(&a, &b),
387 SegmentIntersection::OutOfRange
388 );
389 }
390
391 #[test]
392 fn off_center_crossing_point() {
393 let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 4.0));
395 let b = Seg::new(P::new(0.0, 4.0), P::new(4.0, 0.0));
396 assert_eq!(
397 segment_intersection::<Seg, P>(&a, &b),
398 SegmentIntersection::Single(P::new(2.0, 2.0))
399 );
400 }
401}