exact-poly 0.3.0

Integer polygon geometry library — exact arithmetic, no float errors
Documentation
use crate::aabb::Aabb;

fn validate_polygon(ring: &[[i64; 2]]) -> bool {
    ring.len() >= 3
}

fn project_onto_axis(ring: &[[i64; 2]], ax: i64, ay: i64) -> (i128, i128) {
    ring.iter()
        .map(|pt| pt[0] as i128 * ax as i128 + pt[1] as i128 * ay as i128)
        .fold((i128::MAX, i128::MIN), |(min, max), v| {
            (min.min(v), max.max(v))
        })
}

fn projections_overlap(min_a: i128, max_a: i128, min_b: i128, max_b: i128) -> bool {
    max_a > min_b && max_b > min_a
}

fn has_separating_axis(edge_ring: &[[i64; 2]], a: &[[i64; 2]], b: &[[i64; 2]]) -> bool {
    let n = edge_ring.len();

    for i in 0..n {
        let next = if i + 1 < n { i + 1 } else { 0 };
        let dx = edge_ring[next][0] - edge_ring[i][0];
        let dy = edge_ring[next][1] - edge_ring[i][1];
        let axis_x = dy;
        let axis_y = -dx;

        if axis_x == 0 && axis_y == 0 {
            continue;
        }

        let (min_a, max_a) = project_onto_axis(a, axis_x, axis_y);
        let (min_b, max_b) = project_onto_axis(b, axis_x, axis_y);

        if !projections_overlap(min_a, max_a, min_b, max_b) {
            return true;
        }
    }

    false
}

pub fn sat_overlaps(a: &[[i64; 2]], b: &[[i64; 2]]) -> bool {
    if !validate_polygon(a) || !validate_polygon(b) {
        return false;
    }

    if has_separating_axis(a, a, b) {
        return false;
    }

    if has_separating_axis(b, a, b) {
        return false;
    }

    true
}

pub fn sat_overlaps_with_aabb(a: &[[i64; 2]], b: &[[i64; 2]]) -> bool {
    if !validate_polygon(a) || !validate_polygon(b) {
        return false;
    }

    let aabb_a = Aabb::from_ring(a);
    let aabb_b = Aabb::from_ring(b);

    if !aabb_a.intersects(&aabb_b) {
        return false;
    }

    sat_overlaps(a, b)
}

#[cfg(test)]
mod tests {
    use super::*;

    const M: i64 = 1_000_000;

    fn square(ox: i64, oy: i64, size: i64) -> Vec<[i64; 2]> {
        vec![
            [ox, oy],
            [ox + size, oy],
            [ox + size, oy + size],
            [ox, oy + size],
        ]
    }

    #[test]
    fn sat_gap_detected() {
        let a = square(0, 0, M);
        let b = square(2 * M, 0, M);

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_touching_edges_do_not_overlap() {
        let a = square(0, 0, M);
        let b = square(M, 0, M);

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_overlap_detected() {
        let a = square(0, 0, 2 * M);
        let b = square(M, M, 2 * M);

        assert!(sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_micro_overlap_detected() {
        let a = square(0, 0, M);
        let b = vec![[M - 1, 0], [2 * M - 1, 0], [2 * M - 1, M], [M - 1, M]];

        assert!(sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_corner_touching_does_not_overlap() {
        let a = square(0, 0, M);
        let b = square(M, M, M);

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_triangle_touching_does_not_overlap() {
        let a = vec![[0, 0], [2 * M, 0], [M, 2 * M]];
        let b = vec![[2 * M, 0], [3 * M, 0], [2 * M + M / 2, M]];

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn contained_square_overlaps() {
        let outer = square(0, 0, 10 * M);
        let inner = square(2 * M, 2 * M, 3 * M);

        assert!(sat_overlaps(&outer, &inner));
    }

    #[test]
    fn identical_squares_overlap() {
        let a = square(0, 0, M);
        let b = square(0, 0, M);

        assert!(sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_is_symmetric() {
        let a = square(0, 0, 3 * M);
        let b = square(2 * M, 2 * M, 3 * M);

        assert_eq!(sat_overlaps(&a, &b), sat_overlaps(&b, &a),);
    }

    #[test]
    fn aabb_pipeline_rejects_far_polygons() {
        let a = square(0, 0, M);
        let b = square(3 * M, 0, M);

        assert!(!sat_overlaps_with_aabb(&a, &b));
    }

    #[test]
    fn aabb_prefilter_allows_sat_for_overlapping() {
        let a = square(0, 0, 3 * M);
        let b = square(2 * M, 2 * M, 3 * M);

        assert!(sat_overlaps_with_aabb(&a, &b));
    }

    #[test]
    fn triangles_overlap_correctly() {
        let a = vec![[0, 0], [4 * M, 0], [2 * M, 4 * M]];
        let b = vec![[M, M], [3 * M, M], [2 * M, 3 * M]];

        assert!(sat_overlaps(&a, &b));
    }

    #[test]
    fn right_triangles_sharing_hypotenuse_do_not_overlap() {
        let a = vec![[0, 0], [2 * M, 0], [2 * M, 2 * M]];
        let b = vec![[0, 0], [0, 2 * M], [2 * M, 2 * M]];

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn triangle_fully_inside_another_overlaps() {
        let outer = vec![[0, 0], [4 * M, 0], [0, 4 * M]];
        let inner = vec![[M, M], [2 * M, M], [M, 2 * M]];

        assert!(sat_overlaps(&outer, &inner));
    }

    #[test]
    fn sat_handles_world_extreme_coordinates() {
        const MAX_WORLD: i64 = 40_075_017_000_000;

        let a = vec![
            [-MAX_WORLD, -MAX_WORLD],
            [-MAX_WORLD + M, -MAX_WORLD],
            [-MAX_WORLD + M, -MAX_WORLD + M],
            [-MAX_WORLD, -MAX_WORLD + M],
        ];
        let b = vec![
            [MAX_WORLD, MAX_WORLD],
            [MAX_WORLD + M, MAX_WORLD],
            [MAX_WORLD + M, MAX_WORLD + M],
            [MAX_WORLD, MAX_WORLD + M],
        ];

        assert!(!sat_overlaps(&a, &b));
    }

    #[test]
    fn sat_with_aabb_rejects_far_apart_polygons() {
        let a = square(0, 0, M);
        let b = square(100 * M, 100 * M, M);

        assert!(!sat_overlaps_with_aabb(&a, &b));
    }
}