jagua_rs/geometry/primitives/
rect.rs1use crate::geometry::geo_enums::{GeoPosition, GeoRelation};
2use crate::geometry::geo_traits::{
3 AlmostCollidesWith, CollidesWith, DistanceTo, SeparationDistance,
4};
5use crate::geometry::primitives::Edge;
6use crate::geometry::primitives::GeometryError;
7use crate::geometry::primitives::Point;
8use crate::util::FPA;
9use anyhow::Result;
10use anyhow::ensure;
11use ordered_float::OrderedFloat;
12
13#[derive(Clone, Debug, PartialEq, Copy)]
15pub struct Rect {
16 pub x_min: f32,
17 pub y_min: f32,
18 pub x_max: f32,
19 pub y_max: f32,
20}
21
22impl Rect {
23 pub fn try_new(x_min: f32, y_min: f32, x_max: f32, y_max: f32) -> Result<Self> {
24 ensure!(
25 x_min < x_max && y_min < y_max,
26 GeometryError::InvalidRect {
27 x_min,
28 y_min,
29 x_max,
30 y_max
31 }
32 );
33 Ok(Rect {
34 x_min,
35 y_min,
36 x_max,
37 y_max,
38 })
39 }
40
41 pub fn from_diagonal_corners(c1: Point, c2: Point) -> Result<Self> {
42 let x_min = f32::min(c1.x(), c2.x());
43 let y_min = f32::min(c1.y(), c2.y());
44 let x_max = f32::max(c1.x(), c2.x());
45 let y_max = f32::max(c1.y(), c2.y());
46 Rect::try_new(x_min, y_min, x_max, y_max)
47 }
48
49 #[inline(always)]
52 #[must_use]
53 pub fn relation_to(&self, other: Rect) -> GeoRelation {
54 if !self.collides_with(&other) {
55 return GeoRelation::Disjoint;
56 }
57 if self.x_min <= other.x_min
58 && self.y_min <= other.y_min
59 && self.x_max >= other.x_max
60 && self.y_max >= other.y_max
61 {
62 return GeoRelation::Surrounding;
63 }
64 if self.x_min >= other.x_min
65 && self.y_min >= other.y_min
66 && self.x_max <= other.x_max
67 && self.y_max <= other.y_max
68 {
69 return GeoRelation::Enclosed;
70 }
71 GeoRelation::Intersecting
72 }
73
74 #[inline(always)]
77 #[must_use]
78 pub fn almost_relation_to(&self, other: Rect) -> GeoRelation {
79 if !self.almost_collides_with(&other) {
80 return GeoRelation::Disjoint;
81 }
82 if FPA::from(self.x_min) <= FPA::from(other.x_min)
83 && FPA::from(self.y_min) <= FPA::from(other.y_min)
84 && FPA::from(self.x_max) >= FPA::from(other.x_max)
85 && FPA::from(self.y_max) >= FPA::from(other.y_max)
86 {
87 return GeoRelation::Surrounding;
88 }
89 if FPA::from(self.x_min) >= FPA::from(other.x_min)
90 && FPA::from(self.y_min) >= FPA::from(other.y_min)
91 && FPA::from(self.x_max) <= FPA::from(other.x_max)
92 && FPA::from(self.y_max) <= FPA::from(other.y_max)
93 {
94 return GeoRelation::Enclosed;
95 }
96 GeoRelation::Intersecting
97 }
98
99 #[must_use]
102 pub fn inflate_to_square(&self) -> Rect {
103 let width = self.x_max - self.x_min;
104 let height = self.y_max - self.y_min;
105 let mut dx = 0.0;
106 let mut dy = 0.0;
107 if height < width {
108 dy = (width - height) / 2.0;
109 } else if width < height {
110 dx = (height - width) / 2.0;
111 }
112 Rect {
113 x_min: self.x_min - dx,
114 y_min: self.y_min - dy,
115 x_max: self.x_max + dx,
116 y_max: self.y_max + dy,
117 }
118 }
119
120 #[must_use]
122 pub fn scale(self, factor: f32) -> Self {
123 let dx = (self.x_max - self.x_min) * (factor - 1.0) / 2.0;
124 let dy = (self.y_max - self.y_min) * (factor - 1.0) / 2.0;
125 self.resize_by(dx, dy)
126 .expect("scaling should not lead to invalid rectangle")
127 }
128
129 #[must_use]
132 pub fn resize_by(mut self, dx: f32, dy: f32) -> Option<Self> {
133 self.x_min -= dx;
134 self.y_min -= dy;
135 self.x_max += dx;
136 self.y_max += dy;
137
138 if self.x_min < self.x_max && self.y_min < self.y_max {
139 Some(self)
140 } else {
141 None
143 }
144 }
145
146 #[must_use]
150 pub fn quadrants(&self) -> [Self; 4] {
151 let mid = self.centroid();
152 let corners = self.corners();
153
154 let q1 = Rect::from_diagonal_corners(corners[0], mid).unwrap();
155 let q2 = Rect::from_diagonal_corners(corners[1], mid).unwrap();
156 let q3 = Rect::from_diagonal_corners(corners[2], mid).unwrap();
157 let q4 = Rect::from_diagonal_corners(corners[3], mid).unwrap();
158
159 [q1, q2, q3, q4]
160 }
161
162 #[must_use]
164 pub fn corners(&self) -> [Point; 4] {
165 [
166 Point(self.x_max, self.y_max),
167 Point(self.x_min, self.y_max),
168 Point(self.x_min, self.y_min),
169 Point(self.x_max, self.y_min),
170 ]
171 }
172
173 #[must_use]
175 pub fn sides(&self) -> [Edge; 4] {
176 let c = self.corners();
177 [
178 Edge {
179 start: c[0],
180 end: c[1],
181 },
182 Edge {
183 start: c[1],
184 end: c[2],
185 },
186 Edge {
187 start: c[2],
188 end: c[3],
189 },
190 Edge {
191 start: c[3],
192 end: c[0],
193 },
194 ]
195 }
196
197 #[must_use]
199 pub fn edges(&self) -> [Edge; 4] {
200 let c = self.corners();
201 [
202 Edge {
203 start: c[0],
204 end: c[1],
205 },
206 Edge {
207 start: c[1],
208 end: c[2],
209 },
210 Edge {
211 start: c[2],
212 end: c[3],
213 },
214 Edge {
215 start: c[3],
216 end: c[0],
217 },
218 ]
219 }
220 #[must_use]
221 pub fn width(&self) -> f32 {
222 self.x_max - self.x_min
223 }
224
225 #[must_use]
226 pub fn height(&self) -> f32 {
227 self.y_max - self.y_min
228 }
229
230 #[must_use]
232 pub fn intersection(a: Rect, b: Rect) -> Option<Rect> {
233 let x_min = f32::max(a.x_min, b.x_min);
234 let y_min = f32::max(a.y_min, b.y_min);
235 let x_max = f32::min(a.x_max, b.x_max);
236 let y_max = f32::min(a.y_max, b.y_max);
237 if x_min < x_max && y_min < y_max {
238 Some(Rect {
239 x_min,
240 y_min,
241 x_max,
242 y_max,
243 })
244 } else {
245 None
246 }
247 }
248
249 #[must_use]
251 pub fn bounding_rect(a: Rect, b: Rect) -> Rect {
252 let x_min = f32::min(a.x_min, b.x_min);
253 let y_min = f32::min(a.y_min, b.y_min);
254 let x_max = f32::max(a.x_max, b.x_max);
255 let y_max = f32::max(a.y_max, b.y_max);
256 Rect {
257 x_min,
258 y_min,
259 x_max,
260 y_max,
261 }
262 }
263
264 #[must_use]
265 pub fn centroid(&self) -> Point {
266 Point(
267 f32::midpoint(self.x_min, self.x_max),
268 f32::midpoint(self.y_min, self.y_max),
269 )
270 }
271
272 #[must_use]
273 pub fn area(&self) -> f32 {
274 (self.x_max - self.x_min) * (self.y_max - self.y_min)
275 }
276
277 #[must_use]
278 pub fn diameter(&self) -> f32 {
279 let dx = self.x_max - self.x_min;
280 let dy = self.y_max - self.y_min;
281 (dx.powi(2) + dy.powi(2)).sqrt()
282 }
283}
284
285impl CollidesWith<Rect> for Rect {
286 #[inline(always)]
287 fn collides_with(&self, other: &Rect) -> bool {
288 f32::max(self.x_min, other.x_min) <= f32::min(self.x_max, other.x_max)
289 && f32::max(self.y_min, other.y_min) <= f32::min(self.y_max, other.y_max)
290 }
291}
292
293impl AlmostCollidesWith<Rect> for Rect {
294 #[inline(always)]
295 fn almost_collides_with(&self, other: &Rect) -> bool {
296 FPA(f32::max(self.x_min, other.x_min)) <= FPA(f32::min(self.x_max, other.x_max))
297 && FPA(f32::max(self.y_min, other.y_min)) <= FPA(f32::min(self.y_max, other.y_max))
298 }
299}
300
301impl CollidesWith<Point> for Rect {
302 #[inline(always)]
303 fn collides_with(&self, point: &Point) -> bool {
304 let Point(x, y) = *point;
305 x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max
306 }
307}
308
309impl AlmostCollidesWith<Point> for Rect {
310 #[inline(always)]
311 fn almost_collides_with(&self, point: &Point) -> bool {
312 let (x, y) = (*point).into();
313 FPA(x) >= FPA(self.x_min)
314 && FPA(x) <= FPA(self.x_max)
315 && FPA(y) >= FPA(self.y_min)
316 && FPA(y) <= FPA(self.y_max)
317 }
318}
319
320impl CollidesWith<Edge> for Rect {
321 #[inline(always)]
322 #[allow(clippy::similar_names)]
323 fn collides_with(&self, edge: &Edge) -> bool {
324 let e_x_min = edge.x_min();
328 let e_x_max = edge.x_max();
329 let e_y_min = edge.y_min();
330 let e_y_max = edge.y_max();
331
332 let x_no_overlap = e_x_min.max(self.x_min) > e_x_max.min(self.x_max);
333 let y_no_overlap = e_y_min.max(self.y_min) > e_y_max.min(self.y_max);
334
335 if x_no_overlap || y_no_overlap {
336 return false;
338 }
339
340 if self.collides_with(&edge.start) || self.collides_with(&edge.end) {
341 return true;
343 }
344
345 let Point(s_x, s_y) = edge.start;
346 let Point(e_x, e_y) = edge.end;
347 let edge_dx = e_x - s_x;
348 let edge_dy = e_y - s_y;
349
350 let c = self.corners();
351
352 let sides = [
355 (c[0].0 - s_x) * edge_dy - (c[0].1 - s_y) * edge_dx,
356 (c[1].0 - s_x) * edge_dy - (c[1].1 - s_y) * edge_dx,
357 (c[2].0 - s_x) * edge_dy - (c[2].1 - s_y) * edge_dx,
358 (c[3].0 - s_x) * edge_dy - (c[3].1 - s_y) * edge_dx,
359 ];
360
361 let all_positive = sides.iter().all(|&s| s > 0.0);
362 let all_negative = sides.iter().all(|&s| s < 0.0);
363 !(all_positive || all_negative)
364 }
365}
366
367impl DistanceTo<Point> for Rect {
368 #[inline(always)]
369 fn distance_to(&self, point: &Point) -> f32 {
370 self.sq_distance_to(point).sqrt()
371 }
372
373 #[inline(always)]
374 fn sq_distance_to(&self, point: &Point) -> f32 {
375 let Point(x, y) = *point;
376 let mut distance: f32 = 0.0;
377 if x < self.x_min {
378 distance += (x - self.x_min).powi(2);
379 } else if x > self.x_max {
380 distance += (x - self.x_max).powi(2);
381 }
382 if y < self.y_min {
383 distance += (y - self.y_min).powi(2);
384 } else if y > self.y_max {
385 distance += (y - self.y_max).powi(2);
386 }
387 distance.abs()
388 }
389}
390
391impl SeparationDistance<Point> for Rect {
392 #[inline(always)]
393 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
394 let (position, sq_distance) = self.sq_separation_distance(point);
395 (position, sq_distance.sqrt())
396 }
397
398 #[inline(always)]
399 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
400 if self.collides_with(point) {
401 let Point(x, y) = *point;
402 let min_distance = [
403 (x - self.x_min).abs(),
404 (x - self.x_max).abs(),
405 (y - self.y_min).abs(),
406 (y - self.y_max).abs(),
407 ]
408 .into_iter()
409 .min_by_key(|&d| OrderedFloat(d))
410 .unwrap();
411 (GeoPosition::Interior, min_distance.powi(2))
412 } else {
413 (GeoPosition::Exterior, self.sq_distance_to(point))
414 }
415 }
416}