//
// Tools_EnvironmentalLightTransport.cpp
//
// Created by KJFL - 2004-04-23 14:43:40
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Lighting/Tools_EnvironmentalLightTransport.h"

#include "Light/Asura_GI_Solution.h"
#ifdef ASURA_USE_STREAMING
#include "Streaming/Asura_Streaming_System.h"
#endif

#include "Mesh/Asura_Env_System.h"

#include "Lighting/Tools_Environment_LOS.h"
#include "Lighting/Tools_LightSets.h"
#include "Lighting/Tools_PhotonMapping_System.h"
#include "Lighting/Tools_PhotonMapping_System_Internal.h"
#include "Load&Save/Tools_BatchProcessing.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Adjacency.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Streaming/Tools_Streaming_System.h"

// TODO - this can be removed when the UseMultithreading tickbox is removed
#include "Lighting/Tools_Mode_PhotonMapping.h"

//A dot product to use for polygons that are ignoring their normals.
//RAR 17/02/2006
static const float g_fLightScaleForIgnoreNormal = 0.5f;
																						//Original values		Team Sabre
u_int	Tools_EnvironmentalLightTransport_Handler::s_uDirectLightingExtraSharpenFactor		= 0;				//5
float	Tools_EnvironmentalLightTransport_Handler::s_fDirectLightingExtraIntensityFactor	= 1.0f;				//11.0f
float	Tools_EnvironmentalLightTransport_Handler::s_fInterreflectedScaleFactor				= 1.0f;				//10.0f/3.0f  //RAR 20/06/2006 renormalised so that default is 1
float	Tools_EnvironmentalLightTransport_Handler::s_fSamplingHeight						= 0.001f;			//1.0f

////////////////////////////////////////////////////////////////////////////////////////////////////
/*-------------------------------------------**
** Tools_EnvironmentalLightTransport_Handler **
**-------------------------------------------*/

Tools_EnvironmentalLightTransport_Handler::Tools_EnvironmentalLightTransport_Handler()
{
	#ifdef ASURA_USE_STREAMING
	Initialise();
	#endif
}

Tools_EnvironmentalLightTransport_Handler::~Tools_EnvironmentalLightTransport_Handler()
{
	#ifdef ASURA_USE_STREAMING
	Shutdown();
	#endif
}


void Tools_EnvironmentalLightTransport_Handler::Initialise()
{
	m_pxModules = 0;

	m_uNumberOfModules = 0;

	m_bValidData = false;
	m_bQuickData = false;
}

void Tools_EnvironmentalLightTransport_Handler::Reset( void )
{
	// shutdown actually does the same things we want from reset - delete memory and reset values to zero
	Shutdown();

	m_bValidData = false;
	m_bQuickData = false;
}

void Tools_EnvironmentalLightTransport_Handler::Shutdown( void )
{
	// Currently Shutdown does the same job as Reset(), so Reset() calls this function;
	// be aware of this if you make any changes.
	
	delete [] m_pxModules;

	m_pxModules = 0;
	
	m_uNumberOfModules = 0;
}

bool Tools_EnvironmentalLightTransport_Handler::InitialiseModuleData( void )
{
	if (!Asura_Module_System::IsInitialised())
	{
		Asura_Assert( false, "Module system not yet initialised!" );
		return false;
	}
	
	// allocate array of class which stores per-module data
	m_uNumberOfModules = Asura_Module_System::GetNumberOfModules();
	m_pxModules = new Tools_EnvironmentalLightTransport_Module[m_uNumberOfModules];

	//Initialise vertices for each module
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		Tools_EnvironmentalLightTransport_Module* pxModule = &m_pxModules[uModule];

		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);

		pxModule->m_uNumberOfVertices = pxMesh->NumberOfPolygons*4;
		//allocate SHs
		pxModule->m_pxLightTransportSHs = new Asura_SphericalHarmonics_Order3[pxModule->m_uNumberOfVertices];
		for(u_int i=0; i<pxModule->m_uNumberOfVertices; i++)
		{
			pxModule->m_pxLightTransportSHs[i].Zero();
		}

		//And interreflected SHs
		#ifdef ASURA_USE_STREAMING
		//The connected zones don't need the interreflected SHs , since they are only
		//having enough calculated to help with doing the lighting for the main zone.
		//RAR 20/04/2006
		if(Tools_Streaming_System::IsSetToMainZone())
		#endif
		{
			pxModule->m_pxInterreflectedSHs = new Asura_SphericalHarmonics_Order3[pxModule->m_uNumberOfVertices];
			for(u_int i=0; i<pxModule->m_uNumberOfVertices; i++)
			{
				pxModule->m_pxInterreflectedSHs[i].Zero();
			}
		}

	}

	return true;
}

bool Tools_EnvironmentalLightTransport_Handler::GetSmoothNormalForVertex( Asura_Vector_3& xNormal, u_int uModuleIndex, u_int uVertexIndex )
{
	xNormal.Zero();

	Asura_Vector_3 xNormalsFound[16];
	int iNumNormalsFound = 0;

	//get a list of polygons using this vertex
	const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetVertexAdjacencies(uModuleIndex,uVertexIndex);

	for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
	{
		int iModuleIndex2 = pxAdj->GetModuleIndex();
		int iPolygonIndex2 = pxAdj->GetPolygonIndex();

		#ifdef ASURA_USE_STREAMING
		Asura_Module_Handler* pxModHandler2 = Asura_Module_System::GetHandler(pxAdj->GetZoneHash());
		#else
		Asura_Module_Handler* pxModHandler2 = &Asura_Module_System::GetCurrentHandler();
		#endif

		Asura_Mesh* pxMesh2 = pxModHandler2->GetMeshForModule(iModuleIndex2);
		Asura_Assert(pxMesh2,"");
		if(!pxMesh2->m_bFaceNormalsValid) pxMesh2->CreateFaceNormals();

		{
			//make sure this normal hasn't already been added
			bool bAlreadyFound = false;
			for(int l=0;l<iNumNormalsFound;l++)
			{
				if(xNormalsFound[l] == pxMesh2->NormalsDataPtr[iPolygonIndex2].FaceNormal)
				{
					bAlreadyFound = true;
					break;
				}

			}
			if(!bAlreadyFound)
			{
				xNormalsFound[iNumNormalsFound++] = pxMesh2->NormalsDataPtr[iPolygonIndex2].FaceNormal;
				xNormal += pxMesh2->NormalsDataPtr[iPolygonIndex2].FaceNormal;

				if(iNumNormalsFound>=16) iNumNormalsFound = 15;
			}
		}

	}
	
	//normalise the vertex normal
	float fLength = xNormal.Magnitude();
	
	if(fLength)
	{
		xNormal /= fLength;
		return true;
	}
	else
	{
		return false;	
	}
}

