Code Search for Developers
 
 
  

soundv.c from Nxabega at Krugle


Show soundv.c syntax highlighted

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
/*****************************************************************************
 * name:		soundv.c
 *****************************************************************************/

#include "../common/cmdlib.h"
#include "../common/mathlib.h"
#include "../common/bspfile.h"
#include "../common/imagelib.h"
#include "../common/threads.h"
#include "../common/mutex.h"
#include "../common/scriplib.h"

#include "shaders.h"
#include "mesh.h"

#ifdef _WIN32
//Improve floating-point consistency.
#pragma optimize( "p", on )
#endif

#define MAX_CLUSTERS		16384
#define	MAX_PORTALS			32768
#define MAX_FACETS			65536
#define MAX_LIGHTS			16384

#define LIGHTMAP_SIZE		128

#define LIGHTMAP_PIXELSHIFT		0.5

//#define LIGHTMAP_PATCHSHIFT

#define	PORTALFILE	"PRT1"

#define	ON_EPSILON	0.1

#define VectorSet(v, x, y, z)		v[0] = x;v[1] = y;v[2] = z;

typedef struct
{
	vec3_t		normal;
	float		dist;
} plane_t;

#define MAX_POINTS_ON_WINDING	64
//NOTE: whenever this is overflowed parts of lightmaps might end up not being lit
#define	MAX_POINTS_ON_FIXED_WINDING	48

typedef struct
{
	int		numpoints;
	vec3_t	points[MAX_POINTS_ON_FIXED_WINDING];			// variable sized
} winding_t;

typedef struct
{
	plane_t		plane;	// normal pointing into neighbor
	int			leaf;	// neighbor
	winding_t	*winding;
	vec3_t		origin;	// for fast clip testing
	float		radius;
} lportal_t;

#define	MAX_PORTALS_ON_LEAF		128
typedef struct lleaf_s
{
	int			numportals;
	lportal_t	*portals[MAX_PORTALS_ON_LEAF];
	//
	int			numSurfaces;
	int			firstSurface;
} lleaf_t;

typedef struct lFacet_s
{
	int		num;
	plane_t	plane;
	vec3_t	points[4];				//
	int		numpoints;
	float	lightmapCoords[4][2];
	plane_t boundaries[4];			// negative is outside the bounds
	float	textureMatrix[2][4];	// texture coordinates for translucency
	float	lightmapMatrix[2][4];	// lightmap texture coordinates
	vec3_t	mins;
	int		x, y, width, height;
} lFacet_t;

typedef struct lsurfaceTest_s
{
	vec3_t			mins, maxs;
	vec3_t			origin;
	float			radius;
	qboolean		patch;			// true if this is a patch
	qboolean		trisoup;		// true if this is a triangle soup
	int				numFacets;
	lFacet_t		*facets;
	mesh_t			*detailMesh;	// detailed mesh with points for each lmp
	shaderInfo_t	*shader;		// for translucency
	mutex_t			*mutex;
	int				numvolumes;		// number of volumes casted at this surface
	//
	int				always_tracelight;
	int				always_vsound;
} lsurfaceTest_t;

//volume types
#define VOLUME_NORMAL			0
#define VOLUME_DIRECTED			1

#define MAX_TRANSLUCENTFACETS	32

typedef struct lightvolume_s
{
	int num;
	int cluster;							//cluster this light volume started in
	plane_t endplane;						//end plane
	plane_t farplane;						//original end plane
	vec3_t points[MAX_POINTS_ON_WINDING];	//end winding points
	plane_t planes[MAX_POINTS_ON_WINDING];	//volume bounding planes
	int numplanes;							//number of volume bounding planes
	int type;								//light volume type
	//list with translucent surfaces the volume went through
	int transFacets[MAX_TRANSLUCENTFACETS];
	int transSurfaces[MAX_TRANSLUCENTFACETS];
	int numtransFacets;
	//clusters already tested
	byte clusterTested[MAX_CLUSTERS/8];
	//facets already tested
	byte facetTested[MAX_FACETS/8];
	int facetNum;			//number of the facet blocking the light in this volume
	int surfaceNum;			//number of the surface blocking the light in this volume
} lightvolume_t;

//light types
#define LIGHT_POINTRADIAL			1
#define LIGHT_POINTSPOT				2
#define LIGHT_POINTFAKESURFACE		3
#define LIGHT_SURFACEDIRECTED		4
#define LIGHT_SURFACERADIAL			5
#define LIGHT_SURFACESPOT			6

//light distance attenuation types
#define LDAT_QUADRATIC				0
#define LDAT_LINEAR					1
#define LDAT_NOSCALE				2

//light angle attenuation types
#define LAAT_NORMAL					0
#define LAAT_QUADRATIC				1
#define LAAT_DOUBLEQUADRATIC		2

typedef struct vsound_s
{
	vec3_t origin;				//light origin, for point lights
	winding_t w;				//light winding, for area lights
	vec4_t plane;				//light winding plane
	vec3_t normal;				//direction of the light
	int type;					//light type
	vec3_t color;				//light color
	qboolean twosided;			//radiates light at both sides of the winding
	int style;					//light style (not used)
	int atten_disttype;			//light distance attenuation type
	int atten_angletype;		//light angle attenuation type
	float atten_distscale;		//distance attenuation scale
	float atten_anglescale;		//angle attenuation scale
	float radiusByDist;			//radius by distance for spot lights
	float photons;				//emitted photons
	float intensity;			//intensity
	vec3_t emitColor;			//full out-of-gamut value (not used)
	struct shaderInfo_s	*si;	//shader info
	int insolid;				//set when light is in solid
} vsound_t;

static float	lightLinearScale			= 1.0 / 8000;
static float	lightPointScale				= 7500;
static float	lightAreaScale				= 0.25;
static float	lightFormFactorValueScale	= 3;
static int		lightDefaultSubdivide		= 999;		// vary by surface size?
static vec3_t	lightAmbientColor;

static int			portalclusters, numportals, numfaces;
static lleaf_t		*leafs;
static lportal_t	*portals;
static int			numvsounds = 0;
static vsound_t	*vsounds[MAX_LIGHTS];
static int			nostitching = 0;
static int			noalphashading = 0;
static int			nocolorshading = 0;
static int			nobackfaceculling = 0;
static int			defaulttracelight = 0;
static int			radiosity = 0;
static int			radiosity_scale;

static int				clustersurfaces[MAX_MAP_LEAFFACES];
static int				numclustersurfaces = 0;
static lsurfaceTest_t	*lsurfaceTest[MAX_MAP_DRAW_SURFS];
static int				numfacets;
static float			lightmappixelarea[MAX_MAP_LIGHTING/3];
static float			*lightFloats;//[MAX_MAP_LIGHTING];

// from polylib.c
winding_t	*AllocWinding (int points);
void		FreeWinding (winding_t *w);
void		WindingCenter (winding_t *w, vec3_t center);
void		WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs);
vec_t		WindingArea (winding_t *w);
winding_t	*BaseWindingForPlane (vec3_t normal, vec_t dist);
void		ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, 
				vec_t epsilon, winding_t **front, winding_t **back);
winding_t	*ReverseWinding (winding_t *w);

// from light.c
extern char		source[1024];
extern vec3_t	surfaceOrigin[ MAX_MAP_DRAW_SURFS ];
extern int		entitySurface[ MAX_MAP_DRAW_SURFS ];
extern int		samplesize;
extern qboolean	patchshadows;
extern vec3_t	gridSize;

float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w );
void ColorToBytes( const float *color, byte *colorBytes );
void CountLightmaps( void );
void GridAndVertexLighting( void );
void SetEntityOrigins( void );


//#define DEBUGNET

#ifdef DEBUGNET

#include "l_net.h"

socket_t *debug_socket;

/*
=====================
DebugNet_Setup
=====================
*/
void DebugNet_Setup(void)
{
	address_t address;
	int i;

	Net_Setup();
	Net_StringToAddress("127.0.0.1:28000", &address);
	for (i = 0; i < 10; i++)
	{
		debug_socket = Net_Connect(&address, 28005 + i);
		if (debug_socket)
			break;
	}
}

/*
=====================
DebugNet_Shutdown
=====================
*/
void DebugNet_Shutdown(void)
{
	netmessage_t msg;

	if (debug_socket)
	{
		NMSG_Clear(&msg);
		NMSG_WriteByte(&msg, 1);
		Net_Send(debug_socket, &msg);
		Net_Disconnect(debug_socket);
	}
	debug_socket = NULL;
	Net_Shutdown();
}

/*
=====================
DebugNet_RemoveAllPolys
=====================
*/
void DebugNet_RemoveAllPolys(void)
{
	netmessage_t msg;

	if (!debug_socket)
		return;
	NMSG_Clear(&msg);
	NMSG_WriteByte(&msg, 2);		//remove all debug polys
	Net_Send(debug_socket, &msg);
}

/*
====================
DebugNet_DrawWinding
=====================
*/
void DebugNet_DrawWinding(winding_t *w, int color)
{
	netmessage_t msg;
	int i;

	if (!debug_socket)
		return;
	NMSG_Clear(&msg);
	NMSG_WriteByte(&msg, 0);				//draw a winding
	NMSG_WriteByte(&msg, w->numpoints);		//number of points
	NMSG_WriteLong(&msg, color);			//color
	for (i = 0; i < w->numpoints; i++)
	{
		NMSG_WriteFloat(&msg, w->points[i][0]);
		NMSG_WriteFloat(&msg, w->points[i][1]);
		NMSG_WriteFloat(&msg, w->points[i][2]);
	}
	Net_Send(debug_socket, &msg);
}

/*
=====================
DebugNet_DrawLine
=====================
*/
void DebugNet_DrawLine(vec3_t p1, vec3_t p2, int color)
{
	netmessage_t msg;

	if (!debug_socket)
		return;
	NMSG_Clear(&msg);
	NMSG_WriteByte(&msg, 1);				//draw a line
	NMSG_WriteLong(&msg, color);			//color
	NMSG_WriteFloat(&msg, p1[0]);
	NMSG_WriteFloat(&msg, p1[1]);
	NMSG_WriteFloat(&msg, p1[2]);
	NMSG_WriteFloat(&msg, p2[0]);
	NMSG_WriteFloat(&msg, p2[1]);
	NMSG_WriteFloat(&msg, p2[2]);
	Net_Send(debug_socket, &msg);
}

/*
=====================
DebugNet_DrawMesh
=====================
*/
void DebugNet_DrawMesh(mesh_t *mesh)
{
	int i, j;
	float dot;
	drawVert_t	*v1, *v2, *v3, *v4;
	winding_t winding;
	plane_t plane;
	vec3_t d1, d2;

	for(i = 0; i < mesh->width - 1; i++)
	{
		for(j = 0; j < mesh->height - 1; j++)
		{

			v1 = mesh->verts + j * mesh->width + i;
			v2 = v1 + 1;
			v3 = v1 + mesh->width + 1;
			v4 = v1 + mesh->width;

			VectorSubtract( v4->xyz, v1->xyz, d1 );
			VectorSubtract( v3->xyz, v1->xyz, d2 );
			CrossProduct( d2, d1, plane.normal );
			if ( VectorNormalize( plane.normal, plane.normal ) != 0 )
			{
				plane.dist = DotProduct( v1->xyz, plane.normal );
				dot = DotProduct(plane.normal, v2->xyz) - plane.dist;
				if (fabs(dot) < 0.1)
				{
					VectorCopy(v1->xyz, winding.points[0]);
					VectorCopy(v4->xyz, winding.points[1]);
					VectorCopy(v3->xyz, winding.points[2]);
					VectorCopy(v2->xyz, winding.points[3]);
					winding.numpoints = 4;
					DebugNet_DrawWinding(&winding, 2);
					continue;
				}
			}

			winding.numpoints = 3;
			VectorCopy(v1->xyz, winding.points[0]);
			VectorCopy(v4->xyz, winding.points[1]);
			VectorCopy(v3->xyz, winding.points[2]);
			DebugNet_DrawWinding(&winding, 2);

			VectorCopy(v1->xyz, winding.points[0]);
			VectorCopy(v3->xyz, winding.points[1]);
			VectorCopy(v2->xyz, winding.points[2]);
			DebugNet_DrawWinding(&winding, 2);
		}
	}
}

/*
=====================
VS_DrawLightVolume
=====================
*/
int VS_ChopWinding (winding_t *in, plane_t *split, float epsilon);

void VS_DrawLightVolume(vsound_t *light, lightvolume_t *volume)
{
	winding_t w;
	int i;
	vec3_t p2, invsound;

	memcpy(w.points, volume->points, volume->numplanes * sizeof(vec3_t));
	w.numpoints = volume->numplanes;
	DebugNet_DrawWinding(&w, 2);

	if (volume->type == VOLUME_DIRECTED)
	{
		VectorCopy(light->normal, invsound);
		VectorInverse(invsound);
		for (i = 0; i < volume->numplanes; i++)
		{
			VectorCopy(volume->points[i], w.points[0]);
			VectorCopy(volume->points[(i+1) % volume->numplanes], w.points[1]);
			VectorMA(w.points[1], MAX_WORLD_COORD, invsound, w.points[2]);
			VectorMA(w.points[0], MAX_WORLD_COORD, invsound, w.points[3]);
			w.numpoints = 4;
			DebugNet_DrawWinding(&w, 2);
			VectorMA(volume->points[i], 8, volume->planes[i].normal, p2);
			DebugNet_DrawLine(volume->points[i], p2, 3);
		}
	}
	else
	{
		//
		VectorCopy(light->origin, w.points[0]);
		w.numpoints = 3;
		for (i = 0; i < volume->numplanes; i++)
		{
			VectorCopy(volume->points[i], w.points[1]);
			VectorCopy(volume->points[(i+1) % volume->numplanes], w.points[2]);
			VS_ChopWinding(&w, &volume->endplane, 0);
			DebugNet_DrawWinding(&w, 2);
			VectorMA(volume->points[i], 8, volume->planes[i].normal, p2);
			DebugNet_DrawLine(volume->points[i], p2, 3);
		}
	}
}

/*
=============
VS_DrawLightmapPixel
=============
*/
void VS_DrawLightmapPixel(int surfaceNum, int x, int y, int color)
{
	winding_t w;
	dsurface_t *ds;
	mesh_t *mesh;

	ds = &drawSurfaces[surfaceNum];

	if (ds->surfaceType == MST_PATCH)
	{
		mesh = lsurfaceTest[surfaceNum]->detailMesh;
		VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x-ds->lightmapX].xyz, w.points[0]);
		VectorCopy( mesh->verts[(y+1-ds->lightmapY)*mesh->width+x-ds->lightmapX].xyz, w.points[1]);
		VectorCopy( mesh->verts[(y+1-ds->lightmapY)*mesh->width+x+1-ds->lightmapX].xyz, w.points[2]);
		VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x+1-ds->lightmapX].xyz, w.points[3]);
		w.numpoints = 4;
	}
	else
	{
		VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT - ds->lightmapX, ds->lightmapVecs[0], w.points[0]);
		VectorMA(w.points[0], (float) y - LIGHTMAP_PIXELSHIFT - ds->lightmapY, ds->lightmapVecs[1], w.points[0]);
		VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT - ds->lightmapX, ds->lightmapVecs[0], w.points[1]);
		VectorMA(w.points[1], (float) y - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapY, ds->lightmapVecs[1], w.points[1]);
		VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapX, ds->lightmapVecs[0], w.points[2]);
		VectorMA(w.points[2], (float) y - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapY, ds->lightmapVecs[1], w.points[2]);
		VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapX, ds->lightmapVecs[0], w.points[3]);
		VectorMA(w.points[3], (float) y - LIGHTMAP_PIXELSHIFT - ds->lightmapY, ds->lightmapVecs[1], w.points[3]);
		w.numpoints = 4;
	}
	DebugNet_DrawWinding(&w, color);
}

/*
============
VS_DrawPortals
============
*/
void VS_DrawPortals(void)
{
	int j;
	lportal_t *p;

	for (j = 0; j < numportals * 2; j++)
	{
		p = portals + j;
		DebugNet_DrawWinding(p->winding, 1);
	}
}

/*
============
VS_DrawLeaf
============
*/
void VS_DrawLeaf(int cluster)
{
	int i;
	lleaf_t *leaf;
	lportal_t *p;

	leaf = &leafs[cluster];
	for (i = 0; i < leaf->numportals; i++)
	{
		p = leaf->portals[i];
		DebugNet_DrawWinding(p->winding, 1);
	}
}

#endif //DEBUGNET

