//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "vrad.h"
#include "leaf_ambient_lighting.h"
#include "bsplib.h"
#include "vraddetailprops.h"
#include "anorms.h"
#include "pacifier.h"


static TableVector g_BoxDirections[6] = 
{
	{  1,  0,  0 }, 
	{ -1,  0,  0 },
	{  0,  1,  0 }, 
	{  0, -1,  0 }, 
	{  0,  0,  1 }, 
	{  0,  0, -1 }, 
};



static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, 
									   Vector& radcolor )
{
	if ( !surfID )
		return;

	texinfo_t *pTexInfo = &texinfo[surfID->texinfo];

	// If we hit the sky, use the sky ambient
	if ( pTexInfo->flags & SURF_SKY )
	{
		if ( pSkylight )
		{
			// add in sky ambient
			VectorCopy( pSkylight->intensity, radcolor );
		}
	}
	else
	{
		Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity;
		VectorMultiply( radcolor, reflectivity, radcolor );
	}
}


// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code.
float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta )
{
	float dot, dot2;

	Assert( wl->type == emit_surface );

	dot = DotProduct( snormal, delta );
	if (dot < 0)
		return 0;

	dot2 = -DotProduct (delta, lnormal);
	if (dot2 <= ON_EPSILON/10)
		return 0; // behind light surface

	return dot * dot2;
}


// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code.
float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta )
{
	Assert( wl->type == emit_surface );

	// Cull out stuff that's too far
	if (wl->radius != 0)
	{
		if ( DotProduct( delta, delta ) > (wl->radius * wl->radius))
			return 0.0f;
	}

	return InvRSquared(delta);
}


void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] )
{
	int iThread = 0;

	for ( int iLight=0; iLight < numworldlights; iLight++ )
	{
		dworldlight_t *wl = &dworldlights[iLight];

		// Should this light even go in the ambient cubes?
		if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) )
			continue;

		Assert( wl->type == emit_surface );

		// Can this light see the point?
		if ( TestLine( vStart, wl->origin, 0, iThread ) != CONTENTS_EMPTY )
			continue;

		// Add this light's contribution.
		Vector vDelta = wl->origin - vStart;
		float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta );

		Vector vDeltaNorm = vDelta;
		VectorNormalize( vDeltaNorm );
		float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm );

		float ratio = flDistanceScale * flAngleScale;
		if ( ratio == 0 )
			continue;

		for ( int i=0; i < 6; i++ )
		{
			float t = DotProduct( g_BoxDirections[i], vDeltaNorm );
			if ( t > 0 )
			{
				lightBoxColor[i] += wl->intensity * (t * ratio);
			}
		}
	}	
}


void ComputeAmbientFromSphericalSamples( const Vector &vStart, Vector lightBoxColor[6] )
{
	// Figure out the color that rays hit when shot out from this position.
	Vector radcolor[NUMVERTEXNORMALS];
	for ( int i = 0; i < NUMVERTEXNORMALS; i++ )
	{
		Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74);

		// Now that we've got a ray, see what surface we've hit
		Vector lightStyleColors[MAX_LIGHTSTYLES];
		lightStyleColors[0].Init();	// We only care about light style 0 here.
		Vector colorSum;
		CalcRayAmbientLighting( vStart, vEnd, lightStyleColors, colorSum );
	
		radcolor[i] = lightStyleColors[0];
	}

	// accumulate samples into radiant box
	for ( int j = 6; --j >= 0; )
	{
		float t = 0;

		lightBoxColor[j].Init();

		for (int i = 0; i < NUMVERTEXNORMALS; i++)
		{
			float c = DotProduct( g_anorms[i], g_BoxDirections[j] );
			if (c > 0)
			{
				t += c;
				lightBoxColor[j] += radcolor[i] * c;
			}
		}
		
		lightBoxColor[j] *= 1/t;
	}

	// Now add direct light from the emit_surface lights. These go in the ambient cube because
	// there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin.
	AddEmitSurfaceLights( vStart, lightBoxColor );
}


bool IsLeafAmbientSurfaceLight( dworldlight_t *wl )
{
	static const float g_flWorldLightMinEmitSurface = 0.005f;
	static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) );

	if ( wl->type != emit_surface )
		return false;

	if ( wl->style != 0 )
		return false;

	float intensity = max( wl->intensity[0], wl->intensity[1] );
	intensity = max( intensity, wl->intensity[2] );
	
	return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface;
}


// There are problems with water lighting that make bobbing objects look bad.  the same problems
// show up for other poorly sampled areas of the world, but floating in water makes it look the
// worst because of oscillating back and forth and because water always acts as a bsp splitting
// plane.  The hack turned on if this #define is set makes all per leaf ambient for water exactly
// match the per leaf above the water, and does solve the specific problems seen with d1_canals_11.
// It was decided to punt it for hl2 because of effecting the underwater lighting of every object
// in all levels at the last minute. This solution isn't good anyway - what is needed is either
// some sort of adaptive sampling strategy, or distance-based interpolation or some other sort of
// smoothly spacially varying lighting.
#define MAKE_LIGHT_BELOW_WATER_MATCH_LIGHT_ABOVE_WATER 0

void ComputePerLeafAmbientLighting()
{
	// Figure out which lights should go in the per-leaf ambient cubes.
	int nInAmbientCube = 0;
	int nSurfaceLights = 0;
	for ( int i=0; i < numworldlights; i++ )
	{
		dworldlight_t *wl = &dworldlights[i];
		
		if ( IsLeafAmbientSurfaceLight( wl ) )
			wl->flags |= DWL_FLAGS_INAMBIENTCUBE;
		else
			wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE;
	
		if ( wl->type == emit_surface )
			++nSurfaceLights;

		if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE )
			++nInAmbientCube;
	}

	Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 );

	StartPacifier( "ComputePerLeafAmbientLighting: " );
	
	for ( int leafID = 0; leafID < numleafs; leafID++ )
	{
		dleaf_t *pLeaf = &dleafs[leafID];

		Vector cube[6];
		Vector center = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f;
#if MAKE_LIGHT_BELOW_WATER_MATCH_LIGHT_ABOVE_WATER
		if (pLeaf->contents & CONTENTS_WATER)
		{
			center.z=pLeaf->maxs[2]+1;
			int above_leaf=PointLeafnum( center);
			dleaf_t *pLeaf = &dleafs[above_leaf];
			
			center = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f;
		}
#endif
		
		ComputeAmbientFromSphericalSamples( center, cube );
		for ( int i = 0; i < 6; i++ )
		{
			VectorToColorRGBExp32( cube[i], pLeaf->m_AmbientLighting.m_Color[i] );
		}

		UpdatePacifier( (float)leafID / numleafs );
	}

	EndPacifier( true );
}



