Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I use the XNA Animation Component library to perform blending between animations. It uses spherical linear interpolation between matrices.

In most cases this works without problems. However, I have one model where all of the animations are blended wrong. Weird rotation and warping of the model occurs.

I was told by a math expert that the problem may lie in the method that does the matrix interpolation. He said: "The right way to do it would be to decompose A=UP (polar decomposition), obtain quaternion from U and lerp/slerp it, while interpolating P component-wise. I'm not sure why the original author refrained from implementing the polar decomposition as its implementation is straightforward and easy."

I have included the code below.

I need some help in making the recommended changes. I am an expert in C# but know almost nothing about matrix interpolation.

private static Quaternion qStart, qEnd, qResult;
private static Vector3 curTrans, nextTrans, lerpedTrans;
private static Vector3 curScale, nextScale, lerpedScale;
private static Matrix startRotation, endRotation;
private static Matrix returnMatrix;

public static void SlerpMatrix(
        ref Matrix start, 
        ref Matrix end,
        float slerpAmount,
        out Matrix result)
    {
        if (start == end)
        {
            result = start;
            return;
        }
        // Get rotation components and interpolate (not completely accurate but I don't want 
        // to get into polar decomposition and this seems smooth enough)
        Quaternion.CreateFromRotationMatrix(ref start, out qStart);
        Quaternion.CreateFromRotationMatrix(ref end, out qEnd);
        Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult);

        // Get final translation components
        curTrans.X = start.M41;
        curTrans.Y = start.M42;
        curTrans.Z = start.M43;
        nextTrans.X = end.M41;
        nextTrans.Y = end.M42;
        nextTrans.Z = end.M43;
        Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans);

        // Get final scale component
        Matrix.CreateFromQuaternion(ref qStart, out startRotation);
        Matrix.CreateFromQuaternion(ref qEnd, out endRotation);
        curScale.X = start.M11 - startRotation.M11;
        curScale.Y = start.M22 - startRotation.M22;
        curScale.Z = start.M33 - startRotation.M33;
        nextScale.X = end.M11 - endRotation.M11;
        nextScale.Y = end.M22 - endRotation.M22;
        nextScale.Z = end.M33 - endRotation.M33;
        Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale);

        // Create the rotation matrix from the slerped quaternions
        Matrix.CreateFromQuaternion(ref qResult, out result);

        // Set the translation
        result.M41 = lerpedTrans.X;
        result.M42 = lerpedTrans.Y;
        result.M43 = lerpedTrans.Z;

        // Add the lerped scale component
        result.M11 += lerpedScale.X;
        result.M22 += lerpedScale.Y;
        result.M33 += lerpedScale.Z;
    }
share|improve this question
    
Have you tried Slerp while in the quaternion form? Works great for my animations. You can see an example in the source code for JMonkey 3D. (It's in Java, but I'm sure you'll get the gist of it for C#) –  Byte56 Dec 20 '12 at 22:51
    
Also, since it's just one model, you may want to make sure that it's not being exported in a strange way. I had a model that had one of the bones rotation set to Euler instead of Quaternion. All of its rotations were funky as a result. –  Byte56 Dec 20 '12 at 23:08

2 Answers 2

up vote 3 down vote accepted

The scale computation looks wrong to me. It should be obtained by dividing, not subtracting (with additional checks for divisions by zero), then it should be applied by multiplying (not adding) each column or each row of the final rotation matrix (not just the diagonal term).

If you're fluent in C# you probably have realised that the code is very poor quality. Reading and writing to static member variables from a static method instead of local variables is a good indication of code smell.

Anyway, here is a much simpler version which makes use of the decomposition methods available in XNA:

public static void SlerpMatrix(
        ref Matrix start, 
        ref Matrix end,
        float slerpAmount,
        out Matrix result)
    {
        Quaternion qStart, qEnd, qResult;
        Vector3 curTrans, nextTrans, lerpedTrans, curScale, nextScale, lerpedScale;

        start.Decompose(out curScale, out qStart, out curTrans); 
        end.Decompose(out nextScale, out qEnd, out nextTrans); 

        Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult);
        Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans);
        Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale);

        result = Matrix.CreateScale(lerpedScale)
               * Matrix.CreateFromQuaternion(qResult)
               * Matrix.CreateTranslation(lerpedTrans);
    }
share|improve this answer
    
This actually worked! All the weird issues went away. Amazing. Thank you so much! –  a man Dec 21 '12 at 12:03

Hard to tell from just the code, but a common issue with quaternion interpolation is that 2 (antipodal) quaternions represent the same rotation. If matrix -> quaternion ever returns the 'wrong' one in one of the two cases, your lerp is going to put you at 0 which will lead to all sorts of nonsense. Also your scale calc looks bogus as Sam mentioned ;)

share|improve this answer
1  
just to add the solution to the problem: in case the dot product of the 2 input quats is less than 0 (on different sides of the hemisphere) just negate all components of one quat, then they are on the same hemisphere again and slerp will take the shorter arc –  Maik Semder Dec 21 '12 at 12:01

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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