Skip to main content

pebbles/
rect.rs

1use crate::raise;
2use crate::{PebblesError, PebblesErrorDetails};
3use rand_distr::{Triangular, Distribution};
4
5/// `Rect` is a struct that represents a rectangle.
6///
7/// It has four fields: `x`, `y`, `width`, and `height`.
8/// `x` and `y` represent the coordinates of the top left corner of the rectangle.
9/// `width` and `height` represent the dimensions of the rectangle.
10#[derive(PartialEq)]
11#[derive(Debug)]
12pub struct Rect {
13    pub x:i64,
14    pub y:i64,
15    pub width:i64,
16    pub height:i64,
17}
18
19impl Rect {
20    /// Returns a random point within the rectangle using a triangular distribution.
21    ///
22    /// The point is relative to the top left corner of the rectangle (i.e., local to the rectangle).
23    /// The distribution is triangular with the mode at the center of the rectangle.
24    ///
25    /// # Errors
26    ///
27    /// Returns `PebblesError::MathError` if there is an error generating the triangular distribution.
28    pub fn local_point(&self) -> Result<(i64, i64), PebblesError> {
29        let mut rng = rand::thread_rng();
30        let triangular_x = Triangular::new(0.0, self.width as f64, self.width as f64 / 2.0)
31            .or_else(|error| raise!(PebblesError::MathError, format!(
32                "Error while generating Triangular range: /
33                {}", error)))?;
34        let triangular_y = Triangular::new(0.0, self.height as f64, self.height as f64 / 2.0)
35            .or_else(|error| raise!(PebblesError::MathError, format!(
36                "Error while generating Triangular range: /
37                {}", error)))?;
38        Ok((triangular_x.sample(&mut rng) as i64, triangular_y.sample(&mut rng) as i64))
39    }
40
41    /// Returns a random point within the rectangle using a triangular distribution.
42    ///
43    /// The point is relative to the origin of the coordinate system (i.e., global).
44    /// The distribution is triangular with the mode at the center of the rectangle.
45    ///
46    /// # Errors
47    ///
48    /// Returns `PebblesError::MathError` if there is an error generating the triangular distribution.
49    pub fn world_point(&self) -> Result<(i64, i64), PebblesError> {
50        let (x, y) = self.local_point()?;
51        Ok((self.x + x, self.y + y))
52    }
53
54    pub fn overlaps(&self, other: &Rect) -> bool {
55        let self_right = self.x + self.width;
56        let self_bottom = self.y + self.height;
57        let other_right = other.x + other.width;
58        let other_bottom = other.y + other.height;
59    
60        if self.x < other_right && self_right > other.x && self.y < other_bottom && self_bottom > other.y {
61            return true;
62        }
63    
64        false
65    }
66
67    /// Returns a new `Rect` that represents the overlapping area of two rectangles.
68    ///
69    /// If the rectangles do not overlap, it returns `None`.
70    pub fn overlapping_rect(&self, other: &Rect) -> Option<Rect> {
71        // Calculate the coordinates of the overlapping rectangle
72        let x = std::cmp::max(self.x, other.x);
73        let y = std::cmp::max(self.y, other.y);
74        let right = std::cmp::min(self.x + self.width, other.x + other.width);
75        let bottom = std::cmp::min(self.y + self.height, other.y + other.height);
76
77        // Check if the rectangles overlap
78        if right > x && bottom > y {
79            // If they overlap, return the overlapping rectangle
80            Some(Rect {
81                x,
82                y,
83                width: right - x,
84                height: bottom - y,
85            })
86        } else {
87            // If they do not overlap, return `None`
88            None
89        }
90    }
91}
92
93impl std::fmt::Display for Rect {
94    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
95        write!(f, "{{x:{}, y:{}, width:{}, height:{}}}", self.x, self.y, self.width, self.height)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_local_point() {
105        let rect = Rect {
106            x: 0,
107            y: 0,
108            width: 10,
109            height: 10,
110        };
111
112        let result = rect.local_point();
113        assert!(result.is_ok());
114
115        let point = result.unwrap();
116        assert!(point.0 >= 0 && point.0 <= 10);
117        assert!(point.1 >= 0 && point.1 <= 10);
118    }
119
120    #[test]
121    fn test_world_point() {
122        let rect = Rect {
123            x: 5,
124            y: 5,
125            width: 10,
126            height: 10,
127        };
128
129        let result = rect.world_point();
130        assert!(result.is_ok());
131
132        let point = result.unwrap();
133        assert!(point.0 >= 5 && point.0 <= 15);
134        assert!(point.1 >= 5 && point.1 <= 15);
135    }
136    #[test]
137    fn test_overlaps() {
138        let rect1 = Rect { x: 0, y: 0, width: 10, height: 10 };
139        let rect2 = Rect { x: 5, y: 5, width: 10, height: 10 };
140        let rect3 = Rect { x: 20, y: 20, width: 10, height: 10 };
141
142        assert!(rect1.overlaps(&rect2), "Rect1 should overlap with Rect2");
143        assert!(!rect1.overlaps(&rect3), "Rect1 should not overlap with Rect3");
144    }
145
146    #[test]
147    fn test_overlapping_rect() {
148        let rect1 = Rect { x: 0, y: 0, width: 10, height: 10 };
149        let rect2 = Rect { x: 5, y: 5, width: 10, height: 10 };
150        let rect3 = Rect { x: 20, y: 20, width: 10, height: 10 };
151
152        // Test overlapping rectangles
153        if let Some(overlap) = rect1.overlapping_rect(&rect2) {
154            assert_eq!(overlap, Rect { x: 5, y: 5, width: 5, height: 5 }, "The overlapping area of Rect1 and Rect2 is incorrect");
155        } else {
156            panic!("Rect1 should overlap with Rect2");
157        }
158
159        // Test non-overlapping rectangles
160        assert!(rect1.overlapping_rect(&rect3).is_none(), "Rect1 should not overlap with Rect3");
161    }
162}