///////////////////////////////////////////////////////////////////////////////
//
// 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"


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

// Asura PS3
#include "Rendering/Shaders/Asura_PS3_GCM_Shader.h"
#include "Rendering/Asura_PS3_GCM_VertexBuffer.h"
#include "Rendering/Asura_PS3_Graphics.h"
#include "Rendering/Asura_PS3_FrameBuffer.h"
#include "Rendering/Asura_PS3_GCM_IndexBuffer.h"
#include "Rendering/Asura_PS3_MaterialResponse_System.h"
#include "Rendering/Asura_PS3_RenderingOptions.h"
#include "Rendering/Asura_PS3_TextureManagement.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
#include "SpecialFX/AVP_Laser.h"
#include "SpecialFX/AVP_Platform_Laser.h"

//Shaders definition
#include "Rendering/ShadersDefinition/AVP_PS3_ProjectShaders.h"

#include "Rendering/ShadersDefinition/VD/Laser_VD.h"
#include "Rendering/ShadersDefinition/VD/Fog_VD.h"

#include "Rendering/ShadersDefinition/VP/Laser_VP.h"
#include "Rendering/ShadersDefinition/VP/Fog_VP.h"

#include "Rendering/ShadersDefinition/FP/Laser_FP.h"
#include "Rendering/ShadersDefinition/FP/FSFX_Laser_FP.h"

static Asura_PS3_GCM_VertexShader*		g_pxVertexShaderLaser;
static Asura_PS3_GCM_VertexShader*		g_pxVertexShaderLaserFSFX;
static Asura_PS3_FragmentShaderCopy*	g_pxFragmentShaderLaser;
static Asura_PS3_FragmentShaderCopy*	g_pxFragmentShaderLaser_FSFX;
static Asura_PS3_GCM_VertexBuffer		g_xVertexBuffer;
static Asura_PS3_GCM_VertexBuffer		g_xVertexBufferFSFX;

int AVP_Platform_Laser_System::s_iNoiseTexture;

class AVP_Laser_Vertex
{
public:
	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);
	}
private:
	float x, y, z, w;
	float x2, y2;
};



//
// Functions
//

void AVP_Platform_Laser_System::Initialise()
{
	static bool bInit = false;
	if( !bInit )
	{
		g_pxVertexShaderLaser		= new Asura_PS3_VP_Laser();
		g_pxVertexShaderLaserFSFX	= new Asura_PS3_VP_Fog();
		Asura_PS3_GCM_ShadersLibrary::AddFP( ASURA_SHADER_HASH_PROJECT, ASURA_PS3_LASER_SHADER, (char*)&_binary_PS3_Laser_FP_bin_start, (char*)&_binary_PS3_Laser_FP_bin_end - (char*)&_binary_PS3_Laser_FP_bin_start, new Asura_PS3_Laser_Handles(), 4, true );
		Asura_PS3_GCM_ShadersLibrary::AddFP( ASURA_SHADER_HASH_PROJECT, ASURA_PS3_LASER_FSFX_SHADER, (char*)&_binary_PS3_FSFX_Laser_FP_bin_start, (char*)&_binary_PS3_FSFX_Laser_FP_bin_end - (char*)&_binary_PS3_FSFX_Laser_FP_bin_start, new Asura_PS3_Laser_FSFX_Handles(), 4, true );

		g_pxVertexShaderLaser->InitVertexBufferDeclaration( g_axVertexDeclarationLaser, VD_LASER_ELEMENTS );
		g_pxVertexShaderLaserFSFX->InitVertexBufferDeclaration( g_axVertexDeclaration, VD_ELEMENTS );

		g_xVertexBuffer.Initialise( LASER_VERTEX_SIZE, 6, CELL_GCM_LOCATION_LOCAL );
		g_xVertexBufferFSFX.Initialise( FOG_VERTEX_SIZE, 4, CELL_GCM_LOCATION_MAIN );

		float* pfVertexData = ( float* )g_xVertexBufferFSFX.LockForWriting();
		pfVertexData[ 0 ] = -1; pfVertexData[ 1 ] =  1; //Top left
		pfVertexData[ 2 ] =  1; pfVertexData[ 3 ] =  1; //Top right
		pfVertexData[ 4 ] =  1; pfVertexData[ 5 ] = -1; //Bottom right
		pfVertexData[ 6 ] = -1; pfVertexData[ 7 ] = -1; //Bottom left

		bInit = true;

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

#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)
		Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.Laser", ReloadShaders, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
#endif
	}
}

