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

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 LIGHTMAPS_YDNAR_C



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


//#define USE_HDR_LIGHTMAPS


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

this file contains code that doe lightmap allocation and projection that
runs in the -light phase.

this is handled here rather than in the bsp phase for a few reasons--
surfaces are no longer necessarily convex polygons, patches may or may not be
planar or have lightmaps projected directly onto control points.

also, this allows lightmaps to be calculated before being allocated and stored
in the bsp. lightmaps that have little high-frequency information are candidates
for having their resolutions scaled down.

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

/*
WriteTGA24()
based on WriteTGA() from imagelib.c
*/

void WriteTGA24(char *filename, byte * data, int width, int height, qboolean flip)
{
	int             i, c;
	byte           *buffer, *in;
	FILE           *file;


	/* allocate a buffer and set it up */
	buffer = safe_malloc(width * height * 3 + 18);
	memset(buffer, 0, 18);
	buffer[2] = 2;
	buffer[12] = width & 255;
	buffer[13] = width >> 8;
	buffer[14] = height & 255;
	buffer[15] = height >> 8;
	buffer[16] = 24;

	/* swap rgb to bgr */
	c = (width * height * 3) + 18;
	for(i = 18; i < c; i += 3)
	{
		buffer[i] = data[i - 18 + 2];	/* blue */
		buffer[i + 1] = data[i - 18 + 1];	/* green */
		buffer[i + 2] = data[i - 18 + 0];	/* red */
	}

	/* write it and free the buffer */
	file = fopen(filename, "wb");
	if(file == NULL)
		Error("Unable to open %s for writing", filename);

	/* flip vertically? */
	if(flip)
	{
		fwrite(buffer, 1, 18, file);
		for(in = buffer + ((height - 1) * width * 3) + 18; in >= buffer; in -= (width * 3))
			fwrite(in, 1, (width * 3), file);
	}
	else
		fwrite(buffer, 1, c, file);

	/* close the file */
	fclose(file);
	free(buffer);
}

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



/*
WriteRGBE()
*/

void WriteRGBE(char *filename, float * data, int width, int height)
{
	int             i, c;
	FILE           *file;
	//unsigned char   rgbe[4];
	float			rgb[3];

	file = fopen(filename, "wb");
	if(file == NULL)
		Error("Unable to open %s for writing", filename);


	// write header
	if(fprintf(file,"#?RGBE\n") < 0)
		Error("RGBE write error: %s", filename);

	if(fprintf(file,"# Generated by OWMap\n") < 0)
			Error("RGBE write error: %s", filename);

	if(fprintf(file, "FORMAT=32-bit_rle_rgbe\n\n") < 0)
		Error("RGBE write error: %s", filename);

	if(fprintf(file, "-Y %d +X %d\n", height, width) < 0)
		Error("RGBE write error: %s", filename);

	c = (width * height * 3);
	for(i = 0; i < c; i += 3)
	{
#if 0
		// FIXME XMap2's output is 255 times too high
		rgb[0] = data[i + 0] / 255.0f;
		rgb[1] = data[i + 1] / 255.0f;
		rgb[2] = data[i + 2] / 255.0f;

		ColorToRGBE(rgb, rgbe);
		if(fwrite(rgbe, sizeof(rgbe), 1, file) < 1)
			Error("RGBE write error: %s", filename);
#else
		rgb[0] = data[i + 0];
		rgb[1] = data[i + 1];
		rgb[2] = data[i + 2];

		if(fwrite(rgb, sizeof(rgb), 1, file) < 1)
			Error("RGBE write error: %s", filename);
#endif
	}

	// close the file
	fclose(file);
}

/*
ExportLightmaps()
exports the lightmaps as a list of numbered tga images
*/

void ExportLightmaps(void)
{
	int             i;
	char            dirname[1024], filename[1024];
	byte           *lightmap;


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

	/* do some path mangling */
	strcpy(dirname, source);
	StripExtension(dirname);

	/* sanity check */
	if(bspLightBytes == NULL)
	{
		Sys_Printf("WARNING: No BSP lightmap data\n");
		return;
	}

	/* make a directory for the lightmaps */
	Q_mkdir(dirname);

	/* iterate through the lightmaps */
	for(i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes);
		i++, lightmap += (game->lightmapSize * game->lightmapSize * 3))
	{
		/* write a tga image out */
		sprintf(filename, "%s/lightmap_%04d.tga", dirname, i);
		Sys_Printf("Writing %s\n", filename);
		WriteTGA24(filename, lightmap, game->lightmapSize, game->lightmapSize, qfalse);
	}
}



/*
ExportLightmapsMain()
exports the lightmaps as a list of numbered tga images
*/

int ExportLightmapsMain(int argc, char **argv)
{
	/* arg checking */
	if(argc < 1)
	{
		Sys_Printf("Usage: owmap -export [-v] <mapname>\n");
		return 0;
	}

	/* do some path mangling */
	strcpy(source, ExpandArg(argv[argc - 1]));
	StripExtension(source);
	DefaultExtension(source, ".bsp");

	/* load the bsp */
	Sys_Printf("Loading %s\n", source);
	LoadBSPFile(source);

	/* export the lightmaps */
	ExportLightmaps();

	/* return to sender */
	return 0;
}



/*
ImportLightmapsMain()
imports the lightmaps from a list of numbered tga images
*/

int ImportLightmapsMain(int argc, char **argv)
{
	int             i, x, y, len, width, height;
	char            dirname[1024], filename[1024];
	byte           *lightmap, *buffer, *pixels, *in, *out;


	/* arg checking */
	if(argc < 1)
	{
		Sys_Printf("Usage: owmap -import [-v] <mapname>\n");
		return 0;
	}

	/* do some path mangling */
	strcpy(source, ExpandArg(argv[argc - 1]));
	StripExtension(source);
	DefaultExtension(source, ".bsp");

	/* load the bsp */
	Sys_Printf("Loading %s\n", source);
	LoadBSPFile(source);

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

	/* do some path mangling */
	strcpy(dirname, source);
	StripExtension(dirname);

	/* sanity check */
	if(bspLightBytes == NULL)
		Error("No lightmap data");

	/* make a directory for the lightmaps */
	Q_mkdir(dirname);

	/* iterate through the lightmaps */
	for(i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes);
		i++, lightmap += (game->lightmapSize * game->lightmapSize * 3))
	{
		/* read a tga image */
		sprintf(filename, "%s/lightmap_%04d.tga", dirname, i);
		Sys_Printf("Loading %s\n", filename);
		buffer = NULL;
		len = vfsLoadFile(filename, (void *)&buffer, -1);
		if(len < 0)
		{
			Sys_Printf("WARNING: Unable to load image %s\n", filename);
			continue;
		}

		/* parse file into an image */
		pixels = NULL;
		LoadTGABuffer(buffer, &pixels, &width, &height);
		free(buffer);

		/* sanity check it */
		if(pixels == NULL)
		{
			Sys_Printf("WARNING: Unable to load image %s\n", filename);
			continue;
		}
		if(width != game->lightmapSize || height != game->lightmapSize)
			Sys_Printf("WARNING: Image %s is not the right size (%d, %d) != (%d, %d)\n",
					   filename, width, height, game->lightmapSize, game->lightmapSize);

		/* copy the pixels */
		in = pixels;
		for(y = 1; y <= game->lightmapSize; y++)
		{
			out = lightmap + ((game->lightmapSize - y) * game->lightmapSize * 3);
			for(x = 0; x < game->lightmapSize; x++, in += 4, out += 3)
				VectorCopy(in, out);
		}

		/* free the image */
		free(pixels);
	}

	/* write the bsp */
	Sys_Printf("writing %s\n", source);
	WriteBSPFile(source);

	/* return to sender */
	return 0;
}



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

this section deals with projecting a lightmap onto a raw drawsurface

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

/*
CompareLightSurface()
compare function for qsort()
*/

static int CompareLightSurface(const void *a, const void *b)
{
	shaderInfo_t   *asi, *bsi;


	/* get shaders */
	asi = surfaceInfos[*((const int *)a)].si;
	bsi = surfaceInfos[*((const int *)b)].si;

	/* dummy check */
	if(asi == NULL)
		return -1;
	if(bsi == NULL)
		return 1;

	/* compare shader names */
	return strcmp(asi->shader, bsi->shader);
}



/*
FinishRawLightmap()
allocates a raw lightmap's necessary buffers
*/

void FinishRawLightmap(rawLightmap_t * lm)
{
	int             i, j, c, size, *sc;
	float           is;
	surfaceInfo_t  *info;


	/* sort light surfaces by shader name */
	qsort(&lightSurfaces[lm->firstLightSurface], lm->numLightSurfaces, sizeof(int), CompareLightSurface);

	/* count clusters */
	lm->numLightClusters = 0;
	for(i = 0; i < lm->numLightSurfaces; i++)
	{
		/* get surface info */
		info = &surfaceInfos[lightSurfaces[lm->firstLightSurface + i]];

		/* add surface clusters */
		lm->numLightClusters += info->numSurfaceClusters;
	}

	/* allocate buffer for clusters and copy */
	lm->lightClusters = safe_malloc(lm->numLightClusters * sizeof(*lm->lightClusters));
	c = 0;
	for(i = 0; i < lm->numLightSurfaces; i++)
	{
		/* get surface info */
		info = &surfaceInfos[lightSurfaces[lm->firstLightSurface + i]];

		/* add surface clusters */
		for(j = 0; j < info->numSurfaceClusters; j++)
			lm->lightClusters[c++] = surfaceClusters[info->firstSurfaceCluster + j];
	}

	/* set styles */
	lm->styles[0] = LS_NORMAL;
	for(i = 1; i < MAX_LIGHTMAPS; i++)
		lm->styles[i] = LS_NONE;

	/* set supersampling size */
	lm->sw = lm->w * superSample;
	lm->sh = lm->h * superSample;

	/* add to super luxel count */
	numRawSuperLuxels += (lm->sw * lm->sh);

	/* manipulate origin/vecs for supersampling */
	if(superSample > 1 && lm->vecs != NULL)
	{
		/* calc inverse supersample */
		is = 1.0f / superSample;

		/* scale the vectors and shift the origin */
#if 1
		/* new code that works for arbitrary supersampling values */
		VectorMA(lm->origin, -0.5, lm->vecs[0], lm->origin);
		VectorMA(lm->origin, -0.5, lm->vecs[1], lm->origin);
		VectorScale(lm->vecs[0], is, lm->vecs[0]);
		VectorScale(lm->vecs[1], is, lm->vecs[1]);
		VectorMA(lm->origin, is, lm->vecs[0], lm->origin);
		VectorMA(lm->origin, is, lm->vecs[1], lm->origin);
#else
		/* old code that only worked with a value of 2 */
		VectorScale(lm->vecs[0], is, lm->vecs[0]);
		VectorScale(lm->vecs[1], is, lm->vecs[1]);
		VectorMA(lm->origin, -is, lm->vecs[0], lm->origin);
		VectorMA(lm->origin, -is, lm->vecs[1], lm->origin);
#endif
	}

	/* allocate bsp lightmap storage */
	size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof(float);
	if(lm->bspLuxels[0] == NULL)
		lm->bspLuxels[0] = safe_malloc(size);
	memset(lm->bspLuxels[0], 0, size);

	/* allocate radiosity lightmap storage */
	if(bounce)
	{
		size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof(float);
		if(lm->radLuxels[0] == NULL)
			lm->radLuxels[0] = safe_malloc(size);
		memset(lm->radLuxels[0], 0, size);
	}

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

	/* allocate origin map storage */
	size = lm->sw * lm->sh * SUPER_ORIGIN_SIZE * sizeof(float);
	if(lm->superOrigins == NULL)
		lm->superOrigins = safe_malloc(size);
	memset(lm->superOrigins, 0, size);

	/* allocate normal map storage */
	size = lm->sw * lm->sh * SUPER_NORMAL_SIZE * sizeof(float);
	if(lm->superNormals == NULL)
		lm->superNormals = safe_malloc(size);
	memset(lm->superNormals, 0, size);

	/* allocate floodlight map storage */
	size = lm->sw * lm->sh * SUPER_FLOODLIGHT_SIZE * sizeof(float);
	if(lm->superFloodLight == NULL)
		lm->superFloodLight = safe_malloc(size);
	memset(lm->superFloodLight, 0, size);

	/* allocate cluster map storage */
	size = lm->sw * lm->sh * sizeof(int);
	if(lm->superClusters == NULL)
		lm->superClusters = safe_malloc(size);
	size = lm->sw * lm->sh;
	sc = lm->superClusters;
	for(i = 0; i < size; i++)
		(*sc++) = CLUSTER_UNMAPPED;

	/* deluxemap allocation */
	if(deluxemap)
	{
		/* allocate sampling deluxel storage */
		size = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof(float);
		if(lm->superDeluxels == NULL)
			lm->superDeluxels = safe_malloc(size);
		memset(lm->superDeluxels, 0, size);

		/* allocate bsp deluxel storage */
		size = lm->w * lm->h * BSP_DELUXEL_SIZE * sizeof(float);
		if(lm->bspDeluxels == NULL)
			lm->bspDeluxels = safe_malloc(size);
		memset(lm->bspDeluxels, 0, size);
	}

	/* add to count */
	numLuxels += (lm->sw * lm->sh);
}



/*
AddPatchToRawLightmap()
projects a lightmap for a patch surface
since lightmap calculation for surfaces is now handled in a general way (light_ydnar.c),
it is no longer necessary for patch verts to fall exactly on a lightmap sample
based on AllocateLightmapForPatch()
*/

qboolean AddPatchToRawLightmap(int num, rawLightmap_t * lm)
{
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info;
	int             x, y;
	bspDrawVert_t  *verts, *a, *b;
	vec3_t          delta;
	mesh_t          src, *subdivided, *mesh;
	float           sBasis, tBasis, s, t;
	float           length, widthTable[MAX_EXPANDED_AXIS], heightTable[MAX_EXPANDED_AXIS];


	/* patches finish a raw lightmap */
	lm->finished = qtrue;

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

	/* make a temporary 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);

	/* find the longest distance on each row/column */
	verts = mesh->verts;
	memset(widthTable, 0, sizeof(widthTable));
	memset(heightTable, 0, sizeof(heightTable));
	for(y = 0; y < mesh->height; y++)
	{
		for(x = 0; x < mesh->width; x++)
		{
			/* get width */
			if(x + 1 < mesh->width)
			{
				a = &verts[(y * mesh->width) + x];
				b = &verts[(y * mesh->width) + x + 1];
				VectorSubtract(a->xyz, b->xyz, delta);
				length = VectorLength(delta);
				if(length > widthTable[x])
					widthTable[x] = length;
			}

			/* get height */
			if(y + 1 < mesh->height)
			{
				a = &verts[(y * mesh->width) + x];
				b = &verts[((y + 1) * mesh->width) + x];
				VectorSubtract(a->xyz, b->xyz, delta);
				length = VectorLength(delta);
				if(length > heightTable[y])
					heightTable[y] = length;
			}
		}
	}

	/* determine lightmap width */
	length = 0;
	for(x = 0; x < (mesh->width - 1); x++)
		length += widthTable[x];
	lm->w = ceil(length / lm->sampleSize) + 1;
	if(lm->w < ds->patchWidth)
		lm->w = ds->patchWidth;
	if(lm->w > lm->customWidth)
		lm->w = lm->customWidth;
	sBasis = (float)(lm->w - 1) / (float)(ds->patchWidth - 1);

	/* determine lightmap height */
	length = 0;
	for(y = 0; y < (mesh->height - 1); y++)
		length += heightTable[y];
	lm->h = ceil(length / lm->sampleSize) + 1;
	if(lm->h < ds->patchHeight)
		lm->h = ds->patchHeight;
	if(lm->h > lm->customHeight)
		lm->h = lm->customHeight;
	tBasis = (float)(lm->h - 1) / (float)(ds->patchHeight - 1);

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

	/* set the lightmap texture coordinates in yDrawVerts */
	lm->wrap[0] = qtrue;
	lm->wrap[1] = qtrue;
	verts = &yDrawVerts[ds->firstVert];
	for(y = 0; y < ds->patchHeight; y++)
	{
		t = (tBasis * y) + 0.5f;
		for(x = 0; x < ds->patchWidth; x++)
		{
			s = (sBasis * x) + 0.5f;
			verts[(y * ds->patchWidth) + x].lightmap[0][0] = s * superSample;
			verts[(y * ds->patchWidth) + x].lightmap[0][1] = t * superSample;

			if(y == 0 && !VectorCompare(verts[x].xyz, verts[((ds->patchHeight - 1) * ds->patchWidth) + x].xyz))
				lm->wrap[1] = qfalse;
		}

		if(!VectorCompare(verts[(y * ds->patchWidth)].xyz, verts[(y * ds->patchWidth) + (ds->patchWidth - 1)].xyz))
			lm->wrap[0] = qfalse;
	}

	/* debug code: */
	//% Sys_Printf( "wrap S: %d wrap T: %d\n", lm->wrap[ 0 ], lm->wrap[ 1 ] );
	//% if( lm->w > (ds->lightmapWidth & 0xFF) || lm->h > (ds->lightmapHeight & 0xFF) )
	//%     Sys_Printf( "Patch lightmap: (%3d %3d) > (%3d, %3d)\n", lm->w, lm->h, ds->lightmapWidth & 0xFF, ds->lightmapHeight & 0xFF );
	//% ds->lightmapWidth = lm->w | (ds->lightmapWidth & 0xFFFF0000);
	//% ds->lightmapHeight = lm->h | (ds->lightmapHeight & 0xFFFF0000);

	/* add to counts */
	numPatchesLightmapped++;

	/* return */
	return qtrue;
}



/*
AddSurfaceToRawLightmap()
projects a lightmap for a surface
based on AllocateLightmapForSurface()
*/

qboolean AddSurfaceToRawLightmap(int num, rawLightmap_t * lm)
{
	bspDrawSurface_t *ds, *ds2;
	surfaceInfo_t  *info;
	int             num2, n, i, axisNum;
	float           s, t, d, len, sampleSize;
	vec3_t          mins, maxs, origin, faxis, size, delta, normalized, vecs[2];
	vec4_t          plane;
	bspDrawVert_t  *verts;


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

	/* add the surface to the raw lightmap */
	lightSurfaces[numLightSurfaces++] = num;
	lm->numLightSurfaces++;

	/* does this raw lightmap already have any surfaces? */
	if(lm->numLightSurfaces > 1)
	{
		/* surface and raw lightmap must have the same lightmap projection axis */
		if(VectorCompare(info->axis, lm->axis) == qfalse)
			return qfalse;

		/* match identical attributes */
		if(info->sampleSize != lm->sampleSize ||
		   info->entityNum != lm->entityNum ||
		   info->recvShadows != lm->recvShadows ||
		   info->si->lmCustomWidth != lm->customWidth ||
		   info->si->lmCustomHeight != lm->customHeight ||
		   info->si->lmBrightness != lm->brightness ||
		   info->si->lmFilterRadius != lm->filterRadius || info->si->splotchFix != lm->splotchFix)
			return qfalse;

		/* surface bounds must intersect with raw lightmap bounds */
		for(i = 0; i < 3; i++)
		{
			if(info->mins[i] > lm->maxs[i])
				return qfalse;
			if(info->maxs[i] < lm->mins[i])
				return qfalse;
		}

		/* plane check (fixme: allow merging of nonplanars) */
		if(info->si->lmMergable == qfalse)
		{
			if(info->plane == NULL || lm->plane == NULL)
				return qfalse;

			/* compare planes */
			for(i = 0; i < 4; i++)
				if(fabs(info->plane[i] - lm->plane[i]) > EQUAL_EPSILON)
					return qfalse;
		}

		/* debug code hacking */
		//% if( lm->numLightSurfaces > 1 )
		//%     return qfalse;
	}

	/* set plane */
	if(info->plane == NULL)
		lm->plane = NULL;

	/* add surface to lightmap bounds */
	AddPointToBounds(info->mins, lm->mins, lm->maxs);
	AddPointToBounds(info->maxs, lm->mins, lm->maxs);

	/* check to see if this is a non-planar patch */
	if(ds->surfaceType == MST_PATCH && lm->axis[0] == 0.0f && lm->axis[1] == 0.0f && lm->axis[2] == 0.0f)
		return AddPatchToRawLightmap(num, lm);

	/* start with initially requested sample size */
	sampleSize = lm->sampleSize;

	/* round to the lightmap resolution */
	for(i = 0; i < 3; i++)
	{
		mins[i] = sampleSize * floor(lm->mins[i] / sampleSize);
		maxs[i] = sampleSize * ceil(lm->maxs[i] / sampleSize);
		size[i] = (maxs[i] - mins[i]) / sampleSize + 1.0f;

		/* hack (god this sucks) */
		if(size[i] > lm->customWidth || size[i] > lm->customHeight || (lmLimitSize && size[i] > lmLimitSize))
		{
			i = -1;
			sampleSize += 1.0f;
		}
	}

	if(sampleSize != lm->sampleSize && lmLimitSize == 0)
	{
		Sys_FPrintf(SYS_VRB,
					"WARNING: surface at (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) too large for desired samplesize/lightmapsize/lightmapscale combination, increased samplesize from %d to %d\n",
					info->mins[0], info->mins[1], info->mins[2], info->maxs[0], info->maxs[1], info->maxs[2], lm->sampleSize,
					(int)sampleSize);
	}

	/* set actual sample size */
	lm->actualSampleSize = sampleSize;

	/* fixme: copy rounded mins/maxes to lightmap record? */
	if(lm->plane == NULL)
	{
		VectorCopy(mins, lm->mins);
		VectorCopy(maxs, lm->maxs);
		VectorCopy(mins, origin);
	}

	/* set lightmap origin */
	VectorCopy(lm->mins, origin);

	/* make absolute axis */
	faxis[0] = fabs(lm->axis[0]);
	faxis[1] = fabs(lm->axis[1]);
	faxis[2] = fabs(lm->axis[2]);

	/* clear out lightmap vectors */
	memset(vecs, 0, sizeof(vecs));

	/* classify the plane (x y or z major) (ydnar: biased to z axis projection) */
	if(faxis[2] >= faxis[0] && faxis[2] >= faxis[1])
	{
		axisNum = 2;
		lm->w = size[0];
		lm->h = size[1];
		vecs[0][0] = 1.0f / sampleSize;
		vecs[1][1] = 1.0f / sampleSize;
	}
	else if(faxis[0] >= faxis[1] && faxis[0] >= faxis[2])
	{
		axisNum = 0;
		lm->w = size[1];
		lm->h = size[2];
		vecs[0][1] = 1.0f / sampleSize;
		vecs[1][2] = 1.0f / sampleSize;
	}
	else
	{
		axisNum = 1;
		lm->w = size[0];
		lm->h = size[2];
		vecs[0][0] = 1.0f / sampleSize;
		vecs[1][2] = 1.0f / sampleSize;
	}

	/* check for bogus axis */
	if(faxis[axisNum] == 0.0f)
	{
		Sys_Printf("WARNING: ProjectSurfaceLightmap: A 0-valued axis was encountered\n");
		lm->w = lm->h = 0;
		return qfalse;
	}

	/* store the axis number in the lightmap */
	lm->axisNum = axisNum;

	/* walk the list of surfaces on this raw lightmap */
	for(n = 0; n < lm->numLightSurfaces; n++)
	{
		/* get surface */
		num2 = lightSurfaces[lm->firstLightSurface + n];
		ds2 = &bspDrawSurfaces[num2];
		verts = &yDrawVerts[ds2->firstVert];

		/* set the lightmap texture coordinates in yDrawVerts in [0, superSample * lm->customWidth] space */
		for(i = 0; i < ds2->numVerts; i++)
		{
			VectorSubtract(verts[i].xyz, origin, delta);
			s = DotProduct(delta, vecs[0]) + 0.5f;
			t = DotProduct(delta, vecs[1]) + 0.5f;
			verts[i].lightmap[0][0] = s * superSample;
			verts[i].lightmap[0][1] = t * superSample;

			if(s > (float)lm->w || t > (float)lm->h)
			{
				Sys_FPrintf(SYS_VRB, "WARNING: Lightmap texture coords out of range: S %1.4f > %3d || T %1.4f > %3d\n",
							s, lm->w, t, lm->h);
			}
		}
	}

	/* get first drawsurface */
	num2 = lightSurfaces[lm->firstLightSurface];
	ds2 = &bspDrawSurfaces[num2];
	verts = &yDrawVerts[ds2->firstVert];

	/* calculate lightmap origin */
	if(VectorLength(ds2->lightmapVecs[2]))
		VectorCopy(ds2->lightmapVecs[2], plane);
	else
		VectorCopy(lm->axis, plane);
	plane[3] = DotProduct(verts[0].xyz, plane);

	VectorCopy(origin, lm->origin);
	d = DotProduct(lm->origin, plane) - plane[3];
	d /= plane[axisNum];
	lm->origin[axisNum] -= d;

	/* legacy support */
	VectorCopy(lm->origin, ds->lightmapOrigin);

	/* for planar surfaces, create lightmap vectors for st->xyz conversion */
	if(VectorLength(ds->lightmapVecs[2]) || 1)	/* ydnar: can't remember what exactly i was thinking here... */
	{
		/* allocate space for the vectors */
		lm->vecs = safe_malloc(3 * sizeof(vec3_t));
		memset(lm->vecs, 0, 3 * sizeof(vec3_t));
		VectorCopy(ds->lightmapVecs[2], lm->vecs[2]);

		/* project stepped lightmap blocks and subtract to get planevecs */
		for(i = 0; i < 2; i++)
		{
			len = VectorNormalize2(vecs[i], normalized);
			VectorScale(normalized, (1.0 / len), lm->vecs[i]);
			d = DotProduct(lm->vecs[i], plane);
			d /= plane[axisNum];
			lm->vecs[i][axisNum] -= d;
		}
	}
	else
	{
		/* lightmap vectors are useless on a non-planar surface */
		lm->vecs = NULL;
	}

	/* add to counts */
	if(ds->surfaceType == MST_PATCH)
	{
		numPatchesLightmapped++;
		if(lm->plane != NULL)
			numPlanarPatchesLightmapped++;
	}
	else
	{
		if(lm->plane != NULL)
			numPlanarsLightmapped++;
		else
			numNonPlanarsLightmapped++;
	}

	/* return */
	return qtrue;
}



/*
CompareSurfaceInfo()
compare function for qsort()
*/

