

//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_LightShaft_System.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_TARGET_SM3

// Asura Platform-independent
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_TextureManagement.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Colour.h"

// Asura PC
#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_Effects_System.h"
#include "Rendering/Asura_Direct3D_EffectParameterBlock.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "Rendering/Asura_Direct3D_Shaders.h"
#include "Rendering/Asura_Direct3D_UserOptions.h"
#include "Rendering/Asura_Direct3D_Effects_System.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "SpecialFX/Asura_LightShaft_System.h"



class Asura_Direct3D_LightShaft_System
{
public:
	static void Initialise();
	static void Shutdown();
	static void Reset();
	static void RenderStart();
	static void RenderEnd();
	static void RenderLightShaft(Asura_LightShaft *pxLightShaft);

	static void	OnEffectLoaded(Asura_Direct3D_Effect* pxEffect);
	static void	OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect);
	static void	CaptureEffect(void);

private:
	static Direct3D_VertexDeclaration*	s_pxLightShaftVertexDecl;
	static Direct3D_VertexDeclaration*	s_pxLightShaftCoronaVertexDecl;
	static Asura_Direct3D_Effect*		s_pxLightShaftEffect;
	
	static D3DXHANDLE					s_xTextureHandle;
	static D3DXHANDLE					s_xTexture2Handle;
	static D3DXHANDLE					s_xDepthTextureHandle;
	static D3DXHANDLE					s_xOneOverScreenDimensionsHandle;
	static D3DXHANDLE					s_xPosOrientHandle;	
	static D3DXHANDLE					s_xConeGeometryHandle;
	static D3DXHANDLE					s_xNearColourHandle;
	static D3DXHANDLE					s_xFarColourHandle;
	static D3DXHANDLE					s_xTextureParamsHandle;

	static Direct3D_VertexBuffer*	    s_apxVertexBuffers[4];
	static int							s_aiNumVertices[4];
};

Direct3D_VertexDeclaration*	Asura_Direct3D_LightShaft_System::s_pxLightShaftVertexDecl			= 0;
Direct3D_VertexDeclaration*	Asura_Direct3D_LightShaft_System::s_pxLightShaftCoronaVertexDecl	= 0;
Asura_Direct3D_Effect*		Asura_Direct3D_LightShaft_System::s_pxLightShaftEffect				= 0;

D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xTextureHandle					= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xTexture2Handle					= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xDepthTextureHandle				= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xOneOverScreenDimensionsHandle	= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xPosOrientHandle				= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xConeGeometryHandle				= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xNearColourHandle				= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xFarColourHandle				= 0;
D3DXHANDLE					Asura_Direct3D_LightShaft_System::s_xTextureParamsHandle			= 0;


Direct3D_VertexBuffer*		Asura_Direct3D_LightShaft_System::s_apxVertexBuffers[4] = {0,0,0,0};			// 4 LODs
int							Asura_Direct3D_LightShaft_System::s_aiNumVertices[4];


void Asura_LightShaft_System::Platform_Initialise(void)									{ Asura_Direct3D_LightShaft_System::Initialise();	}
void Asura_LightShaft_System::Platform_Shutdown(void)									{ Asura_Direct3D_LightShaft_System::Shutdown();		}
void Asura_LightShaft_System::Platform_Reset(void)										{ Asura_Direct3D_LightShaft_System::Reset();		}
void Asura_LightShaft_System::Platform_RenderStart(void)								{ Asura_Direct3D_LightShaft_System::RenderStart();	}
void Asura_LightShaft_System::Platform_RenderEnd(void)									{ Asura_Direct3D_LightShaft_System::RenderEnd();	}
void Asura_LightShaft_System::Platform_RenderLightShaft(Asura_LightShaft *pxLightShaft) { Asura_Direct3D_LightShaft_System::RenderLightShaft(pxLightShaft); }