/*
=============
VS_SplitWinding
=============
*/
int VS_SplitWinding (winding_t *in, winding_t *back, plane_t *split, float epsilon)
{
	vec_t	dists[128];
	int		sides[128];
	int		counts[3];
	vec_t	dot;
	int		i, j;
	vec_t	*p1, *p2;
	vec3_t	mid;
	winding_t out;
	winding_t	*neww;

	counts[0] = counts[1] = counts[2] = 0;

	// determine sides for each point
	for (i=0 ; i<in->numpoints ; i++)
	{
		dot = DotProduct (in->points[i], split->normal);
		dot -= split->dist;
		dists[i] = dot;
		if (dot > epsilon)
			sides[i] = SIDE_FRONT;
		else if (dot < -epsilon)
			sides[i] = SIDE_BACK;
		else
		{
			sides[i] = SIDE_ON;
		}
		counts[sides[i]]++;
	}

	if (!counts[SIDE_BACK])
	{
		if (!counts[SIDE_FRONT])
			return SIDE_ON;
		else
			return SIDE_FRONT;
	}
	
	if (!counts[SIDE_FRONT])
	{
		return SIDE_BACK;
	}

	sides[i] = sides[0];
	dists[i] = dists[0];
	
	neww = &out;

	neww->numpoints = 0;
	back->numpoints = 0;

	for (i=0 ; i<in->numpoints ; i++)
	{
		p1 = in->points[i];

		if (neww->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;		// can't chop -- fall back to original
		}
		if (back->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;
		}

		if (sides[i] == SIDE_ON)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
			VectorCopy (p1, back->points[back->numpoints]);
			back->numpoints++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
		}
		if (sides[i] == SIDE_BACK)
		{
			VectorCopy (p1, back->points[back->numpoints]);
			back->numpoints++;
		}
		
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
		if (neww->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;		// can't chop -- fall back to original
		}

		if (back->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;		// can't chop -- fall back to original
		}

		// generate a split point
		p2 = in->points[(i+1)%in->numpoints];
		
		dot = dists[i] / (dists[i]-dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{	// avoid round off error when possible
			if (split->normal[j] == 1)
				mid[j] = split->dist;
			else if (split->normal[j] == -1)
				mid[j] = -split->dist;
			else
				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
		}
			
		VectorCopy (mid, neww->points[neww->numpoints]);
		neww->numpoints++;
		VectorCopy (mid, back->points[back->numpoints]);
		back->numpoints++;
	}
	memcpy(in, &out, sizeof(winding_t));
	
	return SIDE_CROSS;
}

/*
=====================
VS_LinkSurfaceIntoCluster
=====================
*/
void VS_LinkSurfaceIntoCluster(int cluster, int surfaceNum)
{
	lleaf_t *leaf;
	int i;

	leaf = &leafs[cluster];

	for (i = 0; i < leaf->numSurfaces; i++)
	{
		if (clustersurfaces[leaf->firstSurface + i] == surfaceNum)
			return;
	}
	for (i = numclustersurfaces; i > leaf->firstSurface + leaf->numSurfaces; i--)
		clustersurfaces[i] = clustersurfaces[i-1];
	for (i = 0; i < portalclusters; i++)
	{
		if (i == cluster)
			continue;
		if (leafs[i].firstSurface >= leaf->firstSurface + leaf->numSurfaces)
			leafs[i].firstSurface++;
	}
	clustersurfaces[leaf->firstSurface + leaf->numSurfaces] = surfaceNum;
	leaf->numSurfaces++;
	numclustersurfaces++;
	if (numclustersurfaces >= MAX_MAP_LEAFFACES)
		Error("MAX_MAP_LEAFFACES");
}

/*
=====================
VS_R_LinkSurface
=====================
*/
void VS_R_LinkSurface(int nodenum, int surfaceNum, winding_t *w)
{
	int leafnum, cluster, res;
	dnode_t *node;
	dplane_t *plane;
	winding_t back;
	plane_t split;

	while(nodenum >= 0)
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planeNum];

		VectorCopy(plane->normal, split.normal);
		split.dist = plane->dist;
		res = VS_SplitWinding (w, &back, &split, 0.1);

		if (res == SIDE_FRONT)
		{
			nodenum = node->children[0];
		}
		else if (res == SIDE_BACK)
		{
			nodenum = node->children[1];
		}
		else if (res == SIDE_ON)
		{
			memcpy(&back, w, sizeof(winding_t));
			VS_R_LinkSurface(node->children[1], surfaceNum, &back);
			nodenum = node->children[0];
		}
		else
		{
			VS_R_LinkSurface(node->children[1], surfaceNum, &back);
			nodenum = node->children[0];
		}
	}
	leafnum = -nodenum - 1;
	cluster = dleafs[leafnum].cluster;
	if (cluster != -1)
	{
		VS_LinkSurfaceIntoCluster(cluster, surfaceNum);
	}
}

/*
=====================
VS_LinkSurfaces

maybe link each facet seperately instead of the test surfaces?
=====================
*/
void VS_LinkSurfaces(void)
{
	int i, j;
	lsurfaceTest_t *test;
	lFacet_t *facet;
	winding_t winding;

	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		for (j = 0; j < test->numFacets; j++)
		{
			facet = &test->facets[j];
			memcpy(winding.points, facet->points, facet->numpoints * sizeof(vec3_t));
			winding.numpoints = facet->numpoints;
			VS_R_LinkSurface(0, i, &winding);
		}
	}
}

/*
=====================
VS_TextureMatrixFromPoints
=====================
*/
void VS_TextureMatrixFromPoints(lFacet_t * f, drawVert_t * a, drawVert_t * b, drawVert_t * c)
{
	int			i, j;
	float		t;
	float		m[3][4];
	float		s;

	// This is an incredibly stupid way of solving a three variable equation
	for(i = 0; i < 2; i++)
	{

		m[0][0] = a->xyz[0];
		m[0][1] = a->xyz[1];
		m[0][2] = a->xyz[2];
		m[0][3] = a->st[i];

		m[1][0] = b->xyz[0];
		m[1][1] = b->xyz[1];
		m[1][2] = b->xyz[2];
		m[1][3] = b->st[i];

		m[2][0] = c->xyz[0];
		m[2][1] = c->xyz[1];
		m[2][2] = c->xyz[2];
		m[2][3] = c->st[i];

		if(fabs(m[1][0]) > fabs(m[0][0]) && fabs(m[1][0]) > fabs(m[2][0]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[0][j];
				m[0][j] = m[1][j];
				m[1][j] = t;
			}
		}
		else if(fabs(m[2][0]) > fabs(m[0][0]) && fabs(m[2][0]) > fabs(m[1][0]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[0][j];
				m[0][j] = m[2][j];
				m[2][j] = t;
			}
		}

		s = 1.0 / m[0][0];
		m[0][0] *= s;
		m[0][1] *= s;
		m[0][2] *= s;
		m[0][3] *= s;

		s = m[1][0];
		m[1][0] -= m[0][0] * s;
		m[1][1] -= m[0][1] * s;
		m[1][2] -= m[0][2] * s;
		m[1][3] -= m[0][3] * s;

		s = m[2][0];
		m[2][0] -= m[0][0] * s;
		m[2][1] -= m[0][1] * s;
		m[2][2] -= m[0][2] * s;
		m[2][3] -= m[0][3] * s;

		if(fabs(m[2][1]) > fabs(m[1][1]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[1][j];
				m[1][j] = m[2][j];
				m[2][j] = t;
			}
		}

		s = 1.0 / m[1][1];
		m[1][0] *= s;
		m[1][1] *= s;
		m[1][2] *= s;
		m[1][3] *= s;

		s = m[2][1];// / m[1][1];
		m[2][0] -= m[1][0] * s;
		m[2][1] -= m[1][1] * s;
		m[2][2] -= m[1][2] * s;
		m[2][3] -= m[1][3] * s;

		s = 1.0 / m[2][2];
		m[2][0] *= s;
		m[2][1] *= s;
		m[2][2] *= s;
		m[2][3] *= s;

		f->textureMatrix[i][2] = m[2][3];
		f->textureMatrix[i][1] = m[1][3] - f->textureMatrix[i][2] * m[1][2];
		f->textureMatrix[i][0] = m[0][3] - f->textureMatrix[i][2] * m[0][2] - f->textureMatrix[i][1] * m[0][1];

		f->textureMatrix[i][3] = 0;
/*
		s = fabs( DotProduct( a->xyz, f->textureMatrix[i] ) - a->st[i] );
		if ( s > 0.01 ) {
			Error( "Bad textureMatrix" );
		}
		s = fabs( DotProduct( b->xyz, f->textureMatrix[i] ) - b->st[i] );
		if ( s > 0.01 ) {
			Error( "Bad textureMatrix" );
		}
		s = fabs( DotProduct( c->xyz, f->textureMatrix[i] ) - c->st[i] );
		if ( s > 0.01 ) {
			Error( "Bad textureMatrix" );
		}
*/
	}
}

