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

#include "render_pch.h"
#include "r_decal.h"
#include "client.h"
#include <materialsystem/imaterialsystemhardwareconfig.h>
#include "decal.h"
#include "tier0/vprof.h"
#include "materialsystem/materialsystem_config.h"
#include "icliententity.h"
#include "icliententitylist.h"
#include "tier2/tier2.h"
#include "tier1/callqueue.h"
#include "tier1/memstack.h"
#include "mempool.h"
#include "vstdlib/random.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define DECAL_DISTANCE			4

// Empirically determined constants for minimizing overalpping decals
ConVar r_decal_overlap_count("r_decal_overlap_count", "3");
ConVar r_decal_overlap_area("r_decal_overlap_area", "0.4");

// if a new decal covers more than this many old decals, retire until this count remains
ConVar r_decal_cover_count("r_decal_cover_count", "4" );

static unsigned int s_DecalScaleVarCache = 0;
static unsigned int s_DecalScaleVariationVarCache = 0;
static unsigned int s_DecalFadeVarCache = 0;
static unsigned int s_DecalFadeTimeVarCache = 0;
static unsigned int s_DecalSecondPassVarCache = 0;


// This structure contains the information used to create new decals
struct decalinfo_t
{
	Vector		m_Position;			// world coordinates of the decal center
	Vector		m_SAxis;			// the s axis for the decal in world coordinates
	model_t*	m_pModel;			// the model the decal is going to be applied in
	worldbrushdata_t *m_pBrush;		// The shared brush data for this model
	IMaterial*	m_pMaterial;		// The decal material
	float		m_Size;				// Size of the decal (in world coords)
	int			m_Flags;
	int			m_Entity;			// Entity the decal is applied to.
	float		m_scale;
	float		m_flFadeDuration;
	float		m_flFadeStartTime;
	int			m_decalWidth;
	int			m_decalHeight;
	color32		m_Color;
	Vector		m_Basis[3];
	void		*m_pUserData;
	const Vector *m_pNormal;
	CUtlVector<SurfaceHandle_t>	m_aApplySurfs;
};

typedef struct
{
	CDecalVert	decalVert[4];
} decalcache_t;

// UNDONE: Compress this???  256K here?
static CClassMemoryPool<decal_t> g_DecalAllocator( 128 ); // 128 decals per block.
static int				g_nDynamicDecals = 0;
static int				g_nStaticDecals = 0;
static int				g_iLastReplacedDynamic = -1;

CUtlVector<decal_t*>		s_aDecalPool;

const int DECALCACHE_ENTRY_COUNT = 1024;
const int INVALID_CACHE_ENTRY = 0xFFFF;

class ALIGN16 CDecalVertCache
{
	enum decalindex_ordinal
	{
		DECAL_INDEX = 0,	// set this and use this to free the whole decal's list on compact
		NEXT_VERT_BLOCK_INDEX = 1,
		IS_FREE_INDEX = 2,
		FRAME_COUNT_INDEX = 3,
	};
public:
	void Init();
	CDecalVert *GetCachedVerts( decal_t *pDecal );
	void FreeCachedVerts( decal_t *pDecal );
	void StoreVertsInCache( decal_t *pDecal, CDecalVert *pList );

private:
	inline int GetIndex( decalcache_t *pBlock, decalindex_ordinal index )
	{
		return pBlock->decalVert[index].m_decalIndex;
	}
	inline void SetIndex( decalcache_t *pBlock, decalindex_ordinal index, int value )
	{
		pBlock->decalVert[index].m_decalIndex = value;
	}

	inline void SetNext( int iCur, int iNext )
	{
		SetIndex(m_cache + iCur, NEXT_VERT_BLOCK_INDEX, iNext);
	}
	inline void SetFree( int iBlock, bool bFree )
	{
		SetIndex( m_cache + iBlock, IS_FREE_INDEX, bFree );
	}
	inline bool IsFree(int iBlock)
	{
		return GetIndex(m_cache+iBlock, IS_FREE_INDEX) != 0;
	}

	decalcache_t *NextBlock( decalcache_t *pCache );
	void FreeBlock(int cacheIndex);
	// search for blocks not used this frame and free them
	// this way we don't manage an LRU but get similar behavior
	void FindFreeBlocks( int blockCount );
	int AllocBlock();
	int AllocBlocks( int blockCount );

	ALIGN16 decalcache_t	m_cache[DECALCACHE_ENTRY_COUNT] ALIGN16_POST;
	int m_freeBlockCount;
	int m_firstFree;
	int m_frameBlocks;
	int m_lastFrameCount;
	int m_freeTestIndex;
} ALIGN16_POST;

static CDecalVertCache ALIGN16 g_DecalVertCache;
static decal_t	*s_pDecalDestroyList = NULL;

int	g_nMaxDecals = 0;

//
// ConVars that control distance-based decal scaling
//
static ConVar r_dscale_nearscale( "r_dscale_nearscale", "1", FCVAR_CHEAT );
static ConVar r_dscale_neardist( "r_dscale_neardist", "100", FCVAR_CHEAT );
static ConVar r_dscale_farscale( "r_dscale_farscale", "4", FCVAR_CHEAT );
static ConVar r_dscale_fardist( "r_dscale_fardist", "2000", FCVAR_CHEAT );
static ConVar r_dscale_basefov( "r_dscale_basefov", "90", FCVAR_CHEAT );

ConVar r_spray_lifetime( "r_spray_lifetime", "2", 0, "Number of rounds player sprays are visible" );
ConVar r_queued_decals( "r_queued_decals", "0", 0, "Offloads a bit of decal rendering setup work to the material system queue when enabled." );


// This makes sure all the decals got freed before the engine is shutdown.
static class CDecalChecker
{
public:
	~CDecalChecker()
	{
		Assert( g_nDynamicDecals == 0 );
	}
} g_DecalChecker;

// used for decal LOD
VMatrix g_BrushToWorldMatrix;

static CUtlVector<SurfaceHandle_t>	s_DecalSurfaces[ MAX_MAT_SORT_GROUPS + 1 ];
static ConVar r_drawdecals( "r_drawdecals", "1", FCVAR_CHEAT, "Render decals." );
static ConVar r_drawbatchdecals( "r_drawbatchdecals", "1", 0, "Render decals batched." );

#ifndef SWDS
static void R_DecalCreate( decalinfo_t* pDecalInfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement );
static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry);
#endif
void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const float *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal );
void R_DecalSortInit( void );

static void r_printdecalinfo_f()
{
	int nPermanent = 0;
	int nDynamic = 0;

	for ( int i=0; i < g_nMaxDecals; i++ )
	{
		if ( s_aDecalPool[i] )
		{
			if ( s_aDecalPool[i]->flags & FDECAL_PERMANENT )
				++nPermanent;
			else
				++nDynamic;
		}
	}

	Assert( nDynamic == g_nDynamicDecals );
	Msg( "%d decals: %d permanent, %d dynamic\nr_decals: %d\n", nPermanent+nDynamic, nPermanent, nDynamic, r_decals.GetInt() );
}

static ConCommand r_printdecalinfo( "r_printdecalinfo", r_printdecalinfo_f );


void CDecalVertCache::StoreVertsInCache( decal_t *pDecal, CDecalVert *pList )
{
	int vertCount = pDecal->clippedVertCount;
	int blockCount = (vertCount+3)>>2;
	FindFreeBlocks(blockCount);
	if ( blockCount > m_freeBlockCount )
		return;
	int cacheHandle = AllocBlocks(blockCount);
	pDecal->cacheHandle = cacheHandle;
	decalcache_t *pCache = &m_cache[cacheHandle];
	while ( blockCount )
	{
		Assert(GetIndex(pCache, DECAL_INDEX) == -1 );
		// don't memcpy here it overwrites the indices we're storing in the m_decalIndex data
		for ( int i = 0; i < 4; i++ )
		{
			pCache->decalVert[i].m_vPos = pList[i].m_vPos;
			pCache->decalVert[i].m_ctCoords = pList[i].m_ctCoords;
			pCache->decalVert[i].m_cLMCoords = pList[i].m_cLMCoords;
		}

		pList += 4;
		blockCount--;
		SetIndex( pCache, DECAL_INDEX, pDecal->m_iDecalPool );
		SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount );
		pCache = NextBlock(pCache);
	}
}

void CDecalVertCache::FreeCachedVerts( decal_t *pDecal )
{
	// walk the list 
	int nextIndex = INVALID_CACHE_ENTRY;
	for ( int cacheHandle = pDecal->cacheHandle; cacheHandle != INVALID_CACHE_ENTRY; cacheHandle = nextIndex )
	{
		decalcache_t *pCache = m_cache + cacheHandle;
		nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX);
		Assert(GetIndex(pCache,DECAL_INDEX)==pDecal->m_iDecalPool);
		FreeBlock(cacheHandle);
	}
	pDecal->cacheHandle = INVALID_CACHE_ENTRY;
	pDecal->clippedVertCount = 0;
}

CDecalVert *CDecalVertCache::GetCachedVerts( decal_t *pDecal )
{
	int cacheHandle = pDecal->cacheHandle;
	// track blocks used this frame to avoid thrashing
	if ( r_framecount != m_lastFrameCount )
	{
		m_frameBlocks = 0;
		m_lastFrameCount = r_framecount;
	}

	if ( cacheHandle == INVALID_CACHE_ENTRY )
		return NULL;
	decalcache_t *pCache = &m_cache[cacheHandle];
	for ( int i = cacheHandle; i != INVALID_CACHE_ENTRY; i = GetIndex(&m_cache[i], NEXT_VERT_BLOCK_INDEX) )
	{
		SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount );
		Assert( GetIndex(pCache, DECAL_INDEX) == pDecal->m_iDecalPool);
	}

	int vertCount = pDecal->clippedVertCount;
	int blockCount = (vertCount+3)>>2;
	m_frameBlocks += blockCount;

	// Make linked vert lists contiguous by copying to the clip buffer
	if ( blockCount > 1 )
	{
		int indexOut = 0;
		while ( blockCount )
		{
			V_memcpy( &g_DecalClipVerts[indexOut], pCache, sizeof(*pCache) );
			indexOut += 4;
			blockCount --;
			pCache = NextBlock(pCache);
		}
		return g_DecalClipVerts;
	}
	// only one block, no need to copy
	return pCache->decalVert;
}

void CDecalVertCache::Init()
{
	m_firstFree = 0;
	m_freeTestIndex = 0;
	for ( int i = 0; i < DECALCACHE_ENTRY_COUNT; i++ )
	{
		SetNext( i, i+1 );
		SetIndex( &m_cache[i], DECAL_INDEX, -1 );
		SetFree( i, true );
	}
	SetNext( DECALCACHE_ENTRY_COUNT-1, INVALID_CACHE_ENTRY );
	m_freeBlockCount = DECALCACHE_ENTRY_COUNT;
}

decalcache_t *CDecalVertCache::NextBlock( decalcache_t *pCache )
{
	int nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX);
	if ( nextIndex == INVALID_CACHE_ENTRY )
		return NULL;
	return m_cache + nextIndex;
}
void CDecalVertCache::FreeBlock(int cacheIndex)
{
	SetFree( cacheIndex, true );
	SetNext( cacheIndex, m_firstFree );
	SetIndex( &m_cache[cacheIndex], DECAL_INDEX, -1 );

	m_firstFree = cacheIndex;
	m_freeBlockCount++;
}

// search for blocks not used this frame and free them
// this way we don't manage an LRU but get similar behavior
void CDecalVertCache::FindFreeBlocks( int blockCount )
{
	if ( blockCount <= m_freeBlockCount )
		return;
	int possibleFree = DECALCACHE_ENTRY_COUNT - m_frameBlocks;
	if ( blockCount > possibleFree )
		return;

	// limit the search for performance to 16 entries
	int lastTest = (m_freeTestIndex + 16) & (DECALCACHE_ENTRY_COUNT-1);

	for ( ; m_freeTestIndex != lastTest; m_freeTestIndex = (m_freeTestIndex+1)&(DECALCACHE_ENTRY_COUNT-1) )
	{
		if ( !IsFree(m_freeTestIndex) )
		{
			int lastFrame = GetIndex(&m_cache[m_freeTestIndex], FRAME_COUNT_INDEX);
			if ( (r_framecount - lastFrame) > 1 )
			{
				int iDecal = GetIndex(&m_cache[m_freeTestIndex], DECAL_INDEX);
				FreeCachedVerts(s_aDecalPool[iDecal]);
			}
		}
		if ( m_freeBlockCount >= blockCount )
			break;
	}
}

