Leap Motion の Gesture からコマンドを決める

Leap Motion SDK の Sample.m には

- (void)onFrame:(NSNotification *)notification;

なんてメソッドがあって、こいつがジェスチャーの判断から、手や指の動きの状態まで事細かに取得してくれている。
で、今回はこの中のジェスチャーの LEAP_GESTURE_TYPE_CIRCLE と LEAP_GESTURE_TYPE_KEY_TAP で、iTunes を動かす動作にしてみた。


コードはこんな感じ。

- (void)onFrame:(NSNotification *)notification
{
    LeapController *aController = (LeapController *)[notification object];

    // Get the most recent frame and report some basic information
    LeapFrame *frame = [aController frame:0];

    int fingerCount;

    if ([[frame hands] count] != 0) {
        // Get the first hand
        LeapHand *hand = [[frame hands] objectAtIndex:0];

        // Check if the hand has any fingers
        NSArray *fingers = [hand fingers];
        fingerCount = [fingers count];
        if ([fingers count] != 0) {
            // Calculate the hand's average finger tip position
            LeapVector *avgPos = [[LeapVector alloc] init];
            for (int i = 0; i < [fingers count]; i++) {
                LeapFinger *finger = [fingers objectAtIndex:i];
                avgPos = [avgPos plus:[finger tipPosition]];
            }
            avgPos = [avgPos divide:[fingers count]];
        }

        // Get the hand's normal vector and direction
        const LeapVector *normal = [hand palmNormal];
        const LeapVector *direction = [hand direction];
    }

    NSArray *gestures = [frame gestures:nil];
    for (int g = 0; g < [gestures count]; g++) {
        LeapGesture *gesture = [gestures objectAtIndex:g];
        switch (gesture.type) {
            case LEAP_GESTURE_TYPE_CIRCLE: {
                LeapCircleGesture *circleGesture = (LeapCircleGesture *)gesture;

                NSString *clockwiseness;
                BOOL isClockwise = YES;
                if ([[[circleGesture pointable] direction] angleTo:[circleGesture normal]] <= LEAP_PI/4) {
                    clockwiseness = @"clockwise";
                } else {
                    clockwiseness = @"counterclockwise";
                    isClockwise = NO;
                }

                // Calculate the angle swept since the last frame
                float sweptAngle = 0;
                if(circleGesture.state != LEAP_GESTURE_STATE_START) {
                    LeapCircleGesture *previousUpdate = (LeapCircleGesture *)[[aController frame:1] gesture:gesture.id];
                    sweptAngle = (circleGesture.progress - previousUpdate.progress) * 2 * LEAP_PI;
                }

                if (circleGesture.state == LEAP_GESTURE_STATE_START || circleGesture.state == LEAP_GESTURE_STATE_STOP) {
                NSLog(@"Circle id: %d, %@, progress: %f, radius %f, angle: %f degrees %@",
                      circleGesture.id, [Sample stringForState:gesture.state],
                      circleGesture.progress, circleGesture.radius,
                      sweptAngle * LEAP_RAD_TO_DEG, clockwiseness);
                }

                if (circleGesture.state == LEAP_GESTURE_STATE_STOP) {
                    if (isClockwise) {
                        [self fwd];
                    }
                    else {
                        [self rwd];
                    }
                }
                break;
            }
            case LEAP_GESTURE_TYPE_SWIPE: {
                LeapSwipeGesture *swipeGesture = (LeapSwipeGesture *)gesture;
                break;
            }
            case LEAP_GESTURE_TYPE_KEY_TAP: {
                LeapKeyTapGesture *keyTapGesture = (LeapKeyTapGesture *)gesture;
                if (keyTapGesture.state == LEAP_GESTURE_STATE_STOP) {
                    if (fingerCount>1) {
                        [self playerPause];
                    }
                    else {
                        [self playerPlay];
                    }
                }
                break;
            }
            case LEAP_GESTURE_TYPE_SCREEN_TAP: {
                LeapScreenTapGesture *screenTapGesture = (LeapScreenTapGesture *)gesture;
                break;
            }
            default:
                NSLog(@"Unknown gesture type");
                break;
        }
    }
}

NSLog を削除したけど長いな…。


int fingerCount; は、1本指かそれ以上の指かを判断するのに用意した。
手が 2つ以上検知されている場合は、最初の手の指の数になっちゃうけど、ま、今回はとりあえずなので他の手は無視。


指を回すジェスチャー(LEAP_GESTURE_TYPE_CIRCLE)はちゃんと「時計回り」か「半時計回り」かを確認してくれている。
なので、それを BOOL isClockwise; として確保して、「次の曲に移動」か「前の曲に戻る」かを判断している。


キーを叩くジェスチャー(LEAP_GESTURE_TYPE_KEY_TAP)そのものには「何本指か?」という情報はないので、先にゲットしておいた fingerCount で1本かそれ以上かを判断して「曲をプレイする」か「曲をポーズする」を決めている。
やろうと思えば、3本指なら「曲の始めに戻る」とかも簡単。


ざっと Leam Motion の動作からコマンドにする部分はこんな感じ。
基本的にジェスチャーの state がちゃんとジェスチャーの終了を教えてくれる(LEAP_GESTURE_STATE_END)ので、終了したらどんなジェスチャーだったのかを確認してコマンドを飛ばすだけで良い。
楽チン!