/* -------------------------------------------------------------------------------

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

----------------------------------------------------------------------------------

This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."

------------------------------------------------------------------------------- */



/* marker */
#define LIGHT_YDNAR_C



/* dependencies */
#include "q3map2.h"




/*
ColorToBytes()
ydnar: moved to here 2001-02-04
*/

void ColorToBytes(const float *color, byte * colorBytes, float scale)
{
	int             i;
	float           max, gamma;
	vec3_t          sample;
	float           inv, dif;


	/* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */
	if(scale <= 0.0f)
		scale = 1.0f;

	/* make a local copy */
	VectorScale(color, scale, sample);

	/* muck with it */
	gamma = 1.0f / lightmapGamma;
	for(i = 0; i < 3; i++)
	{
		/* handle negative light */
		if(sample[i] < 0.0f)
		{
			sample[i] = 0.0f;
			continue;
		}

		/* gamma */
		sample[i] = pow(sample[i] / 255.0f, gamma) * 255.0f;
	}

	if(lightmapExposure == 1)
	{
		/* clamp with color normalization */
		max = sample[0];
		if(sample[1] > max)
			max = sample[1];
		if(sample[2] > max)
			max = sample[2];
		if(max > 255.0f)
			VectorScale(sample, (255.0f / max), sample);
	}
	else
	{
		if(lightmapExposure == 0)
		{
			lightmapExposure = 1.0f;
		}
		inv = 1.f / lightmapExposure;
		//Exposure

		max = sample[0];
		if(sample[1] > max)
			max = sample[1];
		if(sample[2] > max)
			max = sample[2];

		dif = (1 - exp(-max * inv)) * 255;

		if(max > 0)
		{
			dif = dif / max;
		}
		else
		{
			dif = 0;
		}

		for(i = 0; i < 3; i++)
		{
			sample[i] *= dif;
		}
	}


	/* compensate for ingame overbrighting/bitshifting */
	VectorScale(sample, (1.0f / lightmapCompensate), sample);

	/* store it off */
	colorBytes[0] = sample[0];
	colorBytes[1] = sample[1];
	colorBytes[2] = sample[2];
}

void ColorToFloats(const float *color, float * colorFloats, float scale)
{
	int             i;
	float           max, gamma;
	vec3_t          sample;
	float           inv, dif;


	/* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */
	if(scale <= 0.0f)
		scale = 1.0f;

	/* make a local copy */
	VectorScale(color, scale, sample);

	/* muck with it */
	gamma = 1.0f / lightmapGamma;
	for(i = 0; i < 3; i++)
	{
		/* handle negative light */
		if(sample[i] < 0.0f)
		{
			sample[i] = 0.0f;
			continue;
		}

		/* gamma */
		sample[i] = pow(sample[i] / 255.0f, gamma) * 255.0f;
	}

	if(lightmapExposure == 1)
	{
		/* clamp with color normalization */
		max = sample[0];
		if(sample[1] > max)
			max = sample[1];
		if(sample[2] > max)
			max = sample[2];
		if(max > 255.0f)
			VectorScale(sample, (255.0f / max), sample);
	}
	else
	{
		if(lightmapExposure == 0)
		{
			lightmapExposure = 1.0f;
		}
		inv = 1.f / lightmapExposure;
		//Exposure

		max = sample[0];
		if(sample[1] > max)
			max = sample[1];
		if(sample[2] > max)
			max = sample[2];

		dif = (1 - exp(-max * inv)) * 255;

		if(max > 0)
		{
			dif = dif / max;
		}
		else
		{
			dif = 0;
		}

		for(i = 0; i < 3; i++)
		{
			sample[i] *= dif;
		}
	}


	/* compensate for ingame overbrighting/bitshifting */
	VectorScale(sample, (1.0f / lightmapCompensate), sample);

	/* store it off */
	colorFloats[0] = sample[0];// * (1.0f / 255.0f);
	colorFloats[1] = sample[1];// * (1.0f / 255.0f);
	colorFloats[2] = sample[2];// * (1.0f / 255.0f);
}

/*
ColorToRGBE()
Tr3B: standard conversion from float pixels to rgbe pixels
*/

void ColorToRGBE(const float *color, unsigned char rgbe[4])
{
	vec3_t          sample;
	float			maxComponent;
	int				e;

	VectorCopy(color, sample);

	maxComponent = sample[0];
	if(sample[1] > maxComponent)
		maxComponent = sample[1];
	if(sample[2] > maxComponent)
		maxComponent = sample[2];

	if(maxComponent < 1e-32)
	{
		rgbe[0] = 0;
		rgbe[1] = 0;
		rgbe[2] = 0;
		rgbe[3] = 0;
	}
	else
	{
#if 1
		maxComponent = frexp(maxComponent, &e) * 255.0 / maxComponent;
		rgbe[0] = (unsigned char) (sample[0] * maxComponent);
		rgbe[1] = (unsigned char) (sample[1] * maxComponent);
		rgbe[2] = (unsigned char) (sample[2] * maxComponent);
		rgbe[3] = (unsigned char) (e + 128);
#else
		e = ceil(log2(maxComponent));
		VectorScale(sample, 1.0 / exp2(e), sample);

		rgbe[0] = (unsigned char) (sample[0] * 255);
		rgbe[1] = (unsigned char) (sample[1] * 255);
		rgbe[2] = (unsigned char) (sample[2] * 255);
		rgbe[3] = (unsigned char) (e + 128);
#endif
	}
}

/* -------------------------------------------------------------------------------

this section deals with phong shading (normal interpolation across brush faces)

------------------------------------------------------------------------------- */

/*
SmoothNormals()
smooths together coincident vertex normals across the bsp
*/

#define MAX_SAMPLES				256
#define THETA_EPSILON			0.000001
#define EQUAL_NORMAL_EPSILON	0.01

void SmoothNormals(void)
{
	int             i, j, k, f, cs, numVerts, numVotes, fOld, start;
	float           shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle;
	bspDrawSurface_t *ds;
	shaderInfo_t   *si;
	float          *shadeAngles;
	byte           *smoothed;
	vec3_t          average, diff;
	int             indexes[MAX_SAMPLES];
	vec3_t          votes[MAX_SAMPLES];


	/* allocate shade angle table */
	shadeAngles = safe_malloc(numBSPDrawVerts * sizeof(float));
	memset(shadeAngles, 0, numBSPDrawVerts * sizeof(float));

	/* allocate smoothed table */
	cs = (numBSPDrawVerts / 8) + 1;
	smoothed = safe_malloc(cs);
	memset(smoothed, 0, cs);

	/* set default shade angle */
	defaultShadeAngle = DEG2RAD(shadeAngleDegrees);
	maxShadeAngle = 0;

	/* run through every surface and flag verts belonging to non-lightmapped surfaces
	   and set per-vertex smoothing angle */
	for(i = 0; i < numBSPDrawSurfaces; i++)
	{
		/* get drawsurf */
		ds = &bspDrawSurfaces[i];

		/* get shader for shade angle */
		si = surfaceInfos[i].si;
		if(si->shadeAngleDegrees)
			shadeAngle = DEG2RAD(si->shadeAngleDegrees);
		else
			shadeAngle = defaultShadeAngle;
		if(shadeAngle > maxShadeAngle)
			maxShadeAngle = shadeAngle;

		/* flag its verts */
		for(j = 0; j < ds->numVerts; j++)
		{
			f = ds->firstVert + j;
			shadeAngles[f] = shadeAngle;
			if(ds->surfaceType == MST_TRIANGLE_SOUP)
				smoothed[f >> 3] |= (1 << (f & 7));
		}

		/* ydnar: optional force-to-trisoup */
		if(trisoup && ds->surfaceType == MST_PLANAR)
		{
			ds->surfaceType = MST_TRIANGLE_SOUP;
			ds->lightmapNum[0] = -3;
		}
	}

	/* bail if no surfaces have a shade angle */
	if(maxShadeAngle == 0)
	{
		free(shadeAngles);
		free(smoothed);
		return;
	}

	/* init pacifier */
	fOld = -1;
	start = I_FloatTime();

	/* go through the list of vertexes */
	for(i = 0; i < numBSPDrawVerts; i++)
	{
		/* print pacifier */
		f = 10 * i / numBSPDrawVerts;
		if(f != fOld)
		{
			fOld = f;
			Sys_Printf("%i...", f);
		}

		/* already smoothed? */
		if(smoothed[i >> 3] & (1 << (i & 7)))
			continue;

		/* clear */
		VectorClear(average);
		numVerts = 0;
		numVotes = 0;

		/* build a table of coincident vertexes */
		for(j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++)
		{
			/* already smoothed? */
			if(smoothed[j >> 3] & (1 << (j & 7)))
				continue;

			/* test vertexes */
			if(VectorCompare(yDrawVerts[i].xyz, yDrawVerts[j].xyz) == qfalse)
				continue;

			/* use smallest shade angle */
			shadeAngle = (shadeAngles[i] < shadeAngles[j] ? shadeAngles[i] : shadeAngles[j]);

			/* check shade angle */
			dot = DotProduct(bspDrawVerts[i].normal, bspDrawVerts[j].normal);
			if(dot > 1.0)
				dot = 1.0;
			else if(dot < -1.0)
				dot = -1.0;
			testAngle = acos(dot) + THETA_EPSILON;
			if(testAngle >= shadeAngle)
			{
				//Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
				continue;
			}
			//Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );

			/* add to the list */
			indexes[numVerts++] = j;

			/* flag vertex */
			smoothed[j >> 3] |= (1 << (j & 7));

			/* see if this normal has already been voted */
			for(k = 0; k < numVotes; k++)
			{
				VectorSubtract(bspDrawVerts[j].normal, votes[k], diff);
				if(fabs(diff[0]) < EQUAL_NORMAL_EPSILON &&
				   fabs(diff[1]) < EQUAL_NORMAL_EPSILON && fabs(diff[2]) < EQUAL_NORMAL_EPSILON)
					break;
			}

			/* add a new vote? */
			if(k == numVotes && numVotes < MAX_SAMPLES)
			{
				VectorAdd(average, bspDrawVerts[j].normal, average);
				VectorCopy(bspDrawVerts[j].normal, votes[numVotes]);
				numVotes++;
			}
		}

		/* don't average for less than 2 verts */
		if(numVerts < 2)
			continue;

		/* average normal */
		if(VectorNormalize(average) > 0)
		{
			/* smooth */
			for(j = 0; j < numVerts; j++)
				VectorCopy(average, yDrawVerts[indexes[j]].normal);
		}
	}

	/* free the tables */
	free(shadeAngles);
	free(smoothed);

	/* print time */
	Sys_Printf(" (%i)\n", (int)(I_FloatTime() - start));
}



/* -------------------------------------------------------------------------------

this section deals with phong shaded lightmap tracing

------------------------------------------------------------------------------- */

/* 9th rewrite (recursive subdivision of a lightmap triangle) */

/*
CalcTangentVectors()
calculates the st tangent vectors for normalmapping
*/

static qboolean CalcTangentVectors(int numVerts, bspDrawVert_t ** dv, vec3_t * stv, vec3_t * ttv)
{
	int             i;
	float           bb, s, t;
	vec3_t          bary;


	/* calculate barycentric basis for the triangle */
	bb = (dv[1]->st[0] - dv[0]->st[0]) * (dv[2]->st[1] - dv[0]->st[1]) - (dv[2]->st[0] - dv[0]->st[0]) * (dv[1]->st[1] -
																										  dv[0]->st[1]);
	if(fabs(bb) < 0.00000001f)
		return qfalse;

	/* do each vertex */
	for(i = 0; i < numVerts; i++)
	{
		/* calculate s tangent vector */
		s = dv[i]->st[0] + 10.0f;
		t = dv[i]->st[1];
		bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb;
		bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb;
		bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb;

		stv[i][0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
		stv[i][1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
		stv[i][2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];

		VectorSubtract(stv[i], dv[i]->xyz, stv[i]);
		VectorNormalize(stv[i]);

		/* calculate t tangent vector */
		s = dv[i]->st[0];
		t = dv[i]->st[1] + 10.0f;
		bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb;
		bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb;
		bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb;

		ttv[i][0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
		ttv[i][1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
		ttv[i][2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];

		VectorSubtract(ttv[i], dv[i]->xyz, ttv[i]);
		VectorNormalize(ttv[i]);

		/* debug code */
		//% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i,
		//%     stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] );
	}

	/* return to caller */
	return qtrue;
}




/*
PerturbNormal()
perturbs the normal by the shader's normalmap in tangent space
*/

static void PerturbNormal(bspDrawVert_t * dv, shaderInfo_t * si, vec3_t pNormal, vec3_t stv[3], vec3_t ttv[3])
{
	int             i;
	vec4_t          bump;


	/* passthrough */
	VectorCopy(dv->normal, pNormal);

	/* sample normalmap */
	if(RadSampleImage(si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump) == qfalse)
		return;

	/* remap sampled normal from [0,255] to [-1,-1] */
	for(i = 0; i < 3; i++)
		bump[i] = (bump[i] - 127.0f) * (1.0f / 127.5f);

	/* scale tangent vectors and add to original normal */
	VectorMA(dv->normal, bump[0], stv[0], pNormal);
	VectorMA(pNormal, bump[1], ttv[0], pNormal);
	VectorMA(pNormal, bump[2], dv->normal, pNormal);

	/* renormalize and return */
	VectorNormalize(pNormal);
}



/*
MapSingleLuxel()
maps a luxel for triangle bv at
*/

#define NUDGE			0.5f
#define BOGUS_NUDGE		-99999.0f

static int MapSingleLuxel(rawLightmap_t * lm, surfaceInfo_t * info, bspDrawVert_t * dv, vec4_t plane, float pass, vec3_t stv[3],
						  vec3_t ttv[3], vec3_t worldverts[3])
{
	int             i, x, y, numClusters, *clusters, pointCluster, *cluster;
	float          *luxel, *origin, *normal, d, lightmapSampleOffset;
	shaderInfo_t   *si;
	vec3_t          pNormal;
	vec3_t          vecs[3];
	vec3_t          nudged;
	vec3_t          cverts[3];
	vec3_t          temp;
	vec4_t          sideplane, hostplane;
	vec3_t          origintwo;
	int             j, next;
	float           e;
	float          *nudge;
	static float    nudges[][2] = {
		//%{ 0, 0 },        /* try center first */
		{-NUDGE, 0},			/* left */
		{NUDGE, 0},				/* right */
		{0, NUDGE},				/* up */
		{0, -NUDGE},			/* down */
		{-NUDGE, NUDGE},		/* left/up */
		{NUDGE, -NUDGE},		/* right/down */
		{NUDGE, NUDGE},			/* right/up */
		{-NUDGE, -NUDGE},		/* left/down */
		{BOGUS_NUDGE, BOGUS_NUDGE}
	};


	/* find luxel xy coords (fixme: subtract 0.5?) */
	x = dv->lightmap[0][0];
	y = dv->lightmap[0][1];
	if(x < 0)
		x = 0;
	else if(x >= lm->sw)
		x = lm->sw - 1;
	if(y < 0)
		y = 0;
	else if(y >= lm->sh)
		y = lm->sh - 1;

	/* set shader and cluster list */
	if(info != NULL)
	{
		si = info->si;
		numClusters = info->numSurfaceClusters;
		clusters = &surfaceClusters[info->firstSurfaceCluster];
	}
	else
	{
		si = NULL;
		numClusters = 0;
		clusters = NULL;
	}

	/* get luxel, origin, cluster, and normal */
	luxel = SUPER_LUXEL(0, x, y);
	origin = SUPER_ORIGIN(x, y);
	normal = SUPER_NORMAL(x, y);
	cluster = SUPER_CLUSTER(x, y);

	/* don't attempt to remap occluded luxels for planar surfaces */
	if((*cluster) == CLUSTER_OCCLUDED && lm->plane != NULL)
		return (*cluster);

	/* only average the normal for premapped luxels */
	else if((*cluster) >= 0)
	{
		/* do bumpmap calculations */
		if(stv != NULL)
			PerturbNormal(dv, si, pNormal, stv, ttv);
		else
			VectorCopy(dv->normal, pNormal);

		/* add the additional normal data */
		VectorAdd(normal, pNormal, normal);
		luxel[3] += 1.0f;
		return (*cluster);
	}

	/* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */

	/* get origin */

	/* axial lightmap projection */
	if(lm->vecs != NULL)
	{
		/* calculate an origin for the sample from the lightmap vectors */
		VectorCopy(lm->origin, origin);
		for(i = 0; i < 3; i++)
		{
			/* add unless it's the axis, which is taken care of later */
			if(i == lm->axisNum)
				continue;
			origin[i] += (x * lm->vecs[0][i]) + (y * lm->vecs[1][i]);
		}

		/* project the origin onto the plane */
		d = DotProduct(origin, plane) - plane[3];
		d /= plane[lm->axisNum];
		origin[lm->axisNum] -= d;
	}

	/* non axial lightmap projection (explicit xyz) */
	else
		VectorCopy(dv->xyz, origin);

	//////////////////////
	//27's test to make sure samples stay within the triangle boundaries
	//1) Test the sample origin to see if it lays on the wrong side of any edge (x/y)
	//2) if it does, nudge it onto the correct side.

	if(worldverts != NULL && lightmapTriangleCheck)
	{
		for(j = 0; j < 3; j++)
		{
			VectorCopy(worldverts[j], cverts[j]);
		}
		PlaneFromPoints(hostplane, cverts[0], cverts[1], cverts[2], qtrue);

		for(j = 0; j < 3; j++)
		{
			for(i = 0; i < 3; i++)
			{
				//build plane using 2 edges and a normal
				next = (i + 1) % 3;

				VectorCopy(cverts[next], temp);
				VectorAdd(temp, hostplane, temp);
				PlaneFromPoints(sideplane, cverts[i], cverts[next], temp, qtrue);

				//planetest sample point
				e = DotProduct(origin, sideplane);
				e = e - sideplane[3];
				if(e > 0)
				{
					//we're bad.
					//VectorClear(origin);
					//Move the sample point back inside triangle bounds
					origin[0] -= sideplane[0] * (e + 1);
					origin[1] -= sideplane[1] * (e + 1);
					origin[2] -= sideplane[2] * (e + 1);
#ifdef DEBUG_27_1
					VectorClear(origin);
#endif
				}
			}
		}
	}

	////////////////////////

	/* planar surfaces have precalculated lightmap vectors for nudging */
	if(lm->plane != NULL)
	{
		VectorCopy(lm->vecs[0], vecs[0]);
		VectorCopy(lm->vecs[1], vecs[1]);
		VectorCopy(lm->plane, vecs[2]);
	}

	/* non-planar surfaces must calculate them */
	else
	{
		if(plane != NULL)
			VectorCopy(plane, vecs[2]);
		else
			VectorCopy(dv->normal, vecs[2]);
		MakeNormalVectors(vecs[2], vecs[0], vecs[1]);
	}

	/* push the origin off the surface a bit */
	if(si != NULL)
		lightmapSampleOffset = si->lightmapSampleOffset;
	else
		lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET;
	if(lm->axisNum < 0)
		VectorMA(origin, lightmapSampleOffset, vecs[2], origin);
	else if(vecs[2][lm->axisNum] < 0.0f)
		origin[lm->axisNum] -= lightmapSampleOffset;
	else
		origin[lm->axisNum] += lightmapSampleOffset;

	VectorCopy(origin, origintwo);
	if(lightmapExtraVisClusterNudge)
	{
		origintwo[0] += vecs[2][0];
		origintwo[1] += vecs[2][1];
		origintwo[2] += vecs[2][2];
	}

	/* get cluster */
	pointCluster = ClusterForPointExtFilter(origintwo, LUXEL_EPSILON, numClusters, clusters);

	/* another retarded hack, storing nudge count in luxel[ 1 ] */
	luxel[1] = 0.0f;

	/* point in solid? (except in dark mode) */
	if(pointCluster < 0 && dark == qfalse)
	{
		/* nudge the location around */
		nudge = nudges[0];
		while(nudge[0] > BOGUS_NUDGE && pointCluster < 0)
		{
			/* nudge the vector around a bit */
			for(i = 0; i < 3; i++)
			{
				/* set nudged point */
				nudged[i] = origintwo[i] + (nudge[0] * vecs[0][i]) + (nudge[1] * vecs[1][i]);
			}
			nudge += 2;

			/* get pvs cluster */
			pointCluster = ClusterForPointExtFilter(nudged, LUXEL_EPSILON, numClusters, clusters);	//% + 0.625 );
			if(pointCluster >= 0)
				VectorCopy(nudged, origin);
			luxel[1] += 1.0f;
		}
	}

	/* as a last resort, if still in solid, try drawvert origin offset by normal (except in dark mode) */
	if(pointCluster < 0 && si != NULL && dark == qfalse)
	{
		VectorMA(dv->xyz, lightmapSampleOffset, dv->normal, nudged);
		pointCluster = ClusterForPointExtFilter(nudged, LUXEL_EPSILON, numClusters, clusters);
		if(pointCluster >= 0)
			VectorCopy(nudged, origin);
		luxel[1] += 1.0f;
	}

	/* valid? */
	if(pointCluster < 0)
	{
		(*cluster) = CLUSTER_OCCLUDED;
		VectorClear(origin);
		VectorClear(normal);
		numLuxelsOccluded++;
		return (*cluster);
	}

	/* debug code */
	//% Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] );

	/* do bumpmap calculations */
	if(stv)
		PerturbNormal(dv, si, pNormal, stv, ttv);
	else
		VectorCopy(dv->normal, pNormal);

	/* store the cluster and normal */
	(*cluster) = pointCluster;
	VectorCopy(pNormal, normal);

	/* store explicit mapping pass and implicit mapping pass */
	luxel[0] = pass;
	luxel[3] = 1.0f;

	/* add to count */
	numLuxelsMapped++;

	/* return ok */
	return (*cluster);
}



/*
MapTriangle_r()
recursively subdivides a triangle until its edges are shorter
than the distance between two luxels (thanks jc :)
*/

static void MapTriangle_r(rawLightmap_t * lm, surfaceInfo_t * info, bspDrawVert_t * dv[3], vec4_t plane, vec3_t stv[3],
						  vec3_t ttv[3], vec3_t worldverts[3])
{
	bspDrawVert_t   mid, *dv2[3];
	int             max;


	/* map the vertexes */
#if 0
	MapSingleLuxel(lm, info, dv[0], plane, 1, stv, ttv);
	MapSingleLuxel(lm, info, dv[1], plane, 1, stv, ttv);
	MapSingleLuxel(lm, info, dv[2], plane, 1, stv, ttv);
#endif

	/* subdivide calc */
	{
		int             i;
		float          *a, *b, dx, dy, dist, maxDist;


		/* find the longest edge and split it */
		max = -1;
		maxDist = 0;
		for(i = 0; i < 3; i++)
		{
			/* get verts */
			a = dv[i]->lightmap[0];
			b = dv[(i + 1) % 3]->lightmap[0];

			/* get dists */
			dx = a[0] - b[0];
			dy = a[1] - b[1];
			dist = (dx * dx) + (dy * dy);	//% sqrt( (dx * dx) + (dy * dy) );

			/* longer? */
			if(dist > maxDist)
			{
				maxDist = dist;
				max = i;
			}
		}

		/* try to early out */
		if(max < 0 || maxDist <= subdivideThreshold)	/* ydnar: was i < 0 instead of max < 0 (?) */
			return;
	}

	/* split the longest edge and map it */
	LerpDrawVert(dv[max], dv[(max + 1) % 3], &mid);
	MapSingleLuxel(lm, info, &mid, plane, 1, stv, ttv, worldverts);

	/* push the point up a little bit to account for fp creep (fixme: revisit this) */
	//% VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz );

	/* recurse to first triangle */
	VectorCopy(dv, dv2);
	dv2[max] = &mid;
	MapTriangle_r(lm, info, dv2, plane, stv, ttv, worldverts);

	/* recurse to second triangle */
	VectorCopy(dv, dv2);
	dv2[(max + 1) % 3] = &mid;
	MapTriangle_r(lm, info, dv2, plane, stv, ttv, worldverts);
}



/*
MapTriangle()
seed function for MapTriangle_r()
requires a cw ordered triangle
*/

static qboolean MapTriangle(rawLightmap_t * lm, surfaceInfo_t * info, bspDrawVert_t * dv[3], qboolean mapNonAxial)
{
	int             i;
	vec4_t          plane;
	vec3_t         *stv, *ttv, stvStatic[3], ttvStatic[3];
	vec3_t          worldverts[3];


	/* get plane if possible */
	if(lm->plane != NULL)
	{
		VectorCopy(lm->plane, plane);
		plane[3] = lm->plane[3];
	}

	/* otherwise make one from the points */
	else if(PlaneFromPoints(plane, dv[0]->xyz, dv[1]->xyz, dv[2]->xyz, qtrue) == qfalse)
		return qfalse;

	/* check to see if we need to calculate texture->world tangent vectors */
	if(info->si->normalImage != NULL && CalcTangentVectors(3, dv, stvStatic, ttvStatic))
	{
		stv = stvStatic;
		ttv = ttvStatic;
	}
	else
	{
		stv = NULL;
		ttv = NULL;
	}

	VectorCopy(dv[0]->xyz, worldverts[0]);
	VectorCopy(dv[1]->xyz, worldverts[1]);
	VectorCopy(dv[2]->xyz, worldverts[2]);

	/* map the vertexes */
	MapSingleLuxel(lm, info, dv[0], plane, 1, stv, ttv, worldverts);
	MapSingleLuxel(lm, info, dv[1], plane, 1, stv, ttv, worldverts);
	MapSingleLuxel(lm, info, dv[2], plane, 1, stv, ttv, worldverts);

	/* 2002-11-20: prefer axial triangle edges */
	if(mapNonAxial)
	{
		/* subdivide the triangle */
		MapTriangle_r(lm, info, dv, plane, stv, ttv, worldverts);
		return qtrue;
	}

	for(i = 0; i < 3; i++)
	{
		float          *a, *b;
		bspDrawVert_t  *dv2[3];


		/* get verts */
		a = dv[i]->lightmap[0];
		b = dv[(i + 1) % 3]->lightmap[0];

		/* make degenerate triangles for mapping edges */
		if(fabs(a[0] - b[0]) < 0.01f || fabs(a[1] - b[1]) < 0.01f)
		{
			dv2[0] = dv[i];
			dv2[1] = dv[(i + 1) % 3];
			dv2[2] = dv[(i + 1) % 3];

			/* map the degenerate triangle */
			MapTriangle_r(lm, info, dv2, plane, stv, ttv, worldverts);
		}
	}

	return qtrue;
}



/*
MapQuad_r()
recursively subdivides a quad until its edges are shorter
than the distance between two luxels
*/

static void MapQuad_r(rawLightmap_t * lm, surfaceInfo_t * info, bspDrawVert_t * dv[4], vec4_t plane, vec3_t stv[4], vec3_t ttv[4])
{
	bspDrawVert_t   mid[2], *dv2[4];
	int             max;


	/* subdivide calc */
	{
		int             i;
		float          *a, *b, dx, dy, dist, maxDist;


		/* find the longest edge and split it */
		max = -1;
		maxDist = 0;
		for(i = 0; i < 4; i++)
		{
			/* get verts */
			a = dv[i]->lightmap[0];
			b = dv[(i + 1) % 4]->lightmap[0];

			/* get dists */
			dx = a[0] - b[0];
			dy = a[1] - b[1];
			dist = (dx * dx) + (dy * dy);	//% sqrt( (dx * dx) + (dy * dy) );

			/* longer? */
			if(dist > maxDist)
			{
				maxDist = dist;
				max = i;
			}
		}

		/* try to early out */
		if(max < 0 || maxDist <= subdivideThreshold)
			return;
	}

	/* we only care about even/odd edges */
	max &= 1;

	/* split the longest edges */
	LerpDrawVert(dv[max], dv[(max + 1) % 4], &mid[0]);
	LerpDrawVert(dv[max + 2], dv[(max + 3) % 4], &mid[1]);

	/* map the vertexes */
	MapSingleLuxel(lm, info, &mid[0], plane, 1, stv, ttv, NULL);
	MapSingleLuxel(lm, info, &mid[1], plane, 1, stv, ttv, NULL);

	/* 0 and 2 */
	if(max == 0)
	{
		/* recurse to first quad */
		dv2[0] = dv[0];
		dv2[1] = &mid[0];
		dv2[2] = &mid[1];
		dv2[3] = dv[3];
		MapQuad_r(lm, info, dv2, plane, stv, ttv);

		/* recurse to second quad */
		dv2[0] = &mid[0];
		dv2[1] = dv[1];
		dv2[2] = dv[2];
		dv2[3] = &mid[1];
		MapQuad_r(lm, info, dv2, plane, stv, ttv);
	}

	/* 1 and 3 */
	else
	{
		/* recurse to first quad */
		dv2[0] = dv[0];
		dv2[1] = dv[1];
		dv2[2] = &mid[0];
		dv2[3] = &mid[1];
		MapQuad_r(lm, info, dv2, plane, stv, ttv);

		/* recurse to second quad */
		dv2[0] = &mid[1];
		dv2[1] = &mid[0];
		dv2[2] = dv[2];
		dv2[3] = dv[3];
		MapQuad_r(lm, info, dv2, plane, stv, ttv);
	}
}



/*
MapQuad()
seed function for MapQuad_r()
requires a cw ordered triangle quad
*/

#define QUAD_PLANAR_EPSILON		0.5f

static qboolean MapQuad(rawLightmap_t * lm, surfaceInfo_t * info, bspDrawVert_t * dv[4])
{
	float           dist;
	vec4_t          plane;
	vec3_t         *stv, *ttv, stvStatic[4], ttvStatic[4];


	/* get plane if possible */
	if(lm->plane != NULL)
	{
		VectorCopy(lm->plane, plane);
		plane[3] = lm->plane[3];
	}

	/* otherwise make one from the points */
	else if(PlaneFromPoints(plane, dv[0]->xyz, dv[1]->xyz, dv[2]->xyz, qtrue) == qfalse)
		return qfalse;

	/* 4th point must fall on the plane */
	dist = DotProduct(plane, dv[3]->xyz) - plane[3];
	if(fabs(dist) > QUAD_PLANAR_EPSILON)
		return qfalse;

	/* check to see if we need to calculate texture->world tangent vectors */
	if(info->si->normalImage != NULL && CalcTangentVectors(4, dv, stvStatic, ttvStatic))
	{
		stv = stvStatic;
		ttv = ttvStatic;
	}
	else
	{
		stv = NULL;
		ttv = NULL;
	}

	/* map the vertexes */
	MapSingleLuxel(lm, info, dv[0], plane, 1, stv, ttv, NULL);
	MapSingleLuxel(lm, info, dv[1], plane, 1, stv, ttv, NULL);
	MapSingleLuxel(lm, info, dv[2], plane, 1, stv, ttv, NULL);
	MapSingleLuxel(lm, info, dv[3], plane, 1, stv, ttv, NULL);

	/* subdivide the quad */
	MapQuad_r(lm, info, dv, plane, stv, ttv);
	return qtrue;
}



/*
MapRawLightmap()
maps the locations, normals, and pvs clusters for a raw lightmap
*/

#define VectorDivide( in, d, out )	VectorScale( in, (1.0f / (d)), out )	//% (out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d)

void MapRawLightmap(int rawLightmapNum)
{
	int             n, num, i, x, y, sx, sy, pw[5], r, *cluster, mapNonAxial;
	float          *luxel, *origin, *normal, samples, radius, pass;
	rawLightmap_t  *lm;
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info;
	mesh_t          src, *subdivided, *mesh;
	bspDrawVert_t  *verts, *dv[4], fake;


	/* bail if this number exceeds the number of raw lightmaps */
	if(rawLightmapNum >= numRawLightmaps)
		return;

	/* get lightmap */
	lm = &rawLightmaps[rawLightmapNum];

	/* -----------------------------------------------------------------
	   map referenced surfaces onto the raw lightmap
	   ----------------------------------------------------------------- */

	/* walk the list of surfaces on this raw lightmap */
	for(n = 0; n < lm->numLightSurfaces; n++)
	{
		/* with > 1 surface per raw lightmap, clear occluded */
		if(n > 0)
		{
			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get cluster */
					cluster = SUPER_CLUSTER(x, y);
					if(*cluster < 0)
						*cluster = CLUSTER_UNMAPPED;
				}
			}
		}

		/* get surface */
		num = lightSurfaces[lm->firstLightSurface + n];
		ds = &bspDrawSurfaces[num];
		info = &surfaceInfos[num];

		/* bail if no lightmap to calculate */
		if(info->lm != lm)
		{
			Sys_Printf("!");
			continue;
		}

		/* map the surface onto the lightmap origin/cluster/normal buffers */
		switch (ds->surfaceType)
		{
			case MST_PLANAR:
				/* get verts */
				verts = yDrawVerts + ds->firstVert;

				/* map the triangles */
				for(mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++)
				{
					for(i = 0; i < ds->numIndexes; i += 3)
					{
						dv[0] = &verts[bspDrawIndexes[ds->firstIndex + i]];
						dv[1] = &verts[bspDrawIndexes[ds->firstIndex + i + 1]];
						dv[2] = &verts[bspDrawIndexes[ds->firstIndex + i + 2]];
						MapTriangle(lm, info, dv, mapNonAxial);
					}
				}
				break;

			case MST_PATCH:
				/* make a mesh from the drawsurf */
				src.width = ds->patchWidth;
				src.height = ds->patchHeight;
				src.verts = &yDrawVerts[ds->firstVert];
				//% subdivided = SubdivideMesh( src, 8, 512 );
				subdivided = SubdivideMesh2(src, info->patchIterations);

				/* fit it to the curve and remove colinear verts on rows/columns */
				PutMeshOnCurve(*subdivided);
				mesh = RemoveLinearMeshColumnsRows(subdivided);
				FreeMesh(subdivided);

				/* get verts */
				verts = mesh->verts;

				/* debug code */
#if 0
				if(lm->plane)
				{
					Sys_Printf("Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n",
							   lm->plane[0], lm->plane[1], lm->plane[2],
							   lm->vecs[0][0], lm->vecs[0][1], lm->vecs[0][2], lm->vecs[1][0], lm->vecs[1][1], lm->vecs[1][2]);
				}
#endif

				/* map the mesh quads */
#if 0

				for(mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++)
				{
					for(y = 0; y < (mesh->height - 1); y++)
					{
						for(x = 0; x < (mesh->width - 1); x++)
						{
							/* set indexes */
							pw[0] = x + (y * mesh->width);
							pw[1] = x + ((y + 1) * mesh->width);
							pw[2] = x + 1 + ((y + 1) * mesh->width);
							pw[3] = x + 1 + (y * mesh->width);
							pw[4] = x + (y * mesh->width);	/* same as pw[ 0 ] */

							/* set radix */
							r = (x + y) & 1;

							/* get drawverts and map first triangle */
							dv[0] = &verts[pw[r + 0]];
							dv[1] = &verts[pw[r + 1]];
							dv[2] = &verts[pw[r + 2]];
							MapTriangle(lm, info, dv, mapNonAxial);

							/* get drawverts and map second triangle */
							dv[0] = &verts[pw[r + 0]];
							dv[1] = &verts[pw[r + 2]];
							dv[2] = &verts[pw[r + 3]];
							MapTriangle(lm, info, dv, mapNonAxial);
						}
					}
				}

#else

				for(y = 0; y < (mesh->height - 1); y++)
				{
					for(x = 0; x < (mesh->width - 1); x++)
					{
						/* set indexes */
						pw[0] = x + (y * mesh->width);
						pw[1] = x + ((y + 1) * mesh->width);
						pw[2] = x + 1 + ((y + 1) * mesh->width);
						pw[3] = x + 1 + (y * mesh->width);
						pw[4] = pw[0];

						/* set radix */
						r = (x + y) & 1;

						/* attempt to map quad first */
						dv[0] = &verts[pw[r + 0]];
						dv[1] = &verts[pw[r + 1]];
						dv[2] = &verts[pw[r + 2]];
						dv[3] = &verts[pw[r + 3]];
						if(MapQuad(lm, info, dv))
							continue;

						for(mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++)
						{
							/* get drawverts and map first triangle */
							dv[1] = &verts[pw[r + 1]];
							dv[2] = &verts[pw[r + 2]];
							MapTriangle(lm, info, dv, mapNonAxial);

							/* get drawverts and map second triangle */
							dv[1] = &verts[pw[r + 2]];
							dv[2] = &verts[pw[r + 3]];
							MapTriangle(lm, info, dv, mapNonAxial);
						}
					}
				}

#endif

				/* free the mesh */
				FreeMesh(mesh);
				break;

			default:
				break;
		}
	}

	/* -----------------------------------------------------------------
	   average and clean up luxel normals
	   ----------------------------------------------------------------- */

	/* walk the luxels */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			luxel = SUPER_LUXEL(0, x, y);
			normal = SUPER_NORMAL(x, y);
			cluster = SUPER_CLUSTER(x, y);

			/* only look at mapped luxels */
			if(*cluster < 0)
				continue;

			/* the normal data could be the sum of multiple samples */
			if(luxel[3] > 1.0f)
				VectorNormalize(normal);

			/* mark this luxel as having only one normal */
			luxel[3] = 1.0f;
		}
	}

	/* non-planar surfaces stop here */
	if(lm->plane == NULL)
		return;

	/* -----------------------------------------------------------------
	   map occluded or unuxed luxels
	   ----------------------------------------------------------------- */

	/* walk the luxels */
	radius = floor(superSample / 2);
	radius = radius > 0 ? radius : 1.0f;
	radius += 1.0f;
	for(pass = 2.0f; pass <= radius; pass += 1.0f)
	{
		for(y = 0; y < lm->sh; y++)
		{
			for(x = 0; x < lm->sw; x++)
			{
				/* get luxel */
				luxel = SUPER_LUXEL(0, x, y);
				normal = SUPER_NORMAL(x, y);
				cluster = SUPER_CLUSTER(x, y);

				/* only look at unmapped luxels */
				if(*cluster != CLUSTER_UNMAPPED)
					continue;

				/* divine a normal and origin from neighboring luxels */
				VectorClear(fake.xyz);
				VectorClear(fake.normal);
				fake.lightmap[0][0] = x;	//% 0.0001 + x;
				fake.lightmap[0][1] = y;	//% 0.0001 + y;
				samples = 0.0f;
				for(sy = (y - 1); sy <= (y + 1); sy++)
				{
					if(sy < 0 || sy >= lm->sh)
						continue;

					for(sx = (x - 1); sx <= (x + 1); sx++)
					{
						if(sx < 0 || sx >= lm->sw || (sx == x && sy == y))
							continue;

						/* get neighboring luxel */
						luxel = SUPER_LUXEL(0, sx, sy);
						origin = SUPER_ORIGIN(sx, sy);
						normal = SUPER_NORMAL(sx, sy);
						cluster = SUPER_CLUSTER(sx, sy);

						/* only consider luxels mapped in previous passes */
						if(*cluster < 0 || luxel[0] >= pass)
							continue;

						/* add its distinctiveness to our own */
						VectorAdd(fake.xyz, origin, fake.xyz);
						VectorAdd(fake.normal, normal, fake.normal);
						samples += luxel[3];
					}
				}

				/* any samples? */
				if(samples == 0.0f)
					continue;

				/* average */
				VectorDivide(fake.xyz, samples, fake.xyz);
				//% VectorDivide( fake.normal, samples, fake.normal );
				if(VectorNormalize(fake.normal) == 0.0f)
					continue;

				/* map the fake vert */
				MapSingleLuxel(lm, NULL, &fake, lm->plane, pass, NULL, NULL, NULL);
			}
		}
	}

	/* -----------------------------------------------------------------
	   average and clean up luxel normals
	   ----------------------------------------------------------------- */

	/* walk the luxels */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			luxel = SUPER_LUXEL(0, x, y);
			normal = SUPER_NORMAL(x, y);
			cluster = SUPER_CLUSTER(x, y);

			/* only look at mapped luxels */
			if(*cluster < 0)
				continue;

			/* the normal data could be the sum of multiple samples */
			if(luxel[3] > 1.0f)
				VectorNormalize(normal);

			/* mark this luxel as having only one normal */
			luxel[3] = 1.0f;
		}
	}

	/* debug code */