static int CompareSurfaceInfo(const void *a, const void *b)
{
	surfaceInfo_t  *aInfo, *bInfo;
	int             i;


	/* get surface info */
	aInfo = &surfaceInfos[*((const int *)a)];
	bInfo = &surfaceInfos[*((const int *)b)];

	/* model first */
	if(aInfo->modelindex < bInfo->modelindex)
		return 1;
	else if(aInfo->modelindex > bInfo->modelindex)
		return -1;

	/* then lightmap status */
	if(aInfo->hasLightmap < bInfo->hasLightmap)
		return 1;
	else if(aInfo->hasLightmap > bInfo->hasLightmap)
		return -1;

	/* 27: then shader! */
	if(aInfo->si < bInfo->si)
		return 1;
	else if(aInfo->si > bInfo->si)
		return -1;


	/* then lightmap sample size */
	if(aInfo->sampleSize < bInfo->sampleSize)
		return 1;
	else if(aInfo->sampleSize > bInfo->sampleSize)
		return -1;

	/* then lightmap axis */
	for(i = 0; i < 3; i++)
	{
		if(aInfo->axis[i] < bInfo->axis[i])
			return 1;
		else if(aInfo->axis[i] > bInfo->axis[i])
			return -1;
	}

	/* then plane */
	if(aInfo->plane == NULL && bInfo->plane != NULL)
		return 1;
	else if(aInfo->plane != NULL && bInfo->plane == NULL)
		return -1;
	else if(aInfo->plane != NULL && bInfo->plane != NULL)
	{
		for(i = 0; i < 4; i++)
		{
			if(aInfo->plane[i] < bInfo->plane[i])
				return 1;
			else if(aInfo->plane[i] > bInfo->plane[i])
				return -1;
		}
	}

	/* then position in world */
	for(i = 0; i < 3; i++)
	{
		if(aInfo->mins[i] < bInfo->mins[i])
			return 1;
		else if(aInfo->mins[i] > bInfo->mins[i])
			return -1;
	}

	/* these are functionally identical (this should almost never happen) */
	return 0;
}



/*
SetupSurfaceLightmaps()
allocates lightmaps for every surface in the bsp that needs one
this depends on yDrawVerts being allocated
*/

void SetupSurfaceLightmaps(void)
{
	int             i, j, k, s, num, num2;
	bspModel_t     *model;
	bspLeaf_t      *leaf;
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info, *info2;
	rawLightmap_t  *lm;
	qboolean        added;
	vec3_t          mapSize, entityOrigin;


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

	/* determine supersample amount */
	if(superSample < 1)
		superSample = 1;
	else if(superSample > 8)
	{
		Sys_Printf("WARNING: Insane supersampling amount (%d) detected.\n", superSample);
		superSample = 8;
	}

	/* clear map bounds */
	ClearBounds(mapMins, mapMaxs);

	/* allocate a list of surface clusters */
	numSurfaceClusters = 0;
	maxSurfaceClusters = numBSPLeafSurfaces;
	surfaceClusters = safe_malloc(maxSurfaceClusters * sizeof(*surfaceClusters));
	memset(surfaceClusters, 0, maxSurfaceClusters * sizeof(*surfaceClusters));

	/* allocate a list for per-surface info */
	surfaceInfos = safe_malloc(numBSPDrawSurfaces * sizeof(*surfaceInfos));
	memset(surfaceInfos, 0, numBSPDrawSurfaces * sizeof(*surfaceInfos));
	for(i = 0; i < numBSPDrawSurfaces; i++)
		surfaceInfos[i].childSurfaceNum = -1;

	/* allocate a list of surface indexes to be sorted */
	sortSurfaces = safe_malloc(numBSPDrawSurfaces * sizeof(int));
	memset(sortSurfaces, 0, numBSPDrawSurfaces * sizeof(int));

	/* walk each model in the bsp */
	for(i = 0; i < numBSPModels; i++)
	{
		/* get model */
		model = &bspModels[i];

		/* walk the list of surfaces in this model and fill out the info structs */
		for(j = 0; j < model->numBSPSurfaces; j++)
		{
			/* make surface index */
			num = model->firstBSPSurface + j;

			/* copy index to sort list */
			sortSurfaces[num] = num;

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

			/* set entity origin */
			if(ds->numVerts > 0)
				VectorSubtract(yDrawVerts[ds->firstVert].xyz, bspDrawVerts[ds->firstVert].xyz, entityOrigin);
			else
				VectorClear(entityOrigin);

			/* basic setup */
			info->modelindex = i;
			info->lm = NULL;
			info->plane = NULL;
			info->firstSurfaceCluster = numSurfaceClusters;

			/* get extra data */
			info->si = GetSurfaceExtraShaderInfo(num);
			if(info->si == NULL)
				info->si = ShaderInfoForShader(bspShaders[ds->shaderNum].shader);
			info->parentSurfaceNum = GetSurfaceExtraParentSurfaceNum(num);
			info->entityNum = GetSurfaceExtraEntityNum(num);
			info->castShadows = GetSurfaceExtraCastShadows(num);
			info->recvShadows = GetSurfaceExtraRecvShadows(num);
			info->sampleSize = GetSurfaceExtraSampleSize(num);
			info->longestCurve = GetSurfaceExtraLongestCurve(num);
			info->patchIterations = IterationsForCurve(info->longestCurve, patchSubdivisions);
			GetSurfaceExtraLightmapAxis(num, info->axis);

			/* mark parent */
			if(info->parentSurfaceNum >= 0)
				surfaceInfos[info->parentSurfaceNum].childSurfaceNum = j;

			/* determine surface bounds */
			ClearBounds(info->mins, info->maxs);
			for(k = 0; k < ds->numVerts; k++)
			{
				AddPointToBounds(yDrawVerts[ds->firstVert + k].xyz, mapMins, mapMaxs);
				AddPointToBounds(yDrawVerts[ds->firstVert + k].xyz, info->mins, info->maxs);
			}

			/* find all the bsp clusters the surface falls into */
			for(k = 0; k < numBSPLeafs; k++)
			{
				/* get leaf */
				leaf = &bspLeafs[k];

				/* test bbox */
				if(leaf->mins[0] > info->maxs[0] || leaf->maxs[0] < info->mins[0] ||
				   leaf->mins[1] > info->maxs[1] || leaf->maxs[1] < info->mins[1] ||
				   leaf->mins[2] > info->maxs[2] || leaf->maxs[2] < info->mins[2])
					continue;

				/* test leaf surfaces */
				for(s = 0; s < leaf->numBSPLeafSurfaces; s++)
				{
					if(bspLeafSurfaces[leaf->firstBSPLeafSurface + s] == num)
					{
						if(numSurfaceClusters >= maxSurfaceClusters)
							Error("maxSurfaceClusters exceeded");
						surfaceClusters[numSurfaceClusters] = leaf->cluster;
						numSurfaceClusters++;
						info->numSurfaceClusters++;
					}
				}
			}

			/* determine if surface is planar */
			if(VectorLength(ds->lightmapVecs[2]) > 0.0f)
			{
				/* make a plane */
				info->plane = safe_malloc(4 * sizeof(float));
				VectorCopy(ds->lightmapVecs[2], info->plane);
				info->plane[3] = DotProduct(yDrawVerts[ds->firstVert].xyz, info->plane);
			}

			/* determine if surface requires a lightmap */
			if(ds->surfaceType == MST_TRIANGLE_SOUP || ds->surfaceType == MST_FOLIAGE || (info->si->compileFlags & C_VERTEXLIT))
				numSurfsVertexLit++;
			else
			{
				numSurfsLightmapped++;
				info->hasLightmap = qtrue;
			}
		}
	}

	/* find longest map distance */
	VectorSubtract(mapMaxs, mapMins, mapSize);
	maxMapDistance = VectorLength(mapSize);

	/* sort the surfaces info list */
	qsort(sortSurfaces, numBSPDrawSurfaces, sizeof(int), CompareSurfaceInfo);

	/* allocate a list of surfaces that would go into raw lightmaps */
	numLightSurfaces = 0;
	lightSurfaces = safe_malloc(numSurfsLightmapped * sizeof(int));
	memset(lightSurfaces, 0, numSurfsLightmapped * sizeof(int));

	/* allocate a list of raw lightmaps */
	numRawSuperLuxels = 0;
	numRawLightmaps = 0;
	rawLightmaps = safe_malloc(numSurfsLightmapped * sizeof(*rawLightmaps));
	memset(rawLightmaps, 0, numSurfsLightmapped * sizeof(*rawLightmaps));

	/* walk the list of sorted surfaces */
	for(i = 0; i < numBSPDrawSurfaces; i++)
	{
		/* get info and attempt early out */
		num = sortSurfaces[i];
		ds = &bspDrawSurfaces[num];
		info = &surfaceInfos[num];
		if(info->hasLightmap == qfalse || info->lm != NULL || info->parentSurfaceNum >= 0)
			continue;

		/* allocate a new raw lightmap */
		lm = &rawLightmaps[numRawLightmaps];
		numRawLightmaps++;

		/* set it up */
		lm->splotchFix = info->si->splotchFix;
		lm->firstLightSurface = numLightSurfaces;
		lm->numLightSurfaces = 0;
		/* vortex: multiply lightmap sample size by -samplescale */
		if(sampleScale > 0)
			lm->sampleSize = info->sampleSize * sampleScale;
		else
			lm->sampleSize = info->sampleSize;
		lm->actualSampleSize = lm->sampleSize;
		lm->entityNum = info->entityNum;
		lm->recvShadows = info->recvShadows;
		lm->brightness = info->si->lmBrightness;
		lm->filterRadius = info->si->lmFilterRadius;
		VectorCopy(info->si->floodlightRGB, lm->floodlightRGB);
		lm->floodlightDistance = info->si->floodlightDistance;
		lm->floodlightIntensity = info->si->floodlightIntensity;
		lm->floodlightDirectionScale = info->si->floodlightDirectionScale;
		VectorCopy(info->axis, lm->axis);
		lm->plane = info->plane;
		VectorCopy(info->mins, lm->mins);
		VectorCopy(info->maxs, lm->maxs);

		lm->customWidth = info->si->lmCustomWidth;
		lm->customHeight = info->si->lmCustomHeight;

		/* add the surface to the raw lightmap */
		AddSurfaceToRawLightmap(num, lm);
		info->lm = lm;

		/* do an exhaustive merge */
		added = qtrue;
		while(added)
		{
			/* walk the list of surfaces again */
			added = qfalse;
			for(j = i + 1; j < numBSPDrawSurfaces && lm->finished == qfalse; j++)
			{
				/* get info and attempt early out */
				num2 = sortSurfaces[j];
				info2 = &surfaceInfos[num2];
				if(info2->hasLightmap == qfalse || info2->lm != NULL)
					continue;

				/* add the surface to the raw lightmap */
				if(AddSurfaceToRawLightmap(num2, lm))
				{
					info2->lm = lm;
					added = qtrue;
				}
				else
				{
					/* back up one */
					lm->numLightSurfaces--;
					numLightSurfaces--;
				}
			}
		}

		/* finish the lightmap and allocate the various buffers */
		FinishRawLightmap(lm);
	}

	/* allocate vertex luxel storage */
	for(k = 0; k < MAX_LIGHTMAPS; k++)
	{
		vertexLuxels[k] = safe_malloc(numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof(float));
		memset(vertexLuxels[k], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof(float));

		radVertexLuxels[k] = safe_malloc(numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof(float));
		memset(radVertexLuxels[k], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof(float));

		if(deluxemap)
		{
			vertexDeluxels[k] = safe_malloc(numBSPDrawVerts * VERTEX_DELUXEL_SIZE * sizeof(float));
			memset(vertexDeluxels[k], 0, numBSPDrawVerts * VERTEX_DELUXEL_SIZE * sizeof(float));
		}
	}

	/* emit some stats */
	Sys_FPrintf(SYS_VRB, "%9d surfaces\n", numBSPDrawSurfaces);
	Sys_FPrintf(SYS_VRB, "%9d raw lightmaps\n", numRawLightmaps);
	Sys_FPrintf(SYS_VRB, "%9d surfaces vertex lit\n", numSurfsVertexLit);
	Sys_FPrintf(SYS_VRB, "%9d surfaces lightmapped\n", numSurfsLightmapped);
	Sys_FPrintf(SYS_VRB, "%9d planar surfaces lightmapped\n", numPlanarsLightmapped);
	Sys_FPrintf(SYS_VRB, "%9d non-planar surfaces lightmapped\n", numNonPlanarsLightmapped);
	Sys_FPrintf(SYS_VRB, "%9d patches lightmapped\n", numPatchesLightmapped);
	Sys_FPrintf(SYS_VRB, "%9d planar patches lightmapped\n", numPlanarPatchesLightmapped);
}



/*
StitchSurfaceLightmaps()
stitches lightmap edges
2002-11-20 update: use this func only for stitching nonplanar patch lightmap seams
*/

#define MAX_STITCH_CANDIDATES	32
#define MAX_STITCH_LUXELS		64

void StitchSurfaceLightmaps(void)
{
	/* disabled for now */
#if 0
    int             i, j, x, y, x2, y2, *cluster, *cluster2, numStitched, numCandidates, numLuxels, f, fOld, start;
	rawLightmap_t  *lm, *a, *b, *c[MAX_STITCH_CANDIDATES];
	float          *luxel, *luxel2, *origin, *origin2, *normal, *normal2, sampleSize, average[3], totalColor, ootc;

	/* note it */
	Sys_Printf("--- StitchSurfaceLightmaps ---\n");

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

	/* walk the list of raw lightmaps */
	numStitched = 0;
	for(i = 0; i < numRawLightmaps; i++)
	{
		/* print pacifier */
		f = 10 * i / numRawLightmaps;
		if(f != fOld)
		{
			fOld = f;
			Sys_Printf("%i...", f);
		}

		/* get lightmap a */
		a = &rawLightmaps[i];

		/* walk rest of lightmaps */
		numCandidates = 0;
		for(j = i + 1; j < numRawLightmaps && numCandidates < MAX_STITCH_CANDIDATES; j++)
		{
			/* get lightmap b */
			b = &rawLightmaps[j];

			/* test bounding box */
			if(a->mins[0] > b->maxs[0] || a->maxs[0] < b->mins[0] ||
			   a->mins[1] > b->maxs[1] || a->maxs[1] < b->mins[1] || a->mins[2] > b->maxs[2] || a->maxs[2] < b->mins[2])
				continue;

			/* add candidate */
			c[numCandidates++] = b;
		}

		/* walk luxels */
		for(y = 0; y < a->sh; y++)
		{
			for(x = 0; x < a->sw; x++)
			{
				/* ignore unmapped/unlit luxels */
				lm = a;
				cluster = SUPER_CLUSTER(x, y);
				if(*cluster == CLUSTER_UNMAPPED)
					continue;
				luxel = SUPER_LUXEL(0, x, y);
				if(luxel[3] <= 0.0f)
					continue;

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

				/* walk candidate list */
				for(j = 0; j < numCandidates; j++)
				{
					/* get candidate */
					b = c[j];
					lm = b;

					/* set samplesize to the smaller of the pair */
					sampleSize = 0.5f * (a->actualSampleSize < b->actualSampleSize ? a->actualSampleSize : b->actualSampleSize);

					/* test bounding box */
					if(origin[0] < (b->mins[0] - sampleSize) || (origin[0] > b->maxs[0] + sampleSize) ||
					   origin[1] < (b->mins[1] - sampleSize) || (origin[1] > b->maxs[1] + sampleSize) ||
					   origin[2] < (b->mins[2] - sampleSize) || (origin[2] > b->maxs[2] + sampleSize))
						continue;

					/* walk candidate luxels */
					VectorClear(average);
					numLuxels = 0;
					totalColor = 0.0f;
					for(y2 = 0; y2 < b->sh && numLuxels < MAX_STITCH_LUXELS; y2++)
					{
						for(x2 = 0; x2 < b->sw && numLuxels < MAX_STITCH_LUXELS; x2++)
						{
							/* ignore same luxels */
							if(a == b && abs(x - x2) <= 1 && abs(y - y2) <= 1)
								continue;

							/* ignore unmapped/unlit luxels */
							cluster2 = SUPER_CLUSTER(x2, y2);
							if(*cluster2 == CLUSTER_UNMAPPED)
								continue;
							luxel2 = SUPER_LUXEL(0, x2, y2);
							if(luxel2[3] <= 0.0f)
								continue;

							/* get particulars */
							origin2 = SUPER_ORIGIN(x2, y2);
							normal2 = SUPER_NORMAL(x2, y2);

							/* test normal */
							if(DotProduct(normal, normal2) < 0.5f)
								continue;

							/* test bounds */
							if(fabs(origin[0] - origin2[0]) > sampleSize ||
							   fabs(origin[1] - origin2[1]) > sampleSize || fabs(origin[2] - origin2[2]) > sampleSize)
								continue;

							/* add luxel */
							//% VectorSet( luxel2, 255, 0, 255 );
							VectorAdd(average, luxel2, average);
							totalColor += luxel2[3];
						}
					}

					/* early out */
					if(numLuxels == 0)
						continue;

					/* scale average */
					ootc = 1.0f / totalColor;
					VectorScale(average, ootc, luxel);
					luxel[3] = 1.0f;
					numStitched++;
				}
			}
		}
	}

	/* emit statistics */
	Sys_Printf(" (%i)\n", (int)(I_FloatTime() - start));
	Sys_FPrintf(SYS_VRB, "%9d luxels stitched\n", numStitched);
#endif
}



/*
CompareBSPLuxels()
compares two surface lightmaps' bsp luxels, ignoring occluded luxels
*/

#define SOLID_EPSILON		0.0625
#define LUXEL_TOLERANCE		0.0025
#define LUXEL_COLOR_FRAC	0.001302083	/* 1 / 3 / 256 */

static qboolean CompareBSPLuxels(rawLightmap_t * a, int aNum, rawLightmap_t * b, int bNum)
{
	rawLightmap_t  *lm;
	int             x, y;
	double          delta, total, rd, gd, bd;
	float          *aLuxel, *bLuxel;


	/* styled lightmaps will never be collapsed to non-styled lightmaps when there is _minlight */
	if((minLight[0] || minLight[1] || minLight[2]) && ((aNum == 0 && bNum != 0) || (aNum != 0 && bNum == 0)))
		return qfalse;

	/* basic tests */
	if(a->customWidth != b->customWidth || a->customHeight != b->customHeight ||
	   a->brightness != b->brightness ||
	   a->solid[aNum] != b->solid[bNum] || a->bspLuxels[aNum] == NULL || b->bspLuxels[bNum] == NULL)
		return qfalse;

	/* compare solid color lightmaps */
	if(a->solid[aNum] && b->solid[bNum])
	{
		/* get deltas */
		rd = fabs(a->solidColor[aNum][0] - b->solidColor[bNum][0]);
		gd = fabs(a->solidColor[aNum][1] - b->solidColor[bNum][1]);
		bd = fabs(a->solidColor[aNum][2] - b->solidColor[bNum][2]);

		/* compare color */
		if(rd > SOLID_EPSILON || gd > SOLID_EPSILON || bd > SOLID_EPSILON)
			return qfalse;

		/* okay */
		return qtrue;
	}

	/* compare nonsolid lightmaps */
	if(a->w != b->w || a->h != b->h)
		return qfalse;

	/* compare luxels */
	delta = 0.0;
	total = 0.0;
	for(y = 0; y < a->h; y++)
	{
		for(x = 0; x < a->w; x++)
		{
			/* increment total */
			total += 1.0;

			/* get luxels */
			lm = a;
			aLuxel = BSP_LUXEL(aNum, x, y);
			lm = b;
			bLuxel = BSP_LUXEL(bNum, x, y);

			/* ignore unused luxels */
			if(aLuxel[0] < 0 || bLuxel[0] < 0)
				continue;

			/* get deltas */
			rd = fabs(aLuxel[0] - bLuxel[0]);
			gd = fabs(aLuxel[1] - bLuxel[1]);
			bd = fabs(aLuxel[2] - bLuxel[2]);

			/* 2003-09-27: compare individual luxels */
			if(rd > 3.0 || gd > 3.0 || bd > 3.0)
				return qfalse;

			/* compare (fixme: take into account perceptual differences) */
			delta += rd * LUXEL_COLOR_FRAC;
			delta += gd * LUXEL_COLOR_FRAC;
			delta += bd * LUXEL_COLOR_FRAC;

			/* is the change too high? */
			if(total > 0.0 && ((delta / total) > LUXEL_TOLERANCE))
				return qfalse;
		}
	}

	/* made it this far, they must be identical (or close enough) */
	return qtrue;
}



/*
MergeBSPLuxels()
merges two surface lightmaps' bsp luxels, overwriting occluded luxels
*/

static qboolean MergeBSPLuxels(rawLightmap_t * a, int aNum, rawLightmap_t * b, int bNum)
{
	rawLightmap_t  *lm;
	int             x, y;
	float           luxel[3], *aLuxel, *bLuxel;


	/* basic tests */
	if(a->customWidth != b->customWidth || a->customHeight != b->customHeight ||
	   a->brightness != b->brightness ||
	   a->solid[aNum] != b->solid[bNum] || a->bspLuxels[aNum] == NULL || b->bspLuxels[bNum] == NULL)
		return qfalse;

	/* compare solid lightmaps */
	if(a->solid[aNum] && b->solid[bNum])
	{
		/* average */
		VectorAdd(a->solidColor[aNum], b->solidColor[bNum], luxel);
		VectorScale(luxel, 0.5f, luxel);

		/* copy to both */
		VectorCopy(luxel, a->solidColor[aNum]);
		VectorCopy(luxel, b->solidColor[bNum]);

		/* return to sender */
		return qtrue;
	}

	/* compare nonsolid lightmaps */
	if(a->w != b->w || a->h != b->h)
		return qfalse;

	/* merge luxels */
	for(y = 0; y < a->h; y++)
	{
		for(x = 0; x < a->w; x++)
		{
			/* get luxels */
			lm = a;
			aLuxel = BSP_LUXEL(aNum, x, y);
			lm = b;
			bLuxel = BSP_LUXEL(bNum, x, y);

			/* handle occlusion mismatch */
			if(aLuxel[0] < 0.0f)
				VectorCopy(bLuxel, aLuxel);
			else if(bLuxel[0] < 0.0f)
				VectorCopy(aLuxel, bLuxel);
			else
			{
				/* average */
				VectorAdd(aLuxel, bLuxel, luxel);
				VectorScale(luxel, 0.5f, luxel);

				/* debugging code */
				//% luxel[ 2 ] += 64.0f;

				/* copy to both */
				VectorCopy(luxel, aLuxel);
				VectorCopy(luxel, bLuxel);
			}
		}
	}

	/* done */
	return qtrue;
}



/*
ApproximateLuxel()
determines if a single luxel is can be approximated with the interpolated vertex rgba
*/

static qboolean ApproximateLuxel(rawLightmap_t * lm, bspDrawVert_t * dv)
{
	int             i, x, y, d, lightmapNum;
	float          *luxel;
	vec3_t          color, vertexColor;
	byte            cb[4], vcb[4];


	/* find luxel xy coords */
	x = dv->lightmap[0][0] / superSample;
	y = dv->lightmap[0][1] / superSample;
	if(x < 0)
		x = 0;
	else if(x >= lm->w)
		x = lm->w - 1;
	if(y < 0)
		y = 0;
	else if(y >= lm->h)
		y = lm->h - 1;

	/* walk list */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
	{
		/* early out */
		if(lm->styles[lightmapNum] == LS_NONE)
			continue;

		/* get luxel */
		luxel = BSP_LUXEL(lightmapNum, x, y);

		/* ignore occluded luxels */
		if(luxel[0] < 0.0f || luxel[1] < 0.0f || luxel[2] < 0.0f)
			return qtrue;

		/* copy, set min color and compare */
		VectorCopy(luxel, color);
		VectorCopy(dv->lightColor[0], vertexColor);

		/* styles are not affected by minlight */
		if(lightmapNum == 0)
		{
			for(i = 0; i < 3; i++)
			{
				/* set min color */
				if(color[i] < minLight[i])
					color[i] = minLight[i];
				if(vertexColor[i] < minLight[i])	/* note NOT minVertexLight */
					vertexColor[i] = minLight[i];
			}
		}

		/* set to bytes */
		ColorToBytes(color, cb, 1.0f);
		ColorToBytes(vertexColor, vcb, 1.0f);

		/* compare */
		for(i = 0; i < 3; i++)
		{
			d = cb[i] - vcb[i];
			if(d < 0)
				d *= -1;
			if(d > approximateTolerance)
				return qfalse;
		}
	}

	/* close enough for the girls i date */
	return qtrue;
}



/*
ApproximateTriangle()
determines if a single triangle can be approximated with vertex rgba
*/

static qboolean ApproximateTriangle_r(rawLightmap_t * lm, bspDrawVert_t * dv[3])
{
	bspDrawVert_t   mid, *dv2[3];
	int             max;


	/* approximate the vertexes */
	if(ApproximateLuxel(lm, dv[0]) == qfalse)
		return qfalse;
	if(ApproximateLuxel(lm, dv[1]) == qfalse)
		return qfalse;
	if(ApproximateLuxel(lm, dv[2]) == qfalse)
		return qfalse;

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


		/* find the longest edge and split it */
		max = -1;
		maxDist = 0;
		for(i = 0; i < 3; i++)
		{
			dx = dv[i]->lightmap[0][0] - dv[(i + 1) % 3]->lightmap[0][0];
			dy = dv[i]->lightmap[0][1] - dv[(i + 1) % 3]->lightmap[0][1];
			dist = sqrt((dx * dx) + (dy * dy));
			if(dist > maxDist)
			{
				maxDist = dist;
				max = i;
			}
		}

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

	/* split the longest edge and map it */
	LerpDrawVert(dv[max], dv[(max + 1) % 3], &mid);
	if(ApproximateLuxel(lm, &mid) == qfalse)
		return qfalse;

	/* recurse to first triangle */
	VectorCopy(dv, dv2);
	dv2[max] = &mid;
	if(ApproximateTriangle_r(lm, dv2) == qfalse)
		return qfalse;

	/* recurse to second triangle */
	VectorCopy(dv, dv2);
	dv2[(max + 1) % 3] = &mid;
	return ApproximateTriangle_r(lm, dv2);
}



/*
ApproximateLightmap()
determines if a raw lightmap can be approximated sufficiently with vertex colors
*/

static qboolean ApproximateLightmap(rawLightmap_t * lm)
{
	int             n, num, i, x, y, pw[5], r;
	bspDrawSurface_t *ds;
	surfaceInfo_t  *info;
	mesh_t          src, *subdivided, *mesh;
	bspDrawVert_t  *verts, *dv[3];
	qboolean        approximated;


	/* approximating? */
	if(approximateTolerance <= 0)
		return qfalse;

	/* test for jmonroe */
#if 0
	/* don't approx lightmaps with styled twins */
	if(lm->numStyledTwins > 0)
		return qfalse;

	/* don't approx lightmaps with styles */
	for(i = 1; i < MAX_LIGHTMAPS; i++)
	{
		if(lm->styles[i] != LS_NONE)
			return qfalse;
	}
#endif

	/* assume reduced until shadow detail is found */
	approximated = qtrue;

	/* walk the list of surfaces on this raw lightmap */
	for(n = 0; n < lm->numLightSurfaces; n++)
	{
		/* get surface */
		num = lightSurfaces[lm->firstLightSurface + n];
		ds = &bspDrawSurfaces[num];
		info = &surfaceInfos[num];

		/* assume not-reduced initially */
		info->approximated = qfalse;

		/* bail if lightmap doesn't match up */
		if(info->lm != lm)
			continue;

		/* bail if not vertex lit */
		if(info->si->noVertexLight)
			continue;

		/* assume that surfaces whose bounding boxes is smaller than 2x samplesize will be forced to vertex */
		if((info->maxs[0] - info->mins[0]) <= (2.0f * info->sampleSize) &&
		   (info->maxs[1] - info->mins[1]) <= (2.0f * info->sampleSize) &&
		   (info->maxs[2] - info->mins[2]) <= (2.0f * info->sampleSize))
		{
			info->approximated = qtrue;
			numSurfsVertexForced++;
			continue;
		}

		/* handle the triangles */
		switch (ds->surfaceType)
		{
			case MST_PLANAR:
				/* get verts */
				verts = yDrawVerts + ds->firstVert;

				/* map the triangles */
				info->approximated = qtrue;
				for(i = 0; i < ds->numIndexes && info->approximated; 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]];
					info->approximated = ApproximateTriangle_r(lm, dv);
				}
				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;

				/* map the mesh quads */
				info->approximated = qtrue;
				for(y = 0; y < (mesh->height - 1) && info->approximated; y++)
				{
					for(x = 0; x < (mesh->width - 1) && info->approximated; 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]];
						info->approximated = ApproximateTriangle_r(lm, dv);

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

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

			default:
				break;
		}

		/* reduced? */
		if(info->approximated == qfalse)
			approximated = qfalse;
		else
			numSurfsVertexApproximated++;
	}

	/* return */
	return approximated;
}



/*
TestOutLightmapStamp()
tests a stamp on a given lightmap for validity
*/

static qboolean TestOutLightmapStamp(rawLightmap_t * lm, int lightmapNum, outLightmap_t * olm, int x, int y)
{
	int             sx, sy, ox, oy, offset;
	float          *luxel;


	/* bounds check */
	if(x < 0 || y < 0 || (x + lm->w) > olm->customWidth || (y + lm->h) > olm->customHeight)
		return qfalse;

	/* solid lightmaps test a 1x1 stamp */
	if(lm->solid[lightmapNum])
	{
		offset = (y * olm->customWidth) + x;
		if(olm->lightBits[offset >> 3] & (1 << (offset & 7)))
			return qfalse;
		return qtrue;
	}

	/* test the stamp */
	for(sy = 0; sy < lm->h; sy++)
	{
		for(sx = 0; sx < lm->w; sx++)
		{
			/* get luxel */
			luxel = BSP_LUXEL(lightmapNum, sx, sy);
			if(luxel[0] < 0.0f)
				continue;

			/* get bsp lightmap coords and test */
			ox = x + sx;
			oy = y + sy;
			offset = (oy * olm->customWidth) + ox;
			if(olm->lightBits[offset >> 3] & (1 << (offset & 7)))
				return qfalse;
		}
	}

	/* stamp is empty */
	return qtrue;
}



/*
SetupOutLightmap()
sets up an output lightmap
*/

static void SetupOutLightmap(rawLightmap_t * lm, outLightmap_t * olm)
{
	/* dummy check */
	if(lm == NULL || olm == NULL)
		return;

	/* is this a "normal" bsp-stored lightmap? */
	if((lm->customWidth == game->lightmapSize && lm->customHeight == game->lightmapSize) || externalLightmaps)
	{
		olm->lightmapNum = numBSPLightmaps;
		numBSPLightmaps++;

		/* lightmaps are interleaved with light direction maps */
		if(deluxemap)
			numBSPLightmaps++;
	}
	else
		olm->lightmapNum = -3;

	/* set external lightmap number */
	olm->extLightmapNum = -1;

	/* set it up */
	olm->numLightmaps = 0;
	olm->customWidth = lm->customWidth;
	olm->customHeight = lm->customHeight;
	olm->freeLuxels = olm->customWidth * olm->customHeight;
	olm->numShaders = 0;

	/* allocate buffers */
	olm->lightBits = safe_malloc((olm->customWidth * olm->customHeight / 8) + 8);
	memset(olm->lightBits, 0, (olm->customWidth * olm->customHeight / 8) + 8);

#if defined(USE_HDR_LIGHTMAPS)
	if(hdr)
	{
		olm->bspLightFloats = safe_malloc(olm->customWidth * olm->customHeight * 3 * sizeof(float));
		memset(olm->bspLightFloats, 0, olm->customWidth * olm->customHeight * 3 * sizeof(float));
		olm->bspLightBytes = NULL;
	}
	else
#endif
	{
		olm->bspLightBytes = safe_malloc(olm->customWidth * olm->customHeight * 3);
		memset(olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3);
		olm->bspLightFloats = NULL;
	}

	if(deluxemap)
	{
		olm->bspDirBytes = safe_malloc(olm->customWidth * olm->customHeight * 3);
		memset(olm->bspDirBytes, 0, olm->customWidth * olm->customHeight * 3);
	}
}



/*
FindOutLightmaps()
for a given surface lightmap, find output lightmap pages and positions for it
*/

#define LIGHTMAP_RESERVE_COUNT 1
static void FindOutLightmaps(rawLightmap_t * lm)
{
	int             i, j, k, lightmapNum, xMax, yMax, x = -1, y = -1, sx, sy, ox, oy, offset;
	outLightmap_t  *olm;
	surfaceInfo_t  *info;
	float          *luxel, *deluxel;
	vec3_t          color, direction;
	byte           *pixel;
	qboolean        ok;


	/* set default lightmap number (-3 = LIGHTMAP_BY_VERTEX) */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		lm->outLightmapNums[lightmapNum] = -3;

	/* can this lightmap be approximated with vertex color? */
	if(ApproximateLightmap(lm))
		return;

	/* walk list */
	for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
	{
		/* early out */
		if(lm->styles[lightmapNum] == LS_NONE)
			continue;

		/* don't store twinned lightmaps */
		if(lm->twins[lightmapNum] != NULL)
			continue;

		/* if this is a styled lightmap, try some normalized locations first */
		ok = qfalse;
		if(lightmapNum > 0 && outLightmaps != NULL)
		{
			/* loop twice */
			for(j = 0; j < 2; j++)
			{
				/* try identical position */
				for(i = 0; i < numOutLightmaps; i++)
				{
					/* get the output lightmap */
					olm = &outLightmaps[i];

					/* simple early out test */
					if(olm->freeLuxels < lm->used)
						continue;

					/* don't store non-custom raw lightmaps on custom bsp lightmaps */
					if(olm->customWidth != lm->customWidth || olm->customHeight != lm->customHeight)
						continue;

					/* try identical */
					if(j == 0)
					{
						x = lm->lightmapX[0];
						y = lm->lightmapY[0];
						ok = TestOutLightmapStamp(lm, lightmapNum, olm, x, y);
					}

					/* try shifting */
					else
					{
						for(sy = -1; sy <= 1; sy++)
						{
							for(sx = -1; sx <= 1; sx++)
							{
								x = lm->lightmapX[0] + sx * (olm->customWidth >> 1);	//% lm->w;
								y = lm->lightmapY[0] + sy * (olm->customHeight >> 1);	//% lm->h;
								ok = TestOutLightmapStamp(lm, lightmapNum, olm, x, y);

								if(ok)
									break;
							}

							if(ok)
								break;
						}
					}

					if(ok)
						break;
				}

				if(ok)
					break;
			}
		}

		/* try the general placement algorithm */
		if(ok == qfalse)
		{
			/* reset origin */
			x = 0;
			y = 0;

			/* walk the list of lightmap pages */
			if(lightmapSearchBlockSize <= 0 || numOutLightmaps < LIGHTMAP_RESERVE_COUNT)
				i = 0;
			else
				i = ((numOutLightmaps - LIGHTMAP_RESERVE_COUNT) / lightmapSearchBlockSize) * lightmapSearchBlockSize;
			for(; i < numOutLightmaps; i++)
			{
				/* get the output lightmap */
				olm = &outLightmaps[i];

				/* simple early out test */
				if(olm->freeLuxels < lm->used)
					continue;

				/* don't store non-custom raw lightmaps on custom bsp lightmaps */
				if(olm->customWidth != lm->customWidth || olm->customHeight != lm->customHeight)
					continue;

				/* set maxs */
				if(lm->solid[lightmapNum])
				{
					xMax = olm->customWidth;
					yMax = olm->customHeight;
				}
				else
				{
					xMax = (olm->customWidth - lm->w) + 1;
					yMax = (olm->customHeight - lm->h) + 1;
				}

				/* walk the origin around the lightmap */
				for(y = 0; y < yMax; y++)
				{
					for(x = 0; x < xMax; x++)
					{
						/* find a fine tract of lauhnd */
						ok = TestOutLightmapStamp(lm, lightmapNum, olm, x, y);

						if(ok)
							break;
					}

					if(ok)
						break;
				}

				if(ok)
					break;

				/* reset x and y */
				x = 0;
				y = 0;
			}
		}

		/* no match? */
		if(ok == qfalse)
		{
			/* allocate LIGHTMAP_RESERVE_COUNT new output lightmaps */
			numOutLightmaps += LIGHTMAP_RESERVE_COUNT;
			olm = safe_malloc(numOutLightmaps * sizeof(outLightmap_t));
			if(outLightmaps != NULL && numOutLightmaps > LIGHTMAP_RESERVE_COUNT)
			{
				memcpy(olm, outLightmaps, (numOutLightmaps - LIGHTMAP_RESERVE_COUNT) * sizeof(outLightmap_t));
				free(outLightmaps);
			}
			outLightmaps = olm;

			/* initialize both out lightmaps */
			for(k = numOutLightmaps - LIGHTMAP_RESERVE_COUNT; k < numOutLightmaps; ++k)
				SetupOutLightmap(lm, &outLightmaps[k]);

			/* set out lightmap */
			i = numOutLightmaps - LIGHTMAP_RESERVE_COUNT;
			olm = &outLightmaps[i];

			/* set stamp xy origin to the first surface lightmap */
			if(lightmapNum > 0)
			{
				x = lm->lightmapX[0];
				y = lm->lightmapY[0];
			}
		}

		/* if this is a style-using lightmap, it must be exported */
		if(lightmapNum > 0 && game->load != LoadRBSPFile)
			olm->extLightmapNum = 0;

		/* add the surface lightmap to the bsp lightmap */
		lm->outLightmapNums[lightmapNum] = i;
		lm->lightmapX[lightmapNum] = x;
		lm->lightmapY[lightmapNum] = y;
		olm->numLightmaps++;

		/* add shaders */
		for(i = 0; i < lm->numLightSurfaces; i++)
		{
			/* get surface info */
			info = &surfaceInfos[lightSurfaces[lm->firstLightSurface + i]];

			/* test for shader */
			for(j = 0; j < olm->numShaders; j++)
			{
				if(olm->shaders[j] == info->si)
					break;
			}

			/* if it doesn't exist, add it */
			if(j >= olm->numShaders && olm->numShaders < MAX_LIGHTMAP_SHADERS)
			{
				olm->shaders[olm->numShaders] = info->si;
				olm->numShaders++;
				numLightmapShaders++;
			}
		}

		/* set maxs */
		if(lm->solid[lightmapNum])
		{
			xMax = 1;
			yMax = 1;
		}
		else
		{
			xMax = lm->w;
			yMax = lm->h;
		}

		/* mark the bits used */
		for(y = 0; y < yMax; y++)
		{
			for(x = 0; x < xMax; x++)
			{
				/* get luxel */
				luxel = BSP_LUXEL(lightmapNum, x, y);
				deluxel = BSP_DELUXEL(x, y);
				if(luxel[0] < 0.0f && !lm->solid[lightmapNum])
					continue;

				/* set minimum light */
				if(lm->solid[lightmapNum])
				{
					if(debug)
						VectorSet(color, 255.0f, 0.0f, 0.0f);
					else
						VectorCopy(lm->solidColor[lightmapNum], color);
				}
				else
					VectorCopy(luxel, color);

				/* styles are not affected by minlight */
				if(lightmapNum == 0)
				{
					for(i = 0; i < 3; i++)
					{
						if(color[i] < minLight[i])
							color[i] = minLight[i];
					}
				}

				/* get bsp lightmap coords  */
				ox = x + lm->lightmapX[lightmapNum];
				oy = y + lm->lightmapY[lightmapNum];
				offset = (oy * olm->customWidth) + ox;

				/* flag pixel as used */
				olm->lightBits[offset >> 3] |= (1 << (offset & 7));
				olm->freeLuxels--;

				/* store color */
#if defined(USE_HDR_LIGHTMAPS)
				if(hdr)
				{
					float		*hdrPixel;

					hdrPixel = olm->bspLightFloats + (((oy * olm->customWidth) + ox) * 3);
					//ColorToRGBE(color, pixel, lm->brightness);

					// FIXME gamma
					VectorScale(color, lm->brightness <= 0.0f ? 1.0f : lm->brightness, hdrPixel);
					//VectorCopy(color, hdrPixel);
				}
				else
#endif
				{
					pixel = olm->bspLightBytes + (((oy * olm->customWidth) + ox) * 3);
					ColorToBytes(color, pixel, lm->brightness);
				}

				/* store direction */
				if(deluxemap)
				{
					/* normalize average light direction */
					pixel = olm->bspDirBytes + (((oy * olm->customWidth) + ox) * 3);
					VectorScale(deluxel, 1000.0f, direction);
					VectorNormalize(direction);

					/* encode [-1,1] in [0,255] */
					VectorScale(direction, 127.5f, direction);
					for(i = 0; i < 3; i++)
						pixel[i] = (byte) (127.5f + direction[i]);
				}
			}
		}
	}
}



/*
CompareRawLightmap()
compare function for qsort()
*/

static int CompareRawLightmap(const void *a, const void *b)
{
	rawLightmap_t  *alm, *blm;
	surfaceInfo_t  *aInfo, *bInfo;
	int             i, min, diff;


	/* get lightmaps */
	alm = &rawLightmaps[*((const int *)a)];
	blm = &rawLightmaps[*((const int *)b)];

	/* get min number of surfaces */
	min = (alm->numLightSurfaces < blm->numLightSurfaces ? alm->numLightSurfaces : blm->numLightSurfaces);

	/* iterate */
	for(i = 0; i < min; i++)
	{
		/* get surface info */
		aInfo = &surfaceInfos[lightSurfaces[alm->firstLightSurface + i]];
		bInfo = &surfaceInfos[lightSurfaces[blm->firstLightSurface + i]];

		/* compare shader names */
		diff = strcmp(aInfo->si->shader, bInfo->si->shader);
		if(diff != 0)
			return diff;
	}

	/* test style count */
	diff = 0;
	for(i = 0; i < MAX_LIGHTMAPS; i++)
		diff += blm->styles[i] - alm->styles[i];
	if(diff)
		return diff;

	/* compare size */
	diff = (blm->w * blm->h) - (alm->w * alm->h);
	if(diff != 0)
		return diff;

	/* must be equivalent */
	return 0;
}

void FillOutLightmap(outLightmap_t * olm)
{
	int             x, y;
	int             ofs;
	vec3_t          dir_sum, light_sum;
	int             cnt, filled;
	byte           *lightBitsNew = NULL;
	byte           *lightBytesNew = NULL;
	byte           *dirBytesNew = NULL;

	lightBitsNew = safe_malloc((olm->customWidth * olm->customHeight + 8) / 8);
	lightBytesNew = safe_malloc(olm->customWidth * olm->customHeight * 3);
	if(deluxemap)
		dirBytesNew = safe_malloc(olm->customWidth * olm->customHeight * 3);

	/*
	   memset(olm->lightBits, 0, (olm->customWidth * olm->customHeight + 8) / 8);
	   olm->lightBits[0] |= 1;
	   olm->lightBits[(10 * olm->customWidth + 30) >> 3] |= 1 << ((10 * olm->customWidth + 30) & 7);
	   memset(olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3);
	   olm->bspLightBytes[0] = 255;
	   olm->bspLightBytes[(10 * olm->customWidth + 30) * 3 + 2] = 255;
	 */

	memcpy(lightBitsNew, olm->lightBits, (olm->customWidth * olm->customHeight + 8) / 8);
	memcpy(lightBytesNew, olm->bspLightBytes, olm->customWidth * olm->customHeight * 3);
	if(deluxemap)
		memcpy(dirBytesNew, olm->bspDirBytes, olm->customWidth * olm->customHeight * 3);

	for(;;)
	{
		filled = 0;
		for(y = 0; y < olm->customHeight; ++y)
		{
			for(x = 0; x < olm->customWidth; ++x)
			{
				ofs = y * olm->customWidth + x;
				if(olm->lightBits[ofs >> 3] & (1 << (ofs & 7)))	/* already filled */
					continue;
				cnt = 0;
				VectorClear(dir_sum);
				VectorClear(light_sum);

				/* try all four neighbors */
				ofs = ((y + olm->customHeight - 1) % olm->customHeight) * olm->customWidth + x;
				if(olm->lightBits[ofs >> 3] & (1 << (ofs & 7)))	/* already filled */
				{
					++cnt;
					VectorAdd(light_sum, olm->bspLightBytes + ofs * 3, light_sum);
					if(deluxemap)
						VectorAdd(dir_sum, olm->bspDirBytes + ofs * 3, dir_sum);
				}

				ofs = ((y + 1) % olm->customHeight) * olm->customWidth + x;
				if(olm->lightBits[ofs >> 3] & (1 << (ofs & 7)))	/* already filled */
				{
					++cnt;
					VectorAdd(light_sum, olm->bspLightBytes + ofs * 3, light_sum);
					if(deluxemap)
						VectorAdd(dir_sum, olm->bspDirBytes + ofs * 3, dir_sum);
				}

				ofs = y * olm->customWidth + (x + olm->customWidth - 1) % olm->customWidth;
				if(olm->lightBits[ofs >> 3] & (1 << (ofs & 7)))	/* already filled */
				{
					++cnt;
					VectorAdd(light_sum, olm->bspLightBytes + ofs * 3, light_sum);
					if(deluxemap)
						VectorAdd(dir_sum, olm->bspDirBytes + ofs * 3, dir_sum);
				}

				ofs = y * olm->customWidth + (x + 1) % olm->customWidth;
				if(olm->lightBits[ofs >> 3] & (1 << (ofs & 7)))	/* already filled */
				{
					++cnt;
					VectorAdd(light_sum, olm->bspLightBytes + ofs * 3, light_sum);
					if(deluxemap)
						VectorAdd(dir_sum, olm->bspDirBytes + ofs * 3, dir_sum);
				}

				if(cnt)
				{
					++filled;
					ofs = y * olm->customWidth + x;
					lightBitsNew[ofs >> 3] |= (1 << (ofs & 7));
					VectorScale(light_sum, 1.0 / cnt, lightBytesNew + ofs * 3);
					if(deluxemap)
						VectorScale(dir_sum, 1.0 / cnt, dirBytesNew + ofs * 3);
				}
			}
		}

		if(!filled)
			break;

		memcpy(olm->lightBits, lightBitsNew, (olm->customWidth * olm->customHeight + 8) / 8);
		memcpy(olm->bspLightBytes, lightBytesNew, olm->customWidth * olm->customHeight * 3);
		if(deluxemap)
			memcpy(olm->bspDirBytes, dirBytesNew, olm->customWidth * olm->customHeight * 3);
	}

	free(lightBitsNew);
	free(lightBytesNew);
	if(deluxemap)
		free(dirBytesNew);
}

/*
StoreSurfaceLightmaps()
stores the surface lightmaps into the bsp as byte rgb triplets
*/

void StoreSurfaceLightmaps(void)
{
	int             i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples;
	int             style, size, lightmapNum, lightmapNum2;
	float          *normal, *luxel, *bspLuxel, *bspLuxel2, *radLuxel, samples, occludedSamples;
	vec3_t          sample, occludedSample, dirSample, colorMins, colorMaxs;
	float          *deluxel, *bspDeluxel, *bspDeluxel2;
	byte           *lb;
	int             numUsed, numTwins, numTwinLuxels, numStored;
	float           lmx, lmy, efficiency;
	vec3_t          color;
	vec3_t			lightDirection;
	bspDrawSurface_t *ds, *parent, dsTemp;
	surfaceInfo_t  *info;
	rawLightmap_t  *lm, *lm2;
	outLightmap_t  *olm;
	bspDrawVert_t  *dv, *ydv, *dvParent;
	char            dirname[1024], filename[1024];
	shaderInfo_t   *csi;
	char            lightmapName[128];
	const char     *rgbGenValues[256];
	const char     *alphaGenValues[256];


	/* note it */
	Sys_Printf("--- StoreSurfaceLightmaps ---\n");

	/* setup */
	if(lmCustomDir)
	{
		strcpy(dirname, lmCustomDir);
	}
	else
	{
		strcpy(dirname, source);
		StripExtension(dirname);
	}
	memset(rgbGenValues, 0, sizeof(rgbGenValues));
	memset(alphaGenValues, 0, sizeof(alphaGenValues));

	/* -----------------------------------------------------------------
	   average the sampled luxels into the bsp luxels
	   ----------------------------------------------------------------- */

	/* note it */
	Sys_Printf("Subsampling...");

	/* walk the list of raw lightmaps */
	numUsed = 0;
	numTwins = 0;
	numTwinLuxels = 0;
	numSolidLightmaps = 0;
	for(i = 0; i < numRawLightmaps; i++)
	{
		/* get lightmap */
		lm = &rawLightmaps[i];

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

			/* allocate bsp luxel storage */
			if(lm->bspLuxels[lightmapNum] == NULL)
			{
				size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof(float);
				lm->bspLuxels[lightmapNum] = safe_malloc(size);
				memset(lm->bspLuxels[lightmapNum], 0, size);
			}

			/* allocate radiosity lightmap storage */
			if(bounce)
			{
				size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof(float);
				if(lm->radLuxels[lightmapNum] == NULL)
					lm->radLuxels[lightmapNum] = safe_malloc(size);
				memset(lm->radLuxels[lightmapNum], 0, size);
			}

			/* average supersampled luxels */
			for(y = 0; y < lm->h; y++)
			{
				for(x = 0; x < lm->w; x++)
				{
					/* subsample */
					samples = 0.0f;
					occludedSamples = 0.0f;
					mappedSamples = 0;
					VectorClear(sample);
					VectorClear(occludedSample);
					VectorClear(dirSample);
					for(ly = 0; ly < superSample; ly++)
					{
						for(lx = 0; lx < superSample; lx++)
						{
							/* sample luxel */
							sx = x * superSample + lx;
							sy = y * superSample + ly;
							luxel = SUPER_LUXEL(lightmapNum, sx, sy);
							deluxel = SUPER_DELUXEL(sx, sy);
							normal = SUPER_NORMAL(sx, sy);
							cluster = SUPER_CLUSTER(sx, sy);

							/* sample deluxemap */
							if(deluxemap && lightmapNum == 0)
								VectorAdd(dirSample, deluxel, dirSample);

							/* keep track of used/occluded samples */
							if(*cluster != CLUSTER_UNMAPPED)
								mappedSamples++;

							/* handle lightmap border? */
							if(lightmapBorder && (sx == 0 || sx == (lm->sw - 1) || sy == 0 || sy == (lm->sh - 1)) &&
							   luxel[3] > 0.0f)
							{
								VectorSet(sample, 255.0f, 0.0f, 0.0f);
								samples += 1.0f;
							}

							/* handle debug */
							else if(debug && *cluster < 0)
							{
								if(*cluster == CLUSTER_UNMAPPED)
									VectorSet(luxel, 255, 204, 0);
								else if(*cluster == CLUSTER_OCCLUDED)
									VectorSet(luxel, 255, 0, 255);
								else if(*cluster == CLUSTER_FLOODED)
									VectorSet(luxel, 0, 32, 255);
								VectorAdd(occludedSample, luxel, occludedSample);
								occludedSamples += 1.0f;
							}

							/* normal luxel handling */
							else if(luxel[3] > 0.0f)
							{
								/* handle lit or flooded luxels */
								if(*cluster > 0 || *cluster == CLUSTER_FLOODED)
								{
									VectorAdd(sample, luxel, sample);
									samples += luxel[3];
								}

								/* handle occluded or unmapped luxels */
								else
								{
									VectorAdd(occludedSample, luxel, occludedSample);
									occludedSamples += luxel[3];
								}

								/* handle style debugging */
								if(debug && lightmapNum > 0 && x < 2 && y < 2)
								{
									VectorCopy(debugColors[0], sample);
									samples = 1;
								}
							}
						}
					}

					/* only use occluded samples if necessary */
					if(samples <= 0.0f)
					{
						VectorCopy(occludedSample, sample);
						samples = occludedSamples;
					}

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

					/* store light direction */
					if(deluxemap && lightmapNum == 0)
						VectorCopy(dirSample, deluxel);

					/* store the sample back in super luxels */
					if(samples > 0.01f)
					{
						VectorScale(sample, (1.0f / samples), luxel);
						luxel[3] = 1.0f;
					}

					/* if any samples were mapped in any way, store ambient color */
					else if(mappedSamples > 0)
					{
						if(lightmapNum == 0)
							VectorCopy(ambientColor, luxel);
						else
							VectorClear(luxel);
						luxel[3] = 1.0f;
					}

					/* store a bogus value to be fixed later */
					else
					{
						VectorClear(luxel);
						luxel[3] = -1.0f;
					}
				}
			}

			/* setup */
			lm->used = 0;
			ClearBounds(colorMins, colorMaxs);

			/* clean up and store into bsp luxels */
			for(y = 0; y < lm->h; y++)
			{
				for(x = 0; x < lm->w; x++)
				{
					/* get luxels */
					luxel = SUPER_LUXEL(lightmapNum, x, y);
					deluxel = SUPER_DELUXEL(x, y);

					/* copy light direction */
					if(deluxemap && lightmapNum == 0)
						VectorCopy(deluxel, dirSample);

					/* is this a valid sample? */
					if(luxel[3] > 0.0f)
					{
						VectorCopy(luxel, sample);
						samples = luxel[3];
						numUsed++;
						lm->used++;

						/* fix negative samples */
						for(j = 0; j < 3; j++)
						{
							if(sample[j] < 0.0f)
								sample[j] = 0.0f;
						}
					}
					else
					{
						/* nick an average value from the neighbors */
						VectorClear(sample);
						VectorClear(dirSample);
						samples = 0.0f;

						/* fixme: why is this disabled?? */
						for(sy = (y - 1); sy <= (y + 1); sy++)
						{
							if(sy < 0 || sy >= lm->h)
								continue;

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

								/* get the neighbor's particulars */
								luxel = SUPER_LUXEL(lightmapNum, sx, sy);
								if(luxel[3] < 0.0f)
									continue;
								VectorAdd(sample, luxel, sample);
								samples += luxel[3];
							}
						}

						/* no samples? */
						if(samples == 0.0f)
						{
							VectorSet(sample, -1.0f, -1.0f, -1.0f);
							samples = 1.0f;
						}
						else
						{
							numUsed++;
							lm->used++;

							/* fix negative samples */
							for(j = 0; j < 3; j++)
							{
								if(sample[j] < 0.0f)
									sample[j] = 0.0f;
							}
						}
					}

					/* scale the sample */
					VectorScale(sample, (1.0f / samples), sample);

					/* store the sample in the radiosity luxels */
					if(bounce > 0)
					{
						radLuxel = RAD_LUXEL(lightmapNum, x, y);
						VectorCopy(sample, radLuxel);

						/* if only storing bounced light, early out here */
						if(bounceOnly && !bouncing)
							continue;
					}

					/* store the sample in the bsp luxels */
					bspLuxel = BSP_LUXEL(lightmapNum, x, y);
					bspDeluxel = BSP_DELUXEL(x, y);

					VectorAdd(bspLuxel, sample, bspLuxel);
					if(deluxemap && lightmapNum == 0)
						VectorAdd(bspDeluxel, dirSample, bspDeluxel);

					/* add color to bounds for solid checking */
					if(samples > 0.0f)
						AddPointToBounds(bspLuxel, colorMins, colorMaxs);
				}
			}

			/* set solid color */
			lm->solid[lightmapNum] = qfalse;
			VectorAdd(colorMins, colorMaxs, lm->solidColor[lightmapNum]);
			VectorScale(lm->solidColor[lightmapNum], 0.5f, lm->solidColor[lightmapNum]);

			/* nocollapse prevents solid lightmaps */
			if(noCollapse == qfalse)
			{
				/* check solid color */
				VectorSubtract(colorMaxs, colorMins, sample);
				if((sample[0] <= SOLID_EPSILON && sample[1] <= SOLID_EPSILON && sample[2] <= SOLID_EPSILON) || (lm->w <= 2 && lm->h <= 2))	/* small lightmaps get forced to solid color */
				{
					/* set to solid */
					VectorCopy(colorMins, lm->solidColor[lightmapNum]);
					lm->solid[lightmapNum] = qtrue;
					numSolidLightmaps++;
				}

				/* if all lightmaps aren't solid, then none of them are solid */
				if(lm->solid[lightmapNum] != lm->solid[0])
				{
					for(y = 0; y < MAX_LIGHTMAPS; y++)
					{
						if(lm->solid[y])
							numSolidLightmaps--;
						lm->solid[y] = qfalse;
					}
				}
			}

			/* wrap bsp luxels if necessary */
			if(lm->wrap[0])
			{
				for(y = 0; y < lm->h; y++)
				{
					bspLuxel = BSP_LUXEL(lightmapNum, 0, y);
					bspLuxel2 = BSP_LUXEL(lightmapNum, lm->w - 1, y);
					VectorAdd(bspLuxel, bspLuxel2, bspLuxel);
					VectorScale(bspLuxel, 0.5f, bspLuxel);
					VectorCopy(bspLuxel, bspLuxel2);
					if(deluxemap && lightmapNum == 0)
					{
						bspDeluxel = BSP_DELUXEL(0, y);
						bspDeluxel2 = BSP_DELUXEL(lm->w - 1, y);
						VectorAdd(bspDeluxel, bspDeluxel2, bspDeluxel);
						VectorScale(bspDeluxel, 0.5f, bspDeluxel);
						VectorCopy(bspDeluxel, bspDeluxel2);
					}
				}
			}
			if(lm->wrap[1])
			{
				for(x = 0; x < lm->w; x++)
				{
					bspLuxel = BSP_LUXEL(lightmapNum, x, 0);
					bspLuxel2 = BSP_LUXEL(lightmapNum, x, lm->h - 1);
					VectorAdd(bspLuxel, bspLuxel2, bspLuxel);
					VectorScale(bspLuxel, 0.5f, bspLuxel);
					VectorCopy(bspLuxel, bspLuxel2);
					if(deluxemap && lightmapNum == 0)
					{
						bspDeluxel = BSP_DELUXEL(x, 0);
						bspDeluxel2 = BSP_DELUXEL(x, lm->h - 1);
						VectorAdd(bspDeluxel, bspDeluxel2, bspDeluxel);
						VectorScale(bspDeluxel, 0.5f, bspDeluxel);
						VectorCopy(bspDeluxel, bspDeluxel2);
					}
				}
			}
		}
	}

	/* -----------------------------------------------------------------
	   convert modelspace deluxemaps to tangentspace
	   ----------------------------------------------------------------- */
	/* note it */
	if(!bouncing)
	{
		if(deluxemap && deluxemode == 1)
		{
			vec3_t          worldUp, myNormal, myTangent, myBinormal;
			float           dist;

			Sys_Printf("converting...");

			for(i = 0; i < numRawLightmaps; i++)
			{
				/* get lightmap */
				lm = &rawLightmaps[i];

				/* walk lightmap samples */
				for(y = 0; y < lm->sh; y++)
				{
					for(x = 0; x < lm->sw; x++)
					{
						/* get normal and deluxel */
						normal = SUPER_NORMAL(x, y);
						cluster = SUPER_CLUSTER(x, y);
						bspDeluxel = BSP_DELUXEL(x, y);
						deluxel = SUPER_DELUXEL(x, y);

						/* get normal */
						VectorSet(myNormal, normal[0], normal[1], normal[2]);

						/* get tangent vectors */
						if(myNormal[0] == 0.0f && myNormal[1] == 0.0f)
						{
							if(myNormal[2] == 1.0f)
							{
								VectorSet(myTangent, 1.0f, 0.0f, 0.0f);
								VectorSet(myBinormal, 0.0f, 1.0f, 0.0f);
							}
							else if(myNormal[2] == -1.0f)
							{
								VectorSet(myTangent, -1.0f, 0.0f, 0.0f);
								VectorSet(myBinormal, 0.0f, 1.0f, 0.0f);
							}
						}
						else
						{
							VectorSet(worldUp, 0.0f, 0.0f, 1.0f);
							CrossProduct(myNormal, worldUp, myTangent);
							VectorNormalize(myTangent);
							CrossProduct(myTangent, myNormal, myBinormal);
							VectorNormalize(myBinormal);
						}

						/* project onto plane */
						dist = -DotProduct(myTangent, myNormal);
						VectorMA(myTangent, dist, myNormal, myTangent);
						dist = -DotProduct(myBinormal, myNormal);
						VectorMA(myBinormal, dist, myNormal, myBinormal);

						/* renormalize */
						VectorNormalize(myTangent);
						VectorNormalize(myBinormal);

						/* convert modelspace deluxel to tangentspace */
						dirSample[0] = bspDeluxel[0];
						dirSample[1] = bspDeluxel[1];
						dirSample[2] = bspDeluxel[2];
						VectorNormalize(dirSample);

						/* fix tangents to world matrix */
						if(myNormal[0] > 0 || myNormal[1] < 0 || myNormal[2] < 0)
							VectorNegate(myTangent, myTangent);

						/* build tangentspace vectors */
						bspDeluxel[0] = DotProduct(dirSample, myTangent);
						bspDeluxel[1] = DotProduct(dirSample, myBinormal);
						bspDeluxel[2] = DotProduct(dirSample, myNormal);
					}
				}
			}
		}
	}

	/* -----------------------------------------------------------------
	   blend lightmaps
	   ----------------------------------------------------------------- */

