//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "vbsp.h"
#include "map_shared.h"
#include "disp_vbsp.h"
#include "tier1/strtools.h"
#include "builddisp.h"
#include "tier0/icommandline.h"
#include "KeyValues.h"
#include "materialsub.h"
#include "fgdlib/fgdlib.h"
#include "manifest.h"

#ifdef VSVMFIO
#include "VmfImport.h"
#endif // VSVMFIO


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

#define	RENDER_NORMAL_EPSILON	0.00001
#define	RENDER_DIST_EPSILON	    0.01f

#define BRUSH_CLIP_EPSILON	0.01f			// this should probably be the same
                                            // as clip epsilon, but it is 0.1f and I
											// currently don't know how that number was 
											// come to (cab) - this is 0.01 of an inch
											// for clipping brush solids
struct LoadSide_t
{
	mapbrush_t *pBrush;
	side_t *pSide;
	int nSideIndex;
	int nBaseFlags;
	int nBaseContents;
	Vector planepts[3];
	brush_texture_t	td;
};


extern qboolean onlyents;


CUtlVector< CMapFile * >	g_Maps;
CMapFile					*g_MainMap = NULL;
CMapFile					*g_LoadingMap = NULL;

char CMapFile::m_InstancePath[ MAX_PATH ] = "";
int	CMapFile::m_InstanceCount = 0;
int	CMapFile::c_areaportals = 0;

void CMapFile::Init( void )
{
	entity_num = 0;
	num_entities = 0;

	nummapplanes = 0;
	memset( mapplanes, 0, sizeof( mapplanes ) );

	nummapbrushes = 0;
	memset( mapbrushes, 0, sizeof( mapbrushes ) );

	nummapbrushsides = 0;
	memset( brushsides, 0, sizeof( brushsides ) );

	memset( side_brushtextures, 0, sizeof( side_brushtextures ) );

	memset( planehash, 0, sizeof( planehash ) );

	m_ConnectionPairs = NULL;

	m_StartMapOverlays = g_aMapOverlays.Count();
	m_StartMapWaterOverlays = g_aMapWaterOverlays.Count();

	c_boxbevels = 0;
	c_edgebevels = 0;
	c_clipbrushes = 0;
	g_ClipTexinfo = -1;
}


// All the brush sides referenced by info_no_dynamic_shadow entities.
CUtlVector<int> g_NoDynamicShadowSides;


void TestExpandBrushes (void);

ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo );
ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);

#ifdef VSVMFIO
ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo);
ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo);
#endif // VSVMFIO

ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam);
ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity);

ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity);
ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity);

ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity);
ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush);

ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo);
ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo);



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

PLANE FINDING

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


/*
=================
PlaneTypeForNormal
=================
*/
int	PlaneTypeForNormal (Vector& 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
================
*/
qboolean	PlaneEqual (plane_t *p, Vector& normal, vec_t dist, float normalEpsilon, float distEpsilon)
{
#if 1
	if (
	   fabs(p->normal[0] - normal[0]) < normalEpsilon
	&& fabs(p->normal[1] - normal[1]) < normalEpsilon
	&& fabs(p->normal[2] - normal[2]) < normalEpsilon
	&& fabs(p->dist - dist) < distEpsilon )
		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 CMapFile::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 CMapFile::CreateNewFloatPlane (Vector& normal, vec_t dist)
{
	plane_t	*p, temp;

	if (VectorLength(normal) < 0.5)
		g_MapError.ReportError ("FloatPlane: bad normal");
	// create a new plane
	if (nummapplanes+2 > MAX_MAP_PLANES)
		g_MapError.ReportError ("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
==============
*/
bool SnapVector (Vector& normal)
{
	int		i;

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

		if ( fabs(normal[i] - -1) < RENDER_NORMAL_EPSILON )
		{
			VectorClear (normal);
			normal[i] = -1;
			return true;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial.
//			Rounds dist to integer if it is within an epsilon of integer.
// Input  : normal - Plane normal vector (assumed to be unit length).
//			dist - Plane constant.
//-----------------------------------------------------------------------------
void SnapPlane(Vector &normal, vec_t &dist)
{
	SnapVector(normal);

	if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON)
	{
		dist = RoundInt(dist);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial.
//			Recalculates dist if the normal was snapped. Rounds dist to integer
//			if it is within an epsilon of integer.
// Input  : normal - Plane normal vector (assumed to be unit length).
//			dist - Plane constant.
//			p0, p1, p2 - Three points on the plane.
//-----------------------------------------------------------------------------
void SnapPlane(Vector &normal, vec_t &dist, const Vector &p0, const Vector &p1, const Vector &p2)
{
	if (SnapVector(normal))
	{
		//
		// Calculate a new plane constant using the snapped normal. Use the
		// centroid of the three plane points to minimize error. This is like
		// rotating the plane around the centroid.
		//
		Vector p3 = (p0 + p1 + p2) / 3.0f;
		dist = normal.Dot(p3);
		if ( g_snapAxialPlanes )
		{
			dist = RoundInt(dist);
		}
	}

	if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON)
	{
		dist = RoundInt(dist);
	}
}


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

=============
*/
#ifndef USE_HASHING
int CMapFile::FindFloatPlane (Vector& 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, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON))
			return i;
	}

	return CreateNewFloatPlane (normal, dist);
}
#else
int	CMapFile::FindFloatPlane (Vector& 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, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON))
				return p-mapplanes;
		}
	}

	return CreateNewFloatPlane (normal, dist);
}
#endif


//-----------------------------------------------------------------------------
// Purpose: Builds a plane normal and distance from three points on the plane.
//			If the normal is nearly axial, it will be snapped to be axial. Looks
//			up the plane in the unique planes.
// Input  : p0, p1, p2 - Three points on the plane.
// Output : Returns the index of the plane in the planes list.
//-----------------------------------------------------------------------------
int CMapFile::PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2)
{
	Vector	t1, t2, normal;
	vec_t	dist;
	
	VectorSubtract (p0, p1, t1);
	VectorSubtract (p2, p1, t2);
	CrossProduct (t1, t2, normal);
	VectorNormalize (normal);

	dist = DotProduct (p0, normal);

	SnapPlane(normal, dist, p0, p1, p2);

	return FindFloatPlane (normal, dist);
}


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

	s = &b->original_sides[0];
	contents = s->contents;
	unionContents = contents;
	for (i=1 ; i<b->numsides ; i++, s++)
	{
		s = &b->original_sides[i];

		unionContents |= s->contents;
#if 0
		if (s->contents != contents)
		{
			Msg("Brush %i: mixed face contents\n", b->id);
			break;
		}
#endif
	}

	// NOTE: we're making slime translucent	so that it doesn't block lighting on things floating on its surface
	int transparentContents = unionContents & (CONTENTS_WINDOW|CONTENTS_GRATE|CONTENTS_WATER|CONTENTS_SLIME);
	if ( transparentContents )
	{
		contents |= transparentContents | CONTENTS_TRANSLUCENT;
		contents &= ~CONTENTS_SOLID;
	}

	return contents;
}


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

bool IsAreaPortal( char const *pClassName )
{
	// If the class name starts with "func_areaportal", then it's considered an area portal.
	char const *pBaseName = "func_areaportal";
	char const *pCur = pBaseName;
	while( *pCur && *pClassName )
	{
		if( *pCur != *pClassName )
			break;

		++pCur;
		++pClassName;
	}

	return *pCur == 0;
}


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

Adds any additional planes necessary to allow the brush to be expanded
against axial bounding boxes
=================
*/
void CMapFile::AddBrushBevels (mapbrush_t *b)
{
	int		axis, dir;
	int		i, j, k, l, order;
	side_t	sidetemp;
	brush_texture_t	tdtemp;
	side_t	*s, *s2;
	Vector	normal;
	float	dist;
	winding_t	*w, *w2;
	Vector	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)
					g_MapError.ReportError ("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) < 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) < 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
						// NOTE: Use a larger tolerance for collision planes than for rendering planes
						if ( PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist, 0.01f, 0.01f ) )
							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)
						g_MapError.ReportError ("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 CMapFile::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);
			// adding an epsilon here, due to precision issues creating complex
			// displacement surfaces (cab)
			ChopWindingInPlace( &w, plane->normal, plane->dist, BRUSH_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[i] < MIN_COORD_INTEGER || ob->maxs[i] > MAX_COORD_INTEGER)
			Msg("Brush %i: bounds out of range\n", ob->id);
		if (ob->mins[i] > MAX_COORD_INTEGER || ob->maxs[i] < MIN_COORD_INTEGER)
			Msg("Brush %i: no visible sides on brush\n", ob->id);
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Takes all of the brushes from the current entity and adds them to the
//			world's brush list. Used by func_detail and func_areaportal.
//			THIS ROUTINE MAY ONLY BE USED DURING ENTITY LOADING.
// Input  : mapent - Entity whose brushes are to be moved to the world.
//-----------------------------------------------------------------------------
void CMapFile::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 = (mapbrush_t *)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;
}