/*
=====================
VS_LightmapMatrixFromPoints
=====================
*/
void VS_LightmapMatrixFromPoints(dsurface_t * dsurf, shaderInfo_t * si, lFacet_t * f, drawVert_t * a, drawVert_t * b,
								 drawVert_t * c)
{
	int			i, j;
	float		t;
	float		m[3][4], al, bl, cl;
	float		s;
	int			h, w, ssize;
	vec3_t		mins, maxs, delta, size, planeNormal;
	drawVert_t	*verts;
	static int	message;

	// vertex-lit triangle model
	if(dsurf->surfaceType == MST_TRIANGLE_SOUP)
	{
		return;
	}
	
	if(dsurf->lightmapNum < 0)
	{
		return;		// doesn't need lighting
	}

	VectorClear(f->mins);
	if (dsurf->surfaceType != MST_PATCH)
	{
		ssize = samplesize;
		if (si->lightmapSampleSize)
			ssize = si->lightmapSampleSize;
		ClearBounds( mins, maxs );
		verts = &drawVerts[dsurf->firstVert];
		for(i = 0; i < dsurf->numVerts; i++)
		{
			AddPointToBounds( verts[i].xyz, mins, maxs );
		}
		// round to the lightmap resolution
		for(i = 0; i < 3; i++)
		{
			mins[i] = ssize * floor( mins[i] / ssize );
			maxs[i] = ssize * ceil( maxs[i] / ssize );
			f->mins[i] = mins[i];
			size[i] = (maxs[i] - mins[i]) / ssize + 1;
		}
		// the two largest axis will be the lightmap size
		VectorClear(f->lightmapMatrix[0]);
		f->lightmapMatrix[0][3] = 0;
		VectorClear(f->lightmapMatrix[1]);
		f->lightmapMatrix[1][3] = 0;

		planeNormal[0] = fabs( dsurf->lightmapVecs[2][0] );
		planeNormal[1] = fabs( dsurf->lightmapVecs[2][1] );
		planeNormal[2] = fabs( dsurf->lightmapVecs[2][2] );

		if(planeNormal[0] >= planeNormal[1] && planeNormal[0] >= planeNormal[2])
		{
			w = size[1];
			h = size[2];
			f->lightmapMatrix[0][1] = 1.0 / ssize;
			f->lightmapMatrix[1][2] = 1.0 / ssize;
		}
		else if(planeNormal[1] >= planeNormal[0] && planeNormal[1] >= planeNormal[2])
		{
			w = size[0];
			h = size[2];
			f->lightmapMatrix[0][0] = 1.0 / ssize;
			f->lightmapMatrix[1][2] = 1.0 / ssize;
		}
		else
		{
			w = size[0];
			h = size[1];
			f->lightmapMatrix[0][0] = 1.0 / ssize;
			f->lightmapMatrix[1][1] = 1.0 / ssize;
		}
		if(w > LIGHTMAP_WIDTH)
		{
			VectorScale ( f->lightmapMatrix[0], (float)LIGHTMAP_SIZE/w, f->lightmapMatrix[0] );
		}
		
		if(h > LIGHTMAP_HEIGHT)
		{
			VectorScale ( f->lightmapMatrix[1], (float)LIGHTMAP_SIZE/h, f->lightmapMatrix[1] );
		}
		VectorSubtract(a->xyz, f->mins, delta);
		s = (DotProduct( delta, f->lightmapMatrix[0] ) + dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;
		if(fabs(s - a->lightmap[0]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		t = (DotProduct( delta, f->lightmapMatrix[1] ) + dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		if(fabs(t - a->lightmap[1]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		VectorSubtract(b->xyz, f->mins, delta);
		s = (DotProduct( delta, f->lightmapMatrix[0] ) + dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;
		if(fabs(s - b->lightmap[0]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		t = (DotProduct( delta, f->lightmapMatrix[1] ) + dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		if(fabs(t - b->lightmap[1]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		VectorSubtract(c->xyz, f->mins, delta);
		s = (DotProduct( delta, f->lightmapMatrix[0] ) + dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;
		if(fabs(s - c->lightmap[0]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		t = (DotProduct( delta, f->lightmapMatrix[1] ) + dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		if(fabs(t - c->lightmap[1]) > 0.01)
		{
			_printf( "Bad lightmapMatrix" );
		}
		VectorAdd(f->mins, surfaceOrigin[dsurf - drawSurfaces], f->mins);
		return;
	}
	// This is an incredibly stupid way of solving a three variable equation
	for(i = 0; i < 2; i++)
	{

		if (i)
			al = a->lightmap[i] - ((float) dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		else
			al = a->lightmap[i] - ((float) dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;

		m[0][0] = a->xyz[0] - f->mins[0];
		m[0][1] = a->xyz[1] - f->mins[1];
		m[0][2] = a->xyz[2] - f->mins[2];
		m[0][3] = al;

		if (i)
			bl = b->lightmap[i] - ((float) dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		else
			bl = b->lightmap[i] - ((float) dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;

		m[1][0] = b->xyz[0] - f->mins[0];
		m[1][1] = b->xyz[1] - f->mins[1];
		m[1][2] = b->xyz[2] - f->mins[2];
		m[1][3] = bl;

		if (i)
			cl = c->lightmap[i] - ((float) dsurf->lightmapY + 0.5) / LIGHTMAP_SIZE;
		else
			cl = c->lightmap[i] - ((float) dsurf->lightmapX + 0.5) / LIGHTMAP_SIZE;

		m[2][0] = c->xyz[0] - f->mins[0];
		m[2][1] = c->xyz[1] - f->mins[1];
		m[2][2] = c->xyz[2] - f->mins[2];
		m[2][3] = cl;

		if(fabs(m[1][0]) > fabs(m[0][0]) && fabs(m[1][0]) >= fabs(m[2][0]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[0][j];
				m[0][j] = m[1][j];
				m[1][j] = t;
			}
		}
		else if(fabs(m[2][0]) > fabs(m[0][0]) && fabs(m[2][0]) >= fabs(m[1][0]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[0][j];
				m[0][j] = m[2][j];
				m[2][j] = t;
			}
		}

		if (m[0][0])
		{
			s = 1.0 / m[0][0];
			m[0][0] *= s;
			m[0][1] *= s;
			m[0][2] *= s;
			m[0][3] *= s;

			s = m[1][0];
			m[1][0] -= m[0][0] * s;
			m[1][1] -= m[0][1] * s;
			m[1][2] -= m[0][2] * s;
			m[1][3] -= m[0][3] * s;

			s = m[2][0];
			m[2][0] -= m[0][0] * s;
			m[2][1] -= m[0][1] * s;
			m[2][2] -= m[0][2] * s;
			m[2][3] -= m[0][3] * s;
		}

		if(fabs(m[2][1]) > fabs(m[1][1]))
		{
			for(j = 0; j < 4; j++)
			{
				t = m[1][j];
				m[1][j] = m[2][j];
				m[2][j] = t;
			}
		}

		if (m[1][1])
		{
			s = 1.0 / m[1][1];
			m[1][0] *= s;
			m[1][1] *= s;
			m[1][2] *= s;
			m[1][3] *= s;

			s = m[2][1];
			m[2][0] -= m[1][0] * s;
			m[2][1] -= m[1][1] * s;
			m[2][2] -= m[1][2] * s;
			m[2][3] -= m[1][3] * s;
		}

		if (m[2][2])
		{
			s = 1.0 / m[2][2];
			m[2][0] *= s;
			m[2][1] *= s;
			m[2][2] *= s;
			m[2][3] *= s;
		}

		f->lightmapMatrix[i][2] = m[2][3];
		f->lightmapMatrix[i][1] = m[1][3] - f->lightmapMatrix[i][2] * m[1][2];
		f->lightmapMatrix[i][0] = m[0][3] - f->lightmapMatrix[i][2] * m[0][2] - f->lightmapMatrix[i][1] * m[0][1];

		f->lightmapMatrix[i][3] = 0;

		VectorSubtract(a->xyz, f->mins, delta);
		s = fabs( DotProduct( delta, f->lightmapMatrix[i] ) - al );
		if(s > 0.01)
		{
			if (!message)
				_printf( "Bad lightmapMatrix\n" );
			message = qtrue;
		}
		VectorSubtract(b->xyz, f->mins, delta);
		s = fabs( DotProduct( delta, f->lightmapMatrix[i] ) - bl );
		if(s > 0.01)
		{
			if (!message)
				_printf( "Bad lightmapMatrix\n" );
			message = qtrue;
		}
		VectorSubtract(c->xyz, f->mins, delta);
		s = fabs( DotProduct( delta, f->lightmapMatrix[i] ) - cl );
		if(s > 0.01)
		{
			if (!message)
				_printf( "Bad lightmapMatrix\n" );
			message = qtrue;
		}
		VectorAdd(f->mins, surfaceOrigin[dsurf - drawSurfaces], f->mins);
	}
}

/*
=============
Plane_Equal
=============
*/
#define	NORMAL_EPSILON	0.0001
#define	DIST_EPSILON	0.02

static int Plane_Equal(plane_t *a, plane_t *b, int flip)
{
	vec3_t normal;
	float dist;

	if(flip)
	{
		normal[0] = - b->normal[0];
		normal[1] = - b->normal[1];
		normal[2] = - b->normal[2];
		dist = - b->dist;
	}
	else
	{
		normal[0] = b->normal[0];
		normal[1] = b->normal[1];
		normal[2] = b->normal[2];
		dist = b->dist;
	}
	if(fabs(a->normal[0] - normal[0]) < NORMAL_EPSILON
	&& fabs(a->normal[1] - normal[1]) < NORMAL_EPSILON
	   && fabs(a->normal[2] - normal[2]) < NORMAL_EPSILON && fabs(a->dist - dist) < DIST_EPSILON)
		return qtrue;
	return qfalse;
}

/*
=============
VS_PlaneFromPoints
=============
*/
qboolean VS_PlaneFromPoints(plane_t * plane, const vec3_t a, const vec3_t b, const vec3_t c)
{
	vec3_t	d1, d2;

	VectorSubtract( b, a, d1 );
	VectorSubtract( c, a, d2 );
	CrossProduct( d2, d1, plane->normal );
	if(VectorNormalize(plane->normal, plane->normal) == 0)
	{
		return qfalse;
	}

	plane->dist = DotProduct( a, plane->normal );
	return qtrue;
}

/*
=====================
VS_GenerateBoundaryForPoints
=====================
*/
void VS_GenerateBoundaryForPoints(plane_t * boundary, plane_t * plane, vec3_t a, vec3_t b)
{
	vec3_t	d1;

	// make a perpendicular vector to the edge and the surface
	VectorSubtract( a, b, d1 );
	CrossProduct( plane->normal, d1, boundary->normal );
	VectorNormalize( boundary->normal, boundary->normal );
	boundary->dist = DotProduct( a, boundary->normal );
}

/*
=====================
VS_GenerateFacetFor3Points
=====================
*/
qboolean VS_GenerateFacetFor3Points(dsurface_t * dsurf, shaderInfo_t * si, lFacet_t * f, drawVert_t * a, drawVert_t * b,
									drawVert_t * c)
{
	//
	vec3_t dir;
	int i;

	// if we can't generate a valid plane for the points, ignore the facet
	if(!VS_PlaneFromPoints(&f->plane, a->xyz, b->xyz, c->xyz))
	{
		f->numpoints = 0;
		return qfalse;
	}

	f->num = numfacets++;

	VectorAdd( a->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[0] );
	VectorAdd( b->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[1] );
	VectorAdd( c->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[2] );

	f->lightmapCoords[0][0] = a->lightmap[0];
	f->lightmapCoords[0][1] = a->lightmap[1];
	f->lightmapCoords[1][0] = b->lightmap[0];
	f->lightmapCoords[1][1] = b->lightmap[1];
	f->lightmapCoords[2][0] = c->lightmap[0];
	f->lightmapCoords[2][1] = c->lightmap[1];

	VS_GenerateBoundaryForPoints( &f->boundaries[0], &f->plane, f->points[0], f->points[1] );
	VS_GenerateBoundaryForPoints( &f->boundaries[1], &f->plane, f->points[1], f->points[2] );
	VS_GenerateBoundaryForPoints( &f->boundaries[2], &f->plane, f->points[2], f->points[0] );

	for (i = 0; i < 3; i++)
	{
		VectorSubtract(f->points[(i+1)%3], f->points[i], dir);
		if (VectorLength(dir) < 0.1)
			return qfalse;
	}

	VS_TextureMatrixFromPoints( f, a, b, c );
	VS_LightmapMatrixFromPoints( dsurf, si, f, a, b, c );

	f->numpoints = 3;

	return qtrue;
}

/*
=====================
VS_GenerateFacetFor4Points

Attempts to use four points as a planar quad
=====================
*/
#define	PLANAR_EPSILON	0.1
qboolean VS_GenerateFacetFor4Points(dsurface_t * dsurf, shaderInfo_t * si, lFacet_t * f, drawVert_t * a, drawVert_t * b,
									drawVert_t * c, drawVert_t * d)
{
	float	dist;
	vec3_t dir;
	int i;
	plane_t plane;

	// if we can't generate a valid plane for the points, ignore the facet
	if(!VS_PlaneFromPoints(&f->plane, a->xyz, b->xyz, c->xyz))
	{
		f->numpoints = 0;
		return qfalse;
	}

	// if the fourth point is also on the plane, we can make a quad facet
	dist = DotProduct( d->xyz, f->plane.normal ) - f->plane.dist;
	if(fabs(dist) > PLANAR_EPSILON)
	{
		f->numpoints = 0;
		return qfalse;
	}

	VectorAdd( a->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[0] );
	VectorAdd( b->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[1] );
	VectorAdd( c->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[2] );
	VectorAdd( d->xyz, surfaceOrigin[dsurf - drawSurfaces], f->points[3] );

	for (i = 1; i < 4; i++)
	{
		if(!VS_PlaneFromPoints(&plane, f->points[i], f->points[(i + 1) % 4], f->points[(i + 2) % 4]))
		{
			f->numpoints = 0;
			return qfalse;
		}

		if(!Plane_Equal(&f->plane, &plane, qfalse))
		{
			f->numpoints = 0;
			return qfalse;
		}
	}

	f->lightmapCoords[0][0] = a->lightmap[0];
	f->lightmapCoords[0][1] = a->lightmap[1];
	f->lightmapCoords[1][0] = b->lightmap[0];
	f->lightmapCoords[1][1] = b->lightmap[1];
	f->lightmapCoords[2][0] = c->lightmap[0];
	f->lightmapCoords[2][1] = c->lightmap[1];
	f->lightmapCoords[3][0] = d->lightmap[0];
	f->lightmapCoords[3][1] = d->lightmap[1];

	VS_GenerateBoundaryForPoints( &f->boundaries[0], &f->plane, f->points[0], f->points[1] );
	VS_GenerateBoundaryForPoints( &f->boundaries[1], &f->plane, f->points[1], f->points[2] );
	VS_GenerateBoundaryForPoints( &f->boundaries[2], &f->plane, f->points[2], f->points[3] );
	VS_GenerateBoundaryForPoints( &f->boundaries[3], &f->plane, f->points[3], f->points[0] );

	for (i = 0; i < 4; i++)
	{
		VectorSubtract(f->points[(i+1)%4], f->points[i], dir);
		if (VectorLength(dir) < 0.1)
			return qfalse;
	}

	VS_TextureMatrixFromPoints( f, a, b, c );
	VS_LightmapMatrixFromPoints( dsurf, si, f, a, b, c );

	f->num = numfacets++;
	f->numpoints = 4;

	return qtrue;
}

/*
===============
VS_SphereFromBounds
===============
*/
void VS_SphereFromBounds(vec3_t mins, vec3_t maxs, vec3_t origin, float *radius)
{
	vec3_t		temp;

	VectorAdd( mins, maxs, origin );
	VectorScale( origin, 0.5, origin );
	VectorSubtract( maxs, origin, temp );
	*radius = VectorLength( temp );
}

/*
====================
VS_FacetsForTriangleSurface
====================
*/
void VS_FacetsForTriangleSurface(dsurface_t * dsurf, shaderInfo_t * si, lsurfaceTest_t * test)
{
	int			i;
	drawVert_t	*v1, *v2, *v3, *v4;
	int			count;
	int			i1, i2, i3, i4, i5, i6;

	test->patch = qfalse;
	if (dsurf->surfaceType == MST_TRIANGLE_SOUP)
		test->trisoup = qtrue;
	else
		test->trisoup = qfalse;
	test->numFacets = dsurf->numIndexes / 3;
	test->facets = malloc( sizeof( test->facets[0] ) * test->numFacets );
	test->shader = si;

	count = 0;
	for(i = 0; i < test->numFacets; i++)
	{
		i1 = drawIndexes[ dsurf->firstIndex + i*3 ];
		i2 = drawIndexes[ dsurf->firstIndex + i*3 + 1 ];
		i3 = drawIndexes[ dsurf->firstIndex + i*3 + 2 ];

		v1 = &drawVerts[ dsurf->firstVert + i1 ];
		v2 = &drawVerts[ dsurf->firstVert + i2 ];
		v3 = &drawVerts[ dsurf->firstVert + i3 ];

		// try and make a quad out of two triangles
		if(i != test->numFacets - 1)
		{
			i4 = drawIndexes[ dsurf->firstIndex + i*3 + 3 ];
			i5 = drawIndexes[ dsurf->firstIndex + i*3 + 4 ];
			i6 = drawIndexes[ dsurf->firstIndex + i*3 + 5 ];
			if(i4 == i3 && i5 == i2)
			{
				v4 = &drawVerts[ dsurf->firstVert + i6 ];
				if(VS_GenerateFacetFor4Points(dsurf, si, &test->facets[count], v1, v2, v4, v3))
				{
					count++;
					i++;		// skip next tri
					continue;
				}
			}
		}

		if(VS_GenerateFacetFor3Points(dsurf, si, &test->facets[count], v1, v2, v3))
		{
			count++;
		}
	}		

	// we may have turned some pairs into quads
	test->numFacets = count;
}

/*
====================
VS_FacetsForPatch
====================
*/
void VS_FacetsForPatch(dsurface_t * dsurf, int surfaceNum, shaderInfo_t * si, lsurfaceTest_t * test)
{
	int			i, j, x, y;
	drawVert_t	*v1, *v2, *v3, *v4;
	int			count, ssize;
	mesh_t		mesh;
	mesh_t		*subdivided, *detailmesh, *newmesh;
	int widthtable[LIGHTMAP_SIZE], heighttable[LIGHTMAP_SIZE];

	mesh.width = dsurf->patchWidth;
	mesh.height = dsurf->patchHeight;
	mesh.verts = &drawVerts[ dsurf->firstVert ];

	newmesh = SubdivideMesh( mesh, 8, 999 );
	PutMeshOnCurve( *newmesh );
	MakeMeshNormals( *newmesh );

	subdivided = RemoveLinearMeshColumnsRows( newmesh );
	FreeMesh(newmesh);

	//	DebugNet_RemoveAllPolys();
	//	DebugNet_DrawMesh(subdivided);

	ssize = samplesize;
	if (si->lightmapSampleSize)
		ssize = si->lightmapSampleSize;

	if(dsurf->lightmapNum >= 0)
	{

		detailmesh = SubdivideMeshQuads( subdivided, ssize, LIGHTMAP_SIZE, widthtable, heighttable);
		test->detailMesh = detailmesh;

		// DebugNet_RemoveAllPolys();
		// DebugNet_DrawMesh(detailmesh);

		if(detailmesh->width != dsurf->lightmapWidth || detailmesh->height != dsurf->lightmapHeight)
		{
			Error( "Mesh lightmap miscount");
		}
	}
	else
	{
		test->detailMesh = NULL;
		memset(widthtable, 0, sizeof(widthtable));
		memset(heighttable, 0, sizeof(heighttable));
	}

	test->patch = qtrue;
	test->trisoup = qfalse;
	test->numFacets = ( subdivided->width - 1 ) * ( subdivided->height - 1 ) * 2;
	test->facets = malloc( sizeof( test->facets[0] ) * test->numFacets );
	test->shader = si;

	count = 0;
	x = 0;
	for(i = 0; i < subdivided->width - 1; i++)
	{
		y = 0;
		for(j = 0; j < subdivided->height - 1; j++)
		{

			v1 = subdivided->verts + j * subdivided->width + i;
			v2 = v1 + 1;
			v3 = v1 + subdivided->width + 1;
			v4 = v1 + subdivided->width;

			if(VS_GenerateFacetFor4Points(dsurf, si, &test->facets[count], v1, v4, v3, v2))
			{
				test->facets[count].x = x;
				test->facets[count].y = y;
				test->facets[count].width = widthtable[i];
				test->facets[count].height = heighttable[j];
				count++;
			}
			else
			{
				if(VS_GenerateFacetFor3Points(dsurf, si, &test->facets[count], v1, v4, v3))
				{
					test->facets[count].x = x;
					test->facets[count].y = y;
					test->facets[count].width = widthtable[i];
					test->facets[count].height = heighttable[j];
					count++;
				}
				if(VS_GenerateFacetFor3Points(dsurf, si, &test->facets[count], v1, v3, v2))
				{
					test->facets[count].x = x;
					test->facets[count].y = y;
					test->facets[count].width = widthtable[i];
					test->facets[count].height = heighttable[j];
					count++;
				}
			}
			y += heighttable[j];
		}
		x += widthtable[i];
	}
	test->numFacets = count;

	FreeMesh(subdivided);
}

/*
=====================
VS_InitSurfacesForTesting
=====================
*/
void VS_InitSurfacesForTesting(void)
{

	int				i, j, k;
	dsurface_t		*dsurf;
	lsurfaceTest_t	*test;
	shaderInfo_t	*si;
	lFacet_t		*facet;

	for(i = 0; i < numDrawSurfaces; i++)
	{
		// don't light the entity surfaces with vsound
		if ( entitySurface[i] )
			continue;
		//
		dsurf = &drawSurfaces[ i ];
		if(!dsurf->numIndexes && !dsurf->patchWidth)
		{
			continue;
		}

		si = ShaderInfoForShader( dshaders[ dsurf->shaderNum].shader );
		// if the surface is translucent and does not cast an alpha shadow
		if((si->contents & CONTENTS_TRANSLUCENT) && !(si->surfaceFlags & SURF_ALPHASHADOW))
		{
			// if the surface has no lightmap
			if ( dsurf->lightmapNum < 0 )
				continue;
		}

		test = malloc( sizeof( *test ) );
		memset(test, 0, sizeof( *test ));
		test->mutex = MutexAlloc();
		test->numvolumes = 0;
		if (si->forceTraceLight)
			test->always_tracelight = qtrue;
		else if (si->forceVLight)
			test->always_vsound = qtrue;
		lsurfaceTest[i] = test;

		if(dsurf->surfaceType == MST_TRIANGLE_SOUP || dsurf->surfaceType == MST_PLANAR)
		{
			VS_FacetsForTriangleSurface( dsurf, si, test );
		}
		else if(dsurf->surfaceType == MST_PATCH)
		{
			VS_FacetsForPatch( dsurf, i, si, test );
		}
		if (numfacets >= MAX_FACETS)
			Error("numfacets >= MAX_FACETS (%d)", MAX_FACETS);

		ClearBounds( test->mins, test->maxs );
		for (j = 0; j < test->numFacets; j++)
		{
			facet = &test->facets[j];
			for(k = 0; k < facet->numpoints; k++)
			{
				AddPointToBounds( facet->points[k], test->mins, test->maxs );
			}
		}
		VS_SphereFromBounds( test->mins, test->maxs, test->origin, &test->radius );
	}
	_printf("%6d facets\n", numfacets);
	_printf("linking surfaces...\n");
	VS_LinkSurfaces();
}

/*
=============
VS_ChopWinding
=============
*/
int VS_ChopWinding (winding_t *in, plane_t *split, float epsilon)
{
	vec_t	dists[128];
	int		sides[128];
	int		counts[3];
	vec_t	dot;
	int		i, j;
	vec_t	*p1, *p2;
	vec3_t	mid;
	winding_t out;
	winding_t	*neww;

	counts[0] = counts[1] = counts[2] = 0;

	// determine sides for each point
	for (i=0 ; i<in->numpoints ; i++)
	{
		dot = DotProduct (in->points[i], split->normal);
		dot -= split->dist;
		dists[i] = dot;
		if (dot > epsilon)
			sides[i] = SIDE_FRONT;
		else if (dot < -epsilon)
			sides[i] = SIDE_BACK;
		else
		{
			sides[i] = SIDE_ON;
		}
		counts[sides[i]]++;
	}

	if (!counts[SIDE_BACK])
	{
		if (!counts[SIDE_FRONT])
			return SIDE_ON;
		else
			return SIDE_FRONT;
	}
	
	if (!counts[SIDE_FRONT])
	{
		return SIDE_BACK;
	}

	sides[i] = sides[0];
	dists[i] = dists[0];
	
	neww = &out;

	neww->numpoints = 0;

	for (i=0 ; i<in->numpoints ; i++)
	{
		p1 = in->points[i];

		if (neww->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_ChopWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;		// can't chop -- fall back to original
		}

		if (sides[i] == SIDE_ON)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, neww->points[neww->numpoints]);
			neww->numpoints++;
		}
		
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
		if (neww->numpoints >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_ChopWinding -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return SIDE_FRONT;		// can't chop -- fall back to original
		}

		// generate a split point
		p2 = in->points[(i+1)%in->numpoints];
		
		dot = dists[i] / (dists[i]-dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{	// avoid round off error when possible
			if (split->normal[j] == 1)
				mid[j] = split->dist;
			else if (split->normal[j] == -1)
				mid[j] = -split->dist;
			else
				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
		}
			
		VectorCopy (mid, neww->points[neww->numpoints]);
		neww->numpoints++;
	}
	memcpy(in, &out, sizeof(winding_t));
	
	return SIDE_CROSS;
}

/*
=============
VS_ChopWindingWithBrush

  returns all winding fragments outside the brush
=============
*/
int VS_ChopWindingWithBrush(winding_t *w, dbrush_t *brush, winding_t *outwindings, int maxout)
{
	int i, res, numout;
	winding_t front, back;
	plane_t plane;

	numout = 0;
	memcpy(front.points, w->points, w->numpoints * sizeof(vec3_t));
	front.numpoints = w->numpoints;
	for (i = 0; i < brush->numSides; i++)
	{
		VectorCopy(dplanes[ dbrushsides[ brush->firstSide + i ].planeNum ].normal, plane.normal);
		VectorInverse(plane.normal);
		plane.dist = -dplanes[ dbrushsides[ brush->firstSide + i ].planeNum ].dist;
		res = VS_SplitWinding(&front, &back, &plane, 0.1);
		if (res == SIDE_BACK || res == SIDE_ON)
		{
			memcpy(outwindings[0].points, w->points, w->numpoints * sizeof(vec3_t));
			outwindings[0].numpoints = w->numpoints;
			return 1;	//did not intersect
		}
		if (res != SIDE_FRONT)
		{
			if (numout >= maxout)
			{
				_printf("WARNING: VS_ChopWindingWithBrush: more than %d windings\n", maxout);
				return 0;
			}
			memcpy(outwindings[numout].points, back.points, back.numpoints * sizeof(vec3_t));
			outwindings[numout].numpoints = back.numpoints;
			numout++;
		}
	}
	return numout;
}

/*
=============
VS_WindingAreaOutsideBrushes
=============
*/
float VS_WindingAreaOutsideBrushes(winding_t *w, int *brushnums, int numbrushes)
{
	int i, j, numwindings[2], n;
	winding_t windingsbuf[2][64];
	dbrush_t *brush;
	float area;

	memcpy(windingsbuf[0][0].points, w->points, w->numpoints * sizeof(vec3_t));
	windingsbuf[0][0].numpoints = w->numpoints;
	numwindings[0] = 1;
	for (i = 0; i < numbrushes; i++)
	{
		brush = &dbrushes[brushnums[i]];
		if(!(dshaders[brush->shaderNum].contentFlags & (CONTENTS_LAVA
					| CONTENTS_SLIME
					| CONTENTS_WATER
					| CONTENTS_FOG
					| CONTENTS_AREAPORTAL
					| CONTENTS_PLAYERCLIP
					| CONTENTS_MONSTERCLIP
					| CONTENTS_CLUSTERPORTAL
					| CONTENTS_DONOTENTER
					| CONTENTS_BODY
					| CONTENTS_CORPSE
					| CONTENTS_TRANSLUCENT
					| CONTENTS_TRIGGER
					| CONTENTS_NODROP) ) &&
			(dshaders[brush->shaderNum].contentFlags & CONTENTS_SOLID) )
		{
			numwindings[!(i & 1)] = 0;
			for (j = 0; j < numwindings[i&1]; j++)
			{
				n = VS_ChopWindingWithBrush(&windingsbuf[i&1][j], brush,
											&windingsbuf[!(i & 1)][numwindings[!(i & 1)]], 64 - numwindings[!(i & 1)]);
				numwindings[!(i&1)] += n;
			}
			if (!numwindings[!(i&1)])
				return 0;
		}
		else
		{
			for (j = 0; j < numwindings[i&1]; j++)
			{
				windingsbuf[!(i&1)][j] = windingsbuf[i&1][j];
			}
			numwindings[!(i&1)] = numwindings[i&1];
		}
	}
	area = 0;
	for (j = 0; j < numwindings[i&1]; j++)
	{
		area += WindingArea(&windingsbuf[i&1][j]);
	}
	return area;
}

/*
=============
VS_R_WindingAreaOutsideSolid
=============
*/
float VS_R_WindingAreaOutsideSolid(winding_t *w, vec3_t normal, int nodenum)
{
	int leafnum, res;
	float area;
	dnode_t *node;
	dleaf_t *leaf;
	dplane_t *plane;
	winding_t back;
	plane_t split;

	area = 0;
	while(nodenum >= 0)
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planeNum];

		VectorCopy(plane->normal, split.normal);
		split.dist = plane->dist;
		res = VS_SplitWinding (w, &back, &split, 0.1);

		if (res == SIDE_FRONT)
		{
			nodenum = node->children[0];
		}
		else if (res == SIDE_BACK)
		{
			nodenum = node->children[1];
		}
		else if (res == SIDE_ON)
		{
			if (DotProduct(normal, plane->normal) > 0)
				nodenum = node->children[0];
			else
				nodenum = node->children[1];
		}
		else
		{
			area += VS_R_WindingAreaOutsideSolid(&back, normal, node->children[1]);
			nodenum = node->children[0];
		}
	}
	leafnum = -nodenum - 1;
	leaf = &dleafs[leafnum];
	if (leaf->cluster != -1)
	{
		area += VS_WindingAreaOutsideBrushes(w, &dleafbrushes[leaf->firstLeafBrush], leaf->numLeafBrushes);
	}
	return area;
}

/*
=============
VS_WindingAreaOutsideSolid
=============
*/
float VS_WindingAreaOutsideSolid(winding_t *w, vec3_t normal)
{
	return VS_R_WindingAreaOutsideSolid(w, normal, 0);
}

/*
=============
VS_ChopWindingWithFacet
=============
*/
float VS_ChopWindingWithFacet(winding_t *w, lFacet_t *facet)
{
	int i;

	for (i = 0; i < facet->numpoints; i++)
	{
		if (VS_ChopWinding(w, &facet->boundaries[i], 0) == SIDE_BACK)
			return 0;
	}
	if (nostitching)
		return WindingArea(w);
	else
		return VS_WindingAreaOutsideSolid(w, facet->plane.normal);
}

/*
=============
VS_CalcVisibleLightmapPixelArea

nice brute force ;)
=============
*/
void VS_CalcVisibleLightmapPixelArea(void)
{
	int				i, j, x, y, k;
	dsurface_t		*ds;
	lsurfaceTest_t	*test;
	mesh_t			*mesh;
	winding_t w, tmpw;
	float area;

	_printf("calculating visible lightmap pixel area...\n");
	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		ds = &drawSurfaces[ i ];

		if ( ds->lightmapNum < 0 )
			continue;

		for (y = 0; y < ds->lightmapHeight; y++)
		{
			for (x = 0; x < ds->lightmapWidth; x++)
			{
				if (ds->surfaceType == MST_PATCH)
				{
					if (y == ds->lightmapHeight-1)
						continue;
					if (x == ds->lightmapWidth-1)
						continue;
					mesh = lsurfaceTest[i]->detailMesh;
					VectorCopy( mesh->verts[y*mesh->width+x].xyz, w.points[0]);
					VectorCopy( mesh->verts[(y+1)*mesh->width+x].xyz, w.points[1]);
					VectorCopy( mesh->verts[(y+1)*mesh->width+x+1].xyz, w.points[2]);
					VectorCopy( mesh->verts[y*mesh->width+x+1].xyz, w.points[3]);
					w.numpoints = 4;
					if (nostitching)
						area = WindingArea(&w);
					else
						area = VS_WindingAreaOutsideSolid(&w, mesh->verts[y*mesh->width+x].normal);
				}
				else
				{
					VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT, ds->lightmapVecs[0], w.points[0]);
					VectorMA(w.points[0], (float) y - LIGHTMAP_PIXELSHIFT, ds->lightmapVecs[1], w.points[0]);
					VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT, ds->lightmapVecs[0], w.points[3]);
					VectorMA(w.points[3], (float) y - LIGHTMAP_PIXELSHIFT + 1, ds->lightmapVecs[1], w.points[3]);
					VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT + 1, ds->lightmapVecs[0], w.points[2]);
					VectorMA(w.points[2], (float) y - LIGHTMAP_PIXELSHIFT + 1, ds->lightmapVecs[1], w.points[2]);
					VectorMA(ds->lightmapOrigin, (float) x - LIGHTMAP_PIXELSHIFT + 1, ds->lightmapVecs[0], w.points[1]);
					VectorMA(w.points[1], (float) y - LIGHTMAP_PIXELSHIFT, ds->lightmapVecs[1], w.points[1]);
					w.numpoints = 4;
					area = 0;
					for (j = 0; j < test->numFacets; j++)
					{
						memcpy(&tmpw, &w, sizeof(winding_t));
						area += VS_ChopWindingWithFacet(&tmpw, &test->facets[j]);
					}
				}
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				lightmappixelarea[k] = area;
			}
		}
	}
}

/*
=============
VS_FindAdjacentSurface
=============
*/
int VS_FindAdjacentSurface(int surfaceNum, int facetNum, vec3_t p1, vec3_t p2, int *sNum, int *fNum, int *point)
{
	int i, j, k;
	lsurfaceTest_t *test;
	lFacet_t *facet;
	dsurface_t *ds;
	float *fp1, *fp2;
	vec3_t dir;
	plane_t *facetplane;
	//	winding_t w;

	facetplane = &lsurfaceTest[surfaceNum]->facets[facetNum].plane;
	//	DebugNet_RemoveAllPolys();
	//	memcpy(w.points, lsurfaceTest[surfaceNum]->facets[facetNum].points,
	//			lsurfaceTest[surfaceNum]->facets[facetNum].numpoints * sizeof(vec3_t));
	//	w.numpoints = lsurfaceTest[surfaceNum]->facets[facetNum].numpoints;
	//	DebugNet_DrawWinding(&w, 2);
	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		if (i == surfaceNum)
			continue;
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		if (test->trisoup)// || test->patch)
			continue;
		ds = &drawSurfaces[i];
		if ( ds->lightmapNum < 0 )
			continue;
		//if this surface is not even near the edge
		VectorSubtract(p1, test->origin, dir);
		if(fabs(dir[0]) > test->radius || fabs(dir[1]) > test->radius || fabs(dir[1]) > test->radius)
		{
			VectorSubtract(p2, test->origin, dir);
			if(fabs(dir[0]) > test->radius || fabs(dir[1]) > test->radius || fabs(dir[1]) > test->radius)
			{
				continue;
			}
		}
		//
		for (j = 0; j < test->numFacets; j++)
		{
			facet = &test->facets[j];
			//
			//if (!Plane_Equal(&facet->plane, facetplane, qfalse))
			if (DotProduct(facet->plane.normal, facetplane->normal) < 0.9)
			{
				if (!test->trisoup && !test->patch)
					break;
				continue;
			}
			//
			for (k = 0; k < facet->numpoints; k++)
			{
				fp1 = facet->points[k];
				if(fabs(p2[0] - fp1[0]) < 0.1 && fabs(p2[1] - fp1[1]) < 0.1 && fabs(p2[2] - fp1[2]) < 0.1)
				{
					fp2 = facet->points[(k+1) % facet->numpoints];
					if(fabs(p1[0] - fp2[0]) < 0.1 && fabs(p1[1] - fp2[1]) < 0.1 && fabs(p1[2] - fp2[2]) < 0.1)
					{
						//	memcpy(w.points, facet->points, facet->numpoints * sizeof(vec3_t));
						//	w.numpoints = facet->numpoints;
						//	DebugNet_DrawWinding(&w, 1);
						*sNum = i;
						*fNum = j;
						*point = k;
						return qtrue;
					}
				}
				/*
				else if (fabs(p1[0] - fp1[0]) < 0.1 &&
					fabs(p1[1] - fp1[1]) < 0.1 &&
					fabs(p1[2] - fp1[2]) < 0.1)
				{
					fp2 = facet->points[(k+1) % facet->numpoints];
					if (fabs(p2[0] - fp2[0]) < 0.1 &&
						fabs(p2[1] - fp2[1]) < 0.1 &&
						fabs(p2[2] - fp2[2]) < 0.1)
					{
						//	memcpy(w.points, facet->points, facet->numpoints * sizeof(vec3_t));
						//	w.numpoints = facet->numpoints;
						//	DebugNet_DrawWinding(&w, 1);
						*sNum = i;
						*fNum = j;
						*point = k;
						return qtrue;
					}
				}
				//*/
			}
		}
	}
	return qfalse;
}

/*
=============
VS_SmoothenLightmapEdges

this code is used to smoothen lightmaps across surface edges
=============
*/
void VS_SmoothenLightmapEdges(void)
{
	int i, j, k, coords1[2][2];
	float coords2[2][2];
	int x1, y1, xinc1, yinc1, k1, k2;
	float x2, y2, xinc2, yinc2, length;
	int surfaceNum, facetNum, point;
	lsurfaceTest_t *test;
	lFacet_t *facet1, *facet2;
	dsurface_t *ds1, *ds2;
	float *p[2], s, t, *color1, *color2;
	vec3_t dir, cross;

	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		if (test->trisoup)// || test->patch)
			continue;
		ds1 = &drawSurfaces[i];
		if ( ds1->lightmapNum < 0 )
			continue;
		for (j = 0; j < test->numFacets; j++)
		{
			facet1 = &test->facets[j];
			//
			for (k = 0; k < facet1->numpoints; k++)
			{
				p[0] = facet1->points[k];
				p[1] = facet1->points[(k+1)%facet1->numpoints];
				//
				coords1[0][0] = facet1->lightmapCoords[k][0] * LIGHTMAP_SIZE;
				coords1[0][1] = facet1->lightmapCoords[k][1] * LIGHTMAP_SIZE;
				coords1[1][0] = facet1->lightmapCoords[(k+1)%facet1->numpoints][0] * LIGHTMAP_SIZE;
				coords1[1][1] = facet1->lightmapCoords[(k+1)%facet1->numpoints][1] * LIGHTMAP_SIZE;
				if (coords1[0][0] >= LIGHTMAP_SIZE)
					coords1[0][0] = LIGHTMAP_SIZE-1;
				if (coords1[0][1] >= LIGHTMAP_SIZE)
					coords1[0][1] = LIGHTMAP_SIZE-1;
				if (coords1[1][0] >= LIGHTMAP_SIZE)
					coords1[1][0] = LIGHTMAP_SIZE-1;
				if (coords1[1][1] >= LIGHTMAP_SIZE)
					coords1[1][1] = LIGHTMAP_SIZE-1;
				// try one row or column further because on flat faces the lightmap can
				// extend beyond the edge
				VectorSubtract(p[1], p[0], dir);
				VectorNormalize(dir, dir);
				CrossProduct(dir, facet1->plane.normal, cross);
				//
				if (coords1[0][0] - coords1[1][0] == 0)
				{
					s = DotProduct( cross, facet1->lightmapMatrix[0] );
					coords1[0][0] += s < 0 ? 1 : -1;
					coords1[1][0] += s < 0 ? 1 : -1;
					if (coords1[0][0] < ds1->lightmapX || coords1[0][0] >= ds1->lightmapX + ds1->lightmapWidth)
					{
						coords1[0][0] += s < 0 ? -1 : 1;
						coords1[1][0] += s < 0 ? -1 : 1;
					}
					length = fabs(coords1[1][1] - coords1[0][1]);
				}
				else if (coords1[0][1] - coords1[1][1] == 0)
				{
					t = DotProduct( cross, facet1->lightmapMatrix[1] );
					coords1[0][1] += t < 0 ? 1 : -1;
					coords1[1][1] += t < 0 ? 1 : -1;
					if (coords1[0][1] < ds1->lightmapY || coords1[0][1] >= ds1->lightmapY + ds1->lightmapHeight)
					{
						coords1[0][1] += t < 0 ? -1 : 1;
						coords1[1][1] += t < 0 ? -1 : 1;
					}
					length = fabs(coords1[1][0] - coords1[0][0]);
				}
				else
				{
					//the edge is not parallell to one of the lightmap axis
					continue;
				}
				//
				x1 = coords1[0][0];
				y1 = coords1[0][1];
				xinc1 = coords1[1][0] - coords1[0][0];
				if(xinc1 < 0)
					xinc1 = -1;
				if(xinc1 > 0)
					xinc1 = 1;
				yinc1 = coords1[1][1] - coords1[0][1];
				if(yinc1 < 0)
					yinc1 = -1;
				if(yinc1 > 0)
					yinc1 = 1;
				// the edge should be parallell to one of the lightmap axis
				if (xinc1 != 0 && yinc1 != 0)
					continue;
				//
				if (!VS_FindAdjacentSurface(i, j, p[0], p[1], &surfaceNum, &facetNum, &point))
					continue;
				//
				ds2 = &drawSurfaces[surfaceNum];
				facet2 = &lsurfaceTest[surfaceNum]->facets[facetNum];
				coords2[0][0] = facet2->lightmapCoords[(point+1)%facet2->numpoints][0] * LIGHTMAP_SIZE;
				coords2[0][1] = facet2->lightmapCoords[(point+1)%facet2->numpoints][1] * LIGHTMAP_SIZE;
				coords2[1][0] = facet2->lightmapCoords[point][0] * LIGHTMAP_SIZE;
				coords2[1][1] = facet2->lightmapCoords[point][1] * LIGHTMAP_SIZE;
				if (coords2[0][0] >= LIGHTMAP_SIZE)
					coords2[0][0] = LIGHTMAP_SIZE-1;
				if (coords2[0][1] >= LIGHTMAP_SIZE)
					coords2[0][1] = LIGHTMAP_SIZE-1;
				if (coords2[1][0] >= LIGHTMAP_SIZE)
					coords2[1][0] = LIGHTMAP_SIZE-1;
				if (coords2[1][1] >= LIGHTMAP_SIZE)
					coords2[1][1] = LIGHTMAP_SIZE-1;
				//
				x2 = coords2[0][0];
				y2 = coords2[0][1];
				xinc2 = coords2[1][0] - coords2[0][0];
				if (length)
					xinc2 = xinc2 / length;
				yinc2 = coords2[1][1] - coords2[0][1];
				if (length)
					yinc2 = yinc2 / length;
				// the edge should be parallell to one of the lightmap axis
				if ((int) xinc2 != 0 && (int) yinc2 != 0)
					continue;
				//
				while(1)
				{
					k1 = ( ds1->lightmapNum * LIGHTMAP_HEIGHT + y1) * LIGHTMAP_WIDTH + x1;
					k2 = ( ds2->lightmapNum * LIGHTMAP_HEIGHT + ((int) y2)) * LIGHTMAP_WIDTH + ((int) x2);
					color1 = lightFloats + k1*3;
					color2 = lightFloats + k2*3;
					if (lightmappixelarea[k1] < 0.01)
					{
						color1[0] = color2[0];
						color1[1] = color2[1];
						color1[2] = color2[2];
					}
					else
					{
						color1[0] = (float) color2[0] * 0.7 + (float) color1[0] * 0.3;
						color1[1] = (float) color2[1] * 0.7 + (float) color1[1] * 0.3;
						color1[2] = (float) color2[2] * 0.7 + (float) color1[2] * 0.3;
					}
					//
					if(x1 == coords1[1][0] && y1 == coords1[1][1])
						break;
					x1 += xinc1;
					y1 += yinc1;
					x2 += xinc2;
					y2 += yinc2;
					if (x2 < ds2->lightmapX)
						x2 = ds2->lightmapX;
					if (x2 >= ds2->lightmapX + ds2->lightmapWidth)
						x2 = ds2->lightmapX + ds2->lightmapWidth-1;
					if (y2 < ds2->lightmapY)
						y2 = ds2->lightmapY;
					if (y2 >= ds2->lightmapY + ds2->lightmapHeight)
						y2 = ds2->lightmapY + ds2->lightmapHeight-1;
				}
			}
		}
	}
}

/*
=============
VS_FixLightmapEdges
=============
*/
void VS_FixLightmapEdges(void)
{
	int				i, j, x, y, k, foundvalue, height, width, index;
	int				pos, top, bottom;
	dsurface_t		*ds;
	lsurfaceTest_t	*test;
	float			color[3];
	float			*ptr;
	byte filled[(LIGHTMAP_SIZE+1) * (LIGHTMAP_SIZE+1) / 8];
	float lightmap_edge_epsilon;

	lightmap_edge_epsilon = 0.1 * samplesize;
	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		ds = &drawSurfaces[ i ];

		if ( ds->lightmapNum < 0 )
			continue;
		if (ds->surfaceType == MST_PATCH)
		{
			height = ds->lightmapHeight - 1;
			width = ds->lightmapWidth - 1;
		}
		else
		{
			height = ds->lightmapHeight;
			width = ds->lightmapWidth;
		}
		memset(filled, 0, sizeof(filled));
//		printf("\n");
		for (x = 0; x < width; x++)
		{
			for (y = 0; y < height; y++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (lightmappixelarea[k] > lightmap_edge_epsilon)
				{
					index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
					filled[index >> 3] |= 1 << (index & 7);
//					printf("*");
				}
//				else
//					printf("_");
			}
//			printf("\n");
		}
		for (y = 0; y < height; y++)
		{
			pos = -2;
			for (x = 0; x < width; x++)
			{
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (pos == -2)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
						pos = -1;
				}
				else if (pos == -1)
				{
					if (!(filled[index >> 3] & (1 << (index & 7))))
						pos = x - 1;
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						bottom = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + pos;
						top = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
						for (j = 0; j < (x - pos + 1) / 2; j++)
						{
							k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y)
								* LIGHTMAP_WIDTH + ds->lightmapX + pos + j + 1;
							index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + pos + j + 1;
							filled[index >> 3] |= 1 << (index & 7);
							(lightFloats + k*3)[0] = (lightFloats + top*3)[0];
							(lightFloats + k*3)[1] = (lightFloats + top*3)[1];
							(lightFloats + k*3)[2] = (lightFloats + top*3)[2];
							k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y)
								* LIGHTMAP_WIDTH + ds->lightmapX + x - j - 1;
							index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x - j - 1;
							filled[index >> 3] |= 1 << (index & 7);
							(lightFloats + k*3)[0] = (lightFloats + bottom*3)[0];
							(lightFloats + k*3)[1] = (lightFloats + bottom*3)[1];
							(lightFloats + k*3)[2] = (lightFloats + bottom*3)[2];
						}
						pos = -1;
					}
				}
			}
		}
		for (x = 0; x < width; x++)
		{
			pos = -2;
			for (y = 0; y < height; y++)
			{
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (pos == -2)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
						pos = -1;
				}
				else if (pos == -1)
				{
					if (!(filled[index >> 3] & (1 << (index & 7))))
						pos = y - 1;
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						bottom = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + pos) * LIGHTMAP_WIDTH + ds->lightmapX + x;
						top = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
						for (j = 0; j < (y - pos + 1) / 2; j++)
						{
							k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + pos + j + 1)
								* LIGHTMAP_WIDTH + ds->lightmapX + x;
							index = (ds->lightmapY + pos + j + 1) * LIGHTMAP_WIDTH + ds->lightmapX + x;
							filled[index >> 3] |= 1 << (index & 7);
							(lightFloats + k*3)[0] = (lightFloats + top*3)[0];
							(lightFloats + k*3)[1] = (lightFloats + top*3)[1];
							(lightFloats + k*3)[2] = (lightFloats + top*3)[2];
							k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y - j - 1)
								* LIGHTMAP_WIDTH + ds->lightmapX + x;
							index = (ds->lightmapY + y - j - 1) * LIGHTMAP_WIDTH + ds->lightmapX + x;
							filled[index >> 3] |= 1 << (index & 7);
							(lightFloats + k*3)[0] = (lightFloats + bottom*3)[0];
							(lightFloats + k*3)[1] = (lightFloats + bottom*3)[1];
							(lightFloats + k*3)[2] = (lightFloats + bottom*3)[2];
						}
						pos = -1;
					}
				}
			}
		}
		for (y = 0; y < height; y++)
		{
			foundvalue = qfalse;
			for (x = 0; x < width; x++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (foundvalue)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
					}
					else
					{
						ptr = lightFloats + k*3;
						ptr[0] = color[0];
						ptr[1] = color[1];
						ptr[2] = color[2];
						filled[index >> 3] |= 1 << (index & 7);
					}
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
						foundvalue = qtrue;
					}
				}
			}
			foundvalue = qfalse;
			for (x = width-1; x >= 0; x--)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (foundvalue)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
					}
					else
					{
						ptr = lightFloats + k*3;
						ptr[0] = color[0];
						ptr[1] = color[1];
						ptr[2] = color[2];
						filled[index >> 3] |= 1 << (index & 7);
					}
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
						foundvalue = qtrue;
					}
				}
			}
		}
		for (x = 0; x < width; x++)
		{
			foundvalue = qfalse;
			for (y = 0; y < height; y++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (foundvalue)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
					}
					else
					{
						ptr = lightFloats + k*3;
						ptr[0] = color[0];
						ptr[1] = color[1];
						ptr[2] = color[2];
						filled[index >> 3] |= 1 << (index & 7);
					}
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
						foundvalue = qtrue;
					}
				}
			}
			foundvalue = qfalse;
			for (y = height-1; y >= 0; y--)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				index = (ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				if (foundvalue)
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
					}
					else
					{
						ptr = lightFloats + k*3;
						ptr[0] = color[0];
						ptr[1] = color[1];
						ptr[2] = color[2];
						filled[index >> 3] |= 1 << (index & 7);
					}
				}
				else
				{
					if (filled[index >> 3] & (1 << (index & 7)))
					{
						ptr = lightFloats + k*3;
						color[0] = ptr[0];
						color[1] = ptr[1];
						color[2] = ptr[2];
						foundvalue = qtrue;
					}
				}
			}
		}
		if (ds->surfaceType == MST_PATCH)
		{
			x = ds->lightmapWidth-1;
			for (y = 0; y < ds->lightmapHeight; y++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = (lightFloats + (k-1)*3)[0];
				ptr[1] = (lightFloats + (k-1)*3)[1];
				ptr[2] = (lightFloats + (k-1)*3)[2];
			}
			y = ds->lightmapHeight-1;
			for (x = 0; x < ds->lightmapWidth; x++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[0];
				ptr[1] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[1];
				ptr[2] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[2];
			}
		}
		/*
		//colored debug edges
		if (ds->surfaceType == MST_PATCH)
		{
			x = ds->lightmapWidth-1;
			for (y = 0; y < ds->lightmapHeight; y++)
			{
				k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y)
						* LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = 255;
				ptr[1] = 0;
				ptr[2] = 0;
			}
			y = ds->lightmapHeight-1;
			for (x = 0; x < ds->lightmapWidth; x++)
			{
				k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y)
						* LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = 0;
				ptr[1] = 255;
				ptr[2] = 0;
			}
		}
		//*/
	}
	//
	VS_SmoothenLightmapEdges();
}

