1use std::fmt;
4use std::ops::{Div, DivAssign, Mul, MulAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities;
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::{Matrix, Point, Size, Thickness, Vector};
11
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Rect {
15 pub x: f64,
17 pub y: f64,
19 pub width: f64,
21 pub height: f64,
23}
24
25impl Rect {
26 #[inline]
28 pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
29 Self {
30 x,
31 y,
32 width,
33 height,
34 }
35 }
36
37 #[inline]
39 pub const fn from_size(size: Size) -> Self {
40 Self::new(0.0, 0.0, size.width, size.height)
41 }
42
43 #[inline]
45 pub const fn from_position_size(position: Point, size: Size) -> Self {
46 Self::new(position.x, position.y, size.width, size.height)
47 }
48
49 #[inline]
51 pub fn from_points(top_left: Point, bottom_right: Point) -> Self {
52 Self::new(
53 top_left.x,
54 top_left.y,
55 bottom_right.x - top_left.x,
56 bottom_right.y - top_left.y,
57 )
58 }
59
60 #[inline]
62 pub const fn position(&self) -> Point {
63 Point::new(self.x, self.y)
64 }
65
66 #[inline]
68 pub const fn size(&self) -> Size {
69 Size::new(self.width, self.height)
70 }
71
72 #[inline]
74 pub fn right(&self) -> f64 {
75 self.x + self.width
76 }
77
78 #[inline]
80 pub fn bottom(&self) -> f64 {
81 self.y + self.height
82 }
83
84 #[inline]
86 pub const fn left(&self) -> f64 {
87 self.x
88 }
89
90 #[inline]
92 pub const fn top(&self) -> f64 {
93 self.y
94 }
95
96 #[inline]
98 pub const fn top_left(&self) -> Point {
99 Point::new(self.x, self.y)
100 }
101
102 #[inline]
104 pub fn top_right(&self) -> Point {
105 Point::new(self.right(), self.y)
106 }
107
108 #[inline]
110 pub fn bottom_left(&self) -> Point {
111 Point::new(self.x, self.bottom())
112 }
113
114 #[inline]
116 pub fn bottom_right(&self) -> Point {
117 Point::new(self.right(), self.bottom())
118 }
119
120 #[inline]
122 pub fn center(&self) -> Point {
123 Point::new(self.x + (self.width / 2.0), self.y + (self.height / 2.0))
124 }
125
126 #[inline]
128 pub fn contains(&self, p: Point) -> bool {
129 p.x >= self.x && p.x <= self.x + self.width && p.y >= self.y && p.y <= self.y + self.height
130 }
131
132 #[inline]
135 pub fn contains_exclusive(&self, p: Point) -> bool {
136 p.x >= self.x && p.x < self.x + self.width && p.y >= self.y && p.y < self.y + self.height
137 }
138
139 #[inline]
141 pub fn contains_rect(&self, r: Rect) -> bool {
142 self.contains(r.top_left()) && self.contains(r.bottom_right())
143 }
144
145 #[inline]
147 pub fn center_rect(&self, rect: Rect) -> Rect {
148 Rect::new(
149 self.x + ((self.width - rect.width) / 2.0),
150 self.y + ((self.height - rect.height) / 2.0),
151 rect.width,
152 rect.height,
153 )
154 }
155
156 #[inline]
158 pub fn inflate(&self, thickness: f64) -> Rect {
159 self.inflate_thickness(Thickness::uniform(thickness))
160 }
161
162 #[inline]
164 pub fn inflate_thickness(&self, thickness: Thickness) -> Rect {
165 Rect::from_position_size(
166 Point::new(self.x - thickness.left, self.y - thickness.top),
167 self.size().inflate(thickness),
168 )
169 }
170
171 #[inline]
173 pub fn deflate(&self, thickness: f64) -> Rect {
174 self.deflate_thickness(Thickness::uniform(thickness))
175 }
176
177 #[inline]
180 pub fn deflate_thickness(&self, thickness: Thickness) -> Rect {
181 Rect::from_position_size(
182 Point::new(self.x + thickness.left, self.y + thickness.top),
183 self.size().deflate(thickness),
184 )
185 }
186
187 #[inline]
190 pub fn equals(&self, other: Rect) -> bool {
191 self.x == other.x
192 && self.y == other.y
193 && self.width == other.width
194 && self.height == other.height
195 }
196
197 pub fn intersect(&self, rect: Rect) -> Rect {
200 let new_left = if rect.x > self.x { rect.x } else { self.x };
201 let new_top = if rect.y > self.y { rect.y } else { self.y };
202 let new_right = if rect.right() < self.right() {
203 rect.right()
204 } else {
205 self.right()
206 };
207 let new_bottom = if rect.bottom() < self.bottom() {
208 rect.bottom()
209 } else {
210 self.bottom()
211 };
212
213 if (new_right > new_left) && (new_bottom > new_top) {
214 Rect::new(
215 new_left,
216 new_top,
217 new_right - new_left,
218 new_bottom - new_top,
219 )
220 } else {
221 Rect::default()
222 }
223 }
224
225 #[inline]
227 pub fn intersects(&self, rect: Rect) -> bool {
228 (rect.x < self.right())
229 && (self.x < rect.right())
230 && (rect.y < self.bottom())
231 && (self.y < rect.bottom())
232 }
233
234 pub fn transform_to_aabb(&self, matrix: Matrix) -> Rect {
236 let points = [
237 self.top_left().transform(matrix),
238 self.top_right().transform(matrix),
239 self.bottom_right().transform(matrix),
240 self.bottom_left().transform(matrix),
241 ];
242
243 let mut left = f64::MAX;
244 let mut right = f64::MIN;
245 let mut top = f64::MAX;
246 let mut bottom = f64::MIN;
247
248 for p in points {
249 if p.x < left {
250 left = p.x;
251 }
252 if p.x > right {
253 right = p.x;
254 }
255 if p.y < top {
256 top = p.y;
257 }
258 if p.y > bottom {
259 bottom = p.y;
260 }
261 }
262
263 Rect::from_points(Point::new(left, top), Point::new(right, bottom))
264 }
265
266 #[inline]
268 pub fn translate(&self, offset: Vector) -> Rect {
269 Rect::from_position_size(self.position() + offset, self.size())
270 }
271
272 pub fn normalize(&self) -> Rect {
276 let mut rect = *self;
277
278 if rect.right().is_nan()
279 || rect.bottom().is_nan()
280 || rect.x.is_nan()
281 || rect.y.is_nan()
282 || self.height.is_nan()
283 || self.width.is_nan()
284 {
285 return Rect::default();
286 }
287
288 if rect.width < 0.0 {
289 let x = self.x + self.width;
290 let width = self.x - x;
291
292 rect = rect.with_x(x).with_width(width);
293 }
294
295 if rect.height < 0.0 {
296 let y = self.y + self.height;
297 let height = self.y - y;
298
299 rect = rect.with_y(y).with_height(height);
300 }
301
302 rect
303 }
304
305 pub fn union(&self, rect: Rect) -> Rect {
308 if self.width == 0.0 && self.height == 0.0 {
309 rect
310 } else if rect.width == 0.0 && rect.height == 0.0 {
311 *self
312 } else {
313 let x1 = math_utilities::min(self.x, rect.x);
314 let x2 = math_utilities::max(self.right(), rect.right());
315 let y1 = math_utilities::min(self.y, rect.y);
316 let y2 = math_utilities::max(self.bottom(), rect.bottom());
317
318 Rect::from_points(Point::new(x1, y1), Point::new(x2, y2))
319 }
320 }
321
322 #[inline]
324 pub fn union_optional(left: Option<Rect>, right: Option<Rect>) -> Option<Rect> {
325 match (left, right) {
326 (None, right) => right,
327 (left, None) => left,
328 (Some(left), Some(right)) => Some(left.union(right)),
329 }
330 }
331
332 #[inline]
334 pub const fn with_x(&self, x: f64) -> Rect {
335 Rect::new(x, self.y, self.width, self.height)
336 }
337
338 #[inline]
340 pub const fn with_y(&self, y: f64) -> Rect {
341 Rect::new(self.x, y, self.width, self.height)
342 }
343
344 #[inline]
346 pub const fn with_width(&self, width: f64) -> Rect {
347 Rect::new(self.x, self.y, width, self.height)
348 }
349
350 #[inline]
352 pub const fn with_height(&self, height: f64) -> Rect {
353 Rect::new(self.x, self.y, self.width, height)
354 }
355
356 pub fn parse(s: &str) -> Result<Self, FormatError> {
358 SpanStringTokenizer::with_message(s, "Invalid Rect.").scope(|t| {
359 Ok(Rect::new(
360 t.read_double()?,
361 t.read_double()?,
362 t.read_double()?,
363 t.read_double()?,
364 ))
365 })
366 }
367
368 #[inline]
371 pub fn is_empty(&self) -> bool {
372 *self == Rect::default()
373 }
374}
375
376impl Mul<Vector> for Rect {
378 type Output = Rect;
379 #[inline]
380 fn mul(self, scale: Vector) -> Rect {
381 Rect::new(
382 self.x * scale.x,
383 self.y * scale.y,
384 self.width * scale.x,
385 self.height * scale.y,
386 )
387 }
388}
389
390impl Mul<f64> for Rect {
392 type Output = Rect;
393 #[inline]
394 fn mul(self, scale: f64) -> Rect {
395 Rect::new(
396 self.x * scale,
397 self.y * scale,
398 self.width * scale,
399 self.height * scale,
400 )
401 }
402}
403
404impl Div<Vector> for Rect {
406 type Output = Rect;
407 #[inline]
408 fn div(self, scale: Vector) -> Rect {
409 Rect::new(
410 self.x / scale.x,
411 self.y / scale.y,
412 self.width / scale.x,
413 self.height / scale.y,
414 )
415 }
416}
417
418impl MulAssign<Vector> for Rect {
419 #[inline]
420 fn mul_assign(&mut self, scale: Vector) {
421 *self = *self * scale;
422 }
423}
424
425impl MulAssign<f64> for Rect {
426 #[inline]
427 fn mul_assign(&mut self, scale: f64) {
428 *self = *self * scale;
429 }
430}
431
432impl DivAssign<Vector> for Rect {
433 #[inline]
434 fn div_assign(&mut self, scale: Vector) {
435 *self = *self / scale;
436 }
437}
438
439impl FromStr for Rect {
440 type Err = FormatError;
441 #[inline]
442 fn from_str(s: &str) -> Result<Self, FormatError> {
443 Rect::parse(s)
444 }
445}
446
447impl fmt::Display for Rect {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 write!(
450 f,
451 "{}, {}, {}, {}",
452 InvariantF64(self.x),
453 InvariantF64(self.y),
454 InvariantF64(self.width),
455 InvariantF64(self.height)
456 )
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn union_should_return_correct_value_for_intersecting_rects() {
466 let result = Rect::new(0.0, 0.0, 100.0, 100.0).union(Rect::new(50.0, 50.0, 100.0, 100.0));
467
468 assert_eq!(Rect::new(0.0, 0.0, 150.0, 150.0), result);
469 }
470
471 #[test]
472 fn union_should_return_correct_value_for_non_intersecting_rects() {
473 let result = Rect::new(0.0, 0.0, 100.0, 100.0).union(Rect::new(150.0, 150.0, 100.0, 100.0));
474
475 assert_eq!(Rect::new(0.0, 0.0, 250.0, 250.0), result);
476 }
477
478 #[test]
479 fn union_should_ignore_empty_this_rect() {
480 let result = Rect::new(0.0, 0.0, 0.0, 0.0).union(Rect::new(150.0, 150.0, 100.0, 100.0));
481
482 assert_eq!(Rect::new(150.0, 150.0, 100.0, 100.0), result);
483 }
484
485 #[test]
486 fn union_should_ignore_empty_other_rect() {
487 let result = Rect::new(0.0, 0.0, 100.0, 100.0).union(Rect::new(150.0, 150.0, 0.0, 0.0));
488
489 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result);
490 }
491
492 #[test]
493 fn normalize_should_reverse_negative_size() {
494 let result = Rect::from_points(Point::new(100.0, 100.0), Point::new(0.0, 0.0)).normalize();
495
496 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result);
497 }
498
499 #[test]
500 fn normalize_should_make_invalid_rects_empty() {
501 let result = Rect::new(
502 f64::NEG_INFINITY,
503 f64::INFINITY,
504 f64::INFINITY,
505 f64::INFINITY,
506 )
507 .normalize();
508
509 assert_eq!(Rect::default(), result);
510 }
511
512 #[test]
513 fn parse_parses() {
514 let rect = Rect::parse("1,2 3,-4").unwrap();
515 let expected = Rect::new(1.0, 2.0, 3.0, -4.0);
516 assert_eq!(expected, rect);
517 }
518
519 #[test]
522 fn edges_and_corners() {
523 let r = Rect::new(10.0, 20.0, 30.0, 40.0);
524 assert_eq!(r.right(), 40.0);
525 assert_eq!(r.bottom(), 60.0);
526 assert_eq!(r.left(), 10.0);
527 assert_eq!(r.top(), 20.0);
528 assert_eq!(r.top_right(), Point::new(40.0, 20.0));
529 assert_eq!(r.bottom_left(), Point::new(10.0, 60.0));
530 assert_eq!(r.bottom_right(), Point::new(40.0, 60.0));
531 assert_eq!(r.center(), Point::new(25.0, 40.0));
532 assert_eq!(r.position(), Point::new(10.0, 20.0));
533 assert_eq!(r.size(), Size::new(30.0, 40.0));
534 assert_eq!(
535 Rect::from_size(Size::new(1.0, 2.0)),
536 Rect::new(0.0, 0.0, 1.0, 2.0)
537 );
538 }
539
540 #[test]
541 fn contains_intersects_and_intersect() {
542 let r = Rect::new(0.0, 0.0, 10.0, 10.0);
543 assert!(r.contains(Point::new(10.0, 10.0)));
544 assert!(!r.contains_exclusive(Point::new(10.0, 10.0)));
545 assert!(r.contains_exclusive(Point::new(0.0, 0.0)));
546 assert!(r.contains_rect(Rect::new(1.0, 1.0, 9.0, 9.0)));
547 assert!(!r.contains_rect(Rect::new(1.0, 1.0, 10.0, 9.0)));
548 assert!(r.intersects(Rect::new(5.0, 5.0, 10.0, 10.0)));
549 assert!(!r.intersects(Rect::new(10.0, 0.0, 10.0, 10.0)));
550 assert_eq!(
551 r.intersect(Rect::new(5.0, 5.0, 10.0, 10.0)),
552 Rect::new(5.0, 5.0, 5.0, 5.0)
553 );
554 assert_eq!(
555 r.intersect(Rect::new(10.0, 0.0, 10.0, 10.0)),
556 Rect::default()
557 );
558 assert!(Rect::default().is_empty());
559 }
560
561 #[test]
562 fn inflate_deflate_translate_center_rect() {
563 let r = Rect::new(10.0, 10.0, 20.0, 20.0);
564 assert_eq!(r.inflate(5.0), Rect::new(5.0, 5.0, 30.0, 30.0));
565 assert_eq!(r.deflate(5.0), Rect::new(15.0, 15.0, 10.0, 10.0));
566 assert_eq!(r.deflate(15.0), Rect::new(25.0, 25.0, 0.0, 0.0));
567 assert_eq!(
568 r.inflate_thickness(Thickness::new(1.0, 2.0, 3.0, 4.0)),
569 Rect::new(9.0, 8.0, 24.0, 26.0)
570 );
571 assert_eq!(
572 r.translate(Vector::new(1.0, -1.0)),
573 Rect::new(11.0, 9.0, 20.0, 20.0)
574 );
575 assert_eq!(
576 r.center_rect(Rect::new(0.0, 0.0, 10.0, 10.0)),
577 Rect::new(15.0, 15.0, 10.0, 10.0)
578 );
579 }
580
581 #[test]
582 fn transform_to_aabb() {
583 let r = Rect::new(0.0, 0.0, 10.0, 20.0);
584 assert_eq!(
585 r.transform_to_aabb(Matrix::create_translation(5.0, 5.0)),
586 Rect::new(5.0, 5.0, 10.0, 20.0)
587 );
588 let rotated = r.transform_to_aabb(Matrix::create_rotation(std::f64::consts::FRAC_PI_2));
589 assert!((rotated.x - -20.0).abs() < 1e-9);
590 assert!(rotated.y.abs() < 1e-9);
591 assert!((rotated.width - 20.0).abs() < 1e-9);
592 assert!((rotated.height - 10.0).abs() < 1e-9);
593 }
594
595 #[test]
596 fn operators_display_and_union_optional() {
597 let r = Rect::new(1.0, 2.0, 3.0, 4.0);
598 assert_eq!(r * 2.0, Rect::new(2.0, 4.0, 6.0, 8.0));
599 assert_eq!(r * Vector::new(2.0, 3.0), Rect::new(2.0, 6.0, 6.0, 12.0));
600 assert_eq!(r / Vector::new(2.0, 2.0), Rect::new(0.5, 1.0, 1.5, 2.0));
601 assert_eq!(r.to_string(), "1, 2, 3, 4");
602 assert_eq!(Rect::parse("1,2,3").unwrap_err().message(), "Invalid Rect.");
603 assert_eq!(Rect::union_optional(None, Some(r)), Some(r));
604 assert_eq!(Rect::union_optional(Some(r), None), Some(r));
605 assert_eq!(Rect::union_optional(None, None), None);
606 }
607}