/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

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

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

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

#include "qbsp.h"

extern qboolean onlyents;

int nummapbrushes;
mapbrush_t mapbrushes[MAX_MAP_BRUSHES];

int nummapbrushsides;
side_t brushsides[MAX_MAP_SIDES];
brush_texture_t side_brushtextures[MAX_MAP_SIDES];

int nummapplanes;
plane_t mapplanes[MAX_MAP_PLANES];

#define PLANE_HASHES    1024
plane_t     *planehash[PLANE_HASHES];

vec3_t map_mins, map_maxs;

// undefine to make plane finding use linear sort
#define USE_HASHING

void TestExpandBrushes( void );

int c_boxbevels;
int c_edgebevels;

int c_areaportals;

int c_clipbrushes;

/*
   =============================================================================

   PLANE FINDING

   =============================================================================
 */


/*
   =================
   PlaneTypeForNormal
   =================
 */
int PlaneTypeForNormal( vec3_t normal ){
	vec_t ax, ay, az;

// NOTE: should these have an epsilon around 1.0?
	if ( normal[0] == 1.0 || normal[0] == -1.0 ) {
		return PLANE_X;
	}
	if ( normal[1] == 1.0 || normal[1] == -1.0 ) {
		return PLANE_Y;
	}
	if ( normal[2] == 1.0 || normal[2] == -1.0 ) {
		return PLANE_Z;
	}

	ax = fabs( normal[0] );
	ay = fabs( normal[1] );
	az = fabs( normal[2] );

	if ( ax >= ay && ax >= az ) {
		return PLANE_ANYX;
	}
	if ( ay >= ax && ay >= az ) {
		return PLANE_ANYY;
	}
	return PLANE_ANYZ;
}

/*
   ================
   PlaneEqual
   ================
 */
#define NORMAL_EPSILON  0.00001
#define DIST_EPSILON    0.01
qboolean    PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ){
#if 1
	if (
		fabs( p->normal[0] - normal[0] ) < NORMAL_EPSILON
		&& fabs( p->normal[1] - normal[1] ) < NORMAL_EPSILON
		&& fabs( p->normal[2] - normal[2] ) < NORMAL_EPSILON
		&& fabs( p->dist - dist ) < DIST_EPSILON ) {
		return true;
	}
#else
	if ( p->normal[0] == normal[0]
		 && p->normal[1] == normal[1]
		 && p->normal[2] == normal[2]
		 && p->dist == dist ) {
		return true;
	}
#endif
	return false;
}

/*
   ================
   AddPlaneToHash
   ================
 */
void    AddPlaneToHash( plane_t *p ){
	int hash;

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

	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 ) {
		Error( "FloatPlane: bad normal" );
	}
	// 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;
}

/*
   ==============
   SnapVector
   ==============
 */
