HTML5 Canvasを始めよう:EaselJSを使った流体パーティクル入門

HTML5のCanvasをFlashライクに使えるようにするJavaScriptライブラリ「EaselJS」で流体パーティクルのデモを作ってみました。

EaselJSを使えばFlashのようにHTML5 Canvasを使うことができるので、ActionScriptから概念だけではなくコードの再利用性が高まります。今回は流体パーティクルの表現を通して、EaselJSとCanvasのテクニックを紹介していきます。

デモの作り方

冒頭のデモですが、私が以前作成したFlashの作品「Flashの高速化を試す、BitmapDataを配列に格納することで2〜3倍の高速化」から移植したものです。

さて、このデモをHTML5 Canvasに展開するにあたり、特に抑えておきたいのは次の点だと思いました。

  • フォースマップの作成
  • パーティクル情報を付与した表示クラスの作成
  • フォースマップの適用と計算

フォースマップの作成

フォースマップというのは流体パーティクルを実装する際によく使われる手法で、雲模様の画像からパーティクルの移動方法を決定するのに使うものです。今回は次のフォースマップの画像ファイルを利用しました。

▲フォースマップの画像ファイル。フォースマップはPhotoshopのフィルターの雲模様を使って作成可能。

画像には赤・緑・青の3チャンネルの情報が含まれています。3つのチャンネルはそれぞれ微妙に雲模様の形状が違うことに注目ください。このカラー情報を速度に変換するのですが、X方向は赤のチャンネルを、Y座標には緑のチャンネルを、という感じで使うことになります。それぞれのチャンネルで模様が違うので、X方向とY座標でバラバラの動きをすることになります。

▲赤のチャンネル

▲緑のチャンネル

▲青のチャンネル

カラー情報をパーティクルの速度に変換する方法は後ほど解説しますので、まずはこのフォースマップをJavaScriptに展開する方法を紹介します。

JavaScirptのImageクラス(imgタグのJavaScript用クラス)を使ってフォースマップの画像ファイルを読み込みます。

forceMapImage = new Image();
forceMapImage.onload = initForceMap;
forceMapImage.src = "http://clockmaker.jp/labs/120201_easeljs_forcemap/forcemap.png";

function initForceMap() {
 // 読み込み完了
}

onloadイベント使って、画像読み込み終了時のイベントを使います。次にこのフォースマップの画像から、色情報を取得するために、Canvas要素に転写します。ActionScriptではBitmapDataクラスを使って画像の色情報を解析することができました。JavaScriptではCanvas要素にそれに相当する機能があります。前回も紹介したとおり、Canvas要素はbodyタグに追加 (appendChild)しなくても機能するので、スクリプト上でダミーのCanvasのインスタンス(下記のスクリプトのforceMapCanvasインスタンスが該当)を作成しておきます。

function initForceMap() {
    var forceMapElement = document.createElement("canvas");
    forceMapElement.setAttribute("width", forceMapImage.width);
    forceMapElement.setAttribute("height", forceMapImage.height);

    forceMapCanvas = forceMapElement.getContext("2d");
    forceMapCanvas.drawImage(forceMapImage, 0, 0);
}

なお余談ですが、フォースマップはActionScriptだとBitmapDataクラスのperlinNoise()メソッドを使ってスクリプト内で生成することが可能です。Canvasでも頑張ればコードによってperlinNoise()を自前で作ることができますが、APIの豊富さでもActionScriptのほうが一日の長があると思いました。

パーティクル情報を付与した表示クラスの作成

次に矢印のグラフィックを表示するための表示クラスを作成します。EaselJSにはBitmapクラスがあり、それを使えば画像のXY座標や回転を扱うことができます。今回のデモの場合は、それらのプロパティーに加えて加速度や速度のプロパティーを扱いたいのでBitmapクラスを継承したArrowクラスを作成しました。

JavaScriptのクラスの作成方法は今回は割愛しますが(後日、別の記事で紹介します)、次のようなスクリプトとして記述します。コンストラクタで記述しているvxとvyは速度用のプロパティー、axとayは加速度用のプロパティーです。

