use derive_getters::Getters;
use crate::math::Cardinals;
use crate::math::Circle;
use crate::math::Vector2;
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub enum Anchor {
#[default]
BottomLeft,
Center,
}
#[derive(Debug, Default, Copy, Clone, Getters)]
pub struct Rectangle {
bottom_left: Vector2,
center: Vector2,
size: Vector2,
anchor: Anchor,
}
impl Rectangle {
pub fn x(&self) -> f32 {
match self.anchor {
Anchor::BottomLeft => self.bottom_left.x,
Anchor::Center => self.center.x,
}
}
pub fn y(&self) -> f32 {
match self.anchor {
Anchor::BottomLeft => self.bottom_left.y,
Anchor::Center => self.center.y,
}
}
pub fn width(&self) -> f32 {
self.size.x
}
pub fn height(&self) -> f32 {
self.size.y
}
pub fn top(&self) -> f32 {
self.bottom_left.y + self.size.y
}
pub fn bottom(&self) -> f32 {
self.bottom_left.y
}
pub fn right(&self) -> f32 {
self.bottom_left.x + self.size.x
}
pub fn left(&self) -> f32 {
self.bottom_left.x
}
pub fn offset(&mut self, offset: &Vector2) {
self.bottom_left = self.bottom_left.add(offset);
self.center = self.center.add(offset);
}
pub fn with_offset(mut self, offset: &Vector2) -> Self {
self.bottom_left = self.bottom_left.add(offset);
self.center = self.center.add(offset);
self
}
pub fn with_center(mut self, center: &Vector2) -> Self {
self.anchor = Anchor::Center;
self.center = *center;
self.recalc_from_center();
self
}
pub fn calculate_bounding_circle(&self) -> Circle {
let s = self.size();
let hx = s.x * 0.5;
let hy = s.y * 0.5;
let r = (hx * hx + hy * hy).sqrt();
Circle::default().with_center(self.center()).with_radius(r)
}
fn recalc_from_center(&mut self) {
self.bottom_left = self.center.add(&self.size.scaled(-0.5));
}
fn recalc_from_bottom_left(&mut self) {
self.center = self.bottom_left.add(&self.size.scaled(0.5));
}
pub fn with_bottom_left(mut self, bottom_left: &Vector2) -> Self {
self.anchor = Anchor::BottomLeft;
self.bottom_left = *bottom_left;
self.recalc_from_bottom_left();
self
}
pub fn with_size(mut self, size: &Vector2) -> Self {
self.size = *size;
match self.anchor {
Anchor::BottomLeft => self.recalc_from_bottom_left(),
Anchor::Center => self.recalc_from_center(),
};
self
}
pub fn hflip(&mut self, pivot: f32) {
match self.anchor {
Anchor::BottomLeft => {
self.bottom_left.y = pivot - self.bottom_left.y - self.size.y;
self.recalc_from_bottom_left();
},
a => todo!("Implement hflip for anchor {:?}", a),
};
}
pub fn contains(&self, pos: &Vector2) -> bool {
self.bottom_left.x <= pos.x
&& self.bottom_left.y <= pos.y
&& self.bottom_left.x + self.size.x >= pos.x
&& self.bottom_left.y + self.size.y >= pos.y
}
pub fn combine_with(mut self, other: &Rectangle) -> Rectangle {
let l = self.left().min(other.left());
let r = self.right().max(other.right());
let b = self.bottom().min(other.bottom());
let t = self.top().max(other.top());
let w = r - l;
let h = t - b;
self.size = Vector2::new(w, h);
match self.anchor {
Anchor::BottomLeft => {
todo!();
},
Anchor::Center => {
let cx = (l + r) / 2.0;
let cy = (b + t) / 2.0;
self.with_center(&Vector2::new(cx, cy))
},
}
}
pub fn would_collide(
&self,
start: &Vector2,
end: &Vector2,
other: &Rectangle,
) -> Option<(f32, Cardinals)> {
let rs = other.clone().with_center(start);
let re = other.clone().with_center(end);
let vert = if start.y > end.y {
if re.bottom() < self.top() && rs.bottom() > self.top() {
let ay = self.top();
let p = (ay - rs.bottom()) / (re.bottom() - rs.bottom());
let full = end.sub(&start).scaled(p);
let actual = start.add(&full);
let actual_rect = other.clone().with_center(&actual);
if actual_rect.right() > self.left() && actual_rect.left() < self.right() {
Some((p, Cardinals::Bottom))
} else {
None
}
} else {
None
}
} else {
if re.top() > self.bottom() && rs.top() < self.bottom() {
let ay = self.bottom();
let p = (ay - rs.top()) / (re.top() - rs.top());
let full = end.sub(&start).scaled(p);
let actual = start.add(&full);
let actual_rect = other.clone().with_center(&actual);
if actual_rect.right() > self.left() && actual_rect.left() < self.right() {
Some((p, Cardinals::Top))
} else {
None
}
} else {
None
}
};
let horiz = if start.x > end.x {
if re.left() < self.right() && rs.left() > self.right() {
let ay = self.right();
let p = (ay - rs.left()) / (re.left() - rs.left());
let full = end.sub(&start).scaled(p);
let actual = start.add(&full);
let actual_rect = other.clone().with_center(&actual);
if actual_rect.top() > self.bottom() && actual_rect.bottom() < self.top() {
Some((p, Cardinals::Left))
} else {
None
}
} else {
None
}
} else {
if re.right() > self.left() && rs.right() < self.left() {
let ay = self.left();
let p = (ay - rs.right()) / (re.right() - rs.right());
let full = end.sub(&start).scaled(p);
let actual = start.add(&full);
let actual_rect = other.clone().with_center(&actual);
if actual_rect.top() > self.bottom() && actual_rect.bottom() < self.top() {
Some((p, Cardinals::Right))
} else {
None
}
} else {
None
}
};
match (vert, horiz) {
(None, None) => None,
(None, Some(h)) => Some(h),
(Some(v), None) => Some(v),
(Some(v), Some(h)) => {
if v.0 < h.0 {
Some(v)
} else {
Some(h)
}
},
}
}
}
impl From<(f32, f32, f32, f32)> for Rectangle {
fn from(t: (f32, f32, f32, f32)) -> Self {
let bottom_left = Vector2::new(t.0, t.1);
let size = Vector2::new(t.2, t.3);
Self::default()
.with_bottom_left(&bottom_left)
.with_size(&size)
}
}
impl From<(f64, f64, f64, f64)> for Rectangle {
fn from(t: (f64, f64, f64, f64)) -> Self {
let bottom_left = Vector2::new(t.0 as f32, t.1 as f32);
let size = Vector2::new(t.2 as f32, t.3 as f32);
Self::default()
.with_bottom_left(&bottom_left)
.with_size(&size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_position_and_size_with_vector2() {
let r = Rectangle::default()
.with_bottom_left(&Vector2::new(1.0, 2.0))
.with_size(&Vector2::new(3.0, 4.0));
assert_eq!(r.bottom_left().x, 1.0);
assert_eq!(r.bottom_left().y, 2.0);
assert_eq!(r.size().x, 3.0);
assert_eq!(r.size().y, 4.0);
assert_eq!(r.x(), 1.0);
assert_eq!(r.y(), 2.0);
assert_eq!(r.width(), 3.0);
assert_eq!(r.height(), 4.0);
}
#[test]
fn can_position_and_size_from_f64_tuple() {
let r: Rectangle = (1.0f64, 2.0, 3.0, 4.0).into();
assert_eq!(r.bottom_left().x, 1.0);
assert_eq!(r.bottom_left().y, 2.0);
assert_eq!(r.size().x, 3.0);
assert_eq!(r.size().y, 4.0);
assert_eq!(r.x(), 1.0);
assert_eq!(r.y(), 2.0);
assert_eq!(r.width(), 3.0);
assert_eq!(r.height(), 4.0);
}
#[test]
fn can_position_and_size_from_f32_tuple() {
let r: Rectangle = (1.0f32, 2.0, 3.0, 4.0).into();
assert_eq!(r.bottom_left().x, 1.0);
assert_eq!(r.bottom_left().y, 2.0);
assert_eq!(r.size().x, 3.0);
assert_eq!(r.size().y, 4.0);
assert_eq!(r.x(), 1.0);
assert_eq!(r.y(), 2.0);
assert_eq!(r.width(), 3.0);
assert_eq!(r.height(), 4.0);
}
#[test]
fn can_position_and_size_with_bottom_left() {
let r: Rectangle = Rectangle::default()
.with_bottom_left(&Vector2::new(-5.0, -10.0))
.with_size(&Vector2::new(10.0, 20.0));
assert_eq!(r.x(), -5.0);
assert_eq!(r.y(), -10.0);
assert_eq!(r.width(), 10.0);
assert_eq!(r.height(), 20.0);
assert_eq!(r.center().x, 0.0);
assert_eq!(r.center().y, 0.0);
assert_eq!(r.bottom_left().x, -5.0);
assert_eq!(r.bottom_left().y, -10.0);
}
#[test]
fn can_position_and_size_with_center() {
let r: Rectangle = Rectangle::default()
.with_center(&Vector2::new(0.0, 0.0))
.with_size(&Vector2::new(10.0, 20.0));
assert_eq!(r.x(), 0.0);
assert_eq!(r.y(), 0.0);
assert_eq!(r.width(), 10.0);
assert_eq!(r.height(), 20.0);
assert_eq!(r.center().x, 0.0);
assert_eq!(r.center().y, 0.0);
assert_eq!(r.bottom_left().x, -5.0);
assert_eq!(r.bottom_left().y, -10.0);
}
#[test]
fn check_if_it_contains_a_point() {
let r: Rectangle = Rectangle::default()
.with_center(&Vector2::new(0.0, 0.0))
.with_size(&Vector2::new(10.0, 20.0));
assert_eq!(r.contains(&Vector2::new(0.0, 0.0)), true);
assert_eq!(r.contains(&Vector2::new(-5.0, 0.0)), true);
assert_eq!(r.contains(&Vector2::new(-6.0, 0.0)), false);
assert_eq!(r.contains(&Vector2::new(5.0, 0.0)), true);
assert_eq!(r.contains(&Vector2::new(6.0, 0.0)), false);
assert_eq!(r.contains(&Vector2::new(0.0, -10.0)), true);
assert_eq!(r.contains(&Vector2::new(0.0, -11.0)), false);
assert_eq!(r.contains(&Vector2::new(0.0, 10.0)), true);
assert_eq!(r.contains(&Vector2::new(0.0, 11.0)), false);
}
#[test]
fn can_combine_center() {
let r: Rectangle = Rectangle::default()
.with_center(&Vector2::new(0.0, 0.0))
.with_size(&Vector2::new(10.0, 20.0));
assert_eq!(r.width(), 10.0);
assert_eq!(r.height(), 20.0);
let r2: Rectangle = Rectangle::default()
.with_center(&Vector2::new(0.0, 0.0))
.with_size(&Vector2::new(20.0, 40.0));
let r = r.combine_with(&r2);
assert_eq!(r.width(), 20.0);
assert_eq!(r.height(), 40.0);
dbg!(r);
let r2: Rectangle = Rectangle::default()
.with_center(&Vector2::new(100.0, 100.0))
.with_size(&Vector2::new(20.0, 40.0));
let r = r.combine_with(&r2);
dbg!(r2);
dbg!(r);
assert_eq!(r.width(), 120.0);
assert_eq!(r.height(), 140.0);
}
}