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

Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

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

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

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



/* marker */
#define MAP_C



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



/* FIXME: remove these vars */

/* undefine to make plane finding use linear sort (note: really slow) */
#define	USE_HASHING
#define	PLANE_HASHES	8192

plane_t					*planehash[ PLANE_HASHES ];

int						c_boxbevels;
int						c_edgebevels;
int						c_areaportals;
int						c_detail;
int						c_structural;



/*
PlaneEqual()
ydnar: replaced with variable epsilon for djbob
*/

#define	NORMAL_EPSILON	0.00001
#define	DIST_EPSILON	0.01

qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist )
{
	float	ne, de;
	
	
	/* get local copies */
	ne = normalEpsilon;
	de = distanceEpsilon;
	
	/* compare */
	if( fabs( p->dist - dist ) <= de &&
		fabs( p->normal[ 0 ] - normal[ 0 ] ) <= ne &&
		fabs( p->normal[ 1 ] - normal[ 1 ] ) <= ne &&
		fabs( p->normal[ 2 ] - normal[ 2 ] ) <= ne )
		return qtrue;
	
	/* different */
	return qfalse;
}



/*
AddPlaneToHash()
*/

void AddPlaneToHash( plane_t *p )
{
	int		hash;

	
	hash = (PLANE_HASHES - 1) & (int) fabs( p->dist );

	p->hash_chain = planehash[hash];
	planehash[hash] = p;
}

/*
================
CreateNewFloatPlane
================
*/
int CreateNewFloatPlane (vec3_t normal, vec_t dist)
{
	plane_t	*p, temp;

	if (VectorLength(normal) < 0.5)
	{
		Sys_Printf( "FloatPlane: bad normal\n");
		return -1;
	}

	// create a new plane
	if (nummapplanes+2 > MAX_MAP_PLANES)
		Error ("MAX_MAP_PLANES");

	p = &mapplanes[nummapplanes];
	VectorCopy (normal, p->normal);
	p->dist = dist;
	p->type = (p+1)->type = PlaneTypeForNormal (p->normal);

	VectorSubtract (vec3_origin, normal, (p+1)->normal);
	(p+1)->dist = -dist;

	nummapplanes += 2;

	// allways put axial planes facing positive first
	if (p->type < 3)
	{
		if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0)
		{
			// flip order
			temp = *p;
			*p = *(p+1);
			*(p+1) = temp;

			AddPlaneToHash (p);
			AddPlaneToHash (p+1);
			return nummapplanes - 1;
		}
	}

	AddPlaneToHash (p);
	AddPlaneToHash (p+1);
	return nummapplanes - 2;
}



/*
SnapNormal()
snaps a near-axial normal vector
*/

void SnapNormal( vec3_t normal )
{
	int		i;

	for( i = 0; i < 3; i++ )
	{
		if( fabs( normal[ i ] - 1 ) < normalEpsilon )
		{
			VectorClear( normal );
			normal[ i ] = 1;
			break;
		}
		if( fabs( normal[ i ] - -1 ) < normalEpsilon )
		{
			VectorClear( normal );
			normal[ i ] = -1;
			break;
		}
	}
}



/*
SnapPlane()
snaps a plane to normal/distance epsilons
*/

void SnapPlane( vec3_t normal, vec_t *dist )
{
	SnapNormal( normal );

	if( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon )
		*dist = Q_rint( *dist );
}



/*
FindFloatPlane()
ydnar: changed to allow a number of test points to be supplied that
must be within an epsilon distance of the plane
*/

int FindFloatPlane( vec3_t normal, vec_t dist, int numPoints, vec3_t *points )

#ifdef USE_HASHING

{
	int		i, j, hash, h;
	plane_t	*p;
	vec_t	d;
	
	
	/* hash the plane */
	SnapPlane( normal, &dist );
	hash = (PLANE_HASHES - 1) & (int) fabs( dist );
	
	/* search the border bins as well */
	for( i = -1; i <= 1; i++ )
	{
		h = (hash + i) & (PLANE_HASHES - 1);
		for( p = planehash[ h ]; p != NULL; p = p->hash_chain )
		{
			/* do standard plane compare */
			if( !PlaneEqual( p, normal, dist ) )
				continue;
			
			/* ydnar: uncomment the following line for old-style plane finding */
			//%	return p - mapplanes;
			
			/* ydnar: test supplied points against this plane */
			for( j = 0; j < numPoints; j++ )
			{
				d = DotProduct( points[ j ], normal ) - dist;
				if( fabs( d ) > distanceEpsilon )
					break;
			}
			
			/* found a matching plane */
			if( j >= numPoints )
				return p - mapplanes;
		}
	}
	
	/* none found, so create a new one */
	return CreateNewFloatPlane( normal, dist );
}

#else

{
	int		i;
	plane_t	*p;
	

	SnapPlane( normal, &dist );
	for( i = 0, p = mapplanes; i < nummapplanes; i++, p++ )
	{
		if( PlaneEqual( p, normal, dist ) )
			return i;
	}
	
	return CreateNewFloatPlane( normal, dist );
}

#endif



/*
MapPlaneFromPoints()
takes 3 points and finds the plane they lie in
*/

int MapPlaneFromPoints( vec3_t *p )
{
	vec3_t	t1, t2, normal;
	vec_t	dist;
	
	
	/* calc plane normal */
	VectorSubtract( p[ 0 ], p[ 1 ], t1 );
	VectorSubtract( p[ 2 ], p[ 1 ], t2 );
	CrossProduct( t1, t2, normal );
	VectorNormalize( normal, normal );
	
	/* calc plane distance */
	dist = DotProduct( p[ 0 ], normal );
	
	/* store the plane */
	return FindFloatPlane( normal, dist, 3, p );
}



