Search code examples
c++openglquaternions

glm::mix returning invalid quaternion


I am trying to implement skeletal animation into my game engine and I have a problem with the interpolation.

Each frame, I want to interpolate between frames and thus I have code similar to this:

// This is the soon-to-be-interpolated joint
Joint &finalJoint = finalSkeleton.joints[i];

// These are our existing joints
const Joint &joint0 = skeleton0.joints[i];
const Joint &joint1 = skeleton1.joints[i];

// Interpolate
finalJoint.position = glm::lerp(joint0.position, joint1.position, interpolate);
finalJoint.orientation = glm::mix(joint0.orientation, joint1.orientation, interpolate);

The final line is the problem. The orientation of finalJoint will sometimes be (-1.#IND, -1.#IND, -1.#IND, -1.#IND).

These are some example values and what they result in (note that interpolation is 0.4 in all three cases):

  • joint0.orientation = (0.707107, 0.000242, 0.707107, 0.0)
    joint1.orientation = (0.707107, 0.000242, 0.707107, 0.0)
    finalJoint = (-1.#IND, -1.#IND, -1.#IND, -1.#IND) (Incorrect)

  • joint0.orientation = (-0.451596, -0.61858, -0.262811, -0.586814)
    joint1.orientation = (-0.451596, -0.61858, -0.262811, -0.586814)
    finalJoint = (-0.451596, -0.61858, -0.262811, -0.586814) (Correct)

  • joint0.orientation = (0.449636, 0.6195, 0.26294, 0.58729)
    joint1.orientation = (0.449636, 0.6195, 0.26294, 0.58729)
    finalJoint = (-1.#IND, -1.#IND, -1.#IND, -1.#IND) (Incorrect)

(Yes, I am aware that it's interpolating between the same values.)

I have not yet grasped exactly how quaternions work, but this just seems strange to me.


Solution

  • If you look at the typical Slerp equation, you'll see that it has a Sin(Ω) in the denominator of a fraction where Ω is the angle between the two quaternions: i.e., Ω = acos(dot_product(q1,q1)); Now, Sin(0)==0; And when you divide by zero, there are problems. As mentioned in the Wikipedia article, this is a removable discontinuity, but that takes a bit of extra checking.

    I just pulled down the code to take a look. Here's the sum of it:

    T angle = acos(dot(x, y));
    return (glm::sin((T(1)-a)*angle)*x+glm::sin(a*angle)*y)/glm::sin(angle);
    

    No special checks. This is prone to have numeric problems as the angle approaches zero. It may also take the long way around the rotation sphere for some interpolations. There are two other versions of glm:::mix commented out that do a better job dealing with these problems. It looks like the current version and its accompanying problems were committed to the repository in May of 2011.

    I would recommend reverting to the version that uses linear interpolation if the angle is less than some threshold amount. Just comment out the current and uncomment the old one.