summaryrefslogtreecommitdiff
path: root/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts
diff options
context:
space:
mode:
authorpryazha <pryadeiniv@mail.ru>2025-07-02 08:46:23 -0700
committerpryazha <pryadeiniv@mail.ru>2025-07-02 08:46:23 -0700
commit8263edd59284aba390aca011d25b79efecef4c48 (patch)
tree6346e2afaaabd32156601cafaf20d4ee813befaf /Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts
Diffstat (limited to 'Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts')
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs143
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs84
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs530
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs29
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs190
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs95
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs242
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs237
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs45
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs97
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs.meta11
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs298
-rw-r--r--Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs.meta11
22 files changed, 2111 insertions, 0 deletions
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs
new file mode 100644
index 0000000..f64d156
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs
@@ -0,0 +1,143 @@
+using UnityEngine.XR.Interaction.Toolkit.Locomotion.Climbing;
+using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
+using UnityEngine.XR.Interaction.Toolkit.Utilities;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Affordance component used in conjunction with a <see cref="ClimbTeleportInteractor"/> to display an object
+ /// pointing at the target teleport destination while climbing.
+ /// </summary>
+ public class ClimbTeleportDestinationIndicator : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("The interactor that drives the display and placement of the pointer object.")]
+ ClimbTeleportInteractor m_ClimbTeleportInteractor;
+
+ /// <summary>
+ /// The interactor that drives the display and placement of the pointer object.
+ /// </summary>
+ public ClimbTeleportInteractor climbTeleportInteractor
+ {
+ get => m_ClimbTeleportInteractor;
+ set => m_ClimbTeleportInteractor = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The prefab to spawn when a teleport destination is chosen. The instance will spawn next to the " +
+ "destination and point its forward vector at the destination and its up vector at the camera.")]
+ GameObject m_PointerPrefab;
+
+ /// <summary>
+ /// The prefab to spawn when a teleport destination is chosen. The instance will spawn next to the destination
+ /// and point its forward vector at the destination and its up vector at the camera.
+ /// </summary>
+ public GameObject pointerPrefab
+ {
+ get => m_PointerPrefab;
+ set => m_PointerPrefab = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The distance from the destination at which the pointer object spawns.")]
+ float m_PointerDistance = 0.3f;
+
+ /// <summary>
+ /// The distance from the destination at which the pointer object spawns.
+ /// </summary>
+ public float pointerDistance
+ {
+ get => m_PointerDistance;
+ set => m_PointerDistance = value;
+ }
+
+ TeleportationMultiAnchorVolume m_ActiveTeleportVolume;
+ Transform m_PointerInstance;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnEnable()
+ {
+ if (m_ClimbTeleportInteractor == null)
+ {
+ if (!ComponentLocatorUtility<ClimbTeleportInteractor>.TryFindComponent(out m_ClimbTeleportInteractor))
+ {
+ Debug.LogError($"Could not find {nameof(ClimbTeleportInteractor)} in scene.");
+ enabled = false;
+ return;
+ }
+ }
+
+ m_ClimbTeleportInteractor.hoverEntered.AddListener(OnInteractorHoverEntered);
+ m_ClimbTeleportInteractor.hoverExited.AddListener(OnInteractorHoverExited);
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDisable()
+ {
+ HideIndicator();
+
+ if (m_ActiveTeleportVolume != null)
+ {
+ m_ActiveTeleportVolume.destinationAnchorChanged -= OnClimbTeleportDestinationAnchorChanged;
+ m_ActiveTeleportVolume = null;
+ }
+
+ if (m_ClimbTeleportInteractor != null)
+ {
+ m_ClimbTeleportInteractor.hoverEntered.RemoveListener(OnInteractorHoverEntered);
+ m_ClimbTeleportInteractor.hoverExited.RemoveListener(OnInteractorHoverExited);
+ }
+ }
+
+ void OnInteractorHoverEntered(HoverEnterEventArgs args)
+ {
+ if (m_ActiveTeleportVolume != null || !(args.interactableObject is TeleportationMultiAnchorVolume teleportVolume))
+ return;
+
+ m_ActiveTeleportVolume = teleportVolume;
+ if (m_ActiveTeleportVolume.destinationAnchor != null)
+ OnClimbTeleportDestinationAnchorChanged(m_ActiveTeleportVolume);
+
+ m_ActiveTeleportVolume.destinationAnchorChanged += OnClimbTeleportDestinationAnchorChanged;
+ }
+
+ void OnInteractorHoverExited(HoverExitEventArgs args)
+ {
+ if (!(args.interactableObject is TeleportationMultiAnchorVolume teleportVolume) || teleportVolume != m_ActiveTeleportVolume)
+ return;
+
+ HideIndicator();
+ m_ActiveTeleportVolume.destinationAnchorChanged -= OnClimbTeleportDestinationAnchorChanged;
+ m_ActiveTeleportVolume = null;
+ }
+
+ void OnClimbTeleportDestinationAnchorChanged(TeleportationMultiAnchorVolume teleportVolume)
+ {
+ HideIndicator();
+
+ var destinationAnchor = teleportVolume.destinationAnchor;
+ if (destinationAnchor == null)
+ return;
+
+ m_PointerInstance = Instantiate(m_PointerPrefab).transform;
+ var cameraTrans = teleportVolume.teleportationProvider.mediator.xrOrigin.Camera.transform;
+ var cameraPosition = cameraTrans.position;
+ var destinationPosition = destinationAnchor.position;
+ var destinationDirectionInScreenSpace = cameraTrans.InverseTransformDirection(destinationPosition - cameraPosition);
+ destinationDirectionInScreenSpace.z = 0f;
+ var pointerDirection = cameraTrans.TransformDirection(destinationDirectionInScreenSpace).normalized;
+ m_PointerInstance.position = destinationPosition - pointerDirection * m_PointerDistance;
+ m_PointerInstance.rotation = Quaternion.LookRotation(pointerDirection, -cameraTrans.forward);
+ }
+
+ void HideIndicator()
+ {
+ if (m_PointerInstance != null)
+ Destroy(m_PointerInstance.gameObject);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs.meta
new file mode 100644
index 0000000..d9e84f5
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ClimbTeleportDestinationIndicator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e766f86cb7d2461683eb37d8a971fb14
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs
new file mode 100644
index 0000000..858f8f2
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs
@@ -0,0 +1,84 @@
+using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Component which reads input values and drives the thumbstick, trigger, and grip transforms
+ /// to animate a controller model.
+ /// </summary>
+ public class ControllerAnimator : MonoBehaviour
+ {
+ [Header("Thumbstick")]
+ [SerializeField]
+ Transform m_ThumbstickTransform;
+
+ [SerializeField]
+ Vector2 m_StickRotationRange = new Vector2(30f, 30f);
+
+ [SerializeField]
+ XRInputValueReader<Vector2> m_StickInput = new XRInputValueReader<Vector2>("Thumbstick");
+
+ [Header("Trigger")]
+ [SerializeField]
+ Transform m_TriggerTransform;
+
+ [SerializeField]
+ Vector2 m_TriggerXAxisRotationRange = new Vector2(0f, -15f);
+
+ [SerializeField]
+ XRInputValueReader<float> m_TriggerInput = new XRInputValueReader<float>("Trigger");
+
+ [Header("Grip")]
+ [SerializeField]
+ Transform m_GripTransform;
+
+ [SerializeField]
+ Vector2 m_GripRightRange = new Vector2(-0.0125f, -0.011f);
+
+ [SerializeField]
+ XRInputValueReader<float> m_GripInput = new XRInputValueReader<float>("Grip");
+
+ void OnEnable()
+ {
+ if (m_ThumbstickTransform == null || m_GripTransform == null || m_TriggerTransform == null)
+ {
+ enabled = false;
+ Debug.LogWarning($"Controller Animator component missing references on {gameObject.name}", this);
+ return;
+ }
+
+ m_StickInput?.EnableDirectActionIfModeUsed();
+ m_TriggerInput?.EnableDirectActionIfModeUsed();
+ m_GripInput?.EnableDirectActionIfModeUsed();
+ }
+
+ void OnDisable()
+ {
+ m_StickInput?.DisableDirectActionIfModeUsed();
+ m_TriggerInput?.DisableDirectActionIfModeUsed();
+ m_GripInput?.DisableDirectActionIfModeUsed();
+ }
+
+ void Update()
+ {
+ if (m_StickInput != null)
+ {
+ var stickVal = m_StickInput.ReadValue();
+ m_ThumbstickTransform.localRotation = Quaternion.Euler(-stickVal.y * m_StickRotationRange.x, 0f, -stickVal.x * m_StickRotationRange.y);
+ }
+
+ if (m_TriggerInput != null)
+ {
+ var triggerVal = m_TriggerInput.ReadValue();
+ m_TriggerTransform.localRotation = Quaternion.Euler(Mathf.Lerp(m_TriggerXAxisRotationRange.x, m_TriggerXAxisRotationRange.y, triggerVal), 0f, 0f);
+ }
+
+ if (m_GripInput != null)
+ {
+ var gripVal = m_GripInput.ReadValue();
+ var currentPos = m_GripTransform.localPosition;
+ m_GripTransform.localPosition = new Vector3(Mathf.Lerp(m_GripRightRange.x, m_GripRightRange.y, gripVal), currentPos.y, currentPos.z);
+ }
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs.meta
new file mode 100644
index 0000000..8215d8e
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerAnimator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4a5f76f9ea8c80547973ab01877f9567
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs
new file mode 100644
index 0000000..84a0c0f
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs
@@ -0,0 +1,530 @@
+using System.Collections.Generic;
+using Unity.XR.CoreUtils.Bindings;
+using UnityEngine.Events;
+using UnityEngine.InputSystem;
+using UnityEngine.Serialization;
+using UnityEngine.XR.Interaction.Toolkit.Attachment;
+using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
+using UnityEngine.XR.Interaction.Toolkit.Interactors;
+using UnityEngine.XR.Interaction.Toolkit.UI;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Use this class to mediate the interactors for a controller under different interaction states
+ /// and the input actions used by them.
+ /// </summary>
+ /// <remarks>
+ /// If the teleport ray input is engaged, the Ray Interactor used for distant manipulation is disabled
+ /// and the Ray Interactor used for teleportation is enabled. If the Ray Interactor is selecting and it
+ /// is configured to allow for attach transform manipulation, all locomotion input actions are disabled
+ /// (teleport ray, move, and turn controls) to prevent input collision with the manipulation inputs used
+ /// by the ray interactor.
+ /// <br />
+ /// A typical hierarchy also includes an XR Interaction Group component to mediate between interactors.
+ /// The interaction group ensures that the Direct and Ray Interactors cannot interact at the same time,
+ /// with the Direct Interactor taking priority over the Ray Interactor.
+ /// </remarks>
+ [AddComponentMenu("XR/Controller Input Action Manager")]
+ public class ControllerInputActionManager : MonoBehaviour
+ {
+ [Space]
+ [Header("Interactors")]
+
+ [SerializeField]
+ [Tooltip("The interactor used for distant/ray manipulation. Use this or Near-Far Interactor, not both.")]
+ XRRayInteractor m_RayInteractor;
+
+ [SerializeField]
+ [Tooltip("Near-Far Interactor used for distant/ray manipulation. Use this or Ray Interactor, not both.")]
+ NearFarInteractor m_NearFarInteractor;
+
+ [SerializeField]
+ [Tooltip("The interactor used for teleportation.")]
+ XRRayInteractor m_TeleportInteractor;
+
+ [Space]
+ [Header("Controller Actions")]
+
+ [SerializeField]
+ [Tooltip("The reference to the action to start the teleport aiming mode for this controller.")]
+ [FormerlySerializedAs("m_TeleportModeActivate")]
+ InputActionReference m_TeleportMode;
+
+ [SerializeField]
+ [Tooltip("The reference to the action to cancel the teleport aiming mode for this controller.")]
+ InputActionReference m_TeleportModeCancel;
+
+ [SerializeField]
+ [Tooltip("The reference to the action of continuous turning the XR Origin with this controller.")]
+ InputActionReference m_Turn;
+
+ [SerializeField]
+ [Tooltip("The reference to the action of snap turning the XR Origin with this controller.")]
+ InputActionReference m_SnapTurn;
+
+ [SerializeField]
+ [Tooltip("The reference to the action of moving the XR Origin with this controller.")]
+ InputActionReference m_Move;
+
+ [SerializeField]
+ [Tooltip("The reference to the action of scrolling UI with this controller.")]
+ InputActionReference m_UIScroll;
+
+ [Space]
+ [Header("Locomotion Settings")]
+
+ [SerializeField]
+ [Tooltip("If true, continuous movement will be enabled. If false, teleport will be enabled.")]
+ bool m_SmoothMotionEnabled;
+
+ [SerializeField]
+ [Tooltip("If true, continuous turn will be enabled. If false, snap turn will be enabled. Note: If smooth motion is enabled and enable strafe is enabled on the continuous move provider, turn will be overriden in favor of strafe.")]
+ bool m_SmoothTurnEnabled;
+
+ [SerializeField]
+ [Tooltip("With the Near-Far Interactor, if true, teleport will be enabled during near interaction. If false, teleport will be disabled during near interaction.")]
+ bool m_NearFarEnableTeleportDuringNearInteraction = true;
+
+ [Space]
+ [Header("UI Settings")]
+
+ [SerializeField]
+ [Tooltip("If true, UI scrolling will be enabled. Locomotion will be disabled when pointing at UI to allow it to be scrolled.")]
+ bool m_UIScrollingEnabled = true;
+
+ [Space]
+ [Header("Mediation Events")]
+
+ [SerializeField]
+ [Tooltip("Event fired when the active ray interactor changes between interaction and teleport.")]
+ UnityEvent<IXRRayProvider> m_RayInteractorChanged;
+
+ public bool smoothMotionEnabled
+ {
+ get => m_SmoothMotionEnabled;
+ set
+ {
+ m_SmoothMotionEnabled = value;
+ UpdateLocomotionActions();
+ }
+ }
+
+ public bool smoothTurnEnabled
+ {
+ get => m_SmoothTurnEnabled;
+ set
+ {
+ m_SmoothTurnEnabled = value;
+ UpdateLocomotionActions();
+ }
+ }
+
+ public bool uiScrollingEnabled
+ {
+ get => m_UIScrollingEnabled;
+ set
+ {
+ m_UIScrollingEnabled = value;
+ UpdateUIActions();
+ }
+ }
+
+ bool m_StartCalled;
+ bool m_PostponedDeactivateTeleport;
+ bool m_PostponedNearRegionLocomotion;
+ bool m_HoveringScrollableUI;
+
+ readonly HashSet<InputAction> m_LocomotionUsers = new HashSet<InputAction>();
+ readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
+
+ void SetupInteractorEvents()
+ {
+ if (m_NearFarInteractor != null)
+ {
+ m_NearFarInteractor.uiHoverEntered.AddListener(OnUIHoverEntered);
+ m_NearFarInteractor.uiHoverExited.AddListener(OnUIHoverExited);
+ m_BindingsGroup.AddBinding(m_NearFarInteractor.selectionRegion.Subscribe(OnNearFarSelectionRegionChanged));
+ }
+
+ if (m_RayInteractor != null)
+ {
+ m_RayInteractor.selectEntered.AddListener(OnRaySelectEntered);
+ m_RayInteractor.selectExited.AddListener(OnRaySelectExited);
+ m_RayInteractor.uiHoverEntered.AddListener(OnUIHoverEntered);
+ m_RayInteractor.uiHoverExited.AddListener(OnUIHoverExited);
+ }
+
+ var teleportModeAction = GetInputAction(m_TeleportMode);
+ if (teleportModeAction != null)
+ {
+ teleportModeAction.performed += OnStartTeleport;
+ teleportModeAction.performed += OnStartLocomotion;
+ teleportModeAction.canceled += OnCancelTeleport;
+ teleportModeAction.canceled += OnStopLocomotion;
+ }
+
+ var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
+ if (teleportModeCancelAction != null)
+ {
+ teleportModeCancelAction.performed += OnCancelTeleport;
+ }
+
+ var moveAction = GetInputAction(m_Move);
+ if (moveAction != null)
+ {
+ moveAction.started += OnStartLocomotion;
+ moveAction.canceled += OnStopLocomotion;
+ }
+
+ var turnAction = GetInputAction(m_Turn);
+ if (turnAction != null)
+ {
+ turnAction.started += OnStartLocomotion;
+ turnAction.canceled += OnStopLocomotion;
+ }
+
+ var snapTurnAction = GetInputAction(m_SnapTurn);
+ if (snapTurnAction != null)
+ {
+ snapTurnAction.started += OnStartLocomotion;
+ snapTurnAction.canceled += OnStopLocomotion;
+ }
+ }
+
+ void TeardownInteractorEvents()
+ {
+ m_BindingsGroup.Clear();
+
+ if (m_NearFarInteractor != null)
+ {
+ m_NearFarInteractor.uiHoverEntered.RemoveListener(OnUIHoverEntered);
+ m_NearFarInteractor.uiHoverExited.RemoveListener(OnUIHoverExited);
+ }
+
+ if (m_RayInteractor != null)
+ {
+ m_RayInteractor.selectEntered.RemoveListener(OnRaySelectEntered);
+ m_RayInteractor.selectExited.RemoveListener(OnRaySelectExited);
+ m_RayInteractor.uiHoverEntered.RemoveListener(OnUIHoverEntered);
+ m_RayInteractor.uiHoverExited.RemoveListener(OnUIHoverExited);
+ }
+
+ var teleportModeAction = GetInputAction(m_TeleportMode);
+ if (teleportModeAction != null)
+ {
+ teleportModeAction.performed -= OnStartTeleport;
+ teleportModeAction.performed -= OnStartLocomotion;
+ teleportModeAction.canceled -= OnCancelTeleport;
+ teleportModeAction.canceled -= OnStopLocomotion;
+ }
+
+ var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
+ if (teleportModeCancelAction != null)
+ {
+ teleportModeCancelAction.performed -= OnCancelTeleport;
+ }
+
+ var moveAction = GetInputAction(m_Move);
+ if (moveAction != null)
+ {
+ moveAction.started -= OnStartLocomotion;
+ moveAction.canceled -= OnStopLocomotion;
+ }
+
+ var turnAction = GetInputAction(m_Turn);
+ if (turnAction != null)
+ {
+ turnAction.started -= OnStartLocomotion;
+ turnAction.canceled -= OnStopLocomotion;
+ }
+
+ var snapTurnAction = GetInputAction(m_SnapTurn);
+ if (snapTurnAction != null)
+ {
+ snapTurnAction.started -= OnStartLocomotion;
+ snapTurnAction.canceled -= OnStopLocomotion;
+ }
+ }
+
+ void OnStartTeleport(InputAction.CallbackContext context)
+ {
+ m_PostponedDeactivateTeleport = false;
+
+ if (m_TeleportInteractor != null)
+ m_TeleportInteractor.gameObject.SetActive(true);
+
+ if (m_RayInteractor != null)
+ m_RayInteractor.gameObject.SetActive(false);
+
+ if (m_NearFarInteractor != null && m_NearFarInteractor.selectionRegion.Value != NearFarInteractor.Region.Near)
+ m_NearFarInteractor.gameObject.SetActive(false);
+
+ m_RayInteractorChanged?.Invoke(m_TeleportInteractor);
+ }
+
+ void OnCancelTeleport(InputAction.CallbackContext context)
+ {
+ // Do not deactivate the teleport interactor in this callback.
+ // We delay turning off the teleport interactor in this callback so that
+ // the teleport interactor has a chance to complete the teleport if needed.
+ // OnAfterInteractionEvents will handle deactivating its GameObject.
+ m_PostponedDeactivateTeleport = true;
+
+ if (m_RayInteractor != null)
+ m_RayInteractor.gameObject.SetActive(true);
+
+ if (m_NearFarInteractor != null)
+ m_NearFarInteractor.gameObject.SetActive(true);
+
+ m_RayInteractorChanged?.Invoke(m_RayInteractor);
+ }
+
+ void OnStartLocomotion(InputAction.CallbackContext context)
+ {
+ m_LocomotionUsers.Add(context.action);
+ }
+
+ void OnStopLocomotion(InputAction.CallbackContext context)
+ {
+ m_LocomotionUsers.Remove(context.action);
+
+ if (m_LocomotionUsers.Count == 0 && m_HoveringScrollableUI)
+ {
+ DisableAllLocomotionActions();
+ UpdateUIActions();
+ }
+ }
+
+ void OnNearFarSelectionRegionChanged(NearFarInteractor.Region selectionRegion)
+ {
+ m_PostponedNearRegionLocomotion = false;
+
+ if (selectionRegion == NearFarInteractor.Region.None)
+ {
+ UpdateLocomotionActions();
+ return;
+ }
+
+ var manipulateAttachTransform = false;
+ var attachController = m_NearFarInteractor.interactionAttachController as InteractionAttachController;
+ if (attachController != null)
+ {
+ manipulateAttachTransform = attachController.useManipulationInput &&
+ (attachController.manipulationInput.inputSourceMode == XRInputValueReader.InputSourceMode.InputActionReference && attachController.manipulationInput.inputActionReference != null) ||
+ (attachController.manipulationInput.inputSourceMode != XRInputValueReader.InputSourceMode.InputActionReference && attachController.manipulationInput.inputSourceMode != XRInputValueReader.InputSourceMode.Unused);
+ }
+
+ if (selectionRegion == NearFarInteractor.Region.Far)
+ {
+ if (manipulateAttachTransform)
+ DisableAllLocomotionActions();
+ else
+ DisableTeleportActions();
+ }
+ else if (selectionRegion == NearFarInteractor.Region.Near)
+ {
+ // Determine if the user entered the near region due to pulling back on the thumbstick.
+ // If so, postpone enabling locomotion until the user releases the thumbstick
+ // in order to avoid an immediate snap turn around from triggering on region change.
+ var hasStickInput = manipulateAttachTransform && HasStickInput(attachController);
+ if (hasStickInput)
+ {
+ m_PostponedNearRegionLocomotion = true;
+ DisableAllLocomotionActions();
+ }
+ else
+ {
+ UpdateLocomotionActions();
+ if (!m_NearFarEnableTeleportDuringNearInteraction)
+ DisableTeleportActions();
+ }
+ }
+ }
+
+ void OnRaySelectEntered(SelectEnterEventArgs args)
+ {
+ if (m_RayInteractor.manipulateAttachTransform)
+ {
+ // Disable locomotion and turn actions
+ DisableAllLocomotionActions();
+ }
+ }
+
+ void OnRaySelectExited(SelectExitEventArgs args)
+ {
+ if (m_RayInteractor.manipulateAttachTransform)
+ {
+ // Re-enable the locomotion and turn actions
+ UpdateLocomotionActions();
+ }
+ }
+
+ void OnUIHoverEntered(UIHoverEventArgs args)
+ {
+ m_HoveringScrollableUI = m_UIScrollingEnabled && args.deviceModel.isScrollable;
+ UpdateUIActions();
+
+ // If locomotion is occurring, wait
+ if (m_HoveringScrollableUI && m_LocomotionUsers.Count == 0)
+ {
+ // Disable locomotion and turn actions
+ DisableAllLocomotionActions();
+ }
+ }
+
+ void OnUIHoverExited(UIHoverEventArgs args)
+ {
+ m_HoveringScrollableUI = false;
+ UpdateUIActions();
+
+ // Re-enable the locomotion and turn actions
+ UpdateLocomotionActions();
+ }
+
+ protected void OnEnable()
+ {
+ if (m_RayInteractor != null && m_NearFarInteractor != null)
+ {
+ Debug.LogWarning("Both Ray Interactor and Near-Far Interactor are assigned. Only one should be assigned, not both. Clearing Ray Interactor.", this);
+ m_RayInteractor = null;
+ }
+
+ if (m_TeleportInteractor != null)
+ m_TeleportInteractor.gameObject.SetActive(false);
+
+ // Allow the actions to be refreshed when this component is re-enabled.
+ // See comments in Start for why we wait until Start to enable/disable actions.
+ if (m_StartCalled)
+ {
+ UpdateLocomotionActions();
+ UpdateUIActions();
+ }
+
+ SetupInteractorEvents();
+ }
+
+ protected void OnDisable()
+ {
+ TeardownInteractorEvents();
+ }
+
+ protected void Start()
+ {
+ m_StartCalled = true;
+
+ // Ensure the enabled state of locomotion and turn actions are properly set up.
+ // Called in Start so it is done after the InputActionManager enables all input actions earlier in OnEnable.
+ UpdateLocomotionActions();
+ UpdateUIActions();
+ }
+
+ protected void Update()
+ {
+ // Since this behavior has the default execution order, it runs after the XRInteractionManager,
+ // so selection events have been finished by now this frame. This means that the teleport interactor
+ // has had a chance to process its select interaction event and teleport if needed.
+ if (m_PostponedDeactivateTeleport)
+ {
+ if (m_TeleportInteractor != null)
+ m_TeleportInteractor.gameObject.SetActive(false);
+
+ m_PostponedDeactivateTeleport = false;
+ }
+
+ // If stick input caused the near region to be entered,
+ // wait until the stick is released before enabling locomotion.
+ if (m_PostponedNearRegionLocomotion)
+ {
+ var hasStickInput = false;
+ if (m_NearFarInteractor != null &&
+ m_NearFarInteractor.interactionAttachController is InteractionAttachController attachController
+ && attachController != null)
+ {
+ hasStickInput = HasStickInput(attachController);
+ }
+
+ if (!hasStickInput)
+ {
+ m_PostponedNearRegionLocomotion = false;
+
+ UpdateLocomotionActions();
+ if (!m_NearFarEnableTeleportDuringNearInteraction)
+ DisableTeleportActions();
+ }
+ }
+ }
+
+ void UpdateLocomotionActions()
+ {
+ // Disable/enable Teleport and Turn when Move is enabled/disabled.
+ SetEnabled(m_Move, m_SmoothMotionEnabled);
+ SetEnabled(m_TeleportMode, !m_SmoothMotionEnabled);
+ SetEnabled(m_TeleportModeCancel, !m_SmoothMotionEnabled);
+
+ // Disable ability to turn when using continuous movement
+ SetEnabled(m_Turn, !m_SmoothMotionEnabled && m_SmoothTurnEnabled);
+ SetEnabled(m_SnapTurn, !m_SmoothMotionEnabled && !m_SmoothTurnEnabled);
+ }
+
+ void DisableTeleportActions()
+ {
+ DisableAction(m_TeleportMode);
+ DisableAction(m_TeleportModeCancel);
+ }
+
+ void DisableMoveAndTurnActions()
+ {
+ DisableAction(m_Move);
+ DisableAction(m_Turn);
+ DisableAction(m_SnapTurn);
+ }
+
+ void DisableAllLocomotionActions()
+ {
+ DisableTeleportActions();
+ DisableMoveAndTurnActions();
+ }
+
+ void UpdateUIActions()
+ {
+ SetEnabled(m_UIScroll, m_UIScrollingEnabled && m_HoveringScrollableUI && m_LocomotionUsers.Count == 0);
+ }
+
+ static bool HasStickInput(InteractionAttachController attachController)
+ {
+ // 75% of default 0.5 press threshold
+ const float sqrStickReleaseThreshold = 0.375f * 0.375f;
+
+ return attachController.manipulationInput.TryReadValue(out var stickInput) &&
+ stickInput.sqrMagnitude > sqrStickReleaseThreshold;
+ }
+
+ static void SetEnabled(InputActionReference actionReference, bool enabled)
+ {
+ if (enabled)
+ EnableAction(actionReference);
+ else
+ DisableAction(actionReference);
+ }
+
+ static void EnableAction(InputActionReference actionReference)
+ {
+ var action = GetInputAction(actionReference);
+ action?.Enable();
+ }
+
+ static void DisableAction(InputActionReference actionReference)
+ {
+ var action = GetInputAction(actionReference);
+ action?.Disable();
+ }
+
+ static InputAction GetInputAction(InputActionReference actionReference)
+ {
+#pragma warning disable IDE0031 // Use null propagation -- Do not use for UnityEngine.Object types
+ return actionReference != null ? actionReference.action : null;
+#pragma warning restore IDE0031
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs.meta
new file mode 100644
index 0000000..5962c5e
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ControllerInputActionManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f9ac216f0eb04754b1d938aac6380b31
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs
new file mode 100644
index 0000000..f10ffc4
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs
@@ -0,0 +1,29 @@
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Destroys the GameObject it is attached to after a specified amount of time.
+ /// </summary>
+ public class DestroySelf : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("The amount of time, in seconds, to wait after Start before destroying the GameObject.")]
+ float m_Lifetime = 0.25f;
+
+ /// <summary>
+ /// The amount of time, in seconds, to wait after Start before destroying the GameObject.
+ /// </summary>
+ public float lifetime
+ {
+ get => m_Lifetime;
+ set => m_Lifetime = value;
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Start()
+ {
+ Destroy(gameObject, m_Lifetime);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs.meta
new file mode 100644
index 0000000..bf7bf1f
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DestroySelf.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 717c12e2a4cfe764ab2580b1135e10fd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs
new file mode 100644
index 0000000..033e5d4
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs
@@ -0,0 +1,190 @@
+using Unity.XR.CoreUtils;
+using UnityEngine.Assertions;
+using UnityEngine.XR.Interaction.Toolkit.Locomotion.Movement;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// A version of continuous movement that automatically controls the frame of reference that
+ /// determines the forward direction of movement based on user preference for each hand.
+ /// For example, can configure to use head relative movement for the left hand and controller relative movement for the right hand.
+ /// </summary>
+ public class DynamicMoveProvider : ContinuousMoveProvider
+ {
+ /// <summary>
+ /// Defines which transform the XR Origin's movement direction is relative to.
+ /// </summary>
+ /// <seealso cref="leftHandMovementDirection"/>
+ /// <seealso cref="rightHandMovementDirection"/>
+ public enum MovementDirection
+ {
+ /// <summary>
+ /// Use the forward direction of the head (camera) as the forward direction of the XR Origin's movement.
+ /// </summary>
+ HeadRelative,
+
+ /// <summary>
+ /// Use the forward direction of the hand (controller) as the forward direction of the XR Origin's movement.
+ /// </summary>
+ HandRelative,
+ }
+
+ [Space, Header("Movement Direction")]
+ [SerializeField]
+ [Tooltip("Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.")]
+ Transform m_HeadTransform;
+
+ /// <summary>
+ /// Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.
+ /// </summary>
+ public Transform headTransform
+ {
+ get => m_HeadTransform;
+ set => m_HeadTransform = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the left hand.")]
+ Transform m_LeftControllerTransform;
+
+ /// <summary>
+ /// Directs the XR Origin's movement when using the hand-relative mode with the left hand.
+ /// </summary>
+ public Transform leftControllerTransform
+ {
+ get => m_LeftControllerTransform;
+ set => m_LeftControllerTransform = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the right hand.")]
+ Transform m_RightControllerTransform;
+
+ public Transform rightControllerTransform
+ {
+ get => m_RightControllerTransform;
+ set => m_RightControllerTransform = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Whether to use the specified head transform or left controller transform to direct the XR Origin's movement for the left hand.")]
+ MovementDirection m_LeftHandMovementDirection;
+
+ /// <summary>
+ /// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the left hand.
+ /// </summary>
+ /// <seealso cref="MovementDirection"/>
+ public MovementDirection leftHandMovementDirection
+ {
+ get => m_LeftHandMovementDirection;
+ set => m_LeftHandMovementDirection = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Whether to use the specified head transform or right controller transform to direct the XR Origin's movement for the right hand.")]
+ MovementDirection m_RightHandMovementDirection;
+
+ /// <summary>
+ /// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the right hand.
+ /// </summary>
+ /// <seealso cref="MovementDirection"/>
+ public MovementDirection rightHandMovementDirection
+ {
+ get => m_RightHandMovementDirection;
+ set => m_RightHandMovementDirection = value;
+ }
+
+ Transform m_CombinedTransform;
+ Pose m_LeftMovementPose = Pose.identity;
+ Pose m_RightMovementPose = Pose.identity;
+
+ /// <inheritdoc />
+ protected override void Awake()
+ {
+ base.Awake();
+
+ m_CombinedTransform = new GameObject("[Dynamic Move Provider] Combined Forward Source").transform;
+ m_CombinedTransform.SetParent(transform, false);
+ m_CombinedTransform.localPosition = Vector3.zero;
+ m_CombinedTransform.localRotation = Quaternion.identity;
+
+ forwardSource = m_CombinedTransform;
+ }
+
+ /// <inheritdoc />
+ protected override Vector3 ComputeDesiredMove(Vector2 input)
+ {
+ // Don't need to do anything if the total input is zero.
+ // This is the same check as the base method.
+ if (input == Vector2.zero)
+ return base.ComputeDesiredMove(input);
+
+ // Initialize the Head Transform if necessary, getting the Camera from XR Origin
+ if (m_HeadTransform == null)
+ {
+ var xrOrigin = mediator.xrOrigin;
+ if (xrOrigin != null)
+ {
+ var xrCamera = xrOrigin.Camera;
+ if (xrCamera != null)
+ m_HeadTransform = xrCamera.transform;
+ }
+ }
+
+ // Get the forward source for the left hand input
+ switch (m_LeftHandMovementDirection)
+ {
+ case MovementDirection.HeadRelative:
+ if (m_HeadTransform != null)
+ m_LeftMovementPose = m_HeadTransform.GetWorldPose();
+
+ break;
+
+ case MovementDirection.HandRelative:
+ if (m_LeftControllerTransform != null)
+ m_LeftMovementPose = m_LeftControllerTransform.GetWorldPose();
+
+ break;
+
+ default:
+ Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_LeftHandMovementDirection}");
+ break;
+ }
+
+ // Get the forward source for the right hand input
+ switch (m_RightHandMovementDirection)
+ {
+ case MovementDirection.HeadRelative:
+ if (m_HeadTransform != null)
+ m_RightMovementPose = m_HeadTransform.GetWorldPose();
+
+ break;
+
+ case MovementDirection.HandRelative:
+ if (m_RightControllerTransform != null)
+ m_RightMovementPose = m_RightControllerTransform.GetWorldPose();
+
+ break;
+
+ default:
+ Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_RightHandMovementDirection}");
+ break;
+ }
+
+ // Combine the two poses into the forward source based on the magnitude of input
+ var leftHandValue = leftHandMoveInput.ReadValue();
+ var rightHandValue = rightHandMoveInput.ReadValue();
+
+ var totalSqrMagnitude = leftHandValue.sqrMagnitude + rightHandValue.sqrMagnitude;
+ var leftHandBlend = 0.5f;
+ if (totalSqrMagnitude > Mathf.Epsilon)
+ leftHandBlend = leftHandValue.sqrMagnitude / totalSqrMagnitude;
+
+ var combinedPosition = Vector3.Lerp(m_RightMovementPose.position, m_LeftMovementPose.position, leftHandBlend);
+ var combinedRotation = Quaternion.Slerp(m_RightMovementPose.rotation, m_LeftMovementPose.rotation, leftHandBlend);
+ m_CombinedTransform.SetPositionAndRotation(combinedPosition, combinedRotation);
+
+ return base.ComputeDesiredMove(input);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs.meta
new file mode 100644
index 0000000..561fce4
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/DynamicMoveProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9b1e8c997df241c1a67045eeac79b41b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs
new file mode 100644
index 0000000..43195de
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs
@@ -0,0 +1,95 @@
+using System.Collections.Generic;
+using UnityEngine.InputSystem;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Manages input fallback for <see cref="XRGazeInteractor"/> when eye tracking is not available.
+ /// </summary>
+ public class GazeInputManager : MonoBehaviour
+ {
+ // This is the name of the layout that is registered by EyeGazeInteraction in the OpenXR Plugin package
+ const string k_EyeGazeLayoutName = "EyeGaze";
+
+ [SerializeField]
+ [Tooltip("Enable fallback to head tracking if eye tracking is unavailable.")]
+ bool m_FallbackIfEyeTrackingUnavailable = true;
+
+ /// <summary>
+ /// Enable fallback to head tracking if eye tracking is unavailable.
+ /// </summary>
+ public bool fallbackIfEyeTrackingUnavailable
+ {
+ get => m_FallbackIfEyeTrackingUnavailable;
+ set => m_FallbackIfEyeTrackingUnavailable = value;
+ }
+
+
+ bool m_EyeTrackingDeviceFound;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void Awake()
+ {
+ // Check if we have eye tracking support
+ var inputDeviceList = new List<InputDevice>();
+ InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.EyeTracking, inputDeviceList);
+ if (inputDeviceList.Count > 0)
+ {
+ Debug.Log("Eye tracking device found!", this);
+ m_EyeTrackingDeviceFound = true;
+ return;
+ }
+
+ foreach (var device in InputSystem.InputSystem.devices)
+ {
+ if (device.layout == k_EyeGazeLayoutName)
+ {
+ Debug.Log("Eye gaze device found!", this);
+ m_EyeTrackingDeviceFound = true;
+ return;
+ }
+ }
+
+ Debug.LogWarning($"Could not find a device that supports eye tracking on Awake. {this} has subscribed to device connected events and will activate the GameObject when an eye tracking device is connected.", this);
+
+ InputDevices.deviceConnected += OnDeviceConnected;
+ InputSystem.InputSystem.onDeviceChange += OnDeviceChange;
+
+ gameObject.SetActive(m_FallbackIfEyeTrackingUnavailable);
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDestroy()
+ {
+ InputDevices.deviceConnected -= OnDeviceConnected;
+ InputSystem.InputSystem.onDeviceChange -= OnDeviceChange;
+ }
+
+ void OnDeviceConnected(InputDevice inputDevice)
+ {
+ if (m_EyeTrackingDeviceFound || !inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.EyeTracking))
+ return;
+
+ Debug.Log("Eye tracking device found!", this);
+ m_EyeTrackingDeviceFound = true;
+ gameObject.SetActive(true);
+ }
+
+ void OnDeviceChange(InputSystem.InputDevice device, InputDeviceChange change)
+ {
+ if (m_EyeTrackingDeviceFound || change != InputDeviceChange.Added)
+ return;
+
+ if (device.layout == k_EyeGazeLayoutName)
+ {
+ Debug.Log("Eye gaze device found!", this);
+ m_EyeTrackingDeviceFound = true;
+ gameObject.SetActive(true);
+ }
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs.meta
new file mode 100644
index 0000000..f971bb7
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/GazeInputManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6ef0e4723b64c884699a375196c13ac0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs
new file mode 100644
index 0000000..b72012b
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs
@@ -0,0 +1,242 @@
+using UnityEngine.Rendering;
+using System.Collections.Generic;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+#if UNITY_EDITOR
+ [InitializeOnLoad]
+ static class RenderPipelineValidation
+ {
+ static RenderPipelineValidation()
+ {
+ foreach (var pipelineHandler in GetAllInstances())
+ pipelineHandler.AutoRefreshPipelineShaders();
+ }
+
+ static List<MaterialPipelineHandler> GetAllInstances()
+ {
+ var instances = new List<MaterialPipelineHandler>();
+
+ // Find all GUIDs for objects that match the type MaterialPipelineHandler
+ var guids = AssetDatabase.FindAssets("t:MaterialPipelineHandler");
+ for (int i = 0; i < guids.Length; i++)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guids[i]);
+ var asset = AssetDatabase.LoadAssetAtPath<MaterialPipelineHandler>(path);
+ if (asset != null)
+ instances.Add(asset);
+ }
+
+ return instances;
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Serializable class that contains the shader information for a material.
+ /// </summary>
+ [System.Serializable]
+ public class ShaderContainer
+ {
+ public Material material;
+ public bool useSRPShaderName = true;
+ public string scriptableRenderPipelineShaderName = "Universal Render Pipeline/Lit";
+ public Shader scriptableRenderPipelineShader;
+ public bool useBuiltinShaderName = true;
+ public string builtInPipelineShaderName = "Standard";
+ public Shader builtInPipelineShader;
+ }
+
+ /// <summary>
+ /// Scriptable object that allows for setting the shader on a material based on the current render pipeline.
+ /// Will run automatically OnEnable in the editor to set the shaders on project bootup. Can be refreshed manually with editor button.
+ /// This exists because while objects render correctly using shadergraph shaders, others do not and using the standard shader resolves various rendering issues.
+ /// </summary>
+ [CreateAssetMenu(fileName = "MaterialPipelineHandler", menuName = "XR/MaterialPipelineHandler", order = 0)]
+ public class MaterialPipelineHandler : ScriptableObject
+ {
+ [SerializeField]
+ [Tooltip("List of materials and their associated shaders.")]
+ List<ShaderContainer> m_ShaderContainers;
+
+ [SerializeField]
+ [Tooltip("If true, the shaders will be refreshed automatically when the editor opens and when this scriptable object instance is enabled.")]
+ bool m_AutoRefreshShaders = true;
+
+#if UNITY_EDITOR
+ void OnEnable()
+ {
+ if (Application.isPlaying)
+ return;
+ AutoRefreshPipelineShaders();
+ }
+#endif
+
+ public void AutoRefreshPipelineShaders()
+ {
+ if (m_AutoRefreshShaders)
+ SetPipelineShaders();
+ }
+
+ /// <summary>
+ /// Applies the appropriate shader to the materials based on the current render pipeline.
+ /// </summary>
+ public void SetPipelineShaders()
+ {
+ if (m_ShaderContainers == null)
+ return;
+
+ bool isBuiltinRenderPipeline = GraphicsSettings.currentRenderPipeline == null;
+
+ foreach (var info in m_ShaderContainers)
+ {
+ if (info.material == null)
+ continue;
+
+ // Find the appropriate shaders based on the toggle
+ Shader birpShader = info.useBuiltinShaderName ? Shader.Find(info.builtInPipelineShaderName) : info.builtInPipelineShader;
+ Shader srpShader = info.useSRPShaderName ? Shader.Find(info.scriptableRenderPipelineShaderName) : info.scriptableRenderPipelineShader;
+
+ // Determine current shader for comparison
+ Shader currentShader = info.material.shader;
+
+ // Update shader for the current render pipeline only if necessary
+ if (isBuiltinRenderPipeline && birpShader != null && currentShader != birpShader)
+ {
+ info.material.shader = birpShader;
+ MarkMaterialModified(info.material);
+ }
+ else if (!isBuiltinRenderPipeline && srpShader != null && currentShader != srpShader)
+ {
+ info.material.shader = srpShader;
+ MarkMaterialModified(info.material);
+ }
+ }
+ }
+
+ static void MarkMaterialModified(Material material)
+ {
+#if UNITY_EDITOR
+ EditorUtility.SetDirty(material);
+#endif
+ }
+ }
+
+#if UNITY_EDITOR
+ /// <summary>
+ /// Custom property drawer for the shader container class.
+ /// </summary>
+ [CustomPropertyDrawer(typeof(ShaderContainer))]
+ public class ShaderContainerDrawer : PropertyDrawer
+ {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ EditorGUI.BeginProperty(position, label, property);
+
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+ float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
+
+ SerializedProperty materialProp = property.FindPropertyRelative("material");
+ SerializedProperty useSRPShaderNameProp = property.FindPropertyRelative("useSRPShaderName");
+ SerializedProperty scriptableShaderNameProp = property.FindPropertyRelative("scriptableRenderPipelineShaderName");
+ SerializedProperty scriptableShaderProp = property.FindPropertyRelative("scriptableRenderPipelineShader");
+ SerializedProperty useShaderNameProp = property.FindPropertyRelative("useBuiltinShaderName");
+ SerializedProperty builtInNameProp = property.FindPropertyRelative("builtInPipelineShaderName");
+ SerializedProperty builtInShaderProp = property.FindPropertyRelative("builtInPipelineShader");
+
+ // Draw Material without the header.
+ position.height = singleLineHeight;
+ EditorGUI.PropertyField(position, materialProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ // SRP Shader header and fields.
+ EditorGUI.LabelField(position, "Scriptable Render Pipeline Shader", EditorStyles.boldLabel);
+ position.y += EditorGUIUtility.singleLineHeight + verticalSpacing;
+
+ EditorGUI.PropertyField(position, useSRPShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ if (useSRPShaderNameProp.boolValue)
+ {
+ EditorGUI.PropertyField(position, scriptableShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+ else
+ {
+ EditorGUI.PropertyField(position, scriptableShaderProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+
+ // Built-in Shader header and fields.
+ EditorGUI.LabelField(position, "Built-In Render Pipeline Shader", EditorStyles.boldLabel);
+ position.y += singleLineHeight + verticalSpacing;
+
+ EditorGUI.PropertyField(position, useShaderNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+
+ if (useShaderNameProp.boolValue)
+ {
+ EditorGUI.PropertyField(position, builtInNameProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+ else
+ {
+ EditorGUI.PropertyField(position, builtInShaderProp);
+ position.y += singleLineHeight + verticalSpacing;
+ }
+
+ // Draw a separator line at the end.
+ position.y += verticalSpacing / 2; // Extra space for the line.
+ position.height = 1;
+ EditorGUI.DrawRect(new Rect(position.x, position.y, position.width, 1), Color.gray);
+
+ EditorGUI.EndProperty();
+ }
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ const int baseFieldCount = 4; // The Material field, the two toggles, and one for an optional field.
+ int extraLineCount = property.FindPropertyRelative("useBuiltinShaderName").boolValue ? 0 : 1;
+ extraLineCount += property.FindPropertyRelative("useSRPShaderName").boolValue ? 0 : 1;
+
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+ float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
+ float headerHeight = EditorGUIUtility.singleLineHeight; // No longer need extra height for headers.
+
+ // Calculate height for fields and headers
+ float fieldsHeight = baseFieldCount * singleLineHeight + (baseFieldCount - 1 + extraLineCount) * verticalSpacing;
+
+ // Allow space for header, separator line, and a bit of padding before the line.
+ float headersHeight = 2 * (headerHeight + verticalSpacing);
+ float separatorSpace = verticalSpacing / 2 + 1; // Additional vertical spacing and line height.
+
+ return fieldsHeight + headersHeight + separatorSpace + singleLineHeight * 1.5f;
+ }
+ }
+
+ /// <summary>
+ /// Custom editor MaterialPipelineHandler
+ /// </summary>
+ [CustomEditor(typeof(MaterialPipelineHandler)), CanEditMultipleObjects]
+ public class MaterialPipelineHandlerEditor : Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+
+ // Draw the "Refresh Shaders" button
+ if (GUILayout.Button("Refresh Shaders"))
+ {
+ foreach (var t in targets)
+ {
+ var handler = (MaterialPipelineHandler)t;
+ handler.SetPipelineShaders();
+ }
+ }
+ }
+ }
+#endif
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs.meta
new file mode 100644
index 0000000..cd72936
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/MaterialPipelineHandler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e7883133e628dff4a86f50c082f77055
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs
new file mode 100644
index 0000000..3b278e0
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs
@@ -0,0 +1,237 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine.XR.Interaction.Toolkit.Utilities;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Behavior with an API for spawning objects from a given set of prefabs.
+ /// </summary>
+ public class ObjectSpawner : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("The camera that objects will face when spawned. If not set, defaults to the main camera.")]
+ Camera m_CameraToFace;
+
+ /// <summary>
+ /// The camera that objects will face when spawned. If not set, defaults to the <see cref="Camera.main"/> camera.
+ /// </summary>
+ public Camera cameraToFace
+ {
+ get
+ {
+ EnsureFacingCamera();
+ return m_CameraToFace;
+ }
+ set => m_CameraToFace = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The list of prefabs available to spawn.")]
+ List<GameObject> m_ObjectPrefabs = new List<GameObject>();
+
+ /// <summary>
+ /// The list of prefabs available to spawn.
+ /// </summary>
+ public List<GameObject> objectPrefabs
+ {
+ get => m_ObjectPrefabs;
+ set => m_ObjectPrefabs = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Optional prefab to spawn for each spawned object. Use a prefab with the Destroy Self component to make " +
+ "sure the visualization only lives temporarily.")]
+ GameObject m_SpawnVisualizationPrefab;
+
+ /// <summary>
+ /// Optional prefab to spawn for each spawned object.
+ /// </summary>
+ /// <remarks>Use a prefab with <see cref="DestroySelf"/> to make sure the visualization only lives temporarily.</remarks>
+ public GameObject spawnVisualizationPrefab
+ {
+ get => m_SpawnVisualizationPrefab;
+ set => m_SpawnVisualizationPrefab = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The index of the prefab to spawn. If outside the range of the list, this behavior will select " +
+ "a random object each time it spawns.")]
+ int m_SpawnOptionIndex = -1;
+
+ /// <summary>
+ /// The index of the prefab to spawn. If outside the range of <see cref="objectPrefabs"/>, this behavior will
+ /// select a random object each time it spawns.
+ /// </summary>
+ /// <seealso cref="isSpawnOptionRandomized"/>
+ public int spawnOptionIndex
+ {
+ get => m_SpawnOptionIndex;
+ set => m_SpawnOptionIndex = value;
+ }
+
+ /// <summary>
+ /// Whether this behavior will select a random object from <see cref="objectPrefabs"/> each time it spawns.
+ /// </summary>
+ /// <seealso cref="spawnOptionIndex"/>
+ /// <seealso cref="RandomizeSpawnOption"/>
+ public bool isSpawnOptionRandomized => m_SpawnOptionIndex < 0 || m_SpawnOptionIndex >= m_ObjectPrefabs.Count;
+
+ [SerializeField]
+ [Tooltip("Whether to only spawn an object if the spawn point is within view of the camera.")]
+ bool m_OnlySpawnInView = true;
+
+ /// <summary>
+ /// Whether to only spawn an object if the spawn point is within view of the <see cref="cameraToFace"/>.
+ /// </summary>
+ public bool onlySpawnInView
+ {
+ get => m_OnlySpawnInView;
+ set => m_OnlySpawnInView = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The size, in viewport units, of the periphery inside the viewport that will not be considered in view.")]
+ float m_ViewportPeriphery = 0.15f;
+
+ /// <summary>
+ /// The size, in viewport units, of the periphery inside the viewport that will not be considered in view.
+ /// </summary>
+ public float viewportPeriphery
+ {
+ get => m_ViewportPeriphery;
+ set => m_ViewportPeriphery = value;
+ }
+
+ [SerializeField]
+ [Tooltip("When enabled, the object will be rotated about the y-axis when spawned by Spawn Angle Range, " +
+ "in relation to the direction of the spawn point to the camera.")]
+ bool m_ApplyRandomAngleAtSpawn = true;
+
+ /// <summary>
+ /// When enabled, the object will be rotated about the y-axis when spawned by <see cref="spawnAngleRange"/>
+ /// in relation to the direction of the spawn point to the camera.
+ /// </summary>
+ public bool applyRandomAngleAtSpawn
+ {
+ get => m_ApplyRandomAngleAtSpawn;
+ set => m_ApplyRandomAngleAtSpawn = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The range in degrees that the object will randomly be rotated about the y axis when spawned, " +
+ "in relation to the direction of the spawn point to the camera.")]
+ float m_SpawnAngleRange = 45f;
+
+ /// <summary>
+ /// The range in degrees that the object will randomly be rotated about the y axis when spawned, in relation
+ /// to the direction of the spawn point to the camera.
+ /// </summary>
+ public float spawnAngleRange
+ {
+ get => m_SpawnAngleRange;
+ set => m_SpawnAngleRange = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Whether to spawn each object as a child of this object.")]
+ bool m_SpawnAsChildren;
+
+ /// <summary>
+ /// Whether to spawn each object as a child of this object.
+ /// </summary>
+ public bool spawnAsChildren
+ {
+ get => m_SpawnAsChildren;
+ set => m_SpawnAsChildren = value;
+ }
+
+ /// <summary>
+ /// Event invoked after an object is spawned.
+ /// </summary>
+ /// <seealso cref="TrySpawnObject"/>
+ public event Action<GameObject> objectSpawned;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ void Awake()
+ {
+ EnsureFacingCamera();
+ }
+
+ void EnsureFacingCamera()
+ {
+ if (m_CameraToFace == null)
+ m_CameraToFace = Camera.main;
+ }
+
+ /// <summary>
+ /// Sets this behavior to select a random object from <see cref="objectPrefabs"/> each time it spawns.
+ /// </summary>
+ /// <seealso cref="spawnOptionIndex"/>
+ /// <seealso cref="isSpawnOptionRandomized"/>
+ public void RandomizeSpawnOption()
+ {
+ m_SpawnOptionIndex = -1;
+ }
+
+ /// <summary>
+ /// Attempts to spawn an object from <see cref="objectPrefabs"/> at the given position. The object will have a
+ /// yaw rotation that faces <see cref="cameraToFace"/>, plus or minus a random angle within <see cref="spawnAngleRange"/>.
+ /// </summary>
+ /// <param name="spawnPoint">The world space position at which to spawn the object.</param>
+ /// <param name="spawnNormal">The world space normal of the spawn surface.</param>
+ /// <returns>Returns <see langword="true"/> if the spawner successfully spawned an object. Otherwise returns
+ /// <see langword="false"/>, for instance if the spawn point is out of view of the camera.</returns>
+ /// <remarks>
+ /// The object selected to spawn is based on <see cref="spawnOptionIndex"/>. If the index is outside
+ /// the range of <see cref="objectPrefabs"/>, this method will select a random prefab from the list to spawn.
+ /// Otherwise, it will spawn the prefab at the index.
+ /// </remarks>
+ /// <seealso cref="objectSpawned"/>
+ public bool TrySpawnObject(Vector3 spawnPoint, Vector3 spawnNormal)
+ {
+ if (m_OnlySpawnInView)
+ {
+ var inViewMin = m_ViewportPeriphery;
+ var inViewMax = 1f - m_ViewportPeriphery;
+ var pointInViewportSpace = cameraToFace.WorldToViewportPoint(spawnPoint);
+ if (pointInViewportSpace.z < 0f || pointInViewportSpace.x > inViewMax || pointInViewportSpace.x < inViewMin ||
+ pointInViewportSpace.y > inViewMax || pointInViewportSpace.y < inViewMin)
+ {
+ return false;
+ }
+ }
+
+ var objectIndex = isSpawnOptionRandomized ? Random.Range(0, m_ObjectPrefabs.Count) : m_SpawnOptionIndex;
+ var newObject = Instantiate(m_ObjectPrefabs[objectIndex]);
+ if (m_SpawnAsChildren)
+ newObject.transform.parent = transform;
+
+ newObject.transform.position = spawnPoint;
+ EnsureFacingCamera();
+
+ var facePosition = m_CameraToFace.transform.position;
+ var forward = facePosition - spawnPoint;
+ BurstMathUtility.ProjectOnPlane(forward, spawnNormal, out var projectedForward);
+ newObject.transform.rotation = Quaternion.LookRotation(projectedForward, spawnNormal);
+
+ if (m_ApplyRandomAngleAtSpawn)
+ {
+ var randomRotation = Random.Range(-m_SpawnAngleRange, m_SpawnAngleRange);
+ newObject.transform.Rotate(Vector3.up, randomRotation);
+ }
+
+ if (m_SpawnVisualizationPrefab != null)
+ {
+ var visualizationTrans = Instantiate(m_SpawnVisualizationPrefab).transform;
+ visualizationTrans.position = spawnPoint;
+ visualizationTrans.rotation = newObject.transform.rotation;
+ }
+
+ objectSpawned?.Invoke(newObject);
+ return true;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs.meta
new file mode 100644
index 0000000..70f0008
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/ObjectSpawner.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 956dd6cf70eaca449a45b6a95b96c8c1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs
new file mode 100644
index 0000000..a5799aa
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs
@@ -0,0 +1,45 @@
+using UnityEngine.XR.Interaction.Toolkit.Interactables;
+using UnityEngine.XR.Interaction.Toolkit.Transformers;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// An XR grab transformer that allows for the locking of specific rotation axes. When an object is grabbed and manipulated,
+ /// this class ensures that rotations are only applied to the specified axes, preserving the initial rotation for the others.
+ /// </summary>
+ public class RotationAxisLockGrabTransformer : XRBaseGrabTransformer
+ {
+ [SerializeField]
+ [Tooltip("Defines which rotation axes are allowed when an object is grabbed. Axes not selected will maintain their initial rotation.")]
+ XRGeneralGrabTransformer.ManipulationAxes m_PermittedRotationAxis = XRGeneralGrabTransformer.ManipulationAxes.All;
+
+ /// <inheritdoc />
+ protected override RegistrationMode registrationMode => RegistrationMode.SingleAndMultiple;
+
+ Vector3 m_InitialEulerRotation;
+
+ /// <inheritdoc />
+ public override void OnLink(XRGrabInteractable grabInteractable)
+ {
+ base.OnLink(grabInteractable);
+ m_InitialEulerRotation = grabInteractable.transform.rotation.eulerAngles;
+ }
+
+ /// <inheritdoc />
+ public override void Process(XRGrabInteractable grabInteractable, XRInteractionUpdateOrder.UpdatePhase updatePhase, ref Pose targetPose, ref Vector3 localScale)
+ {
+ Vector3 newRotationEuler = targetPose.rotation.eulerAngles;
+
+ if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.X) == 0)
+ newRotationEuler.x = m_InitialEulerRotation.x;
+
+ if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.Y) == 0)
+ newRotationEuler.y = m_InitialEulerRotation.y;
+
+ if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.Z) == 0)
+ newRotationEuler.z = m_InitialEulerRotation.z;
+
+ targetPose.rotation = Quaternion.Euler(newRotationEuler);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs.meta
new file mode 100644
index 0000000..c16b51c
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/RotationAxisLockGrabTransformer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4dd2e41114c62b44fbd334ca5b314352
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs
new file mode 100644
index 0000000..7cc9a21
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs
@@ -0,0 +1,97 @@
+using System;
+using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
+using UnityEngine.XR.Interaction.Toolkit.Interactables;
+using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Helper component that binds an <see cref="XRInteractableAffordanceStateProvider"/> to a
+ /// <see cref="TeleportationMultiAnchorVolume"/> when the teleport volume sets its destination anchor to a child transform
+ /// of the state provider's originally bound interactable.
+ /// </summary>
+ [RequireComponent(typeof(XRInteractableAffordanceStateProvider))]
+ [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 TeleportVolumeAnchorAffordanceStateLink : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("The teleport volume that will drive affordance states when its destination anchor belongs to this interactable.")]
+ TeleportationMultiAnchorVolume m_ContainingTeleportVolume;
+
+ /// <summary>
+ /// The teleport volume that will drive affordance states when its destination anchor belongs to the
+ /// state provider's originally bound interactable.
+ /// </summary>
+ public TeleportationMultiAnchorVolume containingTeleportVolume
+ {
+ get => m_ContainingTeleportVolume;
+ set => m_ContainingTeleportVolume = value;
+ }
+
+ XRInteractableAffordanceStateProvider m_AffordanceStateProvider;
+ IXRInteractable m_Interactable;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnEnable()
+ {
+ m_AffordanceStateProvider = GetComponent<XRInteractableAffordanceStateProvider>();
+ if (m_AffordanceStateProvider == null)
+ {
+ Debug.LogError($"Missing {nameof(XRInteractableAffordanceStateProvider)} on {gameObject.name}.", this);
+ enabled = false;
+ return;
+ }
+
+ if (m_ContainingTeleportVolume == null)
+ {
+ Debug.LogError($"Missing {nameof(TeleportationMultiAnchorVolume)} reference on {gameObject.name}.", this);
+ enabled = false;
+ return;
+ }
+
+ var interactableSource = m_AffordanceStateProvider.interactableSource;
+ m_Interactable = interactableSource != null && interactableSource is IXRInteractable interactable
+ ? interactable
+ : m_AffordanceStateProvider.GetComponentInParent<IXRInteractable>();
+
+ if (m_Interactable == null)
+ {
+ Debug.LogError($"Interactable source must be an {nameof(IXRInteractable)}.", this);
+ enabled = false;
+ return;
+ }
+
+ m_ContainingTeleportVolume.destinationAnchorChanged += OnDestinationAnchorChanged;
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDisable()
+ {
+ if (m_ContainingTeleportVolume != null)
+ m_ContainingTeleportVolume.destinationAnchorChanged -= OnDestinationAnchorChanged;
+
+ if (m_AffordanceStateProvider != null)
+ m_AffordanceStateProvider.SetBoundInteractionReceiver(m_Interactable);
+ }
+
+ void OnDestinationAnchorChanged(TeleportationMultiAnchorVolume anchorVolume)
+ {
+ var anchor = anchorVolume.destinationAnchor;
+ if (anchor == null)
+ {
+ m_AffordanceStateProvider.SetBoundInteractionReceiver(m_Interactable);
+ return;
+ }
+
+ // Use teleport volume to drive affordance states if its current anchor belongs to this interactable
+ m_AffordanceStateProvider.SetBoundInteractionReceiver(
+ anchor.IsChildOf(m_Interactable.transform)
+ ? m_ContainingTeleportVolume
+ : m_Interactable);
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs.meta
new file mode 100644
index 0000000..2244e8e
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/TeleportVolumeAnchorAffordanceStateLink.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7da98a0edd844d83b9b4de3f91de030c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs
new file mode 100644
index 0000000..25e252c
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs
@@ -0,0 +1,298 @@
+using System;
+using Unity.Mathematics;
+using Unity.XR.CoreUtils.Bindings;
+using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
+using UnityEngine.XR.Interaction.Toolkit.Filtering;
+using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
+
+namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
+{
+ /// <summary>
+ /// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
+ /// Used to animate a pressed transform, such as a button to follow the poke position.
+ /// </summary>
+ /// <remarks>
+ /// 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, including this sample script.
+ /// </remarks>
+ [AddComponentMenu("XR/XR Poke Follow Affordance", 22)]
+ public class XRPokeFollowAffordance : MonoBehaviour
+ {
+ [SerializeField]
+ [Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
+ "\nNote: Should be a direct child GameObject.")]
+ Transform m_PokeFollowTransform;
+
+ /// <summary>
+ /// Transform that will animate along the axis of interaction when this interactable is poked.
+ /// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
+ /// </summary>
+ public Transform pokeFollowTransform
+ {
+ get => m_PokeFollowTransform;
+ set => m_PokeFollowTransform = value;
+ }
+
+ [SerializeField]
+ [Range(0f, 20f)]
+ [Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
+ float m_SmoothingSpeed = 16f;
+
+ /// <summary>
+ /// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
+ /// </summary>
+ public float smoothingSpeed
+ {
+ get => m_SmoothingSpeed;
+ set => m_SmoothingSpeed = value;
+ }
+
+ [SerializeField]
+ [Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
+ bool m_ReturnToInitialPosition = true;
+
+ /// <summary>
+ /// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
+ /// </summary>
+ public bool returnToInitialPosition
+ {
+ get => m_ReturnToInitialPosition;
+ set => m_ReturnToInitialPosition = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
+ "This is useful for UI objects that may have child graphics.")]
+ bool m_ApplyIfChildIsTarget = true;
+
+ /// <summary>
+ /// Whether to apply the follow animation if the target of the poke is a child of this transform.
+ /// This is useful for UI objects that may have child graphics.
+ /// </summary>
+ public bool applyIfChildIsTarget
+ {
+ get => m_ApplyIfChildIsTarget;
+ set => m_ApplyIfChildIsTarget = value;
+ }
+
+ [SerializeField]
+ [Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
+ bool m_ClampToMaxDistance;
+
+ /// <summary>
+ /// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
+ /// </summary>
+ public bool clampToMaxDistance
+ {
+ get => m_ClampToMaxDistance;
+ set => m_ClampToMaxDistance = value;
+ }
+
+ [SerializeField]
+ [Tooltip("The maximum distance from this transform that the Poke Follow Transform can move.")]
+ float m_MaxDistance;
+
+ /// <summary>
+ /// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
+ /// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
+ /// </summary>
+ public float maxDistance
+ {
+ get => m_MaxDistance;
+ set => m_MaxDistance = value;
+ }
+
+ /// <summary>
+ /// The original position of this interactable before any pushes have been applied.
+ /// </summary>
+ public Vector3 initialPosition
+ {
+ get => m_InitialPosition;
+ set => m_InitialPosition = value;
+ }
+
+ IPokeStateDataProvider m_PokeDataProvider;
+ IMultiPokeStateDataProvider m_MultiPokeStateDataProvider;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
+#pragma warning restore CS0618 // Type or member is obsolete
+ readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
+ Vector3 m_InitialPosition;
+ bool m_IsFirstFrame;
+
+ [HideInInspector]
+ [SerializeField]
+ XRPokeFilter m_PokeFilter = null;
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void Awake()
+ {
+ m_MultiPokeStateDataProvider = GetComponentInParent<IMultiPokeStateDataProvider>();
+ if (m_MultiPokeStateDataProvider == null)
+ m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void Start()
+ {
+ if (m_PokeFollowTransform != null)
+ {
+ m_InitialPosition = m_PokeFollowTransform.localPosition;
+ m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
+
+ if (m_MultiPokeStateDataProvider != null)
+ m_BindingsGroup.AddBinding(m_MultiPokeStateDataProvider.GetPokeStateDataForTarget(transform).Subscribe(OnPokeStateDataUpdated));
+ else if (m_PokeDataProvider != null)
+ m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
+ }
+ else
+ {
+ enabled = false;
+ Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
+ }
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void OnDestroy()
+ {
+ m_BindingsGroup.Clear();
+ m_TransformTweenableVariable?.Dispose();
+ }
+
+ /// <summary>
+ /// See <see cref="MonoBehaviour"/>.
+ /// </summary>
+ protected void LateUpdate()
+ {
+ if (m_IsFirstFrame)
+ {
+ m_TransformTweenableVariable.HandleTween(1f);
+ m_IsFirstFrame = false;
+ return;
+ }
+
+ m_TransformTweenableVariable.HandleTween(m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f);
+ }
+
+ protected virtual void OnTransformTweenableVariableUpdated(float3 position)
+ {
+ // UI Anchors can cause this to not work correctly, so we check if it's a RectTransform and set the localPosition Z only
+ if (m_PokeFollowTransform is RectTransform)
+ {
+ var targetPosition = m_PokeFollowTransform.localPosition;
+ targetPosition.z = position.z;
+ m_PokeFollowTransform.localPosition = targetPosition;
+ }
+ else
+ {
+ m_PokeFollowTransform.localPosition = position;
+ }
+ }
+
+ void OnPokeStateDataUpdated(PokeStateData data)
+ {
+ var pokeTarget = data.target;
+ var applyFollow = m_ApplyIfChildIsTarget
+ ? pokeTarget != null && pokeTarget.IsChildOf(transform)
+ : pokeTarget == transform;
+
+ if (applyFollow)
+ {
+ var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
+ if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
+ targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
+
+ m_TransformTweenableVariable.target = targetPosition;
+ }
+ else if (m_ReturnToInitialPosition)
+ {
+ m_TransformTweenableVariable.target = m_InitialPosition;
+ }
+ }
+
+ public void ResetFollowTransform()
+ {
+ if (!m_ClampToMaxDistance || m_PokeFollowTransform == null)
+ return;
+
+ m_PokeFollowTransform.localPosition = m_InitialPosition;
+ }
+
+ void OnDrawGizmos()
+ {
+ if (!TryGetTargetEndPoint(out var endPoint))
+ return;
+
+ Gizmos.color = Color.yellow;
+ Gizmos.DrawLine(transform.position, endPoint);
+ }
+
+ bool TryGetTargetEndPoint(out Vector3 endPoint)
+ {
+ if (!m_ClampToMaxDistance || m_PokeFilter == null)
+ {
+ endPoint = Vector3.zero;
+ return false;
+ }
+
+ Vector3 origin = transform.position;
+ Vector3 direction = ComputeRotatedDepthEvaluationAxis(m_PokeFilter.pokeConfiguration);
+ endPoint = origin + direction.normalized * m_MaxDistance;
+ return true;
+ }
+
+ Vector3 ComputeRotatedDepthEvaluationAxis(PokeThresholdData pokeThresholdData)
+ {
+ if (pokeThresholdData == null)
+ return Vector3.zero;
+
+ Vector3 rotatedDepthEvaluationAxis = Vector3.zero;
+ switch (pokeThresholdData.pokeDirection)
+ {
+ case PokeAxis.X:
+ case PokeAxis.NegativeX:
+ rotatedDepthEvaluationAxis = transform.right;
+ break;
+ case PokeAxis.Y:
+ case PokeAxis.NegativeY:
+ rotatedDepthEvaluationAxis = transform.up;
+ break;
+ case PokeAxis.Z:
+ case PokeAxis.NegativeZ:
+ rotatedDepthEvaluationAxis = transform.forward;
+ break;
+ }
+
+ switch (pokeThresholdData.pokeDirection)
+ {
+ case PokeAxis.X:
+ case PokeAxis.Y:
+ case PokeAxis.Z:
+ rotatedDepthEvaluationAxis = -rotatedDepthEvaluationAxis;
+ break;
+ }
+
+ return rotatedDepthEvaluationAxis;
+ }
+
+ void OnValidate()
+ {
+ if (m_PokeFilter == null)
+ {
+ m_PokeFilter = GetComponentInParent<XRPokeFilter>();
+ }
+
+ // Visually update the end point to match the target clamped position
+ if (m_PokeFollowTransform != null && TryGetTargetEndPoint(out var endPoint))
+ m_PokeFollowTransform.position = endPoint;
+ }
+ }
+}
diff --git a/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs.meta b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs.meta
new file mode 100644
index 0000000..1a3b8e1
--- /dev/null
+++ b/Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Scripts/XRPokeFollowAffordance.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 07b3638c2f5db5b479ff24c2859713d4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: