mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
No longer rely on AMT, IKSystem, or PlayerSetup
Partially rewrote everything so it is cleaner, does not need Traverse/Reflection, and no longer piggybacks on the game handling VRIK on Desktop for whatever reason. This version also handles VRIK Locomotion weight standalone instead of requiring AvatarMotionTweaker, but if AMT is found it will rely on it anyways. This can be overwridden in the melonprefs, but its still gonna be double the maths for no reason.
This commit is contained in:
parent
ee896a6c6b
commit
1eeaf4be54
7 changed files with 721 additions and 625 deletions
|
@ -1,198 +0,0 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.IK.SubSystems;
|
||||
using ABI_RC.Systems.MovementSystem;
|
||||
using HarmonyLib;
|
||||
using RootMotion.FinalIK;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace NAK.Melons.DesktopVRIK;
|
||||
|
||||
public class DesktopVRIK : MonoBehaviour
|
||||
{
|
||||
public static DesktopVRIK Instance;
|
||||
public DesktopVRIKCalibrator Calibrator;
|
||||
|
||||
// DesktopVRIK Settings
|
||||
public bool
|
||||
Setting_Enabled = true,
|
||||
Setting_PlantFeet = true;
|
||||
|
||||
public float
|
||||
Setting_BodyLeanWeight,
|
||||
Setting_BodyHeadingLimit,
|
||||
Setting_PelvisHeadingWeight,
|
||||
Setting_ChestHeadingWeight;
|
||||
|
||||
// DesktopVRIK References
|
||||
bool _isEmotePlaying;
|
||||
float _simulatedRootAngle;
|
||||
Transform _avatarTransform;
|
||||
Transform _cameraTransform;
|
||||
|
||||
// IK Stuff
|
||||
VRIK _vrik;
|
||||
LookAtIK _lookAtIK;
|
||||
IKSolverVR _ikSolver;
|
||||
|
||||
// Movement System Stuff
|
||||
MovementSystem movementSystem;
|
||||
Traverse _isGroundedTraverse;
|
||||
|
||||
// Movement Parent Stuff
|
||||
private Vector3 _previousPosition;
|
||||
private Quaternion _previousRotation;
|
||||
private Traverse _currentParentTraverse;
|
||||
static readonly FieldInfo _referencePointField = typeof(CVRMovementParent).GetField("_referencePoint", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
void Start()
|
||||
{
|
||||
Instance = this;
|
||||
Calibrator = new DesktopVRIKCalibrator();
|
||||
DesktopVRIKMod.UpdateAllSettings();
|
||||
|
||||
movementSystem = GetComponent<MovementSystem>();
|
||||
_cameraTransform = PlayerSetup.Instance.desktopCamera.transform;
|
||||
_isGroundedTraverse = Traverse.Create(movementSystem).Field("_isGrounded");
|
||||
_currentParentTraverse = Traverse.Create(movementSystem).Field("_currentParent");
|
||||
}
|
||||
|
||||
public void OnSetupAvatarDesktop()
|
||||
{
|
||||
if (!Setting_Enabled) return;
|
||||
Calibrator.CalibrateDesktopVRIK();
|
||||
|
||||
_vrik = Calibrator.vrik;
|
||||
_lookAtIK = Calibrator.lookAtIK;
|
||||
_ikSolver = Calibrator.vrik.solver;
|
||||
_avatarTransform = Calibrator.avatarTransform;
|
||||
|
||||
_simulatedRootAngle = transform.eulerAngles.y;
|
||||
}
|
||||
|
||||
public bool OnSetupIKScaling(float scaleDifference)
|
||||
{
|
||||
if (_vrik == null) return false;
|
||||
|
||||
VRIKUtils.ApplyScaleToVRIK
|
||||
(
|
||||
_vrik,
|
||||
Calibrator.initialFootDistance,
|
||||
Calibrator.initialStepThreshold,
|
||||
Calibrator.initialStepHeight,
|
||||
scaleDifference
|
||||
);
|
||||
|
||||
_ikSolver?.Reset();
|
||||
ResetDesktopVRIK();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnPlayerSetupUpdate(bool isEmotePlaying)
|
||||
{
|
||||
bool changed = isEmotePlaying != _isEmotePlaying;
|
||||
if (!changed) return;
|
||||
|
||||
_isEmotePlaying = isEmotePlaying;
|
||||
|
||||
_avatarTransform.localPosition = Vector3.zero;
|
||||
_avatarTransform.localRotation = Quaternion.identity;
|
||||
|
||||
if (_lookAtIK != null)
|
||||
_lookAtIK.enabled = !isEmotePlaying;
|
||||
|
||||
BodySystem.TrackingEnabled = !isEmotePlaying;
|
||||
|
||||
_ikSolver?.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
|
||||
public bool OnPlayerSetupResetIk()
|
||||
{
|
||||
if (_vrik == null) return false;
|
||||
|
||||
CVRMovementParent currentParent = _currentParentTraverse.GetValue<CVRMovementParent>();
|
||||
if (currentParent == null) return false;
|
||||
|
||||
Transform referencePoint = (Transform)_referencePointField.GetValue(currentParent);
|
||||
if (referencePoint == null) return false;
|
||||
|
||||
var currentPosition = referencePoint.position;
|
||||
var currentRotation = currentParent.transform.rotation;
|
||||
|
||||
// Keep only the Y-axis rotation
|
||||
currentRotation = Quaternion.Euler(0f, currentRotation.eulerAngles.y, 0f);
|
||||
|
||||
var deltaPosition = currentPosition - _previousPosition;
|
||||
var deltaRotation = Quaternion.Inverse(_previousRotation) * currentRotation;
|
||||
|
||||
var platformPivot = transform.position;
|
||||
_ikSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
|
||||
|
||||
_previousPosition = currentPosition;
|
||||
_previousRotation = currentRotation;
|
||||
|
||||
ResetDesktopVRIK();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void ResetDesktopVRIK()
|
||||
{
|
||||
_simulatedRootAngle = transform.eulerAngles.y;
|
||||
}
|
||||
|
||||
public void OnPreSolverUpdate()
|
||||
{
|
||||
if (_isEmotePlaying) return;
|
||||
|
||||
bool isGrounded = _isGroundedTraverse.GetValue<bool>();
|
||||
|
||||
// Calculate weight
|
||||
float weight = _ikSolver.IKPositionWeight;
|
||||
weight *= 1f - movementSystem.movementVector.magnitude;
|
||||
weight *= isGrounded ? 1f : 0f;
|
||||
|
||||
// Reset avatar offset
|
||||
_avatarTransform.localPosition = Vector3.zero;
|
||||
_avatarTransform.localRotation = Quaternion.identity;
|
||||
|
||||
// Set plant feet
|
||||
_ikSolver.plantFeet = Setting_PlantFeet;
|
||||
|
||||
// Emulate old VRChat hip movement
|
||||
if (Setting_BodyLeanWeight > 0)
|
||||
{
|
||||
float weightedAngle = Setting_BodyLeanWeight * weight;
|
||||
float angle = _cameraTransform.localEulerAngles.x;
|
||||
angle = angle > 180 ? angle - 360 : angle;
|
||||
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, _avatarTransform.right);
|
||||
_ikSolver.spine.headRotationOffset *= rotation;
|
||||
}
|
||||
|
||||
// Make root heading follow within a set limit
|
||||
if (Setting_BodyHeadingLimit > 0)
|
||||
{
|
||||
float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
|
||||
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _simulatedRootAngle);
|
||||
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
|
||||
if (absDeltaAngleRoot > weightedAngleLimit)
|
||||
{
|
||||
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
|
||||
_simulatedRootAngle = Mathf.MoveTowardsAngle(_simulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
|
||||
}
|
||||
_ikSolver.spine.rootHeadingOffset = deltaAngleRoot;
|
||||
if (Setting_PelvisHeadingWeight > 0)
|
||||
{
|
||||
_ikSolver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||
_ikSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||
}
|
||||
if (Setting_ChestHeadingWeight > 0)
|
||||
{
|
||||
_ikSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_ChestHeadingWeight, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,41 +6,42 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\0Harmony.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp-firstpass">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp-firstpass.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="BTKUILib">
|
||||
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Cohtml.Runtime">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Cohtml.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\MelonLoader.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AnimationModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AnimationModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AssetBundleModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AssetBundleModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputLegacyModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,367 +0,0 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.IK;
|
||||
using ABI_RC.Systems.IK.SubSystems;
|
||||
using HarmonyLib;
|
||||
using RootMotion.FinalIK;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NAK.Melons.DesktopVRIK;
|
||||
|
||||
public class DesktopVRIKCalibrator
|
||||
{
|
||||
// Settings
|
||||
public bool Setting_UseVRIKToes = true;
|
||||
public bool Setting_FindUnmappedToes = true;
|
||||
|
||||
// Avatar Component References
|
||||
public CVRAvatar avatar;
|
||||
public Animator animator;
|
||||
public Transform avatarTransform;
|
||||
public VRIK vrik;
|
||||
public LookAtIK lookAtIK;
|
||||
|
||||
// Calibrated Values
|
||||
public float
|
||||
initialFootDistance,
|
||||
initialStepThreshold,
|
||||
initialStepHeight;
|
||||
|
||||
// Calibration Internals
|
||||
bool fixTransformsRequired;
|
||||
Vector3 leftKneeNormal, rightKneeNormal;
|
||||
HumanPose initialHumanPose;
|
||||
HumanPoseHandler humanPoseHandler;
|
||||
|
||||
// Traverse
|
||||
IKSystem ikSystem;
|
||||
PlayerSetup playerSetup;
|
||||
Traverse
|
||||
_vrikTraverse,
|
||||
_lookIKTraverse,
|
||||
_avatarTraverse,
|
||||
_animatorManagerTraverse,
|
||||
_poseHandlerTraverse,
|
||||
_avatarRootHeightTraverse;
|
||||
|
||||
public DesktopVRIKCalibrator()
|
||||
{
|
||||
// Get base game scripts.
|
||||
ikSystem = IKSystem.Instance;
|
||||
playerSetup = PlayerSetup.Instance;
|
||||
|
||||
// Get traverse to private shit in iksystem.
|
||||
_vrikTraverse = Traverse.Create(ikSystem).Field("_vrik");
|
||||
_avatarTraverse = Traverse.Create(ikSystem).Field("_avatar");
|
||||
_animatorManagerTraverse = Traverse.Create(ikSystem).Field("_animatorManager");
|
||||
_poseHandlerTraverse = Traverse.Create(ikSystem).Field("_poseHandler");
|
||||
_avatarRootHeightTraverse = Traverse.Create(ikSystem).Field("_avatarRootHeight");
|
||||
|
||||
// Get traverse to private shit in playersetup.
|
||||
_lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK");
|
||||
}
|
||||
|
||||
public void CalibrateDesktopVRIK()
|
||||
{
|
||||
// Scan avatar for issues/references
|
||||
ScanAvatarForCalibration();
|
||||
// Prepare CVR IKSystem for external VRIK
|
||||
PrepareIKSystem();
|
||||
|
||||
// Add VRIK and configure
|
||||
PrepareAvatarVRIK();
|
||||
|
||||
Initialize();
|
||||
|
||||
PostInitialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
// Calculate bend normals with motorcycle pose
|
||||
SetHumanPose(0f);
|
||||
VRIKUtils.CalculateKneeBendNormals(vrik, out leftKneeNormal, out rightKneeNormal);
|
||||
|
||||
// Calculate initial IK scaling values with IKPose
|
||||
SetAvatarIKPose(true);
|
||||
VRIKUtils.CalculateInitialIKScaling(vrik, out initialFootDistance, out initialStepThreshold, out initialStepHeight);
|
||||
|
||||
// Setup HeadIK target & calculate initial footstep values
|
||||
SetupDesktopHeadIKTarget();
|
||||
|
||||
// Initiate VRIK manually
|
||||
VRIKUtils.InitiateVRIKSolver(vrik);
|
||||
|
||||
// Return avatar to original pose
|
||||
SetAvatarIKPose(false);
|
||||
}
|
||||
|
||||
private void PostInitialize()
|
||||
{
|
||||
VRIKUtils.ApplyScaleToVRIK
|
||||
(
|
||||
vrik,
|
||||
initialFootDistance,
|
||||
initialStepThreshold,
|
||||
initialStepHeight,
|
||||
1f
|
||||
);
|
||||
VRIKUtils.ApplyKneeBendNormals(vrik, leftKneeNormal, rightKneeNormal);
|
||||
vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate));
|
||||
}
|
||||
|
||||
private void ScanAvatarForCalibration()
|
||||
{
|
||||
// Find required avatar components
|
||||
avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
|
||||
animator = avatar.GetComponent<Animator>();
|
||||
avatarTransform = avatar.transform;
|
||||
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
|
||||
|
||||
// Apply some fixes for weird setups
|
||||
fixTransformsRequired = !animator.enabled;
|
||||
|
||||
// Center avatar local position
|
||||
avatarTransform.localPosition = Vector3.zero;
|
||||
|
||||
// Create a new human pose handler and dispose the old one
|
||||
humanPoseHandler?.Dispose();
|
||||
humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform);
|
||||
// Store original human pose
|
||||
humanPoseHandler.GetHumanPose(ref initialHumanPose);
|
||||
}
|
||||
|
||||
private void PrepareIKSystem()
|
||||
{
|
||||
// Get the animator manager and human pose handler
|
||||
var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>();
|
||||
var ikHumanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>();
|
||||
|
||||
// Store the avatar component
|
||||
_avatarTraverse.SetValue(avatar);
|
||||
|
||||
// Set the animator for the IK system
|
||||
ikSystem.animator = animator;
|
||||
animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController);
|
||||
|
||||
// Set the avatar height float
|
||||
_avatarRootHeightTraverse.SetValue(ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y);
|
||||
|
||||
// Create a new human pose handler and dispose the old one
|
||||
ikHumanPoseHandler?.Dispose();
|
||||
ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform);
|
||||
_poseHandlerTraverse.SetValue(ikHumanPoseHandler);
|
||||
|
||||
// Find valid human bones
|
||||
IKSystem.BoneExists.Clear();
|
||||
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
|
||||
{
|
||||
if (bone != HumanBodyBones.LastBone)
|
||||
{
|
||||
IKSystem.BoneExists.Add(bone, ikSystem.animator.GetBoneTransform(bone) != null);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare BodySystem for calibration
|
||||
BodySystem.TrackingLeftArmEnabled = false;
|
||||
BodySystem.TrackingRightArmEnabled = false;
|
||||
BodySystem.TrackingLeftLegEnabled = false;
|
||||
BodySystem.TrackingRightLegEnabled = false;
|
||||
BodySystem.TrackingPositionWeight = 0f;
|
||||
}
|
||||
|
||||
private void PrepareAvatarVRIK()
|
||||
{
|
||||
// Add and configure VRIK
|
||||
vrik = avatar.gameObject.AddComponentIfMissing<VRIK>();
|
||||
vrik.AutoDetectReferences();
|
||||
|
||||
VRIKUtils.ConfigureVRIKReferences(vrik, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes);
|
||||
|
||||
// Fix animator issue or non-human mapped toes
|
||||
vrik.fixTransforms = fixTransformsRequired || foundUnmappedToes;
|
||||
|
||||
// Default solver settings
|
||||
vrik.solver.locomotion.weight = 0f;
|
||||
vrik.solver.locomotion.angleThreshold = 30f;
|
||||
vrik.solver.locomotion.maxLegStretch = 1f;
|
||||
vrik.solver.spine.minHeadHeight = 0f;
|
||||
vrik.solver.IKPositionWeight = 1f;
|
||||
vrik.solver.spine.chestClampWeight = 0f;
|
||||
vrik.solver.spine.maintainPelvisPosition = 0f;
|
||||
|
||||
// Body leaning settings
|
||||
vrik.solver.spine.neckStiffness = 0.0001f;
|
||||
vrik.solver.spine.bodyPosStiffness = 1f;
|
||||
vrik.solver.spine.bodyRotStiffness = 0.2f;
|
||||
|
||||
// Disable locomotion
|
||||
vrik.solver.locomotion.velocityFactor = 0f;
|
||||
vrik.solver.locomotion.maxVelocity = 0f;
|
||||
vrik.solver.locomotion.rootSpeed = 1000f;
|
||||
|
||||
// Disable chest rotation by hands
|
||||
vrik.solver.spine.rotateChestByHands = 0f;
|
||||
|
||||
// Prioritize LookAtIK
|
||||
vrik.solver.spine.headClampWeight = 0.2f;
|
||||
|
||||
// Disable going on tippytoes
|
||||
vrik.solver.spine.positionWeight = 0f;
|
||||
vrik.solver.spine.rotationWeight = 1f;
|
||||
|
||||
// Tell IKSystem about new VRIK
|
||||
_vrikTraverse.SetValue(vrik);
|
||||
}
|
||||
|
||||
private void SetupDesktopHeadIKTarget()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
private void SetAvatarIKPose(bool enforceTPose)
|
||||
{
|
||||
int ikposeLayerIndex = animator.GetLayerIndex("IKPose");
|
||||
int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes");
|
||||
|
||||
// Use custom IKPose if found.
|
||||
if (ikposeLayerIndex != -1 && locoLayerIndex != -1)
|
||||
{
|
||||
animator.SetLayerWeight(ikposeLayerIndex, enforceTPose ? 1f : 0f);
|
||||
animator.SetLayerWeight(locoLayerIndex, enforceTPose ? 0f : 1f);
|
||||
animator.Update(0f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise use DesktopVRIK IKPose & revert afterwards.
|
||||
if (enforceTPose)
|
||||
{
|
||||
SetHumanPose(1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
humanPoseHandler.SetHumanPose(ref initialHumanPose);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHumanPose(float ikPoseWeight = 1f)
|
||||
{
|
||||
humanPoseHandler.GetHumanPose(ref ikSystem.humanPose);
|
||||
for (int i = 0; i < ikSystem.humanPose.muscles.Length; i++)
|
||||
{
|
||||
float weight = ikPoseWeight * IKPoseMuscles[i];
|
||||
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, weight, ref ikSystem.humanPose.muscles);
|
||||
}
|
||||
ikSystem.humanPose.bodyRotation = Quaternion.identity;
|
||||
humanPoseHandler.SetHumanPose(ref ikSystem.humanPose);
|
||||
}
|
||||
|
||||
private 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
|
||||
};
|
||||
}
|
646
DesktopVRIK/DesktopVRIKSystem.cs
Normal file
646
DesktopVRIK/DesktopVRIKSystem.cs
Normal file
|
@ -0,0 +1,646 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.IK;
|
||||
using ABI_RC.Systems.IK.SubSystems;
|
||||
using ABI_RC.Systems.MovementSystem;
|
||||
using RootMotion.FinalIK;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NAK.Melons.DesktopVRIK;
|
||||
|
||||
internal class DesktopVRIKSystem : MonoBehaviour
|
||||
{
|
||||
public static DesktopVRIKSystem Instance;
|
||||
public static Dictionary<HumanBodyBones, bool> BoneExists;
|
||||
public static readonly float[] IKPoseMuscles = new float[]
|
||||
{
|
||||
0.00133321f,
|
||||
8.195831E-06f,
|
||||
8.537738E-07f,
|
||||
-0.002669832f,
|
||||
-7.651234E-06f,
|
||||
-0.001659694f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0.04213953f,
|
||||
0.0003007996f,
|
||||
-0.008032114f,
|
||||
-0.03059979f,
|
||||
-0.0003182998f,
|
||||
0.009640567f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
0.5768794f,
|
||||
0.01061097f,
|
||||
-0.1127839f,
|
||||
0.9705755f,
|
||||
0.07972051f,
|
||||
-0.0268422f,
|
||||
0.007237188f,
|
||||
0f,
|
||||
0.5768792f,
|
||||
0.01056608f,
|
||||
-0.1127519f,
|
||||
0.9705756f,
|
||||
0.07971933f,
|
||||
-0.02682396f,
|
||||
0.007229362f,
|
||||
0f,
|
||||
-5.651802E-06f,
|
||||
-3.034899E-07f,
|
||||
0.4100508f,
|
||||
0.3610304f,
|
||||
-0.0838329f,
|
||||
0.9262537f,
|
||||
0.1353517f,
|
||||
-0.03578902f,
|
||||
0.06005657f,
|
||||
-4.95989E-06f,
|
||||
-1.43007E-06f,
|
||||
0.4096187f,
|
||||
0.363263f,
|
||||
-0.08205152f,
|
||||
0.9250782f,
|
||||
0.1345718f,
|
||||
-0.03572125f,
|
||||
0.06055461f,
|
||||
-1.079177f,
|
||||
0.2095419f,
|
||||
0.6140652f,
|
||||
0.6365265f,
|
||||
0.6683931f,
|
||||
-0.4764312f,
|
||||
0.8099416f,
|
||||
0.8099371f,
|
||||
0.6658203f,
|
||||
-0.7327053f,
|
||||
0.8113618f,
|
||||
0.8114051f,
|
||||
0.6643661f,
|
||||
-0.40341f,
|
||||
0.8111364f,
|
||||
0.8111367f,
|
||||
0.6170399f,
|
||||
-0.2524227f,
|
||||
0.8138723f,
|
||||
0.8110135f,
|
||||
-1.079171f,
|
||||
0.2095456f,
|
||||
0.6140658f,
|
||||
0.6365255f,
|
||||
0.6683878f,
|
||||
-0.4764301f,
|
||||
0.8099402f,
|
||||
0.8099376f,
|
||||
0.6658241f,
|
||||
-0.7327023f,
|
||||
0.8113653f,
|
||||
0.8113793f,
|
||||
0.664364f,
|
||||
-0.4034042f,
|
||||
0.811136f,
|
||||
0.8111364f,
|
||||
0.6170469f,
|
||||
-0.2524345f,
|
||||
0.8138595f,
|
||||
0.8110138f
|
||||
};
|
||||
enum AvatarPose
|
||||
{
|
||||
Default = 0,
|
||||
Initial = 1,
|
||||
IKPose = 2,
|
||||
TPose = 3
|
||||
}
|
||||
|
||||
// ChilloutVR Player Components
|
||||
private PlayerSetup playerSetup;
|
||||
private MovementSystem movementSystem;
|
||||
|
||||
// DesktopVRIK Settings
|
||||
public bool Setting_Enabled = true;
|
||||
public bool Setting_PlantFeet = true;
|
||||
public float Setting_BodyLeanWeight;
|
||||
public float Setting_BodyHeadingLimit;
|
||||
public float Setting_PelvisHeadingWeight;
|
||||
public float Setting_ChestHeadingWeight;
|
||||
|
||||
// Calibration Settings
|
||||
public bool Setting_UseVRIKToes = true;
|
||||
public bool Setting_FindUnmappedToes = true;
|
||||
|
||||
// Integration Settings
|
||||
public bool Setting_IntegrationAMT = false;
|
||||
|
||||
// Avatar Components
|
||||
public CVRAvatar avatarDescriptor = null;
|
||||
public Animator avatarAnimator = null;
|
||||
public Transform avatarTransform = null;
|
||||
public LookAtIK avatarLookAtIK = null;
|
||||
public VRIK avatarVRIK = null;
|
||||
public IKSolverVR avatarIKSolver = null;
|
||||
|
||||
// Calibration Objects
|
||||
public HumanPose HumanPose;
|
||||
public HumanPose InitialHumanPose;
|
||||
public HumanPoseHandler HumanPoseHandler;
|
||||
|
||||
// Animator Info
|
||||
public int locomotionLayer = -1;
|
||||
public int customIKPoseLayer = -1;
|
||||
public bool requireFixTransforms = false;
|
||||
|
||||
// VRIK Calibration Info
|
||||
public Vector3 leftKneeNormal;
|
||||
public Vector3 rightKneeNormal;
|
||||
public float initialFootDistance;
|
||||
public float initialStepThreshold;
|
||||
public float initialStepHeight;
|
||||
|
||||
// Player Info
|
||||
private Transform _cameraTransform;
|
||||
private bool _isEmotePlaying;
|
||||
private float _simulatedRootAngle;
|
||||
|
||||
// Last Movement Parent Info
|
||||
private Vector3 _previousPosition;
|
||||
private Quaternion _previousRotation;
|
||||
|
||||
DesktopVRIKSystem()
|
||||
{
|
||||
BoneExists = new Dictionary<HumanBodyBones, bool>();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
playerSetup = GetComponent<PlayerSetup>();
|
||||
movementSystem = GetComponent<MovementSystem>();
|
||||
|
||||
_cameraTransform = playerSetup.desktopCamera.transform;
|
||||
|
||||
DesktopVRIKMod.UpdateAllSettings();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (avatarVRIK == null) return;
|
||||
|
||||
HandleLocomotionTracking();
|
||||
ApplyBodySystemWeights();
|
||||
}
|
||||
|
||||
void HandleLocomotionTracking()
|
||||
{
|
||||
bool isMoving = movementSystem.movementVector.magnitude > 0f;
|
||||
|
||||
// AvatarMotionTweaker handles VRIK a bit better than DesktopVRIK
|
||||
if (Setting_IntegrationAMT && DesktopVRIKMod.integration_AMT)
|
||||
{
|
||||
if (isMoving)
|
||||
{
|
||||
if (BodySystem.TrackingLocomotionEnabled)
|
||||
{
|
||||
BodySystem.TrackingLocomotionEnabled = false;
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!BodySystem.TrackingLocomotionEnabled)
|
||||
{
|
||||
BodySystem.TrackingLocomotionEnabled = true;
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool isGrounded = movementSystem._isGrounded;
|
||||
bool isCrouching = movementSystem.crouching;
|
||||
bool isProne = movementSystem.prone;
|
||||
bool isFlying = movementSystem.flying;
|
||||
|
||||
// Why do it myself if VRIK already does the maths
|
||||
Vector3 headLocalPos = avatarIKSolver.spine.headPosition - avatarIKSolver.spine.rootPosition;
|
||||
float upright = 1f + (headLocalPos.y - avatarIKSolver.spine.headHeight);
|
||||
|
||||
if (isMoving || isCrouching || isProne || isFlying || !isGrounded)
|
||||
{
|
||||
if (BodySystem.TrackingLocomotionEnabled)
|
||||
{
|
||||
BodySystem.TrackingLocomotionEnabled = false;
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!BodySystem.TrackingLocomotionEnabled && upright > 0.8f)
|
||||
{
|
||||
BodySystem.TrackingLocomotionEnabled = true;
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
avatarIKSolver.IKPositionWeight = BodySystem.TrackingPositionWeight;
|
||||
avatarIKSolver.locomotion.weight = BodySystem.TrackingLocomotionEnabled ? 1f : 0f;
|
||||
|
||||
SetArmWeight(avatarIKSolver.leftArm, BodySystem.TrackingLeftArmEnabled && avatarIKSolver.leftArm.target != null);
|
||||
SetArmWeight(avatarIKSolver.rightArm, BodySystem.TrackingRightArmEnabled && avatarIKSolver.rightArm.target != null);
|
||||
SetLegWeight(avatarIKSolver.leftLeg, BodySystem.TrackingLeftLegEnabled && avatarIKSolver.leftLeg.target != null);
|
||||
SetLegWeight(avatarIKSolver.rightLeg, BodySystem.TrackingRightLegEnabled && avatarIKSolver.rightLeg.target != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
avatarVRIK.enabled = false;
|
||||
avatarIKSolver.IKPositionWeight = 0f;
|
||||
avatarIKSolver.locomotion.weight = 0f;
|
||||
|
||||
SetArmWeight(avatarIKSolver.leftArm, false);
|
||||
SetArmWeight(avatarIKSolver.rightArm, false);
|
||||
SetLegWeight(avatarIKSolver.leftLeg, false);
|
||||
SetLegWeight(avatarIKSolver.rightLeg, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSetupAvatarDesktop()
|
||||
{
|
||||
if (!Setting_Enabled) return;
|
||||
|
||||
CalibrateDesktopVRIK();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
|
||||
public bool OnSetupIKScaling(float scaleDifference)
|
||||
{
|
||||
if (avatarVRIK == null) return false;
|
||||
|
||||
VRIKUtils.ApplyScaleToVRIK
|
||||
(
|
||||
avatarVRIK,
|
||||
initialFootDistance,
|
||||
initialStepThreshold,
|
||||
initialStepHeight,
|
||||
scaleDifference
|
||||
);
|
||||
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnPlayerSetupUpdate(bool isEmotePlaying)
|
||||
{
|
||||
if (avatarVRIK == null) return;
|
||||
|
||||
bool changed = isEmotePlaying != _isEmotePlaying;
|
||||
if (!changed) return;
|
||||
|
||||
_isEmotePlaying = isEmotePlaying;
|
||||
|
||||
avatarTransform.localPosition = Vector3.zero;
|
||||
avatarTransform.localRotation = Quaternion.identity;
|
||||
|
||||
if (avatarLookAtIK != null)
|
||||
avatarLookAtIK.enabled = !isEmotePlaying;
|
||||
|
||||
BodySystem.TrackingEnabled = !isEmotePlaying;
|
||||
|
||||
avatarIKSolver.Reset();
|
||||
ResetDesktopVRIK();
|
||||
}
|
||||
|
||||
public bool OnPlayerSetupResetIk()
|
||||
{
|
||||
if (avatarVRIK == null) return false;
|
||||
|
||||
CVRMovementParent currentParent = movementSystem._currentParent;
|
||||
if (currentParent == null) return false;
|
||||
|
||||
Transform referencePoint = currentParent._referencePoint;
|
||||
if (referencePoint == null) return false;
|
||||
|
||||
var currentPosition = referencePoint.position;
|
||||
var currentRotation = currentParent.transform.rotation;
|
||||
|
||||
// Keep only the Y-axis rotation
|
||||
currentRotation = Quaternion.Euler(0f, currentRotation.eulerAngles.y, 0f);
|
||||
|
||||
var deltaPosition = currentPosition - _previousPosition;
|
||||
var deltaRotation = Quaternion.Inverse(_previousRotation) * currentRotation;
|
||||
|
||||
var platformPivot = transform.position;
|
||||
avatarIKSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
|
||||
|
||||
_previousPosition = currentPosition;
|
||||
_previousRotation = currentRotation;
|
||||
|
||||
ResetDesktopVRIK();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnPreSolverUpdate()
|
||||
{
|
||||
if (_isEmotePlaying) return;
|
||||
|
||||
bool isGrounded = movementSystem._isGrounded;
|
||||
|
||||
// Calculate weight
|
||||
float weight = avatarIKSolver.IKPositionWeight;
|
||||
weight *= 1f - movementSystem.movementVector.magnitude;
|
||||
weight *= isGrounded ? 1f : 0f;
|
||||
|
||||
// Reset avatar offset
|
||||
avatarTransform.localPosition = Vector3.zero;
|
||||
avatarTransform.localRotation = Quaternion.identity;
|
||||
|
||||
// Set plant feet
|
||||
avatarIKSolver.plantFeet = Setting_PlantFeet;
|
||||
|
||||
// Emulate old VRChat hip movementSystem
|
||||
if (Setting_BodyLeanWeight > 0)
|
||||
{
|
||||
float weightedAngle = Setting_BodyLeanWeight * weight;
|
||||
float angle = _cameraTransform.localEulerAngles.x;
|
||||
angle = angle > 180 ? angle - 360 : angle;
|
||||
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right);
|
||||
avatarIKSolver.spine.headRotationOffset *= rotation;
|
||||
}
|
||||
|
||||
// Make root heading follow within a set limit
|
||||
if (Setting_BodyHeadingLimit > 0)
|
||||
{
|
||||
float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
|
||||
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _simulatedRootAngle);
|
||||
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
|
||||
if (absDeltaAngleRoot > weightedAngleLimit)
|
||||
{
|
||||
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
|
||||
_simulatedRootAngle = Mathf.MoveTowardsAngle(_simulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
|
||||
}
|
||||
avatarIKSolver.spine.rootHeadingOffset = deltaAngleRoot;
|
||||
if (Setting_PelvisHeadingWeight > 0)
|
||||
{
|
||||
avatarIKSolver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||
avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||
}
|
||||
if (Setting_ChestHeadingWeight > 0)
|
||||
{
|
||||
avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_ChestHeadingWeight, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDesktopVRIK()
|
||||
{
|
||||
_simulatedRootAngle = transform.eulerAngles.y;
|
||||
}
|
||||
|
||||
void CalibrateDesktopVRIK()
|
||||
{
|
||||
ScanAvatar();
|
||||
SetupVRIK();
|
||||
CalibrateVRIK();
|
||||
ConfigureVRIK();
|
||||
}
|
||||
|
||||
void ScanAvatar()
|
||||
{
|
||||
// Find required avatar components
|
||||
avatarDescriptor = playerSetup._avatarDescriptor;
|
||||
avatarAnimator = playerSetup._animator;
|
||||
avatarTransform = playerSetup._avatar.transform;
|
||||
avatarLookAtIK = playerSetup.lookIK;
|
||||
|
||||
// Get animator layer inticies
|
||||
locomotionLayer = avatarAnimator.GetLayerIndex("IKPose");
|
||||
customIKPoseLayer = avatarAnimator.GetLayerIndex("Locomotion/Emotes");
|
||||
|
||||
// Dispose and create new HumanPoseHandler
|
||||
HumanPoseHandler?.Dispose();
|
||||
HumanPoseHandler = new HumanPoseHandler(avatarAnimator.avatar, avatarTransform);
|
||||
|
||||
// Get initial human poses
|
||||
HumanPoseHandler.GetHumanPose(ref HumanPose);
|
||||
HumanPoseHandler.GetHumanPose(ref InitialHumanPose);
|
||||
|
||||
// Dumb fix for rare upload issue
|
||||
requireFixTransforms = !avatarAnimator.enabled;
|
||||
|
||||
// Find available HumanoidBodyBones
|
||||
BoneExists.Clear();
|
||||
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
|
||||
{
|
||||
if (bone != HumanBodyBones.LastBone)
|
||||
{
|
||||
BoneExists.Add(bone, avatarAnimator.GetBoneTransform(bone) != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetupVRIK()
|
||||
{
|
||||
// Add and configure VRIK
|
||||
avatarVRIK = avatarTransform.AddComponentIfMissing<VRIK>();
|
||||
avatarVRIK.AutoDetectReferences();
|
||||
avatarIKSolver = avatarVRIK.solver;
|
||||
|
||||
VRIKUtils.ConfigureVRIKReferences(avatarVRIK, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes);
|
||||
|
||||
// Fix animator issue or non-human mapped toes
|
||||
avatarVRIK.fixTransforms = requireFixTransforms || foundUnmappedToes;
|
||||
|
||||
// Default solver settings
|
||||
avatarIKSolver.locomotion.weight = 0f;
|
||||
avatarIKSolver.locomotion.angleThreshold = 30f;
|
||||
avatarIKSolver.locomotion.maxLegStretch = 1f;
|
||||
avatarIKSolver.spine.minHeadHeight = 0f;
|
||||
avatarIKSolver.IKPositionWeight = 1f;
|
||||
avatarIKSolver.spine.chestClampWeight = 0f;
|
||||
avatarIKSolver.spine.maintainPelvisPosition = 0f;
|
||||
|
||||
// Body leaning settings
|
||||
avatarIKSolver.spine.neckStiffness = 0.0001f;
|
||||
avatarIKSolver.spine.bodyPosStiffness = 1f;
|
||||
avatarIKSolver.spine.bodyRotStiffness = 0.2f;
|
||||
|
||||
// Disable locomotion
|
||||
avatarIKSolver.locomotion.velocityFactor = 0f;
|
||||
avatarIKSolver.locomotion.maxVelocity = 0f;
|
||||
avatarIKSolver.locomotion.rootSpeed = 1000f;
|
||||
|
||||
// Disable chest rotation by hands
|
||||
avatarIKSolver.spine.rotateChestByHands = 0f;
|
||||
|
||||
// Prioritize LookAtIK
|
||||
avatarIKSolver.spine.headClampWeight = 0.2f;
|
||||
|
||||
// Disable going on tippytoes
|
||||
avatarIKSolver.spine.positionWeight = 0f;
|
||||
avatarIKSolver.spine.rotationWeight = 1f;
|
||||
|
||||
// We disable these ourselves now, as we no longer use BodySystem
|
||||
avatarIKSolver.spine.maintainPelvisPosition = 1f;
|
||||
avatarIKSolver.spine.positionWeight = 0f;
|
||||
avatarIKSolver.spine.pelvisPositionWeight = 0f;
|
||||
avatarIKSolver.leftArm.positionWeight = 0f;
|
||||
avatarIKSolver.leftArm.rotationWeight = 0f;
|
||||
avatarIKSolver.rightArm.positionWeight = 0f;
|
||||
avatarIKSolver.rightArm.rotationWeight = 0f;
|
||||
avatarIKSolver.leftLeg.positionWeight = 0f;
|
||||
avatarIKSolver.leftLeg.rotationWeight = 0f;
|
||||
avatarIKSolver.rightLeg.positionWeight = 0f;
|
||||
avatarIKSolver.rightLeg.rotationWeight = 0f;
|
||||
|
||||
// This is now our master Locomotion weight
|
||||
avatarIKSolver.locomotion.weight = 1f;
|
||||
avatarIKSolver.IKPositionWeight = 1f;
|
||||
}
|
||||
|
||||
void CalibrateVRIK()
|
||||
{
|
||||
SetAvatarPose(AvatarPose.Default);
|
||||
|
||||
// Calculate bend normals with motorcycle pose
|
||||
VRIKUtils.CalculateKneeBendNormals(avatarVRIK, out leftKneeNormal, out rightKneeNormal);
|
||||
|
||||
SetAvatarPose(AvatarPose.IKPose);
|
||||
|
||||
// Calculate initial IK scaling values with IKPose
|
||||
VRIKUtils.CalculateInitialIKScaling(avatarVRIK, out initialFootDistance, out initialStepThreshold, out initialStepHeight);
|
||||
|
||||
// Setup HeadIKTarget
|
||||
VRIKUtils.SetupHeadIKTarget(avatarVRIK);
|
||||
|
||||
// Initiate VRIK manually
|
||||
VRIKUtils.InitiateVRIKSolver(avatarVRIK);
|
||||
|
||||
SetAvatarPose(AvatarPose.Initial);
|
||||
}
|
||||
|
||||
void ConfigureVRIK()
|
||||
{
|
||||
VRIKUtils.ApplyScaleToVRIK
|
||||
(
|
||||
avatarVRIK,
|
||||
initialFootDistance,
|
||||
initialStepThreshold,
|
||||
initialStepHeight,
|
||||
1f
|
||||
);
|
||||
VRIKUtils.ApplyKneeBendNormals(avatarVRIK, leftKneeNormal, rightKneeNormal);
|
||||
avatarVRIK.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIKSystem.Instance.OnPreSolverUpdate));
|
||||
}
|
||||
|
||||
void SetAvatarPose(AvatarPose pose)
|
||||
{
|
||||
switch (pose)
|
||||
{
|
||||
case AvatarPose.Default:
|
||||
if (HasCustomIKPose())
|
||||
{
|
||||
SetCustomLayersWeights(0f, 1f);
|
||||
avatarAnimator.Update(0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetMusclesToValue(0f);
|
||||
}
|
||||
break;
|
||||
case AvatarPose.Initial:
|
||||
HumanPoseHandler.SetHumanPose(ref InitialHumanPose);
|
||||
break;
|
||||
case AvatarPose.IKPose:
|
||||
if (HasCustomIKPose())
|
||||
{
|
||||
SetCustomLayersWeights(1f, 0f);
|
||||
avatarAnimator.Update(0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetMusclesToPose(IKPoseMuscles);
|
||||
}
|
||||
break;
|
||||
case AvatarPose.TPose:
|
||||
SetMusclesToPose(BodySystem.TPoseMuscles);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasCustomIKPose()
|
||||
{
|
||||
return locomotionLayer != -1 && customIKPoseLayer != -1;
|
||||
}
|
||||
|
||||
void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
|
||||
{
|
||||
avatarAnimator.SetLayerWeight(customIKPoseLayer, customIKPoseLayerWeight);
|
||||
avatarAnimator.SetLayerWeight(locomotionLayer, locomotionLayerWeight);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.IK;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -23,13 +22,20 @@ namespace NAK.Melons.DesktopVRIK.HarmonyPatches;
|
|||
|
||||
class PlayerSetupPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), "Start")]
|
||||
static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||
{
|
||||
__instance.gameObject.AddComponent<DesktopVRIKSystem>();
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")]
|
||||
static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator)
|
||||
{
|
||||
if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman)
|
||||
{
|
||||
DesktopVRIK.Instance?.OnSetupAvatarDesktop();
|
||||
DesktopVRIKSystem.Instance?.OnSetupAvatarDesktop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,30 +43,20 @@ class PlayerSetupPatches
|
|||
[HarmonyPatch(typeof(PlayerSetup), "Update")]
|
||||
static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying)
|
||||
{
|
||||
DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying);
|
||||
DesktopVRIKSystem.Instance?.OnPlayerSetupUpdate(____emotePlaying);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")]
|
||||
private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference)
|
||||
{
|
||||
return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y);
|
||||
return !(bool)DesktopVRIKSystem.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), "ResetIk")]
|
||||
static bool Prefix_PlayerSetup_ResetIk()
|
||||
{
|
||||
return !(bool)DesktopVRIK.Instance?.OnPlayerSetupResetIk();
|
||||
}
|
||||
}
|
||||
|
||||
class IKSystemPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(IKSystem), "Start")]
|
||||
private static void Postfix_IKSystem_Start(ref IKSystem __instance)
|
||||
{
|
||||
__instance.gameObject.AddComponent<DesktopVRIK>();
|
||||
return !(bool)DesktopVRIKSystem.Instance?.OnPlayerSetupResetIk();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ public class DesktopVRIKMod : MelonMod
|
|||
CategoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
|
||||
CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
|
||||
CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movementSystem.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseVRIKToes =
|
||||
CategoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar.");
|
||||
|
@ -33,6 +33,11 @@ public class DesktopVRIKMod : MelonMod
|
|||
public static readonly MelonPreferences_Entry<float> EntryChestHeadingWeight =
|
||||
CategoryDesktopVRIK.CreateEntry("Chest Heading Weight", 0.75f, description: "Determines how much the chest will face the Body Heading Limit. Set to 0 to align with head.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryIntegrationAMT =
|
||||
CategoryDesktopVRIK.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;
|
||||
|
@ -40,40 +45,47 @@ public class DesktopVRIKMod : MelonMod
|
|||
CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings));
|
||||
|
||||
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
|
||||
|
||||
InitializeIntegrations();
|
||||
}
|
||||
|
||||
internal static void UpdateAllSettings()
|
||||
{
|
||||
if (!DesktopVRIK.Instance) return;
|
||||
// DesktopVRIK Settings
|
||||
DesktopVRIK.Instance.Setting_Enabled = EntryEnabled.Value;
|
||||
DesktopVRIK.Instance.Setting_PlantFeet = EntryPlantFeet.Value;
|
||||
|
||||
DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value);
|
||||
DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f);
|
||||
DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value));
|
||||
DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
|
||||
|
||||
// Calibration Settings
|
||||
DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = EntryUseVRIKToes.Value;
|
||||
DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value;
|
||||
}
|
||||
private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
|
||||
|
||||
private static void InitializeIntegrations()
|
||||
{
|
||||
//BTKUILib Misc Tab
|
||||
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
|
||||
{
|
||||
Logger.Msg("Initializing BTKUILib support.");
|
||||
BTKUIAddon.Init();
|
||||
}
|
||||
//AvatarMotionTweaker Handling
|
||||
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker"))
|
||||
{
|
||||
Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
|
||||
integration_AMT = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
internal static void UpdateAllSettings()
|
||||
{
|
||||
if (!DesktopVRIKSystem.Instance) return;
|
||||
// DesktopVRIK Settings
|
||||
DesktopVRIKSystem.Instance.Setting_Enabled = EntryEnabled.Value;
|
||||
DesktopVRIKSystem.Instance.Setting_PlantFeet = EntryPlantFeet.Value;
|
||||
|
||||
DesktopVRIKSystem.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value);
|
||||
DesktopVRIKSystem.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f);
|
||||
DesktopVRIKSystem.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value));
|
||||
DesktopVRIKSystem.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
|
||||
|
||||
// Calibration Settings
|
||||
DesktopVRIKSystem.Instance.Setting_UseVRIKToes = EntryUseVRIKToes.Value;
|
||||
DesktopVRIKSystem.Instance.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value;
|
||||
|
||||
// Integration Settings
|
||||
DesktopVRIKSystem.Instance.Setting_IntegrationAMT = EntryIntegrationAMT.Value;
|
||||
}
|
||||
void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
|
||||
|
||||
void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using RootMotion.FinalIK;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.Melons.DesktopVRIK;
|
||||
|
||||
public static class VRIKUtils
|
||||
{
|
||||
static readonly FieldInfo vrik_bendNormalRelToPelvis = typeof(IKSolverVR.Leg).GetField("bendNormalRelToPelvis", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes, bool findUnmappedToes, out bool foundUnmappedToes)
|
||||
{
|
||||
foundUnmappedToes = false;
|
||||
|
@ -119,16 +116,13 @@ public static class VRIKUtils
|
|||
public static void ApplyKneeBendNormals(VRIK vrik, Vector3 leftKneeNormal, Vector3 rightKneeNormal)
|
||||
{
|
||||
// 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget
|
||||
// modifying pelvis normal weight is better math
|
||||
// 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);
|
||||
var leftLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal;
|
||||
var rightLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal;
|
||||
|
||||
vrik_bendNormalRelToPelvis.SetValue(vrik.solver.leftLeg, leftLeg_bendNormalRelToPelvis);
|
||||
vrik_bendNormalRelToPelvis.SetValue(vrik.solver.rightLeg, rightLeg_bendNormalRelToPelvis);
|
||||
vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal;
|
||||
vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal;
|
||||
}
|
||||
|
||||
private static Vector3 GetNormalFromArray(Vector3[] positions)
|
||||
|
@ -160,6 +154,18 @@ public static class VRIKUtils
|
|||
initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
|
||||
}
|
||||
|
||||
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, float footDistance, float stepThreshold, float stepHeight, float modifier)
|
||||
{
|
||||
vrik.solver.locomotion.footDistance = footDistance * modifier;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue