MRが楽しい

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

HoloLensで両手の位置を検出して立方体を作成する

本日は HoloLens の技術調査枠です。
HoloLensで手の位置を検出する手順を記事にします。
前回記事の続きです。
bluebirdofoz.hatenablog.com

両手の位置を元に立方体を生成する

両手の検出してその位置情報を元に立方体を生成するようスクリプトを修正してみました。
・HandPointGetter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HoloToolkit.Unity.InputModule;

public class HandPointGetter : MonoBehaviour, ISourcePositionHandler
{
    /// <summary>
    /// オブジェクト有効時の実行処理
    /// </summary>
    private void OnEnable()
    {
        // 常にハンドトラッキングのイベントを取得する
        InputManager.Instance.PushFallbackInputHandler(this.gameObject);
    }

    /// <summary>
    /// オブジェクト無効時の実行処理
    /// </summary>
    private void OnDisable()
    {
        if (InputManager.Instance)
        {
            // イベントの取得を無効化する
            InputManager.Instance.PopFallbackInputHandler();
        }
    }

    /// <summary>
    /// 定期実行処理
    /// </summary>
    private void Update()
    {
        // 追加対象のハンドIDがあれば追加処理を実行
        if (createList.Count > 0)
        {
            for (int loop = 0; loop < createList.Count; loop++)
            {
                uint id = createList[loop];
                if (handStates.ContainsKey(id))
                {
                    // Sphereオブジェクトを生成
                    GameObject posObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                    posObject.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
                    // オブジェクトの参照をハンドトラッキング情報に追加
                    handStates[id].Object = posObject;
                }
            }
            createList.Clear();
        }
        // 削除対象のハンドIDがあれば削除処理を実行
        if (lostList.Count > 0)
        {
            for (int loop = 0; loop < lostList.Count; loop++)
            {
                uint id = lostList[loop];
                if (handStates.ContainsKey(id))
                {
                    // Sphereオブジェクトを破棄
                    Destroy(handStates[id].Object);
                    handStates.Remove(id);
                }
            }
            lostList.Clear();
        }
        float currentKey01Time = 0.0f;
        float currentKey02Time = 0.0f;
        // ロストが正常に検出されない場合があるため時間経過でも削除を行う
        foreach (KeyValuePair<uint, HandState> pair in handStates)
        {
            // 最終検出からの経過時間を取得
            float lostTime = Time.time - pair.Value.DetectTime;
            if (lostTime > deleteTime)
            {
                uint id = pair.Key;
                // Sphereオブジェクトを破棄
                Destroy(handStates[id].Object);
                handStates.Remove(id);
            }
            else
            {
                // より最新のキーを2つ取得する
                float checkTime = pair.Value.DetectTime;
                if (checkTime > currentKey02Time)
                {
                    if (checkTime > currentKey01Time)
                    {
                        currentkey01 = pair.Key;
                        currentKey01Time = checkTime;
                    }
                    else
                    {
                        currentkey02 = pair.Key;
                        currentKey02Time = checkTime;
                    }
                }
            }
        }
        // 最新のハンドステータスが2つあれば立方体を生成する
        if (handStates.ContainsKey(currentkey01) &&
            handStates.ContainsKey(currentkey02))
        {
            if (cubeObject == null)
            {
                // Cubeオブジェクトを生成
                cubeObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
            }
            // 手の位置を元にCubeの位置と傾き、大きさを変更
            Vector3 posOne = handStates[currentkey01].Position;
            Vector3 posTwo = handStates[currentkey02].Position;
            CalculateCube(posOne, posTwo);
        }
        else
        {
            // Sphereオブジェクトを破棄
            Destroy(cubeObject);
        }
    }

    private bool checkflg = false;

    /// <summary>
    /// 立方体オブジェクト参照
    /// </summary>
    private GameObject cubeObject = null;

    // 追加ハンドID
    private List<uint> createList = new List<uint>();
    // 削除ハンドID
    private List<uint> lostList = new List<uint>();
    // 自動削除までの待機時間(秒)
    private float deleteTime = 3.0f;

    /// <summary>
    /// 手毎のハンドトラッキング情報
    /// </summary>
    private Dictionary<uint, HandState> handStates = new Dictionary<uint, HandState>();
    private class HandState
    {
        // 位置情報
        public Vector3 Position { get; set; }
        // オブジェクト参照
        public GameObject Object;
        // 最終検出時刻
        public float DetectTime;
    }

    /// <summary>
    /// ハンドトラッキング情報の取得
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private HandState GetHandState(uint id)
    {
        if (handStates.ContainsKey(id))
        {
            // 既に情報がある場合は返却
            return handStates[id];
        }
        else
        {
            // 未検出のハンドIDならば情報を作成
            Debug.Log("GetHandState make: id = " + id);
            HandState handState = new HandState();
            // 更新時刻を登録
            handState.DetectTime = Time.time;
            // ステータスをリスト追加
            handStates.Add(id, handState);
            // 追加リストに登録
            createList.Add(id);
            return handState;
        }
    }

    /// <summary>
    /// ハンド検出イベント
    /// </summary>
    /// <param name="eventData"></param>
    public void OnPositionChanged(SourcePositionEventData eventData)
    {
        HandState getHandState = GetHandState(eventData.SourceId);
        getHandState.Position = eventData.GripPosition;
        if (getHandState.Object != null)
        {
            // ハンドトラッキング情報の位置情報を更新
            getHandState.Position = eventData.GripPosition;
            // 更新時刻を更新
            getHandState.DetectTime = Time.time;
        }
    }

    /// <summary>
    /// ハンドロストイベント
    /// </summary>
    /// <param name="eventData"></param>
    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (handStates.ContainsKey(eventData.SourceId))
        {
            // 既にハンドIDが登録されていれば削除リストに登録
            lostList.Add(eventData.SourceId);
        }
    }

    /// <summary>
    /// 頂点に合わせて立方体を変形
    /// </summary>
    /// <param name="posOne"></param>
    /// <param name="posTwo"></param>
    public void CalculateCube(Vector3 posOne, Vector3 posTwo)
    {
        // 2つの頂点の中央に位置を修正
        cubeObject.transform.localPosition = Vector3.Lerp(posOne, posTwo, 0.5f);
        // 頂点の方向を向くように傾きを修正
        cubeObject.transform.LookAt(posOne);
        // 2つの頂点の距離でサイズを調整
        float size = Vector3.Distance(posOne, posTwo);
        cubeObject.transform.localScale = new Vector3(size, size, size);
    }
}

f:id:bluebirdofoz:20190501232446j:plain

前回記事と同様に、アプリケーションをビルドして HoloLens 上で動作確認してみました。
f:id:bluebirdofoz:20190501232345g:plain

オブジェクトの直感的なトランスフォーム調整として良いかなと思いましたが。
よくよく考えると[Two Hand Manipulatable]でのつまみ操作と理屈は同じです。
bluebirdofoz.hatenablog.com