现在我们知道为了播放骨骼动画,需要有骨骼(bone)的数据,模型(mesh)的数据,关联骨骼和模型上每个顶点的关联数据,以及关键帧的坐标变换数据。所有这些数据必须以某种形式存在于某个地方供我们获取才行。这里要介绍的MS的x文件格式以及从中获取数据的方法。强烈建议大家都来学习一下x文件格式!你会发现它即简单又强大,即使用来存放自定义数据也是相当的方便,一旦掌握之后我保证你会对它爱不释手。 典型的x文件以数据模板和实际数据两部分组成。数据模板类似c++中的结构定义,不过更为灵活和开放。实际数据就是遵守模板定义的数据段。看一个例子, template Employee { <3D82AB43-62DA-11cf-AB39-0020AF71E433> // 每个模板关联唯一的GUID STRING Name; // 姓名 DWORD Sex; // 性别 [ContactEntry] // 联系方式, 另一个模板,模板可以嵌套 } template ContactEntry { <4C9D055B-C64D-4bfe-A7D9-981F507E45FF> // GUID STRING PhoneNumber; // 电话号码 STRING Address; // 地址 } Employee David{ "David"; 1; ContactEntry{ "100-100000000"; "far far away"; } }
从上面这个简单的例子我们就可以看出x文件的大概模样了,详细的情况大家可以参考《Advanced Animation with DirectX》。下面我们看如何来读取这样一个x文件,借助下几个对象, ID3DXFile -- x文件格式文档对象。例如Employee.x这样一个文件。 ID3DXFileEnumObject -- 用来枚举x文档的顶级模板数据。所谓顶级模板数据是指那些没有 父模板的数据,例如上面的David数据段。 ID3DXFILEDATA -- 模板数据。上面的David和他的联系方式都是ID3DXFILEDATA 对象,自包含。 下面看实际的分析函数, 下面的代码适用于DirectX 9.0 SDK Update (October 2004),原书的代码有点过时了。 //----------------------------------------------------------------------------- // 名称 : Parse // 描述 : 分析x文件格式文档 //-----------------------------------------------------------------------------
bool Parse( char *filename, void **pData ) { LPD3DXFILE lpD3DXFile; LPD3DXFILEENUMOBJECT lpD3DXFileEnumObj; LPD3DXFILEDATA lpD3DXFileData; // 参数检查 if( NULL == filename ) return false; // 创建X文件对象 HRESULT hr = D3DXFileCreate( &lpD3DXFile ); if( FAILED( hr ) ) return false; // 注册标准模板 hr = lpD3DXFile->RegisterTemplates( ( LPVOID )D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES ); if( FAILED( hr ) ) { Release<LPD3DXFILE>( lpD3DXFile ); return false; }
// 创建X文件枚举对象 hr = lpD3DXFile->CreateEnumObject( filename, D3DXF_FILELOAD_FROMFILE, &lpD3DXFileEnumObj ); if( FAILED( hr ) ) { Release<LPD3DXFILE>( lpD3DXFile ); return false; } // 解析开始 bool parseResult = BeginParse( pData ); if( true == parseResult ) { // 查询顶级模板数 SIZE_T childCount = 0; lpD3DXFileEnumObj->GetChildren( &childCount ); // 分析每个订级模板 for( DWORD i=0; i<childCount; i++ ) { // 获取当前模板 hr = lpD3DXFileEnumObj->GetChild( i, &lpD3DXFileData ); if( FAILED( hr ) ) break;
// 分析 parseResult = ParseObject( lpD3DXFileData, NULL, 0, pData ); // 释放FileData对象 Release<LPD3DXFILEDATA>( lpD3DXFileData ); // 出现错误,中断分析 if( false == parseResult ) break; } // 解析结束 if( parseResult ) parseResult = EndParse( pData ); }
// 释放相关对象 Release<LPD3DXFILEENUMOBJECT>( lpD3DXFileEnumObj ); Release<LPD3DXFILE>( lpD3DXFile ); // 解析结束 return parseResult; } //----------------------------------------------------------------------------- // 名称 : ParseObject // 描述 : 递归解析顶级模板 //-----------------------------------------------------------------------------
bool ParseObject( LPD3DXFILEDATA pDataObj, LPD3DXFILEDATA pParentDataObj, DWORD depth, void **pData ) { LPD3DXFILEDATA pSubDataObj; bool parseResult = true; HRESULT hr; // 获取子模板数目 DWORD childCount; pDataObj->GetChildren( &childCount ); // 遍历模板并分析 for( DWORD i=0; i<childCount; i++ ) { // 取子模板对象 hr = pDataObj->GetChild( i, &pSubDataObj ); if( FAILED( hr ) ) break; // 分析子模板 parseResult = ParseObject( pSubDataObj, pDataObj, depth+1, pData ); // 释放数据对象 Release<LPD3DXFILEDATA>( pSubDataObj ); // 出现错误,停止分析 if( false == parseResult ) break; } 就那么简单,相信大家都看得明白。通过重载ParseObject方法,我们以判断当前分析的模板类型,然后创建实际的模板对象,从文档中复制数据。有了上面的工具,我们就可以自己来读取和解析x格式的骨骼动画文件了。 |