// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #if UNITY_WSA && UNITY_2017_2_OR_NEWER using System.Collections.Generic; using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.WSA; #endif namespace HoloToolkit.Unity.Boundary { /// <summary> /// Places a floor quad to ground the scene. /// Allows you to check if your GameObject is within setup boundary on the immersive headset. /// </summary> public class BoundaryManager : Singleton<BoundaryManager> { #if UNITY_WSA && UNITY_2017_2_OR_NEWER [Tooltip("Quad prefab to display as the floor.")] public GameObject FloorQuad = null; private GameObject floorQuadInstance = null; [SerializeField] [Tooltip("Approximate max Y height of your space.")] private float boundaryHeight = 10f; private Bounds boundaryBounds; [SerializeField] // Defaulting coordinate system to RoomScale in immersive headsets. // This puts the origin (0, 0, 0) on the floor if a floor has been established during setup via MixedRealityPortal. private TrackingSpaceType opaqueTrackingSpaceType = TrackingSpaceType.RoomScale; // Removed for now, until the HoloLens tracking space type story is more clear. //[SerializeField] // Defaulting coordinate system to Stationary for transparent headsets, like HoloLens. // This puts the origin (0, 0, 0) at the first place where the user started the application. //private TrackingSpaceType transparentTrackingSpaceType = TrackingSpaceType.Stationary; // Testing in the editor found that this moved the floor out of the way enough, and it is only // used in the case where a headset isn't attached. Otherwise, the floor is positioned like normal. private readonly Vector3 floorPositionInEditor = new Vector3(0f, -3f, 0f); [SerializeField] private bool renderFloor = true; public bool RenderFloor { get { return renderFloor; } set { if (renderFloor != value) { renderFloor = value; SetFloorRendering(); } } } [SerializeField] private bool renderBoundary = true; public bool RenderBoundary { get { return renderBoundary; } set { if (renderBoundary != value) { renderBoundary = value; SetBoundaryRendering(); } } } #endif protected override void Awake() { base.Awake(); #if UNITY_WSA && UNITY_2017_2_OR_NEWER if (HolographicSettings.IsDisplayOpaque) { XRDevice.SetTrackingSpaceType(opaqueTrackingSpaceType); } else { // Removed for now, until the HoloLens tracking space type story is more clear. //XRDevice.SetTrackingSpaceType(transparentTrackingSpaceType); Destroy(this); return; } // Render the floor based on if you are in editor or immersive device. RenderFloorQuad(); // Render boundary if configured. SetBoundaryRendering(); // Create a volume out of the specified user boundary. CalculateBoundaryVolume(); } private void SetFloorRendering() { if (floorQuadInstance != null) { floorQuadInstance.SetActive(renderFloor); } #endif } private void SetBoundaryRendering() { #if UNITY_WSA && UNITY_2017_2_OR_NEWER // TODO: BUG: Unity: configured bool always returns false in 2017.2.0p2-MRTP5. if (UnityEngine.Experimental.XR.Boundary.configured) { UnityEngine.Experimental.XR.Boundary.visible = renderBoundary; } #endif } #if UNITY_WSA && UNITY_2017_2_OR_NEWER private void RenderFloorQuad() { if (FloorQuad != null && XRDevice.GetTrackingSpaceType() == TrackingSpaceType.RoomScale) { floorQuadInstance = Instantiate(FloorQuad); if (!XRDevice.isPresent) { // So the floor quad does not occlude in editor testing, draw it lower. floorQuadInstance.transform.position = floorPositionInEditor; } else { floorQuadInstance.transform.position = Vector3.zero; } SetFloorRendering(); } } /// <summary> /// Pass in the game object's position to check if it's within /// the specified boundary space. /// </summary> /// <param name="gameObjectPosition"></param> /// <returns></returns> public bool ContainsObject(Vector3 gameObjectPosition) { // Check if the supplied game object's position is within the bounds volume. return boundaryBounds.Contains(gameObjectPosition); } /// <summary> /// Uses the TryGetGeometry call and Unity Bounds to create a volume out of the setup boundary. /// </summary> public void CalculateBoundaryVolume() { // TODO: BUG: Unity: Should return true if a floor and boundary has been established by user. // But this always returns false with in 2017.2.0p2-MRTP5. //if (!UnityEngine.Experimental.XR.Boundary.configured) //{ // Debug.Log("Boundary not configured."); // return; //} if (XRDevice.GetTrackingSpaceType() != TrackingSpaceType.RoomScale) { Debug.Log("No boundary for non-room scale experiences."); return; } boundaryBounds = new Bounds(); // Get all the bounds setup by the user. var boundaryGeometry = new List<Vector3>(0); // TODO: BUG: Unity: Should return true if a floor and boundary has been established by user. // But this always returns false with in 2017.2.0p2-MRTP5. if (UnityEngine.Experimental.XR.Boundary.TryGetGeometry(boundaryGeometry)) { if (boundaryGeometry.Count > 0) { // Create a UnityEngine.Bounds volume with those values. foreach (Vector3 boundaryGeo in boundaryGeometry) { boundaryBounds.Encapsulate(boundaryGeo); } } } else { Debug.Log("TryGetGeometry always returns false."); } // Ensuring that we set height of the bounds volume to be say 10 feet tall. boundaryBounds.Encapsulate(new Vector3(0, boundaryHeight, 0)); } #endif } }