//
// Asura_Shadow_System.cpp
//
// Created by KJFL - 18 August 2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Shadows/Asura_Shadow_System.h"

#include "Light/Asura_Photon.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_System.h"
#include "Entity/Asura_Entity_Renderable.h"
#include "Entity/DynamicLight/Asura_ClientEntity_DynamicLight.h"
#include "Interface/Asura_CommandConsole.h"
#include "Shadows/Asura_Shadow_Instance.h"
#include "Light/Asura_GI_Solution.h"
#include "Rendering/Asura_Font_System.h"
#include "Camera/Asura_Camera.h"
#include "Projectors/Asura_Projector_System.h"

// JAT 18/10/07 - if lighting is off then certain render loops don't call Asura_DeferredShading::Render()
#ifdef ASURA_TOOLS
#include "Misc/Tools_Render.h"
#endif // #ifdef ASURA_TOOLS

// Library headers
#include <stdlib.h> // for qsort
	
u_int						Asura_Shadow_System::s_uNumShadows			= 0;
u_int						Asura_Shadow_System::s_uNumShadowInstances	= 0;
Asura_Shadow_Instance*		Asura_Shadow_System::s_apxShadowInstances[ASURA_MAX_NUMBER_OF_SHADOW_INSTANCES];

u_int						Asura_Shadow_System::s_uNumShadowTextureSizes		= ASURA_NUM_SHADOW_TEXTURE_SIZES;
u_int						Asura_Shadow_System::s_uNumShadowsPerTextureSize	= ASURA_NUM_SHADOWS_PER_TEXTURE_SIZE;
u_int						Asura_Shadow_System::s_uMaxShadowTextureSize		= ASURA_MAX_SHADOW_TEXTURE_SIZE;

u_int						Asura_Shadow_System::s_uMaxNonCubemapShadows		= (ASURA_NUM_SHADOW_TEXTURE_SIZES * ASURA_NUM_SHADOWS_PER_TEXTURE_SIZE);
u_int						Asura_Shadow_System::s_uMaxShadows					= (ASURA_NUM_SHADOW_TEXTURE_SIZES * ASURA_NUM_SHADOWS_PER_TEXTURE_SIZE) + ASURA_MAX_NUMBER_OF_CUBEMAP_SHADOWS;
Asura_Shadow**				Asura_Shadow_System::s_apxShadows					= NULL;

float						Asura_Shadow_System::s_fShadowCutoffDistance	= 10000.0f;
Asura_Shadow_System_Config	Asura_Shadow_System::s_xConfig;

bool						Asura_Shadow_System::s_bHiResShadowMode	= 0;

uint32						Asura_Shadow_System::s_uNumShadowCastingObjects = 0;
uint32 						Asura_Shadow_System::s_uNumShadowInstancesAdded = 0;
uint32 						Asura_Shadow_System::s_uNumShadowInstancesRendered = 0;


#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) 
bool						Asura_Shadow_System::s_bRenderEnable = true;
#endif

static float g_fAveragedShadowCutoff = 0.0f;
static float g_fMinAveragedShadowDarkness = 0.7f;

