From 6c8514be1b512e683f5a38ee4c0d38add3697b62 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:23:22 +0100 Subject: [PATCH 1/9] initial --- src/game/client/CMakeLists.txt | 2 + src/game/client/clientshadowmgr.cpp | 273 +++++++++++++++- src/game/client/neo/worldlight.cpp | 301 ++++++++++++++++++ src/game/client/neo/worldlight.h | 48 +++ .../weapons/weapon_neobasecombatweapon.cpp | 2 + 5 files changed, 623 insertions(+), 3 deletions(-) create mode 100644 src/game/client/neo/worldlight.cpp create mode 100644 src/game/client/neo/worldlight.h diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index f8e5773785..777130a197 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1517,6 +1517,8 @@ target_sources_grouped( neo/neo_fixup_glshaders.cpp neo/neo_fixup_glshaders.h neo/c_neo_bloom_controller.cpp + neo/worldlight.cpp + neo/worldlight.h ) target_sources_grouped( diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index a55eecae63..6c3a63ab24 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -81,6 +81,10 @@ #include "toolframework_client.h" #include "bonetoworldarray.h" #include "cmodel.h" +#ifdef NEO +#include "debugoverlay_shared.h" +#include "worldlight.h" +#endif // memdbgon must be the last include file in a .cpp file!!! @@ -90,6 +94,14 @@ static ConVar r_flashlightdrawfrustum( "r_flashlightdrawfrustum", "0" ); static ConVar r_flashlightmodels( "r_flashlightmodels", "1" ); static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" ); static ConVar r_flashlight_version2( "r_flashlight_version2", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +#ifdef NEO +void WorldLightCastShadowCallback(IConVar* pVar, const char* pszOldValue, float flOldValue); +static ConVar r_worldlight_castshadows("r_worldlight_castshadows", "1", FCVAR_CHEAT, "Allow world lights to cast shadows", true, 0, true, 1, WorldLightCastShadowCallback); +static ConVar r_worldlight_lerptime("r_worldlight_lerptime", "0.5", FCVAR_CHEAT); +static ConVar r_worldlight_debug("r_worldlight_debug", "0", FCVAR_CHEAT); +static ConVar r_worldlight_shortenfactor("r_worldlight_shortenfactor", "2", FCVAR_CHEAT, "Makes shadows cast from local lights shorter"); +static ConVar r_worldlight_mincastintensity("r_worldlight_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0); +#endif ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1", FCVAR_ALLOWED_IN_COMPETITIVE ); @@ -782,7 +794,11 @@ class CClientShadowMgr : public IClientShadowMgr void RestoreRenderState(); // Computes a rough bounding box encompassing the volume of the shadow +#ifdef NEO + 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 ); @@ -793,6 +809,11 @@ class CClientShadowMgr : public IClientShadowMgr { r_shadows_gamecontrol.SetValue( bDisabled != 1 ); } +#ifdef NEO + void SuppressShadowFromWorldLights(bool bSuppress); + void SetShadowFromWorldLightsEnabled(bool bEnabled); + bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights; } +#endif private: enum @@ -811,8 +832,16 @@ class CClientShadowMgr : public IClientShadowMgr unsigned short m_Flags; VMatrix m_WorldToShadow; Vector2D m_WorldSize; +#ifdef NEO + Vector m_ShadowDir; +#endif Vector m_LastOrigin; QAngle m_LastAngles; +#ifdef NEO + 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; @@ -910,6 +939,9 @@ class CClientShadowMgr : public IClientShadowMgr // Returns renderable-specific shadow info float GetShadowDistance( IClientRenderable *pRenderable ) const; const Vector &GetShadowDirection( IClientRenderable *pRenderable ) const; +#ifdef NEO + const Vector &GetShadowDirection( ClientShadowHandle_t shadowHandle ) const; +#endif // Initialize, shutdown render-to-texture shadows void InitDepthTextureShadows(); @@ -943,6 +975,10 @@ class CClientShadowMgr : public IClientShadowMgr // Sets the view's active flashlight render state void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ); +#ifdef NEO + void UpdateDirtyShadow( ClientShadowHandle_t handle ); + void UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ); +#endif private: Vector m_SimpleShadowDir; @@ -971,6 +1007,9 @@ class CClientShadowMgr : public IClientShadowMgr CUtlVector< CTextureReference > m_DepthTextureCache; CUtlVector< bool > m_DepthTextureCacheLocks; int m_nMaxDepthTextureShadows; +#ifdef NEO + bool m_bShadowFromWorldLights; +#endif friend class CVisibleShadowList; friend class CVisibleShadowFrustumList; @@ -1088,7 +1127,11 @@ void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle ) // Compute a box surrounding the shadow Vector vecAbsMins, vecAbsMaxs; +#ifdef NEO + 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? @@ -1172,6 +1215,9 @@ CClientShadowMgr::CClientShadowMgr() : { m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); m_bThreaded = false; +#ifdef NEO + m_bShadowFromWorldLights = r_worldlight_castshadows.GetBool(); +#endif } @@ -1818,6 +1864,12 @@ ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandl shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow( h, flags ); shadow.m_Flags = flags; shadow.m_nRenderFrame = -1; +#ifdef NEO + 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 ) != @@ -2288,8 +2340,11 @@ void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable, Vector vec[3]; AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); vec[1] *= -1.0f; - +#ifdef NEO + Vector vecShadowDir = GetShadowDirection( handle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif // Project the shadow casting direction into the space of the object Vector localShadowDir; @@ -2474,8 +2529,11 @@ void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderabl Vector vec[3]; AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); vec[1] *= -1.0f; - +#ifdef NEO + Vector vecShadowDir = GetShadowDirection( handle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif // Debugging aid // const model_t *pModel = pRenderable->GetModel(); @@ -2953,8 +3011,12 @@ void CClientShadowMgr::PreRender() while ( i != m_DirtyShadows.InvalidIndex() ) { ClientShadowHandle_t& handle = m_DirtyShadows[ i ]; +#ifdef NEO + UpdateDirtyShadow( handle ); +#else Assert( m_Shadows.IsValidIndex( handle ) ); UpdateProjectedTextureInternal( handle, false ); +#endif i = m_DirtyShadows.NextInorder(i); } m_DirtyShadows.RemoveAll(); @@ -3144,7 +3206,11 @@ void CClientShadowMgr::UpdateShadow( ClientShadowHandle_t handle, bool force ) const Vector& origin = pRenderable->GetRenderOrigin(); const QAngle& angles = pRenderable->GetRenderAngles(); +#ifdef NEO + 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 ); @@ -3263,12 +3329,19 @@ void CClientShadowMgr::ComputeBoundingSphere( IClientRenderable* pRenderable, Ve //----------------------------------------------------------------------------- // Computes a rough AABB encompassing the volume of the shadow //----------------------------------------------------------------------------- +#ifdef NEO +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 NEO + Vector vecShadowDir = GetShadowDirection( shadowHandle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif for (int i = 0; i < 3; ++i) { float flShadowCastDistance = GetShadowDistance( pRenderable ); @@ -3366,7 +3439,11 @@ bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRendera if (foundSeparatingPlane) { // Compute which side of the plane the renderable is on.. +#ifdef NEO + 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 ); @@ -4201,6 +4278,196 @@ bool CClientShadowMgr::IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IC return false; } +#ifdef NEO +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +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; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +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_worldlight_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_worldlight_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_worldlight_shortenfactor.GetFloat(); + vecResult.NormalizeInPlace(); + + shadow.m_ShadowDir = vecResult; + + if (r_worldlight_debug.GetBool()) + { + NDebugOverlay::Line(lightPos, origin, 255, 255, 0, false, 0.0f); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateDirtyShadow(ClientShadowHandle_t handle) +{ + Assert(m_Shadows.IsValidIndex(handle)); + + if (IsShadowingFromWorldLights()) + UpdateShadowDirectionFromLocalLightSource(handle); + + UpdateProjectedTextureInternal(handle, false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WorldLightCastShadowCallback(IConVar* pVar, const char* pszOldValue, float flOldValue) +{ + s_ClientShadowMgr.SetShadowFromWorldLightsEnabled(r_worldlight_castshadows.GetBool()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowFromWorldLightsEnabled(bool bEnabled) +{ + if (bEnabled == IsShadowingFromWorldLights()) + return; + + m_bShadowFromWorldLights = bEnabled; + UpdateAllShadows(); +} +#endif + //----------------------------------------------------------------------------- // A material proxy that resets the base texture to use the rendered shadow //----------------------------------------------------------------------------- diff --git a/src/game/client/neo/worldlight.cpp b/src/game/client/neo/worldlight.cpp new file mode 100644 index 0000000000..10a12898dd --- /dev/null +++ b/src/game/client/neo/worldlight.cpp @@ -0,0 +1,301 @@ +//========= Copyright (C) 2021, CSProMod Team, All rights reserved. =========// +// +// Purpose: Provide world light-related functions to the client +// +// As the engine provides no access to brush/model data (brushdata_t, model_t), +// we hence have no access to dworldlight_t. Therefore, we manually extract the +// world light data from the BSP itself, before entities are initialized on map +// load. +// +// To find the brightest light at a point, all world lights are iterated. +// Lights whose radii do not encompass our sample point are quickly rejected, +// as are lights that are not in our PVS, or visible from the sample point. +// If the sky light is visible from the sample point, then it shall supersede +// all other world lights. +// +// Written: November 2011 +// Author: Saul Rennison +// +//===========================================================================// + +#include "cbase.h" +#include "bspfile.h" +#include "client_factorylist.h" // FactoryList_Retrieve +#include "eiface.h" // IVEngineServer +#include "filesystem.h" +#include "worldlight.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static IVEngineServer* g_pEngineServer = nullptr; + +//----------------------------------------------------------------------------- +// Purpose: Calculate intensity ratio for a worldlight by distance +//----------------------------------------------------------------------------- +static float Engine_WorldLightDistanceFalloff(const dworldlight_t* wl, const Vector& delta) +{ + float falloff; + + switch (wl->type) + { + case emit_surface: + // Cull out stuff that's too far + if (wl->radius != 0) + { + if (DotProduct(delta, delta) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); + + case emit_skylight: + return 1.0f; + + case emit_quakelight: + // X - r; + falloff = wl->linear_attn - FastSqrt(DotProduct(delta, delta)); + if (falloff < 0) + return 0.0f; + + return falloff; + + case emit_skyambient: + return 1.0f; + + case emit_point: + case emit_spotlight: // Directional & positional + { + float dist2, dist; + + dist2 = DotProduct(delta, delta); + dist = FastSqrt(dist2); + + // Cull out stuff that's too far + if (wl->radius != 0 && dist > wl->radius) + return 0.0f; + + return 1.0f / (wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2); + } + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize game system and members +//----------------------------------------------------------------------------- +CWorldLights::CWorldLights() : CAutoGameSystem("World lights") +{ + m_nWorldLights = 0; + m_pWorldLights = nullptr; +} + +//----------------------------------------------------------------------------- +// Purpose: Clear worldlights, free memory +//----------------------------------------------------------------------------- +void CWorldLights::Clear() +{ + m_nWorldLights = 0; + + if (m_pWorldLights) + { + delete[] m_pWorldLights; + m_pWorldLights = nullptr; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the IVEngineServer, we need this for the PVS functions +//----------------------------------------------------------------------------- +bool CWorldLights::Init() +{ + factorylist_t factories; + FactoryList_Retrieve(factories); + + if ((g_pEngineServer = static_cast(factories.appSystemFactory(INTERFACEVERSION_VENGINESERVER, nullptr))) == nullptr) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Get all world lights from the BSP +//----------------------------------------------------------------------------- +void CWorldLights::LevelInitPreEntity() +{ + // Get the map path + const char* pszMapName = modelinfo->GetModelName(modelinfo->GetModel(1)); + + // Open map + FileHandle_t hFile = g_pFullFileSystem->Open(pszMapName, "rb"); + if (!hFile) + { + Warning("CWorldLights: Unable to open map\n"); + return; + } + + // Read the BSP header. We don't need to do any version checks, etc. as we + // can safely assume that the engine did this for us + dheader_t hdr; + g_pFullFileSystem->Read(&hdr, sizeof(hdr), hFile); + + // Grab the light lump and seek to it + lump_t& lightLump = hdr.lumps[LUMP_WORLDLIGHTS]; + + // If the worldlights lump is empty, that means theres no normal, LDR lights to extract + // This can happen when, for example, the map is compiled in HDR mode only + // So move on to the HDR worldlights lump + if (lightLump.filelen == 0) + { + lightLump = hdr.lumps[LUMP_WORLDLIGHTS_HDR]; + } + + // If we can't divide the lump data into a whole number of worldlights, + // then the BSP format changed and we're unaware + if (lightLump.filelen % sizeof(dworldlight_t)) + { + Warning("CWorldLights: Unknown world light lump\n"); + + // Close file + g_pFullFileSystem->Close(hFile); + return; + } + + g_pFullFileSystem->Seek(hFile, lightLump.fileofs, FILESYSTEM_SEEK_HEAD); + + // Allocate memory for the worldlights + m_nWorldLights = lightLump.filelen / sizeof(dworldlight_t); + m_pWorldLights = new dworldlight_t[m_nWorldLights]; + + // Read worldlights then close + g_pFullFileSystem->Read(m_pWorldLights, lightLump.filelen, hFile); + g_pFullFileSystem->Close(hFile); + + DevMsg("CWorldLights: Load successful (%d lights at 0x%p)\n", m_nWorldLights, m_pWorldLights); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the brightest light source at a point +//----------------------------------------------------------------------------- +bool CWorldLights::GetBrightestLightSource(const Vector& vecPosition, Vector& vecLightPos, Vector& vecLightBrightness) +{ + if (!m_nWorldLights || !m_pWorldLights) + return false; + + // Default light position and brightness to zero + vecLightBrightness.Init(); + vecLightPos.Init(); + + // Find the size of the PVS for our current position + int nCluster = g_pEngineServer->GetClusterForOrigin(vecPosition); + int nPVSSize = g_pEngineServer->GetPVSForCluster(nCluster, 0, nullptr); + + // Get the PVS at our position + byte* pvs = new byte[nPVSSize]; + g_pEngineServer->GetPVSForCluster(nCluster, nPVSSize, pvs); + + // Iterate through all the worldlights + for (int i = 0; i < m_nWorldLights; ++i) + { + dworldlight_t* light = &m_pWorldLights[i]; + + // Skip skyambient + if (light->type == emit_skyambient) + { + //engine->Con_NPrintf( i, "%d: skyambient", i ); + continue; + } + + // Handle sun + if (light->type == emit_skylight) + { + // Calculate sun position + Vector vecAbsStart = vecPosition + Vector(0, 0, 30); + Vector vecAbsEnd = vecAbsStart - (light->normal * MAX_TRACE_LENGTH); + + trace_t tr; + UTIL_TraceLine(vecPosition, vecAbsEnd, MASK_OPAQUE, nullptr, COLLISION_GROUP_NONE, &tr); + + // If we didn't hit anything then we have a problem + if (!tr.DidHit()) + { + //engine->Con_NPrintf( i, "%d: skylight: couldn't touch sky", i ); + continue; + } + + // If we did hit something, and it wasn't the skybox, then skip + // this worldlight + if (!(tr.surface.flags & SURF_SKY) && !(tr.surface.flags & SURF_SKY2D)) + { + //engine->Con_NPrintf( i, "%d: skylight: no sight to sun", i ); + continue; + } + + // Act like we didn't find any valid worldlights, so the shadow + // manager uses the default shadow direction instead (should be the + // sun direction) + + delete[] pvs; + + return false; + } + + // Calculate square distance to this worldlight + Vector vecDelta = light->origin - vecPosition; + float flDistSqr = vecDelta.LengthSqr(); + float flRadiusSqr = light->radius * light->radius; + + // Skip lights that are out of our radius + if (flRadiusSqr > 0 && flDistSqr >= flRadiusSqr) + { + //engine->Con_NPrintf( i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt( flDistSqr ), light->radius ); + continue; + } + + // Is it out of our PVS? + if (!g_pEngineServer->CheckOriginInPVS(light->origin, pvs, nPVSSize)) + { + //engine->Con_NPrintf( i, "%d: out of PVS", i ); + continue; + } + + // Calculate intensity at our position + float flRatio = Engine_WorldLightDistanceFalloff(light, vecDelta); + Vector vecIntensity = light->intensity * flRatio; + + // Is this light more intense than the one we already found? + if (vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr()) + { + //engine->Con_NPrintf( i, "%d: too dim", i ); + continue; + } + + // Can we see the light? + trace_t tr; + Vector vecAbsStart = vecPosition + Vector(0, 0, 30); + UTIL_TraceLine(vecAbsStart, light->origin, MASK_OPAQUE, nullptr, COLLISION_GROUP_NONE, &tr); + + if (tr.DidHit()) + { + //engine->Con_NPrintf( i, "%d: trace failed", i ); + continue; + } + + vecLightPos = light->origin; + vecLightBrightness = vecIntensity; + + //engine->Con_NPrintf( i, "%d: set (%.2f)", i, vecIntensity.Length() ); + } + + delete[] pvs; + + //engine->Con_NPrintf( m_nWorldLights, "result: %d", !vecLightBrightness.IsZero() ); + return !vecLightBrightness.IsZero(); +} + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +static CWorldLights s_WorldLights; +CWorldLights* g_pWorldLights = &s_WorldLights; \ No newline at end of file diff --git a/src/game/client/neo/worldlight.h b/src/game/client/neo/worldlight.h new file mode 100644 index 0000000000..b11a80932e --- /dev/null +++ b/src/game/client/neo/worldlight.h @@ -0,0 +1,48 @@ +//========= Copyright (C) 2021, CSProMod Team, All rights reserved. =========// +// +// Purpose: Provide world light-related functions to the client +// +// Written: November 2011 +// Author: Saul Rennison +// +//===========================================================================// + +#ifndef WORLDLIGHT_H +#define WORLDLIGHT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igamesystem.h" // CAutoGameSystem + +class Vector; +struct dworldlight_t; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWorldLights : public CAutoGameSystem +{ +public: + CWorldLights(); + ~CWorldLights() { Clear(); } + + bool GetBrightestLightSource(const Vector& vecPosition, Vector& vecLightPos, Vector& vecLightBrightness); + + // CAutoGameSystem overrides + bool Init() OVERRIDE; + void LevelInitPreEntity() OVERRIDE; + void LevelShutdownPostEntity() OVERRIDE { Clear(); } + +private: + void Clear(); + +private: + int m_nWorldLights; + dworldlight_t* m_pWorldLights; +}; + +// Singleton accessor +extern CWorldLights* g_pWorldLights; + +#endif // WORLDLIGHT_H \ No newline at end of file diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index c6ebf23da1..91ba2c35d4 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -345,9 +345,11 @@ void CNEOBaseCombatWeapon::Equip(CBaseCombatCharacter* pOwner) } else if (weapon & NEO_WEP_KNIFE) { + AddEffects(EF_NOSHADOW); return; } else + RemoveEffects(EF_NOSHADOW); SetParentAttachment("SetParentAttachment", "primary", false); #endif } From bc21ef2eed250d3152730750b12da7bc9cc10f92 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Thu, 31 Jul 2025 21:20:49 +0100 Subject: [PATCH 2/9] backport toggle on shadow_control --- src/game/client/c_shadowcontrol.cpp | 9 +++++++++ src/game/client/clientshadowmgr.cpp | 4 ++-- src/game/client/iclientshadowmgr.h | 4 ++++ src/game/server/shadowcontrol.cpp | 9 +++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/game/client/c_shadowcontrol.cpp b/src/game/client/c_shadowcontrol.cpp index 801692418b..62d87a3bce 100644 --- a/src/game/client/c_shadowcontrol.cpp +++ b/src/game/client/c_shadowcontrol.cpp @@ -32,6 +32,9 @@ class C_ShadowControl : public C_BaseEntity color32 m_shadowColor; float m_flShadowMaxDist; bool m_bDisableShadows; +#ifdef NEO + bool m_bLocalLightShadows; +#endif }; IMPLEMENT_CLIENTCLASS_DT(C_ShadowControl, DT_ShadowControl, CShadowControl) @@ -39,6 +42,9 @@ IMPLEMENT_CLIENTCLASS_DT(C_ShadowControl, DT_ShadowControl, CShadowControl) RecvPropInt(RECVINFO(m_shadowColor)), RecvPropFloat(RECVINFO(m_flShadowMaxDist)), RecvPropBool(RECVINFO(m_bDisableShadows)), +#ifdef NEO + RecvPropBool(RECVINFO(m_bLocalLightShadows)), +#endif END_RECV_TABLE() @@ -54,6 +60,9 @@ void C_ShadowControl::OnDataChanged(DataUpdateType_t updateType) g_pClientShadowMgr->SetShadowColor( m_shadowColor.r, m_shadowColor.g, m_shadowColor.b ); g_pClientShadowMgr->SetShadowDistance( m_flShadowMaxDist ); g_pClientShadowMgr->SetShadowsDisabled( m_bDisableShadows ); +#ifdef NEO + g_pClientShadowMgr->SetShadowFromWorldLightsEnabled( m_bLocalLightShadows ); +#endif } //------------------------------------------------------------------------------ diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index 6c3a63ab24..f1b7d85d29 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -810,8 +810,8 @@ class CClientShadowMgr : public IClientShadowMgr r_shadows_gamecontrol.SetValue( bDisabled != 1 ); } #ifdef NEO - void SuppressShadowFromWorldLights(bool bSuppress); - void SetShadowFromWorldLightsEnabled(bool bEnabled); + void SuppressShadowFromWorldLights( bool bSuppress ); + void SetShadowFromWorldLightsEnabled( bool bEnabled ); bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights; } #endif diff --git a/src/game/client/iclientshadowmgr.h b/src/game/client/iclientshadowmgr.h index 174f57a8dc..b0bcb743aa 100644 --- a/src/game/client/iclientshadowmgr.h +++ b/src/game/client/iclientshadowmgr.h @@ -99,6 +99,10 @@ abstract_class IClientShadowMgr : public IGameSystemPerFrame virtual void SetShadowsDisabled( bool bDisabled ) = 0; +#ifdef NEO + virtual void SetShadowFromWorldLightsEnabled( bool bEnabled ); +#endif + virtual void ComputeShadowDepthTextures( const CViewSetup &pView ) = 0; }; diff --git a/src/game/server/shadowcontrol.cpp b/src/game/server/shadowcontrol.cpp index 36c23c28d3..1778c6f622 100644 --- a/src/game/server/shadowcontrol.cpp +++ b/src/game/server/shadowcontrol.cpp @@ -40,6 +40,9 @@ class CShadowControl : public CBaseEntity CNetworkColor32( m_shadowColor ); CNetworkVar( float, m_flShadowMaxDist ); CNetworkVar( bool, m_bDisableShadows ); +#ifdef NEO + CNetworkVar(bool, m_bLocalLightShadows); +#endif }; LINK_ENTITY_TO_CLASS(shadow_control, CShadowControl); @@ -48,6 +51,9 @@ BEGIN_DATADESC( CShadowControl ) DEFINE_KEYFIELD( m_flShadowMaxDist, FIELD_FLOAT, "distance" ), DEFINE_KEYFIELD( m_bDisableShadows, FIELD_BOOLEAN, "disableallshadows" ), +#ifdef NEO + DEFINE_KEYFIELD( m_bLocalLightShadows, FIELD_BOOLEAN, "enableshadowsfromlocallights" ), +#endif // Inputs DEFINE_INPUT( m_shadowColor, FIELD_COLOR32, "color" ), @@ -74,6 +80,9 @@ CShadowControl::CShadowControl() m_flShadowMaxDist = 50.0f; m_shadowColor.Init( 64, 64, 64, 0 ); m_bDisableShadows = false; +#ifdef NEO + m_bLocalLightShadows = true; +#endif } From 93659c64f8edb92d9a7b8bde29ea6c931ddeb774 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Thu, 31 Jul 2025 21:23:55 +0100 Subject: [PATCH 3/9] forgot --- src/game/client/iclientshadowmgr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/iclientshadowmgr.h b/src/game/client/iclientshadowmgr.h index b0bcb743aa..3621e415d7 100644 --- a/src/game/client/iclientshadowmgr.h +++ b/src/game/client/iclientshadowmgr.h @@ -100,7 +100,7 @@ abstract_class IClientShadowMgr : public IGameSystemPerFrame virtual void SetShadowsDisabled( bool bDisabled ) = 0; #ifdef NEO - virtual void SetShadowFromWorldLightsEnabled( bool bEnabled ); + virtual void SetShadowFromWorldLightsEnabled( bool bEnabled ) = 0; #endif virtual void ComputeShadowDepthTextures( const CViewSetup &pView ) = 0; From a021b8391252c11eb8c821e8bf2b1cc4a790d5f4 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:37:33 +0100 Subject: [PATCH 4/9] forgot to send bool --- src/game/server/shadowcontrol.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/game/server/shadowcontrol.cpp b/src/game/server/shadowcontrol.cpp index 1778c6f622..734729a7f9 100644 --- a/src/game/server/shadowcontrol.cpp +++ b/src/game/server/shadowcontrol.cpp @@ -41,7 +41,7 @@ class CShadowControl : public CBaseEntity CNetworkVar( float, m_flShadowMaxDist ); CNetworkVar( bool, m_bDisableShadows ); #ifdef NEO - CNetworkVar(bool, m_bLocalLightShadows); + CNetworkVar( bool, m_bLocalLightShadows ); #endif }; @@ -71,6 +71,9 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE(CShadowControl, DT_ShadowControl) SendPropInt(SENDINFO(m_shadowColor), 32, SPROP_UNSIGNED), SendPropFloat(SENDINFO(m_flShadowMaxDist), 0, SPROP_NOSCALE ), SendPropBool(SENDINFO(m_bDisableShadows)), +#ifdef NEO + SendPropBool(SENDINFO(m_bLocalLightShadows)) +#endif END_SEND_TABLE() From a68bffc7c9866c8daac4dcf1006cce4eecec7f18 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:45:04 +0100 Subject: [PATCH 5/9] stop sweeping shadows --- src/game/client/clientshadowmgr.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index f1b7d85d29..25b75cdb3c 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -97,7 +97,7 @@ static ConVar r_flashlight_version2( "r_flashlight_version2", "0", FCVAR_CHEAT | #ifdef NEO void WorldLightCastShadowCallback(IConVar* pVar, const char* pszOldValue, float flOldValue); static ConVar r_worldlight_castshadows("r_worldlight_castshadows", "1", FCVAR_CHEAT, "Allow world lights to cast shadows", true, 0, true, 1, WorldLightCastShadowCallback); -static ConVar r_worldlight_lerptime("r_worldlight_lerptime", "0.5", FCVAR_CHEAT); +static ConVar r_worldlight_lerpmaxobjectvel("r_worldlight_lerpmaxobjectvel", "130.0", FCVAR_CHEAT, "The speed the object must be moving in order to make the transition between lights reach maximum speed"); static ConVar r_worldlight_debug("r_worldlight_debug", "0", FCVAR_CHEAT); static ConVar r_worldlight_shortenfactor("r_worldlight_shortenfactor", "2", FCVAR_CHEAT, "Makes shadows cast from local lights shorter"); static ConVar r_worldlight_mincastintensity("r_worldlight_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0); @@ -4344,6 +4344,12 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan } } + // NEO NOTE DG: Comparing the origin to the last origin isnt viable because prediction(?) + // makes players never still. lets check the speed to see if they are moving + Vector delta = pRenderable->GetRenderOrigin() - shadow.m_LastOrigin; + float speed = delta.Length() / gpGlobals->frametime; + float normalisedSpeed = clamp( speed / r_worldlight_lerpmaxobjectvel.GetFloat(), 0.0f, 0.5f); + if (shadow.m_LightPosLerp == FLT_MAX) // First light pos ever, just init { shadow.m_CurrentLightPos = lightPos; @@ -4352,9 +4358,12 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan } 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_worldlight_lerptime.GetFloat(); - shadow.m_LightPosLerp = clamp(shadow.m_LightPosLerp, 0.0f, 1.0f); + // We're in the middle of a lerp from current to target light. Finish it if they aren't moving. + if (speed > 20.0f) + { + shadow.m_LightPosLerp += gpGlobals->frametime * normalisedSpeed; + shadow.m_LightPosLerp = clamp(shadow.m_LightPosLerp, 0.0f, 1.0f); + } Vector currLightPos(shadow.m_CurrentLightPos); Vector targetLightPos(shadow.m_TargetLightPos); From 44e757b352a5aa5af151a076eac0e97740427a94 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:09:43 +0100 Subject: [PATCH 6/9] fix misinterpret --- src/game/shared/neo/weapons/weapon_knife.cpp | 13 ++++++++++++- .../neo/weapons/weapon_neobasecombatweapon.cpp | 2 -- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/game/shared/neo/weapons/weapon_knife.cpp b/src/game/shared/neo/weapons/weapon_knife.cpp index c262c54f96..005e7704ca 100644 --- a/src/game/shared/neo/weapons/weapon_knife.cpp +++ b/src/game/shared/neo/weapons/weapon_knife.cpp @@ -113,7 +113,18 @@ bool CWeaponKnife::CanBePickedUpByClass(int classId) bool CWeaponKnife::ShouldDraw() { auto owner = static_cast(GetOwner()); - return (owner && owner->IsAlive() && owner->GetActiveWeapon() == this); + bool draw = (owner && owner->IsAlive() && owner->GetActiveWeapon() == this); + + if (draw) // DG: Disable shadows manually because it doesn't do it on its own + { + RemoveEffects(EF_NOSHADOW); + } + else + { + AddEffects(EF_NOSHADOW); + } + + return draw; } #else bool CWeaponKnife::IsViewable() diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 91ba2c35d4..c6ebf23da1 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -345,11 +345,9 @@ void CNEOBaseCombatWeapon::Equip(CBaseCombatCharacter* pOwner) } else if (weapon & NEO_WEP_KNIFE) { - AddEffects(EF_NOSHADOW); return; } else - RemoveEffects(EF_NOSHADOW); SetParentAttachment("SetParentAttachment", "primary", false); #endif } From 574dcb8522b11d45b4979f25db879d3eaebda1dc Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:31:44 +0100 Subject: [PATCH 7/9] fix flicking about --- src/game/client/clientshadowmgr.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index 25b75cdb3c..dac0551e7f 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -4322,11 +4322,17 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan return; } + // NEO NOTE DG: Using the bounding box for this will make the shadow jump around. Undone below + // They may appear to jump on walls sometimes, but this behaviour is visible on master currently + // NEO TODO: find out whats causing the size to change 3 times when player stops moving +#if 0 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 +#endif + Vector origin = pRenderable->GetRenderOrigin(); Vector lightPos; Vector lightBrightness; From a143a91b9364d91d803c9a7f476f9e44caa5054a Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:37:05 +0100 Subject: [PATCH 8/9] ARE --- src/game/client/clientshadowmgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index dac0551e7f..feec092c37 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -4364,7 +4364,7 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan } else if (shadow.m_LightPosLerp < 1.0f) { - // We're in the middle of a lerp from current to target light. Finish it if they aren't moving. + // We're in the middle of a lerp from current to target light. Finish it if they are moving. if (speed > 20.0f) { shadow.m_LightPosLerp += gpGlobals->frametime * normalisedSpeed; From 0326a07c3f269aaad81c67e0e48c41de01d9a938 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:24:56 +0100 Subject: [PATCH 9/9] avoid trigging lerp to skylight when the origin clipped into the floor it would get the skybox as the brightest light --- src/game/client/clientshadowmgr.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index feec092c37..e6f06eff78 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -4333,6 +4333,7 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan #endif Vector origin = pRenderable->GetRenderOrigin(); + Vector objectCenter = pRenderable->GetIClientUnknown()->GetBaseEntity()->WorldSpaceCenter(); Vector lightPos; Vector lightBrightness; @@ -4342,7 +4343,7 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan float flMinBrightnessSqr = r_worldlight_mincastintensity.GetFloat(); flMinBrightnessSqr *= flMinBrightnessSqr; - if (g_pWorldLights->GetBrightestLightSource(pRenderable->GetRenderOrigin(), lightPos, lightBrightness) == false || lightBrightness.LengthSqr() < flMinBrightnessSqr) + if (g_pWorldLights->GetBrightestLightSource(objectCenter, 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 @@ -4352,7 +4353,7 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan // NEO NOTE DG: Comparing the origin to the last origin isnt viable because prediction(?) // makes players never still. lets check the speed to see if they are moving - Vector delta = pRenderable->GetRenderOrigin() - shadow.m_LastOrigin; + Vector delta = origin - shadow.m_LastOrigin; float speed = delta.Length() / gpGlobals->frametime; float normalisedSpeed = clamp( speed / r_worldlight_lerpmaxobjectvel.GetFloat(), 0.0f, 0.5f); @@ -4445,7 +4446,7 @@ void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource(ClientShadowHan if (r_worldlight_debug.GetBool()) { - NDebugOverlay::Line(lightPos, origin, 255, 255, 0, false, 0.0f); + NDebugOverlay::Line(lightPos, objectCenter, 255, 255, 0, false, 0.0f); } }