LeapMotionによる仮想マウス

格安でLeapMotionが手元に届いたので、何か作ってみようと思い、仮想マウスを作りました。

LeapMotionの上で指を動かすとポインタが動き、クリックもできるようにしました。また、Macにおける画面遷移もできるようにしました。画面遷移ができるとカッコイイよね！

# ソースコードの在り処

コードはgithubにあります。コード量は少なめです。　</br>
https://github.com/slowsingle/leapmotion_controller/tree/master

ReadMeにインストール方法が記載されていますので、お使いになる場合は是非ごらんください。

# プログラム

リープモーションから取得した人差し指の座標(X, Y, Z)から、PCのディスプレイ上の座標系に変換して、その座標にポインタを移動させます。

![01.gif](https://qiita-image-store.s3.amazonaws.com/0/72509/639b4fde-4264-5734-8b89-6692b17e3a12.gif)

```cpp:座標系の変換
new_x = (x + 120.0) / 240.0 * 1440.0;
if (new_x < 0.0) {
    new_x = 0.0;
} else if (new_x > 1440.0 - 1.0) {
    new_x = 1440.0 - 1.0;
}
new_y = (250.0 - y) / 150.0 * 900.0;
if (new_y < 0.0) {
    new_y = 0.0;
} else if (new_y > 900.0 - 1.0) {
    new_y = 900.0 - 1.0;
}
```

touchDistance()を用いることでタッチの奥行きを測ることができます。

![03.gif](https://qiita-image-store.s3.amazonaws.com/0/72509/1b9a56df-eb7f-5dca-a9ff-224d8361f31d.gif)

一定のしきい値（以下のコードの場合は-0.1）を下回ると、押されたと判定し、そこから先ほどとは異なる一定のしきい値（以下のコードの場合は0.0）を上回ると離されたと判定し、タッチされたと判定します。

```cpp:タッチ認識
// タッチの認識
if (isPressed) {
    if (pointable.touchDistance() > 0.0) {
        isPressed = false;
        PyObject_CallMethod(pModule, "touch", "(dd)", touch_x, touch_y);
        std::cout << "===== PRESS_OK =====" << std::endl;
    }
} else {
    if (pointable.touchDistance() < -0.1) {
        isPressed = true;
        touch_x = new_x;
        touch_y = new_y;
        std::cout << "***** PRESS_Prepare *****" << std::endl;
    }
}
```

スワイプのジェスチャー認識は以下の通りです。

```cpp:スワイプのジェスチャー認識
Leap::GestureList gestures = frame.gestures();

// スワイプのジェスチャーを認識する
if (swipe_counter == 0) {
    for (int g = 0; g < gestures.count(); g++) {
        gesture = gestures[g];

        if (gesture.type() == Leap::Gesture::TYPE_SWIPE) {
            Leap::SwipeGesture swipe = gesture;
            if (swipe.direction().x < -0.7) {
                std::cout << swipe.direction().x << std::endl;
                PyObject_CallMethod(pModule, "swipe_screen", "(i)", 0);
                // スワイプを認識したら関数を抜ける
                isPressed = false;
                swipe_counter = 100;
                return;
            } else if (swipe.direction().x > 0.7) {
                std::cout << swipe.direction().x << std::endl;
                PyObject_CallMethod(pModule, "swipe_screen", "(i)", 1);
                // スワイプを認識したら関数を抜ける
                isPressed = false;
                swipe_counter = 100;
                return;
            }
        }
    }
}
```

リープモーションによる認識はC++で実装し、PCに対してポインタを移動させたりスワイプさせたりするのはPythonで行わせています。未確認ですが、これでOSに非依存な仮想マウスができたかと思います。

C++からPythonを呼び出すのには`PyObject_CallMethod()`を使用しています。</br>
この関数を呼ぶ前に初期化が必要になります。おまじない的な`Py_Initialize()`を呼び出し、その後（必要があれば）パスを通し、モジュールのインポートを行います。

```cpp:C++からPythonを呼び出すための初期化処理
bool SampleListener::initializeForPython() {
    Py_Initialize();
    // カレントディレクトリのパスを通す
    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(path, PyString_FromString("."));

    // カレントディレクトリにあるpythonモジュールをインポートする
    pModule = PyImport_ImportModule("script");

    if (pModule != NULL) {
        return true;
    } else {
        return false;
    }
}
```

なお、`PyObject_CallMethod()`の引数は、1つ目がインポートしたモジュールのポインタ、2つ目が呼び出す関数名、3つ目がこれから呼び出すpython内の関数における引数の数と型（(i)ならint型の引数が1つで、(dd)ならdouble型の引数が2つ）を表し、4つ目がその引数の具体的な値となります。

`PyObject_CallMethod(pModule, "swipe_screen", "(i)", 1)`ならば、pModule（インポートしたモジュール）の`swipe_screen`関数に、引数としてint型の引数1を渡して、関数を実行することになります。

# 終わりに

やる前から何となく覚悟してたけどタッチしづらいです。　　</br>
ただ、画面遷移が手の動きでできるとカッコイイのは確かです。未来感を感じます。