int CDecalVertCache::AllocBlock()
{
	if ( !m_freeBlockCount )
		return INVALID_CACHE_ENTRY;
	Assert(IsFree(m_firstFree));
	int nextFree = GetIndex(m_cache+m_firstFree, NEXT_VERT_BLOCK_INDEX );
	int cacheIndex = m_firstFree;
	SetFree( cacheIndex, false );
	m_firstFree = nextFree;
	m_freeBlockCount--;
	return cacheIndex;
}
int CDecalVertCache::AllocBlocks( int blockCount )
{
	if ( blockCount > m_freeBlockCount )
		return INVALID_CACHE_ENTRY;
	int firstBlock = AllocBlock();
	Assert(firstBlock!=INVALID_CACHE_ENTRY);
	int blockHandle = firstBlock;
	for ( int i = 1; i < blockCount; i++ )
	{
		int nextBlock = AllocBlock();
		Assert(nextBlock!=INVALID_CACHE_ENTRY);
		SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, nextBlock );
		blockHandle = nextBlock;
	}
	SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, INVALID_CACHE_ENTRY );
	return firstBlock;
}


//-----------------------------------------------------------------------------
// Computes the offset for a decal polygon
//-----------------------------------------------------------------------------
float ComputeDecalLightmapOffset( SurfaceHandle_t surfID )
{
	float flOffset;
	if ( MSurf_Flags( surfID ) & SURFDRAW_BUMPLIGHT )
	{
		int nWidth, nHeight;
		materials->GetLightmapPageSize( 
			SortInfoToLightmapPage( MSurf_MaterialSortID( surfID ) ), &nWidth, &nHeight );
 		int nXExtent = ( MSurf_LightmapExtents( surfID )[0] ) + 1;

		flOffset = ( nWidth != 0 ) ? (float)nXExtent / (float)nWidth : 0.0f;
	}
	else
	{
		flOffset = 0.0f;
	}
	return flOffset;
}

static VertexFormat_t GetUncompressedFormat( const IMaterial * pMaterial )
{
	// FIXME: IMaterial::GetVertexFormat() should do this stripping (add a separate 'SupportsCompression' accessor)
	return ( pMaterial->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED );
}

//-----------------------------------------------------------------------------
// Draws a decal polygon
//-----------------------------------------------------------------------------
void Shader_DecalDrawPoly( CDecalVert *v, IMaterial *pMaterial, SurfaceHandle_t surfID, int vertCount, decal_t *pdecal, float flFade )
{
#ifndef SWDS
	int vertexFormat = 0;
	CMatRenderContextPtr pRenderContext( materials );

#ifdef USE_CONVARS
	if( ShouldDrawInWireFrameMode() )
	{
		pRenderContext->Bind( g_materialDecalWireframe );
	}
	else
#endif
	{
		Assert( MSurf_MaterialSortID( surfID ) >= 0 && 
			    MSurf_MaterialSortID( surfID )  < g_WorldStaticMeshes.Count() );
		pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID );
		pRenderContext->Bind( pMaterial, pdecal->userdata );
		vertexFormat = GetUncompressedFormat( pMaterial );
	}

	IMesh *pMesh = pRenderContext->GetDynamicMesh( );
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_POLYGON, vertCount );

	byte color[4] = {pdecal->color.r,pdecal->color.g,pdecal->color.b,pdecal->color.a};
	if ( flFade != 1.0f )
	{
		color[3] = (byte)( color[3] * flFade );
	}

	// Deal with fading out... (should this be done in the shader?)
	// Note that we do it with per-vertex color even though the translucency
	// is constant so as to not change any rendering state (like the constant
	// alpha value)
	if (pdecal->flags & FDECAL_DYNAMIC)
	{
		float fadeval;

		// Negative fadeDuration value means to fade in
		if (pdecal->fadeDuration < 0)
		{
			fadeval = - (cl.GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration;
		}
		else
		{
			fadeval = 1.0 - (cl.GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration;
		}

		fadeval = clamp( fadeval, 0.0f, 1.0f );
		color[3] = (byte) (color[3] * fadeval);
	}


	Vector normal(0,0,1), tangentS(1,0,0), tangentT(0,1,0);

	if ( vertexFormat & (VERTEX_NORMAL|VERTEX_TANGENT_SPACE) )
	{
		normal = MSurf_Plane( surfID ).normal;
		if ( vertexFormat & VERTEX_TANGENT_SPACE )
		{
			Vector tVect;
			bool negate = TangentSpaceSurfaceSetup( surfID, tVect );
			TangentSpaceComputeBasis( tangentS, tangentT, normal, tVect, negate );
		}
	}

	float flOffset = pdecal->lightmapOffset;

	for( int i = 0; i < vertCount; i++, v++ )
	{
		meshBuilder.Position3f( VectorExpand( v->m_vPos ) );
		if ( vertexFormat & VERTEX_NORMAL )
		{
			meshBuilder.Normal3fv( normal.Base() );
		}
		meshBuilder.Color4ubv( color );

		// Check to see if we are in a material page.
		meshBuilder.TexCoord2f( 0, Vector2DExpand( v->m_ctCoords ) );
		meshBuilder.TexCoord2f( 1, Vector2DExpand( v->m_cLMCoords ) );
		meshBuilder.TexCoord1f( 2, flOffset );
		if ( vertexFormat & VERTEX_TANGENT_SPACE )
		{
			meshBuilder.TangentS3fv( tangentS.Base() ); 
			meshBuilder.TangentT3fv( tangentT.Base() ); 
		}
		meshBuilder.AdvanceVertex();
	}

	meshBuilder.End();
	pMesh->Draw();
#endif
}


//-----------------------------------------------------------------------------
// Gets the decal material and radius based on the decal index
//-----------------------------------------------------------------------------
void R_DecalGetMaterialAndSize( int decalIndex, IMaterial*& pDecalMaterial, float& w, float& h )
{
	pDecalMaterial = Draw_DecalMaterial( decalIndex );
	if (!pDecalMaterial)
		return;

	float scale = 1.0f;

	// Compute scale of surface
	// FIXME: cache this?
	bool found;
	IMaterialVar* pDecalScaleVar = pDecalMaterial->FindVar( "$decalScale", &found, false );
	if( found )
	{
		scale = pDecalScaleVar->GetFloatValue();
	}

	// compute the decal dimensions in world space
	w = pDecalMaterial->GetMappingWidth() * scale;
	h = pDecalMaterial->GetMappingHeight() * scale;
}

#ifndef SWDS



static inline decal_t *MSurf_DecalPointer( SurfaceHandle_t surfID )
{
	WorldDecalHandle_t handle = MSurf_Decals(surfID );
	if ( handle == WORLD_DECAL_HANDLE_INVALID )
		return NULL;

	return s_aDecalPool[handle];
}

static WorldDecalHandle_t DecalToHandle( decal_t *pDecal )
{
	if ( !pDecal )
		return WORLD_DECAL_HANDLE_INVALID;

	int decalIndex = pDecal->m_iDecalPool;
	Assert( decalIndex >= 0 && decalIndex < g_nMaxDecals );
	return static_cast<WorldDecalHandle_t> (decalIndex);
}

// Init the decal pool
void R_DecalInit( void )
{
	g_nMaxDecals = Q_atoi( r_decals.GetDefault() );
	g_nMaxDecals = MAX(64, g_nMaxDecals);
	Assert( g_DecalAllocator.Count() == 0 );
	g_nDynamicDecals = 0;
	g_nStaticDecals = 0;
	g_iLastReplacedDynamic = -1;

	s_aDecalPool.Purge();
	s_aDecalPool.SetSize( g_nMaxDecals );

	int i;

	// Traverse all surfaces of map and throw away current decals
	//
	// sort the surfaces into the sort arrays
	if ( host_state.worldbrush )
	{
		for( i = 0; i < host_state.worldbrush->numsurfaces; i++ )
		{
			SurfaceHandle_t surfID = SurfaceHandleFromIndex(i);
			MSurf_Decals( surfID ) = WORLD_DECAL_HANDLE_INVALID;
		}
	}

	for( int iDecal = 0; iDecal < g_nMaxDecals; ++iDecal )
	{
		s_aDecalPool[iDecal] = NULL;
	}

	g_DecalVertCache.Init();

	R_DecalSortInit();
}

void R_DecalTerm( worldbrushdata_t *pBrushData, bool term_permanent_decals )
{
	if( !pBrushData )
		return;

	for( int i = 0; i < pBrushData->numsurfaces; i++ )
	{
		decal_t *pNext;
		SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pBrushData );
		for( decal_t *pDecal=MSurf_DecalPointer( surfID ); pDecal; pDecal=pNext )
		{
			pNext = pDecal->pnext;
			if ( term_permanent_decals 
				|| (!(pDecal->flags & FDECAL_PERMANENT)
				     && !(pDecal->flags & FDECAL_PLAYERSPRAY)) )
			{
				R_DecalUnlink( pDecal, pBrushData );
			}
			else if( pDecal->flags & FDECAL_PLAYERSPRAY )
			{
				// time out player spray after some number of rounds
				pDecal->fadeStartTime += 1.0f;
				if( pDecal->fadeStartTime >= r_spray_lifetime.GetFloat() )
				{
					R_DecalUnlink( pDecal, pBrushData );
				}
			}
		}

		if ( term_permanent_decals )
		{
			Assert( MSurf_DecalPointer( surfID ) == NULL );
		}
	}
}

void R_DecalTermAll()
{
	s_pDecalDestroyList = NULL;
	for ( int i = 0; i<s_aDecalPool.Count(); i++ )
	{
		R_DecalUnlink( s_aDecalPool[i], host_state.worldbrush );
	}
}


static int R_DecalIndex( decal_t *pdecal )
{
	return pdecal->m_iDecalPool;
}


// Release the cache entry for this decal
static void R_DecalCacheClear( decal_t *pdecal )
{
	g_DecalVertCache.FreeCachedVerts( pdecal );
}


void R_DecalFlushDestroyList( void )
{
	decal_t *pDecal = s_pDecalDestroyList;
	while ( pDecal )
	{
		decal_t *pNext = pDecal->pDestroyList;
		R_DecalUnlink( pDecal, host_state.worldbrush );
		pDecal = pNext;
	}
	s_pDecalDestroyList = NULL;
}

static void R_DecalAddToDestroyList( decal_t *pDecal )
{
	if ( !pDecal->pDestroyList )
	{
		pDecal->pDestroyList = s_pDecalDestroyList;
		s_pDecalDestroyList = pDecal;
	}
}

// Unlink pdecal from any surface it's attached to
void R_DecalUnlink( decal_t *pdecal, worldbrushdata_t *pData )
{
	if ( !pdecal )
		return;

	decal_t *tmp;

	R_DecalCacheClear( pdecal );
	if ( IS_SURF_VALID( pdecal->surfID ) )
	{
		if ( MSurf_DecalPointer( pdecal->surfID ) == pdecal )
		{
			MSurf_Decals( pdecal->surfID ) = DecalToHandle( pdecal->pnext );
		}
		else 
		{
			tmp = MSurf_DecalPointer( pdecal->surfID );
			if ( !tmp )
				Sys_Error("Bad decal list");
			while ( tmp->pnext ) 
			{
				if ( tmp->pnext == pdecal ) 
				{
					tmp->pnext = pdecal->pnext;
					break;
				}
				tmp = tmp->pnext;
			}
		}
		
		// Tell the displacement surface.
		if( SurfaceHasDispInfo( pdecal->surfID ) )
		{
			IDispInfo * pDispInfo = MSurf_DispInfo( pdecal->surfID, pData );
			
			if ( pDispInfo )
				pDispInfo->NotifyRemoveDecal( pdecal->m_DispDecal );
		}
	}

	pdecal->surfID = SURFACE_HANDLE_INVALID;

	if ( !(pdecal->flags & FDECAL_PERMANENT) )
	{
		--g_nDynamicDecals;
		Assert( g_nDynamicDecals >= 0 );
	}
	else
	{
		--g_nStaticDecals;
		Assert( g_nStaticDecals >= 0 );
	}
	
	// Free the decal.
	Assert( s_aDecalPool[pdecal->m_iDecalPool] == pdecal );
	s_aDecalPool[pdecal->m_iDecalPool] = NULL;
	g_DecalAllocator.Free( pdecal );
}