void    SnapVector( vec3_t normal ){
	int i;

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

/*
   ==============
   SnapPlane
   ==============
 */
void    SnapPlane( vec3_t normal, vec_t *dist ){
	SnapVector( normal );

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

/*
   =============
   FindFloatPlane

   =============
 */
#ifndef USE_HASHING
int     FindFloatPlane( vec3_t normal, vec_t dist ){
	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 );
}
#else
int     FindFloatPlane( vec3_t normal, vec_t dist ){
	int i;
	plane_t *p;
	int hash, h;

	SnapPlane( normal, &dist );
	hash = (int)fabs( dist ) / 8;
	hash &= ( PLANE_HASHES - 1 );

	// search the border bins as well
	for ( i = -1 ; i <= 1 ; i++ )
	{
		h = ( hash + i ) & ( PLANE_HASHES - 1 );
		for ( p = planehash[h] ; p ; p = p->hash_chain )
		{
			if ( PlaneEqual( p, normal, dist ) ) {
				return p - mapplanes;
			}
		}
	}

	return CreateNewFloatPlane( normal, dist );
}
#endif

/*
   ================
   PlaneFromPoints
   ================
 */
int PlaneFromPoints( int *p0, int *p1, int *p2 ){
	vec3_t t1, t2, normal;
	vec_t dist;

	VectorSubtract( p0, p1, t1 );
	VectorSubtract( p2, p1, t2 );
	CrossProduct( t1, t2, normal );
	VectorNormalize( normal, normal );

	dist = DotProduct( p0, normal );

	return FindFloatPlane( normal, dist );
}


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


/*
   ===========
   BrushContents
   ===========
 */
int BrushContents( mapbrush_t *b ){
	int contents;
	side_t      *s;
	int i;
	int trans;

	s = &b->original_sides[0];
	contents = s->contents;
	trans = texinfo[s->texinfo].flags;
	for ( i = 1 ; i < b->numsides ; i++, s++ )
	{
		s = &b->original_sides[i];
		trans |= texinfo[s->texinfo].flags;
		if ( s->contents != contents ) {
			Sys_Printf( "Entity %i, Brush %i: mixed face contents\n"
						, b->entitynum, b->brushnum );
			break;
		}
	}

	// if any side is translucent, mark the contents
	// and change solid to window
	if ( trans & ( SURF_TRANS33 | SURF_TRANS66 ) ) {
		contents |= CONTENTS_TRANSLUCENT;
		if ( contents & CONTENTS_SOLID ) {
			contents &= ~CONTENTS_SOLID;
			contents |= CONTENTS_WINDOW;
		}
	}

	return contents;
}


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

/*
   =================
   AddBrushBevels

   Adds any additional planes necessary to allow the brush to be expanded
   against axial bounding boxes
   =================
 */
void AddBrushBevels( mapbrush_t *b ){
	int axis, dir;
	int i, j, k, l, order;
	side_t sidetemp;
	brush_texture_t tdtemp;
	side_t  *s, *s2;
	vec3_t normal;
	float dist;
	winding_t   *w, *w2;
	vec3_t vec, vec2;
	float d;

	//
	// 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 = b->original_sides ; i < b->numsides ; i++,s++ )
			{
				if ( mapplanes[s->planenum].normal[axis] == dir ) {
					break;
				}
			}

			if ( i == b->numsides ) { // add a new side
				if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
					Error( "MAX_MAP_BRUSHSIDES" );
				}
				nummapbrushsides++;
				b->numsides++;
				VectorClear( normal );
				normal[axis] = dir;
				if ( dir == 1 ) {
					dist = b->maxs[axis];
				}
				else{
					dist = -b->mins[axis];
				}
				s->planenum = FindFloatPlane( normal, dist );
				s->texinfo = b->original_sides[0].texinfo;
				s->contents = b->original_sides[0].contents;
				s->bevel = true;
				c_boxbevels++;
			}

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

				j = b->original_sides - brushsides;
				tdtemp = side_brushtextures[j + order];
				side_brushtextures[j + order] = side_brushtextures[j + i];
				side_brushtextures[j + i] = tdtemp;
			}
		}
	}

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

	}
	// test the non-axial plane edges
	for ( i = 6 ; i < b->numsides ; i++ )
	{
		s = b->original_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.5 ) {
				continue;
			}
			SnapVector( vec );
			for ( k = 0 ; k < 3 ; k++ )
				if ( vec[k] == -1 || vec[k] == 1 ) {
					break;
				}           // axial
			if ( k != 3 ) {
				continue;   // only test non-axial edges

			}
			// 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.5 ) {
						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 < b->numsides ; k++ )
					{
						// if this plane has allready been used, skip it
						if ( PlaneEqual( &mapplanes[b->original_sides[k].planenum]
										 , normal, dist ) ) {
							break;
						}

						w2 = b->original_sides[k].winding;
						if ( !w2 ) {
							continue;
						}
						for ( l = 0 ; l < w2->numpoints ; l++ )
						{
							d = DotProduct( w2->p[l], normal ) - dist;
							if ( d > 0.1 ) {
								break;  // point in front
							}
						}
						if ( l != w2->numpoints ) {
							break;
						}
					}

					if ( k != b->numsides ) {
						continue;   // wasn't part of the outer hull
					}
					// add this plane
					if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
						Error( "MAX_MAP_BRUSHSIDES" );
					}
					nummapbrushsides++;
					s2 = &b->original_sides[b->numsides];
					s2->planenum = FindFloatPlane( normal, dist );
					s2->texinfo = b->original_sides[0].texinfo;
					s2->contents = b->original_sides[0].contents;
					s2->bevel = true;
					c_edgebevels++;
					b->numsides++;
				}
			}
		}
	}
}


/*
   ================
   MakeBrushWindings

   makes basewindigs for sides and mins / maxs for the brush
   ================
 */
