// tr_map.c

// leave this as first line for PCH reasons...
//
#include "../server/exe_headers.h"

#include "tr_local.h"

#include "../qcommon/cm_local.h"

/*

Loads and prepares a map file for scene rendering.

A single entry point:

void RE_LoadWorldMap( const char *name );

*/

static world_t		s_worldData;
byte		*fileBase;
int			c_subdivisions;
int			c_gridVerts;

void R_RMGInit(void);
//===============================================================================

// We use a special hack to prevent slight differences in channels
// from exploding into big differences, as it causes lighting problems
// later on. This is the maximum channel separation for which we
// enable the hack.
#define MAX_GREYSCALE_CHANNEL_DIFF 15

static void R_ColorShiftLightingBytes16( const byte in[4], byte out[2] ) {
	// What's the largest separation between the red, green, and blue
	// channels?
	int chanDiff = max(in[0],max(in[1],in[2])) -
		min(in[0],min(in[1],in[2]));
	if (chanDiff <= MAX_GREYSCALE_CHANNEL_DIFF)
	{
		// Ensure that all color channels compress to the same value
		byte channelAvg = (in[0] + in[1] + in[2] + 1) / 3;
		out[0] = channelAvg & 0xF0;
		out[0] |= (channelAvg & 0xF0) >> 4;
		out[1] = channelAvg & 0xF0;
		out[1] |= (in[3] & 0xF0) >> 4;

		if (channelAvg % 16 >= 8)
		{
			out[0] |= 0x10;
			out[0] |= 0x01;
			out[1] |= 0x10;
		}
		if (in[4] % 16 >= 8)
		{
			out[1] |= 0x01;
		}
		return;
	}

	// Normal case for vertex colors that are not "near" greyscale
	out[0] = in[0] & 0xF0;
	out[0] |= (in[1] & 0xF0) >> 4;
	out[1] = in[2] & 0xF0;
	out[1] |= (in[3] & 0xF0) >> 4;
	
	if(in[0] % 16 >= 8) {
		out[0] |= 0x10;
	}
	if(in[1] % 16 >= 8) {
		out[0] |= 0x1;
	}
	if(in[2] % 16 >= 8) {
		out[1] |= 0x10;
	}
	if(in[3] % 16 >= 8) {
		out[1] |= 0x1;
	}
}


static void HSVtoRGB( float h, float s, float v, float rgb[3] )
{
	int i;
	float f;
	float p, q, t;

	h *= 5;

	i = floor( h );
	f = h - i;

	p = v * ( 1 - s );
	q = v * ( 1 - s * f );
	t = v * ( 1 - s * ( 1 - f ) );

	switch ( i )
	{
	case 0:
		rgb[0] = v;
		rgb[1] = t;
		rgb[2] = p;
		break;
	case 1:
		rgb[0] = q;
		rgb[1] = v;
		rgb[2] = p;
		break;
	case 2:
		rgb[0] = p;
		rgb[1] = v;
		rgb[2] = t;
		break;
	case 3:
		rgb[0] = p;
		rgb[1] = q;
		rgb[2] = v;
		break;
	case 4:
		rgb[0] = t;
		rgb[1] = p;
		rgb[2] = v;
		break;
	case 5:
		rgb[0] = v;
		rgb[1] = p;
		rgb[2] = q;
		break;
	}
}

/*
===============
R_ColorShiftLightingBytes

===============
*/
void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) {
	int		shift=0, r, g, b;

	// should NOT do it if overbrightBits is 0
	if (tr.overbrightBits)
		shift = 1 - tr.overbrightBits;

	if (!shift)
	{
		out[0] = in[0];
		out[1] = in[1];
		out[2] = in[2];
		out[3] = in[3];
		return;
	}

	// shift the data based on overbright range
	r = in[0] << shift;
	g = in[1] << shift;
	b = in[2] << shift;
	
	// normalize by color instead of saturating to white
	if ( ( r | g | b ) > 255 ) {
		int		max;

		max = r > g ? r : g;
		max = max > b ? max : b;
		r = r * 255 / max;
		g = g * 255 / max;
		b = b * 255 / max;
	}

	out[0] = r;
	out[1] = g;
	out[2] = b;
	out[3] = in[3];
}

/*
===============
R_ColorShiftLightingBytes

===============
*/
static	void R_ColorShiftLightingBytes( byte in[3]) 
{
	int		shift=0, r, g, b;

	// should NOT do it if overbrightBits is 0
	if (tr.overbrightBits)
		shift = 1 - tr.overbrightBits;

	if (!shift) {
		return;	//no need if not overbright
	}
	// shift the data based on overbright range
	r = in[0] << shift;
	g = in[1] << shift;
	b = in[2] << shift;
	
	// normalize by color instead of saturating to white
	if ( ( r | g | b ) > 255 ) {
		int		max;

		max = r > g ? r : g;
		max = max > b ? max : b;
		r = r * 255 / max;
		g = g * 255 / max;
		b = b * 255 / max;
	}

	in[0] = r;
	in[1] = g;
	in[2] = b;
}


/*
===============
R_LoadLightmaps

===============
*/
#define	LIGHTMAP_SIZE	128
void R_LoadLightmaps( void *data, int len, const char *psMapName ) {
	byte		*buf, *buf_p;
	int			i;

	if ( !len ) {
		return;
	}
	buf = (byte *)data + sizeof(int);

	tr.numLightmaps = 0;

	// we are about to upload textures
	R_SyncRenderThread();

	// create all the lightmaps
	int size = *(int*)data;
	tr.numLightmaps = len / size;

	byte* image = (byte*)Z_Malloc(size, TAG_BSP, qfalse, 32);

	char sMapName[MAX_QPATH];
	COM_StripExtension(psMapName,sMapName);	// will already by MAX_QPATH legal, so no length check

	for ( i = 0 ; i < tr.numLightmaps ; i++ ) {
		buf_p = buf + i * size;
		memcpy(image, buf_p, size);

		char lmapName[MAX_QPATH + 32];
		Com_sprintf(lmapName, MAX_QPATH + 32, "*%s/lightmap%d",sMapName,i);
		tr.lightmaps[i] = R_CreateImage( lmapName, image, 
			LIGHTMAP_SIZE, LIGHTMAP_SIZE,
			GL_DDS_RGB16_EXT,
			qfalse, 0, GL_CLAMP);
	}

	Z_Free(image);
}


/*
=================
RE_SetWorldVisData

This is called by the clipmodel subsystem so we can share the 1.8 megs of
space in big maps...
=================
*/
void RE_SetWorldVisData( SPARC<byte> *vis ) {
	tr.externalVisData = vis;
}


