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; 
}