static Asura_RGBA g_fMaxFXColour;

void AVP_Platform_Laser_System::RenderStart()
{
	// Setup initial renderstates
	Asura_PS3_DeferredRendering::BindAsSingleRT();
	Asura_PS3_RenderingStates::RequestAlphaTest( false );
	Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_NONE );
	Asura_PS3_RenderingStates::RequestZBufferWriteEnabled( false );
	Asura_PS3_RenderingStates::RequestZBufferEnabled( true );
	Asura_PS3_RenderingStates::RequestTranslucencyMode( ASURA_TRANSLUCENCY_GLOWING );
	Asura_PS3_RenderingStates::RequestZBufferCompareMode( ASURA_ZBUFFERCMP_LESSEQUAL );

	const Asura_AMatrix4x4& xViewProjectionMatrix = Asura_PS3_Graphics::GetViewProjectionMatrix();
	g_pxVertexShaderLaser->SetUniformParameter( VP_ViewProjectionMatrix, xViewProjectionMatrix.GetElements() );

	g_pxVertexShaderLaser->Bind();
	Asura_Render::ApplyCameraSettings();
	g_fMaxFXColour = Asura_RGBA( 0, 0 );
}

void AVP_Platform_Laser_System::SetupLaserParams(AVP_Laser *pxLaser)
{
	float afParams[] = {
		pxLaser->GetBeamRadius(), 
		Asura_Maths::Max(pxLaser->GetBeamSharpness(), 0.01f),
		pxLaser->GetBeamSpread(),
		
	};	
	g_pxVertexShaderLaser->SetUniformParameter( VP_Params, afParams );

	float afBeamFadeOff[] = {
		pxLaser->GetMaximumLength() - pxLaser->GetFallOffLength(), 
		pxLaser->GetFallOffLength() ? 1.0f / pxLaser->GetFallOffLength() : 0.0f,
		0.0f,
		0.0f
	};	
	g_pxVertexShaderLaser->SetUniformParameter( VP_BeamFadeOff, afBeamFadeOff );


	g_pxFragmentShaderLaser = Asura_PS3_GCM_ShadersLibrary::GetPermanentFP( ASURA_SHADER_HASH_PROJECT, ASURA_PS3_LASER_SHADER );

	float fTime[2] = {Asura_Timers::GetLocalScaledTime(), 1.0f / pxLaser->GetBeamVisibilityDistance()};
	g_pxFragmentShaderLaser->SetFloats( FP_Time, 2,  fTime );
	const Asura_Vector_3& xCamPos = *Asura_Camera::GetPosition();
	float fCameraPos[ 3 ] = { xCamPos.x, xCamPos.y, xCamPos.z };

	Asura_PS3_MaterialResponse_System::SetDepthParameters( g_pxFragmentShaderLaser, FP_DepthAdjustment );

	g_pxFragmentShaderLaser->SetFloats( FP_CameraPosition, 3, fCameraPos );
	g_pxFragmentShaderLaser->SetTexture( FP_NoiseTexture, s_iNoiseTexture );
	g_pxFragmentShaderLaser->SetTexture( FP_DepthTexture, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(), false );
	Asura_RGBA	xColour	= pxLaser->GetColour();
	g_pxFragmentShaderLaser->SetFloats( FP_Colour, 4, &xColour.m_fR );

	g_pxFragmentShaderLaser->Bind();
}

