diff --git a/IKFixes/HarmonyPatches.cs b/IKFixes/HarmonyPatches.cs new file mode 100644 index 0000000..3059a58 --- /dev/null +++ b/IKFixes/HarmonyPatches.cs @@ -0,0 +1,243 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Player; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using ABI_RC.Systems.MovementSystem; +using HarmonyLib; +using RootMotion.FinalIK; +using UnityEngine; + +namespace NAK.Melons.IKFixes.HarmonyPatches; + +internal static class BodySystemPatches +{ + static float _ikSimulatedRootAngle = 0f; + + [HarmonyPostfix] + [HarmonyPatch(typeof(BodySystem), "SetupOffsets")] + private static void Postfix_BodySystem_SetupOffsets(List trackingPoints) + { + //redo offsets for knees as native is too far from pivot + foreach (TrackingPoint trackingPoint in trackingPoints) + { + Transform parent = null; + if (trackingPoint.assignedRole == TrackingPoint.TrackingRole.LeftKnee) + { + parent = IKSystem.vrik.references.leftCalf; + } + else if (trackingPoint.assignedRole == TrackingPoint.TrackingRole.RightKnee) + { + parent = IKSystem.vrik.references.rightCalf; + } + + if (parent != null) + { + trackingPoint.offsetTransform.parent = parent; + trackingPoint.offsetTransform.localPosition = Vector3.zero; + trackingPoint.offsetTransform.localRotation = Quaternion.identity; + trackingPoint.offsetTransform.parent = trackingPoint.referenceTransform; + + // small amount forward, as pivot is different for users who place + // tracker on upper/lower leg. 0.5f was too much for users using upper leg. + Vector3 b = IKSystem.vrik.references.root.forward * 0.1f; + trackingPoint.offsetTransform.position += b; + } + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(BodySystem), "Update")] + private static bool Prefix_BodySystem_Update(ref BodySystem __instance) + { + if (IKSystem.vrik != null) + { + IKSolverVR solver = IKSystem.vrik.solver; + + if (BodySystem.TrackingEnabled) + { + IKSystem.vrik.enabled = true; + solver.IKPositionWeight = BodySystem.TrackingPositionWeight; + solver.locomotion.weight = BodySystem.TrackingLocomotionEnabled ? 1f : 0f; + + // fixes arm weights not being set if leftArm & rightArm targets are null + // game handles TrackingLegs in PlayerSetup, but not for knee goals + SetArmWeight(solver.leftArm, BodySystem.TrackingLeftArmEnabled && solver.leftArm.target != null ? 1f : 0f); + SetArmWeight(solver.rightArm, BodySystem.TrackingRightArmEnabled && solver.rightArm.target != null ? 1f : 0f); + SetLegWeight(solver.leftLeg, BodySystem.TrackingLeftLegEnabled && solver.leftLeg.target != null ? 1f : 0f); + SetLegWeight(solver.rightLeg, BodySystem.TrackingRightLegEnabled && solver.leftLeg.target != null ? 1f : 0f); + SetPelvisWeight(solver.spine, solver.spine.pelvisTarget != null ? 1f : 0f); + + // makes running animation look better + if (BodySystem.isCalibratedAsFullBody) + { + bool isRunning = BodySystem.PlayRunningAnimationInFullBody && MovementSystem.Instance.movementVector.magnitude > 0f; + if (isRunning) SetPelvisWeight(solver.spine, 0f); + } + } + else + { + IKSystem.vrik.enabled = false; + solver.IKPositionWeight = 0f; + solver.locomotion.weight = 0f; + + SetArmWeight(solver.leftArm, 0f); + SetArmWeight(solver.rightArm, 0f); + SetLegWeight(solver.leftLeg, 0f); + SetLegWeight(solver.rightLeg, 0f); + SetPelvisWeight(solver.spine, 0f); + } + + if (IKFixesMod.EntryUseFakeRootAngle.Value && !BodySystem.isCalibratedAsFullBody) + { + // Emulate maxRootAngle because CVR doesn't have the player controller set up ideally for VRIK. + // This is a small small fix, but makes it so the feet dont point in the direction of the head + // when turning. It also means turning with joystick & turning IRL make feet behave the same and follow behind. + float weightedAngleLimit = IKFixesMod.EntryFakeRootAngleLimit.Value * solver.locomotion.weight; + float pivotAngle = MovementSystem.Instance.rotationPivot.eulerAngles.y; + float deltaAngleRoot = Mathf.DeltaAngle(pivotAngle, _ikSimulatedRootAngle); + float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot); + if (absDeltaAngleRoot > weightedAngleLimit) + { + deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit; + _ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, pivotAngle, absDeltaAngleRoot - weightedAngleLimit); + } + solver.spine.maxRootAngle = 0f; + solver.spine.rootHeadingOffset = deltaAngleRoot; + } + else + { + // Allow avatar to rotate seperatly from Player (Desktop&VR) + // FBT needs avatar root to follow head + // VR default is 25 degrees, but maybe during emotes needs 180 degrees..? + solver.spine.maxRootAngle = BodySystem.isCalibratedAsFullBody ? 0f : 25f; + } + } + + int num = 0; + int count = IKSystem.Instance.AllTrackingPoints.FindAll((TrackingPoint m) => m.isActive && m.isValid && m.suggestedRole > TrackingPoint.TrackingRole.Invalid).Count; + + // fixes having all tracking points disabled forcing calibration + if (count == 0) + { + __instance._fbtAvailable = false; + return false; + } + + // solid body count block + if (BodySystem.enableLeftFootTracking) num++; + if (BodySystem.enableRightFootTracking) num++; + if (BodySystem.enableHipTracking) num++; + if (BodySystem.enableLeftKneeTracking) num++; + if (BodySystem.enableRightKneeTracking) num++; + if (BodySystem.enableChestTracking) num++; + if (BodySystem.enableLeftElbowTracking) num++; + if (BodySystem.enableRightElbowTracking) num++; + + __instance._fbtAvailable = (count >= num); + + void SetArmWeight(IKSolverVR.Arm arm, float weight) + { + arm.positionWeight = weight; + arm.rotationWeight = weight; + arm.shoulderRotationWeight = weight; + arm.shoulderTwistWeight = weight; + // assumed fix of bend goal weight if arms disabled with elbows (havent tested) + arm.bendGoalWeight = arm.bendGoal != null ? weight : 0f; + } + void SetLegWeight(IKSolverVR.Leg leg, float weight) + { + leg.positionWeight = weight; + leg.rotationWeight = weight; + // fixes knees bending to tracker if feet disabled (running anim) + leg.bendGoalWeight = leg.bendGoal != null ? weight : 0f; + } + void SetPelvisWeight(IKSolverVR.Spine spine, float weight) + { + // looks better when hips are disabled while running + spine.pelvisPositionWeight = weight; + spine.pelvisRotationWeight = weight; + } + + return false; + } +} + +internal static class VRIKPatches +{ + /** + Leg solver uses virtual bone calf and foot, plus world tracked knee position for normal maths. + This breaks as you playspace up, because calf and foot position aren't offset yet in solve order. + **/ + + [HarmonyPrefix] + [HarmonyPatch(typeof(IKSolverVR.Leg), "ApplyOffsets")] + private static bool Prefix_IKSolverVR_Leg_ApplyOffsets(ref IKSolverVR.Leg __instance) + { + //This is the second part of the above fix, preventing the solver from calculating a bad bendNormal + //when it doesn't need to. The knee tracker should dictate the bendNormal completely. + + if (__instance.usingKneeTracker) + { + __instance.ApplyPositionOffset(__instance.footPositionOffset, 1f); + __instance.ApplyRotationOffset(__instance.footRotationOffset, 1f); + Quaternion quaternion = Quaternion.FromToRotation(__instance.footPosition - __instance.position, __instance.footPosition + __instance.heelPositionOffset - __instance.position); + __instance.footPosition = __instance.position + quaternion * (__instance.footPosition - __instance.position); + __instance.footRotation = quaternion * __instance.footRotation; + return false; + } + + // run full method like normal otherwise + float num = __instance.bendGoalWeight; + __instance.bendGoalWeight = 0f; + __instance.ApplyOffsetsOld(); + __instance.bendGoalWeight = num; + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(IKSolverVR.Leg), "Solve")] + private static void Prefix_IKSolverVR_Leg_Solve(ref IKSolverVR.Leg __instance) + { + //Turns out VRIK applies bend goal maths before root is offset in solving process. + //We will apply ourselves before then to fix it. + if (__instance.usingKneeTracker) + __instance.ApplyBendGoal(); + } +} + +internal static class PlayerSetupPatches +{ + // Last Movement Parent Info + static Vector3 lastMovementPosition; + static Quaternion lastMovementRotation; + + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerSetup), "ResetIk")] + private static bool Prefix_PlayerSetup_ResetIk() + { + if (IKSystem.vrik == null) return false; + + CVRMovementParent currentParent = MovementSystem.Instance._currentParent; + if (currentParent != null && currentParent._referencePoint != null) + { + // Get current position, VR pivots around VR camera + Vector3 currentPosition = MovementSystem.Instance.rotationPivot.transform.position; + currentPosition.y = IKSystem.vrik.transform.position.y; // set pivot to floor + Quaternion currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f); + + // Convert to delta position (how much changed since last frame) + Vector3 deltaPosition = currentPosition - lastMovementPosition; + Quaternion deltaRotation = Quaternion.Inverse(lastMovementRotation) * currentRotation; + + // Add platform motion to IK solver + IKSystem.vrik.solver.AddPlatformMotion(deltaPosition, deltaRotation, currentPosition); + + // Store for next frame + lastMovementPosition = currentPosition; + lastMovementRotation = currentRotation; + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/IKFixes/IKFixes.csproj b/IKFixes/IKFixes.csproj new file mode 100644 index 0000000..6e01903 --- /dev/null +++ b/IKFixes/IKFixes.csproj @@ -0,0 +1,72 @@ + + + + + netstandard2.1 + enable + latest + false + True + + + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\0Harmony.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp-firstpass.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Aura2_Core.dll + + + ..\..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\cohtml.Net.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Cohtml.Runtime.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\MelonLoader.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\SteamVR.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Unity.Postprocessing.Runtime.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Unity.TextMeshPro.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AnimationModule.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.CoreModule.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.InputLegacyModule.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.PhysicsModule.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.VRModule.dll + + + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.XRModule.dll + + + + + + + + + + diff --git a/IKFixes/IKFixes.sln b/IKFixes/IKFixes.sln new file mode 100644 index 0000000..03a1417 --- /dev/null +++ b/IKFixes/IKFixes.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKFixes", "IKFixes.csproj", "{AD8D4FA6-8A34-4773-AA24-61F65976182F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AD8D4FA6-8A34-4773-AA24-61F65976182F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD8D4FA6-8A34-4773-AA24-61F65976182F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD8D4FA6-8A34-4773-AA24-61F65976182F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD8D4FA6-8A34-4773-AA24-61F65976182F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1D85DEE4-4BBC-43DD-8AA4-53478A0915F7} + EndGlobalSection +EndGlobal diff --git a/IKFixes/Main.cs b/IKFixes/Main.cs new file mode 100644 index 0000000..59ab31b --- /dev/null +++ b/IKFixes/Main.cs @@ -0,0 +1,35 @@ +using MelonLoader; + +namespace NAK.Melons.IKFixes; + +public class IKFixesMod : MelonMod +{ + public const string SettingsCategory = "IKFixes"; + public static readonly MelonPreferences_Category CategoryIKFixes = MelonPreferences.CreateCategory(SettingsCategory); + + public static readonly MelonPreferences_Entry EntryUseFakeRootAngle = + CategoryIKFixes.CreateEntry("Use Fake Root Angle", true, description: "Emulates maxRootAngle. This fixes feet pointing in direction of head when looking around."); + + public static readonly MelonPreferences_Entry EntryFakeRootAngleLimit = + CategoryIKFixes.CreateEntry("Fake Root Angle Limit", 25f, description: "Specifies the maximum angle the lower body can have relative to the head when rotating."); + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(HarmonyPatches.VRIKPatches)); + ApplyPatches(typeof(HarmonyPatches.BodySystemPatches)); + ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); + } + + void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } +} \ No newline at end of file diff --git a/IKFixes/Properties/AssemblyInfo.cs b/IKFixes/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..47cc61b --- /dev/null +++ b/IKFixes/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using MelonLoader; +using NAK.Melons.IKFixes.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.Melons.IKFixes))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.Melons.IKFixes))] + +[assembly: MelonInfo( + typeof(NAK.Melons.IKFixes.IKFixesMod), + nameof(NAK.Melons.IKFixes), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/IKFixes" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: HarmonyDontPatchAll] + +namespace NAK.Melons.IKFixes.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.2"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/IKFixes/format.json b/IKFixes/format.json new file mode 100644 index 0000000..0ca28a9 --- /dev/null +++ b/IKFixes/format.json @@ -0,0 +1,23 @@ +{ + "_id": 142, + "name": "IKFixes", + "modversion": "1.0.2", + "gameversion": "2022r170", + "loaderversion": "0.5.7", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "A few small fixes to IK. Major ones are listed below:\n\n**FBT** - Fixes knee tracking & running animations.\n\n**Halfbody** - Fixes footsteps while on a MovementParent & uses root angle offset to prevent feet from only pointing in head direction.", + "searchtags": [ + "knee", + "ik", + "tracking", + "fix" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/IKFixes/releases/download/v1.0.2/IKFixes.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/IKFixes/", + "changelog": "- Initial Release\n- Added FakeRootAngle fix for Halfbody. Feet will no longer only point in the direction of head while looking around/turning. This can be disabled in mod settings.", + "embedcolor": "f46e49" +} \ No newline at end of file diff --git a/README.md b/README.md index 638122d..677a729 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,31 @@ Does nothing on Knuckles controllers as the bind is used for finger tracking. ![VirtualDesktop Android-20220907-172923](https://user-images.githubusercontent.com/37721153/188999382-7663a863-49be-4b9b-8839-8b6e8c32783b.jpg) +# IKFixes +A few small fixes to IK. + +**FBT Fixes** - +* Knee tracking. +* Running animations.. +* Emotes playing in wrong direction. +* Forced to calibrate if all IK Tracking Settings are disabled. + +**Halfbody Fixes** - +* Locomotion footsteps while on Movement Parents. +* Root Angle Offset while looking around. Fixes feet only pointing in direction of head. + +## Relevant Feedback Posts: +https://feedback.abinteractive.net/p/ik-knee-tracking + +https://feedback.abinteractive.net/p/2022r170-ex3-knee-ik-weirdness-when-using-knee-trackers + +https://feedback.abinteractive.net/p/disabling-all-tracked-points-makes-game-assume-fbt + +https://feedback.abinteractive.net/p/about-ik-behaviour + +https://feedback.abinteractive.net/p/vrik-addplatformmotion-for-movement-parents + +https://feedback.abinteractive.net/p/halfbodyik-feet-will-only-point-in-direction-of-head ---