// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using UltimateXR.Avatar.Rig; using UltimateXR.Core; using UltimateXR.Manipulation.HandPoses; using UnityEngine; namespace UltimateXR.Avatar { public partial class UxrAvatar { #region Private Types & Data /// /// Stores the state of an hand. /// private class HandState { #region Public Types & Data /// /// Gets the current hand pose name or null if there is no current hand pose set. /// public string CurrentHandPoseName => CurrentHandPose?.PoseName; /// /// Gets the current hand pose. /// public UxrRuntimeHandPose CurrentHandPose => _currentHandPose; /// /// Gets the current blend value, if is a pose. /// public float CurrentBlendValue => _currentBlendValue; #endregion #region Public Methods /// /// Checks whether a given event would change the current state. /// /// Event arguments /// Whether the event would change the current state public bool IsChange(UxrAvatarHandPoseChangeEventArgs e) { if (CurrentHandPose == null || e.PoseName != CurrentHandPoseName) { return true; } if (CurrentHandPose.PoseType == UxrHandPoseType.Blend && Mathf.Abs(e.BlendValue - CurrentBlendValue) > BlendEpsilon) { return true; } return false; } /// /// Changes the current pose. /// /// New hand pose /// New blend value, if the given pose is public void SetPose(UxrRuntimeHandPose handPose, float blendValue) { if (CurrentHandPose != handPose) { _needsBlend = true; if (CurrentHandPose != null) { // Start smooth transition to new pose. _currentHandPoseFrom = CurrentHandPose; _currentHandPose = handPose; _blendTimer = PoseTransitionSeconds; } else { // First initialization: transition immediately. _currentHandPoseFrom = handPose; _currentHandPose = handPose; _blendTimer = -1.0f; } } _currentBlendValueFrom = CurrentBlendValue; if (!Mathf.Approximately(_currentBlendValue, blendValue) && CurrentHandPose != null && CurrentHandPose.PoseType == UxrHandPoseType.Blend) { _currentBlendValue = blendValue; _needsBlend = true; } } /// /// Updates the hand, mainly transitioning smoothly between poses. /// /// The avatar to update /// The hand to update /// Delta time in seconds public void Update(UxrAvatar avatar, UxrHandSide handSide, float deltaTime) { if (_blendTimer > 0.0f) { _blendTimer -= deltaTime; } if (_blendTimer < 0.0f) { _blendTimer = -1.0f; } if (_needsBlend) { // Blend the poses float t = Mathf.Clamp01(1.0f - _blendTimer / PoseTransitionSeconds); BlendPoses(avatar, handSide, t); } if (_blendTimer < 0.0f) { _needsBlend = false; } } #endregion #region Private Methods /// /// Updates the avatar hand, interpolating between the "from" and "to" poses. /// /// Avatar to update /// Hand to update /// Interpolation value [0.0, 1.0] private void BlendPoses(UxrAvatar avatar, UxrHandSide handSide, float t) { if (_currentHandPoseFrom == null || _currentHandPose == null) { return; } // Compute the hand descriptors to interpolate _currentDescriptorFrom.CopyFrom(_currentHandPoseFrom.GetHandDescriptor(handSide, UxrBlendPoseType.OpenGrip)); _currentDescriptor.CopyFrom(_currentHandPose.GetHandDescriptor(handSide, UxrBlendPoseType.OpenGrip)); // If any of the hand poses has a blend type, compute the blended pose first if (_currentHandPoseFrom.PoseType == UxrHandPoseType.Blend) { _currentDescriptorFrom.InterpolateTo(_currentHandPoseFrom.GetHandDescriptor(handSide, UxrBlendPoseType.ClosedGrip), _currentBlendValueFrom); } if (_currentHandPose.PoseType == UxrHandPoseType.Blend) { _currentDescriptor.InterpolateTo(_currentHandPose.GetHandDescriptor(handSide, UxrBlendPoseType.ClosedGrip), _currentBlendValue); } // Now interpolate between the two and update the hand transforms UxrAvatarRig.UpdateHandUsingRuntimeDescriptor(avatar, handSide, _currentDescriptorFrom, _currentDescriptor, t); } #endregion #region Private Types & Data /// /// Time in seconds it takes to transition from one pose to another. /// private const float PoseTransitionSeconds = 0.1f; /// /// Value that will be used as epsilon to compare two pose blend values. /// private const float BlendEpsilon = 0.005f; private readonly UxrRuntimeHandDescriptor _currentDescriptorFrom = new UxrRuntimeHandDescriptor(); private readonly UxrRuntimeHandDescriptor _currentDescriptor = new UxrRuntimeHandDescriptor(); private UxrRuntimeHandPose _currentHandPoseFrom; private UxrRuntimeHandPose _currentHandPose; private float _currentBlendValueFrom; private float _currentBlendValue; private float _blendTimer = -1.0f; private bool _needsBlend; #endregion } #endregion } }