前回に引き続きMolehill API対応3Dエンジンのひとつ"Alternaiva3D 8.5.0"をさわってみる。
exampleのmouseeventsexampleだ。
package mouseeventsexample { import alternativa.engine3d.controllers.SimpleObjectController; import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.MouseEvent3D; import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.Renderer; import alternativa.engine3d.core.Resource; import alternativa.engine3d.core.View; import alternativa.engine3d.materials.TextureMaterial; import alternativa.engine3d.objects.Mesh; import alternativa.engine3d.primitives.Box; import alternativa.engine3d.resources.BitmapTextureResource; import flash.display.Sprite; import flash.display.Stage3D; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; /** * 3D mouse events demonstration. * Пример работы с событиями мыши. */ public class MouseEventsExample extends Sprite { [Embed(source="texture.jpg")] static private const EmbedTexture:Class; private var scene:Object3D = new Object3D(); private var camera:Camera3D; private var controller:SimpleObjectController; private var stage3D:Stage3D; public function MouseEventsExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Camera and view // Создание камеры и вьюпорта camera = new Camera3D(1, 10000); camera.view = new View(100, 100); addChild(camera.view); addChild(camera.diagram); // Initial position // Установка начального положения камеры camera.rotationX = -160*Math.PI/180; camera.y = -500; camera.z = 1200; controller = new SimpleObjectController(stage, camera, 200); scene.addChild(camera); // Objects // Создание объектов var box:Mesh = new Box(); box.name = "Box"; var texture:BitmapTextureResource = new BitmapTextureResource(new EmbedTexture().bitmapData); var material:TextureMaterial = new TextureMaterial(texture); box.setMaterialToAllSurfaces(material); var boxes:Object3D = new Object3D(); boxes.name = "Boxes"; boxes.rotationZ = -45*Math.PI/180; for (var i:int = 0; i < 5; i++) { for (var j:int = 0; j < 5; j++) { var object:Object3D = box.clone(); object.x = i*180 - 360; object.y = j*180 - 360; object.rotationZ = 45*Math.PI/180; // object.addEventListener(MouseEvent3D.CLICK, onClick); boxes.addChild(object); } } scene.addChild(boxes); // 3D mouse events // Мышиные события в 3D boxes.addEventListener(MouseEvent3D.MOUSE_OVER, onMouseOver); boxes.addEventListener(MouseEvent3D.MOUSE_OUT, onMouseOut); boxes.addEventListener(MouseEvent3D.CLICK, onClick); stage3D = stage.stage3Ds[0]; stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate); stage3D.requestContext3D(); } private function onContextCreate(event:Event):void { stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate); var resources:Vector.<Resource> = scene.getResources(true); for each (var resource:Resource in resources) { resource.upload(stage3D.context3D); } // Listeners // Подписка на события stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); onResize(); } private function onMouseOver(e:MouseEvent3D):void { var object:Object3D = e.target as Object3D; object.scaleX = 1.2; object.scaleY = 1.2; object.scaleZ = 1.2; } private function onMouseOut(e:MouseEvent3D):void { var object:Object3D = e.target as Object3D; object.scaleX = 1; object.scaleY = 1; object.scaleZ = 1; } private function onClick(e:MouseEvent3D):void { var object:Object3D = e.target as Object3D; object.rotationZ -= 45*Math.PI/180; } private function onEnterFrame(e:Event = null):void { controller.update(); camera.render(stage3D); } private function onResize(e:Event = null):void { // Width and height of view // Установка ширины и высоты вьюпорта camera.view.width = stage.stageWidth; camera.view.height = stage.stageHeight; onEnterFrame(); } } }
まずはコンストラクタから。
// Camera and view
camera = new Camera3D(1, 10000);
camera.view = new View(100, 100);
addChild(camera.view);
addChild(camera.diagram);
カメラを作ってCamera3D.viewにビューを設定。
ビューとステータス画面を2D表示リストに追加。
// Initial position
camera.rotationX = -160*Math.PI/180;
camera.y = -500;
camera.z = 1200;
controller = new SimpleObjectController(stage, camera, 200);
scene.addChild(camera);
カメラの位置と向きを設定。
SimpleObjectControllerを作成。
このコントローラーが今回のミソだな
//Base controller for Object3D. Provides object control using keyboard and mouse.
public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1)
ベースって言ってる割にはASdocにサブクラス書いてないけど...
イベントを受け取るオブジェクトとカメラを登録。
でカメラを
private var scene:Object3D = new Object3D();
で作ったObject3Dクラスのsceneに追加。
これは3D表示リストのルートととして使うんだろうな。
今回は名前sceneなのね。
// Objects
var box:Mesh = new Box();
box.name = "Box";
var texture:BitmapTextureResource = new BitmapTextureResource(new EmbedTexture().bitmapData);
var material:TextureMaterial = new TextureMaterial(texture);
box.setMaterialToAllSurfaces(material);
直方体を作って名前をつける。
Embedの画像のBitmapDataからBitmapTextureResourceを作成。
リソースからTextureMaterialを作成。
BitmapTextureResource Allows to upload texture in GPU from BitmapData.
public function BitmapTextureResource(data:BitmapData)
Resourceの継承クラスで、BitmapDataテクスチャをGPUにアップロードできるんだそうな。
Material with diffuse texture without light calculation
public function TextureMaterial(diffuseMap:TextureResource, alpha:Number = 1)
TextureResourceから光源の計算のないdiffuseなマテリアルを作成。
diffuseってなんだ...拡散?
普通の画像テクスチャだと考えていいのかな。
やはりResourceがキモか。
GPUの資源と考えて問題なさそう。
テクスチャ作るにはまずGPUにアップロードしなきゃならないのね。
CUDAやった身としてはGPUへの転送ってのは(GPUの計算に対して)ネックだったと思うんだけど、これは非同期なのかな?
1.BitmapTextureResourceをnewした時点でアップロードが開始される。命令を出したら非同期で次の処理に進む
2.BitmapTextureResourceをnewした時点でアップロードが開始される。終了を待って次の処理へ進む
3.BitmapTextureResourceをnewしてもアップロードはされず、後でどっかでまとめてアップロード(非同期)
4.BitmapTextureResourceをnewしたもアップロードはされず、後でどっかでまとめてアップロード(同期)
うーん。1,2か3,4かはAlternativaのソース見ないとわからんかな。
同期か非同期かはどっか探せば書いてありそう。
ていうかたぶん非同期だろ。
でBoxにマテリアルを設定と。
ちなみに同梱の画像でなく自分の好きな画像でやってみたら
ArgumentError: Error #3682: テクスチャのサイズが 2 の累乗ではありません。
at flash.display3D::Context3D/createTexture()
at alternativa.engine3d.resources::BitmapTextureResource/upload()
at Main2/onContextCreate()
例外投げられた。
FP11のplayerglobal.swcのドキュメントのContext3D.createtexture()によると
ArgumentError — kTextureNotPowerOfTwo when width or height is not a power of two
ArgumentError — kTextureTooBig when width or height is greater than 2048
最高サイズは縦横ともに2048ピクセルで2の累乗じゃなきゃいけないらしい。
ちょっと調べるとOpenGLとかGPU使うときは結構当然みたい。
だったらそれ以下の2の累乗にリサイズするメソッドくらいつけとけよ。
いままで(PV3Dでは)なんも考えてなかったけどテクスチャのサイズにも気をつけなきゃいけないのか。
特に縦横の比が整数比しかだめってこと?
上手く自由にできる方法がなんかあるんだろうな。
ちなみに例外の発生場所はonContextCreate()らしい。
ためしに各所で例外投げまくって(traceもデバッガも使えなくて、テキストフィールドに表示させるか例外投げてメッセージに思いを込める以外に調べる方法ないんかね)探すと、resource.upload(stage3D.context3D);の部分だった。
さっきの疑問はAltanativa的には3か4のあとでまとめてアップロードなわけだ。
ていうか前回ので気づくべきだった。
ドキュメントのResource.upload(context3D:Context3D)にイベントの話書いてないんだけど同期なのかなー。
とりあえずやっぱり
GPUに領域確保してーって言って(stage3D.requestContext3D())
GPUがここ使っていいよーって非同期的に返してきたら(Event.CONTEXT3D_CREATE)
作られたContext3DにGPU用データ(Resource)を全部転送(resource.upload(stage3D.context3D))
の流れで良さそう。
var boxes:Object3D = new Object3D();
boxes.name = "Boxes";
boxes.rotationZ = -45*Math.PI/180;
for (var i:int = 0; i < 5; i++) {
for (var j:int = 0; j < 5; j++) {
var object:Object3D = box.clone();
object.x = i*180 - 360;
object.y = j*180 - 360;
object.rotationZ = 45*Math.PI/180;
// object.addEventListener(MouseEvent3D.CLICK, onClick);
boxes.addChild(object);
}
}
scene.addChild(boxes);
box達を管理するboxesを作って25個のboxを複製して配置。
boxesをシーンに追加。
// 3D mouse events
boxes.addEventListener(MouseEvent3D.MOUSE_OVER, onMouseOver);
boxes.addEventListener(MouseEvent3D.MOUSE_OUT, onMouseOut);
boxes.addEventListener(MouseEvent3D.CLICK, onClick);
boxesにマウスイベントを登録。
MouseEvent3DはAlternaitiva側のクラスか。
どうせどのライブラリでも使うんだからFlash側で用意してくれてもいいのに。
stage3D = stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
stage3D.requestContext3D();
stage3Dを取得してCONTEXT3D_CREATEイベントを登録、Context3Dをリクエスト。
private function onContextCreate(event:Event):void {
stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
var resources:Vector.<Resource> = scene.getResources(true);
for each (var resource:Resource in resources) {
resource.upload(stage3D.context3D);
}
// Listeners
stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(Event.RESIZE, onResize);
onResize();
}
CONTEXT3D_CREATEがdispatchされたらイベントを外して
ルートとして決めたObject3Dオブジェクトからリソースを全部とってきて次々アップロード。
getResources()のtrueは子を含めてとってくるかどうからしい。
つまり新しいオブジェクトを加えるたびにこれをやらなきゃいけないのかな。
そしてENTER_FRAMEイベントとRESIZEイベントを登録。
private function onMouseOver(e:MouseEvent3D):void {
var object:Object3D = e.target as Object3D;
object.scaleX = 1.2;
object.scaleY = 1.2;
object.scaleZ = 1.2;
}
private function onMouseOut(e:MouseEvent3D):void {
var object:Object3D = e.target as Object3D;
object.scaleX = 1;
object.scaleY = 1;
object.scaleZ = 1;
}
private function onClick(e:MouseEvent3D):void {
var object:Object3D = e.target as Object3D;
object.rotationZ -= 45*Math.PI/180;
}
マウスイベントの挙動。
普通のマウスイベントと同様に考えて良さそう。
...?あれ...?
なんちゃらコントローラーは?
なんたらコントローラーの部分をコメントアウトしても普通にBoxどもはマウスイベントで反応した。
じゃあなに?っていうとどうやらカメラの操作の部分らしい。
ステージドラッグしたらカメラが動いた。
この部分のコードがなくてなぜ動くんだ!?と思ってたけどSimpleObjectControllerはカメラ操作だったのね。
マウスイベントで3Dオブジェクトを動かすのに使うのかと思ってたよ。
なんでコンストラクタにカメラ渡してるんだろうと不思議だったわ。
というわけでミソでもなんでもなくてただのデバッグ用?のカメラ操作補助クラスでした。
private function onEnterFrame(e:Event = null):void {
controller.update();
camera.render(stage3D);
}
private function onResize(e:Event = null):void {
// Width and height of view
camera.view.width = stage.stageWidth;
camera.view.height = stage.stageHeight;
onEnterFrame();
}
コントローラーのアップデートとレンダリング。
リサイズイベントハンドラのほうはビューの大きさ調整。
リサイズって大事なのだろうか...?
というわけで3Dオブジェクトのマウス操作でした。
しかしドキュメント読んでると本当にクラス少ないな。
ほんとにこんなんやこんなんができるんだろうか。
まあ少ないほうが把握しやすいけどさ。
今まで見た二つのサンプルは別にMolehillじゃなくたってPV3Dでもできたことだし、
そろそろMolehillならではのすげーとこみたいなあ。
Author:9ballsyn
ActionScriptについて
最近はMolehill