//*******************************************************************
//
// File: Asura_PlatformFSFX_SM30_MotionBlur.cpp        
//
// Created: 07 May 2009
//
// Description: 
//
//*******************************************************************

// system libraries
#include "Core/Asura.h"

#if defined( ASURA_USE_FSFX)

#ifdef ASURA_TARGET_SM3

// main header
#include "FSFX/Asura_FSFX_Component.h"
#include "FSFX/Asura_FSFX_System.h"
#include "FSFX/Asura_FSFX_Template.h"

#include "Camera/Asura_Camera.h"

#include "Core/Asura_Timers.h"

#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_BackBufferCapture.h"
#include "Rendering/Asura_Direct3D_Effects_System.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "Rendering/Asura_Direct3D_UserOptions.h"
#include "Rendering/Asura_Direct3D_Caps.h"
#include "Rendering/Asura_ViewPort.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Overlay_Quad.h"

#include "Interface/Asura_CommandConsole.h"

#include "FSFX/Asura_Platform_FSFX_SM30.h"

// other libraries
#include "SpecialFX/Asura_Glow_System.h"

//*******************************************************************
// Globals
//*******************************************************************

static Asura_Direct3D_Effect*		g_pxMotionBlurEffect			= NULL;

static D3DXHANDLE		g_xScreenTextureHandle						= NULL;

// parameters for the motion blur effect
static D3DXHANDLE		g_xDepthTextureHandle		= NULL;
static D3DXHANDLE		g_xOneOverScreenDimensions	= NULL;

static D3DXHANDLE		g_xInvViewMatrixHandle				= NULL;
static D3DXHANDLE		g_xViewProjMatrixHandle				= NULL;
static D3DXHANDLE		g_xPrevViewProjMatrixHandle			= NULL;
static D3DXHANDLE		g_xIntensityHandle					= NULL;
static D3DXHANDLE		g_xCameraTanHalfVertFOV				= NULL;
static D3DXHANDLE		g_xCameraTanHalfHorizFOV			= NULL;
static D3DXHANDLE		g_xTwoOverScreenWidth				= NULL;
static D3DXHANDLE		g_xTwoOverScreenHeight				= NULL;

static D3DXMATRIX		g_xBlurOldViewMatrix;

static bool				g_bBlurMatrixInitialised	= false;

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
static Asura_FSFX_Component_SM30_MotionBlur * g_pxLastThis	= NULL;
#endif

#if ASURA_LOAD_EFFECT_FILES == 1
void Asura_Platform_FSFX_SM30_MotionBlur_OnEffectLoaded(Asura_Direct3D_Effect* pxEffect);
void Asura_Platform_FSFX_SM30_MotionBlur_OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect);
#endif

void Asura_Platform_FSFX_SM30_MotionBlur_CaptureEffect(void)
{
	g_pxMotionBlurEffect = Asura_Direct3D_Effects_System::GetEffect("FSFX_SM30_MotionBlur");	

	if (!g_pxMotionBlurEffect)
	{
		return;
	}

#if ASURA_LOAD_EFFECT_FILES == 1
	g_pxMotionBlurEffect->SetOnLoadCallback(Asura_Platform_FSFX_SM30_MotionBlur_OnEffectLoaded);
	g_pxMotionBlurEffect->SetOnUnloadCallback(Asura_Platform_FSFX_SM30_MotionBlur_OnEffectUnloaded);
#endif

	g_xScreenTextureHandle		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xTexture" );

	g_xInvViewMatrixHandle		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xInvViewMatrix" );
	g_xViewProjMatrixHandle		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xViewProjMatrix" );
	g_xPrevViewProjMatrixHandle	= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xPrevViewProjMatrix" );
	g_xCameraTanHalfVertFOV		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_fCameraTanHalfVertFOV" );
	g_xCameraTanHalfHorizFOV	= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_fCameraTanHalfHorizFOV" );
	g_xTwoOverScreenWidth		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_fTwoOverScreenWidth" );
	g_xTwoOverScreenHeight		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_fTwoOverScreenHeight" );
	g_xIntensityHandle			= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_fIntensity" );
	g_xDepthTextureHandle		= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xDepthTexture" );
	g_xOneOverScreenDimensions	= g_pxMotionBlurEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xOneOverScreenDimensions" );

	g_bBlurMatrixInitialised = false;

//#undef LOADPARAMETER
}

