diff options
author | pryazha <pryadeiniv@mail.ru> | 2025-07-02 08:46:23 -0700 |
---|---|---|
committer | pryazha <pryadeiniv@mail.ru> | 2025-07-02 08:46:23 -0700 |
commit | 8263edd59284aba390aca011d25b79efecef4c48 (patch) | |
tree | 6346e2afaaabd32156601cafaf20d4ee813befaf /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')
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><MetaAimHand>{LeftHand}/aimFlags</c></description> + /// </item> + /// <item> + /// <description><c><MetaAimHand>{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 (☰). + /// 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: |