Skip to content

Latest commit

 

History

History
337 lines (257 loc) · 13 KB

create-locate-anchors-unity.md

File metadata and controls

337 lines (257 loc) · 13 KB
title description author manager services ms.author ms.date ms.topic ms.service ms.custom
Create & locate anchors in Unity
In-depth explanation of how to create and locate anchors using Azure Spatial Anchors in Unity.
pamistel
MehranAzimi-msft
azure-spatial-anchors
pamistel
11/20/2020
tutorial
azure-spatial-anchors
devx-track-csharp

How to create and locate anchors using Azure Spatial Anchors in Unity

[!div class="op_single_selector"]

Azure Spatial Anchors allow you to share anchors in the world between different devices. It supports several different development environments. In this article, we'll dive into how to use the Azure Spatial Anchors SDK, in Unity, to:

  • Correctly set up and manage an Azure Spatial Anchors session.
  • Create and set properties on local anchors.
  • Upload them to the cloud.
  • Locate and delete cloud spatial anchors.

Prerequisites

To complete this guide, make sure you have:

[!INCLUDE Start]

Learn more about the CloudSpatialAnchorSession class.

    CloudSpatialAnchorSession cloudSession;
    // In your view handler
    this.cloudSession = new CloudSpatialAnchorSession();

[!INCLUDE Account Keys]

Learn more about the SessionConfiguration class.

    this.cloudSession.Configuration.AccountKey = @"MyAccountKey";

[!INCLUDE Access Tokens]

    this.cloudSession.Configuration.AccessToken = @"MyAccessToken";

[!INCLUDE Access Tokens Event]

Learn more about the TokenRequiredDelegate delegate.

    this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
    {
        args.AccessToken = @"MyAccessToken";
    };

[!INCLUDE Asynchronous Tokens]

    this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
    {
        var deferral = args.GetDeferral();
        string myToken = await MyGetTokenAsync();
        if (myToken != null) args.AccessToken = myToken;
        deferral.Complete();
    };

[!INCLUDE Azure AD Tokens]

    this.cloudSession.Configuration.AuthenticationToken = @"MyAuthenticationToken";

[!INCLUDE Azure AD Tokens Event]

    this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
    {
        args.AuthenticationToken = @"MyAuthenticationToken";
    };

[!INCLUDE Asynchronous Tokens]

    this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
    {
        var deferral = args.GetDeferral();
        string myToken = await MyGetTokenAsync();
        if (myToken != null) args.AuthenticationToken = myToken;
        deferral.Complete();
    };

[!INCLUDE Setup]

Learn more about the Start method.

#if UNITY_ANDROID || UNITY_IOS
    this.cloudSession.Session = aRSession.subsystem.nativePtr.GetPlatformPointer();
#elif UNITY_WSA || WINDOWS_UWP
    // No need to set a native session pointer for HoloLens.
#else
    throw new NotSupportedException("The platform is not supported.");
#endif

    this.cloudSession.Start();

[!INCLUDE Frames]

Learn more about the ProcessFrame method.

#if UNITY_ANDROID || UNITY_IOS
    XRCameraFrame xRCameraFrame;
    if (aRCameraManager.subsystem.TryGetLatestFrame(cameraParams, out xRCameraFrame))
    {
        long latestFrameTimeStamp = xRCameraFrame.timestampNs;

        bool newFrameToProcess = latestFrameTimeStamp > lastFrameProcessedTimeStamp;

        if (newFrameToProcess)
        {
            session.ProcessFrame(xRCameraFrame.nativePtr.GetPlatformPointer());
            lastFrameProcessedTimeStamp = latestFrameTimeStamp;
        }
    }
#endif

[!INCLUDE Feedback]

Learn more about the SessionUpdatedDelegate delegate.

    this.cloudSession.SessionUpdated += (object sender, SessionUpdatedEventArgs args) =>
    {
        var status = args.Status;
        if (status.UserFeedback == SessionUserFeedback.None) return;
        this.feedback = $"Feedback: {Enum.GetName(typeof(SessionUserFeedback), status.UserFeedback)} -" +
            $" Recommend Create={status.RecommendedForCreateProgress: 0.#%}";
    };

[!INCLUDE Creating]

Learn more about the CloudSpatialAnchor class.

    // Create a local anchor, perhaps by hit-testing and spawning an object within the scene
    Vector3 hitPosition = new Vector3();
