Skip to content

Commit d7c43bb

Browse files
committed
Reimplement Rect2 functions
1 parent 7dbd80f commit d7c43bb

File tree

3 files changed

+283
-4
lines changed

3 files changed

+283
-4
lines changed

godot-core/src/builtin/rect2.rs

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

10-
use super::{real, Rect2i, Vector2};
10+
use super::{real, Rect2i, RectSide, Vector2};
1111

1212
/// 2D axis-aligned bounding box.
1313
///
@@ -45,6 +45,184 @@ impl Rect2 {
4545
}
4646
}
4747

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

@@ -90,7 +268,6 @@ impl Rect2 {
90268
self.position.is_equal_approx(other.position) && self.size.is_equal_approx(other.size)
91269
}
92270

93-
/* Add in when `Rect2::abs()` is implemented.
94271
/// Assert that the size of the `Rect2` is not negative.
95272
///
96273
/// Certain functions will fail to give a correct result if the size is negative.
@@ -102,7 +279,6 @@ impl Rect2 {
102279
self.size
103280
);
104281
}
105-
*/
106282
}
107283

108284
// SAFETY:

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod option_ffi_test;
2929
mod packed_array_test;
3030
mod projection_test;
3131
mod quaternion_test;
32+
mod rect2_test;
3233
mod rect2i_test;
3334
mod rid_test;
3435
mod singleton_test;

itest/rust/src/rect2_test.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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::prelude::{inner::InnerRect2, *};
10+
11+
#[itest]
12+
fn rect2_equiv_unary() {
13+
let test_rects = [
14+
Rect2::from_components(0.2, 0.3, 1.5, 0.9),
15+
Rect2::from_components(0.2, 0.3, 1.5, 1.9),
16+
Rect2::from_components(0.2, 0.3, 1.0, 1.9),
17+
Rect2::from_components(4.2, 4.3, 1.5, 1.9),
18+
Rect2::from_components(8.2, 8.3, 2.5, 2.9),
19+
Rect2::from_components(8.2, 8.3, 2.5, 3.9),
20+
];
21+
let test_vectors = [
22+
Vector2::ZERO,
23+
Vector2::new(0.0, 10.0),
24+
Vector2::new(10.0, 0.0),
25+
Vector2::new(10.0, 10.0),
26+
];
27+
let test_reals = [0.0, 1.0, 10.0, 32.0];
28+
let test_sides = [
29+
RectSide::Left,
30+
RectSide::Top,
31+
RectSide::Right,
32+
RectSide::Bottom,
33+
];
34+
35+
fn evaluate_mappings<T>(key: &str, a: T, b: T)
36+
where
37+
T: PartialEq + Debug,
38+
{
39+
assert_eq!(a, b, "{}: outer != inner ({:?} != {:?})", key, a, b);
40+
}
41+
42+
for a in test_rects {
43+
let inner_a = InnerRect2::from_outer(&a);
44+
45+
evaluate_mappings("abs", a.abs(), inner_a.abs());
46+
evaluate_mappings("area", a.area() as f64, inner_a.get_area());
47+
evaluate_mappings("center", a.center(), inner_a.get_center());
48+
evaluate_mappings("has_area", a.has_area(), inner_a.has_area());
49+
50+
for b in test_rects {
51+
evaluate_mappings("encloses", a.encloses(b), inner_a.encloses(b));
52+
evaluate_mappings(
53+
"intersects",
54+
a.intersects(b, true),
55+
inner_a.intersects(b, true),
56+
);
57+
// Check intersection without considering borders
58+
evaluate_mappings(
59+
"intersects",
60+
a.intersects(b, false),
61+
inner_a.intersects(b, false),
62+
);
63+
evaluate_mappings(
64+
"intersection",
65+
a.intersection(b).unwrap_or_default(),
66+
inner_a.intersection(b),
67+
);
68+
evaluate_mappings("merge", a.merge(b), inner_a.merge(b));
69+
}
70+
71+
for b in test_vectors {
72+
evaluate_mappings("expand", a.expand(b), inner_a.expand(b));
73+
evaluate_mappings("has_point", a.has_point(b), inner_a.has_point(b));
74+
}
75+
76+
for b in test_reals {
77+
evaluate_mappings("grow", a.grow(b as f32), inner_a.grow(b));
78+
79+
for c in test_reals {
80+
for d in test_reals {
81+
for e in test_reals {
82+
evaluate_mappings(
83+
"grow_individual",
84+
a.grow_individual(b as f32, c as f32, d as f32, e as f32),
85+
inner_a.grow_individual(b, c, d, e),
86+
);
87+
}
88+
}
89+
}
90+
}
91+
92+
for b in test_sides {
93+
for c in test_reals {
94+
evaluate_mappings(
95+
"grow_side",
96+
a.grow_side(b, c as f32),
97+
inner_a.grow_side(b as i64, c),
98+
);
99+
}
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)