using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.SubsystemsImplementation;
using UnityEngine.XR.Hands.Gestures;
using UnityEngine.XR.Hands.Processing;
using UnityEngine.XR.Hands.ProviderImplementation;
namespace UnityEngine.XR.Hands
{
///
/// A subsystem for detecting and tracking hands and their corresponding
/// joint pose data.
///
///
/// The XRHandSubsystem class is the main entry point for accessing hand tracking data
/// provided by an XR device. A provider implementation that reads tracking data from the
/// user's device and provides updates to this subsystem must also be available. The XR
/// Hands package includes a provider implementation for OpenXR.
///
/// Get an instance for this XRHandSubsystem from the active XR
/// loader, as described in [Get the XRHandSubsystem instance](xref:xrhands-access-data#get-instance).
///
/// For lowest latency, read the tracking data available from the
/// and properties in a delegate function assigned to the
/// callback. This callback is invoked twice per frame, once near
/// the MonoBehaviour.Update event and once near the
/// event. The update provides the lowest latency between
/// hand motion and rendering, but occurs too late to affect physics. In addition, trying to
/// perform too much work during the BeforeRender callback can negatively impact framerate.
/// For best results, update game logic affected by hand tracking in a
/// update and perform a final update of hand visuals in a
/// update.
///
/// Refer to [Hand tracking data](xref:xrhands-tracking-data) for more information.
///
public partial class XRHandSubsystem
: SubsystemWithProvider
{
///
/// Constructs a subsystem. Do not invoke directly.
///
///
/// Do not call this constructor if you are an application developer consuming hand tracking data.
/// Instead, get an instance for this XRHandTrackingSubsystem from the active XR
/// loader, as described in [Get the XRHandSubsystem instance](xref:xrhands-access-data#get-instance).
///
/// If you are implementing an XR hand data provider, call Create
/// on the or call
///
/// instead of invoking this constructor.
///
public XRHandSubsystem()
{
}
///
/// Gets the left that is being tracked by this
/// subsystem.
///
///
/// Check the property to determine what data
/// associated with this hand was successfully updated in the last update, if any.
/// The value is also passed to the callback
/// function assigned to .
///
/// Refer to [Hand data model](xref:xrhands-data-model) for a description of the
/// available hand tracking data.
///
public XRHand leftHand => m_LeftHand;
XRHand m_LeftHand;
internal unsafe void SetLeftHand(XRHand hand)
{
if (hand.m_Joints.GetUnsafePtr() != m_LeftHand.m_Joints.GetUnsafePtr())
throw new InvalidOperationException("Cannot overrwrite the left hand with a hand that was not first retrieved from the subsystem's leftHand property!");
m_LeftHand = hand;
}
///
/// Gets the right that is being tracked by this
/// subsystem.
///
///
/// Check the property to determine what data
/// associated with this hand was successfully updated in the last update, if any.
/// The value is also passed to the callback
/// function assigned to .
///
/// Refer to [Hand data model](xref:xrhands-data-model) for a description of the
/// available hand tracking data.
///
public XRHand rightHand => m_RightHand;
XRHand m_RightHand;
internal unsafe void SetRightHand(XRHand hand)
{
if (hand.m_Joints.GetUnsafePtr() != m_RightHand.m_Joints.GetUnsafePtr())
throw new InvalidOperationException("Cannot overrwrite the right hand with a hand that was not first retrieved from the subsystem's rightHand property!");
m_RightHand = hand;
}
///
/// Indicates which joints in the list are
/// supported by the current hand data provider.
///
///
/// Hand data providers might not support tracking every joint in the
/// list. This array contains an element for
/// each possible joint. A value of true indicates the the current provider
/// supports tracking the associated joint.
///
/// To get the correct array index for a joint, call
/// on the
/// in question.
///
/// Refer to [Get supported joints array](xref:xrhands-access-data#joint-layout)
/// for additional information.
///
/// This array will already be valid as soon as you have a reference to
/// a subsystem (in other words, it's filled out before the subsystem is
/// returned by a call to XRHandSubsystemDescriptor.Create).
///
public NativeArray jointsInLayout => m_JointsInLayout;
NativeArray m_JointsInLayout;
///
/// Describes what data on either hand was updated during the most recent hand update.
///
///
/// This property updated every time the hand data is updated, which only occurs while this
/// XRHandSubsystem is running.
///
/// The value is also passed to the callback
/// function assigned to .
///
/// The flags for the most recent update. Applies to the
/// and properties.
public UpdateSuccessFlags updateSuccessFlags { get; protected set; }
///
/// Describes what data on either hand was updated during the call.
///
///
[Flags]
public enum UpdateSuccessFlags
{
///
/// No data was successfully updated for either hand.
///
None = 0,
///
/// The root pose of was updated.
///
LeftHandRootPose = 1 << 0,
///
/// The joints in were updated.
///
LeftHandJoints = 1 << 1,
///
/// The root pose of was updated.
///
RightHandRootPose = 1 << 2,
///
/// The joints in were updated.
///
RightHandJoints = 1 << 3,
///
/// All possible valid data retrieved (hand root poses, and joints for both hands).
///
All = LeftHandRootPose | LeftHandJoints | RightHandRootPose | RightHandJoints
}
///
/// The timing of a hand update during a frame.
///
///
///
public enum UpdateType
{
///
/// Corresponds to timing similar or close to MonoBehaviour.Update.
///
Dynamic,
///
/// Corresponds to timing similar or close to .
///
BeforeRender
}
///
/// A callback invoked for each hand update.
///
///
/// This callback is invoked twice per frame, once near
/// the MonoBehaviour.Update event and once near the
/// event. The update provides the lowest latency between
/// hand motion and rendering, but occurs too late to affect physics. In addition, trying to
/// perform too much work during the BeforeRender callback can negatively impact framerate.
/// For best results, update game logic affected by hand tracking in a
/// update and update hand visuals in a
/// update.
///
/// The delegate assigned to this property must take three parameters, which have the
/// following types and assigned values when the callback is invoked:
///
/// * : contains a reference to this subsystem.
/// * : the flags indicating which data could be updated.
/// * : the update timing.
///
public Action updatedHands;
///
/// A callback invoked when the subsystem begins tracking a hand's root pose and joints.
///
///
/// This is called before .
///
/// The delegate assigned to this property must take one parameter of type
/// , which is assigned a reference to the hand whose tracking was acquired.
///
public Action trackingAcquired;
///
/// A callback invoked when the subsystem stops tracking a hand's root pose and joints.
///
///
/// This is called before .
///
/// The delegate assigned to this property must take one parameter of type
/// , which is assigned a reference to the hand whose tracking was lost.
///
public Action trackingLost;
///
/// Gets common hand gestures getters and callbacks for the left hand.
///
public XRCommonHandGestures leftHandCommonGestures => m_LeftHandCommonGestures;
///
/// Gets common hand gestures getters and callbacks for the right hand.
///
public XRCommonHandGestures rightHandCommonGestures => m_RightHandCommonGestures;
///
/// Request an update from the hand data provider. Application developers
/// consuming hand tracking data should not call this function.
///
///
/// Informs the provider which kind of timing the update is being
/// requested under.
///
///
/// Returns to describe what data was updated successfully.
///
///
/// This function must be called by the subsystem implementation to request an update from
/// the hand data provider.
///
/// When an update is complete, the updated data is available from the and
/// properties. The callback is invoked.
///
/// The update is performed immediately. If you request an update timing that occurs in the
/// future, for example, requesting from a
/// MonoBehaviour.Update function, then the provider predicts what the hand data
/// will be at the requested time.
///
/// If overriding this method in a derived type, it is expected that you
/// call base.TryUpdateHands(updateType) and return what it
/// returns.
///
public virtual unsafe UpdateSuccessFlags TryUpdateHands(UpdateType updateType)
{
if (!running)
return UpdateSuccessFlags.None;
updateSuccessFlags = provider.TryUpdateHands(
updateType,
ref m_LeftHand.m_RootPose,
m_LeftHand.m_Joints,
ref m_RightHand.m_RootPose,
m_RightHand.m_Joints);
XRFingerShapeMath.ClearFingerStateCache(Handedness.Left);
XRFingerShapeMath.ClearFingerStateCache(Handedness.Right);
var wasLeftHandTracked = m_LeftHand.isTracked;
var success = UpdateSuccessFlags.LeftHandRootPose | UpdateSuccessFlags.LeftHandJoints;
m_LeftHand.isTracked = (updateSuccessFlags & success) == success;
if (!wasLeftHandTracked && m_LeftHand.isTracked)
trackingAcquired?.Invoke(m_LeftHand);
else if (wasLeftHandTracked && !m_LeftHand.isTracked)
trackingLost?.Invoke(m_LeftHand);
var wasRightHandTracked = m_RightHand.isTracked;
success = UpdateSuccessFlags.RightHandRootPose | UpdateSuccessFlags.RightHandJoints;
m_RightHand.isTracked = (updateSuccessFlags & success) == success;
if (!wasRightHandTracked && m_RightHand.isTracked)
trackingAcquired?.Invoke(m_RightHand);
else if (wasRightHandTracked && !m_RightHand.isTracked)
trackingLost?.Invoke(m_RightHand);
preprocessJoints?.Invoke(this, updateSuccessFlags, updateType);
for (int processorIndex = 0; processorIndex < m_Processors.Count; ++processorIndex)
m_Processors[processorIndex].ProcessJoints(this, updateSuccessFlags, updateType);
if (updateType == UpdateType.Dynamic && provider.canSurfaceCommonPoseData)
{
if (subsystemDescriptor.supportsAimPose)
{
if (provider.TryGetAimPose(Handedness.Left, out var aimPoseLeft))
m_LeftHandCommonGestures.UpdateAimPose(aimPoseLeft);
else
m_LeftHandCommonGestures.InvalidateAimPose();
if (provider.TryGetAimPose(Handedness.Right, out var aimPoseRight))
m_RightHandCommonGestures.UpdateAimPose(aimPoseRight);
else
m_RightHandCommonGestures.InvalidateAimPose();
}
if (subsystemDescriptor.supportsAimActivateValue)
{
if (provider.TryGetAimActivateValue(Handedness.Left, out var aimActivateValueLeft))
m_LeftHandCommonGestures.UpdateAimActivateValue(aimActivateValueLeft);
else
m_LeftHandCommonGestures.InvalidateAimActivateValue();
if (provider.TryGetAimActivateValue(Handedness.Right, out var aimActivateValueRight))
m_RightHandCommonGestures.UpdateAimActivateValue(aimActivateValueRight);
else
m_RightHandCommonGestures.InvalidateAimActivateValue();
}
if (subsystemDescriptor.supportsGraspValue)
{
if (provider.TryGetGraspValue(Handedness.Left, out var graspValueLeft))
m_LeftHandCommonGestures.UpdateGraspValue(graspValueLeft);
else
m_LeftHandCommonGestures.InvalidateGraspValue();
if (provider.TryGetGraspValue(Handedness.Right, out var graspValueRight))
m_RightHandCommonGestures.UpdateGraspValue(graspValueRight);
else
m_RightHandCommonGestures.InvalidateGraspValue();
}
if (subsystemDescriptor.supportsGripPose)
{
if (provider.TryGetGripPose(Handedness.Left, out var gripPoseLeft))
m_LeftHandCommonGestures.UpdateGripPose(gripPoseLeft);
else
m_LeftHandCommonGestures.InvalidateGripPose();
if (provider.TryGetGripPose(Handedness.Right, out var gripPoseRight))
m_RightHandCommonGestures.UpdateGripPose(gripPoseRight);
else
m_RightHandCommonGestures.InvalidateGripPose();
}
if (subsystemDescriptor.supportsPinchPose)
{
if (provider.TryGetPinchPose(Handedness.Left, out var pinchPoseLeft))
m_LeftHandCommonGestures.UpdatePinchPose(pinchPoseLeft);
else
m_LeftHandCommonGestures.InvalidatePinchPose();
if (provider.TryGetPinchPose(Handedness.Right, out var pinchPoseRight))
m_RightHandCommonGestures.UpdatePinchPose(pinchPoseRight);
else
m_RightHandCommonGestures.InvalidatePinchPose();
}
if (subsystemDescriptor.supportsPinchValue)
{
if (provider.TryGetPinchValue(Handedness.Left, out var pinchValueLeft))
m_LeftHandCommonGestures.UpdatePinchValue(pinchValueLeft);
else
m_LeftHandCommonGestures.InvalidatePinchValue();
if (provider.TryGetPinchValue(Handedness.Right, out var pinchValueRight))
m_RightHandCommonGestures.UpdatePinchValue(pinchValueRight);
else
m_RightHandCommonGestures.InvalidatePinchValue();
}
if (subsystemDescriptor.supportsPokePose)
{
if (provider.TryGetPokePose(Handedness.Left, out var pokePoseLeft))
m_LeftHandCommonGestures.UpdatePokePose(pokePoseLeft);
else
m_LeftHandCommonGestures.InvalidatePokePose();
if (provider.TryGetPokePose(Handedness.Right, out var pokePoseRight))
m_RightHandCommonGestures.UpdatePokePose(pokePoseRight);
else
m_RightHandCommonGestures.InvalidatePokePose();
}
}
if (updatedHands != null)
updatedHands.Invoke(this, updateSuccessFlags, updateType);
#pragma warning disable 618
if (handsUpdated != null)
handsUpdated.Invoke(updateSuccessFlags, updateType);
#pragma warning restore 618
return updateSuccessFlags;
}
///
/// This is called after the subsystem retrieves joint data from the
/// provider, and before and s'
/// are called.
///
public Action preprocessJoints;
///
/// Registers a processor for hand joint data.
///
///
/// The processor to register for this .
///
///
/// The type of the processor to register.
///
public void RegisterProcessor(TProcessor processor)
where TProcessor : class, IXRHandProcessor
{
if (processor == null)
throw new ArgumentException("Processor cannot be null.", nameof(processor));
m_Processors.Add(processor);
m_Processors.Sort(CompareProcessors);
}
///
/// Unregisters a processor for hand joint data.
///
///
/// The processor to unregister from this .
///
///
/// The type of the processor to register.
///
public void UnregisterProcessor(TProcessor processor)
where TProcessor : class, IXRHandProcessor
{
m_Processors.Remove(processor);
}
///
/// Called by Unity before the subsystem is returned from a call to XRHandSubsystemDescriptor.Create.
///
protected override void OnCreate()
{
m_LeftHandCommonGestures = new XRCommonHandGestures(Handedness.Left);
m_RightHandCommonGestures = new XRCommonHandGestures(Handedness.Right);
m_JointsInLayout = new NativeArray(XRHandJointID.EndMarker.ToIndex(), Allocator.Persistent);
provider.GetHandLayout(m_JointsInLayout);
m_LeftHand = new XRHand(Handedness.Left, Allocator.Persistent);
m_RightHand = new XRHand(Handedness.Right, Allocator.Persistent);
for (int jointIndex = XRHandJointID.BeginMarker.ToIndex(); jointIndex < XRHandJointID.EndMarker.ToIndex(); ++jointIndex)
{
if (!m_JointsInLayout[jointIndex])
{
var id = XRHandJointIDUtility.FromIndex(jointIndex);
m_LeftHand.m_Joints[jointIndex] = XRHandProviderUtility.CreateJoint(
Handedness.Left,
XRHandJointTrackingState.WillNeverBeValid,
id,
Pose.identity);
m_RightHand.m_Joints[jointIndex] = XRHandProviderUtility.CreateJoint(
Handedness.Right,
XRHandJointTrackingState.WillNeverBeValid,
id,
Pose.identity);
}
}
for (var fingerID = XRHandFingerID.Thumb; fingerID <= XRHandFingerID.Little; ++fingerID)
XRFingerShapeMath.SetDefaultFingerShapeConfiguration(fingerID, provider.GetFingerShapeConfiguration(fingerID));
}
///
/// Called by Unity before the subsystem is fully destroyed during a call to XRHandSubsystem.Destroy.
///
protected override void OnDestroy()
{
base.OnDestroy();
m_LeftHand.Dispose();
m_RightHand.Dispose();
m_JointsInLayout.Dispose();
}
List m_Processors = new List();
XRCommonHandGestures m_LeftHandCommonGestures;
XRCommonHandGestures m_RightHandCommonGestures;
static int CompareProcessors(IXRHandProcessor a, IXRHandProcessor b)
=> a.callbackOrder.CompareTo(b.callbackOrder);
}
}