Why do quaternion look-at's flip?

Started by
11 comments, last by Kryzon 6 years, 1 month ago

The green axis of the gizmo is pointing at the ball. The red and blue axes twitch when the ball passes a certain point (ignore the discontinuity of the GIF):

quat_Look_At_twitch.gif

This is from a simple game engine so it's easier to analyse, but I've seen this behaviour in plenty of other cases (including animation software, with aim constraints).

Is there a way to avoid this flipping? Is there a method of rotation that doesn't need to adjust the blue and red axes, where it just points the green to the target and the other axes can freely rotate and accumulate rotation, rather than having to do this "reset"? I'm asking this as an animation character rigger, working with some bones with look-at constraints that have this same flipping \ twitching behaviour.

For reference, the source code of the look-at from the image:

The look-at function (from here):


void  bbPointEntity( Entity *e,Entity *t,float roll ){
	if( debug ){ debugEntity(e);debugEntity(t); }
	Vector v=t->getWorldTform().v-e->getWorldTform().v;
	e->setWorldRotation( rotationQuat( v.pitch(),v.yaw(),roll*dtor ) );
}

From that, the v.pitch() and v.yaw() methods (from here):


	float yaw()const{
		return -atan2f( x,z );
	}
	float pitch()const{
		return -atan2f( y,sqrtf( x*x+z*z ) );
}

...and the rotationQuat function that creates the quaternion (from here and here):


inline Quat pitchQuat( float p ){
	return Quat( cosf(p/-2),Vector( sinf(p/-2),0,0 ) );
}

inline Quat yawQuat( float y ){
	return Quat( cosf(y/2),Vector( 0,sinf(y/2),0 ) );
}

inline Quat rollQuat( float r ){
	return Quat( cosf(r/-2),Vector( 0,0,sinf(r/-2) ) );
}

(...)

Quat rotationQuat( float p,float y,float r ){
	return yawQuat(y)*pitchQuat(p)*rollQuat(r);
}
Advertisement

Your Slerp is taking the long way instead of the short path. The code you have here is so compressed I have no idea where the Slerp is.

The way to solve this is to find the dot product of +Quaternion and -Quaternion. Both are the same angle but go in opposite directions to W. If the dotproduct is negative you flip them in your slerp function.

 

Maybe you can check the dot product between +Q and -Q and if it's negative you multiply the Quaternion with -1; this way it can flip and rotates in the short direction.

Hi @Scouting Ninja, thanks for looking into it. I think there's no slerp in there (I didn't write the code, but I took it from an old but good game engine to rule out any implementation errors).

From what I understood the function takes the (world) source -> target vector and creates a quaternion from it, and then sets it as the (world) rotation of the source object, effectively making it point \ aim along the vector to the other object.

But that twisting of the red and blue axes can be seen everywhere, not just in that engine (Blender's Track To and Damped Track, Maya's Aim constraint etc.).

Now that I searched with different terms, it's briefly mentioned in Maya's docs: https://knowledge.autodesk.com/support/maya-lt/learn-explore/caas/CloudHelp/cloudhelp/2015/ENU/MayaLT/files/CSCo-Prevent-rolling-effects-for-aim-constraints-htm.html

I'm wondering if there isn't some space-conversion, hierarchy-arrangement or axis-switching trick to avoid this thing.
Any help is appreciated.

36 minutes ago, Kryzon said:

I think there's no slerp in there....From what I understood the function takes the (world) source -> target vector and creates a quaternion from it

That means your DetlaTime and world is the Slerp or what ever size the tick rate is.

If (DotProduct(Q,-Q) < 0): Q= -Q; Should work then.

Just look into Dot Product and how to invert a quaternion. You also need to invert to subtract angles so it is a good function to have.

 

Your quaternion is taking the long way, invert it to take the short cut.

This image from Maya should help:

GUID-FC3CA5CD-1E25-4108-A002-712201DB0FF6.png

36 minutes ago, Kryzon said:

