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 pub fn relation_to(&self, other: Rect) -> GeoRelation {
47 if !self.collides_with(&other) {
48 return GeoRelation::Disjoint;
49 }
50 if self.x_min <= other.x_min
51 && self.y_min <= other.y_min
52 && self.x_max >= other.x_max
53 && self.y_max >= other.y_max
54 {
55 return GeoRelation::Surrounding;
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::Enclosed;
63 }
64 GeoRelation::Intersecting
65 }
66
67 #[inline(always)]
70 pub fn almost_relation_to(&self, other: Rect) -> GeoRelation {
71 if !self.almost_collides_with(&other) {
72 return GeoRelation::Disjoint;
73 }
74 if FPA::from(self.x_min) <= FPA::from(other.x_min)
75 && FPA::from(self.y_min) <= FPA::from(other.y_min)
76 && FPA::from(self.x_max) >= FPA::from(other.x_max)
77 && FPA::from(self.y_max) >= FPA::from(other.y_max)
78 {
79 return GeoRelation::Surrounding;
80 }
81 if FPA::from(self.x_min) >= FPA::from(other.x_min)
82 && FPA::from(self.y_min) >= FPA::from(other.y_min)
83 && FPA::from(self.x_max) <= FPA::from(other.x_max)
84 && FPA::from(self.y_max) <= FPA::from(other.y_max)
85 {
86 return GeoRelation::Enclosed;
87 }
88 GeoRelation::Intersecting
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 sides(&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
186 pub fn edges(&self) -> [Edge; 4] {
188 let c = self.corners();
189 [
190 Edge {
191 start: c[0],
192 end: c[1],
193 },
194 Edge {
195 start: c[1],
196 end: c[2],
197 },
198 Edge {
199 start: c[2],
200 end: c[3],
201 },
202 Edge {
203 start: c[3],
204 end: c[0],
205 },
206 ]
207 }
208 pub fn width(&self) -> f32 {
209 self.x_max - self.x_min
210 }
211
212 pub fn height(&self) -> f32 {
213 self.y_max - self.y_min
214 }
215
216 pub fn intersection(a: Rect, b: Rect) -> Option<Rect> {
218 let x_min = f32::max(a.x_min, b.x_min);
219 let y_min = f32::max(a.y_min, b.y_min);
220 let x_max = f32::min(a.x_max, b.x_max);
221 let y_max = f32::min(a.y_max, b.y_max);
222 if x_min < x_max && y_min < y_max {
223 Some(Rect {
224 x_min,
225 y_min,
226 x_max,
227 y_max,
228 })
229 } else {
230 None
231 }
232 }
233
234 pub fn bounding_rect(a: Rect, b: Rect) -> Rect {
236 let x_min = f32::min(a.x_min, b.x_min);
237 let y_min = f32::min(a.y_min, b.y_min);
238 let x_max = f32::max(a.x_max, b.x_max);
239 let y_max = f32::max(a.y_max, b.y_max);
240 Rect {
241 x_min,
242 y_min,
243 x_max,
244 y_max,
245 }
246 }
247
248 pub fn centroid(&self) -> Point {
249 Point(
250 (self.x_min + self.x_max) / 2.0,
251 (self.y_min + self.y_max) / 2.0,
252 )
253 }
254
255 pub fn area(&self) -> f32 {
256 (self.x_max - self.x_min) * (self.y_max - self.y_min)
257 }
258
259 pub fn diameter(&self) -> f32 {
260 let dx = self.x_max - self.x_min;
261 let dy = self.y_max - self.y_min;
262 (dx.powi(2) + dy.powi(2)).sqrt()
263 }
264}
265
266impl CollidesWith<Rect> for Rect {
267 #[inline(always)]
268 fn collides_with(&self, other: &Rect) -> bool {
269 f32::max(self.x_min, other.x_min) <= f32::min(self.x_max, other.x_max)
270 && f32::max(self.y_min, other.y_min) <= f32::min(self.y_max, other.y_max)
271 }
272}
273
274impl AlmostCollidesWith<Rect> for Rect {
275 #[inline(always)]
276 fn almost_collides_with(&self, other: &Rect) -> bool {
277 FPA(f32::max(self.x_min, other.x_min)) <= FPA(f32::min(self.x_max, other.x_max))
278 && FPA(f32::max(self.y_min, other.y_min)) <= FPA(f32::min(self.y_max, other.y_max))
279 }
280}
281
282impl CollidesWith<Point> for Rect {
283 #[inline(always)]
284 fn collides_with(&self, point: &Point) -> bool {
285 let Point(x, y) = *point;
286 x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max
287 }
288}
289
290impl AlmostCollidesWith<Point> for Rect {
291 #[inline(always)]
292 fn almost_collides_with(&self, point: &Point) -> bool {
293 let (x, y) = (*point).into();
294 FPA(x) >= FPA(self.x_min)
295 && FPA(x) <= FPA(self.x_max)
296 && FPA(y) >= FPA(self.y_min)
297 && FPA(y) <= FPA(self.y_max)
298 }
299}
300
301impl CollidesWith<Edge> for Rect {
302 #[inline(always)]
303 fn collides_with(&self, edge: &Edge) -> bool {
304 let e_x_min = edge.x_min();
307 let e_x_max = edge.x_max();
308 let e_y_min = edge.y_min();
309 let e_y_max = edge.y_max();
310
311 let x_no_overlap = e_x_min.max(self.x_min) > e_x_max.min(self.x_max);
312 let y_no_overlap = e_y_min.max(self.y_min) > e_y_max.min(self.y_max);
313
314 if x_no_overlap || y_no_overlap {
315 return false; }
317
318 if self.collides_with(&edge.start) || self.collides_with(&edge.end) {
320 return true;
321 }
322
323 let Point(s_x, s_y) = edge.start;
325 let Point(e_x, e_y) = edge.end;
326 let edge_dx = e_x - s_x;
327 let edge_dy = e_y - s_y;
328
329 let side = |p_x, p_y| (p_x - s_x) * edge_dy - (p_y - s_y) * edge_dx;
332
333 let corners = self.corners();
334 let mut last_side = side(corners[3].x(), corners[3].y());
335
336 for i in 0..4 {
337 let current_side = side(corners[i].x(), corners[i].y());
338 if current_side * last_side <= 0.0 {
340 let prev_corner_idx = if i == 0 { 3 } else { i - 1 };
342 let rect_edge = Edge {
343 start: corners[prev_corner_idx],
344 end: corners[i],
345 };
346 if edge.collides_with(&rect_edge) {
347 return true;
348 }
349 }
350 last_side = current_side;
351 }
352
353 false
354 }
355}
356
357impl DistanceTo<Point> for Rect {
358 #[inline(always)]
359 fn distance_to(&self, point: &Point) -> f32 {
360 self.sq_distance_to(point).sqrt()
361 }
362
363 #[inline(always)]
364 fn sq_distance_to(&self, point: &Point) -> f32 {
365 let Point(x, y) = *point;
366 let mut distance: f32 = 0.0;
367 if x < self.x_min {
368 distance += (x - self.x_min).powi(2);
369 } else if x > self.x_max {
370 distance += (x - self.x_max).powi(2);
371 }
372 if y < self.y_min {
373 distance += (y - self.y_min).powi(2);
374 } else if y > self.y_max {
375 distance += (y - self.y_max).powi(2);
376 }
377 distance.abs()
378 }
379}
380
381impl SeparationDistance<Point> for Rect {
382 #[inline(always)]
383 fn separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
384 let (position, sq_distance) = self.sq_separation_distance(point);
385 (position, sq_distance.sqrt())
386 }
387
388 #[inline(always)]
389 fn sq_separation_distance(&self, point: &Point) -> (GeoPosition, f32) {
390 match self.collides_with(point) {
391 false => (GeoPosition::Exterior, self.sq_distance_to(point)),
392 true => {
393 let Point(x, y) = *point;
394 let min_distance = [
395 (x - self.x_min).abs(),
396 (x - self.x_max).abs(),
397 (y - self.y_min).abs(),
398 (y - self.y_max).abs(),
399 ]
400 .into_iter()
401 .min_by_key(|&d| OrderedFloat(d))
402 .unwrap();
403 (GeoPosition::Interior, min_distance.powi(2))
404 }
405 }
406 }
407}