类cAnimMesh是最关键的一个类,所有与骨骼动画相关的具体实现细节都封装在该类中,该类还定义了类cAllocateHierarchy的一个对象m_alloc_hierarchy,该对象完成从文件中加载动画网格模型的骨骼层次结构、动画数据以及其他用于绘制模型的几何数据。
类cAnimMesh的定义如下所示:
class cAnimMesh
{
private:
cAllocateHierarchy* m_alloc_hierarchy;
IDirect3DDevice9* m_device;
D3DXFRAME* m_root_frame;
public:
D3DXVECTOR3 m_object_center;
float m_object_radius;
bool m_is_play_anim;
ID3DXAnimationController* m_anim_controller;
private:
HRESULT load_from_xfile(CONST WCHAR* wfilename);
void update_frame_matrices(D3DXFRAME* base_frame, CONST D3DXMATRIX* parent_matrix);
void draw_frame(CONST D3DXFRAME* frame);
void draw_mesh_container(CONST D3DXMESHCONTAINER* base_mesh_container, CONST D3DXFRAME* base_frame);
public:
HRESULT create(IDirect3DDevice9* device, CONST WCHAR* wfilename);
void render(CONST D3DXMATRIX* mat_world, double app_elapsed_time);
void destroy();
public:
cAnimMesh();
virtual ~cAnimMesh();
};
构造函数负责分配资源和初始化成员变量,析构函数负责释放资源:
cAnimMesh::cAnimMesh()
{
m_is_play_anim = true;
m_device = NULL;
m_anim_controller = NULL;
m_root_frame = NULL;
m_alloc_hierarchy = new cAllocateHierarchy();
}
cAnimMesh::~cAnimMesh()
{
D3DXFrameDestroy(m_root_frame, m_alloc_hierarchy);
release_com(m_anim_controller);
delete m_alloc_hierarchy;
}
函数load_from_xfile()的主要任务是调用函数D3DXLoadMeshHierarchyFromX()从.x文件中加载动画模型,其实现如下:
HRESULT cAnimMesh::load_from_xfile(CONST WCHAR* wfilename)
{
HRESULT hr;
WCHAR wpath[MAX_PATH];
DXUTFindDXSDKMediaFileCch(wpath, sizeof(wpath) / sizeof(WCHAR), wfilename);
V_RETURN(D3DXLoadMeshHierarchyFromXW(wpath, D3DXMESH_MANAGED, m_device, m_alloc_hierarchy, NULL,
&m_root_frame, &m_anim_controller));
V_RETURN(D3DXFrameCalculateBoundingSphere(m_root_frame, &m_object_center, &m_object_radius));
return S_OK;
}
虽然该函数的实现代码非常简单,但其内部过程却是很复杂的,关键是要弄清除D3DXLoadMeshHierarchyFromX()函数中m_alloc_hierarchy参数的作用。D3DXLoadMeshHierarchyFromX()函数在内部隐式地通过m_alloc_hierarchy调用加载网格模型具体数据的函数(即上面提到的cAllocateHeirarchy中的CreateFrame()和CreateMeshContainer()函数),这些函数是由用户编写的,但却是由Direct3D在内部于适当机制调用。
来看看D3DXLoadMeshHierarchyFromX()的具体使用说明:
Loads the first frame hierarchy from a .x file.
HRESULT D3DXLoadMeshHierarchyFromX(
LPCSTR Filename,
DWORD MeshOptions,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXALLOCATEHIERARCHY pAlloc,
LPD3DXLOADUSERDATA pUserDataLoader,
LPD3DXFRAME* ppFrameHierarchy,
LPD3DXANIMATIONCONTROLLER* ppAnimController
);
Parameters
-
Filename
- [in] Pointer to a string that specifies the filename. If the compiler settings require Unicode, the data type LPCTSTR resolves to LPCWSTR. Otherwise, the string data type resolves to LPCSTR. See Remarks. MeshOptions
- [in] Combination of one or more flags from the D3DXMESH enumeration that specify creation options for the mesh. pDevice
- [in] Pointer to an IDirect3DDevice9 interface, the device object associated with the mesh. pAlloc
- [in] Pointer to an ID3DXAllocateHierarchy interface. pUserDataLoader
- [in] Application provided interface that allows loading of user data. ppFrameHierarchy
- [out, retval] Returns a pointer to the loaded frame hierarchy. ppAnimController
- [out, retval] Returns a pointer to the animation controller corresponding to animation in the .x file. This is created with default tracks and events.
Return Values
If the function succeeds, the return value is D3D_OK. If the function fails, the return value can be one of the following values: D3DERR_INVALIDCALL, E_OUTOFMEMORY.
Remarks
The compiler setting also determines the function version. If Unicode is defined, the function call resolves to D3DXLoadMeshHierarchyFromXW. Otherwise, the function call resolves to D3DXLoadMeshHierarchyFromXA.
All the meshes in the file will be collapsed into one output mesh. If the file contains a frame hierarchy, all the transformations will be applied to the mesh.
D3DXLoadMeshHierarchyFromX loads the animation data and frame hierarchy from a .x file. It scans the .x file and builds a frame hierarchy and animation controller according to the ID3DXAllocateHierarchy-derived object passed to it through pAlloc. Loading the data requires several steps as follows:
- Derive ID3DXAllocateHierarchy, implementing each method. This controls how frames and meshes are allocated and freed.
- Derive ID3DXLoadUserData, implementing each method. If your .x file has no embedded user-defined data, or if you do not need it, you can skip this part.
- Create an object of your ID3DXAllocateHierarchy class, and optionally of your LoadUserData class. You do not need to call any methods of these objects yourself.
- Call D3DXLoadMeshHierarchyFromX, passing in your ID3DXAllocateHierarchy object and your ID3DXLoadUserData object (or NULL) to create the frame hierarchy and animation controller. All the animation sets and frames are automatically registered to the animation controller.
During the load, ID3DXAllocateHierarchy::CreateFrame and ID3DXLoadUserData::LoadFrameChildData are called back on each frame to control loading and allocation of the frame. The application defines these methods to control how frames are stored. ID3DXAllocateHierarchy::CreateMeshContainer and ID3DXLoadUserData::LoadMeshChildData are called back on each mesh object to control loading and allocation of mesh objects. ID3DXLoadUserData::LoadTopLevelData is called back for each top level object that doesn't get loaded by the other methods.
To free this data, call ID3DXAnimationController::Release to free the animation sets, and D3DXFRAMEDestroy, passing in the root node of the frame hierarchy and an object of your derived ID3DXAllocateHierarchy class. ID3DXAllocateHierarchy::DestroyFrame and ID3DXAllocateHierarchy::DestroyMeshContainer will each be called for every frame and mesh object in the frame hierarchy. Your implementation of ID3DXAllocateHierarchy::DestroyFrame should release everything allocated by ID3DXAllocateHierarchy::CreateFrame, and likewise for the mesh container methods.
因为在每次渲染网格模型前,只有知道每个框架的确切位置,才能在正确的位置上绘制出该框架包含的具体网格模型,所以需要计算得到各级框架的组合变换矩阵,函数update_frame_matrices()采用递归的方法计算各级框架的组合变换矩阵,具体实现代码如下:
void cAnimMesh::update_frame_matrices(D3DXFRAME* base_frame, CONST D3DXMATRIX* parent_matrix)
{
D3DXFRAME_DERIVED* frame = (D3DXFRAME_DERIVED*) base_frame;
if(parent_matrix != NULL)
D3DXMatrixMultiply(&frame->CombinedTransformMatrix, &frame->TransformationMatrix, parent_matrix);
else
frame->CombinedTransformMatrix = frame->TransformationMatrix;
if(frame->pFrameSibling != NULL)
update_frame_matrices(frame->pFrameSibling, parent_matrix);
if(frame->pFrameFirstChild != NULL)
update_frame_matrices(frame->pFrameFirstChild, &frame->CombinedTransformMatrix);
}
因为骨骼动画网格模型是通过框架按照树状结构组织起来的,而网格模型又包含在框架之中,所以在为了渲染网格模型的同时能将其中的动画播放出来,就需要逐个框架逐个网格模型地进行渲染,其中draw_mesh_container()负责渲染框架中包含的具体网格模型:
void cAnimMesh::draw_mesh_container(CONST D3DXMESHCONTAINER* base_mesh_container, CONST D3DXFRAME* base_frame)
{
D3DXMESHCONTAINER_DERIVED* mesh_container = (D3DXMESHCONTAINER_DERIVED*) base_mesh_container;
D3DXFRAME_DERIVED* frame = (D3DXFRAME_DERIVED*) base_frame;
m_device->SetTransform(D3DTS_WORLD, &frame->CombinedTransformMatrix);
for(UINT i = 0; i < mesh_container->NumMaterials; i++)
{
m_device->SetMaterial(&mesh_container->pMaterials[i].MatD3D);
m_device->SetTexture(0, mesh_container->ppTextures[i]);
mesh_container->MeshData.pMesh->DrawSubset(i);
}
}
该函数的实现比较简单,在渲染每个网格之前,首先调用函数SetTransform(),根据该网格在框架的组合变换矩阵,将网格中所包含的网格模型移动到正确的位置后,再设置材质、纹理,最后进行绘制。
函数draw_frame()以draw_mesh_container()为基础,采用递归的方法,将整个网格模型绘制出来:
void cAnimMesh::draw_frame(CONST D3DXFRAME* frame)
{
D3DXMESHCONTAINER* mesh_container = frame->pMeshContainer;
while(mesh_container != NULL)
{
draw_mesh_container(mesh_container, frame);
mesh_container = mesh_container->pNextMeshContainer;
}
if(frame->pFrameSibling != NULL)
draw_frame(frame->pFrameSibling);
if(frame->pFrameFirstChild != NULL)
draw_frame(frame->pFrameFirstChild);
}
在调用该函数时,只需将参数frame设置为网格模型的根节点就可以绘制出整个网格模型。
函数render()通过draw_frame()完成整个网格模型的渲染,其实现如下:
void cAnimMesh::render(CONST D3DXMATRIX* mat_world, double app_elapsed_time)
{
if(0.0f == app_elapsed_time)
return;
if(m_is_play_anim && m_anim_controller != NULL)
m_anim_controller->AdvanceTime(app_elapsed_time, NULL);
update_frame_matrices(m_root_frame, mat_world);
draw_frame(m_root_frame);
}
在渲染网格模型之前,首先使用动画控制器m_anim_controller调用函数AdvanceTime()将网格模型动画向前推进,然后调用函数update_frame_matrices(),根据当前网格模型的世界矩阵mat_world更新整个网格模型的层次,即计算每个框架的组合变换矩阵,最后调用draw_frame()函数渲染出整个网格模型。
create()函数用于根据参数指定的网格模型文件名创建骨骼动画网格模型:
HRESULT cAnimMesh::create(IDirect3DDevice9* device, CONST WCHAR* wfilename)
{
m_device = device;
HRESULT hr;
V_RETURN(load_from_xfile(wfilename));
return S_OK;
}
函数destroy()只负责销毁对象:
void cAnimMesh::destroy()
{
delete this;
}