/***
*
*	Copyright (c) 1998, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
****/

#include "qrad.h"

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

edgeshare_t	edgeshare[MAX_MAP_EDGES];

vec3_t	face_centroids[MAX_MAP_EDGES];

#ifdef OBSOLETE_CODE

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;
	}
}

#endif

/*
============
PairEdges
============
*/
void PairEdges (void)
{
	int		i, j, k, n;
	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;
				else if ( smoothing_threshold > 0 )
				{
					// see if they fall into a "smoothing group" based on angle of the normals
					vec3_t	normals[2];
					double	cos_normals_angle;
					for(n=0; n<2; n++)
					{
						VectorCopy( dplanes[e->faces[n]->planenum].normal, normals[n] );
						if ( e->faces[n]->side )
							VectorSubtract( vec3_origin, normals[n], normals[n] );
					}
					cos_normals_angle = DotProduct( normals[0], normals[1] );
					if ( cos_normals_angle >= smoothing_threshold ) 
					{
						VectorAdd( normals[0], normals[1], e->interface_normal );
						VectorNormalize( e->interface_normal );
					}
				}
			}
		}
	}
}

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

  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		2048  // Was 1024 originally.
#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 = NULL;


    HANDLE h;
	if ( h = GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, sizeof(triangulation_t) ) )
	{
		t = GlobalLock( h );

		t->numpoints = 0;
		t->numedges = 0;
		t->numtris = 0;

		t->plane = plane;
	}
	else
		Error("Cannot alloc triangulation memory!");

	return t;
}

/*
===============
FreeTriangulation
===============
*/
void FreeTriangulation (triangulation_t *tr)
{
    HANDLE h = GlobalHandle(tr);
    
	if ( h )
	{
		GlobalUnlock(h);
		GlobalFree(h);
	}
	else
		Error("Cannot free triangulation memory!");
}


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);
	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.1f;
	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))
			continue;
		if (!VectorNormalize (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 = (float)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);
}

/*
===============
AddPatchToTriangulation
===============
*/
void AddPatchToTriangulation (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 result)
{
	patch_t		*p1, *p2, *p3;
	vec3_t		base, d1, d2;
	vec_t		x, y, x1, y1, x2, y2;
	int			i;

	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;

#ifdef BROKEN_CODE
	if (fabs(y1)<ON_EPSILON || fabs(x2)<ON_EPSILON)
	{
		VectorCopy( base, result );
	}
	else
	{
		for( i=0; i<3; i++ )
			result[i] = base[i] + x*d2[i]/x2 + y*d1[i]/y1;
	}
#else
	VectorCopy( base, result );
	if ( fabs(x2) >= ON_EPSILON )
		for( i=0; i<3; i++)
			result[i] += x*d2[i]/x2;
	if ( fabs(y1) >= ON_EPSILON )
		for( i=0; i<3; i++)
			result[i] += y*d1[i]/y1;
#endif

}

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, triangle_t **last_tri, vec3_t result)
{
	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)
	{
		VectorFill( result, 0 );
		return;
	}

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

	// try the last one that worked first
	if (*last_tri)
	{
		if (PointInTriangle (point, *last_tri))
		{
			LerpTriangle (trian, *last_tri, point, result);
			return;
		}
	}

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

		// this is it
		*last_tri = t;
		LerpTriangle (trian, t, point, result);
		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);
		VectorSubtract (point, p0->origin, v2);
		d = DotProduct (v2, v1);
		if (d < 0)
			continue;
		if (d > 1)
			continue;

		for( i=0; i<3; i++ )
			result[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 = (float)VectorLength (v1);
		if (d < best)
		{
			best = d;
			p1 = p0;
		}
	}

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

	VectorCopy( p1->totallight, result );
}

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

  LIGHTMAP SAMPLE GENERATION

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


#define	SINGLEMAP	(18*18*4)

typedef struct
{
	vec3_t	lightmaps[MAXLIGHTMAPS][SINGLEMAP];
	int		numlightstyles;
	vec_t	*light;
	vec_t	facedist;
	vec3_t	facenormal;

	int		numsurfpt;
	vec3_t	surfpt[SINGLEMAP];
	vec3_t	facemid;		// world coordinates of center

	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		lightstyles[256];
	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;
	
	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];
		
		for (j=0 ; j<2 ; j++)
		{
			val = v->point[0] * tex->vecs[j][0] + 
				v->point[1] * tex->vecs[j][1] +
				v->point[2] * tex->vecs[j][2] +
				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] = (float)floor(mins[i]/16);
		maxs[i] = (float)ceil(maxs[i]/16);

		l->texmins[i] = (int)mins[i];
		l->texsize[i] = (int)(maxs[i] - mins[i]);
		if (l->texsize[i] > 17)
			Error ("Bad surface extents");
	}
}

/*
================
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;

	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);

// flip it towards plane normal
	distscale = DotProduct (texnormal, l->facenormal);
	if (!distscale)
		Error ("Texture axis perpendicular to face");
	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 = (float)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);
	
}

/*
=================
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)
{
	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	origin;

	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++)
		l->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;
	starts = (float)l->texmins[0]*16;
	startt = (float)l->texmins[1]*16;
	step = 16;

	l->numsurfpt = w * h;

	// get the origin offset for rotating bmodels
	VectorCopy (face_offset[l->surfnum], origin);

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


		// if a line can be traced from surf to facemid, the point is good
			for (i=0 ; i<64; i++)
			{
			// calculate texture point
				dleaf_t		*luxelleaf;

				for (j=0 ; j<3 ; j++)
					surf[j] = l->texorg[j] + l->textoworld[0][j]*us
					+ l->textoworld[1][j]*ut;
				VectorAdd (surf, origin, surf);

				luxelleaf = PointInLeaf(surf);

				// Make sure we are "in the world"(Not the zero leaf)
				if ( luxelleaf != dleafs )
				{
#if defined(BUGGY_TEST)
					if (TestLine_r (0, l->facemid, surf) == CONTENTS_EMPTY)
#endif
						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;
					}
				}
			}
		}
	}
	
}


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



typedef struct
{
	vec3_t		pos;
	vec3_t		light;
} sample_t;

typedef struct
{
	int			numsamples;
	sample_t	*samples[MAXLIGHTMAPS];
} 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;
}

/*
=============
CreateDirectLights
=============
*/
#define	DIRECT_SCALE	0.1f
void CreateDirectLights (void)
{
	unsigned i;
	patch_t	*p;
	directlight_t	*dl;
	dleaf_t	*leaf;
	int		leafnum;
	entity_t	*e, *e2;
	char	*name;
	char	*target;
	float	angle;
	vec3_t	dest;

	numdlights = 0;

	//
	// surfaces
	//
	for (i=0, p=patches ; i<num_patches ; i++, p++)
	{
		if( VectorAvg( p->totallight ) >= dlight_threshold )
			{
			numdlights++;
			dl = calloc(1, sizeof(directlight_t));

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

			leaf = PointInLeaf (dl->origin);
			leafnum = leaf - dleafs;

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

			dl->type = emit_surface;
			VectorCopy (p->normal, dl->normal);
			VectorCopy( p->totallight, dl->intensity );
			VectorScale( dl->intensity, p->area, dl->intensity );
			VectorScale( dl->intensity, DIRECT_SCALE, dl->intensity );
			}

		VectorFill( p->totallight, 0 );		// all sent now  // BUGBUG for progressive refinement runs
	}

	//
	// entities
	//
	for (i=0 ; i<(unsigned)num_entities ; i++)
	{
		char *pLight;
		double r, g, b, scaler;
		float	l1;
		int argCnt;

		e = &entities[i];
		name = ValueForKey (e, "classname");
		if (strncmp (name, "light", 5))
			continue;

		numdlights++;
		dl = calloc(1, sizeof(directlight_t));

		GetVectorForKey (e, "origin", dl->origin);

		leaf = PointInLeaf (dl->origin);
		leafnum = leaf - dleafs;

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

		dl->style = (int)FloatForKey (e, "style");

		pLight = ValueForKey( e, "_light" );
		// scanf into doubles, then assign, so it is vec_t size independent
		r = g = b = scaler = 0;
		argCnt = sscanf ( pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler );
		dl->intensity[0] = (float)r;
		if( argCnt == 1 )
		{
			// The R,G,B values are all equal.
			dl->intensity[1] = dl->intensity[2] = (float)r;
		}
		else if ( argCnt == 3 || argCnt == 4 )
		{
			// Save the other two G,B values.
			dl->intensity[1] = (float)g;
			dl->intensity[2] = (float)b;

			// Did we also get an "intensity" scaler value too?
			if ( argCnt == 4 )
			{
				// Scale the normalized 0-255 R,G,B values by the intensity scaler
				dl->intensity[0] = dl->intensity[0] / 255 * (float)scaler;
				dl->intensity[1] = dl->intensity[1] / 255 * (float)scaler;
				dl->intensity[2] = dl->intensity[2] / 255 * (float)scaler;
			}
		}
		else
		{
			printf( "entity at (%f,%f,%f) has bad '_light' value : '%s'\n", 
					dl->origin[0], dl->origin[1], dl->origin[2], pLight);
			continue;
		}

		target = ValueForKey (e, "target");

		if (!strcmp (name, "light_spot") || !strcmp(name, "light_environment") || target[0])
		{
			if (!VectorAvg( dl->intensity ))
				VectorFill( dl->intensity, 500 );
			dl->type = emit_spotlight;
			dl->stopdot = FloatForKey (e, "_cone");
			if (!dl->stopdot)
				dl->stopdot = 10;
			dl->stopdot2 = FloatForKey (e, "_cone2");
			if (!dl->stopdot2) 
				dl->stopdot2 = dl->stopdot;
			if (dl->stopdot2 < dl->stopdot)
				dl->stopdot2 = dl->stopdot;
			dl->stopdot2 = (float)cos(dl->stopdot2/180*Q_PI);
			dl->stopdot = (float)cos(dl->stopdot/180*Q_PI);

			if (target[0])
			{	// point towards target
				e2 = FindTargetEntity (target);
				if (!e2)
					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);
				}
			}
			else
			{	// point down angle
				angle = (float)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] = (float)cos (angle/180*Q_PI);
					dl->normal[1] = (float)sin (angle/180*Q_PI);
				}

				angle = FloatForKey (e, "pitch");

				dl->normal[2] = (float)sin(angle/180*Q_PI);
				dl->normal[0] *= (float)cos(angle/180*Q_PI);
				dl->normal[1] *= (float)cos(angle/180*Q_PI);
			}
			if (FloatForKey( e, "_sky" ) || !strcmp(name, "light_environment")) 
			{
				dl->type = emit_skylight;
				dl->stopdot2 = FloatForKey( e, "_sky" ); // hack stopdot2 to a sky key number
			}
		}
		else
		{
			if (!VectorAvg( dl->intensity ))
				VectorFill( dl->intensity, 300 );
			dl->type = emit_point;
		}

		if (dl->type != emit_skylight)
		{
			l1 = max( dl->intensity[0], max( dl->intensity[1], dl->intensity[2] ) );
			l1 = l1 * l1 / 10;

			dl->intensity[0] *= l1;
			dl->intensity[1] *= l1;
			dl->intensity[2] *= l1;
		}


	}

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

/*
=============
DeleteDirectLights
=============
*/

void DeleteDirectLights(void)
{
int					l;
directlight_t		*dl;

for ( l = 0; l < numleafs; l++ )
	while ( dl = directlights[l] )
	{
		directlights[l] = dl->next;
		free(dl);
	}
}

/*
=============
GatherSampleLight
=============
*/
#define NUMVERTEXNORMALS	162
float	r_avertexnormals[NUMVERTEXNORMALS][3] = {
#include "..\..\engine\anorms.h"
};

#define VectorMaximum(a) ( max( (a)[0], max( (a)[1], (a)[2] ) ) )

void GatherSampleLight (vec3_t pos, byte *pvs, vec3_t normal, vec3_t *sample, byte *styles)
{
	int				i;
	directlight_t	*l;
	vec3_t			add;
	vec3_t			delta;
	float			dot, dot2;
	float			dist;
	float			ratio;
	int				style_index;
	directlight_t	*sky_used = NULL;

	for (i = 1 ; i<numleafs ; i++)
	{
		if ( (l = directlights[i]) && (pvs[ (i-1)>>3] & (1<<((i-1)&7))) )
		{
			for (; l ; l=l->next)
			{
				// skylights work fundamentally differently than normal lights
				if (l->type == emit_skylight)
				{
					// only allow one of each sky type to hit any given point
					if (sky_used)
						continue;
					sky_used = l;

					// make sure the angle is okay
					dot = -DotProduct( normal, l->normal );
					if (dot <= ON_EPSILON/10)
						continue;

					// search back to see if we can hit a sky brush
					VectorScale( l->normal, -10000, delta );
					VectorAdd( pos, delta, delta );
					if (TestLine_r (0, pos, delta) != CONTENTS_SKY)
						continue;	// occluded
					
					VectorScale(l->intensity, dot, add);
				}
				else
				{
					VectorSubtract (l->origin, pos, delta);
					dist = VectorNormalize (delta);
					dot = DotProduct (delta, normal);
					if (dot <= ON_EPSILON/10)
						continue;	// behind sample surface

					if (dist < 1.0)
						dist = 1.0;

					switch (l->type)
					{
						case emit_point:
							ratio = dot / (dist * dist);
							VectorScale(l->intensity, ratio, add);
							break;

						case emit_surface:
							dot2 = -DotProduct (delta, l->normal);
							if (dot2 <= ON_EPSILON/10)
								continue; // behind light surface
							ratio = dot * dot2 / (dist * dist);
							VectorScale(l->intensity, ratio, add);
							break;

						case emit_spotlight:
							dot2 = -DotProduct (delta, l->normal);
							if (dot2 <= l->stopdot2)
								continue; // outside light cone
							ratio = dot * dot2 / (dist * dist);
							if (dot2 <= l->stopdot)
								ratio *= (dot2 - l->stopdot2) / (l->stopdot - l->stopdot2);
							VectorScale(l->intensity, ratio, add);
							break;
						default:
							Error ("Bad l->type");
					}
				}

				if( VectorMaximum( add ) > ( l->style ? coring : 0 ) )
				{
					if ( l->type != emit_skylight && TestLine_r (0, pos, l->origin) != CONTENTS_EMPTY )
						continue;	// occluded


					for( style_index = 0; style_index < MAXLIGHTMAPS; style_index++ )
						if ( styles[style_index] == l->style || styles[style_index] == 255 )
							break;

					if ( style_index == MAXLIGHTMAPS )
					{
						printf ("WARNING: Too many direct light styles on a face(%f,%f,%f)\n", 
							pos[0], pos[1], pos[2] );
						continue;
					}
					
					if ( styles[style_index] == 255 )
						styles[style_index] = l->style;

					VectorAdd( sample[style_index], add, sample[style_index] );
				}
			}
		}
	}
	if (sky_used && indirect_sun != 0.0)
	{
		vec3_t total;
		int j;
		vec3_t sky_intensity;

		VectorScale( sky_used->intensity, indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity );

		total[0] = total[1] = total[2] = 0.0;
		for (j = 0; j < NUMVERTEXNORMALS; j++)
		{
			// make sure the angle is okay
			dot = -DotProduct( normal, r_avertexnormals[j] );
			if (dot <= ON_EPSILON/10)
				continue;

			// search back to see if we can hit a sky brush
			VectorScale( r_avertexnormals[j], -10000, delta );
			VectorAdd( pos, delta, delta );
			if (TestLine_r (0, pos, delta) != CONTENTS_SKY)
				continue;	// occluded
			
			VectorScale(sky_intensity, dot, add);
			VectorAdd(total, add, total);
		}
		if( VectorMaximum( total ) > 0 )
		{
			for( style_index = 0; style_index < MAXLIGHTMAPS; style_index++ )
				if ( styles[style_index] == sky_used->style || styles[style_index] == 255 )
					break;

			if ( style_index == MAXLIGHTMAPS )
			{
				printf ("WARNING: Too many direct light styles on a face(%f,%f,%f)\n", 
					pos[0], pos[1], pos[2] );
				return;
			}
			
			if ( styles[style_index] == 255 )
				styles[style_index] = sky_used->style;

			VectorAdd( sample[style_index], total, sample[style_index] );
		}
	}
}

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

Take the sample's collected light and
add it back into the apropriate patch
for the radiosity pass.
=============
*/
void AddSampleToPatch (sample_t *s, int facenum)
{
	patch_t	*patch;
	vec3_t	mins, maxs;
	int		i;

	if (numbounce == 0)
		return;
	if( VectorAvg( s->light ) < 1)
		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] > s->pos[i] + 16)
				goto nextpatch;
			if (maxs[i] < s->pos[i] - 16)
				goto nextpatch;
		}

		// add the sample to the patch
		patch->samples++;
		VectorAdd( patch->samplelight, s->light, patch->samplelight );
		//return;

nextpatch:;
	}

	// don't worry if some samples don't find a patch
}

void
GetPhongNormal( int facenum, vec3_t spot, vec3_t phongnormal )
{
	int	j;
	dface_t		*f = dfaces + facenum;
	dplane_t	*p = dplanes + f->planenum;
	vec3_t		facenormal;

	VectorCopy( p->normal, facenormal );
	if ( f->side )
		VectorSubtract( vec3_origin, facenormal, facenormal );
	VectorCopy( facenormal, phongnormal );

	if ( smoothing_threshold != 0 )
	{
		// Calculate modified point normal for surface
		// Use the edge normals iff they are defined.  Bend the surface towards the edge normal(s)
		// Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal.
		// Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric)
		// Better third attempt: generate the point normals for all vertices and do baricentric triangulation.

		for (j=0 ; j<f->numedges ; j++)
		{
			vec3_t	p1, p2, v1, v2, vspot;
			int e = dsurfedges[f->firstedge + j];
			int e1 = dsurfedges[f->firstedge + ((j-1)%f->numedges)];
			int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)];
			vec3_t	n1, n2;
			edgeshare_t	*es = &edgeshare[abs(e)];
			edgeshare_t	*es1 = &edgeshare[abs(e1)];
			edgeshare_t	*es2 = &edgeshare[abs(e2)];
			dface_t	*f2;
			float		a, a1, a2, d1, d2, aa, bb, ab;

			if ( es->coplanar && es1->coplanar && es2->coplanar 
			|| VectorCompare(es->interface_normal, vec3_origin)
			&& VectorCompare(es1->interface_normal, vec3_origin)
			&& VectorCompare(es2->interface_normal, vec3_origin) )
				continue;

			if (e > 0)
			{
				f2 = es->faces[1];
				VectorCopy( dvertexes[dedges[e].v[0]].point, p1 );
				VectorCopy( dvertexes[dedges[e].v[1]].point, p2 );
			}
			else
			{
				f2 = es->faces[0];
				VectorCopy( dvertexes[dedges[-e].v[1]].point, p1 );
				VectorCopy( dvertexes[dedges[-e].v[0]].point, p2 );
			}


			// Build vectors from the middle of the face to the edge vertexes and the sample pos.
			VectorSubtract( p1, face_centroids[facenum], v1 );
			VectorSubtract( p2, face_centroids[facenum], v2 );
			VectorSubtract( spot, face_centroids[facenum], vspot );
			aa = DotProduct( v1, v1 );
			bb = DotProduct( v2, v2 );
			ab = DotProduct( v1, v2 );
			a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab);
			a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb;

			// Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors)
			if ( a1 >= 0.0 && a2 >= 0.0)
			{
				// calculate distance from edge to pos
				vec3_t	temp;
				VectorAdd( es->interface_normal, es1->interface_normal, n1 );

				if ( VectorCompare( n1, vec3_origin ) )
					VectorCopy( facenormal, n1 );
				VectorNormalize(n1);

				VectorAdd( es->interface_normal, es2->interface_normal, n2 );

				if ( VectorCompare( n2, vec3_origin ) )
					VectorCopy( facenormal, n2 );
				VectorNormalize(n2);

				// Interpolate between the center and edge normals based on sample position
				VectorScale( facenormal, 1.0 - a1 - a2, phongnormal );
				VectorScale( n1, a1, temp );
				VectorAdd( phongnormal, temp, phongnormal );
				VectorScale( n2, a2, temp );
				VectorAdd( phongnormal, temp, phongnormal );
				VectorNormalize( phongnormal );
				break;

			}
		}
	}
}


