
// Purpose: This is an Allocation class that is
//		used with the D3DXLoadMeshHierarchyFromX
//		function. It handles the Creation and Deletion
//		of Frames and Mesh Containers. The overloaded
//		functions are callbacks, so there is no need
//		to call any of the functions in written code
//		just pass an Object of this class to the function

#include <d3dx9.h>
#include <tchar.h>
#include <dxerr9.h>
#include "CAllocateHierarchy.h"

#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } }
#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } }
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }

static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}


HRESULT
AllocateName( LPCSTR Name, LPSTR *pNewName )
{
	UINT cbLength;

	if( Name != NULL )
	{
		cbLength = (UINT)strlen( Name ) + 1;
		*pNewName = new CHAR[cbLength];
		if( *pNewName == NULL )
			return E_OUTOFMEMORY;
		memcpy( *pNewName, Name, cbLength * sizeof( CHAR ) );
	}
	else
	{
		*pNewName = NULL;
	}
	return S_OK;
}

HRESULT
CAllocateHierarchy::CreateFrame( LPCTSTR Name, LPD3DXFRAME *ppNewFrame )
{    
	HRESULT hr = S_OK;
	D3DXFRAME_DERIVED *pFrame;

	*ppNewFrame = NULL;

	pFrame = new D3DXFRAME_DERIVED;
	if( pFrame == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}

	hr = AllocateName( Name, &pFrame->Name );
	if( FAILED( hr ) )
		goto e_Exit;

	// initialize other data members of the frame
	D3DXMatrixIdentity( &pFrame->TransformationMatrix );
	D3DXMatrixIdentity( &pFrame->CombinedTransformationMatrix );

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

	*ppNewFrame = pFrame;
	pFrame = NULL;

e_Exit:
	
	delete pFrame;

	return hr;
}

