From 2a611be25fda147bad06a6170025065d510a9ed2 Mon Sep 17 00:00:00 2001 From: juliohq Date: Mon, 24 Apr 2023 08:42:14 -0300 Subject: [PATCH 1/5] Reimplement Rect2 functions --- godot-core/src/builtin/rect2.rs | 184 +++++++++++++++++++++++++++++++- itest/rust/src/lib.rs | 1 + itest/rust/src/rect2_test.rs | 102 ++++++++++++++++++ 3 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 itest/rust/src/rect2_test.rs diff --git a/godot-core/src/builtin/rect2.rs b/godot-core/src/builtin/rect2.rs index b2a89ebf3..c2c199998 100644 --- a/godot-core/src/builtin/rect2.rs +++ b/godot-core/src/builtin/rect2.rs @@ -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. /// @@ -45,6 +45,184 @@ impl Rect2 { } } + /// Returns a rectangle with equivalent position and area, modified so that the top-left corner is the origin and `width` and `height` are positive. + /// + /// _Godot equivalent: `Rect2.abs()`_ + #[inline] + pub fn abs(&self) -> Self { + Self { + position: self.position + self.size.coord_min(Vector2::ZERO), + size: self.size.abs(), + } + } + + /// 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. + /// + /// _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 { + 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)`_ @@ -61,7 +239,7 @@ impl Rect2 { pub fn from_corners(position: Vector2, end: Vector2) -> Self { Self { position, - size: position + end, + size: end - position, } } @@ -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. @@ -102,7 +279,6 @@ impl Rect2 { self.size ); } - */ } // SAFETY: diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 652528406..e5e73ddb5 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -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; diff --git a/itest/rust/src/rect2_test.rs b/itest/rust/src/rect2_test.rs new file mode 100644 index 000000000..838ea5c33 --- /dev/null +++ b/itest/rust/src/rect2_test.rs @@ -0,0 +1,102 @@ +/* + * 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::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 test_sides = [ + RectSide::Left, + RectSide::Top, + RectSide::Right, + RectSide::Bottom, + ]; + + fn evaluate_mappings(key: &str, a: T, b: T) + where + T: PartialEq + Debug, + { + assert_eq!(a, b, "{}: outer != inner ({:?} != {:?})", key, a, b); + } + + for a in test_rects { + let inner_a = InnerRect2::from_outer(&a); + + evaluate_mappings("abs", a.abs(), inner_a.abs()); + evaluate_mappings("area", a.area() as f64, inner_a.get_area()); + evaluate_mappings("center", a.center(), inner_a.get_center()); + evaluate_mappings("has_area", a.has_area(), inner_a.has_area()); + + for b in test_rects { + evaluate_mappings("encloses", a.encloses(b), inner_a.encloses(b)); + evaluate_mappings( + "intersects", + a.intersects(b, true), + inner_a.intersects(b, true), + ); + // Check intersection without considering borders + evaluate_mappings( + "intersects", + a.intersects(b, false), + inner_a.intersects(b, false), + ); + evaluate_mappings( + "intersection", + a.intersection(b).unwrap_or_default(), + inner_a.intersection(b), + ); + evaluate_mappings("merge", a.merge(b), inner_a.merge(b)); + } + + for b in test_vectors { + evaluate_mappings("expand", a.expand(b), inner_a.expand(b)); + evaluate_mappings("has_point", a.has_point(b), inner_a.has_point(b)); + } + + for b in test_reals { + evaluate_mappings("grow", a.grow(b as f32), inner_a.grow(b)); + + for c in test_reals { + for d in test_reals { + for e in test_reals { + evaluate_mappings( + "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 { + evaluate_mappings( + "grow_side", + a.grow_side(b, c as f32), + inner_a.grow_side(b as i64, c), + ); + } + } + } +} From f348bc56aa8584087e7f92cc22d7f46698cd2cc8 Mon Sep 17 00:00:00 2001 From: Julio Henrique Santos <33421921+juliohq@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:03:17 -0300 Subject: [PATCH 2/5] Update encloses() docstring Co-authored-by: Jan Haller --- godot-core/src/builtin/rect2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/godot-core/src/builtin/rect2.rs b/godot-core/src/builtin/rect2.rs index c2c199998..5b137a157 100644 --- a/godot-core/src/builtin/rect2.rs +++ b/godot-core/src/builtin/rect2.rs @@ -56,7 +56,7 @@ impl Rect2 { } } - /// 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. + /// Whether `self` covers at least the entire area of `b` (and possibly more). /// /// _Godot equivalent: `Rect2.encloses(Rect2 b)`_ #[inline] From 0f17fe5373e713a5eedb09fcfc81fc6d079513be Mon Sep 17 00:00:00 2001 From: juliohq Date: Mon, 24 Apr 2023 12:20:26 -0300 Subject: [PATCH 3/5] Update abs() comment --- godot-core/src/builtin/rect2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/godot-core/src/builtin/rect2.rs b/godot-core/src/builtin/rect2.rs index 5b137a157..69e1cf57c 100644 --- a/godot-core/src/builtin/rect2.rs +++ b/godot-core/src/builtin/rect2.rs @@ -45,7 +45,7 @@ impl Rect2 { } } - /// Returns a rectangle with equivalent position and area, modified so that the top-left corner is the origin and `width` and `height` are positive. + /// Returns a rectangle with the same geometry, with top-left corner as position and non-negative size. /// /// _Godot equivalent: `Rect2.abs()`_ #[inline] From 8e33e6a11625ae9005ae781d568e9a105f0e58c6 Mon Sep 17 00:00:00 2001 From: juliohq Date: Mon, 24 Apr 2023 12:21:58 -0300 Subject: [PATCH 4/5] Add missing backticks --- godot-core/src/builtin/rect2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/godot-core/src/builtin/rect2.rs b/godot-core/src/builtin/rect2.rs index 69e1cf57c..efec9ffd0 100644 --- a/godot-core/src/builtin/rect2.rs +++ b/godot-core/src/builtin/rect2.rs @@ -45,7 +45,7 @@ impl Rect2 { } } - /// Returns a rectangle with the same geometry, with top-left corner as position and non-negative size. + /// Returns a rectangle with the same geometry, with top-left corner as `position` and non-negative size. /// /// _Godot equivalent: `Rect2.abs()`_ #[inline] From 2084747595dcd911e7d264ae5b648d4901c29afb Mon Sep 17 00:00:00 2001 From: juliohq Date: Tue, 9 May 2023 10:09:41 -0300 Subject: [PATCH 5/5] Fix --- itest/rust/src/rect2_test.rs | 49 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/itest/rust/src/rect2_test.rs b/itest/rust/src/rect2_test.rs index 838ea5c33..2c388250b 100644 --- a/itest/rust/src/rect2_test.rs +++ b/itest/rust/src/rect2_test.rs @@ -6,7 +6,10 @@ use std::fmt::Debug; use crate::itest; -use godot::prelude::{inner::InnerRect2, *}; +use godot::{ + builtin::{Rect2, RectSide, Vector2}, + prelude::inner::InnerRect2, +}; #[itest] fn rect2_equiv_unary() { @@ -25,6 +28,7 @@ fn rect2_equiv_unary() { 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, @@ -32,54 +36,57 @@ fn rect2_equiv_unary() { RectSide::Bottom, ]; - fn evaluate_mappings(key: &str, a: T, b: T) + fn check_mapping_eq(context: &str, outer: T, inner: T) where T: PartialEq + Debug, { - assert_eq!(a, b, "{}: outer != inner ({:?} != {:?})", key, a, b); + assert_eq!( + outer, inner, + "{context}: outer != inner ({outer:?} != {inner:?})" + ); } for a in test_rects { let inner_a = InnerRect2::from_outer(&a); - evaluate_mappings("abs", a.abs(), inner_a.abs()); - evaluate_mappings("area", a.area() as f64, inner_a.get_area()); - evaluate_mappings("center", a.center(), inner_a.get_center()); - evaluate_mappings("has_area", a.has_area(), inner_a.has_area()); + 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 { - evaluate_mappings("encloses", a.encloses(b), inner_a.encloses(b)); - evaluate_mappings( + 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 - evaluate_mappings( + check_mapping_eq( "intersects", a.intersects(b, false), inner_a.intersects(b, false), ); - evaluate_mappings( + check_mapping_eq( "intersection", a.intersection(b).unwrap_or_default(), inner_a.intersection(b), ); - evaluate_mappings("merge", a.merge(b), inner_a.merge(b)); + check_mapping_eq("merge", a.merge(b), inner_a.merge(b)); } for b in test_vectors { - evaluate_mappings("expand", a.expand(b), inner_a.expand(b)); - evaluate_mappings("has_point", a.has_point(b), inner_a.has_point(b)); + 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 test_reals { - evaluate_mappings("grow", a.grow(b as f32), inner_a.grow(b)); + for b in grow_values { + check_mapping_eq("grow", a.grow(b as f32), inner_a.grow(b)); - for c in test_reals { - for d in test_reals { - for e in test_reals { - evaluate_mappings( + 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), @@ -91,7 +98,7 @@ fn rect2_equiv_unary() { for b in test_sides { for c in test_reals { - evaluate_mappings( + check_mapping_eq( "grow_side", a.grow_side(b, c as f32), inner_a.grow_side(b as i64, c),