//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "render_pch.h" #include "r_decal.h" #include "client.h" #include #include "decal.h" #include "tier0/vprof.h" #include "materialsystem/materialsystem_config.h" #include "icliententity.h" #include "icliententitylist.h" #include "tier2/tier2.h" #include "tier1/callqueue.h" #include "tier1/memstack.h" #include "mempool.h" #include "vstdlib/random.h" #include "getintersectingsurfaces_struct.h" #include "surfinfo.h" #include "collisionutils.h" #include "engine/decal_flags.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define DECAL_DISTANCE 4 // Empirically determined constants for minimizing overalpping decals ConVar r_decal_overlap_count("r_decal_overlap_count", "3"); ConVar r_decal_overlap_area("r_decal_overlap_area", "0.4"); // if a new decal covers more than this many old decals, retire until this count remains ConVar r_decal_cover_count("r_decal_cover_count", "4" ); static unsigned int s_DecalScaleVarCache = 0; static unsigned int s_DecalScaleVariationVarCache = 0; static unsigned int s_DecalFadeVarCache = 0; static unsigned int s_DecalFadeTimeVarCache = 0; static unsigned int s_DecalSecondPassVarCache = 0; inline int R_ConvertToPrivateDecalFlags( int nPublicDecalFlags ) { int nPrivateFlags = 0; if ( ( nPublicDecalFlags & EDF_PLAYERSPRAY ) != 0 ) { nPublicDecalFlags &= ~EDF_PLAYERSPRAY; nPrivateFlags |= FDECAL_PLAYERSPRAY; } if ( ( nPublicDecalFlags & EDF_IMMEDIATECLEANUP ) != 0 ) { nPublicDecalFlags &= ~EDF_IMMEDIATECLEANUP; nPrivateFlags |= FDECAL_IMMEDIATECLEANUP; } // If this hits, we are missing conversion here. Assert( nPublicDecalFlags == 0 ); return nPrivateFlags; } // This structure contains the information used to create new decals struct decalinfo_t { Vector m_Position; // world coordinates of the decal center Vector m_SAxis; // the s axis for the decal in world coordinates model_t* m_pModel; // the model the decal is going to be applied in worldbrushdata_t *m_pBrush; // The shared brush data for this model IMaterial* m_pMaterial; // The decal material float m_Size; // Size of the decal (in world coords) int m_Flags; int m_Entity; // Entity the decal is applied to. float m_scale; float m_flFadeDuration; float m_flFadeStartTime; int m_decalWidth; int m_decalHeight; color32 m_Color; Vector m_Basis[3]; void *m_pUserData; const Vector *m_pNormal; CUtlVector m_aApplySurfs; }; typedef struct { CDecalVert decalVert[4]; } decalcache_t; // UNDONE: Compress this??? 256K here? static CClassMemoryPool g_DecalAllocator( 128 ); // 128 decals per block. static int g_nDynamicDecals = 0; static int g_nStaticDecals = 0; static int g_iLastReplacedDynamic = -1; CUtlVector s_aDecalPool; const int DECALCACHE_ENTRY_COUNT = 1024; const int INVALID_CACHE_ENTRY = 0xFFFF; class ALIGN16 CDecalVertCache { enum decalindex_ordinal { DECAL_INDEX = 0, // set this and use this to free the whole decal's list on compact NEXT_VERT_BLOCK_INDEX = 1, IS_FREE_INDEX = 2, FRAME_COUNT_INDEX = 3, }; public: void Init(); CDecalVert *GetCachedVerts( decal_t *pDecal ); void FreeCachedVerts( decal_t *pDecal ); void StoreVertsInCache( decal_t *pDecal, CDecalVert *pList ); private: inline int GetIndex( decalcache_t *pBlock, decalindex_ordinal index ) { return pBlock->decalVert[index].m_decalIndex; } inline void SetIndex( decalcache_t *pBlock, decalindex_ordinal index, int value ) { pBlock->decalVert[index].m_decalIndex = value; } inline void SetNext( int iCur, int iNext ) { SetIndex(m_cache + iCur, NEXT_VERT_BLOCK_INDEX, iNext); } inline void SetFree( int iBlock, bool bFree ) { SetIndex( m_cache + iBlock, IS_FREE_INDEX, bFree ); } inline bool IsFree(int iBlock) { return GetIndex(m_cache+iBlock, IS_FREE_INDEX) != 0; } decalcache_t *NextBlock( decalcache_t *pCache ); void FreeBlock(int cacheIndex); // search for blocks not used this frame and free them // this way we don't manage an LRU but get similar behavior void FindFreeBlocks( int blockCount ); int AllocBlock(); int AllocBlocks( int blockCount ); ALIGN16 decalcache_t m_cache[DECALCACHE_ENTRY_COUNT]; int m_freeBlockCount; int m_firstFree; int m_frameBlocks; int m_lastFrameCount; int m_freeTestIndex; }; static CDecalVertCache ALIGN16 g_DecalVertCache; // // (Hi John) // Destroying decals is an ancient system that we reused for player graffiti decal previews. // There is a new system for single-frame decals that are scheduled for immediate cleanup // on the same frame that they are inserted. Sometimes these decals don't finish cleaning // themselves up until they render for the second frame. That is okay for rendering purposes, // but it is a problem if they end up in the cleanup list twice because it can create circular // loops in the list or accessing free'd memory. The flags are uint16 and all used up at this // point, so we cannot add a flag whether the decal is in the cleanup list already. // In order not to increase the size of the decal structure I am making the cleanup list to // be terminated by a non-NULL known not valid pointer. This allows all the code to know // whether a specific decal_t object is already in the destroy list by checking its pDestroyList // member for being non-NULL, and only the per-frame destroy list walking code needs to be aware // of stopping at the non-NULL special terminator entry. // The "easiest" spot to recreate a circular deadlock was on overpass T-side of monster in the // left corner of monster wall and slanted wall to just spam the +spray_menu key and have a // conditional breakpoint in R_DecalAddToDestroyList with condition pDecal == s_pDecalDestroyList. // That condition will essentially create a loop of decal pointing to itself as next. // static decal_t * const kpDecalDestroyListNonNullTerminator = reinterpret_cast< decal_t * >( 1 ); static decal_t *s_pDecalDestroyList = kpDecalDestroyListNonNullTerminator; int g_nMaxDecals = 0; // // ConVars that control distance-based decal scaling // static ConVar r_dscale_nearscale( "r_dscale_nearscale", "1", FCVAR_CHEAT ); static ConVar r_dscale_neardist( "r_dscale_neardist", "100", FCVAR_CHEAT ); static ConVar r_dscale_farscale( "r_dscale_farscale", "4", FCVAR_CHEAT ); static ConVar r_dscale_fardist( "r_dscale_fardist", "2000", FCVAR_CHEAT ); static ConVar r_dscale_basefov( "r_dscale_basefov", "90", FCVAR_CHEAT ); // This makes sure all the decals got freed before the engine is shutdown. static class CDecalChecker { public: ~CDecalChecker() { Assert( g_nDynamicDecals == 0 ); } } g_DecalChecker; // used for decal LOD VMatrix g_BrushToWorldMatrix; static CUtlVector s_DecalSurfaces[ MAX_MAT_SORT_GROUPS + 1 ]; static ConVar r_drawdecals( "r_drawdecals", "1", FCVAR_CHEAT, "Render decals." ); static ConVar r_drawbatchdecals( "r_drawbatchdecals", "1", 0, "Render decals batched." ); static void R_DecalCreate( decalinfo_t* pDecalInfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement ); void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const float *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal ); static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry); void R_DecalSortInit( void ); bool DecalUpdate( decal_t* pDecal ); void R_DecalAddToDestroyList( decal_t *pDecal ); inline bool ShouldSkipDecalRender( decal_t *pDecal ) { if ( !pDecal ) return true; const bool cbRequiresDynamicUpdate = ( pDecal->flags & FDECAL_DYNAMIC ) && !( pDecal->flags & FDECAL_HASUPDATED ); const bool cbRequiresCleanupUpdate = ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) != 0 ; // Add the decal to the list of decals to be destroyed if need be. if ( cbRequiresDynamicUpdate || cbRequiresCleanupUpdate ) { if ( cbRequiresDynamicUpdate ) pDecal->flags |= FDECAL_HASUPDATED; if ( DecalUpdate( pDecal ) ) { R_DecalAddToDestroyList( pDecal ); return true; } } return false; } static void r_printdecalinfo_f() { int nPermanent = 0; int nDynamic = 0; for ( int i=0; i < g_nMaxDecals; i++ ) { if ( s_aDecalPool[i] ) { if ( s_aDecalPool[i]->flags & FDECAL_PERMANENT ) ++nPermanent; else ++nDynamic; } } Assert( nDynamic == g_nDynamicDecals ); Msg( "%d decals: %d permanent, %d dynamic\nr_decals: %d\n", nPermanent+nDynamic, nPermanent, nDynamic, r_decals.GetInt() ); } static ConCommand r_printdecalinfo( "r_printdecalinfo", r_printdecalinfo_f ); void CDecalVertCache::StoreVertsInCache( decal_t *pDecal, CDecalVert *pList ) { int vertCount = pDecal->clippedVertCount; int blockCount = (vertCount+3)>>2; FindFreeBlocks(blockCount); if ( blockCount > m_freeBlockCount ) return; int cacheHandle = AllocBlocks(blockCount); pDecal->cacheHandle = cacheHandle; decalcache_t *pCache = &m_cache[cacheHandle]; while ( blockCount ) { Assert(GetIndex(pCache, DECAL_INDEX) == -1 ); // don't memcpy here it overwrites the indices we're storing in the m_decalIndex data for ( int i = 0; i < 4; i++ ) { pCache->decalVert[i].m_vPos = pList[i].m_vPos; pCache->decalVert[i].m_ctCoords = pList[i].m_ctCoords; pCache->decalVert[i].m_cLMCoords = pList[i].m_cLMCoords; } pList += 4; blockCount--; SetIndex( pCache, DECAL_INDEX, pDecal->m_iDecalPool ); SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount ); pCache = NextBlock(pCache); } } void CDecalVertCache::FreeCachedVerts( decal_t *pDecal ) { // walk the list int nextIndex = INVALID_CACHE_ENTRY; for ( int cacheHandle = pDecal->cacheHandle; cacheHandle != INVALID_CACHE_ENTRY; cacheHandle = nextIndex ) { decalcache_t *pCache = m_cache + cacheHandle; nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX); Assert(GetIndex(pCache,DECAL_INDEX)==pDecal->m_iDecalPool); FreeBlock(cacheHandle); } pDecal->cacheHandle = INVALID_CACHE_ENTRY; pDecal->clippedVertCount = 0; } CDecalVert *CDecalVertCache::GetCachedVerts( decal_t *pDecal ) { int cacheHandle = pDecal->cacheHandle; // track blocks used this frame to avoid thrashing if ( r_framecount != m_lastFrameCount ) { m_frameBlocks = 0; m_lastFrameCount = r_framecount; } if ( cacheHandle == INVALID_CACHE_ENTRY ) return NULL; decalcache_t *pCache = &m_cache[cacheHandle]; for ( int i = cacheHandle; i != INVALID_CACHE_ENTRY; i = GetIndex(&m_cache[i], NEXT_VERT_BLOCK_INDEX) ) { SetIndex( pCache, FRAME_COUNT_INDEX, r_framecount ); Assert( GetIndex(pCache, DECAL_INDEX) == pDecal->m_iDecalPool); } int vertCount = pDecal->clippedVertCount; int blockCount = (vertCount+3)>>2; m_frameBlocks += blockCount; // Make linked vert lists contiguous by copying to the clip buffer if ( blockCount > 1 ) { int indexOut = 0; while ( blockCount ) { Plat_FastMemcpy( &g_DecalClipVerts[indexOut], pCache, sizeof(*pCache) ); indexOut += 4; blockCount --; pCache = NextBlock(pCache); } return g_DecalClipVerts; } // only one block, no need to copy return pCache->decalVert; } void CDecalVertCache::Init() { m_firstFree = 0; m_freeTestIndex = 0; for ( int i = 0; i < DECALCACHE_ENTRY_COUNT; i++ ) { SetNext( i, i+1 ); SetIndex( &m_cache[i], DECAL_INDEX, -1 ); SetFree( i, true ); } SetNext( DECALCACHE_ENTRY_COUNT-1, INVALID_CACHE_ENTRY ); m_freeBlockCount = DECALCACHE_ENTRY_COUNT; } decalcache_t *CDecalVertCache::NextBlock( decalcache_t *pCache ) { int nextIndex = GetIndex(pCache, NEXT_VERT_BLOCK_INDEX); if ( nextIndex == INVALID_CACHE_ENTRY ) return NULL; return m_cache + nextIndex; } void CDecalVertCache::FreeBlock(int cacheIndex) { SetFree( cacheIndex, true ); SetNext( cacheIndex, m_firstFree ); SetIndex( &m_cache[cacheIndex], DECAL_INDEX, -1 ); m_firstFree = cacheIndex; m_freeBlockCount++; } // search for blocks not used this frame and free them // this way we don't manage an LRU but get similar behavior void CDecalVertCache::FindFreeBlocks( int blockCount ) { if ( blockCount <= m_freeBlockCount ) return; int possibleFree = DECALCACHE_ENTRY_COUNT - m_frameBlocks; if ( blockCount > possibleFree ) return; // limit the search for performance to 16 entries int lastTest = (m_freeTestIndex + 16) & (DECALCACHE_ENTRY_COUNT-1); for ( ; m_freeTestIndex != lastTest; m_freeTestIndex = (m_freeTestIndex+1)&(DECALCACHE_ENTRY_COUNT-1) ) { if ( !IsFree(m_freeTestIndex) ) { int lastFrame = GetIndex(&m_cache[m_freeTestIndex], FRAME_COUNT_INDEX); if ( (r_framecount - lastFrame) > 1 ) { int iDecal = GetIndex(&m_cache[m_freeTestIndex], DECAL_INDEX); FreeCachedVerts(s_aDecalPool[iDecal]); } } if ( m_freeBlockCount >= blockCount ) break; } } int CDecalVertCache::AllocBlock() { if ( !m_freeBlockCount ) return INVALID_CACHE_ENTRY; Assert(IsFree(m_firstFree)); int nextFree = GetIndex(m_cache+m_firstFree, NEXT_VERT_BLOCK_INDEX ); int cacheIndex = m_firstFree; SetFree( cacheIndex, false ); m_firstFree = nextFree; m_freeBlockCount--; return cacheIndex; } int CDecalVertCache::AllocBlocks( int blockCount ) { if ( blockCount > m_freeBlockCount ) return INVALID_CACHE_ENTRY; int firstBlock = AllocBlock(); Assert(firstBlock!=INVALID_CACHE_ENTRY); int blockHandle = firstBlock; for ( int i = 1; i < blockCount; i++ ) { int nextBlock = AllocBlock(); Assert(nextBlock!=INVALID_CACHE_ENTRY); SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, nextBlock ); blockHandle = nextBlock; } SetIndex( m_cache + blockHandle, NEXT_VERT_BLOCK_INDEX, INVALID_CACHE_ENTRY ); return firstBlock; } //----------------------------------------------------------------------------- // Computes the offset for a decal polygon //----------------------------------------------------------------------------- float ComputeDecalLightmapOffset( SurfaceHandle_t surfID ) { float flOffset; if ( MSurf_Flags( surfID ) & SURFDRAW_BUMPLIGHT ) { int nWidth, nHeight; materials->GetLightmapPageSize( SortInfoToLightmapPage( MSurf_MaterialSortID( surfID ) ), &nWidth, &nHeight ); int nXExtent = ( MSurf_LightmapExtents( surfID )[0] ) + 1; flOffset = ( nWidth != 0 ) ? (float)nXExtent / (float)nWidth : 0.0f; } else { flOffset = 0.0f; } return flOffset; } static VertexFormat_t GetUncompressedFormat( const IMaterial * pMaterial ) { // FIXME: IMaterial::GetVertexFormat() should do this stripping (add a separate 'SupportsCompression' accessor) return ( pMaterial->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED ); } //----------------------------------------------------------------------------- // Draws a decal polygon //----------------------------------------------------------------------------- void Shader_DecalDrawPoly( CDecalVert *v, IMaterial *pMaterial, SurfaceHandle_t surfID, int vertCount, decal_t *pdecal, float flFade ) { #ifndef DEDICATED int vertexFormat = 0; CMatRenderContextPtr pRenderContext( materials ); #ifdef USE_CONVARS if( ShouldDrawInWireFrameMode() || r_drawdecals.GetInt() == 2 ) { pRenderContext->Bind( g_materialDecalWireframe ); } else #endif { Assert( MSurf_MaterialSortID( surfID ) >= 0 && MSurf_MaterialSortID( surfID ) < g_WorldStaticMeshes.Count() ); pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID ); pRenderContext->Bind( pMaterial, pdecal->userdata ); vertexFormat = GetUncompressedFormat( pMaterial ); } IMesh *pMesh = pRenderContext->GetDynamicMesh( ); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_POLYGON, vertCount ); byte color[4] = {pdecal->color.r,pdecal->color.g,pdecal->color.b,pdecal->color.a}; if ( flFade != 1.0f ) { color[3] = (byte)( color[3] * flFade ); } // Deal with fading out... (should this be done in the shader?) // Note that we do it with per-vertex color even though the translucency // is constant so as to not change any rendering state (like the constant // alpha value) if ( pdecal->flags & FDECAL_DYNAMIC ) { float fadeval; // Negative fadeDuration value means to fade in if (pdecal->fadeDuration < 0) { fadeval = - (GetBaseLocalClient().GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration; } else { fadeval = 1.0 - (GetBaseLocalClient().GetTime() - pdecal->fadeStartTime) / pdecal->fadeDuration; } fadeval = clamp( fadeval, 0.0f, 1.0f ); color[3] = (byte) (color[3] * fadeval); } Vector normal(0,0,1), tangentS(1,0,0), tangentT(0,1,0); if ( vertexFormat & (VERTEX_NORMAL|VERTEX_TANGENT_SPACE) ) { normal = MSurf_Plane( surfID ).normal; if ( vertexFormat & VERTEX_TANGENT_SPACE ) { Vector tVect; bool negate = TangentSpaceSurfaceSetup( surfID, tVect ); TangentSpaceComputeBasis( tangentS, tangentT, normal, tVect, negate ); } } float flOffset = pdecal->lightmapOffset; for( int i = 0; i < vertCount; i++, v++ ) { meshBuilder.Position3f( VectorExpand( v->m_vPos ) ); if ( vertexFormat & VERTEX_NORMAL ) { meshBuilder.Normal3fv( normal.Base() ); } meshBuilder.Color4ubv( color ); // Check to see if we are in a material page. meshBuilder.TexCoord2f( 0, Vector2DExpand( v->m_ctCoords ) ); meshBuilder.TexCoord2f( 1, Vector2DExpand( v->m_cLMCoords ) ); meshBuilder.TexCoord1f( 2, flOffset ); if ( vertexFormat & VERTEX_TANGENT_SPACE ) { meshBuilder.TangentS3fv( tangentS.Base() ); meshBuilder.TangentT3fv( tangentT.Base() ); } meshBuilder.AdvanceVertex(); } meshBuilder.End(); pMesh->Draw(); #endif } //----------------------------------------------------------------------------- // Gets the decal material and radius based on the decal index //----------------------------------------------------------------------------- void R_DecalGetMaterialAndSize( int decalIndex, IMaterial*& pDecalMaterial, float& w, float& h ) { pDecalMaterial = Draw_DecalMaterial( decalIndex ); if (!pDecalMaterial) return; float scale = 1.0f; // Compute scale of surface // FIXME: cache this? IMaterialVar *pDecalScaleVar = pDecalMaterial->FindVarFast( "$decalScale", &s_DecalScaleVarCache ); if ( pDecalScaleVar ) { scale = pDecalScaleVar->GetFloatValue(); } pDecalScaleVar = pDecalMaterial->FindVarFast( "$decalScaleVariation", &s_DecalScaleVariationVarCache ); if ( pDecalScaleVar ) { float variation = pDecalScaleVar->GetFloatValue(); variation = clamp( variation, 0.0f, 0.99f ); variation = RandomFloat( -variation, variation ); scale += variation * scale; } // compute the decal dimensions in world space w = pDecalMaterial->GetMappingWidth() * scale; h = pDecalMaterial->GetMappingHeight() * scale; } #ifndef DEDICATED static inline decal_t *MSurf_DecalPointer( SurfaceHandle_t surfID ) { WorldDecalHandle_t handle = MSurf_Decals(surfID ); if ( handle == WORLD_DECAL_HANDLE_INVALID ) return NULL; return s_aDecalPool[handle]; } static WorldDecalHandle_t DecalToHandle( decal_t *pDecal ) { if ( !pDecal ) return WORLD_DECAL_HANDLE_INVALID; int decalIndex = pDecal->m_iDecalPool; Assert( decalIndex >= 0 && decalIndex < g_nMaxDecals ); return static_cast (decalIndex); } // Init the decal pool void R_DecalInit( void ) { g_nMaxDecals = r_decals.GetInt(); g_nMaxDecals = MAX(64, g_nMaxDecals); Assert( g_DecalAllocator.Count() == 0 ); g_nDynamicDecals = 0; g_nStaticDecals = 0; g_iLastReplacedDynamic = -1; s_aDecalPool.Purge(); s_aDecalPool.SetSize( g_nMaxDecals ); int i; // Traverse all surfaces of map and throw away current decals // // sort the surfaces into the sort arrays if ( host_state.worldbrush ) { for( i = 0; i < host_state.worldbrush->numsurfaces; i++ ) { SurfaceHandle_t surfID = SurfaceHandleFromIndex(i); MSurf_Decals( surfID ) = WORLD_DECAL_HANDLE_INVALID; } } for( int iDecal = 0; iDecal < g_nMaxDecals; ++iDecal ) { s_aDecalPool[iDecal] = NULL; } g_DecalVertCache.Init(); R_DecalSortInit(); } void R_DecalTerm( worldbrushdata_t *pBrushData, bool term_permanent_decals, bool term_player_decals ) { if( !pBrushData ) return; for( int i = 0; i < pBrushData->numsurfaces; i++ ) { decal_t *pNext; SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pBrushData ); for( decal_t *pDecal=MSurf_DecalPointer( surfID ); pDecal; pDecal=pNext ) { pNext = pDecal->pnext; /* if( pDecal->flags & FDECAL_PLAYERSPRAY ) { extern IBaseClientDLL *g_ClientDLL; if ( term_player_decals || g_ClientDLL->CanRetirePlayerDecal( pDecal->userdata ) ) R_DecalUnlink( pDecal, pBrushData ); // safe to unlink } else */ if ( term_permanent_decals || !(pDecal->flags & FDECAL_PERMANENT) ) { R_DecalUnlink( pDecal, pBrushData ); } } if ( term_permanent_decals && term_player_decals ) { Assert( MSurf_DecalPointer( surfID ) == NULL ); } } } void R_DecalTermNew( worldbrushdata_t *pBrushData, int nTick ) { if ( !pBrushData ) return; for ( int i = 0; i < pBrushData->numsurfaces; i++ ) { decal_t *pNext; SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pBrushData ); for ( decal_t *pDecal = MSurf_DecalPointer( surfID ); pDecal; pDecal = pNext ) { pNext = pDecal->pnext; if ( !( pDecal->flags & ( FDECAL_PERMANENT | FDECAL_PLAYERSPRAY ) ) && pDecal->m_nTickCreated > nTick ) { R_DecalUnlink( pDecal, pBrushData ); } } } } void R_DecalTermAll() { for ( int i = 0; im_iDecalPool; } // Release the cache entry for this decal static void R_DecalCacheClear( decal_t *pdecal ) { g_DecalVertCache.FreeCachedVerts( pdecal ); } void R_DecalFlushDestroyList( bool bImmediateCleanup ) { decal_t* pNewDestroyList = kpDecalDestroyListNonNullTerminator; decal_t *pDecal = s_pDecalDestroyList; while ( pDecal != kpDecalDestroyListNonNullTerminator ) { decal_t *pNext = pDecal->pDestroyList; if ( ( 0 == ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) ) || bImmediateCleanup ) { R_DecalUnlink( pDecal, host_state.worldbrush ); } else { // These are only cleaned up once per frame, and we're not there, so push this out // for later. pDecal->pDestroyList = pNewDestroyList; pNewDestroyList = pDecal; } pDecal = pNext; } s_pDecalDestroyList = pNewDestroyList; } void R_DecalAddToDestroyList( decal_t *pDecal ) { if ( !pDecal->pDestroyList ) { pDecal->pDestroyList = s_pDecalDestroyList; s_pDecalDestroyList = pDecal; } } // Unlink pdecal from any surface it's attached to void R_DecalUnlink( decal_t *pdecal, worldbrushdata_t *pData ) { if ( !pdecal ) return; decal_t *tmp; R_DecalCacheClear( pdecal ); if ( IS_SURF_VALID( pdecal->surfID ) ) { if ( MSurf_DecalPointer( pdecal->surfID ) == pdecal ) { MSurf_Decals( pdecal->surfID ) = DecalToHandle( pdecal->pnext ); } else { tmp = MSurf_DecalPointer( pdecal->surfID ); if ( !tmp ) Sys_Error("Bad decal list"); while ( tmp->pnext ) { if ( tmp->pnext == pdecal ) { tmp->pnext = pdecal->pnext; break; } tmp = tmp->pnext; } } // Tell the displacement surface. if( SurfaceHasDispInfo( pdecal->surfID ) ) { IDispInfo * pDispInfo = MSurf_DispInfo( pdecal->surfID, pData ); if ( pDispInfo ) pDispInfo->NotifyRemoveDecal( pdecal->m_DispDecal ); } } pdecal->surfID = SURFACE_HANDLE_INVALID; if ( !(pdecal->flags & FDECAL_PERMANENT) ) { --g_nDynamicDecals; Assert( g_nDynamicDecals >= 0 ); } else { --g_nStaticDecals; Assert( g_nStaticDecals >= 0 ); } // Free the decal. Assert( s_aDecalPool[pdecal->m_iDecalPool] == pdecal ); s_aDecalPool[pdecal->m_iDecalPool] = NULL; g_DecalAllocator.Free( pdecal ); } int R_FindFreeDecalSlot() { for ( int i=0; i < g_nMaxDecals; i++ ) { if ( !s_aDecalPool[i] ) return i; } return -1; } // Uncomment this to spew decals if we run out of space!!! // #define SPEW_DECALS #if defined( SPEW_DECALS ) void SpewDecals() { static bool spewdecals = true; if ( spewdecals ) { sspewdecals = false; int i = 0; for ( i = 0 ; i < g_nMaxDecals; ++i ) { decal_t *decal = s_aDecalPool[ i ]; Assert( decal ); if ( decal ) { bool permanent = ( decal->flags & FDECAL_PERMANENT ) ? true : false; Msg( "%i == %s on %i perm %i at %.2f %.2f %.2f on surf %i (%.2f %.2f %2.f)\n", i, decal->material->GetName(), (int)decal->entityIndex, permanent ? 1 : 0, decal->position.x, decal->position.y, decal->position.z, (int)decal->surfID, decal->dx, decal->dy, decal->scale ); } } } } #endif int R_FindDynamicDecalSlot( int iStartAt ) { if ( (iStartAt >= g_nMaxDecals) || (iStartAt < 0) ) { iStartAt = 0; } int i = iStartAt; do { // don't deallocate player sprays or permanent decals if ( s_aDecalPool[i] && !(s_aDecalPool[i]->flags & FDECAL_PERMANENT) && !(s_aDecalPool[i]->flags & FDECAL_PLAYERSPRAY) ) return i; ++i; if ( i >= g_nMaxDecals ) i = 0; } while ( i != iStartAt ); DevMsg("R_FindDynamicDecalSlot: no slot available.\n"); #if defined( SPEW_DECALS ) SpewDecals(); #endif return -1; } // Just reuse next decal in list // A decal that spans multiple surfaces will use multiple decal_t pool entries, as each surface needs // it's own. static decal_t *R_DecalAlloc( int flags ) { static bool bWarningOnce = false; bool bPermanent = (flags & FDECAL_PERMANENT) != 0; int dynamicDecalLimit = MIN( r_decals.GetInt(), g_nMaxDecals ); // Now find a slot. Unless it's dynamic and we're at the limit of dynamic decals, // we can look for a free slot. int iSlot = -1; if ( bPermanent || (g_nDynamicDecals < dynamicDecalLimit) ) { iSlot = R_FindFreeDecalSlot(); } if ( iSlot == -1 ) { iSlot = R_FindDynamicDecalSlot( g_iLastReplacedDynamic+1 ); if ( iSlot == -1 ) { if ( !bWarningOnce ) { // Can't find a free slot. Just kill the first one. DevWarning( 1, "Exceeded MAX_DECALS (%d).\n", g_nMaxDecals ); bWarningOnce = true; } iSlot = 0; } R_DecalUnlink( s_aDecalPool[iSlot], host_state.worldbrush ); g_iLastReplacedDynamic = iSlot; } // Setup the new decal. decal_t *pDecal = g_DecalAllocator.Alloc(); s_aDecalPool[iSlot] = pDecal; pDecal->pDestroyList = NULL; pDecal->m_iDecalPool = iSlot; pDecal->surfID = SURFACE_HANDLE_INVALID; pDecal->cacheHandle = INVALID_CACHE_ENTRY; pDecal->clippedVertCount = 0; if ( !bPermanent ) { ++g_nDynamicDecals; } else { ++g_nStaticDecals; } return pDecal; } // The world coordinate system is right handed with Z up. // // ^ Z // | // | // | //X<----| // \ // \ // \ Y void R_DecalSurface( SurfaceHandle_t surfID, decalinfo_t *decalinfo, bool bForceForDisplacement ) { // If we are a player spray, we should have a normal. Assert( ( ( decalinfo->m_Flags & FDECAL_PLAYERSPRAY ) == 0 ) || decalinfo->m_pNormal != NULL ); if ( decalinfo->m_pNormal ) { const float cosTheta = DotProduct( MSurf_Plane( surfID ).normal, *(decalinfo->m_pNormal) ); const float fComp = ( decalinfo->m_Flags & FDECAL_PLAYERSPRAY ) ? 0.5f // 60 degrees. : 0.0f; if ( cosTheta < fComp ) return; } // Get the texture associated with this surface mtexinfo_t* tex = MSurf_TexInfo( surfID ); Vector4D &textureU = tex->textureVecsTexelsPerWorldUnits[0]; Vector4D &textureV = tex->textureVecsTexelsPerWorldUnits[1]; // project decal center into the texture space of the surface float s = DotProduct( decalinfo->m_Position, textureU.AsVector3D() ) + textureU.w - MSurf_TextureMins( surfID )[0]; float t = DotProduct( decalinfo->m_Position, textureV.AsVector3D() ) + textureV.w - MSurf_TextureMins( surfID )[1]; // Determine the decal basis (measured in world space) // Note that the decal basis vectors 0 and 1 will always lie in the same // plane as the texture space basis vectors textureVecsTexelsPerWorldUnits. R_DecalComputeBasis( MSurf_Plane( surfID ).normal, (decalinfo->m_Flags & FDECAL_USESAXIS) ? &decalinfo->m_SAxis : 0, decalinfo->m_Basis ); // Compute an effective width and height (axis aligned) in the parent texture space // How does this work? decalBasis[0] represents the u-direction (width) // of the decal measured in world space, decalBasis[1] represents the // v-direction (height) measured in world space. // textureVecsTexelsPerWorldUnits[0] represents the u direction of // the surface's texture space measured in world space (with the appropriate // scale factor folded in), and textureVecsTexelsPerWorldUnits[1] // represents the texture space v direction. We want to find the dimensions (w,h) // of a square measured in texture space, axis aligned to that coordinate system. // All we need to do is to find the components of the decal edge vectors // (decalWidth * decalBasis[0], decalHeight * decalBasis[1]) // in texture coordinates: float w = fabs( decalinfo->m_decalWidth * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[0] ) ) + fabs( decalinfo->m_decalHeight * DotProduct( textureU.AsVector3D(), decalinfo->m_Basis[1] ) ); float h = fabs( decalinfo->m_decalWidth * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[0] ) ) + fabs( decalinfo->m_decalHeight * DotProduct( textureV.AsVector3D(), decalinfo->m_Basis[1] ) ); // move s,t to upper left corner s -= ( w * 0.5 ); t -= ( h * 0.5 ); // Is this rect within the surface? -- tex width & height are unsigned if( !bForceForDisplacement ) { if ( s <= -w || t <= -h || s > (MSurf_TextureExtents( surfID )[0]+w) || t > (MSurf_TextureExtents( surfID )[1]+h) ) { return; // nope } } // stamp it R_DecalCreate( decalinfo, surfID, s, t, bForceForDisplacement ); } //----------------------------------------------------------------------------- // iterate over all surfaces on a node, looking for surfaces to decal //----------------------------------------------------------------------------- static void R_DecalNodeSurfaces( mnode_t* node, decalinfo_t *decalinfo ) { // iterate over all surfaces in the node SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface ); for ( int i=0; inumsurfaces ; ++i, ++surfID) { if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS ) continue; // Displacement surfaces get decals in R_DecalLeaf. if ( SurfaceHasDispInfo( surfID ) ) continue; R_DecalSurface( surfID, decalinfo, false ); } } void R_DecalLeaf( mleaf_t *pLeaf, decalinfo_t *decalinfo ) { SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) { SurfaceHandle_t surfID = pHandle[i]; // only process leaf surfaces if ( MSurf_Flags( surfID ) & (SURFDRAW_NODE|SURFDRAW_NODECALS) ) continue; if ( decalinfo->m_aApplySurfs.Find( surfID ) != -1 ) continue; Assert( !MSurf_DispInfo( surfID ) ); float dist = fabs( DotProduct(decalinfo->m_Position, MSurf_Plane( surfID ).normal) - MSurf_Plane( surfID ).dist); if ( dist < DECAL_DISTANCE ) { R_DecalSurface( surfID, decalinfo, false ); } } // Add the decal to each displacement in the leaf it touches. for ( int i = 0; i < pLeaf->dispCount; i++ ) { IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); SurfaceHandle_t surfID = pDispInfo->GetParent(); if ( MSurf_Flags( surfID ) & SURFDRAW_NODECALS ) continue; // Make sure the decal hasn't already been added to it. if( pDispInfo->GetTag() ) continue; pDispInfo->SetTag(); // Trivial bbox reject. Vector bbMin, bbMax; pDispInfo->GetBoundingBox( bbMin, bbMax ); if( decalinfo->m_Position.x - decalinfo->m_Size < bbMax.x && decalinfo->m_Position.x + decalinfo->m_Size > bbMin.x && decalinfo->m_Position.y - decalinfo->m_Size < bbMax.y && decalinfo->m_Position.y + decalinfo->m_Size > bbMin.y && decalinfo->m_Position.z - decalinfo->m_Size < bbMax.z && decalinfo->m_Position.z + decalinfo->m_Size > bbMin.z ) { R_DecalSurface( pDispInfo->GetParent(), decalinfo, true ); } } } //----------------------------------------------------------------------------- // Recursive routine to find surface to apply a decal to. World coordinates of // the decal are passed in r_recalpos like the rest of the engine. This should // be called through R_DecalShoot() //----------------------------------------------------------------------------- static void R_DecalNode( mnode_t *node, decalinfo_t* decalinfo ) { cplane_t *splitplane; float dist; if (!node ) return; if ( node->contents >= 0 ) { R_DecalLeaf( (mleaf_t *)node, decalinfo ); return; } splitplane = node->plane; dist = DotProduct (decalinfo->m_Position, splitplane->normal) - splitplane->dist; // This is arbitrarily set to 10 right now. In an ideal world we'd have the // exact surface but we don't so, this tells me which planes are "sort of // close" to the gunshot -- the gunshot is actually 4 units in front of the // wall (see dlls\weapons.cpp). We also need to check to see if the decal // actually intersects the texture space of the surface, as this method tags // parallel surfaces in the same node always. // JAY: This still tags faces that aren't correct at edges because we don't // have a surface normal if (dist > decalinfo->m_Size) { R_DecalNode (node->children[0], decalinfo); } else if (dist < -decalinfo->m_Size) { R_DecalNode (node->children[1], decalinfo); } else { if ( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) R_DecalNodeSurfaces( node, decalinfo ); R_DecalNode (node->children[0], decalinfo); R_DecalNode (node->children[1], decalinfo); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pList - // count - // Output : static int //----------------------------------------------------------------------------- static int DecalListAdd( decallist_t *pList, int count ) { int i; Vector tmp; decallist_t *pdecal; pdecal = pList + count; for ( i = 0; i < count; i++ ) { if ( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex ) { VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge if ( VectorLength( tmp ) < 2 ) // UNDONE: Tune this '2' constant return count; } } // This is a new decal return count + 1; } typedef int (__cdecl *qsortFunc_t)( const void *, const void * ); static int __cdecl DecalDepthCompare( const decallist_t *elem1, const decallist_t *elem2 ) { if ( elem1->depth > elem2->depth ) return -1; if ( elem1->depth < elem2->depth ) return 1; return 0; } //----------------------------------------------------------------------------- // Purpose: Called by CSaveRestore::SaveClientState // Input : *pList - // Output : int //----------------------------------------------------------------------------- int DecalListCreate( decallist_t *pList ) { int total = 0; int i, depth; if ( host_state.worldmodel ) { for ( i = 0; i < g_nMaxDecals; i++ ) { decal_t *decal = s_aDecalPool[i]; // Decal is in use and is not a custom decal if ( !decal || !IS_SURF_VALID( decal->surfID ) || (decal->flags & ( FDECAL_CUSTOM | FDECAL_DONTSAVE ) ) ) continue; decal_t *pdecals; IMaterial *pMaterial; // compute depth depth = 0; pdecals = MSurf_DecalPointer( decal->surfID ); while ( pdecals && pdecals != decal ) { depth++; pdecals = pdecals->pnext; } pList[total].depth = depth; pList[total].flags = decal->flags; R_DecalUnProject( decal, &pList[total] ); pMaterial = decal->material; Q_strncpy( pList[total].name, pMaterial->GetName(), sizeof( pList[total].name ) ); // Check to see if the decal should be added total = DecalListAdd( pList, total ); } } // Sort the decals lowest depth first, so they can be re-applied in order qsort( pList, total, sizeof(decallist_t), ( qsortFunc_t )DecalDepthCompare ); return total; } // --------------------------------------------------------- static bool R_DecalUnProject( decal_t *pdecal, decallist_t *entry ) { if ( !pdecal || !IS_SURF_VALID( pdecal->surfID ) ) return false; VectorCopy( pdecal->position, entry->position ); entry->entityIndex = pdecal->entityIndex; // Grab surface plane equation cplane_t plane = MSurf_Plane( pdecal->surfID ); VectorCopy( plane.normal, entry->impactPlaneNormal ); return true; } // Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords static void R_DecalShoot_( IMaterial *pMaterial, int entity, const model_t *model, const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal, void *userdata = 0 ) { decalinfo_t decalInfo; VectorCopy( position, decalInfo.m_Position ); // Pass position in global if ( !model || model->type != mod_brush || !pMaterial ) return; decalInfo.m_pModel = (model_t *)model; decalInfo.m_pBrush = model->brush.pShared; // Deal with the s axis if one was passed in if (saxis) { flags |= FDECAL_USESAXIS; VectorCopy( *saxis, decalInfo.m_SAxis ); } // More state used by R_DecalNode() decalInfo.m_pMaterial = pMaterial; decalInfo.m_pUserData = userdata; decalInfo.m_Flags = flags; decalInfo.m_Entity = entity; decalInfo.m_Size = pMaterial->GetMappingWidth() >> 1; if ( (int)(pMaterial->GetMappingHeight() >> 1) > decalInfo.m_Size ) decalInfo.m_Size = pMaterial->GetMappingHeight() >> 1; // Compute scale of surface // FIXME: cache this? float scale = 1.0f; IMaterialVar *pDecalScaleVar = decalInfo.m_pMaterial->FindVarFast( "$decalScale", &s_DecalScaleVarCache ); if ( pDecalScaleVar ) { scale = pDecalScaleVar->GetFloatValue(); } pDecalScaleVar = decalInfo.m_pMaterial->FindVarFast( "$decalScaleVariation", &s_DecalScaleVariationVarCache ); if ( pDecalScaleVar ) { float variation = pDecalScaleVar->GetFloatValue(); variation = clamp( variation, 0.0f, 0.99f ); variation = RandomFloat( -variation, variation ); scale += variation * scale; } if ( scale != 1.0f && scale != 0.0f ) { decalInfo.m_scale = 1.0f / scale; decalInfo.m_Size *= scale; } else { decalInfo.m_scale = 1.0f; } decalInfo.m_flFadeDuration = 0.0f; IMaterialVar* pFadeVar = pMaterial->FindVarFast( "$decalFadeDuration", &s_DecalFadeVarCache ); if ( pFadeVar ) { decalInfo.m_flFadeDuration = pFadeVar->GetFloatValue(); pFadeVar = pMaterial->FindVarFast( "$decalFadeTime", &s_DecalFadeTimeVarCache ); decalInfo.m_flFadeStartTime = pFadeVar ? pFadeVar->GetFloatValue() : 0.0f; } IMaterialVar *pSecondPassVar = pMaterial->FindVarFast( "$decalSecondPass", &s_DecalSecondPassVarCache ); if ( pSecondPassVar ) { decalInfo.m_Flags |= FDECAL_SECONDPASS; } // compute the decal dimensions in world space decalInfo.m_decalWidth = pMaterial->GetMappingWidth() / decalInfo.m_scale; decalInfo.m_decalHeight = pMaterial->GetMappingHeight() / decalInfo.m_scale; decalInfo.m_Color = rgbaColor; // McJohn -- Get surface normal decalInfo.m_pNormal = pNormal; decalInfo.m_aApplySurfs.Purge(); // Clear the displacement tags because we use them in R_DecalNode. DispInfo_ClearAllTags( decalInfo.m_pBrush->hDispInfos ); mnode_t *pnodes = decalInfo.m_pBrush->nodes + decalInfo.m_pModel->brush.firstnode; R_DecalNode( pnodes, &decalInfo ); } // Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords // This is called from cl_parse.cpp, cl_tent.cpp void R_DecalShoot( int textureIndex, int entity, const model_t *model, const Vector &position, const Vector *saxis, int flags, const color32 &rgbaColor, const Vector *pNormal, int nAdditionalDecalFlags ) { void *userdata = NULL; IMaterial* pMaterial = Draw_DecalMaterial( textureIndex ); flags |= R_ConvertToPrivateDecalFlags( nAdditionalDecalFlags ); // Player decal custom handling: if ( flags & FDECAL_PLAYERSPRAY ) { userdata = ( void * ) textureIndex; } R_DecalShoot_( pMaterial, entity, model, position, saxis, flags, rgbaColor, pNormal, userdata ); } //----------------------------------------------------------------------------- // Purpose: // Input : *material - // playerIndex - // entity - // *model - // position - // *saxis - // flags - // &rgbaColor - //----------------------------------------------------------------------------- void R_PlayerDecalShoot( IMaterial *material, void *userdata, int entity, const model_t *model, const Vector& position, const Vector *saxis, int flags, const color32 &rgbaColor, int nAdditionalDecalFlags ) { // The userdata that is passed in is actually the sticker kit ID // it cannot be zero Assert( userdata != 0 ); #if 0 // : decals are implemented differently in CS:GO and time out by game rules // // Linear search through decal pool to retire any other decals this // player has sprayed. It appears that multiple decals can be // allocated for a single spray due to the way they are mapped to // surfaces. We need to run through and clean them all up. This // seems like the cleanest way to manage this - especially since // it doesn't happen that often. // int i; CUtlVector decalVec; for ( i = 0; iflags & FDECAL_PLAYERSPRAY && decal->userdata == userdata ) { decalVec.AddToTail( decal ); } } // remove all the sprays we found for ( i = 0; i < decalVec.Count(); i++ ) { R_DecalUnlink( decalVec[i], host_state.worldbrush ); } #endif // set this to be a player spray so it is timed out appropriately. flags |= FDECAL_PLAYERSPRAY; flags |= R_ConvertToPrivateDecalFlags( nAdditionalDecalFlags ); R_DecalShoot_( material, entity, model, position, saxis, flags, rgbaColor, NULL, userdata ); } struct decalcontext_t { Vector vModelOrg; Vector sAxis; float sOffset; Vector tAxis; float tOffset; float sScale; float tScale; IMatRenderContext *pRenderContext; SurfaceHandle_t pSurf; decalcontext_t( IMatRenderContext *pContext, const Vector &modelorg ) { pRenderContext = pContext; vModelOrg = modelorg; pSurf = NULL; } void InitSurface( SurfaceHandle_t surfID ) { if ( pSurf == surfID ) return; pSurf = surfID; mtexinfo_t* pTexInfo = MSurf_TexInfo( surfID ); int lightmapPageWidth, lightmapPageHeight; materials->GetLightmapPageSize( SortInfoToLightmapPage(MSurf_MaterialSortID( surfID )),&lightmapPageWidth, &lightmapPageHeight ); sScale = 1.0f / float(lightmapPageWidth); tScale = 1.0f / float(lightmapPageHeight); msurfacelighting_t *pSurfacelighting = SurfaceLighting(surfID); sOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][3] - pSurfacelighting->m_LightmapMins[0] + pSurfacelighting->m_OffsetIntoLightmapPage[0] + 0.5f; tOffset = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][3] - pSurfacelighting->m_LightmapMins[1] + pSurfacelighting->m_OffsetIntoLightmapPage[1] + 0.5f; sAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D(); tAxis = pTexInfo->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D(); } inline float ComputeS( const Vector &pos ) const { return sScale * (DotProduct(pos, sAxis) + sOffset); } inline float ComputeT( const Vector &pos ) const { return tScale * (DotProduct(pos, tAxis) + tOffset); } }; // Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf static void R_DecalVertsLight( CDecalVert* v, const decalcontext_t &context, SurfaceHandle_t surfID, int vertCount ) { for ( int j = 0; j < vertCount; j++, v++ ) { v->m_cLMCoords.x = context.ComputeS(v->m_vPos); v->m_cLMCoords.y = context.ComputeT(v->m_vPos); } } //----------------------------------------------------------------------------- // Purpose: Check for intersecting decals on this surface // Input : *psurf - // *pcount - // x - // y - // Output : static decal_t //----------------------------------------------------------------------------- // UNDONE: This probably doesn't work quite right any more // we should base overlap on the new decal basis matrix // decal basis is constant per plane, perhaps we should store it (unscaled) in the shared plane struct // BRJ: Note, decal basis is not constant when decals need to specify an s direction // but that certainly isn't the majority case static decal_t *R_DecalFindOverlappingDecals( decalinfo_t* decalinfo, SurfaceHandle_t surfID ) { decal_t *plast = NULL; // (Same as R_SetupDecalClip). IMaterial *pMaterial = decalinfo->m_pMaterial; int count = 0; // Precalculate the extents of decalinfo's decal in world space. int mapSize[2] = {pMaterial->GetMappingWidth(), pMaterial->GetMappingHeight()}; Vector decalExtents[2]; // this is half the width in world space of the decal. float minProjectedWidth = (mapSize[0] / decalinfo->m_scale) * 0.5; decalExtents[0] = decalinfo->m_Basis[0] * minProjectedWidth; decalExtents[1] = decalinfo->m_Basis[1] * (mapSize[1] / decalinfo->m_scale) * 0.5f; float areaThreshold = r_decal_overlap_area.GetFloat(); float lastArea = 0; bool bFullMatch = false; decal_t *pDecal = MSurf_DecalPointer( surfID ); CUtlVectorFixedGrowable 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 g_aDecalFormats; CUtlVector g_aDecalSortTrees; CUtlFixedLinkedList g_aDecalSortPool; int g_nDecalSortCheckCount; int g_nBrushModelDecalSortCheckCount; CUtlFixedLinkedList g_aDispDecalSortPool; CUtlVector g_aDispDecalSortTrees; int g_nDispDecalSortCheckCount; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void R_DecalSortInit( void ) { g_aDecalFormats.Purge(); g_aDecalSortTrees.Purge(); g_aDecalSortPool.Purge(); g_aDecalSortPool.EnsureCapacity( g_nMaxDecals ); g_aDecalSortPool.SetGrowSize( 128 ); g_nDecalSortCheckCount = 0; g_nBrushModelDecalSortCheckCount = 0; g_aDispDecalSortTrees.Purge(); g_aDispDecalSortPool.Purge(); g_aDispDecalSortPool.EnsureCapacity( g_nMaxDecals ); g_aDispDecalSortPool.SetGrowSize( 128 ); g_nDispDecalSortCheckCount = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void DecalSurfacesInit( bool bBrushModel ) { if ( !bBrushModel ) { // Only clear the pool once per frame. g_aDecalSortPool.RemoveAll(); ++g_nDecalSortCheckCount; } else { ++g_nBrushModelDecalSortCheckCount; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static void R_DecalMaterialSort( decal_t *pDecal, SurfaceHandle_t surfID ) { // Setup the decal material sort data. DecalMaterialSortData_t sort; if ( pDecal->material->InMaterialPage() ) { sort.m_pMaterial = pDecal->material->GetMaterialPage(); } else { sort.m_pMaterial = pDecal->material; } sort.m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; // Does this vertex type exist? VertexFormat_t vertexFormat = GetUncompressedFormat( sort.m_pMaterial ); int iFormat = 0; int nFormatCount = g_aDecalFormats.Count(); for ( ; iFormat < nFormatCount; ++iFormat ) { if ( g_aDecalFormats[iFormat].m_VertexFormat == vertexFormat ) break; } // A new vertex format type. if ( iFormat == nFormatCount ) { iFormat = g_aDecalFormats.AddToTail(); g_aDecalFormats[iFormat].m_VertexFormat = vertexFormat; int iSortTree = g_aDecalSortTrees.AddToTail(); g_aDispDecalSortTrees.AddToTail(); g_aDecalFormats[iFormat].m_iSortTree = iSortTree; } // Get an index for the current sort tree. int iSortTree = g_aDecalFormats[iFormat].m_iSortTree; int iTreeType = -1; // Lightmapped. if ( sort.m_pMaterial->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ) ) { // Permanent lightmapped decals. if ( pDecal->flags & FDECAL_PERMANENT ) { iTreeType = PERMANENT_LIGHTMAP; } // Non-permanent lightmapped decals. else { iTreeType = LIGHTMAP; } } // Non-lightmapped decals. else { iTreeType = NONLIGHTMAP; sort.m_iLightmapPage = -1; } int iSort = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Find( sort ); if ( iSort == -1 ) { int iBucket = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail(); g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].AddToTail(); g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1; g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[0][iTreeType].Element( iBucket ).m_nCheckCount = -1; for ( int iGroup = 1; iGroup < ( MAX_MAT_SORT_GROUPS + 1 ); ++iGroup ) { g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail(); g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].AddToTail(); g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1; g_aDispDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount = -1; } sort.m_iBucket = iBucket; g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort ); g_aDispDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Insert( sort ); pDecal->m_iSortTree = iSortTree; pDecal->m_iSortMaterial = sort.m_iBucket; } else { pDecal->m_iSortTree = iSortTree; pDecal->m_iSortMaterial = g_aDecalSortTrees[iSortTree].m_pTrees[iTreeType]->Element( iSort ).m_iBucket; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void R_DecalReSortMaterials( void ) //X { R_DecalSortInit(); int nDecalCount = s_aDecalPool.Count(); for ( int iDecal = 0; iDecal < nDecalCount; ++iDecal ) { decal_t *pDecal = s_aDecalPool.Element( iDecal ); if ( pDecal ) { SurfaceHandle_t surfID = pDecal->surfID; R_DecalMaterialSort( pDecal, surfID ); } } } // Allocate and initialize a decal from the pool, on surface with offsets x, y // UNDONE: offsets are not really meaningful in new decal coordinate system // the clipping code will recalc the offsets static void R_DecalCreate( decalinfo_t* decalinfo, SurfaceHandle_t surfID, float x, float y, bool bForceForDisplacement ) { decal_t *pdecal; if( !IS_SURF_VALID( surfID ) ) { ConMsg( "psurface NULL in R_DecalCreate!\n" ); return; } decal_t *pold = R_DecalFindOverlappingDecals( decalinfo, surfID ); if ( pold ) { R_DecalUnlink( pold, host_state.worldbrush ); pold = NULL; } pdecal = R_DecalAlloc( decalinfo->m_Flags ); pdecal->m_nTickCreated = GetBaseLocalClient().GetClientTickCount(); pdecal->flags = decalinfo->m_Flags; pdecal->color = decalinfo->m_Color; VectorCopy( decalinfo->m_Position, pdecal->position ); if (pdecal->flags & FDECAL_USESAXIS) VectorCopy( decalinfo->m_SAxis, pdecal->saxis ); pdecal->dx = x; pdecal->dy = y; pdecal->material = decalinfo->m_pMaterial; Assert( pdecal->material ); pdecal->userdata = decalinfo->m_pUserData; // Set scaling pdecal->scale = decalinfo->m_scale; pdecal->entityIndex = decalinfo->m_Entity; // Get dynamic information from the material (fade start, fade time) if ( decalinfo->m_flFadeDuration > 0.0f ) { pdecal->flags |= FDECAL_DYNAMIC; pdecal->fadeDuration = decalinfo->m_flFadeDuration; pdecal->fadeStartTime = decalinfo->m_flFadeStartTime; pdecal->fadeStartTime += GetBaseLocalClient().GetTime(); } // check for a player spray if( pdecal->flags & FDECAL_PLAYERSPRAY ) { // set the time when this decal appered, and allow to self-destruct after enough time passed pdecal->fadeStartTime = 0; // Force the scale to 1 for player sprays. pdecal->scale = 1.0f; } if( !bForceForDisplacement ) { // Check to see if the decal actually intersects the surface // if not, then remove the decal R_DecalVertsClip( NULL, pdecal, surfID, decalinfo->m_pMaterial ); if ( !pdecal->clippedVertCount ) { R_DecalUnlink( pdecal, host_state.worldbrush ); return; } } // Add to the surface's list R_AddDecalToSurface( pdecal, surfID, decalinfo ); // Add decal material/lightmap to sort list. R_DecalMaterialSort( pdecal, surfID ); } //----------------------------------------------------------------------------- // Updates all decals, returns true if the decal should be retired //----------------------------------------------------------------------------- bool DecalUpdate( decal_t* pDecal ) { if ( ( pDecal->flags & FDECAL_IMMEDIATECLEANUP ) != 0 ) { // This is ugly. We want to queue the decal for destruction, but we don't want to skip rendering it. // It will actually be cleaned up at the end of the frame. R_DecalAddToDestroyList( pDecal ); return false; } // retire the decal if it's time has come if (pDecal->fadeDuration > 0) { return (GetBaseLocalClient().GetTime() >= pDecal->fadeStartTime + pDecal->fadeDuration); } return false; } #define NEXT_MULTIPLE_OF_4(P) ( ((P) + ((4)-1)) & (~((4)-1)) ) // Build the vertex list for a decal on a surface and clip it to the surface. // This is a template so it can work on world surfaces and dynamic displacement // triangles the same way. CDecalVert* R_DecalSetupVerts( decalcontext_t &context, decal_t *pDecal, SurfaceHandle_t surfID, IMaterial *pMaterial ) { // // Do not scale playersprays // if( pDecal->flags & FDECAL_DISTANCESCALE ) { if( !(pDecal->flags & FDECAL_PLAYERSPRAY) ) { float scaleFactor = 1.0f; float nearScale, farScale, nearDist, farDist; nearScale = r_dscale_nearscale.GetFloat(); nearDist = r_dscale_neardist.GetFloat(); farScale = r_dscale_farscale.GetFloat(); farDist = r_dscale_fardist.GetFloat(); Vector playerOrigin = CurrentViewOrigin(); float dist = 0; if ( pDecal->entityIndex == 0 ) { dist = (playerOrigin - pDecal->position).Length(); } else { Vector worldSpaceCenter; Vector3DMultiplyPosition(g_BrushToWorldMatrix, pDecal->position, worldSpaceCenter ); dist = (playerOrigin - worldSpaceCenter).Length(); } float fov = g_EngineRenderer->GetFov(); // // If the player is zoomed in, we adjust the nearScale and farScale // if ( fov != r_dscale_basefov.GetFloat() && fov > 0 && r_dscale_basefov.GetFloat() > 0 ) { float fovScale = fov / r_dscale_basefov.GetFloat(); nearScale *= fovScale; farScale *= fovScale; if ( nearScale < 1.0f ) nearScale = 1.0f; if ( farScale < 1.0f ) farScale = 1.0f; } // // Scaling works like this: // // 0->nearDist scale = 1.0 // nearDist -> farDist scale = LERP(nearScale, farScale) // farDist->inf scale = farScale // // scaling in the rest of the code appears to be more of an // attenuation factor rather than a scale, so we compute 1/scale // to account for this. // if ( dist < nearDist ) scaleFactor = 1.0f; else if( dist >= farDist ) scaleFactor = farScale; else { float percent = (dist - nearDist) / (farDist - nearDist); scaleFactor = nearScale + percent * (farScale - nearScale); } // // scaling in the rest of the code appears to be more of an // attenuation factor rather than a scale, so we compute 1/scale // to account for this. // scaleFactor = 1.0f / scaleFactor; float originalScale = pDecal->scale; float scaledScale = pDecal->scale * scaleFactor; pDecal->scale = scaledScale; context.InitSurface( pDecal->surfID ); CDecalVert *v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial ); if ( v ) { R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount ); } pDecal->scale = originalScale; return v; } } // find in cache? CDecalVert *v = g_DecalVertCache.GetCachedVerts(pDecal); if ( !v ) { // not in cache, clip & light context.InitSurface( pDecal->surfID ); v = R_DecalVertsClip( NULL, pDecal, surfID, pMaterial ); if ( pDecal->clippedVertCount ) { #if _DEBUG // squash vector copy asserts in debug int nextVert = NEXT_MULTIPLE_OF_4(pDecal->clippedVertCount); if ( (nextVert - pDecal->clippedVertCount) < 4 ) { for ( int i = pDecal->clippedVertCount; i < nextVert; i++ ) { v[i].m_cLMCoords.Init(); v[i].m_ctCoords.Init(); v[i].m_vPos.Init(); } } #endif R_DecalVertsLight( v, context, surfID, pDecal->clippedVertCount ); g_DecalVertCache.StoreVertsInCache( pDecal, v ); } } return v; } //----------------------------------------------------------------------------- // Renders a single decal, *could retire the decal!!* //----------------------------------------------------------------------------- void DecalUpdateAndDrawSingle( decalcontext_t &context, SurfaceHandle_t surfID, decal_t* pDecal ) { if( !pDecal->material ) return; // Update dynamic decals bool retire = false; if ( pDecal->flags & ( FDECAL_DYNAMIC | FDECAL_IMMEDIATECLEANUP ) ) retire = DecalUpdate( pDecal ); if( SurfaceHasDispInfo( surfID ) ) { // Dispinfos generate lists of tris for decals when the decal is first // created. } else { CDecalVert *v = R_DecalSetupVerts( context, pDecal, surfID, pDecal->material ); if ( v ) Shader_DecalDrawPoly( v, pDecal->material, surfID, pDecal->clippedVertCount, pDecal, 1.0f ); } if( retire ) { R_DecalUnlink( pDecal, host_state.worldbrush ); } } //----------------------------------------------------------------------------- // Renders all decals on a single surface //----------------------------------------------------------------------------- void DrawDecalsOnSingleSurface_NonQueued( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID, const Vector &vModelOrg) { decal_t* plist = MSurf_DecalPointer( surfID ); decalcontext_t context(pRenderContext, vModelOrg); context.InitSurface(surfID); while ( plist ) { // Store off the next pointer, DecalUpdateAndDrawSingle could unlink decal_t* pnext = plist->pnext; if (!(plist->flags & FDECAL_SECONDPASS)) { DecalUpdateAndDrawSingle( context, surfID, plist ); } plist = pnext; } while ( plist ) { // Store off the next pointer, DecalUpdateAndDrawSingle could unlink decal_t* pnext = plist->pnext; if ((plist->flags & FDECAL_SECONDPASS)) { DecalUpdateAndDrawSingle( context, surfID, plist ); } plist = pnext; } } void DrawDecalsOnSingleSurface( IMatRenderContext *pRenderContext, SurfaceHandle_t surfID ) { DrawDecalsOnSingleSurface_NonQueued( pRenderContext, surfID, modelorg ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void R_DrawDecalsAllImmediate( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade ) { SurfaceHandle_t lastSurf = NULL; decalcontext_t context(pRenderContext, vModelOrg); int nSortTreeCount = g_aDecalSortTrees.Count(); bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree ) { int nBucketCount = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Count(); for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) { if ( g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_nCheckCount != nCheckCount ) continue; intp iHead = g_aDecalSortTrees[iSortTree].m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ).m_iHead; int nCount; intp iElement = iHead; while ( iElement != g_aDecalSortPool.InvalidIndex() ) { decal_t *pDecal = g_aDecalSortPool.Element( iElement ); iElement = g_aDecalSortPool.Next( iElement ); if ( ShouldSkipDecalRender( pDecal ) ) continue; if ( pDecal->surfID != lastSurf ) { lastSurf = pDecal->surfID; } CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material ); if ( !pVerts ) continue; nCount = pDecal->clippedVertCount; // Bind texture. VertexFormat_t vertexFormat = 0; if( bWireframe ) { pRenderContext->Bind( g_materialDecalWireframe ); } else { pRenderContext->BindLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( pDecal->surfID )].lightmapPageID ); pRenderContext->Bind( pDecal->material, pDecal->userdata ); vertexFormat = GetUncompressedFormat( pDecal->material ); } IMesh *pMesh = NULL; pMesh = pRenderContext->GetDynamicMesh(); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nCount, ( ( nCount - 2 ) * 3 ) ); // Set base color. byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a }; if ( flFade != 1.0f ) { color[3] = (byte)( color[3] * flFade ); } // Dynamic decals - fading. if ( pDecal->flags & FDECAL_DYNAMIC ) { float flFadeValue; // Negative fadeDuration value means to fade in if ( pDecal->fadeDuration < 0 ) { flFadeValue = -( GetBaseLocalClient().GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; } else { flFadeValue = 1.0 - ( GetBaseLocalClient().GetTime() - pDecal->fadeStartTime ) / pDecal->fadeDuration; } flFadeValue = clamp( flFadeValue, 0.0f, 1.0f ); color[3] = ( byte )( color[3] * flFadeValue ); } // Compute normal and tangent space if necessary. Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) ) { vecNormal = MSurf_Plane( pDecal->surfID ).normal; if ( vertexFormat & VERTEX_TANGENT_SPACE ) { Vector tVect; bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect ); TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate ); } } // Setup verts. float flOffset = pDecal->lightmapOffset; for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts ) { meshBuilder.Position3fv( pVerts->m_vPos.Base() ); if ( vertexFormat & VERTEX_NORMAL ) { meshBuilder.Normal3fv( vecNormal.Base() ); } meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y ); meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y ); meshBuilder.TexCoord1f( 2, flOffset ); if ( vertexFormat & VERTEX_TANGENT_SPACE ) { meshBuilder.TangentS3fv( vecTangentS.Base() ); meshBuilder.TangentT3fv( vecTangentT.Base() ); } meshBuilder.AdvanceVertexF(); } // Setup indices. int nTriCount = ( nCount - 2 ); CIndexBuilder &indexBuilder = meshBuilder; indexBuilder.FastPolygon( 0, nTriCount ); meshBuilder.End(); pMesh->Draw(); } } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- inline void R_DrawDecalMeshList( DecalMeshList_t &meshList ) { CMatRenderContextPtr pRenderContext( materials ); int nBatchCount = meshList.m_aBatches.Count(); for ( int iBatch = 0; iBatch < nBatchCount; ++iBatch ) { if ( g_pMaterialSystemConfig->nFullbright == 1 ) { pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE ); } else { pRenderContext->BindLightmapPage( meshList.m_aBatches[iBatch].m_iLightmapPage ); } pRenderContext->Bind( meshList.m_aBatches[iBatch].m_pMaterial, meshList.m_aBatches[iBatch].m_pProxy ); meshList.m_pMesh->Draw( meshList.m_aBatches[iBatch].m_iStartIndex, meshList.m_aBatches[iBatch].m_nIndexCount ); } } void R_DrawDecalsAll( IMatRenderContext *pRenderContext, int iGroup, int iTreeType, const Vector &vModelOrg, int nCheckCount, float flFade ) { int nSortTreeCount = g_aDecalSortTrees.Count(); if ( !nSortTreeCount ) return; DecalMeshList_t meshList; CMeshBuilder meshBuilder; SurfaceHandle_t lastSurf = NULL; decalcontext_t context(pRenderContext, vModelOrg); Vector vecNormal( 0.0f, 0.0f, 1.0f ), vecTangentS( 1.0f, 0.0f, 0.0f ), vecTangentT( 0.0f, 1.0f, 0.0f ); int nVertCount = 0; int nIndexCount = 0; int nDecalSortMaxVerts = g_nMaxDecals * 5; int nDecalSortMaxIndices = nDecalSortMaxVerts * 3; // NOTE: This is sort of a hack. The decal wireframe material is 20 bytes; this assumes // we're going to render vertices no larger than 40 bytes/vert. nDecalSortMaxVerts = MIN( nDecalSortMaxVerts, pRenderContext->GetMaxVerticesToRender( g_materialDecalWireframe ) / 4 ); nDecalSortMaxIndices = MIN( nDecalSortMaxIndices, pRenderContext->GetMaxIndicesToRender() ); bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawdecals.GetInt() == 2); float localClientTime = GetBaseLocalClient().GetTime(); for ( int iSortTree = 0; iSortTree < nSortTreeCount; ++iSortTree ) { // Reset the mesh list. bool bMeshInit = true; uint16 unPlayerDecalStickerKitID = 0; // player decals must be split into separate calls by actual basetexture, but overall keep the state so they are bucketed by same material DecalSortTrees_t &sortTree = g_aDecalSortTrees[iSortTree]; int nBucketCount = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Count(); for ( int iBucket = 0; iBucket < nBucketCount; ++iBucket ) { DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( iBucket ); if ( bucket.m_nCheckCount != nCheckCount ) continue; intp iHead = bucket.m_iHead; if ( !g_aDecalSortPool.IsValidIndex( iHead ) ) continue; decal_t *pDecalHead = g_aDecalSortPool.Element( iHead ); Assert( pDecalHead->material ); if ( !pDecalHead->material ) continue; // Vertex format. VertexFormat_t vertexFormat = GetUncompressedFormat( pDecalHead->material ); if ( vertexFormat == 0 ) continue; // New bucket = new batch. DecalBatchList_t *pBatch = NULL; bool bBatchInit = true; int nCount; intp iElement = iHead; while ( iElement != g_aDecalSortPool.InvalidIndex() ) { decal_t *pDecal = g_aDecalSortPool.Element( iElement ); iElement = g_aDecalSortPool.Next( iElement ); if ( ShouldSkipDecalRender( pDecal ) ) continue; float flOffset = 0; if ( pDecal->surfID != lastSurf ) { lastSurf = pDecal->surfID; flOffset = pDecal->lightmapOffset; // Compute normal and tangent space if necessary. if ( vertexFormat & ( VERTEX_NORMAL | VERTEX_TANGENT_SPACE ) ) { vecNormal = MSurf_Plane( pDecal->surfID ).normal; if ( vertexFormat & VERTEX_TANGENT_SPACE ) { Vector tVect; bool bNegate = TangentSpaceSurfaceSetup( pDecal->surfID, tVect ); TangentSpaceComputeBasis( vecTangentS, vecTangentT, vecNormal, tVect, bNegate ); } } } CDecalVert *pVerts = R_DecalSetupVerts( context, pDecal, pDecal->surfID, pDecal->material ); if ( !pVerts ) continue; nCount = pDecal->clippedVertCount; if ( ( nCount > nDecalSortMaxVerts ) || ( ( nCount - 2 ) > nDecalSortMaxIndices ) ) { // The overflow case below causes a crash if the overflow occurs on the first iteration // due to the meshBuilder.End() not getting setup with an m_pMesh. Instead, just ignore this // decal's clipped vertexes. if ( bMeshInit ) { // this would have caused otherwise caused a crash because the meshbuilder has not been setup yet DevWarning( "R_DrawDecalsAll: Overflow of mesh builder (%d, %d, %d, %d %d), skipping this decal!\n", nVertCount, nIndexCount, nCount, nDecalSortMaxVerts, nDecalSortMaxIndices ); } continue; } // Overflow - new mesh, batch. if ( ( ( nVertCount + nCount ) > nDecalSortMaxVerts ) || ( nIndexCount + ( nCount - 2 ) > nDecalSortMaxIndices ) || ( !bMeshInit && ( pDecal->flags & FDECAL_PLAYERSPRAY ) && ( uint16( reinterpret_cast< uintp >( pDecal->userdata ) ) != unPlayerDecalStickerKitID ) ) ) { // Finish this batch. if ( pBatch ) { pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex ); } // End the mesh building phase and render. meshBuilder.End(); R_DrawDecalMeshList( meshList ); // Reset. bMeshInit = true; pBatch = NULL; bBatchInit = true; unPlayerDecalStickerKitID = 0; } // Create the mesh. if ( bMeshInit ) { // Reset the mesh list. meshList.m_pMesh = NULL; meshList.m_aBatches.RemoveAll(); // Create a mesh for this vertex format (vertex format per SortTree). if ( bWireframe ) { meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialDecalWireframe ); } else { meshList.m_pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pDecalHead->material ); } meshBuilder.Begin( meshList.m_pMesh, MATERIAL_TRIANGLES, nDecalSortMaxVerts, nDecalSortMaxIndices ); nVertCount = 0; nIndexCount = 0; bMeshInit = false; unPlayerDecalStickerKitID = ( pDecal->flags & FDECAL_PLAYERSPRAY ) ? uint16( reinterpret_cast< uintp >( pDecal->userdata ) ) : 0; // Keep track of playerdecal proxy state for batches roll over } // Create the batch. if ( bBatchInit ) { // Create a batch for this bucket = material/lightmap pair. // Todo: we also could flush it right here and continue. if ( meshList.m_aBatches.Count() + 1 > meshList.m_aBatches.NumAllocated() ) { Warning( "R_DrawDecalsAll: overflowing m_aBatches. Reduce # of decals in the scene.\n" ); meshBuilder.End(); R_DrawDecalMeshList( meshList ); return; } int iBatchList = meshList.m_aBatches.AddToTail(); pBatch = &meshList.m_aBatches[iBatchList]; pBatch->m_iStartIndex = nIndexCount; if ( bWireframe ) { pBatch->m_pMaterial = g_materialDecalWireframe; } else { pBatch->m_pMaterial = pDecalHead->material; if ( pDecal->flags & FDECAL_PLAYERSPRAY ) pBatch->m_pProxy = pDecal->userdata; // Player sprays must use individual proxies, probably all materials can, but this is a safe change else pBatch->m_pProxy = pDecalHead->userdata; pBatch->m_iLightmapPage = materialSortInfoArray[MSurf_MaterialSortID( pDecalHead->surfID )].lightmapPageID; } bBatchInit = false; } Assert ( pBatch ); // Set base color. byte color[4] = { pDecal->color.r, pDecal->color.g, pDecal->color.b, pDecal->color.a }; if ( flFade != 1.0f ) { color[3] = (byte)( color[3] * flFade ); } // Dynamic decals - fading. if ( pDecal->flags & FDECAL_DYNAMIC ) { float flFadeValue; // Negative fadeDuration value means to fade in if ( pDecal->fadeDuration < 0 ) { flFadeValue = -( localClientTime - pDecal->fadeStartTime ) / pDecal->fadeDuration; } else { flFadeValue = 1.0 - ( localClientTime - pDecal->fadeStartTime ) / pDecal->fadeDuration; } flFadeValue = clamp( flFadeValue, 0.0f, 1.0f ); color[3] = ( byte )( color[3] * flFadeValue ); } // Setup verts. for ( int iVert = 0; iVert < nCount; ++iVert, ++pVerts ) { meshBuilder.Position3fv( pVerts->m_vPos.Base() ); if ( vertexFormat & VERTEX_NORMAL ) { meshBuilder.Normal3fv( vecNormal.Base() ); } meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, pVerts->m_ctCoords.x, pVerts->m_ctCoords.y ); meshBuilder.TexCoord2f( 1, pVerts->m_cLMCoords.x, pVerts->m_cLMCoords.y ); meshBuilder.TexCoord1f( 2, flOffset ); if ( vertexFormat & VERTEX_TANGENT_SPACE ) { meshBuilder.TangentS3fv( vecTangentS.Base() ); meshBuilder.TangentT3fv( vecTangentT.Base() ); } meshBuilder.AdvanceVertexF(); } // Setup indices. int nTriCount = nCount - 2; CIndexBuilder &indexBuilder = meshBuilder; indexBuilder.FastPolygon( nVertCount, nTriCount ); // Update counters. nVertCount += nCount; nIndexCount += ( nTriCount * 3 ); } if ( pBatch ) { pBatch->m_nIndexCount = ( nIndexCount - pBatch->m_iStartIndex ); } } if ( !bMeshInit ) { meshBuilder.End(); R_DrawDecalMeshList( meshList ); nVertCount = 0; nIndexCount = 0; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void DecalSurfaceDraw( IMatRenderContext *pRenderContext, int renderGroup, float flFade ) { // VPROF_BUDGET( "Decals", "Decals" ); VPROF( "DecalsDraw" ); if( !r_drawdecals.GetBool() ) { return; } int nCheckCount = g_nDecalSortCheckCount; if ( renderGroup == MAX_MAT_SORT_GROUPS ) { // Brush Model nCheckCount = g_nBrushModelDecalSortCheckCount; } if ( r_drawbatchdecals.GetBool() ) { // Draw world decals. R_DrawDecalsAll( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, modelorg, nCheckCount, flFade ); // Draw lightmapped non-world decals. R_DrawDecalsAll( pRenderContext, renderGroup, LIGHTMAP, modelorg, nCheckCount, flFade ); // Draw non-lit(mod2x) decals. R_DrawDecalsAll( pRenderContext, renderGroup, NONLIGHTMAP, modelorg, nCheckCount, flFade ); } else { // Draw world decals. R_DrawDecalsAllImmediate( pRenderContext, renderGroup, PERMANENT_LIGHTMAP, modelorg, nCheckCount, flFade ); // Draw lightmapped non-world decals. R_DrawDecalsAllImmediate( pRenderContext, renderGroup, LIGHTMAP, modelorg, nCheckCount, flFade ); // Draw non-lit(mod2x) decals. R_DrawDecalsAllImmediate( pRenderContext, renderGroup, NONLIGHTMAP, modelorg, nCheckCount, flFade ); } } //----------------------------------------------------------------------------- // Purpose: Add decals to sorted decal list. //----------------------------------------------------------------------------- void DecalSurfaceAdd( SurfaceHandle_t surfID, int iGroup ) { // Performance analysis. // VPROF_BUDGET( "Decals", "Decals" ); VPROF( "DecalsBatch" ); // Go through surfaces decal list and add them to the correct lists. decal_t *pDecalList = MSurf_DecalPointer( surfID ); if ( !pDecalList ) return; int nCheckCount = g_nDecalSortCheckCount; if ( iGroup == MAX_MAT_SORT_GROUPS ) { // Brush Model nCheckCount = g_nBrushModelDecalSortCheckCount; } int iTreeType = -1; decal_t *pNext = NULL; for ( decal_t *pDecal = pDecalList; pDecal; pDecal = pNext ) { // Get the next pointer. pNext = pDecal->pnext; // Lightmap decals. if ( pDecal->material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ) ) { // Permanent lightmapped decals. if ( pDecal->flags & FDECAL_PERMANENT ) { iTreeType = PERMANENT_LIGHTMAP; } // Non-permanent lightmapped decals. else { iTreeType = LIGHTMAP; } } // Non-lightmapped decals. else { iTreeType = NONLIGHTMAP; } pDecal->flags &= ~FDECAL_HASUPDATED; intp iPool = g_aDecalSortPool.Alloc( true ); if ( iPool != g_aDecalSortPool.InvalidIndex() ) { g_aDecalSortPool[iPool] = pDecal; DecalSortTrees_t &sortTree = g_aDecalSortTrees[ pDecal->m_iSortTree ]; DecalMaterialBucket_t &bucket = sortTree.m_aDecalSortBuckets[iGroup][iTreeType].Element( pDecal->m_iSortMaterial ); if ( bucket.m_nCheckCount == nCheckCount ) { // Brush decals render front-to-back, so link after tail intp iTail = bucket.m_iTail; g_aDecalSortPool.LinkAfter( iTail, iPool ); bucket.m_iTail = iPool; } else { bucket.m_iHead = bucket.m_iTail = iPool; bucket.m_nCheckCount = nCheckCount; } } } } #pragma check_stack(off) #endif // DEDICATED