AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
BoundaryManager.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 #if UNITY_2017_2_OR_NEWER
5 using System;
6 using System.Collections.Generic;
7 using UnityEngine;
8 using UnityEngine.XR;
9 #endif
10 
11 namespace HoloToolkit.Unity.Boundary
12 {
17  public class BoundaryManager : Singleton<BoundaryManager>
18  {
19 #if UNITY_2017_2_OR_NEWER
20  [Tooltip("Quad prefab to display as the floor.")]
21  public GameObject FloorQuad = null;
22  private GameObject floorQuadInstance = null;
23 
24  [SerializeField]
25  [Tooltip("Approximate max Y height of your space.")]
26  private float boundaryHeight = 10f;
27 
28  // Minimum boundary Y value
29  private float boundaryFloor = 0.0f;
30 
31  private InscribedRectangle inscribedRectangle;
32 
33  private Edge[] boundaryGeometryEdges = new Edge[0];
34 
35  [SerializeField]
36  // Defaulting coordinate system to RoomScale in immersive headsets.
37  // This puts the origin (0, 0, 0) on the floor if a floor has been established during setup via MixedRealityPortal.
38  private TrackingSpaceType opaqueTrackingSpaceType = TrackingSpaceType.RoomScale;
39 
40  // Removed for now, until the HoloLens tracking space type story is more clear.
41  //[SerializeField]
42  // Defaulting coordinate system to Stationary for transparent headsets, like HoloLens.
43  // This puts the origin (0, 0, 0) at the first place where the user started the application.
44  //private TrackingSpaceType transparentTrackingSpaceType = TrackingSpaceType.Stationary;
45 
46  // Testing in the editor found that this moved the floor out of the way enough, and it is only
47  // used in the case where a headset isn't attached. Otherwise, the floor is positioned like normal.
48  private readonly Vector3 floorPositionInEditor = new Vector3(0f, -3f, 0f);
49 
50  [SerializeField]
51  private bool renderFloor = true;
52  public bool RenderFloor
53  {
54  get { return renderFloor; }
55  set
56  {
57  if (renderFloor != value)
58  {
59  renderFloor = value;
60  SetFloorRendering();
61  }
62  }
63  }
64 
65  [SerializeField]
66  private bool renderBoundary = true;
67  public bool RenderBoundary
68  {
69  get { return renderBoundary; }
70  set
71  {
72  if (renderBoundary != value)
73  {
74  renderBoundary = value;
75  SetBoundaryRendering();
76  }
77  }
78  }
79 
80  protected override void Awake()
81  {
82  base.Awake();
83 
84 #if UNITY_WSA
85  bool isDisplayOpaque = UnityEngine.XR.WSA.HolographicSettings.IsDisplayOpaque;
86 #else
87  // Assume displays on non Windows MR platforms are all opaque.
88  // This will likely change as new hardware comes to market.
89  bool isDisplayOpaque = true;
90 #endif
91 
92  if (isDisplayOpaque && XRSettings.enabled)
93  {
94  XRDevice.SetTrackingSpaceType(opaqueTrackingSpaceType);
95  }
96  else
97  {
98  // Removed for now, until the HoloLens tracking space type story is more clear.
99  //XRDevice.SetTrackingSpaceType(transparentTrackingSpaceType);
100 
101  Destroy(this);
102  return;
103  }
104 
105  if (XRDevice.GetTrackingSpaceType() == TrackingSpaceType.RoomScale)
106  {
107  // Render the floor if you are in editor or a room scale device.
108  RenderFloorQuad();
109  }
110 
111  // Render boundary if configured.
112  SetBoundaryRendering();
113 
114  // Create a volume out of the specified user boundary.
115  CalculateBoundaryVolume();
116  }
117 
118  private void SetFloorRendering()
119  {
120  if (floorQuadInstance != null)
121  {
122  floorQuadInstance.SetActive(renderFloor);
123  }
124  }
125 
126  private void SetBoundaryRendering()
127  {
128  // This always returns false in WindowsMR.
129  if (UnityEngine.Experimental.XR.Boundary.configured)
130  {
131  UnityEngine.Experimental.XR.Boundary.visible = renderBoundary;
132  }
133  }
134 
135  private void RenderFloorQuad()
136  {
137  if (FloorQuad != null)
138  {
139  floorQuadInstance = Instantiate(FloorQuad);
140 
141  if (!XRDevice.isPresent)
142  {
143  // So the floor quad does not occlude in editor testing, draw it lower.
144  floorQuadInstance.transform.position = floorPositionInEditor;
145  }
146  else
147  {
148  floorQuadInstance.transform.position = Vector3.zero;
149  }
150 
151  SetFloorRendering();
152  }
153  }
154 
161  public bool ContainsObject(Vector3 gameObjectPosition)
162  {
163  return ContainsObject(gameObjectPosition, UnityEngine.Experimental.XR.Boundary.Type.TrackedArea);
164  }
165 
173  public bool ContainsObject(Vector3 gameObjectPosition, UnityEngine.Experimental.XR.Boundary.Type boundaryType)
174  {
175  gameObjectPosition = CameraCache.Main.transform.parent.InverseTransformPoint(gameObjectPosition);
176 
177  if (gameObjectPosition.y < boundaryFloor || gameObjectPosition.y > boundaryHeight)
178  {
179  return false;
180  }
181 
182  if (boundaryType == UnityEngine.Experimental.XR.Boundary.Type.PlayArea)
183  {
184  if (inscribedRectangle == null || !inscribedRectangle.IsRectangleValid)
185  {
186  return false;
187  }
188 
189  return inscribedRectangle.IsPointInRectangleBounds(new Vector2(gameObjectPosition.x, gameObjectPosition.z));
190  }
191  else if (boundaryType == UnityEngine.Experimental.XR.Boundary.Type.TrackedArea)
192  {
193  // Check if the supplied game object's position is within the bounds volume.
194  return EdgeHelpers.IsInside(boundaryGeometryEdges, new Vector2(gameObjectPosition.x, gameObjectPosition.z));
195  }
196 
197  return false;
198  }
199 
206  public Vector3[] TryGetBoundaryRectanglePoints()
207  {
208  if (inscribedRectangle == null || !inscribedRectangle.IsRectangleValid)
209  {
210  return null;
211  }
212 
213  var points2d = inscribedRectangle.GetRectanglePoints();
214 
215  var positions = new Vector3[points2d.Length];
216  for (int i = 0; i < points2d.Length; ++i)
217  {
218  positions[i] = CameraCache.Main.transform.parent.TransformPoint(new Vector3(points2d[i].x, boundaryFloor, points2d[i].y));
219  }
220  return positions;
221  }
222 
226  internal bool TryGetBoundaryRectangleParams(out Vector3 center, out float angle, out float width, out float height)
227  {
228  if (inscribedRectangle == null || !inscribedRectangle.IsRectangleValid)
229  {
230  center = Vector3.zero;
231  angle = width = height = 0.0f;
232  return false;
233  }
234 
235  Vector2 center2D;
236  inscribedRectangle.GetRectangleParams(out center2D, out angle, out width, out height);
237  center = CameraCache.Main.transform.parent.TransformPoint(new Vector3(center2D.x, boundaryFloor, center2D.y));
238  return true;
239  }
240 
244  public void CalculateBoundaryVolume()
245  {
246 #if !UNITY_WSA
247  // This always returns false in WindowsMR.
248  if (!UnityEngine.Experimental.XR.Boundary.configured)
249  {
250  Debug.Log("Boundary not configured.");
251  return;
252  }
253 #endif
254 
255  if (XRDevice.GetTrackingSpaceType() != TrackingSpaceType.RoomScale)
256  {
257  Debug.Log("No boundary for non-room scale experiences.");
258  return;
259  }
260 
261  // Get all the bounds setup by the user.
262  var boundaryGeometryPoints = new List<Vector3>(0);
263  if (UnityEngine.Experimental.XR.Boundary.TryGetGeometry(boundaryGeometryPoints, UnityEngine.Experimental.XR.Boundary.Type.TrackedArea))
264  {
265  if (boundaryGeometryPoints.Count > 0)
266  {
267  for (int pointIndex = 0; pointIndex < boundaryGeometryPoints.Count; pointIndex++)
268  {
269  boundaryFloor = Math.Min(boundaryFloor, boundaryGeometryPoints[pointIndex].y);
270  }
271 
272  boundaryGeometryEdges = EdgeHelpers.ConvertVector3ListToEdgeArray(boundaryGeometryPoints);
273 
274  inscribedRectangle = new InscribedRectangle(boundaryGeometryEdges);
275  }
276  }
277  else
278  {
279  Debug.Log("TryGetGeometry returned false.");
280  }
281  }
282 #endif
283  }
284 }
Places a floor quad to ground the scene. Allows you to check if your GameObject is within setup bound...
static bool IsInside(Edge[] edges, Vector2 point)
Returns true if the given point is within the boundary.
Definition: EdgeHelpers.cs:48
bool IsPointInRectangleBounds(Vector2 point)
Returns true if the given point is within the inscribed rectangle.
void GetRectangleParams(out Vector2 centerOut, out float angleOut, out float widthOut, out float heightOut)
Retrieves the parameters describing the largest inscribed rectangle. within the bounds ...
static Edge [] ConvertVector3ListToEdgeArray(IList< Vector3 > points)
Helper to convert a list of Vector3 points into an array of Edges.
Definition: EdgeHelpers.cs:23
Vector2 [] GetRectanglePoints()
Returns the four points that make up the inscribed rectangle.
Helper struct to hold an edge.
Definition: Edge.cs:11
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
bool IsRectangleValid
Use this to determine if there is a valid inscribed rectangle.
static Camera Main
Returns a cached reference to the main camera and uses Camera.main if it hasn&#39;t been cached yet...
Definition: CameraCache.cs:20
Singleton behaviour class, used for components that should only have one instance.
Definition: Singleton.cs:14