#if 0
	Sys_Printf("\n");
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			vec3_t          mins, maxs;


			cluster = SUPER_CLUSTER(x, y);
			origin = SUPER_ORIGIN(x, y);
			normal = SUPER_NORMAL(x, y);
			luxel = SUPER_LUXEL(x, y);

			if(*cluster < 0)
				continue;

			/* check if within the bounding boxes of all surfaces referenced */
			ClearBounds(mins, maxs);
			for(n = 0; n < lm->numLightSurfaces; n++)
			{
				int             TOL;

				info = &surfaceInfos[lightSurfaces[lm->firstLightSurface + n]];
				TOL = info->sampleSize + 2;
				AddPointToBounds(info->mins, mins, maxs);
				AddPointToBounds(info->maxs, mins, maxs);
				if(origin[0] > (info->mins[0] - TOL) && origin[0] < (info->maxs[0] + TOL) &&
				   origin[1] > (info->mins[1] - TOL) && origin[1] < (info->maxs[1] + TOL) &&
				   origin[2] > (info->mins[2] - TOL) && origin[2] < (info->maxs[2] + TOL))
					break;
			}

			/* inside? */
			if(n < lm->numLightSurfaces)
				continue;

			/* report bogus origin */
			Sys_Printf
				("%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n",
				 rawLightmapNum, x, y, *cluster, origin[0], origin[1], origin[2], mins[0], mins[1], mins[2], maxs[0], maxs[1],
				 maxs[2], luxel[3]);
		}
	}
#endif
}



/*
SetupDirt()
sets up dirtmap (ambient occlusion)
*/

#define DIRT_CONE_ANGLE				88	/* degrees */
#define DIRT_NUM_ANGLE_STEPS		16
#define DIRT_NUM_ELEVATION_STEPS	3
#define	DIRT_NUM_VECTORS			(DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS)

static vec3_t   dirtVectors[DIRT_NUM_VECTORS];
static int      numDirtVectors = 0;

void SetupDirt(void)
{
	int             i, j;
	float           angle, elevation, angleStep, elevationStep;


	/* note it */
	Sys_FPrintf(SYS_VRB, "--- SetupDirt ---\n");

	/* calculate angular steps */
	angleStep = DEG2RAD(360.0f / DIRT_NUM_ANGLE_STEPS);
	elevationStep = DEG2RAD(DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS);

	/* iterate angle */
	angle = 0.0f;
	for(i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep)
	{
		/* iterate elevation */
		for(j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep)
		{
			dirtVectors[numDirtVectors][0] = sin(elevation) * cos(angle);
			dirtVectors[numDirtVectors][1] = sin(elevation) * sin(angle);
			dirtVectors[numDirtVectors][2] = cos(elevation);
			numDirtVectors++;
		}
	}

	/* emit some statistics */
	Sys_FPrintf(SYS_VRB, "%9d dirtmap vectors\n", numDirtVectors);
}


/*
DirtForSample()
calculates dirt value for a given sample
*/

float DirtForSample(trace_t * trace)
{
	int             i;
	float           gatherDirt, outDirt, angle, elevation, ooDepth;
	vec3_t          normal, worldUp, myUp, myRt, temp, direction, displacement;


	/* dummy check */
	if(!dirty)
		return 1.0f;
	if(trace == NULL || trace->cluster < 0)
		return 0.0f;

	/* setup */
	gatherDirt = 0.0f;
	ooDepth = 1.0f / dirtDepth;
	VectorCopy(trace->normal, normal);

	/* check if the normal is aligned to the world-up */
	if(normal[0] == 0.0f && normal[1] == 0.0f && (normal[2] == 1.0f || normal[2] == -1.0f))
	{
		if(normal[2] == 1.0f)
		{
			VectorSet(myRt, 1.0f, 0.0f, 0.0f);
			VectorSet(myUp, 0.0f, 1.0f, 0.0f);
		}
		else if(normal[2] == -1.0f)
		{
			VectorSet(myRt, -1.0f, 0.0f, 0.0f);
			VectorSet(myUp, 0.0f, 1.0f, 0.0f);
		}
	}
	else
	{
		VectorSet(worldUp, 0.0f, 0.0f, 1.0f);
		CrossProduct(normal, worldUp, myRt);
		VectorNormalize(myRt);
		CrossProduct(myRt, normal, myUp);
		VectorNormalize(myUp);
	}

	/* 1 = random mode, 0 (well everything else) = non-random mode */
	if(dirtMode == 1)
	{
		/* iterate */
		for(i = 0; i < numDirtVectors; i++)
		{
			/* get random vector */
			angle = Random() * DEG2RAD(360.0f);
			elevation = Random() * DEG2RAD(DIRT_CONE_ANGLE);
			temp[0] = cos(angle) * sin(elevation);
			temp[1] = sin(angle) * sin(elevation);
			temp[2] = cos(elevation);

			/* transform into tangent space */
			direction[0] = myRt[0] * temp[0] + myUp[0] * temp[1] + normal[0] * temp[2];
			direction[1] = myRt[1] * temp[0] + myUp[1] * temp[1] + normal[1] * temp[2];
			direction[2] = myRt[2] * temp[0] + myUp[2] * temp[1] + normal[2] * temp[2];

			/* set endpoint */
			VectorMA(trace->origin, dirtDepth, direction, trace->end);
			SetupTrace(trace);

			/* trace */
			TraceLine(trace);
			if(trace->opaque && !(trace->compileFlags & C_SKY))
			{
				VectorSubtract(trace->hit, trace->origin, displacement);
				gatherDirt += 1.0f - ooDepth * VectorLength(displacement);
			}
		}
	}
	else
	{
		/* iterate through ordered vectors */
		for(i = 0; i < numDirtVectors; i++)
		{
			/* transform vector into tangent space */
			direction[0] = myRt[0] * dirtVectors[i][0] + myUp[0] * dirtVectors[i][1] + normal[0] * dirtVectors[i][2];
			direction[1] = myRt[1] * dirtVectors[i][0] + myUp[1] * dirtVectors[i][1] + normal[1] * dirtVectors[i][2];
			direction[2] = myRt[2] * dirtVectors[i][0] + myUp[2] * dirtVectors[i][1] + normal[2] * dirtVectors[i][2];

			/* set endpoint */
			VectorMA(trace->origin, dirtDepth, direction, trace->end);
			SetupTrace(trace);

			/* trace */
			TraceLine(trace);
			if(trace->opaque)
			{
				VectorSubtract(trace->hit, trace->origin, displacement);
				gatherDirt += 1.0f - ooDepth * VectorLength(displacement);
			}
		}
	}

	/* direct ray */
	VectorMA(trace->origin, dirtDepth, normal, trace->end);
	SetupTrace(trace);

	/* trace */
	TraceLine(trace);
	if(trace->opaque)
	{
		VectorSubtract(trace->hit, trace->origin, displacement);
		gatherDirt += 1.0f - ooDepth * VectorLength(displacement);
	}

	/* early out */
	if(gatherDirt <= 0.0f)
		return 1.0f;

	/* apply gain (does this even do much? heh) */
	outDirt = pow(gatherDirt / (numDirtVectors + 1), dirtGain);
	if(outDirt > 1.0f)
		outDirt = 1.0f;

	/* apply scale */
	outDirt *= dirtScale;
	if(outDirt > 1.0f)
		outDirt = 1.0f;

	/* return to sender */
	return 1.0f - outDirt;
}



/*
DirtyRawLightmap()
calculates dirty fraction for each luxel
*/

void DirtyRawLightmap(int rawLightmapNum)
{
	int             i, x, y, sx, sy, *cluster;
	float          *origin, *normal, *dirt, *dirt2, average, samples;
	rawLightmap_t  *lm;
	surfaceInfo_t  *info;
	trace_t         trace;
	qboolean        noDirty;


	/* bail if this number exceeds the number of raw lightmaps */
	if(rawLightmapNum >= numRawLightmaps)
		return;

	/* get lightmap */
	lm = &rawLightmaps[rawLightmapNum];

	/* setup trace */
	trace.testOcclusion = qtrue;
	trace.forceSunlight = qfalse;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[lm->firstLightSurface];
	trace.inhibitRadius = 0.0f;
	trace.testAll = qfalse;

	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	trace.twoSided = qfalse;
	for(i = 0; i < trace.numSurfaces; i++)
	{
		/* get surface */
		info = &surfaceInfos[trace.surfaces[i]];

		/* check twosidedness */
		if(info->si->twoSided)
		{
			trace.twoSided = qtrue;
			break;
		}
	}

	noDirty = qfalse;
	for(i = 0; i < trace.numSurfaces; i++)
	{
		/* get surface */
		info = &surfaceInfos[trace.surfaces[i]];

		/* check twosidedness */
		if(info->si->noDirty)
		{
			noDirty = qtrue;
			break;
		}
	}

	/* gather dirt */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			cluster = SUPER_CLUSTER(x, y);
			origin = SUPER_ORIGIN(x, y);
			normal = SUPER_NORMAL(x, y);
			dirt = SUPER_DIRT(x, y);

			/* set default dirt */
			*dirt = 0.0f;

			/* only look at mapped luxels */
			if(*cluster < 0)
				continue;

			/* don't apply dirty on this surface */
			if(noDirty)
			{
				*dirt = 1.0f;
				continue;
			}

			/* copy to trace */
			trace.cluster = *cluster;
			VectorCopy(origin, trace.origin);
			VectorCopy(normal, trace.normal);

			/* get dirt */
			*dirt = DirtForSample(&trace);
		}
	}

	/* testing no filtering */
	//% return;

	/* filter dirt */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			cluster = SUPER_CLUSTER(x, y);
			dirt = SUPER_DIRT(x, y);

			/* filter dirt by adjacency to unmapped luxels */
			average = *dirt;
			samples = 1.0f;
			for(sy = (y - 1); sy <= (y + 1); sy++)
			{
				if(sy < 0 || sy >= lm->sh)
					continue;

				for(sx = (x - 1); sx <= (x + 1); sx++)
				{
					if(sx < 0 || sx >= lm->sw || (sx == x && sy == y))
						continue;

					/* get neighboring luxel */
					cluster = SUPER_CLUSTER(sx, sy);
					dirt2 = SUPER_DIRT(sx, sy);
					if(*cluster < 0 || *dirt2 <= 0.0f)
						continue;

					/* add it */
					average += *dirt2;
					samples += 1.0f;
				}

				/* bail */
				if(samples <= 0.0f)
					break;
			}

			/* bail */
			if(samples <= 0.0f)
				continue;

			/* scale dirt */
			*dirt = average / samples;
		}
	}
}



