NAK_CVR_Mods/DesktopVRIK/DesktopVRIKSystem.cs

338 lines
No EOL
12 KiB
C#

using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using NAK.DesktopVRIK.VRIKHelper;
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.DesktopVRIK;
internal class DesktopVRIKSystem : MonoBehaviour
{
public static DesktopVRIKSystem Instance;
public static DesktopVRIKCalibrator Calibrator;
// VRIK Calibration Info
public VRIKCalibrationData calibrationData;
// Avatar Components
public VRIK avatarVRIK = null;
public LookAtIK avatarLookAtIK = null;
public Transform avatarTransform = null;
public CachedSolver cachedSolver;
// ChilloutVR Player Components
PlayerSetup playerSetup;
MovementSystem movementSystem;
// Player Info
Transform _cameraTransform;
bool _ikEmotePlaying;
float _ikWeightLerp = 1f;
float _ikSimulatedRootAngle = 0f;
float _locomotionWeight = 1f;
float _scaleDifference = 1f;
// Last Movement Parent Info
Vector3 _movementPosition;
Quaternion _movementRotation;
CVRMovementParent _movementParent;
void Start()
{
Instance = this;
Calibrator = new DesktopVRIKCalibrator();
playerSetup = GetComponent<PlayerSetup>();
movementSystem = GetComponent<MovementSystem>();
_cameraTransform = playerSetup.desktopCamera.transform;
}
void Update()
{
if (avatarVRIK == null) return;
HandleLocomotionTracking();
UpdateLocomotionWeight();
ApplyBodySystemWeights();
ResetAvatarLocalPosition();
}
void HandleLocomotionTracking()
{
bool shouldTrackLocomotion = ShouldTrackLocomotion();
if (shouldTrackLocomotion != BodySystem.TrackingLocomotionEnabled)
{
BodySystem.TrackingLocomotionEnabled = shouldTrackLocomotion;
IKResetSolver();
ResetDesktopVRIK();
if (shouldTrackLocomotion) IKResetFootsteps();
}
}
bool ShouldTrackLocomotion()
{
bool isMoving = movementSystem.movementVector.magnitude > 0f;
bool isGrounded = movementSystem._isGrounded;
bool isCrouching = movementSystem.crouching;
bool isProne = movementSystem.prone;
bool isFlying = movementSystem.flying;
bool isSitting = movementSystem.sitting;
bool isStanding = IsStanding();
return !(isMoving || isCrouching || isProne || isFlying || isSitting || !isGrounded || !isStanding);
}
bool IsStanding()
{
// Let AMT handle it if available
if (DesktopVRIK.EntryIntegrationAMT.Value) return true;
// Get Upright value
Vector3 delta = cachedSolver.Spine.headPosition - avatarTransform.position;
Vector3 deltaRotated = Quaternion.Euler(0, avatarTransform.rotation.eulerAngles.y, 0) * delta;
float upright = Mathf.InverseLerp(0f, calibrationData.InitialHeadHeight * _scaleDifference, deltaRotated.y);
return upright > 0.85f;
}
void UpdateLocomotionWeight()
{
float targetWeight = BodySystem.TrackingEnabled && BodySystem.TrackingLocomotionEnabled ? 1.0f : 0.0f;
if (DesktopVRIK.EntryIKLerpSpeed.Value > 0)
{
_ikWeightLerp = Mathf.Lerp(_ikWeightLerp, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value);
_locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * DesktopVRIK.EntryIKLerpSpeed.Value * 2f);
return;
}
_ikWeightLerp = targetWeight;
_locomotionWeight = targetWeight;
}
void ApplyBodySystemWeights()
{
void SetArmWeight(IKSolverVR.Arm arm, bool isTracked)
{
arm.positionWeight = isTracked ? 1f : 0f;
arm.rotationWeight = isTracked ? 1f : 0f;
arm.shoulderRotationWeight = isTracked ? 1f : 0f;
arm.shoulderTwistWeight = isTracked ? 1f : 0f;
}
void SetLegWeight(IKSolverVR.Leg leg, bool isTracked)
{
leg.positionWeight = isTracked ? 1f : 0f;
leg.rotationWeight = isTracked ? 1f : 0f;
}
if (BodySystem.TrackingEnabled)
{
avatarVRIK.enabled = true;
cachedSolver.Solver.IKPositionWeight = BodySystem.TrackingPositionWeight;
cachedSolver.Locomotion.weight = _locomotionWeight;
bool useAnimatedBendNormal = _locomotionWeight <= 0.5f;
cachedSolver.LeftLeg.useAnimatedBendNormal = useAnimatedBendNormal;
cachedSolver.RightLeg.useAnimatedBendNormal = useAnimatedBendNormal;
SetArmWeight(cachedSolver.LeftArm, BodySystem.TrackingLeftArmEnabled && cachedSolver.LeftArm.target != null);
SetArmWeight(cachedSolver.RightArm, BodySystem.TrackingRightArmEnabled && cachedSolver.RightArm.target != null);
SetLegWeight(cachedSolver.LeftLeg, BodySystem.TrackingLeftLegEnabled && cachedSolver.LeftLeg.target != null);
SetLegWeight(cachedSolver.RightLeg, BodySystem.TrackingRightLegEnabled && cachedSolver.RightLeg.target != null);
}
else
{
avatarVRIK.enabled = false;
cachedSolver.Solver.IKPositionWeight = 0f;
cachedSolver.Locomotion.weight = 0f;
cachedSolver.LeftLeg.useAnimatedBendNormal = false;
cachedSolver.RightLeg.useAnimatedBendNormal = false;
SetArmWeight(cachedSolver.LeftArm, false);
SetArmWeight(cachedSolver.RightArm, false);
SetLegWeight(cachedSolver.LeftLeg, false);
SetLegWeight(cachedSolver.RightLeg, false);
}
}
void ResetBodySystem()
{
// DesktopVRSwitch should handle this, but I am not pushing an update yet.
BodySystem.TrackingEnabled = true;
BodySystem.TrackingPositionWeight = 1f;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.isCalibrating = false;
BodySystem.isRecalibration = false;
}
void ResetAvatarLocalPosition()
{
// Reset avatar offset
avatarTransform.localPosition = Vector3.zero;
avatarTransform.localRotation = Quaternion.identity;
}
public void OnSetupAvatarDesktop(Animator animator)
{
if (!DesktopVRIK.EntryEnabled.Value) return;
// only run for humanoid avatars
if (animator != null && animator.avatar != null && animator.avatar.isHuman)
{
Calibrator.CalibrateDesktopVRIK(animator);
ResetBodySystem();
ResetDesktopVRIK();
}
}
public void OnSetupIKScaling(float scaleDifference)
{
_scaleDifference = scaleDifference;
VRIKUtils.ApplyScaleToVRIK
(
avatarVRIK,
calibrationData,
_scaleDifference
);
}
public void OnPlayerSetupUpdate(bool isEmotePlaying)
{
if (isEmotePlaying == _ikEmotePlaying) return;
_ikEmotePlaying = isEmotePlaying;
if (avatarLookAtIK != null)
avatarLookAtIK.enabled = !isEmotePlaying;
// Disable tracking completely while emoting
BodySystem.TrackingEnabled = !isEmotePlaying;
IKResetSolver();
ResetDesktopVRIK();
}
public void OnPlayerSetupSetSitting()
{
IKResetSolver();
ResetDesktopVRIK();
}
public void OnPlayerSetupResetIk()
{
// Check if PlayerSetup.ResetIk() was called for movement parent
CVRMovementParent currentParent = movementSystem._currentParent;
if (currentParent != null && currentParent._referencePoint != null)
{
// Get current position
var currentPosition = currentParent._referencePoint.position;
var currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f);
// Convert to delta position (how much changed since last frame)
var deltaPosition = currentPosition - _movementPosition;
var deltaRotation = Quaternion.Inverse(_movementRotation) * currentRotation;
// desktop pivots from playerlocal transform
var platformPivot = transform.position;
// Prevent targeting other parent position
if (_movementParent == currentParent)
{
// Add platform motion to IK solver
cachedSolver.Solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
ResetDesktopVRIK();
}
// Store for next frame
_movementParent = currentParent;
_movementPosition = currentPosition;
_movementRotation = currentRotation;
return;
}
// if not for movementparent, reset ik
IKResetSolver();
IKResetFootsteps();
ResetDesktopVRIK();
}
public void OnPreSolverUpdate()
{
// Set plant feet
cachedSolver.Solver.plantFeet = DesktopVRIK.EntryPlantFeet.Value;
// Apply custom VRIK solving effects
IKBodyLeaningOffset(_ikWeightLerp);
IKBodyHeadingOffset(_ikWeightLerp);
void IKBodyLeaningOffset(float weight)
{
// Emulate old VRChat hip movement
if (DesktopVRIK.EntryBodyLeanWeight.Value <= 0) return;
if (DesktopVRIK.EntryProneThrusting.Value) weight = 1f;
float weightedAngle = DesktopVRIK.EntryBodyLeanWeight.Value * weight;
float angle = _cameraTransform.localEulerAngles.x;
angle = angle > 180 ? angle - 360 : angle;
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right);
cachedSolver.Spine.headRotationOffset *= rotation;
}
void IKBodyHeadingOffset(float weight)
{
// Make root heading follow within a set limit
if (DesktopVRIK.EntryBodyHeadingLimit.Value <= 0) return;
float weightedAngleLimit = DesktopVRIK.EntryBodyHeadingLimit.Value * weight;
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _ikSimulatedRootAngle);
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
if (absDeltaAngleRoot > weightedAngleLimit)
{
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
_ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
}
cachedSolver.Spine.rootHeadingOffset = deltaAngleRoot;
if (DesktopVRIK.EntryPelvisHeadingWeight.Value > 0)
{
cachedSolver.Spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * DesktopVRIK.EntryPelvisHeadingWeight.Value, 0f);
cachedSolver.Spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * DesktopVRIK.EntryPelvisHeadingWeight.Value, 0f);
}
if (DesktopVRIK.EntryChestHeadingWeight.Value > 0)
{
cachedSolver.Spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * DesktopVRIK.EntryChestHeadingWeight.Value, 0f);
}
}
}
public void OnPostSolverUpdate()
{
if (!DesktopVRIK.EntryNetIKPass.Value) return;
Calibrator.ApplyNetIKPass();
}
void IKResetSolver()
{
cachedSolver.Solver.Reset();
}
void IKResetFootsteps()
{
// Reset footsteps immediatly to initial
if (!DesktopVRIK.EntryResetFootstepsOnIdle.Value) return;
VRIKUtils.ResetToInitialFootsteps
(
avatarVRIK,
calibrationData,
_scaleDifference
);
}
void ResetDesktopVRIK()
{
_ikSimulatedRootAngle = transform.eulerAngles.y;
}
}