Check Out the Demo
Although this chapter only has one demo to tout, it sure is a whopper!
Demonstrating the technique of blended animation, the SkeletalAnimBlend demo
program (see Figure 6.3) shows off Microsoft's Tiny character in all her blended
glory!
Figure 6.3: Explore your new found blended skeletal animation techniques by
choosing which animations to blend in real time.
I edited the Tiny.x file to split her animation up into multiple sets. There
are animation sets for each of her arms and legs, as well as an animation set
for her body. Pressing any of the keys displayed on the screen toggles the
blending of the appropriate animation set. For instance, hitting 1 toggles the
blending of her left arm animation sequence. When enabled, the left arm
animation has Tiny swinging her arm in sequence with her step. When disabled,
her left arm hangs limp.
To really illustrate the power of blending, suppose you add a new animation
set to the Tiny.x file that has Tiny waving her arm as opposed to swinging it
back and forth. You only need to turn off blending of the swinging animation and
blend in the waving animation to create a new and totally unique animation!
SkeletalAnimBlend.h:
#ifndef SKELETAL_ANIM_BLEND_H
#define SKELETAL_ANIM_BLEND_H
#include "SkeletalAnim.h"
class cBlendAnimationCollection : public cAnimationCollection
{
public:
void blend(const char* anim_set_name, DWORD time, bool is_loop, float blend);
};
#endif
SkeletalAnimBlend.cpp:
#include "SkeletalAnimBlend.h"
#pragma warning(disable : 4996)
void cBlendAnimationCollection::blend(const char* anim_set_name,
DWORD time, bool is_loop, float blend)
{
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
D3DXMATRIX anim_matrix;
D3DXMatrixIdentity(&anim_matrix);
// 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_matrix *= 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_matrix *= 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_matrix *= 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_matrix *= diff_matrix;
}
D3DXMATRIX diff_matrix = anim_matrix - anim->bone->mat_original;
diff_matrix *= blend;
anim->bone->TransformationMatrix += diff_matrix;
}
}
WinMain.cpp:
/***********************************************************************************************
Demonstrates how multiple skeletal-based animations can be blended together into one animation.
***********************************************************************************************/
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Direct3D.h"
#include "SkeletalAnimBlend.h"
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
D3DXMESHCONTAINER_EX* g_mesh_container;
D3DXFRAME_EX* g_frame;
IDirect3DTexture9* g_guide_texture;
ID3DXSprite* g_guide_sprite;
cBlendAnimationCollection g_blend_anim_collection;
char g_blend_flags[5]; // blending toggles (arms, legs, )
const char CLASS_NAME[] = "BlendSkeletalAnimClass";
const char CAPTION[] = "Blended 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_KEYUP:
// toggle a body part
if(wParam >= '1' && wParam <= '5')
g_blend_flags[wParam - '1'] ^= 1;
// clear toggles
if(wParam == VK_SPACE)
memset(g_blend_flags, 1, sizeof(g_blend_flags));
break;
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
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_blend_anim_collection.load("..\\Data\\tiny.x"))
return false;
// map the animation to the frame hierarchy
g_blend_anim_collection.map_frames(g_frame);
// load the guide texture and create the sprite interface
D3DXCreateTextureFromFileEx(g_device, "..\\Data\\Guide.bmp", D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT,
0, D3DFMT_A1R5G5B5, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT,
0xFF000000, NULL, NULL, &g_guide_texture);
D3DXCreateSprite(g_device, &g_guide_sprite);
// clear toggles
memset(g_blend_flags, 1, sizeof(g_blend_flags));
return true;
}
void do_shutdown()
{
// free mesh data
delete g_mesh_container; g_mesh_container = NULL;
delete g_frame; g_frame = NULL;
// free guide texture and sprite interface
release_com(g_guide_texture);
release_com(g_guide_sprite);
// release D3D objects
release_com(g_device);
release_com(g_d3d);
}
void do_frame()
{
static DWORD start_time = timeGetTime();
DWORD curr_time = timeGetTime();
g_frame->reset();
DWORD time = curr_time - start_time;
// blend the animations
if(g_blend_flags[0])
g_blend_anim_collection.blend("left_arm", time, true, 1.5f);
if(g_blend_flags[1])
g_blend_anim_collection.blend("right_arm", time, true, 1.5f);
if(g_blend_flags[2])
g_blend_anim_collection.blend("left_leg", time, true, 1.2f);
if(g_blend_flags[3])
g_blend_anim_collection.blend("right_leg", time, true, 1.2f);
if(g_blend_flags[4])
g_blend_anim_collection.blend("body", time, true, 1.0f);
// 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
D3DXMATRIX mat_view;
D3DXVECTOR3 eye(600.0f, 200.0f, -600.0f);
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, 64, 255), 1.0f, 0);
g_device->BeginScene();
draw_mesh(g_mesh_container);
// enable per pixel alpha testing
g_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
// specifies a reference alpha value against which pixels are tested
g_device->SetRenderState(D3DRS_ALPHAREF, 0x01);
// accept the new pixel if its value is greater than the value of the current pixel
g_device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
// draw the guid teture
g_guide_sprite->Begin(0);
g_guide_sprite->Draw(g_guide_texture, NULL, NULL, NULL, D3DCOLOR_RGBA(255 ,255, 0, 255));
g_guide_sprite->End();
g_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
download source file