/*
SubmapRawLuxel()
calculates the pvs cluster, origin, normal of a sub-luxel
*/

static qboolean SubmapRawLuxel(rawLightmap_t * lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin,
							   vec3_t sampleNormal)
{
	int             i, *cluster, *cluster2;
	float          *origin, *origin2, *normal;	//% , *normal2;
	vec3_t          originVecs[2];	//% , normalVecs[ 2 ];


	/* calulate x vector */
	if((x < (lm->sw - 1) && bx >= 0.0f) || (x == 0 && bx <= 0.0f))
	{
		cluster = SUPER_CLUSTER(x, y);
		origin = SUPER_ORIGIN(x, y);
		//% normal = SUPER_NORMAL( x, y );
		cluster2 = SUPER_CLUSTER(x + 1, y);
		origin2 = *cluster2 < 0 ? SUPER_ORIGIN(x, y) : SUPER_ORIGIN(x + 1, y);
		//% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y );
	}
	else if((x > 0 && bx <= 0.0f) || (x == (lm->sw - 1) && bx >= 0.0f))
	{
		cluster = SUPER_CLUSTER(x - 1, y);
		origin = *cluster < 0 ? SUPER_ORIGIN(x, y) : SUPER_ORIGIN(x - 1, y);
		//% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y );
		cluster2 = SUPER_CLUSTER(x, y);
		origin2 = SUPER_ORIGIN(x, y);
		//% normal2 = SUPER_NORMAL( x, y );
	}
	else
	{
		Error("Spurious lightmap S vector");
	}

	VectorSubtract(origin2, origin, originVecs[0]);
	//% VectorSubtract( normal2, normal, normalVecs[ 0 ] );

	/* calulate y vector */
	if((y < (lm->sh - 1) && bx >= 0.0f) || (y == 0 && bx <= 0.0f))
	{
		cluster = SUPER_CLUSTER(x, y);
		origin = SUPER_ORIGIN(x, y);
		//% normal = SUPER_NORMAL( x, y );
		cluster2 = SUPER_CLUSTER(x, y + 1);
		origin2 = *cluster2 < 0 ? SUPER_ORIGIN(x, y) : SUPER_ORIGIN(x, y + 1);
		//% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 );
	}
	else if((y > 0 && bx <= 0.0f) || (y == (lm->sh - 1) && bx >= 0.0f))
	{
		cluster = SUPER_CLUSTER(x, y - 1);
		origin = *cluster < 0 ? SUPER_ORIGIN(x, y) : SUPER_ORIGIN(x, y - 1);
		//% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 );
		cluster2 = SUPER_CLUSTER(x, y);
		origin2 = SUPER_ORIGIN(x, y);
		//% normal2 = SUPER_NORMAL( x, y );
	}
	else
		Sys_Printf("WARNING: Spurious lightmap T vector\n");

	VectorSubtract(origin2, origin, originVecs[1]);
	//% VectorSubtract( normal2, normal, normalVecs[ 1 ] );

	/* calculate new origin */
	//% VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin );
	//% VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin );
	for(i = 0; i < 3; i++)
		sampleOrigin[i] = sampleOrigin[i] + (bx * originVecs[0][i]) + (by * originVecs[1][i]);

	/* get cluster */
	*sampleCluster = ClusterForPointExtFilter(sampleOrigin, (LUXEL_EPSILON * 2), lm->numLightClusters, lm->lightClusters);
	if(*sampleCluster < 0)
		return qfalse;

	/* calculate new normal */
	//% VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal );
	//% VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal );
	//% if( VectorNormalize2( sampleNormal, sampleNormal ) <= 0.0f )
	//%     return qfalse;
	normal = SUPER_NORMAL(x, y);
	VectorCopy(normal, sampleNormal);

	/* return ok */
	return qtrue;
}


/*
SubsampleRawLuxel_r()
recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached
*/

static void SubsampleRawLuxel_r(rawLightmap_t * lm, trace_t * trace, vec3_t sampleOrigin, int x, int y, float bias,
								float *lightLuxel, float *lightDeluxel)
{
	int             b, samples, mapped, lighted;
	int             cluster[4];
	vec4_t          luxel[4];
	vec3_t          deluxel[3];
	vec3_t          origin[4], normal[4];
	float           biasDirs[4][2] = { {-1.0f, -1.0f}, {1.0f, -1.0f}, {-1.0f, 1.0f}, {1.0f, 1.0f} };
	vec3_t          color, direction = { 0, 0, 0 }, total;


	/* limit check */
	if(lightLuxel[3] >= lightSamples)
		return;

	/* setup */
	VectorClear(total);
	mapped = 0;
	lighted = 0;

	/* make 2x2 subsample stamp */
	for(b = 0; b < 4; b++)
	{
		/* set origin */
		VectorCopy(sampleOrigin, origin[b]);

		/* calculate position */
		if(!SubmapRawLuxel(lm, x, y, (bias * biasDirs[b][0]), (bias * biasDirs[b][1]), &cluster[b], origin[b], normal[b]))
		{
			cluster[b] = -1;
			continue;
		}
		mapped++;

		/* increment sample count */
		luxel[b][3] = lightLuxel[3] + 1.0f;

		/* setup trace */
		trace->cluster = *cluster;
		VectorCopy(origin[b], trace->origin);
		VectorCopy(normal[b], trace->normal);

		/* sample light */

		LightContributionToSample(trace);
		if(trace->forceSubsampling > 1.0f)
		{
			/* alphashadow: we subsample as deep as we can */
			++lighted;
			++mapped;
			++mapped;
		}

		/* add to totals (fixme: make contrast function) */
		VectorCopy(trace->color, luxel[b]);
		if(lightDeluxel)
		{
			VectorCopy(trace->directionContribution, deluxel[b]);
		}
		VectorAdd(total, trace->color, total);
		if((luxel[b][0] + luxel[b][1] + luxel[b][2]) > 0.0f)
			lighted++;
	}

	/* subsample further? */
	if((lightLuxel[3] + 1.0f) < lightSamples &&
	   (total[0] > 4.0f || total[1] > 4.0f || total[2] > 4.0f) && lighted != 0 && lighted != mapped)
	{
		for(b = 0; b < 4; b++)
		{
			if(cluster[b] < 0)
				continue;
			SubsampleRawLuxel_r(lm, trace, origin[b], x, y, (bias * 0.5f), luxel[b], lightDeluxel ? deluxel[b] : NULL);
		}
	}

	/* average */
	//% VectorClear( color );
	//% samples = 0;
	VectorCopy(lightLuxel, color);
	if(lightDeluxel)
	{
		VectorCopy(lightDeluxel, direction);
	}
	samples = 1;
	for(b = 0; b < 4; b++)
	{
		if(cluster[b] < 0)
			continue;
		VectorAdd(color, luxel[b], color);
		if(lightDeluxel)
		{
			VectorAdd(direction, deluxel[b], direction);
		}
		samples++;
	}

	/* add to luxel */
	if(samples > 0)
	{
		/* average */
		color[0] /= samples;
		color[1] /= samples;
		color[2] /= samples;

		/* add to color */
		VectorCopy(color, lightLuxel);
		lightLuxel[3] += 1.0f;

		if(lightDeluxel)
		{
			direction[0] /= samples;
			direction[1] /= samples;
			direction[2] /= samples;
			VectorCopy(direction, lightDeluxel);
		}
	}
}

/* A mostly Gaussian-like bounded random distribution (sigma is expected standard deviation) */
static void GaussLikeRandom(float sigma, float *x, float *y)
{
	float           r;

	r = Random() * 2 * Q_PI;
	*x = sigma * 2.73861278752581783822 * cos(r);
	*y = sigma * 2.73861278752581783822 * sin(r);
	r = Random();
	r = 1 - sqrt(r);
	r = 1 - sqrt(r);
	*x *= r;
	*y *= r;
}
static void RandomSubsampleRawLuxel(rawLightmap_t * lm, trace_t * trace, vec3_t sampleOrigin, int x, int y, float bias,
									float *lightLuxel, float *lightDeluxel)
{
	int             b, mapped;
	int             cluster;
	vec3_t          origin, normal;
	vec3_t          total, totaldirection;
	float           dx, dy;

	VectorClear(total);
	VectorClear(totaldirection);
	mapped = 0;
	for(b = 0; b < lightSamples; ++b)
	{
		/* set origin */
		VectorCopy(sampleOrigin, origin);
		GaussLikeRandom(bias, &dx, &dy);

		/* calculate position */
		if(!SubmapRawLuxel(lm, x, y, dx, dy, &cluster, origin, normal))
		{
			cluster = -1;
			continue;
		}
		mapped++;

		trace->cluster = cluster;
		VectorCopy(origin, trace->origin);
		VectorCopy(normal, trace->normal);

		LightContributionToSample(trace);
		VectorAdd(total, trace->color, total);
		if(lightDeluxel)
		{
			VectorAdd(totaldirection, trace->directionContribution, totaldirection);
		}
	}

	/* add to luxel */
	if(mapped > 0)
	{
		/* average */
		lightLuxel[0] = total[0] / mapped;
		lightLuxel[1] = total[1] / mapped;
		lightLuxel[2] = total[2] / mapped;

		if(lightDeluxel)
		{
			lightDeluxel[0] = totaldirection[0] / mapped;
			lightDeluxel[1] = totaldirection[1] / mapped;
			lightDeluxel[2] = totaldirection[2] / mapped;
		}
	}
}



/*
IlluminateRawLightmap()
illuminates the luxels
*/

#define STACK_LL_SIZE			(SUPER_LUXEL_SIZE * 64 * 64)
#define LIGHT_LUXEL( x, y )		(lightLuxels + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE))
#define LIGHT_DELUXEL( x, y )		(lightDeluxels + ((((y) * lm->sw) + (x)) * SUPER_DELUXEL_SIZE))

void IlluminateRawLightmap(int rawLightmapNum)
{
	int             i, t, x, y, sx, sy, size, luxelFilterRadius, lightmapNum;
	int            *cluster, *cluster2, mapped, lighted, totalLighted;
	size_t          llSize, ldSize;
	rawLightmap_t  *lm;
	surfaceInfo_t  *info;
	qboolean        filterColor, filterDir;
	float           brightness;
	float          *origin, *normal, *dirt, *luxel, *luxel2, *deluxel, *deluxel2;
	unsigned char  *flag;
	float          *lightLuxels, *lightDeluxels, *lightLuxel, *lightDeluxel, samples, filterRadius, weight;
	vec3_t          color, direction, averageColor, averageDir, total, temp, temp2;
	float           tests[4][2] = { {0.0f, 0}, {1, 0}, {0, 1}, {1, 1} };
	trace_t         trace;
	float           stackLightLuxels[STACK_LL_SIZE];


	/* bail if this number exceeds the number of raw lightmaps */
	if(rawLightmapNum >= numRawLightmaps)
		return;

	/* get lightmap */
	lm = &rawLightmaps[rawLightmapNum];

	/* setup trace */
	trace.testOcclusion = !noTrace;
	trace.forceSunlight = qfalse;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[lm->firstLightSurface];
	trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;

	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	trace.twoSided = qfalse;
	for(i = 0; i < trace.numSurfaces; i++)
	{
		/* get surface */
		info = &surfaceInfos[trace.surfaces[i]];

		/* check twosidedness */
		if(info->si->twoSided)
		{
			trace.twoSided = qtrue;
			break;
		}
	}

	/* create a culled light list for this raw lightmap */
	CreateTraceLightsForBounds(lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace);

	/* -----------------------------------------------------------------
	   fill pass
	   ----------------------------------------------------------------- */

	/* set counts */
	numLuxelsIlluminated += (lm->sw * lm->sh);

	/* test debugging state */
	if(debugSurfaces || debugAxis || debugCluster || debugOrigin || dirtDebug || normalmap)
	{
		/* debug fill the luxels */
		for(y = 0; y < lm->sh; y++)
		{
			for(x = 0; x < lm->sw; x++)
			{
				/* get cluster */
				cluster = SUPER_CLUSTER(x, y);

				/* only fill mapped luxels */
				if(*cluster < 0)
					continue;

				/* get particulars */
				luxel = SUPER_LUXEL(0, x, y);
				origin = SUPER_ORIGIN(x, y);
				normal = SUPER_NORMAL(x, y);

				/* color the luxel with raw lightmap num? */
				if(debugSurfaces)
					VectorCopy(debugColors[rawLightmapNum % 12], luxel);

				/* color the luxel with lightmap axis? */
				else if(debugAxis)
				{
					luxel[0] = (lm->axis[0] + 1.0f) * 127.5f;
					luxel[1] = (lm->axis[1] + 1.0f) * 127.5f;
					luxel[2] = (lm->axis[2] + 1.0f) * 127.5f;
				}

				/* color the luxel with luxel cluster? */
				else if(debugCluster)
					VectorCopy(debugColors[*cluster % 12], luxel);

				/* color the luxel with luxel origin? */
				else if(debugOrigin)
				{
					VectorSubtract(lm->maxs, lm->mins, temp);
					VectorScale(temp, (1.0f / 255.0f), temp);
					VectorSubtract(origin, lm->mins, temp2);
					luxel[0] = lm->mins[0] + (temp[0] * temp2[0]);
					luxel[1] = lm->mins[1] + (temp[1] * temp2[1]);
					luxel[2] = lm->mins[2] + (temp[2] * temp2[2]);
				}

				/* color the luxel with the normal */
				else if(normalmap)
				{
					luxel[0] = (normal[0] + 1.0f) * 127.5f;
					luxel[1] = (normal[1] + 1.0f) * 127.5f;
					luxel[2] = (normal[2] + 1.0f) * 127.5f;
				}

				/* otherwise clear it */
				else
					VectorClear(luxel);

				/* add to counts */
				luxel[3] = 1.0f;
			}
		}
	}
	else
	{
		/* allocate temporary per-light luxel storage */
		llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof(float);
		ldSize = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof(float);
		if(llSize <= (STACK_LL_SIZE * sizeof(float)))
			lightLuxels = stackLightLuxels;
		else
			lightLuxels = safe_malloc(llSize);
		if(deluxemap)
			lightDeluxels = safe_malloc(ldSize);
		else
			lightDeluxels = NULL;

		/* clear luxels */
		//% memset( lm->superLuxels[ 0 ], 0, llSize );

		/* set ambient color */
		for(y = 0; y < lm->sh; y++)
		{
			for(x = 0; x < lm->sw; x++)
			{
				/* get cluster */
				cluster = SUPER_CLUSTER(x, y);
				luxel = SUPER_LUXEL(0, x, y);
				normal = SUPER_NORMAL(x, y);
				deluxel = SUPER_DELUXEL(x, y);

				/* blacken unmapped clusters */
				if(*cluster < 0)
					VectorClear(luxel);

				/* set ambient */
				else
				{
					VectorCopy(ambientColor, luxel);
					if(deluxemap)
					{
						brightness = RGBTOGRAY(ambientColor) * (1.0f / 255.0f);

						// use AT LEAST this amount of contribution from ambient for the deluxemap, fixes points that receive ZERO light
						if(brightness < 0.00390625f)
							brightness = 0.00390625f;

						VectorScale(normal, brightness, deluxel);
					}
					luxel[3] = 1.0f;
				}
			}
		}

		/* clear styled lightmaps */
		size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof(float);
		for(lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		{
			if(lm->superLuxels[lightmapNum] != NULL)
				memset(lm->superLuxels[lightmapNum], 0, size);
		}

		/* debugging code */
		//% if( trace.numLights <= 0 )
		//%     Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] );

		/* walk light list */
		for(i = 0; i < trace.numLights; i++)
		{
			/* setup trace */
			trace.light = trace.lights[i];

			/* style check */
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				if(lm->styles[lightmapNum] == trace.light->style || lm->styles[lightmapNum] == LS_NONE)
					break;
			}

			/* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */
			if(lightmapNum >= MAX_LIGHTMAPS)
			{
				Sys_Printf("WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS);
				continue;
			}

			/* setup */
			memset(lightLuxels, 0, llSize);
			if(deluxemap)
				memset(lightDeluxels, 0, ldSize);
			totalLighted = 0;

			/* determine filter radius */
			filterRadius = lm->filterRadius > trace.light->filterRadius ? lm->filterRadius : trace.light->filterRadius;
			if(filterRadius < 0.0f)
				filterRadius = 0.0f;

			/* set luxel filter radius */
			luxelFilterRadius = superSample * filterRadius / lm->sampleSize;
			if(luxelFilterRadius == 0 && (filterRadius > 0.0f || filter))
				luxelFilterRadius = 1;

			/* allocate sampling flags storage */
			if((lightSamples > 1 || lightRandomSamples) && luxelFilterRadius == 0)
			{
				size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof(unsigned char);
				if(lm->superFlags == NULL)
					lm->superFlags = safe_malloc(size);
				memset((void *)lm->superFlags, 0, size);
			}

			/* initial pass, one sample per luxel */
			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get cluster */
					cluster = SUPER_CLUSTER(x, y);
					if(*cluster < 0)
						continue;

					/* get particulars */
					lightLuxel = LIGHT_LUXEL(x, y);
					lightDeluxel = LIGHT_DELUXEL(x, y);
					origin = SUPER_ORIGIN(x, y);
					normal = SUPER_NORMAL(x, y);
					flag = SUPER_FLAG(x, y);

#if 0
					////////// 27's temp hack for testing edge clipping ////
					if(origin[0] == 0 && origin[1] == 0 && origin[2] == 0)
					{
						lightLuxel[1] = 255;
						lightLuxel[3] = 1.0f;
						totalLighted++;
					}
					else
#endif
					{
						/* set contribution count */
						lightLuxel[3] = 1.0f;

						/* setup trace */
						trace.cluster = *cluster;
						VectorCopy(origin, trace.origin);
						VectorCopy(normal, trace.normal);

						/* get light for this sample */
						LightContributionToSample(&trace);
						VectorCopy(trace.color, lightLuxel);

						/* add the contribution to the deluxemap */
						if(deluxemap)
						{
							VectorCopy(trace.directionContribution, lightDeluxel);
						}

						/* check for evilness */
						if(trace.forceSubsampling > 1.0f && (lightSamples > 1 || lightRandomSamples) && luxelFilterRadius == 0)
						{
							totalLighted++;
							*flag |= FLAG_FORCE_SUBSAMPLING;	/* force */
						}
						/* add to count */
						else if(trace.color[0] || trace.color[1] || trace.color[2])
							totalLighted++;
					}
				}
			}

			/* don't even bother with everything else if nothing was lit */
			if(totalLighted == 0)
				continue;

			/* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */
			/* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */
			if((lightSamples > 1 || lightRandomSamples) && luxelFilterRadius == 0)
			{
				/* walk luxels */
				for(y = 0; y < (lm->sh - 1); y++)
				{
					for(x = 0; x < (lm->sw - 1); x++)
					{
						/* setup */
						mapped = 0;
						lighted = 0;
						VectorClear(total);

						/* test 2x2 stamp */
						for(t = 0; t < 4; t++)
						{
							/* set sample coords */
							sx = x + tests[t][0];
							sy = y + tests[t][1];

							/* get cluster */
							cluster = SUPER_CLUSTER(sx, sy);
							if(*cluster < 0)
								continue;
							mapped++;

							/* get luxel */
							flag = SUPER_FLAG(sx, sy);
							if(*flag & FLAG_FORCE_SUBSAMPLING)
							{
								/* force a lighted/mapped discrepancy so we subsample */
								++lighted;
								++mapped;
								++mapped;
							}
							lightLuxel = LIGHT_LUXEL(sx, sy);
							VectorAdd(total, lightLuxel, total);
							if((lightLuxel[0] + lightLuxel[1] + lightLuxel[2]) > 0.0f)
								lighted++;
						}

						/* if total color is under a certain amount, then don't bother subsampling */
						if(total[0] <= 4.0f && total[1] <= 4.0f && total[2] <= 4.0f)
							continue;

						/* if all 4 pixels are either in shadow or light, then don't subsample */
						if(lighted != 0 && lighted != mapped)
						{
							for(t = 0; t < 4; t++)
							{
								/* set sample coords */
								sx = x + tests[t][0];
								sy = y + tests[t][1];

								/* get luxel */
								cluster = SUPER_CLUSTER(sx, sy);
								if(*cluster < 0)
									continue;
								flag = SUPER_FLAG(sx, sy);
								if(*flag & FLAG_ALREADY_SUBSAMPLED)	// already subsampled
									continue;
								lightLuxel = LIGHT_LUXEL(sx, sy);
								lightDeluxel = LIGHT_DELUXEL(sx, sy);
								origin = SUPER_ORIGIN(sx, sy);

								/* only subsample shadowed luxels */
								//% if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f )
								//%     continue;

								/* subsample it */
								if(lightRandomSamples)
									RandomSubsampleRawLuxel(lm, &trace, origin, sx, sy, 0.5f * lightSamplesSearchBoxSize,
															lightLuxel, deluxemap ? lightDeluxel : NULL);
								else
									SubsampleRawLuxel_r(lm, &trace, origin, sx, sy, 0.25f * lightSamplesSearchBoxSize, lightLuxel,
														deluxemap ? lightDeluxel : NULL);

								*flag |= FLAG_ALREADY_SUBSAMPLED;

								/* debug code to colorize subsampled areas to yellow */
								//% luxel = SUPER_LUXEL( lightmapNum, sx, sy );
								//% VectorSet( luxel, 255, 204, 0 );
							}
						}
					}
				}
			}

			/* tertiary pass, apply dirt map (ambient occlusion) */
			/*if(dirty)
			{
				/ * walk luxels * /
				for(y = 0; y < lm->sh; y++)
				{
					for(x = 0; x < lm->sw; x++)
					{
						/ * get cluster  * /
						cluster = SUPER_CLUSTER(x, y);
						if(*cluster < 0)
							continue;

						/ * get particulars * /
						lightLuxel = LIGHT_LUXEL(x, y);
						dirt = SUPER_DIRT(x, y);

						/ * scale light value * /
						VectorScale(lightLuxel, *dirt, lightLuxel);
					}
				}
			}*/

			/* allocate sampling lightmap storage */
			if(lm->superLuxels[lightmapNum] == NULL)
			{
				/* allocate sampling lightmap storage */
				size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof(float);
				lm->superLuxels[lightmapNum] = safe_malloc(size);
				memset(lm->superLuxels[lightmapNum], 0, size);
			}

			/* set style */
			if(lightmapNum > 0)
			{
				lm->styles[lightmapNum] = trace.light->style;
				//% Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style );
			}

			/* copy to permanent luxels */
			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get cluster and origin */
					cluster = SUPER_CLUSTER(x, y);
					if(*cluster < 0)
						continue;
					origin = SUPER_ORIGIN(x, y);

					/* filter? */
					if(luxelFilterRadius)
					{
						/* setup */
						VectorClear(averageColor);
						VectorClear(averageDir);
						samples = 0.0f;

						/* cheaper distance-based filtering */
						for(sy = (y - luxelFilterRadius); sy <= (y + luxelFilterRadius); sy++)
						{
							if(sy < 0 || sy >= lm->sh)
								continue;

							for(sx = (x - luxelFilterRadius); sx <= (x + luxelFilterRadius); sx++)
							{
								if(sx < 0 || sx >= lm->sw)
									continue;

								/* get particulars */
								cluster = SUPER_CLUSTER(sx, sy);
								if(*cluster < 0)
									continue;
								lightLuxel = LIGHT_LUXEL(sx, sy);
								lightDeluxel = LIGHT_DELUXEL(sx, sy);

								/* create weight */
								weight = (abs(sx - x) == luxelFilterRadius ? 0.5f : 1.0f);
								weight *= (abs(sy - y) == luxelFilterRadius ? 0.5f : 1.0f);

								/* scale luxel by filter weight */
								VectorScale(lightLuxel, weight, color);
								VectorAdd(averageColor, color, averageColor);
								if(deluxemap)
								{
									VectorScale(lightDeluxel, weight, direction);
									VectorAdd(averageDir, direction, averageDir);
								}
								samples += weight;
							}
						}

						/* any samples? */
						if(samples <= 0.0f)
							continue;

						/* scale into luxel */
						luxel = SUPER_LUXEL(lightmapNum, x, y);
						luxel[3] = 1.0f;

						/* handle negative light */
						if(trace.light->flags & LIGHT_NEGATIVE)
						{
							luxel[0] -= averageColor[0] / samples;
							luxel[1] -= averageColor[1] / samples;
							luxel[2] -= averageColor[2] / samples;
						}

						/* handle positive light */
						else
						{
							luxel[0] += averageColor[0] / samples;
							luxel[1] += averageColor[1] / samples;
							luxel[2] += averageColor[2] / samples;
						}

						if(deluxemap)
						{
							/* scale into luxel */
							deluxel = SUPER_DELUXEL(x, y);
							deluxel[0] += averageDir[0] / samples;
							deluxel[1] += averageDir[1] / samples;
							deluxel[2] += averageDir[2] / samples;
						}
					}

					/* single sample */
					else
					{
						/* get particulars */
						lightLuxel = LIGHT_LUXEL(x, y);
						lightDeluxel = LIGHT_DELUXEL(x, y);
						luxel = SUPER_LUXEL(lightmapNum, x, y);
						deluxel = SUPER_DELUXEL(x, y);

						/* handle negative light */
						if(trace.light->flags & LIGHT_NEGATIVE)
							VectorScale(averageColor, -1.0f, averageColor);

						/* add color */
						luxel[3] = 1.0f;

						/* handle negative light */
						if(trace.light->flags & LIGHT_NEGATIVE)
							VectorSubtract(luxel, lightLuxel, luxel);

						/* handle positive light */
						else
							VectorAdd(luxel, lightLuxel, luxel);

						if(deluxemap)
						{
							VectorAdd(deluxel, lightDeluxel, deluxel);
						}
					}
				}
			}
		}

		/* free temporary luxels */
		if(lightLuxels != stackLightLuxels)
			free(lightLuxels);

		if(deluxemap)
			free(lightDeluxels);
	}

	/* free light list */
	FreeTraceLights(&trace);

	/* floodlight pass */
	if(floodlighty)
		FloodlightIlluminateLightmap(lm);

	if(debugnormals)
	{
		for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		{
			/* early out */
			if(lm->superLuxels[lightmapNum] == NULL)
				continue;

			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get cluster */
					cluster = SUPER_CLUSTER(x, y);
					//% if( *cluster < 0 )
					//%     continue;

					/* get particulars */
					luxel = SUPER_LUXEL(lightmapNum, x, y);
					normal = SUPER_NORMAL(x, y);

					luxel[0] = (normal[0] * 127) + 127;
					luxel[1] = (normal[1] * 127) + 127;
					luxel[2] = (normal[2] * 127) + 127;
				}
			}
		}
	}

	/*  -----------------------------------------------------------------
	   dirt pass
	   ----------------------------------------------------------------- */

	if(dirty)
	{
		/* walk lightmaps */
		for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		{
			/* early out */
			if(lm->superLuxels[lightmapNum] == NULL)
				continue;

			/* apply dirt to each luxel */
			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get cluster */
					cluster = SUPER_CLUSTER(x, y);
					//% if( *cluster < 0 ) // TODO why not do this check? These pixels should be zero anyway
					//%     continue;

					/* get particulars */
					luxel = SUPER_LUXEL(lightmapNum, x, y);
					dirt = SUPER_DIRT(x, y);

					/* apply dirt */
					VectorScale(luxel, *dirt, luxel);

					/* debugging */
					if(dirtDebug)
						VectorSet(luxel, *dirt * 255.0f, *dirt * 255.0f, *dirt * 255.0f);
				}
			}
		}
	}

	/* -----------------------------------------------------------------
	   filter pass
	   ----------------------------------------------------------------- */

	/* walk lightmaps */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
	{
		/* early out */
		if(lm->superLuxels[lightmapNum] == NULL)
			continue;

		/* average occluded luxels from neighbors */
		for(y = 0; y < lm->sh; y++)
		{
			for(x = 0; x < lm->sw; x++)
			{
				/* get particulars */
				cluster = SUPER_CLUSTER(x, y);
				luxel = SUPER_LUXEL(lightmapNum, x, y);
				deluxel = SUPER_DELUXEL(x, y);
				normal = SUPER_NORMAL(x, y);

				/* determine if filtering is necessary */
				filterColor = qfalse;
				filterDir = qfalse;
				if(*cluster < 0 ||
				   (lm->splotchFix &&
					(luxel[0] <= ambientColor[0] || luxel[1] <= ambientColor[1] || luxel[2] <= ambientColor[2])))
					filterColor = qtrue;

				if(deluxemap && lightmapNum == 0 && (*cluster < 0 || filter))
					filterDir = qtrue;

				if(!filterColor && !filterDir)
					continue;

				/* choose seed amount */
				VectorClear(averageColor);
				VectorClear(averageDir);
				samples = 0.0f;

				/* walk 3x3 matrix */
				for(sy = (y - 1); sy <= (y + 1); sy++)
				{
					if(sy < 0 || sy >= lm->sh)
						continue;

					for(sx = (x - 1); sx <= (x + 1); sx++)
					{
						if(sx < 0 || sx >= lm->sw || (sx == x && sy == y))
							continue;

						/* get the neighbor's particulars */
						cluster2 = SUPER_CLUSTER(sx, sy);
						luxel2 = SUPER_LUXEL(lightmapNum, sx, sy);
						deluxel2 = SUPER_DELUXEL(sx, sy);

						/* ignore unmapped/unlit luxels */
						if(*cluster2 < 0 || luxel2[3] == 0.0f || (lm->splotchFix && VectorCompare(luxel2, ambientColor)))
							continue;

						/* add its distinctiveness to our own */
						VectorAdd(averageColor, luxel2, averageColor);
						samples += luxel2[3];
						if(filterDir)
							VectorAdd(averageDir, deluxel2, averageDir);
					}
				}

				/* fall through */
				if(samples <= 0.0f)
					continue;

				/* dark lightmap seams */
				if(dark)
				{
					if(lightmapNum == 0)
						VectorMA(averageColor, 2.0f, ambientColor, averageColor);
					samples += 2.0f;
				}

				/* average it */
				if(filterColor)
				{
					VectorDivide(averageColor, samples, luxel);
					luxel[3] = 1.0f;
				}
				if(filterDir)
					VectorDivide(averageDir, samples, deluxel);

				/* set cluster to -3 */
				if(*cluster < 0)
					*cluster = CLUSTER_FLOODED;
			}
		}
	}


#if 0
	// audit pass
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
	{
		/* early out */
		if(lm->superLuxels[lightmapNum] == NULL)
			continue;
		for(y = 0; y < lm->sh; y++)
			for(x = 0; x < lm->sw; x++)
			{
				/* get cluster */
				cluster = SUPER_CLUSTER(x, y);
				luxel = SUPER_LUXEL(lightmapNum, x, y);
				deluxel = SUPER_DELUXEL(x, y);
				if(!luxel || !deluxel || !cluster)
				{
					Sys_FPrintf(SYS_VRB, "WARNING: I got NULL'd.\n");
					continue;
				}
				else if(*cluster < 0)
				{
					// unmapped pixel
					// should have neither deluxemap nor lightmap
					if(deluxel[3])
						Sys_FPrintf(SYS_VRB, "WARNING: I have written deluxe to an unmapped luxel. Sorry.\n");
				}
				else
				{
					// mapped pixel
					// should have both deluxemap and lightmap
					if(deluxel[3])
						Sys_FPrintf(SYS_VRB, "WARNING: I forgot to write deluxe to a mapped luxel. Sorry.\n");
				}
			}
	}
