///////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Beam.cpp
//
// Copyright (c) 2004-2005 Rebellion. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// Engine independent.
#include "Camera/Asura_Camera.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_TiledTextureManagement.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/Asura_Beam_System.h"

// Engine platform-specific.
#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_Shaders.h"

DEBUGVAR float dbg_fInsideBeamGlareOffset = 0.25f;
DEBUGVAR float dbg_fRadiusCheckModifier = Asura_Maths::InvSqrt(2.f);
static void RenderBeam( const Asura_Beam* pxBeam, float fAngleTolerance )
{
	Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE1, 0 );

#ifdef ASURA_BEAMS_ALL_MODES
	Asura_Render::CurrentStates.RequestTranslucencyMode( pxBeam->GetTranslucencyMode() );
#endif

	// best place to put this ?
	Asura_Direct3D_Shaders::RequestFixedFunctionVertexShader(D3DFVF_GENERICVERTEX);

	// calculate the vertex positions for triangle fan 
	Asura_Vector_3 axVertices[8];
	
	// get camera set up
	const Asura_Matrix_3x3* pxCamOrient = Asura_Camera::GetOrientation();
	const Asura_Vector_3* pxCamPos = Asura_Camera::GetPosition();

	// get camera data
	const Asura_Vector_3 xCameraForward(pxCamOrient->e13,pxCamOrient->e23,pxCamOrient->e33);

	const Asura_Vector_3& xBeamStart = pxBeam->GetTailPos();
	const Asura_Vector_3& xBeamDir = pxBeam->GetDir();
	Asura_Vector_3 xBeamEnd = pxBeam->CalcCurHeadPos();
	
	float fTailRadius, fHeadRadius;
	pxBeam->CalcCurRadii( &fTailRadius, &fHeadRadius );

	Asura_RGBA xTailRGBA, xHeadRGBA;
	pxBeam->CalcCurColours( &xTailRGBA, &xHeadRGBA );

	// Get tiled texture info for this beam.
	const Asura_TiledTextureInfo* const pxTileInfo = Asura_TiledTextureManagement::GetTileInfo( pxBeam->GetTemplate()->iTileHandle );
	// That shouldn't fail since we check for it in independent
	// code when trying to create the beam.
	Asura_Assert( pxTileInfo, "" );

	// calculate new vector directions 
	Asura_Vector_3 xEyeVector = *pxCamPos - xBeamStart;

	// Calculate camera position relative to beam cylinder.
	const float fDistanceAlongAxis = xEyeVector * xBeamDir;
	const Asura_Vector_3 xClosestPointOnAxis = xBeamStart + xBeamDir * fDistanceAlongAxis;
	const float fDistanceFromAxisSquared = (*pxCamPos - xClosestPointOnAxis).MagnitudeSquared();

	// work out if in line with camera - if in line draw two 
	// separate triangle fans with the mirrored head and tail ends
	// else draw a triangle strip with flattend ends facing the camera
	const float fModifiedRadius = dbg_fRadiusCheckModifier * fTailRadius;
	if(fDistanceFromAxisSquared < fModifiedRadius * fModifiedRadius)
	{		
		// draw two triangle fans on each other (tail and head points)
		const Asura_Vector_3 xUp(pxCamOrient->e12,pxCamOrient->e22,pxCamOrient->e32);
		const Asura_Vector_3 xRight(pxCamOrient->e11,pxCamOrient->e21,pxCamOrient->e31);

		Asura_Vector_3 xStart = xBeamStart;
		Asura_Vector_3 xEnd = xBeamEnd;
		if(0.f < fDistanceAlongAxis && fDistanceAlongAxis < pxBeam->GetLength())
		{
			// We are between the two points.
			// Push the beam end to the camera position so we get it glaring in our eyes.

			// Check whether we are facing towards the start or end
			if(xCameraForward * xBeamDir > 0.f)
			{
				// Facing end. Move start.
				xStart = *pxCamPos + xBeamDir * dbg_fInsideBeamGlareOffset;
			}
			else
			{
				// Facing start. Move end.
				xEnd = *pxCamPos - xBeamDir * dbg_fInsideBeamGlareOffset;
			}
		}
		const Asura_Vector_3 xTailUp = xUp * fTailRadius;
		const Asura_Vector_3 xTailRight = xRight * fTailRadius;
		// first fan
		axVertices[0] = xStart - xTailRight;		
		axVertices[1] = xStart + xTailUp;
		axVertices[2] = xStart + xTailRight;
		axVertices[3] = xStart - xTailUp;

		//copy new data into D3D vertices 
		D3DGenericVertex axD3DVerts[4];

		const u_int uTailARGB = xTailRGBA.ToUIntARGB();

		for (int i=0;i<4;i++)
		{
			axD3DVerts[i].X = axVertices[i].x;
			axD3DVerts[i].Y = axVertices[i].y;
			axD3DVerts[i].Z = axVertices[i].z;			
			axD3DVerts[i].Diffuse = uTailARGB;
		}

		//skewed texture coords such that you get mirror effect
		axD3DVerts[0].U = pxTileInfo->m_fu1;
		axD3DVerts[0].V = 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2);
		axD3DVerts[1].U = pxTileInfo->m_fu1;
		axD3DVerts[1].V = pxTileInfo->m_fv1;
		axD3DVerts[2].U = 0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2);
		axD3DVerts[2].V = pxTileInfo->m_fv1;
		axD3DVerts[3].U = pxTileInfo->m_fu1;
		axD3DVerts[3].V = pxTileInfo->m_fv1;

		Asura_Direct3D::D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, (LPVOID)axD3DVerts, sizeof(D3DGenericVertex) );

		//now draw second point in distance
		const Asura_Vector_3 xHeadUp = xUp * fHeadRadius;
		const Asura_Vector_3 xHeadRight = xRight * fHeadRadius;
		axVertices[0] = xEnd - xHeadRight;		
		axVertices[1] = xEnd + xHeadUp;
		axVertices[2] = xEnd + xHeadRight;
		axVertices[3] = xEnd - xHeadUp;
		
		const u_int uHeadARGB = xHeadRGBA.ToUIntARGB();

		//copy new data into D3D vertices 
		for (int i = 0; i < 4; ++i)
		{
			axD3DVerts[i].X = axVertices[i].x;
			axD3DVerts[i].Y = axVertices[i].y;
			axD3DVerts[i].Z = axVertices[i].z;
			
			axD3DVerts[i].Diffuse = uHeadARGB;
		}

		//skewed texture coords such that you get mirror effect
		axD3DVerts[0].U = pxTileInfo->m_fu2;
		axD3DVerts[0].V = 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2);
		axD3DVerts[1].U = pxTileInfo->m_fu2;
		axD3DVerts[1].V = pxTileInfo->m_fv2;
		axD3DVerts[2].U = 0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2);
		axD3DVerts[2].V = pxTileInfo->m_fv2;
		axD3DVerts[3].U = pxTileInfo->m_fu2;
		axD3DVerts[3].V = pxTileInfo->m_fv2;

		Asura_Direct3D::D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, (LPVOID)axD3DVerts, sizeof(D3DGenericVertex) );

		// keep track of num triangles
		Asura_Render::AddToTrianglesRendered(4);
	}
	else 
	{
		xEyeVector.Normalise();
		//cross product ok to use
		Asura_Vector_3 xPerpVector = xBeamDir ^ xEyeVector;
		xPerpVector.Normalise();	
		Asura_Vector_3 xUp = xCameraForward ^ xPerpVector;
		xUp.Normalise();
		Asura_Vector_3 xRight = xCameraForward ^ xUp;

		// vertex 0 at top and nearest
		//		+v0 
		// + v1		+ v2
		// + v3		+ v4
		//		+v5 

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

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

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

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

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

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

		//copy new data into D3D vertices 
		D3DGenericVertex axD3DVerts[6];

		//draw it as a triangle strip
		for (int i=0;i<6;i++)
		{
			axD3DVerts[i].X = axVertices[i].x;
			axD3DVerts[i].Y = axVertices[i].y;
			axD3DVerts[i].Z = axVertices[i].z;
		}

		axD3DVerts[0].U = pxTileInfo->m_fu1;
		axD3DVerts[0].V = pxTileInfo->m_fv1;
		axD3DVerts[1].U = pxTileInfo->m_fu1;
		axD3DVerts[1].V = 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2);
		axD3DVerts[2].U = 0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2);
		axD3DVerts[2].V = pxTileInfo->m_fv1;
		axD3DVerts[3].U = 0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2);
		axD3DVerts[3].V = pxTileInfo->m_fv2;
		axD3DVerts[4].U = pxTileInfo->m_fu2;
		axD3DVerts[4].V = 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2);
		axD3DVerts[5].U = pxTileInfo->m_fu2;
		axD3DVerts[5].V = pxTileInfo->m_fv2;

		unsigned int uARGB = xTailRGBA.ToUIntARGB();
		for (int i=0; i<3; i++)
		{
			axD3DVerts[i].Diffuse = uARGB;
		}

		uARGB = xHeadRGBA.ToUIntARGB();
		for (int i=3; i<6; i++)
		{
			axD3DVerts[i].Diffuse = uARGB;
		}

		Asura_Direct3D::D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 4, (LPVOID)axD3DVerts, sizeof(D3DGenericVertex) );

		// keep track of num triangles
		Asura_Render::AddToTrianglesRendered(4);		
	
	}	

	Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE1, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA );
}

void Asura_Beam_System::Platform_Initialise()
{
}

void Asura_Beam_System::Platform_Shutdown()
{
}

void Asura_Beam_System::Platform_RenderStart(void)
{
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_SINGLE_PASS);
	
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

#ifndef ASURA_BEAMS_ALL_MODES
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);
#endif
	
	// set texture index for rendering
	Asura_Render::CurrentStates.RequestTextureIndex( 0, s_iTiledTextureTextureID );
}

void Asura_Beam_System::Platform_RenderEnd(void)
{
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	// carry on rendering
}


void Asura_Beam_System::Platform_Render(void)
{
	for(int i=0; i<Asura_Camera::GetNumberOfCameras(); i++)
	{
		//if (uCamVisFlags & (1 << i))
		{
			Asura_Camera::SetCameraIndex(i);

			Asura_Render::ApplyCameraSettings();			

			for(u_int j=0; j<s_uNumActiveBeams; j++ )
			{	
				if( !s_apxBeamPtrs[j] ) continue;

#ifdef ASURA_BEAMS_SUPPORT_CAMERA_SELECTION
				if( s_apxBeamPtrs[j]->m_uDontRenderIfCameraIgnoresThisGuid == Asura_Camera::GetEntityToIgnore() ) continue;
				if( (s_apxBeamPtrs[j]->m_uOnlyRenderIfCameraIgnoresThisGuid != ASURA_GUID_UNREGISTERED) && (s_apxBeamPtrs[j]->m_uOnlyRenderIfCameraIgnoresThisGuid != Asura_Camera::GetEntityToIgnore()) ) continue;
#endif

				if( s_apxBeamPtrs[j]->IsInvisible() ) continue;

				RenderBeam(s_apxBeamPtrs[j],s_fEndEffectAngleTolerance);
				s_apxBeamPtrs[j]->m_bDontUpdateUntilRenderedOnce = false;
			}
		}
	}
}
