AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
UI.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 using HoloToolkit.Unity;
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Collections.ObjectModel;
9 using UnityEngine;
10 using UnityEngine.UI;
11 
12 namespace HoloToolkit.Examples.SpatialUnderstandingFeatureOverview
13 {
14  public class UI : LineDrawer
15  {
16  // Consts
17  public const float MenuWidth = 1.5f;
18  public const float MenuHeight = 1.0f;
19  public const float MenuMinDepth = 2.0f;
20 
21  // Enums
22  public enum Panels
23  {
24  Topology,
25  Shapes,
27  PANEL_COUNT
28  }
29  [Serializable]
30  public class TabPanel
31  {
32  public Button Button;
33  public Image ButtonImage;
34  public Image Background;
35  public GridLayoutGroup ButtonGrid;
36  public List<Button> GridButtons = new List<Button>();
37  }
38 
39  // Config
40  public Canvas ParentCanvas;
41  public TabPanel[] ButtonPanels = new TabPanel[(int)Panels.PANEL_COUNT];
42  public Button PrefabButton;
43  public LayerMask UILayerMask;
44 
45  // Properties
46  public bool HasPlacedMenu { get; private set; }
47  public AnimatedBox MenuAnimatedBox { get; private set; }
48  public Panels ActivePanel { get; private set; }
49 
50  // Privates
51  private DateTime timeLastQuery = DateTime.MinValue;
52  private bool placedMenuNeedsBillboard = false;
53 
54  // Functions
55  private void Start()
56  {
57  // Turn menu off until we're placed
58  ParentCanvas.gameObject.SetActive(false);
59 
60  // Events
61  SpatialUnderstanding.Instance.ScanStateChanged += OnScanStateChanged;
62  }
63 
64  protected override void OnDestroy()
65  {
66  if (SpatialUnderstanding.Instance != null)
67  {
68  SpatialUnderstanding.Instance.ScanStateChanged -= OnScanStateChanged;
69  }
70 
71  base.OnDestroy();
72  }
73 
74  private void OnScanStateChanged()
75  {
76  // If we are leaving the None state, go ahead and register shapes now
78  SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
79  {
80  // Make sure we've created our shapes
81  ShapeDefinition.Instance.CreateShapes();
82 
83  // Make sure our solver is initialized
85 
86  // Setup the menu
87  StartCoroutine(SetupMenu());
88  }
89  }
90 
91  private IEnumerator SetupMenu()
92  {
93  // Setup for queries
95  IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
96 
97  // Place on a wall (do it in a thread, as it can take a little while)
99  SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition.Create_OnWall(new Vector3(MenuWidth * 0.5f, MenuHeight * 0.5f, MenuMinDepth * 0.5f), 0.5f, 3.0f);
100  SpatialUnderstandingDllObjectPlacement.ObjectPlacementResult placementResult = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResult();
101 
102  var thread =
103 #if UNITY_EDITOR || !UNITY_WSA
104  new System.Threading.Thread
105 #else
106  System.Threading.Tasks.Task.Run
107 #endif
108  (() =>
109  {
111  "UIPlacement",
112  SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placeOnWallDef),
113  0,
114  IntPtr.Zero,
115  0,
116  IntPtr.Zero,
117  SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResultPtr()) == 0)
118  {
119  placementResult = null;
120  }
121  });
122 
123 #if UNITY_EDITOR || !UNITY_WSA
124  thread.Start();
125 #endif
126 
127  while
128  (
129 #if UNITY_EDITOR || !UNITY_WSA
130  !thread.Join(TimeSpan.Zero)
131 #else
132  !thread.IsCompleted
133 #endif
134  )
135  {
136  yield return null;
137  }
138  if (placementResult != null)
139  {
140  Debug.Log("PlaceMenu - ObjectSolver-OnWall");
141  Vector3 posOnWall = placementResult.Position - placementResult.Forward * MenuMinDepth * 0.5f;
142  PlaceMenu(posOnWall, -placementResult.Forward);
143  yield break;
144  }
145 
146  // Wait a frame
147  yield return null;
148 
149  Transform cameraTransform = CameraCache.Main.transform;
150  // Fallback, place floor (add a facing, if so)
152  resultsTopology.Length, resultsTopologyPtr);
153  if (locationCount > 0)
154  {
155  Debug.Log("PlaceMenu - LargestPositionsOnFloor");
156  SpatialUnderstandingDllTopology.TopologyResult menuLocation = resultsTopology[0];
157  Vector3 menuPosition = menuLocation.position + Vector3.up * MenuHeight;
158  Vector3 menuLookVector = cameraTransform.position - menuPosition;
159  PlaceMenu(menuPosition, (new Vector3(menuLookVector.x, 0.0f, menuLookVector.z)).normalized, true);
160  yield break;
161  }
162 
163  // Final fallback just in front of the user
164  SpatialUnderstandingDll.Imports.QueryPlayspaceAlignment(SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignmentPtr());
165  SpatialUnderstandingDll.Imports.PlayspaceAlignment alignment = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignment();
166  Vector3 defaultPosition = cameraTransform.position + cameraTransform.forward * 2.0f;
167  PlaceMenu(new Vector3(defaultPosition.x, Math.Max(defaultPosition.y, alignment.FloorYValue + 1.5f), defaultPosition.z), (new Vector3(cameraTransform.forward.x, 0.0f, cameraTransform.forward.z)).normalized, true);
168  Debug.Log("PlaceMenu - InFrontOfUser");
169  }
170 
171  private void SetActiveTab(Panels panel)
172  {
173  // Set it
174  ActivePanel = panel;
175  timeLastQuery = DateTime.MinValue;
176 
177  // Colors
178  Update_Colors();
179  }
180 
181  private void Update_Colors()
182  {
183  const float TimeToFadeAfterQuery = 3.0f;
184 
185  // Time since query (fade for a bit after a query)
186  float timeSinceQuery = (float)(DateTime.Now - timeLastQuery).TotalSeconds;
187  float alphaScale = Mathf.SmoothStep(0.0f, 1.0f, Mathf.Clamp01(timeSinceQuery - TimeToFadeAfterQuery)) * 0.8f + 0.2f;
188 
189  // Colors
190  Color colorButtonActive = new Color(1.0f, 1.0f, 1.0f, 0.8f * alphaScale);
191  Color colorButtonInactive = new Color(1.0f, 1.0f, 1.0f, 0.25f * alphaScale);
192  Color colorPanelActive = new Color(1.0f, 1.0f, 1.0f, 0.6f * alphaScale);
193  Color colorPanelInactive = new Color(1.0f, 1.0f, 1.0f, 0.15f * alphaScale);
194 
195  // Colors on buttons
196  for (int i = 0; i < (int)Panels.PANEL_COUNT; ++i)
197  {
198  bool isEnabled = (i == (int)ActivePanel);
199 
200  ButtonPanels[i].ButtonImage.color = isEnabled ? colorButtonActive : colorButtonInactive;
201  ButtonPanels[i].Background.enabled = isEnabled;
202  ButtonPanels[i].Background.color = isEnabled ? colorPanelActive : colorPanelInactive;
203  ButtonPanels[i].ButtonGrid.enabled = isEnabled;
204 
205  for (int j = 0; j < ButtonPanels[i].GridButtons.Count; ++j)
206  {
207  ButtonPanels[i].GridButtons[j].gameObject.SetActive(isEnabled);
208  }
209  }
210  }
211 
212  private void SetupMenus()
213  {
214  // Topology queries
215  ButtonPanels[(int)Panels.Topology].Button.GetComponentInChildren<Text>().text = "Topology Queries";
216  ButtonPanels[(int)Panels.Topology].Button.onClick.AddListener(() => { SetActiveTab(Panels.Topology); });
217  AddButton("Position on wall", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindPositionOnWall(); timeLastQuery = DateTime.MinValue; });
218  AddButton("Large positions on wall", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindLargePositionsOnWalls(); timeLastQuery = DateTime.MinValue; });
219  AddButton("Largest wall", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindLargeWall(); timeLastQuery = DateTime.MinValue; });
220  AddButton("Positions on floor", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindPositionsOnFloor(); timeLastQuery = DateTime.MinValue; });
221  AddButton("Large positions on floor", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindLargestPositionsOnFloor(); timeLastQuery = DateTime.MinValue; });
222  AddButton("Place objects positions", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindPositionsPlaceable(); timeLastQuery = DateTime.MinValue; });
223  AddButton("Large positions sittable", Panels.Topology, () => { SpaceVisualizer.Instance.Query_Topology_FindLargePositionsSittable(); timeLastQuery = DateTime.MinValue; });
224 
225  // Shape queries
226  ButtonPanels[(int)Panels.Shapes].Button.GetComponentInChildren<Text>().text = "Shape Queries";
227  ButtonPanels[(int)Panels.Shapes].Button.onClick.AddListener(() => { SetActiveTab(Panels.Shapes); });
228  ReadOnlyCollection<string> customShapes = ShapeDefinition.Instance.CustomShapeDefinitions;
229  for (int i = 0; i < customShapes.Count; ++i)
230  {
231  string shapeName = customShapes[i];
232  AddButton(shapeName, Panels.Shapes, () =>
233  {
234  SpaceVisualizer.Instance.Query_Shape_FindShapeHalfDims(shapeName);
235  timeLastQuery = DateTime.MinValue;
236  });
237  }
238 
239  // Level solver
240  ButtonPanels[(int)Panels.LevelSolver].Button.GetComponentInChildren<Text>().text = "Object Placement";
241  ButtonPanels[(int)Panels.LevelSolver].Button.onClick.AddListener(() => { SetActiveTab(Panels.LevelSolver); timeLastQuery = DateTime.MinValue; });
242  AddButton("On Floor", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnFloor(); timeLastQuery = DateTime.MinValue; });
243  AddButton("On Wall", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnWall(); timeLastQuery = DateTime.MinValue; });
244  AddButton("On Ceiling", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnCeiling(); timeLastQuery = DateTime.MinValue; });
245  AddButton("On SurfaceEdge", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnEdge(); timeLastQuery = DateTime.MinValue; });
246  AddButton("On FloorAndCeiling", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnFloorAndCeiling(); timeLastQuery = DateTime.MinValue; });
247  AddButton("RandomInAir AwayFromMe", Panels.LevelSolver, () => { LevelSolver.Instance.Query_RandomInAir_AwayFromMe(); timeLastQuery = DateTime.MinValue; });
248  AddButton("OnEdge NearCenter", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnEdge_NearCenter(); timeLastQuery = DateTime.MinValue; });
249  AddButton("OnFloor AwayFromMe", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnFloor_AwayFromMe(); timeLastQuery = DateTime.MinValue; });
250  AddButton("OnFloor NearMe", Panels.LevelSolver, () => { LevelSolver.Instance.Query_OnFloor_NearMe(); timeLastQuery = DateTime.MinValue; });
251 
252  // Default one of them active
253  SetActiveTab(Panels.Topology);
254  }
255 
256  private void AddButton(string text, Panels panel, UnityEngine.Events.UnityAction action)
257  {
258  Button button = Instantiate(PrefabButton);
259  button.GetComponentInChildren<Text>().text = text;
260  button.transform.SetParent(ButtonPanels[(int)panel].ButtonGrid.transform, false);
261  button.transform.localScale = Vector3.one;
262  button.onClick.AddListener(action);
263 
264  ButtonPanels[(int)panel].GridButtons.Add(button);
265  }
266 
267  private void PlaceMenu(Vector3 position, Vector3 normal, bool needsBillboarding = false)
268  {
269  // Offset in a bit
270  position -= normal * 0.05f;
271  Quaternion rotation = Quaternion.LookRotation(normal, Vector3.up);
272 
273  // Place it
274  transform.position = position;
275  transform.rotation = rotation;
276 
277  // Setup the menu
278  SetupMenus();
279 
280  // Enable it
281  ParentCanvas.gameObject.SetActive(true);
282 
283  // Create up a box
284  MenuAnimatedBox = new AnimatedBox(0.0f, position, rotation, new Color(1.0f, 1.0f, 1.0f, 0.25f), new Vector3(MenuWidth * 0.5f, MenuHeight * 0.5f, 0.025f), LineDrawer.DefaultLineWidth * 0.5f);
285 
286  // Initial position
287  transform.position = MenuAnimatedBox.AnimPosition.Evaluate(MenuAnimatedBox.Time);
288  transform.rotation = MenuAnimatedBox.Rotation * Quaternion.AngleAxis(360.0f * MenuAnimatedBox.AnimRotation.Evaluate(MenuAnimatedBox.Time), Vector3.up);
289 
290  // Billboarding (note that because of the transition animation we need to place this late)
291  placedMenuNeedsBillboard = needsBillboarding;
292 
293  // And mark that we've done it
294  HasPlacedMenu = true;
295  }
296 
297  private void Update()
298  {
299  Update_Colors();
300 
301  // Animated box
302  if (MenuAnimatedBox != null)
303  {
304  // We're using the animated box for the animation only
305  MenuAnimatedBox.Update(Time.deltaTime);
306 
307  // Billboarding
308  if (MenuAnimatedBox.IsAnimationComplete &&
309  placedMenuNeedsBillboard)
310  {
311  // Rotate to face the user
312  transform.position = MenuAnimatedBox.AnimPosition.Evaluate(MenuAnimatedBox.Time);
313  Vector3 lookDirTarget = CameraCache.Main.transform.position - transform.position;
314  lookDirTarget = (new Vector3(lookDirTarget.x, 0.0f, lookDirTarget.z)).normalized;
315  transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(-lookDirTarget), Time.deltaTime * 10.0f);
316  }
317  else
318  {
319  // Keep the UI locked to the animated box
320  transform.position = MenuAnimatedBox.AnimPosition.Evaluate(MenuAnimatedBox.Time);
321  transform.rotation = MenuAnimatedBox.Rotation * Quaternion.AngleAxis(360.0f * MenuAnimatedBox.AnimRotation.Evaluate(MenuAnimatedBox.Time), Vector3.up);
322  }
323  }
324  }
325  }
326 }
static int QueryTopology_FindLargestPositionsOnFloor([In] int locationCount, [In, Out] IntPtr locationData)
Finds the largest spaces on the floor
Encapsulates the primary DLL functions, including marshalling helper functions. The DLL functions are...
Object placement result. Defines an oriented bounding box result for the object placement query...
Playspace alignment results. Reports internal alignment of room in Unity space and basic alignment of...
Encapsulates the object placement queries of the understanding DLL. These queries will not be valid u...
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
static T Instance
Returns the Singleton instance of the classes type. If no instance is found, then we search for an in...
Definition: Singleton.cs:26
Encapsulates the topology queries of the understanding DLL. These queries will not be valid until aft...
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
Defines an object placement query. A query consists of a type a name, type, set of rules...
Result of a topology query. Typically results return an array of these structures.
The SpatialUnderstanding class controls the state and flow of the scanning process used in the unders...
static int Solver_PlaceObject([In, MarshalAs(UnmanagedType.LPStr)] string objectName, [In] IntPtr placementDefinition, [In] int placementRuleCount, [In] IntPtr placementRules, [In] int constraintCount, [In] IntPtr placementConstraints, [Out] IntPtr placementResult)
Executes an object placement query.
static int QueryPlayspaceAlignment([In] IntPtr playspaceAlignment)
Query the playspace alignment data. This will not be valid until after scanning is finalized...
static ObjectPlacementDefinition Create_OnWall(Vector3 halfDims, float heightMin, float heightMax, WallTypeFlags wallTypes=WallTypeFlags.External|WallTypeFlags.Normal, float marginLeft=0.0f, float marginRight=0.0f)
Constructs an object placement query definition requiring the object to be placed on a wall...