パーティクル祭りの原点「Liquid10000」を初級者が解説してみる。
パーティクル祭りの原点「Liquid10000」を解説してみる。
[冒頭のコメントに追記あり](Download後の閲覧推奨)
前回のPerlinNoiseはこれの伏線だったのだよ!(偶然ですけど)
今回はPerlinNoiseの復習と、ビット演算子の勉強になります。
前々回
http://wonderfl.kayac.com/code/03190641c00b157abc0dbe7d6a513fa80ec987db
前回
http://wonderfl.kayac.com/code/cafcb78df40b35b0c0d571dc8fd8f1bda791e4b4
フォースマップっていう考え方らしいですが、
PerlinNoiseの色を速度に変換するらしいです。
てっきり難しい物理演算を使ってると思ってたよ・・・。
あと、前々回にやった花火の処理も加わってますので、
前々回・前回の内容を知ってると、にやけることができると思います。
これが分かると、あのsnowとかの作り方もわかるかな・・・?
次回はそれにするかもしれません。しないかもしれません。
パーティクル祭りに関してはclockmaker様のまとめが詳しいです。
この記事を元に、自身で高速化を試してみるのもありだと思います。
http://clockmaker.jp/blog/2009/04/particle/
http://clockmaker.jp/blog/2009/04/particle_fes/
Fork元を製作された方へ。
・Forkを拒否されたい場合は、何かしらのアクションでお伝えください。
削除いたします。(できれば生暖かく見守っていただけると嬉しいです)
中級者・上級者の方へ。
・何かアドバイスや校正箇所があれば、ご指摘お願いします。
初心者・初級者の方へ。
・一緒に勉強しましょう。何か質問があればどうぞ。
自分も初級者なので、答えられるか分かりませんが。(笑
[追記]
朝起きて見てみたらFavoriteの多さにびっくりして目が覚めました。
あくまで解説のみで、自分の作品ではないということで恐縮ではありますが、
こういう試みが支持されてるということは、自分の考えは間違ってなかったんだなと、
確信している次第です。
package {
import flash.display.Stage;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.ColorTransform;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.events.Event;
import flash.events.MouseEvent;
[SWF(width=465, height=465, backgroundColor=0x000000, frameRate=30)];
/*
* パーティクル祭りの原点「Liquid10000」を解説してみる。
* [冒頭のコメントに追記あり](Download後の閲覧推奨)
*
* 前回のPerlinNoiseはこれの伏線だったのだよ!(偶然ですけど)
* 今回はPerlinNoiseの復習と、ビット演算子の勉強になります。
*
* 前々回
* http://wonderfl.kayac.com/code/03190641c00b157abc0dbe7d6a513fa80ec987db
* 前回
* http://wonderfl.kayac.com/code/cafcb78df40b35b0c0d571dc8fd8f1bda791e4b4
*
* フォースマップっていう考え方らしいですが、
* PerlinNoiseの色を速度に変換するらしいです。
* てっきり難しい物理演算を使ってると思ってたよ・・・。
*
* あと、前々回にやった花火の処理も加わってますので、
* 前々回・前回の内容を知ってると、にやけることができると思います。
*
* これが分かると、あのsnowとかの作り方もわかるかな・・・?
* 次回はそれにするかもしれません。しないかもしれません。
*
* パーティクル祭りに関してはclockmaker様のまとめが詳しいです。
* この記事を元に、自身で高速化を試してみるのもありだと思います。
* http://clockmaker.jp/blog/2009/04/particle/
* http://clockmaker.jp/blog/2009/04/particle_fes/
*
* Fork元を製作された方へ。
* ・Forkを拒否されたい場合は、何かしらのアクションでお伝えください。
* 削除いたします。(できれば生暖かく見守っていただけると嬉しいです)
*
* 中級者・上級者の方へ。
* ・何かアドバイスや校正箇所があれば、ご指摘お願いします。
*
* 初心者・初級者の方へ。
* ・一緒に勉強しましょう。何か質問があればどうぞ。
* 自分も初級者なので、答えられるか分かりませんが。(笑
*
* [追記]
* 朝起きて見てみたらFavoriteの多さにびっくりして目が覚めました。
* あくまで解説のみで、自分の作品ではないということで恐縮ではありますが、
* こういう試みが支持されてるということは、自分の考えは間違ってなかったんだなと、
* 確信している次第です。
*
* そしてこういう解説ができるのも、勉強したいと思わせられる素敵な作品たちが
* このwonderflに溢れているからだと思います。製作者の皆さんに多謝。
*
* これからもマイペースでやっていきたいと思いますが、あまりに高度なものは
* 初級者の自分では解析不可能なので、こういう解説を中級者・上級者の方も
* してくださったら、もっとwonderflのすばらしさというのが共有できるんじゃないかと
* 思う次第です。
*
* あ、あと、clockmakerさん、
* 「extends Progression 拡張機能コンテスト」ダブル受賞おめでとうございますー。
*
*/
/*
* おおまかな流れ
* 1.パーティクルを10000個作る
* 2.パーティクル情報を打ち込むためのBitmapを作る
* 3.PerlinNoiseを作る
* 4.パーティクル情報を打ち込むためのBitmapの色を落とす
* 5.PerlinNoiseの情報を元にパーティクルの加速度・速度・位置を求める
* 6.求められたパーティクルの情報に従って、Bitmapに色を打ち込む
* 7.4・5・6の処理をパーティクル毎に処理する
* 8.4・5・6・7の処理を毎フレーム行う
*/
public class Liquid10000 extends Sprite {
private const nums:uint = 10000;
private var bmpDat:BitmapData;
private var vectorDat:BitmapData;
private var randomSeed:uint = Math.floor( Math.random() * 0xFFFF );
private var bmp:Bitmap;
private var vectorList:Array;
private var rect:Rectangle;
private var cTra:ColorTransform;
public function Liquid10000() {
initialize();
}
private function initialize():void {
//stage関連の設定
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//Bitmapを作ってaddChild(描画される、メインのBitmap)
bmpDat = new BitmapData( 465, 465, false, 0x000000 );
bmp= new Bitmap( bmpDat );
addChild( bmp );
//PerlinNoiseを描く(addChildされてないのが肝)
//PerlinNoiseについては、前回やりましたね。なので解説は省略。
vectorDat= new BitmapData( 465, 465, false, 0x000000 );
randomSeed = Math.floor( Math.random() * 0xFFFF );
vectorDat.perlinNoise( 230, 230, 4, randomSeed, false, true, 1 | 2 | 0 | 0 );
//どんなPerlinNoiseか見たい人は、下のaddChildのコメントアウトを消して、
//EnterFrameのaddEventListenerをコメントアウトしてみてください。
//引数からもわかりますが、赤と緑(RG)しか使われていませんね。
//これは理由があります。記憶の片隅にとどめておいてください。
//addChild(new Bitmap(vectorDat));
//stageの大きさの矩形範囲を設定
//colorTransformでの範囲設定に使われます。
rect = new Rectangle( 0, 0, 465, 465 );
//時間経過とともにパーティクルの残像を暗くして、軌跡を作るための色設定。
//花火でもありました。重要なパラメータです。
cTra= new ColorTransform( 0, .8, .8, .9 );
//パーティクルを入れるための配列
vectorList= new Array();
//パーティクルを1万個作ってvectorListに押し込む
for (var i:uint = 0; i < nums; i++) {
//パーティクルの場所:X・YともにRandom
var px:Number = Math.random()*465;
var py:Number = Math.random() * 465;
//X・Y軸の加速度
var av:Point = new Point( 0, 0 );
//X・Y軸の速度
var vv:Point = new Point( 0, 0 );
//最初に求めたpx,pyを代入
var pv:Point = new Point( px, py );
//一番下に定義されているパーティクルの情報を持つVectorDat型のオブジェクトを生成して
//位置情報・速度・加速度情報を入れ込む
var hoge:VectorDat = new VectorDat( av, vv, pv);
//配列に押し込む
vectorList.push( hoge );
}
//メインタイムラインのEnterFrameイベントをloop関数に関連付ける
addEventListener( Event.ENTER_FRAME, loop );
//stageがクリックされたらresetFunc関数が呼び出されるようにする。
stage.addEventListener( MouseEvent.CLICK, resetFunc );
}
//毎フレーム呼び出される関数
private function loop( e:Event ):void {
//描画用のBitmapDataをcolorTransformで暗くする(重要)
//これにより、過去の色が薄くなるのは、以前解説した花火の描画の時も一緒でしたね。
//これがないとどうなるのかは、花火のとき同様、コメントアウトしてみてください。
bmpDat.colorTransform( rect, cTra );
//vectorListのコピーを作成
var list:Array = vectorList;
//listの長さをlenに代入しておく
//なんかこうすると、for文の処理が早いらしい?
var len:uint = list.length;
//このクラスの根幹処理なので超重要!
//for文でlistの最初のエレメントから走査
for (var i:uint = 0; i < len; i++) {
//listのi番目のエレメントをdotsに代入
var dots:VectorDat = list[i];
//PerlinNoiseが描かれているvectorDatの色を抜き出す。
//dotsの場所(dots.pv.x, dots.pv.y)にあるピクセルの色を抜き出し(getPixel)ます。
var col:Number = vectorDat.getPixel( dots.pv.x, dots.pv.y );
//上で抜き出した色から、さらにRGそれぞれの色を抜き出します。
//後の処理で、RはX軸の加速度に、GはY軸の加速度に変換されます。
//Bがないのは、別にZ軸とかその他の処理がいらない(RとGだけで十分)からですね。
//これで、何故PerilinNoiseがRGしか使われてないか理由が判明しました。
//var r:uint = col >> 16; これでもOK
var r:uint = col >> 16 & 0xff;
var g:uint = col >> 8 & 0xff;
//var b:uint = col & 0xff;
/* ビット演算がわかってる人はスルーで。読む人は、紙に書きながら一緒にやってみるとわかりやすいかもしれません。
* (というか自分も勉強しながら解説してるんで、偉そうなこといえない)
* 上の式は普段あまり見ないですが、ビット演算と呼ばれるものです。主に色を弄るときに使われたりします。多分。
* 例えば、
*
* var color:Number = 0x99C6FF;
* trace(color.toString(2));
*
* とやってみると、100110011100011011111111とでます。
* これは、0x99C6FFの2進数バージョンです。つまり、
* 最初の8ビット(10011001)がRで0x99
* 真ん中の8ビット(11000110)がGで0xC6
* 最後の8ビット(11111111)がBで0xFF
* ということになります。
* で、これからどうやってそれぞれの色を抜き出すかというと、例えばRを例に説明すると
* >>16 とやることで、16ビット分右にシフトされます。すると、10011001(最初の8ビット)となります。
* これで最初の8bit、つまり赤色が取り出せたということになります。
* 右側にあふれた数値はどうなるかというと破棄されます。それだけではないらしいのですが、よく分かりません(ずれた分左側を符号で埋める埋めないとかなんとか)。
*
* さて、Gは「col >> 8 & 0xff」となってますが、col >> 8はいいとして、&はなんでしょう。Rでも使われてますね。
* これは何かというと論理積というらしいです。左辺と右辺のビット単位で掛け算するってことですね。
* 言葉だけだと分からないので、まずはGを例としてやってみましょう。
* まず、col >> 8で8ビット右にシフトすると、1001100111000110(RGの色)となりますね。これに0xff(11111111)で論理積を求めるということは、
*
* 1001100111000110
* & 11111111
* -------------------
* 11000110
*
* ということになります。(式がずれていたら数値を右寄せで整列してください)
* 一番右を見てみると、0*1は0だから、答えは0ですね。右から2番目は1*1なので1です。
* これが論理積(AND)・・・らしいです。逆に論理和(OR)というのもありますが、これはどちらかが1なら1ってやつですね。
* if等の条件式のandとorを連想すると分かりやすいかと思います。
*
* Rの部分(前の8ビット)は掛け算されることすらないので、0と考えてください。ということは、答えが1になるはずもないですね。
* 感覚としては、フィルターをかける感じと考えていただければいいと思います。とにかく、これでGが出ました。
* ということは、Bはどうすれば導き出せるでしょう?答えは書いてありますが、自分で考えてみましょうー。
*/
//赤色をX軸の加速度に変換
//r-128は負の方向にも平等に移動させるため。
//赤色が129以上だったら正の方向に、127以下だったら負の方向にそれだけ移動するということですね。
//.0005は多分目算での速度調整用数値だと思います。
dots.av.x += ( r - 128 ) * .0005;
//緑はY軸ですね。
dots.av.y += ( g - 128 ) * .0005;
//あとはそれぞれの速度に加速度をプラスして、位置を調整します。
dots.vv.x += dots.av.x;
dots.vv.y += dots.av.y;
dots.pv.x += dots.vv.x;
dots.pv.y += dots.vv.y;
var _posX:Number = dots.pv.x;
var _posY:Number = dots.pv.y;
//摩擦というか空気抵抗というか、速度が増え続けないように少し速度と加速度を落とす感じですね。
dots.av.x *= .96;
dots.av.y *= .96;
dots.vv.x *= .92;
dots.vv.y *= .92;
//ステージ外に移動した場合の処理です。若干分かりづらいですが、3項演算子の入れ子になってます。
/* 3項演算子はif文の簡略化されたやつという感じです。定義的には怪しいかもしれませんが。
* if文にすると・・・
*
* if ( _posX > 465 ) {
* dots.pv.x = 0; //465を超えたら0(反対側)に移動する
* } else {
* if ( _posX < 0 ) {
* dots.pv.x = 465 //0より小さくなったら465(反対側)に移動する
* else {
* 0; //これよく分からないですけど、処理しない(false)ってこと・・・?
* }
* }
*/
( _posX > 465 )?dots.pv.x = 0:
( _posX < 0 )?dots.pv.x = 465:0;
//Y軸も同様に。
( _posY > 465 )?dots.pv.y = 0:
( _posY < 0 )?dots.pv.y = 465:0;
//1*1の四角形をdots.pv.x, dots.pv.yの位置に0xFFFFFFの色で描く
bmpDat.fillRect( new Rectangle( dots.pv.x, dots.pv.y, 1, 1), 0xFFFFFF );
}
}
//クリックで呼び出される関数。
//これと同じ処理が上でもありましたね。なので、解説はしません。
//・・・が。同じ処理っていうことは、記述的には無駄かもしれませんね。どうすればその無駄を省けるのか考えてみましょう。
//ヒント(というか答え):最初のinitialize関数のときにもこれを呼び出せばいいじゃん?でもe:MouseEventが邪魔だなぁ・・・。
// この引数がなくても関数が使えるようにできないかなぁ・・・。あ、デフォルトの値を入れておけばいいんじゃね?
private function resetFunc(e:MouseEvent):void {
randomSeed= Math.floor( Math.random() * 0xFFFF );
vectorDat.perlinNoise( 230, 230, 4,randomSeed, false, true, 1|2|0|0 );
vectorList= new Array();
for (var i:uint = 0; i < nums; i++) {
var px:Number = Math.random()*465;
var py:Number = Math.random()*465;
var av:Point = new Point( 0, 0 );
var vv:Point = new Point( 0, 0 );
var pv:Point = new Point( px, py );
var hoge:VectorDat = new VectorDat( av, vv, pv);
vectorList.push( hoge );
}
}
}
}
//パーティクル用のクラスです。花火のときと似てる感じですね。これも説明は不要ですかね。
import flash.geom.Point;
class VectorDat {
public var vv:Point;
public var av:Point;
public var pv:Point;
function VectorDat( _av:Point, _vv:Point, _pv:Point ) {
vv = _vv;
av = _av;
pv = _pv;
}
}