From quaternion to rotation matrix and Euler angles, and back
Inside the program we keep a quaternion. But we still want roll, pitch and yaw on the screen, and sometimes a rotation matrix. Here are the three conversions, and the one trap in them.
8.1Euler angles → quaternion
We call the three angles roll φ (phi, about x), pitch θ (theta, about y) and yaw ψ (psi, about z). Careful: here θ is the pitch angle, not the turn angle of lesson 4.
We multiply the three single turns from lesson 7, \(q = q_{yaw} \otimes q_{pitch} \otimes q_{roll}\), and write it out. With \(c_\phi = \cos\frac{\phi}{2}\), \(s_\phi = \sin\frac{\phi}{2}\) and the same for θ and ψ:
Check with a simple case. Only yaw: roll and pitch are zero, so \(c_\phi = c_\theta = 1\) and \(s_\phi = s_\theta = 0\). What is left is \(q = [c_\psi, 0, 0, s_\psi] = [\cos\frac{\psi}{2}, 0, 0, \sin\frac{\psi}{2}]\), a turn about z, as it should be.
8.2Quaternion → rotation matrix
v_world = R · v_object. Row 1 gives the new x: R₁₁v_x + R₁₂v_y + R₁₃v_z. Rows 2 and 3 give the new y and z the same way.
A nice way to read R: each column is one axis of the object, written in world coordinates. Column 1 is where the object’s x axis points.
Where does it come from? We turn the three unit arrows (1, 0, 0), (0, 1, 0) and (0, 0, 1) with \(q \otimes v \otimes q^*\) from lesson 6. The three results are the three columns.
A simple case. Take a turn by ψ about z only: w = cos(ψ/2), z = sin(ψ/2), x = y = 0. Most entries vanish, and with the double-angle rules \(1 - 2\sin^2\frac{\psi}{2} = \cos\psi\) and \(2\sin\frac{\psi}{2}\cos\frac{\psi}{2} = \sin\psi\) we get the 2D turn formula from lesson 3, with z untouched:
8.3Quaternion → Euler angles
We read the angles from the matrix. Written with roll φ, pitch θ and yaw ψ, the ZYX rotation matrix has a few very simple entries:
R₃₁ is row 3, column 1. Dividing R₃₂ by R₃₃ leaves tan φ, and R₂₁ by R₁₁ leaves tan ψ.
So pitch comes from R₃₁ alone, roll from R₃₂ and R₃₃, yaw from R₂₁ and R₁₁. Now we replace each entry by its quaternion form from 8.2. We use atan2(a, b): it is the angle whose tangent is a/b, but it also looks at the signs of a and b, so it covers the full range from −180° to 180°.
Because of rounding, \(2(wy - zx)\) can come out as 1.0000001. Then arcsin has no answer. In C, asinf returns NaN, "not a number", and every later result becomes NaN too. So we always limit the value to the range −1 to 1 first. This one line prevents a bug that is hard to find.
And at pitch ±90° the gimbal lock from lesson 1 is back, but only here, in the conversion for display. Roll and yaw turn about the same line there, so only their combined turn can be found. Our code then returns roll = 0 and puts the whole turn into yaw. The quaternion itself never has this problem. So the rule is simple: keep q inside the program, convert to Euler angles only to show them.
✓Check yourself
q = [1, 0, 0, 0]. What is R?
The identity matrix: the object’s axes are the world axes.
Why use atan2 and not atan for roll and yaw?
atan2 looks at the signs of both inputs, so it gives the full −180° to 180° range. atan only gives −90° to 90°.
+Sources and further reading
- J. Diebel, "Representing Attitude: Euler Angles, Unit Quaternions, and Rotation Vectors", Stanford University, 2006
all conversions between Euler angles, matrices and quaternions. It uses the opposite, world-to-object direction, so its matrices are the transpose of ours - H. A. Hashim, "Special Orthogonal Group SO(3), Euler Angles, Angle-axis, Rodriguez Vector and Unit-Quaternion: Overview, Mapping and Challenges", 2019
conversion tables