// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; #endif namespace Microsoft.MixedReality.Toolkit { /// /// A static class encapsulating the Mixed Reality playspace. /// public static class MixedRealityPlayspace { private const string Name = "MixedRealityPlayspace"; private static Transform mixedRealityPlayspace; public static void Destroy() { // Playspace makes main camera dependent on it (see Transform initialization), // so here it needs to restore camera's initial position. // Without second parameter camera will not move to its original position. CameraCache.Main.transform.SetParent(null, false); UnityEngine.Object.Destroy(mixedRealityPlayspace.gameObject); mixedRealityPlayspace = null; } /// /// The transform of the playspace. /// public static Transform Transform { get { if (mixedRealityPlayspace) { mixedRealityPlayspace.gameObject.SetActive(true); return mixedRealityPlayspace; } if (CameraCache.Main.transform.parent == null) { // Create a new mixed reality playspace GameObject mixedRealityPlayspaceGo = new GameObject(Name); mixedRealityPlayspace = mixedRealityPlayspaceGo.transform; CameraCache.Main.transform.SetParent(mixedRealityPlayspace); } else { mixedRealityPlayspace = CameraCache.Main.transform.parent; } // It's very important that the Playspace align with the tracked space, // otherwise reality-locked things like playspace boundaries won't be aligned properly. // For now, we'll just assume that when the playspace is first initialized, the // tracked space origin overlaps with the world space origin. If a platform ever does // something else (i.e, placing the lower left hand corner of the tracked space at world // space 0,0,0), we should compensate for that here. return mixedRealityPlayspace; } } /// /// The location of the playspace. /// public static Vector3 Position { get { return Transform.position; } set { Transform.position = value; } } /// /// The playspace's rotation. /// public static Quaternion Rotation { get { return Transform.rotation; } set { Transform.rotation = value; } } /// /// Adds a child object to the playspace's hierarchy. /// /// The child object's transform. public static void AddChild(Transform transform) { transform.SetParent(Transform); } /// /// Transforms a position from local to world space. /// /// The position to be transformed. /// /// The position, in world space. /// public static Vector3 TransformPoint(Vector3 localPosition) { return Transform.TransformPoint(localPosition); } /// /// Transforms a position from world to local space. /// /// The position to be transformed. /// /// The position, in local space. /// public static Vector3 InverseTransformPoint(Vector3 worldPosition) { return Transform.InverseTransformPoint(worldPosition); } /// /// Transforms a direction from local to world space. /// /// The direction to be transformed. /// /// The direction, in world space. /// public static Vector3 TransformDirection(Vector3 localDirection) { return Transform.TransformDirection(localDirection); } /// /// Transforms a direction from world to local space. /// /// The direction to be transformed. /// /// The direction, in local space. /// public static Vector3 InverseTransformDirection(Vector3 worldDirection) { return Transform.InverseTransformDirection(worldDirection); } /// /// Rotates the playspace around the specified axis. /// /// The point to pass through during rotation. /// The axis about which to rotate. /// The angle, in degrees, to rotate. public static void RotateAround(Vector3 point, Vector3 axis, float angle) { Transform.RotateAround(point, axis, angle); } /// /// Performs a playspace transformation. /// /// The transformation to be applied to the playspace. /// /// This method takes a lambda function and may contribute to garbage collector pressure. /// For best performance, avoid calling this method from an inner loop function. /// public static void PerformTransformation(Action transformation) { transformation?.Invoke(Transform); } #region Multi-scene management private static bool subscribedToEvents = false; #if UNITY_EDITOR private static bool subscribedToEditorEvents = false; [InitializeOnLoadMethod] public static void InitializeOnLoad() { if (!subscribedToEditorEvents) { EditorSceneManager.sceneOpened += EditorSceneManagerSceneOpened; EditorSceneManager.sceneClosed += EditorSceneManagerSceneClosed; subscribedToEditorEvents = true; } SearchForAndEnableExistingPlayspace(EditorSceneUtils.GetRootGameObjectsInLoadedScenes()); } private static void EditorSceneManagerSceneClosed(Scene scene) { if (Application.isPlaying) { // Let the runtime scene management handle this return; } if (mixedRealityPlayspace == null) { // If we unloaded our playspace, see if another one exists SearchForAndEnableExistingPlayspace(EditorSceneUtils.GetRootGameObjectsInLoadedScenes()); } } private static void EditorSceneManagerSceneOpened(Scene scene, OpenSceneMode mode) { if (Application.isPlaying) { // Let the runtime scene management handle this return; } if (mixedRealityPlayspace == null) { SearchForAndEnableExistingPlayspace(EditorSceneUtils.GetRootGameObjectsInLoadedScenes()); } else { if (scene.isLoaded) { SearchForAndDisableExtraPlayspaces(scene.GetRootGameObjects()); } } } #endif [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] public static void RuntimeInitializeOnLoadMethod() { if (!subscribedToEvents) { SceneManager.sceneLoaded += SceneManagerSceneLoaded; SceneManager.sceneUnloaded += SceneManagerSceneUnloaded; subscribedToEvents = true; } } private static void SceneManagerSceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { if (mixedRealityPlayspace == null) { SearchForAndEnableExistingPlayspace(RuntimeSceneUtils.GetRootGameObjectsInLoadedScenes()); } else { SearchForAndDisableExtraPlayspaces(scene.GetRootGameObjects()); } } private static void SceneManagerSceneUnloaded(Scene scene) { if (mixedRealityPlayspace == null) { // If we unloaded our playspace, see if another one exists SearchForAndEnableExistingPlayspace(RuntimeSceneUtils.GetRootGameObjectsInLoadedScenes()); } } private static void SearchForAndDisableExtraPlayspaces(IEnumerable rootGameObjects) { // We've already got a mixed reality playspace. // Our task is to search for any additional play spaces that may have been loaded, and disable them. foreach (GameObject rootGameObject in rootGameObjects) { if (rootGameObject == mixedRealityPlayspace.gameObject) { // Don't disable our existing playspace continue; } if (rootGameObject.name.Equals(Name)) { rootGameObject.SetActive(false); } } } private static void SearchForAndEnableExistingPlayspace(IEnumerable rootGameObjects) { // We haven't created / found a playspace yet. // Our task is to see if one exists in the newly loaded scene. bool enabledOne = false; foreach (GameObject rootGameObject in rootGameObjects) { if (rootGameObject.name.Equals(Name)) { if (!enabledOne) { mixedRealityPlayspace = rootGameObject.transform; mixedRealityPlayspace.gameObject.SetActive(true); enabledOne = true; } else { // If we've already enabled one, we need to disable all others rootGameObject.SetActive(false); } return; } } } #endregion } }