init
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc6980b6cb3b4f12b6b75074e4ef59f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e989a75b2954bdab01ca618a30d5de6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 865d01d2834c9cb4caa8f2c901104c2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a83bc4aa48d0da648b49d0fd56690b25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cf24614c456bc04c9adb820d19e35c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
public 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8693657abb5062a40a80ba3cb86ef181
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbac611a2982409ab5f5e604f53bcad0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,127 @@
|
||||
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 = 1f;
|
||||
|
||||
/// <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.9f;
|
||||
|
||||
/// <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();
|
||||
m_IsPerformed = m_ValueInput.ReadIsPerformed() || prevPerformed ? pressAmount > m_ReleaseThreshold : pressAmount >= m_PressThreshold;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63f61d1c82c9fc6429ebd4791a4d6817
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf47ae772fb3421292887025bf9b5820
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27bee223346c63f4bb2b0f85250f58b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user