MagicLeapのハンドトラッキング実装
Tags: csharp, magicleap, unity3d
Published: 2020-07-30 16:08:09

# MagicLeapのハンドトラッキング実装の備忘録

基本的には[ここ](https://developer.magicleap.com/en-us/learn/guides/hand-tracking-key-points-unity)の通りに進めていけば問題ない

**前提となるもの**

- MagicLeapのmpkをビルドできる段階までのセッティングが完了している
- The LabでのZeroIterationが可能になっている


**開発環境**
Windows10
Unity 2019.3.7f1
MagicLeapUnityPackage 0.24.1



# 出来上がり

こんな感じに各関節
手首、親指 ~ 小指までの座標の取得
ジェスチャの取得( サンプルの中では利用していない )

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zke2ozg5r4nyujzg1fag.gif)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">MagicLeapのハンドトラッキング実装してみた <a href="https://t.co/ZSYl1BVXay">pic.twitter.com/ZSYl1BVXay</a></p>&mdash; 松本隆介 (@matsumotokaka11) <a href="https://twitter.com/matsumotokaka11/status/1288637836548218886?ref_src=twsrc%5Etfw">July 30, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# 実装方法

## 下準備
シーンに配置するGameObject
大体のVRデバイスとかで主流な構成の配置にしてます
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ws1u3r6unygw3rwz8vcd.JPG)


**CameraRig**
ただのルートオブジェクトとして利用しています


**Head**
MagicLeapパッケージ内 Core > Assets > Prefabs > MainCamera
のプレハブをCameraRigの子オブジェクトにしてHeadと名称を変えたものです、この中にDirectionalLightを子オブジェクトとして配置していますが配置するか否かはお好みで
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8jlrx3womcibdnsb6gg9.JPG)


**Controller**
MagicLeapパッケージ内 Examples > Assets > Prefabs > Controller
のプレハブをCameraRigの子オブジェクトとして配置しています
[こちら](https://dev.to/matsumotokaka11/magicleap-4h9l)の記事で紹介したコントローラの入力でホームボタンでアプリを閉じるために利用してます
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a1i9aad4bu2d6cvuw9s4.JPG)


**LHand, RHand**
今回のメイン
CameraRigの子オブジェクトとしてGameObjectを作成( Emptyとして )
Thumb( 親指 ), Index( 人差し指 ), Middle( 中指 ), Ring( 薬指 ), Pinky( 小指 )をそれぞれEmptyのGameObjectで作成し、
各指の子オブジェクトThumb, Index, Middleは3個、Ring, Pinkyは2個、SphereObjectを生成、スケールは0.01くらいに設定
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xooyjdgpwqw9dcuyhpxv.JPG)


## スクリプト

このスクリプトは[MagicLeap公式サンプル](https://developer.magicleap.com/en-us/learn/guides/hand-tracking-key-points-unity)のものに手を加えたものです
以下のスクリプトをLHand, RHandにアタッチ

```csharp
using UnityEngine;
using UnityEngine.XR.MagicLeap;

/// <summary>
/// ハンドトラッキング.
/// </summary>
public class HandController : MonoBehaviour
{

    [System.Serializable]
    public class HandJointData
    {
        [SerializeField] GameObject wrist;
        [SerializeField] GameObject[] thumb;
        [SerializeField] GameObject[] index;
        [SerializeField] GameObject[] middle;
        [SerializeField] GameObject[] ring;
        [SerializeField] GameObject[] pinky;
        [SerializeField] Material handMaterial;
        [SerializeField] Color color;
        
        public Vector3 Wrist { get; private set; }
        public Vector3[] Thumb { get; private set; }
        public Vector3[] Index { get; private set; }
        public Vector3[] Middle { get; private set; }
        public Vector3[] Ring { get; private set; }
        public Vector3[] Pinky { get; private set; }
        MLHandTracking.Hand hand;
        LineRenderer[] lines;

        public void Initialize(
            MLHandTracking.Hand _hand)
        {
            hand = _hand;
            Wrist = Vector3.zero;
            
            // 各関節 + 手首の分, 配列をとる.
            Thumb = new Vector3[thumb.Length + 1];
            Index = new Vector3[index.Length + 1];
            Middle = new Vector3[middle.Length + 1];
            Ring = new Vector3[ring.Length + 1];
            Pinky = new Vector3[pinky.Length + 1];
            
            lines = new LineRenderer[5];
            lines[0] = thumb[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[0].positionCount = 4;
            
            lines[1] = index[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[1].positionCount = 4;

            lines[2] = middle[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[2].positionCount = 4;
            
            lines[3] = ring[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[3].positionCount = 3;

            lines[4] = pinky[0].transform.parent.gameObject.AddComponent<LineRenderer>();
            lines[4].positionCount = 3;

            // LineRendererの初期セッティング.
            foreach (var line in lines)
            {
                line.material = handMaterial;
                line.startColor = color;
                line.endColor = color;
                line.startWidth = 0.01f;
                line.endWidth = 0.01f;
            }
        }


        public void UpdatePositions()
        {
            if (hand == null) return;
            
            Thumb[0] = hand.Wrist.KeyPoints[0].Position;
            Thumb[1] = hand.Thumb.KeyPoints[0].Position;
            Thumb[2] = hand.Thumb.KeyPoints[1].Position;
            Thumb[3] = hand.Thumb.KeyPoints[2].Position;
            for (var i = 1; i < Thumb.Length; ++i)
            {
                thumb[i - 1].transform.position = Thumb[i];
            }
            lines[0].SetPositions(Thumb);

            Index[0] = hand.Wrist.KeyPoints[0].Position;
            Index[1] = hand.Index.KeyPoints[0].Position;
            Index[2] = hand.Index.KeyPoints[1].Position;
            Index[3] = hand.Index.KeyPoints[2].Position;
            for (var i = 1; i < Index.Length; ++i)
            {
                index[i - 1].transform.position = Index[i];
            }
            lines[1].SetPositions(Index);

            Middle[0] = hand.Wrist.KeyPoints[0].Position;
            Middle[1] = hand.Middle.KeyPoints[0].Position;
            Middle[2] = hand.Middle.KeyPoints[1].Position;
            Middle[3] = hand.Middle.KeyPoints[2].Position;
            for (var i = 1; i < Middle.Length; ++i)
            {
                middle[i - 1].transform.position = Middle[i];
            }
            lines[2].SetPositions(Middle);
            
            Ring[0] = hand.Wrist.KeyPoints[0].Position;
            Ring[1] = hand.Ring.KeyPoints[0].Position;
            Ring[2] = hand.Ring.KeyPoints[1].Position;
            for (var i = 1; i < Ring.Length; ++i)
            {
                ring[i - 1].transform.position = Ring[i];
            }
            lines[3].SetPositions(Ring);

            Pinky[0] = hand.Wrist.KeyPoints[0].Position;
            Pinky[1] = hand.Pinky.KeyPoints[0].Position;
            Pinky[2] = hand.Pinky.KeyPoints[1].Position;
            for (var i = 1; i < Pinky.Length; ++i)
            {
                pinky[i - 1].transform.position = Pinky[i];
            }
            lines[4].SetPositions(Pinky);
            
        }
    }
    

    // ジェスチャ.
    public enum HandPoses
    {
        Ok,
        Finger,
        Thumb,
        OpenHand,
        Fist,
        NoPose,
        NoHand,
    }

    public enum HandId
    {
        RightHand,
        LeftHand
    }


    [SerializeField] HandPoses handPose = HandPoses.NoPose;
    [SerializeField] HandJointData handData;
    [SerializeField] HandId handId;
    MLHandTracking.HandKeyPose[] gestures;
    MLHandTracking.Hand hand;

    
    void Start()
    {
        // HandTrackingを開始する.
        MLHandTracking.Start();
        
        hand = handId == HandId.LeftHand ? MLHandTracking.Left : MLHandTracking.Right;
        handData.Initialize(hand);

        gestures = new MLHandTracking.HandKeyPose[6];

        // 各ジェスチャを登録.
        gestures[0] = MLHandTracking.HandKeyPose.Ok;
        gestures[1] = MLHandTracking.HandKeyPose.Finger;
        gestures[2] = MLHandTracking.HandKeyPose.OpenHand;
        gestures[3] = MLHandTracking.HandKeyPose.Fist;
        gestures[4] = MLHandTracking.HandKeyPose.Thumb;
        gestures[5] = MLHandTracking.HandKeyPose.NoHand;

        MLHandTracking.KeyPoseManager.EnableKeyPoses(gestures, true, false);

    }


    void OnDestroy()
    {
        MLHandTracking.Stop();
    }

    
    void Update()
    {
        handData.UpdatePositions();
        
        if (GetGesture(hand, MLHandTracking.HandKeyPose.Ok))
        {
            handPose = HandPoses.Ok;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Finger))
        {
            handPose = HandPoses.Finger;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.OpenHand))
        {
            handPose = HandPoses.OpenHand;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Fist))
        {
            handPose = HandPoses.Fist;
        }
        else if (GetGesture(hand, MLHandTracking.HandKeyPose.Thumb))
        {
            handPose = HandPoses.Thumb;
        }
        else
        {
            handPose = HandPoses.NoPose;
        }
    }


    /// <summary>
    /// ジェスチャの取得.
    /// </summary>
    /// <param name="hand"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    private bool GetGesture(
        MLHandTracking.Hand hand, 
        MLHandTracking.HandKeyPose type)
    {
        if (hand == null) return false;

        return 0.9f < hand.HandKeyPoseConfidence && hand.KeyPose == type;
    }

}


```

アタッチしたら以下の画像のように各関節のオブジェクトをセット、配列の添え字が若い方が根元に来るように設定
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/axwriwyrc37j1j3fcxx6.JPG)


**各パラメータの説明**
**HandPose** : ジェスチャのポーズ名( 今回はInspectorに表示しているだけです )
**HandData** : 各関節のオブジェクトを保持するクラス、HandCenterは利用していません( うまくトラッキングできなかったので外しました )
**HandMaterial** : 各関節オブジェクトの球をつなぐ線の描画用マテリアルです、今回はMagicLeapパッケージ内のUIBeamを利用しました
**Color** : 各関節オブジェクトの球をつなぐ線の色です、今回は左は赤、右は緑で設定しています
**HandId** : 手の左右を決定する識別子


## Unity の設定
この状態でTheLabでMagicLeapと接続してPlayModeに入るとハンドトラッキングされたオブジェクトの様子が確認できると思います
ただしmpkファイルとして出力する際は以下の設定を行わないと実機ではエラーが出てハンドトラッキング及びジェスチャの取得はできません
 ~~公式のチュートリアル通りにやれば設定の仕方まで説明されてたけど必要なメソッドとか確認したらすぐ実行したくなっちゃうのよね~~
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tgz0x0yeevftj06sxk03.JPG)

Edit > ProjectSettigs > MagicLeap > ManifestSettings の項目を開き
GestureConfig, GestureSubscribeにチェックを入れる ( 公式チュートリアルだと明示的にLowLatencyLightwearにもチェックを入れるように説明されているが現バージョンでは自動で入ってる? )


これでmpkを出力して実機でテストするとハンドトラッキングが実装できているはずです


# あとがき

これはTheLabからDLしてきたMagicLeapUnityPackageでのサンプルです、MagicLeapToolKitでのハンドトラッキングはまだ触ったことがないので後日記事にできればと思います