use crate::geometry::primitives::Point;
use crate::geometry::traits::{Area, BoundingBox, Centroid, Closed, Distance, Perimeter};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rectangle {
center: Point,
width: f64,
height: f64,
}
#[allow(dead_code)]
impl Rectangle {
pub fn new(center: Point, width: f64, height: f64) -> Self {
Rectangle {
center,
width,
height,
}
}
pub fn from_corners(bottom_left: Point, top_right: Point) -> Self {
let center_x = (bottom_left.x() + top_right.x()) / 2.0;
let center_y = (bottom_left.y() + top_right.y()) / 2.0;
let width = top_right.x() - bottom_left.x();
let height = top_right.y() - bottom_left.y();
Rectangle::new(Point::new(center_x, center_y), width, height)
}
pub fn center(&self) -> &Point {
&self.center
}
pub fn width(&self) -> f64 {
self.width
}
pub fn height(&self) -> f64 {
self.height
}
pub fn set_center(&mut self, center: Point) {
self.center = center;
}
pub fn bounds(&self) -> (f64, f64, f64, f64) {
let half_width = self.width / 2.0;
let half_height = self.height / 2.0;
(
self.center.x() - half_width,
self.center.x() + half_width,
self.center.y() - half_height,
self.center.y() + half_height,
)
}
pub fn to_points(self) -> (Point, Point) {
let (x_min, x_max, y_min, y_max) = self.bounds();
(Point::new(x_min, y_min), Point::new(x_max, y_max))
}
pub fn corners(&self) -> [Point; 4] {
let (x_min, x_max, y_min, y_max) = self.bounds();
[
Point::new(x_min, y_min),
Point::new(x_max, y_min),
Point::new(x_max, y_max),
Point::new(x_min, y_max),
]
}
}
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
impl Perimeter for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
impl BoundingBox for Rectangle {
fn bounding_box(&self) -> Rectangle {
*self
}
}
impl Centroid for Rectangle {
fn centroid(&self) -> Point {
self.center
}
}
impl Distance for Rectangle {
fn distance(&self, other: &Self) -> f64 {
let (x1_min, x1_max, y1_min, y1_max) = self.bounds();
let (x2_min, x2_max, y2_min, y2_max) = other.bounds();
let dx = if x1_max < x2_min {
x2_min - x1_max
} else if x2_max < x1_min {
x1_min - x2_max
} else {
0.0
};
let dy = if y1_max < y2_min {
y2_min - y1_max
} else if y2_max < y1_min {
y1_min - y2_max
} else {
0.0
};
(dx * dx + dy * dy).sqrt()
}
}
#[allow(dead_code)]
impl Closed for Rectangle {
fn contains(&self, other: &Self) -> bool {
let (x1_min, x1_max, y1_min, y1_max) = self.bounds();
let (x2_min, x2_max, y2_min, y2_max) = other.bounds();
x2_min >= x1_min && x2_max <= x1_max && y2_min >= y1_min && y2_max <= y1_max
}
fn contains_point(&self, point: &Point) -> bool {
let (x_min, x_max, y_min, y_max) = self.bounds();
point.x() >= x_min && point.x() <= x_max && point.y() >= y_min && point.y() <= y_max
}
fn intersects(&self, other: &Self) -> bool {
let (x1_min, x1_max, y1_min, y1_max) = self.bounds();
let (x2_min, x2_max, y2_min, y2_max) = other.bounds();
!(x1_max < x2_min || x2_max < x1_min || y1_max < y2_min || y2_max < y1_min)
}
fn intersection_area(&self, other: &Self) -> f64 {
let (x1_min, x1_max, y1_min, y1_max) = self.bounds();
let (x2_min, x2_max, y2_min, y2_max) = other.bounds();
let x_overlap = (x1_max.min(x2_max) - x1_min.max(x2_min)).max(0.0);
let y_overlap = (y1_max.min(y2_max) - y1_min.max(y2_min)).max(0.0);
x_overlap * y_overlap
}
fn intersection_points(&self, other: &Self) -> Vec<Point> {
if !self.intersects(other) {
return vec![];
}
let (x1_min, x1_max, y1_min, y1_max) = self.bounds();
let (x2_min, x2_max, y2_min, y2_max) = other.bounds();
let x_min = x1_min.max(x2_min);
let x_max = x1_max.min(x2_max);
let y_min = y1_min.max(y2_min);
let y_max = y1_max.min(y2_max);
if x_min >= x_max || y_min >= y_max {
return vec![];
}
vec![
Point::new(x_min, y_min),
Point::new(x_max, y_min),
Point::new(x_max, y_max),
Point::new(x_min, y_max),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn test_rectangle_new() {
let center = Point::new(1.0, 2.0);
let rect = Rectangle::new(center, 4.0, 3.0);
assert_eq!(rect.width(), 4.0);
assert_eq!(rect.height(), 3.0);
assert_eq!(rect.center().x(), 1.0);
assert_eq!(rect.center().y(), 2.0);
}
#[test]
fn test_rectangle_area() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 4.0, 3.0);
assert!(approx_eq(rect.area(), 12.0));
let rect2 = Rectangle::new(Point::new(5.0, 5.0), 2.0, 5.0);
assert!(approx_eq(rect2.area(), 10.0));
}
#[test]
fn test_rectangle_perimeter() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 4.0, 3.0);
assert!(approx_eq(rect.perimeter(), 14.0));
let rect2 = Rectangle::new(Point::new(1.0, 1.0), 2.0, 2.0);
assert!(approx_eq(rect2.perimeter(), 8.0));
}
#[test]
fn test_rectangle_bounds() {
let rect = Rectangle::new(Point::new(2.0, 3.0), 4.0, 6.0);
let (x_min, x_max, y_min, y_max) = rect.bounds();
assert!(approx_eq(x_min, 0.0));
assert!(approx_eq(x_max, 4.0));
assert!(approx_eq(y_min, 0.0));
assert!(approx_eq(y_max, 6.0));
}
#[test]
fn test_rectangle_corners() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let corners = rect.corners();
assert_eq!(corners.len(), 4);
let expected = [
Point::new(-1.0, -1.0),
Point::new(1.0, -1.0),
Point::new(1.0, 1.0),
Point::new(-1.0, 1.0),
];
for (corner, &expected_corner) in corners.iter().zip(expected.iter()) {
assert!(approx_eq(corner.x(), expected_corner.x()));
assert!(approx_eq(corner.y(), expected_corner.y()));
}
}
#[test]
fn test_rectangle_centroid() {
let rect = Rectangle::new(Point::new(3.0, 4.0), 2.0, 2.0);
let centroid = rect.centroid();
assert!(approx_eq(centroid.x(), 3.0));
assert!(approx_eq(centroid.y(), 4.0));
}
#[test]
fn test_rectangle_contains_point() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 4.0, 2.0);
assert!(rect.contains_point(&Point::new(0.0, 0.0)));
assert!(rect.contains_point(&Point::new(1.0, 0.5)));
assert!(rect.contains_point(&Point::new(-1.0, -0.5)));
assert!(rect.contains_point(&Point::new(2.0, 1.0)));
assert!(!rect.contains_point(&Point::new(3.0, 0.0)));
assert!(!rect.contains_point(&Point::new(0.0, 2.0)));
}
#[test]
fn test_rectangle_distance_no_overlap() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(5.0, 0.0), 2.0, 2.0);
assert!(approx_eq(rect1.distance(&rect2), 3.0));
}
#[test]
fn test_rectangle_distance_touching() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(3.0, 0.0), 2.0, 2.0);
assert!(approx_eq(rect1.distance(&rect2), 1.0));
}
#[test]
fn test_rectangle_distance_overlapping() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(1.0, 0.0), 2.0, 2.0);
assert!(approx_eq(rect1.distance(&rect2), 0.0));
}
#[test]
fn test_rectangle_distance_diagonal() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(5.0, 5.0), 2.0, 2.0);
let expected = ((3.0_f64).powi(2) + (3.0_f64).powi(2)).sqrt();
assert!(approx_eq(rect1.distance(&rect2), expected));
}
#[test]
fn test_rectangle_contains_smaller() {
let large = Rectangle::new(Point::new(0.0, 0.0), 10.0, 10.0);
let small = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
assert!(large.contains(&small));
}
#[test]
fn test_rectangle_contains_self() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 3.0, 3.0);
assert!(rect.contains(&rect));
}
#[test]
fn test_rectangle_not_contains() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(5.0, 0.0), 2.0, 2.0);
assert!(!rect1.contains(&rect2));
}
#[test]
fn test_rectangle_not_contains_partial_overlap() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(2.0, 0.0), 3.0, 3.0);
assert!(!rect1.contains(&rect2));
}
#[test]
fn test_rectangle_intersects_separate() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(5.0, 0.0), 2.0, 2.0);
assert!(!rect1.intersects(&rect2));
}
#[test]
fn test_rectangle_intersects_touching() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(2.0, 0.0), 2.0, 2.0);
assert!(rect1.intersects(&rect2));
}
#[test]
fn test_rectangle_intersects_overlapping() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(1.0, 0.0), 2.0, 2.0);
assert!(rect1.intersects(&rect2));
}
#[test]
fn test_intersection_area_no_overlap() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(10.0, 0.0), 2.0, 2.0);
assert!(approx_eq(rect1.intersection_area(&rect2), 0.0));
}
#[test]
fn test_intersection_area_touching() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(3.0, 0.0), 2.0, 2.0);
assert!(approx_eq(rect1.intersection_area(&rect2), 0.0));
}
#[test]
fn test_intersection_area_complete_overlap_same_size() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 3.0);
let rect2 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 3.0);
assert!(approx_eq(rect1.intersection_area(&rect2), 12.0));
}
#[test]
fn test_intersection_area_one_inside_other() {
let large = Rectangle::new(Point::new(0.0, 0.0), 10.0, 10.0);
let small = Rectangle::new(Point::new(1.0, 0.0), 4.0, 4.0);
let expected = 16.0;
assert!(approx_eq(large.intersection_area(&small), expected));
assert!(approx_eq(small.intersection_area(&large), expected));
}
#[test]
fn test_intersection_area_partial_overlap() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(2.0, 0.0), 4.0, 4.0);
let x_overlap = 2.0;
let y_overlap = 4.0;
let expected = x_overlap * y_overlap;
assert!(approx_eq(rect1.intersection_area(&rect2), expected));
}
#[test]
fn test_intersection_area_symmetric() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 3.0);
let rect2 = Rectangle::new(Point::new(1.5, 0.0), 3.0, 2.0);
let area1 = rect1.intersection_area(&rect2);
let area2 = rect2.intersection_area(&rect1);
assert!(approx_eq(area1, area2));
}
#[test]
fn test_intersection_area_different_sizes() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 6.0, 4.0);
let rect2 = Rectangle::new(Point::new(2.0, 0.0), 2.0, 2.0);
let area = rect1.intersection_area(&rect2);
assert!(area > 0.0);
assert!(area <= 4.0);
}
#[test]
fn test_intersection_points_no_intersection() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
let rect2 = Rectangle::new(Point::new(5.0, 0.0), 2.0, 2.0);
let points = rect1.intersection_points(&rect2);
assert_eq!(points.len(), 0);
}
#[test]
fn test_intersection_points_overlapping() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(1.0, 1.0), 2.0, 2.0);
let points = rect1.intersection_points(&rect2);
assert_eq!(points.len(), 4);
for point in &points {
assert!(rect1.contains_point(point));
assert!(rect2.contains_point(point));
}
}
#[test]
fn test_intersection_points_partial_overlap() {
let rect1 = Rectangle::new(Point::new(0.0, 0.0), 4.0, 4.0);
let rect2 = Rectangle::new(Point::new(2.0, 0.0), 4.0, 4.0);
let points = rect1.intersection_points(&rect2);
assert_eq!(points.len(), 4);
for point in &points {
assert!(rect1.contains_point(point));
assert!(rect2.contains_point(point));
}
}
#[test]
fn test_set_center() {
let mut rect = Rectangle::new(Point::new(0.0, 0.0), 2.0, 2.0);
rect.set_center(Point::new(5.0, 3.0));
assert_eq!(rect.center().x(), 5.0);
assert_eq!(rect.center().y(), 3.0);
}
}