diff --git a/Game.Assets/sdn/shaders/sprite_gl460.fs b/Game.Assets/sdn/shaders/sprite_gl460.fs new file mode 100644 index 00000000..f8e54ee4 --- /dev/null +++ b/Game.Assets/sdn/shaders/sprite_gl460.fs @@ -0,0 +1,52 @@ +#version 460 +// STATIC: "VERTEXCOLOR" "0..1" +// STATIC: "CONSTANTCOLOR" "0..1" +// STATIC: "SRGB" "0..1" +// DYNAMIC: "HDRENABLED" "0..1" +// DYNAMIC: "PIXELFOGTYPE" "0..2" + +layout(location = 0) in vec2 vs_BaseTexCoord; +layout(location = 1) in vec4 vs_Color; +layout(location = 2) in vec4 vs_WorldPos_ProjPosZ; + +layout(std140, binding = 6) uniform source_ps_constants { + vec4 ps_const[256]; +}; + +out vec4 fragColor; + +#include "common_gl460.fs" + +layout(binding = 0) uniform sampler2D TexSampler; + +#define g_Color ps_const[0] +#define g_HDRColorScale ps_const[1].x +#define g_EyePos_SpecExponent ps_const[11] +#define g_FogParams ps_const[12] + +void main() +{ + vec4 result; + vec4 color = texture(TexSampler, vs_BaseTexCoord); + +#if VERTEXCOLOR + color *= vs_Color; +#endif + +#if CONSTANTCOLOR + color *= g_Color; +#endif + +#if HDRENABLED + color.xyz *= g_HDRColorScale; +#endif + + float fogFactor = CalcPixelFogFactor(PIXELFOGTYPE, g_FogParams, g_EyePos_SpecExponent.xyz, vs_WorldPos_ProjPosZ.xyz, vs_WorldPos_ProjPosZ.w); +#if SRGB + result = FinalOutput(color, fogFactor, PIXELFOGTYPE, TONEMAP_SCALE_LINEAR); +#else + result = FinalOutput(color, fogFactor, PIXELFOGTYPE, TONEMAP_SCALE_GAMMA); +#endif + + fragColor = result; +} diff --git a/Game.Assets/sdn/shaders/sprite_gl460.vs b/Game.Assets/sdn/shaders/sprite_gl460.vs new file mode 100644 index 00000000..180a9bc4 --- /dev/null +++ b/Game.Assets/sdn/shaders/sprite_gl460.vs @@ -0,0 +1,43 @@ +#version 460 +// STATIC: "VERTEXCOLOR" "0..1" +// STATIC: "SRGB" "0..1" + +layout(location = 0) in vec3 v_Position; +layout(location = 2) in vec4 v_Color; +layout(location = 10) in vec4 v_TexCoord0; + +layout(std140, binding = 0) uniform source_matrices { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 modelMatrix; +}; + +#include "common_gl460.glsl" + +layout(location = 0) out vec2 vs_BaseTexCoord; +layout(location = 1) out vec4 vs_Color; +layout(location = 2) out vec4 vs_WorldPos_ProjPosZ; + +#include "common_clipplanes_gl460.vs" + +void main() +{ + vec3 worldPos = (modelMatrix * vec4(v_Position, 1.0)).xyz; + + vec4 projPos = projectionMatrix * viewMatrix * vec4(worldPos, 1.0); + gl_Position = projPos; + WriteUserClipDistances(gl_Position); + + vs_WorldPos_ProjPosZ = vec4(worldPos, projPos.z); + + vs_Color = vec4(1.0); +#if VERTEXCOLOR +#if SRGB + vs_Color = vec4(GammaToLinear(v_Color.rgb), v_Color.a); +#else + vs_Color = v_Color; +#endif +#endif + + vs_BaseTexCoord = v_TexCoord0.xy; +} diff --git a/Game.Client/C_BaseAnimating.cs b/Game.Client/C_BaseAnimating.cs index 89f0dc64..dec04ba6 100644 --- a/Game.Client/C_BaseAnimating.cs +++ b/Game.Client/C_BaseAnimating.cs @@ -11,8 +11,10 @@ using Source.Common.Formats.BSP; using Source.Common.Formats.Keyvalues; using Source.Common.Mathematics; +using Source.Common.Physics; using System; +using System.Collections; using System.Drawing; using System.Linq; using System.Net.NetworkInformation; @@ -177,6 +179,21 @@ public void StudioFrameAdvance() { GroundSpeed = (float)GetSequenceGroundSpeed(hdr, GetSequence()) * GetModelScale(); } + public override int VPhysicsGetObjectList(Span list){ + if (IsRagdoll()) { + int i; + for (i = 0; i < Ragdoll.RagdollBoneCount(); ++i) { + if (i >= list.Length) + break; + + list[i] = Ragdoll.GetElement(i); + } + return i; + } + + return base.VPhysicsGetObjectList(list); + } + public TimeUnit_t GetSequenceMoveDist(StudioHdr? studioHdr, int sequence) { Animation.GetSequenceLinearMotion(studioHdr, sequence, PoseParameter, out Vector3 vecReturn); @@ -828,7 +845,7 @@ public string GetSequenceName(int sequence) { return Animation.GetSequenceName(GetModelPtr(), sequence); } - public object? Ragdoll; // TODO + public ClientRagdoll? Ragdoll = null; public override void OnDataChanged(DataUpdateType updateType) { if (Ragdoll != null && GetSequence() != PrevSequence) { diff --git a/Game.Client/C_BaseCombatWeapon.cs b/Game.Client/C_BaseCombatWeapon.cs index d05286d1..ee088128 100644 --- a/Game.Client/C_BaseCombatWeapon.cs +++ b/Game.Client/C_BaseCombatWeapon.cs @@ -1,8 +1,9 @@ -using Game.Client.HUD; +using Game.Client.HUD; using Game.Shared; using Source; using Source.Common; +using Source.Common.Input; using System.Runtime.CompilerServices; @@ -20,6 +21,8 @@ public C_BaseCombatWeapon() { public virtual bool WasDropped() => Dropped; public virtual void SetDropped() => Dropped = true; public virtual void DrawHUD() { } + public virtual bool ShouldFreezeViewAngles() => false; + public virtual int KeyInput(int down, ButtonCode keynum, ReadOnlySpan currentBinding) => 1; public virtual void DrawHUDBackground() { } public override bool IsWeapon() => true; public static BaseCombatWeapon? GetActiveWeapon() { diff --git a/Game.Client/C_BaseEntity.cs b/Game.Client/C_BaseEntity.cs index 76d1f8fd..c65851dd 100644 --- a/Game.Client/C_BaseEntity.cs +++ b/Game.Client/C_BaseEntity.cs @@ -957,7 +957,17 @@ public C_BaseEntity() { IPhysicsObject? PhysicsObject = null!; public void VPhysicsUpdate(IPhysicsObject physics) { } public IPhysicsObject? VPhysicsGetObject() => PhysicsObject; - public int VPhysicsGetObjectList(Span list) => throw new NotImplementedException(); + public virtual int VPhysicsGetObjectList(Span list) { + IPhysicsObject? phys = VPhysicsGetObject(); + if (phys != null) { + Assert((phys.GetGameFlags() & PhysicsFlags.MultiObjectEntity) == 0); + if (list.Length > 0) { + list[0] = phys; + return 1; + } + } + return 0; + } public bool IsFloating() => false; @@ -1241,8 +1251,17 @@ public void GetRenderBounds(out Vector3 mins, out Vector3 maxs) { if (nModelType == ModelType.Studio || nModelType == ModelType.Brush) modelinfo.GetModelRenderBounds(GetModel(), out mins, out maxs); else { - // todo - mins = maxs = default; + if (GetRenderAngles() == CollisionProp().GetCollisionAngles()) { + mins = CollisionProp().OBBMins(); + maxs = CollisionProp().OBBMaxs(); + } + else { + Assert(CollisionProp().GetCollisionAngles() == vec3_angle); + if (CollisionProp().BoundingRadius() == 0.0f) + mins = maxs = vec3_origin; + else + MathLib.IRotateAABB(EntityToWorldTransform(), CollisionProp().OBBMins(), CollisionProp().OBBMaxs(), out mins, out maxs); + } } } diff --git a/Game.Client/ClientModeShared.cs b/Game.Client/ClientModeShared.cs index 0beeaa86..c9a87219 100644 --- a/Game.Client/ClientModeShared.cs +++ b/Game.Client/ClientModeShared.cs @@ -1,4 +1,4 @@ -using Game.Client.HUD; +using Game.Client.HUD; using Game.Shared; using Source; @@ -165,6 +165,15 @@ public virtual int KeyInput(int down, ButtonCode keynum, ReadOnlySpan curr if (HudElementKeyInput(down, keynum, currentBinding) == 0) return 0; +#if GMOD_DLL + if (surface.IsCursorVisible() && (keynum == ButtonCode.MouseWheelUp || keynum == ButtonCode.MouseWheelDown)) + return 0; +#endif + + BaseCombatWeapon? activeWeapon = C_BaseCombatWeapon.GetActiveWeapon(); + if (activeWeapon != null) + return activeWeapon.KeyInput(down, keynum, currentBinding); + return 1; } diff --git a/Game.Client/ClientThinkList.cs b/Game.Client/ClientThinkList.cs index fff70077..83d22ac7 100644 --- a/Game.Client/ClientThinkList.cs +++ b/Game.Client/ClientThinkList.cs @@ -287,10 +287,12 @@ private void CleanUpDeleteList() { pEntity.SetRemovalFlag(false); IClientThinkable? pThink = cl_entitylist.GetClientThinkableFromHandle(handle); - if (pThink != null) + if (pThink != null) pThink.Release(); } } + + DeleteList.Clear(); } private void AddEntityToFrameThinkList(ThinkEntry pEntry, bool bAlwaysChain, ref int count, Span ppFrameThinkList) { diff --git a/Game.Client/GarrysMod/C_GMOD_Player.cs b/Game.Client/GarrysMod/C_GMOD_Player.cs index cc600123..e5497824 100644 --- a/Game.Client/GarrysMod/C_GMOD_Player.cs +++ b/Game.Client/GarrysMod/C_GMOD_Player.cs @@ -74,6 +74,7 @@ public partial class C_GMOD_Player() : C_HL2MP_Player() public Vector3 WeaponColor; public virtual Vector3 GetPlayerColor() => PlayerColor; + public virtual Vector3 GetWeaponColor() => WeaponColor; public virtual void SetPlayerColor(in Vector3 color) { if (PlayerColor != color) diff --git a/Game.Client/HLClient.cs b/Game.Client/HLClient.cs index 9d48d6df..22f2fa46 100644 --- a/Game.Client/HLClient.cs +++ b/Game.Client/HLClient.cs @@ -800,9 +800,7 @@ public void GMOD_OnDrawSkybox() { throw new NotImplementedException(); } - public void IN_MouseWheelAnalog(int value) { - throw new NotImplementedException(); - } + public void IN_MouseWheelAnalog(int value) => input.MouseWheeled(value); public void GMOD_SignOnStateChanged(int userID, int oldState, int newState) { throw new NotImplementedException(); diff --git a/Game.Client/IInput.cs b/Game.Client/IInput.cs index 7963abf0..2d9142f6 100644 --- a/Game.Client/IInput.cs +++ b/Game.Client/IInput.cs @@ -20,6 +20,9 @@ public interface IInput void ExtraMouseSample(double frametime, bool active); void ActivateMouse(); void DeactivateMouse(); +#if GMOD_DLL + void MouseWheeled(int delta); +#endif void ClearStates(); void CAM_Think(); ref UserCmd GetUserCmd(int sequenceNumber); diff --git a/Game.Client/InMain.cs b/Game.Client/InMain.cs index d1697698..c67db0b8 100644 --- a/Game.Client/InMain.cs +++ b/Game.Client/InMain.cs @@ -276,6 +276,9 @@ public void CreateMove(int sequenceNumber, double inputSampleFrametime, bool act cmd.Buttons |= InButtons.Back; cmd.RandomSeed = (int)(MD5Value.PseudoRandom((uint)sequenceNumber) & 0x7fffffff); +#if GMOD_DLL + MouseWheel = 0; +#endif } private void ApplyExtras(ref UserCmd cmd) { @@ -497,6 +500,7 @@ private void ControllerMove(double frametime, ref UserCmd cmd) { private void MouseMove(ref UserCmd cmd) { engine.GetViewAngles(out QAngle viewangles); + QAngle originalViewangles = viewangles; view.StopPitchDrift(); if (!CameraInterceptingMouse && !Surface.IsCursorVisible()) { @@ -509,10 +513,18 @@ private void MouseMove(ref UserCmd cmd) { ResetMouse(); } +#if GMOD_DLL + if (C_BasePlayer.GetLocalPlayer()?.GetActiveWeapon()?.ShouldFreezeViewAngles() == true) + viewangles = originalViewangles; +#endif + engine.SetViewAngles(in viewangles); } private void ApplyMouse(ref QAngle viewangles, ref UserCmd cmd, float mouse_x, float mouse_y) { +#if GMOD_DLL + cmd.ScrollWheelSpeed = (sbyte)MouseWheel; +#endif if ((in_strafe.State & KeyButtonStateFlags.Down) == 0 || lookstrafe.GetInt() != 0) { if (CAM_IsThirdPerson() && thirdperson_platformer.GetInt() != 0) { if (mouse_x != 0) { diff --git a/Game.Client/InMouse.cs b/Game.Client/InMouse.cs index 4efed053..590ea928 100644 --- a/Game.Client/InMouse.cs +++ b/Game.Client/InMouse.cs @@ -1,6 +1,7 @@ using Source.Common.Commands; using Source.Common.GUI; using Source.Common.Launcher; +using Source.GUI.Controls; namespace Game.Client; @@ -43,10 +44,27 @@ public void DeactivateMouse() { AccumulatedMouseXMovement = 0; AccumulatedMouseYMovement = 0; +#if GMOD_DLL + MouseWheel = 0; +#endif launcher.SetWindowRelativeMouseMode(false); } } +#if GMOD_DLL + int MouseWheel; + + public void MouseWheeled(int delta) { + if (!Surface.IsCursorVisible()) { + MouseWheel += delta; + return; + } + + if (input.GetMouseOver() is Panel panel && panel.IsWorldClicker()) + MouseWheel += delta; + } +#endif + ConVar m_filter = new("0", FCvar.Archive, "Mouse filtering (set this to 1 to average the mouse over 2 frames)."); ConVar sensitivity = new("3", FCvar.Archive, "Mouse sensitivity.", 0.0001f, 1000); ConVar m_side = new("0.8", FCvar.Archive, "Mouse side factor.", 0.0001f, 1000); diff --git a/Game.Client/Ragdoll.cs b/Game.Client/Ragdoll.cs new file mode 100644 index 00000000..51aa6e54 --- /dev/null +++ b/Game.Client/Ragdoll.cs @@ -0,0 +1,266 @@ +global using static Game.Client.ClientRagdollGlobals; +global using static Game.Client.RagdollConvars; + +using Game.Shared; + +using Source; +using Source.Common; +using Source.Common.Commands; +using Source.Common.MaterialSystem; +using Source.Common.Mathematics; +using Source.Common.Physics; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Text; + +namespace Game.Client; + +public interface IRagdoll +{ + void RagdollBone(C_BaseEntity ent, Span bones, Span boneSimulated, BoneAccessor boneToWorld); + ref readonly Vector3 GetRagdollOrigin(); + void GetRagdollBounds(out Vector3 mins, out Vector3 maxs); + int RagdollBoneCount(); + IPhysicsObject? GetElement(int elementNum); + void DrawWireframe(); + void VPhysicsUpdate(IPhysicsObject obj); + bool TransformVectorToWorld(int boneIndex, in Vector3 temp, out Vector3 outVec); +} +public static class RagdollConvars +{ + public static readonly ConVar ragdoll_sleepaftertime = new("ragdoll_sleepaftertime", "5.0f", 0, "After this many seconds of being basically stationary, the ragdoll will go to sleep."); +} + +/// +/// Analog of CRagdoll +/// +public class ClientRagdoll : IRagdoll +{ + public void Init( + C_BaseEntity ent, + StudioHdr studiohdr, + in Vector3 forceVector, + int forceBone, + ReadOnlySpan deltaBones0, + ReadOnlySpan deltaBones1, + ReadOnlySpan currentBonePosition, + TimeUnit_t boneDt, + bool fixedConstraints = false + ) { + + } + + public void RagdollBone(C_BaseEntity ent, Span bones, Span boneSimulated, BoneAccessor boneToWorld) { + + } + public ref readonly Vector3 GetRagdollOrigin() { + Ragdoll.List[0].Object!.GetPosition(out Origin, out _); + return ref Origin; + } + public void GetRagdollBounds(out Vector3 mins, out Vector3 maxs) { + mins = Mins; + maxs = Maxs; + } + public int RagdollBoneCount() => Ragdoll.ListCount; + public IPhysicsObject? GetElement(int elementNum) { + return Ragdoll.List[elementNum].Object; + } + public void DrawWireframe() { + IMaterial? wireframe = materials.FindMaterial("shadertest/wireframevertexcolor", MaterialDefines.TEXTURE_GROUP_OTHER); + + int i; + Matrix3x4 matrix; + for (i = 0; i < Ragdoll.ListCount; i++) { + Color debugColor = new(0, 255, 255, 0); + + // draw the actual physics positions, not the cleaned up animation position + Ragdoll.List[i].Object!.GetPositionMatrix(out matrix); + PhysCollide collide = Ragdoll.List[i].Object!.GetCollide(); + engine.DebugDrawPhysCollide(collide, wireframe, matrix, debugColor); + } + } + public void VPhysicsUpdate(IPhysicsObject obj) { + if (LastUpdate == gpGlobals.CurTime) + return; + LastUpdate = gpGlobals.CurTime; + AllAsleep = RagdollIsAsleep(Ragdoll); + if (AllAsleep) { + // NOTE: This is the bbox of the ragdoll's physics + // It's not always correct to use for culling, but it sure beats + // using the radius box! + Vector3 origin = GetRagdollOrigin(); + RagdollComputeExactBbox(Ragdoll, origin, ref Mins, ref Maxs); + Mins -= origin; + Maxs -= origin; + } + else { + Mins.Init(-Radius, -Radius, -Radius); + Maxs.Init(Radius, Radius, Radius); + + if (Ragdoll.Group!.IsInErrorState()) { + C_BaseEntity? entity = (C_BaseEntity?)(Ragdoll.List[0].Object!.GetGameData()); + RagdollSolveSeparation(Ragdoll, entity); + } + } + + // See if we should go to sleep... + CheckSettleStationaryRagdoll(); + } + public bool TransformVectorToWorld(int boneIndex, in Vector3 temp, out Vector3 outVec) { + int listIndex = -1; + if (boneIndex >= 0 && boneIndex < Ragdoll.ListCount) { + for (int i = 0; i < Ragdoll.ListCount; ++i) { + if (Ragdoll.BoneIndex[i] == boneIndex) + listIndex = i; + } + if (listIndex != -1) { + Ragdoll.List[listIndex].Object.LocalToWorld(out outVec, in temp); + return true; + } + } + outVec = default; + return false; + } + + public void BuildRagdollBounds(C_BaseEntity ent) { + Vector3 mins, maxs, size; + modelinfo.GetModelBounds(ent.GetModel(), out mins, out maxs); + size = (maxs - mins) * 0.5f; + Radius = size.Length(); + + Mins.Init(-Radius, -Radius, -Radius); + Maxs.Init(Radius, Radius, Radius); + } + + public IPhysicsConstraintGroup? GetConstraintGroup() => Ragdoll.Group; + + public bool IsValid() => Ragdoll.ListCount > 0; + + public Ragdoll GetRagdoll() => Ragdoll; + + public void ResetRagdollSleepAfterTime() => LastOriginChangeTime = gpGlobals.CurTime; + public TimeUnit_t GetLastVPhysicsUpdateTime() => LastUpdate; + + void CheckSettleStationaryRagdoll() { + Vector3 delta = GetRagdollOrigin() - LastOrigin; + LastOrigin = GetRagdollOrigin(); + for (int i = 0; i < 3; ++i) { + // It's still moving... + if (MathF.Abs(delta[i]) > RAGDOLL_SLEEP_TOLERANCE) { + LastOriginChangeTime = gpGlobals.CurTime; + // Msg( "%d [%p] Still moving\n", gpGlobals->tickcount, this ); + return; + } + } + + // It's totally asleep, don't worry about forcing it to settle + if (AllAsleep) + return; + + // Msg( "%d [%p] Settling\n", gpGlobals->tickcount, this ); + + // It has stopped moving, see if it + TimeUnit_t dt = gpGlobals.CurTime - LastOriginChangeTime; + if (dt < ragdoll_sleepaftertime.GetFloat()) + return; + + // Msg( "%d [%p] FORCE SLEEP\n",gpGlobals->tickcount, this ); + + // Force it to go to sleep + PhysForceRagdollToSleep(); + } + void PhysForceRagdollToSleep() { + for (int i = 0; i < Ragdoll.ListCount; i++) { + if (Ragdoll.List[i].Object != null) { + PhysForceClearVelocity(Ragdoll.List[i].Object!); + Ragdoll.List[i].Object!.Sleep(); + } + } + } + + readonly Ragdoll Ragdoll = new(); + Vector3 Mins, Maxs; + Vector3 Origin; + float Radius; + TimeUnit_t LastUpdate; + bool AllAsleep; + Vector3 LastOrigin; + TimeUnit_t LastOriginChangeTime; +} + +public static class ClientRagdollGlobals +{ + public const float RAGDOLL_SLEEP_TOLERANCE = 1.0f; + public static ClientRagdoll CreateRagdoll( + C_BaseEntity ent, + StudioHdr studiohdr, + in Vector3 forceVector, + int forceBone, + ReadOnlySpan deltaBones0, + ReadOnlySpan deltaBones1, + ReadOnlySpan currentBonePosition, + TimeUnit_t dt, + bool fixedConstraints = false + ) { + ClientRagdoll ragdoll = new(); + ragdoll.Init(ent, studiohdr, forceVector, forceBone, deltaBones0, deltaBones1, currentBonePosition, dt, fixedConstraints); + + if (!ragdoll.IsValid()) + Msg($"Bad ragdoll for {studiohdr.Name()}"); + return null; + + return ragdoll; + } + + struct RagdollRemember + { + public C_BaseEntity Ragdoll; + public long TickCount; + } + struct RagdollMemoryList() + { + public readonly List List = []; + public long TickCount = 0; // is this even used? wtf? + public void Update() { + if (TickCount > gpGlobals.TickCount) { + List.Clear(); + return; + } + + for (int i = List.Count - 1; i >= 0; --i) + if (List[i].TickCount != gpGlobals.TickCount) + List.RemoveAt(i); + } + + public bool IsInList(C_BaseEntity ragdoll) { + for (int i = List.Count() - 1; i >= 0; --i) { + if (List[i].Ragdoll == ragdoll) + return true; + } + + return false; + } + + public void AddToList(C_BaseEntity ragdoll) { + Update(); + List.Add(new() { + Ragdoll = ragdoll, + TickCount = gpGlobals.TickCount + }); + } + } + + static RagdollMemoryList gRagdolls = new(); + + public static void NoteRagdollCreationTick(C_BaseEntity ragdoll) { + gRagdolls.AddToList(ragdoll); + } + + public static bool WasRagdollCreatedOnCurrentTick(C_BaseEntity ragdoll) { + gRagdolls.Update(); + return gRagdolls.IsInList(ragdoll); + } +} diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index 85ecbef8..d3fa2f68 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -1727,6 +1727,7 @@ public virtual void VPhysicsShadowCollision(int index, ref GameVCollisionEvent e public virtual void VPhysicsShadowUpdate(IPhysicsObject physics) { } public virtual BasePlayer? HasPhysicsAttacker(TimeUnit_t dt) => null; + public virtual void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { } public virtual void VPhysicsCollision(int index, ref GameVCollisionEvent ev) { int otherIndex = index == 0 ? 1 : 0; @@ -1822,7 +1823,7 @@ bool VPhysicsInitSetup() { return physicsObject; } - public int VPhysicsGetObjectList(Span list) { + public virtual int VPhysicsGetObjectList(Span list) { IPhysicsObject? phys = VPhysicsGetObject(); if (phys != null) { Assert((phys.GetGameFlags() & PhysicsFlags.MultiObjectEntity) == 0); diff --git a/Game.Server/FourWheelVehiclePhysics.cs b/Game.Server/FourWheelVehiclePhysics.cs new file mode 100644 index 00000000..7ad39684 --- /dev/null +++ b/Game.Server/FourWheelVehiclePhysics.cs @@ -0,0 +1,14 @@ +using Source.Common.Physics; + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Server; + +public class FourWheelVehiclePhysics +{ + public int VPhysicsGetObjectList(Span list) { + throw new NotImplementedException(); + } +} diff --git a/Game.Server/GarrysMod/GMOD_Player.cs b/Game.Server/GarrysMod/GMOD_Player.cs index cf0a0f95..c31e2386 100644 --- a/Game.Server/GarrysMod/GMOD_Player.cs +++ b/Game.Server/GarrysMod/GMOD_Player.cs @@ -69,6 +69,20 @@ public GMOD_Player() { public bool Transition; + public ReadOnlySpan GetPlayerInfo(ReadOnlySpan name, ReadOnlySpan defaultValue) => GetInfo(name); + + public float GetSnapAngles() => Math.Clamp((float)atof(GetPlayerInfo("gm_snapangles", "45")), -360f, 360f); + + public void SnapToGrid(ref Vector3 vec) { + float grid = Math.Clamp((float)atof(GetPlayerInfo("gm_snapgrid", "0")), 0f, 4096f); + if (grid <= 0) + return; + + vec.X = MathF.Round(vec.X / grid) * grid; + vec.Y = MathF.Round(vec.Y / grid) * grid; + vec.Z = MathF.Round(vec.Z / grid) * grid; + } + public override void InitialSpawn() { SetModel("models/player.mdl"); base.InitialSpawn(); diff --git a/Game.Server/Player.cs b/Game.Server/Player.cs index 691f00ab..2a22162c 100644 --- a/Game.Server/Player.cs +++ b/Game.Server/Player.cs @@ -385,6 +385,10 @@ public enum SF protected PlayerPhysFlag PhysicsFlags; public int Train; + int MouseWheelValue; + public int MouseWheel() => MouseWheelValue; + public void SetMouseWheel(int value) => MouseWheelValue = value; + public void SetPhysicsFlag(PlayerPhysFlag flag, bool set) { if (set) PhysicsFlags |= flag; @@ -1583,6 +1587,7 @@ public void ToggleDuck() { public virtual void PlayerRunCommand(UserCmd userCmd, IMoveHelper s_MoveHelperServer) { TouchedPhysObject = false; + MouseWheelValue = userCmd.ScrollWheelSpeed; if (pl.FixAngle == (int)FixAngle.None) MathLib.VectorCopy(userCmd.ViewAngles, out pl.ViewingAngle); diff --git a/Game.Server/PropVehicleDriveable.cs b/Game.Server/PropVehicleDriveable.cs index e474329e..ec71d9cc 100644 --- a/Game.Server/PropVehicleDriveable.cs +++ b/Game.Server/PropVehicleDriveable.cs @@ -1,8 +1,10 @@ using Game.Shared; using Source.Common; +using Source.Common.Physics; using System.Numerics; +using System.Runtime.CompilerServices; namespace Game.Server; @@ -60,6 +62,12 @@ public PropVehicleDriveable() { public partial Vector3 GunCrosshair { get; set; } [NetworkVar("m_bLocked")] public partial bool Locked { get; set; } + readonly FourWheelVehiclePhysics VehiclePhysics = new(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public FourWheelVehiclePhysics GetPhysics() => VehiclePhysics; public override bool IsVehicle() => true; + + public override int VPhysicsGetObjectList(Span list) { + return GetPhysics().VPhysicsGetObjectList(list); + } } diff --git a/Game.Server/Props.cs b/Game.Server/Props.cs index 1e6bf71c..bc105b89 100644 --- a/Game.Server/Props.cs +++ b/Game.Server/Props.cs @@ -153,7 +153,7 @@ public void CheckRemoveRagdolls() { throw new NotImplementedException(); } - public void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { + public override void SetPhysicsAttacker(BasePlayer? entity, TimeUnit_t time) { PhysicsAttacker.Set(entity); LastPhysicsInfluenceTime = time; } @@ -352,7 +352,7 @@ public partial class PhysicsProp : BreakableProp float ForceToEnableMotion; bool FirstCollisionAfterLaunch; - bool ThrownByPlayer; + internal bool ThrownByPlayer; public OutputEvent OnPhysGunPickup_ = new(); public OutputEvent OnPhysGunPunt = new(); diff --git a/Game.Server/RagdollProp.cs b/Game.Server/RagdollProp.cs index 6f1c0b8d..abff3f13 100644 --- a/Game.Server/RagdollProp.cs +++ b/Game.Server/RagdollProp.cs @@ -5,6 +5,7 @@ using Game.Shared; using System.Numerics; +using Source.Common.Physics; namespace Game.Server; [LinkEntityToClass("physics_prop_ragdoll")] @@ -45,4 +46,12 @@ public RagdollProp() { public static Ragdoll? Ragdoll_GetRagdoll(BaseEntity entity) => (entity as RagdollProp)?.GetRagdoll(); public override bool IsRagdollProp() => true; + + public override int VPhysicsGetObjectList(Span list) { + for (int i = 0; i < ragdoll.ListCount; i++) + if (i < list.Length) + list[i] = ragdoll.List[i].Object; + + return ragdoll.ListCount; + } } diff --git a/Game.Shared/GarrysMod/LuaEntity.cs b/Game.Shared/GarrysMod/LuaEntity.cs index 19d5a5ba..c477cdeb 100644 --- a/Game.Shared/GarrysMod/LuaEntity.cs +++ b/Game.Shared/GarrysMod/LuaEntity.cs @@ -962,6 +962,69 @@ static int Entity__SetModel(ILuaInterface lua) { return 0; } + const int MAX_PHYSOBJECT_LIST = 0x400; + + [LuaMethod] + static int Entity__GetPhysicsObject(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; +#if GAME_DLL + if (ent.Edict() != null && ent.EntIndex() != 0) + LuaPhysObj.Push_PhysObj(ent.VPhysicsGetObject()); + else + LuaPhysObj.Push_PhysObj(g_PhysWorldObject); +#else + LuaPhysObj.Push_PhysObj(ent.VPhysicsGetObject()); +#endif + return 1; + } + + [LuaMethod] + static int Entity__GetPhysicsObjectNum(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; +#if GAME_DLL + if (ent.Edict() == null || ent.EntIndex() == 0) { + LuaPhysObj.Push_PhysObj(g_PhysWorldObject); + return 1; + } +#endif + int index = (int)lua.GetNumber(2); + if (index < 0) { + DevWarning($"Entity:GetPhysicsObjectNum - Out of bounds physics object - min 0, got {index}\n"); + return 0; + } + + IPhysicsObject[] list = System.Buffers.ArrayPool.Shared.Rent(MAX_PHYSOBJECT_LIST); + try { + int count = ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_PHYSOBJECT_LIST)); + if (count == 0) + return 0; + + if (index >= count) { + DevWarning($"Entity:GetPhysicsObjectNum - Out of bounds physics object - max {count - 1}, got {index}\n"); + return 0; + } + + LuaPhysObj.Push_PhysObj(list[index]); + return 1; + } + finally { + System.Buffers.ArrayPool.Shared.Return(list, true); + } + } + + [LuaMethod] + static int Entity__GetPhysicsObjectCount(ILuaInterface lua) { + BaseEntity ent = Get_Entity(1, false)!; + IPhysicsObject[] list = System.Buffers.ArrayPool.Shared.Rent(MAX_PHYSOBJECT_LIST); + try { + lua.PushNumber(ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_PHYSOBJECT_LIST))); + return 1; + } + finally { + System.Buffers.ArrayPool.Shared.Return(list, true); + } + } + [LuaMethod] static int Entity__GetPos(ILuaInterface lua) { BaseEntity ent = Get_Entity(1, false)!; diff --git a/Game.Shared/GarrysMod/LuaObject.cs b/Game.Shared/GarrysMod/LuaObject.cs index 0ecc13ca..e11e35fd 100644 --- a/Game.Shared/GarrysMod/LuaObject.cs +++ b/Game.Shared/GarrysMod/LuaObject.cs @@ -713,7 +713,16 @@ public void SetMemberEntity(ReadOnlySpan name, IHandleEntity? ent) { } #endif public void SetEntity(IHandleEntity? ent) => throw new NotImplementedException(); +#if MENU_DLL public void SetMemberPhysObject(ReadOnlySpan name, IPhysicsObject? obj) => throw new NotImplementedException(); +#else + public void SetMemberPhysObject(ReadOnlySpan name, IPhysicsObject? obj) { + if (!isTable()) + return; + LuaPhysObj.Push_PhysObj(obj); + SetMember(name); + } +#endif } public class LuaTable : LuaObject diff --git a/Game.Shared/GarrysMod/LuaPhysObj.cs b/Game.Shared/GarrysMod/LuaPhysObj.cs new file mode 100644 index 00000000..261ece4e --- /dev/null +++ b/Game.Shared/GarrysMod/LuaPhysObj.cs @@ -0,0 +1,900 @@ +#if CLIENT_DLL || GAME_DLL +using Source.Common.GarrysMod.Lua; +using Source.Common.Mathematics; +using Source.Common.Physics; + +using System.Buffers; +using System.Numerics; + +#if CLIENT_DLL +namespace Game.Client.GarrysMod; +#else +namespace Game.Server.GarrysMod; +#endif + +public static partial class LuaPhysObj +{ + const int MAX_OBJECT_LIST = 0x400; + + [LuaClass(typeof(IPhysicsObject), NullError = "Tried to use a NULL physics object!")] + public static readonly LuaClass LC_PhysObj = new("PhysObj", LuaType.PhysObj, null, null); + + public static void Push_PhysObj(IPhysicsObject? obj) => LC_PhysObj.Push(obj); + + public static bool IsPhysicsObjectValid(IPhysicsObject? obj) { + if (obj == null) + return false; + + ReadOnlySpan objects = physenv.GetObjectList(); + for (int i = 0; i < objects.Length; i++) + if (objects[i] == obj) + return true; + + return false; + } + + public static IPhysicsObject? Get_IPhysicsObject(int stackPos, bool allowNull) { + IPhysicsObject? obj = LC_PhysObj.Get(stackPos) as IPhysicsObject; + if (obj != null) { + if (IsPhysicsObjectValid(obj)) + return obj; + obj = null; + } + + if (!allowNull) + g_Lua!.Error("Tried to use a NULL physics object!"); + + return obj; + } + + static bool IsFinite(in Vector3 v) => float.IsFinite(v.X) && float.IsFinite(v.Y) && float.IsFinite(v.Z); + + static BaseEntity? GetGameEntity(IPhysicsObject obj) => obj.GetGameData() as BaseEntity; + + [LuaMethod] + static int PhysObj__GetName(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + lua.PushString(obj.GetName()); + return 1; + } + + [LuaMethod] + static int PhysObj____eq(ILuaInterface lua) { + IPhysicsObject? a = Get_IPhysicsObject(1, false); + IPhysicsObject? b = Get_IPhysicsObject(2, false); + lua.PushBool(a == b); + return 1; + } + + [LuaMethod] + static int PhysObj____tostring(ILuaInterface lua) { + IPhysicsObject? obj = LC_PhysObj.Get(1) as IPhysicsObject; + if (obj == null || !IsPhysicsObjectValid(obj)) { + lua.PushString("[NULL PhysObject]"); + return 1; + } + + BaseEntity? ent = GetGameEntity(obj); + int index = ent != null ? ent.EntIndex() : -1; + string str = $"PhysObject [{obj.GetName()}][Entity {index}]"; + lua.PushString(str.Length > 0xFF ? str[..0xFF] : str); + return 1; + } + + [LuaMethod] + static int PhysObj__IsValid(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, true); + lua.PushBool(obj != null && IsPhysicsObjectValid(obj)); + return 1; + } + + [LuaMethod] + static int PhysObj__Wake(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.Wake(); + return 0; + } + + [LuaMethod] + static int PhysObj__Sleep(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.Sleep(); + return 0; + } + + [LuaMethod] + static int PhysObj__IsAsleep(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsAsleep()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetEntity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent == null) + return 0; + + LuaEntity.Push_Entity(ent); + return 1; + } + + [LuaMethod] + static int PhysObj__SetMass(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float mass = (float)lua.GetNumber(2); + if (0.1 >= mass) + mass = 1.0f; + else if (mass > 50000.0f) + mass = 50000.0f; + + obj.SetMass(mass); + return 0; + } + + [LuaMethod] + static int PhysObj__GetMass(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetMass()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInvMass(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetInvMass()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetEnergy(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetEnergy()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInertia(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetInertia()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetInvInertia(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetInvInertia()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetInertia(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 inertia = LuaVector.Get_Vector(2); + if (!IsFinite(inertia)) + return 0; + + if (inertia.X == 0) + inertia.X = 1; + if (inertia.Y == 0) + inertia.Y = 1; + if (inertia.Z == 0) + inertia.Z = 1; + + if (obj.IsMotionEnabled()) { + obj.SetInertia(inertia); + return 0; + } + + obj.EnableMotion(true); + obj.SetInertia(inertia); + obj.EnableMotion(false); + return 0; + } + + [LuaMethod] + static int PhysObj__SetDamping(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float speed = (float)lua.GetNumber(2); + float rot = (float)lua.GetNumber(3); + obj.SetDamping(ref speed, ref rot); + return 0; + } + + [LuaMethod] + static int PhysObj__GetDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out float speed, out float rot); + lua.PushNumber(speed); + lua.PushNumber(rot); + return 2; + } + + [LuaMethod] + static int PhysObj__GetSpeedDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out float speed, out _); + lua.PushNumber(speed); + return 1; + } + + [LuaMethod] + static int PhysObj__GetRotDamping(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetDamping(out _, out float rot); + lua.PushNumber(rot); + return 1; + } + + [LuaMethod] + static int PhysObj__EnableCollisions(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableCollisions(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableGravity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableGravity(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableDrag(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.EnableDrag(lua.GetBool(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__EnableMotion(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + bool enable = lua.GetBool(2); + + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) { + IPhysicsObject[] list = ArrayPool.Shared.Rent(MAX_OBJECT_LIST); + try { + int count = ent.VPhysicsGetObjectList(list.AsSpan(0, MAX_OBJECT_LIST)); + for (int i = 0; i < count; i++) + list[i].EnableMotion(enable); + } + finally { + ArrayPool.Shared.Return(list, true); + } + } + + obj.EnableMotion(enable); + return 0; + } + + [LuaMethod] + static int PhysObj__IsMotionEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsMotionEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsCollisionEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsCollisionEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsGravityEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsGravityEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsDragEnabled(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsDragEnabled()); + return 1; + } + + [LuaMethod] + static int PhysObj__IsMoveable(ILuaInterface lua) { + lua.PushBool(Get_IPhysicsObject(1, false)!.IsMoveable()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetMaterial(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + int index = (int)physprops.GetSurfaceIndex(lua.GetString(2) ?? ""); + if (index > 0) + obj.SetMaterialIndex(index); + return 0; + } + + [LuaMethod] + static int PhysObj__GetMaterial(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + ReadOnlySpan name = physprops.GetPropName(obj.GetMaterialIndex()); + lua.PushString(name.IsEmpty ? "default" : name); + return 1; + } + + [LuaMethod] + static int PhysObj__LocalToWorld(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 local = LuaVector.Get_Vector(2); + obj.LocalToWorld(out Vector3 world, local); + LuaVector.Push_Vector(world); + return 1; + } + + [LuaMethod] + static int PhysObj__WorldToLocal(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 world = LuaVector.Get_Vector(2); + obj.WorldToLocal(out Vector3 local, world); + LuaVector.Push_Vector(local); + return 1; + } + + [LuaMethod] + static int PhysObj__LocalToWorldVector(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 local = LuaVector.Get_Vector(2); + obj.LocalToWorldVector(out Vector3 world, local); + LuaVector.Push_Vector(world); + return 1; + } + + [LuaMethod] + static int PhysObj__WorldToLocalVector(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 world = LuaVector.Get_Vector(2); + obj.WorldToLocalVector(out Vector3 local, world); + LuaVector.Push_Vector(local); + return 1; + } + + [LuaMethod] + static int PhysObj__ApplyTorqueCenter(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 torque = LuaVector.Get_Vector(2); + if (IsFinite(torque)) + obj.ApplyTorqueCenter(torque); + return 0; + } + + [LuaMethod] + static int PhysObj__ApplyForceCenter(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + if (IsFinite(force)) + obj.ApplyForceCenter(force); + return 0; + } + + [LuaMethod] + static int PhysObj__ApplyForceOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + if (IsFinite(force)) { + Vector3 position = LuaVector.Get_Vector(3); + obj.ApplyForceOffset(force, position); + } + return 0; + } + + [LuaMethod] + static int PhysObj__CalculateForceOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + Vector3 position = LuaVector.Get_Vector(3); + obj.CalculateForceOffset(force, position, out Vector3 centerForce, out Vector3 centerTorque); + LuaVector.Push_Vector(centerForce); + LuaVector.Push_Vector(centerTorque); + return 2; + } + + [LuaMethod] + static int PhysObj__CalculateVelocityOffset(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 force = LuaVector.Get_Vector(2); + Vector3 position = LuaVector.Get_Vector(3); + obj.CalculateVelocityOffset(force, position, out Vector3 centerVelocity, out Vector3 centerAngularVelocity); + LuaVector.Push_Vector(centerVelocity); + LuaVector.Push_Vector(centerAngularVelocity); + return 2; + } + + [LuaMethod] + static int PhysObj__SetPos(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) + return 0; + + Vector3 pos = LuaVector.Get_Vector(2); + bool teleport = lua.GetType(3) != LuaType.Nil && lua.GetBool(3); + + float max = MAX_COORD_FLOAT * 8; + if (!(-max < pos.X && pos.X < max && -max < pos.Y && pos.Y < max && -max < pos.Z && pos.Z < max)) { + Warning($"PhysObj:SetPos( {pos.X:F6} {pos.Y:F6} {pos.Z:F6} ): Ignoring unreasonable position.\n"); + return 0; + } + + obj.GetPosition(out _, out QAngle angles); + obj.SetPosition(pos, angles, teleport); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngles(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + if (ent != null && ent.IsRagdollProp()) + return 0; + + QAngle angles = LuaAngle.Get_Angle(2); + float max = 360000f * 360000f; + if (!(-max < angles.X && angles.X < max && -max < angles.Y && angles.Y < max && -max < angles.Z && angles.Z < max)) { + Warning($"PhysObj:SetAngles( {angles.X:F6} {angles.Y:F6} {angles.Z:F6} ): Ignoring unreasonable angles.\n"); + return 0; + } + + obj.GetPosition(out Vector3 pos, out _); + obj.SetPosition(pos, angles, true); + return 0; + } + + [LuaMethod] + static int PhysObj__GetPos(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPosition(out Vector3 pos, out _); + LuaVector.Push_Vector(pos); + return 1; + } + + [LuaMethod] + static int PhysObj__GetAngles(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPosition(out _, out QAngle angles); + LuaAngle.Push_Angle(angles); + return 1; + } + + [LuaMethod] + static int PhysObj__SetVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (!IsFinite(velocity)) + return 0; + + obj.GetVelocity(out _, out Vector3 angular); + obj.SetVelocity(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetVelocityInstantaneous(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (!IsFinite(velocity)) + return 0; + + obj.GetVelocity(out _, out Vector3 angular); + obj.SetVelocityInstantaneous(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (!IsFinite(angular)) + return 0; + + obj.GetVelocity(out Vector3 velocity, out _); + obj.SetVelocity(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleVelocityInstantaneous(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (!IsFinite(angular)) + return 0; + + obj.GetVelocity(out Vector3 velocity, out _); + obj.SetVelocityInstantaneous(velocity, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__GetVelocity(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetVelocity(out Vector3 velocity, out _); + LuaVector.Push_Vector(velocity); + return 1; + } + + [LuaMethod] + static int PhysObj__GetVelocityAtPoint(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 point = LuaVector.Get_Vector(2); + obj.GetVelocityAtPoint(point, out Vector3 velocity); + LuaVector.Push_Vector(velocity); + return 1; + } + + [LuaMethod] + static int PhysObj__GetAngleVelocity(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetVelocity(out _, out Vector3 angular); + LuaVector.Push_Vector(angular); + return 1; + } + + [LuaMethod] + static int PhysObj__AddVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 velocity = LuaVector.Get_Vector(2); + if (IsFinite(velocity)) + obj.AddVelocity(velocity, default); + return 0; + } + + [LuaMethod] + static int PhysObj__AddAngleVelocity(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 angular = LuaVector.Get_Vector(2); + if (IsFinite(angular)) + obj.AddVelocity(default, angular); + return 0; + } + + [LuaMethod] + static int PhysObj__ComputeShadowControl(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + LuaObject table = new(2, (LuaType)(-1)); + if (!table.isTable()) { + lua.TypeError("table", 2); + table.UnReference(); + return 0; + } + + HLShadowControlParams parms = default; + float secondsToArrive = table.GetMemberFloat("secondstoarrive", (float)(gpGlobals.IntervalPerTick * 2)); + float delta = table.GetMemberFloat("delta", 0.1f); + parms.TargetPosition = table.GetMemberVector("pos", default); + parms.MaxAngular = table.GetMemberFloat("maxangular", 1.0f); + parms.MaxDampAngular = table.GetMemberFloat("maxangulardamp", 1.0f); + parms.MaxSpeed = table.GetMemberFloat("maxspeed", 1.0f); + parms.MaxDampSpeed = table.GetMemberFloat("maxspeeddamp", 1.0f); + parms.DampFactor = table.GetMemberFloat("dampfactor", 1.0f); + parms.TeleportDistance = table.GetMemberFloat("teleportdistance", 0.0f); + parms.TargetRotation = table.GetMemberAngle("angle", default); + + if (secondsToArrive != 0) + obj.ComputeShadowControl(parms, secondsToArrive, delta); + else + lua.Error("ComputeShadowControl Error! secondstoarrive cannot be 0!\n"); + + table.UnReference(); + return 0; + } + + [LuaMethod] + static int PhysObj__SetDragCoefficient(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float drag = (float)lua.GetNumber(2); + obj.SetDragCoefficient(drag, null); + return 0; + } + + [LuaMethod] + static int PhysObj__SetAngleDragCoefficient(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + float drag = (float)lua.GetNumber(2); + obj.SetDragCoefficient(null, drag); + return 0; + } + + [LuaMethod] + static int PhysObj__SetBuoyancyRatio(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.SetBuoyancyRatio((float)lua.GetNumber(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetBuoyancyRatio(ILuaInterface lua) { + lua.PushNumber(Get_IPhysicsObject(1, false)!.GetBuoyancyRatio()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetMassCenter(ILuaInterface lua) { + LuaVector.Push_Vector(Get_IPhysicsObject(1, false)!.GetMassCenterLocalSpace()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetContents(ILuaInterface lua) { + lua.PushNumber((double)Get_IPhysicsObject(1, false)!.GetContents()); + return 1; + } + + [LuaMethod] + static int PhysObj__SetContents(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + obj.SetContents((uint)(long)lua.GetNumber(2)); + return 0; + } + + [LuaMethod] + static int PhysObj__OutputDebugInfo(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.OutputDebugInfo(); + return 0; + } + + [LuaMethod] + static int PhysObj__AlignAngles(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + QAngle from = LuaAngle.Get_Angle(2); + QAngle to = LuaAngle.Get_Angle(3); + + obj.GetPosition(out _, out QAngle physAngles); + + MathLib.AngleMatrix(from, out Matrix3x4 fromMatrix); + MathLib.AngleMatrix(physAngles, out Matrix3x4 physMatrix); + MathLib.MatrixInvert(physMatrix, out Matrix3x4 physInverse); + + Matrix4x4 a = physInverse; + Matrix4x4 b = fromMatrix; + MathLib.MatrixMultiply(a, b, out Matrix4x4 local); + MathLib.MatrixToAngles(local, out QAngle localAngles); + + MathLib.AngleMatrix(to, out Matrix3x4 toMatrix); + MathLib.AngleMatrix(localAngles, out Matrix3x4 localMatrix); + MathLib.MatrixInvert(localMatrix, out Matrix3x4 localInverse); + + Matrix4x4 c = toMatrix; + Matrix4x4 d = localInverse; + MathLib.MatrixMultiply(c, d, out Matrix4x4 result); + MathLib.MatrixToAngles(result, out QAngle resultAngles); + + LuaAngle.Push_Angle(resultAngles); + return 1; + } + + [LuaMethod] + static int PhysObj__RotateAroundAxis(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 axis = LuaVector.Get_Vector(2); + float degrees = (float)lua.GetNumber(3); + + obj.GetPosition(out _, out QAngle physAngles); + MathLib.AngleMatrix(physAngles, out Matrix3x4 physMatrix); + Matrix4x4 phys = physMatrix; + + MathLib.MatrixBuildRotationAboutAxis(out Matrix4x4 rotation, axis, degrees); + MathLib.MatrixMultiply(rotation, phys, out Matrix4x4 result); + MathLib.MatrixToAngles(result, out QAngle resultAngles); + + LuaAngle.Push_Angle(resultAngles); + return 1; + } + + [LuaMethod] + static int PhysObj__UpdateShadow(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + Vector3 pos = LuaVector.Get_Vector(2); + QAngle angles = LuaAngle.Get_Angle(3); + + float max = MAX_COORD_FLOAT * 8; + if (!(-max < pos.X && pos.X < max && -max < pos.Y && pos.Y < max && -max < pos.Z && pos.Z < max)) { + Warning($"PhysObj:UpdateShadow( {pos.X:F6} {pos.Y:F6} {pos.Z:F6} ): Ignoring unreasonable position.\n"); + return 0; + } + + float maxAngle = 360000f * 360000f; + if (!(-maxAngle < angles.X && angles.X < maxAngle && -maxAngle < angles.Y && angles.Y < maxAngle && -maxAngle < angles.Z && angles.Z < maxAngle)) { + Warning($"PhysObj:UpdateShadow( {angles.X:F6} {angles.Y:F6} {angles.Z:F6} ): Ignoring unreasonable angles.\n"); + return 0; + } + + obj.UpdateShadow(pos, angles, false, (float)lua.GetNumber(4)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetShadowPos(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetShadowPosition(out Vector3 pos, out _); + LuaVector.Push_Vector(pos); + return 1; + } + + [LuaMethod] + static int PhysObj__GetShadowAngles(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetShadowPosition(out _, out QAngle angles); + LuaAngle.Push_Angle(angles); + return 1; + } + + [LuaMethod] + static int PhysObj__IsPenetrating(ILuaInterface lua) { + PhysicsFlags flags = Get_IPhysicsObject(1, false)!.GetGameFlags(); + lua.PushBool((((ushort)flags >> 6) & 1) != 0); + return 1; + } + + [LuaMethod] + static int PhysObj__HasGameFlag(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + ushort flags = (ushort)obj.GetGameFlags(); + lua.PushBool((flags & (int)lua.GetNumber(2)) != 0); + return 1; + } + + [LuaMethod] + static int PhysObj__AddGameFlag(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, false); + ushort flag = (ushort)(int)lua.GetNumber(2); + if (obj != null) + obj.SetGameFlags((PhysicsFlags)(ushort)((ushort)obj.GetGameFlags() | flag)); + return 0; + } + + [LuaMethod] + static int PhysObj__ClearGameFlag(ILuaInterface lua) { + IPhysicsObject? obj = Get_IPhysicsObject(1, false); + ushort flag = (ushort)(int)lua.GetNumber(2); + if (obj != null) + obj.SetGameFlags((PhysicsFlags)(ushort)((ushort)obj.GetGameFlags() & ~flag)); + return 0; + } + + [LuaMethod] + static int PhysObj__GetAABB(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + physcollision.CollideGetAABB(out Vector3 mins, out Vector3 maxs, collide, vec3_origin, vec3_angle); + LuaVector.Push_Vector(mins); + LuaVector.Push_Vector(maxs); + return 2; + } + + [LuaMethod] + static int PhysObj__GetVolume(ILuaInterface lua) { + PhysCollide? collide = Get_IPhysicsObject(1, false)!.GetCollide(); + if (collide == null) + return 0; + + lua.PushNumber(physcollision.CollideVolume(collide)); + return 1; + } + + [LuaMethod] + static int PhysObj__GetSurfaceArea(ILuaInterface lua) { + PhysCollide? collide = Get_IPhysicsObject(1, false)!.GetCollide(); + if (collide == null) + return 0; + + lua.PushNumber(physcollision.CollideSurfaceArea(collide)); + return 1; + } + +#if GAME_DLL + [LuaMethod] + static int PhysObj__GetStress(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + BaseEntity? ent = GetGameEntity(obj); + CalculateObjectStress(obj, ent!, out VPhysicsObjectStress stress); + lua.PushNumber(stress.ExertedStress); + lua.PushNumber(stress.ReceivedStress); + return 2; + } +#endif + + [LuaMethod] + static int PhysObj__GetMesh(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + int count = physcollision.CreateDebugMesh(collide, out Span verts); + if (verts.IsEmpty) + return 0; + + LuaTable table = new(null, (uint)count); + for (int i = 0; i < count; i++) { + LuaTable entry = new(null, 0); + entry.SetMemberVector("pos", verts[i]); + table.SetMember((float)(i + 1), entry); + entry.UnReference(); + } + + physcollision.DestroyDebugMesh(count, verts); + table.Push(); + table.UnReference(); + return 1; + } + + [LuaMethod] + static int PhysObj__GetMeshConvexes(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + PhysCollide? collide = obj.GetCollide(); + if (collide == null) + return 0; + + ICollisionQuery? query = physcollision.CreateQueryModel(collide); + if (query == null) + return 0; + + int convexCount = query.ConvexCount(); + LuaTable table = new(null, (uint)convexCount); + Span tri = stackalloc Vector3[3]; + for (int c = 0; c < convexCount; c++) { + int triCount = query.TriangleCount(c); + LuaTable convex = new(null, 0); + for (int t = 0; t < triCount; t++) { + tri.Clear(); + query.GetTriangleVerts(c, t, tri); + for (int v = 0; v < 3; v++) { + LuaTable vertex = new(null, 0); + vertex.SetMemberVector("pos", tri[v]); + convex.SetMember((float)(t * 3 + v + 1), vertex); + vertex.UnReference(); + } + } + + table.SetMember((float)(c + 1), convex); + convex.UnReference(); + } + + physcollision.DestroyQueryModel(query); + table.Push(); + table.UnReference(); + return 1; + } + + [LuaMethod] + static int PhysObj__RecheckCollisionFilter(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.RecheckCollisionFilter(); + return 0; + } + + [LuaMethod] + static int PhysObj__GetPositionMatrix(ILuaInterface lua) { + Get_IPhysicsObject(1, false)!.GetPositionMatrix(out Matrix3x4 matrix); + Matrix4x4 vmatrix = matrix; + LuaVMatrix.Push_VMatrix(vmatrix); + return 1; + } + + [LuaMethod] + static int PhysObj__GetIndex(ILuaInterface lua) { + lua.PushNumber((double)Get_IPhysicsObject(1, false)!.GetGameIndex()); + return 1; + } + + [LuaMethod] + static int PhysObj__GetFrictionSnapshot(ILuaInterface lua) { + IPhysicsObject obj = Get_IPhysicsObject(1, false)!; + LuaTable table = new(null, 0); + IPhysicsFrictionSnapshot snapshot = obj.CreateFrictionSnapshot(); + + int index = 0; + while (snapshot.IsValid()) { + LuaTable entry = new(null, 0); + entry.SetMemberPhysObject("Other", snapshot.GetObject(1)); + entry.SetMember("EnergyAbsorbed", snapshot.GetEnergyAbsorbed()); + entry.SetMember("FrictionCoefficient", snapshot.GetFrictionCoefficient()); + entry.SetMember("NormalForce", snapshot.GetNormalForce()); + snapshot.GetSurfaceNormal(out Vector3 normal); + entry.SetMemberVector("Normal", normal); + snapshot.GetContactPoint(out Vector3 contact); + entry.SetMemberVector("ContactPoint", contact); + entry.SetMember("Material", (float)snapshot.GetMaterial(0)); + entry.SetMember("MaterialOther", (float)snapshot.GetMaterial(1)); + index++; + table.SetMember((float)index, entry); + snapshot.NextFrictionData(); + entry.UnReference(); + } + + obj.DestroyFrictionSnapshot(snapshot); + table.Push(); + table.UnReference(); + return 1; + } +} +#endif diff --git a/Game.Shared/PhysicsShared.cs b/Game.Shared/PhysicsShared.cs index c4475843..404bff1f 100644 --- a/Game.Shared/PhysicsShared.cs +++ b/Game.Shared/PhysicsShared.cs @@ -197,6 +197,26 @@ public static PhysicsFlags PhysClearGameFlags(IPhysicsObject phys, PhysicsFlags return flags; } + public static bool PhysHasContactWithOtherInDirection(IPhysicsObject physics, in Vector3 dir) { + bool hit = false; + object? gameData = physics.GetGameData(); + IPhysicsFrictionSnapshot snapshot = physics.CreateFrictionSnapshot(); + while (snapshot.IsValid()) { + IPhysicsObject? other = snapshot.GetObject(1); + if (other!.GetGameData() != gameData) { + snapshot.GetSurfaceNormal(out Vector3 normal); + if (Vector3.Dot(normal, dir) > 0) { + hit = true; + break; + } + } + snapshot.NextFrictionData(); + } + physics.DestroyFrictionSnapshot(snapshot); + + return hit; + } + public static void PhysRecheckObjectPair(IPhysicsObject obj0, IPhysicsObject obj1) { if (!obj0.IsStatic()) obj0.RecheckCollisionFilter(); diff --git a/Game.Shared/RagdollShared.cs b/Game.Shared/RagdollShared.cs index ac2d35fa..f95997e3 100644 --- a/Game.Shared/RagdollShared.cs +++ b/Game.Shared/RagdollShared.cs @@ -1,4 +1,10 @@ +#if CLIENT_DLL || GAME_DLL +global using static Game.Shared.SharedRagdollGlobals; + using Source; +using Source.Common; +using Source.Common.Formats.BSP; +using Source.Common.Mathematics; using Source.Common.Physics; using System.Numerics; @@ -22,6 +28,9 @@ public struct RagdollAnimatedFriction public int MaxAnimatedFriction; } +/// +/// analog of ragdoll_t, NOT CRagdoll!!! +/// public class Ragdoll { public const int RAGDOLL_MAX_ELEMENTS = 32; @@ -35,3 +44,486 @@ public class Ragdoll public InlineArray32 BoneIndex; public RagdollAnimatedFriction AnimFriction; } + +public ref struct RagdollParams +{ + public object? GameData; + public VCollide Collide; + public StudioHdr StudioHdr; + public int ModelIndex; + public Vector3 ForcePosition; + public Vector3 ForceVector; + public int ForceBoneIndex; + public ReadOnlySpan CurrentBones; + public float JointFrictionScale; + public bool AllowStretch; + public bool FixedConstraints; +} + +class RagdollCollisionRules(IPhysicsCollisionSet set) : IVPhysicsKeyHandler +{ + readonly IPhysicsCollisionSet Set = set; + bool SelfCollisions = true; + + public void ParseKeyValue(ref T data, ReadOnlySpan key, ReadOnlySpan value) { + if (strcmpi(key, "selfcollisions") == 0) { + Assert(atoi(value) == 0); + SelfCollisions = false; + } + else if (strcmpi(key, "collisionpair") == 0) { + if (SelfCollisions) { + nexttoken(out ReadOnlySpan token, value, ',', out ReadOnlySpan str); + int index0 = atoi(token); + nexttoken(out token, str, ',', out _); + int index1 = atoi(token); + + Set.EnableCollisions(index0, index1); + } + else + Assert(false); + } + } + + public void SetDefaults(ref T data) { } +} + +class RagdollAnimatedFrictionParser(Ragdoll ragdoll) : IVPhysicsKeyHandler +{ + readonly Ragdoll Ragdoll = ragdoll; + + public void ParseKeyValue(ref T data, ReadOnlySpan key, ReadOnlySpan value) { + if (strcmpi(key, "animfrictionmin") == 0) + Ragdoll.AnimFriction.MinAnimatedFriction = atoi(value); + else if (strcmpi(key, "animfrictionmax") == 0) + Ragdoll.AnimFriction.MaxAnimatedFriction = atoi(value); + else if (strcmpi(key, "animfrictiontimein") == 0) + Ragdoll.AnimFriction.FrictionTimeIn = strtof(value, out _); + else if (strcmpi(key, "animfrictiontimeout") == 0) + Ragdoll.AnimFriction.FrictionTimeOut = strtof(value, out _); + else if (strcmpi(key, "animfrictiontimehold") == 0) + Ragdoll.AnimFriction.FrictionTimeHold = strtof(value, out _); + } + + public void SetDefaults(ref T data) { } +} + +public static class SharedRagdollGlobals +{ + public const int RAGDOLL_MAX_ELEMENTS = 24; + public const int RAGDOLL_INDEX_BITS = 5;// NOTE 1<= RAGDOLL_MAX_ELEMENTS + public const float CORE_DISSOLVE_FADE_START = 0.2f; + public const float CORE_DISSOLVE_MODEL_FADE_START = 0.1f; + public const float CORE_DISSOLVE_MODEL_FADE_LENGTH = 0.05f; + public const float CORE_DISSOLVE_FADEIN_LENGTH = 0.1f; + + static void ParseCustomBlock(IVPhysicsKeyParser parse, IVPhysicsKeyHandler handler) { + object? custom = handler; + parse.ParseCustom(ref custom, handler); + } + + public static void RagdollSetupAnimatedFriction(IPhysicsEnvironment physEnv, Ragdoll ragdoll, int modelIndex) { + VCollide? collide = modelinfo.GetVCollide(modelIndex); + + if (collide != null) { + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + + if (strcmpi(block, "animatedfriction") == 0) + ParseCustomBlock(parse, new RagdollAnimatedFrictionParser(ragdoll)); + else + parse.SkipBlock(); + } + + physcollision.VPhysicsKeyParserDestroy(parse); + } + } + + static void RagdollAddSolid(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms, ref Solid solid) { + if (solid.Index >= 0 && solid.Index < parms.Collide.SolidCount) { + Assert(ragdoll.ListCount == solid.Index); + ReadOnlySpan solidName = ((ReadOnlySpan)solid.Name).SliceNullTerminatedString(); + int boneIndex = BoneSetup.Studio_BoneIndexByName(parms.StudioHdr, solidName); + ragdoll.BoneIndex[ragdoll.ListCount] = boneIndex; + + if (boneIndex >= 0) { + if (parms.FixedConstraints) + solid.Params.Mass = 1000.0f; + + solid.Params.RotInertiaLimit = 0.1f; + solid.Params.GameData = parms.GameData; + int surfaceData = (int)physprops.GetSurfaceIndex(((ReadOnlySpan)solid.SurfaceProp).SliceNullTerminatedString()); + + if (surfaceData < 0) + surfaceData = (int)physprops.GetSurfaceIndex("default"); + + solid.Params.Name = parms.StudioHdr.Name(); + ref RagdollElement element = ref ragdoll.List[ragdoll.ListCount]; + element.Object = physEnv.CreatePolyObject(parms.Collide.Solids![solid.Index]!, surfaceData, vec3_origin, vec3_angle, ref solid.Params); + element.Object!.SetPositionMatrix(parms.CurrentBones[boneIndex], true); + element.ParentIndex = -1; + element.Object.SetGameIndex((ushort)ragdoll.ListCount); + + ragdoll.ListCount++; + } + else + Msg($"CRagdollProp::CreateObjects: Couldn't Lookup Bone {solidName}\n"); + } + } + + static void RagdollAddConstraint(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms, ref ConstraintRagdollParams constraint) { + if (constraint.ChildIndex == constraint.ParentIndex) { + DevMsg(1, $"Bogus constraint on ragdoll {parms.StudioHdr.Name()}\n"); + constraint.ChildIndex = -1; + constraint.ParentIndex = -1; + } + if (constraint.ChildIndex >= 0 && constraint.ParentIndex >= 0) { + Assert(constraint.ChildIndex < ragdoll.ListCount); + + ref RagdollElement childElement = ref ragdoll.List[constraint.ChildIndex]; + childElement.ParentIndex = constraint.ParentIndex; + + if (parms.JointFrictionScale > 0) { + for (int k = 0; k < 3; k++) + constraint.Axes[k].Torque *= parms.JointFrictionScale; + } + + BoneSetup.Studio_CalcBoneToBoneTransform(parms.StudioHdr, ragdoll.BoneIndex[constraint.ChildIndex], ragdoll.BoneIndex[constraint.ParentIndex], out constraint.ConstraintToAttached); + MathLib.MatrixGetColumn(constraint.ConstraintToAttached, 3, out childElement.OriginParentSpace); + MathLib.SetIdentityMatrix(out constraint.ConstraintToReference); + IPhysicsObject parentObject = ragdoll.List[constraint.ParentIndex].Object!; + if (parms.FixedConstraints) { + ConstraintFixedParams fixedParams = default; + fixedParams.Defaults(); + fixedParams.InitWithCurrentObjectState(childElement.Object!, parentObject); + fixedParams.Constraint.Defaults(); + childElement.Constraint = physEnv.CreateFixedConstraint(childElement.Object!, parentObject, ragdoll.Group!, fixedParams); + } + else + childElement.Constraint = physEnv.CreateRagdollConstraint(childElement.Object!, parentObject, ragdoll.Group!, constraint); + } + } + + static void RagdollCreateObjects(IPhysicsEnvironment physEnv, Ragdoll ragdoll, in RagdollParams parms) { + ragdoll.ListCount = 0; + ragdoll.Group = null; + ragdoll.AllowStretch = parms.AllowStretch; + ragdoll.List = default; + ragdoll.AnimFriction = default; + + if (parms.Collide == null || parms.Collide.SolidCount > RAGDOLL_MAX_ELEMENTS) + return; + + ConstraintGroupParams group = default; + group.Defaults(); + ragdoll.Group = physEnv.CreateConstraintGroup(group); + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(parms.Collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "solid") == 0) { + Solid solid = default; + + parse.ParseSolid(ref solid, g_SolidSetup); + RagdollAddSolid(physEnv, ragdoll, parms, ref solid); + } + else if (strcmpi(block, "ragdollconstraint") == 0) { + ConstraintRagdollParams constraint = default; + parse.ParseRagdollConstraint(ref constraint, null); + RagdollAddConstraint(physEnv, ragdoll, parms, ref constraint); + } + else if (strcmpi(block, "collisionrules") == 0) { + IPhysicsCollisionSet set = physics.FindOrCreateCollisionSet((uint)parms.ModelIndex, ragdoll.ListCount); + ParseCustomBlock(parse, new RagdollCollisionRules(set)); + } + else if (strcmpi(block, "animatedfriction") == 0) + ParseCustomBlock(parse, new RagdollAnimatedFrictionParser(ragdoll)); + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + } + + public static void RagdollSetupCollisions(Ragdoll ragdoll, VCollide? collide, int modelIndex) { + Assert(collide != null); + if (collide == null) + return; + + IPhysicsCollisionSet? set = physics.FindCollisionSet((uint)modelIndex); + if (set == null) { + set = physics.FindOrCreateCollisionSet((uint)modelIndex, ragdoll.ListCount); + if (set == null) + return; + + bool foundRules = false; + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "collisionrules") == 0) { + IPhysicsCollisionSet setRules = physics.FindOrCreateCollisionSet((uint)modelIndex, ragdoll.ListCount); + ParseCustomBlock(parse, new RagdollCollisionRules(setRules)); + foundRules = true; + } + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + + if (!foundRules) { + int i; + for (i = 0; i < ragdoll.ListCount; i++) { + for (int j = i + 1; j < ragdoll.ListCount; j++) + set.EnableCollisions(i, j); + } + for (i = 0; i < ragdoll.ListCount; i++) { + int parent = ragdoll.List[i].ParentIndex; + if (parent >= 0) { + Assert(ragdoll.List[i].Object != null); + Assert(ragdoll.List[i].Constraint != null); + set.DisableCollisions(i, parent); + } + } + } + } + } + + public static void RagdollActivate(Ragdoll ragdoll, VCollide? collide, int modelIndex, bool forceWake) { + RagdollSetupCollisions(ragdoll, collide, modelIndex); + for (int i = 0; i < ragdoll.ListCount; i++) { + IPhysicsObject obj = ragdoll.List[i].Object!; + obj.SetGameIndex((ushort)i); + PhysSetGameFlags(obj, PhysicsFlags.MultiObjectEntity); + obj.EnableCollisions(true); + + if (forceWake) + obj.Wake(); + } + if (ragdoll.Group != null) { + ragdoll.Group.Activate(); + if (!forceWake) { + for (int i = 0; i < ragdoll.ListCount; i++) + ragdoll.List[i].Object!.Sleep(); + } + } + } + + public static bool RagdollCreate(Ragdoll ragdoll, in RagdollParams parms, IPhysicsEnvironment physEnv) { + RagdollCreateObjects(physEnv, ragdoll, parms); + + if (ragdoll.ListCount == 0) + return false; + + int forceBone = parms.ForceBoneIndex; + + int i; + float totalMass = 0; + for (i = 0; i < ragdoll.ListCount; i++) + totalMass += ragdoll.List[i].Object!.GetMass(); + totalMass = MathF.Max(totalMass, 1); + + Vector3 nudgeForce = parms.ForceVector; + Vector3 forcePosition = parms.ForcePosition; + + Assert(forceBone < ragdoll.ListCount); + + if (forceBone >= 0 && forceBone < ragdoll.ListCount) { + ragdoll.List[forceBone].Object!.ApplyForceCenter(nudgeForce); + ragdoll.List[forceBone].Object!.GetPosition(out forcePosition, out _); + } + + for (i = 0; i < ragdoll.ListCount; i++) + PhysSetGameFlags(ragdoll.List[i].Object!, PhysicsFlags.PartOfRagdoll); + + if (forcePosition != vec3_origin) { + for (i = 0; i < ragdoll.ListCount; i++) { + if (forceBone != i) { + float scale = ragdoll.List[i].Object!.GetMass() / totalMass; + ragdoll.List[i].Object!.ApplyForceOffset(scale * nudgeForce, forcePosition); + } + } + } + + return true; + } + + public static void RagdollApplyAnimationAsVelocity(Ragdoll ragdoll, ReadOnlySpan prevBones, ReadOnlySpan currentBones, TimeUnit_t dt) { + for (int i = 0; i < ragdoll.ListCount; i++) { + int boneIndex = ragdoll.BoneIndex[i]; + BoneSetup.CalcBoneDerivatives(out Vector3 velocity, out Vector3 angVel, prevBones[boneIndex], currentBones[boneIndex], (float)dt); + + // Angular velocity is always applied in local space in vphysics + ragdoll.List[i].Object!.WorldToLocalVector(out Vector3 localAngVelocity, angVel); + ragdoll.List[i].Object!.AddVelocity(velocity, localAngVelocity); + } + } + + public static void RagdollApplyAnimationAsVelocity(Ragdoll ragdoll, ReadOnlySpan boneToWorld) { + for (int i = 0; i < ragdoll.ListCount; i++) { + MathLib.MatrixInvert(boneToWorld[i], out Matrix3x4 inverse); + MathLib.MatrixAngles(inverse, out Quaternion q, out Vector3 pos); + + Vector3 velocity; + float flSpin; + + Vector3 localVelocity; + Vector3 localAngVelocity; + + MathLib.QuaternionAxisAngle(q, out localAngVelocity, out flSpin); + localAngVelocity *= flSpin; + localVelocity = pos; + + // move those bone-local coords back to world space using the ragdoll transform + ragdoll.List[i].Object!.LocalToWorldVector(out velocity, localVelocity); + ragdoll.List[i].Object!.AddVelocity(velocity, localAngVelocity); + } + } + + public static void RagdollDestroy(Ragdoll ragdoll) { + if (ragdoll.ListCount == 0) + return; + + int i; + for (i = 0; i < ragdoll.ListCount; i++) { + physenv.DestroyConstraint(ragdoll.List[i].Constraint!); + ragdoll.List[i].Constraint = null; + } + for (i = 0; i < ragdoll.ListCount; i++) { + if (ragdoll.List[i].Object != null) + physenv.DestroyObject(ragdoll.List[i].Object); + ragdoll.List[i].Object = null; + } + physenv.DestroyConstraintGroup(ragdoll.Group!); + ragdoll.Group = null; + ragdoll.ListCount = 0; + } + + public static int RagdollExtractBoneIndices(Span boneIndexOut, StudioHdr studioHdr, VCollide collide) { + int elementCount = 0; + + IVPhysicsKeyParser parse = physcollision.VPhysicsKeyParserCreate(collide.KeyValues); + while (!parse.Finished()) { + ReadOnlySpan block = parse.GetCurrentBlockName(); + if (strcmpi(block, "solid") == 0) { + Solid solid = default; + parse.ParseSolid(ref solid, null); + if (elementCount < RAGDOLL_MAX_ELEMENTS) { + boneIndexOut[elementCount] = BoneSetup.Studio_BoneIndexByName(studioHdr, ((ReadOnlySpan)solid.Name).SliceNullTerminatedString()); + elementCount++; + } + } + else + parse.SkipBlock(); + } + physcollision.VPhysicsKeyParserDestroy(parse); + + return elementCount; + } + + public static bool RagdollGetBoneMatrix(Ragdoll ragdoll, BoneAccessor boneToWorld, int objectIndex) { + int BoneIndex = ragdoll.BoneIndex[objectIndex]; + if (BoneIndex < 0) + return false; + + ref readonly RagdollElement element = ref ragdoll.List[objectIndex]; + + // during restore if a model has changed since the file was saved, this could be NULL + if (element.Object == null) + return false; + element.Object.GetPositionMatrix(out boneToWorld.GetBoneForWrite(BoneIndex)); + if (element.ParentIndex >= 0 && !ragdoll.AllowStretch) { + // overwrite the position from physics to force rigid attachment + // UNDONE: If we support other types of constraints (or multiple constraints per object) + // make sure these don't fight ! + int parentBoneIndex = ragdoll.BoneIndex[element.ParentIndex]; + MathLib.VectorTransform(element.OriginParentSpace, boneToWorld.GetBone(parentBoneIndex), out Vector3 outVec); + MathLib.MatrixSetColumn(outVec, 3, ref boneToWorld.GetBoneForWrite(BoneIndex)); + } + return true; + } + + public static void RagdollComputeExactBbox(Ragdoll ragdoll, in Vector3 origin, ref Vector3 outMins, ref Vector3 outMaxs) { + outMins = origin; + outMaxs = origin; + + for (int i = 0; i < ragdoll.ListCount; i++) { + IPhysicsObject obj = ragdoll.List[i].Object!; + obj.GetPosition(out Vector3 objectOrg, out QAngle objectAng); + physcollision.CollideGetAABB(out Vector3 mins, out Vector3 maxs, obj.GetCollide(), objectOrg, objectAng); + outMins = Vector3.Min(outMins, mins); + outMaxs = Vector3.Max(outMaxs, maxs); + } + } + + public static bool RagdollIsAsleep(Ragdoll ragdoll) { + for (int i = 0; i < ragdoll.ListCount; i++) { + if (ragdoll.List[i].Object != null && !ragdoll.List[i].Object!.IsAsleep()) + return false; + } + + return true; + } + + public static void RagdollSolveSeparation(Ragdoll ragdoll, BaseEntity? entity) { + Span needsFix = stackalloc byte[256]; + int fixCount = 0; + Assert(ragdoll.ListCount <= needsFix.Length); + for (int i = 0; i < ragdoll.ListCount; i++) { + needsFix[i] = 0; + ref readonly RagdollElement element = ref ragdoll.List[i]; + if (element.Constraint != null && element.ParentIndex >= 0) { + element.Object!.GetPosition(out Vector3 start, out _); + ragdoll.List[element.ParentIndex].Object!.LocalToWorld(out Vector3 target, element.OriginParentSpace); + if (needsFix[element.ParentIndex] != 0) { + needsFix[i] = 1; + ++fixCount; + continue; + } + Vector3 dir = target - start; + if (dir.LengthSquared() > 1.0f) { +#if !TF_CLIENT_DLL + float mass = element.Object.GetMass(); + float massParent = ragdoll.List[element.ParentIndex].Object!.GetMass(); + + if (mass * 2.0f < massParent) { + needsFix[i] = 1; + ++fixCount; + continue; + } +#endif + + if (PhysHasContactWithOtherInDirection(element.Object, dir)) { + Ray ray = default; + ray.Init(target, start); + Util.TraceRay(ray, Mask.Solid, entity, CollisionGroup.None, out Trace tr); + if (tr.DidHit()) { + needsFix[i] = 1; + ++fixCount; + } + } + } + } + } + + if (fixCount != 0) { + for (int i = 0; i < ragdoll.ListCount; i++) { + if (needsFix[i] == 0) + continue; + + ref readonly RagdollElement element = ref ragdoll.List[i]; + IPhysicsObject parentObject = ragdoll.List[element.ParentIndex].Object!; + parentObject.LocalToWorld(out Vector3 target, element.OriginParentSpace); + parentObject.GetVelocityAtPoint(target, out Vector3 velocity); + element.Object!.GetPositionMatrix(out Matrix3x4 xform); + MathLib.MatrixSetColumn(target, 3, ref xform); + element.Object.SetPositionMatrix(xform, true); + element.Object.SetVelocity(velocity, vec3_origin); + } + DevMsg(2, $"TICK:{gpGlobals.TickCount,5}:Ragdoll separation count: {fixCount}\n"); + } + else + ragdoll.Group!.ClearErrorState(); + } +} +#endif diff --git a/Source.Common/BoneSetup.cs b/Source.Common/BoneSetup.cs index bcf53947..a4c61745 100644 --- a/Source.Common/BoneSetup.cs +++ b/Source.Common/BoneSetup.cs @@ -1646,6 +1646,26 @@ public static int Studio_BoneIndexByName(StudioHdr? studioHdr, ReadOnlySpan 0) + scale = 1.0f / dt; + + MathLib.MatrixAngles(prev, out QAngle startAngles, out Vector3 startPosition); + MathLib.MatrixAngles(current, out QAngle endAngles, out Vector3 endPosition); + + velocity = (endPosition - startPosition) * scale; + MathLib.RotationDeltaAxisAngle(startAngles, endAngles, out Vector3 deltaAxis, out float deltaAngle); + MathLib.VectorScale(deltaAxis, deltaAngle * scale, out angVel); + } + public static void SetupSingleBoneMatrix(StudioHdr ownerHdr, int sequence, int frame, int bone, out Matrix3x4 boneLocal) { MStudioSeqDesc seqdesc = ownerHdr.Seqdesc(sequence); MStudioAnimDesc animdesc = ownerHdr.Animdesc(seqdesc.Anim(0, 0)); diff --git a/Source.Common/Mathematics/MathLib.cs b/Source.Common/Mathematics/MathLib.cs index 35267d4a..a75b6fb5 100644 --- a/Source.Common/Mathematics/MathLib.cs +++ b/Source.Common/Mathematics/MathLib.cs @@ -2413,6 +2413,21 @@ public static void TransformAABB(in Matrix3x4 transform, in Vector3 minsIn, in V maxsOut = worldCenter + worldExtents; } + public static void IRotateAABB(in Matrix3x4 transform, in Vector3 minsIn, in Vector3 maxsIn, out Vector3 minsOut, out Vector3 maxsOut) { + Vector3 oldCenter = (minsIn + maxsIn) * 0.5f; + Vector3 oldExtents = maxsIn - oldCenter; + + VectorIRotate(oldCenter, transform, out Vector3 newCenter); + + Vector3 newExtents; + newExtents.X = MathF.Abs(oldExtents.X * transform[0][0]) + MathF.Abs(oldExtents.Y * transform[1][0]) + MathF.Abs(oldExtents.Z * transform[2][0]); + newExtents.Y = MathF.Abs(oldExtents.X * transform[0][1]) + MathF.Abs(oldExtents.Y * transform[1][1]) + MathF.Abs(oldExtents.Z * transform[2][1]); + newExtents.Z = MathF.Abs(oldExtents.X * transform[0][2]) + MathF.Abs(oldExtents.Y * transform[1][2]) + MathF.Abs(oldExtents.Z * transform[2][2]); + + minsOut = newCenter - newExtents; + maxsOut = newCenter + newExtents; + } + public static float VectorLength(in Vector3 delta) => delta.Length(); public static float Vector2DLength(in Vector2 v) => MathF.Sqrt(DotProduct2D(v, v)); diff --git a/Source.Common/Physics/PhysicsInterface.cs b/Source.Common/Physics/PhysicsInterface.cs index 5406b144..078847c5 100644 --- a/Source.Common/Physics/PhysicsInterface.cs +++ b/Source.Common/Physics/PhysicsInterface.cs @@ -643,8 +643,9 @@ public interface IPhysicsObject void GetDamping(out float speed, out float rot); // coefficients are optional, pass either - void SetDragCoefficient(ref float drag, ref float angularDrag); + void SetDragCoefficient(in float? drag, in float? angularDrag); void SetBuoyancyRatio(float ratio); // Override bouyancy + float GetBuoyancyRatio(); // material index int GetMaterialIndex(); diff --git a/Source.Engine/CL.cs b/Source.Engine/CL.cs index 45c9bc59..5dda926a 100644 --- a/Source.Engine/CL.cs +++ b/Source.Engine/CL.cs @@ -234,7 +234,7 @@ public void FullyConnected() { if (percent == lastShaderPercent) return; lastShaderPercent = percent; - EngineVGui?.UpdateCustomProgressBar((float)done / total, "#LoadingProgress_LoadResources"); + EngineVGui?.UpdateCustomProgressBar((float)done / total, "Compiling shaders..."); // TODO: LOCALIZE THIS STRING #214 }); #endif diff --git a/Source.Engine/EngineServerImpl.cs b/Source.Engine/EngineServerImpl.cs index ccadff25..6e7a986b 100644 --- a/Source.Engine/EngineServerImpl.cs +++ b/Source.Engine/EngineServerImpl.cs @@ -1103,7 +1103,17 @@ public uint GMOD_LoadModel(ReadOnlySpan path) { } public float GetClientConVarFloat(int client, ReadOnlySpan cvar, float fallback) { - throw new NotImplementedException(); + if (client < 1 || client > sv.GetClientCount()) { + DevMsg(1, "GetClientConVarValue: player invalid index %i\n", client); + return fallback; + } + + ReadOnlySpan value = sv.GetClient(client - 1)!.GetUserSetting(cvar); + if (value.IsEmpty || value[0] == '\0') + return fallback; + + float result = (float)atof(value); + return float.IsFinite(result) ? result : fallback; } public ref CSteamID GMOD_GetPlayerOwnerSteamID(Edict client) { diff --git a/Source.Engine/Game.cs b/Source.Engine/Game.cs index bcc70fe4..5a76be65 100644 --- a/Source.Engine/Game.cs +++ b/Source.Engine/Game.cs @@ -152,6 +152,10 @@ private void DispatchInputEvent(in InputEvent ev) { Key?.Event(in ev); break; default: +#if GMOD_DLL + if (ev.Type == InputEventType.IE_AnalogValueChanged && (AnalogCode)ev.Data == AnalogCode.MouseWheel) + clientDLL?.IN_MouseWheelAnalog(ev.Data3); +#endif if (surface?.HandleInputEvent(in ev) ?? false) break; diff --git a/Source.Engine/VGuiBaseUIInterface.cs b/Source.Engine/VGuiBaseUIInterface.cs index 14fcc311..2b730bce 100644 --- a/Source.Engine/VGuiBaseUIInterface.cs +++ b/Source.Engine/VGuiBaseUIInterface.cs @@ -489,7 +489,13 @@ public void UpdateCustomProgressBar(float progress, ReadOnlySpan desc) { if (staticGameUIFuncs == null) return; - if (staticGameUIFuncs.UpdateProgressBar(progress, localize.Find(desc))) + if (!desc.IsEmpty && desc[0] == '#') { + ReadOnlySpan localized = localize.Find(desc); + if (!localized.IsEmpty) + desc = localized; + } + + if (staticGameUIFuncs.UpdateProgressBar(progress, desc)) Host.View.RenderGuiOnly(); } public void StartCustomProgress() { diff --git a/Source.Physics/PhysicsMaterial.cs b/Source.Physics/PhysicsMaterial.cs index 518c5d92..1efebcc3 100644 --- a/Source.Physics/PhysicsMaterial.cs +++ b/Source.Physics/PhysicsMaterial.cs @@ -90,6 +90,9 @@ public nint GetReservedSurfaceIndex(ReadOnlySpan propName) { } public nint GetSurfaceIndex(ReadOnlySpan surfacePropName) { + if (surfacePropName.IsStringEmpty) + return -1; + if (surfacePropName[0] == '$') { nint index = GetReservedSurfaceIndex(surfacePropName); if (index >= 0) diff --git a/Source.Physics/PhysicsObject.cs b/Source.Physics/PhysicsObject.cs index 8e35935c..149cea52 100644 --- a/Source.Physics/PhysicsObject.cs +++ b/Source.Physics/PhysicsObject.cs @@ -183,8 +183,6 @@ public void EnableGravity(bool enable) { BodyId.GravityScale = enable ? 1.0f : 0.0f; } - public void EnableDrag(bool enable) => DragEnabled = enable; - public void EnableMotion(bool enable) { if (Static || MotionEnabled == enable) return; @@ -306,10 +304,18 @@ public void GetDamping(out float speed, out float rot) { rot = AngularDamping; } - public void SetDragCoefficient(ref float drag, ref float angularDrag) { - DragCoefficient = drag; - AngularDragCoefficient = angularDrag; - DragEnabled = DragCoefficient != 0.0f || AngularDragCoefficient != 0.0f; + public void EnableDrag(bool enable) { + if (IsStatic()) + return; + DragEnabled = enable; + } + + public void SetDragCoefficient(in float? drag, in float? angularDrag) { + if (drag.HasValue) + DragCoefficient = drag.Value; + if (angularDrag.HasValue) + AngularDragCoefficient = angularDrag.Value; + EnableDrag(DragCoefficient != 0.0f || AngularDragCoefficient != 0.0f); RecomputeDragBases(); } @@ -317,6 +323,8 @@ public void SetDragCoefficient(ref float drag, ref float angularDrag) { public float GetBuoyancyRatio() => BuoyancyRatio; void RecomputeDragBases() { + if (IsStatic() || GetCollide() == null) return; + DragBasis = default; AngDragBasis = default; if (Static || Collide == null) diff --git a/Source.StdShader.Gl46/BaseVSShader.cs b/Source.StdShader.Gl46/BaseVSShader.cs index 1a7abc27..9f0918d7 100644 --- a/Source.StdShader.Gl46/BaseVSShader.cs +++ b/Source.StdShader.Gl46/BaseVSShader.cs @@ -536,6 +536,32 @@ public void SetPixelShaderConstantGammaToLinear(int pixelReg, int constantVar) { ShaderAPI!.SetPixelShaderConstant(pixelReg, val); } + public void SetPixelShaderConstantGammaToLinear(int pixelReg, int constantVar, int constantVar2) { + Assert(!IsSnapshotting()); + IMaterialVar[] shaderParams = Params!; + if (shaderParams == null || constantVar == -1 || constantVar2 == -1) + return; + + IMaterialVar pixelVar = shaderParams[constantVar]; + Assert(pixelVar != null); + IMaterialVar pixelVar2 = shaderParams[constantVar2]; + Assert(pixelVar2 != null); + + Span val = stackalloc float[4]; + if (pixelVar.GetVarType() == MaterialVarType.Vector) + pixelVar.GetVecValue(val[..3]); + else + val[0] = val[1] = val[2] = pixelVar.GetFloatValue(); + + val[3] = pixelVar2.GetFloatValue(); + + val[0] = val[0] > 1.0f ? val[0] : MathLib.GammaToLinear(val[0]); + val[1] = val[1] > 1.0f ? val[1] : MathLib.GammaToLinear(val[1]); + val[2] = val[2] > 1.0f ? val[2] : MathLib.GammaToLinear(val[2]); + + ShaderAPI!.SetPixelShaderConstant(pixelReg, val); + } + public void InitUnlitGeneric(int baseTextureVar, int detailVar, int envmapVar, int envmapMaskVar) { IMaterialVar[] shaderParams = Params!; diff --git a/Source.StdShader.Gl46/Sprite.cs b/Source.StdShader.Gl46/Sprite.cs index 93e28769..33f8132d 100644 --- a/Source.StdShader.Gl46/Sprite.cs +++ b/Source.StdShader.Gl46/Sprite.cs @@ -1,6 +1,340 @@ +using Source.Common; using Source.Common.MaterialSystem; +using Source.Common.Mathematics; +using Source.Common.ShaderAPI; using Source.Common.ShaderLib; namespace Source.StdShader.Gl46; -public class Sprite : UnlitGeneric; +public class Sprite : BaseVSShader +{ + public static string HelpString = "Help for Sprite"; + public static ShaderFlags Flags = ShaderFlags.None; + 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, ShaderParamFlags flags) { + Info.Name = "override"; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = 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, ShaderParamFlags flags = 0) { + Info.Name = name; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = 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 ShaderParamFlags GetFlags() => Info.Flags; + public ReadOnlySpan GetHelp() => Info.Help; + } + + public static readonly ShaderParam SPRITEORIGIN = new($"${nameof(SPRITEORIGIN)}", ShaderParamType.Vec3, "[0 0 0]", "sprite origin"); + public static readonly ShaderParam SPRITEORIENTATION = new($"${nameof(SPRITEORIENTATION)}", ShaderParamType.Integer, "0", "sprite orientation"); + public static readonly ShaderParam SPRITERENDERMODE = new($"${nameof(SPRITERENDERMODE)}", ShaderParamType.Integer, "0", "sprite rendermode"); + public static readonly ShaderParam IGNOREVERTEXCOLORS = new($"${nameof(IGNOREVERTEXCOLORS)}", ShaderParamType.Bool, "1", "ignore vertex colors"); + public static readonly ShaderParam NOSRGB = new($"${nameof(NOSRGB)}", ShaderParamType.Bool, "0", "do not operate in srgb space"); + public static readonly ShaderParam HDRCOLORSCALE = new($"${nameof(HDRCOLORSCALE)}", ShaderParamType.Float, "1.0", "hdr color scale"); + + const int SPR_VP_PARALLEL_UPRIGHT = 0; + const int SPR_FACING_UPRIGHT = 1; + const int SPR_VP_PARALLEL = 2; + const int SPR_ORIENTED = 3; + const int SPR_VP_PARALLEL_ORIENTED = 4; + + const uint SHADER_USE_VERTEX_COLOR = 1; + const uint SHADER_USE_CONSTANT_COLOR = 2; + + public override string? GetFallbackShader(IMaterialVar[] vars) => null; + public override ShaderFlags 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 OnInitShaderParams(IMaterialVar[] parms, ReadOnlySpan materialName) { + if (!parms[(int)ShaderMaterialVars.Alpha].IsDefined()) + parms[(int)ShaderMaterialVars.Alpha].SetFloatValue(1.0f); + + if (!parms[HDRCOLORSCALE].IsDefined()) + parms[HDRCOLORSCALE].SetFloatValue(1.0f); + + if (!parms[NOSRGB].IsDefined()) + parms[NOSRGB].SetIntValue(1); + + SetFlags(parms, MaterialVarFlags.NoDebugOverride); + SetFlags(parms, MaterialVarFlags.VertexColor); + SetFlags(parms, MaterialVarFlags.VertexAlpha); + + if (parms[SPRITEORIENTATION].IsDefined()) { + ReadOnlySpan orientationString = parms[SPRITEORIENTATION].GetStringValue(); + if (orientationString.Equals("parallel_upright", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + else if (orientationString.Equals("facing_upright", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_FACING_UPRIGHT); + else if (orientationString.Equals("vp_parallel", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL); + else if (orientationString.Equals("oriented", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_ORIENTED); + else if (orientationString.Equals("vp_parallel_oriented", StringComparison.OrdinalIgnoreCase)) + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_ORIENTED); + else { + Warning("error with $spriteOrientation\n"); + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + } + } + else + parms[SPRITEORIENTATION].SetIntValue(SPR_VP_PARALLEL_UPRIGHT); + } + + protected override void OnInitShaderInstance(IMaterialVar[] parms, ReadOnlySpan materialName) { + bool srgb = parms[NOSRGB].GetIntValue() == 0; + LoadTexture((int)ShaderMaterialVars.BaseTexture, srgb ? (int)TextureFlags.SRGB : 0); + } + + void SetSpriteCommonShadowState(uint shaderFlags) { + IShaderShadow shaderShadow = ShaderShadow!; + shaderShadow.EnableTexture(Sampler.Sampler0, true); + bool srgb = Params![NOSRGB].GetIntValue() == 0; + shaderShadow.EnableSRGBRead(Sampler.Sampler0, srgb); + + VertexFormat flags = VertexFormat.Position; + if ((shaderFlags & SHADER_USE_VERTEX_COLOR) != 0) + flags |= VertexFormat.Color; + int numTexCoords = 1; + shaderShadow.VertexShaderVertexFormat(flags, numTexCoords, null, 0); + + StaticShaderIndex vshIndex = new(shaderShadow, ShaderType.Vertex, "sprite"); + vshIndex.Set("VERTEXCOLOR", (shaderFlags & SHADER_USE_VERTEX_COLOR) != 0); + vshIndex.Set("SRGB", srgb); + shaderShadow.SetVertexShader("sprite", vshIndex.GetIndex()); + + StaticShaderIndex pshIndex = new(shaderShadow, ShaderType.Pixel, "sprite"); + pshIndex.Set("VERTEXCOLOR", (shaderFlags & SHADER_USE_VERTEX_COLOR) != 0); + pshIndex.Set("CONSTANTCOLOR", (shaderFlags & SHADER_USE_CONSTANT_COLOR) != 0); + pshIndex.Set("SRGB", srgb); + shaderShadow.SetPixelShader("sprite", pshIndex.GetIndex()); + + shaderShadow.EnableSRGBWrite(srgb); + } + + void SetSpriteDynamicShaderIndices(int pixelFogCombo) { + IShaderDynamicAPI shaderAPI = ShaderAPI!; + + MaterialFogMode fogType = shaderAPI.GetSceneFogMode(); + int fogIndex = (fogType == MaterialFogMode.LinearBelowFogZ) ? 1 : 0; + DynamicShaderIndex vshIndex = new(shaderAPI, ShaderType.Vertex); + vshIndex.Set("DOWATERFOG", fogIndex); + shaderAPI.SetVertexShaderIndex(vshIndex.GetIndex()); + + DynamicShaderIndex pshIndex = new(shaderAPI, ShaderType.Pixel); + pshIndex.Set("HDRENABLED", IsHDREnabled()); + pshIndex.Set("PIXELFOGTYPE", pixelFogCombo); + shaderAPI.SetPixelShaderIndex(pshIndex.GetIndex()); + + shaderAPI.SetPixelShaderFogParams((int)PixelShaderConst.FogParams); + + Span eyePos_SpecExponent = [0, 0, 0, 0]; + shaderAPI.GetWorldSpaceCameraPosition(eyePos_SpecExponent); + eyePos_SpecExponent[3] = 0.0f; + shaderAPI.SetPixelShaderConstant((int)PixelShaderConst.EyePosSpecExponent, eyePos_SpecExponent); + } + + void SetSpriteCommonDynamicState(uint shaderFlags) { + bool srgb = Params![NOSRGB].GetIntValue() == 0; + + BindTexture(Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, (int)ShaderMaterialVars.Frame); + + SetSpriteDynamicShaderIndices(ShaderAPI!.GetPixelFogCombo()); + + if ((shaderFlags & SHADER_USE_CONSTANT_COLOR) != 0) { + if (srgb) + SetPixelShaderConstantGammaToLinear(0, (int)ShaderMaterialVars.Color, (int)ShaderMaterialVars.Alpha); + else + SetPixelShaderConstant(0, (int)ShaderMaterialVars.Color, (int)ShaderMaterialVars.Alpha); + } + + SetHDRColorScaleConstant(srgb); + } + + void SetHDRColorScaleConstant(bool srgb) { + if (IsHDREnabled()) { + if (srgb) + SetPixelShaderConstantGammaToLinear(1, HDRCOLORSCALE); + else + SetPixelShaderConstant(1, HDRCOLORSCALE); + } + } + + void SetFrameBlendDynamicState(ITexture texture, int frame, float fade, int pixelFogCombo) { + bool srgb = Params![NOSRGB].GetIntValue() == 0; + + ShaderSystem.BindTexture(Sampler.Sampler0, texture, frame); + + SetSpriteDynamicShaderIndices(pixelFogCombo); + + Span color = [0, 0, 0, 1.0f]; + color[0] = color[1] = color[2] = srgb ? MathLib.GammaToLinear(fade) : fade; + ShaderAPI!.SetPixelShaderConstant(0, color); + + SetHDRColorScaleConstant(srgb); + } + + protected override void OnDrawElements(IMaterialVar[] parms, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { + if (IsSnapshotting()) + ShaderShadow!.EnableCulling(false); + + switch ((RenderMode)parms[SPRITERENDERMODE].GetIntValue()) { + case RenderMode.Normal: + if (IsSnapshotting()) { + FogToFogColor(); + SetSpriteCommonShadowState(0); + } + else + SetSpriteCommonDynamicState(0); + Draw(); + break; + + case RenderMode.TransColor: + case RenderMode.TransTexture: + case RenderMode.TransAlpha: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + FogToFogColor(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.Glow: + case RenderMode.WorldGlow: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableDepthTest(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.TransAlphaAdd: + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + FogToFogColor(); + SetSpriteCommonShadowState(SHADER_USE_VERTEX_COLOR); + } + else + SetSpriteCommonDynamicState(SHADER_USE_VERTEX_COLOR); + Draw(); + break; + + case RenderMode.TransAdd: { + uint flags = SHADER_USE_CONSTANT_COLOR; + if (parms[IGNOREVERTEXCOLORS].GetIntValue() == 0) + flags |= SHADER_USE_VERTEX_COLOR; + + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(flags); + } + else + SetSpriteCommonDynamicState(flags); + Draw(); + } + break; + + case RenderMode.TransAddFrameBlend: { + float frame = parms[(int)ShaderMaterialVars.Frame].GetFloatValue(); + float fade = parms[(int)ShaderMaterialVars.Alpha].GetFloatValue(); + uint flags = SHADER_USE_CONSTANT_COLOR; + if (parms[IGNOREVERTEXCOLORS].GetIntValue() == 0) + flags |= SHADER_USE_VERTEX_COLOR; + + if (IsSnapshotting()) { + ShaderShadow!.EnableDepthWrites(false); + ShaderShadow.EnableBlending(true); + ShaderShadow.BlendFunc(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.One); + FogToBlack(); + SetSpriteCommonShadowState(flags); + Draw(); + } + else { + ITexture texture = parms[(int)ShaderMaterialVars.BaseTexture].GetTextureValue()!; + float frameBlendAlpha = 1.0f - (frame - (int)frame); + SetFrameBlendDynamicState(texture, (int)frame, fade * frameBlendAlpha, shaderAPI.GetPixelFogCombo1(true)); + Draw(); + + frameBlendAlpha = frame - (int)frame; + int numAnimationFrames = texture.GetNumAnimationFrames(); + SetFrameBlendDynamicState(texture, ((int)frame + 1) % numAnimationFrames, fade * frameBlendAlpha, shaderAPI.GetPixelFogCombo()); + Draw(); + } + } + break; + + default: + Warning("shader Sprite: Unknown sprite render mode\n"); + break; + } + } +}