[DesktopVRIK] Clean up VRIK calibration and configuration. Add NetIKPass.

This commit is contained in:
NotAKidoS 2023-05-19 02:53:41 -05:00
parent 9c627d3cce
commit d21018001e
11 changed files with 681 additions and 593 deletions

View file

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"/> <Project Sdk="Microsoft.NET.Sdk" />

View file

@ -0,0 +1,321 @@
using ABI_RC.Core.Base;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using NAK.DesktopVRIK.VRIKHelper;
using RootMotion.FinalIK;
using UnityEngine;
using UnityEngine.Events;
namespace NAK.DesktopVRIK;
internal class DesktopVRIKCalibrator
{
public static Dictionary<HumanBodyBones, bool> 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
}
readonly DesktopVRIKSystem ikSystem;
// Avatar Components
Animator _animator;
// Calibration Objects
public HumanPoseHandler _humanPoseHandler;
public HumanPose _humanPose;
public HumanPose _humanPoseInitial;
// Animator Info
int _animLocomotionLayer = -1;
int _animIKPoseLayer = -1;
internal DesktopVRIKCalibrator()
{
ikSystem = DesktopVRIKSystem.Instance;
BoneExists = new Dictionary<HumanBodyBones, bool>();
}
public void ApplyNetIKPass()
{
Transform hipTransform = _animator.GetBoneTransform(HumanBodyBones.Hips);
Vector3 hipPosition = hipTransform.position;
Quaternion hipRotation = hipTransform.rotation;
_humanPoseHandler.GetHumanPose(ref _humanPose);
_humanPoseHandler.SetHumanPose(ref _humanPose);
hipTransform.position = hipPosition;
hipTransform.rotation = hipRotation;
}
public void CalibrateDesktopVRIK(Animator animator)
{
ScanAvatar(animator);
SetupVRIK();
CalibrateVRIK();
ConfigureVRIK();
}
void ScanAvatar(Animator animator)
{
// Find required avatar components
_animator = animator;
ikSystem.avatarTransform = animator.gameObject.transform;
ikSystem.avatarLookAtIK = animator.gameObject.GetComponent<LookAtIK>();
// Get animator layer inticies
_animIKPoseLayer = _animator.GetLayerIndex("IKPose");
_animLocomotionLayer = _animator.GetLayerIndex("Locomotion/Emotes");
// Dispose and create new _humanPoseHandler
_humanPoseHandler?.Dispose();
_humanPoseHandler = new HumanPoseHandler(_animator.avatar, ikSystem.avatarTransform);
// Get initial human poses
_humanPoseHandler.GetHumanPose(ref _humanPose);
_humanPoseHandler.GetHumanPose(ref _humanPoseInitial);
ikSystem.calibrationData.Clear();
// Dumb fix for rare upload issue
ikSystem.calibrationData.FixTransformsRequired = !_animator.enabled;
// Find available HumanoidBodyBones
BoneExists.Clear();
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone != HumanBodyBones.LastBone)
{
BoneExists.Add(bone, _animator.GetBoneTransform(bone) != null);
}
}
}
void SetupVRIK()
{
// Add and configure VRIK
ikSystem.avatarVRIK = ikSystem.avatarTransform.AddComponentIfMissing<VRIK>();
ikSystem.avatarVRIK.AutoDetectReferences();
ikSystem.cachedSolver = new CachedSolver(ikSystem.avatarVRIK.solver);
// Why do I love to overcomplicate things?
VRIKUtils.ConfigureVRIKReferences(ikSystem.avatarVRIK, DesktopVRIK.EntryUseVRIKToes.Value);
VRIKConfigurator.ApplyVRIKConfiguration(ikSystem.cachedSolver, VRIKConfigurations.DesktopVRIKConfiguration());
// Fix potential animator issue
ikSystem.avatarVRIK.fixTransforms = ikSystem.calibrationData.FixTransformsRequired;
}
void CalibrateVRIK()
{
SetAvatarPose(AvatarPose.Default);
// Calculate bend normals with motorcycle pose
VRIKUtils.CalculateKneeBendNormals(ikSystem.avatarVRIK, ref ikSystem.calibrationData);
SetAvatarPose(AvatarPose.IKPose);
// Calculate initial IK scaling values with IKPose
VRIKUtils.CalculateInitialIKScaling(ikSystem.avatarVRIK, ref ikSystem.calibrationData);
// Calculate initial Footstep positions
VRIKUtils.CalculateInitialFootsteps(ikSystem.avatarVRIK, ref ikSystem.calibrationData);
// Setup HeadIKTarget
VRIKUtils.SetupHeadIKTarget(ikSystem.avatarVRIK);
// Initiate VRIK manually
VRIKUtils.InitiateVRIKSolver(ikSystem.avatarVRIK);
SetAvatarPose(AvatarPose.Initial);
}
void ConfigureVRIK()
{
ikSystem.OnSetupIKScaling(1f);
VRIKUtils.ApplyKneeBendNormals(ikSystem.avatarVRIK, ikSystem.calibrationData);
ikSystem.avatarVRIK.onPreSolverUpdate.AddListener(new UnityAction(ikSystem.OnPreSolverUpdate));
ikSystem.avatarVRIK.onPostSolverUpdate.AddListener(new UnityAction(ikSystem.OnPostSolverUpdate));
}
void SetAvatarPose(AvatarPose pose)
{
switch (pose)
{
case AvatarPose.Default:
SetMusclesToValue(0f);
break;
case AvatarPose.Initial:
if (HasCustomIKPose())
{
SetCustomLayersWeights(0f, 1f);
return;
}
_humanPoseHandler.SetHumanPose(ref _humanPoseInitial);
break;
case AvatarPose.IKPose:
if (HasCustomIKPose())
{
SetCustomLayersWeights(1f, 0f);
return;
}
SetMusclesToPose(IKPoseMuscles);
break;
case AvatarPose.TPose:
SetMusclesToPose(BodySystem.TPoseMuscles);
break;
default:
break;
}
}
bool HasCustomIKPose()
{
return _animLocomotionLayer != -1 && _animIKPoseLayer != -1;
}
void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
{
_animator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight);
_animator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight);
_animator.Update(0f);
}
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;
}
}
}

View file

@ -1,178 +1,31 @@
using ABI.CCK.Components; using ABI.CCK.Components;
using ABI_RC.Core.Base;
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem; using ABI_RC.Systems.MovementSystem;
using NAK.DesktopVRIK.VRIKHelper;
using RootMotion.FinalIK; using RootMotion.FinalIK;
using UnityEngine; using UnityEngine;
using UnityEngine.Events;
namespace NAK.DesktopVRIK; namespace NAK.DesktopVRIK;
internal class DesktopVRIKSystem : MonoBehaviour internal class DesktopVRIKSystem : MonoBehaviour
{ {
public static DesktopVRIKSystem Instance; public static DesktopVRIKSystem Instance;
public static Dictionary<HumanBodyBones, bool> BoneExists; public static DesktopVRIKCalibrator Calibrator;
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 // VRIK Calibration Info
{ public VRIKCalibrationData calibrationData;
Default = 0,
Initial = 1,
IKPose = 2,
TPose = 3
}
// DesktopVRIK Settings
public bool Setting_Enabled = true;
public bool Setting_PlantFeet;
public bool Setting_ResetFootsteps;
public bool Setting_ProneThrusting;
public float Setting_BodyLeanWeight;
public float Setting_BodyHeadingLimit;
public float Setting_PelvisHeadingWeight;
public float Setting_ChestHeadingWeight;
public float Setting_IKLerpSpeed;
// Calibration Settings
public bool Setting_UseVRIKToes;
public bool Setting_FindUnmappedToes;
// Integration Settings
public bool Setting_IntegrationAMT;
// Avatar Components // Avatar Components
public CVRAvatar avatarDescriptor = null;
public Animator avatarAnimator = null;
public Transform avatarTransform = null;
public LookAtIK avatarLookAtIK = null;
public VRIK avatarVRIK = null; public VRIK avatarVRIK = null;
public IKSolverVR avatarIKSolver = null; public LookAtIK avatarLookAtIK = null;
public Transform avatarTransform = null;
public CachedSolver cachedSolver;
// ChilloutVR Player Components // ChilloutVR Player Components
PlayerSetup playerSetup; PlayerSetup playerSetup;
MovementSystem movementSystem; MovementSystem movementSystem;
// Calibration Objects
HumanPose _humanPose;
HumanPose _humanPoseInitial;
HumanPoseHandler _humanPoseHandler;
// Animator Info
int _animLocomotionLayer = -1;
int _animIKPoseLayer = -1;
// VRIK Calibration Info
Vector3 _vrikKneeNormalLeft;
Vector3 _vrikKneeNormalRight;
Vector3 _vrikInitialFootPosLeft;
Vector3 _vrikInitialFootPosRight;
Quaternion _vrikInitialFootRotLeft;
Quaternion _vrikInitialFootRotRight;
float _vrikInitialHeadHeight;
float _vrikInitialFootDistance;
float _vrikInitialStepThreshold;
float _vrikInitialStepHeight;
bool _vrikFixTransformsRequired;
// Player Info // Player Info
Transform _cameraTransform; Transform _cameraTransform;
bool _ikEmotePlaying; bool _ikEmotePlaying;
@ -186,21 +39,16 @@ internal class DesktopVRIKSystem : MonoBehaviour
Quaternion _movementRotation; Quaternion _movementRotation;
CVRMovementParent _movementParent; CVRMovementParent _movementParent;
DesktopVRIKSystem()
{
BoneExists = new Dictionary<HumanBodyBones, bool>();
}
void Start() void Start()
{ {
Instance = this; Instance = this;
Calibrator = new DesktopVRIKCalibrator();
playerSetup = GetComponent<PlayerSetup>(); playerSetup = GetComponent<PlayerSetup>();
movementSystem = GetComponent<MovementSystem>(); movementSystem = GetComponent<MovementSystem>();
_cameraTransform = playerSetup.desktopCamera.transform; _cameraTransform = playerSetup.desktopCamera.transform;
DesktopVRIK.UpdateAllSettings();
} }
void Update() void Update()
@ -220,7 +68,7 @@ internal class DesktopVRIKSystem : MonoBehaviour
if (shouldTrackLocomotion != BodySystem.TrackingLocomotionEnabled) if (shouldTrackLocomotion != BodySystem.TrackingLocomotionEnabled)
{ {
BodySystem.TrackingLocomotionEnabled = shouldTrackLocomotion; BodySystem.TrackingLocomotionEnabled = shouldTrackLocomotion;
avatarIKSolver.Reset(); IKResetSolver();
ResetDesktopVRIK(); ResetDesktopVRIK();
if (shouldTrackLocomotion) IKResetFootsteps(); if (shouldTrackLocomotion) IKResetFootsteps();
} }
@ -242,29 +90,26 @@ internal class DesktopVRIKSystem : MonoBehaviour
bool IsStanding() bool IsStanding()
{ {
// Let AMT handle it if available // Let AMT handle it if available
if (Setting_IntegrationAMT) return true; if (DesktopVRIK.EntryIntegrationAMT.Value) return true;
// Get Upright value // Get Upright value
Vector3 delta = avatarIKSolver.spine.headPosition - avatarTransform.position; Vector3 delta = cachedSolver.Spine.headPosition - avatarTransform.position;
Vector3 deltaRotated = Quaternion.Euler(0, avatarTransform.rotation.eulerAngles.y, 0) * delta; Vector3 deltaRotated = Quaternion.Euler(0, avatarTransform.rotation.eulerAngles.y, 0) * delta;
float upright = Mathf.InverseLerp(0f, _vrikInitialHeadHeight * _scaleDifference, deltaRotated.y); float upright = Mathf.InverseLerp(0f, calibrationData.InitialHeadHeight * _scaleDifference, deltaRotated.y);
return upright > 0.85f; return upright > 0.85f;
} }
void UpdateLocomotionWeight() void UpdateLocomotionWeight()
{ {
float targetWeight = BodySystem.TrackingEnabled && BodySystem.TrackingLocomotionEnabled ? 1.0f : 0.0f; float targetWeight = BodySystem.TrackingEnabled && BodySystem.TrackingLocomotionEnabled ? 1.0f : 0.0f;
if (Setting_IKLerpSpeed > 0) if (DesktopVRIK.EntryIKLerpSpeed.Value > 0)
{ {
_ikWeightLerp = Mathf.Lerp(_ikWeightLerp, targetWeight, Time.deltaTime * Setting_IKLerpSpeed); _ikWeightLerp = Mathf.Lerp(_ikWeightLerp, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value);
_locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * Setting_IKLerpSpeed * 2f); _locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value * 2f);
} return;
else
{
_ikWeightLerp = targetWeight;
_locomotionWeight = targetWeight;
} }
_ikWeightLerp = targetWeight;
_locomotionWeight = targetWeight;
} }
void ApplyBodySystemWeights() void ApplyBodySystemWeights()
@ -281,32 +126,33 @@ internal class DesktopVRIKSystem : MonoBehaviour
leg.positionWeight = isTracked ? 1f : 0f; leg.positionWeight = isTracked ? 1f : 0f;
leg.rotationWeight = isTracked ? 1f : 0f; leg.rotationWeight = isTracked ? 1f : 0f;
} }
if (BodySystem.TrackingEnabled) if (BodySystem.TrackingEnabled)
{ {
avatarVRIK.enabled = true; avatarVRIK.enabled = true;
avatarIKSolver.IKPositionWeight = BodySystem.TrackingPositionWeight; cachedSolver.Solver.IKPositionWeight = BodySystem.TrackingPositionWeight;
avatarIKSolver.locomotion.weight = _locomotionWeight; cachedSolver.Locomotion.weight = _locomotionWeight;
bool useAnimatedBendNormal = _locomotionWeight <= 0.5f; bool useAnimatedBendNormal = _locomotionWeight <= 0.5f;
avatarIKSolver.leftLeg.useAnimatedBendNormal = useAnimatedBendNormal; cachedSolver.LeftLeg.useAnimatedBendNormal = useAnimatedBendNormal;
avatarIKSolver.rightLeg.useAnimatedBendNormal = useAnimatedBendNormal; cachedSolver.RightLeg.useAnimatedBendNormal = useAnimatedBendNormal;
SetArmWeight(avatarIKSolver.leftArm, BodySystem.TrackingLeftArmEnabled && avatarIKSolver.leftArm.target != null); SetArmWeight(cachedSolver.LeftArm, BodySystem.TrackingLeftArmEnabled && cachedSolver.LeftArm.target != null);
SetArmWeight(avatarIKSolver.rightArm, BodySystem.TrackingRightArmEnabled && avatarIKSolver.rightArm.target != null); SetArmWeight(cachedSolver.RightArm, BodySystem.TrackingRightArmEnabled && cachedSolver.RightArm.target != null);
SetLegWeight(avatarIKSolver.leftLeg, BodySystem.TrackingLeftLegEnabled && avatarIKSolver.leftLeg.target != null); SetLegWeight(cachedSolver.LeftLeg, BodySystem.TrackingLeftLegEnabled && cachedSolver.LeftLeg.target != null);
SetLegWeight(avatarIKSolver.rightLeg, BodySystem.TrackingRightLegEnabled && avatarIKSolver.rightLeg.target != null); SetLegWeight(cachedSolver.RightLeg, BodySystem.TrackingRightLegEnabled && cachedSolver.RightLeg.target != null);
} }
else else
{ {
avatarVRIK.enabled = false; avatarVRIK.enabled = false;
avatarIKSolver.IKPositionWeight = 0f; cachedSolver.Solver.IKPositionWeight = 0f;
avatarIKSolver.locomotion.weight = 0f; cachedSolver.Locomotion.weight = 0f;
avatarIKSolver.leftLeg.useAnimatedBendNormal = false; cachedSolver.LeftLeg.useAnimatedBendNormal = false;
avatarIKSolver.rightLeg.useAnimatedBendNormal = false; cachedSolver.RightLeg.useAnimatedBendNormal = false;
SetArmWeight(avatarIKSolver.leftArm, false); SetArmWeight(cachedSolver.LeftArm, false);
SetArmWeight(avatarIKSolver.rightArm, false); SetArmWeight(cachedSolver.RightArm, false);
SetLegWeight(avatarIKSolver.leftLeg, false); SetLegWeight(cachedSolver.LeftLeg, false);
SetLegWeight(avatarIKSolver.rightLeg, false); SetLegWeight(cachedSolver.RightLeg, false);
} }
} }
@ -319,42 +165,26 @@ internal class DesktopVRIKSystem : MonoBehaviour
public void OnSetupAvatarDesktop(Animator animator) public void OnSetupAvatarDesktop(Animator animator)
{ {
if (!Setting_Enabled) return; if (!DesktopVRIK.EntryEnabled.Value) return;
// only run for humanoid avatars // only run for humanoid avatars
if (animator != null && animator.avatar != null && animator.avatar.isHuman) if (animator != null && animator.avatar != null && animator.avatar.isHuman)
{ {
CalibrateDesktopVRIK(); Calibrator.CalibrateDesktopVRIK(animator);
ResetDesktopVRIK(); ResetDesktopVRIK();
} }
} }
public bool OnSetupIKScaling(float scaleDifference) public void OnSetupIKScaling(float scaleDifference)
{ {
if (avatarVRIK == null) return false;
_scaleDifference = scaleDifference; _scaleDifference = scaleDifference;
VRIKUtils.ApplyScaleToVRIK VRIKUtils.ApplyScaleToVRIK
( (
avatarVRIK, avatarVRIK,
_vrikInitialFootDistance, calibrationData,
_vrikInitialStepThreshold,
_vrikInitialStepHeight,
_scaleDifference _scaleDifference
); );
//VRIKUtils.SetFootsteps
//(
// avatarVRIK,
// _vrikInitialFootPosLeft * _scaleDifference,
// _vrikInitialFootPosRight * _scaleDifference,
// _vrikInitialFootRotLeft,
// _vrikInitialFootRotRight
//);
ResetDesktopVRIK();
return true;
} }
public void OnPlayerSetupUpdate(bool isEmotePlaying) public void OnPlayerSetupUpdate(bool isEmotePlaying)
@ -367,13 +197,13 @@ internal class DesktopVRIKSystem : MonoBehaviour
// Disable tracking completely while emoting // Disable tracking completely while emoting
BodySystem.TrackingEnabled = !isEmotePlaying; BodySystem.TrackingEnabled = !isEmotePlaying;
avatarIKSolver.Reset(); IKResetSolver();
ResetDesktopVRIK(); ResetDesktopVRIK();
} }
public void OnPlayerSetupSetSitting() public void OnPlayerSetupSetSitting()
{ {
avatarIKSolver.Reset(); IKResetSolver();
ResetDesktopVRIK(); ResetDesktopVRIK();
} }
@ -398,7 +228,7 @@ internal class DesktopVRIKSystem : MonoBehaviour
if (_movementParent == currentParent) if (_movementParent == currentParent)
{ {
// Add platform motion to IK solver // Add platform motion to IK solver
avatarIKSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot); cachedSolver.Solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
ResetDesktopVRIK(); ResetDesktopVRIK();
} }
@ -406,80 +236,88 @@ internal class DesktopVRIKSystem : MonoBehaviour
_movementParent = currentParent; _movementParent = currentParent;
_movementPosition = currentPosition; _movementPosition = currentPosition;
_movementRotation = currentRotation; _movementRotation = currentRotation;
return; return;
} }
// if not for movementparent, reset ik solver // if not for movementparent, reset ik
avatarIKSolver.Reset(); IKResetSolver();
IKResetFootsteps();
ResetDesktopVRIK(); ResetDesktopVRIK();
//IKResetFootsteps();
} }
public void OnPreSolverUpdate() public void OnPreSolverUpdate()
{ {
// Set plant feet // Set plant feet
avatarIKSolver.plantFeet = Setting_PlantFeet; cachedSolver.Solver.plantFeet = DesktopVRIK.EntryPlantFeet.Value;
// Apply custom VRIK solving effects // Apply custom VRIK solving effects
IKBodyLeaningOffset(_ikWeightLerp); IKBodyLeaningOffset(_ikWeightLerp);
IKBodyHeadingOffset(_ikWeightLerp); IKBodyHeadingOffset(_ikWeightLerp);
void IKBodyLeaningOffset(float weight)
{
// Emulate old VRChat hip movement
if (DesktopVRIK.EntryBodyLeanWeight.Value <= 0) return;
if (DesktopVRIK.EntryProneThrusting.Value) weight = 1f;
float weightedAngle = DesktopVRIK.EntryBodyLeanWeight.Value * weight;
float angle = _cameraTransform.localEulerAngles.x;
angle = angle > 180 ? angle - 360 : angle;
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right);
cachedSolver.Spine.headRotationOffset *= rotation;
}
void IKBodyHeadingOffset(float weight)
{
// Make root heading follow within a set limit
if (DesktopVRIK.EntryBodyHeadingLimit.Value <= 0) return;
float weightedAngleLimit = DesktopVRIK.EntryBodyHeadingLimit.Value * weight;
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _ikSimulatedRootAngle);
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
if (absDeltaAngleRoot > weightedAngleLimit)
{
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
_ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
}
cachedSolver.Spine.rootHeadingOffset = deltaAngleRoot;
if (DesktopVRIK.EntryPelvisHeadingWeight.Value > 0)
{
cachedSolver.Spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * DesktopVRIK.EntryPelvisHeadingWeight.Value, 0f);
cachedSolver.Spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * DesktopVRIK.EntryPelvisHeadingWeight.Value, 0f);
}
if (DesktopVRIK.EntryChestHeadingWeight.Value > 0)
{
cachedSolver.Spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * DesktopVRIK.EntryChestHeadingWeight.Value, 0f);
}
}
} }
void IKBodyLeaningOffset(float weight) public void OnPostSolverUpdate()
{ {
// Emulate old VRChat hip movement if (!DesktopVRIK.EntryNetIKPass.Value) return;
if (Setting_BodyLeanWeight <= 0) return; Calibrator.ApplyNetIKPass();
if (Setting_ProneThrusting) weight = 1f;
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;
} }
void IKBodyHeadingOffset(float weight) void IKResetSolver()
{ {
// Make root heading follow within a set limit cachedSolver.Solver.Reset();
if (Setting_BodyHeadingLimit <= 0) return;
float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _ikSimulatedRootAngle);
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
if (absDeltaAngleRoot > weightedAngleLimit)
{
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
_ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, 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 IKResetFootsteps() void IKResetFootsteps()
{ {
// Reset footsteps immediatly to initial // Reset footsteps immediatly to initial
if (!Setting_ResetFootsteps) return; if (!DesktopVRIK.EntryResetFootstepsOnIdle.Value) return;
VRIKUtils.SetFootsteps VRIKUtils.ResetToInitialFootsteps
( (
avatarVRIK, avatarVRIK,
_vrikInitialFootPosLeft, calibrationData,
_vrikInitialFootPosRight, _scaleDifference
_vrikInitialFootRotLeft,
_vrikInitialFootRotRight
); );
} }
@ -487,234 +325,4 @@ internal class DesktopVRIKSystem : MonoBehaviour
{ {
_ikSimulatedRootAngle = transform.eulerAngles.y; _ikSimulatedRootAngle = 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
_animIKPoseLayer = avatarAnimator.GetLayerIndex("IKPose");
_animLocomotionLayer = 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 _humanPoseInitial);
// Dumb fix for rare upload issue
_vrikFixTransformsRequired = !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<VRIK>();
avatarVRIK.AutoDetectReferences();
avatarIKSolver = avatarVRIK.solver;
VRIKUtils.ConfigureVRIKReferences(avatarVRIK, Setting_UseVRIKToes);
// Fix animator issue
avatarVRIK.fixTransforms = _vrikFixTransformsRequired;
// 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.bodyPosStiffness = 1f;
avatarIKSolver.spine.bodyRotStiffness = 0.2f;
// this is a hack, allows chest to rotate slightly
// independent from hip rotation. Funny Spine.Solve()->Bend()
avatarIKSolver.spine.neckStiffness = 0.0001f;
// Disable locomotion
// Setting velocity to 0 aleviated nameplate jitter issue on remote
avatarIKSolver.locomotion.velocityFactor = 0f;
avatarIKSolver.locomotion.maxVelocity = 0f;
avatarIKSolver.locomotion.rootSpeed = 1000f;
// Disable chest rotation by hands
// this fixed Effector, Player Arm Movement, BetterInteractDesktop, ect
// from making entire body shake, as well as while running
avatarIKSolver.spine.rotateChestByHands = 0f;
// Prioritize LookAtIK
avatarIKSolver.spine.headClampWeight = 0.2f;
// Disable going on tippytoes
avatarIKSolver.spine.positionWeight = 0f;
avatarIKSolver.spine.rotationWeight = 1f;
// Set so emotes play properly
avatarIKSolver.spine.maxRootAngle = 180f;
// this is different in VR, as CVR player controller is not set up optimally for VRIK.
// Desktop avatar rotates 1:1 with _PlayerLocal. VR has a disconnect because you can turn IRL.
// 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 _vrikKneeNormalLeft, out _vrikKneeNormalRight);
SetAvatarPose(AvatarPose.IKPose);
_vrikInitialHeadHeight = Mathf.Abs(avatarVRIK.references.head.position.y - avatarVRIK.references.rightFoot.position.y);
// Calculate initial IK scaling values with IKPose
VRIKUtils.CalculateInitialIKScaling(avatarVRIK, out _vrikInitialFootDistance, out _vrikInitialStepThreshold, out _vrikInitialStepHeight);
// Calculate initial Footstep positions
VRIKUtils.CalculateInitialFootsteps(avatarVRIK, out _vrikInitialFootPosLeft, out _vrikInitialFootPosRight, out _vrikInitialFootRotLeft, out _vrikInitialFootRotRight);
// Setup HeadIKTarget
VRIKUtils.SetupHeadIKTarget(avatarVRIK);
// Initiate VRIK manually
VRIKUtils.InitiateVRIKSolver(avatarVRIK);
SetAvatarPose(AvatarPose.Initial);
}
void ConfigureVRIK()
{
// Reset scale diffrence
_scaleDifference = 1f;
VRIKUtils.ApplyScaleToVRIK
(
avatarVRIK,
_vrikInitialFootDistance,
_vrikInitialStepThreshold,
_vrikInitialStepHeight,
1f
);
VRIKUtils.ApplyKneeBendNormals(avatarVRIK, _vrikKneeNormalLeft, _vrikKneeNormalRight);
avatarVRIK.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIKSystem.Instance.OnPreSolverUpdate));
}
void SetAvatarPose(AvatarPose pose)
{
switch (pose)
{
case AvatarPose.Default:
SetMusclesToValue(0f);
break;
case AvatarPose.Initial:
if (HasCustomIKPose())
{
SetCustomLayersWeights(0f, 1f);
return;
}
_humanPoseHandler.SetHumanPose(ref _humanPoseInitial);
break;
case AvatarPose.IKPose:
if (HasCustomIKPose())
{
SetCustomLayersWeights(1f, 0f);
return;
}
SetMusclesToPose(IKPoseMuscles);
break;
case AvatarPose.TPose:
SetMusclesToPose(BodySystem.TPoseMuscles);
break;
default:
break;
}
}
bool HasCustomIKPose()
{
return _animLocomotionLayer != -1 && _animIKPoseLayer != -1;
}
void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
{
avatarAnimator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight);
avatarAnimator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight);
avatarAnimator.Update(0f);
}
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;
}
}
} }

