pixel_map/shapes/
rotated_rect.rs

1use crate::{iline, rect_points, ILine, LineStripPixelIterator, UnsignedPixelIterator};
2use bevy_math::{ivec2, vec2, IRect, IVec2, Vec2};
3use std::cmp::Ordering;
4
5/// A rectangle that is rotated around the center pivot point.
6#[derive(Debug)]
7pub struct RotatedIRect {
8    /// The original axis-aligned rectangle.
9    pub rect: IRect,
10
11    /// The rotation to apply to the original rectangle in radians.
12    pub rotation: f32,
13}
14
15impl RotatedIRect {
16    /// Create a new rotated rectangle for the given axis-aligned rectangle and
17    /// rotation value, in radians.
18    #[inline]
19    #[must_use]
20    pub fn new(rect: IRect, rotation: f32) -> Self {
21        Self { rect, rotation }
22    }
23
24    /// Obtain the four corners of the rotated rectangle.
25    #[inline]
26    #[must_use]
27    pub fn rotated_points(&self) -> [Vec2; 4] {
28        let center = self.rect.center().as_vec2();
29        let cos_theta = self.rotation.cos();
30        let sin_theta = self.rotation.sin();
31
32        let mut corners = rect_points(&self.rect.as_rect());
33        for corner in corners.iter_mut() {
34            let x_rotated = corner.x - center.x;
35            let y_rotated = corner.y - center.y;
36            corner.x = cos_theta * x_rotated - sin_theta * y_rotated + center.x;
37            corner.y = sin_theta * x_rotated + cos_theta * y_rotated + center.y;
38        }
39        corners
40    }
41
42    /// Obtain the four edge line segments of the rotated rectangle.
43    #[inline]
44    #[must_use]
45    pub fn rotated_edges(&self) -> [ILine; 4] {
46        let points = self.rotated_points();
47        let (p1, p2, p3, p4) = (
48            points[0].as_ivec2(),
49            points[1].as_ivec2(),
50            points[2].as_ivec2(),
51            points[3].as_ivec2(),
52        );
53
54        [iline(p1, p2), iline(p2, p3), iline(p3, p4), iline(p4, p1)]
55    }
56
57    /// Calculate the axis-aligned bounding box of the rotated rectangle.
58    #[inline]
59    #[must_use]
60    pub fn aabb(&self) -> IRect {
61        let corners = self.rotated_points();
62
63        let min_x = corners[0]
64            .x
65            .min(corners[1].x)
66            .min(corners[2].x)
67            .min(corners[3].x);
68        let min_y = corners[0]
69            .y
70            .min(corners[1].y)
71            .min(corners[2].y)
72            .min(corners[3].y);
73        let max_x = corners[0]
74            .x
75            .max(corners[1].x)
76            .max(corners[2].x)
77            .max(corners[3].x);
78        let max_y = corners[0]
79            .y
80            .max(corners[1].y)
81            .max(corners[2].y)
82            .max(corners[3].y);
83
84        IRect::from_corners(
85            ivec2(min_x as i32, min_y as i32),
86            ivec2(max_x as i32, max_y as i32),
87        )
88    }
89
90    /// Calculate the largest inscribed rectangle within the rotated rectangle.
91    #[inline]
92    #[must_use]
93    pub fn inner_rect(&self) -> IRect {
94        let w = self.rect.width() as f32;
95        let h = self.rect.height() as f32;
96        if w <= 0.0 || h <= 0.0 {
97            return IRect::new(0, 0, 0, 0);
98        }
99
100        let width_is_longer = w >= h;
101        let (side_long, side_short) = if width_is_longer { (w, h) } else { (h, w) };
102
103        let sin_a = self.rotation.sin().abs();
104        let cos_a = self.rotation.cos().abs();
105
106        let size = if side_short <= 2.0 * sin_a * cos_a * side_long || (sin_a - cos_a).abs() < 1e-10
107        {
108            let x = 0.5 * side_short;
109            let (wr, hr) = if width_is_longer {
110                (x / sin_a, x / cos_a)
111            } else {
112                (x / cos_a, x / sin_a)
113            };
114            vec2(wr, hr)
115        } else {
116            let cos_2a = cos_a * cos_a - sin_a * sin_a;
117            let wr = (w * cos_a - h * sin_a) / cos_2a;
118            let hr = (h * cos_a - w * sin_a) / cos_2a;
119            vec2(wr, hr)
120        };
121
122        IRect::from_center_size(self.rect.center(), size.as_ivec2())
123    }
124
125    /// Iterate over the pixel coordinates of the rotated rectangle's edges.
126    #[inline]
127    #[must_use]
128    pub fn edge_pixels(&self) -> LineStripPixelIterator {
129        let mut points: Vec<IVec2> = self
130            .rotated_points()
131            .map(|p| p.as_ivec2())
132            .into_iter()
133            .collect();
134        // Connect first and last points
135        points.push(points[0]);
136        LineStripPixelIterator::from_points(&points)
137    }
138
139    /// Iterate over the positive pixel coordinates of the rotated rectangle's edges.
140    #[inline]
141    #[must_use]
142    pub fn unsigned_edge_pixels(&self) -> UnsignedPixelIterator<LineStripPixelIterator> {
143        UnsignedPixelIterator::<LineStripPixelIterator>::new(self.edge_pixels())
144    }
145
146    /// Iterator over all pixel coordinates, inclusive, within the rotated rectangle.
147    #[must_use]
148    pub fn pixels(&self) -> LineStripPixelIterator {
149        // Get all edge pixel coordinates
150        let mut edge_pixels: Vec<IVec2> = self.edge_pixels().collect();
151
152        // Sort edge pixel coordinates by y then x
153        edge_pixels.sort_by(|a, b| {
154            if a.y == b.y {
155                a.x.cmp(&b.x)
156            } else {
157                a.y.cmp(&b.y)
158            }
159        });
160
161        let mut rows: Vec<ILine> = Vec::with_capacity(edge_pixels.len() / 2);
162
163        let mut row_points = 0;
164        let mut cur_y = i32::MIN;
165        let mut min_x = 0;
166        let mut max_x = 0;
167        for p in edge_pixels {
168            if p.y == cur_y {
169                // Continue current row
170                max_x = p.x;
171                row_points += 1;
172            } else {
173                // Start new row
174                match row_points.cmp(&1) {
175                    Ordering::Equal => {
176                        rows.push(iline(ivec2(min_x, cur_y), ivec2(min_x, cur_y)));
177                    }
178                    Ordering::Greater => {
179                        rows.push(iline(ivec2(min_x, cur_y), ivec2(max_x, cur_y)));
180                    }
181                    Ordering::Less => {}
182                }
183                cur_y = p.y;
184                min_x = p.x;
185                row_points = 1;
186            }
187        }
188
189        // Complete final row
190        match row_points.cmp(&1) {
191            Ordering::Equal => {
192                rows.push(iline(ivec2(min_x, cur_y), ivec2(min_x, cur_y)));
193            }
194            Ordering::Greater => {
195                rows.push(iline(ivec2(min_x, cur_y), ivec2(max_x, cur_y)));
196            }
197            Ordering::Less => {}
198        }
199
200        LineStripPixelIterator::from_lines(&rows)
201    }
202
203    /// Iterator over all positive pixel coordinates, inclusive, within the rotated rectangle.
204    #[inline]
205    #[must_use]
206    pub fn unsigned_pixels(&self) -> UnsignedPixelIterator<LineStripPixelIterator> {
207        UnsignedPixelIterator::<LineStripPixelIterator>::new(self.pixels())
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_rotated_points() {
217        let rect = IRect::new(0, 0, 10, 10);
218        let rotated_rect = RotatedIRect::new(rect, 0.0);
219        let points = rotated_rect.rotated_points();
220        assert_eq!(points[0], vec2(0.0, 0.0));
221        assert_eq!(points[1], vec2(10.0, 0.0));
222        assert_eq!(points[2], vec2(10.0, 10.0));
223        assert_eq!(points[3], vec2(0.0, 10.0));
224
225        let rect = IRect::new(0, 0, 10, 10);
226        let rotated_rect = RotatedIRect::new(rect, std::f32::consts::PI / 2.0);
227        let points = rotated_rect.rotated_points();
228        assert_eq!(points[0], vec2(10.0, 0.0));
229        assert_eq!(points[1], vec2(10.0, 10.0));
230        assert_eq!(points[2], vec2(0.0, 10.0));
231        assert_eq!(points[3], vec2(0.0, 0.0));
232
233        let rect = IRect::new(0, 0, 10, 10);
234        let rotated_rect = RotatedIRect::new(rect, std::f32::consts::PI);
235        let points = rotated_rect.rotated_points();
236        assert_eq!(points[0], vec2(10.0, 10.0));
237        assert_eq!(points[1], vec2(-4.7683716e-7, 10.0));
238        assert_eq!(points[2], vec2(4.7683716e-7, -4.7683716e-7));
239        assert_eq!(points[3], vec2(10.0, 4.7683716e-7));
240
241        let rect = IRect::new(0, 0, 10, 10);
242        let rotated_rect = RotatedIRect::new(rect, 3.0 * std::f32::consts::PI / 2.0);
243        let points = rotated_rect.rotated_points();
244        assert_eq!(points[0], vec2(0.0, 10.0));
245        assert_eq!(points[1], vec2(0.0, 0.0));
246        assert_eq!(points[2], vec2(10.0, 0.0));
247        assert_eq!(points[3], vec2(10.0, 10.0));
248    }
249
250    #[test]
251    fn test_pixels() {
252        let rect = IRect::new(0, 0, 4, 4);
253        let rotated_rect = RotatedIRect::new(rect, 0.);
254        let mut pixels = rotated_rect.pixels();
255
256        for y in 0..=4 {
257            for x in 0..=4 {
258                assert_eq!(pixels.next().unwrap(), ivec2(x, y));
259            }
260        }
261    }
262}