#if UNITY_ANDROID || UNITY_IOS
    Vector2 screenCenter = new Vector2(0.5f, 0.5f);
    List<ARRaycastHit> aRRaycastHits = new List<ARRaycastHit>();
    if(arRaycastManager.Raycast(screenCenter, aRRaycastHits) && aRRaycastHits.Count > 0)
    {
        ARRaycastHit hit = aRRaycastHits[0];
        hitPosition = hit.pose.position;
    }
#elif WINDOWS_UWP || UNITY_WSA
    RaycastHit hit;
    if (this.TryGazeHitTest(out hit))
    {
        hitPosition = hit.point;
    }
#endif

    Quaternion rotation = Quaternion.AngleAxis(0, Vector3.up);
    this.localAnchor = GameObject.Instantiate(/* some prefab */, hitPosition, rotation);
    this.localAnchor.AddComponent<CloudNativeAnchor>();

    // If the user is placing some application content in their environment,
    // you might show content at this anchor for a while, then save when
    // the user confirms placement.
    CloudNativeAnchor cloudNativeAnchor = this.localAnchor.GetComponent<CloudNativeAnchor>();
    if (cloudNativeAnchor.CloudAnchor == null) { await cloudNativeAnchor.NativeToCloud(); }  
    CloudSpatialAnchor cloudAnchor = cloudNativeAnchor.CloudAnchor;
    await this.cloudSession.CreateAnchorAsync(cloudAnchor);
    this.feedback = $"Created a cloud anchor with ID={cloudAnchor.Identifier}");

[!INCLUDE Session Status]

Learn more about the GetSessionStatusAsync method.

    SessionStatus value = await this.cloudSession.GetSessionStatusAsync();
    if (value.RecommendedForCreateProgress < 1.0f) return;
    // Issue the creation request ...

[!INCLUDE Setting Properties]

Learn more about the AppProperties property.

    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor() { LocalAnchor = localAnchor };
    cloudAnchor.AppProperties[@"model-type"] = @"frame";
    cloudAnchor.AppProperties[@"label"] = @"my latest picture";
    await this.cloudSession.CreateAnchorAsync(cloudAnchor);

[!INCLUDE Update Anchor Properties]

Learn more about the UpdateAnchorPropertiesAsync method.

    CloudSpatialAnchor anchor = /* locate your anchor */;
    anchor.AppProperties[@"last-user-access"] = @"just now";
    await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);

[!INCLUDE Getting Properties]

Learn more about the GetAnchorPropertiesAsync method.

    var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
    if (anchor != null)
    {
        anchor.AppProperties[@"last-user-access"] = @"just now";
        await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);
    }

[!INCLUDE Expiration]

Learn more about the Expiration property.

    cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(7);

[!INCLUDE Locate]

Learn more about the CreateWatcher method.

    AnchorLocateCriteria criteria = new AnchorLocateCriteria();
    criteria.Identifiers = new string[] { @"id1", @"id2", @"id3" };
    this.cloudSession.CreateWatcher(criteria);

[!INCLUDE Locate Events]

Learn more about the AnchorLocatedDelegate delegate.

    this.cloudSession.AnchorLocated += (object sender, AnchorLocatedEventArgs args) =>
    {
        switch (args.Status)
        {
            case LocateAnchorStatus.Located:
                CloudSpatialAnchor foundAnchor = args.Anchor;
                // Go add your anchor to the scene...
                break;
            case LocateAnchorStatus.AlreadyTracked:
                // This anchor has already been reported and is being tracked
                break;
            case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
                // The anchor was deleted or never existed in the first place
                // Drop it, or show UI to ask user to anchor the content anew
                break;
            case LocateAnchorStatus.NotLocated:
                // The anchor hasn't been found given the location data
                // The user might in the wrong location, or maybe more data will help
                // Show UI to tell user to keep looking around
                break;
        }
    }

[!INCLUDE Deleting]

Learn more about the DeleteAnchorAsync method.

Delete anchor after locating (recommended)

    await this.cloudSession.DeleteAnchorAsync(cloudAnchor);
    // Perform any processing you may want when delete finishes

Delete anchor without locating

If you are unable to locate an anchor but would still like to delete it you can use the GetAnchorPropertiesAsync API which takes an anchorId as input to get the CloudSpatialAnchor object. You can then pass this object into DeleteAnchorAsync to delete it.

var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
await this.cloudSession.DeleteAnchorAsync(anchor);

[!INCLUDE Stopping]

Learn more about the Stop method.

    this.cloudSession.Stop();

[!INCLUDE Resetting]

Learn more about the Reset method.

    this.cloudSession.Reset();

[!INCLUDE Cleanup]

Learn more about the Dispose method.

    this.cloudSession.Dispose();

[!INCLUDE Next Steps]