/*
=============
BuildFacelights
=============
*/
void BuildFacelights (int facenum)
{
	dface_t		*f;
	vec3_t		sampled[MAXLIGHTMAPS];
	lightinfo_t	l;
	int			i, j, k;
	sample_t	*s;
	float		*spot;
	patch_t		*patch;
	byte		pvs[(MAX_MAP_LEAFS+7)/8];
    int         thisoffset = -1, lastoffset = -1;
	int			lightmapwidth, lightmapheight, size;
	vec3_t centroid = { 0, 0, 0 };

	f = &dfaces[facenum];

//
// some surfaces don't need lightmaps
//
	f->lightofs = -1;
	for (j=0 ; j<MAXLIGHTMAPS ; j++)
		f->styles[j] = 255;

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

	f->styles[0] = 0; // Everyone gets the style zero map.

	memset (&l, 0, sizeof(l));
	l.surfnum = facenum;
	l.face = f;

//
// rotate plane
//
	VectorCopy (dplanes[f->planenum].normal, l.facenormal);
	l.facedist = dplanes[f->planenum].dist;
	if (f->side)
	{
		VectorSubtract (vec3_origin, l.facenormal, l.facenormal);
		l.facedist = -l.facedist;
	}

	CalcFaceVectors (&l);
	CalcFaceExtents (&l);
	CalcPoints (&l);

	lightmapwidth = l.texsize[0]+1;
	lightmapheight = l.texsize[1]+1;

	size = lightmapwidth*lightmapheight;
	if (size > SINGLEMAP)
		Error ("Bad lightmap size");

	facelight[facenum].numsamples = l.numsurfpt;

	for (k=0 ; k<MAXLIGHTMAPS; k++)
		facelight[facenum].samples[k] = calloc(l.numsurfpt, sizeof(sample_t));

	spot = l.surfpt[0];
	for (i=0 ; i<l.numsurfpt ; i++, spot += 3)
	{
		vec3_t	pointnormal = {0,0,0};

		for (k=0 ; k<MAXLIGHTMAPS; k++)
			VectorCopy (spot, facelight[facenum].samples[k][i].pos);

	    // get the PVS for the pos to limit the number of checks
        if (!visdatasize)
        {       
            memset (pvs, 255, (numleafs+7)/8 );
            lastoffset = -1;
        }
        else 
        {
            dleaf_t *leaf = PointInLeaf( spot );
            thisoffset = leaf->visofs;
            if ( i == 0 || thisoffset != lastoffset )
            { 
                if (thisoffset == -1)
                        Error ("leaf->visofs == -1");

                DecompressVis (&dvisdata[leaf->visofs], pvs);
            }
            lastoffset = thisoffset;
        }

		for( j = 0; j < MAXLIGHTMAPS; j++)
			VectorFill( sampled[j], 0 );

		// If we are doing "extra" samples, oversample the direct light around the point.
		if ( extra )
		{
			int		weighting[3][3] = { { 5, 9, 5 }, { 9, 16, 9 }, { 5, 9, 5 } };
			vec3_t	pos;
			int		s, t, subsamples = 0;
			for ( t = -1; t <= 1; t ++ )
			{
				for ( s = -1; s <= 1; s++ )
				{
					int	subsample = i + t * lightmapwidth + s;
					int	sample_s = i % lightmapwidth;
					int	sample_t = i / lightmapwidth;
					if ( (0 <= s+sample_s) && (s+sample_s < lightmapwidth)
					  && (0 <= t+sample_t) && (t+sample_t < lightmapheight) )
					{
						vec3_t		subsampled[MAXLIGHTMAPS];
						for( j = 0; j < MAXLIGHTMAPS; j++)
							VectorFill( subsampled[j], 0 );
						// Calculate the point one third of the way toward the "subsample point"
						VectorCopy( l.surfpt[i], pos );
						VectorAdd( pos, l.surfpt[i], pos );
						VectorAdd( pos, l.surfpt[subsample], pos );
						VectorScale( pos, 1.0/3.0, pos );

						GetPhongNormal( facenum, pos, pointnormal );
						GatherSampleLight( pos, pvs, pointnormal, subsampled, f->styles );
						for( j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++)
							{
							VectorScale( subsampled[j], weighting[s+1][t+1], subsampled[j] );
							VectorAdd( sampled[j], subsampled[j], sampled[j] );
							}
						subsamples += weighting[s+1][t+1];
					}
				}
			}
			for( j=0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++ )
				VectorScale( sampled[j], 1.0/subsamples, sampled[j] );
		}
		else
		{
			GetPhongNormal( facenum, spot, pointnormal );
			GatherSampleLight( spot, pvs, pointnormal, sampled, f->styles );
		}

		for( j=0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++ )
		{
			VectorCopy (sampled[j], facelight[facenum].samples[j][i].light );
			if ( f->styles[j] == 0 )
			{
				AddSampleToPatch ( &facelight[facenum].samples[j][i], facenum);
			}
		}
	}

	// average up the direct light on each patch for radiosity
	if (numbounce > 0)
	{
		for (patch = face_patches[facenum] ; patch ; patch=patch->next)
		{
			if (patch->samples)
			{ 
				vec3_t v;		// BUGBUG: Use a weighted average instead?
				VectorScale( patch->samplelight, (1.0f/patch->samples), v );
				VectorAdd( patch->totallight, v, patch->totallight );
				VectorAdd( patch->directlight, v, patch->directlight );
			}
		}
	}

	// add an ambient term if desired
	if (ambient[0] || ambient[1] || ambient[2])
	{
		for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
		{
			if ( f->styles[j] == 0 )
			{
				s = facelight[facenum].samples[j];
				for (i=0 ; i<l.numsurfpt ; i++, s++)
					VectorAdd(s->light, ambient, s->light);
				break;
			}
		}

	}

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

	// if( VectorAvg( face_patches[facenum]->baselight ) >= dlight_threshold)	// Now all lighted surfaces glow
	{
		for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
		{
			if ( f->styles[j] == 0 )
			{
				s = facelight[facenum].samples[j];
				for (i=0 ; i<l.numsurfpt ; i++, s++)
					VectorAdd( s->light, face_patches[facenum]->baselight, s->light ); 
				break;
			}
		}
	}
}