inline const float *_VecF(const Asura_Vector_3 &xVec, float fW)
{
	static float floats[4];
	floats[0] = xVec.GetX();
	floats[1] = xVec.GetY();
	floats[2] = xVec.GetZ();
	floats[3] = fW;
	return floats;
}

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);
	}

	Asura_RGBA	xColour	= pxLaser->GetColour();
	fAttenuation = 1.0f - Asura_Maths::Max(fAttenuation - pxLaser->GetFallOffAngleEndIntensity(), 0.0f);
	xColour.m_fA *= fAttenuation;
	g_pxFragmentShaderLaser->SetFloats( FP_Colour, 4, &xColour.m_fR );

	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

	//BW removed to match xbox360
	Asura_Vector_3 xPerpVector;
	AVP_Laser_Vertex* pxVertex;

	// 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

	{

		// 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;

		// Vertex 0 at top and nearest
		//      + v0              -near end of beam
		// + v1     + v2          
		// + v3     + v4
		//      + v5              -far end of beam

		{
			g_xVertexBuffer.Bind( g_axVertexDeclarationLaser, VD_LASER_ELEMENTS );
			//g_xVertexBuffer.RenderTriangleStripInline( 6, pxVertex );
			void*	xPointer = NULL;
			u_int	uNumVertices = 6; 
			u_int	iSize = uNumVertices * sizeof(g_axVertexDeclarationLaser);

			cellGcmSetDrawBeginUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_PRIMITIVE_TRIANGLE_STRIP );
			cellGcmSetDrawInlineArrayPointerUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), iSize / sizeof( float ), &xPointer );

			pxVertex = (AVP_Laser_Vertex*) xPointer;
			pxVertex[0].Set( xPosition	+ xUp		* fNearRadiusInner, fNearRadiusInner, 0.0f); 
			pxVertex[1].Set( xPosition	- xRight	* fNearRadiusInner, fNearRadiusInner, 0.0f); 
			pxVertex[2].Set( xPosition	+ xRight	* fNearRadiusInner,	fNearRadiusInner, 0.0f); 
			pxVertex[3].Set( xBeamEnd	- xRight	* fFarRadiusInner,	fFarRadiusInner, fLength); 
			pxVertex[4].Set( xBeamEnd	+ xRight	* fFarRadiusInner,	fFarRadiusInner, fLength); 
			pxVertex[5].Set( xBeamEnd	- xUp		* fFarRadiusInner,	fFarRadiusInner, fLength); 

			cellGcmSetDrawEndUnsafeInline(Asura_PS3_Graphics::GetGcmCurrentContext() );

			Asura_Render::AddToTrianglesRendered( uNumVertices - 2 );

		}

		{
			g_xVertexBuffer.Bind( g_axVertexDeclarationLaser, VD_LASER_ELEMENTS );
			//g_xVertexBuffer.RenderTriangleStripInline( 6, pxVertex );

			void*	xPointer = NULL;
			u_int	uNumVertices = 6; 
			u_int	iSize = uNumVertices * sizeof(g_axVertexDeclarationLaser);

			cellGcmSetDrawBeginUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_PRIMITIVE_TRIANGLE_STRIP );
			cellGcmSetDrawInlineArrayPointerUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), iSize / sizeof( float ), &xPointer );

			pxVertex = (AVP_Laser_Vertex*) xPointer;
			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( xBeamEnd	- xRight	* fFarRadiusOuter,	fFarRadiusOuter, fLength); 
			pxVertex[4].Set( xBeamEnd	- xRight	* fFarRadiusInner,	fFarRadiusInner, fLength); 
			pxVertex[5].Set( xBeamEnd	- xUp		* fFarRadiusInner,	fFarRadiusInner, fLength); 

			cellGcmSetDrawEndUnsafeInline(Asura_PS3_Graphics::GetGcmCurrentContext() );

			Asura_Render::AddToTrianglesRendered( uNumVertices - 2 );
		}

		{
			g_xVertexBuffer.Bind( g_axVertexDeclarationLaser, VD_LASER_ELEMENTS );
			//g_xVertexBuffer.RenderTriangleStripInline( 6, pxVertex );

			void*	xPointer = NULL;
			u_int	uNumVertices = 6; 
			u_int	iSize = uNumVertices * sizeof(g_axVertexDeclarationLaser);

			cellGcmSetDrawBeginUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_PRIMITIVE_TRIANGLE_STRIP );
			cellGcmSetDrawInlineArrayPointerUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), iSize / sizeof( float ), &xPointer );

			pxVertex = (AVP_Laser_Vertex*) xPointer;
			pxVertex[0].Set( xPosition	+ xUp		* fNearRadiusInner, fNearRadiusInner, 0.0f); 
			pxVertex[1].Set( xPosition	+ xRight	* fNearRadiusInner, fNearRadiusInner, 0.0f); 
			pxVertex[2].Set( xPosition	+ xRight	* fNearRadiusOuter,	fNearRadiusOuter, 0.0f); 
			pxVertex[3].Set( xBeamEnd	+ xRight	* fFarRadiusInner,	fFarRadiusInner, fLength); 
			pxVertex[4].Set( xBeamEnd	+ xRight	* fFarRadiusOuter,	fFarRadiusOuter, fLength); 
			pxVertex[5].Set( xBeamEnd	- xUp		* fFarRadiusInner,	fFarRadiusInner, fLength); 

			cellGcmSetDrawEndUnsafeInline(Asura_PS3_Graphics::GetGcmCurrentContext() );

			Asura_Render::AddToTrianglesRendered( uNumVertices - 2 );
		}
	}
}

