さわぐちFMオキーステーション

幸せの鐘が鳴り響き僕はただ嬉しいふりをする

Oculus Questのハンドトラッキングでポインティング方向を取得する

ハンドトラッキング時、手がどの方向を向いているかを取得する方法についての紹介。

独自に座標や方向を設定しても良さそうだけど、公式が用意している機能を使って実装。公式なので向きや座標の共通化が図れるかなーと思うので自作アプリではなるべくこの方法を使っていこうと思います。

Pointer Poseの取得

以下の実装で手が向いている方向諸々をTransform型で取得することができます。

var hand = GetComponent<OVRHand>();

hand.PointerPose ←これでOK

Transformなのでpositionや、forwardが使えるので簡単なポインティング機能はこれで実装できそうです。また、PointPoseが取得できているかどうかは以下のbool型で取得できます。

hand.IsPointerPoseValid

これがfalseの場合、レイやポインティングを描画しないとすれば良さそうです。

簡単なサンプル

まずはレイを画面に表示するプログラムを作りましょう。初期化処理の手順に従ってOVRCameraRig階下にOVRHandPrefabを追加します。

f:id:amidaMangrove:20200101131438p:plain

以下のスクリプトを作成しOVRHandPrefabにアタッチします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PointerPoseSample : MonoBehaviour
{
    [SerializeField] float _rayDistance = 100;
    OVRHand _hand;
    LineRenderer _lineRenderer;

    // Start is called before the first frame update
    void Start()
    {
        _hand = GetComponent<OVRHand>();
        _lineRenderer = GetComponent<LineRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        //  レイをLineRendererで描画
        var positions = new Vector3[]{
            _hand.PointerPose.position,
            _hand.PointerPose.position + _hand.PointerPose.forward * _rayDistance
        };

        _lineRenderer.SetPositions(positions);

        //  PointerPoseが有効な時のみLineRendererを表示
        _lineRenderer.enabled = _hand.IsPointerPoseValid;

    }
}

LineRendererもOVRHandPrefabにそれぞれ追加します。ラインのサイズは適当の小さめに調整

f:id:amidaMangrove:20200101131911p:plain

実行すると以下の動画のようになります。

www.youtube.com

ホームで表示されている手と同じ位置 & 方向にレイが伸びているのが確認できました。

ピンチを検出してオブジェクトを手元に引っ張ってくる

レイにオブジェクトが衝突している状態でピンチを検出したらオブジェクトを手元に引っ張ってくるサンプルを作ってみます。

ピンチ検出に関してはこちら参照。

amidamangrove.hatenablog.com

先ほどのスクリプトにRaycastを追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PointerPoseSample : MonoBehaviour
{
    [SerializeField] float _rayDistance = 100;
    [SerializeField] float _speed = 10;
    OVRHand _hand;
    LineRenderer _lineRenderer;
    
    //[SerializeField]GameObject _cube;
    // Start is called before the first frame update
    void Start()
    {
        _hand = GetComponent<OVRHand>();
        _lineRenderer = GetComponent<LineRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        //  レイをLineRendererで描画
        var positions = new Vector3[]{
            _hand.PointerPose.position,
            _hand.PointerPose.position + _hand.PointerPose.forward * _rayDistance
        };

        _lineRenderer.SetPositions(positions);

        //  PointerPoseが有効な時のみLineRendererを表示
        _lineRenderer.enabled = _hand.IsPointerPoseValid;

        
        //  ピンチされているか判定
        if (_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index) >= 1f) { 

            //  レイの衝突判定
            var ray = new Ray(_hand.PointerPose.position, _hand.PointerPose.forward * _rayDistance);

            if (Physics.Raycast(ray, out var hitinfo, _rayDistance)){
                var hitObject = hitinfo.collider.gameObject;
                var distance = _hand.PointerPose.position - hitObject.transform.position;
                var velocity = distance.normalized;

                hitObject.GetComponent<Rigidbody>().AddForce(velocity * _speed);
                
            }

        }      
    }
}

あとはRigidBodyを持ったオブジェクトを適当にフィールドに置けば完成です。

実際に動作させた動画がこちらになります。

www.youtube.com

もうちょっと全体に手を加えればテレキネシス的なものが作れそうです

www.youtube.com