0
\$\begingroup\$

I have a rotation represented by a quaternion and I want to get a rotation angle around the forward axis together with another quaternion which will together represent the original rotation.

The reason for this is that I am interpolating the rotation along a path and I want to be able to interpolate the rotation around the path in such a way that it does not have any discontinuities or sudden flips.

\$\endgroup\$
2
  • \$\begingroup\$ Can you clarify whether the forward axis we're computing a twist angle around is the direction forward along the path? Or is it the local forward axis of a rotating object that's allowed to look at an angle away from the path? \$\endgroup\$
    – DMGregory
    Commented Sep 27, 2018 at 11:47
  • \$\begingroup\$ It is a twist angle along the path, yes. The 'object' is always looking forward along the path. \$\endgroup\$
    – oh_cripes
    Commented Sep 27, 2018 at 13:43

1 Answer 1

0
\$\begingroup\$

Avoiding discontinuities is a bit tricky for reasons outlined in this Q&A - if we try to strictly control, say, the up direction to align as closely as possible with world up, then we'll have to flip somewhere if our rotation does a vertical loop-de-loop, reversing its relationship with the world up axis suddenly.

Instead, let's attach a reference "up" vector to each interpolated point, perpendicular to current local forward vector, and we'll try to align it with world up when we can.

You can choose your first point's reference "up" vector arbitrarily (eg. the local up vector of the initial rotation, so your twist angle starts at zero)

For each subsequent point, we can choose our reference vector as:

sourceUp = previousReferenceUp + untwist * dot(previousReferenceUp, worldUp) * worldUp;

referenceRight = normalize(cross(sourceUp, localForward));

referenceUp = cross(localForward, referenceRight);

Here I'm assuming left-handed coordinate system, with untwist being a small tuning coefficient (eg. 0.01) to to control how much our reference up seeks vertical in non-vertical sections.

Now we can compute an angular twist about the forward axis relative to this referenceUp direction as:

localUp = rotation * worldUp;
angle = atan2(-dot(referenceRight, localUp), dot(referenceUp, localUp));

Our twist quaternion can then be expressed as:

// Remember to convert to degrees if expected by your Quaternion library (eg. Unity's)
twist = Quaternion.AngleAxis(angle, worldForward);

We can compute its inverse (ie. negate the x, y, z components):

untwist = Quaternion.Inverse(twist); 

And then compute our untwisted remaining rotation as:

untwisted = rotation * untwist;

Then for any vector v, the result of rotating that vector by our twist angle then by the remaining untwisted rotation is equivalent to rotating the vector by our original quaternion:

rotation * v == untwisted * twist * v;
\$\endgroup\$
3
  • \$\begingroup\$ Thanks, I think this might help solve my problem, the only worry is my particular problem case is a loop-de-loop where the up is obviously changing. I was hoping to do something like what you've described using a reference forward and a start and end rotation. \$\endgroup\$
    – oh_cripes
    Commented Sep 27, 2018 at 13:46
  • \$\begingroup\$ Is your problem really "how do I keep an object from flipping suddenly when it travels in a loop"? If so, your idea of decomposing the rotation into a twist angle and a remaining quaternion might not be the best solution. \$\endgroup\$
    – DMGregory
    Commented Sep 27, 2018 at 13:49
  • \$\begingroup\$ You can do away with the atan2 (and the subsequent sincos in Quaternion.AngleAxis) by normalizing vec2(dot(referenceUp, localUp), -dot(referenceRight, localUp)) and then treating the x and y as the sin and cos. This lets you create the double quaternion directly which is just a nlerp(Quaternion.Identity, twist, 0.5) away from beig the one you want. \$\endgroup\$ Commented Sep 27, 2018 at 16:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .