Azure空间锚/ Unity World锚失去位置

时间:2019-12-05 16:28:11

标签: unity3d hololens azure-spatial-anchors

我正在开发一个简单的统一应用程序,用于测试HoloLens上的天蓝色空间锚点。我从这个示例(https://github.com/Azure/azure-spatial-anchors-samples)开始,并对其进行了一些更改以创建多个锚点。

在一些测试中,我体验到锚定的物体突然失去了位置,并且移动了约10米或更多。

当我不了解HoloLens和混合现实时,摄像机的位置会通过某种视觉测距法或SLAM算法进行跟踪,因此正常情况下设备的姿态会随时间漂移,锚也会这样做。但是我没想到会有如此巨大的变化。 此外,我希望在设备相机再次看到锚点附近的特征时,锚点会恢复​​原位。但这并非总是如此。有时,当这些要素再次可见时,锚点会恢复​​到其原始位置,但这有时并不会改变有关错误位置的任何信息。

这是代码:

using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Input;
using System.Linq;
using System.IO;
using UnityEditor;

public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// The sphere prefab.
    /// </summary>
    public GameObject spherePrefab;

    /// <summary>
    /// Set this string to the Spatial Anchors account id provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountId = "fddac483-0420-4a71-ab14-c6fe5603bd73";

    /// <summary>
    /// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountKey = "5BY7f7xfklPW36LNaPhgo5MeQzc0lrfBJaw/ScLkkQg=";

    /// <summary>
    /// Our queue of actions that will be executed on the main thread.
    /// </summary>
    private readonly Queue<Action> dispatchQueue = new Queue<Action>();

    /// <summary>
    /// Use the recognizer to detect air taps.
    /// </summary>
    private GestureRecognizer recognizer;

    protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

    /// <summary>
    /// The CloudSpatialAnchor that we either 1) placed and are saving or 2) just located.
    /// </summary>
    protected CloudSpatialAnchor currentCloudAnchor;

    /// <summary>
    /// True if we are creating + saving an anchor
    /// </summary>
    protected bool tapExecuted = false;

    /// <summary>
    /// The IDs of the CloudSpatialAnchor that were saved. Use it to find the CloudSpatialAnchors
    /// </summary>
    protected Dictionary<string, GameObject> cloudSpatialAnchorIdsObjects = new Dictionary<string, GameObject> { };

    protected IList<string> anchorIds = new List<string>();

    /// <summary>
    /// The sphere rendered to show the position of the CloudSpatialAnchor.
    /// </summary>
    protected Material sphereMaterial;

    /// <summary>
    /// Indicate if we are ready to save an anchor. We can save an anchor when value is greater than 1.
    /// </summary>
    protected float recommendedForCreate = 0;

    private string pathName;

    // Start is called before the first frame update
    void Start()
    {
        Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);

        recognizer = new GestureRecognizer();

        recognizer.StartCapturingGestures();

        recognizer.SetRecognizableGestures(GestureSettings.Tap);

        recognizer.Tapped += HandleTap;

        InitializeSession();

        string FileName = "ids.txt";
        pathName = Path.Combine(Application.persistentDataPath, FileName);
        getIds();

        if (anchorIds.Count > 0)
        {
            CreateWatcher(anchorIds.ToArray());
        }
    }

    // Update is called once per frame
    void Update()
    {
        lock (dispatchQueue)
        {
            if (dispatchQueue.Count > 0)
            {
                dispatchQueue.Dequeue()();
            }
        }
    }

    /// <summary>
    /// Queues the specified <see cref="Action"/> on update.
    /// </summary>
    /// <param name="updateAction">The update action.</param>
    protected void QueueOnUpdate(Action updateAction)
    {
        lock (dispatchQueue)
        {
            dispatchQueue.Enqueue(updateAction);
        }
    }

    /// <summary>
    /// Cleans up objects.
    /// </summary>
    public void CleanupObjects()
    {
        if (cloudSpatialAnchorIdsObjects != null)
        {
            cloudSpatialAnchorIdsObjects = new Dictionary<string, GameObject>();
        }

        if (sphereMaterial != null)
        {
            Destroy(sphereMaterial);
            sphereMaterial = null;
        }

        //currentCloudAnchor = null;
    }   

    /// <summary>
    /// Initializes a new CloudSpatialAnchorSession.
    /// </summary>
    void InitializeSession()
    {
        Debug.Log("ASA Info: Initializing a CloudSpatialAnchorSession.");

        if (string.IsNullOrEmpty(SpatialAnchorsAccountId))
        {
            Debug.LogError("No account id set.");
            return;
        }

        if (string.IsNullOrEmpty(SpatialAnchorsAccountKey))
        {
            Debug.LogError("No account key set.");
            return;
        }

        cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

        cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
        cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();

        cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

        cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
        cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
        cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
        cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
        cloudSpatialAnchorSession.LocateAnchorsCompleted += CloudSpatialAnchorSession_LocateAnchorsCompleted;

        cloudSpatialAnchorSession.Start();

        Debug.Log("ASA Info: Session was initialized.");
    }

    void CreateWatcher(string[] cloudSpatialAnchorIds)
    {
        Debug.Log("ASA Info: We will look for placeded anchors.");

        // Create a Watcher to look for the anchor we created.
        AnchorLocateCriteria criteria = new AnchorLocateCriteria();
        criteria.Identifiers = cloudSpatialAnchorIds;
        cloudSpatialAnchorSession.CreateWatcher(criteria);

        Debug.Log("ASA Info: Watcher created. Number of active watchers: " + cloudSpatialAnchorSession.GetActiveWatchers().Count);
    }

    private void CloudSpatialAnchorSession_Error(object sender, SessionErrorEventArgs args)
    {
        Debug.LogError("ASA Error: " + args.ErrorMessage);
    }

    private void CloudSpatialAnchorSession_OnLogDebug(object sender, OnLogDebugEventArgs args)
    {
        Debug.Log("ASA Log: " + args.Message);
        System.Diagnostics.Debug.WriteLine("ASA Log: " + args.Message);
    }

    private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
    {
        Debug.Log("ASA Log: recommendedForCreate: " + args.Status.RecommendedForCreateProgress);
        recommendedForCreate = args.Status.RecommendedForCreateProgress;
    }

    private void CloudSpatialAnchorSession_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        switch (args.Status)
        {
            case LocateAnchorStatus.Located:
                Debug.Log("ASA Info: Anchor located! Identifier: " + args.Identifier);
                QueueOnUpdate(() =>
                {
                    // Create a green sphere.
                    GameObject spatialAnchorObj = GameObject.Instantiate(spherePrefab, Vector3.zero, Quaternion.identity) as GameObject;
                    spatialAnchorObj.AddComponent<WorldAnchor>();
                    sphereMaterial = spatialAnchorObj.GetComponent<MeshRenderer>().material;
                    sphereMaterial.color = Color.green;

                    // Get the WorldAnchor from the CloudSpatialAnchor and use it to position the sphere.
                    spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);

                    cloudSpatialAnchorIdsObjects.Add(args.Anchor.Identifier, spatialAnchorObj);

                    Debug.Log("Detected Pos: " + spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().transform.position.ToString("F4"));
                    Debug.Log("Detected Rot: " + spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().transform.rotation.ToString("F4"));

                    tapExecuted = false;
                });
                break;
            case LocateAnchorStatus.AlreadyTracked:
                Debug.Log("ASA Info: Anchor already tracked. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocated:
                Debug.Log("ASA Info: Anchor not located. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
                Debug.LogError("ASA Error: Anchor not located does not exist. Identifier: " + args.Identifier);
                break;
        }
    }

    private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
    {
        Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
    }

    /// <summary>
    /// Called by GestureRecognizer when a tap is detected.
    /// </summary>
    /// <param name="eventArgs">The tap.</param>    
    public void HandleTap(TappedEventArgs eventArgs)
    {
        if (tapExecuted)
        {
            return;
        }
        tapExecuted = true;

        Debug.Log("ASA Info: We will create a new anchor.");

        //// Clean up any anchors that have been placed.
        //CleanupObjects();

        // Construct a Ray using forward direction of the HoloLens.
        Ray GazeRay = new Ray(eventArgs.headPose.position, eventArgs.headPose.forward);

        // Raycast to get the hit point in the real world.
        RaycastHit hitInfo;
        Physics.Raycast(GazeRay, out hitInfo, float.MaxValue);

        this.CreateAndSaveSphere(hitInfo.point);
    }

    /// <summary>
    /// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
    /// </summary>
    /// <param name="hitPoint">The hit point.</param>
    protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
    {
        // Create a white sphere.
        GameObject spatialAnchorObj = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
        spatialAnchorObj.AddComponent<WorldAnchor>();
        sphereMaterial = spatialAnchorObj.GetComponent<MeshRenderer>().material;
        sphereMaterial.color = Color.white;
        Debug.Log("ASA Info: Created a local anchor.");

        // Create the CloudSpatialAnchor.
        currentCloudAnchor = new CloudSpatialAnchor();

        // Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
        WorldAnchor worldAnchor = spatialAnchorObj.GetComponent<WorldAnchor>();
        if (worldAnchor == null)
        {
            throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
        }

        // Save the CloudSpatialAnchor to the cloud.
        currentCloudAnchor.LocalAnchor = worldAnchor.GetNativeSpatialAnchorPtr();

        //cloudAnchor.AppProperties[@"x"] = @"frame";
        //cloudAnchor.AppProperties[@"label"] = @"my latest picture";

        Task.Run(async () =>
        {
            // Wait for enough data about the environment.
            while (recommendedForCreate < 1.0F)
            {
                await Task.Delay(330);
            }

            bool success = false;
            try
            {
                QueueOnUpdate(() =>
                {
                    // We are about to save the CloudSpatialAnchor to the Azure Spatial Anchors, turn it yellow.
                    sphereMaterial.color = Color.yellow;
                });

                await cloudSpatialAnchorSession.CreateAnchorAsync(currentCloudAnchor);
                success = currentCloudAnchor != null;

                if (success)
                {
                    // Record the identifier to locate.
                    string cloudAnchorId = currentCloudAnchor.Identifier;

                    QueueOnUpdate(() =>
                    {
                        // Turn the sphere blue.
                        sphereMaterial.color = Color.blue;
                    });

                    Debug.Log("ASA Info: Saved anchor to Azure Spatial Anchors! Identifier: " + cloudAnchorId);
                    //Debug.Log("Created " + cloudAnchorId + " at pos: " + worldAnchor.transform.position);
                    //Debug.Log("Created " + cloudAnchorId + "at rot: " + worldAnchor.transform.rotation);

                    anchorIds.Add(cloudAnchorId);
                    cloudSpatialAnchorIdsObjects.Add(cloudAnchorId, spatialAnchorObj);

                    WriteIds();
                }
                else
                {
                    sphereMaterial.color = Color.red;
                    Debug.LogError("ASA Error: Failed to save, but no exception was thrown.");
                }
            }
            catch (Exception ex)
            {
                QueueOnUpdate(() =>
                {
                    sphereMaterial.color = Color.red;
                });
                Debug.LogError("ASA Error: " + ex.Message);
            }

            // Allow the user to tap again to clear state and look for the anchor.
            tapExecuted = false;
        });
    }

    void WriteIds()
    {
        try
        {
            string fileContent = ""
                //= ReadString();
                ;

            foreach (string id in anchorIds)
            {
                fileContent += id + Environment.NewLine;
            }

            using (StreamWriter writer = new StreamWriter(new FileStream(pathName, FileMode.OpenOrCreate, FileAccess.Write)))
            {
                writer.Write(fileContent);
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }

    void getIds()
    {
        try
        {
            StreamReader reader = new StreamReader(pathName);

            string line;
            while ((line = reader.ReadLine()) != null)
            {
                anchorIds.Add(line);
            }

            reader.Close();
        }
        catch (FileNotFoundException e)
        {
            Debug.LogWarning("No AnchorId file found");
        }
    }
}

创建锚的方式有问题吗?或者这是正常行为?

1 个答案:

答案 0 :(得分:0)

不足为奇的行为是有时锚失去位置,并且可能是某些锚在恢复跟踪后重新定位,但不是全部。将脚本添加到您为锚创建的对象中可能会很有帮助,该脚本显示附加到锚的锚的跟踪状态。这是一个示例:-

using System;
using UnityEngine;
using UnityEngine.XR.WSA;

public class ShowTrackingState : MonoBehavior
{
    WorldAnchor worldAnchor = null;
    Material renderMaterial = null;
    bool isTracking = false;
    void Start()
    {
        renderMaterial = gameObject.GetComponent<Renderer>().material;
    }

    void OnDestroy()
    {
        if (renderMaterial != null)
        {
            Destroy(renderMaterial);
        }
    }

    void Update()
    {
        if (worldAnchor == null)
        {
            worldAnchor = gameObject.GetComponent<worldAnchor>();
        }

        if (worldAnchor == null)
        {
            isTracking = false;
        }
        else
        {
            isTracking = worldAnchor.isLocated;
        }

        renderMaterial.color = isTracking ? Color.red : Color.green;
    }
}

提醒您,在论坛上发布问题时忘记隐藏自己的帐户ID和密钥是一个常见错误;但这并不安全,因此您可能要从问题中的代码片段中删除该部分:-)