Try It

Everything covered up until this point is what you need to animate a skeleton. In this section, i will provide some sample code that shows how what has been covered so far can be used to draw an animated skeleton.

Displaying a mesh that deforms with this skeleton is called skinning, we will cover skinning in the next section. If you are implementing your own animation system, take some time to confirm that the hirarchy is animating correctly before tacking skinning.

Let's assume we have a Sample class that contains a list of animation clips, the current animation time, the rest pose of the character being animated and the current animated pose. The Sample can initialize, update, render and shut down.

class Sample : {
protected:
    std::vector<Clip> mAnimClips;
    unsigned int mClipIndex;
    float mAnimTime;
    Pose mCurrentPose;
    Pose mRestPose;
public:
    void Initialize();
    void Update(float inDeltaTime);
    void Render(float inAspectRatio);
    void Shutdown();
};

We're only focusing on animating one character. To animate multiple characters, each character would need a unique clip index, animation time, and animated pose. The collection of animation clips and the rest pose could be shared between all of the aniamted models.

The initialize function should load animation clips from a file, as well as the rest pose. Setting animation time to 0 isn't accurate as not all animation clips start at 0 seconds in time. Instead use the animation clips start time.

void Sample::Initialize() {
    mAnimClips = LoadAnimationClips("Woman.clips");
    mRestPose = LoadRestPose("Woman.rest");

    // Reset like this when switching animations
    mCurrentPose = mRestPose; 

    mClipIndex = 6;
    mAnimTime = mAnimClips[mClipIndex].GetStartTime();
}

The Update function is trivial, it samples the current animation clip into the current pose. mAnimationTime is increased by delta time every frame and passed to the Sample function. The result of the Sample function is stored back in mAnimationTime, keeping mAnimationTime always valid.

void Sample::Update(float deltaTime) {
    float time = mAnimTime + deltaTime;
    mAnimTime = mAnimClips[mClipIndex].Sample(mCurrentPose, time);
}

The render function renders only a debug skeleton. Loop trough all of the joints in the current pose, skipping any joints that don't have a parent. Find the global transform of both this joint and it's parent joint and draw a line between their positions.

void Sample::Render(float AspectRatio) {
    for (unsigned int i = 0; i < mCurrentPose.Size(); ++i) {
        int p = mCurrentPose.GetParent(i);
        if (p < 0) { continue; }

        Transform self = mCurrentPose.GetGlobalTransform(i);
        Transform parent = mCurrentPose.GetGlobalTransform(p);

        DrawLine(self.position, parent.position);
    }
}

Running the above sample results in an animation like this: