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;
11use std::cmp::Ordering;
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 "invalid rectangle, x_min: {x_min}, x_max: {x_max}, y_min: {y_min}, y_max: {y_max}"
27 );
28 Ok(Rect {
29 x_min,
30 y_min,
31 x_max,
32 y_max,
33 })
34 }
35
36 pub fn from_diagonal_corners(c1: Point, c2: Point) -> Result<Self> {
37 let x_min = f32::min(c1.x(), c2.x());
38 let y_min = f32::min(c1.y(), c2.y());
39 let x_max = f32::max(c1.x(), c2.x());
40 let y_max = f32::max(c1.y(), c2.y());
41 Rect::try_new(x_min, y_min, x_max, y_max)
42 }
43
44 pub fn relation_to(&self, other: Rect) -> GeoRelation {
46 if self.collides_with(&other) {
47 if self.x_min <= other.x_min
48 && self.y_min <= other.y_min
49 && self.x_max >= other.x_max
50 && self.y_max >= other.y_max
51 {
52 GeoRelation::Surrounding
53 } else if self.x_min >= other.x_min
54 && self.y_min >= other.y_min
55 && self.x_max <= other.x_max
56 && self.y_max <= other.y_max
57 {
58 GeoRelation::Enclosed
59 } else {
60 GeoRelation::Intersecting
61 }
62 } else {
63 GeoRelation::Disjoint
64 }
65 }
66
67 pub fn almost_relation_to(&self, other: Rect) -> GeoRelation {
70 if self.almost_collides_with(&other) {
71 if FPA::from(self.x_min) <= FPA::from(other.x_min)
72 && FPA::from(self.y_min) <= FPA::from(other.y_min)
73 && FPA::from(self.x_max) >= FPA::from(other.x_max)
74 && FPA::from(self.y_max) >= FPA::from(other.y_max)
75 {
76 GeoRelation::Surrounding
77 } else if FPA::from(self.x_min) >= FPA::from(other.x_min)
78 && FPA::from(self.y_min) >= FPA::from(other.y_min)
79 && FPA::from(self.x_max) <= FPA::from(other.x_max)
80 && FPA::from(self.y_max) <= FPA::from(other.y_max)
81 {
82 GeoRelation::Enclosed
83 } else {
84 GeoRelation::Intersecting
85 }
86 } else {
87 GeoRelation::Disjoint
88 }
89 }
90
91 pub fn inflate_to_square(&self) -> Rect {
94 let width = self.x_max - self.x_min;
95 let height = self.y_max - self.y_min;
96 let mut dx = 0.0;
97 let mut dy = 0.0;
98 if height < width {
99 dy = (width - height) / 2.0;
100 } else if width < height {
101 dx = (height - width) / 2.0;
102 }
103 Rect {
104 x_min: self.x_min - dx,
105 y_min: self.y_min - dy,
106 x_max: self.x_max + dx,
107 y_max: self.y_max + dy,
108 }
109 }
110
111 pub fn scale(self, factor: f32) -> Self {
113 let dx = (self.x_max - self.x_min) * (factor - 1.0) / 2.0;
114 let dy = (self.y_max - self.y_min) * (factor - 1.0) / 2.0;
115 self.resize_by(dx, dy)
116 .expect("scaling should not lead to invalid rectangle")
117 }
118
119 pub fn resize_by(mut self, dx: f32, dy: f32) -> Option<Self> {
122 self.x_min -= dx;
123 self.y_min -= dy;
124 self.x_max += dx;
125 self.y_max += dy;
126
127 if self.x_min < self.x_max && self.y_min < self.y_max {
128 Some(self)
129 } else {
130 None
132 }
133 }
134
135 pub const QUADRANT_NEIGHBOR_LAYOUT: [[usize; 2]; 4] = [[1, 3], [0, 2], [1, 3], [0, 2]];
137
138 pub fn quadrants(&self) -> [Self; 4] {
142 let mid = self.centroid();
143 let corners = self.corners();
144
145 let q1 = Rect::from_diagonal_corners(corners[0], mid).unwrap();
146 let q2 = Rect::from_diagonal_corners(corners[1], mid).unwrap();
147 let q3 = Rect::from_diagonal_corners(corners[2], mid).unwrap();
148 let q4 = Rect::from_diagonal_corners(corners[3], mid).unwrap();
149
150 [q1, q2, q3, q4]
151 }
152
153 pub fn corners(&self) -> [Point; 4] {
155 [
156 Point(self.x_max, self.y_max),
157 Point(self.x_min, self.y_max),
158 Point(self.x_min, self.y_min),
159 Point(self.x_max, self.y_min),
160 ]
161 }
162
163 pub fn edges(&self) -> [Edge; 4] {
165 let c = self.corners();
166 [
167 Edge {
168 start: c[0],
169 end: c[1],
170 },
171 Edge {
172 start: c[1],
173 end: c[2],
174 },
175 Edge {
176 start: c[2],
177 end: c[3],
178 },
179 Edge {
180 start: c[3],
181 end: c[0],
182 },
183 ]
184 }
185 pub fn width(&self) -> f32 {
186 self.x_max - self.x_min
187 }
188
189 pub fn height(&self) -> f32 {
190 self.y_max - self.y_min
191 }
192
193 pub fn intersection(a: Rect, b: Rect) -> Option<Rect> {
195 let x_min = f32::max(a.x_min, b.x_min);
196 let y_min = f32::max(a.y_min, b.y_min);
197 let x_max = f32::min(a.x_max, b.x_max);
198 let y_max = f32::min(a.y_max, b.y_max);
199 if x_min < x_max && y_min < y_max {
200 Some(Rect {
201 x_min,
202 y_min,
203 x_max,
204 y_max,
205 })
206 } else {
207 None
208 }
209 }
210
211 pub fn bounding_rect(a: Rect, b: Rect) -> Rect {
213 let x_min = f32::min(a.x_min, b.x_min);
214 let y_min = f32::min(a.y_min, b.y_min);
215 let x_max = f32::max(a.x_max, b.x_max);
216 let y_max = f32::max(a.y_max, b.y_max);
217 Rect {
218 x_min,
219 y_min,
220 x_max,
221 y_max,
222 }
223 }
224
225 pub fn centroid(&self) -> Point {
226 Point(
227 (self.x_min + self.x_max) / 2.0,
228 (self.y_min + self.y_max) / 2.0,
229 )
230 }
231
232 pub fn area(&self) -> f32 {
233 (self.x_max - self.x_min) * (self.y_max - self.y_min)
234 }
235
236 pub fn diameter(&self) -> f32 {
237 let dx = self.x_max - self.x_min;
238 let dy = self.y_max - self.y_min;
239 (dx.powi(2) + dy.powi(2)).sqrt()
240 }
241}
242
243impl CollidesWith<Rect> for Rect {
244 #[inline(always)]
245 fn collides_with(&self, other: &Rect) -> bool {
246 f32::max(self.x_min, other.x_min) <= f32::min(self.x_max, other.x_max)
247 && f32::max(self.y_min, other.y_min) <= f32::min(self.y_max, other.y_max)
248 }
249}
250
251impl AlmostCollidesWith<Rect> for Rect {
252 #[inline(always)]
253 fn almost_collides_with(&self, other: &Rect) -> bool {
254 FPA(f32::max(self.x_min, other.x_min)) <= FPA(f32::min(self.x_max, other.x_max))
255 && FPA(f32::max(self.y_min, other.y_min)) <= FPA(f32::min(self.y_max, other.y_max))
256 }
257}
258
259impl CollidesWith<Point> for Rect {
260 #[inline(always)]
261 fn collides_with(&self, point: &Point) -> bool {
262 let Point(x, y) = *point;
263 x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max
264 }
265}
266
267impl AlmostCollidesWith<Point> for Rect {
268 #[inline(always)]
269 fn almost_collides_with(&self, point: &Point) -> bool {
270 let (x, y) = (*point).into();
271 FPA(x) >= FPA(self.x_min)
272 && FPA(x) <= FPA(self.x_max)
273 && FPA(y) >= FPA(self.y_min)
274 && FPA(y) <= FPA(self.y_max)
275 }
276}
277
278impl CollidesWith<Edge> for Rect {
279 #[inline(always)]
280 fn collides_with(&self, edge: &Edge) -> bool {
281 let x_min = edge.x_min();
284 let x_max = edge.x_max();
285 let y_min = edge.y_min();
286 let y_max = edge.y_max();
287
288 if x_max < self.x_min || x_min > self.x_max || y_max < self.y_min || y_min > self.y_max {
290 return false;
291 }
292
293 if self.collides_with(&edge.start) || self.collides_with(&edge.end) {
295 return true;
296 }
297
298 const POINT_EDGE_RELATION: fn(Point, &Edge) -> Ordering =
300 |p: Point, edge: &Edge| -> Ordering {
301 let Point(p_x, p_y) = p;
302 let Point(s_x, s_y) = edge.start;
303 let Point(e_x, e_y) = edge.end;
304 let v = (p_x - s_x) * (e_y - s_y) - (p_y - s_y) * (e_x - s_x);
308 v.partial_cmp(&0.0).unwrap()
309 };
310
311 let all_corners_same_side = self
312 .corners()
313 .map(|corner| POINT_EDGE_RELATION(corner, edge))
314 .windows(2)
315 .all(|w| w[0] == w[1]);
316
317 if all_corners_same_side {
318 return false;
319 }
320
321 self.edges()
323 .iter()
324 .any(|rect_edge| edge.collides_with(rect_edge))
325 }
326}
327
328impl DistanceTo<Point> for Rect {
329 #[inline(always)]
330 fn distance_to(&self, point: &Point) -> f32 {
331 self.sq_distance_to(point).sqrt()
332 }
333
334 #[inline(always)]
335 fn sq_distance_to(&self, point: &Point) -> f32 {
336 let Point(x, y) = *point;
337 let mut distance: f32 = 0.0;
338 if x < self.x_min {
339 distance += (x - self.x_min).powi(2);
340 } else if x > self.x_max {
341 distance += (x - self.x_max).powi(2);
342 }
343 if y < self.y_min {
344 distance += (y - self.y_min).powi(2);
345 } else if y > self.y_max {
346 distance += (y - self.y_max).powi(2);
347 }
348 distance.abs()
349 }
350}
351
352impl SeparationDistance<Point> for Rect {
353 #[inline(always)]
354 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
355 let (position, sq_distance) = self.sq_separation_distance(point);
356 (position, sq_distance.sqrt())
357 }
358
359 #[inline(always)]
360 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
361 match self.collides_with(point) {
362 false => (GeoPosition::Exterior, self.sq_distance_to(point)),
363 true => {
364 let Point(x, y) = *point;
365 let min_distance = [
366 (x - self.x_min).abs(),
367 (x - self.x_max).abs(),
368 (y - self.y_min).abs(),
369 (y - self.y_max).abs(),
370 ]
371 .into_iter()
372 .min_by_key(|&d| OrderedFloat(d))
373 .unwrap();
374 (GeoPosition::Interior, min_distance.powi(2))
375 }
376 }
377 }
378}