/*
HRESULT CAllocateHierarchy::CreateMeshContainer( LPCTSTR Name, LPD3DXMESHDATA pMeshData, LPD3DXMATERIAL pMaterials, 
																								LPD3DXEFFECTINSTANCE pEffectInstances, DWORD NumMaterials, 
																								DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer) 
*/
HRESULT CAllocateHierarchy::CreateMeshContainer(
    LPCSTR Name, 
    CONST D3DXMESHDATA *pMeshData,
    CONST D3DXMATERIAL *pMaterials, 
    CONST D3DXEFFECTINSTANCE *pEffectInstances, 
    DWORD NumMaterials, 
    CONST DWORD *pAdjacency, 
    LPD3DXSKININFO pSkinInfo, 
    LPD3DXMESHCONTAINER *ppNewMeshContainer) 
{
	HRESULT											hr;
	D3DXMESHCONTAINER_DERIVED*	pMeshContainer = NULL;
	UINT												NumFaces;
	UINT												iMaterial;
	UINT												iBone, cBones;
	LPDIRECT3DDEVICE9						pd3dDevice = NULL;
	LPD3DXMESH									pMesh = NULL;

	*ppNewMeshContainer = NULL;

	// this sample does not handle patch meshes, so fail when one is found
	if( pMeshData->Type != D3DXMESHTYPE_MESH )
	{
		hr = E_FAIL;
		goto e_Exit;
	}

	// get the pMesh interface pointer out of the mesh data structure
	pMesh = pMeshData->pMesh;

	// this sample does not FVF compatible meshes, so fail when one is found
	if( pMesh->GetFVF() == 0 )
	{
		hr = E_FAIL;
		goto e_Exit;
	}

	// allocate the overloaded structure to return as a D3DXMESHCONTAINER
	pMeshContainer = new D3DXMESHCONTAINER_DERIVED;
	if( pMeshContainer == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}
	memset( pMeshContainer, 0, sizeof( D3DXMESHCONTAINER_DERIVED ) );

	// make sure and copy the name.  All memory as input belongs to caller, interfaces can be addref'd though
	hr = AllocateName( Name, &pMeshContainer->Name );
	if( FAILED( hr ) )
		goto e_Exit;        

	pMesh->GetDevice( &pd3dDevice );
	NumFaces = pMesh->GetNumFaces();

	// if no normals are in the mesh, add them
/*	if( !(pMesh->GetFVF() & D3DFVF_NORMAL) )
	{
		pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

		// clone the mesh to make room for the normals
		hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), pMesh->GetFVF() | D3DFVF_NORMAL, pd3dDevice, &pMeshContainer->MeshData.pMesh );
		if( FAILED( hr ) )
			goto e_Exit;

		// get the new pMesh pointer back out of the mesh container to use
		// NOTE: we do not release pMesh because we do not have a reference to it yet
		pMesh = pMeshContainer->MeshData.pMesh;

		// now generate the normals for the pmesh
		D3DXComputeNormals( pMesh, NULL );
	}
	else  // if no normals, just add a reference to the mesh for the mesh container
	{
		pMeshContainer->MeshData.pMesh = pMesh;
		pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

		pMesh->AddRef();
	}*/

	// if no normals are in the mesh, add them
	bool	bHasNormals = false;
	if( !(pMesh->GetFVF() & D3DFVF_NORMAL) )
		bHasNormals = true;


	{
		pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

		D3DVERTEXELEMENT9 NewDecl[] =
		{
				{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },				// size 12
				{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },					// size 12
				{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },				// size 8
				{ 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0 },				// size 12
				{ 0, 44, D3DDECLTYPE_UBYTE4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0 },	// size 4
				{ 0, 48, D3DDECLTYPE_FLOAT4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT,  0 },	// size 16
				D3DDECL_END()
		};

		// clone the mesh to make room for the normals
/*		hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), pMesh->GetFVF() | D3DFVF_NORMAL, pd3dDevice, &pMeshContainer->MeshData.pMesh );
		if( FAILED( hr ) )
			goto e_Exit;*/

		hr = pMesh->CloneMesh( pMesh->GetOptions(), NewDecl, pd3dDevice, &pMeshContainer->MeshData.pMesh );
		if( FAILED( hr ) )
		{
			TRACE( "failed to clone mesh: %s\n", DXGetErrorString9( hr ) );
			if( FAILED( hr ) )
				goto e_Exit;
		}

		// get the new pMesh pointer back out of the mesh container to use
		// NOTE: we do not release pMesh because we do not have a reference to it yet
		pMesh = pMeshContainer->MeshData.pMesh;

		// now generate the normals for the pmesh
		if( !bHasNormals )
			D3DXComputeNormals( pMesh, NULL );

		hr = D3DXComputeTangent( pMesh, 0, 0, 0, TRUE, NULL );
		if( FAILED( hr ) )
		{
			TRACE( "failed to calc tangents: %s\n", DXGetErrorString9( hr ) );
			goto e_Exit;
		}

		// generate tangents
	}

	// allocate memory to contain the material information.  This sample uses
	//   the D3D9 materials and texture names instead of the EffectInstance style materials
	pMeshContainer->NumMaterials = max( 1, NumMaterials );
	pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];
	pMeshContainer->pTextures = new DWORD[pMeshContainer->NumMaterials];
	pMeshContainer->pAdjacency = new DWORD[NumFaces * 3];
	if( (pMeshContainer->pAdjacency == NULL) || (pMeshContainer->pMaterials == NULL) )
	{
		hr = E_OUTOFMEMORY;
		goto e_Exit;
	}

	memcpy( pMeshContainer->pAdjacency, pAdjacency, sizeof( DWORD ) * NumFaces *3 );
	memset( pMeshContainer->pTextures, 0, sizeof( DWORD ) * pMeshContainer->NumMaterials );

	// if materials provided, copy them
	if( NumMaterials > 0 )            
	{
		memcpy( pMeshContainer->pMaterials, pMaterials, sizeof( D3DXMATERIAL ) * NumMaterials );

		for( iMaterial = 0; iMaterial < NumMaterials; iMaterial++ )
		{
			if( pMaterials[iMaterial].pTextureFilename != NULL )
			{
				// Duplicate texture name, to be used later.
				hr = AllocateName( pMaterials[iMaterial].pTextureFilename, &pMeshContainer->pMaterials[iMaterial].pTextureFilename );
				if( FAILED( hr ) )
					goto e_Exit;        
			}
		}
	}
	else // if no materials provided, use a default one
	{
		pMeshContainer->pMaterials[0].pTextureFilename = NULL;
		memset( &pMeshContainer->pMaterials[0].MatD3D, 0, sizeof( D3DMATERIAL9 ) );
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.r = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.g = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Diffuse.b = 0.5f;
		pMeshContainer->pMaterials[0].MatD3D.Specular = pMeshContainer->pMaterials[0].MatD3D.Diffuse;
	}

	// if there is skinning information, save off the required data and then setup for HW skinning
	if( pSkinInfo != NULL )
	{
		// first save off the SkinInfo and original mesh data
		pMeshContainer->pSkinInfo = pSkinInfo;
		pSkinInfo->AddRef();

		pMeshContainer->pOrigMesh = pMesh;
		pMesh->AddRef();

		// Will need an array of offset matrices to move the vertices from the figure space to the bone's space
		cBones = pSkinInfo->GetNumBones();
		pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones];
		if( pMeshContainer->pBoneOffsetMatrices == NULL )
		{
			hr = E_OUTOFMEMORY;
			goto e_Exit;
		}

		// get each of the bone offset matrices so that we don't need to get them later
		for( iBone = 0; iBone < cBones; iBone++ )
		{
			pMeshContainer->pBoneOffsetMatrices[iBone] = *(pMeshContainer->pSkinInfo->GetBoneOffsetMatrix(iBone));
		}

		// GenerateSkinnedMesh will take the general skinning information and transform it to a HW friendly version
		hr = GenerateSkinnedMesh( pd3dDevice, pMeshContainer );
		if( FAILED( hr ) )
			goto e_Exit;
	}
	else
	{
		// optimize mesh.

		LPD3DXMESH pOptMesh;
    hr = pMesh->Optimize( D3DXMESHOPT_VERTEXCACHE | D3DXMESH_MANAGED,
                                 pMeshContainer->pAdjacency,
                                 NULL, NULL, NULL, &pOptMesh );
		if( !FAILED( hr ) )
		{
			pMeshContainer->MeshData.pMesh->Release();
			pMeshContainer->MeshData.pMesh = pOptMesh;
		}
		else
		{
			// failed
			TRACE( "failed to clone mesh: %s\n", DXGetErrorString9( hr ) );
			goto e_Exit;
		}

/*
			LPD3DXMESH pMesh;
			hr = pMeshContainer->MeshData.pMesh->CloneMesh( pMeshContainer->MeshData.pMesh->GetOptions(), NewDecl, pd3dDevice, &pMesh );
			if( !FAILED( hr ) )
			{
				pMeshContainer->MeshData.pMesh->Release();
				pMeshContainer->MeshData.pMesh = pMesh;
				pMesh = NULL;
			}
*/
/*
    hr = pMeshSysMem->Optimize( dwOptFlags|D3DXMESH_SYSTEMMEM,
                                 (DWORD*)pAdjacencyBuffer->GetBufferPointer(),
                                 NULL, NULL, NULL, &pMeshData->m_pMeshSysMem);

	DWORD Flags = D3DXMESHOPT_VERTEXCACHE;
	Flags |= D3DXMESH_MANAGED;

	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );

	hr = pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh(
		pMeshContainer->pOrigMesh,
		Flags, 
		pMeshContainer->NumPaletteEntries, 
		pMeshContainer->pAdjacency, 
		NULL, NULL, NULL,             
		&pMeshContainer->NumInfl,
		&pMeshContainer->NumAttributeGroups, 
		&pMeshContainer->pBoneCombinationBuf, 
		&pMeshContainer->MeshData.pMesh );
	if( FAILED( hr ) )
		goto e_Exit;
																 */
	}