/*
=============
VS_ShiftPatchLightmaps
=============
*/
void VS_ShiftPatchLightmaps(void)
{
	int				i, j, x, y, k;
	drawVert_t		*verts;
	dsurface_t		*ds;
	lsurfaceTest_t	*test;
	float			*ptr;

	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		ds = &drawSurfaces[ i ];

		if ( ds->lightmapNum < 0 )
			continue;
		if (ds->surfaceType != MST_PATCH)
			continue;
		for (x = ds->lightmapWidth; x > 0; x--)
		{
			for (y = 0; y <= ds->lightmapHeight; y++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = (lightFloats + (k-1)*3)[0];
				ptr[1] = (lightFloats + (k-1)*3)[1];
				ptr[2] = (lightFloats + (k-1)*3)[2];
			}
		}
		for (y = ds->lightmapHeight; y > 0; y--)
		{
			for (x = 0; x <= ds->lightmapWidth; x++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				ptr = lightFloats + k*3;
				ptr[0] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[0];
				ptr[1] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[1];
				ptr[2] = (lightFloats + (k-LIGHTMAP_WIDTH)*3)[2];
			}
		}
		verts = &drawVerts[ ds->firstVert ];
		for ( j = 0 ; j < ds->patchHeight * ds->patchWidth; j++ )
		{
			verts[j].lightmap[0] += 0.5 / LIGHTMAP_WIDTH;
			verts[j].lightmap[1] += 0.5 / LIGHTMAP_HEIGHT;
		}
		ds->lightmapHeight++;
		ds->lightmapWidth++;
	}
}