//-----------------------------------------------------------------------------
// Purpose: Takes all of the brushes from the current entity and adds them to the
//			world's brush list. Used by func_detail and func_areaportal.
// Input  : mapent - Entity whose brushes are to be moved to the world.
//-----------------------------------------------------------------------------
void CMapFile::MoveBrushesToWorldGeneral( entity_t *mapent )
{
	int			newbrushes;
	int			worldbrushes;
	mapbrush_t	*temp;
	int			i;

	for( i = 0; i < nummapdispinfo; i++ )
	{
		if ( mapdispinfo[ i ].entitynum == ( mapent - entities ) )
		{
			mapdispinfo[ i ].entitynum = 0;
		}
	}

	// 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 = (mapbrush_t *)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) * (mapent->firstbrush - worldbrushes) );


	// wwwxxxmmyyy

	// 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++)
	{
		if ( entities[ i ].firstbrush < mapent->firstbrush ) // if we use <=, then we'll remap the passed in ent, which we don't want to
		{
			entities[ i ].firstbrush += newbrushes;
		}
	}
	free (temp);

	mapent->numbrushes = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Iterates the sides of brush and removed CONTENTS_DETAIL from each side
// Input  : *brush - 
//-----------------------------------------------------------------------------
void RemoveContentsDetailFromBrush( mapbrush_t *brush )
{
	// Only valid on non-world brushes
	Assert( brush->entitynum != 0 );

	side_t		*s;
	int			i;

	s = &brush->original_sides[0];
	for ( i=0 ; i<brush->numsides ; i++, s++ )
	{
		if ( s->contents & CONTENTS_DETAIL )
		{
			s->contents &= ~CONTENTS_DETAIL;
		}
	}

}

//-----------------------------------------------------------------------------
// Purpose: Iterates all brushes in an entity and removes CONTENTS_DETAIL from all brushes
// Input  : *mapent - 
//-----------------------------------------------------------------------------
void CMapFile::RemoveContentsDetailFromEntity( entity_t *mapent )
{
	int i;
	for ( i = 0; i < mapent->numbrushes; i++ )
	{
		int brushnum = mapent->firstbrush + i;

		mapbrush_t *brush = &mapbrushes[ brushnum ];
		RemoveContentsDetailFromBrush( brush );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispDistancesKeyCallback, pMapDispInfo));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : szKey - 
//			szValue - 
//			pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!strnicmp(szKey, "row", 3))
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy(szBuf, szValue);

		int nCols = (1 << pMapDispInfo->power) + 1;
		int nRow = atoi(&szKey[3]);

		char *pszNext = strtok(szBuf, " ");
		int nIndex = nRow * nCols;

		while (pszNext != NULL)
		{
			pMapDispInfo->dispDists[nIndex] = (float)atof(pszNext);
			pszNext = strtok(NULL, " ");
			nIndex++;
		}
	}

	return(ChunkFile_Ok);
}


//-----------------------------------------------------------------------------
// Purpose: load in the displacement info "chunk" from the .map file into the
//          vbsp map displacement info data structure
// Output : return the index of the map displacement info
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo )
{
    //
    // check to see if we exceeded the maximum displacement info list size
    //
    if (nummapdispinfo > MAX_MAP_DISPINFO)
	{
        g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO" );
	}

    // get a pointer to the next available displacement info slot
    mapdispinfo_t *pMapDispInfo = &mapdispinfo[nummapdispinfo];
    nummapdispinfo++;

	//
	// Set up handlers for the subchunks that we are interested in.
	//
	CChunkHandlerMap Handlers;
	Handlers.AddHandler("normals", (ChunkHandler_t)LoadDispNormalsCallback, pMapDispInfo);
	Handlers.AddHandler("distances", (ChunkHandler_t)LoadDispDistancesCallback, pMapDispInfo);
	Handlers.AddHandler("offsets", (ChunkHandler_t)LoadDispOffsetsCallback, pMapDispInfo);
	Handlers.AddHandler("alphas", (ChunkHandler_t)LoadDispAlphasCallback, pMapDispInfo);
	Handlers.AddHandler("triangle_tags", (ChunkHandler_t)LoadDispTriangleTagsCallback, pMapDispInfo);

#ifdef VSVMFIO
	Handlers.AddHandler("offset_normals", (ChunkHandler_t)LoadDispOffsetNormalsCallback, pMapDispInfo);
#endif // VSVMFIO

	//
	// Read the displacement chunk.
	//
	pFile->PushHandlers(&Handlers);
	ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadDispInfoKeyCallback, pMapDispInfo);
	pFile->PopHandlers();

	if (eResult == ChunkFile_Ok)
	{
		// return a pointer to the displacement info
		*ppMapDispInfo = pMapDispInfo;
	}

	return(eResult);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*mapent - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!stricmp(szKey, "power"))
	{
		CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->power);
	}
#ifdef VSVMFIO
	else if (!stricmp(szKey, "elevation"))
	{
		CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->m_elevation);
	}
#endif // VSVMFIO
	else if (!stricmp(szKey, "uaxis"))
	{
		CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->uAxis);
	}
	else if (!stricmp(szKey, "vaxis"))
	{
		CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->vAxis);
	}
	else if( !stricmp( szKey, "startposition" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pMapDispInfo->startPosition );
	}
	else if( !stricmp( szKey, "flags" ) )
	{
		CChunkFile::ReadKeyValueInt( szValue, pMapDispInfo->flags );
	}
#if 0 // old data
	else if (!stricmp( szKey, "alpha" ) )
	{
		CChunkFile::ReadKeyValueVector4( szValue, pMapDispInfo->alphaValues );
	}
#endif
	else if (!stricmp(szKey, "mintess"))
	{
	    CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->minTess);
	}
	else if (!stricmp(szKey, "smooth"))
	{
		CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->smoothingAngle);
	}

	return(ChunkFile_Ok);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispNormalsKeyCallback, pMapDispInfo));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!strnicmp(szKey, "row", 3))
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy(szBuf, szValue);

		int nCols = (1 << pMapDispInfo->power) + 1;
		int nRow = atoi(&szKey[3]);

		char *pszNext0 = strtok(szBuf, " ");
		char *pszNext1 = strtok(NULL, " ");
		char *pszNext2 = strtok(NULL, " ");

		int nIndex = nRow * nCols;

		while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL))
		{
			pMapDispInfo->vectorDisps[nIndex][0] = (float)atof(pszNext0);
			pMapDispInfo->vectorDisps[nIndex][1] = (float)atof(pszNext1);
			pMapDispInfo->vectorDisps[nIndex][2] = (float)atof(pszNext2);

			pszNext0 = strtok(NULL, " ");
			pszNext1 = strtok(NULL, " ");
			pszNext2 = strtok(NULL, " ");

			nIndex++;
		}
	}

	return(ChunkFile_Ok);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetsKeyCallback, pMapDispInfo));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!strnicmp(szKey, "row", 3))
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy(szBuf, szValue);

		int nCols = (1 << pMapDispInfo->power) + 1;
		int nRow = atoi(&szKey[3]);

		char *pszNext0 = strtok(szBuf, " ");
		char *pszNext1 = strtok(NULL, " ");
		char *pszNext2 = strtok(NULL, " ");

		int nIndex = nRow * nCols;

		while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL))
		{
			pMapDispInfo->vectorOffsets[nIndex][0] = (float)atof(pszNext0);
			pMapDispInfo->vectorOffsets[nIndex][1] = (float)atof(pszNext1);
			pMapDispInfo->vectorOffsets[nIndex][2] = (float)atof(pszNext2);

			pszNext0 = strtok(NULL, " ");
			pszNext1 = strtok(NULL, " ");
			pszNext2 = strtok(NULL, " ");

			nIndex++;
		}
	}

	return(ChunkFile_Ok);
}


#ifdef VSVMFIO
ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetNormalsKeyCallback, pMapDispInfo));
}


ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!strnicmp(szKey, "row", 3))
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy(szBuf, szValue);

		int nCols = (1 << pMapDispInfo->power) + 1;
		int nRow = atoi(&szKey[3]);

		char *pszNext0 = strtok(szBuf, " ");
		char *pszNext1 = strtok(NULL, " ");
		char *pszNext2 = strtok(NULL, " ");

		int nIndex = nRow * nCols;

		while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL))
		{
			pMapDispInfo->m_offsetNormals[nIndex][0] = (float)atof(pszNext0);
			pMapDispInfo->m_offsetNormals[nIndex][1] = (float)atof(pszNext1);
			pMapDispInfo->m_offsetNormals[nIndex][2] = (float)atof(pszNext2);

			pszNext0 = strtok(NULL, " ");
			pszNext1 = strtok(NULL, " ");
			pszNext2 = strtok(NULL, " ");

			nIndex++;
		}
	}

	return(ChunkFile_Ok);
}
#endif // VSVMFIO


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispAlphasKeyCallback, pMapDispInfo));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pDisp - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if (!strnicmp(szKey, "row", 3))
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy(szBuf, szValue);

		int nCols = (1 << pMapDispInfo->power) + 1;
		int nRow = atoi(&szKey[3]);

		char *pszNext0 = strtok(szBuf, " ");

		int nIndex = nRow * nCols;

		while (pszNext0 != NULL)
		{
			pMapDispInfo->alphaValues[nIndex] = (float)atof(pszNext0);
			pszNext0 = strtok(NULL, " ");
			nIndex++;
		}
	}

	return(ChunkFile_Ok);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadDispTriangleTagsKeyCallback, pMapDispInfo));
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo)
{
	if ( !strnicmp( szKey, "row", 3 ) )
	{
		char szBuf[MAX_KEYVALUE_LEN];
		strcpy( szBuf, szValue );

		int nCols = ( 1 << pMapDispInfo->power );
		int nRow = atoi( &szKey[3] );

		char *pszNext = strtok( szBuf, " " );

		int nIndex = nRow * nCols;
		int iTri = nIndex * 2;

		while ( pszNext != NULL ) 
		{
			// Collapse the tags here!
			unsigned short nTriTags = ( unsigned short )atoi( pszNext );

			// Walkable
			bool bWalkable = ( ( nTriTags & COREDISPTRI_TAG_WALKABLE ) != 0 );
			if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) != 0 ) )
			{
				bWalkable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_VAL ) != 0 );
			}

			// Buildable
			bool bBuildable = ( ( nTriTags & COREDISPTRI_TAG_BUILDABLE ) != 0 );
			if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) != 0 ) )
			{
				bBuildable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ) != 0 );
			}

			nTriTags = 0;
			if ( bWalkable )
			{
				nTriTags |= DISPTRI_TAG_WALKABLE;
			}

			if ( bBuildable )
			{
				nTriTags |= DISPTRI_TAG_BUILDABLE;
			}

			pMapDispInfo->triTags[iTri] = nTriTags;
			pszNext = strtok( NULL, " " );
			iTri++;
		}
	}

	return( ChunkFile_Ok );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : brushSideID - 
// Output : int
//-----------------------------------------------------------------------------
int CMapFile::SideIDToIndex( int brushSideID )
{
	int i;
	for ( i = 0; i < nummapbrushsides; i++ )
	{
		if ( brushsides[i].id == brushSideID )
		{
			return i;
		}
	}
	Assert( 0 );
	return -1;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *mapent - 
//			*key - 
//-----------------------------------------------------------------------------
void ConvertSideList( entity_t *mapent, char *key )
{
	char *pszSideList = ValueForKey( mapent, key );

	if (pszSideList)
	{
		char *pszTmpList = ( char* )_alloca( strlen( pszSideList ) + 1 );
		strcpy( pszTmpList, pszSideList );

		bool bFirst = true;
		char szNewValue[1024];
		szNewValue[0] = '\0';

		const char *pszScan = strtok( pszTmpList, " " );
		if ( !pszScan )
			return;
		do
		{
			int nSideID;

			if ( sscanf( pszScan, "%d", &nSideID ) == 1 )
			{
				int nIndex = g_LoadingMap->SideIDToIndex(nSideID);
				if (nIndex != -1)
				{
					if (!bFirst)
					{
						strcat( szNewValue, " " );
					}
					else
					{
						bFirst = false;
					}

					char szIndex[15];
					itoa( nIndex, szIndex, 10 );
					strcat( szNewValue, szIndex );
				}
			}
		} while ( ( pszScan = strtok( NULL, " " ) ) );

		SetKeyValue( mapent, key, szNewValue );
	}
}


// Add all the sides referenced by info_no_dynamic_shadows entities to g_NoDynamicShadowSides.
ChunkFileResult_t HandleNoDynamicShadowsEnt( entity_t *pMapEnt )
{
	// Get the list of the sides.
	char *pSideList = ValueForKey( pMapEnt, "sides" );

	// Parse the side list.
	char *pScan = strtok( pSideList, " " );
	if( pScan )
	{
		do
		{
			int brushSideID;
			if( sscanf( pScan, "%d", &brushSideID ) == 1 )
			{
				if ( g_NoDynamicShadowSides.Find( brushSideID ) == -1 )
					g_NoDynamicShadowSides.AddToTail( brushSideID );
			}
		} while( ( pScan = strtok( NULL, " " ) ) );
	}
	
	// Clear out this entity.
	pMapEnt->epairs = NULL;
	return ( ChunkFile_Ok );
}


static ChunkFileResult_t LoadOverlayDataTransitionKeyCallback( const char *szKey, const char *szValue, mapoverlay_t *pOverlay )
{
	if ( !stricmp( szKey, "material" ) )
	{
		// Get the material name.
		const char *pMaterialName = szValue;
		if( g_ReplaceMaterials )
		{
			pMaterialName = ReplaceMaterialName( szValue );
		}

		Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN );
		if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN )
		{
			Error( "Overlay Material Name (%s) > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN );
			return ChunkFile_Fail;
		}
		strcpy( pOverlay->szMaterialName, pMaterialName );	
	}
	else if ( !stricmp( szKey, "StartU") )
	{
		CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[0] );
	}
	else if ( !stricmp( szKey, "EndU" ) )
	{
		CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[1] );
	}
	else if ( !stricmp( szKey, "StartV" ) )
	{
		CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[0] );
	}
	else if ( !stricmp( szKey, "EndV" ) )
	{
		CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[1] );
	}
	else if ( !stricmp( szKey, "BasisOrigin" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecOrigin );
	}
	else if ( !stricmp( szKey, "BasisU" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[0] );
	}
	else if ( !stricmp( szKey, "BasisV" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[1] );
	}
	else if ( !stricmp( szKey, "BasisNormal" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[2] );
	}
	else if ( !stricmp( szKey, "uv0" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[0] );
	}
	else if ( !stricmp( szKey, "uv1" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[1] );
	}
	else if ( !stricmp( szKey, "uv2" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[2] );
	}
	else if ( !stricmp( szKey, "uv3" ) )
	{
		CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[3] );
	}
	else if ( !stricmp( szKey, "sides" ) )
	{
		const char *pSideList = szValue;
		char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 );
		strcpy( pTmpList, pSideList );
		const char *pScan = strtok( pTmpList, " " );
		if ( !pScan )
			return ChunkFile_Fail;

		pOverlay->aSideList.Purge();
		pOverlay->aFaceList.Purge();

		do
		{
			int nSideId;
			if ( sscanf( pScan, "%d", &nSideId ) == 1 )
			{
				pOverlay->aSideList.AddToTail( nSideId );
			}
		} while ( ( pScan = strtok( NULL, " " ) ) );
	}

	return ChunkFile_Ok;
}

static ChunkFileResult_t LoadOverlayDataTransitionCallback( CChunkFile *pFile, int nParam )
{
	int iOverlay = g_aMapWaterOverlays.AddToTail();
	mapoverlay_t *pOverlay = &g_aMapWaterOverlays[iOverlay];
	if ( !pOverlay )
		return ChunkFile_Fail;

	pOverlay->nId = ( MAX_MAP_OVERLAYS + 1 ) + g_aMapWaterOverlays.Count() - 1;
	pOverlay->m_nRenderOrder = 0;

	ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadOverlayDataTransitionKeyCallback, pOverlay );
	return eResult;
}

static ChunkFileResult_t LoadOverlayTransitionCallback( CChunkFile *pFile, int nParam )
{
	CChunkHandlerMap Handlers;
	Handlers.AddHandler( "overlaydata", ( ChunkHandler_t )LoadOverlayDataTransitionCallback, 0 );
	pFile->PushHandlers( &Handlers );

	ChunkFileResult_t eResult = pFile->ReadChunk( NULL, NULL );

	pFile->PopHandlers();

	return eResult;
}

//-----------------------------------------------------------------------------
// Purpose: Iterates all brushes in a ladder entity, generates its mins and maxs.
//          These are stored in the object, since the brushes are going to go away.
// Input  : *mapent - 
//-----------------------------------------------------------------------------
void CMapFile::AddLadderKeys( entity_t *mapent )
{
	Vector mins, maxs;
	ClearBounds( mins, maxs );

	int i;
	for ( i = 0; i < mapent->numbrushes; i++ )
	{
		int brushnum = mapent->firstbrush + i;
		mapbrush_t *brush = &mapbrushes[ brushnum ];

		AddPointToBounds( brush->mins, mins, maxs );
		AddPointToBounds( brush->maxs, mins, maxs );
	}

	char buf[16];

	Q_snprintf( buf, sizeof(buf), "%2.2f", mins.x );
	SetKeyValue( mapent, "mins.x", buf );

	Q_snprintf( buf, sizeof(buf), "%2.2f", mins.y );
	SetKeyValue( mapent, "mins.y", buf );

	Q_snprintf( buf, sizeof(buf), "%2.2f", mins.z );
	SetKeyValue( mapent, "mins.z", buf );

	Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.x );
	SetKeyValue( mapent, "maxs.x", buf );

	Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.y );
	SetKeyValue( mapent, "maxs.y", buf );

	Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.z );
	SetKeyValue( mapent, "maxs.z", buf );
}

ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam)
{
	return g_LoadingMap->LoadEntityCallback( pFile, nParam );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
//			ulParam - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam)
{
	if (num_entities == MAX_MAP_ENTITIES)
	{
		// Exits.
		g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES");
	}

	entity_t *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;
	
	LoadEntity_t LoadEntity;
	LoadEntity.pEntity = mapent;

	// No default flags/contents
	LoadEntity.nBaseFlags = 0;
	LoadEntity.nBaseContents = 0;

	//
	// Set up handlers for the subchunks that we are interested in.
	//
	CChunkHandlerMap Handlers;
	Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity);
	Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, &LoadEntity);
	Handlers.AddHandler( "overlaytransition", ( ChunkHandler_t )LoadOverlayTransitionCallback, 0 );

	//
	// Read the entity chunk.
	//
	pFile->PushHandlers(&Handlers);
	ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity);
	pFile->PopHandlers();

	if (eResult == ChunkFile_Ok)
	{
		GetVectorForKey (mapent, "origin", mapent->origin);

		const char *pMinDXLevelStr = ValueForKey( mapent, "mindxlevel" );
		const char *pMaxDXLevelStr = ValueForKey( mapent, "maxdxlevel" );
		if( *pMinDXLevelStr != '\0' || *pMaxDXLevelStr != '\0' )
		{
			int min = 0;
			int max = 0;
			if( *pMinDXLevelStr )
			{
				min = atoi( pMinDXLevelStr );
			}
			if( *pMaxDXLevelStr )
			{
				max = atoi( pMaxDXLevelStr );
			}

			// Set min and max to default values.
			if( min == 0 )
			{
				min = g_nDXLevel;
			}
			if( max == 0 )
			{
				max = g_nDXLevel;
			}
			if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < min || g_nDXLevel > max ) )
			{
				mapent->numbrushes = 0;
				mapent->epairs = NULL;
				return(ChunkFile_Ok);
			}
		}
		
		// offset all of the planes and texinfo
		if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] )
		{
			for (int i=0 ; i<mapent->numbrushes ; i++)
			{
				mapbrush_t *b = &mapbrushes[mapent->firstbrush + i];
				for (int j=0 ; j<b->numsides ; j++)
				{
					side_t *s = &b->original_sides[j];
					vec_t newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin);
					s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist);
					if ( !onlyents )
					{
						s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin);
					}
				}
				MakeBrushWindings (b);
			}
		}

		//
		// func_detail brushes are moved into the world entity. The CONTENTS_DETAIL flag was set by the loader.
		//
		const char *pClassName = ValueForKey( mapent, "classname" );

		if ( !strcmp( "func_detail", pClassName ) )
		{
			MoveBrushesToWorld (mapent);
			mapent->numbrushes = 0;
			
			// clear out this entity
			mapent->epairs = NULL;
			return(ChunkFile_Ok);
		}

		// these get added to a list for processing the portal file
		// but aren't necessary to emit to the BSP
		if ( !strcmp( "func_viscluster", pClassName ) )
		{
			AddVisCluster(mapent);
			return(ChunkFile_Ok);
		}

		//
		// func_ladder brushes are moved into the world entity.  We convert the func_ladder to an info_ladder
		// that holds the ladder's mins and maxs, and leave the entity.  This helps the bots figure out ladders.
		//
		if ( !strcmp( "func_ladder", pClassName ) )
		{
			AddLadderKeys( mapent );

			MoveBrushesToWorld (mapent);

			// Convert to info_ladder entity
			SetKeyValue( mapent, "classname", "info_ladder" );

			return(ChunkFile_Ok);
		}

		if( !strcmp( "env_cubemap", pClassName ) )
		{
			if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) )
			{
				const char *pSideListStr = ValueForKey( mapent, "sides" );
				int size;
				size = IntForKey( mapent, "cubemapsize" );
				Cubemap_InsertSample( mapent->origin, size );
				Cubemap_SaveBrushSides( pSideListStr );
			}
			// clear out this entity
			mapent->epairs = NULL;
			return(ChunkFile_Ok);
		}

		if ( !strcmp( "test_sidelist", pClassName ) )
		{
			ConvertSideList(mapent, "sides");
			return ChunkFile_Ok;
		}

		if ( !strcmp( "info_overlay", pClassName ) )
		{
			int iAccessorID = Overlay_GetFromEntity( mapent );

			if ( iAccessorID < 0 )
			{
				// Clear out this entity.
				mapent->epairs = NULL;
			}
			else
			{
				// Convert to info_overlay_accessor entity
				SetKeyValue( mapent, "classname", "info_overlay_accessor" );

				// Remember the id for accessing the overlay
				char buf[16];
				Q_snprintf( buf, sizeof(buf), "%i", iAccessorID );
				SetKeyValue( mapent, "OverlayID", buf );
			}

			return ( ChunkFile_Ok );
		}

		if ( !strcmp( "info_overlay_transition", pClassName ) )
		{
			// Clear out this entity.
			mapent->epairs = NULL;
			return ( ChunkFile_Ok );
		}

		if ( Q_stricmp( pClassName, "info_no_dynamic_shadow" ) == 0 )
		{
			return HandleNoDynamicShadowsEnt( mapent );
		}

		if ( Q_stricmp( pClassName, "func_instance_parms" ) == 0 )
		{
			// Clear out this entity.
			mapent->epairs = NULL;
			return ( ChunkFile_Ok );
		}

		// areaportal entities move their brushes, but don't eliminate
		// the entity
		if( IsAreaPortal( pClassName ) )
		{
			char	str[128];

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

			mapbrush_t *b = &mapbrushes[nummapbrushes-1];
			b->contents = CONTENTS_AREAPORTAL;
			c_areaportals++;
			mapent->areaportalnum = c_areaportals;

			// set the portal number as "portalnumber"
			sprintf (str, "%i", c_areaportals);
			SetKeyValue (mapent, "portalnumber", str);

			MoveBrushesToWorld (mapent);
			return(ChunkFile_Ok);
		}

