www.pudn.com > XFileLoadingCode.zip > XFileEntity.cpp
#include "dxstdafx.h"
#include "xfileentity.h"
#include "structures.h"
#include "MeshHierarchy.h"
#include "Utilities.h"
// The time to change from one animation set to another
// To see how the merging works - increase this time value to slow it down
const float kMoveTransitionTime=0.25f;
/**
* \brief Constructor
* \author Keith Ditchburn \date 17 July 2005
*/
CXFileEntity::CXFileEntity(void): m_frameRoot(0), m_animController(0), m_numAnimationSets(0),m_currentAnimationSet(0),
m_boneMatrices(0),m_maxBones(0),m_sphereCentre(0,0,0),m_sphereRadius(0),m_speedAdjust(1.0f),
m_firstMesh(0),m_currentTrack(0),m_currentTime(0)
{
}
/**
* \brief Destructor
* \author Keith Ditchburn \date 17 July 2005
*/
CXFileEntity::~CXFileEntity(void)
{
if (m_frameRoot)
{
// Create a mesh heirarchy class to control the removal of memory for the frame heirarchy
CMeshHierarchy Alloc;
D3DXFrameDestroy(m_frameRoot, &Alloc);
m_frameRoot=NULL;
}
SAFE_RELEASE(m_animController);
SAFE_DELETE_ARRAY(m_boneMatrices);
}
/**
* \brief Loads the x file
* \param device - the Direct3D device object
* \param filename - the file to load
* \return HRESULT indicating success
* \author Keith Ditchburn \date 17 July 2005
*/
HRESULT CXFileEntity::LoadXFile(LPDIRECT3DDEVICE9 device,const WCHAR* filename)
{
assert(device);
assert(filename);
// Textures for .x files are normally found in the directory the x file is in (or relative to it) so
// change the current directory for the life time of this load operation to the directory of the x file
// Remember old
WCHAR oldCurrentDir[MAX_PATH];
GetCurrentDirectory(MAX_PATH,oldCurrentDir);
// Get path from filename
WCHAR fname[MAX_PATH];WCHAR drv[MAX_PATH];WCHAR dir[MAX_PATH];
_wsplitpath(filename,drv,dir,fname,0);
WCHAR justPath[MAX_PATH];
StringCchPrintf(justPath,MAX_PATH,L"%s%s",drv,dir);
// Set current
SetCurrentDirectory(justPath);
// We load the frame heirarchy and animation set
// We must provide a memory allocator (CMeshHeirarchy here) - it must implement callback functions
// so that this D3DXLoadMeshHierarchyFromX can call them internally during parsing of the x file
// This call also returns an animation controller
// Create our mesh hierarchy class to control the allocation of memory
CMeshHierarchy Alloc;
HRESULT hr = D3DXLoadMeshHierarchyFromX(filename, D3DXMESH_MANAGED, device,
&Alloc, NULL, &m_frameRoot, &m_animController);
if (FAILED(hr))
return E_FAIL;
// if the x file contains any animation remember how many sets there are
if(m_animController)
m_numAnimationSets = m_animController->GetMaxNumAnimationSets();
// Bones for skining
if(m_frameRoot)
{
// Set the bones up
SetupBoneMatrices(device,(D3DXFRAME_EXTENDED*)m_frameRoot, NULL);
// Create the bone matrices array for use during FrameMove to hold the final transform
m_boneMatrices = new D3DXMATRIX[m_maxBones];
ZeroMemory(m_boneMatrices, sizeof(D3DXMATRIX)*m_maxBones);
// Calculate the Bounding Sphere for this model (used later to position camera correctly)
D3DXFrameCalculateBoundingSphere(m_frameRoot, &m_sphereCentre, &m_sphereRadius);
}
// Put the current directory back to what it was
SetCurrentDirectory(oldCurrentDir);
return hr;
}
/**
* \brief we need to go through the hierarchy and set the combined matrices
* calls itself recursively as it tareverses the hierarchy
* \param device - the Direct3D device object
* \param pFrame - current frame
* \param pParentMatrix - the parent frame matrix
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::SetupBoneMatrices(LPDIRECT3DDEVICE9 device,
D3DXFRAME_EXTENDED *pFrame, LPD3DXMATRIX pParentMatrix)
{
// Cast to our extended structure first
D3DXMESHCONTAINER_EXTENDED* pMesh = (D3DXMESHCONTAINER_EXTENDED*)pFrame->pMeshContainer;
// If this frame has a mesh
if(pMesh)
{
// We need to remember which is the first mesh in the hierarchy for later when we
// update (FrameMove)
if(!m_firstMesh)
m_firstMesh = pMesh;
// if there is skin info, then setup the bone matrices
if(pMesh->pSkinInfo && pMesh->MeshData.pMesh)
{
// Create a copy of the mesh to skin into later
// FIX: 19 September 2005 - the CloneMeshFVF method was failing with some .x exported files from some packages
// due to the FVF not being able to be mapped. Changing to GetDeclaration solves the problem.
D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE];
if (FAILED(pMesh->MeshData.pMesh->GetDeclaration(Declaration)))
return;
//pMesh->MeshData.pMesh->CloneMeshFVF(D3DXMESH_MANAGED,
// pMesh->MeshData.pMesh->GetFVF(), device,
// &pMesh->exSkinMesh);
pMesh->MeshData.pMesh->CloneMesh(D3DXMESH_MANAGED,
Declaration, device,
&pMesh->exSkinMesh);
// Max bones is calculated for later use (to know how big to make the bone matrices array)
m_maxBones=max(m_maxBones,pMesh->pSkinInfo->GetNumBones());
// For each bone work out its matrix
for (UINT i = 0; i < pMesh->pSkinInfo->GetNumBones(); i++)
{
// Find the frame containing the bone
D3DXFRAME_EXTENDED* pTempFrame = (D3DXFRAME_EXTENDED*)D3DXFrameFind(m_frameRoot,
pMesh->pSkinInfo->GetBoneName(i));
// set the bone part - point is at the transformation matrix
pMesh->exFrameCombinedMatrixPointer[i] = &pTempFrame->exCombinedTransformationMatrix;
}
}
}
// Pass on to sibblings
if(pFrame->pFrameSibling)
SetupBoneMatrices(device,(D3DXFRAME_EXTENDED*)pFrame->pFrameSibling, pParentMatrix);
// Pass on to children
if(pFrame->pFrameFirstChild)
SetupBoneMatrices(device,(D3DXFRAME_EXTENDED*)pFrame->pFrameFirstChild, &pFrame->exCombinedTransformationMatrix);
}
/**
* \brief Called each frame update with the time and the current world matrix
* \param elapsedTime - time passed
* \param matWorld - current world matrix for the model
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::FrameMove(float elapsedTime,const D3DXMATRIX *matWorld)
{
// Adjust animation speed
elapsedTime/=m_speedAdjust;
// Advance the time and set in the controller
if (m_animController != NULL)
m_animController->AdvanceTime(elapsedTime, NULL);
m_currentTime+=elapsedTime;
// Now update the model matrices in the hierarchy
UpdateFrameMatrices(m_frameRoot, matWorld);
// If the model contains a skinned mesh update the vertices
D3DXMESHCONTAINER_EXTENDED* pMesh = m_firstMesh;
if(pMesh && pMesh->pSkinInfo)
{
UINT Bones = pMesh->pSkinInfo->GetNumBones();
// Create the bone matrices that transform each bone from bone space into character space
// (via exFrameCombinedMatrixPointer) and also wraps the mesh around the bones using the bone offsets
// in exBoneOffsetsArray
for (UINT i = 0; i < Bones; ++i)
D3DXMatrixMultiply(&m_boneMatrices[i],&pMesh->exBoneOffsets[i], pMesh->exFrameCombinedMatrixPointer[i]);
// We need to modify the vertex positions based on the new bone matrices. This is achieved
// by locking the vertex buffers and then calling UpdateSkinnedMesh. UpdateSkinnedMesh takes the
// original vertex data (in pMesh->MeshData.pMesh), applies the matrices and writes the new vertices
// out to skin mesh (pMesh->exSkinMesh).
// UpdateSkinnedMesh uses software skinning and so this is the slowest way of carrying out skinning
// but is easiest to describe and works on the majority of graphic devices.
// Other methods exist that use hardware to do this skinning - see the notes and the
// SDK skinned mesh sample for details
HRESULT hr=S_OK;
void *srcPtr;
V(pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&srcPtr));
void *destPtr;
V(pMesh->exSkinMesh->LockVertexBuffer(0, (void**)&destPtr));
// Update the skinned mesh
V(pMesh->pSkinInfo->UpdateSkinnedMesh(m_boneMatrices, NULL, srcPtr, destPtr));
// Unlock the meshes vertex buffers
V(pMesh->exSkinMesh->UnlockVertexBuffer());
V(pMesh->MeshData.pMesh->UnlockVertexBuffer());
}
}
/**
* \brief Called to update the frame matrices in the hierarchy to reflect current animation stage
* \param frameBase - frame being looked at
* \param parentMatrix - the matrix of our parent (if we have one)
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::UpdateFrameMatrices(const D3DXFRAME *frameBase, const D3DXMATRIX *parentMatrix)
{
D3DXFRAME_EXTENDED *currentFrame = (D3DXFRAME_EXTENDED*)frameBase;
// If parent matrix exists multiply our frame matrix by it
if (parentMatrix != NULL)
D3DXMatrixMultiply(¤tFrame->exCombinedTransformationMatrix, ¤tFrame->TransformationMatrix, parentMatrix);
else
currentFrame->exCombinedTransformationMatrix = currentFrame->TransformationMatrix;
// If we have a sibling recurse
if (currentFrame->pFrameSibling != NULL)
UpdateFrameMatrices(currentFrame->pFrameSibling, parentMatrix);
// If we have a child recurse
if (currentFrame->pFrameFirstChild != NULL)
UpdateFrameMatrices(currentFrame->pFrameFirstChild, ¤tFrame->exCombinedTransformationMatrix);
}
/**
* \brief Render our mesh.
* Call the DrawFrame recursive fn on render with the root frame (see notes diagram)
* \param device - the Direct3D device object
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::Render(LPDIRECT3DDEVICE9 device) const
{
assert(device);
if (m_frameRoot)
DrawFrame(device,m_frameRoot);
}
/**
* \brief Called to render a frame in the hierarchy
* \param device - the Direct3D device object
* \param frame - frame to render
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::DrawFrame(LPDIRECT3DDEVICE9 device,LPD3DXFRAME frame) const
{
// Draw all mesh containers in this frame
LPD3DXMESHCONTAINER meshContainer = frame->pMeshContainer;
while (meshContainer)
{
DrawMeshContainer(device,meshContainer, frame);
meshContainer = meshContainer->pNextMeshContainer;
}
// Recurse for sibblings
if (frame->pFrameSibling != NULL)
DrawFrame(device,frame->pFrameSibling);
// Recurse for children
if (frame->pFrameFirstChild != NULL)
DrawFrame(device,frame->pFrameFirstChild);
}
/**
* \brief Called to render a mesh
* \param device - the Direct3D device object
* \param meshContainerBase - the mesh container
* \param frameBase - frame containing the mesh
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::DrawMeshContainer(LPDIRECT3DDEVICE9 device,
LPD3DXMESHCONTAINER meshContainerBase, LPD3DXFRAME frameBase) const
{
assert(device);
// Cast to our extended frame type
D3DXFRAME_EXTENDED *frame = (D3DXFRAME_EXTENDED*)frameBase;
// Cast to our extended mesh container
D3DXMESHCONTAINER_EXTENDED *meshContainer = (D3DXMESHCONTAINER_EXTENDED*)meshContainerBase;
// Set the world transform
device->SetTransform(D3DTS_WORLD, &frame->exCombinedTransformationMatrix);
// Loop through all the materials in the mesh rendering each subset
for (UINT iMaterial = 0; iMaterial < meshContainer->NumMaterials; iMaterial++)
{
// use the material in our extended data rather than the one in meshContainer->pMaterials[iMaterial].MatD3D
device->SetMaterial( &meshContainer->exMaterials[iMaterial] );
device->SetTexture( 0, meshContainer->exTextures[iMaterial] );
// Select the mesh to draw, if there is skin then use the skinned mesh else the normal one
LPD3DXMESH pDrawMesh = (meshContainer->pSkinInfo) ? meshContainer->exSkinMesh: meshContainer->MeshData.pMesh;
// Finally Call the mesh draw function
pDrawMesh->DrawSubset(iMaterial);
}
}
/**
* \brief Change to a different animation set
* Handles transitions between animations to make it smooth and not a sudden jerk to a new position
* \param index - new animation set index
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::SetAnimationSet(UINT index)
{
if (index>=m_numAnimationSets || index==m_currentAnimationSet)
return;
// Remember current animation
m_currentAnimationSet=index;
// Get the animation set from the controller
LPD3DXANIMATIONSET set;
m_animController->GetAnimationSet(m_currentAnimationSet, &set );
// Note: for a smooth transition between animation sets we can use two tracks and assign the new set to the track
// not currently playing then insert Keys into the KeyTrack to do the transition between the tracks
// tracks can be mixed together so we can gradually change into the new animation
// Alternate tracks
DWORD newTrack = ( m_currentTrack == 0 ? 1 : 0 );
// Assign to our track
m_animController->SetTrackAnimationSet( newTrack, set );
set->Release();
// Clear any track events currently assigned to our two tracks
m_animController->UnkeyAllTrackEvents( m_currentTrack );
m_animController->UnkeyAllTrackEvents( newTrack );
// Add an event key to disable the currently playing track kMoveTransitionTime seconds in the future
m_animController->KeyTrackEnable( m_currentTrack, FALSE, m_currentTime + kMoveTransitionTime );
// Add an event key to change the speed right away so the animation completes in kMoveTransitionTime seconds
m_animController->KeyTrackSpeed( m_currentTrack, 0.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
// Add an event to change the weighting of the current track (the effect it has blended with the secon track)
m_animController->KeyTrackWeight( m_currentTrack, 0.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
// Enable the new track
m_animController->SetTrackEnable( newTrack, TRUE );
// Add an event key to set the speed of the track
m_animController->KeyTrackSpeed( newTrack, 1.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
// Add an event to change the weighting of the current track (the effect it has blended with the first track)
// As you can see this will go from 0 effect to total effect(1.0f) in kMoveTransitionTime seconds and the first track goes from
// total to 0.0f in the same time.
m_animController->KeyTrackWeight( newTrack, 1.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
// Remember current track
m_currentTrack = newTrack;
}
/**
* \brief Go to the next animation
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::NextAnimation()
{
UINT newAnimationSet=m_currentAnimationSet+1;
if (newAnimationSet>=m_numAnimationSets)
newAnimationSet=0;
SetAnimationSet(newAnimationSet);
}
/**
* \brief Get the name of the animation
* \param index - the animation set index
* \return the name
* \author Keith Ditchburn \date 18 July 2005
*/
const char *CXFileEntity::GetAnimationSetName(UINT index)
{
if (index>=m_numAnimationSets)
return NULL;
// Get the animation set
LPD3DXANIMATIONSET set;
m_animController->GetAnimationSet(m_currentAnimationSet, &set );
return set->GetName();
}
/**
* \brief Slow down animation
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::AnimateSlower()
{
m_speedAdjust+=0.1f;
}
/**
* \brief Speed up animation
* \author Keith Ditchburn \date 18 July 2005
*/
void CXFileEntity::AnimateFaster()
{
if (m_speedAdjust>0.1f)
m_speedAdjust-=0.1f;
}