OpenXR Unity編

# 実装例
OpenXR 拡張を薄くラップする例。
詳しくはソース参照のこと。

https://github.com/ousttrue/OpenXR_VRM1

|openxr 拡張|実装されているデバイス|備考|
|-|-|-|
|XR_EXT_hand_tracking|quest, quest2, quest pro, hololens2, HTC Vive Cosmos, HTC Vive Focus 3, Varjo など|quest link でも動きます。leapmotion でも動いた|
|XR_FB_body_tracking|quest, quest2, quest pro|quest link でも動きます。Version 47 が必要な様子(Android側とPC側の両方)|
|XR_FB_eye_tracking_social|quest pro|quest link でも動きます。|
|XR_FB_face_tracking|quest pro|quest link でも動きます。|

https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_EXT_hand_tracking

# Unity の OpenXR
Unity で OpenXR API に直接アクセスするには公式の API からできます。

https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.5/manual/index.html

C の `openxr/openxr.h` の情報から Feature を自作して OpenXR Extension にアクセスする方法について。

https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.5/manual/features.html

下記のリポジトリで
`XR_EXT_hand_tracking` と `XR_FB_hand_tracking_mesh` を実装を発見しました。
`quest`, `quest pro` の `apk` と `oculus link` 双方で動作します。

https://github.com/joemarshall/openxrhands

本記事は、この実装を読み解いた解説的なものです。

# OpenXRFeature
## 継承と属性で Feature を定義する

```csharp
#if UNITY_EDITOR
    [UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Hand tracking Extension",
        BuildTargetGroups = new[] {
            UnityEditor.BuildTargetGroup.Standalone, UnityEditor.BuildTargetGroup.WSA, UnityEditor.BuildTargetGroup.Android },
        Company = "Joe M",
        Desc = "Enable hand tracking in unity",
        DocumentationLink = "https://docs.unity3d.com/Packages/com.unity.xr.openxr@0.1/manual/index.html",
        OpenxrExtensionStrings = xr_extension,
        Version = "0.0.1",
        FeatureId = featureId)]
#endif
    public class HandTrackingFeature : OpenXRFeature
    {
        public const string featureId = "com.joemarshall.handtracking"; // Unique な ID
        public const string xr_extension = "XR_EXT_hand_tracking"; // OPENXR の拡張名。拡張を使わないときは無くて良い。
    }
```

## OpenXRFeature を有効化する

edit - ProjectSettings - XR Plug-in Management - OpenXR

![features.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59664/1fc40d5c-b610-2fb3-5b3b-612c73b4f7f7.jpeg)

## OpenXRFeature にアクセスする

```csharp
// MonoBehaviour.Start で取得するとよい
var feature = OpenXRSettings.Instance.GetFeature<HandTrackingFeature>();
```

:::note warn
feature はシングルトン的にひとつ存在する。

のだけど、手違いで複数のローダーが Asset に存在したりすると多重に生成されて変な動きをするかもしれない(した。)
:::

:::note
Project 設定が Assest として作成される

![openxr_loader.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59664/36fdf9a7-16df-c851-0b84-8e812c083b9f.jpeg)
:::

## OpenXRFeature のライフサイクル

https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#extensions

### OnInstanceCreate

拡張が有効か問い合わせて無かったら false を返すことで、Feature を無効にできる。

```csharp
        override protected bool OnInstanceCreate(ulong xrInstance)
        {
            instance_ = xrInstance;
            if (!OpenXRRuntime.IsExtensionEnabled(xr_extension))
            {
                Debug.LogWarning($"{xr_extension} is not enabled.");
                // Return false here to indicate the system should disable your feature for this execution.  
                // Note that if a feature is marked required, returning false will cause the OpenXRLoader to abort and try another loader.
                return false;
            }

            return true;
        }
```

:::note
機種判定じゃなくて、拡張判定にした方が対応する機種を増やせそう

https://github.khronos.org/OpenXR-Inventory/extension_support.html#matrix
:::

