STL ファイルを動的に読み込んで、Oculus Quest で操作する

STL ファイルを動的に読み込んで、Qculus Quest で回転や移動をして眺められるようにします。
STL ファイルは [pb_Stl](https://github.com/karl-/pb_Stl) を使用させていただき、Qculus Quest で回転や移動は [MRTK (Mixed Reality Toolkit)](https://docs.microsoft.com/ja-jp/windows/mixed-reality/mrtk-unity/) を使用します。
STL ファイルはパソコンと接続した時に置けるようにするため、/sdcard/Android/data/[パッケージ名]/files/ から、STL ファイルを読み込みます。

## 環境
- Unity 2019.4.22f1
- pb_Stl 1.0.0
- Mixed Reality Toolkit Foundation 2.5.3
- Oculus Integration 25.0
- Oculus XR Plugin 1.8.1

## Oculus Quest MRTK のセットアップ

1. [プロジェクトの初期化と最初のアプリケーションの配置 - Mixed Reality | Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/tutorials/mr-learning-base-02) の [シーンを作成して構成する] までを手順通りに行い、Unity プロジェクトを作成します。
    - [ビルド プラットフォームを切り替える] や [2.追加のプロジェクト設定を構成する] は必要ありませんが、記載を簡略化するために参照としています。
2. [OculusQuestMRTK | Microsoft Docs](https://docs.microsoft.com/en-us/windows/mixed-reality/mrtk-unity/features/cross-platform/oculus-quest-mrtk) の [Using the Oculus XR SDK Data Provider] までの手順通りに行い、Qculus Quest で MRTK を使用できるようにします。
    - [Using the Oculus XR SDK Data Provider] の 2 では、[Device Manager Profile] に [OculusXRSDKDeviceManagerProfile] を設定します。
        ![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460904/2f092b0e-9361-c550-a285-5ab2c6de5403.png)
    - [Ovr Camera Rig Prefab] と [Local Avatar Prefab] が [None (Game Object)] になっている場合は、[Mixed Reality Toolkit] > [Utilities] > [Oculus] > [Integrate Oculus Integration UnityModules] を再度実行します。

## pb_Stl のセットアップ

1. [pb_Stl](https://github.com/karl-/pb_Stl) の Install の手順通りに pb_Stl をインストールします。

## STL ファイル読み込みスクリプトの追加

1. [Hierarchy] ウィンドウで [Create Empty] から空のオブジェクトを作成します。
2. 名前を「RuntimeStlLoader」にします。
3. RuntimeStlLoader オブジェクトを選択した状態で、Inspector ウィンドウの [Add Component] から [C# Script] を追加します。
4. ファイル名を「RuntimeStlLoader」とします。
5. RuntimeStlLoader.cs のソースコードを下記のようにします。

    ```CSharp:RuntimeStlLoader.cs
    using Microsoft.MixedReality.Toolkit.UI;
    using Microsoft.MixedReality.Toolkit.UI.BoundsControl;
    using Microsoft.MixedReality.Toolkit.Utilities;
    using Parabox.Stl;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    
    public class RuntimeStlLoader : MonoBehaviour
    {
        [SerializeField]
        private Material material;
    
        private readonly List<GameObject> spawnedObjects = new List<GameObject>();
    
        public void Load()
        {
            string stlPath = Directory.GetFiles(Application.persistentDataPath, "*.stl").FirstOrDefault();
            if (string.IsNullOrEmpty(stlPath))
            {
                Debug.LogError("Not found stl file.");
                return;
            }
    
            Mesh[] meshes = Importer.Import(stlPath);
            if (meshes.Length < 1)
            {
                return;
            }
    
            GameObject parentGameObject = new GameObject();
            parentGameObject.transform.position = new Vector3(0, -0.6f, 2);
    
            foreach (Mesh mesh in meshes)
            {
                GameObject gameObject = new GameObject();
    
                MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
                meshFilter.mesh = mesh;
    
                MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
                meshRenderer.material = material;
    
                gameObject.transform.parent = parentGameObject.gameObject.transform;
            }
    
            parentGameObject.AddComponent<BoundsControl>();
            ObjectManipulator objectManipulator = parentGameObject.AddComponent<ObjectManipulator>();
            objectManipulator.TwoHandedManipulationType = TransformFlags.Move | TransformFlags.Rotate;
    
            spawnedObjects.Add(parentGameObject);
        }
    
        public void Clear()
        {
            foreach (GameObject gameObject in spawnedObjects)
            {
                Destroy(gameObject);
            }
        }
    }
    ```
6. [Assets] ウィンドウで stl のオブジェクトに設定する任意の Material を作成します。
7. [Hierarchy] ウィンドウで RuntimeStlLoader オブジェクトを選択した状態で、Inspector ウィンドウの [RuntimeStlLoader] の [Material] に任意の Material を設定します。

## スクリプトの呼び出しメニューを追加

1. [Project] ウィンドウで、 [Packages] > [Mixed Reality Toolkit Foundation] > [SDK] > [Features] > [UX] > [Prefabs] > [Menus] にある [NearMenu3x1] プレハブを [Hierarchy] ウィンドウにドラッグします。 
2. オブジェクトの名前を「Menu」に変更します。
3. Menu オブジェクトの Position を X = 0、Y = 0.4、Z = 0 にします。
4. SolverHandler コンポーネントの [Tracked Target Type] を [Head] に設定します。
5. RadialView コンポーネントの横にあるチェックボックスをオンにして、既定で有効になるようにします。
    ![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460904/69003c45-120a-1bcb-ed03-606918959ca7.png)

6. Menu オブジェクトを展開し、[ButtonCollection] > [ButtonOne] 選択し、名前を「LoadStL」にします。
7. LoadStl オブジェクトを選択し、[Inspector] ウィンドウの [Button Config Helper] を下記のように変更します。
    - [Main Label Text] を「Load STL」にします。
    - [On Click ()] に RuntimeStlLoader オブジェクトをドラッグアンドドロップして、RuntimeStlLoader.Load を選択します。
    - Icon は任意のアイコンを選択します。
8. [ButtonCollection] > [ButtonThree] 選択し、名前を「Clear」にします。
9. Clear オブジェクトを選択し、[Inspector] ウィンドウの [Button Config Helper] を下記のように変更します。
    - [Main Label Text] を「Clear」にします。
    - [On Click ()] に RuntimeStlLoader オブジェクトをドラッグアンドドロップして、RuntimeStlLoader.Clear を選択します。
    - Icon は任意のアイコンを選択します。

## 実行

1. [OculusQuestMRTK | Microsoft Docs](https://docs.microsoft.com/en-us/windows/mixed-reality/mrtk-unity/features/cross-platform/oculus-quest-mrtk) の [Build and deploy your project to Oculus Quest] の [3. Change the deployment to Android] まで手順通りに行います。
2. [Edit] > [Project Settings...] を選択し、[Project Settings] ウィンドウを開きます。
3. [Player] > [Other Settings] > [Write Permission] を [External (SDCard)] に変更します。
    - [Write Permission] を [External (SDCard)] にすることによって、Application.persistentDataPath の参照先が /data/data/ から /sdcard/Android/data/ に変わります。
    ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460904/020d3000-d4dd-b335-8af3-ac99267bba41.png)

4. [OculusQuestMRTK | Microsoft Docs](https://docs.microsoft.com/en-us/windows/mixed-reality/mrtk-unity/features/cross-platform/oculus-quest-mrtk) の [Build and deploy your project to Oculus Quest] の [4. Ensure that the Oculus Quest is selected as the applicable run device] 移行を行い実行します。
5. Oculus Quest と パソコンを接続し、/sdcard/Android/data/[パッケージ名]/files/ に任意の STL ファイルを置きます。
6. Menu オブジェクトの LoadStl を選択すると STL ファイルが読み込まれ表示されます。

## トラブルシューティング

- Error building Player: BuildFailedException: Oculus Utilities Plugin with OpenXR only supports linear lighting. Please set 'Rendering/Color Space' to 'Linear' in Player Settings
    - OpenXR を使用する予定はないので、下記の手順で OpenXR を無効化します。
        1. [Projects] ウィンドウで、[Assets] > [Oculus] > [VR] > [Plugins] > [プラグインバージョン] > [AndroidOpenXR] > [OVRPlugin] を選択します。
        2. [Inspector] ウィンドウで、[Android] のチェックを外します。
        3. [Projects] ウィンドウで、[Assets] > [Oculus] > [VR] > [Plugins] > [プラグインバージョン] > [AndroidUniversal] > [OVRPlugin] を選択します。
        4. [Inspector] ウィンドウで、[Android] にチェックをします。

## 参考サイト

- [Oculus Quest 2でMRTK使って豆腐と戯れる最小手順 - Qiita](https://qiita.com/miyaura/items/7f6bb7ddcaab8bc6cb8e)
- [【Unity】MRTKをOculus Quest 2で動かすときの環境構築トラブル - Qiita](https://qiita.com/_udonba/items/030dab3e7dbe65922a28)
- [OpenXRを使用してOculusユーティリティプラグインをアクティブにする | Oculus開発者](https://developer.oculus.com/documentation/unity/unity-openxr/)
- [最近のOculus Quest開発環境について。 – ギャップロ](https://gaprot.jp/2020/11/12/oculus-quest-develop/)