Skeletal Animation #3 Jun 23, 2006
In my first skeletal animation article, I briefly mentioned setting joint rotations and how to compute the matrices for each bone from the joint quaternions. Since I just glossed over a possibly complicated subject in a couple lines, I decided to concentrate on it in this article.
Computing the Bone Matrices
In my skeletal animation system, each skeleton is made up of joint/bone pairs. See the skeleton screenshot here. Each joint has a quaternion that controls its rotation. Now this quaternion may be controlled in another way, like by IK, Euler Angles, or a physics system, but it's ultimately what's used to define the joint rotations. The skeleton in the screenshot was rendered by loading a bone matrix for each bone then drawing a non-uniform scaled cube for the bone. These bone matrices are also used for mesh skinning, as described in the previous skeletal animation articles. We'll start at the centerpoint position. Then we can recursively add in each bone and save the matrix, as shown by this code : ( I just wrote this without testing, hopefully it's bug free )
// InMatrix Will probably be the ObjToWorld Matrix for the centerpoint( joint 0 ) void CSkel::SetMatrixFromJoint( int nJoint, CMatrix InMatrix, CMatrix* pBoneMatrices ) { // Set the Bone Matrix for nJoint by converting and adding the rotation quaternion pBoneMatrices[ nJoint ] = CMatrix( m_pJoints[ nJoint ].qRotate ) * InMatrix; // Process any further joints recursively for (int i = 0; i < MAX_JOINT_CHILDREN; i++) { int nJointChild = m_pJoints[ nJoint ].nChild[i]; if (nJointChild != NO_CHILD ) { // translate by joint offset and parent bone length CVec3 vTranslate = m_pJoints[ nJointChild ].vOffset; vTranslate.x += m_pJoints[ nJoint ].fBoneLength; // Use the parent matrix, then translate CMatrix TempM = pBoneMatrices[ nJoint ]; TempM.Translate( -vTranslate ); SetMatrixFromJoint( nJointChild, TempM, pBoneMatrices ); } } } You can get the global position of the a joint by taking the translation vector of its bone matrix. You could also get the global rotation of a joint from the bone matrix, or you could compute and store this rotation separately in a quat. Rag-doll
If you're using a rigid-body physics system for your Rag-Doll, probably you'll set up physics shapes for certain bones and joint them together. From the physics system you'll get back the rotations of the shapes in world space. ( Another way to do rag-doll type effects is to just use points for joint location, and then calculate rotation, choosing twist separately, but I won't discuss that here. )
What I do is use these world space rotations to set the rotation values of each individual joint. Now you might wonder if we really need to convert back to joint rotations. There is a reason I did it this way. I want to be able to convert bones between ragdoll and animation whenever I want, including proper tweening. For instance, a character may use rag doll for a fall, then get back up. ( Getting up is an ambitious example, I've only done simpler cases so far. ) I find it's easiest if everything sets the joint quats. We can send the global target rotations from the physics system into our SetMatrixFromJoint function. There are also other reasons to use a target rotation, such as a character pulling herself onto to a ledge, where you want her hands to stay aligned to the ledge. We use the targets to set the joint rotations where needed, by putting the code is below at the top of the function:
if ( pbUseTarget[ nJoint ] )
m_pJoints[ nJoint ].qRotate = pqTargetRot[ nJoint ] * qParentGlobalRot.Invert();
|