/*
=============
VS_StoreLightmap
=============
*/
void VS_StoreLightmap(void)
{
	int				i, x, y, k;
	dsurface_t		*ds;
	lsurfaceTest_t	*test;
	float			*src;
	byte			*dst;

	_printf("storing lightmaps...\n");
	//fix lightmap edges before storing them
	VS_FixLightmapEdges();
	//
#ifdef LIGHTMAP_PATCHSHIFT
	VS_ShiftPatchLightmaps();
#endif
	//
	for ( i = 0 ; i < numDrawSurfaces ; i++ )
	{
		test = lsurfaceTest[ i ];
		if (!test)
			continue;
		ds = &drawSurfaces[ i ];

		if ( ds->lightmapNum < 0 )
			continue;

		for (y = 0; y < ds->lightmapHeight; y++)
		{
			for (x = 0; x < ds->lightmapWidth; x++)
			{
				k = (ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + y) * LIGHTMAP_WIDTH + ds->lightmapX + x;
				VectorAdd((lightFloats + k*3), lightAmbientColor, (lightFloats + k*3));
				src = &lightFloats[k*3];
				dst = lightBytes + k*3;
				ColorToBytes(src, dst);
			}
		}
	}
}

/*
=============
PointInLeafnum
=============
*/
/*
int	PointInLeafnum(vec3_t point)
{
	int		nodenum;
	vec_t	dist;
	dnode_t	*node;
	dplane_t	*plane;

	nodenum = 0;
	while (nodenum >= 0)
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planeNum];
		dist = DotProduct (point, plane->normal) - plane->dist;
		if (dist > 0)
			nodenum = node->children[0];
		else
			nodenum = node->children[1];
	}

	return -nodenum - 1;
}
*/

/*
=============
VS_PointInLeafnum_r
=============
*/
int	VS_PointInLeafnum_r(vec3_t point, int nodenum)
{
	int leafnum;
	vec_t	dist;
	dnode_t	*node;
	dplane_t	*plane;

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

	leafnum = -nodenum - 1;
	return leafnum;
}

/*
=============
VS_PointInLeafnum
=============
*/
int	VS_PointInLeafnum(vec3_t point)
{
	return VS_PointInLeafnum_r(point, 0);
}

/*
=============
VS_LightLeafnum
=============
*/
int VS_LightLeafnum(vec3_t point)
{
	/*
	int leafnum;
	dleaf_t *leaf;
	float x, y, z;
	vec3_t test;

	leafnum = VS_PointInLeafnum(point);
	leaf = &dleafs[leafnum];
	if (leaf->cluster != -1)
		return leafnum;
	for (z = 1; z >= -1; z -= 1)
	{
		for (x = 1; x >= -1; x -= 1)
		{
			for (y = 1; y >= -1; y -= 1)
			{
				VectorCopy(point, test);
				test[0] += x;
				test[1] += y;
				test[2] += z;
				leafnum = VS_PointInLeafnum(test);
				leaf = &dleafs[leafnum];
				if (leaf->cluster != -1)
				{
					VectorCopy(test, point);
					return leafnum;
				}
			}
		}
	}
	return leafnum;
	*/
	return VS_PointInLeafnum(point);
}

//#define LIGHTPOLYS

#ifdef LIGHTPOLYS

winding_t *lightwindings[MAX_MAP_DRAW_SURFS];
int numlightwindings;

/*
=============
VS_DrawLightWindings
=============
*/
void VS_DrawLightWindings(void)
{
	int i;
	for (i = 0; i < numlightwindings; i++)
	{
#ifdef DEBUGNET
		DebugNet_DrawWinding(lightwindings[i], 1);
#endif
	}
}