:::note warn
拡張が無効な場合。

* プラットフォームあるいはデバイスに拡張が無い
* 拡張がアプリに許可されていない(quest pro の eye tracking など)
:::

:::note warn
同じデバイスでも Runtime によって拡張が変わる

* Oculus 経由と Steam 経由で拡張の内容がわりと違う

https://github.com/maluoi/openxr-explorer
:::

:::note
`hand tracking` (quest1,2,pro), `eye tracking`(quest pro), `face tracking`(quest pro) などは oculus link 経由でも有効化できる。
:::

### OnSessionBegin
#### 関数ポインタを取得

:::note
OpenXR の `C` の API そのまま。xrGetInstanceProcAddr 関数で関数ポインタを得る
:::

```csharp
            var getInstanceProcAddr = Marshal.GetDelegateForFunctionPointer<PFN_xrGetInstanceProcAddr>(xrGetInstanceProcAddr);
            Func<string, IntPtr> getAddr = (string name) =>
            {
                IntPtr ptr;
                getInstanceProcAddr(instance_, name, out ptr);
                return ptr;
            };
            xrCreateHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrCreateHandTrackerEXT>(getAddr("xrCreateHandTrackerEXT"));
            xrDestroyHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrDestroyHandTrackerEXT>(getAddr("xrDestroyHandTrackerEXT"));
            xrLocateHandJointsEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrLocateHandJointsEXT>(getAddr("xrLocateHandJointsEXT"));
```

#### handle初期化など

`XR_EXT_hand_tracking` では右手と左手の `handle` ここで初期化する。

### MainLoop

`XR_EXT_hand_tracking` では右手と左手の `handle` を使って `joint` の情報を得る。

### SessionEnd 

`XR_EXT_hand_tracking` では右手と左手の `handle` を開放する。

:::note
SessionBegin と SessionEnd は何度も起きる場合がある。

例えば HMD を外して、再び装着した場合などに再度始まる様子。
:::

### DestoryInstance

# 個別の技
## xrWaitFrame の呼び出しに割り込んで FrameTime を得る

`GetInstanceProcAddr` の呼び出しに割り込むことができる。
`xrWaitFrame` が来たときに別の関数でラップして、その関数ポインターを返すことで `XrFrameState` を横取りできる。

```csharp
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
```

:::note
`long XrFrameState.predictedDisplayTime` が HandTracking で joint を得るのに必要なのだが、得る方法が他に無い？
:::

## Delegate詳細

`Marshal.GetDelegateForFunctionPointer` で `IntPtr(Cの関数ポインター)` から Delegate を作る。

```csharp
xrCreateHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrCreateHandTrackerEXT>(getAddr("xrCreateHandTrackerEXT"));
```

C 関数の呼び出しの作法で定義を作る。

```csharp
        /*typedef struct XrHandTrackerCreateInfoEXT {
            XrStructureType      type;
            const void*          next;
            XrHandEXT            hand;
            XrHandJointSetEXT    handJointSet;
        } XrHandTrackerCreateInfoEXT;*/
        [StructLayout(LayoutKind.Sequential)]
        internal struct XrHandTrackerCreateInfoEXT
        {
            public int stype;
            public IntPtr next;
            public int hand;
            public int handJointSet;
        }

        /*XrResult xrCreateHandTrackerEXT(
            XrSession                                   session,
            const XrHandTrackerCreateInfoEXT*           createInfo,
            XrHandTrackerEXT*                           handTracker);*/
        internal delegate int Type_xrCreateHandTrackerEXT(ulong session, in XrHandTrackerCreateInfoEXT createInfo, out ulong tracker);
```

下記の仕様もしくは、ヘッダを参照する。

https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html

https://github.com/KhronosGroup/OpenXR-SDK/blob/main/include/openxr/openxr.h

まだ、`Khronos` の仕様に来ていない新しい拡張はベンダーの SDK のドキュメントやヘッダーに定義がある。

`XR_FB_eye_tracking_social` など。