(function (window) {
    function Arrow(imageOrUri, x, y) {
        this.x = x;
        this.y = y;
        this.vx = 0;
        this.vy = 0;
        this.ax = 0;
        this.ay = 0;
        this.initialize(imageOrUri);
        this.regX = 10;
        this.regY = 10;
    }

    // EaselJSのBitmapクラスを継承
    var p = Arrow.prototype = new Bitmap();

    p.step = function (colorA, colorB) {
        // 省略
    };
    window.Arrow = Arrow;
}(window));

regXとregYは画像の基準点を設定するBitmapクラスのプロパティーです。今回利用する画像が20px四方の矢印画像で回転時の基準点を(X, Y) = (10px, 10px)に設定します。

フォースマップの適用と計算

次にフォースマップから速度に変換する方法を紹介します。Canvas要素(Context2Dオブジェクト)のgetImageData()メソッドを呼び出すことで、指定したピクセル位置からのカラー情報を取得することができます。今回は指定したピクセルのデータだけが欲しいので、1px四方で情報を取得します。

var arrow = particleList[i];
var imageData = forceMapCanvas.getImageData(
    arrow.x >> 1,
    arrow.y >> 1,
    1,
    1);

▲例えば、(200px, 150px)の箇所の座標の1px四方で測定すると[200, 128, 102, 255]というカラー情報の配列が取得できる。

取得したImageDataオブジェクトには1次元配列として、赤・緑・青・透明度の順番でピクセル分繰り返してカラー情報(0〜255の数値)が入っています。今回は1pxごとの赤・緑・青の情報だけを使うのですが、赤と緑のチャンネルだけ情報を使う場合は次のように0と1を指定して、Arrowインスタンスのメソッドを呼び出します。

var colorR = imageData.data[0];
var colorG = imageData.data[1];
arrow.step(colorR, colorG);

Arrowクラスのstep()メソッドでは、このカラー情報→加速度→速度→座標/回転へと変換します。カラー情報は1チャンネルあたり0〜255の数値になるので、128を減算して-128〜128までの数値を扱うようにします。X方向に対して適用する場合は、負の値であれば左方向に移動し、正の値であれば右方向に移動することになります。

p.step = function (colorA, colorB) {
    this.ax += ( colorA - 128 ) * .0005;
    this.ay += ( colorB - 128 ) * .0005;
    this.vx += this.ax;
    this.vy += this.ay;
    this.x += this.vx;
    this.y += this.vy;

    this.rotation = Math.atan2(this.vy, this.vx) * 180 / Math.PI;

    this.ax *= .96;
    this.ay *= .96;
    this.vx *= .92;
    this.vy *= .92;

    ( this.x > 465 ) ? this.x = 0 : ( this.x < 0 ) ? this.x = 465 : 0;
    ( this.y > 465 ) ? this.y = 0 : ( this.y < 0 ) ? this.y = 465 : 0;
};

▲例えば、(200px, 150px)の箇所の座標のカラー情報の赤色成分を調べると200という値が取得できます。この値は100分率で示すと80%なので、X方向に向かって+30% (50+30=80%)の加速量となる。

▲例えば、(100px, 350px)の箇所の座標のカラー情報の赤色成分を調べると25という値が取得できます。この値は100分率で示すと10%なので、X方向に向かって-40% (50-10=40%)の加速量となる。

エンターフレームイベントに相当するtick()関数では、パーティクルの個数分だけfor文を回して上記の処理を行い、Canvasをレンダリングして処理は終了となります。

最後に

今回はEaselJSというよりは、Canvas要素のカラー情報の扱いの方法の解説でした。前回も紹介したとおり、Canvas要素はFlashでいうところのBitmapとBitmapDataとGraphicsをごちゃまぜにしたようなHTML5の新要素ですが、EaselJSをはじめとしたフレームワークでは移植できていないAPIも多々あります。そのためEaselJSを学習するのに加えてCanvas要素の各機能を学習することで表現可能なことが増えると思います。

ちなみにFlashだと同じような実装で1000個の表示が可能でしたが、Canvas(+EaselJS)だと200個ぐらいが限度でした。

関連記事

投稿者 : 池田 泰延

BookMark

ブックマークはこちらからどうぞ。

このエントリーをはてなブックマークに追加