diff --git a/DesktopVRIK/DesktopVRIK - Backup.csproj b/DesktopVRIK/DesktopVRIK - Backup.csproj
new file mode 100644
index 0000000..9daf839
--- /dev/null
+++ b/DesktopVRIK/DesktopVRIK - Backup.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+ netstandard1.0
+
+
+
+
+ $(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll
+ False
+
+
+
+
\ No newline at end of file
diff --git a/DesktopVRIK/DesktopVRIK.csproj b/DesktopVRIK/DesktopVRIK.csproj
index e94f9dc..3d7c8cf 100644
--- a/DesktopVRIK/DesktopVRIK.csproj
+++ b/DesktopVRIK/DesktopVRIK.csproj
@@ -1,2 +1,23 @@
-
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
+
+
+
+
+
+ $(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll
+ False
+
+
+
+
\ No newline at end of file
diff --git a/DesktopVRIK/DesktopVRIKCalibrator.cs b/DesktopVRIK/DesktopVRIKCalibrator.cs
deleted file mode 100644
index 525e609..0000000
--- a/DesktopVRIK/DesktopVRIKCalibrator.cs
+++ /dev/null
@@ -1,379 +0,0 @@
-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 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
- HumanPoseHandler _humanPoseHandler;
- HumanPose _humanPose;
- HumanPose _humanPoseInitial;
-
- // Animator Info
- int _animLocomotionLayer = -1;
- int _animIKPoseLayer = -1;
-
- internal DesktopVRIKCalibrator()
- {
- ikSystem = DesktopVRIKSystem.Instance;
- BoneExists = new Dictionary();
- }
-
- 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();
-
- // 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();
- ikSystem.avatarVRIK.AutoDetectReferences();
-
- // Why do I love to overcomplicate things?
- VRIKUtils.ConfigureVRIKReferences(ikSystem.avatarVRIK, DesktopVRIK.EntryUseVRIKToes.Value);
-
- // Fix animator issue
- ikSystem.avatarVRIK.fixTransforms = ikSystem.calibrationData.FixTransformsRequired;
-
- CachedSolver solver = new CachedSolver(ikSystem.avatarVRIK.solver);
-
- // Default solver settings
- solver.Locomotion.weight = 0f;
- solver.Locomotion.angleThreshold = 30f;
- solver.Locomotion.maxLegStretch = 1f;
- solver.Spine.minHeadHeight = 0f;
- solver.Spine.chestClampWeight = 0f;
- solver.Spine.maintainPelvisPosition = 0f;
- solver.Solver.IKPositionWeight = 1f;
-
- // Body leaning settings
- solver.Spine.bodyPosStiffness = 1f;
- solver.Spine.bodyRotStiffness = 0.2f;
- // this is a hack, allows chest to rotate slightly
- // independent from hip rotation. Funny Spine.Solve()->Bend()
- solver.Spine.neckStiffness = 0.0001f;
-
- // Disable locomotion
- // Setting velocity to 0 aleviated nameplate jitter issue on remote
- solver.Locomotion.velocityFactor = 0f;
- solver.Locomotion.maxVelocity = 0f;
- solver.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
- solver.Spine.rotateChestByHands = 0f;
-
- // Prioritize LookAtIK
- solver.Spine.headClampWeight = 0.2f;
-
- // Disable going on tippytoes
- solver.Spine.positionWeight = 0f;
- solver.Spine.rotationWeight = 1f;
-
- // Set so emotes play properly
- solver.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
- solver.Spine.maintainPelvisPosition = 1f;
- solver.Spine.positionWeight = 0f;
- solver.Spine.pelvisPositionWeight = 0f;
- solver.LeftArm.positionWeight = 0f;
- solver.LeftArm.rotationWeight = 0f;
- solver.RightArm.positionWeight = 0f;
- solver.RightArm.rotationWeight = 0f;
- solver.LeftLeg.positionWeight = 0f;
- solver.LeftLeg.rotationWeight = 0f;
- solver.RightLeg.positionWeight = 0f;
- solver.RightLeg.rotationWeight = 0f;
-
- // This is now our master Locomotion weight
- solver.Locomotion.weight = 1f;
- solver.Solver.IKPositionWeight = 1f;
-
- ikSystem.cachedSolver = solver;
- }
-
- 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;
- }
- }
-}
diff --git a/DesktopVRIK/DesktopVRIKSystem.cs b/DesktopVRIK/DesktopVRIKSystem.cs
deleted file mode 100644
index 68be22d..0000000
--- a/DesktopVRIK/DesktopVRIKSystem.cs
+++ /dev/null
@@ -1,338 +0,0 @@
-using ABI.CCK.Components;
-using ABI_RC.Core.Player;
-using ABI_RC.Systems.IK.SubSystems;
-using ABI_RC.Systems.MovementSystem;
-using NAK.DesktopVRIK.VRIKHelper;
-using RootMotion.FinalIK;
-using UnityEngine;
-
-namespace NAK.DesktopVRIK;
-
-internal class DesktopVRIKSystem : MonoBehaviour
-{
- public static DesktopVRIKSystem Instance;
- public static DesktopVRIKCalibrator Calibrator;
-
- // VRIK Calibration Info
- public VRIKCalibrationData calibrationData;
-
- // Avatar Components
- public VRIK avatarVRIK = null;
- public LookAtIK avatarLookAtIK = null;
- public Transform avatarTransform = null;
- public CachedSolver cachedSolver;
-
- // ChilloutVR Player Components
- PlayerSetup playerSetup;
- MovementSystem movementSystem;
-
- // Player Info
- Transform _cameraTransform;
- bool _ikEmotePlaying;
- float _ikWeightLerp = 1f;
- float _ikSimulatedRootAngle = 0f;
- float _locomotionWeight = 1f;
- float _scaleDifference = 1f;
-
- // Last Movement Parent Info
- Vector3 _movementPosition;
- Quaternion _movementRotation;
- CVRMovementParent _movementParent;
-
- void Start()
- {
- Instance = this;
- Calibrator = new DesktopVRIKCalibrator();
-
- playerSetup = GetComponent();
- movementSystem = GetComponent();
-
- _cameraTransform = playerSetup.desktopCamera.transform;
- }
-
- void Update()
- {
- if (avatarVRIK == null) return;
-
- HandleLocomotionTracking();
- UpdateLocomotionWeight();
- ApplyBodySystemWeights();
- ResetAvatarLocalPosition();
- }
-
- void HandleLocomotionTracking()
- {
- bool shouldTrackLocomotion = ShouldTrackLocomotion();
-
- if (shouldTrackLocomotion != BodySystem.TrackingLocomotionEnabled)
- {
- BodySystem.TrackingLocomotionEnabled = shouldTrackLocomotion;
- IKResetSolver();
- ResetDesktopVRIK();
- if (shouldTrackLocomotion) IKResetFootsteps();
- }
- }
-
- bool ShouldTrackLocomotion()
- {
- bool isMoving = movementSystem.movementVector.magnitude > 0f;
- bool isGrounded = movementSystem._isGrounded;
- bool isCrouching = movementSystem.crouching;
- bool isProne = movementSystem.prone;
- bool isFlying = movementSystem.flying;
- bool isSitting = movementSystem.sitting;
- bool isStanding = IsStanding();
-
- return !(isMoving || isCrouching || isProne || isFlying || isSitting || !isGrounded || !isStanding);
- }
-
- bool IsStanding()
- {
- // Let AMT handle it if available
- if (DesktopVRIK.EntryIntegrationAMT.Value) return true;
-
- // Get Upright value
- Vector3 delta = cachedSolver.Spine.headPosition - avatarTransform.position;
- Vector3 deltaRotated = Quaternion.Euler(0, avatarTransform.rotation.eulerAngles.y, 0) * delta;
- float upright = Mathf.InverseLerp(0f, calibrationData.InitialHeadHeight * _scaleDifference, deltaRotated.y);
- return upright > 0.85f;
- }
-
- void UpdateLocomotionWeight()
- {
- float targetWeight = BodySystem.TrackingEnabled && BodySystem.TrackingLocomotionEnabled ? 1.0f : 0.0f;
- if (DesktopVRIK.EntryIKLerpSpeed.Value > 0)
- {
- _ikWeightLerp = Mathf.Lerp(_ikWeightLerp, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value);
- _locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value * 2f);
- return;
- }
- _ikWeightLerp = targetWeight;
- _locomotionWeight = targetWeight;
- }
-
- void ApplyBodySystemWeights()
- {
- void SetArmWeight(IKSolverVR.Arm arm, bool isTracked)
- {
- arm.positionWeight = isTracked ? 1f : 0f;
- arm.rotationWeight = isTracked ? 1f : 0f;
- arm.shoulderRotationWeight = isTracked ? 1f : 0f;
- arm.shoulderTwistWeight = isTracked ? 1f : 0f;
- }
- void SetLegWeight(IKSolverVR.Leg leg, bool isTracked)
- {
- leg.positionWeight = isTracked ? 1f : 0f;
- leg.rotationWeight = isTracked ? 1f : 0f;
- }
-
- if (BodySystem.TrackingEnabled)
- {
- avatarVRIK.enabled = true;
- cachedSolver.Solver.IKPositionWeight = BodySystem.TrackingPositionWeight;
- cachedSolver.Locomotion.weight = _locomotionWeight;
-
- bool useAnimatedBendNormal = _locomotionWeight <= 0.5f;
- cachedSolver.LeftLeg.useAnimatedBendNormal = useAnimatedBendNormal;
- cachedSolver.RightLeg.useAnimatedBendNormal = useAnimatedBendNormal;
- SetArmWeight(cachedSolver.LeftArm, BodySystem.TrackingLeftArmEnabled && cachedSolver.LeftArm.target != null);
- SetArmWeight(cachedSolver.RightArm, BodySystem.TrackingRightArmEnabled && cachedSolver.RightArm.target != null);
- SetLegWeight(cachedSolver.LeftLeg, BodySystem.TrackingLeftLegEnabled && cachedSolver.LeftLeg.target != null);
- SetLegWeight(cachedSolver.RightLeg, BodySystem.TrackingRightLegEnabled && cachedSolver.RightLeg.target != null);
- }
- else
- {
- avatarVRIK.enabled = false;
- cachedSolver.Solver.IKPositionWeight = 0f;
- cachedSolver.Locomotion.weight = 0f;
-
- cachedSolver.LeftLeg.useAnimatedBendNormal = false;
- cachedSolver.RightLeg.useAnimatedBendNormal = false;
- SetArmWeight(cachedSolver.LeftArm, false);
- SetArmWeight(cachedSolver.RightArm, false);
- SetLegWeight(cachedSolver.LeftLeg, false);
- SetLegWeight(cachedSolver.RightLeg, false);
- }
- }
-
- void ResetBodySystem()
- {
- // DesktopVRSwitch should handle this, but I am not pushing an update yet.
- BodySystem.TrackingEnabled = true;
- BodySystem.TrackingPositionWeight = 1f;
- BodySystem.isCalibratedAsFullBody = false;
- BodySystem.isCalibrating = false;
- BodySystem.isRecalibration = false;
- }
-
- void ResetAvatarLocalPosition()
- {
- // Reset avatar offset
- avatarTransform.localPosition = Vector3.zero;
- avatarTransform.localRotation = Quaternion.identity;
- }
-
- public void OnSetupAvatarDesktop(Animator animator)
- {
- if (!DesktopVRIK.EntryEnabled.Value) return;
-
- // only run for humanoid avatars
- if (animator != null && animator.avatar != null && animator.avatar.isHuman)
- {
- Calibrator.CalibrateDesktopVRIK(animator);
- ResetBodySystem();
- ResetDesktopVRIK();
- }
- }
-
- public void OnSetupIKScaling(float scaleDifference)
- {
- _scaleDifference = scaleDifference;
-
- VRIKUtils.ApplyScaleToVRIK
- (
- avatarVRIK,
- calibrationData,
- _scaleDifference
- );
- }
-
- public void OnPlayerSetupUpdate(bool isEmotePlaying)
- {
- if (isEmotePlaying == _ikEmotePlaying) return;
- _ikEmotePlaying = isEmotePlaying;
-
- if (avatarLookAtIK != null)
- avatarLookAtIK.enabled = !isEmotePlaying;
-
- // Disable tracking completely while emoting
- BodySystem.TrackingEnabled = !isEmotePlaying;
- IKResetSolver();
- ResetDesktopVRIK();
- }
-
- public void OnPlayerSetupSetSitting()
- {
- IKResetSolver();
- ResetDesktopVRIK();
- }
-
- public void OnPlayerSetupResetIk()
- {
- // Check if PlayerSetup.ResetIk() was called for movement parent
- CVRMovementParent currentParent = movementSystem._currentParent;
- if (currentParent != null && currentParent._referencePoint != null)
- {
- // Get current position
- var currentPosition = currentParent._referencePoint.position;
- var currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f);
-
- // Convert to delta position (how much changed since last frame)
- var deltaPosition = currentPosition - _movementPosition;
- var deltaRotation = Quaternion.Inverse(_movementRotation) * currentRotation;
-
- // desktop pivots from playerlocal transform
- var platformPivot = transform.position;
-
- // Prevent targeting other parent position
- if (_movementParent == currentParent)
- {
- // Add platform motion to IK solver
- cachedSolver.Solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
- ResetDesktopVRIK();
- }
-
- // Store for next frame
- _movementParent = currentParent;
- _movementPosition = currentPosition;
- _movementRotation = currentRotation;
- return;
- }
-
- // if not for movementparent, reset ik
- IKResetSolver();
- IKResetFootsteps();
- ResetDesktopVRIK();
- }
-
- public void OnPreSolverUpdate()
- {
- // Set plant feet
- cachedSolver.Solver.plantFeet = DesktopVRIK.EntryPlantFeet.Value;
-
- // Apply custom VRIK solving effects
- IKBodyLeaningOffset(_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);
- }
- }
- }
-
- public void OnPostSolverUpdate()
- {
- if (!DesktopVRIK.EntryNetIKPass.Value) return;
- Calibrator.ApplyNetIKPass();
- }
-
- void IKResetSolver()
- {
- cachedSolver.Solver.Reset();
- }
-
- void IKResetFootsteps()
- {
- // Reset footsteps immediatly to initial
- if (!DesktopVRIK.EntryResetFootstepsOnIdle.Value) return;
-
- VRIKUtils.ResetToInitialFootsteps
- (
- avatarVRIK,
- calibrationData,
- _scaleDifference
- );
- }
-
- void ResetDesktopVRIK()
- {
- _ikSimulatedRootAngle = transform.eulerAngles.y;
- }
-}
\ No newline at end of file
diff --git a/DesktopVRIK/HarmonyPatches.cs b/DesktopVRIK/HarmonyPatches.cs
index c4417e4..fbfaa5f 100644
--- a/DesktopVRIK/HarmonyPatches.cs
+++ b/DesktopVRIK/HarmonyPatches.cs
@@ -1,5 +1,7 @@
-using ABI_RC.Core.Player;
+using ABI.CCK.Components;
+using ABI_RC.Core.Player;
using HarmonyLib;
+using NAK.DesktopVRIK.IK;
using UnityEngine;
/**
@@ -25,73 +27,97 @@ using UnityEngine;
namespace NAK.DesktopVRIK.HarmonyPatches;
-class PlayerSetupPatches
+internal class PlayerSetupPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
- static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
+ private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
{
- __instance.gameObject.AddComponent();
+ __instance.gameObject.AddComponent();
}
[HarmonyPostfix]
- [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatarDesktop))]
- static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator)
+ [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))]
+ private static void Postfix_PlayerSetup_SetupAvatar(GameObject inAvatar)
{
- // only intercept if DesktopVRIK is being used
- if (DesktopVRIKSystem.Instance != null)
+ if (!ModSettings.EntryEnabled.Value)
+ return;
+
+ try
{
- DesktopVRIKSystem.Instance.OnSetupAvatarDesktop(____animator);
+ IKManager.Instance?.OnAvatarInitialized(inAvatar);
+ }
+ catch (Exception e)
+ {
+ DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatar)}");
+ DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPostfix]
- [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Update))]
- static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying)
+ [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))]
+ private static void Postfix_PlayerSetup_ClearAvatar()
{
- // only intercept if DesktopVRIK is being used
- if (DesktopVRIKSystem.Instance?.avatarVRIK != null)
+ try
{
- DesktopVRIKSystem.Instance.OnPlayerSetupUpdate(____emotePlaying);
+ IKManager.Instance?.OnAvatarDestroyed();
+ }
+ catch (Exception e)
+ {
+ DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_ClearAvatar)}");
+ DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupIKScaling))]
- private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference)
+ private static void Prefix_PlayerSetup_SetupIKScaling(ref Vector3 ___scaleDifference, ref bool __runOriginal)
{
- // only intercept if DesktopVRIK is being used
- if (DesktopVRIKSystem.Instance?.avatarVRIK != null)
+ try
{
- DesktopVRIKSystem.Instance.OnSetupIKScaling(1f + ___scaleDifference.y);
- return false;
+ if (IKManager.Instance != null)
+ __runOriginal = !IKManager.Instance.OnPlayerScaled(1f + ___scaleDifference.y);
+ }
+ catch (Exception e)
+ {
+ DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_SetupIKScaling)}");
+ DesktopVRIK.Logger.Error(e);
}
-
- return true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetSitting))]
- static void Postfix_PlayerSetup_SetSitting()
+ private static void Postfix_PlayerSetup_SetSitting(ref bool ___isCurrentlyInSeat)
{
- // only intercept if DesktopVRIK is being used
- if (DesktopVRIKSystem.Instance?.avatarVRIK != null)
+ try
{
- DesktopVRIKSystem.Instance.OnPlayerSetupSetSitting();
+ IKManager.Instance?.OnPlayerSeatedStateChanged(___isCurrentlyInSeat);
+ }
+ catch (Exception e)
+ {
+ DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetSitting)}");
+ DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ResetIk))]
- static bool Prefix_PlayerSetup_ResetIk()
+ private static void Prefix_PlayerSetup_ResetIk(ref PlayerSetup __instance, ref bool __runOriginal)
{
- // only intercept if DesktopVRIK is being used
- if (DesktopVRIKSystem.Instance?.avatarVRIK != null)
+ try
{
- DesktopVRIKSystem.Instance.OnPlayerSetupResetIk();
- return false;
- }
+ if (IKManager.Instance == null)
+ return;
- return true;
+ CVRMovementParent currentParent = __instance._movementSystem._currentParent;
+ __runOriginal = currentParent?._referencePoint != null
+ ? IKManager.Instance.OnPlayerHandleMovementParent(currentParent)
+ : IKManager.Instance.OnPlayerTeleported();
+ }
+ catch (Exception e)
+ {
+ DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_ResetIk)}");
+ DesktopVRIK.Logger.Error(e);
+ }
}
}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/IKCalibrator.cs b/DesktopVRIK/IK/IKCalibrator.cs
new file mode 100644
index 0000000..7e7c469
--- /dev/null
+++ b/DesktopVRIK/IK/IKCalibrator.cs
@@ -0,0 +1,229 @@
+using RootMotion.FinalIK;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace NAK.DesktopVRIK.IK;
+
+internal static class IKCalibrator
+{
+ #region VRIK Solver Setup
+
+ public static VRIK SetupVrIk(Animator animator)
+ {
+ if (animator.gameObject.TryGetComponent(out VRIK vrik))
+ Object.DestroyImmediate(vrik);
+
+ vrik = animator.gameObject.AddComponent();
+ vrik.AutoDetectReferences();
+
+ if (!ModSettings.EntryUseToesForVRIK.Value)
+ {
+ vrik.references.leftToes = null;
+ vrik.references.rightToes = null;
+ }
+
+ vrik.solver.SetToReferences(vrik.references);
+
+ GuessWristPalmAxis(vrik.references.leftHand, vrik.references.leftForearm, vrik.solver.leftArm);
+ GuessWristPalmAxis(vrik.references.rightHand, vrik.references.rightForearm, vrik.solver.rightArm);
+
+ SafePalmToThumbAxis(vrik.references.leftHand, vrik.references.leftForearm, vrik.solver.leftArm,
+ animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal));
+ SafePalmToThumbAxis(vrik.references.rightHand, vrik.references.rightForearm, vrik.solver.rightArm,
+ animator.GetBoneTransform(HumanBodyBones.RightThumbProximal));
+
+ AddTwistRelaxer(vrik.references.leftForearm, vrik, vrik.references.leftHand);
+ AddTwistRelaxer(vrik.references.rightForearm, vrik, vrik.references.rightHand);
+
+ //vrik.solver.leftArm.shoulderRotationMode = (IKSolverVR.Arm.ShoulderRotationMode)IkTweaksSettings.ShoulderMode;
+ //vrik.solver.rightArm.shoulderRotationMode = (IKSolverVR.Arm.ShoulderRotationMode)IkTweaksSettings.ShoulderMode;
+
+ // zero all weights controlled by BodyControl
+ vrik.solver.locomotion.weight = 0f;
+ vrik.solver.IKPositionWeight = 0f;
+
+ vrik.solver.spine.pelvisPositionWeight = 0f;
+ vrik.solver.spine.pelvisRotationWeight = 0f;
+ vrik.solver.spine.positionWeight = 0f;
+ vrik.solver.spine.rotationWeight = 0f;
+
+ vrik.solver.leftLeg.positionWeight = 0f;
+ vrik.solver.leftLeg.rotationWeight = 0f;
+ vrik.solver.rightLeg.positionWeight = 0f;
+ vrik.solver.rightLeg.rotationWeight = 0f;
+ vrik.solver.leftArm.positionWeight = 0f;
+ vrik.solver.leftArm.rotationWeight = 0f;
+ vrik.solver.rightArm.positionWeight = 0f;
+ vrik.solver.rightArm.rotationWeight = 0f;
+
+ vrik.solver.leftLeg.bendGoalWeight = 0f;
+ vrik.solver.rightLeg.bendGoalWeight = 0f;
+
+ // these weights are fine
+ vrik.solver.leftArm.shoulderRotationWeight = 0.8f;
+ vrik.solver.rightArm.shoulderRotationWeight = 0.8f;
+
+ vrik.solver.leftLeg.bendToTargetWeight = 0.75f;
+ vrik.solver.rightLeg.bendToTargetWeight = 0.75f;
+
+ // hack to prevent death
+ vrik.fixTransforms = !animator.enabled;
+
+ // Avatar Motion Tweaker uses this hack!
+ vrik.solver.leftLeg.useAnimatedBendNormal = false;
+ vrik.solver.rightLeg.useAnimatedBendNormal = false;
+
+ // purposefully initiating early
+ vrik.solver.Initiate(vrik.transform);
+ vrik.solver.Reset();
+
+ return vrik;
+ }
+
+ private static void GuessWristPalmAxis(Transform hand, Transform forearm, IKSolverVR.Arm arm)
+ {
+ arm.wristToPalmAxis = VRIKCalibrator.GuessWristToPalmAxis(
+ hand,
+ forearm
+ );
+ }
+
+ private static void SafePalmToThumbAxis(Transform hand, Transform forearm, IKSolverVR.Arm arm, Transform thumbBone = null)
+ {
+ if (hand.childCount == 0)
+ {
+ arm.palmToThumbAxis = Vector3.one;
+ return;
+ }
+
+ arm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(
+ hand,
+ forearm,
+ thumbBone
+ );
+ }
+
+ private static void AddTwistRelaxer(Transform forearm, VRIK ik, Transform hand)
+ {
+ if (forearm == null) return;
+ TwistRelaxer twistRelaxer = forearm.gameObject.AddComponent();
+ twistRelaxer.ik = ik;
+ twistRelaxer.weight = 0.5f;
+ twistRelaxer.child = hand;
+ twistRelaxer.parentChildCrossfade = 0.8f;
+ }
+
+ #endregion
+
+ #region VRIK Configuration
+
+ public static void ConfigureDesktopVrIk(VRIK vrik)
+ {
+ // From DesktopVRIK
+ // https://github.com/NotAKidOnSteam/NAK_CVR_Mods/blob/fca0a32257311f044d1a9d6e68269baa4a65a45c/DesktopVRIK/DesktopVRIKCalibrator.cs#L219C2-L247C103
+
+ vrik.solver.spine.bodyPosStiffness = 1f;
+ vrik.solver.spine.bodyRotStiffness = 0.2f;
+ vrik.solver.spine.neckStiffness = 0.0001f;
+ vrik.solver.spine.rotateChestByHands = 0f;
+
+ vrik.solver.spine.minHeadHeight = 0f;
+ vrik.solver.locomotion.angleThreshold = 30f;
+ vrik.solver.locomotion.maxLegStretch = 1f;
+
+ vrik.solver.spine.chestClampWeight = 0f;
+ vrik.solver.spine.headClampWeight = 0.2f;
+
+ vrik.solver.spine.maintainPelvisPosition = 0f;
+ vrik.solver.spine.moveBodyBackWhenCrouching = 0f;
+
+ vrik.solver.locomotion.velocityFactor = 0f;
+ vrik.solver.locomotion.maxVelocity = 0f;
+ vrik.solver.locomotion.rootSpeed = 1000f;
+
+ vrik.solver.spine.positionWeight = 0f;
+ vrik.solver.spine.rotationWeight = 1f;
+
+ vrik.solver.spine.maxRootAngle = 180f;
+
+ vrik.solver.plantFeet = true;
+ }
+
+ public static void ConfigureHalfBodyVrIk(VRIK vrik)
+ {
+ // From IKTweaks
+ // https://github.com/knah/VRCMods/blob/a22bb73a5e40c75152c6e5db2a7a9afb13e42ba5/IKTweaks/FullBodyHandling.cs#L384C1-L394C71
+
+ vrik.solver.spine.bodyPosStiffness = 1f;
+ vrik.solver.spine.bodyRotStiffness = 0f;
+ vrik.solver.spine.neckStiffness = 0.5f;
+ vrik.solver.spine.rotateChestByHands = .25f;
+
+ vrik.solver.spine.minHeadHeight = -100f;
+ vrik.solver.locomotion.angleThreshold = 60f;
+ vrik.solver.locomotion.maxLegStretch = 1f;
+
+ vrik.solver.spine.chestClampWeight = 0f;
+ vrik.solver.spine.headClampWeight = 0f;
+
+ vrik.solver.spine.maintainPelvisPosition = 0f;
+ vrik.solver.spine.moveBodyBackWhenCrouching = 0f;
+
+ vrik.solver.locomotion.velocityFactor = 0.4f;
+ vrik.solver.locomotion.maxVelocity = 0.4f;
+ vrik.solver.locomotion.rootSpeed = 20f;
+
+ vrik.solver.spine.positionWeight = 1f;
+ vrik.solver.spine.rotationWeight = 1f;
+
+ vrik.solver.spine.maxRootAngle = 25f;
+
+ vrik.solver.plantFeet = false;
+ }
+
+ #endregion
+
+ #region VRIK Calibration
+
+ public static void SetupHeadIKTarget(VRIK vrik, Transform parent = null)
+ {
+ Transform existingTarget = parent?.Find("Head IK Target");
+ if (existingTarget != null)
+ Object.DestroyImmediate(existingTarget.gameObject);
+
+ parent ??= vrik.references.head;
+
+ vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
+ vrik.solver.spine.headTarget.SetParent(parent);
+ vrik.solver.spine.headTarget.localPosition = Vector3.zero;
+ vrik.solver.spine.headTarget.localRotation = CalculateLocalRotation(vrik.references.root, vrik.references.head);
+ }
+
+ public static void SetupHandIKTarget(VRIK vrik, Transform handAnchor, bool isLeft)
+ {
+ Transform parent = handAnchor.parent;
+ Transform handRef = isLeft ? vrik.references.leftHand : vrik.references.rightHand;
+
+ handAnchor.SetParent(parent);
+ handAnchor.localPosition = Vector3.zero;
+ handAnchor.localRotation = CalculateLocalRotation(vrik.references.root, handRef);
+
+ if (isLeft)
+ vrik.solver.leftArm.target = handAnchor;
+ else
+ vrik.solver.rightArm.target = handAnchor;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static Quaternion CalculateLocalRotation(Transform root, Transform reference)
+ {
+ Vector3 forward = Quaternion.Inverse(reference.rotation) * root.forward;
+ Vector3 upwards = Quaternion.Inverse(reference.rotation) * root.up;
+ return Quaternion.Inverse(reference.rotation * Quaternion.LookRotation(forward, upwards)) * reference.rotation;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/IKHandlers/IKHandler.cs b/DesktopVRIK/IK/IKHandlers/IKHandler.cs
new file mode 100644
index 0000000..8435a43
--- /dev/null
+++ b/DesktopVRIK/IK/IKHandlers/IKHandler.cs
@@ -0,0 +1,169 @@
+using ABI.CCK.Components;
+using ABI_RC.Systems.IK.SubSystems;
+using NAK.DesktopVRIK.IK.VRIKHelpers;
+using RootMotion.FinalIK;
+using UnityEngine;
+
+namespace NAK.DesktopVRIK.IK.IKHandlers;
+
+internal abstract class IKHandler
+{
+ #region Variables
+
+ internal VRIK _vrik;
+ internal IKSolverVR _solver;
+
+ // VRIK Calibration Info
+ internal VRIKLocomotionData _locomotionData;
+
+ // Last Movement Parent Info
+ internal Vector3 _movementPosition;
+ internal Quaternion _movementRotation;
+ internal CVRMovementParent _movementParent;
+
+ // Solver Info
+ internal float _scaleDifference = 1f;
+ internal float _ikWeight = 1f;
+ internal float _locomotionWeight = 1f;
+ internal float _ikSimulatedRootAngle;
+ internal bool _wasTrackingLocomotion;
+
+ #endregion
+
+ #region Virtual Game Methods
+
+ public virtual void OnInitializeIk() { }
+
+ public virtual void OnPlayerScaled(float scaleDifference)
+ {
+ VRIKUtils.ApplyScaleToVRIK
+ (
+ _vrik,
+ _locomotionData,
+ _scaleDifference = scaleDifference
+ );
+ }
+
+ public virtual void OnPlayerHandleMovementParent(CVRMovementParent currentParent, Vector3 platformPivot)
+ {
+ Vector3 currentPosition = currentParent._referencePoint.position;
+ Quaternion currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f);
+
+ Vector3 deltaPosition = currentPosition - _movementPosition;
+ Quaternion deltaRotation = Quaternion.Inverse(_movementRotation) * currentRotation;
+
+ if (_movementParent == currentParent)
+ {
+ _solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
+ _ikSimulatedRootAngle = Mathf.Repeat(_ikSimulatedRootAngle + deltaRotation.eulerAngles.y, 360f);
+ }
+
+ _movementParent = currentParent;
+ _movementPosition = currentPosition;
+ _movementRotation = currentRotation;
+ }
+
+ #endregion
+
+ #region Virtual IK Weights
+
+ public virtual void UpdateWeights()
+ {
+ Update_HeadWeight();
+
+ Update_LeftArmWeight();
+ Update_RightArmWeight();
+
+ Update_LeftLegWeight();
+ Update_RightLegWeight();
+
+ Update_PelvisWeight();
+
+ Update_LocomotionWeight();
+ ResetSolverIfNeeded();
+
+ Update_IKPositionWeight();
+ }
+
+ protected virtual void Update_HeadWeight()
+ {
+ // There is no Head tracking setting
+ _solver.spine.rotationWeight = _solver.leftArm.positionWeight =
+ GetTargetWeight(BodySystem.TrackingEnabled, _solver.spine.headTarget != null);
+ }
+
+ protected virtual void Update_LeftArmWeight()
+ {
+ _solver.leftArm.rotationWeight = _solver.leftArm.positionWeight =
+ GetTargetWeight(BodySystem.TrackingLeftArmEnabled, _solver.leftArm.target != null);
+ }
+
+ protected virtual void Update_RightArmWeight()
+ {
+ _solver.rightArm.rotationWeight = _solver.rightArm.positionWeight =
+ GetTargetWeight(BodySystem.TrackingRightArmEnabled, _solver.rightArm.target != null);
+ }
+
+ protected virtual void Update_LeftLegWeight()
+ {
+ _solver.leftLeg.rotationWeight = _solver.leftLeg.positionWeight =
+ GetTargetWeight(BodySystem.TrackingLeftLegEnabled, _solver.leftLeg.target != null);
+ }
+
+ protected virtual void Update_RightLegWeight()
+ {
+ _solver.rightLeg.rotationWeight = _solver.rightLeg.positionWeight =
+ GetTargetWeight(BodySystem.TrackingRightLegEnabled, _solver.rightLeg.target != null);
+ }
+
+ protected virtual void Update_PelvisWeight()
+ {
+ // There is no Pelvis tracking setting
+ _solver.spine.pelvisRotationWeight = _solver.spine.pelvisPositionWeight =
+ GetTargetWeight(BodySystem.TrackingEnabled, _solver.spine.pelvisTarget != null);
+ }
+
+ protected virtual void Update_LocomotionWeight()
+ {
+ _solver.locomotion.weight = _locomotionWeight = Mathf.Lerp(_locomotionWeight, BodySystem.TrackingLocomotionEnabled ? 1f : 0f,
+ Time.deltaTime * ModSettings.EntryIKLerpSpeed.Value * 2f);
+ }
+
+ protected virtual void Update_IKPositionWeight()
+ {
+ _solver.IKPositionWeight = _ikWeight = Mathf.Lerp(_ikWeight, BodySystem.TrackingEnabled ? BodySystem.TrackingPositionWeight : 0f,
+ Time.deltaTime * ModSettings.EntryIKLerpSpeed.Value);
+ }
+
+ public virtual void Reset()
+ {
+ _ikSimulatedRootAngle = _vrik.transform.eulerAngles.y;
+ if(ModSettings.EntryResetFootstepsOnIdle.Value)
+ VRIKUtils.ResetToInitialFootsteps(_vrik, _locomotionData, _scaleDifference);
+
+ _solver.Reset();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private float GetTargetWeight(bool isTracking, bool hasTarget)
+ {
+ return isTracking && hasTarget ? 1f : 0f;
+ }
+
+ private void ResetSolverIfNeeded()
+ {
+ if (_wasTrackingLocomotion == BodySystem.TrackingLocomotionEnabled)
+ return;
+
+ _wasTrackingLocomotion = BodySystem.TrackingLocomotionEnabled;
+ if (ModSettings.EntryResetFootstepsOnIdle.Value)
+ VRIKUtils.ResetToInitialFootsteps(_vrik, _locomotionData, _scaleDifference);
+
+ _solver.Reset();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs b/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs
new file mode 100644
index 0000000..09ee809
--- /dev/null
+++ b/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs
@@ -0,0 +1,115 @@
+using ABI_RC.Core.Player;
+using ABI_RC.Systems.IK.SubSystems;
+using ABI_RC.Systems.MovementSystem;
+using RootMotion.FinalIK;
+using UnityEngine;
+
+namespace NAK.DesktopVRIK.IK.IKHandlers;
+
+internal class IKHandlerDesktop : IKHandler
+{
+ public IKHandlerDesktop(VRIK vrik)
+ {
+ _vrik = vrik;
+ _solver = vrik.solver;
+ }
+
+ #region Game Overrides
+
+ public override void OnInitializeIk()
+ {
+ _vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktop);
+ }
+
+ #endregion
+
+ #region Weight Overrides
+
+ public override void UpdateWeights()
+ {
+ // Reset avatar local position
+ _vrik.transform.localPosition = Vector3.zero;
+ _vrik.transform.localRotation = Quaternion.identity;
+
+ UpdateBodySystemTracking();
+
+ base.UpdateWeights();
+ }
+
+ #endregion
+
+ #region VRIK Solver Events
+
+ private void OnPreSolverUpdateDesktop()
+ {
+ _solver.plantFeet = ModSettings.EntryPlantFeet.Value;
+
+ // Emulate old VRChat hip movement
+ if (ModSettings.EntryBodyLeanWeight.Value > 0)
+ {
+ float weightedAngle = ModSettings.EntryProneThrusting.Value ? 1f : ModSettings.EntryBodyLeanWeight.Value * _solver.locomotion.weight;
+ float angle = IKManager.Instance._desktopCamera.localEulerAngles.x;
+ angle = angle > 180 ? angle - 360 : angle;
+ Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, _vrik.transform.right);
+ _solver.spine.headRotationOffset *= rotation;
+ }
+
+ // Make root heading follow within a set limit
+ if (ModSettings.EntryBodyHeadingLimit.Value > 0)
+ {
+ float weightedAngleLimit = ModSettings.EntryBodyHeadingLimit.Value * _solver.locomotion.weight;
+ float deltaAngleRoot = Mathf.DeltaAngle(IKManager.Instance.transform.eulerAngles.y, _ikSimulatedRootAngle);
+ float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
+
+ if (absDeltaAngleRoot > weightedAngleLimit)
+ {
+ deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
+ _ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, IKManager.Instance.transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
+ }
+
+ _solver.spine.rootHeadingOffset = deltaAngleRoot;
+
+ if (ModSettings.EntryPelvisHeadingWeight.Value > 0)
+ {
+ _solver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f);
+ _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f);
+ }
+
+ if (ModSettings.EntryChestHeadingWeight.Value > 0)
+ {
+ _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryChestHeadingWeight.Value, 0f);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void UpdateBodySystemTracking()
+ {
+ BodySystem.TrackingEnabled = ShouldTrackAll();
+ BodySystem.TrackingLocomotionEnabled = ShouldTrackLocomotion();
+ }
+
+ private bool ShouldTrackAll()
+ {
+ return !PlayerSetup.Instance._emotePlaying;
+ }
+
+ private bool ShouldTrackLocomotion()
+ {
+ bool isMoving = MovementSystem.Instance.movementVector.magnitude > 0f;
+ bool isGrounded = MovementSystem.Instance._isGrounded;
+ bool isCrouching = MovementSystem.Instance.crouching;
+ bool isProne = MovementSystem.Instance.prone;
+ bool isFlying = MovementSystem.Instance.flying;
+ bool isSitting = MovementSystem.Instance.sitting;
+ bool isStanding = PlayerSetup.Instance.avatarUpright >=
+ Mathf.Max(PlayerSetup.Instance.avatarProneLimit, PlayerSetup.Instance.avatarCrouchLimit);
+
+ return !(isMoving || isCrouching || isProne || isFlying || isSitting || !isGrounded || !isStanding);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/IKManager.cs b/DesktopVRIK/IK/IKManager.cs
new file mode 100644
index 0000000..f37e87d
--- /dev/null
+++ b/DesktopVRIK/IK/IKManager.cs
@@ -0,0 +1,413 @@
+using ABI.CCK.Components;
+using ABI_RC.Core.Player;
+using ABI_RC.Core.Savior;
+using NAK.DesktopVRIK.IK.IKHandlers;
+using NAK.DesktopVRIK.IK.VRIKHelpers;
+using RootMotion.FinalIK;
+using UnityEngine;
+
+namespace NAK.DesktopVRIK.IK;
+
+public class IKManager : MonoBehaviour
+{
+ public static IKManager Instance;
+
+ #region Variables
+
+ private static VRIK _vrik;
+ private static IKSolverVR _solver;
+
+ private bool _isAvatarInitialized;
+
+ // IK Handling
+ private IKHandler _ikHandler;
+
+ // Player Info
+ internal Transform _desktopCamera;
+ internal Transform _vrCamera;
+
+ // Avatar Info
+ private Animator _animator;
+ private Transform _hipTransform;
+
+ // Animator Info
+ private int _animLocomotionLayer = -1;
+ private int _animIKPoseLayer = -1;
+ private const string _locomotionLayerName = "Locomotion/Emotes";
+ private const string _ikposeLayerName = "IKPose";
+
+ // Pose Info
+ private HumanPoseHandler _humanPoseHandler;
+ private HumanPose _humanPose;
+ private HumanPose _humanPoseInitial;
+
+ #endregion
+
+ #region Unity Methods
+
+ private void Start()
+ {
+ if (Instance != null)
+ {
+ Destroy(this);
+ return;
+ }
+ Instance = this;
+
+ _desktopCamera = PlayerSetup.Instance.desktopCamera.transform;
+ _vrCamera = PlayerSetup.Instance.vrCamera.transform;
+ }
+
+ private void Update()
+ {
+ if (!_isAvatarInitialized)
+ return;
+
+ _ikHandler?.UpdateWeights();
+ }
+
+ #endregion
+
+ #region Avatar Events
+
+ public void OnAvatarInitialized(GameObject inAvatar)
+ {
+ if (MetaPort.Instance.isUsingVr)
+ return;
+
+ if (_isAvatarInitialized)
+ return;
+
+ if (!inAvatar.TryGetComponent(out _animator))
+ return;
+
+ if (_animator.avatar == null || !_animator.avatar.isHuman)
+ return;
+
+ _animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
+
+ _animIKPoseLayer = _animator.GetLayerIndex(_ikposeLayerName);
+ _animLocomotionLayer = _animator.GetLayerIndex(_locomotionLayerName);
+
+ _hipTransform = _animator.GetBoneTransform(HumanBodyBones.Hips);
+
+ _humanPoseHandler?.Dispose();
+ _humanPoseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform);
+
+ _humanPoseHandler.GetHumanPose(ref _humanPose);
+ _humanPoseHandler.GetHumanPose(ref _humanPoseInitial);
+
+ InitializeDesktopIk();
+
+ _isAvatarInitialized = true;
+ }
+
+ public void OnAvatarDestroyed()
+ {
+ if (!_isAvatarInitialized)
+ return;
+
+ _vrik = null;
+ _solver = null;
+ _animator = null;
+ _animIKPoseLayer = -1;
+ _animLocomotionLayer = -1;
+ _hipTransform = null;
+ _humanPoseHandler?.Dispose();
+ _humanPoseHandler = null;
+ _ikHandler = null;
+
+ _isAvatarInitialized = false;
+ }
+
+ #endregion
+
+ #region Game Events
+
+ public bool OnPlayerScaled(float scaleDifference)
+ {
+ if (!_isAvatarInitialized)
+ return false;
+
+ _ikHandler?.OnPlayerScaled(scaleDifference);
+ return true;
+ }
+
+ public void OnPlayerSeatedStateChanged(bool isSitting)
+ {
+ if (!_isAvatarInitialized)
+ return;
+
+ _ikHandler?.Reset();
+ }
+
+ public bool OnPlayerHandleMovementParent(CVRMovementParent movementParent)
+ {
+ if (!_isAvatarInitialized)
+ return false;
+
+ _ikHandler?.OnPlayerHandleMovementParent(movementParent, GetPlayerPosition());
+ return true;
+ }
+
+ public bool OnPlayerTeleported()
+ {
+ if (!_isAvatarInitialized)
+ return false;
+
+ _ikHandler?.Reset();
+ return true;
+ }
+
+ #endregion
+
+ #region IK Initialization
+
+ private void InitializeDesktopIk()
+ {
+ SetupIkGeneral();
+
+ IKCalibrator.ConfigureDesktopVrIk(_vrik);
+ _ikHandler = new IKHandlerDesktop(_vrik);
+
+ IKCalibrator.SetupHeadIKTarget(_vrik);
+
+ InitializeIkGeneral();
+
+ _ikHandler.OnInitializeIk();
+ }
+
+ private void SetupIkGeneral()
+ {
+ _animator.transform.position = GetPlayerPosition();
+ _animator.transform.rotation = GetPlayerRotation();
+ SetAvatarPose(AvatarPose.Default);
+ _vrik = IKCalibrator.SetupVrIk(_animator);
+ _solver = _vrik.solver;
+ }
+
+ private void InitializeIkGeneral()
+ {
+ SetAvatarPose(AvatarPose.IKPose);
+
+ VRIKUtils.CalculateInitialIKScaling(_vrik, ref _ikHandler._locomotionData);
+ VRIKUtils.CalculateInitialFootsteps(_vrik, ref _ikHandler._locomotionData);
+ _solver.Initiate(_vrik.transform); // initiate a second time
+
+ SetAvatarPose(AvatarPose.Initial);
+
+ VRIKUtils.ApplyScaleToVRIK(_vrik, _ikHandler._locomotionData, 1f);
+ _vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateGeneral);
+ _vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdateGeneral);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public Vector3 GetPlayerPosition()
+ {
+ if (!MetaPort.Instance.isUsingVr)
+ return transform.position;
+
+ Vector3 vrPosition = _vrCamera.transform.position;
+ vrPosition.y = transform.position.y;
+ return vrPosition;
+ }
+
+ public Quaternion GetPlayerRotation()
+ {
+ if (!MetaPort.Instance.isUsingVr)
+ return transform.rotation;
+
+ Vector3 vrForward = _vrCamera.transform.forward;
+ vrForward.y = 0f;
+ return Quaternion.LookRotation(vrForward, Vector3.up);
+ }
+
+ #endregion
+
+ #region VRIK Solver Events General
+
+ private void OnPreSolverUpdateGeneral()
+ {
+ if (_solver.IKPositionWeight < 0.9f)
+ return;
+
+ Vector3 hipPos = _hipTransform.position;
+ Quaternion hipRot = _hipTransform.rotation;
+
+ _humanPoseHandler.GetHumanPose(ref _humanPose);
+
+ for (var i = 0; i < _humanPose.muscles.Length; i++)
+ {
+ //if (IkTweaksSettings.IgnoreAnimationsModeParsed == IgnoreAnimationsMode.All && IKTweaksMod.ourRandomPuck.activeInHierarchy)
+ //{
+ // muscles[i] *= ourBoneResetMasks[i] == BoneResetMask.Never ? 1 : 0;
+ // continue;
+ //}
+ switch (ourBoneResetMasks[i])
+ {
+ case BoneResetMask.Never:
+ break;
+ case BoneResetMask.Spine:
+ _humanPose.muscles[i] *= 1 - _solver.spine.pelvisPositionWeight;
+ break;
+ case BoneResetMask.LeftArm:
+ _humanPose.muscles[i] *= 1 - _solver.leftArm.positionWeight;
+ break;
+ case BoneResetMask.RightArm:
+ _humanPose.muscles[i] *= 1 - _solver.rightArm.positionWeight;
+ break;
+ case BoneResetMask.LeftLeg:
+ _humanPose.muscles[i] *= 1 - _solver.leftLeg.positionWeight;
+ break;
+ case BoneResetMask.RightLeg:
+ _humanPose.muscles[i] *= 1 - _solver.rightLeg.positionWeight;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ _humanPoseHandler.SetHumanPose(ref _humanPose);
+
+ _hipTransform.position = hipPos;
+ _hipTransform.rotation = hipRot;
+ }
+
+ // "NetIk Pass", or "Additional Humanoid Pass" hack
+ private void OnPostSolverUpdateGeneral()
+ {
+ Vector3 hipPos = _hipTransform.position;
+ Quaternion hipRot = _hipTransform.rotation;
+
+ _humanPoseHandler.GetHumanPose(ref _humanPose);
+ _humanPoseHandler.SetHumanPose(ref _humanPose);
+
+ _hipTransform.position = hipPos;
+ _hipTransform.rotation = hipRot;
+ }
+
+ #endregion
+
+ #region Avatar Pose Utilities
+
+ private enum AvatarPose
+ {
+ Default = 0,
+ Initial = 1,
+ IKPose = 2,
+ TPose = 3,
+ APose = 4
+ }
+
+ private void SetAvatarPose(AvatarPose pose)
+ {
+ switch (pose)
+ {
+ case AvatarPose.Default:
+ SetMusclesToValue(0f);
+ break;
+ case AvatarPose.Initial:
+ if (HasCustomIKPose())
+ SetCustomLayersWeights(0f, 1f);
+ _humanPoseHandler.SetHumanPose(ref _humanPoseInitial);
+ break;
+ case AvatarPose.IKPose:
+ if (HasCustomIKPose())
+ {
+ SetCustomLayersWeights(1f, 0f);
+ return;
+ }
+ SetMusclesToPose(MusclePoses.IKPoseMuscles);
+ break;
+ case AvatarPose.TPose:
+ SetMusclesToPose(MusclePoses.TPoseMuscles);
+ break;
+ case AvatarPose.APose:
+ SetMusclesToPose(MusclePoses.APoseMuscles);
+ break;
+ }
+ }
+
+ private bool HasCustomIKPose()
+ {
+ return _animLocomotionLayer != -1 && _animIKPoseLayer != -1;
+ }
+
+ private void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
+ {
+ _animator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight);
+ _animator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight);
+ _animator.Update(0f);
+ }
+
+ private void SetMusclesToValue(float value)
+ {
+ _humanPoseHandler.GetHumanPose(ref _humanPose);
+
+ for (var i = 0; i < ourBoneResetMasks.Length; i++)
+ {
+ if (ourBoneResetMasks[i] != BoneResetMask.Never)
+ _humanPose.muscles[i] = value;
+ }
+
+ _humanPose.bodyPosition = Vector3.up;
+ _humanPose.bodyRotation = Quaternion.identity;
+ _humanPoseHandler.SetHumanPose(ref _humanPose);
+ }
+
+ private void SetMusclesToPose(float[] muscles)
+ {
+ _humanPoseHandler.GetHumanPose(ref _humanPose);
+
+ for (var i = 0; i < ourBoneResetMasks.Length; i++)
+ {
+ if (ourBoneResetMasks[i] != BoneResetMask.Never)
+ _humanPose.muscles[i] = muscles[i];
+ }
+
+ _humanPose.bodyPosition = Vector3.up;
+ _humanPose.bodyRotation = Quaternion.identity;
+ _humanPoseHandler.SetHumanPose(ref _humanPose);
+ }
+
+ #endregion
+
+ #region BodyHandling
+
+ public enum BoneResetMask
+ {
+ Never,
+ Spine,
+ LeftArm,
+ RightArm,
+ LeftLeg,
+ RightLeg,
+ }
+
+ private static readonly string[] ourNeverBones = { "Index", "Thumb", "Middle", "Ring", "Little", "Jaw", "Eye" };
+ private static readonly string[] ourArmBones = { "Arm", "Forearm", "Hand", "Shoulder" };
+ private static readonly string[] ourLegBones = { "Leg", "Foot", "Toes" };
+
+ private static BoneResetMask JudgeBone(string name)
+ {
+ if (ourNeverBones.Any(name.Contains))
+ return BoneResetMask.Never;
+
+ if (ourArmBones.Any(name.Contains))
+ {
+ return name.Contains("Left") ? BoneResetMask.LeftArm : BoneResetMask.RightArm;
+ }
+
+ if (ourLegBones.Any(name.Contains))
+ return name.Contains("Left") ? BoneResetMask.LeftLeg : BoneResetMask.RightLeg;
+
+ return BoneResetMask.Spine;
+ }
+
+ internal static readonly BoneResetMask[] ourBoneResetMasks = HumanTrait.MuscleName.Select(JudgeBone).ToArray();
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/MusclePoses.cs b/DesktopVRIK/IK/MusclePoses.cs
new file mode 100644
index 0000000..50ae8fe
--- /dev/null
+++ b/DesktopVRIK/IK/MusclePoses.cs
@@ -0,0 +1,46 @@
+namespace NAK.DesktopVRIK.IK;
+
+public static class MusclePoses
+{
+ public static readonly float[] TPoseMuscles =
+ {
+ 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0.6001086f, 8.6213E-05f,
+ -0.0003308152f, 0.9999163f, -9.559652E-06f, 3.41413E-08f, -3.415095E-06f, -1.024528E-07f, 0.6001086f,
+ 8.602679E-05f, -0.0003311098f, 0.9999163f, -9.510122E-06f, 1.707468E-07f, -2.732077E-06f, 2.035554E-15f,
+ -2.748694E-07f, 2.619475E-07f, 0.401967f, 0.3005583f, 0.04102772f, 0.9998822f, -0.04634236f, 0.002522987f,
+ 0.0003842837f, -2.369134E-07f, -2.232262E-07f, 0.4019674f, 0.3005582f, 0.04103433f, 0.9998825f,
+ -0.04634996f, 0.00252335f, 0.000383302f, -1.52127f, 0.2634507f, 0.4322457f, 0.6443988f, 0.6669409f,
+ -0.4663372f, 0.8116828f, 0.8116829f, 0.6678119f, -0.6186608f, 0.8116842f, 0.8116842f, 0.6677991f,
+ -0.619225f, 0.8116842f, 0.811684f, 0.6670032f, -0.465875f, 0.811684f, 0.8116836f, -1.520098f, 0.2613016f,
+ 0.432256f, 0.6444503f, 0.6668426f, -0.4670413f, 0.8116828f, 0.8116828f, 0.6677986f, -0.6192409f, 0.8116841f,
+ 0.811684f, 0.6677839f, -0.6198869f, 0.8116839f, 0.8116838f, 0.6668782f, -0.4667901f, 0.8116842f, 0.811684f
+ };
+
+ public static readonly float[] APoseMuscles =
+ {
+ 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0.6001087f, 0f,
+ -0.0003306383f, 0.9999163f, 0f, 0f, 0f, 0f, 0.6001087f, 0f, -0.0003306384f, 0.9999163f, 0f, 0f, 0f, 0f, 0f,
+ 0f, -0.1071228f, 0.258636f, 0.1567371f, 0.9998825f, -0.0463457f, 0.002523606f, 0.0003833446f, 0f, 0f,
+ -0.1036742f, 0.2589961f, 0.1562322f, 0.9998825f, -0.04634446f, 0.002522176f, 0.0003835156f, -1.52127f,
+ 0.2634749f, 0.4322476f, 0.6443989f, 0.6669405f, -0.4663376f, 0.8116828f, 0.8116829f, 0.6678116f,
+ -0.6186616f, 0.8116839f, 0.8116837f, 0.6677991f, -0.6192248f, 0.8116839f, 0.8116842f, 0.6670038f,
+ -0.4658763f, 0.8116841f, 0.811684f, -1.520108f, 0.2612858f, 0.4322585f, 0.6444519f, 0.6668428f, -0.4670413f,
+ 0.8116831f, 0.8116828f, 0.6677985f, -0.6192364f, 0.8116842f, 0.8116842f, 0.667784f, -0.6198866f, 0.8116841f,
+ 0.8116835f, 0.6668782f, -0.4667891f, 0.8116841f, 0.811684f
+ };
+
+ public static readonly float[] IKPoseMuscles =
+ {
+ 0.00133321f, 8.195831E-06f, 8.537738E-07f, -0.002669832f, -7.651234E-06f, -0.001659694f, 0f, 0f, 0f,
+ 0.04213953f, 0.0003007996f, -0.008032114f, -0.03059979f, -0.0003182998f, 0.009640567f, 0f, 0f, 0f, 0f, 0f,
+ 0f, 0.5768794f, 0.01061097f, -0.1127839f, 0.9705755f, 0.07972051f, -0.0268422f, 0.007237188f, 0f,
+ 0.5768792f, 0.01056608f, -0.1127519f, 0.9705756f, 0.07971933f, -0.02682396f, 0.007229362f, 0f,
+ -5.651802E-06f, -3.034899E-07f, 0.4100508f, 0.3610304f, -0.0838329f, 0.9262537f, 0.1353517f, -0.03578902f,
+ 0.06005657f, -4.95989E-06f, -1.43007E-06f, 0.4096187f, 0.363263f, -0.08205152f, 0.9250782f, 0.1345718f,
+ -0.03572125f, 0.06055461f, -1.079177f, 0.2095419f, 0.6140652f, 0.6365265f, 0.6683931f, -0.4764312f,
+ 0.8099416f, 0.8099371f, 0.6658203f, -0.7327053f, 0.8113618f, 0.8114051f, 0.6643661f, -0.40341f, 0.8111364f,
+ 0.8111367f, 0.6170399f, -0.2524227f, 0.8138723f, 0.8110135f, -1.079171f, 0.2095456f, 0.6140658f, 0.6365255f,
+ 0.6683878f, -0.4764301f, 0.8099402f, 0.8099376f, 0.6658241f, -0.7327023f, 0.8113653f, 0.8113793f, 0.664364f,
+ -0.4034042f, 0.811136f, 0.8111364f, 0.6170469f, -0.2524345f, 0.8138595f, 0.8110138f
+ };
+}
\ No newline at end of file
diff --git a/DesktopVRIK/VRIKHelpers/VRIKCalibrationData.cs b/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs
similarity index 63%
rename from DesktopVRIK/VRIKHelpers/VRIKCalibrationData.cs
rename to DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs
index f06ae46..95ed9fd 100644
--- a/DesktopVRIK/VRIKHelpers/VRIKCalibrationData.cs
+++ b/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs
@@ -1,33 +1,25 @@
using UnityEngine;
-namespace NAK.DesktopVRIK.VRIKHelper;
+namespace NAK.DesktopVRIK.IK.VRIKHelpers;
-public struct VRIKCalibrationData
+public struct VRIKLocomotionData
{
- 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;
}
}
\ No newline at end of file
diff --git a/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs b/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs
new file mode 100644
index 0000000..81dc29d
--- /dev/null
+++ b/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs
@@ -0,0 +1,62 @@
+using RootMotion.FinalIK;
+using UnityEngine;
+
+namespace NAK.DesktopVRIK.IK.VRIKHelpers;
+
+public static class VRIKUtils
+{
+ public static void CalculateInitialIKScaling(VRIK vrik, ref VRIKLocomotionData locomotionData)
+ {
+ // 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);
+
+ locomotionData.InitialFootDistance = footDistance * 0.5f;
+ locomotionData.InitialStepThreshold = footDistance * scaleModifier;
+ locomotionData.InitialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
+ }
+
+ public static void CalculateInitialFootsteps(VRIK vrik, ref VRIKLocomotionData locomotionData)
+ {
+ Transform root = vrik.references.root;
+ Transform leftFoot = vrik.references.leftFoot;
+ Transform rightFoot = vrik.references.rightFoot;
+
+ // Calculate the world rotation of the root bone at the current frame
+ Quaternion rootWorldRot = root.rotation;
+
+ // Calculate the world rotation of the left and right feet relative to the root bone
+ locomotionData.InitialFootPosLeft = root.InverseTransformPoint(leftFoot.position);
+ locomotionData.InitialFootPosRight = root.InverseTransformPoint(rightFoot.position);
+ locomotionData.InitialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation;
+ locomotionData.InitialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation;
+ }
+
+ public static void ResetToInitialFootsteps(VRIK vrik, VRIKLocomotionData locomotionData, float scaleModifier)
+ {
+ Transform root = vrik.references.root;
+ Quaternion rootWorldRot = vrik.references.root.rotation;
+
+ // hack, use parent transform instead as setting feet position moves root (root.parent), but does not work for VR
+ var footsteps = vrik.solver.locomotion.footsteps;
+ footsteps[0].Reset(rootWorldRot, root.TransformPoint(locomotionData.InitialFootPosLeft * scaleModifier),
+ rootWorldRot * locomotionData.InitialFootRotLeft);
+ footsteps[1].Reset(rootWorldRot, root.TransformPoint(locomotionData.InitialFootPosRight * scaleModifier),
+ rootWorldRot * locomotionData.InitialFootRotRight);
+ }
+
+ public static void ApplyScaleToVRIK(VRIK vrik, VRIKLocomotionData locomotionData, float scaleModifier)
+ {
+ IKSolverVR.Locomotion locomotionSolver = vrik.solver.locomotion;
+ locomotionSolver.footDistance = locomotionData.InitialFootDistance * scaleModifier;
+ locomotionSolver.stepThreshold = locomotionData.InitialStepThreshold * scaleModifier;
+ ScaleStepHeight(locomotionSolver.stepHeight, locomotionData.InitialStepHeight * scaleModifier);
+ }
+
+ private static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag)
+ {
+ var keyframes = stepHeightCurve.keys;
+ keyframes[1].value = mag;
+ stepHeightCurve.keys = keyframes;
+ }
+}
\ No newline at end of file
diff --git a/DesktopVRIK/Integrations/AMTAddon.cs b/DesktopVRIK/Integrations/AMTAddon.cs
new file mode 100644
index 0000000..e4e0f6b
--- /dev/null
+++ b/DesktopVRIK/Integrations/AMTAddon.cs
@@ -0,0 +1,21 @@
+
+namespace NAK.DesktopVRIK.Integrations;
+
+public static class AMTAddon
+{
+ #region Variables
+
+ public static bool integration_AMT = false;
+
+ #endregion
+
+ #region Initialization
+
+ public static void Initialize()
+ {
+ integration_AMT = true;
+ DesktopVRIK.Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/DesktopVRIK/Integrations/BTKUIAddon.cs b/DesktopVRIK/Integrations/BTKUIAddon.cs
index d2e0b45..e3a6c4d 100644
--- a/DesktopVRIK/Integrations/BTKUIAddon.cs
+++ b/DesktopVRIK/Integrations/BTKUIAddon.cs
@@ -6,45 +6,58 @@ namespace NAK.DesktopVRIK.Integrations;
public static class BTKUIAddon
{
+ #region Initialization
+
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void Init()
+ public static void Initialize()
{
- //Add myself to the Misc Menu
-
+ // Add mod to the Misc Menu
Page miscPage = QuickMenuAPI.MiscTabPage;
- Category miscCategory = miscPage.AddCategory(DesktopVRIK.SettingsCategory);
+ Category miscCategory = miscPage.AddCategory(ModSettings.SettingsCategory);
- AddMelonToggle(ref miscCategory, DesktopVRIK.EntryEnabled);
+ AddMelonToggle(ref miscCategory, ModSettings.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";
- Category desktopVRIKCategory = desktopVRIKPage.AddCategory(DesktopVRIK.SettingsCategory);
+ SetupDesktopIKConfigurationPage(ref miscCategory);
+ }
+
+ #endregion
+
+ #region Pages Setup
+
+ private static void SetupDesktopIKConfigurationPage(ref Category parentCategory)
+ {
+ Page desktopIKPage = parentCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", ModSettings.SettingsCategory);
+ desktopIKPage.MenuTitle = "DesktopVRIK Settings";
+ Category desktopIKCategory = desktopIKPage.AddCategory(desktopIKPage.MenuTitle);
// General Settings
- AddMelonToggle(ref desktopVRIKCategory, DesktopVRIK.EntryPlantFeet);
+ AddMelonToggle(ref desktopIKCategory, ModSettings.EntryPlantFeet);
// Calibration Settings
- AddMelonToggle(ref desktopVRIKCategory, DesktopVRIK.EntryUseVRIKToes);
+ AddMelonToggle(ref desktopIKCategory, ModSettings.EntryUseToesForVRIK);
// Fine-tuning Settings
- AddMelonToggle(ref desktopVRIKCategory, DesktopVRIK.EntryResetFootstepsOnIdle);
+ AddMelonToggle(ref desktopIKCategory, ModSettings.EntryResetFootstepsOnIdle);
// Funny Settings
- AddMelonToggle(ref desktopVRIKCategory, DesktopVRIK.EntryProneThrusting);
+ AddMelonToggle(ref desktopIKCategory, ModSettings.EntryProneThrusting);
// Body Leaning Weight
- AddMelonSlider(ref desktopVRIKPage, DesktopVRIK.EntryBodyLeanWeight, 0, 1f, 1);
+ AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyLeanWeight, 0, 1f, 1);
// Max Root Heading Limit & Weights
- AddMelonSlider(ref desktopVRIKPage, DesktopVRIK.EntryBodyHeadingLimit, 0, 90f, 0);
- AddMelonSlider(ref desktopVRIKPage, DesktopVRIK.EntryPelvisHeadingWeight, 0, 1f, 1);
- AddMelonSlider(ref desktopVRIKPage, DesktopVRIK.EntryChestHeadingWeight, 0, 1f, 1);
+ AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyHeadingLimit, 0, 90f, 0);
+ AddMelonSlider(ref desktopIKPage, ModSettings.EntryPelvisHeadingWeight, 0, 1f, 1);
+ AddMelonSlider(ref desktopIKPage, ModSettings.EntryChestHeadingWeight, 0, 1f, 1);
// Lerp Speed
- AddMelonSlider(ref desktopVRIKPage, DesktopVRIK.EntryIKLerpSpeed, 0, 20f, 0);
+ AddMelonSlider(ref desktopIKPage, ModSettings.EntryIKLerpSpeed, 0, 20f, 0);
}
+ #endregion
+
+ #region Melon Pref Helpers
+
private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry entry)
{
category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
@@ -54,4 +67,6 @@ public static class BTKUIAddon
{
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
}
+
+ #endregion
}
\ No newline at end of file
diff --git a/DesktopVRIK/Main.cs b/DesktopVRIK/Main.cs
index 309ab33..41d27d3 100644
--- a/DesktopVRIK/Main.cs
+++ b/DesktopVRIK/Main.cs
@@ -5,75 +5,27 @@ namespace NAK.DesktopVRIK;
public class DesktopVRIK : MelonMod
{
internal static MelonLogger.Instance Logger;
- internal const string SettingsCategory = nameof(DesktopVRIK);
-
- public static readonly MelonPreferences_Category Category =
- MelonPreferences.CreateCategory(SettingsCategory);
-
- public static readonly MelonPreferences_Entry EntryEnabled =
- Category.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
-
- public static readonly MelonPreferences_Entry EntryPlantFeet =
- Category.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
-
- public static readonly MelonPreferences_Entry EntryResetFootstepsOnIdle =
- 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 EntryUseVRIKToes =
- 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 EntryBodyLeanWeight =
- 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 EntryBodyHeadingLimit =
- 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 EntryPelvisHeadingWeight =
- 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 EntryChestHeadingWeight =
- 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 EntryIKLerpSpeed =
- 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 EntryProneThrusting =
- Category.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take effect while crouched or prone.");
-
- public static readonly MelonPreferences_Entry 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 EntryIntegrationAMT =
- Category.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available.");
-
- public static bool integration_AMT = false;
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
- //BTKUILib Misc Tab
- if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
- {
- Logger.Msg("Initializing BTKUILib support.");
- Integrations.BTKUIAddon.Init();
- }
-
- //AvatarMotionTweaker Handling
- if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker"))
- {
- integration_AMT = true;
- Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
- }
- else
- {
- Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling.");
- }
-
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
+
+ InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
+ InitializeIntegration("AvatarMotionTweaker", Integrations.AMTAddon.Initialize);
}
- void ApplyPatches(Type type)
+ private static void InitializeIntegration(string modName, Action integrationAction)
+ {
+ if (RegisteredMelons.All(it => it.Info.Name != modName))
+ return;
+
+ Logger.Msg($"Initializing {modName} integration.");
+ integrationAction.Invoke();
+ }
+
+ private void ApplyPatches(Type type)
{
try
{
diff --git a/DesktopVRIK/ModSettings.cs b/DesktopVRIK/ModSettings.cs
new file mode 100644
index 0000000..8bb9016
--- /dev/null
+++ b/DesktopVRIK/ModSettings.cs
@@ -0,0 +1,50 @@
+using MelonLoader;
+
+namespace NAK.DesktopVRIK;
+
+public static class ModSettings
+{
+ internal const string SettingsCategory = nameof(DesktopVRIK);
+
+ public static readonly MelonPreferences_Category Category =
+ MelonPreferences.CreateCategory(SettingsCategory);
+
+ // Desktop VRIK Settings
+
+ public static readonly MelonPreferences_Entry EntryEnabled =
+ Category.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
+
+ public static readonly MelonPreferences_Entry EntryPlantFeet =
+ Category.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
+
+ public static readonly MelonPreferences_Entry EntryResetFootstepsOnIdle =
+ Category.CreateEntry("Reset Footsteps on Idle", false, description: "Determines if the Locomotion Footsteps will be reset to their calibration position when entering idle.");
+
+ public static readonly MelonPreferences_Entry EntryUseToesForVRIK =
+ 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 EntryBodyLeanWeight =
+ 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 EntryBodyHeadingLimit =
+ 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 EntryPelvisHeadingWeight =
+ 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 EntryChestHeadingWeight =
+ 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 EntryIKLerpSpeed =
+ 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 EntryProneThrusting =
+ Category.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take effect while crouched or prone.");
+
+ public static readonly MelonPreferences_Entry 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 EntryIntegrationAMT =
+ Category.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available.");
+
+}
diff --git a/DesktopVRIK/Properties/AssemblyInfo.cs b/DesktopVRIK/Properties/AssemblyInfo.cs
index c35a721..38f297a 100644
--- a/DesktopVRIK/Properties/AssemblyInfo.cs
+++ b/DesktopVRIK/Properties/AssemblyInfo.cs
@@ -20,12 +20,14 @@ using System.Reflection;
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
-[assembly: MelonOptionalDependencies("BTKUILib")]
+[assembly: MelonOptionalDependencies("BTKUILib", "AvatarMotionTweaker")]
+[assembly: MelonColor(255, 155, 89, 182)]
+[assembly: MelonAuthorColor(255, 158, 21, 32)]
[assembly: HarmonyDontPatchAll]
namespace NAK.DesktopVRIK.Properties;
internal static class AssemblyInfoParams
{
- public const string Version = "4.2.0";
+ public const string Version = "4.2.1";
public const string Author = "NotAKidoS";
}
\ No newline at end of file
diff --git a/DesktopVRIK/README.md b/DesktopVRIK/README.md
index 192da67..7f2507a 100644
--- a/DesktopVRIK/README.md
+++ b/DesktopVRIK/README.md
@@ -12,9 +12,5 @@ https://user-images.githubusercontent.com/37721153/221870123-fbe4f5e8-8d6e-4a43-
* Options to disable VRIK from using mapped toes &/or find unmapped (non-human) toe bones.
-* Autofixes for avatars without fingers & incorrect chest/spine bone mapping (might not play well with netik).
-
## Relevant Feedback Posts:
-https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars
-
-https://feedback.abinteractive.net/p/pivot-desktop-camera-with-head
+https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars
\ No newline at end of file
diff --git a/DesktopVRIK/VRIKHelpers/CachedSolver.cs b/DesktopVRIK/VRIKHelpers/CachedSolver.cs
deleted file mode 100644
index 2b651f1..0000000
--- a/DesktopVRIK/VRIKHelpers/CachedSolver.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-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;
- }
-}
\ No newline at end of file
diff --git a/DesktopVRIK/VRIKHelpers/VRIKUtils.cs b/DesktopVRIK/VRIKHelpers/VRIKUtils.cs
deleted file mode 100644
index ec3288d..0000000
--- a/DesktopVRIK/VRIKHelpers/VRIKUtils.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using RootMotion.FinalIK;
-using UnityEngine;
-
-namespace NAK.DesktopVRIK.VRIKHelper;
-
-public static class VRIKUtils
-{
- public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes)
- {
- if (!useVRIKToes)
- {
- vrik.references.leftToes = null;
- vrik.references.rightToes = null;
- }
-
- //bullshit fix to not cause death
- FixFingerBonesError(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.rightHand, vrik.solver.rightArm);
- }
-
- public static void CalculateKneeBendNormals(VRIK vrik, ref VRIKCalibrationData calibrationData)
- {
- // 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)
- };
- calibrationData.KneeNormalLeft = 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)
- };
- calibrationData.KneeNormalRight = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors);
- }
-
- public static void ApplyKneeBendNormals(VRIK vrik, VRIKCalibrationData calibrationData)
- {
- // 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget
- // modifying pelvis normal weight is easier math
- vrik.solver.leftLeg.bendToTargetWeight = 0f;
- vrik.solver.rightLeg.bendToTargetWeight = 0f;
-
- var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
- vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * calibrationData.KneeNormalLeft;
- vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * calibrationData.KneeNormalRight;
- }
-
- 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, ref VRIKCalibrationData calibrationData)
- {
- // 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);
-
- calibrationData.InitialFootDistance = footDistance * 0.5f;
- 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, ref VRIKCalibrationData calibrationData)
- {
- Transform root = vrik.references.root;
- Transform leftFoot = vrik.references.leftFoot;
- Transform rightFoot = vrik.references.rightFoot;
-
- // Calculate the world rotation of the root bone at the current frame
- Quaternion rootWorldRot = root.rotation;
-
- // Calculate the world rotation of the left and right feet relative to the root bone
- calibrationData.InitialFootPosLeft = root.parent.InverseTransformPoint(leftFoot.position);
- calibrationData.InitialFootPosRight = root.parent.InverseTransformPoint(rightFoot.position);
- calibrationData.InitialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation;
- calibrationData.InitialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation;
- }
-
- public static void ResetToInitialFootsteps(VRIK vrik, VRIKCalibrationData calibrationData, float scaleModifier)
- {
- var locomotionSolver = vrik.solver.locomotion;
-
- var footsteps = locomotionSolver.footsteps;
- var footstepLeft = footsteps[0];
- var footstepRight = footsteps[1];
-
- var root = vrik.references.root;
- var rootWorldRot = vrik.references.root.rotation;
-
- // hack, use parent transform instead as setting feet position moves root
- footstepLeft.Reset(rootWorldRot, root.parent.TransformPoint(calibrationData.InitialFootPosLeft * scaleModifier), rootWorldRot * calibrationData.InitialFootRotLeft);
- footstepRight.Reset(rootWorldRot, root.parent.TransformPoint(calibrationData.InitialFootPosRight * scaleModifier), rootWorldRot * calibrationData.InitialFootRotRight);
- }
-
- public static void SetupHeadIKTarget(VRIK vrik)
- {
- // Lazy HeadIKTarget calibration
- if (vrik.solver.spine.headTarget == null)
- {
- vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
- }
- vrik.solver.spine.headTarget.parent = vrik.references.head;
- vrik.solver.spine.headTarget.localPosition = Vector3.zero;
- vrik.solver.spine.headTarget.localRotation = Quaternion.identity;
- }
-
- public static void ApplyScaleToVRIK(VRIK vrik, VRIKCalibrationData calibrationData, float scaleModifier)
- {
- var locomotionSolver = vrik.solver.locomotion;
- locomotionSolver.footDistance = calibrationData.InitialFootDistance * scaleModifier;
- locomotionSolver.stepThreshold = calibrationData.InitialStepThreshold * scaleModifier;
- ScaleStepHeight(locomotionSolver.stepHeight, calibrationData.InitialStepHeight * scaleModifier);
- }
-
- 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 3516c95..57eba0c 100644
--- a/DesktopVRIK/format.json
+++ b/DesktopVRIK/format.json
@@ -1,8 +1,8 @@
{
"_id": 117,
"name": "DesktopVRIK",
- "modversion": "4.2.0",
- "gameversion": "2022r170p1",
+ "modversion": "4.2.1",
+ "gameversion": "2023r171",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
@@ -17,8 +17,8 @@
"requirements": [
"BTKUILib"
],
- "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r5/DesktopVRIK.dll",
+ "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r14/DesktopVRIK.dll",
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/DesktopVRIK/",
- "changelog": "- Fixed IK not being reset when teleporting/respawning/sitting.\n- Fixed IK Locomotion not being disabled while sitting.\n- Cleanup of VRIK Calibration & Configuration to make updating & debugging easier.\n- Added a temporary fix for DesktopVRSwitch BodySystem handling to ensure tracking is correctly enabled after switch.\n- Added \"NetIKPass\" after VRIK solving, which fixes many rotational errors that were only visible on the local client.\n- Removed Chest VRIK reference fix & find unmapped toe options as they didn't stream over NetIK.",
- "embedcolor": "9b59b6"
+ "changelog": "- Fixes for 2023r171.\n- Fixed ikSimulatedRootAngle being constantly reset while on a movement parent.",
+ "embedcolor": "#9b59b6"
}
\ No newline at end of file