1use super::Vec2;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
9#[repr(C)]
10pub struct Rect {
11 pub x: f32,
13 pub y: f32,
15 pub w: f32,
17 pub h: f32,
19}
20
21impl Rect {
22 #[inline]
24 pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
25 Self { x, y, w, h }
26 }
27
28 #[inline]
30 pub const fn from_min_max(min: Vec2, max: Vec2) -> Self {
31 Self {
32 x: min.x,
33 y: min.y,
34 w: max.x - min.x,
35 h: max.y - min.y,
36 }
37 }
38
39 #[inline]
41 pub const fn centered(center: Vec2, size: Vec2) -> Self {
42 Self {
43 x: center.x - size.x * 0.5,
44 y: center.y - size.y * 0.5,
45 w: size.x,
46 h: size.y,
47 }
48 }
49
50 #[inline]
52 pub fn pos(self) -> Vec2 {
53 Vec2::new(self.x, self.y)
54 }
55
56 #[inline]
58 pub fn size(self) -> Vec2 {
59 Vec2::new(self.w, self.h)
60 }
61
62 #[inline]
64 pub fn center(self) -> Vec2 {
65 Vec2::new(self.x + self.w * 0.5, self.y + self.h * 0.5)
66 }
67
68 #[inline]
70 pub fn max(self) -> Vec2 {
71 Vec2::new(self.x + self.w, self.y + self.h)
72 }
73
74 #[inline]
76 pub fn top_right(self) -> Vec2 {
77 Vec2::new(self.x + self.w, self.y)
78 }
79
80 #[inline]
82 pub fn bottom_left(self) -> Vec2 {
83 Vec2::new(self.x, self.y + self.h)
84 }
85
86 #[inline]
88 pub fn contains(self, point: Vec2) -> bool {
89 point.x >= self.x
90 && point.x <= self.x + self.w
91 && point.y >= self.y
92 && point.y <= self.y + self.h
93 }
94
95 #[inline]
97 pub fn contains_rect(self, other: Rect) -> bool {
98 let self_max = self.max();
99 let other_max = other.max();
100 other.x >= self.x
101 && other.y >= self.y
102 && other_max.x <= self_max.x
103 && other_max.y <= self_max.y
104 }
105
106 #[inline]
108 pub fn overlaps(self, other: Rect) -> bool {
109 self.x < other.x + other.w
110 && self.x + self.w > other.x
111 && self.y < other.y + other.h
112 && self.y + self.h > other.y
113 }
114
115 #[inline]
117 pub fn intersection(self, other: Rect) -> Option<Rect> {
118 if !self.overlaps(other) {
119 return None;
120 }
121 let x1 = self.x.max(other.x);
122 let y1 = self.y.max(other.y);
123 let x2 = (self.x + self.w).min(other.x + other.w);
124 let y2 = (self.y + self.h).min(other.y + other.h);
125 Some(Rect::new(x1, y1, x2 - x1, y2 - y1))
126 }
127
128 #[inline]
130 pub fn union(self, other: Rect) -> Rect {
131 let x1 = self.x.min(other.x);
132 let y1 = self.y.min(other.y);
133 let x2 = (self.x + self.w).max(other.x + other.w);
134 let y2 = (self.y + self.h).max(other.y + other.h);
135 Rect::new(x1, y1, x2 - x1, y2 - y1)
136 }
137
138 #[inline]
140 pub fn inflated(self, amount: f32) -> Rect {
141 Rect::new(
142 self.x - amount,
143 self.y - amount,
144 self.w + amount * 2.0,
145 self.h + amount * 2.0,
146 )
147 }
148
149 #[inline]
151 pub fn deflated(self, amount: f32) -> Rect {
152 self.inflated(-amount)
153 }
154
155 #[inline]
157 pub fn scaled(self, scale: Vec2) -> Rect {
158 let new_size = self.size() * scale;
159 let offset = (new_size - self.size()) * 0.5;
160 Rect::new(self.x - offset.x, self.y - offset.y, new_size.x, new_size.y)
161 }
162
163 #[inline]
165 pub fn with_center(self, center: Vec2) -> Rect {
166 Rect::centered(center, self.size())
167 }
168
169 #[inline]
171 pub fn translated(self, offset: Vec2) -> Rect {
172 Rect::new(self.x + offset.x, self.y + offset.y, self.w, self.h)
173 }
174
175 #[inline]
177 pub fn closest_point(self, point: Vec2) -> Vec2 {
178 Vec2::new(
179 point.x.clamp(self.x, self.x + self.w),
180 point.y.clamp(self.y, self.y + self.h),
181 )
182 }
183
184 #[inline]
186 pub fn area(self) -> f32 {
187 self.w * self.h
188 }
189
190 #[inline]
192 pub fn perimeter(self) -> f32 {
193 2.0 * (self.w + self.h)
194 }
195
196 #[inline]
198 pub fn aspect_ratio(self) -> f32 {
199 self.w / self.h
200 }
201}
202
203impl std::fmt::Display for Rect {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 write!(f, "Rect({}, {}, {}, {})", self.x, self.y, self.w, self.h)
206 }
207}
208
209#[cfg(feature = "serde")]
210impl serde::Serialize for Rect {
211 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
212 where
213 S: serde::Serializer,
214 {
215 use serde::ser::SerializeStruct;
216 let mut s = serializer.serialize_struct("Rect", 4)?;
217 s.serialize_field("x", &self.x)?;
218 s.serialize_field("y", &self.y)?;
219 s.serialize_field("w", &self.w)?;
220 s.serialize_field("h", &self.h)?;
221 s.end()
222 }
223}
224
225#[cfg(feature = "serde")]
226impl<'de> serde::Deserialize<'de> for Rect {
227 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
228 where
229 D: serde::Deserializer<'de>,
230 {
231 #[derive(serde::Deserialize)]
232 struct RectHelper { x: f32, y: f32, w: f32, h: f32 }
233 let h = RectHelper::deserialize(deserializer)?;
234 Ok(Rect::new(h.x, h.y, h.w, h.h))
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_contains_point() {
244 let r = Rect::new(10.0, 10.0, 100.0, 50.0);
245 assert!(r.contains(Vec2::new(50.0, 30.0)));
246 assert!(r.contains(Vec2::new(10.0, 10.0)));
247 assert!(r.contains(Vec2::new(110.0, 60.0)));
248 assert!(!r.contains(Vec2::new(5.0, 30.0)));
249 assert!(!r.contains(Vec2::new(50.0, 65.0)));
250 }
251
252 #[test]
253 fn test_overlaps() {
254 let a = Rect::new(0.0, 0.0, 100.0, 100.0);
255 let b = Rect::new(50.0, 50.0, 100.0, 100.0);
256 let c = Rect::new(200.0, 200.0, 50.0, 50.0);
257 assert!(a.overlaps(b));
258 assert!(!a.overlaps(c));
259 }
260
261 #[test]
262 fn test_intersection() {
263 let a = Rect::new(0.0, 0.0, 100.0, 100.0);
264 let b = Rect::new(50.0, 50.0, 100.0, 100.0);
265 let i = a.intersection(b).unwrap();
266 assert_eq!(i, Rect::new(50.0, 50.0, 50.0, 50.0));
267 }
268}