use crate::Quad;
pub(crate) fn aabb_of(points: &[[f32; 2]]) -> [f32; 4] {
let mut out = [
f32::INFINITY,
f32::INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
];
for point in points {
out[0] = out[0].min(point[0]);
out[1] = out[1].min(point[1]);
out[2] = out[2].max(point[0]);
out[3] = out[3].max(point[1]);
}
out
}
pub(crate) fn aabb_from_quad(quad: &Quad) -> [f32; 4] {
aabb_of(quad.as_slice())
}
pub(crate) fn aabb_from_flat8(polygon: &[f32; 8]) -> [f32; 4] {
let mut x_min = polygon[0];
let mut x_max = polygon[0];
let mut y_min = polygon[1];
let mut y_max = polygon[1];
for pair in polygon.as_chunks::<2>().0 {
let (x, y) = (pair[0], pair[1]);
if x < x_min {
x_min = x;
}
if x > x_max {
x_max = x;
}
if y < y_min {
y_min = y;
}
if y > y_max {
y_max = y;
}
}
[x_min, y_min, x_max, y_max]
}
pub(crate) fn aabb_iou(a: [f32; 4], b: [f32; 4]) -> f64 {
let (ax0, ay0, ax1, ay1) = (a[0] as f64, a[1] as f64, a[2] as f64, a[3] as f64);
let (bx0, by0, bx1, by1) = (b[0] as f64, b[1] as f64, b[2] as f64, b[3] as f64);
let area_a = (ax1 - ax0) * (ay1 - ay0);
let area_b = (bx1 - bx0) * (by1 - by0);
let x_overlap = ax1.min(bx1) - ax0.max(bx0);
let y_overlap = ay1.min(by1) - ay0.max(by0);
if x_overlap <= 0.0 || y_overlap <= 0.0 {
return 0.0;
}
let intersect = x_overlap * y_overlap;
let denominator = area_a + area_b - intersect;
if denominator <= 0.0 {
return 0.0;
}
intersect / denominator
}
pub(crate) fn polygon_area(points: &[[f32; 2]]) -> f64 {
if points.len() < 3 {
return 0.0;
}
let mut sum = 0.0_f64;
for i in 0..points.len() {
let p0 = points[i];
let p1 = points[(i + 1) % points.len()];
sum += f64::from(p0[0]) * f64::from(p1[1]) - f64::from(p1[0]) * f64::from(p0[1]);
}
0.5 * sum
}
#[cfg(test)]
mod tests {
use super::*;
fn ref_aabb(points: &[[f32; 2]]) -> [f32; 4] {
let xs: Vec<f32> = points.iter().map(|p| p[0]).collect();
let ys: Vec<f32> = points.iter().map(|p| p[1]).collect();
[
xs.iter().copied().fold(f32::INFINITY, f32::min),
ys.iter().copied().fold(f32::INFINITY, f32::min),
xs.iter().copied().fold(f32::NEG_INFINITY, f32::max),
ys.iter().copied().fold(f32::NEG_INFINITY, f32::max),
]
}
fn ref_iou(a: [f32; 4], b: [f32; 4]) -> f64 {
let ix = (a[2].min(b[2]) - a[0].max(b[0])).max(0.0) as f64;
let iy = (a[3].min(b[3]) - a[1].max(b[1])).max(0.0) as f64;
let inter = ix * iy;
let area_a = (a[2] - a[0]).max(0.0) as f64 * (a[3] - a[1]).max(0.0) as f64;
let area_b = (b[2] - b[0]).max(0.0) as f64 * (b[3] - b[1]).max(0.0) as f64;
let union = area_a + area_b - inter;
if union <= 0.0 { 0.0 } else { inter / union }
}
#[test]
fn aabb_handles_degenerate_negative_and_single_point() {
assert_eq!(aabb_of(&[[3.0, -2.0]]), [3.0, -2.0, 3.0, -2.0]);
let pts = [[-1.0, 5.0], [4.0, -3.0], [0.0, 0.0], [2.0, 9.0]];
assert_eq!(aabb_of(&pts), [-1.0, -3.0, 4.0, 9.0]);
assert_eq!(aabb_of(&pts), ref_aabb(&pts));
}
#[test]
fn aabb_from_quad_matches_aabb_of() {
let quad: Quad = [[10.0, 0.0], [20.0, 0.0], [20.0, 30.0], [10.0, 30.0]];
assert_eq!(aabb_from_quad(&quad), [10.0, 0.0, 20.0, 30.0]);
assert_eq!(aabb_from_quad(&quad), aabb_of(quad.as_slice()));
}
#[test]
fn aabb_from_flat8_agrees_with_aabb_of_on_the_same_points() {
let flat = [5.0_f32, 5.0, 50.0, 5.0, 50.0, 40.0, 5.0, 40.0];
let points: Vec<[f32; 2]> = flat
.as_chunks::<2>()
.0
.iter()
.map(|p| [p[0], p[1]])
.collect();
assert_eq!(aabb_from_flat8(&flat), aabb_of(&points));
assert_eq!(aabb_from_flat8(&flat), [5.0, 5.0, 50.0, 40.0]);
}
#[test]
fn aabb_iou_boundaries_and_reference() {
assert!((aabb_iou([0.0, 0.0, 10.0, 10.0], [0.0, 0.0, 10.0, 10.0]) - 1.0).abs() < 1e-12);
let contained = aabb_iou([0.0, 0.0, 10.0, 10.0], [0.0, 0.0, 5.0, 5.0]);
assert!((contained - 0.25).abs() < 1e-12);
assert!((contained - ref_iou([0.0, 0.0, 10.0, 10.0], [0.0, 0.0, 5.0, 5.0])).abs() < 1e-12);
assert_eq!(
aabb_iou([0.0, 0.0, 10.0, 10.0], [10.0, 0.0, 20.0, 10.0]),
0.0
);
assert_eq!(
aabb_iou([0.0, 0.0, 10.0, 10.0], [20.0, 20.0, 30.0, 30.0]),
0.0
);
let nan = aabb_iou([3.0, 3.0, 3.0, 3.0], [3.0, 3.0, 3.0, 3.0]);
assert!(!nan.is_nan());
assert_eq!(nan, 0.0);
}
#[test]
fn polygon_area_is_signed_shoelace() {
let ccw = [[0.0_f32, 0.0], [3.0, 0.0], [3.0, 3.0], [0.0, 3.0]];
assert!((polygon_area(&ccw) - 9.0).abs() < 1e-9);
let cw: Vec<[f32; 2]> = ccw.iter().rev().copied().collect();
assert!((polygon_area(&cw) + 9.0).abs() < 1e-9);
assert_eq!(polygon_area(&[[0.0, 0.0], [1.0, 1.0]]), 0.0);
}
}