
///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_Platform_Beam.cpp
// Author : Andy Tate
// Date   : 29-Jun-2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "SpecialFX/Asura_Beam_System.h"

// Asura header-files...
#include "Camera/Asura_Camera.h"
#include "Core/Asura_TiledTextureManagement.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"

#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_RenderingOptions.h"
#include "Rendering/Asura_PS3_TextureManagement.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
// Game header-files...

//Shaders definition
#include "Rendering/ShadersDefinition/VD/Beam_VD.h"
#include "Rendering/ShadersDefinition/VP/Beam_VP.h"
#include "Rendering/ShadersDefinition/FP/Beam_FP.h"

struct BeamVertex
{
	float	x, y, z;
	u_int	m_uColour;
	float	u, v;
};

////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

// Vertex declarations...

static Asura_PS3_GCM_VertexShader*		g_pxVertexShader;

static Asura_PS3_FragmentShader*		g_pxFragmentShader;
static Asura_PS3_Beam_Handles*			g_pxHandles;

////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

static Asura_PS3_GCM_VertexBuffer		g_xVertexBuffer;

static void RenderBeam( const Asura_Beam& xBeam, const float fAngleTolerance )
{
#if defined ASURA_BEAMS_ALL_MODES
	Asura_PS3_RenderingStates::RequestTranslucencyMode( xBeam.GetTranslucencyMode() );
#endif
	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& xBeamStart = xBeam.GetTailPos();
	const Asura_Vector_3& xBeamDir = xBeam.GetDir();
	const Asura_Vector_3 xBeamEnd = xBeam.CalcCurHeadPos();

	float fTailRadius, fHeadRadius;
	xBeam.CalcCurRadii( &fTailRadius, &fHeadRadius );

	Asura_RGBA xTailColour, xHeadColour;
	xBeam.CalcCurColours( &xTailColour, &xHeadColour );

	const Asura_TiledTextureInfo* const pxBeamTile = Asura_TiledTextureManagement::GetTileInfoByHash( xBeam.GetTemplate()->uTileHashID );
	Asura_Assert( pxBeamTile, "Failed to get beam template texture tile." );

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

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

	float fAngleToLineOfSight = xEyeVector * xBeamDir;

	// Switch angle sign if -ve to reduce later work
	if( fAngleToLineOfSight < 0.0f )
	{
		fAngleToLineOfSight = -fAngleToLineOfSight;
	}

	Asura_Vector_3 xPerpVector;
	Asura_Vector_3 axVertexPos[6];
	u_int uColour;
	BeamVertex* pxVertex = (BeamVertex*) g_xVertexBuffer.LockForWriting();

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

	if( fAngleToLineOfSight > 1.0f - fAngleTolerance )
	{
		if( fAngleToLineOfSight >= 1.0f )
		{
			// Alternative to beam direction needed, use camera side
			xPerpVector = xCamRight;
		}
		else
		{
			// Cross product ok to use
			xPerpVector = xBeamDir ^ xEyeVector;
		}

		// Draw two triangle fans on each other (tail and head points)

		xPerpVector.Normalise();

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

		// First fan

		axVertexPos[0] = xRight;
		axVertexPos[0] *= -fTailRadius;
		axVertexPos[0] += xBeamStart;

		axVertexPos[1] = xUp;
		axVertexPos[1] *= fTailRadius;
		axVertexPos[1] += xBeamStart;

		axVertexPos[2] = xRight;
		axVertexPos[2] *= fTailRadius;
		axVertexPos[2] += xBeamStart;

		axVertexPos[3] = xUp;
		axVertexPos[3] *= -fTailRadius;
		axVertexPos[3] += xBeamStart;

		uColour = xTailColour.ToUIntRGBA();

		{
			pxVertex[0].x = axVertexPos[0].x;
			pxVertex[0].y = axVertexPos[0].y;
			pxVertex[0].z = axVertexPos[0].z;
			pxVertex[0].m_uColour = uColour;
			pxVertex[0].u = pxBeamTile->m_fu1;
			pxVertex[0].v = 0.5f * (pxBeamTile->m_fv1 + pxBeamTile->m_fv2);

			pxVertex[1].x = axVertexPos[1].x;
			pxVertex[1].y = axVertexPos[1].y;
			pxVertex[1].z = axVertexPos[1].z;
			pxVertex[1].m_uColour = uColour;
			pxVertex[1].u = pxBeamTile->m_fu1;
			pxVertex[1].v = pxBeamTile->m_fv1;

			pxVertex[2].x = axVertexPos[2].x;
			pxVertex[2].y = axVertexPos[2].y;
			pxVertex[2].z = axVertexPos[2].z;
			pxVertex[2].m_uColour = uColour;
			pxVertex[2].u = 0.5f * (pxBeamTile->m_fu1 + pxBeamTile->m_fu2);
			pxVertex[2].v = pxBeamTile->m_fv1;

			pxVertex[3].x = axVertexPos[3].x;
			pxVertex[3].y = axVertexPos[3].y;
			pxVertex[3].z = axVertexPos[3].z;
			pxVertex[3].m_uColour = uColour;
			pxVertex[3].u = pxBeamTile->m_fu1;
			pxVertex[3].v = pxBeamTile->m_fv1;

			g_xVertexBuffer.Bind( g_axVertexDeclaration, VD_ELEMENTS );
			g_xVertexBuffer.RenderTriangleFanInline( 4, pxVertex );
		}

		// Now draw second point in distance

		axVertexPos[0] = xRight;
		axVertexPos[0] *= -fHeadRadius;
		axVertexPos[0] += xBeamEnd;

		axVertexPos[1] = xUp;
		axVertexPos[1] *= fHeadRadius;
		axVertexPos[1] += xBeamEnd;

		axVertexPos[2] = xRight;
		axVertexPos[2] *= fHeadRadius;
		axVertexPos[2] += xBeamEnd;

		axVertexPos[3] = xUp;
		axVertexPos[3] *= -fHeadRadius;
		axVertexPos[3] += xBeamEnd;

		uColour = xHeadColour.ToUIntRGBA();

		{
			pxVertex[0].x = axVertexPos[0].x;
			pxVertex[0].y = axVertexPos[0].y;
			pxVertex[0].z = axVertexPos[0].z;
			pxVertex[0].m_uColour = uColour;
			pxVertex[0].u = pxBeamTile->m_fu2;
			pxVertex[0].v = 0.5f * (pxBeamTile->m_fv1 + pxBeamTile->m_fv2);

			pxVertex[1].x = axVertexPos[1].x;
			pxVertex[1].y = axVertexPos[1].y;
			pxVertex[1].z = axVertexPos[1].z;
			pxVertex[1].m_uColour = uColour;
			pxVertex[1].u = pxBeamTile->m_fu2;
			pxVertex[1].v = pxBeamTile->m_fv2;

			pxVertex[2].x = axVertexPos[2].x;
			pxVertex[2].y = axVertexPos[2].y;
			pxVertex[2].z = axVertexPos[2].z;
			pxVertex[2].m_uColour = uColour;
			pxVertex[2].u = 0.5f * (pxBeamTile->m_fu1 + pxBeamTile->m_fu2);
			pxVertex[2].v = pxBeamTile->m_fv2;

			pxVertex[3].x = axVertexPos[3].x;
			pxVertex[3].y = axVertexPos[3].y;
			pxVertex[3].z = axVertexPos[3].z;
			pxVertex[3].m_uColour = uColour;
			pxVertex[3].u = pxBeamTile->m_fu2;
			pxVertex[3].v = pxBeamTile->m_fv2;

			g_xVertexBuffer.Bind( g_axVertexDeclaration, VD_ELEMENTS );
			g_xVertexBuffer.RenderTriangleFanInline( 4, pxVertex );
		}
	}
	else
	{
		// Cross product ok to use
		xPerpVector = xBeamDir ^ 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 
		// + v1     + v2
		// + v3     + v4
		//      + v5

		axVertexPos[0] = xUp;
		axVertexPos[0] *= fTailRadius;
		axVertexPos[0] += xBeamStart;

		axVertexPos[1] = xRight;
		axVertexPos[1] *= -fTailRadius;
		axVertexPos[1] += xBeamStart;

		axVertexPos[2] = xRight;
		axVertexPos[2] *= fTailRadius;
		axVertexPos[2] += xBeamStart;

		axVertexPos[3] = xRight;
		axVertexPos[3] *= -fHeadRadius;
		axVertexPos[3] += xBeamEnd;

		axVertexPos[4] = xRight;
		axVertexPos[4] *= fHeadRadius;
		axVertexPos[4] += xBeamEnd;

		axVertexPos[5] = xUp;
		axVertexPos[5] *= -fHeadRadius;
		axVertexPos[5] += xBeamEnd;

		if( pxVertex )
		{
			uColour = xTailColour.ToUIntRGBA();

			pxVertex[0].x = axVertexPos[0].x;
			pxVertex[0].y = axVertexPos[0].y;
			pxVertex[0].z = axVertexPos[0].z;
			pxVertex[0].m_uColour = uColour;
			pxVertex[0].u = pxBeamTile->m_fu1;
			pxVertex[0].v = pxBeamTile->m_fv1;

			pxVertex[1].x = axVertexPos[1].x;
			pxVertex[1].y = axVertexPos[1].y;
			pxVertex[1].z = axVertexPos[1].z;
			pxVertex[1].m_uColour = uColour;
			pxVertex[1].u = pxBeamTile->m_fu1;
			pxVertex[1].v = 0.5f * (pxBeamTile->m_fv1 + pxBeamTile->m_fv2);

			pxVertex[2].x = axVertexPos[2].x;
			pxVertex[2].y = axVertexPos[2].y;
			pxVertex[2].z = axVertexPos[2].z;
			pxVertex[2].m_uColour = uColour;
			pxVertex[2].u = 0.5f * (pxBeamTile->m_fu1 + pxBeamTile->m_fu2);
			pxVertex[2].v = pxBeamTile->m_fv1;

			uColour = xHeadColour.ToUIntRGBA();

			pxVertex[3].x = axVertexPos[3].x;
			pxVertex[3].y = axVertexPos[3].y;
			pxVertex[3].z = axVertexPos[3].z;
			pxVertex[3].m_uColour = uColour;
			pxVertex[3].u = 0.5f * (pxBeamTile->m_fu1 + pxBeamTile->m_fu2);
			pxVertex[3].v = pxBeamTile->m_fv2;

			pxVertex[4].x = axVertexPos[4].x;
			pxVertex[4].y = axVertexPos[4].y;
			pxVertex[4].z = axVertexPos[4].z;
			pxVertex[4].m_uColour = uColour;
			pxVertex[4].u = pxBeamTile->m_fu2;
			pxVertex[4].v = 0.5f * (pxBeamTile->m_fv1 + pxBeamTile->m_fv2);

			pxVertex[5].x = axVertexPos[5].x;
			pxVertex[5].y = axVertexPos[5].y;
			pxVertex[5].z = axVertexPos[5].z;
			pxVertex[5].m_uColour = uColour;
			pxVertex[5].u = pxBeamTile->m_fu2;
			pxVertex[5].v = pxBeamTile->m_fv2;

			g_xVertexBuffer.Bind( g_axVertexDeclaration, VD_ELEMENTS );
			g_xVertexBuffer.RenderTriangleStripInline( 6, pxVertex );
		}
	}
}
////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

void Asura_Beam_System::Platform_Initialise()
{
	g_pxVertexShader	= new Asura_PS3_VP_Beam();
	g_pxHandles = new Asura_PS3_Beam_Handles();
	g_pxFragmentShader = new Asura_PS3_FragmentShader( (unsigned char *)&_binary_PS3_Beam_FP_bin_start, (unsigned char *)&_binary_PS3_Beam_FP_bin_end, g_pxHandles );

	g_pxVertexShader->InitVertexBufferDeclaration( g_axVertexDeclaration, VD_ELEMENTS );

	g_xVertexBuffer.Initialise( BEAM_VERTEX_SIZE, 6, CELL_GCM_LOCATION_LOCAL );
}

void Asura_Beam_System::Platform_Shutdown()
{
	g_xVertexBuffer.Destroy();
	delete g_pxVertexShader;
	delete g_pxFragmentShader;

	delete g_pxHandles;
	g_pxHandles			= NULL;
	g_pxVertexShader	= NULL;
	g_pxFragmentShader	= NULL;
}

void Asura_Beam_System::Platform_RenderStart (void)
{
	g_pxFragmentShader->SetTexture( FP_Sampler, s_iTiledTextureTextureID );

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

	// If the project doesn't allow individual beams to change translucency mode, use glowing for all

#if !defined ASURA_BEAMS_ALL_MODES
	Asura_PS3_RenderingStates::RequestTranslucencyMode( ASURA_TRANSLUCENCY_GLOWING );
#endif
}

void Asura_Beam_System::Platform_RenderEnd (void)
{
}

void Asura_Beam_System::Platform_Render (void)
{
	const u_int uNumCameras = Asura_Camera::GetNumberOfCameras();

	g_pxVertexShader->Bind();
	g_pxFragmentShader->Bind();

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

	for( u_int u = 0; u < uNumCameras; ++u )
	{
		Asura_Camera::SetCameraIndex(u);
		Asura_Render::ApplyCameraSettings();

		for( u_int v = 0; v < GetNumActiveBeams(); ++v )
		{
			if( !s_apxBeamPtrs[v] || s_apxBeamPtrs[v]->IsInvisible() ) continue;

#if defined ASURA_BEAMS_SUPPORT_CAMERA_SELECTION
			if( s_apxBeamPtrs[v]->m_uDontRenderIfCameraIgnoresThisGuid == Asura_Camera::GetEntityToIgnore()
				|| (s_apxBeamPtrs[v]->m_uOnlyRenderIfCameraIgnoresThisGuid != ASURA_GUID_UNREGISTERED && s_apxBeamPtrs[v]->m_uOnlyRenderIfCameraIgnoresThisGuid != Asura_Camera::GetEntityToIgnore()) )
			{
				continue;
			}
#endif // defined ASURA_BEAMS_SUPPORT_CAMERA_SELECTION
			RenderBeam( *s_apxBeamPtrs[v], s_fEndEffectAngleTolerance );
			s_apxBeamPtrs[v]->SetDontUpdateUntilRenderedOnce( false );
			Asura_PS3_Graphics::Flush();
		}
	}
}



/// End of file. ///
