// LightTrack.cpp: implementation of the CROS_impl class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "LightTrack.h"
#include "../../include/xrRender/RenderVisual.h"
#include "../../xrEngine/xr_object.h"

#ifdef _EDITOR
#	include "igame_persistent.h"
#	include "environment.h"
#else
#	include "../../xrEngine/igame_persistent.h"
#	include "../../xrEngine/environment.h"
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CROS_impl::CROS_impl	()
{
	approximate.set		( 0,0,0 );
	dwFrame				= u32(-1);
	shadow_recv_frame	= u32(-1);
	shadow_recv_slot	= -1;

	result_count		= 0;
	result_iterator		= 0;
	result_frame		= u32(-1);
	result_sun			= 0;
	hemi_value			= 0.5f;
	hemi_smooth			= 0.5f;
	sun_value			= 0.2f;
	sun_smooth			= 0.2f;

#if RENDER!=R_R1
	last_position.set( 0.0f, 0.0f, 0.0f );
	ticks_to_update		= 0;
	sky_rays_uptodate	= 0;
#endif	// RENDER!=R_R1

//#if RENDER==R_R1
	MODE				= IRender_ObjectSpecific::TRACE_ALL											;
//#else
//	MODE				= IRender_ObjectSpecific::TRACE_HEMI + IRender_ObjectSpecific::TRACE_SUN	;
//#endif
}

void	CROS_impl::add		(light* source)
{
	// Search
	for (xr_vector<Item>::iterator I=track.begin(); I!=track.end(); I++)	
		if (source == I->source)	{ I->frame_touched = Device.dwFrame; return; }

	// Register _new_
	track.push_back		(Item());
	Item&	L			= track.back();
	L.frame_touched		= Device.dwFrame;
	L.source			= source;
	L.cache.verts[0].set(0,0,0);
	L.cache.verts[1].set(0,0,0);
	L.cache.verts[2].set(0,0,0);
	L.test				= 0.f;
	L.energy			= 0.f;
}

IC bool	pred_energy			(const CROS_impl::Light& L1, const CROS_impl::Light& L2)	{ return L1.energy>L2.energy; }
//////////////////////////////////////////////////////////////////////////
#pragma warning(push)
#pragma warning(disable:4305)

// const float		hdir		[lt_hemisamples][3] = 
// {
// 	{0.00000,	1.00000,	0.00000	},
// 	{0.52573,	0.85065,	0.00000	},
// 	{0.16246,	0.85065,	0.50000	},
// 	{-0.42533,	0.85065,	0.30902	},
// 	{-0.42533,	0.85065,	-0.30902},
// 	{0.16246,	0.85065,	-0.50000},
// 	{0.89443,	0.44721,	0.00000	},
// 	{0.27639,	0.44721,	0.85065	},
// 	{-0.72361,	0.44721,	0.52573	},
// 	{-0.72361,	0.44721,	-0.52573},
// 	{0.27639,	0.44721,	-0.85065},
// 	{0.68819,	0.52573,	0.50000	},
// 	{-0.26287,	0.52573,	0.80902	},
// 	{-0.85065,	0.52573,	-0.00000},
// 	{-0.26287,	0.52573,	-0.80902},
// 	{0.68819,	0.52573,	-0.50000},
// 	{0.95106,	0.00000,	0.30902	},
// 	{0.58779,	0.00000,	0.80902	},
// 	{-0.00000,	0.00000,	1.00000	},
// 	{-0.58779,	0.00000,	0.80902	},
// 	{-0.95106,	0.00000,	0.30902	},
// 	{-0.95106,	0.00000,	-0.30902},
// 	{-0.58779,	0.00000,	-0.80902},
// 	{0.00000,	0.00000,	-1.00000},
// 	{0.58779,	0.00000,	-0.80902},
// 	{0.95106,	0.00000,	-0.30902}
// };

