//
// Tools_PhotonMapping_System_Internal.h
//
// Created by KJL - 03/12/2003 16:24:34
//
// Copyright (C) 2003 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif

#ifndef __TOOLS_PHOTONMAPPING_SYSTEM_INTERNAL_H__
#define __TOOLS_PHOTONMAPPING_SYSTEM_INTERNAL_H__

#include "Core/Asura_Mempool.h"

#include "Lighting/Tools_PhotonMapping_LightSource.h"
#include "Lighting/Tools_PhotonMapping_PolygonLightSource.h"
#include "Lighting/Tools_PhotonMapping_DirectEstimation.h"


/// KJL - 15/12/2003 14:06:32 - data generated when a photon hits a surface. A simple array of this structure
/// is used when casting out rays and constructing the photon map, and then the array is rearranged into a kd-tree.
	
class Tools_PhotonMapping_PhotonHit
{
	public:
#if 01
		/// record photon hit's data
		inline void Set(const Asura_Vector_3& xPosition, const Asura_Vector_3& xDirection, const Asura_Vector_3& xEnergy)
		{
			m_xPosition = xPosition;
			m_xDirection = xDirection;
			m_xEnergy = xEnergy;

			// set to a default value for neatness
			m_uSplittingPlane = 0;
		}
#else
		inline void Set(const Asura_Vector_3& xPosition, const Asura_Vector_3& xDirection, const Asura_Vector_3& xEnergy)
		{
			m_xPosition = xPosition;
			//m_xDirection = xDirection;
			
			m_sDirectionX = (short)Asura_Maths::FloatToInt(xDirection.x * 32767.0f);
			m_sDirectionY = (short)Asura_Maths::FloatToInt(xDirection.y * 32767.0f);
			m_sDirectionZ = (short)Asura_Maths::FloatToInt(xDirection.z * 32767.0f);


			m_xEnergy = xEnergy;

			// set to a default value for neatness
			m_uSplittingPlane = 0;
		}
#endif
		inline const Asura_Vector_3& GetPosition(void) const
		{
			return m_xPosition;
		}
#if 01
		inline const Asura_Vector_3& GetDirection(void) const
		{
			return m_xDirection;
		}
#else
		inline const Asura_Vector_3 GetDirection(void) const
		{
			Asura_Vector_3 m_xDirection;

			m_xDirection.x = ((float)m_sDirectionX)/32768.0f;
			m_xDirection.y = ((float)m_sDirectionY)/32768.0f;
			m_xDirection.z = ((float)m_sDirectionZ)/32768.0f;

			return m_xDirection;
		}
#endif
		inline const Asura_Vector_3& GetEnergy(void) const
		{
			return m_xEnergy;
		}

		inline const u_int GetPlaneAxis(void) const
		{
			return m_uSplittingPlane;
		}

		inline void SetSplittingPlane(u_int uSplittingPlane)
		{
			m_uSplittingPlane = (u_short)uSplittingPlane;
		}

	private:

		/// Used in kd-tree, to determine splitting plane. Values of 0,1,2 mean x,y,z axis respectively. 
		/// So, only two bits are needed, and the rest of the space could be reused if compression required.

#if 0
		u_short			m_uSplittingPlane;
		short			m_sDirectionX;
		short			m_sDirectionY;
		short			m_sDirectionZ;
#else
		u_int			m_uSplittingPlane;
		/// Direction photon was travelling in when it hit the surface (*not* the normal of the surface)
		Asura_Vector_3	m_xDirection;
#endif
		/// Position of photon impact on surface
		Asura_Vector_3	m_xPosition;

		/// Energy of the photon hit, relative to the original energy of the photon cast from the light source.
		/// So this term effectively represents the fraction of the original energy which reaches the surface.
		/// Energy is lost based on angle of incidence (Cosine diffuse response) and reflection parameters.

		Asura_Vector_3	m_xEnergy;  
};

class Tools_PhotonMapping_RegionSample
{

	public:
		Tools_PhotonMapping_RegionSample(const u_int uMaxPhotons)
		{
			m_uMaxPhotonsInSample = uMaxPhotons;
			m_uMostDistantPhoton = 0;
			m_fMaxDistanceSquared = 0.0f;
			m_uCurrentNumberOfPhotonsInSample = 0;

			m_ppxPhotons = new Tools_PhotonMapping_PhotonHit*[uMaxPhotons];
			m_pfPhotonsDistanceSquared = new float[uMaxPhotons];
		}

		~Tools_PhotonMapping_RegionSample()
		{
			delete [] m_ppxPhotons;
			delete [] m_pfPhotonsDistanceSquared;
		}

		void Reset(const Asura_Vector_3& xCentre, const Asura_Vector_3& xNormal)
		{
			m_xCentre = xCentre;
			m_xNormal = xNormal;

			m_uMostDistantPhoton = 0;
			m_fMaxDistanceSquared = 0.0f;
			m_uCurrentNumberOfPhotonsInSample = 0;
		}
		

		void SetCurrentPhotonMap(const class Tools_PhotonMap* pxPhotonMap)
		{
			m_pxPhotonMap = pxPhotonMap;
		}

