using System; using System.Collections.Generic; using Unity.XR.CoreUtils; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Locomotion; using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation; using UnityEngine.XR.Interaction.Toolkit.Utilities.Pooling; namespace UnityEngine.XR.Interaction.Toolkit.Utilities { /// /// Use this class to maintain a list of Interactors that are potentially influenced by teleportation /// and subscribe to the event when teleportation occurs. Uses the events invoked by /// to detect teleportation. /// /// /// Used by the XR Grab Interactable to cancel out the effect of the teleportation from its tracked velocity /// so it does not release at unintentionally high energy. /// /// class TeleportationMonitor { /// /// Calls the methods in its invocation list when one of the Interactors monitored has been influenced by teleportation. /// The event args represents the amount the rig was translated and rotated. /// public event Action teleported; /// /// The list of interactors monitored that are influenced by teleportation. /// Consists of those that are a child GameObject of the rig. /// /// /// There will typically only ever be one in the scene. /// Dictionary> m_TeleportInteractors; /// /// The of the rig before teleportation. /// Used to calculate the teleportation delta using this as reference. /// Dictionary m_OriginPosesBeforeTeleport; static readonly LinkedPool>> s_TeleportInteractorsPool = new LinkedPool>>(() => new Dictionary>()); static readonly LinkedPool> s_OriginPosesBeforeTeleportPool = new LinkedPool>(() => new Dictionary()); /// /// Cached reference to instances found. /// static TeleportationProvider[] s_TeleportationProvidersCache; /// /// Adds to monitor. If it is a child of the XR Origin, /// will be invoked when the player teleports. /// /// The Interactor to add. /// public void AddInteractor(IXRInteractor interactor) { if (interactor == null) throw new ArgumentNullException(nameof(interactor)); var interactorTransform = interactor.transform; if (interactorTransform == null) return; if (!FindTeleportationProviders()) return; foreach (var teleportationProvider in s_TeleportationProvidersCache) { if (!TryGetOriginTransform(teleportationProvider, out var originTransform)) continue; if (!interactorTransform.IsChildOf(originTransform)) continue; if (m_TeleportInteractors == null) m_TeleportInteractors = s_TeleportInteractorsPool.Get(); if (!m_TeleportInteractors.TryGetValue(teleportationProvider, out var interactors)) { interactors = new List(); m_TeleportInteractors.Add(teleportationProvider, interactors); } Debug.Assert(!interactors.Contains(interactor)); interactors.Add(interactor); if (interactors.Count == 1) { teleportationProvider.locomotionStarted += OnBeginTeleportation; teleportationProvider.locomotionEnded += OnEndTeleportation; } } } /// /// Removes from monitor. /// /// The Interactor to remove. /// public void RemoveInteractor(IXRInteractor interactor) { if (interactor == null) throw new ArgumentNullException(nameof(interactor)); var totalInteractors = 0; if (m_TeleportInteractors != null) { foreach (var kvp in m_TeleportInteractors) { var teleportationProvider = kvp.Key; var interactors = kvp.Value; if (interactors.Remove(interactor) && interactors.Count == 0 && teleportationProvider != null) { teleportationProvider.locomotionStarted -= OnBeginTeleportation; teleportationProvider.locomotionEnded -= OnEndTeleportation; } totalInteractors += interactors.Count; } } // Release back to the pool if (totalInteractors == 0) { if (m_TeleportInteractors != null) { s_TeleportInteractorsPool.Release(m_TeleportInteractors); m_TeleportInteractors = null; } if (m_OriginPosesBeforeTeleport != null) { s_OriginPosesBeforeTeleportPool.Release(m_OriginPosesBeforeTeleport); m_OriginPosesBeforeTeleport = null; } } } static bool TryGetOriginTransform(LocomotionProvider locomotionProvider, out Transform originTransform) { // Correct version of locomotionProvider?.system?.xrOrigin?.Origin?.transform if (locomotionProvider != null) { var system = locomotionProvider.mediator; return TryGetOriginTransform(system, out originTransform); } originTransform = null; return false; } static bool TryGetOriginTransform(LocomotionMediator mediator, out Transform originTransform) { // Correct version of system?.xrOrigin?.Origin?.transform if (mediator != null) { var xrOrigin = mediator.xrOrigin; if (xrOrigin != null) { var origin = xrOrigin.Origin; if (origin != null) { originTransform = origin.transform; return true; } } } originTransform = null; return false; } static bool FindTeleportationProviders() { if (s_TeleportationProvidersCache == null) #if UNITY_2023_1_OR_NEWER s_TeleportationProvidersCache = Object.FindObjectsByType(FindObjectsSortMode.None); #else s_TeleportationProvidersCache = Object.FindObjectsOfType(); #endif return s_TeleportationProvidersCache.Length > 0; } void OnBeginTeleportation(LocomotionProvider provider) { var mediator = provider.mediator; if (!TryGetOriginTransform(mediator, out var originTransform)) return; if (m_OriginPosesBeforeTeleport == null) m_OriginPosesBeforeTeleport = s_OriginPosesBeforeTeleportPool.Get(); m_OriginPosesBeforeTeleport[mediator] = originTransform.GetWorldPose(); } void OnEndTeleportation(LocomotionProvider provider) { var mediator = provider.mediator; if (!TryGetOriginTransform(mediator, out var originTransform)) return; if (m_OriginPosesBeforeTeleport == null) return; if (!m_OriginPosesBeforeTeleport.TryGetValue(mediator, out var originPoseBeforeTeleport)) return; var translated = originTransform.position - originPoseBeforeTeleport.position; var rotated = originTransform.rotation * Quaternion.Inverse(originPoseBeforeTeleport.rotation); teleported?.Invoke(new Pose(translated, rotated)); } } }