#endif
}



/*
IlluminateVertexes()
light the surface vertexes
*/

#define VERTEX_NUDGE	4.0f

void IlluminateVertexes(int num)
{
	int             i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster;
	int             lightmapNum, numAvg;
	float           samples, *vertLuxel, *radVertLuxel, *luxel, *deluxel, *vertDeluxel, dirt;
	vec3_t          origin, temp, temp2, colors[MAX_LIGHTMAPS], avgColors[MAX_LIGHTMAPS];
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info;
	rawLightmap_t  *lm;
	bspDrawVert_t  *verts;
	trace_t         trace;
	float           floodLightAmount;
	vec3_t          floodColor;


	/* get surface, info, and raw lightmap */
	ds = &bspDrawSurfaces[num];
	info = &surfaceInfos[num];
	lm = info->lm;

	/* -----------------------------------------------------------------
	   illuminate the vertexes
	   ----------------------------------------------------------------- */

	/* calculate vertex lighting for surfaces without lightmaps */
	if(lm == NULL || cpmaHack)
	{
		/* setup trace */
		trace.testOcclusion = (cpmaHack && lm != NULL) ? qfalse : !noTrace;
		trace.forceSunlight = info->si->forceSunlight;
		trace.recvShadows = info->recvShadows;
		trace.numSurfaces = 1;
		trace.surfaces = &num;
		trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;

		/* twosided lighting */
		trace.twoSided = info->si->twoSided;

		/* make light list for this surface */
		CreateTraceLightsForSurface(num, &trace);

		/* setup */
		verts = yDrawVerts + ds->firstVert;
		numAvg = 0;
		memset(avgColors, 0, sizeof(avgColors));

		/* walk the surface verts */
		for(i = 0; i < ds->numVerts; i++)
		{
			/* get vertex luxel */
			radVertLuxel = RAD_VERTEX_LUXEL(0, ds->firstVert + i);

			if(deluxemap)
			{
				vertDeluxel = VERTEX_DELUXEL(0, ds->firstVert + i);
			}

			/* color the luxel with raw lightmap num? */
			if(debugSurfaces)
				VectorCopy(debugColors[num % 12], radVertLuxel);

			/* color the luxel with luxel origin? */
			else if(debugOrigin)
			{
				VectorSubtract(info->maxs, info->mins, temp);
				VectorScale(temp, (1.0f / 255.0f), temp);
				VectorSubtract(origin, lm->mins, temp2);
				radVertLuxel[0] = info->mins[0] + (temp[0] * temp2[0]);
				radVertLuxel[1] = info->mins[1] + (temp[1] * temp2[1]);
				radVertLuxel[2] = info->mins[2] + (temp[2] * temp2[2]);
			}

			/* color the luxel with the normal */
			else if(normalmap)
			{
				radVertLuxel[0] = (verts[i].normal[0] + 1.0f) * 127.5f;
				radVertLuxel[1] = (verts[i].normal[1] + 1.0f) * 127.5f;
				radVertLuxel[2] = (verts[i].normal[2] + 1.0f) * 127.5f;
			}

			/* illuminate the vertex */
			else
			{
				/* clear vertex luxel */
				VectorSet(radVertLuxel, -1.0f, -1.0f, -1.0f);

				if(deluxemap)
				{
					VectorClear(vertDeluxel);
				}

				/* try at initial origin */
				trace.cluster =
					ClusterForPointExtFilter(verts[i].xyz, VERTEX_EPSILON, info->numSurfaceClusters,
											 &surfaceClusters[info->firstSurfaceCluster]);
				if(trace.cluster >= 0)
				{
					/* setup trace */
					VectorCopy(verts[i].xyz, trace.origin);
					VectorCopy(verts[i].normal, trace.normal);

					/* r7 dirt */
					if(dirty && !bouncing)
						dirt = DirtForSample(&trace);
					else
						dirt = 1.0f;

					/* jal: floodlight */
					floodLightAmount = 0.0f;
					VectorClear(floodColor);
					if(floodlighty && !bouncing)
					{
						floodLightAmount =
							floodlightIntensity * FloodLightForSample(&trace, floodlightDistance, floodlight_lowquality);
						VectorScale(floodlightRGB, floodLightAmount, floodColor);
					}

					/* trace */
					LightingAtSample(&trace, ds->vertexStyles, colors);

					/* store */
					for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
					{
						/* r7 dirt */
						VectorScale(colors[lightmapNum], dirt, colors[lightmapNum]);

						/* jal: floodlight */
						VectorAdd(colors[lightmapNum], floodColor, colors[lightmapNum]);

						/* store */
						radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
						VectorCopy(colors[lightmapNum], radVertLuxel);
						VectorAdd(avgColors[lightmapNum], colors[lightmapNum], colors[lightmapNum]);

						if(deluxemap)
						{
							vertDeluxel = VERTEX_DELUXEL(lightmapNum, ds->firstVert + i);
							VectorCopy(trace.direction, vertDeluxel);
						}
					}
				}

				/* is this sample bright enough? */
				radVertLuxel = RAD_VERTEX_LUXEL(0, ds->firstVert + i);
				if(radVertLuxel[0] <= ambientColor[0] && radVertLuxel[1] <= ambientColor[1] && radVertLuxel[2] <= ambientColor[2])
				{
					/* nudge the sample point around a bit */
					for(x = 0; x < 5; x++)
					{
						/* two's complement 0, 1, -1, 2, -2, etc */
						x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);

						for(y = 0; y < 5; y++)
						{
							y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);

							for(z = 0; z < 5; z++)
							{
								z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);

								/* nudge origin */
								trace.origin[0] = verts[i].xyz[0] + (VERTEX_NUDGE * x1);
								trace.origin[1] = verts[i].xyz[1] + (VERTEX_NUDGE * y1);
								trace.origin[2] = verts[i].xyz[2] + (VERTEX_NUDGE * z1);

								/* try at nudged origin */
								trace.cluster =
									ClusterForPointExtFilter(origin, VERTEX_EPSILON, info->numSurfaceClusters,
															 &surfaceClusters[info->firstSurfaceCluster]);
								if(trace.cluster < 0)
									continue;

								/* r7 dirt */
								if(dirty && !bouncing)
									dirt = DirtForSample(&trace);
								else
									dirt = 1.0f;

								/* jal: floodlight */
								floodLightAmount = 0.0f;
								VectorClear(floodColor);
								if(floodlighty && !bouncing)
								{
									floodLightAmount =
										floodlightIntensity * FloodLightForSample(&trace, floodlightDistance,
																				  floodlight_lowquality);
									VectorScale(floodlightRGB, floodLightAmount, floodColor);
								}

								/* trace */
								LightingAtSample(&trace, ds->vertexStyles, colors);

								/* store */
								for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
								{
									/* r7 dirt */
									VectorScale(colors[lightmapNum], dirt, colors[lightmapNum]);

									/* jal: floodlight */
									VectorAdd(colors[lightmapNum], floodColor, colors[lightmapNum]);

									/* store */
									radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
									VectorCopy(colors[lightmapNum], radVertLuxel);

									if(deluxemap)
									{
										vertDeluxel = VERTEX_DELUXEL(lightmapNum, ds->firstVert + i);
										VectorCopy(trace.direction, vertDeluxel);
									}
								}

								/* bright enough? */
								radVertLuxel = RAD_VERTEX_LUXEL(0, ds->firstVert + i);
								if(radVertLuxel[0] > ambientColor[0] ||
								   radVertLuxel[1] > ambientColor[1] || radVertLuxel[2] > ambientColor[2])
									x = y = z = 1000;
							}
						}
					}
				}

				/* add to average? */
				radVertLuxel = RAD_VERTEX_LUXEL(0, ds->firstVert + i);
				if(radVertLuxel[0] > ambientColor[0] || radVertLuxel[1] > ambientColor[1] || radVertLuxel[2] > ambientColor[2])
				{
					numAvg++;
					for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
					{
						radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
						VectorAdd(avgColors[lightmapNum], radVertLuxel, avgColors[lightmapNum]);
					}
				}
			}

			/* another happy customer */
			numVertsIlluminated++;
		}

		/* set average color */
		if(numAvg > 0)
		{
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
				VectorScale(avgColors[lightmapNum], (1.0f / numAvg), avgColors[lightmapNum]);
		}
		else
		{
			VectorCopy(ambientColor, avgColors[0]);
		}

		/* clean up and store vertex color */
		for(i = 0; i < ds->numVerts; i++)
		{
			/* get vertex luxel */
			radVertLuxel = RAD_VERTEX_LUXEL(0, ds->firstVert + i);

			/* store average in occluded vertexes */
			if(radVertLuxel[0] < 0.0f)
			{
				for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
				{
					radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
					VectorCopy(avgColors[lightmapNum], radVertLuxel);

					/* debug code */
					//% VectorSet( radVertLuxel, 255.0f, 0.0f, 0.0f );
				}
			}

			/* store it */
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				/* get luxels */
				vertLuxel = VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
				radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);

				/* store */
				if(bouncing || bounce == 0 || !bounceOnly)
					VectorAdd(vertLuxel, radVertLuxel, vertLuxel);

				if(deluxemap)
				{
					vertDeluxel = VERTEX_DELUXEL(lightmapNum, ds->firstVert + i);
					VectorNormalize(vertDeluxel);
				}


				if(!info->si->noVertexLight)
				{
					ColorToFloats(vertLuxel, verts[i].lightColor[lightmapNum], info->si->vertexScale);

					if(deluxemap)
					{
						VectorCopy(vertDeluxel, verts[i].lightDirection[lightmapNum]);
					}
					else
					{
						VectorCopy(verts[i].normal, verts[i].lightDirection[lightmapNum]);
					}
				}
			}
		}

		/* free light list */
		FreeTraceLights(&trace);

		/* return to sender */
		return;
	}

	/* -----------------------------------------------------------------
	   reconstitute vertex lighting from the luxels
	   ----------------------------------------------------------------- */

	/* set styles from lightmap */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		ds->vertexStyles[lightmapNum] = lm->styles[lightmapNum];

	/* get max search radius */
	maxRadius = lm->sw;
	maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh;

	/* walk the surface verts */
	verts = yDrawVerts + ds->firstVert;
	for(i = 0; i < ds->numVerts; i++)
	{
		/* do each lightmap */
		for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		{
			/* early out */
			if(lm->superLuxels[lightmapNum] == NULL)
				continue;

			/* get luxel coords */
			x = verts[i].lightmap[lightmapNum][0];
			y = verts[i].lightmap[lightmapNum][1];
			if(x < 0)
				x = 0;
			else if(x >= lm->sw)
				x = lm->sw - 1;
			if(y < 0)
				y = 0;
			else if(y >= lm->sh)
				y = lm->sh - 1;

			/* get vertex luxels */
			vertLuxel = VERTEX_LUXEL(lightmapNum, ds->firstVert + i);
			radVertLuxel = RAD_VERTEX_LUXEL(lightmapNum, ds->firstVert + i);

			if(deluxemap)
			{
				vertDeluxel = VERTEX_DELUXEL(lightmapNum, ds->firstVert + i);
			}

			/* color the luxel with the normal? */
			if(normalmap)
			{
				radVertLuxel[0] = (verts[i].normal[0] + 1.0f) * 127.5f;
				radVertLuxel[1] = (verts[i].normal[1] + 1.0f) * 127.5f;
				radVertLuxel[2] = (verts[i].normal[2] + 1.0f) * 127.5f;
			}

			/* color the luxel with surface num? */
			else if(debugSurfaces)
				VectorCopy(debugColors[num % 12], radVertLuxel);

			/* divine color from the superluxels */
			else
			{
				/* increasing radius */
				VectorClear(radVertLuxel);

				if(deluxemap)
				{
					VectorClear(vertDeluxel);
				}
				samples = 0.0f;
				for(radius = 0; radius < maxRadius && samples <= 0.0f; radius++)
				{
					/* sample within radius */
					for(sy = (y - radius); sy <= (y + radius); sy++)
					{
						if(sy < 0 || sy >= lm->sh)
							continue;

						for(sx = (x - radius); sx <= (x + radius); sx++)
						{
							if(sx < 0 || sx >= lm->sw)
								continue;

							cluster = SUPER_CLUSTER(sx, sy);
							if(*cluster < 0)
								continue;

							/* get luxel particulars */
							luxel = SUPER_LUXEL(lightmapNum, sx, sy);

							/* testing: must be brigher than ambient color */
							//% if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] )
							//%     continue;

							/* add its distinctiveness to our own */
							VectorAdd(radVertLuxel, luxel, radVertLuxel);
							samples += luxel[3];

							if(deluxemap)
							{
								deluxel = SUPER_DELUXEL(sx, sy);
								VectorAdd(vertDeluxel, deluxel, vertDeluxel);
							}
						}
					}
				}

				/* any color? */
				if(samples > 0.0f)
					VectorDivide(radVertLuxel, samples, radVertLuxel);
				else
					VectorCopy(ambientColor, radVertLuxel);
			}

			/* store into floating point storage */
			VectorAdd(vertLuxel, radVertLuxel, vertLuxel);
			if(deluxemap)
			{
				VectorNormalize(vertDeluxel);
			}
			numVertsIlluminated++;

			/* store into bytes (for vertex approximation) */
			if(!info->si->noVertexLight)
			{
				ColorToFloats(vertLuxel, verts[i].lightColor[lightmapNum], 1.0f);

				if(deluxemap)
				{
					VectorCopy(vertDeluxel, verts[i].lightDirection[lightmapNum]);
				}
				else
				{
					VectorCopy(verts[i].normal, verts[i].lightDirection[lightmapNum]);
				}
			}
		}
	}
}



/* -------------------------------------------------------------------------------

light optimization (-fast)

creates a list of lights that will affect a surface and stores it in tw
this is to optimize surface lighting by culling out as many of the
lights in the world as possible from further calculation

------------------------------------------------------------------------------- */

/*
SetupBrushes()
determines opaque brushes in the world and find sky shaders for sunlight calculations
*/

void SetupBrushes(void)
{
	int             i, j, b, compileFlags;
	qboolean        inside;
	bspBrush_t     *brush;
	bspBrushSide_t *side;
	bspShader_t    *shader;
	shaderInfo_t   *si;


	/* note it */
	Sys_FPrintf(SYS_VRB, "--- SetupBrushes ---\n");

	/* allocate */
	if(opaqueBrushes == NULL)
		opaqueBrushes = safe_malloc(numBSPBrushes / 8 + 1);

	/* clear */
	memset(opaqueBrushes, 0, numBSPBrushes / 8 + 1);
	numOpaqueBrushes = 0;

	/* walk the list of worldspawn brushes */
	for(i = 0; i < bspModels[0].numBSPBrushes; i++)
	{
		/* get brush */
		b = bspModels[0].firstBSPBrush + i;
		brush = &bspBrushes[b];

		/* check all sides */
		inside = qtrue;
		compileFlags = 0;
		for(j = 0; j < brush->numSides && inside; j++)
		{
			/* do bsp shader calculations */
			side = &bspBrushSides[brush->firstSide + j];
			shader = &bspShaders[side->shaderNum];

			/* get shader info */
			si = ShaderInfoForShader(shader->shader);
			if(si == NULL)
				continue;

			/* or together compile flags */
			compileFlags |= si->compileFlags;
		}

		/* determine if this brush is opaque to light */
		if(!(compileFlags & C_TRANSLUCENT))
		{
			opaqueBrushes[b >> 3] |= (1 << (b & 7));
			numOpaqueBrushes++;
			maxOpaqueBrush = i;
		}
	}

	/* emit some statistics */
	Sys_FPrintf(SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes);
}



/*
ClusterVisible()
determines if two clusters are visible to each other using the PVS
*/

qboolean ClusterVisible(int a, int b)
{
	int             leafBytes;
	byte           *pvs;


	/* dummy check */
	if(a < 0 || b < 0)
		return qfalse;

	/* early out */
	if(a == b)
		return qtrue;

	/* not vised? */
	if(numBSPVisBytes <= 8)
		return qtrue;

	/* get pvs data */
	/* portalClusters = ((int *) bspVisBytes)[ 0 ]; */
	leafBytes = ((int *)bspVisBytes)[1];
	pvs = bspVisBytes + VIS_HEADER_SIZE + (a * leafBytes);

	/* check */
	if((pvs[b >> 3] & (1 << (b & 7))))
		return qtrue;
	return qfalse;
}