But that twisting of the red and blue axes can be seen everywhere,

Yes, because it is what prevents Gimbal lock, a quaternion has no direction where two axis cross each other; that is what your seeing. It's also why quaternions is so hard to memorize.

Like you said the problem is common that is why slerp is used, but your slerp is based on the world and time, you would need to invert the World And Time; or you can just invert the quaternion.

There is no slerp here, the behaviour comes from the euler angle decomposition i think. The effect is very similar to the one we see when using 'look at' in combination with an up-vector. (the up-vector is encoded in the ordering of the euler angles, similar to how it works for an FPS camera for example.)

You can use the technique discussed here instead: https://www.gamedev.net/forums/topic/695555-quaternion-help/

So, having a method like this to construct a quaternion,


void FromVecToVecNonUniformLength (const sVec3 &dir0, const sVec3 &dir1)
    {
        sScalar dot = dir0.Dot(dir1);
        (*this)[3] = sqrt(dir1.SqL() * dir0.SqL()) + dot; // set w
        if ((*this)[3] >= FP_EPSILON)
        { 
            *((sVec3*)this) = dir0.Cross(dir1); // set xyz
            Normalize();
        }
        else
        {
            if (dot<0) *this = sQuat (1.0f, 0, 0, 0); // input 180 degrees apart -> 180 degrees rotation around x
            else *this = sQuat (0, 0, 0, 1.0f); // zero vector input -> zero rotation output
        }
    }

... you have the ability to create a rotation from one direction to another.

You can use this in a way like this:


	quaternion rotation; 
	rotation.FromVecToVecNonUniformLength (
	myObject.orientation.Rotate(vec(1,0,0)), // transform object x axis to global space, but you could use any other direction as well 
	targetPosition - myObject.position);
	myObject.orientation = rotation * myObject.orientation; // eventually reverse multiplication order depending on your math lib convention
	

After this the objects local x axis should point to the target and it will not cause any kind of flipping behavior.

 

Edit: I'm not sure if this is really what you want. For rigging using up vector makes more sense, but the user typically controls the up vector as well.

 

 

 

