Skip to content

Commit

Permalink
Try #242:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] authored May 24, 2023
2 parents 81f81c6 + 2084747 commit df55343
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 4 deletions.
184 changes: 180 additions & 4 deletions godot-core/src/builtin/rect2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use super::{real, Rect2i, Vector2};
use super::{real, Rect2i, RectSide, Vector2};

/// 2D axis-aligned bounding box.
///
Expand Down Expand Up @@ -45,6 +45,184 @@ impl Rect2 {
}
}

/// Returns a rectangle with the same geometry, with top-left corner as `position` and non-negative size.
///
/// _Godot equivalent: `Rect2.abs()`_
#[inline]
pub fn abs(&self) -> Self {
Self {
position: self.position + self.size.coord_min(Vector2::ZERO),
size: self.size.abs(),
}
}

/// Whether `self` covers at least the entire area of `b` (and possibly more).
///
/// _Godot equivalent: `Rect2.encloses(Rect2 b)`_
#[inline]
pub fn encloses(&self, b: Rect2) -> bool {
let end = self.end();
let b_end = b.end();

b.position.x >= self.position.x
&& b.position.y >= self.position.y
&& b_end.x <= end.x
&& b_end.y <= end.y
}

/// Returns a copy of this rectangle expanded to include a given point.
///
/// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
/// to get a positive sized equivalent rectangle for expanding.
///
/// _Godot equivalent: `Rect2.expand(Vector2 to)`_
#[inline]
pub fn expand(&self, to: Vector2) -> Self {
self.merge(Rect2::new(to, Vector2::ZERO))
}

/// Returns a larger rectangle that contains this `Rect2` and `b`.
///
/// Note: This method is not reliable for `Rect2` with a negative size. Use [`abs`][Self::abs]
/// to get a positive sized equivalent rectangle for merging.
///
/// _Godot equivalent: `Rect2.merge(Rect2 b)`_
#[inline]
pub fn merge(&self, b: Self) -> Self {
let position = self.position.coord_min(b.position);
let end = self.end().coord_max(b.end());

Self::from_corners(position, end)
}

/// Returns the area of the rectangle.
///
/// _Godot equivalent: `Rect2.get_area()`_
#[doc(alias = "get_area")]
#[inline]
pub fn area(&self) -> real {
self.size.x * self.size.y
}

/// Returns the center of the Rect2, which is equal to `position + (size / 2)`.
///
/// _Godot equivalent: `Rect2.get_center()`_
#[doc(alias = "get_center")]
#[inline]
pub fn center(&self) -> Vector2 {
self.position + (self.size / 2.0)
}

/// Returns a copy of the Rect2 grown by the specified `amount` on all sides.
///
/// _Godot equivalent: `Rect2.grow(float amount)`_
#[inline]
#[must_use]
pub fn grow(&self, amount: real) -> Self {
let position = self.position - Vector2::new(amount, amount);
let size = self.size + Vector2::new(amount, amount) * 2.0;

Self { position, size }
}

/// Returns a copy of the Rect2 grown by the specified amount on each side individually.
///
/// _Godot equivalent: `Rect2.grow_individual(float left, float top, float right, float bottom)`_
#[inline]
pub fn grow_individual(&self, left: real, top: real, right: real, bottom: real) -> Self {
Self::from_components(
self.position.x - left,
self.position.y - top,
self.size.x + left + right,
self.size.y + top + bottom,
)
}

/// Returns a copy of the `Rect2` grown by the specified `amount` on the specified `RectSide`.
///
/// `amount` may be negative, but care must be taken: If the resulting `size` has
/// negative components the computation may be incorrect.
///
/// _Godot equivalent: `Rect2.grow_side(int side, float amount)`_
#[inline]
pub fn grow_side(&self, side: RectSide, amount: real) -> Self {
match side {
RectSide::Left => self.grow_individual(amount, 0.0, 0.0, 0.0),
RectSide::Top => self.grow_individual(0.0, amount, 0.0, 0.0),
RectSide::Right => self.grow_individual(0.0, 0.0, amount, 0.0),
RectSide::Bottom => self.grow_individual(0.0, 0.0, 0.0, amount),
}
}

/// Returns `true` if the Rect2 has area, and `false` if the Rect2 is linear, empty, or has a negative size. See also `get_area`.
///
/// _Godot equivalent: `Rect2.has_area()`_
#[inline]
pub fn has_area(&self) -> bool {
self.size.x > 0.0 && self.size.y > 0.0
}

/// 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.
///
/// 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.
///
/// _Godot equivalent: `Rect2.has_area()`_
#[inline]
pub fn has_point(&self, point: Vector2) -> bool {
let point = point - self.position;

point.abs() == point && point.x < self.size.x && point.y < self.size.y
}

/// Returns the intersection of this Rect2 and `b`. If the rectangles do not intersect, an empty Rect2 is returned.
///
/// _Godot equivalent: `Rect2.intersection(Rect2 b)`_
#[inline]
pub fn intersection(&self, b: Self) -> Option<Self> {
if !self.intersects(b, true) {
return None;
}

let mut rect = b;
rect.position = rect.position.coord_max(self.position);

let end = self.end();
let end_b = b.end();
rect.size = end.coord_min(end_b) - rect.position;

Some(rect)
}