/*
=================
R_LoadVisibility
=================
*/
static	void R_LoadVisibility( void *data, int len ) {
	int		length;
	char    *buf;

	length = ( s_worldData.numClusters + 63 ) & ~63;
	s_worldData.novis = ( unsigned char *) Hunk_Alloc( length, h_low );
	memset( s_worldData.novis, 0xff, length );

	if ( !len ) {
		s_worldData.vis = NULL;
		return;
	}
	buf = (char*)data;

	s_worldData.numClusters = ((int *)buf)[0];
	s_worldData.clusterBytes = ((int *)buf)[1];

	// CM_Load should have given us the vis data to share, so
	// we don't need to allocate another copy
	if ( tr.externalVisData ) {
		s_worldData.vis = tr.externalVisData;
	} else {
		assert(0);
	}
}

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

qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData)
{
	qhandle_t	shader;

	if ( (shaderNum < 0) || (shaderNum >= worldData.numShaders) ) 
	{
		Com_Printf( "Warning: Bad index for R_GetShaderByNum - %i", shaderNum );
		return(0);
	}
	shader = RE_RegisterShader(worldData.shaders[ shaderNum ].shader);
	return(shader);
}

/*
===============
ShaderForShaderNum
===============
*/
static shader_t *ShaderForShaderNum( int shaderNum, const int *lightmapNum, const byte *lightmapStyles ) {
	shader_t	*shader;
	dshader_t	*dsh;

	shaderNum = shaderNum;
	if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) {
		Com_Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum );
	}
	dsh = &s_worldData.shaders[ shaderNum ];

	shader = R_FindShader( dsh->shader, lightmapNum, lightmapStyles, qtrue );

	// if the shader had errors, just use default shader
	if ( shader->defaultShader ) {
		return tr.defaultShader;
	}

	return shader;
}

bool NeedVertexColors(shader_t *shader)
{
	int i;
	shaderStage_t *stage;

	for(i=0; i<shader->numUnfoggedPasses; i++) {
		stage = &shader->stages[i];
		switch(stage->rgbGen) {
		case CGEN_EXACT_VERTEX:
		case CGEN_VERTEX:
		case CGEN_ONE_MINUS_VERTEX:
			return true;
		}
		switch(stage->alphaGen) {
		case AGEN_VERTEX:
		case AGEN_ONE_MINUS_VERTEX:
			return true;
		}
	}

	return false;
}

int NumLightMaps(shader_t *shader)
{
	int count = 0;
	int i;

	for(i=0; i<MAXLIGHTMAPS; i++) {
		if(shader->lightmapIndex[i] >= 0) {
			count++;
		} else {
			return count;
		}
	}

	return count;
}

int SurfaceFaceSize(int numVerts, int numLightMaps, bool needVertexColors,
		int numIndexes)
{
	int sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->srfPoints + 
		4 /*sizeof srfPoints*/ + 
		(numVerts * sizeof(unsigned short) *
			(VERTEX_LM + numLightMaps * 2 + 
			(int)needVertexColors * 4));	

	// Add in tangent size
	sfaceSize += sizeof(vec3_t) * numVerts;

	//Indices stored in 8 bits now.
	sfaceSize += numIndexes;

	return sfaceSize;
}


void BuildDrawVertTangents( drawVert_t *verts, int *indexes, int numIndexes, int numVertexes ) 
{
	int i = 0;

	for(i = 0; i < numVertexes; i++)
	{
		verts[i].tangent[0] = 0.0f;
		verts[i].tangent[1] = 0.0f;
		verts[i].tangent[2] = 0.0f;
	}

	for(i = 0; i < numIndexes; i += 3)
	{
		vec3_t vec1, vec2, du, dv, cp;
		float st0[2], st1[2], st2[2];

		Q_CastShort2FloatScale(&st0[0], &verts[indexes[i]].dvst[0], 1.f / DRAWVERT_ST_SCALE);
		Q_CastShort2FloatScale(&st0[1], &verts[indexes[i]].dvst[1], 1.f / DRAWVERT_ST_SCALE);

		Q_CastShort2FloatScale(&st1[0], &verts[indexes[i+1]].dvst[0], 1.f / DRAWVERT_ST_SCALE);
		Q_CastShort2FloatScale(&st1[1], &verts[indexes[i+1]].dvst[1], 1.f / DRAWVERT_ST_SCALE);

		Q_CastShort2FloatScale(&st2[0], &verts[indexes[i+2]].dvst[0], 1.f / DRAWVERT_ST_SCALE);
		Q_CastShort2FloatScale(&st2[1], &verts[indexes[i+2]].dvst[1], 1.f / DRAWVERT_ST_SCALE);

		vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0];
		vec1[1] = st1[0] - st0[0];
		vec1[2] = st1[1] - st0[1];

		vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0];
		vec2[1] = st2[0] - st0[0];
		vec2[2] = st2[1] - st0[1];

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[0] = -cp[1] / cp[0];
		dv[0] = -cp[2] / cp[0];

		vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1];

		vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1];

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[1] = -cp[1] / cp[0];
		dv[1] = -cp[2] / cp[0];

		vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2];

		vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2];

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[2] = -cp[1] / cp[0];
		dv[2] = -cp[2] / cp[0];

		verts[indexes[i]].tangent[0] += du[0];
		verts[indexes[i]].tangent[1] += du[1];
		verts[indexes[i]].tangent[2] += du[2];

		verts[indexes[i+1]].tangent[0] += du[0];
		verts[indexes[i+1]].tangent[1] += du[1];
		verts[indexes[i+1]].tangent[2] += du[2];

		verts[indexes[i+2]].tangent[0] += du[0];
		verts[indexes[i+2]].tangent[1] += du[1];
		verts[indexes[i+2]].tangent[2] += du[2];
	}

	for(i = 0; i < numVertexes; i++)
	{
		VectorNormalizeFast(verts[i].tangent);
	}
}


