1use num_traits::Zero;
18
19use crate::dimen::{Vec2, Vector2};
20use crate::numeric::{max, min, PrimitiveZero};
21
22pub type URect = Rectangle<u32>;
27
28pub type IRect = Rectangle<i32>;
33
34pub type Rect = Rectangle<f32>;
39
40#[derive(Debug, PartialEq, Eq, Clone)]
43#[repr(C)]
44pub struct Rectangle<T = f32>
45{
46 top_left: Vector2<T>,
47 bottom_right: Vector2<T>
48}
49
50impl<T> AsRef<Rectangle<T>> for Rectangle<T>
51{
52 fn as_ref(&self) -> &Self
53 {
54 self
55 }
56}
57
58impl<T> Rectangle<T>
59{
60 #[inline]
63 pub const fn new(top_left: Vector2<T>, bottom_right: Vector2<T>) -> Self
64 {
65 Rectangle {
66 top_left,
67 bottom_right
68 }
69 }
70
71 #[inline]
74 pub fn from_tuples(top_left: (T, T), bottom_right: (T, T)) -> Self
75 {
76 Rectangle {
77 top_left: Vector2::new(top_left.0, top_left.1),
78 bottom_right: Vector2::new(bottom_right.0, bottom_right.1)
79 }
80 }
81
82 #[inline]
84 pub const fn top_left(&self) -> &Vector2<T>
85 {
86 &self.top_left
87 }
88
89 #[inline]
91 pub const fn bottom_right(&self) -> &Vector2<T>
92 {
93 &self.bottom_right
94 }
95}
96
97impl<T: Copy> Rectangle<T>
98{
99 #[inline]
102 pub fn rounded(&self, radius: T) -> RoundedRectangle<T>
103 {
104 RoundedRectangle::from_rectangle(self.clone(), radius)
105 }
106 #[inline]
108 pub fn top_right(&self) -> Vector2<T>
109 {
110 Vector2::new(self.bottom_right.x, self.top_left.y)
111 }
112
113 #[inline]
115 pub fn bottom_left(&self) -> Vector2<T>
116 {
117 Vector2::new(self.top_left.x, self.bottom_right.y)
118 }
119
120 #[inline]
122 pub fn left(&self) -> T
123 {
124 self.top_left.x
125 }
126
127 #[inline]
129 pub fn right(&self) -> T
130 {
131 self.bottom_right.x
132 }
133
134 #[inline]
136 pub fn top(&self) -> T
137 {
138 self.top_left.y
139 }
140
141 #[inline]
143 pub fn bottom(&self) -> T
144 {
145 self.bottom_right.y
146 }
147}
148
149impl<T: Copy + std::ops::Neg<Output = T> + std::ops::Add<Output = T>> RoundedRectangle<T>
150{
151 pub fn inner(&self) -> Rectangle<T>
154 {
155 Rectangle::new(
156 *self.top_left() + Vector2::new(self.radius, self.radius),
157 self.bottom_right() + Vector2::new(-self.radius, -self.radius)
158 )
159 }
160}
161
162impl<T: std::ops::Sub<Output = T> + Copy> Rectangle<T>
163{
164 #[inline]
166 pub fn width(&self) -> T
167 {
168 self.bottom_right.x - self.top_left.x
169 }
170
171 #[inline]
173 pub fn height(&self) -> T
174 {
175 self.bottom_right.y - self.top_left.y
176 }
177
178 #[inline]
180 pub fn size(&self) -> Vector2<T>
181 {
182 Vector2::new(self.width(), self.height())
183 }
184}
185
186impl<T: std::cmp::PartialOrd<T> + Copy> Rectangle<T>
187{
188 #[inline]
192 #[must_use]
193 pub fn contains(&self, point: Vector2<T>) -> bool
194 {
195 point.x >= self.top_left.x
196 && point.y >= self.top_left.y
197 && point.x < self.bottom_right.x
198 && point.y < self.bottom_right.y
199 }
200}
201
202impl<T: std::cmp::PartialOrd + Copy> Rectangle<T>
203{
204 #[inline]
210 #[must_use]
211 pub fn intersect(&self, other: &Self) -> Option<Self>
212 {
213 let result = Self {
214 top_left: Vector2::new(
215 max(self.top_left.x, other.top_left.x),
216 max(self.top_left.y, other.top_left.y)
217 ),
218 bottom_right: Vector2::new(
219 min(self.bottom_right.x, other.bottom_right.x),
220 min(self.bottom_right.y, other.bottom_right.y)
221 )
222 };
223
224 if result.is_positive_area() {
225 Some(result)
226 } else {
227 None
228 }
229 }
230}
231
232impl<T: PrimitiveZero> Rectangle<T>
233{
234 pub const ZERO: Rectangle<T> = Rectangle::new(Vector2::ZERO, Vector2::ZERO);
237}
238
239impl<T: PartialEq> Rectangle<T>
240{
241 #[inline]
243 pub fn is_zero_area(&self) -> bool
244 {
245 self.top_left.x == self.bottom_right.x || self.top_left.y == self.bottom_right.y
246 }
247}
248
249impl<T: std::cmp::PartialOrd> Rectangle<T>
250{
251 #[inline]
253 pub fn is_positive_area(&self) -> bool
254 {
255 self.top_left.x < self.bottom_right.x && self.top_left.y < self.bottom_right.y
256 }
257}
258
259impl<T: Copy> Rectangle<T>
260where
261 Vector2<T>: std::ops::Add<Output = Vector2<T>>
262{
263 #[inline]
267 pub fn with_offset(&self, offset: impl Into<Vector2<T>>) -> Self
268 {
269 let offset = offset.into();
270 Rectangle::new(self.top_left + offset, self.bottom_right + offset)
271 }
272}
273
274impl<T: Copy> Rectangle<T>
275where
276 Vector2<T>: std::ops::Sub<Output = Vector2<T>>
277{
278 #[inline]
282 pub fn with_negative_offset(&self, offset: impl Into<Vector2<T>>) -> Self
283 {
284 let offset = offset.into();
285 Rectangle::new(self.top_left - offset, self.bottom_right - offset)
286 }
287}
288
289impl<T> From<rusttype::Rect<T>> for Rectangle<T>
290{
291 fn from(rect: rusttype::Rect<T>) -> Self
292 {
293 Rectangle::new(Vector2::from(rect.min), Vector2::from(rect.max))
294 }
295}
296
297impl<T: num_traits::AsPrimitive<f32>> Rectangle<T>
298{
299 #[inline]
302 #[must_use]
303 pub fn into_f32(self) -> Rectangle<f32>
304 {
305 Rectangle::new(self.top_left.into_f32(), self.bottom_right.into_f32())
306 }
307}
308
309impl<T: num_traits::AsPrimitive<f32> + Copy> Rectangle<T>
310{
311 #[inline]
314 #[must_use]
315 pub fn as_f32(&self) -> Rectangle<f32>
316 {
317 Rectangle::new(self.top_left.into_f32(), self.bottom_right.into_f32())
318 }
319}
320
321#[derive(Debug, Clone)]
323pub struct Polygon
324{
325 pub(crate) triangles: Vec<[Vec2; 3]>
326}
327
328impl Polygon
329{
330 pub fn new<Point: Into<Vec2> + Copy>(vertices: &[Point]) -> Self
334 {
335 let mut flattened = Vec::with_capacity(vertices.len() * 2);
340
341 for vertex in vertices {
342 let vertex: Vec2 = (*vertex).into();
343
344 flattened.push(vertex.x);
345 flattened.push(vertex.y);
346 }
347
348 let mut triangulation =
349 earcutr::earcut(&flattened, &Vec::new(), 2).unwrap_or_else(|err| {
350 log::error!("Failed to triangulate polygon: {:?}", err);
351 Vec::new()
352 });
353 let mut triangles = Vec::with_capacity(triangulation.len() / 3);
354
355 while !triangulation.is_empty() {
356 triangles.push([
357 vertices[triangulation.pop().unwrap()].into(),
358 vertices[triangulation.pop().unwrap()].into(),
359 vertices[triangulation.pop().unwrap()].into()
360 ])
361 }
362
363 Polygon { triangles }
364 }
365}
366
367#[cfg(test)]
368mod test
369{
370 use crate::shape::URect;
371
372 #[test]
373 pub fn test_intersect_1()
374 {
375 let r1 = URect::from_tuples((100, 100), (200, 200));
376 let r2 = URect::from_tuples((100, 300), (200, 400));
377 let r3 = URect::from_tuples((125, 50), (175, 500));
378
379 assert_eq!(None, r1.intersect(&r2));
380
381 assert_eq!(
382 Some(URect::from_tuples((125, 100), (175, 200))),
383 r1.intersect(&r3)
384 );
385
386 assert_eq!(
387 Some(URect::from_tuples((125, 300), (175, 400))),
388 r2.intersect(&r3)
389 );
390
391 assert_eq!(Some(r1.clone()), r1.intersect(&r1));
392 assert_eq!(Some(r2.clone()), r2.intersect(&r2));
393 assert_eq!(Some(r3.clone()), r3.intersect(&r3));
394 }
395
396 #[test]
397 pub fn test_intersect_2()
398 {
399 let r1 = URect::from_tuples((100, 100), (200, 200));
400 let r2 = URect::from_tuples((100, 200), (200, 300));
401
402 assert_eq!(None, r1.intersect(&r2));
403 }
404}
405
406pub type URoundRect = RoundedRectangle<u32>;
414
415pub type IRoundRect = RoundedRectangle<i32>;
421
422pub type RoundRect = RoundedRectangle<f32>;
428
429#[derive(Debug, PartialEq, Eq, Clone)]
433#[repr(C)]
434pub struct RoundedRectangle<T = f32>
435{
436 rect: Rectangle<T>,
437 radius: T
438}
439
440impl<T> AsRef<RoundedRectangle<T>> for RoundedRectangle<T>
441{
442 fn as_ref(&self) -> &Self
443 {
444 self
445 }
446}
447
448impl<T> RoundedRectangle<T>
449{
450 #[inline]
455 pub const fn new(top_left: Vector2<T>, bottom_right: Vector2<T>, radius: T) -> Self
456 {
457 RoundedRectangle {
458 rect: Rectangle::new(top_left, bottom_right),
459 radius
460 }
461 }
462
463 #[inline]
470 pub fn from_tuples(top_left: (T, T), bottom_right: (T, T), radius: T) -> Self
471 {
472 RoundedRectangle {
473 rect: Rectangle::from_tuples(top_left, bottom_right),
474 radius
475 }
476 }
477
478 #[inline]
483 pub fn from_rectangle(rect: Rectangle<T>, radius: T) -> Self
484 {
485 RoundedRectangle { rect, radius }
486 }
487
488 #[inline]
490 pub const fn top_left(&self) -> &Vector2<T>
491 {
492 &self.rect.top_left
493 }
494
495 #[inline]
497 pub const fn bottom_right(&self) -> &Vector2<T>
498 {
499 &self.rect.bottom_right
500 }
501}
502
503impl<T: Copy> RoundedRectangle<T>
504{
505 #[inline]
507 pub fn top_right(&self) -> Vector2<T>
508 {
509 Vector2::new(self.rect.bottom_right.x, self.rect.top_left.y)
510 }
511
512 #[inline]
514 pub fn bottom_left(&self) -> Vector2<T>
515 {
516 Vector2::new(self.rect.top_left.x, self.rect.bottom_right.y)
517 }
518
519 #[inline]
521 pub fn radius(&self) -> T
522 {
523 self.radius
524 }
525
526 #[inline]
528 pub fn left(&self) -> T
529 {
530 self.rect.top_left.x
531 }
532
533 #[inline]
535 pub fn right(&self) -> T
536 {
537 self.rect.bottom_right.x
538 }
539
540 #[inline]
542 pub fn top(&self) -> T
543 {
544 self.rect.top_left.y
545 }
546
547 #[inline]
549 pub fn bottom(&self) -> T
550 {
551 self.rect.bottom_right.y
552 }
553
554 #[inline]
557 pub fn as_rectangle(&self) -> &Rectangle<T>
558 {
559 &self.rect
560 }
561}
562
563impl<T: std::ops::Sub<Output = T> + Copy> RoundedRectangle<T>
564{
565 #[inline]
567 pub fn width(&self) -> T
568 {
569 self.rect.bottom_right.x - self.rect.top_left.x
570 }
571
572 #[inline]
574 pub fn height(&self) -> T
575 {
576 self.rect.bottom_right.y - self.rect.top_left.y
577 }
578
579 #[inline]
582 pub fn size(&self) -> Vector2<T>
583 {
584 Vector2::new(self.width(), self.height())
585 }
586}
587
588impl<T> RoundedRectangle<T>
589where
590 T: num_traits::AsPrimitive<f32>
591 + std::cmp::PartialOrd
592 + std::ops::Add<Output = T>
593 + std::ops::Sub<Output = T>
594 + std::ops::Mul<Output = T>
595 + std::ops::Neg<Output = T>
596 + std::ops::Div<Output = f32>
597 + std::ops::Div<f32, Output = T>
598 + Zero
599{
600 #[must_use]
604 pub fn contains(&self, point: Vector2<T>) -> bool
605 {
606 if !self.rect.contains(point) {
607 return false;
608 }
609 let inner = self.inner();
610 if inner.contains(point) {
611 return true;
612 }
613
614 let radius_squared = self.radius * self.radius;
615
616 let dx = max(
618 max(inner.left() - point.x, point.x - inner.right()),
619 T::zero()
620 );
621 let dy = max(
622 max(inner.top() - point.y, point.y - inner.bottom()),
623 T::zero()
624 );
625
626 if dx * dx + dy * dy <= radius_squared {
627 return true;
628 }
629
630 false
631 }
632}
633
634impl<T: PartialEq> RoundedRectangle<T>
635{
636 #[inline]
639 pub fn is_zero_area(&self) -> bool
640 {
641 self.rect.is_zero_area()
642 }
643}
644
645impl<T: std::cmp::PartialOrd> RoundedRectangle<T>
646{
647 #[inline]
650 pub fn is_positive_area(&self) -> bool
651 {
652 self.rect.is_positive_area()
653 }
654}
655
656impl<T: Copy> RoundedRectangle<T>
657where
658 Vector2<T>: std::ops::Add<Output = Vector2<T>>
659{
660 #[inline]
664 pub fn with_offset(&self, offset: impl Into<Vector2<T>>) -> Self
665 {
666 let offset = offset.into();
667 RoundedRectangle::new(
668 self.rect.top_left + offset,
669 self.rect.bottom_right + offset,
670 self.radius
671 )
672 }
673}
674
675impl<T: Copy> RoundedRectangle<T>
676where
677 Vector2<T>: std::ops::Sub<Output = Vector2<T>>
678{
679 #[inline]
683 pub fn with_negative_offset(&self, offset: impl Into<Vector2<T>>) -> Self
684 {
685 let offset = offset.into();
686 RoundedRectangle::new(
687 self.rect.top_left - offset,
688 self.rect.bottom_right - offset,
689 self.radius
690 )
691 }
692}
693
694impl<T: num_traits::AsPrimitive<f32>> RoundedRectangle<T>
695{
696 #[inline]
699 #[must_use]
700 pub fn into_f32(self) -> RoundedRectangle<f32>
701 {
702 RoundedRectangle::new(
703 self.rect.top_left.into_f32(),
704 self.rect.bottom_right.into_f32(),
705 self.radius.as_()
706 )
707 }
708}
709
710impl<T: num_traits::AsPrimitive<f32> + Copy> RoundedRectangle<T>
711{
712 #[inline]
715 #[must_use]
716 pub fn as_f32(&self) -> RoundedRectangle<f32>
717 {
718 RoundedRectangle::new(
719 self.rect.top_left.into_f32(),
720 self.rect.bottom_right.into_f32(),
721 self.radius.as_()
722 )
723 }
724}