/*	else
	{
		D3DVERTEXELEMENT9 NewDecl[] =
		{
				{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },				// size 12
				{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },					// size 12
				{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },				// size 8
				{ 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0 },				// size 12
				{ 0, 44, D3DDECLTYPE_UBYTE4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0 },	// size 4
				{ 0, 48, D3DDECLTYPE_FLOAT4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT,  0 },	// size 16
				D3DDECL_END()
		};

		{
			LPD3DXMESH pMesh;
			hr = pMeshContainer->MeshData.pMesh->CloneMesh( pMeshContainer->MeshData.pMesh->GetOptions(), NewDecl, pd3dDevice, &pMesh );
			if( !FAILED( hr ) )
			{
				pMeshContainer->MeshData.pMesh->Release();
				pMeshContainer->MeshData.pMesh = pMesh;
				pMesh = NULL;
			}
		}

		hr = D3DXComputeTangent( pMeshContainer->MeshData.pMesh, 0, 0, 0, TRUE, NULL );
		if( FAILED( hr ) )
		{
			TRACE( "failed to calc tangents: %s\n", DXGetErrorString9( hr ) );
			goto e_Exit;
		}
	}*/

	*ppNewMeshContainer = pMeshContainer;
	pMeshContainer = NULL;

e_Exit:

	SAFE_RELEASE( pd3dDevice );

	// call Destroy function to properly clean up the memory allocated 
	if( pMeshContainer != NULL )
	{
		DestroyMeshContainer( pMeshContainer );
	}

	return hr;
}

