//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Places "detail" objects which are client-only renderable things
//
// $Revision: $
// $NoKeywords: $
//=============================================================================

#include <windows.h>
#include "vbsp.h"
#include "bsplib.h"
#include "KeyValues.h"
#include "UtlSymbol.h"
#include "UtlVector.h"
#include <io.h>
#include "bspfile.h"
#include "utilmatlib.h"
#include "gamebspfile.h"
#include "VMatrix.h"
#include "materialpatch.h"
#include "pacifier.h"
#include "vstdlib/random.h"

// BUGBUG: This is in common, but the tools use it?
// BUGBUG: I don't like this relative include, but I don't want to add the common directory to the tools.
#include "..\..\common\builddisp.h"

#include "disp_vbsp.h"
#include "UtlBuffer.h"
#include "CollisionUtils.h"
#include <float.h>
#include "UtlLinkedList.h"


//-----------------------------------------------------------------------------
// Information about particular detail object types
//-----------------------------------------------------------------------------
enum
{
	MODELFLAG_UPRIGHT = 0x1,
};

struct DetailModel_t
{
	CUtlSymbol	m_ModelName;
	float		m_Amount;
	float		m_MinCosAngle;
	float		m_MaxCosAngle;
	int			m_Flags;
	int			m_Orientation;
	int			m_Type;
	Vector2D	m_Pos[2];
	Vector2D	m_Tex[2];
	float		m_flRandomScaleStdDev;
};

struct DetailObjectGroup_t
{
	float	m_Alpha;
	CUtlVector< DetailModel_t >	m_Models;
};

struct DetailObject_t
{
	CUtlSymbol m_Name;
	float	m_Density;
	CUtlVector< DetailObjectGroup_t >	m_Groups;

	bool operator==(const DetailObject_t& src ) const
	{
		return src.m_Name == m_Name;
	}
};

static CUtlVector<DetailObject_t>	s_DetailObjectDict;


//-----------------------------------------------------------------------------
// Error checking.. make sure the model is valid + is a static prop
//-----------------------------------------------------------------------------
struct StaticPropLookup_t
{
	CUtlSymbol	m_ModelName;
	bool		m_IsValid;
};

static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 )
{
	return src1.m_ModelName < src2.m_ModelName;
}

static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess );


//-----------------------------------------------------------------------------
// These puppies are used to construct the game lumps
//-----------------------------------------------------------------------------
static CUtlVector<DetailObjectDictLump_t>	s_DetailObjectDictLump;
static CUtlVector<DetailObjectLump_t>		s_DetailObjectLump;
static CUtlVector<DetailSpriteDictLump_t>	s_DetailSpriteDictLump;


//-----------------------------------------------------------------------------
// Parses the key-value pairs in the detail.rad file
//-----------------------------------------------------------------------------
static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues )
{
	// Sort the group by alpha
	float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f );
	
	int i = s_DetailObjectDict[detailId].m_Groups.Count();
	while ( --i >= 0 )
	{
		if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha)
			break;
	}

	// Insert after the first guy who's more transparent that we are!
	i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i);
	DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i];

	group.m_Alpha = alpha;

	// Add in all the model groups
	KeyValues* pIter = pGroupKeyValues->GetFirstSubKey();
	float totalAmount = 0.0f;
	while( pIter )
	{
		if (pIter->GetFirstSubKey())
		{
			int i = group.m_Models.AddToTail();

			DetailModel_t &model = group.m_Models[i];

			model.m_ModelName = pIter->GetString( "model", 0 );
			if (model.m_ModelName != UTL_INVAL_SYMBOL)
			{
				model.m_Type = DETAIL_PROP_TYPE_MODEL;
			}
			else
			{
				const char *pSpriteData = pIter->GetString( "sprite", 0 );
				if (pSpriteData)
				{
					model.m_Type = DETAIL_PROP_TYPE_SPRITE;

					model.m_Tex[0].Init();
					model.m_Tex[1].Init();

					float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512;
					int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize ); 
					if ( (nValid != 5) || (flTextureSize == 0) )
					{
						Error( "Invalid arguments to \"sprite\" in detail.vbsp!\n" );
					}

					model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize;
					model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize;
					model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize;
					model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize;

					model.m_Pos[0].Init( -10, 20 );
					model.m_Pos[1].Init( 10, 0 );

					pSpriteData = pIter->GetString( "spritesize", 0 );
					if (pSpriteData)
					{
						sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight );

						float ox = flWidth * x;
						float oy = flHeight * y;

						model.m_Pos[0].x = -ox;
						model.m_Pos[0].y = flHeight - oy;
						model.m_Pos[1].x = flWidth - ox;
						model.m_Pos[1].y = -oy;
					}

					model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f );
				}
			}

			model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount;
			totalAmount = model.m_Amount;

			model.m_Flags = 0;
			if (pIter->GetInt( "upright", 0 ))
			{
				model.m_Flags |= MODELFLAG_UPRIGHT;
			}

			// These are used to prevent emission on steep surfaces
			float minAngle = pIter->GetFloat( "minAngle", 180 );
			float maxAngle = pIter->GetFloat( "maxAngle", 180 );
			model.m_MinCosAngle = cos(minAngle * M_PI / 180.f);
			model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f);
			model.m_Orientation = pIter->GetInt( "detailOrientation", 0 );

			// Make sure minAngle < maxAngle
			if ( model.m_MinCosAngle < model.m_MaxCosAngle)
			{
				model.m_MinCosAngle = model.m_MaxCosAngle;
			}
		}
		pIter = pIter->GetNextKey();
	}

	// renormalize the amount if the total > 1
	if (totalAmount > 1.0f)
	{
		for (i = 0; i < group.m_Models.Count(); ++i)
		{
			group.m_Models[i].m_Amount /= totalAmount;
		}
	}
}


//-----------------------------------------------------------------------------
// Parses the key-value pairs in the detail.vbsp file
//-----------------------------------------------------------------------------
static void ParseDetailObjectFile( KeyValues& keyValues )
{
	// Iterate over all detail object groups...
	KeyValues* pIter;
	for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() )
	{
		if (!pIter->GetFirstSubKey())
			continue;

		int i = s_DetailObjectDict.AddToTail( );
		s_DetailObjectDict[i].m_Name = pIter->GetName() ;
		s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f );

		// Iterate over all detail object groups...
		KeyValues* pIterGroups = pIter->GetFirstSubKey();
		while( pIterGroups )
		{
			if (pIterGroups->GetFirstSubKey())
			{
				ParseDetailGroup( i, pIterGroups );
			}
			pIterGroups = pIterGroups->GetNextKey();
		}
	}
}


//-----------------------------------------------------------------------------
// Loads up the detail object dictionary
//-----------------------------------------------------------------------------
void LoadEmitDetailObjectDictionary( const char* pGameDir )
{
	// Set the required global lights filename and try looking in qproject
	KeyValues * values = new KeyValues("detail.vbsp");
	if (!values->LoadFromFile( g_pFileSystem, "detail.vbsp"))
		return;

	ParseDetailObjectFile( *values );

	values->deleteThis();
}


//-----------------------------------------------------------------------------
// Selects a detail group
//-----------------------------------------------------------------------------
static int SelectGroup( const DetailObject_t& detail, float alpha )
{
	// Find the two groups whose alpha we're between...
	int start, end;
	for ( start = 0; start < detail.m_Groups.Count() - 1; ++start )
	{
		if (alpha < detail.m_Groups[start+1].m_Alpha)
			break;
	}

	end = start + 1;
	if (end >= detail.m_Groups.Count())
		--end;

	if (start == end)
		return start;

	// Figure out how far we are between start and end...
	float dist = 0.0f;
	float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha);
	if (dAlpha != 0.0f)
	{
		dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha;
	}

	// Pick a number, any number...
	float r = rand() / (float)RAND_MAX;

	// When dist == 0, we *always* want start.
	// When dist == 1, we *always* want end
	// That's why this logic looks a little reversed
	return (r > dist) ? start : end;
}


//-----------------------------------------------------------------------------
// Selects a detail object
//-----------------------------------------------------------------------------
static int SelectDetail( DetailObjectGroup_t const& group )
{
	// Pick a number, any number...
	float r = rand() / (float)RAND_MAX;

	// Look through the list of models + pick the one associated with this number
	for ( int i = 0; i < group.m_Models.Count(); ++i )
	{
		if (r <= group.m_Models[i].m_Amount)
			return i;
	}

	return -1;
}


//-----------------------------------------------------------------------------
// Adds a detail dictionary element (expected to oftentimes be shared)
//-----------------------------------------------------------------------------
static int AddDetailDictLump( const char* pModelName )
{
	DetailObjectDictLump_t dictLump;
	strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH );

	for (int i = s_DetailObjectDictLump.Count(); --i >= 0; )
	{
		if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) ))
			return i;
	}

	return s_DetailObjectDictLump.AddToTail( dictLump );
}

static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex )
{
	DetailSpriteDictLump_t dictLump;
	dictLump.m_UL = pPos[0];
	dictLump.m_LR = pPos[1];
	dictLump.m_TexUL = pTex[0];
	dictLump.m_TexLR = pTex[1];

	for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; )
	{
		if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) ))
			return i;
	}

	return s_DetailSpriteDictLump.AddToTail( dictLump );
}


//-----------------------------------------------------------------------------
// Computes the leaf that the detail lies in
//-----------------------------------------------------------------------------
static int ComputeDetailLeaf( const Vector& pt )
{
	int node = 0;
	while( node >= 0 )
	{
		dnode_t* pNode = &dnodes[node];
		dplane_t* pPlane = &dplanes[pNode->planenum];

		if (DotProduct(pt, pPlane->normal) < pPlane->dist)
			node = pNode->children[1];
		else
			node = pNode->children[0];
	}

	return - node - 1;
}


//-----------------------------------------------------------------------------
// Make sure the details are compiled with static prop
//-----------------------------------------------------------------------------
static bool IsModelValid( const char* pModelName )
{
	StaticPropLookup_t lookup;
	lookup.m_ModelName = pModelName;

	int i = s_StaticPropLookup.Find( lookup );
	if (i != s_StaticPropLookup.InvalidIndex() )
		return s_StaticPropLookup[i].m_IsValid;

	CUtlBuffer buf;
	lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf );
	if (!lookup.m_IsValid)
	{
		Warning("Error loading studio model \"%s\"!\n", pModelName );
	}

	s_StaticPropLookup.Insert( lookup );
	return lookup.m_IsValid;
}


//-----------------------------------------------------------------------------
// Add a detail to the lump.
//-----------------------------------------------------------------------------
static int s_nDetailOverflow = 0;
static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation )
{
	// Make sure the model is valid...
	if (!IsModelValid(pModelName))
		return;

	if (s_DetailObjectLump.Count() == 65535)
	{
		++s_nDetailOverflow;
		return;
	}

	// Insert an element into the object dictionary if it aint there...
	int i = s_DetailObjectLump.AddToTail( );

	DetailObjectLump_t& objectLump = s_DetailObjectLump[i];
	objectLump.m_DetailModel = AddDetailDictLump( pModelName ); 
	VectorCopy( angles, objectLump.m_Angles );
	VectorCopy( pt, objectLump.m_Origin );
	objectLump.m_Leaf = ComputeDetailLeaf(pt);
	objectLump.m_Lighting.r = 255;
	objectLump.m_Lighting.g = 255;
	objectLump.m_Lighting.b = 255;
	objectLump.m_Lighting.exponent = 0;
	objectLump.m_LightStyles = -1;
	objectLump.m_LightStyleCount = 0;
	objectLump.m_Orientation = nOrientation;
	objectLump.m_Type = DETAIL_PROP_TYPE_MODEL;
}


//-----------------------------------------------------------------------------
// Add a detail sprite to the lump.
//-----------------------------------------------------------------------------
static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation, const Vector2D *pPos, const Vector2D *pTex, float flScale )
{
	// Insert an element into the object dictionary if it aint there...
	int i = s_DetailObjectLump.AddToTail( );

	if (i >= 65535)
	{
		Error( "Error! Too many detail props emitted on this map!\n" );
	}

	DetailObjectLump_t& objectLump = s_DetailObjectLump[i];
	objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex ); 
	VectorCopy( vecAngles, objectLump.m_Angles );
	VectorCopy( vecOrigin, objectLump.m_Origin );
	objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin);
	objectLump.m_Lighting.r = 255;
	objectLump.m_Lighting.g = 255;
	objectLump.m_Lighting.b = 255;
	objectLump.m_Lighting.exponent = 0;
	objectLump.m_LightStyles = -1;
	objectLump.m_LightStyleCount = 0;
	objectLump.m_Orientation = nOrientation;
	objectLump.m_Type = DETAIL_PROP_TYPE_SPRITE;
	objectLump.m_flScale = flScale;
}


//-----------------------------------------------------------------------------
// Got a detail! Place it on the surface...
//-----------------------------------------------------------------------------
// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function
// (only when not in the debugger?)
// Printing the values of normal at the bottom of the function fixes it as does
// disabling global optimizations.
#pragma optimize("g", off)
static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal )
{
	// But only place it on the surface if it meets the angle constraints...
	float cosAngle = normal.z;

	// Never emit if the angle's too steep
	if (cosAngle < model.m_MaxCosAngle)
		return;

	// If it's between min + max, flip a coin...
	if (cosAngle < model.m_MinCosAngle)
	{
		float probability = (cosAngle - model.m_MaxCosAngle) / 
			(model.m_MinCosAngle - model.m_MaxCosAngle);

		float t = rand() / (float)RAND_MAX;
		if (t > probability)
			return;
	}

	// Compute the orientation of the detail
	QAngle angles;
	if (model.m_Flags & MODELFLAG_UPRIGHT)
	{
		// If it's upright, we just select a random yaw
		angles.Init( 0, 360.0f * rand() / (float)RAND_MAX, 0.0f );
	}
	else
	{
		// It's not upright, so it must conform to the ground. Choose
		// a random orientation based on the surface normal

		Vector zaxis;
		VectorCopy( normal, zaxis );

		// Choose any two arbitrary axes which are perpendicular to the normal
		Vector xaxis( 1, 0, 0 );
		if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3)
			xaxis.Init( 0, 1, 0 );
		Vector yaxis;
		CrossProduct( zaxis, xaxis, yaxis );
		CrossProduct( yaxis, zaxis, xaxis );
		VMatrix matrix;
		matrix.SetBasisVectors( xaxis, yaxis, zaxis );

		float rotAngle = 360.0f * rand() / (float)RAND_MAX;
		VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle );
		matrix = matrix * rot;

		MatrixToAngles( matrix, angles );
	}

	// FIXME: We may also want a purely random rotation too

	// Insert an element into the object dictionary if it aint there...
	switch ( model.m_Type )
	{
	case DETAIL_PROP_TYPE_MODEL:
		AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation );
		break;

	case DETAIL_PROP_TYPE_SPRITE:
		{
			float flScale = 1.0f;
			if ( model.m_flRandomScaleStdDev != 0.0f ) 
			{
				flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) );
			}

			AddDetailSpriteToLump( pt, angles, model.m_Orientation, model.m_Pos, model.m_Tex, flScale );
		}
		break;
	}
}


//-----------------------------------------------------------------------------
// Places Detail Objects on a face
//-----------------------------------------------------------------------------
static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail )
{
	if (pFace->numedges < 3)
		return;

	// We're going to pick a bunch of random points, and then probabilistically
	// decide whether or not to plant a detail object there.

	// Turn the face into a bunch of polygons, and compute the area of each
	int* pSurfEdges = &dsurfedges[pFace->firstedge];
	int vertexIdx = (pSurfEdges[0] < 0);
	int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx];
	dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex];
	for (int i = 1; i < pFace->numedges - 1; ++i )
	{
		int vertexIdx = (pSurfEdges[i] < 0);
		dedge_t* pEdge = &dedges[abs(pSurfEdges[i])];

		// Compute two triangle edges
		Vector e1, e2;
		VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 );
		VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 );

		// Compute the triangle area
		Vector areaVec;
		CrossProduct( e1, e2, areaVec );
		float normalLength = areaVec.Length();
		float area = 0.5f * normalLength;

		// Compute the number of samples to take
		int numSamples = area * detail.m_Density * 0.000001;

		// Now take a sample, and randomly place an object there
		for (int i = 0; i < numSamples; ++i )
		{
			// Create a random sample...
			float u = rand() / (float)RAND_MAX;
			float v = rand() / (float)RAND_MAX;
			if (v > 1.0f - u)
			{
				u = 1.0f - u;
				v = 1.0f - v;
				assert( u + v <= 1.0f );
			}

			// Compute alpha
			float alpha = 1.0f;

			// Select a group based on the alpha value
			int group = SelectGroup( detail, alpha );

			// Now that we've got a group, choose a detail
			int model = SelectDetail( detail.m_Groups[group] );
			if (model < 0)
				continue;

			// Got a detail! Place it on the surface...
			Vector pt, normal;
			VectorMA( pFirstVertex->point, u, e1, pt );
			VectorMA( pt, v, e2, pt );
			VectorDivide( areaVec, -normalLength, normal );

			PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal );
		}
	}
}
#pragma optimize("", on)


