//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/r_light.c                                              $
// $Revision:: 2                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 12/27/00 6:00 PM                                               $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//

#include "r_local.h"

int r_dlightframecount;
void R_BuildLightMap(void);


/*
=============================================================================
DYNAMIC LIGHTS
=============================================================================
*/

/*
=============
R_MarkLights
=============
*/
static void R_MarkLights (dlight_t *light, int bit, mnode_t *node)
{
	mplane_t *splitplane;
	float dist;
	msurface_t *surf;
	int i;

	if (node->contents != -1)
		return;

	splitplane = node->plane;
	dist = DotProduct ( light->origin, splitplane->normal ) - splitplane->dist;

	if (dist > light->intensity)
	{
		R_MarkLights ( light, bit, node->children[0] );
		return;
	}
	if (dist < -light->intensity)
	{
		R_MarkLights ( light, bit, node->children[1] );
		return;
	}

// mark the polygons
	surf = r_worldmodel->surfaces + node->firstsurface;
	for (i=0; i<node->numsurfaces; i++, surf++)
	{
		if (surf->dlightframe != r_dlightframecount)
		{
			surf->dlightbits = 0;
			surf->dlightframe = r_dlightframecount;
		}
		surf->dlightbits |= bit;
	}

	R_MarkLights ( light, bit, node->children[0] );
	R_MarkLights ( light, bit, node->children[1] );
}


/*
=============
R_PushDlights
=============
*/
void R_PushDlights (model_t *model)
{
	int i;
	dlight_t *l;

	r_dlightframecount = r_framecount;

	// if we aren't doing dynamic lights get out of here
	if ( !r_dynamic->value )
		return;

	for (i=0, l = r_newrefdef.dlights; i<r_newrefdef.num_dlights; i++, l++)
	{
		R_MarkLights ( l, 1<<i,
		               model->nodes + model->firstnode );
	}
}


/*
=============================================================================
LIGHT SAMPLING
=============================================================================
*/

vec3_t pointcolor;
mplane_t *lightplane;	// used as shadow plane
vec3_t lightspot;

static int RecursiveLightPoint (mnode_t *node, vec3_t start, vec3_t end)
{
	float front, back, frac;
	int side;
	mplane_t *plane;
	vec3_t mid;
	msurface_t *surf;
	int s, t, ds, dt;
	int i;
	mtexinfo_t  *tex;
	byte *lightmap;
	float *scales;
	int maps;
	float samp;
	int r;

	if (node->contents != -1)
		return -1; // didn't hit anything

// calculate mid point

// FIXME: optimize for axial
	plane = node->plane;
	front = DotProduct ( start, plane->normal ) - plane->dist;
	back = DotProduct ( end, plane->normal ) - plane->dist;
	side = front < 0;

	if ( (back < 0) == side)
		return RecursiveLightPoint ( node->children[side], start, end );

	frac = front / (front-back);
	mid[0] = start[0] + (end[0] - start[0])*frac;
	mid[1] = start[1] + (end[1] - start[1])*frac;
	mid[2] = start[2] + (end[2] - start[2])*frac;
	if (plane->type < 3)    // axial planes
		mid[plane->type] = plane->dist;

// go down front side
	r = RecursiveLightPoint ( node->children[side], start, mid );
	if (r >= 0)
		return r;       // hit something

	if ( (back < 0) == side )
		return -1;      // didn't hit anuthing

// check for impact on this node
	VectorCopy ( mid, lightspot );
	lightplane = plane;

	surf = r_worldmodel->surfaces + node->firstsurface;
	for (i=0; i<node->numsurfaces; i++, surf++)
	{
		if (surf->flags&(SURF_DRAWTURB|SURF_DRAWSKY))
			continue;   // no lightmaps

		tex = surf->texinfo;

		s = DotProduct ( mid, tex->vecs[0] ) + tex->vecs[0][3];
		t = DotProduct ( mid, tex->vecs[1] ) + tex->vecs[1][3];
		if (s < surf->texturemins[0] ||
		    t < surf->texturemins[1])
			continue;

		ds = s - surf->texturemins[0];
		dt = t - surf->texturemins[1];

		if ( ds > surf->extents[0] || dt > surf->extents[1] )
			continue;

		if (surf->flags&SURF_DRAWNONLIT)
		{
			if (surf->texinfo->flags & SURF_USECOLOR)
			{
				pointcolor[0] = tex->surf->nonlit*tex->surf->color[0];
				pointcolor[1] = tex->surf->nonlit*tex->surf->color[1];
				pointcolor[2] = tex->surf->nonlit*tex->surf->color[2];
			}
			else
			{
				pointcolor[0] = tex->surf->nonlit;
				pointcolor[1] = tex->surf->nonlit;
				pointcolor[2] = tex->surf->nonlit;
			}
			return 1;
		}

		if (!surf->samples)
			return 0;

		ds >>= 4;
		dt >>= 4;

		lightmap = surf->samples;
		VectorCopy ( vec3_origin, pointcolor );

		if (lightmap && r_newrefdef.lightstyles)
		{
			lightmap += dt * ((surf->extents[0]>>4)+1) + ds;

			for (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255;
			     maps++)
			{
				samp = *lightmap * /* 0.5 * */ (1.0/255);   // adjust for gl scale
				scales = r_newrefdef.lightstyles[surf->styles[maps]].rgb;
				VectorMA ( pointcolor, samp, scales, pointcolor );
				lightmap += ((surf->extents[0]>>4)+1) *
				            ((surf->extents[1]>>4)+1);
			}
		}

		return 1;
	}

// go down back side
	return RecursiveLightPoint ( node->children[!side], mid, end );
}

void R_GetSurfaceColorAtST(msurface_t *surf,float ss,float tt,vec3_t color)
{
	int s, t, ds, dt;
	byte *lightmap;
	int maps;

	if (surf->flags&(SURF_DRAWTURB|SURF_DRAWSKY|SURF_DRAWNONLIT) || (!surf->samples) )
	{
		color[0] = 1.0;
		color[1] = 1.0;
		color[2] = 1.0;
		return;
	}
	s =  ss * surf->texinfo->image->width;
	t =  tt * surf->texinfo->image->height;


	if ( s < surf->texturemins[0] )
	{
		s = surf->texturemins[0];
	}

	if ( t < surf->texturemins[1] )
	{
		t = surf->texturemins[1];
	}

	ds = s - surf->texturemins[0];
	dt = t - surf->texturemins[1];

	if ( ds > surf->extents[0] )
	{
		ds = surf->extents[ 0 ];
	}

	if ( dt > surf->extents[1] )
	{
		dt = surf->extents[ 1 ];
	}

	ds >>= 4;
	dt >>= 4;


	lightmap = surf->samples;
	VectorCopy ( vec3_origin, color );
	if (lightmap)
	{
		lightmap += dt * ((surf->extents[0]>>4)+1) + ds;

		for (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255;
		     maps++)
		{
			float samp;
			float       *scales;

			samp = *lightmap * /* 0.5 * */ (1.0/255);   // adjust for gl scale
			if ( r_newrefdef.lightstyles )
			{
				scales = r_newrefdef.lightstyles[surf->styles[maps]].rgb;
				VectorMA ( color, samp, scales, color );
			}
			else
			{
				VectorScale ( color, samp, color );
			}
			lightmap += ((surf->extents[0]>>4)+1) *
			            ((surf->extents[1]>>4)+1);
		}
	}
}