qboolean MakeBrushWindings( mapbrush_t *ob ){
	int i, j;
	winding_t   *w;
	side_t      *side;
	plane_t     *plane;

	ClearBounds( ob->mins, ob->maxs );

	for ( i = 0 ; i < ob->numsides ; i++ )
	{
		plane = &mapplanes[ob->original_sides[i].planenum];
		w = BaseWindingForPlane( plane->normal, plane->dist );
		for ( j = 0 ; j < ob->numsides && w; j++ )
		{
			if ( i == j ) {
				continue;
			}
			if ( ob->original_sides[j].bevel ) {
				continue;
			}
			plane = &mapplanes[ob->original_sides[j].planenum ^ 1];
			ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON);
		}

		side = &ob->original_sides[i];
		side->winding = w;
		if ( w ) {
			side->visible = true;
			for ( j = 0 ; j < w->numpoints ; j++ )
				AddPointToBounds( w->p[j], ob->mins, ob->maxs );
		}
	}

	for ( i = 0 ; i < 3 ; i++ )
	{
		if ( ob->mins[0] < -4096 || ob->maxs[0] > 4096 ) {
			Sys_Printf( "entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum );
		}
		if ( ob->mins[0] > 4096 || ob->maxs[0] < -4096 ) {
			Sys_Printf( "entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum );
		}
	}

	return true;
}


/*
   =================
   ParseBrush
   =================
 */
void ParseBrush( entity_t *mapent ){
	mapbrush_t      *b;
	int i,j, k;
	int mt;
	side_t      *side, *s2;
	int planenum;
	brush_texture_t td;
	int planepts[3][3];

	if ( nummapbrushes == MAX_MAP_BRUSHES ) {
		Error( "nummapbrushes == MAX_MAP_BRUSHES" );
	}

	b = &mapbrushes[nummapbrushes];
	b->original_sides = &brushsides[nummapbrushsides];
	b->entitynum = num_entities - 1;
	b->brushnum = nummapbrushes - mapent->firstbrush;

	do
	{
		if ( !GetToken( true ) ) {
			break;
		}
		if ( !strcmp( token, "}" ) ) {
			break;
		}

		if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
			Error( "MAX_MAP_BRUSHSIDES" );
		}
		side = &brushsides[nummapbrushsides];

		// read the three point plane definition
		for ( i = 0 ; i < 3 ; i++ )
		{
			if ( i != 0 ) {
				GetToken( true );
			}
			if ( strcmp( token, "(" ) ) {
				Error( "parsing brush" );
			}

			for ( j = 0 ; j < 3 ; j++ )
			{
				GetToken( false );
				planepts[i][j] = atoi( token );
			}

			GetToken( false );
			if ( strcmp( token, ")" ) ) {
				Error( "parsing brush" );
			}

		}


		//
		// read the texturedef
		//
		GetToken( false );
		strcpy( td.name, token );

		GetToken( false );
		td.shift[0] = atoi( token );
		GetToken( false );
		td.shift[1] = atoi( token );
		GetToken( false );
		td.rotate = atoi( token );
		GetToken( false );
		td.scale[0] = atof( token );
		GetToken( false );
		td.scale[1] = atof( token );

		// find default flags and values
		mt = FindMiptex( td.name );
		td.flags = textureref[mt].flags;
		td.value = textureref[mt].value;
		side->contents = textureref[mt].contents;
		side->surf = td.flags = textureref[mt].flags;

		if ( TokenAvailable() ) {
			GetToken( false );
			side->contents = atoi( token );
			GetToken( false );
			side->surf = td.flags = atoi( token );
			GetToken( false );
			td.value = atoi( token );
		}

		// translucent objects are automatically classified as detail
		if ( side->surf & ( SURF_TRANS33 | SURF_TRANS66 ) ) {
			side->contents |= CONTENTS_DETAIL;
		}
		if ( side->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) {
			side->contents |= CONTENTS_DETAIL;
		}
		if ( fulldetail ) {
			side->contents &= ~CONTENTS_DETAIL;
		}
		if ( !( side->contents & ( ( LAST_VISIBLE_CONTENTS - 1 )
								   | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST )  ) ) {
			side->contents |= CONTENTS_SOLID;
		}

		// hints and skips are never detail, and have no content
		if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) {
			side->contents = 0;
			side->surf &= ~CONTENTS_DETAIL;
		}


		//
		// find the plane number
		//
		planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] );
		if ( planenum == -1 ) {
			Sys_Printf( "Entity %i, Brush %i: plane with no normal\n"
						, b->entitynum, b->brushnum );
			continue;
		}

		//
		// see if the plane has been used already
		//
		for ( k = 0 ; k < b->numsides ; k++ )
		{
			s2 = b->original_sides + k;
			if ( s2->planenum == planenum ) {
				Sys_Printf( "Entity %i, Brush %i: duplicate plane\n"
							, b->entitynum, b->brushnum );
				break;
			}
			if ( s2->planenum == ( planenum ^ 1 ) ) {
				Sys_Printf( "Entity %i, Brush %i: mirrored plane\n"
							, b->entitynum, b->brushnum );
				break;
			}
		}
		if ( k != b->numsides ) {
			continue;       // duplicated

		}
		//
		// keep this side
		//

		side = b->original_sides + b->numsides;
		side->planenum = planenum;
		side->texinfo = TexinfoForBrushTexture( &mapplanes[planenum],
												&td, vec3_origin );

		// save the td off in case there is an origin brush and we
		// have to recalculate the texinfo
		side_brushtextures[nummapbrushsides] = td;

		nummapbrushsides++;
		b->numsides++;
	} while ( 1 );

	// get the content for the entire brush
	b->contents = BrushContents( b );

	// allow detail brushes to be removed
	if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) {
		b->numsides = 0;
		return;
	}

	// allow water brushes to be removed
	if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) {
		b->numsides = 0;
		return;
	}

	// create windings for sides and bounds for brush
	MakeBrushWindings( b );

	// brushes that will not be visible at all will never be
	// used as bsp splitters
	if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) {
		c_clipbrushes++;
		for ( i = 0 ; i < b->numsides ; i++ )
			b->original_sides[i].texinfo = TEXINFO_NODE;
	}

	//
	// 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 ( b->contents & CONTENTS_ORIGIN ) {
		char string[32];
		vec3_t origin;

		if ( num_entities == 1 ) {
			Error( "Entity %i, Brush %i: origin brushes not allowed in world"
				   , b->entitynum, b->brushnum );
			return;
		}

		VectorAdd( b->mins, b->maxs, origin );
		VectorScale( origin, 0.5, origin );

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

		VectorCopy( origin, entities[b->entitynum].origin );

		// don't keep this brush
		b->numsides = 0;

		return;
	}

	AddBrushBevels( b );

	nummapbrushes++;
	mapent->numbrushes++;
}

/*
   ================
   MoveBrushesToWorld

   Takes all of the brushes from the current entity and
   adds them to the world's brush list.

   Used by func_group and func_areaportal
   ================
 */
void MoveBrushesToWorld( entity_t *mapent ){
	int newbrushes;
	int worldbrushes;
	mapbrush_t  *temp;
	int i;

	// this is pretty gross, because the brushes are expected to be
	// in linear order for each entity

	newbrushes = mapent->numbrushes;
	worldbrushes = entities[0].numbrushes;

	temp = malloc( newbrushes * sizeof( mapbrush_t ) );
	memcpy( temp, mapbrushes + mapent->firstbrush, newbrushes * sizeof( mapbrush_t ) );

#if 0       // let them keep their original brush numbers
	for ( i = 0 ; i < newbrushes ; i++ )
		temp[i].entitynum = 0;
#endif

	// make space to move the brushes (overlapped copy)
	memmove( mapbrushes + worldbrushes + newbrushes,
			 mapbrushes + worldbrushes,
			 sizeof( mapbrush_t ) * ( nummapbrushes - worldbrushes - newbrushes ) );

	// copy the new brushes down
	memcpy( mapbrushes + worldbrushes, temp, sizeof( mapbrush_t ) * newbrushes );

	// fix up indexes
	entities[0].numbrushes += newbrushes;
	for ( i = 1 ; i < num_entities ; i++ )
		entities[i].firstbrush += newbrushes;
	free( temp );

	mapent->numbrushes = 0;
}

/*
   ================
   ParseMapEntity
   ================
 */