/// Returns `true` if the Rect2 overlaps with `b` (i.e. they have at least one point in common).
///
/// If `include_borders` is `true`, they will also be considered overlapping if their borders touch, even without intersection.
///
/// _Godot equivalent: `Rect2.intersects(Rect2 b, bool include_borders)`_
#[inline]
pub fn intersects(&self, b: Self, include_borders: bool) -> bool {
let end = self.end();
let end_b = b.end();

if include_borders {
self.position.x <= end_b.x
&& end.x >= b.position.x
&& self.position.y <= end_b.y
&& end.y >= b.position.y
} else {
self.position.x < end_b.x
&& end.x > b.position.x
&& self.position.y < end_b.y
&& end.y > b.position.y
}
}

/// Returns `true` if this Rect2 is finite, by calling `@GlobalScope.is_finite` on each component.
///
/// _Godot equivalent: `Rect2.is_finite()`_
pub fn is_finite(&self) -> bool {
self.position.is_finite() && self.size.is_finite()
}

/// Create a new `Rect2` from a `Rect2i`, using `as` for `i32` to `real` conversions.
///
/// _Godot equivalent: `Rect2(Rect2i from)`_
Expand All @@ -61,7 +239,7 @@ impl Rect2 {
pub fn from_corners(position: Vector2, end: Vector2) -> Self {
Self {
position,
size: position + end,
size: end - position,
}
}

Expand Down Expand Up @@ -90,7 +268,6 @@ impl Rect2 {
self.position.is_equal_approx(other.position) && self.size.is_equal_approx(other.size)
}

/* Add in when `Rect2::abs()` is implemented.
/// Assert that the size of the `Rect2` is not negative.
///
/// Certain functions will fail to give a correct result if the size is negative.
Expand All @@ -102,7 +279,6 @@ impl Rect2 {
self.size
);
}
*/
}

// SAFETY:
Expand Down
1 change: 1 addition & 0 deletions itest/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod object_test;
mod packed_array_test;
mod projection_test;
mod quaternion_test;
mod rect2_test;
mod rect2i_test;
mod rid_test;
mod singleton_test;
Expand Down
109 changes: 109 additions & 0 deletions itest/rust/src/rect2_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
use std::fmt::Debug;

use crate::itest;
use godot::{
builtin::{Rect2, RectSide, Vector2},
prelude::inner::InnerRect2,
};

#[itest]
fn rect2_equiv_unary() {
let test_rects = [
Rect2::from_components(0.2, 0.3, 1.5, 0.9),
Rect2::from_components(0.2, 0.3, 1.5, 1.9),
Rect2::from_components(0.2, 0.3, 1.0, 1.9),
Rect2::from_components(4.2, 4.3, 1.5, 1.9),
Rect2::from_components(8.2, 8.3, 2.5, 2.9),
Rect2::from_components(8.2, 8.3, 2.5, 3.9),
];
let test_vectors = [
Vector2::ZERO,
Vector2::new(0.0, 10.0),
Vector2::new(10.0, 0.0),
Vector2::new(10.0, 10.0),
];
let test_reals = [0.0, 1.0, 10.0, 32.0];
let grow_values = [-1.0, 0.0, 1.0, 7.0];
let test_sides = [
RectSide::Left,
RectSide::Top,
RectSide::Right,
RectSide::Bottom,
];

fn check_mapping_eq<T>(context: &str, outer: T, inner: T)
where
T: PartialEq + Debug,
{
assert_eq!(
outer, inner,
"{context}: outer != inner ({outer:?} != {inner:?})"
);
}

for a in test_rects {
let inner_a = InnerRect2::from_outer(&a);

check_mapping_eq("abs", a.abs(), inner_a.abs());
check_mapping_eq("area", a.area() as f64, inner_a.get_area());
check_mapping_eq("center", a.center(), inner_a.get_center());
check_mapping_eq("has_area", a.has_area(), inner_a.has_area());

for b in test_rects {
check_mapping_eq("encloses", a.encloses(b), inner_a.encloses(b));
check_mapping_eq(
"intersects",
a.intersects(b, true),
inner_a.intersects(b, true),
);
// Check intersection without considering borders
check_mapping_eq(
"intersects",
a.intersects(b, false),
inner_a.intersects(b, false),
);
check_mapping_eq(
"intersection",
a.intersection(b).unwrap_or_default(),
inner_a.intersection(b),
);
check_mapping_eq("merge", a.merge(b), inner_a.merge(b));
}

for b in test_vectors {
check_mapping_eq("expand", a.expand(b), inner_a.expand(b));
check_mapping_eq("has_point", a.has_point(b), inner_a.has_point(b));
}

for b in grow_values {
check_mapping_eq("grow", a.grow(b as f32), inner_a.grow(b));

for c in grow_values {
for d in grow_values {
for e in grow_values {
check_mapping_eq(
"grow_individual",
a.grow_individual(b as f32, c as f32, d as f32, e as f32),
inner_a.grow_individual(b, c, d, e),
);
}
}
}
}

for b in test_sides {
for c in test_reals {
check_mapping_eq(
"grow_side",
a.grow_side(b, c as f32),
inner_a.grow_side(b as i64, c),
);
}
}
}
}

0 comments on commit df55343

Please sign in to comment.