1
- use super :: IsEqualApprox ;
1
+ use super :: { Basis , IsEqualApprox , Vector3 , CMP_EPSILON } ;
2
+ use core:: f32:: consts:: FRAC_PI_2 ;
3
+ use core:: ops:: Mul ;
4
+ use glam:: Mat3 ;
2
5
3
6
#[ derive( Copy , Clone , Debug , PartialEq ) ]
4
7
#[ repr( C ) ]
@@ -9,23 +12,249 @@ pub struct Quat {
9
12
pub w : f32 ,
10
13
}
11
14
15
+ /// Helper methods for `Quat`.
16
+ ///
17
+ /// See the official [`Godot documentation`](https://docs.godotengine.org/en/stable/classes/class_quat.html).
12
18
impl Quat {
19
+ pub const IDENTITY : Self = Self :: new ( 0.0 , 0.0 , 0.0 , 1.0 ) ;
20
+
21
+ /// Constructs a quaternion defined by the given values.
13
22
#[ inline]
14
- pub fn new ( x : f32 , y : f32 , z : f32 , w : f32 ) -> Self {
23
+ pub const fn new ( x : f32 , y : f32 , z : f32 , w : f32 ) -> Self {
15
24
Self { x, y, z, w }
16
25
}
17
26
27
+ /// Constructs a quaternion from the given [Basis]
28
+ #[ inline]
29
+ pub fn from_basis ( basis : & Basis ) -> Self {
30
+ basis. to_quat ( )
31
+ }
32
+
33
+ /// Constructs a quaternion that will perform a rotation specified by Euler angles (in the YXZ
34
+ /// convention: when decomposing, first Z, then X, and Y last), given in the vector format as
35
+ /// (X angle, Y angle, Z angle).
36
+ #[ inline]
37
+ pub fn from_euler ( euler : Vector3 ) -> Self {
38
+ Self :: gd ( glam:: Quat :: from_rotation_ypr ( euler. y , euler. x , euler. z ) )
39
+ }
40
+
41
+ /// Constructs a quaternion that will rotate around the given axis by the specified angle. The
42
+ /// axis must be a normalized vector.
43
+ #[ inline]
44
+ pub fn from_axis_angle ( axis : Vector3 , angle : f32 ) -> Self {
45
+ debug_assert ! ( axis. is_normalized( ) , "Axis is not normalized" ) ;
46
+ Self :: gd ( glam:: Quat :: from_axis_angle ( axis. glam ( ) . into ( ) , angle) )
47
+ }
48
+
49
+ /// Performs a cubic spherical interpolation between quaternions `pre_a`, this quaternion, `b`,
50
+ /// and `post_b`, by the given amount `t`.
51
+ #[ inline]
52
+ pub fn cubic_slerp ( self , b : Self , pre_a : Self , post_b : Self , t : f32 ) -> Self {
53
+ let t2 = ( 1.0 - t) * t * 2.0 ;
54
+ let sp = self . slerp ( b, t) ;
55
+ let sq = pre_a. slerpni ( post_b, t) ;
56
+ sp. slerpni ( sq, t2)
57
+ }
58
+
59
+ /// Returns the dot product of two quaternions.
60
+ #[ inline]
61
+ pub fn dot ( self , b : Self ) -> f32 {
62
+ self . glam ( ) . dot ( b. glam ( ) )
63
+ }
64
+
65
+ /// Returns Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last)
66
+ /// corresponding to the rotation represented by the unit quaternion. Returned vector contains
67
+ /// the rotation angles in the format (X angle, Y angle, Z angle).
68
+ #[ inline]
69
+ pub fn get_euler ( self ) -> Vector3 {
70
+ let basis = Mat3 :: from_quat ( self . glam ( ) ) ;
71
+ let elm = basis. to_cols_array_2d ( ) ;
72
+ // Copied from Basis::get_euler_yxz
73
+ let m12 = elm[ 1 ] [ 2 ] ;
74
+ if m12 < 1.0 - CMP_EPSILON as f32 {
75
+ if m12 > CMP_EPSILON as f32 - 1.0 {
76
+ #[ allow( clippy:: float_cmp) ] // Godot also used direct comparison
77
+ if elm[ 1 ] [ 0 ] == 0.0
78
+ && elm[ 0 ] [ 1 ] == 0.0
79
+ && elm[ 0 ] [ 2 ] == 0.0
80
+ && elm[ 2 ] [ 0 ] == 0.0
81
+ && elm[ 0 ] [ 0 ] == 1.0
82
+ {
83
+ Vector3 :: new ( ( -m12) . atan2 ( elm[ 1 ] [ 1 ] ) , 0.0 , 0.0 )
84
+ } else {
85
+ Vector3 :: new (
86
+ ( -m12) . asin ( ) ,
87
+ elm[ 0 ] [ 2 ] . atan2 ( elm[ 2 ] [ 2 ] ) ,
88
+ elm[ 1 ] [ 0 ] . atan2 ( elm[ 1 ] [ 1 ] ) ,
89
+ )
90
+ }
91
+ } else {
92
+ Vector3 :: new ( FRAC_PI_2 , elm[ 0 ] [ 1 ] . atan2 ( elm[ 0 ] [ 0 ] ) , 0.0 )
93
+ }
94
+ } else {
95
+ Vector3 :: new ( -FRAC_PI_2 , ( -elm[ 0 ] [ 1 ] ) . atan2 ( elm[ 0 ] [ 0 ] ) , 0.0 )
96
+ }
97
+ }
98
+
99
+ /// Returns the inverse of the quaternion.
100
+ #[ inline]
101
+ pub fn inverse ( self ) -> Self {
102
+ Self :: gd ( self . glam ( ) . inverse ( ) )
103
+ }
104
+
105
+ /// Returns `true` if this quaternion and `quat` are approximately equal, by running
106
+ /// `is_equal_approx` on each component
18
107
#[ inline]
19
- pub fn is_equal_approx ( self , to : & Self ) -> bool {
108
+ pub fn is_equal_approx ( self , to : Self ) -> bool {
20
109
self . x . is_equal_approx ( to. x )
21
110
&& self . y . is_equal_approx ( to. y )
22
111
&& self . z . is_equal_approx ( to. z )
23
112
&& self . w . is_equal_approx ( to. w )
24
113
}
25
114
115
+ /// Returns whether the quaternion is normalized or not.
116
+ #[ inline]
117
+ pub fn is_normalized ( self ) -> bool {
118
+ self . glam ( ) . is_normalized ( )
119
+ }
120
+
121
+ /// Returns the length of the quaternion.
122
+ #[ inline]
123
+ pub fn length ( self ) -> f32 {
124
+ self . glam ( ) . length ( )
125
+ }
126
+
127
+ /// Returns the length of the quaternion, squared.
128
+ #[ inline]
129
+ pub fn length_squared ( self ) -> f32 {
130
+ self . glam ( ) . length_squared ( )
131
+ }
132
+
133
+ /// Returns a copy of the quaternion, normalized to unit length.
134
+ #[ inline]
135
+ pub fn normalized ( self ) -> Self {
136
+ Self :: gd ( self . glam ( ) . normalize ( ) )
137
+ }
138
+
139
+ /// Sets the quaternion to a rotation which rotates around axis by the specified angle, in
140
+ /// radians. The axis must be a normalized vector.
141
+ #[ inline]
142
+ pub fn set_axis_angle ( & mut self , axis : Vector3 , angle : f32 ) {
143
+ * self = Self :: from_axis_angle ( axis, angle)
144
+ }
145
+
146
+ /// Sets the quaternion to a rotation specified by Euler angles (in the YXZ convention: when
147
+ /// decomposing, first Z, then X, and Y last), given in the vector format as (X angle, Y angle,
148
+ /// Z angle).
149
+ #[ inline]
150
+ pub fn set_euler ( & mut self , euler : Vector3 ) {
151
+ * self = Self :: from_euler ( euler) ;
152
+ }
153
+
154
+ /// Returns the result of the spherical linear interpolation between this quaternion and to by
155
+ /// amount weight.
156
+ ///
157
+ /// **Note:** Both quaternions must be normalized.
158
+ #[ inline]
159
+ pub fn slerp ( self , b : Self , t : f32 ) -> Self {
160
+ debug_assert ! ( self . is_normalized( ) , "Quaternion `self` is not normalized" ) ;
161
+ debug_assert ! ( b. is_normalized( ) , "Quaternion `b` is not normalized" ) ;
162
+ Self :: gd ( self . glam ( ) . slerp ( b. glam ( ) , t) )
163
+ }
164
+
165
+ /// Returns the result of the spherical linear interpolation between this quaternion and `t` by
166
+ /// amount `t`, but without checking if the rotation path is not bigger than 90 degrees.
167
+ #[ inline]
168
+ pub fn slerpni ( self , b : Self , t : f32 ) -> Self {
169
+ debug_assert ! ( self . is_normalized( ) , "Quaternion `self` is not normalized" ) ;
170
+ debug_assert ! ( b. is_normalized( ) , "Quaternion `b` is not normalized" ) ;
171
+ let dot = self . dot ( b) ;
172
+ if dot. abs ( ) > 0.9999 {
173
+ self
174
+ } else {
175
+ let theta = dot. acos ( ) ;
176
+ let sin_t = 1.0 / theta. sin ( ) ;
177
+ let new_f = ( t * theta) . sin ( ) * sin_t;
178
+ let inv_f = ( ( 1.0 - t) * theta) . sin ( ) * sin_t;
179
+ Self :: new (
180
+ inv_f * self . x + new_f * b. x ,
181
+ inv_f * self . y + new_f * b. y ,
182
+ inv_f * self . z + new_f * b. z ,
183
+ inv_f * self . w + new_f * b. w ,
184
+ )
185
+ }
186
+ }
187
+
188
+ /// Returns a vector transformed (multiplied) by this quaternion.
189
+ #[ inline]
190
+ pub fn xform ( self , v : Vector3 ) -> Vector3 {
191
+ self * v
192
+ }
193
+
194
+ #[ inline]
195
+ fn gd ( quat : glam:: Quat ) -> Self {
196
+ Self :: new ( quat. x , quat. y , quat. z , quat. w )
197
+ }
198
+
26
199
#[ inline]
27
200
#[ allow( unused) ]
28
201
fn glam ( self ) -> glam:: Quat {
29
202
glam:: Quat :: from_xyzw ( self . x , self . y , self . z , self . w )
30
203
}
31
204
}
205
+
206
+ impl Mul < Vector3 > for Quat {
207
+ type Output = Vector3 ;
208
+
209
+ #[ inline]
210
+ fn mul ( self , with : Vector3 ) -> Vector3 {
211
+ debug_assert ! ( self . is_normalized( ) , "Quaternion is not normalized" ) ;
212
+ Vector3 :: gd ( self . glam ( ) * with. glam ( ) )
213
+ }
214
+ }
215
+
216
+ #[ cfg( test) ]
217
+ mod test {
218
+ use super :: * ;
219
+
220
+ #[ test]
221
+ fn from_euler ( ) {
222
+ let euler = Vector3 :: new ( 0.25 , 5.24 , 3.0 ) ;
223
+ let expect = Quat :: new ( 0.485489 , 0.142796 , -0.862501 , 0.001113 ) ;
224
+ assert ! ( Quat :: from_euler( euler) . is_equal_approx( expect) ) ;
225
+ }
226
+
227
+ #[ test]
228
+ fn to_basis ( ) {
229
+ let quat = Quat :: new ( 0.485489 , 0.142796 , -0.862501 , 0.001113 ) ;
230
+ let expect = Basis :: from_elements ( [
231
+ Vector3 :: new ( -0.528598 , 0.140572 , -0.837152 ) ,
232
+ Vector3 :: new ( 0.136732 , -0.959216 , -0.247404 ) ,
233
+ Vector3 :: new ( -0.837788 , -0.245243 , 0.487819 ) ,
234
+ ] ) ;
235
+ let basis = Mat3 :: from_quat ( quat. glam ( ) ) . to_cols_array_2d ( ) ;
236
+ let basis = [
237
+ Vector3 :: new ( basis[ 0 ] [ 0 ] , basis[ 1 ] [ 0 ] , basis[ 2 ] [ 0 ] ) ,
238
+ Vector3 :: new ( basis[ 0 ] [ 1 ] , basis[ 1 ] [ 1 ] , basis[ 2 ] [ 1 ] ) ,
239
+ Vector3 :: new ( basis[ 0 ] [ 2 ] , basis[ 1 ] [ 2 ] , basis[ 2 ] [ 2 ] ) ,
240
+ ] ;
241
+ let basis = Basis :: from_elements ( basis) ;
242
+ assert ! ( basis. is_equal_approx( & expect) ) ;
243
+ }
244
+
245
+ #[ test]
246
+ fn get_euler ( ) {
247
+ let quat = Quat :: new ( 0.485489 , 0.142796 , -0.862501 , 0.001113 ) ;
248
+ let expect = Vector3 :: new ( 0.25 , -1.043185 , 3.00001 ) ;
249
+ dbg ! ( quat. get_euler( ) ) ;
250
+ assert ! ( quat. get_euler( ) . is_equal_approx( expect) ) ;
251
+ }
252
+
253
+ #[ test]
254
+ fn mul ( ) {
255
+ let quat = Quat :: new ( 0.485489 , 0.142796 , -0.862501 , 0.001113 ) ;
256
+ let vec = Vector3 :: new ( 2.2 , 0.8 , 1.65 ) ;
257
+ let expect = Vector3 :: new ( -2.43176 , -0.874777 , -1.234427 ) ;
258
+ assert ! ( expect. is_equal_approx( quat * vec) ) ;
259
+ }
260
+ }
0 commit comments