diff --git a/src/aas/AASFile.cpp b/src/aas/AASFile.cpp
index 15720af..9aa98fd 100644
--- a/src/aas/AASFile.cpp
+++ b/src/aas/AASFile.cpp
@@ -161,6 +161,7 @@ idAASSettings::idAASSettings( void ) {
 	boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) );
 	usePatches = false;
 // jmarshall - aas 1.08
+	generateAllFaces = false;
 	generateTacticalFeatures = false;
 // jmarshall end
 	writeBrushMap = false;
@@ -312,6 +313,9 @@ bool idAASSettings::FromParser( idLexer &src ) {
 		else if ( token == "allowFlyReachabilities" ) {
 			if ( !ParseBool( src, allowFlyReachabilities ) ) { return false; }
 		}
+		else if ( token == "generateAllFaces" ) {
+			if ( !ParseBool( src, generateAllFaces ) ) { return false; }
+		}
 		else if ( token == "fileExtension" ) {
 			src.ExpectTokenString( "=" );
 			src.ExpectTokenType( TT_STRING, 0, &token );
@@ -667,7 +671,7 @@ bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) {
 	name = fileName;
 	crc = mapFileCRC;
 
-	aasFile = fileSystem->OpenFileWrite( fileName, "fs_cdpath" );
+	aasFile = fileSystem->OpenFileWrite( fileName, "fs_devpath" );
 	if ( !aasFile ) {
 		common->Error( "Error opening %s", fileName.c_str() );
 		return false;
@@ -731,7 +735,7 @@ bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) {
 			num++;
 		}
 // jmarshall: AAS 1.08 - numFeatures/firstFeature
-		aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents,
+		aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents,
 						areas[i].firstFace, areas[i].numFaces, areas[i].cluster, areas[i].clusterAreaNum, /*areas[i].numFeatures*/ 0, /*areas[i].firstFeature*/ 0, num );
 // jmarshall end
 		for ( reach = areas[i].reach; reach; reach = reach->next ) {
diff --git a/src/aas/AASFile_optimize.cpp b/src/aas/AASFile_optimize.cpp
index 8fbaff6..d435925 100644
--- a/src/aas/AASFile_optimize.cpp
+++ b/src/aas/AASFile_optimize.cpp
@@ -66,9 +66,11 @@ void idAASFileLocal::Optimize( void ) {
 	newVertices.Resize( vertices.Num() );
 	newEdges.Resize( edges.Num() );
 	newEdges.SetNum( 1, false );
+	memset( &newEdges[0], 0, sizeof( newEdges[0] ) );
 	newEdgeIndex.Resize( edgeIndex.Num() );
 	newFaces.Resize( faces.Num() );
 	newFaces.SetNum( 1, false );
+	memset( &newFaces[0], 0, sizeof( newFaces[0] ) );
 	newFaceIndex.Resize( faceIndex.Num() );
 
 	for ( i = 0; i < areas.Num(); i++ ) {
diff --git a/src/framework/Common.cpp b/src/framework/Common.cpp
index 39a167c..37d66d5 100644
--- a/src/framework/Common.cpp
+++ b/src/framework/Common.cpp
@@ -4641,7 +4641,9 @@ void idCommonLocal::InitCommands( void ) {
 	cmdSystem->AddCommand( "performancePresetSelfTest", Com_PerformancePresetSelfTest_f, CMD_FL_SYSTEM, "validates openQ4 performance preset commands and cvar mappings" );
 
 	cmdSystem->AddCommand("dmap", Dmap_f, CMD_FL_TOOL, "compiles a map", idCmdSystem::ArgCompletion_MapName);
-	//cmdSystem->AddCommand("runAAS", RunAAS_f, CMD_FL_TOOL, "compiles an AAS file for a map", idCmdSystem::ArgCompletion_MapName);
+#if !defined( __EMSCRIPTEN__ )
+	cmdSystem->AddCommand("runAAS", RunAAS_f, CMD_FL_TOOL, "compiles an AAS file for a map", idCmdSystem::ArgCompletion_MapName);
+#endif
 
 #ifdef ID_ALLOW_TOOLS
 	// compilers	
diff --git a/src/tools/compilers/aas/AASBuild.cpp b/src/tools/compilers/aas/AASBuild.cpp
new file mode 100644
index 0000000..59a5c0d
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild.cpp
@@ -0,0 +1,1044 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "framework/FileSystem.h"
+#include "renderer/RenderWorld.h"
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+#define BFL_PATCH		0x1000
+
+//===============================================================
+//
+//	idAASBuild
+//
+//===============================================================
+
+/*
+============
+idAASBuild::idAASBuild
+============
+*/
+idAASBuild::idAASBuild( void ) {
+	file = NULL;
+	procNodes = NULL;
+	numProcNodes = 0;
+	numGravitationalSubdivisions = 0;
+	numMergedLeafNodes = 0;
+	numLedgeSubdivisions = 0;
+	ledgeMap = NULL;
+}
+
+/*
+============
+idAASBuild::~idAASBuild
+============
+*/
+idAASBuild::~idAASBuild( void ) {
+	Shutdown();
+}
+
+/*
+================
+idAASBuild::Shutdown
+================
+*/
+void idAASBuild::Shutdown( void ) {
+	aasSettings = NULL;
+	if ( file ) {
+		delete file;
+		file = NULL;
+	}
+	DeleteProcBSP();
+	numGravitationalSubdivisions = 0;
+	numMergedLeafNodes = 0;
+	numLedgeSubdivisions = 0;
+	ledgeList.Clear();
+	if ( ledgeMap ) {
+		delete ledgeMap;
+		ledgeMap = NULL;
+	}
+}
+
+/*
+================
+idAASBuild::ParseProcNodes
+================
+*/
+void idAASBuild::ParseProcNodes( idLexer *src ) {
+	int i;
+
+	src->ExpectTokenString( "{" );
+
+	idAASBuild::numProcNodes = src->ParseInt();
+	if ( idAASBuild::numProcNodes < 0 ) {
+		src->Error( "idAASBuild::ParseProcNodes: bad numProcNodes" );
+	}
+	idAASBuild::procNodes = (aasProcNode_t *)Mem_ClearedAlloc( idAASBuild::numProcNodes * sizeof( aasProcNode_t ) );
+
+	for ( i = 0; i < idAASBuild::numProcNodes; i++ ) {
+		aasProcNode_t *node;
+
+		node = &(idAASBuild::procNodes[i]);
+
+		src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
+		node->children[0] = src->ParseInt();
+		node->children[1] = src->ParseInt();
+	}
+
+	src->ExpectTokenString( "}" );
+}
+
+/*
+================
+idAASBuild::LoadProcBSP
+================
+*/
+bool idAASBuild::LoadProcBSP( const char *name, ID_TIME_T minFileTime ) {
+	idStr fileName;
+	idToken token;
+	idLexer *src;
+
+	// load it
+	fileName = name;
+	fileName.SetFileExtension( PROC_FILE_EXT );
+	src = new idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
+	if ( !src->IsLoaded() ) {
+		common->Warning("idAASBuild::LoadProcBSP: couldn't load %s", fileName.c_str() );
+		delete src;
+		return false;
+	}
+
+	// if the file is too old
+	if ( src->GetFileTime() < minFileTime ) {
+		delete src;
+		return false;
+	}
+
+	if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) {
+		common->Warning( "idAASBuild::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID );
+		delete src;
+		return false;
+	}
+
+	if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILEVERSION ) ) {
+		common->Warning( "idAASBuild::LoadProcBSP: bad version '%s' instead of '%s'", token.c_str(), PROC_FILEVERSION );
+		delete src;
+		return false;
+	}
+
+	// Quake 4 proc files include the source map CRC after the version.
+	if ( !src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) {
+		common->Warning( "idAASBuild::LoadProcBSP: missing map CRC in %s", fileName.c_str() );
+		delete src;
+		return false;
+	}
+
+	// parse the file
+	while ( 1 ) {
+		if ( !src->ReadToken( &token ) ) {
+			break;
+		}
+
+		if ( token == "model" ) {
+			src->SkipBracedSection();
+			continue;
+		}
+
+		if ( token == "shadowModel" ) {
+			src->SkipBracedSection();
+			continue;
+		}
+
+		if ( token == "interAreaPortals" ) {
+			src->SkipBracedSection();
+			continue;
+		}
+
+		if ( token == "nodes" ) {
+			idAASBuild::ParseProcNodes( src );
+			break;
+		}
+
+		src->Error( "idAASBuild::LoadProcBSP: bad token \"%s\"", token.c_str() );
+	}
+
+	delete src;
+
+	return true;
+}
+
+/*
+============
+idAASBuild::DeleteProcBSP
+============
+*/
+void idAASBuild::DeleteProcBSP( void ) {
+	if ( procNodes ) {
+		Mem_Free( procNodes );
+		procNodes = NULL;
+	}
+	numProcNodes = 0;
+}
+
+/*
+============
+idAASBuild::ChoppedAwayByProcBSP
+============
+*/
+bool idAASBuild::ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) {
+	int res;
+	idFixedWinding back;
+	aasProcNode_t *node;
+	float dist;
+
+	do {
+		node = idAASBuild::procNodes + nodeNum;
+		dist = node->plane.Normal() * origin + node->plane[3];
+		if ( dist > radius ) {
+			res = SIDE_FRONT;
+		}
+		else if ( dist < -radius ) {
+			res = SIDE_BACK;
+		}
+		else {
+			res = w->Split( &back, node->plane, ON_EPSILON );
+		}
+		if ( res == SIDE_FRONT ) {
+			nodeNum = node->children[0];
+		}
+		else if ( res == SIDE_BACK ) {
+			nodeNum = node->children[1];
+		}
+		else if ( res == SIDE_ON ) {
+			// continue with the side the winding faces
+			if ( node->plane.Normal() * normal > 0.0f ) {
+				nodeNum = node->children[0];
+			}
+			else {
+				nodeNum = node->children[1];
+			}
+		}
+		else {
+			// if either node is not solid
+			if ( node->children[0] < 0 || node->children[1] < 0 ) {
+				return false;
+			}
+			// only recurse if the node is not solid
+			if ( node->children[1] > 0 ) {
+				if ( !idAASBuild::ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) {
+					return false;
+				}
+			}
+			nodeNum = node->children[0];
+		}
+	} while ( nodeNum > 0 );
+	if ( nodeNum < 0 ) {
+		return false;
+	}
+	return true;
+}
+
+/*
+============
+idAASBuild::ClipBrushSidesWithProcBSP
+============
+*/
+void idAASBuild::ClipBrushSidesWithProcBSP( idBrushList &brushList ) {
+	int i, clippedSides;
+	idBrush *brush;
+	idFixedWinding neww;
+	idBounds bounds;
+	float radius;
+	idVec3 origin;
+
+	// if the .proc file has no BSP tree
+	if ( idAASBuild::procNodes == NULL ) {
+		return;
+	}
+
+	clippedSides = 0;
+	for ( brush = brushList.Head(); brush; brush = brush->Next() ) {
+		for ( i = 0; i < brush->GetNumSides(); i++ ) {
+
+			if ( !brush->GetSide(i)->GetWinding() ) {
+				continue;
+			}
+
+			// make a local copy of the winding
+			neww = *brush->GetSide(i)->GetWinding();
+			neww.GetBounds( bounds );
+			origin = (bounds[1] - bounds[0]) * 0.5f;
+			radius = origin.Length() + ON_EPSILON;
+			origin = bounds[0] + origin;
+
+			if ( ChoppedAwayByProcBSP( 0, &neww, brush->GetSide(i)->GetPlane().Normal(), origin, radius ) ) {
+				brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER );
+				clippedSides++;
+			}
+		}
+	}
+
+	common->Printf( "%6d brush sides clipped\n", clippedSides );
+}
+
+/*
+============
+idAASBuild::ContentsForAAS
+============
+*/
+int idAASBuild::ContentsForAAS( int contents ) {
+	int c;
+
+	if ( contents & ( CONTENTS_SOLID|CONTENTS_AAS_SOLID|CONTENTS_MONSTERCLIP ) ) {
+		return AREACONTENTS_SOLID;
+	}
+	c = 0;
+	if ( contents & CONTENTS_WATER ) {
+		c |= AREACONTENTS_WATER;
+	}
+	if ( contents & CONTENTS_AREAPORTAL ) {
+		c |= AREACONTENTS_CLUSTERPORTAL;
+	}
+	if ( contents & CONTENTS_AAS_OBSTACLE ) {
+		c |= AREACONTENTS_OBSTACLE;
+	}
+	return c;
+}
+
+/*
+============
+idAASBuild::AddBrushForMapBrush
+============
+*/
+idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
+	int contents, i;
+	idMapBrushSide *mapSide;
+	const idMaterial *mat;
+	idList<idBrushSide *> sideList;
+	idBrush *brush;
+	idPlane plane;
+
+	contents = 0;
+	for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
+		mapSide = mapBrush->GetSide(i);
+		mat = declManager->FindMaterial( mapSide->GetMaterial() );
+		contents |= mat->GetContentFlags();
+		plane = mapSide->GetPlane();
+		plane.FixDegeneracies( DEGENERATE_DIST_EPSILON );
+		sideList.Append( new idBrushSide( plane, -1 ) );
+	}
+
+	contents = ContentsForAAS( contents );
+	if ( !contents ) {
+		for ( i = 0; i < sideList.Num(); i++ ) {
+			delete sideList[i];
+		}
+		return brushList;
+	}
+
+	brush = new idBrush();
+	brush->SetContents( contents );
+
+	if ( !brush->FromSides( sideList ) ) {
+		common->Warning( "brush primitive %d on entity %d is degenerate", primitiveNum, entityNum );
+		delete brush;
+		return brushList;
+	}
+
+	brush->SetEntityNum( entityNum );
+	brush->SetPrimitiveNum( primitiveNum );
+	brush->Transform( origin, axis );
+	brushList.AddToTail( brush );
+
+	return brushList;
+}
+
+/*
+============
+idAASBuild::AddBrushesForPatch
+============
+*/
+idBrushList idAASBuild::AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
+	int i, j, contents, validBrushes;
+	float dot;
+	int v1, v2, v3, v4;
+	idFixedWinding w;
+	idPlane plane;
+	idVec3 d1, d2;
+	idBrush *brush;
+	idSurface_Patch mesh;
+	const idMaterial *mat;
+
+	mat = declManager->FindMaterial( mapPatch->GetMaterial() );
+	contents = ContentsForAAS( mat->GetContentFlags() );
+
+	if ( !contents ) {
+		return brushList;
+	}
+
+	mesh = idSurface_Patch( *mapPatch );
+
+	// if the patch has an explicit number of subdivisions use it to avoid cracks
+	if ( mapPatch->GetExplicitlySubdivided() ) {
+		mesh.SubdivideExplicit( mapPatch->GetHorzSubdivisions(), mapPatch->GetVertSubdivisions(), false, true );
+	} else {
+		mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false );
+	}
+
+	validBrushes = 0;
+
+	for ( i = 0; i < mesh.GetWidth() - 1; i++ ) {
+		for ( j = 0; j < mesh.GetHeight() - 1; j++ ) {
+
+			v1 = j * mesh.GetWidth() + i;
+			v2 = v1 + 1;
+			v3 = v1 + mesh.GetWidth() + 1;
+			v4 = v1 + mesh.GetWidth();
+
+			d1 = mesh[v2].xyz - mesh[v1].xyz;
+			d2 = mesh[v3].xyz - mesh[v1].xyz;
+			plane.SetNormal( d1.Cross(d2) );
+			if ( plane.Normalize() != 0.0f ) {
+				plane.FitThroughPoint( mesh[v1].xyz );
+				dot = plane.Distance( mesh[v4].xyz );
+				// if we can turn it into a quad
+				if ( idMath::Fabs(dot) < 0.1f ) {
+					w.Clear();
+					w += mesh[v1].xyz;
+					w += mesh[v2].xyz;
+					w += mesh[v3].xyz;
+					w += mesh[v4].xyz;
+
+					brush = new idBrush();
+					brush->SetContents( contents );
+					if ( brush->FromWinding( w, plane ) ) {
+						brush->SetEntityNum( entityNum );
+						brush->SetPrimitiveNum( primitiveNum );
+						brush->SetFlag( BFL_PATCH );
+						brush->Transform( origin, axis );
+						brushList.AddToTail( brush );
+						validBrushes++;
+					}
+					else {
+						delete brush;
+					}
+					continue;
+				}
+				else {
+					// create one of the triangles
+					w.Clear();
+					w += mesh[v1].xyz;
+					w += mesh[v2].xyz;
+					w += mesh[v3].xyz;
+
+					brush = new idBrush();
+					brush->SetContents( contents );
+					if ( brush->FromWinding( w, plane ) ) {
+						brush->SetEntityNum( entityNum );
+						brush->SetPrimitiveNum( primitiveNum );
+						brush->SetFlag( BFL_PATCH );
+						brush->Transform( origin, axis );
+						brushList.AddToTail( brush );
+						validBrushes++;
+					}
+					else {
+						delete brush;
+					}
+				}
+			}
+			// create the other triangle
+			d1 = mesh[v3].xyz - mesh[v1].xyz;
+			d2 = mesh[v4].xyz - mesh[v1].xyz;
+			plane.SetNormal( d1.Cross(d2) );
+			if ( plane.Normalize() != 0.0f ) {
+				plane.FitThroughPoint( mesh[v1].xyz );
+
+				w.Clear();
+				w += mesh[v1].xyz;
+				w += mesh[v3].xyz;
+				w += mesh[v4].xyz;
+
+				brush = new idBrush();
+				brush->SetContents( contents );
+				if ( brush->FromWinding( w, plane ) ) {
+					brush->SetEntityNum( entityNum );
+					brush->SetPrimitiveNum( primitiveNum );
+					brush->SetFlag( BFL_PATCH );
+					brush->Transform( origin, axis );
+					brushList.AddToTail( brush );
+					validBrushes++;
+				}
+				else {
+					delete brush;
+				}
+			}
+		}
+	}
+
+	if ( !validBrushes ) {
+		common->Warning( "patch primitive %d on entity %d is completely degenerate", primitiveNum, entityNum );
+	}
+
+	return brushList;
+}
+
+/*
+============
+idAASBuild::AddBrushesForMapEntity
+============
+*/
+idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList ) {
+	int i;
+	idVec3 origin;
+	idMat3 axis;
+
+	if ( mapEnt->GetNumPrimitives() < 1 ) {
+		return brushList;
+	}
+
+	mapEnt->epairs.GetVector( "origin", "0 0 0", origin );
+	if ( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) {
+		float angle = mapEnt->epairs.GetFloat( "angle" );
+		if ( angle != 0.0f ) {
+			axis = idAngles( 0.0f, angle, 0.0f ).ToMat3();
+		} else {
+			axis.Identity();
+		}
+	}
+
+	for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
+		idMapPrimitive	*mapPrim;
+
+		mapPrim = mapEnt->GetPrimitive(i);
+		if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
+			brushList = AddBrushesForMapBrush( static_cast<idMapBrush*>(mapPrim), origin, axis, entityNum, i, brushList );
+			continue;
+		}
+		if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
+			if ( aasSettings->usePatches ) {
+				brushList = AddBrushesForMapPatch( static_cast<idMapPatch*>(mapPrim), origin, axis, entityNum, i, brushList );
+			}
+			continue;
+		}
+	}
+
+	return brushList;
+}
+
+/*
+============
+idAASBuild::AddBrushesForMapFile
+============
+*/
+idBrushList idAASBuild::AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList ) {
+	int i;
+
+	common->Printf( "[Brush Load]\n" );
+
+	brushList = AddBrushesForMapEntity( mapFile->GetEntity( 0 ), 0, brushList );
+
+	for ( i = 1; i < mapFile->GetNumEntities(); i++ ) {
+		const char *classname = mapFile->GetEntity( i )->epairs.GetString( "classname" );
+
+		if ( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 ) {
+			brushList = AddBrushesForMapEntity( mapFile->GetEntity( i ), i, brushList );
+		}
+	}
+
+	common->Printf( "%6d brushes\n", brushList.Num() );
+
+	return brushList;
+}
+
+/*
+============
+idAASBuild::CheckForEntities
+============
+*/
+bool idAASBuild::CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const {
+	int		i;
+	idStr	classname;
+
+	com_editors |= EDITOR_AAS;
+
+	for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
+		if ( !mapFile->GetEntity(i)->epairs.GetString( "classname", "", classname ) ) {
+			continue;
+		}
+
+		if ( aasSettings->ValidEntity( classname ) ) {
+			entityClassNames.AddUnique( classname );
+		}
+	}
+
+	com_editors &= ~EDITOR_AAS;
+
+	return ( entityClassNames.Num() != 0 );
+}
+
+/*
+============
+MergeAllowed
+============
+*/
+bool MergeAllowed( idBrush *b1, idBrush *b2 ) {
+	return ( b1->GetContents() == b2->GetContents() && !( ( b1->GetFlags() | b2->GetFlags() ) & BFL_PATCH ) );
+}
+
+/*
+============
+ExpandedChopAllowed
+============
+*/
+bool ExpandedChopAllowed( idBrush *b1, idBrush *b2 ) {
+	return ( b1->GetContents() == b2->GetContents() );
+}
+
+/*
+============
+ExpandedMergeAllowed
+============
+*/
+bool ExpandedMergeAllowed( idBrush *b1, idBrush *b2 ) {
+	return ( b1->GetContents() == b2->GetContents() );
+}
+
+/*
+============
+idAASBuild::ChangeMultipleBoundingBoxContents
+============
+*/
+void idAASBuild::ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask ) {
+	while( node ) {
+		if ( !( node->GetContents() & mask ) ) {
+			node->SetContents( node->GetContents() & ~AREACONTENTS_SOLID );
+		}
+		ChangeMultipleBoundingBoxContents_r( node->GetChild( 0 ), mask );
+		node = node->GetChild( 1 );
+	}
+}
+
+/*
+============
+idAASBuild::Build
+============
+*/
+bool idAASBuild::Build( const idStr &fileName, const idAASSettings *settings ) {
+	int i, bit, mask, startTime;
+	idMapFile * mapFile;
+	idBrushList brushList;
+	idList<idBrushList*> expandedBrushes;
+	idBrush *b;
+	idBrushBSP bsp;
+	idStr name;
+	idAASReach reach;
+	idAASCluster cluster;
+	idStrList entityClassNames;
+
+	startTime = Sys_Milliseconds();
+
+	Shutdown();
+
+	aasSettings = settings;
+
+	name = fileName;
+	name.SetFileExtension( "map" );
+
+	mapFile = new idMapFile;
+	if ( !mapFile->Parse( name ) ) {
+		delete mapFile;
+		common->Error( "Couldn't load map file: '%s'", name.c_str() );
+		return false;
+	}
+
+	// check if this map has any entities that use this AAS file
+	if ( !CheckForEntities( mapFile, entityClassNames ) ) {
+		delete mapFile;
+		common->Printf( "no entities in map that use %s\n", settings->fileExtension.c_str() );
+		return true;
+	}
+
+	// load map file brushes
+	brushList = AddBrushesForMapFile( mapFile, brushList );
+
+	// if empty map
+	if ( brushList.Num() == 0 ) {
+		delete mapFile;
+		common->Error( "%s is empty", name.c_str() );
+		return false;
+	}
+
+	// merge as many brushes as possible before expansion
+	brushList.Merge( MergeAllowed );
+
+	// if there is a .proc file newer than the .map file
+	if ( LoadProcBSP( fileName, mapFile->GetFileTime() ) ) {
+		ClipBrushSidesWithProcBSP( brushList );
+		DeleteProcBSP();
+	}
+
+	// make copies of the brush list
+	expandedBrushes.Append( &brushList );
+	for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
+		expandedBrushes.Append( brushList.Copy() );
+	}
+
+	// expand brushes for the axial bounding boxes
+	mask = AREACONTENTS_SOLID;
+	for ( i = 0; i < expandedBrushes.Num(); i++ ) {
+		for ( b = expandedBrushes[i]->Head(); b; b = b->Next() ) {
+			b->ExpandForAxialBox( aasSettings->boundingBoxes[i] );
+			bit = 1 << ( i + AREACONTENTS_BBOX_BIT );
+			mask |= bit;
+			b->SetContents( b->GetContents() | bit );
+		}
+	}
+
+	// move all brushes back into the original list
+	for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
+		brushList.AddToTail( *expandedBrushes[i] );
+		delete expandedBrushes[i];
+	}
+
+	if ( aasSettings->writeBrushMap ) {
+		bsp.WriteBrushMap( fileName, "_" + aasSettings->fileExtension, AREACONTENTS_SOLID );
+	}
+
+	// build BSP tree from brushes
+	bsp.Build( brushList, AREACONTENTS_SOLID, ExpandedChopAllowed, ExpandedMergeAllowed );
+
+	// only solid nodes with all bits set for all bounding boxes need to stay solid
+	ChangeMultipleBoundingBoxContents_r( bsp.GetRootNode(), mask );
+
+	// portalize the bsp tree
+	bsp.Portalize();
+
+	// remove subspaces not reachable by entities
+	if ( !bsp.RemoveOutside( mapFile, AREACONTENTS_SOLID, entityClassNames ) ) {
+		bsp.LeakFile( name );
+		delete mapFile;
+		common->Printf( "%s has no outside", name.c_str() );
+		return false;
+	}
+
+	// gravitational subdivision
+	GravitationalSubdivision( bsp );
+
+	// merge portals where possible
+	bsp.MergePortals( AREACONTENTS_SOLID );
+
+	// melt portal windings
+	bsp.MeltPortals( AREACONTENTS_SOLID );
+
+	if ( aasSettings->writeBrushMap ) {
+		WriteLedgeMap( fileName, "_" + aasSettings->fileExtension + "_ledge" );
+	}
+
+	// ledge subdivisions
+	LedgeSubdivision( bsp );
+
+	// merge leaf nodes
+	MergeLeafNodes( bsp );
+
+	// merge portals where possible
+	bsp.MergePortals( AREACONTENTS_SOLID );
+
+	// melt portal windings
+	bsp.MeltPortals( AREACONTENTS_SOLID );
+
+	// store the file from the bsp tree
+	StoreFile( bsp );
+	file->settings = *aasSettings;
+
+	// calculate reachability
+	reach.Build( mapFile, file );
+
+	// build clusters
+	cluster.Build( file );
+
+	// optimize the file
+	if ( !aasSettings->noOptimize ) {
+		file->Optimize();
+	}
+
+	// write the file
+	name.SetFileExtension( aasSettings->fileExtension );
+	file->Write( name, mapFile->GetGeometryCRC() );
+
+	// delete the map file
+	delete mapFile;
+
+	common->Printf( "%6d seconds to create AAS\n", (Sys_Milliseconds() - startTime) / 1000 );
+
+	return true;
+}
+
+/*
+============
+idAASBuild::BuildReachability
+============
+*/
+bool idAASBuild::BuildReachability( const idStr &fileName, const idAASSettings *settings ) {
+	int startTime;
+	idMapFile * mapFile;
+	idStr name;
+	idAASReach reach;
+	idAASCluster cluster;
+
+	startTime = Sys_Milliseconds();
+
+	aasSettings = settings;
+
+	name = fileName;
+	name.SetFileExtension( "map" );
+
+	mapFile = new idMapFile;
+	if ( !mapFile->Parse( name ) ) {
+		delete mapFile;
+		common->Error( "Couldn't load map file: '%s'", name.c_str() );
+		return false;
+	}
+
+	file = new idAASFileLocal();
+
+	name.SetFileExtension( aasSettings->fileExtension );
+	if ( !file->Load( name, 0 ) ) {
+		delete mapFile;
+		common->Error( "Couldn't load AAS file: '%s'", name.c_str() );
+		return false;
+	}
+
+	file->settings = *aasSettings;
+
+	// calculate reachability
+	reach.Build( mapFile, file );
+
+	// build clusters
+	cluster.Build( file );
+
+	// write the file
+	file->Write( name, mapFile->GetGeometryCRC() );
+
+	// delete the map file
+	delete mapFile;
+
+	common->Printf( "%6d seconds to calculate reachability\n", (Sys_Milliseconds() - startTime) / 1000 );
+
+	return true;
+}
+
+/*
+============
+ParseOptions
+============
+*/
+int ParseOptions( const idCmdArgs &args, idAASSettings &settings ) {
+	int i;
+	idStr str;
+
+	for ( i = 1; i < args.Argc(); i++ ) {
+
+		str = args.Argv( i );
+		str.StripLeading( '-' );
+
+		if ( str.Icmp( "usePatches" ) == 0 ) {
+			settings.usePatches = true;
+			common->Printf( "usePatches = true\n" );
+		} else if ( str.Icmp( "writeBrushMap" ) == 0 ) {
+			settings.writeBrushMap = true;
+			common->Printf( "writeBrushMap = true\n" );
+		} else if ( str.Icmp( "playerFlood" ) == 0 ) {
+			settings.playerFlood = true;
+			common->Printf( "playerFlood = true\n" );
+		} else if ( str.Icmp( "noOptimize" ) == 0 ) {
+			settings.noOptimize = true;
+			common->Printf( "noOptimize = true\n" );
+		}
+	}
+	return args.Argc() - 1;
+}
+
+/*
+============
+RunAAS_f
+============
+*/
+void RunAAS_f( const idCmdArgs &args ) {
+	int i;
+	idAASBuild aas;
+	idAASSettings settings;
+	idStr mapName;
+
+	if ( args.Argc() <= 1 ) {
+		common->Printf( "runAAS [options] <mapfile>\n"
+					"options:\n"
+					"  -usePatches        = use bezier patches for collision detection.\n"
+					"  -writeBrushMap     = write a brush map with the AAS geometry.\n"
+					"  -playerFlood       = use player spawn points as valid AAS positions.\n" );
+		return;
+	}
+
+	common->ClearWarnings( "compiling AAS" );
+
+	common->SetRefreshOnPrint( true );
+
+	// get the aas settings definitions
+	const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
+	if ( !dict ) {
+		common->Error( "Unable to find entityDef for 'aas_types'" );
+	}
+
+	const idKeyValue *kv = dict->MatchPrefix( "type" );
+	while( kv != NULL ) {
+		const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
+		if ( !settingsDict ) {
+			common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
+		} else {
+			settings.FromDict( kv->GetValue(), settingsDict );
+			i = ParseOptions( args, settings );
+			mapName = args.Argv(i);
+			mapName.BackSlashesToSlashes();
+			if ( mapName.Icmpn( "maps/", 4 ) != 0 ) {
+				mapName = "maps/" + mapName;
+			}
+			aas.Build( mapName, &settings );
+		}
+
+		kv = dict->MatchPrefix( "type", kv );
+		if ( kv ) {
+			common->Printf( "=======================================================\n" );
+		}
+	}
+	common->SetRefreshOnPrint( false );
+	common->PrintWarnings();
+}
+
+/*
+============
+RunAASDir_f
+============
+*/
+void RunAASDir_f( const idCmdArgs &args ) {
+	int i;
+	idAASBuild aas;
+	idAASSettings settings;
+	idFileList *mapFiles;
+
+	if ( args.Argc() <= 1 ) {
+		common->Printf( "runAASDir <folder>\n" );
+		return;
+	}
+
+	common->ClearWarnings( "compiling AAS" );
+
+	common->SetRefreshOnPrint( true );
+
+	// get the aas settings definitions
+	const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
+	if ( !dict ) {
+		common->Error( "Unable to find entityDef for 'aas_types'" );
+	}
+
+	// scan for .map files
+	mapFiles = fileSystem->ListFiles( idStr("maps/") + args.Argv(1), ".map" );
+
+	// create AAS files for all the .map files
+	for ( i = 0; i < mapFiles->GetNumFiles(); i++ ) {
+		if ( i ) {
+			common->Printf( "=======================================================\n" );
+		}
+
+		const idKeyValue *kv = dict->MatchPrefix( "type" );
+		while( kv != NULL ) {
+			const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
+			if ( !settingsDict ) {
+				common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
+			} else {
+				settings.FromDict( kv->GetValue(), settingsDict );
+				aas.Build( idStr( "maps/" ) + args.Argv( 1 ) + "/" + mapFiles->GetFile( i ), &settings );
+			}
+
+			kv = dict->MatchPrefix( "type", kv );
+			if ( kv ) {
+				common->Printf( "=======================================================\n" );
+			}
+		}
+	}
+
+	fileSystem->FreeFileList( mapFiles );
+
+	common->SetRefreshOnPrint( false );
+	common->PrintWarnings();
+}
+
+/*
+============
+RunReach_f
+============
+*/
+void RunReach_f( const idCmdArgs &args ) {
+	int i;
+	idAASBuild aas;
+	idAASSettings settings;
+
+	if ( args.Argc() <= 1 ) {
+		common->Printf( "runReach [options] <mapfile>\n" );
+		return;
+	}
+
+	common->ClearWarnings( "calculating AAS reachability" );
+
+	common->SetRefreshOnPrint( true );
+
+	// get the aas settings definitions
+	const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
+	if ( !dict ) {
+		common->Error( "Unable to find entityDef for 'aas_types'" );
+	}
+
+	const idKeyValue *kv = dict->MatchPrefix( "type" );
+	while( kv != NULL ) {
+		const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
+		if ( !settingsDict ) {
+			common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
+		} else {
+			settings.FromDict( kv->GetValue(), settingsDict );
+			i = ParseOptions( args, settings );
+			aas.BuildReachability( idStr("maps/") + args.Argv(i), &settings );
+		}
+
+		kv = dict->MatchPrefix( "type", kv );
+		if ( kv ) {
+			common->Printf( "=======================================================\n" );
+		}
+	}
+
+	common->SetRefreshOnPrint( false );
+	common->PrintWarnings();
+}
diff --git a/src/tools/compilers/aas/AASBuild_file.cpp b/src/tools/compilers/aas/AASBuild_file.cpp
new file mode 100644
index 0000000..c5dfb19
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild_file.cpp
@@ -0,0 +1,496 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+#define VERTEX_HASH_BOXSIZE				(1<<6)	// must be power of 2
+#define VERTEX_HASH_SIZE				(VERTEX_HASH_BOXSIZE*VERTEX_HASH_BOXSIZE)
+#define EDGE_HASH_SIZE					(1<<14)
+
+#define INTEGRAL_EPSILON				0.01f
+#define VERTEX_EPSILON					0.1f
+
+#define AAS_PLANE_NORMAL_EPSILON		0.00001f
+#define AAS_PLANE_DIST_EPSILON			0.01f
+
+
+idHashIndex *aas_vertexHash;
+idHashIndex *aas_edgeHash;
+idBounds aas_vertexBounds;
+int aas_vertexShift;
+
+/*
+================
+idAASBuild::SetupHash
+================
+*/
+void idAASBuild::SetupHash( void ) {
+	aas_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 );
+	aas_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 );
+}
+
+/*
+================
+idAASBuild::ShutdownHash
+================
+*/
+void idAASBuild::ShutdownHash( void ) {
+	delete aas_vertexHash;
+	delete aas_edgeHash;
+}
+
+/*
+================
+idAASBuild::ClearHash
+================
+*/
+void idAASBuild::ClearHash( const idBounds &bounds ) {
+	int i;
+	float f, max;
+
+	aas_vertexHash->Clear();
+	aas_edgeHash->Clear();
+	aas_vertexBounds = bounds;
+
+	max = bounds[1].x - bounds[0].x;
+	f = bounds[1].y - bounds[0].y;
+	if ( f > max ) {
+		max = f;
+	}
+	aas_vertexShift = (float) max / VERTEX_HASH_BOXSIZE;
+	for ( i = 0; (1<<i) < aas_vertexShift; i++ ) {
+	}
+	if ( i == 0 ) {
+		aas_vertexShift = 1;
+	}
+	else {
+		aas_vertexShift = i;
+	}
+}
+
+/*
+================
+idAASBuild::HashVec
+================
+*/
+ID_INLINE int idAASBuild::HashVec( const idVec3 &vec ) {
+	int x, y;
+
+	x = (((int) (vec[0] - aas_vertexBounds[0].x + 0.5)) + 2) >> 2;
+	y = (((int) (vec[1] - aas_vertexBounds[0].y + 0.5)) + 2) >> 2;
+	return (x + y * VERTEX_HASH_BOXSIZE) & (VERTEX_HASH_SIZE-1);
+}
+
+/*
+================
+idAASBuild::GetVertex
+================
+*/
+bool idAASBuild::GetVertex( const idVec3 &v, int *vertexNum ) {
+	int i, hashKey, vn;
+	aasVertex_t vert, *p;
+
+	for (i = 0; i < 3; i++) {
+		if ( idMath::Fabs(v[i] - idMath::Rint(v[i])) < INTEGRAL_EPSILON ) {
+			vert[i] = idMath::Rint(v[i]);
+		}
+		else {
+			vert[i] = v[i];
+		}
+	}
+
+	hashKey = idAASBuild::HashVec( vert );
+
+	for ( vn = aas_vertexHash->First( hashKey ); vn >= 0; vn = aas_vertexHash->Next( vn ) ) {
+		p = &file->vertices[vn];
+		// first compare z-axis because hash is based on x-y plane
+		if (idMath::Fabs( vert.z - p->z ) < VERTEX_EPSILON &&
+			idMath::Fabs( vert.x - p->x ) < VERTEX_EPSILON &&
+			idMath::Fabs( vert.y - p->y ) < VERTEX_EPSILON )
+		{
+			*vertexNum = vn;
+			return true;
+		}
+	}
+
+	*vertexNum = file->vertices.Num();
+	aas_vertexHash->Add( hashKey, file->vertices.Num() );
+	file->vertices.Append( vert );
+
+	return false;
+}
+
+/*
+================
+idAASBuild::GetEdge
+================
+*/
+bool idAASBuild::GetEdge( const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ) {
+	int v2num, hashKey, e;
+	int *vertexNum;
+	aasEdge_t edge;
+	bool found;
+
+	if ( v1num != -1 ) {
+		found = true;
+	}
+	else {
+		found = GetVertex( v1, &v1num );
+	}
+	found &= GetVertex( v2, &v2num );
+	// if both vertexes are the same or snapped onto each other
+	if ( v1num == v2num ) {
+		*edgeNum = 0;
+		return true;
+	}
+	hashKey = aas_edgeHash->GenerateKey( v1num, v2num );
+	// if both vertexes where already stored
+	if ( found ) {
+		for ( e = aas_edgeHash->First( hashKey ); e >= 0; e = aas_edgeHash->Next( e ) ) {
+
+			vertexNum = file->edges[e].vertexNum;
+			if ( vertexNum[0] == v2num ) {
+				if ( vertexNum[1] == v1num ) {
+					// negative for a reversed edge
+					*edgeNum = -e;
+					break;
+				}
+			}
+			else if ( vertexNum[0] == v1num ) {
+				if ( vertexNum[1] == v2num ) {
+					*edgeNum = e;
+					break;
+				}
+			}
+		}
+		// if edge found in hash
+		if ( e >= 0 ) {
+			return true;
+		}
+	}
+
+	*edgeNum = file->edges.Num();
+	aas_edgeHash->Add( hashKey, file->edges.Num() );
+
+	edge.vertexNum[0] = v1num;
+	edge.vertexNum[1] = v2num;
+
+	file->edges.Append( edge );
+
+	return false;
+}
+
+/*
+================
+idAASBuild::GetFaceForPortal
+================
+*/
+bool idAASBuild::GetFaceForPortal( idBrushBSPPortal *portal, int side, int *faceNum ) {
+	int i, j, v1num;
+	int numFaceEdges, faceEdges[MAX_POINTS_ON_WINDING];
+	idWinding *w;
+	aasFace_t face;
+
+	if ( portal->GetFaceNum() > 0 ) {
+		if ( side ) {
+			*faceNum = -portal->GetFaceNum();
+		}
+		else {
+			*faceNum = portal->GetFaceNum();
+		}
+		return true;
+	}
+
+	w = portal->GetWinding();
+	// turn the winding into a sequence of edges
+	numFaceEdges = 0;
+	v1num = -1;		// first vertex unknown
+	for ( i = 0; i < w->GetNumPoints(); i++ ) {
+
+		GetEdge( (*w)[i].ToVec3(), (*w)[(i+1)%w->GetNumPoints()].ToVec3(), &faceEdges[numFaceEdges], v1num );
+
+		if ( faceEdges[numFaceEdges] ) {
+			// last vertex of this edge is the first vertex of the next edge
+			v1num = file->edges[ abs(faceEdges[numFaceEdges]) ].vertexNum[ INTSIGNBITNOTSET(faceEdges[numFaceEdges]) ];
+
+			// this edge is valid so keep it
+			numFaceEdges++;
+		}
+	}
+
+	// should have at least 3 edges
+	if ( numFaceEdges < 3 ) {
+		return false;
+	}
+
+	// the polygon is invalid if some edge is found twice
+	for ( i = 0; i < numFaceEdges; i++ ) {
+		for ( j = i+1; j < numFaceEdges; j++ ) {
+			if ( faceEdges[i] == faceEdges[j] || faceEdges[i] == -faceEdges[j] ) {
+				return false;
+			}
+		}
+	}
+
+	portal->SetFaceNum( file->faces.Num() );
+
+	face.planeNum = file->planeList.FindPlane( portal->GetPlane(), AAS_PLANE_NORMAL_EPSILON, AAS_PLANE_DIST_EPSILON );
+	face.flags = portal->GetFlags();
+	face.areas[0] = face.areas[1] = 0;
+	face.firstEdge = file->edgeIndex.Num();
+	face.numEdges = numFaceEdges;
+	for ( i = 0; i < numFaceEdges; i++ ) {
+		file->edgeIndex.Append( faceEdges[i] );
+	}
+	if ( side ) {
+		*faceNum = -file->faces.Num();
+	}
+	else {
+		*faceNum = file->faces.Num();
+	}
+	file->faces.Append( face );
+
+	return true;
+}
+
+/*
+================
+idAASBuild::GetAreaForLeafNode
+================
+*/
+bool idAASBuild::GetAreaForLeafNode( idBrushBSPNode *node, int *areaNum ) {
+	int s, faceNum;
+	idBrushBSPPortal *p;
+	aasArea_t area;
+
+	if ( node->GetAreaNum() ) {
+		*areaNum = -node->GetAreaNum();
+		return true;
+	}
+
+	area.flags = node->GetFlags();
+	area.cluster = area.clusterAreaNum = 0;
+	area.contents = node->GetContents();
+	area.firstFace = file->faceIndex.Num();
+	area.numFaces = 0;
+	area.reach = NULL;
+	area.rev_reach = NULL;
+	area.bounds.Zero();
+	area.center.Zero();
+	area.ceiling = 0.0f;
+	area.travelFlags = 0;
+	area.numFeatures = 0;
+	area.firstFeature = 0;
+	area.firstMarker = NULL;
+
+	for ( p = node->GetPortals(); p; p = p->Next(s) ) {
+		s = (p->GetNode(1) == node);
+
+		if ( !GetFaceForPortal( p, s, &faceNum ) ) {
+			continue;
+		}
+
+		file->faceIndex.Append( faceNum );
+		area.numFaces++;
+
+		if ( faceNum > 0 ) {
+			file->faces[abs(faceNum)].areas[0] = file->areas.Num();
+		}
+		else {
+			file->faces[abs(faceNum)].areas[1] = file->areas.Num();
+		}
+	}
+
+	if ( !area.numFaces ) {
+		*areaNum = 0;
+		return false;
+	}
+
+	*areaNum = -file->areas.Num();
+	node->SetAreaNum( file->areas.Num() );
+	file->areas.Append( area );
+
+	DisplayRealTimeString( "\r%6d", file->areas.Num() );
+
+	return true;
+}
+
+/*
+================
+idAASBuild::StoreTree_r
+================
+*/
+int idAASBuild::StoreTree_r( idBrushBSPNode *node ) {
+	int areaNum, nodeNum, child0, child1;
+	aasNode_t aasNode;
+
+	if ( !node ) {
+		return 0;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return 0;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		if ( GetAreaForLeafNode( node, &areaNum ) ) {
+			return areaNum;
+		}
+		return 0;
+	}
+
+	aasNode.planeNum = file->planeList.FindPlane( node->GetPlane(), AAS_PLANE_NORMAL_EPSILON, AAS_PLANE_DIST_EPSILON );
+	aasNode.children[0] = aasNode.children[1] = 0;
+	nodeNum = file->nodes.Num();
+	file->nodes.Append( aasNode );
+
+	// !@#$%^ cause of some bug we cannot set the children directly with the StoreTree_r return value
+	child0 = StoreTree_r( node->GetChild(0) );
+	file->nodes[nodeNum].children[0] = child0;
+	child1 = StoreTree_r( node->GetChild(1) );
+	file->nodes[nodeNum].children[1] = child1;
+
+	if ( !child0 && !child1 ) {
+		file->nodes.SetNum( file->nodes.Num()-1 );
+		return 0;
+	}
+
+	return nodeNum;
+}
+
+/*
+================
+idAASBuild::GetSizeEstimate_r
+================
+*/
+void idAASBuild::GetSizeEstimate_r( idBrushBSPNode *parent, idBrushBSPNode *node, struct sizeEstimate_s &size ) {
+	idBrushBSPPortal *p;
+	int s;
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		// multiple branches of the bsp tree might point to the same leaf node
+		if ( node->GetParent() == parent ) {
+			size.numAreas++;
+			for ( p = node->GetPortals(); p; p = p->Next(s) ) {
+				s = (p->GetNode(1) == node);
+				size.numFaceIndexes++;
+				size.numEdgeIndexes += p->GetWinding()->GetNumPoints();
+			}
+		}
+	}
+	else {
+		size.numNodes++;
+	}
+
+	GetSizeEstimate_r( node, node->GetChild(0), size );
+	GetSizeEstimate_r( node, node->GetChild(1), size );
+}
+
+/*
+================
+idAASBuild::SetSizeEstimate
+================
+*/
+void idAASBuild::SetSizeEstimate( const idBrushBSP &bsp, idAASFileLocal *file ) {
+	sizeEstimate_t size;
+
+	size.numEdgeIndexes = 1;
+	size.numFaceIndexes = 1;
+	size.numAreas = 1;
+	size.numNodes = 1;
+
+	GetSizeEstimate_r( NULL, bsp.GetRootNode(), size );
+
+	file->planeList.Resize( size.numNodes / 2, 1024 );
+	file->vertices.Resize( size.numEdgeIndexes / 3, 1024 );
+	file->edges.Resize( size.numEdgeIndexes / 2, 1024 );
+	file->edgeIndex.Resize( size.numEdgeIndexes, 4096 );
+	file->faces.Resize( size.numFaceIndexes, 1024 );
+	file->faceIndex.Resize( size.numFaceIndexes, 4096 );
+	file->areas.Resize( size.numAreas, 1024 );
+	file->nodes.Resize( size.numNodes, 1024 );
+}
+
+/*
+================
+idAASBuild::StoreFile
+================
+*/
+bool idAASBuild::StoreFile( const idBrushBSP &bsp ) {
+	aasEdge_t edge;
+	aasFace_t face;
+	aasArea_t area;
+	aasNode_t node;
+
+	common->Printf( "[Store AAS]\n" );
+
+	SetupHash();
+	ClearHash( bsp.GetTreeBounds() );
+
+	file = new idAASFileLocal();
+
+	file->Clear();
+
+	SetSizeEstimate( bsp, file );
+
+	// the first edge is a dummy
+	memset( &edge, 0, sizeof( edge ) );
+	file->edges.Append( edge );
+
+	// the first face is a dummy
+	memset( &face, 0, sizeof( face ) );
+	file->faces.Append( face );
+
+	// the first area is a dummy
+	memset( &area, 0, sizeof( area ) );
+	file->areas.Append( area );
+
+	// the first node is a dummy
+	memset( &node, 0, sizeof( node ) );
+	file->nodes.Append( node );
+
+	// store the tree
+	StoreTree_r( bsp.GetRootNode() );
+
+	// calculate area bounds and a reachable point in the area
+	file->FinishAreas();
+
+	ShutdownHash();
+
+	common->Printf( "\r%6d areas\n", file->areas.Num() );
+
+	return true;
+}
diff --git a/src/tools/compilers/aas/AASBuild_gravity.cpp b/src/tools/compilers/aas/AASBuild_gravity.cpp
new file mode 100644
index 0000000..5f6a1a5
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild_gravity.cpp
@@ -0,0 +1,355 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+/*
+============
+idAASBuild::SetPortalFlags_r
+============
+*/
+void idAASBuild::SetPortalFlags_r( idBrushBSPNode *node ) {
+	int s;
+	idBrushBSPPortal *p;
+	idVec3 normal;
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		for ( p = node->GetPortals(); p; p = p->Next(s) ) {
+			s = (p->GetNode(1) == node);
+
+			// if solid at the other side of the portal
+			if ( p->GetNode(!s)->GetContents() & AREACONTENTS_SOLID ) {
+				if ( s ) {
+					normal = -p->GetPlane().Normal();
+				}
+				else {
+					normal = p->GetPlane().Normal();
+				}
+				if ( normal * aasSettings->invGravityDir > aasSettings->minFloorCos ) {
+					p->SetFlag( FACE_FLOOR );
+				}
+				else {
+					p->SetFlag( FACE_SOLID );
+				}
+			}
+		}
+		return;
+	}
+
+	SetPortalFlags_r( node->GetChild(0) );
+	SetPortalFlags_r( node->GetChild(1) );
+}
+
+/*
+============
+idAASBuild::PortalIsGap
+============
+*/
+bool idAASBuild::PortalIsGap( idBrushBSPPortal *portal, int side ) {
+	idVec3 normal;
+
+	// if solid at the other side of the portal
+	if ( portal->GetNode(!side)->GetContents() & AREACONTENTS_SOLID ) {
+		return false;
+	}
+
+	if ( side ) {
+		normal = -(portal->GetPlane().Normal());
+	}
+	else {
+		normal = portal->GetPlane().Normal();
+	}
+	if ( normal * aasSettings->invGravityDir > aasSettings->minFloorCos ) {
+		return true;
+	}
+	return false;
+}
+
+/*
+============
+idAASBuild::GravSubdivLeafNode
+============
+*/
+#define FACE_CHECKED			BIT(31)
+#define GRAVSUBDIV_EPSILON		0.1f
+
+void idAASBuild::GravSubdivLeafNode( idBrushBSPNode *node ) {
+	int s1, s2, i, j, k, side1;
+	int numSplits, numSplitters;
+	idBrushBSPPortal *p1, *p2;
+	idWinding *w1, *w2;
+	idVec3 normal;
+	idPlane plane;
+	idPlaneSet planeList;
+	float d, min, max;
+	int *splitterOrder;
+	int *bestNumSplits;
+	int floor, gap, numFloorChecked;
+
+	// if this leaf node is already classified it cannot have a combination of floor and gap portals
+	if ( node->GetFlags() & (AREA_FLOOR|AREA_GAP) ) {
+		return;
+	}
+
+	floor = gap = 0;
+
+	// check if the area has a floor
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( p1->GetFlags() & FACE_FLOOR ) {
+			floor++;
+		}
+	}
+
+	// find seperating planes between gap and floor portals
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		// if the portal is a gap seen from this side
+		if ( PortalIsGap( p1, s1 ) ) {
+			gap++;
+			// if the area doesn't have a floor
+			if ( !floor ) {
+				break;
+			}
+		}
+		else {
+			continue;
+		}
+
+		numFloorChecked = 0;
+
+		w1 = p1->GetWinding();
+
+		// test all edges of the gap
+		for ( i = 0; i < w1->GetNumPoints(); i++ ) {
+
+			// create a plane through the edge of the gap parallel to the direction of gravity
+			normal = (*w1)[(i+1)%w1->GetNumPoints()].ToVec3() - (*w1)[i].ToVec3();
+			normal = normal.Cross( aasSettings->invGravityDir );
+			if ( normal.Normalize() < 0.2f ) {
+				continue;
+			}
+			plane.SetNormal( normal );
+			plane.FitThroughPoint( (*w1)[i].ToVec3() );
+
+			// get the side of the plane the gap is on
+			side1 = w1->PlaneSide( plane, GRAVSUBDIV_EPSILON );
+			if ( side1 == SIDE_ON ) {
+				break;
+			}
+
+			// test if the plane through the edge of the gap seperates the gap from a floor portal
+			for ( p2 = node->GetPortals(); p2; p2 = p2->Next(s2) ) {
+				s2 = (p2->GetNode(1) == node);
+
+				if ( !( p2->GetFlags() & FACE_FLOOR ) ) {
+					continue;
+				}
+
+				if ( p2->GetFlags() & FACE_CHECKED ) {
+					continue;
+				}
+
+				w2 = p2->GetWinding();
+
+				min = 2.0f * GRAVSUBDIV_EPSILON;
+				max = GRAVSUBDIV_EPSILON;
+				if ( side1 == SIDE_FRONT ) {
+					for ( j = 0; j < w2->GetNumPoints(); j++ ) {
+						d = plane.Distance( (*w2)[j].ToVec3() );
+						if ( d >= GRAVSUBDIV_EPSILON ) {
+							break;	// point at the same side of the plane as the gap
+						}
+						d = idMath::Fabs( d );
+						if ( d < min ) {
+							min = d;
+						}
+						if ( d > max ) {
+							max = d;
+						}
+					}
+				}
+				else {
+					for ( j = 0; j < w2->GetNumPoints(); j++ ) {
+						d = plane.Distance( (*w2)[j].ToVec3() );
+						if ( d <= -GRAVSUBDIV_EPSILON ) {
+							break;	// point at the same side of the plane as the gap
+						}
+						d = idMath::Fabs( d );
+						if ( d < min ) {
+							min = d;
+						}
+						if ( d > max ) {
+							max = d;
+						}
+					}
+				}
+
+				// a point of the floor portal was found to be at the same side of the plane as the gap
+				if ( j < w2->GetNumPoints() ) {
+					continue;
+				}
+
+				// if the floor portal touches the plane
+				if ( min < GRAVSUBDIV_EPSILON && max > GRAVSUBDIV_EPSILON ) {
+					planeList.FindPlane( plane, 0.00001f, 0.1f );
+				}
+
+				p2->SetFlag( FACE_CHECKED );
+				numFloorChecked++;
+
+			}
+			if ( numFloorChecked == floor ) {
+				break;
+			}
+		}
+
+		for ( p2 = node->GetPortals(); p2; p2 = p2->Next(s2) ) {
+			s2 = (p2->GetNode(1) == node);
+			p2->RemoveFlag( FACE_CHECKED );
+		}
+	}
+
+	// if the leaf node does not have both floor and gap portals
+	if ( !( gap && floor) ) {
+		if ( floor ) {
+			node->SetFlag( AREA_FLOOR );
+		}
+		else if ( gap ) {
+			node->SetFlag( AREA_GAP );
+		}
+		return;
+	}
+
+	// if no valid seperators found
+	if ( planeList.Num() == 0 ) {
+		// NOTE: this should never happend, if it does the leaf node has degenerate portals
+		return;
+	}
+
+	splitterOrder = (int *) _alloca( planeList.Num() * sizeof( int ) );
+	bestNumSplits = (int *) _alloca( planeList.Num() * sizeof( int ) );
+	numSplitters = 0;
+
+	// test all possible seperators and sort them from best to worst
+	for ( i = 0; i < planeList.Num(); i += 2 ) {
+		numSplits = 0;
+
+		for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+			s1 = (p1->GetNode(1) == node);
+			if ( p1->GetWinding()->PlaneSide( planeList[i], 0.1f ) == SIDE_CROSS ) {
+				numSplits++;
+			}
+		}
+
+		for ( j = 0; j < numSplitters; j++ ) {
+			if ( numSplits < bestNumSplits[j] ) {
+				for ( k = numSplitters; k > j; k-- ) {
+					bestNumSplits[k] = bestNumSplits[k-1];
+					splitterOrder[k] = splitterOrder[k-1];
+				}
+				bestNumSplits[j] = numSplits;
+				splitterOrder[j] = i;
+				numSplitters++;
+				break;
+			}
+		}
+		if ( j >= numSplitters ) {
+			bestNumSplits[j] = numSplits;
+			splitterOrder[j] = i;
+			numSplitters++;
+		}
+	}
+
+	// try all seperators in order from best to worst
+	for ( i = 0; i < numSplitters; i++ ) {
+		if ( node->Split( planeList[splitterOrder[i]], -1 ) ) {
+			// we found a seperator that works
+			break;
+		}
+	}
+	if ( i >= numSplitters) {
+		return;
+	}
+
+	DisplayRealTimeString( "\r%6d", ++numGravitationalSubdivisions );
+
+	// test children for further splits
+	GravSubdivLeafNode( node->GetChild(0) );
+	GravSubdivLeafNode( node->GetChild(1) );
+}
+
+/*
+============
+idAASBuild::GravSubdiv_r
+============
+*/
+void idAASBuild::GravSubdiv_r( idBrushBSPNode *node ) {
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		GravSubdivLeafNode( node );
+		return;
+	}
+
+	GravSubdiv_r( node->GetChild(0) );
+	GravSubdiv_r( node->GetChild(1) );
+}
+
+/*
+============
+idAASBuild::GravitationalSubdivision
+============
+*/
+void idAASBuild::GravitationalSubdivision( idBrushBSP &bsp ) {
+	numGravitationalSubdivisions = 0;
+
+	common->Printf( "[Gravitational Subdivision]\n" );
+
+	SetPortalFlags_r( bsp.GetRootNode() );
+	GravSubdiv_r( bsp.GetRootNode() );
+
+	common->Printf( "\r%6d subdivisions\n", numGravitationalSubdivisions );
+}
diff --git a/src/tools/compilers/aas/AASBuild_ledge.cpp b/src/tools/compilers/aas/AASBuild_ledge.cpp
new file mode 100644
index 0000000..fe0f11c
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild_ledge.cpp
@@ -0,0 +1,572 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+#define LEDGE_EPSILON		0.1f
+
+//===============================================================
+//
+//	idLedge
+//
+//===============================================================
+
+/*
+============
+idLedge::idLedge
+============
+*/
+idLedge::idLedge( void ) {
+}
+
+/*
+============
+idLedge::idLedge
+============
+*/
+idLedge::idLedge( const idVec3 &v1, const idVec3 &v2, const idVec3 &gravityDir, idBrushBSPNode *n ) {
+	start = v1;
+	end = v2;
+	node = n;
+	numPlanes = 4;
+	planes[0].SetNormal( (v1 - v2).Cross( gravityDir ) );
+	planes[0].Normalize();
+	planes[0].FitThroughPoint( v1 );
+	planes[1].SetNormal( (v1 - v2).Cross( planes[0].Normal() ) );
+	planes[1].Normalize();
+	planes[1].FitThroughPoint( v1 );
+	planes[2].SetNormal( v1 - v2 );
+	planes[2].Normalize();
+	planes[2].FitThroughPoint( v1 );
+	planes[3].SetNormal( v2 - v1 );
+	planes[3].Normalize();
+	planes[3].FitThroughPoint( v2 );
+}
+
+/*
+============
+idLedge::AddPoint
+============
+*/
+void idLedge::AddPoint( const idVec3 &v ) {
+	if ( planes[2].Distance( v ) > 0.0f ) {
+		start = v;
+		planes[2].FitThroughPoint( start );
+	}
+	if ( planes[3].Distance( v ) > 0.0f ) {
+		end = v;
+		planes[3].FitThroughPoint( end );
+	}
+}
+
+/*
+============
+idLedge::CreateBevels
+
+  NOTE: this assumes the gravity is vertical
+============
+*/
+void idLedge::CreateBevels( const idVec3 &gravityDir ) {
+	int i, j;
+	idBounds bounds;
+	idVec3 size, normal;
+
+	bounds.Clear();
+	bounds.AddPoint( start );
+	bounds.AddPoint( end );
+	size = bounds[1] - bounds[0];
+
+	// plane through ledge
+	planes[0].SetNormal( (start - end).Cross( gravityDir ) );
+	planes[0].Normalize();
+	planes[0].FitThroughPoint( start );
+	// axial bevels at start and end point
+	i = size[1] > size[0];
+	normal = vec3_origin;
+	normal[i] = 1.0f;
+	j = end[i] > start[i];
+	planes[1+j].SetNormal( normal );
+	planes[1+!j].SetNormal( -normal );
+	planes[1].FitThroughPoint( start );
+	planes[2].FitThroughPoint( end );
+	numExpandedPlanes = 3;
+	// if additional bevels are required
+	if ( idMath::Fabs( size[!i] ) > 0.01f ) {
+		normal = vec3_origin;
+		normal[!i] = 1.0f;
+		j = end[!i] > start[!i];
+		planes[3+j].SetNormal( normal );
+		planes[3+!j].SetNormal( -normal );
+		planes[3].FitThroughPoint( start );
+		planes[4].FitThroughPoint( end );
+		numExpandedPlanes = 5;
+	}
+	// opposite of first
+	planes[numExpandedPlanes+0] = -planes[0];
+	// number of planes used for splitting
+	numSplitPlanes = numExpandedPlanes + 1;
+	// top plane
+	planes[numSplitPlanes+0].SetNormal( (start - end).Cross( planes[0].Normal() ) );
+	planes[numSplitPlanes+0].Normalize();
+	planes[numSplitPlanes+0].FitThroughPoint( start );
+	// bottom plane
+	planes[numSplitPlanes+1] = -planes[numSplitPlanes+0];
+	// total number of planes
+	numPlanes = numSplitPlanes + 2;
+}
+
+/*
+============
+idLedge::Expand
+============
+*/
+void idLedge::Expand( const idBounds &bounds, float maxStepHeight ) {
+	int i, j;
+	idVec3 v;
+
+	for ( i = 0; i < numExpandedPlanes; i++ ) {
+
+		for ( j = 0; j < 3; j++ ) {
+			if ( planes[i].Normal()[j] > 0.0f ) {
+				v[j] = bounds[0][j];
+			}
+			else {
+				v[j] = bounds[1][j];
+			}
+		}
+
+		planes[i].SetDist( planes[i].Dist() + v * -planes[i].Normal() );
+	}
+
+	planes[numSplitPlanes+0].SetDist( planes[numSplitPlanes+0].Dist() + maxStepHeight );
+	planes[numSplitPlanes+1].SetDist( planes[numSplitPlanes+1].Dist() + 1.0f );
+}
+
+/*
+============
+idLedge::ChopWinding
+============
+*/
+idWinding *idLedge::ChopWinding( const idWinding *winding ) const {
+	int i;
+	idWinding *w;
+
+	w = winding->Copy();
+	for ( i = 0; i < numPlanes && w; i++ ) {
+		w = w->Clip( -planes[i], ON_EPSILON, true );
+	}
+	return w;
+}
+
+/*
+============
+idLedge::PointBetweenBounds
+============
+*/
+bool idLedge::PointBetweenBounds( const idVec3 &v ) const {
+	return ( planes[2].Distance( v ) < LEDGE_EPSILON ) && ( planes[3].Distance( v ) < LEDGE_EPSILON );
+}
+
+
+//===============================================================
+//
+//	idAASBuild
+//
+//===============================================================
+
+/*
+============
+idAASBuild::LedgeSubdivFlood_r
+============
+*/
+void idAASBuild::LedgeSubdivFlood_r( idBrushBSPNode *node, const idLedge *ledge ) {
+	int s1, i;
+	idBrushBSPPortal *p1;
+	idWinding *w;
+	idList<idBrushBSPNode *> nodeList;
+
+	if ( node->GetFlags() & NODE_VISITED ) {
+		return;
+	}
+
+	// if this is not already a ledge area
+	if ( !( node->GetFlags() & AREA_LEDGE ) ) {
+		for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+			s1 = (p1->GetNode(1) == node);
+
+			if ( !(p1->GetFlags() & FACE_FLOOR) ) {
+				continue;
+			}
+
+			// split the area if some part of the floor portal is inside the expanded ledge
+			w = ledge->ChopWinding( p1->GetWinding() );
+			if ( !w ) {
+				continue;
+			}
+			delete w;
+
+			for ( i = 0; i < ledge->numSplitPlanes; i++ ) {
+				if ( node->PlaneSide( ledge->planes[i], 0.1f ) != SIDE_CROSS ) {
+					continue;
+				}
+				if ( !node->Split( ledge->planes[i], -1 ) ) {
+					continue;
+				}
+				numLedgeSubdivisions++;
+				DisplayRealTimeString( "\r%6d", numLedgeSubdivisions );
+				node->GetChild(0)->SetFlag( NODE_VISITED );
+				LedgeSubdivFlood_r( node->GetChild(1), ledge );
+				return;
+			}
+
+			node->SetFlag( AREA_LEDGE );
+			break;
+		}
+	}
+
+	node->SetFlag( NODE_VISITED );
+
+	// get all nodes we might need to flood into
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( p1->GetNode( !s1 )->GetContents() & AREACONTENTS_SOLID ) {
+			continue;
+		}
+
+		// flood through this portal if the portal is partly inside the expanded ledge
+		w = ledge->ChopWinding( p1->GetWinding() );
+		if ( !w ) {
+			continue;
+		}
+		delete w;
+		// add to list, cannot flood directly cause portals might be split on the way
+		nodeList.Append( p1->GetNode( !s1 ) );
+	}
+
+	// flood into other nodes
+	for ( i = 0; i < nodeList.Num(); i++ ) {
+		LedgeSubdivLeafNodes_r( nodeList[i], ledge );
+	}
+}
+
+/*
+============
+idAASBuild::LedgeSubdivLeafNodes_r
+
+  The node the ledge was originally part of might be split by other ledges.
+  Here we recurse down the tree from the original node to find all the new leaf nodes the ledge might be part of.
+============
+*/
+void idAASBuild::LedgeSubdivLeafNodes_r( idBrushBSPNode *node, const idLedge *ledge ) {
+	if ( !node ) {
+		return;
+	}
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		LedgeSubdivFlood_r( node, ledge );
+		return;
+	}
+	LedgeSubdivLeafNodes_r( node->GetChild(0), ledge );
+	LedgeSubdivLeafNodes_r( node->GetChild(1), ledge );
+}
+
+/*
+============
+idAASBuild::LedgeSubdiv
+============
+*/
+void idAASBuild::LedgeSubdiv( idBrushBSPNode *root ) {
+	int i, j;
+	idBrush *brush;
+	idList<idBrushSide *> sideList;
+
+	// create ledge bevels and expand ledges
+	for ( i = 0; i < ledgeList.Num(); i++ ) {
+
+		ledgeList[i].CreateBevels( aasSettings->gravityDir );
+		ledgeList[i].Expand( aasSettings->boundingBoxes[0], aasSettings->maxStepHeight );
+
+		// if we should write out a ledge map
+		if ( ledgeMap ) {
+			sideList.SetNum( 0 );
+			for ( j = 0; j < ledgeList[i].numPlanes; j++ ) {
+				sideList.Append( new idBrushSide( ledgeList[i].planes[j], -1 ) );
+			}
+
+			brush = new idBrush();
+			brush->FromSides( sideList );
+
+			ledgeMap->WriteBrush( brush );
+
+			delete brush;
+		}
+
+		// flood tree from the ledge node and subdivide areas with the ledge
+		LedgeSubdivLeafNodes_r( ledgeList[i].node, &ledgeList[i] );
+
+		// remove the node visited flags
+		ledgeList[i].node->RemoveFlagRecurseFlood( NODE_VISITED );
+	}
+}
+
+/*
+============
+idAASBuild::IsLedgeSide_r
+============
+*/
+bool idAASBuild::IsLedgeSide_r( idBrushBSPNode *node, idFixedWinding *w, const idPlane &plane, const idVec3 &normal, const idVec3 &origin, const float radius ) {
+	int res, i;
+	idFixedWinding back;
+	float dist;
+
+	if ( !node ) {
+		return false;
+	}
+
+	while ( node->GetChild(0) && node->GetChild(1) ) {
+		dist = node->GetPlane().Distance( origin );
+		if ( dist > radius ) {
+			res = SIDE_FRONT;
+		}
+		else if ( dist < -radius ) {
+			res = SIDE_BACK;
+		}
+		else {
+			res = w->Split( &back, node->GetPlane(), LEDGE_EPSILON );
+		}
+		if ( res == SIDE_FRONT ) {
+			node = node->GetChild(0);
+		}
+		else if ( res == SIDE_BACK ) {
+			node = node->GetChild(1);
+		}
+		else if ( res == SIDE_ON ) {
+			// continue with the side the winding faces
+			if ( node->GetPlane().Normal() * normal > 0.0f ) {
+				node = node->GetChild(0);
+			}
+			else {
+				node = node->GetChild(1);
+			}
+		}
+		else {
+			if ( IsLedgeSide_r( node->GetChild(1), &back, plane, normal, origin, radius ) ) {
+				return true;
+			}
+			node = node->GetChild(0);
+		}
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return false;
+	}
+
+	for ( i = 0; i < w->GetNumPoints(); i++ ) {
+		if ( plane.Distance( (*w)[i].ToVec3() ) > 0.0f ) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+============
+idAASBuild::AddLedge
+============
+*/
+void idAASBuild::AddLedge( const idVec3 &v1, const idVec3 &v2, idBrushBSPNode *node ) {
+	int i, j, merged;
+
+	// first try to merge the ledge with existing ledges
+	merged = -1;
+	for ( i = 0; i < ledgeList.Num(); i++ ) {
+
+		for ( j = 0; j < 2; j++ ) {
+			if ( idMath::Fabs( ledgeList[i].planes[j].Distance( v1 ) ) > LEDGE_EPSILON ) {
+				break;
+			}
+			if ( idMath::Fabs( ledgeList[i].planes[j].Distance( v2 ) ) > LEDGE_EPSILON ) {
+				break;
+			}
+		}
+		if ( j < 2 ) {
+			continue;
+		}
+
+		if ( !ledgeList[i].PointBetweenBounds( v1 ) &&
+			!ledgeList[i].PointBetweenBounds( v2 ) ) {
+			continue;
+		}
+
+		if ( merged == -1 ) {
+			ledgeList[i].AddPoint( v1 );
+			ledgeList[i].AddPoint( v2 );
+			merged = i;
+		}
+		else {
+			ledgeList[merged].AddPoint( ledgeList[i].start );
+			ledgeList[merged].AddPoint( ledgeList[i].end );
+			ledgeList.RemoveIndex(i);
+			break;
+		}
+	}
+
+	// if the ledge could not be merged
+	if ( merged == -1 ) {
+		ledgeList.Append( idLedge( v1, v2, aasSettings->gravityDir, node ) );
+	}
+}
+
+/*
+============
+idAASBuild::FindLeafNodeLedges
+============
+*/
+void idAASBuild::FindLeafNodeLedges( idBrushBSPNode *root, idBrushBSPNode *node ) {
+	int s1, i;
+	idBrushBSPPortal *p1;
+	idWinding *w;
+	idVec3 v1, v2, normal, origin;
+	idFixedWinding winding;
+	idBounds bounds;
+	idPlane plane;
+	float radius;
+
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( !(p1->GetFlags() & FACE_FLOOR) ) {
+			continue;
+		}
+
+		if ( s1 ) {
+			plane = p1->GetPlane();
+			w = p1->GetWinding()->Reverse();
+		}
+		else {
+			plane = -p1->GetPlane();
+			w = p1->GetWinding();
+		}
+
+		for ( i = 0; i < w->GetNumPoints(); i++ ) {
+
+			v1 = (*w)[i].ToVec3();
+			v2 = (*w)[(i+1)%w->GetNumPoints()].ToVec3();
+			normal = (v2 - v1).Cross( aasSettings->gravityDir );
+			if ( normal.Normalize() < 0.5f ) {
+				continue;
+			}
+
+			winding.Clear();
+			winding += v1 + normal * LEDGE_EPSILON * 0.5f;
+			winding += v2 + normal * LEDGE_EPSILON * 0.5f;
+			winding += winding[1].ToVec3() + ( aasSettings->maxStepHeight + 1.0f ) * aasSettings->gravityDir;
+			winding += winding[0].ToVec3() + ( aasSettings->maxStepHeight + 1.0f ) * aasSettings->gravityDir;
+
+			winding.GetBounds( bounds );
+			origin = (bounds[1] - bounds[0]) * 0.5f;
+			radius = origin.Length() + LEDGE_EPSILON;
+			origin = bounds[0] + origin;
+
+			plane.FitThroughPoint( v1 + aasSettings->maxStepHeight * aasSettings->gravityDir );
+
+			if ( !IsLedgeSide_r( root, &winding, plane, normal, origin, radius ) ) {
+				continue;
+			}
+
+			AddLedge( v1, v2, node );
+		}
+
+		if ( w != p1->GetWinding() ) {
+			delete w;
+		}
+	}
+}
+
+/*
+============
+idAASBuild::FindLedges_r
+============
+*/
+void idAASBuild::FindLedges_r( idBrushBSPNode *root, idBrushBSPNode *node ) {
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		if ( node->GetFlags() & NODE_VISITED ) {
+			return;
+		}
+		FindLeafNodeLedges( root, node );
+		node->SetFlag( NODE_VISITED );
+		return;
+	}
+
+	FindLedges_r( root, node->GetChild(0) );
+	FindLedges_r( root, node->GetChild(1) );
+}
+
+/*
+============
+idAASBuild::WriteLedgeMap
+============
+*/
+void idAASBuild::WriteLedgeMap( const idStr &fileName, const idStr &ext ) {
+	ledgeMap = new idBrushMap( fileName, ext );
+	ledgeMap->SetTexture( "textures/base_trim/bluetex4q_ed" );
+}
+
+/*
+============
+idAASBuild::LedgeSubdivision
+
+  NOTE: this assumes the bounding box is higher than the maximum step height
+		only ledges with vertical sides are considered
+============
+*/
+void idAASBuild::LedgeSubdivision( idBrushBSP &bsp ) {
+	numLedgeSubdivisions = 0;
+	ledgeList.Clear();
+
+	common->Printf( "[Ledge Subdivision]\n" );
+
+	bsp.GetRootNode()->RemoveFlagRecurse( NODE_VISITED );
+	FindLedges_r( bsp.GetRootNode(), bsp.GetRootNode() );
+	bsp.GetRootNode()->RemoveFlagRecurse( NODE_VISITED );
+
+	common->Printf( "\r%6d ledges\n", ledgeList.Num() );
+
+	LedgeSubdiv( bsp.GetRootNode() );
+
+	common->Printf( "\r%6d subdivisions\n", numLedgeSubdivisions );
+}
diff --git a/src/tools/compilers/aas/AASBuild_local.h b/src/tools/compilers/aas/AASBuild_local.h
new file mode 100644
index 0000000..d20c25d
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild_local.h
@@ -0,0 +1,147 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#ifndef __AASBUILD_LOCAL_H__
+#define __AASBUILD_LOCAL_H__
+
+#include "tools/compilers/aas/Brush.h"
+#include "tools/compilers/aas/BrushBSP.h"
+#include "aas/AASFile.h"
+#include "tools/compilers/aas/AASReach.h"
+#include "tools/compilers/aas/AASCluster.h"
+#include "aas/AASFile_local.h"
+
+//===============================================================
+//
+//	idAASBuild
+//
+//===============================================================
+
+typedef struct aasProcNode_s {
+	idPlane plane;
+	int children[2];		// negative numbers are (-1 - areaNumber), 0 = solid
+} aasProcNode_t;
+
+
+class idLedge {
+
+public:
+	idVec3					start;
+	idVec3					end;
+	idBrushBSPNode *		node;
+	int						numExpandedPlanes;
+	int						numSplitPlanes;
+	int						numPlanes;
+	idPlane					planes[8];
+
+public:
+							idLedge( void );
+							idLedge( const idVec3 &v1, const idVec3 &v2, const idVec3 &gravityDir, idBrushBSPNode *n );
+	void					AddPoint( const idVec3 &v );
+	void					CreateBevels( const idVec3 &gravityDir );
+	void					Expand( const idBounds &bounds, float maxStepHeight );
+	idWinding *				ChopWinding( const idWinding *winding ) const;
+	bool					PointBetweenBounds( const idVec3 &v ) const;
+};
+
+
+class idAASBuild {
+
+public:
+							idAASBuild( void );
+							~idAASBuild( void );
+	bool					Build( const idStr &fileName, const idAASSettings *settings );
+	bool					BuildReachability( const idStr &fileName, const idAASSettings *settings );
+	void					Shutdown( void );
+
+private:
+	const idAASSettings *	aasSettings;
+	idAASFileLocal *		file;
+	aasProcNode_t *			procNodes;
+	int						numProcNodes;
+	int						numGravitationalSubdivisions;
+	int						numMergedLeafNodes;
+	int						numLedgeSubdivisions;
+	idList<idLedge>			ledgeList;
+	idBrushMap *			ledgeMap;
+
+private:	// map loading
+	void					ParseProcNodes( idLexer *src );
+	bool					LoadProcBSP( const char *name, ID_TIME_T minFileTime );
+	void					DeleteProcBSP( void );
+	bool					ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius );
+	void					ClipBrushSidesWithProcBSP( idBrushList &brushList );
+	int						ContentsForAAS( int contents );
+	idBrushList				AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList );
+	idBrushList				AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList );
+	idBrushList				AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList );
+	idBrushList				AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList );
+	bool					CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const;
+	void					ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask );
+
+private:	// gravitational subdivision
+	void					SetPortalFlags_r( idBrushBSPNode *node );
+	bool					PortalIsGap( idBrushBSPPortal *portal, int side );
+	void					GravSubdivLeafNode( idBrushBSPNode *node );
+	void					GravSubdiv_r( idBrushBSPNode *node );
+	void					GravitationalSubdivision( idBrushBSP &bsp );
+
+private:	// ledge subdivision
+	void					LedgeSubdivFlood_r( idBrushBSPNode *node, const idLedge *ledge );
+	void					LedgeSubdivLeafNodes_r( idBrushBSPNode *node, const idLedge *ledge );
+	void					LedgeSubdiv( idBrushBSPNode *root );
+	bool					IsLedgeSide_r( idBrushBSPNode *node, idFixedWinding *w, const idPlane &plane, const idVec3 &normal, const idVec3 &origin, const float radius );
+	void					AddLedge( const idVec3 &v1, const idVec3 &v2, idBrushBSPNode *node );
+	void					FindLeafNodeLedges( idBrushBSPNode *root, idBrushBSPNode *node );
+	void					FindLedges_r( idBrushBSPNode *root, idBrushBSPNode *node );
+	void					LedgeSubdivision( idBrushBSP &bsp );
+	void					WriteLedgeMap( const idStr &fileName, const idStr &ext );
+
+private:	// merging
+	bool					AllGapsLeadToOtherNode( idBrushBSPNode *nodeWithGaps, idBrushBSPNode *otherNode );
+	bool					MergeWithAdjacentLeafNodes( idBrushBSP &bsp, idBrushBSPNode *node );
+	void					MergeLeafNodes_r( idBrushBSP &bsp, idBrushBSPNode *node );
+	void					MergeLeafNodes( idBrushBSP &bsp );
+
+private:	// storing file
+	void					SetupHash( void );
+	void					ShutdownHash( void );
+	void					ClearHash( const idBounds &bounds );
+	int						HashVec( const idVec3 &vec );
+	bool					GetVertex( const idVec3 &v, int *vertexNum );
+	bool					GetEdge( const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num );
+	bool					GetFaceForPortal( idBrushBSPPortal *portal, int side, int *faceNum );
+	bool					GetAreaForLeafNode( idBrushBSPNode *node, int *areaNum );
+	int						StoreTree_r( idBrushBSPNode *node );
+	void					GetSizeEstimate_r( idBrushBSPNode *parent, idBrushBSPNode *node, struct sizeEstimate_s &size );
+	void					SetSizeEstimate( const idBrushBSP &bsp, idAASFileLocal *file );
+	bool					StoreFile( const idBrushBSP &bsp );
+
+};
+
+#endif /* !__AASBUILD_LOCAL_H__ */
diff --git a/src/tools/compilers/aas/AASBuild_merge.cpp b/src/tools/compilers/aas/AASBuild_merge.cpp
new file mode 100644
index 0000000..194ddd6
--- /dev/null
+++ b/src/tools/compilers/aas/AASBuild_merge.cpp
@@ -0,0 +1,160 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+/*
+============
+idAASBuild::AllGapsLeadToOtherNode
+============
+*/
+bool idAASBuild::AllGapsLeadToOtherNode( idBrushBSPNode *nodeWithGaps, idBrushBSPNode *otherNode ) {
+	int s;
+	idBrushBSPPortal *p;
+
+	for ( p = nodeWithGaps->GetPortals(); p; p = p->Next(s) ) {
+		s = (p->GetNode(1) == nodeWithGaps);
+
+		if ( !PortalIsGap( p, s ) ) {
+			continue;
+		}
+
+		if ( p->GetNode(!s) != otherNode ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+============
+idAASBuild::MergeWithAdjacentLeafNodes
+============
+*/
+bool idAASBuild::MergeWithAdjacentLeafNodes( idBrushBSP &bsp, idBrushBSPNode *node ) {
+	int s, numMerges = 0, otherNodeFlags;
+	idBrushBSPPortal *p;
+
+	do {
+		for ( p = node->GetPortals(); p; p = p->Next(s) ) {
+			s = (p->GetNode(1) == node);
+
+			// both leaf nodes must have the same contents
+			if ( node->GetContents() != p->GetNode(!s)->GetContents() ) {
+				continue;
+			}
+
+			// cannot merge leaf nodes if one is near a ledge and the other is not
+			if ( (node->GetFlags() & AREA_LEDGE) != (p->GetNode(!s)->GetFlags() & AREA_LEDGE) ) {
+				continue;
+			}
+
+			// cannot merge leaf nodes if one has a floor portal and the other a gap portal
+			if ( node->GetFlags() & AREA_FLOOR ) {
+				if ( p->GetNode(!s)->GetFlags() & AREA_GAP ) {
+					if ( !AllGapsLeadToOtherNode( p->GetNode(!s), node ) ) {
+						continue;
+					}
+				}
+			}
+			else if ( node->GetFlags() & AREA_GAP ) {
+				if ( p->GetNode(!s)->GetFlags() & AREA_FLOOR ) {
+					if ( !AllGapsLeadToOtherNode( node, p->GetNode(!s) ) ) {
+						continue;
+					}
+				}
+			}
+
+			otherNodeFlags = p->GetNode(!s)->GetFlags();
+
+			// try to merge the leaf nodes
+			if ( bsp.TryMergeLeafNodes( p, s ) ) {
+				node->SetFlag( otherNodeFlags );
+				if ( node->GetFlags() & AREA_FLOOR ) {
+					node->RemoveFlag( AREA_GAP );
+				}
+				numMerges++;
+				DisplayRealTimeString( "\r%6d", ++numMergedLeafNodes );
+				break;
+			}
+		}
+	} while( p );
+
+	if ( numMerges ) {
+		return true;
+	}
+	return false;
+}
+
+/*
+============
+idAASBuild::MergeLeafNodes_r
+============
+*/
+void idAASBuild::MergeLeafNodes_r( idBrushBSP &bsp, idBrushBSPNode *node ) {
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->GetContents() & AREACONTENTS_SOLID ) {
+		return;
+	}
+
+	if ( node->GetFlags() & NODE_DONE ) {
+		return;
+	}
+
+	if ( !node->GetChild(0) && !node->GetChild(1) ) {
+		MergeWithAdjacentLeafNodes( bsp, node );
+		node->SetFlag( NODE_DONE );
+		return;
+	}
+
+	MergeLeafNodes_r( bsp, node->GetChild(0) );
+	MergeLeafNodes_r( bsp, node->GetChild(1) );
+
+	return;
+}
+
+/*
+============
+idAASBuild::MergeLeafNodes
+============
+*/
+void idAASBuild::MergeLeafNodes( idBrushBSP &bsp ) {
+	numMergedLeafNodes = 0;
+
+	common->Printf( "[Merge Leaf Nodes]\n" );
+
+	MergeLeafNodes_r( bsp, bsp.GetRootNode() );
+	bsp.GetRootNode()->RemoveFlagRecurse( NODE_DONE );
+	bsp.PruneMergedTree_r( bsp.GetRootNode() );
+
+	common->Printf( "\r%6d leaf nodes merged\n", numMergedLeafNodes );
+}
diff --git a/src/tools/compilers/aas/AASCluster.cpp b/src/tools/compilers/aas/AASCluster.cpp
new file mode 100644
index 0000000..49dbdf2
--- /dev/null
+++ b/src/tools/compilers/aas/AASCluster.cpp
@@ -0,0 +1,553 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "aas/AASFile_local.h"
+
+#include "tools/compilers/aas/AASCluster.h"
+
+/*
+================
+idAASCluster::UpdatePortal
+================
+*/
+bool idAASCluster::UpdatePortal( int areaNum, int clusterNum ) {
+	int portalNum;
+	aasPortal_t *portal;
+
+	// find the portal for this area
+	for ( portalNum = 1; portalNum < file->portals.Num(); portalNum++ ) {
+		if ( file->portals[portalNum].areaNum == areaNum ) {
+			break;
+		}
+	}
+
+	if ( portalNum >= file->portals.Num() ) {
+		common->Error( "no portal for area %d", areaNum );
+		return true;
+	}
+
+	portal = &file->portals[portalNum];
+
+	// if the portal is already fully updated
+	if ( portal->clusters[0] == clusterNum ) {
+		return true;
+	}
+	if ( portal->clusters[1] == clusterNum ) {
+		return true;
+	}
+	// if the portal has no front cluster yet
+	if ( !portal->clusters[0] ) {
+		portal->clusters[0] = clusterNum;
+	}
+	// if the portal has no back cluster yet
+	else if ( !portal->clusters[1] )
+	{
+		portal->clusters[1] = clusterNum;
+	}
+	else
+	{
+		// remove the cluster portal flag contents
+		file->areas[areaNum].contents &= ~AREACONTENTS_CLUSTERPORTAL;
+		return false;
+	}
+
+	// set the area cluster number to the negative portal number
+	file->areas[areaNum].cluster = -portalNum;
+
+	// add the portal to the cluster using the portal index
+	file->portalIndex.Append( portalNum );
+	file->clusters[clusterNum].numPortals++;
+	return true;
+}
+
+/*
+================
+idAASCluster::FloodClusterAreas_r
+================
+*/
+bool idAASCluster::FloodClusterAreas_r( int areaNum, int clusterNum ) {
+	aasArea_t *area;
+	aasFace_t *face;
+	int faceNum, i;
+	idReachability *reach;
+
+	area = &file->areas[areaNum];
+
+	// if the area is already part of a cluster
+	if ( area->cluster > 0 ) {
+		if ( area->cluster == clusterNum ) {
+			return true;
+		}
+		// there's a reachability going from one cluster to another only in one direction
+		common->Error( "cluster %d touched cluster %d at area %d\r\n", clusterNum, file->areas[areaNum].cluster, areaNum );
+		return false;
+	}
+
+	// if this area is a cluster portal
+	if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) {
+		return UpdatePortal( areaNum, clusterNum );
+	}
+
+	// set the area cluster number
+	area->cluster = clusterNum;
+
+	if ( !noFaceFlood ) {
+		// use area faces to flood into adjacent areas
+		for ( i = 0; i < area->numFaces; i++ ) {
+			faceNum = abs(file->faceIndex[area->firstFace + i]);
+			face = &file->faces[faceNum];
+			if ( face->areas[0] == areaNum ) {
+				if ( face->areas[1] ) {
+					if ( !FloodClusterAreas_r( face->areas[1], clusterNum ) ) {
+						return false;
+					}
+				}
+			}
+			else {
+				if ( face->areas[0] ) {
+					if ( !FloodClusterAreas_r( face->areas[0], clusterNum ) ) {
+						return false;
+					}
+				}
+			}
+		}
+	}
+
+	// use the reachabilities to flood into other areas
+	for ( reach = file->areas[areaNum].reach; reach; reach = reach->next ) {
+		if ( !FloodClusterAreas_r( reach->toAreaNum, clusterNum) ) {
+			return false;
+		}
+	}
+
+	// use the reversed reachabilities to flood into other areas
+	for ( reach = file->areas[areaNum].rev_reach; reach; reach = reach->rev_next ) {
+		if ( !FloodClusterAreas_r( reach->fromAreaNum, clusterNum) ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+================
+idAASCluster::RemoveAreaClusterNumbers
+================
+*/
+void idAASCluster::RemoveAreaClusterNumbers( void ) {
+	int i;
+
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+		file->areas[i].cluster = 0;
+	}
+}
+
+/*
+================
+idAASCluster::NumberClusterAreas
+================
+*/
+void idAASCluster::NumberClusterAreas( int clusterNum ) {
+	int i, portalNum;
+	aasCluster_t *cluster;
+	aasPortal_t *portal;
+
+	cluster = &file->clusters[clusterNum];
+	cluster->numAreas = 0;
+	cluster->numReachableAreas = 0;
+
+	// number all areas in this cluster WITH reachabilities
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+
+		if ( file->areas[i].cluster != clusterNum ) {
+			continue;
+		}
+
+		if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
+			continue;
+		}
+
+		file->areas[i].clusterAreaNum = cluster->numAreas++;
+		cluster->numReachableAreas++;
+	}
+
+	// number all portals in this cluster WITH reachabilities
+	for ( i = 0; i < cluster->numPortals; i++ ) {
+		portalNum = file->portalIndex[cluster->firstPortal + i];
+		portal = &file->portals[portalNum];
+
+		if ( !(file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
+			continue;
+		}
+
+		if ( portal->clusters[0] == clusterNum ) {
+			portal->clusterAreaNum[0] = cluster->numAreas++;
+		}
+		else {
+			portal->clusterAreaNum[1] = cluster->numAreas++;
+		}
+		cluster->numReachableAreas++;
+	}
+
+	// number all areas in this cluster WITHOUT reachabilities
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+
+		if ( file->areas[i].cluster != clusterNum ) {
+			continue;
+		}
+
+		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
+			continue;
+		}
+
+		file->areas[i].clusterAreaNum = cluster->numAreas++;
+	}
+
+	// number all portals in this cluster WITHOUT reachabilities
+	for ( i = 0; i < cluster->numPortals; i++ ) {
+		portalNum = file->portalIndex[cluster->firstPortal + i];
+		portal = &file->portals[portalNum];
+
+		if ( file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
+			continue;
+		}
+
+		if ( portal->clusters[0] == clusterNum ) {
+			portal->clusterAreaNum[0] = cluster->numAreas++;
+		}
+		else {
+			portal->clusterAreaNum[1] = cluster->numAreas++;
+		}
+	}
+}
+
+/*
+================
+idAASCluster::FindClusters
+================
+*/
+bool idAASCluster::FindClusters( void ) {
+	int i, clusterNum;
+	aasCluster_t cluster;
+
+	RemoveAreaClusterNumbers();
+
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+		// if the area is already part of a cluster
+		if ( file->areas[i].cluster ) {
+			continue;
+		}
+
+		// if not flooding through faces only use areas that have reachabilities
+		if ( noFaceFlood ) {
+			if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
+				continue;
+			}
+		}
+
+		// if the area is a cluster portal
+		if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
+			continue;
+		}
+
+		cluster.numAreas = 0;
+		cluster.numReachableAreas = 0;
+		cluster.firstPortal = file->portalIndex.Num();
+		cluster.numPortals = 0;
+		clusterNum = file->clusters.Num();
+		file->clusters.Append( cluster );
+
+		// flood the areas in this cluster
+		if ( !FloodClusterAreas_r( i, clusterNum ) ) {
+			return false;
+		}
+
+		// number the cluster areas
+		NumberClusterAreas( clusterNum );
+	}
+	return true;
+}
+
+/*
+================
+idAASCluster::CreatePortals
+================
+*/
+void idAASCluster::CreatePortals( void ) {
+	int i;
+	aasPortal_t portal;
+
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+		// if the area is a cluster portal
+		if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
+			portal.areaNum = i;
+			portal.clusters[0] = portal.clusters[1] = 0;
+			portal.maxAreaTravelTime = 0;
+			file->portals.Append( portal );
+		}
+	}
+}
+
+/*
+================
+idAASCluster::TestPortals
+================
+*/
+bool idAASCluster::TestPortals( void ) {
+	int i;
+	aasPortal_t *portal, *portal2;
+	aasArea_t *area, *area2;
+	idReachability *reach;
+	bool ok;
+
+	ok = true;
+	for ( i = 1; i < file->portals.Num(); i++ ) {
+		portal = &file->portals[i];
+		area = &file->areas[portal->areaNum];
+
+		// if this portal was already removed
+		if ( !( area->contents & AREACONTENTS_CLUSTERPORTAL) ) {
+			continue;
+		}
+
+		// may not removed this portal if it has a reachability to a removed portal
+		for ( reach = area->reach; reach; reach = reach->next ) {
+			area2 = &file->areas[ reach->toAreaNum ];
+			if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
+				continue;
+			}
+			if ( area2->cluster < 0 ) {
+				break;
+			}
+		}
+		if ( reach ) {
+			continue;
+		}
+
+		// may not removed this portal if it has a reversed reachability to a removed portal
+		for ( reach = area->rev_reach; reach; reach = reach->rev_next ) {
+			area2 = &file->areas[ reach->toAreaNum ];
+			if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
+				continue;
+			}
+			if ( area2->cluster < 0 ) {
+				break;
+			}
+		}
+		if ( reach ) {
+			continue;
+		}
+
+		// portal should have two clusters set
+		if ( !portal->clusters[0] ) {
+			area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
+			ok = false;
+			continue;
+		}
+		if ( !portal->clusters[1] ) {
+			area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
+			ok = false;
+			continue;
+		}
+
+		// this portal may not have reachabilities to a portal that doesn't seperate the same clusters
+		for ( reach = area->reach; reach; reach = reach->next ) {
+			area2 = &file->areas[ reach->toAreaNum ];
+
+			if ( !(area2->contents & AREACONTENTS_CLUSTERPORTAL) ) {
+				continue;
+			}
+
+			if ( area2->cluster > 0 ) {
+				area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
+				ok = false;
+				continue;
+			}
+
+			portal2 = &file->portals[ -file->areas[ reach->toAreaNum ].cluster ];
+
+			if ( ( portal2->clusters[0] != portal->clusters[0] && portal2->clusters[0] != portal->clusters[1] ) ||
+				( portal2->clusters[1] != portal->clusters[0] && portal2->clusters[1] != portal->clusters[1] ) ) {
+				area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
+				ok = false;
+				continue;
+			}
+		}
+	}
+
+	return ok;
+}
+
+/*
+================
+idAASCluster::RemoveInvalidPortals
+================
+*/
+void idAASCluster::RemoveInvalidPortals( void ) {
+	int i, j, k, face1Num, face2Num, otherAreaNum, numOpenAreas, numInvalidPortals;
+	aasFace_t *face1, *face2;
+
+	numInvalidPortals = 0;
+	for ( i = 0; i < file->areas.Num(); i++ ) {
+		if ( !( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
+			continue;
+		}
+
+		numOpenAreas = 0;
+		for ( j = 0; j < file->areas[i].numFaces; j++ ) {
+			face1Num = file->faceIndex[ file->areas[i].firstFace + j ];
+			face1 = &file->faces[ abs(face1Num) ];
+			otherAreaNum = face1->areas[ face1Num < 0 ];
+
+			if ( !otherAreaNum ) {
+				continue;
+			}
+
+			for ( k = 0; k < j; k++ ) {
+				face2Num = file->faceIndex[ file->areas[i].firstFace + k ];
+				face2 = &file->faces[ abs(face2Num) ];
+				if ( otherAreaNum == face2->areas[ face2Num < 0 ] ) {
+					break;
+				}
+			}
+			if ( k < j ) {
+				continue;
+			}
+
+			if ( !( file->areas[otherAreaNum].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
+				numOpenAreas++;
+			}
+		}
+
+		if ( numOpenAreas <= 1 ) {
+			file->areas[i].contents &= AREACONTENTS_CLUSTERPORTAL;
+			numInvalidPortals++;
+		}
+	}
+
+	common->Printf( "\r%6d invalid portals removed\n", numInvalidPortals );
+}
+
+/*
+================
+idAASCluster::Build
+================
+*/
+bool idAASCluster::Build( idAASFileLocal *file ) {
+
+	common->Printf( "[Clustering]\n" );
+
+	this->file = file;
+	this->noFaceFlood = true;
+
+	RemoveInvalidPortals();
+
+	while( 1 ) {
+
+		// delete all existing clusters
+		file->DeleteClusters();
+
+		// create the portals from the portal areas
+		CreatePortals();
+
+		common->Printf( "\r%6d", file->portals.Num() );
+
+		// find the clusters
+		if ( !FindClusters() ) {
+			continue;
+		}
+
+		// test the portals
+		if ( !TestPortals() ) {
+			continue;
+		}
+
+		break;
+	}
+
+	common->Printf( "\r%6d portals\n", file->portals.Num() );
+	common->Printf( "%6d clusters\n", file->clusters.Num() );
+
+	for ( int i = 0; i < file->clusters.Num(); i++ ) {
+		common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
+	}
+
+	file->ReportRoutingEfficiency();
+
+	return true;
+}
+
+/*
+================
+idAASCluster::BuildSingleCluster
+================
+*/
+bool idAASCluster::BuildSingleCluster( idAASFileLocal *file ) {
+	int i, numAreas;
+	aasCluster_t cluster;
+
+	common->Printf( "[Clustering]\n" );
+
+	this->file = file;
+
+	// delete all existing clusters
+	file->DeleteClusters();
+
+	cluster.firstPortal = 0;
+	cluster.numPortals = 0;
+	cluster.numAreas = file->areas.Num();
+	cluster.numReachableAreas = 0;
+	// give all reachable areas in the cluster a number
+	for ( i = 0; i < file->areas.Num(); i++ ) {
+		file->areas[i].cluster = file->clusters.Num();
+		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
+			file->areas[i].clusterAreaNum = cluster.numReachableAreas++;
+		}
+	}
+	// give the remaining areas a number within the cluster
+	numAreas = cluster.numReachableAreas;
+	for ( i = 0; i < file->areas.Num(); i++ ) {
+		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
+			continue;
+		}
+		file->areas[i].clusterAreaNum = numAreas++;
+	}
+	file->clusters.Append( cluster );
+
+	common->Printf( "%6d portals\n", file->portals.Num() );
+	common->Printf( "%6d clusters\n", file->clusters.Num() );
+
+	for ( i = 0; i < file->clusters.Num(); i++ ) {
+		common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
+	}
+
+	file->ReportRoutingEfficiency();
+
+	return true;
+}
diff --git a/src/tools/compilers/aas/AASCluster.h b/src/tools/compilers/aas/AASCluster.h
new file mode 100644
index 0000000..5795d9a
--- /dev/null
+++ b/src/tools/compilers/aas/AASCluster.h
@@ -0,0 +1,64 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#ifndef __AASCLUSTER_H__
+#define __AASCLUSTER_H__
+
+#include "tools/compilers/aas/AASBuild_local.h"
+
+/*
+===============================================================================
+
+	Area Clustering
+
+===============================================================================
+*/
+
+class idAASCluster {
+
+public:
+	bool					Build( idAASFileLocal *file );
+	bool					BuildSingleCluster( idAASFileLocal *file );
+
+private:
+	idAASFileLocal *		file;
+	bool					noFaceFlood;
+
+private:
+	bool					UpdatePortal( int areaNum, int clusterNum );
+	bool					FloodClusterAreas_r( int areaNum, int clusterNum );
+	void					RemoveAreaClusterNumbers( void );
+	void					NumberClusterAreas( int clusterNum );
+	bool					FindClusters( void );
+	void					CreatePortals( void );
+	bool					TestPortals( void );
+	void					ReportEfficiency( void );
+	void					RemoveInvalidPortals( void );
+};
+
+#endif /* !__AASCLUSTER_H__ */
diff --git a/src/tools/compilers/aas/AASReach.cpp b/src/tools/compilers/aas/AASReach.cpp
new file mode 100644
index 0000000..8ef693b
--- /dev/null
+++ b/src/tools/compilers/aas/AASReach.cpp
@@ -0,0 +1,941 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "tools/compilers/aas/AASReach.h"
+
+#define INSIDEUNITS							2.0f
+#define INSIDEUNITS_WALKEND					0.5f
+#define INSIDEUNITS_WALKSTART				0.1f
+#define INSIDEUNITS_SWIMEND					0.5f
+#define INSIDEUNITS_FLYEND					0.5f
+#define INSIDEUNITS_WATERJUMP				15.0f
+
+/*
+================
+idAASReach::ReachabilityExists
+================
+*/
+bool idAASReach::ReachabilityExists( int fromAreaNum, int toAreaNum ) {
+	aasArea_t *area;
+	idReachability *reach;
+
+	area = &file->areas[fromAreaNum];
+	for ( reach = area->reach; reach; reach = reach->next ) {
+		if ( reach->toAreaNum == toAreaNum ) {
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+================
+idAASReach::CanSwimInArea
+================
+*/
+ID_INLINE bool idAASReach::CanSwimInArea( int areaNum ) {
+	return ( file->areas[areaNum].contents & AREACONTENTS_WATER ) != 0;
+}
+
+/*
+================
+idAASReach::AreaHasFloor
+================
+*/
+ID_INLINE bool idAASReach::AreaHasFloor( int areaNum ) {
+	return ( file->areas[areaNum].flags & AREA_FLOOR ) != 0;
+}
+
+/*
+================
+idAASReach::AreaIsClusterPortal
+================
+*/
+ID_INLINE bool idAASReach::AreaIsClusterPortal( int areaNum ) {
+	return ( file->areas[areaNum].contents & AREACONTENTS_CLUSTERPORTAL ) != 0;
+}
+
+/*
+================
+idAASReach::AddReachabilityToArea
+================
+*/
+void idAASReach::AddReachabilityToArea( idReachability *reach, int areaNum ) {
+	aasArea_t *area;
+
+	area = &file->areas[areaNum];
+	reach->next = area->reach;
+	area->reach = reach;
+	numReachabilities++;
+}
+
+/*
+================
+idAASReach::Reachability_Fly
+================
+*/
+void idAASReach::Reachability_Fly( int areaNum ) {
+	int i, faceNum, otherAreaNum;
+	aasArea_t *area;
+	aasFace_t *face;
+	idReachability_Fly *reach;
+
+	area = &file->areas[areaNum];
+
+	for ( i = 0; i < area->numFaces; i++ ) {
+		faceNum = file->faceIndex[area->firstFace + i];
+		face = &file->faces[abs(faceNum)];
+
+		otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
+
+		if ( otherAreaNum == 0 ) {
+			continue;
+		}
+
+		if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
+			continue;
+		}
+
+		// create reachability going through this face
+		reach = new idReachability_Fly();
+		reach->travelType = TFL_FLY;
+		reach->toAreaNum = otherAreaNum;
+		reach->fromAreaNum = areaNum;
+		reach->edgeNum = 0;
+		reach->travelTime = 1;
+		reach->start = file->FaceCenter( abs(faceNum) );
+		if ( faceNum < 0 ) {
+			reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
+		} else {
+			reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
+		}
+		AddReachabilityToArea( reach, areaNum );
+	}
+}
+
+/*
+================
+idAASReach::Reachability_Swim
+================
+*/
+void idAASReach::Reachability_Swim( int areaNum ) {
+	int i, faceNum, otherAreaNum;
+	aasArea_t *area;
+	aasFace_t *face;
+	idReachability_Swim *reach;
+
+	if ( !CanSwimInArea( areaNum ) ) {
+		return;
+	}
+
+	area = &file->areas[areaNum];
+
+	for ( i = 0; i < area->numFaces; i++ ) {
+		faceNum = file->faceIndex[area->firstFace + i];
+		face = &file->faces[abs(faceNum)];
+
+		otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
+
+		if ( otherAreaNum == 0 ) {
+			continue;
+		}
+
+		if ( !CanSwimInArea( otherAreaNum ) ) {
+			continue;
+		}
+
+		if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
+			continue;
+		}
+
+		// create reachability going through this face
+		reach = new idReachability_Swim();
+		reach->travelType = TFL_SWIM;
+		reach->toAreaNum = otherAreaNum;
+		reach->fromAreaNum = areaNum;
+		reach->edgeNum = 0;
+		reach->travelTime = 1;
+		reach->start = file->FaceCenter( abs(faceNum) );
+		if ( faceNum < 0 ) {
+			reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
+		} else {
+			reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
+		}
+		AddReachabilityToArea( reach, areaNum );
+	}
+}
+
+/*
+================
+idAASReach::Reachability_EqualFloorHeight
+================
+*/
+void idAASReach::Reachability_EqualFloorHeight( int areaNum ) {
+	int i, k, l, m, n, faceNum, face1Num, face2Num, otherAreaNum, edge1Num, edge2Num;
+	aasArea_t *area, *otherArea;
+	aasFace_t *face, *face1, *face2;
+	idReachability_Walk *reach;
+
+	if ( !AreaHasFloor( areaNum ) ) {
+		return;
+	}
+
+	area = &file->areas[areaNum];
+
+	for ( i = 0; i < area->numFaces; i++ ) {
+		faceNum = file->faceIndex[area->firstFace + i];
+		face = &file->faces[abs(faceNum)];
+
+		otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
+		if ( !AreaHasFloor( otherAreaNum ) ) {
+			continue;
+		}
+
+		otherArea = &file->areas[otherAreaNum];
+
+		for ( k = 0; k < area->numFaces; k++ ) {
+			face1Num = file->faceIndex[area->firstFace + k];
+			face1 = &file->faces[abs(face1Num)];
+
+			if ( !( face1->flags & FACE_FLOOR ) ) {
+				continue;
+			}
+			for ( l = 0; l < otherArea->numFaces; l++ ) {
+				face2Num = file->faceIndex[otherArea->firstFace + l];
+				face2 = &file->faces[abs(face2Num)];
+
+				if ( !( face2->flags & FACE_FLOOR ) ) {
+					continue;
+				}
+
+				for ( m = 0; m < face1->numEdges; m++ ) {
+					edge1Num = abs(file->edgeIndex[face1->firstEdge + m]);
+					for ( n = 0; n < face2->numEdges; n++ ) {
+						edge2Num = abs(file->edgeIndex[face2->firstEdge + n]);
+						if ( edge1Num == edge2Num ) {
+							break;
+						}
+					}
+					if ( n < face2->numEdges ) {
+						break;
+					}
+				}
+				if ( m < face1->numEdges ) {
+					break;
+				}
+			}
+			if ( l < otherArea->numFaces ) {
+				break;
+			}
+		}
+		if ( k < area->numFaces ) {
+			// create reachability
+			reach = new idReachability_Walk();
+			reach->travelType = TFL_WALK;
+			reach->toAreaNum = otherAreaNum;
+			reach->fromAreaNum = areaNum;
+			reach->edgeNum = abs( edge1Num );
+			reach->travelTime = 1;
+			reach->start = file->EdgeCenter( edge1Num );
+			if ( faceNum < 0 ) {
+				reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
+			}
+			else {
+				reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
+			}
+			AddReachabilityToArea( reach, areaNum );
+		}
+	}
+}
+
+/*
+================
+idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge
+================
+*/
+bool idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) {
+	int i, j, k, l, edge1Num, edge2Num, areas[10];
+	int floor_bestArea1FloorEdgeNum, floor_foundReach;
+	int water_foundReach;
+	int side1, faceSide1, floorFace1Num;
+	float dist, dist1, dist2, diff, /*invGravityDot, */ orthogonalDot;
+	float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y;
+	float length, floor_bestLength, water_bestLength, floor_bestDist, water_bestDist;
+	idVec3 v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2;
+	idVec3 normal, orthogonal, edgeVec, start, end;
+	idVec3 floor_bestStart, floor_bestEnd, floor_bestNormal;
+	idVec3 water_bestStart, water_bestEnd, water_bestNormal;
+	idVec3 testPoint;
+	idPlane *plane;
+	aasArea_t *area1, *area2;
+	aasFace_t *floorFace1, *floorFace2;
+	aasEdge_t *edge1, *edge2;
+	idReachability_Walk *walkReach;
+	idReachability_BarrierJump *barrierJumpReach;
+	idReachability_WaterJump *waterJumpReach;
+	idReachability_WalkOffLedge *walkOffLedgeReach;
+	aasTrace_t trace;
+
+	// must be able to walk or swim in the first area
+	if ( !AreaHasFloor( area1num ) && !CanSwimInArea( area1num ) ) {
+		return false;
+	}
+
+	if ( !AreaHasFloor( area2num ) && !CanSwimInArea( area2num ) ) {
+		return false;
+	}
+
+	area1 = &file->areas[area1num];
+	area2 = &file->areas[area2num];
+
+	// if the areas are not near anough in the x-y direction
+	for ( i = 0; i < 2; i++ ) {
+		if ( area1->bounds[0][i] > area2->bounds[1][i] + 2.0f ) {
+			return false;
+		}
+		if ( area1->bounds[1][i] < area2->bounds[0][i] - 2.0f ) {
+			return false;
+		}
+	}
+
+	floor_bestArea1FloorEdgeNum = 0;
+
+	floor_bestStart.Zero();
+	floor_bestEnd.Zero();
+	floor_bestNormal.Zero();
+	water_bestStart.Zero();
+	water_bestEnd.Zero();
+	water_bestNormal.Zero();
+
+	floor_foundReach = false;
+	floor_bestDist = 99999;
+	floor_bestLength = 0;
+
+	water_foundReach = false;
+	water_bestDist = 99999;
+	water_bestLength = 0;
+
+	for ( i = 0; i < area1->numFaces; i++ ) {
+		floorFace1Num = file->faceIndex[area1->firstFace + i];
+		faceSide1 = floorFace1Num < 0;
+		floorFace1 = &file->faces[abs(floorFace1Num)];
+
+		// if this isn't a floor face
+		if ( !(floorFace1->flags & FACE_FLOOR) ) {
+
+			// if we can swim in the first area
+			if ( CanSwimInArea( area1num ) ) {
+
+				// face plane must be more or less horizontal
+				plane = &file->planeList[ floorFace1->planeNum ^ (!faceSide1) ];
+				if ( plane->Normal() * file->settings.invGravityDir < file->settings.minFloorCos ) {
+					continue;
+				}
+			}
+			else {
+				// if we can't swim in the area it must be a ground face
+				continue;
+			}
+		}
+
+		for ( k = 0; k < floorFace1->numEdges; k++ ) {
+			edge1Num = file->edgeIndex[floorFace1->firstEdge + k];
+			side1 = (edge1Num < 0);
+			// NOTE: for water faces we must take the side area 1 is on into
+			// account because the face is shared and doesn't have to be oriented correctly
+			if ( !(floorFace1->flags & FACE_FLOOR) ) {
+				side1 = (side1 == faceSide1);
+			}
+			edge1Num = abs(edge1Num);
+			edge1 = &file->edges[edge1Num];
+			// vertices of the edge
+			v1 = file->vertices[edge1->vertexNum[!side1]];
+			v2 = file->vertices[edge1->vertexNum[side1]];
+			// get a vertical plane through the edge
+			// NOTE: normal is pointing into area 2 because the face edges are stored counter clockwise
+			edgeVec = v2 - v1;
+			normal = edgeVec.Cross( file->settings.invGravityDir );
+			normal.Normalize();
+			dist = normal * v1;
+
+			// check the faces from the second area
+			for ( j = 0; j < area2->numFaces; j++ ) {
+				floorFace2 = &file->faces[abs(file->faceIndex[area2->firstFace + j])];
+				// must be a ground face
+				if ( !(floorFace2->flags & FACE_FLOOR) ) {
+					continue;
+				}
+				// check the edges of this ground face
+				for ( l = 0; l < floorFace2->numEdges; l++ ) {
+					edge2Num = abs(file->edgeIndex[floorFace2->firstEdge + l]);
+					edge2 = &file->edges[edge2Num];
+					// vertices of the edge
+					v3 = file->vertices[edge2->vertexNum[0]];
+					v4 = file->vertices[edge2->vertexNum[1]];
+					// check the distance between the two points and the vertical plane through the edge of area1
+					diff = normal * v3 - dist;
+					if ( diff < -0.2f || diff > 0.2f ) {
+						continue;
+					}
+					diff = normal * v4 - dist;
+					if ( diff < -0.2f || diff > 0.2f ) {
+						continue;
+					}
+
+					// project the two ground edges into the step side plane
+					// and calculate the shortest distance between the two
+					// edges if they overlap in the direction orthogonal to
+					// the gravity direction
+					orthogonal = file->settings.invGravityDir.Cross( normal );
+					//invGravityDot = file->settings.invGravityDir * file->settings.invGravityDir;
+					orthogonalDot = orthogonal * orthogonal;
+					// projection into the step plane
+					// NOTE: since gravity is vertical this is just the z coordinate
+					y1 = v1[2];//(v1 * file->settings.invGravity) / invGravityDot;
+					y2 = v2[2];//(v2 * file->settings.invGravity) / invGravityDot;
+					y3 = v3[2];//(v3 * file->settings.invGravity) / invGravityDot;
+					y4 = v4[2];//(v4 * file->settings.invGravity) / invGravityDot;
+
+					x1 = (v1 * orthogonal) / orthogonalDot;
+					x2 = (v2 * orthogonal) / orthogonalDot;
+					x3 = (v3 * orthogonal) / orthogonalDot;
+					x4 = (v4 * orthogonal) / orthogonalDot;
+
+					if ( x1 > x2 ) {
+						tmp = x1; x1 = x2; x2 = tmp;
+						tmp = y1; y1 = y2; y2 = tmp;
+						tmpv = v1; v1 = v2; v2 = tmpv;
+					}
+					if ( x3 > x4 ) {
+						tmp = x3; x3 = x4; x4 = tmp;
+						tmp = y3; y3 = y4; y4 = tmp;
+						tmpv = v3; v3 = v4; v4 = tmpv;
+					}
+					// if the two projected edge lines have no overlap
+					if ( x2 <= x3 || x4 <= x1 ) {
+						continue;
+					}
+					// if the two lines fully overlap
+					if ( (x1 - 0.5f < x3 && x4 < x2 + 0.5f) && (x3 - 0.5f < x1 && x2 < x4 + 0.5f) ) {
+						dist1 = y3 - y1;
+						dist2 = y4 - y2;
+						p1area1 = v1;
+						p2area1 = v2;
+						p1area2 = v3;
+						p2area2 = v4;
+					}
+					else {
+						// if the points are equal
+						if ( x1 > x3 - 0.1f && x1 < x3 + 0.1f ) {
+							dist1 = y3 - y1;
+							p1area1 = v1;
+							p1area2 = v3;
+						}
+						else if ( x1 < x3 ) {
+							y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1);
+							dist1 = y3 - y;
+							p1area1 = v3;
+							p1area1[2] = y;
+							p1area2 = v3;
+						}
+						else {
+							y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3);
+							dist1 = y - y1;
+							p1area1 = v1;
+							p1area2 = v1;
+							p1area2[2] = y;
+						}
+						// if the points are equal
+						if ( x2 > x4 - 0.1f && x2 < x4 + 0.1f ) {
+							dist2 = y4 - y2;
+							p2area1 = v2;
+							p2area2 = v4;
+						}
+						else if ( x2 < x4 ) {
+							y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3);
+							dist2 = y - y2;
+							p2area1 = v2;
+							p2area2 = v2;
+							p2area2[2] = y;
+						}
+						else {
+							y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1);
+							dist2 = y4 - y;
+							p2area1 = v4;
+							p2area1[2] = y;
+							p2area2 = v4;
+						}
+					}
+
+					// if both distances are pretty much equal then we take the middle of the points
+					if ( dist1 > dist2 - 1.0f && dist1 < dist2 + 1.0f ) {
+						dist = dist1;
+						start = ( p1area1 + p2area1 ) * 0.5f;
+						end = ( p1area2 + p2area2 ) * 0.5f;
+					}
+					else if (dist1 < dist2) {
+						dist = dist1;
+						start = p1area1;
+						end = p1area2;
+					}
+					else {
+						dist = dist2;
+						start = p2area1;
+						end = p2area2;
+					}
+
+					// get the length of the overlapping part of the edges of the two areas
+					length = (p2area2 - p1area2).Length();
+
+					if ( floorFace1->flags & FACE_FLOOR ) {
+						// if the vertical distance is smaller
+						if ( dist < floor_bestDist ||
+								// or the vertical distance is pretty much the same
+								// but the overlapping part of the edges is longer
+								(dist < floor_bestDist + 1.0f && length > floor_bestLength) ) {
+							floor_bestDist = dist;
+							floor_bestLength = length;
+							floor_foundReach = true;
+							floor_bestArea1FloorEdgeNum = edge1Num;
+							floor_bestStart = start;
+							floor_bestNormal = normal;
+							floor_bestEnd = end;
+						}
+					}
+					else {
+						// if the vertical distance is smaller
+						if ( dist < water_bestDist ||
+								//or the vertical distance is pretty much the same
+								//but the overlapping part of the edges is longer
+								(dist < water_bestDist + 1.0f && length > water_bestLength) ) {
+							water_bestDist = dist;
+							water_bestLength = length;
+							water_foundReach = true;
+							water_bestStart = start;	// best start point in area1
+							water_bestNormal = normal;	// normal is pointing into area2
+							water_bestEnd = end;		// best point towards area2
+						}
+					}
+				}
+			}
+		}
+	}
+	//
+	// NOTE: swim reachabilities should already be filtered out
+	//
+	// Steps
+	//
+	//         ---------
+	//         |          step height -> TFL_WALK
+	// --------|
+	//
+	//         ---------
+	// ~~~~~~~~|          step height and low water -> TFL_WALK
+	// --------|
+	//
+	// ~~~~~~~~~~~~~~~~~~
+	//         ---------
+	//         |          step height and low water up to the step -> TFL_WALK
+	// --------|
+	//
+	// check for a step reachability
+	if ( floor_foundReach ) {
+		// if area2 is higher but lower than the maximum step height
+		// NOTE: floor_bestDist >= 0 also catches equal floor reachabilities
+		if ( floor_bestDist >= 0 && floor_bestDist < file->settings.maxStepHeight ) {
+			// create walk reachability from area1 to area2
+			walkReach = new idReachability_Walk();
+			walkReach->travelType = TFL_WALK;
+			walkReach->toAreaNum = area2num;
+			walkReach->fromAreaNum = area1num;
+			walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
+			walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
+			walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
+			walkReach->travelTime = 0;
+			if ( area2->flags & AREA_CROUCH ) {
+				walkReach->travelTime += file->settings.tt_startCrouching;
+			}
+			AddReachabilityToArea( walkReach, area1num );
+			return true;
+		}
+	}
+	//
+	// Water Jumps
+	//
+	//         ---------
+	//         |
+	// ~~~~~~~~|
+	//         |
+	//         |          higher than step height and water up to waterjump height -> TFL_WATERJUMP
+	// --------|
+	//
+	// ~~~~~~~~~~~~~~~~~~
+	//         ---------
+	//         |
+	//         |
+	//         |
+	//         |          higher than step height and low water up to the step -> TFL_WATERJUMP
+	// --------|
+	//
+	// check for a waterjump reachability
+	if ( water_foundReach ) {
+		// get a test point a little bit towards area1
+		testPoint = water_bestEnd - INSIDEUNITS * water_bestNormal;
+		// go down the maximum waterjump height
+		testPoint[2] -= file->settings.maxWaterJumpHeight;
+		// if there IS water the sv_maxwaterjump height below the bestend point
+		if ( area1->flags & AREA_LIQUID ) {
+			// don't create rediculous water jump reachabilities from areas very far below the water surface
+			if ( water_bestDist < file->settings.maxWaterJumpHeight + 24 ) {
+				// water jumping from or towards a crouch only areas is not possible
+				if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
+					// create water jump reachability from area1 to area2
+					waterJumpReach = new idReachability_WaterJump();
+					waterJumpReach->travelType = TFL_WATERJUMP;
+					waterJumpReach->toAreaNum = area2num;
+					waterJumpReach->fromAreaNum = area1num;
+					waterJumpReach->start = water_bestStart;
+					waterJumpReach->end = water_bestEnd + INSIDEUNITS_WATERJUMP * water_bestNormal;
+					waterJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
+					waterJumpReach->travelTime = file->settings.tt_waterJump;
+					AddReachabilityToArea( waterJumpReach, area1num );
+					return true;
+				}
+			}
+		}
+	}
+	//
+	// Barrier Jumps
+	//
+	//         ---------
+	//         |
+	//         |
+	//         |
+	//         |         higher than max step height lower than max barrier height -> TFL_BARRIERJUMP
+	// --------|
+	//
+	//         ---------
+	//         |
+	//         |
+	//         |
+	// ~~~~~~~~|         higher than max step height lower than max barrier height
+	// --------|         and a thin layer of water in the area to jump from -> TFL_BARRIERJUMP
+	//
+	// check for a barrier jump reachability
+	if ( floor_foundReach ) {
+		//if area2 is higher but lower than the maximum barrier jump height
+		if ( floor_bestDist > 0 && floor_bestDist < file->settings.maxBarrierHeight ) {
+			//if no water in area1 or a very thin layer of water on the ground
+			if ( !water_foundReach || (floor_bestDist - water_bestDist < 16) ) {
+				// cannot perform a barrier jump towards or from a crouch area
+				if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
+					// create barrier jump reachability from area1 to area2
+					barrierJumpReach = new idReachability_BarrierJump();
+					barrierJumpReach->travelType = TFL_BARRIERJUMP;
+					barrierJumpReach->toAreaNum = area2num;
+					barrierJumpReach->fromAreaNum = area1num;
+					barrierJumpReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
+					barrierJumpReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
+					barrierJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
+					barrierJumpReach->travelTime = file->settings.tt_barrierJump;
+					AddReachabilityToArea( barrierJumpReach, area1num );
+					return true;
+				}
+			}
+		}
+	}
+	//
+	// Walk and Walk Off Ledge
+	//
+	// --------|
+	//         |          can walk or step back -> TFL_WALK
+	//         ---------
+	//
+	// --------|
+	//         |
+	//         |
+	//         |
+	//         |          cannot walk/step back -> TFL_WALKOFFLEDGE
+	//         ---------
+	//
+	// --------|
+	//         |
+	//         |~~~~~~~~
+	//         |
+	//         |          cannot step back but can waterjump back -> TFL_WALKOFFLEDGE
+	//         ---------  FIXME: create TFL_WALK reach??
+	//
+	// check for a walk or walk off ledge reachability
+	if ( floor_foundReach ) {
+		if ( floor_bestDist < 0 ) {
+			if ( floor_bestDist > -file->settings.maxStepHeight ) {
+				// create walk reachability from area1 to area2
+				walkReach = new idReachability_Walk();
+				walkReach->travelType = TFL_WALK;
+				walkReach->toAreaNum = area2num;
+				walkReach->fromAreaNum = area1num;
+				walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
+				walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
+				walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
+				walkReach->travelTime = 1;
+				AddReachabilityToArea( walkReach, area1num );
+				return true;
+			}
+			// if no maximum fall height set or less than the max
+			if ( !file->settings.maxFallHeight || idMath::Fabs(floor_bestDist) < file->settings.maxFallHeight ) {
+				// trace a bounding box vertically to check for solids
+				floor_bestEnd += INSIDEUNITS * floor_bestNormal;
+				start = floor_bestEnd;
+				start[2] = floor_bestStart[2];
+				end = floor_bestEnd;
+				end[2] += 4;
+				trace.areas = areas;
+				trace.maxAreas = sizeof(areas) / sizeof(int);
+				file->Trace( trace, start, end );
+				// if the trace didn't start in solid and nothing was hit
+				if ( trace.lastAreaNum && trace.fraction >= 1.0f ) {
+					// the trace end point must be in the goal area
+					if ( trace.lastAreaNum == area2num ) {
+						// don't create reachability if going through a cluster portal
+						for (i = 0; i < trace.numAreas; i++) {
+							if ( AreaIsClusterPortal( trace.areas[i] ) ) {
+								break;
+							}
+						}
+						if ( i >= trace.numAreas ) {
+							// create a walk off ledge reachability from area1 to area2
+							walkOffLedgeReach = new idReachability_WalkOffLedge();
+							walkOffLedgeReach->travelType = TFL_WALKOFFLEDGE;
+							walkOffLedgeReach->toAreaNum = area2num;
+							walkOffLedgeReach->fromAreaNum = area1num;
+							walkOffLedgeReach->start = floor_bestStart;
+							walkOffLedgeReach->end = floor_bestEnd;
+							walkOffLedgeReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
+							walkOffLedgeReach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(floor_bestDist) * 50 / file->settings.gravityValue;
+							AddReachabilityToArea( walkOffLedgeReach, area1num );
+							return true;
+						}
+					}
+				}
+			}
+		}
+	}
+	return false;
+}
+
+/*
+================
+idAASReach::Reachability_WalkOffLedge
+================
+*/
+void idAASReach::Reachability_WalkOffLedge( int areaNum ) {
+	int i, j, faceNum, edgeNum, side, reachAreaNum, p, areas[10];
+	aasArea_t *area;
+	aasFace_t *face;
+	aasEdge_t *edge;
+	idPlane *plane;
+	idVec3 v1, v2, mid, dir, testEnd;
+	idReachability_WalkOffLedge *reach;
+	aasTrace_t trace;
+
+	if ( !AreaHasFloor( areaNum ) || CanSwimInArea( areaNum ) ) {
+		return;
+	}
+
+	area = &file->areas[areaNum];
+
+	for ( i = 0; i < area->numFaces; i++ ) {
+		faceNum = file->faceIndex[area->firstFace + i];
+		face = &file->faces[abs(faceNum)];
+
+		// face must be a floor face
+		if ( !(face->flags & FACE_FLOOR) ) {
+			continue;
+		}
+
+		for ( j = 0; j < face->numEdges; j++ ) {
+
+			edgeNum = file->edgeIndex[face->firstEdge + j];
+			edge = &file->edges[abs(edgeNum)];
+
+			//if ( !(edge->flags & EDGE_LEDGE) ) {
+			//	continue;
+			//}
+
+			side = edgeNum < 0;
+
+			v1 = file->vertices[edge->vertexNum[side]];
+			v2 = file->vertices[edge->vertexNum[!side]];
+
+			plane = &file->planeList[face->planeNum ^ INTSIGNBITSET(faceNum) ];
+
+			// get the direction into the other area
+			dir = plane->Normal().Cross( v2 - v1 );
+			dir.Normalize();
+
+			mid = ( v1 + v2 ) * 0.5f;
+			testEnd = mid + INSIDEUNITS_WALKEND * dir;
+			testEnd[2] -= file->settings.maxFallHeight + 1.0f;
+			trace.areas = areas;
+			trace.maxAreas = sizeof(areas) / sizeof(int);
+			file->Trace( trace, mid, testEnd );
+
+			reachAreaNum = trace.lastAreaNum;
+			if ( !reachAreaNum || reachAreaNum == areaNum ) {
+				continue;
+			}
+			if ( idMath::Fabs( mid[2] - trace.endpos[2] ) > file->settings.maxFallHeight ) {
+				continue;
+			}
+			if ( !AreaHasFloor( reachAreaNum ) && !CanSwimInArea( reachAreaNum ) ) {
+				continue;
+			}
+			if ( ReachabilityExists( areaNum, reachAreaNum) ) {
+				continue;
+			}
+			// if not going through a cluster portal
+			for ( p = 0; p < trace.numAreas; p++ ) {
+				if ( AreaIsClusterPortal( trace.areas[p] ) ) {
+					break;
+				}
+			}
+			if ( p < trace.numAreas ) {
+				continue;
+			}
+
+			reach = new idReachability_WalkOffLedge();
+			reach->travelType = TFL_WALKOFFLEDGE;
+			reach->toAreaNum = reachAreaNum;
+			reach->fromAreaNum = areaNum;
+			reach->start = mid;
+			reach->end = trace.endpos;
+			reach->edgeNum = abs( edgeNum );
+			reach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(mid[2] - trace.endpos[2]) * 50 / file->settings.gravityValue;
+			AddReachabilityToArea( reach, areaNum );
+		}
+	}
+}
+
+/*
+================
+idAASReach::FlagReachableAreas
+================
+*/
+void idAASReach::FlagReachableAreas( idAASFileLocal *file ) {
+	int i, numReachableAreas;
+
+	numReachableAreas = 0;
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+
+		if ( ( file->areas[i].flags & ( AREA_FLOOR | AREA_LADDER ) ) ||
+				( file->areas[i].contents & AREACONTENTS_WATER ) ) {
+			file->areas[i].flags |= AREA_REACHABLE_WALK;
+		}
+		if ( file->GetSettings().allowFlyReachabilities ) {
+			file->areas[i].flags |= AREA_REACHABLE_FLY;
+		}
+		numReachableAreas++;
+	}
+
+	common->Printf( "%6d reachable areas\n", numReachableAreas );
+}
+
+/*
+================
+idAASReach::Build
+================
+*/
+bool idAASReach::Build( const idMapFile *mapFile, idAASFileLocal *file ) {
+	int i, j, lastPercent, percent;
+
+	this->mapFile = mapFile;
+	this->file = file;
+	numReachabilities = 0;
+
+	common->Printf( "[Reachability]\n" );
+
+	// delete all existing reachabilities
+	file->DeleteReachabilities();
+
+	FlagReachableAreas( file );
+
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+		if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
+			continue;
+		}
+		if ( file->GetSettings().allowSwimReachabilities ) {
+			Reachability_Swim( i );
+		}
+		Reachability_EqualFloorHeight( i );
+	}
+
+	lastPercent = -1;
+	for ( i = 1; i < file->areas.Num(); i++ ) {
+
+		if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
+			continue;
+		}
+
+		for ( j = 0; j < file->areas.Num(); j++ ) {
+			if ( i == j ) {
+				continue;
+			}
+
+			if ( !( file->areas[j].flags & AREA_REACHABLE_WALK ) ) {
+				continue;
+			}
+
+			if ( ReachabilityExists( i, j ) ) {
+				continue;
+			}
+			if ( Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) {
+				continue;
+			}
+		}
+
+		//Reachability_WalkOffLedge( i );
+
+		percent = 100 * i / file->areas.Num();
+		if ( percent > lastPercent ) {
+			common->Printf( "\r%6d%%", percent );
+			lastPercent = percent;
+		}
+	}
+
+	if ( file->GetSettings().allowFlyReachabilities ) {
+		for ( i = 1; i < file->areas.Num(); i++ ) {
+			Reachability_Fly( i );
+		}
+	}
+
+	file->LinkReversedReachability();
+
+	common->Printf( "\r%6d reachabilities\n", numReachabilities );
+
+	return true;
+}
diff --git a/src/tools/compilers/aas/AASReach.h b/src/tools/compilers/aas/AASReach.h
new file mode 100644
index 0000000..3cfa217
--- /dev/null
+++ b/src/tools/compilers/aas/AASReach.h
@@ -0,0 +1,70 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#ifndef __AASREACH_H__
+#define __AASREACH_H__
+
+#include "idlib/MapFile.h"
+#include "aas/AASFile_local.h"
+
+/*
+===============================================================================
+
+	Reachabilities
+
+===============================================================================
+*/
+
+class idAASReach {
+
+public:
+	bool					Build( const idMapFile *mapFile, idAASFileLocal *file );
+
+private:
+	const idMapFile *		mapFile;
+	idAASFileLocal *		file;
+	int						numReachabilities;
+	bool					allowSwimReachabilities;
+	bool					allowFlyReachabilities;
+
+private:	// reachability
+	void					FlagReachableAreas( idAASFileLocal *file );
+	bool					ReachabilityExists( int fromAreaNum, int toAreaNum );
+	bool					CanSwimInArea( int areaNum );
+	bool					AreaHasFloor( int areaNum );
+	bool					AreaIsClusterPortal( int areaNum );
+	void					AddReachabilityToArea( idReachability *reach, int areaNum );
+	void					Reachability_Fly( int areaNum );
+	void					Reachability_Swim( int areaNum );
+	void					Reachability_EqualFloorHeight( int areaNum );
+	bool					Reachability_Step_Barrier_WaterJump_WalkOffLedge( int fromAreaNum, int toAreaNum );
+	void					Reachability_WalkOffLedge( int areaNum );
+
+};
+
+#endif /* !__AASREACH_H__ */
diff --git a/src/tools/compilers/aas/Brush.cpp b/src/tools/compilers/aas/Brush.cpp
new file mode 100644
index 0000000..a8f4c28
--- /dev/null
+++ b/src/tools/compilers/aas/Brush.cpp
@@ -0,0 +1,1583 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "idlib/MapFile.h"
+#include "framework/Common.h"
+#include "framework/FileSystem.h"
+
+#include "tools/compilers/aas/Brush.h"
+
+#define BRUSH_EPSILON					0.1f
+#define BRUSH_PLANE_NORMAL_EPSILON		0.00001f
+#define BRUSH_PLANE_DIST_EPSILON		0.01f
+
+#define OUTPUT_UPDATE_TIME				500		// update every 500 msec
+
+//#define OUTPUT_CHOP_STATS
+
+/*
+============
+DisplayRealTimeString
+============
+*/
+void DisplayRealTimeString( const char *string, ... ) {
+	va_list argPtr;
+	char buf[MAX_STRING_CHARS];
+	static int lastUpdateTime;
+	int time;
+
+	time = Sys_Milliseconds();
+	if ( time > lastUpdateTime + OUTPUT_UPDATE_TIME ) {
+		va_start( argPtr, string );
+		vsprintf( buf, string, argPtr );
+		va_end( argPtr );
+		common->Printf( buf );
+		lastUpdateTime = time;
+	}
+}
+
+
+//===============================================================
+//
+//	idBrushSide
+//
+//===============================================================
+
+/*
+============
+idBrushSide::idBrushSide
+============
+*/
+idBrushSide::idBrushSide( void ) {
+	flags = 0;
+	planeNum = -1;
+	winding = NULL;
+}
+
+/*
+============
+idBrushSide::idBrushSide
+============
+*/
+idBrushSide::idBrushSide( const idPlane &plane, int planeNum ) {
+	this->flags = 0;
+	this->plane = plane;
+	this->planeNum = planeNum;
+	this->winding = NULL;
+}
+
+/*
+============
+idBrushSide::~idBrushSide
+============
+*/
+idBrushSide::~idBrushSide( void ) {
+	if ( winding ) {
+		delete winding;
+	}
+}
+
+/*
+============
+idBrushSide::Copy
+============
+*/
+idBrushSide *idBrushSide::Copy( void ) const {
+	idBrushSide *side;
+
+	side = new idBrushSide( plane, planeNum );
+	side->flags = flags;
+	if ( winding ) {
+		side->winding = winding->Copy();
+	}
+	else {
+		side->winding = NULL;
+	}
+	return side;
+}
+
+/*
+============
+idBrushSide::Split
+============
+*/
+int idBrushSide::Split( const idPlane &splitPlane, idBrushSide **front, idBrushSide **back ) const {
+	idWinding *frontWinding, *backWinding;
+
+	assert( winding );
+
+	*front = *back = NULL;
+
+	winding->Split( splitPlane, 0.0f, &frontWinding, &backWinding );
+
+	if ( frontWinding ) {
+		(*front) = new idBrushSide( plane, planeNum );
+		(*front)->winding = frontWinding;
+		(*front)->flags = flags;
+	}
+
+	if ( backWinding ) {
+		(*back) = new idBrushSide( plane, planeNum );
+		(*back)->winding = backWinding;
+		(*back)->flags = flags;
+	}
+
+	if ( frontWinding && backWinding ) {
+		return PLANESIDE_CROSS;
+	}
+	else if ( frontWinding ) {
+		return PLANESIDE_FRONT;
+	}
+	else {
+		return PLANESIDE_BACK;
+	}
+}
+
+
+//===============================================================
+//
+//	idBrushSide
+//
+//===============================================================
+
+/*
+============
+idBrush::idBrush
+============
+*/
+idBrush::idBrush( void ) {
+	contents = flags = 0;
+	bounds.Clear();
+	sides.Clear();
+	windingsValid = false;
+}
+
+
+/*
+============
+idBrush::~idBrush
+============
+*/
+idBrush::~idBrush( void ) {
+	for ( int i = 0; i < sides.Num(); i++ ) {
+		delete sides[i];
+	}
+}
+
+/*
+============
+idBrush::RemoveSidesWithoutWinding
+============
+*/
+bool idBrush::RemoveSidesWithoutWinding( void ) {
+	int i;
+
+	for ( i = 0; i < sides.Num(); i++ ) {
+
+		if ( sides[i]->winding ) {
+			continue;
+		}
+
+		sides.RemoveIndex( i );
+		i--;
+	}
+
+	return ( sides.Num() >= 4 );
+}
+
+/*
+============
+idBrush::CreateWindings
+============
+*/
+bool idBrush::CreateWindings( void ) {
+	int i, j;
+	idBrushSide *side;
+
+	bounds.Clear();
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+
+		if ( side->winding ) {
+			delete side->winding;
+		}
+
+		side->winding = new idWinding( side->plane.Normal(), side->plane.Dist() );
+
+		for ( j = 0; j < sides.Num() && side->winding; j++ ) {
+			if ( i == j ) {
+				continue;
+			}
+			// keep the winding if on the clip plane
+			side->winding = side->winding->Clip( -sides[j]->plane, BRUSH_EPSILON, true );
+		}
+
+		if ( side->winding ) {
+			for ( j = 0; j < side->winding->GetNumPoints(); j++ ) {
+				bounds.AddPoint( (*side->winding)[j].ToVec3() );
+			}
+		}
+	}
+
+	if ( bounds[0][0] > bounds[1][0] ) {
+		return false;
+	}
+	for ( i = 0; i < 3; i++ ) {
+		if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
+			return false;
+		}
+	}
+
+	windingsValid = true;
+
+	return true;
+}
+
+/*
+============
+idBrush::BoundBrush
+============
+*/
+void idBrush::BoundBrush( const idBrush *original ) {
+	int i, j;
+	idBrushSide *side;
+	idWinding *w;
+
+	assert( windingsValid );
+
+	bounds.Clear();
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+
+		w = side->winding;
+
+		if ( !w ) {
+			continue;
+		}
+
+		for ( j = 0; j < w->GetNumPoints(); j++ ) {
+			bounds.AddPoint( (*w)[j].ToVec3() );
+		}
+	}
+
+	if ( bounds[0][0] > bounds[1][0] ) {
+		if ( original ) {
+			idBrushMap *bm = new idBrushMap( "error_brush", "_original" );
+			bm->WriteBrush( original );
+			delete bm;
+		}
+		common->Error( "idBrush::BoundBrush: brush %d on entity %d without windings", primitiveNum, entityNum );
+	}
+
+	for ( i = 0; i < 3; i++ ) {
+		if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
+			if ( original ) {
+				idBrushMap *bm = new idBrushMap( "error_brush", "_original" );
+				bm->WriteBrush( original );
+				delete bm;
+			}
+			common->Error( "idBrush::BoundBrush: brush %d on entity %d is unbounded", primitiveNum, entityNum );
+		}
+	}
+}
+
+/*
+============
+idBrush::FromSides
+============
+*/
+bool idBrush::FromSides( idList<idBrushSide *> &sideList ) {
+	int i;
+
+	for ( i = 0; i < sideList.Num(); i++ ) {
+		sides.Append( sideList[i] );
+	}
+
+	sideList.Clear();
+
+	return CreateWindings();
+}
+
+/*
+============
+idBrush::FromWinding
+============
+*/
+bool idBrush::FromWinding( const idWinding &w, const idPlane &windingPlane ) {
+	int i, j, bestAxis;
+	idPlane plane;
+	idVec3 normal, axialNormal;
+
+	sides.Append( new idBrushSide( windingPlane, -1 ) );
+	sides.Append( new idBrushSide( -windingPlane, -1 ) );
+
+	bestAxis = 0;
+	for ( i = 1; i < 3; i++ ) {
+		if ( idMath::Fabs( windingPlane.Normal()[i] ) > idMath::Fabs( windingPlane.Normal()[bestAxis] ) ) {
+			bestAxis = i;
+		}
+	}
+	axialNormal = vec3_origin;
+	if ( windingPlane.Normal()[bestAxis] > 0.0f ) {
+		axialNormal[bestAxis] = 1.0f;
+	}
+	else {
+		axialNormal[bestAxis] = -1.0f;
+	}
+
+	for ( i = 0; i < w.GetNumPoints(); i++ ) {
+		j = (i+1) % w.GetNumPoints();
+		normal = ( w[j].ToVec3() - w[i].ToVec3() ).Cross( axialNormal );
+		if ( normal.Normalize() < 0.5f ) {
+			continue;
+		}
+		plane.SetNormal( normal );
+		plane.FitThroughPoint( w[j].ToVec3() );
+		sides.Append( new idBrushSide( plane, -1 ) );
+	}
+
+	if ( sides.Num() < 4 ) {
+		for ( i = 0; i < sides.Num(); i++ ) {
+			delete sides[i];
+		}
+		sides.Clear();
+		return false;
+	}
+
+	sides[0]->winding = w.Copy();
+	windingsValid = true;
+	BoundBrush();
+
+	return true;
+}
+
+/*
+============
+idBrush::FromBounds
+============
+*/
+bool idBrush::FromBounds( const idBounds &bounds ) {
+	int axis, dir;
+	idVec3 normal;
+	idPlane plane;
+
+	for ( axis = 0; axis < 3; axis++ ) {
+		for ( dir = -1; dir <= 1; dir += 2 ) {
+			normal = vec3_origin;
+			normal[axis] = dir;
+			plane.SetNormal( normal );
+			plane.SetDist( dir * bounds[(dir == 1)][axis] );
+			sides.Append( new idBrushSide( plane, -1 ) );
+		}
+	}
+
+	return CreateWindings();
+}
+
+/*
+============
+idBrush::Transform
+============
+*/
+void idBrush::Transform( const idVec3 &origin, const idMat3 &axis ) {
+	int i;
+	bool transformed = false;
+
+	if ( axis.IsRotated() ) {
+		for ( i = 0; i < sides.Num(); i++ ) {
+			sides[i]->plane.RotateSelf( vec3_origin, axis );
+		}
+		transformed = true;
+	}
+	if ( origin != vec3_origin ) {
+		for ( i = 0; i < sides.Num(); i++ ) {
+			sides[i]->plane.TranslateSelf( origin );
+		}
+		transformed = true;
+	}
+	if ( transformed ) {
+		CreateWindings();
+	}
+}
+
+/*
+============
+idBrush::GetVolume
+============
+*/
+float idBrush::GetVolume( void ) const {
+	int i;
+	idWinding *w;
+	idVec3 corner;
+	float d, area, volume;
+
+	// grab the first valid point as a corner
+	w = NULL;
+	for ( i = 0; i < sides.Num(); i++ ) {
+		w = sides[i]->winding;
+		if ( w ) {
+			break;
+		}
+	}
+	if ( !w ) {
+		return 0.0f;
+	}
+	corner = (*w)[0].ToVec3();
+
+	// create tetrahedrons to all other sides
+	volume = 0.0f;
+	for ( ; i < sides.Num(); i++) {
+		w = sides[i]->winding;
+		if ( !w ) {
+			continue;
+		}
+		d = -( corner * sides[i]->plane.Normal() - sides[i]->plane.Dist() );
+		area = w->GetArea();
+		volume += d * area;
+	}
+
+	return ( volume * ( 1.0f / 3.0f ) );
+}
+
+/*
+============
+idBrush::Subtract
+============
+*/
+bool idBrush::Subtract( const idBrush *b, idBrushList &list ) const {
+	int i;
+	idBrush *front, *back;
+	const idBrush *in;
+
+	list.Clear();
+	in = this;
+	for ( i = 0; i < b->sides.Num() && in; i++ ) {
+
+		in->Split( b->sides[i]->plane, b->sides[i]->planeNum, &front, &back );
+
+		if ( in != this ) {
+			delete in;
+		}
+		if ( front ) {
+			list.AddToTail( front );
+		}
+		in = back;
+	}
+	// if didn't really intersect
+	if ( !in ) {
+		list.Free();
+		return false;
+	}
+
+	delete in;
+	return true;
+}
+
+/*
+============
+idBrush::TryMerge
+============
+*/
+bool idBrush::TryMerge( const idBrush *brush, const idPlaneSet &planeList ) {
+	int i, j, k, l, m, seperatingPlane;
+	const idBrush *brushes[2];
+	const idWinding *w;
+	const idPlane *plane;
+
+	// brush bounds should overlap
+	for ( i = 0; i < 3; i++ ) {
+		if ( bounds[0][i] > brush->bounds[1][i] + 0.1f ) {
+			return false;
+		}
+		if ( bounds[1][i] < brush->bounds[0][i] - 0.1f ) {
+			return false;
+		}
+	}
+
+	// the brushes should share an opposite plane
+	seperatingPlane = -1;
+	for ( i = 0; i < GetNumSides(); i++ ) {
+		for ( j = 0; j < brush->GetNumSides(); j++ ) {
+			if ( GetSide(i)->GetPlaneNum() == (brush->GetSide(j)->GetPlaneNum() ^ 1) ) {
+				// may only have one seperating plane
+				if ( seperatingPlane != -1 ) {
+					return false;
+				}
+				seperatingPlane = GetSide(i)->GetPlaneNum();
+				break;
+			}
+		}
+	}
+	if ( seperatingPlane == -1 ) {
+		return false;
+	}
+
+	brushes[0] = this;
+	brushes[1] = brush;
+
+	for ( i = 0; i < 2; i++ ) {
+
+		j = !i;
+
+		for ( k = 0; k < brushes[i]->GetNumSides(); k++ ) {
+
+			// if the brush side plane is the seprating plane
+			if ( !( ( brushes[i]->GetSide(k)->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) {
+				continue;
+			}
+
+			plane = &brushes[i]->GetSide(k)->GetPlane();
+
+			// all the non seperating brush sides of the other brush should be at the back or on the plane
+			for ( l = 0; l < brushes[j]->GetNumSides(); l++ ) {
+
+				w = brushes[j]->GetSide(l)->GetWinding();
+				if ( !w ) {
+					continue;
+				}
+
+				if ( !( ( brushes[j]->GetSide(l)->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) {
+					continue;
+				}
+
+				for ( m = 0; m < w->GetNumPoints(); m++ ) {
+					if ( plane->Distance( (*w)[m].ToVec3() ) > 0.1f ) {
+						return false;
+					}
+				}
+			}
+		}
+	}
+
+	// add any sides from the other brush to this brush
+	for ( i = 0; i < brush->GetNumSides(); i++ ) {
+		for ( j = 0; j < GetNumSides(); j++ ) {
+			if ( !( ( brush->GetSide(i)->GetPlaneNum() ^ GetSide(j)->GetPlaneNum() ) >> 1 ) ) {
+				break;
+			}
+		}
+		if ( j < GetNumSides() ) {
+			sides[j]->flags &= brush->GetSide(i)->GetFlags();
+			continue;
+		}
+		sides.Append( brush->GetSide(i)->Copy() );
+	}
+
+	// remove any side from this brush that is the opposite of a side of the other brush
+	for ( i = 0; i < GetNumSides(); i++ ) {
+		for ( j = 0; j < brush->GetNumSides(); j++ ) {
+			if ( GetSide(i)->GetPlaneNum() == ( brush->GetSide(j)->GetPlaneNum() ^ 1 ) ) {
+				break;
+			}
+		}
+		if ( j < brush->GetNumSides() ) {
+			delete sides[i];
+			sides.RemoveIndex(i);
+			i--;
+			continue;
+		}
+	}
+
+	contents |= brush->contents;
+
+	CreateWindings();
+	BoundBrush();
+
+	return true;
+}
+
+/*
+============
+idBrush::Split
+============
+*/
+int idBrush::Split( const idPlane &plane, int planeNum, idBrush **front, idBrush **back ) const {
+	int res, i, j;
+	idBrushSide *side, *frontSide, *backSide;
+	float dist, maxBack, maxFront, *maxBackWinding, *maxFrontWinding;
+	idWinding *w, *mid;
+
+	assert( windingsValid );
+
+	if ( front ) {
+		*front = NULL;
+	}
+	if ( back ) {
+		*back = NULL;
+	}
+
+	res = bounds.PlaneSide( plane, -BRUSH_EPSILON );
+	if ( res == PLANESIDE_FRONT ) {
+		if ( front ) {
+			*front = Copy();
+		}
+		return res;
+	}
+	if ( res == PLANESIDE_BACK ) {
+		if ( back ) {
+			*back = Copy();
+		}
+		return res;
+	}
+
+	maxBackWinding = (float *) _alloca16( sides.Num() * sizeof(float) );
+	maxFrontWinding = (float *) _alloca16( sides.Num() * sizeof(float) );
+
+	maxFront = maxBack = 0.0f;
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+
+		w = side->winding;
+
+		if ( !w ) {
+			continue;
+		}
+
+		maxBackWinding[i] = 10.0f;
+		maxFrontWinding[i] = -10.0f;
+
+		for ( j = 0; j < w->GetNumPoints(); j++ ) {
+
+			dist = plane.Distance( (*w)[j].ToVec3() );
+			if ( dist > maxFrontWinding[i] ) {
+				maxFrontWinding[i] = dist;
+			}
+			if ( dist < maxBackWinding[i] ) {
+				maxBackWinding[i] = dist;
+			}
+		}
+
+		if ( maxFrontWinding[i] > maxFront ) {
+			maxFront = maxFrontWinding[i];
+		}
+		if ( maxBackWinding[i] < maxBack ) {
+			maxBack = maxBackWinding[i];
+		}
+	}
+
+	if ( maxFront < BRUSH_EPSILON ) {
+		if ( back ) {
+			*back = Copy();
+		}
+		return PLANESIDE_BACK;
+	}
+
+	if ( maxBack > -BRUSH_EPSILON ) {
+		if ( front ) {
+			*front = Copy();
+		}
+		return PLANESIDE_FRONT;
+	}
+
+	mid = new idWinding( plane.Normal(), plane.Dist() );
+
+	for ( i = 0; i < sides.Num() && mid; i++ ) {
+		mid = mid->Clip( -sides[i]->plane, BRUSH_EPSILON, false );
+	}
+
+	if ( mid ) {
+		if ( mid->IsTiny() ) {
+			delete mid;
+			mid = NULL;
+		}
+		else if ( mid->IsHuge() ) {
+			// if the winding is huge then the brush is unbounded
+			common->Warning( "brush %d on entity %d is unbounded"
+						"( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )", primitiveNum, entityNum,
+							bounds[0][0], bounds[0][1], bounds[0][2], bounds[1][0], bounds[1][1], bounds[1][2],
+							bounds[1][0]-bounds[0][0], bounds[1][1]-bounds[0][1], bounds[1][2]-bounds[0][2] );
+			delete mid;
+			mid = NULL;
+		}
+	}
+
+	if ( !mid ) {
+		if ( maxFront > - maxBack ) {
+			if ( front ) {
+				*front = Copy();
+			}
+			return PLANESIDE_FRONT;
+		}
+		else {
+			if ( back ) {
+				*back = Copy();
+			}
+			return PLANESIDE_BACK;
+		}
+	}
+
+	if ( !front && !back ) {
+		delete mid;
+		return PLANESIDE_CROSS;
+	}
+
+	*front = new idBrush();
+	(*front)->SetContents( contents );
+	(*front)->SetEntityNum( entityNum );
+	(*front)->SetPrimitiveNum( primitiveNum );
+	*back = new idBrush();
+	(*back)->SetContents( contents );
+	(*back)->SetEntityNum( entityNum );
+	(*back)->SetPrimitiveNum( primitiveNum );
+
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+
+		if ( !side->winding ) {
+			continue;
+		}
+
+		// if completely at the front
+		if ( maxBackWinding[i] >= BRUSH_EPSILON ) {
+			(*front)->sides.Append( side->Copy() );
+		}
+		// if completely at the back
+		else if ( maxFrontWinding[i] <= -BRUSH_EPSILON ) {
+			(*back)->sides.Append( side->Copy() );
+		}
+		else {
+			// split the side
+			side->Split( plane, &frontSide, &backSide );
+			if ( frontSide ) {
+				(*front)->sides.Append( frontSide );
+			}
+			else if ( maxFrontWinding[i] > -BRUSH_EPSILON ) {
+				// favor an overconstrained brush
+				side = side->Copy();
+				side->winding = side->winding->Clip( idPlane( plane.Normal(), (plane.Dist() - (BRUSH_EPSILON+0.02f)) ), 0.01f, true );
+				assert( side->winding );
+				(*front)->sides.Append( side );
+			}
+			if ( backSide ) {
+				(*back)->sides.Append( backSide );
+			}
+			else if ( maxBackWinding[i] < BRUSH_EPSILON ) {
+				// favor an overconstrained brush
+				side = side->Copy();
+				side->winding = side->winding->Clip( idPlane( -plane.Normal(), -(plane.Dist() + (BRUSH_EPSILON+0.02f)) ), 0.01f, true );
+				assert( side->winding );
+				(*back)->sides.Append( side );
+			}
+		}
+	}
+
+	side = new idBrushSide( -plane, planeNum^1 );
+	side->winding = mid->Reverse();
+	side->flags |= SFL_SPLIT;
+	(*front)->sides.Append( side );
+	(*front)->windingsValid = true;
+	(*front)->BoundBrush( this );
+
+	side = new idBrushSide( plane, planeNum );
+	side->winding = mid;
+	side->flags |= SFL_SPLIT;
+	(*back)->sides.Append( side );
+	(*back)->windingsValid = true;
+	(*back)->BoundBrush( this );
+
+	return PLANESIDE_CROSS;
+}
+
+/*
+============
+idBrush::AddBevelsForAxialBox
+============
+*/
+#define BRUSH_BEVEL_EPSILON		0.1f
+
+void idBrush::AddBevelsForAxialBox( void ) {
+	int axis, dir, i, j, k, l, order;
+	idBrushSide *side, *newSide;
+	idPlane plane;
+	idVec3 normal, vec;
+	idWinding *w, *w2;
+	float d, minBack;
+
+	assert( windingsValid );
+
+	// 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 already present
+			for ( i = 0; i < sides.Num(); i++ ) {
+				if ( dir > 0 ) {
+					if ( sides[i]->plane.Normal()[axis] >= 0.9999f ) {
+						break;
+					}
+				}
+				else {
+					if ( sides[i]->plane.Normal()[axis] <= -0.9999f ) {
+						break;
+					}
+				}
+			}
+
+			if ( i >= sides.Num() ) {
+				normal = vec3_origin;
+				normal[axis] = dir;
+				plane.SetNormal( normal );
+				plane.SetDist( dir * bounds[(dir == 1)][axis] );
+				newSide = new idBrushSide( plane, -1 );
+				newSide->SetFlag( SFL_BEVEL );
+				sides.Append( newSide );
+			}
+		}
+	}
+
+	// if the brush is pure axial we're done
+	if ( sides.Num() == 6 ) {
+		return;
+	}
+
+	// test the non-axial plane edges
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+		w = side->winding;
+		if ( !w ) {
+			continue;
+		}
+
+		for ( j = 0; j < w->GetNumPoints(); j++) {
+			k = (j+1) % w->GetNumPoints();
+			vec = (*w)[j].ToVec3() - (*w)[k].ToVec3();
+			if ( vec.Normalize() < 0.5f ) {
+				continue;
+			}
+			for ( k = 0; k < 3; k++ ) {
+				if ( vec[k] == 1.0f || vec[k] == -1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f) ) {
+					break;	// axial
+				}
+			}
+			if ( k < 3 ) {
+				continue;	// only test non-axial edges
+			}
+
+			// 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
+					normal = vec3_origin;
+					normal[axis] = dir;
+					normal = vec.Cross( normal );
+					if ( normal.Normalize() < 0.5f ) {
+						continue;
+					}
+					plane.SetNormal( normal );
+					plane.FitThroughPoint( (*w)[j].ToVec3() );
+
+					// if all the points on all the sides are
+					// behind this plane, it is a proper edge bevel
+					for ( k = 0; k < sides.Num(); k++ ) {
+
+						// if this plane has already been used, skip it
+						if ( plane.Compare( sides[k]->plane, 0.001f, 0.1f ) ) {
+							break;
+						}
+
+						w2 = sides[k]->winding;
+						if ( !w2 ) {
+							continue;
+						}
+						minBack = 0.0f;
+						for ( l = 0; l < w2->GetNumPoints(); l++ ) {
+							d = plane.Distance( (*w2)[l].ToVec3() );
+							if ( d > BRUSH_BEVEL_EPSILON ) {
+								break;	// point at the front
+							}
+							if ( d < minBack ) {
+								minBack = d;
+							}
+						}
+						// if some point was at the front
+						if ( l < w2->GetNumPoints() ) {
+							break;
+						}
+						// if no points at the back then the winding is on the bevel plane
+						if ( minBack > -BRUSH_BEVEL_EPSILON ) {
+							break;
+						}
+					}
+
+					if ( k < sides.Num() ) {
+						continue;	// wasn't part of the outer hull
+					}
+
+					// add this plane
+					newSide = new idBrushSide( plane, -1 );
+					newSide->SetFlag( SFL_BEVEL );
+					sides.Append( newSide );
+				}
+			}
+		}
+	}
+}
+
+/*
+============
+idBrush::ExpandForAxialBox
+============
+*/
+void idBrush::ExpandForAxialBox( const idBounds &bounds ) {
+	int i, j;
+	idBrushSide *side;
+	idVec3 v;
+
+	AddBevelsForAxialBox();
+
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+
+		for ( j = 0; j < 3; j++ ) {
+			if ( side->plane.Normal()[j] > 0.0f ) {
+				v[j] = bounds[0][j];
+			}
+			else {
+				v[j] = bounds[1][j];
+			}
+		}
+
+		side->plane.SetDist( side->plane.Dist() + v * -side->plane.Normal() );
+	}
+
+	if ( !CreateWindings() ) {
+		common->Error( "idBrush::ExpandForAxialBox: brush %d on entity %d imploded", primitiveNum, entityNum );
+	}
+
+	/*
+	// after expansion at least all non bevel sides should have a winding
+	for ( i = 0; i < sides.Num(); i++ ) {
+		side = sides[i];
+		if ( !side->winding ) {
+			if ( !( side->flags & SFL_BEVEL ) ) {
+				int shit = 1;
+			}
+		}
+	}
+	*/
+}
+
+/*
+============
+idBrush::Copy
+============
+*/
+idBrush *idBrush::Copy( void ) const {
+	int i;
+	idBrush *b;
+
+	b = new idBrush();
+	b->entityNum = entityNum;
+	b->primitiveNum = primitiveNum;
+	b->contents = contents;
+	b->windingsValid = windingsValid;
+	b->bounds = bounds;
+	for ( i = 0; i < sides.Num(); i++ ) {
+		b->sides.Append( sides[i]->Copy() );
+	}
+	return b;
+}
+
+
+//===============================================================
+//
+//	idBrushList
+//
+//===============================================================
+
+/*
+============
+idBrushList::idBrushList
+============
+*/
+idBrushList::idBrushList( void ) {
+	numBrushes = numBrushSides = 0;
+	head = tail = NULL;
+}
+
+/*
+============
+idBrushList::~idBrushList
+============
+*/
+idBrushList::~idBrushList( void ) {
+}
+
+/*
+============
+idBrushList::GetBounds
+============
+*/
+idBounds idBrushList::GetBounds( void ) const {
+	idBounds bounds;
+	idBrush *b;
+
+	bounds.Clear();
+	for ( b = Head(); b; b = b->Next() ) {
+		bounds += b->GetBounds();
+	}
+	return bounds;
+}
+
+/*
+============
+idBrushList::AddToTail
+============
+*/
+void idBrushList::AddToTail( idBrush *brush ) {
+	brush->next = NULL;
+	if ( tail ) {
+		tail->next = brush;
+	}
+	tail = brush;
+	if ( !head ) {
+		head = brush;
+	}
+	numBrushes++;
+	numBrushSides += brush->sides.Num();
+}
+
+/*
+============
+idBrushList::AddToTail
+============
+*/
+void idBrushList::AddToTail( idBrushList &list ) {
+	idBrush *brush, *next;
+
+	for ( brush = list.head; brush; brush = next ) {
+		next = brush->next;
+		brush->next = NULL;
+		if ( tail ) {
+			tail->next = brush;
+		}
+		tail = brush;
+		if ( !head ) {
+			head = brush;
+		}
+		numBrushes++;
+		numBrushSides += brush->sides.Num();
+	}
+	list.head = list.tail = NULL;
+	list.numBrushes = 0;
+}
+
+/*
+============
+idBrushList::AddToFront
+============
+*/
+void idBrushList::AddToFront( idBrush *brush ) {
+	brush->next = head;
+	head = brush;
+	if ( !tail ) {
+		tail = brush;
+	}
+	numBrushes++;
+	numBrushSides += brush->sides.Num();
+}
+
+/*
+============
+idBrushList::AddToFront
+============
+*/
+void idBrushList::AddToFront( idBrushList &list ) {
+	idBrush *brush, *next;
+
+	for ( brush = list.head; brush; brush = next ) {
+		next = brush->next;
+		brush->next = head;
+		head = brush;
+		if ( !tail ) {
+			tail = brush;
+		}
+		numBrushes++;
+		numBrushSides += brush->sides.Num();
+	}
+	list.head = list.tail = NULL;
+	list.numBrushes = 0;
+}
+
+/*
+============
+idBrushList::Remove
+============
+*/
+void idBrushList::Remove( idBrush *brush ) {
+	idBrush	*b, *last;
+
+	last = NULL;
+	for ( b = head; b; b = b->next ) {
+		if ( b == brush ) {
+			if ( last ) {
+				last->next = b->next;
+			}
+			else {
+				head = b->next;
+			}
+			if ( b == tail ) {
+				tail = last;
+			}
+			numBrushes--;
+			numBrushSides -= brush->sides.Num();
+			return;
+		}
+		last = b;
+	}
+}
+
+/*
+============
+idBrushList::Delete
+============
+*/
+void idBrushList::Delete( idBrush *brush ) {
+	idBrush	*b, *last;
+
+	last = NULL;
+	for ( b = head; b; b = b->next ) {
+		if ( b == brush ) {
+			if ( last ) {
+				last->next = b->next;
+			}
+			else {
+				head = b->next;
+			}
+			if ( b == tail ) {
+				tail = last;
+			}
+			numBrushes--;
+			numBrushSides -= b->sides.Num();
+			delete b;
+			return;
+		}
+		last = b;
+	}
+}
+
+/*
+============
+idBrushList::Copy
+============
+*/
+idBrushList *idBrushList::Copy( void ) const {
+	idBrush *brush;
+	idBrushList *list;
+
+	list = new idBrushList;
+
+	for ( brush = head; brush; brush = brush->next ) {
+		list->AddToTail( brush->Copy() );
+	}
+	return list;
+}
+
+/*
+============
+idBrushList::Free
+============
+*/
+void idBrushList::Free( void ) {
+	idBrush *brush, *next;
+
+	for ( brush = head; brush; brush = next ) {
+		next = brush->next;
+		delete brush;
+	}
+	head = tail = NULL;
+	numBrushes = numBrushSides = 0;
+}
+
+/*
+============
+idBrushList::Split
+============
+*/
+void idBrushList::Split( const idPlane &plane, int planeNum, idBrushList &frontList, idBrushList &backList, bool useBrushSavedPlaneSide ) {
+	idBrush *b, *front, *back;
+
+	frontList.Clear();
+	backList.Clear();
+
+	if ( !useBrushSavedPlaneSide ) {
+		for ( b = head; b; b = b->next ) {
+			b->Split( plane, planeNum, &front, &back );
+			if ( front ) {
+				frontList.AddToTail( front );
+			}
+			if ( back ) {
+				backList.AddToTail( back );
+			}
+		}
+		return;
+	}
+
+	for ( b = head; b; b = b->next ) {
+		if ( b->savedPlaneSide & BRUSH_PLANESIDE_BOTH ) {
+			b->Split( plane, planeNum, &front, &back );
+			if ( front ) {
+				frontList.AddToTail( front );
+			}
+			if ( back ) {
+				backList.AddToTail( back );
+			}
+		}
+		else if ( b->savedPlaneSide & BRUSH_PLANESIDE_FRONT ) {
+			frontList.AddToTail( b->Copy() );
+		}
+		else {
+			backList.AddToTail( b->Copy() );
+		}
+	}
+}
+
+/*
+============
+idBrushList::Chop
+============
+*/
+void idBrushList::Chop( bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ) ) {
+	idBrush	*b1, *b2, *next;
+	idBrushList sub1, sub2, keep;
+	int i, j, c1, c2;
+	idPlaneSet planeList;
+
+#ifdef OUTPUT_CHOP_STATS
+	common->Printf( "[Brush CSG]\n");
+	common->Printf( "%6d original brushes\n", this->Num() );
+#endif
+
+	CreatePlaneList( planeList );
+
+	for ( b1 = this->Head(); b1; b1 = this->Head() ) {
+
+		for ( b2 = b1->next; b2; b2 = next ) {
+
+			next = b2->next;
+
+			for ( i = 0; i < 3; i++ ) {
+				if ( b1->bounds[0][i] >= b2->bounds[1][i] ) {
+					break;
+				}
+				if ( b1->bounds[1][i] <= b2->bounds[0][i] ) {
+					break;
+				}
+			}
+			if ( i < 3 ) {
+				continue;
+			}
+
+			for ( i = 0; i < b1->GetNumSides(); i++ ) {
+				for ( j = 0; j < b2->GetNumSides(); j++ ) {
+					if ( b1->GetSide(i)->GetPlaneNum() == ( b2->GetSide(j)->GetPlaneNum() ^ 1 ) ) {
+						// opposite planes, so not touching
+						break;
+					}
+				}
+				if ( j < b2->GetNumSides() ) {
+					break;
+				}
+			}
+			if ( i < b1->GetNumSides() ) {
+				continue;
+			}
+
+			sub1.Clear();
+			sub2.Clear();
+
+			c1 = 999999;
+			c2 = 999999;
+
+			// if b2 may chop up b1
+			if ( !ChopAllowed || ChopAllowed( b2,  b1 ) ) {
+				if ( !b1->Subtract( b2, sub1 ) ) {
+					// didn't really intersect
+					continue;
+				}
+				if ( sub1.IsEmpty() ) {
+					// b1 is swallowed by b2
+					this->Delete( b1 );
+					break;
+				}
+				c1 = sub1.Num();
+			}
+
+			// if b1 may chop up b2
+			if ( !ChopAllowed || ChopAllowed( b1,  b2 ) ) {
+				if ( !b2->Subtract( b1, sub2 ) ) {
+					// didn't really intersect
+					continue;
+				}
+				if ( sub2.IsEmpty() ) {
+					// b2 is swallowed by b1
+					sub1.Free();
+					this->Delete( b2 );
+					continue;
+				}
+				c2 = sub2.Num();
+			}
+
+			if ( sub1.IsEmpty() && sub2.IsEmpty() ) {
+				continue;
+			}
+
+			// don't allow too much fragmentation
+			if ( c1 > 2 && c2 > 2 ) {
+				sub1.Free();
+				sub2.Free();
+				continue;
+			}
+
+			if ( c1 < c2 ) {
+				sub2.Free();
+				this->AddToTail( sub1 );
+				this->Delete( b1 );
+				break;
+			}
+			else {
+				sub1.Free();
+				this->AddToTail( sub2 );
+				this->Delete( b2 );
+				continue;
+			}
+		}
+
+		if ( !b2 ) {
+			// b1 is no longer intersecting anything, so keep it
+			this->Remove( b1 );
+			keep.AddToTail( b1 );
+#ifdef OUTPUT_CHOP_STATS
+			DisplayRealTimeString( "\r%6d", keep.numBrushes );
+#endif
+		}
+	}
+
+	*this = keep;
+
+#ifdef OUTPUT_CHOP_STATS
+	common->Printf( "\r%6d output brushes\n", Num() );
+#endif
+}
+
+
+/*
+============
+idBrushList::Merge
+============
+*/
+void idBrushList::Merge( bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ) {
+	idPlaneSet planeList;
+	idBrush *b1, *b2, *nextb2;
+	int numMerges;
+
+	common->Printf( "[Brush Merge]\n");
+	common->Printf( "%6d original brushes\n", Num() );
+
+	CreatePlaneList( planeList );
+
+	numMerges = 0;
+	for ( b1 = Head(); b1; b1 = b1->next ) {
+
+		for ( b2 = Head(); b2; b2 = nextb2 ) {
+			nextb2 = b2->Next();
+
+			if ( b2 == b1 ) {
+				continue;
+			}
+
+			if ( MergeAllowed && !MergeAllowed( b1, b2 ) ) {
+				continue;
+			}
+
+			if ( b1->TryMerge( b2, planeList ) ) {
+				Delete( b2 );
+				DisplayRealTimeString( "\r%6d", ++numMerges );
+				nextb2 = Head();
+			}
+		}
+	}
+
+	common->Printf( "\r%6d brushes merged\n", numMerges );
+}
+
+/*
+============
+idBrushList::SetFlagOnFacingBrushSides
+============
+*/
+void idBrushList::SetFlagOnFacingBrushSides( const idPlane &plane, int flag ) {
+	int i;
+	idBrush *b;
+	const idWinding *w;
+
+	for ( b = head; b; b = b->next ) {
+		if ( idMath::Fabs( b->GetBounds().PlaneDistance( plane ) ) > 0.1f ) {
+			continue;
+		}
+		for ( i = 0; i < b->GetNumSides(); i++ ) {
+			w = b->GetSide(i)->GetWinding();
+			if ( !w ) {
+				if ( b->GetSide(i)->GetPlane().Compare( plane, BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) ) {
+					b->GetSide(i)->SetFlag( flag );
+				}
+				continue;
+			}
+			if ( w->PlaneSide( plane ) == SIDE_ON ) {
+				b->GetSide(i)->SetFlag( flag );
+			}
+		}
+	}
+}
+
+/*
+============
+idBrushList::CreatePlaneList
+============
+*/
+void idBrushList::CreatePlaneList( idPlaneSet &planeList ) const {
+	int i;
+	idBrush *b;
+	idBrushSide *side;
+
+	planeList.Resize( 512, 128 );
+	for ( b = Head(); b; b = b->Next() ) {
+		for ( i = 0; i < b->GetNumSides(); i++ ) {
+			side = b->GetSide( i );
+			side->SetPlaneNum( planeList.FindPlane( side->GetPlane(), BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) );
+		}
+	}
+}
+
+/*
+============
+idBrushList::CreatePlaneList
+============
+*/
+void idBrushList::WriteBrushMap( const idStr &fileName, const idStr &ext ) const {
+	idBrushMap *map;
+
+	map = new idBrushMap( fileName, ext );
+	map->WriteBrushList( *this );
+	delete map;
+}
+
+
+//===============================================================
+//
+//	idBrushMap
+//
+//===============================================================
+
+/*
+============
+idBrushMap::idBrushMap
+============
+*/
+idBrushMap::idBrushMap( const idStr &fileName, const idStr &ext ) {
+	idStr qpath;
+
+	qpath = fileName;
+	qpath.StripFileExtension();
+	qpath += ext;
+	qpath.SetFileExtension( "map" );
+
+	common->Printf( "writing %s...\n", qpath.c_str() );
+
+	fp = fileSystem->OpenFileWrite( qpath, "fs_devpath" );
+	if ( !fp ) {
+		common->Error( "Couldn't open %s\n", qpath.c_str() );
+		return;
+	}
+
+	texture = "textures/washroom/btile01";
+
+	fp->WriteFloatString( "Version %1.2f\n", (float) CURRENT_MAP_VERSION );
+	fp->WriteFloatString( "{\n" );
+	fp->WriteFloatString( "\"classname\" \"worldspawn\"\n" );
+
+	brushCount = 0;
+}
+
+/*
+============
+idBrushMap::~idBrushMap
+============
+*/
+idBrushMap::~idBrushMap( void ) {
+	if ( !fp ) {
+		return;
+	}
+	fp->WriteFloatString( "}\n" );
+	fileSystem->CloseFile( fp );
+}
+
+/*
+============
+idBrushMap::WriteBrush
+============
+*/
+void idBrushMap::WriteBrush( const idBrush *brush ) {
+	int i;
+	idBrushSide *side;
+
+	if ( !fp ) {
+		return;
+	}
+
+	fp->WriteFloatString( "// primitive %d\n{\nbrushDef3\n{\n", brushCount++ );
+
+	for ( i = 0; i < brush->GetNumSides(); i++ ) {
+		side = brush->GetSide( i );
+		fp->WriteFloatString( " ( %f %f %f %f ) ", side->GetPlane()[0], side->GetPlane()[1], side->GetPlane()[2], -side->GetPlane().Dist() );
+		fp->WriteFloatString( "( ( 0.031250 0 0 ) ( 0 0.031250 0 ) ) %s 0 0 0\n", texture.c_str() );
+
+	}
+	fp->WriteFloatString( "}\n}\n" );
+}
+
+/*
+============
+idBrushMap::WriteBrushList
+============
+*/
+void idBrushMap::WriteBrushList( const idBrushList &brushList ) {
+	idBrush *b;
+
+	if ( !fp ) {
+		return;
+	}
+
+	for ( b = brushList.Head(); b; b = b->Next() ) {
+		WriteBrush( b );
+	}
+}
diff --git a/src/tools/compilers/aas/Brush.h b/src/tools/compilers/aas/Brush.h
new file mode 100644
index 0000000..ac1b261
--- /dev/null
+++ b/src/tools/compilers/aas/Brush.h
@@ -0,0 +1,234 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#ifndef __BRUSH_H__
+#define __BRUSH_H__
+
+#include "idlib/containers/PlaneSet.h"
+#include "idlib/geometry/Winding.h"
+
+/*
+===============================================================================
+
+	Brushes
+
+===============================================================================
+*/
+
+
+#define BRUSH_PLANESIDE_FRONT		1
+#define BRUSH_PLANESIDE_BACK		2
+#define BRUSH_PLANESIDE_BOTH		( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_BACK )
+#define BRUSH_PLANESIDE_FACING		4
+
+class idBrush;
+class idBrushList;
+
+void DisplayRealTimeString( const char *string, ... ) id_attribute((format(printf,1,2)));
+
+
+//===============================================================
+//
+//	idBrushSide
+//
+//===============================================================
+
+#define SFL_SPLIT					0x0001
+#define SFL_BEVEL					0x0002
+#define SFL_USED_SPLITTER			0x0004
+#define SFL_TESTED_SPLITTER			0x0008
+
+class idBrushSide {
+
+	friend class idBrush;
+
+public:
+							idBrushSide( void );
+							idBrushSide( const idPlane &plane, int planeNum );
+							~idBrushSide( void );
+
+	int						GetFlags( void ) const { return flags; }
+	void					SetFlag( int flag ) { flags |= flag; }
+	void					RemoveFlag( int flag ) { flags &= ~flag; }
+	const idPlane &			GetPlane( void ) const { return plane; }
+	void					SetPlaneNum( int num ) { planeNum = num; }
+	int						GetPlaneNum( void ) { return planeNum; }
+	const idWinding *		GetWinding( void ) const { return winding; }
+	idBrushSide *			Copy( void ) const;
+	int						Split( const idPlane &splitPlane, idBrushSide **front, idBrushSide **back ) const;
+
+private:
+	int						flags;
+	int						planeNum;
+	idPlane					plane;
+	idWinding *				winding;
+};
+
+
+//===============================================================
+//
+//	idBrush
+//
+//===============================================================
+
+#define BFL_NO_VALID_SPLITTERS		0x0001
+
+class idBrush {
+
+	friend class idBrushList;
+
+public:
+							idBrush( void );
+							~idBrush( void );
+
+	int						GetFlags( void ) const { return flags; }
+	void					SetFlag( int flag ) { flags |= flag; }
+	void					RemoveFlag( int flag ) { flags &= ~flag; }
+	void					SetEntityNum( int num ) { entityNum = num; }
+	void					SetPrimitiveNum( int num ) { primitiveNum = num; }
+	void					SetContents( int contents ) { this->contents = contents; }
+	int						GetContents( void ) const { return contents; }
+	const idBounds &		GetBounds( void ) const { return bounds; }
+	float					GetVolume( void ) const;
+	int						GetNumSides( void ) const { return sides.Num(); }
+	idBrushSide *			GetSide( int i ) const { return sides[i]; }
+	void					SetPlaneSide( int s ) { planeSide = s; }
+	void					SavePlaneSide( void ) { savedPlaneSide = planeSide; }
+	int						GetSavedPlaneSide( void ) const { return savedPlaneSide; }
+	bool					FromSides( idList<idBrushSide *> &sideList );
+	bool					FromWinding( const idWinding &w, const idPlane &windingPlane );
+	bool					FromBounds( const idBounds &bounds );
+	void					Transform( const idVec3 &origin, const idMat3 &axis );
+	idBrush *				Copy( void ) const;
+	bool					TryMerge( const idBrush *brush, const idPlaneSet &planeList );
+							// returns true if the brushes did intersect
+	bool					Subtract( const idBrush *b, idBrushList &list ) const;
+							// split the brush into a front and back brush
+	int						Split( const idPlane &plane, int planeNum, idBrush **front, idBrush **back ) const;
+							// expand the brush for an axial bounding box
+	void					ExpandForAxialBox( const idBounds &bounds );
+							// next brush in list
+	idBrush *				Next( void ) const { return next; }
+
+private:
+	mutable idBrush *		next;				// next brush in list
+	int						entityNum;			// entity number in editor
+	int						primitiveNum;		// primitive number in editor
+	int						flags;				// brush flags
+	bool					windingsValid;		// set when side windings are valid
+	int						contents;			// contents of brush
+	int						planeSide;			// side of a plane this brush is on
+	int						savedPlaneSide;		// saved plane side
+	idBounds				bounds;				// brush bounds
+	idList<idBrushSide *>	sides;				// list with sides
+
+private:
+	bool					CreateWindings( void );
+	void					BoundBrush( const idBrush *original = NULL );
+	void					AddBevelsForAxialBox( void );
+	bool					RemoveSidesWithoutWinding( void );
+};
+
+
+//===============================================================
+//
+//	idBrushList
+//
+//===============================================================
+
+class idBrushList {
+public:
+							idBrushList( void );
+							~idBrushList( void );
+
+	int						Num( void ) const { return numBrushes; }
+	int						NumSides( void ) const { return numBrushSides; }
+	idBrush *				Head( void ) const { return head; }
+	idBrush *				Tail( void ) const { return tail; }
+	void					Clear( void ) { head = tail = NULL; numBrushes = 0; }
+	bool					IsEmpty( void ) const { return (numBrushes == 0); }
+	idBounds				GetBounds( void ) const;
+							// add brush to the tail of the list
+	void					AddToTail( idBrush *brush );
+							// add list to the tail of the list
+	void					AddToTail( idBrushList &list );
+							// add brush to the front of the list
+	void					AddToFront( idBrush *brush );
+							// add list to the front of the list
+	void					AddToFront( idBrushList &list );
+							// remove the brush from the list
+	void					Remove( idBrush *brush );
+							// remove the brush from the list and delete the brush
+	void					Delete( idBrush *brush);
+							// returns a copy of the brush list
+	idBrushList *			Copy( void ) const;
+							// delete all brushes in the list
+	void					Free( void );
+							// split the brushes in the list into two lists
+	void					Split( const idPlane &plane, int planeNum, idBrushList &frontList, idBrushList &backList, bool useBrushSavedPlaneSide = false );
+							// chop away all brush overlap
+	void					Chop( bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ) );
+							// merge brushes
+	void					Merge( bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) );
+							// set the given flag on all brush sides facing the plane
+	void					SetFlagOnFacingBrushSides( const idPlane &plane, int flag );
+							// get a list with planes for all brushes in the list
+	void					CreatePlaneList( idPlaneSet &planeList ) const;
+							// write a brush map with the brushes in the list
+	void					WriteBrushMap( const idStr &fileName, const idStr &ext ) const;
+
+private:
+	idBrush *				head;
+	idBrush *				tail;
+	int						numBrushes;
+	int						numBrushSides;
+};
+
+
+//===============================================================
+//
+//	idBrushMap
+//
+//===============================================================
+
+class idBrushMap {
+
+public:
+							idBrushMap( const idStr &fileName, const idStr &ext );
+							~idBrushMap( void );
+	void					SetTexture( const idStr &textureName ) { texture = textureName; }
+	void					WriteBrush( const idBrush *brush );
+	void					WriteBrushList( const idBrushList &brushList );
+
+private:
+	idFile *				fp;
+	idStr					texture;
+	int						brushCount;
+};
+
+#endif /* !__BRUSH_H__ */
diff --git a/src/tools/compilers/aas/BrushBSP.cpp b/src/tools/compilers/aas/BrushBSP.cpp
new file mode 100644
index 0000000..03644c2
--- /dev/null
+++ b/src/tools/compilers/aas/BrushBSP.cpp
@@ -0,0 +1,2147 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "framework/FileSystem.h"
+
+#include "tools/compilers/aas/BrushBSP.h"
+
+#define BSP_GRID_SIZE					512.0f
+#define SPLITTER_EPSILON				0.1f
+#define VERTEX_MELT_EPSILON				0.1f
+#define VERTEX_MELT_HASH_SIZE			32
+
+#define PORTAL_PLANE_NORMAL_EPSILON		0.00001f
+#define PORTAL_PLANE_DIST_EPSILON		0.01f
+
+//#define OUPUT_BSP_STATS_PER_GRID_CELL
+
+//===============================================================
+//
+//	idBrushBSPPortal
+//
+//===============================================================
+
+/*
+============
+idBrushBSPPortal::idBrushBSPPortal
+============
+*/
+idBrushBSPPortal::idBrushBSPPortal( void ) {
+	planeNum = -1;
+	winding = NULL;
+	nodes[0] = nodes[1] = NULL;
+	next[0] = next[1] = NULL;
+	faceNum = 0;
+	flags = 0;
+}
+
+/*
+============
+idBrushBSPPortal::~idBrushBSPPortal
+============
+*/
+idBrushBSPPortal::~idBrushBSPPortal( void ) {
+	if ( winding ) {
+		delete winding;
+	}
+}
+
+/*
+============
+idBrushBSPPortal::AddToNodes
+============
+*/
+void idBrushBSPPortal::AddToNodes( idBrushBSPNode *front, idBrushBSPNode *back ) {
+	if ( nodes[0] || nodes[1] ) {
+		common->Error( "AddToNode: already included" );
+	}
+
+	assert( front && back );
+
+	nodes[0] = front;
+	next[0] = front->portals;
+	front->portals = this;
+
+	nodes[1] = back;
+	next[1] = back->portals;
+	back->portals = this;
+}
+
+/*
+============
+idBrushBSPPortal::RemoveFromNode
+============
+*/
+void idBrushBSPPortal::RemoveFromNode( idBrushBSPNode *l ) {
+	idBrushBSPPortal **pp, *t;
+
+	// remove reference to the current portal
+	pp = &l->portals;
+	while (1)
+	{
+		t = *pp;
+		if ( !t ) {
+			common->Error( "idBrushBSPPortal::RemoveFromNode: portal not in node" );
+		}
+
+		if ( t == this ) {
+			break;
+		}
+
+		if ( t->nodes[0] == l ) {
+			pp = &t->next[0];
+		}
+		else if ( t->nodes[1] == l ) {
+			pp = &t->next[1];
+		}
+		else {
+			common->Error( "idBrushBSPPortal::RemoveFromNode: portal not bounding node" );
+		}
+	}
+
+	if ( nodes[0] == l ) {
+		*pp = next[0];
+		nodes[0] = NULL;
+	}
+	else if ( nodes[1] == l ) {
+		*pp = next[1];
+		nodes[1] = NULL;
+	}
+	else {
+		common->Error( "idBrushBSPPortal::RemoveFromNode: mislinked portal" );
+	}
+}
+
+/*
+============
+idBrushBSPPortal::Flip
+============
+*/
+void idBrushBSPPortal::Flip( void ) {
+	idBrushBSPNode *frontNode, *backNode;
+
+	frontNode = nodes[0];
+	backNode = nodes[1];
+
+	if ( frontNode ) {
+		RemoveFromNode( frontNode );
+	}
+	if ( backNode ) {
+		RemoveFromNode( backNode );
+	}
+	AddToNodes( frontNode, backNode );
+
+	plane = -plane;
+	planeNum ^= 1;
+	winding->ReverseSelf();
+}
+
+/*
+============
+idBrushBSPPortal::Split
+============
+*/
+int idBrushBSPPortal::Split( const idPlane &splitPlane, idBrushBSPPortal **front, idBrushBSPPortal **back ) {
+	idWinding *frontWinding, *backWinding;
+
+	(*front) = (*back) = NULL;
+	winding->Split( splitPlane, 0.1f, &frontWinding, &backWinding );
+	if ( frontWinding ) {
+		(*front) = new idBrushBSPPortal();
+		(*front)->plane = plane;
+		(*front)->planeNum = planeNum;
+		(*front)->flags = flags;
+		(*front)->winding = frontWinding;
+	}
+	if ( backWinding ) {
+		(*back) = new idBrushBSPPortal();
+		(*back)->plane = plane;
+		(*back)->planeNum = planeNum;
+		(*back)->flags = flags;
+		(*back)->winding = backWinding;
+	}
+
+	if ( frontWinding && backWinding ) {
+		return PLANESIDE_CROSS;
+	}
+	else if ( frontWinding ) {
+		return PLANESIDE_FRONT;
+	}
+	else {
+		return PLANESIDE_BACK;
+	}
+}
+
+
+//===============================================================
+//
+//	idBrushBSPNode
+//
+//===============================================================
+
+/*
+============
+idBrushBSPNode::idBrushBSPNode
+============
+*/
+idBrushBSPNode::idBrushBSPNode( void ) {
+	brushList.Clear();
+	contents = 0;
+	flags = 0;
+	volume = NULL;
+	portals = NULL;
+	children[0] = children[1] = NULL;
+	areaNum = 0;
+	occupied = 0;
+}
+
+/*
+============
+idBrushBSPNode::~idBrushBSPNode
+============
+*/
+idBrushBSPNode::~idBrushBSPNode( void ) {
+	idBrushBSPPortal *p;
+
+	// delete brushes
+	brushList.Free();
+
+	// delete volume brush
+	if ( volume ) {
+		delete volume;
+	}
+
+	// delete portals
+	for ( p = portals; p; p = portals ) {
+		p->RemoveFromNode( this );
+		if ( !p->nodes[0] && !p->nodes[1] ) {
+			delete p;
+		}
+	}
+}
+
+/*
+============
+idBrushBSPNode::SetContentsFromBrushes
+============
+*/
+void idBrushBSPNode::SetContentsFromBrushes( void ) {
+	idBrush *brush;
+
+	contents = 0;
+	for ( brush = brushList.Head(); brush; brush = brush->Next() ) {
+		contents |= brush->GetContents();
+	}
+}
+
+/*
+============
+idBrushBSPNode::GetPortalBounds
+============
+*/
+idBounds idBrushBSPNode::GetPortalBounds( void ) {
+	int s, i;
+	idBrushBSPPortal *p;
+	idBounds bounds;
+
+	bounds.Clear();
+	for ( p = portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == this);
+
+		for ( i = 0; i < p->winding->GetNumPoints(); i++ ) {
+			bounds.AddPoint( (*p->winding)[i].ToVec3() );
+		}
+	}
+	return bounds;
+}
+
+/*
+============
+idBrushBSPNode::TestLeafNode
+============
+*/
+bool idBrushBSPNode::TestLeafNode( void ) {
+	int s, n;
+	float d;
+	idBrushBSPPortal *p;
+	idVec3 center;
+	idPlane plane;
+
+	n = 0;
+	center = vec3_origin;
+	for ( p = portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == this);
+		center += p->winding->GetCenter();
+		n++;
+	}
+
+	center /= n;
+
+	for ( p = portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == this);
+		if ( s ) {
+			plane = -p->GetPlane();
+		}
+		else {
+			plane = p->GetPlane();
+		}
+		d = plane.Distance( center );
+		if ( d < 0.0f ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+============
+idBrushBSPNode::Split
+============
+*/
+bool idBrushBSPNode::Split( const idPlane &splitPlane, int splitPlaneNum ) {
+	int s, i;
+	idWinding *mid;
+	idBrushBSPPortal *p, *midPortal, *newPortals[2];
+	idBrushBSPNode *newNodes[2];
+
+	mid = new idWinding( splitPlane.Normal(), splitPlane.Dist() );
+
+	for ( p = portals; p && mid; p = p->next[s] ) {
+		s = (p->nodes[1] == this);
+		if ( s ) {
+			mid = mid->Clip( -p->plane, 0.1f, false );
+		}
+		else {
+			mid = mid->Clip( p->plane, 0.1f, false );
+		}
+	}
+
+	if ( !mid ) {
+		return false;
+	}
+
+	// allocate two new nodes
+	for ( i = 0; i < 2; i++ ) {
+		newNodes[i] = new idBrushBSPNode();
+		newNodes[i]->flags = flags;
+		newNodes[i]->contents = contents;
+		newNodes[i]->parent = this;
+	}
+
+	// split all portals of the node
+	for ( p = portals; p; p = portals ) {
+		s = (p->nodes[1] == this);
+		p->Split( splitPlane, &newPortals[0], &newPortals[1] );
+		for ( i = 0; i < 2; i++ ) {
+			if ( newPortals[i] ) {
+				if ( s ) {
+					newPortals[i]->AddToNodes( p->nodes[0], newNodes[i] );
+				}
+				else {
+					newPortals[i]->AddToNodes( newNodes[i], p->nodes[1] );
+				}
+			}
+		}
+		p->RemoveFromNode( p->nodes[0] );
+		p->RemoveFromNode( p->nodes[1] );
+		delete p;
+	}
+
+	// add seperating portal
+	midPortal = new idBrushBSPPortal();
+	midPortal->plane = splitPlane;
+	midPortal->planeNum = splitPlaneNum;
+	midPortal->winding = mid;
+	midPortal->AddToNodes( newNodes[0], newNodes[1] );
+
+	// set new child nodes
+	children[0] = newNodes[0];
+	children[1] = newNodes[1];
+	plane = splitPlane;
+
+	return true;
+}
+
+/*
+============
+idBrushBSPNode::PlaneSide
+============
+*/
+int idBrushBSPNode::PlaneSide( const idPlane &plane, float epsilon ) const {
+	int s, side;
+	idBrushBSPPortal *p;
+	bool front, back;
+
+	front = back = false;
+	for ( p = portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == this);
+
+		side = p->winding->PlaneSide( plane, epsilon );
+		if ( side == SIDE_CROSS || side == SIDE_ON) {
+			return side;
+		}
+		if ( side == SIDE_FRONT ) {
+			if ( back ) {
+				return SIDE_CROSS;
+			}
+			front = true;
+		}
+		if ( side == SIDE_BACK ) {
+			if ( front ) {
+				return SIDE_CROSS;
+			}
+			back = true;
+		}
+	}
+
+	if ( front ) {
+		return SIDE_FRONT;
+	}
+	return SIDE_BACK;
+}
+
+/*
+============
+idBrushBSPNode::RemoveFlagFlood
+============
+*/
+void idBrushBSPNode::RemoveFlagFlood( int flag ) {
+	int s;
+	idBrushBSPPortal *p;
+
+	RemoveFlag( flag );
+
+	for ( p = GetPortals(); p; p = p->Next(s) ) {
+		s = (p->GetNode(1) == this);
+
+		if ( !(p->GetNode( !s )->GetFlags() & flag ) ) {
+			continue;
+		}
+
+		p->GetNode( !s )->RemoveFlagFlood( flag );
+	}
+}
+
+/*
+============
+idBrushBSPNode::RemoveFlagRecurse
+============
+*/
+void idBrushBSPNode::RemoveFlagRecurse( int flag ) {
+	RemoveFlag( flag );
+	if ( children[0] ) {
+		children[0]->RemoveFlagRecurse( flag );
+	}
+	if ( children[1] ) {
+		children[1]->RemoveFlagRecurse( flag );
+	}
+}
+
+/*
+============
+idBrushBSPNode::RemoveFlagRecurseFlood
+============
+*/
+void idBrushBSPNode::RemoveFlagRecurseFlood( int flag ) {
+	RemoveFlag( flag );
+	if ( !children[0] && !children[1] ) {
+		RemoveFlagFlood( flag );
+	}
+	else {
+		if ( children[0] ) {
+			children[0]->RemoveFlagRecurseFlood( flag );
+		}
+		if ( children[1] ) {
+			children[1]->RemoveFlagRecurseFlood( flag );
+		}
+	}
+}
+
+
+//===============================================================
+//
+//	idBrushBSP
+//
+//===============================================================
+
+/*
+============
+idBrushBSP::idBrushBSP
+============
+*/
+idBrushBSP::idBrushBSP( void ) {
+	root = outside = NULL;
+	numSplits = numPrunedSplits = 0;
+	brushMapContents = 0;
+	brushMap = NULL;
+}
+
+/*
+============
+idBrushBSP::~idBrushBSP
+============
+*/
+idBrushBSP::~idBrushBSP( void ) {
+
+	RemoveMultipleLeafNodeReferences_r( root );
+	Free_r( root );
+
+	if ( outside ) {
+		delete outside;
+	}
+}
+
+/*
+============
+idBrushBSP::RemoveMultipleLeafNodeReferences_r
+============
+*/
+void idBrushBSP::RemoveMultipleLeafNodeReferences_r( idBrushBSPNode *node ) {
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->children[0] ) {
+		if ( node->children[0]->parent != node ) {
+			node->children[0] = NULL;
+		}
+		else {
+			RemoveMultipleLeafNodeReferences_r( node->children[0] );
+		}
+	}
+	if ( node->children[1] ) {
+		if ( node->children[1]->parent != node ) {
+			node->children[1] = NULL;
+		}
+		else {
+			RemoveMultipleLeafNodeReferences_r( node->children[1] );
+		}
+	}
+}
+
+/*
+============
+idBrushBSP::Free_r
+============
+*/
+void idBrushBSP::Free_r( idBrushBSPNode *node ) {
+	if ( !node ) {
+		return;
+	}
+
+	Free_r( node->children[0] );
+	Free_r( node->children[1] );
+
+	delete node;
+}
+
+/*
+============
+idBrushBSP::IsValidSplitter
+============
+*/
+ID_INLINE bool idBrushBSP::IsValidSplitter( const idBrushSide *side ) {
+	return !( side->GetFlags() & ( SFL_SPLIT | SFL_USED_SPLITTER ) );
+}
+
+/*
+============
+idBrushBSP::BrushSplitterStats
+============
+*/
+typedef struct splitterStats_s {
+	int numFront;			// number of brushes at the front of the splitter
+	int numBack;			// number of brushes at the back of the splitter
+	int numSplits;			// number of brush sides split by the splitter
+	int numFacing;			// number of brushes facing this splitter
+	int epsilonBrushes;		// number of tiny brushes this splitter would create
+} splitterStats_t;
+
+int idBrushBSP::BrushSplitterStats( const idBrush *brush, int planeNum, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &stats ) {
+	int i, j, num, s, lastNumSplits;
+	const idPlane *plane;
+	const idWinding *w;
+	float d, d_front, d_back, brush_front, brush_back;
+
+	plane = &planeList[planeNum];
+
+	// get the plane side for the brush bounds
+	s = brush->GetBounds().PlaneSide( *plane, SPLITTER_EPSILON );
+	if ( s == PLANESIDE_FRONT ) {
+		stats.numFront++;
+		return BRUSH_PLANESIDE_FRONT;
+	}
+	if ( s == PLANESIDE_BACK ) {
+		stats.numBack++;
+		return BRUSH_PLANESIDE_BACK;
+	}
+
+	// if the brush actually uses the planenum, we can tell the side for sure
+	for ( i = 0; i < brush->GetNumSides(); i++ ) {
+		num = brush->GetSide( i )->GetPlaneNum();
+
+		if ( !(( num ^ planeNum ) >> 1) ) {
+			if ( num == planeNum ) {
+				stats.numBack++;
+				stats.numFacing++;
+				return ( BRUSH_PLANESIDE_BACK | BRUSH_PLANESIDE_FACING );
+			}
+			if ( num == ( planeNum ^ 1 ) ) {
+				stats.numFront++;
+				stats.numFacing++;
+				return ( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_FACING );
+			}
+		}
+	}
+
+	lastNumSplits = stats.numSplits;
+	brush_front = brush_back = 0.0f;
+	for ( i = 0; i < brush->GetNumSides(); i++ ) {
+
+		if ( !IsValidSplitter( brush->GetSide( i ) ) ) {
+			continue;
+		}
+
+		j = brush->GetSide( i )->GetPlaneNum();
+		if ( testedPlanes[j] || testedPlanes[j^1] ) {
+			continue;
+		}
+
+		w = brush->GetSide(i)->GetWinding();
+		if ( !w ) {
+			continue;
+		}
+		d_front = d_back = 0.0f;
+		for ( j = 0; j < w->GetNumPoints(); j++ ) {
+			d = plane->Distance( (*w)[j].ToVec3() );
+			if ( d > d_front ) {
+				d_front = d;
+			}
+			else if ( d < d_back ) {
+				d_back = d;
+			}
+		}
+		if ( d_front > SPLITTER_EPSILON && d_back < -SPLITTER_EPSILON ) {
+			stats.numSplits++;
+		}
+		if ( d_front > brush_front ) {
+			brush_front = d_front;
+		}
+		else if ( d_back < brush_back ) {
+			brush_back = d_back;
+		}
+	}
+
+	// if brush sides are split and the brush only pokes one unit through the plane
+	if ( stats.numSplits > lastNumSplits && (brush_front < 1.0f || brush_back > -1.0f) ) {
+		stats.epsilonBrushes++;
+	}
+
+	return BRUSH_PLANESIDE_BOTH;
+}
+
+/*
+============
+idBrushBSP::FindSplitter
+============
+*/
+int idBrushBSP::FindSplitter( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &bestStats ) {
+	int i, planeNum, bestSplitter, value, bestValue, f, numBrushSides;
+	idBrush *brush, *b;
+	splitterStats_t stats;
+
+	memset( testedPlanes, 0, planeList.Num() * sizeof( bool ) );
+
+	bestSplitter = -1;
+	bestValue = -99999999;
+	for ( brush = node->brushList.Head(); brush; brush = brush->Next() ) {
+
+		if ( brush->GetFlags() & BFL_NO_VALID_SPLITTERS ) {
+			continue;
+		}
+
+		for ( i = 0; i < brush->GetNumSides(); i++ ) {
+
+			if ( !IsValidSplitter( brush->GetSide(i) ) ) {
+				continue;
+			}
+
+			planeNum = brush->GetSide(i)->GetPlaneNum();
+
+			if ( testedPlanes[planeNum] || testedPlanes[planeNum^1] ) {
+				continue;
+			}
+
+			testedPlanes[planeNum] = testedPlanes[planeNum^1] = true;
+
+			if ( node->volume->Split( planeList[planeNum], planeNum, NULL, NULL ) != PLANESIDE_CROSS ) {
+				continue;
+			}
+
+			memset( &stats, 0, sizeof( stats ) );
+
+			f = 15 + 5 * (brush->GetSide(i)->GetPlane().Type() < PLANETYPE_TRUEAXIAL);
+			numBrushSides = node->brushList.NumSides();
+
+			for ( b = node->brushList.Head(); b; b = b->Next() ) {
+
+				// if the brush has no valid splitters left
+				if ( b->GetFlags() & BFL_NO_VALID_SPLITTERS ) {
+					b->SetPlaneSide( BRUSH_PLANESIDE_BOTH );
+				}
+				else {
+					b->SetPlaneSide( BrushSplitterStats( b, planeNum, planeList, testedPlanes, stats ) );
+				}
+
+				numBrushSides -= b->GetNumSides();
+				// best value we can get using this plane as a splitter
+				value = f * (stats.numFacing + numBrushSides) - 10 * stats.numSplits - stats.epsilonBrushes * 1000;
+				// if the best value for this plane can't get any better than the best value we have
+				if ( value < bestValue ) {
+					break;
+				}
+			}
+
+			if ( b ) {
+				continue;
+			}
+
+			value = f * stats.numFacing - 10 * stats.numSplits - abs(stats.numFront - stats.numBack) - stats.epsilonBrushes * 1000;
+
+			if ( value > bestValue ) {
+				bestValue = value;
+				bestSplitter = planeNum;
+				bestStats = stats;
+
+				for ( b = node->brushList.Head(); b; b = b->Next() ) {
+					b->SavePlaneSide();
+				}
+			}
+		}
+	}
+
+	return bestSplitter;
+}
+
+/*
+============
+idBrushBSP::SetSplitterUsed
+============
+*/
+void idBrushBSP::SetSplitterUsed( idBrushBSPNode *node, int planeNum ) {
+	int i, numValidBrushSplitters;
+	idBrush *brush;
+
+	for ( brush = node->brushList.Head(); brush; brush = brush->Next() ) {
+		if ( !( brush->GetSavedPlaneSide() & BRUSH_PLANESIDE_FACING ) ) {
+			continue;
+		}
+		numValidBrushSplitters = 0;
+		for ( i = 0; i < brush->GetNumSides(); i++ ) {
+
+			if ( !(( brush->GetSide(i)->GetPlaneNum() ^ planeNum ) >> 1) ) {
+				brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER );
+			}
+			else if ( IsValidSplitter( brush->GetSide(i) ) ) {
+				numValidBrushSplitters++;
+			}
+		}
+		if ( numValidBrushSplitters == 0 ) {
+			brush->SetFlag( BFL_NO_VALID_SPLITTERS );
+		}
+	}
+}
+
+/*
+============
+idBrushBSP::BuildBrushBSP_r
+============
+*/
+idBrushBSPNode *idBrushBSP::BuildBrushBSP_r( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, int skipContents ) {
+	int planeNum;
+	splitterStats_t bestStats;
+
+	planeNum = FindSplitter( node, planeList, testedPlanes, bestStats );
+
+	// if no split plane found this is a leaf node
+	if ( planeNum == -1 ) {
+
+		node->SetContentsFromBrushes();
+
+		if ( brushMap && ( node->contents & brushMapContents ) ) {
+			brushMap->WriteBrush( node->volume );
+		}
+
+		// free node memory
+		node->brushList.Free();
+		delete node->volume;
+		node->volume = NULL;
+
+		node->children[0] = node->children[1] = NULL;
+		return node;
+	}
+
+	numSplits++;
+	numGridCellSplits++;
+
+	// mark all brush sides on the split plane as used
+	SetSplitterUsed( node, planeNum );
+
+	// set node split plane
+	node->plane = planeList[planeNum];
+
+	// allocate children
+	node->children[0] = new idBrushBSPNode();
+	node->children[1] = new idBrushBSPNode();
+
+	// split node volume and brush list for children
+	node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume );
+	node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList, true );
+	node->children[0]->parent = node->children[1]->parent = node;
+
+	// free node memory
+	node->brushList.Free();
+	delete node->volume;
+	node->volume = NULL;
+
+	// process children
+	node->children[0] = BuildBrushBSP_r( node->children[0], planeList, testedPlanes, skipContents );
+	node->children[1] = BuildBrushBSP_r( node->children[1], planeList, testedPlanes, skipContents );
+
+	// if both children contain the skip contents
+	if ( node->children[0]->contents & node->children[1]->contents & skipContents ) {
+		node->contents = node->children[0]->contents | node->children[1]->contents;
+		delete node->children[0];
+		delete node->children[1];
+		node->children[0] = node->children[1] = NULL;
+		numSplits--;
+		numGridCellSplits--;
+	}
+
+	return node;
+}
+
+/*
+============
+idBrushBSP::ProcessGridCell
+============
+*/
+idBrushBSPNode *idBrushBSP::ProcessGridCell( idBrushBSPNode *node, int skipContents ) {
+	idPlaneSet planeList;
+	bool *testedPlanes;
+
+#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
+	common->Printf( "[Grid Cell %d]\n", ++numGridCells );
+	common->Printf( "%6d brushes\n", node->brushList.Num() );
+#endif
+
+	numGridCellSplits = 0;
+
+	// chop away all brush overlap
+	node->brushList.Chop( BrushChopAllowed );
+
+	// merge brushes if possible
+	//node->brushList.Merge( BrushMergeAllowed );
+
+	// create a list with planes for this grid cell
+	node->brushList.CreatePlaneList( planeList );
+
+#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
+	common->Printf( "[Grid Cell BSP]\n" );
+#endif
+
+	testedPlanes = new bool[planeList.Num()];
+
+	BuildBrushBSP_r( node, planeList, testedPlanes, skipContents );
+
+	delete[] testedPlanes;
+
+#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
+	common->Printf( "\r%6d splits\n", numGridCellSplits );
+#endif
+
+	return node;
+}
+
+/*
+============
+idBrushBSP::BuildGrid_r
+============
+*/
+void idBrushBSP::BuildGrid_r( idList<idBrushBSPNode *> &gridCells, idBrushBSPNode *node ) {
+	int axis;
+	float dist;
+	idBounds bounds;
+	idVec3 normal, halfSize;
+
+	if ( !node->brushList.Num() ) {
+		delete node->volume;
+		node->volume = NULL;
+		node->children[0] = node->children[1] = NULL;
+		return;
+	}
+
+	bounds = node->volume->GetBounds();
+	halfSize = (bounds[1] - bounds[0]) * 0.5f;
+	for ( axis = 0; axis < 3; axis++ ) {
+		if ( halfSize[axis] > BSP_GRID_SIZE ) {
+			dist = BSP_GRID_SIZE * ( floor( (bounds[0][axis] + halfSize[axis]) / BSP_GRID_SIZE ) + 1 );
+		}
+		else {
+			dist = BSP_GRID_SIZE * ( floor( bounds[0][axis] / BSP_GRID_SIZE ) + 1 );
+		}
+		if ( dist > bounds[0][axis] + 1.0f && dist < bounds[1][axis] - 1.0f ) {
+			break;
+		}
+	}
+	if ( axis >= 3 ) {
+		gridCells.Append( node );
+		return;
+	}
+
+	numSplits++;
+
+	normal = vec3_origin;
+	normal[axis] = 1.0f;
+	node->plane.SetNormal( normal );
+	node->plane.SetDist( (int) dist );
+
+	// allocate children
+	node->children[0] = new idBrushBSPNode();
+	node->children[1] = new idBrushBSPNode();
+
+	// split volume and brush list for children
+	node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume );
+	node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList );
+	node->children[0]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER );
+	node->children[1]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER );
+	node->children[0]->parent = node->children[1]->parent = node;
+
+	// free node memory
+	node->brushList.Free();
+	delete node->volume;
+	node->volume = NULL;
+
+	// process children
+	BuildGrid_r( gridCells, node->children[0] );
+	BuildGrid_r( gridCells, node->children[1] );
+}
+
+/*
+============
+idBrushBSP::Build
+============
+*/
+void idBrushBSP::Build( idBrushList brushList, int skipContents,
+								bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ),
+								bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ) {
+
+	int i;
+	idList<idBrushBSPNode *> gridCells;
+
+	common->Printf( "[Brush BSP]\n" );
+	common->Printf( "%6d brushes\n", brushList.Num() );
+
+	BrushChopAllowed = ChopAllowed;
+	BrushMergeAllowed = MergeAllowed;
+
+	numGridCells = 0;
+	treeBounds = brushList.GetBounds();
+	root = new idBrushBSPNode();
+	root->brushList = brushList;
+	root->volume = new idBrush();
+	root->volume->FromBounds( treeBounds );
+	root->parent = NULL;
+
+	BuildGrid_r( gridCells, root );
+
+	common->Printf( "\r%6d grid cells\n", gridCells.Num() );
+
+#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
+	for ( i = 0; i < gridCells.Num(); i++ ) {
+		ProcessGridCell( gridCells[i], skipContents );
+	}
+#else
+	common->Printf( "\r%6d %%", 0 );
+	for ( i = 0; i < gridCells.Num(); i++ ) {
+		DisplayRealTimeString( "\r%6d", i * 100 / gridCells.Num() );
+		ProcessGridCell( gridCells[i], skipContents );
+	}
+	common->Printf( "\r%6d %%\n", 100 );
+#endif
+
+	common->Printf( "\r%6d splits\n", numSplits );
+
+	if ( brushMap ) {
+		delete brushMap;
+	}
+}
+
+/*
+============
+idBrushBSP::WriteBrushMap
+============
+*/
+void idBrushBSP::WriteBrushMap( const idStr &fileName, const idStr &ext, int contents ) {
+	brushMap = new idBrushMap( fileName, ext );
+	brushMapContents = contents;
+}
+
+/*
+============
+idBrushBSP::PruneTree_r
+============
+*/
+void idBrushBSP::PruneTree_r( idBrushBSPNode *node, int contents ) {
+	int i, s;
+	idBrushBSPNode *nodes[2];
+	idBrushBSPPortal *p, *nextp;
+
+	if ( !node->children[0] || !node->children[1] ) {
+		return;
+	}
+
+	PruneTree_r( node->children[0], contents );
+	PruneTree_r( node->children[1], contents );
+
+	if ( ( node->children[0]->contents & node->children[1]->contents & contents ) ) {
+
+		node->contents = node->children[0]->contents | node->children[1]->contents;
+		// move all child portals to parent
+		for ( i = 0; i < 2; i++ ) {
+			for ( p = node->children[i]->portals; p; p = nextp ) {
+				s = ( p->nodes[1] == node->children[i] );
+				nextp = p->next[s];
+				nodes[s] = node;
+				nodes[!s] = p->nodes[!s];
+				p->RemoveFromNode( p->nodes[0] );
+				p->RemoveFromNode( p->nodes[1] );
+				if ( nodes[!s] == node->children[!i] ) {
+					delete p;	// portal seperates both children
+				}
+				else {
+					p->AddToNodes( nodes[0], nodes[1] );
+				}
+			}
+		}
+
+		delete node->children[0];
+		delete node->children[1];
+		node->children[0] = NULL;
+		node->children[1] = NULL;
+
+		numPrunedSplits++;
+	}
+}
+
+/*
+============
+idBrushBSP::PruneTree
+============
+*/
+void idBrushBSP::PruneTree( int contents ) {
+	numPrunedSplits = 0;
+	common->Printf( "[Prune BSP]\n" );
+	PruneTree_r( root, contents );
+	common->Printf( "%6d splits pruned\n", numPrunedSplits );
+}
+
+/*
+============
+idBrushBSP::BaseWindingForNode
+============
+*/
+#define	BASE_WINDING_EPSILON		0.001f
+
+idWinding *idBrushBSP::BaseWindingForNode( idBrushBSPNode *node ) {
+	idWinding *w;
+	idBrushBSPNode *n;
+
+	w = new idWinding( node->plane.Normal(), node->plane.Dist() );
+
+	// clip by all the parents
+	for ( n = node->parent; n && w; n = n->parent ) {
+
+		if ( n->children[0] == node ) {
+			// take front
+			w = w->Clip( n->plane, BASE_WINDING_EPSILON );
+		}
+		else {
+			// take back
+			w = w->Clip( -n->plane, BASE_WINDING_EPSILON );
+		}
+		node = n;
+	}
+
+	return w;
+}
+
+/*
+============
+idBrushBSP::MakeNodePortal
+
+  create the new portal by taking the full plane winding for the cutting
+  plane and clipping it by all of parents of this node
+============
+*/
+void idBrushBSP::MakeNodePortal( idBrushBSPNode *node ) {
+	idBrushBSPPortal *newPortal, *p;
+	idWinding *w;
+	int side = 0;
+
+	w = BaseWindingForNode( node );
+
+	// clip the portal by all the other portals in the node
+	for ( p = node->portals; p && w; p = p->next[side] ) {
+		if ( p->nodes[0] == node ) {
+			side = 0;
+			w = w->Clip( p->plane, 0.1f );
+		}
+		else if ( p->nodes[1] == node ) {
+			side = 1;
+			w = w->Clip( -p->plane, 0.1f );
+		}
+		else {
+			common->Error( "MakeNodePortal: mislinked portal" );
+		}
+	}
+
+	if ( !w ) {
+		return;
+	}
+
+	if ( w->IsTiny() ) {
+		delete w;
+		return;
+	}
+
+	newPortal = new idBrushBSPPortal();
+	newPortal->plane = node->plane;
+	newPortal->winding = w;
+	newPortal->AddToNodes( node->children[0], node->children[1] );
+}
+
+/*
+============
+idBrushBSP::SplitNodePortals
+
+  Move or split the portals that bound the node so that the node's children have portals instead of node.
+============
+*/
+#define	SPLIT_WINDING_EPSILON		0.001f
+
+void idBrushBSP::SplitNodePortals( idBrushBSPNode *node ) {
+	int side;
+	idBrushBSPPortal *p, *nextPortal, *newPortal;
+	idBrushBSPNode *f, *b, *otherNode;
+	idPlane *plane;
+	idWinding *frontWinding, *backWinding;
+
+	plane = &node->plane;
+	f = node->children[0];
+	b = node->children[1];
+
+	for ( p = node->portals; p; p = nextPortal ) {
+		if (p->nodes[0] == node) {
+			side = 0;
+		}
+		else if (p->nodes[1] == node) {
+			side = 1;
+		}
+		else {
+			common->Error( "idBrushBSP::SplitNodePortals: mislinked portal" );
+			return;
+		}
+		nextPortal = p->next[side];
+
+		otherNode = p->nodes[!side];
+		p->RemoveFromNode( p->nodes[0] );
+		p->RemoveFromNode( p->nodes[1] );
+
+		// cut the portal into two portals, one on each side of the cut plane
+		p->winding->Split( *plane, SPLIT_WINDING_EPSILON, &frontWinding, &backWinding );
+
+		if ( frontWinding && frontWinding->IsTiny() ) {
+			delete frontWinding;
+			frontWinding = NULL;
+			//tinyportals++;
+		}
+
+		if ( backWinding && backWinding->IsTiny() ) {
+			delete backWinding;
+			backWinding = NULL;
+			//tinyportals++;
+		}
+
+		if ( !frontWinding && !backWinding ) {
+			// tiny windings on both sides
+			continue;
+		}
+
+		if ( !frontWinding ) {
+			delete backWinding;
+			if ( side == 0 ) {
+				p->AddToNodes( b, otherNode );
+			}
+			else {
+				p->AddToNodes( otherNode, b );
+			}
+			continue;
+		}
+		if ( !backWinding ) {
+			delete frontWinding;
+			if ( side == 0 ) {
+				p->AddToNodes( f, otherNode );
+			}
+			else {
+				p->AddToNodes( otherNode, f );
+			}
+			continue;
+		}
+
+		// the winding is split
+		newPortal = new idBrushBSPPortal();
+		*newPortal = *p;
+		newPortal->winding = backWinding;
+		delete p->winding;
+		p->winding = frontWinding;
+
+		if ( side == 0 ) {
+			p->AddToNodes( f, otherNode );
+			newPortal->AddToNodes( b, otherNode );
+		}
+		else {
+			p->AddToNodes( otherNode, f );
+			newPortal->AddToNodes( otherNode, b );
+		}
+	}
+
+	node->portals = NULL;
+}
+
+/*
+============
+idBrushBSP::MakeTreePortals_r
+============
+*/
+void idBrushBSP::MakeTreePortals_r( idBrushBSPNode *node ) {
+	int i;
+	idBounds bounds;
+
+	numPortals++;
+	DisplayRealTimeString( "\r%6d", numPortals );
+
+	bounds = node->GetPortalBounds();
+
+	if ( bounds[0][0] >= bounds[1][0] ) {
+		//common->Warning( "node without volume" );
+	}
+
+	for ( i = 0; i < 3; i++ ) {
+		if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
+			common->Warning( "node with unbounded volume" );
+			break;
+		}
+	}
+
+	if ( !node->children[0] || !node->children[1] ) {
+		return;
+	}
+
+	MakeNodePortal( node );
+	SplitNodePortals( node );
+
+	MakeTreePortals_r( node->children[0] );
+	MakeTreePortals_r( node->children[1] );
+}
+
+/*
+============
+idBrushBSP::MakeOutsidePortals
+============
+*/
+void idBrushBSP::MakeOutsidePortals( void ) {
+	int i, j, n;
+	idBounds bounds;
+	idBrushBSPPortal *p, *portals[6];
+	idVec3 normal;
+
+	// pad with some space so there will never be null volume leaves
+	bounds = treeBounds.Expand( 32 );
+
+	for ( i = 0; i < 3; i++ ) {
+		if ( bounds[0][i] > bounds[1][i] ) {
+			common->Error( "empty BSP tree" );
+		}
+	}
+
+	outside = new idBrushBSPNode();
+	outside->parent = NULL;
+	outside->children[0] = outside->children[1] = NULL;
+	outside->brushList.Clear();
+	outside->portals = NULL;
+	outside->contents = 0;
+
+	for ( i = 0; i < 3; i++ ) {
+		for ( j = 0; j < 2; j++ ) {
+
+			p = new idBrushBSPPortal();
+			normal = vec3_origin;
+			normal[i] = j ? -1 : 1;
+			p->plane.SetNormal( normal );
+			p->plane.SetDist( j ? -bounds[j][i] : bounds[j][i] );
+			p->winding = new idWinding( p->plane.Normal(), p->plane.Dist() );
+			p->AddToNodes( root, outside );
+
+			n = j * 3 + i;
+			portals[n] = p;
+		}
+	}
+
+	// clip the base windings with all the other planes
+	for ( i = 0; i < 6; i++ ) {
+		for ( j = 0; j < 6; j++ ) {
+			if (j == i) {
+				continue;
+			}
+			portals[i]->winding = portals[i]->winding->Clip( portals[j]->plane, ON_EPSILON );
+		}
+	}
+}
+
+/*
+============
+idBrushBSP::Portalize
+============
+*/
+void idBrushBSP::Portalize( void ) {
+	common->Printf( "[Portalize BSP]\n" );
+	common->Printf( "%6d nodes\n", (numSplits - numPrunedSplits) * 2 + 1 );
+	numPortals = 0;
+	MakeOutsidePortals();
+	MakeTreePortals_r( root );
+	common->Printf( "\r%6d nodes portalized\n", numPortals );
+}
+
+/*
+=============
+LeakFile
+
+Finds the shortest possible chain of portals that
+leads from the outside leaf to a specific occupied leaf.
+=============
+*/
+void idBrushBSP::LeakFile( const idStr &fileName ) {
+	int count, next, s;
+	idVec3 mid;
+	idFile *lineFile;
+	idBrushBSPNode *node, *nextNode = NULL;
+	idBrushBSPPortal *p, *nextPortal = NULL;
+	idStr qpath, name;
+
+	if ( !outside->occupied ) {
+		return;
+	}
+
+	qpath = fileName;
+	qpath.SetFileExtension( "lin" );
+
+	common->Printf( "writing %s...\n", qpath.c_str() );
+
+	lineFile = fileSystem->OpenFileWrite( qpath, "fs_devpath" );
+	if ( !lineFile ) {
+		common->Error( "Couldn't open %s\n", qpath.c_str() );
+		return;
+	}
+
+	count = 0;
+	node = outside;
+	while( node->occupied > 1 ) {
+
+		// find the best portal exit
+		next = node->occupied;
+		for (p = node->portals; p; p = p->next[!s] ) {
+			s = (p->nodes[0] == node);
+			if ( p->nodes[s]->occupied && p->nodes[s]->occupied < next ) {
+				nextPortal = p;
+				nextNode = p->nodes[s];
+				next = nextNode->occupied;
+			}
+		}
+		node = nextNode;
+		mid = nextPortal->winding->GetCenter();
+		lineFile->Printf( "%f %f %f\n", mid[0], mid[1], mid[2] );
+		count++;
+	}
+
+	// add the origin of the entity from which the leak was found
+	lineFile->Printf( "%f %f %f\n", leakOrigin[0], leakOrigin[1], leakOrigin[2] );
+
+	fileSystem->CloseFile( lineFile );
+}
+
+/*
+============
+idBrushBSP::FloodThroughPortals_r
+============
+*/
+void idBrushBSP::FloodThroughPortals_r( idBrushBSPNode *node, int contents, int depth ) {
+	idBrushBSPPortal *p;
+	int s;
+
+	if ( !node ) {
+		common->Error( "FloodThroughPortals_r: NULL node\n" );
+	}
+
+	if ( node->occupied ) {
+		common->Error( "FloodThroughPortals_r: node already occupied\n" );
+	}
+	node->occupied = depth;
+
+	for ( p = node->portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == node);
+
+		// if the node at the other side of the portal is removed
+		if ( !p->nodes[!s] ) {
+			continue;
+		}
+
+		// if the node at the other side of the portal is occupied already
+		if ( p->nodes[!s]->occupied ) {
+			continue;
+		}
+
+		// can't flood through the portal if it has the seperating contents at the other side
+		if ( p->nodes[!s]->contents & contents ) {
+			continue;
+		}
+
+		// flood recursively through the current portal
+		FloodThroughPortals_r( p->nodes[!s], contents, depth+1 );
+	}
+}
+
+/*
+============
+idBrushBSP::FloodFromOrigin
+============
+*/
+bool idBrushBSP::FloodFromOrigin( const idVec3 &origin, int contents ) {
+	idBrushBSPNode *node;
+
+	//find the leaf to start in
+	node = root;
+	while( node->children[0] && node->children[1] ) {
+
+		if ( node->plane.Side( origin ) == PLANESIDE_BACK ) {
+			node = node->children[1];
+		}
+		else {
+			node = node->children[0];
+		}
+	}
+
+	if ( !node ) {
+		return false;
+	}
+
+	// if inside the inside/outside seperating contents
+	if ( node->contents & contents ) {
+		return false;
+	}
+
+	// if the node is already occupied
+	if ( node->occupied ) {
+		return false;
+	}
+
+	FloodThroughPortals_r( node, contents, 1 );
+
+	return true;
+}
+
+/*
+============
+idBrushBSP::FloodFromEntities
+
+  Marks all nodes that can be reached by entites.
+============
+*/
+bool idBrushBSP::FloodFromEntities( const idMapFile *mapFile, int contents, const idStrList &classNames ) {
+	int i, j;
+	bool inside;
+	idVec3 origin;
+	idMapEntity *mapEnt;
+	idStr classname;
+
+	inside = false;
+	outside->occupied = 0;
+
+	// skip the first entity which is assumed to be the worldspawn
+	for ( i = 1; i < mapFile->GetNumEntities(); i++ ) {
+
+		mapEnt = mapFile->GetEntity( i );
+
+		if ( !mapEnt->epairs.GetVector( "origin", "", origin ) ) {
+			continue;
+		}
+
+		if ( !mapEnt->epairs.GetString( "classname", "", classname ) ) {
+			continue;
+		}
+
+		for ( j = 0; j < classNames.Num(); j++ ) {
+			if ( classname.Icmp( classNames[j] ) == 0 ) {
+				break;
+			}
+		}
+
+		if ( j >= classNames.Num() ) {
+			continue;
+		}
+
+		origin[2] += 1;
+
+		// nudge around a little
+		if ( FloodFromOrigin( origin, contents ) ) {
+			inside = true;
+		}
+
+		if ( outside->occupied ) {
+			leakOrigin = origin;
+			break;
+		}
+	}
+
+	if ( !inside ) {
+		common->Warning( "no entities inside" );
+	}
+	else if ( outside->occupied ) {
+		common->Warning( "reached outside from entity %d (%s)", i, classname.c_str() );
+	}
+
+	return ( inside && !outside->occupied );
+}
+
+/*
+============
+idBrushBSP::RemoveOutside_r
+============
+*/
+void idBrushBSP::RemoveOutside_r( idBrushBSPNode *node, int contents ) {
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->children[0] || node->children[1] ) {
+		RemoveOutside_r( node->children[0], contents );
+		RemoveOutside_r( node->children[1], contents );
+		return;
+	}
+
+	if ( !node->occupied ) {
+		if ( !( node->contents & contents ) ) {
+			outsideLeafNodes++;
+			node->contents |= contents;
+		}
+		else {
+			solidLeafNodes++;
+		}
+	}
+	else {
+		insideLeafNodes++;
+	}
+}
+
+/*
+============
+idBrushBSP::RemoveOutside
+============
+*/
+bool idBrushBSP::RemoveOutside( const idMapFile *mapFile, int contents, const idStrList &classNames ) {
+	common->Printf( "[Remove Outside]\n" );
+
+	solidLeafNodes = outsideLeafNodes = insideLeafNodes = 0;
+
+	if ( !FloodFromEntities( mapFile, contents, classNames ) ) {
+		return false;
+	}
+
+	RemoveOutside_r( root, contents );
+
+	common->Printf( "%6d solid leaf nodes\n", solidLeafNodes );
+	common->Printf( "%6d outside leaf nodes\n", outsideLeafNodes );
+	common->Printf( "%6d inside leaf nodes\n", insideLeafNodes );
+
+	//PruneTree( contents );
+
+	return true;
+}
+
+/*
+============
+idBrushBSP::SetPortalPlanes_r
+============
+*/
+void idBrushBSP::SetPortalPlanes_r( idBrushBSPNode *node, idPlaneSet &planeList ) {
+	int s;
+	idBrushBSPPortal *p;
+
+	if ( !node ) {
+		return;
+	}
+
+	for ( p = node->portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == node);
+		if ( p->planeNum == -1 ) {
+			p->planeNum = planeList.FindPlane( p->plane, PORTAL_PLANE_NORMAL_EPSILON, PORTAL_PLANE_DIST_EPSILON );
+		}
+	}
+	SetPortalPlanes_r( node->children[0], planeList );
+	SetPortalPlanes_r( node->children[1], planeList );
+}
+
+/*
+============
+idBrushBSP::SetPortalPlanes
+
+  give all portals a plane number
+============
+*/
+void idBrushBSP::SetPortalPlanes( void ) {
+	SetPortalPlanes_r( root, portalPlanes );
+}
+
+/*
+============
+idBrushBSP::MergeLeafNodePortals
+============
+*/
+void idBrushBSP::MergeLeafNodePortals( idBrushBSPNode *node, int skipContents ) {
+	int s1, s2;
+	bool foundPortal;
+	idBrushBSPPortal *p1, *p2, *nextp1, *nextp2;
+	idWinding *newWinding, *reverse;
+
+	// pass 1: merge all portals that seperate the same leaf nodes
+	for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
+		s1 = (p1->GetNode(1) == node);
+		nextp1 = p1->Next(s1);
+
+		for ( p2 = nextp1; p2; p2 = nextp2 ) {
+			s2 = (p2->GetNode(1) == node);
+			nextp2 = p2->Next(s2);
+
+			// if both portals seperate the same leaf nodes
+			if ( p1->nodes[!s1] == p2->nodes[!s2] ) {
+
+				// add the winding of p2 to the winding of p1
+				p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() );
+
+				// delete p2
+				p2->RemoveFromNode( p2->nodes[0] );
+				p2->RemoveFromNode( p2->nodes[1] );
+				delete p2;
+
+				numMergedPortals++;
+
+				nextp1 = node->GetPortals();
+				break;
+			}
+		}
+	}
+
+	// pass 2: merge all portals in the same plane if they all have the skip contents at the other side
+	for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
+		s1 = (p1->GetNode(1) == node);
+		nextp1 = p1->Next(s1);
+
+		if ( !(p1->nodes[!s1]->contents & skipContents) ) {
+			continue;
+		}
+
+		// test if all portals in this plane have the skip contents at the other side
+		foundPortal = false;
+		for ( p2 = node->GetPortals(); p2; p2 = nextp2 ) {
+			s2 = (p2->GetNode(1) == node);
+			nextp2 = p2->Next(s2);
+
+			if ( p2 == p1 || (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
+				continue;
+			}
+			foundPortal = true;
+			if ( !(p2->nodes[!s2]->contents & skipContents) ) {
+				break;
+			}
+		}
+
+		// if all portals in this plane have the skip contents at the other side
+		if ( !p2 && foundPortal ) {
+			for ( p2 = node->GetPortals(); p2; p2 = nextp2 ) {
+				s2 = (p2->GetNode(1) == node);
+				nextp2 = p2->Next(s2);
+
+				if ( p2 == p1 || (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
+					continue;
+				}
+
+				// add the winding of p2 to the winding of p1
+				p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() );
+
+				// delete p2
+				p2->RemoveFromNode( p2->nodes[0] );
+				p2->RemoveFromNode( p2->nodes[1] );
+				delete p2;
+
+				numMergedPortals++;
+			}
+			nextp1 = node->GetPortals();
+		}
+	}
+
+	// pass 3: try to merge portals in the same plane that have the skip contents at the other side
+	for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
+		s1 = (p1->GetNode(1) == node);
+		nextp1 = p1->Next(s1);
+
+		if ( !(p1->nodes[!s1]->contents & skipContents) ) {
+			continue;
+		}
+
+		for ( p2 = nextp1; p2; p2 = nextp2 ) {
+			s2 = (p2->GetNode(1) == node);
+			nextp2 = p2->Next(s2);
+
+			if ( !(p2->nodes[!s2]->contents & skipContents) ) {
+				continue;
+			}
+
+			if ( (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
+				continue;
+			}
+
+			// try to merge the two portal windings
+			if ( p2->planeNum == p1->planeNum ) {
+				newWinding = p1->winding->TryMerge( *p2->winding, p1->plane.Normal() );
+			}
+			else {
+				reverse = p2->winding->Reverse();
+				newWinding = p1->winding->TryMerge( *reverse, p1->plane.Normal() );
+				delete reverse;
+			}
+
+			// if successfully merged
+			if ( newWinding ) {
+
+				// replace the winding of the first portal
+				delete p1->winding;
+				p1->winding = newWinding;
+
+				// delete p2
+				p2->RemoveFromNode( p2->nodes[0] );
+				p2->RemoveFromNode( p2->nodes[1] );
+				delete p2;
+
+				numMergedPortals++;
+
+				nextp1 = node->GetPortals();
+				break;
+			}
+		}
+	}
+}
+
+/*
+============
+idBrushBSP::MergePortals_r
+============
+*/
+void idBrushBSP::MergePortals_r( idBrushBSPNode *node, int skipContents ) {
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->contents & skipContents ) {
+		return;
+	}
+
+	if ( !node->children[0] && !node->children[1] ) {
+		MergeLeafNodePortals( node, skipContents );
+		return;
+	}
+
+	MergePortals_r( node->children[0], skipContents );
+	MergePortals_r( node->children[1], skipContents );
+}
+
+/*
+============
+idBrushBSP::MergePortals
+============
+*/
+void idBrushBSP::MergePortals( int skipContents ) {
+	numMergedPortals = 0;
+	common->Printf( "[Merge Portals]\n" );
+	SetPortalPlanes();
+	MergePortals_r( root, skipContents );
+	common->Printf( "%6d portals merged\n", numMergedPortals );
+}
+
+/*
+============
+idBrushBSP::PruneMergedTree_r
+============
+*/
+void idBrushBSP::PruneMergedTree_r( idBrushBSPNode *node ) {
+	int i;
+	idBrushBSPNode *leafNode;
+
+	if ( !node ) {
+		return;
+	}
+
+	PruneMergedTree_r( node->children[0] );
+	PruneMergedTree_r( node->children[1] );
+
+	for ( i = 0; i < 2; i++ ) {
+		if ( node->children[i] ) {
+			leafNode = node->children[i]->children[0];
+			if ( leafNode && leafNode == node->children[i]->children[1] ) {
+				if ( leafNode->parent == node->children[i] ) {
+					leafNode->parent = node;
+				}
+				delete node->children[i];
+				node->children[i] = leafNode;
+			}
+		}
+	}
+}
+
+/*
+============
+idBrushBSP::UpdateTreeAfterMerge_r
+============
+*/
+void idBrushBSP::UpdateTreeAfterMerge_r( idBrushBSPNode *node, const idBounds &bounds, idBrushBSPNode *oldNode, idBrushBSPNode *newNode ) {
+
+	if ( !node ) {
+		return;
+	}
+
+	if ( !node->children[0] && !node->children[1] ) {
+		return;
+	}
+
+	if ( node->children[0] == oldNode ) {
+		node->children[0] = newNode;
+	}
+	if ( node->children[1] == oldNode ) {
+		node->children[1] = newNode;
+	}
+
+	switch( bounds.PlaneSide( node->plane, 2.0f ) ) {
+		case PLANESIDE_FRONT:
+			UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode );
+			break;
+		case PLANESIDE_BACK:
+			UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode );
+			break;
+		default:
+			UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode );
+			UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode );
+			break;
+	}
+}
+
+/*
+============
+idBrushBSP::TryMergeLeafNodes
+
+  NOTE: multiple brances of the BSP tree might point to the same leaf node after merging
+============
+*/
+bool idBrushBSP::TryMergeLeafNodes( idBrushBSPPortal *portal, int side ) {
+	int i, j, k, s1, s2, s;
+	idBrushBSPNode *nodes[2], *node1, *node2;
+	idBrushBSPPortal *p1, *p2, *p, *nextp;
+	idPlane plane;
+	idWinding *w;
+	idBounds bounds, b;
+
+	nodes[0] = node1 = portal->nodes[side];
+	nodes[1] = node2 = portal->nodes[!side];
+
+	// check if the merged node would still be convex
+	for ( i = 0; i < 2; i++ ) {
+
+		j = !i;
+
+		for ( p1 = nodes[i]->portals; p1; p1 = p1->next[s1] ) {
+			s1 = (p1->nodes[1] == nodes[i]);
+
+			if ( p1->nodes[!s1] == nodes[j] ) {
+				continue;
+			}
+
+			if ( s1 ) {
+				plane = -p1->plane;
+			}
+			else {
+				plane = p1->plane;
+			}
+
+			// all the non seperating portals of the other node should be at the front or on the plane
+			for ( p2 = nodes[j]->portals; p2; p2 = p2->next[s2] ) {
+				s2 = (p2->nodes[1] == nodes[j]);
+
+				if ( p2->nodes[!s2] == nodes[i] ) {
+					continue;
+				}
+
+				w = p2->winding;
+				for ( k = 0; k < w->GetNumPoints(); k++ ) {
+					if ( plane.Distance( (*w)[k].ToVec3() ) < -0.1f ) {
+						return false;
+					}
+				}
+			}
+		}
+	}
+
+	// remove all portals that seperate the two nodes
+	for ( p = node1->portals; p; p = nextp ) {
+		s = (p->nodes[1] == node1);
+		nextp = p->next[s];
+
+		if ( p->nodes[!s] == node2 ) {
+			p->RemoveFromNode( p->nodes[0] );
+			p->RemoveFromNode( p->nodes[1] );
+			delete p;
+		}
+	}
+
+	// move all portals of node2 to node1
+	for ( p = node2->portals; p; p = node2->portals ) {
+		s = (p->nodes[1] == node2);
+
+		nodes[s] = node1;
+		nodes[!s] = p->nodes[!s];
+		p->RemoveFromNode( p->nodes[0] );
+		p->RemoveFromNode( p->nodes[1] );
+		p->AddToNodes( nodes[0], nodes[1] );
+	}
+
+	// get bounds for the new node
+	bounds.Clear();
+	for ( p = node1->portals; p; p = p->next[s] ) {
+		s = (p->nodes[1] == node1);
+		p->GetWinding()->GetBounds( b );
+		bounds += b;
+	}
+
+	// replace every reference to node2 by a reference to node1
+	UpdateTreeAfterMerge_r( root, bounds, node2, node1 );
+
+	delete node2;
+
+	return true;
+}
+
+/*
+============
+idBrushBSP::MeltFloor_r
+
+  flood through portals touching the bounds to find all vertices that might be inside the bounds
+============
+*/
+void idBrushBSP::MeltFlood_r( idBrushBSPNode *node, int skipContents, idBounds &bounds, idVectorSet<idVec3,3> &vertexList ) {
+	int s1, i;
+	idBrushBSPPortal *p1;
+	idBounds b;
+	const idWinding *w;
+
+	node->SetFlag( NODE_VISITED );
+
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) {
+			continue;
+		}
+
+		w = p1->GetWinding();
+
+		for ( i = 0; i < w->GetNumPoints(); i++ ) {
+			if ( bounds.ContainsPoint( (*w)[i].ToVec3() ) ) {
+				vertexList.FindVector( (*w)[i].ToVec3(), VERTEX_MELT_EPSILON );
+			}
+		}
+	}
+
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) {
+			continue;
+		}
+
+		if ( p1->GetNode( !s1 )->GetContents() & skipContents ) {
+			continue;
+		}
+
+		w = p1->GetWinding();
+		w->GetBounds( b );
+
+		if ( !bounds.IntersectsBounds( b ) ) {
+			continue;
+		}
+
+		MeltFlood_r( p1->GetNode( !s1 ), skipContents, bounds, vertexList );
+	}
+}
+
+/*
+============
+idBrushBSP::MeltLeafNodePortals
+============
+*/
+void idBrushBSP::MeltLeafNodePortals( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList ) {
+	int s1, i;
+	idBrushBSPPortal *p1;
+	idBounds bounds;
+
+	if ( node->GetFlags() & NODE_DONE ) {
+		return;
+	}
+
+	node->SetFlag( NODE_DONE );
+
+	// melt things together
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+
+		if ( p1->GetNode( !s1 )->GetFlags() & NODE_DONE ) {
+			continue;
+		}
+
+		p1->winding->GetBounds( bounds );
+		bounds.ExpandSelf( 2 * VERTEX_MELT_HASH_SIZE * VERTEX_MELT_EPSILON );
+		vertexList.Init( bounds[0], bounds[1], VERTEX_MELT_HASH_SIZE, 128 );
+
+		// get all vertices to be considered
+		MeltFlood_r( node, skipContents, bounds, vertexList );
+		node->RemoveFlagFlood( NODE_VISITED );
+
+		for ( i = 0; i < vertexList.Num(); i++ ) {
+			if ( p1->winding->InsertPointIfOnEdge( vertexList[i], p1->plane, 0.1f ) ) {
+				numInsertedPoints++;
+			}
+		}
+	}
+	DisplayRealTimeString( "\r%6d", numInsertedPoints );
+}
+
+/*
+============
+idBrushBSP::MeltPortals_r
+============
+*/
+void idBrushBSP::MeltPortals_r( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList ) {
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->contents & skipContents ) {
+		return;
+	}
+
+	if ( !node->children[0] && !node->children[1] ) {
+		MeltLeafNodePortals( node, skipContents, vertexList );
+		return;
+	}
+
+	MeltPortals_r( node->children[0], skipContents, vertexList );
+	MeltPortals_r( node->children[1], skipContents, vertexList );
+}
+
+/*
+============
+idBrushBSP::RemoveLeafNodeColinearPoints
+============
+*/
+void idBrushBSP::RemoveLeafNodeColinearPoints( idBrushBSPNode *node ) {
+	int s1;
+	idBrushBSPPortal *p1;
+
+	// remove colinear points
+	for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
+		s1 = (p1->GetNode(1) == node);
+		p1->winding->RemoveColinearPoints( p1->plane.Normal(), 0.1f );
+	}
+}
+
+/*
+============
+idBrushBSP::RemoveColinearPoints_r
+============
+*/
+void idBrushBSP::RemoveColinearPoints_r( idBrushBSPNode *node, int skipContents ) {
+	if ( !node ) {
+		return;
+	}
+
+	if ( node->contents & skipContents ) {
+		return;
+	}
+
+	if ( !node->children[0] && !node->children[1] ) {
+		RemoveLeafNodeColinearPoints( node );
+		return;
+	}
+
+	RemoveColinearPoints_r( node->children[0], skipContents );
+	RemoveColinearPoints_r( node->children[1], skipContents );
+}
+
+/*
+============
+idBrushBSP::MeltPortals
+============
+*/
+void idBrushBSP::MeltPortals( int skipContents ) {
+	idVectorSet<idVec3,3> vertexList;
+
+	numInsertedPoints = 0;
+	common->Printf( "[Melt Portals]\n" );
+	RemoveColinearPoints_r( root, skipContents );
+	MeltPortals_r( root, skipContents, vertexList );
+	root->RemoveFlagRecurse( NODE_DONE );
+	common->Printf( "\r%6d points inserted\n", numInsertedPoints );
+}
diff --git a/src/tools/compilers/aas/BrushBSP.h b/src/tools/compilers/aas/BrushBSP.h
new file mode 100644
index 0000000..e45a115
--- /dev/null
+++ b/src/tools/compilers/aas/BrushBSP.h
@@ -0,0 +1,239 @@
+/*
+===========================================================================
+
+Doom 3 GPL Source Code
+Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
+
+Doom 3 Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
+
+In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#ifndef __BRUSHBSP_H__
+#define __BRUSHBSP_H__
+
+#include "idlib/containers/VectorSet.h"
+#include "idlib/containers/StrList.h"
+#include "idlib/geometry/Winding.h"
+#include "idlib/MapFile.h"
+#include "tools/compilers/aas/Brush.h"
+
+/*
+===============================================================================
+
+	BrushBSP
+
+===============================================================================
+*/
+
+class idBrushBSP;
+class idBrushBSPNode;
+class idBrushBSPPortal;
+
+
+//===============================================================
+//
+//	idBrushBSPPortal
+//
+//===============================================================
+
+class idBrushBSPPortal {
+
+	friend class idBrushBSP;
+	friend class idBrushBSPNode;
+
+public:
+							idBrushBSPPortal( void );
+							~idBrushBSPPortal( void );
+	void					AddToNodes( idBrushBSPNode *front, idBrushBSPNode *back );
+	void					RemoveFromNode( idBrushBSPNode *l );
+	void					Flip( void );
+	int						Split( const idPlane &splitPlane, idBrushBSPPortal **front, idBrushBSPPortal **back );
+	idWinding *				GetWinding( void ) const { return winding; }
+	const idPlane &			GetPlane( void ) const { return plane; }
+	void					SetFaceNum( int num ) { faceNum = num; }
+	int						GetFaceNum( void ) const { return faceNum; }
+	int						GetFlags( void ) const { return flags; }
+	void					SetFlag( int flag ) { flags |= flag; }
+	void					RemoveFlag( int flag ) { flags &= ~flag; }
+	idBrushBSPPortal *		Next( int side ) const { return next[side]; }
+	idBrushBSPNode *		GetNode( int side ) const { return nodes[side]; }
+
+private:
+	idPlane					plane;			// portal plane
+	int						planeNum;		// number of plane this portal is on
+	idWinding *				winding;		// portal winding
+	idBrushBSPNode *		nodes[2];		// nodes this portal seperates
+	idBrushBSPPortal *		next[2];		// next portal in list for both nodes
+	int						flags;			// portal flags
+	int						faceNum;		// number of the face created for this portal
+};
+
+
+//===============================================================
+//
+//	idBrushBSPNode
+//
+//===============================================================
+
+#define NODE_VISITED		BIT(30)
+#define NODE_DONE			BIT(31)
+
+class idBrushBSPNode {
+
+	friend class idBrushBSP;
+	friend class idBrushBSPPortal;
+
+public:
+							idBrushBSPNode( void );
+							~idBrushBSPNode( void );
+	void					SetContentsFromBrushes( void );
+	idBounds				GetPortalBounds( void );
+	idBrushBSPNode *		GetChild( int index ) const { return children[index]; }
+	idBrushBSPNode *		GetParent( void ) const { return parent; }
+	void					SetContents( int contents ) { this->contents = contents; }
+	int						GetContents( void ) const { return contents; }
+	const idPlane &			GetPlane( void ) const { return plane; }
+	idBrushBSPPortal *		GetPortals( void ) const { return portals; }
+	void					SetAreaNum( int num ) { areaNum = num; }
+	int						GetAreaNum( void ) const { return areaNum; }
+	int						GetFlags( void ) const { return flags; }
+	void					SetFlag( int flag ) { flags |= flag; }
+	void					RemoveFlag( int flag ) { flags &= ~flag; }
+	bool					TestLeafNode( void );
+							// remove the flag from nodes found by flooding through portals to nodes with the flag set
+	void					RemoveFlagFlood( int flag );
+							// recurse down the tree and remove the flag from all visited nodes
+	void					RemoveFlagRecurse( int flag );
+							// first recurse down the tree and flood from there
+	void					RemoveFlagRecurseFlood( int flag );
+							// returns side of the plane the node is on
+	int						PlaneSide( const idPlane &plane, float epsilon = ON_EPSILON ) const;
+							// split the leaf node with a plane
+	bool					Split( const idPlane &splitPlane, int splitPlaneNum );
+
+
+private:
+	idPlane					plane;			// split plane if this is not a leaf node
+	idBrush *				volume;			// node volume
+	int						contents;		// node contents
+	idBrushList				brushList;		// list with brushes for this node
+	idBrushBSPNode *		parent;			// parent of this node
+	idBrushBSPNode *		children[2];	// both are NULL if this is a leaf node
+	idBrushBSPPortal *		portals;		// portals of this node
+	int						flags;			// node flags
+	int						areaNum;		// number of the area created for this node
+	int						occupied;		// true when portal is occupied
+};
+
+
+//===============================================================
+//
+//	idBrushBSP
+//
+//===============================================================
+
+class idBrushBSP {
+
+public:
+							idBrushBSP( void );
+							~idBrushBSP( void );
+							// build a bsp tree from a set of brushes
+	void					Build( idBrushList brushList, int skipContents,
+											bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ),
+														bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) );
+							// remove splits in subspaces with the given contents
+	void					PruneTree( int contents );
+							// portalize the bsp tree
+	void					Portalize( void );
+							// remove subspaces outside the map not reachable by entities
+	bool					RemoveOutside( const idMapFile *mapFile, int contents, const idStrList &classNames );
+							// write file with a trace going through a leak
+	void					LeakFile( const idStr &fileName );
+							// try to merge portals
+	void					MergePortals( int skipContents );
+							// try to merge the two leaf nodes at either side of the portal
+	bool					TryMergeLeafNodes( idBrushBSPPortal *portal, int side );
+	void					PruneMergedTree_r( idBrushBSPNode *node );
+							// melt portal windings
+	void					MeltPortals( int skipContents );
+							// write a map file with a brush for every leaf node that has the given contents
+	void					WriteBrushMap( const idStr &fileName, const idStr &ext, int contents );
+							// bounds for the whole tree
+	const idBounds &		GetTreeBounds( void ) const { return treeBounds; }
+							// root node of the tree
+	idBrushBSPNode *		GetRootNode( void ) const { return root; }
+
+private:
+	idBrushBSPNode *		root;
+	idBrushBSPNode *		outside;
+	idBounds				treeBounds;
+	idPlaneSet				portalPlanes;
+	int						numGridCells;
+	int						numSplits;
+	int						numGridCellSplits;
+	int						numPrunedSplits;
+	int						numPortals;
+	int						solidLeafNodes;
+	int						outsideLeafNodes;
+	int						insideLeafNodes;
+	int						numMergedPortals;
+	int						numInsertedPoints;
+	idVec3					leakOrigin;
+	int						brushMapContents;
+	idBrushMap *			brushMap;
+
+	bool					(*BrushChopAllowed)( idBrush *b1, idBrush *b2 );
+	bool					(*BrushMergeAllowed)( idBrush *b1, idBrush *b2 );
+
+private:
+	void					RemoveMultipleLeafNodeReferences_r( idBrushBSPNode *node );
+	void					Free_r( idBrushBSPNode *node );
+	void					IncreaseNumSplits( void );
+	bool					IsValidSplitter( const idBrushSide *side );
+	int						BrushSplitterStats( const idBrush *brush, int planeNum, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &stats );
+	int						FindSplitter( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &bestStats );
+	void					SetSplitterUsed( idBrushBSPNode *node, int planeNum );
+	idBrushBSPNode *		BuildBrushBSP_r( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, int skipContents );
+	idBrushBSPNode *		ProcessGridCell( idBrushBSPNode *node, int skipContents );
+	void					BuildGrid_r( idList<idBrushBSPNode *> &gridCells, idBrushBSPNode *node );
+	void					PruneTree_r( idBrushBSPNode *node, int contents );
+	void					MakeOutsidePortals( void );
+	idWinding *				BaseWindingForNode( idBrushBSPNode *node );
+	void					MakeNodePortal( idBrushBSPNode *node );
+	void					SplitNodePortals( idBrushBSPNode *node );
+	void					MakeTreePortals_r( idBrushBSPNode *node );
+	void					FloodThroughPortals_r( idBrushBSPNode *node, int contents, int depth );
+	bool					FloodFromOrigin( const idVec3 &origin, int contents );
+	bool					FloodFromEntities( const idMapFile *mapFile, int contents, const idStrList &classNames );
+	void					RemoveOutside_r( idBrushBSPNode *node, int contents );
+	void					SetPortalPlanes_r( idBrushBSPNode *node, idPlaneSet &planeList );
+	void					SetPortalPlanes( void );
+	void					MergePortals_r( idBrushBSPNode *node, int skipContents );
+	void					MergeLeafNodePortals( idBrushBSPNode *node, int skipContents );
+	void					UpdateTreeAfterMerge_r( idBrushBSPNode *node, const idBounds &bounds, idBrushBSPNode *oldNode, idBrushBSPNode *newNode );
+	void					RemoveLeafNodeColinearPoints( idBrushBSPNode *node );
+	void					RemoveColinearPoints_r( idBrushBSPNode *node, int skipContents );
+	void					MeltFlood_r( idBrushBSPNode *node, int skipContents, idBounds &bounds, idVectorSet<idVec3,3> &vertexList );
+	void					MeltLeafNodePortals( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList );
+	void					MeltPortals_r( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList );
+};
+
+#endif /* !__BRUSHBSP_H__ */
diff --git a/src/tools/compilers/aas/README.port.md b/src/tools/compilers/aas/README.port.md
new file mode 100644
index 0000000..8e3c0b3
--- /dev/null
+++ b/src/tools/compilers/aas/README.port.md
@@ -0,0 +1,13 @@
+# AAS compiler provenance
+
+These AAS build, reachability, clustering, and brush sources were ported from
+the pinned D3Wasm tree at commit `48f8f65d1216db3ee0b11872bb3b413febadc669`,
+directory `neo/tools/compilers/aas`.
+
+D3Wasm carries the Doom 3 GPL source released by id Software. The copied
+source files retain their original Doom 3 GPL v3-or-later license notices and
+additional terms. OpenQ4 adaptations reuse the engine's existing Quake 4 AAS
+1.08 file implementation instead of the Doom 3 AAS 1.07 file implementation.
+
+Meson includes this compiler only in native engine builds. It is deliberately
+excluded from Emscripten builds.
diff --git a/src/tools/compilers/dmap/output.cpp b/src/tools/compilers/dmap/output.cpp
index 52e0e0d..47f1025 100644
--- a/src/tools/compilers/dmap/output.cpp
+++ b/src/tools/compilers/dmap/output.cpp
@@ -655,8 +655,7 @@ void WriteOutputFile( void ) {
 	sprintf( qpath, "%s." PROC_FILE_EXT, dmapGlobals.mapFileBase );
 
 	common->Printf( "writing %s\n", qpath.c_str() );
-	// _D3XP used fs_cdpath
-	procFile = fileSystem->OpenFileWrite( qpath, "fs_cdpath" );
+	procFile = fileSystem->OpenFileWrite( qpath, "fs_savepath" );
 	if ( !procFile ) {
 		common->Error( "Error opening %s", qpath.c_str() );
 	}
diff --git a/tools/build/meson_sources.py b/tools/build/meson_sources.py
index bacc193..4890c22 100644
--- a/tools/build/meson_sources.py
+++ b/tools/build/meson_sources.py
@@ -76,6 +76,10 @@ ENGINE_SOURCE_GLOBS = [
     "ui/*.cpp",
 ]
 
+NATIVE_TOOL_SOURCE_GLOBS = [
+    "tools/compilers/aas/*.cpp",
+]
+
 GAME_SOURCE_GLOBS = [
     "game/ai/*.cpp",
     "game/anim/*.cpp",
@@ -265,6 +269,10 @@ def main(argv: list[str]) -> int:
         for pattern in ENGINE_SOURCE_GLOBS:
             add_globbed_sources(source_set, ordered_sources, source_root, pattern)
 
+        if args.host_system != "emscripten":
+            for pattern in NATIVE_TOOL_SOURCE_GLOBS:
+                add_globbed_sources(source_set, ordered_sources, source_root, pattern)
+
         if include_game:
             for pattern in GAME_SOURCE_GLOBS:
                 add_globbed_sources(source_set, ordered_sources, source_root, pattern)
