タグ別アーカイブ: OSC

Flash x iOS #001 Away3D

Flash x iOS #001 Away3D from Takepepe on Vimeo.

久々の投稿です。
桜が満開ですね!今年は多忙につき花見はお預けな感じです。

今回はAway3Dを使ったデスクトップAIRアプリです。
OSC通信でiPadから送られて来たタッチ座標と端末の傾きで、指の位置を3D空間に変換しています。
レインボーな箱がウネウネとしています。特に意味はありません。

iPadからOSC送信するアプリはopenFrameWorksで作ったものです。
早速見て行きましょう。

  1. iPadからOSC送信
  2. FlashでOSC受信
  3. 受信した値から指の位置をシュミレート
  4. レインボーな箱をウネウネ

1.iPadからOSC送信

iPadからOSC送信します。
いつもの様にemptyExampleをコピペしたら、「iOS Application Target」の「Devices」を
「iPad」か「Universal」にします。
FI001::setup()で画面の向きをiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_LEFT);
にして、ホームボタンが右に来たときに左上が(0,0)になるようにします。

iPadでは同時に5つのタッチ座標が取得できます。
5つの座標を格納できる「touchLoc」を用意し、タッチが5つに満たない場合、
(-100,-100)を入れる様にしておき、常に5つ分の座標を送ります。

※Supported Intereface Orientationsを「Portrait」にしていますが、
これを「LandscapeRight」にしながら、左上を(0,0)にする方法が分からず…知ってる人いたら教えてください!

FI001.cpp

void FI001::setup(){
    
    ofBackground(127,127,127);
    
    //iPadの向きの設定
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_LEFT);
    
    //加速度センサーを有効に
	ofxAccelerometer.setup();
    
    //ofxiPhoneCoreLocationインスタンスのコンパスを有効に
    location.startHeading();
    
    //OSC設定
    sender.setup( HOST, PORT );
	receiver.setup( MYPORT );
    
    //初期設定
    for (int i=0; i<5; i++) {
        touchLoc[i].set(-100, -100);
    }
    
}

//--------------------------------------------------------------
void FI001::update(){
    
    ofxOscMessage m;
    m.setAddress("/iOSOSC");
    
    //タッチ位置の格納
    for (int i=0; i<5; i++) {
        m.addIntArg(touchLoc[i].x);
        m.addIntArg(touchLoc[i].y);
    }
    
    //加速度
    m.addFloatArg(ofxAccelerometer.getForce().x);
    m.addFloatArg(ofxAccelerometer.getForce().y);
    m.addFloatArg(ofxAccelerometer.getForce().z);
    
    //X軸の傾き
    m.addFloatArg(ofxAccelerometer.getOrientation().y);
    
    //Y軸の傾き(コンパスの向き)
    m.addFloatArg(location.getMagneticHeading());
    
    //Z軸の傾き
    m.addFloatArg(ofxAccelerometer.getOrientation().x);
    
    sender.sendMessage( m );
    
}

2.FlashでOSC受信

OSC.as

FlashでOSC通信をする場合、AIRアプリに限定される様です。(UDPConnectorあたり)
ライブラリはtuio-as3を使っています。

tuio-as3

https://code.google.com/p/tuio-as3-lib/

openFrameWorksから送られて来たOSCメッセージを連想配列にまとめて
メインスレッド(FI001.as)に渡します。
タッチ座標(-100,-100)が送られて来た場合、非表示の真偽値をセットします。

