//
// Asura_Platform_Beam.cpp
//
// Created by KOR - 10 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

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

// Asura headers
#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 "Rendering/Asura_GX.h"
#include "Rendering/Asura_GX_TextureManagement.h"

// Library headers
#include <revolution/gx.h>

void Asura_Beam_System::Platform_Initialise() {}
void Asura_Beam_System::Platform_Shutdown() {}

void RenderBeam(Asura_Beam* pxBeam)
{
	// calculate the vertex positions for triangle fan 
	Asura_Vector_3 xVertex[8];
	
	// get camera set up
	const Asura_Matrix_3x3* cameraOrientation = Asura_Camera::GetOrientation();
	const Asura_Vector_3* pxPos = Asura_Camera::GetPosition();

	// get camera data
	Asura_Vector_3 xCameraSide = Asura_Vector_3(cameraOrientation->e11,cameraOrientation->e21,cameraOrientation->e31);
//	Asura_Vector_3 xCameraDown = Asura_Vector_3(cameraOrientation->e12,cameraOrientation->e22,cameraOrientation->e32);
	Asura_Vector_3 xCameraForward = Asura_Vector_3(cameraOrientation->e13,cameraOrientation->e23,cameraOrientation->e33);
	Asura_Vector_3 xCameraPos = Asura_Vector_3(pxPos->x,pxPos->y,pxPos->z);

	// new Beam positions
	Asura_Vector_3 xBeamStart = pxBeam->GetTailPos();
	Asura_Vector_3 xBeamEnd; 

	// Get template data
	const Asura_Beam_Template* pxTemplate = pxBeam->GetTemplate();

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

	// calculate new vector directions 
	Asura_Vector_3 xEyeVector = xCameraPos - xBeamStart;
	xEyeVector.Normalise();
	Asura_Vector_3 xPerpVector;

	Asura_RGBA xTailRGBA, xHeadRGBA;
	pxBeam->CalcCurColours( &xTailRGBA, &xHeadRGBA );
	const u_int uHeadDiffuse = xHeadRGBA.ToUIntRGBA();
	const u_int uTailDiffuse = xTailRGBA.ToUIntRGBA();

	float fTailRadius, fHeadRadius;
	pxBeam->CalcCurRadii(&fTailRadius, &fHeadRadius);

	// 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 fAngleTolerance = 0.0002f;
	float fAngleToLineOfSight= xEyeVector * pxBeam->GetDir();
	
	// switch angle sign if neg to reduce later work
	if (fAngleToLineOfSight < 0.0f)
	{
		fAngleToLineOfSight *= -1.0f;
	}
	
	// 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)
	{		
		xBeamEnd = xBeamStart;

		if (fAngleToLineOfSight >= 1.0f)
		{
			//alternative to Beam direction needed, use camera side
			xPerpVector = xCameraSide;
		}
		else 
		{
			// cross product ok to use
			xPerpVector = (pxBeam->GetDir()) ^ xEyeVector;
		}

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

		// add length of beam
		xBeamEnd = pxBeam->CalcCurHeadPos();//xBeamStart;

		//cross product ok to use
		xPerpVector = (pxBeam->GetDir()) ^ xEyeVector;
	
		xPerpVector.Normalise();	
		Asura_Vector_3 xUp = xCameraForward ^ xPerpVector;
		xUp.Normalise();
		Asura_Vector_3 xRight = xCameraForward ^ xUp;
		
		// first fan
		xVertex[0] = xRight;
		xVertex[0] *= -fTailRadius;
		xVertex[0] += xBeamStart;
		
		xVertex[1] = xUp;
		xVertex[1] *= fTailRadius;
		xVertex[1] += xBeamStart;

		xVertex[2] = xRight;
		xVertex[2] *= fTailRadius;
		xVertex[2] += xBeamStart;
		
		xVertex[3] = xUp;
		xVertex[3] *= -fTailRadius;
		xVertex[3] += xBeamStart;

		GXBegin(GX_TRIANGLEFAN, GX_VTXFMT3, 4);
			GXPosition3f32(xVertex[0].x, xVertex[0].y, xVertex[0].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu1, 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2));

			GXPosition3f32(xVertex[1].x, xVertex[1].y, xVertex[1].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu1, pxTileInfo->m_fv1);

			GXPosition3f32(xVertex[2].x, xVertex[2].y, xVertex[2].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2), pxTileInfo->m_fv1);

			GXPosition3f32(xVertex[3].x, xVertex[3].y, xVertex[3].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu1, pxTileInfo->m_fv1);
		GXEnd();

		//now draw second point in distance
		xVertex[0] = xRight;
		xVertex[0] *= -fHeadRadius;
		xVertex[0] += xBeamEnd;
		
		xVertex[1] = xUp;
		xVertex[1] *= fHeadRadius;
		xVertex[1] += xBeamEnd;

		xVertex[2] = xRight;
		xVertex[2] *= fHeadRadius;
		xVertex[2] += xBeamEnd;
		
		xVertex[3] = xUp;
		xVertex[3] *= -fHeadRadius;
		xVertex[3] += xBeamEnd;
		
		GXBegin(GX_TRIANGLEFAN, GX_VTXFMT3, 4);
			GXPosition3f32(xVertex[0].x, xVertex[0].y, xVertex[0].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu2, 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2));

			GXPosition3f32(xVertex[1].x, xVertex[1].y, xVertex[1].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu2, pxTileInfo->m_fv2);

			GXPosition3f32(xVertex[2].x, xVertex[2].y, xVertex[2].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2), pxTileInfo->m_fv2);

			GXPosition3f32(xVertex[3].x, xVertex[3].y, xVertex[3].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu2, pxTileInfo->m_fv2);
		GXEnd();

		// keep track of num triangles
		Asura_Render::AddToTrianglesRendered(4, 1, ASURA_RENDER_TTYPE_OTHER);

	}
	else 
	{
		// add length of beam
		xBeamEnd = pxBeam->CalcCurHeadPos();

		//cross product ok to use
		xPerpVector = (pxBeam->GetDir()) ^ 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 

		xVertex[0] = xUp;
		xVertex[0] *= fTailRadius;
		xVertex[0] += xBeamStart;
		
		xVertex[1] = xRight;
		xVertex[1] *= -fTailRadius;
		xVertex[1] += xBeamStart;

		xVertex[2] = xRight;
		xVertex[2] *= fTailRadius;
		xVertex[2] += xBeamStart;
		
		xVertex[3] = xRight;
		xVertex[3] *= -fHeadRadius;
		xVertex[3] += xBeamEnd;
		
		xVertex[4] = xRight;
		xVertex[4] *= fHeadRadius;
		xVertex[4] += xBeamEnd;

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

		GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT3, 6);
			GXPosition3f32(xVertex[0].x, xVertex[0].y, xVertex[0].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu1, pxTileInfo->m_fv1);

			GXPosition3f32(xVertex[1].x, xVertex[1].y, xVertex[1].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu1, 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2));

			GXPosition3f32(xVertex[2].x, xVertex[2].y, xVertex[2].z);
			GXColor1u32(uTailDiffuse);
			GXTexCoord2f32(0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2), pxTileInfo->m_fv1);

			GXPosition3f32(xVertex[3].x, xVertex[3].y, xVertex[3].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(0.5f * (pxTileInfo->m_fu1 + pxTileInfo->m_fu2), pxTileInfo->m_fv2);

			GXPosition3f32(xVertex[4].x, xVertex[4].y, xVertex[4].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu2, 0.5f * (pxTileInfo->m_fv1 + pxTileInfo->m_fv2));

			GXPosition3f32(xVertex[5].x, xVertex[5].y, xVertex[5].z);
			GXColor1u32(uHeadDiffuse);
			GXTexCoord2f32(pxTileInfo->m_fu2, pxTileInfo->m_fv2);
		GXEnd();

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

	
}

