見出し画像

マンデルブロ集合をコードに実装する方法

42 Tokyoの「fract-ol」という課題で、フラクタル図形の描写に取り組んだので、

前日はマンデルブロ集合の数式について説明したので、本日は実際のコードに落とし込む方法について見ていきたい。


コードに実装するには?

実際にコードに落とし込む際に複素数は使えないので、マンデルブロ集合を複素数を使わずに書き直すことが必要になる。

書き直しを行うには、Zn を点 (xn, yn) に、C を点 (a, b) にそれぞれ置き代える。

この置き換えを$${Z_{n + 1}  = Z_{n}^{2}  + C}$$の式に当てはめると、

$$
x_{n+1}+y_{n+1}=\left( x_{n}+y_{n}\right) ^{2}+a+b
$$

と表せる。

2乗の箇所の計算をすると、以下のように展開される。

$$
x_{n+1}+y_{n+1}=x_{n}^2+2x_{n}y_{n}-y_{n}^2+a+b
$$


ここで注意しなければいけないことは

$$
-y_{n}^2
$$

とマイナスになっていることだ。

これは、yは虚数iを含んでおり、虚数iの特徴は「2乗すると-1」となることだ。なので、

$$
-y_{n}^2
$$

はマイナスになり、完全なる実数となった。

ここで、実数と虚数部分を分けると、以下の2つの式に分けることができる。

$$
①   x_{n+1}=x_{n}^2-y_{n}^2+a
$$


$$
②   y_{n+1}=2x_{n}y_{n}+b
$$


横軸(x軸)の座標を求めるには上の式を、縦軸(y軸)の座標を求めるのには下の式を使用することになる。

実際のコード

次に実際に使用したコードを見ていく。

前回、マンデルブロ集合は以下の漸化式(前に出した数字を利用して次の値を出す)で定義され、nを大きくしていった時に、「数が大きくならずに安定する」と述べたが、安定する時の条件は、「2乗した際に、4以下」であれば安定しているとみなす。

$$
\begin{cases}
Z_{0} =\ 0\\
Z_{n\ +\ 1} \ =\ Z_{n}^{2} \ +\ C\
\end{cases}
$$

//この関数の中で、Znが安定するのか、発散するのかを確認し、結果によって色付けを行っている。
unsigned int	ft_pick_color(t_data *data)
{
	int		i;
	int		color;
	double	tmp_r;

	i = 0;
	
	//data->z_rは、本文中の式でいうとxn+1にあたる。rはreal number、つまり実数を表している。
	//data->z_iは、本文中の式でいうとyn+1にあたる。iはimaginary number、つまり虚数を表している。
	//このwhile文ではまず、実数部分と虚数部分のそれぞれを2乗したものが4以内、かつ
	//data->max-itは何回計算を行うかを設定しており、初期値では100に設定してあるので、ここでは100回while分が回ることになる。
	while (pow(data->z_r, 2.0) + pow(data->z_i, 2.0) <= 4 && i < data->max_it)
	{
		//以下が、上記の①の式に当たる。tmp_rというテンポラリーの変数に収めている理由は、ここで求めているはdata->z_rだが、data->z_rは次の式でも使用するので、内容を変更しないため。
		//data->c_rはCの実数部分に当たり、①ではaに当たる。
		tmp_r = pow(data->z_r, 2.0) - pow(data->z_i, 2.0) + data->c_r;
		
		//以下が、上記の②の式に当たる。data->c_iはCの虚数部分、②の式だとbに当たる
		data->z_i = 2 * data->z_r * data->z_i + data->c_i;
		data->z_r = tmp_r;
		i++;
	}
	
	//以下は、計算回数が上限に達した時、白(RGBで255,255,255)を塗るようにしている。
	if (i == data->max_it)
		color = ft_rgb_to_hex(255, 255, 255);
	
	//以下では、発散した場合の色つけを行っている。
	//色のつけ方はネット上でたくさんでているが、ここでは
	//計算回数(i)と実数部分(data->z_r)の大きさによって、色の塗り分けを行う関数を呼び出している。
	else
		color = ft_gradation(data, (double)i
				/ (double)data->max_it, pow(data->z_r, 2.0));
	return (color);
}

int	ft_draw_mandelbrot(t_data *data)
{
	int	x;
	int	y;

	//ここでは、描画するwindow上で1ピクセルだけ進んだ際の、複素平面乗で移動する差分を求めている。
	//data->delta_rは実数部分の差分を求めている。
	//data->max_rは複素平面の実数部分の最大値で、data->min_rは複素平面の実数部分の最小値。
	//横幅(WIDTH)は描画するwindowの横軸のピクセル数。
	data->delta_r = (data->max_r - data->min_r) / (double)WIDTH;
	//data->delta_iは虚数部分の差分を求めている。
	//data->max_iは複素平面の虚数部分の最大値で、data->min_iは複素平面の虚数部分の最小値。
	//縦幅(HEIGHT)は描画するwindowの縦軸のピクセル数。
	data->delta_i = (data->max_i - data->min_i) / (double)HEIGHT;
	y = 0;
	while (y < HEIGHT)
	{
		x = 0;
		while (x < WIDTH)
		{
			//ここでは描画するwindow上での1ピクセルずつ移動し、色を決めておくことになる。
			//data->c_rは、Cの実数部分。①のaにあたる。
			//data->c_iは、Cの虚数部分。①のbにあたる。
			//data->z_rとdata->z_iはZnの初期値だが、両方ともOとなる。
			data->c_r = data->min_r + x * data->delta_r;
			data->c_i = data->min_i + y * data->delta_i;
			data->z_r = 0;
			data->z_i = 0;

			//ここでは紹介しないが、ft_my_mlx_pixel_putは実際に色を塗る関数。
			//ft_pick_color関数は上に紹介してある通り、漸化式が安定するか発散するかによって色を選ぶ関数となる。
			ft_my_mlx_pixel_put(data, x, y, ft_pick_color(data));
			x++;
		}
		y++;
	}
	return (0);
}


mondにて匿名の質問・メッセージを募集しています。なんでも送ってね👍 https://mond.how/ja/hovinci_jp