Skip to main content

speedy2d/
shape.rs

1/*
2 *  Copyright 2021 QuantumBadger
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17use num_traits::Zero;
18
19use crate::dimen::{Vec2, Vector2};
20use crate::numeric::{max, min, PrimitiveZero};
21
22/// A struct representing an axis-aligned rectangle. Two points are stored: the
23/// top left vertex, and the bottom right vertex.
24///
25/// Alias for a rectangle with u32 coordinates.
26pub type URect = Rectangle<u32>;
27
28/// A struct representing an axis-aligned rectangle. Two points are stored: the
29/// top left vertex, and the bottom right vertex.
30///
31/// Alias for a rectangle with i32 coordinates.
32pub type IRect = Rectangle<i32>;
33
34/// A struct representing an axis-aligned rectangle. Two points are stored: the
35/// top left vertex, and the bottom right vertex.
36///
37/// Alias for a rectangle with f32 coordinates.
38pub type Rect = Rectangle<f32>;
39
40/// A struct representing an axis-aligned rectangle. Two points are stored: the
41/// top left vertex, and the bottom right vertex.
42#[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    /// Constructs a new `Rectangle`. The top left vertex must be above and to
61    /// the left of the bottom right vertex.
62    #[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    /// Constructs a new `Rectangle`. The top left vertex must be above and to
72    /// the left of the bottom right vertex.
73    #[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    /// Returns a reference to the top left vertex.
83    #[inline]
84    pub const fn top_left(&self) -> &Vector2<T>
85    {
86        &self.top_left
87    }
88
89    /// Returns a reference to the bottom right vertex.
90    #[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    /// Returns a new `RoundedRectangle` which has the same sizes of `Self` and
100    /// a radius of T
101    #[inline]
102    pub fn rounded(&self, radius: T) -> RoundedRectangle<T>
103    {
104        RoundedRectangle::from_rectangle(self.clone(), radius)
105    }
106    /// Returns a vector representing the top right vertex.
107    #[inline]
108    pub fn top_right(&self) -> Vector2<T>
109    {
110        Vector2::new(self.bottom_right.x, self.top_left.y)
111    }
112
113    /// Returns a vector representing the bottom left vertex.
114    #[inline]
115    pub fn bottom_left(&self) -> Vector2<T>
116    {
117        Vector2::new(self.top_left.x, self.bottom_right.y)
118    }
119
120    /// Returns the x value of the left border
121    #[inline]
122    pub fn left(&self) -> T
123    {
124        self.top_left.x
125    }
126
127    /// Returns the x value of the right border
128    #[inline]
129    pub fn right(&self) -> T
130    {
131        self.bottom_right.x
132    }
133
134    /// Returns the y value of the top border
135    #[inline]
136    pub fn top(&self) -> T
137    {
138        self.top_left.y
139    }
140
141    /// Returns the y value of the bottom border
142    #[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    /// returns a `Rectangle` representing the inner rectangle of this rounded
152    /// rectangle.
153    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    /// Returns the width of the rectangle.
165    #[inline]
166    pub fn width(&self) -> T
167    {
168        self.bottom_right.x - self.top_left.x
169    }
170
171    /// Returns the height of the rectangle.
172    #[inline]
173    pub fn height(&self) -> T
174    {
175        self.bottom_right.y - self.top_left.y
176    }
177
178    /// Returns a `Vector2` containing the width and height of the rectangle.
179    #[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    /// Returns true if the specified point is inside this rectangle. This is
189    /// inclusive of the top and left coordinates, and exclusive of the bottom
190    /// and right coordinates.
191    #[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    /// Finds the intersection of two rectangles -- in other words, the area
205    /// that is common to both of them.
206    ///
207    /// If there is no common area between the two rectangles, then this
208    /// function will return `None`.
209    #[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    /// A constant representing a rectangle with position (0, 0) and zero area.
235    /// Each component is set to zero.
236    pub const ZERO: Rectangle<T> = Rectangle::new(Vector2::ZERO, Vector2::ZERO);
237}
238
239impl<T: PartialEq> Rectangle<T>
240{
241    /// Returns `true` if the rectangle has zero area.
242    #[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    /// Returns `true` if the rectangle has an area greater than zero.
252    #[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    /// Returns a new rectangle, whose vertices are offset relative to the
264    /// current rectangle by the specified amount. This is equivalent to
265    /// adding the specified vector to each vertex.
266    #[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    /// Returns a new rectangle, whose vertices are negatively offset relative
279    /// to the current rectangle by the specified amount. This is equivalent
280    /// to subtracting the specified vector to each vertex.
281    #[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    /// Returns a new rectangle where the coordinates have been cast to `f32`
300    /// values, using the `as` operator.
301    #[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    /// Returns a new rectangle where the coordinates have been cast to `f32`
312    /// values, using the `as` operator.
313    #[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/// A struct representing a polygon.
322#[derive(Debug, Clone)]
323pub struct Polygon
324{
325    pub(crate) triangles: Vec<[Vec2; 3]>
326}
327
328impl Polygon
329{
330    /// Generate a new polygon given points that describe it's outline.
331    ///
332    /// The points must be in either clockwise or couter-clockwise order.
333    pub fn new<Point: Into<Vec2> + Copy>(vertices: &[Point]) -> Self
334    {
335        // We have to flatten the vertices in order for
336        // [earcutr](https://github.com/frewsxcv/earcutr/) to accept it.
337        // In the future, we can add a triangulation algorithm directly into Speed2D if
338        // performance is an issue, but for now, this is simpler and easier
339        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
406///////////////////////////////////
407
408/// A struct representing an axis-aligned rounded rectangle. Two points and an
409/// 'u32' are stored: the top left vertex, the bottom right vertex and the
410/// radius of the rounded corners.
411///
412/// Alias for a rectangle with u32 coordinates.
413pub type URoundRect = RoundedRectangle<u32>;
414
415/// A struct representing an axis-aligned rounded rectangle. Two points and an
416/// 'i32' are stored: the top left vertex, the bottom right vertex and the
417/// radius of the rounded corners.
418///
419/// Alias for a rectangle with i32 coordinates.
420pub type IRoundRect = RoundedRectangle<i32>;
421
422/// A struct representing an axis-aligned rounded rectangle. Two points and an
423/// 'f32' are stored: the top left vertex, the bottom right vertex and the
424/// radius of the rounded corners.
425///
426/// Alias for a rectangle with f32 coordinates.
427pub type RoundRect = RoundedRectangle<f32>;
428
429/// A struct representing an axis-aligned rounded rectangle. Two points and a
430/// value of type 'T' are stored: the top left vertex, the bottom right vertex
431/// and the radius of the rounded corners.
432#[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    /// Constructs a new `RoundedRectangle`. The top left vertex must be above
451    /// and to the left of the bottom right vertex. A negative radius won't be
452    /// checked. A big radius (larger than half the width or height)
453    /// might produce unexpected behavior but it won't be checked.
454    #[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    /// Constructs a new `RoundedRectangle`. The top left vertex must be above
464    /// and to the left of the bottom right vertex. A negative radius won't be
465    /// checked. A big radius (larger than half the width or height)
466    /// might produce unexpected behavior but it won't be checked.
467    ///
468    /// Note: a negative radius won't be checked at runtime.
469    #[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    /// Constructs a new `RoundedRectangle` from a `Rectangle` and a radius.
479    /// A negative radius won't be checked.
480    /// A big radius (larger than half the width or height) might produce
481    /// unexpected behavior but it won't be checked.
482    #[inline]
483    pub fn from_rectangle(rect: Rectangle<T>, radius: T) -> Self
484    {
485        RoundedRectangle { rect, radius }
486    }
487
488    /// Returns a reference to the top left vertex.
489    #[inline]
490    pub const fn top_left(&self) -> &Vector2<T>
491    {
492        &self.rect.top_left
493    }
494
495    /// Returns a reference to the bottom right vertex.
496    #[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    /// Returns a vector representing the top right vertex.
506    #[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    /// Returns a vector representing the bottom left vertex.
513    #[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    /// Returns the radius of the rounded corners.
520    #[inline]
521    pub fn radius(&self) -> T
522    {
523        self.radius
524    }
525
526    /// Returns the x value of the left border
527    #[inline]
528    pub fn left(&self) -> T
529    {
530        self.rect.top_left.x
531    }
532
533    /// Returns the x value of the right border
534    #[inline]
535    pub fn right(&self) -> T
536    {
537        self.rect.bottom_right.x
538    }
539
540    /// Returns the y value of the top border
541    #[inline]
542    pub fn top(&self) -> T
543    {
544        self.rect.top_left.y
545    }
546
547    /// Returns the y value of the bottom border
548    #[inline]
549    pub fn bottom(&self) -> T
550    {
551        self.rect.bottom_right.y
552    }
553
554    /// Returns a `Rectangle` representing the rectangle that encloses this
555    /// rounded rectangle.
556    #[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    /// Returns the width of the rounded rectangle.
566    #[inline]
567    pub fn width(&self) -> T
568    {
569        self.rect.bottom_right.x - self.rect.top_left.x
570    }
571
572    /// Returns the height of the rounded rectangle.
573    #[inline]
574    pub fn height(&self) -> T
575    {
576        self.rect.bottom_right.y - self.rect.top_left.y
577    }
578
579    /// Returns a `Vector2` containing the width and height of the rounded
580    /// rectangle.
581    #[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    /// Returns true if the specified point is inside this rounded rectangle.
601    /// Note: this is always inclusive, in contrast to the `contains` method
602    /// of `Rect` which is sometimes exclusive.
603    #[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        //get distance from the 4 angles of the inner rectangle.
617        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    /// Returns `true` if the rectangle containing this rounded rectangle has
637    /// zero area. (the radius is not taken into account)
638    #[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    /// Returns `true` if the rectangle containing this rounded rectangle has
648    /// positive area. (the radius is not taken into account)
649    #[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    /// Returns a new rounded rectangle, whose vertices are offset relative to
661    /// the current rounded rectangle by the specified amount. This is
662    /// equivalent to adding the specified vector to each vertex.
663    #[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    /// Returns a new rounded rectangle, whose vertices are negatively offset
680    /// relative to the current rectangle by the specified amount. This is
681    /// equivalent to subtracting the specified vector to each vertex.
682    #[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    /// Returns a new rounded rectangle where the coordinates and the radius
697    /// have been cast to `f32` values, using the `as` operator.
698    #[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    /// Returns a new rectangle where the coordinates have been cast to `f32`
713    /// values, using the `as` operator.
714    #[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}