/*
===============
R_LightPoint
===============
*/
void R_LightPoint (vec3_t p, vec3_t color, qboolean dlight)
{
	vec3_t end;
	float r;
	int lnum;
	dlight_t    *dl;
	vec3_t dist;
	float add;

	if (!r_worldmodel->lightdata)
	{
		color[0] = color[1] = color[2] = 1.0;
		return;
	}

	end[0] = p[0];
	end[1] = p[1];
	end[2] = p[2] - 2048;

	r = RecursiveLightPoint ( r_worldmodel->nodes, p, end );

	if (r == -1)
	{
		color[0] = color[1] = color[2] = 1.0;
	}
	else
	{
		VectorCopy ( pointcolor, color );
	}

	//
	// add dynamic lights
	//
	if (dlight)
	{
		for (lnum=0; lnum<r_newrefdef.num_dlights; lnum++)
		{
			dl = &r_newrefdef.dlights[lnum];
			VectorSubtract ( currententity->origin,
			                 dl->origin,
			                 dist );
			add = dl->intensity - VectorLength( dist );
			add *= (1.0/256);
			if (add > 0)
			{
				VectorMA ( color, add, dl->color, color );
			}
		}
	}
}

/*
=============================================================================
LIGHT BURNING
=============================================================================
*/

static int RecursiveLightBurn (mnode_t *node, vec3_t start, vec3_t end, int burnvalue)
{
	int r;
	float front, back, frac;
	int side;
	mplane_t *plane;
	vec3_t mid;
	msurface_t  *surf;
	int s, t, ds, dt, dds, ddt;
	int i;
	mtexinfo_t  *tex;
	byte     *lightmap;
	int maps;
	float ul,ur,ll,lr;

	if (node->contents != -1)
		return -1;      // didn't hit anything

// calculate mid point

// FIXME: optimize for axial
	plane = node->plane;
	front = DotProduct ( start, plane->normal ) - plane->dist;
	back = DotProduct ( end, plane->normal ) - plane->dist;
	side = front < 0;

	if ( (back < 0) == side)
		return RecursiveLightBurn ( node->children[side], start, end, burnvalue );

	frac = front / (front-back);
	mid[0] = start[0] + (end[0] - start[0])*frac;
	mid[1] = start[1] + (end[1] - start[1])*frac;
	mid[2] = start[2] + (end[2] - start[2])*frac;

// go down front side
	r = RecursiveLightBurn ( node->children[side], start, mid, burnvalue );
	if (r >= 0)
		return r;       // hit something

	if ( (back < 0) == side )
		return -1;      // didn't hit anuthing

// check for impact on this node
	VectorCopy ( mid, lightspot );
	lightplane = plane;

	surf = r_worldmodel->surfaces + node->firstsurface;
	for (i=0; i<node->numsurfaces; i++, surf++)
	{
		if (surf->flags&(SURF_DRAWTURB|SURF_DRAWSKY|SURF_DRAWNONLIT))
			continue;   // no lightmaps

		tex = surf->texinfo;

		s = DotProduct ( mid, tex->vecs[0] ) + tex->vecs[0][3];
		t = DotProduct ( mid, tex->vecs[1] ) + tex->vecs[1][3];;

		if (s < surf->texturemins[0] ||
		    t < surf->texturemins[1])
			continue;

		ds = s - surf->texturemins[0];
		dt = t - surf->texturemins[1];

		if ( ds > surf->extents[0]|| dt > surf->extents[1] )
			continue;

		if (!surf->samples)
			return 0;

		for ( r = 0; r < MIPLEVELS; r++ )
			surf->lightmap_modified[r] = true;

		s = ds >> 4;
		t = dt >> 4;


		dds = ds - (s<<4);
		ddt = dt - (t<<4);
		ul = 1.0f - (sqrt((double)(dds*dds + ddt*ddt))/11.32f);
		if (ul<0) ul = 0;

		ddt = ((t+1)<<4) - dt;
		ll = 1.0f - (sqrt((double)(dds*dds + ddt*ddt))/11.32f);
		if (ll<0) ll = 0;

		dds = ((s+1)<<4) - ds;
		ddt = dt - (t<<4);
		ur = 1.0f - (sqrt((double)(dds*dds + ddt*ddt))/11.32f);
		if (ur<0) ur = 0;

		ddt = ((t+1)<<4) - dt;
		lr = 1.0f - (sqrt((double)(dds*dds + ddt*ddt))/11.32f);
		if (lr<0) lr = 0;

		lightmap = surf->samples;
		r = 1;
		if (lightmap)
		{
			float value;
			byte * lmap;

			lightmap += t * ((surf->extents[0]>>4)+1) + s;

			for (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255;
			     maps++)
			{
				lmap = lightmap;
				value = *lmap + burnvalue*ul;
				if (value<0) value = 0;
				if (value>255) value = 255;
				*lmap = value;
				if (s <= surf->extents[0])
				{
					lmap = lightmap+1;
					value = *lmap + burnvalue*ur;
					if (value<0) value = 0;
					if (value>255) value = 255;
					*lmap = value;
				}
				if (t <= surf->extents[1])
				{
					lmap = lightmap+((surf->extents[0]>>4)+1);
					value = *lmap + burnvalue*ll;
					if (value<0) value = 0;
					if (value>255) value = 255;
					*lmap = value;
					if (s <= surf->extents[0])
					{
						lmap = lightmap+((surf->extents[0]>>4)+2);
						value = *lmap + burnvalue*lr;
						if (value<0) value = 0;
						if (value>255) value = 255;
						*lmap = value;
					}
				}
				lightmap += ((surf->extents[0]>>4)+1) *
				            ((surf->extents[1]>>4)+1);
			}
		}
	}
// go down back side
	return RecursiveLightBurn ( node->children[!side], mid, end, burnvalue );
}

