binpack2d/
rectangle.rs

1//! A structure that represents the placement of a single object in a bin.
2
3use super::dimension::{self, Dimension};
4use std::fmt::{Display, Formatter};
5
6/// `Rectangle` specifies an area in a coordinate space that is defined an upper-left point,
7/// as defined by `x` and `y`, and the dimensions, defined by the [`Dimension`] object.
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
9pub struct Rectangle {
10    x: i32,
11    y: i32,
12    dim: Dimension,
13}
14
15impl Rectangle {
16    /// Creates a new `Rect` whose upper-left corner is defined by `x` and `y`, and whose `width`
17    /// and `height` are defined by the [`Dimension`] type.
18    pub fn new(x: i32, y: i32, dim: Dimension) -> Rectangle {
19        Rectangle { x, y, dim }
20    }
21
22    /// Returns the x coordinate of the bounding `Rectangle`.
23    pub fn x(&self) -> i32 {
24        self.x
25    }
26
27    /// Returns the x coordinate of the bounding `Rectangle`, including padding.
28    pub(crate) fn x_total(&self) -> i32 {
29        self.x - self.dim.padding
30    }
31
32    /// Returns the y coordinate of the bounding `Rectangle`.
33    pub fn y(&self) -> i32 {
34        self.y
35    }
36
37    /// Returns the y coordinate of the bounding `Rectangle`, including padding.
38    pub(crate) fn y_total(&self) -> i32 {
39        self.y - self.dim.padding
40    }
41
42    /// Moves this `Rectangle` horizontally to the location specified by x.
43    pub fn set_x(&mut self, x: i32) {
44        self.x = x;
45    }
46
47    /// Moves this `Rectangle` horizontally to the location specified by x.
48    ///
49    /// Includes padding in the calculation.
50    pub(crate) fn set_x_total(&mut self, x: i32) {
51        self.x = x + self.dim.padding;
52    }
53
54    /// Moves this `Rectangle` vertically to the location specified by y.
55    pub fn set_y(&mut self, y: i32) {
56        self.y = y;
57    }
58
59    /// Moves this `Rectangle` vertically to the location specified by y.
60    ///
61    /// Includes padding in the calculation.
62    pub(crate) fn set_y_total(&mut self, y: i32) {
63        self.y = y + self.dim.padding;
64    }
65
66    /// Moves this `Rectangle` to the location specified by x and y.
67    pub fn set_location(&mut self, x: i32, y: i32) {
68        self.x = x;
69        self.y = y;
70    }
71
72    /// Moves this `Rectangle` to the location specified by x and y.
73    ///
74    /// Includes padding in the calculation.
75    pub(crate) fn set_location_total(&mut self, x: i32, y: i32) {
76        self.x = x + self.dim.padding;
77        self.y = y + self.dim.padding;
78    }
79
80    /// Translates this `Rectangle` the indicated distance, to the right along the X axis,
81    /// and downward along the Y axis.
82    ///
83    /// **Note:** Underflow and overflow are bound by [`i32::MIN`] and [`i32::MAX`] respectively.
84    pub fn translate(&mut self, dx: i32, dy: i32) {
85        if dx != 0 {
86            self.x = self.x.saturating_add(dx);
87        }
88        if dy != 0 {
89            self.y = self.y.saturating_add(dy);
90        }
91    }
92
93    /// Returns the identifier associated with the `Rectangle`.
94    pub fn id(&self) -> isize {
95        self.dim.id()
96    }
97
98    /// Returns the width of the bounding `Rectangle` without padding.
99    pub fn width(&self) -> i32 {
100        self.dim.width()
101    }
102
103    /// Returns the width of the bounding `Rectangle` with padding.
104    pub(crate) fn width_total(&self) -> i32 {
105        self.dim.width_total()
106    }
107
108    /// Returns the height of the bounding `Rectangle` without padding.
109    pub fn height(&self) -> i32 {
110        self.dim.height()
111    }
112
113    /// Returns the height of the bounding `Rectangle` with padding.
114    pub(crate) fn height_total(&self) -> i32 {
115        self.dim.height_total()
116    }
117
118    /// Returns an immutable reference to the associated [`Dimension`] object.
119    pub fn dim(&self) -> &Dimension {
120        &self.dim
121    }
122
123    /// Returns a mutable reference to the associated [`Dimension`] object.
124    pub fn dim_mut(&mut self) -> &mut Dimension {
125        &mut self.dim
126    }
127
128    /// Returns `true` if `width` or `height` of the `Rectangle` is 0, and `false` otherwise.
129    ///
130    /// Padding is not included in the check.
131    pub fn is_empty(&self) -> bool {
132        self.dim.is_empty()
133    }
134
135    /// Checks whether or not this `Rectangle` entirely contains the specified `Rectangle`.
136    ///
137    /// Padding is not included in the check.
138    pub fn contains(&self, rect: &Rectangle) -> bool {
139        rect.x >= self.x
140            && rect.y >= self.y
141            && rect.x + rect.width() <= self.x + self.width()
142            && rect.y + rect.height() <= self.y + self.height()
143    }
144
145    /// Checks whether or not this `Rectangle` entirely contains the specified `Rectangle`.
146    ///
147    /// Padding is included in the check.
148    pub(crate) fn contains_total(&self, rect: &Rectangle) -> bool {
149        rect.x_total() >= self.x_total()
150            && rect.y_total() >= self.y_total()
151            && rect.x_total() + rect.width_total() <= self.x_total() + self.width_total()
152            && rect.y_total() + rect.height_total() <= self.y_total() + self.height_total()
153    }
154
155    /// Checks whether or not this `Rectangle` and the specified `Rectangle` intersect.
156    ///
157    /// Padding is not included in the check.
158    pub fn intersects(&self, rect: &Rectangle) -> bool {
159        let mut tw = self.width();
160        let mut th = self.height();
161        let mut rw = rect.width();
162        let mut rh = rect.height();
163        if rw == 0 || rh == 0 || tw == 0 || th == 0 {
164            return false;
165        }
166
167        let tx = self.x;
168        let ty = self.y;
169        let rx = rect.x;
170        let ry = rect.y;
171        rw += rx;
172        rh += ry;
173        tw += tx;
174        th += ty;
175        // overflow || intersect
176        (rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry)
177    }
178
179    /// Computes the union of this `Rectangle` with the specified `Rectangle`.
180    ///
181    /// `rect` specifies the second rectangle to use for the union.
182    ///
183    /// `id` will be used as new identifier for the dimension included the returned `Rectangle`.
184    /// An identifier is autogenerated if if `None` is specified.
185    ///
186    /// The greater padding of the source `Rectangle`s is applied to the returned `Rectangle`.
187    ///
188    /// Returns a new `Rectangle` that represents the union of the two rectangles.
189    pub fn union(&self, rect: &Rectangle, id: Option<isize>) -> Self {
190        let min_x = self.x.min(rect.x);
191        let min_y = self.y.min(rect.y);
192
193        let max_x = (self.x + self.width()).max(rect.x + rect.width());
194        let width = max_x - min_x;
195
196        let max_y = (self.y + self.height()).max(rect.y + rect.height());
197        let height = max_y - min_y;
198
199        let id = id.unwrap_or_else(dimension::get_unique_id);
200
201        let padding = self.dim.padding().max(rect.dim.padding());
202
203        Self {
204            x: min_x,
205            y: min_y,
206            dim: Dimension::with_id(id, width, height, padding),
207        }
208    }
209}
210
211impl From<Dimension> for Rectangle {
212    fn from(value: Dimension) -> Self {
213        Rectangle::new(0, 0, value)
214    }
215}
216
217impl Display for Rectangle {
218    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
219        write!(
220            f,
221            "Rectangle(x: {}, y: {}, dim: {})",
222            self.x, self.y, self.dim
223        )
224    }
225}
226
227#[cfg(test)]
228mod tests;