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

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 "precompiled.h"
#pragma hdrstop

#include "AASBuild_local.h"

#define BFL_PATCH		0x1000

//===============================================================
//
//	idAASBuild
//
//===============================================================

/*
============
idAASBuild::idAASBuild
============
*/
idAASBuild::idAASBuild()
{
	file = NULL;
	procNodes = NULL;
	numProcNodes = 0;
	numGravitationalSubdivisions = 0;
	numMergedLeafNodes = 0;
	numLedgeSubdivisions = 0;
	ledgeMap = NULL;
}

/*
============
idAASBuild::~idAASBuild
============
*/
idAASBuild::~idAASBuild()
{
	Shutdown();
}

/*
================
idAASBuild::Shutdown
================
*/
void idAASBuild::Shutdown()
{
	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 ), TAG_TOOLS );
	
	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;
	}
	
	// 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()
{
	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();
}