void Asura_Shadow_System::Initialise( void )
{
#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) 
	if(!Asura_CommandConsole::VarExists(L"Render.Shadows.Enable"))
	{
		Asura_CommandConsole::AddVar( L"Render.Shadows.Enable", &(s_bRenderEnable), Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.DisplayStats", &s_xConfig.m_bShowStats, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.DebugLevel", &s_xConfig.m_iDebugLevel,0,4, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.GIShadowSoftness", &s_xConfig.m_fGIShadowSoftness,0,8, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.GIShadowStrength", &s_xConfig.m_fGIShadowStrength,0,4, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.GIShadowCutoff",   &s_xConfig.m_fGIShadowCutoff,0,1, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.MaxShadowDistance", &s_xConfig.m_fMaxDistanceForShadows,0,Asura_Maths::LargeFloat, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.MaxFilterDistance", &s_xConfig.m_fMaxDistanceForShadowFiltering,0,Asura_Maths::LargeFloat, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Render.Shadows.MinAveragedShadowDarkness", &g_fMinAveragedShadowDarkness,0.0f,1.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar( L"Render.Shadows.AveragedShadowCutoff", &g_fAveragedShadowCutoff,0.0f,1.0f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );


	}
#endif

	s_uMaxNonCubemapShadows	= s_uNumShadowTextureSizes * s_uNumShadowsPerTextureSize;
	s_uMaxShadows			= s_uMaxNonCubemapShadows + ASURA_MAX_NUMBER_OF_CUBEMAP_SHADOWS;
	s_apxShadows			= new Asura_Shadow* [s_uMaxShadows];

	for(u_int u = 0; u < s_uMaxShadows; u++)
	{
		s_apxShadows[u]	= NULL;
	}
	Platform_Initialise();
}

void Asura_Shadow_System::Reset( void )
{
	DestroyShadowInstances();
	Platform_Reset();
}

void Asura_Shadow_System::Shutdown( void )
{
	DestroyShadowInstances();
	for(u_int u = 0; u < s_uMaxShadows; u++)
	{
		delete(s_apxShadows[u]);
		s_apxShadows[u]	= NULL;
	}
	delete[](s_apxShadows);
	s_apxShadows	= NULL;
	Platform_Shutdown();
}

void Asura_Shadow_System::Traverse_RenderCutsceneShadows (Asura_Hash_ID uHashID, Asura_Cutscene** ppxCutscene)
{
	(*ppxCutscene)->RenderShadows();
}



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::AddShadowInstances
// Author       : Dave Reed
// Date         : 12-Apr-2007
//
// Determines which shadow instances are needed for an object at a specified
// position, and calls the callback to create each shadow instance
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::AddShadowInstances(const Asura_Vector_3 &xPos, void *pxUserData, AddShadowInstanceCallback pfnCallback)
{
	// JAT 18/10/07 - if lighting is off then certain render loops don't call Asura_DeferredShading::Render()
#if defined ASURA_TOOLS && defined ASURA_TARGET_SM3
	if ( Tools_Render::LightingIsOn() == false )
	{
		return;
	}
#endif // defined ASURA_TOOLS && defined ASURA_TARGET_SM3

	Asura_Vector_3 xDir;

	// Default range for averaged lights is now here. May need a way to override this - make it related to the size of the shadowcasting
	// object, or at least have per-project control?
 	float fDefaultRange = 10.0f;


	s_uNumShadowCastingObjects++;
	

#ifndef ASURA_TARGET_SM3
	//
	// Old method of doing things. New way requires some next-gen features
	//
	// Get the brightest light direction - from the GI samples, use that to add a single shadow
	//

	Asura_Vector_3 xBrightestLightDir;			
	Asura_GI_Solution::s_xSHForCurrentRender.GetBrightestDirection( xBrightestLightDir );
	xDir = xBrightestLightDir;
	
	pfnCallback(Asura_ShadowInstance_CreationParams(ASURA_SHADOW_LIGHTSOURCE_AVERAGED,0,Asura_Vector_3(0,0,0), fDefaultRange, 1.0f, 1.0f, xDir, 0), pxUserData);
	s_uNumShadowInstancesAdded++;
#else					
	//
	// DaveR's new shadow adding system... Instead of always creating a single shadow, a callback is used
	// to add any number of shadows
	// 
	// Lights now have shadow flags, and may create their own shadows or contribute to an averaged 
	// shadow. Baked-in GI samples will contribute to the average shadow
	//
	
	// Init
	xDir = Asura_Vector_3(0,0,0);	
	float fSumRangeContrib		= 0.0f;
	float fSumStrengthContrib	= 0.0f;
	float fAvgStrength			= 0.0f;	
	float fAvgRange				= 0.0f;
	float fSumSoftnessContrib   = 0.0f;
	float fAvgSoftness			= 0.0f;



	// Check for any nearby photons that affect the shadow
	Asura_Bounding_Box xBox;		
	float fMaxDistance = s_xConfig.m_fMaxDistanceForShadows;
	xBox.MinX = xPos.x - fMaxDistance;
	xBox.MinY = xPos.y - fMaxDistance;
	xBox.MinZ = xPos.z - fMaxDistance;
	xBox.MaxX = xPos.x + fMaxDistance;
	xBox.MaxY = xPos.y + fMaxDistance;
	xBox.MaxZ = xPos.z + fMaxDistance;		

	// -----------------------------------------------------------------------------------------
	// Process nearby photons
	// -----------------------------------------------------------------------------------------
	Asura_Photon_System::MakePhotonListForBoundingBox(xBox,xPos,true,true);

	for(u_int u=0; u<Asura_Photon_System::NumberOfLocalPhotons; u++)
	{
		const Asura_Local_Photon* const pxPhoton = &Asura_Photon_System::LocalPhotons[u];

		uint32 uFlags = pxPhoton->GetShadowParameters().m_uFlags;
		float fStrength = pxPhoton->GetShadowParameters().m_fShadowStrength;

		if (uFlags & ASURA_SHADOWFLAG_SCALE_STRENGTH_BY_BRIGHTNESS)
		{			
			fStrength *= Asura_Maths::ClampToRange(0.333f*(pxPhoton->GetRedComponent()+pxPhoton->GetGreenComponent()+pxPhoton->GetBlueComponent()),0.0f,2.0f);
		}
		
		Asura_Vector_3 xLightVec = pxPhoton->GetPosition() - xPos;
		xLightVec.y += pxPhoton->GetShadowParameters().m_fVerticalOffset;

		// Lights affect the shadow direction for twice their actual range - It looks better if we do this, nearby short-range
		// lights will have a stronger effect on the shadow direction, and shadows shouldn't lose their direction as we move away
		// from the light
		float fRange = pxPhoton->GetRange();
		float fDoubleRange = 2.0f * fRange;
		float fDistSqr = xLightVec.MagnitudeSquared();

		if (uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW
			&& fDistSqr < (fDoubleRange*fDoubleRange)) // affects averaged shadow within 2x the light's range.
		{				
			float fDist = Asura_Maths::Sqrt(fDistSqr);
			float fContrib = (fDoubleRange-fDist)/fDoubleRange;					

			if (pxPhoton->GetPhoton()->GetIsDirectional())
			{
				xLightVec = pxPhoton->GetPhoton()->GetLightDirection();
			}

			xLightVec.Normalise();
			xDir = xDir + (xLightVec * fContrib);				

			fSumRangeContrib += fContrib;
			fAvgRange += fRange*fContrib;

			fSumSoftnessContrib +=fContrib;
			fAvgSoftness += pxPhoton->GetShadowParameters().m_fShadowSoftness*fContrib;

			// Shadow strength contribution uses the original light range, not the doubled range
			float fStrengthContrib = ((fRange)-fDist)/(fRange);
			if (fStrengthContrib>0.0f)
			{					
				fSumStrengthContrib += fStrengthContrib;						
				fAvgStrength += fStrength*fStrengthContrib*fStrengthContrib;					
			}
		}


		// Add the shadow from this light
		if (uFlags & ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS
			&& fDistSqr < (fDoubleRange*fDoubleRange)) // if it's in 2x the range of the light - this should really check the object's bounds.
		{	
			Asura_Vector_3 xLightPos = pxPhoton->GetPosition();
			float fExtraRange = 0.0f;

			if (!pxPhoton->GetPhoton()->GetIsDirectional())
			{
				xLightPos.y += pxPhoton->GetShadowParameters().m_fVerticalOffset;
			}
			else
			{
				// 'infinite' lights. A bit hacky at the moment... could do with improving if they stay in
				fExtraRange = 50.0f;
				xLightPos = xPos + (pxPhoton->GetPhoton()->GetLightDirection() * 50.0f);
			}

			pfnCallback( Asura_ShadowInstance_CreationParams( ASURA_SHADOW_LIGHTSOURCE_PHOTON, pxPhoton->GetHandle(), xLightPos, fRange+fExtraRange, 
															  fStrength, pxPhoton->GetShadowParameters().m_fShadowSoftness, Asura_Vector_3(0,0,0), uFlags),
															  pxUserData);

			s_uNumShadowInstancesAdded++;
		}
	}

	// -----------------------------------------------------------------------------------------
	// Process nearby projectors 
	//
	// It's not recommended to use darkening-pass shadows from projectors,
	// as the intensity/falloff comes from textures, so we can't do much to match
	// the shadow to these. But we attempt to, and also attempt to add a contribution 
	// to the averaged shadow from projectors
	// -----------------------------------------------------------------------------------------
	int iNumLocalProjectors		    = Asura_Projector_System::MakeLocalProjectorList(xBox);
	Asura_Projector** ppxProjectors = Asura_Projector_System::GetLocalProjectorList();

	for(int i=0;i<iNumLocalProjectors;i++)
	{
		Asura_Projector* pxProjector = ppxProjectors[i];
		const Asura_Vector_3& xProjPos = pxProjector->GetPosition();
		const Asura_ShadowParameters& xParams = pxProjector->GetShadowParameters();

		float fRange = pxProjector->GetMaxRange();
		float fStrength = xParams.m_fShadowStrength;
		
		if (xParams.m_uFlags & ASURA_SHADOWFLAG_SCALE_STRENGTH_BY_BRIGHTNESS)
		{			
			const float* pfRGB = pxProjector->GetRGB();
			float fIntensity = pfRGB[0]+pfRGB[1]+pfRGB[2];
			fIntensity *= 0.333f * pxProjector->GetIntensity();
			fStrength *= Asura_Maths::ClampToRange(fIntensity,0.0f,2.0f);
		}

		

		float fFade = pxProjector->FrustumFade(xPos);

		if (xParams.m_uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW)
		{		
			// Fade the shadow as it enters/leaves the projector frustum
			//
			// TODO - make this dependent on the object size? Currently fades out
			// when the centre point moves beyond the frustum.				
			//
			// As with the photons, the contribution to the direction of the average
			// shadow fades out over a longer distance, so the shaodow fades out 
			// completely before losing it's direction
			float fDirContribFade = fFade+0.5f; // fade direction over a longer range (outside the lit area)
			float fContrib = (fFade)*2.0f; //don't fade in the middle 50% of the area
			fDirContribFade = Asura_Maths::ClampToRange( fDirContribFade, 0.0f, 1.0f );
			fContrib = Asura_Maths::ClampToRange( fContrib, 0.0f, 1.0f );

			// Add the contribution from this projector to the average shadow
			if (fDirContribFade>0.0f)
			{				
				Asura_Vector_3 xLightVec = xProjPos - xPos;
				xLightVec.Normalise();
				xDir = xDir + (xLightVec * fDirContribFade);				
			}
				
			if (fContrib>0.0f)
			{
				fSumRangeContrib += fContrib;				
				fAvgRange += fRange*fContrib;

				fSumSoftnessContrib +=fContrib;
				fAvgSoftness += xParams.m_fShadowSoftness*fContrib;

				// square the contribution to the average strength, so that even if this is the only light the shadow will still fade out
				// do not square the contribution to range & softness, we only want them to fade if there's another light affecting them
				fSumStrengthContrib += fContrib;						
				fAvgStrength += fStrength*fContrib*fContrib;	
			}
		}

		if (xParams.m_uFlags & ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS && fFade > -1.0f) // if it's in 2x the range of the light - this should really check the object's bounds.
		{	
			if ( fFade < 0.0f )
			{
				// this value is not a robust bounding check against the volume, so fade out in case the shadow is still visible when we think it's outside.
				// This tends to happen only when objects get very close to the projector.
				fStrength *= fFade+1.0f;
			}
		
			// Add the shadow from this projector
			pfnCallback( Asura_ShadowInstance_CreationParams( ASURA_SHADOW_LIGHTSOURCE_PROJECTOR, pxProjector->GetHandle(), 
															  xProjPos, fRange, fStrength, xParams.m_fShadowSoftness,
															  Asura_Vector_3(0,0,0), xParams.m_uFlags),
															  pxUserData);

			s_uNumShadowInstancesAdded++;
		}
	}

	// Are any shadows being picked up from the global illumination (baked-in lights only)?
	//BW some tests lead me to believe that these SH values are either outdated or just plain wrong at this point, shadows from GI jump around and sometimes point towards the baked lightsource
	Asura_Vector_3 xBrightestLightDir;			
	Asura_GI_Solution::s_xStaticSHForCurrentRender.GetBrightestDirection( xBrightestLightDir );		

	const Asura_SphericalHarmonics_Order2_RGB& xSH = Asura_GI_Solution::s_xStaticSHForCurrentRender;
	Asura_RGBA xSHVal;
	xSH.EvaluateInDirection( xBrightestLightDir, xSHVal );
	float fGIContrib = xSHVal.GetLuminance();

	// We have a cut-off, to prevent dim (reflected?) GI light from creating feint shadows everywhere.
	// This can be controlled per-project via Asura_Shadow_System::Config
	fGIContrib -= s_xConfig.m_fGIShadowCutoff;
	if (fGIContrib > 0.0f)
	{		
		// for global illumination we can't compute how strong the influence of a light is, so calculate it from it's brightness.
		float fFaded = fGIContrib / (s_xConfig.m_fGIShadowCutoff+1.0f); //BW scale brightness based on how bright the cutoff is, 0 cutoff = scale 1.
		if ( fFaded > 1.0f ) fFaded = 1.0f; // cap it, since we can't use shadows stronger than 1.0
		
		// square the contribution to the average strength, so that even if this is the only light the shadow will still fade out
		// do not square the contribution to range & softness, we only want them to fade if there's another light affecting them - otherwise shadow will become sharper as it fades
		fSumStrengthContrib += fFaded;								
		fAvgStrength		+= fFaded * fFaded * s_xConfig.m_fGIShadowStrength;				

		fSumSoftnessContrib += fFaded;								
		fAvgSoftness		+= fFaded * s_xConfig.m_fGIShadowSoftness;				
		fSumRangeContrib	+= fFaded;						
		fAvgRange			+= fFaded * fDefaultRange;
		xDir			    += (xBrightestLightDir * fFaded);
	}


	xDir.Normalise();

	// -----------------------------------------------------------------------------------------
	// Add the averaged shadow, if we have one
	// -----------------------------------------------------------------------------------------
	if (fSumStrengthContrib>0.0f && fAvgStrength>0.0f)
	{
		fAvgRange/=fSumRangeContrib;
		fAvgSoftness/=fSumSoftnessContrib;
		fAvgStrength/=fSumStrengthContrib;

		// clamp strength to a minimum (to force them to appear), and force softness of 1.0f
		fAvgStrength = Asura_Maths::Max( g_fMinAveragedShadowDarkness, fAvgStrength );
		fAvgSoftness = 1.0f;

		// reject shadows which are too faint
		if ( fAvgStrength < g_fAveragedShadowCutoff ) return;

		pfnCallback(Asura_ShadowInstance_CreationParams(ASURA_SHADOW_LIGHTSOURCE_AVERAGED,0,Asura_Vector_3(0,0,0),
														fAvgRange, fAvgStrength, fAvgSoftness, xDir, ASURA_SHADOWFLAG_USE_NOISE),
														pxUserData);		
		s_uNumShadowInstancesAdded++;
	}		
#endif
	
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::SortInstancesByDistance
// Author       : Andy Tate
// Date         : 28-Nov-2006
////////////////////////////////////////////////////////////////////////////////

int Asura_Shadow_System::SortInstancesByDistance (const void* pData1,
												  const void* pData2)
{
	Asura_Shadow_Instance const* const*	ppxInstance1	= static_cast<Asura_Shadow_Instance const* const*>(pData1);
	Asura_Shadow_Instance const* const*	ppxInstance2	= static_cast<Asura_Shadow_Instance const* const*>(pData2);
	const float							fDistance1		= (*ppxInstance1)->GetDistance();
	const float							fDistance2		= (*ppxInstance2)->GetDistance();

	if(fDistance1 > fDistance2)
	{
		return(1);
	}
	else if(fDistance1 < fDistance2)
	{
		return(-1);
	}
	else
	{
		return(0);
	}
}

/// End of function 'Asura_Shadow_System::SortInstancesByDistance'. ///





////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::Render
// Author       : Dave Reed
// Date         : 12-Apr-2007
//
// For compatibility with non-deferred-shading projects.
////////////////////////////////////////////////////////////////////////////////

#if !defined(ASURA_TARGET_SM3)

void Asura_Shadow_System::Render()
{
	// If deferred shading is not enabled, this will behave as before.
	Asura_Shadow_System::RenderInstances();
	Asura_Shadow_System::RenderOnToScene();
}

#endif // !defined(ASURA_TARGET_SM3)


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::RenderInstances
// Author       : Andy Tate 
// Date         : 28-Nov-2006
//
// DMR 12-Apr-2007. This is basically the old Render() call, but without the 
// RenderOnToScene(). This renders all the shadow buffers.
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::RenderInstances(void)
{
#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) 
	if( false == s_bRenderEnable)
	{
		 return;
	}
#endif

	if (!Asura_Render::IsSystemEnabled(ASURA_RENDER_SYSTEM_SHADOWS))
	{
		return;
	}

	// Do we wish to change the size of our shadows?
#ifdef ASURA_USE_SCREENSHOT_SYSTEM
	static bool bHiResShadowMode = false;
	if(s_bHiResShadowMode != bHiResShadowMode)
	{
#if !(defined(ASURA_TOOLS))
		Platform_Shutdown();
#endif

		DestroyShadowInstances();
		for(u_int u = 0; u < s_uMaxShadows; u++)
		{
			delete(s_apxShadows[u]);
			s_apxShadows[u]	= NULL;
		}
		delete[](s_apxShadows);
		s_apxShadows	= NULL;

		// Are we enabling/disabling hi-res mode?
		if(s_bHiResShadowMode)
		{
			s_uNumShadowTextureSizes = ASURA_NUM_HIRES_SCREENSHOT_SHADOW_TEXTURE_SIZES;
			s_uNumShadowsPerTextureSize = ASURA_NUM_HIRES_SCREENSHOT_SHADOWS_PER_TEXTURE_SIZE;
			s_uMaxShadowTextureSize = ASURA_MAX_HIRES_SCREENSHOT_SHADOW_TEXTURE_SIZE;
		}
		else
		{
			s_uNumShadowTextureSizes = ASURA_NUM_SHADOW_TEXTURE_SIZES;
			s_uNumShadowsPerTextureSize = ASURA_NUM_SHADOWS_PER_TEXTURE_SIZE;
			s_uMaxShadowTextureSize = ASURA_MAX_SHADOW_TEXTURE_SIZE;
		}
		bHiResShadowMode = s_bHiResShadowMode;



		s_uMaxNonCubemapShadows	= s_uNumShadowTextureSizes * s_uNumShadowsPerTextureSize;
		s_uMaxShadows			= s_uMaxNonCubemapShadows + ASURA_MAX_NUMBER_OF_CUBEMAP_SHADOWS;
		s_apxShadows			= new Asura_Shadow* [s_uMaxShadows];

		for(u_int u = 0; u < s_uMaxShadows; u++)
		{
			s_apxShadows[u]	= NULL;
		}
#if defined(ASURA_TOOLS)
		Platform_SetHiResShadowMode(s_bHiResShadowMode);
#else
		Platform_Initialise();
#endif
	}
#endif

	{
		Asura_Classified_List_It<Asura_Entity_Renderable>	xIterator(Asura_Entity_Renderable::GetRenderableList());

		// Set-up the camera...
		Asura_Shadow_Instance::SetupCamera(0, s_fShadowCutoffDistance);

		// Add shadows from entities...
		while(!xIterator.Done())
		{
			Asura_Entity_Renderable*	pxEntity	= xIterator.GetCurrent();

			pxEntity->RenderShadow();
			xIterator.Next();
		}
	}

#if defined(ASURA_TARGET_SM3) && ASURA_TARGET_SM3
	//environment shadows
	{
		Asura_Photon_Iterator xPhotonIterator(Asura_Photon_System::GetDynamicIterator());
		for(xPhotonIterator.Begin();!xPhotonIterator.End();xPhotonIterator.Next())
		{
			Asura_Photon *pxPhoton = xPhotonIterator.GetCurrentPointer();

			if ( pxPhoton && ((pxPhoton->GetShadowParameters().m_uFlags & ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS) != 0) && pxPhoton->IsActive())
			{
#ifdef ASURA_USE_NEW_VISIBILITY_SYSTEM
				if (pxPhoton->IsVisible())
#else
				if (Asura_Camera::IsSphereInFrustum( pxPhoton->GetPosition(), pxPhoton->GetRange()))
#endif
				{
					Asura_ShadowInstanceEnvironment_CreationParams xCreationParams;
					xCreationParams.m_eSource = ASURA_SHADOW_LIGHTSOURCE_PHOTON;
					xCreationParams.m_fLightRange = pxPhoton->GetRange();
					xCreationParams.m_fSoftness = pxPhoton->GetShadowParameters().m_fShadowSoftness;
					xCreationParams.m_fStrength = pxPhoton->GetShadowParameters().m_fShadowStrength;
					xCreationParams.m_uHandle = (u_int)pxPhoton->GetHandle();
					xCreationParams.m_uShadowFlags = pxPhoton->GetShadowParameters().m_uFlags &(ASURA_SHADOWFLAG_IS_DARKENING_PASS|ASURA_SHADOWFLAG_USE_NOISE);
					xCreationParams.m_xLightDirection = Asura_Vector_3(0, 0,1);
					xCreationParams.m_xLightPos = pxPhoton->GetPosition();
					xCreationParams.m_xLightPos.y += pxPhoton->GetShadowParameters().m_fVerticalOffset;

					xCreationParams.m_xOrient = Asura_Maths::IdentityMatrix3x3;
					xCreationParams.m_fFov = Asura_Maths::Deg360;
					xCreationParams.m_fNearClip = 0.1f;
					xCreationParams.m_fFarClip = pxPhoton->GetRange();
					xCreationParams.m_bCubeMap = true;
					Asura_Shadow_System::AddShadowInstance (new Asura_Shadow_Instance_Environment(xCreationParams));
				}
			}
		}
		Asura_Projector_Iterator xProjectorIterator(Asura_Projector_System::GetIterator());
		for(xProjectorIterator.Begin();!xProjectorIterator.End();xProjectorIterator.Next())
		{
			Asura_Projector *pxProjector = xProjectorIterator.GetCurrentPointer();

			if ( pxProjector && ((pxProjector->GetShadowParameters().m_uFlags & ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS) != 0))
			{
#ifdef ASURA_USE_NEW_VISIBILITY_SYSTEM
				if (pxProjector->IsVisible())
#else
				if (Asura_Camera::IsSphereInFrustum( pxProjector->GetPosition(), pxProjector->GetMaxRange()))
#endif
				{
					Asura_ShadowInstanceEnvironment_CreationParams xCreationParams;
					xCreationParams.m_eSource = ASURA_SHADOW_LIGHTSOURCE_PROJECTOR;
					xCreationParams.m_fLightRange = pxProjector->GetMaxRange();
					xCreationParams.m_fSoftness = pxProjector->GetShadowParameters().m_fShadowSoftness;
					xCreationParams.m_fStrength = pxProjector->GetShadowParameters().m_fShadowStrength;
					xCreationParams.m_uHandle = (u_int)pxProjector->GetHandle();
					xCreationParams.m_uShadowFlags = pxProjector->GetShadowParameters().m_uFlags & (ASURA_SHADOWFLAG_IS_DARKENING_PASS|ASURA_SHADOWFLAG_USE_NOISE);
					xCreationParams.m_xLightDirection = Asura_Vector_3(-pxProjector->GetOrient().e13, -pxProjector->GetOrient().e23, -pxProjector->GetOrient().e33);
					xCreationParams.m_xLightPos = pxProjector->GetPosition();
					xCreationParams.m_xLightPos.y += pxProjector->GetShadowParameters().m_fVerticalOffset;


					xCreationParams.m_xOrient = pxProjector->GetOrient();
					xCreationParams.m_fFov = pxProjector->GetFoV();
					xCreationParams.m_fNearClip = Asura_Maths::Min(pxProjector->GetMinRange(), 0.1f);
					xCreationParams.m_fFarClip = pxProjector->GetMaxRange();
					xCreationParams.m_bCubeMap = false;

					Asura_Shadow_System::AddShadowInstance (new Asura_Shadow_Instance_Environment(xCreationParams));
				}
			}
		}
	}
#endif


	// ...and cutscenes...
	Asura_Cutscene_System::TraverseCurrentCutscenes(Traverse_RenderCutsceneShadows);

	// Sort the shadows by priority...
	for(u_int u = 0; u < s_uNumShadowInstances; u++)
	{
		Asura_Shadow_Instance*	pxInstance = s_apxShadowInstances[u];

		pxInstance->CalculateDistance();
	}
	qsort(&s_apxShadowInstances[0], s_uNumShadowInstances, sizeof(s_apxShadowInstances[0]), SortInstancesByDistance);

	// Generate the shadows themselves...
	Platform_BeginCreateShadows();
	{
		Asura_Assert((s_uNumShadows == 0), "Shadow rendering is not re-initialised.");

		u_int uNumNonCubeMappedShadows = 0;
		u_int uNumCubeMappedShadows = 0;
		s_uMaxNonCubemapShadows	= s_uNumShadowTextureSizes * s_uNumShadowsPerTextureSize;

		for(u_int u = 0; 
			(u < s_uNumShadowInstances) && 
			(s_uNumShadows < s_uMaxShadows) && 
			(s_apxShadowInstances[u]->GetDistance() < s_fShadowCutoffDistance); 
			u++)
		{
			if (s_apxShadowInstances[u]->IsCubeMapped())
			{
				if (uNumCubeMappedShadows < ASURA_MAX_NUMBER_OF_CUBEMAP_SHADOWS)
				{
					Asura_Shadow*	pxShadow	= s_apxShadows[s_uNumShadows++];

					// Cube mapped shadows dont get rendered yet
					pxShadow->SetInstance(s_apxShadowInstances[u]);

					uNumCubeMappedShadows++;
				}
			} else {
				if (uNumNonCubeMappedShadows < s_uMaxNonCubemapShadows)
				{
					Asura_Shadow*	pxShadow	= s_apxShadows[s_uNumShadows++];

					// Render this shadow...
					pxShadow->SetInstance(s_apxShadowInstances[u]);
					pxShadow->Render();

					uNumNonCubeMappedShadows++;
				}
			}
		}
	}
	Platform_EndCreateShadows();

	
}



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::RenderOnToScene
// Author       : Dave Reed
// Date         : 11-May-2007
//
// Renders any averaged shadows (shadows that don't belong to specific light
// sources - or those that do but are set to render as a darkening pass)
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::RenderOnToScene(void)
{
	// Apply them to the scene...
	Platform_RenderOnToScene(ASURA_SHADOW_LIGHTSOURCE_AVERAGED, 0);	
}



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::FinishedRenderingShadows
// Author       : Dave Reed
// Date         : 01-June-2007
//
// Shadow rendering is finished - deallocate the shadow instances
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::FinishedRenderingShadows(void)
{
	// Tidy-up...
	DestroyShadowInstances();
}



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::BuildShadowMask
// Author       : Dave Reed
// Date         : 11-May-2007
//
// This s called from the deferred photon/projector renderers, to project the
// shadows for a specific light source into the dest. alpha channel - which is 
// used as a 'shadow mask' for that light source
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::BuildShadowMask(ASURA_SHADOW_LIGHTSOURCE eLightSource, uint32 uHandle)
{
	Platform_RenderOnToScene(eLightSource, uHandle);
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::SetHiResShadowMode
// Author       : Andy Southgate
// Date         : 18-Jun-2008
////////////////////////////////////////////////////////////////////////////////
#if defined(ASURA_USE_SCREENSHOT_SYSTEM)
void Asura_Shadow_System::SetHiResShadowMode(bool bValue)
{
	s_bHiResShadowMode = bValue;

}
#endif


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::DestroyShadowInstances
// Author       : Andy Tate
// Date         : 29-Nov-2006
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::DestroyShadowInstances (void)
{
	for(u_int u = 0; u < s_uNumShadowInstances; u++)
	{
		delete(s_apxShadowInstances[u]);
		s_apxShadowInstances[u] = NULL;
	}

	s_uNumShadows			= 0;
	s_uNumShadowInstances	= 0;
}

/// End of function 'Asura_Shadow_System::DestroyShadowInstances'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::AddShadowInstance
// Author       : Andy Tate
// Date         : 28-Nov-2006
////////////////////////////////////////////////////////////////////////////////

bool Asura_Shadow_System::AddShadowInstance (Asura_Shadow_Instance* pxInstance)
{
	if(s_uNumShadowInstances < ASURA_MAX_NUMBER_OF_SHADOW_INSTANCES)
	{
		s_apxShadowInstances[s_uNumShadowInstances++]	= pxInstance;
		return(true);
	}
	else
	{
		delete(pxInstance);
		return(false);
	}
}

/// End of function 'Asura_Shadow_System::AddShadowInstance'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::LightHasActiveShadows
// Author       : Dave Reed
// Date         : 3-Apr-2007
////////////////////////////////////////////////////////////////////////////////

bool Asura_Shadow_System::LightHasActiveShadows(ASURA_SHADOW_LIGHTSOURCE eLightSource, uint32 uLightSourceHandle)
{	
	for( u_int u = 0; u < s_uNumShadowInstances; ++u )
	{
		const Asura_Shadow_Instance*	pxInstance	= s_apxShadowInstances[u];

		if (pxInstance->GetLightSourceType() == eLightSource)
		{						
			if (uLightSourceHandle == pxInstance->GetLightSourceHandle())
			{
				if (!(pxInstance->GetShadowFlags()&ASURA_SHADOWFLAG_IS_DARKENING_PASS))
				{
					return true;
				}				
			}
		}
	}

	return false;
}

/// End of function 'Asura_Shadow_System::LightHasActiveShadows'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Shadow_System::RenderStats
// Author       : Dave Reed
// Date         : 3-Apr-2007
////////////////////////////////////////////////////////////////////////////////

void Asura_Shadow_System::RenderStats(void)
{	
	if (s_xConfig.m_bShowStats)
	{	
		const u_int uFormat = ASURA_TEXTFORMAT_RELATIVEPOSITION | ASURA_TEXTFORMAT_FORMATSTRING | ASURA_TEXTFORMAT_DROPSHADOWS;
		const float fScale = 0.8f;

		// Just pick the first font available
		Asura_Font_System::SetFont(0);

		Asura_Font_System::Print( 0.05f, 0.77f, fScale, 0xffffffff, uFormat, L"Shadow Instances Rendered: %d", s_uNumShadowInstancesRendered );		
		Asura_Font_System::Print( 0.05f, 0.8f, fScale, 0xffffffff, uFormat, L"(Objects Processed %d,  Shadow Instances Requested: %d)", s_uNumShadowCastingObjects,s_uNumShadowInstancesAdded );
	}

	s_uNumShadowInstancesAdded = 0;
	s_uNumShadowCastingObjects = 0;
	s_uNumShadowInstancesRendered = 0;
}




////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ShadowParameters::ReadFromChunkStream
// Author       : Dave Reed
// Date         : 29-Mar-2007
////////////////////////////////////////////////////////////////////////////////

void Asura_ShadowParameters::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	uint32 uVersion;

	xStream >> uVersion;
	Asura_Assert( uVersion <= CURRENT_VERSION, "Unknown chunk version!" );

	// JAT 11/04/07 - don't need to check if a u_int is +ve it always is!
	//if ( uVersion >= 0 )
	{
		xStream >> m_uFlags;
		xStream >> m_fShadowStrength;
		xStream >> m_fVerticalOffset;		
	}

	if ( uVersion >= 1 )
	{
		xStream >> m_fShadowSoftness;
	}
	else
	{
		m_fShadowSoftness = 1.0f;
	}
}


void Asura_ShadowParameters::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	uint32 uVersion = CURRENT_VERSION;
	xStream << uVersion;
	xStream << m_uFlags;
	xStream << m_fShadowStrength;	
	xStream << m_fVerticalOffset;	
	xStream << m_fShadowSoftness;	
}




/// End of file. ///
