Skip to content

Commit 9d9232c

Browse files
committed
add a euler_angles method to Quaternions and fix some bugs
1 parent bc72386 commit 9d9232c

File tree

9 files changed

+163
-50
lines changed

9 files changed

+163
-50
lines changed

examples/quaternions.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ fn main() {
4242
let x = V3::x_axis();
4343
// quaternion represent the rotation around the z axis 90 degrees, the angle
4444
// is encoded in the vector norm: [0, 0, 90]
45-
let q = Quaternion::rotation_norm_encoded(V3::z_axis() * 90.0);
45+
let q = Quaternion::rotation_norm_encoded(V3::z_axis() * 90.0f32.to_radians());
4646
let r = q * q * q * q * x;
4747
println!("r: {:}", r);
48+
//-------------------------------------------------------------------------
49+
// Quaternions and euler angles
50+
//-------------------------------------------------------------------------
51+
let q = Quaternion::from_euler_angles(0.1, 0.2, 0.3);
52+
let euler_angles = q.to_euler_angles();
53+
// this would have to give the same value :)
54+
println!("euler_angles: {:?}", euler_angles);
4855
}

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,3 @@ pub mod errors;
4646
pub mod slices_methods;
4747
pub mod quaternion;
4848
pub mod transformations;
49-
pub mod constants;

src/matrix5x5.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ impl<T: Num + Copy> M55<T> {
802802
index += 1;
803803
}
804804
}
805-
return result;
805+
result
806806
}
807807

808808
pub fn get_rows(self) -> V5<V5<T>> {

src/matrix6x6.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1563,7 +1563,7 @@ impl<T: Num + Copy> M66<T> {
15631563
index += 1;
15641564
}
15651565
}
1566-
return result;
1566+
result
15671567
}
15681568

