2013年8月4日日曜日

LEAP MOTION + openFrameworks 手のモデル改良


前回のエントリで掲載した、openFrameworkでの手モデルの描画方法を改良しました。


上図の通り、LEAP MOTIONは掌の以下の情報を取得する事が出来ます。


 palmPosition
 掌の中心部のXYZ座標を表しています。

 palmNormal
 掌のLEAP MOTIONの水平面に対する角度(ベクトル)を表しています。

 direction
 掌が指す先の角度を表しています。


しかし、前回のエントリで掲載したコードでは、掌の状態のうちdirectionの角度が正しく反映出来ていませんでした。


具体的に間違っている例を御覧ください。
 ↓

この例では、ピッチ(X軸)とロール(Z軸)の回転は正しく反映出来ているのですが、ヨー(Y軸)の回転が出来ていませんでした。そのため手の指先を左方向に向けると、掌の立方体が回転せずに指だけが左方向に移動しています。


これに対して、改良した例がこちらです。
 ↓

こちらではヨー(Y軸)の回転も正しく表現出来ています。

コードの変更点は116〜118行目のハイライトされている箇所です。directionからY軸の角度だけを取り出して、更に回転させる処理を追加しています。


#include "testApp.h"
#include "Poco/Mutex.h"

//--------------------------------------------------------------
void testApp::setup(){
    // カメラの初期位置と方向を指定
    camdistance = ofGetWidth()/4;
    camdegree = 90;
    camera.setFov(60);
    camera.setPosition(0, 200, camdistance);
    camera.lookAt(ofVec3f(0, 200, 0));
}

//--------------------------------------------------------------
void testApp::update(){
}

//--------------------------------------------------------------
void testApp::draw(){
    
    camera.begin();
    // 背景を黒に塗りつぶし
    ofBackground(0, 0, 0);
    
    // フレームを取得
    Frame frame = controller.frame();
    // Handをあるだけ列挙
    for(int i=0; i<frame.hands().count(); i++) {
        Hand hand = frame.hands()[i];
        // Hand内のFingerをあるだけ描画
        for(int j=0; j<hand.fingers().count(); j++) {
            Finger finger = frame.fingers()[j];
            drawFinger(finger);
        }
        // Handを描画
        drawPalm(hand);
    }
    camera.end();
}

void testApp::drawFinger(Finger finger) {

    // 指先の点を描画
    ofPoint tip(finger.tipPosition().x, finger.tipPosition().y, finger.tipPosition().z);
    drawPoint(tip);

    // 指の付け根の座標を計算
    ofPoint base = ofPoint(tip.x + finger.direction().x * finger.length() * -1,
                           tip.y + finger.direction().y * finger.length() * -1,
                           tip.z + finger.direction().z * finger.length() * -1);
    // 指の付け根を描画
    drawPoint(base);
    
    // 指先から付け根に線を描く
    ofLine(tip.x, tip.y, tip.z, base.x, base.y, base.z);

    // 付け根から掌に線を描く
    ofLine(base.x, base.y, base.z,
           finger.hand().palmPosition().x,
           finger.hand().palmPosition().y,
           finger.hand().palmPosition().z);
    
    // 指の箱を描画
    drawFingerBox(finger, tip, base);
}

// 点を描画
void testApp::drawPoint(ofPoint point) {

    ofPushMatrix();
    ofTranslate(point);
    ofNoFill();
    ofSetColor(0xFF, 0xFF, 0xFF, 255);
    ofSphere(3);
    ofPopMatrix();
}

// 指の箱を描画
void testApp::drawFingerBox(Finger finger, ofPoint tip, ofPoint base) {
    
    // 指の中間の座標
    ofPoint middle = base.middle(tip);
    ofPushMatrix();
    ofTranslate(middle);
    
    // 指の方向に従い回転
    ofQuaternion quat;
    quat.makeRotate(ofPoint(0, -1, 0), ofPoint(finger.direction().x, finger.direction().y, finger.direction().z));
    ofMatrix4x4 matrix;
    quat.get(matrix);
    glMultMatrixf(matrix.getPtr());
    
    ofNoFill();
    ofSetColor(0xCC,0,0,255);
    ofScale(1, finger.length()/10, 1);
    ofBox(10);
    ofPopMatrix();
}

void testApp::drawPalm(Hand hand) {
    // 掌の描画処理
    
    ofPoint point = ofPoint(hand.palmPosition().x, hand.palmPosition().y, hand.palmPosition().z);
    drawPoint(point);
    
    ofPushMatrix();
    ofTranslate(point);
    
    // 掌を回転
    ofQuaternion quat;
    quat.makeRotate(ofPoint(0, -1, 0), ofPoint(hand.palmNormal().x, hand.palmNormal().y, hand.palmNormal().z));
    ofMatrix4x4 matrix;
    quat.get(matrix);
    glMultMatrixf(matrix.getPtr());

    // directionを反映する為に、Y軸の角度を取り出す
    float rotationY = ofRadToDeg(atan2(hand.direction().x, hand.direction().z)) + 180;
    ofRotateY(rotationY);
    
    ofNoFill();
    ofSetColor(0xCC,0x0,0x0,255);
    ofScale(1, 0.25, 1.0);
    ofBox(0, 0, 0, 60);

    ofPopMatrix();
}

//--------------------------------------------------------------
void testApp::keyPressed(int key){

    switch (key) {
        case 358: // 右
            camdegree += 5;
            if(camdegree > 360) camdegree = 0;
            break;
        case 356: // 左
            camdegree -= 5;
            if(camdegree < 0) camdegree = 360;
            break;
        case 357: // 上
            camdistance -= 10;
            break;
        case 359: // 下
            camdistance += 10;
            break;
        default:
            break;
    }
 
    float radian = ofDegToRad(camdegree);
    float x = camdistance * cos(radian);
    float z = camdistance * sin(radian);

    camera.setPosition(x, camera.getY(), z);
    camera.lookAt(ofVec3f(0, 200, 0));
}

//--------------------------------------------------------------
void testApp::keyReleased(int key){
}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){
}

//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){
}

//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){
}


そもそも、positionNormal()とdirection()の2つのベクトルからクォータニオンを求める事が出来れば1度の処理で済みそうなのですが、残念ながらその計算方法が判りませんでした。
どなたか、もっとスマートな方法がありましたら教えて下さい。m(_  _)m



LEAP MOTION関連記事:
LEAP MOTION + openFrameworksでアプリケーション開発
LEAP MOTION + openFrameworks 手のモデル解説
・LEAP MOTION + openFrameworks 手のモデル改良