In situations where you cannot use Azure Spatial Anchors, local anchor transfers enable one HoloLens device to export an anchor to be imported by a second HoloLens device.
[!NOTE] Local anchor transfers provide less robust anchor recall than Azure Spatial Anchors, and iOS and Android devices are not supported by this approach.
In order for an app to transfer spatial anchors, the SpatialPerception capability must be enabled.
How to enable the SpatialPerception capability:
[!NOTE] If you have already exported your Unity project to a Visual Studio solution, you will need to either export to a new folder or manually set this capability in the AppxManifest in Visual Studio.
Namespace: UnityEngine.XR.WSA.Sharing
Type: WorldAnchorTransferBatch
To transfer a WorldAnchor, one must establish the anchor to be transferred. The user of one HoloLens scans their environment and either manually or programmatically chooses a point in space to be the Anchor for the shared experience. The data that represents this point can then be serialized and transmitted to the other devices that are sharing in the experience. Each device then de-serializes the anchor data and attempts to locate that point in space. In order for Anchor Transfer to work, each device must have scanned in enough of the environment such that the point represented by the anchor can be identified.
The sample code on this page has a few fields that will need to be initialized:
public GameObject rootGameObject;
private UnityEngine.XR.WSA.WorldAnchor gameRootAnchor;
void Start ()
{
gameRootAnchor = rootGameObject.GetComponent<UnityEngine.XR.WSA.WorldAnchor>();
if (gameRootAnchor == null)
{
gameRootAnchor = rootGameObject.AddComponent<UnityEngine.XR.WSA.WorldAnchor>();
}
}
To export, we just need a WorldAnchor and to know what we will call it such that it makes sense for the receiving app. One client in the shared experience will perform these steps to export the shared anchor:
We create a WorldAnchorTransferBatch to encapsulate what we will be transferring and then export that into bytes:
private void ExportGameRootAnchor()
{
WorldAnchorTransferBatch transferBatch = new WorldAnchorTransferBatch();
transferBatch.AddWorldAnchor("gameRoot", this.gameRootAnchor);
WorldAnchorTransferBatch.ExportAsync(transferBatch, OnExportDataAvailable, OnExportComplete);
}
As data becomes available, send the bytes to the client or buffer as segments of data is available and send through whatever means desired:
private void OnExportDataAvailable(byte[] data)
{
TransferDataToClient(data);
}
Once the export is complete, if we have been transferring data and serialization failed, tell the client to discard the data. If the serialization succeeded, tell the client that all data has been transferred and importing can start:
private void OnExportComplete(SerializationCompletionReason completionReason)
{
if (completionReason != SerializationCompletionReason.Succeeded)
{
SendExportFailedToClient();
}
else
{
SendExportSucceededToClient();
}
}
After receiving all of the bytes from the sender, we can import the data back into a WorldAnchorTransferBatch and lock our root game object into the same physical location. Note: import will sometimes transiently fail and needs to be retried:
// This byte array should have been updated over the network from TransferDataToClient
private byte[] importedData;
private int retryCount = 3;
private void ImportRootGameObject()
{
WorldAnchorTransferBatch.ImportAsync(importedData, OnImportComplete);
}
private void OnImportComplete(SerializationCompletionReason completionReason, WorldAnchorTransferBatch deserializedTransferBatch)
{
if (completionReason != SerializationCompletionReason.Succeeded)
{
Debug.Log("Failed to import: " + completionReason.ToString());
if (retryCount > 0)
{
retryCount--;
WorldAnchorTransferBatch.ImportAsync(importedData, OnImportComplete);
}
return;
}
this.gameRootAnchor = deserializedTransferBatch.LockObject("gameRoot", this.rootGameObject);
}
After a GameObject is locked via the LockObject call, it will have a WorldAnchor which will keep it in the same physical position in the world, but it may be at a different location in the Unity coordinate space than other users.