const float		hdir		[lt_hemisamples][3] = 
{
	{-0.26287,	0.52573,	0.80902	},
	{0.27639,	0.44721,	0.85065	},
	{-0.95106,	0.00000,	0.30902	},
	{-0.95106,	0.00000,	-0.30902},
	{0.58779,	0.00000,	-0.80902},
	{0.58779,	0.00000,	0.80902	},

	{-0.00000,	0.00000,	1.00000	},
	{0.52573,	0.85065,	0.00000	},
	{-0.26287,	0.52573,	-0.80902},
	{-0.42533,	0.85065,	0.30902	},
	{0.95106,	0.00000,	0.30902	},
	{0.95106,	0.00000,	-0.30902},
	

	{0.00000,	1.00000,	0.00000	},
	{-0.58779,	0.00000,	0.80902	},
	{-0.72361,	0.44721,	0.52573	},
	{-0.72361,	0.44721,	-0.52573},
	{-0.58779,	0.00000,	-0.80902},
	{0.16246,	0.85065,	-0.50000},

	{0.89443,	0.44721,	0.00000	},
	{-0.85065,	0.52573,	-0.00000},
	{0.16246,	0.85065,	0.50000	},
	{0.68819,	0.52573,	-0.50000},
	{0.27639,	0.44721,	-0.85065},
	{0.00000,	0.00000,	-1.00000},

	{-0.42533,	0.85065,	-0.30902},
	{0.68819,	0.52573,	0.50000	},
};
#pragma warning(pop)

//inline CROS_impl::CubeFaces CROS_impl::get_cube_face(Fvector3& dir)
//{
//	float x2 = dir.x*dir.x;
//	float y2 = dir.y*dir.y;
//	float z2 = dir.z*dir.z;
//
//	if (x2 >= y2 + z2)
//	{
//		return (dir.x > 0) ? CUBE_FACE_POS_X : CUBE_FACE_NEG_X;
//	}
//	else if (y2 >= z2 + x2)
//	{
//		return (dir.y > 0) ? CUBE_FACE_POS_Y : CUBE_FACE_NEG_Y;
//	}
//	/*else*/
//	return (dir.z > 0) ? CUBE_FACE_POS_Z : CUBE_FACE_NEG_Z;
//}

inline void CROS_impl::accum_hemi(float* hemi_cube, Fvector3& dir, float scale)
{
	if (dir.x>0)
		hemi_cube[CUBE_FACE_POS_X] +=  dir.x * scale;
	else
		hemi_cube[CUBE_FACE_NEG_X] -=  dir.x * scale;	//	dir.x <= 0

	if (dir.y>0)
		hemi_cube[CUBE_FACE_POS_Y] +=  dir.y * scale;
	else
		hemi_cube[CUBE_FACE_NEG_Y] -=  dir.y * scale;	//	dir.y <= 0

	if (dir.z>0)
		hemi_cube[CUBE_FACE_POS_Z] +=  dir.z * scale;
	else
		hemi_cube[CUBE_FACE_NEG_Z] -=  dir.z * scale;	//	dir.z <= 0
}