void BuildMapVertTangents( mapVert_t *verts, vec3_t *tangents, short *indexes, int numIndexes, int numVertexes ) 
{
	int i = 0;

	for(i = 0; i < numVertexes; i++)
	{
		tangents[i][0] = 0.0f;
		tangents[i][1] = 0.0f;
		tangents[i][2] = 0.0f;
	}

	for(i = 0; i < numIndexes; i += 3)
	{
		vec3_t vec1, vec2, du, dv, cp;

		vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0];
		vec1[1] = (verts[indexes[i+1]].st[0] * POINTS_ST_SCALE) - 
			(verts[indexes[i]].st[0] * POINTS_ST_SCALE);
		vec1[2] = (verts[indexes[i+1]].st[1] * POINTS_ST_SCALE) - 
			(verts[indexes[i]].st[1] * POINTS_ST_SCALE);

		vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0];
		vec2[1] = (verts[indexes[i+2]].st[0] * POINTS_ST_SCALE) - 
			(verts[indexes[i]].st[0] * POINTS_ST_SCALE);
		vec2[2] = (verts[indexes[i+2]].st[1]* POINTS_ST_SCALE) - 
			(verts[indexes[i]].st[1] * POINTS_ST_SCALE);

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[0] = -cp[1] / cp[0];
		dv[0] = -cp[2] / cp[0];

		vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1];

		vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1];

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[1] = -cp[1] / cp[0];
		dv[1] = -cp[2] / cp[0];

		vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2];

		vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2];

		CrossProduct(vec1, vec2, cp);

		if(cp[0] == 0.0f)
			cp[0] = 0.001f;

		du[2] = -cp[1] / cp[0];
		dv[2] = -cp[2] / cp[0];

		tangents[indexes[i]][0] += du[0];
		tangents[indexes[i]][1] += du[1];
		tangents[indexes[i]][2] += du[2];

		tangents[indexes[i+1]][0] += du[0];
		tangents[indexes[i+1]][1] += du[1];
		tangents[indexes[i+1]][2] += du[2];

		tangents[indexes[i+2]][0] += du[0];
		tangents[indexes[i+2]][1] += du[1];
		tangents[indexes[i+2]][2] += du[2];
	}

	for(i = 0; i < numVertexes; i++)
	{
		VectorNormalizeFast(tangents[i]);
	}
}

/*
===============
ParseFace
===============
*/
static void ParseFace( dface_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes, byte *&pFaceDataBuffer) 
{
	int			i, j, k;
	srfSurfaceFace_t	*cv;
	int			numPoints, numIndexes;
	int			lightmapNum[MAXLIGHTMAPS];
	int			sfaceSize, ofsIndexes;
	vec3_t		tangents[1000];

	for(i=0;i<MAXLIGHTMAPS;i++)
	{
		lightmapNum[i] = (int)ds->lightmapNum[i] - 4;
	}

	// get fog volume
	surf->fogIndex = ds->fogNum + 1;

	// get shader value
	surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles );
	if ( r_singleShader->integer && !surf->shader->sky ) {
		surf->shader = tr.defaultShader;
	}

	bool needVertexColors = NeedVertexColors(surf->shader); 
	int numLightMaps = NumLightMaps(surf->shader);
	assert(numLightMaps <= 0x7F);

	numPoints = ds->verts & 0xFFF;
	if (numPoints > MAX_FACE_POINTS) {
		Com_Printf (S_COLOR_YELLOW  "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints);
	}

	numIndexes = ds->indexes & 0xFFF;

	// create the srfSurfaceFace_t
	sfaceSize = SurfaceFaceSize(numPoints,
			numLightMaps, needVertexColors, numIndexes);
	ofsIndexes = sfaceSize - numIndexes;

	cv = (srfSurfaceFace_t *) pFaceDataBuffer;//ri.Hunk_Alloc( sfaceSize );
	pFaceDataBuffer += sfaceSize;	// :-)

	cv->surfaceType = SF_FACE;
	cv->numPoints = numPoints;
	cv->numIndices = numIndexes;
	cv->ofsIndices = ofsIndexes;
	cv->srfPoints = (unsigned short *)(((byte*)cv) + ( int ) &((srfSurfaceFace_t *)0)->srfPoints + 4);
	if(needVertexColors) {
		cv->flags = 1 << 7;
	} else {
		cv->flags = 0;
	}
	cv->flags |= (numLightMaps & 0x7F);

	//Make sure we don't overflow storage.
	assert(numPoints < 256);
	assert(numIndexes < 65536);
	assert(ofsIndexes < 65536);

	int nextSurfPoint = NEXT_SURFPOINT(cv->flags);
	verts += ds->verts >> 12;
	indexes += ds->indexes >> 12;

	BuildMapVertTangents(verts, tangents, indexes, numIndexes, numPoints);

	for ( i = 0 ; i < numPoints ; i++ ) {
		for ( j = 0 ; j < 3 ; j++ ) {
			*(cv->srfPoints + i * nextSurfPoint + j) = verts[i].xyz[j];
		}
		for ( j = 0; j < 3 ; j++ ) {
			assert(tangents[i][j] >= -1 && tangents[i][j] <= 1);
			*(cv->srfPoints + i * nextSurfPoint + 3 + j) = (short)(tangents[i][j] * 32767.0f);
		}
		for ( j = 0 ; j < 2 ; j++ ) {
			*(cv->srfPoints + i * nextSurfPoint + 6 + j) = 
				(short)(verts[i].st[j] * POINTS_ST_SCALE);

			for(k=0;k<numLightMaps;k++)
			{
				*(cv->srfPoints + i * nextSurfPoint + VERTEX_LM+j+(k*2)) = 
					verts[i].lightmap[k][j];
			}
		}
		if(needVertexColors) {
			for(k=0;k<MAXLIGHTMAPS;k++)
			{
				R_ColorShiftLightingBytes16(
					verts[i].color[k],
					(byte*)(cv->srfPoints + i * nextSurfPoint + 
					VERTEX_COLOR(cv->flags) + k));
			}
		}
	}

	unsigned char *indexStorage = ((unsigned char*)cv) + cv->ofsIndices;
	for ( i = 0 ; i < numIndexes ; i++ ) {
		indexStorage[i] = indexes[ i ];
	}

	// take the plane information from the lightmap vector
	for ( i = 0 ; i < 3 ; i++ ) {
		cv->plane.normal[i] = (float)ds->lightmapVecs[i] / 32767.f;
	}
	vec3_t fVec;
	fVec[0] = (float)((short)cv->srfPoints[0]);
	fVec[1] = (float)((short)cv->srfPoints[1]);
	fVec[2] = (float)((short)cv->srfPoints[2]);
	cv->plane.dist = DotProduct( fVec, cv->plane.normal );
	SetPlaneSignbits( &cv->plane );
	cv->plane.type = PlaneTypeForNormal( cv->plane.normal );

	surf->data = (surfaceType_t *)cv;
}


