Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2662 lines
79 KiB

//===== Copyright © 1996-2005, 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"
#include "getintersectingsurfaces_struct.h"
#include "surfinfo.h"
#include "collisionutils.h"
#include "engine/decal_flags.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;
inline int R_ConvertToPrivateDecalFlags( int nPublicDecalFlags )
{
int nPrivateFlags = 0;
if ( ( nPublicDecalFlags & EDF_PLAYERSPRAY ) != 0 ) { nPublicDecalFlags &= ~EDF_PLAYERSPRAY; nPrivateFlags |= FDECAL_PLAYERSPRAY; }
if ( ( nPublicDecalFlags & EDF_IMMEDIATECLEANUP ) != 0 ) { nPublicDecalFlags &= ~EDF_IMMEDIATECLEANUP; nPrivateFlags |= FDECAL_IMMEDIATECLEANUP; }
// If this hits, we are missing conversion here.
Assert( nPublicDecalFlags == 0 );
return nPrivateFlags;
}
// 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];
int m_freeBlockCount;
int m_firstFree;
int m_frameBlocks;
int m_lastFrameCount;
int m_freeTestIndex;
};
static CDecalVertCache ALIGN16 g_DecalVertCache;
//
// <vitaliy> (Hi John)
// Destroying decals is an ancient system that we reused for player graffiti decal previews.
// There is a new system for single-frame decals that are scheduled for immediate cleanup
// on the same frame that they are inserted. Sometimes these decals don't finish cleaning
// themselves up until they render for the second frame. That is okay for rendering purposes,
// but it is a problem if they end up in the cleanup list twice because it can create circular
// loops in the list or accessing free'd memory. The flags are uint16 and all used up at this
// point, so we cannot add a flag whether the decal is in the cleanup list already.
// In order not to increase the size of the decal structure I am making the cleanup list to
// be terminated by a non-NULL known not valid pointer. This allows all the code to know
// whether a specific decal_t object is already in the destroy list by checking its pDestroyList
// member for being non-NULL, and only the per-frame destroy list walking code needs to be aware
// of stopping at the non-NULL special terminator entry.
// The "easiest" spot to recreate a circular deadlock was on overpass T-side of monster in the
// left corner of monster wall and slanted wall to just spam the +spray_menu key and have a
// conditional breakpoint in R_DecalAddToDestroyList with condition pDecal == s_pDecalDestroyList.
// That condition will essentially create a loop of decal pointing to itself as next.
//
static decal_t * const kpDecalDestroyListNonNullTerminator = reinterpret_cast< decal_t * >( 1 );
static decal_t *s_pDecalDestroyList = kpDecalDestroyListNonNullTerminator;
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 );
// 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." );
static void R_DecalCreate( decalinfo_t* pDecalInfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement );
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 );
static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry);
void R_DecalSortInit( void );
bool DecalUpdate( decal_t* pDecal );
void R_DecalAddToDestroyList( decal_t *pDecal );
inline bool ShouldSkipDecalRender( decal_t *pDecal )
{
if ( !pDecal )
return true;
const bool cbRequiresDynamicUpdate = ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED );
const bool cbRequiresCleanupUpdate = ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) != 0 ;
// Add the decal to the list of decals to be destroyed if need be.
if ( cbRequiresDynamicUpdate || cbRequiresCleanupUpdate )
{
if ( cbRequiresDynamicUpdate )
pDecal->flags |= FDECAL_HASUPDATED;
if ( DecalUpdate( pDecal ) )
{
R_DecalAddToDestroyList( pDecal );
return true;
}
}
return false;
}
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 )
{
Plat_FastMemcpy( &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 DEDICATED
int vertexFormat = 0;
CMatRenderContextPtr pRenderContext( materials );
#ifdef USE_CONVARS
if( ShouldDrawInWireFrameMode() || r_drawdecals.GetInt() == 2 )
{
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 = - (GetBaseLocalClient().GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration;
}
else
{
fadeval = 1.0 - (GetBaseLocalClient().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?
IMaterialVar *pDecalScaleVar = pDecalMaterial->FindVarFast( "$decalScale", &s_DecalScaleVarCache );
if ( pDecalScaleVar )
{
scale = pDecalScaleVar->GetFloatValue();
}
pDecalScaleVar = pDecalMaterial->FindVarFast( "$decalScaleVariation", &s_DecalScaleVariationVarCache );
if ( pDecalScaleVar )
{
float variation = pDecalScaleVar->GetFloatValue();
variation = clamp( variation, 0.0f, 0.99f );
variation = RandomFloat( -variation, variation );
scale += variation * scale;
}
// compute the decal dimensions in world space
w = pDecalMaterial->GetMappingWidth() * scale;
h = pDecalMaterial->GetMappingHeight() * scale;
}
#ifndef DEDICATED
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 = r_decals.GetInt();
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, bool term_player_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( pDecal->flags & FDECAL_PLAYERSPRAY )
{
extern IBaseClientDLL *g_ClientDLL;
if ( term_player_decals || g_ClientDLL->CanRetirePlayerDecal( pDecal->userdata ) )
R_DecalUnlink( pDecal, pBrushData ); // safe to unlink
}
else */
if ( term_permanent_decals || !(pDecal->flags & FDECAL_PERMANENT) )
{
R_DecalUnlink( pDecal, pBrushData );
}
}
if ( term_permanent_decals && term_player_decals )
{
Assert( MSurf_DecalPointer( surfID ) == NULL );
}
}
}
void R_DecalTermNew( worldbrushdata_t *pBrushData, int nTick )
{
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 ( !( pDecal->flags & ( FDECAL_PERMANENT | FDECAL_PLAYERSPRAY ) ) && pDecal->m_nTickCreated > nTick )
{
R_DecalUnlink( pDecal, pBrushData );
}
}
}
}
void R_DecalTermAll()
{
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( bool bImmediateCleanup )
{
decal_t* pNewDestroyList = kpDecalDestroyListNonNullTerminator;
decal_t *pDecal = s_pDecalDestroyList;
while ( pDecal != kpDecalDestroyListNonNullTerminator )
{
decal_t *pNext = pDecal->pDestroyList;
if ( ( 0 == ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) ) || bImmediateCleanup )
{
R_DecalUnlink( pDecal, host_state.worldbrush );
}
else
{
// These are only cleaned up once per frame, and we're not there, so push this out
// for later.
pDecal->pDestroyList = pNewDestroyList;
pNewDestroyList = pDecal;
}
pDecal = pNext;
}
s_pDecalDestroyList = pNewDestroyList;
}
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 )
{
sspewdecals = 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 we are a player spray, we should have a normal.
Assert( ( ( decalinfo->m_Flags & FDECAL_PLAYERSPRAY ) == 0 ) || decalinfo->m_pNormal != NULL );
if ( decalinfo->m_pNormal )
{
const float cosTheta = DotProduct( MSurf_Plane( surfID ).normal, *(decalinfo->m_pNormal) );
const float fComp = ( decalinfo->m_Flags & FDECAL_PLAYERSPRAY )
? 0.5f // 60 degrees.
: 0.0f;
if ( cosTheta < fComp )
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;
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?
float scale = 1.0f;
IMaterialVar *pDecalScaleVar = decalInfo.m_pMaterial->FindVarFast( "$decalScale", &s_DecalScaleVarCache );
if ( pDecalScaleVar )
{
scale = pDecalScaleVar->GetFloatValue();
}
pDecalScaleVar = decalInfo.m_pMaterial->FindVarFast( "$decalScaleVariation", &s_DecalScaleVariationVarCache );
if ( pDecalScaleVar )
{
float variation = pDecalScaleVar->GetFloatValue();
variation = clamp( variation, 0.0f, 0.99f );
variation = RandomFloat( -variation, variation );
scale += variation * scale;
}
if ( scale != 1.0f && scale != 0.0f )
{
decalInfo.m_scale = 1.0f / scale;
decalInfo.m_Size *= scale;
}
else
{
decalInfo.m_scale = 1.0f;
}
decalInfo.m_flFadeDuration = 0.0f;
IMaterialVar* pFadeVar = pMaterial->FindVarFast( "$decalFadeDuration", &s_DecalFadeVarCache );
if ( pFadeVar )
{
decalInfo.m_flFadeDuration = pFadeVar->GetFloatValue();
pFadeVar = pMaterial->FindVarFast( "$decalFadeTime", &s_DecalFadeTimeVarCache );
decalInfo.m_flFadeStartTime = pFadeVar ? pFadeVar->GetFloatValue() : 0.0f;
}
IMaterialVar *pSecondPassVar = pMaterial->FindVarFast( "$decalSecondPass", &s_DecalSecondPassVarCache );
if ( pSecondPassVar )
{
decalInfo.m_Flags |= FDECAL_SECONDPASS;
}
// 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;
// McJohn -- Get surface normal
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, int nAdditionalDecalFlags )
{
void *userdata = NULL;
IMaterial* pMaterial = Draw_DecalMaterial( textureIndex );
flags |= R_ConvertToPrivateDecalFlags( nAdditionalDecalFlags );
// Player decal custom handling:
if ( flags & FDECAL_PLAYERSPRAY )
{
userdata = ( void * ) textureIndex;
}
R_DecalShoot_( pMaterial, entity, model, position, saxis, flags, rgbaColor, pNormal, userdata );
}
//-----------------------------------------------------------------------------
// 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, int nAdditionalDecalFlags )
{
// The userdata that is passed in is actually the sticker kit ID
// it cannot be zero
Assert( userdata != 0 );
#if 0 // <vitaliy>: decals are implemented differently in CS:GO and time out by game rules
//
// 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 );
}
#endif
// set this to be a player spray so it is timed out appropriately.
flags |= FDECAL_PLAYERSPRAY;
flags |= R_ConvertToPrivateDecalFlags( nAdditionalDecalFlags );
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 &modelorg )
{
pRenderContext = pContext;
vModelOrg = modelorg;
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();
++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->m_nTickCreated = GetBaseLocalClient().GetClientTickCount();
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 += GetBaseLocalClient().GetTime();
}
// check for a player spray
if( pdecal->flags & FDECAL_PLAYERSPRAY )
{
// set the time when this decal appered, and allow to self-destruct after enough time passed
pdecal->fadeStartTime = 0;
// 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 )
{
if ( ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) != 0 )
{
// This is ugly. We want to queue the decal for destruction, but we don't want to skip rendering it.
// It will actually be cleaned up at the end of the frame.
R_DecalAddToDestroyList( pDecal );
return false;
}
// retire the decal if it's time has come
if (pDecal->fadeDuration > 0)
{
return (GetBaseLocalClient().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 | FDECAL_IMMEDIATECLEANUP ) )
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( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID )
{
DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, modelorg );
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
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 ( ShouldSkipDecalRender( 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 = -( GetBaseLocalClient().GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration;
}
else
{
flFadeValue = 1.0 - ( GetBaseLocalClient().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 );
}
}
void R_DrawDecalsAll( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade )
{
int nSortTreeCount = g_aDecalSortTrees.Count();
if ( !nSortTreeCount )
return;
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 = g_nMaxDecals * 5;
int nDecalSortMaxIndices = nDecalSortMaxVerts * 3;
// NOTE: This is sort of a hack. The decal wireframe material is 20 bytes; this assumes
// we're going to render vertices no larger than 40 bytes/vert.
nDecalSortMaxVerts = MIN( nDecalSortMaxVerts, pRenderContext->GetMaxVerticesToRender( g_materialDecalWireframe ) / 4 );
nDecalSortMaxIndices = MIN( nDecalSortMaxIndices, pRenderContext->GetMaxIndicesToRender() );
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2);
float localClientTime = GetBaseLocalClient().GetTime();
for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree )
{
// Reset the mesh list.
bool bMeshInit = true;
uint16 unPlayerDecalStickerKitID = 0; // player decals must be split into separate calls by actual basetexture, but overall keep the state so they are bucketed by same material
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 ( ShouldSkipDecalRender( 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;
if ( ( nCount > nDecalSortMaxVerts ) || ( ( nCount - 2 ) > nDecalSortMaxIndices ) )
{
// The overflow case below causes a crash if the overflow occurs on the first iteration
// due to the meshBuilder.End() not getting setup with an m_pMesh. Instead, just ignore this
// decal's clipped vertexes.
if ( bMeshInit )
{
// this would have caused otherwise caused a crash because the meshbuilder has not been setup yet
DevWarning( "R_DrawDecalsAll: Overflow of mesh builder (%d, %d, %d, %d %d), skipping this decal!\n", nVertCount, nIndexCount, nCount, nDecalSortMaxVerts, nDecalSortMaxIndices );
}
continue;
}
// Overflow - new mesh, batch.
if ( ( ( nVertCount + nCount ) > nDecalSortMaxVerts ) || ( nIndexCount + ( nCount - 2 ) > nDecalSortMaxIndices )
|| ( !bMeshInit && ( pDecal->flags & FDECAL_PLAYERSPRAY ) && ( uint16( reinterpret_cast< uintp >( pDecal->userdata ) ) != unPlayerDecalStickerKitID ) ) )
{
// 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;
unPlayerDecalStickerKitID = 0;
}
// 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;
unPlayerDecalStickerKitID = ( pDecal->flags & FDECAL_PLAYERSPRAY )
? uint16( reinterpret_cast< uintp >( pDecal->userdata ) )
: 0; // Keep track of playerdecal proxy state for batches roll over
}
// 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.Count() + 1 > meshList.m_aBatches.NumAllocated() )
{
Warning( "R_DrawDecalsAll: overflowing m_aBatches. Reduce # of decals in the scene.\n" );
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;
if ( pDecal->flags & FDECAL_PLAYERSPRAY )
pBatch->m_pProxy = pDecal->userdata; // Player sprays must use individual proxies, probably all materials can, but this is a safe change
else
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 );
nVertCount = 0;
nIndexCount = 0;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
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;
}
if ( r_drawbatchdecals.GetBool() )
{
// Draw world decals.
R_DrawDecalsAll( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, modelorg, nCheckCount, flFade );
// Draw lightmapped non-world decals.
R_DrawDecalsAll( pRenderContext, renderGroup, LIGHTMAP, modelorg, nCheckCount, flFade );
// Draw non-lit(mod2x) decals.
R_DrawDecalsAll( pRenderContext, renderGroup, NONLIGHTMAP, modelorg, nCheckCount, flFade );
}
else
{
// Draw world decals.
R_DrawDecalsAllImmediate( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, modelorg, nCheckCount, flFade );
// Draw lightmapped non-world decals.
R_DrawDecalsAllImmediate( pRenderContext, renderGroup, LIGHTMAP, modelorg, nCheckCount, flFade );
// Draw non-lit(mod2x) decals.
R_DrawDecalsAllImmediate( pRenderContext, renderGroup, NONLIGHTMAP, 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 )
{
// Brush decals render front-to-back, so link after tail
intp iTail = bucket.m_iTail;
g_aDecalSortPool.LinkAfter( iTail, iPool );
bucket.m_iTail = iPool;
}
else
{
bucket.m_iHead = bucket.m_iTail = iPool;
bucket.m_nCheckCount = nCheckCount;
}
}
}
}
#pragma check_stack(off)
#endif // DEDICATED