athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

HoloLensでオブジェクトを移動してみる

こんにちは、情報システム部の高野です。
今回は、オブジェクトの移動を試してみます。

前回作ったプロジェクトをそのまま利用します。


HoloLensでオブジェクト移動するために色々と調べてみたところ
いくつかの実装方法があることが分かりました。
今回は、そのうちの2つの方法を試してみます。
1つは、UnityのInteractionManagerのイベントを使う方法
もう1つは、HoloToolkitのIManipulationHandlerを実装する方法です。

InteractionManagerを利用した移動

前回、オブジェクトを生成できるようにしたので
その生成したオブジェクトを移動できるようにしていきます。

移動用のスクリプトを作成する

ProjectウィンドウのScriptsフォルダにGestureControllerというスクリプトを作成します。
作成してGestureControllerHierarchyウィンドウのObjectManager
ドラッグ&ドロップし設定します。

CubeプレハブにTagを設定する

後程、別オブジェクトを作成するので区別できるようにTagを設定しておきます。
ProjectウィンドウのPrefabsフォルダからCubeを選択します。
Inspectorウィンドウの上部にTagプルダウンがあるので「Add Tag」を選択します。
TagsにあるプラスボタンをクリックしTag名をInteractionとし保存します。
もう一度CubeプレハブのInspectorウィンドウを開き
Tagで「Interaction」を選択します。

Tagに設定した状態
f:id:taktak1974:20170522160017p:plain

移動処理を実装する

GestureControllerスクリプトをVisualStudioで開きます。
コードを以下の様に変更します

using HoloToolkit.Unity.InputModule;
using UnityEngine;
using UnityEngine.VR.WSA.Input;

public class GestureController : MonoBehaviour 
{

    private Vector3 prevPos;
    private bool isHold;
    private GameObject focusObj;

    // Use this for initialization
    void Start()
    {
        InteractionManager.SourcePressed += InteractionManager_SourcePressed;
        InteractionManager.SourceReleased += InteractionManager_SourceReleased;
        InteractionManager.SourceLost += InteractionManager_SourceLost;
        InteractionManager.SourceUpdated += InteractionManager_SourceUpdated;
    }

    // Update is called once per frame
    void Update()
    {
        var obj = GazeManager.Instance.HitObject;

        // ホールドしている時は、オブジェクト入れ替えない
        if (obj != null && !isHold)
        {
            // TagがInteractionのものだけを対象とする
            if (obj.tag == "Interaction")
            {
                focusObj = obj;
            }
        }
    }

    private void InteractionManager_SourcePressed(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = false;

        Vector3 handPosition;
        if (state.source.kind == InteractionSourceKind.Hand && 
            state.properties.location.TryGetPosition(out handPosition))
        {
            isHold = true;
            prevPos = handPosition;
        }
    }

    private void InteractionManager_SourceReleased(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = true;
        isHold = false;
        focusObj = null;
    }

    private void InteractionManager_SourceLost(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = true;
        isHold = false;
        focusObj = null;
    }

    private void InteractionManager_SourceUpdated(InteractionSourceState state)
    {
        if (!isHold || focusObj == null) return;

        Vector3 handPosition;
        state.properties.location.TryGetPosition(out handPosition);

        if (state.source.kind == InteractionSourceKind.Hand && 
            state.properties.location.TryGetPosition(out handPosition))
        {
            var moveVector = Vector3.zero;
            moveVector = handPosition - prevPos;

            prevPos = handPosition;

            var handDistance = Vector3.Distance(Camera.main.transform.position, handPosition);
            var objectDistance = Vector3.Distance(Camera.main.transform.position, focusObj.transform.position);

            focusObj.transform.position += (moveVector * (objectDistance / handDistance));
        }
    }
}