#if ASURA_LOAD_EFFECT_FILES == 1
void Asura_Platform_FSFX_SM30_MotionBlur_OnEffectLoaded(Asura_Direct3D_Effect* pxEffect)
{
	Asura_Platform_FSFX_SM30_MotionBlur_CaptureEffect();
}

void Asura_Platform_FSFX_SM30_MotionBlur_OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect)
{
	g_pxMotionBlurEffect = NULL;
}
#endif

void Asura_FSFX_Component_SM30_MotionBlur::Platform_Initialise()
{
}

void Asura_FSFX_Component_SM30_MotionBlur::Platform_Reset()
{
	Platform_Shutdown();
	Platform_Initialise();
}

void Asura_FSFX_Component_SM30_MotionBlur::Platform_Shutdown()
{
}

static void LerpD3DXMatrix( D3DXMATRIX* pxDest, const D3DXMATRIX* pxFrom, const D3DXMATRIX* pxTo, float fLerpK )
{
	float fOneMinusLerpK = 1.0f - fLerpK;
	float* paDest	= (float*)pxDest;
	float* paFrom	= (float*)pxFrom;
	float* paTo		= (float*)pxTo;
	for( int i = 0; i < 16; i++ )
		paDest[i] = fOneMinusLerpK * paFrom[i] + fLerpK * paTo[i];
}

static float GetTCLerpK( float fTimeDelta, float fLerpRate )
{
	return 1.0f - expf( -fTimeDelta * fLerpRate );
}

/*
static void RenormalizeMatrix( D3DXMATRIX * pxMatrix )
{
	D3DXVECTOR3& xRowX = *(D3DXVECTOR3*)(&pxMatrix->_11);
	D3DXVECTOR3& xRowY = *(D3DXVECTOR3*)(&pxMatrix->_21);
	D3DXVECTOR3& xRowZ = *(D3DXVECTOR3*)(&pxMatrix->_31);
	D3DXVec3Normalize( &xRowX, &xRowX );
	D3DXVec3Normalize( &xRowY, &xRowY );
	D3DXVec3Cross( &xRowZ, &xRowY, &xRowX);
}
*/

static void UpdateBlurMatrix(const float fLerpSpeed)
{
	float fDeltaTime = Asura_Timers::GetGameTimeSlice();

	if( !g_bBlurMatrixInitialised )
	{
		g_xBlurOldViewMatrix = Asura_Direct3D::GetViewMatrix();
		g_bBlurMatrixInitialised = true;
	}

	const float fLerpK = GetTCLerpK( fDeltaTime, fLerpSpeed );
	const D3DXMATRIX& xViewMatrix = Asura_Direct3D::GetViewMatrix();
	LerpD3DXMatrix( &g_xBlurOldViewMatrix, &g_xBlurOldViewMatrix, &xViewMatrix, fLerpK );

	//not really needed, and creates inconsistency if the original viewMatrix is not normalized to begin with
	//RenormalizeMatrix( &g_xBlurOldViewMatrix );
}

