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;