void Tools_EnvironmentalLightTransport_Handler::GetInterpolatedSH( Asura_SphericalHarmonics_Order3& xReturnSH, const Asura_Vector_3& xPosition, const u_int uPolyIndex, const u_int uModuleIndex, const Asura_Hash_ID uZoneHash )
{
	// KJFL - 2004-06-09 11:59:13 - should really be using barycentric coords of los hit on polygon to lerp between samples
	
	#ifdef ASURA_USE_STREAMING
	Tools_EnvironmentalLightTransport_Handler* pxHandler = Tools_EnvironmentalLightTransport::GetHandler(uZoneHash);
	Asura_Assert(pxHandler,"Failed to find ELT handler");
	#else
	Tools_EnvironmentalLightTransport_Handler* pxHandler = &Tools_EnvironmentalLightTransport::GetCurrentHandler();
	#endif

	
	xReturnSH =  pxHandler->m_pxModules[uModuleIndex].m_pxLightTransportSHs[uPolyIndex*4 + 0];
	xReturnSH += pxHandler->m_pxModules[uModuleIndex].m_pxLightTransportSHs[uPolyIndex*4 + 1];
	xReturnSH += pxHandler->m_pxModules[uModuleIndex].m_pxLightTransportSHs[uPolyIndex*4 + 2];

	xReturnSH.Scale(1.0f/3.0f);
}

bool Tools_EnvironmentalLightTransport_Handler::MakeBlankData( void )
{
	// release any existing data (which may be invalid)
	Reset();

	// allocate space, and make black ELT data
	return InitialiseModuleData();
}

bool Tools_EnvironmentalLightTransport_Handler::DoesEnvironmentMatch( void )
{
	if (!m_bValidData) return false;

	if (!Asura_Module_System::IsInitialised())
	{
		Asura_Assert( false, "Module system not yet initialised!" );
		m_bValidData = false;
		return false;
	}

	if (m_uNumberOfModules==0)
	{
		// no modules may mean no data as yet
	//	Asura_Assert (s_uNumberOfModules!=0, "invalid environment!");
		return false;
	}
	
	// check the number of modules matches
	if (m_uNumberOfModules != static_cast<u_int>(Asura_Module_System::GetNumberOfModules()) )
	{
		m_bValidData = false;
		//Need to set the file as modified so that it can save the 'invalid' flag correctly
		Tools_SaveAsura::SetEnvironmentalLightTransportModified();
		return false;
	}

	// check if the number of vertices for each module matches
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);

		u_int uVertices = pxMesh->NumberOfPolygons*4;
		
		if (m_pxModules[uModule].m_uNumberOfVertices != uVertices)
		{
			m_bValidData = false;
			//Need to set the file as modified so that it can save the 'invalid' flag correctly
			Tools_SaveAsura::SetEnvironmentalLightTransportModified();
			return false;
		}
	}

	return true;
}

void Tools_EnvironmentalLightTransport_Handler::GetDiffuseForVertex( Asura_SphericalHarmonics_Order2_RGB& xDiffuseSH, const u_int uVertex, const u_int uModule )
{
	Asura_SphericalHarmonics_Order3 xR;
	Asura_SphericalHarmonics_Order3 xG;
	Asura_SphericalHarmonics_Order3 xB; 

	Tools_PhotonMapping_System::GetHighDynamicRangeSHsAtVertex( uModule,  uVertex,  xR,  xG,  xB );

	xDiffuseSH.Set( xR, xG, xB );
	xDiffuseSH.ScaleCoefficients( Tools_PhotonMapping_System::GetSHZerothOrderResampleScale(), Tools_PhotonMapping_System::GetSHFirstOrderResampleScale() );
}

// JAT 20/07/07 update the vertex SH from the vertex painting 
void Tools_EnvironmentalLightTransport_Handler::UpdateLightingFromVertexPainting( Asura_SphericalHarmonics_Order2_RGB& xHarmonic, const Asura_Mesh* const pxMesh, const int iPolygonIndex, const int iVertex )
{
	if ( pxMesh == NULL )
	{
		return;
	}
	if ( pxMesh->m_pxPaintedColourDataPtr == NULL )
	{
		return;
	}
	const u_int uPaintedColour = pxMesh->m_pxPaintedColourDataPtr[iPolygonIndex].Colour[iVertex];
	// Ignore default painted colour of 1,1,1
	if ( ( uPaintedColour & 0xFFFFFF ) != 0x808080 )
	{
		// Scale painted colours into the range [0.0, 2.0] so < 128 darkens and > 128 lightens
		const float fPaintedRed = static_cast<float>( ( uPaintedColour >> 16 ) & 0xff ) / 128.0f;
		const float fPaintedGreen = static_cast<float>( ( uPaintedColour >> 8 ) & 0xff ) / 128.0f;
		const float fPaintedBlue = static_cast<float>( ( uPaintedColour >> 0 ) & 0xff ) / 128.0f;


		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//
		// (SB - 29/05/08) For the SS2 branch at least we want a simple multiplicative factor so that the results of vertex
		// painting are predicatble for the artists rather than the ability to add light to fully black areas
		// Additionally the simple version makes colour picking with the eyedropper useful
		//
		// Previous implementation, maintained for reference:
		//
		// Vertex painted colours not obvious how to support this for next and per vertex SH but this is how it has been implemented
		//
		// If the colour component painted value is <= 1 then scale the colour component SH by the component painted value
		// e.g. fPaintedScale ) * colour_SH
		// If the colour component painted value is > 1 then scale hte colour component SH & add the painted colour scale in the direction of the verrex normal
		// 2.0f * ( fPaintedScale - 1.0f ) * xNormal + ( 2.0f - fPaintedScale ) * colour_SH
		//
		// Could think about the adding colour fraction the same as adding a coloured sample which would include adding to the zeroth term as well (with scales)
		// The SH has a tonemap applied to it in the shader so perhaps we should be thinking about how to include that here somehow!
		//
		// Previous per code:
		//
		// const Asura_Vector_3& xNormal = pxMesh->NormalsDataPtr[iPolygonIndex].VertexNormal[iVertex];
		//
		// // Red
		// // <Similarly for other components>
		// float fScaleFraction;
		//
		// fScaleFraction = fPaintedRed;//( fPaintedRed <= 1.0f ) ? fPaintedRed : ( 2.0f - fPaintedRed );
		// for ( int i = 0; i < Asura_SphericalHarmonics_Order2_RGB::NUMBER_OF_TERMS; i++ )
		// {
		//		xHarmonic.m_afRedCoefficients[i] *= fScaleFraction;
		// }
		// if ( fPaintedRed > 1.0f )
		// {
		//		// 2.0f * ( fPaintedRed - 1.0f ) * xNormal + ( 2.0f - fPaintedRed ) * colour_SH
		//		const float fAddFraction = 2.0f * ( fPaintedRed - 1.0f );
		//	 	const Asura_Vector_3 xScaledNormal = xNormal * fAddFraction;
		//		xHarmonic.m_afRedCoefficients[1] += xScaledNormal.x;
		//		xHarmonic.m_afRedCoefficients[2] += xScaledNormal.y;
		//		xHarmonic.m_afRedCoefficients[3] += xScaledNormal.z;
		// }
		//
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		// Red
		for ( int i = 0; i < Asura_SphericalHarmonics_Order2_RGB::NUMBER_OF_TERMS; i++ )
		{
			xHarmonic.m_afRedCoefficients[i] *= fPaintedRed;
		}

		// Green
		for ( int i = 0; i < Asura_SphericalHarmonics_Order2_RGB::NUMBER_OF_TERMS; i++ )
		{
			xHarmonic.m_afGreenCoefficients[i] *= fPaintedGreen;
		}

		// Blue
		for ( int i = 0; i < Asura_SphericalHarmonics_Order2_RGB::NUMBER_OF_TERMS; i++ )
		{
			xHarmonic.m_afBlueCoefficients[i] *= fPaintedBlue;
		}
	}
}


u_int Tools_EnvironmentalLightTransport_Handler::GetDiffuseForVertex( const Asura_Vector_3& xPosition, const Asura_Vector_3& xNormal, const u_int uPoly, const u_int uVertex, const u_int uModule, const u_int uPaintedColour )
{
	#ifdef ASURA_USE_STREAMING
	if(!Tools_Streaming_System::IsSetToMainZone()) return TOOLS_DEFAULT_PRELIGHTINGVALUE;
	#endif

	Asura_RGBA xBaseColour(0,0,0,0);

	//If the environment irradiance is zero then it doesn't matter if the ELT is valid , so we can skip some checks.
	//(Prevents ELT from being required when there are no infinite lights).
	//RAR 22/05/2007
	if(!Tools_PhotonMapping_System::IsEnvironmentIrradianceZero())
	{
		if (m_pxModules == 0) return TOOLS_DEFAULT_PRELIGHTINGVALUE;


		//need to make sure module and vertex indeces are in range , since we may have partially invalid data
		if(uModule >= m_uNumberOfModules)
		{
			return TOOLS_DEFAULT_PRELIGHTINGVALUE;
		}
		
		u_int uVertIndex = uPoly*4 + uVertex;
		if(uVertIndex >= m_pxModules[uModule].m_uNumberOfVertices)
		{
			//vert index out of bounds
			return TOOLS_DEFAULT_PRELIGHTINGVALUE;
		}
				

		Asura_SphericalHarmonics_Order3 xVertexVisibilitySH = m_pxModules[uModule].m_pxLightTransportSHs[ uVertIndex ];
		
		xBaseColour = Asura_RGBA
		(
			xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceRed())/255.0f,
			xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceGreen())/255.0f,
			xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceBlue())/255.0f,
			0
		);
	}

	Asura_RGBA xColour;

	if(!Tools_PhotonMapping_System::GetDirectEstimatePhotonMapSample( uPoly, uVertex, uModule, xNormal, xColour ))
	{
		return TOOLS_DEFAULT_PRELIGHTINGVALUE;
	}
//
////	Asura_Vector_3 xFlux
////	(
////		xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceRed()),
////		xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceGreen()),
////		xVertexVisibilitySH.Integrate(Tools_PhotonMapping_System::GetEnvironmentIrradianceBlue())
////	);
//
//	Asura_Vector_3 xFlux
//	(
//		Tools_PhotonMapping_System::GetEnvironmentIrradianceRed().EvaluateInDirection( xNormal ),
//		Tools_PhotonMapping_System::GetEnvironmentIrradianceGreen().EvaluateInDirection( xNormal ),
//		Tools_PhotonMapping_System::GetEnvironmentIrradianceBlue().EvaluateInDirection( xNormal )
//	);
//
//	if (xFlux.x<0.0f) xFlux.x=0.0f;
//	if (xFlux.y<0.0f) xFlux.y=0.0f;
//	if (xFlux.z<0.0f) xFlux.z=0.0f;
//
//	xFlux *= 10.0f;
//	
//	float fMax = 6.0f;
//
//	if (xFlux.x>fMax) xFlux.x=fMax;
//	if (xFlux.y>fMax) xFlux.y=fMax;
//	if (xFlux.z>fMax) xFlux.z=fMax;
//
//	xFlux.x = sqrtf(1.0f - expf(-xFlux.x));
//	xFlux.y = sqrtf(1.0f - expf(-xFlux.y));
//	xFlux.z = sqrtf(1.0f - expf(-xFlux.z));
//		
//	Asura_RGBA xColour( xFlux.x, xFlux.y, xFlux.z, 0.0f);

	u_int uPolyFlags =0;
	const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
	if(pxMesh)
	{
		uPolyFlags = pxMesh->GetPolygonFlags(uPoly);
	}


	xBaseColour += xColour;

	//Add global ambient ,unless flagged otherwise
	//RAR 21/02/2006
	if(!(uPolyFlags & AsuraPolygonFlag_IgnoreAmbientLight))
	{
		xBaseColour += Tools_PhotonMapping_System::GetGlobalAmbience();
	}

	// JGP - Process painted colour.
	const float fPaintedRed = static_cast<float>( ( uPaintedColour >> 16 ) & 0xff ) / 128.0f;
	const float fPaintedGreen = static_cast<float>( ( uPaintedColour >> 8 ) & 0xff ) / 128.0f;
	const float fPaintedBlue = static_cast<float>( ( uPaintedColour >> 0 ) & 0xff ) / 128.0f;

#define PROCESS_PAINTED( _l_, _p_ ) _l_ = ( _p_ <= 1.0f ) ? _l_ * _p_ : 2.0f * ( _p_ - 1.0f ) + ( 2.0f - _p_ ) * _l_

	PROCESS_PAINTED( xBaseColour.m_fR, fPaintedRed );
	PROCESS_PAINTED( xBaseColour.m_fG, fPaintedGreen );
	PROCESS_PAINTED( xBaseColour.m_fB, fPaintedBlue );

	//Scale down if darken flag is set. RAR 07/03/2006
	if(uPolyFlags & AsuraPolygonFlag_Darken)
	{
		xBaseColour *= ASURA_POLYFLAG_DARKEN_SCALE;
	}

	xBaseColour.Clamp();
	

	return xBaseColour.ToUIntARGB();
	
}


bool Tools_EnvironmentalLightTransport_Handler::GetTransportHarmonic( Asura_SphericalHarmonics_Order3& xSH, const u_int uModule, const u_int uVertex )
{
	xSH.Zero();

	// do we actually have any data? i.e. ELT may not exist
	if (!m_pxModules)
	{
		return false;
	}

	//Check for module and vertex number being in range
	//They may not be if dealing with out of data sample data
	if(uModule >= (u_int)Asura_Module_System::GetNumberOfModules() || uModule >= m_uNumberOfModules) return false;

	const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
	if(uVertex >= (u_int)pxMesh->NumberOfVertices) return false;

	// unfortunately ended up with the data in the wrong format for this query - we're storing by polygon, and this is by spatial vertex
	// use vertex adjacency info to grab which polys are used by this vertex
	const Tools_Polygon_Adjacency* pxVertAdj = Tools_Adjacency::GetVertexAdjacencies(uModule,uVertex);


	// KJFL - 2004-07-07  
	// We'll look for all the poly verts (which may differ by vertex normals) that share this position, and average those.
	// Not sure if we want to do this for all modules or just the candidate module... 
	// We'll use just the candidate module since it really does simplify things, and see what the results look like.
			
	float fNumberFound = 0.0f;
	

	for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
	{
		// only want internal adjacencies
		if(pxAdj->IsBetweenModules()) continue;
		
		const u_int uPolyIndex = pxAdj->GetPolygonIndex();

		// only comparing indices, so no need to check how many verts the poly has
		for (u_int u=0; u<4; u++)
		{
			const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolyIndex].VertexID[u];

			// because we're using the same modules, the vertex indices will match if it's the same position
			// if we were using all modules, we'd have to do a position comparison.
			if (uVertex2 == uVertex)
			{
				// we'll include this one thanks
				u_int uVert = uPolyIndex*4 + u;
				if(uVert >= m_pxModules[uModule].m_uNumberOfVertices)
				{
					//vertex out of range (probably old ELT data)
					return false;
				}

				xSH += m_pxModules[uModule].m_pxLightTransportSHs[uVert];
					
				fNumberFound += 1.0f;
				
				// not expecting the same vert used twice
				break;
			}
		}
	}

	if (fNumberFound > 0.0f)
	{
		xSH.Scale(1.0f / fNumberFound);

	}

	return true;
}


