Skip to content

Commit d65adfa

Browse files
committed
function-completeness
1 parent 52628a0 commit d65adfa

File tree

2 files changed

+160
-20
lines changed

2 files changed

+160
-20
lines changed

godot-core/src/builtin/quaternion.rs

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use godot_ffi as sys;
99
use sys::{ffi_methods, GodotFfi};
1010

1111
use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType};
12-
use crate::builtin::{inner, real, Basis, EulerOrder, RQuat, Vector3};
12+
use crate::builtin::{inner, real, Basis, EulerOrder, RQuat, RealConv, Vector3};
1313

1414
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
1515

@@ -30,10 +30,14 @@ impl Quaternion {
3030
Self { x, y, z, w }
3131
}
3232

33+
/// Creates a Quaternion from a Vector3 and an angle.
34+
///
35+
/// # Panics
36+
/// If the Vector3 is not normalized.
3337
pub fn from_angle_axis(axis: Vector3, angle: real) -> Self {
3438
let d = axis.length();
35-
if d == 0.0 {
36-
Self::new(0.0, 0.0, 0.0, 0.0)
39+
if d != 1.0 {
40+
panic!("Attempted to create a Quaternion from a Vector3 that was not normalized.");
3741
} else {
3842
let sin_angle = (angle * 0.5).sin();
3943
let cos_angle = (angle * 0.5).cos();
@@ -130,8 +134,16 @@ impl Quaternion {
130134
Quaternion::new(v.x, v.y, v.z, 0.0)
131135
}
132136

137+
/// Normalizes the Quaternion.
138+
///
139+
/// # Panics
140+
/// If the Quaternion has length of 0.
133141
pub fn normalized(self) -> Self {
134-
self / self.length()
142+
let length = self.length();
143+
if length == 0.0 {
144+
panic!("Attempted to normalize a Quaternion with 0 length.");
145+
}
146+
self / length
135147
}
136148

137149
pub fn slerp(self, to: Self, weight: real) -> Self {
@@ -174,21 +186,73 @@ impl Quaternion {
174186
inv_factor * self + new_factor * to
175187
}
176188

177-
// pub fn spherical_cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: real) -> Self {}
178-
// TODO: Implement godot's function in Rust
179-
/*
180-
pub fn spherical_cubic_interpolate_in_time(
181-
self,
182-
b: Self,
183-
pre_a: Self,
184-
post_b: Self,
185-
weight: real,
186-
b_t: real,
187-
pre_a_t: real,
188-
post_b_t: real,
189-
) -> Self {
189+
/// Performs spherical cubic interpolation.
190+
///
191+
/// # Panics
192+
/// If the provided Quaternions are not normalized.
193+
pub fn spherical_cubic_interpolate(
194+
self,
195+
b: Self,
196+
pre_a: Self,
197+
post_b: Self,
198+
weight: real,
199+
) -> Self {
200+
let interpolated =
201+
self.as_inner()
202+
.spherical_cubic_interpolate(b, pre_a, post_b, weight.as_f64());
203+
// Godot returns default if you give it Quaternions that are not normalized. This means we can check for default
204+
// Then check if we should panic.
205+
if interpolated == Quaternion::default()
206+
&& (!b.is_normalized()
207+
|| !pre_a.is_normalized()
208+
|| !post_b.is_normalized()
209+
|| !self.is_normalized())
210+
{
211+
panic!(
212+
"Attempted spherical cubic interpolation on Quaternions that are not normalized."
213+
);
214+
}
215+
interpolated
216+
}
217+
218+
/// Performs spherical cubic interpolation in time.
219+
///
220+
/// # Panics
221+
/// If the provided Quaternions are not normalized.
222+
#[allow(clippy::too_many_arguments)]
223+
pub fn spherical_cubic_interpolate_in_time(
224+
self,
225+
b: Self,
226+
pre_a: Self,
227+
post_b: Self,
228+
weight: real,
229+
b_t: real,
230+
pre_a_t: real,
231+
post_b_t: real,
232+
) -> Self {
233+
let interpolated = self.as_inner().spherical_cubic_interpolate_in_time(
234+
b,
235+
pre_a,
236+
post_b,
237+
weight.as_f64(),
238+
b_t.as_f64(),
239+
pre_a_t.as_f64(),
240+
post_b_t.as_f64(),
241+
);
242+
// Godot returns default if you give it Quaternions that are not normalized. This means we can check for default
243+
// Then check if we should panic.
244+
if interpolated == Quaternion::default()
245+
&& (!b.is_normalized()
246+
|| !pre_a.is_normalized()
247+
|| !post_b.is_normalized()
248+
|| !self.is_normalized())
249+
{
250+
panic!(
251+
"Attempted spherical cubic interpolation in time on Quaternions that are not normalized."
252+
);
190253
}
191-
*/
254+
interpolated
255+
}
192256

193257
#[doc(hidden)]
194258
pub fn as_inner(&self) -> inner::InnerQuaternion {

itest/rust/src/builtin_tests/geometry/quaternion_test.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use crate::framework::itest;
9-
use godot::builtin::Quaternion;
8+
use crate::framework::{expect_panic, itest};
9+
use godot::builtin::math::assert_eq_approx;
10+
use godot::builtin::{Quaternion, Vector3};
1011

1112
#[itest]
1213
fn quaternion_default() {
@@ -28,4 +29,79 @@ fn quaternion_from_xyzw() {
2829
assert_eq!(quat.w, 0.8924);
2930
}
3031

32+
#[itest]
33+
fn quaternion_normalization() {
34+
expect_panic("Attempted to normalize a Quaternion with 0 length.", || {
35+
Quaternion::new(0.0, 0.0, 0.0, 0.0).normalized();
36+
});
37+
let quat = Quaternion::default();
38+
assert_eq!(quat.normalized().length(), 1.0);
39+
}
40+
41+
#[itest]
42+
fn quaternion_from_angle_axis() {
43+
let quat = Quaternion::from_angle_axis(Vector3::new(0.0, 0.0, 1.0).normalized(), 1.0);
44+
45+
// Taken from doing this in GDScript.
46+
assert_eq!(quat.x, 0.0);
47+
assert_eq!(quat.y, 0.0);
48+
assert_eq_approx!(quat.z, 0.479426);
49+
assert_eq_approx!(quat.w, 0.877583);
50+
51+
expect_panic(
52+
"Attempted to create a Quaternion from a Vector3 that was not normalized.",
53+
|| {
54+
Quaternion::from_angle_axis(Vector3::new(0.0, 0.0, 0.0), 1.0);
55+
},
56+
);
57+
}
58+
59+
#[itest]
60+
fn quaternion_spherical_cubic_interpolate() {
61+
let pre_a = Quaternion::new(-1.0, -1.0, -1.0, -1.0);
62+
let a = Quaternion::new(0.0, 0.0, 0.0, 1.0);
63+
let b = Quaternion::new(0.0, 1.0, 0.0, 2.0);
64+
let post_b = Quaternion::new(2.0, 2.0, 2.0, 2.0);
65+
66+
let outcome =
67+
a.spherical_cubic_interpolate(b.normalized(), pre_a.normalized(), post_b.normalized(), 0.5);
68+
// Taken from doing this in GDScript.
69+
let expected = Quaternion::new(-0.072151, 0.176298, -0.072151, 0.979034);
70+
assert_eq_approx!(outcome, expected);
71+
72+
expect_panic(
73+
"Attempted spherical cubic interpolation on quaternions that are not normalized.",
74+
|| {
75+
a.spherical_cubic_interpolate(b, pre_a, post_b, 0.5);
76+
},
77+
);
78+
}
79+
80+
#[itest]
81+
fn quaternion_spherical_cubic_interpolate_in_time() {
82+
let pre_a = Quaternion::new(-1.0, -1.0, -1.0, -1.0);
83+
let a = Quaternion::new(0.0, 0.0, 0.0, 1.0);
84+
let b = Quaternion::new(0.0, 1.0, 0.0, 2.0);
85+
let post_b = Quaternion::new(2.0, 2.0, 2.0, 2.0);
86+
87+
let outcome = a.spherical_cubic_interpolate_in_time(
88+
b.normalized(),
89+
pre_a.normalized(),
90+
post_b.normalized(),
91+
0.5,
92+
0.1,
93+
0.1,
94+
0.1,
95+
);
96+
// Taken from doing this in GDScript.
97+
let expected = Quaternion::new(0.280511, 0.355936, 0.280511, 0.84613);
98+
assert_eq_approx!(outcome, expected);
99+
100+
expect_panic(
101+
"Attempted spherical cubic interpolation in time on Quaternions that are not normalized.",
102+
|| {
103+
a.spherical_cubic_interpolate_in_time(b, pre_a, post_b, 0.5, 0.1, 0.1, 0.1);
104+
},
105+
);
106+
}
31107
// TODO more tests

0 commit comments

Comments
 (0)