//////////////////////////////////////////////////////////////////////////
void	CROS_impl::update	(IRenderable* O)
{
	// clip & verify
	if					(dwFrame==Device.dwFrame)			return;
	dwFrame				= Device.dwFrame;
	if					(0==O)								return;
	if					(0==O->renderable.visual)			return;
	VERIFY				(dynamic_cast<CROS_impl*>			(O->renderable_ROS()));
	//float	dt			=	Device.fTimeDelta;

	CObject*	_object	= dynamic_cast<CObject*>	(O);

	// select sample, randomize position inside object
	vis_data &vis = O->renderable.visual->getVisData();
	Fvector	position;	O->renderable.xform.transform_tiny	(position,vis.sphere.P);
	position.y			+=  .3f * vis.sphere.R;
	Fvector	direction;	direction.random_dir();
//.			position.mad(direction,0.25f*radius);
//.			position.mad(direction,0.025f*radius);

	//function call order is important at least for r1
	for (size_t i = 0; i < NUM_FACES; ++i)
	{
		hemi_cube[i] = 0;
	}

	bool	bFirstTime	=	(0==result_count);
	calc_sun_value			(position, _object)	;
	calc_sky_hemi_value		(position, _object)	;

	prepare_lights			(position, O)	;

	// Process ambient lighting and approximate average lighting
	// Process our lights to find average luminescences
	CEnvDescriptor&	desc	=	*g_pGamePersistent->Environment().CurrentEnv;
	Fvector			accum	=	{ desc.ambient.x,		desc.ambient.y,		desc.ambient.z		};
	Fvector			hemi	=	{ desc.hemi_color.x,	desc.hemi_color.y,	desc.hemi_color.z	};
	Fvector			sun_	=	{ desc.sun_color.x,		desc.sun_color.y,	desc.sun_color.z	};
	if (MODE & IRender_ObjectSpecific::TRACE_HEMI	)	hemi.mul(hemi_smooth); else hemi.mul(.2f);
					accum.add	( hemi );
	if (MODE & IRender_ObjectSpecific::TRACE_SUN	)	sun_.mul(sun_smooth); else sun_.mul(.2f);
					accum.add	( sun_ );
	if (MODE & IRender_ObjectSpecific::TRACE_LIGHTS )	{
		Fvector		lacc	=	{ 0,0,0 };
#if RENDER!=R_R1
		float hemi_cube_light[NUM_FACES] = {0,0,0,0,0,0};
#endif
		for (u32 lit=0; lit<lights.size(); lit++)	{
			light*	L	=	lights[lit].source;
			float	d	=	L->position.distance_to(position);
			
#if RENDER!=R_R1
			float	a	=	(1/(L->attenuation0 + L->attenuation1*d + L->attenuation2*d*d) - d*L->falloff) *(L->flags.bStatic?1.f:2.f);
			a	=	(a > 0) ? a : 0.0f;

			Fvector3	dir;
			dir.sub(L->position, position);
			dir.normalize_safe();
			
			//multiply intensity on attenuation and accumulate result in hemi cube face
			float koef = (lights[lit].color.r + lights[lit].color.g + lights[lit].color.b) / 3.0f * a
						* ps_r2_dhemi_light_scale;
			
			accum_hemi(hemi_cube_light, dir, koef);
#else
			float	r	=	L->range;
			float	a	=	clampr(1.f - d/(r+EPS),0.f,1.f)*(L->flags.bStatic?1.f:2.f);
#endif
			lacc.x		+=	lights[lit].color.r*a;
			lacc.y		+=	lights[lit].color.g*a;
			lacc.z		+=	lights[lit].color.b*a;
		}
#if RENDER!=R_R1
		const float	minHemiValue = 1/255.f;

		float	hemi_light = (lacc.x + lacc.y + lacc.z)/3.0f * ps_r2_dhemi_light_scale;

		hemi_value += hemi_light;
		hemi_value = std::max(hemi_value, minHemiValue);

		for (size_t i = 0; i < NUM_FACES; ++i)
		{
			hemi_cube[i] += hemi_cube_light[i]*(1-ps_r2_dhemi_light_flow) + ps_r2_dhemi_light_flow*hemi_cube_light[(i+NUM_FACES/2)%NUM_FACES];
			hemi_cube[i] = std::max(hemi_cube[i], minHemiValue);
		}
#endif

//		lacc.x		*= desc.lmap_color.x;
//		lacc.y		*= desc.lmap_color.y;
//		lacc.z		*= desc.lmap_color.z;
//		Msg				("- rgb[%f,%f,%f]",lacc.x,lacc.y,lacc.z);
		accum.add		(lacc);
	} else 			accum.set	( .1f, .1f, .1f );


	//clamp(hemi_value, 0.0f, 1.0f); //Possibly can change hemi value
	if (bFirstTime)
	{
		hemi_smooth	= hemi_value;
		CopyMemory(hemi_cube_smooth, hemi_cube, NUM_FACES*sizeof(float));
	}

	update_smooth			()	;
	approximate				=	accum;
}

#if RENDER!=R_R1

//	Update ticks settings
static const s32 s_iUTFirstTimeMin = 1;
static const s32 s_iUTFirstTimeMax = 1;
static const s32 s_iUTPosChangedMin = 3;
static const s32 s_iUTPosChangedMax = 6;
static const s32 s_iUTIdleMin = 1000;
static const s32 s_iUTIdleMax = 2000;

void 	CROS_impl::smart_update(IRenderable* O)
{
	if (!O) return;
	if (0==O->renderable.visual) return;

	--ticks_to_update;

	//	Acquire current position
	Fvector	position;
	VERIFY(dynamic_cast<CROS_impl*>	(O->renderable_ROS()));
	vis_data &vis = O->renderable.visual->getVisData();
	O->renderable.xform.transform_tiny( position, vis.sphere.P );

	if ( ticks_to_update <= 0)
	{
		update(O);
		last_position = position;

		if ( result_count < lt_hemisamples )
			ticks_to_update = ::Random.randI(s_iUTFirstTimeMin, s_iUTFirstTimeMax+1);
		else if ( sky_rays_uptodate < lt_hemisamples )
			ticks_to_update = ::Random.randI(s_iUTPosChangedMin, s_iUTPosChangedMax+1);
		else
			ticks_to_update = ::Random.randI(s_iUTIdleMin, s_iUTIdleMax+1);
	}
	else
	{
		if (!last_position.similar(position, 0.15))
		{
			sky_rays_uptodate = 0;
			update(O);
			last_position = position;

			if ( result_count < lt_hemisamples )
				ticks_to_update = ::Random.randI(s_iUTFirstTimeMin, s_iUTFirstTimeMax+1);
			else
				ticks_to_update = ::Random.randI(s_iUTPosChangedMin, s_iUTPosChangedMax+1);
		}
	}
}

#endif	//	#if RENDER!=R_R1		

extern float ps_r2_lt_smooth;

// hemi & sun: update and smooth
void	CROS_impl::update_smooth	(IRenderable* O)
{
	if (dwFrameSmooth == Device.dwFrame)
		return;

	dwFrameSmooth			=	Device.dwFrame;

#if RENDER==R_R1
	if (O && (0==result_count)) 
		update(O)						;	// First time only
#else	//	RENDER!=R_R1
	smart_update(O);
#endif	//	RENDER!=R_R1

	float	l_f				=	Device.fTimeDelta*ps_r2_lt_smooth;
	clamp	(l_f,0.f,1.f)	;
	float	l_i				=	1.f-l_f							;
	hemi_smooth				=	hemi_value*l_f + hemi_smooth*l_i;
	sun_smooth				=	sun_value *l_f + sun_smooth *l_i;
	for (size_t i = 0; i < NUM_FACES; ++i)
	{
		hemi_cube_smooth[i] =	hemi_cube[i]*l_f + hemi_cube_smooth[i]*l_i;
	}
}

void CROS_impl::calc_sun_value(Fvector& position, CObject* _object)
{

#if RENDER==R_R1
	light*	sun		=		(light*)RImplementation.L_DB->sun_adapted._get()	;
#else
	light*	sun		=		(light*)RImplementation.Lights.sun_adapted._get()	;
#endif
	if	(MODE & IRender_ObjectSpecific::TRACE_SUN)	{
		if  (--result_sun	< 0)	{
			result_sun		+=		::Random.randI(lt_hemisamples/4,lt_hemisamples/2)	;
			Fvector	direction;	direction.set	(sun->direction).invert().normalize	();
			sun_value		=	!(g_pGameLevel->ObjectSpace.RayTest(position,direction,500.f,collide::rqtBoth,&cache_sun,_object))?1.f:0.f;
		}
	}
}

void CROS_impl::calc_sky_hemi_value(Fvector& position, CObject* _object)
{
	// hemi-tracing
	if	(MODE & IRender_ObjectSpecific::TRACE_HEMI)	
	{
	
#if RENDER!=R_R1
		sky_rays_uptodate	+= ps_r2_dhemi_count;
		sky_rays_uptodate	= _min(sky_rays_uptodate, lt_hemisamples);
#endif	//	RENDER!=R_R1

		for (u32 it=0; it<(u32)ps_r2_dhemi_count;	it++)		{	// five samples per one frame
			u32	sample		=	0				;
			if	(result_count<lt_hemisamples)	{ sample=result_count; result_count++;							}
			else								{ sample=(result_iterator%lt_hemisamples); result_iterator++;	}

			// take sample
			Fvector	direction;	direction.set	(hdir[sample][0],hdir[sample][1],hdir[sample][2]).normalize	();
			//.			result[sample]	=	!g_pGameLevel->ObjectSpace.RayTest(position,direction,50.f,collide::rqtBoth,&cache[sample],_object);
			result[sample]	=	!g_pGameLevel->ObjectSpace.RayTest(position,direction,50.f,collide::rqtStatic,&cache[sample],_object);
			//	Msg				("%d:-- %s",sample,result[sample]?"true":"false");
		}
	}
	// hemi & sun: update and smooth
	//	float	l_f				=	dt*lt_smooth;
	//	float	l_i				=	1.f-l_f;
	int		_pass			=	0;
	for (int it=0; it<result_count; it++)	if (result[it])	_pass	++;
	hemi_value				=	float	(_pass)/float(result_count?result_count:1);
	hemi_value				*=	ps_r2_dhemi_sky_scale;

	for (int it=0; it<result_count; it++)
	{
		if (result[it])
		{
			accum_hemi(hemi_cube, Fvector3().set(hdir[it][0], hdir[it][1], hdir[it][2]), ps_r2_dhemi_sky_scale);
		}
	}
}

