タグ別アーカイブ: LeapMotion

Processing #001 LeapMotion x Box2D

Processing #001 LeapMotion x Box2D from Takepepe on Vimeo.

ご無沙汰してます、Takepepeです。
世間は梅雨、ついにクーラーをかけながらデモムービーを撮る季節に突入しました。
すっかり月1更新になってしまった今日このごろ。反省しないと!

さて、今日は動画のとおり、Leapを使ってお絵描きするインスタレーションをProcessingで作成しました。
今回のキモはLeapSDKをラップしているライブラリから提供される
標準のジェスチャーや座標を、意図した値に変換するところです。

使用しているライブラリは以下

コードはEclipseで書いていますので、このままProcessingのIDEにはっつけても動かないのでご注意を。
OPENGLの恩恵を受けていないコードですが、レンダリングモードをOPENGLにしています。
EclipseでProcessingを書く場合、OPENGLの使用にはcore.jarだけではなく
他にも入れないといけない.jarがありますので適当にググってください。

いつもの様に、順をおって解説していきます。

  1. LeapPointerP5の作成
  2. Box2Dの初期設定
  3. LeapPointerP5の座標をBox2Dに落とし込む
  4. 両手の状態で描画判定

1.LeapPointerP5の作成

P001っていうのはPAppletを継承したやつです。
コンストラクタに引数として渡して保持しておけば、
Processingの便利APIを外からでも使用できます。

update()は、そのP001からdraw()内で呼ばれるループ関数です。
このクラスは、冒頭で述べたキモにあたる部分を担っています。

執筆時現在の開発環境での話ですが、
前回解説したとおり、LeapはFrameオブジェクトからpointable(指か棒)を取得できますが、
これらをロストした時のカバーを自分でやらなければいけません。

今回のインスタレーションでは、ポインターを1つに限定しています。
getPointable()でそのポインターを固定する処理をして、
ポインターが属する手と反対側の手が「グー」なら「つかんでいる」状態というのを
publicなisGrabbed()から参照出来るようになっています。

LeapPointerP5.java


import com.leapmotion.leap.Frame;
import com.leapmotion.leap.Pointable;
import com.leapmotion.leap.Hand;
import com.onformative.leap.LeapMotionP5;

public class LeapPointerP5 {
	
	private P001 p;
	
	private LeapMotionP5 leap;
	private Frame frame;
	private Pointable pointable;
	private Hand currentHand;
	private Hand contraryHand;
	
	private boolean isDoubleHand = false;
	private boolean isGrabbing = false;
	private float offset = 6.0f;
	private float x;
	private float y;
	
	public LeapPointerP5(P001 p,LeapMotionP5 leap){
		this.p = p;
		this.leap = leap;
		this.pointable = leap.getFrame().pointables().get(0);
	}

	//------------------------------------------------------------
	// @ Loop
	
	public void update(){
		
		frame = leap.getFrame();
		pointable = getPointable();
		setHands();
		setPosition();

	}
	
	//------------------------------------------------------------
	// @ private 
	
	private Pointable getPointable() {
		
		// 前のフレームで取得したPointableがある場合、前フレームで取得したPointableを返す。無ければ新しいPointableを返す。
		
		Pointable _pointable = frame.pointables().get(0);
		int count = frame.pointables().count();
		for(int i=0 ; i<count ; i++){
			if(pointable.id() == frame.pointables().get(i).id()){
				_pointable = frame.pointables().get(i);
			}
		}
		return _pointable;
	}
	
	private void setHands(){
		
		// pointableの手を格納。手を2つ取得できた場合、反対側の手を格納
		
		if(pointable.isFinger()){
			currentHand = pointable.hand();
			if(frame.hands().count() == 2){
				contraryHand = frame.hands().get(1);
				if(currentHand == contraryHand){
					contraryHand = frame.hands().get(0);
				}
				isDoubleHand = true;
			}else{
				contraryHand = null;
				isDoubleHand = false;
			}
		}
	}
	
	private void setPosition(){
		
		// pointableから、pointerの座標を算出
		
		float px = pointable.tipPosition().getX()*offset;
		float py = pointable.tipPosition().getY()*offset;
		this.x += ((p.width/2 + px) - this.x)/10;
		this.y += ((p.height/0.6 - py) - this.y)/10;
	}
	
	
	//------------------------------------------------------------
	// @ getter
	
	public float getX(){
		return this.x;
	}
	
	public float getY(){
		return this.y;
	}
	
	public boolean isPointed(){
		boolean res = false;
		if(frame.pointables().count() != 0){
			res = true;
		}
		return res;
	}
	
	public boolean isGrabbed(){
		
		// 描画している手と反対側の手が存在しつつ、その手がグーの時trueを返す。
		
		boolean res = false;
		if(isDoubleHand){
			if(!isGrabbing){
				if(contraryHand.fingers().count() == 0){
					res = true;
					isGrabbing = true;
				}
			}else{
				res = true;
				if(contraryHand.fingers().count() == 5){
					res = false;
					isGrabbing = false;
				}
			}
		}
		return res;
	}
	
}

2.Box2Dの初期設定

ここからはPAppletのコードです。
Box2Dはあんまり新しくはなく、日本語のドキュメントがかなり少ないため苦戦しました。
Physicsという、ProcessingでBox2Dを使用するためのインスタンスを生成し、
それに対して、jbox2dのクラス群を利用していく感じになります。
createPointerBody()ではLeapPointerP5で定義した座標をBox2D上に落とし込むためのインスタンスを生成しています。

P001.java(抜粋)

	private void createPysics(){
		float gravX = 0.0f;
		float gravY = -100.0f;
		float AABBWidth = 2*width;
		float AABBHeight = 2*height;
		float borderBoxWidth = width+11;
		float borderBoxHeight = height+11;
		physics = new Physics(this, width, height, gravX, gravY, AABBWidth, AABBHeight, borderBoxWidth, borderBoxHeight, pixelsPerMeter);
		physics.setDensity(110.1f);
		physics.setCustomRenderingMethod(this, "myCustomRenderer");
	}
	
	private void createPointerBody(){
		
		// LeapPointerP5の座標を用いたpointerBodyを生成。
		
		pointerDef = new BodyDef();
		pointerBody = physics.getWorld().createBody(pointerDef);
		pointerCircleDef = new CircleDef();
		pointerCircleDef.radius = 0.5f;
		pointerCircleDef.friction = physics.getFriction();
		pointerCircleDef.restitution = physics.getRestitution();
		pointerCircleDef.isSensor = physics.getSensor();
		pointerBody.createShape(pointerCircleDef);
		pointerBody.setMassFromShapes();
		updatePointerBody();
	}
	

3.LeapPointerP5の座標をBox2Dに落とし込む

以下のupdatePointerBody()はdraw()内でコールされている関数です。
getColor()は、フレームにあわせて虹色を返してくれる関数です。

P001.java(抜粋)

	private void updatePointerBody(){
		
		// 描画中は描画が始まった段階のpalletColorを使用。描画中でない時は随時描画色を更新。
		
		UserData data = new UserData();
		if(isDrawing){
			data.color = palletColor;
		}else{
			data.color = getColor();
		}
		pointerBody.setUserData(data);
		
		// Box2Dの座標系に変換しつつpointerBodyを移動。
		
		float x = (pointer.getX() - width/2)/pixelsPerMeter;
		float y = (-pointer.getY() + height/2)/pixelsPerMeter;
		pointerBody.setPosition(new Vec2(x,y));
		
	}

4.両手の状態で描画判定

LeapPointerP5で反対の手が「グー」と判定された場合、描画中になります。
まず、verticesというArrayListが生成され、
「グー」の間、ポインターの座標をどんどんverticesにつっこんでいき、
「パー」になった時、もしくは反対の手がなくなった時、
描画が終了し、verticesを使用したCircleが新しいbodyとして追加されます。

P001.java(抜粋)

	private void brushBody(){
		if(isDrawing != pointer.isGrabbed()){
			if(isDrawing){
				onEndDrawing();
			}else{
				onStartDrawing();
			}
		}
		if(isDrawing == pointer.isGrabbed() && isDrawing){
			onProcessDrawing();
		}

		// 描画中であれば、ProcessingのAPIでラインを描く。( verticesの数だけellipseを描く )
		
		if(isDrawing){
			this.fill(palletColor);
			for(int i=0 ; i<vertices.size() ; i++){
				this.ellipse(vertices.get(i).x*pixelsPerMeter+width/2, vertices.get(i).y*-pixelsPerMeter+height/2, 30 ,30);
			}
		}
	}
	
	private void onStartDrawing(){
		
		// 描画が開始した時の処理。 vertices に格納した座標を元に Box2D で body を生成。
		
		isDrawing = true;
		vertices = new ArrayList<Vec2>();
		palletColor = getColor();
	}
	
	private void onProcessDrawing(){
		
		// 描画中の処理。 vertices に格納した座標を格納。
		
		Vec2 point = physics.screenToWorld(pointer.getX(),pointer.getY());
		vertices.add(point);
	}
	
	private void onEndDrawing(){
		
		// 描画が終了した時の処理。verticesに格納した座標を元にBox2Dでbodyを生成。
		
		isDrawing = false;
		BodyDef bd = new BodyDef();
		Body body = physics.getWorld().createBody(bd);
		for(int i =0 ; i<vertices.size() ; i++){
			CircleDef cd = new CircleDef();
			cd.radius = 0.5f;
			cd.density = physics.getDensity();
			cd.friction = physics.getFriction();
			cd.restitution = physics.getRestitution();
			cd.isSensor = physics.getSensor();
			cd.localPosition.set(vertices.get(i));
			body.createShape(cd);
			body.setMassFromShapes();
		}
		
		// 描画したBodyに色を持たせる。必要なメンバはUserDataクラスで定義しておく。
		
		UserData data = new UserData();
		data.color = palletColor;
		body.setUserData(data);
		vertices = null;
	}

P001.java

import java.util.List;
import java.util.ArrayList;

import processing.core.PApplet;

import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.collision.CircleShape;
import org.jbox2d.collision.CircleDef;
import org.jbox2d.collision.Shape;
import org.jbox2d.collision.ShapeType;
import org.jbox2d.p5.Physics;

import com.onformative.leap.LeapMotionP5;

public class P001 extends PApplet{
	private static final long serialVersionUID = 0;
	
	// @ Box2D (JBox2D + BoxWrap2d)
	// JBox2D : http://www.jbox2d.org/
	// BoxWrap2d : http://wiki.processing.org/w/BoxWrap2d
	private Physics physics;
	private List<Vec2> vertices;
	private float pixelsPerMeter = 30;
	private BodyDef pointerDef;
	private Body pointerBody;
	private CircleDef pointerCircleDef;
	
	//  LeapMotionP5
	//  https://github.com/mrzl/LeapMotionP5
	//  auther @ mrzl
	private LeapMotionP5 leap;
	
	//  LeapPointerP5
	//  auther @ Takeppe
	private LeapPointerP5 pointer;
	
	private int palletColor;
	private boolean isDrawing = false;
	
	//------------------------------------------------------------
	// @ Setup
		
	public void setup(){
		size(2560,1390,OPENGL);
		frameRate(60);
		colorMode(HSB, 360, 100, 100);
		noStroke();
		
		leap = new LeapMotionP5(this);
		pointer = new LeapPointerP5(this,leap);
		
		createPysics();
		createPointerBody();
	}
	
	//------------------------------------------------------------
	// @ Box2D 
	
	private void createPysics(){
		float gravX = 0.0f;
		float gravY = -100.0f;
		float AABBWidth = 2*width;
		float AABBHeight = 2*height;
		float borderBoxWidth = width+11;
		float borderBoxHeight = height+11;
		physics = new Physics(this, width, height, gravX, gravY, AABBWidth, AABBHeight, borderBoxWidth, borderBoxHeight, pixelsPerMeter);
		physics.setDensity(110.1f);
		physics.setCustomRenderingMethod(this, "myCustomRenderer");
	}
	
	private void createPointerBody(){
		
		// LeapPointerP5の座標を用いたpointerBodyを生成。
		
		pointerDef = new BodyDef();
		pointerBody = physics.getWorld().createBody(pointerDef);
		pointerCircleDef = new CircleDef();
		pointerCircleDef.radius = 0.5f;
		pointerCircleDef.friction = physics.getFriction();
		pointerCircleDef.restitution = physics.getRestitution();
		pointerCircleDef.isSensor = physics.getSensor();
		pointerBody.createShape(pointerCircleDef);
		pointerBody.setMassFromShapes();
		updatePointerBody();
	}
	
	public void myCustomRenderer(World world){
		
		// BoxWrap2d のカスタムレンダラーを使用。参照は以下。
		// http://wiki.processing.org/w/BoxWrap2d#Using_a_custom_renderer
		
		Body body;
		for (body = world.getBodyList(); body != null; body = body.getNext()) {
			Shape shape;
			for (shape = body.getShapeList(); shape != null; shape = shape.getNext()) {
				ShapeType st = shape.getType();
				if (st == ShapeType.POLYGON_SHAPE) {
				}else if (st == ShapeType.CIRCLE_SHAPE) {
					UserData data = (UserData)shape.getBody().getUserData();
					if(data != null){
						fill(data.color);
					}
					CircleShape circle = (CircleShape) shape;
					Vec2 pos = physics.worldToScreen(body.getWorldPoint(circle.getLocalPosition()));
					float radius = physics.worldToScreen(circle.getRadius());
					this.ellipseMode(CENTER);
					this.ellipse(pos.x, pos.y, radius*2, radius*2);
				}
			}
		}
	}
	
	//------------------------------------------------------------
	// @ Loop
	
	@Override
	public void draw() {
		this.background(0);
		pointer.update();
		updatePointerBody();
		brushBody();
	}
	
	private void updatePointerBody(){
		
		// 描画中は描画が始まった段階のpalletColorを使用。描画中でない時は随時描画色を更新。
		
		UserData data = new UserData();
		if(isDrawing){
			data.color = palletColor;
		}else{
			data.color = getColor();
		}
		pointerBody.setUserData(data);
		
		// Box2Dの座標系に変換しつつpointerBodyを移動。
		
		float x = (pointer.getX() - width/2)/pixelsPerMeter;
		float y = (-pointer.getY() + height/2)/pixelsPerMeter;
		pointerBody.setPosition(new Vec2(x,y));
		
	}
	
	private void brushBody(){
		if(isDrawing != pointer.isGrabbed()){
			if(isDrawing){
				onEndDrawing();
			}else{
				onStartDrawing();
			}
		}
		if(isDrawing == pointer.isGrabbed() && isDrawing){
			onProcessDrawing();
		}

		// 描画中であれば、ProcessingのAPIでラインを描く。( verticesの数だけellipseを描く )
		
		if(isDrawing){
			this.fill(palletColor);
			for(int i=0 ; i<vertices.size() ; i++){
				this.ellipse(vertices.get(i).x*pixelsPerMeter+width/2, vertices.get(i).y*-pixelsPerMeter+height/2, 30 ,30);
			}
		}
	}
	
	private void onStartDrawing(){
		
		// 描画が開始した時の処理。 vertices に格納した座標を元に Box2D で body を生成。
		
		isDrawing = true;
		vertices = new ArrayList<Vec2>();
		palletColor = getColor();
	}
	
	private void onProcessDrawing(){
		
		// 描画中の処理。 vertices に格納した座標を格納。
		
		Vec2 point = physics.screenToWorld(pointer.getX(),pointer.getY());
		vertices.add(point);
	}
	
	private void onEndDrawing(){
		
		// 描画が終了した時の処理。verticesに格納した座標を元にBox2Dでbodyを生成。
		
		isDrawing = false;
		BodyDef bd = new BodyDef();
		Body body = physics.getWorld().createBody(bd);
		for(int i =0 ; i<vertices.size() ; i++){
			CircleDef cd = new CircleDef();
			cd.radius = 0.5f;
			cd.density = physics.getDensity();
			cd.friction = physics.getFriction();
			cd.restitution = physics.getRestitution();
			cd.isSensor = physics.getSensor();
			cd.localPosition.set(vertices.get(i));
			body.createShape(cd);
			body.setMassFromShapes();
		}
		
		// 描画したBodyに色を持たせる。必要なメンバはUserDataクラスで定義しておく。
		
		UserData data = new UserData();
		data.color = palletColor;
		body.setUserData(data);
		vertices = null;
	}
	
	private int getColor(){
		int hue = this.frameCount%360;
		return this.color(hue,100,100);
	}

	//------------------------------------------------------------
	// @ KeybordEvent
		
	@Override
	public void keyPressed(){
		
		// 初期化処理
		
		World world = physics.getWorld();
		if(keyCode == 32){
			Body body;
			for (body = world.getBodyList(); body != null; body = body.getNext()) {
				world.destroyBody(body);
			}
			physics.destroy();
			createPysics();
			createPointerBody();
		}
	}
	
}

余談ですが、先日Processingが2になりましたね!
LeapSDKもバージョンアップがあった様子ですが、ちゃんと見れてません。
(というか、ライブラリ頼みなのでバージョンあがっても対応できないという…むむむ)

LeapMotion #001 Starling

LeapMotion #001 Starling from Takepepe on Vimeo.

1ヶ月ぶりの投稿です。GW最終日、皆様いかがお過ごしですか?
今回の投稿は、個人的に今年最も気になるガジェット「LeapMotion」についてです。

・Leap Motion

LeapMotionは端末の上1m四方空間内で、1ミリ単位で指やツール(ペン等)を検知するセンサーです。
また、SDKレベルで数種類のジェスチャーを認識します。

つい先日、プレオーダーした人への発送延期がアナウンスされたばかりですが、
自分は3月末にプレオーダー、デベロッパー登録したところ、2週間ほどで手元に届きました。
発送延期の理由としては、完璧な状態として世に送り出したい、というような内容でした。
7月22日が発送予定日とされていますが、実際どうなるかはまだわかりませんね。

まだ日本語のリソースが少ない状態ですので、コード解説にはいる前に
DeveloperPortalの概要を写したものを交えて、簡単に解説したいと思います。

Frame

LeapMotionで値を取得するために使用するFrameオブジェクトの中には以下のものが含まれます。

・Lists of tracking data
Hands — すべての手
Pointables — Pointableオブジェクトとして、すべての指とツール
Fingers — すべての指
Tools — すべてのツール
Gestures — 開始、更新、終了のハンドラーを含むすべてのジェスチャー
・Frame motion
Rotation Axis — 回転軸を表現する方向ベクトル。
Rotation Angle — 回転軸(右手の法則を使用して)のまわりで右回りの回転角。
Rotation Matrix — 回転を表現するtransformマトリックス。
Scale Factor — 拡大縮小を表現するファクター。
Translation — 直線運動を表現するベクトル。

Hand model

手について様々な情報を提供します。2本の手を認識しますが、右手左手の識別はしていません。

・Hand attributes
Palm Position — Leapの起点から計測した手のひら中心座標
Palm Velocity — 秒速ミリメートル単位の、手のひらの移動速度
Palm Normal — 手のひらの中心から、下方へ指す垂直方向のベクトル
Direction — 手のひらの中心から、指へ向かうベクトル。
Sphere Center — 手の屈曲に適当な球体の中心。(手でボールを持っているような感じ)
Sphere Radius — 手の屈曲に適当な球体の半径。半径は手の形とともに変化します。
・Hand motion
Rotation Axis — 回転軸を表現する方向ベクトル。
Rotation Angle — 回転軸(右手の法則を使用して)のまわりで右回りの回転角。
Rotation Matrix —回転を表現するtransformマトリックス。
Scale Factor — 拡大縮小を表現するファクター。
Translation — 直線運動を表現するベクトル。
・Finger and Tool lists

検知した手に属する、指やツールの情報を取得できます。

Pointables — Pointableオブジェクトとして、すべての指とツール
Fingers — すべての指
Tools — すべてのツール

Finger and Tool models

Leapはその視界内の指およびツールの両方を検知するおよび追跡します。

Length — オブジェクト(手から先端に及ぶ)の可視部の長さ。
Width — オブジェクトの可視部の平均幅。
Direction — オブジェクト(つまり基礎から先端まで)と同じ方角に指すユニット方向ベクトル。
Tip Position — Leapの起点から計測した指先の座標
Tip Velocity — 秒速ミリメートル単位の、指先の移動速度

Gestures

Leapは特定の移動パターンをGesturesとして認識します。

Circle — 円をトレースする単一の指。
Swipe — 手の直線運動。
Key Tap — キーボード・キーを軽く打つかのような指の動作。
Screen Tap — コンピューター・スクリーンを軽く打つかのような指の動作。
開発環境について

現在サポートされている言語は、C++、C#、Objective-C、Java、Python、JavaScriptになります。
DevelperPortalでは前述の概要の他に、各フレームワークに対応したライブラリの配布、
ガイドライン、コミュニティ、アプリストア概要などが掲載されています。
Leapに興味があるかたは是非覗いてみてください。

・Leap Motion Developer Portal

LeapMotion × Adobe AIR Starling

今回作成したデモについての解説です。
AdobeAIRでStarlingのParticleSystemを使用して作成しています。

・Starling Framework
・Starling-Extension-Particle-System

StarlingはStage3Dを使用しているのでapp.xmlに以下の設定を忘れずにしましょう。

<renderMode>direct</renderMode>
<depthAndStencil>false</depthAndStencil>

LeapをActionScriptで使用するために、以下のライブラリと付属のC++SDKをラップしているANEを使用します。
導入方法については、下記ページの下部に書いてあるので、それぞれ開発環境にあったものを選んでください。
自分はFlashBuilder4.7でした。