#ifdef sdfsdfwq312323
	/* note it */
	Sys_Printf("blending...");

	for(i = 0; i < numRawLightmaps; i++)
	{
		vec3_t          myColor;
		float           myBrightness;

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

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

			/* walk lightmap samples */
			for(y = 0; y < lm->sh; y++)
			{
				for(x = 0; x < lm->sw; x++)
				{
					/* get luxel */
					bspLuxel = BSP_LUXEL(lightmapNum, x, y);

					/* get color */
					VectorNormalize(bspLuxel, myColor);
					myBrightness = VectorLength(bspLuxel);
					myBrightness *= (1 / 127.0f);
					myBrightness = myBrightness * myBrightness;
					myBrightness *= 127.0f;
					VectorScale(myColor, myBrightness, bspLuxel);
				}
			}
		}
	}
#endif

	/* -----------------------------------------------------------------
	   collapse non-unique lightmaps
	   ----------------------------------------------------------------- */

	if(noCollapse == qfalse && deluxemap == qfalse)
	{
		/* note it */
		Sys_Printf("collapsing...");

		/* set all twin refs to null */
		for(i = 0; i < numRawLightmaps; i++)
		{
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				rawLightmaps[i].twins[lightmapNum] = NULL;
				rawLightmaps[i].twinNums[lightmapNum] = -1;
				rawLightmaps[i].numStyledTwins = 0;
			}
		}

		/* walk the list of raw lightmaps */
		for(i = 0; i < numRawLightmaps; i++)
		{
			/* get lightmap */
			lm = &rawLightmaps[i];

			/* walk lightmaps */
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				/* early outs */
				if(lm->bspLuxels[lightmapNum] == NULL || lm->twins[lightmapNum] != NULL)
					continue;

				/* find all lightmaps that are virtually identical to this one */
				for(j = i + 1; j < numRawLightmaps; j++)
				{
					/* get lightmap */
					lm2 = &rawLightmaps[j];

					/* walk lightmaps */
					for(lightmapNum2 = 0; lightmapNum2 < MAX_LIGHTMAPS; lightmapNum2++)
					{
						/* early outs */
						if(lm2->bspLuxels[lightmapNum2] == NULL || lm2->twins[lightmapNum2] != NULL)
							continue;

						/* compare them */
						if(CompareBSPLuxels(lm, lightmapNum, lm2, lightmapNum2))
						{
							/* merge and set twin */
							if(MergeBSPLuxels(lm, lightmapNum, lm2, lightmapNum2))
							{
								lm2->twins[lightmapNum2] = lm;
								lm2->twinNums[lightmapNum2] = lightmapNum;
								numTwins++;
								numTwinLuxels += (lm->w * lm->h);

								/* count styled twins */
								if(lightmapNum > 0)
									lm->numStyledTwins++;
							}
						}
					}
				}
			}
		}
	}

	/* -----------------------------------------------------------------
	   sort raw lightmaps by shader
	   ----------------------------------------------------------------- */

	/* note it */
	Sys_Printf("sorting...");

	/* allocate a new sorted list */
	if(sortLightmaps == NULL)
		sortLightmaps = safe_malloc(numRawLightmaps * sizeof(int));

	/* fill it out and sort it */
	for(i = 0; i < numRawLightmaps; i++)
		sortLightmaps[i] = i;
	qsort(sortLightmaps, numRawLightmaps, sizeof(int), CompareRawLightmap);

	/* -----------------------------------------------------------------
	   allocate output lightmaps
	   ----------------------------------------------------------------- */

	/* note it */
	Sys_Printf("allocating...");

	/* kill all existing output lightmaps */
	if(outLightmaps != NULL)
	{
		for(i = 0; i < numOutLightmaps; i++)
		{
			free(outLightmaps[i].lightBits);
#if defined(USE_HDR_LIGHTMAPS)
			if(hdr)
			{
				free(outLightmaps[i].bspLightFloats);
			}
			else
#endif
			{
				free(outLightmaps[i].bspLightBytes);
			}

		}
		free(outLightmaps);
		outLightmaps = NULL;
	}

	numLightmapShaders = 0;
	numOutLightmaps = 0;
	numBSPLightmaps = 0;
	numExtLightmaps = 0;

	/* find output lightmap */
	for(i = 0; i < numRawLightmaps; i++)
	{
		lm = &rawLightmaps[sortLightmaps[i]];
		FindOutLightmaps(lm);
	}

	/* set output numbers in twinned lightmaps */
	for(i = 0; i < numRawLightmaps; i++)
	{
		/* get lightmap */
		lm = &rawLightmaps[sortLightmaps[i]];

		/* walk lightmaps */
		for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
		{
			/* get twin */
			lm2 = lm->twins[lightmapNum];
			if(lm2 == NULL)
				continue;
			lightmapNum2 = lm->twinNums[lightmapNum];

			/* find output lightmap from twin */
			lm->outLightmapNums[lightmapNum] = lm2->outLightmapNums[lightmapNum2];
			lm->lightmapX[lightmapNum] = lm2->lightmapX[lightmapNum2];
			lm->lightmapY[lightmapNum] = lm2->lightmapY[lightmapNum2];
		}
	}

	/* -----------------------------------------------------------------
	   store output lightmaps
	   ----------------------------------------------------------------- */

	/* delete old conflicting external lightmaps */
	// FIXME scan for all lightmaps
	for(i = 0; i < (numOutLightmaps * 2); i++)
	{
		/* determine if file exists */
		sprintf(filename, "%s/" EXTERNAL_LIGHTMAP, dirname, i);
		if(FileExists(filename))
			remove(filename);

		sprintf(filename, "%s/" EXTERNAL_OLDLIGHTMAP, dirname, i);
		if(FileExists(filename))
			remove(filename);

		sprintf(filename, "%s/" EXTERNAL_HDRLIGHTMAP, dirname, i);
		if(FileExists(filename))
			remove(filename);
	}

	/* note it */
	Sys_Printf("storing...");

	/* count the bsp lightmaps and allocate space */
	if(bspLightBytes != NULL)
		free(bspLightBytes);
	if(numBSPLightmaps == 0 || externalLightmaps)
	{
		numBSPLightBytes = 0;
		bspLightBytes = NULL;
	}
	else
	{
		/*
		if(hdr)
		{
			numBSPLightBytes = (numBSPLightmaps * game->lightmapSize * game->lightmapSize * 4);
		}
		else
		*/
		{
			numBSPLightBytes = (numBSPLightmaps * game->lightmapSize * game->lightmapSize * 3);
		}
		bspLightBytes = safe_malloc(numBSPLightBytes);
		memset(bspLightBytes, 0, numBSPLightBytes);
	}

	/* walk the list of output lightmaps */
	for(i = 0; i < numOutLightmaps; i++)
	{
		/* get output lightmap */
		olm = &outLightmaps[i];

		/* fill output lightmap */
		if(lightmapFill)
			FillOutLightmap(olm);

		/* is this a valid bsp lightmap? */
		if(olm->lightmapNum >= 0 && !externalLightmaps)
		{
			/* copy lighting data */
			lb = bspLightBytes + (olm->lightmapNum * game->lightmapSize * game->lightmapSize * 3);
			memcpy(lb, olm->bspLightBytes, game->lightmapSize * game->lightmapSize * 3);

			/* copy direction data */
			if(deluxemap)
			{
				lb = bspLightBytes + ((olm->lightmapNum + 1) * game->lightmapSize * game->lightmapSize * 3);
				memcpy(lb, olm->bspDirBytes, game->lightmapSize * game->lightmapSize * 3);
			}
		}

		/* external lightmap? */
		if(olm->lightmapNum < 0 || olm->extLightmapNum >= 0 || externalLightmaps)
		{
			/* make a directory for the lightmaps */
			Q_mkdir(dirname);

			/* set external lightmap number */
			olm->extLightmapNum = numExtLightmaps;

			/* write lightmap */
#if defined(USE_HDR_LIGHTMAPS)
			if(hdr)
			{
				sprintf(filename, "%s/" EXTERNAL_HDRLIGHTMAP, dirname, numExtLightmaps);
				Sys_FPrintf(SYS_VRB, "\nwriting %s", filename);
				WriteRGBE(filename, olm->bspLightFloats, olm->customWidth, olm->customHeight);
			}
			else
#endif
			{
				sprintf(filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps);
				Sys_FPrintf(SYS_VRB, "\nwriting %s", filename);
				WriteTGA24(filename, olm->bspLightBytes, olm->customWidth, olm->customHeight, qtrue);
			}
			numExtLightmaps++;

			/* write deluxemap */
			if(deluxemap)
			{
				sprintf(filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps);
				Sys_FPrintf(SYS_VRB, "\nwriting %s", filename);
				WriteTGA24(filename, olm->bspDirBytes, olm->customWidth, olm->customHeight,qtrue);
				numExtLightmaps++;

				if(debugDeluxemap)
					olm->extLightmapNum++;
			}
		}
	}

	if(numExtLightmaps > 0)
		Sys_Printf("\n");

	/* delete unused external lightmaps */
	for(i = numExtLightmaps; i; i++)
	{
		/* determine if file exists */
		sprintf(filename, "%s/" EXTERNAL_HDRLIGHTMAP, dirname, i);
		if(FileExists(filename))
			remove(filename);

		sprintf(filename, "%s/" EXTERNAL_LIGHTMAP, dirname, i);
		if(FileExists(filename))
			remove(filename);
		else
			break;
	}

	/* -----------------------------------------------------------------
	   project the lightmaps onto the bsp surfaces
	   ----------------------------------------------------------------- */

	/* note it */
	Sys_Printf("projecting...");

	/* walk the list of surfaces */
	for(i = 0; i < numBSPDrawSurfaces; i++)
	{
		/* get the surface and info */
		ds = &bspDrawSurfaces[i];
		info = &surfaceInfos[i];
		lm = info->lm;
		olm = NULL;

		/* handle surfaces with identical parent */
		if(info->parentSurfaceNum >= 0)
		{
			/* preserve original data and get parent */
			parent = &bspDrawSurfaces[info->parentSurfaceNum];
			memcpy(&dsTemp, ds, sizeof(*ds));

			/* overwrite child with parent data */
			memcpy(ds, parent, sizeof(*ds));

			/* restore key parts */
			ds->fogNum = dsTemp.fogNum;
			ds->firstVert = dsTemp.firstVert;
			ds->firstIndex = dsTemp.firstIndex;
			memcpy(ds->lightmapVecs, dsTemp.lightmapVecs, sizeof(dsTemp.lightmapVecs));

			/* set vertex data */
			dv = &bspDrawVerts[ds->firstVert];
			dvParent = &bspDrawVerts[parent->firstVert];
			for(j = 0; j < ds->numVerts; j++)
			{
				memcpy(dv[j].lightmap, dvParent[j].lightmap, sizeof(dv[j].lightmap));
				memcpy(dv[j].lightColor, dvParent[j].lightColor, sizeof(dv[j].lightColor));
				memcpy(dv[j].lightDirection, dvParent[j].lightDirection, sizeof(dv[j].lightDirection));
			}

			/* skip the rest */
			continue;
		}

		/* handle vertex lit or approximated surfaces */
		else if(lm == NULL || lm->outLightmapNums[0] < 0)
		{
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				ds->lightmapNum[lightmapNum] = -3;
				ds->lightmapStyles[lightmapNum] = ds->vertexStyles[lightmapNum];
			}
		}

		/* handle lightmapped surfaces */
		else
		{
			/* walk lightmaps */
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				/* set style */
				ds->lightmapStyles[lightmapNum] = lm->styles[lightmapNum];

				/* handle unused style */
				if(lm->styles[lightmapNum] == LS_NONE || lm->outLightmapNums[lightmapNum] < 0)
				{
					ds->lightmapNum[lightmapNum] = -3;
					continue;
				}

				/* get output lightmap */
				olm = &outLightmaps[lm->outLightmapNums[lightmapNum]];

				/* set bsp lightmap number */
				ds->lightmapNum[lightmapNum] = olm->lightmapNum;

				/* deluxemap debugging makes the deluxemap visible */
				if(deluxemap && debugDeluxemap && lightmapNum == 0)
					ds->lightmapNum[lightmapNum]++;

				/* calc lightmap origin in texture space */
				lmx = (float)lm->lightmapX[lightmapNum] / (float)olm->customWidth;
				lmy = (float)lm->lightmapY[lightmapNum] / (float)olm->customHeight;

				/* calc lightmap st coords */
				dv = &bspDrawVerts[ds->firstVert];
				ydv = &yDrawVerts[ds->firstVert];
				for(j = 0; j < ds->numVerts; j++)
				{
					if(lm->solid[lightmapNum])
					{
						dv[j].lightmap[lightmapNum][0] = lmx + (0.5f / (float)olm->customWidth);
						dv[j].lightmap[lightmapNum][1] = lmy + (0.5f / (float)olm->customWidth);
					}
					else
					{
						dv[j].lightmap[lightmapNum][0] = lmx + (ydv[j].lightmap[0][0] / (superSample * olm->customWidth));
						dv[j].lightmap[lightmapNum][1] = lmy + (ydv[j].lightmap[0][1] / (superSample * olm->customHeight));
					}
				}
			}
		}

		/* store vertex colors */
		dv = &bspDrawVerts[ds->firstVert];
		for(j = 0; j < ds->numVerts; j++)
		{
			/* walk lightmaps */
			for(lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				/* handle unused style */
				if(ds->vertexStyles[lightmapNum] == LS_NONE)
					VectorClear(color);
				else
				{
					/* get vertex color */
					luxel = VERTEX_LUXEL(lightmapNum, ds->firstVert + j);
					VectorCopy(luxel, color);

					/* get vertex light direction */
					if(deluxemap)
					{
						deluxel = VERTEX_DELUXEL(lightmapNum, ds->firstVert + j);
						VectorCopy(deluxel, lightDirection);
					}

					/* set minimum light */
					if(lightmapNum == 0)
					{
						for(k = 0; k < 3; k++)
							if(color[k] < minVertexLight[k])
								color[k] = minVertexLight[k];
					}
				}

				/* store to bytes */
				if(!info->si->noVertexLight)
				{
					ColorToFloats(color, dv[j].lightColor[lightmapNum], info->si->vertexScale);

					if(deluxemap)
					{
						VectorCopy(lightDirection, dv[j].lightDirection[lightmapNum]);
					}
					else
					{
						VectorCopy(dv[j].normal, dv[j].lightDirection[lightmapNum]);
					}
				}
			}
		}

		/* surfaces with styled lightmaps and a style marker get a custom generated shader (fixme: make this work with external lightmaps) */
		if(olm != NULL && lm != NULL && lm->styles[1] != LS_NONE && game->load != LoadRBSPFile)	//% info->si->styleMarker > 0 )
		{
			qboolean        dfEqual;
			char            key[32], styleStage[512], styleStages[4096], rgbGen[128], alphaGen[128];


			/* setup */
			sprintf(styleStages, "\n\t// Q3Map2 custom lightstyle stage(s)\n");
			dv = &bspDrawVerts[ds->firstVert];

			/* depthFunc equal? */
			if(info->si->styleMarker == 2 || info->si->implicitMap == IM_MASKED)
				dfEqual = qtrue;
			else
				dfEqual = qfalse;

			/* generate stages for styled lightmaps */
			for(lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++)
			{
				/* early out */
				style = lm->styles[lightmapNum];
				if(style == LS_NONE || lm->outLightmapNums[lightmapNum] < 0)
					continue;

				/* get output lightmap */
				olm = &outLightmaps[lm->outLightmapNums[lightmapNum]];

				/* lightmap name */
				if(lm->outLightmapNums[lightmapNum] == lm->outLightmapNums[0])
					strcpy(lightmapName, "$lightmap");
				else
					sprintf(lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum);

				/* get rgbgen string */
				if(rgbGenValues[style] == NULL)
				{
					sprintf(key, "_style%drgbgen", style);
					rgbGenValues[style] = (char *)ValueForKey(&entities[0], key);
					if(rgbGenValues[style][0] == '\0')
						rgbGenValues[style] = "wave noise 0.5 1 0 5.37";
				}
				rgbGen[0] = '\0';
				if(rgbGenValues[style][0] != '\0')
					sprintf(rgbGen, "\t\trgbGen %s // style %d\n", rgbGenValues[style], style);
				else
					rgbGen[0] = '\0';

				/* get alphagen string */
				if(alphaGenValues[style] == NULL)
				{
					sprintf(key, "_style%dalphagen", style);
					alphaGenValues[style] = (char *)ValueForKey(&entities[0], key);
				}
				if(alphaGenValues[style][0] != '\0')
					sprintf(alphaGen, "\t\talphaGen %s // style %d\n", alphaGenValues[style], style);
				else
					alphaGen[0] = '\0';

				/* calculate st offset */
				lmx = dv[0].lightmap[lightmapNum][0] - dv[0].lightmap[0][0];
				lmy = dv[0].lightmap[lightmapNum][1] - dv[0].lightmap[0][1];

				/* create additional stage */
				if(lmx == 0.0f && lmy == 0.0f)
				{
					sprintf(styleStage, "\t{\n" "\t\tmap %s\n"	/* lightmap */
							"\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" "%s"	/* depthFunc equal */
							"%s"	/* rgbGen */
							"%s"	/* alphaGen */
							"\t\ttcGen lightmap\n"
							"\t}\n", lightmapName, (dfEqual ? "\t\tdepthFunc equal\n" : ""), rgbGen, alphaGen);
				}
				else
				{
					sprintf(styleStage, "\t{\n" "\t\tmap %s\n"	/* lightmap */
							"\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" "%s"	/* depthFunc equal */
							"%s"	/* rgbGen */
							"%s"	/* alphaGen */
							"\t\ttcGen lightmap\n" "\t\ttcMod transform 1 0 0 1 %1.5f %1.5f\n"	/* st offset */
							"\t}\n", lightmapName, (dfEqual ? "\t\tdepthFunc equal\n" : ""), rgbGen, alphaGen, lmx, lmy);

				}

				/* concatenate */
				strcat(styleStages, styleStage);
			}

			/* create custom shader */
			/*
			   Tr3B: commented
			   if(info->si->styleMarker == 2)
			   csi = CustomShader(info->si, "q3map_styleMarker2", styleStages);
			   else
			   csi = CustomShader(info->si, "q3map_styleMarker", styleStages);
			 */
			/* WRONG! csi is left uninitialized. wtf? setting to NULL... */
			csi = NULL;

			/* emit remap command */
			//% EmitVertexRemapShader( csi->shader, info->si->shader );

			/* store it */
			//% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) );
			ds->shaderNum =
				EmitShader(csi->shader, &bspShaders[ds->shaderNum].contentFlags, &bspShaders[ds->shaderNum].surfaceFlags);
			//% Sys_Printf( ")\n" );
		}

		/* devise a custom shader for this surface (fixme: make this work with light styles) */
		else if(olm != NULL && lm != NULL && !externalLightmaps &&
				(olm->customWidth != game->lightmapSize || olm->customHeight != game->lightmapSize))
		{
			/* get output lightmap */
			olm = &outLightmaps[lm->outLightmapNums[0]];

			/* do some name mangling */
			sprintf(lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum);

			/* create custom shader */
			csi = CustomShader(info->si, "$lightmap", lightmapName);

			/* store it */
			//% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) );
			ds->shaderNum =
				EmitShader(csi->shader, &bspShaders[ds->shaderNum].contentFlags, &bspShaders[ds->shaderNum].surfaceFlags);
			//% Sys_Printf( ")\n" );
		}

		/* use the plain-jane shader */
		else
			ds->shaderNum =
				EmitShader(info->si->shader, &bspShaders[ds->shaderNum].contentFlags, &bspShaders[ds->shaderNum].surfaceFlags);
	}

	/* finish */
	Sys_Printf("done.\n");

	/* calc num stored */
	numStored = numBSPLightBytes / 3;
	efficiency = (numStored <= 0) ? 0 : (float)numUsed / (float)numStored;

	/* print stats */
	Sys_Printf("%9d luxels used\n", numUsed);
	Sys_Printf("%9d luxels stored (%3.2f percent efficiency)\n", numStored, efficiency * 100.0f);
	Sys_Printf("%9d solid surface lightmaps\n", numSolidLightmaps);
	Sys_Printf("%9d identical surface lightmaps, using %d luxels\n", numTwins, numTwinLuxels);
	Sys_Printf("%9d vertex forced surfaces\n", numSurfsVertexForced);
	Sys_Printf("%9d vertex approximated surfaces\n", numSurfsVertexApproximated);
	Sys_Printf("%9d BSP lightmaps\n", numBSPLightmaps);
	Sys_Printf("%9d total lightmaps\n", numOutLightmaps);
	Sys_Printf("%9d unique lightmap/shader combinations\n", numLightmapShaders);

	/* write map shader file */
	WriteMapShaderFile();
}
