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 = earcutr::earcut(&flattened, &Vec::new(), 2);
349        let mut triangles = Vec::with_capacity(triangulation.len() / 3);
350
351        while !triangulation.is_empty() {
352            triangles.push([
353                vertices[triangulation.pop().unwrap()].into(),
354                vertices[triangulation.pop().unwrap()].into(),
355                vertices[triangulation.pop().unwrap()].into()
356            ])
357        }
358
359        Polygon { triangles }
360    }
361}
362
363#[cfg(test)]
364mod test
365{
366    use crate::shape::URect;
367
368    #[test]
369    pub fn test_intersect_1()
370    {
371        let r1 = URect::from_tuples((100, 100), (200, 200));
372        let r2 = URect::from_tuples((100, 300), (200, 400));
373        let r3 = URect::from_tuples((125, 50), (175, 500));
374
375        assert_eq!(None, r1.intersect(&r2));
376
377        assert_eq!(
378            Some(URect::from_tuples((125, 100), (175, 200))),
379            r1.intersect(&r3)
380        );
381
382        assert_eq!(
383            Some(URect::from_tuples((125, 300), (175, 400))),
384            r2.intersect(&r3)
385        );
386
387        assert_eq!(Some(r1.clone()), r1.intersect(&r1));
388        assert_eq!(Some(r2.clone()), r2.intersect(&r2));
389        assert_eq!(Some(r3.clone()), r3.intersect(&r3));
390    }
391
392    #[test]
393    pub fn test_intersect_2()
394    {
395        let r1 = URect::from_tuples((100, 100), (200, 200));
396        let r2 = URect::from_tuples((100, 200), (200, 300));
397
398        assert_eq!(None, r1.intersect(&r2));
399    }
400}
401
402///////////////////////////////////
403
404/// A struct representing an axis-aligned rounded rectangle. Two points and an
405/// 'u32' are stored: the top left vertex, the bottom right vertex and the
406/// radius of the rounded corners.
407///
408/// Alias for a rectangle with u32 coordinates.
409pub type URoundRect = RoundedRectangle<u32>;
410
411/// A struct representing an axis-aligned rounded rectangle. Two points and an
412/// 'i32' are stored: the top left vertex, the bottom right vertex and the
413/// radius of the rounded corners.
414///
415/// Alias for a rectangle with i32 coordinates.
416pub type IRoundRect = RoundedRectangle<i32>;
417
418/// A struct representing an axis-aligned rounded rectangle. Two points and an
419/// 'f32' are stored: the top left vertex, the bottom right vertex and the
420/// radius of the rounded corners.
421///
422/// Alias for a rectangle with f32 coordinates.
423pub type RoundRect = RoundedRectangle<f32>;
424
425/// A struct representing an axis-aligned rounded rectangle. Two points and a
426/// value of type 'T' are stored: the top left vertex, the bottom right vertex
427/// and the radius of the rounded corners.
428#[derive(Debug, PartialEq, Eq, Clone)]
429#[repr(C)]
430pub struct RoundedRectangle<T = f32>
431{
432    rect: Rectangle<T>,
433    radius: T
434}
435
436impl<T> AsRef<RoundedRectangle<T>> for RoundedRectangle<T>
437{
438    fn as_ref(&self) -> &Self
439    {
440        self
441    }
442}
443
444impl<T> RoundedRectangle<T>
445{
446    /// Constructs a new `RoundedRectangle`. The top left vertex must be above
447    /// and to the left of the bottom right vertex. A negative radius won't be
448    /// checked. A big radius (larger than half the width or height)
449    /// might produce unexpected behavior but it won't be checked.
450    #[inline]
451    pub const fn new(top_left: Vector2<T>, bottom_right: Vector2<T>, radius: T) -> Self
452    {
453        RoundedRectangle {
454            rect: Rectangle::new(top_left, bottom_right),
455            radius
456        }
457    }
458
459    /// Constructs a new `RoundedRectangle`. The top left vertex must be above
460    /// and to the left of the bottom right vertex. A negative radius won't be
461    /// checked. A big radius (larger than half the width or height)
462    /// might produce unexpected behavior but it won't be checked.
463    ///
464    /// Note: a negative radius won't be checked at runtime.
465    #[inline]
466    pub fn from_tuples(top_left: (T, T), bottom_right: (T, T), radius: T) -> Self
467    {
468        RoundedRectangle {
469            rect: Rectangle::from_tuples(top_left, bottom_right),
470            radius
471        }
472    }
473
474    /// Constructs a new `RoundedRectangle` from a `Rectangle` and a radius.
475    /// A negative radius won't be checked.
476    /// A big radius (larger than half the width or height) might produce
477    /// unexpected behavior but it won't be checked.
478    #[inline]
479    pub fn from_rectangle(rect: Rectangle<T>, radius: T) -> Self
480    {
481        RoundedRectangle { rect, radius }
482    }
483
484    /// Returns a reference to the top left vertex.
485    #[inline]
486    pub const fn top_left(&self) -> &Vector2<T>
487    {
488        &self.rect.top_left
489    }
490
491    /// Returns a reference to the bottom right vertex.
492    #[inline]
493    pub const fn bottom_right(&self) -> &Vector2<T>
494    {
495        &self.rect.bottom_right
496    }
497}
498
499impl<T: Copy> RoundedRectangle<T>
500{
501    /// Returns a vector representing the top right vertex.
502    #[inline]
503    pub fn top_right(&self) -> Vector2<T>
504    {
505        Vector2::new(self.rect.bottom_right.x, self.rect.top_left.y)
506    }
507
508    /// Returns a vector representing the bottom left vertex.
509    #[inline]
510    pub fn bottom_left(&self) -> Vector2<T>
511    {
512        Vector2::new(self.rect.top_left.x, self.rect.bottom_right.y)
513    }
514
515    /// Returns the radius of the rounded corners.
516    #[inline]
517    pub fn radius(&self) -> T
518    {
519        self.radius
520    }
521
522    /// Returns the x value of the left border
523    #[inline]
524    pub fn left(&self) -> T
525    {
526        self.rect.top_left.x
527    }
528
529    /// Returns the x value of the right border
530    #[inline]
531    pub fn right(&self) -> T
532    {
533        self.rect.bottom_right.x
534    }
535
536    /// Returns the y value of the top border
537    #[inline]
538    pub fn top(&self) -> T
539    {
540        self.rect.top_left.y
541    }
542
543    /// Returns the y value of the bottom border
544    #[inline]
545    pub fn bottom(&self) -> T
546    {
547        self.rect.bottom_right.y
548    }
549
550    /// Returns a `Rectangle` representing the rectangle that encloses this
551    /// rounded rectangle.
552    #[inline]
553    pub fn as_rectangle(&self) -> &Rectangle<T>
554    {
555        &self.rect
556    }
557}
558
559impl<T: std::ops::Sub<Output = T> + Copy> RoundedRectangle<T>
560{
561    /// Returns the width of the rounded rectangle.
562    #[inline]
563    pub fn width(&self) -> T
564    {
565        self.rect.bottom_right.x - self.rect.top_left.x
566    }
567
568    /// Returns the height of the rounded rectangle.
569    #[inline]
570    pub fn height(&self) -> T
571    {
572        self.rect.bottom_right.y - self.rect.top_left.y
573    }
574
575    /// Returns a `Vector2` containing the width and height of the rounded
576    /// rectangle.
577    #[inline]
578    pub fn size(&self) -> Vector2<T>
579    {
580        Vector2::new(self.width(), self.height())
581    }
582}
583
584impl<T> RoundedRectangle<T>
585where
586    T: num_traits::AsPrimitive<f32>
587        + std::cmp::PartialOrd
588        + std::ops::Add<Output = T>
589        + std::ops::Sub<Output = T>
590        + std::ops::Mul<Output = T>
591        + std::ops::Neg<Output = T>
592        + std::ops::Div<Output = f32>
593        + std::ops::Div<f32, Output = T>
594        + Zero
595{
596    /// Returns true if the specified point is inside this rounded rectangle.
597    /// Note: this is always inclusive, in contrast to the `contains` method
598    /// of `Rect` which is sometimes exclusive.
599    #[must_use]
600    pub fn contains(&self, point: Vector2<T>) -> bool
601    {
602        if !self.rect.contains(point) {
603            return false;
604        }
605        let inner = self.inner();
606        if inner.contains(point) {
607            return true;
608        }
609
610        let radius_squared = self.radius * self.radius;
611
612        //get distance from the 4 angles of the inner rectangle.
613        let dx = max(
614            max(inner.left() - point.x, point.x - inner.right()),
615            T::zero()
616        );
617        let dy = max(
618            max(inner.top() - point.y, point.y - inner.bottom()),
619            T::zero()
620        );
621
622        if dx * dx + dy * dy <= radius_squared {
623            return true;
624        }
625
626        false
627    }
628}
629
630impl<T: PartialEq> RoundedRectangle<T>
631{
632    /// Returns `true` if the rectangle containing this rounded rectangle has
633    /// zero area. (the radius is not taken into account)
634    #[inline]
635    pub fn is_zero_area(&self) -> bool
636    {
637        self.rect.is_zero_area()
638    }
639}
640
641impl<T: std::cmp::PartialOrd> RoundedRectangle<T>
642{
643    /// Returns `true` if the rectangle containing this rounded rectangle has
644    /// positive area. (the radius is not taken into account)
645    #[inline]
646    pub fn is_positive_area(&self) -> bool
647    {
648        self.rect.is_positive_area()
649    }
650}
651
652impl<T: Copy> RoundedRectangle<T>
653where
654    Vector2<T>: std::ops::Add<Output = Vector2<T>>
655{
656    /// Returns a new rounded rectangle, whose vertices are offset relative to
657    /// the current rounded rectangle by the specified amount. This is
658    /// equivalent to adding the specified vector to each vertex.
659    #[inline]
660    pub fn with_offset(&self, offset: impl Into<Vector2<T>>) -> Self
661    {
662        let offset = offset.into();
663        RoundedRectangle::new(
664            self.rect.top_left + offset,
665            self.rect.bottom_right + offset,
666            self.radius
667        )
668    }
669}
670
671impl<T: Copy> RoundedRectangle<T>
672where
673    Vector2<T>: std::ops::Sub<Output = Vector2<T>>
674{
675    /// Returns a new rounded rectangle, whose vertices are negatively offset
676    /// relative to the current rectangle by the specified amount. This is
677    /// equivalent to subtracting the specified vector to each vertex.
678    #[inline]
679    pub fn with_negative_offset(&self, offset: impl Into<Vector2<T>>) -> Self
680    {
681        let offset = offset.into();
682        RoundedRectangle::new(
683            self.rect.top_left - offset,
684            self.rect.bottom_right - offset,
685            self.radius
686        )
687    }
688}
689
690impl<T: num_traits::AsPrimitive<f32>> RoundedRectangle<T>
691{
692    /// Returns a new rounded rectangle where the coordinates and the radius
693    /// have been cast to `f32` values, using the `as` operator.
694    #[inline]
695    #[must_use]
696    pub fn into_f32(self) -> RoundedRectangle<f32>
697    {
698        RoundedRectangle::new(
699            self.rect.top_left.into_f32(),
700            self.rect.bottom_right.into_f32(),
701            self.radius.as_()
702        )
703    }
704}
705
706impl<T: num_traits::AsPrimitive<f32> + Copy> RoundedRectangle<T>
707{
708    /// Returns a new rectangle where the coordinates have been cast to `f32`
709    /// values, using the `as` operator.
710    #[inline]
711    #[must_use]
712    pub fn as_f32(&self) -> RoundedRectangle<f32>
713    {
714        RoundedRectangle::new(
715            self.rect.top_left.into_f32(),
716            self.rect.bottom_right.into_f32(),
717            self.radius.as_()
718        )
719    }
720}