2010年12月21日火曜日

[Kinectハック]OpenNI+Flashでマルチユーザー・マルチタッチ Flashで実装編



[Kinectハック]OpenNI+Flashでマルチユーザー・マルチタッチ OpenNI解析編 の続き。


fukusinさんがopenNIのサンプルを解析/改造してくれたおかげで、Kinectの信号をXMLSocketを利用してFlashでも受け取れるようになりました。現時点では各ユーザーの両手のみの検出になっていますが、信号的には頭、首、肩…と、ボーンを表示するのに必要な頂点は全て取れているようです。

さて、今回は「Flashで実装編」として、Kinectから送られてきた信号を解析してFlashに組み込んでいきます。今回のデモでは、各ユーザーの両手が識別できるためマルチタッチのような操作が可能になりますが、正確には「マルチ」ですが「タッチ」ではないですね。空中操作になりますから。「マルチポインタ」と言った所でしょうか。重箱の隅をつつくみたいですが、これ、重要なんです。



この場合、UI的にマルチタッチと全く同じ作法にするのは問題があるのです。タッチ操作であれば画面上のオブジェクトに触れる前にどこに触れるか分かりますが、空中操作、しかもKinectのように画面から離れて操作するものの場合は、実際に手を出してみないとどこにカーソルが表示されるか分かりにくいですよね。

普通タッチパネルでは触った=マウスダウンと同じ操作になり、ロールオーバーとダウン操作を共存させる事が出来ないのですが、前述の理由からKinectの場合はそれが必要になってきます。本家Microsoftは長押しの様な動作で決定(ダウン)操作としていますが、幸いにしてKinectは各ポイントのz値も取れるので、今回は各ポイントのz値がある閾値を下回ったら(手が前に突き出されたら)ダウン状態、閾値を越えたら(手が引っ込んだら)アップ状態として扱う事にします。

今回のデモでは、自前の汎用マルチポインタライブラリを使用しています。ポインタが「認識」「ダウン」「移動」「アップ」「消失」した状態をそれぞれ持つ事が出来、さらにポインタの下にある各DisplayObjectがイベントを発行するように設計したため、MouseEventと同じ様に使う事が出来ます。また、Kinectのような特殊なシステムでない、普通のマルチタッチにも対応できるようになっています。以前のエントリにある「自作マルチタッチシステムを作ってみた」でも、このクラスを使用してマルチタッチを実装しました。

まず、コンストラクタでマルチタッチの用意をし、XMLSocketコネクトのための準備をします。

//==========================
//コンストラクタ
//==========================
public function Main():void{
//
mp=new MultiPointer(-1 , false);
mp.simulate=true;
mp.registerDefaultCursor(HandCursor);
addChild(mp);
//
connect_btn.addEventListener(MouseEvent.CLICK , connectStart);
//
}


マルチタッチに用いるクラスは「MultiPointer」クラスです。汎用クラスとして色々出来るように作ってあるのですが、ここでは詳しい説明は割愛します。
詳細はGoogle Codeにアップしたドキュメントをご覧下さい。

//==========================
//接続開始
//==========================
private function connectStart(e:MouseEvent):void{
//
socket=new XMLSocket();
socket.addEventListener(Event.CONNECT , connectComplete);
socket.addEventListener(DataEvent.DATA , receiveData);
socket.connect(ip.text , int(port.text));
//
}
//
//
//==========================
//接続完了
//==========================
private function connectComplete(e:Event):void{
//接続のためダミーデータを送信(サーバー仕様による)
socket.send("test");
}


ステージ上にあるボタンを押したら接続を開始し、接続が完了したらサーバーにデータを送信します。これは今回作成したサーバー側の仕様で、一旦双方向に通信しないと接続が確立出来ないようになっているためです。

//==========================
//データ受信
//==========================
private function receiveData(e:DataEvent):void{
//
//データ解析
var data:Array=e.data.split(",");
var id:int=data[0];
var parts:int=int(data[1]);
var x:Number=Number(data[2]);
var y:Number=Number(data[3]);
var z:Number=Number(data[4]);
//
//一意のIDを作成
var pointer_id:int=int(parts.toString()+id.toString());
//
//ポインタ認識
mp.detect(x,y,pointer_id);
//ポインタを動かす
mp.move(x,y,pointer_id);
if(z<1500){
mp.down(x,y,pointer_id);
}else{
mp.up(x,y,pointer_id);
}
//
}


データを受信したら、受信したデータを解析してMultiPointerクラスに渡します。 今回、データは「,」区切りの文字列として送信されてくるので、splitで分割し、それぞれの変数に入れていきます。 サーバーから送られてくるデータの中身は

ユーザーID , パーツ名(左手9/右手15) , x座標 , y座標 , z座標

というフォーマットになっています。
このデータを受け、ポインタ生成部分は以下のようになっています。

//一意のIDを作成
var pointer_id:int=int(parts.toString()+id.toString());
//
//ポインタ認識
mp.detect(x,y,pointer_id);
//ポインタを動かす
mp.move(x,y,pointer_id);
if(z<1500){
mp.down(x,y,pointer_id);
}else{
mp.up(x,y,pointer_id);
}


MultiPointer内に以下のメソッドがあり、それぞれ

・ポインタ認識 - detect()
・ポインタ移動 - move()
・ポインタ押下 - down()
・ポインタ押上 - up()

そしてここでは出てきていないですが、

・ポインタ消失 - lost()

となっています。

各関数にはx , y , idの引数を渡す必要があり、ここではマルチユーザーに対応するためパーツのIDとユーザーIDを一旦String形式で結合し、intに変換する事で一意のIDを生成しています。

押下/押上の判定については、z値を使用しています。暫定的に閾値を1500と設定し、手の前後操作でdown/upをそれぞれ実行するようにしています。
肝心のマルチタッチを受けてオブジェクトを操作する部分ですが、これは良く使う操作ですので、基本クラスとして用意しました。 ステージ上のオブジェクトの基本クラスに「PointerHandlingObject」というクラスを設定しています。このクラスは、マルチポイントによる操作と言えば王道な移動、拡縮、回転をサポートしています。

これで、Kinectと接続してFlash上のオブジェクトを操作する簡単なデモが出来ました。
Flashのソース: KinectFlash_sdtech.zip

今回使用した「MultiPointer」クラスは「自作マルチタッチシステムを作ってみた」でも使用しているのですが、その解説はまた今度ということで。

0 件のコメント:

コメントを投稿