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--;
}
}
}