///////////////////////////////////////////////////////////////////////////////
//
// File: AVP_Platform_Laser.cpp
//
// Created by Andrew Southgate 12/11/2007
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
// Handles rendering of the predators tri-laser thingy
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Core/Asura_LOS.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_TextureManagement.h"


// Asura Platform-independent
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Maths.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 "Rendering/Asura_Direct3D_BackBufferCapture.h"

// AVP
#include "SpecialFX/AVP_Laser.h"
#include "SpecialFX/AVP_Platform_Laser.h"


#include "AVP_Laser.h"
ASURA_DECLARE_DIRECT3D_EFFECT(AVP_Laser);


Direct3D_VertexDeclaration*	AVP_Platform_Laser_System::s_pxLaserVertexDecl			= 0;
Asura_Direct3D_Effect*		AVP_Platform_Laser_System::s_pxLaserEffect				= 0;


D3DXHANDLE					AVP_Platform_Laser_System::s_xColourHandle;
D3DXHANDLE					AVP_Platform_Laser_System::s_xParamsHandle;
D3DXHANDLE					AVP_Platform_Laser_System::s_xBeamFadeOffHandle;

D3DXHANDLE					AVP_Platform_Laser_System::s_xTimeHandle;
D3DXHANDLE					AVP_Platform_Laser_System::s_xOneOverScreenSizeHandle;

D3DXHANDLE					AVP_Platform_Laser_System::s_xNoiseTexHandle;
D3DXHANDLE					AVP_Platform_Laser_System::s_xSceneTextureHandle;
D3DXHANDLE					AVP_Platform_Laser_System::s_xDepthTextureHandle;
int							AVP_Platform_Laser_System::s_iNoiseTexture;

struct AVP_Laser_Vertex
{
	float x, y, z, w;
	float x2, y2;
	void SetPosition(const Asura_Vector_3 &xPosition)
	{
		x = xPosition.GetX();
		y = xPosition.GetY();
		z = xPosition.GetZ();
		w = 1.0f;
	}
	void SetPos2(float fx, float fy)
	{
		x2 = fx, y2 = fy;
	}
	void Set(const Asura_Vector_3 &xPosition, float fx, float fy)
	{
		SetPosition(xPosition);
		SetPos2(fx,fy);
	}
};

void AVP_Platform_Laser_System::Initialise()
{
	if( NULL == Asura_Direct3D::D3DDevice)
	{
		return;
	}

	Direct3D_VertexElement auVSD[] =
	{
		{	0,	0,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_POSITION,	0 },		
		{	0,	16,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_TEXCOORD,	0 },		
		D3DDECL_END()
	};

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

	s_iNoiseTexture = Asura_Texture_Management::AddTexture("\\SpecialFX\\AVP_Laser.dds");
	CaptureEffect();
}

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

	if (!s_pxLaserEffect)
	{
		s_pxLaserEffect = Asura_Direct3D_Effects_System::GetEffect( "AVP_Laser");
	}

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

		LOADPARAMETER(s_xColourHandle, "g_xColour");
		LOADPARAMETER(s_xParamsHandle, "g_xParams");
		LOADPARAMETER(s_xBeamFadeOffHandle, "g_xBeamFadeOff");

		LOADPARAMETER(s_xTimeHandle, "g_fTime");
		LOADPARAMETER(s_xOneOverScreenSizeHandle, "g_xOneOverScreenSize");

		LOADPARAMETER(s_xNoiseTexHandle, "g_xNoiseTexture");
		LOADPARAMETER(s_xSceneTextureHandle, "g_xSceneTexture");
		LOADPARAMETER(s_xDepthTextureHandle, "g_xDepthTexture");
		
	}
}

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

void AVP_Platform_Laser_System::OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect)
{
	s_pxLaserEffect = NULL;
}
#endif

static Asura_RGBA g_fMaxFXColour;

static D3DXVECTOR4 _D3DVEC(float fX, float fY, float fZ, float fW = 0.0f)
{
	return D3DXVECTOR4(fX, fY, fZ, fW);
}

