From 00de6acbeb7f3966b7a9eb25f9a3e41c43e54468 Mon Sep 17 00:00:00 2001 From: Lili Zoey Date: Tue, 14 Mar 2023 18:28:38 +0100 Subject: [PATCH] Add basic impls of `Rect2`, `Rect2i`, `Aabb`, `Plane` Add `Mul` impls for `Transform2/3D` for the new types Add `min/max` functions for `Vector2/3` --- examples/dodge-the-creeps/rust/src/player.rs | 2 +- godot-core/src/builtin/aabb.rs | 87 +++++++++ godot-core/src/builtin/mod.rs | 27 ++- godot-core/src/builtin/others.rs | 22 +-- godot-core/src/builtin/plane.rs | 176 +++++++++++++++++++ godot-core/src/builtin/rect2.rs | 109 ++++++++++++ godot-core/src/builtin/rect2i.rs | 98 +++++++++++ godot-core/src/builtin/transform2d.rs | 21 ++- godot-core/src/builtin/transform3d.rs | 37 +++- godot-core/src/builtin/variant/impls.rs | 8 +- godot-core/src/builtin/vector_macros.rs | 12 ++ itest/rust/src/string_test.rs | 1 + itest/rust/src/transform2d_test.rs | 41 +++++ itest/rust/src/transform3d_test.rs | 42 +++++ 14 files changed, 651 insertions(+), 32 deletions(-) create mode 100644 godot-core/src/builtin/aabb.rs create mode 100644 godot-core/src/builtin/plane.rs create mode 100644 godot-core/src/builtin/rect2.rs create mode 100644 godot-core/src/builtin/rect2i.rs diff --git a/examples/dodge-the-creeps/rust/src/player.rs b/examples/dodge-the-creeps/rust/src/player.rs index 72b3d9ee1..884dc6a06 100644 --- a/examples/dodge-the-creeps/rust/src/player.rs +++ b/examples/dodge-the-creeps/rust/src/player.rs @@ -53,7 +53,7 @@ impl Area2DVirtual for Player { fn ready(&mut self) { let viewport = self.base.get_viewport_rect(); - self.screen_size = viewport.size(); + self.screen_size = viewport.size; self.base.hide(); } diff --git a/godot-core/src/builtin/aabb.rs b/godot-core/src/builtin/aabb.rs new file mode 100644 index 000000000..f5d9e58c3 --- /dev/null +++ b/godot-core/src/builtin/aabb.rs @@ -0,0 +1,87 @@ +/* + * 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 godot_ffi as sys; +use sys::{ffi_methods, GodotFfi}; + +use super::Vector3; + +/// Axis-aligned bounding box in 3D space. +/// +/// `Aabb` consists of a position, a size, and several utility functions. It is typically used for +/// fast overlap tests. +/// +/// Currently most methods are only available through [`InnerAabb`](super::inner::InnerAabb). +/// +/// The 2D counterpart to `Aabb` is [`Rect2`](super::Rect2). +#[derive(Default, Copy, Clone, PartialEq, Debug)] +#[repr(C)] +pub struct Aabb { + pub position: Vector3, + pub size: Vector3, +} + +impl Aabb { + /// Create a new `Aabb` from a position and a size. + /// + /// _Godot equivalent: `Aabb(Vector3 position, Vector3 size)`_ + #[inline] + pub const fn new(position: Vector3, size: Vector3) -> Self { + Self { position, size } + } + + /// Create a new `Aabb` with the first corner at `position` and opposite corner at `end`. + #[inline] + pub fn from_corners(position: Vector3, end: Vector3) -> Self { + Self { + position, + size: position + end, + } + } + + /// The end of the `Aabb` calculated as `position + size`. + /// + /// _Godot equivalent: `Aabb.size` property_ + #[inline] + pub fn end(&self) -> Vector3 { + self.position + self.size + } + + /// Set size based on desired end-point. + /// + /// _Godot equivalent: `Aabb.size` property_ + #[inline] + pub fn set_end(&mut self, end: Vector3) { + self.size = end - self.position + } + + /// Returns `true` if the two `Aabb`s are approximately equal, by calling `is_equal_approx` on + /// `position` and `size`. + /// + /// _Godot equivalent: `Aabb.is_equal_approx()`_ + #[inline] + pub fn is_equal_approx(&self, other: &Self) -> bool { + self.position.is_equal_approx(other.position) && self.size.is_equal_approx(other.size) + } + + /* Add in when `Aabb::abs()` is implemented. + /// Assert that the size of the `Aabb` is not negative. + /// + /// Certain functions will fail to give a correct result if the size is negative. + #[inline] + pub fn assert_nonnegative(&self) { + assert!( + self.size.x >= 0.0 && self.size.y >= 0.0 && self.size.z >= 0.0, + "size {:?} is negative", + self.size + ); + } + */ +} + +impl GodotFfi for Aabb { + ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } +} diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index c90066d0b..7855f0cdf 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -35,6 +35,7 @@ // Re-export macros. pub use crate::{array, dict, varray}; +pub use aabb::*; pub use array_inner::{Array, VariantArray}; pub use basis::*; pub use color::*; @@ -43,8 +44,11 @@ pub use math::*; pub use node_path::*; pub use others::*; pub use packed_array::*; +pub use plane::*; pub use projection::*; pub use quaternion::*; +pub use rect2::*; +pub use rect2i::*; pub use rid::*; pub use string::*; pub use string_name::*; @@ -84,6 +88,7 @@ mod array_inner; #[path = "dictionary.rs"] mod dictionary_inner; +mod aabb; mod basis; mod color; mod glam_helpers; @@ -91,8 +96,11 @@ mod math; mod node_path; mod others; mod packed_array; +mod plane; mod projection; mod quaternion; +mod rect2; +mod rect2i; mod rid; mod string; mod string_chars; @@ -319,6 +327,17 @@ macro_rules! real { }}; } +/// The side of a [`Rect2`] or [`Rect2i`]. +/// +/// _Godot equivalent: `@GlobalScope.Side`_ +#[repr(C)] +pub enum RectSide { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3, +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Implementations of the `Export` trait for types where it can be done trivially. @@ -352,7 +371,7 @@ mod export { impl_export_by_clone!(f32); impl_export_by_clone!(f64); - // impl_export_by_clone!(Aabb); // TODO uncomment once Aabb implements Clone + impl_export_by_clone!(Aabb); impl_export_by_clone!(Basis); impl_export_by_clone!(Color); impl_export_by_clone!(GodotString); @@ -366,11 +385,11 @@ mod export { impl_export_by_clone!(PackedStringArray); impl_export_by_clone!(PackedVector2Array); impl_export_by_clone!(PackedVector3Array); - // impl_export_by_clone!(Plane); // TODO uncomment once Plane implements Clone + impl_export_by_clone!(Plane); impl_export_by_clone!(Projection); impl_export_by_clone!(Quaternion); - // impl_export_by_clone!(Rect2); // TODO uncomment once Rect2 implements Clone - // impl_export_by_clone!(Rect2i); // TODO uncomment once Rect2i implements Clone + impl_export_by_clone!(Rect2); + impl_export_by_clone!(Rect2i); impl_export_by_clone!(Rid); impl_export_by_clone!(StringName); impl_export_by_clone!(Transform2D); diff --git a/godot-core/src/builtin/others.rs b/godot-core/src/builtin/others.rs index 8606dc91e..92388f6be 100644 --- a/godot-core/src/builtin/others.rs +++ b/godot-core/src/builtin/others.rs @@ -6,36 +6,16 @@ // Stub for various other built-in classes, which are currently incomplete, but whose types // are required for codegen -use crate::builtin::{inner, StringName, Vector2}; +use crate::builtin::{inner, StringName}; use crate::obj::{Gd, GodotClass}; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; // TODO: Swap more inner math types with glam types // Note: ordered by enum ord in extension JSON -impl_builtin_stub!(Rect2, OpaqueRect2); -impl_builtin_stub!(Rect2i, OpaqueRect2i); -impl_builtin_stub!(Plane, OpaquePlane); -impl_builtin_stub!(Aabb, OpaqueAabb); impl_builtin_stub!(Callable, OpaqueCallable); impl_builtin_stub!(Signal, OpaqueSignal); -#[repr(C)] -struct InnerRect { - position: Vector2, - size: Vector2, -} - -impl Rect2 { - pub fn size(self) -> Vector2 { - self.inner().size - } - - fn inner(self) -> InnerRect { - unsafe { std::mem::transmute(self) } - } -} - impl Callable { pub fn from_object_method(object: Gd, method: S) -> Self where diff --git a/godot-core/src/builtin/plane.rs b/godot-core/src/builtin/plane.rs new file mode 100644 index 000000000..259d537cc --- /dev/null +++ b/godot-core/src/builtin/plane.rs @@ -0,0 +1,176 @@ +/* + * 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::ops::Neg; + +use godot_ffi as sys; +use sys::{ffi_methods, GodotFfi}; + +use super::{is_equal_approx, real, Vector3}; + +/// 3D plane in [Hessian normal form](https://mathworld.wolfram.com/HessianNormalForm.html). +/// +/// The Hessian form defines all points `point` which satisfy the equation +/// `dot(normal, point) + d == 0`, where `normal` is the normal vector and `d` +/// the distance from the origin. +/// +/// Currently most methods are only available through [`InnerPlane`](super::inner::InnerPlane). +/// +/// Note: almost all methods on `Plane` require that the `normal` vector have +/// unit length and will panic if this invariant is violated. This is not separately +/// annotated for each method. +#[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] +pub struct Plane { + pub normal: Vector3, + pub d: real, +} + +impl Plane { + /// Creates a new `Plane` from the `normal` and the distance from the origin `d`. + /// + /// # Panics + /// In contrast to construction via `Plane { normal, d }`, this verifies that `normal` has unit length, and will + /// panic if this is not the case. + /// + /// _Godot equivalent: `Plane(Vector3 normal, float d)`_ + #[inline] + pub fn new(unit_normal: Vector3, d: real) -> Self { + let plane = Self { + normal: unit_normal, + d, + }; + plane.assert_normalized(); + plane + } + + /// Create a new `Plane` through the origin from a normal. + /// + /// # Panics + /// See [`Self::new()`]. + /// + /// _Godot equivalent: `Plane(Vector3 normal)`_ + #[inline] + pub fn from_normal_at_origin(normal: Vector3) -> Self { + Self::new(normal, 0.0) + } + + /// Create a new `Plane` from a normal and a point in the plane. + /// + /// # Panics + /// See [`Self::new()`]. + /// + /// _Godot equivalent: `Plane(Vector3 normal, Vector3 point)`_ + #[inline] + pub fn from_point_normal(point: Vector3, normal: Vector3) -> Self { + Self::new(normal, normal.dot(point)) + } + + /// Creates a new `Plane` from normal and origin distance. + /// + /// `nx`, `ny`, `nz` are used for the `normal` vector. + /// `d` is the distance from the origin. + /// + /// # Panics + /// See [`Self::new()`]. + /// + /// _Godot equivalent: `Plane(float a, float b, float c, float d)`_ + #[inline] + pub fn from_components(nx: real, ny: real, nz: real, d: real) -> Self { + Self::new(Vector3::new(nx, ny, nz), d) + } + + /// Creates a new `Plane` from three points, given in clockwise order. + /// + /// # Panics + /// Will panic if all three points are colinear. + /// + /// _Godot equivalent: `Plane(Vector3 point1, Vector3 point2, Vector3 point3)`_ + #[inline] + pub fn from_points(a: Vector3, b: Vector3, c: Vector3) -> Self { + let normal = (a - c).cross(a - b); + assert_ne!( + normal, + Vector3::ZERO, + "points {a}, {b}, {c} are all colinear" + ); + let normal = normal.normalized(); + Self { + normal, + d: normal.dot(a), + } + } + + /// Returns `true` if the two `Plane`s are approximately equal, by calling `is_equal_approx` on + /// `normal` and `d` or on `-normal` and `-d`. + /// + /// _Godot equivalent: `Plane.is_equal_approx()`_ + #[inline] + pub fn is_equal_approx(&self, other: &Self) -> bool { + (self.normal.is_equal_approx(other.normal) && is_equal_approx(self.d, other.d)) + || (self.normal.is_equal_approx(-other.normal) && is_equal_approx(self.d, -other.d)) + } + + #[inline] + fn assert_normalized(self) { + assert!( + self.normal.is_normalized(), + "normal {:?} is not normalized", + self.normal + ); + } +} + +impl Neg for Plane { + type Output = Plane; + + /// Returns the negative value of the plane by flipping both the normal and the distance value. Meaning + /// it creates a plane that is in the same place, but facing the opposite direction. + fn neg(self) -> Self::Output { + Self::new(-self.normal, -self.d) + } +} + +impl GodotFfi for Plane { + ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Tests that none of the constructors panic for some simple planes. + #[test] + fn construction_succeeds() { + let vec = Vector3::new(1.0, 2.0, 3.0).normalized(); + let Vector3 { x, y, z } = vec; + let _ = Plane::new(vec, 5.0); + let _ = Plane::from_normal_at_origin(vec); + let _ = Plane::from_point_normal(Vector3::new(10.0, 20.0, 30.0), vec); + let _ = Plane::from_components(x, y, z, 5.0); + let _ = Plane::from_points( + Vector3::new(1.0, 2.0, 3.0), + Vector3::new(2.0, 3.0, 1.0), + Vector3::new(3.0, 2.0, 1.0), + ); + } + + #[test] + #[should_panic] + fn new_unnormalized_panics() { + let _ = Plane::new(Vector3::new(1.0, 2.0, 3.0), 5.0); + } + + #[test] + #[should_panic] + fn from_points_colinear_panics() { + let _ = Plane::from_points( + Vector3::new(0.0, 0.0, 0.0), + Vector3::new(0.0, 0.0, 1.0), + Vector3::new(0.0, 0.0, 2.0), + ); + } +} diff --git a/godot-core/src/builtin/rect2.rs b/godot-core/src/builtin/rect2.rs new file mode 100644 index 000000000..73a109958 --- /dev/null +++ b/godot-core/src/builtin/rect2.rs @@ -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 godot_ffi as sys; +use sys::{ffi_methods, GodotFfi}; + +use super::{real, Rect2i, Vector2}; + +/// 2D axis-aligned bounding box. +/// +/// `Rect2` consists of a position, a size, and several utility functions. It is typically used for +/// fast overlap tests. +/// +/// Currently most methods are only available through [`InnerRect2`](super::inner::InnerRect2). +/// +/// The 3D counterpart to `Rect2` is [`Aabb`](super::Aabb). +#[derive(Default, Copy, Clone, PartialEq, Debug)] +#[repr(C)] +pub struct Rect2 { + pub position: Vector2, + pub size: Vector2, +} + +impl Rect2 { + /// Create a new `Rect2` from a position and a size. + /// + /// _Godot equivalent: `Rect2(Vector2 position, Vector2 size)`_ + #[inline] + pub const fn new(position: Vector2, size: Vector2) -> Self { + Self { position, size } + } + + /// Create a new `Rect2` from four reals representing position `(x,y)` and size `(width,height)`. + /// + /// _Godot equivalent: `Rect2(float x, float y, float width, float height)`_ + #[inline] + pub const fn from_components(x: real, y: real, width: real, height: real) -> Self { + Self { + position: Vector2::new(x, y), + size: Vector2::new(width, height), + } + } + + /// Create a new `Rect2` from a `Rect2i`, using `as` for `i32` to `real` conversions. + /// + /// _Godot equivalent: `Rect2(Rect2i from)`_ + #[inline] + pub const fn from_rect2i(rect: Rect2i) -> Self { + Self { + position: Vector2::from_vector2i(rect.position), + size: Vector2::from_vector2i(rect.size), + } + } + + /// Create a new `Rect2` with the first corner at `position` and the opposite corner at `end`. + #[inline] + pub fn from_corners(position: Vector2, end: Vector2) -> Self { + Self { + position, + size: position + end, + } + } + + /// The end of the `Rect2` calculated as `position + size`. + /// + /// _Godot equivalent: `Rect2.size` property_ + #[inline] + pub fn end(&self) -> Vector2 { + self.position + self.size + } + + /// Set size based on desired end-point. + /// + /// _Godot equivalent: `Rect2.size` property_ + #[inline] + pub fn set_end(&mut self, end: Vector2) { + self.size = end - self.position + } + + /// Returns `true` if the two `Rect2`s are approximately equal, by calling `is_equal_approx` on + /// `position` and `size`. + /// + /// _Godot equivalent: `Rect2.is_equal_approx()`_ + #[inline] + pub fn is_equal_approx(&self, other: &Self) -> bool { + 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. + #[inline] + pub fn assert_nonnegative(&self) { + assert!( + self.size.x >= 0.0 && self.size.y >= 0.0, + "size {:?} is negative", + self.size + ); + } + */ +} + +impl GodotFfi for Rect2 { + ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } +} diff --git a/godot-core/src/builtin/rect2i.rs b/godot-core/src/builtin/rect2i.rs new file mode 100644 index 000000000..4fc611a5a --- /dev/null +++ b/godot-core/src/builtin/rect2i.rs @@ -0,0 +1,98 @@ +/* + * 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 godot_ffi as sys; +use sys::{ffi_methods, GodotFfi}; + +use super::{Rect2, Vector2i}; + +/// 2D axis-aligned integer bounding box. +/// +/// `Rect2i` consists of a position, a size, and several utility functions. It is typically used for +/// fast overlap tests. +/// +/// Currently most methods are only available through [`InnerRect2i`](super::inner::InnerRect2i). +#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)] +#[repr(C)] +pub struct Rect2i { + pub position: Vector2i, + pub size: Vector2i, +} + +impl Rect2i { + /// Create a new `Rect2i` from a position and a size. + /// + /// _Godot equivalent: `Rect2i(Vector2i position, Vector2i size)`_ + #[inline] + pub const fn new(position: Vector2i, size: Vector2i) -> Self { + Self { position, size } + } + + /// Create a new `Rect2i` from four `i32`s representing position `(x,y)` and size `(width,height)`. + /// + /// _Godot equivalent: `Rect2i(float x, float y, float width, float height)`_ + #[inline] + pub const fn from_components(x: i32, y: i32, width: i32, height: i32) -> Self { + Self { + position: Vector2i::new(x, y), + size: Vector2i::new(width, height), + } + } + + /// Create a new `Rect2i` from a `Rect2`, using `as` for `real` to `i32` conversions. + /// + /// _Godot equivalent: `Rect2i(Rect2 from)`_ + #[inline] + pub const fn from_rect2(rect: Rect2) -> Self { + Self { + position: Vector2i::from_vector2(rect.position), + size: Vector2i::from_vector2(rect.size), + } + } + + /// Create a new `Rect2i` with the first corner at `position` and the opposite corner at `end`. + #[inline] + pub fn from_corners(position: Vector2i, end: Vector2i) -> Self { + Self { + position, + size: position + end, + } + } + + /// The end of the `Rect2i` calculated as `position + size`. + /// + /// _Godot equivalent: `Rect2i.size` property_ + #[inline] + pub const fn end(&self) -> Vector2i { + Vector2i::new(self.position.x + self.size.x, self.position.y + self.size.y) + } + + /// Set size based on desired end-point. + /// + /// _Godot equivalent: `Rect2i.size` property_ + #[inline] + pub fn set_end(&mut self, end: Vector2i) { + self.size = end - self.position + } + + /* Add in when `Rect2i::abs()` is implemented. + /// Assert that the size of the `Rect2i` is not negative. + /// + /// Certain functions will fail to give a correct result if the size is negative. + #[inline] + pub const fn assert_nonnegative(&self) { + assert!( + self.size.x >= 0.0 && self.size.y >= 0.0, + "size {:?} is negative", + self.size + ); + } + */ +} + +impl GodotFfi for Rect2i { + ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } +} diff --git a/godot-core/src/builtin/transform2d.rs b/godot-core/src/builtin/transform2d.rs index d37589d84..7d6c94e4b 100644 --- a/godot-core/src/builtin/transform2d.rs +++ b/godot-core/src/builtin/transform2d.rs @@ -9,7 +9,7 @@ use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use super::glam_helpers::{GlamConv, GlamType}; -use super::{math::*, Vector2}; +use super::{math::*, Rect2, Vector2}; use super::real_consts::PI; use super::{real, RAffine2, RMat2}; @@ -320,6 +320,25 @@ impl Mul for Transform2D { } } +impl Mul for Transform2D { + type Output = Rect2; + + /// Transforms each coordinate in `rhs.position` and `rhs.end()` individually by this transform, then + /// creates a `Rect2` containing all of them. + fn mul(self, rhs: Rect2) -> Self::Output { + // https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/ + let xa = self.a * rhs.position.x; + let xb = self.a * rhs.end().x; + + let ya = self.b * rhs.position.y; + let yb = self.b * rhs.end().y; + + let position = Vector2::min(xa, xb) + Vector2::min(ya, yb) + self.origin; + let end = Vector2::max(xa, xb) + Vector2::max(ya, yb) + self.origin; + Rect2::new(position, end - position) + } +} + impl GlamType for RAffine2 { type Mapped = Transform2D; diff --git a/godot-core/src/builtin/transform3d.rs b/godot-core/src/builtin/transform3d.rs index 476d3ef2e..5b4e4b284 100644 --- a/godot-core/src/builtin/transform3d.rs +++ b/godot-core/src/builtin/transform3d.rs @@ -10,7 +10,7 @@ use sys::{ffi_methods, GodotFfi}; use super::glam_helpers::{GlamConv, GlamType}; use super::{real, RAffine3}; -use super::{Basis, Projection, Vector3}; +use super::{Aabb, Basis, Plane, Projection, Vector3}; /// Affine 3D transform (3x4 matrix). /// @@ -295,6 +295,41 @@ impl Mul for Transform3D { } } +impl Mul for Transform3D { + type Output = Aabb; + + /// Transforms each coordinate in `rhs.position` and `rhs.end()` individually by this transform, then + /// creates an `Aabb` containing all of them. + fn mul(self, rhs: Aabb) -> Self::Output { + // https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/ + let xa = self.basis.col_a() * rhs.position.x; + let xb = self.basis.col_a() * rhs.end().x; + + let ya = self.basis.col_b() * rhs.position.y; + let yb = self.basis.col_b() * rhs.end().y; + + let za = self.basis.col_c() * rhs.position.z; + let zb = self.basis.col_c() * rhs.end().z; + + let position = + Vector3::min(xa, xb) + Vector3::min(ya, yb) + Vector3::min(za, zb) + self.origin; + let end = Vector3::max(xa, xb) + Vector3::max(ya, yb) + Vector3::max(za, zb) + self.origin; + Aabb::new(position, end - position) + } +} + +impl Mul for Transform3D { + type Output = Plane; + + fn mul(self, rhs: Plane) -> Self::Output { + let point = self * (rhs.normal * rhs.d); + + let basis = self.basis.inverse().transposed(); + + Plane::from_point_normal(point, (basis * rhs.normal).normalized()) + } +} + impl GlamType for RAffine3 { type Mapped = Transform3D; diff --git a/godot-core/src/builtin/variant/impls.rs b/godot-core/src/builtin/variant/impls.rs index 177098d21..1365a4cb9 100644 --- a/godot-core/src/builtin/variant/impls.rs +++ b/godot-core/src/builtin/variant/impls.rs @@ -142,6 +142,7 @@ macro_rules! impl_variant_traits_float { mod impls { use super::*; + impl_variant_traits!(Aabb, aabb_to_variant, aabb_from_variant, Aabb); impl_variant_traits!(bool, bool_to_variant, bool_from_variant, Bool); impl_variant_traits!(Basis, basis_to_variant, basis_from_variant, Basis); impl_variant_traits!(Vector2, vector2_to_variant, vector2_from_variant, Vector2); @@ -155,10 +156,6 @@ mod impls { impl_variant_traits!(StringName, string_name_to_variant, string_name_from_variant, StringName); impl_variant_traits!(NodePath, node_path_to_variant, node_path_from_variant, NodePath); // TODO use impl_variant_traits!, as soon as `Default` is available. Also consider auto-generating. - impl_variant_metadata!(Rect2, /* rect2_to_variant, rect2_from_variant, */ Rect2); - impl_variant_metadata!(Rect2i, /* rect2i_to_variant, rect2i_from_variant, */ Rect2i); - impl_variant_metadata!(Plane, /* plane_to_variant, plane_from_variant, */ Plane); - impl_variant_metadata!(Aabb, /* aabb_to_variant, aabb_from_variant, */ Aabb); impl_variant_metadata!(Callable, /* callable_to_variant, callable_from_variant, */ Callable); impl_variant_metadata!(Signal, /* signal_to_variant, signal_from_variant, */ Signal); impl_variant_traits!(PackedByteArray, packed_byte_array_to_variant, packed_byte_array_from_variant, PackedByteArray); @@ -170,8 +167,11 @@ mod impls { impl_variant_traits!(PackedVector2Array, packed_vector2_array_to_variant, packed_vector2_array_from_variant, PackedVector2Array); impl_variant_traits!(PackedVector3Array, packed_vector3_array_to_variant, packed_vector3_array_from_variant, PackedVector3Array); impl_variant_traits!(PackedColorArray, packed_color_array_to_variant, packed_color_array_from_variant, PackedColorArray); + impl_variant_traits!(Plane, plane_to_variant, plane_from_variant, Plane); impl_variant_traits!(Projection, projection_to_variant, projection_from_variant, Projection); impl_variant_traits!(Rid, rid_to_variant, rid_from_variant, Rid); + impl_variant_traits!(Rect2, rect2_to_variant, rect2_from_variant, Rect2); + impl_variant_traits!(Rect2i, rect2i_to_variant, rect2i_from_variant, Rect2i); impl_variant_traits!(Transform2D, transform_2d_to_variant, transform_2d_from_variant, Transform2D); impl_variant_traits!(Transform3D, transform_3d_to_variant, transform_3d_from_variant, Transform3D); impl_variant_traits!(Dictionary, dictionary_to_variant, dictionary_from_variant, Dictionary); diff --git a/godot-core/src/builtin/vector_macros.rs b/godot-core/src/builtin/vector_macros.rs index eed3d9c5c..e66460e5c 100644 --- a/godot-core/src/builtin/vector_macros.rs +++ b/godot-core/src/builtin/vector_macros.rs @@ -266,6 +266,18 @@ macro_rules! impl_float_vector_fns { pub fn normalized(self) -> Self { Self::from_glam(self.to_glam().normalize_or_zero()) } + + /// Returns a vector containing the minimum values for each element of `self` and `other`. + #[inline] + pub fn min(self, other: Self) -> Self { + self.glam2(&other, |a, b| a.min(b)) + } + + /// Returns a vector containing the maximum values for each element of `self` and `other`. + #[inline] + pub fn max(self, other: Self) -> Self { + self.glam2(&other, |a, b| a.max(b)) + } } }; } diff --git a/itest/rust/src/string_test.rs b/itest/rust/src/string_test.rs index 028783460..eea36fbfb 100644 --- a/itest/rust/src/string_test.rs +++ b/itest/rust/src/string_test.rs @@ -50,6 +50,7 @@ fn string_ordering() { #[itest] fn string_clone() { let first = GodotString::from("some string"); + #[allow(clippy::redundant_clone)] let cloned = first.clone(); assert_eq!(first, cloned); diff --git a/itest/rust/src/transform2d_test.rs b/itest/rust/src/transform2d_test.rs index 0c59cb445..187fb4a30 100644 --- a/itest/rust/src/transform2d_test.rs +++ b/itest/rust/src/transform2d_test.rs @@ -60,3 +60,44 @@ fn transform2d_equiv() { "function: get_scale\n" ); } + +#[itest] +fn transform2d_xform_equiv() { + let vec = Vector2::new(1.0, 2.0); + + assert_eq_approx!( + TEST_TRANSFORM * vec, + TEST_TRANSFORM + .to_variant() + .evaluate(&vec.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + Vector2::is_equal_approx, + "operator: Transform2D * Vector2" + ); + + let rect_2 = Rect2::new(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0)); + + assert_eq_approx!( + TEST_TRANSFORM * rect_2, + TEST_TRANSFORM + .to_variant() + .evaluate(&rect_2.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + |a, b| Rect2::is_equal_approx(&a, &b), + "operator: Transform2D * Rect2 (1)" + ); + + assert_eq_approx!( + TEST_TRANSFORM.rotated(0.8) * rect_2, + TEST_TRANSFORM + .rotated(0.8) + .to_variant() + .evaluate(&rect_2.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + |a, b| Rect2::is_equal_approx(&a, &b), + "operator: Transform2D * Rect2 (2)" + ); +} diff --git a/itest/rust/src/transform3d_test.rs b/itest/rust/src/transform3d_test.rs index 4dd07ee9d..f2543b9bd 100644 --- a/itest/rust/src/transform3d_test.rs +++ b/itest/rust/src/transform3d_test.rs @@ -44,3 +44,45 @@ fn transform3d_equiv() { ); } } + +#[itest] +fn transform3d_xform_equiv() { + let vec = Vector3::new(1.0, 2.0, 3.0); + + assert_eq_approx!( + TEST_TRANSFORM * vec, + TEST_TRANSFORM + .to_variant() + .evaluate(&vec.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + Vector3::is_equal_approx, + "operator: Transform3D * Vector3" + ); + + let aabb = Aabb::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(4.0, 5.0, 6.0)); + + assert_eq_approx!( + TEST_TRANSFORM * aabb, + TEST_TRANSFORM + .to_variant() + .evaluate(&aabb.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + |a, b| Aabb::is_equal_approx(&a, &b), + "operator: Transform3D * Aabb" + ); + + let plane = Plane::new(Vector3::new(1.0, 2.0, 3.0).normalized(), 5.0); + + assert_eq_approx!( + TEST_TRANSFORM * plane, + TEST_TRANSFORM + .to_variant() + .evaluate(&plane.to_variant(), VariantOperator::Multiply) + .unwrap() + .to::(), + |a, b| Plane::is_equal_approx(&a, &b), + "operator: Transform3D * Plane" + ); +}