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.
3038 lines
88 KiB
3038 lines
88 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include "render_pch.h" |
|
#include "r_decal.h" |
|
#include "client.h" |
|
#include <materialsystem/imaterialsystemhardwareconfig.h> |
|
#include "decal.h" |
|
#include "tier0/vprof.h" |
|
#include "materialsystem/materialsystem_config.h" |
|
#include "icliententity.h" |
|
#include "icliententitylist.h" |
|
#include "tier2/tier2.h" |
|
#include "tier1/callqueue.h" |
|
#include "tier1/memstack.h" |
|
#include "mempool.h" |
|
#include "vstdlib/random.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define DECAL_DISTANCE 4 |
|
|
|
// Empirically determined constants for minimizing overalpping decals |
|
ConVar r_decal_overlap_count("r_decal_overlap_count", "3"); |
|
ConVar r_decal_overlap_area("r_decal_overlap_area", "0.4"); |
|
|
|
// if a new decal covers more than this many old decals, retire until this count remains |
|
ConVar r_decal_cover_count("r_decal_cover_count", "4" ); |
|
|
|
static unsigned int s_DecalScaleVarCache = 0; |
|
static unsigned int s_DecalScaleVariationVarCache = 0; |
|
static unsigned int s_DecalFadeVarCache = 0; |
|
static unsigned int s_DecalFadeTimeVarCache = 0; |
|
static unsigned int s_DecalSecondPassVarCache = 0; |
|
|
|
|
|
// This structure contains the information used to create new decals |
|
struct decalinfo_t |
|
{ |
|
Vector m_Position; // world coordinates of the decal center |
|
Vector m_SAxis; // the s axis for the decal in world coordinates |
|
model_t* m_pModel; // the model the decal is going to be applied in |
|
worldbrushdata_t *m_pBrush; // The shared brush data for this model |
|
IMaterial* m_pMaterial; // The decal material |
|
float m_Size; // Size of the decal (in world coords) |
|
int m_Flags; |
|
int m_Entity; // Entity the decal is applied to. |
|
float m_scale; |
|
float m_flFadeDuration; |
|
float m_flFadeStartTime; |
|
int m_decalWidth; |
|
int m_decalHeight; |
|
color32 m_Color; |
|
Vector m_Basis[3]; |
|
void *m_pUserData; |
|
const Vector *m_pNormal; |
|
CUtlVector<SurfaceHandle_t> m_aApplySurfs; |
|
}; |
|
|
|
typedef struct |
|
{ |
|
CDecalVert decalVert[4]; |
|
} decalcache_t; |
|
|
|
// UNDONE: Compress this??? 256K here? |
|
static CClassMemoryPool<decal_t> g_DecalAllocator( 128 ); // 128 decals per block. |
|
static int g_nDynamicDecals = 0; |
|
static int g_nStaticDecals = 0; |
|
static int g_iLastReplacedDynamic = -1; |
|
|
|
CUtlVector<decal_t*> s_aDecalPool; |
|
|
|
const int DECALCACHE_ENTRY_COUNT = 1024; |
|
const int INVALID_CACHE_ENTRY = 0xFFFF; |
|
|
|
class ALIGN16 CDecalVertCache |
|
{ |
|
enum decalindex_ordinal |
|
{ |
|
DECAL_INDEX = 0, // set this and use this to free the whole decal's list on compact |
|
NEXT_VERT_BLOCK_INDEX = 1, |
|
IS_FREE_INDEX = 2, |
|
FRAME_COUNT_INDEX = 3, |
|
}; |
|
public: |
|
void Init(); |
|
CDecalVert *GetCachedVerts( decal_t *pDecal ); |
|
void FreeCachedVerts( decal_t *pDecal ); |
|
void StoreVertsInCache( decal_t *pDecal, CDecalVert *pList ); |
|
|
|
private: |
|
inline int GetIndex( decalcache_t *pBlock, decalindex_ordinal index ) |
|
{ |
|
return pBlock->decalVert[index].m_decalIndex; |
|
} |
|
inline void SetIndex( decalcache_t *pBlock, decalindex_ordinal index, int value ) |
|
{ |
|
pBlock->decalVert[index].m_decalIndex = value; |
|
} |
|
|
|
inline void SetNext( int iCur, int iNext ) |
|
{ |
|
SetIndex(m_cache + iCur, NEXT_VERT_BLOCK_INDEX, iNext); |
|
} |
|
inline void SetFree( int iBlock, bool bFree ) |
|
{ |
|
SetIndex( m_cache + iBlock, IS_FREE_INDEX, bFree ); |
|
} |
|
inline bool IsFree(int iBlock) |
|
{ |
|
return GetIndex(m_cache+iBlock, IS_FREE_INDEX) != 0; |
|
} |
|
|
|
decalcache_t *NextBlock( decalcache_t *pCache ); |
|
void FreeBlock(int cacheIndex); |
|
// search for blocks not used this frame and free them |
|
// this way we don't manage an LRU but get similar behavior |
|
void FindFreeBlocks( int blockCount ); |
|
int AllocBlock(); |
|
int AllocBlocks( int blockCount ); |
|
|
|
ALIGN16 decalcache_t m_cache[DECALCACHE_ENTRY_COUNT] ALIGN16_POST; |
|
int m_freeBlockCount; |
|
int m_firstFree; |
|
int m_frameBlocks; |
|
int m_lastFrameCount; |
|
int m_freeTestIndex; |
|
} ALIGN16_POST; |
|
|
|
static CDecalVertCache ALIGN16 g_DecalVertCache; |
|
static decal_t *s_pDecalDestroyList = NULL; |
|
|
|
int g_nMaxDecals = 0; |
|
|
|
// |
|
// ConVars that control distance-based decal scaling |
|
// |
|
static ConVar r_dscale_nearscale( "r_dscale_nearscale", "1", FCVAR_CHEAT ); |
|
static ConVar r_dscale_neardist( "r_dscale_neardist", "100", FCVAR_CHEAT ); |
|
static ConVar r_dscale_farscale( "r_dscale_farscale", "4", FCVAR_CHEAT ); |
|
static ConVar r_dscale_fardist( "r_dscale_fardist", "2000", FCVAR_CHEAT ); |
|
static ConVar r_dscale_basefov( "r_dscale_basefov", "90", FCVAR_CHEAT ); |
|
|
|
ConVar r_spray_lifetime( "r_spray_lifetime", "2", 0, "Number of rounds player sprays are visible" ); |
|
ConVar r_queued_decals( "r_queued_decals", "0", 0, "Offloads a bit of decal rendering setup work to the material system queue when enabled." ); |
|
|
|
|
|
// This makes sure all the decals got freed before the engine is shutdown. |
|
static class CDecalChecker |
|
{ |
|
public: |
|
~CDecalChecker() |
|
{ |
|
Assert( g_nDynamicDecals == 0 ); |
|
} |
|
} g_DecalChecker; |
|
|
|
// used for decal LOD |
|
VMatrix g_BrushToWorldMatrix; |
|
|
|
static CUtlVector<SurfaceHandle_t> s_DecalSurfaces[ MAX_MAT_SORT_GROUPS + 1 ]; |
|
static ConVar r_drawdecals( "r_drawdecals", "1", FCVAR_CHEAT, "Render decals." ); |
|
static ConVar r_drawbatchdecals( "r_drawbatchdecals", "1", 0, "Render decals batched." ); |
|
|
|
#ifndef SWDS |
|
static void R_DecalCreate( decalinfo_t* pDecalInfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement ); |
|
static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry); |
|
#endif |
|
void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const float *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal ); |
|
void R_DecalSortInit( void ); |
|
|
|
static void r_printdecalinfo_f() |
|
{ |
|
int nPermanent = 0; |
|
int nDynamic = 0; |
|
|
|
for ( int i=0; i < g_nMaxDecals; i++ ) |
|
{ |
|
if ( s_aDecalPool[i] ) |
|
{ |
|
if ( s_aDecalPool[i]->flags & FDECAL_PERMANENT ) |
|
++nPermanent; |
|
else |
|
++nDynamic; |
|
} |
|
} |
|
|
|
Assert( nDynamic == g_nDynamicDecals ); |
|
Msg( "%d decals: %d permanent, %d dynamic\nr_decals: %d\n", nPermanent+nDynamic, nPermanent, nDynamic, r_decals.GetInt() ); |
|
} |
|
|
|
static ConCommand r_printdecalinfo( "r_printdecalinfo", r_printdecalinfo_f ); |
|
|
|
|
|
void CDecalVertCache::StoreVertsInCache( decal_t *pDecal, CDecalVert *pList ) |
|
{ |
|
int vertCount = pDecal->clippedVertCount; |
|
int blockCount = (vertCount+3)>>2; |
|
FindFreeBlocks(blockCount); |
|
if ( blockCount > m_freeBlockCount ) |
|
return; |
|
int cacheHandle = AllocBlocks(blockCount); |
|
pDecal->cacheHandle = cacheHandle; |
|
decalcache_t *pCache = &m_cache[cacheHandle]; |
|
while ( blockCount ) |
|
{ |
|
Assert(GetIndex(pCache, DECAL_INDEX) == -1 ); |
|
// don't memcpy here it overwrites the indices we're storing in the m_decalIndex data |
|
for ( int i = 0; i < 4; i++ ) |
|
{ |
|
pCache->decalVert[i].m_vPos = pList[i].m_vPos; |
|
pCache->decalVert[i].m_ctCoords = pList[i].m_ctCoords; |
|
pCache->decalVert[i].m_cLMCoords = pList[i].m_cLMCoords; |
|
} |
|
|
|
pList += 4; |
|
blockCount--; |
|
SetIndex( pCache, DECAL_INDEX, pDecal->m_iDecalPool ); |
|
SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount ); |
|
pCache = NextBlock(pCache); |
|
} |
|
} |
|
|
|
void CDecalVertCache::FreeCachedVerts( decal_t *pDecal ) |
|
{ |
|
// walk the list |
|
int nextIndex = INVALID_CACHE_ENTRY; |
|
for ( int cacheHandle = pDecal->cacheHandle; cacheHandle != INVALID_CACHE_ENTRY; cacheHandle = nextIndex ) |
|
{ |
|
decalcache_t *pCache = m_cache + cacheHandle; |
|
nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX); |
|
Assert(GetIndex(pCache,DECAL_INDEX)==pDecal->m_iDecalPool); |
|
FreeBlock(cacheHandle); |
|
} |
|
pDecal->cacheHandle = INVALID_CACHE_ENTRY; |
|
pDecal->clippedVertCount = 0; |
|
} |
|
|
|
CDecalVert *CDecalVertCache::GetCachedVerts( decal_t *pDecal ) |
|
{ |
|
int cacheHandle = pDecal->cacheHandle; |
|
// track blocks used this frame to avoid thrashing |
|
if ( r_framecount != m_lastFrameCount ) |
|
{ |
|
m_frameBlocks = 0; |
|
m_lastFrameCount = r_framecount; |
|
} |
|
|
|
if ( cacheHandle == INVALID_CACHE_ENTRY ) |
|
return NULL; |
|
decalcache_t *pCache = &m_cache[cacheHandle]; |
|
for ( int i = cacheHandle; i != INVALID_CACHE_ENTRY; i = GetIndex(&m_cache[i], NEXT_VERT_BLOCK_INDEX) ) |
|
{ |
|
SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount ); |
|
Assert( GetIndex(pCache, DECAL_INDEX) == pDecal->m_iDecalPool); |
|
} |
|
|
|
int vertCount = pDecal->clippedVertCount; |
|
int blockCount = (vertCount+3)>>2; |
|
m_frameBlocks += blockCount; |
|
|
|
// Make linked vert lists contiguous by copying to the clip buffer |
|
if ( blockCount > 1 ) |
|
{ |
|
int indexOut = 0; |
|
while ( blockCount ) |
|
{ |
|
V_memcpy( &g_DecalClipVerts[indexOut], pCache, sizeof(*pCache) ); |
|
indexOut += 4; |
|
blockCount --; |
|
pCache = NextBlock(pCache); |
|
} |
|
return g_DecalClipVerts; |
|
} |
|
// only one block, no need to copy |
|
return pCache->decalVert; |
|
} |
|
|
|
void CDecalVertCache::Init() |
|
{ |
|
m_firstFree = 0; |
|
m_freeTestIndex = 0; |
|
for ( int i = 0; i < DECALCACHE_ENTRY_COUNT; i++ ) |
|
{ |
|
SetNext( i, i+1 ); |
|
SetIndex( &m_cache[i], DECAL_INDEX, -1 ); |
|
SetFree( i, true ); |
|
} |
|
SetNext( DECALCACHE_ENTRY_COUNT-1, INVALID_CACHE_ENTRY ); |
|
m_freeBlockCount = DECALCACHE_ENTRY_COUNT; |
|
} |
|
|
|
decalcache_t *CDecalVertCache::NextBlock( decalcache_t *pCache ) |
|
{ |
|
int nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX); |
|
if ( nextIndex == INVALID_CACHE_ENTRY ) |
|
return NULL; |
|
return m_cache + nextIndex; |
|
} |
|
void CDecalVertCache::FreeBlock(int cacheIndex) |
|
{ |
|
SetFree( cacheIndex, true ); |
|
SetNext( cacheIndex, m_firstFree ); |
|
SetIndex( &m_cache[cacheIndex], DECAL_INDEX, -1 ); |
|
|
|
m_firstFree = cacheIndex; |
|
m_freeBlockCount++; |
|
} |
|
|
|
// search for blocks not used this frame and free them |
|
// this way we don't manage an LRU but get similar behavior |
|
void CDecalVertCache::FindFreeBlocks( int blockCount ) |
|
{ |
|
if ( blockCount <= m_freeBlockCount ) |
|
return; |
|
int possibleFree = DECALCACHE_ENTRY_COUNT - m_frameBlocks; |
|
if ( blockCount > possibleFree ) |
|
return; |
|
|
|
// limit the search for performance to 16 entries |
|
int lastTest = (m_freeTestIndex + 16) & (DECALCACHE_ENTRY_COUNT-1); |
|
|
|
for ( ; m_freeTestIndex != lastTest; m_freeTestIndex = (m_freeTestIndex+1)&(DECALCACHE_ENTRY_COUNT-1) ) |
|
{ |
|
if ( !IsFree(m_freeTestIndex) ) |
|
{ |
|
int lastFrame = GetIndex(&m_cache[m_freeTestIndex], FRAME_COUNT_INDEX); |
|
if ( (r_framecount - lastFrame) > 1 ) |
|
{ |
|
int iDecal = GetIndex(&m_cache[m_freeTestIndex], DECAL_INDEX); |
|
FreeCachedVerts(s_aDecalPool[iDecal]); |
|
} |
|
} |
|
if ( m_freeBlockCount >= blockCount ) |
|
break; |
|
} |
|
} |
|
|
|
int CDecalVertCache::AllocBlock() |
|
{ |
|
if ( !m_freeBlockCount ) |
|
return INVALID_CACHE_ENTRY; |
|
Assert(IsFree(m_firstFree)); |
|
int nextFree = GetIndex(m_cache+m_firstFree, NEXT_VERT_BLOCK_INDEX ); |
|
int cacheIndex = m_firstFree; |
|
SetFree( cacheIndex, false ); |
|
m_firstFree = nextFree; |
|
m_freeBlockCount--; |
|
return cacheIndex; |
|
} |
|
int CDecalVertCache::AllocBlocks( int blockCount ) |
|
{ |
|
if ( blockCount > m_freeBlockCount ) |
|
return INVALID_CACHE_ENTRY; |
|
int firstBlock = AllocBlock(); |
|
Assert(firstBlock!=INVALID_CACHE_ENTRY); |
|
int blockHandle = firstBlock; |
|
for ( int i = 1; i < blockCount; i++ ) |
|
{ |
|
int nextBlock = AllocBlock(); |
|
Assert(nextBlock!=INVALID_CACHE_ENTRY); |
|
SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, nextBlock ); |
|
blockHandle = nextBlock; |
|
} |
|
SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, INVALID_CACHE_ENTRY ); |
|
return firstBlock; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the offset for a decal polygon |
|
//----------------------------------------------------------------------------- |
|
float ComputeDecalLightmapOffset( SurfaceHandle_t surfID ) |
|
{ |
|
float flOffset; |
|
if ( MSurf_Flags( surfID ) & SURFDRAW_BUMPLIGHT ) |
|
{ |
|
int nWidth, nHeight; |
|
materials->GetLightmapPageSize( |
|
SortInfoToLightmapPage( MSurf_MaterialSortID( surfID ) ), &nWidth, &nHeight ); |
|
int nXExtent = ( MSurf_LightmapExtents( surfID )[0] ) + 1; |
|
|
|
flOffset = ( nWidth != 0 ) ? (float)nXExtent / (float)nWidth : 0.0f; |
|
} |
|
else |
|
{ |
|
flOffset = 0.0f; |
|
} |
|
return flOffset; |
|
} |
|
|
|
static VertexFormat_t GetUncompressedFormat( const IMaterial * pMaterial ) |
|
{ |
|
// FIXME: IMaterial::GetVertexFormat() should do this stripping (add a separate 'SupportsCompression' accessor) |
|
return ( pMaterial->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Draws a decal polygon |
|
//----------------------------------------------------------------------------- |
|
void Shader_DecalDrawPoly( CDecalVert *v, IMaterial *pMaterial, SurfaceHandle_t surfID, int vertCount, decal_t *pdecal, float flFade ) |
|
{ |
|
#ifndef SWDS |
|
int vertexFormat = 0; |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
#ifdef USE_CONVARS |
|
if( ShouldDrawInWireFrameMode() ) |
|
{ |
|
pRenderContext->Bind( g_materialDecalWireframe ); |
|
} |
|
else |
|
#endif |
|
{ |
|
Assert( MSurf_MaterialSortID( surfID ) >= 0 && |
|
MSurf_MaterialSortID( surfID ) < g_WorldStaticMeshes.Count() ); |
|
pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID ); |
|
pRenderContext->Bind( pMaterial, pdecal->userdata ); |
|
vertexFormat = GetUncompressedFormat( pMaterial ); |
|
} |
|
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( ); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_POLYGON, vertCount ); |
|
|
|
byte color[4] = {pdecal->color.r,pdecal->color.g,pdecal->color.b,pdecal->color.a}; |
|
if ( flFade != 1.0f ) |
|
{ |
|
color[3] = (byte)( color[3] * flFade ); |
|
} |
|
|
|
// Deal with fading out... (should this be done in the shader?) |
|
// Note that we do it with per-vertex color even though the translucency |
|
// is constant so as to not change any rendering state (like the constant |
|
// alpha value) |
|
if (pdecal->flags & FDECAL_DYNAMIC) |
|
{ |
|
float fadeval; |
|
|
|
// Negative fadeDuration value means to fade in |
|
if (pdecal->fadeDuration < 0) |
|
{ |
|
fadeval = - (cl.GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration; |
|
} |
|
else |
|
{ |
|
fadeval = 1.0 - (cl.GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration; |
|
} |
|
|
|
fadeval = clamp( fadeval, 0.0f, 1.0f ); |
|
color[3] = (byte) (color[3] * fadeval); |
|
} |
|
|
|
|
|
Vector normal(0,0,1), tangentS(1,0,0), tangentT(0,1,0); |
|
|
|
if ( vertexFormat & (VERTEX_NORMAL|VERTEX_TANGENT_SPACE) ) |
|
{ |
|
normal = MSurf_Plane( surfID ).normal; |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
Vector tVect; |
|
bool negate = TangentSpaceSurfaceSetup( surfID, tVect ); |
|
TangentSpaceComputeBasis( tangentS, tangentT, normal, tVect, negate ); |
|
} |
|
} |
|
|
|
float flOffset = pdecal->lightmapOffset; |
|
|
|
for( int i = 0; i < vertCount; i++, v++ ) |
|
{ |
|
meshBuilder.Position3f( VectorExpand( v->m_vPos ) ); |
|
if ( vertexFormat & VERTEX_NORMAL ) |
|
{ |
|
meshBuilder.Normal3fv( normal.Base() ); |
|
} |
|
meshBuilder.Color4ubv( color ); |
|
|
|
// Check to see if we are in a material page. |
|
meshBuilder.TexCoord2f( 0, Vector2DExpand( v->m_ctCoords ) ); |
|
meshBuilder.TexCoord2f( 1, Vector2DExpand( v->m_cLMCoords ) ); |
|
meshBuilder.TexCoord1f( 2, flOffset ); |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
meshBuilder.TangentS3fv( tangentS.Base() ); |
|
meshBuilder.TangentT3fv( tangentT.Base() ); |
|
} |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets the decal material and radius based on the decal index |
|
//----------------------------------------------------------------------------- |
|
void R_DecalGetMaterialAndSize( int decalIndex, IMaterial*& pDecalMaterial, float& w, float& h ) |
|
{ |
|
pDecalMaterial = Draw_DecalMaterial( decalIndex ); |
|
if (!pDecalMaterial) |
|
return; |
|
|
|
float scale = 1.0f; |
|
|
|
// Compute scale of surface |
|
// FIXME: cache this? |
|
bool found; |
|
IMaterialVar* pDecalScaleVar = pDecalMaterial->FindVar( "$decalScale", &found, false ); |
|
if( found ) |
|
{ |
|
scale = pDecalScaleVar->GetFloatValue(); |
|
} |
|
|
|
// compute the decal dimensions in world space |
|
w = pDecalMaterial->GetMappingWidth() * scale; |
|
h = pDecalMaterial->GetMappingHeight() * scale; |
|
} |
|
|
|
#ifndef SWDS |
|
|
|
|
|
|
|
static inline decal_t *MSurf_DecalPointer( SurfaceHandle_t surfID ) |
|
{ |
|
WorldDecalHandle_t handle = MSurf_Decals(surfID ); |
|
if ( handle == WORLD_DECAL_HANDLE_INVALID ) |
|
return NULL; |
|
|
|
return s_aDecalPool[handle]; |
|
} |
|
|
|
static WorldDecalHandle_t DecalToHandle( decal_t *pDecal ) |
|
{ |
|
if ( !pDecal ) |
|
return WORLD_DECAL_HANDLE_INVALID; |
|
|
|
int decalIndex = pDecal->m_iDecalPool; |
|
Assert( decalIndex >= 0 && decalIndex < g_nMaxDecals ); |
|
return static_cast<WorldDecalHandle_t> (decalIndex); |
|
} |
|
|
|
// Init the decal pool |
|
void R_DecalInit( void ) |
|
{ |
|
g_nMaxDecals = Q_atoi( r_decals.GetDefault() ); |
|
g_nMaxDecals = MAX(64, g_nMaxDecals); |
|
Assert( g_DecalAllocator.Count() == 0 ); |
|
g_nDynamicDecals = 0; |
|
g_nStaticDecals = 0; |
|
g_iLastReplacedDynamic = -1; |
|
|
|
s_aDecalPool.Purge(); |
|
s_aDecalPool.SetSize( g_nMaxDecals ); |
|
|
|
int i; |
|
|
|
// Traverse all surfaces of map and throw away current decals |
|
// |
|
// sort the surfaces into the sort arrays |
|
if ( host_state.worldbrush ) |
|
{ |
|
for( i = 0; i < host_state.worldbrush->numsurfaces; i++ ) |
|
{ |
|
SurfaceHandle_t surfID = SurfaceHandleFromIndex(i); |
|
MSurf_Decals( surfID ) = WORLD_DECAL_HANDLE_INVALID; |
|
} |
|
} |
|
|
|
for( int iDecal = 0; iDecal < g_nMaxDecals; ++iDecal ) |
|
{ |
|
s_aDecalPool[iDecal] = NULL; |
|
} |
|
|
|
g_DecalVertCache.Init(); |
|
|
|
R_DecalSortInit(); |
|
} |
|
|
|
void R_DecalTerm( worldbrushdata_t *pBrushData, bool term_permanent_decals ) |
|
{ |
|
if( !pBrushData ) |
|
return; |
|
|
|
for( int i = 0; i < pBrushData->numsurfaces; i++ ) |
|
{ |
|
decal_t *pNext; |
|
SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pBrushData ); |
|
for( decal_t *pDecal=MSurf_DecalPointer( surfID ); pDecal; pDecal=pNext ) |
|
{ |
|
pNext = pDecal->pnext; |
|
if ( term_permanent_decals |
|
|| (!(pDecal->flags & FDECAL_PERMANENT) |
|
&& !(pDecal->flags & FDECAL_PLAYERSPRAY)) ) |
|
{ |
|
R_DecalUnlink( pDecal, pBrushData ); |
|
} |
|
else if( pDecal->flags & FDECAL_PLAYERSPRAY ) |
|
{ |
|
// time out player spray after some number of rounds |
|
pDecal->fadeStartTime += 1.0f; |
|
if( pDecal->fadeStartTime >= r_spray_lifetime.GetFloat() ) |
|
{ |
|
R_DecalUnlink( pDecal, pBrushData ); |
|
} |
|
} |
|
} |
|
|
|
if ( term_permanent_decals ) |
|
{ |
|
Assert( MSurf_DecalPointer( surfID ) == NULL ); |
|
} |
|
} |
|
} |
|
|
|
void R_DecalTermAll() |
|
{ |
|
s_pDecalDestroyList = NULL; |
|
for ( int i = 0; i<s_aDecalPool.Count(); i++ ) |
|
{ |
|
R_DecalUnlink( s_aDecalPool[i], host_state.worldbrush ); |
|
} |
|
} |
|
|
|
|
|
static int R_DecalIndex( decal_t *pdecal ) |
|
{ |
|
return pdecal->m_iDecalPool; |
|
} |
|
|
|
|
|
// Release the cache entry for this decal |
|
static void R_DecalCacheClear( decal_t *pdecal ) |
|
{ |
|
g_DecalVertCache.FreeCachedVerts( pdecal ); |
|
} |
|
|
|
|
|
void R_DecalFlushDestroyList( void ) |
|
{ |
|
decal_t *pDecal = s_pDecalDestroyList; |
|
while ( pDecal ) |
|
{ |
|
decal_t *pNext = pDecal->pDestroyList; |
|
R_DecalUnlink( pDecal, host_state.worldbrush ); |
|
pDecal = pNext; |
|
} |
|
s_pDecalDestroyList = NULL; |
|
} |
|
|
|
static void R_DecalAddToDestroyList( decal_t *pDecal ) |
|
{ |
|
if ( !pDecal->pDestroyList ) |
|
{ |
|
pDecal->pDestroyList = s_pDecalDestroyList; |
|
s_pDecalDestroyList = pDecal; |
|
} |
|
} |
|
|
|
// Unlink pdecal from any surface it's attached to |
|
void R_DecalUnlink( decal_t *pdecal, worldbrushdata_t *pData ) |
|
{ |
|
if ( !pdecal ) |
|
return; |
|
|
|
decal_t *tmp; |
|
|
|
R_DecalCacheClear( pdecal ); |
|
if ( IS_SURF_VALID( pdecal->surfID ) ) |
|
{ |
|
if ( MSurf_DecalPointer( pdecal->surfID ) == pdecal ) |
|
{ |
|
MSurf_Decals( pdecal->surfID ) = DecalToHandle( pdecal->pnext ); |
|
} |
|
else |
|
{ |
|
tmp = MSurf_DecalPointer( pdecal->surfID ); |
|
if ( !tmp ) |
|
Sys_Error("Bad decal list"); |
|
while ( tmp->pnext ) |
|
{ |
|
if ( tmp->pnext == pdecal ) |
|
{ |
|
tmp->pnext = pdecal->pnext; |
|
break; |
|
} |
|
tmp = tmp->pnext; |
|
} |
|
} |
|
|
|
// Tell the displacement surface. |
|
if( SurfaceHasDispInfo( pdecal->surfID ) ) |
|
{ |
|
IDispInfo * pDispInfo = MSurf_DispInfo( pdecal->surfID, pData ); |
|
|
|
if ( pDispInfo ) |
|
pDispInfo->NotifyRemoveDecal( pdecal->m_DispDecal ); |
|
} |
|
} |
|
|
|
pdecal->surfID = SURFACE_HANDLE_INVALID; |
|
|
|
if ( !(pdecal->flags & FDECAL_PERMANENT) ) |
|
{ |
|
--g_nDynamicDecals; |
|
Assert( g_nDynamicDecals >= 0 ); |
|
} |
|
else |
|
{ |
|
--g_nStaticDecals; |
|
Assert( g_nStaticDecals >= 0 ); |
|
} |
|
|
|
// Free the decal. |
|
Assert( s_aDecalPool[pdecal->m_iDecalPool] == pdecal ); |
|
s_aDecalPool[pdecal->m_iDecalPool] = NULL; |
|
g_DecalAllocator.Free( pdecal ); |
|
} |
|
|
|
|
|
int R_FindFreeDecalSlot() |
|
{ |
|
for ( int i=0; i < g_nMaxDecals; i++ ) |
|
{ |
|
if ( !s_aDecalPool[i] ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
// Uncomment this to spew decals if we run out of space!!! |
|
// #define SPEW_DECALS |
|
#if defined( SPEW_DECALS ) |
|
void SpewDecals() |
|
{ |
|
static bool spewdecals = true; |
|
|
|
if ( spewdecals ) |
|
{ |
|
spewdecals = false; |
|
|
|
int i = 0; |
|
for ( i = 0 ; i < g_nMaxDecals; ++i ) |
|
{ |
|
decal_t *decal = s_aDecalPool[ i ]; |
|
Assert( decal ); |
|
if ( decal ) |
|
{ |
|
bool permanent = ( decal->flags & FDECAL_PERMANENT ) ? true : false; |
|
Msg( "%i == %s on %i perm %i at %.2f %.2f %.2f on surf %i (%.2f %.2f %2.f)\n", |
|
i, |
|
decal->material->GetName(), |
|
(int)decal->entityIndex, |
|
permanent ? 1 : 0, |
|
decal->position.x, decal->position.y, decal->position.z, |
|
(int)decal->surfID, |
|
decal->dx, |
|
decal->dy, |
|
decal->scale ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
|
|
int R_FindDynamicDecalSlot( int iStartAt ) |
|
{ |
|
if ( (iStartAt >= g_nMaxDecals) || (iStartAt < 0) ) |
|
{ |
|
iStartAt = 0; |
|
} |
|
|
|
int i = iStartAt; |
|
|
|
do |
|
{ |
|
// don't deallocate player sprays or permanent decals |
|
if ( s_aDecalPool[i] && |
|
!(s_aDecalPool[i]->flags & FDECAL_PERMANENT) && |
|
!(s_aDecalPool[i]->flags & FDECAL_PLAYERSPRAY) ) |
|
return i; |
|
|
|
++i; |
|
|
|
if ( i >= g_nMaxDecals ) |
|
i = 0; |
|
} |
|
while ( i != iStartAt ); |
|
|
|
DevMsg("R_FindDynamicDecalSlot: no slot available.\n"); |
|
|
|
#if defined( SPEW_DECALS ) |
|
SpewDecals(); |
|
#endif |
|
|
|
return -1; |
|
} |
|
|
|
// Just reuse next decal in list |
|
// A decal that spans multiple surfaces will use multiple decal_t pool entries, as each surface needs |
|
// it's own. |
|
static decal_t *R_DecalAlloc( int flags ) |
|
{ |
|
static bool bWarningOnce = false; |
|
bool bPermanent = (flags & FDECAL_PERMANENT) != 0; |
|
|
|
int dynamicDecalLimit = min( r_decals.GetInt(), g_nMaxDecals ); |
|
|
|
// Now find a slot. Unless it's dynamic and we're at the limit of dynamic decals, |
|
// we can look for a free slot. |
|
int iSlot = -1; |
|
if ( bPermanent || (g_nDynamicDecals < dynamicDecalLimit) ) |
|
{ |
|
iSlot = R_FindFreeDecalSlot(); |
|
} |
|
|
|
if ( iSlot == -1 ) |
|
{ |
|
iSlot = R_FindDynamicDecalSlot( g_iLastReplacedDynamic+1 ); |
|
if ( iSlot == -1 ) |
|
{ |
|
if ( !bWarningOnce ) |
|
{ |
|
// Can't find a free slot. Just kill the first one. |
|
DevWarning( 1, "Exceeded MAX_DECALS (%d).\n", g_nMaxDecals ); |
|
bWarningOnce = true; |
|
} |
|
iSlot = 0; |
|
} |
|
|
|
R_DecalUnlink( s_aDecalPool[iSlot], host_state.worldbrush ); |
|
g_iLastReplacedDynamic = iSlot; |
|
} |
|
|
|
// Setup the new decal. |
|
decal_t *pDecal = g_DecalAllocator.Alloc(); |
|
s_aDecalPool[iSlot] = pDecal; |
|
pDecal->pDestroyList = NULL; |
|
pDecal->m_iDecalPool = iSlot; |
|
pDecal->surfID = SURFACE_HANDLE_INVALID; |
|
pDecal->cacheHandle = INVALID_CACHE_ENTRY; |
|
pDecal->clippedVertCount = 0; |
|
|
|
if ( !bPermanent ) |
|
{ |
|
++g_nDynamicDecals; |
|
} |
|
else |
|
{ |
|
++g_nStaticDecals; |
|
} |
|
|
|
return pDecal; |
|
} |
|
|
|
/* |
|
// The world coordinate system is right handed with Z up. |
|
// |
|
// ^ Z |
|
// | |
|
// | |
|
// | |
|
//X<----| |
|
// \ |
|
// \ |
|
// \ Y |
|
*/ |
|
|
|
void R_DecalSurface( SurfaceHandle_t surfID, decalinfo_t *decalinfo, bool bForceForDisplacement ) |
|
{ |
|
if ( decalinfo->m_pNormal ) |
|
{ |
|
if ( DotProduct( MSurf_Plane( surfID ).normal, *(decalinfo->m_pNormal) ) < 0.0f ) |
|
return; |
|
} |
|
|
|
// Get the texture associated with this surface |
|
mtexinfo_t* tex = MSurf_TexInfo( surfID ); |
|
|
|
Vector4D &textureU = tex->textureVecsTexelsPerWorldUnits[0]; |
|
Vector4D &textureV = tex->textureVecsTexelsPerWorldUnits[1]; |
|
|
|
// project decal center into the texture space of the surface |
|
float s = DotProduct( decalinfo->m_Position, textureU.AsVector3D() ) + |
|
textureU.w - MSurf_TextureMins( surfID )[0]; |
|
float t = DotProduct( decalinfo->m_Position, textureV.AsVector3D() ) + |
|
textureV.w - MSurf_TextureMins( surfID )[1]; |
|
|
|
|
|
// Determine the decal basis (measured in world space) |
|
// Note that the decal basis vectors 0 and 1 will always lie in the same |
|
// plane as the texture space basis vectors textureVecsTexelsPerWorldUnits. |
|
|
|
R_DecalComputeBasis( MSurf_Plane( surfID ).normal, |
|
(decalinfo->m_Flags & FDECAL_USESAXIS) ? &decalinfo->m_SAxis : 0, |
|
decalinfo->m_Basis ); |
|
|
|
// Compute an effective width and height (axis aligned) in the parent texture space |
|
// How does this work? decalBasis[0] represents the u-direction (width) |
|
// of the decal measured in world space, decalBasis[1] represents the |
|
// v-direction (height) measured in world space. |
|
// textureVecsTexelsPerWorldUnits[0] represents the u direction of |
|
// the surface's texture space measured in world space (with the appropriate |
|
// scale factor folded in), and textureVecsTexelsPerWorldUnits[1] |
|
// represents the texture space v direction. We want to find the dimensions (w,h) |
|
// of a square measured in texture space, axis aligned to that coordinate system. |
|
// All we need to do is to find the components of the decal edge vectors |
|
// (decalWidth * decalBasis[0], decalHeight * decalBasis[1]) |
|
// in texture coordinates: |
|
|
|
float w = fabs( decalinfo->m_decalWidth * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[0] ) ) + |
|
fabs( decalinfo->m_decalHeight * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[1] ) ); |
|
|
|
float h = fabs( decalinfo->m_decalWidth * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[0] ) ) + |
|
fabs( decalinfo->m_decalHeight * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[1] ) ); |
|
|
|
// move s,t to upper left corner |
|
s -= ( w * 0.5 ); |
|
t -= ( h * 0.5 ); |
|
|
|
// Is this rect within the surface? -- tex width & height are unsigned |
|
if( !bForceForDisplacement ) |
|
{ |
|
if ( s <= -w || t <= -h || |
|
s > (MSurf_TextureExtents( surfID )[0]+w) || t > (MSurf_TextureExtents( surfID )[1]+h) ) |
|
{ |
|
return; // nope |
|
} |
|
} |
|
|
|
// stamp it |
|
R_DecalCreate( decalinfo, surfID, s, t, bForceForDisplacement ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// iterate over all surfaces on a node, looking for surfaces to decal |
|
//----------------------------------------------------------------------------- |
|
static void R_DecalNodeSurfaces( mnode_t* node, decalinfo_t *decalinfo ) |
|
{ |
|
// iterate over all surfaces in the node |
|
SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface ); |
|
for ( int i=0; i<node->numsurfaces ; ++i, ++surfID) |
|
{ |
|
if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS ) |
|
continue; |
|
|
|
// Displacement surfaces get decals in R_DecalLeaf. |
|
if ( SurfaceHasDispInfo( surfID ) ) |
|
continue; |
|
|
|
R_DecalSurface( surfID, decalinfo, false ); |
|
} |
|
} |
|
|
|
|
|
void R_DecalLeaf( mleaf_t *pLeaf, decalinfo_t *decalinfo ) |
|
{ |
|
SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; |
|
for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) |
|
{ |
|
SurfaceHandle_t surfID = pHandle[i]; |
|
|
|
// only process leaf surfaces |
|
if ( MSurf_Flags( surfID ) & (SURFDRAW_NODE|SURFDRAW_NODECALS) ) |
|
continue; |
|
|
|
if ( decalinfo->m_aApplySurfs.Find( surfID ) != -1 ) |
|
continue; |
|
|
|
Assert( !MSurf_DispInfo( surfID ) ); |
|
|
|
float dist = fabs( DotProduct(decalinfo->m_Position, MSurf_Plane( surfID ).normal) - MSurf_Plane( surfID ).dist); |
|
if ( dist < DECAL_DISTANCE ) |
|
{ |
|
R_DecalSurface( surfID, decalinfo, false ); |
|
} |
|
} |
|
|
|
// Add the decal to each displacement in the leaf it touches. |
|
for ( int i = 0; i < pLeaf->dispCount; i++ ) |
|
{ |
|
IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); |
|
|
|
SurfaceHandle_t surfID = pDispInfo->GetParent(); |
|
|
|
if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS ) |
|
continue; |
|
|
|
// Make sure the decal hasn't already been added to it. |
|
if( pDispInfo->GetTag() ) |
|
continue; |
|
|
|
pDispInfo->SetTag(); |
|
|
|
// Trivial bbox reject. |
|
Vector bbMin, bbMax; |
|
pDispInfo->GetBoundingBox( bbMin, bbMax ); |
|
if( decalinfo->m_Position.x - decalinfo->m_Size < bbMax.x && decalinfo->m_Position.x + decalinfo->m_Size > bbMin.x && |
|
decalinfo->m_Position.y - decalinfo->m_Size < bbMax.y && decalinfo->m_Position.y + decalinfo->m_Size > bbMin.y && |
|
decalinfo->m_Position.z - decalinfo->m_Size < bbMax.z && decalinfo->m_Position.z + decalinfo->m_Size > bbMin.z ) |
|
{ |
|
R_DecalSurface( pDispInfo->GetParent(), decalinfo, true ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Recursive routine to find surface to apply a decal to. World coordinates of |
|
// the decal are passed in r_recalpos like the rest of the engine. This should |
|
// be called through R_DecalShoot() |
|
//----------------------------------------------------------------------------- |
|
|
|
static void R_DecalNode( mnode_t *node, decalinfo_t* decalinfo ) |
|
{ |
|
cplane_t *splitplane; |
|
float dist; |
|
|
|
if (!node ) |
|
return; |
|
if ( node->contents >= 0 ) |
|
{ |
|
R_DecalLeaf( (mleaf_t *)node, decalinfo ); |
|
return; |
|
} |
|
|
|
splitplane = node->plane; |
|
dist = DotProduct (decalinfo->m_Position, splitplane->normal) - splitplane->dist; |
|
|
|
// This is arbitrarily set to 10 right now. In an ideal world we'd have the |
|
// exact surface but we don't so, this tells me which planes are "sort of |
|
// close" to the gunshot -- the gunshot is actually 4 units in front of the |
|
// wall (see dlls\weapons.cpp). We also need to check to see if the decal |
|
// actually intersects the texture space of the surface, as this method tags |
|
// parallel surfaces in the same node always. |
|
// JAY: This still tags faces that aren't correct at edges because we don't |
|
// have a surface normal |
|
|
|
if (dist > decalinfo->m_Size) |
|
{ |
|
R_DecalNode (node->children[0], decalinfo); |
|
} |
|
else if (dist < -decalinfo->m_Size) |
|
{ |
|
R_DecalNode (node->children[1], decalinfo); |
|
} |
|
else |
|
{ |
|
if ( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) |
|
R_DecalNodeSurfaces( node, decalinfo ); |
|
|
|
R_DecalNode (node->children[0], decalinfo); |
|
R_DecalNode (node->children[1], decalinfo); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pList - |
|
// count - |
|
// Output : static int |
|
//----------------------------------------------------------------------------- |
|
static int DecalListAdd( decallist_t *pList, int count ) |
|
{ |
|
int i; |
|
Vector tmp; |
|
decallist_t *pdecal; |
|
|
|
pdecal = pList + count; |
|
for ( i = 0; i < count; i++ ) |
|
{ |
|
if ( !Q_strcmp( pdecal->name, pList[i].name ) && |
|
pdecal->entityIndex == pList[i].entityIndex ) |
|
{ |
|
VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge |
|
if ( VectorLength( tmp ) < 2 ) // UNDONE: Tune this '2' constant |
|
return count; |
|
} |
|
} |
|
|
|
// This is a new decal |
|
return count + 1; |
|
} |
|
|
|
|
|
typedef int (__cdecl *qsortFunc_t)( const void *, const void * ); |
|
|
|
static int __cdecl DecalDepthCompare( const decallist_t *elem1, const decallist_t *elem2 ) |
|
{ |
|
if ( elem1->depth > elem2->depth ) |
|
return -1; |
|
if ( elem1->depth < elem2->depth ) |
|
return 1; |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called by CSaveRestore::SaveClientState |
|
// Input : *pList - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int DecalListCreate( decallist_t *pList ) |
|
{ |
|
int total = 0; |
|
int i, depth; |
|
|
|
if ( host_state.worldmodel ) |
|
{ |
|
for ( i = 0; i < g_nMaxDecals; i++ ) |
|
{ |
|
decal_t *decal = s_aDecalPool[i]; |
|
|
|
// Decal is in use and is not a custom decal |
|
if ( !decal || !IS_SURF_VALID( decal->surfID ) || (decal->flags & ( FDECAL_CUSTOM | FDECAL_DONTSAVE ) ) ) |
|
continue; |
|
|
|
decal_t *pdecals; |
|
IMaterial *pMaterial; |
|
|
|
// compute depth |
|
depth = 0; |
|
pdecals = MSurf_DecalPointer( decal->surfID ); |
|
while ( pdecals && pdecals != decal ) |
|
{ |
|
depth++; |
|
pdecals = pdecals->pnext; |
|
} |
|
pList[total].depth = depth; |
|
pList[total].flags = decal->flags; |
|
|
|
R_DecalUnProject( decal, &pList[total] ); |
|
|
|
pMaterial = decal->material; |
|
Q_strncpy( pList[total].name, pMaterial->GetName(), sizeof( pList[total].name ) ); |
|
|
|
// Check to see if the decal should be added |
|
total = DecalListAdd( pList, total ); |
|
} |
|
} |
|
|
|
// Sort the decals lowest depth first, so they can be re-applied in order |
|
qsort( pList, total, sizeof(decallist_t), ( qsortFunc_t )DecalDepthCompare ); |
|
|
|
return total; |
|
} |
|
// --------------------------------------------------------- |
|
|
|
static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry ) |
|
{ |
|
if ( !pdecal || !IS_SURF_VALID( pdecal->surfID ) ) |
|
return false; |
|
|
|
VectorCopy( pdecal->position, entry->position ); |
|
entry->entityIndex = pdecal->entityIndex; |
|
|
|
// Grab surface plane equation |
|
cplane_t plane = MSurf_Plane( pdecal->surfID ); |
|
|
|
VectorCopy( plane.normal, entry->impactPlaneNormal ); |
|
return true; |
|
} |
|
|
|
|
|
// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords |
|
static void R_DecalShoot_( IMaterial *pMaterial, int entity, const model_t *model, |
|
const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal, void *userdata = 0 ) |
|
{ |
|
decalinfo_t decalInfo; |
|
decalInfo.m_flFadeDuration = 0; |
|
decalInfo.m_flFadeStartTime = 0; |
|
|
|
VectorCopy( position, decalInfo.m_Position ); // Pass position in global |
|
|
|
if ( !model || model->type != mod_brush || !pMaterial ) |
|
return; |
|
|
|
decalInfo.m_pModel = (model_t *)model; |
|
decalInfo.m_pBrush = model->brush.pShared; |
|
|
|
// Deal with the s axis if one was passed in |
|
if (saxis) |
|
{ |
|
flags |= FDECAL_USESAXIS; |
|
VectorCopy( *saxis, decalInfo.m_SAxis ); |
|
} |
|
|
|
// More state used by R_DecalNode() |
|
decalInfo.m_pMaterial = pMaterial; |
|
decalInfo.m_pUserData = userdata; |
|
|
|
decalInfo.m_Flags = flags; |
|
decalInfo.m_Entity = entity; |
|
decalInfo.m_Size = pMaterial->GetMappingWidth() >> 1; |
|
if ( (int)(pMaterial->GetMappingHeight() >> 1) > decalInfo.m_Size ) |
|
decalInfo.m_Size = pMaterial->GetMappingHeight() >> 1; |
|
|
|
// Compute scale of surface |
|
// FIXME: cache this? |
|
IMaterialVar *decalScaleVar; |
|
bool found; |
|
decalScaleVar = decalInfo.m_pMaterial->FindVar( "$decalScale", &found, false ); |
|
if( found ) |
|
{ |
|
decalInfo.m_scale = 1.0f / decalScaleVar->GetFloatValue(); |
|
decalInfo.m_Size *= decalScaleVar->GetFloatValue(); |
|
} |
|
else |
|
{ |
|
decalInfo.m_scale = 1.0f; |
|
} |
|
|
|
// compute the decal dimensions in world space |
|
decalInfo.m_decalWidth = pMaterial->GetMappingWidth() / decalInfo.m_scale; |
|
decalInfo.m_decalHeight = pMaterial->GetMappingHeight() / decalInfo.m_scale; |
|
decalInfo.m_Color = rgbaColor; |
|
decalInfo.m_pNormal = pNormal; |
|
decalInfo.m_aApplySurfs.Purge(); |
|
|
|
// Clear the displacement tags because we use them in R_DecalNode. |
|
DispInfo_ClearAllTags( decalInfo.m_pBrush->hDispInfos ); |
|
|
|
mnode_t *pnodes = decalInfo.m_pBrush->nodes + decalInfo.m_pModel->brush.firstnode; |
|
R_DecalNode( pnodes, &decalInfo ); |
|
} |
|
|
|
// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords |
|
// This is called from cl_parse.cpp, cl_tent.cpp |
|
void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal ) |
|
{ |
|
IMaterial* pMaterial = Draw_DecalMaterial( textureIndex ); |
|
R_DecalShoot_( pMaterial, entity, model, position, saxis, flags, rgbaColor, pNormal ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *material - |
|
// playerIndex - |
|
// entity - |
|
// *model - |
|
// position - |
|
// *saxis - |
|
// flags - |
|
// &rgbaColor - |
|
//----------------------------------------------------------------------------- |
|
|
|
void R_PlayerDecalShoot( IMaterial *material, void *userdata, int entity, const model_t *model, |
|
const Vector& position, const Vector *saxis, int flags, const color32 &rgbaColor ) |
|
{ |
|
// The userdata that is passed in is actually |
|
// the player number (integer), not sure why it can't be zero. |
|
Assert( userdata != 0 ); |
|
|
|
// |
|
// Linear search through decal pool to retire any other decals this |
|
// player has sprayed. It appears that multiple decals can be |
|
// allocated for a single spray due to the way they are mapped to |
|
// surfaces. We need to run through and clean them all up. This |
|
// seems like the cleanest way to manage this - especially since |
|
// it doesn't happen that often. |
|
// |
|
int i; |
|
CUtlVector<decal_t *> decalVec; |
|
|
|
for ( i = 0; i<s_aDecalPool.Count(); i++ ) |
|
{ |
|
decal_t * decal = s_aDecalPool[i]; |
|
|
|
if( decal && (decal->flags & FDECAL_PLAYERSPRAY) && (decal->userdata == userdata) ) |
|
{ |
|
decalVec.AddToTail( decal ); |
|
} |
|
} |
|
|
|
// remove all the sprays we found |
|
for ( i = 0; i < decalVec.Count(); i++ ) |
|
{ |
|
R_DecalUnlink( decalVec[i], host_state.worldbrush ); |
|
} |
|
|
|
// set this to be a player spray so it is timed out appropriately. |
|
flags |= FDECAL_PLAYERSPRAY; |
|
|
|
R_DecalShoot_( material, entity, model, position, saxis, flags, rgbaColor, NULL, userdata ); |
|
} |
|
|
|
struct decalcontext_t |
|
{ |
|
Vector vModelOrg; |
|
Vector sAxis; |
|
float sOffset; |
|
Vector tAxis; |
|
float tOffset; |
|
float sScale; |
|
float tScale; |
|
IMatRenderContext *pRenderContext; |
|
SurfaceHandle_t pSurf; |
|
|
|
decalcontext_t( IMatRenderContext *pContext, const Vector &vModelorg ) |
|
{ |
|
pRenderContext = pContext; |
|
vModelOrg = vModelorg; |
|
pSurf = NULL; |
|
|
|
} |
|
|
|
void InitSurface( SurfaceHandle_t surfID ) |
|
{ |
|
if ( pSurf == surfID ) |
|
return; |
|
pSurf = surfID; |
|
mtexinfo_t* pTexInfo = MSurf_TexInfo( surfID ); |
|
|
|
int lightmapPageWidth, lightmapPageHeight; |
|
materials->GetLightmapPageSize( SortInfoToLightmapPage(MSurf_MaterialSortID( surfID )),&lightmapPageWidth, &lightmapPageHeight ); |
|
|
|
sScale = 1.0f / float(lightmapPageWidth); |
|
tScale = 1.0f / float(lightmapPageHeight); |
|
msurfacelighting_t *pSurfacelighting = SurfaceLighting(surfID); |
|
sOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][3] - pSurfacelighting->m_LightmapMins[0] + |
|
pSurfacelighting->m_OffsetIntoLightmapPage[0] + 0.5f; |
|
tOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][3] - pSurfacelighting->m_LightmapMins[1] + |
|
pSurfacelighting->m_OffsetIntoLightmapPage[1] + 0.5f; |
|
sAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D(); |
|
tAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D(); |
|
|
|
} |
|
inline float ComputeS( const Vector &pos ) const |
|
{ |
|
return sScale * (DotProduct(pos, sAxis) + sOffset); |
|
} |
|
inline float ComputeT( const Vector &pos ) const |
|
{ |
|
return tScale * (DotProduct(pos, tAxis) + tOffset); |
|
} |
|
}; |
|
|
|
// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf |
|
static void R_DecalVertsLight( CDecalVert* v, const decalcontext_t &context, SurfaceHandle_t surfID, int vertCount ) |
|
{ |
|
for ( int j = 0; j < vertCount; j++, v++ ) |
|
{ |
|
v->m_cLMCoords.x = context.ComputeS(v->m_vPos); |
|
v->m_cLMCoords.y = context.ComputeT(v->m_vPos); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check for intersecting decals on this surface |
|
// Input : *psurf - |
|
// *pcount - |
|
// x - |
|
// y - |
|
// Output : static decal_t |
|
//----------------------------------------------------------------------------- |
|
// UNDONE: This probably doesn't work quite right any more |
|
// we should base overlap on the new decal basis matrix |
|
// decal basis is constant per plane, perhaps we should store it (unscaled) in the shared plane struct |
|
// BRJ: Note, decal basis is not constant when decals need to specify an s direction |
|
// but that certainly isn't the majority case |
|
static decal_t *R_DecalFindOverlappingDecals( decalinfo_t* decalinfo, SurfaceHandle_t surfID ) |
|
{ |
|
decal_t *plast = NULL; |
|
|
|
// (Same as R_SetupDecalClip). |
|
IMaterial *pMaterial = decalinfo->m_pMaterial; |
|
|
|
int count = 0; |
|
|
|
// Precalculate the extents of decalinfo's decal in world space. |
|
int mapSize[2] = {pMaterial->GetMappingWidth(), pMaterial->GetMappingHeight()}; |
|
Vector decalExtents[2]; |
|
// this is half the width in world space of the decal. |
|
float minProjectedWidth = (mapSize[0] / decalinfo->m_scale) * 0.5; |
|
decalExtents[0] = decalinfo->m_Basis[0] * minProjectedWidth; |
|
decalExtents[1] = decalinfo->m_Basis[1] * (mapSize[1] / decalinfo->m_scale) * 0.5f; |
|
|
|
float areaThreshold = r_decal_overlap_area.GetFloat(); |
|
float lastArea = 0; |
|
bool bFullMatch = false; |
|
decal_t *pDecal = MSurf_DecalPointer( surfID ); |
|
CUtlVectorFixedGrowable<decal_t *,32> coveredList; |
|
while ( pDecal ) |
|
{ |
|
pMaterial = pDecal->material; |
|
|
|
// Don't steal bigger decals and replace them with smaller decals |
|
// Don't steal permanent decals, or player sprays |
|
if ( !(pDecal->flags & FDECAL_PERMANENT) && |
|
!(pDecal->flags & FDECAL_PLAYERSPRAY) && pMaterial ) |
|
{ |
|
Vector testBasis[3]; |
|
float testWorldScale[2]; |
|
R_SetupDecalTextureSpaceBasis( pDecal, MSurf_Plane( surfID ).normal, pMaterial, testBasis, testWorldScale ); |
|
|
|
// Here, we project the min and max extents of the decal that got passed in into |
|
// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were |
|
// clipping a triangle into pDecal's clip space. |
|
Vector2D vDecalMin( |
|
DotProduct( decalinfo->m_Position - decalExtents[0], testBasis[0] ) - pDecal->dx + 0.5f, |
|
DotProduct( decalinfo->m_Position - decalExtents[1], testBasis[1] ) - pDecal->dy + 0.5f ); |
|
|
|
Vector2D vDecalMax( |
|
DotProduct( decalinfo->m_Position + decalExtents[0], testBasis[0] ) - pDecal->dx + 0.5f, |
|
DotProduct( decalinfo->m_Position + decalExtents[1], testBasis[1] ) - pDecal->dy + 0.5f ); |
|
|
|
// Now figure out the part of the projection that intersects pDecal's |
|
// clip box [0,0,1,1]. |
|
Vector2D vUnionMin( fpmax( vDecalMin.x, 0.0f ), fpmax( vDecalMin.y, 0.0f ) ); |
|
Vector2D vUnionMax( fpmin( vDecalMax.x, 1.0f ), fpmin( vDecalMax.y, 1.0f ) ); |
|
|
|
// if the decal is less than half the width of the one we're applying, don't test for overlap |
|
// test for complete coverage |
|
float projectWidthTestedDecal = pDecal->material->GetMappingWidth() / pDecal->scale; |
|
|
|
float sizex = vUnionMax.x - vUnionMin.x; |
|
float sizey = vUnionMax.y - vUnionMin.y; |
|
if( sizex >= 0 && sizey >= 0) |
|
{ |
|
// Figure out how much of this intersects the (0,0) - (1,1) bbox. |
|
float flArea = sizex * sizey; |
|
|
|
if ( projectWidthTestedDecal < minProjectedWidth ) |
|
{ |
|
if ( flArea > 0.999f ) |
|
{ |
|
coveredList.AddToTail(pDecal); |
|
} |
|
} |
|
else |
|
{ |
|
if( flArea > areaThreshold ) |
|
{ |
|
// once you pass the threshold, scale the area by the decal size to select the largest |
|
// decal above the threshold |
|
float flAreaScaled = flArea * projectWidthTestedDecal; |
|
count++; |
|
if ( !plast || flAreaScaled > lastArea ) |
|
{ |
|
plast = pDecal; |
|
lastArea = flAreaScaled; |
|
// go ahead and remove even if you're not at the max overlap count yet because this is a very similar decal |
|
bFullMatch = ( flArea >= 0.9f ) ? true : false; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
pDecal = pDecal->pnext; |
|
} |
|
|
|
if ( plast ) |
|
{ |
|
if ( count < r_decal_overlap_count.GetInt() && !bFullMatch ) |
|
{ |
|
plast = NULL; |
|
} |
|
} |
|
if ( coveredList.Count() > r_decal_cover_count.GetInt() ) |
|
{ |
|
int last = coveredList.Count() - r_decal_cover_count.GetInt(); |
|
for ( int i = 0; i < last; i++ ) |
|
{ |
|
R_DecalUnlink( coveredList[i], host_state.worldbrush ); |
|
} |
|
} |
|
|
|
return plast; |
|
} |
|
|
|
|
|
// Add the decal to the surface's list of decals. |
|
// If the surface is a displacement, let the displacement precalculate data for the decal. |
|
static void R_AddDecalToSurface( |
|
decal_t *pdecal, |
|
SurfaceHandle_t surfID, |
|
decalinfo_t *decalinfo ) |
|
{ |
|
pdecal->pnext = NULL; |
|
decal_t *pold = MSurf_DecalPointer( surfID ); |
|
if ( pold ) |
|
{ |
|
while ( pold->pnext ) |
|
pold = pold->pnext; |
|
pold->pnext = pdecal; |
|
} |
|
else |
|
{ |
|
MSurf_Decals( surfID ) = DecalToHandle(pdecal); |
|
} |
|
|
|
// Tag surface |
|
pdecal->surfID = surfID; |
|
pdecal->flSize = decalinfo->m_Size; |
|
pdecal->lightmapOffset = ComputeDecalLightmapOffset( surfID ); |
|
// Let the dispinfo reclip the decal if need be. |
|
if( SurfaceHasDispInfo( surfID ) ) |
|
{ |
|
pdecal->m_DispDecal = MSurf_DispInfo( surfID )->NotifyAddDecal( pdecal, decalinfo->m_Size ); |
|
} |
|
|
|
// Add surface to list. |
|
decalinfo->m_aApplySurfs.AddToTail( surfID ); |
|
} |
|
|
|
//============================================================================= |
|
// |
|
// Decal batches for rendering. |
|
// |
|
CUtlVector<DecalSortVertexFormat_t> g_aDecalFormats; |
|
|
|
CUtlVector<DecalSortTrees_t> g_aDecalSortTrees; |
|
CUtlFixedLinkedList<decal_t*> g_aDecalSortPool; |
|
int g_nDecalSortCheckCount; |
|
int g_nBrushModelDecalSortCheckCount; |
|
|
|
CUtlFixedLinkedList<decal_t*> g_aDispDecalSortPool; |
|
CUtlVector<DecalSortTrees_t> g_aDispDecalSortTrees; |
|
int g_nDispDecalSortCheckCount; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void R_DecalSortInit( void ) |
|
{ |
|
g_aDecalFormats.Purge(); |
|
|
|
g_aDecalSortTrees.Purge(); |
|
g_aDecalSortPool.Purge(); |
|
g_aDecalSortPool.EnsureCapacity( g_nMaxDecals ); |
|
g_aDecalSortPool.SetGrowSize( 128 ); |
|
g_nDecalSortCheckCount = 0; |
|
g_nBrushModelDecalSortCheckCount = 0; |
|
|
|
g_aDispDecalSortTrees.Purge(); |
|
g_aDispDecalSortPool.Purge(); |
|
g_aDispDecalSortPool.EnsureCapacity( g_nMaxDecals ); |
|
g_aDispDecalSortPool.SetGrowSize( 128 ); |
|
g_nDispDecalSortCheckCount = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void DecalSurfacesInit( bool bBrushModel ) |
|
{ |
|
if ( !bBrushModel ) |
|
{ |
|
// Only clear the pool once per frame. |
|
g_aDecalSortPool.RemoveAll(); |
|
// Retire decals on opaque brushmodel surfaces |
|
R_DecalFlushDestroyList(); |
|
++g_nDecalSortCheckCount; |
|
} |
|
else |
|
{ |
|
++g_nBrushModelDecalSortCheckCount; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
static void R_DecalMaterialSort( decal_t *pDecal, SurfaceHandle_t surfID ) |
|
{ |
|
// Setup the decal material sort data. |
|
DecalMaterialSortData_t sort; |
|
if ( pDecal->material->InMaterialPage() ) |
|
{ |
|
sort.m_pMaterial = pDecal->material->GetMaterialPage(); |
|
} |
|
else |
|
{ |
|
sort.m_pMaterial = pDecal->material; |
|
} |
|
sort.m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; |
|
|
|
// Does this vertex type exist? |
|
VertexFormat_t vertexFormat = GetUncompressedFormat( sort.m_pMaterial ); |
|
int iFormat = 0; |
|
int nFormatCount = g_aDecalFormats.Count(); |
|
for ( ; iFormat < nFormatCount; ++iFormat ) |
|
{ |
|
if ( g_aDecalFormats[iFormat].m_VertexFormat == vertexFormat ) |
|
break; |
|
} |
|
|
|
// A new vertex format type. |
|
if ( iFormat == nFormatCount ) |
|
{ |
|
iFormat = g_aDecalFormats.AddToTail(); |
|
g_aDecalFormats[iFormat].m_VertexFormat = vertexFormat; |
|
int iSortTree = g_aDecalSortTrees.AddToTail(); |
|
g_aDispDecalSortTrees.AddToTail(); |
|
g_aDecalFormats[iFormat].m_iSortTree = iSortTree; |
|
} |
|
|
|
// Get an index for the current sort tree. |
|
int iSortTree = g_aDecalFormats[iFormat].m_iSortTree; |
|
int iTreeType = -1; |
|
|
|
// Lightmapped. |
|
if ( sort.m_pMaterial->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ) ) |
|
{ |
|
// Permanent lightmapped decals. |
|
if ( pDecal->flags & FDECAL_PERMANENT ) |
|
{ |
|
iTreeType = PERMANENT_LIGHTMAP; |
|
} |
|
// Non-permanent lightmapped decals. |
|
else |
|
{ |
|
iTreeType = LIGHTMAP; |
|
} |
|
} |
|
// Non-lightmapped decals. |
|
else |
|
{ |
|
iTreeType = NONLIGHTMAP; |
|
sort.m_iLightmapPage = -1; |
|
} |
|
|
|
int iSort = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Find( sort ); |
|
if ( iSort == -1 ) |
|
{ |
|
int iBucket = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail(); |
|
g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail(); |
|
|
|
g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1; |
|
g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1; |
|
|
|
for ( int iGroup = 1; iGroup < ( MAX_MAT_SORT_GROUPS + 1 ); ++iGroup ) |
|
{ |
|
g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail(); |
|
g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail(); |
|
|
|
g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1; |
|
g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1; |
|
} |
|
|
|
sort.m_iBucket = iBucket; |
|
g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort ); |
|
g_aDispDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort ); |
|
|
|
pDecal->m_iSortTree = iSortTree; |
|
pDecal->m_iSortMaterial = sort.m_iBucket; |
|
} |
|
else |
|
{ |
|
pDecal->m_iSortTree = iSortTree; |
|
pDecal->m_iSortMaterial = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Element( iSort ).m_iBucket; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void R_DecalReSortMaterials( void ) //X |
|
{ |
|
R_DecalSortInit(); |
|
|
|
int nDecalCount = s_aDecalPool.Count(); |
|
for ( int iDecal = 0; iDecal < nDecalCount; ++iDecal ) |
|
{ |
|
decal_t *pDecal = s_aDecalPool.Element( iDecal ); |
|
if ( pDecal ) |
|
{ |
|
SurfaceHandle_t surfID = pDecal->surfID; |
|
R_DecalMaterialSort( pDecal, surfID ); |
|
} |
|
} |
|
} |
|
|
|
// Allocate and initialize a decal from the pool, on surface with offsets x, y |
|
// UNDONE: offsets are not really meaningful in new decal coordinate system |
|
// the clipping code will recalc the offsets |
|
static void R_DecalCreate( decalinfo_t* decalinfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement ) |
|
{ |
|
decal_t *pdecal; |
|
|
|
if( !IS_SURF_VALID( surfID ) ) |
|
{ |
|
ConMsg( "psurface NULL in R_DecalCreate!\n" ); |
|
return; |
|
} |
|
|
|
decal_t *pold = R_DecalFindOverlappingDecals( decalinfo, surfID ); |
|
if ( pold ) |
|
{ |
|
R_DecalUnlink( pold, host_state.worldbrush ); |
|
pold = NULL; |
|
} |
|
|
|
pdecal = R_DecalAlloc( decalinfo->m_Flags ); |
|
|
|
pdecal->flags = decalinfo->m_Flags; |
|
pdecal->color = decalinfo->m_Color; |
|
VectorCopy( decalinfo->m_Position, pdecal->position ); |
|
if (pdecal->flags & FDECAL_USESAXIS) |
|
VectorCopy( decalinfo->m_SAxis, pdecal->saxis ); |
|
pdecal->dx = x; |
|
pdecal->dy = y; |
|
pdecal->material = decalinfo->m_pMaterial; |
|
Assert( pdecal->material ); |
|
pdecal->userdata = decalinfo->m_pUserData; |
|
|
|
// Set scaling |
|
pdecal->scale = decalinfo->m_scale; |
|
pdecal->entityIndex = decalinfo->m_Entity; |
|
|
|
// Get dynamic information from the material (fade start, fade time) |
|
if ( decalinfo->m_flFadeDuration > 0.0f ) |
|
{ |
|
pdecal->flags |= FDECAL_DYNAMIC; |
|
pdecal->fadeDuration = decalinfo->m_flFadeDuration; |
|
pdecal->fadeStartTime = decalinfo->m_flFadeStartTime; |
|
pdecal->fadeStartTime += cl.GetTime(); |
|
} |
|
|
|
// check for a player spray |
|
if( pdecal->flags & FDECAL_PLAYERSPRAY ) |
|
{ |
|
// reset the number of rounds this should be visible for |
|
pdecal->fadeStartTime = 0.0f; |
|
|
|
// Force the scale to 1 for player sprays. |
|
pdecal->scale = 1.0f; |
|
} |
|
|
|
if( !bForceForDisplacement ) |
|
{ |
|
// Check to see if the decal actually intersects the surface |
|
// if not, then remove the decal |
|
R_DecalVertsClip( NULL, pdecal, surfID, decalinfo->m_pMaterial ); |
|
if ( !pdecal->clippedVertCount ) |
|
{ |
|
R_DecalUnlink( pdecal, host_state.worldbrush ); |
|
return; |
|
} |
|
} |
|
|
|
// Add to the surface's list |
|
R_AddDecalToSurface( pdecal, surfID, decalinfo ); |
|
|
|
// Add decal material/lightmap to sort list. |
|
R_DecalMaterialSort( pdecal, surfID ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Updates all decals, returns true if the decal should be retired |
|
//----------------------------------------------------------------------------- |
|
|
|
bool DecalUpdate( decal_t* pDecal ) |
|
{ |
|
// retire the decal if it's time has come |
|
if (pDecal->fadeDuration > 0) |
|
{ |
|
return (cl.GetTime() >= pDecal->fadeStartTime + pDecal->fadeDuration); |
|
} |
|
return false; |
|
} |
|
|
|
#define NEXT_MULTIPLE_OF_4(P) ( ((P) + ((4)-1)) & (~((4)-1)) ) |
|
|
|
// Build the vertex list for a decal on a surface and clip it to the surface. |
|
// This is a template so it can work on world surfaces and dynamic displacement |
|
// triangles the same way. |
|
CDecalVert* R_DecalSetupVerts( decalcontext_t &context, decal_t *pDecal, SurfaceHandle_t surfID, IMaterial *pMaterial ) |
|
{ |
|
// |
|
// Do not scale playersprays |
|
// |
|
if( pDecal->flags & FDECAL_DISTANCESCALE ) |
|
{ |
|
if( !(pDecal->flags & FDECAL_PLAYERSPRAY) ) |
|
{ |
|
float scaleFactor = 1.0f; |
|
float nearScale, farScale, nearDist, farDist; |
|
|
|
nearScale = r_dscale_nearscale.GetFloat(); |
|
nearDist = r_dscale_neardist.GetFloat(); |
|
farScale = r_dscale_farscale.GetFloat(); |
|
farDist = r_dscale_fardist.GetFloat(); |
|
|
|
Vector playerOrigin = CurrentViewOrigin(); |
|
|
|
float dist = 0; |
|
|
|
if ( pDecal->entityIndex == 0 ) |
|
{ |
|
dist = (playerOrigin - pDecal->position).Length(); |
|
} |
|
else |
|
{ |
|
Vector worldSpaceCenter; |
|
Vector3DMultiplyPosition(g_BrushToWorldMatrix, pDecal->position, worldSpaceCenter ); |
|
dist = (playerOrigin - worldSpaceCenter).Length(); |
|
} |
|
float fov = g_EngineRenderer->GetFov(); |
|
|
|
// |
|
// If the player is zoomed in, we adjust the nearScale and farScale |
|
// |
|
if ( fov != r_dscale_basefov.GetFloat() && fov > 0 && r_dscale_basefov.GetFloat() > 0 ) |
|
{ |
|
float fovScale = fov / r_dscale_basefov.GetFloat(); |
|
nearScale *= fovScale; |
|
farScale *= fovScale; |
|
|
|
if ( nearScale < 1.0f ) |
|
nearScale = 1.0f; |
|
if ( farScale < 1.0f ) |
|
farScale = 1.0f; |
|
} |
|
|
|
// |
|
// Scaling works like this: |
|
// |
|
// 0->nearDist scale = 1.0 |
|
// nearDist -> farDist scale = LERP(nearScale, farScale) |
|
// farDist->inf scale = farScale |
|
// |
|
// scaling in the rest of the code appears to be more of an |
|
// attenuation factor rather than a scale, so we compute 1/scale |
|
// to account for this. |
|
// |
|
if ( dist < nearDist ) |
|
scaleFactor = 1.0f; |
|
else if( dist >= farDist ) |
|
scaleFactor = farScale; |
|
else |
|
{ |
|
float percent = (dist - nearDist) / (farDist - nearDist); |
|
scaleFactor = nearScale + percent * (farScale - nearScale); |
|
} |
|
|
|
// |
|
// scaling in the rest of the code appears to be more of an |
|
// attenuation factor rather than a scale, so we compute 1/scale |
|
// to account for this. |
|
// |
|
scaleFactor = 1.0f / scaleFactor; |
|
float originalScale = pDecal->scale; |
|
float scaledScale = pDecal->scale * scaleFactor; |
|
pDecal->scale = scaledScale; |
|
|
|
context.InitSurface( pDecal->surfID ); |
|
|
|
CDecalVert *v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial ); |
|
if ( v ) |
|
{ |
|
R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount ); |
|
} |
|
pDecal->scale = originalScale; |
|
return v; |
|
} |
|
} |
|
|
|
// find in cache? |
|
CDecalVert *v = g_DecalVertCache.GetCachedVerts(pDecal); |
|
if ( !v ) |
|
{ |
|
// not in cache, clip & light |
|
context.InitSurface( pDecal->surfID ); |
|
v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial ); |
|
if ( pDecal->clippedVertCount ) |
|
{ |
|
|
|
#if _DEBUG |
|
// squash vector copy asserts in debug |
|
int nextVert = NEXT_MULTIPLE_OF_4(pDecal->clippedVertCount); |
|
if ( (nextVert - pDecal->clippedVertCount) < 4 ) |
|
{ |
|
for ( int i = pDecal->clippedVertCount; i < nextVert; i++ ) |
|
{ |
|
v[i].m_cLMCoords.Init(); |
|
v[i].m_ctCoords.Init(); |
|
v[i].m_vPos.Init(); |
|
} |
|
} |
|
#endif |
|
R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount ); |
|
g_DecalVertCache.StoreVertsInCache( pDecal, v ); |
|
} |
|
} |
|
return v; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renders a single decal, *could retire the decal!!* |
|
//----------------------------------------------------------------------------- |
|
|
|
void DecalUpdateAndDrawSingle( decalcontext_t &context, SurfaceHandle_t surfID, decal_t* pDecal ) |
|
{ |
|
if( !pDecal->material ) |
|
return; |
|
|
|
// Update dynamic decals |
|
bool retire = false; |
|
if ( pDecal->flags & FDECAL_DYNAMIC ) |
|
retire = DecalUpdate( pDecal ); |
|
|
|
if( SurfaceHasDispInfo( surfID ) ) |
|
{ |
|
// Dispinfos generate lists of tris for decals when the decal is first |
|
// created. |
|
} |
|
else |
|
{ |
|
CDecalVert *v = R_DecalSetupVerts( context, pDecal, surfID, pDecal->material ); |
|
|
|
if ( v ) |
|
Shader_DecalDrawPoly( v, pDecal->material, surfID, pDecal->clippedVertCount, pDecal, 1.0f ); |
|
} |
|
|
|
if( retire ) |
|
{ |
|
R_DecalUnlink( pDecal, host_state.worldbrush ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renders all decals on a single surface |
|
//----------------------------------------------------------------------------- |
|
|
|
void DrawDecalsOnSingleSurface_NonQueued( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID, const Vector &vModelOrg) |
|
{ |
|
decal_t* plist = MSurf_DecalPointer( surfID ); |
|
decalcontext_t context(pRenderContext, vModelOrg); |
|
context.InitSurface(surfID); |
|
while ( plist ) |
|
{ |
|
// Store off the next pointer, DecalUpdateAndDrawSingle could unlink |
|
decal_t* pnext = plist->pnext; |
|
|
|
if (!(plist->flags & FDECAL_SECONDPASS)) |
|
{ |
|
DecalUpdateAndDrawSingle( context, surfID, plist ); |
|
} |
|
plist = pnext; |
|
} |
|
while ( plist ) |
|
{ |
|
// Store off the next pointer, DecalUpdateAndDrawSingle could unlink |
|
decal_t* pnext = plist->pnext; |
|
|
|
if ((plist->flags & FDECAL_SECONDPASS)) |
|
{ |
|
DecalUpdateAndDrawSingle( context, surfID, plist ); |
|
} |
|
plist = pnext; |
|
} |
|
} |
|
|
|
void DrawDecalsOnSingleSurface_QueueHelper( SurfaceHandle_t surfID, Vector vModelOrg ) |
|
{ |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, vModelOrg ); |
|
} |
|
|
|
void DrawDecalsOnSingleSurface( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID ) |
|
{ |
|
ICallQueue *pCallQueue; |
|
if ( r_queued_decals.GetBool() && (pCallQueue = pRenderContext->GetCallQueue()) != NULL ) |
|
{ |
|
//queue available and desired |
|
pCallQueue->QueueCall( DrawDecalsOnSingleSurface_QueueHelper, surfID, modelorg ); |
|
} |
|
else |
|
{ |
|
//non-queued mode |
|
DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, modelorg ); |
|
} |
|
} |
|
|
|
void R_DrawDecalsAllImmediate_GatherDecals( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, CUtlVector<decal_t *> &DrawDecals ) |
|
{ |
|
int nCheckCount = g_nDecalSortCheckCount; |
|
if ( iGroup == MAX_MAT_SORT_GROUPS ) |
|
{ |
|
// Brush Model |
|
nCheckCount = g_nBrushModelDecalSortCheckCount; |
|
} |
|
|
|
int nSortTreeCount = g_aDecalSortTrees.Count(); |
|
for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree ) |
|
{ |
|
int nBucketCount = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Count(); |
|
for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) |
|
{ |
|
if ( g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount != nCheckCount ) |
|
continue; |
|
|
|
intp iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead; |
|
|
|
intp iElement = iHead; |
|
while ( iElement != g_aDecalSortPool.InvalidIndex() ) |
|
{ |
|
decal_t *pDecal = g_aDecalSortPool.Element( iElement ); |
|
iElement = g_aDecalSortPool.Next( iElement ); |
|
|
|
if ( !pDecal ) |
|
continue; |
|
|
|
DrawDecals.AddToTail( pDecal ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void R_DrawDecalsAllImmediate_Gathered( IMatRenderContext *pRenderContext, decal_t **ppDecals, int iDecalCount, const Vector &vModelOrg, float flFade ) |
|
{ |
|
SurfaceHandle_t lastSurf = NULL; |
|
decalcontext_t context( pRenderContext, vModelOrg ); |
|
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); |
|
for( int i = 0; i != iDecalCount; ++i ) |
|
{ |
|
decal_t * pDecal = ppDecals[i]; |
|
|
|
Assert( pDecal != NULL ); |
|
|
|
// Add the decal to the list of decals to be destroyed if need be. |
|
if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) ) |
|
{ |
|
pDecal->flags |= FDECAL_HASUPDATED; |
|
if ( DecalUpdate( pDecal ) ) |
|
{ |
|
R_DecalAddToDestroyList( pDecal ); |
|
continue; |
|
} |
|
} |
|
|
|
if ( pDecal->surfID != lastSurf ) |
|
{ |
|
lastSurf = pDecal->surfID; |
|
} |
|
CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material ); |
|
if ( !pVerts ) |
|
continue; |
|
int nCount = pDecal->clippedVertCount; |
|
|
|
// Bind texture. |
|
VertexFormat_t vertexFormat = 0; |
|
if( bWireframe ) |
|
{ |
|
pRenderContext->Bind( g_materialDecalWireframe ); |
|
} |
|
else |
|
{ |
|
pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( pDecal->surfID )].lightmapPageID ); |
|
pRenderContext->Bind( pDecal->material, pDecal->userdata ); |
|
vertexFormat = GetUncompressedFormat( pDecal->material ); |
|
} |
|
|
|
IMesh *pMesh = NULL; |
|
pMesh = pRenderContext->GetDynamicMesh(); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nCount, ( ( nCount - 2 ) * 3 ) ); |
|
|
|
// Set base color. |
|
byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a }; |
|
if ( flFade != 1.0f ) |
|
{ |
|
color[3] = (byte)( color[3] * flFade ); |
|
} |
|
|
|
// Dynamic decals - fading. |
|
if ( pDecal->flags & FDECAL_DYNAMIC ) |
|
{ |
|
float flFadeValue; |
|
|
|
// Negative fadeDuration value means to fade in |
|
if ( pDecal->fadeDuration < 0 ) |
|
{ |
|
flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
else |
|
{ |
|
flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
|
|
flFadeValue = clamp( flFadeValue, 0.0f, 1.0f ); |
|
|
|
color[3] = ( byte )( color[3] * flFadeValue ); |
|
} |
|
|
|
// Compute normal and tangent space if necessary. |
|
Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); |
|
if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) ) |
|
{ |
|
vecNormal = MSurf_Plane( pDecal->surfID ).normal; |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
Vector tVect; |
|
bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect ); |
|
TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate ); |
|
} |
|
} |
|
|
|
// Setup verts. |
|
float flOffset = pDecal->lightmapOffset; |
|
for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts ) |
|
{ |
|
meshBuilder.Position3fv( pVerts->m_vPos.Base() ); |
|
if ( vertexFormat & VERTEX_NORMAL ) |
|
{ |
|
meshBuilder.Normal3fv( vecNormal.Base() ); |
|
} |
|
meshBuilder.Color4ubv( color ); |
|
meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y ); |
|
meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y ); |
|
meshBuilder.TexCoord1f( 2, flOffset ); |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
meshBuilder.TangentS3fv( vecTangentS.Base() ); |
|
meshBuilder.TangentT3fv( vecTangentT.Base() ); |
|
} |
|
|
|
meshBuilder.AdvanceVertexF<VTX_HAVEPOS|VTX_HAVENORMAL|VTX_HAVECOLOR,3>(); |
|
} |
|
|
|
// Setup indices. |
|
CIndexBuilder &indexBuilder = meshBuilder; |
|
indexBuilder.FastPolygon( 0, nCount - 2 ); |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void R_DrawDecalsAllImmediate( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade ) |
|
{ |
|
SurfaceHandle_t lastSurf = NULL; |
|
decalcontext_t context(pRenderContext, vModelOrg); |
|
int nSortTreeCount = g_aDecalSortTrees.Count(); |
|
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); |
|
for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree ) |
|
{ |
|
int nBucketCount = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Count(); |
|
for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) |
|
{ |
|
if ( g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount != nCheckCount ) |
|
continue; |
|
|
|
intp iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead; |
|
|
|
int nCount; |
|
intp iElement = iHead; |
|
while ( iElement != g_aDecalSortPool.InvalidIndex() ) |
|
{ |
|
decal_t *pDecal = g_aDecalSortPool.Element( iElement ); |
|
iElement = g_aDecalSortPool.Next( iElement ); |
|
|
|
if ( !pDecal ) |
|
continue; |
|
|
|
// Add the decal to the list of decals to be destroyed if need be. |
|
if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) ) |
|
{ |
|
pDecal->flags |= FDECAL_HASUPDATED; |
|
if ( DecalUpdate( pDecal ) ) |
|
{ |
|
R_DecalAddToDestroyList( pDecal ); |
|
continue; |
|
} |
|
} |
|
|
|
if ( pDecal->surfID != lastSurf ) |
|
{ |
|
lastSurf = pDecal->surfID; |
|
} |
|
CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material ); |
|
if ( !pVerts ) |
|
continue; |
|
nCount = pDecal->clippedVertCount; |
|
|
|
// Bind texture. |
|
VertexFormat_t vertexFormat = 0; |
|
if( bWireframe ) |
|
{ |
|
pRenderContext->Bind( g_materialDecalWireframe ); |
|
} |
|
else |
|
{ |
|
pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( pDecal->surfID )].lightmapPageID ); |
|
pRenderContext->Bind( pDecal->material, pDecal->userdata ); |
|
vertexFormat = GetUncompressedFormat( pDecal->material ); |
|
} |
|
|
|
IMesh *pMesh = NULL; |
|
pMesh = pRenderContext->GetDynamicMesh(); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nCount, ( ( nCount - 2 ) * 3 ) ); |
|
|
|
// Set base color. |
|
byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a }; |
|
if ( flFade != 1.0f ) |
|
{ |
|
color[3] = (byte)( color[3] * flFade ); |
|
} |
|
|
|
// Dynamic decals - fading. |
|
if ( pDecal->flags & FDECAL_DYNAMIC ) |
|
{ |
|
float flFadeValue; |
|
|
|
// Negative fadeDuration value means to fade in |
|
if ( pDecal->fadeDuration < 0 ) |
|
{ |
|
flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
else |
|
{ |
|
flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
|
|
flFadeValue = clamp( flFadeValue, 0.0f, 1.0f ); |
|
|
|
color[3] = ( byte )( color[3] * flFadeValue ); |
|
} |
|
|
|
// Compute normal and tangent space if necessary. |
|
Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); |
|
if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) ) |
|
{ |
|
vecNormal = MSurf_Plane( pDecal->surfID ).normal; |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
Vector tVect; |
|
bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect ); |
|
TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate ); |
|
} |
|
} |
|
|
|
// Setup verts. |
|
float flOffset = pDecal->lightmapOffset; |
|
for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts ) |
|
{ |
|
meshBuilder.Position3fv( pVerts->m_vPos.Base() ); |
|
if ( vertexFormat & VERTEX_NORMAL ) |
|
{ |
|
meshBuilder.Normal3fv( vecNormal.Base() ); |
|
} |
|
meshBuilder.Color4ubv( color ); |
|
meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y ); |
|
meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y ); |
|
meshBuilder.TexCoord1f( 2, flOffset ); |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
meshBuilder.TangentS3fv( vecTangentS.Base() ); |
|
meshBuilder.TangentT3fv( vecTangentT.Base() ); |
|
} |
|
|
|
meshBuilder.AdvanceVertexF<VTX_HAVEPOS|VTX_HAVENORMAL|VTX_HAVECOLOR,3>(); |
|
} |
|
|
|
// Setup indices. |
|
int nTriCount = ( nCount - 2 ); |
|
CIndexBuilder &indexBuilder = meshBuilder; |
|
indexBuilder.FastPolygon( 0, nTriCount ); |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
inline void R_DrawDecalMeshList( DecalMeshList_t &meshList ) |
|
{ |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
int nBatchCount = meshList.m_aBatches.Count(); |
|
for ( int iBatch = 0; iBatch < nBatchCount; ++iBatch ) |
|
{ |
|
if ( g_pMaterialSystemConfig->nFullbright == 1 ) |
|
{ |
|
pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE ); |
|
} |
|
else |
|
{ |
|
pRenderContext->BindLightmapPage( meshList.m_aBatches[iBatch].m_iLightmapPage ); |
|
} |
|
|
|
pRenderContext->Bind( meshList.m_aBatches[iBatch].m_pMaterial, meshList.m_aBatches[iBatch].m_pProxy ); |
|
meshList.m_pMesh->Draw( meshList.m_aBatches[iBatch].m_iStartIndex, meshList.m_aBatches[iBatch].m_nIndexCount ); |
|
} |
|
} |
|
|
|
#define DECALMARKERS_SWITCHSORTTREE ((decal_t *)0x00000000) |
|
#define DECALMARKERS_SWITCHBUCKET ((decal_t *)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; |
|
|
|
intp iHead = bucket.m_iHead; |
|
if ( !g_aDecalSortPool.IsValidIndex( iHead ) ) |
|
continue; |
|
|
|
decal_t *pDecalHead = g_aDecalSortPool.Element( iHead ); |
|
Assert( pDecalHead->material ); |
|
if ( !pDecalHead->material ) |
|
continue; |
|
|
|
// Vertex format. |
|
VertexFormat_t vertexFormat = GetUncompressedFormat( pDecalHead->material ); |
|
if ( vertexFormat == 0 ) |
|
continue; |
|
|
|
DrawDecals.AddToTail( DECALMARKERS_SWITCHBUCKET ); |
|
|
|
intp iElement = iHead; |
|
while ( iElement != g_aDecalSortPool.InvalidIndex() ) |
|
{ |
|
decal_t *pDecal = g_aDecalSortPool.Element( iElement ); |
|
iElement = g_aDecalSortPool.Next( iElement ); |
|
|
|
if ( !pDecal ) |
|
continue; |
|
|
|
DrawDecals.AddToTail( pDecal ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void R_DecalsGetMaxMesh( IMatRenderContext *pRenderContext, int &nDecalSortMaxVerts, int &nDecalSortMaxIndices ) |
|
{ |
|
nDecalSortMaxVerts = g_nMaxDecals * 5; |
|
nDecalSortMaxIndices = nDecalSortMaxVerts * 3; |
|
int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); |
|
nDecalSortMaxIndices = MIN(nDecalSortMaxIndices, nMaxIndices); |
|
nDecalSortMaxVerts = MIN(nDecalSortMaxVerts, 8192); // just a guess, you should be able to do 8K dynamic verts in any material and this is no big loss on batching |
|
} |
|
|
|
void R_DrawDecalsAll_Gathered( IMatRenderContext *pRenderContext, decal_t **ppDecals, int iDecalCount, const Vector &vModelOrg, float flFade ) |
|
{ |
|
DecalMeshList_t meshList; |
|
CMeshBuilder meshBuilder; |
|
|
|
int nVertCount = 0; |
|
int nIndexCount = 0; |
|
|
|
int nCount; |
|
|
|
int nDecalSortMaxVerts; |
|
int nDecalSortMaxIndices; |
|
R_DecalsGetMaxMesh( pRenderContext, nDecalSortMaxVerts, nDecalSortMaxIndices ); |
|
|
|
bool bMeshInit = true; |
|
bool bBatchInit = true; |
|
|
|
DecalBatchList_t *pBatch = NULL; |
|
VertexFormat_t vertexFormat = 0; |
|
decal_t *pDecalHead = NULL; |
|
SurfaceHandle_t lastSurf = NULL; |
|
decalcontext_t context(pRenderContext, vModelOrg); |
|
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); |
|
|
|
for( int i = 0; i != iDecalCount; ++i ) |
|
{ |
|
decal_t *pDecal = ppDecals[i]; |
|
if( (pDecal == DECALMARKERS_SWITCHSORTTREE) || (pDecal == DECALMARKERS_SWITCHBUCKET) ) |
|
{ |
|
if ( pBatch ) |
|
{ |
|
pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex ); |
|
} |
|
|
|
if ( pDecal == DECALMARKERS_SWITCHSORTTREE ) |
|
{ |
|
if ( !bMeshInit ) |
|
{ |
|
meshBuilder.End(); |
|
R_DrawDecalMeshList( meshList ); |
|
bMeshInit = true; |
|
} |
|
} |
|
|
|
bBatchInit = true; |
|
pBatch = NULL; |
|
|
|
if ( pDecal == DECALMARKERS_SWITCHBUCKET ) |
|
{ |
|
//find the new head decal |
|
for( int j = i + 1; j != iDecalCount; ++j ) |
|
{ |
|
pDecalHead = ppDecals[j]; |
|
if( (pDecalHead != DECALMARKERS_SWITCHSORTTREE) && (pDecalHead != DECALMARKERS_SWITCHBUCKET) ) |
|
break; |
|
} |
|
|
|
vertexFormat = GetUncompressedFormat( pDecalHead->material ); |
|
} |
|
|
|
continue; |
|
} |
|
|
|
// Add the decal to the list of decals to be destroyed if need be. |
|
if ( ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ) ) |
|
{ |
|
pDecal->flags |= FDECAL_HASUPDATED; |
|
if ( DecalUpdate( pDecal ) ) |
|
{ |
|
R_DecalAddToDestroyList( pDecal ); |
|
continue; |
|
} |
|
} |
|
|
|
if ( pDecal->surfID != lastSurf ) |
|
{ |
|
lastSurf = pDecal->surfID; |
|
} |
|
CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material ); |
|
if ( !pVerts ) |
|
continue; |
|
nCount = pDecal->clippedVertCount; |
|
|
|
// Overflow - new mesh, batch. |
|
if ( ( ( nVertCount + nCount ) > nDecalSortMaxVerts ) || ( nIndexCount + ( nCount - 2 ) > nDecalSortMaxIndices ) ) |
|
{ |
|
// Finish this batch. |
|
if ( pBatch ) |
|
{ |
|
pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex ); |
|
} |
|
|
|
// End the mesh building phase and render. |
|
meshBuilder.End(); |
|
R_DrawDecalMeshList( meshList ); |
|
|
|
// Reset. |
|
bMeshInit = true; |
|
pBatch = NULL; |
|
bBatchInit = true; |
|
} |
|
|
|
// Create the mesh. |
|
if ( bMeshInit ) |
|
{ |
|
// Reset the mesh list. |
|
meshList.m_pMesh = NULL; |
|
meshList.m_aBatches.RemoveAll(); |
|
|
|
// Create a mesh for this vertex format (vertex format per SortTree). |
|
if ( bWireframe ) |
|
{ |
|
meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialDecalWireframe ); |
|
} |
|
else |
|
{ |
|
meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pDecalHead->material ); |
|
} |
|
meshBuilder.Begin( meshList.m_pMesh, MATERIAL_TRIANGLES, nDecalSortMaxVerts, nDecalSortMaxIndices ); |
|
|
|
nVertCount = 0; |
|
nIndexCount = 0; |
|
|
|
bMeshInit = false; |
|
} |
|
|
|
// Create the batch. |
|
if ( bBatchInit ) |
|
{ |
|
// Create a batch for this bucket = material/lightmap pair. |
|
// Todo: we also could flush it right here and continue. |
|
if ( meshList.m_aBatches.Size() + 1 > meshList.m_aBatches.NumAllocated() ) |
|
{ |
|
Warning( "R_DrawDecalsAll: overflowing m_aBatches. Reduce %d decals in the scene.\n", nDecalSortMaxVerts * meshList.m_aBatches.NumAllocated() ); |
|
meshBuilder.End(); |
|
R_DrawDecalMeshList( meshList ); |
|
return; |
|
} |
|
|
|
int iBatchList = meshList.m_aBatches.AddToTail(); |
|
pBatch = &meshList.m_aBatches[iBatchList]; |
|
pBatch->m_iStartIndex = nIndexCount; |
|
|
|
if ( bWireframe ) |
|
{ |
|
pBatch->m_pMaterial = g_materialDecalWireframe; |
|
} |
|
else |
|
{ |
|
pBatch->m_pMaterial = pDecalHead->material; |
|
pBatch->m_pProxy = pDecalHead->userdata; |
|
pBatch->m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( pDecalHead->surfID )].lightmapPageID; |
|
} |
|
|
|
bBatchInit = false; |
|
} |
|
Assert ( pBatch ); |
|
|
|
// Set base color. |
|
byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a }; |
|
if ( flFade != 1.0f ) |
|
{ |
|
color[3] = (byte)( color[3] * flFade ); |
|
} |
|
|
|
// Dynamic decals - fading. |
|
if ( pDecal->flags & FDECAL_DYNAMIC ) |
|
{ |
|
float flFadeValue; |
|
|
|
// Negative fadeDuration value means to fade in |
|
if ( pDecal->fadeDuration < 0 ) |
|
{ |
|
flFadeValue = -( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
else |
|
{ |
|
flFadeValue = 1.0 - ( cl.GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; |
|
} |
|
|
|
flFadeValue = clamp( flFadeValue, 0.0f, 1.0f ); |
|
|
|
color[3] = ( byte )( color[3] * flFadeValue ); |
|
} |
|
|
|
// Compute normal and tangent space if necessary. |
|
Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); |
|
if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) ) |
|
{ |
|
vecNormal = MSurf_Plane( pDecal->surfID ).normal; |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
Vector tVect; |
|
bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect ); |
|
TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate ); |
|
} |
|
} |
|
|
|
// Setup verts. |
|
float flOffset = pDecal->lightmapOffset; |
|
|
|
for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts ) |
|
{ |
|
meshBuilder.Position3fv( pVerts->m_vPos.Base() ); |
|
if ( vertexFormat & VERTEX_NORMAL ) |
|
{ |
|
meshBuilder.Normal3fv( vecNormal.Base() ); |
|
} |
|
meshBuilder.Color4ubv( color ); |
|
meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y ); |
|
meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y ); |
|
meshBuilder.TexCoord1f( 2, flOffset ); |
|
if ( vertexFormat & VERTEX_TANGENT_SPACE ) |
|
{ |
|
meshBuilder.TangentS3fv( vecTangentS.Base() ); |
|
meshBuilder.TangentT3fv( vecTangentT.Base() ); |
|
} |
|
|
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
// Setup indices. |
|
int nTriCount = ( nCount - 2 ); |
|
CIndexBuilder &indexBuilder = meshBuilder; |
|
indexBuilder.FastPolygon( nVertCount, nTriCount ); |
|
|
|
// Update counters. |
|
nVertCount += nCount; |
|
nIndexCount += ( nTriCount * 3 ); |
|
} |
|
|
|
if ( pBatch ) |
|
{ |
|
pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex ); |
|
} |
|
|
|
if ( !bMeshInit ) |
|
{ |
|
meshBuilder.End(); |
|
R_DrawDecalMeshList( meshList ); |
|
} |
|
} |
|
|
|
|
|
void R_DrawDecalsAll( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade ) |
|
{ |
|
DecalMeshList_t meshList; |
|
CMeshBuilder meshBuilder; |
|
SurfaceHandle_t lastSurf = NULL; |
|
decalcontext_t context(pRenderContext, vModelOrg); |
|
Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); |
|
|
|
int nVertCount = 0; |
|
int nIndexCount = 0; |
|
|
|
int nDecalSortMaxVerts; |
|
int nDecalSortMaxIndices; |
|
R_DecalsGetMaxMesh( pRenderContext, nDecalSortMaxVerts, nDecalSortMaxIndices ); |
|
|
|
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); |
|
float localClientTime = cl.GetTime(); |
|
|
|
int nSortTreeCount = g_aDecalSortTrees.Count(); |
|
|
|
for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree ) |
|
{ |
|
// Reset the mesh list. |
|
bool bMeshInit = true; |
|
|
|
DecalSortTrees_t &sortTree = g_aDecalSortTrees[iSortTree]; |
|
int nBucketCount = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Count(); |
|
for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) |
|
{ |
|
DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ); |
|
if ( bucket.m_nCheckCount != nCheckCount ) |
|
continue; |
|
|
|
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; |
|
intp iPool = g_aDecalSortPool.Alloc( true ); |
|
if ( iPool != g_aDecalSortPool.InvalidIndex() ) |
|
{ |
|
g_aDecalSortPool[iPool] = pDecal; |
|
|
|
DecalSortTrees_t &sortTree = g_aDecalSortTrees[ pDecal->m_iSortTree ]; |
|
DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( pDecal->m_iSortMaterial ); |
|
if ( bucket.m_nCheckCount == nCheckCount ) |
|
{ |
|
intp iHead = bucket.m_iHead; |
|
g_aDecalSortPool.LinkBefore( iHead, iPool ); |
|
} |
|
|
|
bucket.m_iHead = iPool; |
|
bucket.m_nCheckCount = nCheckCount; |
|
} |
|
} |
|
} |
|
|
|
#pragma check_stack(off) |
|
#endif // SWDS
|
|
|