1
\$\begingroup\$

I have some code that looks like this:

DirectX::XMVECTOR a, b, c;
a = DirectX::XMQuaternionIdentity();
b = DirectX::XMQuaternionRotationAxis(DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), 3.1415f);
c = DirectX::XMQuaternionRotationAxis(DirectX::XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (3.1415f * 3.0f) / 2.0f);

DirectX::XMVECTOR slerpAB = DirectX::XMQuaternionSlerp(a, b, 0.4f);
DirectX::XMVECTOR slerpBC = DirectX::XMQuaternionSlerp(b, c, 0.85f);
DirectX::XMVECTOR slerpCA = DirectX::XMQuaternionSlerp(c, a, 1.0f);

The thing is, I'm sure that slerpBC is wrong. Here's what I'm getting:

slerpBC value in debugger

If I understand it right, slerpBC should be 85% of the way towards a 3-quater turn around the Z axis. But when I plug that value in to Wolfram Alpha, it looks totally wrong.

Furthermore, when I run the code through my own Slerp code I get a totally different answer that looks more correct, to me.

So... What am I doing wrong?

\$\endgroup\$
3
  • \$\begingroup\$ slerpBC starts from a 180° rotation around the Y and then goes to a 270° rotation around the Z axis \$\endgroup\$ – ratchet freak Nov 25 '14 at 11:44
  • \$\begingroup\$ @ratchetfreak Right, so I would expect that at 0.85x the interpolation, I should be close to 270deg around Z, with a small rotation around Y. That's what I see in my own answer; but not the one DirectX gives me? \$\endgroup\$ – Xenoprimate Nov 25 '14 at 11:48
  • \$\begingroup\$ unless it goes the other way, most slerp implementations take the shorter path \$\endgroup\$ – ratchet freak Nov 25 '14 at 11:49
2
\$\begingroup\$

The quaternion you find using your method is indeed correct.

However, it’s also pretty unlikely that DirectX::XMQuaternionSlerp would behave incorrectly with such trivial input.

Thus, I suspect that the thing you are doing wrong is:

  • trusting what you see in the debugger while running potentially optimised code
  • assuming the layout of a __mm128 would be the {W,X,Y,Z} of your quaternion (DirectX quaternions are stored as XYZW so the value you plugged into Wolfram Alpha was X + Yi + Zj + Wk anyway)

So, use XMVectorGetX(slerpBC) to get and print the X value of your quaternion instead, you’ll see whether it matches what the debugger says.

\$\endgroup\$
1
  • \$\begingroup\$ That was exactly the problem, thank you. I was making a double error of assuming the quats were WXYZ and then trusting the debugger output! \$\endgroup\$ – Xenoprimate Nov 26 '14 at 10:27
2
\$\begingroup\$

By design, DirectXMath returns for XMQuaternionSlerp the same result as you'd get from the following (inefficient used only for testing) scalar code:

XMVECTOR ScalarQuatSlerp(XMVECTOR q1, XMVECTOR q2, float t)
{
    // Extract the components
    float q1x = XMVectorGetX(q1);
    float q1y = XMVectorGetY(q1);
    float q1z = XMVectorGetZ(q1);
    float q1w = XMVectorGetW(q1);
    float q2x = XMVectorGetX(q2);
    float q2y = XMVectorGetY(q2);
    float q2z = XMVectorGetZ(q2);
    float q2w = XMVectorGetW(q2);
    // Find the dot product
    float dot = (q1x * q2x) +
        (q1y * q2y) +
        (q1z * q2z) +
        (q1w * q2w);
    // Determine the direction of the rotation.
    if (dot < 0.0f) { 
        dot = -dot;
        q2x = -q2x;
        q2y = -q2y;
        q2z = -q2z;
        q2w = -q2w;
    }
    float theta = acosf(dot);
    float sintheta = sinf(theta);
    float scale1 = (sinf(theta*(1.0f-t)) / sintheta);
    float scale2 = (sinf(theta*t) / sintheta);
    // Perform the slerp.
    q1x = (q1x*scale1) + (q2x*scale2);
    q1y = (q1y*scale1) + (q2y*scale2);
    q1z = (q1z*scale1) + (q2z*scale2);
    q1w = (q1w*scale1) + (q2w*scale2);
    // Convert the scalar answer to a vector
    XMVECTOR r = XMVectorSet(q1x,q1y,q1z,q1w);
    return r;
}

which is derived from the classic quaternion paper: Shoemake, Ken. "Animating Rotation with Quaternion Curves", Compute Graphics (SIGGRAPH 1985), Volume 19, Number 3, 1985.

You should read Jonathan Blow's The Inner Product, April 2004 article on slerp: Understanding Slerp, Then Not Using It. Basically, for most applications you shouldn't use XMQuaternionSlerp. Instead use 'nlerp' which is implemented in DirectXMath as:

XMVECTOR r = XMVectorLerp( q1, q2, t );
r = XMQuaternionNormalize( r );
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your comments. I've also implemented a LerpAndNormalize and my output matches that of DirectX's. Also, it's super useful to see a reference implementation of slerp, so thank you very much! \$\endgroup\$ – Xenoprimate Nov 26 '14 at 10:28

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.