/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include "qrad.h"

#define	MAX_LSTYLES	256

typedef struct
{
	dface_t		*faces[2];
	qboolean	coplanar;
} edgeshare_t;

edgeshare_t	edgeshare[MAX_MAP_EDGES];

int			facelinks[MAX_MAP_FACES];
int			planelinks[2][MAX_MAP_PLANES];

/*
============
LinkPlaneFaces
============
*/
void LinkPlaneFaces (void)
{
	int		i;
	dface_t	*f;

	f = dfaces;
	for (i=0 ; i<numfaces ; i++, f++)
	{
		facelinks[i] = planelinks[f->side][f->planenum];
		planelinks[f->side][f->planenum] = i;
	}
}

/*
============
PairEdges
============
*/
void PairEdges (void)
{
	int		i, j, k;
	dface_t	*f;
	edgeshare_t	*e;

	f = dfaces;
	for (i=0 ; i<numfaces ; i++, f++)
	{
		for (j=0 ; j<f->numedges ; j++)
		{
			k = dsurfedges[f->firstedge + j];
			if (k < 0)
			{
				e = &edgeshare[-k];
				e->faces[1] = f;
			}
			else
			{
				e = &edgeshare[k];
				e->faces[0] = f;
			}

			if (e->faces[0] && e->faces[1])
			{
				// determine if coplanar
				if (e->faces[0]->planenum == e->faces[1]->planenum)
					e->coplanar = true;
			}
		}
	}
}

/*
=================================================================

  POINT TRIANGULATION

=================================================================
*/

typedef struct triedge_s
{
	int			p0, p1;
	vec3_t		normal;
	vec_t		dist;
	struct triangle_s	*tri;
} triedge_t;

typedef struct triangle_s
{
	triedge_t	*edges[3];
} triangle_t;

#define	MAX_TRI_POINTS		1024
#define	MAX_TRI_EDGES		(MAX_TRI_POINTS*6)
#define	MAX_TRI_TRIS		(MAX_TRI_POINTS*2)

typedef struct
{
	int			numpoints;
	int			numedges;
	int			numtris;
	dplane_t	*plane;
	triedge_t	*edgematrix[MAX_TRI_POINTS][MAX_TRI_POINTS];
	patch_t		*points[MAX_TRI_POINTS];
	triedge_t	edges[MAX_TRI_EDGES];
	triangle_t	tris[MAX_TRI_TRIS];
} triangulation_t;

/*
===============
AllocTriangulation
===============
*/
triangulation_t	*AllocTriangulation (dplane_t *plane)
{
	triangulation_t	*t;

	t = malloc(sizeof(triangulation_t));
	t->numpoints = 0;
	t->numedges = 0;
	t->numtris = 0;

	t->plane = plane;

//	memset (t->edgematrix, 0, sizeof(t->edgematrix));

	return t;
}

/*
===============
FreeTriangulation
===============
*/
void FreeTriangulation (triangulation_t *tr)
{
	free (tr);
}


triedge_t	*FindEdge (triangulation_t *trian, int p0, int p1)
{
	triedge_t	*e, *be;
	vec3_t		v1;
	vec3_t		normal;
	vec_t		dist;

	if (trian->edgematrix[p0][p1])
		return trian->edgematrix[p0][p1];

	if (trian->numedges > MAX_TRI_EDGES-2)
		Error ("trian->numedges > MAX_TRI_EDGES-2");

	VectorSubtract (trian->points[p1]->origin, trian->points[p0]->origin, v1);
	VectorNormalize (v1, v1);
	CrossProduct (v1, trian->plane->normal, normal);
	dist = DotProduct (trian->points[p0]->origin, normal);

	e = &trian->edges[trian->numedges];
	e->p0 = p0;
	e->p1 = p1;
	e->tri = NULL;
	VectorCopy (normal, e->normal);
	e->dist = dist;
	trian->numedges++;
	trian->edgematrix[p0][p1] = e;

	be = &trian->edges[trian->numedges];
	be->p0 = p1;
	be->p1 = p0;
	be->tri = NULL;
	VectorSubtract (vec3_origin, normal, be->normal);
	be->dist = -dist;
	trian->numedges++;
	trian->edgematrix[p1][p0] = be;

	return e;
}

triangle_t	*AllocTriangle (triangulation_t *trian)
{
	triangle_t	*t;

	if (trian->numtris >= MAX_TRI_TRIS)
		Error ("trian->numtris >= MAX_TRI_TRIS");

	t = &trian->tris[trian->numtris];
	trian->numtris++;

	return t;
}

/*
============
TriEdge_r
============
*/
void TriEdge_r (triangulation_t *trian, triedge_t *e)
{
	int		i, bestp;
	vec3_t	v1, v2;
	vec_t	*p0, *p1, *p;
	vec_t	best, ang;
	triangle_t	*nt;

	if (e->tri)
		return;		// allready connected by someone

	// find the point with the best angle
	p0 = trian->points[e->p0]->origin;
	p1 = trian->points[e->p1]->origin;
	best = 1.1;
	for (i=0 ; i< trian->numpoints ; i++)
	{
		p = trian->points[i]->origin;
		// a 0 dist will form a degenerate triangle
		if (DotProduct(p, e->normal) - e->dist < 0)
			continue;	// behind edge
		VectorSubtract (p0, p, v1);
		VectorSubtract (p1, p, v2);
		if (!VectorNormalize (v1,v1))
			continue;
		if (!VectorNormalize (v2,v2))
			continue;
		ang = DotProduct (v1, v2);
		if (ang < best)
		{
			best = ang;
			bestp = i;
		}
	}
	if (best >= 1)
		return;		// edge doesn't match anything

	// make a new triangle
	nt = AllocTriangle (trian);
	nt->edges[0] = e;
	nt->edges[1] = FindEdge (trian, e->p1, bestp);
	nt->edges[2] = FindEdge (trian, bestp, e->p0);
	for (i=0 ; i<3 ; i++)
		nt->edges[i]->tri = nt;
	TriEdge_r (trian, FindEdge (trian, bestp, e->p1));
	TriEdge_r (trian, FindEdge (trian, e->p0, bestp));
}

/*
============
TriangulatePoints
============
*/
void TriangulatePoints (triangulation_t *trian)
{
	vec_t	d, bestd;
	vec3_t	v1;
	int		bp1, bp2, i, j;
	vec_t	*p1, *p2;
	triedge_t	*e, *e2;

	if (trian->numpoints < 2)
		return;

	// find the two closest points
	bestd = 9999;
	for (i=0 ; i<trian->numpoints ; i++)
	{
		p1 = trian->points[i]->origin;
		for (j=i+1 ; j<trian->numpoints ; j++)
		{
			p2 = trian->points[j]->origin;
			VectorSubtract (p2, p1, v1);
			d = VectorLength (v1);
			if (d < bestd)
			{
				bestd = d;
				bp1 = i;
				bp2 = j;
			}
		}
	}

	e = FindEdge (trian, bp1, bp2);
	e2 = FindEdge (trian, bp2, bp1);
	TriEdge_r (trian, e);
	TriEdge_r (trian, e2);
}

/*
===============
AddPointToTriangulation
===============
*/
void AddPointToTriangulation (patch_t *patch, triangulation_t *trian)
{
	int			pnum;

	pnum = trian->numpoints;
	if (pnum == MAX_TRI_POINTS)
		Error ("trian->numpoints == MAX_TRI_POINTS");
	trian->points[pnum] = patch;
	trian->numpoints++;
}

/*
===============
LerpTriangle
===============
*/
void	LerpTriangle (triangulation_t *trian, triangle_t *t, vec3_t point, vec3_t color)
{
	patch_t		*p1, *p2, *p3;
	vec3_t		base, d1, d2;
	float		x, y, x1, y1, x2, y2;

	p1 = trian->points[t->edges[0]->p0];
	p2 = trian->points[t->edges[1]->p0];
	p3 = trian->points[t->edges[2]->p0];

	VectorCopy (p1->totallight, base);
	VectorSubtract (p2->totallight, base, d1);
	VectorSubtract (p3->totallight, base, d2);

	x = DotProduct (point, t->edges[0]->normal) - t->edges[0]->dist;
	y = DotProduct (point, t->edges[2]->normal) - t->edges[2]->dist;

	x1 = 0;
	y1 = DotProduct (p2->origin, t->edges[2]->normal) - t->edges[2]->dist;

	x2 = DotProduct (p3->origin, t->edges[0]->normal) - t->edges[0]->dist;
	y2 = 0;

	if (fabs(y1)<ON_EPSILON || fabs(x2)<ON_EPSILON)
	{
		VectorCopy (base, color);
		return;
	}

	VectorMA (base, x/x2, d2, color);
	VectorMA (color, y/y1, d1, color);
}

qboolean PointInTriangle (vec3_t point, triangle_t *t)
{
	int		i;
	triedge_t	*e;
	vec_t	d;

	for (i=0 ; i<3 ; i++)
	{
		e = t->edges[i];
		d = DotProduct (e->normal, point) - e->dist;
		if (d < 0)
			return false;	// not inside
	}

	return true;
}

/*
===============
SampleTriangulation
===============
*/
void SampleTriangulation (vec3_t point, triangulation_t *trian, vec3_t color)
{
	triangle_t	*t;
	triedge_t	*e;
	vec_t		d, best;
	patch_t		*p0, *p1;
	vec3_t		v1, v2;
	int			i, j;

	if (trian->numpoints == 0)
	{
		VectorClear (color);
		return;
	}
	if (trian->numpoints == 1)
	{
		VectorCopy (trian->points[0]->totallight, color);
		return;
	}

	// search for triangles
	for (t = trian->tris, j=0 ; j < trian->numtris ; t++, j++)
	{
		if (!PointInTriangle (point, t))
			continue;

		// this is it
		LerpTriangle (trian, t, point, color);
		return;
	}
	
	// search for exterior edge
	for (e=trian->edges, j=0 ; j< trian->numedges ; e++, j++)
	{
		if (e->tri)
			continue;		// not an exterior edge

		d = DotProduct (point, e->normal) - e->dist;
		if (d < 0)
			continue;	// not in front of edge

		p0 = trian->points[e->p0];
		p1 = trian->points[e->p1];
	
		VectorSubtract (p1->origin, p0->origin, v1);
		VectorNormalize (v1, v1);
		VectorSubtract (point, p0->origin, v2);
		d = DotProduct (v2, v1);
		if (d < 0)
			continue;
		if (d > 1)
			continue;
		for (i=0 ; i<3 ; i++)
			color[i] = p0->totallight[i] + d * (p1->totallight[i] - p0->totallight[i]);
		return;
	}

	// search for nearest point
	best = 99999;
	p1 = NULL;
	for (j=0 ; j<trian->numpoints ; j++)
	{
		p0 = trian->points[j];
		VectorSubtract (point, p0->origin, v1);
		d = VectorLength (v1);
		if (d < best)
		{
			best = d;
			p1 = p0;
		}
	}

	if (!p1)
		Error ("SampleTriangulation: no points");

	VectorCopy (p1->totallight, color);
}

/*
=================================================================

  LIGHTMAP SAMPLE GENERATION

=================================================================
*/


#define	SINGLEMAP	(64*64*4)

typedef struct
{
	vec_t	facedist;
	vec3_t	facenormal;

	int		numsurfpt;
	vec3_t	surfpt[SINGLEMAP];

	vec3_t	modelorg;		// for origined bmodels

	vec3_t	texorg;
	vec3_t	worldtotex[2];	// s = (world - texorg) . worldtotex[0]
	vec3_t	textoworld[2];	// world = texorg + s * textoworld[0]

	vec_t	exactmins[2], exactmaxs[2];
	
	int		texmins[2], texsize[2];
	int		surfnum;
	dface_t	*face;
} lightinfo_t;


/*
================
CalcFaceExtents

Fills in s->texmins[] and s->texsize[]
also sets exactmins[] and exactmaxs[]
================
*/
void CalcFaceExtents (lightinfo_t *l)
{
	dface_t *s;
	vec_t	mins[2], maxs[2], val;
	int		i,j, e;
	dvertex_t	*v;
	texinfo_t	*tex;
	vec3_t		vt;

	s = l->face;

	mins[0] = mins[1] = 999999;
	maxs[0] = maxs[1] = -99999;

	tex = &texinfo[s->texinfo];
	
	for (i=0 ; i<s->numedges ; i++)
	{
		e = dsurfedges[s->firstedge+i];
		if (e >= 0)
			v = dvertexes + dedges[e].v[0];
		else
			v = dvertexes + dedges[-e].v[1];
		
//		VectorAdd (v->point, l->modelorg, vt);
		VectorCopy (v->point, vt);

		for (j=0 ; j<2 ; j++)
		{
			val = DotProduct (vt, tex->vecs[j]) + tex->vecs[j][3];
			if (val < mins[j])
				mins[j] = val;
			if (val > maxs[j])
				maxs[j] = val;
		}
	}

	for (i=0 ; i<2 ; i++)
	{	
		l->exactmins[i] = mins[i];
		l->exactmaxs[i] = maxs[i];
		
		mins[i] = floor(mins[i]/16);
		maxs[i] = ceil(maxs[i]/16);

		l->texmins[i] = mins[i];
		l->texsize[i] = maxs[i] - mins[i];
		if (l->texsize[0] * l->texsize[1] > SINGLEMAP/4)	// div 4 for extrasamples
			Error ("Surface to large to map");
	}
}

