mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 14:29:25 +00:00
[AlternateIKSystem] Initial commit - Testing
shit this out in a day, going to see how much i can do before exploding
This commit is contained in:
parent
fca0a32257
commit
e925b0cfcc
16 changed files with 1367 additions and 0 deletions
2
AlternateIKSystem/AlternateIKSystem.csproj
Normal file
2
AlternateIKSystem/AlternateIKSystem.csproj
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk" />
|
113
AlternateIKSystem/HarmonyPatches.cs
Normal file
113
AlternateIKSystem/HarmonyPatches.cs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Systems.IK;
|
||||||
|
using HarmonyLib;
|
||||||
|
using NAK.AlternateIKSystem.IK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.HarmonyPatches;
|
||||||
|
|
||||||
|
internal class PlayerSetupPatches
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||||
|
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||||
|
{
|
||||||
|
__instance.gameObject.AddComponent<IKManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))]
|
||||||
|
private static void Postfix_PlayerSetup_SetupAvatar(GameObject inAvatar)
|
||||||
|
{
|
||||||
|
if (!ModSettings.EntryEnabled.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IKManager.Instance?.OnAvatarInitialized(inAvatar);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AlternateIKSystem.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatar)}");
|
||||||
|
AlternateIKSystem.Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))]
|
||||||
|
private static void Postfix_PlayerSetup_ClearAvatar()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IKManager.Instance?.OnAvatarDestroyed();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AlternateIKSystem.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_ClearAvatar)}");
|
||||||
|
AlternateIKSystem.Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupIKScaling))]
|
||||||
|
private static void Prefix_PlayerSetup_SetupIKScaling(ref Vector3 ___scaleDifference, ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IKManager.Instance != null)
|
||||||
|
__runOriginal = !IKManager.Instance.OnPlayerScaled(1f + ___scaleDifference.y);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AlternateIKSystem.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_SetupIKScaling)}");
|
||||||
|
AlternateIKSystem.Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetSitting))]
|
||||||
|
private static void Postfix_PlayerSetup_SetSitting(ref bool ___isCurrentlyInSeat)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IKManager.Instance?.OnPlayerSeatedStateChanged(___isCurrentlyInSeat);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AlternateIKSystem.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetSitting)}");
|
||||||
|
AlternateIKSystem.Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ResetIk))]
|
||||||
|
private static void Prefix_PlayerSetup_ResetIk(ref PlayerSetup __instance, ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CVRMovementParent currentParent = __instance._movementSystem._currentParent;
|
||||||
|
if (currentParent?._referencePoint == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (IKManager.Instance != null)
|
||||||
|
__runOriginal = !IKManager.Instance.OnPlayerHandleMovementParent(currentParent);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AlternateIKSystem.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_ResetIk)}");
|
||||||
|
AlternateIKSystem.Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class IKSystemPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(IKSystem), nameof(IKSystem.InitializeAvatar))]
|
||||||
|
private static void Prefix_IKSystem_InitializeAvatar(ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
// Don't setup with native IKSystem
|
||||||
|
__runOriginal = !ModSettings.EntryEnabled.Value;
|
||||||
|
}
|
||||||
|
}
|
95
AlternateIKSystem/IK/BodyControl.cs
Normal file
95
AlternateIKSystem/IK/BodyControl.cs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.IK;
|
||||||
|
|
||||||
|
public static class BodyControl
|
||||||
|
{
|
||||||
|
#region Tracking Controls
|
||||||
|
|
||||||
|
public static bool TrackingAll = true;
|
||||||
|
public static bool TrackingHead = true;
|
||||||
|
public static bool TrackingPelvis = true;
|
||||||
|
public static bool TrackingLeftArm = true;
|
||||||
|
public static bool TrackingRightArm = true;
|
||||||
|
public static bool TrackingLeftLeg = true;
|
||||||
|
public static bool TrackingRightLeg = true;
|
||||||
|
|
||||||
|
//TODO: dont do this, it is effective but lazy
|
||||||
|
public static bool TrackingLocomotion
|
||||||
|
{
|
||||||
|
get => _trackingLocomotion;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_trackingLocomotion == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_trackingLocomotion = value;
|
||||||
|
IKManager.solver?.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static bool _trackingLocomotion = true;
|
||||||
|
|
||||||
|
public static float TrackingPositionWeight = 1f;
|
||||||
|
|
||||||
|
// TODO: decide if these are considered "Tracking Controls"
|
||||||
|
public static float TrackingUpright = 1f;
|
||||||
|
public static float TrackingMaxRootAngle = 0f;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Player Settings
|
||||||
|
|
||||||
|
public static bool useHipTracking = true;
|
||||||
|
public static bool useChestTracking = true;
|
||||||
|
public static bool useLeftFootTracking = true;
|
||||||
|
public static bool useRightFootTracking = true;
|
||||||
|
|
||||||
|
public static bool useLeftElbowTracking = false;
|
||||||
|
public static bool useRightElbowTracking = false;
|
||||||
|
public static bool useLeftKneeTracking = false;
|
||||||
|
public static bool useRightKneeTracking = false;
|
||||||
|
|
||||||
|
public static bool useLocomotionAnimations = true;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BodyControl Configuration
|
||||||
|
|
||||||
|
public static float InvalidTrackerDistance = 1f;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Solver Weight Helpers
|
||||||
|
|
||||||
|
public static void SetHeadWeight(IKSolverVR.Spine spine, LookAtIK lookAtIk, float weight)
|
||||||
|
{
|
||||||
|
spine.positionWeight = weight;
|
||||||
|
spine.rotationWeight = weight;
|
||||||
|
if (lookAtIk != null)
|
||||||
|
lookAtIk.solver.IKPositionWeight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetArmWeight(IKSolverVR.Arm arm, float weight)
|
||||||
|
{
|
||||||
|
arm.positionWeight = weight;
|
||||||
|
arm.rotationWeight = weight;
|
||||||
|
arm.shoulderRotationWeight = weight;
|
||||||
|
arm.shoulderTwistWeight = weight;
|
||||||
|
arm.bendGoalWeight = arm.bendGoal != null ? weight : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetLegWeight(IKSolverVR.Leg leg, float weight)
|
||||||
|
{
|
||||||
|
leg.positionWeight = weight;
|
||||||
|
leg.rotationWeight = weight;
|
||||||
|
leg.bendGoalWeight = leg.usingKneeTracker ? weight : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetPelvisWeight(IKSolverVR.Spine spine, float weight)
|
||||||
|
{
|
||||||
|
spine.pelvisPositionWeight = weight;
|
||||||
|
spine.pelvisRotationWeight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
183
AlternateIKSystem/IK/IKCalibrator.cs
Normal file
183
AlternateIKSystem/IK/IKCalibrator.cs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.IK;
|
||||||
|
|
||||||
|
internal static class IKCalibrator
|
||||||
|
{
|
||||||
|
public static VRIK SetupVrIk(Animator animator)
|
||||||
|
{
|
||||||
|
if (animator.gameObject.TryGetComponent(out VRIK vrik))
|
||||||
|
UnityEngine.Object.DestroyImmediate(vrik);
|
||||||
|
|
||||||
|
vrik = animator.gameObject.AddComponent<VRIK>();
|
||||||
|
vrik.AutoDetectReferences();
|
||||||
|
|
||||||
|
if (!ModSettings.EntryUseVRIKToes.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;
|
||||||
|
|
||||||
|
// 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>();
|
||||||
|
twistRelaxer.ik = ik;
|
||||||
|
twistRelaxer.weight = 0.5f;
|
||||||
|
twistRelaxer.child = hand;
|
||||||
|
twistRelaxer.parentChildCrossfade = 0.8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 0f;
|
||||||
|
|
||||||
|
vrik.solver.plantFeet = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: figure out proper Desktop & VR organization
|
||||||
|
public static void SetupHeadIKTargetDesktop(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;
|
||||||
|
}
|
||||||
|
}
|
37
AlternateIKSystem/IK/IKHandlers/IKHandler.cs
Normal file
37
AlternateIKSystem/IK/IKHandlers/IKHandler.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using NAK.AlternateIKSystem.VRIKHelpers;
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.IK.IKHandlers;
|
||||||
|
|
||||||
|
internal class IKHandler
|
||||||
|
{
|
||||||
|
internal VRIK _vrik;
|
||||||
|
internal IKSolverVR _solver;
|
||||||
|
|
||||||
|
// Last Movement Parent Info
|
||||||
|
internal Vector3 _movementPosition;
|
||||||
|
internal Quaternion _movementRotation;
|
||||||
|
internal CVRMovementParent _movementParent;
|
||||||
|
|
||||||
|
#region Virtual Methods
|
||||||
|
|
||||||
|
public virtual void OnInitializeIk()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnPlayerScaled(float scaleDifference, VRIKCalibrationData calibrationData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnPlayerHandleMovementParent(CVRMovementParent currentParent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
188
AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs
Normal file
188
AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using NAK.AlternateIKSystem.VRIKHelpers;
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.IK.IKHandlers;
|
||||||
|
|
||||||
|
internal class IKHandlerDesktop : IKHandler
|
||||||
|
{
|
||||||
|
public IKHandlerDesktop(VRIK vrik)
|
||||||
|
{
|
||||||
|
_vrik = vrik;
|
||||||
|
_solver = vrik.solver;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides
|
||||||
|
|
||||||
|
public override void OnInitializeIk()
|
||||||
|
{
|
||||||
|
_vrik.onPreSolverUpdate.AddListener(new UnityAction(OnPreSolverUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
// Reset avatar local position
|
||||||
|
_vrik.transform.localPosition = Vector3.zero;
|
||||||
|
_vrik.transform.localRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
UpdateWeights();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPlayerScaled(float scaleDifference, VRIKCalibrationData calibrationData)
|
||||||
|
{
|
||||||
|
VRIKUtils.ApplyScaleToVRIK
|
||||||
|
(
|
||||||
|
_vrik,
|
||||||
|
calibrationData,
|
||||||
|
scaleDifference
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPlayerHandleMovementParent(CVRMovementParent currentParent)
|
||||||
|
{
|
||||||
|
// 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 = IKManager.Instance.transform.position;
|
||||||
|
|
||||||
|
// Prevent targeting other parent position
|
||||||
|
if (_movementParent == currentParent)
|
||||||
|
{
|
||||||
|
_solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
|
||||||
|
_ikSimulatedRootAngle = Mathf.Repeat(_ikSimulatedRootAngle + deltaRotation.eulerAngles.y, 360f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store for next frame
|
||||||
|
_movementParent = currentParent;
|
||||||
|
_movementPosition = currentPosition;
|
||||||
|
_movementRotation = currentRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region VRIK Solver Events
|
||||||
|
|
||||||
|
//TODO: properly expose these settings
|
||||||
|
|
||||||
|
private bool EntryPlantFeet = true;
|
||||||
|
|
||||||
|
private float EntryBodyLeanWeight = 1f;
|
||||||
|
private bool EntryProneThrusting = true;
|
||||||
|
|
||||||
|
private float EntryBodyHeadingLimit = 30f;
|
||||||
|
private float EntryPelvisHeadingWeight = 0.25f;
|
||||||
|
private float EntryChestHeadingWeight = 0.75f;
|
||||||
|
|
||||||
|
private float _ikSimulatedRootAngle = 0f;
|
||||||
|
|
||||||
|
private void OnPreSolverUpdate()
|
||||||
|
{
|
||||||
|
_solver.plantFeet = EntryPlantFeet;
|
||||||
|
|
||||||
|
// Emulate old VRChat hip movement
|
||||||
|
if (EntryBodyLeanWeight > 0)
|
||||||
|
{
|
||||||
|
float weightedAngle = EntryProneThrusting ? 1f : EntryBodyLeanWeight * _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 (EntryBodyHeadingLimit > 0)
|
||||||
|
{
|
||||||
|
float weightedAngleLimit = EntryBodyHeadingLimit * _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 (EntryPelvisHeadingWeight > 0)
|
||||||
|
{
|
||||||
|
_solver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * EntryPelvisHeadingWeight, 0f);
|
||||||
|
_solver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * EntryPelvisHeadingWeight, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EntryChestHeadingWeight > 0)
|
||||||
|
{
|
||||||
|
_solver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * EntryChestHeadingWeight, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private float _locomotionWeight = 1f;
|
||||||
|
|
||||||
|
private void UpdateWeights()
|
||||||
|
{
|
||||||
|
// Lerp locomotion weight, lerp to BodyControl.TrackingUpright???
|
||||||
|
float targetWeight =
|
||||||
|
(BodyControl.TrackingAll && BodyControl.TrackingLocomotion && BodyControl.TrackingUpright > 0.8f)
|
||||||
|
? 1f
|
||||||
|
: 0.0f;
|
||||||
|
_locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * 20f);
|
||||||
|
|
||||||
|
if (BodyControl.TrackingAll)
|
||||||
|
{
|
||||||
|
_vrik.enabled = true;
|
||||||
|
_solver.IKPositionWeight = BodyControl.TrackingPositionWeight;
|
||||||
|
_solver.locomotion.weight = _locomotionWeight;
|
||||||
|
_solver.spine.maxRootAngle = BodyControl.TrackingMaxRootAngle;
|
||||||
|
|
||||||
|
// Hack to make knees bend properly when in custom pose animations
|
||||||
|
bool useAnimatedBendNormal = _locomotionWeight <= 0.5f;
|
||||||
|
_solver.leftLeg.useAnimatedBendNormal = useAnimatedBendNormal;
|
||||||
|
_solver.rightLeg.useAnimatedBendNormal = useAnimatedBendNormal;
|
||||||
|
|
||||||
|
BodyControl.SetHeadWeight(_solver.spine, IKManager.lookAtIk, BodyControl.TrackingHead ? 1f : 0f);
|
||||||
|
|
||||||
|
BodyControl.SetArmWeight(_solver.leftArm, BodyControl.TrackingLeftArm && _solver.leftArm.target != null ? 1f : 0f);
|
||||||
|
BodyControl.SetArmWeight(_solver.rightArm, BodyControl.TrackingRightArm && _solver.rightArm.target != null ? 1f : 0f);
|
||||||
|
|
||||||
|
BodyControl.SetLegWeight(_solver.leftLeg, BodyControl.TrackingLeftLeg && _solver.leftLeg.target != null ? 1f : 0f);
|
||||||
|
BodyControl.SetLegWeight(_solver.rightLeg, BodyControl.TrackingRightLeg && _solver.rightLeg.target != null ? 1f : 0f);
|
||||||
|
|
||||||
|
BodyControl.SetPelvisWeight(_solver.spine, BodyControl.TrackingPelvis && _solver.spine.pelvisTarget != null ? 1f : 0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_vrik.enabled = false;
|
||||||
|
_solver.IKPositionWeight = 0f;
|
||||||
|
_solver.locomotion.weight = 0f;
|
||||||
|
_solver.spine.maxRootAngle = 0f;
|
||||||
|
|
||||||
|
_solver.leftLeg.useAnimatedBendNormal = true;
|
||||||
|
_solver.rightLeg.useAnimatedBendNormal = true;
|
||||||
|
|
||||||
|
BodyControl.SetHeadWeight(_solver.spine, IKManager.lookAtIk, 0f);
|
||||||
|
BodyControl.SetArmWeight(_solver.leftArm, 0f);
|
||||||
|
BodyControl.SetArmWeight(_solver.rightArm, 0f);
|
||||||
|
BodyControl.SetLegWeight(_solver.leftLeg, 0f);
|
||||||
|
BodyControl.SetLegWeight(_solver.rightLeg, 0f);
|
||||||
|
BodyControl.SetPelvisWeight(_solver.spine, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop should never have head position weight
|
||||||
|
_solver.spine.positionWeight = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
416
AlternateIKSystem/IK/IKManager.cs
Normal file
416
AlternateIKSystem/IK/IKManager.cs
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
|
using ABI_RC.Systems.MovementSystem;
|
||||||
|
using NAK.AlternateIKSystem.IK.IKHandlers;
|
||||||
|
using NAK.AlternateIKSystem.VRIKHelpers;
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.IK;
|
||||||
|
|
||||||
|
public class IKManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
public static IKManager Instance;
|
||||||
|
|
||||||
|
public static VRIK vrik => _vrik;
|
||||||
|
private static VRIK _vrik;
|
||||||
|
public static IKSolverVR solver => _vrik?.solver;
|
||||||
|
private static LookAtIK _lookAtIk;
|
||||||
|
public static LookAtIK lookAtIk => _lookAtIk;
|
||||||
|
|
||||||
|
private bool _isAvatarInitialized = false;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Pose Info
|
||||||
|
private HumanPoseHandler _humanPoseHandler;
|
||||||
|
private HumanPose _humanPose;
|
||||||
|
private HumanPose _humanPoseInitial;
|
||||||
|
|
||||||
|
// VRIK Calibration Info
|
||||||
|
private VRIKCalibrationData _calibrationData;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
Destroy(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
_desktopCamera = PlayerSetup.Instance.desktopCamera.transform;
|
||||||
|
_vrCamera = PlayerSetup.Instance.vrCamera.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
BodyControl.TrackingAll = ShouldTrackAll();
|
||||||
|
BodyControl.TrackingUpright = GetPlayerUpright();
|
||||||
|
BodyControl.TrackingLocomotion = ShouldTrackLocomotion();
|
||||||
|
|
||||||
|
if (!_isAvatarInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ikHandler?.OnUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Avatar Events
|
||||||
|
|
||||||
|
public void OnAvatarInitialized(GameObject inAvatar)
|
||||||
|
{
|
||||||
|
if (_isAvatarInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!inAvatar.TryGetComponent(out _animator))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_animator.avatar == null || !_animator.avatar.isHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lookAtIk = inAvatar.GetComponent<LookAtIK>();
|
||||||
|
|
||||||
|
_animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||||
|
|
||||||
|
_animIKPoseLayer = _animator.GetLayerIndex("IKPose");
|
||||||
|
_animLocomotionLayer = _animator.GetLayerIndex("Locomotion/Emotes");
|
||||||
|
|
||||||
|
_hipTransform = _animator.GetBoneTransform(HumanBodyBones.Hips);
|
||||||
|
|
||||||
|
_humanPoseHandler?.Dispose();
|
||||||
|
_humanPoseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform);
|
||||||
|
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPose);
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPoseInitial);
|
||||||
|
|
||||||
|
if (MetaPort.Instance.isUsingVr)
|
||||||
|
{
|
||||||
|
InitializeHalfBodyIk();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InitializeDesktopIk();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAvatarInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAvatarDestroyed()
|
||||||
|
{
|
||||||
|
if (!_isAvatarInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_vrik = null;
|
||||||
|
_lookAtIk = 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, _calibrationData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerSeatedStateChanged(bool isSitting)
|
||||||
|
{
|
||||||
|
if (!_isAvatarInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// update immediately, ShouldTrackLocomotion() will catch up next frame
|
||||||
|
if (isSitting)
|
||||||
|
BodyControl.TrackingLocomotion = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPlayerHandleMovementParent(CVRMovementParent movementParent)
|
||||||
|
{
|
||||||
|
if (!_isAvatarInitialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_ikHandler?.OnPlayerHandleMovementParent(movementParent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private bool ShouldTrackAll()
|
||||||
|
{
|
||||||
|
return !PlayerSetup.Instance._emotePlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldTrackLocomotion()
|
||||||
|
{
|
||||||
|
return !(MovementSystem.Instance.movementVector.magnitude > 0f
|
||||||
|
|| MovementSystem.Instance.crouching
|
||||||
|
|| MovementSystem.Instance.prone
|
||||||
|
|| MovementSystem.Instance.flying
|
||||||
|
|| MovementSystem.Instance.sitting
|
||||||
|
|| !MovementSystem.Instance._isGrounded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetPlayerUpright()
|
||||||
|
{
|
||||||
|
float avatarHeight = PlayerSetup.Instance._avatarHeight;
|
||||||
|
float currentHeight = PlayerSetup.Instance.GetViewRelativePosition().y;
|
||||||
|
return Mathf.Clamp01((avatarHeight > 0f) ? (currentHeight / avatarHeight) : 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IK Initialization
|
||||||
|
|
||||||
|
private void InitializeDesktopIk()
|
||||||
|
{
|
||||||
|
PreSetupIkGeneral();
|
||||||
|
IKCalibrator.ConfigureDesktopVrIk(_vrik);
|
||||||
|
IKCalibrator.SetupHeadIKTargetDesktop(_vrik);
|
||||||
|
InitializeIkGeneral();
|
||||||
|
|
||||||
|
_ikHandler = new IKHandlerDesktop(_vrik);
|
||||||
|
_ikHandler.OnInitializeIk();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeHalfBodyIk()
|
||||||
|
{
|
||||||
|
PreSetupIkGeneral();
|
||||||
|
IKCalibrator.ConfigureHalfBodyVrIk(_vrik);
|
||||||
|
InitializeIkGeneral();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreSetupIkGeneral()
|
||||||
|
{
|
||||||
|
SetAvatarPose(AvatarPose.Default);
|
||||||
|
_vrik = IKCalibrator.SetupVrIk(_animator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeIkGeneral()
|
||||||
|
{
|
||||||
|
SetAvatarPose(AvatarPose.IKPose);
|
||||||
|
|
||||||
|
VRIKUtils.CalculateInitialIKScaling(_vrik, ref _calibrationData);
|
||||||
|
VRIKUtils.CalculateInitialFootsteps(_vrik, ref _calibrationData);
|
||||||
|
|
||||||
|
VRIKUtils.ApplyScaleToVRIK(_vrik, _calibrationData, 1f);
|
||||||
|
|
||||||
|
VRIKUtils.InitiateVRIKSolver(_vrik); // initiate again to store ikpose
|
||||||
|
|
||||||
|
_vrik.onPreSolverUpdate.AddListener(new UnityAction(OnPreSolverUpdateGeneral));
|
||||||
|
_vrik.onPostSolverUpdate.AddListener(new UnityAction(OnPostSolverUpdateGeneral));
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
46
AlternateIKSystem/IK/MusclePoses.cs
Normal file
46
AlternateIKSystem/IK/MusclePoses.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
namespace NAK.AlternateIKSystem.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
|
||||||
|
};
|
||||||
|
}
|
31
AlternateIKSystem/IK/VRIKHelpers/VRIKCalibrationData.cs
Normal file
31
AlternateIKSystem/IK/VRIKHelpers/VRIKCalibrationData.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.VRIKHelpers;
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
}
|
125
AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs
Normal file
125
AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.VRIKHelpers;
|
||||||
|
|
||||||
|
public static class VRIKUtils
|
||||||
|
{
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
37
AlternateIKSystem/Main.cs
Normal file
37
AlternateIKSystem/Main.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem;
|
||||||
|
|
||||||
|
// IKManager is what the game talks to
|
||||||
|
// BodyControl is the master vrik tracking weights
|
||||||
|
|
||||||
|
// IKHandler is created by IKManager, specific to Desktop/VR
|
||||||
|
// It will look at BodyControl to manage its own vrik solver
|
||||||
|
|
||||||
|
// IKCalibrator will setup vrik & its settings
|
||||||
|
|
||||||
|
public class AlternateIKSystem : MelonMod
|
||||||
|
{
|
||||||
|
internal static MelonLogger.Instance Logger;
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
|
||||||
|
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||||
|
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Msg($"Failed while patching {type.Name}!");
|
||||||
|
Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
AlternateIKSystem/ModSettings.cs
Normal file
17
AlternateIKSystem/ModSettings.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem;
|
||||||
|
|
||||||
|
public static class ModSettings
|
||||||
|
{
|
||||||
|
internal const string SettingsCategory = nameof(AlternateIKSystem);
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Category Category =
|
||||||
|
MelonPreferences.CreateCategory(SettingsCategory);
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||||
|
Category.CreateEntry("Enabled", true, description: "Toggle AlternateIKSystem entirely. Requires avatar reload.");
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> 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.");
|
||||||
|
}
|
31
AlternateIKSystem/Properties/AssemblyInfo.cs
Normal file
31
AlternateIKSystem/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.AlternateIKSystem.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.AlternateIKSystem))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.AlternateIKSystem))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.AlternateIKSystem.AlternateIKSystem),
|
||||||
|
nameof(NAK.AlternateIKSystem),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AlternateIKSystem"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonOptionalDependencies("BTKUILib")]
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.AlternateIKSystem.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
16
AlternateIKSystem/README.md
Normal file
16
AlternateIKSystem/README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
## AlternateIKSystem
|
||||||
|
|
||||||
|
This is a super fucked mix of DesktopVRIK, StateBehaviours, IKFixes, & IKTweaks with general structure inspired by ChilloutVRs existing IKSystem.
|
||||||
|
|
||||||
|
It is a personal project that I am unsure how far I will go with. Currently only Desktop VRIK works as that was easiest to implement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI.
|
||||||
|
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||||
|
|
||||||
|
> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive.
|
||||||
|
|
||||||
|
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||||
|
|
||||||
|
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
24
AlternateIKSystem/format.json
Normal file
24
AlternateIKSystem/format.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"_id": 117,
|
||||||
|
"name": "AlternateIKSystem",
|
||||||
|
"modversion": "4.2.0",
|
||||||
|
"gameversion": "2022r170p1",
|
||||||
|
"loaderversion": "0.6.1",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"author": "NotAKidoS",
|
||||||
|
"description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nOptional BTKUILib integration.",
|
||||||
|
"searchtags": [
|
||||||
|
"desktop",
|
||||||
|
"vrik",
|
||||||
|
"ik",
|
||||||
|
"feet",
|
||||||
|
"fish"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"BTKUILib"
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r5/AlternateIKSystem.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AlternateIKSystem/",
|
||||||
|
"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"
|
||||||
|
}
|
|
@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EzCurls", "EzCurls\EzCurls.csproj", "{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EzCurls", "EzCurls\EzCurls.csproj", "{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AlternateIKSystem", "AlternateIKSystem\AlternateIKSystem.csproj", "{CAB05E13-B529-4CDA-A2B5-B62E122408F8}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -225,6 +227,10 @@ Global
|
||||||
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CAB05E13-B529-4CDA-A2B5-B62E122408F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CAB05E13-B529-4CDA-A2B5-B62E122408F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CAB05E13-B529-4CDA-A2B5-B62E122408F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CAB05E13-B529-4CDA-A2B5-B62E122408F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue