PV3D2.1: 水の流れの表現

2009年10月 7日
前回のエントリーの続きです。
滝と川を作ったんですがそのアニメーションのさせ方なんかをメモしておきます。

walk3takitsubo.jpg
滝つぼの所は特に変わったことはしてません。
普通に水が広がるアニメーションのムービークリップを作って、
それをMovieMaterialにして貼っているだけです。
new MovieMaterial(MovieClip参照, false, true, false, new Rectangle(0, 0, 256, 256));
MovieMaterialは↑こんな感じに最後の引数で大きさを指定しておかないと
ムービークリップ全体の矩形サイズが変化してしまうようなアニメーションだった時に
マテリアルがずれてしまうようです。
またアニメーションさせる場合は第3引数をtrueにしないと
マテリアルが更新されずに止まったままになってしまいます。

あと仕組みを理解してるわけではないんだけど、
このマテリアルを毎フレーム更新する処理って結構重いんじゃないかなって思いました。
滝つぼは常に動いちゃってるのでしょうがないけど、
たまにしか見た目が変化しないアニメーションの場合は
第3引数をfalseにしておいて、見た目が変化するタイミングで
MovieMaterialクラスのdrawBitmap()を呼び出して
手動でテクスチャを更新した方がいいのかもしれません。

walk3water.jpg
川と滝の流れですが、こっちはMovieMaterialを使いませんでした。
テクスチャが一定方向にスクロールしているだけだったので
ポリゴンのUV値を弄ってマッピング座標をずらす事でアニメーションさせています。

water.jpg
川のテクスチャ

uvmap.jpg
maxで編集中の川のポリゴンのUV座標はこんな感じになってます。
全てのポリゴンでU軸が川に対して横方向、V軸が縦方向になるようにしてます。
これでV軸方向にUV値を変化させれば川のラインに沿って流れるようになるはず。

そして最初に作ったUV値を毎フレーム変化させるスクリプトがこれです。
TriangleMesh3Dオブジェクトが持っている全Triangle3Dオブジェクトの3つの頂点のUV値を弄っています。
//meshはTriangleMesh3Dオブジェクトです
for each(var face:Triangle3D in mesh.geometry.faces) {
	for each(var uv:NumberUV in face.uv) {
		uv.v -= 0.05;
	}
}
こんな感じでいけるかなーと思ったら何故か動かない・・・。
しばらくハマってたんですがどこかで見つけた記事で(どの記事だったか忘れてしまった)

「UV値を変更したらマテリアルとして使っているBitmapMaterialでresetMapping()をしないと見た目が変わらないよ」

という情報を発見。
早速川のBitmapMaterialをuv値を動かした後にresetMapping()したらやっと動きました。

しかししばらく流れる川を眺めていたら、今度は一定時間経過すると突然流れが止まってしまうトラブルが発生・・・。
これは未だにはっきりとした原因がまだわかっていないんですが、
どうもuv値が大きくなりすぎた時に表示がおかしくなってしまう気がします。
プラスだけじゃなくマイナス方向にスクロールさせても途中で止まります。
値が大きいといってもNumber型の限界を超えたとかそんなすごい数でもなく、
時間的に多分1000とかいってなかった気がする。

で、色々模索して
  • UV値が大きくなりすぎなければいい
  • UV値が0~1の間に収まっていれば問題なさそう
  • じゃあuv0.v = (uv0.v + 0.1)%1;とかやれば解決?
  • でもこれやると3つのuv座標の位置関係が崩れてテクスチャが反転したりするから駄目
  • じゃあ3つのuv値それぞれの初期値+オフセット値として、オフセット値を0~1に収めればいい
  • それには全三角ポリゴンの全UV値を最初に保存しておく必要がある
  • しかもスライドさせる時にどのUV値がどの初期値なのか調べる必要もある
という非常に面倒くさい方法に辿りつきました。
今思うとMovieMaterialでアニメーションさせればよかったかもしれない・・・。
その時に作ったクラスはこれです↓

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.utils.Dictionary;
	import org.papervision3d.core.geom.renderables.Triangle3D;
	import org.papervision3d.core.geom.TriangleMesh3D;
	import org.papervision3d.core.math.NumberUV;
	import org.papervision3d.materials.BitmapMaterial;
	/**
	 * ポリゴンのUV値をスライドさせてテクスチャを動かします。
	 * Papervision3D ver2.1.920で使えます。
	 */
	public class TextureSlider {
		private var faces:Dictionary;
		private var models:Dictionary;
		private var materials:Dictionary;
		private var sp:Sprite;
		//コンストラクタ
		public function TextureSlider() {
			sp = new Sprite();
			faces = new Dictionary();
			models = new Dictionary();
			materials = new Dictionary();
		}
		/**
		 * スライド対象のBitmapMaterialを登録します(登録しないとスライドしても見た目が変化しません)
		 * @param	bitmap
		 */
		public function addMaterial(bitmap:BitmapMaterial):void {
			materials[bitmap] = bitmap;
		}
		/**
		 * テクスチャをスライドさせるモデルを指定します
		 * @param	model	テクスチャをスライドさせたいモデル
		 * @param	speedU	1フレームで変化するU軸方向のスライド値
		 * @param	speedV	1フレームで変化するV軸方向のスライド値
		 */
		public function addModel(model:TriangleMesh3D, speedU:Number, speedV:Number):void {
			if (models[model] != null) return;
			models[model] = { slideU:0, slideV:0, speedU:speedU, speedV:speedV, target:model };
			for each(var face:Triangle3D in model.geometry.faces) faces[face] = { uvs:[face.uv0.clone(), face.uv1.clone(), face.uv2.clone()] };
		}
		/**
		 * 登録情報破棄&スライド停止
		 */
		public function dispose():void {
			faces = new Dictionary();
			models = new Dictionary();
			materials = new Dictionary();
			stopSlide();
		}
		/**
		 * スライド開始
		 */
		public function startSlide():void {
			sp.addEventListener(Event.ENTER_FRAME, onEnter);
		}
		/**
		 * スライドを停止
		 */
		public function stopSlide():void {
			sp.removeEventListener(Event.ENTER_FRAME, onEnter);
		}
		/**
		 * 1回スライドさせる
		 */
		public function stepSlide():void {
			for each(var m:Object in models) {
				m.slideU = (m.slideU + m.speedU) % 1;
				m.slideV = (m.slideV + m.speedV) % 1;
				for each(var face:Triangle3D in m.target.geometry.faces) {
					var fdata:Object = faces[face];
					for (var i:int = 0; i <= 2; i++ ) {
						face.uv[i].u = fdata.uvs[i].u + m.slideU;
						face.uv[i].v = fdata.uvs[i].v + m.slideV;
					}
				}
			}
			for each(var bm:BitmapMaterial in materials) if (bm != null) bm.resetMapping();
		}
		//毎フレーム処理
		private function onEnter(e:Event):void {
			stepSlide();
		}
	}	
}

使い方は以下のような感じで。

//plane1とplane2がマテリアルをスライドさせたいモデル
//map1とmap2がモデルに貼ってあるBitmapMaterial
var slider:TextureSlider = new TextureSlider();
slider.addModel(plane1, 0, 0.02);
slider.addModel(plane2, 0, 0.02);
slider.addMaterial(map1);
slider.addMaterial(map2);
slider.startSlide();
負荷は検証していないので どのくらいのポリゴン数まで耐えられるかちょっとわかりません。
常に一定速度で動くものにしか対応できないので もっと汎用性をもたせた設計にすればよかったかも。

トラックバック(0)

トラックバックURL: http://www.morocoshi.net/mt/mt-tb.cgi/17

コメントする