/*
=============
VS_LightSurfaceWithVolume
=============
*/
void VS_LightSurfaceWithVolume(int surfaceNum, int facetNum, vsound_t *light, lightvolume_t *volume)
{
	winding_t *w;
	lsurfaceTest_t *test;
	lFacet_t *facet;
	int i;

	test = lsurfaceTest[ surfaceNum ];
	facet = &test->facets[ facetNum ];

	//
	w = (winding_t *) malloc(sizeof(winding_t));
	memcpy(w->points, facet->points, sizeof(vec3_t) * facet->numpoints);
	w->numpoints = facet->numpoints;

	for (i = 0; i < volume->numplanes; i++)
	{
		//if totally on the back
		if (VS_ChopWinding(w, &volume->planes[i], 0.01) == SIDE_BACK)
			return;
	}
	lightwindings[numlightwindings] = w;
	numlightwindings++;
	if (numlightwindings >= MAX_MAP_DRAW_SURFS)
		Error("MAX_LIGHTWINDINGS");
}

#else

/*
=============
VS_LightSurfaceWithVolume
=============
*/
/*
int VS_PointInsideLightVolume(vec3_t point, lightvolume_t *volume)
{
	int i;
	float d;

	for (i = 0; i < volume->numplanes; i++)
	{
		d = DotProduct(volume->planes[i].normal, point) - volume->planes[i].dist;
		if (d < 0) return qfalse;
	}
	return qtrue;
}

void VS_LightSurfaceWithVolume( int surfaceNum, int facetNum, vsound_t *light, lightvolume_t *volume )
{
	dsurface_t	*ds;
	int			i, j, k;
	int			numPositions;
	vec3_t		base, normal, color;
	int			sampleWidth, sampleHeight;
	vec3_t		lightmapOrigin, lightmapVecs[2], dir;
	unsigned char *ptr;
	float add, dist, angle;
	mesh_t * mesh;

	ds = &drawSurfaces[surfaceNum];

	// vertex-lit triangle model
	if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
		return;
	}
	
	if ( ds->lightmapNum < 0 ) {
		return;		// doesn't need lighting
	}

	if ( ds->surfaceType == MST_PATCH ) {
		mesh = lsurfaceTest[surfaceNum]->detailMesh;
	} else {
		VectorCopy( ds->lightmapVecs[2], normal );

		VectorCopy( ds->lightmapOrigin, lightmapOrigin );
		VectorCopy( ds->lightmapVecs[0], lightmapVecs[0] );
		VectorCopy( ds->lightmapVecs[1], lightmapVecs[1] );
	}

	sampleWidth = ds->lightmapWidth;
	sampleHeight = ds->lightmapHeight;

	//calculate lightmap
	for ( i = 0 ; i < sampleWidth; i++ ) {
		for ( j = 0 ; j < sampleHeight; j++ ) {

			if ( ds->patchWidth ) {
				numPositions = 9;
				VectorCopy( mesh->verts[j*mesh->width+i].normal, normal );
				// VectorNormalize( normal, normal );
				// push off of the curve a bit
				VectorMA( mesh->verts[j*mesh->width+i].xyz, 1, normal, base );

//				MakeNormalVectors( normal, lightmapVecs[0], lightmapVecs[1] );
			} else {
				numPositions = 9;
				for ( k = 0 ; k < 3 ; k++ ) {
					base[k] = lightmapOrigin[k] + normal[k]
								+ ((float) i) * lightmapVecs[0][k]
								+ ((float) j) * lightmapVecs[1][k];
				}
			}
			VectorAdd( base, surfaceOrigin[ surfaceNum ], base );

			VectorSubtract(base, light->origin, dir);
			dist = VectorNormalize(dir, dir);
			if ( dist < 16 ) {
				dist = 16;
			}
			angle = 1;//DotProduct( normal, dir ); //1;
			if (angle > 1)
				angle = 1;
			if ( light->atten_disttype == LDAT_LINEAR ) {
				add = angle * light->photons * lightLinearScale - dist;
				if ( add < 0 ) {
					add = 0;
				}
			} else {
				add = light->photons / ( dist * dist ) * angle;
			}
			if (add <= 1.0)
				continue;

			if (VS_PointInsideLightVolume(base, volume))
			{
				k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + j) 
					* LIGHTMAP_WIDTH + ds->lightmapX + i;
				ptr = lightBytes + k*3;
				color[0] = (float) ptr[0] + add * light->color[0];
				color[1] = (float) ptr[1] + add * light->color[1];
				color[2] = (float) ptr[2] + add * light->color[2];
				ColorToBytes(color, ptr);
			}
		}
	}
}
*/

/*
=============
VS_GetFilter

FIXME:  don't use a lightmap pixel origin but use the four corner points
		to map part of a translucent surface onto the lightmap pixel
=============
*/
void VS_GetFilter(vsound_t *light, lightvolume_t *volume, vec3_t lmp, vec3_t filter)
{
	lFacet_t *facet;
	lsurfaceTest_t *test;
	float d, d1, d2, frac, s, t, ns;
	int i, j, is, it, b;
	int x, y, u, v, numsamples, radius, color[4], largest;
	byte *image;
	vec3_t point, origin, total;

	VectorSet(filter, 1, 1, 1);

	if (noalphashading)
		return;

	if (volume->numtransFacets <= 0)
		return;

	if (light->type == LIGHT_SURFACEDIRECTED)
	{
		// project the light map pixel origin onto the area light source plane
		d = DotProduct(lmp, light->normal) - DotProduct(light->normal, light->w.points[0]);
		VectorMA(lmp, -d, light->normal, origin);
	}
	else
	{
		VectorCopy(light->origin, origin);
	}
	for (i = 0; i < volume->numtransFacets; i++)
	{
		test = lsurfaceTest[ volume->transSurfaces[i] ];
		facet = &test->facets[ volume->transFacets[i] ];
		// if this surface does not cast an alpha shadow
		if ( !(test->shader->surfaceFlags & SURF_ALPHASHADOW) )
			continue;
		// if there are no texture pixel available
		if(!test->shader->pixels)
		{
			continue;
		}
		//
		d1 = DotProduct( origin, facet->plane.normal) - facet->plane.dist;
		d2 = DotProduct( lmp, facet->plane.normal ) - facet->plane.dist;
		// this should never happen because the light volume went through the facet
		if((d1 < 0) == (d2 < 0))
		{
			continue;
		}
		// calculate the crossing point
		frac = d1 / ( d1 - d2 );

		for(j = 0; j < 3; j++)
		{
			point[j] = origin[j] + frac * ( lmp[j] - origin[j] );
		}

		s = DotProduct( point, facet->textureMatrix[0] ) + facet->textureMatrix[0][3];
		t = DotProduct( point, facet->textureMatrix[1] ) + facet->textureMatrix[1][3];
		if (s < 0)
			s = 0;
		if (t < 0)
			t = 0;

		s = s - floor( s );
		t = t - floor( t );

		is = s * test->shader->width;
		it = t * test->shader->height;

		//if old style alpha shading
		if(nocolorshading)
		{
			image = test->shader->pixels + 4 * ( it * test->shader->width + is );

			// alpha filter
			b = image[3];

			// alpha test makes this a binary option
			b = b < 128 ? 0 : 255;

			filter[0] = filter[0] * (255-b) / 255;
			filter[1] = filter[1] * (255-b) / 255;
			filter[2] = filter[2] * (255-b) / 255;
		}
		else
		{
			VectorClear(total);
			numsamples = 0;
			radius = 2;
			for ( u = -radius; u <= radius; u++ )
			{
				x = is + u;
				if ( x < 0 || x >= test->shader->width)
					continue;
				for ( v = -radius; v <= radius; v++ )
				{
					y = it + v;
					if ( y < 0 || y >= test->shader->height)
						continue;

					image = test->shader->pixels + 4 * ( y * test->shader->width + x );
					color[0] = image[0];
					color[1] = image[1];
					color[2] = image[2];
					largest = 0;
					for (j = 0; j < 3; j++)
						if (image[j] > largest)
							largest = image[j];
					if(largest <= 0 || image[3] == 0)
					{
						color[0] = 255;
						color[1] = 255;
						color[2] = 255;
						largest = 255;
					}
					total[0] += ((float) color[0]/largest) * (255-image[3]) / 255.0;
					total[1] += ((float) color[1]/largest) * (255-image[3]) / 255.0;
					total[2] += ((float) color[2]/largest) * (255-image[3]) / 255.0;
					numsamples++;
				}
			}
			ns = numsamples;
			//
			filter[0] *= total[0] / ns;
			filter[1] *= total[1] / ns;
			filter[2] *= total[2] / ns;
		}
	}
}