/*
================
CalcFaceVectors

Fills in texorg, worldtotex. and textoworld
================
*/
void CalcFaceVectors (lightinfo_t *l)
{
	texinfo_t	*tex;
	int			i, j;
	vec3_t	texnormal;
	vec_t	distscale;
	vec_t	dist, len;
	int			w, h;

	tex = &texinfo[l->face->texinfo];
	
// convert from float to double
	for (i=0 ; i<2 ; i++)
		for (j=0 ; j<3 ; j++)
			l->worldtotex[i][j] = tex->vecs[i][j];

// calculate a normal to the texture axis.  points can be moved along this
// without changing their S/T
	texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2]
		- tex->vecs[1][2]*tex->vecs[0][1];
	texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0]
		- tex->vecs[1][0]*tex->vecs[0][2];
	texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1]
		- tex->vecs[1][1]*tex->vecs[0][0];
	VectorNormalize (texnormal, texnormal);

// flip it towards plane normal
	distscale = DotProduct (texnormal, l->facenormal);
	if (!distscale)
	{
		Sys_FPrintf( SYS_VRB, "WARNING: Texture axis perpendicular to face\n");
		distscale = 1;
	}
	if (distscale < 0)
	{
		distscale = -distscale;
		VectorSubtract (vec3_origin, texnormal, texnormal);
	}	

// distscale is the ratio of the distance along the texture normal to
// the distance along the plane normal
	distscale = 1/distscale;

	for (i=0 ; i<2 ; i++)
	{
		len = VectorLength (l->worldtotex[i]);
		dist = DotProduct (l->worldtotex[i], l->facenormal);
		dist *= distscale;
		VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]);
		VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]);
	}


// calculate texorg on the texture plane
	for (i=0 ; i<3 ; i++)
		l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i];

// project back to the face plane
	dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1;
	dist *= distscale;
	VectorMA (l->texorg, -dist, texnormal, l->texorg);

	// compensate for org'd bmodels
	VectorAdd (l->texorg, l->modelorg, l->texorg);

	// total sample count
	h = l->texsize[1]+1;
	w = l->texsize[0]+1;
	l->numsurfpt = w * h;
}

/*
=================
CalcPoints

For each texture aligned grid point, back project onto the plane
to get the world xyz value of the sample point
=================
*/
void CalcPoints (lightinfo_t *l, float sofs, float tofs)
{
	int		i;
	int		s, t, j;
	int		w, h, step;
	vec_t	starts, startt, us, ut;
	vec_t	*surf;
	vec_t	mids, midt;
	vec3_t	facemid;
	dleaf_t	*leaf;

	surf = l->surfpt[0];
	mids = (l->exactmaxs[0] + l->exactmins[0])/2;
	midt = (l->exactmaxs[1] + l->exactmins[1])/2;

	for (j=0 ; j<3 ; j++)
		facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt;

	h = l->texsize[1]+1;
	w = l->texsize[0]+1;
	l->numsurfpt = w * h;

	starts = l->texmins[0]*16;
	startt = l->texmins[1]*16;
	step = 16;


	for (t=0 ; t<h ; t++)
	{
		for (s=0 ; s<w ; s++, surf+=3)
		{
			us = starts + (s+sofs)*step;
			ut = startt + (t+tofs)*step;


		// if a line can be traced from surf to facemid, the point is good
			for (i=0 ; i<6 ; i++)
			{
			// calculate texture point
				for (j=0 ; j<3 ; j++)
					surf[j] = l->texorg[j] + l->textoworld[0][j]*us
					+ l->textoworld[1][j]*ut;

				leaf = Rad_PointInLeaf (surf);
				if (leaf->contents != CONTENTS_SOLID)
				{
					if (!TestLine_r (0, facemid, surf))
						break;	// got it
				}

				// nudge it
				if (i & 1)
				{
					if (us > mids)
					{
						us -= 8;
						if (us < mids)
							us = mids;
					}
					else
					{
						us += 8;
						if (us > mids)
							us = mids;
					}
				}
				else
				{
					if (ut > midt)
					{
						ut -= 8;
						if (ut < midt)
							ut = midt;
					}
					else
					{
						ut += 8;
						if (ut > midt)
							ut = midt;
					}
				}
			}
		}
	}
	
}


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



