As shown in Figure 4.5, the SkeletalAnim mesh demonstrates what you learned
in this chapter by loading a skinned mesh (the Tiny.x mesh provided in the
DirectX SDK samples) and rendering it to the display.
Figure 4.5: Meet Tiny, Microsoft's woman of skeletal meshes. She is
constructed from a single mesh and an underlying hierarchy of invisible bones.
source added into Direct3D.cpp:
//-------------------------------------------------------------------------------------------
// Declare an internal .X file parser class for loading meshes and frames.
//-------------------------------------------------------------------------------------------
class cXInternalParser
{
public:
// information passed from calling function
IDirect3DDevice9* m_device;
const char* m_texture_path;
DWORD m_new_fvf;
DWORD m_load_flags;
DWORD m_flags; // flags for which data to load: 1 = mesh, 2 = frames, 3 = both.
// hierarchies used during loading
D3DXMESHCONTAINER_EX* m_root_mesh_container;
D3DXFRAME_EX* m_root_frame;
protected:
void parse_object(ID3DXFileData* xfile_data,
ID3DXFileData* parent_xfile_data,
DWORD depth,
void** data,
bool force_ref);
void parse_child_object(ID3DXFileData* xfile_data, DWORD depth, void** data, bool force_ref);
public:
cXInternalParser();
~cXInternalParser();
bool parse(const char* filename, void** data);
char* get_name(ID3DXFileData* xfile_data);
};
//==============================================================================================
// Generic .X parser class code
//==============================================================================================
cXInternalParser::cXInternalParser()
{
ZeroMemory(this, sizeof(*this));
}
cXInternalParser::~cXInternalParser()
{
delete m_root_mesh_container; m_root_mesh_container = NULL;
delete m_root_frame; m_root_frame = NULL;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
bool cXInternalParser::parse(const char* filename, void** data)
{
if(filename == NULL)
return false;
ID3DXFile* xfile;
if(FAILED(D3DXFileCreate(&xfile)))
return false;
// register standard templates
if(FAILED(xfile->RegisterTemplates((LPVOID) D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES)))
{
xfile->Release();
return false;
}
ID3DXFileEnumObject* xfile_enum;
if(FAILED(xfile->CreateEnumObject(filename, DXFILELOAD_FROMFILE, &xfile_enum)))
{
xfile->Release();
return false;
}
SIZE_T num_child;
xfile_enum->GetChildren(&num_child);
// loop through all top-level objects, breaking on errors.
for(SIZE_T i = 0; i < num_child; i++)
{
ID3DXFileData* xfile_data;
xfile_enum->GetChild(i, &xfile_data);
parse_object(xfile_data, NULL, 0, data, false);
release_com(xfile_data);
}
release_com(xfile_enum);
release_com(xfile);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void cXInternalParser::parse_object(ID3DXFileData* xfile_data,
ID3DXFileData* parent_xfile_data,
DWORD depth, void** data, bool force_ref)
{
GUID type;
xfile_data->GetType(&type);
// process templates based on their type
if(type == TID_D3DRMFrame && (m_flags & PARSE_FRAME) && force_ref == false)
{
D3DXFRAME_EX* frame = new D3DXFRAME_EX;
frame->Name = get_name(xfile_data);
// link frame into hierarchy
if(data == NULL)
{
// link as sibling of root
frame->pFrameSibling = m_root_frame;
m_root_frame = frame;
frame = NULL;
data = (void**) &m_root_frame;
}
else
{
// link as child of supplied frame
D3DXFRAME_EX* frame_ptr = (D3DXFRAME_EX*)*data;
frame->pFrameSibling = frame_ptr->pFrameFirstChild;
frame_ptr->pFrameFirstChild = frame;
frame = NULL;
data = (void**)&frame_ptr->pFrameFirstChild;
}
}
else if(type == TID_D3DRMFrameTransformMatrix && (m_flags & PARSE_FRAME) && data && force_ref == false)
{
D3DXFRAME_EX* frame = (D3DXFRAME_EX*)*data;
if(frame)
{
SIZE_T size;
const void* tran_matrix;
xfile_data->Lock(&size, &tran_matrix);
frame->TransformationMatrix = *((const D3DXMATRIX*) tran_matrix);
frame->mat_original = frame->TransformationMatrix;
xfile_data->Unlock();
}
}
else if(type == TID_D3DRMMesh && (m_flags & PARSE_MESH)) // load a mesh (skinned or regular)
{
if(force_ref == false)
{
D3DXMESHCONTAINER_EX* mesh_container;
load_mesh(&mesh_container, m_device, xfile_data, m_texture_path, m_new_fvf, m_load_flags);
// link mesh to head of list of meshes0
if(mesh_container)
{
mesh_container->pNextMeshContainer = m_root_mesh_container;
m_root_mesh_container = mesh_container;
mesh_container = NULL;
// link mesh to frame if needed
if(data)
{
D3DXFRAME_EX* frame = (D3DXFRAME_EX*) *data;
if((m_flags & PARSE_FRAME) && frame)
frame->pMeshContainer = m_root_mesh_container;
}
}
}
else // referenced, then check if wanting to link to frame.
{
if(data)
{
D3DXFRAME_EX* frame = (D3DXFRAME_EX*) *data;
if((m_flags & PARSE_FRAME) && m_root_mesh_container && frame)
{
char* name = get_name(xfile_data);
if(name)
{
frame->pMeshContainer = m_root_mesh_container->find(name);
delete[] name;
name = NULL;
}
}
}
}
}
parse_child_object(xfile_data, depth, data, force_ref);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void cXInternalParser::parse_child_object(ID3DXFileData* xfile_data,
DWORD depth, void** data, bool force_ref)
{
SIZE_T num_child;
xfile_data->GetChildren(&num_child);
// scan for embedded templates
for(SIZE_T i = 0; i < num_child; i++)
{
ID3DXFileData* child_xfile_data;
xfile_data->GetChild(i, &child_xfile_data);
if(child_xfile_data->IsReference())
force_ref = true;
parse_object(child_xfile_data, xfile_data, depth+1, data, force_ref);
release_com(child_xfile_data);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
char* cXInternalParser::get_name(ID3DXFileData* xfile_data)
{
if(xfile_data == NULL)
return NULL;
DWORD size;
if(FAILED(xfile_data->GetName(NULL, &size)))
return NULL;
char* name = NULL;
if(size > 1)
{
name = new char[size];
xfile_data->GetName(name, &size);
}
return name;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
HRESULT load_mesh(D3DXMESHCONTAINER_EX** ret_mesh_container,
D3DXFRAME_EX** ret_frame,
IDirect3DDevice9* device,
const char* filename,
const char* texture_path,
DWORD new_fvf,
DWORD load_flags)
{
// error checking
if(device == NULL || filename == NULL || texture_path == NULL)
return E_FAIL;
cXInternalParser parser;
// set parser data
parser.m_device = device;
parser.m_texture_path = texture_path;
parser.m_new_fvf = new_fvf;
parser.m_load_flags = load_flags;
parser.m_flags = ((ret_mesh_container == NULL) ? PARSE_NONE : PARSE_MESH) |
((ret_frame == NULL) ? PARSE_NONE : PARSE_FRAME);
// clear mesh and frame pointers
parser.m_root_frame = NULL;
parser.m_root_mesh_container = NULL;
if(! parser.parse(filename, NULL))
return E_FAIL;
// Map the matrices to the frames and create an array of bone matrices,
// but only if user passed pointers to receive and the loader found some meshes and frames.
if(ret_mesh_container && ret_frame && parser.m_root_mesh_container && parser.m_root_frame)
{
// scan through all meshes
D3DXMESHCONTAINER_EX* mesh_container = parser.m_root_mesh_container;
while(mesh_container)
{
// does this mesh use skinning?
if(mesh_container->pSkinInfo)
{
DWORD num_bones = mesh_container->pSkinInfo->GetNumBones();
// allocate the matrix pointers and bone matrices
mesh_container->frame_combined_matrices = new D3DXMATRIX*[num_bones];
mesh_container->bone_trans_matrices = new D3DXMATRIX[num_bones];
// match matrix poiners to frames
for(DWORD i = 0; i < num_bones; i++)
{
const char* bone_name = mesh_container->pSkinInfo->GetBoneName(i);
D3DXFRAME_EX* frame = parser.m_root_frame->find(bone_name);
// match frame to bone
if(frame)
mesh_container->frame_combined_matrices[i] = &frame->mat_combined;
else
mesh_container->frame_combined_matrices[i] = NULL;
}
}
// go to next mesh
mesh_container = (D3DXMESHCONTAINER_EX*) mesh_container->pNextMeshContainer;
}
}
if(ret_mesh_container)
{
// copy the pointer into passed variables
*ret_mesh_container = parser.m_root_mesh_container;
parser.m_root_mesh_container = NULL;
}
else
{
// delete list of meshes in case any were not needed.
delete parser.m_root_mesh_container;
parser.m_root_mesh_container = NULL;
}
if(ret_frame)
{
// assign frame hierarchy pointer
*ret_frame = parser.m_root_frame;
parser.m_root_frame = NULL;
}
else
{
// delete frame hierarchy in case it was loaded and it was not needed.
delete parser.m_root_frame;
parser.m_root_frame = NULL;
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
HRESULT update_skin_mesh(D3DXMESHCONTAINER_EX* mesh_container)
{
if(mesh_container == NULL)
return E_FAIL;
if(mesh_container->MeshData.pMesh == NULL || mesh_container->skin_mesh == NULL || mesh_container->pSkinInfo == NULL)
return E_FAIL;
if(mesh_container->bone_trans_matrices == NULL || mesh_container->frame_combined_matrices == NULL)
return E_FAIL;
// copy the bone matrices over (must have been combined before call draw_mesh)
for(DWORD i = 0; i < mesh_container->pSkinInfo->GetNumBones(); i++)
{
// start with bone offset matrix
mesh_container->bone_trans_matrices[i] = *(mesh_container->pSkinInfo->GetBoneOffsetMatrix(i));
// apply frame transformation
if(mesh_container->frame_combined_matrices[i])
mesh_container->bone_trans_matrices[i] *= (*mesh_container->frame_combined_matrices[i]);
}
void* src_vertices;
void* dest_vertices;
mesh_container->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&src_vertices);
mesh_container->skin_mesh->LockVertexBuffer(0, (void**)&dest_vertices);
// update the skinned mesh using provided transformations
mesh_container->pSkinInfo->UpdateSkinnedMesh(mesh_container->bone_trans_matrices, NULL, src_vertices, dest_vertices);
mesh_container->MeshData.pMesh->UnlockVertexBuffer();
mesh_container->skin_mesh->UnlockVertexBuffer();
return S_OK;
}
WinMain.cpp:
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Direct3D.h"
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
D3DXMESHCONTAINER_EX* g_mesh_container;
D3DXFRAME_EX* g_frame;
float g_mesh_radius = 0.0f; // bounding radius of mesh
const char CLASS_NAME[] = "SkeletalClass";
const char CAPTION[] = "Skeletal 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);
if(FAILED(load_mesh(&g_mesh_container, &g_frame, g_device, "..\\Data\\tiny.x", "..\\Data\\", 0, 0)))
return false;
// 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()
{
// 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);
// rebuild the frame hierarchy transformations
if(g_frame)
g_frame->update_hierarchy(NULL);
// rebuild the mesh
update_skin_mesh(g_mesh_container);
// 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();
D3DXMATRIX mat_world;
D3DXMatrixIdentity(&mat_world);
g_device->SetTransform(D3DTS_WORLD, &mat_world);
draw_mesh(g_mesh_container);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
download source file