package {
	
	import flash.display.*;
	import flash.geom.*;
	import org.tuio.connectors.UDPConnector;
	import org.tuio.osc.IOSCListener;
	import org.tuio.osc.OSCManager;
	import org.tuio.osc.OSCMessage;
	
	public class OSC implements IOSCListener {
		
		private var delegate:FI001;
		private var fingers:Vector.<Object>;
		private var accelerometer:Vector.<Number>;
		private var orientation:Vector.<Number>;
		private var oscManager:OSCManager;
		
		public function OSC (_delegate:FI001) {
			
			delegate = _delegate;
			
			//タッチ情報
			fingers = new Vector.<Object>(5,true);
			for(var i:int = 0 ; i<5 ; i++){
				fingers[i] = new Object();
			}
			
			//加速度と傾き
			accelerometer = new Vector.<Number>(3,true);
			orientation = new Vector.<Number>(3,true);
			for(var j:int = 0 ; j<3 ; j++){
				accelerometer[j] = 0;
				orientation[j] = 0;
			}
			
			// OSC設定 ※ UDPConnectorはAIRアプリでのみ利用出来る
			var connectorIn:UDPConnector = new UDPConnector("192.168.11.20",8000);
			var connectorOut:UDPConnector = new UDPConnector("192.168.11.18",8001,false);
			oscManager = new OSCManager(connectorIn,connectorOut,true);
			oscManager.addMsgListener (this);
			
		}

		public function acceptOSCMessage (oscmsg:OSCMessage):void {
			
			//OSCメッセージを受信したら、delegateに連想配列を渡す
			
			if(oscmsg.address == "/iOSOSC") {

				//タッチ情報
				for(var i:int = 0 ; i<5 ; i++){
					fingers[i].x = oscmsg.arguments[i*2];
					fingers[i].y = oscmsg.arguments[i*2+1];
					fingers[i].visible = true;
					if(fingers[i].x < 0){
						fingers[i].visible = false;
					}
				}
				
				//加速度と傾き
				accelerometer[0] = oscmsg.arguments[10];
				accelerometer[1] = oscmsg.arguments[11];
				accelerometer[2] = oscmsg.arguments[12];
				orientation[0] = oscmsg.arguments[13];
				orientation[1] = oscmsg.arguments[14];
				orientation[2] = oscmsg.arguments[15];
				
				//まとめ
				var data:Object = new Object();
				data.fingers = fingers;
				data.accelerometer = accelerometer;
				data.orientation = orientation;

				delegate.onMessage (data);
				
			}
		}
		
		public function sendOSCMessage ():void { }
		public function sendOSCBundle ():void { }

	}

}

3.受信した値から指の位置をシュミレート

FI001.as

ここからAway3Dを使ってコーディングしていきます。
Stage3Dを使ったAIRアプリは「app.xml」の

<renderMode>direct</renderMode>
<depthAndStencil>true</depthAndStencil>

を設定し忘れる「context3Dなんて知らないよ」って怒られるので注意。
ブラウザで見るswfの場合は、htmlのwmodeをdirectにしましょう。
Away3Dは現状で最新の「4.1.0 Alpha」を使います。

Away3D 4.1.0 Alpha

http://away3d.com/download/away3d_4.1.0_alpha

package {
	
	import away3d.cameras.Camera3D;
	import away3d.containers.Scene3D;
	import away3d.containers.View3D;
	import away3d.lights.DirectionalLight;
	import away3d.materials.lightpickers.StaticLightPicker;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Vector3D;
	import flash.system.System;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	
	[SWF(backgroundColor="#000717", width="1024", height="768", frameRate="60")]
	
	public class FI001 extends Sprite{
		
		// OSC通信で使うインスタンス
		private var osc:OSC;
		private var tf:TextField;
		
		// Away3Dでお決まりのインスタンス
		private var view:View3D;
		private var scene:Scene3D;
		private var camera:Camera3D;
		private var light:DirectionalLight;
		private static var ZERO:Vector3D = new Vector3D(0,0,0);
		
		// Away3Dで使うインスタンス
		private var fingers:Vector.<Finger>;
		
		// iPadの傾きを格納する変数
		private var ox:Number = 0;
		private var oy:Number = 0;
		private var oz:Number = 0;		

		public function FI001(){
			
			// ガベージコレクション設定(以下参考)
			// http://adclounge.jp/adobe-max-2011/new-small-features-in-flash-player-11/
			System.pauseForGCIfCollectionImminent(1);
			
			// OSCメッセージを受信したらデータを渡してくれるインスタンス
			osc = new OSC(this);
			
			// Away3Dの設定
			init3D();
			
			// 3Dオブジェクトの設定
			set3D();
			
			// デバッグ用テキスト(OSC受信した値表示用)
			setText();
			
		}
		
		private function init3D():void{
			
			// Away3Dの設定
			view = new View3D();
			scene = view.scene;
			light = new DirectionalLight();
			light.direction = new Vector3D(1, -1, 1);
			light.specular = 0.1;
			light.diffuse = 0.9;
			light.ambient = 0.1;
			scene.addChild(light);
			camera = view.camera;
			camera.z = -500;
			
			addChild(view);
			
		}
		
		private function set3D():void {
			
			var lightPicker:StaticLightPicker = new StaticLightPicker([light]);
			
			// タッチ座標に応じてCubeを表示するFingerインスタンス
			fingers = new Vector.<Finger>(5,true);
			for (var i:int = 0; i < 5; i++) {
				var finger:Finger = new Finger();
				fingers[i] = finger;
				for(var j:int = 0 ; j<fingers[i].cubes.length ; j++){
					scene.addChild(fingers[i].cubes[j].mesh);
					fingers[i].setLightPicker(lightPicker);
				}
			}
			
			// アニメーションループ
			addEventListener(Event.ENTER_FRAME, update, false, 0, true);
			
		}
		
		private function setText():void{
			
			// デバッグ用テキスト(OSC受信した値表示用)
			tf = new TextField();
			tf.textColor = 0xFFFFFF;
			tf.autoSize = TextFieldAutoSize.LEFT;
			addChild(tf);
			
		}
		
		private function update(e:Event):void {
			
			// アニメーションループ
			for (var i:int = 0; i < 5; i++) {
				fingers[i].update();
			}
			view.render();
			
		}
		
		
		
		public function onMessage(_data:Object):void{
			
			// ローパスフィルターを通してセンサーの傾きを格納
			ox = ox*0.9 + (_data.orientation[0]+180)*0.1;
			oy = oy*0.9 + (_data.orientation[1])*0.1;
			oz = oz*0.9 + (_data.orientation[2]+180)*0.1;
			
			// 画面の中心
			var cx:Number = -stage.width/2;
			var cy:Number = -stage.height/2;
			
			for(var i:int = 0 ; i<_data.fingers.length ; i++){
				
				var fx:Number = cx+_data.fingers[i].x;
				var fy:Number = cy+_data.fingers[i].y;
				var x:Number = -fx*Math.cos((oz)*Math.PI/180);
				var y:Number = -fy*Math.sin((ox)*Math.PI/180);
				var z:Number = fy*Math.cos((ox)*Math.PI/180);-fx*Math.cos((oy)*Math.PI/180);
				
				// iPadの傾きとタッチ位置から座標を特定
				fingers[i].vec.setTo(x,y,z);
				
				// 触っていなかったら非表示
				fingers[i].setVisible(_data.fingers[i].visible);
			}
			
			setMessageTet();
			
		}
		
		private function setMessageTet():void{
			
			// デバッグ用テキスト(OSC受信した値表示用)
			var str:String = "";
			for(var i:int = 0 ; i<fingers.length ; i++){
				str += "finger"+(i+1)+"X:"+fingers[i].vec.x+"\n";
				str += "finger"+(i+1)+"Y:"+fingers[i].vec.y+"\n";
			}
			str += "orientationX:"+ox+"\n";
			str += "orientationY:"+oy+"\n";
			str += "orientationZ:"+oz+"\n";
			tf.text = str;
			
		}
		
	}
}

4.レインボーな箱をウネウネ

メインスレッドで利用しているクラスは以下の通りです。
レインボー色を出すのにfrocessingを使って楽しています。