/*
===============
ParseMesh
===============
*/
static void ParseMesh ( dpatch_t *ds, mapVert_t *verts, msurface_t *surf,
					   drawVert_t* points, drawVert_t* ctrl, float* errorTable ) {
	srfGridMesh_t	*grid;
	int				i, j, k;
	int				width, height, numPoints;
	int				lightmapNum[MAXLIGHTMAPS];
	vec3_t			bounds[2];
	vec3_t			tmpVec;
	static surfaceType_t	skipData = SF_SKIP;

	for(i=0;i<MAXLIGHTMAPS;i++)
	{
		lightmapNum[i] = (int)ds->lightmapNum[i] - 4;
	}

	// get fog volume
	surf->fogIndex = ds->fogNum + 1;

	// get shader value
	surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles );
	if ( r_singleShader->integer && !surf->shader->sky ) {
		surf->shader = tr.defaultShader;
	}

	// we may have a nodraw surface, because they might still need to
	// be around for movement clipping
	if ( s_worldData.shaders[ ds->shaderNum ].surfaceFlags & SURF_NODRAW ) {
		surf->data = &skipData;
		return;
	}

	width = ds->patchWidth;
	height = ds->patchHeight;

	verts += ds->verts >> 12;
	numPoints = width * height;
	for ( i = 0 ; i < numPoints ; i++ ) {
		for ( j = 0 ; j < 3 ; j++ ) {
			points[i].xyz[j] = (float)verts[i].xyz[j];
			points[i].normal[j] = (float)verts[i].normal[j] / 32767.f;
		}
		for ( j = 0 ; j < 2 ; j++ ) {
			// Sanity check that alternate fixed point representation
			// is good enough
			assert( verts[i].st[j] * GRID_DRAWVERT_ST_SCALE < 32767 &&
					verts[i].st[j] * GRID_DRAWVERT_ST_SCALE >= -32768 );
			points[i].dvst[j] = verts[i].st[j] * GRID_DRAWVERT_ST_SCALE;
			for(k=0;k<MAXLIGHTMAPS;k++)
			{
				points[i].dvlightmap[k][j] = 
					((float)verts[i].lightmap[k][j] / POINTS_LIGHT_SCALE) *
					DRAWVERT_LIGHTMAP_SCALE;
			}
		}
		for(k=0;k<MAXLIGHTMAPS;k++)
		{
			R_ColorShiftLightingBytes16(verts[i].color[k], 
				points[i].dvcolor[k]);
		}
	}

	// pre-tesseleate
	grid = R_SubdividePatchToGrid( width, height, points, ctrl, errorTable );
	surf->data = (surfaceType_t *)grid;

	// copy the level of detail origin, which is the center
	// of the group of all curves that must subdivide the same
	// to avoid cracking
	for ( i = 0 ; i < 3 ; i++ ) {
		bounds[0][i] = ds->lightmapVecs[0][i];
		bounds[1][i] = ds->lightmapVecs[1][i];
	}
	VectorAdd( bounds[0], bounds[1], bounds[1] );
	VectorScale( bounds[1], 0.5f, grid->lodOrigin );
	VectorSubtract( bounds[0], grid->lodOrigin, tmpVec );
	grid->lodRadius = VectorLength( tmpVec );
}

/*
===============
ParseTriSurf
===============
*/
static void ParseTriSurf( dtrisurf_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes ) {
	srfTriangles_t	*tri;
	int				i, j, k;
	int				numVerts, numIndexes;

	// get fog volume
	surf->fogIndex = ds->fogNum + 1;

	// get shader
	surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapsVertex, ds->lightmapStyles );
	if ( r_singleShader->integer && !surf->shader->sky ) {
		surf->shader = tr.defaultShader;
	}

	numVerts = ds->verts & 0xFFF;
	numIndexes = ds->indexes & 0xFFF;

	tri = (srfTriangles_t *) Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) 
		+ numIndexes * sizeof( tri->indexes[0] ), h_low );
	tri->surfaceType = SF_TRIANGLES;
	tri->numVerts = numVerts;
	tri->numIndexes = numIndexes;
	tri->verts = (drawVert_t *)(tri + 1);
	tri->indexes = (int *)(tri->verts + tri->numVerts );

	surf->data = (surfaceType_t *)tri;

	// copy vertexes
	verts += ds->verts >> 12;
	ClearBounds( tri->bounds[0], tri->bounds[1] );
	for ( i = 0 ; i < numVerts ; i++ ) {
		for ( j = 0 ; j < 3 ; j++ ) {
			tri->verts[i].xyz[j] = (float)verts[i].xyz[j];
			tri->verts[i].normal[j] = (float)verts[i].normal[j] / 32767.f;
		}
		AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] );
		for ( j = 0 ; j < 2 ; j++ ) {
			// Sanity check that alternate fixed point representation
			// is good enough
			// MATT! - double check this!
			assert( verts[i].st[j] * DRAWVERT_ST_SCALE <= 32767 &&
					verts[i].st[j] * DRAWVERT_ST_SCALE >= -32768 );
			tri->verts[i].dvst[j] = verts[i].st[j] * DRAWVERT_ST_SCALE;
			for(k=0;k<MAXLIGHTMAPS;k++)
			{
				tri->verts[i].dvlightmap[k][j] = 
					((float)verts[i].lightmap[k][j] / POINTS_LIGHT_SCALE) *
					DRAWVERT_LIGHTMAP_SCALE;
			}
		}
		for(k=0;k<MAXLIGHTMAPS;k++)
		{
			R_ColorShiftLightingBytes16(verts[i].color[k], 
				tri->verts[i].dvcolor[k]);
		}
	}

	// copy indexes
	indexes += ds->indexes >> 12;
	for ( i = 0 ; i < numIndexes ; i++ ) {
		tri->indexes[i] = indexes[i];
		if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) {
			Com_Error( ERR_DROP, "Bad index in triangle surface" );
		}
	}

	// Build the tangent vectors
	BuildDrawVertTangents(tri->verts, tri->indexes, numIndexes, numVerts);
}


/*
===============
ParseFlare
===============
*/
static void ParseFlare( dflare_t *df, msurface_t *surf )
{
	srfFlare_t		*flare;
	int i;

	surf->fogIndex = df->fogNum + 1;

	// get shader
	surf->shader = ShaderForShaderNum( df->shaderNum, lightmapsVertex, stylesDefault );

	flare = (srfFlare_t *) Hunk_Alloc( sizeof( *flare ), h_low );
	flare->surfaceType = SF_FLARE;

	for ( i = 0 ; i < 3 ; i++ ) {
		flare->origin[i] = df->origin[i];
		flare->color[i] = df->color[i];
		flare->normal[i] = df->normal[i];
	}

	surf->data = (surfaceType_t *)flare;
}


void R_LoadFlares( void *surfaces, int surfacelen ) {
	int count, i;
	dflare_t	*in = NULL;
	msurface_t  *out;

	count = surfacelen / sizeof(*in);

	for ( i = 0 ; i < count ; i++ ) {
		in = (dflare_t *)surfaces + i;
		out = s_worldData.surfaces + in->code;
		ParseFlare( in, out );
	}
}


/*
===============
R_LoadSurfaces
===============
*/
void R_LoadSurfaces( int count ) {
	s_worldData.surfaces = (struct msurface_s *) 
		Hunk_Alloc ( count * sizeof(msurface_s), h_low );
	s_worldData.numsurfaces = count;
}


/*
===============
R_LoadPatches
===============
*/
void R_LoadPatches( void *verts, int vertlen, 
					void *surfaces, int surfacelen ) {
	dpatch_t	*in = NULL;
	msurface_t	*out;
	mapVert_t	*dv;
	int			count;
	int			i;

	if (surfacelen == 0) {
		return;
	}

	count = surfacelen / sizeof(*in);

	dv = (mapVert_t *)(verts);
	if (vertlen % sizeof(*dv))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);

	drawVert_t* points = (drawVert_t*)Z_Malloc(
		MAX_PATCH_SIZE*MAX_PATCH_SIZE*sizeof(drawVert_t), 
		TAG_TEMP_WORKSPACE, qfalse);
	
	drawVert_t*	ctrl = (drawVert_t*)Z_Malloc(
		MAX_GRID_SIZE*MAX_GRID_SIZE*sizeof(drawVert_t), 
		TAG_TEMP_WORKSPACE, qfalse);
	
	float* errorTable = (float*)Z_Malloc(
		2*MAX_GRID_SIZE*sizeof(float), 
		TAG_TEMP_WORKSPACE, qfalse);

	for ( i = 0 ; i < count ; i++ ) {
		in = (dpatch_t *)surfaces + i;
		out = s_worldData.surfaces + in->code;
		ParseMesh ( in, dv, out, points, ctrl, errorTable );
	}

	Z_Free(errorTable);
	Z_Free(ctrl);
	Z_Free(points);

	Com_Printf( "...loaded %i meshes\n", count );
}


					/*
===============
R_LoadTriSurfs
===============
*/
void R_LoadTriSurfs( void *indexdata, int indexlen, 
					void *verts, int vertlen, 
					void *surfaces, int surfacelen ) {
	dtrisurf_t	*in = NULL;
	msurface_t	*out;
	mapVert_t	*dv;
	short		*indexes;
	int			count;
	int			i;

	if (surfacelen == 0) {
		return;
	}
	
	count = surfacelen / sizeof(*in);

	dv = (mapVert_t *)(verts);
	if (vertlen % sizeof(*dv))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);

	indexes = (short *)(indexdata);
	if ( indexlen % sizeof(*indexes))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);

	for ( i = 0 ; i < count ; i++ ) {
		in = (dtrisurf_t *)surfaces + i;
		out = s_worldData.surfaces + in->code;
		ParseTriSurf( in, dv, out, indexes );
	}

	Com_Printf( "...loaded %i trisurfs\n", count );
}


/*
===============
R_LoadFaces
===============
*/
void R_LoadFaces( void *indexdata, int indexlen, 
					void *verts, int vertlen, 
					void *surfaces, int surfacelen ) {
	dface_t		*in = NULL;
	msurface_t	*out;
	mapVert_t	*dv;
	short		*indexes;
	int			count;
	int			i;

	if (surfacelen == 0) {
		return;
	}
	
	count = surfacelen / sizeof(*in);

	dv = (mapVert_t *)(verts);
	if (vertlen % sizeof(*dv))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);

	indexes = (short *)(indexdata);
	if ( indexlen % sizeof(*indexes))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);

	// new bit, the face code on our biggest map requires over 15,000 mallocs, which was no problem on the hunk,
	//	bit hits the zone pretty bad (even the tagFree takes about 9 seconds for that many memblocks), 
	//	so special-case pre-alloc enough space for this data (the patches etc can stay as they are)...
	//
	int nTimes = count / 100;
	int nToGo = nTimes;
	int iFaceDataSizeRequired = 0;
	for ( i = 0 ; i < count ; i++) 
	{ 
		in = (dface_t *)surfaces + i;

		int lightmapNum[MAXLIGHTMAPS];
		for(int j=0; j<4; j++) {
			lightmapNum[j] = (int)in->lightmapNum[j] - 4;
		}
		shader_t *shader = ShaderForShaderNum( in->shaderNum, lightmapNum, in->lightmapStyles );
		bool needVertexColors = NeedVertexColors(shader); 
		int numLightMaps = NumLightMaps(shader);
		
		int sfaceSize = SurfaceFaceSize(in->verts & 0xFFF,
			numLightMaps, needVertexColors,
			in->indexes & 0xFFF);
		
		iFaceDataSizeRequired += sfaceSize;
		assert(sfaceSize < 100 * 1024);
		if (--nToGo <= 0)
		{
			nToGo = nTimes;
		}
	}
	in -= count;	// back it up, ready for loop-proper

	// since this ptr is to hunk data, I can pass it in and have it advanced without worrying about losing
	//	the original alloc ptr...
	//
	byte *orgFaceData;
	byte *pFaceDataBuffer	= (byte *)Hunk_Alloc( iFaceDataSizeRequired, h_low );
	orgFaceData = pFaceDataBuffer;

	// now do regular loop...
	//
	for ( i = 0 ; i < count ; i++ ) {
		in = (dface_t *)surfaces + i;
		out = s_worldData.surfaces + in->code;
		ParseFace( in, dv, out, indexes, pFaceDataBuffer );
		if (--nToGo <= 0)
		{
			nToGo = nTimes;
		}
	}

	Com_Printf( "...loaded %d faces\n", count );
}


/*
=================
R_LoadSubmodels
=================
*/
static	void R_LoadSubmodels( void *data, int len ) {
	dmodel_t	*in;
	bmodel_t	*out;
	int			i, j, count;

	in = (dmodel_t *)(data);
	if (len % sizeof(*in))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	count = len / sizeof(*in);

	s_worldData.bmodels = out = (bmodel_t *) Hunk_Alloc( count * sizeof(*out), h_low );

	for ( i=0 ; i<count ; i++, in++, out++ ) {
		model_t *model;

		model = R_AllocModel();

		assert( model != NULL );			// this should never happen

		model->type = MOD_BRUSH;
		model->bmodel = out;
		Com_sprintf( model->name, sizeof( model->name ), "*%d", i );

		for (j=0 ; j<3 ; j++) {
			out->bounds[0][j] = in->mins[j];
			out->bounds[1][j] = in->maxs[j];
		}

		RE_InsertModelIntoHash(model->name, model);

		out->firstSurface = s_worldData.surfaces + in->firstSurface;
		out->numSurfaces = in->numSurfaces;
	}
}

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

/*
=================
R_SetParent
=================
*/
static	void R_SetParent (mnode_t *node, mnode_t *parent)
{
	node->parent = parent;
	if (node->contents != -1)
		return;
	R_SetParent (node->children[0], node);
	R_SetParent (node->children[1], node);
}

/*
=================
R_LoadNodesAndLeafs
=================
*/
static void R_LoadNodesAndLeafs (void *nodes, int nodelen, void *leafs, int leaflen) {
	int			i, j, p;
	dnode_t		*in;
	dleaf_t		*inLeaf;
	mnode_t 	*outNode;
	mleaf_s 	*outLeaf;
	int			numNodes, numLeafs;

	in = (dnode_t *)(nodes);
	if (nodelen % sizeof(dnode_t) ||
		leaflen % sizeof(dleaf_t) ) {
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	}
	numNodes = nodelen / sizeof(dnode_t);
	numLeafs = leaflen / sizeof(dleaf_t);

	outNode = (struct mnode_s *) Hunk_Alloc ( (numNodes) * sizeof(*outNode), h_low );	
	outLeaf = (struct mleaf_s *) Hunk_Alloc ( (numLeafs) * sizeof(*outLeaf), h_low );	

	s_worldData.nodes = outNode;
	s_worldData.leafs = outLeaf;
	s_worldData.numnodes = numNodes;
	s_worldData.numleafs = numLeafs;

	// load nodes
	for ( i=0 ; i<numNodes; i++, in++, outNode++)
	{
		for (j=0 ; j<3 ; j++)
		{
			outNode->mins[j] = in->mins[j];
			outNode->maxs[j] = in->maxs[j];
		}
	
		outNode->planeNum = in->planeNum;
		outNode->contents = CONTENTS_NODE;	// differentiate from leafs

		for (j=0 ; j<2 ; j++)
		{
			p = in->children[j];
			if (p >= 0) {
				if(p < numNodes) {
					outNode->children[j] = s_worldData.nodes + p;
				} else {
					outNode->children[j] = (mnode_s*)
						(s_worldData.leafs + (p - numNodes));
				}
			} else {
				if(numNodes + (-1 - p) < numNodes) {
					outNode->children[j] = s_worldData.nodes + numNodes + (-1 - p);
				} else {
					outNode->children[j] = (mnode_s*)
						(s_worldData.leafs + (-1 - p));
				}
			}
		}
	}
	
	// load leafs
	inLeaf = (dleaf_t *)(leafs);
	for ( i=0 ; i<numLeafs ; i++, inLeaf++, outLeaf++)
	{
		for (j=0 ; j<3 ; j++)
		{
			outLeaf->mins[j] = inLeaf->mins[j];
			outLeaf->maxs[j] = inLeaf->maxs[j];
		}

		outLeaf->cluster = inLeaf->cluster;
		outLeaf->area = inLeaf->area;

		if ( outLeaf->cluster >= s_worldData.numClusters ) {
			s_worldData.numClusters = outLeaf->cluster + 1;
		}

		outLeaf->firstMarkSurfNum = inLeaf->firstLeafSurface;
		outLeaf->nummarksurfaces = inLeaf->numLeafSurfaces;
	}	

	// chain decendants
	R_SetParent (s_worldData.nodes, NULL);
}

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

/*
=================
R_LoadShaders
=================
*/
void R_LoadShaders( void *data, int len) {	
	dshader_t	*in, *out;
	int			i, count;

	in = (dshader_t *)(data);
	if (len % sizeof(*in)) {
		Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size");
	}
	count = len / sizeof(*in);

	if (count < 1) {
		Com_Error (ERR_DROP, "Map with no shaders");
	}
	out = (dshader_t *)Hunk_Alloc( count*sizeof(*out), h_low);

	s_worldData.shaders = out;
	s_worldData.numShaders = count;

	Com_Memcpy( out, in, count*sizeof(*out) );

	for ( i = 0; i < count; i++, in++, out++ ) 
	{
//		Q_strncpyz(out->shader, in->shader, MAX_QPATH);
		out->contentFlags = in->contentFlags;
		out->surfaceFlags = in->surfaceFlags;
	}
}

/*
=================
R_LoadMarksurfaces
=================
*/
static	void R_LoadMarksurfaces (void *data, int len)
{	
	int		i, count;
	int		*in;
	msurface_t **out;
	
	in = (int *)(data);
	if (len % sizeof(*in))
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	count = len / sizeof(*in);
	out = (struct msurface_s **) Hunk_Alloc ( count*sizeof(*out), h_low );	

	s_worldData.marksurfaces = out;
	s_worldData.nummarksurfaces = count;

	for ( i=0 ; i<count ; i++)
	{
		if(in[i] > s_worldData.numsurfaces)
			assert(0);

		out[i] = s_worldData.surfaces + in[i];

		if (out[i]->shader && out[i]->shader->sort == SS_PORTAL)
		{
			s_worldData.portalPresent = qtrue;
		}
	}
}

/*
=================
R_LoadPlanes
=================
*/
static void R_LoadPlanes( void *data, int len ) {
	int			i, j;
	cplane_t	*out;
	dplane_t 	*in;
	int			count;
	int			bits;

	in = (dplane_t *)(data);
	if (len % sizeof(*in))
		Com_Error (ERR_DROP, "LoadMap: funny lump size");
	count = len / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "Map with no planes");

	out = (struct cplane_s *) Hunk_Alloc( count * 2 * sizeof( *out ), h_low);

	s_worldData.planes = out;
	s_worldData.numplanes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		bits = 0;
		for (j=0 ; j<3 ; j++)
		{
			out->normal[j] = in->normal[j];
			if (out->normal[j] < 0)
				bits |= 1<<j;
		}

		out->dist = in->dist;
		out->type = PlaneTypeForNormal( out->normal );
		out->signbits = bits;
	}
}

/*
=================
R_LoadFogs

=================
*/
static void R_LoadFogs( void *fogdata, int foglen,
					   void *brushdata, int brushlen,
					   void *sidedata, int sidelen ) {
	int			i;
	fog_t		*out;
	dfog_t		*fogs;
	dbrush_t 	*brushes, *brush;
	dbrushside_t	*sides;
	int			count, brushesCount, sidesCount;
	int			sideNum;
	int			planeNum;
	shader_t	*shader;
	float		d;
	int			firstSide=0;
	int			lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_NONE } ;

	fogs = (dfog_t *)(fogdata);
	if (foglen % sizeof(*fogs)) {
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	}
	count = foglen / sizeof(*fogs);

	// create fog structres for them
	// NOTE: we allocate memory for an extra one so that the LA goggles can turn on their own fog
	s_worldData.numfogs = count + 1;
	s_worldData.fogs = (fog_t *)Hunk_Alloc (( s_worldData.numfogs + 1)*sizeof(*out), h_low );
	s_worldData.globalFog = -1;
	out = s_worldData.fogs + 1;

	if ( !count ) {
		return;
	}

	brushes = (dbrush_t *)(brushdata);
	if (brushlen % sizeof(*brushes)) {
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	}
	brushesCount = brushlen / sizeof(*brushes);

	sides = (dbrushside_t *)(sidedata);
	if (sidelen % sizeof(*sides)) {
		Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
	}
	sidesCount = sidelen / sizeof(*sides);

	for ( i=0 ; i<count ; i++, fogs++) {
		out->originalBrushNumber = fogs->brushNum;
		if (out->originalBrushNumber == -1)
		{
			out->bounds[0][0] = out->bounds[0][1] = out->bounds[0][2] = MIN_WORLD_COORD;
			out->bounds[1][0] = out->bounds[1][1] = out->bounds[1][2] = MAX_WORLD_COORD;
			s_worldData.globalFog = i+1;
		}
		else
		{
			if ( (unsigned)out->originalBrushNumber >= brushesCount ) {
				Com_Error( ERR_DROP, "fog brushNumber out of range" );
			}
			brush = brushes + out->originalBrushNumber;
			
			firstSide = brush->firstSide;
			
			if ( (unsigned)firstSide > sidesCount - 6 ) {
				Com_Error( ERR_DROP, "fog brush sideNumber out of range" );
			}
			
			// brushes are always sorted with the axial sides first
			sideNum = firstSide + 0;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist;
			
			sideNum = firstSide + 1;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[1][0] = s_worldData.planes[ planeNum ].dist;
			
			sideNum = firstSide + 2;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist;
			
			sideNum = firstSide + 3;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[1][1] = s_worldData.planes[ planeNum ].dist;
			
			sideNum = firstSide + 4;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist;
			
			sideNum = firstSide + 5;
			planeNum = sides[ sideNum ].planeNum;
			out->bounds[1][2] = s_worldData.planes[ planeNum ].dist;
		}
		
		// get information from the shader for fog parameters
		shader = R_FindShader( fogs->shader, lightmaps, stylesDefault, qtrue );
		
		if (!shader->fogParms)
		{//bad shader!!
			assert(shader->fogParms);
			out->parms.color[0] = 1.0f;
			out->parms.color[1] = 0.0f;
			out->parms.color[2] = 0.0f;
			out->parms.color[3] = 0.0f;
			out->parms.depthForOpaque = 250.0f;
		}
		else
		{
			out->parms = *shader->fogParms;
		}

		out->colorInt = ColorBytes4 ( out->parms.color[0] * tr.identityLight, 
									  out->parms.color[1] * tr.identityLight, 
									  out->parms.color[2] * tr.identityLight, 1.0 );
		
		d = out->parms.depthForOpaque < 1 ? 1 : out->parms.depthForOpaque;
		out->tcScale = 1.0 / ( d * 8 );
		
		// set the gradient vector
		sideNum = fogs->visibleSide;
		
		if ( sideNum == -1 ) {
			out->hasSurface = qfalse;
		} else {
			out->hasSurface = qtrue;
			planeNum = sides[ firstSide + sideNum ].planeNum;
			VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface );
			out->surface[3] = -s_worldData.planes[ planeNum ].dist;
		}
		
		out++;
	}

	// Initialise the last fog so we can use it with the LA Goggles
	// NOTE: We are might appear to be off the end of the array, but we allocated an extra memory slot above but [purposely] didn't 
	//	increment the total world numFogs to match our array size
	VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD);
	VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD);
	out->originalBrushNumber = -1;
	out->parms.color[0] = 0.0f;
	out->parms.color[1] = 0.0f;
	out->parms.color[2] = 0.0f;
	out->parms.color[3] = 0.0f;
	out->parms.depthForOpaque = 0.0f;
	out->colorInt = 0x00000000;
	out->tcScale = 0.0f;
	out->hasSurface = false;
}

/*
================
R_LoadLightGrid

================
*/
void R_LoadLightGrid( void *data, int len ) {
	vec3_t	maxs;
	world_t	*w;
	int		i;
	float	*wMins, *wMaxs;

	w = &s_worldData;

	w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0];
	w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1];
	w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2];

	wMins = w->bmodels[0].bounds[0];
	wMaxs = w->bmodels[0].bounds[1];

	for ( i = 0 ; i < 3 ; i++ ) {
		w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] );
		maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] );
		w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1;
	}

	w->lightGridData = (mgrid_t *)Hunk_Alloc( len, h_low );
	memcpy( w->lightGridData, data, len );
}

/*
================
R_LoadLightGridArray

================
*/
void R_LoadLightGridArray( void *data, int len ) {
	world_t	*w;

	w = &s_worldData;

	w->numGridArrayElements = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];

	if ( len != w->numGridArrayElements * sizeof(*w->lightGridArray) ) {
		if (len>0)//don't warn if not even lit
			Com_Printf( "WARNING: light grid array mismatch\n" );
		w->lightGridData = NULL;
		return;
	}

	w->lightGridArray = (unsigned short *)Hunk_Alloc( len, h_low );
	memcpy( w->lightGridArray, data, len );
}

