Help us understand the problem. What is going on with this article?

力尽く!!「Leap Motion + Unity」で自作モデルを動かす

More than 1 year has passed since last update.

はじめに

この記事は、公式ドキュメントを読むことをあきらめた一匹のゴリラが、腕力にものを言わせてLeap Motionで自作モデルを動かすまでのストーリーです。

公式ドキュメントにならった綺麗な実装が書いてあるわけではないのであしからず

ターゲット

以下の同士(ゴリラ)諸氏におかれては、ためになるかもしれない

  • 公式ドキュメント読むの疲れたよー
  • そもそもドキュメントが英語でよくわからないよー
  • 「Final IK」ってなに?
  • Leap Motion買って、金欠だよー
  • とりあえず、それっぽく動けばいいや

あらすじ

ゴリラ「Leap Motion買ったし、SDKも準備したし、手のモデルも作ったし、そろそろ動かすか!」

今回使用するモデル

~サンプルシーンを眺めるゴリラ~
inspector.png

ゴリラ「設定項目多すぎ、26個もQuaternion手打ちとか、正気の沙汰じゃない」

公式ドキュメント(英語)を眺めるゴリラ~
ゴリラ「AutoRigで自動設定とかあるんだ、やってみよ」
Hand.png
ゴリラ「手の形変わってるし、握ったとき指重なってるし、なんかやだ」

どうするの

公式ドキュメントを探し続ければ、確信的な設定項目が見つかるかもしれないが、すでにサンプルシーンではそれらしく動く手が存在している。
LeapHand.gif
もうさ、こいつの動きをトレースしちゃおうよ、やっちゃおうよ

こうするの

ソースコード

Offset.cs
/// <summary>
/// Quaternionの差分の保持
/// </summary>
private class Offset
{
    /// <summary>
    /// 対象モデルのQuaternion
    /// </summary>
    private Quaternion Model;
    /// <summary>
    /// 参照モデルのQuaternion
    /// </summary>
    private Quaternion Leap;

    /// <summary>
    /// Quaternionの差分を保持し、参照モデルのQuaternionの変化を対象モデルへと伝搬する
    /// </summary>
    /// <param name="model">対象モデル</param>
    /// <param name="leap">参照モデル</param>
    public Offset(Quaternion model, Quaternion leap)
    {
        Model = model;
        Leap = Quaternion.Inverse(leap);
    }

    /// <summary>
    /// オフセットの適用
    /// </summary>
    /// <param name="currentLeap">現在の参照モデルのQuaternion</param>
    /// <returns>オフセットを適用した対象モデルのQuaternion</returns>
    public Quaternion ApplyOffset(Quaternion currentLeap)
    {
        return currentLeap * Leap * Model;
    }
}

/// <summary>
/// 指のオフセット
/// </summary>
private class FingerOffset
{
    private Offset Bone1;
    private Offset Bone2;
    private Offset Bone3;
    private Offset Bone4;

    public FingerOffset(Finger model, Finger leap)
    {
        if (model.Bone1 != null && leap.Bone1 != null)
        {
            Bone1 = new Offset(model.Bone1.transform.rotation, leap.Bone1.transform.rotation);
        }
        Bone2 = new Offset(model.Bone2.transform.rotation, leap.Bone2.transform.rotation);
        Bone3 = new Offset(model.Bone3.transform.rotation, leap.Bone3.transform.rotation);
        Bone4 = new Offset(model.Bone4.transform.rotation, leap.Bone4.transform.rotation);
    }

    public void ApplyOffset(Finger model, Finger leap)
    {
        if (Bone1 != null)
        {
            model.Bone1.rotation = Bone1.ApplyOffset(leap.Bone1.rotation);
        }
        model.Bone2.rotation = Bone2.ApplyOffset(leap.Bone2.rotation);
        model.Bone3.rotation = Bone3.ApplyOffset(leap.Bone3.rotation);
        model.Bone4.rotation = Bone4.ApplyOffset(leap.Bone4.rotation);
    }
}

private class HandOffset
{
    private FingerOffset Thumb;
    private FingerOffset Index;
    private FingerOffset Middle;
    private FingerOffset Ring;
    private FingerOffset Pinky;
    private Offset Wirst;
    private Vector3 OffsetWirst;

    public HandOffset(Hand model, Hand leap)
    {
        Thumb = new FingerOffset(model.Thumb, leap.Thumb);
        Index = new FingerOffset(model.Index, leap.Index);
        Middle = new FingerOffset(model.Middle, leap.Middle);
        Ring = new FingerOffset(model.Ring, leap.Ring);
        Pinky = new FingerOffset(model.Pinky, leap.Pinky);

        Wirst = new Offset(model.Wirst.rotation, leap.Wirst.rotation);
        OffsetWirst = model.Wirst.position - leap.Wirst.position;
    }

    public void ApplyOffset(Hand model, Hand leap)
    {
        Thumb.ApplyOffset(model.Thumb, leap.Thumb);
        Index.ApplyOffset(model.Index, leap.Index);
        Middle.ApplyOffset(model.Middle, leap.Middle);
        Ring.ApplyOffset(model.Ring, leap.Ring);
        Pinky.ApplyOffset(model.Pinky, leap.Pinky);

        model.Wirst.rotation = Wirst.ApplyOffset(leap.Wirst.rotation);

        model.Wirst.position = leap.Wirst.position + OffsetWirst;
    }
}
LeapMotion.cs
[SerializeField]
private Hand HandLeft;
[SerializeField]
private Hand HandLeapLeft;
[SerializeField]
private Hand HandRight;
[SerializeField]
private Hand HandLeapRight;


private HandOffset HandOffsetLeft;
private HandOffset HandOffsetRight;


private void Awake()
{
    HandOffsetLeft = new HandOffset(HandLeft, HandLeapLeft);
    HandOffsetRight = new HandOffset(HandRight, HandLeapRight);

    GameObject obj;
    obj = new GameObject("LeftHandOffset");
    obj.transform.parent = gameObject.transform;
    obj = new GameObject("RightHandOffset");
    obj.transform.parent = gameObject.transform;
}

private void Update()
{
    HandOffsetLeft.ApplyOffset(HandLeft, HandLeapLeft);
    HandOffsetRight.ApplyOffset(HandRight, HandLeapRight);
}

ソース全体はこちら

解説

特に解説する余地もない、力技のソースですが、簡単に説明すると

  1. サンプルのモデル(参照モデル)と自作モデル(対象モデル)の各関節ごとのQuaternionの差分を保持する。
  2. 参照モデルの動き(Quaternonの変化)にオフセットを適用して、対象モデルのQuaternionを更新する。
  3. 1.2.をすべての手、すべての指に適用する。

おわりに

実行結果

Hand2.png

今後の課題

結果としてAutoRigより見た目はマシだが、依然として設定項目が多いので(指(5本) × 骨(4本)のTransformを設定する必要がある)、このスクリプトにもAutoRigのような自動で指のボーンのTransformを設定するスクリプトを組み込みたい。

感想

個人的にはそれなりに動くし、及第点ではあるんだけど、設定項目が多すぎるのがやっぱりつらいところ

いっそのこと、公式ドキュメントを漁った方がより早く幸せになれたかもしれない。
より早く幸せになった人は、是非ともその方法を教えてください。

自分の場合は、AutoRig微妙ではあったんだけど、モデル作成の段階からきれいに動かすノウハウとかあるのかもしれない。
そんなノウハウを知ってる方は(略

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away