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