Azure Spatial anchorsを使ったマルチプラットフォーム位置共有方法でつまずいた点

# はじめに
この記事は、自分がHoloLens2とAndroidとでマルチプラットフォーム化する際に困った部分を記載している記事になります。

※バージョンが更新された場合修正されているかもしれないので気を付けてください。


# 前提条件
公式がだしているAzure Spatial Anchorsのチュートリアルドキュメント

https://docs.microsoft.com/ja-jp/learn/modules/azure-spatial-anchors-tutorials/

イワケンさんが出されている記事を参考にし

https://zenn.dev/iwaken71/articles/hololens-2022-2#%E3%82%B7%E3%83%BC%E3%83%B3%E3%81%AE%E6%BA%96%E5%82%99-(%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB)

これらを一通り実践してから自分のつまづいたところを確認していただけると
理解しやすくなると思います。
<br>
また記事後半は、`Azure Spacial anchors`と`photon`を使った
多人数で体験させるためのコンテンツをつくる際のつまづいた点もご紹介いたします。


開発する上で参考になるドキュメントですのでこれも一通り試していただけますと幸いです。

https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/tutorials/mr-learning-sharing-01

**<font color="red">※HoLoLens2二台とAndroidを使ったものとなります。
iOSはMACが自分の環境にないので検証できていないです。</font>**

# 開発環境


| 機材やソフト | バージョン |    
|:-:|:-:|
|HoLoLens2   |  20348.1450 |
|  Unity |  2020.3.24f1 |
|  Windows10 |   |
|  Visual Studio 2019 |   |
|  Mixed Reality Feature Tool | v1.0.2111.0 Preview  |
|  Xperia XZ1 | Android 9  |


#### Mixed Reality Feature Tool

| Mixed Reality Toolkit| バージョン |
|:-:|:-:|
| Mixed Reality Toolkit Foundation  | 2.7.3  |
| Mixed Reality Toolkit Examples  | 2.7.3  |
|  Mixed Reality Toolkit Standard Assets |  2.7.3 |
| Mixed Reality Toolkit Test Utilities  |  2.7.3 |
| Mixed Reality Toolkit Tools  |  2.7.3 |
| Mixed Reality Toolkit Extensions  |  2.7.3 |

| Azure Mixed Reality Services | バージョン |
|:-:|:-:|
| Azure Spatial Anchors SDK Core  |  2.12.0 |
| Azure Spatial Anchors SDK Windows  |  2.12.0 |
| Azure Spatial Anchors SDK iOS  | 2.12.0  |
| Azure Spatial Anchors SDK Android |  2.12.0 |

| Platform Support | バージョン |
|:-:|:-:|
| Mixed Reality OpenXR Plugin |  1.4.0 |

#### Unity Plugin ＆ Asset

| Unity Plugin | バージョン |
|:-:|:-:|
| ARCore XR Plugin  | 4.1.9  |
| ARKit XR Plugin  | 4.1.9  |
|  Windows XR Plugin | 4.6.2 |
| ARFoundation  |  4.1.7 |

# 導入してつまずいた点
1. AndroidとiOSでは正常にAnchorへの移動が機能するのになぜか
`HoloLens2`では機能しなかった。

2. 先の問題を修正してマルチユーザー機能のチュートリアル通りに行ったが
自分の開発環境ではなぜかホストのアンカー位置へ移動してくれなかった。


# 1. HoloLens2でなぜAnchorへの移動が機能しなかったか
マイクロソフト公式が出されている

このリポジトリのReleaseで配布しているAzure Spatial Anchors v2.7.2

https://github.com/microsoft/MixedRealityLearning/releases

これに入っている`AnchorModuleScript.cs`の`CloudManager_AnchorLocated`
に原因がありました。

```AnchorModuleScript.cs
private void CloudManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        QueueOnUpdate(new Action(() => Debug.Log($"Anchor recognized as a possible Azure anchor")));

        if (args.Status == LocateAnchorStatus.Located || args.Status == LocateAnchorStatus.AlreadyTracked)
        {
            currentCloudAnchor = args.Anchor;

            QueueOnUpdate(() =>
            {
                Debug.Log($"Azure anchor located successfully");

                // Notify AnchorFeedbackScript
                OnASAAnchorLocated?.Invoke();

#if WINDOWS_UWP || UNITY_WSA
                // HoloLens: The position will be set based on the unityARUserAnchor thatwas located.
                // Create a local anchor at the location of the object in question
                gameObject.CreateNativeAnchor();

                // Notify AnchorFeedbackScript
                OnCreateLocalAnchor?.Invoke();

                // On HoloLens, if we do not have a cloudAnchor already, we will have already positioned the
                // object based on the passed in worldPos/worldRot and attached a new world anchor,
                // so we are ready to commit the anchor to the cloud if requested.
                // If we do have a cloudAnchor, we will use it's pointer to setup the world anchor,
                // which will position the object automatically.
                if (currentCloudAnchor != null)
                {
                    Debug.Log("Local anchor position successfully set to Azure anchor position");   

                    //gameObject.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(currentCloudAnchor.LocalAnchor);                 
                }

#elif UNITY_ANDROID || UNITY_IOS
                Pose anchorPose = Pose.identity;
                anchorPose = currentCloudAnchor.GetPose();

                Debug.Log($"Setting object to anchor pose with position '{anchorPose.position}' and rotation '{anchorPose.rotation}'");
                transform.position = anchorPose.position;
                transform.rotation = anchorPose.rotation;

                // Create a native anchor at the location of the object in question
                gameObject.CreateNativeAnchor();

                // Notify AnchorFeedbackScript
                OnCreateLocalAnchor?.Invoke();

#endif
            });
        }
        else
        {
            QueueOnUpdate(new Action(() => Debug.Log($"Attempt to locate Anchor with ID '{args.Identifier}' failed, locate anchor status was not 'Located' but '{args.Status}'")));
        }
    }
```

**通常のAnchorModuleScriptはこうなっているが**
<br>
#### <font color="blue">実際はHoloLensの対象コードでアンカーへ移動する処理がごっそりなくなってる!?</font>
<br>

**プラットフォーム依存コンパイルの
<font color="red">UNITY_ANDROID || UNITY_IOS</font> のプラットフォーム達に入っている内容が
<font color="red">WINDOWS_UWP || UNITY_WSA</font> に一部コードが入っていないのが原因**
**下記のコードが抜けている部分です。**

```AnchorModuleScript.cs
                    Pose anchorPose = Pose.identity;
                    anchorPose = currentCloudAnchor.GetPose();

                    Debug.Log($"Setting object to anchor pose with position '{anchorPose.position}' and rotation '{anchorPose.rotation}'");
                    transform.position = anchorPose.position;
                    transform.rotation = anchorPose.rotation;

                    //removeAnchor = "Inactive";
                    //Create a native anchor at the location of the object in question
                    gameObject.CreateNativeAnchor();

                    //Notify AnchorFeedbackScript
                    OnCreateLocalAnchor?.Invoke();
```
<br>
下記のAnchorModuleScriptが今のをふまえて修正したコード

```AnchorModuleScript.cs
private void CloudManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        QueueOnUpdate(new Action(() => Debug.Log($"Anchor recognized as a possible Azure anchor")));

        if (args.Status == LocateAnchorStatus.Located || args.Status == LocateAnchorStatus.AlreadyTracked)
        {
            currentCloudAnchor = args.Anchor;

            QueueOnUpdate(() =>
            {
                Debug.Log($"Azure anchor located successfully");

                // Notify AnchorFeedbackScript
                OnASAAnchorLocated?.Invoke();

#if WINDOWS_UWP || UNITY_WSA
                // HoloLens: The position will be set based on the unityARUserAnchor thatwas located.
                // Create a local anchor at the location of the object in question
                gameObject.CreateNativeAnchor();

                // Notify AnchorFeedbackScript
                OnCreateLocalAnchor?.Invoke();

                // On HoloLens, if we do not have a cloudAnchor already, we will have already positioned the
                // object based on the passed in worldPos/worldRot and attached a new world anchor,
                // so we are ready to commit the anchor to the cloud if requested.
                // If we do have a cloudAnchor, we will use it's pointer to setup the world anchor,
                // which will position the object automatically.
                if (currentCloudAnchor != null)
                {
                    Debug.Log("Local anchor position successfully set to Azure anchor position");

                    //gameObject.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(currentCloudAnchor.LocalAnchor);

/////////////  ここからOnCreateLocalAnchor?.Invoke();までの文がごっそりなくなっている!!!!  /////////////

                    Pose anchorPose = Pose.identity;
                    anchorPose = currentCloudAnchor.GetPose();

                    Debug.Log($"Setting object to anchor pose with position '{anchorPose.position}' and rotation '{anchorPose.rotation}'");
                    transform.position = anchorPose.position;
                    transform.rotation = anchorPose.rotation;

                    //removeAnchor = "Inactive";
                    //Create a native anchor at the location of the object in question
                    gameObject.CreateNativeAnchor();

                    //Notify AnchorFeedbackScript
                    OnCreateLocalAnchor?.Invoke();

/////////////////////////////////////////////////////////////////////////////////////////////////////

                }

#elif UNITY_ANDROID || UNITY_IOS
                Pose anchorPose = Pose.identity;
                anchorPose = currentCloudAnchor.GetPose();

                Debug.Log($"Setting object to anchor pose with position '{anchorPose.position}' and rotation '{anchorPose.rotation}'");
                transform.position = anchorPose.position;
                transform.rotation = anchorPose.rotation;

                // Create a native anchor at the location of the object in question
                gameObject.CreateNativeAnchor();

                // Notify AnchorFeedbackScript
                OnCreateLocalAnchor?.Invoke();

#endif
            });
        }
        else
        {
            QueueOnUpdate(new Action(() => Debug.Log($"Attempt to locate Anchor with ID '{args.Identifier}' failed, locate anchor status was not 'Located' but '{args.Status}'")));
        }
    }
```

これで一度HoloLensにデプロイすると
アンカーの場所に移動してることが確認できます。

この問題についてissueで触れられていました。

https://github.com/Azure/azure-spatial-anchors-samples/issues/307

# 2.マルチユーザー機能でなぜホストのアンカー位置に移動してくれないか
自分の開発した環境ではなぜかアンカーの位置共有が行われませんでした。
先ほどの[公式のサンプル](https://github.com/microsoft/MixedRealityLearning/releases)で`Multi-User Capabilities v2.7.2`
に専用Unityパッケージがあるのでこれをimportしてください。


詳しい実装方法は、[マルチユーザー機能のチュートリアルの概要](https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/tutorials/mr-learning-sharing-01)のドキュメントを確認してください。



自分は、その中でもHoloLens2同士とAndroidとでアンカーの共有ができなかった問題の修正方法について説明します。

1.の問題を解決した後に、上記のマルチユーザー機能のチュートリアルを一通り終わらせてください。
ドキュメントでは`TableAnchor`と言うオブジェクトを`Hierarchy`に追加したと思いますがその際に

* **AR Session**
* **AR Session Origin**
* **AR Anchor Maneger**

この三つのコンポーネントを`active`にしてあると思います。
![画像1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627129/784acd21-451a-6700-d745-246db34a9866.png)


このコンポーネントたちを`非active`にしてください。

そして`Mixed Reality Toolkit`に下記を追加してください。

* **AR Anchor Maneger**
* **AR Session Origin**
* **DisableDiagnosticsSystem** → AndroidやiOSを使う場合だけ必要。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627129/d2c44da8-5b23-e22d-d1a4-1699be50a64d.png)

これでそれぞれのデバイスにデプロイすれば、
自分が作ったアンカーに他デバイスが、位置共有できていることを確認できます。

# 終わりに

**これで誰でもHoloLens2同士や他デバイスと位置ずれを気にせずにコンテンツ作れます！！！！**


# 参考になったサイト
今回、公式のドキュメントでうまくいかなかった方は下記の方々も同様に
Azure Spatial anchorsの使い方を記事にされているので
つまってしまった場合は彼らの記事を参考にすると良いと思います！！！
<br>

**夜風のMixed Reality**　ホロ元さん

https://redhologerbera.hatenablog.com/entry/2022/01/16/182834

**MRが楽しい**　ホロモンさん

https://bluebirdofoz.hatenablog.com/entry/2020/10/13/231556

**Synamon’s Engineer blog**　Synamonさん

https://synamon.hatenablog.com/entry/2022/02/14/1900