Skip to content

Commit 2eeff84

Browse files
juliohqBromeon
authored andcommitted
Reimplement Rect2 functions
1 parent 00a3005 commit 2eeff84

File tree

3 files changed

+290
-4
lines changed

3 files changed

+290
-4
lines changed

godot-core/src/builtin/rect2.rs

Lines changed: 180 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use godot_ffi as sys;
88
use sys::{ffi_methods, GodotFfi};
99

1010
use crate::builtin::math::ApproxEq;
11-
use crate::builtin::{real, Rect2i, Vector2};
11+
use crate::builtin::{real, Rect2i, RectSide, Vector2};
1212

1313
/// 2D axis-aligned bounding box.
1414
///
@@ -46,6 +46,184 @@ impl Rect2 {
4646
}
4747
}
4848

49+
/// Returns a rectangle with the same geometry, with top-left corner as `position` and non-negative size.
50+
///
51+
/// _Godot equivalent: `Rect2.abs()`_
52+
#[inline]
53+
pub fn abs(&self) -> Self {
54+
Self {
55+
position: self.position + self.size.coord_min(Vector2::ZERO),
56+
size: self.size.abs(),
57+
}
58+
}
59+
60+
/// Whether `self` covers at least the entire area of `b` (and possibly more).
61+
///
62+
/// _Godot equivalent: `Rect2.encloses(Rect2 b)`_
63+
#[inline]
64+
pub fn encloses(&self, b: Rect2) -> bool {
65+
let end = self.end();
66+
let b_end = b.end();
67+
68+
b.position.x >= self.position.x
69+
&& b.position.y >= self.position.y
70+
&& b_end.x <= end.x
71+
&& b_end.y <= end.y
72+
}
73+
74+
/// Returns a copy of this rectangle expanded to include a given point.
75+
///
76+
/// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
77+
/// to get a positive sized equivalent rectangle for expanding.
78+
///
79+
/// _Godot equivalent: `Rect2.expand(Vector2 to)`_
80+
#[inline]
81+
pub fn expand(&self, to: Vector2) -> Self {
82+
self.merge(Rect2::new(to, Vector2::ZERO))
83+
}
84+
85+
/// Returns a larger rectangle that contains this `Rect2` and `b`.
86+
///
87+
/// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
88+
/// to get a positive sized equivalent rectangle for merging.
89+
///
90+
/// _Godot equivalent: `Rect2.merge(Rect2 b)`_
91+
#[inline]
92+
pub fn merge(&self, b: Self) -> Self {
93+
let position = self.position.coord_min(b.position);
94+
let end = self.end().coord_max(b.end());
95+
96+
Self::from_corners(position, end)
97+
}
98+
99+
/// Returns the area of the rectangle.
100+
///
101+
/// _Godot equivalent: `Rect2.get_area()`_
102+
#[doc(alias = "get_area")]
103+
#[inline]
104+
pub fn area(&self) -> real {
105+
self.size.x * self.size.y
106+
}
107+
108+
/// Returns the center of the Rect2, which is equal to `position + (size / 2)`.
109+
///
110+
/// _Godot equivalent: `Rect2.get_center()`_
111+
#[doc(alias = "get_center")]
112+
#[inline]
113+
pub fn center(&self) -> Vector2 {
114+
self.position + (self.size / 2.0)
115+
}
116+
117+
/// Returns a copy of the Rect2 grown by the specified `amount` on all sides.
118+
///
119+
/// _Godot equivalent: `Rect2.grow(float amount)`_
120+
#[inline]
121+
#[must_use]
122+
pub fn grow(&self, amount: real) -> Self {
123+
let position = self.position - Vector2::new(amount, amount);
124+
let size = self.size + Vector2::new(amount, amount) * 2.0;
125+
126+
Self { position, size }
127+
}
128+
129+
/// Returns a copy of the Rect2 grown by the specified amount on each side individually.
130+
///
131+
/// _Godot equivalent: `Rect2.grow_individual(float left, float top, float right, float bottom)`_
132+
#[inline]
133+
pub fn grow_individual(&self, left: real, top: real, right: real, bottom: real) -> Self {
134+
Self::from_components(
135+
self.position.x - left,
136+
self.position.y - top,
137+
self.size.x + left + right,
138+
self.size.y + top + bottom,
139+
)
140+
}
141+
142+
/// Returns a copy of the `Rect2` grown by the specified `amount` on the specified `RectSide`.
143+
///
144+
/// `amount` may be negative, but care must be taken: If the resulting `size` has
145+
/// negative components the computation may be incorrect.
146+
///
147+
/// _Godot equivalent: `Rect2.grow_side(int side, float amount)`_
148+
#[inline]
149+
pub fn grow_side(&self, side: RectSide, amount: real) -> Self {
150+
match side {
151+
RectSide::Left => self.grow_individual(amount, 0.0, 0.0, 0.0),
152+
RectSide::Top => self.grow_individual(0.0, amount, 0.0, 0.0),
153+
RectSide::Right => self.grow_individual(0.0, 0.0, amount, 0.0),
154+
RectSide::Bottom => self.grow_individual(0.0, 0.0, 0.0, amount),
155+
}
156+
}
157+
158+
/// Returns `true` if the Rect2 has area, and `false` if the Rect2 is linear, empty, or has a negative size. See also `get_area`.
159+
///
160+
/// _Godot equivalent: `Rect2.has_area()`_
161+
#[inline]
162+
pub fn has_area(&self) -> bool {
163+
self.size.x > 0.0 && self.size.y > 0.0
164+
}
165+
166+
/// Returns `true` if the Rect2 contains a point. By convention, the right and bottom edges of the Rect2 are considered exclusive, so points on these edges are not included.
167+
///
168+
/// 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.
169+
///
170+
/// _Godot equivalent: `Rect2.has_area()`_
171+
#[inline]
172+
pub fn has_point(&self, point: Vector2) -> bool {
173+
let point = point - self.position;
174+
175+
point.abs() == point && point.x < self.size.x && point.y < self.size.y
176+
}
177+
178+
/// Returns the intersection of this Rect2 and `b`. If the rectangles do not intersect, an empty Rect2 is returned.
179+
///
180+
/// _Godot equivalent: `Rect2.intersection(Rect2 b)`_
181+
#[inline]
182+
pub fn intersection(&self, b: Self) -> Option<Self> {
183+
if !self.intersects(b, true) {
184+
return None;
185+
}
186+
187+
let mut rect = b;
188+
rect.position = rect.position.coord_max(self.position);
189+
190+
let end = self.end();
191+
let end_b = b.end();
192+
rect.size = end.coord_min(end_b) - rect.position;
193+
194+
Some(rect)
195+
}
196+
197+
/// Returns `true` if the Rect2 overlaps with `b` (i.e. they have at least one point in common).
198+
///
199+
/// If `include_borders` is `true`, they will also be considered overlapping if their borders touch, even without intersection.
200+
///
201+
/// _Godot equivalent: `Rect2.intersects(Rect2 b, bool include_borders)`_
202+
#[inline]
203+
pub fn intersects(&self, b: Self, include_borders: bool) -> bool {
204+
let end = self.end();
205+
let end_b = b.end();
206+
207+
if include_borders {
208+
self.position.x <= end_b.x
209+
&& end.x >= b.position.x
210+
&& self.position.y <= end_b.y
211+
&& end.y >= b.position.y
212+
} else {
213+
self.position.x < end_b.x
214+
&& end.x > b.position.x
215+
&& self.position.y < end_b.y
216+
&& end.y > b.position.y
217+
}
218+
}
219+
220+
/// Returns `true` if this Rect2 is finite, by calling `@GlobalScope.is_finite` on each component.
221+
///
222+
/// _Godot equivalent: `Rect2.is_finite()`_
223+
pub fn is_finite(&self) -> bool {
224+
self.position.is_finite() && self.size.is_finite()
225+
}
226+
49227
/// Create a new `Rect2` from a `Rect2i`, using `as` for `i32` to `real` conversions.
50228
///
51229
/// _Godot equivalent: `Rect2(Rect2i from)`_
@@ -62,7 +240,7 @@ impl Rect2 {
62240
pub fn from_corners(position: Vector2, end: Vector2) -> Self {
63241
Self {
64242
position,
65-
size: position + end,
243+
size: end - position,
66244
}
67245
}
68246

@@ -83,7 +261,6 @@ impl Rect2 {
83261
self.size = end - self.position
84262
}
85263

86-
/* Add in when `Rect2::abs()` is implemented.
87264
/// Assert that the size of the `Rect2` is not negative.
88265
///
89266
/// Certain functions will fail to give a correct result if the size is negative.
@@ -95,7 +272,6 @@ impl Rect2 {
95272
self.size
96273
);
97274
}
98-
*/
99275
}
100276