#ifdef VSVMFIO
		if ( !Q_stricmp( pClassName, "light" ) )
		{
			CVmfImport::GetVmfImporter()->ImportLightCallback(
				ValueForKey( mapent, "hammerid" ),
				ValueForKey( mapent, "origin" ),
				ValueForKey( mapent, "_light" ),
				ValueForKey( mapent, "_lightHDR" ),
				ValueForKey( mapent, "_lightscaleHDR" ),
				ValueForKey( mapent, "_quadratic_attn" ) );
		}

		if ( !Q_stricmp( pClassName, "light_spot" ) )
		{
			CVmfImport::GetVmfImporter()->ImportLightSpotCallback(
				ValueForKey( mapent, "hammerid" ),
				ValueForKey( mapent, "origin" ),
				ValueForKey( mapent, "angles" ),
				ValueForKey( mapent, "pitch" ),
				ValueForKey( mapent, "_light" ),
				ValueForKey( mapent, "_lightHDR" ),
				ValueForKey( mapent, "_lightscaleHDR" ),
				ValueForKey( mapent, "_quadratic_attn" ),
				ValueForKey( mapent, "_inner_cone" ),
				ValueForKey( mapent, "_cone" ),
				ValueForKey( mapent, "_exponent" ) );
		}

		if ( !Q_stricmp( pClassName, "light_dynamic" ) )
		{
			CVmfImport::GetVmfImporter()->ImportLightDynamicCallback(
				ValueForKey( mapent, "hammerid" ),
				ValueForKey( mapent, "origin" ),
				ValueForKey( mapent, "angles" ),
				ValueForKey( mapent, "pitch" ),
				ValueForKey( mapent, "_light" ),
				ValueForKey( mapent, "_quadratic_attn" ),
				ValueForKey( mapent, "_inner_cone" ),
				ValueForKey( mapent, "_cone" ),
				ValueForKey( mapent, "brightness" ),
				ValueForKey( mapent, "distance" ),
				ValueForKey( mapent, "spotlight_radius" ) );
		}

		if ( !Q_stricmp( pClassName, "light_environment" ) )
		{
			CVmfImport::GetVmfImporter()->ImportLightEnvironmentCallback(
				ValueForKey( mapent, "hammerid" ),
				ValueForKey( mapent, "origin" ),
				ValueForKey( mapent, "angles" ),
				ValueForKey( mapent, "pitch" ),
				ValueForKey( mapent, "_light" ),
				ValueForKey( mapent, "_lightHDR" ),
				ValueForKey( mapent, "_lightscaleHDR" ),
				ValueForKey( mapent, "_ambient" ),
				ValueForKey( mapent, "_ambientHDR" ),
				ValueForKey( mapent, "_AmbientScaleHDR" ),
				ValueForKey( mapent, "SunSpreadAngle" ) );
		}

		const char *pModel = ValueForKey( mapent, "model" );
		if ( pModel && Q_strlen( pModel ) )
		{
			CVmfImport::GetVmfImporter()->ImportModelCallback(
				pModel,
				ValueForKey( mapent, "hammerid" ),
				ValueForKey( mapent, "angles" ),
				ValueForKey( mapent, "origin" ),
				MDagPath() );
		}
#endif // VSVMFIO

		// If it's not in the world at this point, unmark CONTENTS_DETAIL from all sides...
		if ( mapent != &entities[ 0 ] )
		{
			RemoveContentsDetailFromEntity( mapent );
		}

		return(ChunkFile_Ok);
	}

	return(eResult);
}


entity_t* EntityByName( char const *pTestName )
{
	if( !pTestName )
		return 0;

	for( int i=0; i < g_MainMap->num_entities; i++ )
	{
		entity_t *e = &g_MainMap->entities[i];

		const char *pName = ValueForKey( e, "targetname" );
		if( stricmp( pName, pTestName ) == 0 )
			return e;
	}

	return 0;
}


void CMapFile::ForceFuncAreaPortalWindowContents()
{
	// Now go through all areaportal entities and force CONTENTS_WINDOW
	// on the brushes of the bmodels they point at.
	char *targets[] = {"target", "BackgroundBModel"};
	int nTargets = sizeof(targets) / sizeof(targets[0]);

	for( int i=0; i < num_entities; i++ )
	{
		entity_t *e = &entities[i];

		const char *pClassName = ValueForKey( e, "classname" );

		// Don't do this on "normal" func_areaportal entities.  Those are tied to doors
		// and should be opaque when closed.  But areaportal windows (and any other 
		// distance-based areaportals) should be windows because they are normally open/transparent
		if( !IsAreaPortal( pClassName ) || !Q_stricmp( pClassName, "func_areaportal" ) )
			continue;

//		const char *pTestEntName = ValueForKey( e, "targetname" );

		for( int iTarget=0; iTarget < nTargets; iTarget++ )
		{
			char const *pEntName = ValueForKey( e, targets[iTarget] );
			if( !pEntName[0] )
				continue;

			entity_t *pBrushEnt = EntityByName( pEntName );
			if( !pBrushEnt )
				continue;

			for( int iBrush=0; iBrush < pBrushEnt->numbrushes; iBrush++ )
			{
				mapbrushes[pBrushEnt->firstbrush + iBrush].contents &= ~CONTENTS_SOLID;
				mapbrushes[pBrushEnt->firstbrush + iBrush].contents |= CONTENTS_TRANSLUCENT | CONTENTS_WINDOW;
			}
		}
	}
}


// ============ Instancing ============

// #define MERGE_INSTANCE_DEBUG_INFO	1

#define INSTANCE_VARIABLE_KEY			"replace"

static GameData	GD;

//-----------------------------------------------------------------------------
// Purpose: this function will read in a standard key / value file
// Input  : pFilename - the absolute name of the file to read
// Output : returns the KeyValues of the file, NULL if the file could not be read.
//-----------------------------------------------------------------------------
static KeyValues *ReadKeyValuesFile( const char *pFilename )
{
	// Read in the gameinfo.txt file and null-terminate it.
	FILE *fp = fopen( pFilename, "rb" );
	if ( !fp )
		return NULL;
	CUtlVector<char> buf;
	fseek( fp, 0, SEEK_END );
	buf.SetSize( ftell( fp ) + 1 );
	fseek( fp, 0, SEEK_SET );
	fread( buf.Base(), 1, buf.Count()-1, fp );
	fclose( fp );
	buf[buf.Count()-1] = 0;

	KeyValues *kv = new KeyValues( "" );
	if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) )
	{
		kv->deleteThis();
		return NULL;
	}

	return kv;
}


//-----------------------------------------------------------------------------
// Purpose: this function will set a secondary lookup path for instances.
// Input  : pszInstancePath - the secondary lookup path
//-----------------------------------------------------------------------------
void CMapFile::SetInstancePath( const char *pszInstancePath )
{
	strcpy( m_InstancePath, pszInstancePath );
	V_strlower( m_InstancePath );
	V_FixSlashes( m_InstancePath );
}


//-----------------------------------------------------------------------------
// Purpose: This function will attempt to find a full path given the base and relative names.
// Input  : pszBaseFileName - the base file that referenced this instance
//			pszInstanceFileName - the relative file name of this instance
// Output : Returns true if it was able to locate the file
//			pszOutFileName - the full path to the file name if located
//-----------------------------------------------------------------------------
bool CMapFile::DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName )
{
	char		szInstanceFileNameFixed[ MAX_PATH ];
	const char *pszMapPath = "\\maps\\";

	strcpy( szInstanceFileNameFixed, pszInstanceFileName );
	V_SetExtension( szInstanceFileNameFixed, ".vmf", sizeof( szInstanceFileNameFixed ) );
	V_FixSlashes( szInstanceFileNameFixed );

	// first, try to find a relative location based upon the Base file name
	strcpy( pszOutFileName, pszBaseFileName );
	V_StripFilename( pszOutFileName );

	strcat( pszOutFileName, "\\" );
	strcat( pszOutFileName, szInstanceFileNameFixed );

	if ( g_pFullFileSystem->FileExists( pszOutFileName ) )
	{
		return true;
	}

	// second, try to find the master 'maps' directory and make it relative from that
	strcpy( pszOutFileName, pszBaseFileName );
	V_StripFilename( pszOutFileName );
	V_RemoveDotSlashes( pszOutFileName );
	V_FixDoubleSlashes( pszOutFileName );
	V_strlower( pszOutFileName );
	strcat( pszOutFileName, "\\" );

	char *pos = strstr( pszOutFileName, pszMapPath );
	if ( pos )
	{
		pos += strlen( pszMapPath );
		*pos = 0;
		strcat( pszOutFileName, szInstanceFileNameFixed );

		if ( g_pFullFileSystem->FileExists( pszOutFileName ) )
		{
			return true;
		}
	}

	if ( m_InstancePath[ 0 ] != 0 )
	{
		sprintf( szInstanceFileNameFixed, "%s%s", m_InstancePath, pszInstanceFileName );

		if ( g_pFullFileSystem->FileExists( szInstanceFileNameFixed, "GAME" ) )
		{
			char FullPath[ MAX_PATH ];
			g_pFullFileSystem->RelativePathToFullPath( szInstanceFileNameFixed, "GAME", FullPath, sizeof( FullPath ) );
			strcpy( pszOutFileName, FullPath );

			return true;
		}
	}

	pszOutFileName[ 0 ] = 0;

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: this function will check the main map for any func_instances.  It will
//			also attempt to load in the gamedata file for instancing remapping help.
// Input  : none
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::CheckForInstances( const char *pszFileName )
{
	if ( this != g_MainMap )
	{	// all sub-instances will be appended to the main map master list as they are read in
		// so the main loop below will naturally get to the appended ones.
		return;
	}

	char	GameInfoPath[ MAX_PATH ];

	g_pFullFileSystem->RelativePathToFullPath( "gameinfo.txt", "MOD", GameInfoPath, sizeof( GameInfoPath ) );
	KeyValues *GameInfoKV = ReadKeyValuesFile( GameInfoPath );
	if ( !GameInfoKV )
	{
		Msg( "Could not locate gameinfo.txt for Instance Remapping at %s\n", GameInfoPath );
		return;
	}

	const char *InstancePath = GameInfoKV->GetString( "InstancePath", NULL );
	if ( InstancePath )
	{
		CMapFile::SetInstancePath( InstancePath );
	}

	const char *GameDataFile = GameInfoKV->GetString( "GameData", NULL );
	if ( !GameDataFile )
	{
		Msg( "Could not locate 'GameData' key in %s\n", GameInfoPath );
		return;
	}

	char	FDGPath[ MAX_PATH ];
	if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) )
	{
		if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, NULL, FDGPath, sizeof( FDGPath ) ) )
		{
			Msg( "Could not locate GameData file %s\n", GameDataFile );
		}
	}

	GD.Load( FDGPath );

	// this list will grow as instances are merged onto it.  sub-instances are merged and 
	// automatically done in this processing.
	for ( int i = 0; i < num_entities; i++ )
	{
		char *pEntity = ValueForKey( &entities[ i ], "classname" );
		if ( !strcmp( pEntity, "func_instance" ) )
		{
			char *pInstanceFile = ValueForKey( &entities[ i ], "file" );
			if ( pInstanceFile[ 0 ] )
			{
				char	InstancePath[ MAX_PATH ];
				bool	bLoaded = false;

				if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) )
				{
					if ( LoadMapFile( InstancePath ) )
					{
						MergeInstance( &entities[ i ], g_LoadingMap );
						delete g_LoadingMap;
						bLoaded = true;
					}
				}

				if ( bLoaded == false )
				{
					Color red( 255, 0, 0, 255 );

					ColorSpewMessage( SPEW_ERROR, &red, "Could not open instance file %s\n", pInstanceFile );
				}
			}

			entities[ i ].numbrushes = 0;
			entities[ i ].epairs = NULL;
		}
	}

	g_LoadingMap = this;
}


//-----------------------------------------------------------------------------
// Purpose: this function will do all of the necessary work to merge the instance
//			into the main map.
// Input  : pInstanceEntity - the entity of the func_instance
//			Instance - the map file of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance )
{
	matrix3x4_t	mat;
	QAngle		angles;
	Vector		OriginOffset = pInstanceEntity->origin;

	m_InstanceCount++;

	GetAnglesForKey( pInstanceEntity, "angles", angles );
	AngleMatrix( angles, OriginOffset, mat );

#ifdef MERGE_INSTANCE_DEBUG_INFO
	Msg( "Instance Remapping: O:( %g, %g, %g ) A:( %g, %g, %g )\n", OriginOffset.x, OriginOffset.y, OriginOffset.z, angles.x, angles.y, angles.z );
#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO
	MergePlanes( pInstanceEntity, Instance, OriginOffset, angles, mat );
	MergeBrushes( pInstanceEntity, Instance, OriginOffset, angles, mat );
	MergeBrushSides( pInstanceEntity, Instance, OriginOffset, angles, mat );
	MergeEntities( pInstanceEntity, Instance, OriginOffset, angles, mat );
	MergeOverlays( pInstanceEntity, Instance, OriginOffset, angles, mat );
}


//-----------------------------------------------------------------------------
// Purpose: this function will merge in the map planes from the instance into
//			the main map.
// Input  : pInstanceEntity - the entity of the func_instance
//			Instance - the map file of the instance
//			InstanceOrigin - the translation of the instance
//			InstanceAngle - the rotation of the instance
//			InstanceMatrix - the translation / rotation matrix of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix )
{
	// Each pair of planes needs to be added to the main map
	for ( int i = 0; i < Instance->nummapplanes; i += 2 )
	{
		FindFloatPlane( Instance->mapplanes[i].normal, Instance->mapplanes[i].dist );
	}
}


//-----------------------------------------------------------------------------
// Purpose: this function will merge in the map brushes from the instance into
//			the main map.
// Input  : pInstanceEntity - the entity of the func_instance
//			Instance - the map file of the instance
//			InstanceOrigin - the translation of the instance
//			InstanceAngle - the rotation of the instance
//			InstanceMatrix - the translation / rotation matrix of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix )
{
	int		max_brush_id = 0;

	for( int i = 0; i < nummapbrushes; i++ )
	{
		if ( mapbrushes[ i ].id > max_brush_id )
		{
			max_brush_id = mapbrushes[ i ].id;
		}
	}

	for( int i = 0; i < Instance->nummapbrushes; i++ )
	{
		mapbrushes[ nummapbrushes + i ] = Instance->mapbrushes[ i ];

		mapbrush_t	*brush = &mapbrushes[ nummapbrushes + i ];
		brush->entitynum += num_entities;
		brush->brushnum += nummapbrushes;

		if ( i < Instance->entities[ 0 ].numbrushes || ( brush->contents & CONTENTS_LADDER ) != 0 )
		{	// world spawn brushes as well as ladders we physically move
			Vector	minsIn = brush->mins;
			Vector	maxsIn = brush->maxs;

			TransformAABB( InstanceMatrix, minsIn, maxsIn, brush->mins, brush->maxs );
		}
		else
		{
		}
		brush->id += max_brush_id;
		
		int index = brush->original_sides - Instance->brushsides;
		brush->original_sides = &brushsides[ nummapbrushsides + index ];
	}

	nummapbrushes += Instance->nummapbrushes;
}