void CROS_impl::prepare_lights(Fvector& position, IRenderable* O)
{
	CObject*	_object	= dynamic_cast<CObject*>	(O);
	float	dt			=	Device.fTimeDelta;

	vis_data &vis = O->renderable.visual->getVisData();
	float	radius;		radius	= vis.sphere.R;
	// light-tracing
	BOOL	bTraceLights	= MODE & IRender_ObjectSpecific::TRACE_LIGHTS;
	if		((!O->renderable_ShadowGenerate()) && (!O->renderable_ShadowReceive()))	bTraceLights = FALSE;
	if		(bTraceLights)	{
		// Select nearest lights
		Fvector					bb_size	=	{radius,radius,radius};

#if RENDER!=R_R1
		g_SpatialSpace->q_box				(RImplementation.lstSpatial,0,STYPE_LIGHTSOURCEHEMI,position,bb_size);
#else
		g_SpatialSpace->q_box				(RImplementation.lstSpatial,0,STYPE_LIGHTSOURCE,position,bb_size);
#endif
		for (u32 o_it=0; o_it<RImplementation.lstSpatial.size(); o_it++)	{
			ISpatial*	spatial		= RImplementation.lstSpatial[o_it];
			light*		source		= (light*)	(spatial->dcast_Light());
			VERIFY		(source);	// sanity check
			float	R				= radius+source->range;
			if (position.distance_to(source->position) < R
#if RENDER!=R_R1
				&&source->flags.bStatic
#endif
				)		add	(source);
		}

		// Trace visibility
		lights.clear	();
#if RENDER==R_R1 
		float traceR	= radius*.5f;
#endif
		for (s32 id=0; id<s32(track.size()); id++)
		{
			// remove untouched lights
			xr_vector<CROS_impl::Item>::iterator I	= track.begin()+id;
			if (I->frame_touched!=Device.dwFrame)	{ track.erase(I) ; id--	; continue ; }

			// Trace visibility
			Fvector				P,D;
			float		amount	= 0;
			light*		xrL		= I->source;
			Fvector&	LP		= xrL->position;
#if RENDER==R_R1
			P.mad				(position,P.random_dir(),traceR);		// Random point inside range
#else
			P = position;
#endif

			// point/spot
			float	f			=	D.sub(P,LP).magnitude();
			if (g_pGameLevel->ObjectSpace.RayTest(LP,D.div(f),f,collide::rqtStatic,&I->cache,_object))	amount -=	lt_dec;
			else																						amount +=	lt_inc;
			I->test				+=	amount * dt;	clamp	(I->test,-.5f,1.f);
			I->energy			=	.9f*I->energy + .1f*I->test;

			// 
			float	E			=	I->energy * xrL->color.intensity	();
			if (E > EPS)		{
				// Select light
				lights.push_back			(CROS_impl::Light())		;
				CROS_impl::Light&	L		= lights.back()				;
				L.source					= xrL						;
				L.color.mul_rgb				(xrL->color,I->energy/2)	;
				L.energy					= I->energy/2				;
				if (!xrL->flags.bStatic)	{ L.color.mul_rgb(.5f); L.energy *= .5f; }
			}
		}

#if RENDER==R_R1
		light*	sun		=		(light*)RImplementation.L_DB->sun_adapted._get()	;

		// Sun
		float	E			=	sun_smooth * sun->color.intensity	();
		if (E > EPS)		{
			// Select light
			lights.push_back			(CROS_impl::Light())		;
			CROS_impl::Light&	L		= lights.back()				;
			L.source					= sun						;
			L.color.mul_rgb				(sun->color,sun_smooth/2)	;
			L.energy					= sun_smooth				;
		}
#endif
		// Sort lights by importance - important for R1-shadows
		std::sort	(lights.begin(),lights.end(), pred_energy);

	}
}
