I recently had to solve the problem of extracting rotations around individual axes in 3D space for arbitrary rotations represented as quaternions. I’ve seen a lot of people around forums and so on having problems with this, same as me, but not really getting any satisfying answers, so I thought I would share my approach to the problem here.

But first a rant, hah! Converting from quaternions to Euler angles is often fine, however when approaching certain orientations the values on some axes seem to unexpectedly flip 180 degrees. It turns out that the same quaternion has two valid Euler representations with one of them being the one you would expect and the other being flipped 180 degrees on two axes, and I haven’t been able to find a reliable method of determining which is the appropriate set of Euler angles for my purpose and any other possible solution I have looked into has had similar problems.

I would have expected this to be the sort of problem that is dealt with and solved quite routinely, so if I missed something or did something wrong I would certainly like to know!

Also, suggestions to simply store and handle your rotations as Euler angles are not very useful when, for example, you are relying on a physics engine to produce your rotations, not to mention that being able do calculations using quaternions is quite essential in general!

In any case, I came up with this geo-trigonometric method of extracting some angles that was useful for my case, perhaps they could be for others as well? It all boils down to the following method:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/// <summary> /// Computes the angle-axis rotation that rotates va into the intersecting line between the planes /// of vb and vn. /// Note: In cases where vb and vn are quasiparallel angle is set to 0 as no rotation could possibly /// align va with the plane of vn as there is no intersecting line. /// </summary> /// <param name="angle">a reference to hold the angle parameter of the computed rotation</param> /// <param name="va">a normal representing the plane of rotation (must be normalized)</param> /// <param name="vb">a vector to be aligned with vn (must be normalized and must lie in vb)</param> /// <param name="vn">a normal representing the plane to rotate towards (must be normalized)</param> /// <returns>false if no rotation could be computed (and angle set to 0), otherwise true.</returns> public static bool GetAxisAligningRotation(out float angle, Vector3 va, Vector3 vb, Vector3 vn) { float vbVnDot = Vector3.Dot(va, vn); if (vbVnDot > 0.99999f || vbVnDot < -0.99999f) { angle = 0f; return false; } // Line of intersection of the planes defined by normals vb and vn. Vector3 vpin = Vector3.Cross(va, vn).normalized; float angleDot = Vector3.Dot(vb, vpin); if (angleDot >= 1f) angle = 0f; else if (angleDot <= -1f) angle = 180f; else { angle = Mathf.Acos(angleDot) * Mathf.Rad2Deg; if (Vector3.Dot(vb, vn) <= 0f) angle = -angle; } return true; } |

Using it I was able to deconstruct my quaternion orientations axis for axis and get some fairly reliable results. The method computes the intersecting line between two planes and then the angle-axis rotation that would take the supplied vector ** vb** into the line of intersection. So to calculate, for example, the pitch you would pass the right vector as

**(the normal of the plane of rotation) and the forward facing vector as**

*va***(the vector to rotate), both in the frame of the quaternion rotation, and lastly, as**

*vb***you would supply a normal facing upwards, to represent the horizontal plane. An example of this sort of rotation is seen in the image above, represented by the red arc;**

*vn*It is always nice to see how to use the code and this sort of thing is probably easier to comprehend by playing around a bit with something interactive, so I put together this small Unity project.

Cheers!