diff --git a/godot-core/src/builtin/vectors/vector2i.rs b/godot-core/src/builtin/vectors/vector2i.rs index 9f69a36df..3dfed031b 100644 --- a/godot-core/src/builtin/vectors/vector2i.rs +++ b/godot-core/src/builtin/vectors/vector2i.rs @@ -44,6 +44,8 @@ impl_vector_fns!(Vector2i, glam::IVec2, i32, (x, y)); impl_vector2x_fns!(Vector2i, i32); impl Vector2i { + impl_integer_vector_fns!(x, y); + /// Constructs a new `Vector2i` from a [`Vector2`]. The floating point coordinates will be truncated. #[inline] pub const fn from_vector2(v: Vector2) -> Self { diff --git a/godot-core/src/builtin/vectors/vector3i.rs b/godot-core/src/builtin/vectors/vector3i.rs index b5e77582a..f2eccb574 100644 --- a/godot-core/src/builtin/vectors/vector3i.rs +++ b/godot-core/src/builtin/vectors/vector3i.rs @@ -47,6 +47,8 @@ impl_vector_fns!(Vector3i, glam::IVec3, i32, (x, y, z)); impl_vector3x_fns!(Vector3i, i32); impl Vector3i { + impl_integer_vector_fns!(x, y, z); + /// Constructs a new `Vector3i` from a [`Vector3`]. The floating point coordinates will be truncated. #[inline] pub const fn from_vector3(v: Vector3) -> Self { diff --git a/godot-core/src/builtin/vectors/vector4i.rs b/godot-core/src/builtin/vectors/vector4i.rs index e7b8ca409..d891f804a 100644 --- a/godot-core/src/builtin/vectors/vector4i.rs +++ b/godot-core/src/builtin/vectors/vector4i.rs @@ -48,6 +48,8 @@ impl_vector_fns!(Vector4i, glam::IVec4, i32, (x, y, z, w)); impl_vector4x_fns!(Vector4i, i32); impl Vector4i { + impl_integer_vector_fns!(x, y, z, w); + /// Constructs a new `Vector4i` from a [`Vector4`]. The floating point coordinates will be /// truncated. #[inline] diff --git a/godot-core/src/builtin/vectors/vector_macros.rs b/godot-core/src/builtin/vectors/vector_macros.rs index 27e74108c..e714a1ad7 100644 --- a/godot-core/src/builtin/vectors/vector_macros.rs +++ b/godot-core/src/builtin/vectors/vector_macros.rs @@ -457,6 +457,51 @@ macro_rules! impl_vector_fns { } } +pub(super) fn snap_one(mut value: i32, step: i32) -> i32 { + assert!( + value != i32::MIN || step != -1, + "snapped() called on vector component i32::MIN with step component -1" + ); + + if step != 0 { + let a = value + step / 2; + + // manual implement `a.div_floor(step)` since Rust's native method is still unstable, as of 1.79.0 + let mut d = a / step; + let r = a % step; + if (r > 0 && step < 0) || (r < 0 && step > 0) { + d -= 1; + } + + value = step * d; + } + + value +} + +/// Implements functions that are present only on integer vectors. +macro_rules! impl_integer_vector_fns { + ( + // Names of the components, for example `x, y`. + $($comp:ident),* + ) => { + /// A new vector with each component snapped to the closest multiple of the corresponding + /// component in `step`. + /// + /// # Panics + /// If any component of `self` is `i32::MIN` while the same component on `step` is `-1`. + pub fn snapped(self, step: Self) -> Self { + use crate::builtin::vectors::vector_macros::snap_one; + + Self::new( + $( + snap_one(self.$comp, step.$comp) + ),* + ) + } + }; +} + /// Implements functions that are present only on floating-point vectors. macro_rules! impl_float_vector_fns { ( @@ -612,7 +657,6 @@ macro_rules! impl_float_vector_fns { /// 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( 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 index 6010a7c56..27ace1c12 100644 --- a/itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector2i_test.rs @@ -102,11 +102,13 @@ fn 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)); -// } +#[itest] +fn snapped() { + let a = Vector2i::new(12, 34); + let b = Vector2i::new(5, -5); + let c = Vector2i::new(0, 0); + let d = Vector2i::new(3, 0); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); + assert_eq!(c.snapped(d), c.as_inner().snapped(d)); +} 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 index a830b0955..3219f17ad 100644 --- a/itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector3i_test.rs @@ -99,11 +99,13 @@ fn 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)); -// } +#[itest] +fn snapped() { + let a = Vector3i::new(12, 34, -56); + let b = Vector3i::new(5, -5, 6); + let c = Vector3i::new(0, 3, 0); + let d = Vector3i::new(3, 0, 0); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); + assert_eq!(c.snapped(d), c.as_inner().snapped(d)); +} 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 index a8f1d3a9f..3ab592f24 100644 --- a/itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs +++ b/itest/rust/src/builtin_tests/geometry/vector_test/vector4i_test.rs @@ -103,11 +103,13 @@ fn 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)); -// } +#[itest] +fn snapped() { + let a = Vector4i::new(12, 34, 56, -78); + let b = Vector4i::new(5, -5, 6, 6); + let c = Vector4i::new(0, 3, 0, 0); + let d = Vector4i::new(3, 0, -3, 0); + + assert_eq!(a.snapped(b), a.as_inner().snapped(b)); + assert_eq!(c.snapped(d), c.as_inner().snapped(d)); +}