Skip to content

Commit 7ffe859

Browse files
bors[bot]Demindiro
andauthored
Merge #720
720: Implement Godot methods for Quat r=Bromeon a=Demindiro Co-authored-by: David Hoppenbrouwers <david@salt-inc.org>
2 parents f88f4e5 + 32fd68f commit 7ffe859

File tree

3 files changed

+300
-8
lines changed

3 files changed

+300
-8
lines changed

gdnative-core/src/core_types/geom/basis.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::core_types::{IsEqualApprox, Quat, Vector3};
22
use core::ops::Mul;
3+
use glam::Mat3;
34

45
/// A 3x3 matrix.
56
#[repr(C)]
@@ -86,6 +87,18 @@ impl Basis {
8687
b
8788
}
8889

90+
/// Constructs a pure rotation basis matrix from the given quaternion.
91+
#[inline]
92+
pub fn from_quat(quat: Quat) -> Self {
93+
let basis = Mat3::from_quat(quat.glam()).to_cols_array_2d();
94+
let basis = [
95+
Vector3::new(basis[0][0], basis[1][0], basis[2][0]),
96+
Vector3::new(basis[0][1], basis[1][1], basis[2][1]),
97+
Vector3::new(basis[0][2], basis[1][2], basis[2][2]),
98+
];
99+
Basis::from_elements(basis)
100+
}
101+
89102
/// Rotation matrix from axis and angle.
90103
///
91104
/// See <https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_angle>
@@ -709,7 +722,7 @@ mod tests {
709722
fn to_quat() {
710723
let (b, _bn) = test_inputs();
711724

712-
assert!(Quat::new(-0.167156, 0.677813, -0.043058, 0.714685).is_equal_approx(&b.to_quat()));
725+
assert!(Quat::new(-0.167156, 0.677813, -0.043058, 0.714685).is_equal_approx(b.to_quat()));
713726
}
714727

715728
#[test]

gdnative-core/src/core_types/quat.rs

Lines changed: 284 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use super::IsEqualApprox;
1+
use super::{Basis, IsEqualApprox, Vector3, CMP_EPSILON};
2+
use glam::EulerRot;
3+
use std::ops::{Mul, Neg};
24

35
#[derive(Copy, Clone, Debug, PartialEq)]
46
#[repr(C)]
@@ -9,23 +11,300 @@ pub struct Quat {
911
pub w: f32,
1012
}
1113

14+
/// Helper methods for `Quat`.
15+
///
16+
/// See the official [`Godot documentation`](https://docs.godotengine.org/en/stable/classes/class_quat.html).
1217
impl Quat {
18+
/// The identity quaternion, representing no rotation. Equivalent to an identity [`Basis`] matrix.
19+
/// If a vector is transformed by an identity quaternion, it will not change.
20+
pub const IDENTITY: Self = Self::new(0.0, 0.0, 0.0, 1.0);
21+
22+
/// Constructs a quaternion defined by the given values.
1323
#[inline]
14-
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
24+
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
1525
Self { x, y, z, w }
1626
}
1727

28+
/// Constructs a quaternion from the given [Basis]
29+
#[inline]
30+
pub fn from_basis(basis: &Basis) -> Self {
31+
basis.to_quat()
32+
}
33+
34+
/// Constructs a quaternion that will perform a rotation specified by Euler angles (in the YXZ
35+
/// convention: when decomposing, first Z, then X, and Y last), given in the vector format as
36+
/// (X angle, Y angle, Z angle).
37+
#[inline]
38+
pub fn from_euler(euler: Vector3) -> Self {
39+
Self::gd(glam::Quat::from_euler(
40+
EulerRot::YXZ,
41+
euler.y,
42+
euler.x,
43+
euler.z,
44+
))
45+
}
46+
47+
/// Constructs a quaternion that will rotate around the given axis by the specified angle. The
48+
/// axis must be a normalized vector.
49+
#[inline]
50+
pub fn from_axis_angle(axis: Vector3, angle: f32) -> Self {
51+
debug_assert!(axis.is_normalized(), "Axis is not normalized");
52+
Self::gd(glam::Quat::from_axis_angle(axis.glam().into(), angle))
53+
}
54+
55+
/// Performs a cubic spherical interpolation between quaternions `pre_a`, this quaternion, `b`,
56+
/// and `post_b`, by the given amount `t`.
57+
#[inline]
58+
pub fn cubic_slerp(self, b: Self, pre_a: Self, post_b: Self, t: f32) -> Self {
59+
let t2 = (1.0 - t) * t * 2.0;
60+
let sp = self.slerp(b, t);
61+
let sq = pre_a.slerpni(post_b, t);
62+
sp.slerpni(sq, t2)
63+
}
64+
65+
/// Returns the dot product of two quaternions.
66+
#[inline]
67+
pub fn dot(self, b: Self) -> f32 {
68+
self.glam().dot(b.glam())
69+
}
70+
71+
/// Returns Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last)
72+
/// corresponding to the rotation represented by the unit quaternion. Returned vector contains
73+
/// the rotation angles in the format (X angle, Y angle, Z angle).
74+
#[inline]
75+
pub fn to_euler(self) -> Vector3 {
76+
Basis::from_quat(self).to_euler()
77+
}
78+
79+
/// Returns the inverse of the quaternion.
80+
#[inline]
81+
pub fn inverse(self) -> Self {
82+
Self::gd(self.glam().inverse())
83+
}
84+
85+
/// Returns `true` if this quaternion and `quat` are approximately equal, by running
86+
/// `is_equal_approx` on each component
1887
#[inline]
19-
pub fn is_equal_approx(self, to: &Self) -> bool {
88+
pub fn is_equal_approx(self, to: Self) -> bool {
2089
self.x.is_equal_approx(to.x)
2190
&& self.y.is_equal_approx(to.y)
2291
&& self.z.is_equal_approx(to.z)
2392
&& self.w.is_equal_approx(to.w)
2493
}
2594

95+
/// Returns whether the quaternion is normalized or not.
96+
#[inline]
97+
pub fn is_normalized(self) -> bool {
98+
self.glam().is_normalized()
99+
}
100+
101+
/// Returns the length of the quaternion.
102+
#[inline]
103+
pub fn length(self) -> f32 {
104+
self.glam().length()
105+
}
106+
107+
/// Returns the length of the quaternion, squared.
108+
#[inline]
109+
pub fn length_squared(self) -> f32 {
110+
self.glam().length_squared()
111+
}
112+
113+
/// Returns a copy of the quaternion, normalized to unit length.
114+
///
115+
/// Normalization is necessary before transforming vectors through `xform()` or `*`.
116+
#[inline]
117+
pub fn normalized(self) -> Self {
118+
Self::gd(self.glam().normalize())
119+
}
120+
121+
/// Returns the result of the spherical linear interpolation between this quaternion and to by
122+
/// amount weight.
123+
///
124+
/// **Note:** Both quaternions must be normalized.
125+
#[inline]
126+
pub fn slerp(self, b: Self, t: f32) -> Self {
127+
debug_assert!(self.is_normalized(), "Quaternion `self` is not normalized");
128+
debug_assert!(b.is_normalized(), "Quaternion `b` is not normalized");
129+
130+
// Copied from Godot's Quat::slerp as glam::lerp version diverges too much
131+
132+
// calc cosine
133+
let cos = self.dot(b);
134+
135+
// adjust signs (if necessary)
136+
let (cos, b) = if cos < 0.0 { (-cos, -b) } else { (cos, b) };
137+
138+
// calculate coefficients
139+
let scale = if 1.0 - cos > CMP_EPSILON as f32 {
140+
// standard case (slerp)
141+
let omega = cos.acos();
142+
let sin = omega.sin();
143+
(((1.0 - t) * omega).sin() / sin, (t * omega).sin() / sin)
144+
} else {
145+
// "from" and "to" quaternions are very close
146+
// ... so we can do a linear interpolation
147+
(1.0 - t, t)
148+
};
149+
150+
// calculate final values
151+
Self::new(
152+
scale.0 * self.x + scale.1 * b.x,
153+
scale.0 * self.y + scale.1 * b.y,
154+
scale.0 * self.z + scale.1 * b.z,
155+
scale.0 * self.w + scale.1 * b.w,
156+
)
157+
}
158+
159+
/// Returns the result of the spherical linear interpolation between this quaternion and `t` by
160+
/// amount `t`, but without checking if the rotation path is not bigger than 90 degrees.
161+
#[inline]
162+
pub fn slerpni(self, b: Self, t: f32) -> Self {
163+
debug_assert!(self.is_normalized(), "Quaternion `self` is not normalized");
164+
debug_assert!(b.is_normalized(), "Quaternion `b` is not normalized");
165+
166+
// Copied from Godot's Quat::slerpni as glam::slerp version diverges too much
167+
168+
let dot = self.dot(b);
169+
if dot.abs() > 0.9999 {
170+
self
171+
} else {
172+
let theta = dot.acos();
173+
let sin_t = 1.0 / theta.sin();
174+
let new_f = (t * theta).sin() * sin_t;
175+
let inv_f = ((1.0 - t) * theta).sin() * sin_t;
176+
Self::new(
177+
inv_f * self.x + new_f * b.x,
178+
inv_f * self.y + new_f * b.y,
179+
inv_f * self.z + new_f * b.z,
180+
inv_f * self.w + new_f * b.w,
181+
)
182+
}
183+
}
184+
185+
/// Returns a vector transformed (multiplied) by this quaternion. This is the same as `mul`
186+
///
187+
/// **Note:** The quaternion must be normalized.
26188
#[inline]
27-
#[allow(unused)]
28-
fn glam(self) -> glam::Quat {
189+
pub fn xform(self, v: Vector3) -> Vector3 {
190+
self * v
191+
}
192+
193+
#[inline]
194+
pub(super) fn gd(quat: glam::Quat) -> Self {
195+
Self::new(quat.x, quat.y, quat.z, quat.w)
196+
}
197+
198+
#[inline]
199+
pub(super) fn glam(self) -> glam::Quat {
29200
glam::Quat::from_xyzw(self.x, self.y, self.z, self.w)
30201
}
31202
}
203+
204+
impl Mul<Vector3> for Quat {
205+
type Output = Vector3;
206+
207+
#[inline]
208+
/// Returns a vector transformed (multiplied) by this quaternion. This is the same as `xform`
209+
///
210+
/// **Note:** The quaternion must be normalized.
211+
fn mul(self, with: Vector3) -> Vector3 {
212+
debug_assert!(self.is_normalized(), "Quaternion is not normalized");
213+
Vector3::gd(self.glam() * with.glam())
214+
}
215+
}
216+
217+
impl Mul<Self> for Quat {
218+
type Output = Self;
219+
220+
#[inline]
221+
/// Returns another quaternion transformed (multiplied) by this quaternion.
222+
fn mul(self, with: Self) -> Self {
223+
Self::gd(self.glam() * with.glam())
224+
}
225+
}
226+
227+
impl Neg for Quat {
228+
type Output = Quat;
229+
230+
#[inline]
231+
fn neg(self) -> Self {
232+
Self::gd(-self.glam())
233+
}
234+
}
235+
236+
#[cfg(test)]
237+
mod test {
238+
use super::*;
239+
240+
#[test]
241+
fn from_euler() {
242+
let euler = Vector3::new(0.25, 5.24, 3.0);
243+
let expect = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
244+
assert!(Quat::from_euler(euler).is_equal_approx(expect));
245+
}
246+
247+
#[test]
248+
fn to_basis() {
249+
let quat = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
250+
let expect = Basis::from_elements([
251+
Vector3::new(-0.528598, 0.140572, -0.837152),
252+
Vector3::new(0.136732, -0.959216, -0.247404),
253+
Vector3::new(-0.837788, -0.245243, 0.487819),
254+
]);
255+
let basis = Basis::from_quat(quat);
256+
assert!(basis.is_equal_approx(&expect));
257+
}
258+
259+
#[test]
260+
fn to_euler() {
261+
let quat = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
262+
let expect = Vector3::new(0.25, -1.043185, 3.00001);
263+
assert!(quat.to_euler().is_equal_approx(expect));
264+
}
265+
266+
#[test]
267+
fn mul_vec() {
268+
let quat = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
269+
let vec = Vector3::new(2.2, 0.8, 1.65);
270+
let expect = Vector3::new(-2.43176, -0.874777, -1.234427);
271+
assert!(expect.is_equal_approx(quat * vec));
272+
}
273+
274+
#[test]
275+
fn mul_quat() {
276+
let a = Quat::new(-0.635115, -0.705592, 0.314052, 0.011812);
277+
let b = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
278+
let e = Quat::new(0.568756, -0.394417, 0.242027, 0.67998);
279+
assert!(e.is_equal_approx(a * b));
280+
}
281+
282+
#[test]
283+
fn slerp() {
284+
let q = Quat::new(-0.635115, -0.705592, 0.314052, 0.011812);
285+
let p = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
286+
let t = 0.2;
287+
let e = Quat::new(-0.638517, -0.620742, 0.454844, 0.009609);
288+
assert!(e.is_equal_approx(q.slerp(p, t)));
289+
}
290+
291+
#[test]
292+
fn slerpni() {
293+
let q = Quat::new(-0.635115, -0.705592, 0.314052, 0.011812);
294+
let p = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
295+
let t = 0.2;
296+
let e = Quat::new(-0.535331, -0.836627, -0.114954, 0.016143);
297+
assert!(e.is_equal_approx(q.slerpni(p, t)));
298+
}
299+
300+
#[test]
301+
fn cubic_slerp() {
302+
let a = Quat::new(-0.635115, -0.705592, 0.314052, 0.011812);
303+
let b = Quat::new(0.485489, 0.142796, -0.862501, 0.001113);
304+
let c = Quat::new(-0.666276, 0.03859, 0.083527, -0.740007);
305+
let d = Quat::new(-0.856633, -0.430228, -0.284017, 0.020464);
306+
let t = 0.2;
307+
let e = Quat::new(-0.768253, -0.490687, 0.341836, -0.22839);
308+
assert!(e.is_equal_approx(a.cubic_slerp(b, c, d, t)));
309+
}
310+
}

gdnative-core/src/core_types/vector3.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,12 @@ impl Vector3 {
380380
}
381381

382382
#[inline]
383-
fn glam(self) -> Vec3A {
383+
pub(super) fn glam(self) -> Vec3A {
384384
Vec3A::new(self.x, self.y, self.z)
385385
}
386386

387387
#[inline]
388-
fn gd(from: Vec3A) -> Self {
388+
pub(super) fn gd(from: Vec3A) -> Self {
389389
Self::new(from.x, from.y, from.z)
390390
}
391391
}

0 commit comments

Comments
 (0)