static D3DXVECTOR4 _D3DVEC(const Asura_RGBA &rgba)
{
	return D3DXVECTOR4(rgba.m_fR, rgba.m_fG, rgba.m_fB, rgba.m_fA);
}



void AVP_Platform_Laser_System::RenderStart()
{
	// Setup initial renderstates
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CW);
	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_Effects_System::BeginEffect(*s_pxLaserEffect);

	Asura_Direct3D::D3DDevice->SetVertexDeclaration(s_pxLaserVertexDecl);	

	g_fMaxFXColour = Asura_RGBA(0, 0);

	s_pxLaserEffect->SetParameter(s_xTimeHandle, Asura_Timers::GetLocalScaledTime() * 0.2f);
	s_pxLaserEffect->SetParameter(s_xOneOverScreenSizeHandle,  D3DXVECTOR4(1.0f / Asura_Viewport::GetWidth(), 1.0f / Asura_Viewport::GetHeight(), 0, 0));

	// Set the depth-normal texture
	s_pxLaserEffect->SetParameterTexture(s_xDepthTextureHandle, Asura_Direct3D::GetDeferredRenderingTarget()->GetTexture( ASURA_DIRECT3D_DEFFEDRENDERTARGET_NORMAL_INDEX));

	s_pxLaserEffect->SetParameterTexture(s_xNoiseTexHandle, Asura_Direct3D::GetDirect3DTextureByIndex(s_iNoiseTexture));

}

