JavaScript のオブジェクト指向機能を利用して、複素数を扱うクラス?を作成し、それを利用してフラクタル図形を描きます。
2つの複素数 z0, z1 を以下の様に定義すると (a, b, c, d は実数)、
z0 = a + bi
z1 = c + di
以下の様に、加算、減算、積算、絶対値 が定義できます。
z0 + z1 = (a + c) + (b + d)i
z0 - z1 = (a - c) + (b - d)i
z0 * z1 = (a*c - b*d) + (a*d + b*c)i
|z0| = sqrt(a*a + b*b)
具体的には、以下の様にクラスを作成します。
ここでは、複素数クラス(Complex)の定義として、以下のコンストラクタ、メソッドを作成します。
クラス変数として、以下を用意します。
以下に、複素数クラスと実行例のプログラムを示します。
console.log の引数に複素数オブジェクトを指定すると複素数クラスの toString メソッドが自動的に呼ばれます。
function Complex(r, i) {
this.r = r;
this.i = i;
}
Complex.prototype.copy = function() {
return new Complex(this.r, this.i);
}
Complex.prototype.add = function(z) {
this.r += z.r;
this.i += z.i;
return this;
}
Complex.prototype.sub = function(z) {
this.r -= z.r;
this.i -= z.i;
return this;
}
Complex.prototype.mul = function(z) {
var rw = this.r * z.r - this.i * z.i;
var iw = this.r * z.i + this.i * z.r;
this.r = rw;
this.i = iw;
return this;
}
Complex.prototype.len2 = function() {
return this.r * this.r + this.i * this.i;
}
Complex.prototype.toString = function() {
if(this.i >= 0) {
return this.r + "+" + this.i + "i";
} else {
return this.r + "" + this.i + "i";
}
}
var a = new Complex(1, 1);
var b = new Complex(-1, -1);
var c = a.copy();
console.log(a.add(b));
console.log(a.sub(b));
console.log(a.mul(b));
console.log(a.len2());
console.log(c);
zn+1 = zn2 + c
ここで、z0 = 0 + 0i の時の c の集合をマンデルブロ集合、c を固定した時の z0 の集合をジュリア集合と言います。
これらの集合のフラクタル図形は、初期値の複素数の実数部分をx座標、虚数部分をy座標とし、無限大に発散するまで漸化式を繰り返し、その回数に応じた色を付けた図のことを一般的に言います (なので、実際には、色がついているのは、集合でない部分です)。
以下に、フラクタル図形を表示するサンプルプログラムを示していますが、図形として描画するために、初期値 z0, c を範囲を与えて設定します。
z0 の範囲は、zs ~ ze、c の範囲は、cs ~ ce で与えます。
マンデルブロ集合の描画は、例えば、以下の初期値の範囲で実行します。
zs = new Complex( 0.0, 0.0);
ze = new Complex( 0.0, 0.0);
cs = new Complex(-2.5, -1.1);
ce = new Complex( 1.0, 1.1);
ジュリア集合の描画は、例えば、以下の初期値の範囲で実行します。
zs = new Complex(-1.7, -1.1);
ze = new Complex( 1.7, 1.1);
cs = new Complex(-0.4, -0.6);
ce = new Complex(-0.4, -0.6);
var canvas = document.getElementById('jsccanvas');
var context = canvas.getContext('2d');
function Complex(r, i) {
this.r = r;
this.i = i;
}
Complex.prototype.copy = function() {
return new Complex(this.r, this.i);
}
Complex.prototype.add = function(z) {
this.r += z.r;
this.i += z.i;
return this;
}
Complex.prototype.sub = function(z) {
this.r -= z.r;
this.i -= z.i;
return this;
}
Complex.prototype.mul = function(z) {
var rw = this.r * z.r - this.i * z.i;
var iw = this.r * z.i + this.i * z.r;
this.r = rw;
this.i = iw;
return this;
}
Complex.prototype.len2 = function() {
return this.r * this.r + this.i * this.i;
}
Complex.prototype.toString = function() {
if(this.i >= 0) {
return this.r + "+" + this.i + "i";
} else {
return this.r + "" + this.i + "i";
}
}
function point(x, y, color) {
var b = Math.floor( (color % imax) * 256 / imax);
var g = Math.floor(((color / imax) % imax) * 256 / imax);
var r = Math.floor(((color / imax / imax) % imax) * 256 / imax);
context.strokeStyle="rgb(" + r + "," + g + "," + b + ")";
context.beginPath();
context.moveTo(x, y);
context.lineTo(x+1, y+1);
context.stroke();
}
function converge(z, c) {
var color = 0;
var i;
var zw = z.copy();
for(i = 0; i < max; ++ i) {
if(zw.len2() > 4.0) break;
zw.mul(zw);
zw.add(c);
color ++;
}
return color;
}
var zs = new Complex( 0.0, 0.0);
var ze = new Complex( 0.0, 0.0);
var cs = new Complex(-2.5, -1.1);
var ce = new Complex( 1.0, 1.1);
//var zs = new Complex(-1.7, -1.1);
//var ze = new Complex( 1.7, 1.1);
//var cs = new Complex(-0.4, -0.6);
//var ce = new Complex(-0.4, -0.6);
var width = 640;
var height = 400;
var x = 0;
var y = 0;
var color;
var imax = 8;
var max = imax * imax * imax;
var zd = new Complex((ze.r-zs.r)/width, (ze.i-zs.i)/height);
var cd = new Complex((ce.r-cs.r)/width, (ce.i-cs.i)/height);
var z = zs.copy();
var c = cs.copy();
function loop() {
z.r = zs.r;
c.r = cs.r;
for(x = 0; x < width; ++ x) {
color = converge(z, c);
point(x, y, color);
z.r += zd.r;
c.r += cd.r;
}
z.i += zd.i;
c.i += cd.i;
}
jscsetintervalid = setInterval(function() {
if(y >= height) {
clearInterval(jscsetintervalid);
jscsetintervalid = null;
}
++ y;
loop();
}, 10);