using System; using System.Collections.Generic; using Unity.XR.CoreUtils.Bindings.Variables; using UnityEngine.Events; namespace UnityEngine.XR.Hands { /// /// Event type that contains a reference to an XR Hand that was updated. /// [Serializable] public sealed class HandUpdatedEvent : UnityEvent { } /// /// Extension methods to check if a flag is set in the enum. /// public static class UpdateTypesExtensions { /// /// Checks if the specific flag is set in this flags enum. /// /// The flags. /// The flag to check for. /// Returns if the flag is set in this flags enum. public static bool IsSet(this XRHandTrackingEvents.UpdateTypes self, XRHandTrackingEvents.UpdateTypes flag) { return (self & flag) == flag; } } /// /// Component that subscribes to hand tracking events from the for a particular /// and sends UnityEvents for tracking updates. /// [HelpURL(XRHelpURLConstants.k_XRHandTrackingEvents)] public class XRHandTrackingEvents : MonoBehaviour { /// /// Flags enum that specifies which update type or types to send events for. /// /// /// [Flags] public enum UpdateTypes { /// /// No update type. Will not send events. /// None = 0, /// /// See /// Dynamic = 1 << 0, /// /// See /// BeforeRender = 1 << 1, } [SerializeField] [Tooltip("The hand to track and send events for.")] Handedness m_Handedness = Handedness.Right; [SerializeField] [Tooltip("The update type to send events for when the pose and joints are updated.")] UpdateTypes m_UpdateType = UpdateTypes.Dynamic; [SerializeField] UnityEvent m_PoseUpdated = new UnityEvent(); [SerializeField] HandUpdatedEvent m_JointsUpdated = new HandUpdatedEvent(); [SerializeField] UnityEvent m_TrackingAcquired = new UnityEvent(); [SerializeField] UnityEvent m_TrackingLost = new UnityEvent(); [SerializeField] UnityEvent m_TrackingChanged = new UnityEvent(); /// /// The hand to track and send events for. /// public Handedness handedness { get => m_Handedness; set => m_Handedness = value; } /// /// Which update type to send events for when the pose and joints are updated. /// This can be one or multiple options from . /// /// public UpdateTypes updateType { get => m_UpdateType; set => m_UpdateType = value; } /// /// Whether the hand is currently being tracked, stored as a bindable variable that can be subscribed to for value changes. /// public IReadOnlyBindableVariable bindableHandIsTracked => m_HandIsTracked; /// /// Whether the hand is currently being tracked /// public bool handIsTracked => m_Subsystem != null && m_Subsystem.running && m_HandIsTracked.Value; /// /// The source of the hand tracking data. /// public XRHandSubsystem subsystem => m_Subsystem; /// /// The root pose of the hand. /// public Pose rootPose => m_HandJointsUpdatedEventArgs.hand.rootPose; /// /// Event that occurs when the joints of the hands have updated. /// /// /// The passed to each listener is only valid while the event is invoked, /// do not hold a reference to it. /// public HandUpdatedEvent jointsUpdated => m_JointsUpdated; /// /// Event that occurs when the pose of the hands has updated. /// public UnityEvent poseUpdated => m_PoseUpdated; /// /// Event that occurs when the hand tracking is acquired. /// public UnityEvent trackingAcquired => m_TrackingAcquired; /// /// Event that occurs when the hand tracking is lost. /// public UnityEvent trackingLost => m_TrackingLost; /// /// Event that occurs when the hand tracking is acquired (true) or lost (false). /// public UnityEvent trackingChanged => m_TrackingChanged; static readonly List k_SubsystemsReuse = new List(); readonly BindableVariable m_HandIsTracked = new BindableVariable(); XRHandSubsystem m_Subsystem; readonly XRHandJointsUpdatedEventArgs m_HandJointsUpdatedEventArgs = new XRHandJointsUpdatedEventArgs(); /// /// See . /// protected virtual void Update() { if (m_Subsystem != null && m_Subsystem.running) return; SubsystemManager.GetSubsystems(k_SubsystemsReuse); for (var i = 0; i < k_SubsystemsReuse.Count; ++i) { var handSubsystem = k_SubsystemsReuse[i]; if (handSubsystem.running) { SetSubsystem(handSubsystem); break; } } } /// /// See . /// protected virtual void OnDisable() { UnsubscribeFromSubsystem(); } void UnsubscribeFromSubsystem() { if (m_Subsystem != null) { m_Subsystem.trackingAcquired -= OnTrackingAcquired; m_Subsystem.trackingLost -= OnTrackingLost; m_Subsystem.updatedHands -= OnUpdatedHands; m_Subsystem = null; } } internal void SetSubsystem(XRHandSubsystem handSubsystem) { UnsubscribeFromSubsystem(); m_Subsystem = handSubsystem; XRHand hand; if (m_Handedness == Handedness.Left) hand = m_Subsystem.leftHand; else if (m_Handedness == Handedness.Right) hand = m_Subsystem.rightHand; else hand = default; m_HandJointsUpdatedEventArgs.hand = hand; m_Subsystem.trackingAcquired += OnTrackingAcquired; m_Subsystem.trackingLost += OnTrackingLost; m_Subsystem.updatedHands += OnUpdatedHands; TrackingAcquiredOrLost(hand.isTracked); } void OnTrackingAcquired(XRHand hand) { if (hand.handedness == m_Handedness) TrackingAcquiredOrLost(true); } void OnTrackingLost(XRHand hand) { if (hand.handedness == m_Handedness) TrackingAcquiredOrLost(false); } void TrackingAcquiredOrLost(bool isTracked) { m_HandIsTracked.Value = isTracked; if (m_HandIsTracked.Value) m_TrackingAcquired?.Invoke(); else m_TrackingLost?.Invoke(); m_TrackingChanged?.Invoke(isTracked); } void OnUpdatedHands(XRHandSubsystem handSubsystem, XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags, XRHandSubsystem.UpdateType updateEventType) { if (updateEventType == XRHandSubsystem.UpdateType.Dynamic && !m_UpdateType.IsSet(UpdateTypes.Dynamic) || updateEventType == XRHandSubsystem.UpdateType.BeforeRender && !m_UpdateType.IsSet(UpdateTypes.BeforeRender)) return; if (m_Handedness == Handedness.Left) { var leftJointsUpdated = (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints) != XRHandSubsystem.UpdateSuccessFlags.None; var leftRootPoseUpdated = (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.LeftHandRootPose) != XRHandSubsystem.UpdateSuccessFlags.None; if (leftJointsUpdated || leftRootPoseUpdated) { m_HandJointsUpdatedEventArgs.hand = handSubsystem.leftHand; m_HandJointsUpdatedEventArgs.subsystem = handSubsystem; } if (leftJointsUpdated) m_JointsUpdated?.Invoke(m_HandJointsUpdatedEventArgs); if (leftRootPoseUpdated) m_PoseUpdated?.Invoke(m_HandJointsUpdatedEventArgs.hand.rootPose); } else if (m_Handedness == Handedness.Right) { var rightJointsUpdated = (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.RightHandJoints) != XRHandSubsystem.UpdateSuccessFlags.None; var rightRootPoseUpdated = (updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.RightHandRootPose) != XRHandSubsystem.UpdateSuccessFlags.None; if (rightJointsUpdated || rightRootPoseUpdated) { m_HandJointsUpdatedEventArgs.hand = handSubsystem.rightHand; m_HandJointsUpdatedEventArgs.subsystem = handSubsystem; } if (rightJointsUpdated) m_JointsUpdated?.Invoke(m_HandJointsUpdatedEventArgs); if (rightRootPoseUpdated) m_PoseUpdated?.Invoke(m_HandJointsUpdatedEventArgs.hand.rootPose); } } } }