//-----------------------------------------------------------------------------
// Places Detail Objects on a face
//-----------------------------------------------------------------------------
static float ComputeDisplacementFaceArea( dface_t* pFace )
{
	float area = 0.0f;

	// Compute the area of the base face
	int* pSurfEdges = &dsurfedges[pFace->firstedge];
	int vertexIdx = (pSurfEdges[0] < 0);
	int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx];
	dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex];
	for (int i = 1; i <= 2; ++i )
	{
		int vertexIdx = (pSurfEdges[i] < 0);
		dedge_t* pEdge = &dedges[abs(pSurfEdges[i])];

		// Compute two triangle edges
		Vector e1, e2;
		VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 );
		VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 );

		// Compute the triangle area
		Vector areaVec;
		CrossProduct( e1, e2, areaVec );
		float normalLength = areaVec.Length();
		area += 0.5f * normalLength;
	}

	return area;
}


//-----------------------------------------------------------------------------
// Places Detail Objects on a face
//-----------------------------------------------------------------------------
static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace, 
						DetailObject_t& detail, CCoreDispInfo& coreDispInfo )
{
	assert(pFace->numedges == 4);

	// We're going to pick a bunch of random points, and then probabilistically
	// decide whether or not to plant a detail object there.

	// Compute the area of the base face
	float area = ComputeDisplacementFaceArea( pFace );

	// Compute the number of samples to take
	int numSamples = area * detail.m_Density * 0.000001;

	// Now take a sample, and randomly place an object there
	for (int i = 0; i < numSamples; ++i )
	{
		// Create a random sample...
		float u = rand() / (float)RAND_MAX;
		float v = rand() / (float)RAND_MAX;

		// Compute alpha
		float alpha;
		Vector pt, normal;
		coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha );
		alpha /= 255.0f;

		// Select a group based on the alpha value
		int group = SelectGroup( detail, alpha );

		// Now that we've got a group, choose a detail
		int model = SelectDetail( detail.m_Groups[group] );
		if (model < 0)
			continue;

		// Got a detail! Place it on the surface...
		PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal );
	}
}


//-----------------------------------------------------------------------------
// Sort detail objects by leaf
//-----------------------------------------------------------------------------
static int SortFunc( const void *arg1, const void *arg2 )
{
	int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf;
	if ( nDelta < 0 )
		return -1;
	if ( nDelta > 0 )
		return 1;
	return 0;
}


//-----------------------------------------------------------------------------
// Places Detail Objects in the lump
//-----------------------------------------------------------------------------
static void SetLumpData( )
{
	// Sort detail props by leaf
	qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc );

	GameLumpHandle_t handle = GetGameLumpHandle(GAMELUMP_DETAIL_PROPS);
	if (handle != InvalidGameLump())
		DestroyGameLump(handle);
	int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t);
	int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t);
	int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t);
	int nSize = nDictSize + nSpriteDictSize + nObjSize + 3 * sizeof(int);

	handle = CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION );

	// Serialize the data
	CUtlBuffer buf( GetGameLump(handle), nSize );
	buf.PutInt( s_DetailObjectDictLump.Count() );
	if (nDictSize)
		buf.Put( s_DetailObjectDictLump.Base(), nDictSize );
	buf.PutInt( s_DetailSpriteDictLump.Count() );
	if (nSpriteDictSize)
		buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize );
	buf.PutInt( s_DetailObjectLump.Count() );
	if (nObjSize)
		buf.Put( s_DetailObjectLump.Base(), nObjSize );
}


