diff --git a/DesktopVRIK/DesktopVRIK.cs b/DesktopVRIK/DesktopVRIK.cs deleted file mode 100644 index cd30354..0000000 --- a/DesktopVRIK/DesktopVRIK.cs +++ /dev/null @@ -1,198 +0,0 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Player; -using ABI_RC.Systems.IK.SubSystems; -using ABI_RC.Systems.MovementSystem; -using HarmonyLib; -using RootMotion.FinalIK; -using System.Reflection; -using UnityEngine; - - -namespace NAK.Melons.DesktopVRIK; - -public class DesktopVRIK : MonoBehaviour -{ - public static DesktopVRIK Instance; - public DesktopVRIKCalibrator Calibrator; - - // DesktopVRIK Settings - public bool - Setting_Enabled = true, - Setting_PlantFeet = true; - - public float - Setting_BodyLeanWeight, - Setting_BodyHeadingLimit, - Setting_PelvisHeadingWeight, - Setting_ChestHeadingWeight; - - // DesktopVRIK References - bool _isEmotePlaying; - float _simulatedRootAngle; - Transform _avatarTransform; - Transform _cameraTransform; - - // IK Stuff - VRIK _vrik; - LookAtIK _lookAtIK; - IKSolverVR _ikSolver; - - // Movement System Stuff - MovementSystem movementSystem; - Traverse _isGroundedTraverse; - - // Movement Parent Stuff - private Vector3 _previousPosition; - private Quaternion _previousRotation; - private Traverse _currentParentTraverse; - static readonly FieldInfo _referencePointField = typeof(CVRMovementParent).GetField("_referencePoint", BindingFlags.NonPublic | BindingFlags.Instance); - - void Start() - { - Instance = this; - Calibrator = new DesktopVRIKCalibrator(); - DesktopVRIKMod.UpdateAllSettings(); - - movementSystem = GetComponent(); - _cameraTransform = PlayerSetup.Instance.desktopCamera.transform; - _isGroundedTraverse = Traverse.Create(movementSystem).Field("_isGrounded"); - _currentParentTraverse = Traverse.Create(movementSystem).Field("_currentParent"); - } - - public void OnSetupAvatarDesktop() - { - if (!Setting_Enabled) return; - Calibrator.CalibrateDesktopVRIK(); - - _vrik = Calibrator.vrik; - _lookAtIK = Calibrator.lookAtIK; - _ikSolver = Calibrator.vrik.solver; - _avatarTransform = Calibrator.avatarTransform; - - _simulatedRootAngle = transform.eulerAngles.y; - } - - public bool OnSetupIKScaling(float scaleDifference) - { - if (_vrik == null) return false; - - VRIKUtils.ApplyScaleToVRIK - ( - _vrik, - Calibrator.initialFootDistance, - Calibrator.initialStepThreshold, - Calibrator.initialStepHeight, - scaleDifference - ); - - _ikSolver?.Reset(); - ResetDesktopVRIK(); - return true; - } - - public void OnPlayerSetupUpdate(bool isEmotePlaying) - { - bool changed = isEmotePlaying != _isEmotePlaying; - if (!changed) return; - - _isEmotePlaying = isEmotePlaying; - - _avatarTransform.localPosition = Vector3.zero; - _avatarTransform.localRotation = Quaternion.identity; - - if (_lookAtIK != null) - _lookAtIK.enabled = !isEmotePlaying; - - BodySystem.TrackingEnabled = !isEmotePlaying; - - _ikSolver?.Reset(); - ResetDesktopVRIK(); - } - - public bool OnPlayerSetupResetIk() - { - if (_vrik == null) return false; - - CVRMovementParent currentParent = _currentParentTraverse.GetValue(); - if (currentParent == null) return false; - - Transform referencePoint = (Transform)_referencePointField.GetValue(currentParent); - if (referencePoint == null) return false; - - var currentPosition = referencePoint.position; - var currentRotation = currentParent.transform.rotation; - - // Keep only the Y-axis rotation - currentRotation = Quaternion.Euler(0f, currentRotation.eulerAngles.y, 0f); - - var deltaPosition = currentPosition - _previousPosition; - var deltaRotation = Quaternion.Inverse(_previousRotation) * currentRotation; - - var platformPivot = transform.position; - _ikSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot); - - _previousPosition = currentPosition; - _previousRotation = currentRotation; - - ResetDesktopVRIK(); - return true; - } - - - public void ResetDesktopVRIK() - { - _simulatedRootAngle = transform.eulerAngles.y; - } - - public void OnPreSolverUpdate() - { - if (_isEmotePlaying) return; - - bool isGrounded = _isGroundedTraverse.GetValue(); - - // Calculate weight - float weight = _ikSolver.IKPositionWeight; - weight *= 1f - movementSystem.movementVector.magnitude; - weight *= isGrounded ? 1f : 0f; - - // Reset avatar offset - _avatarTransform.localPosition = Vector3.zero; - _avatarTransform.localRotation = Quaternion.identity; - - // Set plant feet - _ikSolver.plantFeet = Setting_PlantFeet; - - // Emulate old VRChat hip movement - if (Setting_BodyLeanWeight > 0) - { - float weightedAngle = Setting_BodyLeanWeight * weight; - float angle = _cameraTransform.localEulerAngles.x; - angle = angle > 180 ? angle - 360 : angle; - Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, _avatarTransform.right); - _ikSolver.spine.headRotationOffset *= rotation; - } - - // Make root heading follow within a set limit - if (Setting_BodyHeadingLimit > 0) - { - float weightedAngleLimit = Setting_BodyHeadingLimit * weight; - float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _simulatedRootAngle); - float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot); - if (absDeltaAngleRoot > weightedAngleLimit) - { - deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit; - _simulatedRootAngle = Mathf.MoveTowardsAngle(_simulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit); - } - _ikSolver.spine.rootHeadingOffset = deltaAngleRoot; - if (Setting_PelvisHeadingWeight > 0) - { - _ikSolver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_PelvisHeadingWeight, 0f); - _ikSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * Setting_PelvisHeadingWeight, 0f); - } - if (Setting_ChestHeadingWeight > 0) - { - _ikSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_ChestHeadingWeight, 0f); - } - } - } -} \ No newline at end of file diff --git a/DesktopVRIK/DesktopVRIK.csproj b/DesktopVRIK/DesktopVRIK.csproj index 592135f..60fc7d3 100644 --- a/DesktopVRIK/DesktopVRIK.csproj +++ b/DesktopVRIK/DesktopVRIK.csproj @@ -6,41 +6,42 @@ enable latest false + True - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\0Harmony.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\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 + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Cohtml.Runtime.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\MelonLoader.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AnimationModule.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AssetBundleModule.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.CoreModule.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.InputLegacyModule.dll - C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll + C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.PhysicsModule.dll diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs deleted file mode 100644 index 03fe096..0000000 --- a/DesktopVRIK/DesktopVRIKCalibrator.cs +++ /dev/null @@ -1,367 +0,0 @@ -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 -{ - // Settings - public bool Setting_UseVRIKToes = true; - public bool Setting_FindUnmappedToes = true; - - // 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 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. - 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"); - } - - public void CalibrateDesktopVRIK() - { - // Scan avatar for issues/references - ScanAvatarForCalibration(); - // Prepare CVR IKSystem for external VRIK - PrepareIKSystem(); - - // Add VRIK and configure - PrepareAvatarVRIK(); - - Initialize(); - - PostInitialize(); - } - - private void Initialize() - { - // Calculate bend normals with motorcycle pose - SetHumanPose(0f); - 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(); - - // Initiate VRIK manually - VRIKUtils.InitiateVRIKSolver(vrik); - - // Return avatar to original pose - SetAvatarIKPose(false); - } - - private void PostInitialize() - { - VRIKUtils.ApplyScaleToVRIK - ( - vrik, - initialFootDistance, - initialStepThreshold, - initialStepHeight, - 1f - ); - VRIKUtils.ApplyKneeBendNormals(vrik, leftKneeNormal, rightKneeNormal); - vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); - } - - private void ScanAvatarForCalibration() - { - // Find required avatar components - avatar = playerSetup._avatar.GetComponent(); - animator = avatar.GetComponent(); - avatarTransform = avatar.transform; - lookAtIK = _lookIKTraverse.GetValue(); - - // Apply some fixes for weird setups - fixTransformsRequired = !animator.enabled; - - // Center avatar local position - avatarTransform.localPosition = Vector3.zero; - - // 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); - } - - private void PrepareIKSystem() - { - // Get the animator manager and human pose handler - var animatorManager = _animatorManagerTraverse.GetValue(); - var ikHumanPoseHandler = _poseHandlerTraverse.GetValue(); - - // Store the avatar component - _avatarTraverse.SetValue(avatar); - - // Set the animator for the IK system - ikSystem.animator = animator; - animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController); - - // Set the avatar height float - _avatarRootHeightTraverse.SetValue(ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y); - - // Create a new human pose handler and dispose the old one - ikHumanPoseHandler?.Dispose(); - ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); - _poseHandlerTraverse.SetValue(ikHumanPoseHandler); - - // 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(); - - VRIKUtils.ConfigureVRIKReferences(vrik, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes); - - // 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 = 1f; - vrik.solver.spine.minHeadHeight = 0f; - vrik.solver.IKPositionWeight = 1f; - vrik.solver.spine.chestClampWeight = 0f; - vrik.solver.spine.maintainPelvisPosition = 0f; - - // Body leaning settings - vrik.solver.spine.neckStiffness = 0.0001f; - vrik.solver.spine.bodyPosStiffness = 1f; - vrik.solver.spine.bodyRotStiffness = 0.2f; - - // Disable locomotion - vrik.solver.locomotion.velocityFactor = 0f; - vrik.solver.locomotion.maxVelocity = 0f; - vrik.solver.locomotion.rootSpeed = 1000f; - - // Disable chest rotation by hands - vrik.solver.spine.rotateChestByHands = 0f; - - // Prioritize LookAtIK - vrik.solver.spine.headClampWeight = 0.2f; - - // Disable going on tippytoes - vrik.solver.spine.positionWeight = 0f; - vrik.solver.spine.rotationWeight = 1f; - - // Tell IKSystem about new VRIK - _vrikTraverse.SetValue(vrik); - } - - private void SetupDesktopHeadIKTarget() - { - // 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) - { - SetHumanPose(1f); - } - else - { - humanPoseHandler.SetHumanPose(ref initialHumanPose); - } - } - - private void SetHumanPose(float ikPoseWeight = 1f) - { - humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); - for (int i = 0; i < ikSystem.humanPose.muscles.Length; i++) - { - 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); - } - - 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 - }; -} \ No newline at end of file diff --git a/DesktopVRIK/DesktopVRIKSystem.cs b/DesktopVRIK/DesktopVRIKSystem.cs new file mode 100644 index 0000000..52f607e --- /dev/null +++ b/DesktopVRIK/DesktopVRIKSystem.cs @@ -0,0 +1,646 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Base; +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 UnityEngine; +using UnityEngine.Events; + +namespace NAK.Melons.DesktopVRIK; + +internal class DesktopVRIKSystem : MonoBehaviour +{ + public static DesktopVRIKSystem Instance; + public static Dictionary BoneExists; + public 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 + }; + enum AvatarPose + { + Default = 0, + Initial = 1, + IKPose = 2, + TPose = 3 + } + + // ChilloutVR Player Components + private PlayerSetup playerSetup; + private MovementSystem movementSystem; + + // DesktopVRIK Settings + public bool Setting_Enabled = true; + public bool Setting_PlantFeet = true; + public float Setting_BodyLeanWeight; + public float Setting_BodyHeadingLimit; + public float Setting_PelvisHeadingWeight; + public float Setting_ChestHeadingWeight; + + // Calibration Settings + public bool Setting_UseVRIKToes = true; + public bool Setting_FindUnmappedToes = true; + + // Integration Settings + public bool Setting_IntegrationAMT = false; + + // Avatar Components + public CVRAvatar avatarDescriptor = null; + public Animator avatarAnimator = null; + public Transform avatarTransform = null; + public LookAtIK avatarLookAtIK = null; + public VRIK avatarVRIK = null; + public IKSolverVR avatarIKSolver = null; + + // Calibration Objects + public HumanPose HumanPose; + public HumanPose InitialHumanPose; + public HumanPoseHandler HumanPoseHandler; + + // Animator Info + public int locomotionLayer = -1; + public int customIKPoseLayer = -1; + public bool requireFixTransforms = false; + + // VRIK Calibration Info + public Vector3 leftKneeNormal; + public Vector3 rightKneeNormal; + public float initialFootDistance; + public float initialStepThreshold; + public float initialStepHeight; + + // Player Info + private Transform _cameraTransform; + private bool _isEmotePlaying; + private float _simulatedRootAngle; + + // Last Movement Parent Info + private Vector3 _previousPosition; + private Quaternion _previousRotation; + + DesktopVRIKSystem() + { + BoneExists = new Dictionary(); + } + + void Start() + { + Instance = this; + + playerSetup = GetComponent(); + movementSystem = GetComponent(); + + _cameraTransform = playerSetup.desktopCamera.transform; + + DesktopVRIKMod.UpdateAllSettings(); + } + + void Update() + { + if (avatarVRIK == null) return; + + HandleLocomotionTracking(); + ApplyBodySystemWeights(); + } + + void HandleLocomotionTracking() + { + bool isMoving = movementSystem.movementVector.magnitude > 0f; + + // AvatarMotionTweaker handles VRIK a bit better than DesktopVRIK + if (Setting_IntegrationAMT && DesktopVRIKMod.integration_AMT) + { + if (isMoving) + { + if (BodySystem.TrackingLocomotionEnabled) + { + BodySystem.TrackingLocomotionEnabled = false; + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + } + } + else + { + if (!BodySystem.TrackingLocomotionEnabled) + { + BodySystem.TrackingLocomotionEnabled = true; + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + } + } + return; + } + + bool isGrounded = movementSystem._isGrounded; + bool isCrouching = movementSystem.crouching; + bool isProne = movementSystem.prone; + bool isFlying = movementSystem.flying; + + // Why do it myself if VRIK already does the maths + Vector3 headLocalPos = avatarIKSolver.spine.headPosition - avatarIKSolver.spine.rootPosition; + float upright = 1f + (headLocalPos.y - avatarIKSolver.spine.headHeight); + + if (isMoving || isCrouching || isProne || isFlying || !isGrounded) + { + if (BodySystem.TrackingLocomotionEnabled) + { + BodySystem.TrackingLocomotionEnabled = false; + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + } + } + else + { + if (!BodySystem.TrackingLocomotionEnabled && upright > 0.8f) + { + BodySystem.TrackingLocomotionEnabled = true; + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + } + } + } + + void ApplyBodySystemWeights() + { + void SetArmWeight(IKSolverVR.Arm arm, bool isTracked) + { + arm.positionWeight = isTracked ? 1f : 0f; + arm.rotationWeight = isTracked ? 1f : 0f; + arm.shoulderRotationWeight = isTracked ? 1f : 0f; + arm.shoulderTwistWeight = isTracked ? 1f : 0f; + } + void SetLegWeight(IKSolverVR.Leg leg, bool isTracked) + { + leg.positionWeight = isTracked ? 1f : 0f; + leg.rotationWeight = isTracked ? 1f : 0f; + } + if (BodySystem.TrackingEnabled) + { + avatarVRIK.enabled = true; + avatarIKSolver.IKPositionWeight = BodySystem.TrackingPositionWeight; + avatarIKSolver.locomotion.weight = BodySystem.TrackingLocomotionEnabled ? 1f : 0f; + + SetArmWeight(avatarIKSolver.leftArm, BodySystem.TrackingLeftArmEnabled && avatarIKSolver.leftArm.target != null); + SetArmWeight(avatarIKSolver.rightArm, BodySystem.TrackingRightArmEnabled && avatarIKSolver.rightArm.target != null); + SetLegWeight(avatarIKSolver.leftLeg, BodySystem.TrackingLeftLegEnabled && avatarIKSolver.leftLeg.target != null); + SetLegWeight(avatarIKSolver.rightLeg, BodySystem.TrackingRightLegEnabled && avatarIKSolver.rightLeg.target != null); + } + else + { + avatarVRIK.enabled = false; + avatarIKSolver.IKPositionWeight = 0f; + avatarIKSolver.locomotion.weight = 0f; + + SetArmWeight(avatarIKSolver.leftArm, false); + SetArmWeight(avatarIKSolver.rightArm, false); + SetLegWeight(avatarIKSolver.leftLeg, false); + SetLegWeight(avatarIKSolver.rightLeg, false); + } + } + + public void OnSetupAvatarDesktop() + { + if (!Setting_Enabled) return; + + CalibrateDesktopVRIK(); + ResetDesktopVRIK(); + } + + public bool OnSetupIKScaling(float scaleDifference) + { + if (avatarVRIK == null) return false; + + VRIKUtils.ApplyScaleToVRIK + ( + avatarVRIK, + initialFootDistance, + initialStepThreshold, + initialStepHeight, + scaleDifference + ); + + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + return true; + } + + public void OnPlayerSetupUpdate(bool isEmotePlaying) + { + if (avatarVRIK == null) return; + + bool changed = isEmotePlaying != _isEmotePlaying; + if (!changed) return; + + _isEmotePlaying = isEmotePlaying; + + avatarTransform.localPosition = Vector3.zero; + avatarTransform.localRotation = Quaternion.identity; + + if (avatarLookAtIK != null) + avatarLookAtIK.enabled = !isEmotePlaying; + + BodySystem.TrackingEnabled = !isEmotePlaying; + + avatarIKSolver.Reset(); + ResetDesktopVRIK(); + } + + public bool OnPlayerSetupResetIk() + { + if (avatarVRIK == null) return false; + + CVRMovementParent currentParent = movementSystem._currentParent; + if (currentParent == null) return false; + + Transform referencePoint = currentParent._referencePoint; + if (referencePoint == null) return false; + + var currentPosition = referencePoint.position; + var currentRotation = currentParent.transform.rotation; + + // Keep only the Y-axis rotation + currentRotation = Quaternion.Euler(0f, currentRotation.eulerAngles.y, 0f); + + var deltaPosition = currentPosition - _previousPosition; + var deltaRotation = Quaternion.Inverse(_previousRotation) * currentRotation; + + var platformPivot = transform.position; + avatarIKSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot); + + _previousPosition = currentPosition; + _previousRotation = currentRotation; + + ResetDesktopVRIK(); + return true; + } + + public void OnPreSolverUpdate() + { + if (_isEmotePlaying) return; + + bool isGrounded = movementSystem._isGrounded; + + // Calculate weight + float weight = avatarIKSolver.IKPositionWeight; + weight *= 1f - movementSystem.movementVector.magnitude; + weight *= isGrounded ? 1f : 0f; + + // Reset avatar offset + avatarTransform.localPosition = Vector3.zero; + avatarTransform.localRotation = Quaternion.identity; + + // Set plant feet + avatarIKSolver.plantFeet = Setting_PlantFeet; + + // Emulate old VRChat hip movementSystem + if (Setting_BodyLeanWeight > 0) + { + float weightedAngle = Setting_BodyLeanWeight * weight; + float angle = _cameraTransform.localEulerAngles.x; + angle = angle > 180 ? angle - 360 : angle; + Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right); + avatarIKSolver.spine.headRotationOffset *= rotation; + } + + // Make root heading follow within a set limit + if (Setting_BodyHeadingLimit > 0) + { + float weightedAngleLimit = Setting_BodyHeadingLimit * weight; + float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _simulatedRootAngle); + float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot); + if (absDeltaAngleRoot > weightedAngleLimit) + { + deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit; + _simulatedRootAngle = Mathf.MoveTowardsAngle(_simulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit); + } + avatarIKSolver.spine.rootHeadingOffset = deltaAngleRoot; + if (Setting_PelvisHeadingWeight > 0) + { + avatarIKSolver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_PelvisHeadingWeight, 0f); + avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * Setting_PelvisHeadingWeight, 0f); + } + if (Setting_ChestHeadingWeight > 0) + { + avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_ChestHeadingWeight, 0f); + } + } + } + + void ResetDesktopVRIK() + { + _simulatedRootAngle = transform.eulerAngles.y; + } + + void CalibrateDesktopVRIK() + { + ScanAvatar(); + SetupVRIK(); + CalibrateVRIK(); + ConfigureVRIK(); + } + + void ScanAvatar() + { + // Find required avatar components + avatarDescriptor = playerSetup._avatarDescriptor; + avatarAnimator = playerSetup._animator; + avatarTransform = playerSetup._avatar.transform; + avatarLookAtIK = playerSetup.lookIK; + + // Get animator layer inticies + locomotionLayer = avatarAnimator.GetLayerIndex("IKPose"); + customIKPoseLayer = avatarAnimator.GetLayerIndex("Locomotion/Emotes"); + + // Dispose and create new HumanPoseHandler + HumanPoseHandler?.Dispose(); + HumanPoseHandler = new HumanPoseHandler(avatarAnimator.avatar, avatarTransform); + + // Get initial human poses + HumanPoseHandler.GetHumanPose(ref HumanPose); + HumanPoseHandler.GetHumanPose(ref InitialHumanPose); + + // Dumb fix for rare upload issue + requireFixTransforms = !avatarAnimator.enabled; + + // Find available HumanoidBodyBones + BoneExists.Clear(); + foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones))) + { + if (bone != HumanBodyBones.LastBone) + { + BoneExists.Add(bone, avatarAnimator.GetBoneTransform(bone) != null); + } + } + } + + void SetupVRIK() + { + // Add and configure VRIK + avatarVRIK = avatarTransform.AddComponentIfMissing(); + avatarVRIK.AutoDetectReferences(); + avatarIKSolver = avatarVRIK.solver; + + VRIKUtils.ConfigureVRIKReferences(avatarVRIK, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes); + + // Fix animator issue or non-human mapped toes + avatarVRIK.fixTransforms = requireFixTransforms || foundUnmappedToes; + + // Default solver settings + avatarIKSolver.locomotion.weight = 0f; + avatarIKSolver.locomotion.angleThreshold = 30f; + avatarIKSolver.locomotion.maxLegStretch = 1f; + avatarIKSolver.spine.minHeadHeight = 0f; + avatarIKSolver.IKPositionWeight = 1f; + avatarIKSolver.spine.chestClampWeight = 0f; + avatarIKSolver.spine.maintainPelvisPosition = 0f; + + // Body leaning settings + avatarIKSolver.spine.neckStiffness = 0.0001f; + avatarIKSolver.spine.bodyPosStiffness = 1f; + avatarIKSolver.spine.bodyRotStiffness = 0.2f; + + // Disable locomotion + avatarIKSolver.locomotion.velocityFactor = 0f; + avatarIKSolver.locomotion.maxVelocity = 0f; + avatarIKSolver.locomotion.rootSpeed = 1000f; + + // Disable chest rotation by hands + avatarIKSolver.spine.rotateChestByHands = 0f; + + // Prioritize LookAtIK + avatarIKSolver.spine.headClampWeight = 0.2f; + + // Disable going on tippytoes + avatarIKSolver.spine.positionWeight = 0f; + avatarIKSolver.spine.rotationWeight = 1f; + + // We disable these ourselves now, as we no longer use BodySystem + avatarIKSolver.spine.maintainPelvisPosition = 1f; + avatarIKSolver.spine.positionWeight = 0f; + avatarIKSolver.spine.pelvisPositionWeight = 0f; + avatarIKSolver.leftArm.positionWeight = 0f; + avatarIKSolver.leftArm.rotationWeight = 0f; + avatarIKSolver.rightArm.positionWeight = 0f; + avatarIKSolver.rightArm.rotationWeight = 0f; + avatarIKSolver.leftLeg.positionWeight = 0f; + avatarIKSolver.leftLeg.rotationWeight = 0f; + avatarIKSolver.rightLeg.positionWeight = 0f; + avatarIKSolver.rightLeg.rotationWeight = 0f; + + // This is now our master Locomotion weight + avatarIKSolver.locomotion.weight = 1f; + avatarIKSolver.IKPositionWeight = 1f; + } + + void CalibrateVRIK() + { + SetAvatarPose(AvatarPose.Default); + + // Calculate bend normals with motorcycle pose + VRIKUtils.CalculateKneeBendNormals(avatarVRIK, out leftKneeNormal, out rightKneeNormal); + + SetAvatarPose(AvatarPose.IKPose); + + // Calculate initial IK scaling values with IKPose + VRIKUtils.CalculateInitialIKScaling(avatarVRIK, out initialFootDistance, out initialStepThreshold, out initialStepHeight); + + // Setup HeadIKTarget + VRIKUtils.SetupHeadIKTarget(avatarVRIK); + + // Initiate VRIK manually + VRIKUtils.InitiateVRIKSolver(avatarVRIK); + + SetAvatarPose(AvatarPose.Initial); + } + + void ConfigureVRIK() + { + VRIKUtils.ApplyScaleToVRIK + ( + avatarVRIK, + initialFootDistance, + initialStepThreshold, + initialStepHeight, + 1f + ); + VRIKUtils.ApplyKneeBendNormals(avatarVRIK, leftKneeNormal, rightKneeNormal); + avatarVRIK.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIKSystem.Instance.OnPreSolverUpdate)); + } + + void SetAvatarPose(AvatarPose pose) + { + switch (pose) + { + case AvatarPose.Default: + if (HasCustomIKPose()) + { + SetCustomLayersWeights(0f, 1f); + avatarAnimator.Update(0f); + } + else + { + SetMusclesToValue(0f); + } + break; + case AvatarPose.Initial: + HumanPoseHandler.SetHumanPose(ref InitialHumanPose); + break; + case AvatarPose.IKPose: + if (HasCustomIKPose()) + { + SetCustomLayersWeights(1f, 0f); + avatarAnimator.Update(0f); + } + else + { + SetMusclesToPose(IKPoseMuscles); + } + break; + case AvatarPose.TPose: + SetMusclesToPose(BodySystem.TPoseMuscles); + break; + default: + break; + } + } + + bool HasCustomIKPose() + { + return locomotionLayer != -1 && customIKPoseLayer != -1; + } + + void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight) + { + avatarAnimator.SetLayerWeight(customIKPoseLayer, customIKPoseLayerWeight); + avatarAnimator.SetLayerWeight(locomotionLayer, locomotionLayerWeight); + } + + void SetMusclesToValue(float value) + { + HumanPoseHandler.GetHumanPose(ref HumanPose); + + for (int i = 0; i < HumanPose.muscles.Length; i++) + { + ApplyMuscleValue((MuscleIndex)i, value, ref HumanPose.muscles); + } + + HumanPose.bodyRotation = Quaternion.identity; + HumanPoseHandler.SetHumanPose(ref HumanPose); + } + + void SetMusclesToPose(float[] muscles) + { + HumanPoseHandler.GetHumanPose(ref HumanPose); + + for (int i = 0; i < HumanPose.muscles.Length; i++) + { + ApplyMuscleValue((MuscleIndex)i, muscles[i], ref HumanPose.muscles); + } + + HumanPose.bodyRotation = Quaternion.identity; + HumanPoseHandler.SetHumanPose(ref HumanPose); + } + + void ApplyMuscleValue(MuscleIndex index, float value, ref float[] muscles) + { + if (BoneExists.ContainsKey(IKSystem.MusclesToHumanBodyBones[(int)index]) && BoneExists[IKSystem.MusclesToHumanBodyBones[(int)index]]) + { + muscles[(int)index] = value; + } + } +} diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs index dcd965a..7b03dc1 100644 --- a/DesktopVRIK/HarmonyPatches.cs +++ b/DesktopVRIK/HarmonyPatches.cs @@ -1,5 +1,4 @@ using ABI_RC.Core.Player; -using ABI_RC.Systems.IK; using HarmonyLib; using UnityEngine; @@ -23,13 +22,20 @@ namespace NAK.Melons.DesktopVRIK.HarmonyPatches; class PlayerSetupPatches { + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), "Start")] + static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) + { + __instance.gameObject.AddComponent(); + } + [HarmonyPostfix] [HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")] static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator) { if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman) { - DesktopVRIK.Instance?.OnSetupAvatarDesktop(); + DesktopVRIKSystem.Instance?.OnSetupAvatarDesktop(); } } @@ -37,30 +43,20 @@ class PlayerSetupPatches [HarmonyPatch(typeof(PlayerSetup), "Update")] static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying) { - DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying); + DesktopVRIKSystem.Instance?.OnPlayerSetupUpdate(____emotePlaying); } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")] private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference) { - return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y); + return !(bool)DesktopVRIKSystem.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y); } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerSetup), "ResetIk")] static bool Prefix_PlayerSetup_ResetIk() { - return !(bool)DesktopVRIK.Instance?.OnPlayerSetupResetIk(); - } -} - -class IKSystemPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(IKSystem), "Start")] - private static void Postfix_IKSystem_Start(ref IKSystem __instance) - { - __instance.gameObject.AddComponent(); + return !(bool)DesktopVRIKSystem.Instance?.OnPlayerSetupResetIk(); } } diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs index 7adb920..bb88c21 100644 --- a/DesktopVRIK/Main.cs +++ b/DesktopVRIK/Main.cs @@ -13,7 +13,7 @@ public class DesktopVRIKMod : MelonMod 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."); + CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movementSystem."); 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."); @@ -33,6 +33,11 @@ public class DesktopVRIKMod : MelonMod 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 static readonly MelonPreferences_Entry EntryIntegrationAMT = + CategoryDesktopVRIK.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available."); + + public static bool integration_AMT = false; + public override void OnInitializeMelon() { Logger = LoggerInstance; @@ -40,40 +45,47 @@ public class DesktopVRIKMod : MelonMod CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings)); ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); - ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); - InitializeIntegrations(); - } - - internal static void UpdateAllSettings() - { - if (!DesktopVRIK.Instance) return; - // DesktopVRIK Settings - DesktopVRIK.Instance.Setting_Enabled = EntryEnabled.Value; - DesktopVRIK.Instance.Setting_PlantFeet = EntryPlantFeet.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 = EntryUseVRIKToes.Value; - DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = 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")) { Logger.Msg("Initializing BTKUILib support."); BTKUIAddon.Init(); } + //AvatarMotionTweaker Handling + if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker")) + { + Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion."); + integration_AMT = true; + } + else + { + Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling."); + } } - private void ApplyPatches(Type type) + internal static void UpdateAllSettings() + { + if (!DesktopVRIKSystem.Instance) return; + // DesktopVRIK Settings + DesktopVRIKSystem.Instance.Setting_Enabled = EntryEnabled.Value; + DesktopVRIKSystem.Instance.Setting_PlantFeet = EntryPlantFeet.Value; + + DesktopVRIKSystem.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value); + DesktopVRIKSystem.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f); + DesktopVRIKSystem.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value)); + DesktopVRIKSystem.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value)); + + // Calibration Settings + DesktopVRIKSystem.Instance.Setting_UseVRIKToes = EntryUseVRIKToes.Value; + DesktopVRIKSystem.Instance.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value; + + // Integration Settings + DesktopVRIKSystem.Instance.Setting_IntegrationAMT = EntryIntegrationAMT.Value; + } + void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); + + void ApplyPatches(Type type) { try { diff --git a/DesktopVRIK/VRIKUtils.cs b/DesktopVRIK/VRIKUtils.cs index e212622..0444c79 100644 --- a/DesktopVRIK/VRIKUtils.cs +++ b/DesktopVRIK/VRIKUtils.cs @@ -1,13 +1,10 @@ using RootMotion.FinalIK; -using System.Reflection; using UnityEngine; namespace NAK.Melons.DesktopVRIK; public static class VRIKUtils { - static readonly FieldInfo vrik_bendNormalRelToPelvis = typeof(IKSolverVR.Leg).GetField("bendNormalRelToPelvis", BindingFlags.NonPublic | BindingFlags.Instance); - public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes, bool findUnmappedToes, out bool foundUnmappedToes) { foundUnmappedToes = false; @@ -119,16 +116,13 @@ public static class VRIKUtils public static void ApplyKneeBendNormals(VRIK vrik, Vector3 leftKneeNormal, Vector3 rightKneeNormal) { // 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget - // modifying pelvis normal weight is better math + // modifying pelvis normal weight is easier math vrik.solver.leftLeg.bendToTargetWeight = 0f; vrik.solver.rightLeg.bendToTargetWeight = 0f; var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation); - var leftLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal; - var rightLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal; - - vrik_bendNormalRelToPelvis.SetValue(vrik.solver.leftLeg, leftLeg_bendNormalRelToPelvis); - vrik_bendNormalRelToPelvis.SetValue(vrik.solver.rightLeg, rightLeg_bendNormalRelToPelvis); + vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal; + vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal; } private static Vector3 GetNormalFromArray(Vector3[] positions) @@ -160,6 +154,18 @@ public static class VRIKUtils initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; } + public static void SetupHeadIKTarget(VRIK vrik) + { + // 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; + } + public static void ApplyScaleToVRIK(VRIK vrik, float footDistance, float stepThreshold, float stepHeight, float modifier) { vrik.solver.locomotion.footDistance = footDistance * modifier;