【p5.js】カオスゲームでフラクタル図形を描く
Processing Advent Calendar 2023 の20日目の記事になります。
今回はカオスゲームという方法を用いてフラクタルな図形を描いていきます。
カオスゲームとは
まず、カオスゲームについてWikipediaを引用すると、下のようなつ説明が書かれています。
カオスゲーム(英: Chaos game)とは本来、多角形とその内部のランダムな点を使ってフラクタルを作る方法を指す。
多角形を一つ用意して、その多角形の内側のランダムな位置を使うとフラクタル図形が描けるということです。
文章で書くとシンプルになっていますが、具体的に三角形を一例に説明していきます。
三角形を使ったカオスゲーム
三角形を使ったカオスゲームの手順は下になります。
- 正三角形の頂点3つを準備する。
- 描画する点の最初の位置をランダムに選ぶ。
- 正三角形の頂点から一つをランダムに選ぶ。
- 描画する点と頂点の中点を計算し、そこに点を描画する。
- 計算した中点を描画する点として更新する。
- 手順3〜5を何回も繰り返す。
三角形の頂点と描画する点の中点を計算して更新する手順です。
これを実行するp5.jsのコードが下になります。(openprocessingで実行する場合はこちらから)
描画の繰り返しを5万回にしていますが、適当な値にして好みの絵にしてみて下さい。 正三角形の大きさも'''w*0.4'''としているので、調整するのも良いです。
function setup() { createCanvas(w=720, w) background(230) //正三角形の頂点を配列に入れて準備 let vertex_points = [] for(let i=0;i<3;i++){ let x = w*0.4 * cos(TAU/3*i) let y = w*0.4 * sin(TAU/3*i) vertex_points[i] = {x:x, y:y} } //キャンバス中央に移動 translate(w/2, w/2) //描画する点の最初の位置はランダム //キャンパス中央からランダムな位置なので+-の値 let draw_point = {x:random(-1,1)*w/2, y:random(-1,1)*w/2} //5万回繰り返す for(let i=0;i<50000;i++){ //頂点から一つをランダムに選ぶ let choice_point = random(vertex_points) //選んだ頂点と描画する点の中点を計算 let x = draw_point.x + (choice_point.x - draw_point.x) * 0.5 let y = draw_point.y + (choice_point.y - draw_point.y) * 0.5 point(x, y) //描画する点の位置を更新 draw_point = {x:x ,y:y} } }
これを実行すると下の描画が得られます。
カオスゲームを使って、シェルピンスキーのギャスケットが描画されました。
三角形の向きが気になる方はtranslate()
の後にrotate(-PI/2)
を入れれば良いはずです。
(余談ですが、描画する点を三角形の内側にきちんと収めたい場合は、初め10回くらい描画だけスキップしたりすると良いです。)
原理
原理を簡単に説明しておきます。
上で行った三角形のカオスゲームの要素を図に表したのが下になります。
図中では
・赤い点が正三角形の頂点
・緑の点が描画する点
・灰色の線が描画する点と各頂点を結んだ線
・水色の点が描画する点と各頂点の中点
になっています。
上で説明したカオスゲームでは、この水色の位置を計算しながら点を描画していることになります。(水色の点の三つの内どれか一つをランダムに選んで描画してることになる)
この時、水色の点を結んだ線を描いてみます。それが緑の線です。
この緑の線はどんな形をしているかというと、正三角形になっています。
正三角形の頂点とその内部の点で中点を計算すると、小さな正三角形が描けます。
つまり、カオスゲームで正三角形の内部に小さな正三角形を繰り返し描画することに相当する処理をしていたことになります。
フラクタルの同じ形が繰り返し現れる性質と一致します。
多角形のカオスゲーム
正三角形で行っていたカオスゲームを多角形に拡張していきます。
上のカオスゲームの手順で「正三角形」という部分を「正多角形」に読み替えれば多角形での描画になります。
更にここで、中点を求めていた部分を変更します。
中点は丁度真ん中で比率としては0.5になっていました。
頂点と描画する点の直線上の任意の比率になる点を描くようにします。
これを実行するp5.jsのコードが下になります。(openprocessingで実行する場合はこちらから)
function setup() { createCanvas(w=720, w) background(230) //正多角形の頂点を配列に入れて準備 //超点数はN let N = 6 let vertex_points = [] for(let i=0;i<N;i++){ let x = w*0.4 * cos(TAU/N*i) let y = w*0.4 * sin(TAU/N*i) vertex_points[i] = {x:x, y:y} } //キャンバス中央に移動 translate(w/2, w/2) //描画する点の最初の位置はランダム //キャンパス中央からランダムな位置なので+-の値 let draw_point = {x:random(-1,1)*w/2, y:random(-1,1)*w/2} //頂点と描画する点の間の点を計算するときの比率 //0.5が中点 let ratio = 0.65 //7万回繰り返す for(let i=0;i<70000;i++){ //頂点から一つをランダムに選ぶ let choice_point = random(vertex_points) //選んだ頂点と描画する点の間で比率(ratio)になる点を計算 let x = draw_point.x + (choice_point.x - draw_point.x) * ratio let y = draw_point.y + (choice_point.y - draw_point.y) * ratio point(x, y) //描画する点の位置を更新 draw_point = {x:x ,y:y} } }
これを実行すると下の描画が得られます。
N=6
とratio=0.65
を設定して、六角形でカオスゲームができました。
カオスゲームに追加制約
ここまでで多角形のカオスゲームを行えるようになりました。
このカオスゲームに追加で制約を加えると、更に表現できる図形が広がります。
紹介する制約以外にも色々と試してもらうと面白いと思うので、もし面白い絵が描ける条件があれば教えて下さい。
追加する制約として、手順3でランダムに頂点を選択する際に「前回選んだ頂点は選んではいけない。」という制約を設けてみます。
これを実行するp5.jsのコードが下になります。(openprocessingで実行する場合はこちらから)
function setup() { createCanvas(w=720, w) background(230) //正多角形の頂点を配列に入れて準備 //超点数はN let N = 5 let vertex_points = [] for(let i=0;i<N;i++){ let x = w*0.4 * cos(TAU/N*i) let y = w*0.4 * sin(TAU/N*i) vertex_points[i] = {x:x, y:y} } //キャンバス中央に移動 translate(w/2, w/2) //描画する点の最初の位置はランダム //キャンパス中央からランダムな位置なので+-の値 let draw_point = {x:random(-1,1)*w/2, y:random(-1,1)*w/2} //頂点と描画する点の間の点を計算するときの比率 //0.5が中点 let ratio = 0.5 //ランダムに頂点を選ぶためにインデックスで指定する let index = floor(random(vertex_points.length)) //7万回繰り返す for(let i=0;i<70000;i++){ //頂点から一つをランダムに選ぶ //ランダムに選んでおいたインデックスで頂点の情報を引っ張ってくる let choice_point = vertex_points[index] //選んだ頂点と描画する点の間で比率(ratio)になる点を計算 let x = draw_point.x + (choice_point.x - draw_point.x) * ratio let y = draw_point.y + (choice_point.y - draw_point.y) * ratio point(x, y) //描画する点の位置を更新 draw_point = {x:x ,y:y} //次に選択する頂点をインデックスでランダムに指定する let next = floor(random(vertex_points.length)) //indexとnextが異なるものになるまでnextを選び直す while(index == next){ next = floor(random(vertex_points.length)) } index = next } }
これを実行すると下の描画が得られます。
「前回選んだ頂点は選んではいけない。」を加えて、N=5
とratio=0.5
を設定して、五角形で実行しました。
蜘蛛の巣のような少し想像していない形ができました。フラクタルっぽいですね。
カオスゲームにノイズを加える
私がよくやっている手法になりますが、フラクタル図形を描く時にパーリンノイズを加えて、形を崩すということをここで紹介しておきます。
描画する点の座標にパーリンノイズの値を加算した座標をpoint()
で描画します。
これを実行するp5.jsのコードが下になります。(openprocessingで実行する場合はこちらから)
function setup() { createCanvas(w=720, w) background(230) //正多角形の頂点を配列に入れて準備 //超点数はN let N = 5 let vertex_points = [] for(let i=0;i<N;i++){ let x = w*0.4 * cos(TAU/N*i) let y = w*0.4 * sin(TAU/N*i) vertex_points[i] = {x:x, y:y} } //キャンバス中央に移動 translate(w/2, w/2) //描画する点の最初の位置はランダム //キャンパス中央からランダムな位置なので+-の値 let draw_point = {x:random(-1,1)*w/2, y:random(-1,1)*w/2} //頂点と描画する点の間の点を計算するときの比率 //0.5が中点 let ratio = 0.5 //ランダムに頂点を選ぶためにインデックスで指定する let index = floor(random(vertex_points.length)) //パーリンノイズの第三引数用にここで値をランダムに設定しておく let nsx = random(1000) let nsy = random(1000) //10万回繰り返す for(let i=0;i<100000;i++){ //頂点から一つをランダムに選ぶ //ランダムに選んでおいたインデックスで頂点の情報を引っ張ってくる let choice_point = vertex_points[index] //選んだ頂点と描画する点の間で比率(ratio)になる点を計算 let x = draw_point.x + (choice_point.x - draw_point.x) * ratio let y = draw_point.y + (choice_point.y - draw_point.y) * ratio //描画する点にパーリンノイズを加えて崩す let noise_x = w*0.4*(noise(x/w*3, y/w*4, nsx) - 0.5) let noise_y = w*0.4*(noise(y/w*3, x/w*4, nsy) - 0.5) point(x + noise_x, y + noise_y) //描画する点の位置を更新 draw_point = {x:x ,y:y} //次に選択する頂点をインデックスでランダムに指定する let next = floor(random(vertex_points.length)) //indexとnextが異なるものになるまでnextを選び直す while(index == next){ next = floor(random(vertex_points.length)) } index = next } }
これを実行すると下の描画が得られます。
``N=5と
ratio=0.5```を設定
``N=8と
ratio=0.55```を設定
``N=16と
ratio=0.75```を設定
まとめ
今回はカオスゲームという手法を用いてフラクタル図形を描きました。
単純な手順でありながら、設定値を変えることで不思議な形が見えて楽しめると思います。
色々な設定値でやると発見があると思うので、皆さんも自分なりに試してみて下さい。
是非、面白い形ができたらシェアして教えて下さい。