Flash x iOS #001 Away3D from Takepepe on Vimeo.
久々の投稿です。
桜が満開ですね!今年は多忙につき花見はお預けな感じです。
今回はAway3Dを使ったデスクトップAIRアプリです。
OSC通信でiPadから送られて来たタッチ座標と端末の傾きで、指の位置を3D空間に変換しています。
レインボーな箱がウネウネとしています。特に意味はありません。
iPadからOSC送信するアプリはopenFrameWorksで作ったものです。
早速見て行きましょう。
- iPadからOSC送信
- FlashでOSC受信
- 受信した値から指の位置をシュミレート
- レインボーな箱をウネウネ
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--; } } }