// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using UltimateXR.Avatar; using UltimateXR.Avatar.Rig; using UltimateXR.Core; using UltimateXR.Core.Components.Composite; using UltimateXR.Manipulation.HandPoses; using UnityEngine; namespace UltimateXR.Devices.Visualization { /// /// Component that represents a hand holding a VR controller. It allows to graphically render a hand that /// mimics the interaction that the user performs on a VR controller. /// [DisallowMultipleComponent] public partial class UxrControllerHand : UxrAvatarComponent { #region Inspector Properties/Serialized Fields [SerializeField] private bool _hasAvatarSource; [SerializeField] private UxrAvatar _avatarPrefab; [SerializeField] private UxrHandSide _avatarHandSide; [SerializeField] private UxrHandPoseAsset _handPose; [SerializeField] private List _variations; [SerializeField] private UxrAvatarHand _hand; [SerializeField] private FingerIK _thumb; [SerializeField] private FingerIK _index; [SerializeField] private FingerIK _middle; [SerializeField] private FingerIK _ring; [SerializeField] private FingerIK _little; #endregion #region Public Types & Data /// /// Gets the hand references. /// public UxrAvatarHand Hand => _hand; /// /// Gets the hand variations, if there are any. Variations allow to provide different visual representations of the /// hand. It can be different objects and each object may have different materials. /// public IEnumerable Variations => _variations; #endregion #region Public Methods /// /// Initializes the component when the controller hand is dynamic, such as when used through an /// that changes poses. /// /// Avatar the hand belongs to. The current enabled pose will be used to initialize the finger IK. /// Which hand side public void InitializeFromCurrentHandPose(UxrAvatar avatar, UxrHandSide handSide) { UxrRuntimeHandPose runtimeHandPose = avatar.GetCurrentRuntimeHandPose(handSide); if (runtimeHandPose != null) { // Take snapshot of transforms var transforms = UxrAvatarRig.PushHandTransforms(avatar.GetHand(handSide)); // Briefly switch to pose. This is used because we may be in the middle of a transition and the pose hasn't been acquired yet. UxrAvatarRig.UpdateHandUsingRuntimeDescriptor(avatar, handSide, runtimeHandPose.GetHandDescriptor(handSide)); // Initialize IK InitializeFinger(_thumb, true); InitializeFinger(_index, true); InitializeFinger(_middle, true); InitializeFinger(_ring, true); InitializeFinger(_little, true); // Restore transforms from snapshot UxrAvatarRig.PopHandTransforms(avatar.GetHand(handSide), transforms); } } /// /// Updates a given finger. /// /// Finger to update /// Finger contact information public void UpdateFinger(UxrFingerType finger, UxrFingerContactInfo fingerContactInfo) { if (_fingers == null) { return; } if (_fingers.TryGetValue(finger, out FingerIK fingerInfo) && fingerInfo.Initialized && fingerInfo.FingerIKSolver) { Transform fingerIKParent = fingerInfo.FingerIKSolver.Links[0].Bone.parent; if (fingerInfo.CurrentFingerGoal != fingerContactInfo.Transform) { fingerInfo.CurrentFingerGoal = fingerContactInfo.Transform; fingerInfo.TimerToGoal = fingerInfo.FingerToGoalDuration; fingerInfo.LocalGoalTransitionStartPos = fingerIKParent.InverseTransformPoint(fingerInfo.FingerIKSolver.Goal.position); } if (fingerInfo.TimerToGoal > 0.0f) { fingerInfo.TimerToGoal -= Time.deltaTime; float t = 1.0f - Mathf.Clamp01(fingerInfo.TimerToGoal / fingerInfo.FingerToGoalDuration); fingerInfo.FingerIKSolver.Goal.position = Vector3.Lerp(fingerIKParent.TransformPoint(fingerInfo.LocalGoalTransitionStartPos), fingerContactInfo.Transform != null ? fingerContactInfo.Transform.position : fingerIKParent.TransformPoint(fingerInfo.LocalEffectorInitialPos), t); if (fingerContactInfo.Transform != null) { fingerInfo.FingerIKSolver.SolverEnabled = true; } } else { if (fingerContactInfo.Transform != null) { fingerInfo.FingerIKSolver.Goal.position = fingerContactInfo.Transform.position; } else { fingerInfo.FingerIKSolver.SolverEnabled = false; } } } } /// /// Allows to manually update the Inverse Kinematics of all fingers. /// public void UpdateIKManually() { foreach (KeyValuePair fingerPair in _fingers) { if (fingerPair.Value.FingerIKSolver && fingerPair.Value.FingerIKSolver.SolverEnabled) { fingerPair.Value.FingerIKSolver.SolveIK(); } } } #endregion #region Unity /// /// Generates the internal list of fingers. /// protected override void Awake() { base.Awake(); _fingers = new Dictionary(); _thumb.ComponentEnabled = _thumb.FingerIKSolver != null && _thumb.FingerIKSolver.enabled; _index.ComponentEnabled = _index.FingerIKSolver != null && _index.FingerIKSolver.enabled; _middle.ComponentEnabled = _middle.FingerIKSolver != null && _middle.FingerIKSolver.enabled; _ring.ComponentEnabled = _ring.FingerIKSolver != null && _ring.FingerIKSolver.enabled; _little.ComponentEnabled = _little.FingerIKSolver != null && _little.FingerIKSolver.enabled; _fingers.Add(UxrFingerType.Thumb, _thumb); _fingers.Add(UxrFingerType.Index, _index); _fingers.Add(UxrFingerType.Middle, _middle); _fingers.Add(UxrFingerType.Ring, _ring); _fingers.Add(UxrFingerType.Little, _little); } /// /// Enables the finger IK solvers. /// protected override void OnEnable() { base.OnEnable(); UxrManager.StageUpdating += UxrManager_StageUpdating; } /// /// Disables the finger IK solvers. /// protected override void OnDisable() { base.OnDisable(); UxrManager.StageUpdating -= UxrManager_StageUpdating; } /// /// Make sure the hand rig is allocated when the component is reset. /// protected override void Reset() { base.Reset(); _hand = new UxrAvatarHand(); } /// /// Initializes the fingers. /// protected override void Start() { base.Start(); if (Avatar == null) { UxrManager.LogMissingAvatarInHierarchyError(this); } InitializeFinger(_thumb); InitializeFinger(_index); InitializeFinger(_middle); InitializeFinger(_ring); InitializeFinger(_little); } #endregion #region Event Handling Methods /// /// Called when the is about to update a stage. /// /// private void UxrManager_StageUpdating(UxrUpdateStage stage) { if (stage == UxrUpdateStage.Animation) { // Check resetting bone transforms? } } #endregion #region Private Methods /// /// Initializes a finger. /// /// The finger to initialize /// /// Whether to recompute the link data. This is required if the initial pose for the IK /// changed /// private void InitializeFinger(FingerIK finger, bool recomputeLinkData = false) { if (finger.FingerIKSolver != null) { if (recomputeLinkData) { finger.FingerIKSolver.ComputeLinkData(); } else { // Initializing the finger for the first time from the component itself. // This makes sure that the data is computed with the initial finger bone orientations. finger.FingerIKSolver.RestoreInitialRotations(); } finger.Initialized = true; finger.FingerIKSolver.enabled = finger.ComponentEnabled; finger.LocalEffectorInitialPos = finger.FingerIKSolver.Links[0].Bone.parent.InverseTransformPoint(finger.FingerIKSolver.EndEffector.position); finger.FingerIKSolver.Goal.SetPositionAndRotation(finger.FingerIKSolver.EndEffector.position, finger.FingerIKSolver.EndEffector.rotation); finger.CurrentFingerGoal = null; finger.TimerToGoal = -1.0f; } } #endregion #region Private Types & Data private Dictionary _fingers; #endregion } }