//-----------------------------------------------------------------------------
// Purpose: this function will merge in the map sides from the instance into
//			the main map.
// Input  : pInstanceEntity - the entity of the func_instance
//			Instance - the map file of the instance
//			InstanceOrigin - the translation of the instance
//			InstanceAngle - the rotation of the instance
//			InstanceMatrix - the translation / rotation matrix of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix )
{
	int		max_side_id = 0;

	for( int i = 0; i < nummapbrushsides; i++ )
	{
		if ( brushsides[ i ].id > max_side_id )
		{
			max_side_id = brushsides[ i ].id;
		}
	}

	for( int i = 0; i < Instance->nummapbrushsides; i++ )
	{
		brushsides[ nummapbrushsides + i ] = Instance->brushsides[ i ];

		side_t	*side = &brushsides[ nummapbrushsides + i ];
		// The planes got merged & remapped.  So you need to search for the output plane index on each side
		// NOTE: You could optimize this by saving off an index map in MergePlanes
		side->planenum = FindFloatPlane( Instance->mapplanes[side->planenum].normal, Instance->mapplanes[side->planenum].dist );
		side->id += max_side_id; 

		// this could be pre-processed into a list for quicker checking
		bool	bNeedsTranslation = ( side->pMapDisp && side->pMapDisp->entitynum == 0 );
		if ( !bNeedsTranslation )
		{	// check for sides that are part of the world spawn - those need translating
			for( int j = 0; j < Instance->entities[ 0 ].numbrushes; j++ )
			{
				int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides;

				if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) )
				{
					bNeedsTranslation = true;
					break;
				}
			}
		}
		if ( !bNeedsTranslation )
		{	// sides for ladders are outside of the world spawn, but also need translating
			for( int j = Instance->entities[ 0 ].numbrushes; j < Instance->nummapbrushes; j++ )
			{
				int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides;

				if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) && ( Instance->mapbrushes[ j ].contents & CONTENTS_LADDER ) != 0 )
				{
					bNeedsTranslation = true;
					break;
				}
			}
		}
		if ( bNeedsTranslation )
		{	// we only want to do the adjustment on world spawn brushes, not entity brushes
			if ( side->winding )
			{
				for( int point = 0; point < side->winding->numpoints; point++ )
				{
					Vector	inPoint = side->winding->p[ point ];
					VectorTransform( inPoint, InstanceMatrix, side->winding->p[ point ] );
				}
			}

			int		planenum = side->planenum;
			cplane_t inPlane, outPlane;
			inPlane.normal = mapplanes[ planenum ].normal;
			inPlane.dist = mapplanes[ planenum ].dist; 

			MatrixTransformPlane( InstanceMatrix, inPlane, outPlane );
			planenum = FindFloatPlane( outPlane.normal, outPlane.dist );
			side->planenum = planenum;

			brush_texture_t	bt = Instance->side_brushtextures[ i ];

			VectorRotate( Instance->side_brushtextures[ i ].UAxis, InstanceMatrix, bt.UAxis );
			VectorRotate( Instance->side_brushtextures[ i ].VAxis, InstanceMatrix, bt.VAxis );
			bt.shift[ 0 ] -= InstanceOrigin.Dot( bt.UAxis ) / bt.textureWorldUnitsPerTexel[ 0 ];
			bt.shift[ 1 ] -= InstanceOrigin.Dot( bt.VAxis ) / bt.textureWorldUnitsPerTexel[ 1 ];

			if ( !onlyents )
			{
				side->texinfo = TexinfoForBrushTexture ( &mapplanes[ side->planenum ], &bt, vec3_origin );
			}
		}

		if ( side->pMapDisp )
		{
			mapdispinfo_t	*disp = side->pMapDisp;
				
			disp->brushSideID = side->id;
			Vector	inPoint = disp->startPosition;
			VectorTransform( inPoint, InstanceMatrix, disp->startPosition );

			disp->face.originalface = side;
			disp->face.texinfo = side->texinfo;
			disp->face.planenum = side->planenum;
			disp->entitynum += num_entities; 

			for( int point = 0; point < disp->face.w->numpoints; point++ )
			{
				Vector	inPoint = disp->face.w->p[ point ];
				VectorTransform( inPoint, InstanceMatrix, disp->face.w->p[ point ] );
			}

		}
	}

	nummapbrushsides += Instance->nummapbrushsides;
}


//-----------------------------------------------------------------------------
// Purpose: this function will look for replace parameters in the function instance
//			to see if there is anything in the epair that should be replaced.
// Input  : pPair - the epair with the value
//			pInstanceEntity - the func_instance that may ahve replace keywords
// Output : pPair - the value field may be updated
//-----------------------------------------------------------------------------
void CMapFile::ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity )
{
	char	Value[ MAX_KEYVALUE_LEN ], NewValue[ MAX_KEYVALUE_LEN ];
	bool	Overwritten = false;

	strcpy( NewValue, pPair->value );
	for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next )
	{
		if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 )
		{
			char InstanceVariable[ MAX_KEYVALUE_LEN ];

			strcpy( InstanceVariable, epInstance->value );

			char *ValuePos = strchr( InstanceVariable, ' ' );
			if ( !ValuePos )
			{
				continue;
			}
			*ValuePos = 0;
			ValuePos++;

			strcpy( Value, NewValue );
			if ( !V_StrSubst( Value, InstanceVariable, ValuePos, NewValue, sizeof( NewValue ), false ) )
			{
				Overwritten = true;
				break;
			}
		}
	}

	if ( !Overwritten && strcmp( pPair->value, NewValue ) != 0 )
	{
		free( pPair->value );
		pPair->value = copystring( NewValue );
	}
}


//-----------------------------------------------------------------------------
// Purpose: this function will merge in the entities from the instance into
//			the main map.
// Input  : pInstanceEntity - the entity of the func_instance
//			Instance - the map file of the instance
//			InstanceOrigin - the translation of the instance
//			InstanceAngle - the rotation of the instance
//			InstanceMatrix - the translation / rotation matrix of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix )
{
	int						max_entity_id = 0;
	char					temp[ 2048 ];
	char					NameFixup[ 128 ];
	entity_t				*WorldspawnEnt = NULL;
	GameData::TNameFixup	FixupStyle;

	char *pTargetName = ValueForKey( pInstanceEntity, "targetname" );
	char *pName = ValueForKey( pInstanceEntity, "name" );
	if ( pTargetName[ 0 ] )
	{
		sprintf( NameFixup, "%s", pTargetName );
	}
	else if ( pName[ 0 ] )
	{
		sprintf( NameFixup, "%s", pName );
	}
	else
	{
		sprintf( NameFixup, "InstanceAuto%d", m_InstanceCount );
	}

	for( int i = 0; i < num_entities; i++ )
	{
		char *pID = ValueForKey( &entities[ i ], "hammerid" );
		if ( pID[ 0 ] )
		{
			int value = atoi( pID );
			if ( value > max_entity_id )
			{
				max_entity_id = value;
			}
		}
	}

	FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) );

	for( int i = 0; i < Instance->num_entities; i++ )
	{
		entities[ num_entities + i ] = Instance->entities[ i ];

		entity_t *entity = &entities[ num_entities + i ];
		entity->firstbrush += ( nummapbrushes - Instance->nummapbrushes );

		char *pID = ValueForKey( entity, "hammerid" );
		if ( pID[ 0 ] )
		{
			int value = atoi( pID );
			value += max_entity_id;
			sprintf( temp, "%d", value );

			SetKeyValue( entity, "hammerid", temp );
		}

		char *pEntity = ValueForKey( entity, "classname" );
		if ( strcmpi( pEntity, "worldspawn" ) == 0 )
		{
			WorldspawnEnt = entity;
		}
		else
		{
			Vector	inOrigin = entity->origin;
			VectorTransform( inOrigin, InstanceMatrix, entity->origin );

			// search for variables coming from the func_instance to replace inside of the instance
			// this is done before entity fixup, so fixup may occur on the replaced value.  Not sure if this is a desired order of operation yet.
			for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next )
			{
				ReplaceInstancePair( ep, pInstanceEntity );
			}

#ifdef MERGE_INSTANCE_DEBUG_INFO
			Msg( "Remapping class %s\n", pEntity );
#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO
			GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle );
			if ( EntClass )
			{
				for( int i = 0; i < EntClass->GetVariableCount(); i++ )
				{
					GDinputvariable *EntVar = EntClass->GetVariableAt( i );
					char *pValue = ValueForKey( entity, ( char * )EntVar->GetName() );
					if ( GD.RemapKeyValue( EntVar->GetName(), pValue, temp, FixupStyle ) )
					{
#ifdef MERGE_INSTANCE_DEBUG_INFO
						Msg( "   %d. Remapped %s: from %s to %s\n", i, EntVar->GetName(), pValue, temp );
#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO
						SetKeyValue( entity, EntVar->GetName(), temp );
					}
					else
					{
#ifdef MERGE_INSTANCE_DEBUG_INFO
						Msg( "   %d. Ignored %s: %s\n", i, EntVar->GetName(), pValue );
#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO
					}
				}
			}

			if ( strcmpi( pEntity, "func_simpleladder" ) == 0 )
			{	// hate having to do this, but the key values are so screwed up
				AddLadderKeys( entity );
/*				Vector	vInNormal, vOutNormal;

				vInNormal.x = FloatForKey( entity, "normal.x" );
				vInNormal.y = FloatForKey( entity, "normal.y" );
				vInNormal.z = FloatForKey( entity, "normal.z" );
				VectorRotate( vInNormal, InstanceMatrix, vOutNormal );

				Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.x );
				SetKeyValue( entity, "normal.x", temp );

				Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.y );
				SetKeyValue( entity, "normal.y", temp );

				Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z );
				SetKeyValue( entity, "normal.z", temp );*/
			}
		}

#ifdef MERGE_INSTANCE_DEBUG_INFO
		Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i );
		Msg( "   FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush );
		Msg( "   KV Pairs:\n" );
		for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next )
		{
			Msg( "      %s %s\n", ep->key, ep->value );
		}
#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO
	}

	// search for variables coming from the func_instance to replace inside of the instance
	// this is done before connection fix up, so fix up may occur on the replaced value.  Not sure if this is a desired order of operation yet.
	for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next )
	{
		ReplaceInstancePair( Connection->m_Pair, pInstanceEntity );
	}

	for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next )
	{
		char	*newValue, *oldValue;
		char	origValue[ 4096 ];
		int		extraLen = 0;

		oldValue = Connection->m_Pair->value;
		strcpy( origValue, oldValue );
		char *pos = strchr( origValue, ',' );
		if ( pos )
		{	// null terminate the first field
			*pos = NULL;
			extraLen = strlen( pos + 1) + 1;	// for the comma we just null'd
		}

		if ( GD.RemapNameField( origValue, temp, FixupStyle ) )
		{
			newValue = new char [ strlen( temp ) + extraLen + 1 ];
			strcpy( newValue, temp );
			if ( pos )
			{
				strcat( newValue, "," );
				strcat( newValue, pos + 1 );
			}

			Connection->m_Pair->value = newValue;
			delete oldValue;
		}
	}

	num_entities += Instance->num_entities;

	MoveBrushesToWorldGeneral( WorldspawnEnt );
	WorldspawnEnt->numbrushes = 0;
	WorldspawnEnt->epairs = NULL;
}