void AVP_Platform_Laser_System::SetupLaserParams(AVP_Laser *pxLaser)
{
	s_pxLaserEffect->SetParameter(s_xParamsHandle, 
		_D3DVEC(
			pxLaser->GetBeamRadius(), 
			Asura_Maths::Max(pxLaser->GetBeamSharpness(), 0.01f),
			pxLaser->GetBeamSpread(),
			1.0f / pxLaser->GetBeamVisibilityDistance()
		)
	);
	s_pxLaserEffect->SetParameter(s_xBeamFadeOffHandle, 
		_D3DVEC(
		pxLaser->GetMaximumLength() - pxLaser->GetFallOffLength(), 
		pxLaser->GetFallOffLength() ? 1.0f / pxLaser->GetFallOffLength() : 0.0f, 
		0.0f
		)
	);
}
void AVP_Platform_Laser_System::RenderLaser(AVP_Laser *pxLaser, const Asura_Vector_3 &xPosition, const Asura_Vector_3 &xRight, const Asura_Vector_3 &xDown, const Asura_Vector_3 &xForward, float fLength)
{
	const Asura_Matrix_3x3& xCamOri = *Asura_Camera::GetOrientation();
	const Asura_Vector_3& xCamPos = *Asura_Camera::GetPosition();

	const Asura_Vector_3 xCamRight( xCamOri.e11, xCamOri.e21, xCamOri.e31 );
	const Asura_Vector_3 xCamUp( xCamOri.e12, xCamOri.e22, xCamOri.e32 );
	const Asura_Vector_3 xCamForward( xCamOri.e13, xCamOri.e23, xCamOri.e33 );

	const Asura_Vector_3 xBeamEnd = xPosition + xForward * fLength;

	Asura_Vector_3 xCamRelativeDir = xCamPos - xPosition;
	
	float fDistAlongBeam = xCamRelativeDir * xForward;

	float fAttenuation;
	if (fDistAlongBeam < 0.0f)
	{
		float fDistFromOrigin = xCamRelativeDir.Normalise();

		fAttenuation = Asura_Maths::SmoothStep(fDistFromOrigin, pxLaser->GetFallOffStartDistance(), pxLaser->GetFallOffEndDistance());
	} 
	else 
	{
		float fCylinderAttenuation = 
			 Asura_Maths::SmoothStep((xCamRelativeDir - xForward * Asura_Maths::Max(fDistAlongBeam, 0.0f)).Magnitude(), 
				pxLaser->GetFallOffStartDistance(), pxLaser->GetFallOffEndDistance());

		xCamRelativeDir.Normalise();
		float fConeAttenuation = 
			Asura_Maths::SmoothStep(Asura_Maths::ArcCosine(Asura_Maths::Min(xCamRelativeDir * xForward, 1.0f)), 
			pxLaser->GetFallOffStartAngle(), pxLaser->GetFallOffEndAngle());

		fAttenuation = Asura_Maths::Min(fCylinderAttenuation, fConeAttenuation);
	}

	D3DXVECTOR4 xCol = _D3DVEC(pxLaser->GetColour());
	fAttenuation = 1.0f - Asura_Maths::Max(fAttenuation - pxLaser->GetFallOffAngleEndIntensity(), 0.0f);
	xCol.w *= fAttenuation;
	s_pxLaserEffect->SetParameter(s_xColourHandle, xCol);

	float fDot = xCamForward * xForward;
	if (fDot < -0.8f)
	{
		const Asura_Vector_3 xRelativeCamPos = xCamPos - xPosition;
		float fDistAlong = xRelativeCamPos * xForward;
		// check to make sure the beam isnt stopped before it reaches us
		if (fDistAlong < fLength)
		{
			const Asura_Vector_3 xProjectedPos = xForward * fDistAlong;
			float fDistFrom = (xRelativeCamPos - xProjectedPos).MagnitudeSquared(); 
			float fRadiusOuter = pxLaser->GetBeamRadius() + pxLaser->GetBeamSharpness() + pxLaser->GetBeamSpread() * fDistAlong;
			float fIntens;

			if (fDistFrom <= pxLaser->GetBeamRadius())
			{
				fIntens = 1.0f;
			} if (fDistFrom > fRadiusOuter)
			{
				fIntens = 0.0f;
			} else {
				fIntens = 1.0f - (fDistFrom - pxLaser->GetBeamRadius()) / (fRadiusOuter - pxLaser->GetBeamRadius());
			}
			fIntens *= fIntens * Asura_Maths::Abs(fDot + 0.8f) * (1.0f / 0.2f);

			if (fDistAlong > pxLaser->GetMaximumLength())
			{
				fIntens = 0.0f;
			} 
			else if (fDistAlong > pxLaser->GetMaximumLength() - pxLaser->GetFallOffLength())
			{
				fIntens *= 1.0f - (fDistAlong - (pxLaser->GetMaximumLength() - pxLaser->GetFallOffLength())) / pxLaser->GetFallOffLength();
			}


			Asura_RGBA xColour = pxLaser->GetColour() * fIntens * fAttenuation;

			if (xColour.m_fA > g_fMaxFXColour.m_fA)
			{
				g_fMaxFXColour.m_fA = xColour.m_fA;
			}
			g_fMaxFXColour.m_fR = Asura_Maths::Max(xColour.m_fR, g_fMaxFXColour.m_fR);
			g_fMaxFXColour.m_fG = Asura_Maths::Max(xColour.m_fG, g_fMaxFXColour.m_fG);
			g_fMaxFXColour.m_fB = Asura_Maths::Max(xColour.m_fB, g_fMaxFXColour.m_fB);
		}
	}

	float fNearRadiusInner = pxLaser->GetBeamRadius();
	float fNearRadiusOuter = pxLaser->GetBeamRadius() + pxLaser->GetBeamSharpness();
	float fFarRadiusInner = pxLaser->GetBeamRadius();
	float fFarRadiusOuter = pxLaser->GetBeamRadius() + pxLaser->GetBeamSharpness() + fLength * pxLaser->GetBeamSpread();

	Asura_Vector_3 xEyeVector = xCamPos;
	xEyeVector -= xPosition;
	xEyeVector.Normalise();

	// The effect doesn't work at low angles to sight so we introduce 
	// a tolerance angle in which the far end of the beam is brought
	// up to the near end - there is a tolerance to stop things
	// flipping too much with small error

	Asura_Vector_3 xPerpVector;

	// Work out if in line with camera - if in line draw two 
	// separate triangle fans with the mirrored far and near ends
	// else draw a triangle strip with flattend ends facing the camera

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

	{
		AVP_Laser_Vertex pxVertex[10];

		// Cross product ok to use
		xPerpVector = xForward ^ xEyeVector;
		xPerpVector.Normalise();

		Asura_Vector_3 xUp = xCamForward ^ xPerpVector;
		xUp.Normalise();
		const Asura_Vector_3 xRight = xCamForward ^ xUp;

		pxVertex[0].Set(xPosition	+ xUp    * fNearRadiusInner, fNearRadiusInner, 0.0f);

		pxVertex[1].Set(xPosition	- xRight * fNearRadiusOuter, fNearRadiusOuter, 0.0f);
		pxVertex[2].Set(xPosition	- xRight * fNearRadiusInner, fNearRadiusInner, 0.0f);
		pxVertex[3].Set(xPosition	+ xRight * fNearRadiusInner, fNearRadiusInner, 0.0f);
		pxVertex[4].Set(xPosition	+ xRight * fNearRadiusOuter, fNearRadiusOuter, 0.0f);

		pxVertex[5].Set(xBeamEnd	- xRight * fFarRadiusOuter, fFarRadiusOuter, fLength);
		pxVertex[6].Set(xBeamEnd	- xRight * fFarRadiusInner, fFarRadiusInner, fLength);
		pxVertex[7].Set(xBeamEnd	+ xRight * fFarRadiusInner, fFarRadiusInner, fLength);
		pxVertex[8].Set(xBeamEnd	+ xRight * fFarRadiusOuter, fFarRadiusOuter, fLength);

		pxVertex[9].Set(xBeamEnd	- xUp    * fFarRadiusInner, fFarRadiusInner, fLength);

		static unsigned short pusIndices[] = {  
			0, 1, 2, 5, 6, 9, 
			9, 0, 
			0, 2, 3, 6, 7, 9, 
			9, 0, 
			0, 3, 4, 7, 8, 9
		};

		Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLESTRIP,
			0, 10, 20, pusIndices, D3DFMT_INDEX16, pxVertex, sizeof(AVP_Laser_Vertex));
	}
	s_pxLaserEffect->GetD3DEffect()->EndPass();
}