bool Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_FirstPass( const u_int uNumSamples )
{
	u_int uTotalNumberOfVertices = 0;
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
		uTotalNumberOfVertices += pxMesh->NumberOfVertices;
	}
	u_int uVerticesSoFar = 0;

	bool bShouldQuit = false;

	#ifdef ASURA_USE_STREAMING
	char szHeading[100];
	const Asura_Streaming_Zone* pxZone = Asura_Streaming_System::GetCurrentZone();
	Asura_Assert(pxZone,"");

	const char* szFilename = pxZone->GetFilename();
	Asura_PrintfToBfr(szHeading,"Calculating Light Transport (Direct Shadowed) - %s",sizeof(szHeading),true,Tools_FileSystem::StripPath(szFilename));
	#else
	const char* szHeading = "Calculating Light Transport (Direct Shadowed)"; 
	#endif

	// do basic incoming light from 'outside' the environment

	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
		const u_int uNumberOfVertices = pxMesh->NumberOfVertices;

		for (u_int uVertex=0; uVertex<uNumberOfVertices; uVertex++, uVerticesSoFar++)
		{
			
			bShouldQuit = Tools_PhotonMapping_System::UpdateProgress( uVerticesSoFar, uTotalNumberOfVertices, szHeading);
			if (bShouldQuit) break;

			Tools_PhotonMapping_LightSource xLightSource;
			
			Asura_Vector_3 xPos = Asura_Module_System::GetPositionOfModule( uModule ) + pxMesh->VertexDataPtr[uVertex].Position;
			Asura_Vector_3 xNormal;

			if (!GetSmoothNormalForVertex( xNormal, uModule, uVertex ))
			{
				//xNormal = pxMesh->NormalsDataPtr[uPoly].VertexNormal[uVertex];
				xNormal.Zero();
			}

			// raise the position up my a mm in the direction of the normal.
			xPos += xNormal * s_fSamplingHeight;//0.001f;//
	
			xLightSource.SetPosition( xPos );

			xLightSource.SetStratificationLevels( uNumSamples );

			// now use vertex normal instead!

			//xNormal = pxMesh->NormalsDataPtr[uPoly].VertexNormal[uVertex];
					//get a list of polygons using this vertex
			const Tools_Polygon_Adjacency* pxVertAdj = Tools_Adjacency::GetVertexAdjacencies(uModule,uVertex);

			//Get combined polygon flags from adjacent polygons. RAR 17/02/2006
			u_int uPolyFlags = 0;
			for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
			{
				if(pxAdj->IsBetweenModules()) continue;
				const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

				uPolyFlags |= pxMesh->GetPolygonFlags(uPolygonIndex);

			}


			//Initialise settings for all the LOS tests
			Tools_Environment_LOS_Data xLOSData;
			xLOSData.SetReturnOnFirstHit(true);
			xLOSData.SetPolyFlagsToIgnore(TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE|AsuraPolygonFlag_DontBlockLight);
			xLOSData.SetShouldTestModuleCallback(ShouldTestModuleForELTLOS, &uModule);

			for (u_int u=0; u<uNumSamples; u++)
			{
				for (u_int v=0; v<uNumSamples; v++)
				{
					Asura_Vector_3 xDirection;
					Asura_Vector_3 xPosition;

					xLightSource.GetStratifiedPhotonPositionAndDirection( xPosition, xDirection, u, v ); 

// optimisation test
					if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
					{
						bool bSkipSample = true;
				
						for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
						{
							if(pxAdj->IsBetweenModules()) continue;
							const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

							u_int uVertexIndex = 4;
							
							for (u_int l=0; l<4; l++)
							{
								const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

								if (uVertex2 == uVertex)
								{
									uVertexIndex = l;
									break;
								}
							}

							if (uVertexIndex >= 4)
							{
								Asura_Assert( uVertexIndex<4, "Invalid Index" );
								continue;
							}
				
							const float fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];

							if (fDotProduct > 0.0f)
							{
								bSkipSample = false;
								break;
							}
							
						}
						if (bSkipSample) continue;
					}
// optimisation test

					bool bAddSample = false;
					{ 
					
						xLOSData.SetRayOrigin(xPosition);
						xLOSData.SetRayDirection(xDirection);

						if (!Tools_Environment_LOS::TestWithRayUsingPreviousResults(xLOSData))// || fDistance>5.0f)
						{
							// didn't hit anything - ray went out to the skybox
							
							bAddSample = true;
						}
					}

					if (bAddSample)
					{
						for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
						{
							if(pxAdj->IsBetweenModules()) continue;
							const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

							u_int uVertexIndex = 4;
							
							for (u_int l=0; l<4; l++)
							{
								const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

								if (uVertex2 == uVertex)
								{
									uVertexIndex = l;
									break;
								}
							}

							if (uVertexIndex >= 4)
							{
								Asura_Assert( uVertexIndex<4, "Invalid Index" );
								continue;
							}

							float fDotProduct;
							if(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting)
							{
								fDotProduct = g_fLightScaleForIgnoreNormal;
							}
							else
							{
								fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];
							}

							if (fDotProduct > 0.0f)
							{
//CSB 12/05/2006	vvvvv
								if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
								{
									for(u_int uM = 0; uM < s_uDirectLightingExtraSharpenFactor; ++uM)
									{
										fDotProduct *= fDotProduct;
									}
								}
								fDotProduct *= s_fDirectLightingExtraIntensityFactor;
//CSB 12/05/2006	^^^^^
								Asura_SphericalHarmonics_Order3& xSH = m_pxModules[uModule].m_pxLightTransportSHs[ uPolygonIndex*4 + uVertexIndex];
								xSH.AddSampleForDirection( fDotProduct, xDirection );
							}
							
						}
					}
				}
			}
		}

		if (bShouldQuit) break;
	}

	return !bShouldQuit;
}

#ifdef ASURA_USE_TASK_SYSTEM
// TODO - move these to top when finished 
#include "Core/Threads/Asura_Task_Array.h"
#include "Core/Asura_Atomic.h"
//#include "Core/Asura_Multithread.h"

// These variables are constant over the multithreaded per-module work, and so 
// can be global.
//static u_int g_uModuleIndex_MT;
//static const Asura_Mesh* g_pxModuleMesh_MT;
static u_int g_uNumSamples_MT;
static Tools_EnvironmentalLightTransport_Handler* g_pxHandler_MT;
static Asura_Mutex g_xDataAccess_MT;

static bool g_bShouldQuit_MT;

static u_int g_uNumberOfVerticesProcessed_MT;

