topical media & game development
graphic-directx-game-16-Skinned-Mesh-Demo-SkinnedMesh.cpp / cpp
//=============================================================================
// SkinnedMesh.cpp by Frank Luna (C) 2005 All Rights Reserved.
//=============================================================================
include <SkinnedMesh.h>
include <AllocMeshHierarchy.h>
include <Vertex.h>
SkinnedMesh::SkinnedMesh(std::string XFilename)
{
AllocMeshHierarchy allocMeshHierarchy;
HR(D3DXLoadMeshHierarchyFromX(XFilename.c_str(), D3DXMESH_SYSTEMMEM,
gd3dDevice, &allocMeshHierarchy, 0, /* ignore user data */
&mRoot, &mAnimCtrl));
// In this demo we assume that the input .X file contains only one
// mesh. So search for that one and only mesh.
D3DXFRAME* f = findNodeWithMesh(mRoot);
if( f == 0 ) HR(E_FAIL);
D3DXMESHCONTAINER* meshContainer = f->pMeshContainer;
mSkinInfo = meshContainer->pSkinInfo;
mSkinInfo->AddRef();
mNumBones = meshContainer->pSkinInfo->GetNumBones();
mFinalXForms.resize(mNumBones);
mToRootXFormPtrs.resize(mNumBones, 0);
buildSkinnedMesh(meshContainer->MeshData.pMesh);
buildToRootXFormPtrArray();
}
SkinnedMesh::~SkinnedMesh()
{
if( mRoot )
{
AllocMeshHierarchy allocMeshHierarchy;
HR(D3DXFrameDestroy(mRoot, &allocMeshHierarchy));
mRoot = 0;
}
ReleaseCOM(mSkinnedMesh);
ReleaseCOM(mSkinInfo);
ReleaseCOM(mAnimCtrl);
}
UINT SkinnedMesh::numVertices()
{
return mSkinnedMesh->GetNumVertices();
}
UINT SkinnedMesh::numTriangles()
{
return mSkinnedMesh->GetNumFaces();
}
UINT SkinnedMesh::numBones()
{
return mNumBones;
}
const D3DXMATRIX* SkinnedMesh::getFinalXFormArray()
{
return &mFinalXForms[0];
}
void SkinnedMesh::update(float deltaTime)
{
// Animate the mesh. The AnimationController has pointers to the hierarchy frame
// transform matrices. The AnimationController updates these matrices to reflect
// the given pose at the current time by interpolating between animation keyframes.
HR(mAnimCtrl->AdvanceTime(deltaTime, 0));
// Recurse down the tree and generate a frame's toRoot transform from the updated pose.
D3DXMATRIX identity;
D3DXMatrixIdentity(&identity);
buildToRootXForms((FrameEx*)mRoot, identity);
// Premultiply the offset-transform to transform the vertices to the bone's local
// coordinate system first, before applying the other transforms.
D3DXMATRIX offsetTemp, toRootTemp;
for(UINT i = 0; i < mNumBones; ++i)
{
offsetTemp = *mSkinInfo->GetBoneOffsetMatrix(i);
toRootTemp = *mToRootXFormPtrs[i];
mFinalXForms[i] = offsetTemp * toRootTemp;
}
}
void SkinnedMesh::draw()
{
HR(mSkinnedMesh->DrawSubset(0));
}
D3DXFRAME* SkinnedMesh::findNodeWithMesh(D3DXFRAME* frame)
{
if( frame->pMeshContainer )
if( frame->pMeshContainer->MeshData.pMesh != 0 )
return frame;
D3DXFRAME* f = 0;
if(frame->pFrameSibling)
if( f = findNodeWithMesh(frame->pFrameSibling) )
return f;
if(frame->pFrameFirstChild)
if( f = findNodeWithMesh(frame->pFrameFirstChild) )
return f;
return 0;
}
bool SkinnedMesh::hasNormals(ID3DXMesh* mesh)
{
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
HR(mesh->GetDeclaration(elems));
bool hasNormals = false;
for(int i = 0; i < MAX_FVF_DECL_SIZE; ++i)
{
// Did we reach D3DDECL_END() {0xFF,0,D3DDECLTYPE_UNUSED, 0,0,0}?
if(elems[i].Stream == 0xff)
break;
if( elems[i].Type == D3DDECLTYPE_FLOAT3 &&
elems[i].Usage == D3DDECLUSAGE_NORMAL &&
elems[i].UsageIndex == 0 )
{
hasNormals = true;
break;
}
}
return hasNormals;
}
void SkinnedMesh::buildSkinnedMesh(ID3DXMesh* mesh)
{
//====================================================================
// First add a normal component and 2D texture coordinates component.
D3DVERTEXELEMENT9 elements[64];
UINT numElements = 0;
VertexPNT::Decl->GetDeclaration(elements, &numElements);
ID3DXMesh* tempMesh = 0;
HR(mesh->CloneMesh(D3DXMESH_SYSTEMMEM, elements, gd3dDevice, &tempMesh));
if( !hasNormals(tempMesh) )
HR(D3DXComputeNormals(tempMesh, 0));
//====================================================================
// Optimize the mesh; in particular, the vertex cache.
DWORD* adj = new DWORD[tempMesh->GetNumFaces()*3];
ID3DXBuffer* remap = 0;
HR(tempMesh->GenerateAdjacency(EPSILON, adj));
ID3DXMesh* optimizedTempMesh = 0;
HR(tempMesh->Optimize(D3DXMESH_SYSTEMMEM | D3DXMESHOPT_VERTEXCACHE |
D3DXMESHOPT_ATTRSORT, adj, 0, 0, &remap, &optimizedTempMesh));
ReleaseCOM(tempMesh); // Done w/ this mesh.
delete[] adj; // Done with buffer.
// In the .X file (specifically the array DWORD vertexIndices[nWeights]
// data member of the SkinWeights template) each bone has an array of
// indices which identify the vertices of the mesh that the bone influences.
// Because we have just rearranged the vertices (from optimizing), the vertex
// indices of a bone are obviously incorrect (i.e., they index to vertices the bone
// does not influence since we moved vertices around). In order to update a bone's
// vertex indices to the vertices the bone _does_ influence, we simply need to specify
// where we remapped the vertices to, so that the vertex indices can be updated to
// match. This is done with the ID3DXSkinInfo::Remap method.
HR(mSkinInfo->Remap(optimizedTempMesh->GetNumVertices(),
(DWORD*)remap->GetBufferPointer()));
ReleaseCOM(remap); // Done with remap info.
//====================================================================
// The vertex format of the source mesh does not include vertex weights
// nor bone index data, which are both needed for vertex blending.
// Therefore, we must convert the source mesh to an "indexed-blended-mesh,"
// which does have the necessary data.
DWORD numBoneComboEntries = 0;
ID3DXBuffer* boneComboTable = 0;
HR(mSkinInfo->ConvertToIndexedBlendedMesh(optimizedTempMesh, D3DXMESH_MANAGED | D3DXMESH_WRITEONLY,
MAX_NUM_BONES_SUPPORTED, 0, 0, 0, 0, &mMaxVertInfluences,
&numBoneComboEntries, &boneComboTable, &mSkinnedMesh));
ReleaseCOM(optimizedTempMesh); // Done with tempMesh.
ReleaseCOM(boneComboTable); // Don't need bone table.
if defined(DEBUG) | defined(_DEBUG)
// Output to the debug output the vertex declaration of the mesh at this point.
// This is for insight only to see what exactly ConvertToIndexedBlendedMesh
// does to the vertex declaration.
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
HR(mSkinnedMesh->GetDeclaration(elems));
OutputDebugString("\nVertex Format After ConvertToIndexedBlendedMesh\n");
int i = 0;
while( elems[i].Stream != 0xff ) // While not D3DDECL_END()
{
if( elems[i].Type == D3DDECLTYPE_FLOAT1)
OutputDebugString("Type = D3DDECLTYPE_FLOAT1; ");
if( elems[i].Type == D3DDECLTYPE_FLOAT2)
OutputDebugString("Type = D3DDECLTYPE_FLOAT2; ");
if( elems[i].Type == D3DDECLTYPE_FLOAT3)
OutputDebugString("Type = D3DDECLTYPE_FLOAT3; ");
if( elems[i].Type == D3DDECLTYPE_UBYTE4)
OutputDebugString("Type = D3DDECLTYPE_UBYTE4; ");
if( elems[i].Usage == D3DDECLUSAGE_POSITION)
OutputDebugString("Usage = D3DDECLUSAGE_POSITION\n");
if( elems[i].Usage == D3DDECLUSAGE_BLENDWEIGHT)
OutputDebugString("Usage = D3DDECLUSAGE_BLENDWEIGHT\n");
if( elems[i].Usage == D3DDECLUSAGE_BLENDINDICES)
OutputDebugString("Usage = D3DDECLUSAGE_BLENDINDICES\n");
if( elems[i].Usage == D3DDECLUSAGE_NORMAL)
OutputDebugString("Usage = D3DDECLUSAGE_NORMAL\n");
if( elems[i].Usage == D3DDECLUSAGE_TEXCOORD)
OutputDebugString("Usage = D3DDECLUSAGE_TEXCOORD\n");
++i;
}
endif
}
void SkinnedMesh::buildToRootXFormPtrArray()
{
// Get a pointer to each frame's to-root transformation (store in an
// array) such that the ith element corresponds with the ith bone
// offset matrix. In this way, we can access a frame's to-root
// transformation with a simple array look up, instead of traversing the
// tree.
for(UINT i = 0; i < mNumBones; ++i)
{
// Find the frame that corresponds with the ith bone offset matrix.
const char* boneName = mSkinInfo->GetBoneName(i);
D3DXFRAME* frame = D3DXFrameFind(mRoot, boneName);
if( frame )
{
FrameEx* frameEx = static_cast<FrameEx*>( frame );
mToRootXFormPtrs[i] = &frameEx->toRoot;
}
}
}
void SkinnedMesh::buildToRootXForms(FrameEx* frame,
D3DXMATRIX& parentsToRoot)
{
// Save some references to economize line space.
D3DXMATRIX& toParent = frame->TransformationMatrix;
D3DXMATRIX& toRoot = frame->toRoot;
toRoot = toParent * parentsToRoot;
FrameEx* sibling = (FrameEx*)frame->pFrameSibling;
FrameEx* firstChild = (FrameEx*)frame->pFrameFirstChild;
// Recurse down siblings.
if( sibling )
buildToRootXForms(sibling, parentsToRoot);
// Recurse to first child.
if( firstChild )
buildToRootXForms(firstChild, toRoot);
}
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.