第11回から前回の第16回までで、パーティクルの作例「Away3D 15/03/13: Animating particles simulating fire」(第16回サンプル1)を仕上げた。今回から取り組むお題も、Away3D TypeScriptサイトの「Examples」からパーティクルの作例「Exploding browser logos using particles」を参考にする(図1)。
ロゴがひとつパーティクルで弾けるアニメーション
前のお題でわかったように、パーティクルをつくるにはアニメーションなどの下ごしらえが手間取り、順に進めていってもなかなか動きが目で確かめられるコードにならない。そこで、今回目指すパーティクルのアニメーションをひとつつくるまでのスクリプトは、初めにコード1としてまとめて示すことにする。
見てのとおり、すでにそれなりの行数だ。もっとも、前回のパーティクルのコードと仕組みが似ている部分も多い。それらも参考にしながら解説していこう。動きは、jsdo.itに掲げたサンプル1で確かめられる。なお、画像として読み込むChromeロゴのファイルは、awayjs-examplesの中のbin/assets/chrome.pngを用い、assetsフォルダに納めた(図2)。
初期設定で呼び出す関数
前掲コード1の初期設定の関数(initialize())は、つぎの5つの関数を呼び出している。これらの関数の中のcreateView()は、第16回コード3とまったく同じである。また、関数setupCameraController()も引数とプロパティの設定が減っただけだ。この2つの関数は抜き書きしないので、前掲コード1を直接参照してほしい。
- createLights()
- createView()
- setupCameraController()
- createMaterial()
- setListeners()
関数createLights()は、つぎのように光源をつくったうえでStaticLightPickerオブジェクトに納めて返す。ただし、光源はパーティクルを照らすだけなので、平行光源(DirectionalLightオブジェクト)は使わず、PointLightオブジェクトをつくっている(第14回「炎の光を加える」参照)。PointLightオブジェクトは、後に複数つくることになるため、関数(createPointLight())を分けた。PointLightオブジェクトに定めているプロパティは、すでにこの連載で紹介したので、以下に表1としてまとめておこう。
表1 LightBaseクラスのプロパティ
LightBaseクラスのプロパティ | 値と機能 |
ambient | 環境光の強さを示す0以上1以下の数値(デフォルト値0) |
ambientColor | 環境光のカラー値を示す0から0xFFFFFFまでの整数(デフォルト値0xFFFFFF) |
color | 光のカラー値(デフォルト値0xFFFFFF) |
diffuse | 光の拡散する強さを示す0以上の数値(デフォルト値1) |
fallOff | 光が届く距離の最大値(デフォルト値10000) |
radius | 光が届く距離の最小値(デフォルト値9000) |
specular | 光の反射する強さを示す0以上の数値(デフォルト値1) |
関数createMaterial()は、つぎのようにマテリアル(MethodMaterialオブジェクト)をつくって返す。MaterialBase.bothSidesプロパティは、マテリアルのカメラと反対側の面を描画するかどうかを定める。負荷を減らすため、描画しないfalseがデフォルト値だ。このプロパティをtrueとし、MaterialBase.lightPickerプロパティには、PointLightオブジェクトが納められたStaticLightPickerを与えた。
関数setListeners()の仕事のひとつは、ロゴの画像ファイル(assetsURL)をAssetLibrary.load()メソッドでロードし、読み込み終えたとき(LoaderEvent.RESOURCE_COMPLETEイベント)のリスナー関数(onResourceComplete())を定めることだ。この関数は、ロードした素材をCast.bitmapData()メソッドの引数に渡して、BitmapDataオブジェクトを得る。このオブジェクトからは、ピクセル単位でカラー値が調べられる。そのカラーをパーティクルひとつひとつに当てようという目論見だ。
素材を読み込み終えたときのリスナー関数(onResourceComplete())からは、さらにつぎの2つの関数が呼び出される。ひとつは、パーティクルの幾何学情報のGeometryオブジェクトやそれらのカラーと位置、およびアニメーションのデータをつくる関数(createParticles())だ。そしてもうひとつは、Meshオブジェクトをつくって、パーティクルのアニメーションを初期設定する関数(startParticleAnimation())になる。
- createParticles()
- startParticleAnimation()
パーティクルとアニメーションを定める関数
関数createParticles()は、つぎの2つの関数を呼び出したあと、以下のようにパーティクルのParticleGeometryオブジェクト(colorGeometry)をつくって返す。呼び出したひとつ目の関数(createColorAnimationSet())は、ParticleAnimationSetオブジェクトを返すので、それが変数(colorAnimationSet)に与えられる。2つ目の関数(setParticlesData())は、読み込んだロゴのBitmapDataオブジェクトからピクセルごとのカラー値と位置座標をそれぞれ変数の配列(colorValuesとcolorPoints)に納める。
- createColorAnimationSet()
- setParticlesData()
パーティクルの幾何学情報のGeometryオブジェクト(geometry)はPrimitivePrefabBase.geometryプロパティから得た。Geometryオブジェクトの配列(colorGeometrySet)は、ParticleGeometryHelper.generateGeometry()メソッドに渡すと、ParticleGeometryオブジェクトができる(第12回「パーティクルのオブジェクトを加える」参照)。これとMethodMaterialオブジェクト(前述変数colorMaterial)をMesh()コンストラクタに渡せば、パーティクルの平面オブジェクトがつくれる。なお、配列に納めるGeometryオブジェクトの数はロゴのBitmapDataオブジェクトの総ピクセル数(count)だ。
関数createColorAnimationSet()は、以下のとおりParticleAnimationSetオブジェクト(colorAnimationSet)をつくって返す。オブジェクトには、AnimationNodeBase(のサブクラス)のオブジェクトをParticleAnimationSet.addAnimation()メソッドで加える(第12回「パーティクルにアニメーションを定める」および第13回「パーティクルのカラーをアニメーションさせる」参照)。ParticleNodeBaseのサブクラスのコンストラクタを、前回のパーティクルのお題ですでに用いたものも含めて表2にまとめた(なお、ParticleNodeBaseはAnimationNodeBaseのサブクラスだ)。関数が引数に受け取った初期化の関数(initParticleFunc)は、ParticleAnimationSet.initParticleFuncプロパティに定めた。
表2 ParticleNodeBaseのサブクラスのコンストラクタ
ParticleNodeBaseのサブクラスのコンストラクタ | 機能 |
ParticleBezierCurveNode(モード, コントロールポイント, 終点) | 時間に応じた位置をベジエ曲線で定める |
ParticleBillboardNode() | パーティクルの角度をつねにカメラに向ける。
|
ParticleColorNode(モード, 乗数データの使用, オフセットデータの使用, usesCycleの使用, usesPhaseの使用, 初めの色, 終わりの色) | パーティクルアニメーションの時間に応じた色の変わり方を定める |
ParticleInitialColorNode(モード, 乗数データの使用, オフセットデータの使用, 初めの色) | パーティクルの初めの色を定める |
ParticlePositionNode(モード, 位置ベクトル) | パーティクルの初めの位置を定める |
ParticleScaleNode(モード, usesCycleの使用, usesPhaseの使用, 最小伸縮率, 最大伸縮率) | パーティクルアニメーションが時間に応じてどう伸縮するかを定める |
ParticleVelocityNode(モード, 速度ベクトル) | パーティクルアニメーションが始まるときの速度を定める |
関数setParticlesData()には、つぎのように4つの引数を渡す。第1引数のBitmapDataオブジェクトと第2引数のパーティクルの大きさにもとづいて、第3引数と第4引数の配列にそれぞれピクセルごとのカラー値と位置座標を納める。2つの配列は空のまま受け取る前提だ。
BitmapDataオブジェクトのピクセルごとのカラー値(color)は、水平と垂直のふたつのインデックスを引数にしてBitmapData.getPixel32()メソッドで調べられる。座標(point)は、BitmapDataオブジェクト(bitmapData)の中心を原点(0, 0)として関数の第2引数の大きさ(size)に比例させ、Vector3Dオブジェクト(point)で定めた(z座標はデフォルト値0)。
関数getRgbComponents()は、カラー値の整数からRGB成分値を取り出し(「2進数・16進数とビット演算」05「カラー値とビット演算」参照)、Vector3Dオブジェクトのxyzプロパティに納めて返す。なお、関数setParticlesData()では、受け取ったオブジェクトにVector3D.scaleBy()メソッドを用いて、256階調の値を0~1の比率に変えた。これは、後でパーティクルを初期化するとき(initColorParticle()関数)、カラーをColorTransformオブジェクトで定めるためだ(第13回「パーティクルのカラーをアニメーションさせる」参照)。こうして得られたピクセルごとのカラーと座標は、それぞれ配列の変数(colorValuesとcolorPoints)に納めている。
関数startParticleAnimation()は、パーティクルのアニメーションを初期設定する。引数に受け取ったParticleGeometryオブジェクト(colorGeometry)とMethodMaterialオブジェクト(colorMaterial)をMesh()コンストラクタに渡して、パーティクルのオブジェクト(colorParticleMesh)がつくられる。そのMesh.animatorプロパティにParticleAnimatorオブジェクト(animator)を定めたうえで、SceneオブジェクトにScene.addChild()メソッドで加えた。
パーティクルを初期化する関数
パーティクルを初期化する関数は、前述のParticleAnimationSetオブジェクトをつくる関数(createColorAnimationSet())がParticleAnimationSet.initParticleFuncプロパティに定めたinitColorParticle()だ。
プロパティParticleProperties.startTimeとParticleProperties.durationについてはよいだろう(第12回「パーティクルのアニメーションを動かす」参照)。パーティクルのカラーと座標は、配列の変数(colorValuesとcolorPoints)からインデックス(index)にしたがって取り出し、それぞれ引数のオブジェクト(properties)のParticleInitialColorNode.COLOR_INITIAL_COLORTRANSFORMとParticlePositionNode.POSITION_VECTOR3Dプロパティに定める。
今回のアニメーションの動きを決める鍵となるのは、ParticleBezierCurveNodeのプロパティの定めだ。名前から想像がつくように、ベジエ曲線で動き方を決める。その終点(アンカーポイント)はParticleBezierCurveNode.BEZIER_END_VECTOR3D、コントロールポイントがParticleBezierCurveNode.BEZIER_CONTROL_VECTOR3Dで、ともにVector3Dオブジェクトを与える。前者(endPoint)は引数なしのVector3D()コンストラクタでつくられた値なのでデフォルト値の原点(0, 0, 0)、つまりもとの位置だ。後者は、別に定めた関数getRandomVector3D()により、引数の距離以内のランダムなVector3Dオブジェクトが与えられる。
すると、パーティクルのアニメーションは、コントロールポイントにより一旦初めの位置から遠ざかり、その後終点に定めたもとの位置に戻ってくることになる。なお、関数getRandomVector3D()の距離とふたつの角度から座標を求める計算については、第13回「パーティクルの動きにランダムな広がりを与える」を参照してほしい。
パーティクルをアニメーションさせる関数
関数setListeners()は、RequestAnimationFrameオブジェクトでアニメーションのコールバック関数render()を定めている。この関数からAnimatorBase.update()メソッドを呼び出した。総経過時間を引数として、アニメーションの状態が改められる。これがこのアニメーションの2つ目の鍵だ。
RequestAnimationFrame()コンストラクタに渡したコールバック関数(render())は、引数として前回の呼び出しからの経過時間(deltaTime)を受け取る。その値を変数(time)に加えて、総経過時間をもたせた。ところが、AnimatorBase.update()メソッドにはこの値を直に与えず、Math.sin()メソッドで手を加えた値(_time)が渡されている。この式が、初めに掲げたサンプル1の動きをつくっているのだ。
試しに、前掲コードをつぎのように書き替えて、AnimatorBase.update()メソッドに総経過時間(time)に調整係数を乗じて引数として渡してみよう。前述のとおり、ParticleBezierCurveNodeのプロパティでベジエ曲線のコントロールポイントをランダムな距離で外側に定めたため、パーティクルはばらばらに広がり出す。だが、終点(アンカーポイント)はもとの位置なので、やがて引き戻されていく。そして、その先は慣性にしたがってそのまま進み続け、やがて画面の外に消えてしまう。これが、総経過時間をひたすら増やした結果だ。
三角関数のsinは、物理のバネ運動の式に用いられ、±1の範囲で増減を繰り返す(図3)。前掲の式では1を加えているので、0~2の間をバネのように変化する。さらに調整係数で、変化の速さと幅を決めた結果、0から一定時間の間を行ったり来たりすることになった。そのため、パーティクルが広がったり、縮まったりというアニメーションを繰り返したのだ。
ようやく、ロゴひとつをパーティクルでアニメーションさせるコード1が説明し終えた。jsdo.itに掲げたサンプル1も試してみるとよいだろう。
次回からは、書き加えたコードの動きを確かめながら解説する。実は、今回のパーティクルのお題が、この連載の結びとなる。あとしばらくおつき合い願いたい。