読者です 読者をやめる 読者になる 読者になる

roombaの日記

読書・非線形科学・プログラミング・アート・etc...

某エンブレムをフラクタル化して無限にズームし、面積も求める

はじめに

東京オリンピックのエンブレムについて一悶着あったようですね。詳しいことは知りませんが、エンブレムの面積を計算している方がいて人生楽しそうだなと感じました。

togetter.com

この姿勢を見習い、本記事ではエンブレムをフラクタルっぽくしてみることにします。さらに、フラクタル図形は無限に細かい形状といえるので、HTML5Canvasを用いて無限にズームし続けるアニメーションを製作します。そして最後に、こうしてできたフラクタル図形の面積を計算して締め括ることとします。

参考:フラクタル図形とは、図形の部分と全体が自己相似になっているようなマトリョーシカ的図形のことです↓
フラクタル - Wikipedia

フラクタル

配色は適当ですが、以下の模様をベースに考えます。
f:id:roomba:20150803152145j:plain

ここで、以下の2つの円に注目します。

  • 「左上と右下の弧を含む大きな円(円全体は明示的に描かれていない)」
  • 「右上の赤い円」

前者を「大きな円」、後者を「小さな円」と呼ぶことにします。

以下の図のようにエンブレムを2つ並べ、右側のエンブレムの「大きな円」が左側のエンブレムの「小さな円」と同じ大きさになるように右側のエンブレムを縮小します。
f:id:roomba:20150803152146j:plain
そして、縮小後のエンブレムの「大きな円」が左側のエンブレムの「小さな円」に一致するように配置すると、以下のようになりました。
f:id:roomba:20150803152422j:plain
この作業を無限に繰り返すと、以下のようなフラクタル(的?)図形が完成します。
f:id:roomba:20150801135834p:plain
画質の関係ではっきりとは見えませんが、右上が無限に細かくなっています。

無限にズーム

ここまではフラクタル化の方法を紹介しました。フラクタル化されているので右上が無限に細かくなっているのですが、イメージがつかみにくいと思うので、その部分を拡大してゆくアニメーションを作ります。無限に細かいので、無限に拡大し続けることができます。

アニメーション

細かい説明は後回しにして、できたアニメーションを以下に貼り付けます。スマホでも動くはずですが、スクロール中は止まるので注意してください。

アニメーションの作り方

上のアニメーションをどう作るか説明します。飛ばしても結構です。

(1) フラクタルの描画

まず、以下のように座標・点A~Jを定めます。原点は左上で、下向きがy・右向きがxです。これはCanvasのデフォルトだったと思います。
サイズは300ピクセルとしました。I, Jは大きな円の接線の交点です。
f:id:roomba:20150803152423j:plain
このような「金色+黒色+灰色」の領域を描くdraw_tokyo関数を用意します。

そして、draw_tokyo関数の引数として拡大率をとるようにし、拡大率z倍なら右上(300, 0)を中心にz倍した座標をもとに「金色+黒色+灰色」の領域を描けるようにします。
この様な拡大(縮小)は以下の座標変換によって実現できます。この座標変換は関数zoom_x, zoom_yに実装されています。
{ \displaystyle
\begin{eqnarray}
  \begin{cases}
    x' = 300 - z (300 - x) & 
    \\
    y' = z y
  \end{cases}
\end{eqnarray}
}

そして、draw_tokyo関数によって

  • 拡大率1の「金色+黒色+灰色」領域
  • 拡大率1/sqrt(10)の「金色+黒色+灰色」領域
  • 拡大率1/(sqrt(10))^2の「金色+黒色+灰色」領域
  • 拡大率1/(sqrt(10))^3の「金色+黒色+灰色」領域
  • ……

と無限に描画すればフラクタル形状が描画できます(まだアニメーションではない)。「無限に」と書きましたが、画面の細かさは有限なので実際は数回行えば十分でしょう。
f:id:roomba:20150803152424j:plain

(2) アニメーション
以上のフラクタル図形にズームインするアニメーションを行うには、全体を拡大率1.0からルート10の範囲で拡大(右上を中心)してゆくだけです。プログラムではグローバル変数zを拡大率としています。

ルート10というのは、「大きい円」と「小さい円」の半径比です。フラクタルなのでルート10倍まで拡大すれば拡大前と一致するのがポイントです。

(3) ソースコード
以下に貼りました。汚いので、わからないことがあれば気軽にコメントください。

<canvas id="canvas1"></canvas>
<script type="text/javascript">
var c1 = document.getElementById("canvas1");
c1.width = 300;
c1.height = 300;
var ctx = c1.getContext("2d");

// ---------- 変数 ---------- 
// 各店の座標
A = [0.0, 0.0];
B = [0.0, 100.0];
C = [100.0, 0.0];
D = [100.0, 300.0];
E = [200.0, 300.0];
F = [200.0, 0.0];
G = [300.0, 300.0];
H = [300.0, 200.0];
I = [25.0, 25.0];
J = [275.0, 275.0];
K = [250.0, 50.0];

// 拡大率
var z = 1.0;

// ---------- main ----------  
function main(){
    setInterval(display, 10);// displayを定期的に実行
}
// 実行
main();

// ---------- その他の関数 ---------- 
// X座標を右上を中心に拡大率zで拡大する関数
function zoom_x(x, z){
    return 300 - z*(300-x);
}
// Y座標を右上を中心に拡大率zで拡大する関数
function zoom_y(y, z){
    return z*y;
}

// 拡大率zoom(右上を中心に)で左上の金色・右下の灰色・中央の黒を描画する関数
function draw_tokyo(zoom){
    // 中心の黒い長方形
    ctx.beginPath();
    ctx.fillStyle = "rgb(20, 20, 20)";
    ctx.fillRect(zoom_x(C[0], zoom), zoom_y(C[1], zoom), zoom*(E[0]-C[0]), zoom*(E[1]-C[1]));
    ctx.fill();
    // 左上の金色の三角形っぽい部分
    ctx.beginPath();
    ctx.fillStyle = "rgb(230, 180, 30)";
    ctx.moveTo(zoom_x(A[0], zoom), zoom_y(A[1], zoom));
    ctx.lineTo(zoom_x(B[0], zoom), zoom_y(B[1], zoom));
    ctx.arcTo(zoom_x(I[0], zoom), zoom_y(I[1], zoom), zoom_x(C[0], zoom), zoom_y(C[1], zoom), zoom*50.0*Math.sqrt(10));
    ctx.lineTo(zoom_x(A[0], zoom), zoom_y(A[1], zoom));
    ctx.fill();    
    // 右下の灰色の三角形っぽい部分
    ctx.beginPath();
    ctx.fillStyle = "rgb(200, 200, 200)";
    ctx.moveTo(zoom_x(G[0], zoom), zoom_y(G[1], zoom));
    ctx.lineTo(zoom_x(E[0], zoom), zoom_y(E[1], zoom));
    ctx.arcTo(zoom_x(J[0], zoom), zoom_y(J[1], zoom), zoom_x(H[0], zoom), zoom_y(H[1], zoom), zoom*50.0*Math.sqrt(10));
    ctx.lineTo(zoom_x(G[0], zoom), zoom_y(G[1], zoom));
    ctx.fill();
}

function display(){
    var dz = 0.005;
    if (z < Math.sqrt(10)){
        z += dz;
    }else{
        z = 1.0;// ルート10倍になったら最初に戻る
    }
    ctx.clearRect(0, 0, 300, 300);// 残像を消す
   
    draw_tokyo(z);
    draw_tokyo(z/Math.sqrt(10));
    draw_tokyo(z/(Math.sqrt(10)*Math.sqrt(10)));
    draw_tokyo(z/(Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)));
    draw_tokyo(z/(Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)));
    draw_tokyo(z/(Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)*Math.sqrt(10)));
}
</script>

面積をもとめる

もともとの面積

以下の記事によれば、一辺を2aとして、中央の黒い長方形の面積が
{ \displaystyle
\frac{4}{3} a^2
}
左上の金色部と右下の灰色部の合計が
{ \displaystyle
2 (\frac{2}{3} a^2 - \frac{1}{2} arctan(\frac{4}{3}) (\frac{\sqrt{10}}{3} a)^2)
}
右上の赤い円が
{ \displaystyle
\pi (\frac{a}{3})^2
}
となっています。togetter.com

フラクタル化した場合、以下の図から大きい円と小さい円の半径比が1:ルート10なので、面積比は1:10となります。
f:id:roomba:20150803152425j:plain
もとの「金色部+灰色部+黒色部」の面積をxとすれば、フラクタル化後の面積は
{ \displaystyle
x + \frac{1}{10} x + (\frac{1}{10})^2 x + (\frac{1}{10})^3 x + ...
}
となります。無限等比級数の和の公式より、これは
{ \displaystyle
\frac{x}{1-\frac{1}{10}}\;\;\;\;(1)
}


ここで、xは「金色部+灰色部+黒色部」の面積なので
{ \displaystyle
x = \frac{4}{3} a^2 + 2 (\frac{2}{3} a^2 - \frac{1}{2} arctan(\frac{4}{3}) (\frac{\sqrt{10}}{3} a)^2)\;\;\;\;(2)
}
となり、フラクタル化されたエンブレムの面積は式(1)に式(2)を代入することで
{ \displaystyle
\frac{\frac{4}{3} a^2 + 2 (\frac{2}{3} a^2 - \frac{1}{2} arctan(\frac{4}{3}) (\frac{\sqrt{10}}{3} a)^2)}{1-\frac{1}{10}}
}
{ \displaystyle
 = \frac{40}{27} a^2 + \frac{20}{9} (\frac{2}{3} a^2 - \frac{1}{2} arctan(\frac{4}{3})(\frac{\sqrt{10}}{3} a)^2)
}
と求められます。めでたしめでたし。