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

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
Copyright (C) 2013-2015 Robert Beckebans

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 "dmap.h"

dmapGlobals_t	dmapGlobals;

/*
============
ProcessModel
============
*/
bool ProcessModel( uEntity_t* e, bool floodFill )
{
	bspface_t*	faces;
	
	// build a bsp tree using all of the sides
	// of all of the structural brushes
	faces = MakeStructuralBspFaceList( e->primitives );
	
	// RB: dump BSP for debugging
	if( dmapGlobals.glview )
	{
		WriteGLView( faces, "facelist" );
	}
	// RB end
	
	e->tree = FaceBSP( faces );
	
	// create portals at every leaf intersection
	// to allow flood filling
	MakeTreePortals( e->tree );
	
	// RB: calculate node numbers for split plane analysis
	NumberNodes_r( e->tree->headnode, 0 );
	
	// classify the leafs as opaque or areaportal
	FilterBrushesIntoTree( e );
	
	// RB: use mapTri_t by MapPolygonMesh primitives in case we don't use brushes
	FilterMeshesIntoTree( e );
	
	// RB: dump BSP for debugging
	//if( dmapGlobals.glview )
	//{
	//WriteGLView( e->tree, "unclipped", dmapGlobals.entityNum );
	//}
	// RB end
	
	// see if the bsp is completely enclosed
	if( floodFill && !dmapGlobals.noFlood )
	{
		if( FloodEntities( e->tree ) )
		{
			// set the outside leafs to opaque
			FillOutside( e );
		}
		else
		{
			common->Printf( "**********************\n" );
			common->Warning( "******* leaked *******" );
			common->Printf( "**********************\n" );
			LeakFile( e->tree );
			// bail out here.  If someone really wants to
			// process a map that leaks, they should use
			// -noFlood
			return false;
		}
	}
	
	// get minimum convex hulls for each visible side
	// this must be done before creating area portals,
	// because the visible hull is used as the portal
	ClipSidesByTree( e );
	
	// determine areas before clipping tris into the
	// tree, so tris will never cross area boundaries
	FloodAreas( e );
	
	// RB: dump BSP for debugging
	if( dmapGlobals.glview )
	{
		WriteGLView( e->tree, "areas", dmapGlobals.entityNum );
	}
	// RB end
	
	// we now have a BSP tree with solid and non-solid leafs marked with areas
	// all primitives will now be clipped into this, throwing away
	// fragments in the solid areas
	PutPrimitivesInAreas( e );
	
	// now build shadow volumes for the lights and split
	// the optimize lists by the light beam trees
	// so there won't be unneeded overdraw in the static
	// case
	Prelight( e );
	
	// optimizing is a superset of fixing tjunctions
	if( !dmapGlobals.noOptimize )
	{
		OptimizeEntity( e );
	}
	else  if( !dmapGlobals.noTJunc )
	{
		FixEntityTjunctions( e );
	}
	
	// now fix t junctions across areas
	FixGlobalTjunctions( e );
	
	return true;
}

/*
============
ProcessModels
============
*/
bool ProcessModels()
{
	bool	oldVerbose;
	uEntity_t*	entity;
	
	oldVerbose = dmapGlobals.verbose;
	
	for( dmapGlobals.entityNum = 0 ; dmapGlobals.entityNum < dmapGlobals.num_entities ; dmapGlobals.entityNum++ )
	{
	
		entity = &dmapGlobals.uEntities[dmapGlobals.entityNum];
		if( !entity->primitives )
		{
			continue;
		}
		
		common->Printf( "############### entity %i ###############\n", dmapGlobals.entityNum );
		
		// if we leaked, stop without any more processing
		if( !ProcessModel( entity, ( bool )( dmapGlobals.entityNum == 0 ) ) )
		{
			return false;
		}
		
		// we usually don't want to see output for submodels unless
		// something strange is going on
		if( !dmapGlobals.verboseentities )
		{
			dmapGlobals.verbose = false;
		}
	}
	
	dmapGlobals.verbose = oldVerbose;
	
	return true;
}

/*
============
DmapHelp
============
*/
void DmapHelp()
{
	common->Printf(
	
		"Usage: dmap [options] mapfile\n"
		"Options:\n"
		"noCurves          = don't process curves\n"
		"noCM              = don't create collision map\n"
		"noAAS             = don't create AAS files\n"
		
	);
}

/*
============
ResetDmapGlobals
============
*/
void ResetDmapGlobals()
{
	dmapGlobals.mapFileBase[0] = '\0';
	dmapGlobals.dmapFile = NULL;
	dmapGlobals.mapPlanes.Clear();
	dmapGlobals.num_entities = 0;
	dmapGlobals.uEntities = NULL;
	dmapGlobals.entityNum = 0;
	dmapGlobals.mapLights.Clear();
	dmapGlobals.verbose = false;
	dmapGlobals.glview = false;
	dmapGlobals.noOptimize = false;
	dmapGlobals.verboseentities = false;
	dmapGlobals.noCurves = false;
	dmapGlobals.fullCarve = false;
	dmapGlobals.noModelBrushes = false;
	dmapGlobals.noTJunc = false;
	dmapGlobals.nomerge = false;
	dmapGlobals.noFlood = false;
	dmapGlobals.noClipSides = false;
	dmapGlobals.noLightCarve = false;
	dmapGlobals.noShadow = false;
	dmapGlobals.shadowOptLevel = SO_NONE;
	dmapGlobals.drawBounds.Clear();
	dmapGlobals.drawflag = false;
	dmapGlobals.totalShadowTriangles = 0;
	dmapGlobals.totalShadowVerts = 0;
}