int R_FindFreeDecalSlot()
{
	for ( int i=0; i < g_nMaxDecals; i++ )
	{
		if ( !s_aDecalPool[i] )
			return i;
	}
	return -1;
}

// Uncomment this to spew decals if we run out of space!!!
// #define SPEW_DECALS 
#if defined( SPEW_DECALS )
void SpewDecals()
{
	static bool spewdecals = true;

	if ( spewdecals )
	{
		spewdecals = false;

		int i = 0;
		for ( i = 0 ; i  < g_nMaxDecals; ++i )
		{
			decal_t *decal = s_aDecalPool[ i ];
			Assert( decal );
			if ( decal )
			{
				bool permanent = ( decal->flags & FDECAL_PERMANENT ) ? true : false;
				Msg( "%i == %s on %i perm %i at %.2f %.2f %.2f on surf %i (%.2f %.2f %2.f)\n", 
					i, 
					decal->material->GetName(), 
					(int)decal->entityIndex, 
					permanent ? 1 : 0,
					decal->position.x, decal->position.y, decal->position.z,
					(int)decal->surfID,
					decal->dx,
					decal->dy,
					decal->scale );
			}
		}
	}
}

#endif

int R_FindDynamicDecalSlot( int iStartAt )
{
	if ( (iStartAt >= g_nMaxDecals) || (iStartAt < 0) )
	{
		iStartAt = 0;
	}

	int i = iStartAt;

	do
	{
		// don't deallocate player sprays or permanent decals
		if ( s_aDecalPool[i] && 
			!(s_aDecalPool[i]->flags & FDECAL_PERMANENT) &&
			!(s_aDecalPool[i]->flags & FDECAL_PLAYERSPRAY) )
			return i;
		
		++i;

		if ( i >= g_nMaxDecals )
			i = 0;
	}
	while ( i != iStartAt );

	DevMsg("R_FindDynamicDecalSlot: no slot available.\n");

#if defined( SPEW_DECALS )
	SpewDecals();
#endif

	return -1;
}	

// Just reuse next decal in list
// A decal that spans multiple surfaces will use multiple decal_t pool entries, as each surface needs
// it's own.
static decal_t *R_DecalAlloc( int flags )
{
	static bool bWarningOnce = false;
	bool bPermanent = (flags & FDECAL_PERMANENT) != 0;

	int dynamicDecalLimit = min( r_decals.GetInt(), g_nMaxDecals );

	// Now find a slot. Unless it's dynamic and we're at the limit of dynamic decals,
	// we can look for a free slot.
	int iSlot = -1;
	if ( bPermanent || (g_nDynamicDecals < dynamicDecalLimit) )
	{
		iSlot = R_FindFreeDecalSlot();
	}

	if ( iSlot == -1 )
	{
		iSlot = R_FindDynamicDecalSlot( g_iLastReplacedDynamic+1 );
		if ( iSlot == -1 )
		{
			if ( !bWarningOnce )
			{
				// Can't find a free slot. Just kill the first one.
				DevWarning( 1, "Exceeded MAX_DECALS (%d).\n", g_nMaxDecals );
				bWarningOnce = true;
			}
			iSlot = 0;
		}

		R_DecalUnlink( s_aDecalPool[iSlot], host_state.worldbrush );
		g_iLastReplacedDynamic = iSlot;
	}
	
	// Setup the new decal.
	decal_t *pDecal = g_DecalAllocator.Alloc();
	s_aDecalPool[iSlot] = pDecal;
	pDecal->pDestroyList = NULL;
	pDecal->m_iDecalPool = iSlot;
	pDecal->surfID = SURFACE_HANDLE_INVALID;
	pDecal->cacheHandle = INVALID_CACHE_ENTRY;
	pDecal->clippedVertCount = 0;

	if ( !bPermanent )
	{
		++g_nDynamicDecals;
	}
	else
	{
		++g_nStaticDecals;
	}
		
	return pDecal;
}

/*
// The world coordinate system is right handed with Z up.
// 
//      ^ Z
//      |
//      |   
//      | 
//X<----|
//       \
//        \
//         \ Y
*/

void R_DecalSurface( SurfaceHandle_t surfID, decalinfo_t *decalinfo, bool bForceForDisplacement )
{
	if ( decalinfo->m_pNormal )
	{
		if ( DotProduct( MSurf_Plane( surfID ).normal, *(decalinfo->m_pNormal) ) < 0.0f )
			return;
	}

	// Get the texture associated with this surface
	mtexinfo_t* tex = MSurf_TexInfo( surfID );

	Vector4D &textureU = tex->textureVecsTexelsPerWorldUnits[0];
	Vector4D &textureV = tex->textureVecsTexelsPerWorldUnits[1];

	// project decal center into the texture space of the surface
	float s = DotProduct( decalinfo->m_Position, textureU.AsVector3D() ) + 
		textureU.w - MSurf_TextureMins( surfID )[0];
	float t = DotProduct( decalinfo->m_Position, textureV.AsVector3D() ) + 
		textureV.w - MSurf_TextureMins( surfID )[1];


	// Determine the decal basis (measured in world space)
	// Note that the decal basis vectors 0 and 1 will always lie in the same
	// plane as the texture space basis vectors	textureVecsTexelsPerWorldUnits.

	R_DecalComputeBasis( MSurf_Plane( surfID ).normal,
		(decalinfo->m_Flags & FDECAL_USESAXIS) ? &decalinfo->m_SAxis : 0,
		decalinfo->m_Basis );

	// Compute an effective width and height (axis aligned)	in the parent texture space
	// How does this work? decalBasis[0] represents the u-direction (width)
	// of the decal measured in world space, decalBasis[1] represents the 
	// v-direction (height) measured in world space.
	// textureVecsTexelsPerWorldUnits[0] represents the u direction of 
	// the surface's texture space measured in world space (with the appropriate
	// scale factor folded in), and textureVecsTexelsPerWorldUnits[1]
	// represents the texture space v direction. We want to find the dimensions (w,h)
	// of a square measured in texture space, axis aligned to that coordinate system.
	// All we need to do is to find the components of the decal edge vectors
	// (decalWidth * decalBasis[0], decalHeight * decalBasis[1])
	// in texture coordinates:

	float w = fabs( decalinfo->m_decalWidth  * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[0] ) ) +
		fabs( decalinfo->m_decalHeight * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[1] ) );
	
	float h = fabs( decalinfo->m_decalWidth  * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[0] ) ) +
		fabs( decalinfo->m_decalHeight * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[1] ) );

	// move s,t to upper left corner
	s -= ( w * 0.5 );
	t -= ( h * 0.5 );

	// Is this rect within the surface? -- tex width & height are unsigned
	if( !bForceForDisplacement )
	{
		if ( s <= -w || t <= -h || 
			 s > (MSurf_TextureExtents( surfID )[0]+w) || t > (MSurf_TextureExtents( surfID )[1]+h) )
		{
			return; // nope
		}
	}

	// stamp it
	R_DecalCreate( decalinfo, surfID, s, t, bForceForDisplacement );
}

//-----------------------------------------------------------------------------
// iterate over all surfaces on a node, looking for surfaces to decal
//-----------------------------------------------------------------------------
static void R_DecalNodeSurfaces( mnode_t* node, decalinfo_t *decalinfo )
{
	// iterate over all surfaces in the node
	SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface );
	for ( int i=0; i<node->numsurfaces ; ++i, ++surfID) 
	{
		if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS )
			continue;

		// Displacement surfaces get decals in R_DecalLeaf.
        if ( SurfaceHasDispInfo( surfID ) )
            continue;

		R_DecalSurface( surfID, decalinfo, false );
	}
}						 


void R_DecalLeaf( mleaf_t *pLeaf, decalinfo_t *decalinfo )
{
	SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface];
	for ( int i = 0; i < pLeaf->nummarksurfaces; i++ )
	{
		SurfaceHandle_t surfID = pHandle[i];
		
		// only process leaf surfaces
		if ( MSurf_Flags( surfID ) & (SURFDRAW_NODE|SURFDRAW_NODECALS) )
			continue;

		if ( decalinfo->m_aApplySurfs.Find( surfID ) != -1 )
			continue;

		Assert( !MSurf_DispInfo( surfID ) );

		float dist = fabs( DotProduct(decalinfo->m_Position, MSurf_Plane( surfID ).normal) - MSurf_Plane( surfID ).dist);
		if ( dist < DECAL_DISTANCE )
		{
			R_DecalSurface( surfID, decalinfo, false );
		}
	}

	// Add the decal to each displacement in the leaf it touches.
	for ( int i = 0; i < pLeaf->dispCount; i++ )
	{
		IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i );

		SurfaceHandle_t surfID = pDispInfo->GetParent();

		if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS )
			continue;

		// Make sure the decal hasn't already been added to it.
		if( pDispInfo->GetTag() )
			continue;

		pDispInfo->SetTag();

		// Trivial bbox reject.
		Vector bbMin, bbMax;
		pDispInfo->GetBoundingBox( bbMin, bbMax );
		if( decalinfo->m_Position.x - decalinfo->m_Size < bbMax.x && decalinfo->m_Position.x + decalinfo->m_Size > bbMin.x && 
			decalinfo->m_Position.y - decalinfo->m_Size < bbMax.y && decalinfo->m_Position.y + decalinfo->m_Size > bbMin.y && 
			decalinfo->m_Position.z - decalinfo->m_Size < bbMax.z && decalinfo->m_Position.z + decalinfo->m_Size > bbMin.z )
		{
			R_DecalSurface( pDispInfo->GetParent(), decalinfo, true );
		}
	}
}

//-----------------------------------------------------------------------------
// Recursive routine to find surface to apply a decal to.  World coordinates of 
// the decal are passed in r_recalpos like the rest of the engine.  This should 
// be called through R_DecalShoot()
//-----------------------------------------------------------------------------

