1use super::vector2::Vector2;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum RectangleSide {
6 Top,
7 Right,
8 Bottom,
9 Left,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CardinalDirection {
15 N,
16 E,
17 S,
18 W,
19 NE,
20 SE,
21 SW,
22 NW,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct Rectangle {
28 pub x: f32,
29 pub y: f32,
30 pub width: f32,
31 pub height: f32,
32}
33
34impl Rectangle {
35 pub fn center(&self) -> Vector2 {
37 [self.x + self.width / 2.0, self.y + self.height / 2.0]
38 }
39
40 pub fn translate(&self, delta: Vector2) -> Self {
42 Self {
43 x: self.x + delta[0],
44 y: self.y + delta[1],
45 ..*self
46 }
47 }
48
49 pub fn scale(&self, origin: Vector2, scale: Vector2) -> Self {
51 let [sx, sy] = scale;
52 let [ox, oy] = origin;
53 Self {
54 x: ox + (self.x - ox) * sx,
55 y: oy + (self.y - oy) * sy,
56 width: self.width * sx,
57 height: self.height * sy,
58 }
59 }
60
61 pub fn axis_dimension(&self, axis: super::vector2::Axis) -> f32 {
63 match axis {
64 super::vector2::Axis::X => self.width,
65 super::vector2::Axis::Y => self.height,
66 }
67 }
68
69 pub fn contains(&self, other: &Rectangle) -> bool {
71 self.x <= other.x
72 && self.y <= other.y
73 && self.x + self.width >= other.x + other.width
74 && self.y + self.height >= other.y + other.height
75 }
76
77 pub fn contains_point(&self, point: Vector2) -> bool {
79 let [px, py] = point;
80 px >= self.x && px <= self.x + self.width && py >= self.y && py <= self.y + self.height
81 }
82
83 pub fn offset_to(&self, point: Vector2) -> Vector2 {
85 let clamped_x = point[0].max(self.x).min(self.x + self.width);
86 let clamped_y = point[1].max(self.y).min(self.y + self.height);
87 [point[0] - clamped_x, point[1] - clamped_y]
88 }
89
90 pub fn intersects(&self, other: &Rectangle) -> bool {
92 let a_right = self.x + self.width;
93 let a_bottom = self.y + self.height;
94 let b_right = other.x + other.width;
95 let b_bottom = other.y + other.height;
96
97 !(self.x > b_right || self.y > b_bottom || a_right < other.x || a_bottom < other.y)
98 }
99
100 pub fn intersection(&self, other: &Rectangle) -> Option<Rectangle> {
102 let x1 = self.x.max(other.x);
103 let y1 = self.y.max(other.y);
104 let x2 = (self.x + self.width).min(other.x + other.width);
105 let y2 = (self.y + self.height).min(other.y + other.height);
106
107 if x2 <= x1 || y2 <= y1 {
108 return None;
109 }
110
111 Some(Rectangle {
112 x: x1,
113 y: y1,
114 width: x2 - x1,
115 height: y2 - y1,
116 })
117 }
118
119 pub fn subtract(&self, other: Rectangle) -> Vec<Rectangle> {
121 boolean::subtract(*self, other)
122 }
123}
124
125pub fn from_points(points: &[Vector2]) -> Rectangle {
127 assert!(!points.is_empty(), "at least one point is required");
128 let mut min_x = f32::INFINITY;
129 let mut min_y = f32::INFINITY;
130 let mut max_x = f32::NEG_INFINITY;
131 let mut max_y = f32::NEG_INFINITY;
132 for &[x, y] in points {
133 if x < min_x {
134 min_x = x;
135 }
136 if y < min_y {
137 min_y = y;
138 }
139 if x > max_x {
140 max_x = x;
141 }
142 if y > max_y {
143 max_y = y;
144 }
145 }
146 Rectangle {
147 x: min_x,
148 y: min_y,
149 width: max_x - min_x,
150 height: max_y - min_y,
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq)]
156pub struct Rect9Points {
157 pub top_left: Vector2,
158 pub top_right: Vector2,
159 pub bottom_right: Vector2,
160 pub bottom_left: Vector2,
161 pub top_center: Vector2,
162 pub right_center: Vector2,
163 pub bottom_center: Vector2,
164 pub left_center: Vector2,
165 pub center: Vector2,
166}
167
168pub fn to_9points(rect: &Rectangle) -> Rect9Points {
170 let Rectangle {
171 x,
172 y,
173 width,
174 height,
175 } = *rect;
176 let center_x = x + width / 2.0;
177 let center_y = y + height / 2.0;
178 Rect9Points {
179 top_left: [x, y],
180 top_right: [x + width, y],
181 bottom_right: [x + width, y + height],
182 bottom_left: [x, y + height],
183 top_center: [center_x, y],
184 right_center: [x + width, center_y],
185 bottom_center: [center_x, y + height],
186 left_center: [x, center_y],
187 center: [center_x, center_y],
188 }
189}
190
191pub fn to_9points_chunk(rect: &Rectangle) -> [Vector2; 9] {
195 let p = to_9points(rect);
196 [
197 p.top_left,
198 p.top_right,
199 p.bottom_right,
200 p.bottom_left,
201 p.top_center,
202 p.right_center,
203 p.bottom_center,
204 p.left_center,
205 p.center,
206 ]
207}
208
209pub fn contains(a: &Rectangle, b: &Rectangle) -> bool {
211 b.contains(a)
212}
213
214pub fn contains_point(rect: &Rectangle, point: Vector2) -> bool {
216 rect.contains_point(point)
217}
218
219pub fn offset(rect: &Rectangle, point: Vector2) -> Vector2 {
221 rect.offset_to(point)
222}
223
224pub fn intersects(a: &Rectangle, b: &Rectangle) -> bool {
226 a.intersects(b)
227}
228
229pub fn intersection(a: &Rectangle, b: &Rectangle) -> Option<Rectangle> {
231 a.intersection(b)
232}
233
234pub fn union(rects: &[Rectangle]) -> Rectangle {
236 assert!(!rects.is_empty(), "rectangles array cannot be empty");
237 let mut min_x = f32::INFINITY;
238 let mut min_y = f32::INFINITY;
239 let mut max_x = f32::NEG_INFINITY;
240 let mut max_y = f32::NEG_INFINITY;
241 for r in rects {
242 if r.x < min_x {
243 min_x = r.x;
244 }
245 if r.y < min_y {
246 min_y = r.y;
247 }
248 if r.x + r.width > max_x {
249 max_x = r.x + r.width;
250 }
251 if r.y + r.height > max_y {
252 max_y = r.y + r.height;
253 }
254 }
255 Rectangle {
256 x: min_x,
257 y: min_y,
258 width: max_x - min_x,
259 height: max_y - min_y,
260 }
261}
262
263pub mod boolean {
265 use super::{Rectangle, intersection};
266
267 pub fn subtract(a: Rectangle, b: Rectangle) -> Vec<Rectangle> {
269 let inter = match intersection(&a, &b) {
270 Some(i) if i.width > 0.0 && i.height > 0.0 => i,
271 _ => return vec![a],
272 };
273
274 let mut result = Vec::new();
275
276 if a.y < inter.y {
278 result.push(Rectangle {
279 x: a.x,
280 y: a.y,
281 width: a.width,
282 height: inter.y - a.y,
283 });
284 }
285
286 if a.y + a.height > inter.y + inter.height {
288 result.push(Rectangle {
289 x: a.x,
290 y: inter.y + inter.height,
291 width: a.width,
292 height: a.y + a.height - (inter.y + inter.height),
293 });
294 }
295
296 if a.x < inter.x {
298 result.push(Rectangle {
299 x: a.x,
300 y: inter.y,
301 width: inter.x - a.x,
302 height: inter.height,
303 });
304 }
305
306 if a.x + a.width > inter.x + inter.width {
308 result.push(Rectangle {
309 x: inter.x + inter.width,
310 y: inter.y,
311 width: a.x + a.width - (inter.x + inter.width),
312 height: inter.height,
313 });
314 }
315
316 result
317 }
318}
319
320pub fn get_gaps(rectangles: &[Rectangle], axis: super::vector2::Axis) -> Vec<f32> {
326 if rectangles.len() < 2 {
327 return Vec::new();
328 }
329
330 let mut sorted: Vec<&Rectangle> = rectangles.iter().collect();
331 sorted.sort_by(|a, b| {
332 if axis == super::vector2::Axis::X {
333 a.x.partial_cmp(&b.x).unwrap()
334 } else {
335 a.y.partial_cmp(&b.y).unwrap()
336 }
337 });
338
339 let mut gaps = Vec::new();
340 for i in 0..sorted.len() - 1 {
341 let end = if axis == super::vector2::Axis::X {
342 sorted[i].x + sorted[i].width
343 } else {
344 sorted[i].y + sorted[i].height
345 };
346 let next_start = if axis == super::vector2::Axis::X {
347 sorted[i + 1].x
348 } else {
349 sorted[i + 1].y
350 };
351 gaps.push(next_start - end);
352 }
353 gaps
354}
355
356pub fn get_uniform_gap(
359 rectangles: &[Rectangle],
360 axis: super::vector2::Axis,
361 tolerance: f32,
362) -> (Option<f32>, Vec<f32>) {
363 let gaps = get_gaps(rectangles, axis);
364 if gaps.is_empty() {
365 return (None, gaps);
366 }
367
368 if crate::utils::is_uniform(&gaps, tolerance) {
369 let mut best_val = gaps[0];
370 let mut best_count = 0;
371 for &g in &gaps {
372 let count = gaps.iter().filter(|&&x| x == g).count();
373 if count > best_count {
374 best_count = count;
375 best_val = g;
376 }
377 }
378 let most = best_val;
379 (Some(most), gaps)
380 } else {
381 (None, gaps)
382 }
383}
384
385pub fn distribute_evenly(rectangles: &[Rectangle], axis: super::vector2::Axis) -> Vec<Rectangle> {
388 if rectangles.len() < 2 {
389 return rectangles.to_vec();
390 }
391
392 let bbox = union(rectangles);
393 let start = if axis == super::vector2::Axis::X {
394 bbox.x
395 } else {
396 bbox.y
397 };
398 let total_size = if axis == super::vector2::Axis::X {
399 bbox.width
400 } else {
401 bbox.height
402 };
403 let total_rect_size: f32 = rectangles
404 .iter()
405 .map(|r| {
406 if axis == super::vector2::Axis::X {
407 r.width
408 } else {
409 r.height
410 }
411 })
412 .sum();
413
414 let gap_size = (total_size - total_rect_size) / (rectangles.len() as f32 - 1.0);
415
416 let mut sorted_indices: Vec<usize> = (0..rectangles.len()).collect();
417 sorted_indices.sort_by(|&a, &b| {
418 if axis == super::vector2::Axis::X {
419 rectangles[a].x.partial_cmp(&rectangles[b].x).unwrap()
420 } else {
421 rectangles[a].y.partial_cmp(&rectangles[b].y).unwrap()
422 }
423 });
424
425 let mut current = start;
426 let mut distributed = vec![
427 Rectangle {
428 x: 0.0,
429 y: 0.0,
430 width: 0.0,
431 height: 0.0
432 };
433 rectangles.len()
434 ];
435 for idx in sorted_indices {
436 let r = rectangles[idx];
437 let mut new_r = r;
438 if axis == super::vector2::Axis::X {
439 new_r.x = current;
440 current += r.width + gap_size;
441 } else {
442 new_r.y = current;
443 current += r.height + gap_size;
444 }
445 distributed[idx] = new_r;
446 }
447
448 distributed
449}
450
451#[derive(Debug, Clone, Copy)]
453pub struct Sides {
454 pub top: f32,
455 pub right: f32,
456 pub bottom: f32,
457 pub left: f32,
458}
459
460impl From<f32> for Sides {
461 fn from(all: f32) -> Self {
462 Self {
463 top: all,
464 right: all,
465 bottom: all,
466 left: all,
467 }
468 }
469}
470
471impl From<[f32; 4]> for Sides {
472 fn from(v: [f32; 4]) -> Self {
473 Self {
474 top: v[0],
475 right: v[1],
476 bottom: v[2],
477 left: v[3],
478 }
479 }
480}
481
482pub fn quantize(rect: Rectangle, step: impl super::vector2::IntoVector2) -> Rectangle {
484 let s = step.into_vector2();
485 Rectangle {
486 x: crate::quantize(rect.x, s[0]),
487 y: crate::quantize(rect.y, s[1]),
488 width: crate::quantize(rect.width, s[0]),
489 height: crate::quantize(rect.height, s[1]),
490 }
491}
492
493pub fn positive(rect: Rectangle) -> Rectangle {
495 Rectangle {
496 x: rect.x.min(rect.x + rect.width),
497 y: rect.y.min(rect.y + rect.height),
498 width: rect.width.abs(),
499 height: rect.height.abs(),
500 }
501}
502
503pub fn aspect_ratio(rect: Rectangle) -> f32 {
505 rect.width / rect.height
506}
507
508pub fn get_scale_factors(a: Rectangle, b: Rectangle) -> Vector2 {
510 [b.width / a.width, b.height / a.height]
511}
512
513use super::transform::AffineTransform;
514
515pub fn get_relative_transform(a: Rectangle, b: Rectangle) -> AffineTransform {
517 let sx = if a.width == 0.0 {
518 1.0
519 } else {
520 b.width / a.width
521 };
522 let sy = if a.height == 0.0 {
523 1.0
524 } else {
525 b.height / a.height
526 };
527
528 let t1 = AffineTransform::translate(-a.x, -a.y);
529 let t2 = AffineTransform {
530 matrix: [[sx, 0.0, 0.0], [0.0, sy, 0.0]],
531 };
532 let t3 = AffineTransform::translate(b.x, b.y);
533
534 t3.compose(&t2.compose(&t1))
535}
536
537pub fn transform(rect: Rectangle, t: &AffineTransform) -> Rectangle {
539 let corners = [
540 [rect.x, rect.y],
541 [rect.x + rect.width, rect.y],
542 [rect.x, rect.y + rect.height],
543 [rect.x + rect.width, rect.y + rect.height],
544 ];
545 let transformed: Vec<Vector2> = corners
546 .iter()
547 .map(|&p| super::vector2::transform(p, t))
548 .collect();
549 from_points(&transformed)
550}
551
552pub fn rotate(rect: Rectangle, degrees: f32) -> Rectangle {
554 let center = rect.center();
555 let rad = degrees.to_radians();
556 let (sin, cos) = rad.sin_cos();
557 let rotate_point = |p: Vector2| -> Vector2 {
558 let dx = p[0] - center[0];
559 let dy = p[1] - center[1];
560 [
561 center[0] + dx * cos - dy * sin,
562 center[1] + dx * sin + dy * cos,
563 ]
564 };
565 let pts = [
566 rotate_point([rect.x, rect.y]),
567 rotate_point([rect.x + rect.width, rect.y]),
568 rotate_point([rect.x, rect.y + rect.height]),
569 rotate_point([rect.x + rect.width, rect.y + rect.height]),
570 ];
571 from_points(&pts)
572}
573
574pub fn get_cardinal_point(rect: Rectangle, dir: CardinalDirection) -> Vector2 {
576 match dir {
577 CardinalDirection::N => [rect.x + rect.width / 2.0, rect.y],
578 CardinalDirection::E => [rect.x + rect.width, rect.y + rect.height / 2.0],
579 CardinalDirection::S => [rect.x + rect.width / 2.0, rect.y + rect.height],
580 CardinalDirection::W => [rect.x, rect.y + rect.height / 2.0],
581 CardinalDirection::NE => [rect.x + rect.width, rect.y],
582 CardinalDirection::SE => [rect.x + rect.width, rect.y + rect.height],
583 CardinalDirection::SW => [rect.x, rect.y + rect.height],
584 CardinalDirection::NW => [rect.x, rect.y],
585 }
586}
587
588pub fn get_center(rect: Rectangle) -> Vector2 {
590 rect.center()
591}
592
593pub fn axis_projection_intersection(
595 rects: &[Rectangle],
596 axis: super::vector2::Axis,
597) -> Option<Vector2> {
598 assert!(rects.len() >= 2, "At least two rectangles are required");
599 let projections: Vec<Vector2> = rects
600 .iter()
601 .map(|r| {
602 if axis == super::vector2::Axis::X {
603 [r.y, r.y + r.height]
604 } else {
605 [r.x, r.x + r.width]
606 }
607 })
608 .collect();
609
610 projections
611 .iter()
612 .skip(1)
613 .fold(Some(projections[0]), |acc, p| {
614 acc.and_then(|cur| super::vector2::intersection(cur, *p))
615 })
616}
617
618pub fn is_identical(a: Rectangle, b: Rectangle) -> bool {
620 a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height
621}
622
623pub fn is_uniform(rects: &[Rectangle]) -> bool {
625 rects.windows(2).all(|w| is_identical(w[0], w[1]))
626}
627
628pub fn pad(rect: Rectangle, padding: impl Into<Sides>) -> Rectangle {
630 let p = padding.into();
631 let cx = rect.x + rect.width / 2.0;
632 let cy = rect.y + rect.height / 2.0;
633 let w = rect.width + p.left + p.right;
634 let h = rect.height + p.top + p.bottom;
635 Rectangle {
636 x: cx - w / 2.0,
637 y: cy - h / 2.0,
638 width: w,
639 height: h,
640 }
641}
642
643pub fn inset(rect: Rectangle, margin: impl Into<Sides>) -> Rectangle {
645 let m = margin.into();
646 let cx = rect.x + rect.width / 2.0;
647 let cy = rect.y + rect.height / 2.0;
648 let mut w = rect.width - (m.left + m.right);
649 let mut h = rect.height - (m.top + m.bottom);
650 if w < 0.0 {
651 w = 0.0;
652 }
653 if h < 0.0 {
654 h = 0.0;
655 }
656 Rectangle {
657 x: cx - w / 2.0,
658 y: cy - h / 2.0,
659 width: w,
660 height: h,
661 }
662}
663
664#[derive(Clone, Copy, Debug, PartialEq, Eq)]
666pub enum AlignKind {
667 None,
668 Min,
669 Max,
670 Center,
671}
672
673#[derive(Clone, Copy, Debug)]
675pub struct Alignment {
676 pub horizontal: AlignKind,
677 pub vertical: AlignKind,
678}
679
680impl Default for Alignment {
681 fn default() -> Self {
682 Self {
683 horizontal: AlignKind::None,
684 vertical: AlignKind::None,
685 }
686 }
687}
688
689pub fn align(rects: &[Rectangle], options: Alignment) -> Vec<Rectangle> {
691 if rects.len() < 2 {
692 return rects.to_vec();
693 }
694 let bbox = union(rects);
695 rects
696 .iter()
697 .map(|r| {
698 let mut n = *r;
699 match options.horizontal {
700 AlignKind::Min => n.x = bbox.x,
701 AlignKind::Max => n.x = bbox.x + bbox.width - r.width,
702 AlignKind::Center => n.x = bbox.x + (bbox.width - r.width) / 2.0,
703 AlignKind::None => {}
704 }
705 match options.vertical {
706 AlignKind::Min => n.y = bbox.y,
707 AlignKind::Max => n.y = bbox.y + bbox.height - r.height,
708 AlignKind::Center => n.y = bbox.y + (bbox.height - r.height) / 2.0,
709 AlignKind::None => {}
710 }
711 n
712 })
713 .collect()
714}
715
716pub fn align_a(a: Rectangle, b: Rectangle, options: Alignment) -> Rectangle {
718 let mut r = a;
719 match options.horizontal {
720 AlignKind::Min => r.x = b.x,
721 AlignKind::Max => r.x = b.x + b.width - a.width,
722 AlignKind::Center => r.x = b.x + (b.width - a.width) / 2.0,
723 AlignKind::None => {}
724 }
725 match options.vertical {
726 AlignKind::Min => r.y = b.y,
727 AlignKind::Max => r.y = b.y + b.height - a.height,
728 AlignKind::Center => r.y = b.y + (b.height - a.height) / 2.0,
729 AlignKind::None => {}
730 }
731 r
732}