diff --git a/godot-core/src/builtin/glam_helpers.rs b/godot-core/src/builtin/glam_helpers.rs new file mode 100644 index 000000000..466824177 --- /dev/null +++ b/godot-core/src/builtin/glam_helpers.rs @@ -0,0 +1,74 @@ +/* + * 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/. + */ + +// TODO this is experimental -- do not refactor existing types to this yet +// Need to see if ergonomics are worth the generic complexity. +// +// Nice: +// self.glam2(&with, |a, b| a.dot(b)) +// self.glam2(&with, glam::f32::Quat::dot) +// +// Alternative with only conversions: +// self.glam().dot(b.glam()) +// GlamType::dot(self.glam(), b.glam()) + +pub(crate) trait GlamConv { + type Glam: GlamType; + + fn to_glam(&self) -> Self::Glam { + Self::Glam::from_front(self) + } + + fn glam(&self, unary_fn: F) -> R::Mapped + where + R: GlamType, + F: FnOnce(Self::Glam) -> R, + { + let arg = Self::Glam::from_front(self); + let result = unary_fn(arg); + + result.to_front() + } + + fn glam2(&self, rhs: &P, binary_fn: F) -> R::Mapped + where + P: GlamConv, + R: GlamType, + F: FnOnce(Self::Glam, P::Glam) -> R, + { + let arg0 = Self::Glam::from_front(self); + let arg1 = P::Glam::from_front(rhs); + + let result = binary_fn(arg0, arg1); + result.to_front() + } +} + +pub(crate) trait GlamType { + type Mapped; + + fn to_front(&self) -> Self::Mapped; + fn from_front(mapped: &Self::Mapped) -> Self; +} + +macro_rules! impl_glam_map_self { + ($T:ty) => { + impl GlamType for $T { + type Mapped = $T; + + fn to_front(&self) -> $T { + *self + } + + fn from_front(mapped: &$T) -> Self { + *mapped + } + } + }; +} + +impl_glam_map_self!(f32); +impl_glam_map_self!((f32, f32, f32)); diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index 005efb96c..95cb8514b 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -80,6 +80,7 @@ mod array_inner; mod dictionary_inner; mod color; +mod glam_helpers; mod math; mod node_path; mod others; diff --git a/godot-core/src/builtin/quaternion.rs b/godot-core/src/builtin/quaternion.rs index bda871be5..0d33d1a16 100644 --- a/godot-core/src/builtin/quaternion.rs +++ b/godot-core/src/builtin/quaternion.rs @@ -8,72 +8,71 @@ use std::ops::*; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::{math::*, vector3::*}; +use crate::builtin::glam_helpers::{GlamConv, GlamType}; +use crate::builtin::{inner, math::*, vector3::*}; -type Inner = glam::f32::Quat; - -#[derive(Default, Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub struct Quaternion { - inner: Inner, + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, } impl Quaternion { pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { - Self { - inner: Inner::from_xyzw(x, y, z, w), - } + Self { x, y, z, w } } pub fn from_angle_axis(axis: Vector3, angle: f32) -> Self { let d = axis.length(); if d == 0.0 { - Self { - inner: Inner::from_xyzw(0.0, 0.0, 0.0, 0.0), - } + Self::new(0.0, 0.0, 0.0, 0.0) } else { let sin_angle = (angle * 0.5).sin(); let cos_angle = (angle * 0.5).cos(); let s = sin_angle / d; - let x = axis.x() * s; - let y = axis.y() * s; - let z = axis.z() * s; + let x = axis.x * s; + let y = axis.y * s; + let z = axis.z * s; let w = cos_angle; - Self { - inner: Inner::from_xyzw(x, y, z, w), - } + Self::new(x, y, z, w) } } pub fn angle_to(self, to: Self) -> f32 { - self.inner.angle_between(to.inner) + self.glam2(&to, glam::f32::Quat::angle_between) } pub fn dot(self, with: Self) -> f32 { - self.inner.dot(with.inner) + self.glam2(&with, glam::f32::Quat::dot) } pub fn to_exp(self) -> Self { - let mut v = Vector3::new(self.inner.x, self.inner.y, self.inner.z); + let mut v = Vector3::new(self.x, self.y, self.z); let theta = v.length(); v = v.normalized(); + if theta < CMP_EPSILON || !v.is_normalized() { - return Quaternion::new(0.0, 0.0, 0.0, 1.0); + Self::default() + } else { + Self::from_angle_axis(v, theta) } - Quaternion::from_angle_axis(v, theta) } pub fn from_euler(self, euler: Vector3) -> Self { - let half_a1 = euler.y() * 0.5; - let half_a2 = euler.x() * 0.5; - let half_a3 = euler.z() * 0.5; + let half_a1 = euler.y * 0.5; + let half_a2 = euler.x * 0.5; + let half_a3 = euler.z * 0.5; let cos_a1 = half_a1.cos(); let sin_a1 = half_a1.sin(); let cos_a2 = half_a2.cos(); let sin_a2 = half_a2.sin(); let cos_a3 = half_a3.cos(); let sin_a3 = half_a3.sin(); - Quaternion::new( + + Self::new( sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3, sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3, -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3, @@ -82,44 +81,46 @@ impl Quaternion { } pub fn get_angle(self) -> f32 { - 2.0 * self.inner.w.acos() + 2.0 * self.w.acos() } pub fn get_axis(self) -> Vector3 { - if self.inner.w.abs() > 1.0 - CMP_EPSILON { - return Vector3::new(self.inner.x, self.inner.y, self.inner.z); + let Self { x, y, z, w } = self; + let axis = Vector3::new(x, y, z); + + if self.w.abs() > 1.0 - CMP_EPSILON { + axis + } else { + let r = 1.0 / (1.0 - w * w).sqrt(); + r * axis } - let r = 1.0 / (1.0 - self.inner.w * self.inner.w).sqrt(); - Vector3::new(self.inner.x * r, self.inner.y * r, self.inner.z * r) } - /// TODO: Figure out how godot actually treats "order", then make a match/if chain + // TODO: Figure out how godot actually treats "order", then make a match/if chain pub fn get_euler(self, order: Option) -> Vector3 { let _o = order.unwrap_or(2); - let vt = self.inner.to_euler(glam::EulerRot::XYZ); + let vt = self.glam(|quat| quat.to_euler(glam::EulerRot::XYZ)); + Vector3::new(vt.0, vt.1, vt.2) } pub fn inverse(self) -> Self { - Quaternion::new(-self.inner.x, -self.inner.y, -self.inner.z, self.inner.w) + Self::new(-self.x, -self.y, -self.z, self.w) } pub fn is_equal_approx(self, to: Self) -> bool { - is_equal_approx(self.inner.x, to.inner.x) - && is_equal_approx(self.inner.y, to.inner.y) - && is_equal_approx(self.inner.z, to.inner.z) - && is_equal_approx(self.inner.w, to.inner.w) + is_equal_approx(self.x, to.x) + && is_equal_approx(self.y, to.y) + && is_equal_approx(self.z, to.z) + && is_equal_approx(self.w, to.w) } pub fn is_finite(self) -> bool { - self.inner.x.is_finite() - && self.inner.y.is_finite() - && self.inner.z.is_finite() - && self.inner.w.is_finite() + self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite() } pub fn is_normalized(self) -> bool { - is_equal_approx(self.length_squared(), 1.0) /*,UNIT_EPSILON)*/ + is_equal_approx(self.length_squared(), 1.0) } pub fn length(self) -> f32 { @@ -132,7 +133,7 @@ impl Quaternion { pub fn log(self) -> Self { let v = self.get_axis() * self.get_angle(); - Quaternion::new(v.x(), v.y(), v.z(), 0.0) + Quaternion::new(v.x, v.y, v.z, 0.0) } pub fn normalized(self) -> Self { @@ -162,12 +163,8 @@ impl Quaternion { scale0 = 1.0 - weight; scale1 = weight; } - Quaternion::new( - scale0 * self.inner.x + scale1 * to1.inner.x, - scale0 * self.inner.y + scale1 * to1.inner.y, - scale0 * self.inner.z + scale1 * to1.inner.z, - scale0 * self.inner.w + scale1 * to1.inner.w, - ) + + scale0 * self + scale1 * to1 } pub fn slerpni(self, to: Self, weight: f32) -> Self { @@ -179,12 +176,8 @@ impl Quaternion { let sin_t = 1.0 / theta.sin(); let new_factor = (weight * theta).sin() * sin_t; let inv_factor = ((1.0 - weight) * theta).sin() * sin_t; - Quaternion::new( - inv_factor * self.inner.x + new_factor * to.inner.x, - inv_factor * self.inner.y + new_factor * to.inner.y, - inv_factor * self.inner.z + new_factor * to.inner.z, - inv_factor * self.inner.w + new_factor * to.inner.w, - ) + + inv_factor * self + new_factor * to } // pub fn spherical_cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: f32) -> Self {} @@ -202,6 +195,11 @@ impl Quaternion { ) -> Self { } */ + + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerQuaternion { + inner::InnerQuaternion::from_outer(self) + } } impl Add for Quaternion { @@ -209,10 +207,10 @@ impl Add for Quaternion { fn add(self, other: Self) -> Self { Self::new( - self.inner.x + other.inner.x, - self.inner.y + other.inner.y, - self.inner.z + other.inner.z, - self.inner.w + other.inner.w, + self.x + other.x, + self.y + other.y, + self.z + other.z, + self.w + other.w, ) } } @@ -228,10 +226,10 @@ impl Sub for Quaternion { fn sub(self, other: Self) -> Self { Self::new( - self.inner.x - other.inner.x, - self.inner.y - other.inner.y, - self.inner.z - other.inner.z, - self.inner.w - other.inner.w, + self.x - other.x, + self.y - other.y, + self.z - other.z, + self.w - other.w, ) } } @@ -246,26 +244,49 @@ impl Mul for Quaternion { type Output = Self; fn mul(self, other: Quaternion) -> Self { - let x = self.inner.w * other.inner.x - + self.inner.x * other.inner.w - + self.inner.y * other.inner.z - - self.inner.z * other.inner.y; - let y = self.inner.w * other.inner.y - + self.inner.y * other.inner.w - + self.inner.z * other.inner.x - - self.inner.x * other.inner.z; - let z = self.inner.w * other.inner.z - + self.inner.z * other.inner.w - + self.inner.x * other.inner.y - - self.inner.y * other.inner.x; - let w = self.inner.w * other.inner.w - - self.inner.x * other.inner.x - - self.inner.y * other.inner.y - - self.inner.z * other.inner.z; + // TODO use glam? + + let x = self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y; + let y = self.w * other.y + self.y * other.w + self.z * other.x - self.x * other.z; + let z = self.w * other.z + self.z * other.w + self.x * other.y - self.y * other.x; + let w = self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z; + Self::new(x, y, z, w) } } +impl GodotFfi for Quaternion { + ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } +} + +impl std::fmt::Display for Quaternion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_glam().fmt(f) + } +} + +impl Default for Quaternion { + fn default() -> Self { + Self::new(0.0, 0.0, 0.0, 1.0) + } +} + +impl GlamConv for Quaternion { + type Glam = glam::f32::Quat; +} + +impl GlamType for glam::f32::Quat { + type Mapped = Quaternion; + + fn to_front(&self) -> Self::Mapped { + Quaternion::new(self.x, self.y, self.z, self.w) + } + + fn from_front(mapped: &Self::Mapped) -> Self { + glam::f32::Quat::from_xyzw(mapped.x, mapped.y, mapped.z, mapped.w) + } +} + impl MulAssign for Quaternion { fn mul_assign(&mut self, other: Quaternion) { *self = *self * other @@ -277,14 +298,22 @@ impl Mul for Quaternion { fn mul(self, other: f32) -> Self { Quaternion::new( - self.inner.x * other, - self.inner.y * other, - self.inner.z * other, - self.inner.w * other, + self.x * other, + self.y * other, + self.z * other, + self.w * other, ) } } +impl Mul for f32 { + type Output = Quaternion; + + fn mul(self, other: Quaternion) -> Quaternion { + other * self + } +} + impl MulAssign for Quaternion { fn mul_assign(&mut self, other: f32) { *self = *self * other @@ -296,10 +325,10 @@ impl Div for Quaternion { fn div(self, other: f32) -> Self { Self::new( - self.inner.x / other, - self.inner.y / other, - self.inner.z / other, - self.inner.w / other, + self.x / other, + self.y / other, + self.z / other, + self.w / other, ) } } @@ -314,16 +343,6 @@ impl Neg for Quaternion { type Output = Self; fn neg(self) -> Self { - Self::new(-self.inner.x, -self.inner.y, -self.inner.z, -self.inner.w) - } -} - -impl GodotFfi for Quaternion { - ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } -} - -impl std::fmt::Display for Quaternion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.inner.fmt(f) + Self::new(-self.x, -self.y, -self.z, -self.w) } } diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 7aedc7c58..010e92235 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -20,6 +20,7 @@ mod gdscript_ffi_test; mod node_test; mod object_test; mod packed_array_test; +mod quaternion_test; mod singleton_test; mod string_test; mod utilities_test; @@ -28,19 +29,20 @@ mod virtual_methods_test; fn run_tests() -> bool { let mut ok = true; + ok &= array_test::run(); ok &= base_test::run(); ok &= builtin_test::run(); ok &= codegen_test::run(); + ok &= dictionary_test::run(); ok &= enum_test::run(); ok &= export_test::run(); ok &= gdscript_ffi_test::run(); ok &= node_test::run(); ok &= object_test::run(); + ok &= packed_array_test::run(); + ok &= quaternion_test::run(); ok &= singleton_test::run(); ok &= string_test::run(); - ok &= array_test::run(); - ok &= packed_array_test::run(); - ok &= dictionary_test::run(); ok &= utilities_test::run(); ok &= variant_test::run(); ok &= virtual_methods_test::run(); diff --git a/itest/rust/src/quaternion_test.rs b/itest/rust/src/quaternion_test.rs new file mode 100644 index 000000000..79ddc57c3 --- /dev/null +++ b/itest/rust/src/quaternion_test.rs @@ -0,0 +1,37 @@ +/* + * 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::itest; +use godot::builtin::Quaternion; + +pub fn run() -> bool { + let mut ok = true; + ok &= quaternion_default(); + ok &= quaternion_from_xyzw(); + ok +} + +#[itest] +fn quaternion_default() { + let quat = Quaternion::default(); + + assert_eq!(quat.x, 0.0); + assert_eq!(quat.y, 0.0); + assert_eq!(quat.z, 0.0); + assert_eq!(quat.w, 1.0); +} + +#[itest] +fn quaternion_from_xyzw() { + let quat = Quaternion::new(0.2391, 0.099, 0.3696, 0.8924); + + assert_eq!(quat.x, 0.2391); + assert_eq!(quat.y, 0.099); + assert_eq!(quat.z, 0.3696); + assert_eq!(quat.w, 0.8924); +} + +// TODO more tests