・LeapMotionAS3

Starlingを使用したパーティクルのデモはClockmakerさんにゃあプロジェクトさんが充実していますので、
そちらを参考にさせていただきました。

FL001.as

package {
	
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageDisplayState;
	import flash.display.StageScaleMode;
	import flash.events.KeyboardEvent;
	import starling.core.Starling;
	
	[SWF(backgroundColor="#00164a", width="1440", height="900", frameRate="60")]
	
	public class FL001 extends Sprite {
		
		private var starling:Starling;
		
		public function FL001() {
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
			
			starling = new Starling(MainView, stage);
			starling.start();
			
		}
		
		private function onKeyDown(e:KeyboardEvent):void {
			
			if(stage.displayState == StageDisplayState.NORMAL){
				stage.displayState = StageDisplayState.FULL_SCREEN;
			} else {
				stage.displayState = StageDisplayState.NORMAL
			}
			
		}
		
		
	}
}

MainView.as

package {
	
	import flash.geom.Rectangle;
	import starling.core.Starling;
	import starling.display.Sprite;
	import starling.events.Event;
	import starling.events.ResizeEvent;
	import starling.extensions.ParticleDesignerPS;
	import starling.textures.Texture;
	import com.leapmotion.leap.LeapMotion;
	import com.leapmotion.leap.Pointable;
	import com.leapmotion.leap.events.LeapEvent;
	
	internal class MainView extends Sprite { 
		
		// 事前に「online particle editor」で.pexファイルとtexture.pngを用意しておきます
		// http://onebyonedesign.com/flash/particleeditor/
		
		[Embed(source = "assets/particle.pex", mimeType = "application/octet-stream")]
		private static var ParticleData:Class;
		
		[Embed(source = "assets/texture.png")]
		private static var ParticleImage:Class;
		
		private var particles:Vector.<ParticleDesignerPS>;		
		private var count:int = 10;
		private var leap:LeapMotion;
		
		public function MainView() {
			addEventListener(Event.ADDED_TO_STAGE, onAddStage);
		}
		
		private function onAddStage(e:Event):void {
			
			setParticles();
			leap = new LeapMotion();
			leap.controller.addEventListener( LeapEvent.LEAPMOTION_FRAME, onLeapFrame );
			stage.addEventListener(ResizeEvent.RESIZE, onResizeStage);
			
		}
		
		private function setParticles():void{
			
			particles = new Vector.<ParticleDesignerPS>(10);
			for(var i:int = 0; i<count ; i++){
				particles[i] = new ParticleDesignerPS( XML(new ParticleData()), Texture.fromBitmap(new ParticleImage()));
				particles[i].startSize = 100;
				particles[i].endSize = 100;
				particles[i].speed = 0;
				particles[i].start();
				Starling.juggler.add(particles[i]);
				addChild(particles[i]);
			}
			
		}
		
		private function onResizeStage(e:ResizeEvent):void {
			
			Starling.current.viewPort = new Rectangle(0, 0, e.width, e.height);
			stage.stageWidth = e.width;
			stage.stageHeight = e.height;
			
		}
		
		private function onLeapFrame(e:LeapEvent):void {
			
			var max:int = e.frame.pointables.length;
			for(var i:int = 0; i<count ; i++){
				if( i < max ){
					var pointable:Pointable = e.frame.pointables[i];
					particles[i].emitterX = (pointable.tipPosition.x)*3+stage.stageWidth/2;
					particles[i].emitterY = (pointable.tipPosition.y)*-3+stage.stageHeight;
					particles[i].gravityX = pointable.tipVelocity.x*-5;
					particles[i].gravityY = pointable.tipVelocity.y*5;
					particles[i].maxNumParticles = 50;
				}else{
					particles[i].maxNumParticles = 1;
				}
			}
			
		}
		
	}
}

1ヶ月いじった上で得た感想(というかぶちあたっている壁)です。

  1. 指がLeapに向かって垂直に重なると検知できない。
  2. 細かなUI操作に向いていない
  3. ジェスチャーはそのままでは利用しづらい

あくまで、現状公開されているSDKとライブラリを使用した上での感想です。
概要を読む限りではワクワクするばかりですが、ちゃんとアプリケーションに落とし込むためには
上記の問題をカバーしたフレームワークが必要だなと思いました。

今回の投稿はさらっと紹介しただけになってしまったので、
次回はちょっとしたフレームワークと何か面白いネタで投稿できればいいなー。