From 5ec59f3e03b8df3169899db125c2ac449fa89f52 Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Tue, 28 May 2024 00:45:46 +0200 Subject: [PATCH 1/8] Refactor vector_macros --- godot-core/src/builtin/math/float.rs | 1 + godot-core/src/builtin/math/glam_helpers.rs | 3 + .../src/builtin/vectors/vector_macros.rs | 644 ++++++++++++++---- 3 files changed, 531 insertions(+), 117 deletions(-) diff --git a/godot-core/src/builtin/math/float.rs b/godot-core/src/builtin/math/float.rs index 83a8ff7ef..134f234a9 100644 --- a/godot-core/src/builtin/math/float.rs +++ b/godot-core/src/builtin/math/float.rs @@ -32,6 +32,7 @@ pub trait FloatExt: private::Sealed + Copy { /// Check if `self` is within [`Self::CMP_EPSILON`] of `0.0`. fn is_zero_approx(self) -> bool; + /// Returns the floating-point modulus of `self` divided by `pmod`, wrapping equally in positive and negative. fn fposmod(self, pmod: Self) -> Self; /// Returns the multiple of `step` that is closest to `self`. diff --git a/godot-core/src/builtin/math/glam_helpers.rs b/godot-core/src/builtin/math/glam_helpers.rs index 95320e1fb..607be41a4 100644 --- a/godot-core/src/builtin/math/glam_helpers.rs +++ b/godot-core/src/builtin/math/glam_helpers.rs @@ -21,10 +21,12 @@ use crate::builtin::real; pub(crate) trait GlamConv { type Glam: GlamType; + #[inline] fn to_glam(&self) -> Self::Glam { Self::Glam::from_front(self) } + #[inline] fn glam(&self, unary_fn: F) -> R::Mapped where R: GlamType, @@ -36,6 +38,7 @@ pub(crate) trait GlamConv { result.to_front() } + #[inline] fn glam2(&self, rhs: &P, binary_fn: F) -> R::Mapped where P: GlamConv, diff --git a/godot-core/src/builtin/vectors/vector_macros.rs b/godot-core/src/builtin/vectors/vector_macros.rs index 9d33dcc4a..a761c78ca 100644 --- a/godot-core/src/builtin/vectors/vector_macros.rs +++ b/godot-core/src/builtin/vectors/vector_macros.rs @@ -251,9 +251,8 @@ macro_rules! impl_vector_index { } } -/// Implements functions on vector types which make sense for both floating-point and integer -/// vectors. -macro_rules! impl_common_vector_fns { +/// Implements constants that are present on floating-point and integer vectors. +macro_rules! impl_vector_consts { ( // Name of the vector type. $Vector:ty, @@ -261,31 +260,46 @@ macro_rules! impl_common_vector_fns { $Scalar:ty ) => { impl $Vector { - /// Returns a new vector with all components in absolute values (i.e. positive or - /// zero). - #[inline] - pub fn abs(self) -> Self { - Self::from_glam(self.to_glam().abs()) - } + /// Zero vector, a vector with all components set to `0`. + pub const ZERO: Self = Self::splat(0 as $Scalar); - /// Returns a new vector containing the minimum of the two vectors, component-wise. - #[inline] - pub fn coord_min(self, other: Self) -> Self { - self.glam2(&other, |a, b| a.min(b)) - } + /// One vector, a vector with all components set to `1`. + pub const ONE: Self = Self::splat(1 as $Scalar); + } + }; +} - /// Returns a new vector containing the maximum of the two vectors, component-wise. - #[inline] - pub fn coord_max(self, other: Self) -> Self { - self.glam2(&other, |a, b| a.max(b)) - } +/// Implements constants that are present only on floating-point vectors. +macro_rules! impl_float_vector_consts { + ( + // Name of the vector type. + $Vector:ty + ) => { + impl $Vector { + /// Infinity vector, a vector with all components set to `real::INFINITY`. + pub const INF: Self = Self::splat(real::INFINITY); + } + }; +} + +/// Implements constants that are present only on integer vectors. +macro_rules! impl_integer_vector_consts { + ( + // Name of the vector type. + $Vector:ty + ) => { + impl $Vector { + /// Min vector, a vector with all components equal to [`i32::MIN`]. Can be used as a negative integer equivalent of [`Vector2::INF`]. + pub const MIN: Self = Self::splat(i32::MIN); + + /// Max vector, a vector with all components equal to [`i32::MAX`]. Can be used as an integer equivalent of [`Vector2::INF`]. + pub const MAX: Self = Self::splat(i32::MAX); } }; } -/// Implements common constants and methods for floating-point type vectors. Works for any vector -/// type that has `to_glam` and `from_glam` functions. -macro_rules! impl_float_vector_glam_fns { +/// Implements constants present on 2D vectors. +macro_rules! impl_vector2x_consts { ( // Name of the vector type. $Vector:ty, @@ -293,27 +307,23 @@ macro_rules! impl_float_vector_glam_fns { $Scalar:ty ) => { impl $Vector { - /// Returns the length (magnitude) of this vector. - #[inline] - pub fn length(self) -> $Scalar { - self.to_glam().length() - } + /// Left unit vector. Represents the direction of left. + pub const LEFT: Self = Self::new(-1 as $Scalar, 0 as $Scalar); - /// Returns the vector scaled to unit length. Equivalent to `self / self.length()`. See - /// also `is_normalized()`. - /// - /// If the vector is zero, the result is also zero. - #[inline] - pub fn normalized(self) -> Self { - Self::from_glam(self.to_glam().normalize_or_zero()) - } + /// Right unit vector. Represents the direction of right. + pub const RIGHT: Self = Self::new(1 as $Scalar, 0 as $Scalar); + + /// Up unit vector. Y is down in 2D, so this vector points -Y. + pub const UP: Self = Self::new(0 as $Scalar, -1 as $Scalar); + + /// Down unit vector. Y is down in 2D, so this vector points +Y. + pub const DOWN: Self = Self::new(0 as $Scalar, 1 as $Scalar); } }; } -/// Implements common constants and methods for integer type vectors. Works for any vector type that -/// has `to_glam`, `from_glam` and `to_glam_real` functions. -macro_rules! impl_integer_vector_glam_fns { +/// Implements constants present on 3D vectors. +macro_rules! impl_vector3x_consts { ( // Name of the vector type. $Vector:ty, @@ -321,116 +331,288 @@ macro_rules! impl_integer_vector_glam_fns { $Scalar:ty ) => { impl $Vector { - /// Length (magnitude) of this vector. - #[inline] - pub fn length(self) -> $Scalar { - self.to_glam_real().length() - } + /// Unit vector in -X direction. Can be interpreted as left in an untransformed 3D world. + pub const LEFT: Self = Self::new(-1 as $Scalar, 0 as $Scalar, 0 as $Scalar); - /// A new vector with each component set to 1 if it's positive, -1 if it's negative, - /// and 0 if it's zero. - #[inline] - pub fn sign(self) -> Self { - Self::from_glam(self.to_glam().signum()) - } + /// Unit vector in +X direction. Can be interpreted as right in an untransformed 3D world. + pub const RIGHT: Self = Self::new(1 as $Scalar, 0 as $Scalar, 0 as $Scalar); + + /// Unit vector in +Y direction. Typically interpreted as up in a 3D world. + pub const UP: Self = Self::new(0 as $Scalar, 1 as $Scalar, 0 as $Scalar); + + /// Unit vector in -Y direction. Typically interpreted as down in a 3D world. + pub const DOWN: Self = Self::new(0 as $Scalar, -1 as $Scalar, 0 as $Scalar); + + /// Unit vector in -Z direction. Can be interpreted as “into the screen” in an untransformed 3D world. + pub const FORWARD: Self = Self::new(0 as $Scalar, 0 as $Scalar, -1 as $Scalar); + + /// Unit vector in +Z direction. Can be interpreted as “out of the screen” in an untransformed 3D world. + pub const BACK: Self = Self::new(0 as $Scalar, 0 as $Scalar, 1 as $Scalar); } }; } -/// Implements common constants and methods for floating-point type vectors based on their components. -macro_rules! impl_float_vector_component_fns { +/// Implements functions that are present on floating-point and integer vectors. +macro_rules! impl_vector_fns { ( // Name of the vector type. $Vector:ty, + // Name of the glam vector type. + $GlamVector:ty, // Type of target component, for example `real`. $Scalar:ty, // Names of the components, with parentheses, for example `(x, y)`. ($($comp:ident),*) ) => { impl $Vector { - pub fn lerp(self, other: Self, weight: $Scalar) -> Self { + /// Returns a vector with the given components. + pub const fn new($($comp: $Scalar),*) -> Self { + Self { + $( $comp ),* + } + } + + /// Returns a new vector with all components set to `v`. + pub const fn splat(v: $Scalar) -> Self { + Self { + $( $comp: v ),* + } + } + + /// Converts the corresponding `glam` type to `Self`. + fn from_glam(v: $GlamVector) -> Self { Self::new( - $( - self.$comp.lerp(other.$comp, weight) - ),* + $( v.$comp ),* ) } - pub fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: $Scalar) -> Self { - $( - let $comp = self.$comp.bezier_derivative(control_1.$comp, control_2.$comp, end.$comp, t); - )* + /// Converts `self` to the corresponding `glam` type. + fn to_glam(self) -> $GlamVector { + <$GlamVector>::new( + $( self.$comp ),* + ) + } - Self::new($($comp),*) + /// Returns a new vector with all components in absolute values (i.e. positive or + /// zero). + #[inline] + pub fn abs(self) -> Self { + Self::from_glam(self.to_glam().abs()) } - pub fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: $Scalar) -> Self { - $( - let $comp = self.$comp.bezier_interpolate(control_1.$comp, control_2.$comp, end.$comp, t); - )* + /// Returns a new vector with all components clamped between the components of `min` and `max`. + /// + /// Panics + /// Panics if `min` > `max`, `min` is NaN, or `max` is NaN. + #[inline] + pub fn clamp(self, min: Self, max: Self) -> Self { + Self::from_glam(self.to_glam().clamp(min.to_glam(), max.to_glam())) + } - Self::new($($comp),*) + /// Returns the length (magnitude) of this vector. + #[inline] + pub fn length(self) -> real { + // does the same as glam's length() but also works for integer vectors + (self.length_squared() as real).sqrt() } - pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: $Scalar) -> Self { - $( - let $comp = self.$comp.cubic_interpolate(b.$comp, pre_a.$comp, post_b.$comp, weight); - )* + /// Squared length (squared magnitude) of this vector. + /// + /// Runs faster than [`Self::length`], so prefer it if you need to compare vectors or need the + /// squared distance for some formula. + #[inline] + pub fn length_squared(self) -> $Scalar { + self.to_glam().length_squared() + } - Self::new($($comp),*) + /// Returns a new vector containing the minimum of the two vectors, component-wise. + #[inline] + pub fn coord_min(self, other: Self) -> Self { + self.glam2(&other, |a, b| a.min(b)) } + /// Returns a new vector containing the maximum of the two vectors, component-wise. + #[inline] + pub fn coord_max(self, other: Self) -> Self { + self.glam2(&other, |a, b| a.max(b)) + } + + /// Returns a new vector with each component set to 1 if it's positive, -1 if it's negative, and 0 if it's zero. + #[inline] + pub fn sign(self) -> Self { + Self::from_glam(self.to_glam().signum()) + } + } + } +} + +/// Implements functions that are present only on floating-point vectors. +macro_rules! impl_float_vector_fns { + ( + // Name of the vector type. + $Vector:ty, + // Names of the components, with parentheses, for example `(x, y)`. + ($($comp:ident),*) + ) => { + impl $Vector { + /// Returns a new vector with all components rounded up (towards positive infinity). + #[inline] + pub fn ceil(self) -> Self { + Self::from_glam(self.to_glam().ceil()) + } + + /// Performs a cubic interpolation between this vector and `b` using `pre_a` and `post_b` as handles, + /// and returns the result at position `weight`. `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. + #[inline] + pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: real) -> Self { + Self::new( + $( + self.$comp.cubic_interpolate(b.$comp, pre_a.$comp, post_b.$comp, weight) + ),* + ) + } + + /// Performs a cubic interpolation between this vector and `b` using `pre_a` and `post_b` as handles, + /// and returns the result at position `weight`. `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. + /// It can perform smoother interpolation than [`Self::cubic_interpolate`] by the time values. + #[inline] #[allow(clippy::too_many_arguments)] pub fn cubic_interpolate_in_time( self, b: Self, pre_a: Self, post_b: Self, - weight: $Scalar, - b_t: $Scalar, - pre_a_t: $Scalar, - post_b_t: $Scalar, + weight: real, + b_t: real, + pre_a_t: real, + post_b_t: real, ) -> Self { - $( - let $comp = self.$comp.cubic_interpolate_in_time( - b.$comp, pre_a.$comp, post_b.$comp, weight, b_t, pre_a_t, post_b_t, - ); - )* + Self::new( + $( + self.$comp.cubic_interpolate_in_time( + b.$comp, pre_a.$comp, post_b.$comp, weight, b_t, pre_a_t, post_b_t, + ) + ),* + ) + } + + /// Returns the normalized vector pointing from this vector to `to`. This is equivalent to using `(b - a).normalized()`. + #[inline] + pub fn direction_to(self, to: Self) -> Self { + (to - self).normalized() + } + + /// Returns the squared distance between this vector and `to`. + /// + /// This method runs faster than [`Self::distance_to`], so prefer it if you need to compare vectors or need the squared distance for some formula. + #[inline] + pub fn distance_squared_to(self, to: Self) -> real { + (to - self).length_squared() + } + + /// Returns the distance between this vector and `to`. + #[inline] + pub fn distance_to(self, to: Self) -> real { + (to - self).length() + } - Self::new($($comp),*) + /// Returns the dot product of this vector and `with`. + #[inline] + pub fn dot(self, with: Self) -> real { + self.to_glam().dot(with.to_glam()) } + /// Returns a new vector with all components rounded down (towards negative infinity). + #[inline] + pub fn floor(self) -> Self { + Self::from_glam(self.to_glam().floor()) + } + + /// Returns true if each component of this vector is finite. + #[inline] + pub fn is_finite(self) -> bool { + self.to_glam().is_finite() + } + + /// Returns `true` if the vector is normalized, i.e. its length is approximately equal to 1. + #[inline] + pub fn is_normalized(self) -> bool { + self.to_glam().is_normalized() + } + + /// Returns `true` if this vector's values are approximately zero. + /// This method is faster than using [`Self::is_equal_approx`] with one value as a zero vector. + #[inline] pub fn is_zero_approx(self) -> bool { - $(self.$comp.is_zero_approx())&&* + $( self.$comp.is_zero_approx() )&&* } - pub fn posmod(self, pmod: $Scalar) -> Self { + /// Returns the result of the linear interpolation between this vector and `to` by amount `weight`. + /// `weight` is on the range of `0.0` to `1.0`, representing the amount of interpolation. + #[inline] + pub fn lerp(self, other: Self, weight: real) -> Self { + Self::new( + $( self.$comp.lerp(other.$comp, weight) ),* + ) + } + + /// Returns the vector scaled to unit length. Equivalent to `self / self.length()`. See + /// also `is_normalized()`. + /// + /// If the vector is zero, the result is also zero. + #[inline] + pub fn normalized(self) -> Self { + // Copy Godot's implementation since it's faster than using glam's normalize_or_zero(). + if self == Self::ZERO { + return self; + } + + let l = self.length(); + + Self::new( + $( self.$comp / l ),* + ) + } + + /// Returns a vector composed of the [`FloatExt::fposmod`] of this vector's components and `pmod`. + #[inline] + pub fn posmod(self, pmod: real) -> Self { Self::new( $( self.$comp.fposmod(pmod) ),* ) } + /// Returns a vector composed of the [`FloatExt::fposmod`] of this vector's components and `modv`'s components. + #[inline] pub fn posmodv(self, modv: Self) -> Self { Self::new( $( self.$comp.fposmod(modv.$comp) ),* ) } - pub fn sign(self) -> Self { - Self::new( - $( self.$comp.sign() ),* - ) + /// Returns a new vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. + #[inline] + pub fn round(self) -> Self { + Self::from_glam(self.to_glam().round()) } + /// A new vector with each component snapped to the closest multiple of the corresponding + /// component in `step`. + // TODO: also implement for integer vectors + #[inline] pub fn snapped(self, step: Self) -> Self { Self::new( - $( self.$comp.snapped(step.$comp) ),* + $( + self.$comp.snapped(step.$comp) + ),* ) } } impl $crate::builtin::math::ApproxEq for $Vector { + /// Returns `true` if this vector and `to` are approximately equal. #[inline] + #[doc(alias = "is_approx_eq")] fn approx_eq(&self, other: &Self) -> bool { $( self.$comp.approx_eq(&other.$comp) )&&* } @@ -438,46 +620,50 @@ macro_rules! impl_float_vector_component_fns { }; } -/// Implements common constants and methods for integer type vectors based on their components. -macro_rules! impl_integer_vector_component_fns { +/// Implements functions present on 2D vectors. +macro_rules! impl_vector2x_fns { ( // Name of the vector type. $Vector:ty, // Type of target component, for example `real`. - $Scalar:ty, - // Names of the components, with parentheses, for example `(x, y)`. - ($($comp:ident),*) + $Scalar:ty ) => { impl $Vector { - /// Squared length (squared magnitude) of this vector. - /// - /// Runs faster than `length`, so prefer it if you need to compare vectors or need the - /// squared distance for some formula. + /// Returns the aspect ratio of this vector, the ratio of [`Self::x`] to [`Self::y`]. #[inline] - pub fn length_squared(self) -> i64 { - let mut val = 0; - - $( - val += self.$comp as i64 * self.$comp as i64; - )* + pub fn aspect(self) -> real { + self.x as real / self.y as real + } - val + /// Returns the axis of the vector's highest value. See [`Vector2Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector2Axis::X)`. + #[inline] + #[doc(alias = "max_axis_index")] + pub fn max_axis(self) -> Option { + match self.x.partial_cmp(&self.y) { + Some(Ordering::Less) => Some(Vector2Axis::Y), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector2Axis::X), + _ => None, + } } - /// A new vector with each component snapped to the closest multiple of the corresponding - /// component in step. + /// Returns the axis of the vector's lowest value. See [`Vector2Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector2Axis::Y)`. #[inline] - pub fn snapped(self, step: Self) -> Self { - Self::new( - $( (self.$comp as $Scalar).snapped(step.$comp as $Scalar) as i32 ),* - ) + #[doc(alias = "min_axis_index")] + pub fn min_axis(self) -> Option { + match self.x.partial_cmp(&self.y) { + Some(Ordering::Less) => Some(Vector2Axis::X), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector2Axis::Y), + _ => None, + } } } - }; -} -macro_rules! impl_swizzle_trait_for_vector2x { - ($Vector:ty, $Scalar:ty) => { impl $crate::builtin::SwizzleToVector for ($Scalar, $Scalar) { type Output = $Vector; fn swizzle_to_vector(self) -> $Vector { @@ -487,8 +673,70 @@ macro_rules! impl_swizzle_trait_for_vector2x { }; } -macro_rules! impl_swizzle_trait_for_vector3x { - ($Vector:ty, $Scalar:ty) => { +/// Implements functions present on 3D vectors. +macro_rules! impl_vector3x_fns { + ( + // Name of the vector type. + $Vector:ty, + // Type of target component, for example `real`. + $Scalar:ty + ) => { + impl $Vector { + /// Returns the axis of the vector's highest value. See [`Vector3Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector3Axis::X)`. + #[inline] + #[doc(alias = "max_axis_index")] + pub fn max_axis(self) -> Option { + match self.x.partial_cmp(&self.y) { + Some(Ordering::Less) => match self.y.partial_cmp(&self.z) { + Some(Ordering::Less) => Some(Vector3Axis::Z), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector3Axis::Y), + _ => None, + }, + Some(Ordering::Equal) => match self.x.partial_cmp(&self.z) { + Some(Ordering::Less) => Some(Vector3Axis::Z), + _ => None, + }, + Some(Ordering::Greater) => match self.x.partial_cmp(&self.z) { + Some(Ordering::Less) => Some(Vector3Axis::Z), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector3Axis::X), + _ => None, + }, + _ => None, + } + } + + /// Returns the axis of the vector's lowest value. See [`Vector3Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector3Axis::Z)`. + #[inline] + #[doc(alias = "min_axis_index")] + pub fn min_axis(self) -> Option { + match self.x.partial_cmp(&self.y) { + Some(Ordering::Less) => match self.x.partial_cmp(&self.z) { + Some(Ordering::Less) => Some(Vector3Axis::X), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector3Axis::Z), + _ => None, + }, + Some(Ordering::Equal) => match self.x.partial_cmp(&self.z) { + Some(Ordering::Equal) => Some(Vector3Axis::Z), + _ => None, + }, + Some(Ordering::Greater) => match self.y.partial_cmp(&self.z) { + Some(Ordering::Less) => Some(Vector3Axis::Y), + Some(Ordering::Equal) => None, + Some(Ordering::Greater) => Some(Vector3Axis::Z), + _ => None, + }, + _ => None, + } + } + } + impl $crate::builtin::SwizzleToVector for ($Scalar, $Scalar, $Scalar) { type Output = $Vector; fn swizzle_to_vector(self) -> $Vector { @@ -498,8 +746,70 @@ macro_rules! impl_swizzle_trait_for_vector3x { }; } -macro_rules! impl_swizzle_trait_for_vector4x { - ($Vector:ty, $Scalar:ty) => { +/// Implements functions present on 4D vectors. +macro_rules! impl_vector4x_fns { + ( + // Name of the vector type. + $Vector:ty, + // Type of target component, for example `real`. + $Scalar:ty + ) => { + impl $Vector { + /// Returns the axis of the vector's highest value. See [`Vector4Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector4Axis::X)`. + #[inline] + #[doc(alias = "max_axis_index")] + pub fn max_axis(self) -> Option { + let mut max_axis = Vector4Axis::X; + let mut previous = None; + let mut max_value = self.x; + + let components = [ + (Vector4Axis::Y, self.y), + (Vector4Axis::Z, self.z), + (Vector4Axis::W, self.w), + ]; + + for (axis, value) in components { + if value >= max_value { + max_axis = axis; + previous = Some(max_value); + max_value = value; + } + } + + (Some(max_value) != previous).then_some(max_axis) + } + + /// Returns the axis of the vector's lowest value. See [`Vector4Axis`] enum. If all components are equal, this method returns [`None`]. + /// + /// To mimic Godot's behavior, unwrap this function's result with `unwrap_or(Vector4Axis::W)`. + #[inline] + #[doc(alias = "min_axis_index")] + pub fn min_axis(self) -> Option { + let mut min_axis = Vector4Axis::X; + let mut previous = None; + let mut min_value = self.x; + + let components = [ + (Vector4Axis::Y, self.y), + (Vector4Axis::Z, self.z), + (Vector4Axis::W, self.w), + ]; + + for (axis, value) in components { + if value <= min_value { + min_axis = axis; + previous = Some(min_value); + min_value = value; + } + } + + (Some(min_value) != previous).then_some(min_axis) + } + } + impl $crate::builtin::SwizzleToVector for ($Scalar, $Scalar, $Scalar, $Scalar) { type Output = $Vector; fn swizzle_to_vector(self) -> $Vector { @@ -508,3 +818,103 @@ macro_rules! impl_swizzle_trait_for_vector4x { } }; } + +/// Implements functions present on floating-point 2D and 3D vectors. +macro_rules! impl_vector2_vector3_fns { + ( + // Name of the vector type. + $Vector:ty, + // Names of the components, with parentheses, for example `(x, y, z, w)`. + ($($comp:ident),*) + ) => { + impl $Vector { + /// Returns the angle to the given vector, in radians. + #[inline] + pub fn angle_to(self, to: Self) -> real { + self.glam2(&to, |a, b| a.angle_between(b)) + } + + /// Returns the derivative at the given `t` on the [Bézier](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) + /// curve defined by this vector and the given `control_1`, `control_2`, and `end` points. + #[inline] + pub fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: real) -> Self { + Self::new( + $( + self.$comp.bezier_derivative(control_1.$comp, control_2.$comp, end.$comp, t) + ),* + ) + } + + /// Returns the point at the given `t` on the [Bézier](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) + /// curve defined by this vector and the given `control_1`, `control_2`, and `end` points. + #[inline] + pub fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: real) -> Self { + Self::new( + $( + self.$comp.bezier_interpolate(control_1.$comp, control_2.$comp, end.$comp, t) + ),* + ) + } + + /// Returns a new vector "bounced off" from a plane defined by the given normal. + #[inline] + pub fn bounce(self, normal: Self) -> Self { + -self.reflect(normal) + } + + /// Returns the vector with a maximum length by limiting its length to `length`. + #[inline] + pub fn limit_length(self, length: Option) -> Self { + let length = length.unwrap_or(1.0); + + Self::from_glam(self.to_glam().clamp_length_max(length)) + + } + + /// Returns a new vector moved toward `to` by the fixed `delta` amount. Will not go past the final value. + #[inline] + pub fn move_toward(self, to: Self, delta: real) -> Self { + Self::from_glam(self.to_glam().move_towards(to.to_glam(), delta)) + } + + /// Returns the result of projecting the vector onto the given vector `b`. + #[inline] + pub fn project(self, b: Self) -> Self { + Self::from_glam(self.to_glam().project_onto(b.to_glam())) + } + + /// Returns the result of reflecting the vector defined by the given direction vector `n`. + #[inline] + pub fn reflect(self, n: Self) -> Self { + 2.0 * n * self.dot(n) - self + } + + /// Returns a new vector slid along a plane defined by the given normal. + #[inline] + pub fn slide(self, n: Self) -> Self { + self - n * self.dot(n) + } + } + }; +} + +/// Implements functions present on floating-point 3D and 4D vectors. +macro_rules! impl_vector3_vector4_fns { + ( + // Name of the vector type. + $Vector:ty, + // Names of the components, with parentheses, for example `(x, y, z, w)`. + ($($comp:ident),*) + ) => { + impl $Vector { + /// Returns the reciprocal (inverse) of the vector. This is the same as `1.0/n` for each component. + #[inline] + #[doc(alias = "inverse")] + pub fn recip(self) -> Self { + Self::new( + $( 1.0 / self.$comp ),* + ) + } + } + }; +} From ccf7745498b722da7c7acbbf504f3ffd33a81514 Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Wed, 29 May 2024 21:03:32 +0200 Subject: [PATCH 2/8] Refactor 2D vectors with new macros --- godot-core/src/builtin/vectors/vector2.rs | 219 ++++++--------------- godot-core/src/builtin/vectors/vector2i.rs | 86 ++------ 2 files changed, 78 insertions(+), 227 deletions(-) diff --git a/godot-core/src/builtin/vectors/vector2.rs b/godot-core/src/builtin/vectors/vector2.rs index bd1e165ab..da44966b1 100644 --- a/godot-core/src/builtin/vectors/vector2.rs +++ b/godot-core/src/builtin/vectors/vector2.rs @@ -5,13 +5,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use core::cmp::Ordering; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::inner; use crate::builtin::math::{FloatExt, GlamConv, GlamType}; use crate::builtin::vectors::Vector2Axis; -use crate::builtin::{real, RAffine2, RVec2, Vector2i}; +use crate::builtin::{inner, real, RAffine2, RVec2, Vector2i}; use std::fmt; @@ -36,39 +36,20 @@ pub struct Vector2 { pub y: real, } -impl Vector2 { - /// Vector with all components set to `0.0`. - pub const ZERO: Self = Self::splat(0.0); - - /// Vector with all components set to `1.0`. - pub const ONE: Self = Self::splat(1.0); - - /// Vector with all components set to `real::INFINITY`. - pub const INF: Self = Self::splat(real::INFINITY); - - /// Unit vector in -X direction (right in 2D coordinate system). - pub const LEFT: Self = Self::new(-1.0, 0.0); - - /// Unit vector in +X direction (right in 2D coordinate system). - pub const RIGHT: Self = Self::new(1.0, 0.0); - - /// Unit vector in -Y direction (up in 2D coordinate system). - pub const UP: Self = Self::new(0.0, -1.0); +impl_vector_operators!(Vector2, real, (x, y)); - /// Unit vector in +Y direction (down in 2D coordinate system). - pub const DOWN: Self = Self::new(0.0, 1.0); +impl_vector_consts!(Vector2, real); +impl_float_vector_consts!(Vector2); +impl_vector2x_consts!(Vector2, real); - /// Constructs a new `Vector2` from the given `x` and `y`. - pub const fn new(x: real, y: real) -> Self { - Self { x, y } - } - - /// Constructs a new `Vector2` with both components set to `v`. - pub const fn splat(v: real) -> Self { - Self::new(v, v) - } +impl_vector_fns!(Vector2, RVec2, real, (x, y)); +impl_float_vector_fns!(Vector2, (x, y)); +impl_vector2x_fns!(Vector2, real); +impl_vector2_vector3_fns!(Vector2, (x, y)); +impl Vector2 { /// Constructs a new `Vector2` from a [`Vector2i`]. + #[inline] pub const fn from_vector2i(v: Vector2i) -> Self { Self { x: v.x as real, @@ -76,132 +57,80 @@ impl Vector2 { } } - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: RVec2) -> Self { - Self::new(v.x, v.y) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> RVec2 { - RVec2::new(self.x, self.y) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector2 { + inner::InnerVector2::from_outer(self) } + /// Returns this vector's angle with respect to the positive X axis, or `(1.0, 0.0)` vector, in radians. + /// + /// For example, `Vector2::RIGHT.angle()` will return zero, `Vector2::DOWN.angle()` will return `PI / 2` (a quarter turn, or 90 degrees), + /// and `Vector2::new(1.0, -1.0).angle()` will return `-PI / 4` (a negative eighth turn, or -45 degrees). + /// + /// [Illustration of the returned angle.](https://raw.githubusercontent.com/godotengine/godot-docs/master/img/vector2_angle.png) + /// + /// Equivalent to the result of `y.atan2(x)`. + #[inline] pub fn angle(self) -> real { self.y.atan2(self.x) } - pub fn angle_to(self, to: Self) -> real { - self.to_glam().angle_between(to.to_glam()) - } - + /// Returns the angle to the given vector, in radians. + /// + /// [Illustration of the returned angle.](https://raw.githubusercontent.com/godotengine/godot-docs/master/img/vector2_angle_to.png) + #[inline] pub fn angle_to_point(self, to: Self) -> real { (to - self).angle() } - pub fn aspect(self) -> real { - self.x / self.y - } - - pub fn bounce(self, normal: Self) -> Self { - -self.reflect(normal) - } - - pub fn ceil(self) -> Self { - Self::from_glam(self.to_glam().ceil()) - } - - pub fn clamp(self, min: Self, max: Self) -> Self { - Self::from_glam(self.to_glam().clamp(min.to_glam(), max.to_glam())) - } - + /// Returns the 2D analog of the cross product for this vector and `with`. + /// + /// This is the signed area of the parallelogram formed by the two vectors. If the second vector is clockwise from the first vector, + /// then the cross product is the positive area. If counter-clockwise, the cross product is the negative area. If the two vectors are + /// parallel this returns zero, making it useful for testing if two vectors are parallel. + /// + /// Note: Cross product is not defined in 2D mathematically. This method embeds the 2D vectors in the XY plane of 3D space and uses + /// their cross product's Z component as the analog. + #[inline] pub fn cross(self, with: Self) -> real { self.to_glam().perp_dot(with.to_glam()) } - pub fn direction_to(self, to: Self) -> Self { - (to - self).normalized() - } - - pub fn distance_squared_to(self, to: Self) -> real { - (to - self).length_squared() - } - - pub fn distance_to(self, to: Self) -> real { - (to - self).length() - } - - pub fn dot(self, other: Self) -> real { - self.to_glam().dot(other.to_glam()) - } - - pub fn floor(self) -> Self { - Self::from_glam(self.to_glam().floor()) - } - + /// Creates a unit Vector2 rotated to the given `angle` in radians. This is equivalent to doing `Vector2::new(angle.cos(), angle.sin())` + /// or `Vector2::RIGHT.rotated(angle)`. + /// + /// ```no_run + /// use godot::prelude::*; + /// use std::f32::consts::PI; + /// + /// let a = Vector2::from_angle(0.0); // (1.0, 0.0) + /// let b = Vector2::new(1.0, 0.0).angle(); // 0.0 + /// let c = Vector2::from_angle(PI / 2.0); // (0.0, 1.0) + /// ``` + #[inline] pub fn from_angle(angle: real) -> Self { Self::from_glam(RVec2::from_angle(angle)) } - pub fn is_finite(self) -> bool { - self.to_glam().is_finite() - } - - pub fn is_normalized(self) -> bool { - self.to_glam().is_normalized() - } - - pub fn length_squared(self) -> real { - self.to_glam().length_squared() - } - - pub fn limit_length(self, length: Option) -> Self { - Self::from_glam(self.to_glam().clamp_length_max(length.unwrap_or(1.0))) - } - - pub fn max_axis_index(self) -> Vector2Axis { - if self.x < self.y { - Vector2Axis::Y - } else { - Vector2Axis::X - } - } - - pub fn min_axis_index(self) -> Vector2Axis { - if self.x < self.y { - Vector2Axis::X - } else { - Vector2Axis::Y - } - } - - pub fn move_toward(self, to: Self, delta: real) -> Self { - let vd = to - self; - let len = vd.length(); - if len <= delta || len < real::CMP_EPSILON { - to - } else { - self + vd / len * delta - } - } - + /// Returns a perpendicular vector rotated 90 degrees counter-clockwise compared to the original, with the same length. + #[inline] pub fn orthogonal(self) -> Self { Self::new(self.y, -self.x) } - pub fn project(self, b: Self) -> Self { - Self::from_glam(self.to_glam().project_onto(b.to_glam())) - } - - pub fn reflect(self, normal: Self) -> Self { - 2.0 * normal * self.dot(normal) - self - } - - pub fn round(self) -> Self { - Self::from_glam(self.to_glam().round()) + /// Returns the result of rotating this vector by `angle` (in radians). + #[inline] + pub fn rotated(self, angle: real) -> Self { + Self::from_glam(RAffine2::from_angle(angle).transform_vector2(self.to_glam())) } - // TODO compare with gdnative implementation: - // https://github.com/godot-rust/gdnative/blob/master/gdnative-core/src/core_types/vector3.rs#L335-L343 + /// Returns the result of spherical linear interpolation between this vector and `to`, by amount `weight`. + /// `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. + /// + /// This method also handles interpolating the lengths if the input vectors have different lengths. + /// For the special case of one or both input vectors having zero length, this method behaves like [`Vector2::lerp`]. + #[inline] pub fn slerp(self, to: Self, weight: real) -> Self { let start_length_sq = self.length_squared(); let end_length_sq = to.length_squared(); @@ -213,24 +142,6 @@ impl Vector2 { let angle = self.angle_to(to); self.rotated(angle * weight) * (result_length / start_length) } - - pub fn slide(self, normal: Self) -> Self { - self - normal * self.dot(normal) - } - - /// Returns the result of rotating this vector by `angle` (in radians). - pub fn rotated(self, angle: real) -> Self { - Self::from_glam(RAffine2::from_angle(angle).transform_vector2(self.to_glam())) - } - - #[doc(hidden)] - pub fn as_inner(&self) -> inner::InnerVector2 { - inner::InnerVector2::from_outer(self) - } - - pub fn coords(&self) -> (real, real) { - (self.x, self.y) - } } /// Formats the vector like Godot: `(x, y)`. @@ -240,12 +151,6 @@ impl fmt::Display for Vector2 { } } -impl_common_vector_fns!(Vector2, real); -impl_float_vector_glam_fns!(Vector2, real); -impl_float_vector_component_fns!(Vector2, real, (x, y)); -impl_vector_operators!(Vector2, real, (x, y)); -impl_swizzle_trait_for_vector2x!(Vector2, real); - // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector2 { diff --git a/godot-core/src/builtin/vectors/vector2i.rs b/godot-core/src/builtin/vectors/vector2i.rs index 5afdcc208..55a60de73 100644 --- a/godot-core/src/builtin/vectors/vector2i.rs +++ b/godot-core/src/builtin/vectors/vector2i.rs @@ -9,8 +9,8 @@ use godot_ffi as sys; use std::cmp::Ordering; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::math::{FloatExt, GlamConv, GlamType}; -use crate::builtin::{real, RVec2, Vector2, Vector2Axis}; +use crate::builtin::math::{GlamConv, GlamType}; +use crate::builtin::{inner, real, RVec2, Vector2, Vector2Axis}; use std::fmt; @@ -34,59 +34,18 @@ pub struct Vector2i { pub y: i32, } -impl Vector2i { - /// Vector with all components set to `0`. - pub const ZERO: Self = Self::splat(0); - - /// Vector with all components set to `1`. - pub const ONE: Self = Self::splat(1); - - /// Unit vector in -X direction (right in 2D coordinate system). - pub const LEFT: Self = Self::new(-1, 0); - - /// Unit vector in +X direction (right in 2D coordinate system). - pub const RIGHT: Self = Self::new(1, 0); - - /// Unit vector in -Y direction (up in 2D coordinate system). - pub const UP: Self = Self::new(0, -1); - - /// Unit vector in +Y direction (down in 2D coordinate system). - pub const DOWN: Self = Self::new(0, 1); - - /// Constructs a new `Vector2i` from the given `x` and `y`. - pub const fn new(x: i32, y: i32) -> Self { - Self { x, y } - } - - /// Aspect ratio: x / y, as a `real` value. - pub fn aspect(self) -> real { - self.x as real / self.y as real - } - - /// Axis of the vector's highest value. [`None`] if components are equal. - pub fn max_axis(self) -> Option { - match self.x.cmp(&self.y) { - Ordering::Less => Some(Vector2Axis::Y), - Ordering::Equal => None, - Ordering::Greater => Some(Vector2Axis::X), - } - } +impl_vector_operators!(Vector2i, i32, (x, y)); - /// Axis of the vector's highest value. [`None`] if components are equal. - pub fn min_axis(self) -> Option { - match self.x.cmp(&self.y) { - Ordering::Less => Some(Vector2Axis::X), - Ordering::Equal => None, - Ordering::Greater => Some(Vector2Axis::Y), - } - } +impl_vector_consts!(Vector2i, i32); +impl_integer_vector_consts!(Vector2i); +impl_vector2x_consts!(Vector2i, i32); - /// Constructs a new `Vector2i` with both components set to `v`. - pub const fn splat(v: i32) -> Self { - Self::new(v, v) - } +impl_vector_fns!(Vector2i, glam::IVec2, i32, (x, y)); +impl_vector2x_fns!(Vector2i, i32); +impl Vector2i { /// Constructs a new `Vector2i` from a [`Vector2`]. The floating point coordinates will be truncated. + #[inline] pub const fn from_vector2(v: Vector2) -> Self { Self { x: v.x as i32, @@ -94,23 +53,16 @@ impl Vector2i { } } - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: glam::IVec2) -> Self { - Self::new(v.x, v.y) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> glam::IVec2 { - glam::IVec2::new(self.x, self.y) - } - /// Converts `self` to the corresponding [`real`] `glam` type. - fn to_glam_real(self) -> RVec2 { + #[inline] + pub fn to_glam_real(self) -> RVec2 { RVec2::new(self.x as real, self.y as real) } - pub fn coords(&self) -> (i32, i32) { - (self.x, self.y) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector2i { + inner::InnerVector2i::from_outer(self) } } @@ -121,12 +73,6 @@ impl fmt::Display for Vector2i { } } -impl_common_vector_fns!(Vector2i, i32); -impl_integer_vector_glam_fns!(Vector2i, real); -impl_integer_vector_component_fns!(Vector2i, real, (x, y)); -impl_vector_operators!(Vector2i, i32, (x, y)); -impl_swizzle_trait_for_vector2x!(Vector2i, i32); - // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector2i { From f777cfaf25a51548d0a558f1e754d8c000af32ab Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Wed, 29 May 2024 21:07:48 +0200 Subject: [PATCH 3/8] Refactor 3D vectors with new macros --- godot-core/src/builtin/aabb.rs | 4 +- godot-core/src/builtin/vectors/vector3.rs | 235 +++++++----------- godot-core/src/builtin/vectors/vector3i.rs | 113 ++------- .../src/builtin/vectors/vector_macros.rs | 2 +- 4 files changed, 106 insertions(+), 248 deletions(-) diff --git a/godot-core/src/builtin/aabb.rs b/godot-core/src/builtin/aabb.rs index 955bb0880..5d37d3871 100644 --- a/godot-core/src/builtin/aabb.rs +++ b/godot-core/src/builtin/aabb.rs @@ -200,7 +200,7 @@ impl Aabb { /// Returns the index of the longest axis of the AABB (according to Vector3's AXIS_* constants). #[inline] pub fn longest_axis_index(&self) -> Vector3Axis { - self.size.max_axis_index() + self.size.max_axis().unwrap_or(Vector3Axis::X) } /// Returns the scalar length of the longest axis of the AABB. @@ -222,7 +222,7 @@ impl Aabb { /// Returns the index of the shortest axis of the AABB (according to Vector3::AXIS* enum). #[inline] pub fn shortest_axis_index(&self) -> Vector3Axis { - self.size.min_axis_index() + self.size.min_axis().unwrap_or(Vector3Axis::Z) } /// Returns the scalar length of the shortest axis of the AABB. diff --git a/godot-core/src/builtin/vectors/vector3.rs b/godot-core/src/builtin/vectors/vector3.rs index f93c9a52f..e613c2624 100644 --- a/godot-core/src/builtin/vectors/vector3.rs +++ b/godot-core/src/builtin/vectors/vector3.rs @@ -5,12 +5,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use core::cmp::Ordering; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use crate::builtin::math::{FloatExt, GlamConv, GlamType}; use crate::builtin::vectors::Vector3Axis; -use crate::builtin::{real, Basis, RVec3, Vector3i}; +use crate::builtin::{inner, real, Basis, RVec3, Vector2, Vector3i}; use std::fmt; @@ -38,42 +39,39 @@ pub struct Vector3 { pub z: real, } -impl Vector3 { - /// Vector with all components set to `0.0`. - pub const ZERO: Self = Self::splat(0.0); +impl_vector_operators!(Vector3, real, (x, y, z)); - /// Vector with all components set to `1.0`. - pub const ONE: Self = Self::splat(1.0); +impl_vector_consts!(Vector3, real); +impl_float_vector_consts!(Vector3); +impl_vector3x_consts!(Vector3, real); - /// Unit vector in -X direction. Can be interpreted as left in an untransformed 3D world. - pub const LEFT: Self = Self::new(-1.0, 0.0, 0.0); +impl_vector_fns!(Vector3, RVec3, real, (x, y, z)); +impl_float_vector_fns!(Vector3, (x, y, z)); +impl_vector3x_fns!(Vector3, real); +impl_vector2_vector3_fns!(Vector3, (x, y, z)); +impl_vector3_vector4_fns!(Vector3, (x, y, z)); - /// Unit vector in +X direction. Can be interpreted as right in an untransformed 3D world. - pub const RIGHT: Self = Self::new(1.0, 0.0, 0.0); +impl Vector3 { + /// Unit vector pointing towards the left side of imported 3D assets. + pub const MODEL_LEFT: Self = Self::new(1.0, 0.0, 0.0); - /// Unit vector in +Y direction. Typically interpreted as up in a 3D world. - pub const UP: Self = Self::new(0.0, 1.0, 0.0); + /// Unit vector pointing towards the right side of imported 3D assets. + pub const MODEL_RIGHT: Self = Self::new(-1.0, 0.0, 0.0); - /// Unit vector in -Y direction. Typically interpreted as down in a 3D world. - pub const DOWN: Self = Self::new(0.0, -1.0, 0.0); + /// Unit vector pointing towards the top side (up) of imported 3D assets. + pub const MODEL_UP: Self = Self::new(0.0, 1.0, 0.0); - /// Unit vector in -Z direction. Can be interpreted as "into the screen" in an untransformed 3D world. - pub const FORWARD: Self = Self::new(0.0, 0.0, -1.0); + /// Unit vector pointing towards the bottom side (down) of imported 3D assets. + pub const MODEL_BOTTOM: Self = Self::new(0.0, -1.0, 0.0); - /// Unit vector in +Z direction. Can be interpreted as "out of the screen" in an untransformed 3D world. - pub const BACK: Self = Self::new(0.0, 0.0, 1.0); + /// Unit vector pointing towards the front side (facing forward) of imported 3D assets. + pub const MODEL_FRONT: Self = Self::new(0.0, 0.0, 1.0); - /// Returns a `Vector3` with the given components. - pub const fn new(x: real, y: real, z: real) -> Self { - Self { x, y, z } - } - - /// Returns a new `Vector3` with all components set to `v`. - pub const fn splat(v: real) -> Self { - Self::new(v, v, v) - } + /// Unit vector pointing towards the rear side (back) of imported 3D assets. + pub const MODEL_REAR: Self = Self::new(0.0, 0.0, -1.0); /// Constructs a new `Vector3` from a [`Vector3i`]. + #[inline] pub const fn from_vector3i(v: Vector3i) -> Self { Self { x: v.x as real, @@ -82,126 +80,89 @@ impl Vector3 { } } - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: RVec3) -> Self { - Self::new(v.x, v.y, v.z) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> RVec3 { - RVec3::new(self.x, self.y, self.z) - } - - pub fn angle_to(self, to: Self) -> real { - self.to_glam().angle_between(to.to_glam()) - } - - pub fn bounce(self, normal: Self) -> Self { - -self.reflect(normal) - } - - pub fn ceil(self) -> Self { - Self::from_glam(self.to_glam().ceil()) - } - - pub fn clamp(self, min: Self, max: Self) -> Self { - Self::from_glam(self.to_glam().clamp(min.to_glam(), max.to_glam())) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector3 { + inner::InnerVector3::from_outer(self) } + /// Returns the cross product of this vector and `with`. + /// + /// This returns a vector perpendicular to both this and `with`, which would be the normal vector of the plane + /// defined by the two vectors. As there are two such vectors, in opposite directions, + /// this method returns the vector defined by a right-handed coordinate system. + /// If the two vectors are parallel this returns an empty vector, making it useful for testing if two vectors are parallel. + #[inline] pub fn cross(self, with: Self) -> Self { Self::from_glam(self.to_glam().cross(with.to_glam())) } - pub fn direction_to(self, to: Self) -> Self { - (to - self).normalized() - } - - pub fn distance_squared_to(self, to: Self) -> real { - (to - self).length_squared() - } - - pub fn distance_to(self, to: Self) -> real { - (to - self).length() - } - - pub fn dot(self, with: Self) -> real { - self.to_glam().dot(with.to_glam()) - } - - pub fn floor(self) -> Self { - Self::from_glam(self.to_glam().floor()) - } + /// Returns the Vector3 from an octahedral-compressed form created using [`Vector3::octahedron_encode`] (stored as a [`Vector2`]). + #[inline] + pub fn octahedron_decode(uv: Vector2) -> Self { + let f = Vector2::new(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0); + let mut n = Vector3::new(f.x, f.y, 1.0 - f.x.abs() - f.y.abs()); - pub fn inverse(self) -> Self { - Self::new(1.0 / self.x, 1.0 / self.y, 1.0 / self.z) - } + let t = (-n.z).clamp(0.0, 1.0); + n.x += if n.x >= 0.0 { -t } else { t }; + n.y += if n.y >= 0.0 { -t } else { t }; - pub fn is_finite(self) -> bool { - self.to_glam().is_finite() + n.normalized() } - pub fn is_normalized(self) -> bool { - self.to_glam().is_normalized() - } + /// Returns the octahedral-encoded (oct32) form of this Vector3 as a [`Vector2`]. Since a [`Vector2`] occupies 1/3 less memory compared to Vector3, + /// this form of compression can be used to pass greater amounts of [`Vector3::normalized`] Vector3s without increasing storage or memory requirements. + /// See also [`Vector3::octahedron_decode`]. + /// + /// Note: Octahedral compression is lossy, although visual differences are rarely perceptible in real world scenarios. + /// + /// # Panics + /// If vector is not normalized. + #[inline] + pub fn octahedron_encode(self) -> Vector2 { + assert!(self.is_normalized()); - pub fn length_squared(self) -> real { - self.to_glam().length_squared() - } + let mut n = self; + n /= n.x.abs() + n.y.abs() + n.z.abs(); - pub fn limit_length(self, length: Option) -> Self { - Self::from_glam(self.to_glam().clamp_length_max(length.unwrap_or(1.0))) - } - - pub fn max_axis_index(self) -> Vector3Axis { - if self.x < self.y { - if self.y < self.z { - Vector3Axis::Z - } else { - Vector3Axis::Y - } - } else if self.x < self.z { - Vector3Axis::Z + let mut o = if n.z >= 0.0 { + Vector2::new(n.x, n.y) } else { - Vector3Axis::X - } - } + let x = (1.0 - n.y.abs()) * (if n.x >= 0.0 { 1.0 } else { -1.0 }); + let y = (1.0 - n.x.abs()) * (if n.y >= 0.0 { 1.0 } else { -1.0 }); - pub fn min_axis_index(self) -> Vector3Axis { - if self.x < self.y { - if self.x < self.z { - Vector3Axis::X - } else { - Vector3Axis::Z - } - } else if self.y < self.z { - Vector3Axis::Y - } else { - Vector3Axis::Z - } - } + Vector2::new(x, y) + }; - pub fn move_toward(self, to: Self, delta: real) -> Self { - let vd = to - self; - let len = vd.length(); - if len <= delta || len < real::CMP_EPSILON { - to - } else { - self + vd / len * delta - } - } + o.x = o.x * 0.5 + 0.5; + o.y = o.y * 0.5 + 0.5; - pub fn project(self, b: Self) -> Self { - Self::from_glam(self.to_glam().project_onto(b.to_glam())) + o } - pub fn reflect(self, normal: Self) -> Self { - 2.0 * normal * self.dot(normal) - self + /// Returns the outer product with `with`. + #[inline] + pub fn outer(self, with: Self) -> Basis { + let x = Vector3::new(self.x * with.x, self.x * with.y, self.x * with.z); + let y = Vector3::new(self.y * with.x, self.y * with.y, self.y * with.z); + let z = Vector3::new(self.z * with.x, self.z * with.y, self.z * with.z); + + Basis::from_rows(x, y, z) } - pub fn round(self) -> Self { - Self::from_glam(self.to_glam().round()) + /// Returns this vector rotated around `axis` by `angle` radians. `axis` must be normalized. + /// + /// # Panics + /// If `axis` is not normalized. + #[inline] + pub fn rotated(self, axis: Self, angle: real) -> Self { + assert!(axis.is_normalized()); + Basis::from_axis_angle(axis, angle) * self } + /// Returns the signed angle to the given vector, in radians. The sign of the angle is positive in a counter-clockwise direction and + /// negative in a clockwise direction when viewed from the side specified by the `axis`. + #[inline] pub fn signed_angle_to(self, to: Self, axis: Self) -> real { let cross_to = self.cross(to); let unsigned_angle = cross_to.length().atan2(self.dot(to)); @@ -221,6 +182,7 @@ impl Vector3 { /// Length is also interpolated in the case that the input vectors have different lengths. If both /// input vectors have zero length or are collinear to each other, the method instead behaves like /// [`Vector3::lerp`]. + #[inline] pub fn slerp(self, to: Self, weight: real) -> Self { let start_length_sq: real = self.length_squared(); let end_length_sq = to.length_squared(); @@ -245,23 +207,6 @@ impl Vector3 { let angle = self.angle_to(to); self.rotated(unit_axis, angle * weight) * (result_length / start_length) } - - pub fn slide(self, normal: Self) -> Self { - self - normal * self.dot(normal) - } - - /// Returns this vector rotated around `axis` by `angle` radians. `axis` must be normalized. - /// - /// # Panics - /// If `axis` is not normalized. - pub fn rotated(self, axis: Self, angle: real) -> Self { - assert!(axis.is_normalized()); - Basis::from_axis_angle(axis, angle) * self - } - - pub fn coords(&self) -> (real, real, real) { - (self.x, self.y, self.z) - } } /// Formats the vector like Godot: `(x, y, z)`. @@ -271,12 +216,6 @@ impl fmt::Display for Vector3 { } } -impl_common_vector_fns!(Vector3, real); -impl_float_vector_glam_fns!(Vector3, real); -impl_float_vector_component_fns!(Vector3, real, (x, y, z)); -impl_vector_operators!(Vector3, real, (x, y, z)); -impl_swizzle_trait_for_vector3x!(Vector3, real); - // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector3 { diff --git a/godot-core/src/builtin/vectors/vector3i.rs b/godot-core/src/builtin/vectors/vector3i.rs index ac099e721..0f6ac5505 100644 --- a/godot-core/src/builtin/vectors/vector3i.rs +++ b/godot-core/src/builtin/vectors/vector3i.rs @@ -11,8 +11,8 @@ use std::fmt; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::math::{FloatExt, GlamConv, GlamType}; -use crate::builtin::{real, RVec3, Vector3, Vector3Axis}; +use crate::builtin::math::{GlamConv, GlamType}; +use crate::builtin::{inner, real, RVec3, Vector3, Vector3Axis}; /// Vector used for 3D math using integer coordinates. /// @@ -37,86 +37,18 @@ pub struct Vector3i { pub z: i32, } -impl Vector3i { - /// Vector with all components set to `0`. - pub const ZERO: Self = Self::splat(0); - - /// Vector with all components set to `1`. - pub const ONE: Self = Self::splat(1); - - /// Unit vector in -X direction. - pub const LEFT: Self = Self::new(-1, 0, 0); - - /// Unit vector in +X direction. - pub const RIGHT: Self = Self::new(1, 0, 0); - - /// Unit vector in +Y direction. - pub const UP: Self = Self::new(0, 1, 0); - - /// Unit vector in -Y direction. - pub const DOWN: Self = Self::new(0, -1, 0); - - /// Unit vector in -Z direction. - pub const FORWARD: Self = Self::new(0, 0, -1); - - /// Unit vector in +Z direction. - pub const BACK: Self = Self::new(0, 0, 1); - - /// Returns a `Vector3i` with the given components. - pub const fn new(x: i32, y: i32, z: i32) -> Self { - Self { x, y, z } - } - - /// Axis of the vector's highest value. [`None`] if at least two components are equal. - pub fn max_axis(self) -> Option { - use Vector3Axis::*; - - match self.x.cmp(&self.y) { - Ordering::Less => match self.y.cmp(&self.z) { - Ordering::Less => Some(Z), - Ordering::Equal => None, - Ordering::Greater => Some(Y), - }, - Ordering::Equal => match self.x.cmp(&self.z) { - Ordering::Less => Some(Z), - _ => None, - }, - Ordering::Greater => match self.x.cmp(&self.z) { - Ordering::Less => Some(Z), - Ordering::Equal => None, - Ordering::Greater => Some(X), - }, - } - } +impl_vector_operators!(Vector3i, i32, (x, y, z)); - /// Axis of the vector's highest value. [`None`] if at least two components are equal. - pub fn min_axis(self) -> Option { - use Vector3Axis::*; - - match self.x.cmp(&self.y) { - Ordering::Less => match self.x.cmp(&self.z) { - Ordering::Less => Some(X), - Ordering::Equal => None, - Ordering::Greater => Some(Z), - }, - Ordering::Equal => match self.x.cmp(&self.z) { - Ordering::Greater => Some(Z), - _ => None, - }, - Ordering::Greater => match self.y.cmp(&self.z) { - Ordering::Less => Some(Y), - Ordering::Equal => None, - Ordering::Greater => Some(Z), - }, - } - } +impl_vector_consts!(Vector3i, i32); +impl_integer_vector_consts!(Vector3i); +impl_vector3x_consts!(Vector3i, i32); - /// Constructs a new `Vector3i` with all components set to `v`. - pub const fn splat(v: i32) -> Self { - Self::new(v, v, v) - } +impl_vector_fns!(Vector3i, glam::IVec3, i32, (x, y, z)); +impl_vector3x_fns!(Vector3i, i32); +impl Vector3i { /// Constructs a new `Vector3i` from a [`Vector3`]. The floating point coordinates will be truncated. + #[inline] pub const fn from_vector3(v: Vector3) -> Self { Self { x: v.x as i32, @@ -125,23 +57,16 @@ impl Vector3i { } } - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: glam::IVec3) -> Self { - Self::new(v.x, v.y, v.z) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> glam::IVec3 { - glam::IVec3::new(self.x, self.y, self.z) - } - /// Converts `self` to the corresponding [`real`] `glam` type. - fn to_glam_real(self) -> RVec3 { + #[inline] + pub fn to_glam_real(self) -> RVec3 { RVec3::new(self.x as real, self.y as real, self.z as real) } - pub fn coords(&self) -> (i32, i32, i32) { - (self.x, self.y, self.z) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector3i { + inner::InnerVector3i::from_outer(self) } } @@ -152,12 +77,6 @@ impl fmt::Display for Vector3i { } } -impl_common_vector_fns!(Vector3i, i32); -impl_integer_vector_glam_fns!(Vector3i, real); -impl_integer_vector_component_fns!(Vector3i, real, (x, y, z)); -impl_vector_operators!(Vector3i, i32, (x, y, z)); -impl_swizzle_trait_for_vector3x!(Vector3i, i32); - // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector3i { diff --git a/godot-core/src/builtin/vectors/vector_macros.rs b/godot-core/src/builtin/vectors/vector_macros.rs index a761c78ca..18670470b 100644 --- a/godot-core/src/builtin/vectors/vector_macros.rs +++ b/godot-core/src/builtin/vectors/vector_macros.rs @@ -723,7 +723,7 @@ macro_rules! impl_vector3x_fns { _ => None, }, Some(Ordering::Equal) => match self.x.partial_cmp(&self.z) { - Some(Ordering::Equal) => Some(Vector3Axis::Z), + Some(Ordering::Greater) => Some(Vector3Axis::Z), _ => None, }, Some(Ordering::Greater) => match self.y.partial_cmp(&self.z) { From 2a8d97b2a37829534c1722930b55cd60d9b7a820 Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Wed, 29 May 2024 21:08:11 +0200 Subject: [PATCH 4/8] Refactor 4D vectors with new macros --- godot-core/src/builtin/vectors/vector4.rs | 49 +++--------- godot-core/src/builtin/vectors/vector4i.rs | 92 ++++------------------ 2 files changed, 28 insertions(+), 113 deletions(-) diff --git a/godot-core/src/builtin/vectors/vector4.rs b/godot-core/src/builtin/vectors/vector4.rs index 2af943420..e0bb21b4a 100644 --- a/godot-core/src/builtin/vectors/vector4.rs +++ b/godot-core/src/builtin/vectors/vector4.rs @@ -9,7 +9,7 @@ use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use crate::builtin::math::{FloatExt, GlamConv, GlamType}; -use crate::builtin::{real, RVec4, Vector4i}; +use crate::builtin::{inner, real, RVec4, Vector4Axis, Vector4i}; use std::fmt; @@ -40,23 +40,17 @@ pub struct Vector4 { } impl_vector_operators!(Vector4, real, (x, y, z, w)); -impl_common_vector_fns!(Vector4, real); -impl_float_vector_glam_fns!(Vector4, real); -impl_float_vector_component_fns!(Vector4, real, (x, y, z, w)); -impl_swizzle_trait_for_vector4x!(Vector4, real); -impl Vector4 { - /// Returns a `Vector4` with the given components. - pub const fn new(x: real, y: real, z: real, w: real) -> Self { - Self { x, y, z, w } - } +impl_vector_consts!(Vector4, real); +impl_float_vector_consts!(Vector4); - /// Returns a new `Vector4` with all components set to `v`. - pub const fn splat(v: real) -> Self { - Self::new(v, v, v, v) - } +impl_vector_fns!(Vector4, RVec4, real, (x, y, z, w)); +impl_float_vector_fns!(Vector4, (x, y, z, w)); +impl_vector4x_fns!(Vector4, real); +impl_vector3_vector4_fns!(Vector4, (x, y, z, w)); - /// Constructs a new `Vector3` from a [`Vector3i`][crate::builtin::Vector3i]. +impl Vector4 { + /// Constructs a new `Vector4` from a [`Vector4i`][crate::builtin::Vector4i]. pub const fn from_vector4i(v: Vector4i) -> Self { Self { x: v.x as real, @@ -66,27 +60,10 @@ impl Vector4 { } } - /// Zero vector, a vector with all components set to `0.0`. - pub const ZERO: Self = Self::splat(0.0); - - /// One vector, a vector with all components set to `1.0`. - pub const ONE: Self = Self::splat(1.0); - - /// Infinity vector, a vector with all components set to `real::INFINITY`. - pub const INF: Self = Self::splat(real::INFINITY); - - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: RVec4) -> Self { - Self::new(v.x, v.y, v.z, v.w) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> RVec4 { - RVec4::new(self.x, self.y, self.z, self.w) - } - - pub fn coords(&self) -> (real, real, real, real) { - (self.x, self.y, self.z, self.w) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector4 { + inner::InnerVector4::from_outer(self) } } diff --git a/godot-core/src/builtin/vectors/vector4i.rs b/godot-core/src/builtin/vectors/vector4i.rs index e5f4533ad..0390fa9a9 100644 --- a/godot-core/src/builtin/vectors/vector4i.rs +++ b/godot-core/src/builtin/vectors/vector4i.rs @@ -8,8 +8,8 @@ use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::math::{FloatExt, GlamConv, GlamType}; -use crate::builtin::{real, RVec4, Vector4, Vector4Axis}; +use crate::builtin::math::{GlamConv, GlamType}; +use crate::builtin::{inner, real, RVec4, Vector4, Vector4Axis}; use std::fmt; @@ -39,67 +39,18 @@ pub struct Vector4i { } impl_vector_operators!(Vector4i, i32, (x, y, z, w)); -impl_integer_vector_glam_fns!(Vector4i, real); -impl_integer_vector_component_fns!(Vector4i, real, (x, y, z, w)); -impl_common_vector_fns!(Vector4i, i32); -impl_swizzle_trait_for_vector4x!(Vector4i, i32); -impl Vector4i { - /// Returns a `Vector4i` with the given components. - pub const fn new(x: i32, y: i32, z: i32, w: i32) -> Self { - Self { x, y, z, w } - } - - /// Axis of the vector's highest value. [`None`] if at least two components are equal. - pub fn max_axis(self) -> Option { - use Vector4Axis::*; - - let mut max_axis = X; - let mut previous = None; - let mut max_value = self.x; - - let components = [(Y, self.y), (Z, self.z), (W, self.w)]; - - for (axis, value) in components { - if value >= max_value { - max_axis = axis; - previous = Some(max_value); - max_value = value; - } - } - - (Some(max_value) != previous).then_some(max_axis) - } - - /// Axis of the vector's highest value. [`None`] if at least two components are equal. - pub fn min_axis(self) -> Option { - use Vector4Axis::*; - - let mut min_axis = X; - let mut previous = None; - let mut min_value = self.x; - - let components = [(Y, self.y), (Z, self.z), (W, self.w)]; +impl_vector_consts!(Vector4i, i32); +impl_integer_vector_consts!(Vector4i); - for (axis, value) in components { - if value <= min_value { - min_axis = axis; - previous = Some(min_value); - min_value = value; - } - } - - (Some(min_value) != previous).then_some(min_axis) - } - - /// Constructs a new `Vector4i` with all components set to `v`. - pub const fn splat(v: i32) -> Self { - Self::new(v, v, v, v) - } +impl_vector_fns!(Vector4i, glam::IVec4, i32, (x, y, z, w)); +impl_vector4x_fns!(Vector4i, i32); +impl Vector4i { /// Constructs a new `Vector4i` from a [`Vector4`]. The floating point coordinates will be /// truncated. - pub const fn from_vector3(v: Vector4) -> Self { + #[inline] + pub const fn from_vector4(v: Vector4) -> Self { Self { x: v.x as i32, y: v.y as i32, @@ -108,24 +59,9 @@ impl Vector4i { } } - /// Zero vector, a vector with all components set to `0`. - pub const ZERO: Self = Self::splat(0); - - /// One vector, a vector with all components set to `1`. - pub const ONE: Self = Self::splat(1); - - /// Converts the corresponding `glam` type to `Self`. - fn from_glam(v: glam::IVec4) -> Self { - Self::new(v.x, v.y, v.z, v.w) - } - - /// Converts `self` to the corresponding `glam` type. - fn to_glam(self) -> glam::IVec4 { - glam::IVec4::new(self.x, self.y, self.z, self.w) - } - /// Converts `self` to the corresponding [`real`] `glam` type. - fn to_glam_real(self) -> RVec4 { + #[inline] + pub fn to_glam_real(self) -> RVec4 { RVec4::new( self.x as real, self.y as real, @@ -134,8 +70,10 @@ impl Vector4i { ) } - pub fn coords(&self) -> (i32, i32, i32, i32) { - (self.x, self.y, self.z, self.w) + #[doc(hidden)] + #[inline] + pub fn as_inner(&self) -> inner::InnerVector4i { + inner::InnerVector4i::from_outer(self) } } From 22f68803ee4b0c06d7e6e8eda5b081989db3106c Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Tue, 4 Jun 2024 01:37:35 +0200 Subject: [PATCH 5/8] Fix some vector refactor errors --- godot-core/src/builtin/aabb.rs | 28 ++++----- godot-core/src/builtin/math/glam_helpers.rs | 3 - godot-core/src/builtin/vectors/vector2.rs | 7 +-- godot-core/src/builtin/vectors/vector3.rs | 6 +- .../src/builtin/vectors/vector_macros.rs | 61 +++++++++++-------- 5 files changed, 57 insertions(+), 48 deletions(-) diff --git a/godot-core/src/builtin/aabb.rs b/godot-core/src/builtin/aabb.rs index 5d37d3871..1c506e35b 100644 --- a/godot-core/src/builtin/aabb.rs +++ b/godot-core/src/builtin/aabb.rs @@ -189,18 +189,18 @@ impl Aabb { /// Returns the normalized longest axis of the AABB. #[inline] - pub fn longest_axis(&self) -> Vector3 { - match self.longest_axis_index() { + pub fn longest_axis(&self) -> Option { + self.longest_axis_index().map(|axis| match axis { Vector3Axis::X => Vector3::RIGHT, Vector3Axis::Y => Vector3::UP, Vector3Axis::Z => Vector3::BACK, - } + }) } /// Returns the index of the longest axis of the AABB (according to Vector3's AXIS_* constants). #[inline] - pub fn longest_axis_index(&self) -> Vector3Axis { - self.size.max_axis().unwrap_or(Vector3Axis::X) + pub fn longest_axis_index(&self) -> Option { + self.size.max_axis() } /// Returns the scalar length of the longest axis of the AABB. @@ -211,18 +211,18 @@ impl Aabb { /// Returns the normalized shortest axis of the AABB. #[inline] - pub fn shortest_axis(&self) -> Vector3 { - match self.shortest_axis_index() { + pub fn shortest_axis(&self) -> Option { + self.shortest_axis_index().map(|axis| match axis { Vector3Axis::X => Vector3::RIGHT, Vector3Axis::Y => Vector3::UP, Vector3Axis::Z => Vector3::BACK, - } + }) } /// Returns the index of the shortest axis of the AABB (according to Vector3::AXIS* enum). #[inline] - pub fn shortest_axis_index(&self) -> Vector3Axis { - self.size.min_axis().unwrap_or(Vector3Axis::Z) + pub fn shortest_axis_index(&self) -> Option { + self.size.min_axis() } /// Returns the scalar length of the shortest axis of the AABB. @@ -436,12 +436,12 @@ mod test { size: Vector3::new(4.0, 6.0, 8.0), }; - assert_eq!(aabb.shortest_axis(), Vector3::RIGHT); - assert_eq!(aabb.longest_axis(), Vector3::BACK); + assert_eq!(aabb.shortest_axis(), Some(Vector3::RIGHT)); + assert_eq!(aabb.longest_axis(), Some(Vector3::BACK)); assert_eq!(aabb.shortest_axis_size(), 4.0); assert_eq!(aabb.longest_axis_size(), 8.0); - assert_eq!(aabb.shortest_axis_index(), Vector3Axis::X); - assert_eq!(aabb.longest_axis_index(), Vector3Axis::Z); + assert_eq!(aabb.shortest_axis_index(), Some(Vector3Axis::X)); + assert_eq!(aabb.longest_axis_index(), Some(Vector3Axis::Z)); } #[test] diff --git a/godot-core/src/builtin/math/glam_helpers.rs b/godot-core/src/builtin/math/glam_helpers.rs index 607be41a4..95320e1fb 100644 --- a/godot-core/src/builtin/math/glam_helpers.rs +++ b/godot-core/src/builtin/math/glam_helpers.rs @@ -21,12 +21,10 @@ use crate::builtin::real; pub(crate) trait GlamConv { type Glam: GlamType; - #[inline] fn to_glam(&self) -> Self::Glam { Self::Glam::from_front(self) } - #[inline] fn glam(&self, unary_fn: F) -> R::Mapped where R: GlamType, @@ -38,7 +36,6 @@ pub(crate) trait GlamConv { result.to_front() } - #[inline] fn glam2(&self, rhs: &P, binary_fn: F) -> R::Mapped where P: GlamConv, diff --git a/godot-core/src/builtin/vectors/vector2.rs b/godot-core/src/builtin/vectors/vector2.rs index da44966b1..d1beddae3 100644 --- a/godot-core/src/builtin/vectors/vector2.rs +++ b/godot-core/src/builtin/vectors/vector2.rs @@ -102,11 +102,10 @@ impl Vector2 { /// /// ```no_run /// use godot::prelude::*; - /// use std::f32::consts::PI; /// - /// let a = Vector2::from_angle(0.0); // (1.0, 0.0) - /// let b = Vector2::new(1.0, 0.0).angle(); // 0.0 - /// let c = Vector2::from_angle(PI / 2.0); // (0.0, 1.0) + /// let a = Vector2::from_angle(0.0); // (1.0, 0.0) + /// let b = Vector2::new(1.0, 0.0).angle(); // 0.0 + /// let c = Vector2::from_angle(real_consts::PI / 2.0); // (0.0, 1.0) /// ``` #[inline] pub fn from_angle(angle: real) -> Self { diff --git a/godot-core/src/builtin/vectors/vector3.rs b/godot-core/src/builtin/vectors/vector3.rs index e613c2624..df39d4c90 100644 --- a/godot-core/src/builtin/vectors/vector3.rs +++ b/godot-core/src/builtin/vectors/vector3.rs @@ -59,7 +59,7 @@ impl Vector3 { pub const MODEL_RIGHT: Self = Self::new(-1.0, 0.0, 0.0); /// Unit vector pointing towards the top side (up) of imported 3D assets. - pub const MODEL_UP: Self = Self::new(0.0, 1.0, 0.0); + pub const MODEL_TOP: Self = Self::new(0.0, 1.0, 0.0); /// Unit vector pointing towards the bottom side (down) of imported 3D assets. pub const MODEL_BOTTOM: Self = Self::new(0.0, -1.0, 0.0); @@ -120,7 +120,7 @@ impl Vector3 { /// If vector is not normalized. #[inline] pub fn octahedron_encode(self) -> Vector2 { - assert!(self.is_normalized()); + assert!(self.is_normalized(), "vector is not normalized!"); let mut n = self; n /= n.x.abs() + n.y.abs() + n.z.abs(); @@ -156,7 +156,7 @@ impl Vector3 { /// If `axis` is not normalized. #[inline] pub fn rotated(self, axis: Self, angle: real) -> Self { - assert!(axis.is_normalized()); + assert!(axis.is_normalized(), "axis is not normalized!"); Basis::from_axis_angle(axis, angle) * self } diff --git a/godot-core/src/builtin/vectors/vector_macros.rs b/godot-core/src/builtin/vectors/vector_macros.rs index 18670470b..5148939a7 100644 --- a/godot-core/src/builtin/vectors/vector_macros.rs +++ b/godot-core/src/builtin/vectors/vector_macros.rs @@ -289,10 +289,10 @@ macro_rules! impl_integer_vector_consts { $Vector:ty ) => { impl $Vector { - /// Min vector, a vector with all components equal to [`i32::MIN`]. Can be used as a negative integer equivalent of [`Vector2::INF`]. + /// Min vector, a vector with all components equal to [`i32::MIN`]. Can be used as a negative integer equivalent of `real::INF`. pub const MIN: Self = Self::splat(i32::MIN); - /// Max vector, a vector with all components equal to [`i32::MAX`]. Can be used as an integer equivalent of [`Vector2::INF`]. + /// Max vector, a vector with all components equal to [`i32::MAX`]. Can be used as an integer equivalent of `real::INF`. pub const MAX: Self = Self::splat(i32::MAX); } }; @@ -402,8 +402,8 @@ macro_rules! impl_vector_fns { /// Returns a new vector with all components clamped between the components of `min` and `max`. /// - /// Panics - /// Panics if `min` > `max`, `min` is NaN, or `max` is NaN. + /// # Panics + /// If `min` > `max`, `min` is NaN, or `max` is NaN. #[inline] pub fn clamp(self, min: Self, max: Self) -> Self { Self::from_glam(self.to_glam().clamp(min.to_glam(), max.to_glam())) @@ -462,7 +462,9 @@ macro_rules! impl_float_vector_fns { } /// Performs a cubic interpolation between this vector and `b` using `pre_a` and `post_b` as handles, - /// and returns the result at position `weight`. `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. + /// and returns the result at position `weight`. + /// + /// `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. #[inline] pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: real) -> Self { Self::new( @@ -473,7 +475,9 @@ macro_rules! impl_float_vector_fns { } /// Performs a cubic interpolation between this vector and `b` using `pre_a` and `post_b` as handles, - /// and returns the result at position `weight`. `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. + /// and returns the result at position `weight`. + /// + /// `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation. /// It can perform smoother interpolation than [`Self::cubic_interpolate`] by the time values. #[inline] #[allow(clippy::too_many_arguments)] @@ -496,7 +500,9 @@ macro_rules! impl_float_vector_fns { ) } - /// Returns the normalized vector pointing from this vector to `to`. This is equivalent to using `(b - a).normalized()`. + /// Returns the normalized vector pointing from this vector to `to`. + /// + /// This is equivalent to using `(b - a).normalized()`. #[inline] pub fn direction_to(self, to: Self) -> Self { (to - self).normalized() @@ -541,13 +547,15 @@ macro_rules! impl_float_vector_fns { } /// Returns `true` if this vector's values are approximately zero. - /// This method is faster than using [`Self::is_equal_approx`] with one value as a zero vector. + /// + /// This method is faster than using `approx_eq()` with one value as a zero vector. #[inline] pub fn is_zero_approx(self) -> bool { $( self.$comp.is_zero_approx() )&&* } /// Returns the result of the linear interpolation between this vector and `to` by amount `weight`. + /// /// `weight` is on the range of `0.0` to `1.0`, representing the amount of interpolation. #[inline] pub fn lerp(self, other: Self, weight: real) -> Self { @@ -559,19 +567,14 @@ macro_rules! impl_float_vector_fns { /// Returns the vector scaled to unit length. Equivalent to `self / self.length()`. See /// also `is_normalized()`. /// - /// If the vector is zero, the result is also zero. + /// # Panics + /// If called on a zero vector. #[inline] pub fn normalized(self) -> Self { - // Copy Godot's implementation since it's faster than using glam's normalize_or_zero(). - if self == Self::ZERO { - return self; - } - - let l = self.length(); + assert_ne!(self, Self::ZERO, "normalized() called on zero vector"); - Self::new( - $( self.$comp / l ),* - ) + // Copy Godot's implementation since it's faster than using glam's normalize_or_zero(). + self / self.length() } /// Returns a vector composed of the [`FloatExt::fposmod`] of this vector's components and `pmod`. @@ -612,7 +615,7 @@ macro_rules! impl_float_vector_fns { impl $crate::builtin::math::ApproxEq for $Vector { /// Returns `true` if this vector and `to` are approximately equal. #[inline] - #[doc(alias = "is_approx_eq")] + #[doc(alias = "is_equal_approx")] fn approx_eq(&self, other: &Self) -> bool { $( self.$comp.approx_eq(&other.$comp) )&&* } @@ -857,9 +860,13 @@ macro_rules! impl_vector2_vector3_fns { } /// Returns a new vector "bounced off" from a plane defined by the given normal. + /// + /// # Panics + /// If `n` is not normalized. #[inline] - pub fn bounce(self, normal: Self) -> Self { - -self.reflect(normal) + pub fn bounce(self, n: Self) -> Self { + assert!(n.is_normalized(), "n is not normalized!"); + -self.reflect(n) } /// Returns the vector with a maximum length by limiting its length to `length`. @@ -884,14 +891,22 @@ macro_rules! impl_vector2_vector3_fns { } /// Returns the result of reflecting the vector defined by the given direction vector `n`. + /// + /// # Panics + /// If `n` is not normalized. #[inline] pub fn reflect(self, n: Self) -> Self { + assert!(n.is_normalized(), "n is not normalized!"); 2.0 * n * self.dot(n) - self } /// Returns a new vector slid along a plane defined by the given normal. + /// + /// # Panics + /// If `n` is not normalized. #[inline] pub fn slide(self, n: Self) -> Self { + assert!(n.is_normalized(), "n is not normalized!"); self - n * self.dot(n) } } @@ -911,9 +926,7 @@ macro_rules! impl_vector3_vector4_fns { #[inline] #[doc(alias = "inverse")] pub fn recip(self) -> Self { - Self::new( - $( 1.0 / self.$comp ),* - ) + Self::from_glam(self.to_glam().recip()) } } }; From 064511fdb1ccdb6379896f2052c5e59cea8a2173 Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Tue, 4 Jun 2024 02:25:27 +0200 Subject: [PATCH 6/8] Add vector tests --- .../src/builtin_tests/geometry/vector_test.rs | 85 ---- .../geometry/vector_test/vector2_test.rs | 448 +++++++++++++++++ .../geometry/vector_test/vector2i_test.rs | 112 +++++ .../geometry/vector_test/vector3_test.rs | 473 ++++++++++++++++++ .../geometry/vector_test/vector3i_test.rs | 109 ++++ .../geometry/vector_test/vector4_test.rs | 287 +++++++++++ .../geometry/vector_test/vector4i_test.rs | 113 +++++ itest/rust/src/builtin_tests/mod.rs | 9 +- 8 files changed, 1550 insertions(+), 86 deletions(-) delete mode 100644 itest/rust/src/builtin_tests/geometry/vector_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector3_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector4_test.rs create mode 100644 itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs diff --git a/itest/rust/src/builtin_tests/geometry/vector_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test.rs deleted file mode 100644 index cdb4c1915..000000000 --- a/itest/rust/src/builtin_tests/geometry/vector_test.rs +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) godot-rust; Bromeon and contributors. - * 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 crate::framework::itest; - -use godot::builtin::inner::{InnerVector2, InnerVector3}; -use godot::builtin::{real, real_consts::PI, Vector2, Vector3}; -use godot::private::class_macros::assert_eq_approx; - -#[itest] -fn vector2_equiv() { - for c in 0..10 { - let angle = 0.2 * c as real * PI; - - let outer = Vector2::new(angle.cos(), angle.sin()); - let inner = InnerVector2::from_outer(&outer); - - let x_axis = Vector2::new(1.0, 0.0); - let y_axis = Vector2::new(0.0, 1.0); - - assert_eq_approx!( - outer.reflect(x_axis), - inner.reflect(x_axis), - "reflect (x-axis)\n", - ); - - assert_eq_approx!( - outer.reflect(y_axis), - inner.reflect(y_axis), - "reflect (y-axis)\n", - ); - } -} - -#[itest] -fn vector3_equiv() { - for c in 0..10 { - let angle = 0.2 * c as real * PI; - let z = 0.2 * c as real - 1.0; - - let outer = Vector3::new(angle.cos(), angle.sin(), z); - let inner = InnerVector3::from_outer(&outer); - - let x_axis = Vector3::new(1.0, 0.0, 0.0); - let y_axis = Vector3::new(0.0, 1.0, 0.0); - let z_axis = Vector3::new(0.0, 0.0, 1.0); - - assert_eq_approx!( - outer.reflect(x_axis), - inner.reflect(x_axis), - "reflect (x-axis)\n", - ); - - assert_eq_approx!( - outer.reflect(y_axis), - inner.reflect(y_axis), - "reflect (y-axis)\n", - ); - - assert_eq_approx!( - outer.reflect(z_axis), - inner.reflect(z_axis), - "reflect (z-axis)\n", - ); - } -} - -#[itest] -fn vector3_signed_angle_to() { - let outer = Vector3::new(1.0, 1.0, 0.0); - let inner = InnerVector3::from_outer(&outer); - - let to = Vector3::new(1.0, 1.0, 1.0); - let axis = Vector3::UP; - - assert_eq_approx!( - outer.signed_angle_to(to, axis), - inner.signed_angle_to(to, axis) as real, - "signed_angle_to\n", - ); -} diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs new file mode 100644 index 000000000..0fdefb711 --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs @@ -0,0 +1,448 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{ + inner::InnerVector2, + math::{assert_eq_approx, ApproxEq}, + real, + real_consts::PI, + Vector2, Vector2Axis, +}; + +#[itest] +fn abs() { + let a = Vector2::new(-1.0, 2.0); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn angle() { + let a = Vector2::new(1.2, -3.4); + + assert_eq!(a.angle(), a.as_inner().angle() as real); +} + +#[itest] +fn angle_to() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq_approx!(a.angle_to(b), a.as_inner().angle_to(b) as real); +} + +#[itest] +fn angle_to_point() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.angle_to_point(b), a.as_inner().angle_to_point(b) as real); +} + +#[itest] +fn aspect() { + let a = Vector2::new(4.0, 2.0); + + assert_eq!(a.aspect(), a.as_inner().aspect() as real); +} + +#[itest] +fn bezier_derivative() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + let c = Vector2::new(9.0, -10.0); + let d = Vector2::new(-11.0, 12.0); + + let e = 10.0; + + assert_eq_approx!( + a.bezier_derivative(b, c, d, e as real), + a.as_inner().bezier_derivative(b, c, d, e) + ); +} + +#[itest] +fn bezier_interpolate() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + let c = Vector2::new(9.0, -10.0); + let d = Vector2::new(-11.0, 12.0); + + let e = 10.0; + + assert_eq!( + a.bezier_interpolate(b, c, d, e as real), + a.as_inner().bezier_interpolate(b, c, d, e) + ); +} + +#[itest] +fn bounce() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8).normalized(); + + assert_eq!(a.bounce(b), a.as_inner().bounce(b)); +} + +#[itest] +fn ceil() { + let a = Vector2::new(1.2, -3.4); + + assert_eq!(a.ceil(), a.as_inner().ceil()); +} + +#[itest] +fn clamp() { + let a = Vector2::new(12.3, 45.6); + + let min = Vector2::new(15.0, 15.0); + let max = Vector2::new(30.0, 30.0); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn cross() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.cross(b), a.as_inner().cross(b) as real); +} + +#[itest] +fn cubic_interpolate() { + let a = Vector2::new(1.0, 2.0); + let b = Vector2::new(3.0, 4.0); + let c = Vector2::new(0.0, 1.0); + let d = Vector2::new(5.0, 6.0); + + let e = 0.5; + + assert_eq!( + a.cubic_interpolate(b, c, d, e as real), + a.as_inner().cubic_interpolate(b, c, d, e) + ); +} + +#[itest] +fn cubic_interpolate_in_time() { + let a = Vector2::new(1.0, 2.0); + let b = Vector2::new(3.0, 4.0); + let c = Vector2::new(0.0, 1.0); + let d = Vector2::new(5.0, 6.0); + + let e = 0.5; + let f = 0.3; + let g = 0.2; + let h = 0.4; + + assert_eq!( + a.cubic_interpolate_in_time(b, c, d, e as real, f as real, g as real, h as real), + a.as_inner().cubic_interpolate_in_time(b, c, d, e, f, g, h) + ); +} + +#[itest] +fn direction_to() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.direction_to(b), a.as_inner().direction_to(b)); +} + +#[itest] +fn distance_squared_to() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!( + a.distance_squared_to(b), + a.as_inner().distance_squared_to(b) as real + ); +} + +#[itest] +fn distance_to() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.distance_to(b), a.as_inner().distance_to(b) as real); +} + +#[itest] +fn dot() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.dot(b), a.as_inner().dot(b) as real); +} + +#[itest] +fn floor() { + let a = Vector2::new(1.2, -3.4); + + assert_eq!(a.floor(), a.as_inner().floor()); +} + +#[itest] +fn from_angle() { + let a = 1.2; + + assert_eq!(Vector2::from_angle(a as real), InnerVector2::from_angle(a)); +} + +#[itest] +fn is_equal_approx() { + let a = Vector2::new(1.2, -3.4); + let b = a * 1.000001; + + let c = Vector2::new(-5.6, 7.8); + + assert_eq!(a.approx_eq(&b), a.as_inner().is_equal_approx(b)); + assert_eq!(a.approx_eq(&c), a.as_inner().is_equal_approx(c)); +} + +#[itest] +fn is_finite() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(real::INFINITY, real::INFINITY); + + assert_eq!(a.is_finite(), a.as_inner().is_finite()); + assert_eq!(b.is_finite(), b.as_inner().is_finite()); +} + +#[itest] +fn is_normalized() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(1.0, 0.0); + + assert_eq!(a.is_normalized(), a.as_inner().is_normalized()); + assert_eq!(b.is_normalized(), b.as_inner().is_normalized()); +} + +#[itest] +fn is_zero_approx() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(0.0, 0.0); + + assert_eq!(a.is_zero_approx(), a.as_inner().is_zero_approx()); + assert_eq!(b.is_zero_approx(), b.as_inner().is_zero_approx()); +} + +#[itest] +fn length() { + let a = Vector2::new(1.2, -3.4); + + assert_eq_approx!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector2::new(1.2, -3.4); + + assert_eq_approx!(a.length_squared(), a.as_inner().length_squared() as real); +} + +#[itest] +fn lerp() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + let c = 0.5; + + assert_eq!(a.lerp(b, c as real), a.as_inner().lerp(b, c)); +} + +#[itest] +fn limit_length() { + let a = Vector2::new(1.2, -3.4); + let b = 5.0; + + assert_eq!( + a.limit_length(Some(b as real)), + a.as_inner().limit_length(b) + ); +} + +#[itest] +fn max_axis() { + let a = Vector2::new(10.0, 5.0); + let b = Vector2::new(10.0, 10.0); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector2Axis::X), + 1 => Some(Vector2Axis::Y), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector2Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector2Axis::X, + 1 => Vector2Axis::Y, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector2::new(10.0, 5.0); + let b = Vector2::new(10.0, 10.0); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector2Axis::X), + 1 => Some(Vector2Axis::Y), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector2Axis::Y), + match b.as_inner().min_axis_index() { + 0 => Vector2Axis::X, + 1 => Vector2Axis::Y, + _ => unreachable!(), + } + ); +} + +#[itest] +fn move_toward() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + let c = 5.0; + + assert_eq!(a.move_toward(b, c as real), a.as_inner().move_toward(b, c)); +} + +#[itest] +fn normalized() { + let a = Vector2::new(1.2, -3.4); + + assert_eq_approx!(a.normalized(), a.as_inner().normalized()); +} + +#[itest] +fn orthogonal() { + let a = Vector2::new(1.2, -3.4); + + assert_eq!(a.orthogonal(), a.as_inner().orthogonal()); +} + +#[itest] +fn posmod() { + let a = Vector2::new(1.2, -3.4); + let b = 5.6; + + assert_eq!(a.posmod(b as real), a.as_inner().posmod(b)); +} + +#[itest] +fn posmodv() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.posmodv(b), a.as_inner().posmodv(b)); +} + +#[itest] +fn project() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + assert_eq!(a.project(b), a.as_inner().project(b)); +} + +#[itest] +fn reflect() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8).normalized(); + + assert_eq!(a.reflect(b), a.as_inner().reflect(b)); +} + +#[itest] +fn rotated() { + let a = Vector2::new(1.2, -3.4); + let b = 1.0; + + assert_eq_approx!(a.rotated(b as real), a.as_inner().rotated(b)); +} + +#[itest] +fn round() { + let a = Vector2::new(1.2, -3.6); + + assert_eq!(a.round(), a.as_inner().round()); +} + +#[itest] +fn sign() { + let a = Vector2::new(-1.0, 2.0); + let b = Vector2::new(-0.0, 0.0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +#[itest] +fn slerp() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8); + + let c = 0.5; + + assert_eq_approx!(a.slerp(b, c as real), a.as_inner().slerp(b, c)); +} + +#[itest] +fn slide() { + let a = Vector2::new(1.2, -3.4); + let b = Vector2::new(-5.6, 7.8).normalized(); + + assert_eq!(a.slide(b), a.as_inner().slide(b)); +} + +#[itest] +fn snapped() { + let a = Vector2::new(5.0, -5.0); + let b = Vector2::new(5.6, 7.8); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +} + +#[itest] +fn equiv() { + for c in 0..10 { + let angle = 0.2 * c as real * PI; + + let outer = Vector2::new(angle.cos(), angle.sin()); + let inner = InnerVector2::from_outer(&outer); + + let x_axis = Vector2::new(1.0, 0.0); + let y_axis = Vector2::new(0.0, 1.0); + + assert_eq_approx!( + outer.reflect(x_axis), + inner.reflect(x_axis), + "reflect (x-axis)\n", + ); + + assert_eq_approx!( + outer.reflect(y_axis), + inner.reflect(y_axis), + "reflect (y-axis)\n", + ); + } +} diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs new file mode 100644 index 000000000..6010a7c56 --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs @@ -0,0 +1,112 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{real, Vector2Axis, Vector2i}; + +#[itest] +fn abs() { + let a = Vector2i::new(-1, 2); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn aspect() { + let a = Vector2i::new(4, 2); + + assert_eq!(a.aspect(), a.as_inner().aspect() as real); +} + +#[itest] +fn clamp() { + let a = Vector2i::new(12, 34); + + let min = Vector2i::new(15, 15); + let max = Vector2i::new(30, 30); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn length() { + let a = Vector2i::new(3, 4); + + assert_eq!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector2i::new(3, 4); + + assert_eq!(a.length_squared(), a.as_inner().length_squared() as i32); +} + +#[itest] +fn max_axis() { + let a = Vector2i::new(10, 5); + let b = Vector2i::new(10, 10); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector2Axis::X), + 1 => Some(Vector2Axis::Y), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector2Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector2Axis::X, + 1 => Vector2Axis::Y, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector2i::new(10, 5); + let b = Vector2i::new(10, 10); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector2Axis::X), + 1 => Some(Vector2Axis::Y), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector2Axis::Y), + match b.as_inner().min_axis_index() { + 0 => Vector2Axis::X, + 1 => Vector2Axis::Y, + _ => unreachable!(), + } + ); +} + +#[itest] +fn sign() { + let a = Vector2i::new(-1, 2); + let b = Vector2i::new(-0, 0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +// TODO: implement snapped for integer vectors +// #[itest] +// fn snapped() { +// let a = Vector2i::new(12, 34); +// let b = Vector2i::new(5, -5); + +// assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +// } diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector3_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector3_test.rs new file mode 100644 index 000000000..2493b5065 --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector3_test.rs @@ -0,0 +1,473 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{ + inner::InnerVector3, + math::{assert_eq_approx, ApproxEq}, + real, + real_consts::PI, + Vector3, Vector3Axis, +}; + +#[itest] +fn abs() { + let a = Vector3::new(-1.0, 2.0, -0.0); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn angle_to() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.1, -11.12); + + assert_eq_approx!(a.angle_to(b), a.as_inner().angle_to(b) as real); +} + +#[itest] +fn bezier_derivative() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.1, -11.12); + let c = Vector3::new(13.0, -14.0, 15.0); + let d = Vector3::new(-16.0, 17.0, -18.0); + + let e = 10.0; + + assert_eq!( + a.bezier_derivative(b, c, d, e as real), + a.as_inner().bezier_derivative(b, c, d, e) + ); +} + +#[itest] +fn bezier_interpolate() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + let c = Vector3::new(13.0, -14.0, 15.0); + let d = Vector3::new(-16.0, 17.0, -18.0); + + let e = 10.0; + + assert_eq!( + a.bezier_interpolate(b, c, d, e as real), + a.as_inner().bezier_interpolate(b, c, d, e) + ); +} + +#[itest] +fn bounce() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12).normalized(); + + assert_eq_approx!(a.bounce(b), a.as_inner().bounce(b)); +} + +#[itest] +fn ceil() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.ceil(), a.as_inner().ceil()); +} + +#[itest] +fn clamp() { + let a = Vector3::new(12.3, 45.6, 78.9); + + let min = Vector3::new(15.0, 15.0, 15.0); + let max = Vector3::new(30.0, 30.0, 30.0); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn cross() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq_approx!(a.cross(b), a.as_inner().cross(b)); +} + +#[itest] +fn cubic_interpolate() { + let a = Vector3::new(1.0, 2.0, 3.0); + let b = Vector3::new(4.0, 5.0, 6.0); + let c = Vector3::new(0.0, 1.0, 2.0); + let d = Vector3::new(5.0, 6.0, 7.0); + + let e = 0.5; + + assert_eq!( + a.cubic_interpolate(b, c, d, e as real), + a.as_inner().cubic_interpolate(b, c, d, e) + ); +} + +#[itest] +fn cubic_interpolate_in_time() { + let a = Vector3::new(1.0, 2.0, 3.0); + let b = Vector3::new(4.0, 5.0, 6.0); + let c = Vector3::new(0.0, 1.0, 2.0); + let d = Vector3::new(5.0, 6.0, 7.0); + + let e = 0.5; + let f = 0.3; + let g = 0.2; + let h = 0.4; + + assert_eq_approx!( + a.cubic_interpolate_in_time(b, c, d, e as real, f as real, g as real, h as real), + a.as_inner().cubic_interpolate_in_time(b, c, d, e, f, g, h) + ); +} + +#[itest] +fn direction_to() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.direction_to(b), a.as_inner().direction_to(b)); +} + +#[itest] +fn distance_squared_to() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!( + a.distance_squared_to(b), + a.as_inner().distance_squared_to(b) as real + ); +} + +#[itest] +fn distance_to() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.distance_to(b), a.as_inner().distance_to(b) as real); +} + +#[itest] +fn dot() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq_approx!(a.dot(b), a.as_inner().dot(b) as real); +} + +#[itest] +fn floor() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.floor(), a.as_inner().floor()); +} + +#[itest] +fn inverse() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.recip(), a.as_inner().inverse()); +} + +#[itest] +fn is_equal_approx() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = a * 1.000001; + + let c = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.approx_eq(&b), a.as_inner().is_equal_approx(b)); + assert_eq!(a.approx_eq(&c), a.as_inner().is_equal_approx(c)); +} + +#[itest] +fn is_finite() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(real::INFINITY, real::INFINITY, real::INFINITY); + + assert_eq!(a.is_finite(), a.as_inner().is_finite()); + assert_eq!(b.is_finite(), b.as_inner().is_finite()); +} + +#[itest] +fn is_normalized() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(1.0, 0.0, 1.0); + + assert_eq!(a.is_normalized(), a.as_inner().is_normalized()); + assert_eq!(b.is_normalized(), b.as_inner().is_normalized()); +} + +#[itest] +fn is_zero_approx() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(0.0, 0.0, 0.0); + + assert_eq!(a.is_zero_approx(), a.as_inner().is_zero_approx()); + assert_eq!(b.is_zero_approx(), b.as_inner().is_zero_approx()); +} + +#[itest] +fn length() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.length_squared(), a.as_inner().length_squared() as real); +} + +#[itest] +fn lerp() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + let c = 0.5; + + assert_eq!(a.lerp(b, c as real), a.as_inner().lerp(b, c)); +} + +#[itest] +fn limit_length() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = 5.0; + + assert_eq!( + a.limit_length(Some(b as real)), + a.as_inner().limit_length(b) + ); +} + +#[itest] +fn max_axis() { + let a = Vector3::new(10.0, 5.0, 0.0); + let b = Vector3::new(10.0, 10.0, 10.0); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector3Axis::X), + 1 => Some(Vector3Axis::Y), + 2 => Some(Vector3Axis::Z), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector3Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector3Axis::X, + 1 => Vector3Axis::Y, + 2 => Vector3Axis::Z, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector3::new(10.0, 5.0, 0.0); + let b = Vector3::new(10.0, 10.0, 10.0); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector3Axis::X), + 1 => Some(Vector3Axis::Y), + 2 => Some(Vector3Axis::Z), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector3Axis::Z), + match b.as_inner().min_axis_index() { + 0 => Vector3Axis::X, + 1 => Vector3Axis::Y, + 2 => Vector3Axis::Z, + _ => unreachable!(), + } + ); +} + +#[itest] +fn move_toward() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + let c = 5.0; + + assert_eq!(a.move_toward(b, c as real), a.as_inner().move_toward(b, c)); +} + +#[itest] +fn normalized() { + let a = Vector3::new(1.2, -3.4, 5.6); + + assert_eq!(a.normalized(), a.as_inner().normalized()); +} + +#[itest] +fn octahedron_encode() { + let a = Vector3::new(1.2, -3.4, 5.6).normalized(); + + assert_eq!(a.octahedron_encode(), a.as_inner().octahedron_encode()); +} + +#[itest] +fn orthogonal_decode() { + let a = Vector3::new(1.2, -3.4, 5.6) + .normalized() + .octahedron_encode(); + + assert_eq!( + Vector3::octahedron_decode(a), + InnerVector3::octahedron_decode(a) + ); +} + +#[itest] +fn outer() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.outer(b), a.as_inner().outer(b)); +} + +#[itest] +fn posmod() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = 5.6; + + assert_eq!(a.posmod(b as real), a.as_inner().posmod(b)); +} + +#[itest] +fn posmodv() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.posmodv(b), a.as_inner().posmodv(b)); +} + +#[itest] +fn project() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq_approx!(a.project(b), a.as_inner().project(b)); +} + +#[itest] +fn reflect() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12).normalized(); + + assert_eq_approx!(a.reflect(b), a.as_inner().reflect(b)); +} + +#[itest] +fn rotated() { + let a = Vector3::new(1.2, -3.4, 5.6); + + let b = Vector3::UP; + let c = 1.0; + + assert_eq!(a.rotated(b, c as real), a.as_inner().rotated(b, c)); +} + +#[itest] +fn round() { + let a = Vector3::new(1.2, -3.6, 7.8); + + assert_eq!(a.round(), a.as_inner().round()); +} + +#[itest] +fn sign() { + let a = Vector3::new(-1.0, 2.0, -3.0); + let b = Vector3::new(-0.0, 0.0, -0.0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +#[itest] +fn signed_angle_to() { + let a = Vector3::new(1.0, 1.0, 0.0); + let b = Vector3::new(1.0, 1.0, 1.0); + let c = Vector3::UP; + + assert_eq_approx!( + a.signed_angle_to(b, c), + a.as_inner().signed_angle_to(b, c) as real, + "signed_angle_to\n", + ); +} + +#[itest] +fn slerp() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + let c = 0.5; + + assert_eq_approx!(a.slerp(b, c as real), a.as_inner().slerp(b, c)); +} + +#[itest] +fn slide() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12).normalized(); + + assert_eq_approx!(a.slide(b), a.as_inner().slide(b)); +} + +#[itest] +fn snapped() { + let a = Vector3::new(1.2, -3.4, 5.6); + let b = Vector3::new(-7.8, 9.10, -11.12); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +} + +#[itest] +fn equiv() { + for c in 0..10 { + let angle = 0.2 * c as real * PI; + let z = 0.2 * c as real - 1.0; + + let outer = Vector3::new(angle.cos(), angle.sin(), z); + let inner = InnerVector3::from_outer(&outer); + + let x_axis = Vector3::new(1.0, 0.0, 0.0); + let y_axis = Vector3::new(0.0, 1.0, 0.0); + let z_axis = Vector3::new(0.0, 0.0, 1.0); + + assert_eq_approx!( + outer.reflect(x_axis), + inner.reflect(x_axis), + "reflect (x-axis)\n", + ); + + assert_eq_approx!( + outer.reflect(y_axis), + inner.reflect(y_axis), + "reflect (y-axis)\n", + ); + + assert_eq_approx!( + outer.reflect(z_axis), + inner.reflect(z_axis), + "reflect (z-axis)\n", + ); + } +} diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs new file mode 100644 index 000000000..a830b0955 --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs @@ -0,0 +1,109 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{real, Vector3Axis, Vector3i}; + +#[itest] +fn abs() { + let a = Vector3i::new(-1, 2, -3); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn clamp() { + let a = Vector3i::new(12, -34, 56); + + let min = Vector3i::new(15, 15, 15); + let max = Vector3i::new(30, 30, 30); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn length() { + let a = Vector3i::new(1, 2, 3); + + assert_eq!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector3i::new(1, 2, 3); + + assert_eq!(a.length_squared() as i64, a.as_inner().length_squared()); +} + +#[itest] +fn max_axis() { + let a = Vector3i::new(10, 5, 0); + let b = Vector3i::new(10, 10, 10); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector3Axis::X), + 1 => Some(Vector3Axis::Y), + 2 => Some(Vector3Axis::Z), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector3Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector3Axis::X, + 1 => Vector3Axis::Y, + 2 => Vector3Axis::Z, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector3i::new(10, 5, 0); + let b = Vector3i::new(10, 10, 10); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector3Axis::X), + 1 => Some(Vector3Axis::Y), + 2 => Some(Vector3Axis::Z), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector3Axis::Z), + match b.as_inner().min_axis_index() { + 0 => Vector3Axis::X, + 1 => Vector3Axis::Y, + 2 => Vector3Axis::Z, + _ => unreachable!(), + } + ); +} + +#[itest] +fn sign() { + let a = Vector3i::new(-1, 2, -3); + let b = Vector3i::new(-0, 0, -0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +// TODO: implement snapped for integer vectors +// #[itest] +// fn snapped() { +// let a = Vector3i::new(12, 34, 56); +// let b = Vector3i::new(5, -5, 6); + +// assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +// } diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector4_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector4_test.rs new file mode 100644 index 000000000..d9fe673b0 --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector4_test.rs @@ -0,0 +1,287 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{ + math::{assert_eq_approx, ApproxEq}, + real, Vector4, Vector4Axis, +}; + +#[itest] +fn abs() { + let a = Vector4::new(-1.0, 2.0, -0.0, 0.0); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn ceil() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq!(a.ceil(), a.as_inner().ceil()); +} + +#[itest] +fn clamp() { + let a = Vector4::new(12.3, 45.6, 78.9, 101.1); + + let min = Vector4::new(15.0, 15.0, 15.0, 15.0); + let max = Vector4::new(30.0, 30.0, 30.0, 30.0); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn cubic_interpolate() { + let a = Vector4::new(1.0, 2.0, 3.0, 4.0); + let b = Vector4::new(5.0, 6.0, 7.0, 8.0); + let c = Vector4::new(0.0, 1.0, 2.0, 3.0); + let d = Vector4::new(9.0, 10.0, 11.0, 12.0); + + let e = 0.5; + + assert_eq!( + a.cubic_interpolate(b, c, d, e as real), + a.as_inner().cubic_interpolate(b, c, d, e) + ); +} + +#[itest] +fn cubic_interpolate_in_time() { + let a = Vector4::new(1.0, 2.0, 3.0, 4.0); + let b = Vector4::new(5.0, 6.0, 7.0, 8.0); + let c = Vector4::new(0.0, 1.0, 2.0, 3.0); + let d = Vector4::new(9.0, 10.0, 11.0, 12.0); + + let e = 0.5; + let f = 0.3; + let g = 0.2; + let h = 0.4; + + assert_eq!( + a.cubic_interpolate_in_time(b, c, d, e as real, f as real, g as real, h as real), + a.as_inner().cubic_interpolate_in_time(b, c, d, e, f, g, h) + ); +} + +#[itest] +fn direction_to() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!(a.direction_to(b), a.as_inner().direction_to(b)); +} + +#[itest] +fn distance_squared_to() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!( + a.distance_squared_to(b), + a.as_inner().distance_squared_to(b) as real + ); +} + +#[itest] +fn distance_to() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!(a.distance_to(b), a.as_inner().distance_to(b) as real); +} + +#[itest] +fn dot() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq_approx!(a.dot(b), a.as_inner().dot(b) as real); +} + +#[itest] +fn floor() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq!(a.floor(), a.as_inner().floor()); +} + +#[itest] +fn inverse() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq!(a.recip(), a.as_inner().inverse()); +} + +#[itest] +fn is_equal_approx() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = a * 1.000001; + + let c = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!(a.approx_eq(&b), a.as_inner().is_equal_approx(b)); + assert_eq!(a.approx_eq(&c), a.as_inner().is_equal_approx(c)); +} + +#[itest] +fn is_finite() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new( + real::INFINITY, + real::INFINITY, + real::INFINITY, + real::INFINITY, + ); + + assert_eq!(a.is_finite(), a.as_inner().is_finite()); + assert_eq!(b.is_finite(), b.as_inner().is_finite()); +} + +#[itest] +fn is_normalized() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(1.0, 0.0, 1.0, 0.0); + + assert_eq!(a.is_normalized(), a.as_inner().is_normalized()); + assert_eq!(b.is_normalized(), b.as_inner().is_normalized()); +} + +#[itest] +fn is_zero_approx() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(0.0, 0.0, 0.0, 0.0); + + assert_eq!(a.is_zero_approx(), a.as_inner().is_zero_approx()); + assert_eq!(b.is_zero_approx(), b.as_inner().is_zero_approx()); +} + +#[itest] +fn length() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq_approx!(a.length_squared(), a.as_inner().length_squared() as real); +} + +#[itest] +fn lerp() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + let c = 0.5; + + assert_eq!(a.lerp(b, c as real), a.as_inner().lerp(b, c)); +} + +#[itest] +fn max_axis() { + let a = Vector4::new(10.0, 5.0, 0.0, -5.0); + let b = Vector4::new(10.0, 10.0, 10.0, 10.0); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector4Axis::X), + 1 => Some(Vector4Axis::Y), + 2 => Some(Vector4Axis::Z), + 3 => Some(Vector4Axis::W), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector4Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector4Axis::X, + 1 => Vector4Axis::Y, + 2 => Vector4Axis::Z, + 3 => Vector4Axis::W, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector4::new(10.0, 5.0, 0.0, -5.0); + let b = Vector4::new(10.0, 10.0, 10.0, 10.0); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector4Axis::X), + 1 => Some(Vector4Axis::Y), + 2 => Some(Vector4Axis::Z), + 3 => Some(Vector4Axis::W), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector4Axis::W), + match b.as_inner().min_axis_index() { + 0 => Vector4Axis::X, + 1 => Vector4Axis::Y, + 2 => Vector4Axis::Z, + 3 => Vector4Axis::W, + _ => unreachable!(), + } + ); +} + +#[itest] +fn normalized() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + + assert_eq!(a.normalized(), a.as_inner().normalized()); +} + +#[itest] +fn posmod() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = 5.6; + + assert_eq!(a.posmod(b as real), a.as_inner().posmod(b)); +} + +#[itest] +fn posmodv() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!(a.posmodv(b), a.as_inner().posmodv(b)); +} + +#[itest] +fn round() { + let a = Vector4::new(1.2, -3.6, 7.8, -11.12); + + assert_eq!(a.round(), a.as_inner().round()); +} + +#[itest] +fn sign() { + let a = Vector4::new(-1.0, 2.0, -3.0, 4.0); + let b = Vector4::new(-0.0, 0.0, -0.0, 0.0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +#[itest] +fn snapped() { + let a = Vector4::new(1.2, -3.4, 5.6, -7.8); + let b = Vector4::new(-9.10, 11.12, -13.14, 15.16); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +} diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs new file mode 100644 index 000000000..a8f1d3a9f --- /dev/null +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs @@ -0,0 +1,113 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::framework::itest; + +use godot::builtin::{real, Vector4Axis, Vector4i}; + +#[itest] +fn abs() { + let a = Vector4i::new(-1, 2, -3, 4); + + assert_eq!(a.abs(), a.as_inner().abs()); +} + +#[itest] +fn clamp() { + let a = Vector4i::new(12, -34, 56, -78); + + let min = Vector4i::new(15, 15, 15, 15); + let max = Vector4i::new(30, 30, 30, 30); + + assert_eq!(a.clamp(min, max), a.as_inner().clamp(min, max)); +} + +#[itest] +fn length() { + let a = Vector4i::new(1, 3, 4, 5); + + assert_eq!(a.length(), a.as_inner().length() as real); +} + +#[itest] +fn length_squared() { + let a = Vector4i::new(1, 3, 4, 5); + + assert_eq!(a.length_squared(), a.as_inner().length_squared() as i32); +} + +#[itest] +fn max_axis() { + let a = Vector4i::new(10, 5, 0, -5); + let b = Vector4i::new(10, 10, 10, 10); + + assert_eq!( + a.max_axis(), + match a.as_inner().max_axis_index() { + 0 => Some(Vector4Axis::X), + 1 => Some(Vector4Axis::Y), + 2 => Some(Vector4Axis::Z), + 3 => Some(Vector4Axis::W), + _ => None, + } + ); + assert_eq!( + b.max_axis().unwrap_or(Vector4Axis::X), + match b.as_inner().max_axis_index() { + 0 => Vector4Axis::X, + 1 => Vector4Axis::Y, + 2 => Vector4Axis::Z, + 3 => Vector4Axis::W, + _ => unreachable!(), + } + ); +} + +#[itest] +fn min_axis() { + let a = Vector4i::new(10, 5, 0, -5); + let b = Vector4i::new(10, 10, 10, 10); + + assert_eq!( + a.min_axis(), + match a.as_inner().min_axis_index() { + 0 => Some(Vector4Axis::X), + 1 => Some(Vector4Axis::Y), + 2 => Some(Vector4Axis::Z), + 3 => Some(Vector4Axis::W), + _ => None, + } + ); + assert_eq!( + b.min_axis().unwrap_or(Vector4Axis::W), + match b.as_inner().min_axis_index() { + 0 => Vector4Axis::X, + 1 => Vector4Axis::Y, + 2 => Vector4Axis::Z, + 3 => Vector4Axis::W, + _ => unreachable!(), + } + ); +} + +#[itest] +fn sign() { + let a = Vector4i::new(-1, 2, -3, 4); + let b = Vector4i::new(-0, 0, -0, 0); + + assert_eq!(a.sign(), a.as_inner().sign()); + assert_eq!(b.sign(), b.as_inner().sign()); +} + +// TODO: implement snapped for integer vectors +// #[itest] +// fn snapped() { +// let a = Vector4i::new(12, 34, 56, 78); +// let b = Vector4i::new(5, -5, 6, -6); + +// assert_eq!(a.snapped(b), a.as_inner().snapped(b)); +// } diff --git a/itest/rust/src/builtin_tests/mod.rs b/itest/rust/src/builtin_tests/mod.rs index e0c5e0fa0..85cc29ad4 100644 --- a/itest/rust/src/builtin_tests/mod.rs +++ b/itest/rust/src/builtin_tests/mod.rs @@ -14,7 +14,14 @@ mod geometry { mod rect2i_test; mod transform2d_test; mod transform3d_test; - mod vector_test; + mod vector_test { + mod vector2_test; + mod vector2i_test; + mod vector3_test; + mod vector3i_test; + mod vector4_test; + mod vector4i_test; + } } mod containers { From 90665f891bafcac387069be2163b65534b6eaff7 Mon Sep 17 00:00:00 2001 From: Joris Kleiber Date: Tue, 4 Jun 2024 17:41:30 +0200 Subject: [PATCH 7/8] Fix sign for float vectors --- godot-core/src/builtin/vectors/vector4.rs | 1 + godot-core/src/builtin/vectors/vector4i.rs | 1 + godot-core/src/builtin/vectors/vector_macros.rs | 13 ++++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/godot-core/src/builtin/vectors/vector4.rs b/godot-core/src/builtin/vectors/vector4.rs index e0bb21b4a..ce7270fd9 100644 --- a/godot-core/src/builtin/vectors/vector4.rs +++ b/godot-core/src/builtin/vectors/vector4.rs @@ -5,6 +5,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use core::cmp::Ordering; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; diff --git a/godot-core/src/builtin/vectors/vector4i.rs b/godot-core/src/builtin/vectors/vector4i.rs index 0390fa9a9..3f3db6a5e 100644 --- a/godot-core/src/builtin/vectors/vector4i.rs +++ b/godot-core/src/builtin/vectors/vector4i.rs @@ -5,6 +5,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use core::cmp::Ordering; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; diff --git a/godot-core/src/builtin/vectors/vector_macros.rs b/godot-core/src/builtin/vectors/vector_macros.rs index 5148939a7..27e74108c 100644 --- a/godot-core/src/builtin/vectors/vector_macros.rs +++ b/godot-core/src/builtin/vectors/vector_macros.rs @@ -440,7 +440,18 @@ macro_rules! impl_vector_fns { /// Returns a new vector with each component set to 1 if it's positive, -1 if it's negative, and 0 if it's zero. #[inline] pub fn sign(self) -> Self { - Self::from_glam(self.to_glam().signum()) + #[inline] + fn f(x: i32) -> i32 { + match x.cmp(&0) { + Ordering::Equal => 0, + Ordering::Greater => 1, + Ordering::Less => -1, + } + } + + Self::new( + $( f(self.$comp as i32) as $Scalar ),* + ) } } } From e9d5b92c6bac965d668e787f9efe31fffe61206b Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Wed, 5 Jun 2024 12:49:28 +0200 Subject: [PATCH 8/8] Fix vector2_test::project, hide to_glam_real() --- godot-core/src/builtin/vectors/vector2i.rs | 1 + godot-core/src/builtin/vectors/vector3i.rs | 1 + godot-core/src/builtin/vectors/vector4i.rs | 1 + .../rust/src/builtin_tests/geometry/vector_test/vector2_test.rs | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/godot-core/src/builtin/vectors/vector2i.rs b/godot-core/src/builtin/vectors/vector2i.rs index 55a60de73..9f69a36df 100644 --- a/godot-core/src/builtin/vectors/vector2i.rs +++ b/godot-core/src/builtin/vectors/vector2i.rs @@ -54,6 +54,7 @@ impl Vector2i { } /// Converts `self` to the corresponding [`real`] `glam` type. + #[doc(hidden)] #[inline] pub fn to_glam_real(self) -> RVec2 { RVec2::new(self.x as real, self.y as real) diff --git a/godot-core/src/builtin/vectors/vector3i.rs b/godot-core/src/builtin/vectors/vector3i.rs index 0f6ac5505..b5e77582a 100644 --- a/godot-core/src/builtin/vectors/vector3i.rs +++ b/godot-core/src/builtin/vectors/vector3i.rs @@ -58,6 +58,7 @@ impl Vector3i { } /// Converts `self` to the corresponding [`real`] `glam` type. + #[doc(hidden)] #[inline] pub fn to_glam_real(self) -> RVec3 { RVec3::new(self.x as real, self.y as real, self.z as real) diff --git a/godot-core/src/builtin/vectors/vector4i.rs b/godot-core/src/builtin/vectors/vector4i.rs index 3f3db6a5e..e7b8ca409 100644 --- a/godot-core/src/builtin/vectors/vector4i.rs +++ b/godot-core/src/builtin/vectors/vector4i.rs @@ -61,6 +61,7 @@ impl Vector4i { } /// Converts `self` to the corresponding [`real`] `glam` type. + #[doc(hidden)] #[inline] pub fn to_glam_real(self) -> RVec4 { RVec4::new( diff --git a/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs b/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs index 0fdefb711..585860e15 100644 --- a/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector2_test.rs @@ -361,7 +361,7 @@ fn project() { let a = Vector2::new(1.2, -3.4); let b = Vector2::new(-5.6, 7.8); - assert_eq!(a.project(b), a.as_inner().project(b)); + assert_eq_approx!(a.project(b), a.as_inner().project(b)); } #[itest]