From cbd65aa8130d8f2368687aceb593f0a480f2335e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Fri, 24 Feb 2023 20:07:48 -0600 Subject: [PATCH 1/8] rewrite --- DesktopVRIK/DesktopVRIK.cs | 238 ++++++------- DesktopVRIK/DesktopVRIK.csproj | 7 +- DesktopVRIK/DesktopVRIKCalibrator.cs | 450 +++++++++++++++++++++++++ DesktopVRIK/DesktopVRIK_Helper.cs | 108 ------ DesktopVRIK/HarmonyPatches.cs | 235 ++----------- DesktopVRIK/Integrations/BTKUIAddon.cs | 48 --- DesktopVRIK/Main.cs | 79 ++--- DesktopVRIK/Properties/AssemblyInfo.cs | 9 +- 8 files changed, 635 insertions(+), 539 deletions(-) create mode 100644 DesktopVRIK/DesktopVRIKCalibrator.cs delete mode 100644 DesktopVRIK/DesktopVRIK_Helper.cs delete mode 100644 DesktopVRIK/Integrations/BTKUIAddon.cs diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index 5e73a26..a63d9ce 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -1,169 +1,149 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Player; +using ABI_RC.Core.Player; using ABI_RC.Systems.IK; using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.MovementSystem; using RootMotion.FinalIK; +using System.Reflection; using UnityEngine; -using UnityEngine.Events; namespace NAK.Melons.DesktopVRIK; public class DesktopVRIK : MonoBehaviour { public static DesktopVRIK Instance; + public DesktopVRIKCalibrator Calibrator; - public static bool - Setting_Enabled, - Setting_EnforceViewPosition, - Setting_EmoteVRIK, - Setting_EmoteLookAtIK; + // DesktopVRIK Settings + public bool + Setting_Enabled = true, + Setting_HipMovement = true, + Setting_ResetOnLand = true, + Setting_PlantFeet = true, + Setting_EnforceViewPosition; + public float + Setting_BodyLeanWeight, + Setting_BodyHeadingLimit, + Setting_PelvisHeadingWeight, + Setting_ChestHeadingWeight; - public static float - Setting_BodyLeanWeight = 0.5f, - Setting_BodyAngleLimit = 0f; - - public Transform viewpoint; - public Vector3 eyeOffset; + // Internal Stuff + private float + ik_SimulatedRootAngle; + private bool + ms_lastGrounded, + ps_emoteIsPlaying; + static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance); void Start() { + Calibrator = new DesktopVRIKCalibrator(); Instance = this; + DesktopVRIKMod.UpdateAllSettings(); } - public void ChangeViewpointHandling(bool enabled) + public void OnSetupAvatarDesktop() { - if (Setting_EnforceViewPosition == enabled) return; - Setting_EnforceViewPosition = enabled; - if (enabled) - { - PlayerSetup.Instance.desktopCamera.transform.localPosition = Vector3.zero; - return; - } - PlayerSetup.Instance.desktopCamera.transform.localPosition = eyeOffset; + if (!Setting_Enabled) return; + Calibrator.SetupDesktopVRIK(); + ik_SimulatedRootAngle = transform.eulerAngles.y; } - public void AlternativeOnPreSolverUpdate() + //public void OnReCalibrateAvatar() + //{ + // Calibrator.RecalibrateDesktopVRIK(); + // ik_SimulatedRootAngle = transform.eulerAngles.y; + //} + + public bool OnApplyAvatarScaleToIk(float height) { - //this order matters, rotation offset will be choppy if avatar is not cenetered first - - DesktopVRIK_Helper.Instance?.OnUpdateVRIK(); - - //Reset avatar offset (VRIK will literally make you walk away from root otherwise) - IKSystem.vrik.transform.localPosition = Vector3.zero; - IKSystem.vrik.transform.localRotation = Quaternion.identity; - - IKSystem.vrik.solver.plantFeet = true; + if (Calibrator.vrik != null) + { + Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * height; + Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * height; + return true; + } + return false; } - public Animator animator; - - public VRIK AlternativeCalibration(CVRAvatar avatar) + public void OnPlayerSetupUpdate(bool isEmotePlaying) { - animator = avatar.GetComponent(); - Transform avatarHeadBone = animator.GetBoneTransform(HumanBodyBones.Head); - - //Stuff to make bad armatures work (Fuck you Default Robot Kyle) - avatar.transform.localPosition = Vector3.zero; - - //ikpose layer (specified by avatar author) - int ikposeLayerIndex = animator.GetLayerIndex("IKPose"); - int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes"); - if (ikposeLayerIndex != -1) + bool changed = isEmotePlaying != ps_emoteIsPlaying; + if (changed) { - animator.SetLayerWeight(ikposeLayerIndex, 1f); - if (locoLayerIndex != -1) + ps_emoteIsPlaying = isEmotePlaying; + Calibrator.vrik.transform.localPosition = Vector3.zero; + Calibrator.vrik.transform.localRotation = Quaternion.identity; + if (Calibrator.lookAtIK != null) { - animator.SetLayerWeight(locoLayerIndex, 0f); + Calibrator.lookAtIK.enabled = !isEmotePlaying; } - animator.Update(0f); + BodySystem.TrackingEnabled = !isEmotePlaying; + Calibrator.vrik.solver?.Reset(); + } + } + + public void OnPreSolverUpdate() + { + if (ps_emoteIsPlaying) return; + + bool isGrounded = (bool)ms_isGrounded.GetValue(MovementSystem.Instance); + + // Calculate everything that affects weight + float weight = Calibrator.vrik.solver.IKPositionWeight; + weight *= (1 - MovementSystem.Instance.movementVector.magnitude); + weight *= isGrounded ? 1f : 0f; + + // Reset avatar offset (VRIK will literally make you walk away from root otherwise) + Calibrator.vrik.transform.localPosition = Vector3.zero; + Calibrator.vrik.transform.localRotation = Quaternion.identity; + + // Plant feet is nice for Desktop + Calibrator.vrik.solver.plantFeet = Setting_PlantFeet; + + // This is nice for walk cycles + //Calibrator.vrik.solver.spine.rotateChestByHands = Setting_RotateChestByHands * weight; + + //reset solver if weight changes dramatically + if (Setting_ResetOnLand) + { + if (isGrounded && !ms_lastGrounded) + { + Calibrator.vrik.solver.Reset(); + } + ms_lastGrounded = isGrounded; } - VRIK vrik = avatar.gameObject.AddComponent(); - vrik.AutoDetectReferences(); - - //fuck toes - vrik.references.leftToes = null; - vrik.references.rightToes = null; - - vrik.fixTransforms = true; - vrik.solver.plantFeet = false; - vrik.solver.locomotion.angleThreshold = 30f; - vrik.solver.locomotion.maxLegStretch = 0.75f; - vrik.solver.spine.minHeadHeight = -100f; - - vrik.solver.spine.bodyRotStiffness = 0.15f; - vrik.solver.spine.headClampWeight = 1f; - vrik.solver.spine.maintainPelvisPosition = 1f; - vrik.solver.spine.neckStiffness = 0f; - - vrik.solver.locomotion.weight = 0f; - vrik.solver.spine.bodyPosStiffness = 0f; - vrik.solver.spine.positionWeight = 0f; - vrik.solver.spine.pelvisPositionWeight = 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; - vrik.solver.IKPositionWeight = 0f; - - BodySystem.TrackingLeftArmEnabled = false; - BodySystem.TrackingRightArmEnabled = false; - BodySystem.TrackingLeftLegEnabled = false; - BodySystem.TrackingRightLegEnabled = false; - BodySystem.TrackingPositionWeight = 0f; - - //Custom funky AF head ik shit - foreach (Transform transform in DesktopVRIK_Helper.Instance.ik_HeadFollower) + // Old VRChat hip movement emulation + if (Setting_BodyLeanWeight > 0) { - if (transform.name == "Head IK Target") - { - Destroy(transform.gameObject); - } + float weightedAngle = Setting_BodyLeanWeight * weight; + float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x; + angle = (angle > 180) ? angle - 360 : angle; + Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, IKSystem.Instance.avatar.transform.right); + Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation); } - DesktopVRIK_Helper.Instance.avatar_HeadBone = avatarHeadBone; - DesktopVRIK_Helper.Instance.ik_HeadFollower.position = avatarHeadBone.position; - DesktopVRIK_Helper.Instance.ik_HeadFollower.rotation = Quaternion.identity; - VRIKCalibrator.CalibrateHead(vrik, DesktopVRIK_Helper.Instance.ik_HeadFollower.transform, IKSystem.Instance.headAnchorPositionOffset, IKSystem.Instance.headAnchorRotationOffset); - DesktopVRIK_Helper.Instance.ik_HeadFollower.localRotation = Quaternion.identity; - - //force immediate calibration before animator decides to fuck us - vrik.solver.SetToReferences(vrik.references); - vrik.solver.Initiate(vrik.transform); - - if (ikposeLayerIndex != -1) + // Make root heading follow within a set limit + if (Setting_BodyHeadingLimit > 0) { - animator.SetLayerWeight(ikposeLayerIndex, 0f); - if (locoLayerIndex != -1) + float weightedAngleLimit = Setting_BodyHeadingLimit * weight; + float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_SimulatedRootAngle); + float angleMaxDelta = Mathf.Abs(currentAngle); + if (angleMaxDelta > weightedAngleLimit) { - animator.SetLayerWeight(locoLayerIndex, 1f); + currentAngle = Mathf.Sign(currentAngle) * weightedAngleLimit; + ik_SimulatedRootAngle = Mathf.MoveTowardsAngle(ik_SimulatedRootAngle, transform.eulerAngles.y, angleMaxDelta - weightedAngleLimit); + } + Calibrator.vrik.solver.spine.rootHeadingOffset = currentAngle; + if (Setting_PelvisHeadingWeight > 0) + { + Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Pelvis, new Vector3(0f, currentAngle * Setting_PelvisHeadingWeight, 0f)); + Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, -currentAngle * Setting_PelvisHeadingWeight, 0f)); + } + if (Setting_ChestHeadingWeight > 0) + { + Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, currentAngle * Setting_ChestHeadingWeight, 0f)); } } - - //Find eyeoffset - eyeOffset = PlayerSetup.Instance.desktopCamera.transform.localPosition; - viewpoint = avatarHeadBone.Find("LocalHeadPoint"); - ChangeViewpointHandling(Setting_EnforceViewPosition); - - //reset ikpose layer - if (ikposeLayerIndex != -1) - { - animator.SetLayerWeight(ikposeLayerIndex, 0f); - if (locoLayerIndex != -1) - { - animator.SetLayerWeight(locoLayerIndex, 1f); - } - } - - vrik?.onPreSolverUpdate.AddListener(new UnityAction(this.AlternativeOnPreSolverUpdate)); - - DesktopVRIK_Helper.Instance?.OnResetIK(); - - return vrik; } } \ No newline at end of file diff --git a/DesktopVRIK/DesktopVRIK.csproj b/DesktopVRIK/DesktopVRIK.csproj index 5a31a95..ea97a04 100644 --- a/DesktopVRIK/DesktopVRIK.csproj +++ b/DesktopVRIK/DesktopVRIK.csproj @@ -18,9 +18,6 @@ C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll - - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll @@ -44,6 +41,10 @@ + + + + diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs new file mode 100644 index 0000000..e16340f --- /dev/null +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -0,0 +1,450 @@ +using ABI.CCK.Components; +using ABI_RC.Core; +using ABI_RC.Core.Base; +using ABI_RC.Core.Player; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using HarmonyLib; +using RootMotion.FinalIK; +using UnityEngine; +using UnityEngine.Events; + +namespace NAK.Melons.DesktopVRIK; + +public class DesktopVRIKCalibrator +{ + public DesktopVRIKCalibrator() + { + // Get base game scripts. + ikSystem = IKSystem.Instance; + playerSetup = PlayerSetup.Instance; + + // Get traverse to private shit in iksystem. + _vrikTraverse = Traverse.Create(ikSystem).Field("_vrik"); + _avatarTraverse = Traverse.Create(ikSystem).Field("_avatar"); + _animatorManagerTraverse = Traverse.Create(ikSystem).Field("_animatorManager"); + _poseHandlerTraverse = Traverse.Create(ikSystem).Field("_poseHandler"); + _avatarRootHeightTraverse = Traverse.Create(ikSystem).Field("_avatarRootHeight"); + + // Get traverse to private shit in playersetup. + _lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK"); + } + + //Settings + public bool Setting_UseVRIKToes = true; + public bool Setting_FindUnmappedToes = true; + + //DesktopVRIK + public CVRAvatar avatar; + public Animator animator; + public Transform avatarTransform; + public VRIK vrik; + public LookAtIK lookAtIK; + public HumanPoseHandler humanPoseHandler; + public HumanPose initialHumanPose; + public bool fixTransformsRequired; + public float initialFootDistance; + public float initialStepThreshold; + + //Traverse + private IKSystem ikSystem; + private PlayerSetup playerSetup; + private Traverse _vrikTraverse; + private Traverse _lookIKTraverse; + private Traverse _avatarTraverse; + private Traverse _animatorManagerTraverse; + private Traverse _poseHandlerTraverse; + private Traverse _avatarRootHeightTraverse; + + //public void RecalibrateDesktopVRIK() + //{ + // if (avatar != null) + // { + // //calibrate VRIK + // CalibrateDesktopVRIK(); + // } + // else + // { + // //we never calibrated + // SetupDesktopVRIK(); + // } + //} + + public void SetupDesktopVRIK() + { + //store avatar root transform & center it + avatar = playerSetup._avatar.GetComponent(); + animator = avatar.GetComponent(); + avatarTransform = avatar.transform; + avatarTransform.localPosition = Vector3.zero; + lookAtIK = _lookIKTraverse.GetValue(); + + //prepare for VRIK + PrepareIKSystem(); + CalibrateDesktopVRIK(); + + //add presolver update listener + vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); + } + + private void CalibrateDesktopVRIK() + { + //calibrate VRIK + PrepareAvatarVRIK(); + SetAvatarIKPose(true); + CalculateInitialIKScaling(); + CalibrateHeadIK(); + ForceInitiateVRIKSolver(); + SetAvatarIKPose(false); + } + + private void PrepareIKSystem() + { + // Get the animator manager and human pose handler + var animatorManager = _animatorManagerTraverse.GetValue(); + humanPoseHandler = _poseHandlerTraverse.GetValue(); + + // Store the avatar component + _avatarTraverse.SetValue(avatar); + + // Set the animator for the IK system + ikSystem.animator = animator; + if (ikSystem.animator != null) + { + animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController); + } + + // Set the avatar height float + float avatarHeight = ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y; + _avatarRootHeightTraverse.SetValue(avatarHeight); + + // Create a new human pose handler and dispose the old one + if (humanPoseHandler != null) + { + humanPoseHandler.Dispose(); + _poseHandlerTraverse.SetValue(null); + } + humanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); + _poseHandlerTraverse.SetValue(humanPoseHandler); + + // Find valid human bones + IKSystem.BoneExists.Clear(); + foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones))) + { + if (bone != HumanBodyBones.LastBone) + { + IKSystem.BoneExists.Add(bone, ikSystem.animator.GetBoneTransform(bone) != null); + } + } + + // Prepare BodySystem for calibration + BodySystem.TrackingLeftArmEnabled = false; + BodySystem.TrackingRightArmEnabled = false; + BodySystem.TrackingLeftLegEnabled = false; + BodySystem.TrackingRightLegEnabled = false; + BodySystem.TrackingPositionWeight = 0f; + } + + private void PrepareAvatarVRIK() + { + //add and configure VRIK + vrik = avatar.gameObject.AddComponentIfMissing(); + vrik.AutoDetectReferences(); + ConfigureVRIKReferences(); + _vrikTraverse.SetValue(vrik); + + //in testing, not really needed + //only required if Setting_FindUnmappedToes + //and non-human mapped toes are found + vrik.fixTransforms = fixTransformsRequired; + + //default solver settings + vrik.solver.locomotion.weight = 0f; + vrik.solver.locomotion.angleThreshold = 30f; + vrik.solver.locomotion.maxLegStretch = 0.75f; + vrik.solver.spine.minHeadHeight = 0f; + vrik.solver.IKPositionWeight = 1f; + //disable to not bleed into anims + vrik.solver.spine.chestClampWeight = 0f; + vrik.solver.spine.maintainPelvisPosition = 0f; + //for body leaning + vrik.solver.spine.neckStiffness = 0.0001f; //cannot be 0 + vrik.solver.spine.bodyPosStiffness = 1f; + vrik.solver.spine.bodyRotStiffness = 0.2f; + //disable so avatar doesnt try and walk away + //fixes nameplate spazzing on remote + vrik.solver.locomotion.velocityFactor = 0f; + vrik.solver.locomotion.maxVelocity = 0f; + //disable so PAM & BID dont make body shake + vrik.solver.spine.rotateChestByHands = 0f; + //enable so knees on fucked models work better + vrik.solver.leftLeg.useAnimatedBendNormal = true; + vrik.solver.rightLeg.useAnimatedBendNormal = true; + //enable to prioritize LookAtIK + vrik.solver.spine.headClampWeight = 0.2f; + //disable to not go on tippytoes + vrik.solver.spine.positionWeight = 0f; + vrik.solver.spine.rotationWeight = 1f; + + //vrik.solver.spine.maintainPelvisPosition = 1f; + //vrik.solver.locomotion.weight = 0f; + //vrik.solver.spine.positionWeight = 0f; + //vrik.solver.spine.pelvisPositionWeight = 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; + //vrik.solver.IKPositionWeight = 0f; + + //THESE ARE CONFIGURABLE IN GAME IK SETTINGS + //vrik.solver.leftLeg.target = null; + //vrik.solver.leftLeg.bendGoal = null; + //vrik.solver.leftLeg.positionWeight = 0f; + //vrik.solver.leftLeg.bendGoalWeight = 0f; + //vrik.solver.rightLeg.target = null; + //vrik.solver.rightLeg.bendGoal = null; + //vrik.solver.rightLeg.positionWeight = 0f; + //vrik.solver.rightLeg.bendGoalWeight = 0f; + //vrik.solver.spine.pelvisTarget = null; + //vrik.solver.spine.chestGoal = null; + //vrik.solver.spine.positionWeight = 0f; + //vrik.solver.spine.rotationWeight = 0f; + //vrik.solver.spine.pelvisPositionWeight = 0f; + //vrik.solver.spine.pelvisRotationWeight = 0f; + //vrik.solver.spine.chestGoalWeight = 0f; + } + + private void CalculateInitialIKScaling() + { + // Get distance between feets and thighs (fixes some weird armatures) + float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); + float thighDistance = Vector3.Distance(vrik.references.leftThigh.position, vrik.references.rightThigh.position); + float greatestDistance = Mathf.Min(footDistance, thighDistance); + initialFootDistance = greatestDistance * 0.5f; + initialStepThreshold = greatestDistance * 0.4f; + + //set initial values now, as avatars without scaling dont apply it + vrik.solver.locomotion.footDistance = initialFootDistance; + vrik.solver.locomotion.stepThreshold = initialStepThreshold; + } + + private void CalibrateHeadIK() + { + // Lazy HeadIKTarget calibration + if (vrik.solver.spine.headTarget == null) + { + vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform; + } + vrik.solver.spine.headTarget.parent = vrik.references.head; + vrik.solver.spine.headTarget.localPosition = Vector3.zero; + vrik.solver.spine.headTarget.localRotation = Quaternion.identity; + } + + private void SetAvatarIKPose(bool enforceTPose) + { + int ikposeLayerIndex = animator.GetLayerIndex("IKPose"); + int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes"); + + // Use custom IKPose if found. + if (ikposeLayerIndex != -1 && locoLayerIndex != -1) + { + animator.SetLayerWeight(ikposeLayerIndex, enforceTPose ? 1f : 0f); + animator.SetLayerWeight(locoLayerIndex, enforceTPose ? 0f : 1f); + animator.Update(0f); + return; + } + + // Otherwise use DesktopVRIK IKPose & revert afterwards. + if (enforceTPose) + { + humanPoseHandler.GetHumanPose(ref initialHumanPose); + humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); + for (int i = 0; i < IKPoseMuscles.Length; i++) + { + IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ikSystem.humanPose.muscles); + } + humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); + } + else + { + humanPoseHandler.SetHumanPose(ref initialHumanPose); + } + } + + private void ForceInitiateVRIKSolver() + { + //force immediate calibration before animator decides to fuck us + vrik.solver.SetToReferences(vrik.references); + vrik.solver.Initiate(vrik.transform); + } + + private void ConfigureVRIKReferences() + { + fixTransformsRequired = false; + + Transform leftShoulderBone = vrik.references.leftShoulder; + Transform rightShoulderBone = vrik.references.rightShoulder; + Transform assumedChest = leftShoulderBone?.parent; + + // Repair chest & spine bone references (valve models were messed up) + if (assumedChest != null && rightShoulderBone.parent == assumedChest && + vrik.references.chest != assumedChest) + { + vrik.references.chest = assumedChest; + vrik.references.spine = assumedChest.parent; + } + + if (!Setting_UseVRIKToes) + { + vrik.references.leftToes = null; + vrik.references.rightToes = null; + } + else if (Setting_FindUnmappedToes) + { + Transform leftToes = vrik.references.leftToes; + Transform rightToes = vrik.references.rightToes; + if (leftToes == null && rightToes == null) + { + leftToes = FindUnmappedToe(vrik.references.leftFoot); + rightToes = FindUnmappedToe(vrik.references.rightFoot); + if (leftToes != null && rightToes != null) + { + fixTransformsRequired = true; + vrik.references.leftToes = leftToes; + vrik.references.rightToes = rightToes; + } + } + } + + // Fix error when there is no finger bones + // Making up bullshit cause VRIK is evil otherwise + if (vrik.references.leftHand.childCount == 0) + { + vrik.solver.leftArm.wristToPalmAxis = Vector3.up; + vrik.solver.leftArm.palmToThumbAxis = -Vector3.forward; + } + if (vrik.references.rightHand.childCount == 0) + { + vrik.solver.rightArm.wristToPalmAxis = Vector3.up; + vrik.solver.rightArm.palmToThumbAxis = Vector3.forward; + } + } + + private Transform FindUnmappedToe(Transform foot) + { + foreach (Transform bone in foot) + { + if (bone.name.ToLowerInvariant().Contains("toe") || + bone.name.ToLowerInvariant().EndsWith("_end")) + { + return bone; + } + } + + return null; + } + + private static readonly float[] IKPoseMuscles = new float[] + { + 0.00133321f, + 8.195831E-06f, + 8.537738E-07f, + -0.002669832f, + -7.651234E-06f, + -0.001659694f, + 0f, + 0f, + 0f, + 0.04213953f, + 0.0003007996f, + -0.008032114f, + -0.03059979f, + -0.0003182998f, + 0.009640567f, + 0f, + 0f, + 0f, + 0f, + 0f, + 0f, + 0.5768794f, + 0.01061097f, + -0.1127839f, + 0.9705755f, + 0.07972051f, + -0.0268422f, + 0.007237188f, + 0f, + 0.5768792f, + 0.01056608f, + -0.1127519f, + 0.9705756f, + 0.07971933f, + -0.02682396f, + 0.007229362f, + 0f, + -5.651802E-06f, + -3.034899E-07f, + 0.4100508f, + 0.3610304f, + -0.0838329f, + 0.9262537f, + 0.1353517f, + -0.03578902f, + 0.06005657f, + -4.95989E-06f, + -1.43007E-06f, + 0.4096187f, + 0.363263f, + -0.08205152f, + 0.9250782f, + 0.1345718f, + -0.03572125f, + 0.06055461f, + -1.079177f, + 0.2095419f, + 0.6140652f, + 0.6365265f, + 0.6683931f, + -0.4764312f, + 0.8099416f, + 0.8099371f, + 0.6658203f, + -0.7327053f, + 0.8113618f, + 0.8114051f, + 0.6643661f, + -0.40341f, + 0.8111364f, + 0.8111367f, + 0.6170399f, + -0.2524227f, + 0.8138723f, + 0.8110135f, + -1.079171f, + 0.2095456f, + 0.6140658f, + 0.6365255f, + 0.6683878f, + -0.4764301f, + 0.8099402f, + 0.8099376f, + 0.6658241f, + -0.7327023f, + 0.8113653f, + 0.8113793f, + 0.664364f, + -0.4034042f, + 0.811136f, + 0.8111364f, + 0.6170469f, + -0.2524345f, + 0.8138595f, + 0.8110138f + }; +} + diff --git a/DesktopVRIK/DesktopVRIK_Helper.cs b/DesktopVRIK/DesktopVRIK_Helper.cs deleted file mode 100644 index ae4f433..0000000 --- a/DesktopVRIK/DesktopVRIK_Helper.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEngine; -using ABI_RC.Core.Player; -using ABI_RC.Systems.IK; -using ABI_RC.Systems.IK.SubSystems; -using ABI_RC.Systems.MovementSystem; -using RootMotion.FinalIK; - -namespace NAK.Melons.DesktopVRIK; - -internal class DesktopVRIK_Helper : MonoBehaviour -{ - public static DesktopVRIK_Helper Instance; - - //Avatar - public Transform avatar_HeadBone; - - //DesktopVRIK - public Transform ik_HeadFollower; - public Quaternion ik_HeadRotation; - - public static void CreateInstance() - { - Transform helper = new GameObject("[DesktopVRIK] Virtual Rig").transform; - helper.parent = PlayerSetup.Instance.transform; - helper.localPosition = Vector3.zero; - helper.localRotation = Quaternion.identity; - helper.gameObject.AddComponent(); - } - - void Start() - { - Instance = this; - - Transform headFollower = new GameObject("HeadBone_Follower").transform; - headFollower.parent = transform; - headFollower.localPosition = new Vector3(0f, 1.8f, 0f); - headFollower.localRotation = Quaternion.identity; - ik_HeadFollower = headFollower; - } - - public void OnUpdateVRIK() - { - if (avatar_HeadBone != null) - { - float globalWeight = (1 - MovementSystem.Instance.movementVector.magnitude); - globalWeight *= IKSystem.vrik.solver.locomotion.weight; - - //the most important thing ever - //IKSystem.vrik.solver.spine.rotationWeight = globalWeight; - - HeadIK_FollowPosition(); - - HeadIK_RotateWithWeight(globalWeight); - HeadIK_FollowWithinAngle(globalWeight); - ik_HeadFollower.rotation = ik_HeadRotation; - } - } - - public void OnResetIK() - { - ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z); - } - - public void HeadIK_FollowPosition() - { - ik_HeadFollower.position = new Vector3(transform.position.x, avatar_HeadBone.position.y, transform.position.z); - } - - public void HeadIK_FollowWithinAngle(float weight) - { - if (DesktopVRIK.Setting_BodyAngleLimit != 0) - { - float weightedAngle = DesktopVRIK.Setting_BodyAngleLimit * weight; - float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_HeadRotation.eulerAngles.y); - if (Mathf.Abs(currentAngle) > weightedAngle) - { - float fixedCurrentAngle = currentAngle > 0 ? currentAngle : -currentAngle; - float clampedAngle = Mathf.MoveTowardsAngle(ik_HeadRotation.eulerAngles.y, transform.eulerAngles.y, fixedCurrentAngle - weightedAngle); - ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, clampedAngle, 0); - } - } - else - { - ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z); - } - } - - public void HeadIK_RotateWithWeight(float weight) - { - //VRChat hip movement emulation - if (DesktopVRIK.Setting_BodyLeanWeight != 0) - { - float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x; - if (angle > 180) angle -= 360; - float leanAmount = angle * weight * DesktopVRIK.Setting_BodyLeanWeight; - ik_HeadRotation = Quaternion.Euler(leanAmount * 0.33f, ik_HeadRotation.eulerAngles.y, 0); - } - else - { - ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, ik_HeadRotation.eulerAngles.y, transform.eulerAngles.z); - } - } -} \ No newline at end of file diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs index 2a9a6b1..2a9562e 100644 --- a/DesktopVRIK/HarmonyPatches.cs +++ b/DesktopVRIK/HarmonyPatches.cs @@ -1,34 +1,21 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; +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; /** The process of calibrating VRIK is fucking painful. - Immediatly doing GetHumanPose() and then SetHumanPose() fixed heels in ground for all avatars. - - Setting the avatars rotation to identity, head rotation offset to head bone world rotation, and then calibrating head IK target (kinda) fixed only robot kyle. - - Enforcing a TPose only fixed my ferret avatars right shoulder. - - Mix and matching these, and changing order, fucks with random specific avatars with fucky armatures. - MOST AVATARS DONT EVEN CHANGE, ITS JUST THESE FEW SPECIFIC ONES - I NEED to look into an IKPose controller... - Avatars of Note: - TurtleNeck Ferret- broken/inverted right shoulder - Space Robot Kyle- head ik target is rotated -90 90 0, so body/neck is fucked (Fuck you Default Robot Kyle) - Exteratta- the knees bend backwards like a fucking chicken... what the fuck im enforcing a tpose nowww + TurtleNeck Ferret- close feet, far shoulders, nonideal rig. + Space Robot Kyle- the worst bone rolls on the planet, tpose/headikcalibration fixed it mostly... ish. + Exteratta- knees bend backwards without proper tpose. + Chito- left foot is far back without proper tpose & foot ik distance, was uploaded in falling anim state. + Atlas (portal2)- Wide stance, proper feet distance needed to be calculated. + Freddy (gmod)- Doesn't have any fingers, wristToPalmAxis & palmToThumbAxis needed to be set manually. - Most other avatars play just fine. Never changes even when adding Tpose, rotating the avatar, headikrotationoffset, ect... - WHY (Fuck you Default Robot Kyle) + Most other avatars play just fine. **/ @@ -36,210 +23,44 @@ namespace NAK.Melons.DesktopVRIK.HarmonyPatches; class PlayerSetupPatches { - private static bool emotePlayed = false; - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), "SetupAvatarGeneral")] - static void SetupDesktopIKSystem(ref CVRAvatar ____avatarDescriptor, ref Animator ____animator) + [HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")] + static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator) { - if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) + if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman) { - if (____avatarDescriptor != null && ____animator != null && ____animator.isHuman) - { - //this will stop at the useless isVr return (the function is only ever called by vr anyways...) - IKSystem.Instance.InitializeAvatar(____avatarDescriptor); - } + DesktopVRIK.Instance?.OnSetupAvatarDesktop(); } } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerSetup), "Update")] - private static void CorrectVRIK(ref bool ____emotePlaying, ref LookAtIK ___lookIK) + static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying) { - if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) - { - bool changed = ____emotePlaying != emotePlayed; - if (changed) - { - emotePlayed = ____emotePlaying; - IKSystem.vrik.transform.localPosition = Vector3.zero; - IKSystem.vrik.transform.localRotation = Quaternion.identity; - if (DesktopVRIK.Setting_EmoteLookAtIK && ___lookIK != null) - { - ___lookIK.enabled = !____emotePlaying; - } - if (DesktopVRIK.Setting_EmoteVRIK) - { - BodySystem.TrackingEnabled = !____emotePlaying; - IKSystem.vrik.solver?.Reset(); - DesktopVRIK_Helper.Instance?.OnResetIK(); - } - } - } + DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying); } - - //should probably patch movement system instead - [HarmonyPrefix] - [HarmonyPatch(typeof(PlayerSetup), "HandleDesktopCameraPosition")] - private static void Prefix_PlayerSetup_HandleDesktopCameraPosition - ( - bool ignore, - ref PlayerSetup __instance, - ref MovementSystem - ____movementSystem, - ref int ___headBobbingLevel - ) - { - if (___headBobbingLevel != 2) - { - return; - } - - if (!DesktopVRIK.Setting_Enabled || !DesktopVRIK.Setting_EnforceViewPosition) - { - return; - } - - if (____movementSystem.disableCameraControl && !ignore) - { - return; - } - - if (DesktopVRIK.Instance.viewpoint == null) - { - return; - } - - __instance.desktopCamera.transform.position = DesktopVRIK.Instance.viewpoint.position; - return; - } + //[HarmonyPostfix] + //[HarmonyPatch(typeof(PlayerSetup), "ReCalibrateAvatar")] + //static void Postfix_PlayerSetup_ReCalibrateAvatar() + //{ + // DesktopVRIK.Instance?.OnReCalibrateAvatar(); + //} } class IKSystemPatches { [HarmonyPostfix] - [HarmonyPatch(typeof(IKSystem), "InitializeAvatar")] - private static void InitializeDesktopAvatarVRIK(CVRAvatar avatar, ref VRIK ____vrik, ref HumanPoseHandler ____poseHandler, ref HumanPose ___humanPose) + [HarmonyPatch(typeof(IKSystem), "Start")] + private static void Postfix_IKSystem_Start(ref IKSystem __instance) { - if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) - { - if (IKSystem.Instance.animator != null && IKSystem.Instance.animator.avatar != null && IKSystem.Instance.animator.avatar.isHuman) - { - if (____poseHandler == null) - { - ____poseHandler = new HumanPoseHandler(IKSystem.Instance.animator.avatar, IKSystem.Instance.animator.transform); - } - - ____poseHandler.GetHumanPose(ref ___humanPose); - for (int i = 0; i < IKPoseMuscles.Length; i++) - { - IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ___humanPose.muscles); - } - ____poseHandler.SetHumanPose(ref ___humanPose); - - ____vrik = DesktopVRIK.Instance.AlternativeCalibration(avatar); - IKSystem.Instance.ApplyAvatarScaleToIk(avatar.viewPosition.y); - } - } + __instance.gameObject.AddComponent(); } - private static readonly float[] IKPoseMuscles = new float[] + [HarmonyPrefix] + [HarmonyPatch(typeof(IKSystem), "ApplyAvatarScaleToIk")] + private static bool Prefix_IKSystem_ApplyAvatarScaleToIk(float height) { - 0.00133321f, - 8.195831E-06f, - 8.537738E-07f, - -0.002669832f, - -7.651234E-06f, - -0.001659694f, - 0f, - 0f, - 0f, - 0.04213953f, - 0.0003007996f, - -0.008032114f, - -0.03059979f, - -0.0003182998f, - 0.009640567f, - 0f, - 0f, - 0f, - 0f, - 0f, - 0f, - 0.5768794f, - 0.01061097f, - -0.1127839f, - 0.9705755f, - 0.07972051f, - -0.0268422f, - 0.007237188f, - 0f, - 0.5768792f, - 0.01056608f, - -0.1127519f, - 0.9705756f, - 0.07971933f, - -0.02682396f, - 0.007229362f, - 0f, - -5.651802E-06f, - -3.034899E-07f, - 0.4100508f, - 0.3610304f, - -0.0838329f, - 0.9262537f, - 0.1353517f, - -0.03578902f, - 0.06005657f, - -4.95989E-06f, - -1.43007E-06f, - 0.4096187f, - 0.363263f, - -0.08205152f, - 0.9250782f, - 0.1345718f, - -0.03572125f, - 0.06055461f, - -1.079177f, - 0.2095419f, - 0.6140652f, - 0.6365265f, - 0.6683931f, - -0.4764312f, - 0.8099416f, - 0.8099371f, - 0.6658203f, - -0.7327053f, - 0.8113618f, - 0.8114051f, - 0.6643661f, - -0.40341f, - 0.8111364f, - 0.8111367f, - 0.6170399f, - -0.2524227f, - 0.8138723f, - 0.8110135f, - -1.079171f, - 0.2095456f, - 0.6140658f, - 0.6365255f, - 0.6683878f, - -0.4764301f, - 0.8099402f, - 0.8099376f, - 0.6658241f, - -0.7327023f, - 0.8113653f, - 0.8113793f, - 0.664364f, - -0.4034042f, - 0.811136f, - 0.8111364f, - 0.6170469f, - -0.2524345f, - 0.8138595f, - 0.8110138f - }; + return !(bool)DesktopVRIK.Instance?.OnApplyAvatarScaleToIk(height); + } } diff --git a/DesktopVRIK/Integrations/BTKUIAddon.cs b/DesktopVRIK/Integrations/BTKUIAddon.cs deleted file mode 100644 index e2ab2ab..0000000 --- a/DesktopVRIK/Integrations/BTKUIAddon.cs +++ /dev/null @@ -1,48 +0,0 @@ -using BTKUILib; -using BTKUILib.UIObjects; -using System.Runtime.CompilerServices; - -namespace NAK.Melons.DesktopVRIK; - -public static class BTKUIAddon -{ - [MethodImpl(MethodImplOptions.NoInlining)] - public static void Init() - { - //Add myself to the Misc Menu - - Page miscPage = QuickMenuAPI.MiscTabPage; - Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory); - - AddMelonToggle(ref miscCategory, DesktopVRIKMod.m_entryEnabled); - - //Add my own page to not clog up Misc Menu - - Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK"); - desktopVRIKPage.MenuTitle = "DesktopVRIK Settings"; - desktopVRIKPage.MenuSubtitle = "Simplified settings for VRIK on Desktop."; - - Category desktopVRIKCategory = desktopVRIKPage.AddCategory("DesktopVRIK"); - - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnabled); - - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnforceViewPosition); - - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteVRIK); - - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteLookAtIK); - - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyLeanWeight, 0, 1f); - - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyAngleLimit, 0, 90f); - } - private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry entry) - { - category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; - } - - private static void AddMelonSlider(ref Page page, MelonLoader.MelonPreferences_Entry entry, float min, float max) - { - page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max).OnValueUpdated += f => entry.Value = f; - } -} \ No newline at end of file diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs index 524f85b..4f2aec4 100644 --- a/DesktopVRIK/Main.cs +++ b/DesktopVRIK/Main.cs @@ -1,5 +1,4 @@ -using ABI_RC.Core.Player; -using MelonLoader; +using MelonLoader; using UnityEngine; namespace NAK.Melons.DesktopVRIK; @@ -8,69 +7,71 @@ public class DesktopVRIKMod : MelonMod { internal const string SettingsCategory = "DesktopVRIK"; internal static MelonPreferences_Category m_categoryDesktopVRIK; - internal static MelonPreferences_Entry m_entryEnabled, + internal static MelonPreferences_Entry + m_entryEnabled, m_entryEnforceViewPosition, - m_entryEmoteVRIK, - m_entryEmoteLookAtIK; + m_entryResetIKOnLand, + m_entryPlantFeet, + m_entryUseVRIKToes, + m_entryFindUnmappedToes; internal static MelonPreferences_Entry m_entryBodyLeanWeight, - m_entryBodyAngleLimit; + m_entryBodyHeadingLimit, + m_entryPelvisHeadingWeight, + m_entryChestHeadingWeight; public override void OnInitializeMelon() { m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); m_entryEnabled = m_categoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); - m_entryEmoteVRIK = m_categoryDesktopVRIK.CreateEntry("Disable Emote VRIK", true, description: "Disable VRIK while emoting. Only disable if you are ok with looking dumb."); - m_entryEmoteLookAtIK = m_categoryDesktopVRIK.CreateEntry("Disable Emote LookAtIK", true, description: "Disable LookAtIK while emoting. This setting doesn't really matter, as LookAtIK isn't networked while doing an emote."); + m_entryResetIKOnLand = m_categoryDesktopVRIK.CreateEntry("Reset IK On Land", true, description: "Reset Solver IK when landing on the ground."); + m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving."); + m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you."); + m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?"); m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable."); - m_entryBodyAngleLimit = m_categoryDesktopVRIK.CreateEntry("Body Angle Limit", 0f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable. (this setting only affects the feet due to chillout not setting up the player controller for VRIK)"); + m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable."); + m_entryPelvisHeadingWeight = m_categoryDesktopVRIK.CreateEntry("Pelvis Heading Weight", 0.25f, description: "How much the pelvis will face the heading limit. Set to 0 to align with head."); + m_entryChestHeadingWeight = m_categoryDesktopVRIK.CreateEntry("Chest Heading Weight", 0.75f, description: "How much the chest will face the heading limit. Set to 0 to align with head."); foreach (var setting in m_categoryDesktopVRIK.Entries) { setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings); } - //BTKUILib Misc Support - if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib")) - { - MelonLogger.Msg("Initializing BTKUILib support."); - BTKUIAddon.Init(); - } - - //Apply patches (i stole) ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); - MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); + InitializeIntegrations(); } - System.Collections.IEnumerator WaitForLocalPlayer() - { - while (PlayerSetup.Instance == null) - yield return null; - - DesktopVRIK_Helper.CreateInstance(); - PlayerSetup.Instance.gameObject.AddComponent(); - - while (DesktopVRIK.Instance == null) - yield return null; - UpdateAllSettings(); - } - - private void UpdateAllSettings() + internal static void UpdateAllSettings() { if (!DesktopVRIK.Instance) return; - DesktopVRIK.Setting_Enabled = m_entryEnabled.Value; - DesktopVRIK.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); - DesktopVRIK.Setting_BodyAngleLimit = Mathf.Clamp(m_entryBodyAngleLimit.Value, 0f, 90f); - DesktopVRIK.Setting_EmoteVRIK = m_entryEmoteVRIK.Value; - DesktopVRIK.Setting_EmoteLookAtIK = m_entryEmoteLookAtIK.Value; - DesktopVRIK.Instance.ChangeViewpointHandling(m_entryEnforceViewPosition.Value); + // DesktopVRIK Settings + DesktopVRIK.Instance.Setting_Enabled = m_entryEnabled.Value; + DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); + DesktopVRIK.Instance.Setting_ResetOnLand = m_entryResetIKOnLand.Value; + DesktopVRIK.Instance.Setting_PlantFeet = m_entryPlantFeet.Value; + DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(m_entryBodyHeadingLimit.Value, 0f, 90f); + DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(m_entryPelvisHeadingWeight.Value)); + DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(m_entryChestHeadingWeight.Value)); + // Calibration Settings + DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value; + DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value; } - private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); + private static void InitializeIntegrations() + { + //BTKUILib Misc Tab + if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib")) + { + MelonLogger.Msg("Initializing BTKUILib support."); + //BTKUIAddon.Init(); + } + } + private void ApplyPatches(Type type) { try diff --git a/DesktopVRIK/Properties/AssemblyInfo.cs b/DesktopVRIK/Properties/AssemblyInfo.cs index 2494ba3..879ab4b 100644 --- a/DesktopVRIK/Properties/AssemblyInfo.cs +++ b/DesktopVRIK/Properties/AssemblyInfo.cs @@ -1,8 +1,7 @@ -using DesktopVRIK.Properties; -using MelonLoader; +using MelonLoader; +using NAK.Melons.DesktopVRIK.Properties; using System.Reflection; - [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] @@ -24,9 +23,9 @@ using System.Reflection; [assembly: MelonOptionalDependencies("BTKUILib")] [assembly: HarmonyDontPatchAll] -namespace DesktopVRIK.Properties; +namespace NAK.Melons.DesktopVRIK.Properties; internal static class AssemblyInfoParams { - public const string Version = "2.0.5"; + public const string Version = "4.0.0"; public const string Author = "NotAKidoS"; } \ No newline at end of file From a27d42d8769d9c79a17e179d66a06575d38912db Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 28 Feb 2023 07:16:32 -0600 Subject: [PATCH 2/8] push --- DesktopVRIK/DesktopVRIK.cs | 47 +++++---- DesktopVRIK/DesktopVRIKCalibrator.cs | 152 +++++++++++++++------------ DesktopVRIK/HarmonyPatches.cs | 19 ++-- 3 files changed, 113 insertions(+), 105 deletions(-) diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index a63d9ce..47e1a5d 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -30,7 +30,6 @@ public class DesktopVRIK : MonoBehaviour private float ik_SimulatedRootAngle; private bool - ms_lastGrounded, ps_emoteIsPlaying; static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance); @@ -45,26 +44,31 @@ public class DesktopVRIK : MonoBehaviour { if (!Setting_Enabled) return; Calibrator.SetupDesktopVRIK(); - ik_SimulatedRootAngle = transform.eulerAngles.y; + ResetDesktopVRIK(); } - //public void OnReCalibrateAvatar() - //{ - // Calibrator.RecalibrateDesktopVRIK(); - // ik_SimulatedRootAngle = transform.eulerAngles.y; - //} - - public bool OnApplyAvatarScaleToIk(float height) + public bool OnSetupIKScaling(float avatarHeight, float scaleDifference) { if (Calibrator.vrik != null) { - Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * height; - Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * height; + Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * scaleDifference; + Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * scaleDifference; + DesktopVRIK.ScaleStepHeight(Calibrator.vrik.solver.locomotion.stepHeight, Calibrator.initialStepHeight * scaleDifference); + Calibrator.vrik.solver.Reset(); + ResetDesktopVRIK(); + return true; } return false; } + public static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag) + { + Keyframe[] keyframes = stepHeightCurve.keys; + keyframes[1].value = mag; + stepHeightCurve.keys = keyframes; + } + public void OnPlayerSetupUpdate(bool isEmotePlaying) { bool changed = isEmotePlaying != ps_emoteIsPlaying; @@ -79,12 +83,21 @@ public class DesktopVRIK : MonoBehaviour } BodySystem.TrackingEnabled = !isEmotePlaying; Calibrator.vrik.solver?.Reset(); + ResetDesktopVRIK(); } } + public void ResetDesktopVRIK() + { + ik_SimulatedRootAngle = transform.eulerAngles.y; + } + public void OnPreSolverUpdate() { - if (ps_emoteIsPlaying) return; + if (ps_emoteIsPlaying) + { + return; + } bool isGrounded = (bool)ms_isGrounded.GetValue(MovementSystem.Instance); @@ -103,16 +116,6 @@ public class DesktopVRIK : MonoBehaviour // This is nice for walk cycles //Calibrator.vrik.solver.spine.rotateChestByHands = Setting_RotateChestByHands * weight; - //reset solver if weight changes dramatically - if (Setting_ResetOnLand) - { - if (isGrounded && !ms_lastGrounded) - { - Calibrator.vrik.solver.Reset(); - } - ms_lastGrounded = isGrounded; - } - // Old VRChat hip movement emulation if (Setting_BodyLeanWeight > 0) { diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs index e16340f..abbcba0 100644 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -30,45 +30,33 @@ public class DesktopVRIKCalibrator _lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK"); } - //Settings + // Settings public bool Setting_UseVRIKToes = true; public bool Setting_FindUnmappedToes = true; - //DesktopVRIK + // DesktopVRIK public CVRAvatar avatar; public Animator animator; public Transform avatarTransform; public VRIK vrik; public LookAtIK lookAtIK; + // Calibration public HumanPoseHandler humanPoseHandler; public HumanPose initialHumanPose; + // Calibrator public bool fixTransformsRequired; - public float initialFootDistance; - public float initialStepThreshold; + public float initialFootDistance, initialStepThreshold, initialStepHeight; - //Traverse + // Traverse private IKSystem ikSystem; private PlayerSetup playerSetup; - private Traverse _vrikTraverse; - private Traverse _lookIKTraverse; - private Traverse _avatarTraverse; - private Traverse _animatorManagerTraverse; - private Traverse _poseHandlerTraverse; - private Traverse _avatarRootHeightTraverse; - - //public void RecalibrateDesktopVRIK() - //{ - // if (avatar != null) - // { - // //calibrate VRIK - // CalibrateDesktopVRIK(); - // } - // else - // { - // //we never calibrated - // SetupDesktopVRIK(); - // } - //} + private Traverse + _vrikTraverse, + _lookIKTraverse, + _avatarTraverse, + _animatorManagerTraverse, + _poseHandlerTraverse, + _avatarRootHeightTraverse; public void SetupDesktopVRIK() { @@ -92,9 +80,9 @@ public class DesktopVRIKCalibrator //calibrate VRIK PrepareAvatarVRIK(); SetAvatarIKPose(true); - CalculateInitialIKScaling(); CalibrateHeadIK(); ForceInitiateVRIKSolver(); + CalculateInitialIKScaling(); SetAvatarIKPose(false); } @@ -220,18 +208,19 @@ public class DesktopVRIKCalibrator private void CalculateInitialIKScaling() { - // Get distance between feets and thighs (fixes some weird armatures) + // Get distance between feets and thighs float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); - float thighDistance = Vector3.Distance(vrik.references.leftThigh.position, vrik.references.rightThigh.position); - float greatestDistance = Mathf.Min(footDistance, thighDistance); - initialFootDistance = greatestDistance * 0.5f; - initialStepThreshold = greatestDistance * 0.4f; + initialFootDistance = footDistance * 0.5f; + initialStepThreshold = footDistance * 0.4f; + initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; - //set initial values now, as avatars without scaling dont apply it + // Set initial values vrik.solver.locomotion.footDistance = initialFootDistance; vrik.solver.locomotion.stepThreshold = initialStepThreshold; + DesktopVRIK.ScaleStepHeight(vrik.solver.locomotion.stepHeight, initialStepHeight); } + private void CalibrateHeadIK() { // Lazy HeadIKTarget calibration @@ -262,12 +251,7 @@ public class DesktopVRIKCalibrator if (enforceTPose) { humanPoseHandler.GetHumanPose(ref initialHumanPose); - humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); - for (int i = 0; i < IKPoseMuscles.Length; i++) - { - IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ikSystem.humanPose.muscles); - } - humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); + SetCustomPose(IKPoseMuscles); } else { @@ -275,6 +259,16 @@ public class DesktopVRIKCalibrator } } + private void SetCustomPose(float[] muscleValues) + { + humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); + for (int i = 0; i < muscleValues.Length; i++) + { + IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, muscleValues[i], ref ikSystem.humanPose.muscles); + } + humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); + } + private void ForceInitiateVRIKSolver() { //force immediate calibration before animator decides to fuck us @@ -286,17 +280,8 @@ public class DesktopVRIKCalibrator { fixTransformsRequired = false; - Transform leftShoulderBone = vrik.references.leftShoulder; - Transform rightShoulderBone = vrik.references.rightShoulder; - Transform assumedChest = leftShoulderBone?.parent; - - // Repair chest & spine bone references (valve models were messed up) - if (assumedChest != null && rightShoulderBone.parent == assumedChest && - vrik.references.chest != assumedChest) - { - vrik.references.chest = assumedChest; - vrik.references.spine = assumedChest.parent; - } + //might not work over netik + FixChestAndSpineReferences(); if (!Setting_UseVRIKToes) { @@ -305,32 +290,44 @@ public class DesktopVRIKCalibrator } else if (Setting_FindUnmappedToes) { - Transform leftToes = vrik.references.leftToes; - Transform rightToes = vrik.references.rightToes; - if (leftToes == null && rightToes == null) - { - leftToes = FindUnmappedToe(vrik.references.leftFoot); - rightToes = FindUnmappedToe(vrik.references.rightFoot); - if (leftToes != null && rightToes != null) - { - fixTransformsRequired = true; - vrik.references.leftToes = leftToes; - vrik.references.rightToes = rightToes; - } - } + //doesnt work with netik, but its toes... + FindAndSetUnmappedToes(); } - // Fix error when there is no finger bones - // Making up bullshit cause VRIK is evil otherwise - if (vrik.references.leftHand.childCount == 0) + //bullshit fix to not cause death + FixFingerBonesError(); + } + + private void FixChestAndSpineReferences() + { + Transform leftShoulderBone = vrik.references.leftShoulder; + Transform rightShoulderBone = vrik.references.rightShoulder; + Transform assumedChest = leftShoulderBone?.parent; + + if (assumedChest != null && rightShoulderBone.parent == assumedChest && + vrik.references.chest != assumedChest) { - vrik.solver.leftArm.wristToPalmAxis = Vector3.up; - vrik.solver.leftArm.palmToThumbAxis = -Vector3.forward; + vrik.references.chest = assumedChest; + vrik.references.spine = assumedChest.parent; } - if (vrik.references.rightHand.childCount == 0) + } + + private void FindAndSetUnmappedToes() + { + Transform leftToes = vrik.references.leftToes; + Transform rightToes = vrik.references.rightToes; + + if (leftToes == null && rightToes == null) { - vrik.solver.rightArm.wristToPalmAxis = Vector3.up; - vrik.solver.rightArm.palmToThumbAxis = Vector3.forward; + leftToes = FindUnmappedToe(vrik.references.leftFoot); + rightToes = FindUnmappedToe(vrik.references.rightFoot); + + if (leftToes != null && rightToes != null) + { + vrik.references.leftToes = leftToes; + vrik.references.rightToes = rightToes; + fixTransformsRequired = true; + } } } @@ -348,6 +345,21 @@ public class DesktopVRIKCalibrator return null; } + private void FixFingerBonesError() + { + FixFingerBones(vrik.references.leftHand, vrik.solver.leftArm); + FixFingerBones(vrik.references.rightHand, vrik.solver.rightArm); + } + + private void FixFingerBones(Transform hand, IKSolverVR.Arm armSolver) + { + if (hand.childCount == 0) + { + armSolver.wristToPalmAxis = Vector3.up; + armSolver.palmToThumbAxis = hand == vrik.references.leftHand ? -Vector3.forward : Vector3.forward; + } + } + private static readonly float[] IKPoseMuscles = new float[] { 0.00133321f, diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs index 2a9562e..9b7d4af 100644 --- a/DesktopVRIK/HarmonyPatches.cs +++ b/DesktopVRIK/HarmonyPatches.cs @@ -40,12 +40,12 @@ class PlayerSetupPatches DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying); } - //[HarmonyPostfix] - //[HarmonyPatch(typeof(PlayerSetup), "ReCalibrateAvatar")] - //static void Postfix_PlayerSetup_ReCalibrateAvatar() - //{ - // DesktopVRIK.Instance?.OnReCalibrateAvatar(); - //} + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")] + private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference) + { + return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(height, 1f + ___scaleDifference.y); + } } class IKSystemPatches @@ -56,11 +56,4 @@ class IKSystemPatches { __instance.gameObject.AddComponent(); } - - [HarmonyPrefix] - [HarmonyPatch(typeof(IKSystem), "ApplyAvatarScaleToIk")] - private static bool Prefix_IKSystem_ApplyAvatarScaleToIk(float height) - { - return !(bool)DesktopVRIK.Instance?.OnApplyAvatarScaleToIk(height); - } } From d6c8b3c2e4dc8f2326dc38c5235ca822e9153ac8 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 28 Feb 2023 07:48:10 -0600 Subject: [PATCH 3/8] Update README.md --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e5c790..c2d4f73 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,16 @@ Adds VRIK to Desktop ChilloutVR avatars. No longer will you be a liveless slidin (adds the small feet stepping when looking around on Desktop) -Additional option to emulate VRChat-like hip movement if you like becoming a fish. +https://user-images.githubusercontent.com/37721153/221870123-fbe4f5e8-8d6e-4a43-aa5e-f2188e6491a9.mp4 -Control over disabling VRIK & LookAtIK during Emotes. (this can cause funky behavior) +## Configuration: +* Configurable body lean weight. Set to 0 to disable. + +* Configurable max root heading angle with chest/pelvis weight settings. Set to 0 to disable. + +* Options to disable VRIK from using mapped toes &/or find unmapped (non-human) toe bones. + +* Autofixes for avatars without fingers & incorrect chest/spine bone mapping (might not play well with netik). ## Relevant Feedback Posts: https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars @@ -22,4 +29,3 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. > To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. - From eae03f0ec2a7a6ba174efe3e1da86ee00242c39a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:31:23 -0600 Subject: [PATCH 4/8] add back btkui addon --- DesktopVRIK/DesktopVRIK.cs | 5 +-- DesktopVRIK/DesktopVRIK.csproj | 3 ++ DesktopVRIK/Integrations/BTKUIAddon.cs | 52 ++++++++++++++++++++++++++ DesktopVRIK/Main.cs | 10 ++--- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 DesktopVRIK/Integrations/BTKUIAddon.cs diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index 47e1a5d..f402744 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -16,10 +16,7 @@ public class DesktopVRIK : MonoBehaviour // DesktopVRIK Settings public bool Setting_Enabled = true, - Setting_HipMovement = true, - Setting_ResetOnLand = true, - Setting_PlantFeet = true, - Setting_EnforceViewPosition; + Setting_PlantFeet = true; public float Setting_BodyLeanWeight, Setting_BodyHeadingLimit, diff --git a/DesktopVRIK/DesktopVRIK.csproj b/DesktopVRIK/DesktopVRIK.csproj index ea97a04..592135f 100644 --- a/DesktopVRIK/DesktopVRIK.csproj +++ b/DesktopVRIK/DesktopVRIK.csproj @@ -18,6 +18,9 @@ C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + + ..\..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll diff --git a/DesktopVRIK/Integrations/BTKUIAddon.cs b/DesktopVRIK/Integrations/BTKUIAddon.cs new file mode 100644 index 0000000..7fd4f52 --- /dev/null +++ b/DesktopVRIK/Integrations/BTKUIAddon.cs @@ -0,0 +1,52 @@ +using BTKUILib; +using BTKUILib.UIObjects; +using System.Runtime.CompilerServices; + +namespace NAK.Melons.DesktopVRIK; + +public static class BTKUIAddon +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Init() + { + //Add myself to the Misc Menu + + Page miscPage = QuickMenuAPI.MiscTabPage; + Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory); + + AddMelonToggle(ref miscCategory, DesktopVRIKMod.m_entryEnabled); + + //Add my own page to not clog up Misc Menu + + Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK"); + desktopVRIKPage.MenuTitle = "DesktopVRIK Settings"; + desktopVRIKPage.MenuSubtitle = "Simplified settings for VRIK on Desktop."; + + Category desktopVRIKCategory = desktopVRIKPage.AddCategory("DesktopVRIK"); + + // General Settings + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnabled); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryPlantFeet); + + // Calibration Settings + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryUseVRIKToes); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryFindUnmappedToes); + + // Body Leaning Weight + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyLeanWeight, 0, 1f, 1); + + // Max Root Heading Limit & Weights + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyHeadingLimit, 0, 90f, 0); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryPelvisHeadingWeight, 0, 1f, 1); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryChestHeadingWeight, 0, 1f, 1); + } + private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry entry) + { + category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; + } + + private static void AddMelonSlider(ref Page page, MelonLoader.MelonPreferences_Entry entry, float min, float max, int decimalPlaces = 2) + { + page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f; + } +} \ No newline at end of file diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs index 4f2aec4..5425782 100644 --- a/DesktopVRIK/Main.cs +++ b/DesktopVRIK/Main.cs @@ -23,8 +23,7 @@ public class DesktopVRIKMod : MelonMod { m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); m_entryEnabled = m_categoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); - m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); - m_entryResetIKOnLand = m_categoryDesktopVRIK.CreateEntry("Reset IK On Land", true, description: "Reset Solver IK when landing on the ground."); + //m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving."); m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you."); m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?"); @@ -50,12 +49,13 @@ public class DesktopVRIKMod : MelonMod if (!DesktopVRIK.Instance) return; // DesktopVRIK Settings DesktopVRIK.Instance.Setting_Enabled = m_entryEnabled.Value; - DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); - DesktopVRIK.Instance.Setting_ResetOnLand = m_entryResetIKOnLand.Value; DesktopVRIK.Instance.Setting_PlantFeet = m_entryPlantFeet.Value; + + DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(m_entryBodyHeadingLimit.Value, 0f, 90f); DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(m_entryPelvisHeadingWeight.Value)); DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(m_entryChestHeadingWeight.Value)); + // Calibration Settings DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value; DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value; @@ -68,7 +68,7 @@ public class DesktopVRIKMod : MelonMod if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib")) { MelonLogger.Msg("Initializing BTKUILib support."); - //BTKUIAddon.Init(); + BTKUIAddon.Init(); } } From c6c9b712bd87734318f0541b7e3dacee5b4cde7f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Mon, 6 Mar 2023 19:53:27 -0600 Subject: [PATCH 5/8] implement knee bend normal calculation implemented knee bend normal calculation to fix knee bending at extreme scales, as well as finally fix robot kyle... tweaked ikpose method to also allow for setting to motorcycle pose, as well as resetting hip to default rotation --- DesktopVRIK/DesktopVRIK.cs | 4 +- DesktopVRIK/DesktopVRIKCalibrator.cs | 264 ++++++++++++++++++++------- DesktopVRIK/Main.cs | 11 +- 3 files changed, 209 insertions(+), 70 deletions(-) diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index f402744..093b84f 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -40,7 +40,7 @@ public class DesktopVRIK : MonoBehaviour public void OnSetupAvatarDesktop() { if (!Setting_Enabled) return; - Calibrator.SetupDesktopVRIK(); + Calibrator.CalibrateDesktopVRIK(); ResetDesktopVRIK(); } @@ -51,7 +51,7 @@ public class DesktopVRIK : MonoBehaviour Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * scaleDifference; Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * scaleDifference; DesktopVRIK.ScaleStepHeight(Calibrator.vrik.solver.locomotion.stepHeight, Calibrator.initialStepHeight * scaleDifference); - Calibrator.vrik.solver.Reset(); + //Calibrator.vrik.solver.Reset(); ResetDesktopVRIK(); return true; diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs index abbcba0..798e52c 100644 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -13,6 +13,42 @@ namespace NAK.Melons.DesktopVRIK; public class DesktopVRIKCalibrator { + // Settings + public bool Setting_UseVRIKToes = true; + public bool Setting_FindUnmappedToes = true; + public bool Setting_ExperimentalKneeBend = true; + public bool Setting_DebugCalibrationPose = false; + + // Avatar Component References + public CVRAvatar avatar; + public Animator animator; + public Transform avatarTransform; + public VRIK vrik; + public LookAtIK lookAtIK; + + // Calibrated Values + public float + initialFootDistance, + initialStepThreshold, + initialStepHeight; + + // Calibration Internals + bool DebugCalibrationPose; + bool fixTransformsRequired; + Vector3 leftKneeNormal, rightKneeNormal; + HumanPose initialHumanPose; + HumanPoseHandler humanPoseHandler; + // Traverse + IKSystem ikSystem; + PlayerSetup playerSetup; + Traverse + _vrikTraverse, + _lookIKTraverse, + _avatarTraverse, + _animatorManagerTraverse, + _poseHandlerTraverse, + _avatarRootHeightTraverse; + public DesktopVRIKCalibrator() { // Get base game scripts. @@ -30,67 +66,102 @@ public class DesktopVRIKCalibrator _lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK"); } - // Settings - public bool Setting_UseVRIKToes = true; - public bool Setting_FindUnmappedToes = true; - - // DesktopVRIK - public CVRAvatar avatar; - public Animator animator; - public Transform avatarTransform; - public VRIK vrik; - public LookAtIK lookAtIK; - // Calibration - public HumanPoseHandler humanPoseHandler; - public HumanPose initialHumanPose; - // Calibrator - public bool fixTransformsRequired; - public float initialFootDistance, initialStepThreshold, initialStepHeight; - - // Traverse - private IKSystem ikSystem; - private PlayerSetup playerSetup; - private Traverse - _vrikTraverse, - _lookIKTraverse, - _avatarTraverse, - _animatorManagerTraverse, - _poseHandlerTraverse, - _avatarRootHeightTraverse; - - public void SetupDesktopVRIK() + public void CalibrateDesktopVRIK() { - //store avatar root transform & center it + PreInitialize(); + + // Don't do anything else if just debugging calibration pose + DebugCalibrationPose = !DebugCalibrationPose; + if (Setting_DebugCalibrationPose && DebugCalibrationPose) + { + ForceCalibrationPose(); + return; + } + + // Add VRIK and configure + PrepareAvatarVRIK(); + + Initialize(); + + PostInitialize(); + } + + public void ForceCalibrationPose(bool toggle = true) + { + animator.enabled = !toggle; + SetHumanPose(0f); + //SetAvatarIKPose(toggle); + } + + private void PreInitialize() + { + // Scan avatar for issues/references + ScanAvatarForCalibration(); + // Prepare CVR IKSystem for external VRIK + PrepareIKSystem(); + } + + private void Initialize() + { + // Calculate bend normals with motorcycle pose + SetHumanPose(0f); + CalculateKneeBendNormals(); + + SetAvatarIKPose(true); + + // Setup HeadIK target & calculate initial footstep values + SetupDesktopHeadIKTarget(); + CalculateInitialIKScaling(); + + // Initiate VRIK manually + ForceInitiateVRIKSolver(); + + SetAvatarIKPose(false); + } + + private void PostInitialize() + { + ApplyKneeBendNormals(); + ApplyInitialIKScaling(); + vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); + } + + private void ScanAvatarForCalibration() + { + // Reset some stuff to default + fixTransformsRequired = false; + + // Find required avatar components avatar = playerSetup._avatar.GetComponent(); animator = avatar.GetComponent(); avatarTransform = avatar.transform; - avatarTransform.localPosition = Vector3.zero; lookAtIK = _lookIKTraverse.GetValue(); - //prepare for VRIK - PrepareIKSystem(); - CalibrateDesktopVRIK(); + // Apply some fixes for weird setups + if (!animator.enabled) + { + fixTransformsRequired = true; + DesktopVRIKMod.Logger.Error("Avatar has Animator disabled by default!"); + } - //add presolver update listener - vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); - } + // Center avatar local offsets + avatarTransform.localPosition = Vector3.zero; + //avatarTransform.localRotation = Quaternion.identity; - private void CalibrateDesktopVRIK() - { - //calibrate VRIK - PrepareAvatarVRIK(); - SetAvatarIKPose(true); - CalibrateHeadIK(); - ForceInitiateVRIKSolver(); - CalculateInitialIKScaling(); - SetAvatarIKPose(false); + // Store original human pose + if (humanPoseHandler != null) + { + humanPoseHandler.Dispose(); + } + humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform); + humanPoseHandler.GetHumanPose(ref initialHumanPose); } private void PrepareIKSystem() { // Get the animator manager and human pose handler var animatorManager = _animatorManagerTraverse.GetValue(); - humanPoseHandler = _poseHandlerTraverse.GetValue(); + var ikHumanPoseHandler = _poseHandlerTraverse.GetValue(); // Store the avatar component _avatarTraverse.SetValue(avatar); @@ -107,13 +178,13 @@ public class DesktopVRIKCalibrator _avatarRootHeightTraverse.SetValue(avatarHeight); // Create a new human pose handler and dispose the old one - if (humanPoseHandler != null) + if (ikHumanPoseHandler != null) { - humanPoseHandler.Dispose(); + ikHumanPoseHandler.Dispose(); _poseHandlerTraverse.SetValue(null); } - humanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); - _poseHandlerTraverse.SetValue(humanPoseHandler); + ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); + _poseHandlerTraverse.SetValue(ikHumanPoseHandler); // Find valid human bones IKSystem.BoneExists.Clear(); @@ -160,14 +231,12 @@ public class DesktopVRIKCalibrator vrik.solver.spine.bodyPosStiffness = 1f; vrik.solver.spine.bodyRotStiffness = 0.2f; //disable so avatar doesnt try and walk away - //fixes nameplate spazzing on remote vrik.solver.locomotion.velocityFactor = 0f; vrik.solver.locomotion.maxVelocity = 0f; + //fixes nameplate spazzing on remote & magicacloth + vrik.solver.locomotion.rootSpeed = 1000f; //disable so PAM & BID dont make body shake vrik.solver.spine.rotateChestByHands = 0f; - //enable so knees on fucked models work better - vrik.solver.leftLeg.useAnimatedBendNormal = true; - vrik.solver.rightLeg.useAnimatedBendNormal = true; //enable to prioritize LookAtIK vrik.solver.spine.headClampWeight = 0.2f; //disable to not go on tippytoes @@ -206,6 +275,70 @@ public class DesktopVRIKCalibrator //vrik.solver.spine.chestGoalWeight = 0f; } + private void CalculateKneeBendNormals() + { + // Get assumed left knee normal + Vector3[] leftVectors = new Vector3[] + { + vrik.references.leftThigh?.position ?? Vector3.zero, + vrik.references.leftCalf?.position ?? Vector3.zero, + vrik.references.leftFoot?.position ?? Vector3.zero, + }; + leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors); + + // Get assumed right knee normal + Vector3[] rightVectors = new Vector3[] + { + vrik.references.rightThigh?.position ?? Vector3.zero, + vrik.references.rightCalf?.position ?? Vector3.zero, + vrik.references.rightFoot?.position ?? Vector3.zero, + }; + rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors); + } + + private void ApplyKneeBendNormals() + { + if (!Setting_ExperimentalKneeBend) + { + //enable so knees on fucked models work better + vrik.solver.leftLeg.useAnimatedBendNormal = true; + vrik.solver.rightLeg.useAnimatedBendNormal = true; + return; + } + + vrik.solver.leftLeg.bendToTargetWeight = 0f; + vrik.solver.rightLeg.bendToTargetWeight = 0f; + + Traverse leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis"); + Traverse rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis"); + + // Calculate knee normal without root rotation but with pelvis rotation + Quaternion pelvisLocalRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation); + Vector3 leftLegBendNormalRelToPelvis = pelvisLocalRotationInverse * leftKneeNormal; + Vector3 rightLegBendNormalRelToPelvis = pelvisLocalRotationInverse * rightKneeNormal; + //Quaternion rootRotation = vrik.references.root.rotation; + //Quaternion pelvisRotationRelativeToRoot = Quaternion.Inverse(rootRotation) * vrik.references.pelvis.rotation; + //Quaternion pelvisRotationInverse = Quaternion.Inverse(pelvisRotationRelativeToRoot); + leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLegBendNormalRelToPelvis); + rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLegBendNormalRelToPelvis); + } + + private Vector3 GetNormalFromArray(Vector3[] positions) + { + Vector3 vector = Vector3.zero; + Vector3 vector2 = Vector3.zero; + for (int i = 0; i < positions.Length; i++) + { + vector2 += positions[i]; + } + vector2 /= (float)positions.Length; + for (int j = 0; j < positions.Length - 1; j++) + { + vector += Vector3.Cross(positions[j] - vector2, positions[j + 1] - vector2).normalized; + } + return Vector3.Normalize(vector); + } + private void CalculateInitialIKScaling() { // Get distance between feets and thighs @@ -213,7 +346,10 @@ public class DesktopVRIKCalibrator initialFootDistance = footDistance * 0.5f; initialStepThreshold = footDistance * 0.4f; initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; + } + private void ApplyInitialIKScaling() + { // Set initial values vrik.solver.locomotion.footDistance = initialFootDistance; vrik.solver.locomotion.stepThreshold = initialStepThreshold; @@ -221,7 +357,7 @@ public class DesktopVRIKCalibrator } - private void CalibrateHeadIK() + private void SetupDesktopHeadIKTarget() { // Lazy HeadIKTarget calibration if (vrik.solver.spine.headTarget == null) @@ -250,8 +386,7 @@ public class DesktopVRIKCalibrator // Otherwise use DesktopVRIK IKPose & revert afterwards. if (enforceTPose) { - humanPoseHandler.GetHumanPose(ref initialHumanPose); - SetCustomPose(IKPoseMuscles); + SetHumanPose(1f); } else { @@ -259,13 +394,15 @@ public class DesktopVRIKCalibrator } } - private void SetCustomPose(float[] muscleValues) + private void SetHumanPose(float ikPoseWeight = 1f) { humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); - for (int i = 0; i < muscleValues.Length; i++) + for (int i = 0; i < ikSystem.humanPose.muscles.Length; i++) { - IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, muscleValues[i], ref ikSystem.humanPose.muscles); + float weight = ikPoseWeight * IKPoseMuscles[i]; + IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, weight, ref ikSystem.humanPose.muscles); } + ikSystem.humanPose.bodyRotation = Quaternion.identity; humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); } @@ -278,8 +415,6 @@ public class DesktopVRIKCalibrator private void ConfigureVRIKReferences() { - fixTransformsRequired = false; - //might not work over netik FixChestAndSpineReferences(); @@ -458,5 +593,4 @@ public class DesktopVRIKCalibrator 0.8138595f, 0.8110138f }; -} - +} \ No newline at end of file diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs index 5425782..38baece 100644 --- a/DesktopVRIK/Main.cs +++ b/DesktopVRIK/Main.cs @@ -5,6 +5,7 @@ namespace NAK.Melons.DesktopVRIK; public class DesktopVRIKMod : MelonMod { + internal static MelonLogger.Instance Logger; internal const string SettingsCategory = "DesktopVRIK"; internal static MelonPreferences_Category m_categoryDesktopVRIK; internal static MelonPreferences_Entry @@ -13,7 +14,8 @@ public class DesktopVRIKMod : MelonMod m_entryResetIKOnLand, m_entryPlantFeet, m_entryUseVRIKToes, - m_entryFindUnmappedToes; + m_entryFindUnmappedToes, + m_entryExperimentalKneeBend; internal static MelonPreferences_Entry m_entryBodyLeanWeight, m_entryBodyHeadingLimit, @@ -21,12 +23,14 @@ public class DesktopVRIKMod : MelonMod m_entryChestHeadingWeight; public override void OnInitializeMelon() { + Logger = LoggerInstance; m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); m_entryEnabled = m_categoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); //m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving."); m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you."); m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?"); + m_entryExperimentalKneeBend = m_categoryDesktopVRIK.CreateEntry("Experimental Knee Bend", true, description: "Experimental method to calculate knee bend normal. This may break avatars."); m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable."); m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable."); @@ -59,6 +63,7 @@ public class DesktopVRIKMod : MelonMod // Calibration Settings DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value; DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value; + DesktopVRIK.Instance.Calibrator.Setting_ExperimentalKneeBend = m_entryExperimentalKneeBend.Value; } private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); @@ -80,8 +85,8 @@ public class DesktopVRIKMod : MelonMod } catch (Exception e) { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); + Logger.Msg($"Failed while patching {type.Name}!"); + Logger.Error(e); } } } \ No newline at end of file From 05979d9542103bc7eaca06e99b87fefac5f72241 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:26:42 -0600 Subject: [PATCH 6/8] fix footstep when resetting --- DesktopVRIK/DesktopVRIKCalibrator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs index 798e52c..61e0bd6 100644 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -344,7 +344,7 @@ public class DesktopVRIKCalibrator // Get distance between feets and thighs float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); initialFootDistance = footDistance * 0.5f; - initialStepThreshold = footDistance * 0.4f; + initialStepThreshold = footDistance * 0.8f; initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; } From 20222e563e9c2372c6ad140d4d953758c4c3983e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:53:23 -0600 Subject: [PATCH 7/8] touchy --- DesktopVRIK/DesktopVRIK.cs | 23 ++++++++++------------- DesktopVRIK/DesktopVRIKCalibrator.cs | 3 +-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index 093b84f..8a3d15c 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -24,14 +24,14 @@ public class DesktopVRIK : MonoBehaviour Setting_ChestHeadingWeight; // Internal Stuff - private float - ik_SimulatedRootAngle; - private bool - ps_emoteIsPlaying; + bool ps_emoteIsPlaying; + float ik_SimulatedRootAngle; + Transform desktopCameraTransform; static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance); void Start() { + desktopCameraTransform = PlayerSetup.Instance.desktopCamera.transform; Calibrator = new DesktopVRIKCalibrator(); Instance = this; DesktopVRIKMod.UpdateAllSettings(); @@ -72,8 +72,8 @@ public class DesktopVRIK : MonoBehaviour if (changed) { ps_emoteIsPlaying = isEmotePlaying; - Calibrator.vrik.transform.localPosition = Vector3.zero; - Calibrator.vrik.transform.localRotation = Quaternion.identity; + Calibrator.avatarTransform.localPosition = Vector3.zero; + Calibrator.avatarTransform.localRotation = Quaternion.identity; if (Calibrator.lookAtIK != null) { Calibrator.lookAtIK.enabled = !isEmotePlaying; @@ -104,22 +104,19 @@ public class DesktopVRIK : MonoBehaviour weight *= isGrounded ? 1f : 0f; // Reset avatar offset (VRIK will literally make you walk away from root otherwise) - Calibrator.vrik.transform.localPosition = Vector3.zero; - Calibrator.vrik.transform.localRotation = Quaternion.identity; + Calibrator.avatarTransform.localPosition = Vector3.zero; + Calibrator.avatarTransform.localRotation = Quaternion.identity; // Plant feet is nice for Desktop Calibrator.vrik.solver.plantFeet = Setting_PlantFeet; - // This is nice for walk cycles - //Calibrator.vrik.solver.spine.rotateChestByHands = Setting_RotateChestByHands * weight; - // Old VRChat hip movement emulation if (Setting_BodyLeanWeight > 0) { float weightedAngle = Setting_BodyLeanWeight * weight; - float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x; + float angle = desktopCameraTransform.localEulerAngles.x; angle = (angle > 180) ? angle - 360 : angle; - Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, IKSystem.Instance.avatar.transform.right); + Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, Calibrator.avatarTransform.right); Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation); } diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs index 61e0bd6..77a60c7 100644 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -355,8 +355,7 @@ public class DesktopVRIKCalibrator vrik.solver.locomotion.stepThreshold = initialStepThreshold; DesktopVRIK.ScaleStepHeight(vrik.solver.locomotion.stepHeight, initialStepHeight); } - - + private void SetupDesktopHeadIKTarget() { // Lazy HeadIKTarget calibration From 3807482093ea0cfada297510bb1a1f30707d3f56 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:07:56 -0600 Subject: [PATCH 8/8] clean up --- DesktopVRIK/DesktopVRIK.cs | 103 ++++---- DesktopVRIK/DesktopVRIKCalibrator.cs | 340 ++++--------------------- DesktopVRIK/HarmonyPatches.cs | 2 +- DesktopVRIK/Integrations/BTKUIAddon.cs | 21 +- DesktopVRIK/Main.cs | 78 +++--- DesktopVRIK/Properties/AssemblyInfo.cs | 2 +- DesktopVRIK/VRIKUtils.cs | 183 +++++++++++++ DesktopVRIK/format.json | 14 +- 8 files changed, 347 insertions(+), 396 deletions(-) create mode 100644 DesktopVRIK/VRIKUtils.cs diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs index 8a3d15c..60e8371 100644 --- a/DesktopVRIK/DesktopVRIK.cs +++ b/DesktopVRIK/DesktopVRIK.cs @@ -1,5 +1,4 @@ using ABI_RC.Core.Player; -using ABI_RC.Systems.IK; using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.MovementSystem; using RootMotion.FinalIK; @@ -28,6 +27,8 @@ public class DesktopVRIK : MonoBehaviour float ik_SimulatedRootAngle; Transform desktopCameraTransform; static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance); + bool forceSteps; + bool forceStepsNow; void Start() { @@ -44,46 +45,43 @@ public class DesktopVRIK : MonoBehaviour ResetDesktopVRIK(); } - public bool OnSetupIKScaling(float avatarHeight, float scaleDifference) + public bool OnSetupIKScaling(float scaleDifference) { - if (Calibrator.vrik != null) - { - Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * scaleDifference; - Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * scaleDifference; - DesktopVRIK.ScaleStepHeight(Calibrator.vrik.solver.locomotion.stepHeight, Calibrator.initialStepHeight * scaleDifference); - //Calibrator.vrik.solver.Reset(); - ResetDesktopVRIK(); + if (Calibrator.vrik == null) return false; - return true; - } - return false; - } + VRIKUtils.ApplyScaleToVRIK + ( + Calibrator.vrik, + Calibrator.initialFootDistance, + Calibrator.initialStepThreshold, + Calibrator.initialStepHeight, + scaleDifference + ); - public static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag) - { - Keyframe[] keyframes = stepHeightCurve.keys; - keyframes[1].value = mag; - stepHeightCurve.keys = keyframes; + ResetDesktopVRIK(); + return true; } public void OnPlayerSetupUpdate(bool isEmotePlaying) { bool changed = isEmotePlaying != ps_emoteIsPlaying; - if (changed) - { - ps_emoteIsPlaying = isEmotePlaying; - Calibrator.avatarTransform.localPosition = Vector3.zero; - Calibrator.avatarTransform.localRotation = Quaternion.identity; - if (Calibrator.lookAtIK != null) - { - Calibrator.lookAtIK.enabled = !isEmotePlaying; - } - BodySystem.TrackingEnabled = !isEmotePlaying; - Calibrator.vrik.solver?.Reset(); - ResetDesktopVRIK(); - } + if (!changed) return; + + ps_emoteIsPlaying = isEmotePlaying; + + Calibrator.avatarTransform.localPosition = Vector3.zero; + Calibrator.avatarTransform.localRotation = Quaternion.identity; + + if (Calibrator.lookAtIK != null) + Calibrator.lookAtIK.enabled = !isEmotePlaying; + + BodySystem.TrackingEnabled = !isEmotePlaying; + + Calibrator.vrik.solver?.Reset(); + ResetDesktopVRIK(); } + public void ResetDesktopVRIK() { ik_SimulatedRootAngle = transform.eulerAngles.y; @@ -91,33 +89,34 @@ public class DesktopVRIK : MonoBehaviour public void OnPreSolverUpdate() { - if (ps_emoteIsPlaying) - { - return; - } + if (ps_emoteIsPlaying) return; - bool isGrounded = (bool)ms_isGrounded.GetValue(MovementSystem.Instance); + var movementSystem = MovementSystem.Instance; + var vrikSolver = Calibrator.vrik.solver; + var avatarTransform = Calibrator.avatarTransform; - // Calculate everything that affects weight - float weight = Calibrator.vrik.solver.IKPositionWeight; - weight *= (1 - MovementSystem.Instance.movementVector.magnitude); + bool isGrounded = (bool)ms_isGrounded.GetValue(movementSystem); + + // Calculate weight + float weight = vrikSolver.IKPositionWeight; + weight *= 1f - movementSystem.movementVector.magnitude; weight *= isGrounded ? 1f : 0f; - // Reset avatar offset (VRIK will literally make you walk away from root otherwise) - Calibrator.avatarTransform.localPosition = Vector3.zero; - Calibrator.avatarTransform.localRotation = Quaternion.identity; + // Reset avatar offset + avatarTransform.localPosition = Vector3.zero; + avatarTransform.localRotation = Quaternion.identity; - // Plant feet is nice for Desktop - Calibrator.vrik.solver.plantFeet = Setting_PlantFeet; + // Set plant feet + vrikSolver.plantFeet = Setting_PlantFeet; - // Old VRChat hip movement emulation + // Emulate old VRChat hip movement if (Setting_BodyLeanWeight > 0) { float weightedAngle = Setting_BodyLeanWeight * weight; float angle = desktopCameraTransform.localEulerAngles.x; - angle = (angle > 180) ? angle - 360 : angle; - Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, Calibrator.avatarTransform.right); - Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation); + angle = angle > 180 ? angle - 360 : angle; + Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right); + vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation); } // Make root heading follow within a set limit @@ -131,15 +130,15 @@ public class DesktopVRIK : MonoBehaviour currentAngle = Mathf.Sign(currentAngle) * weightedAngleLimit; ik_SimulatedRootAngle = Mathf.MoveTowardsAngle(ik_SimulatedRootAngle, transform.eulerAngles.y, angleMaxDelta - weightedAngleLimit); } - Calibrator.vrik.solver.spine.rootHeadingOffset = currentAngle; + vrikSolver.spine.rootHeadingOffset = currentAngle; if (Setting_PelvisHeadingWeight > 0) { - Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Pelvis, new Vector3(0f, currentAngle * Setting_PelvisHeadingWeight, 0f)); - Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, -currentAngle * Setting_PelvisHeadingWeight, 0f)); + vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Pelvis, new Vector3(0f, currentAngle * Setting_PelvisHeadingWeight, 0f)); + vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, -currentAngle * Setting_PelvisHeadingWeight, 0f)); } if (Setting_ChestHeadingWeight > 0) { - Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, currentAngle * Setting_ChestHeadingWeight, 0f)); + vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, currentAngle * Setting_ChestHeadingWeight, 0f)); } } } diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs index 77a60c7..03fe096 100644 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ b/DesktopVRIK/DesktopVRIKCalibrator.cs @@ -16,8 +16,6 @@ public class DesktopVRIKCalibrator // Settings public bool Setting_UseVRIKToes = true; public bool Setting_FindUnmappedToes = true; - public bool Setting_ExperimentalKneeBend = true; - public bool Setting_DebugCalibrationPose = false; // Avatar Component References public CVRAvatar avatar; @@ -27,26 +25,26 @@ public class DesktopVRIKCalibrator public LookAtIK lookAtIK; // Calibrated Values - public float - initialFootDistance, - initialStepThreshold, + public float + initialFootDistance, + initialStepThreshold, initialStepHeight; // Calibration Internals - bool DebugCalibrationPose; bool fixTransformsRequired; Vector3 leftKneeNormal, rightKneeNormal; HumanPose initialHumanPose; HumanPoseHandler humanPoseHandler; + // Traverse IKSystem ikSystem; PlayerSetup playerSetup; Traverse - _vrikTraverse, - _lookIKTraverse, - _avatarTraverse, - _animatorManagerTraverse, - _poseHandlerTraverse, + _vrikTraverse, + _lookIKTraverse, + _avatarTraverse, + _animatorManagerTraverse, + _poseHandlerTraverse, _avatarRootHeightTraverse; public DesktopVRIKCalibrator() @@ -68,15 +66,10 @@ public class DesktopVRIKCalibrator public void CalibrateDesktopVRIK() { - PreInitialize(); - - // Don't do anything else if just debugging calibration pose - DebugCalibrationPose = !DebugCalibrationPose; - if (Setting_DebugCalibrationPose && DebugCalibrationPose) - { - ForceCalibrationPose(); - return; - } + // Scan avatar for issues/references + ScanAvatarForCalibration(); + // Prepare CVR IKSystem for external VRIK + PrepareIKSystem(); // Add VRIK and configure PrepareAvatarVRIK(); @@ -86,51 +79,42 @@ public class DesktopVRIKCalibrator PostInitialize(); } - public void ForceCalibrationPose(bool toggle = true) - { - animator.enabled = !toggle; - SetHumanPose(0f); - //SetAvatarIKPose(toggle); - } - - private void PreInitialize() - { - // Scan avatar for issues/references - ScanAvatarForCalibration(); - // Prepare CVR IKSystem for external VRIK - PrepareIKSystem(); - } - private void Initialize() { // Calculate bend normals with motorcycle pose SetHumanPose(0f); - CalculateKneeBendNormals(); + VRIKUtils.CalculateKneeBendNormals(vrik, out leftKneeNormal, out rightKneeNormal); + // Calculate initial IK scaling values with IKPose SetAvatarIKPose(true); + VRIKUtils.CalculateInitialIKScaling(vrik, out initialFootDistance, out initialStepThreshold, out initialStepHeight); // Setup HeadIK target & calculate initial footstep values SetupDesktopHeadIKTarget(); - CalculateInitialIKScaling(); // Initiate VRIK manually - ForceInitiateVRIKSolver(); + VRIKUtils.InitiateVRIKSolver(vrik); + // Return avatar to original pose SetAvatarIKPose(false); } private void PostInitialize() { - ApplyKneeBendNormals(); - ApplyInitialIKScaling(); + VRIKUtils.ApplyScaleToVRIK + ( + vrik, + initialFootDistance, + initialStepThreshold, + initialStepHeight, + 1f + ); + VRIKUtils.ApplyKneeBendNormals(vrik, leftKneeNormal, rightKneeNormal); vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); } - + private void ScanAvatarForCalibration() { - // Reset some stuff to default - fixTransformsRequired = false; - // Find required avatar components avatar = playerSetup._avatar.GetComponent(); animator = avatar.GetComponent(); @@ -138,22 +122,15 @@ public class DesktopVRIKCalibrator lookAtIK = _lookIKTraverse.GetValue(); // Apply some fixes for weird setups - if (!animator.enabled) - { - fixTransformsRequired = true; - DesktopVRIKMod.Logger.Error("Avatar has Animator disabled by default!"); - } + fixTransformsRequired = !animator.enabled; - // Center avatar local offsets + // Center avatar local position avatarTransform.localPosition = Vector3.zero; - //avatarTransform.localRotation = Quaternion.identity; - // Store original human pose - if (humanPoseHandler != null) - { - humanPoseHandler.Dispose(); - } + // Create a new human pose handler and dispose the old one + humanPoseHandler?.Dispose(); humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform); + // Store original human pose humanPoseHandler.GetHumanPose(ref initialHumanPose); } @@ -168,21 +145,13 @@ public class DesktopVRIKCalibrator // Set the animator for the IK system ikSystem.animator = animator; - if (ikSystem.animator != null) - { - animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController); - } + animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController); // Set the avatar height float - float avatarHeight = ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y; - _avatarRootHeightTraverse.SetValue(avatarHeight); + _avatarRootHeightTraverse.SetValue(ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y); // Create a new human pose handler and dispose the old one - if (ikHumanPoseHandler != null) - { - ikHumanPoseHandler.Dispose(); - _poseHandlerTraverse.SetValue(null); - } + ikHumanPoseHandler?.Dispose(); ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); _poseHandlerTraverse.SetValue(ikHumanPoseHandler); @@ -206,156 +175,48 @@ public class DesktopVRIKCalibrator private void PrepareAvatarVRIK() { - //add and configure VRIK + // Add and configure VRIK vrik = avatar.gameObject.AddComponentIfMissing(); vrik.AutoDetectReferences(); - ConfigureVRIKReferences(); - _vrikTraverse.SetValue(vrik); - //in testing, not really needed - //only required if Setting_FindUnmappedToes - //and non-human mapped toes are found - vrik.fixTransforms = fixTransformsRequired; + VRIKUtils.ConfigureVRIKReferences(vrik, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes); - //default solver settings + // Fix animator issue or non-human mapped toes + vrik.fixTransforms = fixTransformsRequired || foundUnmappedToes; + + // Default solver settings vrik.solver.locomotion.weight = 0f; vrik.solver.locomotion.angleThreshold = 30f; - vrik.solver.locomotion.maxLegStretch = 0.75f; + vrik.solver.locomotion.maxLegStretch = 1f; vrik.solver.spine.minHeadHeight = 0f; vrik.solver.IKPositionWeight = 1f; - //disable to not bleed into anims vrik.solver.spine.chestClampWeight = 0f; vrik.solver.spine.maintainPelvisPosition = 0f; - //for body leaning - vrik.solver.spine.neckStiffness = 0.0001f; //cannot be 0 + + // Body leaning settings + vrik.solver.spine.neckStiffness = 0.0001f; vrik.solver.spine.bodyPosStiffness = 1f; vrik.solver.spine.bodyRotStiffness = 0.2f; - //disable so avatar doesnt try and walk away + + // Disable locomotion vrik.solver.locomotion.velocityFactor = 0f; vrik.solver.locomotion.maxVelocity = 0f; - //fixes nameplate spazzing on remote & magicacloth vrik.solver.locomotion.rootSpeed = 1000f; - //disable so PAM & BID dont make body shake + + // Disable chest rotation by hands vrik.solver.spine.rotateChestByHands = 0f; - //enable to prioritize LookAtIK + + // Prioritize LookAtIK vrik.solver.spine.headClampWeight = 0.2f; - //disable to not go on tippytoes + + // Disable going on tippytoes vrik.solver.spine.positionWeight = 0f; vrik.solver.spine.rotationWeight = 1f; - //vrik.solver.spine.maintainPelvisPosition = 1f; - //vrik.solver.locomotion.weight = 0f; - //vrik.solver.spine.positionWeight = 0f; - //vrik.solver.spine.pelvisPositionWeight = 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; - //vrik.solver.IKPositionWeight = 0f; - - //THESE ARE CONFIGURABLE IN GAME IK SETTINGS - //vrik.solver.leftLeg.target = null; - //vrik.solver.leftLeg.bendGoal = null; - //vrik.solver.leftLeg.positionWeight = 0f; - //vrik.solver.leftLeg.bendGoalWeight = 0f; - //vrik.solver.rightLeg.target = null; - //vrik.solver.rightLeg.bendGoal = null; - //vrik.solver.rightLeg.positionWeight = 0f; - //vrik.solver.rightLeg.bendGoalWeight = 0f; - //vrik.solver.spine.pelvisTarget = null; - //vrik.solver.spine.chestGoal = null; - //vrik.solver.spine.positionWeight = 0f; - //vrik.solver.spine.rotationWeight = 0f; - //vrik.solver.spine.pelvisPositionWeight = 0f; - //vrik.solver.spine.pelvisRotationWeight = 0f; - //vrik.solver.spine.chestGoalWeight = 0f; + // Tell IKSystem about new VRIK + _vrikTraverse.SetValue(vrik); } - private void CalculateKneeBendNormals() - { - // Get assumed left knee normal - Vector3[] leftVectors = new Vector3[] - { - vrik.references.leftThigh?.position ?? Vector3.zero, - vrik.references.leftCalf?.position ?? Vector3.zero, - vrik.references.leftFoot?.position ?? Vector3.zero, - }; - leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors); - - // Get assumed right knee normal - Vector3[] rightVectors = new Vector3[] - { - vrik.references.rightThigh?.position ?? Vector3.zero, - vrik.references.rightCalf?.position ?? Vector3.zero, - vrik.references.rightFoot?.position ?? Vector3.zero, - }; - rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors); - } - - private void ApplyKneeBendNormals() - { - if (!Setting_ExperimentalKneeBend) - { - //enable so knees on fucked models work better - vrik.solver.leftLeg.useAnimatedBendNormal = true; - vrik.solver.rightLeg.useAnimatedBendNormal = true; - return; - } - - vrik.solver.leftLeg.bendToTargetWeight = 0f; - vrik.solver.rightLeg.bendToTargetWeight = 0f; - - Traverse leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis"); - Traverse rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis"); - - // Calculate knee normal without root rotation but with pelvis rotation - Quaternion pelvisLocalRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation); - Vector3 leftLegBendNormalRelToPelvis = pelvisLocalRotationInverse * leftKneeNormal; - Vector3 rightLegBendNormalRelToPelvis = pelvisLocalRotationInverse * rightKneeNormal; - //Quaternion rootRotation = vrik.references.root.rotation; - //Quaternion pelvisRotationRelativeToRoot = Quaternion.Inverse(rootRotation) * vrik.references.pelvis.rotation; - //Quaternion pelvisRotationInverse = Quaternion.Inverse(pelvisRotationRelativeToRoot); - leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLegBendNormalRelToPelvis); - rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLegBendNormalRelToPelvis); - } - - private Vector3 GetNormalFromArray(Vector3[] positions) - { - Vector3 vector = Vector3.zero; - Vector3 vector2 = Vector3.zero; - for (int i = 0; i < positions.Length; i++) - { - vector2 += positions[i]; - } - vector2 /= (float)positions.Length; - for (int j = 0; j < positions.Length - 1; j++) - { - vector += Vector3.Cross(positions[j] - vector2, positions[j + 1] - vector2).normalized; - } - return Vector3.Normalize(vector); - } - - private void CalculateInitialIKScaling() - { - // Get distance between feets and thighs - float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); - initialFootDistance = footDistance * 0.5f; - initialStepThreshold = footDistance * 0.8f; - initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; - } - - private void ApplyInitialIKScaling() - { - // Set initial values - vrik.solver.locomotion.footDistance = initialFootDistance; - vrik.solver.locomotion.stepThreshold = initialStepThreshold; - DesktopVRIK.ScaleStepHeight(vrik.solver.locomotion.stepHeight, initialStepHeight); - } - private void SetupDesktopHeadIKTarget() { // Lazy HeadIKTarget calibration @@ -405,95 +266,6 @@ public class DesktopVRIKCalibrator humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); } - private void ForceInitiateVRIKSolver() - { - //force immediate calibration before animator decides to fuck us - vrik.solver.SetToReferences(vrik.references); - vrik.solver.Initiate(vrik.transform); - } - - private void ConfigureVRIKReferences() - { - //might not work over netik - FixChestAndSpineReferences(); - - if (!Setting_UseVRIKToes) - { - vrik.references.leftToes = null; - vrik.references.rightToes = null; - } - else if (Setting_FindUnmappedToes) - { - //doesnt work with netik, but its toes... - FindAndSetUnmappedToes(); - } - - //bullshit fix to not cause death - FixFingerBonesError(); - } - - private void FixChestAndSpineReferences() - { - Transform leftShoulderBone = vrik.references.leftShoulder; - Transform rightShoulderBone = vrik.references.rightShoulder; - Transform assumedChest = leftShoulderBone?.parent; - - if (assumedChest != null && rightShoulderBone.parent == assumedChest && - vrik.references.chest != assumedChest) - { - vrik.references.chest = assumedChest; - vrik.references.spine = assumedChest.parent; - } - } - - private void FindAndSetUnmappedToes() - { - Transform leftToes = vrik.references.leftToes; - Transform rightToes = vrik.references.rightToes; - - if (leftToes == null && rightToes == null) - { - leftToes = FindUnmappedToe(vrik.references.leftFoot); - rightToes = FindUnmappedToe(vrik.references.rightFoot); - - if (leftToes != null && rightToes != null) - { - vrik.references.leftToes = leftToes; - vrik.references.rightToes = rightToes; - fixTransformsRequired = true; - } - } - } - - private Transform FindUnmappedToe(Transform foot) - { - foreach (Transform bone in foot) - { - if (bone.name.ToLowerInvariant().Contains("toe") || - bone.name.ToLowerInvariant().EndsWith("_end")) - { - return bone; - } - } - - return null; - } - - private void FixFingerBonesError() - { - FixFingerBones(vrik.references.leftHand, vrik.solver.leftArm); - FixFingerBones(vrik.references.rightHand, vrik.solver.rightArm); - } - - private void FixFingerBones(Transform hand, IKSolverVR.Arm armSolver) - { - if (hand.childCount == 0) - { - armSolver.wristToPalmAxis = Vector3.up; - armSolver.palmToThumbAxis = hand == vrik.references.leftHand ? -Vector3.forward : Vector3.forward; - } - } - private static readonly float[] IKPoseMuscles = new float[] { 0.00133321f, diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs index 9b7d4af..5eb1c49 100644 --- a/DesktopVRIK/HarmonyPatches.cs +++ b/DesktopVRIK/HarmonyPatches.cs @@ -44,7 +44,7 @@ class PlayerSetupPatches [HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")] private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference) { - return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(height, 1f + ___scaleDifference.y); + return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y); } } diff --git a/DesktopVRIK/Integrations/BTKUIAddon.cs b/DesktopVRIK/Integrations/BTKUIAddon.cs index 7fd4f52..33a512c 100644 --- a/DesktopVRIK/Integrations/BTKUIAddon.cs +++ b/DesktopVRIK/Integrations/BTKUIAddon.cs @@ -14,31 +14,30 @@ public static class BTKUIAddon Page miscPage = QuickMenuAPI.MiscTabPage; Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory); - AddMelonToggle(ref miscCategory, DesktopVRIKMod.m_entryEnabled); + AddMelonToggle(ref miscCategory, DesktopVRIKMod.EntryEnabled); //Add my own page to not clog up Misc Menu Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK"); desktopVRIKPage.MenuTitle = "DesktopVRIK Settings"; - desktopVRIKPage.MenuSubtitle = "Simplified settings for VRIK on Desktop."; - Category desktopVRIKCategory = desktopVRIKPage.AddCategory("DesktopVRIK"); + Category desktopVRIKCategory = desktopVRIKPage.AddCategory(DesktopVRIKMod.SettingsCategory); // General Settings - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnabled); - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryPlantFeet); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryEnabled); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryPlantFeet); // Calibration Settings - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryUseVRIKToes); - AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryFindUnmappedToes); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryUseVRIKToes); + AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryFindUnmappedToes); // Body Leaning Weight - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyLeanWeight, 0, 1f, 1); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyLeanWeight, 0, 1f, 1); // Max Root Heading Limit & Weights - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyHeadingLimit, 0, 90f, 0); - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryPelvisHeadingWeight, 0, 1f, 1); - AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryChestHeadingWeight, 0, 1f, 1); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyHeadingLimit, 0, 90f, 0); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryPelvisHeadingWeight, 0, 1f, 1); + AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryChestHeadingWeight, 0, 1f, 1); } private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry entry) { diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs index 38baece..7adb920 100644 --- a/DesktopVRIK/Main.cs +++ b/DesktopVRIK/Main.cs @@ -6,41 +6,38 @@ namespace NAK.Melons.DesktopVRIK; public class DesktopVRIKMod : MelonMod { internal static MelonLogger.Instance Logger; - internal const string SettingsCategory = "DesktopVRIK"; - internal static MelonPreferences_Category m_categoryDesktopVRIK; - internal static MelonPreferences_Entry - m_entryEnabled, - m_entryEnforceViewPosition, - m_entryResetIKOnLand, - m_entryPlantFeet, - m_entryUseVRIKToes, - m_entryFindUnmappedToes, - m_entryExperimentalKneeBend; - internal static MelonPreferences_Entry - m_entryBodyLeanWeight, - m_entryBodyHeadingLimit, - m_entryPelvisHeadingWeight, - m_entryChestHeadingWeight; + public const string SettingsCategory = "DesktopVRIK"; + public static readonly MelonPreferences_Category CategoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); + + public static readonly MelonPreferences_Entry EntryEnabled = + CategoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); + + public static readonly MelonPreferences_Entry EntryPlantFeet = + CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement."); + + public static readonly MelonPreferences_Entry EntryUseVRIKToes = + CategoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar."); + + public static readonly MelonPreferences_Entry EntryFindUnmappedToes = + CategoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Determines if DesktopVRIK should look for unmapped toe bones if the humanoid rig does not have any."); + + public static readonly MelonPreferences_Entry EntryBodyLeanWeight = + CategoryDesktopVRIK.CreateEntry("Body Lean Weight", 0.5f, description: "Adds rotational influence to the body solver when looking up/down. Set to 0 to disable."); + + public static readonly MelonPreferences_Entry EntryBodyHeadingLimit = + CategoryDesktopVRIK.CreateEntry("Body Heading Limit", 20f, description: "Specifies the maximum angle the lower body can have relative to the head when rotating. Set to 0 to disable."); + + public static readonly MelonPreferences_Entry EntryPelvisHeadingWeight = + CategoryDesktopVRIK.CreateEntry("Pelvis Heading Weight", 0.25f, description: "Determines how much the pelvis will face the Body Heading Limit. Set to 0 to align with head."); + + public static readonly MelonPreferences_Entry EntryChestHeadingWeight = + CategoryDesktopVRIK.CreateEntry("Chest Heading Weight", 0.75f, description: "Determines how much the chest will face the Body Heading Limit. Set to 0 to align with head."); + public override void OnInitializeMelon() { Logger = LoggerInstance; - m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); - m_entryEnabled = m_categoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); - //m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); - m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving."); - m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you."); - m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?"); - m_entryExperimentalKneeBend = m_categoryDesktopVRIK.CreateEntry("Experimental Knee Bend", true, description: "Experimental method to calculate knee bend normal. This may break avatars."); - m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable."); - m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable."); - m_entryPelvisHeadingWeight = m_categoryDesktopVRIK.CreateEntry("Pelvis Heading Weight", 0.25f, description: "How much the pelvis will face the heading limit. Set to 0 to align with head."); - m_entryChestHeadingWeight = m_categoryDesktopVRIK.CreateEntry("Chest Heading Weight", 0.75f, description: "How much the chest will face the heading limit. Set to 0 to align with head."); - - foreach (var setting in m_categoryDesktopVRIK.Entries) - { - setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings); - } + CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings)); ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); @@ -52,18 +49,17 @@ public class DesktopVRIKMod : MelonMod { if (!DesktopVRIK.Instance) return; // DesktopVRIK Settings - DesktopVRIK.Instance.Setting_Enabled = m_entryEnabled.Value; - DesktopVRIK.Instance.Setting_PlantFeet = m_entryPlantFeet.Value; + DesktopVRIK.Instance.Setting_Enabled = EntryEnabled.Value; + DesktopVRIK.Instance.Setting_PlantFeet = EntryPlantFeet.Value; - DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); - DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(m_entryBodyHeadingLimit.Value, 0f, 90f); - DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(m_entryPelvisHeadingWeight.Value)); - DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(m_entryChestHeadingWeight.Value)); + DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value); + DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f); + DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value)); + DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value)); // Calibration Settings - DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value; - DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value; - DesktopVRIK.Instance.Calibrator.Setting_ExperimentalKneeBend = m_entryExperimentalKneeBend.Value; + DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = EntryUseVRIKToes.Value; + DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value; } private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); @@ -72,7 +68,7 @@ public class DesktopVRIKMod : MelonMod //BTKUILib Misc Tab if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib")) { - MelonLogger.Msg("Initializing BTKUILib support."); + Logger.Msg("Initializing BTKUILib support."); BTKUIAddon.Init(); } } diff --git a/DesktopVRIK/Properties/AssemblyInfo.cs b/DesktopVRIK/Properties/AssemblyInfo.cs index 879ab4b..ace74a2 100644 --- a/DesktopVRIK/Properties/AssemblyInfo.cs +++ b/DesktopVRIK/Properties/AssemblyInfo.cs @@ -26,6 +26,6 @@ using System.Reflection; namespace NAK.Melons.DesktopVRIK.Properties; internal static class AssemblyInfoParams { - public const string Version = "4.0.0"; + public const string Version = "4.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/DesktopVRIK/VRIKUtils.cs b/DesktopVRIK/VRIKUtils.cs new file mode 100644 index 0000000..fcdfaa5 --- /dev/null +++ b/DesktopVRIK/VRIKUtils.cs @@ -0,0 +1,183 @@ +using HarmonyLib; +using RootMotion.FinalIK; +using UnityEngine; + +namespace NAK.Melons.DesktopVRIK; + +public static class VRIKUtils +{ + public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes, bool findUnmappedToes, out bool foundUnmappedToes) + { + foundUnmappedToes = false; + + //might not work over netik + FixChestAndSpineReferences(vrik); + + if (!useVRIKToes) + { + vrik.references.leftToes = null; + vrik.references.rightToes = null; + } + else if (findUnmappedToes) + { + //doesnt work with netik, but its toes... + FindAndSetUnmappedToes(vrik, out foundUnmappedToes); + } + + //bullshit fix to not cause death + FixFingerBonesError(vrik); + } + + private static void FixChestAndSpineReferences(VRIK vrik) + { + Transform leftShoulderBone = vrik.references.leftShoulder; + Transform rightShoulderBone = vrik.references.rightShoulder; + Transform assumedChest = leftShoulderBone?.parent; + + if (assumedChest != null && rightShoulderBone.parent == assumedChest && + vrik.references.chest != assumedChest) + { + vrik.references.chest = assumedChest; + vrik.references.spine = assumedChest.parent; + } + } + + private static void FindAndSetUnmappedToes(VRIK vrik, out bool foundUnmappedToes) + { + foundUnmappedToes = false; + + Transform leftToes = vrik.references.leftToes; + Transform rightToes = vrik.references.rightToes; + + if (leftToes == null && rightToes == null) + { + leftToes = FindUnmappedToe(vrik.references.leftFoot); + rightToes = FindUnmappedToe(vrik.references.rightFoot); + + if (leftToes != null && rightToes != null) + { + vrik.references.leftToes = leftToes; + vrik.references.rightToes = rightToes; + foundUnmappedToes = true; + } + } + } + + private static Transform FindUnmappedToe(Transform foot) + { + foreach (Transform bone in foot) + { + if (bone.name.ToLowerInvariant().Contains("toe") || + bone.name.ToLowerInvariant().EndsWith("_end")) + { + return bone; + } + } + + return null; + } + + private static void FixFingerBonesError(VRIK vrik) + { + FixFingerBones(vrik, vrik.references.leftHand, vrik.solver.leftArm); + FixFingerBones(vrik, vrik.references.rightHand, vrik.solver.rightArm); + } + + private static void FixFingerBones(VRIK vrik, Transform hand, IKSolverVR.Arm armSolver) + { + if (hand.childCount == 0) + { + armSolver.wristToPalmAxis = Vector3.up; + armSolver.palmToThumbAxis = hand == vrik.references.leftHand ? -Vector3.forward : Vector3.forward; + } + } + + public static void CalculateKneeBendNormals(VRIK vrik, out Vector3 leftKneeNormal, out Vector3 rightKneeNormal) + { + // Helper function to get position or default to Vector3.zero + Vector3 GetPositionOrDefault(Transform transform) => transform?.position ?? Vector3.zero; + + // Get assumed left knee normal + Vector3[] leftVectors = { + GetPositionOrDefault(vrik.references.leftThigh), + GetPositionOrDefault(vrik.references.leftCalf), + GetPositionOrDefault(vrik.references.leftFoot) + }; + leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors); + + // Get assumed right knee normal + Vector3[] rightVectors = { + GetPositionOrDefault(vrik.references.rightThigh), + GetPositionOrDefault(vrik.references.rightCalf), + GetPositionOrDefault(vrik.references.rightFoot) + }; + rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors); + } + + public static void ApplyKneeBendNormals(VRIK vrik, Vector3 leftKneeNormal, Vector3 rightKneeNormal) + { + // 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget + // modifying pelvis normal weight is better math + vrik.solver.leftLeg.bendToTargetWeight = 0f; + vrik.solver.rightLeg.bendToTargetWeight = 0f; + + var leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis"); + var rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis"); + + var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation); + var leftLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal; + var rightLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal; + + leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLeg_bendNormalRelToPelvis); + rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLeg_bendNormalRelToPelvis); + } + + private static Vector3 GetNormalFromArray(Vector3[] positions) + { + Vector3 centroid = Vector3.zero; + for (int i = 0; i < positions.Length; i++) + { + centroid += positions[i]; + } + centroid /= positions.Length; + + Vector3 normal = Vector3.zero; + for (int i = 0; i < positions.Length - 2; i++) + { + Vector3 side1 = positions[i] - centroid; + Vector3 side2 = positions[i + 1] - centroid; + normal += Vector3.Cross(side1, side2); + } + return normal.normalized; + } + + public static void CalculateInitialIKScaling(VRIK vrik, out float initialFootDistance, out float initialStepThreshold, out float initialStepHeight) + { + // Get distance between feet and thighs + float scaleModifier = Mathf.Max(1f, vrik.references.pelvis.lossyScale.x); + float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); + initialFootDistance = footDistance * 0.5f; + initialStepThreshold = footDistance * scaleModifier; + initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; + } + + public static void ApplyScaleToVRIK(VRIK vrik, float footDistance, float stepThreshold, float stepHeight, float modifier) + { + vrik.solver.locomotion.footDistance = footDistance * modifier; + vrik.solver.locomotion.stepThreshold = stepThreshold * modifier; + ScaleStepHeight(vrik.solver.locomotion.stepHeight, stepHeight * modifier); + } + + private static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag) + { + Keyframe[] keyframes = stepHeightCurve.keys; + keyframes[1].value = mag; + stepHeightCurve.keys = keyframes; + } + + public static void InitiateVRIKSolver(VRIK vrik) + { + vrik.solver.SetToReferences(vrik.references); + vrik.solver.Initiate(vrik.transform); + } +} \ No newline at end of file diff --git a/DesktopVRIK/format.json b/DesktopVRIK/format.json index d1d47d6..6337a93 100644 --- a/DesktopVRIK/format.json +++ b/DesktopVRIK/format.json @@ -1,23 +1,25 @@ { "_id": 117, "name": "DesktopVRIK", - "modversion": "2.0.5", + "modversion": "4.0.1", "gameversion": "2022r170", "loaderversion": "0.5.7", "modtype": "Mod", "author": "NotAKidoS", - "description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nAdditional option to emulate VRChat-like hip movement if you like being fish.", + "description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nIt is highly recommended to use AvatarMotionTweaker alongside this mod.", "searchtags": [ "desktop", "vrik", "ik", - "feet" + "feet", + "fish" ], "requirements": [ - "BTKUILib" + "BTKUILib", + "AvatarMotionTweaker" ], - "downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/v2.0.5/DesktopVRIK.dll", + "downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/v4.0.1/DesktopVRIK.dll", "sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRIK/", - "changelog": "- Tweaks to VRIK settings so BetterInteractDesktop & PickupArmMovement better behave alongside VRIK.\n- Tweaks to BTKUI integration.", + "changelog": "- Implemented fixes for terrible armatures that had broken knee bending, initial hip rotation, and initial step calculations.\n- Added pelvis & chest weight sliders for heading angle limit option.\n- Implemented bandaid fixes for disabled animator and avatars without fingers.\n- Added options to use or remove toes from VRIK and find unmapped non-humanoid toe bones.\n- Contact me if you find an avatar that is still broken.", "embedcolor": "9b59b6" } \ No newline at end of file