AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
WorldAnchorManager.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 UnityEngine;
5 
6 #if UNITY_WSA
7 using System;
8 using System.Collections.Generic;
9 #if UNITY_2017_2_OR_NEWER
10 using UnityEngine.XR.WSA;
11 using UnityEngine.XR.WSA.Persistence;
12 #else
13 using UnityEngine.VR.WSA;
14 using UnityEngine.VR.WSA.Persistence;
15 #endif
16 #if !UNITY_EDITOR
18 #endif
19 #endif
20 
21 
22 namespace HoloToolkit.Unity
23 {
27  public class WorldAnchorManager : Singleton<WorldAnchorManager>
28  {
32  public TextMesh AnchorDebugText;
33 
38  [Tooltip("Enables detailed logs in console window. If the Sharing Service is used, it will inherit the log settings.")]
39  public bool ShowDetailedLogs;
40 
44  [Tooltip("Enables anchors to be stored from subsequent game sessions.")]
45  public bool PersistentAnchors;
46 
47 #if UNITY_WSA
48  protected struct AnchorAttachmentInfo
56  {
57  public GameObject AnchoredGameObject { get; set; }
58  public string AnchorName { get; set; }
59  public AnchorOperation Operation { get; set; }
60  }
61 
65  protected enum AnchorOperation
66  {
70  Save,
74  Delete
75  }
76 
80  protected Queue<AnchorAttachmentInfo> LocalAnchorOperations = new Queue<AnchorAttachmentInfo>();
81 
86  public WorldAnchorStore AnchorStore { get; protected set; }
87 
91  protected Dictionary<string, GameObject> AnchorGameObjectReferenceList = new Dictionary<string, GameObject>(0);
92 
93  #region Unity Methods
94 
95  protected override void Awake()
96  {
97  base.Awake();
98  AnchorStore = null;
99  }
100 
101  protected virtual void Start()
102  {
103  WorldAnchorStore.GetAsync(AnchorStoreReady);
104  }
105 
106  protected virtual void Update()
107  {
108  if (AnchorStore == null) { return; }
109 
110  if (LocalAnchorOperations.Count > 0)
111  {
112  DoAnchorOperation(LocalAnchorOperations.Dequeue());
113  }
114  }
115 
116  #endregion // Unity Methods
117 
118  #region Event Callbacks
119 
124  protected virtual void AnchorStoreReady(WorldAnchorStore anchorStore)
125  {
126  AnchorStore = anchorStore;
127 
128  if (!PersistentAnchors)
129  {
130  AnchorStore.Clear();
131  }
132  }
133 
141  private void Anchor_OnTrackingChanged(WorldAnchor anchor, bool located)
142  {
143  if (located && SaveAnchor(anchor))
144  {
145  if (ShowDetailedLogs)
146  {
147  Debug.LogFormat("[WorldAnchorManager] Successfully updated cached anchor \"{0}\".", anchor.name);
148  }
149 
150  if (AnchorDebugText != null)
151  {
152  AnchorDebugText.text += string.Format("\nSuccessfully updated cached anchor \"{0}\".", anchor.name);
153  }
154  }
155  else
156  {
157  if (ShowDetailedLogs)
158  {
159  Debug.LogFormat("[WorldAnchorManager] Failed to locate cached anchor \"{0}\", attempting to acquire anchor again.", anchor.name);
160  }
161 
162  if (AnchorDebugText != null)
163  {
164  AnchorDebugText.text += string.Format("\nFailed to locate cached anchor \"{0}\", attempting to acquire anchor again.", anchor.name);
165  }
166 
167  GameObject anchoredObject;
168  AnchorGameObjectReferenceList.TryGetValue(anchor.name, out anchoredObject);
169  AnchorGameObjectReferenceList.Remove(anchor.name);
170  AttachAnchor(anchoredObject, anchor.name);
171  }
172 
173  anchor.OnTrackingChanged -= Anchor_OnTrackingChanged;
174  }
175 
176  #endregion // Event Callbacks
177 #endif
178  public static string GenerateAnchorName(GameObject gameObjectToAnchor, string proposedAnchorname = null)
186  {
187  return string.IsNullOrEmpty(proposedAnchorname) ? gameObjectToAnchor.name : proposedAnchorname;
188  }
189 
199  public string AttachAnchor(GameObject gameObjectToAnchor, string anchorName = null)
200  {
201 #if !UNITY_WSA || UNITY_EDITOR
202  Debug.LogWarning("World Anchor Manager does not work for this build. AttachAnchor will not be called.");
203  return null;
204 #else
205  if (gameObjectToAnchor == null)
206  {
207  Debug.LogError("[WorldAnchorManager] Must pass in a valid gameObject");
208  return null;
209  }
210 
211  // This case is unexpected, but just in case.
212  if (AnchorStore == null)
213  {
214  Debug.LogWarning("[WorldAnchorManager] AttachAnchor called before anchor store is ready.");
215  }
216 
217  anchorName = GenerateAnchorName(gameObjectToAnchor, anchorName);
218 
219  LocalAnchorOperations.Enqueue(
220  new AnchorAttachmentInfo
221  {
222  AnchoredGameObject = gameObjectToAnchor,
223  AnchorName = anchorName,
224  Operation = AnchorOperation.Save
225  }
226  );
227 
228  return anchorName;
229 #endif
230  }
231 
236  public void RemoveAnchor(GameObject gameObjectToUnanchor)
237  {
238  if (gameObjectToUnanchor == null)
239  {
240  Debug.LogError("[WorldAnchorManager] Invalid GameObject! Try removing anchor by name.");
241  if (AnchorDebugText != null)
242  {
243  AnchorDebugText.text += "\nInvalid GameObject! Try removing anchor by name.";
244  }
245  return;
246  }
247 
248  RemoveAnchor(string.Empty, gameObjectToUnanchor);
249  }
250 
256  public void RemoveAnchor(string anchorName)
257  {
258  if (string.IsNullOrEmpty(anchorName))
259  {
260  Debug.LogErrorFormat("[WorldAnchorManager] Invalid anchor \"{0}\"! Try removing anchor by GameObject.", anchorName);
261  if (AnchorDebugText != null)
262  {
263  AnchorDebugText.text += string.Format("\nInvalid anchor \"{0}\"! Try removing anchor by GameObject.", anchorName);
264  }
265  return;
266  }
267 
268  RemoveAnchor(anchorName, null);
269  }
270 
277  private void RemoveAnchor(string anchorName, GameObject gameObjectToUnanchor)
278  {
279  if (string.IsNullOrEmpty(anchorName) && gameObjectToUnanchor == null)
280  {
281  Debug.LogWarning("Invalid Remove Anchor Request!");
282  return;
283  }
284 
285 #if !UNITY_WSA || UNITY_EDITOR
286  Debug.LogWarning("World Anchor Manager does not work for this build. RemoveAnchor will not be called.");
287 #else
288  // This case is unexpected, but just in case.
289  if (AnchorStore == null)
290  {
291  Debug.LogWarning("[WorldAnchorManager] RemoveAnchor called before anchor store is ready.");
292  }
293 
294  LocalAnchorOperations.Enqueue(
295  new AnchorAttachmentInfo
296  {
297  AnchoredGameObject = gameObjectToUnanchor,
298  AnchorName = anchorName,
299  Operation = AnchorOperation.Delete
300  });
301 #endif
302  }
303 
307  public void RemoveAllAnchors()
308  {
309 #if !UNITY_WSA || UNITY_EDITOR
310  Debug.LogWarning("World Anchor Manager does not work for this build. RemoveAnchor will not be called.");
311 #else
312  SpatialMappingManager spatialMappingManager = SpatialMappingManager.Instance;
313 
314  // This case is unexpected, but just in case.
315  if (AnchorStore == null)
316  {
317  Debug.LogWarning("[WorldAnchorManager] RemoveAllAnchors called before anchor store is ready.");
318  }
319 
320  var anchors = FindObjectsOfType<WorldAnchor>();
321 
322  if (anchors == null) { return; }
323 
324  for (var i = 0; i < anchors.Length; i++)
325  {
326  // Don't remove SpatialMapping anchors if exists
327  if (spatialMappingManager != null && anchors[i].gameObject.transform.parent.gameObject == spatialMappingManager.gameObject)
328  { continue; }
329 
330  // Let's check to see if there are anchors we weren't accounting for.
331  // Maybe they were created without using the WorldAnchorManager.
332  if (!AnchorGameObjectReferenceList.ContainsKey(anchors[i].name))
333  {
334  Debug.LogWarning("[WorldAnchorManager] Removing an anchor that was created outside of the WorldAnchorManager. Please use the WorldAnchorManager to create or delete anchors.");
335  if (AnchorDebugText != null)
336  {
337  AnchorDebugText.text += string.Format("\nRemoving an anchor that was created outside of the WorldAnchorManager. Please use the WorldAnchorManager to create or delete anchors.");
338  }
339  }
340 
341  LocalAnchorOperations.Enqueue(new AnchorAttachmentInfo
342  {
343  AnchorName = anchors[i].name,
344  AnchoredGameObject = anchors[i].gameObject,
345  Operation = AnchorOperation.Delete
346  });
347  }
348 #endif
349  }
350 
351 #if UNITY_WSA
352  protected void DoAnchorOperation(AnchorAttachmentInfo anchorAttachmentInfo)
357  {
358  string anchorId = anchorAttachmentInfo.AnchorName;
359  GameObject anchoredGameObject = anchorAttachmentInfo.AnchoredGameObject;
360 
361  switch (anchorAttachmentInfo.Operation)
362  {
363  case AnchorOperation.Save:
364  if (anchoredGameObject == null)
365  {
366  Debug.LogError("[WorldAnchorManager] The GameObject referenced must have been destroyed before we got a chance to anchor it.");
367  if (AnchorDebugText != null)
368  {
369  AnchorDebugText.text += "\nThe GameObject referenced must have been destroyed before we got a chance to anchor it.";
370  }
371  break;
372  }
373 
374  if (string.IsNullOrEmpty(anchorId))
375  {
376  anchorId = anchoredGameObject.name;
377  }
378 
379  // Try to load a previously saved world anchor.
380  WorldAnchor savedAnchor = AnchorStore.Load(anchorId, anchoredGameObject);
381 
382  if (savedAnchor == null)
383  {
384  // Check if we need to import the anchor.
385  if (ImportAnchor(anchorId, anchoredGameObject) == false)
386  {
387  if (ShowDetailedLogs)
388  {
389  Debug.LogFormat("[WorldAnchorManager] Anchor could not be loaded for \"{0}\". Creating a new anchor.", anchoredGameObject.name);
390  }
391 
392  if (AnchorDebugText != null)
393  {
394  AnchorDebugText.text += string.Format("\nAnchor could not be loaded for \"{0}\". Creating a new anchor.", anchoredGameObject.name);
395  }
396 
397  // Create anchor since one does not exist.
398  CreateAnchor(anchoredGameObject, anchorId);
399  }
400  }
401  else
402  {
403  savedAnchor.name = anchorId;
404  if (ShowDetailedLogs)
405  {
406  Debug.LogFormat("[WorldAnchorManager] Anchor loaded from anchor store and updated for \"{0}\".", anchoredGameObject.name);
407  }
408 
409  if (AnchorDebugText != null)
410  {
411  AnchorDebugText.text += string.Format("\nAnchor loaded from anchor store and updated for \"{0}\".", anchoredGameObject.name);
412  }
413  }
414 
415  AnchorGameObjectReferenceList.Add(anchorId, anchoredGameObject);
416  break;
417  case AnchorOperation.Delete:
418  if (AnchorStore == null)
419  {
420  Debug.LogError("[WorldAnchorManager] Remove anchor called before anchor store is ready.");
421  break;
422  }
423 
424  // If we don't have a GameObject reference, let's try to get the GameObject reference from our dictionary.
425  if (!string.IsNullOrEmpty(anchorId) && anchoredGameObject == null)
426  {
427  AnchorGameObjectReferenceList.TryGetValue(anchorId, out anchoredGameObject);
428  }
429 
430  if (anchoredGameObject != null)
431  {
432  var anchor = anchoredGameObject.GetComponent<WorldAnchor>();
433 
434  if (anchor != null)
435  {
436  anchorId = anchor.name;
437  DestroyImmediate(anchor);
438  }
439  else
440  {
441  Debug.LogErrorFormat("[WorldAnchorManager] Unable remove WorldAnchor from {0}!", anchoredGameObject.name);
442  if (AnchorDebugText != null)
443  {
444  AnchorDebugText.text += string.Format("\nUnable remove WorldAnchor from {0}!", anchoredGameObject.name);
445  }
446  }
447  }
448  else
449  {
450  Debug.LogError("[WorldAnchorManager] Unable find a GameObject to remove an anchor from!");
451  if (AnchorDebugText != null)
452  {
453  AnchorDebugText.text += "\nUnable find a GameObject to remove an anchor from!";
454  }
455  }
456 
457  if (!string.IsNullOrEmpty(anchorId))
458  {
459  AnchorGameObjectReferenceList.Remove(anchorId);
460  DeleteAnchor(anchorId);
461  }
462  else
463  {
464  Debug.LogError("[WorldAnchorManager] Unable find an anchor to delete!");
465  if (AnchorDebugText != null)
466  {
467  AnchorDebugText.text += "\nUnable find an anchor to delete!";
468  }
469  }
470 
471  break;
472  default:
473  throw new ArgumentOutOfRangeException();
474  }
475  }
476 
482  private void CreateAnchor(GameObject gameObjectToAnchor, string anchorName)
483  {
484  var anchor = gameObjectToAnchor.EnsureComponent<WorldAnchor>();
485  anchor.name = anchorName;
486 
487  // Sometimes the anchor is located immediately. In that case it can be saved immediately.
488  if (anchor.isLocated)
489  {
490  SaveAnchor(anchor);
491  }
492  else
493  {
494  // Other times we must wait for the tracking system to locate the world.
495  anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
496  }
497  }
498 
503  private bool SaveAnchor(WorldAnchor anchor)
504  {
505  // Save the anchor to persist holograms across sessions.
506  if (AnchorStore.Save(anchor.name, anchor))
507  {
508  if (ShowDetailedLogs)
509  {
510  Debug.LogFormat("[WorldAnchorManager] Successfully saved anchor \"{0}\".", anchor.name);
511  }
512 
513  if (AnchorDebugText != null)
514  {
515  AnchorDebugText.text += string.Format("\nSuccessfully saved anchor \"{0}\".", anchor.name);
516  }
517 
518  ExportAnchor(anchor);
519 
520  return true;
521  }
522 
523  Debug.LogErrorFormat("[WorldAnchorManager] Failed to save anchor \"{0}\"!", anchor.name);
524 
525  if (AnchorDebugText != null)
526  {
527  AnchorDebugText.text += string.Format("\nFailed to save anchor \"{0}\"!", anchor.name);
528  }
529  return false;
530  }
531 
536  private void DeleteAnchor(string anchorId)
537  {
538  if (AnchorStore.Delete(anchorId))
539  {
540  Debug.LogFormat("[WorldAnchorManager] Anchor {0} deleted successfully.", anchorId);
541  if (AnchorDebugText != null)
542  {
543  AnchorDebugText.text += string.Format("\nAnchor {0} deleted successfully.", anchorId);
544  }
545  }
546  else
547  {
548  if (string.IsNullOrEmpty(anchorId))
549  {
550  anchorId = "NULL";
551  }
552 
553  Debug.LogErrorFormat("[WorldAnchorManager] Failed to delete \"{0}\".", anchorId);
554  if (AnchorDebugText != null)
555  {
556  AnchorDebugText.text += string.Format("\nFailed to delete \"{0}\".", anchorId);
557  }
558  }
559  }
560 
567  protected virtual bool ImportAnchor(string anchorId, GameObject objectToAnchor)
568  {
569  return false;
570  }
571 
577  protected virtual void ExportAnchor(WorldAnchor anchor) { }
578 #endif
579  }
580 }
void RemoveAllAnchors()
Removes all anchors from the scene and deletes them from the anchor store.
bool ShowDetailedLogs
Enables detailed logs in console window.
Wrapper around world anchor store to streamline some of the persistence API busy work.
void RemoveAnchor(GameObject gameObjectToUnanchor)
Removes the anchor component from the GameObject and deletes the anchor from the anchor store...
string AttachAnchor(GameObject gameObjectToAnchor, string anchorName=null)
Attaches an anchor to the GameObject. If the anchor store has an anchor with the specified name it wi...
void RemoveAnchor(string anchorName)
Removes the anchor from the anchor store, without a GameObject reference. If a GameObject reference c...
TextMesh AnchorDebugText
Debug text for displaying information.
bool PersistentAnchors
Enables anchors to be stored from subsequent game sessions.
Singleton behaviour class, used for components that should only have one instance.
Definition: Singleton.cs:14