//-----------------------------------------------------------------------------
// Purpose: this function will translate overlays from the instance into
//			the main map.
// Input  : InstanceEntityNum - the entity number of the func_instance
//			Instance - the map file of the instance
//			InstanceOrigin - the translation of the instance
//			InstanceAngle - the rotation of the instance
//			InstanceMatrix - the translation / rotation matrix of the instance
// Output : none
//-----------------------------------------------------------------------------
void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix )
{
	for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ )
	{
		Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix );
	}
	for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ )
	{
		Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Loads a VMF or MAP file. If the file has a .MAP extension, the MAP
//			loader is used, otherwise the file is assumed to be in VMF format.
// Input  : pszFileName - Full path of the map file to load.
//-----------------------------------------------------------------------------
bool LoadMapFile( const char *pszFileName )
{
	bool				bLoadingManifest = false;
	CManifest			*pMainManifest = NULL;
	ChunkFileResult_t	eResult;
	
	//
	// Dummy this up for the texture handling. This can be removed when old .MAP file
	// support is removed.
	//
	g_nMapFileVersion = 400;

	const char *pszExtension =V_GetFileExtension( pszFileName );
	if ( pszExtension && strcmpi( pszExtension, "vmm" ) == 0 )
	{
		pMainManifest = new CManifest();
		if ( pMainManifest->LoadVMFManifest( pszFileName ) )
		{
			eResult = ChunkFile_Ok;
			pszFileName = pMainManifest->GetInstancePath();
		}
		else
		{
			eResult = ChunkFile_Fail;
		}
		bLoadingManifest = true;
	}
	else
	{
		//
		// Open the file.
		//
		CChunkFile File;
		eResult = File.Open(pszFileName, ChunkFile_Read);

		//
		// Read the file.
		//
		if (eResult == ChunkFile_Ok)
		{
			int index = g_Maps.AddToTail( new CMapFile() );
			g_LoadingMap = g_Maps[ index ];
			if ( g_MainMap == NULL )
			{
				g_MainMap = g_LoadingMap;
			}

			if ( g_MainMap == g_LoadingMap || verbose )
			{
				Msg( "Loading %s\n", pszFileName );
			}


			// reset the displacement info count
	//		nummapdispinfo = 0;

			//
			// Set up handlers for the subchunks that we are interested in.
			//
			CChunkHandlerMap Handlers;
			Handlers.AddHandler("world", (ChunkHandler_t)LoadEntityCallback, 0);
			Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0);

			File.PushHandlers(&Handlers);

			//
			// Read the sub-chunks. We ignore keys in the root of the file.
			//
			while (eResult == ChunkFile_Ok)
			{
				eResult = File.ReadChunk();
			}

			File.PopHandlers();
		}
		else
		{
			Error("Error opening %s: %s.\n", pszFileName, File.GetErrorText(eResult));
		}
	}

	if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF))
	{
		// Update the overlay/side list(s).
		Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays );
		OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays );

		g_LoadingMap->CheckForInstances( pszFileName );

		if ( pMainManifest )
		{
			pMainManifest->CordonWorld();
		}

		ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs);
		for (int i=0 ; i<g_MainMap->entities[0].numbrushes ; i++)
		{
			// HLTOOLS: Raise map limits
			if (g_LoadingMap->mapbrushes[i].mins[0] > MAX_COORD_INTEGER)
			{
				continue;	// no valid points
			}

			AddPointToBounds (g_LoadingMap->mapbrushes[i].mins, g_LoadingMap->map_mins, g_LoadingMap->map_maxs);
			AddPointToBounds (g_LoadingMap->mapbrushes[i].maxs, g_LoadingMap->map_mins, g_LoadingMap->map_maxs);
		}

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

		//TestExpandBrushes();
		
		// Clear the error reporting
		g_MapError.ClearState();
	}

	if ( g_MainMap == g_LoadingMap )
	{
		num_entities = g_MainMap->num_entities;
		memcpy( entities, g_MainMap->entities, sizeof( g_MainMap->entities ) );
	}
	g_LoadingMap->ForceFuncAreaPortalWindowContents();

	return ( ( eResult == ChunkFile_Ok ) || ( eResult == ChunkFile_EOF ) );
}

ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo)
{
	return g_LoadingMap->LoadSideCallback( pFile, pSideInfo );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pFile - 
//			pParent - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapFile::LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo)
{
	if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
	{
		g_MapError.ReportError ("MAX_MAP_BRUSHSIDES");
	}

	pSideInfo->pSide = &brushsides[nummapbrushsides];

	side_t *side = pSideInfo->pSide;
	mapbrush_t *b = pSideInfo->pBrush;
	g_MapError.BrushSide( pSideInfo->nSideIndex++ );

	// initialize the displacement info
	pSideInfo->pSide->pMapDisp = NULL;

	//
	// Set up handlers for the subchunks that we are interested in.
	//
	CChunkHandlerMap Handlers;
	Handlers.AddHandler( "dispinfo", ( ChunkHandler_t )LoadDispInfoCallback, &side->pMapDisp );

	//
	// Read the side chunk.
	//
	pFile->PushHandlers(&Handlers);
	ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSideKeyCallback, pSideInfo);
	pFile->PopHandlers();

	if (eResult == ChunkFile_Ok)
	{
		side->contents |= pSideInfo->nBaseContents;
		side->surf |= pSideInfo->nBaseFlags;
		pSideInfo->td.flags |= pSideInfo->nBaseFlags;

		if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) )
		{
			side->contents |= CONTENTS_DETAIL;
		}

		if (fulldetail )
		{
			side->contents &= ~CONTENTS_DETAIL;
		}
		
		if (!(side->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP)  ) )
		{
			side->contents |= CONTENTS_SOLID;
		}

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

		//
		// find the plane number
		//
		int planenum = PlaneFromPoints(pSideInfo->planepts[0], pSideInfo->planepts[1], pSideInfo->planepts[2]);
		if  (planenum != -1)
		{
			//
			// See if the plane has been used already.
			//
			int k;
			for ( k = 0; k < b->numsides; k++)
			{
				side_t *s2 = b->original_sides + k;
				if (s2->planenum == planenum)
				{
					g_MapError.ReportWarning("duplicate plane");
					break;
				}
				if ( s2->planenum == (planenum^1) )
				{
					g_MapError.ReportWarning("mirrored plane");
					break;
				}
			}

			//
			// If the plane hasn't been used already, keep this side.
			//
			if (k == b->numsides)
			{
				side = b->original_sides + b->numsides;
				side->planenum = planenum;
				if ( !onlyents )
				{
					side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &pSideInfo->td, vec3_origin);
				}
        
				// save the td off in case there is an origin brush and we
				// have to recalculate the texinfo
				if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
					g_MapError.ReportError ("MAX_MAP_BRUSHSIDES");
				side_brushtextures[nummapbrushsides] = pSideInfo->td;
				nummapbrushsides++;
				b->numsides++;

#ifdef VSVMFIO
				// Tell Maya We Have Another Side
				if ( CVmfImport::GetVmfImporter() )
				{
					CVmfImport::GetVmfImporter()->AddSideCallback(
						b, side, pSideInfo->td,
						pSideInfo->planepts[ 0 ], pSideInfo->planepts[ 1 ], pSideInfo->planepts[ 2 ] );
				}
#endif // VSVMFIO

			}
		}
		else
		{
			g_MapError.ReportWarning("plane with no normal");
		}
	}

	return(eResult);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : szKey - 
//			szValue - 
//			pSideInfo - 
// Output : 
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo)
{
	if (!stricmp(szKey, "plane"))
	{
		int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)",
			&pSideInfo->planepts[0][0], &pSideInfo->planepts[0][1], &pSideInfo->planepts[0][2],
			&pSideInfo->planepts[1][0], &pSideInfo->planepts[1][1], &pSideInfo->planepts[1][2],
			&pSideInfo->planepts[2][0],  &pSideInfo->planepts[2][1],  &pSideInfo->planepts[2][2]);

		if (nRead != 9)
		{
			g_MapError.ReportError("parsing plane definition");
		}
	}
	else if (!stricmp(szKey, "material"))
	{
		// Get the material name.
		if( g_ReplaceMaterials )
		{
			szValue = ReplaceMaterialName( szValue );
		}

		strcpy(pSideInfo->td.name, szValue);
		g_MapError.TextureState(szValue);

		// Find default flags and values for this material.
		int mt = FindMiptex(pSideInfo->td.name);
        pSideInfo->td.flags = textureref[mt].flags;
		pSideInfo->td.lightmapWorldUnitsPerLuxel = textureref[mt].lightmapWorldUnitsPerLuxel;

		pSideInfo->pSide->contents = textureref[mt].contents;
		pSideInfo->pSide->surf = pSideInfo->td.flags;
	}
	else if (!stricmp(szKey, "uaxis"))
	{
		int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.UAxis[0], &pSideInfo->td.UAxis[1], &pSideInfo->td.UAxis[2], &pSideInfo->td.shift[0], &pSideInfo->td.textureWorldUnitsPerTexel[0]);
		if (nRead != 5)
		{
			g_MapError.ReportError("parsing U axis definition");
		}
	}
	else if (!stricmp(szKey, "vaxis"))
	{
		int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.VAxis[0], &pSideInfo->td.VAxis[1], &pSideInfo->td.VAxis[2], &pSideInfo->td.shift[1], &pSideInfo->td.textureWorldUnitsPerTexel[1]);
		if (nRead != 5)
		{
			g_MapError.ReportError("parsing V axis definition");
		}
	}
	else if (!stricmp(szKey, "lightmapscale"))
	{
		pSideInfo->td.lightmapWorldUnitsPerLuxel = atoi(szValue);
		if (pSideInfo->td.lightmapWorldUnitsPerLuxel == 0.0f)
		{
			g_MapError.ReportWarning("luxel size of 0");
			pSideInfo->td.lightmapWorldUnitsPerLuxel = g_defaultLuxelSize; 
		}
		pSideInfo->td.lightmapWorldUnitsPerLuxel *= g_luxelScale;
		if (pSideInfo->td.lightmapWorldUnitsPerLuxel < g_minLuxelScale)
		{
			pSideInfo->td.lightmapWorldUnitsPerLuxel = g_minLuxelScale;
		}
	}
	else if (!stricmp(szKey, "contents"))
	{
		pSideInfo->pSide->contents |= atoi(szValue);
	}
	else if (!stricmp(szKey, "flags"))
	{
		pSideInfo->td.flags |= atoi(szValue);
		pSideInfo->pSide->surf = pSideInfo->td.flags;
	}
	else if (!stricmp(szKey, "id"))
	{
		pSideInfo->pSide->id = atoi( szValue );
	}
	else if (!stricmp(szKey, "smoothing_groups"))
	{
		pSideInfo->pSide->smoothingGroups = atoi( szValue );
	}

	return(ChunkFile_Ok);
}