/*
=============
ProgressiveRefinement

Progressive mesh refinement of the patches
=============
*/

int
ProgressiveRefinement()
{
	return 0;
}

/*
=============
PrecompLightmapOffsets
=============
*/

void
PrecompLightmapOffsets()
{
int			facenum;
dface_t		*f;
patch_t		*patch;
facelight_t	*fl;
int			lightstyles;

lightdatasize = 0;

for( facenum = 0; facenum < numfaces; facenum++ )
	{
	f = &dfaces[facenum];
	fl = &facelight[facenum];

	if ( texinfo[f->texinfo].flags & TEX_SPECIAL)
		continue;		// non-lit texture

	for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
		if ( f->styles[lightstyles] == 255 )
			break;

	if ( !lightstyles )
		continue;

	f->lightofs = lightdatasize;
	lightdatasize += fl->numsamples * 3 * lightstyles;
	}
}

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

Add the indirect lighting on top of the direct
lighting and save into final map format
=============
*/
void FinalLightFace (int facenum)
{
	dface_t	*f, *f2;
	int		i, j, k;
	vec3_t	lb, v;
	patch_t	*patch;
	triangulation_t	*trian;
	edgeshare_t	*es;
	int		edgenum;
	facelight_t	*fl;
	sample_t	*samp;
	triangle_t	*last_tri;
	float		minlight;
	int			lightstyles;

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

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

	for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
		if ( f->styles[lightstyles] == 255 )
			break;

	if ( !lightstyles )
		return;

	//
	// set up the triangulation
	//
	if (numbounce > 0)
	{
		trian = AllocTriangulation (&dplanes[f->planenum]);

		for (patch = face_patches[facenum] ; patch ; patch=patch->next)
			AddPatchToTriangulation (patch, trian);
		for (j=0 ; j<f->numedges ; j++)
		{
			edgenum = dsurfedges[f->firstedge + j];
			if (edgenum > 0)
			{
				es = &edgeshare[edgenum];
				f2 = es->faces[1];
			}
			else
			{
				es = &edgeshare[-edgenum];
				f2 = es->faces[0];
			}

			if (!es->coplanar && VectorCompare(vec3_origin, es->interface_normal) )
				continue;
			for (patch = face_patches[f2-dfaces] ; patch ; patch=patch->next)
				AddPatchToTriangulation (patch, trian);
		}

		TriangulatePoints (trian);
	}
	//
	// sample the triangulation
	//
	minlight = FloatForKey (face_entity[facenum], "_minlight") * 128;

	for (k=0 ; k < lightstyles; k++ )
	{
		last_tri = NULL;
		samp = fl->samples[k];
		for (j=0 ; j<fl->numsamples ; j++, samp++)
		{
			// Should be a VectorCopy, but we scale by 2 to compensate for an earlier lighting flaw
			// Specifically, the directlight contribution was included in the bounced light AND the directlight
			// Since many of the levels were built with this assumption, this "fudge factor" compensates for it.
			VectorScale( samp->light, 2.0, lb ); 

			if (numbounce > 0 && k == 0 )
			{
				SampleTriangulation (samp->pos, trian, &last_tri, v);
				VectorAdd( lb, v, lb );
			}

			VectorScale( lb, lightscale, lb );

			// clip from the bottom first
			for( i=0; i<3; i++ )
				if( lb[i] < minlight )
					lb[i] = minlight;

			// clip from the top
			if( lb[0]>maxlight || lb[1]>maxlight || lb[2]>maxlight )
			{
				// find max value and scale the whole color down;
				float max = lb[0] > lb[1] ? lb[0] : lb[1];
				max = max > lb[2] ? max : lb[2];

				for( i=0; i<3; i++ )
					lb[i] = ( lb[i] * maxlight ) / max;
			}

			// gamma adjust
			if (gamma != 1.0)
				for( i=0; i<3; i++ )
					lb[i] = (float)pow( lb[i] / 256.0f, gamma ) * 256.0f;

			dlightdata[f->lightofs + k*fl->numsamples*3 + j*3] = (unsigned char)lb[0];
			dlightdata[f->lightofs + k*fl->numsamples*3 + j*3 + 1] = (unsigned char)lb[1];
			dlightdata[f->lightofs + k*fl->numsamples*3 + j*3 + 2] = (unsigned char)lb[2];
		}
	}

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