View file

@ -2,7 +2,7 @@
using BTKUILib.UIObjects; using BTKUILib.UIObjects;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace NAK.DesktopVRIK; namespace NAK.DesktopVRIK.Integrations;
public static class BTKUIAddon public static class BTKUIAddon
{ {

View file

@ -1,5 +1,4 @@
using MelonLoader; using MelonLoader;
using UnityEngine;
namespace NAK.DesktopVRIK; namespace NAK.DesktopVRIK;
@ -8,41 +7,44 @@ public class DesktopVRIK : MelonMod
internal static MelonLogger.Instance Logger; internal static MelonLogger.Instance Logger;
internal const string SettingsCategory = nameof(DesktopVRIK); internal const string SettingsCategory = nameof(DesktopVRIK);
public static readonly MelonPreferences_Category CategoryDesktopVRIK = public static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(SettingsCategory); MelonPreferences.CreateCategory(SettingsCategory);
public static readonly MelonPreferences_Entry<bool> EntryEnabled = public static readonly MelonPreferences_Entry<bool> EntryEnabled =
CategoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); Category.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet = public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement."); Category.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle = public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
CategoryDesktopVRIK.CreateEntry("Reset Footsteps on Idle", true, description: "Determins if the Locomotion Footsteps will be reset to their calibration position when entering idle."); Category.CreateEntry("Reset Footsteps on Idle", false, description: "Determins if the Locomotion Footsteps will be reset to their calibration position when entering idle.");
public static readonly MelonPreferences_Entry<bool> EntryUseVRIKToes = public static readonly MelonPreferences_Entry<bool> 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."); Category.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<float> EntryBodyLeanWeight = public static readonly MelonPreferences_Entry<float> 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."); Category.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<float> EntryBodyHeadingLimit = public static readonly MelonPreferences_Entry<float> 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."); Category.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<float> EntryPelvisHeadingWeight = public static readonly MelonPreferences_Entry<float> 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."); Category.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<float> EntryChestHeadingWeight = public static readonly MelonPreferences_Entry<float> 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."); Category.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<float> EntryIKLerpSpeed = public static readonly MelonPreferences_Entry<float> EntryIKLerpSpeed =
CategoryDesktopVRIK.CreateEntry("IK Lerp Speed", 10f, description: "Determines fast the IK & Locomotion weights blend after entering idle. Set to 0 to disable."); Category.CreateEntry("IK Lerp Speed", 10f, description: "Determines fast the IK & Locomotion weights blend after entering idle. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<bool> EntryProneThrusting = public static readonly MelonPreferences_Entry<bool> EntryProneThrusting =
CategoryDesktopVRIK.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take effect while crouched or prone."); Category.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take effect while crouched or prone.");
public static readonly MelonPreferences_Entry<bool> EntryNetIKPass =
Category.CreateEntry("Network IK Pass", true, description: "Should NetIK pass be applied? This fixes a bunch of small rotation errors after VRIK is run.");
public static readonly MelonPreferences_Entry<bool> EntryIntegrationAMT = public static readonly MelonPreferences_Entry<bool> EntryIntegrationAMT =
CategoryDesktopVRIK.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available."); Category.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available.");
public static bool integration_AMT = false; public static bool integration_AMT = false;
@ -50,57 +52,27 @@ public class DesktopVRIK : MelonMod
{ {
Logger = LoggerInstance; Logger = LoggerInstance;
CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings));
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
//BTKUILib Misc Tab //BTKUILib Misc Tab
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib")) if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
{ {
Logger.Msg("Initializing BTKUILib support."); Logger.Msg("Initializing BTKUILib support.");
BTKUIAddon.Init(); Integrations.BTKUIAddon.Init();
} }
//AvatarMotionTweaker Handling //AvatarMotionTweaker Handling
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker")) if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker"))
{ {
Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
integration_AMT = true; integration_AMT = true;
Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
} }
else else
{ {
Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling."); Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling.");
} }
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
} }
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_ResetFootsteps = EntryResetFootstepsOnIdle.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));
DesktopVRIKSystem.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
DesktopVRIKSystem.Instance.Setting_IKLerpSpeed = Mathf.Clamp(EntryIKLerpSpeed.Value, 0f, 20f);
// Calibration Settings
DesktopVRIKSystem.Instance.Setting_UseVRIKToes = EntryUseVRIKToes.Value;
// Fine-tuning Settings
DesktopVRIKSystem.Instance.Setting_ResetFootsteps = EntryResetFootstepsOnIdle.Value;
// Integration Settings
DesktopVRIKSystem.Instance.Setting_IntegrationAMT = EntryIntegrationAMT.Value && integration_AMT;
// Funny Settings
DesktopVRIKSystem.Instance.Setting_ProneThrusting = EntryProneThrusting.Value;
}
void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
void ApplyPatches(Type type) void ApplyPatches(Type type)
{ {
try try

View file

@ -26,6 +26,6 @@ using System.Reflection;
namespace NAK.DesktopVRIK.Properties; namespace NAK.DesktopVRIK.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "4.1.8"; public const string Version = "4.2.0";
public const string Author = "NotAKidoS"; public const string Author = "NotAKidoS";
} }

View file

@ -0,0 +1,28 @@
using RootMotion.FinalIK;
namespace NAK.DesktopVRIK.VRIKHelper;
// I don't think this was needed at all, but it looks fancy.
//https://github.com/knah/VRCMods/blob/master/IKTweaks/CachedSolver.cs
public struct CachedSolver
{
public readonly IKSolverVR Solver;
public readonly IKSolverVR.Spine Spine;
public readonly IKSolverVR.Leg LeftLeg;
public readonly IKSolverVR.Leg RightLeg;
public readonly IKSolverVR.Arm LeftArm;
public readonly IKSolverVR.Arm RightArm;
public readonly IKSolverVR.Locomotion Locomotion;
public CachedSolver(IKSolverVR solver)
{
Solver = solver;
Spine = solver.spine;
LeftArm = solver.leftArm;
LeftLeg = solver.leftLeg;
RightArm = solver.rightArm;
RightLeg = solver.rightLeg;
Locomotion = solver.locomotion;
}
}

View file

@ -0,0 +1,33 @@
using UnityEngine;
namespace NAK.DesktopVRIK.VRIKHelper;
public struct VRIKCalibrationData
{
public Vector3 KneeNormalLeft;
public Vector3 KneeNormalRight;
public Vector3 InitialFootPosLeft;
public Vector3 InitialFootPosRight;
public Quaternion InitialFootRotLeft;
public Quaternion InitialFootRotRight;
public float InitialHeadHeight;
public float InitialFootDistance;
public float InitialStepThreshold;
public float InitialStepHeight;
public bool FixTransformsRequired;
public void Clear()
{
KneeNormalLeft = Vector3.zero;
KneeNormalRight = Vector3.zero;
InitialFootPosLeft = Vector3.zero;
InitialFootPosRight = Vector3.zero;
InitialFootRotLeft = Quaternion.identity;
InitialFootRotRight = Quaternion.identity;
InitialHeadHeight = 0f;
InitialFootDistance = 0f;
InitialStepThreshold = 0f;
InitialStepHeight = 0f;
FixTransformsRequired = false;
}
}

View file

@ -0,0 +1,87 @@
namespace NAK.DesktopVRIK.VRIKHelper;
public class VRIKConfiguration
{
// Solver settings
public float LocomotionWeight { get; set; }
public float LocomotionAngleThreshold { get; set; }
public float LocomotionMaxLegStretch { get; set; }
public float SpineMinHeadHeight { get; set; }
public float SolverIKPositionWeight { get; set; }
public float SpineChestClampWeight { get; set; }
public float SpineMaintainPelvisPosition { get; set; }
// Body leaning settings
public float SpineBodyPosStiffness { get; set; }
public float SpineBodyRotStiffness { get; set; }
public float SpineNeckStiffness { get; set; }
// Locomotion settings
public float LocomotionVelocityFactor { get; set; }
public float LocomotionMaxVelocity { get; set; }
public float LocomotionRootSpeed { get; set; }
// Chest rotation
public float SpineRotateChestByHands { get; set; }
public float SpineHeadClampWeight { get; set; }
public float SpinePositionWeight { get; set; }
public float SpineRotationWeight { get; set; }
public float SpineMaxRootAngle { get; set; }
// BodySystem
public float SpinePelvisPositionWeight { get; set; }
public float LeftArmPositionWeight { get; set; }
public float LeftArmRotationWeight { get; set; }
public float RightArmPositionWeight { get; set; }
public float RightArmRotationWeight { get; set; }
public float LeftLegPositionWeight { get; set; }
public float LeftLegRotationWeight { get; set; }
public float RightLegPositionWeight { get; set; }
public float RightLegRotationWeight { get; set; }
}
public static class VRIKConfigurator
{
public static void ApplyVRIKConfiguration(CachedSolver cachedSolver, VRIKConfiguration config)
{
cachedSolver.Solver.IKPositionWeight = config.SolverIKPositionWeight;
cachedSolver.Locomotion.weight = config.LocomotionWeight;
cachedSolver.Locomotion.angleThreshold = config.LocomotionAngleThreshold;
cachedSolver.Locomotion.maxLegStretch = config.LocomotionMaxLegStretch;
cachedSolver.Spine.chestClampWeight = config.SpineChestClampWeight;
cachedSolver.Spine.maintainPelvisPosition = config.SpineMaintainPelvisPosition;
cachedSolver.Spine.minHeadHeight = config.SpineMinHeadHeight;
cachedSolver.Spine.bodyPosStiffness = config.SpineBodyPosStiffness;
cachedSolver.Spine.bodyRotStiffness = config.SpineBodyRotStiffness;
cachedSolver.Spine.neckStiffness = config.SpineNeckStiffness;
cachedSolver.Locomotion.velocityFactor = config.LocomotionVelocityFactor;
cachedSolver.Locomotion.maxVelocity = config.LocomotionMaxVelocity;
cachedSolver.Locomotion.rootSpeed = config.LocomotionRootSpeed;
cachedSolver.Spine.rotateChestByHands = config.SpineRotateChestByHands;
cachedSolver.Spine.headClampWeight = config.SpineHeadClampWeight;
cachedSolver.Spine.positionWeight = config.SpinePositionWeight;
cachedSolver.Spine.rotationWeight = config.SpineRotationWeight;
cachedSolver.Spine.maxRootAngle = config.SpineMaxRootAngle;
cachedSolver.Spine.maintainPelvisPosition = config.SpineMaintainPelvisPosition;
cachedSolver.Spine.pelvisPositionWeight = config.SpinePelvisPositionWeight;
cachedSolver.LeftArm.positionWeight = config.LeftArmPositionWeight;
cachedSolver.LeftArm.rotationWeight = config.LeftArmRotationWeight;
cachedSolver.RightArm.positionWeight = config.RightArmPositionWeight;
cachedSolver.RightArm.rotationWeight = config.RightArmRotationWeight;
cachedSolver.LeftLeg.positionWeight = config.LeftLegPositionWeight;
cachedSolver.LeftLeg.rotationWeight = config.LeftLegRotationWeight;
cachedSolver.RightLeg.positionWeight = config.RightLegPositionWeight;
cachedSolver.RightLeg.rotationWeight = config.RightLegRotationWeight;
}
}

View file

@ -0,0 +1,53 @@
namespace NAK.DesktopVRIK.VRIKHelper;
public static class VRIKConfigurations
{
public static VRIKConfiguration DesktopVRIKConfiguration()
{
return new VRIKConfiguration
{
// Solver settings
LocomotionWeight = 0f,
LocomotionAngleThreshold = 30f,
LocomotionMaxLegStretch = 1f,
SpineMinHeadHeight = 0f,
SolverIKPositionWeight = 1f,
SpineChestClampWeight = 0f,
SpineMaintainPelvisPosition = 1f,
// Body leaning settings
SpineBodyPosStiffness = 1f,
SpineBodyRotStiffness = 0.2f,
SpineNeckStiffness = 0.0001f, //hack
// Locomotion settings
LocomotionVelocityFactor = 0f,
LocomotionMaxVelocity = 0f,
LocomotionRootSpeed = 1000f,
// Chest rotation
SpineRotateChestByHands = 0f, //pam, bid, leap motion change
// LookAtIK priority
SpineHeadClampWeight = 0.2f,
// Tippytoes
SpinePositionWeight = 0f,
SpineRotationWeight = 1f,
// Emotes
SpineMaxRootAngle = 180f,
// BodySystem
SpinePelvisPositionWeight = 0f,
LeftArmPositionWeight = 0f,
LeftArmRotationWeight = 0f,
RightArmPositionWeight = 0f,
RightArmRotationWeight = 0f,
LeftLegPositionWeight = 0f,
LeftLegRotationWeight = 0f,
RightLegPositionWeight = 0f,
RightLegRotationWeight = 0f,
};
}
}

