Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement intersection #4

Merged
merged 27 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
808b93e
start implementing intersections
Czaki Jan 22, 2025
77fe9e7
fix command name
Czaki Jan 22, 2025
a217c82
improve handling test parameterers, add orientation
Czaki Jan 26, 2025
7fcd6ef
improve tests
Czaki Jan 26, 2025
1f3e690
add intersections function
Czaki Jan 27, 2025
df21ed2
add share endpoint
Czaki Jan 27, 2025
b7c7b6a
improve tests
Czaki Jan 27, 2025
3ca24f3
improve readabilty of tests
Czaki Jan 27, 2025
45394ee
improve docstrings
Czaki Jan 27, 2025
a1e0f62
add find_intersection function
Czaki Jan 27, 2025
6818fca
add find_intersections function
Czaki Jan 27, 2025
5f5ebff
Merge remote-tracking branch 'upstream/main' into intersections
Czaki Jan 27, 2025
cdb3a09
small improvement
Czaki Jan 27, 2025
57c6561
improve tests
Czaki Jan 27, 2025
11aa37f
Update crates/triangulation/src/intersection.rs
Czaki Jan 29, 2025
383b606
use enum for colinear results
Czaki Jan 29, 2025
0c5543c
use enum in find_intersection
Czaki Jan 29, 2025
db89ecd
improvements from code review
Czaki Jan 29, 2025
bb18095
fix tests
Czaki Jan 29, 2025
e236f8d
Merge branch 'main' into intersections
Czaki Jan 29, 2025
0c6a17f
Merge remote-tracking branch 'upstream/main' into intersections
Czaki Jan 30, 2025
9058f9f
Merge remote-tracking branch 'upstream/main' into intersections
Czaki Jan 30, 2025
86967a9
use orientatin form point crate
Czaki Jan 30, 2025
40d2985
fix formating
Czaki Jan 30, 2025
e4bdd7d
Merge branch 'main' into intersections
Czaki Feb 4, 2025
e11ed91
return intersection if we ore only epsilon out of range
Czaki Feb 4, 2025
c10b8fa
fix typo in var name
Czaki Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading