//////////////////////////////////////////////////////////////////////////////////////////////////
// 
// File: MeshHierarchy.cpp
// 
// Author: Keith Ditchburn
//
// Homepage: www.toymaker.info
//
// Date: July 17, 2005
//
// Desc: Mesh hierarchy for animated .x file load
//          
//////////////////////////////////////////////////////////////////////////////////////////////////

#include "MeshHierarchy.h"

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

/**
 * \brief callback called when a new frame is encountered during the .x file load
 * \param Name - name of the frame
 * \param ppNewFrame - output pointer assign our newly created frame
 * \return success code
 * \author Keith Ditchburn \date 17 July 2005
*/
HRESULT CMeshHierarchy::CreateFrame(LPCSTR Name, LPD3DXFRAME *ppNewFrame)
{
	// Always a good idea to initialise return pointer before proceeding
	*ppNewFrame = NULL;

	// Create a new frame using the derived version of the structure
    D3DXFRAME_EXTENDED *newFrame = new D3DXFRAME_EXTENDED;
	ZeroMemory(newFrame,sizeof(D3DXFRAME_EXTENDED));

	// Now fill in the data members in the frame structure
	
	// The frame name (note: may be null or zero length)
	newFrame->Name=CUtilities::DuplicateCharString(Name);

    // Now initialize other data members of the frame to defaults
    D3DXMatrixIdentity(&newFrame->TransformationMatrix);
    D3DXMatrixIdentity(&newFrame->exCombinedTransformationMatrix);

    newFrame->pMeshContainer = NULL;
    newFrame->pFrameSibling = NULL;
    newFrame->pFrameFirstChild = NULL;

	// Assign the return pointer to our newly created frame
    *ppNewFrame = newFrame;
	
	// Output some debug info
#if defined(DEBUG) | defined(_DEBUG)
	WCHAR debugText[2048];
	WCHAR wideName[512];
	if (Name && strlen(Name)>0)
		CUtilities::FillWideStringFromCharString(Name,wideName,512);
	else
		StringCchPrintf(wideName,512,L"<no name>");

	StringCchPrintf(debugText,2048,L"Added frame %s\n",wideName);
	OutputDebugString(debugText);
#endif

    return S_OK;
}

/**
 * \brief callback called when a mesh data is encountered during the .x file load
 * \param Name - name of the Mesh (const char*)
 * \param pMeshData - the mesh data
 * \param pMaterials - material array
 * \param pEffectInstances - effect files / settings for the mesh
 * \param NumMaterials - number of materials in the mesh
 * \param pAdjacency - adjacency array 
 * \param pSkinInfo - skin info.
 * \param ppNewMeshContainer - output pointer to assign our newly created mesh container
 * \return success code
 * \author Keith Ditchburn \date 17 July 2005
*/
HRESULT CMeshHierarchy::CreateMeshContainer( THIS_
    LPCSTR Name,
    CONST D3DXMESHDATA *pMeshData,
    CONST D3DXMATERIAL *pMaterials,
    CONST D3DXEFFECTINSTANCE *pEffectInstances,
    DWORD NumMaterials,
    CONST DWORD *pAdjacency,
    LPD3DXSKININFO pSkinInfo,
    LPD3DXMESHCONTAINER* ppNewMeshContainer)
{    
	// Create a mesh container structure to fill and initilaise to zero values
	// Note: I use my extended version of the structure (D3DXMESHCONTAINER_EXTENDED)
	D3DXMESHCONTAINER_EXTENDED *newMeshContainer=new D3DXMESHCONTAINER_EXTENDED;
	ZeroMemory(newMeshContainer, sizeof(D3DXMESHCONTAINER_EXTENDED));

	// Always a good idea to initialise return pointer before proceeding
	*ppNewMeshContainer = NULL;

	// The mesh name (may be null) needs copying over
	newMeshContainer->Name=CUtilities::DuplicateCharString(Name);

	// Output some debug info
#if defined(DEBUG) | defined(_DEBUG)
	WCHAR debugText[2048];
	WCHAR wName[512];
	if (Name && strlen(Name)>0)
		CUtilities::FillWideStringFromCharString(Name,wName,512);
	else
		StringCchPrintf(wName,512,L"<no name>");
	
	StringCchPrintf(debugText,2048,L"Added Mesh %s\n",wName);
	OutputDebugString(debugText);
#endif

	// The mesh type (D3DXMESHTYPE_MESH, D3DXMESHTYPE_PMESH or D3DXMESHTYPE_PATCHMESH)
	if (pMeshData->Type!=D3DXMESHTYPE_MESH)
	{
		// This demo does not handle mesh types other than the standard
		// Other types are D3DXMESHTYPE_PMESH (progressive mesh) and D3DXMESHTYPE_PATCHMESH (patch mesh)
		DestroyMeshContainer(newMeshContainer);
		return E_FAIL;
	}

	newMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;
	
	// Adjacency data - holds information about triangle adjacency, required by the ID3DMESH object
	DWORD dwFaces = pMeshData->pMesh->GetNumFaces();
	newMeshContainer->pAdjacency = new DWORD[dwFaces*3];
	memcpy(newMeshContainer->pAdjacency, pAdjacency, sizeof(DWORD) * dwFaces*3);
	
	// Get the Direct3D device, luckily this is held in the mesh itself
	LPDIRECT3DDEVICE9 pd3dDevice = NULL;
	pMeshData->pMesh->GetDevice(&pd3dDevice);

	// Make a copy of the mesh data that is passed in and place it in our mesh container
	// 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(pMeshData->pMesh->GetDeclaration(Declaration)))
		return E_FAIL;

	//pMeshData->pMesh->CloneMeshFVF(D3DXMESH_MANAGED, 
	//	pMeshData->pMesh->GetFVF(), pd3dDevice, 
	//	&newMeshContainer->MeshData.pMesh);
	pMeshData->pMesh->CloneMesh(D3DXMESH_MANAGED, 
		Declaration, pd3dDevice, 
		&newMeshContainer->MeshData.pMesh);


	// Create material and texture arrays. Note that I always want to have at least one
	newMeshContainer->NumMaterials = max(NumMaterials,1);
	newMeshContainer->exMaterials = new D3DMATERIAL9[newMeshContainer->NumMaterials];
	newMeshContainer->exTextures  = new LPDIRECT3DTEXTURE9[newMeshContainer->NumMaterials];
	ZeroMemory(newMeshContainer->exTextures, sizeof(LPDIRECT3DTEXTURE9) * newMeshContainer->NumMaterials);

	if (NumMaterials>0)
	{
		// Load all the textures and copy the materials over		
		for(DWORD i = 0; i < NumMaterials; ++i)
		{
			newMeshContainer->exTextures[i] = NULL;	
			newMeshContainer->exMaterials[i]=pMaterials[i].MatD3D;

			if(pMaterials[i].pTextureFilename)
			{
				// Note: the texture filename in the mesh container is an LPSTR (char *) but this
				// demo uses UNICODE so convert to LPWSTR (WCHAR*) required by D3DXCreateTextureFromFileW
				WCHAR wszTexName[MAX_PATH];
				CUtilities::FillWideStringFromCharString(pMaterials[i].pTextureFilename,wszTexName,MAX_PATH);

				// Use the D3DX function to create the texturer
				if(FAILED(D3DXCreateTextureFromFile(pd3dDevice, wszTexName,&newMeshContainer->exTextures[i])))
				{
					OutputDebugString(L"Could not load texture\n");
					newMeshContainer->exTextures[i] = NULL;

					// Note: there is often pain finding the textures for .x files as sometimes the filename
					// includes the path and other times it does not. It is a cause of a lot of issues. By
					// saving your .x file as text you can look at it and see what the filenames are set to
					// which can help spot problems.

					// Normally in my code I have complex methods of looking for the textures in various
					// paths but it is beyond the scope of this demo so this demo simply looks in the 
					// directory the .x file is in (see code at top of CXFileEntity::LoadXFile for setting
					// current directory)
				}
			}
		}
	}
	else    
	// make a default material in the case where the mesh did not come with one
    {
		ZeroMemory(&newMeshContainer->exMaterials[0], sizeof( D3DMATERIAL9 ) );
        newMeshContainer->exMaterials[0].Diffuse.r = 0.5f;
        newMeshContainer->exMaterials[0].Diffuse.g = 0.5f;
        newMeshContainer->exMaterials[0].Diffuse.b = 0.5f;
        newMeshContainer->exMaterials[0].Specular = newMeshContainer->exMaterials[0].Diffuse;
		newMeshContainer->exTextures[0]=NULL;
    }

	// If there is skin data associated with the mesh copy it over
	if (pSkinInfo)
	{
		// save off the SkinInfo
	    newMeshContainer->pSkinInfo = pSkinInfo;
	    pSkinInfo->AddRef();

	    // Need an array of offset matrices to move the vertices from the figure space to the bone's space
	    UINT numBones = pSkinInfo->GetNumBones();
	    newMeshContainer->exBoneOffsets = new D3DXMATRIX[numBones];

		// Create the arrays for the bones and the frame matrices
		newMeshContainer->exFrameCombinedMatrixPointer = new D3DXMATRIX*[numBones];

	    // get each of the bone offset matrices so that we don't need to get them later
	    for (UINT i = 0; i < numBones; i++)
	        newMeshContainer->exBoneOffsets[i] = *(newMeshContainer->pSkinInfo->GetBoneOffsetMatrix(i));

		// Output some debug info
		#if defined(DEBUG) | defined(_DEBUG)
			WCHAR debugText[2048];
			StringCchPrintf(debugText,2048,L"Mesh has skinning info.\n Number of bones %d\n",numBones);
			OutputDebugString(debugText);
		#endif

        // Note: in the Microsoft samples a GenerateSkinnedMesh function is called here in order to prepare
		// the skinned mesh data for optimial hardware acceleration. As mentioned in the notes this sample
		// does not do hardware skinning but instead uses software skinning.
	}
	else	
	{
		// No skin info so null all the pointers
		newMeshContainer->pSkinInfo = NULL;
		newMeshContainer->exBoneOffsets = NULL;
		newMeshContainer->exSkinMesh = NULL;
		newMeshContainer->exFrameCombinedMatrixPointer = NULL;
	}

	// When we got the device we caused an internal reference count to be incremented
	// So we now need to release it
	SAFE_RELEASE(pd3dDevice);

	// The mesh may contain a reference to an effect file
	if (pEffectInstances)
	{
		if (pEffectInstances->pEffectFilename)
		{
			OutputDebugString(L"This .x file references an effect file. Effect files are not handled by this demo");
		}
	}
	
	// Set the output mesh container to our newly created one
	*ppNewMeshContainer = newMeshContainer;    

	return S_OK;
}

/**
 * \brief callback called to deallocate the frame data
 * \param the frame to free
 * \return success result
 * \author Keith Ditchburn \date 17 July 2005
*/
HRESULT CMeshHierarchy::DestroyFrame(LPD3DXFRAME pFrameToFree) 
{
	// Convert to our extended type. OK as we know for sure it is:
	D3DXFRAME_EXTENDED *pFrame = (D3DXFRAME_EXTENDED*)pFrameToFree;

	SAFE_DELETE_ARRAY( pFrame->Name );
    SAFE_DELETE( pFrame );

    return S_OK; 
}

/**
 * \brief callback called to deallocate the mesh container data
 * \param the mesh data to free
 * \return success result
 * \author Keith Ditchburn \date 17 July 2005
*/
HRESULT CMeshHierarchy::DestroyMeshContainer(LPD3DXMESHCONTAINER pMeshContainerBase)
{
	// Convert to our extended type. OK as we know for sure it is:
    D3DXMESHCONTAINER_EXTENDED* pMeshContainer = (D3DXMESHCONTAINER_EXTENDED*)pMeshContainerBase;
	
	// name
	SAFE_DELETE_ARRAY(pMeshContainer->Name)

	// material array
	SAFE_DELETE_ARRAY(pMeshContainer->exMaterials)

	// release the textures before deleting the array
	if(pMeshContainer->exTextures)
	{
		for(UINT i = 0; i < pMeshContainer->NumMaterials; ++i)
			SAFE_RELEASE(pMeshContainer->exTextures[i]);
	}

	// texture array
	SAFE_DELETE_ARRAY(pMeshContainer->exTextures)

	// adjacency data
	SAFE_DELETE_ARRAY(pMeshContainer->pAdjacency) 
	
	// bone parts
	SAFE_DELETE_ARRAY(pMeshContainer->exBoneOffsets)
	
	// frame matrices
	SAFE_DELETE_ARRAY(pMeshContainer->exFrameCombinedMatrixPointer)
	
	// release skin mesh
	SAFE_RELEASE(pMeshContainer->exSkinMesh)
	
	// release the main mesh
	SAFE_RELEASE(pMeshContainer->MeshData.pMesh)
	
	// release skin information
	SAFE_RELEASE(pMeshContainer->pSkinInfo)
	
	// finally delete the mesh container itself
	SAFE_DELETE(pMeshContainer);

    return S_OK;
}

/**
 * \brief makes a new character string from the passed one
 * \param c_str - the null terminated char string
 * \return the new string. Caller is responsible for freeing the memory
 * \author Keith Ditchburn \date 17 July 2005
*/
char* CUtilities::DuplicateCharString(const char* c_str)
{
    if (!c_str)
		return NULL;

	size_t len=strlen(c_str) + 1;
	char *newString = new char[len];
	memcpy(newString, c_str, len*sizeof(char));

	return newString;
}

/**
 * \brief takes a char string and creates a wide string from it
 * \param c_str - the null terminated char string
 * \return the new string. Called is responsible for deleting memory
 * \author Keith Ditchburn \date 17 July 2005
*/
WCHAR* CUtilities::CreateWideStringFromCharString(const char *c_str)
{
	if (!c_str)
		return NULL;

	size_t len=strlen(c_str)+1;
	WCHAR *wideString=new WCHAR[len];

	if( MultiByteToWideChar( CP_ACP, 0, c_str, -1, wideString, (int)len ) == 0 )
		return NULL;

	return wideString;
}

/**
 * \brief takes a char string and creates a wide string from it
 * \param c_str - the null terminated char string
 * \param widestring - the wide string to write to
 * \param wideStringLen - the length of the wide string
 * \return the new string. Caller is responsible for deleting memory.
 * \author Keith Ditchburn \date 17 July 2005
*/
bool CUtilities::FillWideStringFromCharString(const char *c_str,WCHAR *wideString,size_t wideStringLen)
{
	if (!c_str || !wideString)
		return false;

	if( MultiByteToWideChar( CP_ACP, 0, c_str, -1, wideString, (int)wideStringLen ) == 0 )
		return false;

	return true;
}