/*
SetBrushContents()
the content flags and compile flags on all sides of a brush should be the same
*/

void SetBrushContents( brush_t *b )
{
	int			contentFlags, compileFlags;
	side_t		*s;
	int			i;
	qboolean	mixed;
	
	
	/* get initial compile flags from first side */
	s = &b->sides[ 0 ];
	contentFlags = s->contentFlags;
	compileFlags = s->compileFlags;
	b->contentShader = s->shaderInfo;
	mixed = qfalse;
	
	/* get the content/compile flags for every side in the brush */
	for( i = 1; i < b->numsides; i++, s++ )
	{
		s = &b->sides[ i ];
		if( s->shaderInfo == NULL )
			continue;
		if( s->contentFlags != contentFlags || s->compileFlags != compileFlags )
			mixed = qtrue;
	}
	
	/* ydnar: getting rid of this stupid warning */
	//%	if( mixed )
	//%		Sys_FPrintf( SYS_VRB,"Entity %i, Brush %i: mixed face contentFlags\n", b->entitynum, b->brushnum );

	/* check for detail & structural */
	if( (compileFlags & C_DETAIL) && (compileFlags & C_STRUCTURAL) )
	{
		xml_Select( "Mixed detail and structural (defaulting to structural)", mapEnt->mapEntityNum, entitySourceBrushes, qfalse );
		compileFlags &= ~C_DETAIL;
	}
	
	/* the fulldetail flag will cause detail brushes to be treated like normal brushes */
	if( fulldetail )
		compileFlags &= ~C_DETAIL;
	
	/* all translucent brushes that aren't specifically made structural will be detail */
	if( (compileFlags & C_TRANSLUCENT) && !(compileFlags & C_STRUCTURAL) )
		compileFlags |= C_DETAIL;
	
	/* detail? */
	if( compileFlags & C_DETAIL )
	{
		c_detail++;
		b->detail = qtrue;
	}
	else
	{
		c_structural++;
		b->detail = qfalse;
	}
	
	/* opaque? */
	if( compileFlags & C_TRANSLUCENT )
		b->opaque = qfalse;
	else
		b->opaque = qtrue;
	
	/* areaportal? */
	if( compileFlags & C_AREAPORTAL )
		c_areaportals++;
	
	/* set brush flags */
	b->contentFlags = contentFlags;
	b->compileFlags = compileFlags;
}



/*
AddBrushBevels()
adds any additional planes necessary to allow the brush being
built to be expanded against axial bounding boxes
ydnar 2003-01-20: added mrelusive fixes
*/

void AddBrushBevels( void )
{
	int			axis, dir;
	int			i, j, k, l, order;
	side_t		sidetemp;
	side_t		*s, *s2;
	winding_t	*w, *w2;
	vec3_t		normal;
	float		dist;
	vec3_t		vec, vec2;
	float		d, minBack;

	//
	// add the axial planes
	//
	order = 0;
	for ( axis = 0; axis < 3; axis++ ) {
		for ( dir = -1; dir <= 1; dir += 2, order++ ) {
			// see if the plane is allready present
			for ( i = 0, s = buildBrush->sides; i < buildBrush->numsides; i++, s++ )
			{
				/* ydnar: testing disabling of mre code */
				#if 0
					if ( dir > 0 ) {
						if ( mapplanes[s->planenum].normal[axis] >= 0.9999f ) {
							break;
						}
					}
					else {
						if ( mapplanes[s->planenum].normal[axis] <= -0.9999f ) {
							break;
						}
					}
				#else
					if( (dir > 0 && mapplanes[ s->planenum ].normal[ axis ] == 1.0f ) ||
						(dir < 0 && mapplanes[ s->planenum ].normal[ axis ] == -1.0f) )
						break;
				#endif
			}

			if ( i == buildBrush->numsides ) {
				// add a new side
				if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
					xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue);
				}
				memset( s, 0, sizeof( *s ) );
				buildBrush->numsides++;
				VectorClear (normal);
				normal[axis] = dir;

				if( dir == 1 )
				{
					/* ydnar: adding bevel plane snapping for fewer bsp planes */
					if( bevelSnap > 0 )
						dist = floor( buildBrush->maxs[ axis ] / bevelSnap ) * bevelSnap;
					else
						dist = buildBrush->maxs[ axis ];
				}
				else
				{
					/* ydnar: adding bevel plane snapping for fewer bsp planes */
					if( bevelSnap > 0 )
						dist = -ceil( buildBrush->mins[ axis ] / bevelSnap ) * bevelSnap;
					else
						dist = -buildBrush->mins[ axis ];
				}

				s->planenum = FindFloatPlane( normal, dist, 0, NULL );
				s->contentFlags = buildBrush->sides[ 0 ].contentFlags;
				s->bevel = qtrue;
				c_boxbevels++;
			}

			// if the plane is not in it canonical order, swap it
			if ( i != order ) {
				sidetemp = buildBrush->sides[order];
				buildBrush->sides[order] = buildBrush->sides[i];
				buildBrush->sides[i] = sidetemp;
			}
		}
	}

	//
	// add the edge bevels
	//
	if ( buildBrush->numsides == 6 ) {
		return;		// pure axial
	}

	// test the non-axial plane edges
	for ( i = 6; i < buildBrush->numsides; i++ ) {
		s = buildBrush->sides + i;
		w = s->winding;
		if ( !w ) {
			continue;
		}
		for ( j = 0; j < w->numpoints; j++) {
			k = (j+1)%w->numpoints;
			VectorSubtract( w->p[j], w->p[k], vec );
			if ( VectorNormalize( vec, vec ) < 0.5f ) {
				continue;
			}
			SnapNormal( vec );
			for ( k = 0; k < 3; k++ ) {
				if ( vec[k] == -1.0f || vec[k] == 1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f) ) {
					break;	// axial
				}
			}
			if ( k != 3 ) {
				continue;	// only test non-axial edges
			}

			/* debug code */
			//%	Sys_Printf( "-------------\n" );

			// try the six possible slanted axials from this edge
			for ( axis = 0; axis < 3; axis++ ) {
				for ( dir = -1; dir <= 1; dir += 2 ) {
					// construct a plane
					VectorClear( vec2 );
					vec2[axis] = dir;
					CrossProduct( vec, vec2, normal );
					if ( VectorNormalize( normal, normal ) < 0.5f ) {
						continue;
					}
					dist = DotProduct( w->p[j], normal );
					
					// if all the points on all the sides are
					// behind this plane, it is a proper edge bevel
					for ( k = 0; k < buildBrush->numsides; k++ ) {

						// if this plane has allready been used, skip it
						if ( PlaneEqual( &mapplanes[buildBrush->sides[k].planenum], normal, dist ) ) {
							break;
						}

						w2 = buildBrush->sides[k].winding;
						if ( !w2 ) {
							continue;
						}
						minBack = 0.0f;
						for ( l = 0; l < w2->numpoints; l++ ) {
							d = DotProduct( w2->p[l], normal ) - dist;
							if ( d > 0.1f ) {
								break;	// point in front
							}
							if ( d < minBack ) {
								minBack = d;
							}
						}
						// if some point was at the front
						if ( l != w2->numpoints ) {
							break;
						}

						// if no points at the back then the winding is on the bevel plane
						if ( minBack > -0.1f ) {
							//%	Sys_Printf( "On bevel plane\n" );
							break;
						}
					}

					if ( k != buildBrush->numsides ) {
						continue;	// wasn't part of the outer hull
					}
					
					/* debug code */
					//%	Sys_Printf( "n = %f %f %f\n", normal[ 0 ], normal[ 1 ], normal[ 2 ] );
					
					// add this plane
					if( buildBrush->numsides == MAX_BUILD_SIDES ) {
						xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue);
					}
					s2 = &buildBrush->sides[buildBrush->numsides];
					buildBrush->numsides++;
					memset( s2, 0, sizeof( *s2 ) );

					s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[ j ] );
					s2->contentFlags = buildBrush->sides[0].contentFlags;
					s2->bevel = qtrue;
					c_edgebevels++;
				}
			}
		}
	}
}



/*
FinishBrush()
produces a final brush based on the buildBrush->sides array
and links it to the current entity
*/

brush_t *FinishBrush( void )
{
	brush_t		*b;
	
	
	/* create windings for sides and bounds for brush */
	if ( !CreateBrushWindings( buildBrush ) )
		return NULL;

	/* origin brushes are removed, but they set the rotation origin for the rest of the brushes in the entity.
	   after the entire entity is parsed, the planenums and texinfos will be adjusted for the origin brush */
	if( buildBrush->compileFlags & C_ORIGIN )
	{
		char	string[ 32 ];
		vec3_t	origin;

		if( numEntities == 1 )
		{
			Sys_Printf( "Entity %i, Brush %i: origin brushes not allowed in world\n", 
				mapEnt->mapEntityNum, entitySourceBrushes );
			return NULL;
		}
		
		VectorAdd (buildBrush->mins, buildBrush->maxs, origin);
		VectorScale (origin, 0.5, origin);

		sprintf( string, "%i %i %i", (int) origin[ 0 ], (int) origin[ 1 ], (int) origin[ 2 ] );
		SetKeyValue( &entities[ numEntities - 1 ], "origin", string);

		VectorCopy( origin, entities[ numEntities - 1 ].origin);

		/* don't keep this brush */
		return NULL;
	}
	
	/* determine if the brush is an area portal */
	if( buildBrush->compileFlags & C_AREAPORTAL )
	{
		if( numEntities != 1 )
		{
			Sys_Printf ("Entity %i, Brush %i: areaportals only allowed in world\n", numEntities - 1, entitySourceBrushes );
			return NULL;
		}
	}
	
	/* add bevel planes */
	AddBrushBevels();
	
	/* keep it */
	b = CopyBrush( buildBrush );
	
	/* set map entity and brush numbering */
	b->entityNum = mapEnt->mapEntityNum;
	b->brushNum = entitySourceBrushes;
	
	/* set original */
	b->original = b;
	
	/* link opaque brushes to head of list, translucent brushes to end */
	if( b->opaque || mapEnt->lastBrush == NULL )
	{
		b->next = mapEnt->brushes;
		mapEnt->brushes = b;
		if( mapEnt->lastBrush == NULL )
			mapEnt->lastBrush = b;
	}
	else
	{
		b->next = NULL;
		mapEnt->lastBrush->next = b;
		mapEnt->lastBrush = b;
	}
	
	/* link colorMod volume brushes to the entity directly */
	if( b->contentShader != NULL &&
		b->contentShader->colorMod != NULL &&
		b->contentShader->colorMod->type == CM_VOLUME )
	{
		b->nextColorModBrush = mapEnt->colorModBrushes;
		mapEnt->colorModBrushes = b;
	}
	
	/* return to sender */
	return b;
}



/*
TextureAxisFromPlane()
determines best orthagonal axis to project a texture onto a wall
(must be identical in radiant!)
*/