void AVP_Platform_Laser_System::RenderEnd()
{
	if (g_fMaxFXColour.m_fA > 0.0f)
	{
		Asura_PS3_DeferredRendering::ResolveBackBuffer();
		Asura_PS3_DeferredRendering::BindAsSingleRT();
		g_pxVertexShaderLaserFSFX->Bind();
		// Resolve the current RT back out so we can read the initial colours
		Asura_PS3_RenderingStates::RequestTranslucencyMode( ASURA_TRANSLUCENCY_GLOWING );
		Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW );
		// Taken these settings out of the effect, as they were screwing up the cached states
		Asura_PS3_RenderingStates::RequestZBufferEnabled(false);
		Asura_PS3_RenderingStates::RequestZBufferWriteEnabled(false);

		g_pxFragmentShaderLaser_FSFX = Asura_PS3_GCM_ShadersLibrary::GetPermanentFP( ASURA_SHADER_HASH_PROJECT, ASURA_PS3_LASER_FSFX_SHADER );
		float fScreenHeight = Asura_PS3_Graphics::GetScreenHeight();
		g_pxFragmentShaderLaser_FSFX->SetFloats( FP_FSFX_Colour, 4, &g_fMaxFXColour.m_fR );
		g_pxFragmentShaderLaser_FSFX->SetFloats( FP_FSFX_ScreenHeight, 1, &fScreenHeight );

		g_pxFragmentShaderLaser_FSFX->SetTexture( FP_FSFX_SceneTextureSampler, Asura_PS3_DeferredRendering::GetResolvedTexture(), false );
		g_pxFragmentShaderLaser_FSFX->Bind();

		g_xVertexBufferFSFX.Bind( g_axVertexDeclaration, VD_ELEMENTS );
		g_xVertexBufferFSFX.RenderQuads( 4 );
	}
}

void AVP_Platform_Laser_System::Shutdown()
{
}


#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)
bool AVP_Platform_Laser_System::ReloadShaders( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("recompile & load laser beam shader");
		return true;
	}

	const char *aszIncludePath[2]= { "~/Project/AVP/Game/PS3/Shaders/", NULL };
	Asura_PS3_GCM_ShadersLibrary::LoadAndCompileShader( "~/Project/AVP/Game/PS3/Shaders/Laser_FP.fpx",
		ASURA_SHADER_HASH_PROJECT, ASURA_PS3_LASER_SHADER, NULL, aszIncludePath, new Asura_PS3_Laser_Handles );

	return true;
}
#endif
