MRが楽しい

MRやVRについて学習したことを書き残す

MRTK v2のドキュメントを少しずつ読み解く ハンドトラッキング その2

本日は MRTKv2 の調査枠です。
MRTKv2 の Guides ドキュメントを少しずつ読み進めていきます。

MRTKv2のGuidesドキュメント

以下のドキュメントを読み進めていきます。
microsoft.github.io

以下のページでは有志による本ドキュメントの日本語翻訳が行われています。
投稿時点でこちらで未翻訳、または著者が興味のある部分について記事にしていきます。
hololabinc.github.io

本記事では以下のページを読み進めます。
microsoft.github.io
f:id:bluebirdofoz:20200309091911j:plain

ハンドトラッキング

スクリプティング

手の位置と回転は MixedRealityPose として、個々の手の関節の入力システムから要求できます。

あるいはジョイントに従う GameObject へのアクセスを許可します。
これは別の GameObject がジョイントを継続的に追跡する必要がある場合に役立ちます。

使用可能なジョイントは TrackedHandJoint 列挙型にリストされています。
microsoft.github.io

注意

ハンドトラッキングが失われると、ジョイントオブジェクトが破壊されます。
null エラーを回避するために、ジョイントオブジェクトを使用するスクリプトが適切に処理するようにしてください。

所定のハンドコントローラーへのアクセス

入力イベントを処理する場合などに、特定のハンドコントローラーが利用できることがあります。
この場合、IMixedRealityHand インタフェースを使うことで、デバイスからジョイントデータを直接要求できます。

コントローラからのジョイントポーズのポーリング

要求されたジョイントが何らかの理由で利用できない場合、TryGetJoint 関数は false を返します。
その場合、結果のポーズは MixedRealityPose.ZeroIdentity になります。

public void OnSourceDetected(SourceStateEventData eventData)
{
  var hand = eventData.Controller as IMixedRealityHand;
  if (hand != null)
  {
    if (hand.TryGetJoint(TrackedHandJoint.IndexTip, out MixedRealityPose jointPose))
    {
      // ...
    }
  }
}

f:id:bluebirdofoz:20200309091936j:plain

ハンドビジュアライザーからのジョイント変換

コントローラービジュアライザーからジョイントオブジェクトを要求できます。

public void OnSourceDetected(SourceStateEventData eventData)
{
  var handVisualizer = eventData.Controller.Visualizer as IMixedRealityHandVisualizer;
  if (handVisualizer != null)
  {
    if (handVisualizer.TryGetJointTransform(TrackedHandJoint.IndexTip, out Transform jointTransform))
    {
      // ...
    }
  }
}

f:id:bluebirdofoz:20200309091945j:plain

簡素化されたジョイントデータアクセス

特定のコントローラーが指定されていない場合、手関節データに簡単にアクセスできるユーティリティクラスが提供されます。
これらの関数は、現在追跡されている最初の利用可能なハンドデバイスからの共同データを要求します。

HandJointUtilsからのジョイントポーズのポーリング

HandJointUtils は最初のアクティブなハンドデバイスを照会する静的クラスです。

if (HandJointUtils.TryGetJointPose(TrackedHandJoint.IndexTip, Handedness.Right, out MixedRealityPose pose))
{
    // ...
}

f:id:bluebirdofoz:20200309091954j:plain

手関節サービスからの関節変換

IMixedRealityHandJointService はジョイントを追跡するための永続的な GameObjects のセットを保持します。

IMixedRealityHandJointService handJointService = null;
if (CoreServices.InputSystem != null)
{
    var dataProviderAccess = CoreServices.InputSystem as IMixedRealityDataProviderAccess;
    if (dataProviderAccess != null)
    {
        handJointService = dataProviderAccess.GetDataProvider<IMixedRealityHandJointService>();
    }
}

if (handJointService != null)
{
    Transform jointTransform = handJointService.RequestJointTransform(TrackedHandJoint.IndexTip, Handedness.Right);
    // ...
}

f:id:bluebirdofoz:20200309092004j:plain

ハンドトラッキングイベント

コントローラから直接データをポーリングすることが望ましくない場合、入力システムもイベントを提供します。

ジョイントイベント

IMixedRealityHandJointHandler はジョイント位置の更新を処理します。

public class MyHandJointEventHandler : IMixedRealityHandJointHandler
{
    public Handedness myHandedness;

    void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData)
    {
        if (eventData.Handedness == myHandedness)
        {
            if (eventData.InputData.TryGetValue(TrackedHandJoint.IndexTip, out MixedRealityPose pose))
            {
                // ...
            }
        }
    }
}

f:id:bluebirdofoz:20200309092015j:plain

メッシュイベント

IMixedRealityHandMeshHandler は関節式ハンドメッシュの変更を処理します。
ハンドメッシュはデフォルトでは有効になっていないことに注意してください。

public class MyHandMeshEventHandler : IMixedRealityHandMeshHandler
{
    public Handedness myHandedness;
    public Mesh myMesh;

    public void OnHandMeshUpdated(InputEventData<HandMeshInfo> eventData)
    {
        if (eventData.Handedness == myHandedness)
        {
            myMesh.vertices = eventData.InputData.vertices;
            myMesh.normals = eventData.InputData.normals;
            myMesh.triangles = eventData.InputData.triangles;

            if (eventData.InputData.uvs != null && eventData.InputData.uvs.Length > 0)
            {
                myMesh.uv = eventData.InputData.uvs;
            }

            // ...
        }
    }
}

f:id:bluebirdofoz:20200309092025j:plain

既知の問題点

.NETネイティブ

現在、.NETバックエンドを使用したマスタービルドには既知の問題があります。
.NET Nativeでは、Marshal.GetObjectForIUnknown を使用して IInspectable ポインターをネイティブコードからマネージコードにマーシャリングできません。
MRTKはこれを使用して SpatialCoordinateSystem を取得し、プラットフォームから手と目のデータを受信します。

この問題の回避策として、ネイティブの Mixed Reality Toolkit リポジトリでDLLソースを提供しました。
そこのREADMEの指示に従って、バイナリを Unity アセットの Plugins フォルダーにコピーしてください。
その後、MRTKで提供される WindowsMixedRealityUtilities スクリプトが解決します。

独自のDLLを作成するか、この回避策を既存のDLLに含めたい場合、回避策のコードは次のとおりです。

extern "C" __declspec(dllexport) void __stdcall MarshalIInspectable(IUnknown* nativePtr, IUnknown** inspectable)
{
    *inspectable = nativePtr;
}
[DllImport("DotNetNativeWorkaround.dll", EntryPoint = "MarshalIInspectable")]
private static extern void GetSpatialCoordinateSystem(IntPtr nativePtr, out SpatialCoordinateSystem coordinateSystem);

private static SpatialCoordinateSystem GetSpatialCoordinateSystem(IntPtr nativePtr)
{
    try
    {
        GetSpatialCoordinateSystem(nativePtr, out SpatialCoordinateSystem coordinateSystem);
        return coordinateSystem;
    }
    catch
    {
        UnityEngine.Debug.LogError("Call to the DotNetNativeWorkaround plug-in failed. The plug-in is required for correct behavior when using .NET Native compilation");
        return Marshal.GetObjectForIUnknown(nativePtr) as SpatialCoordinateSystem;
    }
}