// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.Utilities; using Unity.Profiling; using UnityEngine; using UnityEngine.EventSystems; namespace Microsoft.MixedReality.Toolkit.Teleport { /// /// The Mixed Reality Toolkit's implementation of the . /// public class MixedRealityTeleportSystem : BaseCoreSystem, IMixedRealityTeleportSystem { /// /// Constructor. /// /// The instance that loaded the service. [System.Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")] public MixedRealityTeleportSystem( IMixedRealityServiceRegistrar registrar) : base(registrar, null) // Teleport system does not use a profile { Registrar = registrar; } /// /// Constructor. /// public MixedRealityTeleportSystem() : base(null) { } // Teleport system does not use a profile private TeleportEventData teleportEventData; private bool isTeleporting = false; private bool isProcessingTeleportRequest = false; private Vector3 targetPosition = Vector3.zero; private Vector3 targetRotation = Vector3.zero; /// /// Used to clean up event system when shutting down, if this system created one. /// private GameObject eventSystemReference = null; #region IMixedRealityService Implementation /// public override string Name { get; protected set; } = "Mixed Reality Teleport System"; /// public override void Initialize() { base.Initialize(); InitializeInternal(); } private void InitializeInternal() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { var eventSystems = Object.FindObjectsOfType(); if (eventSystems.Length == 0) { if (!IsInputSystemEnabled) { eventSystemReference = new GameObject("Event System"); eventSystemReference.AddComponent(); } else { Debug.Log("The input system didn't properly add an event system to your scene. Please make sure the input system's priority is set higher than the teleport system."); } } else if (eventSystems.Length > 1) { Debug.Log("Too many event systems in the scene. The Teleport System requires only one."); } } #endif // UNITY_EDITOR teleportEventData = new TeleportEventData(EventSystem.current); } /// public override void Destroy() { base.Destroy(); if (eventSystemReference != null) { if (!Application.isPlaying) { Object.DestroyImmediate(eventSystemReference); } else { Object.Destroy(eventSystemReference); } } } #endregion IMixedRealityService Implementation #region IEventSystemManager Implementation private static readonly ProfilerMarker HandleEventPerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.HandleEvent"); /// public override void HandleEvent(BaseEventData eventData, ExecuteEvents.EventFunction eventHandler) { using (HandleEventPerfMarker.Auto()) { Debug.Assert(eventData != null); var teleportData = ExecuteEvents.ValidateEventData(eventData); Debug.Assert(teleportData != null); Debug.Assert(!teleportData.used); // Process all the event listeners base.HandleEvent(teleportData, eventHandler); } } /// /// Register a GameObject to listen to teleport events. /// public override void Register(GameObject listener) => base.Register(listener); /// /// Unregister a GameObject from listening to teleport events. /// public override void Unregister(GameObject listener) => base.Unregister(listener); #endregion IEventSystemManager Implementation #region IMixedRealityTeleportSystem Implementation /// /// Is an input system registered? /// private bool IsInputSystemEnabled => CoreServices.InputSystem != null; private float teleportDuration = 0.25f; /// public float TeleportDuration { get => teleportDuration; set { if (isProcessingTeleportRequest) { Debug.LogWarning("Couldn't change teleport duration. Teleport in progress."); return; } teleportDuration = value; } } private static readonly ExecuteEvents.EventFunction OnTeleportRequestHandler = delegate (IMixedRealityTeleportHandler handler, BaseEventData eventData) { var casted = ExecuteEvents.ValidateEventData(eventData); handler.OnTeleportRequest(casted); }; private static readonly ProfilerMarker RaiseTeleportRequestPerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.RaiseTeleportRequest"); /// public void RaiseTeleportRequest(IMixedRealityPointer pointer, IMixedRealityTeleportHotspot hotSpot) { using (RaiseTeleportRequestPerfMarker.Auto()) { // initialize event teleportEventData.Initialize(pointer, hotSpot); // Pass handler HandleEvent(teleportEventData, OnTeleportRequestHandler); } } private static readonly ExecuteEvents.EventFunction OnTeleportStartedHandler = delegate (IMixedRealityTeleportHandler handler, BaseEventData eventData) { var casted = ExecuteEvents.ValidateEventData(eventData); handler.OnTeleportStarted(casted); }; private static readonly ProfilerMarker RaiseTeleportStartedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.RaiseTeleportStarted"); /// public void RaiseTeleportStarted(IMixedRealityPointer pointer, IMixedRealityTeleportHotspot hotSpot) { if (isTeleporting) { Debug.LogError("Teleportation already in progress"); return; } using (RaiseTeleportStartedPerfMarker.Auto()) { isTeleporting = true; // initialize event teleportEventData.Initialize(pointer, hotSpot); // Pass handler HandleEvent(teleportEventData, OnTeleportStartedHandler); ProcessTeleportationRequest(teleportEventData); } } private static readonly ExecuteEvents.EventFunction OnTeleportCompletedHandler = delegate (IMixedRealityTeleportHandler handler, BaseEventData eventData) { var casted = ExecuteEvents.ValidateEventData(eventData); handler.OnTeleportCompleted(casted); }; private static readonly ProfilerMarker RaiseTeleportCompletePerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.RaiseTeleportComplete"); /// /// Raise a teleportation completed event. /// /// The pointer that raised the event. /// The teleport target private void RaiseTeleportComplete(IMixedRealityPointer pointer, IMixedRealityTeleportHotspot hotSpot) { if (!isTeleporting) { Debug.LogError("No Active Teleportation in progress."); return; } using (RaiseTeleportCompletePerfMarker.Auto()) { // initialize event teleportEventData.Initialize(pointer, hotSpot); // Pass handler HandleEvent(teleportEventData, OnTeleportCompletedHandler); isTeleporting = false; } } private static readonly ExecuteEvents.EventFunction OnTeleportCanceledHandler = delegate (IMixedRealityTeleportHandler handler, BaseEventData eventData) { var casted = ExecuteEvents.ValidateEventData(eventData); handler.OnTeleportCanceled(casted); }; private static readonly ProfilerMarker RaiseTeleportCanceledPerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.RaiseTeleportHandled"); /// public void RaiseTeleportCanceled(IMixedRealityPointer pointer, IMixedRealityTeleportHotspot hotSpot) { using (RaiseTeleportCanceledPerfMarker.Auto()) { // initialize event teleportEventData.Initialize(pointer, hotSpot); // Pass handler HandleEvent(teleportEventData, OnTeleportCanceledHandler); } } #endregion IMixedRealityTeleportSystem Implementation private static readonly ProfilerMarker ProcessTeleportationRequestPerfMarker = new ProfilerMarker("[MRTK] MixedRealityTeleportSystem.ProcessTeleportationRequest"); private void ProcessTeleportationRequest(TeleportEventData eventData) { using (ProcessTeleportationRequestPerfMarker.Auto()) { isProcessingTeleportRequest = true; targetRotation = Vector3.zero; if (eventData.Pointer is IMixedRealityTeleportPointer teleportPointer && !teleportPointer.IsNull()) { targetRotation.y = teleportPointer.PointerOrientation; } targetPosition = eventData.Pointer.Result.Details.Point; if (eventData.Hotspot != null) { targetPosition = eventData.Hotspot.Position; if (eventData.Hotspot.OverrideOrientation) { targetRotation.y = eventData.Hotspot.TargetRotation; } } float height = targetPosition.y; targetPosition -= CameraCache.Main.transform.position - MixedRealityPlayspace.Position; targetPosition.y = height; MixedRealityPlayspace.Position = targetPosition; MixedRealityPlayspace.RotateAround( CameraCache.Main.transform.position, Vector3.up, targetRotation.y - CameraCache.Main.transform.eulerAngles.y); isProcessingTeleportRequest = false; // Raise complete event using the pointer and hot spot provided. RaiseTeleportComplete(eventData.Pointer, eventData.Hotspot); } } } }