static void R_DecalNode( mnode_t *node, decalinfo_t* decalinfo )
{
	cplane_t	*splitplane;
	float		dist;
	
	if (!node )
		return;
	if ( node->contents >= 0 )
	{
		R_DecalLeaf( (mleaf_t *)node, decalinfo );
		return;
	}

	splitplane = node->plane;
	dist = DotProduct (decalinfo->m_Position, splitplane->normal) - splitplane->dist;

	// This is arbitrarily set to 10 right now.  In an ideal world we'd have the 
	// exact surface but we don't so, this tells me which planes are "sort of 
	// close" to the gunshot -- the gunshot is actually 4 units in front of the 
	// wall (see dlls\weapons.cpp). We also need to check to see if the decal 
	// actually intersects the texture space of the surface, as this method tags
	// parallel surfaces in the same node always.
	// JAY: This still tags faces that aren't correct at edges because we don't 
	// have a surface normal

	if (dist > decalinfo->m_Size)
	{
		R_DecalNode (node->children[0], decalinfo);
	}
	else if (dist < -decalinfo->m_Size)
	{
		R_DecalNode (node->children[1], decalinfo);
	}
	else 
	{
		if ( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )
			R_DecalNodeSurfaces( node, decalinfo );

		R_DecalNode (node->children[0], decalinfo);
		R_DecalNode (node->children[1], decalinfo);
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pList - 
//			count - 
// Output : static int
//-----------------------------------------------------------------------------
static int DecalListAdd( decallist_t *pList, int count )
{
	int			i;
	Vector		tmp;
	decallist_t	*pdecal;

	pdecal = pList + count;
	for ( i = 0; i < count; i++ )
	{
		if ( !Q_strcmp( pdecal->name, pList[i].name ) && 
			pdecal->entityIndex == pList[i].entityIndex )
		{
			VectorSubtract( pdecal->position, pList[i].position, tmp );	// Merge
			if ( VectorLength( tmp ) < 2 )	// UNDONE: Tune this '2' constant
				return count;
		}
	}

	// This is a new decal
	return count + 1;
}


typedef int (__cdecl *qsortFunc_t)( const void *, const void * );

static int __cdecl DecalDepthCompare( const decallist_t *elem1, const decallist_t *elem2 )
{
	if ( elem1->depth > elem2->depth )
		return -1;
	if ( elem1->depth < elem2->depth )
		return 1;

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Called by CSaveRestore::SaveClientState
// Input  : *pList - 
// Output : int
//-----------------------------------------------------------------------------
int DecalListCreate( decallist_t *pList )
{
	int total = 0;
	int i, depth;

	if ( host_state.worldmodel )
	{
		for ( i = 0; i < g_nMaxDecals; i++ )
		{
			decal_t *decal = s_aDecalPool[i];

			// Decal is in use and is not a custom decal
			if ( !decal || !IS_SURF_VALID( decal->surfID ) || (decal->flags & ( FDECAL_CUSTOM | FDECAL_DONTSAVE ) ) )	
				 continue;

			decal_t		*pdecals;
			IMaterial 	*pMaterial;

			// compute depth
			depth = 0;
			pdecals = MSurf_DecalPointer( decal->surfID );
			while ( pdecals && pdecals != decal )
			{
				depth++;
				pdecals = pdecals->pnext;
			}
			pList[total].depth = depth;
			pList[total].flags = decal->flags;
			
			R_DecalUnProject( decal, &pList[total] );

			pMaterial = decal->material;
			Q_strncpy( pList[total].name, pMaterial->GetName(), sizeof( pList[total].name ) );

			// Check to see if the decal should be added
			total = DecalListAdd( pList, total );
		}
	}

	// Sort the decals lowest depth first, so they can be re-applied in order
	qsort( pList, total, sizeof(decallist_t), ( qsortFunc_t )DecalDepthCompare );

	return total;
}
// ---------------------------------------------------------

static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry )
{
	if ( !pdecal || !IS_SURF_VALID( pdecal->surfID ) )
		return false;

	VectorCopy( pdecal->position, entry->position );
	entry->entityIndex = pdecal->entityIndex;

	// Grab surface plane equation
	cplane_t plane = MSurf_Plane( pdecal->surfID );

	VectorCopy( plane.normal, entry->impactPlaneNormal );
	return true;
}


// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords
static void R_DecalShoot_( IMaterial *pMaterial, int entity, const model_t *model, 
						  const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal, void *userdata = 0 )
{
	decalinfo_t decalInfo;
	decalInfo.m_flFadeDuration = 0;
	decalInfo.m_flFadeStartTime = 0;

	VectorCopy( position, decalInfo.m_Position );	// Pass position in global

	if ( !model || model->type != mod_brush || !pMaterial )
		return;

	decalInfo.m_pModel = (model_t *)model;
	decalInfo.m_pBrush = model->brush.pShared;

	// Deal with the s axis if one was passed in
	if (saxis)
	{
		flags |= FDECAL_USESAXIS;
		VectorCopy( *saxis, decalInfo.m_SAxis );
	}

	// More state used by R_DecalNode()
	decalInfo.m_pMaterial = pMaterial;
	decalInfo.m_pUserData = userdata;

	decalInfo.m_Flags = flags;
	decalInfo.m_Entity = entity;
	decalInfo.m_Size = pMaterial->GetMappingWidth() >> 1;
	if ( (int)(pMaterial->GetMappingHeight() >> 1) > decalInfo.m_Size )
		decalInfo.m_Size = pMaterial->GetMappingHeight() >> 1;

	// Compute scale of surface
	// FIXME: cache this?
	IMaterialVar *decalScaleVar;
	bool found;
	decalScaleVar = decalInfo.m_pMaterial->FindVar( "$decalScale", &found, false );
	if( found )
	{
		decalInfo.m_scale = 1.0f / decalScaleVar->GetFloatValue();
		decalInfo.m_Size *= decalScaleVar->GetFloatValue();
	}
	else
	{
		decalInfo.m_scale = 1.0f;
	}

	// compute the decal dimensions in world space
	decalInfo.m_decalWidth = pMaterial->GetMappingWidth() / decalInfo.m_scale;
	decalInfo.m_decalHeight = pMaterial->GetMappingHeight() / decalInfo.m_scale;
	decalInfo.m_Color = rgbaColor;
	decalInfo.m_pNormal = pNormal;
	decalInfo.m_aApplySurfs.Purge();

	// Clear the displacement tags because we use them in R_DecalNode.
	DispInfo_ClearAllTags( decalInfo.m_pBrush->hDispInfos );

	mnode_t *pnodes = decalInfo.m_pBrush->nodes + decalInfo.m_pModel->brush.firstnode;
	R_DecalNode( pnodes, &decalInfo );
}

// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords
// This is called from cl_parse.cpp, cl_tent.cpp
void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal )
{	
	IMaterial* pMaterial = Draw_DecalMaterial( textureIndex );
	R_DecalShoot_( pMaterial, entity, model, position, saxis, flags, rgbaColor, pNormal );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *material - 
//			playerIndex - 
//			entity - 
//			*model - 
//			position - 
//			*saxis - 
//			flags - 
//			&rgbaColor - 
//-----------------------------------------------------------------------------

void R_PlayerDecalShoot( IMaterial *material, void *userdata, int entity, const model_t *model, 
	const Vector& position, const Vector *saxis, int flags, const color32 &rgbaColor )
{
	// The userdata that is passed in is actually 
	// the player number (integer), not sure why it can't be zero.
	Assert( userdata != 0 );

	//
	// Linear search through decal pool to retire any other decals this
	// player has sprayed.  It appears that multiple decals can be
	// allocated for a single spray due to the way they are mapped to
	// surfaces.  We need to run through and clean them all up.  This
	// seems like the cleanest way to manage this - especially since
	// it doesn't happen that often.
	//
	int i;
	CUtlVector<decal_t *> decalVec;

	for ( i = 0; i<s_aDecalPool.Count(); i++ )
	{
		decal_t * decal = s_aDecalPool[i];

		if( decal && (decal->flags & FDECAL_PLAYERSPRAY) && (decal->userdata == userdata) )
		{
			decalVec.AddToTail( decal );
		}
	}

	// remove all the sprays we found
	for ( i = 0; i < decalVec.Count(); i++ )
	{
		R_DecalUnlink( decalVec[i], host_state.worldbrush );
	}

	// set this to be a player spray so it is timed out appropriately.
	flags |= FDECAL_PLAYERSPRAY;

	R_DecalShoot_( material, entity, model, position, saxis, flags, rgbaColor, NULL, userdata );
}

struct decalcontext_t 
{
	Vector vModelOrg;
	Vector sAxis;
	float sOffset;
	Vector tAxis;
	float tOffset;
	float sScale;
	float tScale;
	IMatRenderContext *pRenderContext;
	SurfaceHandle_t pSurf;

	decalcontext_t( IMatRenderContext *pContext, const Vector &vModelorg )
	{
		pRenderContext = pContext;
		vModelOrg = vModelorg;
		pSurf = NULL;

	}

	void InitSurface( SurfaceHandle_t surfID )
	{
		if ( pSurf == surfID )
			return;
		pSurf = surfID;
		mtexinfo_t* pTexInfo = MSurf_TexInfo( surfID );

		int lightmapPageWidth, lightmapPageHeight;
		materials->GetLightmapPageSize( SortInfoToLightmapPage(MSurf_MaterialSortID( surfID )),&lightmapPageWidth, &lightmapPageHeight );

		sScale = 1.0f / float(lightmapPageWidth);
		tScale = 1.0f / float(lightmapPageHeight);
		msurfacelighting_t *pSurfacelighting = SurfaceLighting(surfID);
		sOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][3] - pSurfacelighting->m_LightmapMins[0] +
			pSurfacelighting->m_OffsetIntoLightmapPage[0] + 0.5f;
		tOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][3] - pSurfacelighting->m_LightmapMins[1] +
			pSurfacelighting->m_OffsetIntoLightmapPage[1] + 0.5f;
		sAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D();
		tAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D();

	}
	inline float ComputeS( const Vector &pos ) const
	{
		return sScale * (DotProduct(pos, sAxis) + sOffset);
	}
	inline float ComputeT( const Vector &pos ) const
	{
		return tScale * (DotProduct(pos, tAxis) + tOffset);
	}
};

// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf
static void R_DecalVertsLight( CDecalVert* v, const decalcontext_t &context, SurfaceHandle_t surfID, int vertCount )
{
	for ( int j = 0; j < vertCount; j++, v++ )
	{
		v->m_cLMCoords.x = context.ComputeS(v->m_vPos);
		v->m_cLMCoords.y = context.ComputeT(v->m_vPos);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Check for intersecting decals on this surface
// Input  : *psurf - 
//			*pcount - 
//			x - 
//			y - 
// Output : static decal_t
//-----------------------------------------------------------------------------
// UNDONE: This probably doesn't work quite right any more
// we should base overlap on the new decal basis matrix
// decal basis is constant per plane, perhaps we should store it (unscaled) in the shared plane struct
// BRJ: Note, decal basis is not constant when decals need to specify an s direction
// but that certainly isn't the majority case
static decal_t *R_DecalFindOverlappingDecals( decalinfo_t* decalinfo, SurfaceHandle_t surfID )
{
	decal_t		*plast = NULL;

	// (Same as R_SetupDecalClip).
	IMaterial	*pMaterial = decalinfo->m_pMaterial;

	int count = 0;
	
	// Precalculate the extents of decalinfo's decal in world space.
	int mapSize[2] = {pMaterial->GetMappingWidth(), pMaterial->GetMappingHeight()};
	Vector decalExtents[2];
	// this is half the width in world space of the decal. 
	float minProjectedWidth = (mapSize[0] / decalinfo->m_scale) * 0.5;
	decalExtents[0] = decalinfo->m_Basis[0] * minProjectedWidth;
	decalExtents[1] = decalinfo->m_Basis[1] * (mapSize[1] / decalinfo->m_scale) * 0.5f;

	float areaThreshold = r_decal_overlap_area.GetFloat();
	float lastArea = 0;
	bool bFullMatch = false;
	decal_t *pDecal = MSurf_DecalPointer( surfID );
	CUtlVectorFixedGrowable<decal_t *,32> coveredList;
	while ( pDecal ) 
	{
		pMaterial = pDecal->material;

		// Don't steal bigger decals and replace them with smaller decals
		// Don't steal permanent decals, or player sprays
		if ( !(pDecal->flags & FDECAL_PERMANENT) && 
			 !(pDecal->flags & FDECAL_PLAYERSPRAY) && pMaterial )
		{
			Vector testBasis[3];
			float testWorldScale[2];
			R_SetupDecalTextureSpaceBasis( pDecal, MSurf_Plane( surfID ).normal, pMaterial, testBasis, testWorldScale );

			// Here, we project the min and max extents of the decal that got passed in into
			// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were
			// clipping a triangle into pDecal's clip space.
			Vector2D vDecalMin(
				DotProduct( decalinfo->m_Position - decalExtents[0], testBasis[0] ) - pDecal->dx + 0.5f,
				DotProduct( decalinfo->m_Position - decalExtents[1], testBasis[1] ) - pDecal->dy + 0.5f );

			Vector2D vDecalMax( 
				DotProduct( decalinfo->m_Position + decalExtents[0], testBasis[0] ) - pDecal->dx + 0.5f,
				DotProduct( decalinfo->m_Position + decalExtents[1], testBasis[1] ) - pDecal->dy + 0.5f );	

			// Now figure out the part of the projection that intersects pDecal's
			// clip box [0,0,1,1].
			Vector2D vUnionMin( fpmax( vDecalMin.x, 0.0f ), fpmax( vDecalMin.y, 0.0f ) );
			Vector2D vUnionMax( fpmin( vDecalMax.x, 1.0f ), fpmin( vDecalMax.y, 1.0f ) );

			// if the decal is less than half the width of the one we're applying, don't test for overlap
			// test for complete coverage
			float projectWidthTestedDecal = pDecal->material->GetMappingWidth() / pDecal->scale;

			float sizex = vUnionMax.x - vUnionMin.x;
			float sizey = vUnionMax.y - vUnionMin.y;
			if( sizex >= 0 && sizey >= 0)
			{
				// Figure out how much of this intersects the (0,0) - (1,1) bbox.			
				float flArea = sizex * sizey;

				if ( projectWidthTestedDecal < minProjectedWidth )
				{
					if ( flArea > 0.999f )
					{
						coveredList.AddToTail(pDecal);
					}
				}
				else
				{
					if( flArea > areaThreshold )
					{
						// once you pass the threshold, scale the area by the decal size to select the largest 
						// decal above the threshold
						float flAreaScaled = flArea * projectWidthTestedDecal;
						count++;
						if ( !plast || flAreaScaled > lastArea ) 
						{
							plast = pDecal;
							lastArea =  flAreaScaled;
							// go ahead and remove even if you're not at the max overlap count yet because this is a very similar decal
							bFullMatch = ( flArea >= 0.9f ) ? true : false;
						}
					}
				}
			}
		}
		
		pDecal = pDecal->pnext;
	}
	
	if ( plast )
	{
		if ( count < r_decal_overlap_count.GetInt() && !bFullMatch )
		{
			plast = NULL;
		}
	}
	if ( coveredList.Count() > r_decal_cover_count.GetInt() )
	{
		int last = coveredList.Count() - r_decal_cover_count.GetInt();
		for ( int i = 0; i < last; i++ )
		{
			R_DecalUnlink( coveredList[i], host_state.worldbrush );
		}
	}

	return plast;
}


// Add the decal to the surface's list of decals.
// If the surface is a displacement, let the displacement precalculate data for the decal.
static void R_AddDecalToSurface( 
	decal_t *pdecal, 
	SurfaceHandle_t surfID,
	decalinfo_t *decalinfo )
{
	pdecal->pnext = NULL;
	decal_t *pold = MSurf_DecalPointer( surfID );
	if ( pold ) 
	{
		while ( pold->pnext ) 
			pold = pold->pnext;
		pold->pnext = pdecal;
	}
	else
	{
		MSurf_Decals( surfID ) = DecalToHandle(pdecal);
	}

	// Tag surface
	pdecal->surfID = surfID;
	pdecal->flSize = decalinfo->m_Size;
	pdecal->lightmapOffset = ComputeDecalLightmapOffset( surfID );
	// Let the dispinfo reclip the decal if need be.
	if( SurfaceHasDispInfo( surfID ) )
	{
		pdecal->m_DispDecal = MSurf_DispInfo( surfID )->NotifyAddDecal( pdecal, decalinfo->m_Size );
	}

	// Add surface to list.
	decalinfo->m_aApplySurfs.AddToTail( surfID );
}

//=============================================================================
//
// Decal batches for rendering.
//
CUtlVector<DecalSortVertexFormat_t>	g_aDecalFormats;

CUtlVector<DecalSortTrees_t>		g_aDecalSortTrees;
CUtlFixedLinkedList<decal_t*>		g_aDecalSortPool;
int									g_nDecalSortCheckCount;
int									g_nBrushModelDecalSortCheckCount;

CUtlFixedLinkedList<decal_t*>		g_aDispDecalSortPool;
CUtlVector<DecalSortTrees_t>		g_aDispDecalSortTrees;
int									g_nDispDecalSortCheckCount;

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void R_DecalSortInit( void )
{
	g_aDecalFormats.Purge();

	g_aDecalSortTrees.Purge();
	g_aDecalSortPool.Purge();
	g_aDecalSortPool.EnsureCapacity( g_nMaxDecals );
	g_aDecalSortPool.SetGrowSize( 128 );
	g_nDecalSortCheckCount = 0;
	g_nBrushModelDecalSortCheckCount = 0;

	g_aDispDecalSortTrees.Purge();
	g_aDispDecalSortPool.Purge();
	g_aDispDecalSortPool.EnsureCapacity( g_nMaxDecals );
	g_aDispDecalSortPool.SetGrowSize( 128 );
	g_nDispDecalSortCheckCount = 0;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DecalSurfacesInit( bool bBrushModel )
{
	if ( !bBrushModel )
	{
		// Only clear the pool once per frame.
		g_aDecalSortPool.RemoveAll();
		// Retire decals on opaque brushmodel surfaces
		R_DecalFlushDestroyList();
		++g_nDecalSortCheckCount;
	}
	else
	{
		++g_nBrushModelDecalSortCheckCount;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void R_DecalMaterialSort( decal_t *pDecal, SurfaceHandle_t surfID )
{
	// Setup the decal material sort data.
	DecalMaterialSortData_t sort;
	if ( pDecal->material->InMaterialPage() )
	{
		sort.m_pMaterial = pDecal->material->GetMaterialPage();
	}
	else
	{
		sort.m_pMaterial = pDecal->material;
	}
	sort.m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID;

	// Does this vertex type exist?
	VertexFormat_t vertexFormat = GetUncompressedFormat( sort.m_pMaterial );
	int iFormat = 0;
	int nFormatCount = g_aDecalFormats.Count();
	for ( ; iFormat < nFormatCount; ++iFormat )
	{
		if ( g_aDecalFormats[iFormat].m_VertexFormat == vertexFormat )
			break;
	}

	// A new vertex format type.
	if ( iFormat == nFormatCount )
	{
		iFormat = g_aDecalFormats.AddToTail();
		g_aDecalFormats[iFormat].m_VertexFormat = vertexFormat;
		int iSortTree = g_aDecalSortTrees.AddToTail();
		g_aDispDecalSortTrees.AddToTail();
		g_aDecalFormats[iFormat].m_iSortTree = iSortTree;
	}

	// Get an index for the current sort tree.
	int iSortTree = g_aDecalFormats[iFormat].m_iSortTree;
	int iTreeType = -1;

	// Lightmapped.
	if ( sort.m_pMaterial->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ) )
	{
		// Permanent lightmapped decals.
		if ( pDecal->flags & FDECAL_PERMANENT )
		{
			iTreeType = PERMANENT_LIGHTMAP;
		}
		// Non-permanent lightmapped decals.
		else
		{
			iTreeType = LIGHTMAP;
		}
	}
	// Non-lightmapped decals.
	else
	{
		iTreeType = NONLIGHTMAP;
		sort.m_iLightmapPage = -1;
	}

	int iSort = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Find( sort );
	if ( iSort == -1 )
	{
		int iBucket = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail();
		g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail();

		g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1;
		g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1;

		for ( int iGroup = 1; iGroup < ( MAX_MAT_SORT_GROUPS + 1 ); ++iGroup )
		{
			g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail();
			g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail();

			g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1;
			g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1;
		}
		
		sort.m_iBucket = iBucket;
		g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort );
		g_aDispDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort );

		pDecal->m_iSortTree = iSortTree;
		pDecal->m_iSortMaterial = sort.m_iBucket;
	}
	else
	{
		pDecal->m_iSortTree = iSortTree;
		pDecal->m_iSortMaterial = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Element( iSort ).m_iBucket;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void R_DecalReSortMaterials( void ) //X
{
	R_DecalSortInit();
	
	int nDecalCount = s_aDecalPool.Count();
	for ( int iDecal = 0; iDecal < nDecalCount; ++iDecal )
	{
		decal_t *pDecal = s_aDecalPool.Element( iDecal );
		if ( pDecal )
		{
			SurfaceHandle_t surfID = pDecal->surfID;
			R_DecalMaterialSort( pDecal, surfID );
		}
	}
}

// Allocate and initialize a decal from the pool, on surface with offsets x, y
// UNDONE: offsets are not really meaningful in new decal coordinate system
// the clipping code will recalc the offsets
static void R_DecalCreate( decalinfo_t* decalinfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement )
{
	decal_t			*pdecal;

	if( !IS_SURF_VALID( surfID ) )
	{
		ConMsg( "psurface NULL in R_DecalCreate!\n" );
		return;
	}
	
	decal_t *pold = R_DecalFindOverlappingDecals( decalinfo, surfID );
	if ( pold ) 
	{
		R_DecalUnlink( pold, host_state.worldbrush );
		pold = NULL;
	}

	pdecal = R_DecalAlloc( decalinfo->m_Flags );
	
	pdecal->flags = decalinfo->m_Flags;
	pdecal->color = decalinfo->m_Color;
	VectorCopy( decalinfo->m_Position, pdecal->position );
	if (pdecal->flags & FDECAL_USESAXIS)
		VectorCopy( decalinfo->m_SAxis, pdecal->saxis );
	pdecal->dx = x;
	pdecal->dy = y;
	pdecal->material = decalinfo->m_pMaterial;
	Assert( pdecal->material );
	pdecal->userdata = decalinfo->m_pUserData;

	// Set scaling
	pdecal->scale = decalinfo->m_scale;
	pdecal->entityIndex = decalinfo->m_Entity;

	// Get dynamic information from the material (fade start, fade time)
	if ( decalinfo->m_flFadeDuration > 0.0f )
	{
		pdecal->flags |= FDECAL_DYNAMIC;
		pdecal->fadeDuration = decalinfo->m_flFadeDuration;
		pdecal->fadeStartTime = decalinfo->m_flFadeStartTime;
		pdecal->fadeStartTime += cl.GetTime();
	}

	// check for a player spray
	if( pdecal->flags & FDECAL_PLAYERSPRAY )
	{
		// reset the number of rounds this should be visible for
		pdecal->fadeStartTime = 0.0f;

		// Force the scale to 1 for player sprays.
		pdecal->scale = 1.0f;
	}

	if( !bForceForDisplacement )
	{
		// Check to see if the decal actually intersects the surface
		// if not, then remove the decal
		R_DecalVertsClip( NULL, pdecal, surfID, decalinfo->m_pMaterial );
		if ( !pdecal->clippedVertCount )
		{
			R_DecalUnlink( pdecal, host_state.worldbrush );
			return;
		}
	}

	// Add to the surface's list
	R_AddDecalToSurface( pdecal, surfID, decalinfo );

	// Add decal material/lightmap to sort list.
	R_DecalMaterialSort( pdecal, surfID );
}

//-----------------------------------------------------------------------------
// Updates all decals, returns true if the decal should be retired
//-----------------------------------------------------------------------------

bool DecalUpdate( decal_t* pDecal )
{
	// retire the decal if it's time has come
	if (pDecal->fadeDuration > 0)
	{
		return (cl.GetTime() >= pDecal->fadeStartTime + pDecal->fadeDuration);
	}
	return false;
}

#define NEXT_MULTIPLE_OF_4(P)  ( ((P) + ((4)-1)) & (~((4)-1)) )

// Build the vertex list for a decal on a surface and clip it to the surface.
// This is a template so it can work on world surfaces and dynamic displacement 
// triangles the same way.
CDecalVert* R_DecalSetupVerts( decalcontext_t &context, decal_t *pDecal, SurfaceHandle_t surfID, IMaterial *pMaterial )
{
	//
	// Do not scale playersprays
	//
	if( pDecal->flags & FDECAL_DISTANCESCALE )
	{
		if( !(pDecal->flags & FDECAL_PLAYERSPRAY) )
		{
			float scaleFactor = 1.0f;
			float nearScale, farScale, nearDist, farDist;

			nearScale = r_dscale_nearscale.GetFloat();
			nearDist = r_dscale_neardist.GetFloat();
			farScale = r_dscale_farscale.GetFloat();
			farDist = r_dscale_fardist.GetFloat();

			Vector playerOrigin = CurrentViewOrigin();

			float dist = 0;
			
			if ( pDecal->entityIndex == 0 )
			{
				dist = (playerOrigin - pDecal->position).Length();
			}
			else
			{
				Vector worldSpaceCenter;
				Vector3DMultiplyPosition(g_BrushToWorldMatrix, pDecal->position, worldSpaceCenter );
				dist = (playerOrigin - worldSpaceCenter).Length();
			}
			float fov = g_EngineRenderer->GetFov();

			//
			// If the player is zoomed in, we adjust the nearScale and farScale
			//
			if ( fov != r_dscale_basefov.GetFloat() && fov > 0 && r_dscale_basefov.GetFloat() > 0 )
			{
				float fovScale = fov / r_dscale_basefov.GetFloat();
				nearScale *= fovScale;
				farScale *= fovScale;

				if ( nearScale < 1.0f )
					nearScale = 1.0f;
				if ( farScale < 1.0f )
					farScale = 1.0f;
			}

			//
			// Scaling works like this:
			// 
			// 0->nearDist             scale = 1.0
			// nearDist -> farDist     scale = LERP(nearScale, farScale)
			// farDist->inf            scale = farScale
			//
			// scaling in the rest of the code appears to be more of an
			// attenuation factor rather than a scale, so we compute 1/scale
			// to account for this.
			//
			if ( dist < nearDist )
				scaleFactor = 1.0f;
			else if( dist >= farDist )
				scaleFactor = farScale;
			else
			{
				float percent = (dist - nearDist) / (farDist - nearDist);
				scaleFactor = nearScale + percent * (farScale - nearScale);
			}

			//
			// scaling in the rest of the code appears to be more of an
			// attenuation factor rather than a scale, so we compute 1/scale
			// to account for this.
			//
			scaleFactor = 1.0f / scaleFactor;
			float originalScale = pDecal->scale;
			float scaledScale = pDecal->scale * scaleFactor;
			pDecal->scale = scaledScale;

			context.InitSurface( pDecal->surfID );

			CDecalVert *v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial );
			if ( v )
			{
				R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount );
			}
			pDecal->scale = originalScale;
			return v;
		}
	}

	// find in cache?
	CDecalVert *v = g_DecalVertCache.GetCachedVerts(pDecal);
	if ( !v )
	{
		// not in cache, clip & light
		context.InitSurface( pDecal->surfID );
		v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial );
		if ( pDecal->clippedVertCount )
		{

#if _DEBUG
			// squash vector copy asserts in debug
			int nextVert = NEXT_MULTIPLE_OF_4(pDecal->clippedVertCount);
			if ( (nextVert - pDecal->clippedVertCount) < 4 )
			{
				for ( int i = pDecal->clippedVertCount; i < nextVert; i++ )
				{
					v[i].m_cLMCoords.Init();
					v[i].m_ctCoords.Init();
					v[i].m_vPos.Init();
				}
			}
#endif
			R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount );
			g_DecalVertCache.StoreVertsInCache( pDecal, v );
		}
	}
	return v;
}


//-----------------------------------------------------------------------------
// Renders a single decal, *could retire the decal!!*
//-----------------------------------------------------------------------------

void DecalUpdateAndDrawSingle( decalcontext_t &context, SurfaceHandle_t surfID, decal_t* pDecal )
{
	if( !pDecal->material )
		return;

	// Update dynamic decals
	bool retire = false;
	if ( pDecal->flags & FDECAL_DYNAMIC )
		retire = DecalUpdate( pDecal );

	if( SurfaceHasDispInfo( surfID ) )
	{
		// Dispinfos generate lists of tris for decals when the decal is first
		// created.
	}
	else
	{
		CDecalVert *v = R_DecalSetupVerts( context, pDecal, surfID, pDecal->material );

		if ( v )
			Shader_DecalDrawPoly( v, pDecal->material, surfID, pDecal->clippedVertCount, pDecal, 1.0f );
	}

	if( retire )
	{
		R_DecalUnlink( pDecal, host_state.worldbrush );
	}
}


//-----------------------------------------------------------------------------
// Renders all decals on a single surface
//-----------------------------------------------------------------------------

void DrawDecalsOnSingleSurface_NonQueued( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID, const Vector &vModelOrg)
{
	decal_t* plist = MSurf_DecalPointer( surfID );
	decalcontext_t context(pRenderContext, vModelOrg);
	context.InitSurface(surfID);
	while ( plist ) 
	{
		// Store off the next pointer, DecalUpdateAndDrawSingle could unlink
		decal_t* pnext = plist->pnext;

		if (!(plist->flags & FDECAL_SECONDPASS))
		{
			DecalUpdateAndDrawSingle( context, surfID, plist );
		}
		plist = pnext;
	}
	while ( plist ) 
	{
		// Store off the next pointer, DecalUpdateAndDrawSingle could unlink
		decal_t* pnext = plist->pnext;

		if ((plist->flags & FDECAL_SECONDPASS))
		{
			DecalUpdateAndDrawSingle( context, surfID, plist );
		}
		plist = pnext;
	}
}

void DrawDecalsOnSingleSurface_QueueHelper( SurfaceHandle_t surfID, Vector vModelOrg )
{
	CMatRenderContextPtr pRenderContext( materials );
	DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, vModelOrg );
}

void DrawDecalsOnSingleSurface( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID )
{
	ICallQueue *pCallQueue;
	if ( r_queued_decals.GetBool() && (pCallQueue = pRenderContext->GetCallQueue()) != NULL )
	{
		//queue available and desired
		pCallQueue->QueueCall( DrawDecalsOnSingleSurface_QueueHelper, surfID, modelorg );
	}
	else
	{
		//non-queued mode
		DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, modelorg );
	}
}

void R_DrawDecalsAllImmediate_GatherDecals( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, CUtlVector<decal_t *> &DrawDecals )
{
	int nCheckCount = g_nDecalSortCheckCount;
	if ( iGroup == MAX_MAT_SORT_GROUPS )
	{
		// Brush Model
		nCheckCount = g_nBrushModelDecalSortCheckCount;
	}

	int nSortTreeCount = g_aDecalSortTrees.Count();
	for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree )
	{
		int nBucketCount = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Count();
		for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket )
		{
			if ( g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount != nCheckCount )
				continue;

			intp iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead;

			intp iElement = iHead;
			while ( iElement != g_aDecalSortPool.InvalidIndex() )
			{
				decal_t *pDecal = g_aDecalSortPool.Element( iElement );
				iElement = g_aDecalSortPool.Next( iElement );

				if ( !pDecal )
					continue;

				DrawDecals.AddToTail( pDecal );
			}
		}
	}
}

void R_DrawDecalsAllImmediate_Gathered( IMatRenderContext *pRenderContext, decal_t **ppDecals, int iDecalCount, const Vector &vModelOrg, float flFade )
{
	SurfaceHandle_t lastSurf = NULL;
	decalcontext_t context( pRenderContext, vModelOrg );
	bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2);
	for( int i = 0; i != iDecalCount; ++i )
	{
		decal_t * pDecal = ppDecals[i];

		Assert( pDecal != NULL );

		// Add the decal to the list of decals to be destroyed if need be.
		if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) )
		{
			pDecal->flags |= FDECAL_HASUPDATED;
			if ( DecalUpdate( pDecal ) )
			{
				R_DecalAddToDestroyList( pDecal );
				continue;
			}
		}

		if ( pDecal->surfID != lastSurf )
		{
			lastSurf = pDecal->surfID;
		}
		CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material );
		if ( !pVerts )
			continue;
		int nCount = pDecal->clippedVertCount;

		// Bind texture.
		VertexFormat_t vertexFormat = 0;
		if( bWireframe )
		{
			pRenderContext->Bind( g_materialDecalWireframe );
		}
		else
		{
			pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( pDecal->surfID )].lightmapPageID );
			pRenderContext->Bind( pDecal->material, pDecal->userdata );
			vertexFormat = GetUncompressedFormat( pDecal->material );
		}

		IMesh *pMesh = NULL;
		pMesh = pRenderContext->GetDynamicMesh();
		CMeshBuilder meshBuilder;
		meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nCount, ( ( nCount - 2 ) * 3 ) );

		// Set base color.
		byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a };
		if ( flFade != 1.0f )
		{
			color[3] = (byte)( color[3] * flFade );
		}

		// Dynamic decals - fading.
		if ( pDecal->flags & FDECAL_DYNAMIC )
		{
			float flFadeValue;

			// Negative fadeDuration value means to fade in
			if ( pDecal->fadeDuration < 0 )
			{
				flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
			}
			else
			{
				flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
			}

			flFadeValue = clamp( flFadeValue, 0.0f, 1.0f );

			color[3] = ( byte )( color[3] * flFadeValue );
		}

		// Compute normal and tangent space if necessary.
		Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f );
		if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) )
		{
			vecNormal = MSurf_Plane( pDecal->surfID ).normal;
			if ( vertexFormat & VERTEX_TANGENT_SPACE )
			{
				Vector tVect;
				bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect );
				TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate );
			}
		}

		// Setup verts.
		float flOffset = pDecal->lightmapOffset;
		for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts )
		{
			meshBuilder.Position3fv( pVerts->m_vPos.Base() );
			if ( vertexFormat & VERTEX_NORMAL )
			{
				meshBuilder.Normal3fv( vecNormal.Base() );
			}
			meshBuilder.Color4ubv( color );
			meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y  );
			meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x,  pVerts->m_cLMCoords.y );
			meshBuilder.TexCoord1f( 2, flOffset );
			if ( vertexFormat & VERTEX_TANGENT_SPACE )
			{
				meshBuilder.TangentS3fv( vecTangentS.Base() ); 
				meshBuilder.TangentT3fv( vecTangentT.Base() ); 
			}

			meshBuilder.AdvanceVertexF<VTX_HAVEPOS|VTX_HAVENORMAL|VTX_HAVECOLOR,3>();
		}

		// Setup indices.
		CIndexBuilder &indexBuilder = meshBuilder;
		indexBuilder.FastPolygon( 0, nCount - 2 );

		meshBuilder.End();
		pMesh->Draw();
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void R_DrawDecalsAllImmediate( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade )
{
	SurfaceHandle_t lastSurf = NULL;
	decalcontext_t context(pRenderContext, vModelOrg);
	int nSortTreeCount = g_aDecalSortTrees.Count();
	bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2);
	for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree )
	{
		int nBucketCount = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Count();
		for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket )
		{
			if ( g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount != nCheckCount )
				continue;

			intp iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead;

			int nCount;
			intp iElement = iHead;
			while ( iElement != g_aDecalSortPool.InvalidIndex() )
			{
				decal_t *pDecal = g_aDecalSortPool.Element( iElement );
				iElement = g_aDecalSortPool.Next( iElement );
				
				if ( !pDecal )
					continue;

				// Add the decal to the list of decals to be destroyed if need be.
				if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) )
				{
					pDecal->flags |= FDECAL_HASUPDATED;
					if ( DecalUpdate( pDecal ) )
					{
						R_DecalAddToDestroyList( pDecal );
						continue;
					}
				}

				if ( pDecal->surfID != lastSurf )
				{
					lastSurf = pDecal->surfID;
				}
				CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material );
				if ( !pVerts )
					continue;
				nCount = pDecal->clippedVertCount;

				// Bind texture.
				VertexFormat_t vertexFormat = 0;
				if( bWireframe )
				{
					pRenderContext->Bind( g_materialDecalWireframe );
				}
				else
				{
					pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( pDecal->surfID )].lightmapPageID );
					pRenderContext->Bind( pDecal->material, pDecal->userdata );
					vertexFormat = GetUncompressedFormat( pDecal->material );
				}

				IMesh *pMesh = NULL;
				pMesh = pRenderContext->GetDynamicMesh();
				CMeshBuilder meshBuilder;
				meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nCount, ( ( nCount - 2 ) * 3 ) );

				// Set base color.
				byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a };
				if ( flFade != 1.0f )
				{
					color[3] = (byte)( color[3] * flFade );
				}
				
				// Dynamic decals - fading.
				if ( pDecal->flags & FDECAL_DYNAMIC )
				{
					float flFadeValue;
					
					// Negative fadeDuration value means to fade in
					if ( pDecal->fadeDuration < 0 )
					{
						flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
					}
					else
					{
						flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
					}
					
					flFadeValue = clamp( flFadeValue, 0.0f, 1.0f );
					
					color[3] = ( byte )( color[3] * flFadeValue );
				}
				
				// Compute normal and tangent space if necessary.
				Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f );
				if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) )
				{
					vecNormal = MSurf_Plane( pDecal->surfID ).normal;
					if ( vertexFormat & VERTEX_TANGENT_SPACE )
					{
						Vector tVect;
						bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect );
						TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate );
					}
				}
				
				// Setup verts.
				float flOffset = pDecal->lightmapOffset;
				for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts )
				{
					meshBuilder.Position3fv( pVerts->m_vPos.Base() );
					if ( vertexFormat & VERTEX_NORMAL )
					{
						meshBuilder.Normal3fv( vecNormal.Base() );
					}
					meshBuilder.Color4ubv( color );
					meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y  );
					meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x,  pVerts->m_cLMCoords.y );
					meshBuilder.TexCoord1f( 2, flOffset );
					if ( vertexFormat & VERTEX_TANGENT_SPACE )
					{
						meshBuilder.TangentS3fv( vecTangentS.Base() ); 
						meshBuilder.TangentT3fv( vecTangentT.Base() ); 
					}
					
					meshBuilder.AdvanceVertexF<VTX_HAVEPOS|VTX_HAVENORMAL|VTX_HAVECOLOR,3>();
				}

				// Setup indices.
				int nTriCount = ( nCount - 2 );
				CIndexBuilder &indexBuilder = meshBuilder;
				indexBuilder.FastPolygon( 0, nTriCount );

				meshBuilder.End();
				pMesh->Draw();
			}
		}
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
inline void R_DrawDecalMeshList( DecalMeshList_t &meshList )
{
	CMatRenderContextPtr pRenderContext( materials );

	int nBatchCount = meshList.m_aBatches.Count();
	for ( int iBatch = 0; iBatch < nBatchCount; ++iBatch )
	{
		if ( g_pMaterialSystemConfig->nFullbright == 1 )
		{
			pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE );
		}
		else
		{
			pRenderContext->BindLightmapPage( meshList.m_aBatches[iBatch].m_iLightmapPage );
		}
		
		pRenderContext->Bind( meshList.m_aBatches[iBatch].m_pMaterial, meshList.m_aBatches[iBatch].m_pProxy );
		meshList.m_pMesh->Draw( meshList.m_aBatches[iBatch].m_iStartIndex, meshList.m_aBatches[iBatch].m_nIndexCount );
	}
}

#define DECALMARKERS_SWITCHSORTTREE ((decal_t *)0x00000000)
#define DECALMARKERS_SWITCHBUCKET	((decal_t *)-1)
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void R_DrawDecalsAll_GatherDecals( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, CUtlVector<decal_t *> &DrawDecals )
{
	int nCheckCount = g_nDecalSortCheckCount;
	if ( iGroup == MAX_MAT_SORT_GROUPS )
	{
		// Brush Model
		nCheckCount = g_nBrushModelDecalSortCheckCount;
	}

	int nSortTreeCount = g_aDecalSortTrees.Count();
	for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree )
	{		
		DrawDecals.AddToTail( DECALMARKERS_SWITCHSORTTREE );

		DecalSortTrees_t &sortTree = g_aDecalSortTrees[iSortTree];
		int nBucketCount = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Count();
		for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket )
		{
			DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket );
			if ( bucket.m_nCheckCount != nCheckCount )
				continue;

			intp iHead = bucket.m_iHead;
			if ( !g_aDecalSortPool.IsValidIndex( iHead ) )
				continue;

			decal_t *pDecalHead = g_aDecalSortPool.Element( iHead );
			Assert( pDecalHead->material );
			if ( !pDecalHead->material )
				continue;

			// Vertex format.
			VertexFormat_t vertexFormat = GetUncompressedFormat( pDecalHead->material );
			if ( vertexFormat == 0 )
				continue;

			DrawDecals.AddToTail( DECALMARKERS_SWITCHBUCKET );

			intp iElement = iHead;
			while ( iElement != g_aDecalSortPool.InvalidIndex() )
			{
				decal_t *pDecal = g_aDecalSortPool.Element( iElement );
				iElement = g_aDecalSortPool.Next( iElement );

				if ( !pDecal )
					continue;

				DrawDecals.AddToTail( pDecal );
			}			
		}
	}	
}

void R_DecalsGetMaxMesh( IMatRenderContext *pRenderContext, int &nDecalSortMaxVerts, int &nDecalSortMaxIndices )
{
	nDecalSortMaxVerts = g_nMaxDecals * 5;
	nDecalSortMaxIndices = nDecalSortMaxVerts * 3;
	int nMaxIndices = pRenderContext->GetMaxIndicesToRender();
	nDecalSortMaxIndices = MIN(nDecalSortMaxIndices, nMaxIndices);
	nDecalSortMaxVerts = MIN(nDecalSortMaxVerts, 8192); // just a guess, you should be able to do 8K dynamic verts in any material and this is no big loss on batching
}

void R_DrawDecalsAll_Gathered( IMatRenderContext *pRenderContext, decal_t **ppDecals, int iDecalCount, const Vector &vModelOrg, float flFade )
{
	DecalMeshList_t		meshList;
	CMeshBuilder		meshBuilder;

	int nVertCount = 0;
	int nIndexCount = 0;

	int nCount;

	int nDecalSortMaxVerts;
	int nDecalSortMaxIndices;
	R_DecalsGetMaxMesh( pRenderContext, nDecalSortMaxVerts, nDecalSortMaxIndices );

	bool bMeshInit = true;
	bool bBatchInit = true;
	
	DecalBatchList_t *pBatch = NULL;
	VertexFormat_t vertexFormat = 0;
	decal_t *pDecalHead = NULL;
	SurfaceHandle_t lastSurf = NULL;
	decalcontext_t context(pRenderContext, vModelOrg);
	bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2);

	for( int i = 0; i != iDecalCount; ++i )
	{
		decal_t *pDecal = ppDecals[i];
		if( (pDecal == DECALMARKERS_SWITCHSORTTREE) || (pDecal == DECALMARKERS_SWITCHBUCKET) )
		{
			if ( pBatch )
			{
				pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex );
			}

			if ( pDecal == DECALMARKERS_SWITCHSORTTREE )
			{
				if ( !bMeshInit )
				{
					meshBuilder.End();
					R_DrawDecalMeshList( meshList );
					bMeshInit = true;
				}
			}

			bBatchInit = true;
			pBatch = NULL;

			if ( pDecal == DECALMARKERS_SWITCHBUCKET )
			{
				//find the new head decal
				for( int j = i + 1; j != iDecalCount; ++j )
				{
					pDecalHead = ppDecals[j];
					if( (pDecalHead != DECALMARKERS_SWITCHSORTTREE) && (pDecalHead != DECALMARKERS_SWITCHBUCKET) )
						break;
				}

				vertexFormat = GetUncompressedFormat( pDecalHead->material );
			}

			continue;
		}

		// Add the decal to the list of decals to be destroyed if need be.
		if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) )
		{
			pDecal->flags |= FDECAL_HASUPDATED;
			if ( DecalUpdate( pDecal ) )
			{
				R_DecalAddToDestroyList( pDecal );
				continue;
			}
		}

		if ( pDecal->surfID != lastSurf )
		{
			lastSurf = pDecal->surfID;
		}
		CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material );
		if ( !pVerts )
			continue;
		nCount = pDecal->clippedVertCount;

		// Overflow - new mesh, batch.
		if ( ( ( nVertCount + nCount ) > nDecalSortMaxVerts ) || ( nIndexCount + ( nCount - 2 ) > nDecalSortMaxIndices ) )
		{
			// Finish this batch.
			if ( pBatch )
			{
				pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex );
			}

			// End the mesh building phase and render.
			meshBuilder.End();
			R_DrawDecalMeshList( meshList );

			// Reset.
			bMeshInit = true;
			pBatch = NULL;
			bBatchInit = true;
		}

		// Create the mesh.
		if ( bMeshInit )
		{
			// Reset the mesh list.
			meshList.m_pMesh = NULL;
			meshList.m_aBatches.RemoveAll();

			// Create a mesh for this vertex format (vertex format per SortTree).
			if ( bWireframe )
			{
				meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialDecalWireframe );
			}
			else
			{
				meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pDecalHead->material );
			}
			meshBuilder.Begin( meshList.m_pMesh, MATERIAL_TRIANGLES, nDecalSortMaxVerts, nDecalSortMaxIndices );

			nVertCount = 0;
			nIndexCount = 0;

			bMeshInit = false;
		}

		// Create the batch.
		if ( bBatchInit )
		{
			// Create a batch for this bucket = material/lightmap pair.
			// Todo: we also could flush it right here and continue.
			if ( meshList.m_aBatches.Size() + 1 > meshList.m_aBatches.NumAllocated() )
			{
				Warning( "R_DrawDecalsAll: overflowing m_aBatches. Reduce %d decals in the scene.\n", nDecalSortMaxVerts * meshList.m_aBatches.NumAllocated() );
				meshBuilder.End();
				R_DrawDecalMeshList( meshList );
				return;
			}

			int iBatchList = meshList.m_aBatches.AddToTail();
			pBatch = &meshList.m_aBatches[iBatchList];
			pBatch->m_iStartIndex = nIndexCount;

			if ( bWireframe )
			{
				pBatch->m_pMaterial = g_materialDecalWireframe;
			}
			else
			{
				pBatch->m_pMaterial = pDecalHead->material;
				pBatch->m_pProxy = pDecalHead->userdata;
				pBatch->m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( pDecalHead->surfID )].lightmapPageID;
			}

			bBatchInit = false;
		}
		Assert ( pBatch );

		// Set base color.
		byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a };
		if ( flFade != 1.0f )
		{
			color[3] = (byte)( color[3] * flFade );
		}

		// Dynamic decals - fading.
		if ( pDecal->flags & FDECAL_DYNAMIC )
		{
			float flFadeValue;

			// Negative fadeDuration value means to fade in
			if ( pDecal->fadeDuration < 0 )
			{
				flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
			}
			else
			{
				flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
			}

			flFadeValue = clamp( flFadeValue, 0.0f, 1.0f );

			color[3] = ( byte )( color[3] * flFadeValue );
		}

		// Compute normal and tangent space if necessary.
		Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f );
		if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) )
		{
			vecNormal = MSurf_Plane( pDecal->surfID ).normal;
			if ( vertexFormat & VERTEX_TANGENT_SPACE )
			{
				Vector tVect;
				bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect );
				TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate );
			}
		}

		// Setup verts.
		float flOffset = pDecal->lightmapOffset;

		for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts )
		{
			meshBuilder.Position3fv( pVerts->m_vPos.Base() );
			if ( vertexFormat & VERTEX_NORMAL )
			{
				meshBuilder.Normal3fv( vecNormal.Base() );
			}
			meshBuilder.Color4ubv( color );
			meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y  );
			meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y );
			meshBuilder.TexCoord1f( 2, flOffset );
			if ( vertexFormat & VERTEX_TANGENT_SPACE )
			{
				meshBuilder.TangentS3fv( vecTangentS.Base() ); 
				meshBuilder.TangentT3fv( vecTangentT.Base() ); 
			}

			meshBuilder.AdvanceVertex();
		}

		// Setup indices.
		int nTriCount = ( nCount - 2 );
		CIndexBuilder &indexBuilder = meshBuilder;
		indexBuilder.FastPolygon( nVertCount, nTriCount );

		// Update counters.
		nVertCount += nCount;
		nIndexCount += ( nTriCount * 3 );
	}

	if ( pBatch )
	{
		pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex );
	}

	if ( !bMeshInit )
	{
		meshBuilder.End();
		R_DrawDecalMeshList( meshList );
	}
}


void R_DrawDecalsAll( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade )
{
	DecalMeshList_t		meshList;
	CMeshBuilder		meshBuilder;
	SurfaceHandle_t lastSurf = NULL;
	decalcontext_t context(pRenderContext, vModelOrg);
	Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f );

	int nVertCount = 0;
	int nIndexCount = 0;

	int nDecalSortMaxVerts;
	int nDecalSortMaxIndices;
	R_DecalsGetMaxMesh( pRenderContext, nDecalSortMaxVerts, nDecalSortMaxIndices );

	bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2);
	float localClientTime = cl.GetTime();

	int nSortTreeCount = g_aDecalSortTrees.Count();

	for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree )
	{		
		// Reset the mesh list.
		bool bMeshInit = true;

		DecalSortTrees_t &sortTree = g_aDecalSortTrees[iSortTree];
		int nBucketCount = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Count();
		for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket )
		{
			DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket );
			if ( bucket.m_nCheckCount != nCheckCount )
				continue;
			
			intp iHead = bucket.m_iHead;
			if ( !g_aDecalSortPool.IsValidIndex( iHead ) )
				continue;

			decal_t *pDecalHead = g_aDecalSortPool.Element( iHead );
			Assert( pDecalHead->material );
			if ( !pDecalHead->material )
				continue;

			// Vertex format.
			VertexFormat_t vertexFormat = GetUncompressedFormat( pDecalHead->material );
			if ( vertexFormat == 0 )
				continue;

			// New bucket = new batch.
			DecalBatchList_t *pBatch = NULL;
			bool bBatchInit = true;
			
			int nCount;
			intp iElement = iHead;
			while ( iElement != g_aDecalSortPool.InvalidIndex() )
			{
				decal_t *pDecal = g_aDecalSortPool.Element( iElement );
				iElement = g_aDecalSortPool.Next( iElement );

				if ( !pDecal || !pDecal->surfID )
					continue;

				// Add the decal to the list of decals to be destroyed if need be.
				if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) )
				{
					pDecal->flags |= FDECAL_HASUPDATED;
					if ( DecalUpdate( pDecal ) )
					{
						R_DecalAddToDestroyList( pDecal );
						continue;
					}
				}

				float flOffset = 0;
				if ( pDecal->surfID != lastSurf )
				{
					lastSurf = pDecal->surfID;
					flOffset = pDecal->lightmapOffset;
					// Compute normal and tangent space if necessary.
					if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) )
					{
						vecNormal = MSurf_Plane( pDecal->surfID ).normal;
						if ( vertexFormat & VERTEX_TANGENT_SPACE )
						{
							Vector tVect;
							bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect );
							TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate );
						}
					}
				}
				CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material );
				if ( !pVerts )
					continue;
				nCount = pDecal->clippedVertCount;
				
				// Overflow - new mesh, batch.
				if ( ( ( nVertCount + nCount ) > nDecalSortMaxVerts ) || ( nIndexCount + ( nCount - 2 ) > nDecalSortMaxIndices ) )
				{
					// Finish this batch.
					if ( pBatch )
					{
						pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex );
					}

					// End the mesh building phase and render.
					meshBuilder.End();
					R_DrawDecalMeshList( meshList );

					// Reset.
					bMeshInit = true;
					pBatch = NULL;
					bBatchInit = true;
				}
				
				// Create the mesh.
				if ( bMeshInit )
				{
					// Reset the mesh list.
					meshList.m_pMesh = NULL;
					meshList.m_aBatches.RemoveAll();

					// Create a mesh for this vertex format (vertex format per SortTree).
					if ( bWireframe )
					{
						meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialDecalWireframe );
					}
					else
					{
						meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pDecalHead->material );
					}
					meshBuilder.Begin( meshList.m_pMesh, MATERIAL_TRIANGLES, nDecalSortMaxVerts, nDecalSortMaxIndices );
					
					nVertCount = 0;
					nIndexCount = 0;
					
					bMeshInit = false;
				}
				// Create the batch.
				if ( bBatchInit )
				{
					// Create a batch for this bucket = material/lightmap pair.
					// Todo: we also could flush it right here and continue.
					if ( meshList.m_aBatches.Size() + 1 > meshList.m_aBatches.NumAllocated() )
					{
						Warning( "R_DrawDecalsAll: overflowing m_aBatches. Reduce %d decals in the scene.\n", nDecalSortMaxVerts * meshList.m_aBatches.NumAllocated() );
						meshBuilder.End();
						R_DrawDecalMeshList( meshList );
						return;
					}

					int iBatchList = meshList.m_aBatches.AddToTail();
					pBatch = &meshList.m_aBatches[iBatchList];
					pBatch->m_iStartIndex = nIndexCount;
					
					if ( bWireframe )
					{
						pBatch->m_pMaterial = g_materialDecalWireframe;
					}
					else
					{
						pBatch->m_pMaterial = pDecalHead->material;
						pBatch->m_pProxy = pDecalHead->userdata;
						pBatch->m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( pDecalHead->surfID )].lightmapPageID;
					}
											
					bBatchInit = false;
				}
				Assert ( pBatch );
				
				// Set base color.
				byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a };
				if ( flFade != 1.0f )
				{
					color[3] = (byte)( color[3] * flFade );
				}
				
				// Dynamic decals - fading.
				if ( pDecal->flags & FDECAL_DYNAMIC )
				{
					float flFadeValue;
					
					// Negative fadeDuration value means to fade in
					if ( pDecal->fadeDuration < 0 )
					{
						flFadeValue = -( localClientTime - pDecal->fadeStartTime ) / pDecal->fadeDuration;
					}
					else
					{
						flFadeValue = 1.0 - ( localClientTime - pDecal->fadeStartTime ) / pDecal->fadeDuration;
					}
					
					flFadeValue = clamp( flFadeValue, 0.0f, 1.0f );
					
					color[3] = ( byte )( color[3] * flFadeValue );
				}

				// Setup verts.
				for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts )
				{
					meshBuilder.Position3fv( pVerts->m_vPos.Base() );
					if ( vertexFormat & VERTEX_NORMAL )
					{
						meshBuilder.Normal3fv( vecNormal.Base() );
					}
					meshBuilder.Color4ubv( color );
					meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y  );
					meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y );
					meshBuilder.TexCoord1f( 2, flOffset );
					if ( vertexFormat & VERTEX_TANGENT_SPACE )
					{
						meshBuilder.TangentS3fv( vecTangentS.Base() ); 
						meshBuilder.TangentT3fv( vecTangentT.Base() ); 
					}
					
					meshBuilder.AdvanceVertexF<VTX_HAVEALL, 3>();
				}
				
				// Setup indices.
				int nTriCount = nCount - 2;
				CIndexBuilder &indexBuilder = meshBuilder;
				indexBuilder.FastPolygon( nVertCount, nTriCount );
				
				// Update counters.
				nVertCount += nCount;
				nIndexCount += ( nTriCount * 3 );
			}
			
			if ( pBatch )
			{
				pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex );
			}
		}
		
		if ( !bMeshInit )
		{
			meshBuilder.End();
			R_DrawDecalMeshList( meshList );
		}
	}	
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DecalSurfaceDraw_NonQueued( IMatRenderContext *pRenderContext, int renderGroup, const Vector &vModelOrg, int nCheckCount, float flFade )
{
	if ( r_drawbatchdecals.GetBool() )
	{
		// Draw world decals.
		R_DrawDecalsAll( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, vModelOrg, nCheckCount, flFade );
		
		// Draw lightmapped non-world decals.
		R_DrawDecalsAll( pRenderContext, renderGroup, LIGHTMAP, vModelOrg, nCheckCount, flFade );
		
		// Draw non-lit(mod2x) decals.
		R_DrawDecalsAll( pRenderContext, renderGroup, NONLIGHTMAP, vModelOrg, nCheckCount, flFade );
	}
	else
	{
		// Draw world decals.
		R_DrawDecalsAllImmediate( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, vModelOrg, nCheckCount, flFade );
		
		// Draw lightmapped non-world decals.
		R_DrawDecalsAllImmediate( pRenderContext, renderGroup, LIGHTMAP, vModelOrg, nCheckCount, flFade );

		// Draw non-lit(mod2x) decals.
		R_DrawDecalsAllImmediate( pRenderContext, renderGroup, NONLIGHTMAP, vModelOrg, nCheckCount, flFade );
	}
}

void DecalSurfaceDraw_QueueHelper( bool bBatched, int renderGroup, Vector vModelOrg, int nCheckCount, decal_t **ppDecals, int iPermanentLightmap, int iLightmap, int iNonLightmap, float flFade )
{
	CMatRenderContextPtr pRenderContext( materials );

	if( bBatched )
	{
		R_DrawDecalsAll_Gathered( pRenderContext, ppDecals, iPermanentLightmap, vModelOrg, flFade );
		ppDecals += iPermanentLightmap;
		R_DrawDecalsAll_Gathered( pRenderContext, ppDecals, iLightmap, vModelOrg, flFade );
		ppDecals += iLightmap;
		R_DrawDecalsAll_Gathered( pRenderContext, ppDecals, iNonLightmap, vModelOrg, flFade );
	}
	else
	{
		R_DrawDecalsAllImmediate_Gathered( pRenderContext, ppDecals, iPermanentLightmap, vModelOrg, flFade );
		ppDecals += iPermanentLightmap;
		R_DrawDecalsAllImmediate_Gathered( pRenderContext, ppDecals, iLightmap, vModelOrg, flFade );
		ppDecals += iLightmap;
		R_DrawDecalsAllImmediate_Gathered( pRenderContext, ppDecals, iNonLightmap, vModelOrg, flFade );
	}
}

void DecalSurfaceDraw( IMatRenderContext *pRenderContext, int renderGroup, float flFade )
{
	//	VPROF_BUDGET( "Decals", "Decals" );
	VPROF( "DecalsDraw" );

	if( !r_drawdecals.GetBool() )
	{
		return;
	}

	int nCheckCount = g_nDecalSortCheckCount;
	if ( renderGroup == MAX_MAT_SORT_GROUPS )
	{
		// Brush Model
		nCheckCount = g_nBrushModelDecalSortCheckCount;
	}

	ICallQueue *pCallQueue;
	if( r_queued_decals.GetBool() && (pCallQueue = pRenderContext->GetCallQueue()) != NULL )
	{
		//queue available and desired
		bool bBatched = r_drawbatchdecals.GetBool();
		static CUtlVector<decal_t *> DrawDecals;

		int iPermanentLightmap, iLightmap, iNonLightmap;
		if( bBatched )
		{
			R_DrawDecalsAll_GatherDecals( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, DrawDecals );
			iPermanentLightmap = DrawDecals.Count();
			R_DrawDecalsAll_GatherDecals( pRenderContext, renderGroup, LIGHTMAP, DrawDecals );
			iLightmap = DrawDecals.Count() - iPermanentLightmap;
			R_DrawDecalsAll_GatherDecals( pRenderContext, renderGroup, NONLIGHTMAP, DrawDecals );
			iNonLightmap = DrawDecals.Count() - (iPermanentLightmap + iLightmap);
		}
		else
		{
			R_DrawDecalsAllImmediate_GatherDecals( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, DrawDecals );
			iPermanentLightmap = DrawDecals.Count();
			R_DrawDecalsAllImmediate_GatherDecals( pRenderContext, renderGroup, LIGHTMAP, DrawDecals );
			iLightmap = DrawDecals.Count() - iPermanentLightmap;
			R_DrawDecalsAllImmediate_GatherDecals( pRenderContext, renderGroup, NONLIGHTMAP, DrawDecals );
			iNonLightmap = DrawDecals.Count() - (iPermanentLightmap + iLightmap);
		}

		if( DrawDecals.Count() )
		{
			size_t memSize = DrawDecals.Count() * sizeof( decal_t * );
			CMatRenderData< byte > rd(pRenderContext, memSize);
			memcpy( rd.Base(), DrawDecals.Base(), DrawDecals.Count() * sizeof( decal_t * ) );
			pCallQueue->QueueCall( DecalSurfaceDraw_QueueHelper, bBatched, renderGroup, modelorg, nCheckCount, (decal_t **)rd.Base(), iPermanentLightmap, iLightmap, iNonLightmap, flFade  );
			
			DrawDecals.RemoveAll();
		}
	}
	else
	{
		//non-queued mode
		DecalSurfaceDraw_NonQueued( pRenderContext, renderGroup, modelorg, nCheckCount, flFade );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Add decals to sorted decal list.
//-----------------------------------------------------------------------------
void DecalSurfaceAdd( SurfaceHandle_t surfID, int iGroup )
{
	// Performance analysis.
//	VPROF_BUDGET( "Decals", "Decals" );
	VPROF( "DecalsBatch" );
	
	// Go through surfaces decal list and add them to the correct lists.
	decal_t *pDecalList = MSurf_DecalPointer( surfID );
	if ( !pDecalList )
		return;

	int nCheckCount = g_nDecalSortCheckCount;
	if ( iGroup == MAX_MAT_SORT_GROUPS )
	{
		// Brush Model
		nCheckCount = g_nBrushModelDecalSortCheckCount;
	}

	int iTreeType = -1;
	decal_t *pNext = NULL;
	for ( decal_t *pDecal = pDecalList; pDecal; pDecal = pNext )
	{
		// Get the next pointer.
		pNext = pDecal->pnext;

		// Lightmap decals.
		if ( pDecal->material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ) )
		{
			// Permanent lightmapped decals.
			if ( pDecal->flags & FDECAL_PERMANENT )
			{
				iTreeType = PERMANENT_LIGHTMAP;
			}
			// Non-permanent lightmapped decals.
			else
			{
				iTreeType = LIGHTMAP;
			}
		}
		// Non-lightmapped decals.
		else
		{
			iTreeType = NONLIGHTMAP;
		}

		pDecal->flags &= ~FDECAL_HASUPDATED;
		intp iPool = g_aDecalSortPool.Alloc( true );
		if ( iPool != g_aDecalSortPool.InvalidIndex() )
		{
			g_aDecalSortPool[iPool] = pDecal;
						
			DecalSortTrees_t &sortTree = g_aDecalSortTrees[ pDecal->m_iSortTree ];
			DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( pDecal->m_iSortMaterial );
			if ( bucket.m_nCheckCount == nCheckCount )
			{	
				intp iHead = bucket.m_iHead;
				g_aDecalSortPool.LinkBefore( iHead, iPool );
			}
			
			bucket.m_iHead = iPool;
			bucket.m_nCheckCount = nCheckCount;			
		}
	}	
}

#pragma check_stack(off)
#endif // SWDS