void Asura_Beam_System::Platform_RenderStart(void)
{
	// get a suitable texture
	static int BeamTexture = 0;
	if (!BeamTexture)
	{
		BeamTexture = Asura_Texture_Management::AddTextureWithResetGroup("beams.tpl", ASURA_TEXTURE_LOADED_PERMANENT );
		Asura_Texture_Management::LoadTextures();
	}		

	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	// AJG - Copied below from PS2
// (MP - 03.07.2007) Crappy partial support that's sufficient for the Simpsons.
#ifdef ASURA_BEAMS_ALL_MODES
    Asura_Render::CurrentStates.RequestTranslucencyMode( s_apxBeamPtrs[0]->GetTranslucencyMode() );
#else
    Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_GLOWING );
#endif
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);	
	Asura_Render::CurrentStates.RequestTextureIndex(0,BeamTexture);

	// No lighting
	GXSetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE);
	
	GXClearVtxDesc();
	GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
	GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
	GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);

	// Use Unit Matrix in Texture Coordinate Generation
	GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
	GXSetNumTexGens(1);
	GXSetNumChans(1);
	GXSetNumTevStages(1);
	// Stage 0 hands over the CMPR Texture Colors to the subsequent stage without modification.
	GXSetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
	// Input = Tex colour, raster colour
	GXSetTevColorIn( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO );
	// Output Color = Tex colour * raster colour
	GXSetTevColorOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
	// Input = 0
	GXSetTevAlphaIn( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
	// Output = 0
	GXSetTevAlphaOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );

	if(Asura_GX_Texture_Management::IsAlphaTextureLoaded())
	{
		GXSetNumTevStages(2);
		GXSetTevOrder( GX_TEVSTAGE1, GX_TEXCOORD0, Asura_GX_Texture_Management::GetAlphaTexMapID(0), GX_COLOR_NULL);
		// Input = Tex colour, raster colour
		GXSetTevColorIn( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);
		GXSetTevColorOp( GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
		GXSetTevAlphaIn( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO );
		GXSetTevAlphaOp( GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
	}

}

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
				RenderBeam(s_apxBeamPtrs[j]);
				s_apxBeamPtrs[j]->SetDontUpdateUntilRenderedOnce( false );
			}
		}
	}
}

