// --------------------------------------------------------------------------------------------------------------------
//
// 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
}
}