qboolean    ParseMapEntity( void ){
	entity_t    *mapent;
	epair_t     *e;
	side_t      *s;
	int i, j;
	int startbrush, startsides;
	vec_t newdist;
	mapbrush_t  *b;

	if ( !GetToken( true ) ) {
		return false;
	}

	if ( strcmp( token, "{" ) ) {
		Error( "ParseEntity: { not found" );
	}

	if ( num_entities == MAX_MAP_ENTITIES ) {
		Error( "num_entities == MAX_MAP_ENTITIES" );
	}

	startbrush = nummapbrushes;
	startsides = nummapbrushsides;

	mapent = &entities[num_entities];
	num_entities++;
	memset( mapent, 0, sizeof( *mapent ) );
	mapent->firstbrush = nummapbrushes;
	mapent->numbrushes = 0;
//	mapent->portalareas[0] = -1;
//	mapent->portalareas[1] = -1;

	do
	{
		if ( !GetToken( true ) ) {
			Error( "ParseEntity: EOF without closing brace" );
		}
		if ( !strcmp( token, "}" ) ) {
			break;
		}
		if ( !strcmp( token, "{" ) ) {
			ParseBrush( mapent );
		}
		else
		{
			e = ParseEpair();
			e->next = mapent->epairs;
			mapent->epairs = e;
		}
	} while ( 1 );

	GetVectorForKey( mapent, "origin", mapent->origin );

	//
	// if there was an origin brush, offset all of the planes and texinfo
	//
	if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) {
		for ( i = 0 ; i < mapent->numbrushes ; i++ )
		{
			b = &mapbrushes[mapent->firstbrush + i];
			for ( j = 0 ; j < b->numsides ; j++ )
			{
				s = &b->original_sides[j];
				newdist = mapplanes[s->planenum].dist -
						  DotProduct( mapplanes[s->planenum].normal, mapent->origin );
				s->planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist );
				s->texinfo = TexinfoForBrushTexture( &mapplanes[s->planenum],
													 &side_brushtextures[s - brushsides], mapent->origin );
			}
			MakeBrushWindings( b );
		}
	}

	// group entities are just for editor convenience
	// toss all brushes into the world entity
	if ( !strcmp( "func_group", ValueForKey( mapent, "classname" ) ) ) {
		MoveBrushesToWorld( mapent );
		mapent->numbrushes = 0;
		return true;
	}

	// areaportal entities move their brushes, but don't eliminate
	// the entity
	if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) {
		char str[128];

		if ( mapent->numbrushes != 1 ) {
			Error( "Entity %i: func_areaportal can only be a single brush", num_entities - 1 );
		}

		b = &mapbrushes[nummapbrushes - 1];
		b->contents = CONTENTS_AREAPORTAL;
		c_areaportals++;
		mapent->areaportalnum = c_areaportals;
		// set the portal number as "style"
		sprintf( str, "%i", c_areaportals );
		SetKeyValue( mapent, "style", str );
		MoveBrushesToWorld( mapent );
		return true;
	}

	return true;
}

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

/*
   ================
   LoadMapFile
   ================
 */
void LoadMapFile( char *filename ){
	int i;

	Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" );

	LoadScriptFile( filename );

	nummapbrushsides = 0;
	num_entities = 0;

	while ( ParseMapEntity() )
	{
	}

	ClearBounds( map_mins, map_maxs );
	for ( i = 0 ; i < entities[0].numbrushes ; i++ )
	{
		if ( mapbrushes[i].mins[0] > 4096 ) {
			continue;   // no valid points
		}
		AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs );
		AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs );
	}

	Sys_FPrintf( SYS_VRB, "%5i brushes\n", nummapbrushes );
	Sys_FPrintf( SYS_VRB, "%5i clipbrushes\n", c_clipbrushes );
	Sys_FPrintf( SYS_VRB, "%5i total sides\n", nummapbrushsides );
	Sys_FPrintf( SYS_VRB, "%5i boxbevels\n", c_boxbevels );
	Sys_FPrintf( SYS_VRB, "%5i edgebevels\n", c_edgebevels );
	Sys_FPrintf( SYS_VRB, "%5i entities\n", num_entities );
	Sys_FPrintf( SYS_VRB, "%5i planes\n", nummapplanes );
	Sys_FPrintf( SYS_VRB, "%5i areaportals\n", c_areaportals );
	Sys_FPrintf( SYS_VRB, "size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2],
				 map_maxs[0],map_maxs[1],map_maxs[2] );

//	TestExpandBrushes ();
}


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


/*
   ================
   TestExpandBrushes

   Expands all the brush planes and saves a new map out
   ================
 */
void TestExpandBrushes( void ){
	FILE    *f;
	side_t  *s;
	int i, j, bn;
	winding_t   *w;
	char    *name = "expanded.map";
	mapbrush_t  *brush;
	vec_t dist;

	Sys_Printf( "writing %s\n", name );
	f = fopen( name, "wb" );
	if ( !f ) {
		Error( "Can't write %s\b", name );
	}

	fprintf( f, "{\n\"classname\" \"worldspawn\"\n" );

	for ( bn = 0 ; bn < nummapbrushes ; bn++ )
	{
		brush = &mapbrushes[bn];
		fprintf( f, "{\n" );
		for ( i = 0 ; i < brush->numsides ; i++ )
		{
			s = brush->original_sides + i;
			dist = mapplanes[s->planenum].dist;
			for ( j = 0 ; j < 3 ; j++ )
				dist += fabs( 16 * mapplanes[s->planenum].normal[j] );

			w = BaseWindingForPlane( mapplanes[s->planenum].normal, dist );

			fprintf( f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] );
			fprintf( f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] );
			fprintf( f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] );

			fprintf( f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture );
			FreeWinding( w );
		}
		fprintf( f, "}\n" );
	}
	fprintf( f, "}\n" );

	fclose( f );

	Error( "can't proceed after expanding brushes" );
}
