VOXELS_3DAnimation
mName /std::string //animation name
mLength /float // animation duration of time
mTracks[] /vector<VOXELS_3DTrack> //keyFrame storation for each bone/joint
...
VOXELS_3DTrack
mBone /std::string //bone-joint name
mKeyFrames[] /vector<VOXELS_3DKeyFrame> // keyFrame storation for the "mBone"
...
VOXELS_3DKeyFrame
mTime /float //tells at which time the this VOXELS_3DKeyFrame occurs
mTrans /vec3f //translate vector3f
mRotate /float //angle for rotation of the bone at current keyFrame
mAxis /float //axis with which to rotate
.......................................
With skeletal animation each character is a hierarchy of bones( ie. skeleton ), and the bones control the deformation of the character mesh. What follows is an quick overview of one way to handle skeletal animation, based on a much simplified version of how I do it in my own program. As usual, my advice for anyone just starting is to get something simple working and build from there, and make sure your program can display enough visuals&values that you can tell exactly what's going on if you need to debug.When I initially set up skeletons in my program I had joints (which controled the rotations between bones) and bones as separate types, each able to have multiple children of the other type. While there's not really anything wrong with this, I found there wasn't any need for it either. Here what I called a joint is included in my definition of bone.
Structure
Off the top of my head, the basic bone structure for a skeleton might look like this:
struct Bone {
quat qRotate;
CVec3 vOffset;
float fHingeAngle, fMin, fMax;
float fLength;
int nJointType;
int Child[ MAX_BONE_CHILDREN ];
int Parent;
char sName[ 32 ];
};
qRotate
- a quaternion representing the rotation of the bone in relation to its parent
vOffset
- a 3D vector for how to offset the bone from the end of its parent ( defaults to 0,0,0 )
fHingeAngle, fMin, fMax
- Hinge angle with a minimum and maximum. These values are only needed forInverse Kinematics. I will ignore it in this article.
fLength
- The length of the bone
nJointType
- The joint type ( normal, hinge, fixed... )
Child
- The numbers in the skeleton of any child bones. (initialized to invalid)
Parent
- The number of the parent bone. Not strictly neccessary, but you'll probably end up wanting it.
sName
- The name of this bone. In some situations not needed, but definitely is for my editor.
Animation
You'll want a separate structure for Bone keyframes. At their simplest, a keyframe need only include a quaternion for rotation and its frame number. I also include location and velocity (for IK & offset control,) as well as a few other elements.
For the actual animating, we start with setting the proper values for the current frame by interpolating between the left and right keyframe. Spherical Linear Interpolation (Slerp) can be used for quaternions, and a linear average or hermite curves for the location vector. Once we have set the values of current keyframe, we use this keyframe to calculate a matrix for each bone. This matrix could be added to the Bone Structure above, but I can have multiple objects with separate animations that are instances of a single skeleton, so I prefer to keep it in an array of size numBones that is part of each skeleton object. The bone matrices are computed by starting with main bone and recursively processing each child bone in the same manner. For each bone we send in the current matrix, add to it the bone's rotation, store as BoneMatrix for that bone, then translate by length.
Rendering
Once we have a matrix for each bone, we can draw some visual representation of the skeleton structure in its current position. On the simple end you can loop through each bone, load the bone matrix, and draw a box( perhaps of size bone[nBone].fLength,1,1 ) representing the bone.
On the more complex end, you can use the skeleton to deform a mesh. The details of that will have to wait to a later article. Here's an example of how you'd do it if each vert is only attached to one bone. The InvBindMatrix of a bone is the inverse of that bone's matrix in the position the skeleton was bound to the mesh. Therefore if a bone's current matrix is the same as its bind matrix, meaning it hasn't moved at all, InvBindMatrix * BoneMatrix will cancel out as we want.
CMatrix MBone[ MAX_BONES ];
for (int nBone = 0; nBone < nTotalBones; nBone++)
{
MBone[nBone] = InvBindMatrices[ nBone ] * BoneMatrices[ nBone ];
}
for (int i = 0; i < numVerts; i++)
{
pRenderVertices[ i ] = MBone[ pnBoneAttached[ i ] ] * pInVerts[ i ];
}
.........................................................
This article builds on my first skeletal animation article. I recommend reading that one first if you haven't already. In this article I'll discuss a couple of techniques used to improve the results of deforming a mesh with skeletal animation.
Weighted Vertices
Multiple attachments are a standard technique to improve the look of the deformed mesh, for things like reducing bunching at joints or adding subtle control bones, like putting a bone in a character's stomach to allow visible breathing. Somewhere along the way someone decided that 4 was a good number of different bones to allow a vertex to be attached to, and allowing up to 4 attachments has been sufficient for me for many years. So for each vertex we store each bone it is attached to (pnBoneAttached), and the weight for that attachment (pfWeights). If you add up all the weights, they should add up to 1.0. The code for computing the vertex locations deformed by a weighted skeleton follows:
for (i = 0; i < numVerts; i++)
{
pRenderVertices[ i ].SetZero();
for (int x = 0; x < 4; x++)
{
int nBone = pnBoneAttached[ 4*i+x ];
if ( nBone == NO_BONE ) break;
pRenderVertices[ i ] += (MBones[ nBone ] * pVerts[ i ]) * pfWeights[ 4*i+x ];
}
}
With multiple attachments not just the vertices need to be blended, the vertex normals do too, or in the case of object space normal mapping, the light vectors. This can be done using pretty much the same method used for blending the verts. The main difference is you'll just want to rotate the normal without any translating. What I do is use the line pNormal[i].RotByMatrix( MBones[ nBone ] ), where RotByMatrix uses three dot products to rotate pNormal by the matrix without translating.
Blending Multiple Mesh Chunks
Another way to animate meshes is to blend between multiple chunks of a mesh to create the final mesh. This is used most often for facial animation, like blinking or smiling, although it can be used in plenty of other ways. (It is also possible to use skeletal animation for facial animation.) You might have one head mesh chunk with the eyes open, and on with the eyes closed, and blend between them to blink. You can allow stacking of multiple blend shapes, and sliders to adjust their influence.
The actual blending between the chunks can be simple, although what I do is more complicated than what I'll present here. For instance you might want to define vertices to be blended instead of an entire chunk, and there are specific implementation issues that vary between programs, so are not covered in this article. Let's say you have 1 chunk for the default position, and 2 additional blend chunks, each with a weight between 0 and 1. I'm assuming in this example that the blend chunks aren't offset from the default chunk, if they are you'll need to translate them first. The weights are scaled so that they all add up to one. If the weights of all the blend chunks total less than 1, the weight of the default chunk is assigned so that they'll total 1.
int i;
float fTotal = 0.0f;
pfWeights[0] = 0.0f;
for ( i = 1; i < nChunks; i++ ) {
fTotal += pfWeights[i];
}
if ( fTotal < 1.0f ) {
pfWeights[0] = 1.0f - fTotal;
fTotal = 1.0f;
}
for ( i = 1; i < nChunks; i++ ) {
pfWeights[i] /= fTotal;
}
for (i = 0; i < nVertsInChunk; i++)
{
pOutVerts[i].SetZero();
for ( int x = 0; x < nChunks; x++ ) {
pOutVerts[i] += pInVerts[x][i] * pfWeights[x];
}
}
To use this with skeletal animation, you need to first run a pass to do the blending, and use the output as the input( pVerts ) in deforming the vertices by the attached bones.
.....................................................
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 )
void CSkel::SetMatrixFromJoint( int nJoint, CMatrix InMatrix, CMatrix* pBoneMatrices )
{
pBoneMatrices[ nJoint ] = CMatrix( m_pJoints[ nJoint ].qRotate ) * InMatrix;
for (int i = 0; i < MAX_JOINT_CHILDREN; i++)
{
int nJointChild = m_pJoints[ nJoint ].nChild[i];
if (nJointChild != NO_CHILD )
{
CVec3 vTranslate = m_pJoints[ nJointChild ].vOffset;
vTranslate.x += m_pJoints[ nJoint ].fBoneLength;
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();
.....................................................
.....................................................
.....................................................