/*
=============
VS_LightSurfaceWithVolume
=============
*/
void VS_LightSurfaceWithVolume( int surfaceNum, int facetNum, vsound_t *light, lightvolume_t *volume )
{
	int i;
	dsurface_t	*ds;
	lFacet_t *facet;
	lsurfaceTest_t *test;
	winding_t w;
	vec3_t base, dir, delta, normal, filter, origin;
	int min_x[LIGHTMAP_SIZE+2], max_x[LIGHTMAP_SIZE+2];
	int min_y, max_y, k, x, y, n;
	float *color, distscale;
	float d, add, angle, dist, area, insidearea, coords[MAX_POINTS_ON_WINDING+1][2];
	mesh_t *mesh;
	byte polygonedges[(LIGHTMAP_SIZE+1) * (LIGHTMAP_SIZE+1) / 8];


	ds = &drawSurfaces[surfaceNum];

	// vertex-lit triangle model
	if(ds->surfaceType == MST_TRIANGLE_SOUP)
	{
		return;
	}
	
	if(ds->lightmapNum < 0)
	{
		return;		// doesn't need lighting
	}

	test = lsurfaceTest[ surfaceNum ];
	facet = &test->facets[ facetNum ];

	if (defaulttracelight && !test->always_vsound)
		return;
	if (test->always_tracelight)
		return;

	memcpy(w.points, facet->points, sizeof(vec3_t) * facet->numpoints);
	w.numpoints = facet->numpoints;

	for (i = 0; i < volume->numplanes; i++)
	{
		//if totally on the back
		if (VS_ChopWinding(&w, &volume->planes[i], 0.01) == SIDE_BACK)
			return;
	}

	// only one thread at a time may write to the lightmap of this surface
	MutexLock(test->mutex);

	test->numvolumes++;

	if (ds->surfaceType == MST_PATCH)
	{
		// FIXME: reduce size and don't mark all as edge
		min_y = ds->lightmapY + facet->y;
		max_y = ds->lightmapY + facet->y + facet->height - 1;
		for (y = min_y; y <= max_y; y++)
		{
			min_x[y] = ds->lightmapX + facet->x;
			max_x[y] = ds->lightmapX + facet->x + facet->width - 1;
			for (x = min_x[y]; x <= max_x[y]; x++)
			{
				n = y * LIGHTMAP_SIZE + x;
				polygonedges[n >> 3] |= 1 << (n & 7);
			}
		}
	}
	else
	{
		for (i = 0; i < w.numpoints; i++)
		{
			float	s, t;

			if (i >= MAX_POINTS_ON_WINDING)
				_printf("coords overflow\n");
			if (ds->surfaceType != MST_PATCH)
			{
				VectorSubtract(w.points[i], facet->mins, delta);
				s = DotProduct( delta, facet->lightmapMatrix[0] ) + ds->lightmapX + 0.5;
				t = DotProduct( delta, facet->lightmapMatrix[1] ) + ds->lightmapY + 0.5;
				if (s >= LIGHTMAP_SIZE)
					s = LIGHTMAP_SIZE - 0.5;
				if (s < 0)
					s = 0;
				if (t >= LIGHTMAP_SIZE)
					t = LIGHTMAP_SIZE - 0.5;
				if (t < 0)
					t = 0;
				coords[i][0] = s;
				coords[i][1] = t;
			}
			else
			{
				s = DotProduct( w.points[i], facet->lightmapMatrix[0] ) + facet->lightmapMatrix[0][3];
				t = DotProduct( w.points[i], facet->lightmapMatrix[1] ) + facet->lightmapMatrix[1][3];

				s = s - floor( s );
				t = t - floor( t );

				coords[i][0] = ds->lightmapX + s * LIGHTMAP_SIZE;// + 0.5;
				coords[i][1] = ds->lightmapY + t * LIGHTMAP_SIZE;// + 0.5;

				if (coords[i][0] >= LIGHTMAP_SIZE)
					coords[i][0] -= LIGHTMAP_SIZE;
				if (coords[i][1] >= LIGHTMAP_SIZE)
					coords[i][1] -= LIGHTMAP_SIZE;
				if (coords[i][0] < ds->lightmapX)
					coords[i][0] = ds->lightmapX;
				if (coords[i][1] < ds->lightmapY)
					coords[i][1] = ds->lightmapY;
			}
			x = coords[i][0];
			y = coords[i][1];
			if (x < ds->lightmapX || x >= LIGHTMAP_SIZE)
				_printf("VS_LightSurfaceWithVolume: x outside lightmap\n");
			if (y < ds->lightmapY || y >= LIGHTMAP_SIZE)
				_printf("VS_LightSurfaceWithVolume: y outside lightmap\n");
		}
		coords[i][0] = coords[0][0];
		coords[i][1] = coords[0][1];

		//
		min_y = LIGHTMAP_SIZE;
		max_y = 0;
		for (i = 0; i < LIGHTMAP_SIZE; i++)
		{
			min_x[i] = LIGHTMAP_SIZE;
			max_x[i] = 0;
		}
		memset(polygonedges, 0, sizeof(polygonedges));
		// scan convert the polygon onto the lightmap
		// for each edge it marks *every* lightmap pixel the edge goes through
		// so no brasenham and no scan conversion used for texture mapping but
		// more something like ray casting
		// this is necesary because we need all lightmap pixels totally or partly
		// inside the light volume. these lightmap pixels are only lit for the part
		// that they are inside the light volume.
		for (i = 0; i < w.numpoints; i++)
		{
			float xf, yf, dx, dy, xstep, ystep, xfrac, yfrac;
			int xinc, yinc;

			xf = coords[i][0];
			yf = coords[i][1];
			dx = coords[i+1][0] - xf;
			dy = coords[i+1][1] - yf;
			//
			x = (int) xf;
			y = (int) yf;
			//
			if (y < min_y)
				min_y = y;
			if (y > max_y)
				max_y = y;
			//
			if (fabs(dx) > fabs(dy))
			{
				if (dx > 0)
				{
					// y fraction at integer x below fractional x
					yfrac = yf + (floor(xf) - xf) * dy / dx;
					xinc = 1;
				}
				else if (dx < 0)
				{
					// y fraction at integer x above fractional x
					yfrac = yf + (floor(xf) + 1 - xf) * dy / dx;
					xinc = -1;
				}
				else
				{
					yfrac = yf;
					xinc = 0;
				}
				// step in y direction per 1 unit in x direction
				if (dx)
					ystep = dy / fabs(dx);
				else
					ystep = 0;
				while(1)
				{
					if (x < ds->lightmapX || x >= LIGHTMAP_SIZE)
						_printf("VS_LightSurfaceWithVolume: x outside lightmap\n");
					if (y < ds->lightmapY || y >= LIGHTMAP_SIZE)
						_printf("VS_LightSurfaceWithVolume: y outside lightmap\n");
					//
					n = y * LIGHTMAP_SIZE + x;
					polygonedges[n >> 3] |= 1 << (n & 7);
					if (x < min_x[y])
						min_x[y] = x;
					if (x > max_x[y])
						max_x[y] = x;
					if (x == (int) coords[i+1][0])
						break;
					yfrac += ystep;
					if (dy > 0)
					{
						if (yfrac > (float) y + 1)
						{
							y += 1;
							//
							n = y * LIGHTMAP_SIZE + x;
							polygonedges[n >> 3] |= 1 << (n & 7);
							if (x < min_x[y])
								min_x[y] = x;
							if (x > max_x[y])
								max_x[y] = x;
						}
					}
					else
					{
						if (yfrac < (float) y)
						{
							y -= 1;
							//
							n = y * LIGHTMAP_SIZE + x;
							polygonedges[n >> 3] |= 1 << (n & 7);
							if (x < min_x[y])
								min_x[y] = x;
							if (x > max_x[y])
								max_x[y] = x;
						}
					}
					x += xinc;
				}
			}
			else
			{
				if (dy > 0)
				{
					//x fraction at integer y below fractional y
					xfrac = xf + (floor(yf) - yf) * dx / dy;
					yinc = 1;
				}
				else if (dy < 0)
				{
					//x fraction at integer y above fractional y
					xfrac = xf + (floor(yf) + 1 - yf) * dx / dy;
					yinc = -1;
				}
				else
				{
					xfrac = xf;
					yinc = 0;
				}
				// step in x direction per 1 unit in y direction
				if (dy)
					xstep = dx / fabs(dy);
				else
					xstep = 0;
				while(1)
				{
					if (x < ds->lightmapX || x >= LIGHTMAP_SIZE)
						_printf("VS_LightSurfaceWithVolume: x outside lightmap\n");
					if (y < ds->lightmapY || y >= LIGHTMAP_SIZE)
						_printf("VS_LightSurfaceWithVolume: y outside lightmap\n");
					//
					n = y * LIGHTMAP_SIZE + x;
					polygonedges[n >> 3] |= 1 << (n & 7);
					if (x < min_x[y])
						min_x[y] = x;
					if (x > max_x[y])
						max_x[y] = x;
					if (y == (int) coords[i+1][1])
						break;
					xfrac += xstep;
					if (dx > 0)
					{
						if (xfrac > (float) x + 1)
						{
							x += 1;
							//
							n = y * LIGHTMAP_SIZE + x;
							polygonedges[n >> 3] |= 1 << (n & 7);
							if (x < min_x[y])
								min_x[y] = x;
							if (x > max_x[y])
								max_x[y] = x;
						}
					}
					else
					{
						if (xfrac < (float) x)
						{
							x -= 1;
							//
							n = y * LIGHTMAP_SIZE + x;
							polygonedges[n >> 3] |= 1 << (n & 7);
							if (x < min_x[y])
								min_x[y] = x;
							if (x > max_x[y])
								max_x[y] = x;
						}
					}
					y += yinc;
				}
			}
		}
	}
	// map light onto the lightmap
	for (y = min_y; y <= max_y; y++)
	{
		for (x = min_x[y]; x <= max_x[y]; x++)
		{
			if (ds->surfaceType == MST_PATCH)
			{
				mesh = test->detailMesh;
				VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x-ds->lightmapX].xyz, base);
				VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x-ds->lightmapX].normal, normal);
				//VectorCopy(facet->plane.normal, normal);
			}
			else
			{
				VectorMA(ds->lightmapOrigin, (float) x - ds->lightmapX, ds->lightmapVecs[0], base);
				VectorMA(base, (float) y - ds->lightmapY, ds->lightmapVecs[1], base);
				VectorCopy(facet->plane.normal, normal);
			}
			if (light->type == LIGHT_POINTSPOT)
			{
				float	distByNormal;
				vec3_t	pointAtDist;
				float	radiusAtDist;
				float	sampleRadius;
				vec3_t	distToSample;
				float	coneScale;

				VectorSubtract( light->origin, base, dir );

				distByNormal = -DotProduct( dir, light->normal );
				if(distByNormal < 0)
				{
					continue;
				}
				VectorMA( light->origin, distByNormal, light->normal, pointAtDist );
				radiusAtDist = light->radiusByDist * distByNormal;

				VectorSubtract( base, pointAtDist, distToSample );
				sampleRadius = VectorLength( distToSample );

				if(sampleRadius >= radiusAtDist)
				{
					continue;		// outside the cone
				}
				if(sampleRadius <= radiusAtDist - 32)
				{
					coneScale = 1.0;	// fully inside
				}
				else
				{
					coneScale = ( radiusAtDist - sampleRadius ) / 32.0;
				}
				
				dist = VectorNormalize( dir, dir );
				// clamp the distance to prevent super hot spots
				if(dist < 16)
				{
					dist = 16;
				}
				angle = DotProduct( normal, dir );
				if (angle > 1)
					angle = 1;
				if(angle > 0)
				{
					if(light->atten_angletype == LAAT_QUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle;
						angle = 1 - angle;
					}
					else if(light->atten_angletype == LAAT_DOUBLEQUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle * angle;
						angle = 1 - angle;
					}
				}
				if(light->atten_anglescale > 0)
				{
					angle /= light->atten_anglescale;
					if (angle > 1)
						angle = 1;
				}
				if(light->atten_distscale > 0)
				{
					distscale = light->atten_distscale;
				}
				else
				{
					distscale = 1;
				}
				//
				if(light->atten_disttype == LDAT_NOSCALE)
				{
					add = angle * coneScale;
				}
				else if(light->atten_disttype == LDAT_LINEAR)
				{
					add = angle * light->photons * lightLinearScale * coneScale - dist * distscale;
					if(add < 0)
					{
						add = 0;
					}
				}
				else
				{
					add = light->photons / ( dist * dist * distscale) * angle * coneScale;
				}
				if (add <= 1.0)
					continue;
			}
			else if (light->type == LIGHT_POINTFAKESURFACE)
			{
				// calculate the contribution
				add = PointToPolygonFormFactor( base, normal, &light->w );
				if(add <= 0)
				{
					if(light->twosided)
					{
						add = -add;
					}
					else
					{
						continue;
					}
				}
			}
			else if (light->type == LIGHT_SURFACEDIRECTED)
			{
				//VectorCopy(light->normal, dir);
				//VectorInverse(dir);
				// project the light map pixel origin onto the area light source plane
				d = DotProduct(base, light->normal) - DotProduct(light->normal, light->w.points[0]);
				VectorMA(base, -d, light->normal, origin);
				VectorSubtract(origin, base, dir);
				dist = VectorNormalize(dir, dir);
				if(dist < 16)
				{
					dist = 16;
				}
				//
				angle = DotProduct( normal, dir );
				if (angle > 1)
					angle = 1;
				if(angle > 0)
				{
					if(light->atten_angletype == LAAT_QUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle;
						angle = 1 - angle;
					}
					else if(light->atten_angletype == LAAT_DOUBLEQUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle * angle;
						angle = 1 - angle;
					}
				}
				if(light->atten_anglescale > 0)
				{
					angle /= light->atten_anglescale;
					if (angle > 1)
						angle = 1;
				}
				if(light->atten_distscale > 0)
				{
					distscale = light->atten_distscale;
				}
				else
				{
					distscale = 1;
				}
				if(light->atten_disttype == LDAT_NOSCALE)
				{
					add = angle;
				}
				else if(light->atten_disttype == LDAT_LINEAR)
				{
					add = angle * light->photons * lightLinearScale - dist * distscale;
					if(add < 0)
					{
						add = 0;
					}
				}
				else
				{				//default quadratic
					add = light->photons / ( dist * dist * distscale) * angle;
				}
				if (add <= 0)
					continue;
			}
			else //normal radial point light
			{
				VectorSubtract(light->origin, base, dir);
				dist = VectorNormalize(dir, dir);
				if(dist < 16)
				{
					dist = 16;
				}
				angle = DotProduct( normal, dir );
				if (angle > 1)
					angle = 1;
				if(angle > 0)
				{
					if(light->atten_angletype == LAAT_QUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle;
						angle = 1 - angle;
					}
					else if(light->atten_angletype == LAAT_DOUBLEQUADRATIC)
					{
						angle = 1 - angle;
						angle *= angle * angle;
						angle = 1 - angle;
					}
				}
				if(light->atten_anglescale > 0)
				{
					angle /= light->atten_anglescale;
					if (angle > 1)
						angle = 1;
				}
				if(light->atten_distscale > 0)
				{
					distscale = light->atten_distscale;
				}
				else
				{
					distscale = 1;
				}
				if(light->atten_disttype == LDAT_NOSCALE)
				{
					add = angle;
				}
				else if(light->atten_disttype == LDAT_LINEAR)
				{
					add = angle * light->photons * lightLinearScale - dist * distscale;
					if(add < 0)
					{
						add = 0;
					}
				}
				else
				{
					add = light->photons / ( dist * dist * distscale) * angle;
				}
				if (add <= 1.0)
					continue;
			}
			//
			k = (ds->lightmapNum * LIGHTMAP_HEIGHT + y) * LIGHTMAP_WIDTH + x;
			//if on one of the edges
			n = y * LIGHTMAP_SIZE + x;
			if ((polygonedges[n >> 3] & (1 << (n & 7)) ))
			{
				// multiply 'add' by the relative area being lit of the total visible lightmap pixel area
				//
				// first create a winding for the lightmap pixel
				if (ds->surfaceType == MST_PATCH)
				{
					mesh = test->detailMesh;
					if (y-ds->lightmapY >= mesh->height-1)
						_printf("y outside mesh\n");
					if (x-ds->lightmapX >= mesh->width-1)
						_printf("x outside mesh\n");
					VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x-ds->lightmapX].xyz, w.points[0]);
					VectorCopy( mesh->verts[(y+1-ds->lightmapY)*mesh->width+x-ds->lightmapX].xyz, w.points[1]);
					VectorCopy( mesh->verts[(y+1-ds->lightmapY)*mesh->width+x+1-ds->lightmapX].xyz, w.points[2]);
					VectorCopy( mesh->verts[(y-ds->lightmapY)*mesh->width+x+1-ds->lightmapX].xyz, w.points[3]);
					w.numpoints = 4;
				}
				else
				{
					VectorMA(ds->lightmapOrigin, (float)x - LIGHTMAP_PIXELSHIFT - ds->lightmapX, ds->lightmapVecs[0],
							 w.points[0]);
					VectorMA(w.points[0], (float) y - LIGHTMAP_PIXELSHIFT - ds->lightmapY, ds->lightmapVecs[1], w.points[0]);
					VectorMA(ds->lightmapOrigin, (float)x - LIGHTMAP_PIXELSHIFT - ds->lightmapX, ds->lightmapVecs[0],
							 w.points[1]);
					VectorMA(w.points[1], (float) y - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapY, ds->lightmapVecs[1], w.points[1]);
					VectorMA(ds->lightmapOrigin, (float)x - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapX, ds->lightmapVecs[0],
							 w.points[2]);
					VectorMA(w.points[2], (float) y - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapY, ds->lightmapVecs[1], w.points[2]);
					VectorMA(ds->lightmapOrigin, (float)x - LIGHTMAP_PIXELSHIFT + 1 - ds->lightmapX, ds->lightmapVecs[0],
							 w.points[3]);
					VectorMA(w.points[3], (float) y - LIGHTMAP_PIXELSHIFT - ds->lightmapY, ds->lightmapVecs[1], w.points[3]);
					w.numpoints = 4;
				}
				//
				// take the visible area of the lightmap pixel into account
				//
				//area = WindingArea(&w);
				area = lightmappixelarea[k];
				if (area <= 0)
					continue;
				// chop the lightmap pixel winding with the light volume
				for (i = 0; i < volume->numplanes; i++)
				{
					//if totally on the back
					if (VS_ChopWinding(&w, &volume->planes[i], 0) == SIDE_BACK)
						break;
				}
				// if the lightmap pixel is partly inside the light volume
				if (i >= volume->numplanes)
				{
					insidearea = WindingArea(&w);
					if (insidearea <= 0)
						i = 0;
					add = add * insidearea / area;
				}
				else
				{
					//DebugNet_DrawWinding(&w, 2);
					continue;	// this shouldn't happen
				}
			}
			// get the light filter from all the translucent surfaces the light volume went through
			VS_GetFilter(light, volume, base, filter);
			//
			color = &lightFloats[k*3];
			color[0] += add * light->color[0] * filter[0];
			color[1] += add * light->color[1] * filter[1];
			color[2] += add * light->color[2] * filter[2];
		}
	}

	MutexUnlock(test->mutex);
}

#endif

