diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/shadowmgr.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/shadowmgr.cpp')
| -rw-r--r-- | engine/shadowmgr.cpp | 3774 |
1 files changed, 3774 insertions, 0 deletions
diff --git a/engine/shadowmgr.cpp b/engine/shadowmgr.cpp new file mode 100644 index 0000000..d60f953 --- /dev/null +++ b/engine/shadowmgr.cpp @@ -0,0 +1,3774 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + + +#include "render_pch.h" +#include "shadowmgr.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "interface.h" +#include "mathlib/vmatrix.h" +#include "bsptreedata.h" +#include "materialsystem/itexture.h" +#include "filesystem.h" +#include "utlbidirectionalset.h" +#include "l_studio.h" +#include "istudiorender.h" +#include "engine/ivmodelrender.h" +#include "collisionutils.h" +#include "debugoverlay.h" +#include "tier0/vprof.h" +#include "disp.h" +#include "gl_rmain.h" +#include "MaterialBuckets.h" +#include "r_decal.h" +#include "cmodel_engine.h" +#include "iclientrenderable.h" +#include "cdll_engine_int.h" +#include "sys_dll.h" +#include "render.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Shadow-related functionality exported by the engine +// +// We have two shadow-related caches in this system +// 1) A surface cache. We keep track of which surfaces the shadows can +// potentially hit. The computation of the surface cache should be +// as fast as possible +// 2) A surface vertex cache. Once we know what surfaces the shadow +// hits, we caompute the actual polygons using a clip. This is only +// useful for shadows that we know don't change too frequently, so +// we pass in a flag when making the shadow to indicate whether the +// vertex cache should be used or not. The assumption is that the client +// of this system should know whether the shadows are always changing or not +// +// The first cache is generated when the shadow is initially projected, and +// the second cache is generated when the surfaces are actually being rendered. +// +// For rendering, I assign a sort order ID to all materials used by shadow +// decals. The sort order serves the identical purpose to the material's EnumID +// but I remap those IDs so I can keep a small list of decals to render with +// that enum ID (the other option would be to allocate an array with a number +// of elements == to the number of material enumeration IDs, which is pretty large). +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// forward decarations +//----------------------------------------------------------------------------- +extern int r_surfacevisframe; +extern IStudioRender *g_pStudioRender; + + +#define BACKFACE_EPSILON 0.01f + + +// Max number of vertices per shadow decal +enum +{ + SHADOW_VERTEX_SMALL_CACHE_COUNT = 8, + SHADOW_VERTEX_LARGE_CACHE_COUNT = 32, + SHADOW_VERTEX_TEMP_COUNT = 48, + MAX_CLIP_PLANE_COUNT = 4, + SURFACE_BOUNDS_CACHE_COUNT = 1024, + //============================================================================= + // HPE_BEGIN: + // [smessick] Cache size for the shadow decals. This used to be on the stack. + //============================================================================= + SHADOW_DECAL_CACHE_COUNT = 16*1024, + MAX_SHADOW_DECAL_CACHE_COUNT = 64*1024, + //============================================================================= + // HPE_END + //============================================================================= +}; + +//----------------------------------------------------------------------------- +// Used to clip the shadow decals +//----------------------------------------------------------------------------- +struct ShadowClipState_t +{ + int m_CurrVert; + int m_TempCount; + int m_ClipCount; + ShadowVertex_t m_pTempVertices[SHADOW_VERTEX_TEMP_COUNT]; + ShadowVertex_t* RESTRICT m_ppClipVertices[2][SHADOW_VERTEX_TEMP_COUNT]; +}; + + +//----------------------------------------------------------------------------- +// ConVars (must be defined before CShadowMgr is instanced!) +//----------------------------------------------------------------------------- +ConVar r_shadows("r_shadows", "1"); +ConVar r_shadows_gamecontrol("r_shadows_gamecontrol", "-1", FCVAR_CHEAT ); // Shadow override controlled by game entities (shadow_controller) +static ConVar r_shadowwireframe("r_shadowwireframe", "0", FCVAR_CHEAT ); +static ConVar r_shadowids("r_shadowids", "0", FCVAR_CHEAT ); +static ConVar r_flashlightdrawsweptbbox( "r_flashlightdrawsweptbbox", "0" ); +static ConVar r_flashlightdrawfrustumbbox( "r_flashlightdrawfrustumbbox", "0" ); +static ConVar r_flashlightnodraw( "r_flashlightnodraw", "0" ); + +static ConVar r_flashlightupdatedepth( "r_flashlightupdatedepth", "1" ); +static ConVar r_flashlightdrawdepth( "r_flashlightdrawdepth", "0" ); +static ConVar r_flashlightrenderworld( "r_flashlightrenderworld", "1" ); +static ConVar r_flashlightrendermodels( "r_flashlightrendermodels", "1" ); +static ConVar r_flashlightrender( "r_flashlightrender", "1" ); +static ConVar r_flashlightculldepth( "r_flashlightculldepth", "1" ); +ConVar r_flashlight_version2( "r_flashlight_version2", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); + + +//----------------------------------------------------------------------------- +// Implementation of IShadowMgr +//----------------------------------------------------------------------------- +class CShadowMgr : public IShadowMgrInternal, ISpatialLeafEnumerator +{ +public: + // constructor + CShadowMgr(); + + // Methods inherited from IShadowMgr + virtual ShadowHandle_t CreateShadow( IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy, int creationFlags ); + virtual ShadowHandle_t CreateShadowEx( IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy, int creationFlags ); + virtual void DestroyShadow( ShadowHandle_t handle ); + virtual void SetShadowMaterial( ShadowHandle_t handle, IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy ); + virtual void EnableShadow( ShadowHandle_t handle, bool bEnable ); + virtual void ProjectFlashlight( ShadowHandle_t handle, const VMatrix& worldToShadow, int nLeafCount, const int *pLeafList ); + virtual void ProjectShadow( ShadowHandle_t handle, const Vector &origin, + const Vector& projectionDir, const VMatrix& worldToShadow, const Vector2D& size, + int nLeafCount, const int *pLeafList, + float maxHeight, float falloffOffset, float falloffAmount, const Vector &vecCasterOrigin ); + virtual const Frustum_t &GetFlashlightFrustum( ShadowHandle_t handle ); + virtual const FlashlightState_t &GetFlashlightState( ShadowHandle_t handle ); + virtual int ProjectAndClipVertices( ShadowHandle_t handle, int count, + Vector** ppPosition, ShadowVertex_t*** ppOutVertex ); + virtual void AddShadowToBrushModel( ShadowHandle_t handle, model_t* pModel, + const Vector& origin, const QAngle& angles ); + virtual void RemoveAllShadowsFromBrushModel( model_t* pModel ); + virtual void AddShadowToModel( ShadowHandle_t shadow, ModelInstanceHandle_t handle ); + virtual void RemoveAllShadowsFromModel( ModelInstanceHandle_t handle ); + virtual const ShadowInfo_t& GetInfo( ShadowHandle_t handle ); + virtual void SetFlashlightRenderState( ShadowHandle_t handle ); + + // Methods inherited from IShadowMgrInternal + virtual void LevelInit( int nSurfCount ); + virtual void LevelShutdown(); + virtual void AddShadowsOnSurfaceToRenderList( ShadowDecalHandle_t decalHandle ); + virtual void ClearShadowRenderList(); + virtual void ComputeRenderInfo( ShadowDecalRenderInfo_t* pInfo, ShadowHandle_t handle ) const; + virtual void SetModelShadowState( ModelInstanceHandle_t instance ); + virtual unsigned short InvalidShadowIndex( ); + + // Methods of ISpatialLeafEnumerator + virtual bool EnumerateLeaf( int leaf, int context ); + + // Sets the texture coordinate range for a shadow... + virtual void SetShadowTexCoord( ShadowHandle_t handle, float x, float y, float w, float h ); + + // Set extra clip planes related to shadows... + // These are used to prevent pokethru and back-casting + virtual void ClearExtraClipPlanes( ShadowHandle_t shadow ); + virtual void AddExtraClipPlane( ShadowHandle_t shadow, const Vector& normal, float dist ); + + // Gets the first model associated with a shadow + unsigned short& FirstModelInShadow( ShadowHandle_t h ) { return m_Shadows[h].m_FirstModel; } + + // Set the darkness falloff bias + virtual void SetFalloffBias( ShadowHandle_t shadow, unsigned char ucBias ); + + // Set the number of world material buckets. This should happen exactly once per level load. + virtual void SetNumWorldMaterialBuckets( int numMaterialSortBins ); + + // Update the state for a flashlight. + virtual void UpdateFlashlightState( ShadowHandle_t shadowHandle, const FlashlightState_t &lightState ); + + virtual void DrawFlashlightDecals( int sortGroup, bool bDoMasking ); + virtual void DrawFlashlightDecalsOnSingleSurface( SurfaceHandle_t surfID, bool bDoMasking ); + + virtual void DrawFlashlightOverlays( int sortGroup, bool bDoMasking ); + + virtual void DrawFlashlightDepthTexture( ); + virtual void SetFlashlightDepthTexture( ShadowHandle_t shadowHandle, ITexture *pFlashlightDepthTexture, unsigned char ucShadowStencilBit ); + + virtual void AddFlashlightRenderable( ShadowHandle_t shadow, IClientRenderable *pRenderable ); + virtual void DrawFlashlightDecalsOnDisplacements( int sortGroup, CDispInfo **visibleDisps, int nVisibleDisps, bool bDoMasking ); + virtual bool ModelHasShadows( ModelInstanceHandle_t instance ); + +private: + enum + { + SHADOW_DISABLED = (SHADOW_LAST_FLAG << 1), + }; + + typedef CUtlFixedLinkedList< ShadowDecalHandle_t >::IndexType_t ShadowSurfaceIndex_t; + + struct SurfaceBounds_t + { + fltx4 m_vecMins; + fltx4 m_vecMaxs; + Vector m_vecCenter; + float m_flRadius; + int m_nSurfaceIndex; + }; + + struct ShadowVertexSmallList_t + { + ShadowVertex_t m_Verts[SHADOW_VERTEX_SMALL_CACHE_COUNT]; + }; + + struct ShadowVertexLargeList_t + { + ShadowVertex_t m_Verts[SHADOW_VERTEX_LARGE_CACHE_COUNT]; + }; + + // A cache entries' worth of vertices.... + struct ShadowVertexCache_t + { + unsigned short m_Count; + ShadowHandle_t m_Shadow; + unsigned short m_CachedVerts; + ShadowVertex_t* m_pVerts; + }; + + typedef unsigned short FlashlightHandle_t; + + // Shadow state + struct Shadow_t : public ShadowInfo_t + { + Vector m_ProjectionDir; + IMaterial* m_pMaterial; // material for rendering surfaces + IMaterial* m_pModelMaterial; // material for rendering models + void* m_pBindProxy; + unsigned short m_Flags; + unsigned short m_SortOrder; + float m_flSphereRadius; // Radius of sphere surrounding the shadow + Ray_t m_Ray; // NOTE: Ray needs to be on 16-byte boundaries. + Vector m_vecSphereCenter; // Sphere surrounding the shadow + + FlashlightHandle_t m_FlashlightHandle; + ITexture *m_pFlashlightDepthTexture; + + // Extra clip planes + unsigned short m_ClipPlaneCount; + Vector m_ClipPlane[MAX_CLIP_PLANE_COUNT]; + float m_ClipDist[MAX_CLIP_PLANE_COUNT]; + + // First shadow decal the shadow has + ShadowSurfaceIndex_t m_FirstDecal; + + // First model the shadow is projected onto + unsigned short m_FirstModel; + + // Stencil bit used to mask this shadow + unsigned char m_ucShadowStencilBit; + }; + + // Each surface has one of these, they reference the main shadow + // projector and cached off shadow decals. + struct ShadowDecal_t + { + SurfaceHandle_t m_SurfID; + ShadowSurfaceIndex_t m_ShadowListIndex; + ShadowHandle_t m_Shadow; + DispShadowHandle_t m_DispShadow; + unsigned short m_ShadowVerts; + + // This is a handle of the next shadow decal to be rendered + ShadowDecalHandle_t m_NextRender; + }; + + // This structure is used when building new shadow information + struct ShadowBuildInfo_t + { + ShadowHandle_t m_Shadow; + Vector m_RayStart; + Vector m_ProjectionDirection; + Vector m_vecSphereCenter; // Sphere surrounding the shadow + float m_flSphereRadius; // Radius of sphere surrounding the shadow + const byte *m_pVis; // Vis from the ray start + }; + + // This structure contains rendering information + struct ShadowRenderInfo_t + { + int m_VertexCount; + int m_IndexCount; + int m_nMaxVertices; + int m_nMaxIndices; + int m_Count; + int* m_pCache; + int m_DispCount; + const VMatrix* m_pModelToWorld; + VMatrix m_WorldToModel; + DispShadowHandle_t* m_pDispCache; + }; + + // Structures used to assign sort order handles + struct SortOrderInfo_t + { + int m_MaterialEnum; + int m_RefCount; + }; + + typedef void (*ShadowDebugFunc_t)( ShadowHandle_t shadowHandle, const Vector &vecCentroid ); + + // m_FlashlightWorldMaterialBuckets is where surfaces are stored per flashlight each frame. + typedef CUtlVector<FlashlightHandle_t> WorldMaterialBuckets_t; + + struct FlashlightInfo_t + { + FlashlightState_t m_FlashlightState; + unsigned short m_Shadow; + Frustum_t m_Frustum; + CMaterialsBuckets<SurfaceHandle_t> m_MaterialBuckets; + CMaterialsBuckets<SurfaceHandle_t> m_OccluderBuckets; + + CUtlVector< IClientRenderable *> m_Renderables; + }; + +private: + // Applies a flashlight to all surfaces in the leaf + void ApplyFlashlightToLeaf( const Shadow_t &shadow, mleaf_t* pLeaf, ShadowBuildInfo_t* pBuild ); + + // Applies a shadow to all surfaces in the leaf + void ApplyShadowToLeaf( const Shadow_t &shadow, mleaf_t* RESTRICT pLeaf, ShadowBuildInfo_t* RESTRICT pBuild ); + + // These functions deal with creation of render sort ids + void SetMaterial( Shadow_t& shadow, IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy ); + void CleanupMaterial( Shadow_t& shadow ); + + // These functions add/remove shadow decals to surfaces + ShadowDecalHandle_t AddShadowDecalToSurface( SurfaceHandle_t surfID, ShadowHandle_t handle ); + void RemoveShadowDecalFromSurface( SurfaceHandle_t surfID, ShadowDecalHandle_t decalHandle ); + + // Adds the surface to the list for this shadow + bool AddDecalToShadowList( ShadowHandle_t handle, ShadowDecalHandle_t decalHandle ); + + // Removes the shadow to the list of surfaces + void RemoveDecalFromShadowList( ShadowHandle_t handle, ShadowDecalHandle_t decalHandle ); + + // Actually projects + clips vertices + int ProjectAndClipVertices( const Shadow_t& shadow, const VMatrix& worldToShadow, + const VMatrix *pWorldToModel, int count, Vector** ppPosition, ShadowVertex_t*** ppOutVertex ); + + // These functions hook/unhook shadows up to surfaces + vice versa + void AddSurfaceToShadow( ShadowHandle_t handle, SurfaceHandle_t surfID ); + void RemoveSurfaceFromShadow( ShadowHandle_t handle, SurfaceHandle_t surfID ); + void RemoveAllSurfacesFromShadow( ShadowHandle_t handle ); + void RemoveAllShadowsFromSurface( SurfaceHandle_t surfID ); + + // Deals with model shadow management + void RemoveAllModelsFromShadow( ShadowHandle_t handle ); + + // Applies the shadow to a surface + void ApplyShadowToSurface( ShadowBuildInfo_t& build, SurfaceHandle_t surfID ); + + // Applies the shadow to a displacement + void ApplyShadowToDisplacement( ShadowBuildInfo_t& build, IDispInfo *pDispInfo, bool bIsFlashlight ); + + // Renders shadows that all share a material enumeration + void RenderShadowList( IMatRenderContext *pRenderContext, ShadowDecalHandle_t decalHandle, const VMatrix* pModelToWorld ); + + // Should we cache vertices? + bool ShouldCacheVertices( const ShadowDecal_t& decal ); + + // Generates a list displacement shadow vertices to render + bool GenerateDispShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecal_t& decal, ShadowRenderInfo_t& info ); + + // Generates a list shadow vertices to render + bool GenerateNormalShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecal_t& decal, ShadowRenderInfo_t& info ); + + // Adds normal shadows to the mesh builder + int AddNormalShadowsToMeshBuilder( CMeshBuilder& meshBuilder, ShadowRenderInfo_t& info ); + + // Adds displacement shadows to the mesh builder + int AddDisplacementShadowsToMeshBuilder( CMeshBuilder& meshBuilder, + ShadowRenderInfo_t& info, int baseIndex ); + + // Does the actual work of computing shadow vertices + bool ComputeShadowVertices( ShadowDecal_t& decal, const VMatrix* pModelToWorld, const VMatrix* pWorldToModel, ShadowVertexCache_t* pVertexCache ); + + // Project vertices into shadow space + bool ProjectVerticesIntoShadowSpace( const VMatrix& modelToShadow, + float maxDist, int count, Vector** RESTRICT ppPosition, ShadowClipState_t& clip ); + + // Copies vertex info from the clipped vertices + void CopyClippedVertices( int count, ShadowVertex_t** ppSrcVert, ShadowVertex_t* pDstVert, const Vector &vToAdd ); + + // Allocate, free vertices + ShadowVertex_t* AllocateVertices( ShadowVertexCache_t& cache, int count ); + void FreeVertices( ShadowVertexCache_t& cache ); + + // Gets at cache entry... + ShadowVertex_t* GetCachedVerts( const ShadowVertexCache_t& cache ); + + // Clears out vertices in the temporary cache + void ClearTempCache( ); + + // Renders debugging information + void RenderDebuggingInfo( const ShadowRenderInfo_t &info, ShadowDebugFunc_t func ); + + // Methods for dealing with world material buckets for flashlights. + void ClearAllFlashlightMaterialBuckets( void ); + void AddSurfaceToFlashlightMaterialBuckets( ShadowHandle_t handle, SurfaceHandle_t surfID ); + void AllocFlashlightMaterialBuckets( FlashlightHandle_t flashlightID ); + + // Render all projected textures (including shadows and flashlights) + void RenderProjectedTextures( const VMatrix* pModelToWorld ); + + void RenderFlashlights( bool bDoMasking, const VMatrix* pModelToWorld ); + + void SetFlashlightStencilMasks( bool bDoMasking ); + + void SetStencilAndScissor( IMatRenderContext *pRenderContext, FlashlightInfo_t &flashlightInfo, bool bUseStencil ); + + void EnableStencilAndScissorMasking( IMatRenderContext *pRenderContext, const FlashlightInfo_t &flashlightInfo, bool bDoMasking ); + + void DisableStencilAndScissorMasking( IMatRenderContext *pRenderContext ); + + void RenderShadows( const VMatrix* pModelToWorld ); + + // Generates a list shadow vertices to render + void GenerateShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecalHandle_t decalHandle, ShadowRenderInfo_t& info ); + + // Methods related to the surface bounds cache + void ComputeSurfaceBounds( SurfaceBounds_t* pBounds, SurfaceHandle_t nSurfID ); + const SurfaceBounds_t* GetSurfaceBounds( SurfaceHandle_t nSurfID ); + bool IsShadowNearSurface( ShadowHandle_t h, SurfaceHandle_t nSurfID, const VMatrix* pModelToWorld, const VMatrix* pWorldToModel ); + +private: + // List of all shadows (one per cast shadow) + // Align it so the Ray in the Shadow_t is aligned + CUtlLinkedList< Shadow_t, ShadowHandle_t, false, int, CUtlMemoryAligned< UtlLinkedListElem_t< Shadow_t, ShadowHandle_t >, 16 > > m_Shadows; + + // List of all shadow decals (one per surface hit by a shadow) + CUtlLinkedList< ShadowDecal_t, ShadowDecalHandle_t, true, int > m_ShadowDecals; + + // List of all shadow decals associated with a particular shadow + CUtlFixedLinkedList< ShadowDecalHandle_t > m_ShadowSurfaces; + + // List of queued decals waiting to be rendered.... + CUtlVector<ShadowDecalHandle_t> m_RenderQueue; + + // Used to assign sort order handles + CUtlLinkedList<SortOrderInfo_t, unsigned short> m_SortOrderIds; + + // A cache of shadow vertex data... + CUtlLinkedList<ShadowVertexCache_t, unsigned short> m_VertexCache; + + // This is temporary, not saved off.... + CUtlVector<ShadowVertexCache_t> m_TempVertexCache; + + // Vertex data + CUtlLinkedList<ShadowVertexSmallList_t, unsigned short> m_SmallVertexList; + CUtlLinkedList<ShadowVertexLargeList_t, unsigned short> m_LargeVertexList; + + // Model-shadow association + CBidirectionalSet< ModelInstanceHandle_t, ShadowHandle_t, unsigned short > m_ShadowsOnModels; + + // Cache of information for surface bounds + typedef CUtlLinkedList< SurfaceBounds_t, unsigned short, false, int, CUtlMemoryFixed< UtlLinkedListElem_t< SurfaceBounds_t, unsigned short >, SURFACE_BOUNDS_CACHE_COUNT, 16 > > SurfaceBoundsCache_t; + typedef SurfaceBoundsCache_t::IndexType_t SurfaceBoundsCacheIndex_t; + SurfaceBoundsCache_t m_SurfaceBoundsCache; + SurfaceBoundsCacheIndex_t *m_pSurfaceBounds; + + // The number of decals we're gonna need to render + int m_DecalsToRender; + + CUtlLinkedList<FlashlightInfo_t> m_FlashlightStates; + int m_NumWorldMaterialBuckets; + bool m_bInitialized; + + //============================================================================= + // HPE_BEGIN: + // [smessick] These used to be dynamically allocated on the stack. + //============================================================================= + CUtlMemory<int> m_ShadowDecalCache; + CUtlMemory<DispShadowHandle_t> m_DispShadowDecalCache; + //============================================================================= + // HPE_END + //============================================================================= +}; + + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CShadowMgr s_ShadowMgr; +IShadowMgrInternal* g_pShadowMgr = &s_ShadowMgr; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CShadowMgr, IShadowMgr, + ENGINE_SHADOWMGR_INTERFACE_VERSION, s_ShadowMgr); + + +//----------------------------------------------------------------------------- +// Shadows on model instances +//----------------------------------------------------------------------------- +unsigned short& FirstShadowOnModel( ModelInstanceHandle_t h ) +{ + // See l_studio.cpp + return FirstShadowOnModelInstance( h ); +} + +unsigned short& FirstModelInShadow( ShadowHandle_t h ) +{ + return s_ShadowMgr.FirstModelInShadow(h); +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CShadowMgr::CShadowMgr() +{ + m_ShadowSurfaces.SetGrowSize( 4096 ); + m_ShadowDecals.SetGrowSize( 4096 ); + + m_ShadowsOnModels.Init( ::FirstShadowOnModel, ::FirstModelInShadow ); + m_NumWorldMaterialBuckets = 0; + m_pSurfaceBounds = NULL; + m_bInitialized = false; + ClearShadowRenderList(); + + //============================================================================= + // HPE_BEGIN: + // [smessick] Initialize the shadow decal caches. These used to be dynamically + // allocated on the stack, but we were getting stack overflows. + //============================================================================= + + m_ShadowDecalCache.SetGrowSize( 4096 ); + m_DispShadowDecalCache.SetGrowSize( 4096 ); + + m_ShadowDecalCache.Grow( SHADOW_DECAL_CACHE_COUNT ); + m_DispShadowDecalCache.Grow( SHADOW_DECAL_CACHE_COUNT ); + + //============================================================================= + // HPE_END + //============================================================================= +} + + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- +void CShadowMgr::LevelInit( int nSurfCount ) +{ + if ( m_bInitialized ) + return; + m_bInitialized = true; + + m_pSurfaceBounds = new SurfaceBoundsCacheIndex_t[nSurfCount]; + + // NOTE: Need to memset to 0 if we switch to integer SurfaceBoundsCacheIndex_t here + COMPILE_TIME_ASSERT( sizeof(SurfaceBoundsCacheIndex_t) == 2 ); + memset( m_pSurfaceBounds, 0xFF, nSurfCount * sizeof(SurfaceBoundsCacheIndex_t) ); +} + +void CShadowMgr::LevelShutdown() +{ + if ( !m_bInitialized ) + return; + + if ( m_pSurfaceBounds ) + { + delete[] m_pSurfaceBounds; + m_pSurfaceBounds = NULL; + } + + m_SurfaceBoundsCache.RemoveAll(); + m_bInitialized = false; +} + + +//----------------------------------------------------------------------------- +// Create, destroy material sort order ids... +//----------------------------------------------------------------------------- +void CShadowMgr::SetMaterial( Shadow_t& shadow, IMaterial* pMaterial, IMaterial* pModelMaterial, void *pBindProxy ) +{ + shadow.m_pMaterial = pMaterial; + shadow.m_pModelMaterial = pModelMaterial; + shadow.m_pBindProxy = pBindProxy; + + // We're holding onto this material + if ( pMaterial ) + { + pMaterial->IncrementReferenceCount(); + } + if ( pModelMaterial ) + { + pModelMaterial->IncrementReferenceCount(); + } + + // Search the sort order handles for an enumeration id match + int materialEnum = (int)pMaterial; + for (unsigned short i = m_SortOrderIds.Head(); i != m_SortOrderIds.InvalidIndex(); + i = m_SortOrderIds.Next(i) ) + { + // Found a match, lets increment the refcount of this sort order id + if (m_SortOrderIds[i].m_MaterialEnum == materialEnum) + { + ++m_SortOrderIds[i].m_RefCount; + shadow.m_SortOrder = i; + return; + } + } + + // Didn't find it, lets assign a new sort order ID, with a refcount of 1 + shadow.m_SortOrder = m_SortOrderIds.AddToTail(); + m_SortOrderIds[shadow.m_SortOrder].m_MaterialEnum = materialEnum; + m_SortOrderIds[shadow.m_SortOrder].m_RefCount = 1; + + // Make sure the render queue has as many entries as the max sort order id. + int count = m_RenderQueue.Count(); + while( count < m_SortOrderIds.MaxElementIndex() ) + { + MEM_ALLOC_CREDIT(); + m_RenderQueue.AddToTail( SHADOW_DECAL_HANDLE_INVALID ); + ++count; + } +} + +void CShadowMgr::CleanupMaterial( Shadow_t& shadow ) +{ + // Decrease the sort order reference count + if (--m_SortOrderIds[shadow.m_SortOrder].m_RefCount <= 0) + { + // No one referencing the sort order number? + // Then lets clean up the sort order id + m_SortOrderIds.Remove(shadow.m_SortOrder); + } + + // We're done with this material + if ( shadow.m_pMaterial ) + { + shadow.m_pMaterial->DecrementReferenceCount(); + } + if ( shadow.m_pModelMaterial ) + { + shadow.m_pModelMaterial->DecrementReferenceCount(); + } +} + + +//----------------------------------------------------------------------------- +// For the model shadow list +//----------------------------------------------------------------------------- +unsigned short CShadowMgr::InvalidShadowIndex( ) +{ + return m_ShadowsOnModels.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Create, destroy shadows +//----------------------------------------------------------------------------- +ShadowHandle_t CShadowMgr::CreateShadow( IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy, int creationFlags ) +{ + return CreateShadowEx( pMaterial, pModelMaterial, pBindProxy, creationFlags ); +} + + +ShadowHandle_t CShadowMgr::CreateShadowEx( IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy, int creationFlags ) +{ +#ifndef SWDS + ShadowHandle_t h = m_Shadows.AddToTail(); + //============================================================================= + // HPE_BEGIN: + // [smessick] Check for overflow. + //============================================================================= + if ( h == m_Shadows.InvalidIndex() ) + { + ExecuteNTimes( 10, Warning( "CShadowMgr::CreateShadowEx - overflowed m_Shadows linked list!\n" ) ); + return h; + } + //============================================================================= + // HPE_END + //============================================================================= + + Shadow_t& shadow = m_Shadows[h]; + SetMaterial( shadow, pMaterial, pModelMaterial, pBindProxy ); + shadow.m_Flags = creationFlags; + shadow.m_FirstDecal = m_ShadowSurfaces.InvalidIndex(); + shadow.m_FirstModel = m_ShadowsOnModels.InvalidIndex(); + shadow.m_ProjectionDir.Init( 0, 0, 1 ); + shadow.m_TexOrigin.Init( 0, 0 ); + shadow.m_TexSize.Init( 1, 1 ); + shadow.m_ClipPlaneCount = 0; + shadow.m_FalloffBias = 0; + shadow.m_pFlashlightDepthTexture = NULL; + shadow.m_FlashlightHandle = m_FlashlightStates.InvalidIndex(); + + if ( ( creationFlags & SHADOW_FLASHLIGHT ) != 0 ) + { + shadow.m_FlashlightHandle = m_FlashlightStates.AddToTail(); + m_FlashlightStates[shadow.m_FlashlightHandle].m_Shadow = h; + if ( !IsX360() && !r_flashlight_version2.GetInt() ) + { + AllocFlashlightMaterialBuckets( shadow.m_FlashlightHandle ); + } + } + + MatrixSetIdentity( shadow.m_WorldToShadow ); + return h; +#endif +} + +void CShadowMgr::DestroyShadow( ShadowHandle_t handle ) +{ + CleanupMaterial( m_Shadows[handle] ); + RemoveAllSurfacesFromShadow( handle ); + RemoveAllModelsFromShadow( handle ); + if( m_Shadows[handle].m_FlashlightHandle != m_FlashlightStates.InvalidIndex() ) + { + m_FlashlightStates.Remove( m_Shadows[handle].m_FlashlightHandle ); + } + + m_Shadows.Remove(handle); +} + + +//----------------------------------------------------------------------------- +// Resets the shadow material (useful for shadow LOD.. doing blobby at distance) +//----------------------------------------------------------------------------- +void CShadowMgr::SetShadowMaterial( ShadowHandle_t handle, IMaterial* pMaterial, IMaterial* pModelMaterial, void* pBindProxy ) +{ + Shadow_t& shadow = m_Shadows[handle]; + if ( (shadow.m_pMaterial != pMaterial) || (shadow.m_pModelMaterial != pModelMaterial) || (shadow.m_pBindProxy != pBindProxy) ) + { + CleanupMaterial( shadow ); + SetMaterial( shadow, pMaterial, pModelMaterial, pBindProxy ); + } +} + + +//----------------------------------------------------------------------------- +// Sets the texture coordinate range for a shadow... +//----------------------------------------------------------------------------- +void CShadowMgr::SetShadowTexCoord( ShadowHandle_t handle, float x, float y, float w, float h ) +{ + Shadow_t& shadow = m_Shadows[handle]; + shadow.m_TexOrigin.Init( x, y ); + shadow.m_TexSize.Init( w, h ); +} + + +//----------------------------------------------------------------------------- +// Set extra clip planes related to shadows... +//----------------------------------------------------------------------------- +void CShadowMgr::ClearExtraClipPlanes( ShadowHandle_t h ) +{ + m_Shadows[h].m_ClipPlaneCount = 0; +} + +void CShadowMgr::AddExtraClipPlane( ShadowHandle_t h, const Vector& normal, float dist ) +{ + Shadow_t& shadow = m_Shadows[h]; + Assert( shadow.m_ClipPlaneCount < MAX_CLIP_PLANE_COUNT ); + + VectorCopy( normal, shadow.m_ClipPlane[shadow.m_ClipPlaneCount] ); + shadow.m_ClipDist[shadow.m_ClipPlaneCount] = dist; + ++shadow.m_ClipPlaneCount; +} + + +//----------------------------------------------------------------------------- +// Gets at information about a particular shadow +//----------------------------------------------------------------------------- +const ShadowInfo_t& CShadowMgr::GetInfo( ShadowHandle_t handle ) +{ + return m_Shadows[handle]; +} + + +//----------------------------------------------------------------------------- +// Gets at cache entry... +//----------------------------------------------------------------------------- +ShadowVertex_t* CShadowMgr::GetCachedVerts( const ShadowVertexCache_t& cache ) +{ + if (cache.m_Count == 0) + return 0 ; + + if (cache.m_pVerts) + return cache.m_pVerts; + + if (cache.m_Count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) + return m_SmallVertexList[cache.m_CachedVerts].m_Verts; + + return m_LargeVertexList[cache.m_CachedVerts].m_Verts; +} + +//----------------------------------------------------------------------------- +// Allocates, cleans up vertex cache vertices +//----------------------------------------------------------------------------- +inline ShadowVertex_t* CShadowMgr::AllocateVertices( ShadowVertexCache_t& cache, int count ) +{ + cache.m_pVerts = 0; + if (count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) + { + cache.m_Count = count; + cache.m_CachedVerts = m_SmallVertexList.AddToTail( ); + return m_SmallVertexList[cache.m_CachedVerts].m_Verts; + } + else if (count <= SHADOW_VERTEX_LARGE_CACHE_COUNT) + { + cache.m_Count = count; + cache.m_CachedVerts = m_LargeVertexList.AddToTail( ); + return m_LargeVertexList[cache.m_CachedVerts].m_Verts; + } + + cache.m_Count = count; + if (count > 0) + { + cache.m_pVerts = new ShadowVertex_t[count]; + } + cache.m_CachedVerts = m_LargeVertexList.InvalidIndex(); + return cache.m_pVerts; +} + +inline void CShadowMgr::FreeVertices( ShadowVertexCache_t& cache ) +{ + if (cache.m_Count == 0) + return; + + if (cache.m_pVerts) + { + delete[] cache.m_pVerts; + } + else if (cache.m_Count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) + { + m_SmallVertexList.Remove( cache.m_CachedVerts ); + } + else + { + m_LargeVertexList.Remove( cache.m_CachedVerts ); + } +} + + +//----------------------------------------------------------------------------- +// Clears out vertices in the temporary cache +//----------------------------------------------------------------------------- +void CShadowMgr::ClearTempCache( ) +{ + // Clear out the vertices + for (int i = m_TempVertexCache.Count(); --i >= 0; ) + { + FreeVertices( m_TempVertexCache[i] ); + } + + m_TempVertexCache.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Adds the surface to the list for this shadow +//----------------------------------------------------------------------------- +bool CShadowMgr::AddDecalToShadowList( ShadowHandle_t handle, ShadowDecalHandle_t decalHandle ) +{ + // Add the shadow to the list of surfaces affected by this shadow + ShadowSurfaceIndex_t idx = m_ShadowSurfaces.Alloc( true ); + if ( idx == m_ShadowSurfaces.InvalidIndex() ) + { + ExecuteNTimes( 10, Warning( "CShadowMgr::AddDecalToShadowList - overflowed m_ShadowSurfaces linked list!\n" ) ); + return false; + } + + m_ShadowSurfaces[idx] = decalHandle; + if ( m_Shadows[handle].m_FirstDecal != m_ShadowSurfaces.InvalidIndex() ) + { + m_ShadowSurfaces.LinkBefore( m_Shadows[handle].m_FirstDecal, idx ); + } + m_Shadows[handle].m_FirstDecal = idx; + m_ShadowDecals[decalHandle].m_ShadowListIndex = idx; + + return true; +} + + +//----------------------------------------------------------------------------- +// Removes the shadow to the list of surfaces +//----------------------------------------------------------------------------- +void CShadowMgr::RemoveDecalFromShadowList( ShadowHandle_t handle, ShadowDecalHandle_t decalHandle ) +{ + ShadowSurfaceIndex_t idx = m_ShadowDecals[decalHandle].m_ShadowListIndex; + + // Make sure the list of shadow decals for a single shadow is ok + if ( m_Shadows[handle].m_FirstDecal == idx ) + { + m_Shadows[handle].m_FirstDecal = m_ShadowSurfaces.Next(idx); + } + + // Remove it from the shadow surfaces list + m_ShadowSurfaces.Free(idx); + + // Blat out the decal index + m_ShadowDecals[decalHandle].m_ShadowListIndex = m_ShadowSurfaces.InvalidIndex(); +} + + +//----------------------------------------------------------------------------- +// Computes spherical bounds for a surface +//----------------------------------------------------------------------------- +void CShadowMgr::ComputeSurfaceBounds( SurfaceBounds_t* pBounds, SurfaceHandle_t nSurfID ) +{ + pBounds->m_vecCenter.Init(); + pBounds->m_vecMins = ReplicateX4( FLT_MAX ); + pBounds->m_vecMaxs = ReplicateX4( -FLT_MAX ); + int nCount = MSurf_VertCount( nSurfID ); + for ( int i = 0; i < nCount; ++i ) + { + int nVertIndex = host_state.worldbrush->vertindices[ MSurf_FirstVertIndex( nSurfID ) + i ]; + const Vector &position = host_state.worldbrush->vertexes[ nVertIndex ].position; + pBounds->m_vecCenter += position; + + fltx4 pos4 = LoadUnaligned3SIMD( position.Base() ); + pBounds->m_vecMins = MinSIMD( pos4, pBounds->m_vecMins ); + pBounds->m_vecMaxs = MaxSIMD( pos4, pBounds->m_vecMaxs ); + } + + fltx4 eps = ReplicateX4( 1e-3 ); + pBounds->m_vecMins = SetWToZeroSIMD( SubSIMD( pBounds->m_vecMins, eps ) ); + pBounds->m_vecMaxs = SetWToZeroSIMD( AddSIMD( pBounds->m_vecMaxs, eps ) ); + pBounds->m_vecCenter /= nCount; + + pBounds->m_flRadius = 0.0f; + for ( int i = 0; i < nCount; ++i ) + { + int nVertIndex = host_state.worldbrush->vertindices[ MSurf_FirstVertIndex( nSurfID ) + i ]; + const Vector &position = host_state.worldbrush->vertexes[ nVertIndex ].position; + float flDistSq = position.DistToSqr( pBounds->m_vecCenter ); + if ( flDistSq > pBounds->m_flRadius ) + { + pBounds->m_flRadius = flDistSq; + } + } + pBounds->m_flRadius = sqrt( pBounds->m_flRadius ); +} + + +//----------------------------------------------------------------------------- +// Get spherical bounds for a surface +//----------------------------------------------------------------------------- +const CShadowMgr::SurfaceBounds_t* CShadowMgr::GetSurfaceBounds( SurfaceHandle_t surfID ) +{ + int nSurfaceIndex = MSurf_Index( surfID ); + + // NOTE: We're not bumping the surface index to the front of the LRU + // here, but I think if we did the cost doing that would exceed the cost + // of anything else in this path. + // If this turns out to not be true, then we should make this a true LRU + if ( m_pSurfaceBounds[nSurfaceIndex] != m_SurfaceBoundsCache.InvalidIndex() ) + return &m_SurfaceBoundsCache[ m_pSurfaceBounds[nSurfaceIndex] ]; + + SurfaceBoundsCacheIndex_t nIndex; + if ( m_SurfaceBoundsCache.Count() >= SURFACE_BOUNDS_CACHE_COUNT ) + { + // Retire existing cache entry if we're out of space, + // move it to the head of the LRU cache + nIndex = m_SurfaceBoundsCache.Tail( ); + m_SurfaceBoundsCache.Unlink( nIndex ); + m_SurfaceBoundsCache.LinkToHead( nIndex ); + m_pSurfaceBounds[ m_SurfaceBoundsCache[nIndex].m_nSurfaceIndex ] = m_SurfaceBoundsCache.InvalidIndex(); + } + else + { + // Allocate new cache entry if we have more room + nIndex = m_SurfaceBoundsCache.AddToHead( ); + } + m_pSurfaceBounds[ nSurfaceIndex ] = nIndex; + + // Computes the surface bounds + SurfaceBounds_t &bounds = m_SurfaceBoundsCache[nIndex]; + bounds.m_nSurfaceIndex = nSurfaceIndex; + ComputeSurfaceBounds( &bounds, surfID ); + return &bounds; +} + + +//----------------------------------------------------------------------------- +// Is the shadow near the surface? +//----------------------------------------------------------------------------- +bool CShadowMgr::IsShadowNearSurface( ShadowHandle_t h, SurfaceHandle_t nSurfID, + const VMatrix* pModelToWorld, const VMatrix* pWorldToModel ) +{ + const Shadow_t &shadow = m_Shadows[h]; + const SurfaceBounds_t* pBounds = GetSurfaceBounds( nSurfID ); + Vector vecSurfCenter; + if ( !pModelToWorld ) + { + vecSurfCenter = pBounds->m_vecCenter; + } + else + { + Vector3DMultiplyPosition( *pModelToWorld, pBounds->m_vecCenter, vecSurfCenter ); + } + + // Sphere check + Vector vecDelta; + VectorSubtract( shadow.m_vecSphereCenter, vecSurfCenter, vecDelta ); + float flDistSqr = vecDelta.LengthSqr(); + float flMinDistSqr = pBounds->m_flRadius + shadow.m_flSphereRadius; + flMinDistSqr *= flMinDistSqr; + if ( flDistSqr >= flMinDistSqr ) + return false; + + if ( !pModelToWorld ) + return IsBoxIntersectingRay( pBounds->m_vecMins, pBounds->m_vecMaxs, shadow.m_Ray ); + + Ray_t transformedRay; + Vector3DMultiplyPosition( *pWorldToModel, shadow.m_Ray.m_Start, transformedRay.m_Start ); + Vector3DMultiply( *pWorldToModel, shadow.m_Ray.m_Delta, transformedRay.m_Delta ); + transformedRay.m_StartOffset = shadow.m_Ray.m_StartOffset; + transformedRay.m_Extents = shadow.m_Ray.m_Extents; + transformedRay.m_IsRay = shadow.m_Ray.m_IsRay; + transformedRay.m_IsSwept = shadow.m_Ray.m_IsSwept; + return IsBoxIntersectingRay( pBounds->m_vecMins, pBounds->m_vecMaxs, transformedRay ); +} + + +//----------------------------------------------------------------------------- +// Adds the shadow decal reference to the surface +//----------------------------------------------------------------------------- +inline ShadowDecalHandle_t CShadowMgr::AddShadowDecalToSurface( SurfaceHandle_t surfID, ShadowHandle_t handle ) +{ + ShadowDecalHandle_t decalHandle = m_ShadowDecals.Alloc( true ); + if ( decalHandle == m_ShadowDecals.InvalidIndex() ) + { + ExecuteNTimes( 10, Warning( "CShadowMgr::AddShadowDecalToSurface - overflowed m_ShadowDecals linked list!\n" ) ); + return decalHandle; + } + + ShadowDecal_t& decal = m_ShadowDecals[decalHandle]; + + decal.m_SurfID = surfID; + m_ShadowDecals.LinkBefore( MSurf_ShadowDecals( surfID ), decalHandle ); + MSurf_ShadowDecals( surfID ) = decalHandle; + + // Hook the shadow into the displacement system.... + if ( !SurfaceHasDispInfo( surfID ) ) + { + decal.m_DispShadow = DISP_SHADOW_HANDLE_INVALID; + } + else + { + decal.m_DispShadow = MSurf_DispInfo( surfID )->AddShadowDecal( handle ); + } + + decal.m_Shadow = handle; + decal.m_ShadowVerts = m_VertexCache.InvalidIndex(); + decal.m_NextRender = SHADOW_DECAL_HANDLE_INVALID; + decal.m_ShadowListIndex = m_ShadowSurfaces.InvalidIndex(); + + //============================================================================= + // HPE_BEGIN: + // [smessick] Check the return value of AddDecalToShadowList and make sure + // to delete the newly created shadow decal if there is a failure. + //============================================================================= + if ( !AddDecalToShadowList( handle, decalHandle ) ) + { + m_ShadowDecals.Free( decalHandle ); + decalHandle = m_ShadowDecals.InvalidIndex(); + } + //============================================================================= + // HPE_END + //============================================================================= + + return decalHandle; +} + +inline void CShadowMgr::RemoveShadowDecalFromSurface( SurfaceHandle_t surfID, ShadowDecalHandle_t decalHandle ) +{ + // Clean up its shadow verts if it has any + ShadowDecal_t& decal = m_ShadowDecals[decalHandle]; + if (decal.m_ShadowVerts != m_VertexCache.InvalidIndex()) + { + FreeVertices( m_VertexCache[decal.m_ShadowVerts] ); + m_VertexCache.Remove(decal.m_ShadowVerts); + decal.m_ShadowVerts = m_VertexCache.InvalidIndex(); + } + + // Clean up displacement... + if ( decal.m_DispShadow != DISP_SHADOW_HANDLE_INVALID ) + { + MSurf_DispInfo( decal.m_SurfID )->RemoveShadowDecal( decal.m_DispShadow ); + } + + // Make sure the list of shadow decals on a surface is set up correctly + if ( MSurf_ShadowDecals( surfID ) == decalHandle ) + { + MSurf_ShadowDecals( surfID ) = m_ShadowDecals.Next(decalHandle); + } + + RemoveDecalFromShadowList( decal.m_Shadow, decalHandle ); + + // Kill the shadow decal + m_ShadowDecals.Free( decalHandle ); +} + +void CShadowMgr::AddSurfaceToFlashlightMaterialBuckets( ShadowHandle_t handle, SurfaceHandle_t surfID ) +{ + // Make sure that this is a flashlight. + Assert( m_Shadows[handle].m_Flags & SHADOW_FLASHLIGHT ); + + // Get the flashlight id for this particular shadow handle and make sure that it's valid. + FlashlightHandle_t flashlightID = m_Shadows[handle].m_FlashlightHandle; + Assert( flashlightID != m_FlashlightStates.InvalidIndex() ); + + m_FlashlightStates[flashlightID].m_MaterialBuckets.AddElement( MSurf_MaterialSortID( surfID ), surfID ); +} + + +//----------------------------------------------------------------------------- +// Adds the shadow decal reference to the surface +// This causes a shadow decal to be made +//----------------------------------------------------------------------------- +void CShadowMgr::AddSurfaceToShadow( ShadowHandle_t handle, SurfaceHandle_t surfID ) +{ + // FIXME: We could make this work, but there's a perf cost... + // Basically, we'd need to have a separate rendering batch for + // each translucent material the shadow is projected onto. The + // material alpha would have to be taken into account, so that + // no multiplication occurs where the alpha == 0 + // FLASHLIGHTFIXME: get rid of some of these checks for the ones that will work just fine with the flashlight. + bool bIsFlashlight = ( ( m_Shadows[handle].m_Flags & SHADOW_FLASHLIGHT ) != 0 ); + if ( !bIsFlashlight && MSurf_Flags(surfID) & (SURFDRAW_TRANS | SURFDRAW_ALPHATEST | SURFDRAW_NOSHADOWS) ) + return; + +#ifdef _XBOX + // Don't let the flashlight get on water on XBox + if ( bIsFlashlight && ( MSurf_Flags(surfID) & SURFDRAW_WATERSURFACE ) ) + return; +#endif + +#if 0 + // Make sure the surface has the shadow on it exactly once... + ShadowDecalHandle_t dh = MSurf_ShadowDecals( surfID ); + while (dh != m_ShadowDecals.InvalidIndex() ) + { + Assert ( m_ShadowDecals[dh].m_Shadow != handle ); + dh = m_ShadowDecals.Next(dh); + } +#endif + + // Create a shadow decal for this surface and add it to the surface + AddShadowDecalToSurface( surfID, handle ); +} + +void CShadowMgr::RemoveSurfaceFromShadow( ShadowHandle_t handle, SurfaceHandle_t surfID ) +{ + // Find the decal associated with the handle that lies on the surface + + // FIXME: Linear search; bleah. + // Luckily the search is probably over only a couple items at most + // Linear searching over the shadow surfaces so we can remove the entry + // in the shadow surface list if we find a match + ASSERT_SURF_VALID( surfID ); + ShadowSurfaceIndex_t i = m_Shadows[handle].m_FirstDecal; + while ( i != m_ShadowSurfaces.InvalidIndex() ) + { + ShadowDecalHandle_t decalHandle = m_ShadowSurfaces[i]; + if ( m_ShadowDecals[decalHandle].m_SurfID == surfID ) + { + // Found a match! There should be at most one shadow decal + // associated with a particular shadow per surface + RemoveShadowDecalFromSurface( surfID, decalHandle ); + + // FIXME: Could check the shadow doesn't appear again in the list + return; + } + + i = m_ShadowSurfaces.Next(i); + } + +#ifdef _DEBUG + // Here, the shadow didn't have the surface in its list + // let's make sure the surface doesn't think it's got the shadow in its list + ShadowDecalHandle_t dh = MSurf_ShadowDecals( surfID ); + while (dh != m_ShadowDecals.InvalidIndex() ) + { + Assert ( m_ShadowDecals[dh].m_Shadow != handle ); + dh = m_ShadowDecals.Next(dh); + } + +#endif +} + +void CShadowMgr::RemoveAllSurfacesFromShadow( ShadowHandle_t handle ) +{ + // Iterate over all the decals associated with a particular shadow + // Remove the decals from the surfaces they are associated with + ShadowSurfaceIndex_t i = m_Shadows[handle].m_FirstDecal; + ShadowSurfaceIndex_t next; + while ( i != m_ShadowSurfaces.InvalidIndex() ) + { + ShadowDecalHandle_t decalHandle = m_ShadowSurfaces[i]; + + next = m_ShadowSurfaces.Next(i); + + RemoveShadowDecalFromSurface( m_ShadowDecals[decalHandle].m_SurfID, decalHandle ); + + i = next; + } + + m_Shadows[handle].m_FirstDecal = m_ShadowSurfaces.InvalidIndex(); +} + +void CShadowMgr::RemoveAllShadowsFromSurface( SurfaceHandle_t surfID ) +{ + // Iterate over all the decals associated with a particular shadow + // Remove the decals from the surfaces they are associated with + ShadowDecalHandle_t dh = MSurf_ShadowDecals( surfID ); + while (dh != m_ShadowDecals.InvalidIndex() ) + { + // Remove this shadow from the surface + ShadowDecalHandle_t next = m_ShadowDecals.Next(dh); + + // Remove the surface from the shadow + RemoveShadowDecalFromSurface( m_ShadowDecals[dh].m_SurfID, dh ); + + dh = next; + } + + MSurf_ShadowDecals( surfID ) = m_ShadowDecals.InvalidIndex(); +} + + +//----------------------------------------------------------------------------- +// Shadow/model association +//----------------------------------------------------------------------------- +void CShadowMgr::AddShadowToModel( ShadowHandle_t handle, ModelInstanceHandle_t model ) +{ + // FIXME: Add culling here based on the model bbox + // and the shadow bbox + // FIXME: + /* + // Trivial bbox reject. + Vector bbMin, bbMax; + pDisp->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 ) + */ + + if ( model == MODEL_INSTANCE_INVALID ) + { + // async data not loaded yet + return; + } + + if( r_flashlightrender.GetBool()==false ) + return; + + m_ShadowsOnModels.AddElementToBucket( model, handle ); + + +} + +void CShadowMgr::RemoveAllShadowsFromModel( ModelInstanceHandle_t model ) +{ + if( model != MODEL_INSTANCE_INVALID ) + { + m_ShadowsOnModels.RemoveBucket( model ); + + FOR_EACH_LL( m_FlashlightStates, i ) + { + FlashlightInfo_t &info = m_FlashlightStates[i]; + + for( int j=0;j<info.m_Renderables.Count();j++ ) + { + if( info.m_Renderables[j]->GetModelInstance() == model ) + { + info.m_Renderables.Remove( j ); + break; + } + } + } + } +} + +void CShadowMgr::RemoveAllModelsFromShadow( ShadowHandle_t handle ) +{ + m_ShadowsOnModels.RemoveElement( handle ); + + FOR_EACH_LL( m_FlashlightStates, i ) + { + FlashlightInfo_t &info = m_FlashlightStates[i]; + + if( info.m_Shadow==handle ) + { + info.m_Renderables.RemoveAll(); + } + } +} + + +//----------------------------------------------------------------------------- +// Shadow state... +//----------------------------------------------------------------------------- +void CShadowMgr::SetModelShadowState( ModelInstanceHandle_t instance ) +{ +#ifndef SWDS + g_pStudioRender->ClearAllShadows(); + if (instance != MODEL_INSTANCE_INVALID && r_shadows.GetInt() ) + { + bool bWireframe = r_shadowwireframe.GetBool(); + unsigned short i = m_ShadowsOnModels.FirstElement( instance ); + while ( i != m_ShadowsOnModels.InvalidIndex() ) + { + Shadow_t& shadow = m_Shadows[m_ShadowsOnModels.Element(i)]; + + if( !bWireframe ) + { + if( shadow.m_Flags & SHADOW_FLASHLIGHT ) + { + // NULL means that the models material should be used. + // This is what we want in the case of the flashlight + // since we need to render the models material again with different lighting. + // Need to add something here to specify which flashlight. + g_pStudioRender->AddShadow( NULL, NULL, &m_FlashlightStates[shadow.m_FlashlightHandle].m_FlashlightState, &shadow.m_WorldToShadow, shadow.m_pFlashlightDepthTexture ); + } + else if( r_shadows_gamecontrol.GetInt() != 0 ) + { + g_pStudioRender->AddShadow( shadow.m_pModelMaterial, shadow.m_pBindProxy ); + } + } + else if( ( shadow.m_Flags & SHADOW_FLASHLIGHT ) || r_shadows_gamecontrol.GetInt() != 0 ) + { + g_pStudioRender->AddShadow( g_pMaterialMRMWireframe, NULL ); + } + + i = m_ShadowsOnModels.NextElement(i); + } + } +#endif +} + +bool CShadowMgr::ModelHasShadows( ModelInstanceHandle_t instance ) +{ + if ( instance != MODEL_INSTANCE_INVALID ) + { + if ( m_ShadowsOnModels.FirstElement(instance) != m_ShadowsOnModels.InvalidIndex() ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Applies the shadow to a surface +//----------------------------------------------------------------------------- +void CShadowMgr::ApplyShadowToSurface( ShadowBuildInfo_t& build, SurfaceHandle_t surfID ) +{ + // We've found a potential surface to add to the shadow + // At this point, we want to do fast culling to see whether we actually + // should apply the shadow or not before actually adding it to any lists + + // FIXME: implement + // Put the texture extents into shadow space; see if there's an intersection + // If not, we can early out + + + // To do this, we're gonna want to project the surface into the space of the decal + // Therefore, we want to produce a surface->world transformation, and a + // world->shadow/light space transformation + // Then we transform the surface points into shadow space and apply the projection + // in shadow space. + + /* + // Get the texture associated with this surface + mtexinfo_t* tex = pSurface->texinfo; + + 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 - surf->textureMins[0]; + float t = DotProduct( decalinfo->m_Position, textureV.AsVector3D() ) + + textureV.w - surf->textureMins[1]; + */ + + // Don't do any more computation at the moment, only do it if + // we end up rendering the surface later on + AddSurfaceToShadow( build.m_Shadow, surfID ); +} + + +//----------------------------------------------------------------------------- +// Applies the shadow to a displacement +//----------------------------------------------------------------------------- +void CShadowMgr::ApplyShadowToDisplacement( ShadowBuildInfo_t& build, IDispInfo *pDispInfo, bool bIsFlashlight ) +{ + // Avoid noshadow displacements + if ( !bIsFlashlight && ( MSurf_Flags( pDispInfo->GetParent() ) & SURFDRAW_NOSHADOWS ) ) + return; + + // Trivial bbox reject. + Vector bbMin, bbMax; + pDispInfo->GetBoundingBox( bbMin, bbMax ); + if ( !bIsFlashlight ) + { + if ( !IsBoxIntersectingSphere( bbMin, bbMax, build.m_vecSphereCenter, build.m_flSphereRadius ) ) + return; + } + else + { + if( R_CullBox( bbMin, bbMax, GetFlashlightFrustum( build.m_Shadow ) ) ) + return; + } + + SurfaceHandle_t surfID = pDispInfo->GetParent(); + + if ( surfID->m_bDynamicShadowsEnabled == false && !bIsFlashlight ) + return; + + AddSurfaceToShadow( build.m_Shadow, surfID ); +} + + +//----------------------------------------------------------------------------- +// Allows us to disable particular shadows +//----------------------------------------------------------------------------- +void CShadowMgr::EnableShadow( ShadowHandle_t handle, bool bEnable ) +{ + if (!bEnable) + { + // We need to remove the shadow from all surfaces it may currently be in + RemoveAllSurfacesFromShadow( handle ); + RemoveAllModelsFromShadow( handle ); + + m_Shadows[handle].m_Flags |= SHADOW_DISABLED; + } + else + { + // FIXME: Could make this recompute the cache... + m_Shadows[handle].m_Flags &= ~SHADOW_DISABLED; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the darkness falloff bias +// Input : shadow - +// ucBias - +//----------------------------------------------------------------------------- +void CShadowMgr::SetFalloffBias( ShadowHandle_t shadow, unsigned char ucBias ) +{ + m_Shadows[shadow].m_FalloffBias = ucBias; +} + +//----------------------------------------------------------------------------- +// 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() +//----------------------------------------------------------------------------- +void CShadowMgr::ProjectShadow( ShadowHandle_t handle, const Vector &origin, + const Vector& projectionDir, const VMatrix& worldToShadow, const Vector2D& size, + int nLeafCount, const int *pLeafList, + float maxHeight, float falloffOffset, float falloffAmount, const Vector &vecCasterOrigin ) +{ + VPROF_BUDGET( "CShadowMgr::ProjectShadow", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + // First, we need to remove the shadow from all surfaces it may + // currently be in; in other words we're invalidating the shadow surface cache + RemoveAllSurfacesFromShadow( handle ); + RemoveAllModelsFromShadow( handle ); + + // Don't bother with this shadow if it's disabled + Shadow_t &shadow = m_Shadows[handle]; + if ( shadow.m_Flags & SHADOW_DISABLED ) + return; + + // Don't compute the surface cache if shadows are off.. + if ( !r_shadows.GetInt() ) + return; + + // Set the falloff coefficient + shadow.m_FalloffOffset = falloffOffset; + VectorCopy( projectionDir, shadow.m_ProjectionDir ); + + // We need to know about surfaces in leaves hit by the ray... + // We'd like to stop iterating as soon as the entire swept volume + // enters a solid leaf; that may be hard to determine. Instead, + // we should stop iterating when the ray center enters a solid leaf? + AssertFloatEquals( projectionDir.LengthSqr(), 1.0f, 1e-3 ); + + // The maximum ray distance is equal to the distance it takes the + // falloff to get to 15%. + shadow.m_MaxDist = maxHeight; //sqrt( coeff / 0.10f ) + falloffOffset; + shadow.m_FalloffAmount = falloffAmount; + MatrixCopy( worldToShadow, shadow.m_WorldToShadow ); + + // Compute a rough bounding sphere for the ray + float flRadius = sqrt( size.x * size.x + size.y * size.y ) * 0.5f; + VectorMA( origin, 0.5f * maxHeight, projectionDir, shadow.m_vecSphereCenter ); + shadow.m_flSphereRadius = 0.5f * maxHeight + flRadius; + + Vector vecEndPoint; + Vector vecMins( -flRadius, -flRadius, -flRadius ); + Vector vecMaxs( flRadius, flRadius, flRadius ); + VectorMA( origin, maxHeight, projectionDir, vecEndPoint ); + shadow.m_Ray.Init( origin, vecEndPoint, vecMins, vecMaxs ); + + // No more work necessary if it hits no leaves + if ( nLeafCount == 0 ) + return; + + // We're hijacking the surface vis frame to make sure we enumerate + // surfaces only once; + ++r_surfacevisframe; + + // Clear out the displacement tags also + DispInfo_ClearAllTags( host_state.worldbrush->hDispInfos ); + + ShadowBuildInfo_t build; + build.m_Shadow = handle; + build.m_RayStart = origin; + build.m_pVis = NULL; + build.m_vecSphereCenter = shadow.m_vecSphereCenter; + build.m_flSphereRadius = shadow.m_flSphereRadius; + VectorCopy( projectionDir, build.m_ProjectionDirection ); + + // Enumerate leaves + for ( int i = 0; i < nLeafCount; ++i ) + { + // NOTE: Scope specifier eliminates virtual function call + CShadowMgr::EnumerateLeaf( pLeafList[i], (int)&build ); + } +} + +void DrawFrustum( Frustum_t &frustum ) +{ + const int maxPoints = 8; + int i; + for( i = 0; i < FRUSTUM_NUMPLANES; i++ ) + { + Vector points[maxPoints]; + Vector points2[maxPoints]; + int numPoints = PolyFromPlane( points, frustum.GetPlane( i )->normal, frustum.GetPlane( i )->dist ); + Assert( numPoints <= maxPoints ); + Vector *in, *out; + in = points; + out = points2; + int j; + for( j = 0; j < FRUSTUM_NUMPLANES; j++ ) + { + if( i == j ) + { + continue; + } + numPoints = ClipPolyToPlane( in, numPoints, out, frustum.GetPlane( j )->normal, frustum.GetPlane( j )->dist ); + Assert( numPoints <= maxPoints ); + V_swap( in, out ); + } + int c; + for( c = 0; c < numPoints; c++ ) + { + CDebugOverlay::AddLineOverlay( in[c], in[(c+1)%numPoints], 0, 255, 0, 255, true, 0.0f ); + } + } +} + +//static void LineDrawHelper( const Vector &startShadowSpace, const Vector &endShadowSpace, +// const VMatrix &shadowToWorld, unsigned char r, unsigned char g, +// unsigned char b, bool ignoreZ ) +//{ +// Vector startWorldSpace, endWorldSpace; +// Vector3DMultiplyPositionProjective( shadowToWorld, startShadowSpace, startWorldSpace ); +// Vector3DMultiplyPositionProjective( shadowToWorld, endShadowSpace, endWorldSpace ); +// +// CDebugOverlay::AddLineOverlay( startWorldSpace, +// endWorldSpace, +// r, g, b, ignoreZ +// , 0.0 ); +//} + +void CShadowMgr::ProjectFlashlight( ShadowHandle_t handle, const VMatrix& worldToShadow, int nLeafCount, const int *pLeafList ) +{ + VPROF_BUDGET( "CShadowMgr::ProjectFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + Shadow_t& shadow = m_Shadows[handle]; + + if ( !IsX360() && !r_flashlight_version2.GetInt() ) + { + // First, we need to remove the shadow from all surfaces it may + // currently be in; in other words we're invalidating the shadow surface cache + RemoveAllSurfacesFromShadow( handle ); + RemoveAllModelsFromShadow( handle ); + + m_FlashlightStates[ shadow.m_FlashlightHandle ].m_OccluderBuckets.Flush(); + } + + // Don't bother with this shadow if it's disabled + if ( m_Shadows[handle].m_Flags & SHADOW_DISABLED ) + return; + + // Don't compute the surface cache if shadows are off.. + if ( !r_shadows.GetInt() ) + return; + + MatrixCopy( worldToShadow, shadow.m_WorldToShadow ); + + // We need this for our various bounding computations + VMatrix shadowToWorld; + MatrixInverseGeneral( shadow.m_WorldToShadow, shadowToWorld ); + + // Set up the frustum for the flashlight so that we can cull each leaf against it. + Assert( shadow.m_Flags & SHADOW_FLASHLIGHT ); + Frustum_t &frustum = m_FlashlightStates[shadow.m_FlashlightHandle].m_Frustum; + FrustumPlanesFromMatrix( shadowToWorld, frustum ); + CalculateSphereFromProjectionMatrixInverse( shadowToWorld, &shadow.m_vecSphereCenter, &shadow.m_flSphereRadius ); + + if ( nLeafCount == 0 ) + return; + + // We're hijacking the surface vis frame to make sure we enumerate + // surfaces only once; + ++r_surfacevisframe; + + // Clear out the displacement tags also + DispInfo_ClearAllTags( host_state.worldbrush->hDispInfos ); + + ShadowBuildInfo_t build; + build.m_Shadow = handle; + build.m_RayStart = m_FlashlightStates[shadow.m_FlashlightHandle].m_FlashlightState.m_vecLightOrigin; + build.m_pVis = NULL; + build.m_vecSphereCenter = shadow.m_vecSphereCenter; + build.m_flSphereRadius = shadow.m_flSphereRadius; + + if( r_flashlightdrawfrustumbbox.GetBool() ) + { + Vector mins, maxs; + CalculateAABBFromProjectionMatrixInverse( shadowToWorld, &mins, &maxs ); + CDebugOverlay::AddBoxOverlay( Vector( 0.0f, 0.0f, 0.0f ), mins, maxs, QAngle( 0, 0, 0 ), + 0, 0, 255, 100, 0.0f ); + } + + for ( int i = 0; i < nLeafCount; ++i ) + { + // NOTE: Scope specifier eliminates virtual function call + CShadowMgr::EnumerateLeaf( pLeafList[i], (int)&build ); + } +} + + +//----------------------------------------------------------------------------- +// Applies the flashlight to all surfaces in the leaf +//----------------------------------------------------------------------------- +void CShadowMgr::ApplyFlashlightToLeaf( const Shadow_t &shadow, mleaf_t* pLeaf, ShadowBuildInfo_t* pBuild ) +{ + // Get the bounds of the leaf so that we can test it against the flashlight frustum. + Vector leafMins, leafMaxs; + VectorAdd( pLeaf->m_vecCenter, pLeaf->m_vecHalfDiagonal, leafMaxs ); + VectorSubtract( pLeaf->m_vecCenter, pLeaf->m_vecHalfDiagonal, leafMins ); + + // The flashlight frustum didn't intersect the bounding box for this leaf! Get outta here! + if( R_CullBox( leafMins, leafMaxs, GetFlashlightFrustum( pBuild->m_Shadow ) ) ) + return; + + // Iterate over all surfaces in the leaf, check for backfacing + // and apply the shadow to the surface if it's not backfaced. + // Note that this really only indicates that the shadow may potentially + // sit on the surface; when we render, we'll actually do the clipping + // computation and at that point we'll remove surfaces that don't + // actually hit the surface + + bool bCullDepth = r_flashlightculldepth.GetBool(); + + SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; + for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) + { + SurfaceHandle_t surfID = pHandle[i]; + + // only process each surface once; + if( MSurf_VisFrame( surfID ) == r_surfacevisframe ) + continue; + + MSurf_VisFrame( surfID ) = r_surfacevisframe; + Assert( !MSurf_DispInfo( surfID ) ); + + // perspective projection + + // world-space vertex + int vertIndex = host_state.worldbrush->vertindices[MSurf_FirstVertIndex( surfID )]; + Vector& worldPos = host_state.worldbrush->vertexes[vertIndex].position; + + // Get the lookdir + Vector lookdir; + VectorSubtract( worldPos, pBuild->m_RayStart, lookdir ); + VectorNormalize( lookdir ); + + const cplane_t &surfPlane = MSurf_Plane( surfID ); + + // Now apply the spherical cull + float flDist = DotProduct( surfPlane.normal, pBuild->m_vecSphereCenter ) - surfPlane.dist; + if ( fabs(flDist) >= pBuild->m_flSphereRadius ) + continue; + + ApplyShadowToSurface( *pBuild, surfID ); + + // Backface cull + + if( bCullDepth ) + { + if ( (MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0 ) + { + if ( DotProduct(surfPlane.normal, lookdir) < BACKFACE_EPSILON ) + continue; + } + else + { + // Avoid edge-on shadows regardless. + float dot = DotProduct(surfPlane.normal, lookdir); + if (fabs(dot) < BACKFACE_EPSILON) + continue; + } + } + + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[ shadow.m_FlashlightHandle ]; + flashlightInfo.m_OccluderBuckets.AddElement( MSurf_MaterialSortID( surfID ), surfID ); + } +} + + +//----------------------------------------------------------------------------- +// Applies a shadow to all surfaces in the leaf +//----------------------------------------------------------------------------- +void CShadowMgr::ApplyShadowToLeaf( const Shadow_t &shadow, mleaf_t* RESTRICT pLeaf, ShadowBuildInfo_t* RESTRICT pBuild ) +{ + // Iterate over all surfaces in the leaf, check for backfacing + // and apply the shadow to the surface if it's not backfaced. + // Note that this really only indicates that the shadow may potentially + // sit on the surface; when we render, we'll actually do the clipping + // computation and at that point we'll remove surfaces that don't + // actually hit the surface + SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; + for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) + { + SurfaceHandleRestrict_t surfID = pHandle[i]; + + // only process each surface once; + if( MSurf_VisFrame( surfID ) == r_surfacevisframe ) + continue; + + MSurf_VisFrame( surfID ) = r_surfacevisframe; + Assert( !MSurf_DispInfo( surfID ) ); + + // If this surface has specifically had dynamic shadows disabled on it, then get out! + if ( !MSurf_AreDynamicShadowsEnabled( surfID ) ) + continue; + + // Backface cull + const cplane_t * RESTRICT pSurfPlane = &MSurf_Plane( surfID ); + bool bInFront; + if ( (MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0 ) + { + if ( DotProduct( pSurfPlane->normal, pBuild->m_ProjectionDirection) > -BACKFACE_EPSILON ) + continue; + + bInFront = true; + } + else + { + // Avoid edge-on shadows regardless. + float dot = DotProduct( pSurfPlane->normal, pBuild->m_ProjectionDirection ); + if (fabs(dot) < BACKFACE_EPSILON) + continue; + + bInFront = (dot < 0); + } + + // Here, it's front facing... + // Discard stuff on the wrong side of the ray start + if (bInFront) + { + if ( DotProduct( pSurfPlane->normal, pBuild->m_RayStart) < pSurfPlane->dist ) + continue; + } + else + { + if ( DotProduct( pSurfPlane->normal, pBuild->m_RayStart) > pSurfPlane->dist ) + continue; + } + + // Now apply the spherical cull + float flDist = DotProduct( pSurfPlane->normal, pBuild->m_vecSphereCenter ) - pSurfPlane->dist; + if ( fabs(flDist) >= pBuild->m_flSphereRadius ) + continue; + + ApplyShadowToSurface( *pBuild, surfID ); + } +} + + +#define BIT_SET( a, b ) ((a)[(b)>>3] & (1<<((b)&7))) + +//----------------------------------------------------------------------------- +// Applies a projected texture to all surfaces in the leaf +//----------------------------------------------------------------------------- +bool CShadowMgr::EnumerateLeaf( int leaf, int context ) +{ + VPROF( "CShadowMgr::EnumerateLeaf" ); + ShadowBuildInfo_t* pBuild = (ShadowBuildInfo_t*)context; + + // Skip this leaf if it's not visible from the shadow caster + if ( pBuild->m_pVis ) + { + int cluster = CM_LeafCluster( leaf ); + if ( !BIT_SET( pBuild->m_pVis, cluster ) ) + return true; + } + + const Shadow_t &shadow = m_Shadows[pBuild->m_Shadow]; + + mleaf_t* pLeaf = &host_state.worldbrush->leafs[leaf]; + + bool bIsFlashlight; + if( shadow.m_Flags & SHADOW_FLASHLIGHT ) + { + bIsFlashlight = true; + ApplyFlashlightToLeaf( shadow, pLeaf, pBuild ); + } + else + { + bIsFlashlight = false; + ApplyShadowToLeaf( shadow, pLeaf, pBuild ); + } + + // 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 ); + + // Make sure the decal hasn't already been added to it. + if( pDispInfo->GetTag() ) + continue; + + pDispInfo->SetTag(); + + ApplyShadowToDisplacement( *pBuild, pDispInfo, bIsFlashlight ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to a brush model +//----------------------------------------------------------------------------- +void CShadowMgr::AddShadowToBrushModel( ShadowHandle_t handle, model_t* pModel, + const Vector& origin, const QAngle& angles ) +{ + // Don't compute the surface cache if shadows are off.. + if ( !r_shadows.GetInt() ) + return; + + const Shadow_t * RESTRICT pShadow = &m_Shadows[handle]; + + // Transform the shadow ray direction into model space + Vector shadowDirInModelSpace; + bool bIsFlashlight = ( pShadow->m_Flags & SHADOW_FLASHLIGHT ) != 0; + if( !bIsFlashlight ) + { + // FLASHLIGHTFIXME: should do backface culling for projective light sources. + matrix3x4_t worldToModel; + AngleIMatrix( angles, worldToModel ); + VectorRotate( pShadow->m_ProjectionDir, worldToModel, shadowDirInModelSpace ); + } + + // Just add all non-backfacing brush surfaces to the list of potential + // surfaces that we may be casting a shadow onto. + SurfaceHandleRestrict_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared ); + for (int i=0; i<pModel->brush.nummodelsurfaces; ++i, ++surfID) + { + // Don't bother with nodraw surfaces + int nFlags = MSurf_Flags( surfID ); + if ( nFlags & SURFDRAW_NODRAW ) + continue; + + if( !bIsFlashlight ) + { + // FLASHLIGHTFIXME: should do backface culling for projective light sources. + // Don't bother with backfacing surfaces + if ( (nFlags & SURFDRAW_NOCULL) == 0 ) + { + const cplane_t * RESTRICT pSurfPlane = &MSurf_Plane( surfID ); + float dot = DotProduct( shadowDirInModelSpace, pSurfPlane->normal ); + if ( dot > 0 ) + continue; + } + } + + // FIXME: We may want to do some more high-level per-surface culling + // If so, it'll be added to ApplyShadowToSurface. Call it instead. + AddSurfaceToShadow( handle, surfID ); + } +} + + +//----------------------------------------------------------------------------- +// Removes all shadows from a brush model +//----------------------------------------------------------------------------- +void CShadowMgr::RemoveAllShadowsFromBrushModel( model_t* pModel ) +{ + SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared ); + for (int i=0; i<pModel->brush.nummodelsurfaces; ++i, ++surfID) + { + RemoveAllShadowsFromSurface( surfID ); + } +} + + +//----------------------------------------------------------------------------- +// Adds the shadow decals on the surface to a queue of things to render +//----------------------------------------------------------------------------- +void CShadowMgr::AddShadowsOnSurfaceToRenderList( ShadowDecalHandle_t decalHandle ) +{ + // Don't compute the surface cache if shadows are off.. + if (!r_shadows.GetInt() ) + return; + + // Add all surface decals into the appropriate render lists + while( decalHandle != m_ShadowDecals.InvalidIndex() ) + { + ShadowDecal_t& shadowDecal = m_ShadowDecals[decalHandle]; + if( m_Shadows[shadowDecal.m_Shadow].m_Flags & SHADOW_FLASHLIGHT ) + { + AddSurfaceToFlashlightMaterialBuckets( shadowDecal.m_Shadow, shadowDecal.m_SurfID ); + + // We've got one more decal to render + ++m_DecalsToRender; + } + else if( r_shadows_gamecontrol.GetInt() != 0 ) + { + // For shadow rendering, hook the decal into the render list based on the shadow material, not the surface material. + int sortOrder = m_Shadows[shadowDecal.m_Shadow].m_SortOrder; + m_ShadowDecals[decalHandle].m_NextRender = m_RenderQueue[sortOrder]; + m_RenderQueue[sortOrder] = decalHandle; + + // We've got one more decal to render + ++m_DecalsToRender; + } + decalHandle = m_ShadowDecals.Next(decalHandle); + } +} + +void CShadowMgr::ClearShadowRenderList() +{ + COMPILE_TIME_ASSERT( sizeof(ShadowDecalHandle_t) == 2 ); + + // Clear out the render list + if (m_RenderQueue.Count() > 0) + { + memset( m_RenderQueue.Base(), 0xFF, m_RenderQueue.Count() * sizeof(ShadowDecalHandle_t) ); + } + m_DecalsToRender = 0; + // Clear all lists pertaining to flashlight decals that need to be rendered. + ClearAllFlashlightMaterialBuckets(); +} + +void CShadowMgr::RenderShadows( const VMatrix* pModelToWorld ) +{ + VPROF_BUDGET( "CShadowMgr::RenderShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + // Iterate through all sort ids and render for regular shadows, which get their materials from the shadow material. + CMatRenderContextPtr pRenderContext( materials ); + int i; + for( i = 0; i < m_RenderQueue.Count(); ++i ) + { + if (m_RenderQueue[i] != m_ShadowDecals.InvalidIndex()) + { + RenderShadowList(pRenderContext, m_RenderQueue[i], pModelToWorld ); + } + } +} + +void CShadowMgr::RenderProjectedTextures( const VMatrix* pModelToWorld ) +{ + VPROF_BUDGET( "CShadowMgr::RenderProjectedTextures", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + RenderFlashlights( true, pModelToWorld ); + RenderShadows( pModelToWorld ); + + // Clear out the render list, we've rendered it now + ClearShadowRenderList(); +} + + +//----------------------------------------------------------------------------- +// A 2D sutherland-hodgman clipper +//----------------------------------------------------------------------------- +class CClipTop +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) { return vert.m_ShadowSpaceTexCoord.y < 1;} + static inline float Clip( const Vector& one, const Vector& two ) { return (1 - one.y) / (two.y - one.y);} + static inline bool IsPlane() {return false;} + static inline bool IsAbove() {return false;} +}; + +class CClipLeft +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) { return vert.m_ShadowSpaceTexCoord.x > 0;} + static inline float Clip( const Vector& one, const Vector& two ) { return one.x / (one.x - two.x);} + static inline bool IsPlane() {return false;} + static inline bool IsAbove() {return false;} +}; + +class CClipRight +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) {return vert.m_ShadowSpaceTexCoord.x < 1;} + static inline float Clip( const Vector& one, const Vector& two ) {return (1 - one.x) / (two.x - one.x);} + static inline bool IsPlane() {return false;} + static inline bool IsAbove() {return false;} +}; + +class CClipBottom +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) {return vert.m_ShadowSpaceTexCoord.y > 0;} + static inline float Clip( const Vector& one, const Vector& two ) {return one.y / (one.y - two.y);} + static inline bool IsPlane() {return false;} + static inline bool IsAbove() {return false;} +}; + +class CClipAbove +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) {return vert.m_ShadowSpaceTexCoord.z > 0;} + static inline float Clip( const Vector& one, const Vector& two ) {return one.z / (one.z - two.z);} + static inline bool IsPlane() {return false;} + static inline bool IsAbove() {return true;} +}; + +class CClipPlane +{ +public: + static inline bool Inside( ShadowVertex_t const& vert ) + { + return DotProduct( vert.m_Position, *m_pNormal ) < m_Dist; + } + + static inline float Clip( const Vector& one, const Vector& two ) + { + Vector dir; + VectorSubtract( two, one, dir ); + return IntersectRayWithPlane( one, dir, *m_pNormal, m_Dist ); + } + + static inline bool IsAbove() {return false;} + static inline bool IsPlane() {return true;} + + static void SetPlane( const Vector& normal, float dist ) + { + m_pNormal = &normal; + m_Dist = dist; + } + + +private: + static const Vector *m_pNormal; + static float m_Dist; +}; + +const Vector *CClipPlane::m_pNormal; +float CClipPlane::m_Dist; + +static inline void ClampTexCoord( ShadowVertex_t *pInVertex, ShadowVertex_t *pOutVertex ) +{ + if ( fabs(pInVertex->m_ShadowSpaceTexCoord[0]) < 1e-3 ) + pOutVertex->m_ShadowSpaceTexCoord[0] = 0.0f; + else if ( fabs(pInVertex->m_ShadowSpaceTexCoord[0] - 1.0f) < 1e-3 ) + pOutVertex->m_ShadowSpaceTexCoord[0] = 1.0f; + + if ( fabs(pInVertex->m_ShadowSpaceTexCoord[1]) < 1e-3 ) + pOutVertex->m_ShadowSpaceTexCoord[1] = 0.0f; + else if ( fabs(pInVertex->m_ShadowSpaceTexCoord[1] - 1.0f) < 1e-3 ) + pOutVertex->m_ShadowSpaceTexCoord[1] = 1.0f; +} +template <class Clipper> +static inline void Intersect( ShadowVertex_t* pStart, ShadowVertex_t* pEnd, ShadowVertex_t* pOut, bool startInside, Clipper& clipper ) +{ + // Clip the edge to the clip plane + float t; + if (!Clipper::IsPlane()) + { + if (!Clipper::IsAbove()) + { + // This is the path the we always take for perspective light volumes. + t = Clipper::Clip( pStart->m_ShadowSpaceTexCoord, pEnd->m_ShadowSpaceTexCoord ); + + VectorLerp( pStart->m_ShadowSpaceTexCoord, pEnd->m_ShadowSpaceTexCoord, t, pOut->m_ShadowSpaceTexCoord ); + } + else + { + t = Clipper::Clip( pStart->m_ShadowSpaceTexCoord, pEnd->m_ShadowSpaceTexCoord ); + VectorLerp( pStart->m_ShadowSpaceTexCoord, pEnd->m_ShadowSpaceTexCoord, t, pOut->m_ShadowSpaceTexCoord ); + + // This is a special thing we do here to avoid hard-edged shadows + if (startInside) + ClampTexCoord( pEnd, pOut ); + else + ClampTexCoord( pStart, pOut ); + } + } + else + { + t = Clipper::Clip( pStart->m_Position, pEnd->m_Position ); + VectorLerp( pStart->m_ShadowSpaceTexCoord, pEnd->m_ShadowSpaceTexCoord, t, pOut->m_ShadowSpaceTexCoord ); + } + + VectorLerp( pStart->m_Position, pEnd->m_Position, t, pOut->m_Position ); +} + +template <class Clipper> +static void ShadowClip( ShadowClipState_t& clip, Clipper& clipper ) +{ + if ( clip.m_ClipCount == 0 ) + return; + + // Ye Olde Sutherland-Hodgman clipping algorithm + int numOutVerts = 0; + ShadowVertex_t** pSrcVert = clip.m_ppClipVertices[clip.m_CurrVert]; + ShadowVertex_t** pDestVert = clip.m_ppClipVertices[!clip.m_CurrVert]; + + int numVerts = clip.m_ClipCount; + ShadowVertex_t* pStart = pSrcVert[numVerts-1]; + bool startInside = Clipper::Inside( *pStart ); + for (int i = 0; i < numVerts; ++i) + { + ShadowVertex_t* pEnd = pSrcVert[i]; + bool endInside = Clipper::Inside( *pEnd ); + if (endInside) + { + if (!startInside) + { + // Started outside, ended inside, need to clip the edge + if ( clip.m_TempCount >= SHADOW_VERTEX_TEMP_COUNT ) + return; + + // Allocate a new clipped vertex + pDestVert[numOutVerts] = &clip.m_pTempVertices[clip.m_TempCount++]; + + // Clip the edge to the clip plane + Intersect( pStart, pEnd, pDestVert[numOutVerts], startInside, clipper ); + ++numOutVerts; + } + pDestVert[numOutVerts++] = pEnd; + } + else + { + if (startInside) + { + // Started inside, ended outside, need to clip the edge + if ( clip.m_TempCount >= SHADOW_VERTEX_TEMP_COUNT ) + return; + + // Allocate a new clipped vertex + pDestVert[numOutVerts] = &clip.m_pTempVertices[clip.m_TempCount++]; + + // Clip the edge to the clip plane + Intersect( pStart, pEnd, pDestVert[numOutVerts], startInside, clipper ); + ++numOutVerts; + } + } + pStart = pEnd; + startInside = endInside; + } + + // Switch source lists + clip.m_CurrVert = 1 - clip.m_CurrVert; + clip.m_ClipCount = numOutVerts; + Assert( clip.m_ClipCount <= SHADOW_VERTEX_TEMP_COUNT ); +} + + +//----------------------------------------------------------------------------- +// Project vertices into shadow space +//----------------------------------------------------------------------------- +bool CShadowMgr::ProjectVerticesIntoShadowSpace( const VMatrix& modelToShadow, + float maxDist, int count, Vector** RESTRICT ppPosition, ShadowClipState_t& clip ) +{ + bool insideVolume = false; + + // Create vertices to clip to... + for (int i = 0; i < count; ++i ) + { + Assert( ppPosition[i] ); + + VectorCopy( *ppPosition[i], clip.m_pTempVertices[i].m_Position ); + + // Project the points into shadow texture space + Vector3DMultiplyPosition( modelToShadow, *ppPosition[i], clip.m_pTempVertices[i].m_ShadowSpaceTexCoord ); + + // Set up clipping coords... + clip.m_ppClipVertices[0][i] = &clip.m_pTempVertices[i]; + + if (clip.m_pTempVertices[i].m_ShadowSpaceTexCoord[2] < maxDist ) + { + insideVolume = true; + } + } + + clip.m_TempCount = clip.m_ClipCount = count; + clip.m_CurrVert = 0; + + return insideVolume; +} + + +//----------------------------------------------------------------------------- +// Projects + clips shadows +//----------------------------------------------------------------------------- +int CShadowMgr::ProjectAndClipVertices( const Shadow_t& shadow, const VMatrix& worldToShadow, + const VMatrix *pWorldToModel, int count, Vector** ppPosition, ShadowVertex_t*** ppOutVertex ) +{ + VPROF( "ProjectAndClipVertices" ); + static ShadowClipState_t clip; + if ( !ProjectVerticesIntoShadowSpace( worldToShadow, shadow.m_MaxDist, count, ppPosition, clip ) ) + return 0; + + // Clippers... + CClipTop top; + CClipBottom bottom; + CClipLeft left; + CClipRight right; + CClipAbove above; + CClipPlane plane; + + // Sutherland-hodgman clip + ShadowClip( clip, top ); + ShadowClip( clip, bottom ); + ShadowClip( clip, left ); + ShadowClip( clip, right ); + ShadowClip( clip, above ); + + // Planes to suppress back-casting + for (int i = 0; i < shadow.m_ClipPlaneCount; ++i) + { + if ( pWorldToModel ) + { + cplane_t worldPlane, modelPlane; + worldPlane.normal = shadow.m_ClipPlane[i]; + worldPlane.dist = shadow.m_ClipDist[i]; + MatrixTransformPlane( *pWorldToModel, worldPlane, modelPlane ); + plane.SetPlane( modelPlane.normal, modelPlane.dist ); + } + else + { + plane.SetPlane( shadow.m_ClipPlane[i], shadow.m_ClipDist[i] ); + } + ShadowClip( clip, plane ); + } + + if (clip.m_ClipCount < 3) + return 0; + + // Return a pointer to the array of clipped vertices... + Assert(ppOutVertex); + *ppOutVertex = clip.m_ppClipVertices[clip.m_CurrVert]; + return clip.m_ClipCount; +} + + +//----------------------------------------------------------------------------- +// Accessor for use by the displacements +//----------------------------------------------------------------------------- +int CShadowMgr::ProjectAndClipVertices( ShadowHandle_t handle, int count, + Vector** ppPosition, ShadowVertex_t*** ppOutVertex ) +{ + return ProjectAndClipVertices( m_Shadows[handle], + m_Shadows[handle].m_WorldToShadow, NULL, count, ppPosition, ppOutVertex ); +} + + +//----------------------------------------------------------------------------- +// Copies vertex info from the clipped vertices +//----------------------------------------------------------------------------- +// This version treats texcoords as Vector +inline void CShadowMgr::CopyClippedVertices( int count, ShadowVertex_t** ppSrcVert, ShadowVertex_t* pDstVert, const Vector &vToAdd ) +{ + for (int i = 0; i < count; ++i) + { + pDstVert[i].m_Position = ppSrcVert[i]->m_Position + vToAdd; + pDstVert[i].m_ShadowSpaceTexCoord = ppSrcVert[i]->m_ShadowSpaceTexCoord; + + // Make sure it's been clipped + Assert( ppSrcVert[i]->m_ShadowSpaceTexCoord[0] >= -1e-3f ); + Assert( ppSrcVert[i]->m_ShadowSpaceTexCoord[0] - 1.0f <= 1e-3f ); + Assert( ppSrcVert[i]->m_ShadowSpaceTexCoord[1] >= -1e-3f ); + Assert( ppSrcVert[i]->m_ShadowSpaceTexCoord[1] - 1.0f <= 1e-3f ); + } +} + + +//----------------------------------------------------------------------------- +// Does the actual work of computing shadow vertices +//----------------------------------------------------------------------------- +bool CShadowMgr::ComputeShadowVertices( ShadowDecal_t& decal, + const VMatrix* pModelToWorld, const VMatrix *pWorldToModel, ShadowVertexCache_t* pVertexCache ) +{ + VPROF( "CShadowMgr::ComputeShadowVertices" ); + // Prepare for the clipping + Vector **ppVec = (Vector**)stackalloc( MSurf_VertCount( decal.m_SurfID ) * sizeof(Vector*) ); + for (int i = 0; i < MSurf_VertCount( decal.m_SurfID ); ++i ) + { + int vertIndex = host_state.worldbrush->vertindices[MSurf_FirstVertIndex( decal.m_SurfID )+i]; + ppVec[i] = &host_state.worldbrush->vertexes[vertIndex].position; + } + + // Compute the modelToShadow transform. + // In the case of the world, just use worldToShadow... + VMatrix* pModelToShadow = &m_Shadows[decal.m_Shadow].m_WorldToShadow; + + VMatrix temp; + if ( pModelToWorld ) + { + MatrixMultiply( *pModelToShadow, *pModelToWorld, temp ); + pModelToShadow = &temp; + } + else + { + pWorldToModel = NULL; + } + + // Create vertices to clip to... + ShadowVertex_t** ppSrcVert; + int clipCount = ProjectAndClipVertices( m_Shadows[decal.m_Shadow], *pModelToShadow, pWorldToModel, + MSurf_VertCount( decal.m_SurfID ), ppVec, &ppSrcVert ); + if (clipCount == 0) + { + pVertexCache->m_Count = 0; + return false; + } + + // Allocate the vertices we're going to use for the decal + ShadowVertex_t* pDstVert = AllocateVertices( *pVertexCache, clipCount ); + Assert( pDstVert ); + + // Copy the clipped vertices into the cache + const Vector &vNormal = MSurf_Plane( decal.m_SurfID ).normal; + CopyClippedVertices( clipCount, ppSrcVert, pDstVert, vNormal * OVERLAY_AVOID_FLICKER_NORMAL_OFFSET ); + + // Indicate which shadow this is related to + pVertexCache->m_Shadow = decal.m_Shadow; + + return true; +} + + + +//----------------------------------------------------------------------------- +// Should we cache vertices? +//----------------------------------------------------------------------------- +inline bool CShadowMgr::ShouldCacheVertices( const ShadowDecal_t& decal ) +{ + return (m_Shadows[decal.m_Shadow].m_Flags & SHADOW_CACHE_VERTS) != 0; +} + + +//----------------------------------------------------------------------------- +// Generates a list displacement shadow vertices to render +//----------------------------------------------------------------------------- +inline bool CShadowMgr::GenerateDispShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecal_t& decal, ShadowRenderInfo_t& info ) +{ + //============================================================================= + // HPE_BEGIN: + // [smessick] Added an overflow condition for the max disp decal cache. + //============================================================================= + if ( info.m_DispCount >= MAX_SHADOW_DECAL_CACHE_COUNT ) + { + info.m_DispCount = MAX_SHADOW_DECAL_CACHE_COUNT; + return true; + } + //============================================================================= + // HPE_END + //============================================================================= + + int v, i; + if ( !MSurf_DispInfo( decal.m_SurfID )->ComputeShadowFragments( decal.m_DispShadow, v, i ) ) + return false; + + // Catch overflows.... + if ( ( info.m_VertexCount + v >= info.m_nMaxVertices ) || ( info.m_IndexCount + i >= info.m_nMaxIndices ) ) + return true; + + info.m_VertexCount += v; + info.m_IndexCount += i; + info.m_pDispCache[info.m_DispCount++] = decal.m_DispShadow; + return true; +} + + +//----------------------------------------------------------------------------- +// Generates a list shadow vertices to render +//----------------------------------------------------------------------------- +inline bool CShadowMgr::GenerateNormalShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecal_t& decal, ShadowRenderInfo_t& info ) +{ + //============================================================================= + // HPE_BEGIN: + // [smessick] Check for cache overflow. + //============================================================================= + if ( info.m_Count >= MAX_SHADOW_DECAL_CACHE_COUNT ) + { + info.m_Count = MAX_SHADOW_DECAL_CACHE_COUNT; + return true; + } + //============================================================================= + // HPE_END + //============================================================================= + + // Look for a cache hit + ShadowVertexCache_t* pVertexCache; + if (decal.m_ShadowVerts != m_VertexCache.InvalidIndex()) + { + // Ok, we've already computed the data, lets use it + info.m_pCache[info.m_Count] = decal.m_ShadowVerts; + pVertexCache = &m_VertexCache[decal.m_ShadowVerts]; + } + else + { + // Attempt to cull the surface + bool bIsNear = IsShadowNearSurface( decal.m_Shadow, decal.m_SurfID, info.m_pModelToWorld, &info.m_WorldToModel ); + if ( !bIsNear ) + return false; + + // In this case, we gotta recompute the shadow decal vertices + // and maybe even store it into the cache.... + bool shouldCacheVerts = ShouldCacheVertices( decal ); + if (shouldCacheVerts) + { + decal.m_ShadowVerts = m_VertexCache.AddToTail(); + info.m_pCache[info.m_Count] = decal.m_ShadowVerts; + pVertexCache = &m_VertexCache[decal.m_ShadowVerts]; + } + else + { + int i = m_TempVertexCache.AddToTail(); + info.m_pCache[info.m_Count] = -i-1; + pVertexCache = &m_TempVertexCache[i]; + Assert( info.m_pCache[info.m_Count] < 0 ); + } + + // Compute the shadow vertices + // If no vertices were created, indicate this surface should be removed from the cache + if ( !ComputeShadowVertices( decal, info.m_pModelToWorld, &info.m_WorldToModel, pVertexCache ) ) + return false; + } + + // Catch overflows.... + int nAdditionalIndices = 3 * (pVertexCache->m_Count - 2); + if ( ( info.m_VertexCount + pVertexCache->m_Count >= info.m_nMaxVertices ) || + ( info.m_IndexCount + nAdditionalIndices >= info.m_nMaxIndices ) ) + { + return true; + } + + // Update vertex, index, and decal counts + info.m_VertexCount += pVertexCache->m_Count; + info.m_IndexCount += nAdditionalIndices; + ++info.m_Count; + + return true; +} + + +//----------------------------------------------------------------------------- +// Generates a list shadow vertices to render +//----------------------------------------------------------------------------- +void CShadowMgr::GenerateShadowRenderInfo( IMatRenderContext *pRenderContext, ShadowDecalHandle_t decalHandle, ShadowRenderInfo_t& info ) +{ + info.m_VertexCount = 0; + info.m_IndexCount = 0; + info.m_Count = 0; + info.m_DispCount = 0; + + // Keep the lists only full of valid decals; that way we can preserve + // the render lists in the case that we discover a shadow isn't needed. + ShadowDecalHandle_t next; + for ( ; decalHandle != m_ShadowDecals.InvalidIndex(); decalHandle = next ) + { + ShadowDecal_t& decal = m_ShadowDecals[decalHandle]; + next = m_ShadowDecals[decalHandle].m_NextRender; + + // Skip translucent shadows [ don't add their verts + indices to the render lists ] + Shadow_t &shadow = m_Shadows[ decal.m_Shadow ]; + if ( shadow.m_FalloffBias == 255 ) + continue; + + bool keepShadow; + if ( decal.m_DispShadow != DISP_SHADOW_HANDLE_INVALID ) + { + // Handle shadows on displacements... + keepShadow = GenerateDispShadowRenderInfo( pRenderContext, decal, info ); + } + else + { + // Handle shadows on normal surfaces + keepShadow = GenerateNormalShadowRenderInfo( pRenderContext, decal, info ); + } + + // Retire the surface if the shadow didn't actually hit it + if ( !keepShadow && ShouldCacheVertices( decal ) ) + { + // If no triangles were generated + // (the decal was completely clipped off) + // In this case, remove the decal from the surface cache + // so next time it'll be faster (for cached decals) + RemoveShadowDecalFromSurface( decal.m_SurfID, decalHandle ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes information for rendering +//----------------------------------------------------------------------------- +void CShadowMgr::ComputeRenderInfo( ShadowDecalRenderInfo_t* pInfo, ShadowHandle_t handle ) const +{ + const ShadowInfo_t& i = m_Shadows[handle]; + pInfo->m_vTexOrigin = i.m_TexOrigin; + pInfo->m_vTexSize = i.m_TexSize; + pInfo->m_flFalloffOffset = i.m_FalloffOffset; + pInfo->m_flFalloffAmount = i.m_FalloffAmount; + pInfo->m_flFalloffBias = i.m_FalloffBias; + + float flFalloffDist = i.m_MaxDist - i.m_FalloffOffset; + pInfo->m_flOOZFalloffDist = ( flFalloffDist > 0.0f ) ? 1.0f / flFalloffDist : 1.0f; +} + + +//----------------------------------------------------------------------------- +// Adds normal shadows to the mesh builder +//----------------------------------------------------------------------------- +int CShadowMgr::AddNormalShadowsToMeshBuilder( CMeshBuilder& meshBuilder, ShadowRenderInfo_t& info ) +{ + // Step through the cache and add all shadows on normal surfaces + ShadowDecalRenderInfo_t shadow; + int baseIndex = 0; + for (int i = 0; i < info.m_Count; ++i) + { + // Two loops here, basically to minimize the # of if statements we need + ShadowVertexCache_t* pVertexCache; + if (info.m_pCache[i] < 0) + { + pVertexCache = &m_TempVertexCache[-info.m_pCache[i]-1]; + } + else + { + pVertexCache = &m_VertexCache[info.m_pCache[i]]; + } + + ShadowVertex_t* pVerts = GetCachedVerts( *pVertexCache ); + g_pShadowMgr->ComputeRenderInfo( &shadow, pVertexCache->m_Shadow ); + + int j; + unsigned char c; + Vector2D texCoord; + int vCount = pVertexCache->m_Count - 2; + if ( vCount <= 0 ) + continue; + + for ( j = 0; j < vCount; ++j, ++pVerts ) + { + // Transform + offset the texture coords + Vector2DMultiply( pVerts->m_ShadowSpaceTexCoord.AsVector2D(), shadow.m_vTexSize, texCoord ); + texCoord += shadow.m_vTexOrigin; + c = ComputeDarkness( pVerts->m_ShadowSpaceTexCoord.z, shadow ); + + meshBuilder.Position3fv( pVerts->m_Position.Base() ); + meshBuilder.Color4ub( c, c, c, c ); + meshBuilder.TexCoord2fv( 0, texCoord.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( baseIndex ); + meshBuilder.FastIndex( j + baseIndex + 1 ); + meshBuilder.FastIndex( j + baseIndex + 2 ); + } + + Vector2DMultiply( pVerts->m_ShadowSpaceTexCoord.AsVector2D(), shadow.m_vTexSize, texCoord ); + texCoord += shadow.m_vTexOrigin; + c = ComputeDarkness( pVerts->m_ShadowSpaceTexCoord.z, shadow ); + meshBuilder.Position3fv( pVerts->m_Position.Base() ); + meshBuilder.Color4ub( c, c, c, c ); + meshBuilder.TexCoord2fv( 0, texCoord.Base() ); + meshBuilder.AdvanceVertex(); + ++pVerts; + + Vector2DMultiply( pVerts->m_ShadowSpaceTexCoord.AsVector2D(), shadow.m_vTexSize, texCoord ); + texCoord += shadow.m_vTexOrigin; + c = ComputeDarkness( pVerts->m_ShadowSpaceTexCoord.z, shadow ); + meshBuilder.Position3fv( pVerts->m_Position.Base() ); + meshBuilder.Color4ub( c, c, c, c ); + meshBuilder.TexCoord2fv( 0, texCoord.Base() ); + meshBuilder.AdvanceVertex(); + + // Update the base index + baseIndex += vCount + 2; + } + + return baseIndex; +} + + +//----------------------------------------------------------------------------- +// Adds displacement shadows to the mesh builder +//----------------------------------------------------------------------------- +int CShadowMgr::AddDisplacementShadowsToMeshBuilder( CMeshBuilder& meshBuilder, + ShadowRenderInfo_t& info, int baseIndex ) +{ + if ( !r_DrawDisp.GetBool() ) + return baseIndex; + + // Step through the cache and add all shadows on displacement surfaces + for (int i = 0; i < info.m_DispCount; ++i) + { + baseIndex = DispInfo_AddShadowsToMeshBuilder( meshBuilder, info.m_pDispCache[i], baseIndex ); + } + + return baseIndex; +} + + +//----------------------------------------------------------------------------- +// The following methods will display debugging info in the middle of each shadow decal +//----------------------------------------------------------------------------- +static void DrawShadowID( ShadowHandle_t shadowHandle, const Vector &vecCentroid ) +{ +#ifndef SWDS + char buf[32]; + Q_snprintf(buf, sizeof( buf ), "%d", shadowHandle ); + CDebugOverlay::AddTextOverlay( vecCentroid, 0, buf ); +#endif +} + +void CShadowMgr::RenderDebuggingInfo( const ShadowRenderInfo_t &info, ShadowDebugFunc_t func ) +{ + // Step through the cache and add all shadows on normal surfaces + for (int i = 0; i < info.m_Count; ++i) + { + ShadowVertexCache_t* pVertexCache; + if (info.m_pCache[i] < 0) + { + pVertexCache = &m_TempVertexCache[-info.m_pCache[i]-1]; + } + else + { + pVertexCache = &m_VertexCache[info.m_pCache[i]]; + } + + ShadowVertex_t* pVerts = GetCachedVerts( *pVertexCache ); + + Vector vecNormal; + float flTotalArea = 0.0f; + Vector vecCentroid(0,0,0); + Vector vecApex = pVerts[0].m_Position; + int vCount = pVertexCache->m_Count; + + for ( int j = 0; j < vCount - 2; ++j ) + { + Vector v1 = pVerts[j + 1].m_Position; + Vector v2 = pVerts[j + 2].m_Position; + CrossProduct( v2 - v1, v1 - vecApex, vecNormal ); + float flArea = vecNormal.Length(); + flTotalArea += flArea; + vecCentroid += (vecApex + v1 + v2) * flArea / 3.0f; + } + + if (flTotalArea) + { + vecCentroid /= flTotalArea; + } + + func( pVertexCache->m_Shadow, vecCentroid ); + } +} + + +//----------------------------------------------------------------------------- +// Renders shadows that all share a material enumeration +//----------------------------------------------------------------------------- +void CShadowMgr::RenderShadowList( IMatRenderContext *pRenderContext, ShadowDecalHandle_t decalHandle, const VMatrix* pModelToWorld ) +{ + //============================================================================= + // HPE_BEGIN: + // [smessick] Make sure we don't overflow our caches. + //============================================================================= + + if ( m_DecalsToRender > m_ShadowDecalCache.Count() ) + { + // Don't grow past the MAX_SHADOW_DECAL_CACHE_COUNT cap. + int diff = min( m_DecalsToRender, (int)MAX_SHADOW_DECAL_CACHE_COUNT ) - m_ShadowDecalCache.Count(); + if ( diff > 0 ) + { + // Grow the cache. + m_ShadowDecalCache.Grow( diff ); + DevMsg( "[CShadowMgr::RenderShadowList] growing shadow decal cache (decals: %d, cache: %d, diff: %d).\n", m_DecalsToRender, m_ShadowDecalCache.Count(), diff ); + } + } + + if ( m_DecalsToRender > m_DispShadowDecalCache.Count() ) + { + // Don't grow past the MAX_SHADOW_DECAL_CACHE_COUNT cap. + int diff = min( m_DecalsToRender, (int)MAX_SHADOW_DECAL_CACHE_COUNT ) - m_DispShadowDecalCache.Count(); + if ( diff > 0 ) + { + // Grow the cache. + m_DispShadowDecalCache.Grow( diff ); + DevMsg( "[CShadowMgr::RenderShadowList] growing disp shadow decal cache (decals: %d, cache: %d, diff: %d).\n", m_DecalsToRender, m_DispShadowDecalCache.Count(), diff ); + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + // Set the render state... + Shadow_t& shadow = m_Shadows[m_ShadowDecals[decalHandle].m_Shadow]; + + if ( r_shadowwireframe.GetInt() == 0 ) + { + pRenderContext->Bind( shadow.m_pMaterial, shadow.m_pBindProxy ); + } + else + { + pRenderContext->Bind( g_materialWorldWireframe ); + } + + // Blow away the temporary vertex cache (for normal surfaces) + ClearTempCache(); + + // Set up rendering info structure + ShadowRenderInfo_t info; + + //============================================================================= + // HPE_BEGIN: + // [smessick] This code used to create the cache dynamically on the stack. + //============================================================================= + info.m_pCache = m_ShadowDecalCache.Base(); + info.m_pDispCache = m_DispShadowDecalCache.Base(); + //============================================================================= + // HPE_END + //============================================================================= + + info.m_pModelToWorld = pModelToWorld; + if ( pModelToWorld ) + { + MatrixInverseTR( *pModelToWorld, info.m_WorldToModel ); + } + info.m_nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + info.m_nMaxVertices = pRenderContext->GetMaxVerticesToRender( shadow.m_pMaterial ); + + // Iterate over all decals in the decal list and generate polygon lists + // Creating them from scratch if their shadow poly cache is invalid + GenerateShadowRenderInfo(pRenderContext, decalHandle, info); + Assert( info.m_Count <= m_DecalsToRender ); + Assert( info.m_DispCount <= m_DecalsToRender ); + //============================================================================= + // HPE_BEGIN: + // [smessick] Also check against the max. + //============================================================================= + Assert( info.m_Count <= m_ShadowDecalCache.Count() && + info.m_Count <= MAX_SHADOW_DECAL_CACHE_COUNT ); + Assert( info.m_DispCount <= m_DispShadowDecalCache.Count() && + info.m_DispCount <= MAX_SHADOW_DECAL_CACHE_COUNT ); + //============================================================================= + // HPE_END + //============================================================================= + + // Now that the vertex lists are created, render them + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, info.m_VertexCount, info.m_IndexCount ); + + // Add in shadows from both normal surfaces + displacement surfaces + int baseIndex = AddNormalShadowsToMeshBuilder( meshBuilder, info ); + AddDisplacementShadowsToMeshBuilder( meshBuilder, info, baseIndex ); + + meshBuilder.End(); + pMesh->Draw(); + + if (r_shadowids.GetInt() != 0) + { + RenderDebuggingInfo( info, DrawShadowID ); + } +} + +//----------------------------------------------------------------------------- +// Set the number of world material buckets. This should get called on level load. +//----------------------------------------------------------------------------- +void CShadowMgr::SetNumWorldMaterialBuckets( int numMaterialSortBins ) +{ + m_NumWorldMaterialBuckets = numMaterialSortBins; + FlashlightHandle_t flashlightID; + for( flashlightID = m_FlashlightStates.Head(); + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + m_FlashlightStates[flashlightID].m_MaterialBuckets.SetNumMaterialSortIDs( numMaterialSortBins ); + m_FlashlightStates[flashlightID].m_OccluderBuckets.SetNumMaterialSortIDs( numMaterialSortBins ); + } + ClearAllFlashlightMaterialBuckets(); +} + +//----------------------------------------------------------------------------- +// Per frame call to clear all of the flashlight world material buckets. +//----------------------------------------------------------------------------- +void CShadowMgr::ClearAllFlashlightMaterialBuckets( void ) +{ + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + FlashlightHandle_t flashlightID; + for( flashlightID = m_FlashlightStates.Head(); + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + m_FlashlightStates[flashlightID].m_MaterialBuckets.Flush(); + } +} + +//----------------------------------------------------------------------------- +// Allocate world material buckets for a particular flashlight. This should get called on flashlight creation. +//----------------------------------------------------------------------------- +void CShadowMgr::AllocFlashlightMaterialBuckets( FlashlightHandle_t flashlightID ) +{ + Assert( m_FlashlightStates.MaxElementIndex() >= flashlightID ); + m_FlashlightStates[flashlightID].m_MaterialBuckets.SetNumMaterialSortIDs( m_NumWorldMaterialBuckets ); + m_FlashlightStates[flashlightID].m_OccluderBuckets.SetNumMaterialSortIDs( m_NumWorldMaterialBuckets ); +} + +//----------------------------------------------------------------------------- +// Update a particular flashlight's state. +//----------------------------------------------------------------------------- +void CShadowMgr::UpdateFlashlightState( ShadowHandle_t shadowHandle, const FlashlightState_t &lightState ) +{ + m_FlashlightStates[m_Shadows[shadowHandle].m_FlashlightHandle].m_FlashlightState = lightState; +} + +void CShadowMgr::SetFlashlightDepthTexture( ShadowHandle_t shadowHandle, ITexture *pFlashlightDepthTexture, unsigned char ucShadowStencilBit ) +{ + m_Shadows[shadowHandle].m_pFlashlightDepthTexture = pFlashlightDepthTexture; + m_Shadows[shadowHandle].m_ucShadowStencilBit = ucShadowStencilBit; +} + +bool ScreenSpaceRectFromPoints( IMatRenderContext *pRenderContext, Vector vClippedPolygons[8][10], int *pNumPoints, int nNumPolygons, int *nLeft, int *nTop, int *nRight, int *nBottom ) +{ + if( nNumPolygons == 0 ) + return false; + + VMatrix matView, matProj, matViewProj; + pRenderContext->GetMatrix( MATERIAL_VIEW, &matView ); + pRenderContext->GetMatrix( MATERIAL_PROJECTION, &matProj ); + MatrixMultiply( matProj, matView, matViewProj ); + + float fMinX, fMaxX, fMinY, fMaxY; // Init bounding rect + fMinX = fMinY = FLT_MAX; + fMaxX = fMaxY = -FLT_MAX; + + for ( int i=0; i<nNumPolygons; i++ ) + { + for ( int j=0; j<pNumPoints[i]; j++ ) + { + Vector vScreenSpacePoint; + matViewProj.V3Mul( vClippedPolygons[i][j], vScreenSpacePoint ); // Transform from World to screen space + + fMinX = fpmin( fMinX, vScreenSpacePoint.x ); // Update mins/maxes + fMaxX = fpmax( fMaxX, vScreenSpacePoint.x ); // + fMinY = fpmin( fMinY, -vScreenSpacePoint.y ); // These are in -1 to +1 range + fMaxY = fpmax( fMaxY, -vScreenSpacePoint.y ); // + } + } + + int nWidth, nHeight; + g_pMaterialSystem->GetBackBufferDimensions( nWidth, nHeight ); // Get render target dimensions + + *nLeft = ((fMinX * 0.5f + 0.5f) * (float) nWidth ) - 1; // Convert to render target pixel units + *nTop = ((fMinY * 0.5f + 0.5f) * (float) nHeight) - 1; + *nRight = ((fMaxX * 0.5f + 0.5f) * (float) nWidth ) + 1; + *nBottom = ((fMaxY * 0.5f + 0.5f) * (float) nHeight) + 1; + + *nLeft = clamp( *nLeft, 0, nWidth ); // Clamp to render target dimensions + *nTop = clamp( *nTop, 0, nHeight ); + *nRight = clamp( *nRight, 0, nWidth ); + *nBottom = clamp( *nBottom, 0, nHeight ); + + Assert( (*nLeft <= *nRight) && (*nTop <= *nBottom) ); + + // Do we have an actual subrect of the whole screen? + bool bWithinBounds = ((*nLeft > 0 ) || (*nTop > 0) || (*nRight < nWidth) || (*nBottom < nHeight)); + + // Compute valid area + nWidth = (*nRight - *nLeft); + nHeight = (*nBottom - *nTop); + int nArea = ( nWidth > 0 ) && ( nHeight > 0 ) ? nWidth * nHeight : 0; + + // Valid rect? + return bWithinBounds && (nArea > 0); +} + +// Turn this optimization off by default +static ConVar r_flashlightclip("r_flashlightclip", "0", FCVAR_CHEAT ); +static ConVar r_flashlightdrawclip("r_flashlightdrawclip", "0", FCVAR_CHEAT ); +static ConVar r_flashlightscissor( "r_flashlightscissor", "1", 0 ); + +void ExtractFrustumPlanes( Frustum frustumPlanes, float flPlaneEpsilon ) +{ + const CViewSetup &view = g_EngineRenderer->ViewGetCurrent(); + + float flFOVy = CalcFovY( view.fov, view.m_flAspectRatio ); + + Frustum_t frustum; + + Vector vForward, vRight, vUp; + AngleVectors( view.angles, &vForward, &vRight, &vUp ); + + GeneratePerspectiveFrustum( view.origin, vForward, vRight, vUp, + view.zNear + flPlaneEpsilon, view.zFar - flPlaneEpsilon, // Apply epsilon to near and far + view.fov, flFOVy, frustum ); + + // Copy out to the planes that the engine renderer uses. + for( int i=0; i < FRUSTUM_NUMPLANES; i++ ) + { + frustumPlanes[i].m_Normal = frustum.GetPlane(i)->normal; + frustumPlanes[i].m_Dist = frustum.GetPlane(i)->dist; + } +} + +void ConstructNearAndFarPolygons( Vector *pVecNearPlane, Vector *pVecFarPlane, float flPlaneEpsilon ) +{ + const CViewSetup &view = g_EngineRenderer->ViewGetCurrent(); + + float fovY = CalcFovY( view.fov, view.m_flAspectRatio ); + + // Compute near and far plane half-width and half-height + float flTanHalfAngleRadians = tan( view.fov * ( 0.5f * M_PI / 180.0f ) ); + float flHalfNearWidth = flTanHalfAngleRadians * ( view.zNear + flPlaneEpsilon ); + float flHalfFarWidth = flTanHalfAngleRadians * ( view.zFar - flPlaneEpsilon ); + flTanHalfAngleRadians = tan( fovY * ( 0.5f * M_PI / 180.0f ) ); + float flHalfNearHeight = flTanHalfAngleRadians * ( view.zNear + flPlaneEpsilon ); + float flHalfFarHeight = flTanHalfAngleRadians * ( view.zFar - flPlaneEpsilon ); + + // World-space orientation of viewer + Vector vForward, vRight, vUp; + AngleVectors( view.angles, &vForward, &vRight, &vUp ); + vForward.NormalizeInPlace(); + vRight.NormalizeInPlace(); + vUp.NormalizeInPlace(); + + // Center of near and far planes in world space + Vector vCenterNear = view.origin + vForward * ( view.zNear + flPlaneEpsilon ); + Vector vCenterFar = view.origin + vForward * ( view.zFar - flPlaneEpsilon ); + + pVecNearPlane[0] = vCenterNear - ( vRight * flHalfNearWidth ) - ( vUp * flHalfNearHeight ); + pVecNearPlane[1] = vCenterNear - ( vRight * flHalfNearWidth ) + ( vUp * flHalfNearHeight ); + pVecNearPlane[2] = vCenterNear + ( vRight * flHalfNearWidth ) + ( vUp * flHalfNearHeight ); + pVecNearPlane[3] = vCenterNear + ( vRight * flHalfNearWidth ) - ( vUp * flHalfNearHeight ); + + pVecFarPlane[0] = vCenterNear - ( vRight * flHalfFarWidth ) - ( vUp * flHalfFarHeight ); + pVecFarPlane[1] = vCenterNear + ( vRight * flHalfFarWidth ) - ( vUp * flHalfFarHeight ); + pVecFarPlane[2] = vCenterNear + ( vRight * flHalfFarWidth ) + ( vUp * flHalfFarHeight ); + pVecFarPlane[3] = vCenterNear - ( vRight * flHalfFarWidth ) + ( vUp * flHalfFarHeight ); +} + +void DrawDebugPolygon( int nNumVerts, Vector *pVecPoints, bool bFrontFacing, bool bNearPlane ) +{ + int r=0, g=0, b=0; + if ( bFrontFacing ) + b = 255; + else + r = 255; + + if ( bNearPlane ) // Draw near plane green for visualization + { + r = b = 0; + g = 255; + } + + // Draw triangles fanned out from vertex zero + for (int i=1; i<(nNumVerts-1); i++) + { + Vector v0 = pVecPoints[0]; + Vector v1 = pVecPoints[bFrontFacing ? i : i+1]; + Vector v2 = pVecPoints[bFrontFacing ? i+1 : i]; + + CDebugOverlay::AddTriangleOverlay(v0, v1, v2, r, g, b, 20, true, 0 ); + } + + // Draw solid lines around the polygon + for (int i=0; i<nNumVerts; i++) + { + Vector v0 = pVecPoints[i]; + Vector v1 = pVecPoints[ (i+1) % nNumVerts]; + + CDebugOverlay::AddLineOverlay( v0, v1, 255, 255, 255, 255, false, 0); + } +} + +void DrawPolygonToStencil( IMatRenderContext *pRenderContext, int nNumVerts, Vector *pVecPoints, bool bFrontFacing, bool bNearPlane ) +{ + IMaterial *pMaterial = materials->FindMaterial( "engine/writestencil", TEXTURE_GROUP_OTHER, true ); + + pRenderContext->Bind( pMaterial ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nNumVerts-2 ); + + // Fan out from vertex zero + for (int i=1; i<(nNumVerts-1); i++) + { + meshBuilder.Position3f( pVecPoints[0].x, pVecPoints[0].y, pVecPoints[0].z ); + meshBuilder.AdvanceVertex(); + + int index = bFrontFacing ? i : i+1; + meshBuilder.Position3f( pVecPoints[index].x, pVecPoints[index].y, pVecPoints[index].z ); + meshBuilder.AdvanceVertex(); + + index = bFrontFacing ? i+1 : i; + meshBuilder.Position3f( pVecPoints[index].x, pVecPoints[index].y, pVecPoints[index].z ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End( false, true ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PopMatrix(); +} + +// Determine if two Vectors are sufficiently close (Manhattan-ish distance, not Euclidean) +bool SufficientlyClose( Vector v1, Vector v2, float flEpsilon ) +{ + if ( fabs( v1.x - v2.x ) > flEpsilon ) // Bail if x components are sufficiently different + return false; + + if ( fabs( v1.y - v2.y ) > flEpsilon ) // Bail if y components are sufficiently different + return false; + + if ( fabs( v1.z - v2.z ) > flEpsilon ) // Bail if z components are sufficiently different + return false; + + return true; +} + + +int ClipPlaneToFrustum( Vector *pInPoints, Vector *pOutPoints, Vector *pVecWorldFrustumPoints ) +{ + Vector vClipPing[10]; // Vector lists to ping-pong between while clipping + Vector vClipPong[10]; // + bool bPing = true; // Ping holds the latest polygon + + vClipPing[0] = pInPoints[0]; // Copy into Ping + vClipPing[1] = pInPoints[1]; + vClipPing[2] = pInPoints[2]; + vClipPing[3] = pInPoints[3]; + + int nNumPoints = 4; + + for ( int i=0; i < 6; i++ ) + { + Vector vNormal; + float flDist; + + if ( nNumPoints < 3 ) // If we're already clipped away, bail out entirely + break; + + Vector *pClipPolygon = pVecWorldFrustumPoints+(4*i); // Polygon defining clip plane + ComputeTrianglePlane( pClipPolygon[0], pClipPolygon[1], pClipPolygon[2], vNormal, flDist ); // Compute plane normal and dist + + if ( bPing ) + nNumPoints = ClipPolyToPlane( vClipPing, nNumPoints, vClipPong, vNormal, flDist ); // Clip Ping into Pong + else + nNumPoints = ClipPolyToPlane( vClipPong, nNumPoints, vClipPing, vNormal, flDist ); // Clip Pong into Ping + + bPing = !bPing; // Flip buffers + } + + if ( nNumPoints < 3) + return 0; + + if ( bPing ) + memcpy( pOutPoints, vClipPing, nNumPoints * sizeof(Vector) ); + else + memcpy( pOutPoints, vClipPong, nNumPoints * sizeof(Vector) ); + + return nNumPoints; +} + + + +void CShadowMgr::SetStencilAndScissor( IMatRenderContext *pRenderContext, FlashlightInfo_t &flashlightInfo, bool bUseStencil ) +{ + VMatrix matFlashlightToWorld; + MatrixInverseGeneral( m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow, matFlashlightToWorld ); + + // Eight points defining the frustum in Flashlight space + Vector vFrustumPoints[24] = { Vector(0.0f, 0.0f, 0.0f), Vector(1.0f, 0.0f, 0.0f), Vector(1.0f, 1.0f, 0.0f), Vector(0.0f, 1.0f, 0.0f), // Near + Vector(0.0f, 0.0f, 1.0f), Vector(0.0f, 1.0f, 1.0f), Vector(1.0f, 1.0f, 1.0f), Vector(1.0f, 0.0f, 1.0f), // Far + Vector(1.0f, 0.0f, 0.0f), Vector(1.0f, 0.0f, 1.0f), Vector(1.0f, 1.0f, 1.0f), Vector(1.0f, 1.0f, 0.0f), // Right + Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 1.0f, 0.0f), Vector(0.0f, 1.0f, 1.0f), Vector(0.0f, 0.0f, 1.0f), // Left + Vector(0.0f, 1.0f, 0.0f), Vector(1.0f, 1.0f, 0.0f), Vector(1.0f, 1.0f, 1.0f), Vector(0.0f, 1.0f, 1.0f), // Bottom + Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 1.0f), Vector(1.0f, 0.0f, 1.0f), Vector(1.0f, 0.0f, 0.0f)}; // Top + + // Transform points to world space + Vector vWorldFrustumPoints[24]; + for ( int i=0; i < 24; i++ ) + { + matFlashlightToWorld.V3Mul( vFrustumPoints[i], vWorldFrustumPoints[i] ); + } + + // Express near and far planes of View frustum in world space + Frustum frustumPlanes; + const float flPlaneEpsilon = 0.4f; + ExtractFrustumPlanes( frustumPlanes, flPlaneEpsilon ); + Vector vNearNormal = frustumPlanes[FRUSTUM_NEARZ].m_Normal; + Vector vFarNormal = frustumPlanes[FRUSTUM_FARZ].m_Normal; + float flNearDist = frustumPlanes[FRUSTUM_NEARZ].m_Dist; + float flFarDist = frustumPlanes[FRUSTUM_FARZ].m_Dist; + + Vector vTempFace[5]; + Vector vClippedFace[6]; + Vector vClippedPolygons[8][10]; // Array of up to eight polygons (10 verts is more than enough for each) + int nNumVertices[8]; // Number vertices on each of the of clipped polygons + int nNumPolygons = 0; // How many polygons have survived the clip + + // Clip each face individually to near and far planes + for ( int i=0; i < 6; i++ ) + { + Vector *inVerts = vWorldFrustumPoints+(4*i); // Series of quadrilateral inputs + Vector *tempVerts = vTempFace; + Vector *outVerts = vClippedFace; + + int nClipCount = ClipPolyToPlane( inVerts, 4, tempVerts, vNearNormal, flNearDist ); // need to set fOnPlaneEpsilon? + + if ( nClipCount > 2 ) // If the polygon survived the near clip, try the far as well + { + nClipCount = ClipPolyToPlane( tempVerts, nClipCount, outVerts, vFarNormal, flFarDist ); // need to set fOnPlaneEpsilon? + + if ( nClipCount > 2 ) // If we still have a poly after clipping to both planes, add it to the list + { + memcpy( vClippedPolygons[nNumPolygons], outVerts, nClipCount * sizeof (Vector) ); + nNumVertices[nNumPolygons] = nClipCount; + nNumPolygons++; + } + } + } + + // Construct polygons for near and far planes + Vector vNearPlane[4], vFarPlane[4]; + ConstructNearAndFarPolygons( vNearPlane, vFarPlane, flPlaneEpsilon ); + bool bNearPlane = false; + + // Clip near plane to flashlight frustum and tack on to list + int nClipCount = ClipPlaneToFrustum( vNearPlane, vClippedPolygons[nNumPolygons], vWorldFrustumPoints ); + if ( nClipCount > 2 ) // If the near plane clipped and resulted in a polygon, take note in the polygon list + { + nNumVertices[nNumPolygons] = nClipCount; + nNumPolygons++; + bNearPlane = true; + } + +/* +TODO: do we even need to do the far plane? + + // Clip near plane to flashlight frustum and tack on to list + nClipCount = ClipPlaneToFrustum( vFarPlane, vClippedPolygons[nNumPolygons], vWorldFrustumPoints ); + if ( nClipCount > 2 ) // If the near plane clipped and resulted in a polygon, take note in the polygon list + { + nNumVertices[nNumPolygons] = nClipCount; + nNumPolygons++; + } +*/ + // Fuse positions of any verts which are within epsilon + for (int i=0; i<nNumPolygons; i++) // For each polygon + { + for (int j=0; j<nNumVertices[i]; j++) // For each vertex + { + for (int k=i+1; k<nNumPolygons; k++) // For each later polygon + { + for (int m=0; m<nNumVertices[k]; m++) // For each vertex + { + if ( SufficientlyClose(vClippedPolygons[i][j], vClippedPolygons[k][m], 0.1f) ) + { + vClippedPolygons[k][m] = vClippedPolygons[i][j]; + } + } + } + } + } + + // Calculate scissoring rect + flashlightInfo.m_FlashlightState.m_bScissor = false; + if ( r_flashlightscissor.GetBool() && (nNumPolygons > 0) ) + { + int nLeft, nTop, nRight, nBottom; + flashlightInfo.m_FlashlightState.m_bScissor = ScreenSpaceRectFromPoints( pRenderContext, vClippedPolygons, nNumVertices, nNumPolygons, &nLeft, &nTop, &nRight, &nBottom ); + if ( flashlightInfo.m_FlashlightState.m_bScissor ) + { + flashlightInfo.m_FlashlightState.m_nLeft = nLeft; + flashlightInfo.m_FlashlightState.m_nTop = nTop; + flashlightInfo.m_FlashlightState.m_nRight = nRight; + flashlightInfo.m_FlashlightState.m_nBottom = nBottom; + } + } + + if ( r_flashlightdrawclip.GetBool() && r_flashlightclip.GetBool() && bUseStencil ) + { + // Draw back facing debug polygons + for (int i=0; i<nNumPolygons; i++) + { + DrawDebugPolygon( nNumVertices[i], vClippedPolygons[i], false, false ); + } +/* + // Draw front facing debug polygons + for (int i=0; i<nNumPolygons; i++) + { + DrawDebugPolygon( nNumVertices[i], vClippedPolygons[i], true, bNearPlane && (i == nNumPolygons-1) ); + } +*/ + } + + if ( r_flashlightclip.GetBool() && bUseStencil ) + { +/* + // The traditional settings... + + // Set up to set stencil bit on front facing polygons + pRenderContext->SetStencilEnable( true ); + pRenderContext->SetStencilFailOperation( STENCILOPERATION_KEEP ); // Stencil fails + pRenderContext->SetStencilZFailOperation( STENCILOPERATION_KEEP ); // Stencil passes but depth fails + pRenderContext->SetStencilPassOperation( STENCILOPERATION_REPLACE ); // Z and stencil both pass + pRenderContext->SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_ALWAYS ); // Stencil always pass + pRenderContext->SetStencilReferenceValue( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); + pRenderContext->SetStencilTestMask( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); + pRenderContext->SetStencilWriteMask( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); // Bit mask which is specific to this shadow +*/ + + // Just blast front faces into the stencil buffer no matter what... + pRenderContext->SetStencilEnable( true ); + pRenderContext->SetStencilFailOperation( STENCILOPERATION_REPLACE ); // Stencil fails + pRenderContext->SetStencilZFailOperation( STENCILOPERATION_REPLACE ); // Stencil passes but depth fails + pRenderContext->SetStencilPassOperation( STENCILOPERATION_REPLACE ); // Z and stencil both pass + pRenderContext->SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_ALWAYS ); // Stencil always pass + pRenderContext->SetStencilReferenceValue( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); + pRenderContext->SetStencilTestMask( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); + pRenderContext->SetStencilWriteMask( m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit ); // Bit mask which is specific to this shadow + + for ( int i=0; i<nNumPolygons; i++ ) // Set the stencil bit on front facing + { + DrawPolygonToStencil( pRenderContext, nNumVertices[i], vClippedPolygons[i], true, false ); + } + +/* + pRenderContext->SetStencilReferenceValue( 0x00000000 ); // All bits cleared + + for (int i=0; i<nNumPolygons; i++) // Clear the stencil bit on back facing + { + DrawPolygonToStencil( nNumVertices[i], vClippedPolygons[i], false, false ); + } +*/ + + pRenderContext->SetStencilEnable( false ); + } +} + +//--------------------------------------------------------------------------------------- +// Set masking stencil bits for all flashlights +//--------------------------------------------------------------------------------------- +void CShadowMgr::SetFlashlightStencilMasks( bool bDoMasking ) +{ + VPROF_BUDGET( "CShadowMgr::RenderFlashlights", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + // Bail out if we're not doing any of these optimizations + if ( !( r_flashlightclip.GetBool() || r_flashlightscissor.GetBool()) ) + return; + + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + + SetStencilAndScissor( pRenderContext, flashlightInfo, m_Shadows[flashlightInfo.m_Shadow].m_pFlashlightDepthTexture != NULL ); + } +} + + +void CShadowMgr::DisableStencilAndScissorMasking( IMatRenderContext *pRenderContext ) +{ + if ( r_flashlightclip.GetBool() ) + { + pRenderContext->SetStencilEnable( false ); + } + + // Scissor even if we're not shadow depth mapping + if ( r_flashlightscissor.GetBool() ) + { + pRenderContext->SetScissorRect( -1, -1, -1, -1, false ); + } +} + + +//--------------------------------------------------------------------------------------- +// Enable/Disable masking based on stencil bit +//--------------------------------------------------------------------------------------- +void CShadowMgr::EnableStencilAndScissorMasking( IMatRenderContext *pRenderContext, const FlashlightInfo_t &flashlightInfo, bool bDoMasking ) +{ + // Bail out if we're not doing any of these optimizations + if ( !( r_flashlightclip.GetBool() || r_flashlightscissor.GetBool()) || !bDoMasking ) + return; + + // Only turn on scissor when rendering to the back buffer + if ( pRenderContext->GetRenderTarget() == NULL ) + { + // Only do the stencil optimization when shadow depth mapping + if ( r_flashlightclip.GetBool() && m_Shadows[flashlightInfo.m_Shadow].m_pFlashlightDepthTexture != NULL ) + { + unsigned char ucShadowStencilBit = m_Shadows[flashlightInfo.m_Shadow].m_ucShadowStencilBit; + + pRenderContext->SetStencilEnable( true ); + pRenderContext->SetStencilFailOperation( STENCILOPERATION_KEEP ); // Stencil fails + pRenderContext->SetStencilZFailOperation( STENCILOPERATION_KEEP ); // Stencil passes but depth fails + pRenderContext->SetStencilPassOperation( STENCILOPERATION_KEEP ); // Z and stencil both pass + + pRenderContext->SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_EQUAL ); // Bit must be set + pRenderContext->SetStencilReferenceValue( ucShadowStencilBit ); // Specific bit + pRenderContext->SetStencilTestMask( ucShadowStencilBit ); // Specific bit + pRenderContext->SetStencilWriteMask( 0x00000000 ); + } + + // Scissor even if we're not shadow depth mapping + if ( r_flashlightscissor.GetBool() && flashlightInfo.m_FlashlightState.m_bScissor ) + { + pRenderContext->SetScissorRect( flashlightInfo.m_FlashlightState.m_nLeft, flashlightInfo.m_FlashlightState.m_nTop, + flashlightInfo.m_FlashlightState.m_nRight, flashlightInfo.m_FlashlightState.m_nBottom, true ); + } + } + else // disable + { + DisableStencilAndScissorMasking( pRenderContext ); + } +} + + +//--------------------------------------------------------------------------------------- +// Sets the render states necessary to render a flashlight +//--------------------------------------------------------------------------------------- +void CShadowMgr::SetFlashlightRenderState( ShadowHandle_t handle ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + if ( handle == SHADOW_HANDLE_INVALID ) + { + pRenderContext->SetFlashlightMode( false ); + return; + } + + const Shadow_t &shadow = m_Shadows[handle]; + pRenderContext->SetFlashlightMode( true ); + const FlashlightInfo_t &flashlightInfo = m_FlashlightStates[ shadow.m_FlashlightHandle ]; + pRenderContext->SetFlashlightStateEx( flashlightInfo.m_FlashlightState, shadow.m_WorldToShadow, shadow.m_pFlashlightDepthTexture ); +} + + +//--------------------------------------------------------------------------------------- +// Render all of the world and displacement surfaces that need to be drawn for flashlights +//--------------------------------------------------------------------------------------- +void CShadowMgr::RenderFlashlights( bool bDoMasking, const VMatrix* pModelToWorld ) +{ +#ifndef SWDS + VPROF_BUDGET( "CShadowMgr::RenderFlashlights", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + if( r_flashlightrender.GetBool()==false ) + return; + + // Draw the projective light sources, which get their material + // from the surface and not from the shadow. + // Tell the materialsystem that we are drawing additive flashlight lighting. + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + bool bWireframe = r_shadowwireframe.GetBool(); + + CMatRenderContextPtr pRenderContext( materials ); + PIXEVENT( pRenderContext, "CShadowMgr::RenderFlashlights" ); + + pRenderContext->SetFlashlightMode( true ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + CMaterialsBuckets<SurfaceHandle_t> &materialBuckets = flashlightInfo.m_MaterialBuckets; + CMaterialsBuckets<SurfaceHandle_t>::SortIDHandle_t sortIDHandle = materialBuckets.GetFirstUsedSortID(); + if ( sortIDHandle == materialBuckets.InvalidSortIDHandle() ) + continue; + + pRenderContext->SetFlashlightStateEx(flashlightInfo.m_FlashlightState, m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow, m_Shadows[flashlightInfo.m_Shadow].m_pFlashlightDepthTexture ); + EnableStencilAndScissorMasking( pRenderContext, flashlightInfo, bDoMasking ); + + for( ; sortIDHandle != materialBuckets.InvalidSortIDHandle(); + sortIDHandle = materialBuckets.GetNextUsedSortID( sortIDHandle ) ) + { + int sortID = materialBuckets.GetSortID( sortIDHandle ); + + if( bWireframe ) + { + pRenderContext->Bind( g_materialWorldWireframe ); + } + else + { + pRenderContext->Bind( materialSortInfoArray[sortID].material ); + pRenderContext->BindLightmapPage( materialSortInfoArray[sortID].lightmapPageID ); + } + + CMaterialsBuckets<SurfaceHandle_t>::ElementHandle_t elemHandle; + // Figure out how many indices we have. + int numIndices = 0; + for( elemHandle = materialBuckets.GetElementListHead( sortID ); + elemHandle != materialBuckets.InvalidElementHandle(); + elemHandle = materialBuckets.GetElementListNext( elemHandle ) ) + { + SurfaceHandle_t surfID = materialBuckets.GetElement( elemHandle ); + if( !SurfaceHasDispInfo( surfID ) ) + { + numIndices += 3 * ( MSurf_VertCount( surfID ) - 2 ); + } + } + + if( numIndices > 0 ) + { + // NOTE: If we ever need to make this faster, we could get larger + // batches here. + // Draw this batch. +#if NEWMESH + IIndexBuffer *pIndexBuffer = pRenderContext->GetDynamicIndexBuffer( MATERIAL_INDEX_FORMAT_16BIT ); + CIndexBufferBuilder indexBufferBuilder; + indexBufferBuilder.Begin( pIndexBuffer, numIndices ); +#else + IMesh *pMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[sortID], 0 ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 0, numIndices ); +#endif + for( elemHandle = materialBuckets.GetElementListHead( sortID ); + elemHandle != materialBuckets.InvalidElementHandle(); + elemHandle = materialBuckets.GetElementListNext( elemHandle ) ) + { + SurfaceHandle_t surfID = materialBuckets.GetElement( elemHandle ); + if( !SurfaceHasDispInfo( surfID ) ) + { +#if NEWMESH + BuildIndicesForWorldSurface( indexBufferBuilder, surfID, host_state.worldbrush ); +#else + BuildIndicesForWorldSurface( meshBuilder, surfID, host_state.worldbrush ); +#endif + } + } + // close out the index buffer +#if NEWMESH + indexBufferBuilder.End( false ); // haven't tested this one yet (flashlights) + // FIXME: IMaterial::GetVertexFormat() should do this stripping (add a separate 'SupportsCompression' accessor) + VertexFormat_t vertexFormat = materialSortInfoArray[sortID].material->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED; + pRenderContext->BindVertexBuffer( 0, g_WorldStaticMeshes[sortID], 0, materialSortInfoArray[sortID].material->GetVertexFormat() ); // hack fixme. . . use currently bound material format instead of passing in? + pRenderContext->BindIndexBuffer( pIndexBuffer, 0 ); + pRenderContext->Draw( MATERIAL_TRIANGLES, 0, numIndices ); +#else + meshBuilder.End( false, true ); +#endif + } + + // NOTE: If we ever need to make this faster, we could get larger batches here. + // Draw displacements + for( elemHandle = materialBuckets.GetElementListHead( sortID ); + elemHandle != materialBuckets.InvalidElementHandle(); + elemHandle = materialBuckets.GetElementListNext( elemHandle ) ) + { + SurfaceHandle_t surfID = materialBuckets.GetElement( elemHandle ); + if( SurfaceHasDispInfo( surfID ) ) + { + CDispInfo *pDisp = ( CDispInfo * )MSurf_DispInfo( surfID ); + Assert( pDisp ); + if( bWireframe ) + { + pDisp->SpecifyDynamicMesh(); + } + else + { + Assert( pDisp && pDisp->m_pMesh && pDisp->m_pMesh->m_pMesh ); + pDisp->m_pMesh->m_pMesh->Draw( pDisp->m_iIndexOffset, pDisp->m_nIndices ); + } + } + } + } + } + + // Tell the materialsystem that we are finished drawing additive flashlight lighting. + pRenderContext->SetFlashlightMode( false ); + + // Turn off stencil masking + DisableStencilAndScissorMasking( pRenderContext ); +#endif +} + +const Frustum_t &CShadowMgr::GetFlashlightFrustum( ShadowHandle_t handle ) +{ + Assert( m_Shadows[handle].m_Flags & SHADOW_FLASHLIGHT ); + Assert( m_Shadows[handle].m_FlashlightHandle != m_Shadows.InvalidIndex() ); + return m_FlashlightStates[m_Shadows[handle].m_FlashlightHandle].m_Frustum; +} + +const FlashlightState_t &CShadowMgr::GetFlashlightState( ShadowHandle_t handle ) +{ + Assert( m_Shadows[handle].m_Flags & SHADOW_FLASHLIGHT ); + Assert( m_Shadows[handle].m_FlashlightHandle != m_Shadows.InvalidIndex() ); + return m_FlashlightStates[m_Shadows[handle].m_FlashlightHandle].m_FlashlightState; +} + +void CShadowMgr::DrawFlashlightDecals( int sortGroup, bool bDoMasking ) +{ + VPROF_BUDGET( "CShadowMgr::DrawFlashlightDecals", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->SetFlashlightMode( true ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + pRenderContext->SetFlashlightState(flashlightInfo.m_FlashlightState, m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow ); + + EnableStencilAndScissorMasking( pRenderContext, flashlightInfo, bDoMasking ); + + DecalSurfaceDraw( pRenderContext, sortGroup ); + } + + // Tell the materialsystem that we are finished drawing additive flashlight lighting. + pRenderContext->SetFlashlightMode( false ); + + // Turn off stencil masking + DisableStencilAndScissorMasking( pRenderContext ); +} + +void CShadowMgr::DrawFlashlightDecalsOnDisplacements( int sortGroup, CDispInfo *visibleDisps[MAX_MAP_DISPINFO], int nVisibleDisps, bool bDoMasking ) +{ + VPROF_BUDGET( "CShadowMgr::DrawFlashlightDecalsOnDisplacements", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->SetFlashlightMode( true ); + + DispInfo_BatchDecals( visibleDisps, nVisibleDisps ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + pRenderContext->SetFlashlightState(flashlightInfo.m_FlashlightState, m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow ); + + EnableStencilAndScissorMasking( pRenderContext, flashlightInfo, bDoMasking ); + + DispInfo_DrawDecals( visibleDisps, nVisibleDisps ); + } + + // Tell the materialsystem that we are finished drawing additive flashlight lighting. + pRenderContext->SetFlashlightMode( false ); + + // Turn off stencil masking + DisableStencilAndScissorMasking( pRenderContext ); +} + +void CShadowMgr::DrawFlashlightDecalsOnSingleSurface( SurfaceHandle_t surfID, bool bDoMasking ) +{ + VPROF_BUDGET( "CShadowMgr::DrawFlashlightDecalsOnSingleSurface", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->SetFlashlightMode( true ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + pRenderContext->SetFlashlightState(flashlightInfo.m_FlashlightState, m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow ); + + EnableStencilAndScissorMasking( pRenderContext, flashlightInfo, bDoMasking ); + + DrawDecalsOnSingleSurface( pRenderContext, surfID ); + } + + // Tell the materialsystem that we are finished drawing additive flashlight lighting. + pRenderContext->SetFlashlightMode( false ); + + // Turn off stencil masking + DisableStencilAndScissorMasking( pRenderContext ); +} + +void CShadowMgr::DrawFlashlightOverlays( int nSortGroup, bool bDoMasking ) +{ + VPROF_BUDGET( "CShadowMgr::DrawFlashlightOverlays", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( IsX360() || r_flashlight_version2.GetInt() ) + return; + + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + if ( flashlightID == m_FlashlightStates.InvalidIndex() ) + return; + + if ( r_flashlightrender.GetBool()==false ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->SetFlashlightMode( true ); + + for( ; + flashlightID != m_FlashlightStates.InvalidIndex(); + flashlightID = m_FlashlightStates.Next( flashlightID ) ) + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[flashlightID]; + pRenderContext->SetFlashlightState(flashlightInfo.m_FlashlightState, m_Shadows[flashlightInfo.m_Shadow].m_WorldToShadow ); + + EnableStencilAndScissorMasking( pRenderContext, flashlightInfo, bDoMasking ); + + OverlayMgr()->RenderOverlays( nSortGroup ); + } + + // Tell the materialsystem that we are finished drawing additive flashlight lighting. + pRenderContext->SetFlashlightMode( false ); + + // Turn off stencil masking + DisableStencilAndScissorMasking( pRenderContext ); +} + +void CShadowMgr::DrawFlashlightDepthTexture( ) +{ + int i = 0; + FlashlightHandle_t flashlightID = m_FlashlightStates.Head(); + while ( flashlightID != m_FlashlightStates.InvalidIndex() ) // Count up the shadows + { + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[ flashlightID ]; + + if( m_Shadows[ flashlightInfo.m_Shadow ].m_pFlashlightDepthTexture ) + { + bool foundVar; + IMaterial *pMaterial = materials->FindMaterial( "debug/showz", TEXTURE_GROUP_OTHER, true ); + IMaterialVar *BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + if (!foundVar) + return; + IMaterialVar *FrameVar = pMaterial->FindVar( "$frame", &foundVar, false ); + if (!foundVar) + return; + + float w = 256.0f, h = 256.0f; + float wOffset = (i % 2) * 256.0f; // Even|Odd go left|right + float hOffset = (i / 2) * 256.0f; // Rows of two + + BaseTextureVar->SetTextureValue( m_Shadows[ flashlightInfo.m_Shadow ].m_pFlashlightDepthTexture ); + FrameVar->SetIntValue( 0 ); + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->Bind( pMaterial ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Position3f( wOffset, hOffset, 0.0f ); +#ifdef DX_TO_GL_ABSTRACTION + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); // Posix is rotated due to render target origin differences +#else + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); +#endif + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( wOffset + w, hOffset, 0.0f ); +#ifdef DX_TO_GL_ABSTRACTION + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); +#else + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); +#endif + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( wOffset + w, hOffset + h, 0.0f ); +#ifdef DX_TO_GL_ABSTRACTION + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); +#else + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); +#endif + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( wOffset, hOffset + h, 0.0f ); +#ifdef DX_TO_GL_ABSTRACTION + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); +#else + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); +#endif + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + i++; + } + + flashlightID = m_FlashlightStates.Next( flashlightID ); + } +} + +void CShadowMgr::AddFlashlightRenderable( ShadowHandle_t shadowHandle, IClientRenderable *pRenderable ) +{ + Shadow_t &shadow = m_Shadows[ shadowHandle ]; + FlashlightInfo_t &flashlightInfo = m_FlashlightStates[ shadow.m_FlashlightHandle ]; + + if( pRenderable->GetModelInstance() != MODEL_INSTANCE_INVALID ) + { + flashlightInfo.m_Renderables.AddToTail( pRenderable ); + } +} |