#include "dxstdafx.h"
#include "RTEffectsCommon.h"
#include "XObject.h"

#pragma once


//--------------------------------------------------------------------------------------
// Global variables
//--------------------------------------------------------------------------------------
// Wanted vertex declaration for a model (trying to have a 32k size to enhance vertex caching)
const D3DVERTEXELEMENT9 pObjectVertexElement[7] =
{
	{ 0,  0, D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION,	0},
	{ 0, 12, D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_NORMAL,	0},
	{ 0, 24, D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TANGENT,	0},
	{ 0, 36, D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_BINORMAL,	0},
	{ 0, 48, D3DDECLTYPE_FLOAT2,	D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD,	0},
	{ 0, 56, D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_COLOR,		0},
	D3DDECL_END()
};


//------------------------------------------------------------------------------------------
// Constructor
// Basically, we'll load an X file and compute tangent space if needed + do classical optims
//------------------------------------------------------------------------------------------
XObject::XObject(LPCWSTR sFileName)
{
	// Init the class members
	m_pXMesh = NULL;
	m_pMeshMaterials = NULL;
	m_dwNumMaterials = 0;
	m_pMeshTextures = NULL;
	m_pMeshVertDecl = NULL;

	// Device validity testing
	LPDIRECT3DDEVICE9 pd3dDevice = DXUTGetD3DDevice();
	if(!pd3dDevice) return;

	// In case the Xfile doesn't contains full path infos we need to find its ressources directory
	WCHAR sPath[260];

	//Get the X file full path
	DXUTFindDXSDKMediaFileCch(sPath, sizeof(sPath)/sizeof(WCHAR), sFileName);
    
	// Then cut the XFile name of it
	WCHAR * pLastSeparator = wcsrchr(sPath, L'\\');
    
	// If we found a "\"
	if(pLastSeparator)
		*(pLastSeparator + 1) = L'\0';
    else
		// Searching for a "/"
		pLastSeparator = wcsrchr(sPath, L'/');
	
	// If we found something
	if(pLastSeparator)
		*(pLastSeparator + 1) = L'\0';
	else
		*sPath = L'\0';

	//------------LOADING AN D3DXMESH FROM AN XFILE-------------//
	// Creating a generic D3DXBuffer to retrieve materials
	LPD3DXBUFFER pD3DXMtrlBuffer = NULL;

	// Load the mesh from the specified file
	if(FAILED(D3DXLoadMeshFromX(sFileName, D3DXMESH_32BIT|D3DXMESH_MANAGED, pd3dDevice, NULL, &pD3DXMtrlBuffer, NULL, &m_dwNumMaterials, &m_pXMesh)))
	{
		m_pXMesh = NULL;
		MessageBox(NULL, sFileName, L"Error loading X-File", MB_OK);
	}
	else
	{
		// We need to extract the material properties and texture names from the pD3DXMtrlBuffer
		D3DXMATERIAL * d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
		m_pMeshMaterials = new D3DMATERIAL9[m_dwNumMaterials];
		if(m_pMeshMaterials == NULL) MessageBox(NULL, L"Out of Memory", L"Framework", MB_OK);

		m_pMeshTextures  = new LPDIRECT3DTEXTURE9[m_dwNumMaterials];
		if(m_pMeshTextures == NULL) MessageBox(NULL, L"Out of Memory", L"Framework", MB_OK);

		CHAR sTexturePath[100];
		for(DWORD i=0; i<m_dwNumMaterials; ++i)
		{
			// Copy the material
			m_pMeshMaterials[i] = d3dxMaterials[i].MatD3D;

			// Set the ambient color for the material (D3DX does not do this)
			m_pMeshMaterials[i].Ambient = m_pMeshMaterials[i].Diffuse;

			// Load texture if there are any
			m_pMeshTextures[i] = NULL;
			if(d3dxMaterials[i].pTextureFilename != NULL && lstrlenA(d3dxMaterials[i].pTextureFilename) > 0)
			{
				// Convert the model path from WCHAR to CHAR, then cat it the texture name
				WideCharToMultiByte(GetACP(), 0, sPath, -1, sTexturePath, sizeof(sTexturePath)/sizeof(CHAR), NULL, NULL );
				strcat_s(sTexturePath, d3dxMaterials[i].pTextureFilename);

				// Create the texture
				if(FAILED(D3DXCreateTextureFromFileA(pd3dDevice, sTexturePath, &m_pMeshTextures[i])))
					MessageBox(NULL, L"Could not find texture map", L"Framework.exe", MB_OK);
			}
		}

		// Done with the material buffer
		SAFE_RELEASE(pD3DXMtrlBuffer);
	}

	// Uniformize the mesh vertex declaration
	UniformizeMeshDeclaration();

	// Finally, optimise the mesh for vertex cache
	DWORD* rgdwAdjacencyIn = new DWORD[m_pXMesh->GetNumFaces()*3];
	if(rgdwAdjacencyIn!=NULL)
	{
		m_pXMesh->GenerateAdjacency(1e-6f, rgdwAdjacencyIn);
		m_pXMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, rgdwAdjacencyIn, NULL, NULL, NULL);
		delete[] rgdwAdjacencyIn;
	}
}