I'm not a mathmagician, and I think JoeJ and Scouting Ninja have mostly covered it, but as I understand it:

  • Yaw, Pitch and Roll suffers from Gimbal lock (https://en.wikipedia.org/wiki/Gimbal_lock)
  • Lookat with an Up vector can also suffer from a similar 'undefined' rotation.
  • I also use a similar routine to JoeJ's 'FromVecToVec' with a dot product and cross product, this too can suffer at 180 degrees from undefined rotation.

I think part of reason for this problem occurring with constraints is that the 'previous' orientation is not defined in the 3d package. The constraint is applied each frame to the original animation orientation, whereas for the removal of this problem, you want a continuous flow from the previously constrained rotation, so there is frame to frame consistency.

So it is something maybe more easily solvable in a game where everything can rely on previous orientation, whereas in a 3d package where it is required to move to frames at random, it would require building the whole sequence of orientations up to that frame. It can be done in fact, but whether most 3d packages do it out of the box I don't know.

1 hour ago, lawnjelly said:

I think part of reason for this problem occurring with constraints is that the 'previous' orientation is not defined in the 3d package.

Yeah agree to that, many problems related to rotations have no robust instant solution in special cases.

In a game we can solve this over time, e.g. if a up vector constraint is near a flipping case, we could decouple the 'upwards' from the 'point at' rotation, and correct the upwards rotation by small angles over time. Same if 'point at' direction is 180 degrees away from the current, we can solve slowly for it.

In animation tools this could be done as well, similar to e.g. recording the state of a physics simulation, but for something like a character rig this would not be intuitive. Problems can be avoided usually with a good setup. E.g. for a leg IK solver we only need a end effector box to control target foot position and orientation, and a point that forms a plane with hip joint and foot to orient the knee. So with only two elements the whole leg can be animated, foot roll happens just automatically. Issues only happen if the point is at the line between knee and hip so no plane can be built, but this is a intuitive limitation any animator will grasp. In this case the point controlling the knee is a way to expose the up-vector to the user.

Thanks for all the info guys, I'm going through it right now.

I like this statement from that "Quaternion help" thread: "you also need an up vector or else you have infinite number of solutions." 

That makes sense!

From that game engine clip, when the target goes near the up-axis of the source, the source must have trouble aligning its up vector with the world up vector. In case of aim constraints (for 3D rigs and animation) the Maya docs recommend animating the up vector of the source object. Maybe this can be done with a driven expression -- changing a property of an object based on the properties of other objects (such as how high or low the target is).

 

- - - - - -

The rig parts that need this aim constraint are the shoulders and thighs: ball-socket type joints with the possibility of roll.

It's not simple to explain, but the way that real muscle masses and skin are deformed by bones, if you simply share the shoulder skin influence between the upper arm bone and the clavicle bone, the entire shoulder rolls along with the upper arm in a wrong way unlike real life.

So a way to fix this is to use a helper bone that aims the elbow but keeps its up vector still, so you can get realistic twisting on the shoulder area (see how the upper arm can be subdivided in two bones, one for the shoulder and another for the upper arm roll):

znNod.gif

The downside of this method is that, because of the aim constraint, the shoulders do that up-vector fail when the arms are all the way up or down, (the twitch from that GIF on the first post).

 

 

1 hour ago, Kryzon said:

"you also need an up vector or else you have infinite number of solutions." 

This does not apply to you. This only applies if you want to define orientation with nothing else than a single direction, but you already have a given orientation (the shoulder) to start with. If an initial orientation is given, rotating it by a single rotation, e.g. axis and angle between two directions makes sense.

So your goal is procedural roll of the bone in the middle? There's no need for an up vector then, but also thinking about aligning directions won't help much in the first place.

From the gif it seems the middle bone rotates with the same rate than the lower arm, it would be nice if you have control over this, e.g. taking 50% roll from shoulder and 50% from lower arm.

I did something similar with XSI. I made splines from torso to upper arm and from upper arm to lower arm etc. And on various spline positions i created quaternion slerps with percentage weights as said above, and finally i constrained local quat axis to align with the spline tangent. I was quite impressed all this was possible without any scripting. 

As your case is a bit simpler, there are more options.

First, the easiest seems to constrain the euler angle. You would create the middle bone as a child from the shoulder and script the euler axis representing roll to be the average of shoulder and lower arm. But this works only if the ordering of euler angles is right by luck, otherwise you need to experiment with either changing the order (messing up your animation data), or creating children for shoulder and arm, rotating them in 90 degrees steps and pick various angles until it magically works. (Tedious trial and error expected :) )

Another option is to do it like i mentioned, but without splines. First, if orientation of shoulder / arm would differ a lot in rest pose you'd need to create a child for one of them so they are pretty equal. Then you constrain the middle bone by quaternion slerp between those two, and finally you align middle bone axis to the direction of the upper arm. This should not cause any flipping issues other than rolling the arm > 180 degrees.

Neither approach should use an up vector.

 

Edit:

Here is another related quaternion method eventually useful here:

qt = q0.Inversed() * q1;

float xAngle = atan (qt.x / qt.w) * 2.0f;

What this does is to extract the rotation over x axis between orientations q0 and q1. Using this you could implement this without the need for a slerp, if it would be a performance critical in engine problem.

 

22 minutes ago, JoeJ said:

But this works only if the ordering of euler angles is right by luck, otherwise you need to experiment with either changing the order (messing up your animation data), or creating children for shoulder and arm, rotating them in 90 degrees steps and pick various angles until it magically works. (Tedious trial and error expected :) )

This is interesting. Are you saying that there's an order of Euler angles that can let you roll a bone along its length axis, without changing the aim? (I remember this happening before)

This topic is closed to new replies.

Advertisement