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));
}
}
}