1use crate::Point;
4
5#[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 #[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 #[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}