use std::fmt;
use crate::{lerp, pos2, vec2, Div, Mul, Pos2, Rangef, Rot2, Vec2};
#[repr(C)]
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Rect {
pub min: Pos2,
pub max: Pos2,
}
impl Rect {
pub const EVERYTHING: Self = Self {
min: pos2(-f32::INFINITY, -f32::INFINITY),
max: pos2(f32::INFINITY, f32::INFINITY),
};
pub const NOTHING: Self = Self {
min: pos2(f32::INFINITY, f32::INFINITY),
max: pos2(-f32::INFINITY, -f32::INFINITY),
};
pub const NAN: Self = Self {
min: pos2(f32::NAN, f32::NAN),
max: pos2(f32::NAN, f32::NAN),
};
pub const ZERO: Self = Self {
min: Pos2::ZERO,
max: Pos2::ZERO,
};
#[inline(always)]
pub const fn from_min_max(min: Pos2, max: Pos2) -> Self {
Self { min, max }
}
#[inline(always)]
pub fn from_min_size(min: Pos2, size: Vec2) -> Self {
Self {
min,
max: min + size,
}
}
#[inline(always)]
pub fn from_center_size(center: Pos2, size: Vec2) -> Self {
Self {
min: center - size * 0.5,
max: center + size * 0.5,
}
}
#[inline(always)]
pub fn from_x_y_ranges(x_range: impl Into<Rangef>, y_range: impl Into<Rangef>) -> Self {
let x_range = x_range.into();
let y_range = y_range.into();
Self {
min: pos2(x_range.min, y_range.min),
max: pos2(x_range.max, y_range.max),
}
}
#[inline]
pub fn from_two_pos(a: Pos2, b: Pos2) -> Self {
Self {
min: pos2(a.x.min(b.x), a.y.min(b.y)),
max: pos2(a.x.max(b.x), a.y.max(b.y)),
}
}
#[inline]
pub fn from_pos(point: Pos2) -> Self {
Self {
min: point,
max: point,
}
}
pub fn from_points(points: &[Pos2]) -> Self {
let mut rect = Self::NOTHING;
for &p in points {
rect.extend_with(p);
}
rect
}
#[inline]
pub fn everything_right_of(left_x: f32) -> Self {
let mut rect = Self::EVERYTHING;
rect.set_left(left_x);
rect
}
#[inline]
pub fn everything_left_of(right_x: f32) -> Self {
let mut rect = Self::EVERYTHING;
rect.set_right(right_x);
rect
}
#[inline]
pub fn everything_below(top_y: f32) -> Self {
let mut rect = Self::EVERYTHING;
rect.set_top(top_y);
rect
}
#[inline]
pub fn everything_above(bottom_y: f32) -> Self {
let mut rect = Self::EVERYTHING;
rect.set_bottom(bottom_y);
rect
}
#[must_use]
#[inline]
pub fn with_min_x(mut self, min_x: f32) -> Self {
self.min.x = min_x;
self
}
#[must_use]
#[inline]
pub fn with_min_y(mut self, min_y: f32) -> Self {
self.min.y = min_y;
self
}
#[must_use]
#[inline]
pub fn with_max_x(mut self, max_x: f32) -> Self {
self.max.x = max_x;
self
}
#[must_use]
#[inline]
pub fn with_max_y(mut self, max_y: f32) -> Self {
self.max.y = max_y;
self
}
#[must_use]
pub fn expand(self, amnt: f32) -> Self {
self.expand2(Vec2::splat(amnt))
}
#[must_use]
pub fn expand2(self, amnt: Vec2) -> Self {
Self::from_min_max(self.min - amnt, self.max + amnt)
}
#[must_use]
pub fn scale_from_center(self, scale_factor: f32) -> Self {
self.scale_from_center2(Vec2::splat(scale_factor))
}
#[must_use]
pub fn scale_from_center2(self, scale_factor: Vec2) -> Self {
Self::from_center_size(self.center(), self.size() * scale_factor)
}
#[must_use]
pub fn shrink(self, amnt: f32) -> Self {
self.shrink2(Vec2::splat(amnt))
}
#[must_use]
pub fn shrink2(self, amnt: Vec2) -> Self {
Self::from_min_max(self.min + amnt, self.max - amnt)
}
#[must_use]
#[inline]
pub fn translate(self, amnt: Vec2) -> Self {
Self::from_min_size(self.min + amnt, self.size())
}
#[must_use]
#[inline]
pub fn rotate_bb(self, rot: Rot2) -> Self {
let a = rot * self.left_top().to_vec2();
let b = rot * self.right_top().to_vec2();
let c = rot * self.left_bottom().to_vec2();
let d = rot * self.right_bottom().to_vec2();
Self::from_min_max(
a.min(b).min(c).min(d).to_pos2(),
a.max(b).max(c).max(d).to_pos2(),
)
}
#[must_use]
#[inline]
pub fn intersects(self, other: Self) -> bool {
self.min.x <= other.max.x
&& other.min.x <= self.max.x
&& self.min.y <= other.max.y
&& other.min.y <= self.max.y
}
pub fn set_width(&mut self, w: f32) {
self.max.x = self.min.x + w;
}
pub fn set_height(&mut self, h: f32) {
self.max.y = self.min.y + h;
}
pub fn set_center(&mut self, center: Pos2) {
*self = self.translate(center - self.center());
}
#[must_use]
#[inline(always)]
pub fn contains(&self, p: Pos2) -> bool {
self.min.x <= p.x && p.x <= self.max.x && self.min.y <= p.y && p.y <= self.max.y
}
#[must_use]
pub fn contains_rect(&self, other: Self) -> bool {
self.contains(other.min) && self.contains(other.max)
}
#[must_use]
pub fn clamp(&self, p: Pos2) -> Pos2 {
p.clamp(self.min, self.max)
}
#[inline(always)]
pub fn extend_with(&mut self, p: Pos2) {
self.min = self.min.min(p);
self.max = self.max.max(p);
}
#[inline(always)]
pub fn extend_with_x(&mut self, x: f32) {
self.min.x = self.min.x.min(x);
self.max.x = self.max.x.max(x);
}
#[inline(always)]
pub fn extend_with_y(&mut self, y: f32) {
self.min.y = self.min.y.min(y);
self.max.y = self.max.y.max(y);
}
#[inline(always)]
#[must_use]
pub fn union(self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline]
#[must_use]
pub fn intersect(self, other: Self) -> Self {
Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
}
}
#[inline(always)]
pub fn center(&self) -> Pos2 {
Pos2 {
x: (self.min.x + self.max.x) / 2.0,
y: (self.min.y + self.max.y) / 2.0,
}
}
#[inline(always)]
pub fn size(&self) -> Vec2 {
self.max - self.min
}
#[inline(always)]
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
#[inline(always)]
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
pub fn aspect_ratio(&self) -> f32 {
self.width() / self.height()
}
pub fn square_proportions(&self) -> Vec2 {
let w = self.width();
let h = self.height();
if w > h {
vec2(w / h, 1.0)
} else {
vec2(1.0, h / w)
}
}
#[inline(always)]
pub fn area(&self) -> f32 {
self.width() * self.height()
}
#[inline]
pub fn distance_to_pos(&self, pos: Pos2) -> f32 {
self.distance_sq_to_pos(pos).sqrt()
}
#[inline]
pub fn distance_sq_to_pos(&self, pos: Pos2) -> f32 {
if self.is_negative() {
return f32::INFINITY;
}
let dx = if self.min.x > pos.x {
self.min.x - pos.x
} else if pos.x > self.max.x {
pos.x - self.max.x
} else {
0.0
};
let dy = if self.min.y > pos.y {
self.min.y - pos.y
} else if pos.y > self.max.y {
pos.y - self.max.y
} else {
0.0
};
dx * dx + dy * dy
}
pub fn signed_distance_to_pos(&self, pos: Pos2) -> f32 {
if self.is_negative() {
return f32::INFINITY;
}
let edge_distances = (pos - self.center()).abs() - self.size() * 0.5;
let inside_dist = edge_distances.max_elem().min(0.0);
let outside_dist = edge_distances.max(Vec2::ZERO).length();
inside_dist + outside_dist
}
#[inline]
pub fn lerp_inside(&self, t: Vec2) -> Pos2 {
Pos2 {
x: lerp(self.min.x..=self.max.x, t.x),
y: lerp(self.min.y..=self.max.y, t.y),
}
}
#[inline]
pub fn lerp_towards(&self, other: &Self, t: f32) -> Self {
Self {
min: self.min.lerp(other.min, t),
max: self.max.lerp(other.max, t),
}
}
#[inline(always)]
pub fn x_range(&self) -> Rangef {
Rangef::new(self.min.x, self.max.x)
}
#[inline(always)]
pub fn y_range(&self) -> Rangef {
Rangef::new(self.min.y, self.max.y)
}
#[inline(always)]
pub fn bottom_up_range(&self) -> Rangef {
Rangef::new(self.max.y, self.min.y)
}
#[inline(always)]
pub fn is_negative(&self) -> bool {
self.max.x < self.min.x || self.max.y < self.min.y
}
#[inline(always)]
pub fn is_positive(&self) -> bool {
self.min.x < self.max.x && self.min.y < self.max.y
}
#[inline(always)]
pub fn is_finite(&self) -> bool {
self.min.is_finite() && self.max.is_finite()
}
#[inline(always)]
pub fn any_nan(self) -> bool {
self.min.any_nan() || self.max.any_nan()
}
}
impl Rect {
#[inline(always)]
pub fn left(&self) -> f32 {
self.min.x
}
#[inline(always)]
pub fn left_mut(&mut self) -> &mut f32 {
&mut self.min.x
}
#[inline(always)]
pub fn set_left(&mut self, x: f32) {
self.min.x = x;
}
#[inline(always)]
pub fn right(&self) -> f32 {
self.max.x
}
#[inline(always)]
pub fn right_mut(&mut self) -> &mut f32 {
&mut self.max.x
}
#[inline(always)]
pub fn set_right(&mut self, x: f32) {
self.max.x = x;
}
#[inline(always)]
pub fn top(&self) -> f32 {
self.min.y
}
#[inline(always)]
pub fn top_mut(&mut self) -> &mut f32 {
&mut self.min.y
}
#[inline(always)]
pub fn set_top(&mut self, y: f32) {
self.min.y = y;
}
#[inline(always)]
pub fn bottom(&self) -> f32 {
self.max.y
}
#[inline(always)]
pub fn bottom_mut(&mut self) -> &mut f32 {
&mut self.max.y
}
#[inline(always)]
pub fn set_bottom(&mut self, y: f32) {
self.max.y = y;
}
#[inline(always)]
#[doc(alias = "top_left")]
pub fn left_top(&self) -> Pos2 {
pos2(self.left(), self.top())
}
#[inline(always)]
pub fn center_top(&self) -> Pos2 {
pos2(self.center().x, self.top())
}
#[inline(always)]
#[doc(alias = "top_right")]
pub fn right_top(&self) -> Pos2 {
pos2(self.right(), self.top())
}
#[inline(always)]
pub fn left_center(&self) -> Pos2 {
pos2(self.left(), self.center().y)
}
#[inline(always)]
pub fn right_center(&self) -> Pos2 {
pos2(self.right(), self.center().y)
}
#[inline(always)]
#[doc(alias = "bottom_left")]
pub fn left_bottom(&self) -> Pos2 {
pos2(self.left(), self.bottom())
}
#[inline(always)]
pub fn center_bottom(&self) -> Pos2 {
pos2(self.center().x, self.bottom())
}
#[inline(always)]
#[doc(alias = "bottom_right")]
pub fn right_bottom(&self) -> Pos2 {
pos2(self.right(), self.bottom())
}
pub fn split_left_right_at_fraction(&self, t: f32) -> (Self, Self) {
self.split_left_right_at_x(lerp(self.min.x..=self.max.x, t))
}
pub fn split_left_right_at_x(&self, split_x: f32) -> (Self, Self) {
let left = Self::from_min_max(self.min, Pos2::new(split_x, self.max.y));
let right = Self::from_min_max(Pos2::new(split_x, self.min.y), self.max);
(left, right)
}
pub fn split_top_bottom_at_fraction(&self, t: f32) -> (Self, Self) {
self.split_top_bottom_at_y(lerp(self.min.y..=self.max.y, t))
}
pub fn split_top_bottom_at_y(&self, split_y: f32) -> (Self, Self) {
let top = Self::from_min_max(self.min, Pos2::new(self.max.x, split_y));
let bottom = Self::from_min_max(Pos2::new(self.min.x, split_y), self.max);
(top, bottom)
}
}
impl Rect {
pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool {
debug_assert!(d.is_normalized(), "expected normalized direction");
let mut tmin = -f32::INFINITY;
let mut tmax = f32::INFINITY;
if d.x != 0.0 {
let tx1 = (self.min.x - o.x) / d.x;
let tx2 = (self.max.x - o.x) / d.x;
tmin = tmin.max(tx1.min(tx2));
tmax = tmax.min(tx1.max(tx2));
}
if d.y != 0.0 {
let ty1 = (self.min.y - o.y) / d.y;
let ty2 = (self.max.y - o.y) / d.y;
tmin = tmin.max(ty1.min(ty2));
tmax = tmax.min(ty1.max(ty2));
}
0.0 <= tmax && tmin <= tmax
}
pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 {
debug_assert!(d.is_normalized(), "expected normalized direction");
let mut tmin = f32::NEG_INFINITY;
let mut tmax = f32::INFINITY;
for i in 0..2 {
let inv_d = 1.0 / -d[i];
let mut t0 = (self.min[i] - self.center()[i]) * inv_d;
let mut t1 = (self.max[i] - self.center()[i]) * inv_d;
if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}
tmin = tmin.max(t0);
tmax = tmax.min(t1);
}
let t = tmax.min(tmin);
self.center() + t * -d
}
}
impl fmt::Debug for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{:?} - {:?}]", self.min, self.max)
}
}
impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[")?;
self.min.fmt(f)?;
f.write_str(" - ")?;
self.max.fmt(f)?;
f.write_str("]")?;
Ok(())
}
}
impl From<[Pos2; 2]> for Rect {
#[inline]
fn from([min, max]: [Pos2; 2]) -> Self {
Self { min, max }
}
}
impl Mul<f32> for Rect {
type Output = Self;
#[inline]
fn mul(self, factor: f32) -> Self {
Self {
min: self.min * factor,
max: self.max * factor,
}
}
}
impl Mul<Rect> for f32 {
type Output = Rect;
#[inline]
fn mul(self, vec: Rect) -> Rect {
Rect {
min: self * vec.min,
max: self * vec.max,
}
}
}
impl Div<f32> for Rect {
type Output = Self;
#[inline]
fn div(self, factor: f32) -> Self {
Self {
min: self.min / factor,
max: self.max / factor,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rect() {
let r = Rect::from_min_max(pos2(10.0, 10.0), pos2(20.0, 20.0));
assert_eq!(r.distance_sq_to_pos(pos2(15.0, 15.0)), 0.0);
assert_eq!(r.distance_sq_to_pos(pos2(10.0, 15.0)), 0.0);
assert_eq!(r.distance_sq_to_pos(pos2(10.0, 10.0)), 0.0);
assert_eq!(r.distance_sq_to_pos(pos2(5.0, 15.0)), 25.0); assert_eq!(r.distance_sq_to_pos(pos2(25.0, 15.0)), 25.0); assert_eq!(r.distance_sq_to_pos(pos2(15.0, 5.0)), 25.0); assert_eq!(r.distance_sq_to_pos(pos2(15.0, 25.0)), 25.0); assert_eq!(r.distance_sq_to_pos(pos2(25.0, 5.0)), 50.0); }
#[test]
fn scale_rect() {
let c = pos2(100.0, 50.0);
let r = Rect::from_center_size(c, vec2(30.0, 60.0));
assert_eq!(
r.scale_from_center(2.0),
Rect::from_center_size(c, vec2(60.0, 120.0))
);
assert_eq!(
r.scale_from_center(0.5),
Rect::from_center_size(c, vec2(15.0, 30.0))
);
assert_eq!(
r.scale_from_center2(vec2(2.0, 3.0)),
Rect::from_center_size(c, vec2(60.0, 180.0))
);
}
#[test]
fn test_ray_intersection() {
let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0));
println!("Righward ray from left:");
assert!(rect.intersects_ray(pos2(0.0, 2.0), Vec2::RIGHT));
println!("Righward ray from center:");
assert!(rect.intersects_ray(pos2(2.0, 2.0), Vec2::RIGHT));
println!("Righward ray from right:");
assert!(!rect.intersects_ray(pos2(4.0, 2.0), Vec2::RIGHT));
println!("Leftward ray from left:");
assert!(!rect.intersects_ray(pos2(0.0, 2.0), Vec2::LEFT));
println!("Leftward ray from center:");
assert!(rect.intersects_ray(pos2(2.0, 2.0), Vec2::LEFT));
println!("Leftward ray from right:");
assert!(rect.intersects_ray(pos2(4.0, 2.0), Vec2::LEFT));
}
#[test]
fn test_ray_from_center_intersection() {
let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0));
assert_eq!(
rect.intersects_ray_from_center(Vec2::RIGHT),
pos2(3.0, 2.0),
"rightward ray"
);
assert_eq!(
rect.intersects_ray_from_center(Vec2::UP),
pos2(2.0, 1.0),
"upward ray"
);
assert_eq!(
rect.intersects_ray_from_center(Vec2::LEFT),
pos2(1.0, 2.0),
"leftward ray"
);
assert_eq!(
rect.intersects_ray_from_center(Vec2::DOWN),
pos2(2.0, 3.0),
"downward ray"
);
assert_eq!(
rect.intersects_ray_from_center((Vec2::LEFT + Vec2::DOWN).normalized()),
pos2(1.0, 3.0),
"bottom-left corner ray"
);
assert_eq!(
rect.intersects_ray_from_center((Vec2::LEFT + Vec2::UP).normalized()),
pos2(1.0, 1.0),
"top-left corner ray"
);
assert_eq!(
rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::DOWN).normalized()),
pos2(3.0, 3.0),
"bottom-right corner ray"
);
assert_eq!(
rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::UP).normalized()),
pos2(3.0, 1.0),
"top-right corner ray"
);
}
}