デコシノニッキ

ホロレンジャーの戦いの記録

Azure Spatial AnchorsのAPIを眺める

de:code2019できいてAzureSpatialAnchorsは面白いなーとは思ったのですが、中身がどうなっているのか全く分からなかったので、 こちらのブログを参考にAPIを眺めてみました

Azure Spatial Anchorsとは?

クイック スタート - Azure Spatial Anchors を使用して Unity HoloLens アプリを作成する | Microsoft Docs

Spatial Anchorと呼ばれる空間の位置を覚えておく機能の永続化と共有がAzureSpatialAnchorsです。iOSAndroid、HoloLensがそのサポート対象です。

Microsoft.Azure.SpatialAnchors

UWP用のSpatialAnchorを扱うネームスペースです。

Microsoft.Azure.SpatialAnchors Namespace | Microsoft Docs

見ての通り、C++なのでUnityから扱うには一手間いります。それがExampleのPluginに含まれる、AzureSpatialAnchorsBridgeです。
HoloLensであれば、UNITY_WSAで囲われた部分のNativeLibraryの部分がそうです。
自分でこれらをフル移植はするのはなかなか面倒なので、ここのPluginだけぶっこ抜くのがいいと思います。

C++ちょっとできる人はこちらを読むほうが早いかもしれません。
C++/WinRT で Azure Spatial Anchors を使用してアンカーを作成して配置する方法 | Microsoft Docs

CloundSpatialAnchor

SpatialAnchorsへ永続化されるSpatialAnchorです。
Identifierによって一意に識別が可能であり、生成時に取得できるIdentifierをDBやローカルに保持、またこの識別子を用いてSpatialAnchorを取得することができます。LocalAnchorはAnchorとしての実際のデータになります。
また、アンカーの寿命をExpirationで指定したり、PropertiesからAnchor自体に情報を付加することもできます。

        // 生成したCubeにARAnchorを打つ
        GameObject.CreatePrimitive(PrimitiveType.Cube).AddARAnchor();

        // CloudSpatialAnchorを作成する
        var cloudAnchor = new CloudSpatialAnchor();
        cloudAnchor.LocalAnchor = cube.GetNativeAnchorPointer();
        cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(1);

CloudSpatialAnchorSession

CloudSpatialAnchorSession.CreateAnchorAsync(CloudSpatialAnchor) Method (Microsoft.Azure.SpatialAnchors) | Microsoft Docs

SpatialAnchorの永続化や接続などはCloudSpatialAnchorSessionが担います。
ConfigurationでSpatialAnchorsのアカウントIDとアカウントKeyを設定し、各種デリゲートの登録(後述)を行います。

        // Sessionを生成する
        var session = new CloudSpatialAnchorSession();
        //アカウントを設定する
        session.Configuration.AccountId  = "AccountID";
        session.Configuration.AccountKey = "AccountKey";

        //デリゲートを登録する
        //AnchorLocatedは、アンカーを見つかったこと、あるいは見つけられていないことを通知します
        //LocateAnchorsCompletedは、検索操作が完了したことを通知します
        session.AnchorLocated  += SessionOnAnchorLocated;
        session.LocateAnchorsCompleted += SessionOnLocateAnchorsCompleted;

        //セッションを開始する
        session.Start();

CloudAnchorを生成する場合、アンカーを作成するのに環境データを十分に取得している必要があります。

        // CloudAnchorが生成可能になる十分な状態まで待機する
        // RecommendedForCreateProgress: 推奨閾値の範囲で[0,1]を返す
        while (true)
        {
            var status = await session.GetSessionStatusAsync();
            if (status.RecommendedForCreateProgress >= 1.0) { break; }
            else {Debug.Log($"Progress: {status.RecommendedForCreateProgress * 100f}%");}
            await Task.Delay(250);
        }

        // CreateForProgress: 操作に十分なレベルの範囲で[0,1]を返す
        if((await session.GetSessionStatusAsync()).ReadyForCreateProgress < 1) return;

AnchorをAzure Spatial Anchorsへ永続化し、Anchorの識別子も別途保存します。
Anchorの保存とAnchorの識別子の保存は別で考える必要があるのです(これがちょっとややこしい)

チュートリアルではCosmosDBを使っていますが、SQLなりローカルにJSONで保存するなり何でもいいと思います。

        // SpatialAnchorを永続化する
        await session.CreateAnchorAsync(cloudAnchor);

        //識別子をDBやローカルに永続化する(てきとーなメソッド)
        SaveAnchorIdentifier(cloudAnchor.Identifier);

続いて、Anchorが見つかった時の挙動です。

    private void SessionOnAnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        UnityEngine.WSA.Application.InvokeOnAppThread(() =>
        {
            if (args.Status == LocateAnchorStatus.Located)
            {
                var cube = CreateCube();
                var anchor = cube.AddComponent<WorldAnchor>();
                // Anchorからデータを取り出し、WorldAnchorへセットする
                anchor.SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);
            }
        }, false);
    }

検出した後はWatcherを止めます。Watcherについては次で説明します。

    private void SessionOnLocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
    {
        args.Watcher.Stop();
    }

AnchorWatcher&Criteria

先ほどは生成までのフローを行いましたが、今度は検出するところをやります。

Class AnchorLocateCriteria | Microsoft Docs Class CloudSpatialAnchorWatcher | Microsoft Docs

AnchorWatcherはAnchorCritiriaを使ってアンカーの検出を行います。Criteriaというのはふるいのことです。
Criteriaには検出対象のIdentifierを指定します。これは先ほどローカルやサーバへ保存したIdentifierを指定します。

        // IDの一覧をとってくる(てきとーなメソッド)
        var identifies = GetIdentifies();

        // 検出条件を生成する
        var creteria = new AnchorLocateCriteria()
        {
            Identifiers = identifies,
            BypassCache = true,
            RequestedCategories = AnchorDataCategory.Spatial,
            Strategy = LocateStrategy.AnyStrategy
        };

また、このCriteriaにはあるSpatialAnchor近傍のAnchorだけを探索するNearAnchorCriteriaも指定することもできます。

        var nearCreteria = new NearAnchorCriteria();
        //指定のSpatialAnchor付近のAnchorを探索する
        nearCreteria.SourceAnchor = targetAnchor;
        //範囲を指定する
        nearCreteria.DistanceInMeters = 100;
        //最大アンカー数
        nearCreteria.MaxResultCount = 10;

あとはWatcherに渡すだけです。これで探索が始まり、見つかり次第先のLocatedイベントが呼ばれるようです。

session.CreateWatcher(creteria);

まとめ

Sessionを作って、Anchorを登録する。Watcherを作って探索を行い、Locatedから結果を受け取る。以上が基本的なSpatialAnchorを扱う上での基本要素になります。
次は何か作るところまでやりたい。

[デコシノニッキ]は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。」