bool Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_FirstPass_MT( const u_int uNumSamples )
{
	// KJFL - 6 February 2008 
	// We need to make sure certain things are up-to-date before trying to use them multi-threaded.
	// This will include any per-mesh data like normals, which we'll do in the loop below
	Tools_Environment_LOS::EnsureDataIsValid();
	if ( !Tools_Adjacency::IsInitialised() )
	{
		Tools_Adjacency::Initialise();
	}

	u_int uTotalNumberOfVertices = 0;
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);

		// Make sure normals are valid now, since it's not a threadsafe
		// operation.
		if( !pxMesh->m_bFaceNormalsValid ) pxMesh->CreateFaceNormals();

		// make sure vertex adjacencies are calc'ed
		Tools_Adjacency::GetVertexAdjacencies( uModule, 0 );

		uTotalNumberOfVertices += pxMesh->NumberOfVertices;
	}

#ifdef ASURA_USE_STREAMING
	char szHeading[100];
	const Asura_Streaming_Zone* pxZone = Asura_Streaming_System::GetCurrentZone();
	Asura_Assert(pxZone,"");

	const char* szFilename = pxZone->GetFilename();
	Asura_PrintfToBfr(szHeading,"Calculating Light Transport (Direct Shadowed) - %s",sizeof(szHeading),true,Tools_FileSystem::StripPath(szFilename));
#else
	const char* szHeading = "Calculating Light Transport (Direct Shadowed)"; 
#endif


	// do basic incoming light from 'outside' the environment
	g_uNumSamples_MT = uNumSamples;
	g_pxHandler_MT = this;
	g_uNumberOfVerticesProcessed_MT = 0;

	g_bShouldQuit_MT = false;

	Tools_PhotonMapping_System::UpdateProgress( 0, uTotalNumberOfVertices, szHeading );
	
	{
		Asura_Task_Array xTaskArray( m_uNumberOfModules, 1, ProcessEnvironment_FirstPass_MT_Callback );

		while ( !xTaskArray.HasFinished() )
		{
			// yield for a bit to let the worker threads do their thing
			Asura_Thread_System::DelayCurrentThread( 0.1f, false );

			g_bShouldQuit_MT = Tools_PhotonMapping_System::UpdateProgress( g_uNumberOfVerticesProcessed_MT, uTotalNumberOfVertices, szHeading );
			if (g_bShouldQuit_MT) break;
		}
	}

	return !g_bShouldQuit_MT;
}

void Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_FirstPass_MT_Callback( const void* pxTaskDataPacket )
{
	const Asura_Task_Array::TaskDataPacket* pxData = static_cast<const Asura_Task_Array::TaskDataPacket*>(pxTaskDataPacket);

	const u_int uModule = pxData->GetArrayStartIndex();
	Asura_Assert( pxData->GetNumberToProcess() == 1, "Expecting to process one vertex at a time" );

	// Tell the Asura_Task_Array we're done with this data packet. We won't be able to read from it after this.
	pxData->SignalFinishedWithData();

	const u_int uNumSamples = g_uNumSamples_MT;

	const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
	const u_int uNumberOfVertices = pxMesh->NumberOfVertices;

	for (u_int uVertex=0; uVertex<uNumberOfVertices; uVertex++ )
	{
		Tools_PhotonMapping_LightSource xLightSource;

		Asura_Vector_3 xPos = Asura_Module_System::GetPositionOfModule( uModule ) + pxMesh->VertexDataPtr[uVertex].Position;
		Asura_Vector_3 xNormal;

		if (!g_pxHandler_MT->GetSmoothNormalForVertex( xNormal, uModule, uVertex ))
		{
			xNormal.Zero();
		}

		// raise the position up my a mm in the direction of the normal.
		xPos += xNormal * s_fSamplingHeight;//0.001f;//

		xLightSource.SetPosition( xPos );

		xLightSource.SetStratificationLevels( uNumSamples );

		// now use vertex normal instead!

		//xNormal = pxMesh->NormalsDataPtr[uPoly].VertexNormal[uVertex];
		//get a list of polygons using this vertex
		const Tools_Polygon_Adjacency* pxVertAdj = Tools_Adjacency::GetVertexAdjacencies(uModule,uVertex);

		//Get combined polygon flags from adjacent polygons. RAR 17/02/2006
		u_int uPolyFlags = 0;
		for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			if(pxAdj->IsBetweenModules()) continue;
			const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

			uPolyFlags |= pxMesh->GetPolygonFlags(uPolygonIndex);

		}

		//Initialise settings for all the LOS tests
		Tools_Environment_LOS_Data xLOSData;
		xLOSData.SetReturnOnFirstHit(true);
		xLOSData.SetPolyFlagsToIgnore(TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE|AsuraPolygonFlag_DontBlockLight);
		xLOSData.SetShouldTestModuleCallback(ShouldTestModuleForELTLOS, &uModule);


		for (u_int u=0; u<uNumSamples; u++)
		{
			for (u_int v=0; v<uNumSamples; v++)
			{
				if (g_bShouldQuit_MT) return;


				Asura_Vector_3 xDirection;
				Asura_Vector_3 xPosition;

				xLightSource.GetStratifiedPhotonPositionAndDirection( xPosition, xDirection, u, v ); 

				// optimisation test
				if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
				{
					bool bSkipSample = true;

					for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
					{
						if(pxAdj->IsBetweenModules()) continue;
						const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

						u_int uVertexIndex = 4;

						for (u_int l=0; l<4; l++)
						{
							const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

							if (uVertex2 == uVertex)
							{
								uVertexIndex = l;
								break;
							}
						}

						if (uVertexIndex >= 4)
						{
							Asura_Assert( uVertexIndex<4, "Invalid Index" );
							continue;
						}

						const float fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];

						if (fDotProduct > 0.0f)
						{
							bSkipSample = false;
							break;
						}

					}
					if (bSkipSample) continue;
				}
				// optimisation test

				bool bAddSample = false;
				{ 
					xLOSData.SetRayOrigin(xPosition);
					xLOSData.SetRayDirection(xDirection);
					if (!Tools_Environment_LOS::TestWithRayUsingPreviousResults(xLOSData))// || fDistance>5.0f)
					{
						// didn't hit anything - ray went out to the skybox

						bAddSample = true;
					}
				}

				if (bAddSample)
				{
					for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
					{
						if(pxAdj->IsBetweenModules()) continue;
						const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

						u_int uVertexIndex = 4;

						for (u_int l=0; l<4; l++)
						{
							const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

							if (uVertex2 == uVertex)
							{
								uVertexIndex = l;
								break;
							}
						}

						if (uVertexIndex >= 4)
						{
							Asura_Assert( uVertexIndex<4, "Invalid Index" );
							continue;
						}

						float fDotProduct;
						if(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting)
						{
							fDotProduct = g_fLightScaleForIgnoreNormal;
						}
						else
						{
							fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];
						}

						if (fDotProduct > 0.0f)
						{
							//CSB 12/05/2006	vvvvv
							if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
							{
								for(u_int uM = 0; uM < s_uDirectLightingExtraSharpenFactor; ++uM)
								{
									fDotProduct *= fDotProduct;
								}
							}
							fDotProduct *= s_fDirectLightingExtraIntensityFactor;
							//CSB 12/05/2006	^^^^^

							u_int uTransportIndex = uPolygonIndex*4 + uVertexIndex;
							
			
							Asura_SphericalHarmonics_Order3& xSH = g_pxHandler_MT->m_pxModules[uModule].m_pxLightTransportSHs[uTransportIndex];
							xSH.AddSampleForDirection( fDotProduct, xDirection );
								
						}

					}
				}
			}
		}

		// KJFL - 8 February 2008 - update the vertices processed count
		// so our progress bar can go up smoothly. Using simple atomic 
		// operations like this won't cause nasty locks like a mutex would.
		Asura_Atomic::Increment(g_uNumberOfVerticesProcessed_MT);
	}
}

