Catmull-Rom for Quaternions?

Started by
3 comments, last by JoeJ 5 years, 9 months ago

I am having a bit of a problem with my camera interpolations. I am using a catmull-rom interpolation between several camera transforms. The transforms have different locations and quaternion rotations. The catmull-rom works perfectly for the locations, and nearly perfectly for the quaternions, except when the cameras need to rotate around something in a full circle, then there is always a point at which a full backward rotation occurs, instead of the short path (after the 3/4 circle rotation point).

I am using the DirectXMath library, which has a XMQuaternionSquad method, which has the exact same parameter list that the XMVectorCatmullRom has, but for quaternions. It makes use of quaternion slerp functions in its implementation. Using this, the animation starts out somewhat good, but quickly breaks (jumps) when the chain of rotations is stepped (I have a big array of camera transforms, but only 4 of them are interpolated at once, so when the interpolation factor is > 1, then the transform chain is offset by 1). 

The MSDN documentation for the Quaternion Squad function also says that the XMQuaternionSquadSetup function needs to be used before this. But I had no success, the animation rotation keeps breaking when the chain is offset.

This is how I am trying right now:

(a, b, c, d are the transforms on the spline, t is a float between 0 and 1. With catmull-rom, the result location is always between b and c)


XMVECTOR Q1, Q2, Q3;
XMQuaternionSquadSetup(
  &Q1, &Q2, &Q3,
  XMLoadFloat4(&a->rotation),
  XMLoadFloat4(&b->rotation),
  XMLoadFloat4(&c->rotation),
  XMLoadFloat4(&d->rotation)
);
XMVECTOR R = XMQuaternionSquad(
  XMQuaternionNormalize(XMLoadFloat4(&a->rotation)),
  XMQuaternionNormalize(Q1), 
  XMQuaternionNormalize(Q2), 
  XMQuaternionNormalize(Q3),
  t
);
R = XMQuaternionNormalize(R);

Anyone had any luck getting this to work?

Advertisement
1 hour ago, turanszkij said:

says that the XMQuaternionSquadSetup function needs to be used before this

I assume what this function does is to negate individual quats so interpolation does not go along the longer arcs. (doc does not mention it, but it must be this)

1 hour ago, turanszkij said:

(a, b, c, d are the transforms on the spline, t is a float between 0 and 1. With catmull-rom, the result location is always between b and c)

I think that's your problem. More likely the range [0,1] here covers the whole path [a,b,c,d].

So to fit the convention to what you would expect, you would need to modify a and d somehow like this:

 

quat diff_ab = rotation from a to b

// eventually need to divide angle of diff_ab rotation by 3; ... not sure

quat modified_a = rotate b by diff_ab

 

quat diff_dc = rotation from d to c

// eventually need to divide angle of diff_dc rotation by 3

quat modified_d = rotate c by diff_dc

 

(i use words instead multiplications and inversions, because i don't know DX multiplication convention)

Then Setup followed by QuadSlerp (b, modified_a, modified_d, c, t) should give the expected result between b and c.

Hope this helps.

 

1 hour ago, JoeJ said:

I assume what this function does is to negate individual quats so interpolation does not go along the longer arcs. (doc does not mention it, but it must be this)

I think that's your problem. More likely the range [0,1] here covers the whole path [a,b,c,d].

So to fit the convention to what you would expect, you would need to modify a and d somehow like this:

 

quat diff_ab = rotation from a to b

// eventually need to divide angle of diff_ab rotation by 3; ... not sure

quat modified_a = rotate b by diff_ab

 

quat diff_dc = rotation from d to c

// eventually need to divide angle of diff_dc rotation by 3

quat modified_d = rotate c by diff_dc

 

(i use words instead multiplications and inversions, because i don't know DX multiplication convention)

Then Setup followed by QuadSlerp (b, modified_a, modified_d, c, t) should give the expected result between b and c.

Hope this helps.

 

Thanks, I tried to code up your suggestion using directxmath, here is what I came up with:



XMVECTOR qA = XMQuaternionNormalize(XMLoadFloat4(&a->rotation));
XMVECTOR qB = XMQuaternionNormalize(XMLoadFloat4(&b->rotation));
XMVECTOR qC = XMQuaternionNormalize(XMLoadFloat4(&c->rotation));
XMVECTOR qD = XMQuaternionNormalize(XMLoadFloat4(&d->rotation));

XMQuaternionSquadSetup(&qB, &qC, &qD, qA, qB, qC, qD);

XMVECTOR diff_ab = XMQuaternionMultiply(XMQuaternionInverse(qA), qB); // rot from a to b
diff_ab = XMQuaternionNormalize(XMVectorMultiply(diff_ab, XMVectorSet(1, 1, 1, 0.33333f))); // div angle by 3 and normalize
XMVECTOR modified_a = XMQuaternionNormalize(XMQuaternionMultiply(qB, diff_ab)); // rot b by diff_ab and normalize

XMVECTOR diff_dc = XMQuaternionMultiply(XMQuaternionInverse(qD), qC); // rot from d to c
diff_dc = XMQuaternionNormalize(XMVectorMultiply(diff_dc, XMVectorSet(1, 1, 1, 0.33333f))); // div angle by 3 and normalize
XMVECTOR modified_d = XMQuaternionNormalize(XMQuaternionMultiply(qC, diff_dc)); // rot c by diff_dc and normalize

XMVECTOR R = XMQuaternionSquad(qB, modified_a, modified_d, qC, t);

If you can't read the code well, I can't blame you. So this kind of works, the movement is not jumping around now, and kind of smooth, but not in a nice way. When reaching the next camera location, the rotation speeds up to remain on the path and then slowly blends to the next. But the rotation is always the shortest now. I made a gif:

 

camsquad.gif

1 hour ago, turanszkij said:

When reaching the next camera location, the rotation speeds up to remain on the path and then slowly blends to the next. But the rotation is always the shortest now.

Yeah, i already expected this to happen meanwhile.

The problem is we have no smooth 'tangents' at the control points, if we would compare the approach to converting a catmull rom spline to bezier spline, which is what i have in mind basically.

Should be easy to fix.

Lets say we have a catmull rom spline of N quats, and we are at control point i:

quat cpQ[N];

for (int i=1, i<N-2, i++) { 

quat diff_left = rotation from cpQ[i-1] to cpQ[i+1] // 'segment' from previous to next control point (before we used from current to next)

// divide angle by 6, because the 'segment' we use to calculate the 'tangent' now is twice as long

quat modified_left = rotate cpQ by diff_left

int j = i+1; // index for right control point

quat diff_right = rotation from cpQ[j+1] to cpQ[j-1]

// divide angle by 6

quat modified_right = rotate cpQ[j] by diff_right

... }

 

 

 

I'm not really sure this works really continuously, because i miss experience with quad slerp (and i expect some wobbling because the camera view vector likely will not be aligned to the position spline tangent exactly).

If it does not work well, you could look at this: 

 

This topic is closed to new replies.

Advertisement