#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Orientation {
CounterClockwise,
Clockwise,
Collinear,
}
pub fn cross2d(ax: i64, ay: i64, bx: i64, by: i64, cx: i64, cy: i64) -> i128 {
let dx1 = (bx as i128) - (ax as i128);
let dy1 = (by as i128) - (ay as i128);
let dx2 = (cx as i128) - (ax as i128);
let dy2 = (cy as i128) - (ay as i128);
dx1 * dy2 - dy1 * dx2
}
pub fn orientation(ax: i64, ay: i64, bx: i64, by: i64, cx: i64, cy: i64) -> Orientation {
match cross2d(ax, ay, bx, by, cx, cy).cmp(&0) {
std::cmp::Ordering::Greater => Orientation::CounterClockwise,
std::cmp::Ordering::Less => Orientation::Clockwise,
std::cmp::Ordering::Equal => Orientation::Collinear,
}
}
pub fn is_left(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
cross2d(ax, ay, bx, by, px, py) > 0
}
pub fn is_left_or_on(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
cross2d(ax, ay, bx, by, px, py) >= 0
}
pub fn is_right(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
cross2d(ax, ay, bx, by, px, py) < 0
}
pub fn is_right_or_on(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
cross2d(ax, ay, bx, by, px, py) <= 0
}
pub fn is_collinear_pts(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
cross2d(ax, ay, bx, by, px, py) == 0
}
pub fn is_reflex(
prev_x: i64,
prev_y: i64,
curr_x: i64,
curr_y: i64,
next_x: i64,
next_y: i64,
) -> bool {
cross2d(prev_x, prev_y, curr_x, curr_y, next_x, next_y) < 0
}
pub fn edge_squared_length(ax: i64, ay: i64, bx: i64, by: i64) -> u128 {
let dx = (bx as i128) - (ax as i128);
let dy = (by as i128) - (ay as i128);
(dx * dx + dy * dy) as u128
}
pub fn point_on_segment(px: i64, py: i64, ax: i64, ay: i64, bx: i64, by: i64) -> bool {
if cross2d(ax, ay, bx, by, px, py) != 0 {
return false;
}
px >= ax.min(bx) && px <= ax.max(bx) && py >= ay.min(by) && py <= ay.max(by)
}
pub fn segments_properly_intersect(
a1x: i64,
a1y: i64,
a2x: i64,
a2y: i64,
b1x: i64,
b1y: i64,
b2x: i64,
b2y: i64,
) -> bool {
let d1 = cross2d(b1x, b1y, b2x, b2y, a1x, a1y);
let d2 = cross2d(b1x, b1y, b2x, b2y, a2x, a2y);
let d3 = cross2d(a1x, a1y, a2x, a2y, b1x, b1y);
let d4 = cross2d(a1x, a1y, a2x, a2y, b2x, b2y);
((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))
}
pub fn segments_intersect(
a1x: i64,
a1y: i64,
a2x: i64,
a2y: i64,
b1x: i64,
b1y: i64,
b2x: i64,
b2y: i64,
) -> bool {
if segments_properly_intersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
return true;
}
point_on_segment(b1x, b1y, a1x, a1y, a2x, a2y)
|| point_on_segment(b2x, b2y, a1x, a1y, a2x, a2y)
|| point_on_segment(a1x, a1y, b1x, b1y, b2x, b2y)
|| point_on_segment(a2x, a2y, b1x, b1y, b2x, b2y)
}
#[cfg(test)]
mod tests {
use super::*;
const M: i64 = 1_000_000;
const MAX_WORLD: i64 = 40_075_017_000_000;
#[test]
fn cross2d_sign_and_collinearity_cases() {
assert!(cross2d(0, 0, M, 0, 0, M) > 0);
assert!(cross2d(0, 0, 0, M, M, 0) < 0);
assert_eq!(cross2d(0, 0, M, 0, 2 * M, 0), 0);
}
#[test]
fn cross2d_matches_expected_magnitude() {
assert_eq!(
cross2d(0, 0, 3 * M, 0, 0, 4 * M),
12 * (M as i128) * (M as i128)
);
assert_eq!(cross2d(-M, -M, M, -M, -M, M), 4 * (M as i128) * (M as i128));
assert_eq!(
cross2d(5 * M, 5 * M, 6 * M, 7 * M, 9 * M, 12 * M),
-(M as i128) * (M as i128)
);
}
#[test]
fn cross2d_handles_max_world_without_overflow() {
let result = cross2d(0, 0, MAX_WORLD, 0, 0, MAX_WORLD);
let expected = (MAX_WORLD as i128) * (MAX_WORLD as i128);
assert_eq!(result, expected);
assert!(cross2d(-MAX_WORLD, 0, MAX_WORLD, 0, 0, MAX_WORLD) > 0);
assert_eq!(
cross2d(0, 0, MAX_WORLD, MAX_WORLD, 2 * MAX_WORLD, 2 * MAX_WORLD),
0
);
}
#[test]
fn orientation_classifies_three_cases() {
assert_eq!(orientation(0, 0, M, 0, 0, M), Orientation::CounterClockwise);
assert_eq!(orientation(0, 0, 0, M, M, 0), Orientation::Clockwise);
assert_eq!(
orientation(0, 0, M, M, 2 * M, 2 * M),
Orientation::Collinear
);
}
#[test]
fn orientation_handles_shifted_and_negative_points() {
assert_eq!(
orientation(-M, -M, M, -M, 0, M),
Orientation::CounterClockwise
);
assert_eq!(orientation(-M, -M, 0, M, M, -M), Orientation::Clockwise);
assert_eq!(
orientation(-2 * M, -2 * M, -M, -M, 0, 0),
Orientation::Collinear
);
}
#[test]
fn orientation_handles_degenerate_repeated_points() {
assert_eq!(orientation(0, 0, 0, 0, M, M), Orientation::Collinear);
assert_eq!(orientation(0, 0, M, M, M, M), Orientation::Collinear);
assert_eq!(orientation(7, 11, 7, 11, 7, 11), Orientation::Collinear);
}
#[test]
fn side_predicates_classify_left_right_and_on() {
assert!(is_left(0, 0, M, 0, 0, M));
assert!(!is_left(0, 0, M, 0, M, 0));
assert!(!is_left(0, 0, M, 0, 0, -M));
assert!(is_left_or_on(0, 0, M, 0, 0, M));
assert!(is_left_or_on(0, 0, M, 0, M, 0));
assert!(!is_left_or_on(0, 0, M, 0, 0, -M));
assert!(is_right(0, 0, M, 0, 0, -M));
assert!(!is_right(0, 0, M, 0, M, 0));
assert!(!is_right(0, 0, M, 0, 0, M));
assert!(is_right_or_on(0, 0, M, 0, 0, -M));
assert!(is_right_or_on(0, 0, M, 0, M, 0));
assert!(!is_right_or_on(0, 0, M, 0, 0, M));
assert!(is_collinear_pts(0, 0, M, M, 2 * M, 2 * M));
assert!(is_collinear_pts(0, 0, 2 * M, 0, M, 0));
assert!(!is_collinear_pts(0, 0, M, 0, M, M));
}
#[test]
fn side_predicates_work_with_vertical_lines() {
assert!(is_left(0, 0, 0, M, -M, 0));
assert!(is_left_or_on(0, 0, 0, M, 0, M / 2));
assert!(is_right(0, 0, 0, M, M, 0));
assert!(is_right_or_on(0, 0, 0, M, 0, M / 2));
assert!(is_collinear_pts(0, 0, 0, 2 * M, 0, M));
}
#[test]
fn side_predicates_work_with_negative_coordinates() {
assert!(is_left(-M, -M, M, -M, 0, 0));
assert!(is_left_or_on(-M, -M, M, -M, -M, -M));
assert!(is_right(-M, -M, M, -M, 0, -2 * M));
assert!(is_right_or_on(-M, -M, M, -M, M, -M));
assert!(!is_collinear_pts(-M, -M, M, -M, 0, 0));
}
#[test]
fn is_reflex_distinguishes_concave_and_convex_vertices() {
assert!(is_reflex(0, 0, M, M, 2 * M, 0));
assert!(!is_reflex(0, 2 * M, 0, 0, 2 * M, 0));
assert!(!is_reflex(0, 0, M, 0, 2 * M, 0));
}
#[test]
fn is_reflex_handles_shifted_vertices() {
assert!(is_reflex(-2 * M, -2 * M, -M, -M, 0, -2 * M));
assert!(!is_reflex(-2 * M, -2 * M, -M, -2 * M, -M, -M));
assert!(!is_reflex(5 * M, 5 * M, 6 * M, 6 * M, 7 * M, 7 * M));
}
#[test]
fn is_reflex_uses_exact_orientation_semantics() {
assert_eq!(is_reflex(0, 0, M, 0, M, M), false);
assert_eq!(is_reflex(0, 0, M, 0, M, -M), true);
assert_eq!(is_reflex(0, 0, M, 0, 2 * M, 0), false);
}
#[test]
fn edge_squared_length_matches_known_distances() {
assert_eq!(
edge_squared_length(0, 0, 3 * M, 4 * M),
25 * (M as u128) * (M as u128)
);
assert_eq!(edge_squared_length(0, 0, M, 0), (M as u128) * (M as u128));
assert_eq!(
edge_squared_length(-M, -M, M, M),
8 * (M as u128) * (M as u128)
);
}
#[test]
fn edge_squared_length_is_symmetric_and_zero_for_same_point() {
let ab = edge_squared_length(-7, 11, 13, -17);
let ba = edge_squared_length(13, -17, -7, 11);
assert_eq!(ab, ba);
assert_eq!(edge_squared_length(5, 5, 5, 5), 0);
assert_eq!(
edge_squared_length(-M, 0, -M, 3 * M),
9 * (M as u128) * (M as u128)
);
}
#[test]
fn edge_squared_length_handles_max_world() {
let len_sq = edge_squared_length(0, 0, MAX_WORLD, MAX_WORLD);
assert_eq!(len_sq, 2 * (MAX_WORLD as u128) * (MAX_WORLD as u128));
assert!(len_sq > 0);
assert_eq!(
edge_squared_length(-MAX_WORLD, 0, MAX_WORLD, 0),
4 * (MAX_WORLD as u128) * (MAX_WORLD as u128)
);
}
#[test]
fn point_on_segment_handles_midpoint_endpoint_and_outside() {
assert!(point_on_segment(M, 0, 0, 0, 2 * M, 0));
assert!(point_on_segment(0, 0, 0, 0, 2 * M, 0));
assert!(!point_on_segment(3 * M, 0, 0, 0, 2 * M, 0));
}
#[test]
fn point_on_segment_handles_diagonal_and_vertical_segments() {
assert!(point_on_segment(M, M, 0, 0, 2 * M, 2 * M));
assert!(point_on_segment(0, M, 0, 0, 0, 2 * M));
assert!(!point_on_segment(M, M + 1, 0, 0, 2 * M, 2 * M));
}
#[test]
fn point_on_segment_handles_reversed_bounds() {
assert!(point_on_segment(M, 0, 2 * M, 0, 0, 0));
assert!(point_on_segment(0, M, 0, 2 * M, 0, 0));
assert!(!point_on_segment(-1, 0, 2 * M, 0, 0, 0));
}
#[test]
fn segments_properly_intersect_detects_crossings() {
assert!(segments_properly_intersect(
0,
0,
2 * M,
2 * M,
0,
2 * M,
2 * M,
0
));
assert!(segments_properly_intersect(0, 0, 3 * M, 0, M, -M, M, M));
assert!(segments_properly_intersect(-M, -M, M, M, -M, M, M, -M));
}
#[test]
fn segments_properly_intersect_excludes_parallel_touching_and_collinear_cases() {
assert!(!segments_properly_intersect(0, 0, 2 * M, 0, 0, M, 2 * M, M));
assert!(!segments_properly_intersect(0, 0, 2 * M, 0, M, 0, M, 2 * M));
assert!(!segments_properly_intersect(0, 0, 2 * M, 0, M, 0, 3 * M, 0));
}
#[test]
fn segments_properly_intersect_excludes_separated_segments() {
assert!(!segments_properly_intersect(
0,
0,
M,
M,
2 * M,
2 * M,
3 * M,
3 * M
));
assert!(!segments_properly_intersect(0, 0, 0, M, M, 0, M, M));
assert!(!segments_properly_intersect(
-2 * M,
0,
-M,
0,
M,
0,
2 * M,
0
));
}
#[test]
fn segments_intersect_detects_proper_and_endpoint_intersections() {
assert!(segments_intersect(0, 0, 2 * M, 2 * M, 0, 2 * M, 2 * M, 0));
assert!(segments_intersect(0, 0, 2 * M, 0, M, 0, M, 2 * M));
assert!(segments_intersect(0, 0, 2 * M, 0, 2 * M, 0, 3 * M, 0));
}
#[test]
fn segments_intersect_detects_collinear_overlap_and_containment() {
assert!(segments_intersect(0, 0, 4 * M, 0, M, 0, 3 * M, 0));
assert!(segments_intersect(M, 0, 3 * M, 0, 0, 0, 4 * M, 0));
assert!(segments_intersect(0, 0, 0, 4 * M, 0, M, 0, 3 * M));
}
#[test]
fn segments_intersect_excludes_disjoint_segments() {
assert!(!segments_intersect(0, 0, M, 0, 2 * M, 0, 3 * M, 0));
assert!(!segments_intersect(0, 0, 0, M, M, 0, M, M));
assert!(!segments_intersect(
-3 * M,
-3 * M,
-2 * M,
-2 * M,
2 * M,
2 * M,
3 * M,
3 * M
));
}
}