void Asura_Direct3D_LightShaft_System::Initialise()
{
	if( NULL == Asura_Direct3D::D3DDevice)
	{
		return;
	}
	
	Direct3D_VertexElement auVSD[] =
	{
		{	0,	0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_POSITION,	0 },		
		{	0, 12,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_TEXCOORD,	0 },		
		D3DDECL_END()
	};

	Asura_Direct3D::D3DDevice->CreateVertexDeclaration( auVSD, &(s_pxLightShaftVertexDecl) );

	Direct3D_VertexElement auVSD2[] =
	{
		{	0,	0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_POSITION,	0 },		
		{	0, 12,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_COLOR,		0 },
		{	0, 28,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_TEXCOORD,	0 },
		D3DDECL_END()
	};

	Asura_Direct3D::D3DDevice->CreateVertexDeclaration( auVSD2, &(s_pxLightShaftCoronaVertexDecl) );



	for (int iLOD = 0; iLOD<4; iLOD++)
	{	
		int iRadialSegments = 32;
		int iLateralSegments = 16;

		switch (iLOD)
		{
			case 1:
				iRadialSegments = 28;
				iLateralSegments = 12;
				break;
			case 2:
				iRadialSegments = 24;
				iLateralSegments = 8;
				break;
			case 3:
				iRadialSegments = 16;
				iLateralSegments = 2;
				break;
			default:
				break;
		}
		
		int iNumVerts = ((iRadialSegments+2) * 2) * iLateralSegments;

		s_aiNumVertices[iLOD] = iNumVerts;

		Asura_Direct3D::D3DDevice->CreateVertexBuffer( iNumVerts * sizeof(Asura_LightShaftVertex), D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &s_apxVertexBuffers[iLOD], NULL );		

		Asura_LightShaftVertex *pxVerts;
		if (SUCCEEDED(s_apxVertexBuffers[iLOD]->Lock(0,0, (void **)&pxVerts,0)))
		{
			Asura_LightShaft_System::CreateConeVertices(iRadialSegments, iLateralSegments, pxVerts, iNumVerts);

			s_apxVertexBuffers[iLOD]->Unlock();
		} else {
			Asura_Assert(0, "Failed to lock VB");
		}
		
	}

	CaptureEffect();
}


// ------------------------------------------------------------------------------------
// Shader loading + parameter getting
// ------------------------------------------------------------------------------------

void Asura_Direct3D_LightShaft_System::CaptureEffect(void)
{
#define LOADPARAMETER(codeName, shaderName) { codeName = s_pxLightShaftEffect->GetD3DEffect()->GetParameterByName(NULL, shaderName); Asura_Assert(codeName, "Could not get shader parameter '" shaderName "'."); }

	s_pxLightShaftEffect = Asura_Direct3D_Effects_System::GetEffect("lightshaft");

	if (s_pxLightShaftEffect)
	{	
#if ASURA_LOAD_EFFECT_FILES == 1
		s_pxLightShaftEffect->SetOnLoadCallback(Asura_Direct3D_LightShaft_System::OnEffectLoaded);
		s_pxLightShaftEffect->SetOnUnloadCallback(Asura_Direct3D_LightShaft_System::OnEffectUnloaded);
#endif

		LOADPARAMETER(s_xTextureHandle					, "g_xTexture" );
		LOADPARAMETER(s_xTexture2Handle					, "g_xTexture2" );
		LOADPARAMETER(s_xDepthTextureHandle				, "g_xDepthTexture" );
		LOADPARAMETER(s_xOneOverScreenDimensionsHandle  , "g_xOneOverScreenDimensions" );
		LOADPARAMETER(s_xPosOrientHandle				, "g_xPosOrient" );		
		LOADPARAMETER(s_xConeGeometryHandle				, "g_xConeGeometry" );
		LOADPARAMETER(s_xNearColourHandle				, "g_xNearColour" );
		LOADPARAMETER(s_xFarColourHandle				, "g_xFarColour" );
		LOADPARAMETER(s_xTextureParamsHandle			, "g_xTextureParams" );
	}
}

#if ASURA_LOAD_EFFECT_FILES == 1
void Asura_Direct3D_LightShaft_System::OnEffectLoaded(Asura_Direct3D_Effect* pxEffect)
{
	Asura_Direct3D_LightShaft_System::CaptureEffect();
}

void Asura_Direct3D_LightShaft_System::OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect)
{
	Asura_Direct3D_LightShaft_System::CaptureEffect();
}
#endif