//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
XObject::~XObject()
{
	unsigned int i;

	// Destroy the light control
	CDXUTDirectionWidget::StaticOnDestroyDevice();

	// Release the textures
	for(i=0; i<m_dwNumMaterials; ++i)
		SAFE_RELEASE(m_pMeshTextures[i]);
	delete [] m_pMeshTextures;
	m_pMeshTextures = NULL;

	// Destroy the materials
	delete [] m_pMeshMaterials;
	m_pMeshMaterials = NULL;

	// Release the mesh and its vertex declaration
	SAFE_RELEASE(m_pXMesh);
	SAFE_RELEASE(m_pMeshVertDecl);
}


//--------------------------------------------------------------------------------------
// Render the mesh
//--------------------------------------------------------------------------------------
HRESULT XObject::RenderMesh(RTEffectsCommon * pRTEffect) const
{
	HRESULT hr;

	if(!m_pXMesh || !pRTEffect) return D3DERR_INVALIDCALL;

	// We first retrieve a pointer on the LPD3DXEFFECT
	LPD3DXEFFECT pEffect = pRTEffect->getEffect();

	// Effect pass init
	UINT iPass, cPasses;
	V_RETURN(pEffect->Begin(&cPasses, 0));

	for (iPass=0; iPass<cPasses; ++iPass)
	{
		// Begin the effet pass
		V_RETURN(pEffect->BeginPass(iPass));

		// For every mesh material
		for(unsigned int i=0; i<m_dwNumMaterials; ++i)
		{
			// Setup textures
			V_RETURN(pEffect->SetTexture(pRTEffect->GetDiffuseTextureHandle(), m_pMeshTextures[i]));

			// Commit the effect parameters changes
			V_RETURN(pEffect->CommitChanges());

			// Draw the mesh subset
			V_RETURN(m_pXMesh->DrawSubset(i));
		}

		// End the effect pass
		V_RETURN(pEffect->EndPass());
	}
	
	// End the effect
	return (pEffect->End());
}


//--------------------------------------------------------------------------------------
// Render the mesh geometry (for Z-PrePass)
//--------------------------------------------------------------------------------------
HRESULT XObject::ZPassRender(RTEffectsCommon * pRTEffect) const
{
	HRESULT hr;

	if(!m_pXMesh || !pRTEffect) return D3DERR_INVALIDCALL;

	// We first retrieve a pointer on the LPD3DXEFFECT
	LPD3DXEFFECT pEffect = pRTEffect->getEffect();

	// Effect pass init
	UINT iPass, cPasses;
	V_RETURN(pEffect->Begin(&cPasses, 0));

	for (iPass=0; iPass<cPasses; ++iPass)
	{
		// Begin the effet pass
		V_RETURN(pEffect->BeginPass(iPass));

		// For every mesh material
		for(unsigned int i=0; i<m_dwNumMaterials; ++i)
		{
			// Draw the mesh subset
			// TODO : manage alpha surfaces that Z-Prepass MUST exclude
			V_RETURN(m_pXMesh->DrawSubset(i));
		}

		// End the effect pass
		V_RETURN(pEffect->EndPass());
	}
	
	// End the effect
	return (pEffect->End());
}