		void AddPhoton(Tools_PhotonMapping_PhotonHit* pxPhotonHit);
	
		void CalculateFlux(Asura_Vector_3& xFlux);

		const Tools_PhotonMap* m_pxPhotonMap;
		Asura_Vector_3 m_xCentre;
		Asura_Vector_3 m_xNormal;

		float	m_fMaxDistanceSquared;
		u_int	m_uMostDistantPhoton;

		u_int	m_uMaxPhotonsInSample;

		Tools_PhotonMapping_PhotonHit** m_ppxPhotons;
		float*	m_pfPhotonsDistanceSquared;
		
		u_int	m_uCurrentNumberOfPhotonsInSample;


};

class Tools_PhotonMap
{
	public:

		Tools_PhotonMap()
		{
			Initialise();
		}

		~Tools_PhotonMap()
		{
			Shutdown();
		}

		void Reset(void)
		{
			Shutdown();
			Initialise();
		}

		void AddPhotonHit(const Asura_Vector_3& xPosition, const Asura_Vector_3& xDirection, const Asura_Vector_3& xEnergy)
		{
			Asura_Handle iNewPhoton = m_pxDynamicPhotonHitArray->Allocate();
			Tools_PhotonMapping_PhotonHit* pxPhotonHit = m_pxDynamicPhotonHitArray->GetPointer(iNewPhoton);

			pxPhotonHit->Set( xPosition, xDirection, xEnergy );
			
			m_uNumberOfPhotonHits++;

			m_xBoundingBox.Set( xPosition );
		}
		
		void ReorderForSampling(void)
		{
			ConstructLeftBalancedTree();

		}

		const Tools_PhotonMapping_PhotonHit& GetPhotonHit(u_int uIndex) const
		{
			return m_pxPhotonHits[uIndex];
		}

		const void AddPhotonsToSample(Tools_PhotonMapping_RegionSample& xSample)
		{
			if (!m_uNumberOfPhotonHits) return;
			if (!m_xBoundingBox.IntersectsSphere(xSample.m_xCentre,Asura_Maths::Sqrt(xSample.m_fMaxDistanceSquared))) return;
			
			TestTree(0.0f,1,xSample);
		}

		void RenderPhotonMapRepresentation(void) const {}

	private:

		
		void Initialise(void)
		{
			m_pxPhotonHits = 0;

			m_uNumberOfPhotonHits = 0;

			m_pxDynamicPhotonHitArray = new Asura_TrackingHandlePool<Tools_PhotonMapping_PhotonHit>(128*10);

			m_xBoundingBox.Reset();
		}

		void Shutdown(void)
		{
			delete [] m_pxPhotonHits;
			delete m_pxDynamicPhotonHitArray;
		}
					
		void ConstructLeftBalancedTree(void);
		void BalanceSegment(Tools_PhotonMapping_PhotonHit* pxBalancedArray, Tools_PhotonMapping_PhotonHit** ppxOriginalArray, const u_int uIndex, const u_int uStart, const u_int uEnd);
		void MedianSplit(Tools_PhotonMapping_PhotonHit** ppxArray, const u_int uStart, const u_int uEnd, const u_int uMedian, const u_int uAxis);
		void TestTree(const float fMaxDistance, const u_int uIndex, Tools_PhotonMapping_RegionSample& xSample); 

		Asura_TrackingHandlePool<Tools_PhotonMapping_PhotonHit>* m_pxDynamicPhotonHitArray;

		Tools_PhotonMapping_PhotonHit*			m_pxPhotonHits;
		u_int									m_uNumberOfPhotonHits;

		Asura_Bounding_Box						m_xBoundingBox;
		
};


//common base class for the standard lights ,and the emissive polygon lights
class Tools_PhotonMapping_BaseLightData
{
	public :
		virtual ~Tools_PhotonMapping_BaseLightData(){};
		virtual Tools_PhotonMapping_LightSource* GetLightSource() = 0;
		virtual void SetModified();

		//functions so this can be used in hashed list
		Asura_Guid GetGuid() const{return (Asura_Guid)this;}
		bool IsDestroyed() const {return false;}

		Tools_PhotonMap						m_xPhotonMap;
		Tools_PhotonMap_DirectEstimation	m_xDirectEstimationPhotonMap;
};


//! KJL - 16/01/2004 11:58:51 - contains everything needed for one light (source properties, photon map(s))
class Tools_PhotonMapping_LightData : public Tools_PhotonMapping_BaseLightData
{
	public:
		Tools_PhotonMapping_LightSource* GetLightSource() {return &m_xLightSource;}
		virtual void SetModified();

		Tools_PhotonMapping_LightSource		m_xLightSource;
		Tools_PhotonMapping_TemplateData	m_xTemplateData;
};


//and for an emissive polygon light
class Tools_PhotonMapping_PolygonLightData : public Tools_PhotonMapping_BaseLightData
{
	public:
		Tools_PhotonMapping_LightSource* 	GetLightSource() {return &m_xLightSource;}

		Tools_PhotonMapping_PolygonLightSource	m_xLightSource;
};




#endif//__TOOLS_PHOTONMAPPING_SYSTEM_INTERNAL_H__