vec3_t	baseaxis[18] =
{
	{0,0,1}, {1,0,0}, {0,-1,0},			// floor
	{0,0,-1}, {1,0,0}, {0,-1,0},		// ceiling
	{1,0,0}, {0,1,0}, {0,0,-1},			// west wall
	{-1,0,0}, {0,1,0}, {0,0,-1},		// east wall
	{0,1,0}, {1,0,0}, {0,0,-1},			// south wall
	{0,-1,0}, {1,0,0}, {0,0,-1}			// north wall
};

void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv )
{
	int		bestaxis;
	vec_t	dot,best;
	int		i;
	
	best = 0;
	bestaxis = 0;
	
	for (i=0 ; i<6 ; i++)
	{
		dot = DotProduct (pln->normal, baseaxis[i*3]);
		if( dot > best + 0.0001f )	/* ydnar: bug 637 fix, suggested by jmonroe */
		{
			best = dot;
			bestaxis = i;
		}
	}
	
	VectorCopy (baseaxis[bestaxis*3+1], xv);
	VectorCopy (baseaxis[bestaxis*3+2], yv);
}



/*
QuakeTextureVecs()
creates world-to-texture mapping vecs for crappy quake plane arrangements
*/

void QuakeTextureVecs( plane_t *plane, vec_t shift[ 2 ], vec_t rotate, vec_t scale[ 2 ], vec_t mappingVecs[ 2 ][ 4 ] )
{
	vec3_t	vecs[2];
	int		sv, tv;
	vec_t	ang, sinv, cosv;
	vec_t	ns, nt;
	int		i, j;
	
	
	TextureAxisFromPlane(plane, vecs[0], vecs[1]);
	
	if (!scale[0])
		scale[0] = 1;
	if (!scale[1])
		scale[1] = 1;

	// rotate axis
	if (rotate == 0)
		{ sinv = 0 ; cosv = 1; }
	else if (rotate == 90)
		{ sinv = 1 ; cosv = 0; }
	else if (rotate == 180)
		{ sinv = 0 ; cosv = -1; }
	else if (rotate == 270)
		{ sinv = -1 ; cosv = 0; }
	else
	{	
		ang = rotate / 180 * Q_PI;
		sinv = sin(ang);
		cosv = cos(ang);
	}

	if (vecs[0][0])
		sv = 0;
	else if (vecs[0][1])
		sv = 1;
	else
		sv = 2;
				
	if (vecs[1][0])
		tv = 0;
	else if (vecs[1][1])
		tv = 1;
	else
		tv = 2;
					
	for (i=0 ; i<2 ; i++) {
		ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
		nt = sinv * vecs[i][sv] +  cosv * vecs[i][tv];
		vecs[i][sv] = ns;
		vecs[i][tv] = nt;
	}

	for (i=0 ; i<2 ; i++)
		for (j=0 ; j<3 ; j++)
			mappingVecs[i][j] = vecs[i][j] / scale[i];

	mappingVecs[0][3] = shift[0];
	mappingVecs[1][3] = shift[1];
}



/*
ParseRawBrush()
parses the sides into buildBrush->sides[], nothing else.
no validation, back plane removal, etc.

Timo - 08/26/99
added brush epairs parsing ( ignoring actually )
Timo - 08/04/99
added exclusive brush primitive parsing
Timo - 08/08/99
support for old brush format back in
NOTE: it would be "cleaner" to have seperate functions to parse between old and new brushes
*/

static void ParseRawBrush( qboolean onlyLights )
{
	side_t			*side;
	vec3_t			planePoints[ 3 ];
	int				planenum;
	shaderInfo_t	*si;
	vec_t			shift[ 2 ];
	vec_t			rotate;
	vec_t			scale[ 2 ];
	char			name[ MAX_QPATH ];
	char			shader[ MAX_QPATH ];
	int				flags;
	
	
	/* initial setup */
	buildBrush->numsides = 0;
	buildBrush->detail = qfalse;
	
	/* bp */
	if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES )
		MatchToken( "{" );
	
	/* parse sides */
	while( 1 )
	{
		if( !GetToken( qtrue ) )
			break;
		if( !strcmp( token, "}" ) )
			break;
		 
		/* ttimo : bp: here we may have to jump over brush epairs (only used in editor) */
		if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES )
		{
			while( 1 )
			{
				if( strcmp( token, "(" ) )
					GetToken( qfalse );
				else
					break;
				GetToken( qtrue );
			}
		}
		UnGetToken();
		
		/* test side count */
		if( buildBrush->numsides >= MAX_BUILD_SIDES )
			xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue );
		
		/* add side */
		side = &buildBrush->sides[ buildBrush->numsides ];
		memset( side, 0, sizeof( *side ) );
		buildBrush->numsides++;
		
		/* read the three point plane definition */
		Parse1DMatrix( 3, planePoints[ 0 ] );
		Parse1DMatrix( 3, planePoints[ 1 ] );
		Parse1DMatrix( 3, planePoints[ 2 ] );
		
		/* bp: read the texture matrix */
		if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES )
			Parse2DMatrix( 2, 3, (float*) side->texMat );
		
		/* read shader name */
		GetToken( qfalse );
		strcpy( name, token );
		
		/* bp */
		if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES )
		{
			GetToken( qfalse );
			shift[ 0 ] = atof( token );
			GetToken( qfalse );
			shift[ 1 ] = atof( token );
			GetToken( qfalse );
			rotate = atof( token );	
			GetToken( qfalse );
			scale[ 0 ] = atof( token );
			GetToken( qfalse );
			scale[ 1 ] = atof( token );
		}
		
		/* set default flags and values */
		sprintf( shader, "textures/%s", name );
		if( onlyLights )
			si = &shaderInfo[ 0 ];
		else
			si = ShaderInfoForShader( shader );
		side->shaderInfo = si;
		side->surfaceFlags = si->surfaceFlags;
		side->contentFlags = si->contentFlags;
		side->compileFlags = si->compileFlags;
		side->value = si->value;
		
		/* ydnar: gs mods: bias texture shift */
		if( si->globalTexture == qfalse )
		{
			shift[ 0 ] -= (floor( shift[ 0 ] / si->shaderWidth ) * si->shaderWidth);
			shift[ 1 ] -= (floor( shift[ 1 ] / si->shaderHeight ) * si->shaderHeight);
		}
		
		/*
			historically, there are 3 integer values at the end of a brushside line in a .map file.
			in quake 3, the only thing that mattered was the first of these three values, which
			was previously the content flags. and only then did a single bit matter, the detail
			bit. because every game has its own special flags for specifying detail, the
			traditionally game-specified CONTENTS_DETAIL flag was overridden for Q3Map 2.3.0
			by C_DETAIL, defined in q3map2.h. the value is exactly as it was before, but
			is stored in compileFlags, as opposed to contentFlags, for multiple-game
			portability. :sigh:
		*/
		
		if( TokenAvailable() )
		{
			/* get detail bit from map content flags */
			GetToken( qfalse );
			flags = atoi( token );
			if( flags & C_DETAIL )
				side->compileFlags |= C_DETAIL;
			
			/* historical */
			GetToken( qfalse );
			//% td.flags = atoi( token );
			GetToken( qfalse );
			//% td.value = atoi( token );
		}
		
		/* find the plane number */
		planenum = MapPlaneFromPoints( planePoints );
		side->planenum = planenum;
		
		/* bp: get the texture mapping for this texturedef / plane combination */
		if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES )
			QuakeTextureVecs( &mapplanes[ planenum ], shift, rotate, scale, side->vecs );
	}
	
	/* bp */
	if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES )
	{
		UnGetToken();
		MatchToken( "}" );
		MatchToken( "}" );
	}
}



