Skip to content

Instantly share code, notes, and snippets.

@mhalber
Created March 31, 2019 03:56
Show Gist options
  • Save mhalber/8054459427c5a9bec32b5d42fe54fbe7 to your computer and use it in GitHub Desktop.
Save mhalber/8054459427c5a9bec32b5d42fe54fbe7 to your computer and use it in GitHub Desktop.
quaternion rotation
In this stackoverflow question[1] user ltjax provides an answer on the order of applying quaternions to
accumulate rotations. The answer makes an interesting claim that the proposed order of quaternion
multiplications:
(1) cameraOrientation = framePitch * cameraOrientation * frameYaw
is equivalent to storing the 'Yaw' and 'Pitch' angles separately. It is an interesting observation,
but no reasoning, or proof is given.
For the rest of this discussion we will assume lab 1-2-3 sequence[2], where we first roll the
body around x, then we nose it up around y(pitch), and finally rotate around z(yaw).
What we wish to show is that, for the case of just two Euler angles, converting from an accumulated
Euler Angles is in fact equal to left and right multiplying by the incremental change in the Euler
Angles.
Assuming E(roll,pitch,yaw) is a function that converts Euler into a quaternion, and we mark 'roll',
'pitch', 'yaw' as the accumulated angles, and d_roll, d_pitch, d_yaw as the incremental angle,
we wish to show following is true:
(2) E( 0, pitch, yaw ) = E( 0, d_pitch, 0) * E(0, pitch - d_pitch, yaw - d_yaw ) * E(0, 0, d_yaw)
For brevity we will now just mark 'roll', 'pitch', 'yaw' as 'x', 'y', 'z'. Similar treatment
is made for incremental angles.
In this document we will represent quaternions as [x,y,z,w], where [x,y,z] is the imaginary part and
w is the real part. We assume quaternion can be indexed as an array.
First lets define function that converts a Euler to a quaternion:
(3) E(x, y, z) = [ cos(0.5*x) * cos(0.5*y) * sin(0.5*z) - sin(0.5*x) * sin(0.5*y) * cos(0.5*z),
sin(0.5*x) * cos(0.5*y) * sin(0.5*z) + cos(0.5*x) * sin(0.5*y) * cos(0.5*z),
sin(0.5*x) * cos(0.5*y) * cos(0.5*z) - cos(0.5*x) * sin(0.5*y) * sin(0.5*z),
cos(0.5*x) * cos(0.5*y) * cos(0.5*z) + sin(0.5*x) * sin(0.5*y) * sin(0.5*z) ]
Without the loss of generality we can drop the 0.5 constant factor, to make our derivations slightly
more compact.
Secondly let us define quaternion multiplication:
(4) A*B = [ A[3]*B[0] + A[0]*B[3] + A[1]*B[2] - A[2]*B[1],
A[3]*B[1] + A[1]*B[3] + A[2]*B[0] - A[0]*B[2],
A[3]*B[2] + A[2]*B[3] + A[0]*B[1] - A[1]*B[0],
A[3]*B[3] - A[0]*B[0] - A[1]*B[1] - A[2]*B[2] ]
With all in place, we can start. Let us first calculate left hand side and repeat definition of
the right hand side ( from (3) )
(5) LHS = E(0, y, z) = [sin(z)*cos(y), sin(y)*cos(z), -sin(y)*sin(z), cos(y)*cos(z)]
(6) RHS = E(0, d_y, 0) * E(0, y-d_y, z-d_z) * E(0, 0, d_z)
Let's start calculating RHS. Using our conversion formulas
(7) E(0, d_y, 0) = [0, sin(dy), 0, cos(dy)]
(8) E(0, 0, d_z) = [sin(dz), 0, 0, cos(dz)]
(9) E(0, y-d_y, z-d_z) = [-sin(dz - z)*cos(dy - y), -sin(dy - y)*cos(dz - z), -sin(dy - y)*sin(dz - z), cos(dy - y)*cos(dz - z)]
Now let's perform first quaternion multiplication
(10) Q_A = E(0, d_y, 0) * E(0, y-d_y, z-d_z)
Let us work out the first index of Q_A:
Q_A[0] = -sin(dy)*sin(dy - y)*sin(dz - z) - sin(dz - z)*cos(dy)*cos(dy - y)
Q_A[0] = -(sin(dy)*sin(dy - y) + cos(dy)*cos(dy - y))*sin(dz - z)// Factor out the common expression
Q_A[0] = -sin(dz - z)*cos(dy-dy+y) // Use Ptolemy’s identitiy
Q_A[0] = -sin(dz - z)*cos(y) // Simplify
The rest of the components can be simplified to get:
Q_A[1] = sin(y)*cos(dz - z)
Q_A[2] = sin(y)*sin(dz - z)
Q_A[3] = cos(y)*cos(dz - z)
Now the second quaternion multiplication
(11) RHS = Q_A * E(0, 0, d_z)
Similarly to calculating q_A we have:
RHS[0] = sin(dz)*cos(y)*cos(dz - z) - sin(dz - z)*cos(dz)*cos(y)
RHS[0] = (sin(dz)*cos(dz - z) - sin(dz - z)*cos(dz))*cos(y) // Factor out the common expression
RHS[0] = sin(dz - dz + z)*cos(y) // Use Ptolemy’s identity
RHS[0] = sin(z)*cos(y) // Simplify
RHS[1] = sin(y)*cos(z)
RHS[2] = -sin(y)*sin(z)
RHS[3] = cos(y)*cos(z)
Finally, we have:
LHS = [sin(z)*cos(y), sin(y)*cos(z), -sin(y)*sin(z), cos(y)*cos(z)]
RHS = [sin(z)*cos(y), sin(y)*cos(z), -sin(y)*sin(z), cos(y)*cos(z)]
As such we see that
LHS = RHS, as intended
------------------------------
TODO / Questions:
[ ] What happens if there is roll? Provide additional derivation.
[ ] What is the advantage of the accumulating rotations in quaternion vs Euler angle accumulation? Gimbal loc
References
------------------
[1] https://gamedev.stackexchange.com/questions/30644/how-to-keep-my-quaternion-using-fps-camera-from-tilting-and-messing-up/30654
[2] https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment