NAK_CVR_Mods/DesktopVRIK/DesktopVRIKCalibrator.cs
2023-03-06 20:53:23 -06:00

595 lines
No EOL
20 KiB
C#

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;
public bool Setting_ExperimentalKneeBend = true;
public bool Setting_DebugCalibrationPose = false;
// Avatar Component References
public CVRAvatar avatar;
public Animator animator;
public Transform avatarTransform;
public VRIK vrik;
public LookAtIK lookAtIK;
// Calibrated Values
public float
initialFootDistance,
initialStepThreshold,
initialStepHeight;
// Calibration Internals
bool DebugCalibrationPose;
bool fixTransformsRequired;
Vector3 leftKneeNormal, rightKneeNormal;
HumanPose initialHumanPose;
HumanPoseHandler humanPoseHandler;
// Traverse
IKSystem ikSystem;
PlayerSetup playerSetup;
Traverse
_vrikTraverse,
_lookIKTraverse,
_avatarTraverse,
_animatorManagerTraverse,
_poseHandlerTraverse,
_avatarRootHeightTraverse;
public DesktopVRIKCalibrator()
{
// Get base game scripts.
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()
{
PreInitialize();
// Don't do anything else if just debugging calibration pose
DebugCalibrationPose = !DebugCalibrationPose;
if (Setting_DebugCalibrationPose && DebugCalibrationPose)
{
ForceCalibrationPose();
return;
}
// Add VRIK and configure
PrepareAvatarVRIK();
Initialize();
PostInitialize();
}
public void ForceCalibrationPose(bool toggle = true)
{
animator.enabled = !toggle;
SetHumanPose(0f);
//SetAvatarIKPose(toggle);
}
private void PreInitialize()
{
// Scan avatar for issues/references
ScanAvatarForCalibration();
// Prepare CVR IKSystem for external VRIK
PrepareIKSystem();
}
private void Initialize()
{
// Calculate bend normals with motorcycle pose
SetHumanPose(0f);
CalculateKneeBendNormals();
SetAvatarIKPose(true);
// Setup HeadIK target & calculate initial footstep values
SetupDesktopHeadIKTarget();
CalculateInitialIKScaling();
// Initiate VRIK manually
ForceInitiateVRIKSolver();
SetAvatarIKPose(false);
}
private void PostInitialize()
{
ApplyKneeBendNormals();
ApplyInitialIKScaling();
vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate));
}
private void ScanAvatarForCalibration()
{
// Reset some stuff to default
fixTransformsRequired = false;
// Find required avatar components
avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
animator = avatar.GetComponent<Animator>();
avatarTransform = avatar.transform;
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
// Apply some fixes for weird setups
if (!animator.enabled)
{
fixTransformsRequired = true;
DesktopVRIKMod.Logger.Error("Avatar has Animator disabled by default!");
}
// Center avatar local offsets
avatarTransform.localPosition = Vector3.zero;
//avatarTransform.localRotation = Quaternion.identity;
// Store original human pose
if (humanPoseHandler != null)
{
humanPoseHandler.Dispose();
}
humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform);
humanPoseHandler.GetHumanPose(ref initialHumanPose);
}
private void PrepareIKSystem()
{
// Get the animator manager and human pose handler
var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>();
var ikHumanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>();
// Store the avatar component
_avatarTraverse.SetValue(avatar);
// Set the animator for the IK system
ikSystem.animator = animator;
if (ikSystem.animator != null)
{
animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController);
}
// Set the avatar height float
float avatarHeight = ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y;
_avatarRootHeightTraverse.SetValue(avatarHeight);
// Create a new human pose handler and dispose the old one
if (ikHumanPoseHandler != null)
{
ikHumanPoseHandler.Dispose();
_poseHandlerTraverse.SetValue(null);
}
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();
ConfigureVRIKReferences();
_vrikTraverse.SetValue(vrik);
//in testing, not really needed
//only required if Setting_FindUnmappedToes
//and non-human mapped toes are found
vrik.fixTransforms = fixTransformsRequired;
//default solver settings
vrik.solver.locomotion.weight = 0f;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 0.75f;
vrik.solver.spine.minHeadHeight = 0f;
vrik.solver.IKPositionWeight = 1f;
//disable to not bleed into anims
vrik.solver.spine.chestClampWeight = 0f;
vrik.solver.spine.maintainPelvisPosition = 0f;
//for body leaning
vrik.solver.spine.neckStiffness = 0.0001f; //cannot be 0
vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0.2f;
//disable so avatar doesnt try and walk away
vrik.solver.locomotion.velocityFactor = 0f;
vrik.solver.locomotion.maxVelocity = 0f;
//fixes nameplate spazzing on remote & magicacloth
vrik.solver.locomotion.rootSpeed = 1000f;
//disable so PAM & BID dont make body shake
vrik.solver.spine.rotateChestByHands = 0f;
//enable to prioritize LookAtIK
vrik.solver.spine.headClampWeight = 0.2f;
//disable to not go on tippytoes
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.rotationWeight = 1f;
//vrik.solver.spine.maintainPelvisPosition = 1f;
//vrik.solver.locomotion.weight = 0f;
//vrik.solver.spine.positionWeight = 0f;
//vrik.solver.spine.pelvisPositionWeight = 0f;
//vrik.solver.leftArm.positionWeight = 0f;
//vrik.solver.leftArm.rotationWeight = 0f;
//vrik.solver.rightArm.positionWeight = 0f;
//vrik.solver.rightArm.rotationWeight = 0f;
//vrik.solver.leftLeg.positionWeight = 0f;
//vrik.solver.leftLeg.rotationWeight = 0f;
//vrik.solver.rightLeg.positionWeight = 0f;
//vrik.solver.rightLeg.rotationWeight = 0f;
//vrik.solver.IKPositionWeight = 0f;
//THESE ARE CONFIGURABLE IN GAME IK SETTINGS
//vrik.solver.leftLeg.target = null;
//vrik.solver.leftLeg.bendGoal = null;
//vrik.solver.leftLeg.positionWeight = 0f;
//vrik.solver.leftLeg.bendGoalWeight = 0f;
//vrik.solver.rightLeg.target = null;
//vrik.solver.rightLeg.bendGoal = null;
//vrik.solver.rightLeg.positionWeight = 0f;
//vrik.solver.rightLeg.bendGoalWeight = 0f;
//vrik.solver.spine.pelvisTarget = null;
//vrik.solver.spine.chestGoal = null;
//vrik.solver.spine.positionWeight = 0f;
//vrik.solver.spine.rotationWeight = 0f;
//vrik.solver.spine.pelvisPositionWeight = 0f;
//vrik.solver.spine.pelvisRotationWeight = 0f;
//vrik.solver.spine.chestGoalWeight = 0f;
}
private void CalculateKneeBendNormals()
{
// Get assumed left knee normal
Vector3[] leftVectors = new Vector3[]
{
vrik.references.leftThigh?.position ?? Vector3.zero,
vrik.references.leftCalf?.position ?? Vector3.zero,
vrik.references.leftFoot?.position ?? Vector3.zero,
};
leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors);
// Get assumed right knee normal
Vector3[] rightVectors = new Vector3[]
{
vrik.references.rightThigh?.position ?? Vector3.zero,
vrik.references.rightCalf?.position ?? Vector3.zero,
vrik.references.rightFoot?.position ?? Vector3.zero,
};
rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors);
}
private void ApplyKneeBendNormals()
{
if (!Setting_ExperimentalKneeBend)
{
//enable so knees on fucked models work better
vrik.solver.leftLeg.useAnimatedBendNormal = true;
vrik.solver.rightLeg.useAnimatedBendNormal = true;
return;
}
vrik.solver.leftLeg.bendToTargetWeight = 0f;
vrik.solver.rightLeg.bendToTargetWeight = 0f;
Traverse leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis");
Traverse rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis");
// Calculate knee normal without root rotation but with pelvis rotation
Quaternion pelvisLocalRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
Vector3 leftLegBendNormalRelToPelvis = pelvisLocalRotationInverse * leftKneeNormal;
Vector3 rightLegBendNormalRelToPelvis = pelvisLocalRotationInverse * rightKneeNormal;
//Quaternion rootRotation = vrik.references.root.rotation;
//Quaternion pelvisRotationRelativeToRoot = Quaternion.Inverse(rootRotation) * vrik.references.pelvis.rotation;
//Quaternion pelvisRotationInverse = Quaternion.Inverse(pelvisRotationRelativeToRoot);
leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLegBendNormalRelToPelvis);
rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLegBendNormalRelToPelvis);
}
private Vector3 GetNormalFromArray(Vector3[] positions)
{
Vector3 vector = Vector3.zero;
Vector3 vector2 = Vector3.zero;
for (int i = 0; i < positions.Length; i++)
{
vector2 += positions[i];
}
vector2 /= (float)positions.Length;
for (int j = 0; j < positions.Length - 1; j++)
{
vector += Vector3.Cross(positions[j] - vector2, positions[j + 1] - vector2).normalized;
}
return Vector3.Normalize(vector);
}
private void CalculateInitialIKScaling()
{
// Get distance between feets and thighs
float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position);
initialFootDistance = footDistance * 0.5f;
initialStepThreshold = footDistance * 0.8f;
initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
}
private void ApplyInitialIKScaling()
{
// Set initial values
vrik.solver.locomotion.footDistance = initialFootDistance;
vrik.solver.locomotion.stepThreshold = initialStepThreshold;
DesktopVRIK.ScaleStepHeight(vrik.solver.locomotion.stepHeight, initialStepHeight);
}
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 void ForceInitiateVRIKSolver()
{
//force immediate calibration before animator decides to fuck us
vrik.solver.SetToReferences(vrik.references);
vrik.solver.Initiate(vrik.transform);
}
private void ConfigureVRIKReferences()
{
//might not work over netik
FixChestAndSpineReferences();
if (!Setting_UseVRIKToes)
{
vrik.references.leftToes = null;
vrik.references.rightToes = null;
}
else if (Setting_FindUnmappedToes)
{
//doesnt work with netik, but its toes...
FindAndSetUnmappedToes();
}
//bullshit fix to not cause death
FixFingerBonesError();
}
private void FixChestAndSpineReferences()
{
Transform leftShoulderBone = vrik.references.leftShoulder;
Transform rightShoulderBone = vrik.references.rightShoulder;
Transform assumedChest = leftShoulderBone?.parent;
if (assumedChest != null && rightShoulderBone.parent == assumedChest &&
vrik.references.chest != assumedChest)
{
vrik.references.chest = assumedChest;
vrik.references.spine = assumedChest.parent;
}
}
private void FindAndSetUnmappedToes()
{
Transform leftToes = vrik.references.leftToes;
Transform rightToes = vrik.references.rightToes;
if (leftToes == null && rightToes == null)
{
leftToes = FindUnmappedToe(vrik.references.leftFoot);
rightToes = FindUnmappedToe(vrik.references.rightFoot);
if (leftToes != null && rightToes != null)
{
vrik.references.leftToes = leftToes;
vrik.references.rightToes = rightToes;
fixTransformsRequired = true;
}
}
}
private Transform FindUnmappedToe(Transform foot)
{
foreach (Transform bone in foot)
{
if (bone.name.ToLowerInvariant().Contains("toe") ||
bone.name.ToLowerInvariant().EndsWith("_end"))
{
return bone;
}
}
return null;
}
private void FixFingerBonesError()
{
FixFingerBones(vrik.references.leftHand, vrik.solver.leftArm);
FixFingerBones(vrik.references.rightHand, vrik.solver.rightArm);
}
private void FixFingerBones(Transform hand, IKSolverVR.Arm armSolver)
{
if (hand.childCount == 0)
{
armSolver.wristToPalmAxis = Vector3.up;
armSolver.palmToThumbAxis = hand == vrik.references.leftHand ? -Vector3.forward : Vector3.forward;
}
}
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
};
}