#define	MAX_STYLES	32
typedef struct
{
	int			numsamples;
	float		*origins;
	int			numstyles;
	int			stylenums[MAX_STYLES];
	float		*samples[MAX_STYLES];
} facelight_t;

directlight_t	*directlights[MAX_MAP_LEAFS];
facelight_t		facelight[MAX_MAP_FACES];
int				numdlights;

/*
==================
FindTargetEntity
==================
*/
entity_t *FindTargetEntity (char *target)
{
	int		i;
	char	*n;

	for (i=0 ; i<num_entities ; i++)
	{
		n = ValueForKey (&entities[i], "targetname");
		if (!strcmp (n, target))
			return &entities[i];
	}

	return NULL;
}

//#define	DIRECT_LIGHT	3000
#define	DIRECT_LIGHT	3

/*
=============
CreateDirectLights
=============
*/
void CreateDirectLights (void)
{
	int		i;
	patch_t	*p;
	directlight_t	*dl;
	dleaf_t	*leaf;
	int		cluster;
	entity_t	*e, *e2;
	char	*name;
	char	*target;
	float	angle;
	vec3_t	dest;
	char	*_color;
	float	intensity;

	//
	// surfaces
	//
	for (i=0, p=patches ; i< (int) num_patches ; i++, p++)
	{
		if (p->totallight[0] < DIRECT_LIGHT
			&& p->totallight[1] < DIRECT_LIGHT
			&& p->totallight[2] < DIRECT_LIGHT)
			continue;

		numdlights++;
		dl = malloc(sizeof(directlight_t));
		memset (dl, 0, sizeof(*dl));

		VectorCopy (p->origin, dl->origin);

		leaf = Rad_PointInLeaf (dl->origin);
		cluster = leaf->cluster;
		dl->next = directlights[cluster];
		directlights[cluster] = dl;

		dl->type = emit_surface;
		VectorCopy (p->plane->normal, dl->normal);

		dl->intensity = ColorNormalize (p->totallight, dl->color);
		dl->intensity *= p->area * direct_scale;
		VectorClear (p->totallight);	// all sent now
	}

	//
	// entities
	//
	for (i=0 ; i<num_entities ; i++)
	{
		e = &entities[i];
		name = ValueForKey (e, "classname");
		if (strncmp (name, "light", 5))
			continue;

		numdlights++;
		dl = malloc(sizeof(directlight_t));
		memset (dl, 0, sizeof(*dl));

		GetVectorForKey (e, "origin", dl->origin);
		dl->style = FloatForKey (e, "_style");
		if (!dl->style)
			dl->style = FloatForKey (e, "style");
		if (dl->style < 0 || dl->style >= MAX_LSTYLES)
			dl->style = 0;

		leaf = Rad_PointInLeaf (dl->origin);
		cluster = leaf->cluster;

		dl->next = directlights[cluster];
		directlights[cluster] = dl;

		intensity = FloatForKey (e, "light");
		if (!intensity)
			intensity = FloatForKey (e, "_light");
		if (!intensity)
			intensity = 300;
		_color = ValueForKey (e, "_color");
		if (_color && _color[0])
		{
			sscanf (_color, "%f %f %f", &dl->color[0],&dl->color[1],&dl->color[2]);
			ColorNormalize (dl->color, dl->color);
		}
		else
			dl->color[0] = dl->color[1] = dl->color[2] = 1.0;
		dl->intensity = intensity*entity_scale;
		dl->type = emit_point;

		target = ValueForKey (e, "target");

		if (!strcmp (name, "light_spot") || target[0])
		{
			dl->type = emit_spotlight;
			dl->stopdot = FloatForKey (e, "_cone");
			if (!dl->stopdot)
				dl->stopdot = 10;
			dl->stopdot = cos(dl->stopdot/180*3.14159);
			if (target[0])
			{	// point towards target
				e2 = FindTargetEntity (target);
				if (!e2)
					Sys_Printf ("WARNING: light at (%i %i %i) has missing target\n",
					(int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]);
				else
				{
					GetVectorForKey (e2, "origin", dest);
					VectorSubtract (dest, dl->origin, dl->normal);
					VectorNormalize (dl->normal, dl->normal);
				}
			}
			else
			{	// point down angle
				angle = FloatForKey (e, "angle");
				if (angle == ANGLE_UP)
				{
					dl->normal[0] = dl->normal[1] = 0;
					dl->normal[2] = 1;
				}
				else if (angle == ANGLE_DOWN)
				{
					dl->normal[0] = dl->normal[1] = 0;
					dl->normal[2] = -1;
				}
				else
				{
					dl->normal[2] = 0;
					dl->normal[0] = cos (angle/180*3.14159);
					dl->normal[1] = sin (angle/180*3.14159);
				}
			}
		}
	}

	Sys_FPrintf( SYS_VRB, "%i direct lights\n", numdlights);
}

/*
=============
GatherSampleLight

Lightscale is the normalizer for multisampling
=============
*/
void GatherSampleLight (vec3_t pos, vec3_t normal,
			float **styletable, int offset, int mapsize, float lightscale)
{
	int				i;
	directlight_t	*l;
	byte			pvs[(MAX_MAP_LEAFS+7)/8];
	vec3_t			delta;
	float			dot, dot2;
	float			dist;
	float			scale;
	float			*dest;

	// get the PVS for the pos to limit the number of checks
	if (!PvsForOrigin (pos, pvs))
	{
		return;
	}

	for (i = 0 ; i<dvis->numclusters ; i++)
	{
		if ( ! (pvs[ i>>3] & (1<<(i&7))) )
			continue;

		for (l=directlights[i] ; l ; l=l->next)
		{
			VectorSubtract (l->origin, pos, delta);
			dist = VectorNormalize (delta, delta);
			dot = DotProduct (delta, normal);
			if (dot <= 0.001)
				continue;	// behind sample surface

			switch (l->type)
			{
			case emit_point:
				// linear falloff
				scale = (l->intensity - dist) * dot;
				break;

			case emit_surface:
				dot2 = -DotProduct (delta, l->normal);
				if (dot2 <= 0.001)
					goto skipadd;	// behind light surface
				scale = (l->intensity / (dist*dist) ) * dot * dot2;
				break;

			case emit_spotlight:
				// linear falloff
				dot2 = -DotProduct (delta, l->normal);
				if (dot2 <= l->stopdot)
					goto skipadd;	// outside light cone
				scale = (l->intensity - dist) * dot;
				break;
			default:
				Error ("Bad l->type");
			}

			if (TestLine_r (0, pos, l->origin))
				continue;	// occluded

			if (scale <= 0)
				continue;

			// if this style doesn't have a table yet, allocate one
			if (!styletable[l->style])
			{
				styletable[l->style] = malloc (mapsize);
				memset (styletable[l->style], 0, mapsize);
			}

			dest = styletable[l->style] + offset;			
			// add some light to it
			VectorMA (dest, scale*lightscale, l->color, dest);

skipadd: ;
		}
	}

}

/*
=============
AddSampleToPatch

Take the sample's collected light and
add it back into the apropriate patch
for the radiosity pass.

The sample is added to all patches that might include
any part of it.  They are counted and averaged, so it
doesn't generate extra light.
=============
*/
void AddSampleToPatch (vec3_t pos, vec3_t color, int facenum)
{
	patch_t	*patch;
	vec3_t	mins, maxs;
	int		i;

	if (numbounce == 0)
		return;
	if (color[0] + color[1] + color[2] < 3)
		return;

	for (patch = face_patches[facenum] ; patch ; patch=patch->next)
	{
		// see if the point is in this patch (roughly)
		WindingBounds (patch->winding, mins, maxs);
		for (i=0 ; i<3 ; i++)
		{
			if (mins[i] > pos[i] + 16)
				goto nextpatch;
			if (maxs[i] < pos[i] - 16)
				goto nextpatch;
		}

		// add the sample to the patch
		patch->samples++;
		VectorAdd (patch->samplelight, color, patch->samplelight);
nextpatch:;
	}

}


/*
=============
BuildFacelights
=============
*/
float	sampleofs[5][2] = 
{  {0,0}, {-0.25, -0.25}, {0.25, -0.25}, {0.25, 0.25}, {-0.25, 0.25} };


void BuildFacelights (int facenum)
{
	dface_t	*f;
	lightinfo_t	l[5];
	float		*styletable[MAX_LSTYLES];
	int			i, j;
	float		*spot;
	patch_t		*patch;
	int			numsamples;
	int			tablesize;
	facelight_t		*fl;
	
	f = &dfaces[facenum];

	if ( texinfo[f->texinfo].flags & (SURF_WARP|SURF_SKY) )
		return;		// non-lit texture

	memset (styletable,0, sizeof(styletable));

	if (extrasamples)
		numsamples = 5;
	else
		numsamples = 1;
	for (i=0 ; i<numsamples ; i++)
	{
		memset (&l[i], 0, sizeof(l[i]));
		l[i].surfnum = facenum;
		l[i].face = f;
		VectorCopy (dplanes[f->planenum].normal, l[i].facenormal);
		l[i].facedist = dplanes[f->planenum].dist;
		if (f->side)
		{
			VectorSubtract (vec3_origin, l[i].facenormal, l[i].facenormal);
			l[i].facedist = -l[i].facedist;
		}

		// get the origin offset for rotating bmodels
		VectorCopy (face_offset[facenum], l[i].modelorg);

		CalcFaceVectors (&l[i]);
		CalcFaceExtents (&l[i]);
		CalcPoints (&l[i], sampleofs[i][0], sampleofs[i][1]);
	}

	tablesize = l[0].numsurfpt * sizeof(vec3_t);
	styletable[0] = malloc(tablesize);
	memset (styletable[0], 0, tablesize);

	fl = &facelight[facenum];
	fl->numsamples = l[0].numsurfpt;
	fl->origins = malloc (tablesize);
	memcpy (fl->origins, l[0].surfpt, tablesize);

	for (i=0 ; i<l[0].numsurfpt ; i++)
	{
		for (j=0 ; j<numsamples ; j++)
		{
			GatherSampleLight (l[j].surfpt[i], l[0].facenormal, styletable,
				i*3, tablesize, 1.0/numsamples);
		}

		// contribute the sample to one or more patches
		AddSampleToPatch (l[0].surfpt[i], styletable[0]+i*3, facenum);
	}

	// average up the direct light on each patch for radiosity
	for (patch = face_patches[facenum] ; patch ; patch=patch->next)
	{
		if (patch->samples)
		{
			VectorScale (patch->samplelight, 1.0/patch->samples, patch->samplelight);
		}
		else
		{
//			printf ("patch with no samples\n");
		}
	}

	for (i=0 ; i<MAX_LSTYLES ; i++)
	{
		if (!styletable[i])
			continue;
		if (fl->numstyles == MAX_STYLES)
			break;
		fl->samples[fl->numstyles] = styletable[i];
		fl->stylenums[fl->numstyles] = i;
		fl->numstyles++;
	}

	// the light from DIRECT_LIGHTS is sent out, but the
	// texture itself should still be full bright

	if (face_patches[facenum]->baselight[0] >= DIRECT_LIGHT ||
		face_patches[facenum]->baselight[1] >= DIRECT_LIGHT ||
		face_patches[facenum]->baselight[2] >= DIRECT_LIGHT
		)
	{
		spot = fl->samples[0];
		for (i=0 ; i<l[0].numsurfpt ; i++, spot+=3)
		{
			VectorAdd (spot, face_patches[facenum]->baselight, spot);
		}
	}
}


