using ABI.CCK.Components; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using NAK.AlternateIKSystem.IK.IKHandlers; using NAK.AlternateIKSystem.IK.WeightManipulators; using NAK.AlternateIKSystem.VRIKHelpers; using RootMotion.FinalIK; using UnityEngine; namespace NAK.AlternateIKSystem.IK; public class IKManager : MonoBehaviour { public static IKManager Instance; #region Variables public BodyControl BodyControl = new BodyControl(); public WeightManipulatorManager WeightManipulator = new WeightManipulatorManager(); 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; // IK Handling private IKHandler _ikHandler; // Player Info internal Transform _desktopCamera; internal Transform _vrCamera; internal Transform _vrHeadTarget; // Controller Info private Transform _leftController; private Transform _rightController; private Transform _leftHandTarget; private Transform _leftHandRotations; private Transform _rightHandTarget; private Transform _rightHandRotations; // Hand Anchor Offsets (the magic numbers that make or break the game) private readonly Vector3 _handAnchorPositionOffset = new Vector3(-0.048f, 0.0389f, -0.138f); private readonly Vector3 _handAnchorRotationOffset = new Vector3(70f, 0f, 50f); // Avatar Info private Animator _animator; private 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 private HumanPoseHandler _humanPoseHandler; private HumanPose _humanPose; private 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; _vrHeadTarget = PlayerSetup.Instance.vrHeadTracker.transform; _leftController = PlayerSetup.Instance.vrLeftHandTracker.transform; _rightController = PlayerSetup.Instance.vrRightHandTracker.transform; // ouchie _leftHandTarget = _leftController.Find("LeftHandTarget"); _leftHandRotations = _leftHandTarget.Find("LeftHandRotations"); _rightHandTarget = _rightController.Find("RightHandTarget"); _rightHandRotations = _rightHandTarget.Find("RightHandRotations"); WeightManipulator.AddOverride(new TrackingControlManipulator()); WeightManipulator.AddOverride(new DeviceControlManipulator()); BodyControl.Start(); } private void Update() { if (!_isAvatarInitialized) return; BodyControl.Update(); if (vrik.solver != null) WeightManipulator.UpdateWeights(vrik.solver); _ikHandler?.UpdateWeights(); } #endregion #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(); _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); 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); 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, GetPlayerPosition()); return true; } public bool OnPlayerTeleported() { if (!_isAvatarInitialized) return false; _vrik?.solver.Reset(); return true; } #endregion #region IK Initialization private void InitializeDesktopIk() { SetupIkGeneral(); IKCalibrator.ConfigureDesktopVrIk(_vrik); _ikHandler = new IKHandlerDesktop(_vrik); IKCalibrator.SetupHeadIKTarget(_vrik); InitializeIkGeneral(); _ikHandler.OnInitializeIk(); } private void InitializeHalfBodyIk() { SetupIkGeneral(); IKCalibrator.ConfigureHalfBodyVrIk(_vrik); _ikHandler = new IKHandlerHalfBody(_vrik); IKCalibrator.SetupHeadIKTarget(_vrik, _vrHeadTarget); IKCalibrator.SetupHandIKTarget(_vrik, _leftHandRotations, true); IKCalibrator.SetupHandIKTarget(_vrik, _rightHandRotations, false); // Configure controller offsets _leftHandTarget.localPosition = _handAnchorPositionOffset; _leftHandTarget.localEulerAngles = _handAnchorRotationOffset; _rightHandTarget.localPosition = Vector3.Scale(_handAnchorPositionOffset, new Vector3(-1, 1, 1)); _rightHandTarget.localEulerAngles = Vector3.Scale(_handAnchorRotationOffset, new Vector3(1, -1, -1)); InitializeIkGeneral(); _ikHandler.OnInitializeIk(); } private void SetupIkGeneral() { _animator.transform.position = GetPlayerPosition(); _animator.transform.rotation = GetPlayerRotation(); SetAvatarPose(AvatarPose.Default); _vrik = IKCalibrator.SetupVrIk(_animator); } private void InitializeIkGeneral() { SetAvatarPose(AvatarPose.IKPose); VRIKUtils.CalculateInitialIKScaling(_vrik, ref _ikHandler._locomotionData); VRIKUtils.CalculateInitialFootsteps(_vrik, ref _ikHandler._locomotionData); _vrik.solver.Initiate(_vrik.transform); // initiate a second time SetAvatarPose(AvatarPose.Initial); VRIKUtils.ApplyScaleToVRIK(_vrik, _ikHandler._locomotionData, 1f); _vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateGeneral); _vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdateGeneral); } #endregion #region Public Methods 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 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; } // "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 < 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 }