/*
================
R_LoadEntities
================
*/
void R_LoadEntities( void *data, int len ) {
	const char *p, *token;
	char keyname[MAX_TOKEN_CHARS];
	char value[MAX_TOKEN_CHARS];
	world_t	*w;

	w = &s_worldData;
	w->lightGridSize[0] = 64;
	w->lightGridSize[1] = 64;
	w->lightGridSize[2] = 128;

	VectorSet(tr.sunAmbient, 1, 1, 1);
	tr.distanceCull = 6000;//DEFAULT_DISTANCE_CULL;

	p = (char *)(data);

	// store for reference by the cgame
	w->entityString = (char *)Hunk_Alloc( len + 1, h_low );
	strcpy( w->entityString, p );
	w->entityParsePoint = w->entityString;

	token = COM_ParseExt( &p, qtrue );
	if (!*token || *token != '{') {
		return;
	}

	// only parse the world spawn
	while ( 1 ) {	
		// parse key
		token = COM_ParseExt( &p, qtrue );

		if ( !*token || *token == '}' ) {
			break;
		}
		Q_strncpyz(keyname, token, sizeof(keyname));

		// parse value
		token = COM_ParseExt( &p, qtrue );

		if ( !*token || *token == '}' ) {
			break;
		}
		Q_strncpyz(value, token, sizeof(value));

		if (!Q_stricmp(keyname, "distanceCull")) {
			sscanf(value, "%f", &tr.distanceCull );
			continue;
		}

		// check for a different grid size
		if (!Q_stricmp(keyname, "gridsize")) {
			sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] );
			continue;
		}

		// find the optional world ambient for arioche
		if (!Q_stricmp(keyname, "_color")) {
			sscanf(value, "%f %f %f", &tr.sunAmbient[0], &tr.sunAmbient[1], &tr.sunAmbient[2] );
			continue;
		}
	}
}

/*
=================
R_GetEntityToken
=================
*/
qboolean R_GetEntityToken( char *buffer, int size ) {
	const char	*s;

	if (size == -1)
	{ //force reset
		s_worldData.entityParsePoint = s_worldData.entityString;
		return qtrue;
	}

	s = COM_Parse( (const char **) &s_worldData.entityParsePoint );
	Q_strncpyz( buffer, s, size );
	if ( !s_worldData.entityParsePoint || !s[0] ) {
		return qfalse;
	} else {
		return qtrue;
	}
}


/*
=================
RE_LoadWorldMap

Called directly from cgame
=================
*/
void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ) {
	char		stripName[MAX_QPATH];
	Lump outputLumps[3];

	if ( tr.worldMapLoaded ) {
		Com_Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" );
	}

	skyboxportal = 0;

	// set default sun direction to be used if it isn't
	// overridden by a shader
	tr.sunDirection[0] = 0.45f;
	tr.sunDirection[1] = 0.3f;
	tr.sunDirection[2] = 0.9f;

	Cvar_SetValue( "r_sundir_x", tr.sunDirection[0] );
	Cvar_SetValue( "r_sundir_y", tr.sunDirection[1] );
	Cvar_SetValue( "r_sundir_z", tr.sunDirection[2] );

	VectorNormalize( tr.sunDirection );

	tr.worldMapLoaded = qtrue;

	// clear tr.world so if the level fails to load, the next
	// try will not look at the partially loaded version
	tr.world = NULL;

	memset( &s_worldData, 0, sizeof( s_worldData ) );
	Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );

	Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) );
	COM_StripExtension( s_worldData.baseName, s_worldData.baseName );

	COM_StripExtension(name, stripName);
	
	c_gridVerts = 0;

	// load into heap
	outputLumps[0].load(stripName, "shaders");
	R_LoadShaders(outputLumps[0].data, outputLumps[0].len);

	outputLumps[0].load(stripName, "lightmaps");
	R_LoadLightmaps(outputLumps[0].data, outputLumps[0].len, name);

	outputLumps[0].load(stripName, "planes");
	R_LoadPlanes(outputLumps[0].data, outputLumps[0].len);

	outputLumps[0].load(stripName, "fogs");
	outputLumps[1].load(stripName, "brushes");
	outputLumps[2].load(stripName, "brushsides");
	R_LoadFogs( outputLumps[0].data, outputLumps[0].len,
		outputLumps[1].data, outputLumps[1].len,
		outputLumps[2].data, outputLumps[2].len );
	outputLumps[2].clear();
	outputLumps[1].clear();

	Lump misc;
	misc.load(stripName, "misc");
		
	int num_surfs = *(int*)misc.data;
	misc.clear();

	R_LoadSurfaces(num_surfs);
		
	Lump verts;
	verts.load(stripName, "verts");

	Lump patches;
	patches.load(stripName, "patches");
	R_LoadPatches(verts.data, verts.len, patches.data, patches.len);
	patches.clear();

	Lump indexes;
	indexes.load(stripName, "indexes");

	Lump trisurfs;
	trisurfs.load(stripName, "trisurfs");
	R_LoadTriSurfs(indexes.data, indexes.len, verts.data, verts.len, trisurfs.data, trisurfs.len);
	trisurfs.clear();
	
	Lump faces;
	faces.load(stripName, "faces");
	R_LoadFaces(indexes.data, indexes.len, verts.data, verts.len, faces.data, faces.len);

	Lump flares;
	flares.load(stripName, "flares");
	R_LoadFlares(flares.data, flares.len);

	outputLumps[0].load(stripName, "leafsurfaces");
	R_LoadMarksurfaces (outputLumps[0].data, outputLumps[0].len);

	outputLumps[0].load(stripName, "nodes");
	outputLumps[1].load(stripName, "leafs");
	R_LoadNodesAndLeafs (outputLumps[0].data, outputLumps[0].len,
		outputLumps[1].data, outputLumps[1].len);
	outputLumps[1].clear();
	
	outputLumps[0].load(stripName, "models");
	R_LoadSubmodels (outputLumps[0].data, outputLumps[0].len);

	outputLumps[0].load(stripName, "visibility");
	R_LoadVisibility(outputLumps[0].data, outputLumps[0].len);

	outputLumps[0].load(stripName, "entities");
	R_LoadEntities( outputLumps[0].data, outputLumps[0].len );

	outputLumps[0].load(stripName, "lightgrid");
	R_LoadLightGrid( outputLumps[0].data, outputLumps[0].len );

	outputLumps[0].load(stripName, "lightarray");
	R_LoadLightGridArray( outputLumps[0].data, outputLumps[0].len );

	// only set tr.world now that we know the entire level has loaded properly
	tr.world = &s_worldData;

	// Load the light parms for this level
	R_LoadLevelLightParms();
	R_GetLightParmsForLevel();
}


// new wrapper used for convenience to tell z_malloc()-fail recovery code whether it's safe to dump the cached-bsp or not.
//
extern qboolean gbUsingCachedMapDataRightNow;
void RE_LoadWorldMap( const char *name )
{
	gbUsingCachedMapDataRightNow = qtrue;	// !!!!!!!!!!!!

		RE_LoadWorldMap_Actual( name, s_worldData, 0 );

	gbUsingCachedMapDataRightNow = qfalse;	// !!!!!!!!!!!!
}
