Check Out the Demos
In this chapter, you learned how to load animation sets and use that data to
animate your on−screen meshes. To better demonstrate these animation concepts, I
have created a program (SkeletalAnim) that shows your favorite lady of
skeletal−based animation, Microsoft's Tiny (from the DirectX SDK samples), doing
what she does best walking around! When you run the demo application, you'll be
greeted with a scene like the one shown in Figure 5.3.
Figure 5.3: Tiny on the move in the SkeletalAnim demo! This demo shows you
how to use skeletal−based animated meshes.
SkeletalAnim.h:
#ifndef SKELETAL_ANIM_H
#define SKELETAL_ANIM_H
#include <windows.h>
#include "Direct3D.h"
#include "XParser.h"
//=====================================================================================
// Animation key type data structures
//=====================================================================================
struct sAnimVectorKey
{
DWORD time;
D3DXVECTOR3 vec;
};
struct sAnimQuatKey
{
DWORD time;
D3DXQUATERNION quat;
};
struct sAnimMatrixKey
{
DWORD time;
D3DXMATRIX matrix;
};
//=====================================================================================
// Animation structures
//=====================================================================================
struct sAnimation
{
char* bone_name;
D3DXFRAME_EX* bone; // pointer to bone frame
sAnimation* next; // next animation object in list
// each key type and array of each type's keys
DWORD num_translation_keys;
sAnimVectorKey* translation_keys;
DWORD num_scale_keys;
sAnimVectorKey* scale_keys;
DWORD num_rotation_keys;
sAnimQuatKey* rotation_keys;
DWORD num_matrix_keys;
sAnimMatrixKey* matrix_keys;
sAnimation()
{
ZeroMemory(this, sizeof(*this));
}
~sAnimation()
{
delete[] bone_name; bone_name = NULL;
delete[] translation_keys;
delete[] scale_keys;
delete[] rotation_keys;
delete[] matrix_keys;
delete next; next = NULL;
}
};
//=====================================================================================
// Animation set is container of animation.
//=====================================================================================
struct sAnimationSet
{
char* name; // name of animation set
DWORD length; // length of animation
sAnimationSet* next;
DWORD num_anims;
sAnimation* anims;
sAnimationSet()
{
ZeroMemory(this, sizeof(*this));
}
~sAnimationSet()
{
delete[] name; name = NULL;
delete anims; anims = NULL;
delete next; next = NULL;
}
};
//=====================================================================================
// Parse animation data from X file.
//=====================================================================================
class cAnimationCollection : public cXParser
{
protected:
DWORD m_num_anim_sets;
sAnimationSet* m_anim_sets;
protected:
virtual bool parse_objects(ID3DXFileData* xfile_data,
ID3DXFileData* parent_xfile_data,
DWORD depth,
void** data,
bool force_ref);
public:
cAnimationCollection()
{
m_num_anim_sets = 0;
m_anim_sets = NULL;
}
~cAnimationCollection()
{
free();
}
void free()
{
m_num_anim_sets = 0;
delete m_anim_sets; m_anim_sets = NULL;
}
bool load(const char* filename)
{
free(); // free a prior loaded collection
return parse(filename, NULL);
}
void map_frames(D3DXFRAME_EX* root_frame);
void update(const char* anim_set_name, DWORD time, bool is_loop);
};
#endif
SkeletalAnim.cpp:
#include <d3dx9xof.h>
#include "XTemplate.h"
#include "SkeletalAnim.h"
#pragma warning(disable : 4996)
bool cAnimationCollection::parse_objects(ID3DXFileData* xfile_data,
ID3DXFileData* parent_xfile_data,
DWORD depth, void** data, bool force_ref)
{
GUID type;
get_object_guid(xfile_data, &type);
if(type == TID_D3DRMAnimationSet)
{
// create and link in a sAnimationSet object
sAnimationSet* anim_set = new sAnimationSet;
anim_set->next = m_anim_sets;
m_anim_sets = anim_set;
m_num_anim_sets++;
anim_set->name = get_object_name(xfile_data);
}
else if(type == TID_D3DRMAnimation && m_anim_sets)
{
// add a sAnimation to top-level sAnimationSet
sAnimation* anim = new sAnimation;
anim->next = m_anim_sets->anims;
m_anim_sets->anims = anim;
m_anim_sets->num_anims++;
}
else if(type == TID_D3DRMFrame && force_ref == true && m_anim_sets && m_anim_sets->anims)
{
// a frame reference inside animation template
if(parent_xfile_data)
{
GUID parent_type;
get_object_guid(parent_xfile_data, &parent_type);
// make sure parent object is an animation template
if(parent_type == TID_D3DRMAnimation)
m_anim_sets->anims->bone_name = get_object_name(xfile_data);
}
return true; // do not process child of reference frames
}
else if(type == TID_D3DRMAnimationKey && m_anim_sets && m_anim_sets->anims)
{
sAnimation* anim = m_anim_sets->anims;
SIZE_T size;
DWORD* data_ptr;
xfile_data->Lock(&size, (LPCVOID*) &data_ptr);
DWORD type = *data_ptr++;
DWORD num_keys = *data_ptr++;
// branch based on key type
switch(type)
{
case 0: // rotation
delete[] anim->rotation_keys;
anim->num_rotation_keys = num_keys;
anim->rotation_keys = new sAnimQuatKey[num_keys];
for(DWORD i = 0; i < num_keys; i++)
{
anim->rotation_keys[i].time = *data_ptr++;
if(anim->rotation_keys[i].time > m_anim_sets->length)
m_anim_sets->length = anim->rotation_keys[i].time;
data_ptr++; // skip number of keys to follow (should be 4)
// quaternion data stored with w,x,y,z order in xfile, so can not cast directly to assigned!
float* float_ptr = (float*) data_ptr;
anim->rotation_keys[i].quat.w = *float_ptr++;
anim->rotation_keys[i].quat.x = *float_ptr++;
anim->rotation_keys[i].quat.y = *float_ptr++;
anim->rotation_keys[i].quat.z = *float_ptr++;
data_ptr += 4;
}
break;
case 1: // scaling
delete[] anim->scale_keys;
anim->num_scale_keys = num_keys;
anim->scale_keys = new sAnimVectorKey[num_keys];
for(DWORD i = 0; i < num_keys; i++)
{
anim->scale_keys[i].time = *data_ptr++;
if(anim->scale_keys[i].time > m_anim_sets->length)
m_anim_sets->length = anim->scale_keys[i].time;
data_ptr++; // skip number of keys to follow (should be 3)
anim->scale_keys[i].vec = *((D3DXVECTOR3*) data_ptr);
data_ptr += 3;
}
break;
case 2: // translation
delete[] anim->translation_keys;
anim->num_translation_keys = num_keys;
anim->translation_keys = new sAnimVectorKey[num_keys];
for(DWORD i = 0; i < num_keys; i++)
{
anim->translation_keys[i].time = *data_ptr++;
if(anim->translation_keys[i].time > m_anim_sets->length)
m_anim_sets->length = anim->translation_keys[i].time;
data_ptr++; // skip number of keys to follow (should be 3)
anim->translation_keys[i].vec = *((D3DXVECTOR3*) data_ptr);
data_ptr += 3;
}
break;
case 4: // transformation matrix
delete[] anim->matrix_keys;
anim->num_matrix_keys = num_keys;
anim->matrix_keys = new sAnimMatrixKey[num_keys];
for(DWORD i = 0; i < num_keys; i++)
{
anim->matrix_keys[i].time = *data_ptr++;
if(anim->matrix_keys[i].time > m_anim_sets->length)
m_anim_sets->length = anim->matrix_keys[i].time;
data_ptr++; // skip number of keys to follow (should be 16)
anim->matrix_keys[i].matrix = *((D3DXMATRIX*) data_ptr);
data_ptr += 16;
}
break;
}
xfile_data->Unlock();
}
return parse_child_objects(xfile_data, depth, data, force_ref);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void cAnimationCollection::map_frames(D3DXFRAME_EX* root_frame)
{
for(sAnimationSet* anim_set = m_anim_sets; anim_set != NULL; anim_set = anim_set->next)
{
for(sAnimation* anim = anim_set->anims; anim != NULL; anim = anim->next)
anim->bone = root_frame->find(anim->bone_name);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void cAnimationCollection::update(const char* anim_set_name, DWORD time, bool is_loop)
{
sAnimationSet* anim_set = m_anim_sets;
// look for matching animation set if name used
if(anim_set_name)
{
// find matching animation set name
while(anim_set != NULL)
{
// break when match found
if(! stricmp(anim_set->name, anim_set_name))
break;
anim_set = anim_set->next;
}
}
if(anim_set == NULL) // no animation set found
return;
// bounds time to animation length
if(time > anim_set->length)
time = is_loop ? (time % (anim_set->length + 1)) : anim_set->length;
for(sAnimation* anim = anim_set->anims; anim != NULL; anim = anim->next)
{
if(anim->bone == NULL) // only process if it is attached to a bone
continue;
// reset transformation
D3DXMatrixIdentity(&anim->bone->TransformationMatrix);
// apply various matrices to transformation
// scaling
if(anim->num_scale_keys && anim->scale_keys)
{
DWORD key1 = 0, key2 = 0;
// loop for matching scale key
for(DWORD i = 0; i < anim->num_scale_keys; i++)
{
if(time >= anim->scale_keys[i].time)
key1 = i;
}
key2 = (key1 >= (anim->num_scale_keys - 1)) ? key1 : key1+1;
DWORD time_diff = anim->scale_keys[key2].time - anim->scale_keys[key1].time;
if(time_diff == 0)
time_diff = 1;
float scalar = (float)(time - anim->scale_keys[key1].time) / time_diff;
// calculate interpolated scale values
D3DXVECTOR3 scale_vec = anim->scale_keys[key2].vec - anim->scale_keys[key1].vec;
scale_vec *= scalar;
scale_vec += anim->scale_keys[key1].vec;
// create scale matrix and combine with transformation
D3DXMATRIX scale_matrix;
D3DXMatrixScaling(&scale_matrix, scale_vec.x, scale_vec.y, scale_vec.z);
anim->bone->TransformationMatrix *= scale_matrix;
}
// rotation
if(anim->num_rotation_keys && anim->rotation_keys)
{
DWORD key1 = 0, key2 = 0;
// loop for matching rotation key
for(DWORD i = 0; i < anim->num_rotation_keys; i++)
{
if(time >= anim->rotation_keys[i].time)
key1 = i;
}
key2 = (key1 >= (anim->num_rotation_keys - 1)) ? key1 : key1+1;
DWORD time_diff = anim->rotation_keys[key2].time - anim->rotation_keys[key1].time;
if(time_diff == 0)
time_diff = 1;
float scalar = (float)(time - anim->rotation_keys[key1].time) / time_diff;
// slerp rotation values
D3DXQUATERNION rot_quat;
D3DXQuaternionSlerp(&rot_quat, &anim->rotation_keys[key1].quat, &anim->rotation_keys[key2].quat, scalar);
// create rotation matrix and combine with transformation
D3DXMATRIX rot_matrix;
D3DXMatrixRotationQuaternion(&rot_matrix, &rot_quat);
anim->bone->TransformationMatrix *= rot_matrix;
}
// translation
if(anim->num_translation_keys && anim->translation_keys)
{
DWORD key1 = 0, key2 = 0;
// loop for matching translation key
for(DWORD i = 0; i < anim->num_translation_keys; i++)
{
if(time >= anim->translation_keys[i].time)
key1 = i;
}
key2 = (key1 >= (anim->num_matrix_keys - 1)) ? key1 : key1+1;
DWORD time_diff = anim->translation_keys[key2].time - anim->translation_keys[key1].time;
if(time_diff == 0)
time_diff = 1;
float scalar = (float)(time - anim->translation_keys[key1].time) / time_diff;
// calculate interpolated vector values
D3DXVECTOR3 pos_vec = anim->translation_keys[key2].vec - anim->translation_keys[key1].vec;
pos_vec *= scalar;
pos_vec += anim->translation_keys[key1].vec;
// create translation matrix and combine with transformation
D3DXMATRIX translation_matrix;
D3DXMatrixTranslation(&translation_matrix, pos_vec.x, pos_vec.y, pos_vec.z);
anim->bone->TransformationMatrix *= translation_matrix;
}
// matrix
if(anim->num_matrix_keys && anim->matrix_keys)
{
DWORD key1 = 0, key2 = 0;
// loop for matching matrix key
for(DWORD i = 0; i < anim->num_matrix_keys; i++)
{
if(time >= anim->matrix_keys[i].time)
key1 = i;
}
key2 = (key1 >= (anim->num_matrix_keys - 1)) ? key1 : key1+1;
DWORD time_diff = anim->matrix_keys[key2].time - anim->matrix_keys[key1].time;
if(time_diff == 0)
time_diff = 1;
float scalar = (float)(time - anim->matrix_keys[key1].time) / time_diff;
// calculate interpolated matrix
D3DXMATRIX diff_matrix = anim->matrix_keys[key2].matrix - anim->matrix_keys[key1].matrix;
diff_matrix *= scalar;
diff_matrix += anim->matrix_keys[key1].matrix;
// combine with transformation
anim->bone->TransformationMatrix *= diff_matrix;
}
}
}
WinMain.cpp:
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Direct3D.h"
#include "SkeletalAnim.h"
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
D3DXMESHCONTAINER_EX* g_mesh_container;
D3DXFRAME_EX* g_frame;
cAnimationCollection g_anim_collection;
float g_mesh_radius = 0.0f; // bounding radius of mesh
const char CLASS_NAME[] = "SkeletalAnimClass";
const char CAPTION[] = "Skeletal Animation Demo";
////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool do_init(HWND hwnd);
void do_shutdown();
void do_frame();
//////////////////////////////////////////////////////////////////////////////////////////////
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR, int cmd_show)
{
CoInitialize(NULL); // Initialize the COM system
// Create the window class here and register it
WNDCLASSEX win_class;
win_class.cbSize = sizeof(win_class);
win_class.style = CS_CLASSDC;
win_class.lpfnWndProc = window_proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = 0;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = NULL;
win_class.lpszMenuName = NULL;
win_class.lpszClassName = CLASS_NAME;
win_class.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&win_class))
return -1;
// Create the main window
HWND hwnd = CreateWindow(CLASS_NAME, CAPTION, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
0, 0, 640, 480, NULL, NULL, inst, NULL);
if(hwnd == NULL)
return -1;
ShowWindow(hwnd, cmd_show);
UpdateWindow(hwnd);
// Call init function and enter message pump
if(do_init(hwnd))
{
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
// Start message pump, waiting for user to exit
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
do_frame(); // Render a single frame
}
}
do_shutdown();
UnregisterClass(CLASS_NAME, inst);
CoUninitialize();
return 0;
}
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Only handle window destruction messages
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
DestroyWindow(hwnd);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
bool do_init(HWND hwnd)
{
init_d3d(&g_d3d, &g_device, hwnd, false, false);
// load skeletal mesh
if(FAILED(load_mesh(&g_mesh_container, &g_frame, g_device, "..\\Data\\tiny.x", "..\\Data\\", 0, D3DXMESH_SYSTEMMEM)))
return false;
// load animation data
if(! g_anim_collection.load("..\\Data\\tiny.x"))
return false;
// map the animation to the frame hierarchy
g_anim_collection.map_frames(g_frame);
// get the bounding radius of the object
g_mesh_radius = 0.0f;
D3DXMESHCONTAINER_EX* mesh_container = g_mesh_container;
while(mesh_container)
{
ID3DXMesh* mesh = mesh_container->MeshData.pMesh;
if(mesh)
{
// lock the vertex buffer, get its radius, and unlock buffer.
D3DXVECTOR3* vertices;
D3DXVECTOR3 center;
float radius;
mesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&vertices);
D3DXComputeBoundingSphere(vertices, mesh->GetNumVertices(), D3DXGetFVFVertexSize(mesh->GetFVF()),
¢er, &radius);
mesh->UnlockVertexBuffer();
// update radius
if(radius > g_mesh_radius)
g_mesh_radius = radius;
}
// goto next mesh
mesh_container = (D3DXMESHCONTAINER_EX*) mesh_container->pNextMeshContainer;
}
return true;
}
void do_shutdown()
{
// free mesh data
delete g_mesh_container; g_mesh_container = NULL;
delete g_frame; g_frame = NULL;
// release D3D objects
release_com(g_device);
release_com(g_d3d);
}
void do_frame()
{
static DWORD start_time = timeGetTime();
DWORD curr_time = timeGetTime();
// update the animation (convert to 30 fps)
g_anim_collection.update(NULL, (curr_time - start_time) * 3, true);
// rebuild the frame hierarchy transformations
if(g_frame)
g_frame->update_hierarchy(NULL);
// rebuild the mesh
update_skin_mesh(g_mesh_container);
// calculate a view transformation matrix using the mesh's bounding radius to position the viewer
float distance = g_mesh_radius * 3.0f;
float angle = timeGetTime() / 2000.0f;
D3DXMATRIX mat_view;
D3DXVECTOR3 eye(cos(angle) * distance, g_mesh_radius, sin(angle) * distance);
D3DXVECTOR3 at(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up);
g_device->SetTransform(D3DTS_VIEW, &mat_view);
D3DXMATRIX mat_world;
D3DXMatrixIdentity(&mat_world);
g_device->SetTransform(D3DTS_WORLD, &mat_world);
// clear the device and start drawing the scene
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 255), 1.0f, 0);
g_device->BeginScene();
draw_mesh(g_mesh_container);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
download
source file