//-----------------------------------------------------------------------------
// Purpose: Reads the connections chunk of the entity.
// Input  : pFile - Chunk file to load from.
//			pLoadEntity - Structure to receive loaded entity information.
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity)
{
	return(pFile->ReadChunk((KeyHandler_t)LoadConnectionsKeyCallback, pLoadEntity));
}


//-----------------------------------------------------------------------------
// Purpose: Parses a key/value pair from the entity connections chunk.
// Input  : szKey - Key indicating the name of the entity output.
//			szValue - Comma delimited fields in the following format:
//				<target>,<input>,<parameter>,<delay>,<times to fire>
//			pLoadEntity - Structure to receive loaded entity information.
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity)
{
	return g_LoadingMap->LoadConnectionsKeyCallback( szKey, szValue, pLoadEntity );
}

ChunkFileResult_t CMapFile::LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity)
{
	//
	// Create new input and fill it out.
	//
	epair_t *pOutput = new epair_t;

	pOutput->key = new char [strlen(szKey) + 1];
	pOutput->value = new char [strlen(szValue) + 1];

	strcpy(pOutput->key, szKey);
	strcpy(pOutput->value, szValue);

	m_ConnectionPairs = new CConnectionPairs( pOutput, m_ConnectionPairs );
	
	//
	// Append it to the end of epairs list.
	//
	pOutput->next = NULL;

	if (!pLoadEntity->pEntity->epairs)
	{
		pLoadEntity->pEntity->epairs = pOutput;
	}
	else
	{
		epair_t *ep;
		for ( ep = pLoadEntity->pEntity->epairs; ep->next != NULL; ep = ep->next )
		{
		}
		ep->next = pOutput;
	}

	return(ChunkFile_Ok);
}


ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity)
{
	return g_LoadingMap->LoadSolidCallback( pFile, pLoadEntity );
};

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pFile - 
//			pParent - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapFile::LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity)
{
	if (nummapbrushes == MAX_MAP_BRUSHES)
	{
		g_MapError.ReportError ("nummapbrushes == MAX_MAP_BRUSHES");
	}

	mapbrush_t *b = &mapbrushes[nummapbrushes];
	b->original_sides = &brushsides[nummapbrushsides];
	b->entitynum = num_entities-1;
	b->brushnum = nummapbrushes - pLoadEntity->pEntity->firstbrush;

	LoadSide_t SideInfo;
	SideInfo.pBrush = b;
	SideInfo.nSideIndex = 0;
	SideInfo.nBaseContents = pLoadEntity->nBaseContents;
	SideInfo.nBaseFlags = pLoadEntity->nBaseFlags;

	//
	// Set up handlers for the subchunks that we are interested in.
	//
	CChunkHandlerMap Handlers;
	Handlers.AddHandler("side", (ChunkHandler_t)::LoadSideCallback, &SideInfo);

	//
	// Read the solid chunk.
	//
	pFile->PushHandlers(&Handlers);
	ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSolidKeyCallback, b);
	pFile->PopHandlers();

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

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

		// allow water brushes to be removed
		if (nowater && (b->contents & MASK_WATER) )
		{
			b->numsides = 0;
			return(ChunkFile_Ok);
		}

		// 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
		//
		// only do this on the world entity
		//
		if ( b->entitynum == 0 )
		{
			if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) )
			{
				if ( g_ClipTexinfo < 0 )
				{
					g_ClipTexinfo = b->original_sides[0].texinfo;
				}
				c_clipbrushes++;
				for (int 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];
			Vector	origin;

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

			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(ChunkFile_Ok);
		}

#ifdef VSVMFIO
		if ( CVmfImport::GetVmfImporter() )
		{
			CVmfImport::GetVmfImporter()->MapBrushToMayaCallback( b );
		}
#endif // VSVMFIO

		//
		// find a map brushes with displacement surfaces and remove them from the "world"
		//
		if( HasDispInfo( b ) )
		{
			// add the base face data to the displacement surface
			DispGetFaceInfo( b );			

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

			return( ChunkFile_Ok );
		}

		AddBrushBevels (b);

		nummapbrushes++;
		pLoadEntity->pEntity->numbrushes++;		
	}
	else
	{
		return eResult;
	}

	return(ChunkFile_Ok);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pFile - 
//			parent - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush)
{
	if (!stricmp(szKey, "id"))
	{
		pLoadBrush->id = atoi(szValue);
		g_MapError.BrushState(pLoadBrush->id);
	}

	return ChunkFile_Ok;
}


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

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

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

	fprintf (f, "{\n\"classname\" \"worldspawn\"\n");
	fprintf( f, "\"mapversion\" \"220\"\n\"sounds\" \"1\"\n\"MaxRange\" \"4096\"\n\"mapversion\" \"220\"\n\"wad\" \"vert.wad;dev.wad;generic.wad;spire.wad;urb.wad;cit.wad;water.wad\"\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 1 -512 ] [ 0 -1 0 -256 ] 0 1 1 \n", 
				TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) );

			FreeWinding (w);
		}
		fprintf (f, "}\n");
	}
	fprintf (f, "}\n");

	fclose (f);

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


//-----------------------------------------------------------------------------
// Purpose: load in the displacement info "chunk" from the .map file into the
//          vbsp map displacement info data structure
//  Output: return the pointer to the displacement map
//-----------------------------------------------------------------------------
mapdispinfo_t *ParseDispInfoChunk( void )
{
    int             i, j;
    int             vertCount;
    mapdispinfo_t   *pMapDispInfo;

    //
    // check to see if we exceeded the maximum displacement info list size
    //
    if( nummapdispinfo > MAX_MAP_DISPINFO )
        g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO");

    // get a pointer to the next available displacement info slot
    pMapDispInfo = &mapdispinfo[nummapdispinfo];
    nummapdispinfo++;

    //
    // get the chunk opener - "{"
    //
    GetToken( false );
    if( strcmp( token, "{" ) )
        g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - {" );

    //
    //
    // get the displacement info attribs
    //
    //

    // power
    GetToken( true );
    pMapDispInfo->power = atoi( token );

    // u and v mapping axes
    for( i = 0; i < 2; i++ )
    {
        GetToken( false );
        if( strcmp( token, "[" ) )
            g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - [" );

        for( j = 0; j < 3; j++ )
        {
            GetToken( false );

            if( i == 0 )
            {
                pMapDispInfo->uAxis[j] = atof( token );
            }
            else
            {
                pMapDispInfo->vAxis[j] = atof( token );
            }
        }

        GetToken( false );
        if( strcmp( token, "]" ) )
            g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - ]" );
    }

    // max displacement value
   	if( g_nMapFileVersion < 350 )
	{
		GetToken( false );
		pMapDispInfo->maxDispDist = atof( token );
	}

    // minimum tesselation value
    GetToken( false );
    pMapDispInfo->minTess = atoi( token );

    // light smoothing angle
    GetToken( false );
    pMapDispInfo->smoothingAngle = atof( token );

    //
    // get the displacement info displacement normals
    //
    GetToken( true );
    pMapDispInfo->vectorDisps[0][0] = atof( token );
    GetToken( false );
    pMapDispInfo->vectorDisps[0][1] = atof( token );
    GetToken( false );
    pMapDispInfo->vectorDisps[0][2] = atof( token );

    vertCount = ( ( ( 1 << pMapDispInfo->power ) + 1 ) * ( ( 1 << pMapDispInfo->power ) + 1 ) );
    for( i = 1; i < vertCount; i++ )
    {
        GetToken( false );
        pMapDispInfo->vectorDisps[i][0] = atof( token );
        GetToken( false );
        pMapDispInfo->vectorDisps[i][1] = atof( token );
        GetToken( false );
        pMapDispInfo->vectorDisps[i][2] = atof( token );
    }

    //
    // get the displacement info displacement values
    //
    GetToken( true );
    pMapDispInfo->dispDists[0] = atof( token );

    for( i = 1; i < vertCount; i++ )
    {
        GetToken( false );
        pMapDispInfo->dispDists[i] = atof( token );
    }

    //
    // get the chunk closer - "}"
    //
    GetToken( true );
    if( strcmp( token, "}" ) )
        g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - }" );
    
    // return the index of the displacement info slot
    return pMapDispInfo;
}