/*
   Copyright (C) 1999-2007 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_FPrintf( SYS_WRN, "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 );
	}
}