void Asura_Direct3D_LightShaft_System::Shutdown()
{
	for(int i=0; i<4; i++)
	{
		SAFE_RELEASE( s_apxVertexBuffers[i]);
	}

	SAFE_RELEASE( s_pxLightShaftVertexDecl);

	SAFE_RELEASE( s_pxLightShaftCoronaVertexDecl);
}

void Asura_Direct3D_LightShaft_System::Reset()
{

}

void Asura_Direct3D_LightShaft_System::RenderStart()
{
	D3DPERFCALL( BeginEvent( D3DCOLOR_XRGB( 0xFF, 0xFF, 0xFF ), L"Light shafts" ) );

	SCOPEDPIXEVENT(( D3DCOLOR_XRGB( 0xFF, 0xFF, 0xFF ), L"Initial setup" ));

	const float 				fScreenWidth	= static_cast<float>(Asura_Viewport::GetWidth());
	const float 				fScreenHeight	= static_cast<float>(Asura_Viewport::GetHeight());

	// Setup initial renderstates
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);
	Asura_Render::CurrentStates.RequestAlphaTest(false);

	Asura_Render::ApplyCameraSettings();
	
	Asura_Direct3D::ClearRenderTargets( 1);

	// Constant shader params
	D3DXVECTOR4 xVec;
	xVec.x = 1.0f / fScreenWidth;
	xVec.y = 1.0f / fScreenHeight;
	s_pxLightShaftEffect->GetD3DEffect()->SetVector(s_xOneOverScreenDimensionsHandle, &xVec);
	s_pxLightShaftEffect->GetD3DEffect()->SetTexture(s_xDepthTextureHandle, Asura_Direct3D::GetDeferredRenderingTarget()->GetTexture(  ASURA_DIRECT3D_DEFFEDRENDERTARGET_NORMAL_INDEX));
	

	Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_CCW);

	Asura_Direct3D_Effects_System::BeginEffect(*s_pxLightShaftEffect);

}


void Asura_Direct3D_LightShaft_System::RenderEnd()
{
	{
		SCOPEDPIXEVENT(( D3DCOLOR_XRGB( 0xFF, 0xFF, 0xFF ), L"Cleanup" ));

		s_pxLightShaftEffect->GetD3DEffect()->SetTexture(s_xDepthTextureHandle, NULL);
		Asura_Direct3D_Effects_System::FlushEffects();

		Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_SRGBTEXTURE, FALSE );
		Asura_Direct3D::D3DDevice->SetSamplerState( 1, D3DSAMP_SRGBTEXTURE, FALSE );
	}

	D3DPERFCALL( EndEvent() ); // Light shafts
}

