Octo
Octo

Reputation: 563

Rotation accuracy error builds up too fast?

When applying rotations one after another, precision errors accumulate.

But I am surprised of how fast the error builds up.

In this example I am comparing 2 transformations that are equivalent in theory.

In practice I get 0.02 degrees error by doing just 2 rotations instead of one.

I was expecting the error to be lower.

Is there a way to make the result of these 2 transformations closer? Other than using double precision variables.

#include <glm/gtx/rotate_vector.hpp>

double RadToDeg(double rad) 
{
    return rad * 180.0 / M_PI;
}

const glm::vec3 UP(0, 0, 1);

void CompareRotations()
{
    glm::vec3 v0 = UP;
    glm::vec3 v1 = glm::normalize((glm::vec3(0.0491, 0.0057, 0.9987)));
    glm::vec3 v2 = glm::normalize((glm::vec3(0.0493, 0.0057, 0.9987)));

    glm::vec3 axis_0_to_1 = glm::cross(v0, v1);
    glm::vec3 axis_1_to_2 = glm::cross(v1, v2);
    glm::vec3 axis_global = glm::cross(v0, v2);

    float angle_0_to_1 = RadToDeg(acos(glm::dot(v0, v1)));
    float angle_1_to_2 = RadToDeg(acos(glm::dot(v1, v2)));
    float angle_global = RadToDeg(acos(glm::dot(v0, v2)));

    glm::vec3 v_step = UP;
    v_step = glm::rotate(v_step, angle_0_to_1, axis_0_to_1);
    v_step = glm::rotate(v_step, angle_1_to_2, axis_1_to_2);

    glm::vec3 v_glob = UP;
    v_glob = glm::rotate(v_glob, angle_global, axis_global);

    float angle = RadToDeg(acos(glm::dot(v_step, v_glob)));
    if (angle > 0.01)
    {
       printf("error");
    }
}

Upvotes: 1

Views: 1130

Answers (2)

fluffy
fluffy

Reputation: 5314

Floating-point multiplication isn't as precise as you think, and every time you multiply two floating-point numbers you lose precision -- quite rapidly, as you have discovered.

Generally you want to store your transforms not as the result matrix, but as the steps required to get that matrix; for example, if you are doing only a single-axis transform, you store your transform as the angle and recompute the matrix each time. However, if multiple axes are involved, this gets very complicated very quickly.

Another approach is to use an underlying representation of the transform that can itself be transformed precisely. Quaternions are very popular for this (per Michael Kenzel's answer), but another approach that can be easier to visualize is to use a pair of vectors that represent the transform in a way that you can reconstitute a normalized matrix. For example, you can think of your rotation as a pair of vectors, forward and up. From this you can compute your transformation matrix with e.g.:

z_axis = normalize(forward);
x_axis = normalize(cross(up, forward));
y_axis = normalize(cross(forward, x_axis));

and then you build your transform matrix from these vectors; given those axes and a pos for your position the (column-major) OpenGL matrix will be:

{ x_axis.x, x_axis.y, x_axis.z, 0,
  y_axis.x, y_axis.y, y_axis.z, 0,
  z_axis.x, z_axis.y, z_axis.z, 0,
  pos.x,    pos.y,    pos.z,    1 }

Similarly, you can renormalize a transform matrix by extracting the Z and Y vectors from your matrix as direction and up, respectively, and reconstructing a new matrix from them.

This does take a lot more computational complexity than using quaternions, but I find it much easier to wrap my head around.

Upvotes: 1

Michael Kenzel
Michael Kenzel

Reputation: 15941

If you just want to continue rotating along the same axis, then it would probably be best to just increment the rotation angle around that axis and recompute a new matrix from that angle every time. Note that you can directly compute a matrix for rotation around an arbitrary axis. Building rotations from Euler Angles, for example, is generally neither necessary nor a great solution (singularities, numerically not ideal, behavior not very intuitive). There is an overload of glm::rotate() that takes an axis and an angle that you could use for that.

If you really have to concatenate many arbitrary rotations around arbitrary axes, then using Quaternions to represent your rotations would potentially be numerically more stable. Since you're already using GLM, you could just use the quaternions in there. You might find this tutorial useful.

Upvotes: 3

Related Questions