using System.Collections.Generic; using UnityEngine.Events; #if XR_HANDS_1_1_OR_NEWER using UnityEngine.XR.Hands; #endif namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands { /// /// Behavior that provides events for when an starts and ends a poke gesture. The gesture is /// detected if the index finger is extended and the middle, ring, and little fingers are curled in. /// public class PokeGestureDetector : MonoBehaviour { [SerializeField] [Tooltip("Which hand to check for the poke gesture.")] #if XR_HANDS_1_1_OR_NEWER Handedness m_Handedness; #else int m_Handedness; #endif [SerializeField] [Tooltip("Called when the hand has started a poke gesture.")] UnityEvent m_PokeGestureStarted; [SerializeField] [Tooltip("Called when the hand has ended a poke gesture.")] UnityEvent m_PokeGestureEnded; #if XR_HANDS_1_1_OR_NEWER XRHandSubsystem m_Subsystem; bool m_IsPoking; static readonly List s_Subsystems = new List(); #endif /// /// See . /// protected void OnEnable() { #if XR_HANDS_1_1_OR_NEWER SubsystemManager.GetSubsystems(s_Subsystems); if (s_Subsystems.Count == 0) return; m_Subsystem = s_Subsystems[0]; m_Subsystem.updatedHands += OnUpdatedHands; #else Debug.LogError("Script requires XR Hands (com.unity.xr.hands) package. Install using Window > Package Manager or click Fix on the related issue in Edit > Project Settings > XR Plug-in Management > Project Validation.", this); #endif } /// /// See . /// protected void OnDisable() { #if XR_HANDS_1_1_OR_NEWER if (m_Subsystem == null) return; m_Subsystem.updatedHands -= OnUpdatedHands; m_Subsystem = null; #endif } #if XR_HANDS_1_1_OR_NEWER void OnUpdatedHands(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags, XRHandSubsystem.UpdateType updateType) { var wasPoking = m_IsPoking; switch (m_Handedness) { case Handedness.Left: if (!HasUpdateSuccessFlag(updateSuccessFlags, XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints)) return; var leftHand = subsystem.leftHand; m_IsPoking = IsIndexExtended(leftHand) && IsMiddleGrabbing(leftHand) && IsRingGrabbing(leftHand) && IsLittleGrabbing(leftHand); break; case Handedness.Right: if (!HasUpdateSuccessFlag(updateSuccessFlags, XRHandSubsystem.UpdateSuccessFlags.RightHandJoints)) return; var rightHand = subsystem.rightHand; m_IsPoking = IsIndexExtended(rightHand) && IsMiddleGrabbing(rightHand) && IsRingGrabbing(rightHand) && IsLittleGrabbing(rightHand); break; } if (m_IsPoking && !wasPoking) StartPokeGesture(); else if (!m_IsPoking && wasPoking) EndPokeGesture(); } /// /// Determines whether one or more bit fields are set in the flags. /// Non-boxing version of HasFlag for . /// /// The flags enum instance. /// The flag to check if set. /// Returns if the bit field or bit fields are set, otherwise returns . static bool HasUpdateSuccessFlag(XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateSuccessFlags successFlag) { return (successFlags & successFlag) == successFlag; } /// /// Returns true if the given hand's index finger tip is farther from the wrist than the index intermediate joint. /// /// Hand to check for the required pose. /// True if the given hand's index finger tip is farther from the wrist than the index intermediate joint, false otherwise. static bool IsIndexExtended(XRHand hand) { if (!(hand.GetJoint(XRHandJointID.Wrist).TryGetPose(out var wristPose) && hand.GetJoint(XRHandJointID.IndexTip).TryGetPose(out var tipPose) && hand.GetJoint(XRHandJointID.IndexIntermediate).TryGetPose(out var intermediatePose))) { return false; } var wristToTip = tipPose.position - wristPose.position; var wristToIntermediate = intermediatePose.position - wristPose.position; return wristToTip.sqrMagnitude > wristToIntermediate.sqrMagnitude; } /// /// Returns true if the given hand's middle finger tip is closer to the wrist than the middle proximal joint. /// /// Hand to check for the required pose. /// True if the given hand's middle finger tip is closer to the wrist than the middle proximal joint, false otherwise. static bool IsMiddleGrabbing(XRHand hand) { if (!(hand.GetJoint(XRHandJointID.Wrist).TryGetPose(out var wristPose) && hand.GetJoint(XRHandJointID.MiddleTip).TryGetPose(out var tipPose) && hand.GetJoint(XRHandJointID.MiddleProximal).TryGetPose(out var proximalPose))) { return false; } var wristToTip = tipPose.position - wristPose.position; var wristToProximal = proximalPose.position - wristPose.position; return wristToProximal.sqrMagnitude >= wristToTip.sqrMagnitude; } /// /// Returns true if the given hand's ring finger tip is closer to the wrist than the ring proximal joint. /// /// Hand to check for the required pose. /// True if the given hand's ring finger tip is closer to the wrist than the ring proximal joint, false otherwise. static bool IsRingGrabbing(XRHand hand) { if (!(hand.GetJoint(XRHandJointID.Wrist).TryGetPose(out var wristPose) && hand.GetJoint(XRHandJointID.RingTip).TryGetPose(out var tipPose) && hand.GetJoint(XRHandJointID.RingProximal).TryGetPose(out var proximalPose))) { return false; } var wristToTip = tipPose.position - wristPose.position; var wristToProximal = proximalPose.position - wristPose.position; return wristToProximal.sqrMagnitude >= wristToTip.sqrMagnitude; } /// /// Returns true if the given hand's little finger tip is closer to the wrist than the little proximal joint. /// /// Hand to check for the required pose. /// True if the given hand's little finger tip is closer to the wrist than the little proximal joint, false otherwise. static bool IsLittleGrabbing(XRHand hand) { if (!(hand.GetJoint(XRHandJointID.Wrist).TryGetPose(out var wristPose) && hand.GetJoint(XRHandJointID.LittleTip).TryGetPose(out var tipPose) && hand.GetJoint(XRHandJointID.LittleProximal).TryGetPose(out var proximalPose))) { return false; } var wristToTip = tipPose.position - wristPose.position; var wristToProximal = proximalPose.position - wristPose.position; return wristToProximal.sqrMagnitude >= wristToTip.sqrMagnitude; } void StartPokeGesture() { m_IsPoking = true; m_PokeGestureStarted.Invoke(); } void EndPokeGesture() { m_IsPoking = false; m_PokeGestureEnded.Invoke(); } #endif } }