/*
=============
VS_SplitLightVolume
=============
*/
int VS_SplitLightVolume(lightvolume_t *volume, lightvolume_t *back, plane_t *split, float epsilon)
{
	lightvolume_t f, b;
	vec_t	dists[128];
	int		sides[128];
	int		counts[3];
	vec_t	dot;
	int		i, j;
	vec_t	*p1, *p2;
	vec3_t	mid;

	counts[0] = counts[1] = counts[2] = 0;

	// determine sides for each point
	for (i = 0; i < volume->numplanes; i++)
	{
		dot = DotProduct (volume->points[i], split->normal);
		dot -= split->dist;
		dists[i] = dot;
		if (dot > epsilon)
			sides[i] = SIDE_FRONT;
		else if (dot < -epsilon)
			sides[i] = SIDE_BACK;
		else
		{
			sides[i] = SIDE_ON;
		}
		counts[sides[i]]++;
	}

	if (!counts[1])
		return 0;		// completely on front side
	
	if (!counts[0])
		return 1;		// completely on back side

	sides[i] = sides[0];
	dists[i] = dists[0];
	
	f.numplanes = 0;
	b.numplanes = 0;

	for (i = 0; i < volume->numplanes; i++)
	{
		p1 = volume->points[i];

		if (f.numplanes >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitLightVolume -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return 0;		// can't chop -- fall back to original
		}
		if (b.numplanes >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitLightVolume -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return 0;		// can't chop -- fall back to original
		}

		if (sides[i] == SIDE_ON)
		{
			VectorCopy(p1, f.points[f.numplanes]);
			VectorCopy(p1, b.points[b.numplanes]);
			if (sides[i+1] == SIDE_BACK)
			{
				f.planes[f.numplanes] = *split;
				b.planes[b.numplanes] = volume->planes[i];
			}
			else if (sides[i+1] == SIDE_FRONT)
			{
				f.planes[f.numplanes] = volume->planes[i];
				b.planes[b.numplanes] = *split;
				VectorInverse(b.planes[b.numplanes].normal);
				b.planes[b.numplanes].dist = -b.planes[b.numplanes].dist;
			}
			else //this shouldn't happen
			{
				f.planes[f.numplanes] = *split;
				b.planes[b.numplanes] = *split;
				VectorInverse(b.planes[b.numplanes].normal);
				b.planes[b.numplanes].dist = -b.planes[b.numplanes].dist;
			}
			f.numplanes++;
			b.numplanes++;
			continue;
		}
	
		if (sides[i] == SIDE_FRONT)
		{
			VectorCopy (p1, f.points[f.numplanes]);
			f.planes[f.numplanes] = volume->planes[i];
			f.numplanes++;
		}
		if (sides[i] == SIDE_BACK)
		{
			VectorCopy (p1, b.points[b.numplanes]);
			b.planes[b.numplanes] = volume->planes[i];
			b.numplanes++;
		}
		
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;
			
		if (f.numplanes >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitLightVolume -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return 0;		// can't chop -- fall back to original
		}
		if (b.numplanes >= MAX_POINTS_ON_FIXED_WINDING)
		{
			_printf("WARNING: VS_SplitLightVolume -> MAX_POINTS_ON_FIXED_WINDING overflowed\n");
			return 0;		// can't chop -- fall back to original
		}

		// generate a split point
		p2 = volume->points[(i+1)%volume->numplanes];
		
		dot = dists[i] / (dists[i]-dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{	// avoid round off error when possible
			if (split->normal[j] == 1)
				mid[j] = split->dist;
			else if (split->normal[j] == -1)
				mid[j] = -split->dist;
			else
				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
		}

		VectorCopy (mid, f.points[f.numplanes]);
		VectorCopy(mid, b.points[b.numplanes]);
		if (sides[i+1] == SIDE_BACK)
		{
			f.planes[f.numplanes] = *split;
			b.planes[b.numplanes] = volume->planes[i];
		}
		else
		{
			f.planes[f.numplanes] = volume->planes[i];
			b.planes[b.numplanes] = *split;
			VectorInverse(b.planes[b.numplanes].normal);
			b.planes[b.numplanes].dist = -b.planes[b.numplanes].dist;
		}
		f.numplanes++;
		b.numplanes++;
	}
	memcpy(volume->points, f.points, sizeof(vec3_t) * f.numplanes);
	memcpy(volume->planes, f.planes, sizeof(plane_t) * f.numplanes);
	volume->numplanes = f.numplanes;
	memcpy(back->points, b.points, sizeof(vec3_t) * b.numplanes);
	memcpy(back->planes, b.planes, sizeof(plane_t) * b.numplanes);
	back->numplanes = b.numplanes;

	return 2;
}

/*
=============
VS_PlaneForEdgeToWinding
=============
*/
void VS_PlaneForEdgeToWinding(vec3_t p1, vec3_t p2, winding_t *w, int windingonfront, plane_t *plane)
{
	int i, j;
	float length, d;
	vec3_t v1, v2;

	VectorSubtract(p2, p1, v1);
	for (i = 0; i < w->numpoints; i++)
	{
		VectorSubtract (w->points[i], p1, v2);

		plane->normal[0] = v1[1]*v2[2] - v1[2]*v2[1];
		plane->normal[1] = v1[2]*v2[0] - v1[0]*v2[2];
		plane->normal[2] = v1[0]*v2[1] - v1[1]*v2[0];
			
		// if points don't make a valid plane, skip it
		length = plane->normal[0] * plane->normal[0] + plane->normal[1] * plane->normal[1] + plane->normal[2] * plane->normal[2];
			
		if (length < ON_EPSILON)
			continue;

		length = 1/sqrt(length);
			
		plane->normal[0] *= length;
		plane->normal[1] *= length;
		plane->normal[2] *= length;

		plane->dist = DotProduct (w->points[i], plane->normal);
		//
		for (j = 0; j < w->numpoints; j++)
		{
			if (j == i)
				continue;
			d = DotProduct(w->points[j], plane->normal) - plane->dist;
			if (windingonfront)
			{
				if (d < -ON_EPSILON)
					break;
			}
			else
			{
				if (d > ON_EPSILON)
					break;
			}
		}
		if (j >= w->numpoints)
			return;
	}
}

/*
=============
VS_R_CastLightAtSurface
=============
*/
void VS_R_FloodLight(vsound_t *light, lightvolume_t *volume, int cluster, int firstportal);

void VS_R_CastLightAtSurface(vsound_t *light, lightvolume_t *volume)
{
	lsurfaceTest_t *test;
	int i, n;

	// light the surface with this volume
	VS_LightSurfaceWithVolume(volume->surfaceNum, volume->facetNum, light, volume);
	//
	test = lsurfaceTest[ volume->surfaceNum ];
	// if this is not a translucent surface
	if ( !(test->shader->surfaceFlags & SURF_ALPHASHADOW) && !(test->shader->contents & CONTENTS_TRANSLUCENT))
		return;
	//
	if (volume->numtransFacets >= MAX_TRANSLUCENTFACETS)
		Error("a light valume went through more than %d translucent facets", MAX_TRANSLUCENTFACETS);
	//add this translucent surface to the list
	volume->transSurfaces[volume->numtransFacets] = volume->surfaceNum;
	volume->transFacets[volume->numtransFacets] = volume->facetNum;
	volume->numtransFacets++;
	//clear the tested facets except the translucent ones
	memset(volume->facetTested, 0, sizeof(volume->facetTested));
	for (i = 0; i < volume->numtransFacets; i++)
	{
		test = lsurfaceTest[ volume->transSurfaces[i] ];
		n = test->facets[volume->transFacets[i]].num;
		volume->facetTested[n >> 3] |= 1 << (n & 7);
	}
	memset(volume->clusterTested, 0, sizeof(volume->clusterTested));
	volume->endplane = volume->farplane;
	volume->surfaceNum = -1;
	volume->facetNum = 0;
	VS_R_FloodLight(light, volume, volume->cluster, 0);
	if (volume->surfaceNum >= 0)
	{
		VS_R_CastLightAtSurface(light, volume);
	}
}

/*
=============
VS_R_SplitLightVolume
=============
*/
static int numvolumes = 0;

int VS_R_SplitLightVolume(vsound_t *light, lightvolume_t *volume, plane_t *split, int cluster, int firstportal)
{
	lightvolume_t back;
	int res;

	//
	res = VS_SplitLightVolume(volume, &back, split, 0.1);
	// if the volume was split
	if (res == 2)
	{
		memcpy(back.clusterTested, volume->clusterTested, sizeof(back.clusterTested));
		memcpy(back.facetTested, volume->facetTested, sizeof(back.facetTested));
		back.num = numvolumes++;
		back.endplane = volume->endplane;
		back.surfaceNum = volume->surfaceNum;
		back.facetNum = volume->facetNum;
		back.type = volume->type;
		back.cluster = volume->cluster;
		back.farplane = volume->farplane;
		if (volume->numtransFacets > 0)
		{
			memcpy(back.transFacets, volume->transFacets, sizeof(back.transFacets));
			memcpy(back.transSurfaces, volume->transSurfaces, sizeof(back.transSurfaces));
		}
		back.numtransFacets = volume->numtransFacets;
		//
		// flood the volume at the back of the split plane
		VS_R_FloodLight(light, &back, cluster, firstportal);
		// if the back volume hit a surface
		if (back.surfaceNum >= 0)
		{
			VS_R_CastLightAtSurface(light, &back);
		}
	}
	return res;
}

/*
=============
VS_R_FloodLight
=============
*/
void VS_R_FloodLight(vsound_t *light, lightvolume_t *volume, int cluster, int firstportal)
{
	int i, j, k, res, surfaceNum, backfaceculled, testculled;
	float d;
	winding_t winding, tmpwinding;
	lleaf_t *leaf;
	lportal_t *p;
	lsurfaceTest_t *test;
	lFacet_t *facet;
	vec3_t dir1, dir2;
	plane_t plane;

	//	DebugNet_RemoveAllPolys();
	//	VS_DrawLightVolume(light, volume);

	// if the first portal is not zero then we've checked all occluders in this leaf already
	if (firstportal == 0)
	{
		// check all potential occluders in this leaf
		for (i = 0; i < leafs[cluster].numSurfaces; i++)
		{
			surfaceNum = clustersurfaces[leafs[cluster].firstSurface + i];
			//
			test = lsurfaceTest[ surfaceNum ];
			if ( !test )
				continue;
			//
			testculled = qfalse;
			// use surface as an occluder
			for (j = 0; j < test->numFacets; j++)
			{
				// use each facet as an occluder
				facet = &test->facets[j];
				//
				//	memcpy(winding.points, facet->points, sizeof(vec3_t) * facet->numpoints);
				//	winding.numpoints = facet->numpoints;
				//	DebugNet_DrawWinding(&winding, 5);
				//
				// if the facet was tested already
				if ( volume->facetTested[facet->num >> 3] & (1 << (facet->num & 7)) )
					continue;
				volume->facetTested[facet->num >> 3] |= 1 << (facet->num & 7);
				// backface culling for planar surfaces
				backfaceculled = qfalse;
				if (!test->patch && !test->trisoup)
				{
					if (volume->type == VOLUME_NORMAL)
					{
						// facet backface culling
						d = DotProduct(light->origin, facet->plane.normal) - facet->plane.dist;
						if (d < 0)
						{
							// NOTE: this doesn't work too great because of sometimes very bad tesselation
							//		of surfaces that are supposed to be flat
							// FIXME: to work around this problem we should make sure that all facets
							//		created from planar surfaces use the lightmapVecs normal vector
							/*
							if ( !test->shader->twoSided )
							{
								// skip all other facets of this surface as well because they are in the same plane
								for (k = 0; k < test->numFacets; k++)
								{
									facet = &test->facets[k];
									volume->facetTested[facet->num >> 3] |= 1 << (facet->num & 7);
								}
							}*/
							backfaceculled = qtrue;
						}
					}
					else
					{
						// FIXME: if all light source winding points are at the back of the facet
						//			plane then backfaceculled = qtrue
					}
				}
				else // backface culling per facet for patches and triangle soups
				{
					if (volume->type == VOLUME_NORMAL)
					{
						// facet backface culling
						d = DotProduct(light->origin, facet->plane.normal) - facet->plane.dist;
						if (d < 0)
							backfaceculled = qtrue;
					}
					else
					{
						// FIXME: if all light source winding points are at the back of the facet
						//			plane then backfaceculled = qtrue
					}
				}
				/* chopping does this already
				// check if this facet is totally or partly in front of the volume end plane
				for (k = 0; k < facet->numpoints; k++)
				{
					d = DotProduct(volume->endplane.normal, facet->points[k]) - volume->endplane.dist;
					if (d > ON_EPSILON)
						break;
				}
				// if this facet is outside the light volume
				if (k >= facet->numpoints)
					continue;
				*/
				//
				if (backfaceculled)
				{
					// if the facet is not two sided
					if ( !nobackfaceculling && !test->shader->twoSided )
						continue;
					// flip the winding
					for (k = 0; k < facet->numpoints; k++)
						VectorCopy(facet->points[k], winding.points[facet->numpoints - k - 1]);
					winding.numpoints = facet->numpoints;
				}
				else
				{
					memcpy(winding.points, facet->points, sizeof(vec3_t) * facet->numpoints);
					winding.numpoints = facet->numpoints;
				}
				//
				if (!testculled)
				{
					testculled = qtrue;
					// fast check if the surface sphere is totally behind the volume end plane
					d = DotProduct(volume->endplane.normal, test->origin) - volume->endplane.dist;
					if (d < -test->radius)
					{
						for (k = 0; k < test->numFacets; k++)
						{
							facet = &test->facets[k];
							volume->facetTested[facet->num >> 3] |= 1 << (facet->num & 7);
						}
						break;
					}
					for (k = 0; k < volume->numplanes; k++)
					{
						d = DotProduct(volume->planes[k].normal, test->origin) - volume->planes[k].dist;
						if (d < - test->radius)
						{
							for (k = 0; k < test->numFacets; k++)
							{
								facet = &test->facets[k];
								volume->facetTested[facet->num >> 3] |= 1 << (facet->num & 7);
							}
							break;
						}
					}
					if (k < volume->numplanes)
						break;
				}
				//NOTE: we have to chop the facet winding with the volume end plane because
				//		the faces in Q3 are not stitched together nicely
				res = VS_ChopWinding(&winding, &volume->endplane, 0.01);
				// if the facet is on or at the back of the volume end plane
				if (res == SIDE_BACK || res == SIDE_ON)
					continue;
				// check if the facet winding is totally or partly inside the light volume
				memcpy(&tmpwinding, &winding, sizeof(winding_t));
				for (k = 0; k < volume->numplanes; k++)
				{
					res = VS_ChopWinding(&tmpwinding, &volume->planes[k], 0.01);
					if (res == SIDE_BACK || res == SIDE_ON)
						break;
				}
				// if no part of the light volume is occluded by this facet
				if (k < volume->numplanes)
					continue;
				//
				for (k = 0; k < winding.numpoints; k++)
				{
					if (volume->type == VOLUME_DIRECTED)
					{
						VectorSubtract(winding.points[(k+1) % winding.numpoints], winding.points[k], dir1);
						CrossProduct(light->normal, dir1, plane.normal);
						VectorNormalize(plane.normal, plane.normal);
						plane.dist = DotProduct(plane.normal, winding.points[k]);
					}
					else
					{
						VectorSubtract(winding.points[(k+1) % winding.numpoints], winding.points[k], dir1);
						VectorSubtract(light->origin, winding.points[k], dir2);
						CrossProduct(dir1, dir2, plane.normal);
						VectorNormalize(plane.normal, plane.normal);
						plane.dist = DotProduct(plane.normal, winding.points[k]);
					}
					res = VS_R_SplitLightVolume(light, volume, &plane, cluster, 0);
					if (res == 1)
						break; //the facet wasn't really inside the volume
				}
				if (k >= winding.numpoints)
				{
					volume->endplane = facet->plane;
					if (backfaceculled)
					{
						VectorInverse(volume->endplane.normal);
						volume->endplane.dist = -volume->endplane.dist;
					}
					volume->surfaceNum = surfaceNum;
					volume->facetNum = j;
				}
			}
		}
	}
	// we've tested all occluders in this cluster
	volume->clusterTested[cluster >> 3] |= 1 << (cluster & 7);
	// flood light through the portals of the current leaf
	leaf = &leafs[cluster];
	for (i = firstportal; i < leaf->numportals; i++)
	{
		p = leaf->portals[i];
		//
		//	memcpy(&winding, p->winding, sizeof(winding_t));
		//	DebugNet_DrawWinding(&winding, 5);
		// if already flooded into the cluster this portal leads to
		if ( volume->clusterTested[p->leaf >> 3] & (1 << (p->leaf & 7)) )
			continue;
		//
		if (volume->type == VOLUME_NORMAL)
		{
			// portal backface culling
			d = DotProduct(light->origin, p->plane.normal) - p->plane.dist;
			if (d > 0) // portal plane normal points into neighbour cluster
				continue;
		}
		else
		{
			// FIXME: if all light source winding points are at the back of this portal
			//			plane then there's no need to flood through
		}
		// check if this portal is totally or partly in front of the volume end plane
		// fast check with portal sphere
		d = DotProduct(volume->endplane.normal, p->origin) - volume->endplane.dist;
		if (d < -p->radius)
			continue;
		for (j = 0; j < p->winding->numpoints; j++)
		{
			d = DotProduct(volume->endplane.normal, p->winding->points[j]) - volume->endplane.dist;
			if (d > -0.01)
				break;
		}
		// if this portal is totally behind the light volume end plane
		if (j >= p->winding->numpoints)
			continue;
		//distance from point light to portal
		d = DotProduct(p->plane.normal, light->origin) - p->plane.dist;
		// only check if a point light is Not *on* the portal
		if (volume->type != VOLUME_NORMAL || fabs(d) > 0.1)
		{
			// check if the portal is partly or totally inside the light volume
			memcpy(&winding, p->winding, sizeof(winding_t));
			for (j = 0; j < volume->numplanes; j++)
			{
				res = VS_ChopWinding(&winding, &volume->planes[j], 0.01);
				if (res == SIDE_BACK || res == SIDE_ON)
					break;
			}
			// if the light volume does not go through this portal at all
			if (j < volume->numplanes)
				continue;
		}
		// chop the light volume with the portal
		for (k = 0; k < p->winding->numpoints; k++)
		{
			if (volume->type == VOLUME_DIRECTED)
			{
				VectorSubtract(p->winding->points[(k+1) % p->winding->numpoints], p->winding->points[k], dir1);
				CrossProduct(light->normal, dir1, plane.normal);
				VectorNormalize(plane.normal, plane.normal);
				plane.dist = DotProduct(plane.normal, p->winding->points[k]);
			}
			else
			{
				VectorSubtract(p->winding->points[(k+1) % p->winding->numpoints], p->winding->points[k], dir1);
				VectorSubtract(light->origin, p->winding->points[k], dir2);
				CrossProduct(dir1, dir2, plane.normal);
				VectorNormalize(plane.normal, plane.normal);
				plane.dist = DotProduct(plane.normal, p->winding->points[k]);
			}
			res = VS_R_SplitLightVolume(light, volume, &plane, cluster, i+1);
			if (res == 1)
				break; //volume didn't really go through the portal
		}
		// if the light volume went through the portal
		if (k >= p->winding->numpoints)
		{
			// flood through the portal
			VS_R_FloodLight(light, volume, p->leaf, 0);
		}
	}
}

/*
=============
VS_R_FloodAreaSpotLight
=============
*/
void VS_FloodAreaSpotLight(vsound_t *light, winding_t *w, int leafnum)
{
}

/*
=============
VS_R_SubdivideAreaSpotLight
=============
*/
void VS_R_SubdivideAreaSpotLight(vsound_t *light, int nodenum, winding_t *w)
{
	int leafnum, res;
	dnode_t *node;
	dplane_t *plane;
	winding_t back;
	plane_t split;

	while(nodenum >= 0)
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planeNum];

		VectorCopy(plane->normal, split.normal);
		split.dist = plane->dist;
		res = VS_SplitWinding (w, &back, &split, 0.1);

		if (res == SIDE_FRONT)
		{
			nodenum = node->children[0];
		}
		else if (res == SIDE_BACK)
		{
			nodenum = node->children[1];
		}
		else if (res == SIDE_ON)
		{
			memcpy(&back, w, sizeof(winding_t));
			VS_R_SubdivideAreaSpotLight(light, node->children[1], &back);
			nodenum = node->children[0];
		}
		else
		{
			VS_R_SubdivideAreaSpotLight(light, node->children[1], &back);
			nodenum = node->children[0];
		}
	}
	leafnum = -nodenum - 1;
	if (dleafs[leafnum].cluster != -1)
	{
		VS_FloodAreaSpotLight(light, w, leafnum);
	}
}

/*
=============
VS_R_FloodRadialAreaLight
=============
*/
void VS_FloodRadialAreaLight(vsound_t *light, winding_t *w, int leafnum)
{
}

/*
=============
VS_R_SubdivideRadialAreaLight
=============
*/
void VS_R_SubdivideRadialAreaLight(vsound_t *light, int nodenum, winding_t *w)
{
	int leafnum, res;
	dnode_t *node;
	dplane_t *plane;
	winding_t back;
	plane_t split;

	while(nodenum >= 0)
	{
		node = &dnodes[nodenum];
		plane = &dplanes[node->planeNum];

		VectorCopy(plane->normal, split.normal);
		split.dist = plane->dist;
		res = VS_SplitWinding (w, &back, &split, 0.1);

		if (res == SIDE_FRONT)
		{
			nodenum = node->children[0];
		}
		else if (res == SIDE_BACK)
		{
			nodenum = node->children[1];
		}
		else if (res == SIDE_ON)
		{
			memcpy(&back, w, sizeof(winding_t));
			VS_R_SubdivideRadialAreaLight(light, node->children[1], &back);
			nodenum = node->children[0];
		}
		else
		{
			VS_R_SubdivideRadialAreaLight(light, node->children[1], &back);
			nodenum = node->children[0];
		}
	}
	leafnum = -nodenum - 1;
	if (dleafs[leafnum].cluster != -1)
	{
		VS_FloodRadialAreaLight(light, w, leafnum);
	}
}

/*
=============
VS_R_FloodDirectedLight
=============
*/
void VS_FloodDirectedLight(vsound_t *light, winding_t *w, int leafnum)
{
	int i;
	float dist;
	lightvolume_t volume;
	vec3_t dir;

	if (light->atten_disttype == LD