/*
=============
FinalLightFace

Add the indirect lighting on top of the direct
lighting and save into final map format
=============
*/
void FinalLightFace (int facenum)
{
	dface_t		*f;
	int			i, j, k, st;
	vec3_t		lb;
	patch_t		*patch;
	triangulation_t	*trian;
	facelight_t	*fl;
	float		minlight;
	float		max, newmax;
	byte		*dest;
	int			pfacenum;
	vec3_t		facemins, facemaxs;

	f = &dfaces[facenum];
	fl = &facelight[facenum];

	if ( texinfo[f->texinfo].flags & (SURF_WARP|SURF_SKY) )
		return;		// non-lit texture

	ThreadLock ();
	f->lightofs = lightdatasize;
	lightdatasize += fl->numstyles*(fl->numsamples*3);

// add green sentinals between lightmaps
#if 0
lightdatasize += 64*3;
for (i=0 ; i<64 ; i++)
dlightdata[lightdatasize-(i+1)*3 + 1] = 255;
#endif

	if (lightdatasize > MAX_MAP_LIGHTING)
		Error ("MAX_MAP_LIGHTING");
	ThreadUnlock ();

	f->styles[0] = 0;
	f->styles[1] = f->styles[2] = f->styles[3] = 0xff;

	//
	// set up the triangulation
	//
	if (numbounce > 0)
	{
		ClearBounds (facemins, facemaxs);
		for (i=0 ; i<f->numedges ; i++)
		{
			int		ednum;

			ednum = dsurfedges[f->firstedge+i];
			if (ednum >= 0)
				AddPointToBounds (dvertexes[dedges[ednum].v[0]].point,
				facemins, facemaxs);
			else
				AddPointToBounds (dvertexes[dedges[-ednum].v[1]].point,
				facemins, facemaxs);
		}

		trian = AllocTriangulation (&dplanes[f->planenum]);

		// for all faces on the plane, add the nearby patches
		// to the triangulation
		for (pfacenum = planelinks[f->side][f->planenum]
			; pfacenum ; pfacenum = facelinks[pfacenum])
		{
			for (patch = face_patches[pfacenum] ; patch ; patch=patch->next)
			{
				for (i=0 ; i < 3 ; i++)
				{
					if (facemins[i] - patch->origin[i] > subdiv*2)
						break;
					if (patch->origin[i] - facemaxs[i] > subdiv*2)
						break;
				}
				if (i != 3)
					continue;	// not needed for this face
				AddPointToTriangulation (patch, trian);
			}
		}
		for (i=0 ; i<trian->numpoints ; i++)
			memset (trian->edgematrix[i], 0, trian->numpoints*sizeof(trian->edgematrix[0][0]) );
		TriangulatePoints (trian);
	}
	
	//
	// sample the triangulation
	//

	// _minlight allows models that have faces that would not be
	// illuminated to receive a mottled light pattern instead of
	// black
	minlight = FloatForKey (face_entity[facenum], "_minlight") * 128;

	dest = &dlightdata[f->lightofs];

	if (fl->numstyles > MAXLIGHTMAPS)
	{
		fl->numstyles = MAXLIGHTMAPS;
		Sys_Printf ("face with too many lightstyles: (%f %f %f)\n",
			face_patches[facenum]->origin[0],
			face_patches[facenum]->origin[1],
			face_patches[facenum]->origin[2]
			);
	}

	for (st=0 ; st<fl->numstyles ; st++)
	{
		f->styles[st] = fl->stylenums[st];
		for (j=0 ; j<fl->numsamples ; j++)
		{
			VectorCopy ( (fl->samples[st]+j*3), lb);
			if (numbounce > 0 && st == 0)
			{
				vec3_t	add;

				SampleTriangulation (fl->origins + j*3, trian, add);
				VectorAdd (lb, add, lb);
			}
			// add an ambient term if desired
			lb[0] += ambient; 
			lb[1] += ambient; 
			lb[2] += ambient; 

			VectorScale (lb, lightscale, lb);

			// we need to clamp without allowing hue to change
			for (k=0 ; k<3 ; k++)
				if (lb[k] < 1)
					lb[k] = 1;
			max = lb[0];
			if (lb[1] > max)
				max = lb[1];
			if (lb[2] > max)
				max = lb[2];
			newmax = max;
			if (newmax < 0)
				newmax = 0;		// roundoff problems
			if (newmax < minlight)
			{
				newmax = minlight + (rand()%48);
			}
			if (newmax > maxlight)
				newmax = maxlight;

			for (k=0 ; k<3 ; k++)
			{
				*dest++ = lb[k]*newmax/max;
			}
		}
	}

	if (numbounce > 0)
		FreeTriangulation (trian);
}
