Skip to content

Commit

Permalink
Implement intersection (#4)
Browse files Browse the repository at this point in the history
Implement intersection module

Setup rust test for basic utility 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced geometric intersection detection to improve accuracy and
efficiency for complex segment configurations, including crossing,
parallel, and collinear cases.
- Introduced a new public module for advanced intersection operations
and added flexible constructors for creating geometric points and
segments from both integer and floating-point inputs.
  
- **Tests**
- Deployed a comprehensive suite of validations across various geometric
scenarios to ensure robust functionality and reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ashley Anderson <[email protected]>
  • Loading branch information
Czaki and aganders3 authored Feb 10, 2025
1 parent 6405677 commit 1475152
Show file tree
Hide file tree
Showing 4 changed files with 624 additions and 7 deletions.
217 changes: 217 additions & 0 deletions crates/bermuda/tests/intersection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use rstest::rstest;
use std::collections::HashSet;
use triangulation::intersection;
use triangulation::point::{Point, Segment};

fn seg(x1: i32, y1: i32, x2: i32, y2: i32) -> Segment {
Segment::new(
Point::new(x1 as f32, y1 as f32),
Point::new(x2 as f32, y2 as f32),
)
}

fn seg_f(x1: f32, y1: f32, x2: f32, y2: f32) -> Segment {
Segment::new(Point::new(x1, y1), Point::new(x2, y2))
}

#[rstest]
#[case(seg(0, 0, 2, 2), Point::new(1.0, 1.0), true)]
#[case(seg(0, 0, 0, 2), Point::new(0.0, 1.0), true)]
#[case(seg(0, 0, 2, 0), Point::new(1.0, 0.0), true)]
#[case(seg(0, 0, 1, 1), Point::new(2.0, 2.0), false)]
#[case(seg(0, 0, 0, 1), Point::new(0.0, 2.0), false)]
#[case(seg(0, 0, 1, 0), Point::new(2.0, 0.0), false)]
#[case(seg_f(1e6, 1e6, 3e6, 3e6), Point::new(2e6, 2e6), true)]
#[case(seg_f(0.0, 0.0, 0.0001, 0.0001), Point::new(0.00005, 0.00005), true)]
#[case(seg(0, 0, -2, -2), Point::new(-1.0, -1.0), true)]
fn test_on_segment_if_collinear(#[case] s: Segment, #[case] q: Point, #[case] expected: bool) {
assert_eq!(intersection::on_segment_if_collinear(&s, q), expected);
}

#[rstest]
fn test_do_intersect_crossing_segments() {
assert!(intersection::do_intersect(
&Segment::new(Point::new_i(0, -1), Point::new_i(0, 1)),
&Segment::new(Point::new_i(-1, 0), Point::new_i(1, 0))
));
}

#[rstest]
#[case(Segment::new_i((0, 0), (1, 1)), Segment::new_i((1, 0), (0, 1)))]
#[case(Segment::new_i((1, 0), (0, 1)), Segment::new_i((0, 0), (1, 1)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_i((0, 1), (1, 1)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_i((1, 1), (0, 1)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_i((0, 0), (1, 1)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_i((1, 1), (0, 0)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_f((0.0, 0.5), (1.0, 1.0)))]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_f((1.0, 1.0), (0.0, 0.5)))]
fn test_do_intersect(#[case] s1: Segment, #[case] s2: Segment) {
assert!(intersection::do_intersect(&s1, &s2));
}

#[rstest]
#[case(Segment::new_i((0, 0), (0, 1)), Segment::new_i((1, 2), (-1, 2)))]
#[case(Segment::new_i((0, 0), (1, 0)), Segment::new_i((2, 1), (2, -1)))]
#[case(Segment::new_i((0, 0), (1, 1)), Segment::new_i((1, 2), (0, 1)))]
fn test_do_intersect_ne(#[case] s1: Segment, #[case] s2: Segment) {
assert!(!intersection::do_intersect(&s1, &s2));
}

#[rstest]
fn test_do_intersect_parallel_segments() {
assert_ne!(
intersection::do_intersect(
&Segment::new(Point::new_i(0, -1), Point::new_i(0, 1)),
&Segment::new(Point::new_i(1, -2), Point::new_i(1, 1))
),
true
)
}

#[rstest]
#[case(Segment::new_i((0, 0), (2, 2)), Segment::new_i((2, 0), (0, 2)), Point::new_i(1, 1))]
#[case(Segment::new_i((0, 0), (1, 0)), Segment::new_i((0, 1), (0, 0)), Point::new_i(0, 0))]
#[case(Segment::new_i((0, 0), (2, 0)), Segment::new_i((1, 0), (1, 2)), Point::new_i(1, 0))]
#[case(Segment::new_f((0.0, 0.0), (2.0, 2.0)), Segment::new_f((2.0, 0.0), (0.0, 2.0)), Point::new(1.0, 1.0))]
#[case(Segment::new_f((0.0, 0.0), (1.0, 1.0)), Segment::new_f((0.99, 0.0), (0.0, 0.99)), Point::new(0.495, 0.495))]
#[case(Segment::new_f((1e6, 1e6), (2e6, 2e6)), Segment::new_f((2e6, 1e6), (1e6, 2e6)), Point::new(1.5e6, 1.5e6))]
fn test_find_intersection_point(#[case] s1: Segment, #[case] s2: Segment, #[case] expected: Point) {
assert_eq!(
intersection::find_intersection(&s1, &s2),
intersection::Intersection::PointIntersection(expected)
);
assert_eq!(
intersection::find_intersection(&s2, &s1),
intersection::Intersection::PointIntersection(expected)
);
}

#[rstest]
fn test_find_intersection_collinear_segments() {
assert_eq!(
intersection::find_intersection(
&Segment::new_i((0, 0), (2, 0)),
&Segment::new_i((1, 0), (3, 0))
),
intersection::Intersection::CollinearWithOverlap(vec![
Point::new_i(1, 0),
Point::new_i(2, 0)
])
);
assert_eq!(
intersection::find_intersection(
&Segment::new_i((0, 0), (2, 0)),
&Segment::new_i((1, 0), (3, 0))
),
intersection::Intersection::CollinearWithOverlap(vec![
Point::new_i(1, 0),
Point::new_i(2, 0)
])
);
}

/// Tests a simple square configuration with no intersections.
/// Each segment connects to the next at endpoints, but there
/// are no true intersections between non-adjacent segments.
/// (1, 0) --- (1, 1)
/// | |
/// (0, 0) --- (0, 1)
#[rstest]
fn test_find_intersections_1() {
let segments = vec![
Segment::new(Point::new(0.0, 0.0), Point::new(0.0, 1.0)),
Segment::new(Point::new(0.0, 1.0), Point::new(1.0, 1.0)),
Segment::new(Point::new(1.0, 1.0), Point::new(1.0, 0.0)),
Segment::new(Point::new(1.0, 0.0), Point::new(0.0, 0.0)),
];
let intersections = intersection::find_intersections(&segments);
assert!(intersections.is_empty());
}

/// Tests a configuration with two intersecting diagonals.
/// Expected behavior:
/// - Only one intersection is recorded between segments 1 and 3
/// - The intersection occurs at (0.5, 0.5)
/// (1, 0) --- (1, 1)
/// \ /
/// \ /
/// \ /
/// X
/// / \
/// / \
/// / \
/// (0, 0) --- (0, 1)
#[rstest]
fn test_find_intersections_2() {
let segments = vec![
Segment::new(Point::new(0.0, 0.0), Point::new(0.0, 1.0)),
Segment::new(Point::new(0.0, 1.0), Point::new(1.0, 0.0)),
Segment::new(Point::new(1.0, 0.0), Point::new(1.0, 1.0)),
Segment::new(Point::new(1.0, 1.0), Point::new(0.0, 0.0)),
Segment::new(Point::new(0.0, 0.0), Point::new(0.0, 1.0)),
];
let intersections = intersection::find_intersections(&segments);
let expected = [(1, 3)]
.iter()
.map(|&(a, b)| intersection::OrderedPair::new(a, b))
.collect();
assert_eq!(intersections, expected);
}

#[rstest]
#[case::no_intersections_simple_square(
vec![
Segment::new_i((0, 0), (0, 1)),
Segment::new_i((0, 1), (1, 1)),
Segment::new_i((1, 1), (1, 0)),
Segment::new_i((1, 0), (0, 0)),
],
HashSet::new()
)]
#[case::one_intersection_crossing_diagonals(
vec![
Segment::new_i((0, 0), (2, 2)),
Segment::new_i((2, 0), (0, 2)),
],
[(0, 1)].iter().map(|&(a, b)| intersection::OrderedPair::new(a, b)).collect()
)]
#[case::multiple_intersections_complex_shape(
vec![
Segment::new_i((0, 0), (2, 2)),
Segment::new_i((2, 0), (0, 2)),
Segment::new_i((1, 0), (1, 2)),
Segment::new_i((0, 1), (2, 1)),
],
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)].iter().map(|&(a, b)| intersection::OrderedPair::new(a, b)).collect()
)]
#[case::no_intersections_non_intersecting_lines(
vec![
Segment::new_i((0, 0), (1, 1)),
Segment::new_i((2, 2), (3, 3)),
],
HashSet::new()
)]
#[case::one_intersection_t_shaped_intersection(
vec![
Segment::new_i((0, 0), (2, 0)),
Segment::new_i((1, -1), (1, 1)),
],
[(0, 1)].iter().map(|&(a, b)| intersection::OrderedPair::new(a, b)).collect()
)]
#[case::multiple_intersections_grid_shape(
vec![
Segment::new_i((0, 0), (2, 0)),
Segment::new_i((0, 1), (2, 1)),
Segment::new_i((0, 2), (2, 2)),
Segment::new_i((0, 0), (0, 2)),
Segment::new_i((1, 0), (1, 2)),
Segment::new_i((2, 0), (2, 2)),
],
[(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)].iter().map(|&(a, b)| intersection::OrderedPair::new(a, b)).collect()
)]
fn test_find_intersections_param(
#[case] segments: Vec<Segment>,
#[case] expected: HashSet<intersection::OrderedPair>,
) {
assert_eq!(intersection::find_intersections(&segments), expected);
}
Loading

0 comments on commit 1475152

Please sign in to comment.