void R_BurnPoint (vec3_t p, vec3_t dir, int value)
{
	vec3_t start;
	vec3_t end;
	vec3_t u,v;
	vec3_t xoff, yoff;

	PerpendicularVector( u, dir );
	CrossProduct( u, dir, v );

	VectorScale( u, 8, xoff );
	VectorScale( v, 8, yoff );
	VectorCopy( p, start );

	end[0] = start[0] + dir[0]*2048;
	end[1] = start[1] + dir[1]*2048;
	end[2] = start[2] + dir[2]*2048;

	RecursiveLightBurn ( r_worldmodel->nodes, start, end, value/5 );

	VectorAdd( start, xoff, start );
	VectorAdd( end, xoff, end );
	RecursiveLightBurn ( r_worldmodel->nodes, start, end, value/5 );

	VectorAdd( start, yoff, start );
	VectorAdd( end, yoff, end );
	RecursiveLightBurn ( r_worldmodel->nodes, start, end, value/5 );

	VectorSubtract( start, xoff, start );
	VectorSubtract( end, xoff, end );
	VectorSubtract( start, xoff, start );
	VectorSubtract( end, xoff, end );
	RecursiveLightBurn ( r_worldmodel->nodes, start, end, value/5 );

	VectorSubtract( start, yoff, start );
	VectorSubtract( end, yoff, end );
	VectorSubtract( start, yoff, start );
	VectorSubtract( end, yoff, end );
	RecursiveLightBurn ( r_worldmodel->nodes, start, end, value/5 );
}

//===================================================================


unsigned blocklights[1024];	// allow some very large lightmaps