101277
// SAFETY:

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod packed_array_test;
3030
mod plane_test;
3131
mod projection_test;
3232
mod quaternion_test;
33+
mod rect2_test;
3334
mod rect2i_test;
3435
mod rid_test;
3536
mod signal_test;

itest/rust/src/rect2_test.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
use std::fmt::Debug;
7+
8+
use crate::itest;
9+
use godot::{
10+
builtin::{Rect2, RectSide, Vector2},
11+
prelude::inner::InnerRect2,
12+
};
13+
14+
#[itest]
15+
fn rect2_equiv_unary() {
16+
let test_rects = [
17+
Rect2::from_components(0.2, 0.3, 1.5, 0.9),
18+
Rect2::from_components(0.2, 0.3, 1.5, 1.9),
19+
Rect2::from_components(0.2, 0.3, 1.0, 1.9),
20+
Rect2::from_components(4.2, 4.3, 1.5, 1.9),
21+
Rect2::from_components(8.2, 8.3, 2.5, 2.9),
22+
Rect2::from_components(8.2, 8.3, 2.5, 3.9),
23+
];
24+
let test_vectors = [
25+
Vector2::ZERO,
26+
Vector2::new(0.0, 10.0),
27+
Vector2::new(10.0, 0.0),
28+
Vector2::new(10.0, 10.0),
29+
];
30+
let test_reals = [0.0, 1.0, 10.0, 32.0];
31+
let grow_values = [-1.0, 0.0, 1.0, 7.0];
32+
let test_sides = [
33+
RectSide::Left,
34+
RectSide::Top,
35+
RectSide::Right,
36+
RectSide::Bottom,
37+
];
38+
39+
fn check_mapping_eq<T>(context: &str, outer: T, inner: T)
40+
where
41+
T: PartialEq + Debug,
42+
{
43+
assert_eq!(
44+
outer, inner,
45+
"{context}: outer != inner ({outer:?} != {inner:?})"
46+
);
47+
}
48+
49+
for a in test_rects {
50+
let inner_a = InnerRect2::from_outer(&a);
51+
52+
check_mapping_eq("abs", a.abs(), inner_a.abs());
53+
check_mapping_eq("area", a.area() as f64, inner_a.get_area());
54+
check_mapping_eq("center", a.center(), inner_a.get_center());
55+
check_mapping_eq("has_area", a.has_area(), inner_a.has_area());
56+
57+
for b in test_rects {
58+
check_mapping_eq("encloses", a.encloses(b), inner_a.encloses(b));
59+
check_mapping_eq(
60+
"intersects",
61+
a.intersects(b, true),
62+
inner_a.intersects(b, true),
63+
);
64+
// Check intersection without considering borders
65+
check_mapping_eq(
66+
"intersects",
67+
a.intersects(b, false),
68+
inner_a.intersects(b, false),
69+
);
70+
check_mapping_eq(
71+
"intersection",
72+
a.intersection(b).unwrap_or_default(),
73+
inner_a.intersection(b),
74+
);
75+
check_mapping_eq("merge", a.merge(b), inner_a.merge(b));
76+
}
77+
78+
for b in test_vectors {
79+
check_mapping_eq("expand", a.expand(b), inner_a.expand(b));
80+
check_mapping_eq("has_point", a.has_point(b), inner_a.has_point(b));
81+
}
82+
83+
for b in grow_values {
84+
check_mapping_eq("grow", a.grow(b as f32), inner_a.grow(b));
85+
86+
for c in grow_values {
87+
for d in grow_values {
88+
for e in grow_values {
89+
check_mapping_eq(
90+
"grow_individual",
91+
a.grow_individual(b as f32, c as f32, d as f32, e as f32),
92+
inner_a.grow_individual(b, c, d, e),
93+
);
94+
}
95+
}
96+
}
97+
}
98+
99+
for b in test_sides {
100+
for c in test_reals {
101+
check_mapping_eq(
102+
"grow_side",
103+
a.grow_side(b, c as f32),
104+
inner_a.grow_side(b as i64, c),
105+
);
106+
}
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)