1use crate::error::{SpatialError, SpatialResult};
33use std::cmp::Ordering;
34
35const EPSILON: f64 = 1e-10;
37
38#[derive(Debug, Clone, Copy)]
40pub struct Point2D {
41 pub x: f64,
43 pub y: f64,
45}
46
47impl Point2D {
48 pub fn new(x: f64, y: f64) -> Self {
50 Self { x, y }
51 }
52}
53
54impl PartialEq for Point2D {
55 fn eq(&self, other: &Self) -> bool {
56 (self.x - other.x).abs() < EPSILON && (self.y - other.y).abs() < EPSILON
57 }
58}
59
60impl Eq for Point2D {}
61
62impl PartialOrd for Point2D {
63 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64 Some(self.cmp(other))
65 }
66}
67
68impl Ord for Point2D {
69 fn cmp(&self, other: &Self) -> Ordering {
70 match float_cmp(self.x, other.x) {
72 Ordering::Equal => float_cmp(self.y, other.y),
73 ord => ord,
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy)]
80pub struct Segment2D {
81 pub start: Point2D,
83 pub end: Point2D,
85 id: usize,
87}
88
89impl Segment2D {
90 pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
100 let p1 = Point2D::new(x1, y1);
101 let p2 = Point2D::new(x2, y2);
102 let (start, end) = if p1 <= p2 { (p1, p2) } else { (p2, p1) };
103 Self { start, end, id: 0 }
104 }
105
106 pub fn from_points(p1: Point2D, p2: Point2D) -> Self {
108 let (start, end) = if p1 <= p2 { (p1, p2) } else { (p2, p1) };
109 Self { start, end, id: 0 }
110 }
111
112 fn y_at_x(&self, x: f64) -> Option<f64> {
116 let dx = self.end.x - self.start.x;
117 if dx.abs() < EPSILON {
118 Some((self.start.y + self.end.y) / 2.0)
120 } else {
121 let t = (x - self.start.x) / dx;
122 if !(-EPSILON..=1.0 + EPSILON).contains(&t) {
123 return None;
124 }
125 let t_clamped = t.clamp(0.0, 1.0);
126 Some(self.start.y + t_clamped * (self.end.y - self.start.y))
127 }
128 }
129
130 fn is_vertical(&self) -> bool {
132 (self.end.x - self.start.x).abs() < EPSILON
133 }
134
135 fn slope(&self) -> Option<f64> {
137 let dx = self.end.x - self.start.x;
138 if dx.abs() < EPSILON {
139 None
140 } else {
141 Some((self.end.y - self.start.y) / dx)
142 }
143 }
144}
145
146impl PartialEq for Segment2D {
147 fn eq(&self, other: &Self) -> bool {
148 self.id == other.id
149 }
150}
151
152impl Eq for Segment2D {}
153
154#[derive(Debug, Clone)]
156pub struct Intersection {
157 pub point: Point2D,
159 pub segment_a: usize,
161 pub segment_b: usize,
163}
164
165#[derive(Debug, Clone)]
167enum EventType {
168 LeftEndpoint(usize),
170 RightEndpoint(usize),
172 IntersectionEvent(usize, usize),
174}
175
176#[derive(Debug, Clone)]
178struct SweepEvent {
179 point: Point2D,
180 event_type: EventType,
181}
182
183impl PartialEq for SweepEvent {
184 fn eq(&self, other: &Self) -> bool {
185 self.point == other.point
186 }
187}
188
189impl Eq for SweepEvent {}
190
191impl PartialOrd for SweepEvent {
192 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
193 Some(self.cmp(other))
194 }
195}
196
197impl Ord for SweepEvent {
198 fn cmp(&self, other: &Self) -> Ordering {
199 self.point.cmp(&other.point)
200 }
201}
202
203#[derive(Debug, Clone)]
205struct StatusEntry {
206 segment_id: usize,
207 current_y: f64,
209 slope: f64,
211}
212
213impl PartialEq for StatusEntry {
214 fn eq(&self, other: &Self) -> bool {
215 self.segment_id == other.segment_id
216 }
217}
218
219impl Eq for StatusEntry {}
220
221impl PartialOrd for StatusEntry {
222 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
223 Some(self.cmp(other))
224 }
225}
226
227impl Ord for StatusEntry {
228 fn cmp(&self, other: &Self) -> Ordering {
229 match float_cmp(self.current_y, other.current_y) {
230 Ordering::Equal => {
231 match float_cmp(self.slope, other.slope) {
233 Ordering::Equal => self.segment_id.cmp(&other.segment_id),
234 ord => ord,
235 }
236 }
237 ord => ord,
238 }
239 }
240}
241
242fn float_cmp(a: f64, b: f64) -> Ordering {
244 if (a - b).abs() < EPSILON {
245 Ordering::Equal
246 } else if a < b {
247 Ordering::Less
248 } else {
249 Ordering::Greater
250 }
251}
252
253pub fn segment_intersection(seg_a: &Segment2D, seg_b: &Segment2D) -> Option<(Point2D, f64, f64)> {
268 let x1 = seg_a.start.x;
269 let y1 = seg_a.start.y;
270 let x2 = seg_a.end.x;
271 let y2 = seg_a.end.y;
272 let x3 = seg_b.start.x;
273 let y3 = seg_b.start.y;
274 let x4 = seg_b.end.x;
275 let y4 = seg_b.end.y;
276
277 let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
278
279 if denom.abs() < EPSILON {
280 return None;
282 }
283
284 let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
285 let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
286
287 let tol = EPSILON;
289 if t >= -tol && t <= 1.0 + tol && u >= -tol && u <= 1.0 + tol {
290 let ix = x1 + t * (x2 - x1);
291 let iy = y1 + t * (y2 - y1);
292 Some((Point2D::new(ix, iy), t, u))
293 } else {
294 None
295 }
296}
297
298pub fn find_all_intersections(segments: &[Segment2D]) -> SpatialResult<Vec<Intersection>> {
336 if segments.is_empty() || segments.len() < 2 {
337 return Ok(Vec::new());
338 }
339
340 let mut segs: Vec<Segment2D> = segments.to_vec();
347 for (i, seg) in segs.iter_mut().enumerate() {
348 seg.id = i;
349 }
350
351 let mut events: Vec<(f64, bool, usize)> = Vec::with_capacity(segs.len() * 2);
353 for (i, seg) in segs.iter().enumerate() {
354 events.push((seg.start.x, true, i)); events.push((seg.end.x, false, i)); }
357 events.sort_by(|a, b| {
358 float_cmp(a.0, b.0).then_with(|| {
359 match (a.1, b.1) {
361 (true, false) => std::cmp::Ordering::Less,
362 (false, true) => std::cmp::Ordering::Greater,
363 _ => a.2.cmp(&b.2),
364 }
365 })
366 });
367
368 let mut active: Vec<usize> = Vec::new();
370 let mut intersections: Vec<Intersection> = Vec::new();
371 let mut found_pairs: std::collections::HashSet<(usize, usize)> =
372 std::collections::HashSet::new();
373
374 for (_, is_start, seg_idx) in &events {
375 if *is_start {
376 for &other_idx in &active {
378 let pair = if *seg_idx < other_idx {
379 (*seg_idx, other_idx)
380 } else {
381 (other_idx, *seg_idx)
382 };
383
384 if !found_pairs.contains(&pair) {
385 if let Some((pt, _, _)) = segment_intersection(&segs[pair.0], &segs[pair.1]) {
386 found_pairs.insert(pair);
387 intersections.push(Intersection {
388 point: pt,
389 segment_a: pair.0,
390 segment_b: pair.1,
391 });
392 }
393 }
394 }
395 active.push(*seg_idx);
396 } else {
397 active.retain(|&id| id != *seg_idx);
399 }
400 }
401
402 Ok(intersections)
403}
404
405pub fn find_all_intersections_brute_force(segments: &[Segment2D]) -> Vec<Intersection> {
424 let mut intersections = Vec::new();
425
426 for i in 0..segments.len() {
427 for j in (i + 1)..segments.len() {
428 if let Some((pt, _, _)) = segment_intersection(&segments[i], &segments[j]) {
429 intersections.push(Intersection {
430 point: pt,
431 segment_a: i,
432 segment_b: j,
433 });
434 }
435 }
436 }
437
438 intersections
439}
440
441pub fn count_intersections(segments: &[Segment2D]) -> SpatialResult<usize> {
453 let intersections = find_all_intersections(segments)?;
454 Ok(intersections.len())
455}
456
457pub fn has_any_intersection(segments: &[Segment2D]) -> SpatialResult<bool> {
469 if segments.len() <= 10 {
471 for i in 0..segments.len() {
472 for j in (i + 1)..segments.len() {
473 if segment_intersection(&segments[i], &segments[j]).is_some() {
474 return Ok(true);
475 }
476 }
477 }
478 return Ok(false);
479 }
480
481 let intersections = find_all_intersections(segments)?;
482 Ok(!intersections.is_empty())
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn test_two_crossing_segments() {
491 let segments = vec![
492 Segment2D::new(0.0, 0.0, 2.0, 2.0),
493 Segment2D::new(0.0, 2.0, 2.0, 0.0),
494 ];
495
496 let intersections = find_all_intersections(&segments).expect("Operation failed");
497 assert_eq!(intersections.len(), 1);
498 assert!((intersections[0].point.x - 1.0).abs() < 1e-6);
499 assert!((intersections[0].point.y - 1.0).abs() < 1e-6);
500 }
501
502 #[test]
503 fn test_no_intersections() {
504 let segments = vec![
505 Segment2D::new(0.0, 0.0, 1.0, 0.0),
506 Segment2D::new(0.0, 1.0, 1.0, 1.0),
507 ];
508
509 let intersections = find_all_intersections(&segments).expect("Operation failed");
510 assert_eq!(intersections.len(), 0);
511 }
512
513 #[test]
514 fn test_multiple_intersections() {
515 let segments = vec![
517 Segment2D::new(0.0, 0.0, 4.0, 4.0), Segment2D::new(0.0, 4.0, 4.0, 0.0), Segment2D::new(0.0, 2.0, 4.0, 2.0), ];
521
522 let intersections = find_all_intersections(&segments).expect("Operation failed");
523 assert_eq!(intersections.len(), 3);
525 }
526
527 #[test]
528 fn test_parallel_segments() {
529 let segments = vec![
530 Segment2D::new(0.0, 0.0, 2.0, 0.0),
531 Segment2D::new(0.0, 1.0, 2.0, 1.0),
532 Segment2D::new(0.0, 2.0, 2.0, 2.0),
533 ];
534
535 let intersections = find_all_intersections(&segments).expect("Operation failed");
536 assert_eq!(intersections.len(), 0);
537 }
538
539 #[test]
540 fn test_endpoint_intersection() {
541 let segments = vec![
543 Segment2D::new(0.0, 0.0, 1.0, 1.0),
544 Segment2D::new(1.0, 1.0, 2.0, 0.0),
545 ];
546
547 let intersections = find_all_intersections(&segments).expect("Operation failed");
548 assert_eq!(intersections.len(), 1);
550 assert!((intersections[0].point.x - 1.0).abs() < 1e-6);
551 assert!((intersections[0].point.y - 1.0).abs() < 1e-6);
552 }
553
554 #[test]
555 fn test_brute_force_matches_sweep() {
556 let segments = vec![
557 Segment2D::new(0.0, 0.0, 3.0, 3.0),
558 Segment2D::new(0.0, 3.0, 3.0, 0.0),
559 Segment2D::new(1.0, 0.0, 1.0, 4.0),
560 ];
561
562 let sweep_result = find_all_intersections(&segments).expect("Operation failed");
563 let brute_result = find_all_intersections_brute_force(&segments);
564
565 assert_eq!(sweep_result.len(), brute_result.len());
566 }
567
568 #[test]
569 fn test_empty_input() {
570 let segments: Vec<Segment2D> = vec![];
571 let intersections = find_all_intersections(&segments).expect("Operation failed");
572 assert_eq!(intersections.len(), 0);
573 }
574
575 #[test]
576 fn test_single_segment() {
577 let segments = vec![Segment2D::new(0.0, 0.0, 1.0, 1.0)];
578 let intersections = find_all_intersections(&segments).expect("Operation failed");
579 assert_eq!(intersections.len(), 0);
580 }
581
582 #[test]
583 fn test_segment_intersection_function() {
584 let seg1 = Segment2D::new(0.0, 0.0, 2.0, 2.0);
585 let seg2 = Segment2D::new(0.0, 2.0, 2.0, 0.0);
586
587 let result = segment_intersection(&seg1, &seg2);
588 assert!(result.is_some());
589
590 let (pt, t, u) = result.expect("Operation failed");
591 assert!((pt.x - 1.0).abs() < 1e-9);
592 assert!((pt.y - 1.0).abs() < 1e-9);
593 assert!((t - 0.5).abs() < 1e-9);
594 assert!((u - 0.5).abs() < 1e-9);
595 }
596
597 #[test]
598 fn test_has_any_intersection() {
599 let crossing = vec![
600 Segment2D::new(0.0, 0.0, 2.0, 2.0),
601 Segment2D::new(0.0, 2.0, 2.0, 0.0),
602 ];
603 assert!(has_any_intersection(&crossing).expect("Operation failed"));
604
605 let parallel = vec![
606 Segment2D::new(0.0, 0.0, 2.0, 0.0),
607 Segment2D::new(0.0, 1.0, 2.0, 1.0),
608 ];
609 assert!(!has_any_intersection(¶llel).expect("Operation failed"));
610 }
611
612 #[test]
613 fn test_count_intersections() {
614 let segments = vec![
615 Segment2D::new(0.0, 0.0, 4.0, 4.0),
616 Segment2D::new(0.0, 4.0, 4.0, 0.0),
617 Segment2D::new(0.0, 2.0, 4.0, 2.0),
618 ];
619
620 let count = count_intersections(&segments).expect("Operation failed");
621 assert_eq!(count, 3);
622 }
623
624 #[test]
625 fn test_star_pattern() {
626 let segments = vec![
628 Segment2D::new(0.0, 2.0, 4.0, 2.0), Segment2D::new(2.0, 0.0, 2.0, 4.0), Segment2D::new(0.0, 0.0, 4.0, 4.0), Segment2D::new(0.0, 4.0, 4.0, 0.0), ];
633
634 let intersections = find_all_intersections(&segments).expect("Operation failed");
635 let brute = find_all_intersections_brute_force(&segments);
638 assert_eq!(intersections.len(), brute.len());
639 }
640
641 #[test]
642 fn test_vertical_segment() {
643 let segments = vec![
644 Segment2D::new(1.0, 0.0, 1.0, 2.0), Segment2D::new(0.0, 1.0, 2.0, 1.0), ];
647
648 let intersections = find_all_intersections(&segments).expect("Operation failed");
649 assert_eq!(intersections.len(), 1);
650 assert!((intersections[0].point.x - 1.0).abs() < 1e-6);
651 assert!((intersections[0].point.y - 1.0).abs() < 1e-6);
652 }
653}