using ABI.CCK.Components; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.MovementSystem; using NAK.DesktopVRIK.IK.IKHandlers; using NAK.DesktopVRIK.IK.VRIKHelpers; using RootMotion.FinalIK; using UnityEngine; namespace NAK.DesktopVRIK.IK; public class IKManager : MonoBehaviour { public static IKManager Instance; #region Variables private static VRIK _vrik; private static IKSolverVR _solver; private bool _isAvatarInitialized; // IK Handling private IKHandler _ikHandler; // Player Info internal Transform _desktopCamera; internal Transform _vrCamera; // Avatar Info private Animator _animator; internal Transform _hipTransform; // Animator Info private int _animLocomotionLayer = -1; private int _animIKPoseLayer = -1; private const string _locomotionLayerName = "Locomotion/Emotes"; private const string _ikposeLayerName = "IKPose"; // Pose Info internal HumanPoseHandler _humanPoseHandler; internal HumanPose _humanPose; internal HumanPose _humanPoseInitial; #endregion #region Unity Methods private void Start() { if (Instance != null) { Destroy(this); return; } Instance = this; _desktopCamera = PlayerSetup.Instance.desktopCamera.transform; _vrCamera = PlayerSetup.Instance.vrCamera.transform; } private void Update() { _ikHandler?.UpdateWeights(); } #endregion #region Avatar Events public void OnAvatarInitialized(GameObject inAvatar) { if (MetaPort.Instance.isUsingVr) return; if (_isAvatarInitialized) return; if (!inAvatar.TryGetComponent(out _animator)) return; if (_animator.avatar == null || !_animator.avatar.isHuman) return; _animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; _animIKPoseLayer = _animator.GetLayerIndex(_ikposeLayerName); _animLocomotionLayer = _animator.GetLayerIndex(_locomotionLayerName); _hipTransform = _animator.GetBoneTransform(HumanBodyBones.Hips); _humanPoseHandler?.Dispose(); _humanPoseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform); _humanPoseHandler.GetHumanPose(ref _humanPose); _humanPoseHandler.GetHumanPose(ref _humanPoseInitial); InitializeDesktopIk(); _isAvatarInitialized = true; } public void OnAvatarDestroyed() { if (!_isAvatarInitialized) return; _vrik = null; _solver = null; _animator = null; _animIKPoseLayer = -1; _animLocomotionLayer = -1; _hipTransform = null; _humanPoseHandler?.Dispose(); _humanPoseHandler = null; _ikHandler = null; _isAvatarInitialized = false; } #endregion #region Game Events public bool OnPlayerScaled(float scaleDifference) { if (_ikHandler == null) return false; DesktopVRIK.Logger.Msg(scaleDifference); _ikHandler.OnPlayerScaled(scaleDifference); return true; } public void OnPlayerSeatedStateChanged(bool isSitting) { if (_ikHandler == null) return; _ikHandler.Reset(); } public bool OnPlayerHandleMovementParent(CVRMovementParent movementParent) { if (_ikHandler == null) return false; _ikHandler.OnPlayerHandleMovementParent(movementParent, GetPlayerPosition()); return true; } public bool OnPlayerTeleported() { if (_ikHandler == null) return false; _ikHandler.Reset(); return true; } public void OnPlayerUpdate() { if (_ikHandler == null) return; _ikHandler.UpdateTracking(); } #endregion #region IK Initialization private void InitializeDesktopIk() { SetupIkGeneral(); IKCalibrator.ConfigureDesktopVrIk(_vrik); _ikHandler = new IKHandlerDesktop(_vrik); IKCalibrator.SetupHeadIKTarget(_vrik); InitializeIkGeneral(); _ikHandler.OnInitializeIk(); } private void SetupIkGeneral() { _animator.transform.position = GetPlayerPosition(); _animator.transform.rotation = GetPlayerRotation(); SetAvatarPose(AvatarPose.Default); _vrik = IKCalibrator.SetupVrIk(_animator); _solver = _vrik.solver; } private void InitializeIkGeneral() { SetAvatarPose(AvatarPose.IKPose); VRIKUtils.CalculateInitialIKScaling(_vrik, ref _ikHandler._locomotionData); VRIKUtils.CalculateInitialFootsteps(_vrik, ref _ikHandler._locomotionData); _solver.Initiate(_vrik.transform); // initiate a second time SetAvatarPose(AvatarPose.Initial); VRIKUtils.ApplyScaleToVRIK(_vrik, _ikHandler._locomotionData, 1f); _vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateGeneral); _vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktopSwimming); _vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdateGeneral); } #endregion #region Public Methods public bool IsAvatarInitialized() { return _isAvatarInitialized; } public Vector3 GetPlayerPosition() { if (!MetaPort.Instance.isUsingVr) return transform.position; Vector3 vrPosition = _vrCamera.transform.position; vrPosition.y = transform.position.y; return vrPosition; } public Quaternion GetPlayerRotation() { if (!MetaPort.Instance.isUsingVr) return transform.rotation; Vector3 vrForward = _vrCamera.transform.forward; vrForward.y = 0f; return Quaternion.LookRotation(vrForward, Vector3.up); } #endregion #region VRIK Solver Events General private void OnPreSolverUpdateDesktopSwimming() { if (_solver.IKPositionWeight < 0.9f) return; Vector3 headPosition = _animator.GetBoneTransform(HumanBodyBones.Head).position; // rotate hips around head bone to simulate diving if (MovementSystem.Instance._playerIsSubmerged) { const float maxAngleForward = 85f; const float maxAngleBackward = -85f; float lookAngle = _desktopCamera.localEulerAngles.x; if (lookAngle > 180) lookAngle -= 360; lookAngle = Mathf.Clamp(lookAngle, maxAngleBackward, maxAngleForward); float multiplier = Mathf.Lerp(0f, 1f, (MovementSystem.Instance.movementVector.magnitude - 0.5f) * 2f); // blend in when moving w/ sprint multiplier *= (-1f * MovementSystem.Instance.GetDepth()); // blend out when at surface _solver.IKPositionWeight = 1f - (-1f * multiplier); // disable IK completely when fast swimming (only applicable to DesktopVRIK) _hipTransform.RotateAround(headPosition, _animator.transform.right, lookAngle * multiplier); } // offset avatar hips so head bone is centered on avatar root // this fixes animations becoming scrunched up once VRIK solves! _hipTransform.position -= (headPosition - _animator.transform.position) with { y = 0f }; } 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++) { switch (BodySystem.boneResetMasks[i]) { case BodySystem.BoneResetMask.Never: break; case BodySystem.BoneResetMask.Spine: _humanPose.muscles[i] *= 1 - _solver.spine.pelvisPositionWeight; break; case BodySystem.BoneResetMask.LeftArm: _humanPose.muscles[i] *= 1 - _solver.leftArm.positionWeight; break; case BodySystem.BoneResetMask.RightArm: _humanPose.muscles[i] *= 1 - _solver.rightArm.positionWeight; break; case BodySystem.BoneResetMask.LeftLeg: _humanPose.muscles[i] *= 1 - _solver.leftLeg.positionWeight; break; case BodySystem.BoneResetMask.RightLeg: _humanPose.muscles[i] *= 1 - _solver.rightLeg.positionWeight; break; default: throw new ArgumentOutOfRangeException(); } } _humanPoseHandler.SetHumanPose(ref _humanPose); _hipTransform.position = hipPos; _hipTransform.rotation = hipRot; } // "NetIk Pass", or "Additional Humanoid Pass" hack private void OnPostSolverUpdateGeneral() { Vector3 hipPos = _hipTransform.position; Quaternion hipRot = _hipTransform.rotation; _humanPoseHandler.GetHumanPose(ref _humanPose); _humanPoseHandler.SetHumanPose(ref _humanPose); _hipTransform.position = hipPos; _hipTransform.rotation = hipRot; } #endregion #region Avatar Pose Utilities private enum AvatarPose { Default = 0, Initial = 1, IKPose = 2, TPose = 3, APose = 4 } private void SetAvatarPose(AvatarPose pose) { switch (pose) { case AvatarPose.Default: SetMusclesToValue(0f); break; case AvatarPose.Initial: if (HasCustomIKPose()) SetCustomLayersWeights(0f, 1f); _humanPoseHandler.SetHumanPose(ref _humanPoseInitial); break; case AvatarPose.IKPose: if (HasCustomIKPose()) { SetCustomLayersWeights(1f, 0f); return; } SetMusclesToPose(MusclePoses.IKPoseMuscles); break; case AvatarPose.TPose: SetMusclesToPose(MusclePoses.TPoseMuscles); break; case AvatarPose.APose: SetMusclesToPose(MusclePoses.APoseMuscles); break; } } private bool HasCustomIKPose() { return _animLocomotionLayer != -1 && _animIKPoseLayer != -1; } private void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight) { _animator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight); _animator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight); _animator.Update(0f); } private void SetMusclesToValue(float value) { _humanPoseHandler.GetHumanPose(ref _humanPose); for (var i = 0; i < BodySystem.boneResetMasks.Length; i++) { if (BodySystem.boneResetMasks[i] != BodySystem.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 < BodySystem.boneResetMasks.Length; i++) { if (BodySystem.boneResetMasks[i] != BodySystem.BoneResetMask.Never) _humanPose.muscles[i] = muscles[i]; } _humanPose.bodyPosition = Vector3.up; _humanPose.bodyRotation = Quaternion.identity; _humanPoseHandler.SetHumanPose(ref _humanPose); } #endregion }