Skip to content

Commit 12c8281

Browse files
committed
Reimplement Rect2 functions
1 parent 3c81e8c commit 12c8281

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
///
@@ -44,6 +44,184 @@ impl Rect2 {
4444
}
4545
}
4646

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

@@ -89,7 +267,6 @@ impl Rect2 {
89267
self.position.is_equal_approx(other.position) && self.size.is_equal_approx(other.size)
90268
}
91269

92-
/* Add in when `Rect2::abs()` is implemented.
93270
/// Assert that the size of the `Rect2` is not negative.
94271
///
95272
/// Certain functions will fail to give a correct result if the size is negative.
@@ -101,7 +278,6 @@ impl Rect2 {
101278
self.size
102279
);
103280
}
104-
*/
105281
}
106282

107283
// SAFETY:

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod object_test;
2626
mod packed_array_test;
2727
mod projection_test;
2828
mod quaternion_test;
29+
mod rect2_test;
2930
mod rect2i_test;
3031
mod rid_test;
3132
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)