HRESULT
CAllocateHierarchy::DestroyFrame( LPD3DXFRAME pFrameToFree )
{
  SAFE_DELETE_ARRAY( pFrameToFree->Name );
  SAFE_DELETE( pFrameToFree );
  return S_OK; 
}

HRESULT
CAllocateHierarchy::DestroyMeshContainer( LPD3DXMESHCONTAINER pMeshContainerBase )
{
	UINT iMaterial;
	D3DXMESHCONTAINER_DERIVED*	pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;

	SAFE_DELETE_ARRAY( pMeshContainer->Name );
	SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency );
	SAFE_DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices );

	// release all the allocated textures
//	if( pMeshContainer->ppTextures != NULL )
	{
		for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ )
		{
			SAFE_DELETE_ARRAY( pMeshContainer->pMaterials[iMaterial].pTextureFilename );
//			SAFE_RELEASE( pMeshContainer->ppTextures[iMaterial] );
		}
	}

	SAFE_DELETE_ARRAY( pMeshContainer->pMaterials );
//	SAFE_DELETE_ARRAY( pMeshContainer->ppTextures );
	SAFE_DELETE_ARRAY( pMeshContainer->pTextures );
	SAFE_DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs );
	SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );
	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
	SAFE_RELEASE( pMeshContainer->pSkinInfo );
	SAFE_RELEASE( pMeshContainer->pOrigMesh );
	SAFE_DELETE( pMeshContainer );
	return S_OK;
}

HRESULT
CAllocateHierarchy::GenerateSkinnedMesh( LPDIRECT3DDEVICE9 pd3dDevice, D3DXMESHCONTAINER_DERIVED *pMeshContainer )
{
	HRESULT hr = S_OK;

	if( pMeshContainer->pSkinInfo == NULL )
		return hr;

	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
	SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf );

	// Get palette size
	// First 9 constants are used for other data.  Each 4x3 matrix takes up 3 constants.
	// (96 - 9) /3 i.e. Maximum constant count - used constants 
	UINT MaxMatrices = 20; 
	pMeshContainer->NumPaletteEntries = min( MaxMatrices, pMeshContainer->pSkinInfo->GetNumBones() );

	DWORD Flags = D3DXMESHOPT_VERTEXCACHE;
	Flags |= D3DXMESH_MANAGED;

