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); } }