InteractionManager_SourcePressedで手の位置を取得しておき
InteractionManager_SourceUpdatedで移動距離を計算して
オブジェクトに新しい位置を割り当てます。
この移動方法は下記の記事を参考にさせていただきました。
qiita.com
もうちょっと複雑な計算をしているものも見かけたのですが
今のところは、この方法で支障がないような気がしています。

IManipulationHandlerを利用した移動

今度はHoloToolkitのIManipulationHandlerインターフェイスを実装する方法です。
HoloToolkitを利用しているので、できればこちらの方法で実装した方が良いかと思います。

新たなCubeプレハブを作成する

元々、用意してあったCubeとは別のオブジェクトを生成して
そちらをIManipulationHandlerで移動するようにしていきます。
前回、作成したようにCubeのプレハブを作成します。
見た目で判別できるように黄色にしておきます。
プレハブ名もYellowCubeにします。
RigidbodyCubeManagerスクリプトも設定しておきます。

YelloCubeを生成できるようにコードを変更する

前回作成したObejectManagerスクリプトのコードを変更します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ObjectManager : MonoBehaviour, IInputClickHandler
{
    public GameObject obj1;
    public GameObject obj2;
    private bool toggle = true;

    private void Start()
    {
        InputManager.Instance.PushFallbackInputHandler(gameObject);
    }

    public void OnInputClicked(InputClickedEventData eventData)
    {
        var obj = toggle ? obj1 : obj2;
        toggle = !toggle;

        var pos = Camera.main.transform.position;
        var forword = Camera.main.transform.forward;

        Instantiate(obj, pos + forword, new Quaternion());
    }
}

Unityに戻りobj1にCubeプレハブをobj2にYellowCubeプレハブを設定します。

移動処理を実装する

Unityで新しいスクリプトManipulationControllerを作成します。
作成したスクリプトYellowCubeプレハブに設定します。

設定したスクリプトをVisualStudioで開き
下記の様にコードを変更します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ManipulationController : MonoBehaviour, IManipulationHandler
{

    Vector3 prevPos;

    public void OnManipulationCanceled(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = true;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnManipulationCompleted(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = true;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnManipulationStarted(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = false;
        prevPos = eventData.CumulativeDelta;

        // これが無いとオブジェクトにフォーカス時しか操作ができない
        InputManager.Instance.PushModalInputHandler(gameObject);
    }

    public void OnManipulationUpdated(ManipulationEventData eventData)
    {
        Vector3 moveVector = Vector3.zero;
        moveVector = eventData.CumulativeDelta - prevPos;

        prevPos = eventData.CumulativeDelta;

        // 手の位置が取得できないので決め打ちで40cmに
        var handDistance = 0.4f;
        var objectDistance = Vector3.Distance(Camera.main.transform.position, gameObject.transform.position);

        gameObject.transform.position += (moveVector * (objectDistance / handDistance));
    }
}

移動のロジックは、InteractionManagerの方とあまり変わりはありませんが
IManipulationHandlerだと元記事にもありますが手の位置の取得方法が不明です。
私もいろいろと試しましたが今のところ分かっていません。
下記を実行するとfalseが返ってくるので取得できる望みは低いのはないかと思います。

eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.Position)

OnManipulationUpdatedメソッドは、InteractionManager.SourceUpdatedイベントと違い
フォーカスが外れるとイベントが飛びません。
それを回避するためにInputManager.Instance.PushModalInputHandlerメソッドに
gameObjectを渡しています。


今回、作成したものはこんな感じで動きます。

HoloLensでオブジェクトを移動する
動画では分かりづらいのですが、
緑のオブジェクト(InteractionManagerを使った方)の方が
手の移動とオブジェクトの移動がしっくりきます。



HoloToolkitを利用してオブジェクトを移動するなら
IManipulationHandlerを実装する方法でやりたいところですが
手の位置の取得方法が分からないのでうーんって感じです。
(やり方分かる方は、こそっと教えてほしいです)
今回は以上になります。次もHoloLensネタになるかと思います。


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp