use crate::primitives::{cross2d, point_on_segment};
pub fn point_strictly_inside_convex(px: i64, py: i64, ring: &[[i64; 2]]) -> bool {
let n = ring.len();
if n < 3 {
return false;
}
let mut all_pos = true;
let mut all_neg = true;
for i in 0..n {
let j = (i + 1) % n;
let cp = cross2d(ring[i][0], ring[i][1], ring[j][0], ring[j][1], px, py);
if cp <= 0 {
all_pos = false;
}
if cp >= 0 {
all_neg = false;
}
}
all_pos || all_neg
}
pub fn point_on_polygon_boundary(px: i64, py: i64, ring: &[[i64; 2]]) -> bool {
let n = ring.len();
for i in 0..n {
let j = (i + 1) % n;
if point_on_segment(px, py, ring[i][0], ring[i][1], ring[j][0], ring[j][1]) {
return true;
}
}
false
}
fn point_inside_polygon_ray_cast(px: i64, py: i64, ring: &[[i64; 2]]) -> bool {
let n = ring.len();
if n < 3 {
return false;
}
let mut crossings = 0i32;
for i in 0..n {
let j = (i + 1) % n;
let ax = ring[i][0];
let ay = ring[i][1];
let bx = ring[j][0];
let by = ring[j][1];
if (ay > py) != (by > py) {
let lhs = (py as i128 - ay as i128) * (bx as i128 - ax as i128);
let rhs = (px as i128 - ax as i128) * (by as i128 - ay as i128);
let to_right = if by > ay { lhs > rhs } else { lhs < rhs };
if to_right {
crossings += 1;
}
}
}
crossings % 2 == 1
}
pub fn point_inside_or_on_boundary(px: i64, py: i64, ring: &[[i64; 2]]) -> bool {
point_on_polygon_boundary(px, py, ring) || point_inside_polygon_ray_cast(px, py, ring)
}
pub fn collinear_segments_overlap_area(
a1x: i64,
a1y: i64,
a2x: i64,
a2y: i64,
b1x: i64,
b1y: i64,
b2x: i64,
b2y: i64,
a_ring: &[[i64; 2]], b_ring: &[[i64; 2]], ) -> bool {
let dax = (a2x as i128) - (a1x as i128);
let day = (a2y as i128) - (a1y as i128);
let dbx = (b2x as i128) - (b1x as i128);
let dby = (b2y as i128) - (b1y as i128);
if dax * dby != day * dbx {
return false;
}
let collinear_check = cross2d(a1x, a1y, a2x, a2y, b1x, b1y);
if collinear_check != 0 {
return false;
}
let has_overlap = if dax != 0 || dbx != 0 {
let (a_lo, a_hi) = (a1x.min(a2x), a1x.max(a2x));
let (b_lo, b_hi) = (b1x.min(b2x), b1x.max(b2x));
a_lo.max(b_lo) < a_hi.min(b_hi)
} else {
let (a_lo, a_hi) = (a1y.min(a2y), a1y.max(a2y));
let (b_lo, b_hi) = (b1y.min(b2y), b1y.max(b2y));
a_lo.max(b_lo) < a_hi.min(b_hi)
};
if !has_overlap {
return false;
}
let mut side_a: i128 = 0;
for point in a_ring {
let cp = cross2d(a1x, a1y, a2x, a2y, point[0], point[1]);
if cp != 0 {
side_a = cp;
break;
}
}
let mut side_b: i128 = 0;
for point in b_ring {
let cp = cross2d(a1x, a1y, a2x, a2y, point[0], point[1]);
if cp != 0 {
side_b = cp;
break;
}
}
if side_a == 0 || side_b == 0 {
return false;
}
(side_a > 0) == (side_b > 0)
}
#[cfg(test)]
mod tests {
use super::*;
const M: i64 = 1_000_000;
fn square() -> Vec<[i64; 2]> {
vec![[0, 0], [M, 0], [M, M], [0, M]]
}
fn rhombus() -> Vec<[i64; 2]> {
vec![[0, 4 * M], [2 * M, 0], [0, -4 * M], [-2 * M, 0]]
}
#[test]
fn point_strictly_inside() {
assert!(point_strictly_inside_convex(M / 2, M / 2, &square()));
}
#[test]
fn point_strictly_inside_convex_rhombus_centroid_and_edge_neighbors() {
let ring = rhombus();
assert!(point_strictly_inside_convex(0, 0, &ring));
let edge_mid_x = M;
let edge_mid_y = 2 * M;
assert!(point_strictly_inside_convex(
edge_mid_x - 2,
edge_mid_y - 1,
&ring
));
assert!(!point_strictly_inside_convex(
edge_mid_x + 2,
edge_mid_y + 1,
&ring
));
}
#[test]
fn point_on_boundary_not_strictly_inside() {
assert!(!point_strictly_inside_convex(M / 2, 0, &square()));
}
#[test]
fn point_at_vertex_not_strictly_inside() {
assert!(!point_strictly_inside_convex(0, 0, &square()));
}
#[test]
fn point_outside() {
assert!(!point_strictly_inside_convex(2 * M, 2 * M, &square()));
}
#[test]
fn point_inside_or_on_boundary_interior() {
assert!(point_inside_or_on_boundary(M / 2, M / 2, &square()));
}
#[test]
fn point_inside_or_on_boundary_edge() {
assert!(point_inside_or_on_boundary(M / 2, 0, &square()));
}
#[test]
fn point_inside_or_on_boundary_vertex() {
assert!(point_inside_or_on_boundary(0, 0, &square()));
}
#[test]
fn point_inside_or_on_boundary_outside() {
assert!(!point_inside_or_on_boundary(2 * M, 0, &square()));
}
#[test]
fn point_on_polygon_boundary_edge_midpoint_and_off_edge() {
let ring = rhombus();
assert!(point_on_polygon_boundary(M, 2 * M, &ring));
assert!(!point_on_polygon_boundary(M + 2, 2 * M + 1, &ring));
}
#[test]
fn point_inside_or_on_boundary_inclusive_cases() {
let ring = rhombus();
assert!(point_inside_or_on_boundary(0, 4 * M, &ring));
assert!(point_inside_or_on_boundary(M, 2 * M, &ring));
assert!(point_inside_or_on_boundary(0, 0, &ring));
assert!(!point_inside_or_on_boundary(3 * M, 3 * M, &ring));
}
fn l_shape() -> Vec<[i64; 2]> {
vec![
[0, 0], [60 * M, 0], [60 * M, 40 * M], [30 * M, 40 * M],
[30 * M, 80 * M], [0, 80 * M],
]
}
#[test]
fn point_inside_or_on_boundary_non_convex_l_shape_interior() {
let ring = l_shape();
assert!(point_inside_or_on_boundary(15 * M, 20 * M, &ring));
assert!(point_inside_or_on_boundary(15 * M, 60 * M, &ring));
assert!(!point_inside_or_on_boundary(45 * M, 60 * M, &ring));
}
#[test]
fn ray_cast_non_convex_star_interior() {
let arrow: Vec<[i64; 2]> = vec![
[0, 30 * M], [60 * M, 30 * M], [60 * M, 0],
[100 * M, 50 * M], [60 * M, 100 * M], [60 * M, 70 * M], [0, 70 * M],
];
assert!(point_inside_or_on_boundary(30 * M, 50 * M, &arrow));
assert!(!point_inside_or_on_boundary(30 * M, 90 * M, &arrow));
}
#[test]
fn collinear_overlap_same_side_returns_true() {
let a_ring = vec![[0, 0], [2 * M, 0], [2 * M, M], [0, M]];
let b_ring = vec![[M, 0], [3 * M, 0], [3 * M, M], [M, M]];
let result = collinear_segments_overlap_area(
0,
0,
2 * M,
0, M,
0,
3 * M,
0, &a_ring,
&b_ring,
);
assert!(result);
}
#[test]
fn adjacent_polygons_opposite_sides_no_overlap() {
let a_ring = vec![[0, 0], [2 * M, 0], [2 * M, M], [0, M]]; let b_ring = vec![[0, 0], [2 * M, 0], [2 * M, -M], [0, -M]];
let result =
collinear_segments_overlap_area(0, 0, 2 * M, 0, 0, 0, 2 * M, 0, &a_ring, &b_ring);
assert!(!result);
}
#[test]
fn non_collinear_segments_return_false() {
let a_ring = vec![[0, 0], [M, 0], [M, M], [0, M]]; let b_ring = vec![[2 * M, 0], [3 * M, 0], [3 * M, M], [2 * M, M]];
let result = collinear_segments_overlap_area(
0,
0,
M,
0, 2 * M,
0,
2 * M,
M, &a_ring,
&b_ring,
);
assert!(!result);
}
}