//--------------------------------------------------------------------------------------
// Adapat the loaded mesh to our needed vertex declaration if needed
//--------------------------------------------------------------------------------------
HRESULT XObject::UniformizeMeshDeclaration()
{
	HRESULT hr;

	// Device validity testing
	LPDIRECT3DDEVICE9 pd3dDevice = DXUTGetD3DDevice();
	if(!pd3dDevice || !m_pXMesh) return D3DERR_INVALIDCALL;

	// Checking if the mesh is missing some needed elements
	D3DVERTEXELEMENT9 decl[MAX_FVF_DECL_SIZE];
	V_RETURN(m_pXMesh->GetDeclaration(decl));
	
	short nPos = -1, nNorm = -1, nCol = -1, nTex = -1, nTan = -1, nBi = -1; // we need these ones
	for(short nVE= 0; nVE<MAX_FVF_DECL_SIZE && decl[nVE].Stream!=255; ++nVE)
	{
		if(decl[nVE].Usage == D3DDECLUSAGE_POSITION	&& decl[nVE].UsageIndex == 0){ nPos	= nVE; }
		if(decl[nVE].Usage == D3DDECLUSAGE_NORMAL	&& decl[nVE].UsageIndex == 0){ nNorm= nVE; }
		if(decl[nVE].Usage == D3DDECLUSAGE_COLOR	&& decl[nVE].UsageIndex == 0){ nCol = nVE; }
		if(decl[nVE].Usage == D3DDECLUSAGE_TEXCOORD	&& decl[nVE].UsageIndex == 0){ nTex = nVE; }
		if(decl[nVE].Usage == D3DDECLUSAGE_TANGENT	&& decl[nVE].UsageIndex == 0){ nTan = nVE; }
		if(decl[nVE].Usage == D3DDECLUSAGE_BINORMAL	&& decl[nVE].UsageIndex == 0){ nBi	= nVE; }
	}
	
	// If something is missing from the mesh vertex declaration
	if( nPos<0 || nNorm<0 || nCol<0 || nTex<0 || nTan<0 || nBi<0 )
	{
		// We clone the mesh with our complete vertex declaration...
		LPD3DXMESH pUniformizedMesh = NULL;
		V_RETURN(m_pXMesh->CloneMesh(D3DXMESH_32BIT|D3DXMESH_MANAGED|m_pXMesh->GetOptions(), pObjectVertexElement, pd3dDevice, &pUniformizedMesh));

		// If tangent space is missing/incomplete, we complete it
		if( nNorm<0 || nBi<0 || nTan<0 )
		{
			// ...then we compute the tangent space (normal, tangent and binormal) missing elements, using the adjacency list with the new cleaned mesh
			DWORD dwOptions = !(D3DXTANGENT_WIND_CW) | D3DXTANGENT_GENERATE_IN_PLACE | !(D3DXTANGENT_WEIGHT_BY_AREA | D3DXTANGENT_WEIGHT_EQUAL) & !(D3DXTANGENT_ORTHOGONALIZE_FROM_U | D3DXTANGENT_ORTHOGONALIZE_FROM_V) & !(D3DXTANGENT_WRAP_UV) & !(D3DXTANGENT_DONT_NORMALIZE_PARTIALS);
			if(nNorm<0) dwOptions|=D3DXTANGENT_CALCULATE_NORMALS;
			V_RETURN( D3DXComputeTangentFrameEx( pUniformizedMesh
												,D3DDECLUSAGE_TEXCOORD, 0
												,(nTan>-1)?D3DX_DEFAULT:D3DDECLUSAGE_TANGENT, 0	// Only computing tangents if missings
												,(nBi>-1)?D3DX_DEFAULT:D3DDECLUSAGE_BINORMAL, 0	// Only computing binormals if missings
												,(nNorm>-1)?D3DX_DEFAULT:D3DDECLUSAGE_NORMAL, 0	// Only computing normals if missings
												,dwOptions
												,NULL
												,-1.01f	// aka : cos 180
												,1e-6f	// vertices spacing before considering as overlapping
												,-1.01f	//
												,NULL, NULL )
												);
		}

		// ...then we replace the current mesh by its cloned one
		SAFE_RELEASE(m_pXMesh);
		m_pXMesh = pUniformizedMesh;
	}

	// Finally , create the mesh vertex declaration
	m_pXMesh->GetDeclaration(decl);								// Retrieve the (adapted) mesh vertex declaration
	pd3dDevice->CreateVertexDeclaration(decl, &m_pMeshVertDecl);// Finally, create the vertex declaration to use this mesh

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Compute the mesh bounding sphere
//--------------------------------------------------------------------------------------
HRESULT XObject::ComputeBoundingSphere(D3DXVECTOR3 * pSphereCenter, FLOAT * pSphereRadius)
{
	HRESULT hr;

	// Return failure if no mesh pointer provided
	if(!m_pXMesh) return D3DERR_INVALIDCALL;

	// Get the face count
	DWORD numVerts = m_pXMesh->GetNumVertices();

	// Get the vertex declaration
	D3DVERTEXELEMENT9 decl[MAX_FVF_DECL_SIZE];
	m_pXMesh->GetDeclaration(decl);
	DWORD declSize = D3DXGetDeclVertexSize(decl, 0);

	// Lock the vertex buffer
	BYTE *ptr = NULL;
	V_RETURN(m_pXMesh->LockVertexBuffer(D3DLOCK_READONLY, (VOID**)(&ptr)));

	// Compute bounding sphere
	V_RETURN(D3DXComputeBoundingSphere((D3DXVECTOR3 *)ptr, numVerts, declSize, pSphereCenter, pSphereRadius));

	// Unlock the vertex buffer
	V_RETURN(m_pXMesh->UnlockVertexBuffer());

	// Success
	return S_OK;
}