void AVP_Platform_Laser_System::RenderEnd()
{
	if (g_fMaxFXColour.m_fA > 0.0f)
	{
		Asura_Direct3D_BackBufferCapture::CaptureCurrentBackBuffer();

		s_pxLaserEffect->SetParameterTexture( s_xSceneTextureHandle,
			Asura_Direct3D_BackBufferCapture::GetCaptureTexture() );

		s_pxLaserEffect->SetParameter(s_xColourHandle, _D3DVEC(g_fMaxFXColour));


		Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE);
		Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);


		static const AVP_Laser_Vertex axVerts[] =
		{
			{ -1.0f,  1.0f, 0.0f,1.0f, 0.0f, 0.0f, },
			{  1.0f,  1.0f, 0.0f,1.0f, 1.0f, 0.0f },
			{  1.0f, -1.0f, 0.0f,1.0f, 1.0f, 1.0f },
			{ -1.0f, -1.0f, 0.0f,1.0f, 0.0f, 1.0f },
		};
		Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

		Asura_Direct3D_Effects_System::BeginEffect(*s_pxLaserEffect,1);
		s_pxLaserEffect->BeginPass(0);

		Asura_Direct3D::D3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, axVerts,
			sizeof( AVP_Laser_Vertex ) );

		s_pxLaserEffect->EndPass();
	}
	s_pxLaserEffect->SetParameterTexture( s_xNoiseTexHandle, NULL );
	s_pxLaserEffect->SetParameterTexture( s_xSceneTextureHandle, NULL );
	s_pxLaserEffect->SetParameterTexture( s_xDepthTextureHandle, NULL );

	Asura_Direct3D_Effects_System::FlushEffects();

	for (int i = 0; i < 8; i++)
	{
		Asura_Direct3D::D3DDevice->SetSamplerState(i, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		Asura_Direct3D::D3DDevice->SetSamplerState(i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		Asura_Direct3D::D3DDevice->SetSamplerState(i, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	}
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
	Asura_Direct3D_BackBufferCapture::AdviseNextFrame();


}

void AVP_Platform_Laser_System::Shutdown()
{

	SAFE_RELEASE( s_pxLaserVertexDecl);
}