View file

@ -1,15 +1,12 @@
using RootMotion.FinalIK; using RootMotion.FinalIK;
using UnityEngine; using UnityEngine;
namespace NAK.DesktopVRIK; namespace NAK.DesktopVRIK.VRIKHelper;
public static class VRIKUtils public static class VRIKUtils
{ {
public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes) public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes)
{ {
//might not work over netik
//FixChestAndSpineReferences(vrik);
if (!useVRIKToes) if (!useVRIKToes)
{ {
vrik.references.leftToes = null; vrik.references.leftToes = null;
@ -20,36 +17,22 @@ public static class VRIKUtils
FixFingerBonesError(vrik); 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 FixFingerBonesError(VRIK vrik) private static void FixFingerBonesError(VRIK vrik)
{ {
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;
}
}
FixFingerBones(vrik, vrik.references.leftHand, vrik.solver.leftArm); FixFingerBones(vrik, vrik.references.leftHand, vrik.solver.leftArm);
FixFingerBones(vrik, vrik.references.rightHand, vrik.solver.rightArm); FixFingerBones(vrik, vrik.references.rightHand, vrik.solver.rightArm);
} }
private static void FixFingerBones(VRIK vrik, Transform hand, IKSolverVR.Arm armSolver) public static void CalculateKneeBendNormals(VRIK vrik, ref VRIKCalibrationData calibrationData)
{
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 // Helper function to get position or default to Vector3.zero
Vector3 GetPositionOrDefault(Transform transform) => transform?.position ?? Vector3.zero; Vector3 GetPositionOrDefault(Transform transform) => transform?.position ?? Vector3.zero;
@ -60,7 +43,7 @@ public static class VRIKUtils
GetPositionOrDefault(vrik.references.leftCalf), GetPositionOrDefault(vrik.references.leftCalf),
GetPositionOrDefault(vrik.references.leftFoot) GetPositionOrDefault(vrik.references.leftFoot)
}; };
leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors); calibrationData.KneeNormalLeft = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors);
// Get assumed right knee normal // Get assumed right knee normal
Vector3[] rightVectors = { Vector3[] rightVectors = {
@ -68,10 +51,10 @@ public static class VRIKUtils
GetPositionOrDefault(vrik.references.rightCalf), GetPositionOrDefault(vrik.references.rightCalf),
GetPositionOrDefault(vrik.references.rightFoot) GetPositionOrDefault(vrik.references.rightFoot)
}; };
rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors); calibrationData.KneeNormalRight = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors);
} }
public static void ApplyKneeBendNormals(VRIK vrik, Vector3 leftKneeNormal, Vector3 rightKneeNormal) public static void ApplyKneeBendNormals(VRIK vrik, VRIKCalibrationData calibrationData)
{ {
// 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget // 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget
// modifying pelvis normal weight is easier math // modifying pelvis normal weight is easier math
@ -79,8 +62,8 @@ public static class VRIKUtils
vrik.solver.rightLeg.bendToTargetWeight = 0f; vrik.solver.rightLeg.bendToTargetWeight = 0f;
var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation); var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal; vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * calibrationData.KneeNormalLeft;
vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal; vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * calibrationData.KneeNormalRight;
} }
private static Vector3 GetNormalFromArray(Vector3[] positions) private static Vector3 GetNormalFromArray(Vector3[] positions)
@ -102,17 +85,19 @@ public static class VRIKUtils
return normal.normalized; return normal.normalized;
} }
public static void CalculateInitialIKScaling(VRIK vrik, out float initialFootDistance, out float initialStepThreshold, out float initialStepHeight) public static void CalculateInitialIKScaling(VRIK vrik, ref VRIKCalibrationData calibrationData)
{ {
// Get distance between feet and thighs // Get distance between feet and thighs
float scaleModifier = Mathf.Max(1f, vrik.references.pelvis.lossyScale.x); float scaleModifier = Mathf.Max(1f, vrik.references.pelvis.lossyScale.x);
float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position); float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position);
initialFootDistance = footDistance * 0.5f;
initialStepThreshold = footDistance * scaleModifier; calibrationData.InitialFootDistance = footDistance * 0.5f;
initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; calibrationData.InitialStepThreshold = footDistance * scaleModifier;
calibrationData.InitialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
calibrationData.InitialHeadHeight = Mathf.Abs(vrik.references.head.position.y - vrik.references.rightFoot.position.y);
} }
public static void CalculateInitialFootsteps(VRIK vrik, out Vector3 initialFootPosLeft, out Vector3 initialFootPosRight, out Quaternion initialFootRotLeft, out Quaternion initialFootRotRight) public static void CalculateInitialFootsteps(VRIK vrik, ref VRIKCalibrationData calibrationData)
{ {
Transform root = vrik.references.root; Transform root = vrik.references.root;
Transform leftFoot = vrik.references.leftFoot; Transform leftFoot = vrik.references.leftFoot;
@ -122,13 +107,13 @@ public static class VRIKUtils
Quaternion rootWorldRot = root.rotation; Quaternion rootWorldRot = root.rotation;
// Calculate the world rotation of the left and right feet relative to the root bone // Calculate the world rotation of the left and right feet relative to the root bone
initialFootPosLeft = root.InverseTransformPoint(leftFoot.position); calibrationData.InitialFootPosLeft = root.parent.InverseTransformPoint(leftFoot.position);
initialFootPosRight = root.InverseTransformPoint(rightFoot.position); calibrationData.InitialFootPosRight = root.parent.InverseTransformPoint(rightFoot.position);
initialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation; calibrationData.InitialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation;
initialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation; calibrationData.InitialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation;
} }
public static void SetFootsteps(VRIK vrik, Vector3 footPosLeft, Vector3 footPosRight, Quaternion footRotLeft, Quaternion footRotRight) public static void ResetToInitialFootsteps(VRIK vrik, VRIKCalibrationData calibrationData, float scaleModifier)
{ {
var locomotionSolver = vrik.solver.locomotion; var locomotionSolver = vrik.solver.locomotion;
@ -138,9 +123,10 @@ public static class VRIKUtils
var root = vrik.references.root; var root = vrik.references.root;
var rootWorldRot = vrik.references.root.rotation; var rootWorldRot = vrik.references.root.rotation;
// hack, use parent transform instead as setting feet position moves root // hack, use parent transform instead as setting feet position moves root
footstepLeft.Reset(rootWorldRot, root.parent.TransformPoint(footPosLeft), rootWorldRot * footRotLeft); footstepLeft.Reset(rootWorldRot, root.parent.TransformPoint(calibrationData.InitialFootPosLeft * scaleModifier), rootWorldRot * calibrationData.InitialFootRotLeft);
footstepRight.Reset(rootWorldRot, root.parent.TransformPoint(footPosRight), rootWorldRot * footRotRight); footstepRight.Reset(rootWorldRot, root.parent.TransformPoint(calibrationData.InitialFootPosRight * scaleModifier), rootWorldRot * calibrationData.InitialFootRotRight);
} }
public static void SetupHeadIKTarget(VRIK vrik) public static void SetupHeadIKTarget(VRIK vrik)
@ -155,15 +141,15 @@ public static class VRIKUtils
vrik.solver.spine.headTarget.localRotation = Quaternion.identity; vrik.solver.spine.headTarget.localRotation = Quaternion.identity;
} }
public static void ApplyScaleToVRIK(VRIK vrik, float footDistance, float stepThreshold, float stepHeight, float modifier) public static void ApplyScaleToVRIK(VRIK vrik, VRIKCalibrationData calibrationData, float scaleModifier)
{ {
var locomotionSolver = vrik.solver.locomotion; var locomotionSolver = vrik.solver.locomotion;
locomotionSolver.footDistance = footDistance * modifier; locomotionSolver.footDistance = calibrationData.InitialFootDistance * scaleModifier;
locomotionSolver.stepThreshold = stepThreshold * modifier; locomotionSolver.stepThreshold = calibrationData.InitialStepThreshold * scaleModifier;
ScaleStepHeight(locomotionSolver.stepHeight, stepHeight * modifier); ScaleStepHeight(locomotionSolver.stepHeight, calibrationData.InitialStepHeight * scaleModifier);
} }
private static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag) static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag)
{ {
Keyframe[] keyframes = stepHeightCurve.keys; Keyframe[] keyframes = stepHeightCurve.keys;
keyframes[1].value = mag; keyframes[1].value = mag;