AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
UNetAnchorManager.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 using UnityEngine.Networking;
6 #if UNITY_WSA
7 using System;
8 using System.Collections.Generic;
9 using System.Collections.ObjectModel;
10 #if UNITY_2017_2_OR_NEWER
11 using UnityEngine.XR.WSA;
12 using UnityEngine.XR.WSA.Persistence;
13 using UnityEngine.XR.WSA.Sharing;
14 #else
15 using UnityEngine.VR;
16 using UnityEngine.VR.WSA;
17 using UnityEngine.VR.WSA.Persistence;
18 using UnityEngine.VR.WSA.Sharing;
19 #endif
21 #endif
22 
23 namespace HoloToolkit.Unity.SharingWithUNET
24 {
28  public class UNetAnchorManager : NetworkBehaviour
29  {
34  private static UNetAnchorManager _Instance;
35 
36  public static UNetAnchorManager Instance
37  {
38  get
39  {
40  if (_Instance == null)
41  {
42  _Instance = FindObjectOfType<UNetAnchorManager>();
43  }
44  return _Instance;
45  }
46  }
47 
51  [SyncVar]
52  public string AnchorName = "";
53 
57  public bool UseSpatialMapping = true;
58 
62  public bool AnchorEstablished { get; set; }
63 
67  public bool ImportInProgress { get; private set; }
68 
72  public bool DownloadingAnchor { get; private set; }
73 
77  private NetworkManager networkManager;
78 
82  private GenericNetworkTransmitter networkTransmitter;
83 
84 #if UNITY_WSA
85  const string SavedAnchorKey = "SavedAnchorName";
86 
90  private bool StartedObserver = false;
91 
95  private SpatialMappingManager spatialMapping;
96 
100  private GameObject objectToAnchor;
101 
106  private const uint MinTrustworthySerializedAnchorDataSize = 500000;
107 
111  private List<byte> exportingAnchorBytes = new List<byte>(0);
112 
116  private bool createdAnchor = false;
117 
121  private string oldAnchorName = "";
122 
126  private byte[] anchorData = null;
127 
131  private bool gotOne = false;
132 
136  private string exportingAnchorName;
137 #endif
138 
143  private bool CheckConfiguration()
144  {
145  networkTransmitter = GenericNetworkTransmitter.Instance;
146  if (networkTransmitter == null)
147  {
148  Debug.Log("No UNetNetworkTransmitter found in scene");
149  return false;
150  }
151 
152  networkManager = NetworkManager.singleton;
153  if (networkManager == null)
154  {
155  Debug.Log("No NetworkManager found in scene");
156  return false;
157  }
158 
159  if (SharedCollection.Instance == null)
160  {
161  Debug.Log("No SharedCollection found in scene");
162  return false;
163  }
164 
165 #if UNITY_WSA
166  objectToAnchor = SharedCollection.Instance.gameObject;
167 
168  if (UseSpatialMapping)
169  {
170  spatialMapping = SpatialMappingManager.Instance;
171  if (spatialMapping == null)
172  {
173  Debug.Log("Spatial mapping not found in scene. Better anchor locations can be found if a SpatialMappingManager is in the scene");
174  }
175  }
176 #endif
177 
178  return true;
179  }
180 
181  private void Start()
182  {
183  if (!CheckConfiguration())
184  {
185  Debug.Log("Missing required component for UNetAnchorManager");
186  Destroy(this);
187  return;
188  }
189 
190 #if UNITY_WSA && !(ENABLE_IL2CPP && NET_STANDARD_2_0)
191 #if UNITY_2017_2_OR_NEWER
192  if (HolographicSettings.IsDisplayOpaque)
193 #else
194  if (!VRDevice.isPresent)
195 #endif
196  {
197  AnchorEstablished = true;
198  }
199  else
200  {
201  networkTransmitter.DataReadyEvent += NetworkTransmitter_DataReadyEvent;
202  }
203 #else
204  AnchorEstablished = true;
205 #endif
206 
207  // If we have a debug panel, then we have debug data for the panel.
208  DebugPanel debugPanel = DebugPanel.Instance;
209  if (debugPanel != null)
210  {
211  DebugPanel.Instance.RegisterExternalLogCallback(GenerateDebugData);
212  }
213  }
214 
215  private void Update()
216  {
217 #if UNITY_WSA
218 #if UNITY_2017_2_OR_NEWER
219  if (HolographicSettings.IsDisplayOpaque)
220  {
221  return;
222  }
223 #else
224  if (!VRDevice.isPresent)
225  {
226  return;
227  }
228 #endif
229 
230  if (gotOne)
231  {
232  Debug.Log("Importing");
233  gotOne = false;
234  ImportInProgress = true;
235  WorldAnchorTransferBatch.ImportAsync(anchorData, ImportComplete);
236  }
237 
238  if (oldAnchorName != AnchorName && !createdAnchor)
239  {
240  Debug.LogFormat("New anchor name {0} => {1}", oldAnchorName, AnchorName);
241  oldAnchorName = AnchorName;
242  if (string.IsNullOrEmpty(AnchorName))
243  {
244  Debug.Log("Anchor is empty");
245  AnchorEstablished = false;
246  }
247  else if (!AttachToCachedAnchor(AnchorName))
248  {
249  Debug.Log("Requesting download of anchor data");
250  WaitForAnchor();
251  }
252  }
253 #endif
254  }
255 
260  private string GenerateDebugData()
261  {
262 #if UNITY_WSA
263  return string.Format("Anchor Name: {0}\nAnchor Size: {1}\nAnchor Established?: {2}\nImporting?: {3}\nDownloading? {4}\n",
264  AnchorName,
265  anchorData == null ? exportingAnchorBytes.Count : anchorData.Length,
266  AnchorEstablished.ToString(),
267  ImportInProgress.ToString(),
268  DownloadingAnchor.ToString());
269 #else
270  return "No Anchor data";
271 #endif
272  }
273 
277  public void CreateAnchor()
278  {
279 #if UNITY_WSA
280  exportingAnchorBytes.Clear();
281  GenericNetworkTransmitter.Instance.SetData(null);
282  objectToAnchor = SharedCollection.Instance.gameObject;
283  FindAnchorPosition();
284 #endif
285  }
286 
287 #if UNITY_WSA
288  private void FindAnchorPosition()
295  {
296  // 1. recover a stored anchor if we can
297  if (PlayerPrefs.HasKey(SavedAnchorKey) && AttachToCachedAnchor(PlayerPrefs.GetString(SavedAnchorKey)))
298  {
299  exportingAnchorName = PlayerPrefs.GetString(SavedAnchorKey);
300  Debug.Log("found " + exportingAnchorName + " again");
301  ExportAnchor();
302  }
303  // 2. just use the current object position if we don't have access to spatial mapping
304  else if (spatialMapping == null)
305  {
306  if (UseSpatialMapping)
307  {
308  Debug.Log("No spatial mapping...");
309  }
310 
311  ExportAnchorAtPosition(objectToAnchor.transform.position);
312  }
313  // 3. seek a vertex dense portion of spatial mapping
314  else
315  {
316  ReadOnlyCollection<SpatialMappingSource.SurfaceObject> surfaces = spatialMapping.GetSurfaceObjects();
317  if (surfaces == null || surfaces.Count == 0)
318  {
319  // If we aren't getting surfaces we may need to start the observer.
320  if (spatialMapping.IsObserverRunning() == false)
321  {
322  spatialMapping.StartObserver();
323  StartedObserver = true;
324  }
325 
326  // And try again after the observer has a chance to get an update.
327  Invoke("FindAnchorPosition", spatialMapping.GetComponent<SpatialMappingObserver>().TimeBetweenUpdates);
328  }
329  else
330  {
331  float startTime = Time.realtimeSinceStartup;
332  // If we have surfaces, we need to iterate through them to find a dense area
333  // of geometry, which should provide a good spot for an anchor.
334  Mesh bestMesh = null;
335  MeshFilter bestFilter = null;
336  int mostVerts = 0;
337 
338  for (int index = 0; index < surfaces.Count; index++)
339  {
340  // If the current surface doesn't have a filter or a mesh, skip to the next one
341  // This happens as a surface is being processed. We need to track both the mesh
342  // and the filter because the mesh has the verts in local space and the filter has the transform to
343  // world space.
344  MeshFilter currentFilter = surfaces[index].Filter;
345  if (currentFilter == null)
346  {
347  continue;
348  }
349 
350  Mesh currentMesh = currentFilter.sharedMesh;
351  if (currentMesh == null)
352  {
353  continue;
354  }
355 
356  // If we have a collider we can use the extents to estimate the volume.
357  MeshCollider currentCollider = surfaces[index].Collider;
358  float volume = currentCollider == null ? 1.0f : currentCollider.bounds.extents.magnitude;
359 
360  // get th verts divided by the volume if any
361  int meshVerts = (int)(currentMesh.vertexCount / volume);
362 
363  // and if this is most verts/volume we've seen, record this mesh as the current best.
364  mostVerts = Mathf.Max(meshVerts, mostVerts);
365  if (mostVerts == meshVerts)
366  {
367  bestMesh = currentMesh;
368  bestFilter = currentFilter;
369  }
370  }
371 
372  // If we have a good area to use, then use it.
373  if (bestMesh != null && mostVerts > 100)
374  {
375  // Get the average of the vertices
376  Vector3[] verts = bestMesh.vertices;
377  Vector3 avgVert = verts.Average();
378 
379  // transform the average into world space.
380  Vector3 center = bestFilter.transform.TransformPoint(avgVert);
381 
382  Debug.LogFormat("found a good mesh mostVerts = {0} processed {1} meshes in {2} ms", mostVerts, surfaces.Count, 1000 * (Time.realtimeSinceStartup - startTime));
383  // then export the anchor where we've calculated.
384  ExportAnchorAtPosition(center);
385  }
386  else
387  {
388  // If we didn't find a good mesh, try again a little later.
389  Debug.LogFormat("Failed to find a good mesh mostVerts = {0} processed {1} meshes in {2} ms", mostVerts, surfaces.Count, 1000 * (Time.realtimeSinceStartup - startTime));
390  Invoke("FindAnchorPosition", spatialMapping.GetComponent<SpatialMappingObserver>().TimeBetweenUpdates);
391  }
392  }
393  }
394  }
395 
400  private void ExportAnchorAtPosition(Vector3 worldPos)
401  {
402  // Need to remove any anchor that is on the object before we can move the object.
403  WorldAnchor worldAnchor = objectToAnchor.GetComponent<WorldAnchor>();
404  if (worldAnchor != null)
405  {
406  DestroyImmediate(worldAnchor);
407  }
408 
409  // Move the object to the specified place
410  objectToAnchor.transform.position = worldPos;
411 
412  // Attach a new anchor
413  worldAnchor = objectToAnchor.AddComponent<WorldAnchor>();
414 
415  // Name the anchor
416  exportingAnchorName = Guid.NewGuid().ToString();
417  Debug.Log("preparing " + exportingAnchorName);
418 
419  // Register for on tracking changed in case the anchor isn't already located
420  worldAnchor.OnTrackingChanged += WorldAnchor_OnTrackingChanged;
421 
422  // And call our callback in line just in case it is already located.
423  WorldAnchor_OnTrackingChanged(worldAnchor, worldAnchor.isLocated);
424  }
425 
431  private void WorldAnchor_OnTrackingChanged(WorldAnchor self, bool located)
432  {
433  if (located)
434  {
435  // If we have located the anchor we can export it.
436  Debug.Log("exporting " + exportingAnchorName);
437  // And we don't need this callback anymore
438  self.OnTrackingChanged -= WorldAnchor_OnTrackingChanged;
439 
440  ExportAnchor();
441  }
442  }
443 
447  private void ExportAnchor()
448  {
449  WorldAnchorTransferBatch watb = new WorldAnchorTransferBatch();
450  WorldAnchor worldAnchor = objectToAnchor.GetComponent<WorldAnchor>();
451  watb.AddWorldAnchor(exportingAnchorName, worldAnchor);
452  WorldAnchorTransferBatch.ExportAsync(watb, WriteBuffer, ExportComplete);
453 
454  // If we started the observer to find a good anchor position, then we need to
455  // stop the observer.
456  if (StartedObserver)
457  {
458  spatialMapping.StopObserver();
459  StartedObserver = false;
460  }
461  }
462 
466  public void WaitForAnchor()
467  {
468  DownloadingAnchor = networkTransmitter.RequestAndGetData();
469  if (!DownloadingAnchor)
470  {
471  Invoke("WaitForAnchor", 0.5f);
472  }
473  }
474 
479  private bool AttachToCachedAnchor(string cachedAnchorName)
480  {
481  if (string.IsNullOrEmpty(cachedAnchorName))
482  {
483  Debug.Log("Ignoring empty name");
484  return false;
485  }
486 
487  WorldAnchorStore anchorStore = WorldAnchorManager.Instance.AnchorStore;
488  Debug.Log("Looking for " + cachedAnchorName);
489  string[] ids = anchorStore.GetAllIds();
490  for (int index = 0; index < ids.Length; index++)
491  {
492  if (ids[index] == cachedAnchorName)
493  {
494  Debug.Log("Using what we have");
495  anchorStore.Load(ids[index], objectToAnchor);
496  AnchorEstablished = true;
497  return true;
498  }
499  }
500 
501  // Didn't find the anchor.
502  return false;
503  }
504 
509  private void NetworkTransmitter_DataReadyEvent(byte[] data)
510  {
511  Debug.Log("Anchor data arrived.");
512  anchorData = data;
513  Debug.Log(data.Length);
514  DownloadingAnchor = false;
515  gotOne = true;
516  }
517 
523  private void ImportComplete(SerializationCompletionReason status, WorldAnchorTransferBatch wat)
524  {
525  if (status == SerializationCompletionReason.Succeeded && wat.GetAllIds().Length > 0)
526  {
527  Debug.Log("Import complete");
528 
529  string first = wat.GetAllIds()[0];
530  Debug.Log("Anchor name: " + first);
531 
532  WorldAnchor existingAnchor = objectToAnchor.GetComponent<WorldAnchor>();
533  if (existingAnchor != null)
534  {
535  DestroyImmediate(existingAnchor);
536  }
537 
538  WorldAnchor anchor = wat.LockObject(first, objectToAnchor);
539  anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
540  Anchor_OnTrackingChanged(anchor, anchor.isLocated);
541 
542  ImportInProgress = false;
543  }
544  else
545  {
546  // if we failed, we can simply try again.
547  gotOne = true;
548  Debug.Log("Import fail");
549  }
550  }
551 
552  private void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
553  {
554  if (located)
555  {
556  AnchorEstablished = true;
557  WorldAnchorManager.Instance.AnchorStore.Save(AnchorName, self);
558  self.OnTrackingChanged -= Anchor_OnTrackingChanged;
559  }
560  }
561 
566  private void WriteBuffer(byte[] data)
567  {
568  exportingAnchorBytes.AddRange(data);
569  }
570 
575  private void ExportComplete(SerializationCompletionReason status)
576  {
577  if (status == SerializationCompletionReason.Succeeded && exportingAnchorBytes.Count > MinTrustworthySerializedAnchorDataSize)
578  {
579  AnchorName = exportingAnchorName;
580  anchorData = exportingAnchorBytes.ToArray();
581  GenericNetworkTransmitter.Instance.SetData(anchorData);
582  createdAnchor = true;
583  Debug.Log("Anchor ready " + exportingAnchorBytes.Count);
584  GenericNetworkTransmitter.Instance.ConfigureAsServer();
585  AnchorEstablished = true;
586  }
587  else
588  {
589  Debug.Log("Create anchor failed " + status + " " + exportingAnchorBytes.Count);
590  exportingAnchorBytes.Clear();
591  objectToAnchor = SharedCollection.Instance.gameObject;
592  DestroyImmediate(objectToAnchor.GetComponent<WorldAnchor>());
593  CreateAnchor();
594  }
595  }
596 
601  public void AnchorFoundRemotely()
602  {
603  Debug.Log("Setting saved anchor to " + AnchorName);
604  WorldAnchorManager.Instance.AnchorStore.Save(AnchorName, objectToAnchor.GetComponent<WorldAnchor>());
605  PlayerPrefs.SetString(SavedAnchorKey, AnchorName);
606  PlayerPrefs.Save();
607  }
608 
612  public void MakeNewAnchor()
613  {
614  // forget our cached anchor if we have one.
615  if (PlayerPrefs.HasKey(SavedAnchorKey))
616  {
617  PlayerPrefs.DeleteKey(SavedAnchorKey);
618  }
619 
620  // remove the world anchor from the object if it is there.
621  WorldAnchor currentAnchor = objectToAnchor.GetComponent<WorldAnchor>();
622  if (currentAnchor != null)
623  {
624  DestroyImmediate(currentAnchor);
625  }
626 
627  // reset the anchor name so that other participants see that the current anchor is no longer valid.
628  AnchorName = "";
629 
630  // and then go to create the anchor.
631  CreateAnchor();
632  }
633 #endif
634  }
635 }
bool RequestAndGetData()
Requests data from the server and handles getting the data and firing the dataReadyEvent.
This script exists as a stub to allow other scripts to find the shared world anchor transform...
Wrapper around world anchor store to streamline some of the persistence API busy work.
Script to control writing the Debug.Log output to a control.
Definition: DebugPanel.cs:12
Creates, exports, and imports anchors as required.
void CreateAnchor()
If we are supposed to create the anchor for export, this is the function to call. ...
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
For a UWP application this should allow us to send or receive data given a server IP address...