2011 08 18

フラクタル

本棚から フラクタルCGコレクション という本を発掘して、懐かしい気分に浸ったのが連休前のこと。 学生の時に買ったものだから、もう20年近く前だ。 本に載っている BASIC のソースコードを、自分の使っていた X68000 用に移植して計算させていたんだよな。

で、見ているうちにまたやってみたくなって、せっかくだから HTML5 canvas 用に 移植してみた。 canvas の勉強を兼ねてのことだったんだけど、この程度だと、勉強って程でもなかったな。

移植したのは3種類。

再帰図形

当時は、パソコンのメモリはせいぜい 2MB 程度。 なので、移植元のソースは再帰処理を展開して実装していた。

今、俺のメインマシンの MacBookPro は、メモリが8GBで当時の4000倍。 再帰を再帰として少々深くしても問題無いと思うので、素直に再帰で実行してみた。

描画部分はこんな感じ。

function Core() { this.clear = function ( info, context ) { context.clearRect( 0, 0, info.width, info.height ); }; this.draw = function ( info, level, context, generator ) { _draw( { x : info.margin, y : info.height / 2 }, { x : info.width - info.margin, y : info.height / 2 }, level ); function _draw( _beginPoint, _endPoint, _level ) { if ( _level === 0 ) { context.beginPath(); context.moveTo( _beginPoint.x, _beginPoint.y ); context.lineTo( _endPoint.x, _endPoint.y ); context.stroke(); } else { var applied = generator.apply( _beginPoint, _endPoint ); for ( var i = 0; i < applied.length - 1; i++ ) { _draw( applied[ i ], applied[ i + 1 ], _level - 1 ); } } } }; }

generator は再帰適用するパターンのオブジェクト。 apply メソッドは、与えられた始点と終点で決まる線分にパターンを適用してできる線分群の始点/終点座標を配列で返すもの。 この処理を指定の level だけ繰り返し、結果を線で結ぶ。

canvas に線を引くのは、こんな手順。

var context = document.getElementsByTagName( 'canvas' )[ 0 ].getContext( '2d' );

なんて感じで、描画対象のキャンバスからコンテキストを取得し、これに対して、

context.beginPath(); context.moveTo( 始点X座標, 始点Y座標 ); context.lineTo( 終点X座標, 終点Y座標 ); context.stroke();

とする。 ちなみに、原点 (0,0) は canvas の左上。

自己平方フラクタル

簡単な計算式で複雑な図形を描くのが自己平方フラクタル。

window.addEventListener('load', function() { var canvas = document.getElementById( 'base' ); var context = canvas.getContext( '2d' ); var width = parseInt( canvas.width, 10 ); var height = parseInt( canvas.height, 10 ); document.getElementById( 'draw' ).addEventListener( 'click', function () { var rs = parseFloat( document.getElementById( 'rs' ).value ); var re = parseFloat( document.getElementById( 're' ).value ); var is = parseFloat( document.getElementById( 'is' ).value ); var ie = parseFloat( document.getElementById( 'ie' ).value ); var rc = parseFloat( document.getElementById( 'rc' ).value ); var ic = parseFloat( document.getElementById( 'ic' ).value ); var rAdjust = ( re - rs ) / width; var iAdjust = ( ie - is ) / height; var level = parseInt( document.getElementById( 'level' ).value, 10 ); var color_step = parseInt( 256 / level ); var resolution = parseInt( document.getElementById( 'resolution' ).value, 10 ); var threshold = 4; context.clearRect( 0, 0, width, height ); for ( var x = 0; x < width; x += resolution ) { for ( var y = 0; y < height; y += resolution ) { var rv = x * rAdjust + rs; var iv = y * iAdjust + is; for ( var l = 0; l < level; l++ ) { var r = rv * rv - iv * iv + rc; var i = 2 * rv * iv + ic; if ( r * r + i * i > threshold ) { var color = l * color_step; context.fillStyle = 'rgb(' + color + ',' + color + ',' + color + ' )'; context.fillRect( x, y, resolution, resolution ); break; } rv = r; iv = i; } } } }, false ); }, false);

妥当な値が入力される前提で入力のチェックを一切やっていないこともあって、JavaScript のソースはこれだけ。

再帰図形と違って、こちらは四角形で塗り潰している。 塗り潰しは、線のときと同様に取得したコンテキストに対して

context.fillStyle = 'rgb( 赤成分, 緑成分, 青成分 )'; context.fillRect( 始点X座標, 始点Y座標, 幅, 高さ );

とする。 色はそれぞれ 0 〜 255 の範囲。 幅と高さはピクセル単位。

マンデルブロート集合

出来上がるのは同じ図形だが、一部をズームしていくのが楽しいマンデルブロート集合。

window.addEventListener('load', function() { var canvas = document.getElementById( 'base' ); var context = canvas.getContext( '2d' ); var width = parseInt( canvas.width, 10 ); var height = parseInt( canvas.height, 10 ); document.getElementById( 'draw' ).addEventListener( 'click', function () { var rs = parseFloat( document.getElementById( 'rs' ).value ); var re = parseFloat( document.getElementById( 're' ).value ); var is = parseFloat( document.getElementById( 'is' ).value ); var ie = parseFloat( document.getElementById( 'ie' ).value ); var rAdjust = ( re - rs ) / width; var iAdjust = ( ie - is ) / height; var level = parseInt( document.getElementById( 'level' ).value, 10 ); var color_step = parseInt( 256 / level ); var resolution = parseInt( document.getElementById( 'resolution' ).value, 10 ); var threshold = 4; context.clearRect( 0, 0, width, height ); for ( var x = 0; x < width; x += resolution ) { for ( var y = 0; y < height; y += resolution ) { var rc = x * rAdjust + rs; var ic = y * iAdjust + is; var rv = 0; var iv = 0; for ( var l = 0; l < level; l++ ) { var r = rv * rv - iv * iv + rc; var i = 2 * rv * iv + ic; if ( r * r + i * i > threshold ) { var color = l * color_step; context.fillStyle = 'rgb(' + color + ',' + color + ',' + color + ' )'; context.fillRect( x, y, resolution, resolution ); break; } rv = r; iv = i; } } } }, false ); }, false);

自己平方フラクタルとほとんど同じだが、こっちの方が少し簡単。

それにしても、いい世の中になったよなぁ。 CPU のクロックは、X68000 の10MHzから、MacBookPro の2.2GHzで220倍。 その上メモリも4000倍。 当時は、ちょっとした計算をするのに数時間待ったものだが、今じゃ1秒もかからない。 試しに iPhone でやって見たら、解像度1、レベル128でも10秒ぐらいだった。 でも、これも十分に凄いんだよな。 当時の感覚だと 「手のひらにスーパーコンピューター」 ってところ。

そうそう、canvas を使っているせいで、IE8 以前では全く動かない。 Vista も 7 もないから、IE9 でどうなるかは判らない。 Safari, Chrome, Firefox の最新バージョンだと問題無い。 iPhone4 も iPad2 も問題無い。 いや、無いことも無いのか。 iPhone だと、再帰図形が厳しい。 パターンの節点を8個以上、再帰レベルを7以上にすると、途中で力尽きてしまう。 たぶん、CPU時間の上限10秒にひっかかっているんだろう。