diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs new file mode 100644 index 0000000..ba4e1dd --- /dev/null +++ b/DesktopVRIK/DesktopVRIK.cs @@ -0,0 +1,108 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.MovementSystem; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using MelonLoader; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using RootMotion.FinalIK; + +namespace DesktopVRIK; + +public class NAKDesktopVRIK : MonoBehaviour +{ + public static NAKDesktopVRIK Instance; + public VRIK vrik; + + void Start() + { + Instance = this; + } + + void LateUpdate() + { + //pretty much zero out VRIK trying to locomote us using autofootstep + transform.localPosition = Vector3.zero; + transform.localRotation = Quaternion.identity; + } + + public void CalibrateAvatarVRIK(CVRAvatar avatar) + { + //check if VRIK already exists, as it is an allowed component + vrik = avatar.gameObject.GetComponent(); + if (vrik == null) + { + vrik = avatar.gameObject.AddComponent(); + } + + //Generic VRIK calibration shit + + vrik.fixTransforms = false; + vrik.solver.plantFeet = false; + vrik.solver.locomotion.weight = 1f; + vrik.solver.locomotion.angleThreshold = 30f; + vrik.solver.locomotion.maxLegStretch = 0.75f; + //nuke weights + vrik.solver.spine.headClampWeight = 0f; + vrik.solver.spine.minHeadHeight = 0f; + vrik.solver.leftArm.positionWeight = 0f; + vrik.solver.leftArm.rotationWeight = 0f; + vrik.solver.rightArm.positionWeight = 0f; + vrik.solver.rightArm.rotationWeight = 0f; + vrik.solver.leftLeg.positionWeight = 0f; + vrik.solver.leftLeg.rotationWeight = 0f; + vrik.solver.rightLeg.positionWeight = 0f; + vrik.solver.rightLeg.rotationWeight = 0f; + + //ChilloutVR specific stuff + + //centerEyeAnchor now is head bone + Transform headAnchor = FindIKTarget(IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Head)); + IKSystem.Instance.headAnchorPositionOffset = Vector3.zero; + IKSystem.Instance.headAnchorRotationOffset = Vector3.zero; + IKSystem.Instance.ApplyAvatarScaleToIk(avatar.viewPosition.y); + BodySystem.TrackingLeftArmEnabled = false; + BodySystem.TrackingRightArmEnabled = false; + BodySystem.TrackingLeftLegEnabled = false; + BodySystem.TrackingRightLegEnabled = false; + vrik.solver.IKPositionWeight = 0f; + vrik.enabled = false; + //Calibrate HeadIKOffset + VRIKCalibrator.CalibrateHead(vrik, headAnchor, IKSystem.Instance.headAnchorPositionOffset, IKSystem.Instance.headAnchorRotationOffset); + vrik.enabled = true; + vrik.solver.IKPositionWeight = 1f; + vrik.solver.spine.maintainPelvisPosition = 0f; + } + + private static Transform FindIKTarget(Transform targetParent) + { + /** + I want creators to be able to specify their own custom IK Targets, so they can move them around with animations if they want. + We check for existing target objects, and if none are found we make our own. + Naming scheme is parentobject name + " IK Target". + **/ + Transform parentTransform = targetParent.transform; + string targetName = parentTransform.name + " IK Target"; + + //check for existing target + foreach (object obj in parentTransform) + { + Transform childTransform = (Transform)obj; + if (childTransform.name == targetName) + { + return childTransform; + } + } + + //create new target if none are found + GameObject newTarget = new GameObject(targetName); + newTarget.transform.parent = parentTransform; + newTarget.transform.localPosition = Vector3.zero; + newTarget.transform.localRotation = Quaternion.identity; + return newTarget.transform; + } +} \ No newline at end of file diff --git a/DesktopVRIK/DesktopVRIK.csproj b/DesktopVRIK/DesktopVRIK.csproj new file mode 100644 index 0000000..c947a92 --- /dev/null +++ b/DesktopVRIK/DesktopVRIK.csproj @@ -0,0 +1,52 @@ + + + + + net472 + enable + latest + false + + + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll + + + ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + + + ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll + + + ..\..\Giamoz\Giamoz\bin\Debug\net472\Giamoz.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\SteamVR.dll + + + ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll + + + ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll + + + + + + + + + diff --git a/DesktopVRIK/DesktopVRIK.sln b/DesktopVRIK/DesktopVRIK.sln new file mode 100644 index 0000000..6aba351 --- /dev/null +++ b/DesktopVRIK/DesktopVRIK.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}") = "DesktopVRIK", "DesktopVRIK.csproj", "{07F06485-C387-470A-A43D-F3779A059F30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07F06485-C387-470A-A43D-F3779A059F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07F06485-C387-470A-A43D-F3779A059F30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07F06485-C387-470A-A43D-F3779A059F30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07F06485-C387-470A-A43D-F3779A059F30}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {05F83429-FD88-4C7A-B7D5-40516382B6FD} + EndGlobalSection +EndGlobal diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs new file mode 100644 index 0000000..43cc8eb --- /dev/null +++ b/DesktopVRIK/HarmonyPatches.cs @@ -0,0 +1,35 @@ +using ABI_RC.Core.Player; +using ABI.CCK.Components; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using HarmonyLib; +using MelonLoader; +using RootMotion.FinalIK; + +namespace DesktopVRIK; + +[HarmonyPatch] +internal class HarmonyPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), "SetupAvatarGeneral")] + private static void InitializeDesktopIKSystem(ref CVRAvatar ____avatarDescriptor) + { + if (MetaPort.Instance.isUsingVr) return; + + //this will stop at the useless isVr return (the function is only ever called by vr anyways...) + IKSystem.Instance.InitializeAvatar(____avatarDescriptor); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(IKSystem), "InitializeAvatar")] + private static void InitializeDesktopAvatar(CVRAvatar avatar, ref VRIK ____vrik) + { + //need IKSystem to see VRIK component for setup + ____vrik = avatar.gameObject.AddComponent(); + //now i add my own VRIK stuff + NAKDesktopVRIK NAKVRIK = avatar.gameObject.AddComponent(); + NAKVRIK.CalibrateAvatarVRIK(avatar); + } +} \ No newline at end of file diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs new file mode 100644 index 0000000..21e25d9 --- /dev/null +++ b/DesktopVRIK/Main.cs @@ -0,0 +1,240 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.MovementSystem; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using HarmonyLib; +using MelonLoader; +using RootMotion.FinalIK; +using UnityEngine; +using UnityEngine.Events; + +namespace DesktopVRIK; + +public class DesktopVRIK : MelonMod +{ + + private static MelonPreferences_Category m_categoryDesktopVRIK; + private static MelonPreferences_Entry m_entryEnabled; + private static MelonPreferences_Entry m_entryEmulateHipMovement; + private static MelonPreferences_Entry m_entryEmoteVRIK; + private static MelonPreferences_Entry m_entryEmoteLookAtIK; + + public override void OnApplicationStart() + { + m_categoryDesktopVRIK = MelonPreferences.CreateCategory(nameof(DesktopVRIK)); + m_entryEnabled = m_categoryDesktopVRIK.CreateEntry("Enabled", true, description: "Attempt to give Desktop VRIK."); + m_entryEmulateHipMovement = m_categoryDesktopVRIK.CreateEntry("Emulate Hip Movement", true, description: "Emulates VRChat-like hip movement when moving head up/down on desktop."); + m_entryEmoteVRIK = m_categoryDesktopVRIK.CreateEntry("Disable Emote VRIK", true, description: "Disable VRIK while emoting."); + m_entryEmoteLookAtIK = m_categoryDesktopVRIK.CreateEntry("Disable Emote LookAtIK", true, description: "Disable LookAtIK while emoting."); + } + + [HarmonyPatch] + private class HarmonyPatches + { + private static bool emotePlayed = false; + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), "Update")] + private static void CorrectVRIK(ref bool ____emotePlaying, ref LookAtIK ___lookIK) + { + if (MetaPort.Instance.isUsingVr) return; + + if (IKSystem.vrik == null) return; + + //pretty much zero out VRIK trying to locomote us using autofootstep + IKSystem.Instance.avatar.transform.localPosition = Vector3.zero; + IKSystem.Instance.avatar.transform.localRotation = Quaternion.identity; + + //TODO: Smooth out offset when walking/running + if ( m_entryEmulateHipMovement.Value ) + { + float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x; + angle = (angle > 180) ? angle - 360 : angle; + float weight = (1 - MovementSystem.Instance.movementVector.magnitude); + Quaternion rotation = Quaternion.AngleAxis(angle * weight, IKSystem.Instance.avatar.transform.right); + IKSystem.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation); + } + + + //Avatar Motion Tweaker has custom emote detection to disable VRIK via state tags + if (____emotePlaying && !emotePlayed) + { + emotePlayed = true; + if (m_entryEmoteVRIK.Value) + { + BodySystem.TrackingEnabled = false; + IKSystem.vrik.solver.Reset(); + } + if (m_entryEmoteLookAtIK.Value && ___lookIK != null) + { + ___lookIK.enabled = false; + } + } + else if (!____emotePlaying && emotePlayed) + { + emotePlayed = false; + BodySystem.TrackingEnabled = true; + IKSystem.vrik.solver.Reset(); + if (___lookIK != null) + { + ___lookIK.enabled = true; + } + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(IKSystem), "InitializeAvatar")] + private static void InitializeDesktopAvatar(CVRAvatar avatar, ref VRIK ____vrik, ref HumanPoseHandler ____poseHandler, ref Vector3 ____referenceRootPosition, ref Quaternion ____referenceRootRotation, ref float[] ___HandCalibrationPoseMuscles) + { + if (!m_entryEnabled.Value) return; + + if (MetaPort.Instance.isUsingVr) return; + + //set avatar to + Quaternion initialRotation = avatar.transform.rotation; + avatar.transform.rotation = Quaternion.identity; + + ____vrik = avatar.gameObject.AddComponent(); + ____vrik.fixTransforms = false; + ____vrik.solver.locomotion.weight = 1f; + IKSystem.Instance.ApplyAvatarScaleToIk(avatar.viewPosition.y); + ____vrik.solver.locomotion.angleThreshold = 30f; + ____vrik.solver.locomotion.maxLegStretch = 0.75f; + ____vrik.solver.spine.headClampWeight = 0f; + ____vrik.solver.spine.minHeadHeight = 0f; + if (____vrik != null) + { + ____vrik.onPreSolverUpdate.AddListener(new UnityAction(IKSystem.Instance.OnPreSolverUpdate)); + } + + if (____poseHandler == null) + { + ____poseHandler = new HumanPoseHandler(IKSystem.Instance.animator.avatar, IKSystem.Instance.animator.transform); + } + ____poseHandler.GetHumanPose(ref IKSystem.Instance.humanPose); + ____referenceRootPosition = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Hips).position; + ____referenceRootRotation = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Hips).rotation; + for (int i = 0; i < ___HandCalibrationPoseMuscles.Length; i++) + { + IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, ___HandCalibrationPoseMuscles[i], ref IKSystem.Instance.humanPose.muscles); + } + ____poseHandler.SetHumanPose(ref IKSystem.Instance.humanPose); + if (IKSystem.Instance.applyOriginalHipPosition) + { + IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Hips).position = ____referenceRootPosition; + } + if (IKSystem.Instance.applyOriginalHipRotation) + { + IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Hips).rotation = ____referenceRootRotation; + } + + BodySystem.isCalibratedAsFullBody = false; + BodySystem.isCalibrating = false; + BodySystem.TrackingPositionWeight = 1f; + BodySystem.isCalibratedAsFullBody = false; + + //InitializeDesktopIK + + //centerEyeAnchor now is head bone + Transform headAnchor = FindIKTarget(IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.Head)); + IKSystem.Instance.headAnchorPositionOffset = Vector3.zero; + IKSystem.Instance.headAnchorRotationOffset = Vector3.zero; + + IKSystem.Instance.leftHandModel.SetActive(false); + IKSystem.Instance.rightHandModel.SetActive(false); + IKSystem.Instance.animator = IKSystem.Instance.avatar.GetComponent(); + IKSystem.Instance.animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; + + //tell game to not track limbs + BodySystem.TrackingLeftArmEnabled = false; + BodySystem.TrackingRightArmEnabled = false; + BodySystem.TrackingLeftLegEnabled = false; + BodySystem.TrackingRightLegEnabled = false; + + //create ik targets for avatars to utilize + //____vrik.solver.leftLeg.target = FindIKTarget(IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.LeftFoot)); + //____vrik.solver.rightLeg.target = FindIKTarget(IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.RightFoot)); + + ____vrik.solver.IKPositionWeight = 0f; + ____vrik.enabled = false; + + VRIKCalibrator.CalibrateHead(____vrik, headAnchor, IKSystem.Instance.headAnchorPositionOffset, IKSystem.Instance.headAnchorRotationOffset); + //IKSystem.Instance.leftHandPose.transform.position = ____vrik.references.leftHand.position; + //IKSystem.Instance.rightHandPose.transform.position = ____vrik.references.rightHand.position; + //____vrik.solver.leftArm.target = IKSystem.Instance.leftHandAnchor; + //____vrik.solver.rightArm.target = IKSystem.Instance.rightHandAnchor; + + //Transform boneTransform = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.LeftLowerArm); + //Transform boneTransform2 = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.RightLowerArm); + //if (boneTransform.GetComponent() == null) + //{ + // TwistRelaxer twistRelaxer = boneTransform.gameObject.AddComponent(); + // twistRelaxer.ik = ____vrik; + // twistRelaxer.child = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.LeftHand); + // twistRelaxer.weight = 0.5f; + // twistRelaxer.parentChildCrossfade = 0.9f; + // twistRelaxer.twistAngleOffset = 0f; + //} + //if (boneTransform2.GetComponent() == null) + //{ + // TwistRelaxer twistRelaxer2 = boneTransform2.gameObject.AddComponent(); + // twistRelaxer2.ik = ____vrik; + // twistRelaxer2.child = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.RightHand); + // twistRelaxer2.weight = 0.5f; + // twistRelaxer2.parentChildCrossfade = 0.9f; + // twistRelaxer2.twistAngleOffset = 0f; + //} + //if (IKSystem.Instance.animator != null && IKSystem.Instance.animator.avatar != null && IKSystem.Instance.animator.avatar.isHuman) + //{ + // Transform boneTransform3 = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal); + // if (boneTransform3 != null) + // { + // ____vrik.solver.leftArm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(____vrik.references.leftHand, ____vrik.references.leftForearm, boneTransform3); + // } + // Transform boneTransform4 = IKSystem.Instance.animator.GetBoneTransform(HumanBodyBones.RightThumbProximal); + // if (boneTransform4 != null) + // { + // ____vrik.solver.rightArm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(____vrik.references.rightHand, ____vrik.references.rightForearm, boneTransform4); + // } + //} + + ____vrik.enabled = true; + ____vrik.solver.IKPositionWeight = 1f; + ____vrik.solver.spine.maintainPelvisPosition = 0f; + + //prevent the IK from walking away + ____vrik.solver.locomotion.maxVelocity = 0f; + avatar.transform.rotation = initialRotation; + } + + private static Transform FindIKTarget(Transform targetParent) + { + /** + I want creators to be able to specify their own custom IK Targets, so they can move them around with animations if they want. + We check for existing target objects, and if none are found we make our own. + Naming scheme is parentobject name + " IK Target". + **/ + Transform parentTransform = targetParent.transform; + string targetName = parentTransform.name + " IK Target"; + + //check for existing target + foreach (object obj in parentTransform) + { + Transform childTransform = (Transform)obj; + if (childTransform.name == targetName) + { + return childTransform; + } + } + + //create new target if none are found + GameObject newTarget = new GameObject(targetName); + newTarget.transform.parent = parentTransform; + newTarget.transform.localPosition = Vector3.zero; + newTarget.transform.localRotation = Quaternion.identity; + return newTarget.transform; + } + } +} \ No newline at end of file diff --git a/DesktopVRIK/Properties/AssemblyInfo.cs b/DesktopVRIK/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e0f699c --- /dev/null +++ b/DesktopVRIK/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using MelonLoader; +using System.Reflection; +using DesktopVRIK.Properties; + + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(DesktopVRIK))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(DesktopVRIK))] + +[assembly: MelonInfo( + typeof(DesktopVRIK.DesktopVRIK), + nameof(DesktopVRIK), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/DesktopVRIK" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace DesktopVRIK.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/DesktopVRIK/format.json b/DesktopVRIK/format.json new file mode 100644 index 0000000..aee1a24 --- /dev/null +++ b/DesktopVRIK/format.json @@ -0,0 +1,23 @@ +{ + "_id": 95, + "name": "DesktopVRIK", + "modversion": "1.1.0", + "gameversion": "2022r168", + "loaderversion": "0.5.4", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Corrects MM and QM position when avatar is scaled.\nAdditional option to scale player collision.", + "searchtags": [ + "menu", + "scale", + "avatarscale", + "slider" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/r2/DesktopVRIK.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRIK/", + "changelog": "Added option to scale player collision. Fixed some VR specific issues.", + "embedcolor": "804221" +} \ No newline at end of file