ルックアップテーブルとビット演算

O’REILLYの「JavaScriptグラフィックス」1章効率化についてメモ。

O’REILLYの書籍サイトではサンプルとして以下が掲載されている。
http://www.oreilly.co.jp/pub/9784873115283/ch01/ex0100.html
サインカーブを描くシンプルなもの。

特徴は

  • Canvasを用いず、ブロック要素(div)をhtml側に記述しcssで幅・高さを指定
  • ブロック要素内に幅1pxの縦線を幅の分の数描画する(この場合480本)
  • 描画の開始位置と高さはJavaScriptで変動させアニメーション化する

効率化のメインは

  1. ルックアップテーブル
  2. ビット演算

の2つ。
1つ目のルックアップテーブルは、都度sinを計算するのではなく、前もってsinを計算し、配列に格納することにより速度向上を図るということ。wonderFL(flash)でもスクリプトで絵柄をビットマップ描画し、それを配列に格納することで超高速化していたサンプルがあった(http://wonderfl.net/c/c0Gi)。

一方、2つ目のビット演算は少々わかりづらい。
なんの前置きも無く、

        drawGraph(x * 50, 32 - (sinTable[(x * 20) & 4095] * 16),
                  50 - (sinTable[(x * 10) & 4095] * 20));

とか

bars[i].style.top = 160 - height + 
                 sinTable[(ang + (i * freq)) & 4095] * height + 'px';

など唐突に4095という数字が出てくる。
結局
配列の生成数4096は16の3乗、0×1000
配列範囲(添字)の最大値 4095は16の3乗-1    0xFFF
ということにさえ気づければなんということは無かったのだけれど。

このサンプルでは360度(Math.2PI)を4096分割し、そのsin値を配列に格納している。
タイマー内の変数xを単純な足し算(x++)でどんどん増やしているため、このxから0~4095(配列範囲)の添字を算出する必要がある。そこでビット単位のAND(&)計算を用い
0×1000 (=4096)
0x  FFF(=4095)        &
———————————–
0X  000(=0 配列の先頭の添字)
などと計算し、範囲溢れを防ぎつつ添字を求めている。
私はこういう場合、余り(%、MOD)を求めて算出するのが通常だった。
余りで計算する場合、配列数(length=4096)を用いる。
上記の例だと
4096%4096=0 余り0 配列の先頭
4097&4096=0 余り1 配列の2つ目
となる。

ところがこの「余り」の計算は四則演算の中で最も遅いらしい
(参考にさせていただきました http://ufcpp.net/study/algorithm/col_circular.html) 。

このサンプルでは高速化のため、これをAND演算に切り替えている。
なるほど。
確かにカーブを書く都度「余り」計算を行なっているので計算回数も多い。ここの高速化は全体への影響も大きいだろう。
これを流用したサンプルはまた後日。

「ActionScript3.0アニメーション」をcanvasで置き換える 引き合うパーティクル

今回はChapter12のノードガーデンを作成する。
パーティクルをランダムに移動させ、それぞれの距離が200ピクセルより近ければ、近いパーティクル方向へより加速させる。
[Flash]
http://nouv.biz/labo/20120902_nodeGarden/flash/NodeGardenTest.html
[ javascript]
http://nouv.biz/labo/20120902_nodeGarden/javascript/nodeGarden.html

線を描画するときの透明度指定が少々ぎこちない。javascriptではこんな風なのだろうか。
var alpha=1-dist/minDist;//距離が遠いほど小さい→遠いほど透明になる
var rgba=’rgba(255, 255, 255,’ + alpha + ‘)’;
context.strokeStyle=rgba;

「ActionScript3.0アニメーション」をcanvasで置き換える 3D回転(及びJavascriptにおける継承)

Flashがz軸に対応した今となってはもはや古典か?「ActionScript3.0アニメーション」。
とはいえ内容は多いに役立つので一読をおすすめする。

今回はchapter15 3Dの基本の最後、3D回転。
[Flash]
回転公式忘れ。
+-入れ替えてzが後ろに来る、と。
var nx:Number = cos * ball.xpos – sin * ball.zpos;
var nz:Number = cos * ball.zpos + sin * ball.xpos;
それ以外は特に問題なし

http://nouv.biz/labo/20120901_rotate/flash/RotateXYZ.html


[Javascript]
先日作ったPoint3Dクラスを継承した、Point3DExtendSampleを作る。

Point3D.js:自身の座標保持。3D座標を2D座標に変換
Point3DExtendSample.js:上記クラスを継承し、drawメソッドをオーバーライドする。

作るにあたり、Javascriptの継承の方法を調べたらいくつかあることがわかった。今回はもっともオーソドック(教科書的)とオライリーに記載されてた方法を用いる。
即ち

function Point3DExtendSample(radius , xpos , ypos , zpos , fl){
this.radius=radius;
Point3D.call(this , xpos , ypos , zpos , fl);//親のオブジェクトのコンストラクタを呼び出す
}

//Point3Dの継承を実現する
Point3DExtendSample.prototype=new Point3D();

//Point3Dのメソッドをoverrideする
Point3DExtendSample.prototype.draw=function(myCanvas){
with(this){
var context=myCanvas.getContext(“2d”);
context.setTransform(getScale() , 0 , 0 , getScale() , x , y);
context.strokeStyle=”#cccccc”;
context.fillStyle=”#ff0000″;

context.beginPath();
context.arc(0 , 0 , radius , 0 , Math.PI*2 , true);
context.fill();
context.stroke();
context.setTransform(1 , 0 , 0 , 1 , 0 , 0);
}
}
の手法を用いる。
callってのがよくわからないうえcallした後にprototypeに引数無しの
new Point3D()がきてるのもよくわからん。後ほど調べる
(http://taiju.hatenablog.com/entry/20100515/1273903873 後で読ませて頂く)。

 2012年9月21日 以下自分なりの解釈を追記。

//とりあえず関数を作る
function Point3DExtendSample(radius , xpos , ypos , zpos , fl){
...
}

//その関数にPoint3D(インスタンス?機能)を埋め込む。
Point3DExtendSample.prototype=new Point3D();

//prototypeにオブジェクト(Point3Dのインスタンス)が設定される
//new Point3D()でのreturnは
//オブジェクト=Point3Dのインスタンス。
//これはPoint3DExtendSampleにPoint3Dインスタンスを生成(Point3Dをコピー)
//している、とも言える
//そのコピーしたPoint3Dに拡張したいメソッドなどを追記していく
//関数が起動するまえ即ちhtmlに 
//<script type="text/javascript" 
//src="nouvdotbiz/Point3DExtendSample.js"></script>
//と記載された時点で以下は実行される。
//その時点でxpos,yposなどのインスタンス変数が生成される。
//しかし上記では引数が設定されていないため、undefinedが代入されると思われる。
//スーパークラス(この場合Point3D)のコンストラクタ状態によってはエラーが発生する。
//その後、継承クラスのインスタンスが生成された時点で引数に代入されるのは通常通り。

初回時ボールが2重に描画されてしまった。 これはFlashの元ロジックをそのまま踏襲したことが仇になったようだ。x軸回転とy軸回転をそれぞれ別の関数にて起動しており、その関数内で Point3DExtendSampleのdrawメソッドを呼び出していたことが原因。x軸回転とy軸回転のわずかな時間差で描画されるため、2重になっていた。この処理をenterFrame内に統合し解決。

http://nouv.biz/labo/20120901_rotate/javascript/RotateXYZ.html

「ActionScript3.0アニメーション」をcanvasで置き換える 3Dラッピング応用

chapter15 3dの基本 ラッピングの応用。Tree部にImageを用いる。
オリジナルクラスImageLoadAndDraw.jsで画像を読込み描画させる手もあったが、ひとつだけなので今回は使用しない。
結果以下のクラスを用いる。
Point3D.js :
自身の座標保持。3D座標を2D座標に変換
applyImage3DMoving.js :
画像の読込
Point3Dの生成
Point3Dの変換2D座標へのImage描画

今回は1枚しか画像を使わないが、複数枚使う場合は上記
1.ImageLoadAndDraw.jsにて読込
2.すべての画像の読込判定
3.Point3D生成
4.画像配列( imageLoadAndDraws)と3D座標配列(point3ds)の対応付け(ソートがあるので少々面倒か?)が必要となる。

操作は前回同様↑キーで直進↓キーで後退。→キー←キーでそれぞれの方向に移動。spaceキーで上昇。

http://nouv.biz/labo/20120816_tree/javascript/applyImage3DMoving.html

発展系としてスプライトシートを作って走るアニメを用いるのが考えられる。またいずれ。
それにしてもFlashを使わないってのはスマホ・タブレット対策。にも関わらず操作に矢印キーだのスペースキーだの用いるって時点でもう…。この対策もまたいずれ。

「ActionScript3.0アニメーション」をcanvasで置き換える 3Dラッピング

chapter15 3dの基本 ラッピング部をcanvasに置き換え。
まずはFlashのおさらい。
[Flash]
操作は↑キーで直進↓キーで後退。→キー←キーでそれぞれの方向に移動。spaceキーで上昇。なぜかchromeでspaceキーが効かず上昇できない。後で調査。

flashの場合は以下2つのクラスを用いている
Treeクラス:sprite継承指定。幹枝をそれぞれランダムな長さ・角度にて生成。
Treesクラス:Treeを指定個数分(今回は100個)生成。加速計算・treeの3D座標計算、2D座標への変換計算など全てここで行い、描画する。
treeはspriteの描画機能のみを用い大半の処理はTreesクラスに依存している。

http://nouv.biz/labo/20120816_tree/flash/trees2Test.html

[javascript]
一方javascript側はspriteのようなものがないため、少々treeの機能が重くなる。FlashでのBitmapDataへの描画に近い手法を用いるため、以下のクラス構成となる。
Tree.js:
幹と枝の描画
canvasと焦点距離を引数で受ける
拡大率・奥行きの透明度を計算
焦点距離と3D座標から2D座標変換を計算
上記計算結果を用いcanvasに描画する

Trees.js:
treeの生成と制御
enterFrame処理(timer処理)
キー操作制御
のみとなり、treeに描画・2D変換を依存する分、こちらの処理が軽くなっている。
http://nouv.biz/labo/20120816_tree/javascript/trees.html

context描画時にmatrixを用いているが、描画サイズの半分マイナス移動処理(中心移動)を入れ忘れ。若干右にずれてしまっているがまぁご愛嬌。
操作はFlashと同様。
flashはline描画なので奥行きに関わらず幹の太さは一定(1ピクセル。細く見えるのは透明度の効果)。一方こちらは都度描画かつmatrixを用いているため、幹がscaleを反映し前に来ると太くなる。こっちの方がまぁリアルではあるが、奥にある木は細すぎて描画できない。
そこでscaleに下限 (0.5)を設けている。