godot_core/builtin/rect2.rs
1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use godot_ffi as sys;
9use sys::{ffi_methods, ExtVariantType, GodotFfi};
10
11use crate::builtin::math::ApproxEq;
12use crate::builtin::{real, Rect2i, Side, Vector2};
13
14/// 2D axis-aligned bounding box.
15///
16/// `Rect2` consists of a position, a size, and several utility functions. It is typically used for
17/// fast overlap tests.
18///
19/// # All bounding-box types
20///
21/// | Dimension | Floating-point | Integer |
22/// |-----------|-----------------|--------------|
23/// | 2D | **`Rect2`** | [`Rect2i`] |
24/// | 3D | [`Aabb`] | |
25///
26/// <br>You can convert to `Rect2i` using [`cast_int()`][Self::cast_int].
27///
28/// [`Aabb`]: crate::builtin::Aabb
29///
30/// # Soft invariants
31/// `Rect2` requires non-negative size for certain operations, which is validated only on a best-effort basis. Violations may
32/// cause panics in Debug mode. See also [_Builtin API design_](../__docs/index.html#6-public-fields-and-soft-invariants).
33///
34/// # Godot docs
35/// [`Rect2` (stable)](https://docs.godotengine.org/en/stable/classes/class_rect2.html)
36#[derive(Default, Copy, Clone, PartialEq, Debug)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38#[repr(C)]
39pub struct Rect2 {
40 pub position: Vector2,
41 pub size: Vector2,
42}
43
44impl Rect2 {
45 /// Create a new `Rect2` from a position and a size.
46 ///
47 /// _Godot equivalent: `Rect2(Vector2 position, Vector2 size)`_
48 #[inline]
49 pub const fn new(position: Vector2, size: Vector2) -> Self {
50 Self { position, size }
51 }
52
53 /// Create a new `Rect2` with the first corner at `position` and the opposite corner at `end`.
54 #[inline]
55 pub fn from_corners(position: Vector2, end: Vector2) -> Self {
56 // Cannot use floating point arithmetic in const functions.
57 Self::new(position, end - position)
58 }
59
60 /// Create a new `Rect2` from four reals representing position `(x,y)` and size `(width,height)`.
61 ///
62 /// _Godot equivalent: `Rect2(float x, float y, float width, float height)`_
63 #[inline]
64 pub const fn from_components(x: real, y: real, width: real, height: real) -> Self {
65 Self {
66 position: Vector2::new(x, y),
67 size: Vector2::new(width, height),
68 }
69 }
70
71 /// Create a new `Rect2i` from a `Rect2`, using `as` for `real` to `i32` conversions.
72 ///
73 /// _Godot equivalent: `Rect2i(Rect2 from)`_
74 #[inline]
75 pub const fn cast_int(self) -> Rect2i {
76 Rect2i {
77 position: self.position.cast_int(),
78 size: self.size.cast_int(),
79 }
80 }
81
82 /// Returns a rectangle with the same geometry, with top-left corner as `position` and non-negative size.
83 #[inline]
84 pub fn abs(self) -> Self {
85 Self {
86 position: self.position + self.size.coord_min(Vector2::ZERO),
87 size: self.size.abs(),
88 }
89 }
90
91 /// Whether `self` covers at least the entire area of `b` (and possibly more).
92 #[inline]
93 pub fn encloses(self, b: Rect2) -> bool {
94 let end = self.end();
95 let b_end = b.end();
96
97 b.position.x >= self.position.x
98 && b.position.y >= self.position.y
99 && b_end.x <= end.x
100 && b_end.y <= end.y
101 }
102
103 /// Returns a copy of this rectangle expanded to include a given point.
104 ///
105 /// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
106 /// to get a positive sized equivalent rectangle for expanding.
107 #[inline]
108 pub fn expand(self, to: Vector2) -> Self {
109 self.merge(Rect2::new(to, Vector2::ZERO))
110 }
111
112 /// Returns a larger rectangle that contains this `Rect2` and `b`.
113 ///
114 /// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
115 /// to get a positive sized equivalent rectangle for merging.
116 #[inline]
117 pub fn merge(self, b: Self) -> Self {
118 let position = self.position.coord_min(b.position);
119 let end = self.end().coord_max(b.end());
120
121 Self::from_corners(position, end)
122 }
123
124 /// Returns the area of the rectangle.
125 #[inline]
126 pub fn area(self) -> real {
127 self.size.x * self.size.y
128 }
129
130 /// Returns the center of the Rect2, which is equal to `position + (size / 2)`.
131 #[inline]
132 pub fn center(self) -> Vector2 {
133 self.position + (self.size / 2.0)
134 }
135
136 /// Returns a copy of the Rect2 grown by the specified `amount` on all sides.
137 #[inline]
138 #[must_use]
139 pub fn grow(self, amount: real) -> Self {
140 let position = self.position - Vector2::new(amount, amount);
141 let size = self.size + Vector2::new(amount, amount) * 2.0;
142
143 Self { position, size }
144 }
145
146 /// Returns a copy of the Rect2 grown by the specified amount on each side individually.
147 #[inline]
148 pub fn grow_individual(self, left: real, top: real, right: real, bottom: real) -> Self {
149 Self::from_components(
150 self.position.x - left,
151 self.position.y - top,
152 self.size.x + left + right,
153 self.size.y + top + bottom,
154 )
155 }
156
157 /// Returns a copy of the `Rect2` grown by the specified `amount` on the specified `RectSide`.
158 ///
159 /// `amount` may be negative, but care must be taken: If the resulting `size` has
160 /// negative components the computation may be incorrect.
161 #[inline]
162 pub fn grow_side(self, side: Side, amount: real) -> Self {
163 match side {
164 Side::LEFT => self.grow_individual(amount, 0.0, 0.0, 0.0),
165 Side::TOP => self.grow_individual(0.0, amount, 0.0, 0.0),
166 Side::RIGHT => self.grow_individual(0.0, 0.0, amount, 0.0),
167 Side::BOTTOM => self.grow_individual(0.0, 0.0, 0.0, amount),
168 }
169 }
170
171 /// Returns `true` if the Rect2 has area, and `false` if the Rect2 is linear, empty, or has a negative size. See also `get_area`.
172 #[inline]
173 pub fn has_area(self) -> bool {
174 self.size.x > 0.0 && self.size.y > 0.0
175 }
176
177 /// Returns `true` if the Rect2 contains a point (excluding right/bottom edges).
178 ///
179 /// By convention, the right and bottom edges of the Rect2 are considered exclusive, so points on these edges are not included.
180 ///
181 /// Note: This method is not reliable for Rect2 with a negative size. Use `abs` to get a positive sized equivalent rectangle to check for contained points.
182 #[inline]
183 #[doc(alias = "has_point")]
184 pub fn contains_point(self, point: Vector2) -> bool {
185 let point = point - self.position;
186
187 point.abs() == point && point.x < self.size.x && point.y < self.size.y
188 }
189
190 /// Returns the intersection of this Rect2 and `b`. If the rectangles do not intersect, an empty Rect2 is returned.
191 #[inline]
192 pub fn intersect(self, b: Self) -> Option<Self> {
193 if !self.intersects(b) {
194 return None;
195 }
196
197 let mut rect = b;
198 rect.position = rect.position.coord_max(self.position);
199
200 let end = self.end();
201 let end_b = b.end();
202 rect.size = end.coord_min(end_b) - rect.position;
203
204 Some(rect)
205 }
206
207 /// Checks whether two rectangles have at least one point in common.
208 ///
209 /// Also returns `true` if the rects only touch each other (share a point/edge).
210 /// See [`intersects_exclude_borders`][Self::intersects_exclude_borders] if you want to return `false` in that case.
211 ///
212 /// _Godot equivalent: `Rect2.intersects(Rect2 b, bool include_borders = true)`_
213 #[inline]
214 pub fn intersects(self, b: Self) -> bool {
215 let end = self.end();
216 let end_b = b.end();
217
218 self.position.x <= end_b.x
219 && end.x >= b.position.x
220 && self.position.y <= end_b.y
221 && end.y >= b.position.y
222 }
223
224 /// Checks whether two rectangles have at least one _inner_ point in common (not on the borders).
225 ///
226 /// Returns `false` if the rects only touch each other (share a point/edge).
227 /// See [`intersects`][Self::intersects] if you want to return `true` in that case.
228 ///
229 /// _Godot equivalent: `Rect2.intersects(AABB b, bool include_borders = false)`_
230 #[inline]
231 pub fn intersects_exclude_borders(self, b: Self) -> bool {
232 let end = self.end();
233 let end_b = b.end();
234
235 self.position.x < end_b.x
236 && end.x > b.position.x
237 && self.position.y < end_b.y
238 && end.y > b.position.y
239 }
240
241 /// Returns `true` if this Rect2 is finite, by calling `@GlobalScope.is_finite` on each component.
242 #[inline]
243 pub fn is_finite(self) -> bool {
244 self.position.is_finite() && self.size.is_finite()
245 }
246
247 /// The end of the `Rect2` calculated as `position + size`.
248 #[inline]
249 pub fn end(self) -> Vector2 {
250 self.position + self.size
251 }
252
253 /// Set size based on desired end-point.
254 #[inline]
255 pub fn set_end(&mut self, end: Vector2) {
256 self.size = end - self.position
257 }
258
259 /// Assert that the size of the `Rect2` is not negative.
260 ///
261 /// Certain functions will fail to give a correct result if the size is negative.
262 #[inline]
263 pub fn assert_nonnegative(self) {
264 assert!(
265 self.size.x >= 0.0 && self.size.y >= 0.0,
266 "size {:?} is negative",
267 self.size
268 );
269 }
270}
271
272// SAFETY:
273// This type is represented as `Self` in Godot, so `*mut Self` is sound.
274unsafe impl GodotFfi for Rect2 {
275 const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::RECT2);
276
277 ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
278}
279
280crate::meta::impl_godot_as_self!(Rect2: ByValue);
281
282impl ApproxEq for Rect2 {
283 /// Returns if the two `Rect2`s are approximately equal, by comparing `position` and `size` separately.
284 #[inline]
285 fn approx_eq(&self, other: &Self) -> bool {
286 Vector2::approx_eq(&self.position, &other.position)
287 && Vector2::approx_eq(&self.size, &other.size)
288 }
289}
290
291impl std::fmt::Display for Rect2 {
292 /// Formats `Rect2` to match Godot's string representation.
293 ///
294 /// # Example
295 /// ```
296 /// use godot::prelude::*;
297 /// let rect = Rect2::new(Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0));
298 /// assert_eq!(format!("{}", rect), "[P: (0, 0), S: (1, 1)]");
299 /// ```
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 // godot output be like:
302 // [P: (0, 0), S: (0, 0)]
303 write!(f, "[P: {}, S: {}]", self.position, self.size)
304 }
305}
306
307#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
308mod test {
309 #[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
310 #[test]
311 fn serde_roundtrip() {
312 let rect = super::Rect2::default();
313 let expected_json = "{\"position\":{\"x\":0.0,\"y\":0.0},\"size\":{\"x\":0.0,\"y\":0.0}}";
314
315 crate::builtin::test_utils::roundtrip(&rect, expected_json);
316 }
317}