//////////////////////////////////////////////////////////////////////////////////////////////////
// 
// File: XFileEntity.cpp
// 
// Author: Keith Ditchburn
//
// Homepage: www.toymaker.info
//
// Date: July 17, 2005
//
// Desc: XFileEntity class loads a .x file and maintains the heirarchy
//          
//////////////////////////////////////////////////////////////////////////////////////////////////

#include "XFileEntity.h"

#pragma warning( disable : 4996 ) // disable deprecated warning 
#include <strsafe.h>
#pragma warning( default : 4996 )

// 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)
{
	m_copy = true; // everyone is copy unless proven diferently :)
}

/**
 * \brief Destructor
 * \author Keith Ditchburn \date 17 July 2005
*/
CXFileEntity::~CXFileEntity(void)
{	 
	if (m_frameRoot && !m_copy)
	{
		// 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);
	if (!m_copy)
		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);

	StringCchCat(fname, 255, L".x");

	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(fname, 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);

	m_copy = false; //New X file is loaded - this is not a copy of another model :)

	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::OnFrameMove(LPDIRECT3DDEVICE9 device, 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
	D3DXMATRIX Identity;
	D3DXMatrixIdentity(&Identity);
	//This use to be called with world matrix but it didn't work well
    UpdateFrameMatrices(m_frameRoot, &Identity);

	// 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)
		{
			D3DXMatrixIdentity(&m_boneMatrices[i]);
			D3DXMatrixMultiply(&m_boneMatrices[i], &pMesh->exBoneOffsets[i], pMesh->exFrameCombinedMatrixPointer[i]);
			//We multiply with world matrix here because UpdateFrameMatrices is called with identity matrix
			D3DXMatrixMultiply(&m_boneMatrices[i],&m_boneMatrices[i],matWorld);
		}
		
		// 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(&currentFrame->exCombinedTransformationMatrix, &currentFrame->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, &currentFrame->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;
}