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