VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/Utilities/TeleportationMonitor.cs
2025-07-29 13:45:50 +03:00

223 lines
8.8 KiB
C#

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
{
/// <summary>
/// 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
/// <see cref="TeleportationProvider"/> to detect teleportation.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso cref="XRGrabInteractable"/>
class TeleportationMonitor
{
/// <summary>
/// Calls the methods in its invocation list when one of the Interactors monitored has been influenced by teleportation.
/// The <see cref="Pose"/> event args represents the amount the <see cref="XROrigin"/> rig was translated and rotated.
/// </summary>
public event Action<Pose> teleported;
/// <summary>
/// The list of interactors monitored that are influenced by teleportation.
/// Consists of those that are a child GameObject of the <see cref="XROrigin"/> rig.
/// </summary>
/// <remarks>
/// There will typically only ever be one <see cref="TeleportationProvider"/> in the scene.
/// </remarks>
Dictionary<TeleportationProvider, List<IXRInteractor>> m_TeleportInteractors;
/// <summary>
/// The <see cref="Pose"/> of the <see cref="XROrigin"/> rig before teleportation.
/// Used to calculate the teleportation delta using this as reference.
/// </summary>
Dictionary<LocomotionMediator, Pose> m_OriginPosesBeforeTeleport;
static readonly LinkedPool<Dictionary<TeleportationProvider, List<IXRInteractor>>> s_TeleportInteractorsPool =
new LinkedPool<Dictionary<TeleportationProvider, List<IXRInteractor>>>(() => new Dictionary<TeleportationProvider, List<IXRInteractor>>());
static readonly LinkedPool<Dictionary<LocomotionMediator, Pose>> s_OriginPosesBeforeTeleportPool =
new LinkedPool<Dictionary<LocomotionMediator, Pose>>(() => new Dictionary<LocomotionMediator, Pose>());
/// <summary>
/// Cached reference to <see cref="TeleportationProvider"/> instances found.
/// </summary>
static TeleportationProvider[] s_TeleportationProvidersCache;
/// <summary>
/// Adds <paramref name="interactor"/> to monitor. If it is a child of the XR Origin, <see cref="teleported"/>
/// will be invoked when the player teleports.
/// </summary>
/// <param name="interactor">The Interactor to add.</param>
/// <seealso cref="RemoveInteractor"/>
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<IXRInteractor>();
m_TeleportInteractors.Add(teleportationProvider, interactors);
}
Debug.Assert(!interactors.Contains(interactor));
interactors.Add(interactor);
if (interactors.Count == 1)
{
teleportationProvider.locomotionStarted += OnBeginTeleportation;
teleportationProvider.locomotionEnded += OnEndTeleportation;
}
}
}
/// <summary>
/// Removes <paramref name="interactor"/> from monitor.
/// </summary>
/// <param name="interactor">The Interactor to remove.</param>
/// <seealso cref="AddInteractor"/>
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<TeleportationProvider>(FindObjectsSortMode.None);
#else
s_TeleportationProvidersCache = Object.FindObjectsOfType<TeleportationProvider>();
#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));
}
}
}