/*
RemoveDuplicateBrushPlanes
returns false if the brush has a mirrored set of planes,
meaning it encloses no volume.
also removes planes without any normal
*/

qboolean RemoveDuplicateBrushPlanes( brush_t *b )
{
	int			i, j, k;
	side_t		*sides;

	sides = b->sides;

	for ( i = 1 ; i < b->numsides ; i++ ) {

		// check for a degenerate plane
		if ( sides[i].planenum == -1) {
		  xml_Select( "degenerate plane", b->entityNum, b->brushNum, qfalse );
			// remove it
			for ( k = i + 1 ; k < b->numsides ; k++ ) {
				sides[k-1] = sides[k];
			}
			b->numsides--;
			i--;
			continue;
		}

		// check for duplication and mirroring
		for ( j = 0 ; j < i ; j++ ) {
			if ( sides[i].planenum == sides[j].planenum ) {
			  xml_Select( "duplicate plane", b->entityNum, b->brushNum, qfalse );
				// remove the second duplicate
				for ( k = i + 1 ; k < b->numsides ; k++ ) {
					sides[k-1] = sides[k];
				}
				b->numsides--;
				i--;
				break;
			}

			if ( sides[i].planenum == (sides[j].planenum ^ 1) ) {
				// mirror plane, brush is invalid
			  xml_Select( "mirrored plane", b->entityNum, b->brushNum, qfalse );
				return qfalse;
			}
		}
	}
	return qtrue;
}



/*
ParseBrush()
parses a brush out of a map file and sets it up
*/

static void ParseBrush( qboolean onlyLights )
{
	brush_t	*b;
	
	
	/* parse the brush out of the map */
	ParseRawBrush( onlyLights );
	
	/* only go this far? */
	if( onlyLights )
		return;
	
	/* set some defaults */
	buildBrush->portalareas[ 0 ] = -1;
	buildBrush->portalareas[ 1 ] = -1;
	buildBrush->entityNum = numMapEntities - 1;
	buildBrush->brushNum = entitySourceBrushes;
	
	/* if there are mirrored planes, the entire brush is invalid */
	if( !RemoveDuplicateBrushPlanes( buildBrush ) )
		return;
	
	/* get the content for the entire brush */
	SetBrushContents( buildBrush );
	
	/* allow detail brushes to be removed */
	if( nodetail && (buildBrush->compileFlags & C_DETAIL) )
	{
		//%	FreeBrush( buildBrush );
		return;
	}
	
	/* allow liquid brushes to be removed */
	if( nowater && (buildBrush->compileFlags & C_LIQUID ) )
	{
		//%	FreeBrush( buildBrush );
		return;
	}
	
	/* ydnar: allow hint brushes to be removed */
	if( noHint && (buildBrush->compileFlags & C_HINT) )
	{
		//%	FreeBrush( buildBrush );
		return;
	}
	
	/* finish the brush */
	b = FinishBrush();
}



/*
MoveBrushesToWorld()
takes all of the brushes from the current entity and
adds them to the world's brush list
(used by func_group)
*/

void MoveBrushesToWorld( entity_t *ent )
{
	brush_t		*b, *next;
	parseMesh_t	*pm;

	
	/* move brushes */
	for( b = ent->brushes; b != NULL; b = next )
	{
		/* get next brush */
		next = b->next;
		
		/* link opaque brushes to head of list, translucent brushes to end */
		if( b->opaque || entities[ 0 ].lastBrush == NULL )
		{
			b->next = entities[ 0 ].brushes;
			entities[ 0 ].brushes = b;
			if( entities[ 0 ].lastBrush == NULL )
				entities[ 0 ].lastBrush = b;
		}
		else
		{
			b->next = NULL;
			entities[ 0 ].lastBrush->next = b;
			entities[ 0 ].lastBrush = b;
		}
	}
	ent->brushes = NULL;
	
	/* ydnar: move colormod brushes */
	if( ent->colorModBrushes != NULL )
	{
		for( b = ent->colorModBrushes; b->nextColorModBrush != NULL; b = b->nextColorModBrush );
		
		b->nextColorModBrush = entities[ 0 ].colorModBrushes;
		entities[ 0 ].colorModBrushes = ent->colorModBrushes;
		
		ent->colorModBrushes = NULL;
	}
	
	/* move patches */
	if( ent->patches != NULL )
	{
		for( pm = ent->patches; pm->next != NULL; pm = pm->next );
		
		pm->next = entities[ 0 ].patches;
		entities[ 0 ].patches = ent->patches;
		
		ent->patches = NULL;
	}
}



/*
AdjustBrushesForOrigin()
*/

void AdjustBrushesForOrigin( entity_t *ent )
{
	
	int			i;
	side_t		*s;
	vec_t		newdist;
	brush_t		*b;
	parseMesh_t	*p;
	
	
	/* walk brush list */
	for( b = ent->brushes; b != NULL; b = b->next )
	{
		/* offset brush planes */
		for( i = 0; i < b->numsides; i++)
		{
			/* get brush side */
			s = &b->sides[ i ];
			
			/* offset side plane */
			newdist = mapplanes[ s->planenum ].dist - DotProduct( mapplanes[ s->planenum ].normal, ent->origin );
			
			/* find a new plane */
			s->planenum = FindFloatPlane( mapplanes[ s->planenum ].normal, newdist, 0, NULL );
		}
		
		/* rebuild brush windings (ydnar: just offsetting the winding above should be fine) */
		CreateBrushWindings( b );
	}
	
	/* walk patch list */
	for( p = ent->patches; p != NULL; p = p->next )
	{
		for( i = 0; i < (p->mesh.width * p->mesh.height); i++ )
			VectorSubtract( p->mesh.verts[ i ].xyz, ent->origin, p->mesh.verts[ i ].xyz );
	}
}



/*
SetEntityBounds() - ydnar
finds the bounds of an entity's brushes (necessary for terrain-style generic metashaders)
*/

void SetEntityBounds( entity_t *e )
{
	int			i;
	brush_t	*b;
	parseMesh_t	*p;
	vec3_t		mins, maxs;
	const char	*value;

	
	

	/* walk the entity's brushes/patches and determine bounds */
	ClearBounds( mins, maxs );
	for( b = e->brushes; b; b = b->next )
	{
		AddPointToBounds( b->mins, mins, maxs );
		AddPointToBounds( b->maxs, mins, maxs );
	}
	for( p = e->patches; p; p = p->next )
	{
		for( i = 0; i < (p->mesh.width * p->mesh.height); i++ )
			AddPointToBounds( p->mesh.verts[ i ].xyz, mins, maxs );
	}
	
	/* try to find explicit min/max key */
	value = ValueForKey( e, "min" ); 
	if( value[ 0 ] != '\0' )
		GetVectorForKey( e, "min", mins );
	value = ValueForKey( e, "max" ); 
	if( value[ 0 ] != '\0' )
		GetVectorForKey( e, "max", maxs );
	
	/* store the bounds */
	for( b = e->brushes; b; b = b->next )
	{
		VectorCopy( mins, b->eMins );
		VectorCopy( maxs, b->eMaxs );
	}
	for( p = e->patches; p; p = p->next )
	{
		VectorCopy( mins, p->eMins );
		VectorCopy( maxs, p->eMaxs );
	}
}



/*
LoadEntityIndexMap() - ydnar
based on LoadAlphaMap() from terrain.c, a little more generic
*/