/*
============
Dmap
============
*/
void Dmap( const idCmdArgs& args )
{
	int			i;
	int			start, end;
	char		path[1024];
	idStr		passedName;
	bool		leaked = false;
	bool		noCM = false;
	bool		noAAS = false;
	
	ResetDmapGlobals();
	
	if( args.Argc() < 2 )
	{
		DmapHelp();
		return;
	}
	
	common->Printf( "---- dmap ----\n" );
	
	dmapGlobals.fullCarve = true;
	dmapGlobals.shadowOptLevel = SO_MERGE_SURFACES;		// create shadows by merging all surfaces, but no super optimization
//	dmapGlobals.shadowOptLevel = SO_CLIP_OCCLUDERS;		// remove occluders that are completely covered
//	dmapGlobals.shadowOptLevel = SO_SIL_OPTIMIZE;
//	dmapGlobals.shadowOptLevel = SO_CULL_OCCLUDED;

	dmapGlobals.noLightCarve = true;
	
	for( i = 1 ; i < args.Argc() ; i++ )
	{
		const char* s;
		
		s = args.Argv( i );
		if( s[0] == '-' )
		{
			s++;
			if( s[0] == '\0' )
			{
				continue;
			}
		}
		
		if( !idStr::Icmp( s, "glview" ) )
		{
			dmapGlobals.glview = true;
		}
		else if( !idStr::Icmp( s, "v" ) )
		{
			common->Printf( "verbose = true\n" );
			dmapGlobals.verbose = true;
		}
		else if( !idStr::Icmp( s, "draw" ) )
		{
			common->Printf( "drawflag = true\n" );
			dmapGlobals.drawflag = true;
		}
		else if( !idStr::Icmp( s, "noFlood" ) )
		{
			common->Printf( "noFlood = true\n" );
			dmapGlobals.noFlood = true;
		}
		else if( !idStr::Icmp( s, "noLightCarve" ) )
		{
			common->Printf( "noLightCarve = true\n" );
			dmapGlobals.noLightCarve = true;
		}
		else if( !idStr::Icmp( s, "lightCarve" ) )
		{
			common->Printf( "noLightCarve = false\n" );
			dmapGlobals.noLightCarve = false;
		}
		else if( !idStr::Icmp( s, "noOpt" ) )
		{
			common->Printf( "noOptimize = true\n" );
			dmapGlobals.noOptimize = true;
		}
		else if( !idStr::Icmp( s, "verboseentities" ) )
		{
			common->Printf( "verboseentities = true\n" );
			dmapGlobals.verboseentities = true;
		}
		else if( !idStr::Icmp( s, "noCurves" ) )
		{
			common->Printf( "noCurves = true\n" );
			dmapGlobals.noCurves = true;
		}
		else if( !idStr::Icmp( s, "noModels" ) )
		{
			common->Printf( "noModels = true\n" );
			dmapGlobals.noModelBrushes = true;
		}
		else if( !idStr::Icmp( s, "noClipSides" ) )
		{
			common->Printf( "noClipSides = true\n" );
			dmapGlobals.noClipSides = true;
		}
		else if( !idStr::Icmp( s, "noCarve" ) )
		{
			common->Printf( "noCarve = true\n" );
			dmapGlobals.fullCarve = false;
		}
		else if( !idStr::Icmp( s, "shadowOpt" ) )
		{
			dmapGlobals.shadowOptLevel = ( shadowOptLevel_t )atoi( args.Argv( i + 1 ) );
			common->Printf( "shadowOpt = %i\n", dmapGlobals.shadowOptLevel );
			i += 1;
		}
		else if( !idStr::Icmp( s, "noTjunc" ) )
		{
			// triangle optimization won't work properly without tjunction fixing
			common->Printf( "noTJunc = true\n" );
			dmapGlobals.noTJunc = true;
			dmapGlobals.noOptimize = true;
			common->Printf( "forcing noOptimize = true\n" );
		}
		else if( !idStr::Icmp( s, "noCM" ) )
		{
			noCM = true;
			common->Printf( "noCM = true\n" );
		}
		else if( !idStr::Icmp( s, "noAAS" ) )
		{
			noAAS = true;
			common->Printf( "noAAS = true\n" );
		}
		else
		{
			break;
		}
	}
	
	if( i >= args.Argc() )
	{
		common->Error( "usage: dmap [options] mapfile" );
	}
	
	passedName = args.Argv( i );		// may have an extension
	passedName.BackSlashesToSlashes();
	if( passedName.Icmpn( "maps/", 4 ) != 0 )
	{
		passedName = "maps/" + passedName;
	}
	
	idStr stripped = passedName;
	stripped.StripFileExtension();
	idStr::Copynz( dmapGlobals.mapFileBase, stripped, sizeof( dmapGlobals.mapFileBase ) );
	
	bool region = false;
	// if this isn't a regioned map, delete the last saved region map
	if( passedName.Right( 4 ) != ".reg" )
	{
		idStr::snPrintf( path, sizeof( path ), "%s.reg", dmapGlobals.mapFileBase );
		fileSystem->RemoveFile( path );
	}
	else
	{
		region = true;
	}
	
	
	passedName = stripped;
	
	// delete any old line leak files
	idStr::snPrintf( path, sizeof( path ), "%s.lin", dmapGlobals.mapFileBase );
	fileSystem->RemoveFile( path );
	
	// delete any old generated binary proc files
	idStr generated = va( "generated/%s.bproc", dmapGlobals.mapFileBase );
	fileSystem->RemoveFile( generated.c_str() );
	
	//
	// start from scratch
	//
	start = Sys_Milliseconds();
	
	if( !LoadDMapFile( passedName ) )
	{
		return;
	}
	
	if( ProcessModels() )
	{
		WriteOutputFile();
		
		// RB: dump BSP after nodes being pruned and optimized
		if( dmapGlobals.glview )
		{
			uEntity_t* world = &dmapGlobals.uEntities[0];
			
			WriteGLView( world->tree, "pruned", 0, true );
		}
		// RB end
	}
	else
	{
		leaked = true;
	}
	
	FreeDMapFile();
	
	common->Printf( "%i total shadow triangles\n", dmapGlobals.totalShadowTriangles );
	common->Printf( "%i total shadow verts\n", dmapGlobals.totalShadowVerts );
	
	end = Sys_Milliseconds();
	common->Printf( "-----------------------\n" );
	common->Printf( "%5.0f seconds for dmap\n", ( end - start ) * 0.001f );
	
	if( !leaked )
	{
	
		if( !noCM )
		{
		
			// make sure the collision model manager is not used by the game
			cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
			
			// create the collision map
			start = Sys_Milliseconds();
			
			collisionModelManager->LoadMap( dmapGlobals.dmapFile );
			collisionModelManager->FreeMap();
			
			end = Sys_Milliseconds();
			common->Printf( "-------------------------------------\n" );
			common->Printf( "%5.0f seconds to create collision map\n", ( end - start ) * 0.001f );
		}
		
		if( !noAAS && !region )
		{
			// create AAS files
			RunAAS_f( args );
		}
	}
	
	// free the common .map representation
	delete dmapGlobals.dmapFile;
	
	// clear the map plane list
	dmapGlobals.mapPlanes.Clear();
}

/*
============
Dmap_f
============
*/
void Dmap_f( const idCmdArgs& args )
{

	common->ClearWarnings( "running dmap" );
	
	// refresh the screen each time we print so it doesn't look
	// like it is hung
	common->SetRefreshOnPrint( true );
	Dmap( args );
	common->SetRefreshOnPrint( false );
	
	common->PrintWarnings();
}