/*
	DWORD Flags = D3DXMESHOPT_VERTEXCACHE;
	if( d3dCaps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) )
	{
		pMeshContainer->UseSoftwareVP = false;
		Flags |= D3DXMESH_MANAGED;
	}
	else
	{
		// Require HW support.
		hr = D3DERR_INVALIDCALL;
		goto e_Exit;
//		pMeshContainer->UseSoftwareVP = true;
//		Flags |= D3DXMESH_SYSTEMMEM;
	}
	*/

	SAFE_RELEASE( pMeshContainer->MeshData.pMesh );

	hr = pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh(
		pMeshContainer->pOrigMesh,
		Flags, 
		pMeshContainer->NumPaletteEntries, 
		pMeshContainer->pAdjacency, 
		NULL, NULL, NULL,             
		&pMeshContainer->NumInfl,
		&pMeshContainer->NumAttributeGroups, 
		&pMeshContainer->pBoneCombinationBuf, 
		&pMeshContainer->MeshData.pMesh );
	if( FAILED( hr ) )
		goto e_Exit;


  D3DVERTEXELEMENT9 NewDecl[] =
  {
      { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },				// size 12
      { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },					// size 12
      { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },				// size 8
      { 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0 },				// size 12
      { 0, 44, D3DDECLTYPE_UBYTE4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0 },	// size 4
      { 0, 48, D3DDECLTYPE_FLOAT4,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT,  0 },	// size 16
      D3DDECL_END()
  };

	// FVF has to match our declarator. Vertex shaders are not as forgiving as FF pipeline
/*	DWORD NewFVF = (pMeshContainer->MeshData.pMesh->GetFVF() & D3DFVF_POSITION_MASK) | D3DFVF_NORMAL | D3DFVF_TEX1 | D3DFVF_LASTBETA_UBYTE4;
	if( NewFVF != pMeshContainer->MeshData.pMesh->GetFVF() )
	{
		LPD3DXMESH pMesh;
		hr = pMeshContainer->MeshData.pMesh->CloneMeshFVF( pMeshContainer->MeshData.pMesh->GetOptions(), NewFVF, pd3dDevice, &pMesh );
		if( !FAILED( hr ) )
		{
			pMeshContainer->MeshData.pMesh->Release();
			pMeshContainer->MeshData.pMesh = pMesh;
			pMesh = NULL;
		}
	}
*/

	{
		LPD3DXMESH pMesh;
		hr = pMeshContainer->MeshData.pMesh->CloneMesh( pMeshContainer->MeshData.pMesh->GetOptions(), NewDecl, pd3dDevice, &pMesh );
		if( !FAILED( hr ) )
		{
			TRACE( "failed close mesh: %s\n", DXGetErrorString9( hr ) );

			pMeshContainer->MeshData.pMesh->Release();
			pMeshContainer->MeshData.pMesh = pMesh;
			pMesh = NULL;
		}
	}

/*  hr = D3DXComputeTangent( pMeshContainer->MeshData.pMesh, 0, 0, 0, TRUE, NULL );
	if( FAILED( hr ) )
	{
		TRACE( "failed to calc tangents: %s\n", DXGetErrorString9( hr ) );
		goto e_Exit;
	}*/

	D3DVERTEXELEMENT9		pDecl[MAX_FVF_DECL_SIZE];
	LPD3DVERTEXELEMENT9	pDeclCur;
	hr = pMeshContainer->MeshData.pMesh->GetDeclaration( pDecl );
	if( FAILED( hr ) )
	{
		TRACE( "failed get decl: %s\n", DXGetErrorString9( hr ) );
		goto e_Exit;
	}

	// the vertex shader is expecting to interpret the UBYTE4 as a D3DCOLOR, so update the type 
	//   NOTE: this cannot be done with CloneMesh, that would convert the UBYTE4 data to float and then to D3DCOLOR
	//          this is more of a "cast" operation
	pDeclCur = pDecl;
	while( pDeclCur->Stream != 0xff )
	{
		if( (pDeclCur->Usage == D3DDECLUSAGE_BLENDINDICES) && (pDeclCur->UsageIndex == 0) )
			pDeclCur->Type = D3DDECLTYPE_D3DCOLOR;
		pDeclCur++;
	}

	hr = pMeshContainer->MeshData.pMesh->UpdateSemantics( pDecl );
	if( FAILED( hr ) )
		goto e_Exit;

e_Exit:
	return hr;
}