diff --git a/godot-core/src/builtin/glam_helpers.rs b/godot-core/src/builtin/glam_helpers.rs index 466824177..bd43db123 100644 --- a/godot-core/src/builtin/glam_helpers.rs +++ b/godot-core/src/builtin/glam_helpers.rs @@ -71,4 +71,5 @@ macro_rules! impl_glam_map_self { } impl_glam_map_self!(f32); +impl_glam_map_self!(bool); impl_glam_map_self!((f32, f32, f32)); diff --git a/godot-core/src/builtin/math.rs b/godot-core/src/builtin/math.rs index 600cfc3e4..880c0cb78 100644 --- a/godot-core/src/builtin/math.rs +++ b/godot-core/src/builtin/math.rs @@ -4,6 +4,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::f32::consts::TAU; + pub const CMP_EPSILON: f32 = 0.00001; pub fn lerp(a: f32, b: f32, t: f32) -> f32 { @@ -119,3 +121,99 @@ pub fn cubic_interpolate_in_time( let b2 = lerp(a2, a3, if post_t == 0.0 { 1.0 } else { t / post_t }); lerp(b1, b2, if to_t == 0.0 { 0.5 } else { t / to_t }) } + +/// Linearly interpolates between two angles (in radians) by a `weight` value +/// between 0.0 and 1.0. +/// +/// Similar to [`lerp`], but interpolates correctly when the angles wrap around +/// [`TAU`]. +/// +/// The resulting angle is not normalized. +/// +/// Note: This function lerps through the shortest path between `from` and +/// `to`. However, when these two angles are approximately `PI + k * TAU` apart +/// for any integer `k`, it's not obvious which way they lerp due to +/// floating-point precision errors. For example, `lerp_angle(0.0, PI, weight)` +/// lerps clockwise, while `lerp_angle(0.0, PI + 3.0 * TAU, weight)` lerps +/// counter-clockwise. +/// +/// _Godot equivalent: @GlobalScope.lerp_angle()_ +pub fn lerp_angle(from: f32, to: f32, weight: f32) -> f32 { + let difference = (to - from) % TAU; + let distance = (2.0 * difference) % TAU - difference; + from + distance * weight +} + +/// Asserts that two values are approximately equal, using the provided `func` +/// for equality checking. +#[macro_export] +macro_rules! assert_eq_approx { + ($a:expr, $b:expr, $func:expr $(,)?) => { + match ($a, $b) { + (a, b) => { + assert!(($func)(a,b), "\n left: {:?},\n right: {:?}", $a, $b); + } + } + }; + ($a:expr, $b:expr, $func:expr, $($t:tt)+) => { + match ($a, $b) { + (a, b) => { + assert!(($func)(a,b), "\n left: {:?},\n right: {:?},\n{}", $a, $b, format_args!($($t)+)); + } + } + }; +} + +/// Asserts that two values are not approximately equal, using the provided +/// `func` for equality checking. +#[macro_export] +macro_rules! assert_ne_approx { + ($a:expr, $b:expr, $func:expr $(, $($t:tt)*)?) => { + #[allow(clippy::redundant_closure_call)] + { + assert_eq_approx!($a, $b, |a,b| !($func)(a,b) $(, $($t)*)?) + } + }; +} + +#[cfg(test)] +mod test { + use std::f32::consts::{FRAC_PI_2, PI}; + + use super::*; + + #[test] + fn equal_approx() { + assert_eq_approx!(1.0, 1.000001, is_equal_approx); + assert_ne_approx!(1.0, 2.0, is_equal_approx); + assert_eq_approx!(1.0, 1.000001, is_equal_approx, "Message {}", "formatted"); + assert_ne_approx!(1.0, 2.0, is_equal_approx, "Message {}", "formatted"); + } + + #[test] + #[should_panic(expected = "I am inside format")] + fn eq_approx_fail_with_message() { + assert_eq_approx!(1.0, 2.0, is_equal_approx, "I am inside {}", "format"); + } + + #[test] + fn lerp_angle_test() { + assert_eq_approx!(lerp_angle(0.0, PI, 0.5), -FRAC_PI_2, is_equal_approx); + assert_eq_approx!( + lerp_angle(0.0, PI + 3.0 * TAU, 0.5), + FRAC_PI_2, + is_equal_approx + ); + let angle = PI * 2.0 / 3.0; + assert_eq_approx!( + lerp_angle(-5.0 * TAU, angle + 3.0 * TAU, 0.5).sin(), + (angle / 2.0).sin(), + is_equal_approx + ); + assert_eq_approx!( + lerp_angle(-5.0 * TAU, angle + 3.0 * TAU, 0.5).cos(), + (angle / 2.0).cos(), + is_equal_approx + ); + } +} diff --git a/godot-core/src/builtin/vector2.rs b/godot-core/src/builtin/vector2.rs index 318fd3b05..1f9433ea5 100644 --- a/godot-core/src/builtin/vector2.rs +++ b/godot-core/src/builtin/vector2.rs @@ -6,12 +6,16 @@ use std::fmt; use std::ops::*; +use glam::Vec2; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use crate::builtin::math::*; use crate::builtin::{inner, Vector2i}; +use super::glam_helpers::GlamConv; +use super::glam_helpers::GlamType; + /// Vector used for 2D math using floating point coordinates. /// /// 2-element structure that can be used to represent positions in 2D space or any other pair of @@ -97,6 +101,10 @@ impl Vector2 { self.x / self.y } + pub fn lerp(self, other: Self, weight: f32) -> Self { + Self::new(lerp(self.x, other.x, weight), lerp(self.y, other.y, weight)) + } + pub fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: f32) -> Self { let x = bezier_derivative(self.x, control_1.x, control_2.x, end.x, t); let y = bezier_derivative(self.y, control_1.y, control_2.y, end.y, t); @@ -199,10 +207,6 @@ impl Vector2 { self.to_glam().length_squared() } - pub fn lerp(self, to: Self, weight: f32) -> Self { - Self::from_glam(self.to_glam().lerp(to.to_glam(), weight)) - } - pub fn limit_length(self, length: Option) -> Self { Self::from_glam(self.to_glam().clamp_length_max(length.unwrap_or(1.0))) } @@ -323,3 +327,19 @@ pub enum Vector2Axis { impl GodotFfi for Vector2Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } + +impl GlamType for Vec2 { + type Mapped = Vector2; + + fn to_front(&self) -> Self::Mapped { + Vector2::new(self.x, self.y) + } + + fn from_front(mapped: &Self::Mapped) -> Self { + Vec2::new(mapped.x, mapped.y) + } +} + +impl GlamConv for Vector2 { + type Glam = Vec2; +} diff --git a/godot-core/src/builtin/vector3.rs b/godot-core/src/builtin/vector3.rs index 92d156c51..5cb9a9138 100644 --- a/godot-core/src/builtin/vector3.rs +++ b/godot-core/src/builtin/vector3.rs @@ -7,12 +7,17 @@ use std::ops::*; use std::fmt; +use glam::f32::Vec3; +use glam::Vec3A; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use crate::builtin::math::*; use crate::builtin::Vector3i; +use super::glam_helpers::GlamConv; +use super::glam_helpers::GlamType; + /// Vector used for 3D math using floating point coordinates. /// /// 3-element structure that can be used to represent positions in 3D space or any other triple of @@ -336,3 +341,31 @@ pub enum Vector3Axis { impl GodotFfi for Vector3Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } + +impl GlamType for Vec3 { + type Mapped = Vector3; + + fn to_front(&self) -> Self::Mapped { + Vector3::new(self.x, self.y, self.z) + } + + fn from_front(mapped: &Self::Mapped) -> Self { + Vec3::new(mapped.x, mapped.y, mapped.z) + } +} + +impl GlamType for Vec3A { + type Mapped = Vector3; + + fn to_front(&self) -> Self::Mapped { + Vector3::new(self.x, self.y, self.z) + } + + fn from_front(mapped: &Self::Mapped) -> Self { + Vec3A::new(mapped.x, mapped.y, mapped.z) + } +} + +impl GlamConv for Vector3 { + type Glam = Vec3; +} diff --git a/godot-core/src/builtin/vector4.rs b/godot-core/src/builtin/vector4.rs index 9a8274010..6999d56fc 100644 --- a/godot-core/src/builtin/vector4.rs +++ b/godot-core/src/builtin/vector4.rs @@ -6,11 +6,14 @@ use std::fmt; +use glam::Vec4; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use crate::builtin::Vector4i; +use super::glam_helpers::{GlamConv, GlamType}; + /// Vector used for 4D math using floating point coordinates. /// /// 4-element structure that can be used to represent any quadruplet of numeric values. @@ -107,3 +110,19 @@ pub enum Vector4Axis { impl GodotFfi for Vector4Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } + +impl GlamType for Vec4 { + type Mapped = Vector4; + + fn to_front(&self) -> Self::Mapped { + Vector4::new(self.x, self.y, self.z, self.w) + } + + fn from_front(mapped: &Self::Mapped) -> Self { + Vec4::new(mapped.x, mapped.y, mapped.z, mapped.w) + } +} + +impl GlamConv for Vector4 { + type Glam = Vec4; +}