bool Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_SecondPass_MT( const u_int uNumSamples )
{
	u_int uTotalNumberOfVertices = 0;
for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
		uTotalNumberOfVertices += pxMesh->NumberOfVertices;
	}

	
	// do basic incoming light from 'outside' the environment
	g_uNumSamples_MT = uNumSamples;
	g_pxHandler_MT = this;
	g_uNumberOfVerticesProcessed_MT = 0;

	g_bShouldQuit_MT = false;

	const char* szHeading = "Calculating Light Transport (Inter-reflections)"; 

	Tools_PhotonMapping_System::UpdateProgress( 0, uTotalNumberOfVertices, szHeading );

	{
		Asura_Task_Array xTaskArray( m_uNumberOfModules, 1, ProcessEnvironment_SecondPass_MT_Callback );

		while ( !xTaskArray.HasFinished() )
		{
			// yield for a bit to let the worker threads do their thing
			Asura_Thread_System::DelayCurrentThread( 0.1f, false );
			
			g_bShouldQuit_MT = Tools_PhotonMapping_System::UpdateProgress( g_uNumberOfVerticesProcessed_MT, uTotalNumberOfVertices, szHeading );
			if (g_bShouldQuit_MT) break;
		}
	}

	return !g_bShouldQuit_MT;
}

void Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_SecondPass_MT_Callback( const void* pxTaskDataPacket )
{
	const Asura_Task_Array::TaskDataPacket* pxData = static_cast<const Asura_Task_Array::TaskDataPacket*>(pxTaskDataPacket);

	const u_int uModule = pxData->GetArrayStartIndex();
	Asura_Assert( pxData->GetNumberToProcess() == 1, "Expecting to process one vertex at a time" );

	// Tell the Asura_Task_Array we're done with this data packet. We won't be able to read from it after this.
	pxData->SignalFinishedWithData();

	const u_int uNumSamples = g_uNumSamples_MT;

	const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
	const u_int uNumberOfVertices = pxMesh->NumberOfVertices;



	for (u_int uVertex=0; uVertex<uNumberOfVertices; uVertex++ )
	{
		Tools_PhotonMapping_LightSource xLightSource;
		
		Asura_Vector_3 xPos = Asura_Module_System::GetPositionOfModule( uModule ) + pxMesh->VertexDataPtr[uVertex].Position;
		Asura_Vector_3 xNormal;

		if (!g_pxHandler_MT->GetSmoothNormalForVertex( xNormal, uModule, uVertex ))
		{
			xNormal.Zero();
		}

		// raise the position up my a mm in the direction of the normal.
		xPos += xNormal * s_fSamplingHeight;//0.001f;//

		xLightSource.SetPosition( xPos );

		xLightSource.SetStratificationLevels( uNumSamples );

		// now use vertex normal instead!
		const Tools_Polygon_Adjacency* pxVertAdj = Tools_Adjacency::GetVertexAdjacencies(uModule,uVertex);

		//Get combined polygon flags from adjacent polygons. RAR 17/02/2006
		u_int uPolyFlags = 0;
		for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			if(pxAdj->IsBetweenModules()) continue;
			const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

			uPolyFlags |= pxMesh->GetPolygonFlags(uPolygonIndex);

		}


	
		//Initialise settings for all the LOS tests
		Tools_Environment_LOS_Data xLOSData;
		xLOSData.SetReturnOnFirstHit(false);
		xLOSData.SetPolyFlagsToIgnore(TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE|AsuraPolygonFlag_DontBlockLight);
		xLOSData.SetShouldTestModuleCallback(ShouldTestModuleForELTLOS, &uModule);
		
		for (u_int u=0; u<uNumSamples; u++)
		{
			for (u_int v=0; v<uNumSamples; v++)
			{
				if (g_bShouldQuit_MT) return;

				Asura_Vector_3 xDirection;
				Asura_Vector_3 xPosition;
				Asura_SphericalHarmonics_Order3 xReflection;
				
				xLightSource.GetStratifiedPhotonPositionAndDirection( xPosition, xDirection, u, v ); 
// optimisation test
				if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
				{
					bool bSkipSample = true;
			
					for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
					{
						if(pxAdj->IsBetweenModules()) continue;
						
						const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

						u_int uVertexIndex = 4;
						
						for (u_int l=0; l<4; l++)
						{
							const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

							if (uVertex2 == uVertex)
							{
								uVertexIndex = l;
								break;
							}
						}

						if (uVertexIndex >= 4)
						{
							Asura_Assert( uVertexIndex<4, "Invalid Index" );
							continue;
						}
			
						const float fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];

						if (fDotProduct > 0.0f)
						{
							bSkipSample = false;
							break;
						}
						
					}
					if (bSkipSample) continue;
				}
// optimisation test

				bool bAddSample = false;

				{ 
					xLOSData.SetRayOrigin(xPosition);
					xLOSData.SetRayDirection(xDirection);
					
					if (Tools_Environment_LOS::TestWithRayUsingPreviousResults(xLOSData))// || fDistance>5.0f)
					{
						// DID hit something
						bAddSample = true;
						GetInterpolatedSH( xReflection, xPosition, xLOSData.GetPolyIndex(), xLOSData.GetModuleIndex(), xLOSData.GetZoneHash());

					}
				}

				if (bAddSample)
				{
					for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
					{
						if(pxAdj->IsBetweenModules()) continue;

						const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

						u_int uVertexIndex = 4;
						
						for (u_int l=0; l<4; l++)
						{
							const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

							if (uVertex2 == uVertex)
							{
								uVertexIndex = l;
								break;
							}
						}

						if (uVertexIndex >= 4)
						{
							Asura_Assert( uVertexIndex<4, "Invalid Index" );
							continue;
						}
			
						float fDotProduct;

						if(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting)
						{
							fDotProduct = g_fLightScaleForIgnoreNormal;
						}
						else
						{
							fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];
						}

						if (fDotProduct > 0.0f)
						{
							Asura_SphericalHarmonics_Order3& xSH = g_pxHandler_MT->m_pxModules[uModule].m_pxInterreflectedSHs[ uPolygonIndex*4 + uVertexIndex];
							Asura_SphericalHarmonics_Order3 xScaledReflection = xReflection;
							
							xScaledReflection.Scale(fDotProduct);
								
							xSH += xScaledReflection;
						}
						
					}
				}
			}
		}
		
		// KJFL - 8 February 2008 - update the vertices processed count
		// so our progress bar can go up smoothly. Using simple atomic 
		// operations like this won't cause nasty locks like a mutex would.
		Asura_Atomic::Increment(g_uNumberOfVerticesProcessed_MT);
	}
}

#endif// ASURA_USE_TASK_SYSTEM



bool Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_SecondPass( const u_int uNumSamples )
{
	u_int uTotalNumberOfVertices = 0;
	u_int uVerticesSoFar = 0;
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
		uTotalNumberOfVertices += pxMesh->NumberOfVertices;
	}

	bool bShouldQuit = false;

	// Add in the first bounce of inter-reflected light from surface to surface.
	for (u_int uModule=0; uModule<m_uNumberOfModules; uModule++)
	{
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(uModule);
		const u_int uNumberOfVertices = pxMesh->NumberOfVertices;
		
		for (u_int uVertex=0; uVertex<uNumberOfVertices; uVertex++, uVerticesSoFar++)
		{
			
			bShouldQuit = Tools_PhotonMapping_System::UpdateProgress( uVerticesSoFar, uTotalNumberOfVertices, "Calculating Light Transport (Inter-reflections)" );
			if (bShouldQuit) break;

			Tools_PhotonMapping_LightSource xLightSource;
			
			Asura_Vector_3 xPos = Asura_Module_System::GetPositionOfModule( uModule ) + pxMesh->VertexDataPtr[uVertex].Position;
			Asura_Vector_3 xNormal;

			if (!GetSmoothNormalForVertex( xNormal, uModule, uVertex ))
			{
				xNormal.Zero();
			}

			// raise the position up my a mm in the direction of the normal.
			xPos += xNormal * s_fSamplingHeight;//0.001f;//
	
			xLightSource.SetPosition( xPos );

			xLightSource.SetStratificationLevels( uNumSamples );

			// now use vertex normal instead!
			const Tools_Polygon_Adjacency* pxVertAdj = Tools_Adjacency::GetVertexAdjacencies(uModule,uVertex);

			//Get combined polygon flags from adjacent polygons. RAR 17/02/2006
			u_int uPolyFlags = 0;
			for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
			{
				if(pxAdj->IsBetweenModules()) continue;
				const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

				uPolyFlags |= pxMesh->GetPolygonFlags(uPolygonIndex);

			}


		
			//Initialise settings for all the LOS tests
			Tools_Environment_LOS_Data xLOSData;
			xLOSData.SetReturnOnFirstHit(false);
			xLOSData.SetPolyFlagsToIgnore(TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE|AsuraPolygonFlag_DontBlockLight);
			xLOSData.SetShouldTestModuleCallback(ShouldTestModuleForELTLOS, &uModule);
			
			for (u_int u=0; u<uNumSamples; u++)
			{
				for (u_int v=0; v<uNumSamples; v++)
				{
					Asura_Vector_3 xDirection;
					Asura_Vector_3 xPosition;
					Asura_SphericalHarmonics_Order3 xReflection;
					
					xLightSource.GetStratifiedPhotonPositionAndDirection( xPosition, xDirection, u, v ); 
// optimisation test
					if(!(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting))
					{
						bool bSkipSample = true;
				
						for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
						{
							if(pxAdj->IsBetweenModules()) continue;
							
							const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

							u_int uVertexIndex = 4;
							
							for (u_int l=0; l<4; l++)
							{
								const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

								if (uVertex2 == uVertex)
								{
									uVertexIndex = l;
									break;
								}
							}

							if (uVertexIndex >= 4)
							{
								Asura_Assert( uVertexIndex<4, "Invalid Index" );
								continue;
							}
				
							const float fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];

							if (fDotProduct > 0.0f)
							{
								bSkipSample = false;
								break;
							}
							
						}
						if (bSkipSample) continue;
					}
// optimisation test

					bool bAddSample = false;

					{ 
						xLOSData.SetRayOrigin(xPosition);
						xLOSData.SetRayDirection(xDirection);

						if (Tools_Environment_LOS::TestWithRayUsingPreviousResults(xLOSData))// || fDistance>5.0f)
						{
							// DID hit something
							bAddSample = true;
							GetInterpolatedSH( xReflection, xPosition, xLOSData.GetPolyIndex(), xLOSData.GetModuleIndex(), xLOSData.GetZoneHash());

						}
					}

					if (bAddSample)
					{
						for(const Tools_Adjacent_Polygon* pxAdj = pxVertAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
						{
							if(pxAdj->IsBetweenModules()) continue;

							const u_int uPolygonIndex = pxAdj->GetPolygonIndex();

							u_int uVertexIndex = 4;
							
							for (u_int l=0; l<4; l++)
							{
								const u_int uVertex2 = pxMesh->PolygonDataPtr[uPolygonIndex].VertexID[l];

								if (uVertex2 == uVertex)
								{
									uVertexIndex = l;
									break;
								}
							}

							if (uVertexIndex >= 4)
							{
								Asura_Assert( uVertexIndex<4, "Invalid Index" );
								continue;
							}
				
							float fDotProduct;

							if(uPolyFlags & AsuraPolygonFlag_IgnoreNormalForLighting)
							{
								fDotProduct = g_fLightScaleForIgnoreNormal;
							}
							else
							{
								fDotProduct = xDirection* pxMesh->NormalsDataPtr[uPolygonIndex].VertexNormal[uVertexIndex];
							}

							if (fDotProduct > 0.0f)
							{
								Asura_SphericalHarmonics_Order3& xSH = m_pxModules[uModule].m_pxInterreflectedSHs[ uPolygonIndex*4 + uVertexIndex];
								Asura_SphericalHarmonics_Order3 xScaledReflection = xReflection;
								
								xScaledReflection.Scale(fDotProduct);
									
								xSH += xScaledReflection;
							}
							
						}
					}
				}
			}
		}

		if (bShouldQuit) break;
	}

	return !bShouldQuit;
}


void Tools_EnvironmentalLightTransport_Handler::ProcessEnvironment_Finish( const u_int uNumSamples, bool bQuickApproximation )
{

	for( u_int uModule = 0; uModule<m_uNumberOfModules; uModule++)
	{
		Tools_EnvironmentalLightTransport_Module* pxModule = &m_pxModules[uModule];

		for (u_int u=0; u<pxModule->m_uNumberOfVertices; u++)
		{
			pxModule->m_pxLightTransportSHs[u].ScaleCoefficientsForNSamples( static_cast<float>(uNumSamples * uNumSamples) );
			pxModule->m_pxInterreflectedSHs[u].ScaleCoefficientsForNSamples( static_cast<float>(uNumSamples * uNumSamples) );
//DeadCode CSB 22/05/2006 		m_pxInterreflectedSHs[u].Scale(0.003f / 8.0f);
			pxModule->m_pxInterreflectedSHs[u].Scale(s_fInterreflectedScaleFactor*0.003f / 8.0f);
			pxModule->m_pxLightTransportSHs[u] += pxModule->m_pxInterreflectedSHs[u];

		}
		delete [] pxModule->m_pxInterreflectedSHs;
		pxModule->m_pxInterreflectedSHs = 0;

	}

	Tools_SaveAsura::SetEnvironmentalLightTransportModified();
	m_bValidData = true;
	m_bQuickData = bQuickApproximation;
}

