//! Rectangles where the sides are parallel to the x and y-axes. use std::ops::{Add, AddAssign, Sub}; //use alga::general::{Additive, Identity}; use nalgebra::Scalar; use num_traits::sign::{self, Signed}; use num_traits::Zero; use serde::{Deserialize, Serialize}; use super::Vec2; /// Represents a Rectangle with the value type T. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Rect { /// The x coordinate, or leftmost coordinate of the Rect. pub x: T, /// The y coordinate, or rightmost coordinate of the Rect. pub y: T, /// The width of the Rect. pub w: T, /// The height of the Rect. pub h: T, } impl Rect { /// Create a new Rectangle from the internal values, where it might be nicer /// to use a function instead of setting the values directly. pub fn new(x: T, y: T, w: T, h: T) -> Self { Self { x, y, w, h } } /// Create a Rectangle from a slice. Indices are [x, y, w, h]. pub fn from_slice(slice: [T; 4]) -> Rect where T: Copy, { Rect { x: slice[0], y: slice[1], w: slice[2], h: slice[3], } } /// Move by the Vec provided. pub fn translate(&mut self, by: Vec2) where T: AddAssign, { self.x += by.x; self.y += by.y; } /// Set the posiotien of the rectangle to the given one without changing its /// size pub fn set_pos(&mut self, pos: Vec2) { self.x = pos.x; self.y = pos.y; } /// Test if two rectangles intersect. pub fn intersect<'a>(this: &'a Rect, other: &'a Rect) -> bool where T: Add + PartialOrd + Copy, { !(this.x > other.x + other.w || this.x + this.w < other.x || this.y > other.y + other.h || this.y + this.h < other.y) } /// Function to calculate the bounding rectangle that is between two /// vectors. The order of the vectors is irrelevent for this. As long as /// they are diagonally opposite of each other, this function will work. pub fn bounding_rect(pos1: Vec2, pos2: Vec2) -> Self where T: PartialOrd + Sub, { let vertices = [pos1, pos2]; Self::bounding_rect_n(&vertices) } /// Function to calculate the bounding rectangle of n vertices provided. The /// order of them is not relevant and a point that is contained by the /// vertices will not change the result. /// /// # Panics /// If there is not at least one vertex in the vertices slice, the function /// will panic, since it is impossible to calculate any bounds in such a /// case. pub fn bounding_rect_n(vertices: &[Vec2]) -> Self where T: PartialOrd + Sub, { if vertices.is_empty() { panic!("Cannot create bounding rectangle without any vertices"); } let mut min = vertices[0]; let mut max = vertices[0]; for vertex in vertices.iter().skip(1) { min.x = super::partial_min(min.x, vertex.x); min.y = super::partial_min(min.y, vertex.y); max.x = super::partial_max(max.x, vertex.x); max.y = super::partial_max(max.y, vertex.y); } Self { x: min.x, y: min.y, w: max.x - min.x, h: max.y - min.y, } } /// Get the shortest way that must be applied to this Rect to clear out of /// another Rect of the same type so that they would not intersect any more. pub fn shortest_way_out(&self, of: &Rect) -> Vec2 where T: Add + Sub + Signed + PartialOrd, { // Check upwards let mut move_y = of.y - self.y - self.h; // Check downwards let move_down = of.y + of.h - self.y; if move_down < -move_y { move_y = move_down; } // Check left let mut move_x = of.x - self.x - self.w; // Check right let move_right = of.x + of.w - self.x; if move_right < -move_x { move_x = move_right; } if move_x.abs() < move_y.abs() { Vec2::new(move_x, T::zero()) } else { Vec2::new(T::zero(), move_y) } } /// Calculate the area of the rectangle. pub fn area(&self) -> T where T: Signed, { sign::abs(self.w) * sign::abs(self.h) } fn contains_point(&self, point: &Vec2) -> bool where T: Add + PartialOrd, { point.x >= self.x && point.x <= self.x + self.w && point.y >= self.y && point.y <= self.y + self.h } fn contains_rect(&self, rect: &Rect) -> bool where T: Add + Sub + Zero + PartialOrd, { /* True, if the rightmost x-coordinate of the called rectangle is further * right or the same as the rightmost coordinate of the given rect. */ let x_exceeds = self.x + self.w - rect.x - rect.w >= T::zero(); // The same for the y-coordinates let y_exceeds = self.y + self.h - rect.y - rect.h >= T::zero(); x_exceeds && y_exceeds && self.x <= rect.x && self.y <= rect.y } fn is_inside_rect(&self, rect: &Rect) -> bool where T: Add + Sub + Zero + PartialOrd, { rect.contains_rect(&self) } } #[cfg(test)] mod test { use super::*; #[test] fn test_intersect() { let a = Rect::from_slice([0, 0, 4, 4]); let b = Rect::from_slice([-1, -1, 1, 1]); assert!(Rect::intersect(&a, &b)); } }