#[derive(Debug, Clone, Copy)]
pub(crate) struct Rect {
pub left: f32,
pub right: f32,
pub y_min: f32,
pub y_max: f32,
}
impl Rect {
pub fn new(left: f32, right: f32, y_min: f32, y_max: f32) -> Self {
Self {
left: left.min(right),
right: left.max(right),
y_min: y_min.min(y_max),
y_max: y_min.max(y_max),
}
}
pub fn from_lbrt(left: f32, bottom: f32, right: f32, top: f32) -> Self {
Self::new(left, right, bottom, top)
}
pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Self {
Self::new(left, right, top, bottom)
}
pub fn from_xywh(x: f32, y: f32, width: f32, height: f32) -> Self {
Self::new(x, x + width, y, y + height)
}
pub fn width(&self) -> f32 {
self.right - self.left
}
pub fn height(&self) -> f32 {
self.y_max - self.y_min
}
pub fn area(&self) -> f32 {
self.width() * self.height()
}
pub fn center_x(&self) -> f32 {
(self.left + self.right) * 0.5
}
pub fn center_y(&self) -> f32 {
(self.y_min + self.y_max) * 0.5
}
pub fn intersection_area(&self, other: &Rect) -> f32 {
let inter_left = self.left.max(other.left);
let inter_right = self.right.min(other.right);
let inter_y_min = self.y_min.max(other.y_min);
let inter_y_max = self.y_max.min(other.y_max);
if inter_left >= inter_right || inter_y_min >= inter_y_max {
return 0.0;
}
(inter_right - inter_left) * (inter_y_max - inter_y_min)
}
pub fn intersection_over_self(&self, container: &Rect) -> f32 {
let self_area = self.area();
if self_area <= 0.0 {
return 0.0;
}
self.intersection_area(container) / self_area
}
pub fn contains_point(&self, x: f32, y: f32) -> bool {
x >= self.left && x <= self.right && y >= self.y_min && y <= self.y_max
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_area() {
let r = Rect::new(0.0, 10.0, 0.0, 5.0);
assert!((r.area() - 50.0).abs() < f32::EPSILON);
}
#[test]
fn test_zero_area() {
let r = Rect::new(5.0, 5.0, 0.0, 10.0);
assert!((r.area() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_area_overlap() {
let a = Rect::new(0.0, 10.0, 0.0, 10.0);
let b = Rect::new(5.0, 15.0, 5.0, 15.0);
assert!((a.intersection_area(&b) - 25.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_area_no_overlap() {
let a = Rect::new(0.0, 5.0, 0.0, 5.0);
let b = Rect::new(10.0, 15.0, 10.0, 15.0);
assert!((a.intersection_area(&b) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_area_contained() {
let outer = Rect::new(0.0, 20.0, 0.0, 20.0);
let inner = Rect::new(5.0, 10.0, 5.0, 10.0);
assert!((inner.intersection_area(&outer) - 25.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_over_self_full() {
let inner = Rect::new(5.0, 10.0, 5.0, 10.0);
let outer = Rect::new(0.0, 20.0, 0.0, 20.0);
assert!((inner.intersection_over_self(&outer) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_over_self_half() {
let a = Rect::new(0.0, 10.0, 0.0, 10.0);
let b = Rect::new(5.0, 15.0, 0.0, 10.0);
assert!((a.intersection_over_self(&b) - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_over_self_zero_area() {
let zero = Rect::new(5.0, 5.0, 0.0, 10.0);
let other = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!((zero.intersection_over_self(&other) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_contains_point_inside() {
let r = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!(r.contains_point(5.0, 5.0));
}
#[test]
fn test_contains_point_boundary() {
let r = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!(r.contains_point(0.0, 0.0));
assert!(r.contains_point(10.0, 10.0));
}
#[test]
fn test_contains_point_outside() {
let r = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!(!r.contains_point(11.0, 5.0));
assert!(!r.contains_point(5.0, -1.0));
}
#[test]
fn test_from_lbrt() {
let r = Rect::from_lbrt(10.0, 20.0, 50.0, 80.0);
assert!((r.left - 10.0).abs() < f32::EPSILON);
assert!((r.y_min - 20.0).abs() < f32::EPSILON);
assert!((r.right - 50.0).abs() < f32::EPSILON);
assert!((r.y_max - 80.0).abs() < f32::EPSILON);
}
#[test]
fn test_from_xywh() {
let r = Rect::from_xywh(10.0, 20.0, 30.0, 40.0);
assert!((r.left - 10.0).abs() < f32::EPSILON);
assert!((r.y_min - 20.0).abs() < f32::EPSILON);
assert!((r.right - 40.0).abs() < f32::EPSILON);
assert!((r.y_max - 60.0).abs() < f32::EPSILON);
}
#[test]
fn test_normalizes_inverted() {
let r = Rect::new(10.0, 0.0, 10.0, 0.0);
assert!((r.left - 0.0).abs() < f32::EPSILON);
assert!((r.right - 10.0).abs() < f32::EPSILON);
assert!((r.y_min - 0.0).abs() < f32::EPSILON);
assert!((r.y_max - 10.0).abs() < f32::EPSILON);
}
#[test]
fn test_width_and_height() {
let r = Rect::new(10.0, 30.0, 5.0, 25.0);
assert!((r.width() - 20.0).abs() < f32::EPSILON);
assert!((r.height() - 20.0).abs() < f32::EPSILON);
}
#[test]
fn test_center_x_and_y() {
let r = Rect::new(0.0, 10.0, 0.0, 20.0);
assert!((r.center_x() - 5.0).abs() < f32::EPSILON);
assert!((r.center_y() - 10.0).abs() < f32::EPSILON);
}
#[test]
fn test_from_ltrb() {
let r = Rect::from_ltrb(10.0, 80.0, 50.0, 20.0);
assert!((r.left - 10.0).abs() < f32::EPSILON);
assert!((r.right - 50.0).abs() < f32::EPSILON);
assert!((r.y_min - 20.0).abs() < f32::EPSILON);
assert!((r.y_max - 80.0).abs() < f32::EPSILON);
}
#[test]
fn test_zero_width_rect() {
let r = Rect::new(5.0, 5.0, 0.0, 10.0);
assert!((r.width() - 0.0).abs() < f32::EPSILON);
assert!((r.area() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_zero_height_rect() {
let r = Rect::new(0.0, 10.0, 5.0, 5.0);
assert!((r.height() - 0.0).abs() < f32::EPSILON);
assert!((r.area() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_identical_rects_intersection() {
let a = Rect::new(0.0, 10.0, 0.0, 10.0);
let b = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!((a.intersection_area(&b) - 100.0).abs() < f32::EPSILON);
assert!((a.intersection_over_self(&b) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_adjacent_rects_no_intersection() {
let a = Rect::new(0.0, 10.0, 0.0, 10.0);
let b = Rect::new(10.0, 20.0, 0.0, 10.0);
assert!((a.intersection_area(&b) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_contains_point_just_outside() {
let r = Rect::new(0.0, 10.0, 0.0, 10.0);
assert!(!r.contains_point(-0.001, 5.0));
assert!(!r.contains_point(10.001, 5.0));
assert!(!r.contains_point(5.0, -0.001));
assert!(!r.contains_point(5.0, 10.001));
}
#[test]
fn test_intersection_over_self_no_overlap() {
let a = Rect::new(0.0, 5.0, 0.0, 5.0);
let b = Rect::new(100.0, 200.0, 100.0, 200.0);
assert!((a.intersection_over_self(&b) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_intersection_area_symmetric() {
let a = Rect::new(0.0, 10.0, 0.0, 10.0);
let b = Rect::new(5.0, 15.0, 5.0, 15.0);
assert!((a.intersection_area(&b) - b.intersection_area(&a)).abs() < f32::EPSILON);
}
}