4
\$\begingroup\$

I'm trying to un-rotate a quaternion, aligning it with the axis, and then rotate it back to where it was originally. But with every iteration it seems to lose precision and just after 20 iterations the rotation disappears. Here is an example, which is a simplification of my code:

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 = currentRotation * rotation;
}

Debug.Log ("After:  " + rotation.ToString ("F7") + " / Euler: " +
  rotation.eulerAngles.ToString());

With 1 iteration the output is:

Before: (0.0000000, 0.7071068, 0.0000000, 0.7071068) / Euler: (0.0, 90.0, 0.0)
After:  (0.0000000, 0.7071067, 0.0000000, 0.7071067) / Euler: (0.0, 90.0, 0.0)

With 5 iterations:

Before: (0.0000000, 0.7071068, 0.0000000, 0.7071068) / Euler: (0.0, 90.0, 0.0)
After:  (0.0000000, 0.7071019, 0.0000000, 0.7071019) / Euler: (0.0, 90.0, 0.0)

With 10 iterations:

Before: (0.0000000, 0.7071068, 0.0000000, 0.7071068) / Euler: (0.0, 90.0, 0.0)
After:  (0.0000000, 0.7059279, 0.0000000, 0.7059279) / Euler: (0.0, 90.0, 0.0)

With 15 iterations:

Before: (0.0000000, 0.7071068, 0.0000000, 0.7071068) / Euler: (0.0, 90.0, 0.0)
After:  (0.0000000, 0.4714038, 0.0000000, 0.4714038) / Euler: (0.0, 90.0, 0.0)

And finally with 19 iterations

Before: (0.0000000, 0.7071068, 0.0000000, 0.7071068) / Euler: (0.0, 90.0, 0.0)
After:  (0.0000000, 0.0000000, 0.0000000, 0.0000000) / Euler: (0.0, 0.0, 0.0)

At the 19th iteration the euler angles becomes zero, and at previous iterations altough the euler angles are still correct, if I rotate a vector with the quaternion its magnitude is changed.

I hope you can help me and sorry for my bad english!

\$\endgroup\$
4
\$\begingroup\$

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 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.

\$\endgroup\$
  • \$\begingroup\$ Great answer. After 8 months of waiting I’ve finally come to fully understand the why of this problem. I tested your code (did some minor syntax changes) and worked like a charm. Now I'm just wondering if this workaround has any drawback in performance... but anyway at the moment is the best solution I have. My thanks to you hatch22. \$\endgroup\$ – Celtc Jan 9 '15 at 0:07
  • \$\begingroup\$ Glad I could help. I'm hoping for the necromancer badge :-) \$\endgroup\$ – hatch22 Jan 9 '15 at 0:10
  • \$\begingroup\$ If you are concerned about performance, be sure to read up on using quaternions in game code, including the StackOverflow question I linked. GameDev.net has some good articles and forum posts on smart use of quaternions. \$\endgroup\$ – hatch22 Jan 12 '15 at 19:55
-1
\$\begingroup\$

Seeing your code, I think it would be better to store the two rotations and switch between them

Rotation firstRotation; // Stores the initial value
Rotation rotationAmount; // Stores the rotation tick per loop
bool isRotated = false; // False is the current rotation is the initial value, true if it's the new one
int iterations = 20;
for (int i = 0; i < iterations; i++) {
    if (!isRotated) {
        transform.rotation = transform.rotation * rotationAmount;
        isRotated = true;
    } else {
        transform.rotation = firstRotation;
        isRotated = false;
    }
}

Also you may want to provide some context as to why you are doing the same rotation 20 times in the same for loop (which effectively shouldn't do anything) what is your objective?

\$\endgroup\$
  • \$\begingroup\$ This is a fine workaround, but doesn't directly address the problem. The compound rotation problem is common and may appear in a scenario that your solution doesn't cover. Also, I suspect the OP was simply providing an sscce, which every question should do, but it may not exactly reflect the real use case. \$\endgroup\$ – Seth Battin May 3 '14 at 20:08
  • \$\begingroup\$ Thanks to both replies. As Seth said, the example I provided is an sscce. The context of my problem is that, I'm creating an OBB (oriented bounding box). And when I rotate it repeatedly (specially rotate and unrotate), because of the rotation of the object to which is attached, the rotation of this OBB gets messed up. As a temporal workaround I'm using euler angles instead of quaternions. But I'm afraid of getting a gimbal lock at any moment. \$\endgroup\$ – Celtc May 3 '14 at 23:09
  • \$\begingroup\$ Maybe don't make rotation in every iteration but try to accumulate angles and then after loop make only one rotation from original orientation. I made something like that in my program for lathed objects (I have also problem with precision) \$\endgroup\$ – Harry May 4 '14 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.