Skip to main content
4 of 4
added 3 characters in body
hatch22
  • 166
  • 1
  • 4

This is a late response, but I figured this question illustrates a common problem that many people are likely to run into and that deserves an answer.

Quaternion rotation uses half the angle you want to rotate by. Since you (in this example case) are rotating by 90 degrees, the quaternion needs to calculate the sine and cosine of 45 degrees, both of which are sqrt(2)/2.0 = 0.7071.... Since this is irrational you are guaranteed to get some round-off error, as well as for any value other than 0 or 180 degrees, or some integer multiple of them. (see Niven's Theorem).

Calculating the inverse is even worse, since this requires division by the squared norm of the 4 components, and even though the norm of a unit quaternion is supposed to be 1, the round-off error from the sines and cosines used internally to calculate the quaternion components often make it not be 1, but just "close to" 1. This usually gets worse with repeated inversions.

So losing precision is generally unavoidable if you're using limited precision numbers like float or double. However, there is still hope. One strategy to help prevent the repeated inversions getting worse and worse is to re-normalize the quaternion after each inversion (or after a few of them if you can tolerate more error).

public static class QuaternionExtensions { public static Quaternion Normalize(this Quaternion q) { Quaternion result; float sq = q.x * q.x; sq += q.y * q.y; sq += q.z * q.z; sq += q.w * q.w; //detect badness assert(sq > 0.1f); float inv = 1.0f / sqrt(sq); result.x = q.x * inv; result.y = q.y * inv; result.z = q.z * inv; result.w = q.w * inv; return result; } } 

which I borrowed from the Physics for Games article Quaternions: How.

This keeps the precision loss from growing out of control. See also this StackOverflow question.

So your final code should look something like this:

Quaternion rotation = Quaternion.Euler (0f, 90f, 0f); Debug.Log ("Before: " + rotation.ToString ("F7") + " / Euler: " + rotation.eulerAngles.ToString()); int iterations = 1; for (int i = 0; i < iterations; i++) { var currentRotation = rotation; rotation = Quaternion.Inverse (currentRotation) * rotation; rotation = Quaternion.Normalize (currentRotation * rotation); } Debug.Log ("After: " + rotation.ToString ("F7") + " / Euler: " + rotation.eulerAngles.ToString()); 

but I haven't actually tested this.

hatch22
  • 166
  • 1
  • 4