From 7bffee32a9216914cd90bebdec6b52c5abb883df Mon Sep 17 00:00:00 2001 From: RealAstolfo Date: Mon, 16 Jan 2023 13:21:24 -0700 Subject: [PATCH 1/2] Quaternion implementation, stub removal --- godot-core/src/builtin/mod.rs | 2 + godot-core/src/builtin/others.rs | 1 - godot-core/src/builtin/quaternion.rs | 329 +++++++++++++++++++++++++++ 3 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 godot-core/src/builtin/quaternion.rs diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index cf8f37bb6..005efb96c 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -42,6 +42,7 @@ pub use math::*; pub use node_path::*; pub use others::*; pub use packed_array::*; +pub use quaternion::*; pub use string::*; pub use string_name::*; pub use variant::*; @@ -83,6 +84,7 @@ mod math; mod node_path; mod others; mod packed_array; +mod quaternion; mod string; mod string_name; mod variant; diff --git a/godot-core/src/builtin/others.rs b/godot-core/src/builtin/others.rs index 894a02273..99f40a32f 100644 --- a/godot-core/src/builtin/others.rs +++ b/godot-core/src/builtin/others.rs @@ -16,7 +16,6 @@ use sys::{ffi_methods, GodotFfi}; impl_builtin_stub!(Rect2, OpaqueRect2); impl_builtin_stub!(Rect2i, OpaqueRect2i); impl_builtin_stub!(Plane, OpaquePlane); -impl_builtin_stub!(Quaternion, OpaqueQuaternion); impl_builtin_stub!(Aabb, OpaqueAabb); impl_builtin_stub!(Basis, OpaqueBasis); impl_builtin_stub!(Transform2D, OpaqueTransform2D); diff --git a/godot-core/src/builtin/quaternion.rs b/godot-core/src/builtin/quaternion.rs new file mode 100644 index 000000000..bda871be5 --- /dev/null +++ b/godot-core/src/builtin/quaternion.rs @@ -0,0 +1,329 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +use std::ops::*; + +use godot_ffi as sys; +use sys::{ffi_methods, GodotFfi}; + +use crate::builtin::{math::*, vector3::*}; + +type Inner = glam::f32::Quat; + +#[derive(Default, Copy, Clone, Debug, PartialEq)] +#[repr(C)] +pub struct Quaternion { + inner: Inner, +} + +impl Quaternion { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self { + inner: Inner::from_xyzw(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), + } + } 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 w = cos_angle; + Self { + inner: Inner::from_xyzw(x, y, z, w), + } + } + } + + pub fn angle_to(self, to: Self) -> f32 { + self.inner.angle_between(to.inner) + } + + pub fn dot(self, with: Self) -> f32 { + self.inner.dot(with.inner) + } + + pub fn to_exp(self) -> Self { + let mut v = Vector3::new(self.inner.x, self.inner.y, self.inner.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); + } + 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 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( + 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, + sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3, + ) + } + + pub fn get_angle(self) -> f32 { + 2.0 * self.inner.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 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 + pub fn get_euler(self, order: Option) -> Vector3 { + let _o = order.unwrap_or(2); + let vt = self.inner.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) + } + + 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) + } + + 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() + } + + pub fn is_normalized(self) -> bool { + is_equal_approx(self.length_squared(), 1.0) /*,UNIT_EPSILON)*/ + } + + pub fn length(self) -> f32 { + self.length_squared().sqrt() + } + + pub fn length_squared(self) -> f32 { + self.dot(self) + } + + pub fn log(self) -> Self { + let v = self.get_axis() * self.get_angle(); + Quaternion::new(v.x(), v.y(), v.z(), 0.0) + } + + pub fn normalized(self) -> Self { + self / self.length() + } + + pub fn slerp(self, to: Self, weight: f32) -> Self { + let mut cosom = self.dot(to); + let to1: Self; + let omega: f32; + let sinom: f32; + let scale0: f32; + let scale1: f32; + if cosom < 0.0 { + cosom = -cosom; + to1 = -to; + } else { + to1 = to; + } + + if 1.0 - cosom > CMP_EPSILON { + omega = cosom.acos(); + sinom = omega.sin(); + scale0 = ((1.0 - weight) * omega).sin() / sinom; + scale1 = (weight * omega).sin() / sinom; + } else { + 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, + ) + } + + pub fn slerpni(self, to: Self, weight: f32) -> Self { + let dot = self.dot(to); + if dot.abs() > 0.9999 { + return self; + } + let theta = dot.acos(); + 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, + ) + } + + // pub fn spherical_cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: f32) -> Self {} + // TODO: Implement godot's function in rust + /* + pub fn spherical_cubic_interpolate_in_time( + self, + b: Self, + pre_a: Self, + post_b: Self, + weight: f32, + b_t: f32, + pre_a_t: f32, + post_b_t: f32, + ) -> Self { + } + */ +} + +impl Add for Quaternion { + type Output = Self; + + 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, + ) + } +} + +impl AddAssign for Quaternion { + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl Sub for Quaternion { + type Output = Self; + + 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, + ) + } +} + +impl SubAssign for Quaternion { + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +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; + Self::new(x, y, z, w) + } +} + +impl MulAssign for Quaternion { + fn mul_assign(&mut self, other: Quaternion) { + *self = *self * other + } +} + +impl Mul for Quaternion { + type Output = Self; + + fn mul(self, other: f32) -> Self { + Quaternion::new( + self.inner.x * other, + self.inner.y * other, + self.inner.z * other, + self.inner.w * other, + ) + } +} + +impl MulAssign for Quaternion { + fn mul_assign(&mut self, other: f32) { + *self = *self * other + } +} + +impl Div for Quaternion { + type Output = Self; + + fn div(self, other: f32) -> Self { + Self::new( + self.inner.x / other, + self.inner.y / other, + self.inner.z / other, + self.inner.w / other, + ) + } +} + +impl DivAssign for Quaternion { + fn div_assign(&mut self, other: f32) { + *self = *self / other + } +} + +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) + } +} From 4f3d1062379cf65f5f1d29f0822850849b7595e2 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 12 Feb 2023 09:38:08 +0100 Subject: [PATCH 2/2] Make Quaternion work with latest master; new experimental glam pattern --- godot-core/src/builtin/glam_helpers.rs | 74 +++++++++ godot-core/src/builtin/mod.rs | 1 + godot-core/src/builtin/quaternion.rs | 217 ++++++++++++++----------- itest/rust/src/lib.rs | 8 +- itest/rust/src/quaternion_test.rs | 37 +++++ 5 files changed, 235 insertions(+), 102 deletions(-) create mode 100644 godot-core/src/builtin/glam_helpers.rs create mode 100644 itest/rust/src/quaternion_test.rs 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