summaryrefslogtreecommitdiff
path: root/engine/r_decal.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/r_decal.cpp')
-rw-r--r--engine/r_decal.cpp3038
1 files changed, 3038 insertions, 0 deletions
diff --git a/engine/r_decal.cpp b/engine/r_decal.cpp
new file mode 100644
index 0000000..95f8c8e
--- /dev/null
+++ b/engine/r_decal.cpp
@@ -0,0 +1,3038 @@
+//========= 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;
+
+ int iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead;
+
+ int 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;
+
+ int iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead;
+
+ int nCount;
+ int 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 *)0xFFFFFFFF)
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+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;
+
+ int 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 );
+
+ int 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;
+
+ int 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;
+ int 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;
+ int 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 )
+ {
+ int iHead = bucket.m_iHead;
+ g_aDecalSortPool.LinkBefore( iHead, iPool );
+ }
+
+ bucket.m_iHead = iPool;
+ bucket.m_nCheckCount = nCheckCount;
+ }
+ }
+}
+
+#pragma check_stack(off)
+#endif // SWDS