text: ﻿/***
  *
  *	Copyright (c) 1996-2002, 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
  				vec3_t vAngles;
  				GetVectorForKey( e, "angles", vAngles );
  
  				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
  				{
  					// if we don't have a specific "angle" use the "angles" YAW
  					if ( !angle )
  					{
  						angle = vAngles[1];
  					}
  
  					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");
  				if ( !angle )
  				{
  					// if we don't have a specific "pitch" use the "angles" PITCH
  					angle = vAngles[0];
  				}
  
  				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);
  }
  