feb19.jp

Nobuhiro Takahashi
Designer / Engineer

3D物理演算エンジンWOWEngineとPapervision3D 2.0 GreatWhite

3D物理演算エンジンWOWEngineとPapervision3D 2.0 GreatWhite

2D平面の中での物理演算フレームワークはAPEとか、Box2DFlashAS3とかFisixとか他にもいろいろあったけど、最近初めて3D空間上で物理演算するフレームワークWowEngineが出てきました。

座標計算とか、衝突計算とか、そういうことだけをWOWEngineはやってくれます。だから、3D空間に壁をつくるとか、球体を表示するとか、「表示をする」ならば、Papervision3Dとか、Sandyとかと組み合わせて作らなければなりません。

というわけで作ってみました。ちょっと重いのでここをクリックしてご覧ください。

一秒ごとに球が増えてきて、一分間経つと止まります。空間はマウスで回転させることができます。 ちょうど最近murakenさんが制作されたBeyesのスペシャルサイトや、勇吾さんパーソナルサイトのモーションアートなどの3D版みたいな感じです。ただ僕のサンプルは永遠と出てきませんし、出てきた球は消えないんですし、見た目はひどいのですけど。

3Dの表現って、ちょっとマウスで介入できるっていうだけで、すごく面白さを引き出すなぁと思っていたのですが、そこに物理法則が適用されていると、ものすごく面白くなるなぁといった印象。

では、ソースでございます。意外と簡単に作れました。

WowTest.as

package {
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	import org.papervision3d.cameras.*;
	import org.papervision3d.core.geom.*;
	import org.papervision3d.materials.*;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.materials.shadematerials.PhongMaterial;
	import org.papervision3d.objects.*;
	import org.papervision3d.objects.primitives.*;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.lights.PointLight3D;
	
	import fr.seraf.wow.primitive.WSphere;
	import fr.seraf.wow.primitive.WBoundArea;
	import fr.seraf.wow.core.WOWEngine;
	import fr.seraf.wow.core.data.WVector;
	
	public class WowTest {
		private var basemc;
		
		private var viewport	:Viewport3D;
		private var render		:BasicRenderEngine;
		private var scene			:Scene3D;
		private var light			:PointLight3D;
		private var camera		:Camera3D;
		private var ba				:Cube;
		private var root3d		:DisplayObject3D;
		private var particles	:Array;
		
		private var wow			:WOWEngine;
		private var positions	:Array;
		private var ba2			:WBoundArea;
		
		private var timer_cnt	:uint;
		private var timer			:Timer;
		
		private var cnt				:uint;
		private var n				:uint;
		
		public function WowTest(mc) {
			basemc = mc;
			init();
		}
		private function init():void {
			basemc.stage.align = StageAlign.TOP_LEFT;
			basemc.stage.scaleMode = StageScaleMode.NO_SCALE;
			
			cnt = 0;
			
			particles = new Array();
			positions = new Array();
			
			initPapervision();
			initWowEngine();
			
			initTimer();
			
			basemc.addEventListener(Event.ENTER_FRAME, onEnterFrameHandler);
		}
		private function initPapervision():void {
			viewport = new Viewport3D(0, 0, true);
			basemc.addChild(viewport);
			
			render = new BasicRenderEngine();
			
			scene = new Scene3D();
			
			root3d = new DisplayObject3D();
			scene.addChild(root3d);
			
			light = new PointLight3D();
			
			camera = new Camera3D();
			camera.z = -1400;
			camera.y = 800;
			camera.zoom = 5;
			
			var material:WireframeMaterial = new WireframeMaterial(0x000000, .2);
			material.doubleSided = true;
			ba = new Cube(new MaterialsList({all:material}), 900, 900, 900);
			ba.name = "BoundArea";
			root3d.addChild(ba);
		}
		private function initWowEngine():void {
			wow = new WOWEngine(1);
			wow.collisionResponseMode = wow.STANDARD;
			wow.addMasslessForce(new WVector(0, -4, 0));
			wow.damping = .98;
			
			ba2 = new WBoundArea(900, 900, 900);
			ba2.setPosition(0, 0, 0);
			ba2.elasticity = 2;
			ba2.friction = 1;
			
			wow.setBoundArea(ba2);
		}
		private function initTimer():void {
			timer_cnt = 0;
			
			timer = new Timer(1000, 60);
			timer.start();
			timer.addEventListener(TimerEvent.TIMER, onTimerHandler);
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerCompleteHandler);
		}
		private function onTimerHandler(e:TimerEvent):void {
			timer_cnt++;
			n++;
			
			var material:PhongMaterial = new PhongMaterial(light, 0xffffff*Math.random(), 0x000000, 5);
			var ball:Sphere = new Sphere(material, 30);
			root3d.addChild(ball);
			particles.push(ball);
			
			var sphere:WSphere = new WSphere(Math.random()*1000-500, Math.random()*100-50, Math.random()*100-50, 30, false, 2, 1, 0);
			sphere.velocity = new WVector(Math.random()*100-50, Math.random()*100-50, Math.random()*100-50);
			wow.addParticle(sphere);
			positions.push(sphere);
		}
		private function onTimerCompleteHandler(e:TimerEvent):void {
			timer_cnt = 0;
			timer.removeEventListener(TimerEvent.TIMER, onTimerHandler);
			timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerCompleteHandler);
		}
		private function onEnterFrameHandler(e:Event):void {
			var w = basemc.stage.stageWidth / 2;
			
			root3d.rotationY -= (basemc.mouseX-(basemc.stage.width/2))/50;
			cnt++;
			ba.rotationX = cnt;
			
			wow.step();
			for(var i:uint=0; i<particles.length; i++) {
				particles[i].x = positions[i].px;
				particles[i].y = positions[i].py;
				particles[i].z = positions[i].pz;
			}
			ba2.setRotation(-cnt, 0, 0);
			
			render.renderScene(scene, camera, viewport);
		}
	}
}

Flashの1フレーム目に、以下のコードでWowTest.asを呼びだしてください。

var wow = new WowTest(this);

一応、全ソースファイル

というわけで、ちょっとだけ解説します。

まずinitPapervisionと、initWowEngine()でそれぞれ初期化しているのですが、initWowEngine()ほうでは、最初の4行でWOWによる空間の定義をしています。

まず、wow = new WOWEngine(1);でwow空間インスタンスを生成します。引数は、フレームレートに対して、どれぐらいの間隔で計算を行うかという割合。マシンスペックがそこそこ良いのを対象にするなら、1で。ちなみに初期値は0.3。

wow.collisionResponseMode = wow.STANDARD;で、衝突具合を定義。wow.STANDARDっていうのは定数で、「100」を意味しています。ほかに、wow.SELECTIVE(200)とか、wow.SIMPLE(300)などの定数があります。これは衝突反応の時にどれぐらいのレベルの少数の計算をするかみたいなことだと思うんですが、たとえば、wow.SIMPLE、つまり300とかにすると、球体が壁に接触したとき、細かい「跳ね」は無視されて、見た目上、ぺちょって感じな衝突反応をします。言葉で説明するのは難しいですので、実際にこのソースを試すときにSTANDARDをSELECTIVEや、SIMPLEなど、いろいろ値を変えてみてください。

wow.addMasslessForce(new WVector(0, -4, 0));で、y軸方向へ-4の力を発生させて、重力っぽい感じにしています。

wow.damping = .98;dampingを直訳すると、湿度、っていうことですが、たぶん力の加速度的なことを意味してるんだろうなと思って、高校で習った重力加速度g=0.98を代入してみました。0~1の値を与えられるのですが、0.4とか、0とかにしてもぜんぜん動かないし、試しに5とか入れてやると、パーティクルがどっかぶっ飛んでいきますし。

で、残りの行で、バウンドエリアを定義しています。WOWEngineは、wowインスタンスに対して、setBoundAreaとか、addParticle(addChildみたいなこと)をするみたいです。そのバウンドエリアを作るとき、papervisionの表示部で作るCubeと同じサイズにするとサンプルみたいな感じになります。

ここまでで初期化ゾーン。次に一秒ごとに発生するイベント、onTimerHandler()のなかで球を実際に作っていくのですが、Papervisionと同じサイズや形で作るとして、球体の物理オブジェクトを作るときは、WSphereを使います。

WSphereの引数は、

  1. xの初期位置
  2. yの初期位置
  3. zの初期位置
  4. パーティクルの大きさ(球体の大きさ)
  5. 固定粒子にするか否か(衝突や、力によって影響されるか否か)。物体がぶつかってきても微動だにしない、たとえば背景的なオブジェクトを作りたいときとかに便利です
  6. 質量。重いほうが跳ねない。けど衝突エネルギーは大きくなるはずだよね。バウンドエリアの設定にもよるのかな。で、ちなみに球と球で衝突したときには質量が大きいほうがよく飛ぶ
  7. 弾性。0~1かな。数値が大きければ大きいほど、よく跳ねます。初期値は0.3とかだけど、0.3だとあんまりおもしろくない気がします。0.8とか1とかぐらいのほうが球体っぽいですよ
  8. 摩擦係数。0~1。初期値は0

とりあえず初速度(.velocity)を定義しておいて、wow.addParticleで空間に加えます。ちなみに消すときは.removeParticleってのがあります。

最後のonEnterFrameHandler()にて、WOWパーティクルの座標の値を、Papervisionパーティクルの座標に代入してやって、物理演算を適用させます。

こんな流れでした。思ってたより簡単に作れたので、あとはパラメータの調整し具合で気持ちよくなるなぁという感じがします。

物理演算面白いです。これをとっかかりにして自分なりに物理演算フレームワークを作ってみるとより面白いかも。

Navigation

prev: AS3で音のボリュームやパンを操作する
next: AS3でAS2のMovieClip.enabledはどうなった

Recently Entries