Files
HL2Overcharged/game/client/clientshadowmgr.cpp
2025-05-21 21:09:22 +03:00

5013 lines
166 KiB
C++

//========= 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
//#include "filesystem.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar r_flashlightdrawfrustum("r_flashlightdrawfrustum", "0");
static ConVar r_flashlightmodels("r_flashlightmodels", "1");
void UpdateShadowRender();
ConVar r_shadowrendertotexture("r_shadowrendertotexture", "0", FCVAR_ARCHIVE, "", (FnChangeCallback_t)UpdateShadowRender);
void UpdateShadowRender()
{
if (cvar->FindVar("oc_global_lightning_enabled")->GetBool())
r_shadowrendertotexture.SetValue(1);
}
#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);
void UpdateFlashlightdepthtexture();
ConVar r_flashlightdepthtexture("r_flashlightdepthtexture", "1", FCVAR_ARCHIVE, "", (FnChangeCallback_t)UpdateFlashlightdepthtexture);
void UpdateFlashlightdepthtexture()
{
if (cvar->FindVar("oc_global_lightning_enabled")->GetBool())
r_flashlightdepthtexture.SetValue(1);
}
#if defined( _X360 )
ConVar r_flashlightdepthres("r_flashlightdepthres", "512", FCVAR_ARCHIVE); // BJ over twi tweak - archive
#else
#ifdef MAPBASE
ConVar r_flashlightdepthres("r_flashlightdepthres", "2048", FCVAR_ARCHIVE); // BJ over twi tweak - archive
#else
ConVar r_flashlightdepthres("r_flashlightdepthres", "1024", FCVAR_ARCHIVE); // BJ over twi tweak - archive
#endif
#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_ARCHIVE);// 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() { /*m_bEnableDynamicShadows = oc_enable_global_illumination.GetBool(); UpdateGlobalIllum(); */ }
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; }
bool m_bEnableDynamicShadows;
#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();
public:
// Initialize, shutdown render-to-texture shadows
void InitRenderToTextureShadows();
void ShutdownRenderToTextureShadows();
private:
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;
/*void UpdateGlobalIllum()
{*/
/*if (!oc_enable_global_illumination.GetBool())
s_ClientShadowMgr.InitRenderToTextureShadows();
else
s_ClientShadowMgr.ShutdownRenderToTextureShadows();*/
/*r_shadows_gamecontrol.SetValue(oc_enable_global_illumination.GetInt() ? 0 : -1);
}*/
//-----------------------------------------------------------------------------
// 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<VisibleShadowInfo_t> m_ShadowsInView;
CUtlVector<int> m_PriorityIndex;
};
//-----------------------------------------------------------------------------
// Singleton instances of shadow and shadow frustum lists
//-----------------------------------------------------------------------------
static CVisibleShadowList s_VisibleShadowList;
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static CUtlVector<C_BaseAnimating *> s_NPCShadowBoneSetups;
static CUtlVector<C_BaseAnimating *> 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();
}
/*ConVar oc_enable_global_illumination("oc_enable_global_illumination", "0", FCVAR_ARCHIVE, "");
bool GetShadowsValue(void)
{
// Now grab the hl2/cfg/config.cfg and suss out the sv_unlockedchapters cvar to estimate how far they got in HL2
char fullpath[MAX_PATH];
g_pFullFileSystem->RelativePathToFullPath("cfg/config.cfg", "GAME", fullpath, sizeof(fullpath));
Q_FixSlashes(fullpath, '/');
if (filesystem->FileExists(fullpath))
{
FileHandle_t fh = filesystem->Open(fullpath, "rb");
if (FILESYSTEM_INVALID_HANDLE != fh)
{
// read file into memory
int size = filesystem->Size(fh);
char *configBuffer = new char[size + 1];
filesystem->Read(configBuffer, size, fh);
configBuffer[size] = 0;
filesystem->Close(fh);
// loop through looking for all the cvars to apply
const char *search = Q_stristr(configBuffer, "oc_enable_global_illumination");
if (search)
{
// read over the token
search = strtok((char *)search, " \n");
search = strtok(NULL, " \n");
if (search[0] == '\"')
++search;
// read the value
int iValue = Q_atoi(search);
oc_enable_global_illumination.SetValue(iValue);
}
// free
delete[] configBuffer;
}
}
return oc_enable_global_illumination.GetBool();
}*/
//-----------------------------------------------------------------------------
// 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.
int iNumShadows = CommandLine()->ParmValue("-numshadowtextures", 5);
m_nMaxDepthTextureShadows = iNumShadows;
#endif
//CommandLine()->AppendParm("-num_edicts", "20480");
//int idx = CommandLine()->FindParm("-num_edicts");
//CommandLine()->SetParm(idx, "20480");
//CommandLine()->
//const char *val = CommandLine()->ParmValue("-num_edicts");
/*KeyValuesAD gameinfo("GameInfo");
gameinfo->LoadFromFile(filesystem, "gameinfo.txt");*/
m_bEnableDynamicShadows = false;// gameinfo->GetBool("oc_enable_dynamic_shadows", false);
//r_shadows_gamecontrol.SetValue(cvar->FindVar("oc_global_lightning_enabled")->GetBool());
//m_bEnableDynamicShadows = GetShadowsValue();
bool bLowEnd = (g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80);
if (!bLowEnd && r_shadowrendertotexture.GetBool() && !m_bEnableDynamicShadows)
{
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);
}
/*try
{
materials->EndRenderTargetAllocation();
}
catch (...)
{
} */
}
}
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;
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_bEnableDynamicShadows)
{
if (m_RenderToTextureActive)
{
ShutdownRenderToTextureShadows();
}
else
{
InitRenderToTextureShadows();
}
}
else
ShutdownRenderToTextureShadows();
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<model_t*>(pRenderable->GetModel()),
pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles());
shadowmgr->AddFlashlightRenderable(shadow.m_ShadowHandle, pRenderable);
}
}
else
{
shadowmgr->AddShadowToBrushModel(shadow.m_ShadowHandle,
const_cast<model_t*>(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<model_t*>(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<C_BaseEntity*>(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<C_BaseAnimating *>(pEntity));
}
else if (pEntity->GetBaseAnimating())
{
s_NonNPCShadowBoneSetups.AddToTail(assert_cast<C_BaseAnimating *>(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*/ #ifdef 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<FlashlightState_t&>(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);