//-----------------------------------------------------------------------------
// Places Detail Objects in the level
//-----------------------------------------------------------------------------
void EmitDetailModels()
{
	StartPacifier("Placing detail props : ");

	// Place stuff on each face
	dface_t* pFace = dfaces;
	for (int j = 0; j < numfaces; ++j)
	{
		UpdatePacifier( (float)j / (float)numfaces );

		// Get at the material associated with this face
		texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo];
		dtexdata_t* pTexData = GetTexData( pTexInfo->texdata );

		// Try to get at the material
		bool found;
		MaterialSystemMaterial_t handle = 
			FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), 
						  &found, false );
		if (!found)
			continue;

		// See if its got any detail objects on it
		const char* pDetailType = GetMaterialVar( handle, "%detailtype" );
		if (!pDetailType)
			continue;

		// Get the detail type...
		DetailObject_t search;
		search.m_Name = pDetailType;
		int objectType = s_DetailObjectDict.Find(search);
		if (objectType < 0)
		{
			Warning("Material %s uses unknown detail object type %s!\n",	
				TexDataStringTable_GetString( pTexData->nameStringTableID ),
				pDetailType);
			continue;
		}

		// Emit objects on a particular face
		DetailObject_t& detail = s_DetailObjectDict[objectType];

		if (pFace[j].dispinfo < 0)
		{
			EmitDetailObjectsOnFace( &pFace[j], detail );
		}
		else
		{
			// Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates.
			mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo];
			CCoreDispInfo coreDispInfo;
			DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, &pFace[j] );

			EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo );
		}
	}

	// Emit specifically specified detail props
	Vector origin;
	QAngle angles;
	Vector2D pos[2];
	Vector2D tex[2];
	for (int i = 0; i < num_entities; ++i)
	{
		char* pEntity = ValueForKey(&entities[i], "classname");
		if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail"))
		{
			GetVectorForKey( &entities[i], "origin", origin );
			GetAnglesForKey( &entities[i], "angles", angles );
			char* pModelName = ValueForKey( &entities[i], "model" );
			int nOrientation = IntForKey( &entities[i], "detailOrientation" );

			AddDetailToLump( pModelName, origin, angles, nOrientation );

			// strip this ent from the .bsp file
			entities[i].epairs = 0;
			continue;
		}

		if (!strcmp(pEntity, "prop_detail_sprite"))
		{
			GetVectorForKey( &entities[i], "origin", origin );
			GetAnglesForKey( &entities[i], "angles", angles );
			int nOrientation = IntForKey( &entities[i], "detailOrientation" );
			GetVector2DForKey( &entities[i], "position_ul", pos[0] );
			GetVector2DForKey( &entities[i], "position_lr", pos[1] );
			GetVector2DForKey( &entities[i], "tex_ul", tex[0] );
			GetVector2DForKey( &entities[i], "tex_size", tex[1] );
			float flTextureSize = FloatForKey( &entities[i], "tex_total_size" );

			tex[1].x += tex[0].x - 0.5f;
			tex[1].y += tex[0].y - 0.5f;
			tex[0].x += 0.5f;
			tex[0].y += 0.5f;
			tex[0] /= flTextureSize;
			tex[1] /= flTextureSize;

			AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f );

			// strip this ent from the .bsp file
			entities[i].epairs = 0;
			continue;
		}
	}

	EndPacifier( true );
}


//-----------------------------------------------------------------------------
// Places Detail Objects in the level
//-----------------------------------------------------------------------------
void EmitDetailObjects()
{
	// Guarantee identical random emission...
	srand(1);
	RandomSeed( 1 );

	EmitDetailModels();

	// Done! Now lets add the lumps (destroy previous ones)
	SetLumpData( );

	if ( s_nDetailOverflow != 0 )
	{
		Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow );
	}
}