/*
===============
R_AddDynamicLights
===============
*/
static void R_AddDynamicLights (void)
{
	msurface_t *surf;
	int lnum;
	int sd, td;
	float dist, rad, minlight;
	vec3_t impact, local;
	int s, t;
	int i;
	int smax, tmax;
	mtexinfo_t  *tex;
	dlight_t    *dl;
	int sign;
	float scalefactor;
	vec3_t offset;
	vec3_t ofs;
	float mat[3][3];
	qboolean trans;
	float tx_length, ty_length;

	TransposeMatrix( currententity->mat, mat );
	trans = ( currententity->angles[0] || currententity->angles[1] || currententity->angles[2] );

	surf = r_drawsurf.surf;
	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;
	tex = surf->texinfo;

	for (lnum=0; lnum<r_newrefdef.num_dlights; lnum++)
	{
		if ( !(surf->dlightbits & (1<<lnum) ) )
			continue;       // not lit by this light

		dl = &r_newrefdef.dlights[lnum];
		rad = dl->intensity;
		sign = dl->color[0] < 0 ? -1 : 1;
		if ( trans )
		{
			VectorSubtract( dl->origin, currententity->origin, ofs );
			MatrixTransformVector( ofs, mat, offset );
		}
		else
		{
			VectorSubtract( dl->origin, currententity->origin, offset );
		}
		dist = DotProduct ( offset, surf->plane->normal ) - surf->plane->dist;

		rad -= fabs( dist );
		minlight = 32;      // dl->minlight;
		if (rad < minlight)
			continue;

		minlight = rad - minlight;
		tx_length = VectorLength( tex->vecs[0] );
		ty_length = VectorLength( tex->vecs[1] );
		if ( tx_length < ty_length )
			scalefactor = tx_length;
		else
			scalefactor = ty_length;
		minlight *= scalefactor;
		scalefactor = 1.0f / scalefactor;

		for (i=0; i<3; i++)
		{
			impact[i] = offset[i] -
			            surf->plane->normal[i]*dist;
		}

		local[0] = DotProduct ( impact, tex->vecs[0] ) + tex->vecs[0][3];
		local[1] = DotProduct ( impact, tex->vecs[1] ) + tex->vecs[1][3];

		local[0] -= surf->texturemins[0];
		local[1] -= surf->texturemins[1];

		if ( !vid.colorlighting )
		{
			for (t = 0; t<tmax; t++)
			{
				td = local[1] - t*16;
				if (td < 0)
					td = -td;
				for (s=0; s<smax; s++)
				{
					sd = local[0] - s*16;
					if (sd < 0)
						sd = -sd;
					if (sd > td)
						dist = sd + (td>>1);
					else
						dist = td + (sd>>1);
					if (dist < minlight)
					{
						if ( scalefactor != 1 )
							dist *= scalefactor;
						blocklights[t*smax + s] += (rad - dist)*256*sign;
					}
				}
			}
		}
		else
		{
			int offset;
			for (t = 0; t<tmax; t++)
			{
				td = local[1] - t*16;
				if (td < 0)
					td = -td;
				for (s=0; s<smax; s++)
				{
					sd = local[0] - s*16;
					if (sd < 0)
						sd = -sd;
					if (sd > td)
						dist = sd + (td>>1);
					else
						dist = td + (sd>>1);
					if (dist < minlight)
					{
						if ( scalefactor != 1 )
							dist *= scalefactor;
						offset = (t*smax + s) * 3;
						blocklights[offset + 0] += (rad - dist)*dl->color[0]*256;
						blocklights[offset + 1] += (rad - dist)*dl->color[1]*256;
						blocklights[offset + 2] += (rad - dist)*dl->color[2]*256;
					}
				}
			}
		}
	}
}

/*
===============
R_BuildLightMap

Combine and scale multiple lightmaps into the 8.8 format in blocklights
===============
*/
void R_BuildLightMap (void)
{
	int smax, tmax;
	int t;
	int i, size;
	byte *lightmap;
	unsigned scale;
	int maps;
	msurface_t *surf;

	surf = r_drawsurf.surf;

	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;
	size = smax*tmax;

	if ( vid.colorlighting )
		size *= 3;

	if (r_fullbright->value || !r_worldmodel->lightdata)
	{
		for (i=0; i<size; i++)
			blocklights[i] = 0;
		return;
	}

// clear to no light
	for (i=0; i<size; i++)
		blocklights[i] = 0;


// add all the lightmaps
	lightmap = surf->samples;
	if (lightmap)
		for (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255;
		     maps++)
		{
			scale = r_drawsurf.lightadj[maps];  // 8.8 fraction
			for (i=0; i<size; i++)
				blocklights[i] += lightmap[i] * scale;
			lightmap += size;   // skip to next lightmap
		}

// add all the dynamic lights
	if (surf->dlightframe == r_framecount)
		R_AddDynamicLights ();

// bound, invert, and shift
	for (i=0; i<size; i++)
	{
		t = (int)blocklights[i];
		if (t < 0)
			t = 0;
		t = (255*256 - t) >> (8 - VID_CBITS);

		if (t < VID_GRADES /*(1 << 6)*/)
			t = VID_GRADES /*(1 << 6)*/;

		blocklights[i] = t;
	}
}