frocessing

http://www.libspark.org/wiki/nutsu/Frocessing

Finger.as

package {
	
	import flash.geom.Point;
	import flash.geom.Vector3D;
	import frocessing.color.ColorHSV;
	import away3d.materials.lightpickers.StaticLightPicker;
	
	public class Finger{
		
		public var vec:Vector3D;
		public var cubes:Vector.<Cube>;
		private var index:int = 0;
		private static var NUM:Number = 50;
		
		public function Finger(){
			
			// 指の疑似3次元座標
			vec = new Vector3D;
			
			// ウネウネするCube達
			cubes = new Vector.<Cube>(NUM);
			for(var i:int = 0; i<NUM ; i++){
				var color:ColorHSV = new ColorHSV(360/NUM*i,0.4);
				cubes[i] = new Cube(1,1,1,color.value);
			}
			
		}
		
		public function setLightPicker(_lp:StaticLightPicker):void{
			
			// Cube達に光源設定
			for(var i:int = 0; i<NUM ; i++){
				cubes[i].setLightPicker(_lp);
			}
			
		}
		
		public function setVisible(_visible:Boolean):void{
			
			// iPadを触っていない場合非表示
			for(var i:int = 0; i<NUM ; i++){
				cubes[i].mesh.visible = _visible;
			}
			
		}
		
		
		
		public function update():void{
			
			// 操作するCubeのIndex
			index++;
			if(index>=NUM){
				index = 0;
			}
			
			// 順番が回って来たCubeが触ってる場所に移動する
			cubes[index].setLife(100);
			cubes[index].setVector(vec.x,vec.y,vec.z);
			
			// Cube達を更新
			for(var i:int = 0; i<NUM ; i++){
				cubes[i].update();
			}
			
		}
		
	}
}

Cube.as

package {

	import away3d.entities.Mesh;
	import away3d.materials.ColorMaterial;
	import away3d.materials.lightpickers.StaticLightPicker;
	import away3d.primitives.CubeGeometry;
	
	public class Cube{
		
		public var mesh:Mesh;
		private var life:Number = 0;
		private var isSurvive:Boolean = false;
		
		public function Cube(_width:Number,_height:Number,_depth:Number,_color:uint){
			
			// CubeMeshの生成
			var geo:CubeGeometry = new CubeGeometry(_width,_height,_depth);
			var mat:ColorMaterial = new ColorMaterial(_color,0.8);
			mesh = new Mesh(geo,mat);
			
			// 影を落とすかどうか
			mesh.castsShadows=true;

		}
		
		public function setVector(_x:Number,_y:Number,_z:Number):void{

			mesh.x = _x;
			mesh.y = _y;
			mesh.z = _z;
			
		}
		
		public function setLightPicker(_lp:StaticLightPicker):void{
			
			mesh.material.lightPicker = _lp;
			
		}
		
		public function setLife(_life:Number):void{
			
			life = _life;
			
		}
		
		public function update():void{
			
			mesh.rotationX = Math.sin(life*Math.PI/180)*500;
			mesh.rotationY = Math.cos(life*Math.PI/180)*500;
			mesh.rotationZ = Math.cos(life*Math.PI/180)*500;
			
			var scale:Number = Math.pow(life,4)/2000000;
			mesh.scaleX = scale;
			mesh.scaleY = scale;
			mesh.scaleZ = scale;
			
			life--;
			
		}
		
	}
}

openFrameworks x iOS #001

openFrameworks x iOS #001 from Takepepe on Vimeo.

最近めっきり暖かくなりましたね。
花粉が飛び始めたようですが、Takepepeまだ花粉症ではないのでどこ吹く風です。
(花粉症の方すみません;)

今回は上の動画の様なものを作りました。
openFrameworksで作成した複数のiOSアプリから、
openFrameworksで作成したDesktopアプリケーションを操作します。
通信には前回同様、OSCを使っています。

前回は1対1の一方通信でしたが、今回は1対複の通信になります。
動画上では2台のiPhoneのタッチ座標を取得し、その座標をつなぐ線が描画されていますが、
コード上はOSC接続している端末数に応じて点と線が増えていくものになっています。
iPadやiOSシュミレーターでも確認できます。
(引き出しに眠っているiPhone3GSが復活。捨てなくてよかった。)
物理演算処理にはBox2Dを使用しています。

全体の流れを詳しく説明していきます。

  1. iOS端末からDesktopアプリケーションにOSC通信
  2. iOS端末の数に応じたClientインスタンスの生成
  3. Box2Dのセッティングと円を降らす処理
  4. Clientメンバの座標間で線をひく

1.iOS端末からDesktopアプリケーションにOSC通信

まずはiOSアプリをopenFrameworksで作ります。
アプリ単体で見ると、タッチした場所に円が描かれるだけの簡単なものになっています。

oscS.h (iOS端末のコード)


#define HOST "192.168.10.191" // iMacのIPアドレス
#define PORT 8000 // 接続ポート

class oscS : public ofxiPhoneApp{
	
    public:
        void setup();
        void update();
        void draw();
        float getX(float mx);
        float getY(float my);
        void touchMoved(ofTouchEventArgs & touch);
    
    private:
        ofxOscSender sender;
};

oscS.mm (iOS端末のコード)


#include "oscS.h"
#include "ofxOsc.h"

//--------------------------------------------------------------
void oscS::setup(){
    
	ofxAccelerometer.setup();
    sender.setup( HOST, PORT );
	ofBackground(0, 0, 0);
    
}

//--------------------------------------------------------------
void oscS::update(){

}

//--------------------------------------------------------------
void oscS::draw(){
	ofSetColor(255, 255, 255);
    ofCircle(mouseX, mouseY, 10);
}

//--------------------------------------------------------------
float oscS::getX(float mx){
    int res = ofGetWidth();
    return mx/res;
}

//--------------------------------------------------------------
float oscS::getY(float my){
    int res = ofGetHeight();
    return my/res;
}

//--------------------------------------------------------------
void oscS::touchMoved(ofTouchEventArgs & touch){
    ofxOscMessage m;
    m.setAddress( "/mouse/position" );
    m.addFloatArg( getX(touch.x) );
    m.addFloatArg( getY(touch.y) );
    sender.sendMessage( m );
}

2.iOS端末の数に応じたClientインスタンスの生成

ここからはDesktopアプリケーションです。
まずは「Client」クラスを作成します。
このクラスはコンストラクタにiOS端末のipアドレスを引数で渡します。
update関数が呼ばれるたび、タッチ座標を変換・更新します。

Client.h

#include "ofxOsc.h"
class Client {
    public:
        Client(std::string ip);
        void update(ofxOscMessage message);
        void draw();
        ofPoint pos;
        string getIp();
    
    private:
        string ip;
};

Client.cpp

#include "ofxOsc.h"

//--------------------------------------------------------------
Client::Client(string _ip){
    ip = _ip;
}

//--------------------------------------------------------------
void Client::update(ofxOscMessage message){
    ofxOscMessage m = message;
    
    // Clientオブジェクトの更新
    if ( m.getAddress() == "/mouse/position" && ip == m.getRemoteIp()){
        
        // 画面比率にあわせてタッチ座標を変換
        pos.x = ofGetWidth() * m.getArgAsFloat(0);
        pos.y = ofGetHeight() * m.getArgAsFloat(1);
    }
}

//--------------------------------------------------------------
void Client::draw(){
    ofCircle(pos.x, pos.y, 5);
}

//--------------------------------------------------------------
string Client::getIp(){
    return ip;
}

メインスレッドでOSCメッセージを受信した際、そのメッセージが新しい端末からの場合、
Clientインスタンスが生成されるようになっています。

oscR.h (抜粋)

#include "Client.h"
#define PORT 8000
class oscR : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();
    
    private:
        void updateClient();
        ofxOscReceiver  receiver;
        vector <Client *> clients;

}

oscR.cpp (抜粋)

#include "oscR.h"
#include "ofxOsc.h"

//--------------------------------------------------------------
void oscR::setup(){

    receiver.setup( PORT );
    
}
//--------------------------------------------------------------
void oscR::update(){
    
    // OSCクライアントの更新
    updateClient();

}
//--------------------------------------------------------------
void oscR::updateClient(){
    while( receiver.hasWaitingMessages() ){
        ofxOscMessage m;
        receiver.getNextMessage( &m );
        
        // 1つ目のOSCクライアント
        if(clients.size() == 0){
            clients.push_back(new Client(m.getRemoteIp()));
        }
        
        // 新しいOSCクライアントからのメッセージかどうか
        int flag = 1;
        for(int i=0 ; i< clients.size() ; i++){
            if(clients[i]->getIp() == m.getRemoteIp()){
                flag = 0;
            }
        }
        
        // 新しいOSCクライアントだったら
        if(flag){
            clients.push_back(new Client(m.getRemoteIp()));
        }
        
        // Clientオブジェクトの更新
        int size = clients.size();
        for(int i=0 ; i< size ; i++){
            clients[i]->update(m);
        }
    }
}
//--------------------------------------------------------------
void oscR::draw(){

    ofSetColor(50,200,255);
    ofFill();
    for(int i=0; i<clients.size(); i++) {
        clients[i]->draw();
    }

}

3.Box2Dのセッティングと円を降らす処理

次にBox2Dの実装です。
updateでカウントアップし、circlesの数が100に達したら
新しいofxBox2dCircleを作り、古いものは削除します。

oscR.h (抜粋)

#include "ofxBox2d.h"
class oscR : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();
    
    private:
        void updateCircles();
        int count;
    
        ofxBox2d box2d;
        vector <ofxBox2dCircle *> circles;
    
};

oscR.cpp (抜粋)

#include "ofxBox2d.h"

//--------------------------------------------------------------
void oscR::setup(){

    count = 0;
    
    // Box2D設定
    box2d.init();
    box2d.setGravity(0, 0.1);
    box2d.createBounds(0, 0, ofGetWidth(), ofGetHeight());
    box2d.setFPS(10.0);
    box2d.setIterations(5, 5);
    
}

//--------------------------------------------------------------
void oscR::update(){
    
    // ofxBox2dCircleを更新
    updateCircles();
    box2d.update();

}

//--------------------------------------------------------------
void oscR::updateCircles(){
    count++;
    if(count > 100){
        
        // ofxBox2dCircleの追加
        ofPoint pos;
        int r = ofRandom(10, 30);
        int x = ofRandom(r, ofGetWidth());
        pos.set(x,r);
        
        ofxBox2dCircle *c = new ofxBox2dCircle();
        c->setPhysics(1.0, 0.2, 0.2);
        c->setup(box2d.getWorld(),x,r,r);
        circles.push_back(c);
        
        // 100個以上になったら先頭のofxBox2dCircleを削除
        if(circles.size() > 100){
            vector <ofxBox2dCircle *>::iterator it = circles.begin();
            (*it)->destroy();
            delete *it;
            it = circles.erase(it);
        }
        
        count = 0;
    }
}

//--------------------------------------------------------------
void oscR::draw(){

    ofNoFill();
    ofSetColor(255,255,255);
    for(int i=0; i<circles.size(); i++) {
        circles[i]->draw();
    }

}

4.Clientメンバの座標間で線をひく

最後にiOS端末から取得した座標でofxBox2dPolygonの線をひきます。
ofPolylineに座標を追加し、ofxBox2dPolygonに代入します。
画面上で線が見える様、draw関数ではofPolylineを描画しています。 続きを読む