bool Tools_EnvironmentalLightTransport_Handler::ShouldTestModuleForELTLOS( Asura_Hash_ID uZone, u_int uModuleIndex, const void* pCurrentModuleIndex, const Tools_Environment_LOS_Data& xLOSData )
{
	//If using light sets,make sure both modules are flagged for the same sets
	if(!Tools_LightSets::IsUsingLightSets()) return true;

	//get the original module's light sets
	Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
	u_int uCurrentModuleIndex = *(const u_int*) pCurrentModuleIndex;

	u_int uCurrentModuleLightSets = pxEnv->GetLightSets(uCurrentModuleIndex);

	//and those for the module being tested
	#ifdef ASURA_USE_STREAMING
	Asura_Environment* pxEnv2 = Asura_Environment_System::GetEnvironment(uZone);
	u_int uTestModuleLightSets = pxEnv2->GetLightSets(uModuleIndex);
	#else
	u_int uTestModuleLightSets = pxEnv->GetLightSets(uModuleIndex);
	#endif

	return (uCurrentModuleLightSets & Tools_LightSets::GetUsedLightSets()) == (uTestModuleLightSets & Tools_LightSets::GetUsedLightSets());
}


void Tools_EnvironmentalLightTransport_Handler::DeleteModule(u_int uModuleIndex)
{

	if(!m_uNumberOfModules) return;
	if(uModuleIndex >= m_uNumberOfModules)
	{
		Asura_Assert(uModuleIndex < m_uNumberOfModules,"Invalid module index in Tools_EnvironmentLightTransport_Handler::DeleteModule()");
		return;
	}
	
	//deallocate the lighting for the deleted module
	m_pxModules[uModuleIndex].Shutdown();

	//Replace the deleted module , to mimic the behaviour done elsewhere with module deletion
	m_uNumberOfModules--;
	if(uModuleIndex != m_uNumberOfModules)
	{
		Asura_Mem::Copy(&m_pxModules[uModuleIndex], &m_pxModules[m_uNumberOfModules], sizeof(Tools_EnvironmentalLightTransport_Module));
		Asura_Mem::Set(&m_pxModules[m_uNumberOfModules], 0, sizeof(Tools_EnvironmentalLightTransport_Module));
	}

}

////////////////////////////////////////////////////////////////////////////////////////////////////
/*---------------------------------**
** Tools_EnvironmentLightTransport **
**---------------------------------*/

#if defined(ASURA_USE_STREAMING)
Asura_TemplateHashTree<Tools_EnvironmentalLightTransport_Handler>	Tools_EnvironmentalLightTransport::s_xHandlerTree;
Tools_EnvironmentalLightTransport_Handler*						Tools_EnvironmentalLightTransport::s_pxCurrentHandler	= NULL;
#else
Tools_EnvironmentalLightTransport_Handler	Tools_EnvironmentalLightTransport::s_xHandler;
#endif	// defined(ASURA_USE_STREAMING)


void Tools_EnvironmentalLightTransport::Initialise( void )
{
	#ifndef ASURA_USE_STREAMING
	s_xHandler.Initialise();
	#endif
}


void Tools_EnvironmentalLightTransport::Reset( void )
{
	Shutdown();
}

void Tools_EnvironmentalLightTransport::Shutdown( void )
{
	#if defined(ASURA_USE_STREAMING)
	// Reset the entire handler tree...
	s_xHandlerTree.Reset();
	s_pxCurrentHandler	= NULL;
	#else
	// Reset the current instance...
	s_xHandler.Shutdown();
	#endif	// defined(ASURA_USE_STREAMING)
}

#if defined(ASURA_USE_STREAMING)
Tools_EnvironmentalLightTransport_Handler* Tools_EnvironmentalLightTransport::CreateHandler (const Asura_Hash_ID uHash)
{
	Tools_EnvironmentalLightTransport_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= s_xHandlerTree.AddNode(uHash);
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have an ELT handler with this hash-ID.");
	}

	// Return the handler...
	return(pxHandler);
}

bool Tools_EnvironmentalLightTransport::DestroyHandler (const Asura_Hash_ID uHash)
{
	// Is this the current handler?
	if(s_pxCurrentHandler == s_xHandlerTree.GetDataByHash(uHash))
	{
		s_pxCurrentHandler	= NULL;
	}

	// Call the delete function...
	return(s_xHandlerTree.DeleteNodeByHash(uHash));
}

void Tools_EnvironmentalLightTransport::SetCurrentHandler(Tools_EnvironmentalLightTransport_Handler* pxHandler)
{
	if(pxHandler != s_pxCurrentHandler)
	{
		s_pxCurrentHandler = pxHandler;
	}
}
#endif	// defined(ASURA_USE_STREAMING)


void Tools_EnvironmentalLightTransport::ProcessEnvironment(bool bQuickApproximation /*= false*/)
{
	//Initialise SHs for all zones
	for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next())
	{
		if(!MakeBlankData()) return;
	}

	//If the ELT is being recalculated then the GI samples will no longer be up to date
	//RAR 17/08/2005
	Asura_GI_Solution::InvalidateSamples();

	// comes out to 32*32 = 1024 samples for full accuracy, 8*8 = 64 samples for quick approx
	const u_int uNumSamples = bQuickApproximation ? 8 : 32;

	bool bShouldQuit = false;

	// first do basic incoming light from 'outside' the environment
	for(Tools_Streaming_Iterator xIt;!xIt.Done() && !bShouldQuit;xIt.Next())
	{
#ifdef ASURA_USE_TASK_SYSTEM
		if ( Tools_Mode_PhotonMapping::s_bUseMultithreading )
		{
			if(!GetCurrentHandler().ProcessEnvironment_FirstPass_MT(uNumSamples))
			{
				bShouldQuit = true;
			}
			// And then add in the first bounce of inter-reflected light from surface to surface.
			// Unfortunately we'll end up doing the same ray tests again - there's just too many of them to cache them.
			
			// Only care about this for the main environment/zone RAR 20/04/2006
			if(!bShouldQuit)
			{
				if(!GetCurrentHandler().ProcessEnvironment_SecondPass_MT(uNumSamples))
				{
					bShouldQuit = true;
				}
			}
		}
		else
#endif
		{
			if(!GetCurrentHandler().ProcessEnvironment_FirstPass(uNumSamples))
			{
				bShouldQuit = true;
			}
			// And then add in the first bounce of inter-reflected light from surface to surface.
			// Unfortunately we'll end up doing the same ray tests again - there's just too many of them to cache them.
			
			// Only care about this for the main environment/zone RAR 20/04/2006
			if(!bShouldQuit)
			{
				if(!GetCurrentHandler().ProcessEnvironment_SecondPass(uNumSamples))
				{
					bShouldQuit = true;
				}
			}
		}
	}

	

	if(!bShouldQuit)
	{
		//Finish up , and mark the data as valid
		GetCurrentHandler().ProcessEnvironment_Finish(uNumSamples,bQuickApproximation);
	}
	else
	{
		ProcessEnvironmentCancelled();
	}
}

void Tools_EnvironmentalLightTransport::ProcessEnvironmentCancelled()
{
	GetCurrentHandler().m_bValidData = false;
	if (Tools_BatchProcessing::IsGlobalIllumination())
	{
		Tools_BatchProcessing::Abort();
	}
}





