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