15691569
pub fn get_rows(self) -> V6<V6<T>> {

src/quaternion.rs

Lines changed: 148 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@
2929
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3030
//-------------------------------------------------------------------------
3131
use std::ops::{Mul, Add, Sub, Neg, Div};
32-
use num::{Num, Float, Signed};
32+
use num::{Num, Float, Signed, Zero, One};
33+
use num::traits::FloatConst;
3334

3435
use crate::vector3::*;
3536
use crate::matrix3x3::M33;
3637

37-
// NOTE(elsuizo:2021-04-13): vamos a tomar la idea de poner un flag para saber
38-
// si el Quaternion es un unit quaternion
38+
// TODO(elsuizo:2021-04-14): missing methods:
39+
// - [ ] quaternion -> euler_angles
40+
3941
/// Quaternion type
40-
#[derive(Copy, Debug, Clone)]
42+
#[derive(Copy, Debug, Clone, PartialEq)]
4143
pub struct Quaternion<T> {
4244
/// Scalar part
4345
pub q0: T,
@@ -49,15 +51,15 @@ pub struct Quaternion<T> {
4951

5052
impl<T> Quaternion<T> {
5153

54+
/// construct a new Quaternion from a number(real part) and a vector(imag part)
5255
pub const fn new(q0: T, q: V3<T>) -> Self {
5356
Self {q0, q, normalized: false}
5457
}
5558

59+
/// construct a new Quaternion from four numbers
5660
pub fn new_from(q0: T, q1: T, q2: T, q3: T) -> Self {
5761
Self {q0, q: V3::new([q1, q2, q3]), normalized: false}
5862
}
59-
60-
6163
}
6264

6365
impl<T: Num + Copy> Quaternion<T> {
@@ -76,20 +78,54 @@ impl<T: Num + Copy> Quaternion<T> {
7678
self.q
7779
}
7880

81+
/// construct a unit Quaternion
82+
pub fn one() -> Quaternion<T> {
83+
<Quaternion<T> as One>::one()
84+
}
85+
86+
/// construct a zero Quaternion
7987
pub fn zero() -> Self {
80-
Self::new(T::zero(), V3::zeros())
88+
<Quaternion<T> as Zero>::zero()
8189
}
8290

91+
/// construct a pure "real" Quaternion
8392
pub fn new_real(q0: T) -> Self {
8493
Self {q0, q: V3::zeros(), normalized: true}
8594
}
95+
96+
/// construct a pure "imaginary" Quaternion
97+
pub fn new_imag(q: V3<T>) -> Self {
98+
Self {q0: T::zero(), q, normalized: false}
99+
}
100+
101+
/// calculate the abs2 of the Quaternion
102+
pub fn abs2(&self) -> T {
103+
self.q0 * self.q0 + self.q[0] * self.q[0] + self.q[1] * self.q[1] + self.q[2] * self.q[2]
104+
}
105+
}
106+
107+
impl<T: Num + Copy> Zero for Quaternion<T> {
108+
fn zero() -> Self {
109+
Self::new(T::zero(), V3::zeros())
110+
}
111+
112+
fn is_zero(&self) -> bool {
113+
*self == Quaternion::zero()
114+
}
115+
}
116+
117+
impl<T: Num + Copy> One for Quaternion<T> {
118+
/// Create an identity Quaternion
119+
fn one() -> Self {
120+
let one = T::one();
121+
Self::new(one, V3::zeros())
122+
}
86123
}
87124

88125
// q + q
89126
impl<T: Num + Copy> Add for Quaternion<T> {
90127
type Output = Self;
91128
fn add(self, rhs: Self) -> Self {
92-
// Self{q0: self.q0 + rhs.q0, q: self.q + rhs.q}
93129
Self::new(self.q0 + rhs.q0, self.q + rhs.q)
94130
}
95131
}
@@ -98,7 +134,6 @@ impl<T: Num + Copy> Add for Quaternion<T> {
98134
impl<T: Num + Copy> Sub for Quaternion<T> {
99135
type Output = Self;
100136
fn sub(self, rhs: Self) -> Self {
101-
// Self{q0: self.q0 - rhs.q0, q: self.q - rhs.q}
102137
Self::new(self.q0 + rhs.q0, self.q + rhs.q)
103138
}
104139
}
@@ -110,7 +145,6 @@ impl<T: Num + Copy> Div<T> for Quaternion<T> {
110145
fn div(self, rhs: T) -> Self::Output {
111146
let q0 = self.q0 / rhs;
112147
let q = self.q / rhs;
113-
// Self{q0, q}
114148
Self::new(q0, q)
115149
}
116150
}
@@ -122,7 +156,6 @@ impl<T: Num + Copy> Mul for Quaternion<T> {
122156
fn mul(self, rhs: Self) -> Self::Output {
123157
let q0 = self.q0 * rhs.q0 - self.q * rhs.q;
124158
let q = rhs.q * self.q0 + self.q * rhs.q0 + self.q.cross(rhs.q);
125-
// Self{q0, q}
126159
Self::new(q0, q)
127160
}
128161
}
@@ -144,27 +177,41 @@ impl<T: Num + Copy + Signed> Neg for Quaternion<T> {
144177
type Output = Self;
145178
#[inline]
146179
fn neg(self) -> Self {
147-
// Self{q0: -self.q0, q: -self.q}
148180
Self::new(-self.q0, -self.q)
149181
}
150182
}
151183

152184
impl<T: Num + Copy + Signed> Quaternion<T> {
153185
pub fn conj(&self) -> Self {
154-
// Self{q0: self.q0, q: -self.q}
155186
Self::new(self.q0, -self.q)
156187
}
157188
}
158189

159-
impl<T: Float> Quaternion<T> {
190+
impl<T: Float + Signed> Quaternion<T> {
191+
pub fn inv(&self) -> Option<Self> {
192+
if !self.normalized {
193+
let norm_sqr = self.dot(*self);
194+
if norm_sqr > T::epsilon() {
195+
Some(self.conj() / self.abs2())
196+
} else {
197+
None
198+
}
199+
} else {
200+
Some(self.conj())
201+
}
202+
}
203+
}
204+
205+
// TODO(elsuizo:2021-04-14): a warning from here but i dont known why???
206+
impl<T: Float + FloatConst> Quaternion<T> {
160207
/// normalize the Quaternion only if necesary
161208
pub fn normalize(&self) -> Option<Self> {
162209
if self.normalized {
163210
Some(*self)
164211
} else {
165212
let norm_sqr = self.dot(*self);
166-
let mut result = Self::zero();
167213
if norm_sqr > T::epsilon() {
214+
let mut result = Self::zero();
168215
result = *self / self.dot(*self).sqrt();
169216
result.normalized = true;
170217
Some(result)
@@ -174,13 +221,14 @@ impl<T: Float> Quaternion<T> {
174221
}
175222
}
176223

224+
177225
/// generate a Quaternion that represents a rotation of a angle `theta`
178226
/// around the axis(normalized) `v`
179227
pub fn rotation(theta: T, v: V3<T>) -> Self {
180228
let two = T::from(2u8).unwrap();
181229
let n = v.normalize().expect("the input has to be a non zero vector");
182-
let q0 = (theta.to_radians() / two).cos();
183-
let q = n * (theta.to_radians() / two).sin();
230+
let q0 = (theta / two).cos();
231+
let q = n * (theta / two).sin();
184232
Self{q0, q, normalized: true}
185233
}
186234

@@ -192,8 +240,8 @@ impl<T: Float> Quaternion<T> {
192240
let two = T::from(2.0).unwrap();
193241
let theta = v.norm2();
194242
if theta > T::epsilon() {
195-
let s = T::sin(theta.to_radians() / two) / theta;
196-
Self::new(T::cos(theta.to_radians() / two), v * s)
243+
let s = T::sin(theta / two) / theta;
244+
Self::new(T::cos(theta / two), v * s)
197245
} else {
198246
Self::new(one, V3::zeros())
199247
}
@@ -213,21 +261,26 @@ impl<T: Float> Quaternion<T> {
213261
two*q1*q3 - two*q0*q2, two*q2*q3 + two*q0*q1, q0_s - q1_s - q2_s + q3_s)
214262
}
215263

264+
// NOTE(elsuizo:2021-04-14): this implementation avoid the use of sqrt(which is expensive
265+
// computationally)
266+
// TODO(elsuizo:2021-04-14): maybe here we could acept a tuple (T, T, T)
216267
/// create a quaternion that represents the rotation from a Euler angles
217268
/// with the roll-pitch-yay convention
218269
pub fn from_euler_angles(yay: T, pitch: T, roll: T) -> Self {
219270
let two = T::one() + T::one();
220-
let cy = T::cos(yay.to_radians() / two);
221-
let sy = T::sin(yay.to_radians() / two);
222-
let cp = T::cos(pitch.to_radians() / two);
223-
let sp = T::sin(pitch.to_radians() / two);
224-
let cr = T::cos(roll.to_radians() / two);
225-
let sr = T::sin(roll.to_radians() / two);
226-
227-
let q0 = cr * cp * cy + sr * sp * sy;
228-
let q1 = sr * cp * cy - cr * sp * sy;
229-
let q2 = cr * sp * cy + sr * cp * sy;
230-
let q3 = cr * cp * sy - sr * sp * cy;
271+
let c1 = T::cos(yay / two);
272+
let s1 = T::sin(yay / two);
273+
let c2 = T::cos(pitch / two);
274+
let s2 = T::sin(pitch / two);
275+
let c3 = T::cos(roll / two);
276+
let s3 = T::sin(roll / two);
277+
let c1c2 = c1 * c2;
278+
let s1s2 = s1 * s2;
279+
280+
let q0 = c1c2 * c3 - s1s2 * s3;
281+
let q1 = c1c2 * s3 + s1s2 * c3;
282+
let q2 = s1 * c2 * c3 + c1 * s2 * s3;
283+
let q3 = c1 * s2 * c3 - s1 * c2 * s3;
231284

232285
Self::new_from(q0, q1, q2, q3)
233286
}
@@ -248,6 +301,43 @@ impl<T: Float> Quaternion<T> {
248301
None
249302
}
250303
}
304+
305+
// NOTE(elsuizo:2021-04-14): this implementation is a translation from this
306+
// webpage from Martin John Baker.
307+
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm
308+
//
309+
pub fn to_euler_angles(&self) -> (T, T, T) {
310+
let one = T::one();
311+
let two = one + one;
312+
let q0_2 = self.q0 * self.q0;
313+
let q1_2 = self.q[0] * self.q[0];
314+
let q2_2 = self.q[1] * self.q[1];
315+
let q3_2 = self.q[2] * self.q[2];
316+
// if the Quaternion is normalized this is one, otherwise is a correction factor
317+
let unit = q0_2 + q1_2 + q2_2 + q3_2;
318+
let test = self.q[0] * self.q[1] + self.q[2] * self.q0;
319+
let sing_number = one / two;
320+
if test - sing_number * unit > T::epsilon() {
321+
let yay = two * T::atan2(self.q[0], self.q0);
322+
let pitch = T::FRAC_PI_2();
323+
let roll = T::zero();
324+
return (yay, pitch, roll);
325+
}
326+
if test + sing_number * unit < T::epsilon() {
327+
let yay = -two * T::atan2(self.q[0], self.q0);
328+
let pitch = -one * T::FRAC_PI_2();
329+
let roll = T::zero();
330+
return (yay, pitch, roll);
331+
}
332+
let aux1 = two * self.q[1] * self.q0 - two * self.q[0] * self.q[2];
333+
let aux2 = q1_2 - q2_2 - q3_2 + q0_2;
334+
let yay = T::atan2(aux1, aux2);
335+
let pitch = T::asin(two * test / unit);
336+
let aux3 = two * self.q[0] * self.q0 - two * self.q[1] * self.q[2];
337+
let aux4 = -q1_2 + q2_2 - q3_2 + q0_2;
338+
let roll = T::atan2(aux3, aux4);
339+
(yay, pitch, roll)
340+
}
251341
}
252342

253343
//-------------------------------------------------------------------------
@@ -317,9 +407,10 @@ mod test_quaternion {
317407
assert_eq!(result_float.q[2], -1.0);
318408
}
319409

410+
// NOTE(elsuizo:2021-04-14): we assume all the values of the angles in radians!!!
320411
#[test]
321412
fn rotate_vec() {
322-
let q1 = Quaternion::rotation(90.0, V3::new_from(0.0, 0.0, 1.0));
413+
let q1 = Quaternion::rotation(90.0f32.to_radians(), V3::new_from(0.0, 0.0, 1.0));
323414
let x = V3::new_from(1.0, 0.0, 0.0);
324415
// rotate x around z 90 degrees
325416
let result = q1 * x;
@@ -331,7 +422,7 @@ mod test_quaternion {
331422

332423
#[test]
333424
fn rotate_vec_composition_360() {
334-
let q1 = Quaternion::rotation(90.0, V3::new_from(0.0, 0.0, 1.0));
425+
let q1 = Quaternion::rotation(90.0f32.to_radians(), V3::new_from(0.0, 0.0, 1.0));
335426
let x = V3::new_from(1.0, 0.0, 0.0);
336427
// rotate x around z (90 * 4 = 360) degrees
337428
let result = q1 * q1 * q1 * q1 * x;
@@ -342,7 +433,7 @@ mod test_quaternion {
342433

343434
#[test]
344435
fn rotate_vec_angle_encode() {
345-
let q = Quaternion::rotation_norm_encoded(V3::new_from(0.0, 0.0, 90.0));
436+
let q = Quaternion::rotation_norm_encoded(V3::new_from(0.0, 0.0, 90.0f32.to_radians()));
346437
let x = V3::x_axis();
347438
let result = q * x;
348439
let expected = V3::new_from(0.0, -1.0, 0.0);
@@ -353,7 +444,7 @@ mod test_quaternion {
353444

354445
#[test]
355446
fn convert_dcm_test() {
356-
let q = Quaternion::rotation_norm_encoded(V3::new_from(0.0, 0.0, 90.0));
447+
let q = Quaternion::rotation_norm_encoded(V3::new_from(0.0, 0.0, 90.0f32.to_radians()));
357448
let x = V3::x_axis();
358449
// rotate the x around z axis 360 degrees
359450
let expected = q * q * q * q * x;
@@ -366,4 +457,27 @@ mod test_quaternion {
366457
assert!(compare_floats(result[1], expected[1], EPS));
367458
assert!(compare_floats(result[2], expected[2], EPS));
368459
}
460+
461+
#[test]
462+
fn inverse_test() {
463+
let q = Quaternion::new_from(1.0, 1.0, 1.0, 10.0);
464+
if let Some(inv) = q.inv() {
465+
let result = q * inv;
466+
let expected = Quaternion::one();
467+
assert!(compare_floats(result.q0, expected.q0, EPS));
468+
assert!(compare_floats(result.q[0], expected.q[0], EPS));
469+
assert!(compare_floats(result.q[1], expected.q[1], EPS));
470+
assert!(compare_floats(result.q[2], expected.q[2], EPS));
471+
}
472+
}
473+
474+
#[test]
475+
fn euler_and_quaternions() {
476+
let expected = (0.1, 0.2, 0.3);
477+
let q = Quaternion::from_euler_angles(expected.0, expected.1, expected.2);
478+
let result = q.to_euler_angles();
479+
assert!(compare_floats(result.0, expected.0, EPS));
480+
assert!(compare_floats(result.1, expected.1, EPS));
481+
assert!(compare_floats(result.2, expected.2, EPS));
482+
}
369483
}

src/transformations.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
//-------------------------------------------------------------------------
3131
use crate::matrix2x2::M22;
3232
use crate::matrix3x3::M33;
33-
use crate::matrix4x4::M44;
34-
use crate::vector3::V3;
33+
// use crate::matrix4x4::M44;
34+
// use crate::vector3::V3;
3535

36-
use num::{Float, Zero};
36+
use num::{Float};
3737

3838
///
3939
/// Euler sequences conventions of rotations

0 commit comments

Comments
 (0)