void Asura_Direct3D_LightShaft_System::RenderLightShaft(Asura_LightShaft *pxLightShaft)
{
	SCOPEDPIXEVENT(( D3DCOLOR_XRGB( 0xFF, 0xFF, 0xFF ),
					 L"Light shaft @ (%.01f, %.01f, %.01f)",
					 pxLightShaft->GetPosition().x,
					 pxLightShaft->GetPosition().y,
					 pxLightShaft->GetPosition().z ));

	Asura_Vector_3 xOfs = *Asura_Camera::GetPosition() - pxLightShaft->GetPosition();
	float fDist = xOfs.Magnitude();

	float fDistanceFade = 1.0f;
	if (fDist < pxLightShaft->GetCloseFadeStart())
	{
		fDistanceFade = 1.0f - ((pxLightShaft->GetCloseFadeStart() - fDist)/pxLightShaft->GetCloseFadeDist());
	}
	else if (fDist > pxLightShaft->GetDistanceFadeStart())
	{
		fDistanceFade = 1.0f - ((fDist - pxLightShaft->GetDistanceFadeStart())/pxLightShaft->GetDistanceFadeDist());
	}

	if (fDistanceFade <= 0.0f)
	{
		return;
	}


	D3DXMATRIX xPosOrient;
	Asura_Vector_3 xDir = Asura_Vector_3(0,0,1)*pxLightShaft->GetOrient();

//	Asura_Matrix_3x3 xTmp;
//	xTmp.Identity();
//	Asura_Render::RenderBB(pxLightShaft->GetBoundingBox(),Asura_Vector_3(0,0,0),xTmp);

	// Rotation	
	memset( &xPosOrient, 0, sizeof( xPosOrient ) );
	for( u_int u = 0; u < 3; ++u )
	{
		for( u_int v = 0; v < 3; ++v )
		{
			xPosOrient.m[u][v] = pxLightShaft->GetOrient().Element[v][u];
		}
	}
	xPosOrient._44 = 1.0f;

	// Translation
	Asura_Vector_3 xPos = pxLightShaft->GetPosition();
	D3DXMATRIX xTemp;
	D3DXMatrixTranslation( &xTemp, xPos.x, xPos.y, xPos.z );
	xPosOrient *= xTemp;
	s_pxLightShaftEffect->SetParameter(s_xPosOrientHandle, xPosOrient);

	
	Asura_RGBA xInheritedColour = pxLightShaft->GetInheritedColour();


	
	bool bShaftVisible = true;

	if  ((pxLightShaft->GetNearRadius() + pxLightShaft->GetFarRadius() <=0.0f) ||
		(pxLightShaft->GetNearDistance()) == pxLightShaft->GetFarDistance() ||
		((pxLightShaft->GetNearColour().ToUIntARGB()&0xffffff) == 0  && (pxLightShaft->GetFarColour().ToUIntARGB()&0xffffff) == 0))
	{
		bShaftVisible = false;
	}
	
	if (bShaftVisible)
	{
		// Cone geometry
		D3DXVECTOR4 xConeParams;
		xConeParams.x = pxLightShaft->GetNearRadius();
		xConeParams.y = pxLightShaft->GetFarRadius();		
		xConeParams.z = pxLightShaft->GetNearDistance();
		xConeParams.w = pxLightShaft->GetFarDistance();
		s_pxLightShaftEffect->SetParameter(s_xConeGeometryHandle, xConeParams);

		// Near/Far colours
		D3DXVECTOR4 xColour;
		Asura_RGBA  xRGBA = pxLightShaft->GetNearColour();
		xColour.x = xRGBA.m_fR*fDistanceFade*xInheritedColour.m_fR;
		xColour.y = xRGBA.m_fG*fDistanceFade*xInheritedColour.m_fG;
		xColour.z = xRGBA.m_fB*fDistanceFade*xInheritedColour.m_fB;
		xColour.w = xRGBA.m_fA*fDistanceFade*xInheritedColour.m_fA;
		s_pxLightShaftEffect->SetParameter(s_xNearColourHandle, xColour);

		xRGBA = pxLightShaft->GetFarColour();
		xColour.x = xRGBA.m_fR*fDistanceFade*xInheritedColour.m_fR;
		xColour.y = xRGBA.m_fG*fDistanceFade*xInheritedColour.m_fG;
		xColour.z = xRGBA.m_fB*fDistanceFade*xInheritedColour.m_fB;
		xColour.w = xRGBA.m_fA*fDistanceFade*xInheritedColour.m_fA;
		s_pxLightShaftEffect->SetParameter(s_xFarColourHandle, xColour);

		// Texture params
		D3DXVECTOR4 xTextureParams;
		xTextureParams.x = pxLightShaft->GetUScale(0);
		xTextureParams.y = pxLightShaft->GetUScale(1);
		xTextureParams.z = Asura_Maths::FloatMod(pxLightShaft->GetUScrollRate(0) * Asura_Timers::GetLocalScaledTime(), 1.0f);
		xTextureParams.w = Asura_Maths::FloatMod(pxLightShaft->GetUScrollRate(1) * Asura_Timers::GetLocalScaledTime(), 1.0f);

		s_pxLightShaftEffect->SetParameter(s_xTextureParamsHandle, xTextureParams);	

		// Textures
		int iTex = pxLightShaft->GetTextureIndex(0);
		s_pxLightShaftEffect->GetD3DEffect()->SetTexture(s_xTextureHandle,  Asura_Direct3D::GetDirect3DTextureByIndex(iTex));
		iTex = pxLightShaft->GetTextureIndex(1);
		s_pxLightShaftEffect->GetD3DEffect()->SetTexture(s_xTexture2Handle,  Asura_Direct3D::GetDirect3DTextureByIndex(iTex));
			

		// Draw the shaft
		// --------------------------------------------------
		Asura_Direct3D::D3DDevice->SetVertexDeclaration(s_pxLightShaftVertexDecl);	

		s_pxLightShaftEffect->GetD3DEffect()->BeginPass(0);
		s_pxLightShaftEffect->GetD3DEffect()->CommitChanges();

		// Pick a LOD
		float fSize = Asura_Maths::Max(pxLightShaft->GetFarDistance(), 2.0f*pxLightShaft->GetFarRadius());
		int iLOD = (int)(fDist*0.25f/fSize);
		iLOD = Asura_Maths::ClampToRange(iLOD,0,3);
		
		Asura_Direct3D::D3DDevice->SetStreamSource(0,s_apxVertexBuffers[iLOD],0,sizeof(Asura_LightShaftVertex));
		Asura_Direct3D::D3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, s_aiNumVertices[iLOD]-2);

		s_pxLightShaftEffect->GetD3DEffect()->EndPass();
	}

	// Now draw the corona
	// --------------------------------------------------
	int iTex = pxLightShaft->GetCoronaTextureIndex();

	if (iTex>=0)
	{	
		Asura_LightShaftCoronaVertex xVerts[4];

		float fSize;
		float fForwards;

		Asura_Vector_3 xFwd;
		xOfs.Normalise();
		float fDot = xDir*xOfs;

		float fAngleRange = pxLightShaft->GetCoronaAngleRange();
		float fAmount = fDistanceFade * ((fDot - (1.0f-fAngleRange))/fAngleRange);

		// Special feature to use these coronas for fog planes... set angle range to max,
		// and it's 'always on' with no fade up/down
		if (fAngleRange>=1.0f)
		{
			fAmount = 1.0f;
		}

		if (fAmount>0)
		{		
			Asura_Direct3D::D3DDevice->SetVertexDeclaration(s_pxLightShaftCoronaVertexDecl);
			
			s_pxLightShaftEffect->GetD3DEffect()->SetTexture(s_xTextureHandle,  Asura_Direct3D::GetDirect3DTextureByIndex(iTex));

			fSize = pxLightShaft->GetCoronaSize();
			fForwards = pxLightShaft->GetCoronaDepth();
			
			fSize *= (0.666f * (0.5f+fAmount));
			fForwards *= fAmount;
			
			Asura_RGBA xCol = pxLightShaft->GetCoronaColour();
			xCol.m_fR *= xInheritedColour.m_fR;
			xCol.m_fG *= xInheritedColour.m_fG;
			xCol.m_fB *= xInheritedColour.m_fB;
			xCol.m_fA = fAmount;

			for(int i=0;i<4;i++)
			{
				const float afLeft[4] = {-1,-1,1,1};
				const float afUp[4]   = {-1,1,1,-1};
				const float afU[4]    = { 0,0,1,1};
				const float afV[4]    = { 0,1,1,0};

				xVerts[i].xPos.x = afLeft[i] * fSize;
				xVerts[i].xPos.y = afUp[i] * fSize;
				xVerts[i].xPos.z = fForwards;
				xVerts[i].fU	 = afU[i];
				xVerts[i].fV	 = afV[i];
				xVerts[i].fDepth = fForwards;
				xVerts[i].fR	 = xCol.m_fR;
				xVerts[i].fG	 = xCol.m_fG;
				xVerts[i].fB	 = xCol.m_fB;
				xVerts[i].fA	 = xCol.m_fA;
			}
			

			s_pxLightShaftEffect->GetD3DEffect()->BeginPass(1);	
			s_pxLightShaftEffect->GetD3DEffect()->CommitChanges();

			Asura_Direct3D::D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, (void *)&xVerts[0], sizeof(Asura_LightShaftCoronaVertex));

			s_pxLightShaftEffect->GetD3DEffect()->EndPass();
		}
	}
}
			


#endif // ASURA_TARGET_SM3
