From e8f0058c0d43e8b9f769b6d28881bfbb1ae9c1cc Mon Sep 17 00:00:00 2001 From: celisej567 Date: Sun, 28 Aug 2022 19:39:38 +0300 Subject: [PATCH] Create clientshadowmgr.cpp --- client/clientshadowmgr.cpp | 4917 ++++++++++++++++++++++++++++++++++++ 1 file changed, 4917 insertions(+) create mode 100644 client/clientshadowmgr.cpp diff --git a/client/clientshadowmgr.cpp b/client/clientshadowmgr.cpp new file mode 100644 index 0000000..3f0ebd5 --- /dev/null +++ b/client/clientshadowmgr.cpp @@ -0,0 +1,4917 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +// Interface to the client system responsible for dealing with shadows +// +// Boy is this complicated. OK, lets talk about how this works at the moment +// +// The ClientShadowMgr contains all of the highest-level state for rendering +// shadows, and it controls the ShadowMgr in the engine which is the central +// clearing house for rendering shadows. +// +// There are two important types of objects with respect to shadows: +// the shadow receiver, and the shadow caster. How is the association made +// between casters + the receivers? Turns out it's done slightly differently +// depending on whether the receiver is the world, or if it's an entity. +// +// In the case of the world, every time the engine's ProjectShadow() is called, +// any previous receiver state stored (namely, which world surfaces are +// receiving shadows) are cleared. Then, when ProjectShadow is called, +// the engine iterates over all nodes + leaves within the shadow volume and +// marks front-facing surfaces in them as potentially being affected by the +// shadow. Later on, if those surfaces are actually rendered, the surfaces +// are clipped by the shadow volume + rendered. +// +// In the case of entities, there are slightly different methods depending +// on whether the receiver is a brush model or a studio model. However, there +// are a couple central things that occur with both. +// +// Every time a shadow caster is moved, the ClientLeafSystem's ProjectShadow +// method is called to tell it to remove the shadow from all leaves + all +// renderables it's currently associated with. Then it marks each leaf in the +// shadow volume as being affected by that shadow, and it marks every renderable +// in that volume as being potentially affected by the shadow (the function +// AddShadowToRenderable is called for each renderable in leaves affected +// by the shadow volume). +// +// Every time a shadow receiver is moved, the ClientLeafSystem first calls +// RemoveAllShadowsFromRenderable to have it clear out its state, and then +// the ClientLeafSystem calls AddShadowToRenderable() for all shadows in all +// leaves the renderable has moved into. +// +// Now comes the difference between brush models + studio models. In the case +// of brush models, when a shadow is added to the studio model, it's done in +// the exact same way as for the world. Surfaces on the brush model are marked +// as potentially being affected by the shadow, and if those surfaces are +// rendered, the surfaces are clipped to the shadow volume. When ProjectShadow() +// is called, turns out the same operation that removes the shadow that moved +// from the world surfaces also works to remove the shadow from brush surfaces. +// +// In the case of studio models, we need a separate operation to remove +// the shadow from all studio models +//===========================================================================// + + +#include "cbase.h" +#include "engine/ishadowmgr.h" +#include "model_types.h" +#include "bitmap/imageformat.h" +#include "materialsystem/imaterialproxy.h" +#include "materialsystem/imaterialvar.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imesh.h" +#include "materialsystem/itexture.h" +#include "bsptreedata.h" +#include "utlmultilist.h" +#include "collisionutils.h" +#include "iviewrender.h" +#include "ivrenderview.h" +#include "tier0/vprof.h" +#include "engine/ivmodelinfo.h" +#include "view_shared.h" +#include "engine/ivdebugoverlay.h" +#include "engine/IStaticPropMgr.h" +#include "datacache/imdlcache.h" +#include "viewrender.h" +#include "tier0/icommandline.h" +#include "vstdlib/jobthread.h" +#include "toolframework_client.h" +#include "bonetoworldarray.h" +#include "cmodel.h" +#ifdef MAPBASE +#include "renderparm.h" +#endif +#ifdef ASW_PROJECTED_TEXTURES +#include "flashlighteffect.h" +#endif +#ifdef DYNAMIC_RTT_SHADOWS +#include "debugoverlay_shared.h" +#include "worldlight.h" +#endif + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +const char* maxprojlights = CommandLine()->ParmValue("-ptmax", "10"); +ConVar pr_max("pr_max", maxprojlights, 0, "Changes maximum of enabled env_projectedtexture and everything related to it at one time."); + +static ConVar r_flashlightdrawfrustum( "r_flashlightdrawfrustum", "0" ); +static ConVar r_flashlightmodels( "r_flashlightmodels", "1" ); +static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" ); +#ifdef ASW_PROJECTED_TEXTURES +static ConVar r_shadow_lightpos_lerptime( "r_shadow_lightpos_lerptime", "0.5" ); +static ConVar r_shadowfromworldlights_debug( "r_shadowfromworldlights_debug", "0", FCVAR_CHEAT ); +static ConVar r_shadow_shortenfactor( "r_shadow_shortenfactor", "2" , 0, "Makes shadows cast from local lights shorter" ); +static ConVar r_shadow_mincastintensity( "r_shadow_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0 ); +#endif +static ConVar r_flashlight_version2( "r_flashlight_version2", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); + +ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" ); + +#if defined( _X360 ) +ConVar r_flashlightdepthres( "r_flashlightdepthres", "512" ); +#else +const char* projlightres = CommandLine()->ParmValue("-ptr", "2048"); //Параметр запуска обазначающий разрешение env_projectedtexture + +ConVar r_flashlightdepthres("r_flashlightdepthres", projlightres, 0, "Changes resolution of env_projectedtexture and everything related to it."); +#endif + +#ifdef ASW_PROJECTED_TEXTURES +ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "1" ); +#else +ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "0" ); +#endif + +#ifdef _WIN32 +#pragma warning( disable: 4701 ) +#endif + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + + +//----------------------------------------------------------------------------- +// A texture allocator used to batch textures together +// At the moment, the implementation simply allocates blocks of max 256x256 +// and each block stores an array of uniformly-sized textures +//----------------------------------------------------------------------------- +typedef unsigned short TextureHandle_t; +enum +{ + INVALID_TEXTURE_HANDLE = (TextureHandle_t)~0 +}; + +class CTextureAllocator +{ +public: + // Initialize the allocator with something that knows how to refresh the bits + void Init(); + void Shutdown(); + + // Resets the allocator + void Reset(); + + // Deallocates everything + void DeallocateAllTextures(); + + // Allocate, deallocate texture + TextureHandle_t AllocateTexture( int w, int h ); + void DeallocateTexture( TextureHandle_t h ); + + // Mark texture as being used... (return true if re-render is needed) + bool UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea ); + bool HasValidTexture( TextureHandle_t h ); + + // Advance frame... + void AdvanceFrame(); + + // Get at the location of the texture + void GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h ); + + // Get at the texture it's a part of + ITexture *GetTexture(); + + // Get at the total texture size. + void GetTotalTextureSize( int& w, int& h ); + + void DebugPrintCache( void ); + +private: + typedef unsigned short FragmentHandle_t; + + enum + { + INVALID_FRAGMENT_HANDLE = (FragmentHandle_t)~0, + TEXTURE_PAGE_SIZE = 1024, + MAX_TEXTURE_POWER = 8, +#if !defined( _X360 ) + MIN_TEXTURE_POWER = 4, +#else + MIN_TEXTURE_POWER = 5, // per resolve requirements to ensure 32x32 aligned offsets +#endif + MAX_TEXTURE_SIZE = (1 << MAX_TEXTURE_POWER), + MIN_TEXTURE_SIZE = (1 << MIN_TEXTURE_POWER), + BLOCK_SIZE = MAX_TEXTURE_SIZE, + BLOCKS_PER_ROW = (TEXTURE_PAGE_SIZE / MAX_TEXTURE_SIZE), + BLOCK_COUNT = (BLOCKS_PER_ROW * BLOCKS_PER_ROW), + }; + + struct TextureInfo_t + { + FragmentHandle_t m_Fragment; + unsigned short m_Size; + unsigned short m_Power; + }; + + struct FragmentInfo_t + { + unsigned short m_Block; + unsigned short m_Index; + TextureHandle_t m_Texture; + + // Makes sure we don't overflow + unsigned int m_FrameUsed; + }; + + struct BlockInfo_t + { + unsigned short m_FragmentPower; + }; + + struct Cache_t + { + unsigned short m_List; + }; + + // Adds a block worth of fragments to the LRU + void AddBlockToLRU( int block ); + + // Unlink fragment from cache + void UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment ); + + // Mark something as being used (MRU).. + void MarkUsed( FragmentHandle_t fragment ); + + // Mark something as being unused (LRU).. + void MarkUnused( FragmentHandle_t fragment ); + + // Disconnect texture from fragment + void DisconnectTextureFromFragment( FragmentHandle_t f ); + + // Returns the size of a particular fragment + int GetFragmentPower( FragmentHandle_t f ) const; + + // Stores the actual texture we're writing into + CTextureReference m_TexturePage; + + CUtlLinkedList< TextureInfo_t, TextureHandle_t > m_Textures; + CUtlMultiList< FragmentInfo_t, FragmentHandle_t > m_Fragments; + + Cache_t m_Cache[MAX_TEXTURE_POWER+1]; + BlockInfo_t m_Blocks[BLOCK_COUNT]; + unsigned int m_CurrentFrame; +}; + +//----------------------------------------------------------------------------- +// Allocate/deallocate the texture page +//----------------------------------------------------------------------------- +void CTextureAllocator::Init() +{ + for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.InvalidIndex(); + } + +#if !defined( _X360 ) + // don't need depth buffer for shadows + m_TexturePage.InitRenderTarget( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" ); +#else + // unfortunate explicit management required for this render target + // 32bpp edram is only largest shadow fragment, but resolved to actual shadow atlas + // because full-res 1024x1024 shadow buffer is too large for EDRAM + m_TexturePage.InitRenderTargetTexture( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" ); + + // edram footprint is only 256x256x4 = 256K + m_TexturePage.InitRenderTargetSurface( MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, IMAGE_FORMAT_ARGB8888, false ); + + // due to texture/surface size mismatch, ensure texture page is entirely cleared translucent + // otherwise border artifacts at edge of shadows due to pixel shader averaging of unwanted bits + m_TexturePage->ClearTexture( 0, 0, 0, 0 ); +#endif +} + +void CTextureAllocator::Shutdown() +{ + m_TexturePage.Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Initialize the allocator with something that knows how to refresh the bits +//----------------------------------------------------------------------------- +void CTextureAllocator::Reset() +{ + DeallocateAllTextures(); + + m_Textures.EnsureCapacity(256); + m_Fragments.EnsureCapacity(256); + + // Set up the block sizes.... + // FIXME: Improve heuristic?!? +#if !defined( _X360 ) + m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-4; // 128 cells at ExE resolution +#else + m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution +#endif + m_Blocks[1].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution + m_Blocks[2].m_FragmentPower = MAX_TEXTURE_POWER-2; // 32 cells at CxC resolution + m_Blocks[3].m_FragmentPower = MAX_TEXTURE_POWER-2; + m_Blocks[4].m_FragmentPower = MAX_TEXTURE_POWER-1; // 24 cells at BxB resolution + m_Blocks[5].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[6].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[7].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[8].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[9].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[10].m_FragmentPower = MAX_TEXTURE_POWER; // 6 cells at AxA resolution + m_Blocks[11].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[12].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[13].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[14].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[15].m_FragmentPower = MAX_TEXTURE_POWER; + + // Initialize the LRU + int i; + for ( i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.CreateList(); + } + + // Now that the block sizes are allocated, create LRUs for the various block sizes + for ( i = 0; i < BLOCK_COUNT; ++i) + { + // Initialize LRU + AddBlockToLRU( i ); + } + + m_CurrentFrame = 0; +} + +void CTextureAllocator::DeallocateAllTextures() +{ + m_Textures.Purge(); + m_Fragments.Purge(); + for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.InvalidIndex(); + } +} + + +//----------------------------------------------------------------------------- +// Dump the state of the cache to debug out +//----------------------------------------------------------------------------- +void CTextureAllocator::DebugPrintCache( void ) +{ + // For each fragment + int nNumFragments = m_Fragments.TotalCount(); + int nNumInvalidFragments = 0; + + Warning("Fragments (%d):\n===============\n", nNumFragments); + + for ( int f = 0; f < nNumFragments; f++ ) + { + if ( ( m_Fragments[f].m_FrameUsed != 0 ) && ( m_Fragments[f].m_Texture != INVALID_TEXTURE_HANDLE ) ) + Warning("Fragment %d, Block: %d, Index: %d, Texture: %d Frame Used: %d\n", f, m_Fragments[f].m_Block, m_Fragments[f].m_Index, m_Fragments[f].m_Texture, m_Fragments[f].m_FrameUsed ); + else + nNumInvalidFragments++; + } + + Warning("Invalid Fragments: %d\n", nNumInvalidFragments); + +// for ( int c = 0; c <= MAX_TEXTURE_POWER; ++c ) +// { +// Warning("Cache Index (%d)\n", m_Cache[c].m_List); +// } + +} + + +//----------------------------------------------------------------------------- +// Adds a block worth of fragments to the LRU +//----------------------------------------------------------------------------- +void CTextureAllocator::AddBlockToLRU( int block ) +{ + int power = m_Blocks[block].m_FragmentPower; + int size = (1 << power); + + // Compute the number of fragments in this block + int fragmentCount = MAX_TEXTURE_SIZE / size; + fragmentCount *= fragmentCount; + + // For each fragment, indicate which block it's a part of (and the index) + // and then stick in at the top of the LRU + while (--fragmentCount >= 0 ) + { + FragmentHandle_t f = m_Fragments.Alloc( ); + m_Fragments[f].m_Block = block; + m_Fragments[f].m_Index = fragmentCount; + m_Fragments[f].m_Texture = INVALID_TEXTURE_HANDLE; + m_Fragments[f].m_FrameUsed = 0xFFFFFFFF; + m_Fragments.LinkToHead( m_Cache[power].m_List, f ); + } +} + + +//----------------------------------------------------------------------------- +// Unlink fragment from cache +//----------------------------------------------------------------------------- +void CTextureAllocator::UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment ) +{ + m_Fragments.Unlink( cache.m_List, fragment); +} + + +//----------------------------------------------------------------------------- +// Mark something as being used (MRU).. +//----------------------------------------------------------------------------- +void CTextureAllocator::MarkUsed( FragmentHandle_t fragment ) +{ + int block = m_Fragments[fragment].m_Block; + int power = m_Blocks[block].m_FragmentPower; + + // Hook it at the end of the LRU + Cache_t& cache = m_Cache[power]; + m_Fragments.LinkToTail( cache.m_List, fragment ); + m_Fragments[fragment].m_FrameUsed = m_CurrentFrame; +} + + +//----------------------------------------------------------------------------- +// Mark something as being unused (LRU).. +//----------------------------------------------------------------------------- +void CTextureAllocator::MarkUnused( FragmentHandle_t fragment ) +{ + int block = m_Fragments[fragment].m_Block; + int power = m_Blocks[block].m_FragmentPower; + + // Hook it at the end of the LRU + Cache_t& cache = m_Cache[power]; + m_Fragments.LinkToHead( cache.m_List, fragment ); +} + + +//----------------------------------------------------------------------------- +// Allocate, deallocate texture +//----------------------------------------------------------------------------- +TextureHandle_t CTextureAllocator::AllocateTexture( int w, int h ) +{ + // Implementational detail for now + Assert( w == h ); + + // Clamp texture size + if (w < MIN_TEXTURE_SIZE) + w = MIN_TEXTURE_SIZE; + else if (w > MAX_TEXTURE_SIZE) + w = MAX_TEXTURE_SIZE; + + TextureHandle_t handle = m_Textures.AddToTail(); + m_Textures[handle].m_Fragment = INVALID_FRAGMENT_HANDLE; + m_Textures[handle].m_Size = w; + + // Find the power of two + int power = 0; + int size = 1; + while(size < w) + { + size <<= 1; + ++power; + } + Assert( size == w ); + + m_Textures[handle].m_Power = power; + + return handle; +} + +void CTextureAllocator::DeallocateTexture( TextureHandle_t h ) +{ +// Warning("Beginning of DeallocateTexture\n"); +// DebugPrintCache(); + + if (m_Textures[h].m_Fragment != INVALID_FRAGMENT_HANDLE) + { + MarkUnused(m_Textures[h].m_Fragment); + m_Fragments[m_Textures[h].m_Fragment].m_FrameUsed = 0xFFFFFFFF; // non-zero frame + DisconnectTextureFromFragment( m_Textures[h].m_Fragment ); + } + m_Textures.Remove(h); + +// Warning("End of DeallocateTexture\n"); +// DebugPrintCache(); +} + + +//----------------------------------------------------------------------------- +// Disconnect texture from fragment +//----------------------------------------------------------------------------- +void CTextureAllocator::DisconnectTextureFromFragment( FragmentHandle_t f ) +{ +// Warning( "Beginning of DisconnectTextureFromFragment\n" ); +// DebugPrintCache(); + + FragmentInfo_t& info = m_Fragments[f]; + if (info.m_Texture != INVALID_TEXTURE_HANDLE) + { + m_Textures[info.m_Texture].m_Fragment = INVALID_FRAGMENT_HANDLE; + info.m_Texture = INVALID_TEXTURE_HANDLE; + } + + +// Warning( "End of DisconnectTextureFromFragment\n" ); +// DebugPrintCache(); +} + + +//----------------------------------------------------------------------------- +// Do we have a valid texture assigned? +//----------------------------------------------------------------------------- +bool CTextureAllocator::HasValidTexture( TextureHandle_t h ) +{ + TextureInfo_t& info = m_Textures[h]; + FragmentHandle_t currentFragment = info.m_Fragment; + return (currentFragment != INVALID_FRAGMENT_HANDLE); +} + + +//----------------------------------------------------------------------------- +// Mark texture as being used... +//----------------------------------------------------------------------------- +bool CTextureAllocator::UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea ) +{ +// Warning( "Top of UseTexture\n" ); +// DebugPrintCache(); + + TextureInfo_t& info = m_Textures[h]; + + // spin up to the best fragment size + int nDesiredPower = MIN_TEXTURE_POWER; + int nDesiredWidth = MIN_TEXTURE_SIZE; + while ( (nDesiredWidth * nDesiredWidth) < flArea ) + { + if ( nDesiredPower >= info.m_Power ) + { + nDesiredPower = info.m_Power; + break; + } + + ++nDesiredPower; + nDesiredWidth <<= 1; + } + + // If we've got a valid fragment for this texture, no worries! + int nCurrentPower = -1; + FragmentHandle_t currentFragment = info.m_Fragment; + if (currentFragment != INVALID_FRAGMENT_HANDLE) + { + // If the current fragment is at or near the desired power, we're done + nCurrentPower = GetFragmentPower(info.m_Fragment); + Assert( nCurrentPower <= info.m_Power ); + bool bShouldKeepTexture = (!bWillRedraw) && (nDesiredPower < 8) && (nDesiredPower - nCurrentPower <= 1); + if ((nCurrentPower == nDesiredPower) || bShouldKeepTexture) + { + // Move to the back of the LRU + MarkUsed( currentFragment ); + return false; + } + } + +// Warning( "\n\nUseTexture B\n" ); +// DebugPrintCache(); + + // Grab the LRU fragment from the appropriate cache + // If that fragment is connected to a texture, disconnect it. + int power = nDesiredPower; + + FragmentHandle_t f = INVALID_FRAGMENT_HANDLE; + bool done = false; + while (!done && power >= 0) + { + f = m_Fragments.Head( m_Cache[power].m_List ); + + // This represents an overflow condition (used too many textures of + // the same size in a single frame). It that happens, just use a texture + // of lower res. + if ( (f != m_Fragments.InvalidIndex()) && (m_Fragments[f].m_FrameUsed != m_CurrentFrame) ) + { + done = true; + } + else + { + --power; + } + } + + +// Warning( "\n\nUseTexture C\n" ); +// DebugPrintCache(); + + // Ok, lets see if we're better off than we were... + if (currentFragment != INVALID_FRAGMENT_HANDLE) + { + if (power <= nCurrentPower) + { + // Oops... we're not. Let's leave well enough alone + // Move to the back of the LRU + MarkUsed( currentFragment ); + return false; + } + else + { + // Clear out the old fragment + DisconnectTextureFromFragment(currentFragment); + } + } + + if ( f == INVALID_FRAGMENT_HANDLE ) + { + return false; + } + + // Disconnect existing texture from this fragment (if necessary) + DisconnectTextureFromFragment(f); + + // Connnect new texture to this fragment + info.m_Fragment = f; + m_Fragments[f].m_Texture = h; + + // Move to the back of the LRU + MarkUsed( f ); + + // Indicate we need a redraw + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the size of a particular fragment +//----------------------------------------------------------------------------- +int CTextureAllocator::GetFragmentPower( FragmentHandle_t f ) const +{ + return m_Blocks[m_Fragments[f].m_Block].m_FragmentPower; +} + + +//----------------------------------------------------------------------------- +// Advance frame... +//----------------------------------------------------------------------------- +void CTextureAllocator::AdvanceFrame() +{ + // Be sure that this is called as infrequently as possible (i.e. once per frame, + // NOT once per view) to prevent cache thrash when rendering multiple views in a single frame + m_CurrentFrame++; +} + + +//----------------------------------------------------------------------------- +// Prepare to render into texture... +//----------------------------------------------------------------------------- +ITexture* CTextureAllocator::GetTexture() +{ + return m_TexturePage; +} + +//----------------------------------------------------------------------------- +// Get at the total texture size. +//----------------------------------------------------------------------------- +void CTextureAllocator::GetTotalTextureSize( int& w, int& h ) +{ + w = h = TEXTURE_PAGE_SIZE; +} + + +//----------------------------------------------------------------------------- +// Returns the rectangle the texture lives in.. +//----------------------------------------------------------------------------- +void CTextureAllocator::GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h ) +{ + TextureInfo_t& info = m_Textures[handle]; + Assert( info.m_Fragment != INVALID_FRAGMENT_HANDLE ); + + // Compute the position of the fragment in the page + FragmentInfo_t& fragment = m_Fragments[info.m_Fragment]; + int blockY = fragment.m_Block / BLOCKS_PER_ROW; + int blockX = fragment.m_Block - blockY * BLOCKS_PER_ROW; + + int fragmentSize = (1 << m_Blocks[fragment.m_Block].m_FragmentPower); + int fragmentsPerRow = BLOCK_SIZE / fragmentSize; + int fragmentY = fragment.m_Index / fragmentsPerRow; + int fragmentX = fragment.m_Index - fragmentY * fragmentsPerRow; + + x = blockX * BLOCK_SIZE + fragmentX * fragmentSize; + y = blockY * BLOCK_SIZE + fragmentY * fragmentSize; + w = fragmentSize; + h = fragmentSize; +} + + +//----------------------------------------------------------------------------- +// Defines how big of a shadow texture we should be making per caster... +//----------------------------------------------------------------------------- +#define TEXEL_SIZE_PER_CASTER_SIZE 2.0f +#define MAX_FALLOFF_AMOUNT 240 +#define MAX_CLIP_PLANE_COUNT 4 +#define SHADOW_CULL_TOLERANCE 0.5f + +static ConVar r_shadows( "r_shadows", "1" ); // hook into engine's cvars.. +static ConVar r_shadowmaxrendered("r_shadowmaxrendered", "32"); +static ConVar r_shadows_gamecontrol( "r_shadows_gamecontrol", "-1", FCVAR_CHEAT ); // hook into engine's cvars.. + +//----------------------------------------------------------------------------- +// The class responsible for dealing with shadows on the client side +// Oh, and let's take a moment and notice how happy Robin and John must be +// owing to the lack of space between this lovely comment and the class name =) +//----------------------------------------------------------------------------- +class CClientShadowMgr : public IClientShadowMgr +{ +public: + CClientShadowMgr(); + + virtual char const *Name() { return "CCLientShadowMgr"; } + + // Inherited from IClientShadowMgr + virtual bool Init(); + virtual void PostInit() {} + virtual void Shutdown(); + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity() {} + virtual void LevelShutdownPreEntity() {} + virtual void LevelShutdownPostEntity(); + + virtual bool IsPerFrame() { return true; } + + virtual void PreRender(); + virtual void Update( float frametime ) { } + virtual void PostRender() {} + + virtual void OnSave() {} + virtual void OnRestore() {} + virtual void SafeRemoveIfDesired() {} + + virtual ClientShadowHandle_t CreateShadow( ClientEntityHandle_t entity, int flags ); + virtual void DestroyShadow( ClientShadowHandle_t handle ); + + // Create flashlight (projected texture light source) + virtual ClientShadowHandle_t CreateFlashlight( const FlashlightState_t &lightState ); + virtual void UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &lightState ); + virtual void DestroyFlashlight( ClientShadowHandle_t shadowHandle ); + + // Update a shadow + virtual void UpdateProjectedTexture( ClientShadowHandle_t handle, bool force ); + + void ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius ); + + virtual void AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce ); + virtual void AddToDirtyShadowList( IClientRenderable *pRenderable, bool force ); + + // Marks the render-to-texture shadow as needing to be re-rendered + virtual void MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle ); + + // deals with shadows being added to shadow receivers + void AddShadowToReceiver( ClientShadowHandle_t handle, + IClientRenderable* pRenderable, ShadowReceiver_t type ); + + // deals with shadows being added to shadow receivers + void RemoveAllShadowsFromReceiver( IClientRenderable* pRenderable, ShadowReceiver_t type ); + + // Re-renders all shadow textures for shadow casters that lie in the leaf list + void ComputeShadowTextures( const CViewSetup &view, int leafCount, LeafIndex_t* pLeafList ); + + // Kicks off rendering into shadow depth maps (if any) + void ComputeShadowDepthTextures( const CViewSetup &view ); + + // Frees shadow depth textures for use in subsequent view/frame + void FreeShadowDepthTextures(); + + // Returns the shadow texture + ITexture* GetShadowTexture( unsigned short h ); + + // Returns shadow information + const ShadowInfo_t& GetShadowInfo( ClientShadowHandle_t h ); + + // Renders the shadow texture to screen... + void RenderShadowTexture( int w, int h ); + + // Sets the shadow direction + virtual void SetShadowDirection( const Vector& dir ); + const Vector &GetShadowDirection() const; + + // Sets the shadow color + virtual void SetShadowColor( unsigned char r, unsigned char g, unsigned char b ); + void GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const; + + // Sets the shadow distance + virtual void SetShadowDistance( float flMaxDistance ); + float GetShadowDistance( ) const; + + // Sets the screen area at which blobby shadows are always used + virtual void SetShadowBlobbyCutoffArea( float flMinArea ); + float GetBlobbyCutoffArea( ) const; + + // Set the darkness falloff bias + virtual void SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias ); + + void RestoreRenderState(); + + // Computes a rough bounding box encompassing the volume of the shadow +#ifdef DYNAMIC_RTT_SHADOWS + void ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ); +#else + void ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ); +#endif + + bool WillParentRenderBlobbyShadow( IClientRenderable *pRenderable ); + + // Are we the child of a shadow with render-to-texture? + bool ShouldUseParentShadow( IClientRenderable *pRenderable ); + + void SetShadowsDisabled( bool bDisabled ) + { + r_shadows_gamecontrol.SetValue( bDisabled != 1 ); + } + +#ifdef DYNAMIC_RTT_SHADOWS + // Toggle shadow casting from world light sources + virtual void SetShadowFromWorldLightsEnabled( bool bEnable ); + void SuppressShadowFromWorldLights( bool bSuppress ); + bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights && !m_bSuppressShadowFromWorldLights; } +#endif + +private: + enum + { + SHADOW_FLAGS_TEXTURE_DIRTY = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 1), + SHADOW_FLAGS_BRUSH_MODEL = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 2), + SHADOW_FLAGS_USING_LOD_SHADOW = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 3), + SHADOW_FLAGS_LIGHT_WORLD = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 4), + }; + + struct ClientShadow_t + { + ClientEntityHandle_t m_Entity; + ShadowHandle_t m_ShadowHandle; + ClientLeafShadowHandle_t m_ClientLeafShadowHandle; + unsigned short m_Flags; + VMatrix m_WorldToShadow; + Vector2D m_WorldSize; +#ifdef DYNAMIC_RTT_SHADOWS + Vector m_ShadowDir; +#endif + Vector m_LastOrigin; + QAngle m_LastAngles; +#ifdef DYNAMIC_RTT_SHADOWS + Vector m_CurrentLightPos; // When shadowing from local lights, stores the position of the currently shadowing light + Vector m_TargetLightPos; // When shadowing from local lights, stores the position of the new shadowing light + float m_LightPosLerp; // Lerp progress when going from current to target light +#endif + TextureHandle_t m_ShadowTexture; + CTextureReference m_ShadowDepthTexture; + int m_nRenderFrame; + EHANDLE m_hTargetEntity; + }; + +private: + // Shadow update functions + void UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ); + void UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ); + void UpdateShadow( ClientShadowHandle_t handle, bool force ); + +#ifdef DYNAMIC_RTT_SHADOWS + // Updates shadow cast direction when shadowing from world lights + void UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ); +#endif + + // Gets the entity whose shadow this shadow will render into + IClientRenderable *GetParentShadowEntity( ClientShadowHandle_t handle ); + + // Adds the child bounds to the bounding box + void AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs ); + + // Compute a bounds for the entity + children + void ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs ); + + // Builds matrices transforming from world space to shadow space + void BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ); + + void BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation ); + + void BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ); + +#ifdef ASW_PROJECTED_TEXTURES + void BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ); +#endif + + // Update a shadow + void UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force ); + + // Compute the shadow origin and attenuation start distance + float ComputeLocalShadowOrigin( IClientRenderable* pRenderable, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin ); + + // Remove a shadow from the dirty list + void RemoveShadowFromDirtyList( ClientShadowHandle_t handle ); + + // NOTE: this will ONLY return SHADOWS_NONE, SHADOWS_SIMPLE, or SHADOW_RENDER_TO_TEXTURE. + ShadowType_t GetActualShadowCastType( ClientShadowHandle_t handle ) const; + ShadowType_t GetActualShadowCastType( IClientRenderable *pRenderable ) const; + + // Builds a simple blobby shadow + void BuildOrthoShadow( IClientRenderable* pRenderable, ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs); + + // Builds a more complex shadow... + void BuildRenderToTextureShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs ); + + // Build a projected-texture flashlight + void BuildFlashlight( ClientShadowHandle_t handle ); + + // Does all the lovely stuff we need to do to have render-to-texture shadows + void SetupRenderToTextureShadow( ClientShadowHandle_t h ); + void CleanUpRenderToTextureShadow( ClientShadowHandle_t h ); + + // Compute the extra shadow planes + void ComputeExtraClipPlanes( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector* vec, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir ); + + // Set extra clip planes related to shadows... + void ClearExtraClipPlanes( ClientShadowHandle_t h ); + void AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist ); + + // Cull if the origin is on the wrong side of a shadow clip plane.... + bool CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable, IClientRenderable* pSourceRenderable ); + + bool ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane ); + + // Causes all shadows to be re-updated + void UpdateAllShadows(); + + // One of these gets called with every shadow that potentially will need to re-render + bool DrawRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ); + void DrawRenderToTextureShadowLOD( unsigned short clientShadowHandle ); + + // Draws all children shadows into our own + bool DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false ); + + // Setup stage for threading + bool BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ); + bool BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false ); + + // Computes + sets the render-to-texture texcoords + void SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h ); + + // Visualization.... + void DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs ); + + // Advance frame + void AdvanceFrame(); + + // Returns renderable-specific shadow info + float GetShadowDistance( IClientRenderable *pRenderable ) const; + const Vector &GetShadowDirection( IClientRenderable *pRenderable ) const; +#ifdef DYNAMIC_RTT_SHADOWS + const Vector &GetShadowDirection( ClientShadowHandle_t shadowHandle ) const; +#endif + + // Initialize, shutdown render-to-texture shadows + void InitDepthTextureShadows(); + void ShutdownDepthTextureShadows(); + + // Initialize, shutdown render-to-texture shadows + void InitRenderToTextureShadows(); + void ShutdownRenderToTextureShadows(); + + static bool ShadowHandleCompareFunc( const ClientShadowHandle_t& lhs, const ClientShadowHandle_t& rhs ) + { + return lhs < rhs; + } + + ClientShadowHandle_t CreateProjectedTexture( ClientEntityHandle_t entity, int flags ); + + // Lock down the usage of a shadow depth texture...must be unlocked use on subsequent views / frames + bool LockShadowDepthTexture( CTextureReference *shadowDepthTexture ); + void UnlockAllShadowDepthTextures(); + + // Set and clear flashlight target renderable + void SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity ); + +#ifdef ASW_PROJECTED_TEXTURES + // Get current frustum extents + void GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax ); +#endif + + // Set flashlight light world flag + void SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ); + + bool IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable ); + + // Builds a list of active shadows requiring shadow depth renders + int BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ); + +#ifdef ASW_PROJECTED_TEXTURES + // Builds a list of active flashlights + int BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights ); +#endif + + // Sets the view's active flashlight render state + void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ); + +#ifdef DYNAMIC_RTT_SHADOWS + void UpdateDirtyShadow( ClientShadowHandle_t handle ); +#endif + +private: + Vector m_SimpleShadowDir; + color32 m_AmbientLightColor; + CMaterialReference m_SimpleShadow; + CMaterialReference m_RenderShadow; + CMaterialReference m_RenderModelShadow; + CTextureReference m_DummyColorTexture; + CUtlLinkedList< ClientShadow_t, ClientShadowHandle_t > m_Shadows; + CTextureAllocator m_ShadowAllocator; + + bool m_RenderToTextureActive; + bool m_bRenderTargetNeedsClear; + bool m_bUpdatingDirtyShadows; + bool m_bThreaded; + float m_flShadowCastDist; + float m_flMinShadowArea; + CUtlRBTree< ClientShadowHandle_t, unsigned short > m_DirtyShadows; + CUtlVector< ClientShadowHandle_t > m_TransparentShadows; + +#ifdef ASW_PROJECTED_TEXTURES + int m_nPrevFrameCount; +#endif + + // These members maintain current state of depth texturing (size and global active state) + // If either changes in a frame, PreRender() will catch it and do the appropriate allocation, deallocation or reallocation + bool m_bDepthTextureActive; + int m_nDepthTextureResolution; // Assume square (height == width) + + CUtlVector< CTextureReference > m_DepthTextureCache; + CUtlVector< bool > m_DepthTextureCacheLocks; + int m_nMaxDepthTextureShadows; + +#ifdef DYNAMIC_RTT_SHADOWS + bool m_bShadowFromWorldLights; + bool m_bSuppressShadowFromWorldLights; +#endif + + friend class CVisibleShadowList; + friend class CVisibleShadowFrustumList; +}; + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CClientShadowMgr s_ClientShadowMgr; +IClientShadowMgr* g_pClientShadowMgr = &s_ClientShadowMgr; + + +//----------------------------------------------------------------------------- +// Builds a list of potential shadows that lie within our PVS + view frustum +//----------------------------------------------------------------------------- +struct VisibleShadowInfo_t +{ + ClientShadowHandle_t m_hShadow; + float m_flArea; + Vector m_vecAbsCenter; +}; + +class CVisibleShadowList : public IClientLeafShadowEnum +{ +public: + + CVisibleShadowList(); + int FindShadows( const CViewSetup *pView, int nLeafCount, LeafIndex_t *pLeafList ); + int GetVisibleShadowCount() const; + + const VisibleShadowInfo_t &GetVisibleShadow( int i ) const; + +private: + void EnumShadow( unsigned short clientShadowHandle ); + float ComputeScreenArea( const Vector &vecCenter, float r ) const; + void PrioritySort(); + + CUtlVector m_ShadowsInView; + CUtlVector m_PriorityIndex; +}; + + +//----------------------------------------------------------------------------- +// Singleton instances of shadow and shadow frustum lists +//----------------------------------------------------------------------------- +static CVisibleShadowList s_VisibleShadowList; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CUtlVector s_NPCShadowBoneSetups; +static CUtlVector s_NonNPCShadowBoneSetups; + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Constructor and Accessors +//----------------------------------------------------------------------------- +CVisibleShadowList::CVisibleShadowList() : m_ShadowsInView( 0, 64 ), m_PriorityIndex( 0, 64 ) +{ +} + +int CVisibleShadowList::GetVisibleShadowCount() const +{ + return m_ShadowsInView.Count(); +} + +const VisibleShadowInfo_t &CVisibleShadowList::GetVisibleShadow( int i ) const +{ + return m_ShadowsInView[m_PriorityIndex[i]]; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Computes approximate screen area of the shadow +//----------------------------------------------------------------------------- +float CVisibleShadowList::ComputeScreenArea( const Vector &vecCenter, float r ) const +{ + CMatRenderContextPtr pRenderContext( materials ); + float flScreenDiameter = pRenderContext->ComputePixelDiameterOfSphere( vecCenter, r ); + return flScreenDiameter * flScreenDiameter; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Visits every shadow in the list of leaves +//----------------------------------------------------------------------------- +void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle ) +{ + CClientShadowMgr::ClientShadow_t& shadow = s_ClientShadowMgr.m_Shadows[clientShadowHandle]; + + // Don't bother if we rendered it this frame, no matter which view it was rendered for + if ( shadow.m_nRenderFrame == gpGlobals->framecount ) + return; + +#ifdef ASW_PROJECTED_TEXTURES + // Don't bother with flashlights + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ) + return; +#endif + + // We don't need to bother with it if it's not render-to-texture + if ( s_ClientShadowMgr.GetActualShadowCastType( clientShadowHandle ) != SHADOWS_RENDER_TO_TEXTURE ) + return; + + // Don't bother with it if the shadow is totally transparent + const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + if ( shadowInfo.m_FalloffBias == 255 ) + return; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + Assert( pRenderable ); + + // Don't bother with children of hierarchy; they will be drawn with their parents + if ( s_ClientShadowMgr.ShouldUseParentShadow( pRenderable ) || s_ClientShadowMgr.WillParentRenderBlobbyShadow( pRenderable ) ) + return; + + // Compute a sphere surrounding the shadow + // FIXME: This doesn't account for children of hierarchy... too bad! + Vector vecAbsCenter; + float flRadius; + s_ClientShadowMgr.ComputeBoundingSphere( pRenderable, vecAbsCenter, flRadius ); + + // Compute a box surrounding the shadow + Vector vecAbsMins, vecAbsMaxs; +#ifdef DYNAMIC_RTT_SHADOWS + s_ClientShadowMgr.ComputeShadowBBox( pRenderable, shadow.m_ShadowHandle, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs ); +#else + s_ClientShadowMgr.ComputeShadowBBox( pRenderable, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs ); +#endif + + // FIXME: Add distance check here? + + // Make sure it's in the frustum. If it isn't it's not interesting + if (engine->CullBox( vecAbsMins, vecAbsMaxs )) + return; + + int i = m_ShadowsInView.AddToTail( ); + VisibleShadowInfo_t &info = m_ShadowsInView[i]; + info.m_hShadow = clientShadowHandle; + m_ShadowsInView[i].m_flArea = ComputeScreenArea( vecAbsCenter, flRadius ); + + // Har, har. When water is rendering (or any multipass technique), + // we may well initially render from a viewpoint which doesn't include this shadow. + // That doesn't mean we shouldn't check it again though. Sucks that we need to compute + // the sphere + bbox multiply times though. + shadow.m_nRenderFrame = gpGlobals->framecount; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Sort based on screen area/priority +//----------------------------------------------------------------------------- +void CVisibleShadowList::PrioritySort() +{ + int nCount = m_ShadowsInView.Count(); + m_PriorityIndex.EnsureCapacity( nCount ); + + m_PriorityIndex.RemoveAll(); + + int i, j; + for ( i = 0; i < nCount; ++i ) + { + m_PriorityIndex.AddToTail(i); + } + + for ( i = 0; i < nCount - 1; ++i ) + { + int nLargestInd = i; + float flLargestArea = m_ShadowsInView[m_PriorityIndex[i]].m_flArea; + for ( j = i + 1; j < nCount; ++j ) + { + int nIndex = m_PriorityIndex[j]; + if ( flLargestArea < m_ShadowsInView[nIndex].m_flArea ) + { + nLargestInd = j; + flLargestArea = m_ShadowsInView[nIndex].m_flArea; + } + } + ::V_swap( m_PriorityIndex[i], m_PriorityIndex[nLargestInd] ); + } +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Main entry point for finding shadows in the leaf list +//----------------------------------------------------------------------------- +int CVisibleShadowList::FindShadows( const CViewSetup *pView, int nLeafCount, LeafIndex_t *pLeafList ) +{ + VPROF_BUDGET( "CVisibleShadowList::FindShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + m_ShadowsInView.RemoveAll(); + ClientLeafSystem()->EnumerateShadowsInLeaves( nLeafCount, pLeafList, this ); + int nCount = m_ShadowsInView.Count(); + if (nCount != 0) + { + // Sort based on screen area/priority + PrioritySort(); + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CClientShadowMgr::CClientShadowMgr() : + m_DirtyShadows( 0, 0, ShadowHandleCompareFunc ), +#ifdef ASW_PROJECTED_TEXTURES + m_nPrevFrameCount( -1 ), +#endif + m_RenderToTextureActive( false ), +#ifdef DYNAMIC_RTT_SHADOWS + m_bShadowFromWorldLights( false ), + m_bSuppressShadowFromWorldLights( false ), +#endif + m_bDepthTextureActive( false ) +{ + m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); + m_bThreaded = false; +} + + +//----------------------------------------------------------------------------- +// Changes the shadow direction... +//----------------------------------------------------------------------------- +CON_COMMAND_F( r_shadowdir, "Set shadow direction", FCVAR_CHEAT ) +{ + Vector dir; + if ( args.ArgC() == 1 ) + { + Vector dir = s_ClientShadowMgr.GetShadowDirection(); + Msg( "%.2f %.2f %.2f\n", dir.x, dir.y, dir.z ); + return; + } + + if ( args.ArgC() == 4 ) + { + dir.x = atof( args[1] ); + dir.y = atof( args[2] ); + dir.z = atof( args[3] ); + s_ClientShadowMgr.SetShadowDirection(dir); + } +} + +CON_COMMAND_F( r_shadowangles, "Set shadow angles", FCVAR_CHEAT ) +{ + Vector dir; + QAngle angles; + if (args.ArgC() == 1) + { + Vector dir = s_ClientShadowMgr.GetShadowDirection(); + QAngle angles; + VectorAngles( dir, angles ); + Msg( "%.2f %.2f %.2f\n", angles.x, angles.y, angles.z ); + return; + } + + if (args.ArgC() == 4) + { + angles.x = atof( args[1] ); + angles.y = atof( args[2] ); + angles.z = atof( args[3] ); + AngleVectors( angles, &dir ); + s_ClientShadowMgr.SetShadowDirection(dir); + } +} + +CON_COMMAND_F( r_shadowcolor, "Set shadow color", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + unsigned char r, g, b; + s_ClientShadowMgr.GetShadowColor( &r, &g, &b ); + Msg( "Shadow color %d %d %d\n", r, g, b ); + return; + } + + if (args.ArgC() == 4) + { + int r = atoi( args[1] ); + int g = atoi( args[2] ); + int b = atoi( args[3] ); + s_ClientShadowMgr.SetShadowColor(r, g, b); + } +} + +CON_COMMAND_F( r_shadowdist, "Set shadow distance", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + float flDist = s_ClientShadowMgr.GetShadowDistance( ); + Msg( "Shadow distance %.2f\n", flDist ); + return; + } + + if (args.ArgC() == 2) + { + float flDistance = atof( args[1] ); + s_ClientShadowMgr.SetShadowDistance( flDistance ); + } +} + +CON_COMMAND_F( r_shadowblobbycutoff, "some shadow stuff", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + float flArea = s_ClientShadowMgr.GetBlobbyCutoffArea( ); + Msg( "Cutoff area %.2f\n", flArea ); + return; + } + + if (args.ArgC() == 2) + { + float flArea = atof( args[1] ); + s_ClientShadowMgr.SetShadowBlobbyCutoffArea( flArea ); + } +} + +#ifdef DYNAMIC_RTT_SHADOWS +void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue ); +static ConVar r_shadowfromworldlights( "r_shadowfromworldlights", "1", FCVAR_NONE, "Enable shadowing from world lights", OnShadowFromWorldLights ); +void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue ) +{ + s_ClientShadowMgr.SuppressShadowFromWorldLights( !r_shadowfromworldlights.GetBool() ); +} +#endif + +static void ShadowRestoreFunc( int nChangeFlags ) +{ + s_ClientShadowMgr.RestoreRenderState(); +} + +//----------------------------------------------------------------------------- +// Initialization, shutdown +//----------------------------------------------------------------------------- +bool CClientShadowMgr::Init() +{ + m_bRenderTargetNeedsClear = false; + m_SimpleShadow.Init( "decals/simpleshadow", TEXTURE_GROUP_DECAL ); + + Vector dir( 0.1, 0.1, -1 ); + SetShadowDirection(dir); + SetShadowDistance( 50 ); + + SetShadowBlobbyCutoffArea( 0.005 ); + +#ifndef MAPBASE + bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL; + m_nMaxDepthTextureShadows = bTools ? 4 : 1; // Just one shadow depth texture in games, more in tools +#else + // 5 lets mappers use up to 4 shadow-casting projected textures, which is better than 3. + m_nMaxDepthTextureShadows = pr_max.GetInt(); +#endif + + bool bLowEnd = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ); + + if ( !bLowEnd && r_shadowrendertotexture.GetBool() ) + { + InitRenderToTextureShadows(); + } + + // If someone turned shadow depth mapping on but we can't do it, force it off + if ( r_flashlightdepthtexture.GetBool() && !materials->SupportsShadowDepthTextures() ) + { + r_flashlightdepthtexture.SetValue( 0 ); + ShutdownDepthTextureShadows(); + } + + if ( !bLowEnd && r_flashlightdepthtexture.GetBool() ) + { + InitDepthTextureShadows(); + } + + materials->AddRestoreFunc( ShadowRestoreFunc ); + + return true; +} + +void CClientShadowMgr::Shutdown() +{ + m_SimpleShadow.Shutdown(); + m_Shadows.RemoveAll(); + ShutdownRenderToTextureShadows(); + + ShutdownDepthTextureShadows(); + + materials->RemoveRestoreFunc( ShadowRestoreFunc ); +} + + +//----------------------------------------------------------------------------- +// Initialize, shutdown depth-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::InitDepthTextureShadows() +{ + VPROF_BUDGET( "CClientShadowMgr::InitDepthTextureShadows", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: set m_nDepthTextureResolution to the depth resolution we want + m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); +#endif + + if( !m_bDepthTextureActive ) + { + m_bDepthTextureActive = true; + + ImageFormat dstFormat = materials->GetShadowDepthTextureFormat(); // Vendor-dependent depth texture format +#if !defined( _X360 ) + ImageFormat nullFormat = materials->GetNullTextureFormat(); // Vendor-dependent null texture format (takes as little memory as possible) +#endif + materials->BeginRenderTargetAllocation(); + +#if defined( _X360 ) + // For the 360, we'll be rendering depth directly into the dummy depth and Resolve()ing to the depth texture. + // only need the dummy surface, don't care about color results + m_DummyColorTexture.InitRenderTargetTexture( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, IMAGE_FORMAT_BGR565, MATERIAL_RT_DEPTH_SHARED, false, "_rt_ShadowDummy" ); + m_DummyColorTexture.InitRenderTargetSurface( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), IMAGE_FORMAT_BGR565, true ); +#else +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: we want to create a render target of specific size, so use RT_SIZE_NO_CHANGE + m_DummyColorTexture.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" ); +#else + m_DummyColorTexture.InitRenderTarget( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" ); +#endif +#endif + + // Create some number of depth-stencil textures + m_DepthTextureCache.Purge(); + m_DepthTextureCacheLocks.Purge(); + for( int i=0; i < m_nMaxDepthTextureShadows; i++ ) + { + CTextureReference depthTex; // Depth-stencil surface + bool bFalse = false; + + char strRTName[64]; + Q_snprintf( strRTName, ARRAYSIZE( strRTName ), "_rt_ShadowDepthTexture_%d", i ); + +#if defined( _X360 ) + // create a render target to use as a resolve target to get the shared depth buffer + // surface is effectively never used + depthTex.InitRenderTargetTexture( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); + depthTex.InitRenderTargetSurface( 1, 1, dstFormat, false ); +#else +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: we want to create a *DEPTH TEXTURE* of specific size, so use RT_SIZE_NO_CHANGE and MATERIAL_RT_DEPTH_ONLY + // However, MATERIAL_RT_DEPTH_ONLY forces point filtering to be enabled which negatively affect PCF, so the standard MATERIAL_RT_DEPTH_NONE works better. + depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); +#else + depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); +#endif +#endif + +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: ensure the depth texture size wasn't changed + Assert(depthTex->GetActualWidth() == m_nDepthTextureResolution); +#endif + + if ( i == 0 ) + { + // Shadow may be resized during allocation (due to resolution constraints etc) + m_nDepthTextureResolution = depthTex->GetActualWidth(); + r_flashlightdepthres.SetValue( m_nDepthTextureResolution ); + } + + m_DepthTextureCache.AddToTail( depthTex ); + m_DepthTextureCacheLocks.AddToTail( bFalse ); + } + + materials->EndRenderTargetAllocation(); + } +} + +void CClientShadowMgr::ShutdownDepthTextureShadows() +{ + if( m_bDepthTextureActive ) + { + // Shut down the dummy texture + m_DummyColorTexture.Shutdown(); + + while( m_DepthTextureCache.Count() ) + { + m_DepthTextureCache[ m_DepthTextureCache.Count()-1 ].Shutdown(); + + m_DepthTextureCacheLocks.Remove( m_DepthTextureCache.Count()-1 ); + m_DepthTextureCache.Remove( m_DepthTextureCache.Count()-1 ); + } + + m_bDepthTextureActive = false; + } +} + +//----------------------------------------------------------------------------- +// Initialize, shutdown render-to-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::InitRenderToTextureShadows() +{ + if ( !m_RenderToTextureActive ) + { + m_RenderToTextureActive = true; + m_RenderShadow.Init( "decals/rendershadow", TEXTURE_GROUP_DECAL ); + m_RenderModelShadow.Init( "decals/rendermodelshadow", TEXTURE_GROUP_DECAL ); + m_ShadowAllocator.Init(); + + m_ShadowAllocator.Reset(); + m_bRenderTargetNeedsClear = true; + + float fr = (float)m_AmbientLightColor.r / 255.0f; + float fg = (float)m_AmbientLightColor.g / 255.0f; + float fb = (float)m_AmbientLightColor.b / 255.0f; + m_RenderShadow->ColorModulate( fr, fg, fb ); + m_RenderModelShadow->ColorModulate( fr, fg, fb ); + + // Iterate over all existing textures and allocate shadow textures + for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + if ( shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE ) + { + SetupRenderToTextureShadow( i ); + MarkRenderToTextureShadowDirty( i ); + + // Switch the material to use render-to-texture shadows + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)(uintp)i ); + } + } + } +} + +void CClientShadowMgr::ShutdownRenderToTextureShadows() +{ + if (m_RenderToTextureActive) + { + // Iterate over all existing textures and deallocate shadow textures + for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + CleanUpRenderToTextureShadow( i ); + + // Switch the material to use blobby shadows + ClientShadow_t& shadow = m_Shadows[i]; + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE ); + shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 ); + ClearExtraClipPlanes( i ); + } + + m_RenderShadow.Shutdown(); + m_RenderModelShadow.Shutdown(); + + m_ShadowAllocator.DeallocateAllTextures(); + m_ShadowAllocator.Shutdown(); + + // Cause the render target to go away + materials->UncacheUnusedMaterials(); + + m_RenderToTextureActive = false; + } +} + + +//----------------------------------------------------------------------------- +// Sets the shadow color +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowColor( unsigned char r, unsigned char g, unsigned char b ) +{ + float fr = (float)r / 255.0f; + float fg = (float)g / 255.0f; + float fb = (float)b / 255.0f; + + // Hook the shadow color into the shadow materials + m_SimpleShadow->ColorModulate( fr, fg, fb ); + + if (m_RenderToTextureActive) + { + m_RenderShadow->ColorModulate( fr, fg, fb ); + m_RenderModelShadow->ColorModulate( fr, fg, fb ); + } + + m_AmbientLightColor.r = r; + m_AmbientLightColor.g = g; + m_AmbientLightColor.b = b; +} + +void CClientShadowMgr::GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const +{ + *r = m_AmbientLightColor.r; + *g = m_AmbientLightColor.g; + *b = m_AmbientLightColor.b; +} + + +//----------------------------------------------------------------------------- +// Level init... get the shadow color +//----------------------------------------------------------------------------- +void CClientShadowMgr::LevelInitPreEntity() +{ + m_bUpdatingDirtyShadows = false; + +#ifdef DYNAMIC_RTT_SHADOWS + // Default setting for this, can be overridden by shadow control entities +#ifdef MAPBASE + SetShadowFromWorldLightsEnabled( false ); +#else + SetShadowFromWorldLightsEnabled( true ); +#endif +#endif + + Vector ambientColor; + engine->GetAmbientLightColor( ambientColor ); + ambientColor *= 3; + ambientColor += Vector( 0.3f, 0.3f, 0.3f ); + + unsigned char r = ambientColor[0] > 1.0 ? 255 : 255 * ambientColor[0]; + unsigned char g = ambientColor[1] > 1.0 ? 255 : 255 * ambientColor[1]; + unsigned char b = ambientColor[2] > 1.0 ? 255 : 255 * ambientColor[2]; + + SetShadowColor(r, g, b); + + // Set up the texture allocator + if ( m_RenderToTextureActive ) + { + m_ShadowAllocator.Reset(); + m_bRenderTargetNeedsClear = true; + } +} + + +//----------------------------------------------------------------------------- +// Clean up all shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::LevelShutdownPostEntity() +{ + // All shadows *should* have been cleaned up when the entities went away + // but, just in case.... + Assert( m_Shadows.Count() == 0 ); + + ClientShadowHandle_t h = m_Shadows.Head(); + while (h != CLIENTSHADOW_INVALID_HANDLE) + { + ClientShadowHandle_t next = m_Shadows.Next(h); + DestroyShadow( h ); + h = next; + } + + // Deallocate all textures + if (m_RenderToTextureActive) + { + m_ShadowAllocator.DeallocateAllTextures(); + } + + r_shadows_gamecontrol.SetValue( -1 ); +} + + +//----------------------------------------------------------------------------- +// Deals with alt-tab +//----------------------------------------------------------------------------- +void CClientShadowMgr::RestoreRenderState() +{ + // Mark all shadows dirty; they need to regenerate their state + ClientShadowHandle_t h; + for ( h = m_Shadows.Head(); h != m_Shadows.InvalidIndex(); h = m_Shadows.Next(h) ) + { + m_Shadows[h].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + } + + SetShadowColor( m_AmbientLightColor.r, m_AmbientLightColor.g, m_AmbientLightColor.b ); + m_bRenderTargetNeedsClear = true; +} + + +//----------------------------------------------------------------------------- +// Does all the lovely stuff we need to do to have render-to-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetupRenderToTextureShadow( ClientShadowHandle_t h ) +{ + // First, compute how much texture memory we want to use. + ClientShadow_t& shadow = m_Shadows[h]; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + return; + + Vector mins, maxs; + pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( h ) ); + + // Compute the maximum dimension + Vector size; + VectorSubtract( maxs, mins, size ); + float maxSize = MAX( size.x, size.y ); + maxSize = MAX( maxSize, size.z ); + + // Figure out the texture size + // For now, we're going to assume a fixed number of shadow texels + // per shadow-caster size; add in some extra space at the boundary. + int texelCount = TEXEL_SIZE_PER_CASTER_SIZE * maxSize; + + // Pick the first power of 2 larger... + int textureSize = 1; + while (textureSize < texelCount) + { + textureSize <<= 1; + } + + shadow.m_ShadowTexture = m_ShadowAllocator.AllocateTexture( textureSize, textureSize ); +} + + +void CClientShadowMgr::CleanUpRenderToTextureShadow( ClientShadowHandle_t h ) +{ + ClientShadow_t& shadow = m_Shadows[h]; + if (m_RenderToTextureActive && (shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE)) + { + m_ShadowAllocator.DeallocateTexture( shadow.m_ShadowTexture ); + shadow.m_ShadowTexture = INVALID_TEXTURE_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +// Causes all shadows to be re-updated +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateAllShadows() +{ + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + // Don't bother with flashlights + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ) + continue; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + continue; + + Assert( pRenderable->GetShadowHandle() == i ); + AddToDirtyShadowList( pRenderable, true ); + } +} + + +//----------------------------------------------------------------------------- +// Sets the shadow direction +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowDirection( const Vector& dir ) +{ + VectorCopy( dir, m_SimpleShadowDir ); + VectorNormalize( m_SimpleShadowDir ); + + if ( m_RenderToTextureActive ) + { + UpdateAllShadows(); + } +} + +const Vector &CClientShadowMgr::GetShadowDirection() const +{ + // This will cause blobby shadows to always project straight down + static Vector s_vecDown( 0, 0, -1 ); + if ( !m_RenderToTextureActive ) + return s_vecDown; + + return m_SimpleShadowDir; +} + + +//----------------------------------------------------------------------------- +// Gets shadow information for a particular renderable +//----------------------------------------------------------------------------- +float CClientShadowMgr::GetShadowDistance( IClientRenderable *pRenderable ) const +{ + float flDist = m_flShadowCastDist; + + // Allow the renderable to override the default + pRenderable->GetShadowCastDistance( &flDist, GetActualShadowCastType( pRenderable ) ); + + return flDist; +} + +const Vector &CClientShadowMgr::GetShadowDirection( IClientRenderable *pRenderable ) const +{ + Vector &vecResult = AllocTempVector(); + vecResult = GetShadowDirection(); + + // Allow the renderable to override the default + pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) ); + + return vecResult; +} + +#ifdef DYNAMIC_RTT_SHADOWS +const Vector &CClientShadowMgr::GetShadowDirection( ClientShadowHandle_t shadowHandle ) const +{ + Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); + + IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[shadowHandle].m_Entity ); + Assert( pRenderable ); + + if ( !IsShadowingFromWorldLights() ) + { + return GetShadowDirection( pRenderable ); + } + + Vector &vecResult = AllocTempVector(); + vecResult = m_Shadows[shadowHandle].m_ShadowDir; + + // Allow the renderable to override the default + pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) ); + + return vecResult; +} + +void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ) +{ + Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); + + ClientShadow_t& shadow = m_Shadows[shadowHandle]; + + IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + // TODO: Figure out why this still gets hit + Assert( pRenderable ); + if ( !pRenderable ) + { + DevWarning( "%s(): Skipping shadow with invalid client renderable (shadow handle %d)\n", __FUNCTION__, shadowHandle ); + return; + } + + Vector bbMin, bbMax; + pRenderable->GetRenderBoundsWorldspace( bbMin, bbMax ); + Vector origin( 0.5f * ( bbMin + bbMax ) ); + origin.z = bbMin.z; // Putting origin at the bottom of the bounding box makes the shadows a little shorter + + Vector lightPos; + Vector lightBrightness; + + if ( shadow.m_LightPosLerp >= 1.0f ) // skip finding new light source if we're in the middle of a lerp + { + // Calculate minimum brightness squared + float flMinBrightnessSqr = r_shadow_mincastintensity.GetFloat(); + flMinBrightnessSqr *= flMinBrightnessSqr; + + if(g_pWorldLights->GetBrightestLightSource(pRenderable->GetRenderOrigin(), lightPos, lightBrightness) == false || + lightBrightness.LengthSqr() < flMinBrightnessSqr ) + { + // didn't find a light source at all, use default shadow direction + // TODO: Could switch to using blobby shadow in this case + lightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + } + } + + if ( shadow.m_LightPosLerp == FLT_MAX ) // first light pos ever, just init + { + shadow.m_CurrentLightPos = lightPos; + shadow.m_TargetLightPos = lightPos; + shadow.m_LightPosLerp = 1.0f; + } + else if ( shadow.m_LightPosLerp < 1.0f ) + { + // We're in the middle of a lerp from current to target light. Finish it. + shadow.m_LightPosLerp += gpGlobals->frametime * 1.0f/r_shadow_lightpos_lerptime.GetFloat(); + shadow.m_LightPosLerp = clamp( shadow.m_LightPosLerp, 0.0f, 1.0f ); + + Vector currLightPos( shadow.m_CurrentLightPos ); + Vector targetLightPos( shadow.m_TargetLightPos ); + if ( currLightPos.x == FLT_MAX ) + { + currLightPos = origin - 200.0f * GetShadowDirection(); + } + if ( targetLightPos.x == FLT_MAX ) + { + targetLightPos = origin - 200.0f * GetShadowDirection(); + } + + // lerp light pos + Vector v1 = origin - shadow.m_CurrentLightPos; + v1.NormalizeInPlace(); + + Vector v2 = origin - shadow.m_TargetLightPos; + v2.NormalizeInPlace(); + + // SAULUNDONE: caused over top sweeping far too often +#if 0 + if ( v1.Dot( v2 ) < 0.0f ) + { + // if change in shadow angle is more than 90 degrees, lerp over the renderable's top to avoid long sweeping shadows + Vector fakeOverheadLightPos( origin.x, origin.y, origin.z + 200.0f ); + if( shadow.m_LightPosLerp < 0.5f ) + { + lightPos = Lerp( 2.0f * shadow.m_LightPosLerp, currLightPos, fakeOverheadLightPos ); + } + else + { + lightPos = Lerp( 2.0f * shadow.m_LightPosLerp - 1.0f, fakeOverheadLightPos, targetLightPos ); + } + } + else +#endif + { + lightPos = Lerp( shadow.m_LightPosLerp, currLightPos, targetLightPos ); + } + + if ( shadow.m_LightPosLerp >= 1.0f ) + { + shadow.m_CurrentLightPos = shadow.m_TargetLightPos; + } + } + else if ( shadow.m_LightPosLerp >= 1.0f ) + { + // check if we have a new closest light position and start a new lerp + float flDistSq = ( lightPos - shadow.m_CurrentLightPos ).LengthSqr(); + + if ( flDistSq > 1.0f ) + { + // light position has changed, which means we got a new light source. Initiate a lerp + shadow.m_TargetLightPos = lightPos; + shadow.m_LightPosLerp = 0.0f; + } + + lightPos = shadow.m_CurrentLightPos; + } + + if ( lightPos.x == FLT_MAX ) + { + lightPos = origin - 200.0f * GetShadowDirection(); + } + + Vector vecResult( origin - lightPos ); + vecResult.NormalizeInPlace(); + + vecResult.z *= r_shadow_shortenfactor.GetFloat(); + vecResult.NormalizeInPlace(); + + shadow.m_ShadowDir = vecResult; + + if ( r_shadowfromworldlights_debug.GetBool() ) + { + NDebugOverlay::Line( lightPos, origin, 255, 255, 0, false, 0.0f ); + } +} +#endif + + +//----------------------------------------------------------------------------- +// Sets the shadow distance +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowDistance( float flMaxDistance ) +{ + m_flShadowCastDist = flMaxDistance; + UpdateAllShadows(); +} + +float CClientShadowMgr::GetShadowDistance( ) const +{ + return m_flShadowCastDist; +} + + +//----------------------------------------------------------------------------- +// Sets the screen area at which blobby shadows are always used +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowBlobbyCutoffArea( float flMinArea ) +{ + m_flMinShadowArea = flMinArea; +} + +float CClientShadowMgr::GetBlobbyCutoffArea( ) const +{ + return m_flMinShadowArea; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias ) +{ + shadowmgr->SetFalloffBias( m_Shadows[handle].m_ShadowHandle, ucBias ); +} + +//----------------------------------------------------------------------------- +// Returns the shadow texture +//----------------------------------------------------------------------------- +ITexture* CClientShadowMgr::GetShadowTexture( unsigned short h ) +{ + return m_ShadowAllocator.GetTexture(); +} + + +//----------------------------------------------------------------------------- +// Returns information needed by the model proxy +//----------------------------------------------------------------------------- +const ShadowInfo_t& CClientShadowMgr::GetShadowInfo( ClientShadowHandle_t h ) +{ + return shadowmgr->GetInfo( m_Shadows[h].m_ShadowHandle ); +} + + +//----------------------------------------------------------------------------- +// Renders the shadow texture to screen... +//----------------------------------------------------------------------------- +void CClientShadowMgr::RenderShadowTexture( int w, int h ) +{ + if (m_RenderToTextureActive) + { + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_RenderShadow ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, h, 0.0f ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, h, 0.0f ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + } +} + + +//----------------------------------------------------------------------------- +// Create/destroy a shadow +//----------------------------------------------------------------------------- +ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandle_t entity, int flags ) +{ + // We need to know if it's a brush model for shadows + if( !( flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity ); + int modelType = modelinfo->GetModelType( pRenderable->GetModel() ); + if (modelType == mod_brush) + { + flags |= SHADOW_FLAGS_BRUSH_MODEL; + } + } + + ClientShadowHandle_t h = m_Shadows.AddToTail(); + ClientShadow_t& shadow = m_Shadows[h]; + shadow.m_Entity = entity; + shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow( h, flags ); + shadow.m_Flags = flags; + shadow.m_nRenderFrame = -1; +#ifdef DYNAMIC_RTT_SHADOWS + shadow.m_ShadowDir = GetShadowDirection(); + shadow.m_CurrentLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_TargetLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_LightPosLerp = FLT_MAX; +#endif + shadow.m_LastOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + Assert( ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) != + ( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 ) ); + + // Set up the flags.... + IMaterial* pShadowMaterial = m_SimpleShadow; + IMaterial* pShadowModelMaterial = m_SimpleShadow; + void* pShadowProxyData = (void*)CLIENTSHADOW_INVALID_HANDLE; + + if ( m_RenderToTextureActive && (flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE) ) + { + SetupRenderToTextureShadow(h); + + pShadowMaterial = m_RenderShadow; + pShadowModelMaterial = m_RenderModelShadow; + pShadowProxyData = (void*)(uintp)h; + } + +#ifdef ASW_PROJECTED_TEXTURES + if( ( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) || ( flags & ( SHADOW_FLAGS_FLASHLIGHT ) ) ) + { + pShadowMaterial = NULL; // these materials aren't used for shadow depth texture shadows. + pShadowModelMaterial = NULL; + pShadowProxyData = (void*)(uintp)h; + } +#else + if( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) + { + pShadowMaterial = m_RenderShadow; + pShadowModelMaterial = m_RenderModelShadow; + pShadowProxyData = (void*)(uintp)h; + } +#endif + + int createShadowFlags; + if( flags & SHADOW_FLAGS_FLASHLIGHT ) + { + // don't use SHADOW_CACHE_VERTS with projective lightsources since we expect that they will change every frame. + // FIXME: might want to make it cache optionally if it's an entity light that is static. + createShadowFlags = SHADOW_FLASHLIGHT; + } + else + { + createShadowFlags = SHADOW_CACHE_VERTS; + } + shadow.m_ShadowHandle = shadowmgr->CreateShadowEx( pShadowMaterial, pShadowModelMaterial, pShadowProxyData, createShadowFlags ); + return h; +} + +ClientShadowHandle_t CClientShadowMgr::CreateFlashlight( const FlashlightState_t &lightState ) +{ + // We don't really need a model entity handle for a projective light source, so use an invalid one. + static ClientEntityHandle_t invalidHandle = INVALID_CLIENTENTITY_HANDLE; + + int shadowFlags = SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_LIGHT_WORLD; + if( lightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() ) + { + shadowFlags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + + ClientShadowHandle_t shadowHandle = CreateProjectedTexture( invalidHandle, shadowFlags ); + + UpdateFlashlightState( shadowHandle, lightState ); + UpdateProjectedTexture( shadowHandle, true ); + return shadowHandle; +} + +ClientShadowHandle_t CClientShadowMgr::CreateShadow( ClientEntityHandle_t entity, int flags ) +{ + // We don't really need a model entity handle for a projective light source, so use an invalid one. + flags &= ~SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK; + flags |= SHADOW_FLAGS_SHADOW | SHADOW_FLAGS_TEXTURE_DIRTY; + ClientShadowHandle_t shadowHandle = CreateProjectedTexture( entity, flags ); + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity ); + if ( pRenderable ) + { + Assert( !pRenderable->IsShadowDirty( ) ); + pRenderable->MarkShadowDirty( true ); + } + + // NOTE: We *have* to call the version that takes a shadow handle + // even if we have an entity because this entity hasn't set its shadow handle yet + AddToDirtyShadowList( shadowHandle, true ); + return shadowHandle; +} + + +//----------------------------------------------------------------------------- +// Updates the flashlight direction and re-computes surfaces it should lie on +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::UpdateFlashlightState", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + +#ifdef ASW_PROJECTED_TEXTURES + if( flashlightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() ) + { + m_Shadows[shadowHandle].m_Flags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + else + { + m_Shadows[shadowHandle].m_Flags &= ~SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + + if ( flashlightState.m_bOrtho ) + { + BuildOrthoWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); + } + else + { + BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); + } +#else + BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); +#endif + + shadowmgr->UpdateFlashlightState( m_Shadows[shadowHandle].m_ShadowHandle, flashlightState ); +} + +void CClientShadowMgr::DestroyFlashlight( ClientShadowHandle_t shadowHandle ) +{ + DestroyShadow( shadowHandle ); +} + +//----------------------------------------------------------------------------- +// Remove a shadow from the dirty list +//----------------------------------------------------------------------------- +void CClientShadowMgr::RemoveShadowFromDirtyList( ClientShadowHandle_t handle ) +{ + int idx = m_DirtyShadows.Find( handle ); + if ( idx != m_DirtyShadows.InvalidIndex() ) + { + // Clean up the shadow update bit. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity ); + if ( pRenderable ) + { + pRenderable->MarkShadowDirty( false ); + } + m_DirtyShadows.RemoveAt( idx ); + } +} + + +//----------------------------------------------------------------------------- +// Remove a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::DestroyShadow( ClientShadowHandle_t handle ) +{ + Assert( m_Shadows.IsValidIndex(handle) ); + RemoveShadowFromDirtyList( handle ); + shadowmgr->DestroyShadow( m_Shadows[handle].m_ShadowHandle ); + ClientLeafSystem()->RemoveShadow( m_Shadows[handle].m_ClientLeafShadowHandle ); + CleanUpRenderToTextureShadow( handle ); + m_Shadows.Remove(handle); +} + + +//----------------------------------------------------------------------------- +// Build the worldtotexture matrix +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ) +{ + // We're assuming here that xvec + yvec aren't necessary perpendicular + + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + matWorldToShadow.SetBasisVectors( xvec, yvec, dir ); + matWorldToShadow.SetTranslation( origin ); + matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f; + matWorldToShadow[3][3] = 1.0f; + + // Now do a general inverse to get matWorldToShadow + MatrixInverseGeneral( matWorldToShadow, matWorldToShadow ); +} + +void CClientShadowMgr::BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation ) +{ + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + // The inverse of this transposes the rotational component + // and the translational component = - (rotation transpose) * origin + + matrix3x4_t matOrientation; + QuaternionMatrix( quatOrientation, matOrientation ); // Convert quat to matrix3x4 + PositionMatrix( vec3_origin, matOrientation ); // Zero out translation elements + + VMatrix matBasis( matOrientation ); // Convert matrix3x4 to VMatrix + + Vector vForward, vLeft, vUp; + matBasis.GetBasisVectors( vForward, vLeft, vUp ); + matBasis.SetForward( vLeft ); // Bizarre vector flip inherited from earlier code, WTF? + matBasis.SetLeft( vUp ); + matBasis.SetUp( vForward ); + matWorldToShadow = matBasis.Transpose(); // Transpose + + Vector translation; + Vector3DMultiply( matWorldToShadow, origin, translation ); + + translation *= -1.0f; + matWorldToShadow.SetTranslation( translation ); + + // The the bottom row. + matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f; + matWorldToShadow[3][3] = 1.0f; +} + + +void CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // Buildworld to shadow matrix, then perspective projection and concatenate + VMatrix matWorldToShadowView, matPerspective, addW, scaleHalf; + + BuildWorldToShadowMatrix(matWorldToShadowView, flashlightState.m_vecLightOrigin, + flashlightState.m_quatOrientation); + + MatrixBuildPerspective(matPerspective, flashlightState.m_fHorizontalFOVDegrees, + flashlightState.m_fVerticalFOVDegrees, + flashlightState.m_NearZ, flashlightState.m_FarZ); + + + MatrixMultiply(matPerspective, matWorldToShadowView, matWorldToShadow); + + +} + +#ifdef ASW_PROJECTED_TEXTURES +void CClientShadowMgr::BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // Buildworld to shadow matrix, then perspective projection and concatenate + VMatrix matWorldToShadowView, matPerspective; + BuildWorldToShadowMatrix( matWorldToShadowView, flashlightState.m_vecLightOrigin, + flashlightState.m_quatOrientation ); + + MatrixBuildOrtho( matPerspective, + flashlightState.m_fOrthoLeft, flashlightState.m_fOrthoTop, flashlightState.m_fOrthoRight, flashlightState.m_fOrthoBottom, + flashlightState.m_NearZ, flashlightState.m_FarZ ); + + // Shift it z/y to 0 to -2 space + VMatrix addW; + addW.Identity(); + addW[0][3] = -1.0f; + addW[1][3] = -1.0f; + addW[2][3] = 0.0f; + MatrixMultiply( addW, matPerspective, matPerspective ); + + // Flip x/y to positive 0 to 1... flip z to negative + VMatrix scaleHalf; + scaleHalf.Identity(); + scaleHalf[0][0] = -0.5f; + scaleHalf[1][1] = -0.5f; + scaleHalf[2][2] = -1.0f; + MatrixMultiply( scaleHalf, matPerspective, matPerspective ); + + MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow ); +} +#endif + +//----------------------------------------------------------------------------- +// Compute the shadow origin and attenuation start distance +//----------------------------------------------------------------------------- +float CClientShadowMgr::ComputeLocalShadowOrigin( IClientRenderable* pRenderable, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin ) +{ + // Compute the centroid of the object... + Vector vecCentroid; + VectorAdd( mins, maxs, vecCentroid ); + vecCentroid *= 0.5f; + + Vector vecSize; + VectorSubtract( maxs, mins, vecSize ); + float flRadius = vecSize.Length() * 0.5f; + + // NOTE: The *origin* of the shadow cast is a point on a line passing through + // the centroid of the caster. The direction of this line is the shadow cast direction, + // and the point on that line corresponds to the endpoint of the box that is + // furthest *back* along the shadow direction + + // For the first point at which the shadow could possibly start falling off, + // we need to use the point at which the ray described above leaves the + // bounding sphere surrounding the entity. This is necessary because otherwise, + // tall, thin objects would have their shadows appear + disappear as then spun about their origin + + // Figure out the corner corresponding to the min + max projection + // along the shadow direction + + // We're basically finding the point on the cube that has the largest and smallest + // dot product with the local shadow dir. Then we're taking the dot product + // of that with the localShadowDir. lastly, we're subtracting out the + // centroid projection to give us a distance along the localShadowDir to + // the front and back of the cube along the direction of the ray. + float centroidProjection = DotProduct( vecCentroid, localShadowDir ); + float minDist = -centroidProjection; + for (int i = 0; i < 3; ++i) + { + if ( localShadowDir[i] > 0.0f ) + { + minDist += localShadowDir[i] * mins[i]; + } + else + { + minDist += localShadowDir[i] * maxs[i]; + } + } + + minDist *= backupFactor; + + VectorMA( vecCentroid, minDist, localShadowDir, origin ); + + return flRadius - minDist; +} + + +//----------------------------------------------------------------------------- +// Sorts the components of a vector +//----------------------------------------------------------------------------- +static inline void SortAbsVectorComponents( const Vector& src, int* pVecIdx ) +{ + Vector absVec( fabs(src[0]), fabs(src[1]), fabs(src[2]) ); + + int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1; + if (absVec[2] > absVec[maxIdx]) + { + maxIdx = 2; + } + + // always choose something right-handed.... + switch( maxIdx ) + { + case 0: + pVecIdx[0] = 1; + pVecIdx[1] = 2; + pVecIdx[2] = 0; + break; + case 1: + pVecIdx[0] = 2; + pVecIdx[1] = 0; + pVecIdx[2] = 1; + break; + case 2: + pVecIdx[0] = 0; + pVecIdx[1] = 1; + pVecIdx[2] = 2; + break; + } +} + + +//----------------------------------------------------------------------------- +// Build the worldtotexture matrix +//----------------------------------------------------------------------------- +static void BuildWorldToTextureMatrix( const VMatrix& matWorldToShadow, + const Vector2D& size, VMatrix& matWorldToTexture ) +{ + // Build a matrix that maps from shadow space to (u,v) coordinates + VMatrix shadowToUnit; + MatrixBuildScale( shadowToUnit, 1.0f / size.x, 1.0f / size.y, 1.0f ); + shadowToUnit[0][3] = shadowToUnit[1][3] = 0.5f; + + // Store off the world to (u,v) transformation + MatrixMultiply( shadowToUnit, matWorldToShadow, matWorldToTexture ); +} + + + +static void BuildOrthoWorldToShadowMatrix( VMatrix& worldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ) +{ + // This version is faster and assumes dir, xvec, yvec are perpendicular + AssertFloatEquals( DotProduct( dir, xvec ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( dir, yvec ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( xvec, yvec ), 0.0f, 1e-3 ); + + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + // The inverse of this transposes the rotational component + // and the translational component = - (rotation transpose) * origin + worldToShadow.SetBasisVectors( xvec, yvec, dir ); + MatrixTranspose( worldToShadow, worldToShadow ); + + Vector translation; + Vector3DMultiply( worldToShadow, origin, translation ); + + translation *= -1.0f; + worldToShadow.SetTranslation( translation ); + + // The the bottom row. + worldToShadow[3][0] = worldToShadow[3][1] = worldToShadow[3][2] = 0.0f; + worldToShadow[3][3] = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Set extra clip planes related to shadows... +//----------------------------------------------------------------------------- +void CClientShadowMgr::ClearExtraClipPlanes( ClientShadowHandle_t h ) +{ + shadowmgr->ClearExtraClipPlanes( m_Shadows[h].m_ShadowHandle ); +} + +void CClientShadowMgr::AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist ) +{ + shadowmgr->AddExtraClipPlane( m_Shadows[h].m_ShadowHandle, normal, dist ); +} + + +//----------------------------------------------------------------------------- +// Compute the extra shadow planes +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeExtraClipPlanes( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector* vec, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir ) +{ + // Compute the world-space position of the corner of the bounding box + // that's got the highest dotproduct with the local shadow dir... + Vector origin = pRenderable->GetRenderOrigin( ); + float dir[3]; + + int i; + for ( i = 0; i < 3; ++i ) + { + if (localShadowDir[i] < 0.0f) + { + VectorMA( origin, maxs[i], vec[i], origin ); + dir[i] = 1; + } + else + { + VectorMA( origin, mins[i], vec[i], origin ); + dir[i] = -1; + } + } + + // Now that we have it, create 3 planes... + Vector normal; + ClearExtraClipPlanes(handle); + for ( i = 0; i < 3; ++i ) + { + VectorMultiply( vec[i], dir[i], normal ); + float dist = DotProduct( normal, origin ); + AddExtraClipPlane( handle, normal, dist ); + } + + ClientShadow_t& shadow = m_Shadows[handle]; + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( shadow.m_Entity ); + if ( pEntity && pEntity->m_bEnableRenderingClipPlane ) + { + normal[ 0 ] = -pEntity->m_fRenderingClipPlane[ 0 ]; + normal[ 1 ] = -pEntity->m_fRenderingClipPlane[ 1 ]; + normal[ 2 ] = -pEntity->m_fRenderingClipPlane[ 2 ]; + AddExtraClipPlane( handle, normal, -pEntity->m_fRenderingClipPlane[ 3 ] - 0.5f ); + } +} + + +inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( ClientShadowHandle_t handle ) const +{ + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + { + return SHADOWS_NONE; + } + + if ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE ) + { + return ( m_RenderToTextureActive ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_SIMPLE ); + } + else if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) + { + return SHADOWS_RENDER_TO_DEPTH_TEXTURE; + } + else + { + return SHADOWS_SIMPLE; + } +} + +inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( IClientRenderable *pEnt ) const +{ + return GetActualShadowCastType( pEnt->GetShadowHandle() ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to all leaves along a ray +//----------------------------------------------------------------------------- +class CShadowLeafEnum : public ISpatialLeafEnumerator +{ +public: + bool EnumerateLeaf( int leaf, int context ) + { + m_LeafList.AddToTail( leaf ); + return true; + } + + CUtlVectorFixedGrowable< int, 512 > m_LeafList; +}; + + +//----------------------------------------------------------------------------- +// Builds a list of leaves inside the shadow volume +//----------------------------------------------------------------------------- +static void BuildShadowLeafList( CShadowLeafEnum *pEnum, const Vector& origin, + const Vector& dir, const Vector2D& size, float maxDist ) +{ + Ray_t ray; + VectorCopy( origin, ray.m_Start ); + VectorMultiply( dir, maxDist, ray.m_Delta ); + ray.m_StartOffset.Init( 0, 0, 0 ); + + float flRadius = sqrt( size.x * size.x + size.y * size.y ) * 0.5f; + ray.m_Extents.Init( flRadius, flRadius, flRadius ); + ray.m_IsRay = false; + ray.m_IsSwept = true; + + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesAlongRay( ray, pEnum, 0 ); +} + + +//----------------------------------------------------------------------------- +// Builds a simple blobby shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs) +{ + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else + Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif + + // Project the shadow casting direction into the space of the object + Vector localShadowDir; + localShadowDir[0] = DotProduct( vec[0], vecShadowDir ); + localShadowDir[1] = DotProduct( vec[1], vecShadowDir ); + localShadowDir[2] = DotProduct( vec[2], vecShadowDir ); + + // Figure out which vector has the largest component perpendicular + // to the shadow handle... + // Sort by how perpendicular it is + int vecIdx[3]; + SortAbsVectorComponents( localShadowDir, vecIdx ); + + // Here's our shadow basis vectors; namely the ones that are + // most perpendicular to the shadow casting direction + Vector xvec = vec[vecIdx[0]]; + Vector yvec = vec[vecIdx[1]]; + + // Project them into a plane perpendicular to the shadow direction + xvec -= vecShadowDir * DotProduct( vecShadowDir, xvec ); + yvec -= vecShadowDir * DotProduct( vecShadowDir, yvec ); + VectorNormalize( xvec ); + VectorNormalize( yvec ); + + // Compute the box size + Vector boxSize; + VectorSubtract( maxs, mins, boxSize ); + + // We project the two longest sides into the vectors perpendicular + // to the projection direction, then add in the projection of the perp direction + Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] ); + size.x *= fabs( DotProduct( vec[vecIdx[0]], xvec ) ); + size.y *= fabs( DotProduct( vec[vecIdx[1]], yvec ) ); + + // Add the third component into x and y + size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], xvec ) ); + size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], yvec ) ); + + // Bloat a bit, since the shadow wants to extend outside the model a bit + size.x += 10.0f; + size.y += 10.0f; + + // Clamp the minimum size + Vector2DMax( size, Vector2D(10.0f, 10.0f), size ); + + // Place the origin at the point with min dot product with shadow dir + Vector org; + float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 2.0f, org ); + + // Transform the local origin into world coordinates + Vector worldOrigin = pRenderable->GetRenderOrigin( ); + VectorMA( worldOrigin, org.x, vec[0], worldOrigin ); + VectorMA( worldOrigin, org.y, vec[1], worldOrigin ); + VectorMA( worldOrigin, org.z, vec[2], worldOrigin ); + + // FUNKY: A trick to reduce annoying texelization artifacts!? + float dx = 1.0f / TEXEL_SIZE_PER_CASTER_SIZE; + worldOrigin.x = (int)(worldOrigin.x / dx) * dx; + worldOrigin.y = (int)(worldOrigin.y / dx) * dx; + worldOrigin.z = (int)(worldOrigin.z / dx) * dx; + + // NOTE: We gotta use the general matrix because xvec and yvec aren't perp + VMatrix matWorldToShadow, matWorldToTexture; + BuildGeneralWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec ); + BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture ); + Vector2DCopy( size, m_Shadows[handle].m_WorldSize ); + + // Compute the falloff attenuation + // Area computation isn't exact since xvec is not perp to yvec, but close enough +// float shadowArea = size.x * size.y; + + // The entity may be overriding our shadow cast distance + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea ); + + CShadowLeafEnum leafList; + BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight ); + int nCount = leafList.m_LeafList.Count(); + const int *pLeafList = leafList.m_LeafList.Base(); + + shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin, + vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() ); + + // Compute extra clip planes to prevent poke-thru +// FIXME!!!!!!!!!!!!!! Removing this for now since it seems to mess up the blobby shadows. +#ifdef ASW_PROJECTED_TEXTURES + ComputeExtraClipPlanes( pRenderable, handle, vec, mins, maxs, localShadowDir ); +#else +// ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir ); +#endif + + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList ); +} + + +//----------------------------------------------------------------------------- +// Visualization.... +//----------------------------------------------------------------------------- +void CClientShadowMgr::DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs ) +{ + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + Vector vecSize; + VectorSubtract( maxs, mins, vecSize ); + + Vector vecOrigin = pRenderable->GetRenderOrigin(); + Vector start, end, end2; + + VectorMA( vecOrigin, mins.x, vec[0], start ); + VectorMA( start, mins.y, vec[1], start ); + VectorMA( start, mins.z, vec[2], start ); + + VectorMA( start, vecSize.x, vec[0], end ); + VectorMA( end, vecSize.z, vec[2], end2 ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.y, vec[1], end ); + VectorMA( end, vecSize.z, vec[2], end2 ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.z, vec[2], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + start = end; + VectorMA( start, vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( end, vecSize.x, vec[0], start ); + VectorMA( start, -vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.z, vec[2], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + start = end; + VectorMA( start, -vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt ) + { + debugoverlay->AddTextOverlay( vecOrigin, 0, "%d", pEnt->entindex() ); + } + else + { + debugoverlay->AddTextOverlay( vecOrigin, 0, "%X", (size_t)pRenderable ); + } +} + + +extern ConVar cl_drawshadowtexture; +extern ConVar cl_shadowtextureoverlaysize; + +//----------------------------------------------------------------------------- +// Builds a more complex shadow... +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs) +{ + if ( cl_drawshadowtexture.GetInt() ) + { + // Red wireframe bounding box around objects whose RTT shadows are being updated that frame + DrawRenderToTextureDebugInfo( pRenderable, mins, maxs ); + } + + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else + Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif + +// Debugging aid +// const model_t *pModel = pRenderable->GetModel(); +// const char *pDebugName = modelinfo->GetModelName( pModel ); + + // Project the shadow casting direction into the space of the object + Vector localShadowDir; + localShadowDir[0] = DotProduct( vec[0], vecShadowDir ); + localShadowDir[1] = DotProduct( vec[1], vecShadowDir ); + localShadowDir[2] = DotProduct( vec[2], vecShadowDir ); + + // Compute the box size + Vector boxSize; + VectorSubtract( maxs, mins, boxSize ); + + Vector yvec; + float fProjMax = 0.0f; + for( int i = 0; i != 3; ++i ) + { + Vector test = vec[i] - ( vecShadowDir * DotProduct( vecShadowDir, vec[i] ) ); + test *= boxSize[i]; //doing after the projection to simplify projection math + float fLengthSqr = test.LengthSqr(); + if( fLengthSqr > fProjMax ) + { + fProjMax = fLengthSqr; + yvec = test; + } + } + + VectorNormalize( yvec ); + + // Compute the x vector + Vector xvec; + CrossProduct( yvec, vecShadowDir, xvec ); + + // We project the two longest sides into the vectors perpendicular + // to the projection direction, then add in the projection of the perp direction + Vector2D size; + size.x = boxSize.x * fabs( DotProduct( vec[0], xvec ) ) + + boxSize.y * fabs( DotProduct( vec[1], xvec ) ) + + boxSize.z * fabs( DotProduct( vec[2], xvec ) ); + size.y = boxSize.x * fabs( DotProduct( vec[0], yvec ) ) + + boxSize.y * fabs( DotProduct( vec[1], yvec ) ) + + boxSize.z * fabs( DotProduct( vec[2], yvec ) ); + + size.x += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + size.y += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + + // Place the origin at the point with min dot product with shadow dir + Vector org; + float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 1.0f, org ); + + // Transform the local origin into world coordinates + Vector worldOrigin = pRenderable->GetRenderOrigin( ); + VectorMA( worldOrigin, org.x, vec[0], worldOrigin ); + VectorMA( worldOrigin, org.y, vec[1], worldOrigin ); + VectorMA( worldOrigin, org.z, vec[2], worldOrigin ); + + VMatrix matWorldToTexture; + BuildOrthoWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec ); + BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture ); + Vector2DCopy( size, m_Shadows[handle].m_WorldSize ); + + // Compute the falloff attenuation + // Area computation isn't exact since xvec is not perp to yvec, but close enough + // Extra factor of 4 in the maxHeight due to the size being half as big +// float shadowArea = size.x * size.y; + + // The entity may be overriding our shadow cast distance + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea ); + + CShadowLeafEnum leafList; + BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight ); + int nCount = leafList.m_LeafList.Count(); + const int *pLeafList = leafList.m_LeafList.Base(); + + shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin, + vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() ); + + // Compute extra clip planes to prevent poke-thru + ComputeExtraClipPlanes( pRenderable, handle, vec, mins, maxs, localShadowDir ); + + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList ); +} + +static void LineDrawHelper( const Vector &startShadowSpace, const Vector &endShadowSpace, + const VMatrix &shadowToWorld, unsigned char r = 255, unsigned char g = 255, + unsigned char b = 255 ) +{ + Vector startWorldSpace, endWorldSpace; + Vector3DMultiplyPositionProjective( shadowToWorld, startShadowSpace, startWorldSpace ); + Vector3DMultiplyPositionProjective( shadowToWorld, endShadowSpace, endWorldSpace ); + + debugoverlay->AddLineOverlay( startWorldSpace + Vector( 0.0f, 0.0f, 1.0f ), + endWorldSpace + Vector( 0.0f, 0.0f, 1.0f ), r, g, b, false, -1 ); +} + +static void DebugDrawFrustum( const Vector &vOrigin, const VMatrix &matWorldToFlashlight ) +{ + VMatrix flashlightToWorld; + MatrixInverseGeneral( matWorldToFlashlight, flashlightToWorld ); + + // Draw boundaries of frustum + LineDrawHelper( Vector( 0.0f, 0.0f, 0.0f ), Vector( 0.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 1.0f ), Vector( 0.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 1.0f ), Vector( 0.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 0.0f ), Vector( 0.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 0.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 1.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 1.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 0.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + + // Draw RGB triad at front plane + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 1.0f, 0.5f, 0.0f ), flashlightToWorld, 255, 0, 0 ); + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 1.0f, 0.0f ), flashlightToWorld, 0, 255, 0 ); + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 0.5f, 0.35f ), flashlightToWorld, 0, 0, 255 ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of leaves inside the flashlight volume +//----------------------------------------------------------------------------- +static void BuildFlashlightLeafList( CShadowLeafEnum *pEnum, const VMatrix &worldToShadow ) +{ + // Use an AABB around the frustum to enumerate leaves. + Vector mins, maxs; + CalculateAABBFromProjectionMatrix( worldToShadow, &mins, &maxs ); + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesInBox( mins, maxs, pEnum, 0 ); +} + + +void CClientShadowMgr::BuildFlashlight( ClientShadowHandle_t handle ) +{ + // For the 360, we just draw flashlights with the main geometry + // and bypass the entire shadow casting system. + ClientShadow_t &shadow = m_Shadows[handle]; + if ( IsX360() || r_flashlight_version2.GetInt() ) + { + // This will update the matrices, but not do work to add the flashlight to surfaces + shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, 0, NULL ); + return; + } + + VPROF_BUDGET( "CClientShadowMgr::BuildFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + bool bLightModels = r_flashlightmodels.GetBool(); + bool bLightSpecificEntity = shadow.m_hTargetEntity.Get() != NULL; + bool bLightWorld = ( shadow.m_Flags & SHADOW_FLAGS_LIGHT_WORLD ) != 0; + int nCount = 0; + const int *pLeafList = 0; + + CShadowLeafEnum leafList; + if ( bLightWorld || ( bLightModels && !bLightSpecificEntity ) ) + { + BuildFlashlightLeafList( &leafList, shadow.m_WorldToShadow ); + nCount = leafList.m_LeafList.Count(); + pLeafList = leafList.m_LeafList.Base(); + } + + if( bLightWorld ) + { + shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, nCount, pLeafList ); + } + else + { + // This should clear all models and surfaces from this shadow + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + } + + if ( !bLightModels ) + return; + + if ( !bLightSpecificEntity ) + { + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectFlashlight( shadow.m_ClientLeafShadowHandle, nCount, pLeafList ); + return; + } + + // We know what we are focused on, so just add the shadow directly to that receiver + Assert( shadow.m_hTargetEntity->GetModel() ); + + C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild(); + while( pChild ) + { + int modelType = modelinfo->GetModelType( pChild->GetModel() ); + if (modelType == mod_brush) + { + AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if ( modelType == mod_studio ) + { + AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_STUDIO_MODEL ); + } + + pChild = pChild->NextMovePeer(); + } + + int modelType = modelinfo->GetModelType( shadow.m_hTargetEntity->GetModel() ); + if (modelType == mod_brush) + { + AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if ( modelType == mod_studio ) + { + AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_STUDIO_MODEL ); + } +} + + +//----------------------------------------------------------------------------- +// Adds the child bounds to the bounding box +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs ) +{ + Vector vecChildMins, vecChildMaxs; + Vector vecNewChildMins, vecNewChildMaxs; + matrix3x4_t childToBBox; + + IClientRenderable *pChild = pParent->FirstShadowChild(); + while( pChild ) + { + // Transform the child bbox into the space of the main bbox + // FIXME: Optimize this? + if ( GetActualShadowCastType( pChild ) != SHADOWS_NONE) + { + pChild->GetShadowRenderBounds( vecChildMins, vecChildMaxs, SHADOWS_RENDER_TO_TEXTURE ); + ConcatTransforms( matWorldToBBox, pChild->RenderableToWorldTransform(), childToBBox ); + TransformAABB( childToBBox, vecChildMins, vecChildMaxs, vecNewChildMins, vecNewChildMaxs ); + VectorMin( vecMins, vecNewChildMins, vecMins ); + VectorMax( vecMaxs, vecNewChildMaxs, vecMaxs ); + } + + AddChildBounds( matWorldToBBox, pChild, vecMins, vecMaxs ); + pChild = pChild->NextShadowPeer(); + } +} + + +//----------------------------------------------------------------------------- +// Compute a bounds for the entity + children +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs ) +{ + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + + pRenderable->GetShadowRenderBounds( vecMins, vecMaxs, shadowType ); + + // We could use a good solution for this in the regular PC build, since + // it causes lots of extra bone setups for entities you can't see. + if ( IsPC() ) + { + IClientRenderable *pChild = pRenderable->FirstShadowChild(); + + // Don't recurse down the tree when we hit a blobby shadow + if ( pChild && shadowType != SHADOWS_SIMPLE ) + { + matrix3x4_t matWorldToBBox; + MatrixInvert( pRenderable->RenderableToWorldTransform(), matWorldToBBox ); + AddChildBounds( matWorldToBBox, pRenderable, vecMins, vecMaxs ); + } + } +} + + +//----------------------------------------------------------------------------- +// Shadow update functions +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ) +{ + if( !( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + Vector mins, maxs; + ComputeHierarchicalBounds( pRenderable, mins, maxs ); + + ShadowType_t shadowType = GetActualShadowCastType( handle ); + if ( shadowType != SHADOWS_RENDER_TO_TEXTURE ) + { + BuildOrthoShadow( pRenderable, handle, mins, maxs ); + } + else + { + BuildRenderToTextureShadow( pRenderable, handle, mins, maxs ); + } + } + else + { + BuildFlashlight( handle ); + } +} + +void CClientShadowMgr::UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ) +{ + if( !( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + // Compute the bounding box in the space of the shadow... + Vector mins, maxs; + ComputeHierarchicalBounds( pRenderable, mins, maxs ); + + ShadowType_t shadowType = GetActualShadowCastType( handle ); + if ( shadowType != SHADOWS_RENDER_TO_TEXTURE ) + { + BuildOrthoShadow( pRenderable, handle, mins, maxs ); + } + else + { + BuildRenderToTextureShadow( pRenderable, handle, mins, maxs ); + } + } + else + { + VPROF_BUDGET( "CClientShadowMgr::UpdateBrushShadow", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + BuildFlashlight( handle ); + } +} + + +#ifdef _DEBUG + +static bool s_bBreak = false; + +void ShadowBreak_f() +{ + s_bBreak = true; +} + +static ConCommand r_shadowbreak("r_shadowbreak", ShadowBreak_f); + +#endif // _DEBUG + + +bool CClientShadowMgr::WillParentRenderBlobbyShadow( IClientRenderable *pRenderable ) +{ + if ( !pRenderable ) + return false; + + IClientRenderable *pShadowParent = pRenderable->GetShadowParent(); + if ( !pShadowParent ) + return false; + + // If there's *no* shadow casting type, then we want to see if we can render into its parent + ShadowType_t shadowType = GetActualShadowCastType( pShadowParent ); + if ( shadowType == SHADOWS_NONE ) + return WillParentRenderBlobbyShadow( pShadowParent ); + + return shadowType == SHADOWS_SIMPLE; +} + + +//----------------------------------------------------------------------------- +// Are we the child of a shadow with render-to-texture? +//----------------------------------------------------------------------------- +bool CClientShadowMgr::ShouldUseParentShadow( IClientRenderable *pRenderable ) +{ + if ( !pRenderable ) + return false; + + IClientRenderable *pShadowParent = pRenderable->GetShadowParent(); + if ( !pShadowParent ) + return false; + + // Can't render into the parent if the parent is blobby + ShadowType_t shadowType = GetActualShadowCastType( pShadowParent ); + if ( shadowType == SHADOWS_SIMPLE ) + return false; + + // If there's *no* shadow casting type, then we want to see if we can render into its parent + if ( shadowType == SHADOWS_NONE ) + return ShouldUseParentShadow( pShadowParent ); + + // Here, the parent uses a render-to-texture shadow + return true; +} + + +//----------------------------------------------------------------------------- +// Before we render any view, make sure all shadows are re-projected vs world +//----------------------------------------------------------------------------- +void CClientShadowMgr::PreRender() +{ +#ifdef ASW_PROJECTED_TEXTURES + // only update shadows once per frame + if( gpGlobals->framecount == m_nPrevFrameCount ) + return; + m_nPrevFrameCount = gpGlobals->framecount; +#endif + + VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + MDLCACHE_CRITICAL_SECTION(); + + // + // -- Shadow Depth Textures ----------------------- + // + + { + // VPROF scope + VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // If someone turned shadow depth mapping on but we can't do it, force it off + if ( r_flashlightdepthtexture.GetBool() && !materials->SupportsShadowDepthTextures() ) + { + r_flashlightdepthtexture.SetValue( 0 ); + ShutdownDepthTextureShadows(); + } + + bool bDepthTextureActive = r_flashlightdepthtexture.GetBool(); + int nDepthTextureResolution = r_flashlightdepthres.GetInt(); + + // If shadow depth texture size or enable/disable changed, do appropriate deallocation/(re)allocation + if ( ( bDepthTextureActive != m_bDepthTextureActive ) || ( nDepthTextureResolution != m_nDepthTextureResolution ) ) + { + // If shadow depth texturing remains on, but resolution changed, shut down and reinitialize depth textures + if ( ( bDepthTextureActive == true ) && ( m_bDepthTextureActive == true ) && + ( nDepthTextureResolution != m_nDepthTextureResolution ) ) + { + ShutdownDepthTextureShadows(); + InitDepthTextureShadows(); + } + else + { + if ( m_bDepthTextureActive && !bDepthTextureActive ) // Turning off shadow depth texturing + { + ShutdownDepthTextureShadows(); + } + else if ( bDepthTextureActive && !m_bDepthTextureActive) // Turning on shadow depth mapping + { + InitDepthTextureShadows(); + } + } + } + } + + // + // -- Render to Texture Shadows ----------------------- + // + + bool bRenderToTextureActive = r_shadowrendertotexture.GetBool(); + if ( bRenderToTextureActive != m_RenderToTextureActive ) + { + if ( m_RenderToTextureActive ) + { + ShutdownRenderToTextureShadows(); + } + else + { + InitRenderToTextureShadows(); + } + + UpdateAllShadows(); + return; + } + + m_bUpdatingDirtyShadows = true; + + unsigned short i = m_DirtyShadows.FirstInorder(); + while ( i != m_DirtyShadows.InvalidIndex() ) + { + MDLCACHE_CRITICAL_SECTION(); + ClientShadowHandle_t& handle = m_DirtyShadows[ i ]; +#ifdef DYNAMIC_RTT_SHADOWS + UpdateDirtyShadow(handle); +#else + Assert( m_Shadows.IsValidIndex( handle ) ); + UpdateProjectedTextureInternal( handle, false ); +#endif + i = m_DirtyShadows.NextInorder(i); + } + m_DirtyShadows.RemoveAll(); + + // Transparent shadows must remain dirty, since they were not re-projected + int nCount = m_TransparentShadows.Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_DirtyShadows.Insert( m_TransparentShadows[i] ); + } + m_TransparentShadows.RemoveAll(); + + m_bUpdatingDirtyShadows = false; +} + +#ifdef DYNAMIC_RTT_SHADOWS +//----------------------------------------------------------------------------- +// Updates a single dirty shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle ) +{ + Assert( m_Shadows.IsValidIndex( handle ) ); + if ( IsShadowingFromWorldLights() ) + { + UpdateShadowDirectionFromLocalLightSource( handle ); + } + UpdateProjectedTextureInternal( handle, false ); +} +#endif + +//----------------------------------------------------------------------------- +// Gets the entity whose shadow this shadow will render into +//----------------------------------------------------------------------------- +IClientRenderable *CClientShadowMgr::GetParentShadowEntity( ClientShadowHandle_t handle ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( pRenderable ) + { + if ( ShouldUseParentShadow( pRenderable ) ) + { + IClientRenderable *pParent = pRenderable->GetShadowParent(); + while ( GetActualShadowCastType( pParent ) == SHADOWS_NONE ) + { + pParent = pParent->GetShadowParent(); + Assert( pParent ); + } + return pParent; + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Marks a shadow as needing re-projection +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce ) +{ + // Don't add to the dirty shadow list while we're iterating over it + // The only way this can happen is if a child is being rendered into a parent + // shadow, and we don't need it to be added to the dirty list in that case. + if ( m_bUpdatingDirtyShadows ) + return; + + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + + Assert( m_DirtyShadows.Find( handle ) == m_DirtyShadows.InvalidIndex() ); + m_DirtyShadows.Insert( handle ); + + // This pretty much guarantees we'll recompute the shadow + if ( bForce ) + { + m_Shadows[handle].m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + } + + // If we use our parent shadow, then it's dirty too... + IClientRenderable *pParent = GetParentShadowEntity( handle ); + if ( pParent ) + { + AddToDirtyShadowList( pParent, bForce ); + } +} + + +//----------------------------------------------------------------------------- +// Marks a shadow as needing re-projection +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddToDirtyShadowList( IClientRenderable *pRenderable, bool bForce ) +{ + // Don't add to the dirty shadow list while we're iterating over it + // The only way this can happen is if a child is being rendered into a parent + // shadow, and we don't need it to be added to the dirty list in that case. + if ( m_bUpdatingDirtyShadows ) + return; + + // Are we already in the dirty list? + if ( pRenderable->IsShadowDirty( ) ) + return; + + ClientShadowHandle_t handle = pRenderable->GetShadowHandle(); + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + +#ifdef _DEBUG + // Make sure everything's consistent + if ( handle != CLIENTSHADOW_INVALID_HANDLE ) + { + IClientRenderable *pShadowRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity ); + Assert( pRenderable == pShadowRenderable ); + } +#endif + + pRenderable->MarkShadowDirty( true ); + AddToDirtyShadowList( handle, bForce ); +} + + +//----------------------------------------------------------------------------- +// Marks the render-to-texture shadow as needing to be re-rendered +//----------------------------------------------------------------------------- +void CClientShadowMgr::MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle ) +{ + // Don't add bogus handles! + if (handle != CLIENTSHADOW_INVALID_HANDLE) + { + // Mark the shadow has having a dirty renter-to-texture + ClientShadow_t& shadow = m_Shadows[handle]; + shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + + // If we use our parent shadow, then it's dirty too... + IClientRenderable *pParent = GetParentShadowEntity( handle ); + if ( pParent ) + { + ClientShadowHandle_t parentHandle = pParent->GetShadowHandle(); + if ( parentHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + m_Shadows[parentHandle].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateShadow( ClientShadowHandle_t handle, bool force ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + + // Get the client entity.... + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + { + // Retire the shadow if the entity is gone + DestroyShadow( handle ); + return; + } + + // Don't bother if there's no model on the renderable + if ( !pRenderable->GetModel() ) + { + pRenderable->MarkShadowDirty( false ); + return; + } + + // FIXME: NOTE! Because this is called from PreRender, the falloff bias is + // off by a frame. We could move the code in PreRender to occur after world + // list building is done to fix this issue. + // Don't bother with it if the shadow is totally transparent + const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + if ( shadowInfo.m_FalloffBias == 255 ) + { + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + m_TransparentShadows.AddToTail( handle ); + return; + } + +#ifdef _DEBUG + if (s_bBreak) + { + s_bBreak = false; + } +#endif + // Hierarchical children shouldn't be projecting shadows... + // Check to see if it's a child of an entity with a render-to-texture shadow... + if ( ShouldUseParentShadow( pRenderable ) || WillParentRenderBlobbyShadow( pRenderable ) ) + { + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + pRenderable->MarkShadowDirty( false ); + return; + } + + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + + // Figure out if the shadow moved... + // Even though we have dirty bits, some entities + // never clear those dirty bits + const Vector& origin = pRenderable->GetRenderOrigin(); + const QAngle& angles = pRenderable->GetRenderAngles(); + +#ifdef DYNAMIC_RTT_SHADOWS + if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f) +#else + if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles)) +#endif + { + // Store off the new pos/orientation + VectorCopy( origin, shadow.m_LastOrigin ); + VectorCopy( angles, shadow.m_LastAngles ); + + CMatRenderContextPtr pRenderContext( materials ); + const model_t *pModel = pRenderable->GetModel(); + MaterialFogMode_t fogMode = pRenderContext->GetFogMode(); + pRenderContext->FogMode( MATERIAL_FOG_NONE ); + switch( modelinfo->GetModelType( pModel ) ) + { + case mod_brush: + UpdateBrushShadow( pRenderable, handle ); + break; + + case mod_studio: + UpdateStudioShadow( pRenderable, handle ); + break; + + default: + // Shouldn't get here if not a brush or studio + Assert(0); + break; + } + pRenderContext->FogMode( fogMode ); + } + + // NOTE: We can't do this earlier because pEnt->GetRenderOrigin() can + // provoke a recomputation of render origin, which, for aiments, can cause everything + // to be marked as dirty. So don't clear the flag until this point. + pRenderable->MarkShadowDirty( false ); +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTextureInternal", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + Assert( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 ); + ClientShadow_t& shadow = m_Shadows[handle]; + + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + + // FIXME: What's the difference between brush and model shadows for light projectors? Answer: nothing. + UpdateBrushShadow( NULL, handle ); + } + else + { + Assert( shadow.m_Flags & SHADOW_FLAGS_SHADOW ); + Assert( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ); + UpdateShadow( handle, force ); + } +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateProjectedTexture( ClientShadowHandle_t handle, bool force ) +{ + VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTexture", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + + // NOTE: This can only work for flashlights, since UpdateProjectedTextureInternal + // depends on the falloff offset to cull shadows. + ClientShadow_t &shadow = m_Shadows[ handle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + { + Warning( "CClientShadowMgr::UpdateProjectedTexture can only be used with flashlights!\n" ); + return; + } + + UpdateProjectedTextureInternal( handle, force ); + RemoveShadowFromDirtyList( handle ); +} + + +//----------------------------------------------------------------------------- +// Computes bounding sphere +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius ) +{ + Assert( pRenderable ); + Vector mins, maxs; + pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( pRenderable ) ); + Vector size; + VectorSubtract( maxs, mins, size ); + radius = size.Length() * 0.5f; + + // Compute centroid (local space) + Vector centroid; + VectorAdd( mins, maxs, centroid ); + centroid *= 0.5f; + + // Transform centroid into world space + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + VectorCopy( pRenderable->GetRenderOrigin(), origin ); + VectorMA( origin, centroid.x, vec[0], origin ); + VectorMA( origin, centroid.y, vec[1], origin ); + VectorMA( origin, centroid.z, vec[2], origin ); +} + + +//----------------------------------------------------------------------------- +// Computes a rough AABB encompassing the volume of the shadow +//----------------------------------------------------------------------------- +#ifdef DYNAMIC_RTT_SHADOWS +void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) +#else +void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) +#endif +{ + // This is *really* rough. Basically we simply determine the + // maximum shadow casting length and extrude the box by that distance + +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( shadowHandle ); +#else + Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif + for (int i = 0; i < 3; ++i) + { + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float flDist = flShadowCastDistance * vecShadowDir[i]; + + if (vecShadowDir[i] < 0) + { + (*pAbsMins)[i] = vecAbsCenter[i] - flRadius + flDist; + (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius; + } + else + { + (*pAbsMins)[i] = vecAbsCenter[i] - flRadius; + (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius + flDist; + } + } +} + + +//----------------------------------------------------------------------------- +// Compute a separating axis... +//----------------------------------------------------------------------------- +bool CClientShadowMgr::ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane ) +{ + Vector min1, max1, min2, max2; + pRend1->GetShadowRenderBounds( min1, max1, GetActualShadowCastType( pRend1 ) ); + pRend2->GetShadowRenderBounds( min2, max2, GetActualShadowCastType( pRend2 ) ); + return ::ComputeSeparatingPlane( + pRend1->GetRenderOrigin(), pRend1->GetRenderAngles(), min1, max1, + pRend2->GetRenderOrigin(), pRend2->GetRenderAngles(), min2, max2, + 3.0f, pPlane ); +} + + +//----------------------------------------------------------------------------- +// Cull shadows based on rough bounding volumes +//----------------------------------------------------------------------------- +bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable, + IClientRenderable* pSourceRenderable ) +{ + // check flags here instead and assert !pSourceRenderable + if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::CullReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + Assert( !pSourceRenderable ); + const Frustum_t &frustum = shadowmgr->GetFlashlightFrustum( m_Shadows[handle].m_ShadowHandle ); + + Vector mins, maxs; + pRenderable->GetRenderBoundsWorldspace( mins, maxs ); + + return R_CullBox( mins, maxs, frustum ); + } + + Assert( pSourceRenderable ); + // Compute a bounding sphere for the renderable + Vector origin; + float radius; + ComputeBoundingSphere( pRenderable, origin, radius ); + + // Transform the sphere center into the space of the shadow + Vector localOrigin; + const ClientShadow_t& shadow = m_Shadows[handle]; + const ShadowInfo_t& info = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + Vector3DMultiplyPosition( shadow.m_WorldToShadow, origin, localOrigin ); + + // Compute a rough bounding box for the shadow (in shadow space) + Vector shadowMin, shadowMax; + shadowMin.Init( -shadow.m_WorldSize.x * 0.5f, -shadow.m_WorldSize.y * 0.5f, 0 ); + shadowMax.Init( shadow.m_WorldSize.x * 0.5f, shadow.m_WorldSize.y * 0.5f, info.m_MaxDist ); + + // If the bounding sphere doesn't intersect with the shadow volume, cull + if (!IsBoxIntersectingSphere( shadowMin, shadowMax, localOrigin, radius )) + return true; + + Vector originSource; + float radiusSource; + ComputeBoundingSphere( pSourceRenderable, originSource, radiusSource ); + + // Fast check for separating plane... + bool foundSeparatingPlane = false; + cplane_t plane; + if (!IsSphereIntersectingSphere( originSource, radiusSource, origin, radius )) + { + foundSeparatingPlane = true; + + // the plane normal doesn't need to be normalized... + VectorSubtract( origin, originSource, plane.normal ); + } + else + { + foundSeparatingPlane = ComputeSeparatingPlane( pRenderable, pSourceRenderable, &plane ); + } + + if (foundSeparatingPlane) + { + // Compute which side of the plane the renderable is on.. +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else + Vector vecShadowDir = GetShadowDirection( pSourceRenderable ); +#endif + float shadowDot = DotProduct( vecShadowDir, plane.normal ); + float receiverDot = DotProduct( plane.normal, origin ); + float sourceDot = DotProduct( plane.normal, originSource ); + + if (shadowDot > 0.0f) + { + if (receiverDot <= sourceDot) + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f ); + return true; + } + else + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f ); + } + } + else + { + if (receiverDot >= sourceDot) + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), -50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f ); + return true; + } + else + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f ); + } + } + } + + // No additional clip planes? ok then it's a valid receiver + /* + if (shadow.m_ClipPlaneCount == 0) + return false; + + // Check the additional cull planes + int i; + for ( i = 0; i < shadow.m_ClipPlaneCount; ++i) + { + // Fast sphere cull + if (DotProduct( origin, shadow.m_ClipPlane[i] ) - radius > shadow.m_ClipDist[i]) + return true; + } + + // More expensive box on plane side cull... + Vector vec[3]; + Vector mins, maxs; + cplane_t plane; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + pRenderable->GetBounds( mins, maxs ); + + for ( i = 0; i < shadow.m_ClipPlaneCount; ++i) + { + // Transform the plane into the space of the receiver + plane.normal.x = DotProduct( vec[0], shadow.m_ClipPlane[i] ); + plane.normal.y = DotProduct( vec[1], shadow.m_ClipPlane[i] ); + plane.normal.z = DotProduct( vec[2], shadow.m_ClipPlane[i] ); + + plane.dist = shadow.m_ClipDist[i] - DotProduct( shadow.m_ClipPlane[i], pRenderable->GetRenderOrigin() ); + + // If the box is on the front side of the plane, we're done. + if (BoxOnPlaneSide2( mins, maxs, &plane, 3.0f ) == 1) + return true; + } + */ + + return false; +} + + +//----------------------------------------------------------------------------- +// deals with shadows being added to shadow receivers +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddShadowToReceiver( ClientShadowHandle_t handle, + IClientRenderable* pRenderable, ShadowReceiver_t type ) +{ + ClientShadow_t &shadow = m_Shadows[handle]; + + // Don't add a shadow cast by an object to itself... + IClientRenderable* pSourceRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + // NOTE: if pSourceRenderable == NULL, the source is probably a flashlight since there is no entity. + if (pSourceRenderable == pRenderable) + return; + + // Don't bother if this renderable doesn't receive shadows or light from flashlights + if( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) + return; + + // Cull if the origin is on the wrong side of a shadow clip plane.... + if ( CullReceiver( handle, pRenderable, pSourceRenderable ) ) + return; + + // Do different things depending on the receiver type + switch( type ) + { + case SHADOW_RECEIVER_BRUSH_MODEL: + + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle, + const_cast(pRenderable->GetModel()), + pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() ); + + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + else + { + shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle, + const_cast(pRenderable->GetModel()), + pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() ); + } + break; + + case SHADOW_RECEIVER_STATIC_PROP: + // Don't add shadows to props if we're not using render-to-texture + if ( GetActualShadowCastType( handle ) == SHADOWS_RENDER_TO_TEXTURE ) + { + // Also don't add them unless an NPC or player casts them.. + // They are wickedly expensive!!! + C_BaseEntity *pEnt = pSourceRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt && ( pEnt->GetFlags() & (FL_NPC | FL_CLIENT)) ) + { + staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable ); + } + } + else if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable ); + + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + break; + + case SHADOW_RECEIVER_STUDIO_MODEL: + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + pRenderable->CreateModelInstance(); + shadowmgr->AddShadowToModel( shadow.m_ShadowHandle, pRenderable->GetModelInstance() ); + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + break; +// default: + } +} + + +//----------------------------------------------------------------------------- +// deals with shadows being added to shadow receivers +//----------------------------------------------------------------------------- +void CClientShadowMgr::RemoveAllShadowsFromReceiver( + IClientRenderable* pRenderable, ShadowReceiver_t type ) +{ + // Don't bother if this renderable doesn't receive shadows + if ( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) + return; + + // Do different things depending on the receiver type + switch( type ) + { + case SHADOW_RECEIVER_BRUSH_MODEL: + { + model_t* pModel = const_cast(pRenderable->GetModel()); + shadowmgr->RemoveAllShadowsFromBrushModel( pModel ); + } + break; + + case SHADOW_RECEIVER_STATIC_PROP: + staticpropmgr->RemoveAllShadowsFromStaticProp(pRenderable); + break; + + case SHADOW_RECEIVER_STUDIO_MODEL: + if( pRenderable && pRenderable->GetModelInstance() != MODEL_INSTANCE_INVALID ) + { + shadowmgr->RemoveAllShadowsFromModel( pRenderable->GetModelInstance() ); + } + break; + +// default: +// // FIXME: How do deal with this stuff? Add a method to IClientRenderable? +// C_BaseEntity* pEnt = static_cast(pRenderable); +// pEnt->RemoveAllShadows(); + } +} + + +//----------------------------------------------------------------------------- +// Computes + sets the render-to-texture texcoords +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h ) +{ + // Let the shadow mgr know about the texture coordinates... + // That way it'll be able to batch rendering better. + int textureW, textureH; + m_ShadowAllocator.GetTotalTextureSize( textureW, textureH ); + + // Go in a half-pixel to avoid blending with neighboring textures.. + float u, v, du, dv; + + u = ((float)x + 0.5f) / (float)textureW; + v = ((float)y + 0.5f) / (float)textureH; + du = ((float)w - 1) / (float)textureW; + dv = ((float)h - 1) / (float)textureH; + + shadowmgr->SetShadowTexCoord( handle, u, v, du, dv ); +} + + +//----------------------------------------------------------------------------- +// Setup all children shadows +//----------------------------------------------------------------------------- +bool CClientShadowMgr::BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild ) +{ + bool bDrewTexture = false; + + // Stop traversing when we hit a blobby shadow + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + if ( pRenderable && shadowType == SHADOWS_SIMPLE ) + return false; + + if ( !pRenderable || shadowType != SHADOWS_NONE ) + { + bool bDrawModelShadow; + if ( !bChild ) + { + bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0); + } + else + { + int nModelType = modelinfo->GetModelType( pRenderable->GetModel() ); + bDrawModelShadow = nModelType == mod_studio; + } + + if ( bDrawModelShadow ) + { + C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEntity ) + { + if ( pEntity->IsNPC() ) + { + s_NPCShadowBoneSetups.AddToTail( assert_cast( pEntity ) ); + } + else if ( pEntity->GetBaseAnimating() ) + { + s_NonNPCShadowBoneSetups.AddToTail( assert_cast( pEntity ) ); + } + + } + bDrewTexture = true; + } + } + + if ( !pRenderable ) + return bDrewTexture; + + IClientRenderable *pChild; + for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() ) + { + if ( BuildSetupShadowHierarchy( pChild, shadow, true ) ) + { + bDrewTexture = true; + } + } + return bDrewTexture; +} + +//----------------------------------------------------------------------------- +// Draws all children shadows into our own +//----------------------------------------------------------------------------- +bool CClientShadowMgr::DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild ) +{ + bool bDrewTexture = false; + + // Stop traversing when we hit a blobby shadow + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + if ( pRenderable && shadowType == SHADOWS_SIMPLE ) + return false; + + if ( !pRenderable || shadowType != SHADOWS_NONE ) + { + bool bDrawModelShadow; + bool bDrawBrushShadow; + if ( !bChild ) + { + bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0); + bDrawBrushShadow = !bDrawModelShadow; + } + else + { + int nModelType = modelinfo->GetModelType( pRenderable->GetModel() ); + bDrawModelShadow = nModelType == mod_studio; + bDrawBrushShadow = nModelType == mod_brush; + } + + if ( bDrawModelShadow ) + { + DrawModelInfo_t info; + matrix3x4_t *pBoneToWorld = modelrender->DrawModelShadowSetup( pRenderable, pRenderable->GetBody(), pRenderable->GetSkin(), &info ); + if ( pBoneToWorld ) + { + modelrender->DrawModelShadow( pRenderable, info, pBoneToWorld ); + } + bDrewTexture = true; + } + else if ( bDrawBrushShadow ) + { + render->DrawBrushModelShadow( pRenderable ); + bDrewTexture = true; + } + } + + if ( !pRenderable ) + return bDrewTexture; + + IClientRenderable *pChild; + for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() ) + { + if ( DrawShadowHierarchy( pChild, shadow, true ) ) + { + bDrewTexture = true; + } + } + return bDrewTexture; +} + +//----------------------------------------------------------------------------- +// This gets called with every shadow that potentially will need to re-render +//----------------------------------------------------------------------------- +bool CClientShadowMgr::BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ) +{ + ClientShadow_t& shadow = m_Shadows[clientShadowHandle]; + bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0; + bool bNeedsRedraw = m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea ); + if ( bNeedsRedraw || bDirtyTexture ) + { + shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + + if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) ) + return false; + + // shadow to be redrawn; for now, we'll always do it. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + if ( BuildSetupShadowHierarchy( pRenderable, shadow ) ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// This gets called with every shadow that potentially will need to re-render +//----------------------------------------------------------------------------- +bool CClientShadowMgr::DrawRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ) +{ + ClientShadow_t& shadow = m_Shadows[clientShadowHandle]; + + // If we were previously using the LOD shadow, set the material + bool bPreviouslyUsingLODShadow = ( shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW ) != 0; + shadow.m_Flags &= ~SHADOW_FLAGS_USING_LOD_SHADOW; + if ( bPreviouslyUsingLODShadow ) + { + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)(uintp)clientShadowHandle ); + } + + // Mark texture as being used... + bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0; + bool bDrewTexture = false; + bool bNeedsRedraw = ( !m_bThreaded && m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea ) ); + + if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) ) + { + DrawRenderToTextureShadowLOD( clientShadowHandle ); + return false; + } + + if ( bNeedsRedraw || bDirtyTexture ) + { + // shadow to be redrawn; for now, we'll always do it. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + CMatRenderContextPtr pRenderContext( materials ); + + // Sets the viewport state + int x, y, w, h; + m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h ); + pRenderContext->Viewport( IsX360() ? 0 : x, IsX360() ? 0 : y, w, h ); + + // Clear the selected viewport only (don't need to clear depth) + pRenderContext->ClearBuffers( true, false ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadMatrix( shadowmgr->GetInfo( shadow.m_ShadowHandle ).m_WorldToShadow ); + + if ( DrawShadowHierarchy( pRenderable, shadow ) ) + { + bDrewTexture = true; + if ( IsX360() ) + { + // resolve render target to system memory texture + Rect_t srcRect = { 0, 0, w, h }; + Rect_t dstRect = { x, y, w, h }; + pRenderContext->CopyRenderTargetToTextureEx( m_ShadowAllocator.GetTexture(), 0, &srcRect, &dstRect ); + } + } + else + { + // NOTE: Think the flags reset + texcoord set should only happen in DrawShadowHierarchy + // but it's 2 days before 360 ship.. not going to change this now. + DevMsg( "Didn't draw shadow hierarchy.. bad shadow texcoords probably going to happen..grab Brian!\n" ); + } + + // Only clear the dirty flag if the caster isn't animating + if ( (shadow.m_Flags & SHADOW_FLAGS_ANIMATING_SOURCE) == 0 ) + { + shadow.m_Flags &= ~SHADOW_FLAGS_TEXTURE_DIRTY; + } + + SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h ); + } + else if ( bPreviouslyUsingLODShadow ) + { + // In this case, we were previously using the LOD shadow, but we didn't + // have to reconstitute the texture. In this case, we need to reset the texcoord + int x, y, w, h; + m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h ); + SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h ); + } + + return bDrewTexture; +} + + +//----------------------------------------------------------------------------- +// "Draws" the shadow LOD, which really means just set up the blobby shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::DrawRenderToTextureShadowLOD( unsigned short clientShadowHandle ) +{ + ClientShadow_t &shadow = m_Shadows[clientShadowHandle]; + if ( (shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW) == 0 ) + { + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE ); + shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 ); + ClearExtraClipPlanes( clientShadowHandle ); // this was ClearExtraClipPlanes( shadow.m_ShadowHandle ), fix is from Joe Demers + shadow.m_Flags |= SHADOW_FLAGS_USING_LOD_SHADOW; + } +} + + +//----------------------------------------------------------------------------- +// Advances to the next frame, +//----------------------------------------------------------------------------- +void CClientShadowMgr::AdvanceFrame() +{ + // We're starting the next frame + m_ShadowAllocator.AdvanceFrame(); +} + + +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ) +{ +#ifdef ASW_PROJECTED_TEXTURES + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); +#endif + + int nActiveDepthShadowCount = 0; + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + // If this is not a flashlight which should use a shadow depth texture, skip! + if ( ( shadow.m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) == 0 ) + continue; + + const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); + + // Bail if this flashlight doesn't want shadows + if ( !flashlightState.m_bEnableShadows ) + continue; + + // Calculate an AABB around the shadow frustum + Vector vecAbsMins, vecAbsMaxs; + CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs ); + + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); + + // FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc. + // If it's not in the view frustum, move on +#ifdef MAPBASE + if ( !flashlightState.m_bAlwaysDraw && !flashlightState.m_bOrtho && R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#elif ASW_PROJECTED_TEXTURES + if ( !flashlightState.m_bOrtho && R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#else + if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#endif + { + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + if ( nActiveDepthShadowCount >= nMaxDepthShadows ) + { + static bool s_bOverflowWarning = false; + if ( !s_bOverflowWarning ) + { + Warning( "Too many depth textures rendered in a single view!\n" ); + Assert( 0 ); + s_bOverflowWarning = true; + } + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + pActiveDepthShadows[nActiveDepthShadowCount++] = i; + } + return nActiveDepthShadowCount; +} + + +#ifdef ASW_PROJECTED_TEXTURES +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +int CClientShadowMgr::BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights ) +{ + int nActiveFlashlightCount = 0; + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + continue; + + // Calculate an AABB around the shadow frustum + Vector vecAbsMins, vecAbsMaxs; + CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs ); + + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); + + // FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc. + // If it's not in the view frustum, move on + if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) + { + continue; + } + + if ( nActiveFlashlightCount >= nMaxFlashlights ) + { + static bool s_bOverflowWarning = false; + if ( !s_bOverflowWarning ) + { + Warning( "Too many flashlights rendered in a single view!\n" ); + Assert( 0 ); + s_bOverflowWarning = true; + } + //shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + pActiveFlashlights[nActiveFlashlightCount++] = i; + } + return nActiveFlashlightCount; +} +#endif + +//----------------------------------------------------------------------------- +// Sets the view's active flashlight render state +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ) +{ + // NOTE: On the 360, we render the entire scene with the flashlight state + // set and don't render flashlights additively in the shadow mgr at a far later time + // because the CPU costs are prohibitive + if ( !IsX360() && !r_flashlight_version2.GetInt() ) + return; + + Assert( nActiveFlashlightCount<= 1 ); + if ( nActiveFlashlightCount > 0 ) + { + Assert( ( m_Shadows[ pActiveFlashlights[0] ].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ); + shadowmgr->SetFlashlightRenderState( pActiveFlashlights[0] ); + } + else + { + shadowmgr->SetFlashlightRenderState( SHADOW_HANDLE_INVALID ); + } +} + +#ifdef ASW_PROJECTED_TEXTURES +void AddPointToExtentsHelper( const VMatrix &flashlightToWorld, const Vector &vecPos, Vector &vecMin, Vector &vecMax ) +{ + Vector worldSpacePos; + + Vector3DMultiplyPositionProjective( flashlightToWorld, vecPos, worldSpacePos ); + VectorMin( vecMin, worldSpacePos, vecMin ); + VectorMax( vecMax, worldSpacePos, vecMax ); +} + + +void CClientShadowMgr::GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax ) +{ + Assert( m_Shadows.IsValidIndex( handle ) ); + + CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ handle ]; + + VMatrix flashlightToWorld; + MatrixInverseGeneral( shadow.m_WorldToShadow, flashlightToWorld ); + + vecMin = Vector( FLT_MAX, FLT_MAX, FLT_MAX ); + vecMax = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 1.0f ), vecMin, vecMax ); +} +#endif + +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) +{ +#ifdef ASW_PROJECTED_TEXTURES + if ( !r_flashlightdepthtexture.GetBool() ) + { + // Build list of active flashlights + ClientShadowHandle_t pActiveFlashlights[16]; + int nActiveFlashlights = BuildActiveFlashlightList( viewSetup, ARRAYSIZE( pActiveFlashlights ), pActiveFlashlights ); + SetViewFlashlightState( nActiveFlashlights, pActiveFlashlights ); + return; + } +#endif + + VPROF_BUDGET( "CClientShadowMgr::ComputeShadowDepthTextures", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + CMatRenderContextPtr pRenderContext( materials ); + PIXEVENT( pRenderContext, "Shadow Depth Textures" ); + + // Build list of active render-to-texture shadows + ClientShadowHandle_t pActiveDepthShadows[1024]; + int nActiveDepthShadowCount = BuildActiveShadowDepthList( viewSetup, ARRAYSIZE( pActiveDepthShadows ), pActiveDepthShadows ); + + // Iterate over all existing textures and allocate shadow textures + bool bDebugFrustum = r_flashlightdrawfrustum.GetBool(); + for ( int j = 0; j < nActiveDepthShadowCount; ++j ) + { + ClientShadow_t& shadow = m_Shadows[ pActiveDepthShadows[j] ]; + +#ifdef ASW_PROJECTED_TEXTURES + FlashlightState_t& flashlightState = const_cast( shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ) ); +#endif + + CTextureReference shadowDepthTexture; + bool bGotShadowDepthTexture = LockShadowDepthTexture( &shadowDepthTexture ); + if ( !bGotShadowDepthTexture ) + { + // If we don't get one, that means we have too many this frame so bind no depth texture + static int bitchCount = 0; + if( bitchCount < 10 ) + { + Warning( "Too many shadow maps this frame!\n" ); + bitchCount++; + } + + Assert(0); + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); +#ifdef MAPBASE + if ( j <= ( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST ) ) + { + pRenderContext->SetIntRenderingParameter( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, 0 ); + } +#endif + continue; + } + + CViewSetup shadowView; + shadowView.m_flAspectRatio = 1.0f; + shadowView.x = shadowView.y = 0; + shadowView.width = shadowDepthTexture->GetActualWidth(); + shadowView.height = shadowDepthTexture->GetActualHeight(); +#ifndef ASW_PROJECTED_TEXTURES + shadowView.m_bOrtho = false; + shadowView.m_bDoBloomAndToneMapping = false; +#endif + + // Copy flashlight parameters +#ifdef ASW_PROJECTED_TEXTURES + if ( !flashlightState.m_bOrtho ) + { + shadowView.m_bOrtho = false; + } + else + { + shadowView.m_bOrtho = true; + shadowView.m_OrthoLeft = flashlightState.m_fOrthoLeft; + shadowView.m_OrthoTop = flashlightState.m_fOrthoTop; + shadowView.m_OrthoRight = flashlightState.m_fOrthoRight; + shadowView.m_OrthoBottom = flashlightState.m_fOrthoBottom; + } + + shadowView.m_bDoBloomAndToneMapping = false; +#else + const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); +#endif + shadowView.fov = shadowView.fovViewmodel = flashlightState.m_fHorizontalFOVDegrees; + shadowView.origin = flashlightState.m_vecLightOrigin; + QuaternionAngles( flashlightState.m_quatOrientation, shadowView.angles ); // Convert from Quaternion to QAngle + + shadowView.zNear = shadowView.zNearViewmodel = flashlightState.m_NearZ; + shadowView.zFar = shadowView.zFarViewmodel = flashlightState.m_FarZ; + + // Can turn on all light frustum overlays or per light with flashlightState parameter... + if ( bDebugFrustum || flashlightState.m_bDrawShadowFrustum ) + { + DebugDrawFrustum( shadowView.origin, shadow.m_WorldToShadow ); + } + + // Set depth bias factors specific to this flashlight + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->SetShadowDepthBiasFactors( flashlightState.m_flShadowSlopeScaleDepthBias, flashlightState.m_flShadowDepthBias ); + + // Render to the shadow depth texture with appropriate view + view->UpdateShadowDepthTexture( m_DummyColorTexture, shadowDepthTexture, shadowView ); + +#ifdef MAPBASE + if ( j <= ( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST ) ) + { + pRenderContext->SetIntRenderingParameter( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, int((ITexture*)shadowDepthTexture) ); + + FlashlightState_t state = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); + + state.m_nShadowQuality = state.m_nShadowQuality | ( ( j + 1 ) << 16 ); + + shadowmgr->UpdateFlashlightState( shadow.m_ShadowHandle, state ); + } +#endif + + // Associate the shadow depth texture and stencil bit with the flashlight for use during scene rendering + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, shadowDepthTexture, 0 ); + } + + SetViewFlashlightState( nActiveDepthShadowCount, pActiveDepthShadows ); +} + + +//----------------------------------------------------------------------------- +// Re-renders all shadow textures for shadow casters that lie in the leaf list +//----------------------------------------------------------------------------- +static void SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating ) +{ + pBaseAnimating->SetupBones( NULL, -1, -1, gpGlobals->curtime ); +} + + +void CClientShadowMgr::ComputeShadowTextures( const CViewSetup &view, int leafCount, LeafIndex_t* pLeafList ) +{ + VPROF_BUDGET( "CClientShadowMgr::ComputeShadowTextures", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( !m_RenderToTextureActive || (r_shadows.GetInt() == 0) || r_shadows_gamecontrol.GetInt() == 0 ) + return; + +#ifdef ASW_PROJECTED_TEXTURES + m_bThreaded = ( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); +#else + m_bThreaded = false;//( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); +#endif + + MDLCACHE_CRITICAL_SECTION(); + // First grab all shadow textures we may want to render + int nCount = s_VisibleShadowList.FindShadows( &view, leafCount, pLeafList ); + if ( nCount == 0 ) + return; + + // FIXME: Add heuristics based on distance, etc. to futz with + // the shadow allocator + to select blobby shadows + + CMatRenderContextPtr pRenderContext( materials ); + + PIXEVENT( pRenderContext, "Render-To-Texture Shadows" ); + + // Clear to white (color unused), black alpha + pRenderContext->ClearColor4ub( 255, 255, 255, 0 ); + + // No height clip mode please. + MaterialHeightClipMode_t oldHeightClipMode = pRenderContext->GetHeightClipMode(); + pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE ); + + // No projection matrix (orthographic mode) + // FIXME: Make it work for projective shadows? + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->Scale( 1, -1, 1 ); + pRenderContext->Ortho( 0, 0, 1, 1, -9999, 0 ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + + pRenderContext->PushRenderTargetAndViewport( m_ShadowAllocator.GetTexture() ); + + if ( !IsX360() && m_bRenderTargetNeedsClear ) + { + // don't need to clear absent depth buffer + pRenderContext->ClearBuffers( true, false ); + m_bRenderTargetNeedsClear = false; + } + + int nMaxShadows = r_shadowmaxrendered.GetInt(); + int nModelsRendered = 0; + int i; + + if ( m_bThreaded && g_pThreadPool->NumIdleThreads() ) + { + s_NPCShadowBoneSetups.RemoveAll(); + s_NonNPCShadowBoneSetups.RemoveAll(); + + for (i = 0; i < nCount; ++i) + { + const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); + if ( nModelsRendered < nMaxShadows ) + { + if ( BuildSetupListForRenderToTextureShadow( info.m_hShadow, info.m_flArea ) ) + { + ++nModelsRendered; + } + } + } + + ParallelProcess( "NPCShadowBoneSetups", s_NPCShadowBoneSetups.Base(), s_NPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating ); + ParallelProcess( "NonNPCShadowBoneSetups", s_NonNPCShadowBoneSetups.Base(), s_NonNPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating ); + + nModelsRendered = 0; + } + + for (i = 0; i < nCount; ++i) + { + const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); + if ( nModelsRendered < nMaxShadows ) + { + if ( DrawRenderToTextureShadow( info.m_hShadow, info.m_flArea ) ) + { + ++nModelsRendered; + } + } + else + { + DrawRenderToTextureShadowLOD( info.m_hShadow ); + } + } + + // Render to the backbuffer again + pRenderContext->PopRenderTargetAndViewport(); + + // Restore the matrices + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + + pRenderContext->SetHeightClipMode( oldHeightClipMode ); + + pRenderContext->SetHeightClipMode( oldHeightClipMode ); + + // Restore the clear color + pRenderContext->ClearColor3ub( 0, 0, 0 ); +} + +//------------------------------------------------------------------------------------------------------- +// Lock down the usage of a shadow depth texture...must be unlocked for use on subsequent views / frames +//------------------------------------------------------------------------------------------------------- +bool CClientShadowMgr::LockShadowDepthTexture( CTextureReference *shadowDepthTexture ) +{ + for ( int i=0; i < m_DepthTextureCache.Count(); i++ ) // Search for cached shadow depth texture + { + if ( m_DepthTextureCacheLocks[i] == false ) // If a free one is found + { + *shadowDepthTexture = m_DepthTextureCache[i]; + m_DepthTextureCacheLocks[i] = true; + return true; + } + } + + return false; // Didn't find it... +} + +//------------------------------------------------------------------ +// Unlock shadow depth texture for use on subsequent views / frames +//------------------------------------------------------------------ +void CClientShadowMgr::UnlockAllShadowDepthTextures() +{ + for (int i=0; i< m_DepthTextureCache.Count(); i++ ) + { + m_DepthTextureCacheLocks[i] = false; + } + SetViewFlashlightState( 0, NULL ); +} + +void CClientShadowMgr::SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity ) +{ + Assert( m_Shadows.IsValidIndex( shadowHandle ) ); + + CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + return; + +// shadow.m_pTargetRenderable = pRenderable; + shadow.m_hTargetEntity = targetEntity; +} + + +void CClientShadowMgr::SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ) +{ + Assert( m_Shadows.IsValidIndex( shadowHandle ) ); + + ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + return; + + if ( bLightWorld ) + { + shadow.m_Flags |= SHADOW_FLAGS_LIGHT_WORLD; + } + else + { + shadow.m_Flags &= ~SHADOW_FLAGS_LIGHT_WORLD; + } +} + + +bool CClientShadowMgr::IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable ) +{ + ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + + if( shadow.m_hTargetEntity->GetClientRenderable() == pRenderable ) + return true; + + C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild(); + while( pChild ) + { + if( pChild->GetClientRenderable()==pRenderable ) + return true; + + pChild = pChild->NextMovePeer(); + } + + return false; +} + +#ifdef DYNAMIC_RTT_SHADOWS +void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnable ) +{ + bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights(); + m_bShadowFromWorldLights = bEnable; + if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() ) + { + UpdateAllShadows(); + } +} + +void CClientShadowMgr::SuppressShadowFromWorldLights( bool bSuppress ) +{ + bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights(); + m_bSuppressShadowFromWorldLights = bSuppress; + if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() ) + { + UpdateAllShadows(); + } +} +#endif + +//----------------------------------------------------------------------------- +// A material proxy that resets the base texture to use the rendered shadow +//----------------------------------------------------------------------------- +class CShadowProxy : public IMaterialProxy +{ +public: + CShadowProxy(); + virtual ~CShadowProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pProxyData ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar* m_BaseTextureVar; +}; + +CShadowProxy::CShadowProxy() +{ + m_BaseTextureVar = NULL; +} + +CShadowProxy::~CShadowProxy() +{ +} + + +bool CShadowProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + return foundVar; +} + +void CShadowProxy::OnBind( void *pProxyData ) +{ + unsigned short clientShadowHandle = ( unsigned short )(int)pProxyData&0xffff; + ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle ); + m_BaseTextureVar->SetTextureValue( pTex ); + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CShadowProxy::GetMaterial() +{ + return m_BaseTextureVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CShadowProxy, IMaterialProxy, "Shadow" IMATERIAL_PROXY_INTERFACE_VERSION ); + + + +//----------------------------------------------------------------------------- +// A material proxy that resets the base texture to use the rendered shadow +//----------------------------------------------------------------------------- +class CShadowModelProxy : public IMaterialProxy +{ +public: + CShadowModelProxy(); + virtual ~CShadowModelProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pProxyData ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar* m_BaseTextureVar; + IMaterialVar* m_BaseTextureOffsetVar; + IMaterialVar* m_BaseTextureScaleVar; + IMaterialVar* m_BaseTextureMatrixVar; + IMaterialVar* m_FalloffOffsetVar; + IMaterialVar* m_FalloffDistanceVar; + IMaterialVar* m_FalloffAmountVar; +}; + +CShadowModelProxy::CShadowModelProxy() +{ + m_BaseTextureVar = NULL; + m_BaseTextureOffsetVar = NULL; + m_BaseTextureScaleVar = NULL; + m_BaseTextureMatrixVar = NULL; + m_FalloffOffsetVar = NULL; + m_FalloffDistanceVar = NULL; + m_FalloffAmountVar = NULL; +} + +CShadowModelProxy::~CShadowModelProxy() +{ +} + + +bool CShadowModelProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureOffsetVar = pMaterial->FindVar( "$basetextureoffset", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureScaleVar = pMaterial->FindVar( "$basetexturescale", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureMatrixVar = pMaterial->FindVar( "$basetexturetransform", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffOffsetVar = pMaterial->FindVar( "$falloffoffset", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffDistanceVar = pMaterial->FindVar( "$falloffdistance", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffAmountVar = pMaterial->FindVar( "$falloffamount", &foundVar, false ); + return foundVar; +} + +void CShadowModelProxy::OnBind( void *pProxyData ) +{ + unsigned short clientShadowHandle = ( unsigned short )((int)pProxyData&0xffff); + ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle ); + m_BaseTextureVar->SetTextureValue( pTex ); + + const ShadowInfo_t& info = s_ClientShadowMgr.GetShadowInfo( clientShadowHandle ); + m_BaseTextureMatrixVar->SetMatrixValue( info.m_WorldToShadow ); + m_BaseTextureOffsetVar->SetVecValue( info.m_TexOrigin.Base(), 2 ); + m_BaseTextureScaleVar->SetVecValue( info.m_TexSize.Base(), 2 ); + m_FalloffOffsetVar->SetFloatValue( info.m_FalloffOffset ); + m_FalloffDistanceVar->SetFloatValue( info.m_MaxDist ); + m_FalloffAmountVar->SetFloatValue( info.m_FalloffAmount ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CShadowModelProxy::GetMaterial() +{ + return m_BaseTextureVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CShadowModelProxy, IMaterialProxy, "ShadowModel" IMATERIAL_PROXY_INTERFACE_VERSION );