tg_geom/
rect.rs

1//! Axis-aligned bounding rectangle.
2
3use crate::Point;
4
5/// Defined by minimum and maximum corner points.
6#[derive(Debug, Clone, Copy, PartialEq)]
7#[repr(C)]
8pub struct Rect {
9    pub min: Point,
10    pub max: Point,
11}
12
13impl Rect {
14    #[inline]
15    pub const fn new(min: Point, max: Point) -> Self {
16        Self { min, max }
17    }
18
19    #[inline]
20    pub const fn from_coords(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
21        Self {
22            min: Point::new(min_x, min_y),
23            max: Point::new(max_x, max_y),
24        }
25    }
26
27    #[inline]
28    pub fn center(self) -> Point {
29        let p = unsafe { tg_geom_sys::tg_rect_center(self.into()) };
30        p.into()
31    }
32
33    /// Returns the union with `other`.
34    #[inline]
35    pub fn expand(self, other: Rect) -> Rect {
36        let r = unsafe { tg_geom_sys::tg_rect_expand(self.into(), other.into()) };
37        r.into()
38    }
39
40    /// Expands to include `point`.
41    #[inline]
42    pub fn expand_point(self, point: Point) -> Rect {
43        let r = unsafe { tg_geom_sys::tg_rect_expand_point(self.into(), point.into()) };
44        r.into()
45    }
46
47    #[inline]
48    pub fn intersects_rect(self, other: Rect) -> bool {
49        unsafe { tg_geom_sys::tg_rect_intersects_rect(self.into(), other.into()) }
50    }
51
52    #[inline]
53    pub fn intersects_point(self, point: Point) -> bool {
54        unsafe { tg_geom_sys::tg_rect_intersects_point(self.into(), point.into()) }
55    }
56
57    #[inline]
58    pub fn width(self) -> f64 {
59        self.max.x - self.min.x
60    }
61
62    #[inline]
63    pub fn height(self) -> f64 {
64        self.max.y - self.min.y
65    }
66
67    #[inline]
68    pub fn area(self) -> f64 {
69        self.width() * self.height()
70    }
71}
72
73impl From<tg_geom_sys::tg_rect> for Rect {
74    #[inline]
75    fn from(r: tg_geom_sys::tg_rect) -> Self {
76        Self {
77            min: r.min.into(),
78            max: r.max.into(),
79        }
80    }
81}
82
83impl From<Rect> for tg_geom_sys::tg_rect {
84    #[inline]
85    fn from(r: Rect) -> Self {
86        Self {
87            min: r.min.into(),
88            max: r.max.into(),
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    fn p(x: f64, y: f64) -> Point {
98        Point::new(x, y)
99    }
100
101    fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
102        Rect::from_coords(min_x, min_y, max_x, max_y)
103    }
104
105    #[test]
106    fn test_rect_center() {
107        assert_eq!(r(0.0, 0.0, 10.0, 10.0).center(), p(5.0, 5.0));
108        assert_eq!(r(0.0, 0.0, 0.0, 0.0).center(), p(0.0, 0.0));
109        assert_eq!(r(-10.0, -10.0, 10.0, 10.0).center(), p(0.0, 0.0));
110    }
111
112    #[test]
113    fn test_rect_intersects_rect() {
114        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 10.0, 10.0)));
115        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(2.0, 2.0, 8.0, 8.0)));
116        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(-1.0, 0.0, 10.0, 10.0)));
117        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, -1.0, 10.0, 10.0)));
118        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 11.0, 10.0)));
119        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 10.0, 11.0)));
120        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(11.0, 0.0, 21.0, 10.0)));
121        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 11.0, 10.0, 21.0)));
122        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(11.0, 11.0, 21.0, 21.0)));
123        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(-11.0, -11.0, -1.0, -1.0)));
124    }
125
126    #[test]
127    fn test_rect_intersects_point() {
128        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(5.0, 5.0)));
129        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_point(p(15.0, 15.0)));
130        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(0.0, 0.0)));
131        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(5.0, 0.0)));
132    }
133
134    #[test]
135    fn test_rect_new() {
136        let rect = Rect::new(p(1.0, 2.0), p(3.0, 4.0));
137        assert_eq!(rect.min, p(1.0, 2.0));
138        assert_eq!(rect.max, p(3.0, 4.0));
139    }
140
141    #[test]
142    fn test_rect_from_coords() {
143        let rect = Rect::from_coords(1.0, 2.0, 3.0, 4.0);
144        assert_eq!(rect.min, p(1.0, 2.0));
145        assert_eq!(rect.max, p(3.0, 4.0));
146    }
147
148    #[test]
149    fn test_rect_width_height_area() {
150        let rect = r(1.0, 2.0, 5.0, 8.0);
151        assert!((rect.width() - 4.0).abs() < 1e-10);
152        assert!((rect.height() - 6.0).abs() < 1e-10);
153        assert!((rect.area() - 24.0).abs() < 1e-10);
154
155        let h_line = r(0.0, 5.0, 10.0, 5.0);
156        assert_eq!(h_line.height(), 0.0);
157        assert_eq!(h_line.area(), 0.0);
158
159        let v_line = r(5.0, 0.0, 5.0, 10.0);
160        assert_eq!(v_line.width(), 0.0);
161        assert_eq!(v_line.area(), 0.0);
162
163        let point_rect = r(5.0, 5.0, 5.0, 5.0);
164        assert_eq!(point_rect.width(), 0.0);
165        assert_eq!(point_rect.height(), 0.0);
166        assert_eq!(point_rect.area(), 0.0);
167    }
168
169    #[test]
170    fn test_rect_expand() {
171        let r1 = r(0.0, 0.0, 1.0, 1.0);
172        let r2 = r(2.0, 2.0, 3.0, 3.0);
173        let expanded = r1.expand(r2);
174        assert_eq!(expanded.min, p(0.0, 0.0));
175        assert_eq!(expanded.max, p(3.0, 3.0));
176
177        let r3 = r(-2.0, -2.0, -1.0, -1.0);
178        let expanded2 = r1.expand(r3);
179        assert_eq!(expanded2.min, p(-2.0, -2.0));
180        assert_eq!(expanded2.max, p(1.0, 1.0));
181
182        assert_eq!(r1.expand(r1), r1);
183    }
184
185    #[test]
186    fn test_rect_expand_point() {
187        let rect = r(0.0, 0.0, 1.0, 1.0);
188        let expanded = rect.expand_point(p(5.0, 5.0));
189        assert_eq!(expanded.max, p(5.0, 5.0));
190
191        let r2 = r(0.0, 0.0, 10.0, 10.0);
192        let expanded2 = r2.expand_point(p(5.0, 5.0));
193        assert_eq!(expanded2, r2);
194
195        let expanded3 = rect.expand_point(p(-5.0, -5.0));
196        assert_eq!(expanded3.min, p(-5.0, -5.0));
197    }
198
199    #[test]
200    fn test_rect_sys_roundtrip() {
201        let rect = r(1.0, 2.0, 3.0, 4.0);
202        let sys_rect: tg_geom_sys::tg_rect = rect.into();
203        let back: Rect = sys_rect.into();
204        assert_eq!(rect, back);
205    }
206
207    #[test]
208    fn test_rect_eq() {
209        assert_eq!(r(0.0, 0.0, 1.0, 1.0), r(0.0, 0.0, 1.0, 1.0));
210        assert_ne!(r(0.0, 0.0, 1.0, 1.0), r(0.0, 0.0, 2.0, 2.0));
211    }
212
213    #[test]
214    fn test_rect_debug() {
215        let rect = r(1.0, 2.0, 3.0, 4.0);
216        let debug_str = format!("{rect:?}");
217        assert!(debug_str.contains("Rect"));
218    }
219
220    #[test]
221    fn test_rect_negative() {
222        let rect = r(-10.0, -10.0, -5.0, -5.0);
223        assert_eq!(rect.center(), p(-7.5, -7.5));
224        assert!(rect.intersects_point(p(-7.5, -7.5)));
225        assert!(!rect.intersects_point(p(0.0, 0.0)));
226    }
227}