/*
PointInLeafNum_r()
borrowed from vlight.c
*/

int PointInLeafNum_r(vec3_t point, int nodenum)
{
	int             leafnum;
	vec_t           dist;
	bspNode_t      *node;
	bspPlane_t     *plane;


	while(nodenum >= 0)
	{
		node = &bspNodes[nodenum];
		plane = &bspPlanes[node->planeNum];
		dist = DotProduct(point, plane->normal) - plane->dist;
		if(dist > 0.1)
			nodenum = node->children[0];
		else if(dist < -0.1)
			nodenum = node->children[1];
		else
		{
			leafnum = PointInLeafNum_r(point, node->children[0]);
			if(bspLeafs[leafnum].cluster != -1)
				return leafnum;
			nodenum = node->children[1];
		}
	}

	leafnum = -nodenum - 1;
	return leafnum;
}



/*
PointInLeafnum()
borrowed from vlight.c
*/

int PointInLeafNum(vec3_t point)
{
	return PointInLeafNum_r(point, 0);
}



/*
ClusterVisibleToPoint() - ydnar
returns qtrue if point can "see" cluster
*/

qboolean ClusterVisibleToPoint(vec3_t point, int cluster)
{
	int             pointCluster;


	/* get leafNum for point */
	pointCluster = ClusterForPoint(point);
	if(pointCluster < 0)
		return qfalse;

	/* check pvs */
	return ClusterVisible(pointCluster, cluster);
}



/*
ClusterForPoint() - ydnar
returns the pvs cluster for point
*/

int ClusterForPoint(vec3_t point)
{
	int             leafNum;


	/* get leafNum for point */
	leafNum = PointInLeafNum(point);
	if(leafNum < 0)
		return -1;

	/* return the cluster */
	return bspLeafs[leafNum].cluster;
}



/*
ClusterForPointExt() - ydnar
also takes brushes into account for occlusion testing
*/

int ClusterForPointExt(vec3_t point, float epsilon)
{
	int             i, j, b, leafNum, cluster;
	float           dot;
	qboolean        inside;
	int            *brushes, numBSPBrushes;
	bspLeaf_t      *leaf;
	bspBrush_t     *brush;
	bspPlane_t     *plane;


	/* get leaf for point */
	leafNum = PointInLeafNum(point);
	if(leafNum < 0)
		return -1;
	leaf = &bspLeafs[leafNum];

	/* get the cluster */
	cluster = leaf->cluster;
	if(cluster < 0)
		return -1;

	/* transparent leaf, so check point against all brushes in the leaf */
	brushes = &bspLeafBrushes[leaf->firstBSPLeafBrush];
	numBSPBrushes = leaf->numBSPLeafBrushes;
	for(i = 0; i < numBSPBrushes; i++)
	{
		/* get parts */
		b = brushes[i];
		if(b > maxOpaqueBrush)
			continue;
		brush = &bspBrushes[b];
		if(!(opaqueBrushes[b >> 3] & (1 << (b & 7))))
			continue;

		/* check point against all planes */
		inside = qtrue;
		for(j = 0; j < brush->numSides && inside; j++)
		{
			plane = &bspPlanes[bspBrushSides[brush->firstSide + j].planeNum];
			dot = DotProduct(point, plane->normal);
			dot -= plane->dist;
			if(dot > epsilon)
				inside = qfalse;
		}

		/* if inside, return bogus cluster */
		if(inside)
			return -1 - b;
	}

	/* if the point made it this far, it's not inside any opaque brushes */
	return cluster;
}



/*
ClusterForPointExtFilter() - ydnar
adds cluster checking against a list of known valid clusters
*/

int ClusterForPointExtFilter(vec3_t point, float epsilon, int numClusters, int *clusters)
{
	int             i, cluster;


	/* get cluster for point */
	cluster = ClusterForPointExt(point, epsilon);

	/* check if filtering is necessary */
	if(cluster < 0 || numClusters <= 0 || clusters == NULL)
		return cluster;

	/* filter */
	for(i = 0; i < numClusters; i++)
	{
		if(cluster == clusters[i] || ClusterVisible(cluster, clusters[i]))
			return cluster;
	}

	/* failed */
	return -1;
}



/*
ShaderForPointInLeaf() - ydnar
checks a point against all brushes in a leaf, returning the shader of the brush
also sets the cumulative surface and content flags for the brush hit
*/

int ShaderForPointInLeaf(vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags,
						 int *surfaceFlags)
{
	int             i, j;
	float           dot;
	qboolean        inside;
	int            *brushes, numBSPBrushes;
	bspLeaf_t      *leaf;
	bspBrush_t     *brush;
	bspBrushSide_t *side;
	bspPlane_t     *plane;
	bspShader_t    *shader;
	int             allSurfaceFlags, allContentFlags;


	/* clear things out first */
	*surfaceFlags = 0;
	*contentFlags = 0;

	/* get leaf */
	if(leafNum < 0)
		return -1;
	leaf = &bspLeafs[leafNum];

	/* transparent leaf, so check point against all brushes in the leaf */
	brushes = &bspLeafBrushes[leaf->firstBSPLeafBrush];
	numBSPBrushes = leaf->numBSPLeafBrushes;
	for(i = 0; i < numBSPBrushes; i++)
	{
		/* get parts */
		brush = &bspBrushes[brushes[i]];

		/* check point against all planes */
		inside = qtrue;
		allSurfaceFlags = 0;
		allContentFlags = 0;
		for(j = 0; j < brush->numSides && inside; j++)
		{
			side = &bspBrushSides[brush->firstSide + j];
			plane = &bspPlanes[side->planeNum];
			dot = DotProduct(point, plane->normal);
			dot -= plane->dist;
			if(dot > epsilon)
				inside = qfalse;
			else
			{
				shader = &bspShaders[side->shaderNum];
				allSurfaceFlags |= shader->surfaceFlags;
				allContentFlags |= shader->contentFlags;
			}
		}

		/* handle if inside */
		if(inside)
		{
			/* if there are desired flags, check for same and continue if they aren't matched */
			if(wantContentFlags && !(wantContentFlags & allContentFlags))
				continue;
			if(wantSurfaceFlags && !(wantSurfaceFlags & allSurfaceFlags))
				continue;

			/* store the cumulative flags and return the brush shader (which is mostly useless) */
			*surfaceFlags = allSurfaceFlags;
			*contentFlags = allContentFlags;
			return brush->shaderNum;
		}
	}

	/* if the point made it this far, it's not inside any brushes */
	return -1;
}



/*
ChopBounds()
chops a bounding box by the plane defined by origin and normal
returns qfalse if the bounds is entirely clipped away

this is not exactly the fastest way to do this...
*/

qboolean ChopBounds(vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal)
{
	/* FIXME: rewrite this so it doesn't use bloody brushes */
	return qtrue;
}



/*
SetupEnvelopes()
calculates each light's effective envelope,
taking into account brightness, type, and pvs.
*/

#define LIGHT_EPSILON	0.125f
#define LIGHT_NUDGE		2.0f

void SetupEnvelopes(qboolean forGrid, qboolean fastFlag)
{
	int             i, x, y, z, x1, y1, z1;
	light_t        *light, *light2, **owner;
	bspLeaf_t      *leaf;
	vec3_t          origin, dir, mins, maxs;
	float           radius, intensity;
	light_t        *buckets[256];


	/* early out for weird cases where there are no lights */
	if(lights == NULL)
		return;

	/* note it */
	Sys_FPrintf(SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "");

	/* count lights */
	numLights = 0;
	numCulledLights = 0;
	owner = &lights;
	while(*owner != NULL)
	{
		/* get light */
		light = *owner;

		/* handle negative lights */
		if(light->photons < 0.0f || light->add < 0.0f)
		{
			light->photons *= -1.0f;
			light->add *= -1.0f;
			light->flags |= LIGHT_NEGATIVE;
		}

		/* sunlight? */
		if(light->type == EMIT_SUN)
		{
			/* special cased */
			light->cluster = 0;
			light->envelope = MAX_WORLD_COORD * 8.0f;
			VectorSet(light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f);
			VectorSet(light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f);
		}

		/* everything else */
		else
		{
			/* get pvs cluster for light */
			light->cluster = ClusterForPointExt(light->origin, LIGHT_EPSILON);

			/* invalid cluster? */
			if(light->cluster < 0)
			{
				/* nudge the sample point around a bit */
				for(x = 0; x < 4; x++)
				{
					/* two's complement 0, 1, -1, 2, -2, etc */
					x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);

					for(y = 0; y < 4; y++)
					{
						y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);

						for(z = 0; z < 4; z++)
						{
							z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);

							/* nudge origin */
							origin[0] = light->origin[0] + (LIGHT_NUDGE * x1);
							origin[1] = light->origin[1] + (LIGHT_NUDGE * y1);
							origin[2] = light->origin[2] + (LIGHT_NUDGE * z1);

							/* try at nudged origin */
							light->cluster = ClusterForPointExt(origin, LIGHT_EPSILON);
							if(light->cluster < 0)
								continue;

							/* set origin */
							VectorCopy(origin, light->origin);
						}
					}
				}
			}

			/* only calculate for lights in pvs and outside of opaque brushes */
			if(light->cluster >= 0)
			{
				/* set light fast flag */
				if(fastFlag)
					light->flags |= LIGHT_FAST_TEMP;
				else
					light->flags &= ~LIGHT_FAST_TEMP;
				if(light->si && light->si->noFast)
					light->flags &= ~(LIGHT_FAST | LIGHT_FAST_TEMP);

				/* clear light envelope */
				light->envelope = 0;

				/* handle area lights */
				if(exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL)
				{
					/* ugly hack to calculate extent for area lights, but only done once */
					VectorScale(light->normal, -1.0f, dir);
					for(radius = 100.0f; radius < 130000.0f && light->envelope == 0; radius += 10.0f)
					{
						float           factor;

						VectorMA(light->origin, radius, light->normal, origin);
						factor = PointToPolygonFormFactor(origin, dir, light->w);
						if(factor < 0.0f)
							factor *= -1.0f;
						if((factor * light->add) <= light->falloffTolerance)
							light->envelope = radius;
					}

					/* check for fast mode */
					if(!(light->flags & LIGHT_FAST) && !(light->flags & LIGHT_FAST_TEMP))
						light->envelope = MAX_WORLD_COORD * 8.0f;
					intensity = light->photons;	/* hopefully not used */
				}
				else
				{
					radius = 0.0f;
					intensity = light->photons;
				}

				/* other calcs */
				if(light->envelope <= 0.0f)
				{
					/* solve distance for non-distance lights */
					if(!(light->flags & LIGHT_ATTEN_DISTANCE))
						light->envelope = MAX_WORLD_COORD * 8.0f;

					/* solve distance for linear lights */
					else if((light->flags & LIGHT_ATTEN_LINEAR))
						//% light->envelope = ((intensity / light->falloffTolerance) * linearScale - 1 + radius) / light->fade;
						light->envelope = ((intensity * linearScale) - light->falloffTolerance) / light->fade;

					/*
					   add = angle * light->photons * linearScale - (dist * light->fade);
					   T = (light->photons * linearScale) - (dist * light->fade);
					   T + (dist * light->fade) = (light->photons * linearScale);
					   dist * light->fade = (light->photons * linearScale) - T;
					   dist = ((light->photons * linearScale) - T) / light->fade;
					 */

					/* solve for inverse square falloff */
					else
						light->envelope = sqrt(intensity / light->falloffTolerance) + radius;

					/*
					   add = light->photons / (dist * dist);
					   T = light->photons / (dist * dist);
					   T * (dist * dist) = light->photons;
					   dist = sqrt( light->photons / T );
					 */
				}

				/* chop radius against pvs */
				{
					/* clear bounds */
					ClearBounds(mins, maxs);

					/* check all leaves */
					for(i = 0; i < numBSPLeafs; i++)
					{
						/* get test leaf */
						leaf = &bspLeafs[i];

						/* in pvs? */
						if(leaf->cluster < 0)
							continue;
						if(ClusterVisible(light->cluster, leaf->cluster) == qfalse)	/* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */
							continue;

						/* add this leafs bbox to the bounds */
						VectorCopy(leaf->mins, origin);
						AddPointToBounds(origin, mins, maxs);
						VectorCopy(leaf->maxs, origin);
						AddPointToBounds(origin, mins, maxs);
					}

					/* test to see if bounds encompass light */
					for(i = 0; i < 3; i++)
					{
						if(mins[i] > light->origin[i] || maxs[i] < light->origin[i])
						{
							//% Sys_Printf( "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n",
							//%     mins[ 0 ], mins[ 1 ], mins[ 2 ],
							//%     maxs[ 0 ], maxs[ 1 ], maxs[ 2 ],
							//%     numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] );
							AddPointToBounds(light->origin, mins, maxs);
						}
					}

					/* chop the bounds by a plane for area lights and spotlights */
					if(light->type == EMIT_AREA || light->type == EMIT_SPOT)
						ChopBounds(mins, maxs, light->origin, light->normal);

					/* copy bounds */
					VectorCopy(mins, light->mins);
					VectorCopy(maxs, light->maxs);

					/* reflect bounds around light origin */
					//% VectorMA( light->origin, -1.0f, origin, origin );
					VectorScale(light->origin, 2, origin);
					VectorSubtract(origin, maxs, origin);
					AddPointToBounds(origin, mins, maxs);
					//% VectorMA( light->origin, -1.0f, mins, origin );
					VectorScale(light->origin, 2, origin);
					VectorSubtract(origin, mins, origin);
					AddPointToBounds(origin, mins, maxs);

					/* calculate spherical bounds */
					VectorSubtract(maxs, light->origin, dir);
					radius = (float)VectorLength(dir);

					/* if this radius is smaller than the envelope, then set the envelope to it */
					if(radius < light->envelope)
					{
						light->envelope = radius;
						//% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights );
					}
					//% else
					//%     Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope );
				}

				/* add grid/surface only check */
				if(forGrid)
				{
					if(!(light->flags & LIGHT_GRID))
						light->envelope = 0.0f;
				}
				else
				{
					if(!(light->flags & LIGHT_SURFACES))
						light->envelope = 0.0f;
				}
			}

			/* culled? */
			if(light->cluster < 0 || light->envelope <= 0.0f)
			{
				/* debug code */
				//% Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope );

				/* delete the light */
				numCulledLights++;
				*owner = light->next;
				if(light->w != NULL)
					free(light->w);
				free(light);
				continue;
			}
		}

		/* square envelope */
		light->envelope2 = (light->envelope * light->envelope);

		/* increment light count */
		numLights++;

		/* set next light */
		owner = &((**owner).next);
	}

	/* bucket sort lights by style */
	memset(buckets, 0, sizeof(buckets));
	light2 = NULL;
	for(light = lights; light != NULL; light = light2)
	{
		/* get next light */
		light2 = light->next;

		/* filter into correct bucket */
		light->next = buckets[light->style];
		buckets[light->style] = light;

		/* if any styled light is present, automatically set nocollapse */
		if(light->style != LS_NORMAL)
			noCollapse = qtrue;
	}

	/* filter back into light list */
	lights = NULL;
	for(i = 255; i >= 0; i--)
	{
		light2 = NULL;
		for(light = buckets[i]; light != NULL; light = light2)
		{
			light2 = light->next;
			light->next = lights;
			lights = light;
		}
	}

	/* emit some statistics */
	Sys_Printf("%9d total lights\n", numLights);
	Sys_Printf("%9d culled lights\n", numCulledLights);
}



/*
CreateTraceLightsForBounds()
creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves)
*/

void CreateTraceLightsForBounds(vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags,
								trace_t * trace)
{
	int             i;
	light_t        *light;
	vec3_t          origin, dir, nullVector = { 0.0f, 0.0f, 0.0f };
	float           radius, dist, length;


	/* potential pre-setup  */
	if(numLights == 0)
		SetupEnvelopes(qfalse, fast);

	/* debug code */
	//% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );

	/* allocate the light list */
	trace->lights = safe_malloc(sizeof(light_t *) * (numLights + 1));
	trace->numLights = 0;

	/* calculate spherical bounds */
	VectorAdd(mins, maxs, origin);
	VectorScale(origin, 0.5f, origin);
	VectorSubtract(maxs, origin, dir);
	radius = (float)VectorLength(dir);

	/* get length of normal vector */
	if(normal != NULL)
		length = VectorLength(normal);
	else
	{
		normal = nullVector;
		length = 0;
	}

	/* test each light and see if it reaches the sphere */
	/* note: the attenuation code MUST match LightingAtSample() */
	for(light = lights; light; light = light->next)
	{
		/* check zero sized envelope */
		if(light->envelope <= 0)
		{
			lightsEnvelopeCulled++;
			continue;
		}

		/* check flags */
		if(!(light->flags & flags))
			continue;

		/* sunlight skips all this nonsense */
		if(light->type != EMIT_SUN)
		{
			/* sun only? */
			if(sunOnly)
				continue;

			/* check against pvs cluster */
			if(numClusters > 0 && clusters != NULL)
			{
				for(i = 0; i < numClusters; i++)
				{
					if(ClusterVisible(light->cluster, clusters[i]))
						break;
				}

				/* fixme! */
				if(i == numClusters)
				{
					lightsClusterCulled++;
					continue;
				}
			}

			/* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */
			VectorSubtract(light->origin, origin, dir);
			dist = VectorLength(dir);
			dist -= light->envelope;
			dist -= radius;
			if(dist > 0)
			{
				lightsEnvelopeCulled++;
				continue;
			}

			/* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */
#if 0
			skip = qfalse;
			for(i = 0; i < 3; i++)
			{
				if(mins[i] > light->maxs[i] || maxs[i] < light->mins[i])
					skip = qtrue;
			}
			if(skip)
			{
				lightsBoundsCulled++;
				continue;
			}
#endif
		}

		/* planar surfaces (except twosided surfaces) have a couple more checks */
		if(length > 0.0f && trace->twoSided == qfalse)
		{
			/* lights coplanar with a surface won't light it */
			if(!(light->flags & LIGHT_TWOSIDED) && DotProduct(light->normal, normal) > 0.999f)
			{
				lightsPlaneCulled++;
				continue;
			}

			/* check to see if light is behind the plane */
			if(DotProduct(light->origin, normal) - DotProduct(origin, normal) < -1.0f)
			{
				lightsPlaneCulled++;
				continue;
			}
		}

		/* add this light */
		trace->lights[trace->numLights++] = light;
	}

	/* make last night null */
	trace->lights[trace->numLights] = NULL;
}



void FreeTraceLights(trace_t * trace)
{
	if(trace->lights != NULL)
		free(trace->lights);
}



/*
CreateTraceLightsForSurface()
creates a list of lights that can potentially affect a drawsurface
*/

void CreateTraceLightsForSurface(int num, trace_t * trace)
{
	int             i;
	vec3_t          mins, maxs, normal;
	bspDrawVert_t  *dv;
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info;


	/* dummy check */
	if(num < 0)
		return;

	/* get drawsurface and info */
	ds = &bspDrawSurfaces[num];
	info = &surfaceInfos[num];

	/* get the mins/maxs for the dsurf */
	ClearBounds(mins, maxs);
	VectorCopy(bspDrawVerts[ds->firstVert].normal, normal);
	for(i = 0; i < ds->numVerts; i++)
	{
		dv = &yDrawVerts[ds->firstVert + i];
		AddPointToBounds(dv->xyz, mins, maxs);
		if(!VectorCompare(dv->normal, normal))
			VectorClear(normal);
	}

	/* create the lights for the bounding box */
	CreateTraceLightsForBounds(mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[info->firstSurfaceCluster],
							   LIGHT_SURFACES, trace);
}

/////////////////////////////////////////////////////////////

#define FLOODLIGHT_CONE_ANGLE			88	/* degrees */
#define FLOODLIGHT_NUM_ANGLE_STEPS		16
#define FLOODLIGHT_NUM_ELEVATION_STEPS	4
#define FLOODLIGHT_NUM_VECTORS			(FLOODLIGHT_NUM_ANGLE_STEPS * FLOODLIGHT_NUM_ELEVATION_STEPS)

static vec3_t   floodVectors[FLOODLIGHT_NUM_VECTORS];
static int      numFloodVectors = 0;

void SetupFloodLight(void)
{
	int             i, j;
	float           angle, elevation, angleStep, elevationStep;
	const char     *value;
	double          v1, v2, v3, v4, v5, v6;

	/* note it */
	Sys_FPrintf(SYS_VRB, "--- SetupFloodLight ---\n");

	/* calculate angular steps */
	angleStep = DEG2RAD(360.0f / FLOODLIGHT_NUM_ANGLE_STEPS);
	elevationStep = DEG2RAD(FLOODLIGHT_CONE_ANGLE / FLOODLIGHT_NUM_ELEVATION_STEPS);

	/* iterate angle */
	angle = 0.0f;
	for(i = 0, angle = 0.0f; i < FLOODLIGHT_NUM_ANGLE_STEPS; i++, angle += angleStep)
	{
		/* iterate elevation */
		for(j = 0, elevation = elevationStep * 0.5f; j < FLOODLIGHT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep)
		{
			floodVectors[numFloodVectors][0] = sin(elevation) * cos(angle);
			floodVectors[numFloodVectors][1] = sin(elevation) * sin(angle);
			floodVectors[numFloodVectors][2] = cos(elevation);
			numFloodVectors++;
		}
	}

	/* emit some statistics */
	Sys_FPrintf(SYS_VRB, "%9d numFloodVectors\n", numFloodVectors);

	/* floodlight */
	value = ValueForKey(&entities[0], "_floodlight");

	if(value[0] != '\0')
	{
		v1 = v2 = v3 = 0;
		v4 = floodlightDistance;
		v5 = floodlightIntensity;
		v6 = floodlightDirectionScale;

		sscanf(value, "%lf %lf %lf %lf %lf %lf", &v1, &v2, &v3, &v4, &v5, &v6);

		floodlightRGB[0] = v1;
		floodlightRGB[1] = v2;
		floodlightRGB[2] = v3;

		if(VectorLength(floodlightRGB) == 0)
		{
			VectorSet(floodlightRGB, 240, 240, 255);
		}

		if(v4 < 1)
			v4 = 1024;
		if(v5 < 1)
			v5 = 128;
		if(v6 < 0)
			v6 = 1;

		floodlightDistance = v4;
		floodlightIntensity = v5;
		floodlightDirectionScale = v6;

		floodlighty = qtrue;
		Sys_Printf("FloodLighting enabled via worldspawn _floodlight key.\n");
	}
	else
	{
		VectorSet(floodlightRGB, 240, 240, 255);
		//floodlighty = qtrue;
		//Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" );
	}
	VectorNormalize(floodlightRGB);
}

/*
FloodLightForSample()
calculates floodlight value for a given sample
once again, kudos to the dirtmapping coder
*/

float FloodLightForSample(trace_t * trace, float floodLightDistance, qboolean floodLightLowQuality)
{
	int             i;
	float           d;
	float           contribution;
	int             sub = 0;
	float           gatherLight, outLight;
	vec3_t          normal, worldUp, myUp, myRt, direction, displacement;
	float           dd;
	int             vecs = 0;

	gatherLight = 0;
	/* dummy check */
	//if( !dirty )
	//  return 1.0f;
	if(trace == NULL || trace->cluster < 0)
		return 0.0f;


	/* setup */
	dd = floodLightDistance;
	VectorCopy(trace->normal, normal);

	/* check if the normal is aligned to the world-up */
	if(normal[0] == 0.0f && normal[1] == 0.0f && (normal[2] == 1.0f || normal[2] == -1.0f))
	{
		if(normal[2] == 1.0f)
		{
			VectorSet(myRt, 1.0f, 0.0f, 0.0f);
			VectorSet(myUp, 0.0f, 1.0f, 0.0f);
		}
		else if(normal[2] == -1.0f)
		{
			VectorSet(myRt, -1.0f, 0.0f, 0.0f);
			VectorSet(myUp, 0.0f, 1.0f, 0.0f);
		}
	}
	else
	{
		VectorSet(worldUp, 0.0f, 0.0f, 1.0f);
		CrossProduct(normal, worldUp, myRt);
		VectorNormalize(myRt);
		CrossProduct(myRt, normal, myUp);
		VectorNormalize(myUp);
	}

	/* vortex: optimise floodLightLowQuality a bit */
	if(floodLightLowQuality == qtrue)
	{
		/* iterate through ordered vectors */
		for(i = 0; i < numFloodVectors; i++)
			if(rand() % 10 != 0)
				continue;
	}
	else
	{
		/* iterate through ordered vectors */
		for(i = 0; i < numFloodVectors; i++)
		{
			vecs++;

			/* transform vector into tangent space */
			direction[0] = myRt[0] * floodVectors[i][0] + myUp[0] * floodVectors[i][1] + normal[0] * floodVectors[i][2];
			direction[1] = myRt[1] * floodVectors[i][0] + myUp[1] * floodVectors[i][1] + normal[1] * floodVectors[i][2];
			direction[2] = myRt[2] * floodVectors[i][0] + myUp[2] * floodVectors[i][1] + normal[2] * floodVectors[i][2];

			/* set endpoint */
			VectorMA(trace->origin, dd, direction, trace->end);

			//VectorMA( trace->origin, 1, direction, trace->origin );

			SetupTrace(trace);
			/* trace */
			TraceLine(trace);
			contribution = 1;

			if(trace->compileFlags & C_SKY || trace->compileFlags & C_TRANSLUCENT)
			{
				contribution = 1.0f;
			}
			else if(trace->opaque)
			{
				VectorSubtract(trace->hit, trace->origin, displacement);
				d = VectorLength(displacement);

				// d=trace->distance;
				//if (d>256) gatherDirt+=1;
				contribution = d / dd;
				if(contribution > 1)
					contribution = 1.0f;

				//gatherDirt += 1.0f - ooDepth * VectorLength( displacement );
			}

			gatherLight += contribution;
		}
	}

	/* early out */
	if(gatherLight <= 0.0f)
		return 0.0f;

	sub = vecs;

	if(sub < 1)
		sub = 1;
	gatherLight /= (sub);

	outLight = gatherLight;
	if(outLight > 1.0f)
		outLight = 1.0f;

	/* return to sender */
	return outLight;
}

/*
FloodLightRawLightmap
lighttracer style ambient occlusion light hack.
Kudos to the dirtmapping author for most of this source.
VorteX: modified to floodlight up custom surfaces (q3map_floodLight)
VorteX: fixed problems with deluxemapping
*/

// floodlight pass on a lightmap
void FloodLightRawLightmapPass(rawLightmap_t * lm, vec3_t lmFloodLightRGB, float lmFloodLightIntensity,
							   float lmFloodLightDistance, qboolean lmFloodLightLowQuality, float floodlightDirectionScale)
{
	int             i, x, y, *cluster;
	float          *origin, *normal, *floodlight, floodLightAmount;
	surfaceInfo_t  *info;
	trace_t         trace;

	// int sx, sy;
	// float samples, average, *floodlight2;

	memset(&trace, 0, sizeof(trace_t));

	/* setup trace */
	trace.testOcclusion = qtrue;
	trace.forceSunlight = qfalse;
	trace.twoSided = qtrue;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[lm->firstLightSurface];
	trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
	trace.testAll = qfalse;
	trace.distance = 1024;

	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	//trace.twoSided = qfalse;
	for(i = 0; i < trace.numSurfaces; i++)
	{
		/* get surface */
		info = &surfaceInfos[trace.surfaces[i]];

		/* check twosidedness */
		if(info->si->twoSided)
		{
			trace.twoSided = qtrue;
			break;
		}
	}

	/* gather floodlight */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			cluster = SUPER_CLUSTER(x, y);
			origin = SUPER_ORIGIN(x, y);
			normal = SUPER_NORMAL(x, y);
			floodlight = SUPER_FLOODLIGHT(x, y);

			/* set default dirt */
			*floodlight = 0.0f;

			/* only look at mapped luxels */
			if(*cluster < 0)
				continue;

			/* copy to trace */
			trace.cluster = *cluster;
			VectorCopy(origin, trace.origin);
			VectorCopy(normal, trace.normal);

			/* get floodlight */
			floodLightAmount = FloodLightForSample(&trace, lmFloodLightDistance, lmFloodLightLowQuality) * lmFloodLightIntensity;

			/* add floodlight */
			floodlight[0] += lmFloodLightRGB[0] * floodLightAmount;
			floodlight[1] += lmFloodLightRGB[1] * floodLightAmount;
			floodlight[2] += lmFloodLightRGB[2] * floodLightAmount;
			floodlight[3] += floodlightDirectionScale;
		}
	}

	/* testing no filtering */
	return;

#if 0

	/* filter "dirt" */
	for(y = 0; y < lm->sh; y++)
	{
		for(x = 0; x < lm->sw; x++)
		{
			/* get luxel */
			cluster = SUPER_CLUSTER(x, y);
			floodlight = SUPER_FLOODLIGHT(x, y);

			/* filter dirt by adjacency to unmapped luxels */
			average = *floodlight;
			samples = 1.0f;
			for(sy = (y - 1); sy <= (y + 1); sy++)
			{
				if(sy < 0 || sy >= lm->sh)
					continue;

				for(sx = (x - 1); sx <= (x + 1); sx++)
				{
					if(sx < 0 || sx >= lm->sw || (sx == x && sy == y))
						continue;

					/* get neighboring luxel */
					cluster = SUPER_CLUSTER(sx, sy);
					floodlight2 = SUPER_FLOODLIGHT(sx, sy);
					if(*cluster < 0 || *floodlight2 <= 0.0f)
						continue;

					/* add it */
					average += *floodlight2;
					samples += 1.0f;
				}

				/* bail */
				if(samples <= 0.0f)
					break;
			}

			/* bail */
			if(samples <= 0.0f)
				continue;

			/* scale dirt */
			*floodlight = average / samples;
		}
	}
#endif
}

void FloodLightRawLightmap(int rawLightmapNum)
{
	rawLightmap_t  *lm;

	/* bail if this number exceeds the number of raw lightmaps */
	if(rawLightmapNum >= numRawLightmaps)
		return;
	/* get lightmap */
	lm = &rawLightmaps[rawLightmapNum];

	/* global pass */
	if(floodlighty && floodlightIntensity)
		FloodLightRawLightmapPass(lm, floodlightRGB, floodlightIntensity, floodlightDistance, floodlight_lowquality,
								  floodlightDirectionScale);

	/* custom pass */
	if(lm->floodlightIntensity)
	{
		FloodLightRawLightmapPass(lm, lm->floodlightRGB, lm->floodlightIntensity, lm->floodlightDistance, qfalse,
								  lm->floodlightDirectionScale);
		numSurfacesFloodlighten += 1;
	}
}

void FloodlightRawLightmaps(void)
{
	Sys_Printf("--- FloodlightRawLightmap ---\n");
	numSurfacesFloodlighten = 0;
	RunThreadsOnIndividual(numRawLightmaps, qtrue, FloodLightRawLightmap);
	Sys_Printf("%9d custom lightmaps floodlighted\n", numSurfacesFloodlighten);
}

/*
FloodLightIlluminate()
illuminate floodlight into lightmap luxels
*/

void FloodlightIlluminateLightmap(rawLightmap_t * lm)
{
	float          *luxel, *floodlight, *deluxel, *normal;
	int            *cluster;
	float           brightness;
	int             x, y, lightmapNum;

	/* walk lightmaps */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
	{
		/* early out */
		if(lm->superLuxels[lightmapNum] == NULL)
			continue;

		/* apply floodlight to each luxel */
		for(y = 0; y < lm->sh; y++)
		{
			for(x = 0; x < lm->sw; x++)
			{
				/* get floodlight */
				floodlight = SUPER_FLOODLIGHT(x, y);
				if(!floodlight[0] && !floodlight[1] && !floodlight[2])
					continue;

				/* get cluster */
				cluster = SUPER_CLUSTER(x, y);

				/* only process mapped luxels */
				if(*cluster < 0)
					continue;

				/* get particulars */
				luxel = SUPER_LUXEL(lightmapNum, x, y);
				deluxel = SUPER_DELUXEL(x, y);

				/* add to lightmap */
				luxel[0] += floodlight[0];
				luxel[1] += floodlight[1];
				luxel[2] += floodlight[2];

				if(luxel[3] == 0)
					luxel[3] = 1;

				/* add to deluxemap */
				if(deluxemap && floodlight[3] > 0)
				{
					vec3_t          lightvector;

					normal = SUPER_NORMAL(x, y);
					brightness = RGBTOGRAY(floodlight) * (1.0f / 255.0f) * floodlight[3];

					// use AT LEAST this amount of contribution from ambient for the deluxemap, fixes points that receive ZERO light
					if(brightness < 0.00390625f)
						brightness = 0.00390625f;

					VectorScale(normal, brightness, lightvector);
					VectorAdd(deluxel, lightvector, deluxel);
				}
			}
		}
	}
}
