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}