void Asura_FSFX_Component_SM30_MotionBlur::Platform_Render( const float fIntensity )
{
	if ( fIntensity == 0.0f || g_pxMotionBlurEffect == NULL )
	{
		return;
	}

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
	if( g_pxLastThis != this )
	{
		g_pxLastThis = this;
		if( Asura_CommandConsole::VarExists(L"Render.MotionBlur.BlurAmount") )
			Asura_CommandConsole::RemoveVar(L"Render.MotionBlur.BlurAmount", true );
		Asura_CommandConsole::AddVar(L"Render.MotionBlur.BlurAmount", &m_fBlurAmount, 0.0f, 1.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	}
#endif

	SCOPEDPIXEVENT(( 0xFFA89C90, L"FSFX: Motion Blur" ));

	float fLerpSpeed = 10.0f / Asura_Maths::Max( m_fBlurAmount, 1e-5f );
	UpdateBlurMatrix( fLerpSpeed );

	if ( !Asura_Direct3D_BackBufferCapture::HasCapturedThisFrame() )
	{
		Asura_Direct3D_BackBufferCapture::CaptureCurrentBackBuffer();
	}
	Asura_Render::AllowFullScreenRendering();

	//// Clear any unused render targets (in case deferred shading targets are still active)
	unsigned int u;
	for (u = 1; u < Asura_Direct3D_Caps::GetMaxNumberOfRenderTargets(); ++u)
	{
		Asura_Direct3D::D3DDevice->SetRenderTarget(u, NULL);
	}

	const bool bSRGB = Asura_Direct3D::UseGammaCorrection();
	Asura_Direct3D::D3DDevice->SetSamplerState( 0, D3DSAMP_SRGBTEXTURE, bSRGB );

	// Set up the verts for a full-screen pass
	// ----------------------------------------------------------------------------------------------------
	const float fViewWidth		= static_cast< float> ( Asura_Direct3D::GetPresentParameters().BackBufferWidth);
	const float fViewHeight		= static_cast< float> ( Asura_Direct3D::GetPresentParameters().BackBufferHeight);
	const float fBufferWidth	= static_cast< float> ( fViewWidth  * Asura_Platform_FSFX_SM30::g_fBloomEffectBlurBufferScale );
	const float fBufferHeight	= static_cast< float> ( fViewHeight * Asura_Platform_FSFX_SM30::g_fBloomEffectBlurBufferScale);

	float fHalfTexelU = 0.5f/fViewWidth;
	float fHalfTexelV = 0.5f/fViewHeight;

	BloomVertex axVerts[4];

	axVerts[0].fX = -1.0f;     axVerts[0].fY =  1.0f;
	axVerts[1].fX = -1.0f;     axVerts[1].fY = -1.0f;
	axVerts[2].fX =  1.0f;     axVerts[2].fY =  1.0f; 
	axVerts[3].fX =  1.0f;     axVerts[3].fY = -1.0f; 

	fHalfTexelU = 0.5f/fBufferWidth;
	fHalfTexelV = 0.5f/fBufferHeight;

	float fUMax =  (fViewWidth  * Asura_Platform_FSFX_SM30::g_fBloomEffectBlurBufferScale)  / fBufferWidth;
	float fVMax =  (fViewHeight * Asura_Platform_FSFX_SM30::g_fBloomEffectBlurBufferScale)  / fBufferHeight;

	axVerts[0].fU = 0.0f+fHalfTexelU;	   axVerts[0].fV = 0.0f+fHalfTexelV;
	axVerts[1].fU = 0.0f+fHalfTexelU;	   axVerts[1].fV = fVMax+fHalfTexelV;
	axVerts[2].fU = fUMax+fHalfTexelU;	   axVerts[2].fV = 0.0f+fHalfTexelV;
	axVerts[3].fU = fUMax+fHalfTexelU;	   axVerts[3].fV = fVMax+fHalfTexelV;

	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);

	Asura_Direct3D_Effects_System::BeginEffect(*g_pxMotionBlurEffect);
	Asura_Assert(g_pxMotionBlurEffect->IsD3DEffectValid(), "bloom effect invalid");

	Asura_Direct3D::D3DDevice->SetVertexDeclaration(Asura_Platform_FSFX_SM30::g_pxBloomVertexDecl);

	if (g_pxMotionBlurEffect->IsD3DEffectValid())
	{
		g_pxMotionBlurEffect->SetParameterTexture( g_xScreenTextureHandle, Asura_Direct3D_BackBufferCapture::GetCaptureTexture() );
		g_pxMotionBlurEffect->GetD3DEffect()->SetFloat( g_xIntensityHandle, fIntensity );

		{
			D3DXMATRIX xViewMatrix;
			D3DXMATRIX xViewMatrixInv;
			xViewMatrix = Asura_Direct3D::GetViewMatrix( );
			D3DXMatrixInverse( &xViewMatrixInv, NULL, &xViewMatrix );

			g_pxMotionBlurEffect->GetD3DEffect()->SetMatrix( g_xInvViewMatrixHandle, &xViewMatrixInv );

			const D3DXMATRIX& xViewProjMatrix = Asura_Direct3D::GetViewProjectionMatrix();
			g_pxMotionBlurEffect->GetD3DEffect()->SetMatrix( g_xViewProjMatrixHandle, &xViewProjMatrix );

			/*
			D3DXMATRIX xBlurMatrix = g_xBlurOldViewMatrix;
			This is not correct.... think it over.
			xBlurMatrix._41 = Asura_Maths::Lerp( xBlurMatrix._41, xViewMatrix._41, m_fIgnoreMovementBlur );
			xBlurMatrix._42 = Asura_Maths::Lerp( xBlurMatrix._42, xViewMatrix._42, m_fIgnoreMovementBlur );
			xBlurMatrix._43 = Asura_Maths::Lerp( xBlurMatrix._43, xViewMatrix._43, m_fIgnoreMovementBlur );
			xBlurMatrix._44 = Asura_Maths::Lerp( xBlurMatrix._44, xViewMatrix._44, m_fIgnoreMovementBlur );
			xBlurMatrix = xBlurMatrix * Asura_Direct3D::GetProjectionMatrix();
			*/
			D3DXMATRIX xBlurMatrix = g_xBlurOldViewMatrix * Asura_Direct3D::GetProjectionMatrix();
			g_pxMotionBlurEffect->GetD3DEffect()->SetMatrix( g_xPrevViewProjMatrixHandle, &xBlurMatrix );

			float z_fCameraTanHalfVertFOV	= Asura_Maths::Tangent( Asura_Camera::GetVerticalFieldOfView() * 0.5f );
			float z_fCameraTanHalfHorizFOV  = z_fCameraTanHalfVertFOV * Asura_Camera::GetXOverYAspectRatio();
			float z_fTwoOverScreenWidth		= 2.0f / Asura_Viewport::GetWidth();
			float z_fTwoOverScreenHeight	= 2.0f / Asura_Viewport::GetHeight();	

			g_pxMotionBlurEffect->GetD3DEffect()->SetFloat( g_xCameraTanHalfVertFOV, z_fCameraTanHalfVertFOV );
			g_pxMotionBlurEffect->GetD3DEffect()->SetFloat( g_xCameraTanHalfHorizFOV, z_fCameraTanHalfHorizFOV );
			g_pxMotionBlurEffect->GetD3DEffect()->SetFloat( g_xTwoOverScreenWidth, z_fTwoOverScreenWidth );
			g_pxMotionBlurEffect->GetD3DEffect()->SetFloat( g_xTwoOverScreenHeight, z_fTwoOverScreenHeight );
		}

		float afOneOverScreenDimensions[4] = {1.0f / Asura_Viewport::GetWidth(), 1.0f / Asura_Viewport::GetHeight(),  fIntensity, 1};
		g_pxMotionBlurEffect->GetD3DEffect()->SetFloatArray(g_xOneOverScreenDimensions, afOneOverScreenDimensions, 4);
		g_pxMotionBlurEffect->GetD3DEffect()->SetTexture(g_xDepthTextureHandle,  Asura_Direct3D::GetDeferredRenderingTarget()->GetTexture( ASURA_DIRECT3D_DEFFEDRENDERTARGET_NORMAL_INDEX ));

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

		//const int aiLoops[4] = { m_uBlurQuality, 0, 1, 0 };
		//Asura_Direct3D::D3DDevice->SetPixelShaderConstantI( 0, aiLoops, 1 );

		Asura_Direct3D::D3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, axVerts, sizeof(BloomVertex) );
		g_pxMotionBlurEffect->GetD3DEffect()->EndPass();

		g_pxMotionBlurEffect->SetParameterTexture( g_xScreenTextureHandle, NULL );
		g_pxMotionBlurEffect->SetParameterTexture( g_xDepthTextureHandle, NULL );

		// Flush any effects we may still be using
		Asura_Direct3D_Effects_System::FlushEffects();
	}

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

	// This is very important. If we leave any render target bound in any shader, it'll cause
	// device resets to fail - shader has a reference to the RT, so it won't be released
	//g_pxMotionBlurEffect->GetD3DEffect()->SetTexture(g_xMotionBlurEffectSourceTextureHandle, NULL);
	
	// Effect file sets it like this
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	// and now re-enable it for others
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);


	Asura_Direct3D_BackBufferCapture::AdviseNextFrame();
	Asura_Render::ApplyCameraSettings();
}

#endif //ASURA_TARGET_SM3
#endif //defined( ASURA_USE_FSFX)
