- Home /
Strange behaviour with Quaternion.AngleAxis
I have a jointed character and I'm trying to use some IK-type logic for generating procedural walking animations. I'm using a very constrained set up which means I can solve the resulting equations analytically. Each section of the leg is a GameObject with the usual parenting relationships (i.e. hip->knee->ankle). I'm considering each joint in the leg as being able to rotate only through 1 axis, effectively the the x-axis of the body (in my case, body y is up, body z is forward). This means that my character's knees can hinge in the expected manner (i.e. forward and back), and the hips are similarly only allowed to swing forwards and back (no side-to-side movement or rotation is permitted).
I'm doing the rotation of the knee like so:
private Transform knee; ...
float currentAngle = calculateCurrentRotationAngleToWorldVertical(knee); float desiredAngle = calculateDesiredRotationAngleToWorldVertical(knee); float delta = Mathf.DeltaAngle(currentAngle, desiredAngle);
Vector3 rotationAxisInLocalSpace = knee.InverseTransformDirection(rotationAxisInWorldSpace); Quaternion q = Quaternion.AngleAxis(delta, rotationAxisInLocalSpace); knee.localRotation *= q;
i.e. I first measure the angle of the knee relative to vertical (with all vectors projected into the plane of the rotation axis), then calculate the desired angle of the knee relative to vertical, figure the change in angle and apply that rotation about the rotation axis to the knee's local rotation quaternion. I haven't supplied details of how I calculate the current and desired rotation angles as I don't think they are directly relevant, but I can add them if required.
This works most of the time, but occasionally seems to generate rather odd results. Sometimes it seems to rotate the wrong way around the rotation axis. For example, the knee is initially rotated such that the angle between it and the world y axis is 143.9 degrees. The desired angle is 170 degrees, so the delta is +26.1 degrees (i.e. 170 - 143.9 = 26.1). When I do the rotation as above, it goes the wrong way, and if I measure the angle afterwards, I see that it is actually 117.8 degrees, i.e. it has subtracted the delta, not added it.
Does anyone know why this is happening? Is it some subtlety of quaternion maths that I'm ignorant of?
Any help much appreciated.
Edit - the issue seems to surface if the stride length decreases (i.e. if the character is trying to move his foot back towards his body).
Have you debugged float currentAngle = calculateCurrentRotationAngleToWorldVertical(knee); float desiredAngle = calculateDesiredRotationAngleToWorldVertical(knee);
to make sure that they are putting out the right angle every time.
Hmm, the only thing i can think of (if your magic I$$anonymous$$ works ;) ) is that maybe something is wrong with your rotation axis. Could it be that it's pointing into the wrong direction sometimes? $$anonymous$$aybe try to use Debug.DrawLine()
. I've never written my own I$$anonymous$$ solver, but some day i will ;). AFAI$$anonymous$$ there's no known bug in Quaternion.AngleAxis.
I think it's something to do with not being able to distinguish an angle of +x from one of -x when using Vector3.Angle. I'll carry out some experiments and post my results.
Answer by bm212 · May 02, 2011 at 09:01 PM
I've found a solution, but it's a horrible hack and I think it simply highlights my lack of understanding about the way quaternions work.
If I do a Quaternion.AngleAxis to tell it to rotate -90 degrees about (1,0,0) in world space, and then do Quaternion.ToAngleAxis on the resulting quaternion output, I get an angle of +90 degrees about (-1,0,0). This should be ok, but it seems like it is actually doing a rotation of 90 degrees about (+1,0,0). As a result, I can hack my way around the problem like so:
Quaternion q = Quaternion.AngleAxis(delta, rotationAxisInLocalSpace);
float angle = 0.0f;
Vector3 axis = Vector3.zero;
q.ToAngleAxis(out angle, out axis);
if (!axis.Equals(rotationAxisInWorldSpace)) {
knee.localRotation*=Quaternion.Inverse(q);
}
else {
knee.localRotation *=q;
}
Pretty horrible, and requires quite a lot of spurious calculation. If anyone can suggest a better way, I'd love to hear it.