void LoadEntityIndexMap( entity_t *e )
{
	int				i, size, numLayers, w, h;
	const char		*value, *indexMapFilename, *shader;
	char			ext[ MAX_QPATH ], offset[ 4096 ], *search, *space;
	byte			*pixels;
	unsigned int	*pixels32;
	indexMap_t		*im;
	brush_t			*b;
	parseMesh_t		*p;
	
	
	/* this only works with bmodel ents */
	if( e->brushes == NULL && e->patches == NULL )
		return;
	
	/* determine if there is an index map (support legacy "alphamap" key as well) */
	value = ValueForKey( e, "_indexmap" );
	if( value[ 0 ] == '\0' )
		value = ValueForKey( e, "alphamap" );
	if( value[ 0 ] == '\0' )
		return;
	indexMapFilename = value;
	
	/* get number of layers (support legacy "layers" key as well) */
	value = ValueForKey( e, "_layers" );
	if( value[ 0 ] == '\0' )
		value = ValueForKey( e, "layers" );
	if( value[ 0 ] == '\0' )
	{
		Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_layers\" or \"layers\" key\n", indexMapFilename );
		Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
		return;
	}
	numLayers = atoi( value );
	if( numLayers < 1 )
	{
		Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has < 1 layer (%d)\n", indexMapFilename, numLayers );
		Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
		return;
	}
	
	/* get base shader name (support legacy "shader" key as well) */
	value = ValueForKey( mapEnt, "_shader" );
	if( value[ 0 ] == '\0' )
		value = ValueForKey( e, "shader" );
	if( value[ 0 ] == '\0' )
	{
		Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_shader\" or \"shader\" key\n", indexMapFilename );
		Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
		return;
	}
	shader = value;
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "Entity %d (%s) has shader index map \"%s\"\n",  mapEnt->mapEntityNum, ValueForKey( e, "classname" ), indexMapFilename );
	
	/* get index map file extension */
	ExtractFileExtension( indexMapFilename, ext );
	
	/* handle tga image */
	if( !Q_stricmp( ext, "tga" ) )
	{
		/* load it */
		Load32BitImage( indexMapFilename, &pixels32, &w, &h );
		
		/* convert to bytes */
		size = w * h;
		pixels = safe_malloc( size );
		for( i = 0; i < size; i++ )
		{
			pixels[ i ] = ((pixels32[ i ] & 0xFF) * numLayers) / 256;
			if( pixels[ i ] >= numLayers )
				pixels[ i ] = numLayers - 1;
		}
		
		/* free the 32 bit image */
		free( pixels32 );
	}
	else
	{
		/* load it */
		Load256Image( indexMapFilename, &pixels, NULL, &w, &h );
		
		/* debug code */
		//%	Sys_Printf( "-------------------------------" );
		
		/* fix up out-of-range values */
		size = w * h;
		for( i = 0; i < size; i++ )
		{
			if( pixels[ i ] >= numLayers )
				pixels[ i ] = numLayers - 1;
			
			/* debug code */
			//%	if( (i % w) == 0 )
			//%		Sys_Printf( "\n" );
			//%	Sys_Printf( "%c", pixels[ i ] + '0' );
		}
		
		/* debug code */
		//%	Sys_Printf( "\n-------------------------------\n" );
	}
	
	/* the index map must be at least 2x2 pixels */
	if( w < 2 || h < 2 )
	{
		Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" is smaller than 2x2 pixels\n", indexMapFilename );
		Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
		free( pixels );
		return;
	}

	/* create a new index map */
	im = safe_malloc( sizeof( *im ) );
	memset( im, 0, sizeof( *im ) );
	
	/* set it up */
	im->w = w;
	im->h = h;
	im->numLayers = numLayers;
	strcpy( im->name, indexMapFilename );
	strcpy( im->shader, shader );
	im->pixels = pixels;
	
	/* get height offsets */
	value = ValueForKey( mapEnt, "_offsets" );
	if( value[ 0 ] == '\0' )
		value = ValueForKey( e, "offsets" );
	if( value[ 0 ] != '\0' )
	{
		/* value is a space-seperated set of numbers */
		strcpy( offset, value );
		search = offset;
		
		/* get each value */
		for( i = 0; i < 256 && *search != '\0'; i++ )
		{
			space = strstr( search, " " );
			if( space != NULL )
				*space = '\0';
			im->offsets[ i ] = atof( search );
			if( space == NULL )
				break;
			search = space + 1;
		}
	}
	
	/* store the index map in every brush/patch in the entity */
	for( b = e->brushes; b != NULL; b = b->next )
		b->im = im;
	for( p = e->patches; p != NULL; p = p->next )
		p->im = im;
}







/*
ParseMapEntity()
parses a single entity out of a map file
*/

static qboolean ParseMapEntity( qboolean onlyLights )
{
	epair_t			*ep;
	const char		*classname, *value;
	float			lightmapScale;
	char			shader[ MAX_QPATH ];
	shaderInfo_t	*celShader = NULL;
	brush_t			*brush;
	parseMesh_t		*patch;
	qboolean		funcGroup;
	int				castShadows, recvShadows;
	
	
	/* eof check */
	if( !GetToken( qtrue ) )
		return qfalse;
	
	/* conformance check */
	if( strcmp( token, "{" ) )
	{
		Sys_Printf( "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n"
			"Continuing to process map, but resulting BSP may be invalid.\n",
			token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] );
		return qfalse;
	}
	
	/* range check */
	if( numEntities >= MAX_MAP_ENTITIES )
		Error( "numEntities == MAX_MAP_ENTITIES" );
	
	/* setup */
	entitySourceBrushes = 0;
	mapEnt = &entities[ numEntities ];
	numEntities++;
	memset( mapEnt, 0, sizeof( *mapEnt ) );
	
	/* ydnar: true entity numbering */
	mapEnt->mapEntityNum = numMapEntities;
	numMapEntities++;
	
	/* loop */
	while( 1 )
	{
		/* get initial token */
		if( !GetToken( qtrue ) )
		{
			Sys_Printf( "WARNING: ParseEntity: EOF without closing brace\n"
				"Continuing to process map, but resulting BSP may be invalid.\n" );
			return qfalse;
		}
		
		if( !strcmp( token, "}" ) )
			break;
		
		if( !strcmp( token, "{" ) )
		{
			/* parse a brush or patch */
			if( !GetToken( qtrue ) )
				break;
			
			/* check */
			if( !strcmp( token, "patchDef2" ) )
			{
				numMapPatches++;
				ParsePatch( onlyLights );
			}
			else if( !strcmp( token, "terrainDef" ) )
			{
				//% ParseTerrain();
				Sys_Printf( "WARNING: Terrain entity parsing not supported in this build.\n" );	/* ydnar */
			}
			else if( !strcmp( token, "brushDef" ) )
			{
				if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES )
					Error( "Old brush format not allowed in new brush format map" );
				g_bBrushPrimit = BPRIMIT_NEWBRUSHES;
				
				/* parse brush primitive */
				ParseBrush( onlyLights );
			}
			else
			{
				if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES )
					Error( "New brush format not allowed in old brush format map" );
				g_bBrushPrimit = BPRIMIT_OLDBRUSHES;
				
				/* parse old brush format */
				UnGetToken();
				ParseBrush( onlyLights );
			}
			entitySourceBrushes++;
		}
		else
		{
			/* parse a key / value pair */
			ep = ParseEPair();
			
			/* ydnar: 2002-07-06 fixed wolf bug with empty epairs */
			if( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' )
			{
				ep->next = mapEnt->epairs;
				mapEnt->epairs = ep;
			}
		}
	}
	
	/* ydnar: get classname */
	classname = ValueForKey( mapEnt, "classname" );
	
	/* ydnar: only lights? */
	if( onlyLights && Q_strncasecmp( classname, "light", 5 ) )
	{
		numEntities--;
		return qtrue;
	}
	
	/* ydnar: determine if this is a func_group */
	if( !Q_stricmp( "func_group", classname ) )
		funcGroup = qtrue;
	else
		funcGroup = qfalse;
	
	/* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */
	if( funcGroup || mapEnt->mapEntityNum == 0 )
	{
		//%	Sys_Printf( "World:  %d\n", mapEnt->mapEntityNum );
		castShadows = WORLDSPAWN_CAST_SHADOWS;
		recvShadows = WORLDSPAWN_RECV_SHADOWS;
	}
	
	/* other entities don't cast any shadows, but recv worldspawn shadows */
	else
	{
		//%	Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum );
		castShadows = ENTITY_CAST_SHADOWS;
		recvShadows = ENTITY_RECV_SHADOWS;
	}
	
	/* get explicit shadow flags */
	GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows );
	
	/* ydnar: get lightmap scaling value for this entity */
	if( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) ||
		strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) )
	{
		/* get lightmap scale from entity */
		lightmapScale = FloatForKey( mapEnt, "lightmapscale" );
		if( lightmapScale <= 0.0f )
			lightmapScale = FloatForKey( mapEnt, "_lightmapscale" );
		if( lightmapScale > 0.0f )
			Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale );
	}
	else
		lightmapScale = 0.0f;
	
	/* ydnar: get cel shader :) for this entity */
	value = ValueForKey( mapEnt, "_celshader" );
	if( value[ 0 ] == '\0' )	
		value = ValueForKey( &entities[ 0 ], "_celshader" );
	if( value[ 0 ] != '\0' )
	{
		sprintf( shader, "textures/%s", value );
		celShader = ShaderInfoForShader( shader );
		Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader );
	}
	else
		celShader = NULL;
	
	/* attach stuff to everything in the entity */
	for( brush = mapEnt->brushes; brush != NULL; brush = brush->next )
	{
		brush->entityNum = mapEnt->mapEntityNum;
		brush->castShadows = castShadows;
		brush->recvShadows = recvShadows;
		brush->lightmapScale = lightmapScale;
		brush->celShader = celShader;
	}
	
	for( patch = mapEnt->patches; patch != NULL; patch = patch->next )
	{
		patch->entityNum = mapEnt->mapEntityNum;
		patch->castShadows = castShadows;
		patch->recvShadows = recvShadows;
		patch->lightmapScale = lightmapScale;
		patch->celShader = celShader;
	}
	
	/* ydnar: gs mods: set entity bounds */
	SetEntityBounds( mapEnt );
	
	/* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */
	LoadEntityIndexMap( mapEnt );
	
	/* get entity origin and adjust brushes */
	GetVectorForKey( mapEnt, "origin", mapEnt->origin );
	if( mapEnt->origin[ 0 ] || mapEnt->origin[ 1 ] || mapEnt->origin[ 2 ] )
		AdjustBrushesForOrigin( mapEnt );

	/* group_info entities are just for editor grouping (fixme: leak!) */
	if( !Q_stricmp( "group_info", classname ) )
	{
		numEntities--;
		return qtrue;
	}
	
	/* group entities are just for editor convenience, toss all brushes into worldspawn */
	if( funcGroup )
	{
		MoveBrushesToWorld( mapEnt );
		numEntities--;
		return qtrue;
	}
	
	/* done */
	return qtrue;
}



/*
LoadMapFile()
loads a map file into a list of entities
*/

void LoadMapFile( char *filename, qboolean onlyLights )
{		
	FILE		*file;
	brush_t		*b;
	int			oldNumEntities, numMapBrushes;
	
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" );
	Sys_Printf( "Loading %s\n", filename );
	
	/* hack */
	file = SafeOpenRead( filename );
	fclose( file );
	
	/* load the map file */
	LoadScriptFile( filename, -1 );
	
	/* setup */
	if( onlyLights )
		oldNumEntities = numEntities;
	else
		numEntities = 0;
	
	/* initial setup */
	numMapDrawSurfs = 0;
	c_detail = 0;
	g_bBrushPrimit = BPRIMIT_UNDEFINED;
	
	/* allocate a very large temporary brush for building the brushes as they are loaded */
	buildBrush = AllocBrush( MAX_BUILD_SIDES );
	
	/* parse the map file */
	while( ParseMapEntity( onlyLights ) );
	
	/* light loading */
	if( onlyLights )
	{
		/* emit some statistics */
		Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities );
	}
	else
	{
		/* set map bounds */
		ClearBounds( mapMins, mapMaxs );
		for( b = entities[ 0 ].brushes; b; b = b->next )
		{
			AddPointToBounds( b->mins, mapMins, mapMaxs );
			AddPointToBounds( b->maxs, mapMins, mapMaxs );
		}
		
		/* get brush counts */
		numMapBrushes = CountBrushList( entities[ 0 ].brushes );
		if( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 )
			Sys_Printf( "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" );
		
		/* emit some statistics */
		Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes );
		Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail );
		Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches);
		Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels);
		Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels);
		Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities );
		Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes);
		Sys_Printf( "%9d areaportals\n", c_areaportals);
		Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n",
			mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ],
			mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ]);
		
		/* write bogus map */
		if( fakemap )
			WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes );
	}
}
