summaryrefslogtreecommitdiff
path: root/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts
diff options
context:
space:
mode:
authorpryazha <pryadeiniv@mail.ru>2025-07-02 08:46:23 -0700
committerpryazha <pryadeiniv@mail.ru>2025-07-02 08:46:23 -0700
commit8263edd59284aba390aca011d25b79efecef4c48 (patch)
tree6346e2afaaabd32156601cafaf20d4ee813befaf /Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts
Diffstat (limited to 'Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts')
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs141
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs49
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs73
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs263
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs126
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs185
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs200
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs133
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs121
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs33
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs.meta11
20 files changed, 1434 insertions, 0 deletions
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs
new file mode 100644
index 0000000..1dd5278
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs
@@ -0,0 +1,141 @@
+#if XR_HANDS_1_2_OR_NEWER
+using System.Collections.Generic;
+using UnityEngine.XR.Hands;
+using UnityEngine.XR.Hands.Processing;
+#endif
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// A post processor for XR hand tracking data, using the One Euro filter to smooth hand positions.
+ /// </summary>
+#if XR_HANDS_1_2_OR_NEWER
+ public class HandsOneEuroFilterPostProcessor : MonoBehaviour, IXRHandProcessor
+#else
+ public class HandsOneEuroFilterPostProcessor : MonoBehaviour
+#endif
+ {
+ [SerializeField]
+ [Tooltip("Smoothing amount at low speeds.")]
+#pragma warning disable CS0414 // Field assigned but its value is never used -- Keep to retain serialized value when XR Hands is not installed
+ float m_FilterMinCutoff = 0.1f;
+#pragma warning restore CS0414
+
+ [SerializeField]
+ [Tooltip("Filter's responsiveness to speed changes.")]
+#pragma warning disable CS0414 // Field assigned but its value is never used -- Keep to retain serialized value when XR Hands is not installed
+ float m_FilterBeta = 0.2f;
+#pragma warning restore CS0414
+
+#if XR_HANDS_1_2_OR_NEWER
+ /// <inheritdoc />
+ public int callbackOrder => 0;
+
+ readonly OneEuroFilterVector3 m_LeftHandFilter = new OneEuroFilterVector3(Vector3.zero);
+ readonly OneEuroFilterVector3 m_RightHandFilter = new OneEuroFilterVector3(Vector3.zero);
+
+ bool m_WasLeftHandTrackedLastFrame;
+ bool m_WasRightHandTrackedLastFrame;
+
+ XRHandSubsystem m_Subsystem;
+ static readonly List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
+#endif
+
+#if XR_HANDS_1_2_OR_NEWER
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnDisable()
+ {
+ if (m_Subsystem != null)
+ {
+ m_Subsystem.UnregisterProcessor(this);
+ m_Subsystem = null;
+ }
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Update()
+ {
+ if (m_Subsystem != null && m_Subsystem.running)
+ return;
+
+ SubsystemManager.GetSubsystems(s_SubsystemsReuse);
+ var foundRunningHandSubsystem = false;
+ for (var i = 0; i < s_SubsystemsReuse.Count; ++i)
+ {
+ var handSubsystem = s_SubsystemsReuse[i];
+ if (handSubsystem.running)
+ {
+ m_Subsystem?.UnregisterProcessor(this);
+ m_Subsystem = handSubsystem;
+ foundRunningHandSubsystem = true;
+ break;
+ }
+ }
+
+ if (!foundRunningHandSubsystem)
+ return;
+
+ m_WasLeftHandTrackedLastFrame = false;
+ m_WasRightHandTrackedLastFrame = false;
+ m_Subsystem.RegisterProcessor(this);
+ }
+
+ /// <inheritdoc />
+ public void ProcessJoints(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType)
+ {
+ var leftHand = subsystem.leftHand;
+ if (leftHand.isTracked)
+ {
+ var leftHandPose = leftHand.rootPose;
+ if (!m_WasLeftHandTrackedLastFrame)
+ {
+ m_LeftHandFilter.Initialize(leftHandPose.position);
+ }
+ else
+ {
+ var newLeftPosition = m_LeftHandFilter.Filter(leftHandPose.position, Time.deltaTime, m_FilterMinCutoff, m_FilterBeta);
+ var newLeftPose = new Pose(newLeftPosition, leftHandPose.rotation);
+
+ leftHand.SetRootPose(newLeftPose);
+ subsystem.SetCorrespondingHand(leftHand);
+ }
+ }
+
+ m_WasLeftHandTrackedLastFrame = leftHand.isTracked;
+
+ var rightHand = subsystem.rightHand;
+ if (rightHand.isTracked)
+ {
+ var rightHandPose = rightHand.rootPose;
+ if (!m_WasRightHandTrackedLastFrame)
+ {
+ m_RightHandFilter.Initialize(rightHandPose.position);
+ }
+ else
+ {
+ var newRightPosition = m_RightHandFilter.Filter(rightHandPose.position, Time.deltaTime, m_FilterMinCutoff, m_FilterBeta);
+ var newRightPose = new Pose(newRightPosition, rightHandPose.rotation);
+
+ rightHand.SetRootPose(newRightPose);
+ subsystem.SetCorrespondingHand(rightHand);
+ }
+ }
+
+ m_WasRightHandTrackedLastFrame = rightHand.isTracked;
+ }
+#else
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Awake()
+ {
+ Debug.LogWarning("HandsOneEuroFilterPostProcessor requires XR Hands (com.unity.xr.hands) 1.2.0 or newer. Disabling component.", this);
+ enabled = false;
+ }
+#endif
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs.meta
new file mode 100644
index 0000000..73b2b27
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HandsOneEuroFilterPostProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bc6980b6cb3b4f12b6b75074e4ef59f3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs
new file mode 100644
index 0000000..912894a
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs
@@ -0,0 +1,49 @@
+using UnityEngine.XR.Interaction.Toolkit.Interactors;
+using UnityEngine.XR.Interaction.Toolkit.Interactors.Visuals;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Hides the specified GameObject when the associated interactor is blocked by an interaction within its group.
+ /// </summary>
+ public class HideObjectWhenInteractorBlocked : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("The interactor that this component monitors for blockages.")]
+ XRBaseInteractor m_Interactor;
+
+ [SerializeField]
+ [Tooltip("The GameObject to hide when the interactor is blocked.")]
+ GameObject m_ObjectToHide;
+
+ ICurveInteractionDataProvider m_CurveInteractionDataProvider;
+ bool m_HasCurveDataProvider;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnEnable()
+ {
+ if (m_Interactor == null || m_ObjectToHide == null)
+ enabled = false;
+
+ m_HasCurveDataProvider = false;
+ if (m_Interactor is ICurveInteractionDataProvider provider)
+ {
+ m_CurveInteractionDataProvider = provider;
+ m_HasCurveDataProvider = true;
+ }
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Update()
+ {
+ if (m_HasCurveDataProvider)
+ m_ObjectToHide.SetActive(m_CurveInteractionDataProvider.isActive);
+ else
+ m_ObjectToHide.SetActive(m_Interactor.isActiveAndEnabled && !m_Interactor.IsBlockedByInteractionWithinGroup());
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs.meta
new file mode 100644
index 0000000..7413c99
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/HideObjectWhenInteractorBlocked.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2e989a75b2954bdab01ca618a30d5de6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs
new file mode 100644
index 0000000..26806fb
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs
@@ -0,0 +1,73 @@
+using System;
+using Unity.Mathematics;
+using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.Receiver.Primitives;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Affordance receiver applying a Vector3 (Float3) affordance theme to a Transform local position.
+ /// Broadcasts new affordance value with Unity Event.
+ /// </summary>
+ [AddComponentMenu("Affordance System/Receiver/Transformation/Local Position Offset Affordance Receiver", 12)]
+ [Obsolete("The Affordance System namespace and all associated classes have been deprecated. The existing affordance system will be moved, replaced and updated with a new interaction feedback system in a future version of XRI.")]
+ public class LocalPositionOffsetAffordanceReceiver : Vector3AffordanceReceiver
+ {
+ [SerializeField]
+ [Tooltip("Transform on which to apply a local translation value.")]
+ Transform m_TransformToTranslate;
+
+ /// <summary>
+ /// Transform on which to apply a local translation value.
+ /// </summary>
+ public Transform transformToTranslate
+ {
+ get => m_TransformToTranslate;
+ set
+ {
+ m_TransformToTranslate = value;
+ m_HasTransformToTranslate = m_TransformToTranslate != null;
+ }
+ }
+
+ bool m_HasTransformToTranslate;
+ float3 m_InitialOffset = float3.zero;
+
+ /// <inheritdoc/>
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+ m_HasTransformToTranslate = m_TransformToTranslate != null;
+ }
+
+ /// <inheritdoc/>
+ protected override float3 GetCurrentValueForCapture()
+ {
+ if (m_HasTransformToTranslate)
+ {
+ m_InitialOffset = m_TransformToTranslate.localPosition;
+ }
+
+ return float3.zero;
+ }
+
+ /// <inheritdoc/>
+ protected override void OnAffordanceValueUpdated(float3 newValue)
+ {
+ if (m_HasTransformToTranslate)
+ {
+ m_TransformToTranslate.localPosition = m_InitialOffset + newValue;
+ }
+
+ base.OnAffordanceValueUpdated(newValue);
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnValidate()
+ {
+ if (m_TransformToTranslate == null)
+ m_TransformToTranslate = transform;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs.meta
new file mode 100644
index 0000000..03d9955
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/LocalPositionOffsetAffordanceReceiver.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 865d01d2834c9cb4caa8f2c901104c2d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs
new file mode 100644
index 0000000..a4c142c
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs
@@ -0,0 +1,263 @@
+using System;
+using Unity.XR.CoreUtils.Bindings.Variables;
+using UnityEngine.Events;
+using UnityEngine.InputSystem;
+using UnityEngine.XR.Interaction.Toolkit.Inputs;
+#if XR_HANDS_1_1_OR_NEWER
+using UnityEngine.XR.Hands;
+#endif
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Behavior that provides events for when the system gesture starts and ends and when the
+ /// menu palm pinch gesture occurs while hand tracking is in use.
+ /// </summary>
+ /// <remarks>
+ /// See <see href="https://docs.unity3d.com/Packages/com.unity.xr.hands@1.1/manual/features/metahandtrackingaim.html">Meta Hand Tracking Aim</see>.
+ /// </remarks>
+ /// <seealso cref="MetaAimHand"/>
+ public class MetaSystemGestureDetector : MonoBehaviour
+ {
+ /// <summary>
+ /// The state of the system gesture.
+ /// </summary>
+ /// <seealso cref="systemGestureState"/>
+ public enum SystemGestureState
+ {
+ /// <summary>
+ /// The system gesture has fully ended.
+ /// </summary>
+ Ended,
+
+ /// <summary>
+ /// The system gesture has started or is ongoing. Typically, this means the user is looking at
+ /// their palm at eye level or has not yet released the palm pinch gesture or turned their hand around.
+ /// </summary>
+ Started,
+ }
+
+ [SerializeField]
+ InputActionProperty m_AimFlagsAction = new InputActionProperty(new InputAction(expectedControlType: "Integer"));
+
+ /// <summary>
+ /// The Input System action to read the Aim Flags.
+ /// </summary>
+ /// <remarks>
+ /// Typically a <b>Value</b> action type with an <b>Integer</b> control type with a binding to either:
+ /// <list type="bullet">
+ /// <item>
+ /// <description><c>&lt;MetaAimHand&gt;{LeftHand}/aimFlags</c></description>
+ /// </item>
+ /// <item>
+ /// <description><c>&lt;MetaAimHand&gt;{RightHand}/aimFlags</c></description>
+ /// </item>
+ /// </list>
+ /// </remarks>
+ public InputActionProperty aimFlagsAction
+ {
+ get => m_AimFlagsAction;
+ set
+ {
+ if (Application.isPlaying)
+ UnbindAimFlags();
+
+ m_AimFlagsAction = value;
+
+ if (Application.isPlaying && isActiveAndEnabled)
+ BindAimFlags();
+ }
+ }
+
+ [SerializeField]
+ UnityEvent m_SystemGestureStarted;
+
+ /// <summary>
+ /// Calls the methods in its invocation list when the system gesture starts, which typically occurs when
+ /// the user looks at their palm at eye level.
+ /// </summary>
+ /// <seealso cref="systemGestureEnded"/>
+ /// <seealso cref="MetaAimFlags.SystemGesture"/>
+ public UnityEvent systemGestureStarted
+ {
+ get => m_SystemGestureStarted;
+ set => m_SystemGestureStarted = value;
+ }
+
+ [SerializeField]
+ UnityEvent m_SystemGestureEnded;
+
+ /// <summary>
+ /// Calls the methods in its invocation list when the system gesture ends.
+ /// </summary>
+ /// <remarks>
+ /// This behavior postpones ending the system gesture until the user has turned their hand around.
+ /// In other words, it isn't purely based on the <see cref="MetaAimFlags.SystemGesture"/>
+ /// being cleared from the aim flags in order to better replicate the native visual feedback in the Meta Home menu.
+ /// </remarks>
+ /// <seealso cref="systemGestureStarted"/>
+ /// <seealso cref="MetaAimFlags.SystemGesture"/>
+ public UnityEvent systemGestureEnded
+ {
+ get => m_SystemGestureEnded;
+ set => m_SystemGestureEnded = value;
+ }
+
+ [SerializeField]
+ UnityEvent m_MenuPressed;
+
+ /// <summary>
+ /// Calls the methods in its invocation list when the menu button is triggered by a palm pinch gesture.
+ /// </summary>
+ /// <remarks>
+ /// This is triggered by the non-dominant hand, which is the one with the menu icon (&#x2630;).
+ /// The universal menu (Oculus icon) on the dominant hand does not trigger this event.
+ /// </remarks>
+ /// <seealso cref="MetaAimFlags.MenuPressed"/>
+ public UnityEvent menuPressed
+ {
+ get => m_MenuPressed;
+ set => m_MenuPressed = value;
+ }
+
+ /// <summary>
+ /// The state of the system gesture.
+ /// </summary>
+ /// <seealso cref="SystemGestureState"/>
+ /// <seealso cref="systemGestureStarted"/>
+ /// <seealso cref="systemGestureEnded"/>
+ public IReadOnlyBindableVariable<SystemGestureState> systemGestureState => m_SystemGestureState;
+
+ readonly BindableEnum<SystemGestureState> m_SystemGestureState = new BindableEnum<SystemGestureState>(checkEquality: false);
+
+#if XR_HANDS_1_1_OR_NEWER && (ENABLE_VR || UNITY_GAMECORE)
+ [NonSerialized] // NonSerialized is required to avoid an "Unsupported enum base type" error about the Flags enum being ulong
+ MetaAimFlags m_AimFlags;
+#endif
+
+ bool m_AimFlagsBound;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnEnable()
+ {
+ BindAimFlags();
+
+#if XR_HANDS_1_1_OR_NEWER
+#if ENABLE_VR || UNITY_GAMECORE
+ var action = m_AimFlagsAction.action;
+ if (action != null)
+ // Force invoking the events upon initialization to simplify making sure the callback's desired results are synced
+ UpdateAimFlags((MetaAimFlags)action.ReadValue<int>(), true);
+#endif
+#else
+ Debug.LogWarning("Script requires XR Hands (com.unity.xr.hands) package to monitor Meta Aim Flags. Install using Window > Package Manager or click Fix on the related issue in Edit > Project Settings > XR Plug-in Management > Project Validation.", this);
+ SetGestureState(SystemGestureState.Ended, true);
+#endif
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDisable()
+ {
+ UnbindAimFlags();
+ }
+
+ void BindAimFlags()
+ {
+ if (m_AimFlagsBound)
+ return;
+
+ var action = m_AimFlagsAction.action;
+ if (action == null)
+ return;
+
+ action.performed += OnAimFlagsActionPerformedOrCanceled;
+ action.canceled += OnAimFlagsActionPerformedOrCanceled;
+ m_AimFlagsBound = true;
+
+ m_AimFlagsAction.EnableDirectAction();
+ }
+
+ void UnbindAimFlags()
+ {
+ if (!m_AimFlagsBound)
+ return;
+
+ var action = m_AimFlagsAction.action;
+ if (action == null)
+ return;
+
+ m_AimFlagsAction.DisableDirectAction();
+
+ action.performed -= OnAimFlagsActionPerformedOrCanceled;
+ action.canceled -= OnAimFlagsActionPerformedOrCanceled;
+ m_AimFlagsBound = false;
+ }
+
+ void SetGestureState(SystemGestureState state, bool forceInvoke)
+ {
+ if (!forceInvoke && m_SystemGestureState.Value == state)
+ return;
+
+ m_SystemGestureState.Value = state;
+ switch (state)
+ {
+ case SystemGestureState.Ended:
+ m_SystemGestureEnded?.Invoke();
+ break;
+ case SystemGestureState.Started:
+ m_SystemGestureStarted?.Invoke();
+ break;
+ }
+ }
+
+#if XR_HANDS_1_1_OR_NEWER && (ENABLE_VR || UNITY_GAMECORE)
+ void UpdateAimFlags(MetaAimFlags value, bool forceInvoke = false)
+ {
+ var hadMenuPressed = (m_AimFlags & MetaAimFlags.MenuPressed) != 0;
+ m_AimFlags = value;
+ var hasSystemGesture = (m_AimFlags & MetaAimFlags.SystemGesture) != 0;
+ var hasMenuPressed = (m_AimFlags & MetaAimFlags.MenuPressed) != 0;
+ var hasValid = (m_AimFlags & MetaAimFlags.Valid) != 0;
+ var hasIndexPinching = (m_AimFlags & MetaAimFlags.IndexPinching) != 0;
+
+ if (!hadMenuPressed && hasMenuPressed)
+ {
+ m_MenuPressed?.Invoke();
+ }
+
+ if (hasSystemGesture || hasMenuPressed)
+ {
+ SetGestureState(SystemGestureState.Started, forceInvoke);
+ return;
+ }
+
+ if (hasValid)
+ {
+ SetGestureState(SystemGestureState.Ended, forceInvoke);
+ return;
+ }
+
+ // We want to keep the system gesture going when the user is still index pinching
+ // even though the SystemGesture flag is no longer set.
+ if (hasIndexPinching && m_SystemGestureState.Value != SystemGestureState.Ended)
+ {
+ SetGestureState(SystemGestureState.Started, forceInvoke);
+ return;
+ }
+
+ SetGestureState(SystemGestureState.Ended, forceInvoke);
+ }
+#endif
+
+ void OnAimFlagsActionPerformedOrCanceled(InputAction.CallbackContext context)
+ {
+#if XR_HANDS_1_1_OR_NEWER && (ENABLE_VR || UNITY_GAMECORE)
+ UpdateAimFlags((MetaAimFlags)context.ReadValue<int>());
+#endif
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs.meta
new file mode 100644
index 0000000..6a3e37b
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/MetaSystemGestureDetector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a83bc4aa48d0da648b49d0fd56690b25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs
new file mode 100644
index 0000000..2e0643c
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs
@@ -0,0 +1,126 @@
+using UnityEngine.XR.Interaction.Toolkit.Utilities;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Provides a means to smooth jittery <see cref="Vector3"/> signals.
+ /// This filter is particularly effective for small and rapid movements,
+ /// making it useful for applications like motion tracking or gesture recognition.
+ /// </summary>
+ /// <remarks>
+ /// The filtering process relies on two main parameters: <c>minCutoff</c> and <c>beta</c>.
+ /// <list type="bullet">
+ /// <item>
+ /// <term><c>minCutoff</c></term>
+ /// <description> primarily influences the smoothing at low speeds.</description>
+ /// </item>
+ /// <item>
+ /// <term><c>beta</c></term>
+ /// <description> determines the filter's responsiveness to speed changes.</description>
+ /// </item>
+ /// </list>
+ /// </remarks>
+ public class OneEuroFilterVector3
+ {
+ Vector3 m_LastRawValue;
+ Vector3 m_LastFilteredValue;
+ readonly float m_MinCutoff;
+ readonly float m_Beta;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OneEuroFilterVector3"/> with specified cutoff and beta values.
+ /// </summary>
+ /// <param name="initialRawValue">The initial raw value for the filter.</param>
+ /// <param name="minCutoff">The minimum cutoff value for the filter. Default is 0.1f.</param>
+ /// <param name="beta">The beta value for the filter. Default is 0.02f.</param>
+ /// <remarks>
+ /// Filter parameters:
+ /// <list type="bullet">
+ /// <item>
+ /// <term><paramref name="minCutoff"/></term>
+ /// <description>
+ /// Controls the amount of smoothing at low speeds. A smaller value will introduce
+ /// more smoothing and potential lag, helping to reduce low-frequency jitter. A larger value
+ /// may feel more responsive but can let through more jitter. It's advised to start with a
+ /// value around 0.1 for masking jitter in movements of about 1 cm.
+ /// </description>
+ /// </item>
+ /// <item>
+ /// <term><paramref name="beta"/></term>
+ /// <description>
+ /// Determines the filter's adjustment to speed changes. A smaller value provides consistent
+ /// smoothing, while a larger one introduces more aggressive adjustments for speed changes, offering
+ /// responsive filtering at high speeds. A starting value of 0.02 is recommended, but fine-tuning
+ /// might be necessary based on specific use cases.
+ /// </description>
+ /// </item>
+ /// </list>
+ /// </remarks>
+ /// <seealso cref="Initialize"/>
+ public OneEuroFilterVector3(Vector3 initialRawValue, float minCutoff = 0.1f, float beta = 0.02f)
+ {
+ m_LastRawValue = initialRawValue;
+ m_LastFilteredValue = initialRawValue;
+ m_MinCutoff = minCutoff;
+ m_Beta = beta;
+ }
+
+ /// <summary>
+ /// Resets the initial raw value. Useful to recover from tracking loss.
+ /// </summary>
+ /// <param name="initialRawValue">Raw value to reset filtering basis to.</param>
+ public void Initialize(Vector3 initialRawValue)
+ {
+ m_LastRawValue = initialRawValue;
+ m_LastFilteredValue = initialRawValue;
+ }
+
+ /// <summary>
+ /// Filters the given <see cref="Vector3"/> rawValue using the internal minCutoff and beta parameters.
+ /// </summary>
+ /// <param name="rawValue">The raw <see cref="Vector3"/> value to be filtered.</param>
+ /// <param name="deltaTime">The time since the last filter update.</param>
+ /// <returns>The filtered <see cref="Vector3"/> value.</returns>
+ public Vector3 Filter(Vector3 rawValue, float deltaTime)
+ {
+ return Filter(rawValue, deltaTime, m_MinCutoff, m_Beta);
+ }
+
+ /// <summary>
+ /// Filters the given <see cref="Vector3"/> rawValue using provided minCutoff and beta parameters.
+ /// This method computes the speed of change in the signal and dynamically adjusts the amount of smoothing
+ /// based on the speed and the provided minCutoff and beta values.
+ /// </summary>
+ /// <param name="rawValue">The raw <see cref="Vector3"/> value to be filtered.</param>
+ /// <param name="deltaTime">The time since the last filter update.</param>
+ /// <param name="minCutoff">The minimum cutoff value for the filter. Influences the amount of smoothing at low speeds.</param>
+ /// <param name="beta">Determines the filter's adjustment to speed changes, influencing its responsiveness.</param>
+ /// <returns>The filtered <see cref="Vector3"/> value.</returns>
+ public Vector3 Filter(Vector3 rawValue, float deltaTime, float minCutoff, float beta)
+ {
+ // Calculate speed as a Vector3
+ Vector3 speed = (rawValue - m_LastRawValue) / deltaTime;
+
+ // Compute cutoffs for x, y, and z
+ Vector3 cutoffs = new Vector3(minCutoff, minCutoff, minCutoff);
+ Vector3 betaValues = new Vector3(beta, beta, beta);
+
+ // Incorporate speed into the cutoffs
+ Vector3 combinedCutoffs = cutoffs + Vector3.Scale(betaValues, speed);
+
+ // Compute alpha for x, y, and z
+ BurstMathUtility.FastSafeDivide(Vector3.one, Vector3.one + combinedCutoffs, out Vector3 alpha);
+
+ Vector3 rawFiltered = Vector3.Scale(alpha, rawValue);
+ Vector3 lastFiltered = Vector3.Scale(Vector3.one - alpha, m_LastFilteredValue);
+
+ // Calculate the final filtered value
+ Vector3 filteredValue = rawFiltered + lastFiltered;
+
+ m_LastRawValue = rawValue;
+ m_LastFilteredValue = filteredValue;
+
+ return filteredValue;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs.meta
new file mode 100644
index 0000000..c5cfc42
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/OneEuroFilterVector3.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8cf24614c456bc04c9adb820d19e35c5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs
new file mode 100644
index 0000000..0e4e4e5
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs
@@ -0,0 +1,185 @@
+#if XR_HANDS_1_2_OR_NEWER
+using Unity.XR.CoreUtils.Bindings;
+using UnityEngine.XR.Hands;
+using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
+#endif
+using UnityEngine.XR.Interaction.Toolkit.Interactors;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// A class that follows the pinch point between the thumb and index finger using XR Hand Tracking.
+ /// It updates its position to the midpoint between the thumb and index tip while optionally adjusting its rotation
+ /// to look at a specified target. The rotation towards the target can also be smoothly interpolated over time.
+ /// </summary>
+ public class PinchPointFollow : MonoBehaviour
+ {
+ [Header("Events")]
+ [SerializeField]
+ [Tooltip("The XR Hand Tracking Events component that will be used to subscribe to hand tracking events.")]
+#if XR_HANDS_1_2_OR_NEWER
+ XRHandTrackingEvents m_XRHandTrackingEvents;
+#else
+ Object m_XRHandTrackingEvents;
+#endif
+
+ [Header("Interactor reference (Pick one)")]
+ [SerializeField]
+ [Tooltip("The transform will use the XRRayInteractor endpoint position to calculate the transform rotation.")]
+ XRRayInteractor m_RayInteractor;
+
+ [SerializeField]
+ [Tooltip("The transform will use the NearFarInteractor endpoint position to calculate the transform rotation.")]
+ NearFarInteractor m_NearFarInteractor;
+
+ [Header("Rotation Config")]
+ [SerializeField]
+ [Tooltip("The transform to match the rotation of.")]
+ Transform m_TargetRotation;
+
+ [SerializeField]
+ [Tooltip("How fast to match rotation (0 means no rotation smoothing.)")]
+ [Range(0f, 32f)]
+#pragma warning disable CS0414 // Field assigned but its value is never used -- Keep to retain serialized value when XR Hands is not installed
+ float m_RotationSmoothingSpeed = 12f;
+#pragma warning restore CS0414
+
+#if XR_HANDS_1_2_OR_NEWER
+ bool m_HasTargetRotationTransform;
+ IXRRayProvider m_RayProvider;
+ bool m_HasRayProvider;
+ OneEuroFilterVector3 m_OneEuroFilterVector3;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ readonly QuaternionTweenableVariable m_QuaternionTweenableVariable = new QuaternionTweenableVariable();
+#pragma warning restore CS0618 // Type or member is obsolete
+ readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
+#endif
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnEnable()
+ {
+#if XR_HANDS_1_2_OR_NEWER
+ if (m_XRHandTrackingEvents != null)
+ m_XRHandTrackingEvents.jointsUpdated.AddListener(OnJointsUpdated);
+
+ m_OneEuroFilterVector3 = new OneEuroFilterVector3(transform.localPosition);
+ if (m_RayInteractor != null)
+ {
+ m_RayProvider = m_RayInteractor;
+ m_HasRayProvider = true;
+ }
+ if (m_NearFarInteractor != null)
+ {
+ m_RayProvider = m_NearFarInteractor;
+ m_HasRayProvider = true;
+ }
+ m_HasTargetRotationTransform = m_TargetRotation != null;
+ m_BindingsGroup.AddBinding(m_QuaternionTweenableVariable.Subscribe(newValue => transform.rotation = newValue));
+#else
+ Debug.LogWarning("PinchPointFollow requires XR Hands (com.unity.xr.hands) 1.2.0 or newer. Disabling component.", this);
+ enabled = false;
+#endif
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnDisable()
+ {
+#if XR_HANDS_1_2_OR_NEWER
+ m_BindingsGroup.Clear();
+ if (m_XRHandTrackingEvents != null)
+ m_XRHandTrackingEvents.jointsUpdated.RemoveListener(OnJointsUpdated);
+#endif
+ }
+
+#if XR_HANDS_1_2_OR_NEWER
+ static bool TryGetPinchPosition(XRHandJointsUpdatedEventArgs args, out Vector3 position)
+ {
+#if XR_HANDS_1_5_OR_NEWER
+ if (args.subsystem != null)
+ {
+ var commonHandGestures = args.hand.handedness == Handedness.Left
+ ? args.subsystem.leftHandCommonGestures
+ : args.hand.handedness == Handedness.Right
+ ? args.subsystem.rightHandCommonGestures
+ : null;
+ if (commonHandGestures != null && commonHandGestures.TryGetPinchPose(out var pinchPose))
+ {
+ // Protect against platforms returning bad data like (NaN, NaN, NaN)
+ if (!float.IsNaN(pinchPose.position.x) &&
+ !float.IsNaN(pinchPose.position.y) &&
+ !float.IsNaN(pinchPose.position.z))
+ {
+ position = pinchPose.position;
+ return true;
+ }
+ }
+ }
+#endif
+
+ var thumbTip = args.hand.GetJoint(XRHandJointID.ThumbTip);
+ if (!thumbTip.TryGetPose(out var thumbTipPose))
+ {
+ position = Vector3.zero;
+ return false;
+ }
+
+ var indexTip = args.hand.GetJoint(XRHandJointID.IndexTip);
+ if (!indexTip.TryGetPose(out var indexTipPose))
+ {
+ position = Vector3.zero;
+ return false;
+ }
+
+ position = Vector3.Lerp(thumbTipPose.position, indexTipPose.position, 0.5f);
+ return true;
+ }
+
+ void OnJointsUpdated(XRHandJointsUpdatedEventArgs args)
+ {
+ if (!TryGetPinchPosition(args, out var targetPos))
+ return;
+
+ var filteredTargetPos = m_OneEuroFilterVector3.Filter(targetPos, Time.deltaTime);
+
+ // Hand pose data is in local space relative to the XR Origin.
+ transform.localPosition = filteredTargetPos;
+
+ if (m_HasTargetRotationTransform && m_HasRayProvider)
+ {
+ // Given that the ray endpoint is in world space, we need to use the world space transform of this point to determine the target rotation.
+ // This allows us to keep orientation consistent when moving the XR Origin for locomotion.
+ var targetDir = (m_RayProvider.rayEndPoint - transform.position).normalized;
+ if (targetDir != Vector3.zero)
+ {
+ // Use the parent Transform's up vector if available, otherwise use the world up vector.
+ // The assumption is the parent Transform matches the XR Origin rotation.
+ // This allows the XR Origin to teleport to angled surfaces or upside down surfaces
+ // and the visual will still be correct relative to the application's ground.
+ var upwards = Vector3.up;
+ var parentTransform = transform.parent;
+ if (!(parentTransform is null))
+ upwards = parentTransform.up;
+
+ var targetRot = Quaternion.LookRotation(targetDir, upwards);
+
+ // If there aren't any major swings in rotation, follow the target rotation.
+ if (Vector3.Dot(m_TargetRotation.forward, targetDir) > 0.5f)
+ m_QuaternionTweenableVariable.target = targetRot;
+ }
+ else
+ {
+ m_QuaternionTweenableVariable.target = m_TargetRotation.rotation;
+ }
+
+ var tweenTarget = m_RotationSmoothingSpeed > 0f ? m_RotationSmoothingSpeed * Time.deltaTime : 1f;
+ m_QuaternionTweenableVariable.HandleTween(tweenTarget);
+ }
+ }
+#endif
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs.meta
new file mode 100644
index 0000000..3e39de3
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PinchPointFollow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8693657abb5062a40a80ba3cb86ef181
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs
new file mode 100644
index 0000000..85bfa1b
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs
@@ -0,0 +1,200 @@
+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
+{
+ /// <summary>
+ /// Behavior that provides events for when an <see cref="XRHand"/> 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.
+ /// </summary>
+ 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<XRHandSubsystem> s_Subsystems = new List<XRHandSubsystem>();
+#endif
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ 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
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ 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();
+ }
+
+ /// <summary>
+ /// Determines whether one or more bit fields are set in the flags.
+ /// Non-boxing version of <c>HasFlag</c> for <see cref="XRHandSubsystem.UpdateSuccessFlags"/>.
+ /// </summary>
+ /// <param name="successFlags">The flags enum instance.</param>
+ /// <param name="successFlag">The flag to check if set.</param>
+ /// <returns>Returns <see langword="true"/> if the bit field or bit fields are set, otherwise returns <see langword="false"/>.</returns>
+ static bool HasUpdateSuccessFlag(XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateSuccessFlags successFlag)
+ {
+ return (successFlags & successFlag) == successFlag;
+ }
+
+ /// <summary>
+ /// Returns true if the given hand's index finger tip is farther from the wrist than the index intermediate joint.
+ /// </summary>
+ /// <param name="hand">Hand to check for the required pose.</param>
+ /// <returns>True if the given hand's index finger tip is farther from the wrist than the index intermediate joint, false otherwise.</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns true if the given hand's middle finger tip is closer to the wrist than the middle proximal joint.
+ /// </summary>
+ /// <param name="hand">Hand to check for the required pose.</param>
+ /// <returns>True if the given hand's middle finger tip is closer to the wrist than the middle proximal joint, false otherwise.</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns true if the given hand's ring finger tip is closer to the wrist than the ring proximal joint.
+ /// </summary>
+ /// <param name="hand">Hand to check for the required pose.</param>
+ /// <returns>True if the given hand's ring finger tip is closer to the wrist than the ring proximal joint, false otherwise.</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns true if the given hand's little finger tip is closer to the wrist than the little proximal joint.
+ /// </summary>
+ /// <param name="hand">Hand to check for the required pose.</param>
+ /// <returns>True if the given hand's little finger tip is closer to the wrist than the little proximal joint, false otherwise.</returns>
+ 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
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs.meta
new file mode 100644
index 0000000..591d36e
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/PokeGestureDetector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dbac611a2982409ab5f5e604f53bcad0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs
new file mode 100644
index 0000000..e286cbf
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs
@@ -0,0 +1,133 @@
+using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// An input button reader based on another <see cref="XRInputButtonReader"/> and holds it true until falling below a lower release threshold.
+ /// Useful with hand interaction because the bool select value can bounce when the hand is near the tight internal threshold,
+ /// so using this will keep the pinch true until moving the fingers much further away than the pinch activation threshold.
+ /// </summary>
+ [DefaultExecutionOrder(XRInteractionUpdateOrder.k_XRInputDeviceButtonReader)]
+ public class ReleaseThresholdButtonReader : MonoBehaviour, IXRInputButtonReader
+ {
+ [SerializeField]
+ [Tooltip("The source input that this component reads to create a processed button value.")]
+ XRInputButtonReader m_ValueInput = new XRInputButtonReader("Value");
+
+ /// <summary>
+ /// The source input that this component reads to create a processed button value.
+ /// </summary>
+ public XRInputButtonReader valueInput
+ {
+ get => m_ValueInput;
+ set => XRInputReaderUtility.SetInputProperty(ref m_ValueInput, value, this);
+ }
+
+ [SerializeField]
+ [Tooltip("The threshold value to use to determine when the button is pressed. Considered pressed equal to or greater than this value.")]
+ [Range(0f, 1f)]
+ float m_PressThreshold = 0.8f;
+
+ /// <summary>
+ /// The threshold value to use to determine when the button is pressed. Considered pressed equal to or greater than this value.
+ /// </summary>
+ /// <remarks>
+ /// This reader will also be considered performed if the source input is performed.
+ /// </remarks>
+ public float pressThreshold
+ {
+ get => m_PressThreshold;
+ set => m_PressThreshold = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The threshold value to use to determine when the button is released when it was previously pressed. Keeps being pressed until falls back to a value of or below this value.")]
+ [Range(0f, 1f)]
+ float m_ReleaseThreshold = 0.25f;
+
+ /// <summary>
+ /// The threshold value to use to determine when the button is released when it was previously pressed.
+ /// Keeps being pressed until falls back to a value of or below this value.
+ /// </summary>
+ /// <remarks>
+ /// This reader will still be considered performed if the source input is still performed
+ /// when this threshold is reached.
+ /// </remarks>
+ public float releaseThreshold
+ {
+ get => m_ReleaseThreshold;
+ set => m_ReleaseThreshold = value;
+ }
+
+ bool m_IsPerformed;
+ bool m_WasPerformedThisFrame;
+ bool m_WasCompletedThisFrame;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnEnable()
+ {
+ m_ValueInput?.EnableDirectActionIfModeUsed();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnDisable()
+ {
+ m_ValueInput?.DisableDirectActionIfModeUsed();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Update()
+ {
+ // Go true when either the press threshold is reached or the bool is already performed.
+ // Only drop back to false when the release threshold is reached and the bool is no longer performed.
+ var prevPerformed = m_IsPerformed;
+ var pressAmount = m_ValueInput.ReadValue();
+
+ bool newValue;
+ if (prevPerformed)
+ newValue = pressAmount > m_ReleaseThreshold || m_ValueInput.ReadIsPerformed();
+ else
+ newValue = pressAmount >= m_PressThreshold || m_ValueInput.ReadIsPerformed();
+
+ m_IsPerformed = newValue;
+ m_WasPerformedThisFrame = !prevPerformed && m_IsPerformed;
+ m_WasCompletedThisFrame = prevPerformed && !m_IsPerformed;
+ }
+
+ /// <inheritdoc />
+ public bool ReadIsPerformed()
+ {
+ return m_IsPerformed;
+ }
+
+ /// <inheritdoc />
+ public bool ReadWasPerformedThisFrame()
+ {
+ return m_WasPerformedThisFrame;
+ }
+
+ /// <inheritdoc />
+ public bool ReadWasCompletedThisFrame()
+ {
+ return m_WasCompletedThisFrame;
+ }
+
+ /// <inheritdoc />
+ public float ReadValue()
+ {
+ return m_ValueInput.ReadValue();
+ }
+
+ /// <inheritdoc />
+ public bool TryReadValue(out float value)
+ {
+ return m_ValueInput.TryReadValue(out value);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs.meta
new file mode 100644
index 0000000..cd19e72
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ReleaseThresholdButtonReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 63f61d1c82c9fc6429ebd4791a4d6817
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs
new file mode 100644
index 0000000..828ccab
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs
@@ -0,0 +1,121 @@
+using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Example class that reads a float value from an <see cref="XRInputValueReader"/> and converts it to a bool.
+ /// Useful with hand interaction because the bool select value can be unreliable when the hand is near the tight internal threshold.
+ /// </summary>
+ [DefaultExecutionOrder(XRInteractionUpdateOrder.k_XRInputDeviceButtonReader)]
+ public class ValueDerivedButtonReader : MonoBehaviour, IXRInputButtonReader
+ {
+ [SerializeField]
+ [Tooltip("The input reader used to reference the float value to convert to a bool.")]
+ XRInputValueReader<float> m_ValueInput = new XRInputValueReader<float>("Value");
+
+ /// <summary>
+ /// The input reader used to reference the float value to convert to a bool.
+ /// </summary>
+ public XRInputValueReader<float> valueInput
+ {
+ get => m_ValueInput;
+ set => XRInputReaderUtility.SetInputProperty(ref m_ValueInput, value, this);
+ }
+
+ [SerializeField]
+ [Tooltip("The threshold value to use to determine when the button is pressed. Considered pressed equal to or greater than this value.")]
+ [Range(0f, 1f)]
+ float m_PressThreshold = 0.8f;
+
+ /// <summary>
+ /// The threshold value to use to determine when the button is pressed. Considered pressed equal to or greater than this value.
+ /// </summary>
+ public float pressThreshold
+ {
+ get => m_PressThreshold;
+ set => m_PressThreshold = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The threshold value to use to determine when the button is released when it was previously pressed. Keeps being pressed until falls back to a value of or below this value.")]
+ [Range(0f, 1f)]
+ float m_ReleaseThreshold = 0.25f;
+
+ /// <summary>
+ /// The threshold value to use to determine when the button is released when it was previously pressed.
+ /// Keeps being pressed until falls back to a value of or below this value.
+ /// </summary>
+ public float releaseThreshold
+ {
+ get => m_ReleaseThreshold;
+ set => m_ReleaseThreshold = value;
+ }
+
+ bool m_IsPerformed;
+ bool m_WasPerformedThisFrame;
+ bool m_WasCompletedThisFrame;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnEnable()
+ {
+ m_ValueInput?.EnableDirectActionIfModeUsed();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void OnDisable()
+ {
+ m_ValueInput?.DisableDirectActionIfModeUsed();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Update()
+ {
+ var prevPerformed = m_IsPerformed;
+ var pressAmount = m_ValueInput.ReadValue();
+
+ var newValue = pressAmount >= m_PressThreshold;
+ if (!newValue && prevPerformed)
+ newValue = pressAmount > m_ReleaseThreshold;
+
+ m_IsPerformed = newValue;
+ m_WasPerformedThisFrame = !prevPerformed && m_IsPerformed;
+ m_WasCompletedThisFrame = prevPerformed && !m_IsPerformed;
+ }
+
+ /// <inheritdoc />
+ public bool ReadIsPerformed()
+ {
+ return m_IsPerformed;
+ }
+
+ /// <inheritdoc />
+ public bool ReadWasPerformedThisFrame()
+ {
+ return m_WasPerformedThisFrame;
+ }
+
+ /// <inheritdoc />
+ public bool ReadWasCompletedThisFrame()
+ {
+ return m_WasCompletedThisFrame;
+ }
+
+ /// <inheritdoc />
+ public float ReadValue()
+ {
+ return m_ValueInput.ReadValue();
+ }
+
+ /// <inheritdoc />
+ public bool TryReadValue(out float value)
+ {
+ return m_ValueInput.TryReadValue(out value);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs.meta
new file mode 100644
index 0000000..1ed61a4
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/ValueDerivedButtonReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bf47ae772fb3421292887025bf9b5820
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs
new file mode 100644
index 0000000..3845f19
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs
@@ -0,0 +1,33 @@
+using System;
+using Unity.Mathematics;
+using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.Receiver.Primitives;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
+{
+ /// <summary>
+ /// Affordance receiver applying a Vector3 (Float3) affordance theme to a Transform local scale.
+ /// Broadcasts new affordance value with Unity Event.
+ /// </summary>
+ [Obsolete("The Affordance System namespace and all associated classes have been deprecated. The existing affordance system will be moved, replaced and updated with a new interaction feedback system in a future version of XRI.")]
+ public class Vector3ScaleAffordanceReceiver : Vector3AffordanceReceiver
+ {
+ [SerializeField]
+ [Tooltip("The transform to apply the scale value to.")]
+ Transform m_TargetTransform;
+
+ /// <inheritdoc />
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+ if (m_TargetTransform == null)
+ m_TargetTransform = transform;
+ }
+
+ /// <inheritdoc />
+ protected override void OnAffordanceValueUpdated(float3 newValue)
+ {
+ base.OnAffordanceValueUpdated(newValue);
+ m_TargetTransform.localScale = newValue;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs.meta
new file mode 100644
index 0000000..c56261b
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Hands Interaction Demo/Scripts/Vector3ScaleAffordanceReceiver.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27bee223346c63f4bb2b0f85250f58b7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: