diff --git a/Dependencies/OpenGL/Gl46.cs b/Dependencies/OpenGL/Gl46.cs index 1916db7f..007c5356 100644 --- a/Dependencies/OpenGL/Gl46.cs +++ b/Dependencies/OpenGL/Gl46.cs @@ -27,6 +27,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System; using System.Text; using System.Security; +using Source.Common.MaterialSystem; using System.Numerics; using System.Runtime.InteropServices; @@ -139,6 +140,32 @@ public static int GLEnum(this ShaderDepthFunc factor) { _ => throw new NotImplementedException() }; } + public static int GLEnum(this StencilOperation op) { + return op switch { + StencilOperation.Keep => GL_KEEP, + StencilOperation.Zero => GL_ZERO, + StencilOperation.Replace => GL_REPLACE, + StencilOperation.IncrSat => GL_INCR, + StencilOperation.DecrSat => GL_DECR, + StencilOperation.Invert => GL_INVERT, + StencilOperation.Incr => GL_INCR_WRAP, + StencilOperation.Decr => GL_DECR_WRAP, + _ => throw new NotImplementedException() + }; + } + public static int GLEnum(this StencilComparisonFunction cmpfn) { + return cmpfn switch { + StencilComparisonFunction.Never => GL_NEVER, + StencilComparisonFunction.Less => GL_LESS, + StencilComparisonFunction.Equal => GL_EQUAL, + StencilComparisonFunction.LessEqual => GL_LEQUAL, + StencilComparisonFunction.Greater => GL_GREATER, + StencilComparisonFunction.NotEqual => GL_NOTEQUAL, + StencilComparisonFunction.GreaterEqual => GL_GEQUAL, + StencilComparisonFunction.Always => GL_ALWAYS, + _ => throw new NotImplementedException() + }; + } public static int GLEnum(this ShaderPolyMode polyMode) { return polyMode switch { ShaderPolyMode.Point => GL_POINT, diff --git a/Game.Assets/hl2/shaders/common_gl460.fs b/Game.Assets/hl2/shaders/common_gl460.fs index 12a7c5a6..ded80b5c 100644 --- a/Game.Assets/hl2/shaders/common_gl460.fs +++ b/Game.Assets/hl2/shaders/common_gl460.fs @@ -15,7 +15,7 @@ #define TCOMBINE_MULTIPLY 8 #define TCOMBINE_MASK_BASE_BY_DETAIL_ALPHA 9 // use alpha channel of detail to mask base #define TCOMBINE_SSBUMP_BUMP 10 // use detail to modulate lighting as an ssbump -#define TCOMBINE_SSBUMP_NOBUMP 11 // detail is an ssbump but use it as an albedo. shader does the magic here - no user needs to specify mode 11 +#define TCOMBINE_SSBUMP_NOBUMP 11 // detail is an ssbump but use it as an albedo. shader does the magic here - no user needs to specify mode 11 vec4 TextureCombine(vec4 baseColor, vec4 detailColor, int combine_mode, float fBlendFactor) { @@ -24,37 +24,29 @@ vec4 TextureCombine(vec4 baseColor, vec4 detailColor, int combine_mode, float fB vec3 dc = vec3(mix(detailColor.r, detailColor.a, baseColor.a)); baseColor.rgb *= mix(vec3(1, 1, 1), 2.0 * dc, fBlendFactor); } - if (combine_mode == TCOMBINE_RGB_EQUALS_BASE_x_DETAILx2) + else if (combine_mode == TCOMBINE_RGB_EQUALS_BASE_x_DETAILx2) baseColor.rgb *= mix(vec3(1, 1, 1), 2.0 * detailColor.rgb, fBlendFactor); - if (combine_mode == TCOMBINE_RGB_ADDITIVE) + else if (combine_mode == TCOMBINE_RGB_ADDITIVE) baseColor.rgb += fBlendFactor * detailColor.rgb; - if (combine_mode == TCOMBINE_DETAIL_OVER_BASE) + else if (combine_mode == TCOMBINE_DETAIL_OVER_BASE) { float fblend = fBlendFactor * detailColor.a; baseColor.rgb = mix(baseColor.rgb, detailColor.rgb, fblend); } - if (combine_mode == TCOMBINE_FADE) - { + else if (combine_mode == TCOMBINE_FADE) baseColor = mix(baseColor, detailColor, fBlendFactor); - } - if (combine_mode == TCOMBINE_BASE_OVER_DETAIL) + else if (combine_mode == TCOMBINE_BASE_OVER_DETAIL) { float fblend = fBlendFactor * (1.0 - baseColor.a); baseColor.rgb = mix(baseColor.rgb, detailColor.rgb, fblend); baseColor.a = detailColor.a; } - if (combine_mode == TCOMBINE_MULTIPLY) - { + else if (combine_mode == TCOMBINE_MULTIPLY) baseColor = mix(baseColor, baseColor * detailColor, fBlendFactor); - } - if (combine_mode == TCOMBINE_MASK_BASE_BY_DETAIL_ALPHA) - { + else if (combine_mode == TCOMBINE_MASK_BASE_BY_DETAIL_ALPHA) baseColor.a = mix(baseColor.a, baseColor.a * detailColor.a, fBlendFactor); - } - if (combine_mode == TCOMBINE_SSBUMP_NOBUMP) - { + else if (combine_mode == TCOMBINE_SSBUMP_NOBUMP) baseColor.rgb = baseColor.rgb * dot(detailColor.rgb, vec3(2.0 / 3.0)); - } return baseColor; } @@ -67,7 +59,7 @@ vec3 TextureCombinePostLighting(vec3 lit_baseColor, vec4 detailColor, int combin { if (combine_mode == TCOMBINE_RGB_ADDITIVE_SELFILLUM) lit_baseColor += fBlendFactor * detailColor.rgb; - if (combine_mode == TCOMBINE_RGB_ADDITIVE_SELFILLUM_THRESHOLD_FADE) + else if (combine_mode == TCOMBINE_RGB_ADDITIVE_SELFILLUM_THRESHOLD_FADE) { // fade in an unusual way - instead of fading out color, remap an increasing band of it from // 0..1 diff --git a/Game.Assets/hl2/shaders/common_gl460.vs b/Game.Assets/hl2/shaders/common_gl460.vs index 96d517b4..b9919971 100644 --- a/Game.Assets/hl2/shaders/common_gl460.vs +++ b/Game.Assets/hl2/shaders/common_gl460.vs @@ -111,14 +111,10 @@ float GetVertexAttenForLight(vec3 worldPos, int lightNum, bool useStaticControlF if (useStaticControlFlow) { if (g_bLightEnabled[lightNum]) - { result = VertexAttenInternal(worldPos, lightNum); - } } else - { result = VertexAttenInternal(worldPos, lightNum); - } return result; } @@ -137,9 +133,7 @@ vec3 DoLightingUnrolled(vec3 worldPos, vec3 worldNormal, vec3 linearColor = vec3(0.0, 0.0, 0.0); if (bStaticLight) // Static light - { linearColor += GammaToLinear(staticLightingColor * cOverbright); - } if (bDynamicLight) // Ambient light { @@ -154,9 +148,7 @@ vec3 DoLightingUnrolled(vec3 worldPos, vec3 worldNormal, } if (bDynamicLight) - { linearColor += AmbientLight(worldNormal); //ambient light is already remapped - } return linearColor; } diff --git a/Game.Assets/hl2/shaders/shadow_gl460.fs b/Game.Assets/hl2/shaders/shadow_gl460.fs new file mode 100644 index 00000000..5660c81e --- /dev/null +++ b/Game.Assets/hl2/shaders/shadow_gl460.fs @@ -0,0 +1,41 @@ +#version 460 + +in vec2 vs_TexCoord0; +in vec2 vs_TexCoord1; +in vec2 vs_TexCoord2; +in vec2 vs_TexCoord3; +in vec2 vs_TexCoord4; +in vec4 vs_ShadowColor; + +layout(std140, binding = 6) uniform source_ps_constants { + vec4 ps_const[256]; +}; + +#define g_ShadowColor ps_const[1] + +layout(binding = 0) uniform sampler2D basetexture; + +out vec4 fragColor; + +void main() +{ + vec4 samples[5]; + samples[0] = texture(basetexture, vs_TexCoord0); + samples[1] = texture(basetexture, vs_TexCoord1); + samples[2] = texture(basetexture, vs_TexCoord2); + samples[3] = texture(basetexture, vs_TexCoord3); + samples[4] = texture(basetexture, vs_TexCoord4); + + float shadowCoverage = (samples[0].a + samples[1].a + samples[2].a + samples[3].a + samples[4].a) * 0.2; + + shadowCoverage = clamp(shadowCoverage - vs_ShadowColor.a, 0.0, 1.0); + + vec4 result = shadowCoverage * g_ShadowColor - shadowCoverage; + result = 1.0 + result; + + float alpha = 1.0; + + // fog todo + + fragColor = vec4(result.rgb, alpha); +} diff --git a/Game.Assets/hl2/shaders/shadow_gl460.vs b/Game.Assets/hl2/shaders/shadow_gl460.vs new file mode 100644 index 00000000..4cec730a --- /dev/null +++ b/Game.Assets/hl2/shaders/shadow_gl460.vs @@ -0,0 +1,47 @@ +#version 460 + +layout(location = 0) in vec3 v_Position; +layout(location = 2) in vec4 v_Color; +layout(location = 10) in vec2 v_TexCoord; + +layout(std140, binding = 0) uniform source_matrices { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 modelMatrix; +}; + +layout(std140, binding = 5) uniform source_vs_constants { + vec4 vs_const[256]; +}; + +#define cBaseTexCoordTransform0 vs_const[48] +#define cBaseTexCoordTransform1 vs_const[49] +#define cTextureJitter0 vs_const[50] +#define cTextureJitter1 vs_const[51] + +out vec2 vs_TexCoord0; +out vec2 vs_TexCoord1; +out vec2 vs_TexCoord2; +out vec2 vs_TexCoord3; +out vec2 vs_TexCoord4; +out vec4 vs_ShadowColor; + +void main() +{ + mat4 mvp = projectionMatrix * viewMatrix * modelMatrix; + + gl_Position = mvp * vec4(v_Position, 1.0); + + vs_ShadowColor = v_Color; + + vec4 texCoordIn = vec4(v_TexCoord, 0.0, 1.0); + vec2 texCoord; + texCoord.x = dot(texCoordIn, cBaseTexCoordTransform0); + texCoord.y = dot(texCoordIn, cBaseTexCoordTransform1); + + vs_TexCoord0 = texCoord; + vs_TexCoord1 = texCoord + cTextureJitter0.xy; + vs_TexCoord2 = texCoord - cTextureJitter0.xy; + vs_TexCoord3 = texCoord + cTextureJitter1.xy; + vs_TexCoord4 = texCoord - cTextureJitter1.xy; +} diff --git a/Game.Assets/hl2/shaders/shadowmodel_gl460.fs b/Game.Assets/hl2/shaders/shadowmodel_gl460.fs new file mode 100644 index 00000000..8a8c5136 --- /dev/null +++ b/Game.Assets/hl2/shaders/shadowmodel_gl460.fs @@ -0,0 +1,25 @@ +#version 460 + +in vec3 vs_T0; +in vec3 vs_T1; +in vec3 vs_T2; +in float vs_T3; +in vec4 vs_Color; + +layout(binding = 0) uniform sampler2D basetexture; + +out vec4 fragColor; + +void main() +{ + if (vs_T1.x < 0.0 || vs_T1.y < 0.0 || vs_T1.z < 0.0) + discard; + if (vs_T2.x < 0.0 || vs_T2.y < 0.0 || vs_T2.z < 0.0) + discard; + if (vs_T3 < 0.0) + discard; + + float shadowAlpha = texture(basetexture, vs_T0.xy).a; + + fragColor = vec4(mix(vec3(1.0, 1.0, 1.0), vs_Color.xyz, shadowAlpha), 1.0); +} diff --git a/Game.Assets/hl2/shaders/shadowmodel_gl460.vs b/Game.Assets/hl2/shaders/shadowmodel_gl460.vs new file mode 100644 index 00000000..fd79dfa7 --- /dev/null +++ b/Game.Assets/hl2/shaders/shadowmodel_gl460.vs @@ -0,0 +1,58 @@ +#version 460 + +layout(location = 0) in vec3 v_Position; +layout(location = 1) in vec3 v_Normal; + +layout(std140, binding = 0) uniform source_matrices { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 modelMatrix; +}; + +layout(std140, binding = 5) uniform source_vs_constants { + vec4 vs_const[256]; +}; + +#define cShadowTextureMatrix0 vs_const[48] +#define cShadowTextureMatrix1 vs_const[49] +#define cShadowTextureMatrix2 vs_const[50] +#define cTexOrigin vs_const[51] +#define cTexScale vs_const[52] +#define cShadowConstants vs_const[53] +#define cModulationColor vs_const[47] + +#define flShadowFalloffOffset cShadowConstants.x +#define flOneOverShadowDist cShadowConstants.y +#define flShadowScale cShadowConstants.z + +out vec3 vs_T0; +out vec3 vs_T1; +out vec3 vs_T2; +out float vs_T3; +out vec4 vs_Color; + +void main() +{ + vec3 worldPos = (modelMatrix * vec4(v_Position, 1.0)).xyz; + vec3 worldNormal = mat3(modelMatrix) * v_Normal; + + gl_Position = projectionMatrix * viewMatrix * vec4(worldPos, 1.0); + + vec3 vTexturePos; + vTexturePos.x = dot(vec4(worldPos, 1.0), cShadowTextureMatrix0); + vTexturePos.y = dot(vec4(worldPos, 1.0), cShadowTextureMatrix1); + vTexturePos.z = dot(vec4(worldPos, 1.0), cShadowTextureMatrix2); + + float flShadowFade = (vTexturePos.z - flShadowFalloffOffset) * flOneOverShadowDist; + + vs_T0 = vTexturePos * cTexScale.xyz + cTexOrigin.xyz; + + vs_T1.xyz = vTexturePos.xyz; + vs_T2.xyz = vec3(1.0, 1.0, 1.0) - vTexturePos.xyz; + vs_T2.z = 1.0 - flShadowFade; + + vs_T3 = dot(worldNormal, -cShadowTextureMatrix2.xyz); + + vs_Color.xyz = cModulationColor.xyz; + vs_Color.w = flShadowFade * flShadowScale; +} diff --git a/Game.Client/C_BaseAnimating.cs b/Game.Client/C_BaseAnimating.cs index 5a7651b8..bd94fb15 100644 --- a/Game.Client/C_BaseAnimating.cs +++ b/Game.Client/C_BaseAnimating.cs @@ -97,6 +97,31 @@ public override void ResetLatched() { base.ResetLatched(); } public bool IsRagdoll() => Ragdoll != null && RenderFX == (byte)RenderFx.Ragdoll; + + public override ShadowType ShadowCastType() { + StudioHdr? studioHdr = GetModelPtr(); + if (studioHdr == null || !studioHdr.SequencesAvailable()) + return ShadowType.None; + + if (IsEffectActive(EntityEffects.NoDraw | EntityEffects.NoShadow)) + return ShadowType.None; + + if (studioHdr.GetNumSeq() == 0) + return ShadowType.RenderToTexture; + + if (!IsRagdoll()) { + if (studioHdr.GetNumPoseParameters() > 0) + return ShadowType.RenderToTextureDynamic; + + if (studioHdr.GetRenderHdr().NumBoneControllers > 0) + return ShadowType.RenderToTextureDynamic; + + if (studioHdr.GetRenderHdr().NumIKChains > 0) + return ShadowType.RenderToTextureDynamic; + } + + return ShadowType.RenderToTexture; + } public bool IsAboutToRagdoll() => RenderFX == (byte)RenderFx.Ragdoll; public override void ClientThink() { base.ClientThink(); @@ -148,7 +173,7 @@ public void StudioFrameAdvance() { } public TimeUnit_t GetSequenceMoveDist(StudioHdr? studioHdr, int sequence) { - Animation.GetSequenceLinearMotion(studioHdr, Sequence, PoseParameter, out Vector3 vecReturn); + Animation.GetSequenceLinearMotion(studioHdr, sequence, PoseParameter, out Vector3 vecReturn); return vecReturn.Length(); } @@ -337,7 +362,7 @@ protected virtual void ApplyBoneMatrixTransform(ref Matrix3x4 transform) { public void DelayedInitModelEffects() { /* todo */ } public void ClearRagdoll() { /* todo */ } - public virtual void Simulate() { + public override void Simulate() { if (DelayInitModelEffects) DelayedInitModelEffects(); @@ -371,6 +396,8 @@ public void RemoveBaseAnimatingInterpolatedVars() { RemoveVar(this, DA_Cycle, false); } + public bool ComputeHitboxSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) => throw new NotImplementedException(); + public override bool SetupBones(Span boneToWorldOut, int maxBones, int boneMask, double currentTime) { if (!boneToWorldOut.IsEmpty && !IsBoneAccessAllowed()) { if (gpGlobals.RealTime >= SetupBones__lastWarning + 1.0f) { diff --git a/Game.Client/C_BaseCombatWeapon.cs b/Game.Client/C_BaseCombatWeapon.cs index 143f9e84..91177270 100644 --- a/Game.Client/C_BaseCombatWeapon.cs +++ b/Game.Client/C_BaseCombatWeapon.cs @@ -2,6 +2,7 @@ using Game.Shared; using Source; +using Source.Common; using System.Runtime.CompilerServices; @@ -15,6 +16,21 @@ public partial class C_BaseCombatWeapon : C_BaseAnimating return player?.GetActiveWeapon(); } + public bool IsBeingCarried() => GetOwner() != null; + + public override ShadowType ShadowCastType() { + if (IsEffectActive(EntityEffects.NoShadow)) + return ShadowType.None; + + if (!IsBeingCarried()) + return ShadowType.RenderToTexture; + + if (IsCarriedByLocalPlayer() && !C_BasePlayer.ShouldDrawLocalPlayer()) + return ShadowType.None; + + return ShadowType.RenderToTexture; + } + public bool IsCarriedByLocalPlayer() { BaseEntity? owner = GetOwner(); if (owner == null) diff --git a/Game.Client/C_BaseEntity.cs b/Game.Client/C_BaseEntity.cs index 99762701..b95868fc 100644 --- a/Game.Client/C_BaseEntity.cs +++ b/Game.Client/C_BaseEntity.cs @@ -760,6 +760,9 @@ public void VPhysicsUpdate(IPhysicsObject physics) { } public EntClientFlags EntClientFlags; + public Source.InlineArray4 RenderingClipPlane; + public bool EnableRenderingClipPlane; + public Vector3 Origin; public readonly InterpolatedVar IV_Origin = new("Origin"); public QAngle Rotation; @@ -1667,6 +1670,18 @@ protected virtual void UpdateVisibility() { ClientRenderHandle_t renderHandle; + public virtual void ComputeWorldSpaceSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + Assert(false); + vecWorldMins = default; + vecWorldMaxs = default; + } + + public void MarkRenderHandleDirty() { + ClientRenderHandle_t handle = GetRenderHandle(); + if (handle != INVALID_CLIENT_RENDER_HANDLE) + clientLeafSystem.RenderableChanged(handle); + } + public ClientRenderHandle_t GetRenderHandle() => renderHandle; public ref ClientRenderHandle_t RenderHandle() => ref renderHandle; @@ -1695,7 +1710,7 @@ private void RemoveFromLeafSystem() { clientLeafSystem.RemoveRenderable(renderHandle); renderHandle = INVALID_CLIENT_RENDER_HANDLE; } - // DestroyShadow(); + DestroyShadow(); } @@ -1827,7 +1842,26 @@ private TimeUnit_t GetLastChangeTime(LatchFlags flags) { } private void CreateShadow() { + ShadowType shadowType = ShadowCastType(); + if (shadowType == ShadowType.None) + DestroyShadow(); + else { + if (ShadowHandle == CLIENTSHADOW_INVALID_HANDLE) { + int flags = (int)ShadowFlags.Shadow; + if (shadowType != ShadowType.Simple) + flags |= (int)ClientShadowFlags.UseRenderToTexture; + if (shadowType == ShadowType.RenderToTextureDynamic) + flags |= (int)ClientShadowFlags.AnimatingSource; + ShadowHandle = g_ClientShadowMgr.CreateShadow(GetClientHandle(), flags); + } + } + } + private void DestroyShadow() { + if (ShadowHandle != CLIENTSHADOW_INVALID_HANDLE) { + g_ClientShadowMgr.DestroyShadow(ShadowHandle); + ShadowHandle = CLIENTSHADOW_INVALID_HANDLE; + } } public virtual void Spawn() { } @@ -2851,7 +2885,7 @@ void CollectPackedOffsets_R(DataMap? map, nuint baseOffset, List<(string ClassNa } } - ClientShadowHandle_t ShadowHandle = 0; + ClientShadowHandle_t ShadowHandle = CLIENTSHADOW_INVALID_HANDLE; public bool UsesPowerOfTwoFrameBufferTexture() => false; public bool UsesFullFrameBufferTexture() => false; @@ -2893,21 +2927,19 @@ public bool ShouldReceiveProjectedTextures(ShadowFlags flags) { return true; } - public bool GetShadowCastDistance(out float dist, ShadowType shadowType) { + public bool GetShadowCastDistance(ref float dist, ShadowType shadowType) { if (ShadowCastDistance != 0.0f) { dist = ShadowCastDistance; return true; } - dist = default; return false; } EHANDLE ShadowDirUseOtherEntity = default; - public bool GetShadowCastDirection(out Vector3 direction, ShadowType shadowType) { + public bool GetShadowCastDirection(ref Vector3 direction, ShadowType shadowType) { if (ShadowDirUseOtherEntity.Get() != null) - return ShadowDirUseOtherEntity.Get()!.GetShadowCastDirection(out direction, shadowType); - direction = default; + return ShadowDirUseOtherEntity.Get()!.GetShadowCastDirection(ref direction, shadowType); return false; } @@ -2934,7 +2966,7 @@ public void MarkShadowDirty(bool bDirty) { return parent?.GetClientRenderable(); } - public ShadowType ShadowCastType() { + public virtual ShadowType ShadowCastType() { if (IsEffectActive(EntityEffects.NoDraw | EntityEffects.NoShadow)) return ShadowType.None; @@ -2947,8 +2979,10 @@ public ShadowType ShadowCastType() { public virtual int LookupAttachment(ReadOnlySpan attachmentName) => -1; public Span GetRenderClipPlane() { - // todo - return null; + if (EnableRenderingClipPlane) + return RenderingClipPlane; + else + return null; } public virtual int GetSkin() => 0; diff --git a/Game.Client/C_BasePlayer.cs b/Game.Client/C_BasePlayer.cs index 634c6316..e67db112 100644 --- a/Game.Client/C_BasePlayer.cs +++ b/Game.Client/C_BasePlayer.cs @@ -41,6 +41,10 @@ public struct C_PredictionError [LinkEntityToClass("player")] public partial class C_BasePlayer : C_BaseCombatCharacter, IGameEventListener2 { + const int FLASHLIGHT_DISTANCE = 1000; + + public override ShadowType ShadowCastType() => ShadowType.None; + public static readonly DataMap PM_PlayerState = new(typeof(PlayerState), [ DEFINE.PRED_FIELD( nameof(PlayerState.DeadFlag), FieldType.Boolean, FieldTypeDescFlags.InSendTable ), ]); @@ -153,6 +157,45 @@ public virtual void UpdateClientData() { } } public virtual void UpdateFogController() { } + + public void UpdateFlashlight() { + if (IsEffectActive(EntityEffects.DimLight)) { + if (PointFlashlight == null) { + PointFlashlight = new FlashlightEffect(Index); + + if (PointFlashlight == null) + return; + + PointFlashlight.TurnOn(); + } + + EyeVectors(out Vector3 forward, out Vector3 right, out Vector3 up); + + PointFlashlight.UpdateLight(EyePosition(), in forward, in right, in up, FLASHLIGHT_DISTANCE); + } + else { + PointFlashlight?.Dispose(); + PointFlashlight = null; + } + } + + public void Flashlight() => UpdateFlashlight(); + + public override void Simulate() { + if (this == GetLocalPlayer()) { + Flashlight(); + + UpdateFogController(); + } + else { + // todo + } + + base.Simulate(); + if (IsNoInterpolationFrame() || Teleported()) + ResetLatched(); + } + public virtual void PreThink() { ItemPreFrame(); @@ -457,6 +500,7 @@ public void SetLocalViewAngles(in QAngle angles) { public int TickBase; public long FinalPredictedTick; InlineArray32 AnimExtension; + FlashlightEffect? PointFlashlight; public int GetHealth() => Health; public bool IsSuitEquipped() => Local.WearingSuit; diff --git a/Game.Client/ClientEntityList.cs b/Game.Client/ClientEntityList.cs index 9b0ee230..5589ae58 100644 --- a/Game.Client/ClientEntityList.cs +++ b/Game.Client/ClientEntityList.cs @@ -1,4 +1,5 @@ global using static Game.Client.ClientEntityGlobals; + using Game.Shared; using Source; @@ -6,7 +7,8 @@ namespace Game.Client; -public static class ClientEntityGlobals { +public static class ClientEntityGlobals +{ public static readonly BaseHandle INVALID_CLIENTENTITY_HANDLE = new(Constants.INVALID_EHANDLE_INDEX); } public static class ClientEntityExts @@ -39,13 +41,40 @@ public class ClientEntityList : BaseEntityList, IClientEntityList } public IClientNetworkable? GetClientNetworkableFromHandle(in BaseHandle ent) { - throw new NotImplementedException(); + IClientUnknown? pEnt = GetClientUnknownFromHandle(ent); + return pEnt == null ? null : pEnt.GetClientNetworkable(); + } + + public void Release() { + BaseHandle iter = FirstHandle(); + while (iter != InvalidHandle()) { + IClientNetworkable? net = GetClientNetworkableFromHandle(iter); + if (net != null) + net.Release(); + else { + IClientThinkable? thinkable = GetClientThinkableFromHandle(iter); + thinkable?.Release(); + } + RemoveEntity(iter); + + iter = FirstHandle(); + } + + NumServerEnts = 0; + MaxServerEnts = 0; + NumClientNonNetworkable = 0; + MaxUsedServerIndex = -1; } public IClientUnknown? GetClientUnknownFromHandle(in BaseHandle ent) { return (IClientUnknown?)LookupEntity(ent); } + public IClientRenderable? GetClientRenderableFromHandle(in BaseHandle ent) { + IClientUnknown? pEnt = GetClientUnknownFromHandle(ent); + return pEnt == null ? null : pEnt.GetClientRenderable(); + } + public int GetHighestEntityIndex() { return MaxUsedServerIndex; } diff --git a/Game.Client/ClientLeafSystem.cs b/Game.Client/ClientLeafSystem.cs index 9c3cc615..d9fd9a4c 100644 --- a/Game.Client/ClientLeafSystem.cs +++ b/Game.Client/ClientLeafSystem.cs @@ -25,6 +25,11 @@ public class ClientLeafSubSystemData { } +public interface IClientLeafShadowEnum +{ + void EnumShadow(ClientShadowHandle_t userId); +} + public struct RenderableInfo { public IClientRenderable? Renderable; @@ -56,7 +61,7 @@ public struct ShadowInfo_t public uint FirstLeaf; public uint FirstRenderable; public int EnumCount; - // public ClientShadowHandle_t Shadow; + public ClientShadowHandle_t Shadow; public ushort Flags; } public class EnumResult @@ -76,6 +81,11 @@ class RenderableInfoBox public RenderableInfo Info = new(); } +class ShadowInfoBox +{ + public ShadowInfo_t Info; +} + public class ClientLeafSystem : IClientLeafSystem, ISpatialLeafEnumerator { public const int CLSUBSYSTEM_DETAILOBJECTS = 0; @@ -90,16 +100,26 @@ public class ClientLeafSystem : IClientLeafSystem, ISpatialLeafEnumerator bool DrawStaticProps = true; readonly BidirectionalSet RenderablesInLeaf = new(); + readonly BidirectionalSet ShadowsInLeaf = new(); + readonly BidirectionalSet ShadowsOnRenderable = new(); + readonly Dictionary Shadows = []; + ClientLeafShadowHandle_t curShadowHandleIdx; int ShadowEnum; readonly Queue DeferredInserts = new(); public ClientLeafSystem() { RenderablesInLeaf.Init(FirstRenderableInLeaf, FirstLeafInRenderable); + ShadowsInLeaf.Init(FirstShadowInLeaf, FirstLeafInShadow); + ShadowsOnRenderable.Init(FirstShadowOnRenderable, FirstRenderableInShadow); IClientLeafSystemEngine.DefaultRenderBoundsWorldspaceEv += DefaultRenderBoundsWorldspace; } ref uint FirstRenderableInLeaf(int leaf) => ref Leaf.AsSpan()[leaf].FirstElement; ref uint FirstLeafInRenderable(ClientRenderHandle_t renderable) => ref Renderables[renderable].Info.LeafList; + ref uint FirstShadowInLeaf(int leaf) => ref Leaf.AsSpan()[leaf].FirstShadow; + ref uint FirstLeafInShadow(ClientLeafShadowHandle_t shadow) => ref Shadows[shadow].Info.FirstLeaf; + ref uint FirstShadowOnRenderable(ClientRenderHandle_t renderable) => ref Renderables[renderable].Info.FirstShadow; + ref uint FirstRenderableInShadow(ClientLeafShadowHandle_t shadow) => ref Shadows[shadow].Info.FirstRenderable; void AddRenderableToLeaf(int leaf, ClientRenderHandle_t renderable) { RenderablesInLeaf.AddElementToBucket(leaf, renderable); @@ -107,7 +127,13 @@ void AddRenderableToLeaf(int leaf, ClientRenderHandle_t renderable) { void RemoveFromTree(ClientRenderHandle_t handle) { RenderablesInLeaf.RemoveElement(handle); - // todo + + ShadowsOnRenderable.RemoveBucket(handle); + + if ((Renderables[handle].Info.Flags & RenderFlags.BrushModel) != 0) + g_ClientShadowMgr.RemoveAllShadowsFromReceiver(Renderables[handle].Info.Renderable, ShadowReceiver.BrushModel); + else if ((Renderables[handle].Info.Flags & RenderFlags.StudioModel) != 0) + g_ClientShadowMgr.RemoveAllShadowsFromReceiver(Renderables[handle].Info.Renderable, ShadowReceiver.StudioModel); } public bool EnumerateLeaf(int leaf, nint context) { @@ -687,6 +713,123 @@ public void RemoveRenderable(ClientRenderHandle_t handle) { Renderables.Remove(handle); } + public ClientLeafShadowHandle_t AddShadow(ClientShadowHandle_t userId, ushort flags) { + ClientLeafShadowHandle_t idx = curShadowHandleIdx++; + Shadows[idx] = new(); + ref ShadowInfo_t shadow = ref Shadows[idx].Info; + shadow.Shadow = userId; + shadow.FirstLeaf = unchecked((uint)BidirectionalSet.InvalidIndex); + shadow.FirstRenderable = unchecked((uint)BidirectionalSet.InvalidIndex); + shadow.EnumCount = 0; + shadow.Flags = flags; + return idx; + } + + public void RemoveShadow(ClientLeafShadowHandle_t handle) { + RemoveShadowFromLeaves(handle); + RemoveShadowFromRenderables(handle); + + Shadows.Remove(handle); + } + + bool ShouldRenderableReceiveShadow(ClientRenderHandle_t renderHandle, ShadowFlags shadowFlags) { + ref RenderableInfo renderable = ref Renderables[renderHandle].Info; + if ((renderable.Flags & (RenderFlags.BrushModel | RenderFlags.StaticProp | RenderFlags.StudioModel)) == 0) + return false; + + return renderable.Renderable!.ShouldReceiveProjectedTextures(shadowFlags); + } + + void AddShadowToRenderable(ClientRenderHandle_t renderHandle, ClientLeafShadowHandle_t shadowHandle) { + ShadowFlags shadowFlags = (ShadowFlags)Shadows[shadowHandle].Info.Flags; + if (!ShouldRenderableReceiveShadow(renderHandle, shadowFlags)) + return; + + ShadowsOnRenderable.AddElementToBucket(renderHandle, shadowHandle); + + if ((Renderables[renderHandle].Info.Flags & RenderFlags.BrushModel) != 0) { + IClientRenderable? renderable = Renderables[renderHandle].Info.Renderable; + g_ClientShadowMgr.AddShadowToReceiver(Shadows[shadowHandle].Info.Shadow, renderable, ShadowReceiver.BrushModel); + } + else if ((Renderables[renderHandle].Info.Flags & RenderFlags.StaticProp) != 0) { + IClientRenderable? renderable = Renderables[renderHandle].Info.Renderable; + g_ClientShadowMgr.AddShadowToReceiver(Shadows[shadowHandle].Info.Shadow, renderable, ShadowReceiver.StaticProp); + } + else if ((Renderables[renderHandle].Info.Flags & RenderFlags.StudioModel) != 0) { + IClientRenderable? renderable = Renderables[renderHandle].Info.Renderable; + g_ClientShadowMgr.AddShadowToReceiver(Shadows[shadowHandle].Info.Shadow, renderable, ShadowReceiver.StudioModel); + } + } + + void RemoveShadowFromRenderables(ClientLeafShadowHandle_t handle) => ShadowsOnRenderable.RemoveElement(handle); + + void AddShadowToLeaf(int leaf, ClientLeafShadowHandle_t shadow) { + ShadowsInLeaf.AddElementToBucket(leaf, shadow); + + int i = RenderablesInLeaf.FirstElementInBucket(leaf); + while (i != BidirectionalSet.InvalidIndex) { + ClientRenderHandle_t renderable = RenderablesInLeaf.Element(i); + ref RenderableInfo info = ref Renderables[renderable].Info; + + if (info.EnumCount != ShadowEnum) { + AddShadowToRenderable(renderable, shadow); + info.EnumCount = ShadowEnum; + } + + i = RenderablesInLeaf.NextElement(i); + } + } + + void RemoveShadowFromLeaves(ClientLeafShadowHandle_t handle) => ShadowsInLeaf.RemoveElement(handle); + + public void EnumerateShadowsInLeaves(int leafCount, List leaves, IClientLeafShadowEnum enumerator) { + if (leafCount == 0) + return; + + ++ShadowEnum; + + for (int i = 0; i < leafCount; ++i) { + int leaf = leaves[i]; + + int j = ShadowsInLeaf.FirstElementInBucket(leaf); + while (j != BidirectionalSet.InvalidIndex) { + ClientLeafShadowHandle_t shadow = ShadowsInLeaf.Element(j); + ref ShadowInfo_t info = ref Shadows[shadow].Info; + + if (info.EnumCount != ShadowEnum) { + enumerator.EnumShadow(info.Shadow); + info.EnumCount = ShadowEnum; + } + + j = ShadowsInLeaf.NextElement(j); + } + } + } + + public void ProjectShadow(ClientLeafShadowHandle_t handle, int leafCount, ReadOnlySpan leafList) { + RemoveShadowFromLeaves(handle); + RemoveShadowFromRenderables(handle); + + Assert(((ShadowFlags)Shadows[handle].Info.Flags & ShadowFlags.ProjectedTextureTypeMask) == ShadowFlags.Shadow); + + ++ShadowEnum; + + for (int i = 0; i < leafCount; ++i) + AddShadowToLeaf(leafList[i], handle); + } + + public void ProjectFlashlight(ClientLeafShadowHandle_t handle, int leafCount, ReadOnlySpan leafList) { + RemoveShadowFromLeaves(handle); + RemoveShadowFromRenderables(handle); + + Assert(((ShadowFlags)Shadows[handle].Info.Flags & ShadowFlags.ProjectedTextureTypeMask) == ShadowFlags.Flashlight); + + ++ShadowEnum; + + for (int i = 0; i < leafCount; ++i) + AddShadowToLeaf(leafList[i], handle); + } + public void RenderableChanged(ClientRenderHandle_t handle) { if (!ValidHandles.Contains(handle)) return; diff --git a/Game.Client/ClientShadowMgr.cs b/Game.Client/ClientShadowMgr.cs new file mode 100644 index 00000000..798d0f26 --- /dev/null +++ b/Game.Client/ClientShadowMgr.cs @@ -0,0 +1,2313 @@ +global using static Game.Client.ClientShadowMgrGlobals; +using static Source.Engine.ShadowMgrGlobals; +using static Source.Engine.StaticPropMgrGlobals; + +using CommunityToolkit.HighPerformance; + +using Source; +using Source.Common; +using Source.Common.Bitmap; +using Source.Common.Commands; +using Source.Common.Engine; +using Source.Common.Formats.Keyvalues; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; + +using System.Numerics; + +using Game.Shared; + +namespace Game.Client; + +public static class ClientShadowMgrGlobals +{ + public static readonly ConVar r_flashlightdrawfrustum = new("r_flashlightdrawfrustum", "0"); + public static readonly ConVar r_flashlightmodels = new("r_flashlightmodels", "1"); + public static readonly ConVar r_shadowrendertotexture = new("r_shadowrendertotexture", "0"); + + public static readonly ConVar r_flashlightdepthtexture = new("r_flashlightdepthtexture", "1"); + public static readonly ConVar r_flashlightdepthres = new("r_flashlightdepthres", "1024"); + + public static readonly ConVar r_threaded_client_shadow_manager = new("r_threaded_client_shadow_manager", "0"); + + public const TextureHandle_t INVALID_TEXTURE_HANDLE = unchecked((TextureHandle_t)~0); + + public const float TEXEL_SIZE_PER_CASTER_SIZE = 2.0f; + public const int MAX_FALLOFF_AMOUNT = 240; + public const int MAX_CLIP_PLANE_COUNT = 4; + public const float SHADOW_CULL_TOLERANCE = 0.5f; + + public static readonly ConVar r_shadowmaxrendered = new("r_shadowmaxrendered", "32"); + + public static readonly ClientShadowMgr s_ClientShadowMgr = new(); + public static readonly IClientShadowMgr g_ClientShadowMgr = s_ClientShadowMgr; + + public static readonly VisibleShadowList s_VisibleShadowList = new(); + + public static void ShadowRestoreFunc(int changeFlags) { + s_ClientShadowMgr.RestoreRenderState(); + } + + public static readonly List s_NPCShadowBoneSetups = []; + public static readonly List s_NonNPCShadowBoneSetups = []; + + [ConCommand("r_shadowangles", "Set shadow angles", FCvar.Cheat)] + public static void r_shadowangles(in TokenizedCommand args) { + if (args.ArgC() == 1) { + Vector3 dir = s_ClientShadowMgr.GetShadowDirection(); + MathLib.VectorAngles(dir, out QAngle angles); + Msg($"Shadow angles {angles.X} {angles.Y} {angles.Z}\n"); + return; + } + + if (args.ArgC() == 4) { + QAngle angles = default; + _ = float.TryParse(args[1], out angles.X); + _ = float.TryParse(args[2], out angles.Y); + _ = float.TryParse(args[3], out angles.Z); + MathLib.AngleVectors(angles, out Vector3 dir); + s_ClientShadowMgr.SetShadowDirection(dir); + } + } + + [ConCommand("r_shadowcolor", "Set shadow color", FCvar.Cheat)] + public static void r_shadowcolor(in TokenizedCommand args) { + if (args.ArgC() == 1) { + s_ClientShadowMgr.GetShadowColor(out byte r, out byte g, out byte b); + Msg($"Shadow color {r} {g} {b}\n"); + return; + } + + if (args.ArgC() == 4) { + _ = int.TryParse(args[1], out int r); + _ = int.TryParse(args[2], out int g); + _ = int.TryParse(args[3], out int b); + s_ClientShadowMgr.SetShadowColor((byte)r, (byte)g, (byte)b); + } + } + + [ConCommand("r_shadowdist", "Set shadow distance", FCvar.Cheat)] + public static void r_shadowdist(in TokenizedCommand args) { + if (args.ArgC() == 1) { + float dist = s_ClientShadowMgr.GetShadowDistance(); + Msg($"Shadow distance {dist:F2}\n"); + return; + } + + if (args.ArgC() == 2) { + _ = float.TryParse(args[1], out float dist); + s_ClientShadowMgr.SetShadowDistance(dist); + } + } + + [ConCommand("r_shadowblobbycutoff", "some shadow stuff", FCvar.Cheat)] + public static void r_shadowblobbycutoff(in TokenizedCommand args) { + if (args.ArgC() == 1) { + float area = s_ClientShadowMgr.GetBlobbyCutoffArea(); + Msg($"Cutoff area {area:F2}\n"); + return; + } + + if (args.ArgC() == 2) + s_ClientShadowMgr.SetShadowBlobbyCutoffArea(float.Parse(args[1])); + } +} + +public class TextureAllocator +{ + public const FragmentHandle_t INVALID_FRAGMENT_HANDLE = unchecked((FragmentHandle_t)~0); + public const int TEXTURE_PAGE_SIZE = 1024; + public const int MAX_TEXTURE_POWER = 8; + public const int MIN_TEXTURE_POWER = 4; + public const int MAX_TEXTURE_SIZE = 1 << MAX_TEXTURE_POWER; + public const int MIN_TEXTURE_SIZE = 1 << MIN_TEXTURE_POWER; + public const int BLOCK_SIZE = MAX_TEXTURE_SIZE; + public const int BLOCKS_PER_ROW = TEXTURE_PAGE_SIZE / MAX_TEXTURE_SIZE; + public const int BLOCK_COUNT = BLOCKS_PER_ROW * BLOCKS_PER_ROW; + + public struct TextureInfo_t + { + public FragmentHandle_t Fragment; + public ushort Size; + public ushort Power; + } + + public struct FragmentInfo_t + { + public ushort Block; + public ushort Index; + public TextureHandle_t Texture; + + public uint FrameUsed; + + public FragmentHandle_t Prev; + public FragmentHandle_t Next; + } + + public struct BlockInfo_t + { + public ushort FragmentPower; + } + + public struct Cache_t + { + public FragmentHandle_t Head; + public FragmentHandle_t Tail; + } + + readonly TextureReference TexturePage = new(); + + readonly PooledLinkedList Textures = new(); + readonly List Fragments = new(256); + + Cache_t[] Cache = new Cache_t[MAX_TEXTURE_POWER + 1]; + BlockInfo_t[] Blocks = new BlockInfo_t[BLOCK_COUNT]; + uint CurrentFrame; + + Span Frags => Fragments.AsSpan(); + + public void Init() { + for (int i = 0; i <= MAX_TEXTURE_POWER; ++i) { + Cache[i].Head = INVALID_FRAGMENT_HANDLE; + Cache[i].Tail = INVALID_FRAGMENT_HANDLE; + } + + TexturePage.InitRenderTarget(TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RenderTargetSizeMode.NoChange, ImageFormat.ARGB8888, MaterialRenderTargetDepth.None, false, "_rt_Shadows"); + } + + public void Shutdown() => TexturePage.Shutdown(); + + public void Reset() { + DeallocateAllTextures(); + + Fragments.EnsureCapacity(256); + + Blocks[0].FragmentPower = MAX_TEXTURE_POWER - 4; + Blocks[1].FragmentPower = MAX_TEXTURE_POWER - 3; + Blocks[2].FragmentPower = MAX_TEXTURE_POWER - 2; + Blocks[3].FragmentPower = MAX_TEXTURE_POWER - 2; + Blocks[4].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[5].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[6].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[7].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[8].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[9].FragmentPower = MAX_TEXTURE_POWER - 1; + Blocks[10].FragmentPower = MAX_TEXTURE_POWER; + Blocks[11].FragmentPower = MAX_TEXTURE_POWER; + Blocks[12].FragmentPower = MAX_TEXTURE_POWER; + Blocks[13].FragmentPower = MAX_TEXTURE_POWER; + Blocks[14].FragmentPower = MAX_TEXTURE_POWER; + Blocks[15].FragmentPower = MAX_TEXTURE_POWER; + + int i; + for (i = 0; i <= MAX_TEXTURE_POWER; ++i) { + Cache[i].Head = INVALID_FRAGMENT_HANDLE; + Cache[i].Tail = INVALID_FRAGMENT_HANDLE; + } + + for (i = 0; i < BLOCK_COUNT; ++i) + AddBlockToLRU(i); + + CurrentFrame = 0; + } + + public void DeallocateAllTextures() { + Textures.Clear(); + Fragments.Clear(); + for (int i = 0; i <= MAX_TEXTURE_POWER; ++i) { + Cache[i].Head = INVALID_FRAGMENT_HANDLE; + Cache[i].Tail = INVALID_FRAGMENT_HANDLE; + } + } + + public void DebugPrintCache() { + int numFragments = Fragments.Count; + int numInvalidFragments = 0; + + Warning($"Fragments ({numFragments}):\n===============\n"); + + Span frags = Frags; + for (int f = 0; f < numFragments; f++) { + if (frags[f].FrameUsed != 0 && frags[f].Texture != INVALID_TEXTURE_HANDLE) + Warning($"Fragment {f}, Block: {frags[f].Block}, Index: {frags[f].Index}, Texture: {frags[f].Texture} Frame Used: {frags[f].FrameUsed}\n"); + else + numInvalidFragments++; + } + + Warning($"Invalid Fragments: {numInvalidFragments}\n"); + } + + void AddBlockToLRU(int block) { + int power = Blocks[block].FragmentPower; + int size = 1 << power; + + int fragmentCount = MAX_TEXTURE_SIZE / size; + fragmentCount *= fragmentCount; + + while (--fragmentCount >= 0) { + FragmentHandle_t f = (FragmentHandle_t)Fragments.Count; + Fragments.Add(new FragmentInfo_t() { + Block = (ushort)block, + Index = (ushort)fragmentCount, + Texture = INVALID_TEXTURE_HANDLE, + FrameUsed = 0xFFFFFFFF, + Prev = INVALID_FRAGMENT_HANDLE, + Next = INVALID_FRAGMENT_HANDLE + }); + LinkToHead(ref Cache[power], f); + } + } + + void LinkToHead(ref Cache_t cache, FragmentHandle_t fragment) { + Unlink(ref cache, fragment); + + Span frags = Frags; + frags[fragment].Next = cache.Head; + if (cache.Head != INVALID_FRAGMENT_HANDLE) + frags[cache.Head].Prev = fragment; + else + cache.Tail = fragment; + cache.Head = fragment; + } + + void LinkToTail(ref Cache_t cache, FragmentHandle_t fragment) { + Unlink(ref cache, fragment); + + Span frags = Frags; + frags[fragment].Prev = cache.Tail; + if (cache.Tail != INVALID_FRAGMENT_HANDLE) + frags[cache.Tail].Next = fragment; + else + cache.Head = fragment; + cache.Tail = fragment; + } + + void Unlink(ref Cache_t cache, FragmentHandle_t fragment) { + Span frags = Frags; + FragmentHandle_t prev = frags[fragment].Prev; + FragmentHandle_t next = frags[fragment].Next; + + if (prev != INVALID_FRAGMENT_HANDLE) + frags[prev].Next = next; + else if (cache.Head == fragment) + cache.Head = next; + + if (next != INVALID_FRAGMENT_HANDLE) + frags[next].Prev = prev; + else if (cache.Tail == fragment) + cache.Tail = prev; + + frags[fragment].Prev = INVALID_FRAGMENT_HANDLE; + frags[fragment].Next = INVALID_FRAGMENT_HANDLE; + } + + void UnlinkFragmentFromCache(ref Cache_t cache, FragmentHandle_t fragment) => Unlink(ref cache, fragment); + + void MarkUsed(FragmentHandle_t fragment) { + int block = Frags[fragment].Block; + int power = Blocks[block].FragmentPower; + + LinkToTail(ref Cache[power], fragment); + Frags[fragment].FrameUsed = CurrentFrame; + } + + void MarkUnused(FragmentHandle_t fragment) { + int block = Frags[fragment].Block; + int power = Blocks[block].FragmentPower; + + LinkToHead(ref Cache[power], fragment); + } + + public TextureHandle_t AllocateTexture(int w, int h) { + Assert(w == h); + + if (w < MIN_TEXTURE_SIZE) + w = MIN_TEXTURE_SIZE; + else if (w > MAX_TEXTURE_SIZE) + w = MAX_TEXTURE_SIZE; + + TextureHandle_t handle = (TextureHandle_t)Textures.Alloc(); + Textures[handle].Fragment = INVALID_FRAGMENT_HANDLE; + Textures[handle].Size = (ushort)w; + + int power = 0; + int size = 1; + while (size < w) { + size <<= 1; + ++power; + } + Assert(size == w); + + Textures[handle].Power = (ushort)power; + + return handle; + } + + public void DeallocateTexture(TextureHandle_t h) { + if (Textures[h].Fragment != INVALID_FRAGMENT_HANDLE) { + MarkUnused(Textures[h].Fragment); + Frags[Textures[h].Fragment].FrameUsed = 0xFFFFFFFF; + DisconnectTextureFromFragment(Textures[h].Fragment); + } + Textures.Remove(h); + } + + void DisconnectTextureFromFragment(FragmentHandle_t f) { + ref FragmentInfo_t info = ref Frags[f]; + if (info.Texture != INVALID_TEXTURE_HANDLE) { + Textures[info.Texture].Fragment = INVALID_FRAGMENT_HANDLE; + info.Texture = INVALID_TEXTURE_HANDLE; + } + } + + public bool HasValidTexture(TextureHandle_t h) { + ref TextureInfo_t info = ref Textures[h]; + FragmentHandle_t currentFragment = info.Fragment; + return currentFragment != INVALID_FRAGMENT_HANDLE; + } + + public bool UseTexture(TextureHandle_t h, bool willRedraw, float area) { + ref TextureInfo_t info = ref Textures[h]; + + int desiredPower = MIN_TEXTURE_POWER; + int desiredWidth = MIN_TEXTURE_SIZE; + while (desiredWidth * desiredWidth < area) { + if (desiredPower >= info.Power) { + desiredPower = info.Power; + break; + } + + ++desiredPower; + desiredWidth <<= 1; + } + + int currentPower = -1; + FragmentHandle_t currentFragment = info.Fragment; + if (currentFragment != INVALID_FRAGMENT_HANDLE) { + currentPower = GetFragmentPower(info.Fragment); + Assert(currentPower <= info.Power); + bool shouldKeepTexture = !willRedraw && desiredPower < 8 && desiredPower - currentPower <= 1; + if (currentPower == desiredPower || shouldKeepTexture) { + MarkUsed(currentFragment); + return false; + } + } + + int power = desiredPower; + + FragmentHandle_t f = INVALID_FRAGMENT_HANDLE; + bool done = false; + while (!done && power >= 0) { + f = Cache[power].Head; + + if (f != INVALID_FRAGMENT_HANDLE && Frags[f].FrameUsed != CurrentFrame) + done = true; + else + --power; + } + + if (currentFragment != INVALID_FRAGMENT_HANDLE) { + if (power <= currentPower) { + MarkUsed(currentFragment); + return false; + } + else { + DisconnectTextureFromFragment(currentFragment); + } + } + + if (f == INVALID_FRAGMENT_HANDLE) + return false; + + DisconnectTextureFromFragment(f); + + info.Fragment = f; + Frags[f].Texture = h; + + MarkUsed(f); + + return true; + } + + int GetFragmentPower(FragmentHandle_t f) => Blocks[Frags[f].Block].FragmentPower; + + public void AdvanceFrame() => CurrentFrame++; + + public ITexture? GetTexture() => TexturePage.Get(); + + public void GetTotalTextureSize(out int w, out int h) => w = h = TEXTURE_PAGE_SIZE; + + public void GetTextureRect(TextureHandle_t handle, out int x, out int y, out int w, out int h) { + ref TextureInfo_t info = ref Textures[handle]; + Assert(info.Fragment != INVALID_FRAGMENT_HANDLE); + + ref FragmentInfo_t fragment = ref Frags[info.Fragment]; + int blockY = fragment.Block / BLOCKS_PER_ROW; + int blockX = fragment.Block - blockY * BLOCKS_PER_ROW; + + int fragmentSize = 1 << Blocks[fragment.Block].FragmentPower; + int fragmentsPerRow = BLOCK_SIZE / fragmentSize; + int fragmentY = fragment.Index / fragmentsPerRow; + int fragmentX = fragment.Index - fragmentY * fragmentsPerRow; + + x = blockX * BLOCK_SIZE + fragmentX * fragmentSize; + y = blockY * BLOCK_SIZE + fragmentY * fragmentSize; + w = fragmentSize; + h = fragmentSize; + } +} + +public struct VisibleShadowInfo_t +{ + public ClientShadowHandle_t Shadow; + public float Area; + public Vector3 AbsCenter; +} + +public class VisibleShadowList : IClientLeafShadowEnum +{ + readonly List ShadowsInView = []; + readonly List PriorityIndex = []; + + public int GetVisibleShadowCount() => ShadowsInView.Count; + + public ref readonly VisibleShadowInfo_t GetVisibleShadow(int i) => ref ShadowsInView.AsSpan()[PriorityIndex[i]]; + + float ComputeScreenArea(in Vector3 center, float r) { + IMatRenderContext renderContext = materials.GetRenderContext(); + float screenDiameter = renderContext.ComputePixelDiameterOfSphere(center, r); + return screenDiameter * screenDiameter; + } + + public void EnumShadow(ClientShadowHandle_t clientShadowHandle) { + ref ClientShadowMgr.ClientShadow_t shadow = ref s_ClientShadowMgr.Shadows[clientShadowHandle].Shadow; + + if (shadow.RenderFrame == gpGlobals.FrameCount) + return; + + if (s_ClientShadowMgr.GetActualShadowCastType(clientShadowHandle) != ShadowType.RenderToTexture) + return; + + ref readonly Source.Common.Engine.ShadowInfo_t shadowInfo = ref g_ShadowMgr.GetInfo(shadow.ShadowHandle); + if (shadowInfo.FalloffBias == 255) + return; + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + Assert(renderable != null); + + if (s_ClientShadowMgr.ShouldUseParentShadow(renderable) || s_ClientShadowMgr.WillParentRenderBlobbyShadow(renderable)) + return; + + s_ClientShadowMgr.ComputeBoundingSphere(renderable, out Vector3 absCenter, out float radius); + + s_ClientShadowMgr.ComputeShadowBBox(renderable, in absCenter, radius, out Vector3 absMins, out Vector3 absMaxs); + + if (engine.CullBox(in absMins, in absMaxs)) + return; + + VisibleShadowInfo_t info = default; + info.Shadow = clientShadowHandle; + info.Area = ComputeScreenArea(in absCenter, radius); + ShadowsInView.Add(info); + + shadow.RenderFrame = gpGlobals.FrameCount; + } + + void PrioritySort() { + int count = ShadowsInView.Count; + PriorityIndex.EnsureCapacity(count); + + PriorityIndex.Clear(); + + int i, j; + for (i = 0; i < count; ++i) + PriorityIndex.Add(i); + + for (i = 0; i < count - 1; ++i) { + int largestInd = i; + float largestArea = ShadowsInView[PriorityIndex[i]].Area; + for (j = i + 1; j < count; ++j) { + int index = PriorityIndex[j]; + if (largestArea < ShadowsInView[index].Area) { + largestInd = j; + largestArea = ShadowsInView[index].Area; + } + } + (PriorityIndex[i], PriorityIndex[largestInd]) = (PriorityIndex[largestInd], PriorityIndex[i]); + } + } + + public int FindShadows(in ViewSetup view, int leafCount, List leafList) { + ShadowsInView.Clear(); + clientLeafSystem.EnumerateShadowsInLeaves(leafCount, leafList, this); + int count = ShadowsInView.Count; + if (count != 0) + PrioritySort(); + + return count; + } +} + +public class ShadowLeafEnum : ISpatialLeafEnumerator +{ + public readonly List LeafList = []; + + public bool EnumerateLeaf(int leaf, nint context) { + LeafList.Add(leaf); + return true; + } +} + +public class ClientShadowBox +{ + public ClientShadowMgr.ClientShadow_t Shadow = new(); +} + +public class ClientShadowMgr : IClientShadowMgr +{ + public enum ShadowFlags_t + { + TextureDirty = ClientShadowFlags.LastFlag << 1, + BrushModel = ClientShadowFlags.LastFlag << 2, + UsingLodShadow = ClientShadowFlags.LastFlag << 3, + LightWorld = ClientShadowFlags.LastFlag << 4, + } + + public struct ClientShadow_t + { + public ClientShadow_t() => TargetEntity = new(); + public ClientEntityHandle Entity; + public ShadowHandle_t ShadowHandle; + public ClientLeafShadowHandle_t ClientLeafShadowHandle; + public ushort Flags; + public Matrix4x4 WorldToShadow; + public Vector2 WorldSize; + public Vector3 LastOrigin; + public QAngle LastAngles; + public TextureHandle_t ShadowTexture; + public ITexture? ShadowDepthTexture; + public long RenderFrame; + public EHANDLE TargetEntity; + } + + Vector3 SimpleShadowDir; + Color AmbientLightColor; + IMaterial? SimpleShadow; + IMaterial? RenderShadow; + IMaterial? RenderModelShadow; + ITexture? DummyColorTexture; + internal readonly Dictionary Shadows = []; + readonly List ValidShadowHandles = []; + ClientShadowHandle_t curShadowHandleIdx; + readonly TextureAllocator ShadowAllocator = new(); + + bool RenderToTextureActive; + bool RenderTargetNeedsClear; + bool UpdatingDirtyShadows; + bool Threaded; + float ShadowCastDist; + float MinShadowArea; + readonly SortedSet DirtyShadows = []; + readonly List TransparentShadows = []; + + bool DepthTextureActive; + int DepthTextureResolution; + + readonly List DepthTextureCache = []; + readonly List DepthTextureCacheLocks = []; + int MaxDepthTextureShadows; + + public ClientShadowMgr() { + RenderToTextureActive = false; + DepthTextureActive = false; + + DepthTextureResolution = r_flashlightdepthres.GetInt(); + Threaded = false; + } + + public ReadOnlySpan Name() => "CCLientShadowMgr"; + + public bool Init() { + RenderTargetNeedsClear = false; + SimpleShadow = materials.FindMaterial("decals/simpleshadow", MaterialDefines.TEXTURE_GROUP_DECAL); + + Vector3 dir = new(0.1f, 0.1f, -1); + SetShadowDirection(dir); + SetShadowDistance(50); + + SetShadowBlobbyCutoffArea(0.005f); + + bool tools = commandLine.CheckParm("-tools"); + MaxDepthTextureShadows = tools ? 4 : 2; + + if (r_shadowrendertotexture.GetBool()) + InitRenderToTextureShadows(); + + if (r_flashlightdepthtexture.GetBool() && !materials.SupportsShadowDepthTextures()) { + r_flashlightdepthtexture.SetValue(0); + ShutdownDepthTextureShadows(); + } + + if (r_flashlightdepthtexture.GetBool()) + InitDepthTextureShadows(); + + materials.AddRestoreFunc(ShadowRestoreFunc); + + return true; + } + public void PostInit() { } + public void Shutdown() { + SimpleShadow = null; + Shadows.Clear(); + ValidShadowHandles.Clear(); + ShutdownRenderToTextureShadows(); + + ShutdownDepthTextureShadows(); + + materials.RemoveRestoreFunc(ShadowRestoreFunc); + } + + public void LevelInitPreEntity() { + UpdatingDirtyShadows = false; + + engine.GetAmbientLightColor(out Vector3 ambientColor); + ambientColor *= 3; + ambientColor += new Vector3(0.3f, 0.3f, 0.3f); + + byte r = ambientColor[0] > 1.0 ? (byte)255 : (byte)(255 * ambientColor[0]); + byte g = ambientColor[1] > 1.0 ? (byte)255 : (byte)(255 * ambientColor[1]); + byte b = ambientColor[2] > 1.0 ? (byte)255 : (byte)(255 * ambientColor[2]); + + SetShadowColor(r, g, b); + + if (RenderToTextureActive) { + ShadowAllocator.Reset(); + RenderTargetNeedsClear = true; + } + } + public void LevelInitPostEntity() { } + public void LevelShutdownPreClearSteamAPIContext() { } + public void LevelShutdownPreEntity() { } + public void LevelShutdownPostEntity() { + Assert(Shadows.Count == 0); + + for (int i = ValidShadowHandles.Count - 1; i >= 0; i--) + DestroyShadow(ValidShadowHandles[i]); + + if (RenderToTextureActive) + ShadowAllocator.DeallocateAllTextures(); + + r_shadows_gamecontrol.SetValue(-1); + } + + public bool IsPerFrame() => true; + + public void PreRender() { + if (r_flashlightdepthtexture.GetBool() && !materials.SupportsShadowDepthTextures()) { + r_flashlightdepthtexture.SetValue(0); + ShutdownDepthTextureShadows(); + } + + bool depthTextureActive = r_flashlightdepthtexture.GetBool(); + int depthTextureResolution = r_flashlightdepthres.GetInt(); + + if ((depthTextureActive != DepthTextureActive) || (depthTextureResolution != DepthTextureResolution)) { + if ((depthTextureActive == true) && (DepthTextureActive == true) && + (depthTextureResolution != DepthTextureResolution)) { + ShutdownDepthTextureShadows(); + InitDepthTextureShadows(); + } + else { + if (DepthTextureActive && !depthTextureActive) + ShutdownDepthTextureShadows(); + else if (depthTextureActive && !DepthTextureActive) + InitDepthTextureShadows(); + } + } + + bool renderToTextureActive = r_shadowrendertotexture.GetBool(); + if (renderToTextureActive != RenderToTextureActive) { + if (RenderToTextureActive) + ShutdownRenderToTextureShadows(); + else + InitRenderToTextureShadows(); + + UpdateAllShadows(); + return; + } + + UpdatingDirtyShadows = true; + + foreach (ClientShadowHandle_t handle in DirtyShadows) { + Assert(Shadows.ContainsKey(handle)); + UpdateProjectedTextureInternal(handle, false); + } + DirtyShadows.Clear(); + + int count = TransparentShadows.Count; + for (int j = 0; j < count; ++j) + DirtyShadows.Add(TransparentShadows[j]); + TransparentShadows.Clear(); + + UpdatingDirtyShadows = false; + } + public void Update(double frametime) { } + public void PostRender() { } + + public void OnSave() { } + public void OnRestore() { } + public void SafeRemoveIfDesired() { } + + public ClientShadowHandle_t CreateShadow(ClientEntityHandle entity, int flags) { + flags &= ~(int)ShadowFlags.ProjectedTextureTypeMask; + flags |= (int)ShadowFlags.Shadow | (int)ShadowFlags_t.TextureDirty; + ClientShadowHandle_t shadowHandle = CreateProjectedTexture(entity, flags); + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(entity); + if (renderable != null) { + Assert(!renderable.IsShadowDirty()); + renderable.MarkShadowDirty(true); + } + + AddToDirtyShadowList(shadowHandle, true); + return shadowHandle; + } + + public void DestroyShadow(ClientShadowHandle_t handle) { + Assert(Shadows.ContainsKey(handle)); + RemoveShadowFromDirtyList(handle); + g_ShadowMgr.DestroyShadow(Shadows[handle].Shadow.ShadowHandle); + clientLeafSystem.RemoveShadow(Shadows[handle].Shadow.ClientLeafShadowHandle); + CleanUpRenderToTextureShadow(handle); + Shadows.Remove(handle); + ValidShadowHandles.Remove(handle); + } + + public ClientShadowHandle_t CreateFlashlight(in FlashlightState lightState) { + ClientEntityHandle invalidHandle = INVALID_CLIENTENTITY_HANDLE; + + int shadowFlags = (int)ShadowFlags.Flashlight | (int)ShadowFlags_t.LightWorld; + if (lightState.EnableShadows && r_flashlightdepthtexture.GetBool()) { + shadowFlags |= (int)ClientShadowFlags.UseDepthTexture; + } + + ClientShadowHandle_t shadowHandle = CreateProjectedTexture(invalidHandle, shadowFlags); + + UpdateFlashlightState(shadowHandle, in lightState); + UpdateProjectedTexture(shadowHandle, true); + return shadowHandle; + } + + public void UpdateFlashlightState(ClientShadowHandle_t shadowHandle, in FlashlightState lightState) { + BuildPerspectiveWorldToFlashlightMatrix(out Shadows[shadowHandle].Shadow.WorldToShadow, in lightState); + + g_ShadowMgr.UpdateFlashlightState(Shadows[shadowHandle].Shadow.ShadowHandle, in lightState); + } + + public void DestroyFlashlight(ClientShadowHandle_t shadowHandle) => DestroyShadow(shadowHandle); + + public void UpdateProjectedTexture(ClientShadowHandle_t handle, bool force = false) { + if (handle == CLIENTSHADOW_INVALID_HANDLE) + return; + + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + if ((shadow.Flags & (int)ShadowFlags.Flashlight) == 0) { + Warning("CClientShadowMgr::UpdateProjectedTexture can only be used with flashlights!\n"); + return; + } + + UpdateProjectedTextureInternal(handle, force); + RemoveShadowFromDirtyList(handle); + } + + public void ComputeBoundingSphere(IClientRenderable? renderable, out Vector3 origin, out float radius) { + Assert(renderable != null); + renderable!.GetShadowRenderBounds(out Vector3 mins, out Vector3 maxs, GetActualShadowCastType(renderable)); + MathLib.VectorSubtract(maxs, mins, out Vector3 size); + radius = size.Length() * 0.5f; + + MathLib.VectorAdd(mins, maxs, out Vector3 centroid); + centroid *= 0.5f; + + Span vec = stackalloc Vector3[3]; + MathLib.AngleVectors(renderable.GetRenderAngles(), out vec[0], out vec[1], out vec[2]); + vec[1] *= -1.0f; + + MathLib.VectorCopy(renderable.GetRenderOrigin(), out origin); + MathLib.VectorMA(origin, centroid.X, vec[0], out origin); + MathLib.VectorMA(origin, centroid.Y, vec[1], out origin); + MathLib.VectorMA(origin, centroid.Z, vec[2], out origin); + } + + public void AddToDirtyShadowList(ClientShadowHandle_t handle, bool force = false) { + if (UpdatingDirtyShadows) + return; + + if (handle == CLIENTSHADOW_INVALID_HANDLE) + return; + + Assert(!DirtyShadows.Contains(handle)); + DirtyShadows.Add(handle); + + if (force) + Shadows[handle].Shadow.LastAngles = new(float.MaxValue, float.MaxValue, float.MaxValue); + + IClientRenderable? parent = GetParentShadowEntity(handle); + if (parent != null) + AddToDirtyShadowList(parent, force); + } + public void AddToDirtyShadowList(IClientRenderable? renderable, bool force = false) { + if (UpdatingDirtyShadows) + return; + + if (renderable!.IsShadowDirty()) + return; + + ClientShadowHandle_t handle = renderable.GetShadowHandle(); + if (handle == CLIENTSHADOW_INVALID_HANDLE) + return; + +#if DEBUG + if (handle != CLIENTSHADOW_INVALID_HANDLE) { + IClientRenderable? shadowRenderable = cl_entitylist.GetClientRenderableFromHandle(Shadows[handle].Shadow.Entity); + Assert(renderable == shadowRenderable); + } +#endif + + renderable.MarkShadowDirty(true); + AddToDirtyShadowList(handle, force); + } + + public void MarkRenderToTextureShadowDirty(ClientShadowHandle_t handle) { + if (handle != CLIENTSHADOW_INVALID_HANDLE) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + shadow.Flags |= (ushort)ShadowFlags_t.TextureDirty; + + IClientRenderable? parent = GetParentShadowEntity(handle); + if (parent != null) { + ClientShadowHandle_t parentHandle = parent.GetShadowHandle(); + if (parentHandle != CLIENTSHADOW_INVALID_HANDLE) + Shadows[parentHandle].Shadow.Flags |= (ushort)ShadowFlags_t.TextureDirty; + } + } + } + + public void AddShadowToReceiver(ClientShadowHandle_t handle, IClientRenderable? renderable, ShadowReceiver type) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + + IClientRenderable? sourceRenderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + + if (sourceRenderable == renderable) + return; + + if (!renderable!.ShouldReceiveProjectedTextures(ShadowFlags.ProjectedTextureTypeMask)) + return; + + if (CullReceiver(handle, renderable, sourceRenderable)) + return; + + switch (type) { + case ShadowReceiver.BrushModel: + if ((shadow.Flags & (int)ShadowFlags.Flashlight) != 0) { + if (!shadow.TargetEntity.IsValid() || IsFlashlightTarget(handle, renderable)) { + g_ShadowMgr.AddShadowToBrushModel(shadow.ShadowHandle, renderable.GetModel(), renderable.GetRenderOrigin(), renderable.GetRenderAngles()); + g_ShadowMgr.AddFlashlightRenderable(shadow.ShadowHandle, renderable); + } + } + else + g_ShadowMgr.AddShadowToBrushModel(shadow.ShadowHandle, renderable.GetModel(), renderable.GetRenderOrigin(), renderable.GetRenderAngles()); + break; + + case ShadowReceiver.StaticProp: + if (GetActualShadowCastType(handle) == ShadowType.RenderToTexture) { + C_BaseEntity? ent = sourceRenderable!.GetIClientUnknown()!.GetBaseEntity(); + if (ent != null && (ent.GetFlags() & (EntityFlags.NPC | EntityFlags.Client)) != 0) + g_StaticPropMgr.AddShadowToStaticProp(shadow.ShadowHandle, renderable); + } + else if ((shadow.Flags & (int)ShadowFlags.Flashlight) != 0) { + if (!shadow.TargetEntity.IsValid() || IsFlashlightTarget(handle, renderable)) { + g_StaticPropMgr.AddShadowToStaticProp(shadow.ShadowHandle, renderable); + g_ShadowMgr.AddFlashlightRenderable(shadow.ShadowHandle, renderable); + } + } + break; + + case ShadowReceiver.StudioModel: + if ((shadow.Flags & (int)ShadowFlags.Flashlight) != 0) { + if (!shadow.TargetEntity.IsValid() || IsFlashlightTarget(handle, renderable)) { + renderable.CreateModelInstance(); + g_ShadowMgr.AddShadowToModel(shadow.ShadowHandle, renderable.GetModelInstance()); + g_ShadowMgr.AddFlashlightRenderable(shadow.ShadowHandle, renderable); + } + } + break; + } + } + + public void RemoveAllShadowsFromReceiver(IClientRenderable? renderable, ShadowReceiver type) { + if (!renderable!.ShouldReceiveProjectedTextures(ShadowFlags.ProjectedTextureTypeMask)) + return; + + switch (type) { + case ShadowReceiver.BrushModel: + Model? model = renderable.GetModel(); + g_ShadowMgr.RemoveAllShadowsFromBrushModel(model); + break; + case ShadowReceiver.StaticProp: + g_StaticPropMgr.RemoveAllShadowsFromStaticProp(renderable); + break; + case ShadowReceiver.StudioModel: + if (renderable.GetModelInstance() != MODEL_INSTANCE_INVALID) + g_ShadowMgr.RemoveAllShadowsFromModel(renderable.GetModelInstance()); + break; + } + } + + public void ComputeShadowTextures(in ViewSetup view, int leafCount, List leafList) { + if (!RenderToTextureActive || r_shadows.GetInt() == 0 || r_shadows_gamecontrol.GetInt() == 0) + return; + + Threaded = false; + + int count = s_VisibleShadowList.FindShadows(in view, leafCount, leafList); + if (count == 0) + return; + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.ClearColor4ub(255, 255, 255, 0); + + MaterialHeightClipMode oldHeightClipMode = renderContext.GetHeightClipMode(); + renderContext.SetHeightClipMode(MaterialHeightClipMode.Disable); + + renderContext.MatrixMode(MaterialMatrixMode.Projection); + renderContext.PushMatrix(); + renderContext.LoadIdentity(); + renderContext.Scale(1, -1, 1); + renderContext.Ortho(0, 0, 1, 1, -9999, 0); + + renderContext.MatrixMode(MaterialMatrixMode.View); + renderContext.PushMatrix(); + + renderContext.PushRenderTargetAndViewport(ShadowAllocator.GetTexture()); + + if (RenderTargetNeedsClear) { + renderContext.ClearBuffers(true, false); + RenderTargetNeedsClear = false; + } + + int maxShadows = r_shadowmaxrendered.GetInt(); + int modelsRendered = 0; + int i; + + for (i = 0; i < count; ++i) { + ref readonly VisibleShadowInfo_t info = ref s_VisibleShadowList.GetVisibleShadow(i); + if (modelsRendered < maxShadows) { + if (DrawRenderToTextureShadow(info.Shadow, info.Area)) + ++modelsRendered; + } + else + DrawRenderToTextureShadowLOD(info.Shadow); + } + + renderContext.PopRenderTargetAndViewport(); + + renderContext.MatrixMode(MaterialMatrixMode.Projection); + renderContext.PopMatrix(); + + renderContext.MatrixMode(MaterialMatrixMode.View); + renderContext.PopMatrix(); + + renderContext.SetHeightClipMode(oldHeightClipMode); + + renderContext.SetHeightClipMode(oldHeightClipMode); + + renderContext.ClearColor3ub(0, 0, 0); + } + + public void ComputeShadowDepthTextures(in ViewSetup view) => throw new NotImplementedException(); + + public void FreeShadowDepthTextures() => throw new NotImplementedException(); + + public ITexture? GetShadowTexture(ushort h) => ShadowAllocator.GetTexture(); + + public ref readonly Source.Common.Engine.ShadowInfo_t GetShadowInfo(ClientShadowHandle_t h) => ref g_ShadowMgr.GetInfo(Shadows[h].Shadow.ShadowHandle); + + public void RenderShadowTexture(int w, int h) => throw new NotImplementedException(); + + public void SetShadowDirection(in Vector3 dir) { + MathLib.VectorCopy(dir, out SimpleShadowDir); + MathLib.VectorNormalize(ref SimpleShadowDir); + + if (RenderToTextureActive) + UpdateAllShadows(); + } + + static Vector3 s_vecDown = new(0, 0, -1); + public ref readonly Vector3 GetShadowDirection() { + if (!RenderToTextureActive) + return ref s_vecDown; + + return ref SimpleShadowDir; + } + + public void SetShadowColor(byte r, byte g, byte b) { + float fr = r / 255.0f; + float fg = g / 255.0f; + float fb = b / 255.0f; + + SimpleShadow!.ColorModulate(fr, fg, fb); + + if (RenderToTextureActive) { + RenderShadow!.ColorModulate(fr, fg, fb); + RenderModelShadow!.ColorModulate(fr, fg, fb); + } + + AmbientLightColor.R = r; + AmbientLightColor.G = g; + AmbientLightColor.B = b; + } + public void GetShadowColor(out byte r, out byte g, out byte b) { + r = AmbientLightColor.R; + g = AmbientLightColor.G; + b = AmbientLightColor.B; + } + + public void SetShadowDistance(float maxDistance) { + ShadowCastDist = maxDistance; + UpdateAllShadows(); + } + public float GetShadowDistance() => ShadowCastDist; + + public void SetShadowBlobbyCutoffArea(float minArea) => MinShadowArea = minArea; + public float GetBlobbyCutoffArea() => MinShadowArea; + + public void SetFalloffBias(ClientShadowHandle_t handle, byte bias) => throw new NotImplementedException(); + + public void RestoreRenderState() { + foreach (ClientShadowHandle_t h in ValidShadowHandles) + Shadows[h].Shadow.Flags |= (ushort)ShadowFlags_t.TextureDirty; + + SetShadowColor(AmbientLightColor.R, AmbientLightColor.G, AmbientLightColor.B); + RenderTargetNeedsClear = true; + } + + public void ComputeShadowBBox(IClientRenderable? renderable, in Vector3 absCenter, float radius, out Vector3 absMins, out Vector3 absMaxs) { + absMins = default; + absMaxs = default; + + Vector3 shadowDir = GetShadowDirection(renderable); + for (int i = 0; i < 3; ++i) { + float shadowCastDistance = GetShadowDistance(renderable); + float dist = shadowCastDistance * shadowDir[i]; + + if (shadowDir[i] < 0) { + absMins[i] = absCenter[i] - radius + dist; + absMaxs[i] = absCenter[i] + radius; + } + else { + absMins[i] = absCenter[i] - radius; + absMaxs[i] = absCenter[i] + radius + dist; + } + } + } + + public bool WillParentRenderBlobbyShadow(IClientRenderable? renderable) { + if (renderable == null) + return false; + + IClientRenderable? shadowParent = renderable.GetShadowParent(); + if (shadowParent == null) + return false; + + ShadowType shadowType = GetActualShadowCastType(shadowParent); + if (shadowType == ShadowType.None) + return WillParentRenderBlobbyShadow(shadowParent); + + return shadowType == ShadowType.Simple; + } + + public bool ShouldUseParentShadow(IClientRenderable? renderable) { + if (renderable == null) + return false; + + IClientRenderable? shadowParent = renderable.GetShadowParent(); + if (shadowParent == null) + return false; + + ShadowType shadowType = GetActualShadowCastType(shadowParent); + if (shadowType == ShadowType.Simple) + return false; + + if (shadowType == ShadowType.None) + return ShouldUseParentShadow(shadowParent); + + return true; + } + + public void SetShadowsDisabled(bool disabled) => r_shadows_gamecontrol.SetValue(disabled != true ? 1 : 0); + + void UpdateStudioShadow(IClientRenderable? renderable, ClientShadowHandle_t handle) { + if ((Shadows[handle].Shadow.Flags & (int)ShadowFlags.Flashlight) == 0) { + ComputeHierarchicalBounds(renderable, out Vector3 mins, out Vector3 maxs); + + ShadowType shadowType = GetActualShadowCastType(handle); + if (shadowType != ShadowType.RenderToTexture) + BuildOrthoShadow(renderable, handle, mins, maxs); + else + BuildRenderToTextureShadow(renderable, handle, mins, maxs); + } + else + BuildFlashlight(handle); + } + + void UpdateBrushShadow(IClientRenderable? renderable, ClientShadowHandle_t handle) { + if ((Shadows[handle].Shadow.Flags & (int)ShadowFlags.Flashlight) == 0) { + ComputeHierarchicalBounds(renderable, out Vector3 mins, out Vector3 maxs); + + ShadowType shadowType = GetActualShadowCastType(handle); + if (shadowType != ShadowType.RenderToTexture) + BuildOrthoShadow(renderable, handle, mins, maxs); + else + BuildRenderToTextureShadow(renderable, handle, mins, maxs); + } + else + BuildFlashlight(handle); + } + void UpdateShadow(ClientShadowHandle_t handle, bool force) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + if (renderable == null) { + DestroyShadow(handle); + return; + } + + if (renderable.GetModel() == null) { + renderable.MarkShadowDirty(false); + return; + } + + ref readonly Source.Common.Engine.ShadowInfo_t shadowInfo = ref g_ShadowMgr.GetInfo(shadow.ShadowHandle); + if (shadowInfo.FalloffBias == 255) { + g_ShadowMgr.EnableShadow(shadow.ShadowHandle, false); + TransparentShadows.Add(handle); + return; + } + + if (ShouldUseParentShadow(renderable) || WillParentRenderBlobbyShadow(renderable)) { + g_ShadowMgr.EnableShadow(shadow.ShadowHandle, false); + renderable.MarkShadowDirty(false); + return; + } + + g_ShadowMgr.EnableShadow(shadow.ShadowHandle, true); + + ref readonly Vector3 origin = ref renderable.GetRenderOrigin(); + ref readonly QAngle angles = ref renderable.GetRenderAngles(); + + if (force || (origin != shadow.LastOrigin) || (angles != shadow.LastAngles)) { + MathLib.VectorCopy(origin, out shadow.LastOrigin); + MathLib.VectorCopy(angles, out shadow.LastAngles); + + using MatRenderContextPtr renderContext = new(materials); + Model? model = renderable.GetModel(); + // MaterialFogMode fogMode = renderContext.GetFogMode(); + // renderContext.FogMode(MaterialFogMode.None); + switch (modelinfo.GetModelType(model)) { + case ModelType.Brush: + UpdateBrushShadow(renderable, handle); + break; + case ModelType.Studio: + UpdateStudioShadow(renderable, handle); + break; + default: + Assert(false); + break; + } + // renderContext.FogMode(fogMode); + } + + renderable.MarkShadowDirty(false); + } + + IClientRenderable? GetParentShadowEntity(ClientShadowHandle_t handle) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + if (renderable != null) { + if (ShouldUseParentShadow(renderable)) { + IClientRenderable? parent = renderable.GetShadowParent(); + while (GetActualShadowCastType(parent) == ShadowType.None) { + parent = parent!.GetShadowParent(); + Assert(parent != null); + } + return parent; + } + } + return null; + } + + void AddChildBounds(in Matrix3x4 matWorldToBBox, IClientRenderable? parent, ref Vector3 mins, ref Vector3 maxs) { + IClientRenderable? child = parent!.FirstShadowChild(); + while (child != null) { + if (GetActualShadowCastType(child) != ShadowType.None) { + child.GetShadowRenderBounds(out Vector3 childMins, out Vector3 childMaxs, ShadowType.RenderToTexture); + MathLib.ConcatTransforms(in matWorldToBBox, in child.RenderableToWorldTransform(), out Matrix3x4 childToBBox); + MathLib.TransformAABB(in childToBBox, in childMins, in childMaxs, out Vector3 newChildMins, out Vector3 newChildMaxs); + MathLib.VectorMin(mins, newChildMins, out mins); + MathLib.VectorMax(maxs, newChildMaxs, out maxs); + } + + AddChildBounds(in matWorldToBBox, child, ref mins, ref maxs); + child = child.NextShadowPeer(); + } + } + + void ComputeHierarchicalBounds(IClientRenderable? renderable, out Vector3 mins, out Vector3 maxs) { + ShadowType shadowType = GetActualShadowCastType(renderable); + + renderable!.GetShadowRenderBounds(out mins, out maxs, shadowType); + + IClientRenderable? child = renderable.FirstShadowChild(); + + if (child != null && shadowType != ShadowType.Simple) { + MathLib.MatrixInvert(in renderable.RenderableToWorldTransform(), out Matrix3x4 matWorldToBBox); + AddChildBounds(in matWorldToBBox, renderable, ref mins, ref maxs); + } + } + + void BuildGeneralWorldToShadowMatrix(out Matrix4x4 matWorldToShadow, in Vector3 origin, in Vector3 dir, in Vector3 xvec, in Vector3 yvec) { + matWorldToShadow = default; + MathLib.MatrixSetColumn(ref matWorldToShadow, 0, in xvec); + MathLib.MatrixSetColumn(ref matWorldToShadow, 1, in yvec); + MathLib.MatrixSetColumn(ref matWorldToShadow, 2, in dir); + MathLib.MatrixSetColumn(ref matWorldToShadow, 3, in origin); + matWorldToShadow[3, 0] = matWorldToShadow[3, 1] = matWorldToShadow[3, 2] = 0.0f; + matWorldToShadow[3, 3] = 1.0f; + + MathLib.MatrixInverseGeneral(in matWorldToShadow, out matWorldToShadow); + } + + static void BuildOrthoWorldToShadowMatrix(out Matrix4x4 worldToShadow, in Vector3 origin, in Vector3 dir, in Vector3 xvec, in Vector3 yvec) { + Assert(MathF.Abs(MathLib.DotProduct(dir, xvec)) < 1e-3f); + Assert(MathF.Abs(MathLib.DotProduct(dir, yvec)) < 1e-3f); + Assert(MathF.Abs(MathLib.DotProduct(xvec, yvec)) < 1e-3f); + + worldToShadow = default; + worldToShadow.SetBasisVectors(in xvec, in yvec, in dir); + MathLib.MatrixTranspose(in worldToShadow, out worldToShadow); + + MathLib.Vector3DMultiply(in worldToShadow, in origin, out Vector3 translation); + + translation *= -1.0f; + worldToShadow.SetTranslation(in translation); + + worldToShadow[3, 0] = worldToShadow[3, 1] = worldToShadow[3, 2] = 0.0f; + worldToShadow[3, 3] = 1.0f; + } + + static void BuildWorldToTextureMatrix(in Matrix4x4 matWorldToShadow, in Vector2 size, out Matrix4x4 matWorldToTexture) { + MathLib.MatrixBuildScale(out Matrix4x4 shadowToUnit, 1.0f / size.X, 1.0f / size.Y, 1.0f); + shadowToUnit[0, 3] = shadowToUnit[1, 3] = 0.5f; + + MathLib.MatrixMultiply(in shadowToUnit, in matWorldToShadow, out matWorldToTexture); + } + + static void SortAbsVectorComponents(in Vector3 src, Span vecIdx) { + Vector3 absVec = new(MathF.Abs(src[0]), MathF.Abs(src[1]), MathF.Abs(src[2])); + + int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1; + if (absVec[2] > absVec[maxIdx]) + maxIdx = 2; + + switch (maxIdx) { + case 0: + vecIdx[0] = 1; + vecIdx[1] = 2; + vecIdx[2] = 0; + break; + case 1: + vecIdx[0] = 2; + vecIdx[1] = 0; + vecIdx[2] = 1; + break; + case 2: + vecIdx[0] = 0; + vecIdx[1] = 1; + vecIdx[2] = 2; + break; + } + } + + void BuildWorldToShadowMatrix(out Matrix4x4 matWorldToShadow, in Vector3 origin, in Quaternion quatOrientation) { + MathLib.QuaternionMatrix(in quatOrientation, out Matrix3x4 matOrientation); + MathLib.PositionMatrix(in vec3_origin, ref matOrientation); + + Matrix4x4 matBasis = matOrientation; + + matBasis.GetBasisVectors(out Vector3 forward, out Vector3 left, out Vector3 up); + matBasis.SetForward(in left); + matBasis.SetLeft(in up); + matBasis.SetUp(in forward); + MathLib.MatrixTranspose(in matBasis, out matWorldToShadow); + + MathLib.Vector3DMultiply(in matWorldToShadow, in origin, out Vector3 translation); + + translation *= -1.0f; + matWorldToShadow.SetTranslation(in translation); + + matWorldToShadow[3, 0] = matWorldToShadow[3, 1] = matWorldToShadow[3, 2] = 0.0f; + matWorldToShadow[3, 3] = 1.0f; + } + + void BuildPerspectiveWorldToFlashlightMatrix(out Matrix4x4 matWorldToShadow, in FlashlightState flashlightState) { + BuildWorldToShadowMatrix(out Matrix4x4 matWorldToShadowView, in flashlightState.LightOrigin, in flashlightState.Orientation); + + MathLib.MatrixBuildPerspective(out Matrix4x4 matPerspective, flashlightState.HorizontalFOVDegrees, + flashlightState.VerticalFOVDegrees, + flashlightState.NearZ, flashlightState.FarZ); + + MathLib.MatrixMultiply(in matPerspective, in matWorldToShadowView, out matWorldToShadow); + } + + void UpdateProjectedTextureInternal(ClientShadowHandle_t handle, bool force) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + + if ((shadow.Flags & (int)ShadowFlags.Flashlight) != 0) { + Assert((shadow.Flags & (int)ShadowFlags.Shadow) == 0); + ref ClientShadow_t shadowClient = ref Shadows[handle].Shadow; + + g_ShadowMgr.EnableShadow(shadowClient.ShadowHandle, true); + + UpdateBrushShadow(null, handle); + } + else { + Assert((shadow.Flags & (int)ShadowFlags.Shadow) != 0); + Assert((shadow.Flags & (int)ShadowFlags.Flashlight) == 0); + UpdateShadow(handle, force); + } + } + + float ComputeLocalShadowOrigin(IClientRenderable? renderable, in Vector3 mins, in Vector3 maxs, in Vector3 localShadowDir, float backupFactor, out Vector3 origin) { + MathLib.VectorAdd(in mins, in maxs, out Vector3 centroid); + centroid *= 0.5f; + + MathLib.VectorSubtract(in maxs, in mins, out Vector3 size); + float radius = size.Length() * 0.5f; + + float centroidProjection = MathLib.DotProduct(centroid, 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; + + MathLib.VectorMA(in centroid, minDist, in localShadowDir, out origin); + + return radius - minDist; + } + + void RemoveShadowFromDirtyList(ClientShadowHandle_t handle) { + if (DirtyShadows.Contains(handle)) { + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(Shadows[handle].Shadow.Entity); + renderable?.MarkShadowDirty(false); + DirtyShadows.Remove(handle); + } + } + + internal ShadowType GetActualShadowCastType(ClientShadowHandle_t handle) { + if (handle == CLIENTSHADOW_INVALID_HANDLE) + return ShadowType.None; + + if ((Shadows[handle].Shadow.Flags & (int)ClientShadowFlags.UseRenderToTexture) != 0) + return RenderToTextureActive ? ShadowType.RenderToTexture : ShadowType.Simple; + else if ((Shadows[handle].Shadow.Flags & (int)ClientShadowFlags.UseDepthTexture) != 0) + return ShadowType.RenderToDepthTexture; + else + return ShadowType.Simple; + } + ShadowType GetActualShadowCastType(IClientRenderable? renderable) => GetActualShadowCastType(renderable != null ? renderable.GetShadowHandle() : CLIENTSHADOW_INVALID_HANDLE); + + static void BuildShadowLeafList(ShadowLeafEnum shadowEnum, in Vector3 origin, in Vector3 dir, in Vector2 size, float maxDist) { + Ray ray = default; + MathLib.VectorCopy(origin, out ray.Start); + MathLib.VectorMultiply(dir, maxDist, out ray.Delta); + ray.StartOffset = new(0, 0, 0); + + float radius = MathF.Sqrt(size.X * size.X + size.Y * size.Y) * 0.5f; + ray.Extents = new(radius, radius, radius); + ray.IsRay = false; + ray.IsSwept = true; + + ISpatialQuery query = engine.GetBSPTreeQuery()!; + ISpatialLeafEnumerator queryRef = shadowEnum; + query.EnumerateLeavesAlongRay(in ray, ref queryRef, 0); + } + + void BuildOrthoShadow(IClientRenderable? renderable, ClientShadowHandle_t handle, in Vector3 mins, in Vector3 maxs) { + Span vec = stackalloc Vector3[3]; + MathLib.AngleVectors(renderable!.GetRenderAngles(), out vec[0], out vec[1], out vec[2]); + vec[1] *= -1.0f; + + Vector3 shadowDir = GetShadowDirection(renderable); + + Vector3 localShadowDir = default; + localShadowDir[0] = MathLib.DotProduct(vec[0], shadowDir); + localShadowDir[1] = MathLib.DotProduct(vec[1], shadowDir); + localShadowDir[2] = MathLib.DotProduct(vec[2], shadowDir); + + Span vecIdx = stackalloc int[3]; + SortAbsVectorComponents(in localShadowDir, vecIdx); + + Vector3 xvec = vec[vecIdx[0]]; + Vector3 yvec = vec[vecIdx[1]]; + + xvec -= shadowDir * MathLib.DotProduct(shadowDir, xvec); + yvec -= shadowDir * MathLib.DotProduct(shadowDir, yvec); + MathLib.VectorNormalize(ref xvec); + MathLib.VectorNormalize(ref yvec); + + MathLib.VectorSubtract(in maxs, in mins, out Vector3 boxSize); + + Vector2 size = new(boxSize[vecIdx[0]], boxSize[vecIdx[1]]); + size.X *= MathF.Abs(MathLib.DotProduct(vec[vecIdx[0]], xvec)); + size.Y *= MathF.Abs(MathLib.DotProduct(vec[vecIdx[1]], yvec)); + + size.X += boxSize[vecIdx[2]] * MathF.Abs(MathLib.DotProduct(vec[vecIdx[2]], xvec)); + size.Y += boxSize[vecIdx[2]] * MathF.Abs(MathLib.DotProduct(vec[vecIdx[2]], yvec)); + + size.X += 10.0f; + size.Y += 10.0f; + + MathLib.Vector2DMax(in size, new Vector2(10.0f, 10.0f), out size); + + float falloffStart = ComputeLocalShadowOrigin(renderable, in mins, in maxs, in localShadowDir, 2.0f, out Vector3 org); + + Vector3 worldOrigin = renderable.GetRenderOrigin(); + MathLib.VectorMA(in worldOrigin, org.X, vec[0], out worldOrigin); + MathLib.VectorMA(in worldOrigin, org.Y, vec[1], out worldOrigin); + MathLib.VectorMA(in worldOrigin, org.Z, vec[2], out worldOrigin); + + 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; + + BuildGeneralWorldToShadowMatrix(out Shadows[handle].Shadow.WorldToShadow, in worldOrigin, in shadowDir, in xvec, in yvec); + BuildWorldToTextureMatrix(in Shadows[handle].Shadow.WorldToShadow, in size, out Matrix4x4 matWorldToTexture); + MathLib.Vector2DCopy(in size, out Shadows[handle].Shadow.WorldSize); + + float shadowCastDistance = GetShadowDistance(renderable); + float maxHeight = shadowCastDistance + falloffStart; + + ShadowLeafEnum leafList = new(); + BuildShadowLeafList(leafList, in worldOrigin, in shadowDir, in size, maxHeight); + Span pLeafList = leafList.LeafList.AsSpan(); + + g_ShadowMgr.ProjectShadow(Shadows[handle].Shadow.ShadowHandle, in worldOrigin, + in shadowDir, in matWorldToTexture, in size, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, renderable.GetRenderOrigin()); + + clientLeafSystem.ProjectShadow(Shadows[handle].Shadow.ClientLeafShadowHandle, pLeafList.Length, pLeafList); + } + + void BuildRenderToTextureShadow(IClientRenderable? renderable, ClientShadowHandle_t handle, in Vector3 mins, in Vector3 maxs) { + if (DebugViewRender.cl_drawshadowtexture.GetInt() != 0) + DrawRenderToTextureDebugInfo(renderable, in mins, in maxs); + + Span vec = stackalloc Vector3[3]; + MathLib.AngleVectors(renderable!.GetRenderAngles(), out vec[0], out vec[1], out vec[2]); + vec[1] *= -1.0f; + + Vector3 shadowDir = GetShadowDirection(renderable); + + Vector3 localShadowDir = default; + localShadowDir[0] = MathLib.DotProduct(vec[0], shadowDir); + localShadowDir[1] = MathLib.DotProduct(vec[1], shadowDir); + localShadowDir[2] = MathLib.DotProduct(vec[2], shadowDir); + + MathLib.VectorSubtract(in maxs, in mins, out Vector3 boxSize); + + Vector3 yvec = vec3_origin; + float projMax = 0.0f; + for (int i = 0; i < 3; ++i) { + Vector3 test = vec[i] - shadowDir * MathLib.DotProduct(shadowDir, vec[i]); + test *= boxSize[i]; + float lengthSqr = test.LengthSquared(); + if (lengthSqr > projMax) { + projMax = lengthSqr; + yvec = test; + } + } + + MathLib.VectorNormalize(ref yvec); + + MathLib.CrossProduct(in yvec, in shadowDir, out Vector3 xvec); + + Vector2 size; + size.X = boxSize.X * MathF.Abs(MathLib.DotProduct(vec[0], xvec)) + boxSize.Y * MathF.Abs(MathLib.DotProduct(vec[1], xvec)) + boxSize.Z * MathF.Abs(MathLib.DotProduct(vec[2], xvec)); + size.Y = boxSize.X * MathF.Abs(MathLib.DotProduct(vec[0], yvec)) + boxSize.Y * MathF.Abs(MathLib.DotProduct(vec[1], yvec)) + boxSize.Z * MathF.Abs(MathLib.DotProduct(vec[2], yvec)); + + size.X += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + size.Y += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + + float falloffStart = ComputeLocalShadowOrigin(renderable, in mins, in maxs, in localShadowDir, 1.0f, out Vector3 org); + + Vector3 worldOrigin = renderable.GetRenderOrigin(); + MathLib.VectorMA(in worldOrigin, org.X, vec[0], out worldOrigin); + MathLib.VectorMA(in worldOrigin, org.Y, vec[1], out worldOrigin); + MathLib.VectorMA(in worldOrigin, org.Z, vec[2], out worldOrigin); + + BuildOrthoWorldToShadowMatrix(out Shadows[handle].Shadow.WorldToShadow, in worldOrigin, in shadowDir, in xvec, in yvec); + BuildWorldToTextureMatrix(in Shadows[handle].Shadow.WorldToShadow, in size, out Matrix4x4 matWorldToTexture); + MathLib.Vector2DCopy(in size, out Shadows[handle].Shadow.WorldSize); + + float shadowCastDistance = GetShadowDistance(renderable); + float maxHeight = shadowCastDistance + falloffStart; + + ShadowLeafEnum leafList = new(); + BuildShadowLeafList(leafList, in worldOrigin, in shadowDir, in size, maxHeight); + Span pLeafList = leafList.LeafList.AsSpan(); + + g_ShadowMgr.ProjectShadow(Shadows[handle].Shadow.ShadowHandle, in worldOrigin, in shadowDir, in matWorldToTexture, in size, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, renderable.GetRenderOrigin()); + + ComputeExtraClipPlanes(renderable, handle, vec, in mins, in maxs, in localShadowDir); + + clientLeafSystem.ProjectShadow(Shadows[handle].Shadow.ClientLeafShadowHandle, pLeafList.Length, pLeafList); + } + + static void BuildFlashlightLeafList(ShadowLeafEnum shadowEnum, in Matrix4x4 worldToShadow) { + MathLib.CalculateAABBFromProjectionMatrix(in worldToShadow, out Vector3 mins, out Vector3 maxs); + ISpatialQuery query = engine.GetBSPTreeQuery()!; + ISpatialLeafEnumerator queryRef = shadowEnum; + query.EnumerateLeavesInBox(in mins, in maxs, ref queryRef, 0); + } + + void BuildFlashlight(ClientShadowHandle_t handle) { + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + if (r_flashlight_version2.GetInt() != 0) { + g_ShadowMgr.ProjectFlashlight(shadow.ShadowHandle, in shadow.WorldToShadow, default); + return; + } + + bool lightModels = r_flashlightmodels.GetBool(); + bool lightSpecificEntity = shadow.TargetEntity.Get() != null; + bool lightWorld = (shadow.Flags & (int)ShadowFlags_t.LightWorld) != 0; + int count = 0; + ReadOnlySpan leafList = default; + + ShadowLeafEnum leafEnum = new(); + if (lightWorld || (lightModels && !lightSpecificEntity)) { + BuildFlashlightLeafList(leafEnum, in shadow.WorldToShadow); + count = leafEnum.LeafList.Count; + leafList = leafEnum.LeafList.AsSpan(); + } + + if (lightWorld) + g_ShadowMgr.ProjectFlashlight(shadow.ShadowHandle, in shadow.WorldToShadow, leafList); + else { + g_ShadowMgr.EnableShadow(shadow.ShadowHandle, false); + g_ShadowMgr.EnableShadow(shadow.ShadowHandle, true); + } + + if (!lightModels) + return; + + if (!lightSpecificEntity) { + clientLeafSystem.ProjectFlashlight(shadow.ClientLeafShadowHandle, count, leafList); + return; + } + + Assert(shadow.TargetEntity.Get()!.GetModel() != null); + + C_BaseEntity? child = shadow.TargetEntity.Get()!.FirstMoveChild(); + while (child != null) { + ModelType modelType = modelinfo.GetModelType(child.GetModel()); + if (modelType == ModelType.Brush) + AddShadowToReceiver(handle, child, ShadowReceiver.BrushModel); + else if (modelType == ModelType.Studio) + AddShadowToReceiver(handle, child, ShadowReceiver.StudioModel); + + child = child.NextMovePeer(); + } + + ModelType targetModelType = modelinfo.GetModelType(shadow.TargetEntity.Get()!.GetModel()); + if (targetModelType == ModelType.Brush) + AddShadowToReceiver(handle, shadow.TargetEntity.Get(), ShadowReceiver.BrushModel); + else if (targetModelType == ModelType.Studio) + AddShadowToReceiver(handle, shadow.TargetEntity.Get(), ShadowReceiver.StudioModel); + } + + void SetupRenderToTextureShadow(ClientShadowHandle_t h) { + ref ClientShadow_t shadow = ref Shadows[h].Shadow; + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + if (renderable == null) + return; + + renderable.GetShadowRenderBounds(out Vector3 mins, out Vector3 maxs, GetActualShadowCastType(h)); + + MathLib.VectorSubtract(maxs, mins, out Vector3 size); + float maxSize = Math.Max(size.X, size.Y); + maxSize = Math.Max(maxSize, size.Z); + + float texelCount = TEXEL_SIZE_PER_CASTER_SIZE * maxSize; + + int textureSize = 1; + while (textureSize < texelCount) + textureSize <<= 1; + + shadow.ShadowTexture = ShadowAllocator.AllocateTexture(textureSize, textureSize); + } + void CleanUpRenderToTextureShadow(ClientShadowHandle_t h) { + ref ClientShadow_t shadow = ref Shadows[h].Shadow; + if (RenderToTextureActive && (shadow.Flags & (int)ClientShadowFlags.UseRenderToTexture) != 0) { + ShadowAllocator.DeallocateTexture(shadow.ShadowTexture); + shadow.ShadowTexture = INVALID_TEXTURE_HANDLE; + } + } + + void ComputeExtraClipPlanes(IClientRenderable? renderable, ClientShadowHandle_t handle, ReadOnlySpan vec, in Vector3 mins, in Vector3 maxs, in Vector3 localShadowDir) { + Vector3 origin = renderable!.GetRenderOrigin(); + Span dir = stackalloc float[3]; + + int i; + for (i = 0; i < 3; ++i) { + if (localShadowDir[i] < 0.0f) { + MathLib.VectorMA(in origin, maxs[i], vec[i], out origin); + dir[i] = 1; + } + else { + MathLib.VectorMA(in origin, mins[i], vec[i], out origin); + dir[i] = -1; + } + } + + Vector3 normal = default; + ClearExtraClipPlanes(handle); + for (i = 0; i < 3; ++i) { + MathLib.VectorMultiply(vec[i], dir[i], out normal); + float dist = MathLib.DotProduct(normal, origin); + AddExtraClipPlane(handle, in normal, dist); + } + + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + C_BaseEntity? entity = cl_entitylist.GetBaseEntityFromHandle(shadow.Entity); + if (entity != null && entity.EnableRenderingClipPlane) { + normal[0] = -entity.RenderingClipPlane[0]; + normal[1] = -entity.RenderingClipPlane[1]; + normal[2] = -entity.RenderingClipPlane[2]; + AddExtraClipPlane(handle, in normal, -entity.RenderingClipPlane[3] - 0.5f); + } + } + + void ClearExtraClipPlanes(ClientShadowHandle_t h) => g_ShadowMgr.ClearExtraClipPlanes(Shadows[h].Shadow.ShadowHandle); + void AddExtraClipPlane(ClientShadowHandle_t h, in Vector3 normal, float dist) => g_ShadowMgr.AddExtraClipPlane(Shadows[h].Shadow.ShadowHandle, in normal, dist); + + bool CullReceiver(ClientShadowHandle_t handle, IClientRenderable? renderable, IClientRenderable? sourceRenderable) { + if ((Shadows[handle].Shadow.Flags & (int)ShadowFlags.Flashlight) != 0) { + Assert(sourceRenderable == null); + Frustum_t frustum = g_ShadowMgr.GetFlashlightFrustum(Shadows[handle].Shadow.ShadowHandle); + + renderable!.GetRenderBoundsWorldspace(out Vector3 mins, out Vector3 maxs); + + return MathLib.R_CullBox(mins, maxs, frustum); + } + + Assert(sourceRenderable != null); + ComputeBoundingSphere(renderable, out Vector3 origin, out float radius); + + ref ClientShadow_t shadow = ref Shadows[handle].Shadow; + ref readonly Source.Common.Engine.ShadowInfo_t info = ref g_ShadowMgr.GetInfo(shadow.ShadowHandle); + MathLib.Vector3DMultiplyPosition(shadow.WorldToShadow, origin, out Vector3 localOrigin); + + Vector3 shadowMin = new(-shadow.WorldSize.X * 0.5f, -shadow.WorldSize.Y * 0.5f, 0); + Vector3 shadowMax = new(shadow.WorldSize.X * 0.5f, shadow.WorldSize.Y * 0.5f, info.MaxDist); + + if (!CollisionUtils.IsBoxIntersectingSphere(shadowMin, shadowMax, localOrigin, radius)) + return true; + + ComputeBoundingSphere(sourceRenderable, out Vector3 originSource, out float radiusSource); + + bool foundSeparatingPlane; + CollisionPlane plane; + if (!CollisionUtils.IsSphereIntersectingSphere(originSource, radiusSource, origin, radius)) { + foundSeparatingPlane = true; + plane = default; + + MathLib.VectorSubtract(origin, originSource, out plane.Normal); + } + else + foundSeparatingPlane = ComputeSeparatingPlane(renderable, sourceRenderable, out plane); + + if (foundSeparatingPlane) { + Vector3 shadowDir = GetShadowDirection(sourceRenderable); + float shadowDot = MathLib.DotProduct(shadowDir, plane.Normal); + float receiverDot = MathLib.DotProduct(plane.Normal, origin); + float sourceDot = MathLib.DotProduct(plane.Normal, originSource); + + if (shadowDot > 0.0f) { + if (receiverDot <= sourceDot) + return true; + } + else { + if (receiverDot >= sourceDot) + return true; + } + } + + return false; + } + + bool ComputeSeparatingPlane(IClientRenderable? rend1, IClientRenderable? rend2, out CollisionPlane plane) { + rend1!.GetShadowRenderBounds(out Vector3 min1, out Vector3 max1, GetActualShadowCastType(rend1)); + rend2!.GetShadowRenderBounds(out Vector3 min2, out Vector3 max2, GetActualShadowCastType(rend2)); + return CollisionUtils.ComputeSeparatingPlane(rend1.GetRenderOrigin(), rend1.GetRenderAngles(), min1, max1, rend2.GetRenderOrigin(), rend2.GetRenderAngles(), min2, max2, 3.0f, out plane); + } + + void UpdateAllShadows() { + foreach (ClientShadowHandle_t i in ValidShadowHandles) { + ref ClientShadow_t shadow = ref Shadows[i].Shadow; + + if ((shadow.Flags & (int)ShadowFlags.Flashlight) != 0) + continue; + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + if (renderable == null) + continue; + + Assert(renderable.GetShadowHandle() == i); + UpdateProjectedTextureInternal(i, false); + } + } + + bool DrawRenderToTextureShadow(ushort clientShadowHandle, float area) { + ref ClientShadow_t shadow = ref Shadows[clientShadowHandle].Shadow; + + bool previouslyUsingLODShadow = (shadow.Flags & (int)ShadowFlags_t.UsingLodShadow) != 0; + shadow.Flags &= unchecked((ushort)~(int)ShadowFlags_t.UsingLodShadow); + if (previouslyUsingLODShadow) + g_ShadowMgr.SetShadowMaterial(shadow.ShadowHandle, RenderShadow, RenderModelShadow, clientShadowHandle); + + bool dirtyTexture = (shadow.Flags & (int)ShadowFlags_t.TextureDirty) != 0; + bool drewTexture = false; + bool needsRedraw = !Threaded && ShadowAllocator.UseTexture(shadow.ShadowTexture, dirtyTexture, area); + + if (!ShadowAllocator.HasValidTexture(shadow.ShadowTexture)) { + DrawRenderToTextureShadowLOD(clientShadowHandle); + return false; + } + + if (needsRedraw || dirtyTexture) { + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + + using MatRenderContextPtr renderContext = new(materials); + + ShadowAllocator.GetTextureRect(shadow.ShadowTexture, out int x, out int y, out int w, out int h); + renderContext.Viewport(x, y, w, h); + + renderContext.ClearBuffers(true, false); + + renderContext.MatrixMode(MaterialMatrixMode.View); + renderContext.LoadMatrix(g_ShadowMgr.GetInfo(shadow.ShadowHandle).WorldToShadow); + + if (DrawShadowHierarchy(renderable, in shadow)) + drewTexture = true; + else + DevMsg("Didn't draw shadow hierarchy.. bad shadow texcoords probably going to happen..grab Brian!\n"); + + if ((shadow.Flags & (int)ClientShadowFlags.AnimatingSource) == 0) + shadow.Flags &= unchecked((ushort)~(int)ShadowFlags_t.TextureDirty); + + SetRenderToTextureShadowTexCoords(shadow.ShadowHandle, x, y, w, h); + } + else if (previouslyUsingLODShadow) { + ShadowAllocator.GetTextureRect(shadow.ShadowTexture, out int x, out int y, out int w, out int h); + SetRenderToTextureShadowTexCoords(shadow.ShadowHandle, x, y, w, h); + } + + return drewTexture; + } + void DrawRenderToTextureShadowLOD(ushort clientShadowHandle) { + ref ClientShadow_t shadow = ref Shadows[clientShadowHandle].Shadow; + if ((shadow.Flags & (int)ShadowFlags_t.UsingLodShadow) == 0) { + g_ShadowMgr.SetShadowMaterial(shadow.ShadowHandle, SimpleShadow, SimpleShadow, CLIENTSHADOW_INVALID_HANDLE); + g_ShadowMgr.SetShadowTexCoord(shadow.ShadowHandle, 0, 0, 1, 1); + ClearExtraClipPlanes(clientShadowHandle); + shadow.Flags |= (ushort)ShadowFlags_t.UsingLodShadow; + } + } + + bool DrawShadowHierarchy(IClientRenderable? renderable, in ClientShadow_t shadow, bool child = false) { + bool drewTexture = false; + + ShadowType shadowType = GetActualShadowCastType(renderable); + if (renderable != null && shadowType == ShadowType.Simple) + return false; + + if (renderable == null || shadowType != ShadowType.None) { + bool drawModelShadow; + bool drawBrushShadow; + if (!child) { + drawModelShadow = (shadow.Flags & (int)ShadowFlags_t.BrushModel) == 0; + drawBrushShadow = !drawModelShadow; + } + else { + ModelType modelType = modelinfo.GetModelType(renderable!.GetModel()); + drawModelShadow = modelType == ModelType.Studio; + drawBrushShadow = modelType == ModelType.Brush; + } + + if (drawModelShadow) { + DrawModelInfo info = default; + if (modelrender.DrawModelShadowSetup(renderable!, renderable!.GetBody(), renderable.GetSkin(), ref info, default, out Span boneToWorld)) + modelrender.DrawModelShadow(renderable, in info, boneToWorld); + drewTexture = true; + } + else if (drawBrushShadow) { + render.DrawBrushModelShadow(renderable!); + drewTexture = true; + } + } + + if (renderable == null) + return drewTexture; + + for (IClientRenderable? pChild = renderable.FirstShadowChild(); pChild != null; pChild = pChild.NextShadowPeer()) { + if (DrawShadowHierarchy(pChild, in shadow, true)) + drewTexture = true; + } + return drewTexture; + } + + bool BuildSetupListForRenderToTextureShadow(ushort clientShadowHandle, float area) { + ref ClientShadow_t shadow = ref Shadows[clientShadowHandle].Shadow; + bool dirtyTexture = (shadow.Flags & (int)ShadowFlags_t.TextureDirty) != 0; + bool needsRedraw = ShadowAllocator.UseTexture(shadow.ShadowTexture, dirtyTexture, area); + if (needsRedraw || dirtyTexture) { + shadow.Flags |= (ushort)ShadowFlags_t.TextureDirty; + + if (!ShadowAllocator.HasValidTexture(shadow.ShadowTexture)) + return false; + + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(shadow.Entity); + + if (BuildSetupShadowHierarchy(renderable, in shadow)) + return true; + } + return false; + } + + bool BuildSetupShadowHierarchy(IClientRenderable? renderable, in ClientShadow_t shadow, bool child = false) { + bool drewTexture = false; + + ShadowType shadowType = GetActualShadowCastType(renderable); + if (renderable != null && shadowType == ShadowType.Simple) + return false; + + if (renderable == null || shadowType != ShadowType.None) { + bool drawModelShadow; + if (!child) { + drawModelShadow = (shadow.Flags & (int)ShadowFlags_t.BrushModel) == 0; + } + else { + ModelType modelType = modelinfo.GetModelType(renderable!.GetModel()); + drawModelShadow = modelType == ModelType.Studio; + } + + if (drawModelShadow) { + C_BaseEntity? entity = renderable?.GetIClientUnknown()?.GetBaseEntity(); + if (entity != null) { + if (entity.IsNPC()) + s_NPCShadowBoneSetups.Add((C_BaseAnimating)entity); + else if (entity.GetBaseAnimating() != null) + s_NonNPCShadowBoneSetups.Add((C_BaseAnimating)entity); + } + drewTexture = true; + } + } + + if (renderable == null) + return drewTexture; + + for (IClientRenderable? pChild = renderable.FirstShadowChild(); pChild != null; pChild = pChild.NextShadowPeer()) { + if (BuildSetupShadowHierarchy(pChild, in shadow, true)) + drewTexture = true; + } + return drewTexture; + } + + void SetRenderToTextureShadowTexCoords(ShadowHandle_t handle, int x, int y, int w, int h) { + ShadowAllocator.GetTotalTextureSize(out int textureW, out int textureH); + + 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; + + g_ShadowMgr.SetShadowTexCoord(handle, u, v, du, dv); + } + + void DrawRenderToTextureDebugInfo(IClientRenderable? renderable, in Vector3 mins, in Vector3 maxs) { + if (debugoverlay == null) + return; + + Span vec = stackalloc Vector3[3]; + MathLib.AngleVectors(renderable!.GetRenderAngles(), out vec[0], out vec[1], out vec[2]); + vec[1] *= -1.0f; + + MathLib.VectorSubtract(in maxs, in mins, out Vector3 size); + + Vector3 origin = renderable.GetRenderOrigin(); + + MathLib.VectorMA(in origin, mins.X, vec[0], out Vector3 start); + MathLib.VectorMA(in start, mins.Y, vec[1], out start); + MathLib.VectorMA(in start, mins.Z, vec[2], out start); + + MathLib.VectorMA(in start, size.X, vec[0], out Vector3 end); + MathLib.VectorMA(in end, size.Z, vec[2], out Vector3 end2); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + debugoverlay.AddLineOverlay(in end2, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, size.Y, vec[1], out end); + MathLib.VectorMA(in end, size.Z, vec[2], out end2); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + debugoverlay.AddLineOverlay(in end2, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, size.Z, vec[2], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + start = end; + MathLib.VectorMA(in start, size.X, vec[0], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, size.Y, vec[1], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in end, size.X, vec[0], out start); + MathLib.VectorMA(in start, -size.X, vec[0], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, -size.Y, vec[1], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, -size.Z, vec[2], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + start = end; + MathLib.VectorMA(in start, -size.X, vec[0], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + MathLib.VectorMA(in start, -size.Y, vec[1], out end); + debugoverlay.AddLineOverlay(in start, in end, 255, 0, 0, true, 0.01f); + + C_BaseEntity? ent = renderable.GetIClientUnknown()?.GetBaseEntity(); + if (ent != null) + debugoverlay.AddTextOverlay(in origin, 0, $"{ent.EntIndex()}"); + else + debugoverlay.AddTextOverlay(in origin, 0, $"{renderable}"); + } + + public void AdvanceFrame() => ShadowAllocator.AdvanceFrame(); + + float GetShadowDistance(IClientRenderable? renderable) { + float dist = ShadowCastDist; + + renderable!.GetShadowCastDistance(ref dist, GetActualShadowCastType(renderable)); + + return dist; + } + + Vector3 GetShadowDirection(IClientRenderable? renderable) { + Vector3 result = GetShadowDirection(); + + renderable!.GetShadowCastDirection(ref result, GetActualShadowCastType(renderable)); + + return result; + } + + void InitDepthTextureShadows() { + if (!DepthTextureActive) { + DepthTextureActive = true; + + ImageFormat dstFormat = materials.GetShadowDepthTextureFormat(); + ImageFormat nullFormat = materials.GetNullTextureFormat(); + + materials.BeginRenderTargetAllocation(); + + DummyColorTexture = InitRenderTarget(r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RenderTargetSizeMode.Offscreen, nullFormat, MaterialRenderTargetDepth.None, false, "_rt_ShadowDummy"); + + DepthTextureCache.Clear(); + DepthTextureCacheLocks.Clear(); + Span strRTName = stackalloc char[64]; + for (int i = 0; i < MaxDepthTextureShadows; i++) { + sprintf(strRTName, "_rt_ShadowDepthTexture_%d").D(i); + + ITexture? depthTex = InitRenderTarget(DepthTextureResolution, DepthTextureResolution, RenderTargetSizeMode.Offscreen, dstFormat, MaterialRenderTargetDepth.None, false, strRTName.SliceNullTerminatedString()); + + if (i == 0) { + DepthTextureResolution = depthTex!.GetActualWidth(); + r_flashlightdepthres.SetValue(DepthTextureResolution); + } + + DepthTextureCache.Add(depthTex); + DepthTextureCacheLocks.Add(false); + } + + materials.EndRenderTargetAllocation(); + } + } + + static ITexture? InitRenderTarget(int w, int h, RenderTargetSizeMode sizeMode, ImageFormat fmt, MaterialRenderTargetDepth depth, bool hdr, ReadOnlySpan strOptionalName) { + TextureFlags textureFlags = TextureFlags.ClampS | TextureFlags.ClampT; + if (depth == MaterialRenderTargetDepth.Only) + textureFlags |= TextureFlags.PointSample; + + CreateRenderTargetFlags renderTargetFlags = hdr ? CreateRenderTargetFlags.HDR : 0; + + ITexture? texture = materials.CreateNamedRenderTargetTextureEx(strOptionalName, w, h, sizeMode, fmt, depth, textureFlags, renderTargetFlags); + + Assert(texture != null); + return texture; + } + + void ShutdownDepthTextureShadows() { + if (DepthTextureActive) { + DummyColorTexture = null; + + while (DepthTextureCache.Count != 0) { + DepthTextureCacheLocks.RemoveAt(DepthTextureCache.Count - 1); + DepthTextureCache.RemoveAt(DepthTextureCache.Count - 1); + } + + DepthTextureActive = false; + } + } + + void InitRenderToTextureShadows() { + if (!RenderToTextureActive) { + RenderToTextureActive = true; + RenderShadow = materials.FindMaterial("decals/rendershadow", MaterialDefines.TEXTURE_GROUP_DECAL); + RenderModelShadow = materials.FindMaterial("decals/rendermodelshadow", MaterialDefines.TEXTURE_GROUP_DECAL); + ShadowAllocator.Init(); + + ShadowAllocator.Reset(); + RenderTargetNeedsClear = true; + + float fr = AmbientLightColor.R / 255.0f; + float fg = AmbientLightColor.G / 255.0f; + float fb = AmbientLightColor.B / 255.0f; + RenderShadow!.ColorModulate(fr, fg, fb); + RenderModelShadow!.ColorModulate(fr, fg, fb); + + foreach (ClientShadowHandle_t i in ValidShadowHandles) { + ref ClientShadow_t shadow = ref Shadows[i].Shadow; + if ((shadow.Flags & (int)ClientShadowFlags.UseRenderToTexture) != 0) { + SetupRenderToTextureShadow(i); + MarkRenderToTextureShadowDirty(i); + + g_ShadowMgr.SetShadowMaterial(shadow.ShadowHandle, RenderShadow, RenderModelShadow, i); + } + } + } + } + + void ShutdownRenderToTextureShadows() { + if (RenderToTextureActive) { + foreach (ClientShadowHandle_t i in ValidShadowHandles) { + CleanUpRenderToTextureShadow(i); + + ref ClientShadow_t shadow = ref Shadows[i].Shadow; + g_ShadowMgr.SetShadowMaterial(shadow.ShadowHandle, SimpleShadow, SimpleShadow, CLIENTSHADOW_INVALID_HANDLE); + g_ShadowMgr.SetShadowTexCoord(shadow.ShadowHandle, 0, 0, 1, 1); + ClearExtraClipPlanes(i); + } + + RenderShadow = null; + RenderModelShadow = null; + + ShadowAllocator.DeallocateAllTextures(); + ShadowAllocator.Shutdown(); + + // materials.UncacheUnusedMaterials(); + + RenderToTextureActive = false; + } + } + + static bool ShadowHandleCompareFunc(ClientShadowHandle_t lhs, ClientShadowHandle_t rhs) => lhs < rhs; + + ClientShadowHandle_t CreateProjectedTexture(ClientEntityHandle entity, int flags) { + if ((flags & (int)ShadowFlags.Flashlight) == 0) { + IClientRenderable? renderable = cl_entitylist.GetClientRenderableFromHandle(entity); + if (renderable == null) + return CLIENTSHADOW_INVALID_HANDLE; + + ModelType modelType = modelinfo.GetModelType(renderable.GetModel()); + if (modelType == ModelType.Brush) + flags |= (int)ShadowFlags_t.BrushModel; + } + + while (curShadowHandleIdx == CLIENTSHADOW_INVALID_HANDLE || Shadows.ContainsKey(curShadowHandleIdx)) + curShadowHandleIdx++; + + ClientShadowHandle_t h = curShadowHandleIdx++; + Shadows[h] = new(); + ValidShadowHandles.Add(h); + ref ClientShadow_t shadow = ref Shadows[h].Shadow; + shadow.Entity = entity; + shadow.ClientLeafShadowHandle = clientLeafSystem.AddShadow(h, (ushort)flags); + shadow.Flags = (ushort)flags; + shadow.RenderFrame = -1; + shadow.LastOrigin = new(float.MaxValue, float.MaxValue, float.MaxValue); + shadow.LastAngles = new(float.MaxValue, float.MaxValue, float.MaxValue); + Assert((shadow.Flags & (int)ShadowFlags.Flashlight) == 0 != ((shadow.Flags & (int)ShadowFlags.Shadow) == 0)); + + IMaterial? shadowMaterial = SimpleShadow; + IMaterial? shadowModelMaterial = SimpleShadow; + object? shadowProxyData = CLIENTSHADOW_INVALID_HANDLE; + + if (RenderToTextureActive && (flags & (int)ClientShadowFlags.UseRenderToTexture) != 0) { + SetupRenderToTextureShadow(h); + + shadowMaterial = RenderShadow; + shadowModelMaterial = RenderModelShadow; + shadowProxyData = h; + } + + if ((flags & (int)ClientShadowFlags.UseDepthTexture) != 0) { + shadowMaterial = RenderShadow; + shadowModelMaterial = RenderModelShadow; + shadowProxyData = h; + } + + ShadowCreateFlags createShadowFlags; + if ((flags & (int)ShadowFlags.Flashlight) != 0) + createShadowFlags = ShadowCreateFlags.Flashlight; + else + createShadowFlags = ShadowCreateFlags.CacheVerts; + + shadow.ShadowHandle = g_ShadowMgr.CreateShadowEx(shadowMaterial, shadowModelMaterial, shadowProxyData, (int)createShadowFlags); + return h; + } + + bool LockShadowDepthTexture(ref ITexture? shadowDepthTexture) => throw new NotImplementedException(); + public void UnlockAllShadowDepthTextures() => throw new NotImplementedException(); + + public void SetFlashlightTarget(ClientShadowHandle_t shadowHandle, EHANDLE targetEntity) { + Assert(Shadows.ContainsKey(shadowHandle)); + + ref ClientShadow_t shadow = ref Shadows[shadowHandle].Shadow; + if ((shadow.Flags & (int)ShadowFlags.Flashlight) == 0) + return; + + shadow.TargetEntity = targetEntity; + } + + public void SetFlashlightLightWorld(ClientShadowHandle_t shadowHandle, bool lightWorld) { + Assert(Shadows.ContainsKey(shadowHandle)); + + ref ClientShadow_t shadow = ref Shadows[shadowHandle].Shadow; + if ((shadow.Flags & (int)ShadowFlags.Flashlight) == 0) + return; + + if (lightWorld) + shadow.Flags |= (ushort)ShadowFlags_t.LightWorld; + else + shadow.Flags &= unchecked((ushort)~(int)ShadowFlags_t.LightWorld); + } + + bool IsFlashlightTarget(ClientShadowHandle_t shadowHandle, IClientRenderable? renderable) { + ref ClientShadow_t shadow = ref Shadows[shadowHandle].Shadow; + + if (shadow.TargetEntity.Get()!.GetClientRenderable() == renderable) + return true; + + C_BaseEntity? child = shadow.TargetEntity.Get()!.FirstMoveChild(); + while (child != null) { + if (child.GetClientRenderable() == renderable) + return true; + + child = child.NextMovePeer(); + } + + return false; + } + + int BuildActiveShadowDepthList(in ViewSetup viewSetup, int maxDepthShadows, Span activeDepthShadows) => throw new NotImplementedException(); + + void SetViewFlashlightState(int activeFlashlightCount, ReadOnlySpan activeFlashlights) => throw new NotImplementedException(); +} + +[ExposeMaterialProxy(Name = "Shadow")] +public class ShadowProxy : IMaterialProxy +{ + IMaterialVar? BaseTextureVar; + + public bool Init(IMaterial material, KeyValues keyValues) { + BaseTextureVar = material.FindVar("$basetexture", out bool foundVar, false); + return foundVar; + } + + public void OnBind(object? proxyData) { + ClientShadowHandle_t clientShadowHandle = (ClientShadowHandle_t)(proxyData ?? CLIENTSHADOW_INVALID_HANDLE); + ITexture? tex = s_ClientShadowMgr.GetShadowTexture(clientShadowHandle); + BaseTextureVar!.SetTextureValue(tex); + } + + public void Release() { } + + public IMaterial GetMaterial() => BaseTextureVar!.GetOwningMaterial(); +} + +[ExposeMaterialProxy(Name = "ShadowModel")] +public class ShadowModelProxy : IMaterialProxy +{ + IMaterialVar? BaseTextureVar; + IMaterialVar? BaseTextureOffsetVar; + IMaterialVar? BaseTextureScaleVar; + IMaterialVar? BaseTextureMatrixVar; + IMaterialVar? FalloffOffsetVar; + IMaterialVar? FalloffDistanceVar; + IMaterialVar? FalloffAmountVar; + + public bool Init(IMaterial material, KeyValues keyValues) { + BaseTextureVar = material.FindVar("$basetexture", out bool foundVar, false); + if (!foundVar) return false; + BaseTextureOffsetVar = material.FindVar("$basetextureoffset", out foundVar, false); + if (!foundVar) return false; + BaseTextureScaleVar = material.FindVar("$basetexturescale", out foundVar, false); + if (!foundVar) return false; + BaseTextureMatrixVar = material.FindVar("$basetexturetransform", out foundVar, false); + if (!foundVar) return false; + FalloffOffsetVar = material.FindVar("$falloffoffset", out foundVar, false); + if (!foundVar) return false; + FalloffDistanceVar = material.FindVar("$falloffdistance", out foundVar, false); + if (!foundVar) return false; + FalloffAmountVar = material.FindVar("$falloffamount", out foundVar, false); + return foundVar; + } + + public void OnBind(object? proxyData) { + ClientShadowHandle_t clientShadowHandle = (ClientShadowHandle_t)(proxyData ?? CLIENTSHADOW_INVALID_HANDLE); + ITexture? tex = s_ClientShadowMgr.GetShadowTexture(clientShadowHandle); + BaseTextureVar!.SetTextureValue(tex); + + ref readonly Source.Common.Engine.ShadowInfo_t info = ref s_ClientShadowMgr.GetShadowInfo(clientShadowHandle); + BaseTextureMatrixVar!.SetMatrixValue(in info.WorldToShadow); + BaseTextureOffsetVar!.SetVecValue(in info.TexOrigin); + BaseTextureScaleVar!.SetVecValue(in info.TexSize); + FalloffOffsetVar!.SetFloatValue(info.FalloffOffset); + FalloffDistanceVar!.SetFloatValue(info.MaxDist); + FalloffAmountVar!.SetFloatValue(info.FalloffAmount); + } + + public void Release() { } + + public IMaterial GetMaterial() => BaseTextureVar!.GetOwningMaterial(); +} diff --git a/Game.Client/DebugOverlayPanel.cs b/Game.Client/DebugOverlayPanel.cs index fcf56ca0..7a6cdacc 100644 --- a/Game.Client/DebugOverlayPanel.cs +++ b/Game.Client/DebugOverlayPanel.cs @@ -44,13 +44,13 @@ public override void OnTick() { SetVisible(visible); } - private bool ShouldDraw() => debugoverlay != null && false;//debugoverlay.GetFirst() != null; //todo + private bool ShouldDraw() => debugoverlay != null && debugoverlay.GetFirst() != null; public override void Paint() { if (debugoverlay == null) return; - OverlayText? curText = null;//debugoverlay.GetFirst(); //TODO + OverlayText? curText = debugoverlay.GetFirst(); while (curText != null) { if (curText.Text[0] != '\0') { byte r = (byte)curText.R; diff --git a/Game.Client/DetailObjectSystem.cs b/Game.Client/DetailObjectSystem.cs index bdb88ce9..4e720647 100644 --- a/Game.Client/DetailObjectSystem.cs +++ b/Game.Client/DetailObjectSystem.cs @@ -215,8 +215,8 @@ public void GetRenderBounds(out Vector3 mins, out Vector3 maxs) { public IPVSNotify? GetPVSNotifyInterface() => null; public void GetRenderBoundsWorldspace(out Vector3 mins, out Vector3 maxs) => IClientLeafSystemEngine.DefaultRenderBoundsWorldspace(this, out mins, out maxs); public bool ShouldReceiveProjectedTextures(ShadowFlags flags) => false; - public bool GetShadowCastDistance(out float dist, ShadowType shadowType) { dist = 0; return false; } - public bool GetShadowCastDirection(out Vector3 direction, ShadowType shadowType) { direction = default; return false; } + public bool GetShadowCastDistance(ref float dist, ShadowType shadowType) { return false; } + public bool GetShadowCastDirection(ref Vector3 direction, ShadowType shadowType) { return false; } public bool UsesPowerOfTwoFrameBufferTexture() => false; public bool UsesFullFrameBufferTexture() => false; public bool IgnoresZBuffer() => false; diff --git a/Game.Client/FlashlightEffect.cs b/Game.Client/FlashlightEffect.cs new file mode 100644 index 00000000..d03192e3 --- /dev/null +++ b/Game.Client/FlashlightEffect.cs @@ -0,0 +1,299 @@ +using Source; +using Source.Common; +using Source.Common.Commands; +using Source.Common.Engine; +using Source.Common.Formats.BSP; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; + +using System.Numerics; + +using static Game.Client.FlashlightEffectGlobals; + +namespace Game.Client; + +static class FlashlightEffectGlobals +{ + public static ConVar r_newflashlight = new("r_newflashlight", "1", FCvar.Cheat, ""); + public static ConVar r_swingflashlight = new("r_swingflashlight", "1", FCvar.Cheat); + public static ConVar r_flashlightlockposition = new("r_flashlightlockposition", "0", FCvar.Cheat); + public static ConVar r_flashlightfov = new("r_flashlightfov", "45.0", FCvar.Cheat); + public static ConVar r_flashlightoffsetx = new("r_flashlightoffsetx", "10.0", FCvar.Cheat); + public static ConVar r_flashlightoffsety = new("r_flashlightoffsety", "-20.0", FCvar.Cheat); + public static ConVar r_flashlightoffsetz = new("r_flashlightoffsetz", "24.0", FCvar.Cheat); + public static ConVar r_flashlightnear = new("r_flashlightnear", "4.0", FCvar.Cheat); + public static ConVar r_flashlightfar = new("r_flashlightfar", "750.0", FCvar.Cheat); + public static ConVar r_flashlightconstant = new("r_flashlightconstant", "0.0", FCvar.Cheat); + public static ConVar r_flashlightlinear = new("r_flashlightlinear", "100.0", FCvar.Cheat); + public static ConVar r_flashlightquadratic = new("r_flashlightquadratic", "0.0", FCvar.Cheat); + public static ConVar r_flashlightvisualizetrace = new("r_flashlightvisualizetrace", "0", FCvar.Cheat); + public static ConVar r_flashlightambient = new("r_flashlightambient", "0.0", FCvar.Cheat); + public static ConVar r_flashlightshadowatten = new("r_flashlightshadowatten", "0.35", FCvar.Cheat); + public static ConVar r_flashlightladderdist = new("r_flashlightladderdist", "40.0", FCvar.Cheat); + public static ConVar mat_slopescaledepthbias_shadowmap = new("mat_slopescaledepthbias_shadowmap", "16", FCvar.Cheat); + public static ConVar mat_depthbias_shadowmap = new("mat_depthbias_shadowmap", "0.0005", FCvar.Cheat); +} + +struct TraceFilterSkipPlayerAndViewModel : ITraceFilter +{ + public bool ShouldHitEntity(IHandleEntity serverEntity, Contents contentsMask) { + C_BaseEntity? entity = (C_BaseEntity?)EntityFromEntityHandle(serverEntity); + if (entity == null) + return true; + + if (entity is C_BaseViewModel || + entity is C_BasePlayer || + entity.GetCollisionGroup() == CollisionGroup.Debris || + entity.GetCollisionGroup() == CollisionGroup.InteractiveDebris) { + return false; + } + + return true; + } +} + +class FlashlightEffect : IDisposable +{ + bool IsOn; + int EntIndex; + ClientShadowHandle_t FlashlightHandle; + // dlight todo + float DistMod; + protected TextureReference FlashlightTexture = new(); + + public FlashlightEffect(int entIndex) { + FlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + EntIndex = entIndex; + + IsOn = false; + // PointLight = NULL; + DistMod = 0; + + // if (g_pMaterialSystemHardwareConfig->SupportsBorderColor()) + FlashlightTexture.Init("effects/flashlight_border", MaterialDefines.TEXTURE_GROUP_OTHER, true); + // else + // m_FlashlightTexture.Init("effects/flashlight001", TEXTURE_GROUP_OTHER, true); + } + + public void Dispose() { + LightOff(); + } + + public virtual void UpdateLight(in Vector3 pos, in Vector3 dir, in Vector3 right, in Vector3 up, int distance) { + if (!IsOn) + return; + + if (r_newflashlight.GetBool()) + UpdateLightNew(in pos, in dir, in right, in up); + else + UpdateLightOld(in pos, in dir, distance); + } + + public void TurnOn() { + IsOn = true; + DistMod = 1.0f; + } + + public void TurnOff() { + if (IsOn) { + IsOn = false; + LightOff(); + } + } + + public bool GetIsOn() => IsOn; + + public ClientShadowHandle_t GetFlashlightHandle() => FlashlightHandle; + + public void SetFlashlightHandle(ClientShadowHandle_t handle) => FlashlightHandle = handle; + + protected void UpdateLightNew(in Vector3 pos, in Vector3 forward, in Vector3 right, in Vector3 up) { + FlashlightState state = new(); + + bool playerOnLadder = C_BasePlayer.GetLocalPlayer()!.GetMoveType() == MoveType.Ladder; + + const float epsilon = 0.1f; + const float distCutoff = 128.0f; + const float distDrag = 0.2f; + + TraceFilterSkipPlayerAndViewModel traceFilter = new(); + float offsetY = r_flashlightoffsety.GetFloat(); + + if (r_swingflashlight.GetBool()) { + Vector3 swingLight = pos + forward * -12.0f; + if (swingLight.Z > pos.Z) + offsetY += swingLight.Z - pos.Z; + } + + Vector3 origin = pos + offsetY * up; + + if (!playerOnLadder) { + Util.TraceHull(in pos, in origin, new Vector3(-4, -4, -4), new Vector3(4, 4, 4), Mask.Solid & ~(Mask)Contents.HitBox, ref traceFilter, out Trace originTrace); + + if (originTrace.DidHit()) + origin = pos; + } + else + origin = pos; + + Mask mask = Mask.OpaqueAndNPCs; + mask &= ~(Mask)Contents.HitBox; + mask |= (Mask)Contents.Window; + + Vector3 target = pos + forward * r_flashlightfar.GetFloat(); + + Vector3 dir = target - origin; + Vector3 vRight = right; + Vector3 vUp = up; + MathLib.VectorNormalize(ref dir); + MathLib.VectorNormalize(ref vRight); + MathLib.VectorNormalize(ref vUp); + + vUp -= MathLib.DotProduct(dir, vUp) * dir; + MathLib.VectorNormalize(ref vUp); + vRight -= MathLib.DotProduct(dir, vRight) * dir; + MathLib.VectorNormalize(ref vRight); + vRight -= MathLib.DotProduct(vUp, vRight) * vUp; + MathLib.VectorNormalize(ref vRight); + + AssertFloatEquals(MathLib.DotProduct(dir, vRight), 0.0f, 1e-3f); + AssertFloatEquals(MathLib.DotProduct(dir, vUp), 0.0f, 1e-3f); + AssertFloatEquals(MathLib.DotProduct(vRight, vUp), 0.0f, 1e-3f); + + Util.TraceHull(in origin, in target, new Vector3(-4, -4, -4), new Vector3(4, 4, 4), mask, ref traceFilter, out Trace directionTrace); + + if (r_flashlightvisualizetrace.GetBool() == true) { + if (debugoverlay != null) { + debugoverlay.AddBoxOverlay(in directionTrace.EndPos, new Vector3(-4, -4, -4), new Vector3(4, 4, 4), new QAngle(0, 0, 0), 0, 0, 255, 16, 0); + debugoverlay.AddLineOverlay(in origin, in directionTrace.EndPos, 255, 0, 0, false, 0); + } + } + + float dist = (directionTrace.EndPos - origin).Length(); + if (dist < distCutoff) { + float pullBackDist = playerOnLadder ? r_flashlightladderdist.GetFloat() : distCutoff - dist; + DistMod = MathLib.Lerp(distDrag, DistMod, pullBackDist); + + if (!playerOnLadder) { + Util.TraceHull(in origin, origin - dir * (pullBackDist - epsilon), new Vector3(-4, -4, -4), new Vector3(4, 4, 4), mask, ref traceFilter, out Trace backTrace); + if (backTrace.DidHit()) { + float maxDist = (backTrace.EndPos - origin).Length() - epsilon; + if (DistMod > maxDist) + DistMod = maxDist; + } + } + } + else + DistMod = MathLib.Lerp(distDrag, DistMod, 0.0f); + origin -= dir * DistMod; + + state.LightOrigin = origin; + + MathLib.BasisToQuaternion(in dir, in vRight, in vUp, out state.Orientation); + + state.QuadraticAtten = r_flashlightquadratic.GetFloat(); + + const bool flicker = false; + + // HL2_EPISODIC todo + + if (flicker == false) { + state.LinearAtten = r_flashlightlinear.GetFloat(); + state.HorizontalFOVDegrees = r_flashlightfov.GetFloat(); + state.VerticalFOVDegrees = r_flashlightfov.GetFloat(); + } + + state.ConstantAtten = r_flashlightconstant.GetFloat(); + state.Color[0] = 1.0f; + state.Color[1] = 1.0f; + state.Color[2] = 1.0f; + state.Color[3] = r_flashlightambient.GetFloat(); + state.NearZ = r_flashlightnear.GetFloat() + DistMod; + state.FarZ = r_flashlightfar.GetFloat(); + state.EnableShadows = r_flashlightdepthtexture.GetBool(); + state.ShadowMapResolution = r_flashlightdepthres.GetInt(); + + state.SpotlightTexture = FlashlightTexture.Get(); + state.SpotlightTextureFrame = 0; + + state.ShadowAtten = r_flashlightshadowatten.GetFloat(); + state.ShadowSlopeScaleDepthBias = mat_slopescaledepthbias_shadowmap.GetFloat(); + state.ShadowDepthBias = mat_depthbias_shadowmap.GetFloat(); + + if (FlashlightHandle == CLIENTSHADOW_INVALID_HANDLE) + FlashlightHandle = g_ClientShadowMgr.CreateFlashlight(in state); + else if (!r_flashlightlockposition.GetBool()) + g_ClientShadowMgr.UpdateFlashlightState(FlashlightHandle, in state); + + g_ClientShadowMgr.UpdateProjectedTexture(FlashlightHandle, true); + + LightOffOld(); + } + + protected void UpdateLightOld(in Vector3 pos, in Vector3 dir, int distance) { + // dlight todo + + LightOffNew(); + } + + protected void LightOffNew() { + if (FlashlightHandle != CLIENTSHADOW_INVALID_HANDLE) { + g_ClientShadowMgr.DestroyFlashlight(FlashlightHandle); + FlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + } + } + + protected void LightOffOld() { + // dlight todo + } + + protected void LightOff() { + LightOffOld(); + LightOffNew(); + } +} + +class HeadlightEffect : FlashlightEffect +{ + public HeadlightEffect() : base(0) { } + + public override void UpdateLight(in Vector3 pos, in Vector3 dir, in Vector3 right, in Vector3 up, int distance) { + if (GetIsOn() == false) + return; + + FlashlightState state = new(); + Vector3 basisX, basisY, basisZ; + basisX = dir; + basisY = right; + basisZ = up; + MathLib.VectorNormalize(ref basisX); + MathLib.VectorNormalize(ref basisY); + MathLib.VectorNormalize(ref basisZ); + + MathLib.BasisToQuaternion(in basisX, in basisY, in basisZ, out state.Orientation); + + state.LightOrigin = pos; + + state.HorizontalFOVDegrees = 45.0f; + state.VerticalFOVDegrees = 30.0f; + state.QuadraticAtten = r_flashlightquadratic.GetFloat(); + state.LinearAtten = r_flashlightlinear.GetFloat(); + state.ConstantAtten = r_flashlightconstant.GetFloat(); + state.Color[0] = 1.0f; + state.Color[1] = 1.0f; + state.Color[2] = 1.0f; + state.Color[3] = r_flashlightambient.GetFloat(); + state.NearZ = r_flashlightnear.GetFloat(); + state.FarZ = r_flashlightfar.GetFloat(); + state.EnableShadows = true; + state.SpotlightTexture = FlashlightTexture.Get(); + state.SpotlightTextureFrame = 0; + + if (GetFlashlightHandle() == CLIENTSHADOW_INVALID_HANDLE) + SetFlashlightHandle(g_ClientShadowMgr.CreateFlashlight(in state)); + else + g_ClientShadowMgr.UpdateFlashlightState(GetFlashlightHandle(), in state); + + g_ClientShadowMgr.UpdateProjectedTexture(GetFlashlightHandle(), true); + } +} \ No newline at end of file diff --git a/Game.Client/GlobalUsings.cs b/Game.Client/GlobalUsings.cs index 634c7904..d65bd4f8 100644 --- a/Game.Client/GlobalUsings.cs +++ b/Game.Client/GlobalUsings.cs @@ -1,4 +1,7 @@ global using EHANDLE = Source.Common.Handle; +global using ClientLeafShadowHandle_t = uint; +global using TextureHandle_t = ushort; +global using FragmentHandle_t = ushort; global using static Game.Client.SourceDllMain; global using static Game.Client.BeamDraw; diff --git a/Game.Client/HL2MP/C_HL2MP_Player.cs b/Game.Client/HL2MP/C_HL2MP_Player.cs index 345af727..b479cee9 100644 --- a/Game.Client/HL2MP/C_HL2MP_Player.cs +++ b/Game.Client/HL2MP/C_HL2MP_Player.cs @@ -104,6 +104,13 @@ public override void ClientThink() { PlayerAnimState.Update(); } + public override ShadowType ShadowCastType() { + if (!IsVisible()) + return ShadowType.None; + + return ShadowType.RenderToTextureDynamic; + } + public override ref readonly QAngle GetRenderAngles() => ref PlayerAnimState.GetRenderAngles(); } diff --git a/Game.Client/HLClient.cs b/Game.Client/HLClient.cs index d119ab6c..45e86f5a 100644 --- a/Game.Client/HLClient.cs +++ b/Game.Client/HLClient.cs @@ -119,6 +119,7 @@ public bool Init() { IGameSystem.Add(Singleton()); IGameSystem.Add(DetailObjectSystem.GetDetailObjectSystem()); IGameSystem.Add(Singleton()); + IGameSystem.Add(g_ClientShadowMgr); IGameSystem.Add(ClientSoundscapeSystem()); vgui = services.GetService(); @@ -410,7 +411,8 @@ public void LevelShutdown() { modemanager.LevelShutdown(); // tempents.LevelShutdown(); - // cl_entitylist.Release() + + cl_entitylist.Release(); // C_BaseEntityClassList classList = s_pClassLists; // while (classList != null) { @@ -486,9 +488,9 @@ public void GMod_ReceiveLuaFile(ReadOnlySpan fileName, in SHA256Value sha2 h.Stream.Write(compressed); filesRequesting_Recv++; - if (filesRequesting_Recv != filesRequesting_Total) + if (filesRequesting_Recv != filesRequesting_Total) gameUI.UpdateProgressBar(filesRequesting_Recv / (float)filesRequesting_Total, $"Received {filesRequesting_Recv}/{filesRequesting_Total} Lua files..."); - + } public void FileReceived(ReadOnlySpan fileName, uint transferID) { diff --git a/Game.Client/IClientLeafSystem.cs b/Game.Client/IClientLeafSystem.cs index 3f11c85c..63142500 100644 --- a/Game.Client/IClientLeafSystem.cs +++ b/Game.Client/IClientLeafSystem.cs @@ -66,6 +66,14 @@ public interface IClientLeafSystem : IClientLeafSystemEngine, IGameSystemPerFram void EnableAlternateSorting(ClientRenderHandle_t renderHandle, bool alternateSorting); bool IsRenderableInPVS(IClientRenderable renderable); + ClientLeafShadowHandle_t AddShadow(ClientShadowHandle_t userId, ushort flags); + void RemoveShadow(ClientLeafShadowHandle_t h); + + void ProjectShadow(ClientLeafShadowHandle_t handle, int leafCount, ReadOnlySpan leafList); + void ProjectFlashlight(ClientLeafShadowHandle_t handle, int leafCount, ReadOnlySpan leafList); + + void EnumerateShadowsInLeaves(int leafCount, List leaves, IClientLeafShadowEnum enumerator); + void RenderableChanged(ClientRenderHandle_t handle); void SetRenderGroup(ClientRenderHandle_t handle, RenderGroup group); diff --git a/Game.Client/IClientShadowMgr.cs b/Game.Client/IClientShadowMgr.cs new file mode 100644 index 00000000..da051edd --- /dev/null +++ b/Game.Client/IClientShadowMgr.cs @@ -0,0 +1,69 @@ +using Game.Shared; + +using Source.Common; +using Source.Common.Engine; +using Source.Common.MaterialSystem; + +using System.Numerics; + +namespace Game.Client; + +public enum ShadowReceiver +{ + BrushModel = 0, + StaticProp, + StudioModel, +} + +public enum ClientShadowFlags +{ + UseRenderToTexture = (int)ShadowFlags.LastFlag << 1, + AnimatingSource = (int)ShadowFlags.LastFlag << 2, + UseDepthTexture = (int)ShadowFlags.LastFlag << 3, + LastFlag = UseDepthTexture, +} + +public interface IClientShadowMgr : IGameSystemPerFrame +{ + ClientShadowHandle_t CreateShadow(ClientEntityHandle entity, int flags); + void DestroyShadow(ClientShadowHandle_t handle); + + ClientShadowHandle_t CreateFlashlight(in FlashlightState lightState); + void UpdateFlashlightState(ClientShadowHandle_t shadowHandle, in FlashlightState lightState); + void DestroyFlashlight(ClientShadowHandle_t handle); + + void UpdateProjectedTexture(ClientShadowHandle_t handle, bool force = false); + + void AddToDirtyShadowList(ClientShadowHandle_t handle, bool force = false); + void AddToDirtyShadowList(IClientRenderable? renderable, bool force = false); + + void AddShadowToReceiver(ClientShadowHandle_t handle, IClientRenderable? renderable, ShadowReceiver type); + + void RemoveAllShadowsFromReceiver(IClientRenderable? renderable, ShadowReceiver type); + + void ComputeShadowTextures(in ViewSetup view, int leafCount, List leafList); + + void UnlockAllShadowDepthTextures(); + + void RenderShadowTexture(int w, int h); + + void SetShadowDirection(in Vector3 dir); + ref readonly Vector3 GetShadowDirection(); + + void SetShadowColor(byte r, byte g, byte b); + void SetShadowDistance(float maxDistance); + void SetShadowBlobbyCutoffArea(float minArea); + void SetFalloffBias(ClientShadowHandle_t handle, byte bias); + + void MarkRenderToTextureShadowDirty(ClientShadowHandle_t handle); + + void AdvanceFrame(); + + void SetFlashlightTarget(ClientShadowHandle_t shadowHandle, EHANDLE targetEntity); + + void SetFlashlightLightWorld(ClientShadowHandle_t shadowHandle, bool lightWorld); + + void SetShadowsDisabled(bool disabled); + + void ComputeShadowDepthTextures(in ViewSetup view); +} diff --git a/Game.Client/View.cs b/Game.Client/View.cs index 5a52c704..d86a6556 100644 --- a/Game.Client/View.cs +++ b/Game.Client/View.cs @@ -358,6 +358,8 @@ public void RenderView(in ViewSetup viewRender, ClearFlags clearFlags, RenderVie ITexture? saveRenderTarget = renderContext.GetRenderTarget(); } + g_ClientShadowMgr.AdvanceFrame(); + RenderingView = true; render.SceneBegin(); using (renderContext = new MatRenderContextPtr(materials)) @@ -452,6 +454,12 @@ private void ViewDrawScene(bool drew3dSkybox, SkyboxVisibility skyboxVisible, in IGameSystem.PreRenderAllSystems(); SetupVis(in viewRender, out uint visFlags); + g_ClientShadowMgr.PreRender(); + + // todo + // if (r_flashlightdepthtexture.GetBool() && viewID == ViewID.Main) + // g_ClientShadowMgr.ComputeShadowDepthTextures(viewRender); + bool drawSkybox = ViewRenderConVars.r_skybox.GetBool(); if (drew3dSkybox || skyboxVisible == SkyboxVisibility.NotVisible) drawSkybox = false; @@ -459,6 +467,10 @@ private void ViewDrawScene(bool drew3dSkybox, SkyboxVisibility skyboxVisible, in DrawWorldAndEntities(drawSkybox, in viewRender, clearFlags); DebugViewRender.Draw3DDebuggingInfo(in viewRender); + + // todo + // if (r_flashlightdepthtexture.GetBool()) + // g_ClientShadowMgr.UnlockAllShadowDepthTextures(); } private void DrawWorldAndEntities(bool drawSkybox, in ViewSetup viewRender, ClearFlags clearFlags) { diff --git a/Game.Client/ViewDebug.cs b/Game.Client/ViewDebug.cs index 1593151c..e787c3cf 100644 --- a/Game.Client/ViewDebug.cs +++ b/Game.Client/ViewDebug.cs @@ -1,4 +1,5 @@ using Source.Common; +using Source.Common.Commands; using System; using System.Collections.Generic; @@ -8,6 +9,9 @@ namespace Game.Client; public static class DebugViewRender { + public static readonly ConVar cl_drawshadowtexture = new("cl_drawshadowtexture", "0", FCvar.Cheat); + public static readonly ConVar cl_shadowtextureoverlaysize = new("cl_shadowtextureoverlaysize", "256", FCvar.Cheat); + public static void Draw3DDebuggingInfo(in ViewSetup view) { render.Draw3DDebugOverlays(); } diff --git a/Game.Client/ViewRender.cs b/Game.Client/ViewRender.cs index 57189d22..36b7c4fe 100644 --- a/Game.Client/ViewRender.cs +++ b/Game.Client/ViewRender.cs @@ -112,8 +112,15 @@ static void MaybeInvalidateLocalPlayerAnimation() { } protected void DrawExecute(float waterHeight, ViewID viewID, float waterZAdjust) { + ViewID savedViewID = ViewRender.g_CurrentViewID; + + ViewRender.g_CurrentViewID = ViewID.ShadowDepthTexture; + MaybeInvalidateLocalPlayerAnimation(); + g_ClientShadowMgr.ComputeShadowTextures(in setup, WorldListInfo.LeafCount, WorldListInfo.LeafList); MaybeInvalidateLocalPlayerAnimation(); + ViewRender.g_CurrentViewID = savedViewID; + using MatRenderContextPtr renderContext = new(mainView.materials); renderContext.ClearBuffers(false, true, false); @@ -596,6 +603,8 @@ private void DrawInternal(ViewID skyBoxViewID, bool invokePreAndPostRender, ITex BuildRenderableRenderLists(skyBoxViewID); // render.EndUpdateLightmaps(); + g_ClientShadowMgr.ComputeShadowTextures(in setup, WorldListInfo.LeafCount, WorldListInfo.LeafList); + DrawWorld(0); // Iterate over all leaves and render objects in those leaves diff --git a/Game.Server/BaseAnimating.cs b/Game.Server/BaseAnimating.cs index 5c4c92b0..ac1f5955 100644 --- a/Game.Server/BaseAnimating.cs +++ b/Game.Server/BaseAnimating.cs @@ -110,6 +110,8 @@ public void ResetSequence(int sequence) { ResetSequenceInfo(); } + public bool ComputeHitboxSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) => throw new NotImplementedException(); + public Activity LookupActivity(ReadOnlySpan label) { return Animation.LookupActivity(GetModelPtr(), label); } @@ -169,7 +171,7 @@ public void LockStudioHdr() { pStudioHdrContainer.Init(pStudioHdr, mdlcache); } } - else + else pStudioHdrContainer = StudioHdr; Assert((pStudioHdr == null && pStudioHdrContainer == null) || (pStudioHdrContainer != null && pStudioHdrContainer.GetRenderHdr() == pStudioHdr)); diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index 08f77fa5..e3510d33 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -963,6 +963,12 @@ protected void CalcAbsolutePosition() { public void ClearSolidFlags() => CollisionProp().ClearSolidFlags(); public object? GetBaseEntity() => this; public virtual BaseAnimating? GetBaseAnimating() => null; + + public virtual void ComputeWorldSpaceSurroundingBox(out Vector3 vecMins, out Vector3 vecMaxs) { + Assert(false); + vecMins = default; + vecMaxs = default; + } private float GetFriction() => Friction; public void NetworkStateChanged() => NetworkProp().NetworkStateChanged(); diff --git a/Game.Shared/BaseEntityShared.cs b/Game.Shared/BaseEntityShared.cs index d3a3f78d..088d2eb3 100644 --- a/Game.Shared/BaseEntityShared.cs +++ b/Game.Shared/BaseEntityShared.cs @@ -118,22 +118,28 @@ public void InvalidatePhysicsRecursive(InvalidatePhysicsBits changeFlags) { if ((changeFlags & InvalidatePhysicsBits.PositionChanged) != 0) { dirtyFlags |= EFL.DirtyAbsTransform; - // TODO: mark dirty + +#if !CLIENT_DLL + // todo + // NetworkProp().MarkPVSInformationDirty(); +#endif + + CollisionProp().MarkPartitionHandleDirty(); } // NOTE: This has to be done after velocity + position because we change the // changeFlags for child entities. An angle change also requires recomputing position. if ((changeFlags & InvalidatePhysicsBits.AnglesChanged) != 0) { dirtyFlags |= EFL.DirtyAbsTransform; - if (CollisionProp().DoesRotationInvalidateSurroundingBox()) + if (CollisionProp().DoesRotationInvalidateSurroundingBox()) // NOTE: This will handle the KD-tree, surrounding bounds, PVS // render-to-texture shadow, shadow projection, and client leaf dirty CollisionProp().MarkSurroundingBoundsDirty(); else { #if CLIENT_DLL // MarkRenderHandleDirty(); - // g_pClientShadowMgr.AddToDirtyShadowList(this); - // g_pClientShadowMgr.MarkRenderToTextureShadowDirty(GetShadowHandle()); + g_ClientShadowMgr.AddToDirtyShadowList(this); + g_ClientShadowMgr.MarkRenderToTextureShadowDirty(GetShadowHandle()); #endif } changeFlags |= InvalidatePhysicsBits.PositionChanged | InvalidatePhysicsBits.VelocityChanged; @@ -185,7 +191,7 @@ public long GetNextThinkTick(ReadOnlySpan context = default) { int index = 0; if (context.IsEmpty) { #if DEBUG - if (CurrentThinkContext != NO_THINK_CONTEXT) + if (CurrentThinkContext != NO_THINK_CONTEXT) Msg($"Warning: Getting base nextthink time within think context {ThinkFunctions[CurrentThinkContext].Context}\n"); #endif @@ -195,40 +201,40 @@ public long GetNextThinkTick(ReadOnlySpan context = default) { // Old system return (long)(TICK_INTERVAL * NextThinkTick); } - else + else // Find the think function in our list index = GetIndexForThinkContext(context); - + if (index == NO_THINK_CONTEXT) return TICK_NEVER_THINK; ref ThinkFunc tf = ref ThinkFunctions.AsSpan()[index]; - if (tf.NextThinkTick == TICK_NEVER_THINK) + if (tf.NextThinkTick == TICK_NEVER_THINK) return TICK_NEVER_THINK; - + return (long)(TICK_INTERVAL * (tf.NextThinkTick)); } - public TimeUnit_t GetLastThink(ReadOnlySpan context){ + public TimeUnit_t GetLastThink(ReadOnlySpan context) { // Are we currently in a think function with a context? int index = 0; if (context.IsEmpty) { #if DEBUG - if (CurrentThinkContext != NO_THINK_CONTEXT) + if (CurrentThinkContext != NO_THINK_CONTEXT) Msg($"Warning: Getting base lastthink time within think context {ThinkFunctions[CurrentThinkContext].Context}\n"); #endif // Old system return LastThinkTick * TICK_INTERVAL; } - else + else // Find the think function in our list index = GetIndexForThinkContext(context); return ThinkFunctions.AsSpan()[index].LastThinkTick * TICK_INTERVAL; } - public long GetLastThinkTick(ReadOnlySpan context){ + public long GetLastThinkTick(ReadOnlySpan context) { // Are we currently in a think function with a context? int index = 0; if (context.IsEmpty) { @@ -304,7 +310,7 @@ public void CheckHasGamePhysicsSimulation() { } int iIndex = GetIndexForThinkContext(context); - if (iIndex == NO_THINK_CONTEXT) + if (iIndex == NO_THINK_CONTEXT) iIndex = RegisterThinkContext(context); var thinkFns = ThinkFunctions.AsSpan(); @@ -340,11 +346,11 @@ private bool WillSimulateGamePhysics() { public void SetNextThink(int contextIndex, TimeUnit_t thinkTime) { int thinkTick = (thinkTime == TICK_NEVER_THINK) ? TICK_NEVER_THINK : TIME_TO_TICKS(thinkTime); - if (contextIndex < 0) + if (contextIndex < 0) SetNextThink(thinkTime); - else + else ThinkFunctions.AsSpan()[contextIndex].NextThinkTick = thinkTick; - + CheckHasThinkFunction(thinkTick == TICK_NEVER_THINK ? false : true); } diff --git a/Game.Shared/CollisionProperty.cs b/Game.Shared/CollisionProperty.cs index c5e6c0cb..8fbc5931 100644 --- a/Game.Shared/CollisionProperty.cs +++ b/Game.Shared/CollisionProperty.cs @@ -13,6 +13,7 @@ using Source.Common.Engine; using Source.Common.Formats.BSP; using Source.Common.Mathematics; +using Source.Common.Physics; using Source.Engine; using System.Numerics; @@ -267,8 +268,146 @@ public bool DoesRotationInvalidateSurroundingBox() { } + void ComputeVPhysicsSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + bool setBounds = false; + vecWorldMins = default; + vecWorldMaxs = default; + IPhysicsObject? physicsObject = GetOuter().VPhysicsGetObject(); + if (physicsObject != null) { + if (physicsObject.GetCollide() != null) { + physcollision.CollideGetAABB(out vecWorldMins, out vecWorldMaxs, + physicsObject.GetCollide(), GetCollisionOrigin(), GetCollisionAngles()); + setBounds = true; + } + else if (physicsObject.GetSphereRadius() != 0) { + float radius = physicsObject.GetSphereRadius(); + Vector3 extents = new(radius, radius, radius); + MathLib.VectorSubtract(in GetCollisionOrigin(), in extents, out vecWorldMins); + MathLib.VectorAdd(in GetCollisionOrigin(), in extents, out vecWorldMaxs); + setBounds = true; + } + } + + if (!setBounds) { + vecWorldMins = GetCollisionOrigin(); + vecWorldMaxs = vecWorldMins; + } + + if (IsSolidFlagSet(Source.SolidFlags.UseTriggerBounds)) { + WorldSpaceTriggerBounds(out Vector3 vecWorldTriggerMins, out Vector3 vecWorldTriggerMaxs); + MathLib.VectorMin(in vecWorldTriggerMins, in vecWorldMins, out vecWorldMins); + MathLib.VectorMax(in vecWorldTriggerMaxs, in vecWorldMaxs, out vecWorldMaxs); + } + } + + bool ComputeHitboxSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + BaseAnimating? anim = GetOuter().GetBaseAnimating(); + if (anim != null) + return anim.ComputeHitboxSurroundingBox(out vecWorldMins, out vecWorldMaxs); + + vecWorldMins = default; + vecWorldMaxs = default; + return false; + } + + void ComputeRotationExpandedBounds(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + if (!IsBoundsDefinedInEntitySpace()) { + vecWorldMins = Mins; + vecWorldMaxs = Maxs; + } + else { + vecWorldMins = default; + vecWorldMaxs = default; + + float maxVal; + maxVal = Math.Max(FloatMakePositive(Mins.X), FloatMakePositive(Maxs.X)); + vecWorldMins.X = -maxVal; + vecWorldMaxs.X = maxVal; + + maxVal = Math.Max(FloatMakePositive(Mins.Y), FloatMakePositive(Maxs.Y)); + vecWorldMins.Y = -maxVal; + vecWorldMaxs.Y = maxVal; + + maxVal = Math.Max(FloatMakePositive(Mins.Z), FloatMakePositive(Maxs.Z)); + vecWorldMins.Z = -maxVal; + vecWorldMaxs.Z = maxVal; + } + } + + void ComputeCollisionSurroundingBox(bool useVPhysics, out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + Assert(GetSolid() != Source.SolidType.Custom); + + if (useVPhysics) + ComputeVPhysicsSurroundingBox(out vecWorldMins, out vecWorldMaxs); + else + WorldSpaceTriggerBounds(out vecWorldMins, out vecWorldMaxs); + } + + void ComputeSurroundingBox(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { + if ((GetSolid() == Source.SolidType.Custom) && ((SurroundingBoundsType)SurroundType != SurroundingBoundsType.UseGameCode)) { + vecWorldMins = GetCollisionOrigin(); + vecWorldMaxs = vecWorldMins; + return; + } + + switch ((SurroundingBoundsType)SurroundType) { + case SurroundingBoundsType.UseOBBCollisionBounds: { + Assert(GetSolid() != Source.SolidType.Custom); + bool useVPhysics = false; + if ((GetSolid() == Source.SolidType.VPhysics) && (GetOuter().GetMoveType() == MoveType.VPhysics)) { + IPhysicsObject? physics = GetOuter().VPhysicsGetObject(); + useVPhysics = physics != null && physics.IsAsleep(); + } + ComputeCollisionSurroundingBox(useVPhysics, out vecWorldMins, out vecWorldMaxs); + } + break; + + case SurroundingBoundsType.UseBestCollisionBounds: + Assert(GetSolid() != Source.SolidType.Custom); + ComputeCollisionSurroundingBox(GetSolid() == Source.SolidType.VPhysics, out vecWorldMins, out vecWorldMaxs); + break; + + case SurroundingBoundsType.UseCollisionBoundsNeverVPhysics: + Assert(GetSolid() != Source.SolidType.Custom); + ComputeCollisionSurroundingBox(false, out vecWorldMins, out vecWorldMaxs); + break; + + case SurroundingBoundsType.UseHitboxes: + ComputeHitboxSurroundingBox(out vecWorldMins, out vecWorldMaxs); + break; + + case SurroundingBoundsType.UseRotationExpandedBounds: + ComputeRotationExpandedBounds(out vecWorldMins, out vecWorldMaxs); + break; + + case SurroundingBoundsType.UseSpecifiedBounds: + MathLib.VectorAdd(in GetCollisionOrigin(), in SpecifiedSurroundingMins, out vecWorldMins); + MathLib.VectorAdd(in GetCollisionOrigin(), in SpecifiedSurroundingMaxs, out vecWorldMaxs); + break; + + case SurroundingBoundsType.UseGameCode: + GetOuter().ComputeWorldSpaceSurroundingBox(out vecWorldMins, out vecWorldMaxs); + Assert(vecWorldMins.X <= vecWorldMaxs.X); + Assert(vecWorldMins.Y <= vecWorldMaxs.Y); + Assert(vecWorldMins.Z <= vecWorldMaxs.Z); + return; + + default: + vecWorldMins = default; + vecWorldMaxs = default; + break; + } + } + public void MarkSurroundingBoundsDirty() { + GetOuter().AddEFlags(EFL.DirtySurroundingCollisionBounds); + MarkPartitionHandleDirty(); +#if CLIENT_DLL + g_ClientShadowMgr.MarkRenderToTextureShadowDirty(GetOuter().GetShadowHandle()); +#else + // GetOuter().NetworkProp().MarkPVSInformationDirty(); +#endif } public IHandleEntity? GetEntityHandle() => Outer; @@ -315,7 +454,17 @@ public void SetCollisionBounds(in Vector3 mins, in Vector3 maxs) { } public void WorldSpaceTriggerBounds(out Vector3 vecWorldMins, out Vector3 vecWorldMaxs) { - throw new NotImplementedException(); + WorldSpaceAABB(out vecWorldMins, out vecWorldMaxs); + if ((GetSolidFlags() & (int)Source.SolidFlags.UseTriggerBounds) == 0) + return; + + // Don't bloat below, we don't want to trigger it with our heads + vecWorldMins.X -= TriggerBloat; + vecWorldMins.Y -= TriggerBloat; + + vecWorldMaxs.X += TriggerBloat; + vecWorldMaxs.Y += TriggerBloat; + vecWorldMaxs.Z += (float)TriggerBloat * 0.5f; } public bool TestCollision(in Ray ray, Contents contentsMask, ref Trace tr) { @@ -334,26 +483,41 @@ public int GetCollisionModelIndex() { throw new NotImplementedException(); } - public ref readonly Vector3 GetCollisionOrigin() { - throw new NotImplementedException(); - } + public ref readonly Vector3 GetCollisionOrigin() => ref Outer.GetAbsOrigin(); + static readonly QAngle s_vec3_angle = new(0, 0, 0); public ref readonly QAngle GetCollisionAngles() { - throw new NotImplementedException(); + if (IsBoundsDefinedInEntitySpace()) + return ref Outer.GetAbsAngles(); + + return ref s_vec3_angle; } + Matrix3x4 CollisionToWorldTransformResult; public ref readonly Matrix3x4 CollisionToWorldTransform() { - throw new NotImplementedException(); - } + if (IsBoundsDefinedInEntitySpace()) + return ref Outer.EntityToWorldTransform(); - public SolidType GetSolid() { - throw new NotImplementedException(); + MathLib.SetIdentityMatrix(out CollisionToWorldTransformResult); + MathLib.MatrixSetColumn(in GetCollisionOrigin(), 3, ref CollisionToWorldTransformResult); + return ref CollisionToWorldTransformResult; } - public int GetSolidFlags() { - throw new NotImplementedException(); + public void CollisionAABBToWorldAABB(in Vector3 entityMins, in Vector3 entityMaxs, out Vector3 worldMins, out Vector3 worldMaxs) { + if (!IsBoundsDefinedInEntitySpace() || (GetCollisionAngles() == s_vec3_angle)) { + MathLib.VectorAdd(in entityMins, in GetCollisionOrigin(), out worldMins); + MathLib.VectorAdd(in entityMaxs, in GetCollisionOrigin(), out worldMaxs); + } + else + MathLib.TransformAABB(in CollisionToWorldTransform(), in entityMins, in entityMaxs, out worldMins, out worldMaxs); } + public void WorldSpaceAABB(out Vector3 worldMins, out Vector3 worldMaxs) => CollisionAABBToWorldAABB(in Mins, in Maxs, out worldMins, out worldMaxs); + + public SolidType GetSolid() => (SolidType)SolidType; + + public int GetSolidFlags() => SolidFlags; + public IClientUnknown? GetIClientUnknown() { throw new NotImplementedException(); } @@ -363,7 +527,17 @@ public int GetCollisionGroup() { } public void WorldSpaceSurroundingBounds(out Vector3 vecMins, out Vector3 vecMaxs) { - throw new NotImplementedException(); + ref readonly Vector3 absOrigin = ref GetCollisionOrigin(); + if (GetOuter().IsEFlagSet(EFL.DirtySurroundingCollisionBounds)) { + GetOuter().RemoveEFlags(EFL.DirtySurroundingCollisionBounds); + ComputeSurroundingBox(out vecMins, out vecMaxs); + MathLib.VectorSubtract(in vecMins, in absOrigin, out SurroundingMins); + MathLib.VectorSubtract(in vecMaxs, in absOrigin, out SurroundingMaxs); + } + else { + MathLib.VectorAdd(in SurroundingMins, in absOrigin, out vecMins); + MathLib.VectorAdd(in SurroundingMaxs, in absOrigin, out vecMaxs); + } } public bool ShouldTouchTrigger(int triggerSolidFlags) { @@ -400,7 +574,18 @@ public void DestroyPartitionHandle() { } public ushort GetPartitionHandle() => Partition; public void MarkPartitionHandleDirty() { + if (Outer.EntIndex() == 0) + return; + if (!Outer.IsEFlagSet(EFL.DirtySpatialPartition)) { + Outer.AddEFlags(EFL.DirtySpatialPartition); + DirtySpatialPartitionEntityList.s_DirtyKDTree.AddEntity(Outer); + } + +#if CLIENT_DLL + GetOuter().MarkRenderHandleDirty(); + g_ClientShadowMgr.AddToDirtyShadowList(GetOuter()); +#endif } public void UpdateServerPartitionMask() { #if !CLIENT_DLL @@ -422,9 +607,9 @@ public void UpdateServerPartitionMask() { // Make sure it's in the list of all entities bool bIsSolid = IsSolid() || IsSolidFlagSet(Source.SolidFlags.Trigger); - if (bIsSolid || Outer.IsEFlagSet(EFL.UsePartitionWhenNotSolid)) + if (bIsSolid || Outer.IsEFlagSet(EFL.UsePartitionWhenNotSolid)) partition.Insert(PartitionListMask.EngineNonStaticEdicts, handle); - + if (!bIsSolid) return; diff --git a/Source.Bitmap/ImageLoader.cs b/Source.Bitmap/ImageLoader.cs index be1ea1af..cfa36fa8 100644 --- a/Source.Bitmap/ImageLoader.cs +++ b/Source.Bitmap/ImageLoader.cs @@ -266,7 +266,7 @@ public static int GetNumMipMapLevels(int width, int height, int depth) { ImageFormat.BGR888 => GL_BGR, ImageFormat.RGB888_Bluescreen => GL_RGB, ImageFormat.BGR888_Bluescreen => GL_BGR, // TODO: what does bluescreen mean here - // ImageFormat.ARGB8888 => Gl46.ARGB, + ImageFormat.ARGB8888 => GL_BGRA, ImageFormat.BGRA8888 => GL_BGRA, ImageFormat.BGRX8888 => GL_BGRA, ImageFormat.RGBA16161616 => GL_RGBA, @@ -289,7 +289,7 @@ public static int GetNumMipMapLevels(int width, int height, int depth) { ImageFormat.A8 => GL_RGBA8, ImageFormat.RGB888_Bluescreen => GL_RGB8, ImageFormat.BGR888_Bluescreen => GL_RGB8, // TODO: what does bluescreen mean here - // ImageFormat.ARGB8888 => Gl46.ARGB, + ImageFormat.ARGB8888 => GL_RGBA8, ImageFormat.BGRA8888 => GL_RGBA8, ImageFormat.BGRX8888 => GL_RGBA8, // ImageFormat.BGRX5551 => GL_RGBA8, diff --git a/Source.Common/BaseTypes.cs b/Source.Common/BaseTypes.cs index 6a75e2c5..522c04d5 100644 --- a/Source.Common/BaseTypes.cs +++ b/Source.Common/BaseTypes.cs @@ -44,4 +44,8 @@ public static float Random(in Interval interval) { public static class BaseTypesGlobals { public static int PAD_NUMBER(int number, int boundary) => (number + (boundary - 1)) / boundary * boundary; + + public static float FloatMakeNegative(vec_t f) => -MathF.Abs(f); + public static float FloatMakePositive(vec_t f) => MathF.Abs(f); + public static float FloatNegate(vec_t f) => -f; } diff --git a/Source.Common/CollisionUtils.cs b/Source.Common/CollisionUtils.cs index 4bc2f0a0..4e35b218 100644 --- a/Source.Common/CollisionUtils.cs +++ b/Source.Common/CollisionUtils.cs @@ -1,3 +1,4 @@ +using Source.Common.MaterialSystem; using Source.Common.Mathematics; using System.Numerics; @@ -22,6 +23,31 @@ public static bool IsSphereIntersectingCone(in Vector3 sphereCenter, float spher return false; } + public static bool IsBoxIntersectingRay(in Vector3 boxMin, in Vector3 boxMax, in Ray ray, float tolerance = 0.0f) { + if (!ray.IsSwept) { + Vector3 rayMins = ray.Start - ray.Extents; + Vector3 rayMaxs = ray.Start + ray.Extents; + rayMins += new Vector3(tolerance); + rayMaxs += new Vector3(tolerance); + + return IsBoxIntersectingBox(boxMin, boxMax, rayMins, rayMaxs); + } + + Vector3 expandedBoxMin = boxMin - ray.Extents; + Vector3 expandedBoxMax = boxMax + ray.Extents; + + return IsBoxIntersectingRay(expandedBoxMin, expandedBoxMax, ray.Start, ray.Delta, Vector3.One / ray.Delta, tolerance); + } + + public static float IntersectRayWithPlane(in Vector3 org, in Vector3 dir, in Vector3 normal, float dist) { + float denom = MathLib.DotProduct(dir, normal); + if (denom == 0.0f) + return 0.0f; + + denom = 1.0f / denom; + return (dist - MathLib.DotProduct(org, normal)) * denom; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float IntersectRayWithTriangle(in Ray ray, in Vector3 v1, in Vector3 v2, in Vector3 v3, bool oneSided) { Vector3 edge1 = v2 - v1; @@ -142,6 +168,47 @@ public static bool IsBoxIntersectingBoxExtents(in Vector3 boxCenter1, in Vector3 return delta.X <= size.X && delta.Y <= size.Y && delta.Z <= size.Z; } + public static bool IsSphereIntersectingSphere(in Vector3 center1, float radius1, in Vector3 center2, float radius2) { + MathLib.VectorSubtract(center2, center1, out Vector3 delta); + float distSq = delta.LengthSquared(); + float radiusSum = radius1 + radius2; + return distSq <= (radiusSum * radiusSum); + } + + public static bool IsBoxIntersectingSphere(in Vector3 boxMin, in Vector3 boxMax, in Vector3 center, float radius) { + float dmin = 0.0f; + float delta; + + if (center[0] < boxMin[0]) { + delta = center[0] - boxMin[0]; + dmin += delta * delta; + } + else if (center[0] > boxMax[0]) { + delta = boxMax[0] - center[0]; + dmin += delta * delta; + } + + if (center[1] < boxMin[1]) { + delta = center[1] - boxMin[1]; + dmin += delta * delta; + } + else if (center[1] > boxMax[1]) { + delta = boxMax[1] - center[1]; + dmin += delta * delta; + } + + if (center[2] < boxMin[2]) { + delta = center[2] - boxMin[2]; + dmin += delta * delta; + } + else if (center[2] > boxMax[2]) { + delta = boxMax[2] - center[2]; + dmin += delta * delta; + } + + return dmin < radius * radius; + } + public static bool IsBoxIntersectingSphereExtents(in Vector3 boxCenter, in Vector3 boxHalfDiag, in Vector3 center, float radius) { float dmin = 0.0f; float delta, diff; @@ -269,4 +336,215 @@ public static bool IsBoxIntersectingTriangle(in Vector3 boxCenter, in Vector3 bo return true; } + + static void ComputeCenterMatrix(in Vector3 origin, in QAngle angles, in Vector3 mins, in Vector3 maxs, out Matrix3x4 matrix) { + MathLib.VectorAdd(mins, maxs, out Vector3 centroid); + centroid *= 0.5f; + MathLib.AngleMatrix(angles, out matrix); + + MathLib.VectorRotate(centroid, matrix, out Vector3 worldCentroid); + worldCentroid += origin; + MathLib.MatrixSetColumn(worldCentroid, 3, ref matrix); + } + + static void ComputeCenterIMatrix(in Vector3 origin, in QAngle angles, in Vector3 mins, in Vector3 maxs, out Matrix3x4 matrix) { + MathLib.VectorAdd(mins, maxs, out Vector3 centroid); + centroid *= -0.5f; + MathLib.AngleIMatrix(angles, out matrix); + + MathLib.VectorRotate(origin, matrix, out Vector3 localOrigin); + centroid -= localOrigin; + MathLib.MatrixSetColumn(centroid, 3, ref matrix); + } + + static void ComputeAbsMatrix(in Matrix3x4 input, out Matrix3x4 output) { + output = default; + output[0, 0] = MathF.Abs(input[0, 0]); + output[0, 1] = MathF.Abs(input[0, 1]); + output[0, 2] = MathF.Abs(input[0, 2]); + output[1, 0] = MathF.Abs(input[1, 0]); + output[1, 1] = MathF.Abs(input[1, 1]); + output[1, 2] = MathF.Abs(input[1, 2]); + output[2, 0] = MathF.Abs(input[2, 0]); + output[2, 1] = MathF.Abs(input[2, 1]); + output[2, 2] = MathF.Abs(input[2, 2]); + } + + static bool ComputeSeparatingPlane(in Matrix3x4 worldToBox1, in Matrix3x4 box2ToWorld, in Vector3 box1Size, in Vector3 box2Size, float tolerance, out CollisionPlane plane) { + plane = default; + + MathLib.ConcatTransforms(worldToBox1, box2ToWorld, out Matrix3x4 box2ToBox1); + MathLib.MatrixGetColumn(box2ToBox1, 3, out Vector3 box2Origin); + + ComputeAbsMatrix(box2ToBox1, out Matrix3x4 absBox2ToBox1); + + Vector3 tmp; + float boxProjectionSum; + float originProjection; + + boxProjectionSum = box1Size.X + MathLib.MatrixRowDotProduct(absBox2ToBox1, 0, box2Size); + originProjection = FloatMakePositive(box2Origin.X) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + plane.Normal = new(worldToBox1[0, 0], worldToBox1[0, 1], worldToBox1[0, 2]); + return true; + } + + boxProjectionSum = box1Size.Y + MathLib.MatrixRowDotProduct(absBox2ToBox1, 1, box2Size); + originProjection = FloatMakePositive(box2Origin.Y) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + plane.Normal = new(worldToBox1[1, 0], worldToBox1[1, 1], worldToBox1[1, 2]); + return true; + } + + boxProjectionSum = box1Size.Z + MathLib.MatrixRowDotProduct(absBox2ToBox1, 2, box2Size); + originProjection = FloatMakePositive(box2Origin.Z) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + plane.Normal = new(worldToBox1[2, 0], worldToBox1[2, 1], worldToBox1[2, 2]); + return true; + } + + boxProjectionSum = box2Size.X + MathLib.MatrixColumnDotProduct(absBox2ToBox1, 0, box1Size); + originProjection = FloatMakePositive(MathLib.MatrixColumnDotProduct(box2ToBox1, 0, box2Origin)) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 0, out plane.Normal); + return true; + } + + boxProjectionSum = box2Size.Y + MathLib.MatrixColumnDotProduct(absBox2ToBox1, 1, box1Size); + originProjection = FloatMakePositive(MathLib.MatrixColumnDotProduct(box2ToBox1, 1, box2Origin)) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 1, out plane.Normal); + return true; + } + + boxProjectionSum = box2Size.Z + MathLib.MatrixColumnDotProduct(absBox2ToBox1, 2, box1Size); + originProjection = FloatMakePositive(MathLib.MatrixColumnDotProduct(box2ToBox1, 2, box2Origin)) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 2, out plane.Normal); + return true; + } + + if (absBox2ToBox1[0, 0] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.Y * absBox2ToBox1[2, 0] + box1Size.Z * absBox2ToBox1[1, 0] + + box2Size.Y * absBox2ToBox1[0, 2] + box2Size.Z * absBox2ToBox1[0, 1]; + originProjection = FloatMakePositive(-box2Origin.Y * box2ToBox1[2, 0] + box2Origin.Z * box2ToBox1[1, 0]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 0, out tmp); + MathLib.CrossProduct(new(worldToBox1[0, 0], worldToBox1[0, 1], worldToBox1[0, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[0, 1] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.Y * absBox2ToBox1[2, 1] + box1Size.Z * absBox2ToBox1[1, 1] + + box2Size.X * absBox2ToBox1[0, 2] + box2Size.Z * absBox2ToBox1[0, 0]; + originProjection = FloatMakePositive(-box2Origin.Y * box2ToBox1[2, 1] + box2Origin.Z * box2ToBox1[1, 1]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 1, out tmp); + MathLib.CrossProduct(new(worldToBox1[0, 0], worldToBox1[0, 1], worldToBox1[0, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[0, 2] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.Y * absBox2ToBox1[2, 2] + box1Size.Z * absBox2ToBox1[1, 2] + + box2Size.X * absBox2ToBox1[0, 1] + box2Size.Y * absBox2ToBox1[0, 0]; + originProjection = FloatMakePositive(-box2Origin.Y * box2ToBox1[2, 2] + box2Origin.Z * box2ToBox1[1, 2]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 2, out tmp); + MathLib.CrossProduct(new(worldToBox1[0, 0], worldToBox1[0, 1], worldToBox1[0, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[1, 0] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[2, 0] + box1Size.Z * absBox2ToBox1[0, 0] + + box2Size.Y * absBox2ToBox1[1, 2] + box2Size.Z * absBox2ToBox1[1, 1]; + originProjection = FloatMakePositive(box2Origin.X * box2ToBox1[2, 0] - box2Origin.Z * box2ToBox1[0, 0]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 0, out tmp); + MathLib.CrossProduct(new(worldToBox1[1, 0], worldToBox1[1, 1], worldToBox1[1, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[1, 1] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[2, 1] + box1Size.Z * absBox2ToBox1[0, 1] + + box2Size.X * absBox2ToBox1[1, 2] + box2Size.Z * absBox2ToBox1[1, 0]; + originProjection = FloatMakePositive(box2Origin.X * box2ToBox1[2, 1] - box2Origin.Z * box2ToBox1[0, 1]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 1, out tmp); + MathLib.CrossProduct(new(worldToBox1[1, 0], worldToBox1[1, 1], worldToBox1[1, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[1, 2] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[2, 2] + box1Size.Z * absBox2ToBox1[0, 2] + + box2Size.X * absBox2ToBox1[1, 1] + box2Size.Y * absBox2ToBox1[1, 0]; + originProjection = FloatMakePositive(box2Origin.X * box2ToBox1[2, 2] - box2Origin.Z * box2ToBox1[0, 2]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 2, out tmp); + MathLib.CrossProduct(new(worldToBox1[1, 0], worldToBox1[1, 1], worldToBox1[1, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[2, 0] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[1, 0] + box1Size.Y * absBox2ToBox1[0, 0] + + box2Size.Y * absBox2ToBox1[2, 2] + box2Size.Z * absBox2ToBox1[2, 1]; + originProjection = FloatMakePositive(-box2Origin.X * box2ToBox1[1, 0] + box2Origin.Y * box2ToBox1[0, 0]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 0, out tmp); + MathLib.CrossProduct(new(worldToBox1[2, 0], worldToBox1[2, 1], worldToBox1[2, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[2, 1] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[1, 1] + box1Size.Y * absBox2ToBox1[0, 1] + + box2Size.X * absBox2ToBox1[2, 2] + box2Size.Z * absBox2ToBox1[2, 0]; + originProjection = FloatMakePositive(-box2Origin.X * box2ToBox1[1, 1] + box2Origin.Y * box2ToBox1[0, 1]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 1, out tmp); + MathLib.CrossProduct(new(worldToBox1[2, 0], worldToBox1[2, 1], worldToBox1[2, 2]), tmp, out plane.Normal); + return true; + } + } + + if (absBox2ToBox1[2, 2] < 1.0f - 1e-3f) { + boxProjectionSum = + box1Size.X * absBox2ToBox1[1, 2] + box1Size.Y * absBox2ToBox1[0, 2] + + box2Size.X * absBox2ToBox1[2, 1] + box2Size.Y * absBox2ToBox1[2, 0]; + originProjection = FloatMakePositive(-box2Origin.X * box2ToBox1[1, 2] + box2Origin.Y * box2ToBox1[0, 2]) + tolerance; + if (originProjection.FloatBits() > boxProjectionSum.FloatBits()) { + MathLib.MatrixGetColumn(box2ToWorld, 2, out tmp); + MathLib.CrossProduct(new(worldToBox1[2, 0], worldToBox1[2, 1], worldToBox1[2, 2]), tmp, out plane.Normal); + return true; + } + } + return false; + } + + public static bool ComputeSeparatingPlane(in Vector3 org1, in QAngle angles1, in Vector3 min1, in Vector3 max1, + in Vector3 org2, in QAngle angles2, in Vector3 min2, in Vector3 max2, + float tolerance, out CollisionPlane plane) { + ComputeCenterIMatrix(org1, angles1, min1, max1, out Matrix3x4 worldToBox1); + ComputeCenterMatrix(org2, angles2, min2, max2, out Matrix3x4 box2ToWorld); + + MathLib.VectorSubtract(max1, min1, out Vector3 box1Size); + MathLib.VectorSubtract(max2, min2, out Vector3 box2Size); + box1Size *= 0.5f; + box2Size *= 0.5f; + + return ComputeSeparatingPlane(worldToBox1, box2ToWorld, box1Size, box2Size, tolerance, out plane); + } } diff --git a/Source.Common/Commands/Cvar.cs b/Source.Common/Commands/Cvar.cs index 8cb6e1ab..e6501667 100644 --- a/Source.Common/Commands/Cvar.cs +++ b/Source.Common/Commands/Cvar.cs @@ -194,29 +194,29 @@ public void RegisterConCommand(ConCommandBase variable) { if (!childVar.defaultValue.Equals(parentVar.defaultValue, StringComparison.OrdinalIgnoreCase)) Dbg.Warning($"Parent and child ConVars with different default values! " + $"{childVar.defaultValue} child, {childVar.defaultValue} parent (parent wins)\n"); + } - childVar.parent = parentVar.parent; - parentVar.Flags |= childVar.Flags & (FCvar.AccessibleFromThreads); - if (childVar.HasChangeCallback) { - if (!parentVar.HasChangeCallback) - parentVar.SyncChangeTo(childVar); - } + childVar.parent = parentVar.parent; + parentVar.Flags |= childVar.Flags & (FCvar.AccessibleFromThreads); + if (childVar.HasChangeCallback) { + if (!parentVar.HasChangeCallback) + parentVar.SyncChangeTo(childVar); + } - if (!string.IsNullOrEmpty(childVar.HelpString)) { - if (!string.IsNullOrEmpty(parentVar.HelpString)) { - if (!parentVar.HelpString.Equals(childVar.HelpString, StringComparison.OrdinalIgnoreCase)) - Dbg.Warning($"Convar {variable.GetName()} has multiple help strings (parent wins)\n"); - } - else { - parentVar.HelpString = childVar.HelpString; - } + if (!string.IsNullOrEmpty(childVar.HelpString)) { + if (!string.IsNullOrEmpty(parentVar.HelpString)) { + if (!parentVar.HelpString.Equals(childVar.HelpString, StringComparison.OrdinalIgnoreCase)) + Dbg.Warning($"Convar {variable.GetName()} has multiple help strings (parent wins)\n"); + } + else { + parentVar.HelpString = childVar.HelpString; } - - if ((childVar.Flags & FCvar.Cheat) != (parentVar.Flags & FCvar.Cheat)) - Dbg.Warning($"Convar {variable.GetName()} has conflicting Cheat flags (parent wins)\n"); - if ((childVar.Flags & FCvar.Replicated) != (parentVar.Flags & FCvar.Replicated)) - Dbg.Warning($"Convar {variable.GetName()} has conflicting Replicated flags (parent wins)\n"); } + + if ((childVar.Flags & FCvar.Cheat) != (parentVar.Flags & FCvar.Cheat)) + Dbg.Warning($"Convar {variable.GetName()} has conflicting Cheat flags (parent wins)\n"); + if ((childVar.Flags & FCvar.Replicated) != (parentVar.Flags & FCvar.Replicated)) + Dbg.Warning($"Convar {variable.GetName()} has conflicting Replicated flags (parent wins)\n"); } } diff --git a/Source.Common/Dbg.cs b/Source.Common/Dbg.cs index 8f6be467..dbb607ef 100644 --- a/Source.Common/Dbg.cs +++ b/Source.Common/Dbg.cs @@ -418,6 +418,16 @@ public static void AssertEquals(T? i1, T? i2, params object?[] args ) => _AssertMsg(i1 == null ? i2 == null : i1.Equals(i2), "Expected {0} but got {1}!", args, ____fileP ?? "", ____lineNum, false); + [Conditional("DBGFLAG_ASSERT")] +#if DBGFLAG_HIDE_ASSERTS_FROM_DEBUGGING_STACK + [DebuggerHidden] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AssertFloatEquals(float exp, float expectedValue, float tol, + [CallerFilePath] string? ____fileP = null, + [CallerLineNumber] int ____lineNum = -1 + ) => _AssertMsg(MathF.Abs(exp - expectedValue) <= tol, $"Expected {expectedValue} but got {exp}!", ____fileP ?? "", ____lineNum, false); + public static void SpewActivate(string groupName, int level) { Assert(groupName != null); diff --git a/Source.Common/Engine/IModelRender.cs b/Source.Common/Engine/IModelRender.cs index 8f3b48bb..8e390c71 100644 --- a/Source.Common/Engine/IModelRender.cs +++ b/Source.Common/Engine/IModelRender.cs @@ -61,4 +61,6 @@ public interface IModelRender void DrawModelExecute(ref DrawModelState state, ref ModelRenderInfo info, Span boneToWorldArray); bool DrawModelSetup(ref ModelRenderInfo info, ref DrawModelState state, Span customBoneToWorld, out Span boneToWorldArray); ref Matrix4x4 SetupModelState(IClientRenderable renderable); + bool DrawModelShadowSetup(IClientRenderable renderable, int body, int skin, ref Source.Common.DrawModelInfo info, Span customBoneToWorld, out Span boneToWorldOut); + void DrawModelShadow(IClientRenderable renderable, in Source.Common.DrawModelInfo info, Span boneToWorld); } diff --git a/Source.Common/Engine/IShadowMgr.cs b/Source.Common/Engine/IShadowMgr.cs index d485a2ff..cd4c80d3 100644 --- a/Source.Common/Engine/IShadowMgr.cs +++ b/Source.Common/Engine/IShadowMgr.cs @@ -34,7 +34,7 @@ public enum ShadowCreateFlags public struct ShadowInfo_t { // Transforms from world space into texture space of the shadow - public Matrix4x4 m_WorldToShadow; + public Matrix4x4 WorldToShadow; // The shadow should no longer be drawn once it's further than MaxDist // along z in shadow texture coordinates. @@ -81,7 +81,7 @@ public interface IShadowMgr // Gets at information about a particular shadow ref readonly ShadowInfo_t GetInfo(ShadowHandle_t handle); - ref readonly Frustum GetFlashlightFrustum(ShadowHandle_t handle); + Frustum_t GetFlashlightFrustum(ShadowHandle_t handle); // Methods related to shadows on brush models void AddShadowToBrushModel(ShadowHandle_t handle, Model? model, in Vector3 origin, in QAngle angles); diff --git a/Source.Common/Engine/Model.cs b/Source.Common/Engine/Model.cs index b3eacdca..53819b90 100644 --- a/Source.Common/Engine/Model.cs +++ b/Source.Common/Engine/Model.cs @@ -44,7 +44,7 @@ public class WorldBrushData public int NumLeafs; public MLeafAmbientIndex[]? LeafAmbient; public MLeafAmbientLighting[]? AmbientSamples; - public BSPDLeafWaterData[]? LeafWaterData; + public BSPMLeafWaterData[]? LeafWaterData; public BSPDertex[]? Vertexes; public BSPDOccluderData[]? Occluders; public BSPDOccluderPolyData[]? OccluderPolys; diff --git a/Source.Common/Exts.cs b/Source.Common/Exts.cs index 88a67cde..f68f33c2 100644 --- a/Source.Common/Exts.cs +++ b/Source.Common/Exts.cs @@ -169,6 +169,25 @@ public struct MaxDispVertsBitVec } +/// +/// An inline bit-vector array able to hold the 85 nodes of a 17x17 displacement. +/// +[InlineArray((85 + 31) / 32 * 4)] +public struct DispNodeIntersectBitVec +{ + public byte bytes; + public uint GetDWord(int i) => BitVecBase.GetDWord(this, i); + public void SetDWord(int i, uint val) => BitVecBase.SetDWord(this, i, val); + public int GetNumDWords() => (85 + 31) / 32; + public int Get(int bit) => BitVecBase.IsBitSet(this, bit) ? 1 : 0; + public bool IsBitSet(int bit) => BitVecBase.IsBitSet(this, bit); + public void Set(int bit) => BitVecBase.Set(this, bit); + public void Clear(int bit) => BitVecBase.Clear(this, bit); + public void Set(int bit, bool newVal) => BitVecBase.Set(this, bit, newVal); + public int FindNextSetBit(int startBit) => BitVecBase.FindNextSetBit(this, startBit); + public void ClearAll() => BitVecBase.ClearAll(this); +} + /// /// An inline bit-vector array of MAX_EDICTS >> 3 bytes. /// diff --git a/Source.Common/Formats/BSP/BSPFile.cs b/Source.Common/Formats/BSP/BSPFile.cs index 53cbcef7..2a694930 100644 --- a/Source.Common/Formats/BSP/BSPFile.cs +++ b/Source.Common/Formats/BSP/BSPFile.cs @@ -458,7 +458,7 @@ public class BSPMNode public ushort NumSurfaces; } -public class BSPMLeafWaterData +public struct BSPMLeafWaterData { public float SurfaceZ; public float MinZ; @@ -673,6 +673,15 @@ public ref LightShadowZBufferSample GetSample(in Vector3 vecNormalizedDirection) public interface IDispInfo { ref BSPMSurface2 GetParent(); + + void GetBoundingBox(out Vector3 bbMin, out Vector3 bbMax); + + bool GetTag(); + void SetTag(); + + DispShadowHandle AddShadowDecal(ShadowHandle_t shadowHandle); + void RemoveShadowDecal(DispShadowHandle handle); + bool ComputeShadowFragments(DispShadowHandle h, out int vertexCount, out int indexCount); } public struct BSPMSurface2 diff --git a/Source.Common/Formats/BSP/GameBSPFile.cs b/Source.Common/Formats/BSP/GameBSPFile.cs index 3e97c85e..b117e837 100644 --- a/Source.Common/Formats/BSP/GameBSPFile.cs +++ b/Source.Common/Formats/BSP/GameBSPFile.cs @@ -144,6 +144,135 @@ public struct StaticPropLumpV6 public ushort MaxDXLevel; } +public struct StaticPropLumpV7 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public byte Flags; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public ushort MinDXLevel; + public ushort MaxDXLevel; + public Color DiffuseModulation; +} + +public struct StaticPropLumpV8 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public byte Flags; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public byte MinCPULevel; + public byte MaxCPULevel; + public byte MinGPULevel; + public byte MaxGPULevel; + public Color DiffuseModulation; +} + +public struct StaticPropLumpV9 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public byte Flags; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public byte MinCPULevel; + public byte MaxCPULevel; + public byte MinGPULevel; + public byte MaxGPULevel; + public Color DiffuseModulation; + public bool DisableX360; +} + +public struct StaticPropLumpV10 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public ushort MinDXLevel; + public ushort MaxDXLevel; + public uint Flags; + public ushort LightmapResolutionX; + public ushort LightmapResolutionY; +} + +public struct StaticPropLumpV10_21 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public byte Flags; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public byte MinCPULevel; + public byte MaxCPULevel; + public byte MinGPULevel; + public byte MaxGPULevel; + public Color DiffuseModulation; + public bool DisableX360; + public int FlagsEx; +} + +public struct StaticPropLumpV11 +{ + public Vector3 Origin; + public QAngle Angles; + public ushort PropType; + public ushort FirstLeaf; + public ushort LeafCount; + public byte Solid; + public byte Flags; + public int Skin; + public float FadeMinDist; + public float FadeMaxDist; + public Vector3 LightingOrigin; + public float ForcedFadeScale; + public byte MinCPULevel; + public byte MaxCPULevel; + public byte MinGPULevel; + public byte MaxGPULevel; + public Color DiffuseModulation; + public bool DisableX360; + public int FlagsEx; + public float PropScale; +} + public struct StaticPropLump { public Vector3 Origin; @@ -163,6 +292,10 @@ public struct StaticPropLump public ushort LightmapResolutionX; public ushort LightmapResolutionY; + public Color DiffuseModulation; + public bool DisableX360; + public int FlagsEx; + public static implicit operator StaticPropLump(StaticPropLumpV4 rhs) { StaticPropLump lump = default; lump.Origin = rhs.Origin; @@ -182,6 +315,7 @@ public static implicit operator StaticPropLump(StaticPropLumpV4 rhs) { lump.MaxDXLevel = 0; lump.LightmapResolutionX = 0; lump.LightmapResolutionY = 0; + lump.DiffuseModulation = default; lump.Flags |= (uint)StaticPropFlags.NoPerTexelLighting; return lump; @@ -199,6 +333,30 @@ public static implicit operator StaticPropLump(StaticPropLumpV6 rhs) { lump.MaxDXLevel = rhs.MaxDXLevel; return lump; } + + public static implicit operator StaticPropLump(StaticPropLumpV9 rhs) { + StaticPropLump lump = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref rhs, 1))[0]; + lump.DiffuseModulation = rhs.DiffuseModulation; + return lump; + } + + public static implicit operator StaticPropLump(StaticPropLumpV10 rhs) { + StaticPropLump lump = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref rhs, 1))[0]; + lump.Flags = rhs.Flags; + lump.LightmapResolutionX = rhs.LightmapResolutionX; + lump.LightmapResolutionY = rhs.LightmapResolutionY; + return lump; + } + + public static implicit operator StaticPropLump(StaticPropLumpV10_21 rhs) { + StaticPropLump lump = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref rhs, 1))[0]; + lump.FlagsEx = rhs.FlagsEx; + return lump; + } + + public static implicit operator StaticPropLump(StaticPropLumpV11 rhs) { + return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref rhs, 1))[0]; + } } public struct StaticPropLeafLump diff --git a/Source.Common/IClientEntityList.cs b/Source.Common/IClientEntityList.cs index a284a7b4..77651fd0 100644 --- a/Source.Common/IClientEntityList.cs +++ b/Source.Common/IClientEntityList.cs @@ -6,6 +6,7 @@ public interface IClientEntityList { IClientUnknown? GetClientUnknownFromHandle(in BaseHandle ent); IClientEntity? GetClientEntity(int entNum); IClientEntity? GetClientEntityFromHandle(in BaseHandle ent); + IClientRenderable? GetClientRenderableFromHandle(in BaseHandle ent); IClientThinkable? GetClientThinkableFromHandle(in BaseHandle ent); int NumberOfEntities(bool includeNonNetworkable); int GetHighestEntityIndex(); diff --git a/Source.Common/IClientRenderable.cs b/Source.Common/IClientRenderable.cs index 8c41e32a..858b3b7b 100644 --- a/Source.Common/IClientRenderable.cs +++ b/Source.Common/IClientRenderable.cs @@ -8,6 +8,11 @@ namespace Source.Common; +public static class ClientRenderableGlobals +{ + public const ClientShadowHandle_t CLIENTSHADOW_INVALID_HANDLE = unchecked((ClientShadowHandle_t)~0); +} + public enum ShadowType { None = 0, @@ -85,8 +90,8 @@ public interface IClientRenderable bool ShouldReceiveProjectedTextures(ShadowFlags flags); // These methods return true if we want a per-renderable shadow cast direction + distance - bool GetShadowCastDistance(out float dist, ShadowType shadowType); - bool GetShadowCastDirection(out Vector3 direction, ShadowType shadowType); + bool GetShadowCastDistance(ref float dist, ShadowType shadowType); + bool GetShadowCastDirection(ref Vector3 direction, ShadowType shadowType); // Other methods related to shadow rendering bool IsShadowDirty(); @@ -214,11 +219,11 @@ public bool ShouldReceiveProjectedTextures(ShadowFlags flags) { throw new NotImplementedException(); } - public bool GetShadowCastDistance(out float dist, ShadowType shadowType) { + public bool GetShadowCastDistance(ref float dist, ShadowType shadowType) { throw new NotImplementedException(); } - public bool GetShadowCastDirection(out Vector3 direction, ShadowType shadowType) { + public bool GetShadowCastDirection(ref Vector3 direction, ShadowType shadowType) { throw new NotImplementedException(); } diff --git a/Source.Common/IRenderView.cs b/Source.Common/IRenderView.cs index 7f083ad6..0f20c45d 100644 --- a/Source.Common/IRenderView.cs +++ b/Source.Common/IRenderView.cs @@ -68,6 +68,7 @@ public interface IRenderView public const uint VIEW_SETUP_VIS_EX_RETURN_FLAGS_USES_RADIAL_VIS = 1; void DrawBrushModel(IClientEntity baseentity, Model model, in Vector3 origin, in QAngle angles); void DrawIdentityBrushModel(IWorldRenderList list, Model model); + void DrawBrushModelShadow(IClientRenderable renderable); void VGui_Paint(PaintMode mode); void Push2DView(ViewSetup view, ClearFlags flags, ITexture? renderTarget, Frustum frustumPlanes); void PopView(Frustum frustumPlanes); diff --git a/Source.Common/IStudioRender.cs b/Source.Common/IStudioRender.cs index ea465770..6a064ee3 100644 --- a/Source.Common/IStudioRender.cs +++ b/Source.Common/IStudioRender.cs @@ -74,6 +74,14 @@ public struct StudioRenderConfig { public bool StatsMode; } +public enum OverrideType +{ + Normal, + BuildShadows, + DepthWrite, + SSAODepthWrite +} + public struct DrawModelResults { public int ActualTriCount; public int TextureMemoryBytes; @@ -97,6 +105,9 @@ public interface IStudioRender { void SetViewState(in Vector3 currentViewOrigin, in Vector3 currentViewRight, in Vector3 currentViewUp, in Vector3 currentViewForward); void SetColorModulation(Vector3 r_colormod); void SetAlphaModulation(float r_blend); + void SetEyeViewTarget(StudioHeader? studioHdr, int bodyPart, in Vector3 viewtarget); + void ForcedMaterialOverride(IMaterial? newMaterial, OverrideType overrideType = OverrideType.Normal); + int ComputeModelLod(StudioHWData hardwareData, float unitSphereSize); int GetNumAmbientLightSamples(); ReadOnlySpan GetAmbientLightDirections(); diff --git a/Source.Common/MaterialSystem/IMaterialSystem.cs b/Source.Common/MaterialSystem/IMaterialSystem.cs index b5067167..a41363bf 100644 --- a/Source.Common/MaterialSystem/IMaterialSystem.cs +++ b/Source.Common/MaterialSystem/IMaterialSystem.cs @@ -187,11 +187,11 @@ public FlashlightState() { public int GetBottom() => Bottom; - bool Scissor; - int Left; - int Top; - int Right; - int Bottom; + public bool Scissor; + public int Left; + public int Top; + public int Right; + public int Bottom; } public enum CreateRenderTargetFlags @@ -269,7 +269,7 @@ public interface IMaterialSystem IMaterial CreateMaterial(ReadOnlySpan name, ReadOnlySpan textureGroupName, KeyValues keyValues); IMaterial CreateMaterial(ReadOnlySpan name, KeyValues keyValues); bool CanUseEditorMaterials(); - IMaterial FindMaterial(ReadOnlySpan filename, ReadOnlySpan textureGroup, bool complain = false, ReadOnlySpan complainPrefix = default); + IMaterial FindMaterial(ReadOnlySpan filename, ReadOnlySpan textureGroup, bool complain = true, ReadOnlySpan complainPrefix = default); IMaterial? FindProceduralMaterial(ReadOnlySpan materialName, ReadOnlySpan textureGroupName, KeyValues keyValues); void RestoreShaderObjects(IServiceProvider services, int changeFlags); ITexture CreateProceduralTexture(ReadOnlySpan textureName, ReadOnlySpan textureGroup, int wide, int tall, ImageFormat format, TextureFlags flags); @@ -293,6 +293,11 @@ public interface IMaterialSystem void EndUpdateLightmaps(); void SetMaterialProxyFactory(IMaterialProxyFactory? factory); IMaterialProxyFactory? GetMaterialProxyFactory(); + void AddRestoreFunc(Action func); + void RemoveRestoreFunc(Action func); + bool SupportsShadowDepthTextures(); + ImageFormat GetShadowDepthTextureFormat(); + ImageFormat GetNullTextureFormat(); } public interface IMatRenderContext @@ -310,6 +315,9 @@ public interface IMatRenderContext void ClearColor4ub(byte r, byte g, byte b, byte a); void DepthRange(double near, double far); + MaterialHeightClipMode GetHeightClipMode(); + void SetHeightClipMode(MaterialHeightClipMode heightClipMode); + void MatrixMode(MaterialMatrixMode mode); void PushMatrix(); void PopMatrix(); @@ -355,6 +363,20 @@ public interface IMatRenderContext void SetLight(int lightNum, in Source.Common.Mathematics.LightDesc desc); void DisableAllLocalLights(); int GetMaxLights(); + void SetFlashlightMode(bool enable); + bool GetFlashlightMode(); + void SetFlashlightState(in FlashlightState state, in Matrix4x4 worldToTexture); + void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture); + void GetMatrix(MaterialMatrixMode matrixMode, out Matrix4x4 matrix); + void SetStencilEnable(bool onoff); + void SetStencilFailOperation(StencilOperation op); + void SetStencilZFailOperation(StencilOperation op); + void SetStencilPassOperation(StencilOperation op); + void SetStencilCompareFunction(StencilComparisonFunction cmpfn); + void SetStencilReferenceValue(int reference); + void SetStencilTestMask(uint msk); + void SetStencilWriteMask(uint msk); + void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor); } public readonly struct MatRenderContextPtr : IDisposable, IMatRenderContext @@ -384,6 +406,8 @@ public MatRenderContextPtr(IMaterialSystem from) { public void ClearColor3ub(byte r, byte g, byte b) => ctx.ClearColor3ub(r, g, b); public void ClearColor4ub(byte r, byte g, byte b, byte a) => ctx.ClearColor4ub(r, g, b, a); public void DepthRange(double near, double far) => ctx.DepthRange(near, far); + public MaterialHeightClipMode GetHeightClipMode() => ctx.GetHeightClipMode(); + public void SetHeightClipMode(MaterialHeightClipMode heightClipMode) => ctx.SetHeightClipMode(heightClipMode); public void MatrixMode(MaterialMatrixMode mode) => ctx.MatrixMode(mode); public void PushMatrix() => ctx.PushMatrix(); public void LoadIdentity() => ctx.LoadIdentity(); @@ -451,4 +475,18 @@ public void GetWorldSpaceCameraPosition(out Vector3 vecCameraPos) { public void SetLight(int lightNum, in Mathematics.LightDesc desc) => ctx.SetLight(lightNum, desc); public void DisableAllLocalLights() => ctx.DisableAllLocalLights(); public int GetMaxLights() => ctx.GetMaxLights(); + public void SetFlashlightMode(bool enable) => ctx.SetFlashlightMode(enable); + public bool GetFlashlightMode() => ctx.GetFlashlightMode(); + public void SetFlashlightState(in FlashlightState state, in Matrix4x4 worldToTexture) => ctx.SetFlashlightState(state, worldToTexture); + public void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture) => ctx.SetFlashlightStateEx(state, worldToTexture, flashlightDepthTexture); + public void GetMatrix(MaterialMatrixMode matrixMode, out Matrix4x4 matrix) => ctx.GetMatrix(matrixMode, out matrix); + public void SetStencilEnable(bool onoff) => ctx.SetStencilEnable(onoff); + public void SetStencilFailOperation(StencilOperation op) => ctx.SetStencilFailOperation(op); + public void SetStencilZFailOperation(StencilOperation op) => ctx.SetStencilZFailOperation(op); + public void SetStencilPassOperation(StencilOperation op) => ctx.SetStencilPassOperation(op); + public void SetStencilCompareFunction(StencilComparisonFunction cmpfn) => ctx.SetStencilCompareFunction(cmpfn); + public void SetStencilReferenceValue(int reference) => ctx.SetStencilReferenceValue(reference); + public void SetStencilTestMask(uint msk) => ctx.SetStencilTestMask(msk); + public void SetStencilWriteMask(uint msk) => ctx.SetStencilWriteMask(msk); + public void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor) => ctx.SetScissorRect(left, top, right, bottom, enableScissor); } diff --git a/Source.Common/MaterialSystem/TextureReference.cs b/Source.Common/MaterialSystem/TextureReference.cs index 0aa24f25..d0dd6e83 100644 --- a/Source.Common/MaterialSystem/TextureReference.cs +++ b/Source.Common/MaterialSystem/TextureReference.cs @@ -8,22 +8,44 @@ public class TextureReference : Reference readonly IMaterialSystem materials = Singleton(); public void Init(ReadOnlySpan texture, ReadOnlySpan textureGroupName, bool complain = true) { + Shutdown(); + reference = materials.FindTexture(texture, textureGroupName, complain); } public void InitProceduralTexture(ReadOnlySpan textureName, ReadOnlySpan textureGroupName, int w, int h, ImageFormat format, TextureFlags flags) { + Shutdown(); + reference = materials.CreateProceduralTexture(textureName, textureGroupName, w, h, format, flags); } public void InitRenderTarget(int w, int h, RenderTargetSizeMode sizeMod, ImageFormat format, MaterialRenderTargetDepth depth, bool hdr, ReadOnlySpan optionalName = default) { + Shutdown(); + TextureFlags textureFlags = TextureFlags.ClampS | TextureFlags.ClampT; + if (depth == MaterialRenderTargetDepth.Only) + textureFlags |= TextureFlags.PointSample; + + CreateRenderTargetFlags renderTargetFlags = hdr ? CreateRenderTargetFlags.HDR : 0; + + reference = materials.CreateNamedRenderTargetTextureEx(optionalName, w, h, sizeMod, format, depth, textureFlags, renderTargetFlags); + + Assert(reference != null); } public void Init(ITexture texture) { + Shutdown(); + reference = texture; + reference?.IncrementReferenceCount(); } public void Shutdown(bool deleteIfUnreferenced = false) { - + if (reference != null && materials != null) { + reference.DecrementReferenceCount(); + if (deleteIfUnreferenced) + reference.DeleteIfUnreferenced(); + reference = null; + } } } diff --git a/Source.Common/Mathematics/MathLib.cs b/Source.Common/Mathematics/MathLib.cs index 839ff47c..ad4dd044 100644 --- a/Source.Common/Mathematics/MathLib.cs +++ b/Source.Common/Mathematics/MathLib.cs @@ -549,6 +549,9 @@ static MathLib() { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void VectorDivide(in Vector3 inVec, vec_t scale, out Vector3 result) => result = Vector3.Divide(inVec, scale); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void VectorMin(in Vector3 a, in Vector3 b, out Vector3 result) => result = Vector3.Min(a, b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void VectorMax(in Vector3 a, in Vector3 b, out Vector3 result) => result = Vector3.Max(a, b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float VectorMaximum(in Vector3 v) => MathF.Max(v.X, MathF.Max(v.Y, v.Z)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RoundFloatToInt(float f) => (int)f; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte RoundFloatToByte(float f) => (byte)RoundFloatToInt(f); @@ -704,6 +707,48 @@ public static double Gain(double x, double biasAmount) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DEG2RAD(float x) => x * (MathF.PI / 180); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double DEG2RAD(double x) => x * (Math.PI / 180); + private static void FrustumPlanesFromMatrixHelper(in Matrix4x4 shadowToWorld, in Vector3 p1, in Vector3 p2, in Vector3 p3, out Vector3 normal, out float dist) { + Vector3DMultiplyPositionProjective(in shadowToWorld, in p1, out Vector3 world1); + Vector3DMultiplyPositionProjective(in shadowToWorld, in p2, out Vector3 world2); + Vector3DMultiplyPositionProjective(in shadowToWorld, in p3, out Vector3 world3); + + VectorSubtract(world2, world1, out Vector3 v1); + VectorSubtract(world3, world1, out Vector3 v2); + + CrossProduct(v1, v2, out normal); + VectorNormalize(ref normal); + dist = DotProduct(normal, world1); + } + + public static void FrustumPlanesFromMatrix(in Matrix4x4 clipToWorld, Frustum_t frustum) { + Vector3 normal; + float dist; + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(0.0f, 0.0f, 0.0f), new(1.0f, 0.0f, 0.0f), new(0.0f, 1.0f, 0.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.NearZ, 5, normal, dist); + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(0.0f, 0.0f, 1.0f), new(0.0f, 1.0f, 1.0f), new(1.0f, 0.0f, 1.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.FarZ, 5, normal, dist); + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(1.0f, 0.0f, 0.0f), new(1.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 0.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.Right, 5, normal, dist); + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(0.0f, 0.0f, 0.0f), new(0.0f, 1.0f, 1.0f), new(0.0f, 0.0f, 1.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.Left, 5, normal, dist); + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(1.0f, 1.0f, 0.0f), new(1.0f, 1.0f, 1.0f), new(0.0f, 1.0f, 1.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.Top, 5, normal, dist); + + FrustumPlanesFromMatrixHelper(in clipToWorld, + new(1.0f, 0.0f, 0.0f), new(0.0f, 0.0f, 1.0f), new(1.0f, 0.0f, 1.0f), out normal, out dist); + frustum.SetPlane((int)FrustumPlane.Bottom, 5, normal, dist); + } + public static void GeneratePerspectiveFrustum(in Vector3 origin, in Vector3 forward, in Vector3 right, in Vector3 up, float zNear, float zFar, float fovX, float fovY, Frustum_t frustum) { float intercept = DotProduct(origin, forward); @@ -947,6 +992,45 @@ public static void AngleMatrix(in QAngle angles, out Matrix3x4 matrix) { matrix[2, 3] = 0.0f; } + public static void AngleIMatrix(in QAngle angles, out Matrix3x4 matrix) { + matrix = default; + + SinCos(DEG2RAD(angles[YAW]), out float sy, out float cy); + SinCos(DEG2RAD(angles[PITCH]), out float sp, out float cp); + SinCos(DEG2RAD(angles[ROLL]), out float sr, out float cr); + + matrix[0, 0] = cp * cy; + matrix[0, 1] = cp * sy; + matrix[0, 2] = -sp; + matrix[1, 0] = sr * sp * cy + cr * -sy; + matrix[1, 1] = sr * sp * sy + cr * cy; + matrix[1, 2] = sr * cp; + matrix[2, 0] = (cr * sp * cy + -sr * -sy); + matrix[2, 1] = (cr * sp * sy + -sr * cy); + matrix[2, 2] = cr * cp; + matrix[0, 3] = 0.0f; + matrix[1, 3] = 0.0f; + matrix[2, 3] = 0.0f; + } + + public static void AngleIMatrix(in QAngle angles, in Vector3 position, out Matrix3x4 mat) { + AngleIMatrix(angles, out mat); + + VectorRotate(position, mat, out Vector3 translation); + translation *= -1.0f; + MatrixSetColumn(translation, 3, ref mat); + } + + public static float MatrixRowDotProduct(in Matrix3x4 in1, int row, in Vector3 in2) { + Assert((row >= 0) && (row < 3)); + return in1[row, 0] * in2[0] + in1[row, 1] * in2[1] + in1[row, 2] * in2[2]; + } + + public static float MatrixColumnDotProduct(in Matrix3x4 in1, int col, in Vector3 in2) { + Assert((col >= 0) && (col < 4)); + return in1[0, col] * in2[0] + in1[1, col] * in2[1] + in1[2, col] * in2[2]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NormalizeAngles(ref QAngle angles) { int i; @@ -1195,6 +1279,85 @@ private static void MatrixBuildPerspectiveZRange(ref Matrix4x4 dst, float znear, dst[2, 3] = znear * zfar / (znear - zfar); } + private static void CalculateAABBForNormalizedFrustum_Helper(float x, float y, float z, in Matrix4x4 volumeToWorld, ref Vector3 mins, ref Vector3 maxs) { + Vector3 volumeSpacePos = new(x, y, z); + + Assert(volumeSpacePos[0] >= -1e-3f); + Assert(volumeSpacePos[0] - 1.0f <= 1e-3f); + Assert(volumeSpacePos[1] >= -1e-3f); + Assert(volumeSpacePos[1] - 1.0f <= 1e-3f); + Assert(volumeSpacePos[2] >= -1e-3f); + Assert(volumeSpacePos[2] - 1.0f <= 1e-3f); + + Vector3DMultiplyPositionProjective(in volumeToWorld, in volumeSpacePos, out Vector3 worldPos); + AddPointToBounds(in worldPos, ref mins, ref maxs); + } + + public static void CalculateAABBFromProjectionMatrixInverse(in Matrix4x4 volumeToWorld, out Vector3 mins, out Vector3 maxs) { + ClearBounds(out mins, out maxs); + CalculateAABBForNormalizedFrustum_Helper(0, 0, 0, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(0, 0, 1, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(0, 1, 0, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(0, 1, 1, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(1, 0, 0, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(1, 0, 1, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(1, 1, 0, in volumeToWorld, ref mins, ref maxs); + CalculateAABBForNormalizedFrustum_Helper(1, 1, 1, in volumeToWorld, ref mins, ref maxs); + } + + public static void CalculateAABBFromProjectionMatrix(in Matrix4x4 worldToVolume, out Vector3 mins, out Vector3 maxs) { + MatrixInverseGeneral(in worldToVolume, out Matrix4x4 volumeToWorld); + CalculateAABBFromProjectionMatrixInverse(in volumeToWorld, out mins, out maxs); + } + + public static void CalculateSphereFromProjectionMatrixInverse(in Matrix4x4 volumeToWorld, out Vector3 center, out float radius) { + Vector3DMultiplyPositionProjective(in volumeToWorld, new(0.5f, 0.5f, 0.0f), out Vector3 centerNear); + Vector3DMultiplyPositionProjective(in volumeToWorld, new(0.5f, 0.5f, 1.0f), out Vector3 centerFar); + Vector3DMultiplyPositionProjective(in volumeToWorld, new(0.0f, 0.0f, 0.0f), out Vector3 nearEdge); + Vector3DMultiplyPositionProjective(in volumeToWorld, new(0.0f, 0.0f, 1.0f), out Vector3 farEdge); + + VectorSubtract(centerFar, centerNear, out Vector3 delta); + float l = delta.Length(); + float h1Sqr = centerNear.DistToSqr(nearEdge); + float h2Sqr = centerFar.DistToSqr(farEdge); + float x = (l * l + h2Sqr - h1Sqr) / (2.0f * l); + VectorMA(centerNear, x / l, delta, out center); + radius = MathF.Sqrt(h1Sqr + x * x); + } + + public static void CalculateSphereFromProjectionMatrix(in Matrix4x4 worldToVolume, out Vector3 center, out float radius) { + MatrixInverseGeneral(in worldToVolume, out Matrix4x4 volumeToWorld); + CalculateSphereFromProjectionMatrixInverse(in volumeToWorld, out center, out radius); + } + + public static void MatrixBuildPerspective(out Matrix4x4 dst, float fovX, float fovY, float zNear, float zFar) { + float width = 2 * zNear * MathF.Tan(fovX * (MathF.PI / 180.0f) * 0.5f); + float height = 2 * zNear * MathF.Tan(fovY * (MathF.PI / 180.0f) * 0.5f); + + dst = default; + dst[0, 0] = 2.0f * zNear / width; + dst[1, 1] = 2.0f * zNear / height; + dst[2, 2] = -zFar / (zNear - zFar); + dst[3, 2] = 1.0f; + dst[2, 3] = zNear * zFar / (zNear - zFar); + + Matrix4x4 negateXY = Matrix4x4.Identity; + negateXY[0, 0] = -1.0f; + negateXY[1, 1] = -1.0f; + MatrixMultiply(in negateXY, in dst, out dst); + + Matrix4x4 addW = Matrix4x4.Identity; + addW[0, 3] = 1.0f; + addW[1, 3] = 1.0f; + addW[2, 3] = 0.0f; + MatrixMultiply(in addW, in dst, out dst); + + Matrix4x4 scaleHalf = Matrix4x4.Identity; + scaleHalf[0, 0] = 0.5f; + scaleHalf[1, 1] = 0.5f; + MatrixMultiply(in scaleHalf, in dst, out dst); + } + public static bool IsZero(this in Vector3 v, float tolerance = 0.01f) { Vector3 zero = Vector3.Zero; Vector3 diff = Vector3.Abs(v - zero); @@ -1310,6 +1473,15 @@ public static unsafe bool IsValid(this ref RadianEuler R) { return !Vector3.AnyWhereAllBitsSet(Vector3.IsNaN(*(Vector3*)pR)); } + public static void MatrixInverseGeneral(in Matrix4x4 src, out Matrix4x4 dst) => Matrix4x4.Invert(src, out dst); + + public static void V3Mul(this in Matrix4x4 m, in Vector3 vIn, out Vector3 vOut) { + float rw = 1.0f / (m[3, 0] * vIn.X + m[3, 1] * vIn.Y + m[3, 2] * vIn.Z + m[3, 3]); + vOut.X = (m[0, 0] * vIn.X + m[0, 1] * vIn.Y + m[0, 2] * vIn.Z + m[0, 3]) * rw; + vOut.Y = (m[1, 0] * vIn.X + m[1, 1] * vIn.Y + m[1, 2] * vIn.Z + m[1, 3]) * rw; + vOut.Z = (m[2, 0] * vIn.X + m[2, 1] * vIn.Y + m[2, 2] * vIn.Z + m[2, 3]) * rw; + } + public static void Init(this ref Matrix4x4 m, in Matrix3x4 m3x4) { new ReadOnlySpan(in m3x4).Cast().CopyTo(new Span(ref m).Cast()); @@ -1416,6 +1588,23 @@ public static void MatrixBuildTranslation(out Matrix4x4 dst, float x, float y, f dst[2, 3] = z; } + public static void BasisToQuaternion(in Vector3 forward, in Vector3 right, in Vector3 up, out Quaternion q) { + Assert(MathF.Abs(forward.LengthSquared() - 1.0f) < 1e-3); + Assert(MathF.Abs(right.LengthSquared() - 1.0f) < 1e-3); + Assert(MathF.Abs(up.LengthSquared() - 1.0f) < 1e-3); + + VectorMultiply(in right, -1.0f, out Vector3 left); + + Matrix3x4 mat = default; + MatrixSetColumn(in forward, 0, ref mat); + MatrixSetColumn(in left, 1, ref mat); + MatrixSetColumn(in up, 2, ref mat); + + MatrixAngles(in mat, out QAngle angles); + + AngleQuaternion(in angles, out q); + } + public static void MatrixAngles(in Matrix3x4 matrix, out QAngle angles) { angles = default; Span forward = stackalloc float[3]; @@ -1673,6 +1862,8 @@ public static unsafe void AngleVectors(in QAngle angles, out Vector3 forward, ou [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Vector2DSubtract(in Vector2 a, in Vector2 b, out Vector2 c) => c = a - b; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Vector2DMultiply(in Vector2 a, float b, out Vector2 c) => c = a * b; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Vector2DMultiply(in Vector2 a, in Vector2 b, out Vector2 c) => c = a * b; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Vector2DMin(in Vector2 a, in Vector2 b, out Vector2 c) => c = Vector2.Min(a, b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Vector2DMax(in Vector2 a, in Vector2 b, out Vector2 c) => c = Vector2.Max(a, b); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float VectorNormalizeFast(ref Vector3 v) { @@ -1727,6 +1918,11 @@ public static void MatrixGetColumn(in Matrix3x4 inMatrix, int column, out Vector outVec.Y = inMatrix[1][column]; outVec.Z = inMatrix[2][column]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PositionMatrix(in Vector3 position, ref Matrix3x4 mat) { + MatrixSetColumn(in position, 3, ref mat); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void MatrixPosition(in Matrix3x4 matrix, out Vector3 origin) { MatrixGetColumn(matrix, 3, out origin); @@ -1965,6 +2161,14 @@ public static int PolyFromPlane(Span outVerts, in Vector3 normal, float return 4; } + public static void ComputeTrianglePlane(in Vector3 v1, in Vector3 v2, in Vector3 v3, out Vector3 normal, out float intercept) { + VectorSubtract(v2, v1, out Vector3 e1); + VectorSubtract(v3, v1, out Vector3 e2); + CrossProduct(e1, e2, out normal); + VectorNormalize(ref normal); + intercept = DotProduct(normal, v1); + } + public static int ClipPolyToPlane(Span inVerts, int vertCount, Span outVerts, in Vector3 normal, float dist, float onPlaneEpsilon = 0.1f) { Span dists = stackalloc float[vertCount * 4]; //4x vertcount should cover all cases Span sides = stackalloc int[vertCount * 4]; @@ -2406,6 +2610,128 @@ public static void Vector3DMultiplyPosition(in Matrix4x4 src1, in Vector3 src2, dst.Z = src1[2][0] * src2.X + src1[2][1] * src2.Y + src1[2][2] * src2.Z + src1[2][3]; } + public static void Vector3DMultiply(in Matrix4x4 src1, in Vector3 src2, out Vector3 dst) { + dst.X = src1[0][0] * src2.X + src1[0][1] * src2.Y + src1[0][2] * src2.Z; + dst.Y = src1[1][0] * src2.X + src1[1][1] * src2.Y + src1[1][2] * src2.Z; + dst.Z = src1[2][0] * src2.X + src1[2][1] * src2.Y + src1[2][2] * src2.Z; + } + + public static void ClearBounds(out Vector3 mins, out Vector3 maxs) { + mins = new(99999, 99999, 99999); + maxs = new(-99999, -99999, -99999); + } + + public static void AddPointToBounds(in Vector3 v, ref Vector3 mins, ref Vector3 maxs) { + vec_t val = v.X; + if (val < mins.X) + mins.X = val; + if (val > maxs.X) + maxs.X = val; + + val = v.Y; + if (val < mins.Y) + mins.Y = val; + if (val > maxs.Y) + maxs.Y = val; + + val = v.Z; + if (val < mins.Z) + mins.Z = val; + if (val > maxs.Z) + maxs.Z = val; + } + + public static void Vector3DMultiplyPositionProjective(in Matrix4x4 src1, in Vector3 src2, out Vector3 dst) { + float w = src1[3][0] * src2.X + src1[3][1] * src2.Y + src1[3][2] * src2.Z + src1[3][3]; + if (w != 0.0f) + w = 1.0f / w; + + dst.X = (src1[0][0] * src2.X + src1[0][1] * src2.Y + src1[0][2] * src2.Z + src1[0][3]) * w; + dst.Y = (src1[1][0] * src2.X + src1[1][1] * src2.Y + src1[1][2] * src2.Z + src1[1][3]) * w; + dst.Z = (src1[2][0] * src2.X + src1[2][1] * src2.Y + src1[2][2] * src2.Z + src1[2][3]) * w; + } + + public static void MatrixSetColumn(ref Matrix4x4 src, int col, in Vector3 column) { + Assert((col >= 0) && (col <= 3)); + + src[0, col] = column.X; + src[1, col] = column.Y; + src[2, col] = column.Z; + } + + public static void SetForward(ref this Matrix4x4 m, in Vector3 forward) { + m[0, 0] = forward.X; + m[1, 0] = forward.Y; + m[2, 0] = forward.Z; + } + + public static void SetLeft(ref this Matrix4x4 m, in Vector3 left) { + m[0, 1] = left.X; + m[1, 1] = left.Y; + m[2, 1] = left.Z; + } + + public static void SetUp(ref this Matrix4x4 m, in Vector3 up) { + m[0, 2] = up.X; + m[1, 2] = up.Y; + m[2, 2] = up.Z; + } + + public static void GetBasisVectors(this in Matrix4x4 m, out Vector3 forward, out Vector3 left, out Vector3 up) { + forward = new(m[0, 0], m[1, 0], m[2, 0]); + left = new(m[0, 1], m[1, 1], m[2, 1]); + up = new(m[0, 2], m[1, 2], m[2, 2]); + } + + public static void SetBasisVectors(ref this Matrix4x4 m, in Vector3 forward, in Vector3 left, in Vector3 up) { + m.SetForward(in forward); + m.SetLeft(in left); + m.SetUp(in up); + } + + public static void SetTranslation(ref this Matrix4x4 m, in Vector3 trans) { + m[0, 3] = trans.X; + m[1, 3] = trans.Y; + m[2, 3] = trans.Z; + } + + public static void MatrixTranspose(in Matrix4x4 src, out Matrix4x4 dst) { + dst = default; + dst[0, 0] = src[0, 0]; dst[0, 1] = src[1, 0]; dst[0, 2] = src[2, 0]; dst[0, 3] = src[3, 0]; + dst[1, 0] = src[0, 1]; dst[1, 1] = src[1, 1]; dst[1, 2] = src[2, 1]; dst[1, 3] = src[3, 1]; + dst[2, 0] = src[0, 2]; dst[2, 1] = src[1, 2]; dst[2, 2] = src[2, 2]; dst[2, 3] = src[3, 2]; + dst[3, 0] = src[0, 3]; dst[3, 1] = src[1, 3]; dst[3, 2] = src[2, 3]; dst[3, 3] = src[3, 3]; + } + + public static ref Vector3 GetTranslation(this in Matrix4x4 m, ref Vector3 trans) { + trans.X = m[0, 3]; + trans.Y = m[1, 3]; + trans.Z = m[2, 3]; + return ref trans; + } + + public static void MatrixTransformPlane(in Matrix4x4 src, in CollisionPlane inPlane, out CollisionPlane outPlane) { + Vector3 trans = default; + outPlane = default; + Vector3DMultiply(in src, in inPlane.Normal, out outPlane.Normal); + outPlane.Dist = inPlane.Dist * DotProduct(outPlane.Normal, outPlane.Normal); + outPlane.Dist += DotProduct(outPlane.Normal, src.GetTranslation(ref trans)); + } + + public static void MatrixInverseTR(in Matrix4x4 src, out Matrix4x4 dst) { + dst = default; + dst[0, 0] = src[0, 0]; dst[0, 1] = src[1, 0]; dst[0, 2] = src[2, 0]; + dst[1, 0] = src[0, 1]; dst[1, 1] = src[1, 1]; dst[1, 2] = src[2, 1]; + dst[2, 0] = src[0, 2]; dst[2, 1] = src[1, 2]; dst[2, 2] = src[2, 2]; + + Vector3 trans = new(-src[0, 3], -src[1, 3], -src[2, 3]); + Vector3DMultiply(in dst, in trans, out Vector3 newTrans); + MatrixSetColumn(ref dst, 3, in newTrans); + + dst[3, 0] = dst[3, 1] = dst[3, 2] = 0.0f; + dst[3, 3] = 1.0f; + } + public static byte FastFToC(float c) => (byte)(int)(c * 255.0f); public static float LinearToVertexLight(float f) { diff --git a/Source.Common/ShaderAPI/IShaderAPI.cs b/Source.Common/ShaderAPI/IShaderAPI.cs index 0706f35a..dc8ac37a 100644 --- a/Source.Common/ShaderAPI/IShaderAPI.cs +++ b/Source.Common/ShaderAPI/IShaderAPI.cs @@ -3,6 +3,8 @@ using Source.Common.MaterialSystem; using Source.Common.Mathematics; +using System.Numerics; + namespace Source.Common.ShaderAPI; public enum CreateTextureFlags @@ -67,6 +69,9 @@ public interface IShaderAPI : IShaderDynamicAPI void ClearColor4ub(byte r, byte g, byte b, byte a); void GetBackBufferDimensions(out int width, out int height); ImageFormat GetBackBufferFormat(); + bool SupportsShadowDepthTextures(); + ImageFormat GetShadowDepthTextureFormat(); + ImageFormat GetNullTextureFormat(); void BeginFrame(); void EndFrame(); int GetCurrentDynamicVBSize(); @@ -137,4 +142,14 @@ void CreateTextures(Span textureHandles, void SetLight(int lightNum, in LightDesc desc); void DisableAllLocalLights(); int GetMaxLights(); + void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture); + void SetStencilEnable(bool onoff); + void SetStencilFailOperation(StencilOperation op); + void SetStencilZFailOperation(StencilOperation op); + void SetStencilPassOperation(StencilOperation op); + void SetStencilCompareFunction(StencilComparisonFunction cmpfn); + void SetStencilReferenceValue(int reference); + void SetStencilTestMask(uint msk); + void SetStencilWriteMask(uint msk); + void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor); } diff --git a/Source.Engine/CL.cs b/Source.Engine/CL.cs index 689afe33..3d0a4f9f 100644 --- a/Source.Engine/CL.cs +++ b/Source.Engine/CL.cs @@ -229,7 +229,7 @@ public void FullyConnected() { // modelloader.FlushDynamicModels(); // Purge unused models - // modelloader.PurgeUnusedModels(); + modelloader.PurgeUnusedModels(); // Shutdown preload data // MDLCache.ShutdownPreloadData(); @@ -348,7 +348,7 @@ internal bool CheckCRCs(ReadOnlySpan levelFileName) { readonly byte[] voiceData = new byte[2048]; - public void SendVoicePacket(bool final){ + public void SendVoicePacket(bool final) { if (!Voice.IsRecording()) return; @@ -782,6 +782,9 @@ internal void ClearState() { if (LocalNetworkBackdoor != null) LocalNetworkBackdoor.ClearState(); + Host.FreeStateAndWorld(false); + Host.FreeToLowMark(false); + cl.Clear(); } } @@ -826,7 +829,7 @@ private void InitRecvTableMgr() { } #if !SWDS RecvTable.Init(recvTables.AsSpan()[..nRecvTables]!); // << ! is acceptable here; anything beyond recvTables is null, anything before it shouldnt be - // (and if something is null before that point something else is already horribly broken) + // (and if something is null before that point something else is already horribly broken) #endif } diff --git a/Source.Engine/CdllEngineInterface.cs b/Source.Engine/CdllEngineInterface.cs index cf4cd0e5..5437053d 100644 --- a/Source.Engine/CdllEngineInterface.cs +++ b/Source.Engine/CdllEngineInterface.cs @@ -275,7 +275,11 @@ public void ComputeDynamicLighting(in Vector3 pt, in Vector3 normal, out Vector3 } public void GetAmbientLightColor(out Vector3 color) { - throw new NotImplementedException(); + BSPDWorldLight? worldLight = ((Render)g_EngineRenderer).FindAmbientLight(); + if (worldLight == null) + color = new(0, 0, 0); + else + MathLib.VectorCopy(worldLight.Value.Intensity, out color); } public int GetDXSupportLevel() { diff --git a/Source.Engine/CollisionModelSubsystem.cs b/Source.Engine/CollisionModelSubsystem.cs index 1d87b383..1d3752a9 100644 --- a/Source.Engine/CollisionModelSubsystem.cs +++ b/Source.Engine/CollisionModelSubsystem.cs @@ -80,6 +80,47 @@ internal bool Init() { internal void PreLoad() { Init(); } + internal void Destroy() { + for (int i = 0; i < MapCollisionModels.Count; i++) + physcollision.VCollideUnload(MapCollisionModels[i].VCollisionData); + + // DispCollTrees_FreeLeafList(this); + // CM.DestroyDispPhysCollide(); + // DispCollTrees_Free(CM.g_DispCollTrees); + // CM.g_DispCollTrees = null; + // CM.g_DispBounds = null; + CM.g_DispCollTreeCount = 0; + + MapPlanes.Clear(); + TextureNames.Clear(); + MapSurfaces.Clear(); + // MapAreaPortals.Clear(); + // PortalOpen.Clear(); + // MapAreas.Clear(); + MapEntityString = null; + MapBrushes.Clear(); + MapDispList.Clear(); + MapCollisionModels.Clear(); + MapLeafBrushes.Clear(); + MapLeafs.Clear(); + MapNodes.Clear(); + MapBrushSides.Clear(); + MapVis = null; + + NumBrushSides = 0; + EmptyLeaf = SolidLeaf = 0; + NumNodes = 0; + NumLeafs = 0; + NumAreas = 0; + NumTextures = 0; + // FloodValid = 0; + // NumAreaPortals = 0; + NumClusters = 0; + NumVisibility = 0; + // NumPortalOpen = 0; + MapName = null; + MapRootNode = 0; + } internal void LoadTextures() { MapLoadHelper lh = new MapLoadHelper(LumpIndex.TexData); MapLoadHelper lhStringData = new MapLoadHelper(LumpIndex.TexDataStringData); @@ -690,6 +731,11 @@ public static void LoadMap(ReadOnlySpan name, bool allowReusePrevious, out return; } + public static void FreeMap() { + CollisionBSPData bspData = GetCollisionBSPData(); + bspData.Destroy(); + } + private static void FloodAreaConnections(CollisionBSPData bspData) { } diff --git a/Source.Engine/DebugOverlay.cs b/Source.Engine/DebugOverlay.cs index 5f6ef569..86287726 100644 --- a/Source.Engine/DebugOverlay.cs +++ b/Source.Engine/DebugOverlay.cs @@ -131,6 +131,7 @@ public class DebugOverlay : IVDebugOverlay static readonly object s_OverlayMutex = new(); static OverlayBase? s_pOverlays; + static OverlayText? s_pOverlayText; public void AddBoxOverlay(in Vector3 origin, in Vector3 mins, in Vector3 maxs, in QAngle angles, int r, int g, int b, int a, float duration) { if (cl.IsPaused()) @@ -212,7 +213,25 @@ public void AddSweptBoxOverlay(in Vector3 start, in Vector3 end, in Vector3 mins } public void AddTextOverlay(in Vector3 origin, float duration, ReadOnlySpan text) { - // throw new NotImplementedException(); + if (cl.IsPaused()) + return; + + lock (s_OverlayMutex) { + OverlayText new_overlay = new(); + + MathLib.VectorCopy(origin, out new_overlay.Origin); + strcpy(new_overlay.Text, text); + new_overlay.UseOrigin = true; + new_overlay.LineOffset = 0; + new_overlay.SetEndTime(duration); + new_overlay.R = 255; + new_overlay.G = 255; + new_overlay.B = 255; + new_overlay.A = 255; + + new_overlay.NextOverlayText = s_pOverlayText; + s_pOverlayText = new_overlay; + } } public void AddTextOverlay(in Vector3 origin, int line_offset, float duration, ReadOnlySpan text) { @@ -261,7 +280,7 @@ public static void ClearAllOverlays() { s_pOverlays = s_pOverlays.NextOverlay; DestroyOverlay(pOldOverlay); } - // todo: overlay text + s_pOverlayText = null; } s_bDrawGrid = false; @@ -270,23 +289,57 @@ public static void ClearAllOverlays() { void IVDebugOverlay.ClearAllOverlays() => DebugOverlay.ClearAllOverlays(); public void ClearDeadOverlays() { - throw new NotImplementedException(); - } + lock (s_OverlayMutex) { + OverlayText? currText = s_pOverlayText; + OverlayText? lastText = null; - public OverlayText? GetFirst() { - throw new NotImplementedException(); - } + while (currText != null) { + if (currText.IsDead()) { + if (lastText != null) + lastText.NextOverlayText = currText.NextOverlayText; + else + s_pOverlayText = currText.NextOverlayText; - public OverlayText? GetNext(OverlayText? current) { - throw new NotImplementedException(); + currText = currText.NextOverlayText; + } + else { + lastText = currText; + currText = currText.NextOverlayText; + } + } + } } + public OverlayText? GetFirst() => s_pOverlayText; + + public OverlayText? GetNext(OverlayText? current) => current!.NextOverlayText; + public int ScreenPosition(in Vector3 point, out Vector3 screen) { - throw new NotImplementedException(); + lock (s_OverlayMutex) { + int retval = g_EngineRenderer.ClipTransform(point, out screen) ? 1 : 0; + + materials.GetRenderContext().GetViewport(out int x, out int y, out int w, out int h); + + screen[0] = screen[0] * w / 2; + screen[1] = -screen[1] * h / 2; + screen[0] += w / 2; + screen[1] += h / 2; + return retval; + } } public int ScreenPosition(float xPos, float yPos, out Vector3 screen) { - throw new NotImplementedException(); + screen = default; + if (xPos > 1.0 || yPos > 1.0 || xPos < 0.0 || yPos < 0.0) + return 1; + + lock (s_OverlayMutex) { + materials.GetRenderContext().GetViewport(out int x, out int y, out int w, out int h); + + screen[0] = xPos * w; + screen[1] = yPos * h; + return 0; + } } static readonly ConVar enable_debug_overlays = new("enable_debug_overlays", "1", FCvar.GameDLL | FCvar.Cheat, "Enable rendering of debug overlays"); diff --git a/Source.Engine/Disp.cs b/Source.Engine/Disp.cs index 03a9c107..fd378bf1 100644 --- a/Source.Engine/Disp.cs +++ b/Source.Engine/Disp.cs @@ -15,6 +15,10 @@ public static class Disp public const int MAX_STATIC_BUFFER_INDICES = (8 * 1024); public const int MAX_DISP_DECALS = 32; + public const DispShadowHandle DISP_SHADOW_HANDLE_INVALID = unchecked((DispShadowHandle)~0); + public const DispDecalFragmentHandle DISP_DECAL_FRAGMENT_HANDLE_INVALID = unchecked((DispDecalFragmentHandle)~0); + public const DispShadowFragmentHandle DISP_SHADOW_FRAGMENT_HANDLE_INVALID = unchecked((DispShadowFragmentHandle)~0); + public static readonly List g_DispLMAlpha = []; public static readonly List g_DispLightmapSamplePositions = []; public static readonly List g_DispGroups = []; @@ -77,24 +81,49 @@ struct SideVertCorners public InlineArray2 Corners; } -public class DispDecalBase +[Flags] +public enum DecalFlags : byte +{ + NodeBitfieldComputed = 0x1, + DecalShadow = 0x2, + NoIntersection = 0x4, + FragmentsComputed = 0x8, +} + +public struct DispDecalBase { + public DispNodeIntersectBitVec NodeIntersect; + public DecalFlags Flags; + public ushort NVerts; + public ushort NTris; } -public class DispDecal : DispDecalBase +public struct DispDecal { + public DispDecalBase Base; + // public Decal? Decal; + public InlineArray2 DecalWorldScale; + public InlineArray3 TextureSpaceBasis; + public float Size; + public DispDecalFragmentHandle FirstFragment; } -class DispShadowDecal : DispDecalBase +public struct DispShadowDecal { + public DispDecalBase Base; + public ShadowHandle_t Shadow; + public DispShadowFragmentHandle FirstFragment; } -class DispShadowFragment +public struct DispShadowFragment { + public const int MAX_VERTS = 12; + public int NVerts; + public ShadowVertex[]? ShadowVerts; } public struct DispRenderVert diff --git a/Source.Engine/DispInfo.cs b/Source.Engine/DispInfo.cs index 31482374..419f4dce 100644 --- a/Source.Engine/DispInfo.cs +++ b/Source.Engine/DispInfo.cs @@ -84,10 +84,17 @@ public class DispInfo : DispUtilsHelper, IDispInfo static readonly MatSysInterface MatSys = Singleton(); + static readonly PooledLinkedList s_DispShadowDecals = new(); + static readonly PooledLinkedList s_DispShadowFragments = new(); + static readonly PooledLinkedList s_DispDecals = new(); + public void GetIntersectingSurfaces(GetIntersectingSurfaces_Struct pStruct) => throw new NotImplementedException(); public void RenderWireframeInLightmapPage(int pageId) => throw new NotImplementedException(); - public void GetBoundingBox(out Vector3 bbMin, out Vector3 bbMax) => throw new NotImplementedException(); + public void GetBoundingBox(out Vector3 bbMin, out Vector3 bbMax) { + bbMin = BBoxMin; + bbMax = BBoxMax; + } internal void SetParent(ref BSPMSurface2 surfID, WorldBrushData brushData) { this.brushData.SetTarget(brushData); @@ -105,13 +112,33 @@ public ref BSPMSurface2 GetParent() { // public DispDecalHandle NotifyAddDecal(Decal decal, float flSize) => throw new NotImplementedException(); public void NotifyRemoveDecal(DispDecalHandle h) => throw new NotImplementedException(); - public DispShadowHandle AddShadowDecal(ShadowHandle_t shadowHandle) => throw new NotImplementedException(); - public void RemoveShadowDecal(DispShadowHandle handle) => throw new NotImplementedException(); + public DispShadowHandle AddShadowDecal(ShadowHandle_t shadowHandle) { + DispShadowHandle h = unchecked((DispShadowHandle)s_DispShadowDecals.Alloc()); + if (FirstShadowDecal != DISP_SHADOW_HANDLE_INVALID) + s_DispShadowDecals.LinkBefore(FirstShadowDecal, h); + FirstShadowDecal = h; + + ref DispShadowDecal shadowDecal = ref s_DispShadowDecals[h]; + shadowDecal.Base.NTris = 0; + shadowDecal.Base.NVerts = 0; + shadowDecal.Shadow = shadowHandle; + shadowDecal.FirstFragment = DISP_SHADOW_FRAGMENT_HANDLE_INVALID; + + return h; + } + public void RemoveShadowDecal(DispShadowHandle handle) { + ClearShadowDecalFragments(handle); + + if (FirstShadowDecal == handle) + FirstShadowDecal = unchecked((DispShadowHandle)s_DispShadowDecals.Next(handle)); + + s_DispShadowDecals.Remove(handle); + } public bool ComputeShadowFragments(DispShadowHandle h, out int vertexCount, out int indexCount) => throw new NotImplementedException(); - public bool GetTag() => throw new NotImplementedException(); - public void SetTag() => throw new NotImplementedException(); + public bool GetTag() => Tag == DispArray!.CurTag; + public void SetTag() => Tag = (ushort)DispArray!.CurTag; public DispInfo? GetDispByIndex(int index) => index == 0xFFFF ? null : DispArray!.DispInfos[index]; @@ -380,7 +407,24 @@ public ref DispRenderVert GetVertex(int i) { // DispShadowFragment AllocateShadowDecalFragment(DispShadowHandle h, int nCount) => throw new NotImplementedException(); - // void ClearShadowDecalFragments(DispShadowHandle h) => throw new NotImplementedException(); + void ClearShadowDecalFragments(DispShadowHandle h) { + ref DispShadowDecal decal = ref s_DispShadowDecals[h]; + DispShadowFragmentHandle f = decal.FirstFragment; + DispShadowFragmentHandle next; + while (f != DISP_SHADOW_FRAGMENT_HANDLE_INVALID) { + next = unchecked((DispShadowFragmentHandle)s_DispShadowFragments.Next(f)); + s_DispShadowFragments.Remove(f); + f = next; + } + + decal.FirstFragment = DISP_SHADOW_FRAGMENT_HANDLE_INVALID; + + decal.Base.Flags &= ~DecalFlags.FragmentsComputed; + + decal.Base.NTris = 0; + decal.Base.NVerts = 0; + } + void ClearAllShadowDecalFragments() => throw new NotImplementedException(); void GenerateDecalFragments_R(in VertIndex nodeIndex, int nodeBitIndex, ushort decalHandle, DispDecalBase dispDecal, int level) => throw new NotImplementedException(); @@ -393,7 +437,7 @@ public ref DispRenderVert GetVertex(int i) { return DispInfo_IndexArray(world.Brush.Shared!.DispInfos, i); } - static readonly ConVar r_DrawDisp = new("r_DrawDisp", "1", FCvar.Cheat, "Toggles rendering of displacment maps"); + public static readonly ConVar r_DrawDisp = new("r_DrawDisp", "1", FCvar.Cheat, "Toggles rendering of displacment maps"); public static void DispInfo_RenderList(int sortGroup, Span list, int listCount, bool ortho, uint flags, RenderDepthMode depthMode) { if (r_DrawDisp.GetInt() == 0 || listCount == 0) return; @@ -412,7 +456,7 @@ public static void DispInfo_RenderList(int sortGroup, Span list ref BSPMSurface2 surf = ref ModelLoader.SurfaceHandleFromIndex(cur); ShadowDecalHandle_t decalHandle = ModelLoader.MSurf_ShadowDecals(ref surf); if (decalHandle != SHADOW_DECAL_HANDLE_INVALID) { - // g_pShadowMgr.AddShadowsOnSurfaceToRenderList(decalHandle) // todo + g_ShadowMgr.AddShadowsOnSurfaceToRenderList(decalHandle); } } @@ -420,17 +464,17 @@ public static void DispInfo_RenderList(int sortGroup, Span list // todo - // g_pShadowMgr.RenderFlashlights(flashlightMask) + g_ShadowMgr.RenderFlashlights(flashlightMask); // OverlayMgr().RenderOverlays(sortGroup) - // g_pShadowMgr.DrawFlashlightOverlays(sortGroup, flashlightMask + g_ShadowMgr.DrawFlashlightOverlays(sortGroup, flashlightMask); // OverlayMgr().ClearRenderLists(sortGroup) // DispInfo_BatchDecals(visibleDisps, visibleDispCount); // DispInfo_DrawDecals(visibleDisps, visibleDispCount); - // g_pShadowMgr.DrawFlashlightDecalsOnDisplacements(sortGroup, visibleDisps, visibleDispCount, flashlightMask) - // g_pShadowMgr.RenderShadows() - // g_pShadowMgr.ClearShadowRenderList() + g_ShadowMgr.DrawFlashlightDecalsOnDisplacements(sortGroup, visibleDisps, visibleDispCount, flashlightMask); + g_ShadowMgr.RenderShadows(); + g_ShadowMgr.ClearShadowRenderList(); DispInfo_DrawDebugInformation(list, listCount); } @@ -510,6 +554,94 @@ static void DispInfo_DrawPrimLists(RenderDepthMode depthMode) { } } } + public static void DispInfo_ClearAllTags(object? hArray) { + DispArray? array = (DispArray?)hArray; + if (array == null) + return; + + ++array.CurTag; + if (array.CurTag == 0xFFFF) { + array.CurTag = 1; + for (int i = 0; i < array.DispInfos.Length; i++) + array.DispInfos[i].Tag = 0; + } + } + + public static int DispInfo_AddShadowsToMeshBuilder(ref MeshBuilder meshBuilder, DispShadowHandle h, int baseIndex) { +#if SWDS + return 0; +#else + ShadowDecalRenderInfo info = default; + ref DispShadowDecal shadowDecal = ref s_DispShadowDecals[h]; + g_ShadowMgr.ComputeRenderInfo(ref info, shadowDecal.Shadow); + + Assert((shadowDecal.Base.Flags & DecalFlags.FragmentsComputed) != 0); + +#if DEBUG + int triCount = 0; + int vertCount = 0; +#endif + + Vector2 texCoord; + byte c; + DispShadowFragmentHandle f = shadowDecal.FirstFragment; + while (f != DISP_SHADOW_FRAGMENT_HANDLE_INVALID) { + ref DispShadowFragment fragment = ref s_DispShadowFragments[f]; + Span shadowVerts = fragment.ShadowVerts; + + int i; + for (i = 0; i < fragment.NVerts - 2; ++i) { + ref ShadowVertex shadowVert = ref shadowVerts[i]; + + MathLib.Vector2DMultiply(shadowVert.ShadowSpaceTexCoord.AsVector2D(), info.TexSize, out texCoord); + texCoord += info.TexOrigin; + c = ((IShadowMgrInternal)g_ShadowMgr).ComputeDarkness(shadowVert.ShadowSpaceTexCoord.Z, in info); + + meshBuilder.Position3fv(shadowVert.Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex((ushort)baseIndex); + meshBuilder.FastIndex((ushort)(i + baseIndex + 1)); + meshBuilder.FastIndex((ushort)(i + baseIndex + 2)); + } + + MathLib.Vector2DMultiply(shadowVerts[i].ShadowSpaceTexCoord.AsVector2D(), info.TexSize, out texCoord); + texCoord += info.TexOrigin; + c = ((IShadowMgrInternal)g_ShadowMgr).ComputeDarkness(shadowVerts[i].ShadowSpaceTexCoord.Z, in info); + meshBuilder.Position3fv(shadowVerts[i].Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + ++i; + + MathLib.Vector2DMultiply(shadowVerts[i].ShadowSpaceTexCoord.AsVector2D(), info.TexSize, out texCoord); + texCoord += info.TexOrigin; + c = ((IShadowMgrInternal)g_ShadowMgr).ComputeDarkness(shadowVerts[i].ShadowSpaceTexCoord.Z, in info); + meshBuilder.Position3fv(shadowVerts[i].Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + + baseIndex += fragment.NVerts; + f = (DispShadowFragmentHandle)s_DispShadowFragments.Next(f); + +#if DEBUG + triCount += fragment.NVerts - 2; + vertCount += fragment.NVerts; +#endif + } + +#if DEBUG + Assert(triCount == shadowDecal.Base.NTris); + Assert(vertCount == shadowDecal.Base.NVerts); +#endif + + return baseIndex; +#endif + } + static void DispInfo_BatchDecals(DispInfo[] visibleDisps, int visibleDispCount) => throw new NotImplementedException(); static void DispInfo_DrawDecals(DispInfo[] visibleDisps, int visibleDispCount) => throw new NotImplementedException(); static void DispInfo_DrawDebugInformation(Span list, int listCount) { diff --git a/Source.Engine/DispMapload.cs b/Source.Engine/DispMapload.cs index c3b0c685..832a6a6f 100644 --- a/Source.Engine/DispMapload.cs +++ b/Source.Engine/DispMapload.cs @@ -373,6 +373,8 @@ public static bool DispInfo_LoadDisplacements(Model world, MaterialSystem_SortIn MapLoadHelper dispLMPositions = new(LumpIndex.DispLightmapSamplePositions); dispLMAlphas.LoadLumpData(g_DispLightmapSamplePositions.AsSpan()); + DispInfo_ReleaseMaterialSystemObjects(world); + Span tempDisps = stackalloc BSPDDispInfo[BSPFileCommon.MAX_MAP_DISPINFO]; dispInfos.LoadLumpData(tempDisps); @@ -436,7 +438,31 @@ public static bool DispInfo_LoadDisplacements(Model world, MaterialSystem_SortIn return true; } - static void DispInfo_ReleaseMaterialSystemObjects(Model world) => throw new NotImplementedException(); + static void DispInfo_ReleaseMaterialSystemObjects(Model? world) { + using MatRenderContextPtr renderContext = new(SourceDllMain.materials); + + foreach (DispGroup group in g_DispGroups) { + foreach (GroupMesh mesh in group.Meshes) + renderContext.DestroyStaticMesh(mesh.Mesh!); + + group.Meshes.Clear(); + } + + g_DispGroups.Clear(); + + if (world != null) { + for (int iDisp = 0; iDisp < world.Brush.Shared!.NumDispInfos; iDisp++) { + DispInfo? disp = DispInfo.GetModelDisp(world, iDisp); + if (disp == null) { + Assert(false); + continue; + } + + disp.Mesh = null; + disp.VertOffset = disp.IndexOffset = 0; + } + } + } static void BuildTagData(CoreDispInfo coreDisp, DispInfo disp) { int walkTest = 0; diff --git a/Source.Engine/GLRSurf.cs b/Source.Engine/GLRSurf.cs index b18c2d5b..3e5dc249 100644 --- a/Source.Engine/GLRSurf.cs +++ b/Source.Engine/GLRSurf.cs @@ -776,7 +776,7 @@ static void ComputeFogVolumeInfo(ref FogVolumeInfo fogVolume) { fogVolume.InFogVolume = true; - ref BSPDLeafWaterData leafWaterData = ref host_state.WorldBrush!.LeafWaterData![leaf.LeafWaterDataID]; + ref BSPMLeafWaterData leafWaterData = ref host_state.WorldBrush!.LeafWaterData![leaf.LeafWaterDataID]; if (leafWaterData.SurfaceTexInfoID == -1) { fogVolume.State.FogEnabled = false; return; @@ -811,7 +811,9 @@ public static void Shader_WorldBegin(WorldRenderList renderList) { ResetWorldRenderList(renderList); - // TODO decal/overlaymgr/shadowmgr + // TODO decal/overlaymgr + + g_ShadowMgr.ClearShadowRenderList(); } static void Shader_WorldZFillSurfChain(in MSurfaceSortList sortList, in SurfaceSortGroup group, MeshBuilder meshBuilder, ref nint startVertIn, uint includeFlags) => throw new NotImplementedException(); static void Shader_WorldShadowDepthFill(WorldRenderList renderList, DrawWorldListFlags flags) => throw new NotImplementedException(); @@ -836,10 +838,10 @@ static void Shader_WorldEnd(WorldRenderList renderList, DrawWorldListFlags flags if ((flags & DrawWorldListFlags.ClipSkybox) != 0) g_EngineRenderer.DrawSkybox(g_EngineRenderer.GetZFar()); else { - // MaterialHeightClipMode clipMode = renderCtx.GetHeightClipMode(); // todo - // renderCtx.SetHeightClipMode(MaterialHeightClipMode.Disable); + MaterialHeightClipMode clipMode = renderCtx.GetHeightClipMode(); + renderCtx.SetHeightClipMode(MaterialHeightClipMode.Disable); g_EngineRenderer.DrawSkybox(g_EngineRenderer.GetZFar()); - // renderCtx.SetHeightClipMode(clipMode); + renderCtx.SetHeightClipMode(clipMode); } } } @@ -868,10 +870,24 @@ static void Shader_WorldEnd(WorldRenderList renderList, DrawWorldListFlags flags Shader_DrawChains(renderList, sortGroup, false); AddProjectedTextureDecalsToList(renderList, sortGroup); - // g_pShadowMgr.AddShadowsOnSurfaceToRenderList // todo + for (int j = renderList.ShadowHandles[sortGroup].Count - 1; j >= 0; --j) + g_ShadowMgr.AddShadowsOnSurfaceToRenderList(renderList.ShadowHandles[sortGroup].ElementAt(j)); + renderList.ShadowHandles[sortGroup].Clear(); - // g_pShadowMgr flashlights + OverlayMgr + DecalSurfaceDraw + RenderShadows // todo + bool flashlightMask = !((flags & DrawWorldListFlags.Refraction) != 0 || (flags & DrawWorldListFlags.Reflection) != 0); + + g_ShadowMgr.SetFlashlightStencilMasks(flashlightMask); + g_ShadowMgr.RenderFlashlights(flashlightMask); + + // OverlayMgr + DecalSurfaceDraw // todo + + g_ShadowMgr.DrawFlashlightOverlays(sortGroup, flashlightMask); + + g_ShadowMgr.DrawFlashlightDecals(sortGroup, flashlightMask); + + g_ShadowMgr.RenderShadows(); + g_ShadowMgr.ClearShadowRenderList(); if (sortGroup == (int)MatSortGroup.WaterSurface && waterZAdjust != 0.0f) { renderCtx.MatrixMode(MaterialMatrixMode.Model); @@ -1220,7 +1236,18 @@ public static void R_DrawBrushModel(IClientEntity? baseEntity, Model? model, in g_ShaderDebug.TestAnyDebug(); } } - public static void R_DrawBrushModelShadow(IClientRenderable renderable) => throw new NotImplementedException(); + public static void R_DrawBrushModelShadow(IClientRenderable renderable) { + if (r_drawbrushmodels.GetInt() == 0) + return; + + Model? model = renderable.GetModel(); + Vector3 origin = renderable.GetRenderOrigin(); + QAngle angles = renderable.GetRenderAngles(); + + using MatRenderContextPtr renderContext = new(materials); + using BrushModelTransform brushTransform = new(origin, angles, renderContext); + g_BrushBatchRenderer.DrawBrushModelShadow(model, renderable); + } public static void R_DrawIdentityBrushModel(IWorldRenderList renderListIn, Model? model) => throw new NotImplementedException(); } @@ -1757,7 +1784,48 @@ private void DrawTransLists(ref TransRender renderT, object? proxyData) { } } - public void DrawBrushModelShadow(Model? model, IClientRenderable renderable) => throw new NotImplementedException(); + public void DrawBrushModelShadow(Model? model, IClientRenderable renderable) { + BrushRender? render = FindOrCreateRenderBatch(model!); + if (render == null) + return; + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.Bind(MatSys.MaterialShadowBuild!, renderable); + + SurfaceHandle_t surfID = model!.Brush.FirstModelSurface; + IMesh mesh = renderContext.GetDynamicMesh(); + MeshBuilder meshBuilder = new(); + meshBuilder.Begin(mesh, MaterialPrimitiveType.Triangles, render.TotalVertexCount, render.TotalIndexCount); + + for (int i = 0; i < model.Brush.NumModelSurfaces; i++, surfID++) { + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID, model.Brush.Shared); + Assert((ModelLoader.MSurf_Flags(ref surface) & SurfDraw.NoDraw) == 0); + + if ((ModelLoader.MSurf_Flags(ref surface) & SurfDraw.Trans) != 0) + continue; + + int startVert = ModelLoader.MSurf_FirstVertIndex(ref surface); + int vertCount = ModelLoader.MSurf_VertCount(ref surface); + int startIndex = meshBuilder.GetCurrentVertex(); + int j; + for (j = 0; j < vertCount; j++) { + int vertIndex = model.Brush.Shared!.VertIndices![startVert + j]; + + meshBuilder.Position3fv(model.Brush.Shared.Vertexes![vertIndex].Position); + meshBuilder.TexCoord2f(0, 0.0f, 0.0f); + meshBuilder.AdvanceVertex(); + } + + for (j = 0; j < vertCount - 2; j++) { + meshBuilder.FastIndex((ushort)startIndex); + meshBuilder.FastIndex((ushort)(startIndex + j + 1)); + meshBuilder.FastIndex((ushort)(startIndex + j + 2)); + } + } + meshBuilder.End(); + mesh.Draw(); + } } public class BrushModelTransform : IDisposable @@ -1851,7 +1919,19 @@ public bool EnumerateLeavesInSphere(in Vector3 center, float radius, ref T pE } } - public bool EnumerateLeavesAlongRay(in Ray ray, ref T pEnum, nint context) where T : ISpatialLeafEnumerator => throw new NotImplementedException(); + public bool EnumerateLeavesAlongRay(in Ray ray, ref T pEnum, nint context) where T : ISpatialLeafEnumerator { + if (!ray.IsSwept) { + MathLib.VectorAdd(in ray.Start, in ray.Extents, out Vector3 maxs); + MathLib.VectorSubtract(in ray.Start, in ray.Extents, out Vector3 mins); + + return EnumerateLeavesInBox(in mins, in maxs, ref pEnum, context); + } + + if (ray.IsRay) + return EnumerateLeavesAlongRay_R(host_state.WorldBrush!.Nodes![0], in ray, 0.0f, 1.0f, pEnum, context); + else + return EnumerateLeavesAlongExtrudedRay_R(host_state.WorldBrush!.Nodes![0], in ray, 0.0f, 1.0f, pEnum, context); + } static bool EnumerateLeafInBox_R(BSPMNode node, ref EnumLeafBoxInfo info) where T : ISpatialLeafEnumerator { if (node.Contents == (int)Contents.Solid) @@ -1905,9 +1985,112 @@ static bool EnumerateLeafInBox_R(BSPMNode node, ref EnumLeafBoxInfo info) } } - static bool EnumerateLeavesAlongRay_R(BSPMNode node, in Ray ray, float start, float end, ISpatialLeafEnumerator pEnum, nint context) => throw new NotImplementedException(); + static bool EnumerateLeavesAlongRay_R(BSPMNode node, in Ray ray, float start, float end, ISpatialLeafEnumerator pEnum, nint context) { + if (node.Contents == (int)Contents.Solid) + return true; + + if (node.Contents >= 0) + return pEnum.EnumerateLeaf(((BSPMLeaf)node).Index, context); + + ref CollisionPlane plane = ref node.Plane; + + float startDotN, deltaDotN; + if ((byte)plane.Type <= 2) { + startDotN = ray.Start[(byte)plane.Type]; + deltaDotN = ray.Delta[(byte)plane.Type]; + } + else { + startDotN = MathLib.DotProduct(ray.Start, plane.Normal); + deltaDotN = MathLib.DotProduct(ray.Delta, plane.Normal); + } + + float front = startDotN + start * deltaDotN - plane.Dist; + float back = startDotN + end * deltaDotN - plane.Dist; + + int side = front < 0 ? 1 : 0; + + if ((back < 0 ? 1 : 0) == side) + return EnumerateLeavesAlongRay_R(node.Children[side]!, in ray, start, end, pEnum, context); + + float frac = front / (front - back); + float mid = start * (1.0f - frac) + end * frac; + + bool ok = EnumerateLeavesAlongRay_R(node.Children[side]!, in ray, start, mid, pEnum, context); + if (!ok) + return ok; + + return EnumerateLeavesAlongRay_R(node.Children[side != 0 ? 0 : 1]!, in ray, mid, end, pEnum, context); + } + + static bool EnumerateLeavesAlongExtrudedRay_R(BSPMNode node, in Ray ray, float start, float end, ISpatialLeafEnumerator pEnum, nint context) { + if (node.Contents == (int)Contents.Solid) + return true; - static bool EnumerateLeavesAlongExtrudedRay_R(BSPMNode node, in Ray ray, float start, float end, ISpatialLeafEnumerator pEnum, nint context) => throw new NotImplementedException(); + if (node.Contents >= 0) + return pEnum.EnumerateLeaf(((BSPMLeaf)node).Index, context); + + ref CollisionPlane plane = ref node.Plane; + + float t1, t2, offset; + float startDotN, deltaDotN; + if ((byte)plane.Type <= 2) { + startDotN = ray.Start[(byte)plane.Type]; + deltaDotN = ray.Delta[(byte)plane.Type]; + offset = ray.Extents[(byte)plane.Type] + DIST_EPSILON; + } + else { + startDotN = MathLib.DotProduct(ray.Start, plane.Normal); + deltaDotN = MathLib.DotProduct(ray.Delta, plane.Normal); + offset = MathF.Abs(ray.Extents[0] * plane.Normal[0]) + + MathF.Abs(ray.Extents[1] * plane.Normal[1]) + + MathF.Abs(ray.Extents[2] * plane.Normal[2]) + DIST_EPSILON; + } + t1 = startDotN + start * deltaDotN - plane.Dist; + t2 = startDotN + end * deltaDotN - plane.Dist; + + if (t1 > offset && t2 > offset) + return EnumerateLeavesAlongExtrudedRay_R(node.Children[0]!, in ray, start, end, pEnum, context); + + if (t1 < -offset && t2 < -offset) + return EnumerateLeavesAlongExtrudedRay_R(node.Children[1]!, in ray, start, end, pEnum, context); + + if (MathF.Abs(t1 - t2) < DIST_EPSILON) { + bool parallelRet = EnumerateLeavesAlongExtrudedRay_R(node.Children[0]!, in ray, start, end, pEnum, context); + if (!parallelRet) + return false; + return EnumerateLeavesAlongExtrudedRay_R(node.Children[1]!, in ray, start, end, pEnum, context); + } + + float idist, frac2, frac; + int side; + if (t1 < t2) { + idist = 1.0f / (t1 - t2); + side = 1; + frac2 = (t1 + offset) * idist; + frac = (t1 - offset) * idist; + } + else if (t1 > t2) { + idist = 1.0f / (t1 - t2); + side = 0; + frac2 = (t1 - offset) * idist; + frac = (t1 + offset) * idist; + } + else { + side = 0; + frac = 1; + frac2 = 0; + } + + frac = Math.Clamp(frac, 0f, 1f); + float midf = start + (end - start) * frac; + bool ret = EnumerateLeavesAlongExtrudedRay_R(node.Children[side]!, in ray, start, midf, pEnum, context); + if (!ret) + return ret; + + frac2 = Math.Clamp(frac2, 0f, 1f); + midf = start + (end - start) * frac2; + return EnumerateLeavesAlongExtrudedRay_R(node.Children[side != 0 ? 0 : 1]!, in ray, midf, end, pEnum, context); + } static bool EnumerateLeafInSphere_R(BSPMNode node, ref EnumLeafSphereInfo info, int testFlags) where T : ISpatialLeafEnumerator { while (true) { diff --git a/Source.Engine/Host.cs b/Source.Engine/Host.cs index e517dc40..b87e1f72 100644 --- a/Source.Engine/Host.cs +++ b/Source.Engine/Host.cs @@ -539,6 +539,8 @@ public void Shutdown() { Disconnect(true); + CM.FreeMap(); + #if !SWDS Scr.DisabledForLoading = true; if (!sv.IsDedicated()) { @@ -549,7 +551,7 @@ public void Shutdown() { ClientDLL.Shutdown(); // TextMessageShutdown(); EngineVGui.Shutdown(); - // StaticPropMgr.Shutdown(); + g_StaticPropMgr.Shutdown(); modelloader.Shutdown(); // ShutdownStudioRender(); // ShutdownMaterialSystem(); @@ -591,13 +593,21 @@ public void Shutdown() { } private void OverlayText_SetEndTimeFn(OverlayText text, TimeUnit_t duration) { + text.ServerCount = cl.ServerCount; + + if (duration <= 0.0f) { + text.EndTime = 0.0f; + text.CreationTick = (int)GetOverlayTick(); + return; + } + if (duration == IVDebugOverlay.NDEBUG_PERSIST_TILL_NEXT_SERVER) + text.EndTime = IVDebugOverlay.NDEBUG_PERSIST_TILL_NEXT_SERVER; + else + text.EndTime = cl.GetTime() + duration; } private bool OverlayText_IsDeadFn(OverlayText text) { - if (cl.IsPaused()) - return false; - if (text.ServerCount != cl.ServerCount) return true; @@ -1184,8 +1194,9 @@ public void ShutdownServer() { #endif // static prop manager - // free state and world + FreeStateAndWorld(true); sv.Shutdown(); + FreeToLowMark(true); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive); } @@ -1291,7 +1302,52 @@ internal bool ValidGame() { return false; } - internal void FreeStateAndWorld(bool v) { + internal void FreeStateAndWorld(bool server) { + bool needsPurge = false; + + Assert(Initialized); + Assert(HunkLevel != 0); + + if (!server && sv.IsActive()) + return; + +#if !SWDS + if (server && !sv.IsDedicated()) + CL.ClearState(); +#endif + + if (host_state.WorldModel != null) { + modelloader.UnreferenceModel(host_state.WorldModel, ModelLoaderFlags.Server); + modelloader.UnreferenceModel(host_state.WorldModel, ModelLoaderFlags.Client); + host_state.SetWorldModel(null); + needsPurge = server && true; + } + + if (server) { + modelInfo.OnLevelChange(); + } +#if !SWDS + else { + // modelinfoclient.OnLevelChange(); + } +#endif + + // modelloader.UnloadUnreferencedModels(); + + // TimeLastMemTest = 0; + } + + internal void FreeToLowMark(bool server) { + Assert(Initialized); + Assert(HunkLevel != 0); + + if (!server && sv.IsActive()) + return; + + CM.FreeMap(); + // if (HunkLevel != 0) { + // Hunk_FreeToLowMark(HunkLevel); + // } } } diff --git a/Source.Engine/IShadowMgrInternal.cs b/Source.Engine/IShadowMgrInternal.cs new file mode 100644 index 00000000..314d7bf6 --- /dev/null +++ b/Source.Engine/IShadowMgrInternal.cs @@ -0,0 +1,65 @@ +using Source.Common.Engine; + +using System.Numerics; + +namespace Source.Engine; + +public struct ShadowVertex +{ + public Vector3 Position; + public Vector3 ShadowSpaceTexCoord; +} + +public struct ShadowDecalRenderInfo +{ + public Vector2 TexOrigin; + public Vector2 TexSize; + public float FalloffOffset; + public float OOZFalloffDist; + public float FalloffAmount; + public float FalloffBias; +} + +public interface IShadowMgrInternal : IShadowMgr +{ + void LevelInit(int surfCount); + void LevelShutdown(); + + void AddShadowsOnSurfaceToRenderList(ShadowDecalHandle_t decalHandle); + + void RenderProjectedTextures(Matrix4x4? modelToWorld = null); + + void RenderShadows(Matrix4x4? modelToWorld = null); + + void RenderFlashlights(bool doMasking, Matrix4x4? modelToWorld = null); + + void ClearShadowRenderList(); + + int ProjectAndClipVertices(ShadowHandle_t handle, ReadOnlySpan position, out ShadowVertex[]? outVertex); + + void ComputeRenderInfo(ref ShadowDecalRenderInfo info, ShadowHandle_t handle); + + int InvalidShadowIndex(); + void SetModelShadowState(ModelInstanceHandle_t instance); + + void SetNumWorldMaterialBuckets(int numMaterialSortBins); + + void DrawFlashlightDecals(int sortGroup, bool doMasking); + + void DrawFlashlightDecalsOnSingleSurface(SurfaceHandle_t surfID, bool doMasking); + + void DrawFlashlightOverlays(int sortGroup, bool doMasking); + + void DrawFlashlightDecalsOnDisplacements(int sortGroup, ReadOnlySpan visibleDisps, int visibleDispCount, bool doMasking); + + void SetFlashlightStencilMasks(bool doMasking); + bool ModelHasShadows(ModelInstanceHandle_t instance); + + byte ComputeDarkness(float z, in ShadowDecalRenderInfo info) { + z = (z - info.FalloffOffset) * info.OOZFalloffDist; + z = z >= 0 ? z : 0.0f; + z = info.FalloffBias + z * info.FalloffAmount; + z = (z - 255.0f) >= 0 ? 255.0f : z; + return (byte)z; + } +} diff --git a/Source.Engine/ImplStudio.cs b/Source.Engine/ImplStudio.cs index 41f2b6af..0566e8b4 100644 --- a/Source.Engine/ImplStudio.cs +++ b/Source.Engine/ImplStudio.cs @@ -32,6 +32,7 @@ public class ModelInstance public LightCacheHandle_t LightCacheHandle; public StudioDecalHandle_t DecalHandle = ModelRender.STUDIORENDER_DECAL_INVALID; public DataCacheHandle_t ColorMeshHandle; + public uint FirstShadow; } public struct ColorMeshParams @@ -186,6 +187,8 @@ public class ModelRender : IModelRender ModelInstanceHandle_t curModelHandle; readonly Dictionary ModelInstances = []; + public ref uint FirstShadowOnModelInstance(ModelInstanceHandle_t handle) => ref ModelInstances[handle].FirstShadow; + ModelInstanceHandle_t NewHandle() { ModelInstanceHandle_t handle = Interlocked.Increment(ref curModelHandle); ModelInstances[handle] = new(); @@ -209,6 +212,10 @@ public ModelInstanceHandle_t CreateInstance(IClientRenderable renderable, LightC for (int i = 0; i < 6; ++i) instance.AmbientLightingState.BoxColor[i].X = 1.0f; +#if !SWDS + instance.FirstShadow = unchecked((uint)g_ShadowMgr.InvalidShadowIndex()); +#endif + // Static props use baked lighting for performance reasons if (cache != null) { SetStaticLighting(handle, cache); @@ -315,6 +322,87 @@ public bool DrawModelSetup(ref ModelRenderInfo info, ref DrawModelState state, S return true; } + static readonly ConVar r_shadowlod = new("r_shadowlod", "-1"); + static readonly ConVar r_shadowlodbias = new("r_shadowlodbias", "2"); + + public bool DrawModelShadowSetup(IClientRenderable renderable, int body, int skin, ref DrawModelInfo info, Span customBoneToWorld, out Span boneToWorldOut) { + boneToWorldOut = default; + + Model? model = renderable.GetModel(); + if (model == null) + return false; + + if (model.Type != ModelType.Studio) + return false; + + // Assert(modelloader.IsLoaded(model) && model.Type == ModelType.Studio);//2do + + info.StudioHdr = MDLCache.GetStudioHdr(model.Studio)!; + info.ColorMeshes = null; + + if (info.StudioHdr.NumBodyParts == 0) + return false; + + Assert(renderable != null); + info.HardwareData = MDLCache.GetHardwareData(model.Studio)!; + if (info.HardwareData == null) + return false; + + info.Skin = skin; + info.Body = body; + info.ClientEntity = renderable; + info.HitboxSet = 0; + + info.Lod = r_shadowlod.GetInt(); + if ((info.StudioHdr.Flags & StudioHdrFlags.HasShadowLod) != 0) + info.Lod = info.HardwareData.NumLODs - 1; + else if (info.Lod == Studio.USESHADOWLOD) { + int lastlod = info.HardwareData.NumLODs - 1; + info.Lod = lastlod; + } + else if (info.Lod < 0) { + using MatRenderContextPtr renderContext = new(materialSystem); + float factor = r_shadowlodbias.GetFloat() > 0.0f ? 1.0f / r_shadowlodbias.GetFloat() : 1.0f; + float screenSize = factor * renderContext.ComputePixelWidthOfSphere(renderable!.GetRenderOrigin(), 0.5f); + info.Lod = StudioRender.ComputeModelLod(info.HardwareData, screenSize); + info.Lod = info.HardwareData.NumLODs - 2; + if (info.Lod < 0) { + info.Lod = 0; + } + } + + if (info.Lod < info.HardwareData.RootLOD) + info.Lod = info.HardwareData.RootLOD; + + Span boneToWorld = customBoneToWorld; + if (boneToWorld.IsEmpty) + boneToWorld = StudioRender.LockBoneMatrices(info.StudioHdr.NumBones); + bool ok = renderable!.SetupBones(boneToWorld, info.StudioHdr.NumBones, Studio.BONE_USED_BY_VERTEX_AT_LOD(info.Lod), cl.GetTime()); + StudioRender.UnlockBoneMatrices(); + if (!ok) + return false; + + boneToWorldOut = boneToWorld; + return true; + } + + public void DrawModelShadow(IClientRenderable renderable, in DrawModelInfo info, Span boneToWorld) { + StudioRender.SetEyeViewTarget(info.StudioHdr, info.Body, in vec3_origin); + + Vector3 white = new(1, 1, 1); + StudioRender.SetColorModulation(white); + StudioRender.SetAlphaModulation(1.0f); + + if ((info.StudioHdr.Flags & StudioHdrFlags.UseShadowLodMaterials) == 0) { + StudioRender.ForcedMaterialOverride(MatSys.MaterialShadowBuild, OverrideType.BuildShadows); + } + + DrawModelInfo drawInfo = info; + StudioRender.DrawModel(ref Unsafe.NullRef(), ref drawInfo, boneToWorld, null, null, renderable.GetRenderOrigin(), + StudioRenderFlags.DrawNoShadows | StudioRenderFlags.DrawEntireModel | StudioRenderFlags.DrawNoFlexes); + StudioRender.ForcedMaterialOverride(null); + } + readonly IMDLCache MDLCache; readonly IStudioRender StudioRender; readonly IMaterialSystem materials; diff --git a/Source.Engine/MatSysInterface.cs b/Source.Engine/MatSysInterface.cs index 6b3483fc..ea9e7d4d 100644 --- a/Source.Engine/MatSysInterface.cs +++ b/Source.Engine/MatSysInterface.cs @@ -277,6 +277,7 @@ private void InitDebugMaterials() { MaterialWireframe = GL_LoadMaterial("debug/debugwireframe", MaterialDefines.TEXTURE_GROUP_OTHER); MaterialWorldWireframe = GL_LoadMaterial("debug/debugworldwireframe", MaterialDefines.TEXTURE_GROUP_OTHER); MaterialWorldWireframeZBuffer = GL_LoadMaterial("debug/debugworldwireframezbuffer", MaterialDefines.TEXTURE_GROUP_OTHER); + MaterialShadowBuild = GL_LoadMaterial("engine/shadowbuild", MaterialDefines.TEXTURE_GROUP_OTHER); // TODO: the rest of these important materials #endif } @@ -370,6 +371,12 @@ public void WorldStaticMeshCreate() { Skybox3DMeshesIndices.Clear(); int sortIDs = materials.GetNumSortIDs(); + if (sortIDs == 0) { + Assert(false); + return; + } + + g_ShadowMgr.SetNumWorldMaterialBuckets(sortIDs); Assert(WorldStaticMeshes.Count == 0); WorldStaticMeshes.EnsureCountDefault(sortIDs); @@ -838,6 +845,7 @@ public void WorldStaticMeshDestroy() { public IMaterial? MaterialWireframe; public IMaterial? MaterialWorldWireframe; public IMaterial? MaterialWorldWireframeZBuffer; + public IMaterial? MaterialShadowBuild; #endif public IMaterial GL_LoadMaterial(ReadOnlySpan name, ReadOnlySpan textureGroupName) { diff --git a/Source.Engine/MaterialBuckets.cs b/Source.Engine/MaterialBuckets.cs new file mode 100644 index 00000000..295edb39 --- /dev/null +++ b/Source.Engine/MaterialBuckets.cs @@ -0,0 +1,70 @@ +using Source.Common; + +namespace Source.Engine; + +public class MaterialsBuckets where Element_t : struct +{ + struct MaterialSortInfo_t + { + public int FlushCount; + public int Head; + } + + readonly List UsedSortIDs = []; + + readonly List MaterialSortInfoArray = []; + + readonly PooledLinkedList Elements = new(); + + int FlushCount = -1; + + public void SetNumMaterialSortIDs(int n) { + MaterialSortInfoArray.Clear(); + for (int i = 0; i < n; i++) + MaterialSortInfoArray.Add(new MaterialSortInfo_t { FlushCount = -1, Head = PooledLinkedList.INVALID_INDEX }); + Elements.Clear(); + + UsedSortIDs.Clear(); + } + + public void Flush() { + FlushCount++; + Elements.Clear(); + UsedSortIDs.Clear(); + } + + public int GetFirstUsedSortID() => UsedSortIDs.Count > 0 ? 0 : InvalidSortIDHandle(); + + public int GetNextUsedSortID(int prevSortID) => prevSortID + 1 < UsedSortIDs.Count ? prevSortID + 1 : InvalidSortIDHandle(); + + public int GetSortID(int handle) => UsedSortIDs[handle]; + + public int InvalidSortIDHandle() => -1; + + public int GetElementListHead(int sortID) => MaterialSortInfoArray[sortID].Head; + + public int GetElementListNext(int h) => Elements.Next(h); + + public Element_t GetElement(int h) => Elements[h]; + + public int InvalidElementHandle() => PooledLinkedList.INVALID_INDEX; + + public void AddElement(short sortID, Element_t elem) { + int elemID = Elements.Alloc(); + Elements[elemID] = elem; + + MaterialSortInfo_t sortInfo = MaterialSortInfoArray[sortID]; + if (sortInfo.FlushCount != FlushCount) { + sortInfo.FlushCount = FlushCount; + + UsedSortIDs.Add(sortID); + + sortInfo.Head = elemID; + } + else { + Elements.LinkBefore(sortInfo.Head, elemID); + sortInfo.Head = elemID; + } + MaterialSortInfoArray[sortID] = sortInfo; + } +} diff --git a/Source.Engine/ModelInfo.cs b/Source.Engine/ModelInfo.cs index 5660a402..2890a713 100644 --- a/Source.Engine/ModelInfo.cs +++ b/Source.Engine/ModelInfo.cs @@ -352,7 +352,10 @@ public int GetSurfacepropsForTerrain(int index) { } public void OnLevelChange() { - throw new NotImplementedException(); + NetworkedDynamicModels.Clear(); + + // TODO + // modelloader.ForceUnloadNonClientDynamicModels(); } public int RegisterDynamicModel(ReadOnlySpan name, bool bClientSide) { diff --git a/Source.Engine/ModelLoader.cs b/Source.Engine/ModelLoader.cs index bab02e46..7ce4dd51 100644 --- a/Source.Engine/ModelLoader.cs +++ b/Source.Engine/ModelLoader.cs @@ -1061,7 +1061,31 @@ public static void Mod_ComputeBrushModelFlags(Model mod) { private void Map_SetRenderInfoAllocated(bool allocated) => MapRenderInfoLoaded = allocated; private void Mod_LoadLeafWaterData() { + MapLoadHelper lh = new(LumpIndex.LeafWaterData); + BSPDLeafWaterData[] _in = lh.LoadLumpData(); + if ((lh.LumpSize % Unsafe.SizeOf()) != 0) + Host.Error($"Mod_LoadLeafs: funny lump size in {lh.GetMapName()}"); + int count = lh.LumpSize / Unsafe.SizeOf(); + BSPMLeafWaterData[] _out = new BSPMLeafWaterData[count]; + + lh.GetMap().LeafWaterData = _out; + for (int i = 0; i < count; i++) { + _out[i].MinZ = _in[i].MinZ; + _out[i].SurfaceTexInfoID = _in[i].SurfaceTexInfoID; + _out[i].SurfaceZ = _in[i].SurfaceZ; + _out[i].FirstLeafIndex = -1; + } + + if (count == 1) { + WorldBrushData brush = lh.GetMap(); + for (int i = 0; i < brush.NumLeafs; i++) { + if (brush.Leafs![i].LeafWaterDataID >= 0) { + brush.LeafWaterData![0].FirstLeafIndex = (short)i; + break; + } + } + } } private void Mod_LoadCubemapSamples() { @@ -1734,8 +1758,7 @@ private void Mod_LoadFaces() { } } - // todo - // _out2.ShadowDecals = SHADOW_DECAL_HANDLE_INVALID; + surfID.ShadowDecals = SHADOW_DECAL_HANDLE_INVALID; // _out2.Decals = WORLD_DECAL_HANDLE_INVALID; // out2.FirstOverlayFragment = OVERLAY_FRAGMENT_INVALID; @@ -1946,7 +1969,8 @@ public void PurgeUnusedModels() { } public void ResetModelServerCounts() { - + foreach (Model model in Models.Values) + model.ServerCount = -1; } public void UnreferenceAllModels(ModelLoaderFlags referenceType) { @@ -1954,7 +1978,8 @@ public void UnreferenceAllModels(ModelLoaderFlags referenceType) { } public void UnreferenceModel(Model model, ModelLoaderFlags referenceType) { - throw new NotImplementedException(); + AssertMsg((referenceType & ModelLoaderFlags.Dynamic) == 0, "UnreferenceModel: do not use for dynamic models"); + model.LoadFlags &= ~referenceType; } internal static int MSurf_FirstPrimID(ref BSPMSurface2 surfID, WorldBrushData bsp) { diff --git a/Source.Engine/Overlay.cs b/Source.Engine/Overlay.cs new file mode 100644 index 00000000..3d2f8699 --- /dev/null +++ b/Source.Engine/Overlay.cs @@ -0,0 +1,8 @@ +global using static Source.Engine.Overlay; + +namespace Source.Engine; + +public static class Overlay +{ + public const float OVERLAY_AVOID_FLICKER_NORMAL_OFFSET = 0.1f; +} diff --git a/Source.Engine/Render.cs b/Source.Engine/Render.cs index 46f27154..dd8e93e7 100644 --- a/Source.Engine/Render.cs +++ b/Source.Engine/Render.cs @@ -323,9 +323,11 @@ public void LevelInit() { // FIXME: Is this the best place to initialize the kd tree when we're client-only? if (!sv.IsActive()) { + g_ShadowMgr.LevelShutdown(); StaticPropMgr().LevelShutdown(); SpatialPartition().Init(host_state.WorldModel!.Mins, host_state.WorldModel!.Maxs); StaticPropMgr().LevelInit(); + g_ShadowMgr.LevelInit(host_state.WorldBrush!.NumSurfaces); } LoadWorldGeometry(); diff --git a/Source.Engine/Server/GameServer.cs b/Source.Engine/Server/GameServer.cs index a897c15b..6b022255 100644 --- a/Source.Engine/Server/GameServer.cs +++ b/Source.Engine/Server/GameServer.cs @@ -460,13 +460,14 @@ internal bool SpawnServer(ReadOnlySpan mapName, ReadOnlySpan mapFile Common.TimestampedLog("StaticPropMgr()->LevelShutdown()"); #if !SWDS - // g_pShadowMgr->LevelShutdown(); + g_ShadowMgr.LevelShutdown(); #endif - // StaticPropMgr()->LevelShutdown(); + StaticPropMgr().LevelShutdown(); Common.TimestampedLog("Host_FreeToLowMark"); Host.FreeStateAndWorld(true); + Host.FreeToLowMark(true); serverGlobalVariables.MapVersion = 0; diff --git a/Source.Engine/ShadowMgr.cs b/Source.Engine/ShadowMgr.cs new file mode 100644 index 00000000..7ddcd714 --- /dev/null +++ b/Source.Engine/ShadowMgr.cs @@ -0,0 +1,2134 @@ +global using static Source.Engine.ShadowMgrGlobals; + +using CommunityToolkit.HighPerformance; + +using Source.Common; +using Source.Common.Commands; +using Source.Common.Engine; +using Source.Common.Formats.BSP; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; + +using System.Numerics; + +namespace Source.Engine; + +public static class ShadowMgrGlobals +{ + public const int SHADOW_VERTEX_SMALL_CACHE_COUNT = 8; + public const int SHADOW_VERTEX_LARGE_CACHE_COUNT = 32; + public const int SHADOW_VERTEX_TEMP_COUNT = 48; + public const int MAX_CLIP_PLANE_COUNT = 4; + public const int SURFACE_BOUNDS_CACHE_COUNT = 1024; + public const int SHADOW_DECAL_CACHE_COUNT = 16 * 1024; + public const int MAX_SHADOW_DECAL_CACHE_COUNT = 64 * 1024; + + public static readonly ConVar r_shadows = new("r_shadows", "1"); + public static readonly ConVar r_shadows_gamecontrol = new("r_shadows_gamecontrol", "-1", FCvar.Cheat); + public static readonly ConVar r_shadowwireframe = new("r_shadowwireframe", "0", FCvar.Cheat); + public static readonly ConVar r_shadowids = new("r_shadowids", "0", FCvar.Cheat); + public static readonly ConVar r_flashlightdrawsweptbbox = new("r_flashlightdrawsweptbbox", "0"); + public static readonly ConVar r_flashlightdrawfrustumbbox = new("r_flashlightdrawfrustumbbox", "0"); + public static readonly ConVar r_flashlightnodraw = new("r_flashlightnodraw", "0"); + public static readonly ConVar r_flashlightupdatedepth = new("r_flashlightupdatedepth", "1"); + public static readonly ConVar r_flashlightdrawdepth = new("r_flashlightdrawdepth", "0"); + public static readonly ConVar r_flashlightrenderworld = new("r_flashlightrenderworld", "1"); + public static readonly ConVar r_flashlightrendermodels = new("r_flashlightrendermodels", "1"); + public static readonly ConVar r_flashlightrender = new("r_flashlightrender", "1"); + public static readonly ConVar r_flashlightculldepth = new("r_flashlightculldepth", "1"); + public static readonly ConVar r_flashlight_version2 = new("r_flashlight_version2", "0", FCvar.Cheat | FCvar.DevelopmentOnly); + + public static readonly ShadowMgr g_ShadowMgr = new(); + + public static bool ScreenSpaceRectFromPoints(MatRenderContextPtr renderContext, Vector3[][] clippedPolygons, Span numPoints, int numPolygons, out int left, out int top, out int right, out int bottom) { + left = top = right = bottom = 0; + + if (numPolygons == 0) + return false; + + renderContext.GetMatrix(MaterialMatrixMode.View, out Matrix4x4 matView); + renderContext.GetMatrix(MaterialMatrixMode.Projection, out Matrix4x4 matProj); + Matrix4x4 matViewProj = Matrix4x4.Multiply(matProj, matView); + + float minX, maxX, minY, maxY; + minX = minY = float.MaxValue; + maxX = maxY = -float.MaxValue; + + for (int i = 0; i < numPolygons; i++) { + for (int j = 0; j < numPoints[i]; j++) { + matViewProj.V3Mul(in clippedPolygons[i][j], out Vector3 screenSpacePoint); + + minX = MathF.Min(minX, screenSpacePoint.X); + maxX = MathF.Max(maxX, screenSpacePoint.X); + minY = MathF.Min(minY, -screenSpacePoint.Y); + maxY = MathF.Max(maxY, -screenSpacePoint.Y); + } + } + + materials.GetBackBufferDimensions(out int width, out int height); + + left = (int)((minX * 0.5f + 0.5f) * width) - 1; + top = (int)((minY * 0.5f + 0.5f) * height) - 1; + right = (int)((maxX * 0.5f + 0.5f) * width) + 1; + bottom = (int)((maxY * 0.5f + 0.5f) * height) + 1; + + left = Math.Clamp(left, 0, width); + top = Math.Clamp(top, 0, height); + right = Math.Clamp(right, 0, width); + bottom = Math.Clamp(bottom, 0, height); + + Assert((left <= right) && (top <= bottom)); + + bool withinBounds = (left > 0) || (top > 0) || (right < width) || (bottom < height); + + width = right - left; + height = bottom - top; + int area = (width > 0) && (height > 0) ? width * height : 0; + + return withinBounds && (area > 0); + } + + public static void DrawDebugPolygon(int numVerts, Span vecPoints, bool frontFacing, bool nearPlane) { + int r = 0, g = 0, b = 0; + if (frontFacing) + b = 255; + else + r = 255; + + if (nearPlane) { + r = b = 0; + g = 255; + } + + for (int i = 1; i < (numVerts - 1); i++) { + Vector3 v0 = vecPoints[0]; + Vector3 v1 = vecPoints[frontFacing ? i : i + 1]; + Vector3 v2 = vecPoints[frontFacing ? i + 1 : i]; + + debugoverlay.AddTriangleOverlay(v0, v1, v2, r, g, b, 20, true, 0); + } + + for (int i = 0; i < numVerts; i++) { + Vector3 v0 = vecPoints[i]; + Vector3 v1 = vecPoints[(i + 1) % numVerts]; + + debugoverlay.AddLineOverlayAlpha(v0, v1, 255, 255, 255, 255, false, 0); + } + } + + public static void DrawPolygonToStencil(MatRenderContextPtr renderContext, int numVerts, Span vecPoints, bool frontFacing, bool nearPlane) { + IMaterial? material = materials.FindMaterial("engine/writestencil", MaterialDefines.TEXTURE_GROUP_OTHER, true); + + renderContext.Bind(material!); + IMesh mesh = renderContext.GetDynamicMesh(true); + + renderContext.MatrixMode(MaterialMatrixMode.Model); + renderContext.PushMatrix(); + renderContext.LoadIdentity(); + + MeshBuilder meshBuilder = new(); + meshBuilder.Begin(mesh, MaterialPrimitiveType.Triangles, numVerts - 2); + + for (int i = 1; i < (numVerts - 1); i++) { + meshBuilder.Position3f(vecPoints[0].X, vecPoints[0].Y, vecPoints[0].Z); + meshBuilder.AdvanceVertex(); + + int index = frontFacing ? i : i + 1; + meshBuilder.Position3f(vecPoints[index].X, vecPoints[index].Y, vecPoints[index].Z); + meshBuilder.AdvanceVertex(); + + index = frontFacing ? i + 1 : i; + meshBuilder.Position3f(vecPoints[index].X, vecPoints[index].Y, vecPoints[index].Z); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(false, true); + + renderContext.MatrixMode(MaterialMatrixMode.Model); + renderContext.PopMatrix(); + } + + public static readonly ConVar r_flashlightclip = new("r_flashlightclip", "0", FCvar.Cheat); + public static readonly ConVar r_flashlightdrawclip = new("r_flashlightdrawclip", "0", FCvar.Cheat); + public static readonly ConVar r_flashlightscissor = new("r_flashlightscissor", "1", 0); + + public static void ExtractFrustumPlanes(out Frustum frustumPlanes, float planeEpsilon) { + ref readonly ViewSetup view = ref g_EngineRenderer.ViewGetCurrent(); + + float fovY = MathLib.CalcFovY(view.FOV, view.AspectRatio); + + Frustum_t frustum = new(); + MathLib.AngleVectors(view.Angles, out Vector3 forward, out Vector3 right, out Vector3 up); + MathLib.GeneratePerspectiveFrustum(view.Origin, forward, right, up, view.ZNear + planeEpsilon, view.ZFar - planeEpsilon, view.FOV, fovY, frustum); + + frustumPlanes = default; + for (int i = 0; i < (int)FrustumPlane.NumPlanes; i++) + frustumPlanes.SetPlane(i, frustum.GetPlane(i).Normal, frustum.GetPlane(i).Dist); + } + + public static void ConstructNearAndFarPolygons(Span vecNearPlane, Span vecFarPlane, float planeEpsilon) { + ref readonly ViewSetup view = ref g_EngineRenderer.ViewGetCurrent(); + + float fovY = MathLib.CalcFovY(view.FOV, view.AspectRatio); + + float tanHalfAngleRadians = MathF.Tan(MathLib.DEG2RAD(view.FOV * 0.5f)); + float halfNearWidth = tanHalfAngleRadians * (view.ZNear + planeEpsilon); + float halfFarWidth = tanHalfAngleRadians * (view.ZFar - planeEpsilon); + tanHalfAngleRadians = MathF.Tan(MathLib.DEG2RAD(fovY * 0.5f)); + float halfNearHeight = tanHalfAngleRadians * (view.ZNear + planeEpsilon); + float halfFarHeight = tanHalfAngleRadians * (view.ZFar - planeEpsilon); + + MathLib.AngleVectors(view.Angles, out Vector3 forward, out Vector3 right, out Vector3 up); + forward.NormalizeInPlace(); + right.NormalizeInPlace(); + up.NormalizeInPlace(); + + Vector3 centerNear = view.Origin + forward * (view.ZNear + planeEpsilon); + + Vector3 rightHalfNearWidth = right * halfNearWidth; + Vector3 upHalfNearHeight = up * halfNearHeight; + + vecNearPlane[0] = centerNear - rightHalfNearWidth - upHalfNearHeight; + vecNearPlane[1] = centerNear - rightHalfNearWidth + upHalfNearHeight; + vecNearPlane[2] = centerNear + rightHalfNearWidth + upHalfNearHeight; + vecNearPlane[3] = centerNear + rightHalfNearWidth - upHalfNearHeight; + + Vector3 rightHalfFarWidth = right * halfFarWidth; + Vector3 upHalfFarHeight = up * halfFarHeight; + + vecFarPlane[0] = centerNear - rightHalfFarWidth - upHalfFarHeight; + vecFarPlane[1] = centerNear + rightHalfFarWidth - upHalfFarHeight; + vecFarPlane[2] = centerNear + rightHalfFarWidth + upHalfFarHeight; + vecFarPlane[3] = centerNear - rightHalfFarWidth + upHalfFarHeight; + } + + public static bool SufficientlyClose(Vector3 v1, Vector3 v2, float epsilon) { + if (MathF.Abs(v1.X - v2.X) > epsilon) + return false; + + if (MathF.Abs(v1.Y - v2.Y) > epsilon) + return false; + + if (MathF.Abs(v1.Z - v2.Z) > epsilon) + return false; + + return true; + } + + public static int ClipPlaneToFrustum(Span inPoints, Span outPoints, Span vecWorldFrustumPoints) { + Span clipPing = stackalloc Vector3[10]; + Span clipPong = stackalloc Vector3[10]; + bool ping = true; + + clipPing[0] = inPoints[0]; + clipPing[1] = inPoints[1]; + clipPing[2] = inPoints[2]; + clipPing[3] = inPoints[3]; + + int numPoints = 4; + + for (int i = 0; i < 6; i++) { + if (numPoints < 3) + break; + + Span clipPolygon = vecWorldFrustumPoints[(4 * i)..]; + MathLib.ComputeTrianglePlane(clipPolygon[0], clipPolygon[1], clipPolygon[2], out Vector3 normal, out float dist); + + if (ping) + numPoints = MathLib.ClipPolyToPlane(clipPing, numPoints, clipPong, normal, dist); + else + numPoints = MathLib.ClipPolyToPlane(clipPong, numPoints, clipPing, normal, dist); + + ping = !ping; + } + + if (numPoints < 3) + return 0; + + if (ping) + clipPing[..numPoints].CopyTo(outPoints); + else + clipPong[..numPoints].CopyTo(outPoints); + + return numPoints; + } + + public static ref uint FirstShadowOnModel(ModelInstanceHandle_t h) => ref ((ModelRender)modelrender).FirstShadowOnModelInstance(h); + + public static ref uint FirstModelInShadow(ShadowHandle_t h) => ref g_ShadowMgr.FirstModelInShadow(h); +} + +public interface IShadowClipper +{ + static abstract bool Inside(in ShadowVertex vert); + static abstract float Clip(in Vector3 one, in Vector3 two); + static abstract bool IsPlane(); + static abstract bool IsAbove(); +} + +public struct ClipTop : IShadowClipper +{ + public static bool Inside(in ShadowVertex vert) => vert.ShadowSpaceTexCoord.Y < 1; + public static float Clip(in Vector3 one, in Vector3 two) => (1 - one.Y) / (two.Y - one.Y); + public static bool IsPlane() => false; + public static bool IsAbove() => false; +} + +public struct ClipLeft : IShadowClipper +{ + public static bool Inside(in ShadowVertex vert) => vert.ShadowSpaceTexCoord.X > 0; + public static float Clip(in Vector3 one, in Vector3 two) => one.X / (one.X - two.X); + public static bool IsPlane() => false; + public static bool IsAbove() => false; +} + +public struct ClipRight : IShadowClipper +{ + public static bool Inside(in ShadowVertex vert) => vert.ShadowSpaceTexCoord.X < 1; + public static float Clip(in Vector3 one, in Vector3 two) => (1 - one.X) / (two.X - one.X); + public static bool IsPlane() => false; + public static bool IsAbove() => false; +} + +public struct ClipBottom : IShadowClipper +{ + public static bool Inside(in ShadowVertex vert) => vert.ShadowSpaceTexCoord.Y > 0; + public static float Clip(in Vector3 one, in Vector3 two) => one.Y / (one.Y - two.Y); + public static bool IsPlane() => false; + public static bool IsAbove() => false; +} + +public struct ClipAbove : IShadowClipper +{ + public static bool Inside(in ShadowVertex vert) => vert.ShadowSpaceTexCoord.Z > 0; + public static float Clip(in Vector3 one, in Vector3 two) => one.Z / (one.Z - two.Z); + public static bool IsPlane() => false; + public static bool IsAbove() => true; +} + +public struct ClipPlane : IShadowClipper +{ + static Vector3 Normal; + static float Dist; + + public static bool Inside(in ShadowVertex vert) => MathLib.DotProduct(vert.Position, Normal) < Dist; + + public static float Clip(in Vector3 one, in Vector3 two) { + MathLib.VectorSubtract(two, one, out Vector3 dir); + return CollisionUtils.IntersectRayWithPlane(one, dir, Normal, Dist); + } + + public static bool IsAbove() => false; + public static bool IsPlane() => true; + + public static void SetPlane(in Vector3 normal, float dist) { + Normal = normal; + Dist = dist; + } +} + +public struct ShadowClipState +{ + public int CurrVert; + public int TempCount; + public int ClipCount; + public ShadowVertex[] TempVertices = new ShadowVertex[SHADOW_VERTEX_TEMP_COUNT]; + public int[,] ClipVertices = new int[2, SHADOW_VERTEX_TEMP_COUNT]; + + public ShadowClipState() { } + + static void ClampTexCoord(ref ShadowVertex inVertex, ref ShadowVertex outVertex) { + if (MathF.Abs(inVertex.ShadowSpaceTexCoord.X) < 1e-3) + outVertex.ShadowSpaceTexCoord.X = 0.0f; + else if (MathF.Abs(inVertex.ShadowSpaceTexCoord.X - 1.0f) < 1e-3) + outVertex.ShadowSpaceTexCoord.X = 1.0f; + + if (MathF.Abs(inVertex.ShadowSpaceTexCoord.Y) < 1e-3) + outVertex.ShadowSpaceTexCoord.Y = 0.0f; + else if (MathF.Abs(inVertex.ShadowSpaceTexCoord.Y - 1.0f) < 1e-3) + outVertex.ShadowSpaceTexCoord.Y = 1.0f; + } + + static void Intersect(ref ShadowVertex start, ref ShadowVertex end, ref ShadowVertex outVertex, bool startInside) where Clipper : IShadowClipper { + float t; + if (!Clipper.IsPlane()) { + if (!Clipper.IsAbove()) { + t = Clipper.Clip(start.ShadowSpaceTexCoord, end.ShadowSpaceTexCoord); + + MathLib.VectorLerp(start.ShadowSpaceTexCoord, end.ShadowSpaceTexCoord, t, out outVertex.ShadowSpaceTexCoord); + } + else { + t = Clipper.Clip(start.ShadowSpaceTexCoord, end.ShadowSpaceTexCoord); + MathLib.VectorLerp(start.ShadowSpaceTexCoord, end.ShadowSpaceTexCoord, t, out outVertex.ShadowSpaceTexCoord); + + if (startInside) + ClampTexCoord(ref end, ref outVertex); + else + ClampTexCoord(ref start, ref outVertex); + } + } + else { + t = Clipper.Clip(start.Position, end.Position); + MathLib.VectorLerp(start.ShadowSpaceTexCoord, end.ShadowSpaceTexCoord, t, out outVertex.ShadowSpaceTexCoord); + } + + MathLib.VectorLerp(start.Position, end.Position, t, out outVertex.Position); + } + + public static void ShadowClip(ref ShadowClipState clip) where Clipper : IShadowClipper { + if (clip.ClipCount == 0) + return; + + int numOutVerts = 0; + int srcVert = clip.CurrVert; + int destVert = clip.CurrVert == 0 ? 1 : 0; + + int numVerts = clip.ClipCount; + int start = clip.ClipVertices[srcVert, numVerts - 1]; + bool startInside = Clipper.Inside(clip.TempVertices[start]); + for (int i = 0; i < numVerts; ++i) { + int end = clip.ClipVertices[srcVert, i]; + bool endInside = Clipper.Inside(clip.TempVertices[end]); + if (endInside) { + if (!startInside) { + if (clip.TempCount >= SHADOW_VERTEX_TEMP_COUNT) + return; + + clip.ClipVertices[destVert, numOutVerts] = clip.TempCount++; + + Intersect(ref clip.TempVertices[start], ref clip.TempVertices[end], ref clip.TempVertices[clip.ClipVertices[destVert, numOutVerts]], startInside); + ++numOutVerts; + } + clip.ClipVertices[destVert, numOutVerts++] = end; + } + else { + if (startInside) { + if (clip.TempCount >= SHADOW_VERTEX_TEMP_COUNT) + return; + + clip.ClipVertices[destVert, numOutVerts] = clip.TempCount++; + + Intersect(ref clip.TempVertices[start], ref clip.TempVertices[end], ref clip.TempVertices[clip.ClipVertices[destVert, numOutVerts]], startInside); + ++numOutVerts; + } + } + start = end; + startInside = endInside; + } + + clip.CurrVert = 1 - clip.CurrVert; + clip.ClipCount = numOutVerts; + Assert(clip.ClipCount <= SHADOW_VERTEX_TEMP_COUNT); + } +} + +class FlashlightInfoBox +{ + public ShadowMgr.FlashlightInfo Info = new(); +} + +public class ShadowMgr : IShadowMgrInternal, ISpatialLeafEnumerator +{ + public const float BACKFACE_EPSILON = 0.01f; + + public const ShadowCreateFlags SHADOW_DISABLED = (ShadowCreateFlags)((int)ShadowCreateFlags.LastFlag << 1); + + struct SurfaceBounds_t + { + public fltx4 Mins; + public fltx4 Maxs; + public Vector3 Center; + public float Radius; + public int SurfaceIndex; + } + + struct ShadowVertexSmallList + { + public InlineArray8 Verts; + } + + struct ShadowVertexLargeList + { + public InlineArray32 Verts; + } + + struct ShadowVertexCache + { + public ushort Count; + public ShadowHandle_t Shadow; + public ushort CachedVerts; + public ShadowVertex[]? Verts; + } + + struct Shadow + { + public ShadowInfo_t Info; + public Vector3 ProjectionDir; + public IMaterial? Material; + public IMaterial? ModelMaterial; + public object? BindProxy; + public ShadowCreateFlags Flags; + public ushort SortOrder; + public float SphereRadius; + public Ray Ray; + public Vector3 SphereCenter; + + public FlashlightHandle_t FlashlightHandle; + public ITexture? FlashlightDepthTexture; + + public ushort ClipPlaneCount; + public InlineArray4 ClipPlane; + public InlineArray4 ClipDist; + + public ShadowSurfaceIndex_t FirstDecal; + + public uint FirstModel; + + public byte ShadowStencilBit; + } + + struct ShadowDecal + { + public SurfaceHandle_t SurfID; + public ShadowSurfaceIndex_t ShadowListIndex; + public ShadowHandle_t Shadow; + public DispShadowHandle DispShadow; + public ushort ShadowVerts; + + public ShadowDecalHandle_t NextRender; + } + + struct ShadowBuildInfo + { + public ShadowHandle_t Shadow; + public Vector3 RayStart; + public Vector3 ProjectionDirection; + public Vector3 SphereCenter; + public float SphereRadius; + public byte[]? Vis; + } + + struct ShadowRenderInfo + { + public int VertexCount; + public int IndexCount; + public int MaxVertices; + public int MaxIndices; + public int Count; + public nint[]? Cache; + public int DispCount; + public Matrix4x4? ModelToWorld; + public Matrix4x4 WorldToModel; + public DispShadowHandle[]? DispCache; + } + + struct SortOrderInfo + { + public IMaterial? MaterialEnum; + public int RefCount; + } + + delegate void ShadowDebugFunc(ShadowHandle_t shadowHandle, in Vector3 centroid); + + internal struct FlashlightInfo + { + public FlashlightInfo() { + FlashlightState = new(); + Frustum = new(); + MaterialBuckets = new(); + OccluderBuckets = new(); + Renderables = []; + } + + public FlashlightState FlashlightState; + public ShadowHandle_t Shadow; + public Frustum_t Frustum; + public MaterialsBuckets MaterialBuckets; + public MaterialsBuckets OccluderBuckets; + + public List Renderables; + } + + readonly PooledLinkedList Shadows = new(); + + readonly PooledLinkedList ShadowDecals = new(); + + readonly PooledLinkedList ShadowSurfaces = new(); + + readonly List RenderQueue = []; + + readonly List SortOrderIds = []; + + readonly PooledLinkedList VertexCache = new(); + + readonly List TempVertexCache = []; + + readonly PooledLinkedList SmallVertexList = new(); + readonly PooledLinkedList LargeVertexList = new(); + + readonly BidirectionalSet ShadowsOnModels = new(); + + readonly LinkedList SurfaceBoundsCache = []; + LinkedListNode?[]? SurfaceBounds; + + int DecalsToRender; + + readonly Dictionary FlashlightStates = []; + readonly List ValidFlashlightHandles = []; + FlashlightHandle_t curFlashlightHandleIdx; + int NumWorldMaterialBuckets; + bool Initialized; + + nint[] ShadowDecalCache = new nint[SHADOW_DECAL_CACHE_COUNT]; + DispShadowHandle[] DispShadowDecalCache = new DispShadowHandle[SHADOW_DECAL_CACHE_COUNT]; + + public ShadowMgr() { + ShadowsOnModels.Init(FirstShadowOnModel, FirstModelInShadow); + NumWorldMaterialBuckets = 0; + SurfaceBounds = null; + Initialized = false; + ClearShadowRenderList(); + } + + public void LevelInit(int surfCount) { + if (Initialized) + return; + Initialized = true; + + SurfaceBounds = new LinkedListNode?[surfCount]; + } + + public void LevelShutdown() { + if (!Initialized) + return; + + if (SurfaceBounds != null) + SurfaceBounds = null; + + SurfaceBoundsCache.Clear(); + Initialized = false; + } + + void SetMaterial(ref Shadow shadow, IMaterial? material, IMaterial? modelMaterial, object? bindProxy) { + shadow.Material = material; + shadow.ModelMaterial = modelMaterial; + shadow.BindProxy = bindProxy; + + material?.IncrementReferenceCount(); + modelMaterial?.IncrementReferenceCount(); + + for (int i = 0; i < SortOrderIds.Count; i++) { + if (SortOrderIds[i].MaterialEnum == material) { + SortOrderInfo used = SortOrderIds[i]; + ++used.RefCount; + SortOrderIds[i] = used; + shadow.SortOrder = (ushort)i; + return; + } + } + + shadow.SortOrder = (ushort)SortOrderIds.Count; + SortOrderIds.Add(new SortOrderInfo { MaterialEnum = material, RefCount = 1 }); + + int count = RenderQueue.Count; + while (count < SortOrderIds.Count) { + RenderQueue.Add(SHADOW_DECAL_HANDLE_INVALID); + ++count; + } + } + + void CleanupMaterial(ref Shadow shadow) { + SortOrderInfo sortOrder = SortOrderIds[shadow.SortOrder]; + --sortOrder.RefCount; + SortOrderIds[shadow.SortOrder] = sortOrder; + + shadow.Material?.DecrementReferenceCount(); + shadow.ModelMaterial?.DecrementReferenceCount(); + } + + public int InvalidShadowIndex() => BidirectionalSet.InvalidIndex; + + public ShadowHandle_t CreateShadow(IMaterial? material, IMaterial? modelMaterial, object? bindProxy, int creationFlags) => CreateShadowEx(material, modelMaterial, bindProxy, creationFlags); + + public ShadowHandle_t CreateShadowEx(IMaterial? material, IMaterial? modelMaterial, object? bindProxy, int creationFlags) { + ShadowHandle_t h = unchecked((ShadowHandle_t)Shadows.Alloc()); + + ref Shadow shadow = ref Shadows[h]; + SetMaterial(ref shadow, material, modelMaterial, bindProxy); + shadow.Flags = (ShadowCreateFlags)creationFlags; + shadow.FirstDecal = PooledLinkedList.INVALID_INDEX; + shadow.FirstModel = unchecked((uint)BidirectionalSet.InvalidIndex); + shadow.ProjectionDir = new(0, 0, 1); + shadow.Info.TexOrigin = new(0, 0); + shadow.Info.TexSize = new(1, 1); + shadow.ClipPlaneCount = 0; + shadow.Info.FalloffBias = 0; + shadow.FlashlightDepthTexture = null; + shadow.FlashlightHandle = SHADOW_HANDLE_INVALID; + + if (((ShadowCreateFlags)creationFlags & ShadowCreateFlags.Flashlight) != 0) { + shadow.FlashlightHandle = AllocFlashlightHandle(); + FlashlightStates[shadow.FlashlightHandle] = new(); + ValidFlashlightHandles.Add(shadow.FlashlightHandle); + FlashlightStates[shadow.FlashlightHandle].Info.Shadow = h; + if (r_flashlight_version2.GetInt() == 0) + AllocFlashlightMaterialBuckets(shadow.FlashlightHandle); + } + + shadow.Info.WorldToShadow = Matrix4x4.Identity; + return h; + } + + FlashlightHandle_t AllocFlashlightHandle() => ++curFlashlightHandleIdx; + + public void DestroyShadow(ShadowHandle_t handle) { + CleanupMaterial(ref Shadows[handle]); + RemoveAllSurfacesFromShadow(handle); + RemoveAllModelsFromShadow(handle); + if (Shadows[handle].FlashlightHandle != SHADOW_HANDLE_INVALID) { + FlashlightStates.Remove(Shadows[handle].FlashlightHandle); + ValidFlashlightHandles.Remove(Shadows[handle].FlashlightHandle); + } + + Shadows.Remove(handle); + } + + public void SetShadowMaterial(ShadowHandle_t handle, IMaterial? material, IMaterial? modelMaterial, object? bindProxy) { + ref Shadow shadow = ref Shadows[handle]; + if ((shadow.Material != material) || (shadow.ModelMaterial != modelMaterial) || (shadow.BindProxy != bindProxy)) { + CleanupMaterial(ref shadow); + SetMaterial(ref shadow, material, modelMaterial, bindProxy); + } + } + + public void SetShadowTexCoord(ShadowHandle_t handle, float x, float y, float w, float h) { + ref Shadow shadow = ref Shadows[handle]; + shadow.Info.TexOrigin = new(x, y); + shadow.Info.TexSize = new(w, h); + } + + public void ClearExtraClipPlanes(ShadowHandle_t h) => Shadows[h].ClipPlaneCount = 0; + + public void AddExtraClipPlane(ShadowHandle_t h, in Vector3 normal, float dist) { + ref Shadow shadow = ref Shadows[h]; + Assert(shadow.ClipPlaneCount < MAX_CLIP_PLANE_COUNT); + + shadow.ClipPlane[shadow.ClipPlaneCount] = normal; + shadow.ClipDist[shadow.ClipPlaneCount] = dist; + ++shadow.ClipPlaneCount; + } + + public ref readonly ShadowInfo_t GetInfo(ShadowHandle_t handle) => ref Shadows[handle].Info; + + Span GetCachedVerts(in ShadowVertexCache cache) { + if (cache.Count == 0) + return default; + + if (cache.Verts != null) + return cache.Verts; + + if (cache.Count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) + return SmallVertexList[cache.CachedVerts].Verts; + + return LargeVertexList[cache.CachedVerts].Verts; + } + + Span AllocateVertices(ref ShadowVertexCache cache, int count) { + cache.Verts = null; + if (count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) { + cache.Count = (ushort)count; + cache.CachedVerts = (ushort)SmallVertexList.Alloc(); + return SmallVertexList[cache.CachedVerts].Verts; + } + else if (count <= SHADOW_VERTEX_LARGE_CACHE_COUNT) { + cache.Count = (ushort)count; + cache.CachedVerts = (ushort)LargeVertexList.Alloc(); + return LargeVertexList[cache.CachedVerts].Verts; + } + + cache.Count = (ushort)count; + if (count > 0) + cache.Verts = new ShadowVertex[count]; + + cache.CachedVerts = unchecked((ushort)PooledLinkedList.INVALID_INDEX); + return cache.Verts; + } + + void FreeVertices(ref ShadowVertexCache cache) { + if (cache.Count == 0) + return; + + if (cache.Verts != null) + cache.Verts = null; + else if (cache.Count <= SHADOW_VERTEX_SMALL_CACHE_COUNT) + SmallVertexList.Remove(cache.CachedVerts); + else + LargeVertexList.Remove(cache.CachedVerts); + } + + void ClearTempCache() { + for (int i = TempVertexCache.Count; --i >= 0;) + FreeVertices(ref TempVertexCache.AsSpan()[i]); + + TempVertexCache.Clear(); + } + + bool AddDecalToShadowList(ShadowHandle_t handle, ShadowDecalHandle_t decalHandle) { + ShadowSurfaceIndex_t idx = ShadowSurfaces.Alloc(); + if (idx == PooledLinkedList.INVALID_INDEX) { + Warning("CShadowMgr::AddDecalToShadowList - overflowed m_ShadowSurfaces linked list!\n"); + return false; + } + + ShadowSurfaces[idx] = decalHandle; + if (Shadows[handle].FirstDecal != PooledLinkedList.INVALID_INDEX) + ShadowSurfaces.LinkBefore(Shadows[handle].FirstDecal, idx); + + Shadows[handle].FirstDecal = idx; + ShadowDecals[decalHandle].ShadowListIndex = idx; + + return true; + } + + ShadowDecalHandle_t AddShadowDecalToSurface(SurfaceHandle_t surfID, ShadowHandle_t handle) { + ShadowDecalHandle_t decalHandle = unchecked((ShadowDecalHandle_t)ShadowDecals.Alloc()); + if (decalHandle == SHADOW_DECAL_HANDLE_INVALID) { + Warning("CShadowMgr::AddShadowDecalToSurface - overflowed m_ShadowDecals linked list!\n"); + return decalHandle; + } + + ref ShadowDecal decal = ref ShadowDecals[decalHandle]; + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + + decal.SurfID = surfID; + if (ModelLoader.MSurf_ShadowDecals(ref surface) != SHADOW_DECAL_HANDLE_INVALID) + ShadowDecals.LinkBefore(ModelLoader.MSurf_ShadowDecals(ref surface), decalHandle); + ModelLoader.MSurf_ShadowDecals(ref surface) = decalHandle; + + if (!ModelLoader.SurfaceHasDispInfo(ref surface)) + decal.DispShadow = DISP_SHADOW_HANDLE_INVALID; + else + decal.DispShadow = surface.DispInfo!.AddShadowDecal(handle); + + decal.Shadow = handle; + decal.ShadowVerts = unchecked((ushort)PooledLinkedList.INVALID_INDEX); + decal.NextRender = SHADOW_DECAL_HANDLE_INVALID; + decal.ShadowListIndex = PooledLinkedList.INVALID_INDEX; + + if (!AddDecalToShadowList(handle, decalHandle)) { + ShadowDecals.Remove(decalHandle); + decalHandle = SHADOW_DECAL_HANDLE_INVALID; + } + + return decalHandle; + } + + void RemoveDecalFromShadowList(ShadowHandle_t handle, ShadowDecalHandle_t decalHandle) { + ShadowSurfaceIndex_t idx = ShadowDecals[decalHandle].ShadowListIndex; + + ref ShadowSurfaceIndex_t decal = ref Shadows[handle].FirstDecal; + if (decal == idx) + decal = ShadowSurfaces.Next(idx); + + ShadowSurfaces.Remove(idx); + + ShadowDecals[decalHandle].ShadowListIndex = PooledLinkedList.INVALID_INDEX; + } + + void RemoveShadowDecalFromSurface(SurfaceHandle_t surfID, ShadowDecalHandle_t decalHandle) { + ref ShadowDecal decal = ref ShadowDecals[decalHandle]; + if (decal.ShadowVerts != unchecked((ushort)PooledLinkedList.INVALID_INDEX)) { + FreeVertices(ref VertexCache[decal.ShadowVerts]); + VertexCache.Remove(decal.ShadowVerts); + decal.ShadowVerts = unchecked((ushort)PooledLinkedList.INVALID_INDEX); + } + + if (decal.DispShadow != DISP_SHADOW_HANDLE_INVALID) + ModelLoader.SurfaceHandleFromIndex(decal.SurfID).DispInfo!.RemoveShadowDecal(decal.DispShadow); + + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + if (ModelLoader.MSurf_ShadowDecals(ref surface) == decalHandle) + ModelLoader.MSurf_ShadowDecals(ref surface) = unchecked((ShadowDecalHandle_t)ShadowDecals.Next(decalHandle)); + + RemoveDecalFromShadowList(decal.Shadow, decalHandle); + + ShadowDecals.Remove(decalHandle); + } + + void ComputeSurfaceBounds(ref SurfaceBounds_t bounds, SurfaceHandle_t surfID) { + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + + bounds.Center = new(); + bounds.Mins = MathLib.ReplicateX4(float.MaxValue); + bounds.Maxs = MathLib.ReplicateX4(-float.MaxValue); + int count = ModelLoader.MSurf_VertCount(ref surface); + for (int i = 0; i < count; ++i) { + int vertIndex = host_state.WorldBrush!.VertIndices![ModelLoader.MSurf_FirstVertIndex(ref surface) + i]; + ref Vector3 position = ref host_state.WorldBrush.Vertexes![vertIndex].Position; + bounds.Center += position; + + fltx4 pos4 = MathLib.LoadFloat3(in position); + bounds.Mins = MathLib.MinSIMD(pos4, bounds.Mins); + bounds.Maxs = MathLib.MaxSIMD(pos4, bounds.Maxs); + } + + fltx4 eps = MathLib.ReplicateX4(1e-3f); + bounds.Mins = MathLib.SetWToZeroSIMD(MathLib.SubSIMD(bounds.Mins, eps)); + bounds.Maxs = MathLib.SetWToZeroSIMD(MathLib.AddSIMD(bounds.Maxs, eps)); + bounds.Center /= count; + + bounds.Radius = 0.0f; + for (int i = 0; i < count; ++i) { + int vertIndex = host_state.WorldBrush!.VertIndices![ModelLoader.MSurf_FirstVertIndex(ref surface) + i]; + ref Vector3 position = ref host_state.WorldBrush.Vertexes![vertIndex].Position; + float distSq = position.DistToSqr(bounds.Center); + if (distSq > bounds.Radius) + bounds.Radius = distSq; + } + bounds.Radius = MathF.Sqrt(bounds.Radius); + } + + ref readonly SurfaceBounds_t GetSurfaceBounds(SurfaceHandle_t surfID) { + int surfaceIndex = ModelLoader.MSurf_Index(ref ModelLoader.SurfaceHandleFromIndex(surfID)); + + if (SurfaceBounds![surfaceIndex] != null) + return ref SurfaceBounds[surfaceIndex]!.ValueRef; + + LinkedListNode node; + if (SurfaceBoundsCache.Count >= SURFACE_BOUNDS_CACHE_COUNT) { + node = SurfaceBoundsCache.Last!; + SurfaceBoundsCache.Remove(node); + SurfaceBoundsCache.AddFirst(node); + SurfaceBounds[node.ValueRef.SurfaceIndex] = null; + } + else + node = SurfaceBoundsCache.AddFirst(default(SurfaceBounds_t)); + SurfaceBounds[surfaceIndex] = node; + + ref SurfaceBounds_t bounds = ref node.ValueRef; + bounds.SurfaceIndex = surfaceIndex; + ComputeSurfaceBounds(ref bounds, surfID); + return ref bounds; + } + + bool IsShadowNearSurface(ShadowHandle_t h, SurfaceHandle_t surfID, Matrix4x4? modelToWorld, Matrix4x4? worldToModel) { + ref readonly Shadow shadow = ref Shadows[h]; + ref readonly SurfaceBounds_t bounds = ref GetSurfaceBounds(surfID); + Vector3 surfCenter; + if (modelToWorld == null) + surfCenter = bounds.Center; + else + MathLib.Vector3DMultiplyPosition(modelToWorld.Value, bounds.Center, out surfCenter); + + MathLib.VectorSubtract(shadow.SphereCenter, surfCenter, out Vector3 delta); + float distSqr = delta.LengthSquared(); + float minDistSqr = bounds.Radius + shadow.SphereRadius; + minDistSqr *= minDistSqr; + if (distSqr >= minDistSqr) + return false; + + Vector3 boundsMins = new(bounds.Mins[0], bounds.Mins[1], bounds.Mins[2]); + Vector3 boundsMaxs = new(bounds.Maxs[0], bounds.Maxs[1], bounds.Maxs[2]); + + if (modelToWorld == null) + return CollisionUtils.IsBoxIntersectingRay(boundsMins, boundsMaxs, shadow.Ray); + + Ray transformedRay = default; + MathLib.Vector3DMultiplyPosition(worldToModel!.Value, shadow.Ray.Start, out transformedRay.Start); + MathLib.Vector3DMultiply(worldToModel.Value, shadow.Ray.Delta, out transformedRay.Delta); + transformedRay.StartOffset = shadow.Ray.StartOffset; + transformedRay.Extents = shadow.Ray.Extents; + transformedRay.IsRay = shadow.Ray.IsRay; + transformedRay.IsSwept = shadow.Ray.IsSwept; + return CollisionUtils.IsBoxIntersectingRay(boundsMins, boundsMaxs, transformedRay); + } + + void AddSurfaceToFlashlightMaterialBuckets(ShadowHandle_t handle, SurfaceHandle_t surfID) { + Assert((Shadows[handle].Flags & ShadowCreateFlags.Flashlight) != 0); + + FlashlightHandle_t flashlightID = Shadows[handle].FlashlightHandle; + Assert(flashlightID != SHADOW_HANDLE_INVALID); + + FlashlightStates[flashlightID].Info.MaterialBuckets.AddElement(ModelLoader.MSurf_MaterialSortID(ref ModelLoader.SurfaceHandleFromIndex(surfID)), surfID); + } + + void AddSurfaceToShadow(ShadowHandle_t handle, SurfaceHandle_t surfID) { + bool isFlashlight = (Shadows[handle].Flags & ShadowCreateFlags.Flashlight) != 0; + if (!isFlashlight && (ModelLoader.MSurf_Flags(ref ModelLoader.SurfaceHandleFromIndex(surfID)) & (SurfDraw.Trans | SurfDraw.AlphaTest | SurfDraw.NoShadows)) != 0) + return; + + AddShadowDecalToSurface(surfID, handle); + } + + void RemoveSurfaceFromShadow(ShadowHandle_t handle, SurfaceHandle_t surfID) => throw new NotImplementedException(); + + void RemoveAllSurfacesFromShadow(ShadowHandle_t handle) { + ShadowSurfaceIndex_t i = Shadows[handle].FirstDecal; + while (i != PooledLinkedList.INVALID_INDEX) { + ShadowDecalHandle_t decalHandle = ShadowSurfaces[i]; + ShadowSurfaceIndex_t next = ShadowSurfaces.Next(i); + + RemoveShadowDecalFromSurface(ShadowDecals[decalHandle].SurfID, decalHandle); + + i = next; + } + + Shadows[handle].FirstDecal = PooledLinkedList.INVALID_INDEX; + } + + void RemoveAllShadowsFromSurface(SurfaceHandle_t surfID) { + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + ShadowDecalHandle_t dh = ModelLoader.MSurf_ShadowDecals(ref surface); + while (dh != SHADOW_DECAL_HANDLE_INVALID) { + ShadowDecalHandle_t next = unchecked((ShadowDecalHandle_t)ShadowDecals.Next(dh)); + + RemoveShadowDecalFromSurface(ShadowDecals[dh].SurfID, dh); + + dh = next; + } + + ModelLoader.MSurf_ShadowDecals(ref surface) = SHADOW_DECAL_HANDLE_INVALID; + } + + public void AddShadowToModel(ShadowHandle_t handle, ModelInstanceHandle_t model) { + if (model == MODEL_INSTANCE_INVALID) + return; + + if (r_flashlightrender.GetBool() == false) + return; + + ShadowsOnModels.AddElementToBucket(model, handle); + } + + public void RemoveAllShadowsFromModel(ModelInstanceHandle_t model) { + if (model != MODEL_INSTANCE_INVALID) { + ShadowsOnModels.RemoveBucket(model); + + foreach (FlashlightHandle_t i in ValidFlashlightHandles) { + ref FlashlightInfo info = ref FlashlightStates[i].Info; + + for (int j = 0; j < info.Renderables.Count; j++) { + if (info.Renderables[j]!.GetModelInstance() == model) { + info.Renderables.RemoveAt(j); + break; + } + } + } + } + } + + void RemoveAllModelsFromShadow(ShadowHandle_t handle) { + ShadowsOnModels.RemoveElement(handle); + + foreach (FlashlightHandle_t i in ValidFlashlightHandles) { + ref FlashlightInfo info = ref FlashlightStates[i].Info; + + if (info.Shadow == handle) + info.Renderables.Clear(); + } + } + + public void SetModelShadowState(ModelInstanceHandle_t instance) => throw new NotImplementedException(); + + public bool ModelHasShadows(ModelInstanceHandle_t instance) => throw new NotImplementedException(); + + void ApplyShadowToSurface(ref ShadowBuildInfo build, SurfaceHandle_t surfID) { + AddSurfaceToShadow(build.Shadow, surfID); + } + + void ApplyShadowToDisplacement(ref ShadowBuildInfo build, IDispInfo? dispInfo, bool isFlashlight) { + if (!isFlashlight && (ModelLoader.MSurf_Flags(ref dispInfo!.GetParent()) & SurfDraw.NoShadows) != 0) + return; + + dispInfo!.GetBoundingBox(out Vector3 bbMin, out Vector3 bbMax); + if (!isFlashlight) { + if (!CollisionUtils.IsBoxIntersectingSphere(bbMin, bbMax, build.SphereCenter, build.SphereRadius)) + return; + } + else { + if (MathLib.R_CullBox(bbMin, bbMax, GetFlashlightFrustum(build.Shadow))) + return; + } + + SurfaceHandle_t surfID = ModelLoader.MSurf_Index(ref dispInfo.GetParent()); + + if (dispInfo.GetParent().DynamicShadowsEnabled == false && !isFlashlight) + return; + + AddSurfaceToShadow(build.Shadow, surfID); + } + + public void EnableShadow(ShadowHandle_t handle, bool enable) { + if (!enable) { + RemoveAllSurfacesFromShadow(handle); + RemoveAllModelsFromShadow(handle); + + Shadows[handle].Flags |= SHADOW_DISABLED; + } + else + Shadows[handle].Flags &= ~SHADOW_DISABLED; + } + + public void SetFalloffBias(ShadowHandle_t shadow, byte bias) => Shadows[shadow].Info.FalloffBias = bias; + + public void ProjectShadow(ShadowHandle_t handle, in Vector3 origin, in Vector3 projectionDir, in Matrix4x4 worldToShadow, in Vector2 size, ReadOnlySpan leafList, float maxHeight, float falloffOffset, float falloffAmount, in Vector3 casterOrigin) { + RemoveAllSurfacesFromShadow(handle); + RemoveAllModelsFromShadow(handle); + + ref Shadow shadow = ref Shadows[handle]; + if ((shadow.Flags & SHADOW_DISABLED) != 0) + return; + + if (r_shadows.GetInt() == 0) + return; + + shadow.Info.FalloffOffset = falloffOffset; + shadow.ProjectionDir = projectionDir; + + shadow.Info.MaxDist = maxHeight; + shadow.Info.FalloffAmount = falloffAmount; + shadow.Info.WorldToShadow = worldToShadow; + + float radius = MathF.Sqrt(size.X * size.X + size.Y * size.Y) * 0.5f; + MathLib.VectorMA(origin, 0.5f * maxHeight, projectionDir, out shadow.SphereCenter); + shadow.SphereRadius = 0.5f * maxHeight + radius; + + Vector3 mins = new(-radius, -radius, -radius); + Vector3 maxs = new(radius, radius, radius); + MathLib.VectorMA(origin, maxHeight, projectionDir, out Vector3 endPoint); + shadow.Ray.Init(origin, endPoint, mins, maxs); + + if (leafList.Length == 0) + return; + + ++r_surfacevisframe; + + DispInfo.DispInfo_ClearAllTags(host_state.WorldBrush!.DispInfos); + + EnumerateBuild = default; + EnumerateBuild.Shadow = handle; + EnumerateBuild.RayStart = origin; + EnumerateBuild.Vis = null; + EnumerateBuild.SphereCenter = shadow.SphereCenter; + EnumerateBuild.SphereRadius = shadow.SphereRadius; + EnumerateBuild.ProjectionDirection = projectionDir; + + for (int i = 0; i < leafList.Length; ++i) + EnumerateLeaf(leafList[i], 0); + } + + public void ProjectFlashlight(ShadowHandle_t handle, in Matrix4x4 worldToShadow, ReadOnlySpan leafList) { + ref Shadow shadow = ref Shadows[handle]; + + if (r_flashlight_version2.GetInt() == 0) { + RemoveAllSurfacesFromShadow(handle); + RemoveAllModelsFromShadow(handle); + + FlashlightStates[shadow.FlashlightHandle].Info.OccluderBuckets.Flush(); + } + + if ((Shadows[handle].Flags & SHADOW_DISABLED) != 0) + return; + + if (r_shadows.GetInt() == 0) + return; + + shadow.Info.WorldToShadow = worldToShadow; + + MathLib.MatrixInverseGeneral(in shadow.Info.WorldToShadow, out Matrix4x4 shadowToWorld); + + Assert((shadow.Flags & (ShadowCreateFlags)ShadowFlags.Flashlight) != 0); + Frustum_t frustum = FlashlightStates[shadow.FlashlightHandle].Info.Frustum; + MathLib.FrustumPlanesFromMatrix(in shadowToWorld, frustum); + MathLib.CalculateSphereFromProjectionMatrixInverse(in shadowToWorld, out shadow.SphereCenter, out shadow.SphereRadius); + + if (leafList.Length == 0) + return; + + ++r_surfacevisframe; + + DispInfo.DispInfo_ClearAllTags(host_state.WorldBrush!.DispInfos); + + EnumerateBuild = default; + EnumerateBuild.Shadow = handle; + EnumerateBuild.RayStart = FlashlightStates[shadow.FlashlightHandle].Info.FlashlightState.LightOrigin; + EnumerateBuild.Vis = null; + EnumerateBuild.SphereCenter = shadow.SphereCenter; + EnumerateBuild.SphereRadius = shadow.SphereRadius; + + if (r_flashlightdrawfrustumbbox.GetBool()) { + MathLib.CalculateAABBFromProjectionMatrixInverse(in shadowToWorld, out Vector3 mins, out Vector3 maxs); + debugoverlay?.AddBoxOverlay(new Vector3(0.0f, 0.0f, 0.0f), in mins, in maxs, new QAngle(0, 0, 0), + 0, 0, 255, 100, 0.0f); + } + + for (int i = 0; i < leafList.Length; ++i) + EnumerateLeaf(leafList[i], 0); + } + + void ApplyFlashlightToLeaf(in Shadow shadow, BSPMLeaf? leaf, ref ShadowBuildInfo build) { + MathLib.VectorAdd(leaf!.Center, leaf.HalfDiagonal, out Vector3 leafMaxs); + MathLib.VectorSubtract(leaf.Center, leaf.HalfDiagonal, out Vector3 leafMins); + + if (MathLib.R_CullBox(in leafMins, in leafMaxs, GetFlashlightFrustum(build.Shadow))) + return; + + bool cullDepth = r_flashlightculldepth.GetBool(); + + for (int i = 0; i < leaf.NumMarkSurfaces; i++) { + SurfaceHandle_t surfID = host_state.WorldBrush!.MarkSurfaces![leaf.FirstMarkSurface + i]; + + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + + if (ModelLoader.MSurf_VisFrame(ref surface) == r_surfacevisframe) + continue; + + ModelLoader.MSurf_VisFrame(ref surface) = r_surfacevisframe; + Assert(surface.DispInfo == null); + + int vertIndex = host_state.WorldBrush.VertIndices![ModelLoader.MSurf_FirstVertIndex(ref surface)]; + ref Vector3 worldPos = ref host_state.WorldBrush.Vertexes![vertIndex].Position; + + MathLib.VectorSubtract(worldPos, build.RayStart, out Vector3 lookdir); + MathLib.VectorNormalize(ref lookdir); + + ref CollisionPlane surfPlane = ref ModelLoader.MSurf_Plane(ref surface); + + float dist = MathLib.DotProduct(surfPlane.Normal, build.SphereCenter) - surfPlane.Dist; + if (MathF.Abs(dist) >= build.SphereRadius) + continue; + + ApplyShadowToSurface(ref build, surfID); + + if (cullDepth) { + if ((ModelLoader.MSurf_Flags(ref surface) & SurfDraw.NoCull) == 0) { + if (MathLib.DotProduct(surfPlane.Normal, lookdir) < BACKFACE_EPSILON) + continue; + } + else { + float dot = MathLib.DotProduct(surfPlane.Normal, lookdir); + if (MathF.Abs(dot) < BACKFACE_EPSILON) + continue; + } + } + + FlashlightInfoBox flashlightInfo = FlashlightStates[shadow.FlashlightHandle]; + flashlightInfo.Info.OccluderBuckets.AddElement(ModelLoader.MSurf_MaterialSortID(ref surface), surfID); + } + } + + void ApplyShadowToLeaf(in Shadow shadow, BSPMLeaf leaf, ref ShadowBuildInfo build) { + for (int i = 0; i < leaf.NumMarkSurfaces; i++) { + SurfaceHandle_t surfID = host_state.WorldBrush!.MarkSurfaces![leaf.FirstMarkSurface + i]; + + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(surfID); + + if (ModelLoader.MSurf_VisFrame(ref surface) == r_surfacevisframe) + continue; + + ModelLoader.MSurf_VisFrame(ref surface) = r_surfacevisframe; + Assert(surface.DispInfo == null); + + if (!surface.DynamicShadowsEnabled) + continue; + + ref CollisionPlane surfPlane = ref ModelLoader.MSurf_Plane(ref surface); + bool inFront; + if ((ModelLoader.MSurf_Flags(ref surface) & SurfDraw.NoCull) == 0) { + if (MathLib.DotProduct(surfPlane.Normal, build.ProjectionDirection) > -BACKFACE_EPSILON) + continue; + + inFront = true; + } + else { + float dot = MathLib.DotProduct(surfPlane.Normal, build.ProjectionDirection); + if (MathF.Abs(dot) < BACKFACE_EPSILON) + continue; + + inFront = dot < 0; + } + + if (inFront) { + if (MathLib.DotProduct(surfPlane.Normal, build.RayStart) < surfPlane.Dist) + continue; + } + else { + if (MathLib.DotProduct(surfPlane.Normal, build.RayStart) > surfPlane.Dist) + continue; + } + + float dist = MathLib.DotProduct(surfPlane.Normal, build.SphereCenter) - surfPlane.Dist; + if (MathF.Abs(dist) >= build.SphereRadius) + continue; + + ApplyShadowToSurface(ref build, surfID); + } + } + + ShadowBuildInfo EnumerateBuild; + + public bool EnumerateLeaf(int leaf, nint context) { + ref ShadowBuildInfo build = ref EnumerateBuild; + + if (build.Vis != null) { + int cluster = CM.LeafCluster(leaf); + if ((build.Vis[cluster >> 3] & (1 << (cluster & 7))) == 0) + return true; + } + + ref readonly Shadow shadow = ref Shadows[build.Shadow]; + + BSPMLeaf leafData = host_state.WorldBrush!.Leafs![leaf]; + + bool isFlashlight; + if ((shadow.Flags & ShadowCreateFlags.Flashlight) != 0) { + isFlashlight = true; + ApplyFlashlightToLeaf(in shadow, leafData, ref build); + } + else { + isFlashlight = false; + ApplyShadowToLeaf(in shadow, leafData, ref build); + } + + for (int i = 0; i < leafData.DispCount; i++) { + IDispInfo? dispInfo = DispInfo.MLeaf_Disaplcement(leafData, i); + + if (dispInfo!.GetTag()) + continue; + + dispInfo.SetTag(); + + ApplyShadowToDisplacement(ref build, dispInfo, isFlashlight); + } + + return true; + } + + public void AddShadowToBrushModel(ShadowHandle_t handle, Model? model, in Vector3 origin, in QAngle angles) { + if (r_shadows.GetInt() == 0) + return; + + ref Shadow shadow = ref Shadows[handle]; + + Vector3 shadowDirInModelSpace = default; + bool isFlashlight = (shadow.Flags & ShadowCreateFlags.Flashlight) != 0; + if (!isFlashlight) { + MathLib.AngleIMatrix(angles, out Matrix3x4 worldToModel); + MathLib.VectorRotate(shadow.ProjectionDir, worldToModel, out shadowDirInModelSpace); + } + + for (int i = 0; i < model!.Brush.NumModelSurfaces; ++i) { + SurfaceHandle_t surfID = model.Brush.FirstModelSurface + i; + ref BSPMSurface2 surf = ref ModelLoader.SurfaceHandleFromIndex(surfID, model.Brush.Shared); + + SurfDraw flags = ModelLoader.MSurf_Flags(ref surf); + if ((flags & SurfDraw.NoDraw) != 0) + continue; + + if (!isFlashlight) { + if ((flags & SurfDraw.NoCull) == 0) { + ref CollisionPlane surfPlane = ref ModelLoader.MSurf_Plane(ref surf); + float dot = MathLib.DotProduct(shadowDirInModelSpace, surfPlane.Normal); + if (dot > 0) + continue; + } + } + + AddSurfaceToShadow(handle, surfID); + } + } + + public void RemoveAllShadowsFromBrushModel(Model? model) { + for (int i = 0; i < model!.Brush.NumModelSurfaces; ++i) + RemoveAllShadowsFromSurface(model.Brush.FirstModelSurface + i); + } + + public void AddShadowsOnSurfaceToRenderList(ShadowDecalHandle_t decalHandle) { + if (r_shadows.GetInt() == 0) + return; + + while (decalHandle != SHADOW_DECAL_HANDLE_INVALID) { + ref ShadowDecal shadowDecal = ref ShadowDecals[decalHandle]; + if ((Shadows[shadowDecal.Shadow].Flags & ShadowCreateFlags.Flashlight) != 0) { + AddSurfaceToFlashlightMaterialBuckets(shadowDecal.Shadow, shadowDecal.SurfID); + + ++DecalsToRender; + } + else if (r_shadows_gamecontrol.GetInt() != 0) { + int sortOrder = Shadows[shadowDecal.Shadow].SortOrder; + ShadowDecals[decalHandle].NextRender = RenderQueue[sortOrder]; + RenderQueue[sortOrder] = decalHandle; + + ++DecalsToRender; + } + decalHandle = unchecked((ShadowDecalHandle_t)ShadowDecals.Next(decalHandle)); + } + } + + public void ClearShadowRenderList() { + if (RenderQueue.Count > 0) + memset(RenderQueue.AsSpan(), SHADOW_DECAL_HANDLE_INVALID); + + DecalsToRender = 0; + ClearAllFlashlightMaterialBuckets(); + } + + public void RenderShadows(Matrix4x4? modelToWorld = null) { + using MatRenderContextPtr renderContext = new(materials); + int i; + for (i = 0; i < RenderQueue.Count; ++i) { + if (RenderQueue[i] != SHADOW_DECAL_HANDLE_INVALID) + RenderShadowList(renderContext, RenderQueue[i], modelToWorld); + } + } + + public void RenderProjectedTextures(Matrix4x4? modelToWorld = null) => throw new NotImplementedException(); + + bool ProjectVerticesIntoShadowSpace(in Matrix4x4 modelToShadow, float maxDist, ReadOnlySpan position, ref ShadowClipState clip) { + bool insideVolume = false; + + for (int i = 0; i < position.Length; ++i) { + clip.TempVertices[i].Position = position[i]; + + MathLib.Vector3DMultiplyPosition(in modelToShadow, in position[i], out clip.TempVertices[i].ShadowSpaceTexCoord); + + clip.ClipVertices[0, i] = i; + + if (clip.TempVertices[i].ShadowSpaceTexCoord.Z < maxDist) + insideVolume = true; + } + + clip.TempCount = clip.ClipCount = position.Length; + clip.CurrVert = 0; + + return insideVolume; + } + + static ShadowClipState clip = new(); + + int ProjectAndClipVertices(in Shadow shadow, in Matrix4x4 worldToShadow, Matrix4x4? worldToModel, ReadOnlySpan position, out ShadowVertex[]? outVertex) { + outVertex = null; + if (!ProjectVerticesIntoShadowSpace(in worldToShadow, shadow.Info.MaxDist, position, ref clip)) + return 0; + + ShadowClipState.ShadowClip(ref clip); + ShadowClipState.ShadowClip(ref clip); + ShadowClipState.ShadowClip(ref clip); + ShadowClipState.ShadowClip(ref clip); + ShadowClipState.ShadowClip(ref clip); + + for (int i = 0; i < shadow.ClipPlaneCount; ++i) { + if (worldToModel != null) { + CollisionPlane worldPlane = default; + worldPlane.Normal = shadow.ClipPlane[i]; + worldPlane.Dist = shadow.ClipDist[i]; + MathLib.MatrixTransformPlane(worldToModel.Value, in worldPlane, out CollisionPlane modelPlane); + ClipPlane.SetPlane(modelPlane.Normal, modelPlane.Dist); + } + else + ClipPlane.SetPlane(shadow.ClipPlane[i], shadow.ClipDist[i]); + + ShadowClipState.ShadowClip(ref clip); + } + + if (clip.ClipCount < 3) + return 0; + + outVertex = new ShadowVertex[clip.ClipCount]; + for (int i = 0; i < clip.ClipCount; ++i) + outVertex[i] = clip.TempVertices[clip.ClipVertices[clip.CurrVert, i]]; + + return clip.ClipCount; + } + + public int ProjectAndClipVertices(ShadowHandle_t handle, ReadOnlySpan position, out ShadowVertex[]? outVertex) => + ProjectAndClipVertices(in Shadows[handle], in Shadows[handle].Info.WorldToShadow, null, position, out outVertex); + + void CopyClippedVertices(int count, ReadOnlySpan srcVert, Span dstVert, in Vector3 toAdd) { + for (int i = 0; i < count; ++i) { + dstVert[i].Position = srcVert[i].Position + toAdd; + dstVert[i].ShadowSpaceTexCoord = srcVert[i].ShadowSpaceTexCoord; + + Assert(srcVert[i].ShadowSpaceTexCoord.X >= -1e-3f); + Assert(srcVert[i].ShadowSpaceTexCoord.X - 1.0f <= 1e-3f); + Assert(srcVert[i].ShadowSpaceTexCoord.Y >= -1e-3f); + Assert(srcVert[i].ShadowSpaceTexCoord.Y - 1.0f <= 1e-3f); + } + } + + bool ShouldCacheVertices(in ShadowDecal decal) => (Shadows[decal.Shadow].Flags & ShadowCreateFlags.CacheVerts) != 0; + + bool GenerateDispShadowRenderInfo(MatRenderContextPtr renderContext, ref ShadowDecal decal, ref ShadowRenderInfo info) { + if (info.DispCount >= MAX_SHADOW_DECAL_CACHE_COUNT) { + info.DispCount = MAX_SHADOW_DECAL_CACHE_COUNT; + return true; + } + + if (!ModelLoader.SurfaceHandleFromIndex(decal.SurfID).DispInfo!.ComputeShadowFragments(decal.DispShadow, out int v, out int i)) + return false; + + if ((info.VertexCount + v >= info.MaxVertices) || (info.IndexCount + i >= info.MaxIndices)) + return true; + + info.VertexCount += v; + info.IndexCount += i; + info.DispCache![info.DispCount++] = decal.DispShadow; + return true; + } + + bool GenerateNormalShadowRenderInfo(MatRenderContextPtr renderContext, ref ShadowDecal decal, ref ShadowRenderInfo info) { + if (info.Count >= MAX_SHADOW_DECAL_CACHE_COUNT) { + info.Count = MAX_SHADOW_DECAL_CACHE_COUNT; + return true; + } + + int vertexCacheIndex; + bool temp = false; + if (decal.ShadowVerts != unchecked((ushort)PooledLinkedList.INVALID_INDEX)) { + info.Cache![info.Count] = decal.ShadowVerts; + vertexCacheIndex = decal.ShadowVerts; + } + else { + bool isNear = IsShadowNearSurface(decal.Shadow, decal.SurfID, info.ModelToWorld, info.WorldToModel); + if (!isNear) + return false; + + bool shouldCacheVerts = ShouldCacheVertices(in decal); + if (shouldCacheVerts) { + decal.ShadowVerts = (ushort)VertexCache.Alloc(); + info.Cache![info.Count] = decal.ShadowVerts; + vertexCacheIndex = decal.ShadowVerts; + } + else { + TempVertexCache.Add(default); + vertexCacheIndex = TempVertexCache.Count - 1; + info.Cache![info.Count] = -vertexCacheIndex - 1; + temp = true; + Assert(info.Cache[info.Count] < 0); + } + + if (!ComputeShadowVertices(ref decal, info.ModelToWorld, info.WorldToModel, ref temp ? ref TempVertexCache.AsSpan()[vertexCacheIndex] : ref VertexCache[vertexCacheIndex])) + return false; + } + + ref ShadowVertexCache vertexCache = ref temp ? ref TempVertexCache.AsSpan()[vertexCacheIndex] : ref VertexCache[vertexCacheIndex]; + + int additionalIndices = 3 * (vertexCache.Count - 2); + if ((info.VertexCount + vertexCache.Count >= info.MaxVertices) || + (info.IndexCount + additionalIndices >= info.MaxIndices)) + return true; + + info.VertexCount += vertexCache.Count; + info.IndexCount += additionalIndices; + ++info.Count; + + return true; + } + + bool ComputeShadowVertices(ref ShadowDecal decal, Matrix4x4? modelToWorld, Matrix4x4? worldToModel, ref ShadowVertexCache vertexCache) { + ref BSPMSurface2 surface = ref ModelLoader.SurfaceHandleFromIndex(decal.SurfID); + int vertCount = ModelLoader.MSurf_VertCount(ref surface); + Span vecs = stackalloc Vector3[vertCount]; + for (int i = 0; i < vertCount; ++i) { + int vertIndex = host_state.WorldBrush!.VertIndices![ModelLoader.MSurf_FirstVertIndex(ref surface) + i]; + vecs[i] = host_state.WorldBrush.Vertexes![vertIndex].Position; + } + + Matrix4x4 modelToShadow = Shadows[decal.Shadow].Info.WorldToShadow; + + if (modelToWorld != null) { + Matrix4x4 modelToWorldValue = modelToWorld.Value; + MathLib.MatrixMultiply(in modelToShadow, in modelToWorldValue, out modelToShadow); + } + else + worldToModel = null; + + int clipCount = ProjectAndClipVertices(in Shadows[decal.Shadow], in modelToShadow, worldToModel, vecs, out ShadowVertex[]? srcVert); + if (clipCount == 0) { + vertexCache.Count = 0; + return false; + } + + Span dstVert = AllocateVertices(ref vertexCache, clipCount); + Assert(!dstVert.IsEmpty); + + ref Vector3 normal = ref ModelLoader.MSurf_Plane(ref surface).Normal; + CopyClippedVertices(clipCount, srcVert!, dstVert, normal * OVERLAY_AVOID_FLICKER_NORMAL_OFFSET); + + vertexCache.Shadow = decal.Shadow; + + return true; + } + + void GenerateShadowRenderInfo(MatRenderContextPtr renderContext, ShadowDecalHandle_t decalHandle, ref ShadowRenderInfo info) { + info.VertexCount = 0; + info.IndexCount = 0; + info.Count = 0; + info.DispCount = 0; + + ShadowDecalHandle_t next; + for (; decalHandle != SHADOW_DECAL_HANDLE_INVALID; decalHandle = next) { + ref ShadowDecal decal = ref ShadowDecals[decalHandle]; + next = ShadowDecals[decalHandle].NextRender; + + ref Shadow shadow = ref Shadows[decal.Shadow]; + if (shadow.Info.FalloffBias == 255) + continue; + + bool keepShadow; + if (decal.DispShadow != DISP_SHADOW_HANDLE_INVALID) + keepShadow = GenerateDispShadowRenderInfo(renderContext, ref decal, ref info); + else + keepShadow = GenerateNormalShadowRenderInfo(renderContext, ref decal, ref info); + + if (!keepShadow && ShouldCacheVertices(in decal)) + RemoveShadowDecalFromSurface(decal.SurfID, decalHandle); + } + } + + public void ComputeRenderInfo(ref ShadowDecalRenderInfo info, ShadowHandle_t handle) { + ref ShadowInfo_t i = ref Shadows[handle].Info; + info.TexOrigin = i.TexOrigin; + info.TexSize = i.TexSize; + info.FalloffOffset = i.FalloffOffset; + info.FalloffAmount = i.FalloffAmount; + info.FalloffBias = i.FalloffBias; + + float falloffDist = i.MaxDist - i.FalloffOffset; + info.OOZFalloffDist = (falloffDist > 0.0f) ? 1.0f / falloffDist : 1.0f; + } + + int AddNormalShadowsToMeshBuilder(ref MeshBuilder meshBuilder, ref ShadowRenderInfo info) { + ShadowDecalRenderInfo shadow = default; + int baseIndex = 0; + for (int i = 0; i < info.Count; ++i) { + ref ShadowVertexCache vertexCache = ref (info.Cache![i] < 0 + ? ref TempVertexCache.AsSpan()[(int)(-info.Cache[i] - 1)] + : ref VertexCache[(int)info.Cache[i]]); + + Span verts = GetCachedVerts(in vertexCache); + g_ShadowMgr.ComputeRenderInfo(ref shadow, vertexCache.Shadow); + + int j; + byte c; + Vector2 texCoord; + int vCount = vertexCache.Count - 2; + if (vCount <= 0) + continue; + + int vert = 0; + for (j = 0; j < vCount; ++j, ++vert) { + MathLib.Vector2DMultiply(new Vector2(verts[vert].ShadowSpaceTexCoord.X, verts[vert].ShadowSpaceTexCoord.Y), shadow.TexSize, out texCoord); + texCoord += shadow.TexOrigin; + c = ((IShadowMgrInternal)this).ComputeDarkness(verts[vert].ShadowSpaceTexCoord.Z, in shadow); + + meshBuilder.Position3fv(verts[vert].Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex((ushort)baseIndex); + meshBuilder.FastIndex((ushort)(j + baseIndex + 1)); + meshBuilder.FastIndex((ushort)(j + baseIndex + 2)); + } + + MathLib.Vector2DMultiply(new Vector2(verts[vert].ShadowSpaceTexCoord.X, verts[vert].ShadowSpaceTexCoord.Y), shadow.TexSize, out texCoord); + texCoord += shadow.TexOrigin; + c = ((IShadowMgrInternal)this).ComputeDarkness(verts[vert].ShadowSpaceTexCoord.Z, in shadow); + meshBuilder.Position3fv(verts[vert].Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + ++vert; + + MathLib.Vector2DMultiply(new Vector2(verts[vert].ShadowSpaceTexCoord.X, verts[vert].ShadowSpaceTexCoord.Y), shadow.TexSize, out texCoord); + texCoord += shadow.TexOrigin; + c = ((IShadowMgrInternal)this).ComputeDarkness(verts[vert].ShadowSpaceTexCoord.Z, in shadow); + meshBuilder.Position3fv(verts[vert].Position); + meshBuilder.Color4ub(c, c, c, c); + meshBuilder.TexCoord2fv(0, texCoord); + meshBuilder.AdvanceVertex(); + + baseIndex += vCount + 2; + } + + return baseIndex; + } + + int AddDisplacementShadowsToMeshBuilder(ref MeshBuilder meshBuilder, ref ShadowRenderInfo info, int baseIndex) { + if (!DispInfo.r_DrawDisp.GetBool()) + return baseIndex; + + for (int i = 0; i < info.DispCount; ++i) + baseIndex = DispInfo.DispInfo_AddShadowsToMeshBuilder(ref meshBuilder, info.DispCache![i], baseIndex); + + return baseIndex; + } + + void RenderDebuggingInfo(in ShadowRenderInfo info, ShadowDebugFunc func) { + for (int i = 0; i < info.Count; ++i) { + ref ShadowVertexCache vertexCache = ref (info.Cache![i] < 0 ? ref TempVertexCache.AsSpan()[(int)(-info.Cache[i] - 1)] : ref VertexCache[(int)info.Cache[i]]); + + Span verts = GetCachedVerts(vertexCache); + + float totalArea = 0.0f; + Vector3 centroid = new(0, 0, 0); + Vector3 apex = verts[0].Position; + int count = vertexCache.Count; + + for (int j = 0; j < count - 2; ++j) { + Vector3 v1 = verts[j + 1].Position; + Vector3 v2 = verts[j + 2].Position; + MathLib.CrossProduct(v2 - v1, v1 - apex, out Vector3 normal); + float area = normal.Length(); + totalArea += area; + centroid += (apex + v1 + v2) * area / 3.0f; + } + + if (totalArea != 0) + centroid /= totalArea; + + func(vertexCache.Shadow, centroid); + } + } + + static void DrawShadowID(ShadowHandle_t shadowHandle, in Vector3 centroid) { +#if !SWDS + Span buf = stackalloc char[16]; + shadowHandle.TryFormat(buf, out int written); + debugoverlay.AddTextOverlay(centroid, 0, buf[..written]); +#endif + } + + void RenderShadowList(MatRenderContextPtr renderContext, ShadowDecalHandle_t decalHandle, Matrix4x4? modelToWorld) { + if (DecalsToRender > ShadowDecalCache.Length) { + int diff = Math.Min(DecalsToRender, MAX_SHADOW_DECAL_CACHE_COUNT) - ShadowDecalCache.Length; + if (diff > 0) { + Array.Resize(ref ShadowDecalCache, ShadowDecalCache.Length + diff); + DevMsg($"[CShadowMgr::RenderShadowList] growing shadow decal cache (decals: {DecalsToRender}, cache: {ShadowDecalCache.Length}, diff: {diff}).\n"); + } + } + + if (DecalsToRender > DispShadowDecalCache.Length) { + int diff = Math.Min(DecalsToRender, MAX_SHADOW_DECAL_CACHE_COUNT) - DispShadowDecalCache.Length; + if (diff > 0) { + Array.Resize(ref DispShadowDecalCache, DispShadowDecalCache.Length + diff); + DevMsg($"[CShadowMgr::RenderShadowList] growing disp shadow decal cache (decals: {DecalsToRender}, cache: {DispShadowDecalCache.Length}, diff: {diff}).\n"); + } + } + + ref Shadow shadow = ref Shadows[ShadowDecals[decalHandle].Shadow]; + + if (r_shadowwireframe.GetInt() == 0) + renderContext.Bind(shadow.Material!, shadow.BindProxy); + else + renderContext.Bind(MatSys.MaterialWorldWireframe!, null); + + ClearTempCache(); + + ShadowRenderInfo info = default; + + info.Cache = ShadowDecalCache; + info.DispCache = DispShadowDecalCache; + + info.ModelToWorld = modelToWorld; + if (modelToWorld != null) + MathLib.MatrixInverseTR(modelToWorld.Value, out info.WorldToModel); + + info.MaxIndices = renderContext.GetMaxIndicesToRender(); + info.MaxVertices = renderContext.GetMaxVerticesToRender(shadow.Material!); + + GenerateShadowRenderInfo(renderContext, decalHandle, ref info); + Assert(info.Count <= DecalsToRender); + Assert(info.DispCount <= DecalsToRender); + Assert(info.Count <= ShadowDecalCache.Length && info.Count <= MAX_SHADOW_DECAL_CACHE_COUNT); + Assert(info.DispCount <= DispShadowDecalCache.Length && info.DispCount <= MAX_SHADOW_DECAL_CACHE_COUNT); + + IMesh mesh = renderContext.GetDynamicMesh(); + MeshBuilder meshBuilder = new(); + meshBuilder.Begin(mesh, MaterialPrimitiveType.Triangles, info.VertexCount, info.IndexCount); + + int baseIndex = AddNormalShadowsToMeshBuilder(ref meshBuilder, ref info); + AddDisplacementShadowsToMeshBuilder(ref meshBuilder, ref info, baseIndex); + + meshBuilder.End(); + mesh.Draw(); + + if (r_shadowids.GetInt() != 0) + RenderDebuggingInfo(in info, DrawShadowID); + } + + public void SetNumWorldMaterialBuckets(int numMaterialSortBins) { + NumWorldMaterialBuckets = numMaterialSortBins; + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + FlashlightStates[flashlightID].Info.MaterialBuckets.SetNumMaterialSortIDs(numMaterialSortBins); + FlashlightStates[flashlightID].Info.OccluderBuckets.SetNumMaterialSortIDs(numMaterialSortBins); + } + ClearAllFlashlightMaterialBuckets(); + } + + void ClearAllFlashlightMaterialBuckets() { + if (r_flashlight_version2.GetInt() != 0) + return; + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) + FlashlightStates[flashlightID].Info.MaterialBuckets.Flush(); + } + + void AllocFlashlightMaterialBuckets(FlashlightHandle_t flashlightID) { + Assert(FlashlightStates.Count >= flashlightID); + FlashlightStates[flashlightID].Info.MaterialBuckets.SetNumMaterialSortIDs(NumWorldMaterialBuckets); + FlashlightStates[flashlightID].Info.OccluderBuckets.SetNumMaterialSortIDs(NumWorldMaterialBuckets); + } + + public void UpdateFlashlightState(ShadowHandle_t shadowHandle, in FlashlightState lightState) { + FlashlightStates[Shadows[shadowHandle].FlashlightHandle].Info.FlashlightState = lightState; + } + + public void SetFlashlightDepthTexture(ShadowHandle_t shadowHandle, ITexture? flashlightDepthTexture, byte shadowStencilBit) { + Shadows[shadowHandle].FlashlightDepthTexture = flashlightDepthTexture; + Shadows[shadowHandle].ShadowStencilBit = shadowStencilBit; + } + + void SetStencilAndScissor(MatRenderContextPtr renderContext, ref FlashlightInfo flashlightInfo, bool useStencil) { + MathLib.MatrixInverseGeneral(Shadows[flashlightInfo.Shadow].Info.WorldToShadow, out Matrix4x4 matFlashlightToWorld); + + Span frustumPoints = [ new(0.0f, 0.0f, 0.0f), new(1.0f, 0.0f, 0.0f), new(1.0f, 1.0f, 0.0f), new(0.0f, 1.0f, 0.0f), + new(0.0f, 0.0f, 1.0f), new(0.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 1.0f), new(1.0f, 0.0f, 1.0f), + new(1.0f, 0.0f, 0.0f), new(1.0f, 0.0f, 1.0f), new(1.0f, 1.0f, 1.0f), new(1.0f, 1.0f, 0.0f), + new(0.0f, 0.0f, 0.0f), new(0.0f, 1.0f, 0.0f), new(0.0f, 1.0f, 1.0f), new(0.0f, 0.0f, 1.0f), + new(0.0f, 1.0f, 0.0f), new(1.0f, 1.0f, 0.0f), new(1.0f, 1.0f, 1.0f), new(0.0f, 1.0f, 1.0f), + new(0.0f, 0.0f, 0.0f), new(0.0f, 0.0f, 1.0f), new(1.0f, 0.0f, 1.0f), new(1.0f, 0.0f, 0.0f)]; + + Span worldFrustumPoints = stackalloc Vector3[24]; + for (int i = 0; i < 24; i++) + matFlashlightToWorld.V3Mul(frustumPoints[i], out worldFrustumPoints[i]); + + const float planeEpsilon = 0.4f; + ExtractFrustumPlanes(out Frustum frustumPlanes, planeEpsilon); + Vector3 nearNormal = frustumPlanes[FrustumPlane.NearZ].Normal; + Vector3 farNormal = frustumPlanes[FrustumPlane.FarZ].Normal; + float nearDist = frustumPlanes[FrustumPlane.NearZ].Dist; + float farDist = frustumPlanes[FrustumPlane.FarZ].Dist; + + Span tempFace = stackalloc Vector3[5]; + Span clippedFace = stackalloc Vector3[6]; + Vector3[][] clippedPolygons = new Vector3[8][]; + for (int i = 0; i < 8; i++) + clippedPolygons[i] = new Vector3[10]; + Span numVertices = stackalloc int[8]; + int numPolygons = 0; + + for (int i = 0; i < 6; i++) { + Span inVerts = worldFrustumPoints[(4 * i)..]; + + int clipCount = MathLib.ClipPolyToPlane(inVerts, 4, tempFace, nearNormal, nearDist); + + if (clipCount > 2) { + clipCount = MathLib.ClipPolyToPlane(tempFace, clipCount, clippedFace, farNormal, farDist); + + if (clipCount > 2) { + clippedFace[..clipCount].CopyTo(clippedPolygons[numPolygons]); + numVertices[numPolygons] = clipCount; + numPolygons++; + } + } + } + + Span nearPlane = stackalloc Vector3[4]; + Span farPlane = stackalloc Vector3[4]; + ConstructNearAndFarPolygons(nearPlane, farPlane, planeEpsilon); + + int nearClipCount = ClipPlaneToFrustum(nearPlane, clippedPolygons[numPolygons], worldFrustumPoints); + if (nearClipCount > 2) { + numVertices[numPolygons] = nearClipCount; + numPolygons++; + } + + for (int i = 0; i < numPolygons; i++) { + for (int j = 0; j < numVertices[i]; j++) { + for (int k = i + 1; k < numPolygons; k++) { + for (int m = 0; m < numVertices[k]; m++) { + if (SufficientlyClose(clippedPolygons[i][j], clippedPolygons[k][m], 0.1f)) + clippedPolygons[k][m] = clippedPolygons[i][j]; + } + } + } + } + + flashlightInfo.FlashlightState.Scissor = false; + if (r_flashlightscissor.GetBool() && (numPolygons > 0)) { + flashlightInfo.FlashlightState.Scissor = ScreenSpaceRectFromPoints(renderContext, clippedPolygons, numVertices, numPolygons, out int left, out int top, out int right, out int bottom); + if (flashlightInfo.FlashlightState.Scissor) { + flashlightInfo.FlashlightState.Left = left; + flashlightInfo.FlashlightState.Top = top; + flashlightInfo.FlashlightState.Right = right; + flashlightInfo.FlashlightState.Bottom = bottom; + } + } + + if (r_flashlightdrawclip.GetBool() && r_flashlightclip.GetBool() && useStencil) { + for (int i = 0; i < numPolygons; i++) + DrawDebugPolygon(numVertices[i], clippedPolygons[i], false, false); + } + + if (r_flashlightclip.GetBool() && useStencil) { + renderContext.SetStencilEnable(true); + renderContext.SetStencilFailOperation(StencilOperation.Replace); + renderContext.SetStencilZFailOperation(StencilOperation.Replace); + renderContext.SetStencilPassOperation(StencilOperation.Replace); + renderContext.SetStencilCompareFunction(StencilComparisonFunction.Always); + renderContext.SetStencilReferenceValue(Shadows[flashlightInfo.Shadow].ShadowStencilBit); + renderContext.SetStencilTestMask(Shadows[flashlightInfo.Shadow].ShadowStencilBit); + renderContext.SetStencilWriteMask(Shadows[flashlightInfo.Shadow].ShadowStencilBit); + + for (int i = 0; i < numPolygons; i++) + DrawPolygonToStencil(renderContext, numVertices[i], clippedPolygons[i], true, false); + + renderContext.SetStencilEnable(false); + } + } + + public void SetFlashlightStencilMasks(bool doMasking) { + if (r_flashlight_version2.GetInt() != 0) + return; + + if (!(r_flashlightclip.GetBool() || r_flashlightscissor.GetBool())) + return; + + if (FlashlightStates.Count == 0) + return; + + using MatRenderContextPtr renderContext = new(materials); + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + ref FlashlightInfo flashlightInfo = ref FlashlightStates[flashlightID].Info; + + SetStencilAndScissor(renderContext, ref flashlightInfo, Shadows[flashlightInfo.Shadow].FlashlightDepthTexture != null); + + } + } + + void DisableStencilAndScissorMasking(MatRenderContextPtr renderContext) { + if (r_flashlightclip.GetBool()) + renderContext.SetStencilEnable(false); + + if (r_flashlightscissor.GetBool()) + renderContext.SetScissorRect(-1, -1, -1, -1, false); + } + + void EnableStencilAndScissorMasking(MatRenderContextPtr renderContext, in FlashlightInfo flashlightInfo, bool doMasking) { + if (!(r_flashlightclip.GetBool() || r_flashlightscissor.GetBool()) || !doMasking) + return; + + if (renderContext.GetRenderTarget() == null) { + if (r_flashlightclip.GetBool() && Shadows[flashlightInfo.Shadow].FlashlightDepthTexture != null) { + byte shadowStencilBit = Shadows[flashlightInfo.Shadow].ShadowStencilBit; + + renderContext.SetStencilEnable(true); + renderContext.SetStencilFailOperation(StencilOperation.Keep); + renderContext.SetStencilZFailOperation(StencilOperation.Keep); + renderContext.SetStencilPassOperation(StencilOperation.Keep); + + renderContext.SetStencilCompareFunction(StencilComparisonFunction.Equal); + renderContext.SetStencilReferenceValue(shadowStencilBit); + renderContext.SetStencilTestMask(shadowStencilBit); + renderContext.SetStencilWriteMask(0x00000000); + } + + if (r_flashlightscissor.GetBool() && flashlightInfo.FlashlightState.Scissor) + renderContext.SetScissorRect(flashlightInfo.FlashlightState.Left, flashlightInfo.FlashlightState.Top, flashlightInfo.FlashlightState.Right, flashlightInfo.FlashlightState.Bottom, true); + } + else + DisableStencilAndScissorMasking(renderContext); + } + + public void SetFlashlightRenderState(ShadowHandle_t handle) => throw new NotImplementedException(); + + public void RenderFlashlights(bool doMasking, Matrix4x4? modelToWorld = null) { +#if !SWDS + if (r_flashlight_version2.GetInt() != 0) + return; + + if (r_flashlightrender.GetBool() == false) + return; + + if (FlashlightStates.Count == 0) + return; + + bool wireframe = r_shadowwireframe.GetBool(); + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.SetFlashlightMode(true); + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + ref FlashlightInfo flashlightInfo = ref FlashlightStates[flashlightID].Info; + MaterialsBuckets materialBuckets = flashlightInfo.MaterialBuckets; + int sortIDHandle = materialBuckets.GetFirstUsedSortID(); + if (sortIDHandle == materialBuckets.InvalidSortIDHandle()) + continue; + + renderContext.SetFlashlightStateEx(flashlightInfo.FlashlightState, Shadows[flashlightInfo.Shadow].Info.WorldToShadow, Shadows[flashlightInfo.Shadow].FlashlightDepthTexture); + EnableStencilAndScissorMasking(renderContext, flashlightInfo, doMasking); + + for (; sortIDHandle != materialBuckets.InvalidSortIDHandle(); sortIDHandle = materialBuckets.GetNextUsedSortID(sortIDHandle)) { + int sortID = materialBuckets.GetSortID(sortIDHandle); + + if (wireframe) + renderContext.Bind(MatSys.MaterialWorldWireframe!, null); + else { + renderContext.Bind(MatSys.MaterialSortInfoArray![sortID].Material!, null); + renderContext.BindLightmapPage(MatSys.MaterialSortInfoArray![sortID].LightmapPageID); + } + + int elemHandle; + int numIndices = 0; + for (elemHandle = materialBuckets.GetElementListHead(sortID); elemHandle != materialBuckets.InvalidElementHandle(); elemHandle = materialBuckets.GetElementListNext(elemHandle)) { + SurfaceHandle_t surfID = materialBuckets.GetElement(elemHandle); + if (!ModelLoader.SurfaceHasDispInfo(ref ModelLoader.SurfaceHandleFromIndex(surfID))) + numIndices += 3 * (ModelLoader.MSurf_VertCount(ref ModelLoader.SurfaceHandleFromIndex(surfID)) - 2); + } + + if (numIndices > 0) { + IMesh mesh = renderContext.GetDynamicMesh(false, MatSys.WorldStaticMeshes[sortID]); + MeshBuilder meshBuilder = new(); + meshBuilder.Begin(mesh, MaterialPrimitiveType.Triangles, 0, numIndices); + + for (elemHandle = materialBuckets.GetElementListHead(sortID); elemHandle != materialBuckets.InvalidElementHandle(); elemHandle = materialBuckets.GetElementListNext(elemHandle)) { + SurfaceHandle_t surfID = materialBuckets.GetElement(elemHandle); + if (!ModelLoader.SurfaceHasDispInfo(ref ModelLoader.SurfaceHandleFromIndex(surfID))) + BuildIndicesForWorldSurface(ref meshBuilder, surfID, host_state.WorldBrush!); + } + + meshBuilder.End(false, true); + } + + for (elemHandle = materialBuckets.GetElementListHead(sortID); elemHandle != materialBuckets.InvalidElementHandle(); elemHandle = materialBuckets.GetElementListNext(elemHandle)) { + SurfaceHandle_t surfID = materialBuckets.GetElement(elemHandle); + if (ModelLoader.SurfaceHasDispInfo(ref ModelLoader.SurfaceHandleFromIndex(surfID))) { + DispInfo? disp = (DispInfo?)ModelLoader.SurfaceHandleFromIndex(surfID).DispInfo; + Assert(disp != null); + if (wireframe) + disp!.SpecifyDynamicMesh(); + else { + Assert(disp != null && disp.Mesh != null && disp.Mesh.Mesh != null); + disp!.Mesh!.Mesh!.Draw(disp.IndexOffset, disp.NumIndices); + } + } + } + } + } + + renderContext.SetFlashlightMode(false); + + DisableStencilAndScissorMasking(renderContext); +#endif + } + + public Frustum_t GetFlashlightFrustum(ShadowHandle_t handle) { + Assert((Shadows[handle].Flags & ShadowCreateFlags.Flashlight) != 0); + Assert(Shadows[handle].FlashlightHandle != SHADOW_HANDLE_INVALID); + return FlashlightStates[Shadows[handle].FlashlightHandle].Info.Frustum; + } + + public ref readonly FlashlightState GetFlashlightState(ShadowHandle_t handle) { + Assert((Shadows[handle].Flags & ShadowCreateFlags.Flashlight) != 0); + Assert(Shadows[handle].FlashlightHandle != SHADOW_HANDLE_INVALID); + return ref FlashlightStates[Shadows[handle].FlashlightHandle].Info.FlashlightState; + } + + public void DrawFlashlightDecals(int sortGroup, bool doMasking) { + if (r_flashlight_version2.GetInt() != 0) + return; + + if (FlashlightStates.Count == 0) + return; + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.SetFlashlightMode(true); + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + ref FlashlightInfo flashlightInfo = ref FlashlightStates[flashlightID].Info; + renderContext.SetFlashlightState(flashlightInfo.FlashlightState, Shadows[flashlightInfo.Shadow].Info.WorldToShadow); + + EnableStencilAndScissorMasking(renderContext, flashlightInfo, doMasking); + + // DecalSurfaceDraw(renderContext, sortGroup); + } + + renderContext.SetFlashlightMode(false); + + DisableStencilAndScissorMasking(renderContext); + } + + public void DrawFlashlightDecalsOnDisplacements(int sortGroup, ReadOnlySpan visibleDisps, int visibleDispCount, bool doMasking) { + if (r_flashlight_version2.GetInt() != 0) + return; + + if (FlashlightStates.Count == 0) + return; + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.SetFlashlightMode(true); + + // DispInfo_BatchDecals(visibleDisps, visibleDispCount); + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + ref FlashlightInfo flashlightInfo = ref FlashlightStates[flashlightID].Info; + renderContext.SetFlashlightState(flashlightInfo.FlashlightState, Shadows[flashlightInfo.Shadow].Info.WorldToShadow); + + EnableStencilAndScissorMasking(renderContext, flashlightInfo, doMasking); + + // DispInfo_DrawDecals(visibleDisps, visibleDispCount); + } + + renderContext.SetFlashlightMode(false); + + DisableStencilAndScissorMasking(renderContext); + } + + public void DrawFlashlightDecalsOnSingleSurface(SurfaceHandle_t surfID, bool doMasking) => throw new NotImplementedException(); + + public void DrawFlashlightOverlays(int sortGroup, bool doMasking) { + if (r_flashlight_version2.GetInt() != 0) + return; + + if (FlashlightStates.Count == 0) + return; + + if (r_flashlightrender.GetBool() == false) + return; + + using MatRenderContextPtr renderContext = new(materials); + + renderContext.SetFlashlightMode(true); + + foreach (FlashlightHandle_t flashlightID in ValidFlashlightHandles) { + ref FlashlightInfo flashlightInfo = ref FlashlightStates[flashlightID].Info; + renderContext.SetFlashlightState(flashlightInfo.FlashlightState, Shadows[flashlightInfo.Shadow].Info.WorldToShadow); + + EnableStencilAndScissorMasking(renderContext, flashlightInfo, doMasking); + + // OverlayMgr().RenderOverlays(sortGroup); + } + + renderContext.SetFlashlightMode(false); + + DisableStencilAndScissorMasking(renderContext); + } + + public void DrawFlashlightDepthTexture() => throw new NotImplementedException(); + + public void AddFlashlightRenderable(ShadowHandle_t shadowHandle, IClientRenderable? renderable) { + ref Shadow shadow = ref Shadows[shadowHandle]; + FlashlightInfoBox flashlightInfo = FlashlightStates[shadow.FlashlightHandle]; + + if (renderable!.GetModelInstance() != MODEL_INSTANCE_INVALID) + flashlightInfo.Info.Renderables.Add(renderable); + } + + public ref uint FirstModelInShadow(ShadowHandle_t h) => ref Shadows[h].FirstModel; +} diff --git a/Source.Engine/SourceDLLMain.cs b/Source.Engine/SourceDLLMain.cs index a545b9fc..a8b5fdb1 100644 --- a/Source.Engine/SourceDLLMain.cs +++ b/Source.Engine/SourceDLLMain.cs @@ -69,6 +69,7 @@ public static class SourceDllMain [Dependency] public static IMaterialSystemHardwareConfig HardwareConfig { get; private set; } = null!; [Dependency] public static ICommandLine commandLine { get; private set; } = null!; [KeyedDependency(Key = Realm.Server)] public static NetworkStringTableContainer networkStringTableContainerServer { get; private set; } = null!; + [Dependency] public static IVDebugOverlay debugoverlay { get; private set; } = null!; #if !SWDS diff --git a/Source.Engine/StaticPropMgr.cs b/Source.Engine/StaticPropMgr.cs index cc2717cd..8f68ad30 100644 --- a/Source.Engine/StaticPropMgr.cs +++ b/Source.Engine/StaticPropMgr.cs @@ -223,11 +223,18 @@ public void AddColorDecalToStaticProp(Vector3 rayStart, Vector3 rayEnd, int stat } public void AddShadowToStaticProp(ushort shadowHandle, IClientRenderable renderable) { - throw new NotImplementedException(); + Assert(renderable as StaticProp != null); + + StaticProp prop = (StaticProp)renderable; + + g_ShadowMgr.AddShadowToModel(shadowHandle, prop.GetModelInstance()); } public void RemoveAllShadowsFromStaticProp(IClientRenderable renderable) { - throw new NotImplementedException(); + Assert(renderable as StaticProp != null); + StaticProp prop = (StaticProp)renderable; + if (prop.GetModelInstance() != MODEL_INSTANCE_INVALID) + g_ShadowMgr.RemoveAllShadowsFromModel(prop.GetModelInstance()); } public void GetStaticPropMaterialColorAndLighting(Trace trace, int staticPropIndex, out Vector3 lighting, out Vector3 matColor) { @@ -436,8 +443,29 @@ void UnserializeModels(Stream buf) { } case 7: case 10: - buf.ReadToStruct(ref lump); + if (MapLoadHelper.MapHeader.Version == 21) { + StaticPropLumpV10_21 v = default; + buf.ReadToStruct(ref v); + lump = v; + } + else { + StaticPropLumpV10 v = default; + buf.ReadToStruct(ref v); + lump = v; + } break; + case 9: { + StaticPropLumpV9 v = default; + buf.ReadToStruct(ref v); + lump = v; + break; + } + case 11: { + StaticPropLumpV11 v = default; + buf.ReadToStruct(ref v); + lump = v; + break; + } default: Sys.Error($"Unexpected lump version {lumpVersion} while deserializing lumps."); break; @@ -692,8 +720,8 @@ public bool ShouldReceiveProjectedTextures(ShadowFlags flags) { else return false; } - public bool GetShadowCastDistance(out float dist, ShadowType shadowType) { dist = 0; return false; } - public bool GetShadowCastDirection(out Vector3 direction, ShadowType shadowType) { direction = default; return false; } + public bool GetShadowCastDistance(ref float dist, ShadowType shadowType) { return false; } + public bool GetShadowCastDirection(ref Vector3 direction, ShadowType shadowType) { return false; } public bool UsesPowerOfTwoFrameBufferTexture() => throw new NotImplementedException(); public bool UsesFullFrameBufferTexture() => throw new NotImplementedException(); public ClientShadowHandle_t GetShadowHandle() => unchecked((ClientShadowHandle_t)~0); diff --git a/Source.Engine/View.cs b/Source.Engine/View.cs index 70bad0ca..996e18cc 100644 --- a/Source.Engine/View.cs +++ b/Source.Engine/View.cs @@ -77,6 +77,8 @@ public void DrawBrushModel(IClientEntity baseentity, Model model, in Vector3 ori R_DrawBrushModel(baseentity, model, origin, angles, RenderDepthMode.Normal, true, true); } + public void DrawBrushModelShadow(IClientRenderable renderable) => R_DrawBrushModelShadow(renderable); + public void DrawIdentityBrushModel(IWorldRenderList list, Model model) { throw new NotImplementedException(); } diff --git a/Source.Engine/World.cs b/Source.Engine/World.cs index 1f5eda08..ebf941ef 100644 --- a/Source.Engine/World.cs +++ b/Source.Engine/World.cs @@ -4,14 +4,23 @@ namespace Source.Engine; -public partial class SV { - public void ClearWorld(){ +public partial class SV +{ + public void ClearWorld() { +#if !SWDS + g_ShadowMgr.LevelShutdown(); +#endif StaticPropMgr().LevelShutdown(); - for (int i = 0; i < 3; i++) - if (host_state.WorldModel!.Mins[i] < MIN_COORD_INTEGER || host_state.WorldModel!.Maxs[i] > MAX_COORD_INTEGER) + for (int i = 0; i < 3; i++) + if (host_state.WorldModel!.Mins[i] < MIN_COORD_INTEGER || host_state.WorldModel!.Maxs[i] > MAX_COORD_INTEGER) Host.EndGame(true, "Map coordinate extents are too large!!\nCheck for errors!\n"); SpatialPartition().Init(host_state.WorldModel!.Mins, host_state.WorldModel!.Maxs); + + StaticPropMgr().LevelInit(); +#if !SWDS + g_ShadowMgr.LevelInit(host_state.WorldBrush!.NumSurfaces); +#endif } } diff --git a/Source.MaterialSystem/MatRenderContext.cs b/Source.MaterialSystem/MatRenderContext.cs index f4251003..390daba6 100644 --- a/Source.MaterialSystem/MatRenderContext.cs +++ b/Source.MaterialSystem/MatRenderContext.cs @@ -61,8 +61,14 @@ public void ClearBuffers(bool clearColor, bool clearDepth, bool clearStencil = f } public void GetRenderTargetDimensions(out int width, out int height) { - // todo - shaderAPI.GetBackBufferDimensions(out width, out height); + ITexture? tos = RenderTargetStack.Top().RenderTarget0; + + if (tos != null) { + width = tos.GetActualWidth(); + height = tos.GetActualHeight(); + } + else + shaderAPI.GetBackBufferDimensions(out width, out height); } public void DepthRange(double near, double far) { @@ -154,6 +160,31 @@ public void Viewport(int x, int y, int width, int height) { newTOS.ViewH = height; RenderTargetStack.Pop(); RenderTargetStack.Push(newTOS); + + if ((width < 0) || (height < 0)) { + ITexture? target = RenderTargetStack.Top().RenderTarget0; + + if (target == null) { + ActiveViewport.TopLeftX = 0; + ActiveViewport.TopLeftY = 0; + shaderAPI.GetBackBufferDimensions(out ActiveViewport.Width, out ActiveViewport.Height); + shaderAPI.SetViewports(new Span(ref ActiveViewport)); + } + else { + ActiveViewport.TopLeftX = 0; + ActiveViewport.TopLeftY = 0; + ActiveViewport.Width = target.GetActualWidth(); + ActiveViewport.Height = target.GetActualHeight(); + shaderAPI.SetViewports(new Span(ref ActiveViewport)); + } + } + else { + ActiveViewport.TopLeftX = x; + ActiveViewport.TopLeftY = y; + ActiveViewport.Width = width; + ActiveViewport.Height = height; + shaderAPI.SetViewports(new Span(ref ActiveViewport)); + } } IMaterialInternal? currentMaterial; @@ -258,6 +289,14 @@ public void EndBatch() { bool DirtyViewState; bool DirtyViewProjState; bool EnableClipping; + MaterialHeightClipMode HeightClipMode; + + public MaterialHeightClipMode GetHeightClipMode() => HeightClipMode; + + public void SetHeightClipMode(MaterialHeightClipMode heightClipMode) { + if (HeightClipMode != heightClipMode) + HeightClipMode = heightClipMode; + } public bool InFlashlightMode() { return FlashlightEnable; @@ -537,7 +576,7 @@ private void RecomputeViewState() { VecViewUp = new(viewMatrix[1][0], viewMatrix[1][1], viewMatrix[1][2]); } - private void GetMatrix(MaterialMatrixMode mode, out Matrix4x4 viewMatrix) { + public void GetMatrix(MaterialMatrixMode mode, out Matrix4x4 viewMatrix) { var stack = MatrixStacks[(int)mode]; if (stack.Count == 0) { viewMatrix = Matrix4x4.Identity; @@ -611,6 +650,26 @@ public void BindLocalCubemap(ITexture? texture) { public void DisableAllLocalLights() => shaderAPI.DisableAllLocalLights(); public int GetMaxLights() => shaderAPI.GetMaxLights(); + public void SetFlashlightMode(bool enable) { + if (enable != FlashlightEnable) { + shaderAPI.FlushBufferedPrimitives(); + FlashlightEnable = enable; + } + } + public bool GetFlashlightMode() => FlashlightEnable; + public void SetFlashlightState(in FlashlightState state, in Matrix4x4 worldToTexture) => SetFlashlightStateEx(state, worldToTexture, null); + public void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture) => shaderAPI.SetFlashlightStateEx(state, worldToTexture, flashlightDepthTexture); + + public void SetStencilEnable(bool onoff) => shaderAPI.SetStencilEnable(onoff); + public void SetStencilFailOperation(StencilOperation op) => shaderAPI.SetStencilFailOperation(op); + public void SetStencilZFailOperation(StencilOperation op) => shaderAPI.SetStencilZFailOperation(op); + public void SetStencilPassOperation(StencilOperation op) => shaderAPI.SetStencilPassOperation(op); + public void SetStencilCompareFunction(StencilComparisonFunction cmpfn) => shaderAPI.SetStencilCompareFunction(cmpfn); + public void SetStencilReferenceValue(int reference) => shaderAPI.SetStencilReferenceValue(reference); + public void SetStencilTestMask(uint msk) => shaderAPI.SetStencilTestMask(msk); + public void SetStencilWriteMask(uint msk) => shaderAPI.SetStencilWriteMask(msk); + public void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor) => shaderAPI.SetScissorRect(left, top, right, bottom, enableScissor); + public MatLightmaps GetLightmaps() => materials.MatLightmaps; public void BindLightmap(Sampler sampler) { diff --git a/Source.MaterialSystem/MatStub.cs b/Source.MaterialSystem/MatStub.cs index f7175358..eb8675ef 100644 --- a/Source.MaterialSystem/MatStub.cs +++ b/Source.MaterialSystem/MatStub.cs @@ -389,6 +389,9 @@ public void GetLightmapPageSize(int lightmap, out int width, out int height) { width = height = 32; } public IMaterialProxyFactory? GetMaterialProxyFactory() => null; + public void AddRestoreFunc(Action func) { } + public void RemoveRestoreFunc(Action func) { } + public bool SupportsShadowDepthTextures() => false; public int GetMaxIndicesToRender() => 32768; public int GetMaxVerticesToRender(IMaterial material) => 32768; public int GetNumSortIDs() => 10; @@ -417,6 +420,8 @@ public void LoadBoneMatrix(int hardwareID, in Matrix3x4 matrix4x4) { } public void LoadIdentity() { } public void LoadMatrix(in Matrix3x4 matrix) { } public void LoadMatrix(in Matrix4x4 matrix) { } + public MaterialHeightClipMode GetHeightClipMode() => MaterialHeightClipMode.Disable; + public void SetHeightClipMode(MaterialHeightClipMode heightClipMode) { } public void MatrixMode(MaterialMatrixMode mode) { } public void ModInit() { } public void ModShutdown() { } @@ -452,4 +457,20 @@ public void SetAmbientLight(float r, float g, float b) { } public void SetLight(int lightNum, in LightDesc desc) { } public void DisableAllLocalLights() { } public int GetMaxLights() => 0; + public void SetFlashlightMode(bool enable) { } + public bool GetFlashlightMode() => false; + public void SetFlashlightState(in FlashlightState state, in Matrix4x4 worldToTexture) { } + public void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture) { } + public void GetMatrix(MaterialMatrixMode matrixMode, out Matrix4x4 matrix) => matrix = Matrix4x4.Identity; + public void SetStencilEnable(bool onoff) { } + public void SetStencilFailOperation(StencilOperation op) { } + public void SetStencilZFailOperation(StencilOperation op) { } + public void SetStencilPassOperation(StencilOperation op) { } + public void SetStencilCompareFunction(StencilComparisonFunction cmpfn) { } + public void SetStencilReferenceValue(int reference) { } + public void SetStencilTestMask(uint msk) { } + public void SetStencilWriteMask(uint msk) { } + public void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor) { } + public ImageFormat GetShadowDepthTextureFormat() => ImageFormat.Unknown; + public ImageFormat GetNullTextureFormat() => ImageFormat.Unknown; } diff --git a/Source.MaterialSystem/MaterialSystem.cs b/Source.MaterialSystem/MaterialSystem.cs index 8169aea6..1bf1831e 100644 --- a/Source.MaterialSystem/MaterialSystem.cs +++ b/Source.MaterialSystem/MaterialSystem.cs @@ -913,6 +913,8 @@ public void RestoreShaderObjects(IServiceProvider? services, int changeFlags) { TextureSystem.RestoreRenderTargets(); Restore?.Invoke(); + for (int i = 0; i < RestoreFunc.Count; i++) + RestoreFunc[i](changeFlags); TextureSystem.RestoreNonRenderTargetTextures(); } @@ -1036,6 +1038,23 @@ void RecomputeAllStateSnapshots() { public event Action? Restore; + readonly List> RestoreFunc = []; + + public void AddRestoreFunc(Action func) { + Assert(!RestoreFunc.Contains(func)); + RestoreFunc.Add(func); + } + + public void RemoveRestoreFunc(Action func) { + RestoreFunc.Remove(func); + } + + public bool SupportsShadowDepthTextures() => ShaderAPI.SupportsShadowDepthTextures(); + + public ImageFormat GetShadowDepthTextureFormat() => ShaderAPI.GetShadowDepthTextureFormat(); + + public ImageFormat GetNullTextureFormat() => ShaderAPI.GetNullTextureFormat(); + public IMaterialInternal errorMaterial; public readonly MatLightmaps MatLightmaps; } @@ -1071,11 +1090,9 @@ public struct RenderTargetStackElement public int ViewW; public int ViewH; - public readonly int Size => - (RenderTarget0 != null ? 1 : 0) + - (RenderTarget1 != null ? 1 : 0) + - (RenderTarget2 != null ? 1 : 0) + - (RenderTarget3 != null ? 1 : 0); + public const int NUM_RENDER_TARGET_BINDS = 4; + + public readonly int Size => NUM_RENDER_TARGET_BINDS; public RenderTargetStackElement(int viewX, int viewY, int viewW, int viewH) { this.ViewX = viewX; diff --git a/Source.MaterialSystem/Texture.cs b/Source.MaterialSystem/Texture.cs index 0f376ffb..3ad671fc 100644 --- a/Source.MaterialSystem/Texture.cs +++ b/Source.MaterialSystem/Texture.cs @@ -506,7 +506,7 @@ private void ReconstructTexture(bool copyFromCurrent) { private ITexture? GetEmbeddedTexture(int index) => index == 0 ? this : null; private bool IsDepthTextureFormat(ImageFormat imageFormat) => imageFormat == ImageFormat.NV_DST16 || - imageFormat == ImageFormat.ATI_DST24 || + imageFormat == ImageFormat.NV_DST24 || imageFormat == ImageFormat.NV_IntZ || imageFormat == ImageFormat.NV_RawZ || imageFormat == ImageFormat.ATI_DST16 || diff --git a/Source.Physics/PhysicsCollide.cs b/Source.Physics/PhysicsCollide.cs index 7fa9b9a7..f42e08d3 100644 --- a/Source.Physics/PhysicsCollide.cs +++ b/Source.Physics/PhysicsCollide.cs @@ -227,7 +227,8 @@ public void VCollideLoad(VCollide output, int solidCount, ReadOnlySpan buf } public void VCollideUnload(VCollide vCollide) { - throw new NotImplementedException(); + // throw new NotImplementedException(); + // TODO! } public IVPhysicsKeyParser VPhysicsKeyParserCreate(ReadOnlySpan keyData) { diff --git a/Source.ShaderAPI.Gl46/HardwareConfig.cs b/Source.ShaderAPI.Gl46/HardwareConfig.cs index 210c74c6..bcfda75b 100644 --- a/Source.ShaderAPI.Gl46/HardwareConfig.cs +++ b/Source.ShaderAPI.Gl46/HardwareConfig.cs @@ -1,3 +1,4 @@ +using Source.Common.Bitmap; using Source.Common.Commands; using Source.Common.MaterialSystem; @@ -5,6 +6,10 @@ namespace Source.ShaderAPI.Gl46; public class HardwareConfig : IMaterialSystemHardwareConfig { + public bool SupportsShadowDepthTexturesCap = true; + public ImageFormat ShadowDepthTextureFormat = ImageFormat.NV_DST24; + public ImageFormat NullTextureFormat = ImageFormat.NV_NULL; + public bool ActuallySupportsPixelShaders_2_b() { throw new NotImplementedException(); } diff --git a/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs b/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs index d84962f6..3109b27b 100644 --- a/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs +++ b/Source.ShaderAPI.Gl46/ShaderAPIGl46.cs @@ -127,7 +127,20 @@ public void ClearBuffers(bool clearColor, bool clearDepth, bool clearStencil, in if (clearStencil) glStencilMask(0xFF); - glClear(flags); + if (flags != 0) { + bool renderTargetMatchesViewport = + (renderTargetWidth == -1 && renderTargetHeight == -1) || + (renderTargetWidth == Viewport.Width && renderTargetHeight == Viewport.Height); + + if (renderTargetMatchesViewport) + glClear(flags); + else { + glEnable(GL_SCISSOR_TEST); + glScissor(Viewport.X, renderTargetHeight - (Viewport.Y + Viewport.Height), Viewport.Width, Viewport.Height); + glClear(flags); + glDisable(GL_SCISSOR_TEST); + } + } } public void ClearColor3ub(byte r, byte g, byte b) => glClearColor(r / 255f, g / 255f, b / 255f, 1); @@ -390,6 +403,89 @@ public void SetLight(int lightNum, in LightDesc desc) { Lights[lightNum] = light; } + FlashlightState FlashlightState; + Matrix4x4 FlashlightWorldToTexture; + ITexture? FlashlightDepthTexture; + + StencilOperation StencilFailOperation = StencilOperation.Keep; + StencilOperation StencilZFailOperation = StencilOperation.Keep; + StencilOperation StencilPassOperation = StencilOperation.Keep; + StencilComparisonFunction StencilCompareFunction = StencilComparisonFunction.Always; + int StencilReferenceValue; + uint StencilTestMask = 0xFFFFFFFF; + + public void SetFlashlightStateEx(in FlashlightState state, in Matrix4x4 worldToTexture, ITexture? flashlightDepthTexture) { + FlushBufferedPrimitives(); + FlashlightState = state; + FlashlightWorldToTexture = worldToTexture; + FlashlightDepthTexture = flashlightDepthTexture; + } + + public void SetStencilEnable(bool onoff) { + FlushBufferedPrimitives(); + if (onoff) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); + } + + public void SetStencilFailOperation(StencilOperation op) { + FlushBufferedPrimitives(); + StencilFailOperation = op; + ApplyStencilOperations(); + } + + public void SetStencilZFailOperation(StencilOperation op) { + FlushBufferedPrimitives(); + StencilZFailOperation = op; + ApplyStencilOperations(); + } + + public void SetStencilPassOperation(StencilOperation op) { + FlushBufferedPrimitives(); + StencilPassOperation = op; + ApplyStencilOperations(); + } + + public void SetStencilCompareFunction(StencilComparisonFunction cmpfn) { + FlushBufferedPrimitives(); + StencilCompareFunction = cmpfn; + ApplyStencilFunc(); + } + + public void SetStencilReferenceValue(int reference) { + FlushBufferedPrimitives(); + StencilReferenceValue = reference; + ApplyStencilFunc(); + } + + public void SetStencilTestMask(uint msk) { + FlushBufferedPrimitives(); + StencilTestMask = msk; + ApplyStencilFunc(); + } + + public void SetStencilWriteMask(uint msk) { + FlushBufferedPrimitives(); + glStencilMask(msk); + } + + void ApplyStencilOperations() => glStencilOp(StencilFailOperation.GLEnum(), StencilZFailOperation.GLEnum(), StencilPassOperation.GLEnum()); + + void ApplyStencilFunc() => glStencilFunc(StencilCompareFunction.GLEnum(), StencilReferenceValue, StencilTestMask); + + public void SetScissorRect(int left, int top, int right, int bottom, bool enableScissor) { + FlushBufferedPrimitives(); + if (!enableScissor) { + glDisable(GL_SCISSOR_TEST); + return; + } + + GetBackBufferDimensions(out _, out int height); + glEnable(GL_SCISSOR_TEST); + glScissor(left, height - bottom, right - left, bottom - top); + } + public void DisableAllLocalLights() { bool flushed = false; for (int lightNum = 0; lightNum < MAX_NUM_LIGHTS; lightNum++) { @@ -896,13 +992,17 @@ private unsafe void SetVertexShaderConstantInternal(int var, Span vec) { } bool UsingTextureRenderTarget; + int ViewportMaxWidth; + int ViewportMaxHeight; + GfxViewport Viewport; public void SetViewports(ReadOnlySpan viewports) { Assert(viewports.Length == 1); if (viewports.Length != 1) return; - GfxViewport viewport = new(); + ref GfxViewport viewport = ref Viewport; + viewport = new(); viewport.X = viewports[0].TopLeftX; viewport.Y = viewports[0].TopLeftY; viewport.Width = viewports[0].Width; @@ -910,16 +1010,33 @@ public void SetViewports(ReadOnlySpan viewports) { viewport.MinZ = viewports[0].MinZ; viewport.MaxZ = viewports[0].MaxZ; - if (UsingTextureRenderTarget) { - int maxWidth = 0, maxHeight = 0; - GetBackBufferDimensions(out maxWidth, out maxHeight); + int targetHeight; + if (!UsingTextureRenderTarget) { + GetBackBufferDimensions(out int maxWidth, out int maxHeight); + + if (viewport.Width > maxWidth && maxWidth > 0) + viewport.Width = maxWidth; + + if (viewport.Height > maxHeight && maxHeight > 0) + viewport.Height = maxHeight; + + targetHeight = maxHeight; } - // TODO: this has a lot more logic... + else { + if (viewport.Width > ViewportMaxWidth) + viewport.Width = ViewportMaxWidth; + if (viewport.Height > ViewportMaxHeight) + viewport.Height = ViewportMaxHeight; + + targetHeight = ViewportMaxHeight; + } + FlushBufferedPrimitives(); - // HACK BECAUSE SOMETHING IS REALLY WRONG: We report the right viewport width/height to OpenGL, but regardless the first couple of frames it decides that we didn't. So this hack - // skips the first couple of loading screen frames. + // HACK BECAUSE SOMETHING IS REALLY WRONG: We report the right viewport width/height to OpenGL, but regardless the first couple of frames it decides that we didn't. So this hack + // skips the first couple of loading screen frames. + // TODO: Is this hack still needed? if (frame >= 2) { - glViewport(viewport.X, viewport.Y, viewport.Width, viewport.Height); + glViewport(viewport.X, targetHeight - (viewport.Y + viewport.Height), viewport.Width, viewport.Height); glDepthRangef(viewport.MinZ, viewport.MaxZ); } frame++; @@ -945,6 +1062,11 @@ public ImageFormat GetBackBufferFormat() { // PresentParameters.BackBufferFormat but what actually sets that I'm not sure yet return ImageFormat.RGBA8888; } + + public bool SupportsShadowDepthTextures() => ((HardwareConfig)HardwareConfig).SupportsShadowDepthTexturesCap; + public ImageFormat GetShadowDepthTextureFormat() => ((HardwareConfig)HardwareConfig).ShadowDepthTextureFormat; + public ImageFormat GetNullTextureFormat() => ((HardwareConfig)HardwareConfig).NullTextureFormat; + public void GetBackBufferDimensions(out int width, out int height) { width = PresentParameters.DisplayMode.Width; height = PresentParameters.DisplayMode.Height; @@ -1680,9 +1802,13 @@ public unsafe void DeleteTexture(ShaderAPITextureHandle_t handle) { return; UnbindTexture(handle); + + InternalTextureInfo tex = GetTexture(handle); + uint[] copies = tex.GetTextureArray(); + fixed (uint* h = copies) + glDeleteTextures(tex.NumCopies, h); + Textures.Remove(handle); - uint h = (uint)handle; - glDeleteTextures(1, &h); } public void UnbindTexture(int handle) { @@ -1944,26 +2070,43 @@ public void EnableLinearColorSpaceFrameBuffer(bool v) { public void SetRenderTargetEx(int renderTargetID, ShaderAPITextureHandle_t colorTextureHandle = -1, ShaderAPITextureHandle_t depthTextureHandle = -1) { FlushBufferedPrimitives(); + bool usingTextureTarget = false; + if (colorTextureHandle == -1 && depthTextureHandle == -1) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (renderTargetID == 0) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + UsingTextureRenderTarget = usingTextureTarget; + } return; } + if (colorTextureHandle >= 0) + usingTextureTarget = true; + + if (renderTargetID == 0) + UsingTextureRenderTarget = usingTextureTarget; + glBindFramebuffer(GL_FRAMEBUFFER, renderFBO); + if (UsingTextureRenderTarget && renderTargetID == 0) { + InternalTextureInfo tex = GetTexture(depthTextureHandle < 0 ? colorTextureHandle : depthTextureHandle); + ViewportMaxWidth = tex.Width; + ViewportMaxHeight = tex.Height; + } + if (colorTextureHandle == -2) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); else if (colorTextureHandle >= 0) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, (uint)colorTextureHandle, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, GetGL46Texture(colorTextureHandle), 0); if (depthTextureHandle == -2) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); else if (depthTextureHandle >= 0) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (uint)depthTextureHandle, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, GetGL46Texture(depthTextureHandle), 0); var status = glCheckFramebufferStatus(GL_FRAMEBUFFER); Assert(status == GL_FRAMEBUFFER_COMPLETE, "Framebuffer incomplete"); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + // glBindFramebuffer(GL_FRAMEBUFFER, 0); } public IMesh CreateStaticMesh(VertexFormat format, ReadOnlySpan textureGroup, IMaterial? material) { diff --git a/Source.ShaderAPI.Gl46/VertexBufferGl46.cs b/Source.ShaderAPI.Gl46/VertexBufferGl46.cs index 3bbd4b4d..c99d8c05 100644 --- a/Source.ShaderAPI.Gl46/VertexBufferGl46.cs +++ b/Source.ShaderAPI.Gl46/VertexBufferGl46.cs @@ -310,15 +310,17 @@ public unsafe void RecomputeVBO() { Position = 0; } - baseVertexIndex = VertexSize == 0 ? 0 : (Position / VertexSize); - if (SysmemBuffer == null) + int lockOffset = NextLockOffset(); + baseVertexIndex = VertexSize == 0 ? 0 : (lockOffset / VertexSize); + if (SysmemBuffer == null) RecomputeVBO(); - else if (discard) + else if (discard) glNamedBufferData((uint)vbo, BufferSize, null, GL_DYNAMIC_DRAW); - + Locked = true; - return (byte*)((nint)SysmemBuffer + Position); + Position = lockOffset; + return (byte*)((nint)SysmemBuffer + lockOffset); } public void Unlock(int vertexCount) { diff --git a/Source.StdShader.Gl46/BaseVSShader.cs b/Source.StdShader.Gl46/BaseVSShader.cs index e65c8871..60b7fd1a 100644 --- a/Source.StdShader.Gl46/BaseVSShader.cs +++ b/Source.StdShader.Gl46/BaseVSShader.cs @@ -336,6 +336,20 @@ public void SetVertexShaderTextureScaledTransform(int vertexReg, int transformVa ShaderAPI!.SetVertexShaderConstant(vertexReg, transformation); } + public void SetVertexShaderMatrix3x4(int vertexReg, int matrixVar) { + IMaterialVar? translationVar = Params![matrixVar]; + if (translationVar != null) { + Matrix4x4 mat = translationVar.GetMatrixValue(); + Span rows = [mat.M11, mat.M12, mat.M13, mat.M14, mat.M21, mat.M22, mat.M23, mat.M24, mat.M31, mat.M32, mat.M33, mat.M34]; + ShaderAPI!.SetVertexShaderConstant(vertexReg, rows); + } + else { + Matrix4x4 matrix = Matrix4x4.Identity; + Span rows = [matrix.M11, matrix.M12, matrix.M13, matrix.M14, matrix.M21, matrix.M22, matrix.M23, matrix.M24, matrix.M31, matrix.M32, matrix.M33, matrix.M34]; + ShaderAPI!.SetVertexShaderConstant(vertexReg, rows); + } + } + public static void ColorVarsToVector(int colorVar, int alphaVar, Span color) { IMaterialVar[] shaderParams = Params!; diff --git a/Source.StdShader.Gl46/Shadow.cs b/Source.StdShader.Gl46/Shadow.cs new file mode 100644 index 00000000..b3505c09 --- /dev/null +++ b/Source.StdShader.Gl46/Shadow.cs @@ -0,0 +1,133 @@ +using Source.Common; +using Source.Common.MaterialSystem; +using Source.Common.ShaderAPI; +using Source.Common.ShaderLib; + +namespace Source.StdShader.Gl46; + +public class Shadow : BaseVSShader +{ + + public static string HelpString = "Help for Shadow"; + public static int Flags = (int)ShaderParamFlags.NotEditable; + public static List ShaderParams = []; + public static ShaderParam[] ShaderParamOverrides = new ShaderParam[(int)ShaderMaterialVars.Count]; + + public class ShaderParam + { + public readonly ShaderParamInfo Info; + public readonly int Index; + public ShaderParam(ShaderMaterialVars var, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags) { + Info.Name = "override"; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + + if (ShaderParamOverrides[(int)var] == null) { + + } + else { + AssertMsg(false, "ShaderParamOverrides at var index had null value"); + } + + ShaderParamOverrides[(int)var] = this; + Index = (int)var; + } + public ShaderParam(string name, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags = 0) { + Info.Name = name; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + Index = (int)ShaderMaterialVars.Count + ShaderParams.Count; + ShaderParams.Add(this); + } + public static implicit operator int(ShaderParam param) => param.Index; + public ReadOnlySpan GetName() => Info.Name; + public ShaderParamType GetType() => Info.Type; + public ReadOnlySpan GetDefaultValue() => Info.DefaultValue; + public int GetFlags() => (int)Info.Flags; + public ReadOnlySpan GetHelp() => Info.Help; + } + + protected override void OnInitShaderParams(IMaterialVar[] vars, ReadOnlySpan materialName) { + + } + + public override string? GetFallbackShader(IMaterialVar[] vars) { + return null; + } + public override int GetFlags() => Flags; + public override int GetNumParams() => base.GetNumParams() + ShaderParams.Count; + public override ReadOnlySpan GetParamName(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamName(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetName(); + } + public override ReadOnlySpan GetParamHelp(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamHelp(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetHelp(); + } + public override ShaderParamType GetParamType(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamType(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetType(); + } + public override ReadOnlySpan GetParamDefault(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamDefault(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetDefaultValue(); + } + protected override void OnInitShaderInstance(IMaterialVar[] vars, ReadOnlySpan materialName) { + LoadTexture((int)ShaderMaterialVars.BaseTexture, (int)TextureFlags.SRGB); + } + protected override void OnDrawElements(IMaterialVar[] vars, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { + if (ShaderShadow != null) { + ShaderShadow.EnableTexture(Sampler.Sampler0, true); + + EnableAlphaBlending(ShaderBlendFactor.Zero, ShaderBlendFactor.SrcColor); + + VertexFormat flags = VertexFormat.Position | VertexFormat.Color; + int numTexCoords = 1; + ShaderShadow.VertexShaderVertexFormat(flags | VertexFormat.TexCoord2D_0, numTexCoords, null, 0); + + ShaderShadow.SetVertexShader("shadow"); + ShaderShadow.SetPixelShader("shadow"); + + SetStandardShaderUniforms(); + } + + if (shaderAPI != null) { + BindTexture(Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, (int)ShaderMaterialVars.Frame); + + SetVertexShaderTextureTransform(VertexShaderConst.ShaderSpecificConst0, (int)ShaderMaterialVars.BaseTextureTransform); + SetPixelShaderConstant(1, (int)ShaderMaterialVars.Color); + + int width = 16; + int height = 16; + ITexture? texture = vars[(int)ShaderMaterialVars.BaseTexture].GetTextureValue(); + if (texture != null) { + width = texture.GetActualWidth(); + height = texture.GetActualHeight(); + } + + Span jitter = [1.0f / width, 1.0f / height, 0.0f, 0.0f]; + shaderAPI.SetVertexShaderConstant(VertexShaderConst.ShaderSpecificConst2, jitter); + + jitter[1] *= -1.0f; + shaderAPI.SetVertexShaderConstant(VertexShaderConst.ShaderSpecificConst3, jitter); + } + + Draw(); + } +} diff --git a/Source.StdShader.Gl46/ShadowModel.cs b/Source.StdShader.Gl46/ShadowModel.cs new file mode 100644 index 00000000..c20689a9 --- /dev/null +++ b/Source.StdShader.Gl46/ShadowModel.cs @@ -0,0 +1,149 @@ +using Source.Common; +using Source.Common.MaterialSystem; +using Source.Common.ShaderAPI; +using Source.Common.ShaderLib; + +namespace Source.StdShader.Gl46; + +public class ShadowModel : BaseVSShader +{ + public static string HelpString = "Help for ShadowModel"; + public static int Flags = (int)ShaderParamFlags.NotEditable; + public static List ShaderParams = []; + public static ShaderParam[] ShaderParamOverrides = new ShaderParam[(int)ShaderMaterialVars.Count]; + + public class ShaderParam + { + public readonly ShaderParamInfo Info; + public readonly int Index; + public ShaderParam(ShaderMaterialVars var, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags) { + Info.Name = "override"; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + + if (ShaderParamOverrides[(int)var] == null) { + + } + else { + AssertMsg(false, "ShaderParamOverrides at var index had null value"); + } + + ShaderParamOverrides[(int)var] = this; + Index = (int)var; + } + public ShaderParam(string name, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags = 0) { + Info.Name = name; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + Index = (int)ShaderMaterialVars.Count + ShaderParams.Count; + ShaderParams.Add(this); + } + public static implicit operator int(ShaderParam param) => param.Index; + public ReadOnlySpan GetName() => Info.Name; + public ShaderParamType GetType() => Info.Type; + public ReadOnlySpan GetDefaultValue() => Info.DefaultValue; + public int GetFlags() => (int)Info.Flags; + public ReadOnlySpan GetHelp() => Info.Help; + } + + public static readonly ShaderParam BASETEXTUREOFFSET = new("$basetextureoffset", ShaderParamType.Vec2, "[0 0]", "$baseTexture texcoord offset"); + public static readonly ShaderParam BASETEXTURESCALE = new("$basetexturescale", ShaderParamType.Vec2, "[1 1]", "$baseTexture texcoord scale"); + public static readonly ShaderParam FALLOFFOFFSET = new("$falloffoffset", ShaderParamType.Float, "0", "Distance at which shadow starts to fade"); + public static readonly ShaderParam FALLOFFDISTANCE = new("$falloffdistance", ShaderParamType.Float, "100", "Max shadow distance"); + public static readonly ShaderParam FALLOFFAMOUNT = new("$falloffamount", ShaderParamType.Float, "0.9", "Amount to brighten the shadow at max dist"); + + protected override void OnInitShaderParams(IMaterialVar[] vars, ReadOnlySpan materialName) { + if (!vars[BASETEXTURESCALE].IsDefined()) { + vars[BASETEXTURESCALE].SetVecValue(1, 1); + } + + if (!vars[FALLOFFDISTANCE].IsDefined()) + vars[FALLOFFDISTANCE].SetFloatValue(100.0f); + + if (!vars[FALLOFFAMOUNT].IsDefined()) + vars[FALLOFFAMOUNT].SetFloatValue(0.9f); + } + + public override string? GetFallbackShader(IMaterialVar[] vars) { + return null; + } + public override int GetFlags() => Flags; + public override int GetNumParams() => base.GetNumParams() + ShaderParams.Count; + public override ReadOnlySpan GetParamName(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamName(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetName(); + } + public override ReadOnlySpan GetParamHelp(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamHelp(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetHelp(); + } + public override ShaderParamType GetParamType(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamType(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetType(); + } + public override ReadOnlySpan GetParamDefault(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamDefault(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetDefaultValue(); + } + protected override void OnInitShaderInstance(IMaterialVar[] vars, ReadOnlySpan materialName) { + if (vars[(int)ShaderMaterialVars.BaseTexture].IsDefined()) { + LoadTexture((int)ShaderMaterialVars.BaseTexture); + } + } + protected override void OnDrawElements(IMaterialVar[] vars, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { + if (ShaderShadow != null) { + ShaderShadow.EnableTexture(Sampler.Sampler0, true); + + EnableAlphaBlending(ShaderBlendFactor.DstColor, ShaderBlendFactor.Zero); + + VertexFormat fmt = VertexFormat.Position | VertexFormat.Normal; + ShaderShadow.VertexShaderVertexFormat(fmt, 1, null, 0); + + ShaderShadow.SetVertexShader("shadowmodel"); + ShaderShadow.SetPixelShader("shadowmodel"); + + SetStandardShaderUniforms(); + } + + if (shaderAPI != null) { + BindTexture(Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, (int)ShaderMaterialVars.Frame); + SetVertexShaderMatrix3x4(VertexShaderConst.ShaderSpecificConst0, (int)ShaderMaterialVars.BaseTextureTransform); + + Span texOffset = stackalloc float[4]; + vars[BASETEXTUREOFFSET].GetVecValue(texOffset); + shaderAPI.SetVertexShaderConstant(VertexShaderConst.ShaderSpecificConst3, texOffset); + + Span texScale = stackalloc float[4]; + vars[BASETEXTURESCALE].GetVecValue(texScale); + shaderAPI.SetVertexShaderConstant(VertexShaderConst.ShaderSpecificConst4, texScale); + + Span shadow = stackalloc float[4]; + shadow[0] = vars[FALLOFFOFFSET].GetFloatValue(); + shadow[1] = vars[FALLOFFDISTANCE].GetFloatValue() + shadow[0]; + if (shadow[1] != 0.0f) + shadow[1] = 1.0f / shadow[1]; + shadow[2] = vars[FALLOFFAMOUNT].GetFloatValue(); + shaderAPI.SetVertexShaderConstant(VertexShaderConst.ShaderSpecificConst5, shadow); + + SetModulationVertexShaderDynamicState(); + } + + Draw(); + } +} diff --git a/Source.StudioRender/StudioRenderContext.cs b/Source.StudioRender/StudioRenderContext.cs index 89648c0a..65e9c37f 100644 --- a/Source.StudioRender/StudioRenderContext.cs +++ b/Source.StudioRender/StudioRenderContext.cs @@ -36,14 +36,6 @@ public class StudioRenderCtx public OverrideType ForcedMaterialType; } -public enum OverrideType -{ - Normal, - BuildShadows, - DepthWrite, - SSAODepthWrite -} - /// /// Analog of CStudioRenderContext /// @@ -682,6 +674,15 @@ public void SetAlphaModulation(float alpha) { RC.AlphaMod = alpha; } + public void SetEyeViewTarget(StudioHeader? studioHdr, int bodyPart, in Vector3 viewtarget) => RC.ViewTarget = viewtarget; + + public void ForcedMaterialOverride(IMaterial? newMaterial, OverrideType overrideType = OverrideType.Normal) { + RC.ForcedMaterial = newMaterial; + RC.ForcedMaterialType = overrideType; + } + + public int ComputeModelLod(StudioHWData hardwareData, float unitSphereSize) => ComputeModelLODAndMetric(hardwareData, unitSphereSize, out _); + static readonly Vector3[] AmbientLightDir = [ new( 1, 0, 0), new(-1, 0, 0), diff --git a/Usings/GlobalUsings.cs b/Usings/GlobalUsings.cs index c6318993..8f6eae7e 100644 --- a/Usings/GlobalUsings.cs +++ b/Usings/GlobalUsings.cs @@ -63,9 +63,15 @@ global using SurfaceHandle_t = int; global using WaitForResourcesHandle_t = int; global using DispShadowHandle = ushort; +global using ShadowSurfaceIndex_t = int; +global using FlashlightHandle_t = ushort; +global using SurfaceBoundsCacheIndex_t = ushort; global using DispDecalHandle = short; +global using DispDecalFragmentHandle = ushort; +global using DispShadowFragmentHandle = ushort; global using fltx4 = System.Runtime.Intrinsics.Vector128; global using static Source.Common.ClientRenderHandleGlobals; +global using static Source.Common.ClientRenderableGlobals; global using static Source.Common.Audio.AudioConstants; global using static Source.Common.Engine.EdictGlobals; global using static Source.Common.Engine.ShadowGlobals;