use std::ops::{Add, AddAssign, Div, Sub};
use num_traits::One;
use crate::math::Vec2;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash)]
#[cfg_attr(
feature = "serde_support",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Rectangle<T = f32> {
pub x: T,
pub y: T,
pub width: T,
pub height: T,
}
impl<T> Rectangle<T> {
pub const fn new(x: T, y: T, width: T, height: T) -> Rectangle<T> {
Rectangle {
x,
y,
width,
height,
}
}
}
impl<T> Rectangle<T>
where
T: Copy,
{
pub fn row(x: T, y: T, width: T, height: T) -> impl Iterator<Item = Rectangle<T>>
where
T: AddAssign,
{
RectangleRow {
next_rect: Rectangle::new(x, y, width, height),
}
}
pub fn column(x: T, y: T, width: T, height: T) -> impl Iterator<Item = Rectangle<T>>
where
T: AddAssign,
{
RectangleColumn {
next_rect: Rectangle::new(x, y, width, height),
}
}
pub fn intersects(&self, other: &Rectangle<T>) -> bool
where
T: Add<Output = T> + PartialOrd,
{
self.x < other.x + other.width
&& self.x + self.width > other.x
&& self.y < other.y + other.height
&& self.y + self.height > other.y
}
pub fn contains(&self, other: &Rectangle<T>) -> bool
where
T: Add<Output = T> + PartialOrd,
{
self.x <= other.x
&& other.x + other.width <= self.x + self.width
&& self.y <= other.y
&& other.y + other.height <= self.y + self.height
}
pub fn contains_point(&self, point: Vec2<T>) -> bool
where
T: Add<Output = T> + PartialOrd,
{
self.x <= point.x
&& point.x < self.x + self.width
&& self.y <= point.y
&& point.y < self.y + self.height
}
pub fn combine(&self, other: &Rectangle<T>) -> Rectangle<T>
where
T: Add<Output = T> + Sub<Output = T> + PartialOrd,
{
let x = if self.x < other.x { self.x } else { other.x };
let y = if self.y < other.y { self.y } else { other.y };
let right = if self.right() > other.right() {
self.right()
} else {
other.right()
};
let bottom = if self.bottom() > other.bottom() {
self.bottom()
} else {
other.bottom()
};
Rectangle {
x,
y,
width: right - x,
height: bottom - y,
}
}
pub fn left(&self) -> T {
self.x
}
pub fn right(&self) -> T
where
T: Add<Output = T>,
{
self.x + self.width
}
pub fn top(&self) -> T {
self.y
}
pub fn bottom(&self) -> T
where
T: Add<Output = T>,
{
self.y + self.height
}
pub fn center(&self) -> Vec2<T>
where
T: One + Add<Output = T> + Div<Output = T>,
{
Vec2::new(
self.x + (self.width / (T::one() + T::one())),
self.y + (self.height / (T::one() + T::one())),
)
}
pub fn top_left(&self) -> Vec2<T> {
Vec2::new(self.x, self.y)
}
pub fn top_right(&self) -> Vec2<T>
where
T: Add<Output = T>,
{
Vec2::new(self.right(), self.y)
}
pub fn bottom_left(&self) -> Vec2<T>
where
T: Add<Output = T>,
{
Vec2::new(self.x, self.bottom())
}
pub fn bottom_right(&self) -> Vec2<T>
where
T: Add<Output = T>,
{
Vec2::new(self.right(), self.bottom())
}
}
#[derive(Debug, Clone)]
struct RectangleRow<T> {
next_rect: Rectangle<T>,
}
impl<T> Iterator for RectangleRow<T>
where
T: Copy + AddAssign,
{
type Item = Rectangle<T>;
fn next(&mut self) -> Option<Rectangle<T>> {
let current_rect = self.next_rect;
self.next_rect.x += self.next_rect.width;
Some(current_rect)
}
}
#[derive(Debug, Clone)]
struct RectangleColumn<T> {
next_rect: Rectangle<T>,
}
impl<T> Iterator for RectangleColumn<T>
where
T: Copy + AddAssign,
{
type Item = Rectangle<T>;
fn next(&mut self) -> Option<Rectangle<T>> {
let current_rect = self.next_rect;
self.next_rect.y += self.next_rect.height;
Some(current_rect)
}
}
#[cfg(test)]
mod tests {
use super::{Rectangle, Vec2};
#[test]
fn intersects() {
let base = Rectangle::new(2.0, 2.0, 4.0, 4.0);
let fully_contained = Rectangle::new(2.5, 2.5, 2.0, 2.0);
let overlapping = Rectangle::new(3.0, 3.0, 4.0, 4.0);
let seperate = Rectangle::new(20.0, 20.0, 4.0, 4.0);
let adjacent = Rectangle::new(6.0, 2.0, 4.0, 4.0);
assert!(base.intersects(&base));
assert!(base.intersects(&fully_contained));
assert!(base.intersects(&overlapping));
assert!(!base.intersects(&seperate));
assert!(!base.intersects(&adjacent));
}
#[test]
fn contains() {
let base = Rectangle::new(2.0, 2.0, 4.0, 4.0);
let fully_contained = Rectangle::new(2.5, 2.5, 2.0, 2.0);
let overlapping = Rectangle::new(3.0, 3.0, 4.0, 4.0);
let seperate = Rectangle::new(20.0, 20.0, 4.0, 4.0);
let adjacent = Rectangle::new(6.0, 2.0, 4.0, 4.0);
assert!(base.contains(&base));
assert!(base.contains(&fully_contained));
assert!(!base.contains(&overlapping));
assert!(!base.contains(&seperate));
assert!(!base.contains(&adjacent));
}
#[test]
fn contains_point() {
let base = Rectangle::new(2.0, 2.0, 4.0, 4.0);
let top_left = Vec2::new(2.0, 2.0);
let top_right = Vec2::new(6.0, 2.0);
let bottom_left = Vec2::new(2.0, 6.0);
let bottom_right = Vec2::new(6.0, 6.0);
let centre = Vec2::new(4.0, 4.0);
let less_than = Vec2::new(1.0, 1.0);
let more_than = Vec2::new(7.0, 7.0);
assert!(base.contains_point(top_left));
assert!(base.contains_point(centre));
assert!(!base.contains_point(top_right));
assert!(!base.contains_point(bottom_left));
assert!(!base.contains_point(bottom_right));
assert!(!base.contains_point(less_than));
assert!(!base.contains_point(more_than));
}
#[test]
fn combine() {
assert_eq!(
Rectangle::new(16.0, 8.0, 32.0, 64.0).combine(&Rectangle::new(8.0, 0.0, 32.0, 16.0)),
Rectangle::new(8.0, 0.0, 40.0, 72.0),
)
}
}