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

ニ☆ウ☆ト☆ラ☆ボ

タイトルテスト中

ドラゴン曲線の描き方

雑記 プログラミング

「世代」に 0 〜 16 の数字、「角度」に 60 〜 180 の数字を入力してください:

世代:   角度:

ドラゴン曲線とは

「第 n 世代 のドラゴン曲線」を次の図のように描いていきます。 その極限として得られる曲線が「ドラゴン曲線」です。

f:id:ddkd:20161218002223p:plain
  • 前の世代を構成する各線分(グレー)を片側に引き出し、直角をなす2本の線分()に置き換えます。
  • 引き出す方向は、曲線の進行方向に対して 、... と交互になるようにします (左の端点が始点で、右の端点が終点のとき)。

観察

第 n 世代のドラゴン曲線(n ≧ 1)を前半部分と後半部分に分けてみます。

前半部分:

f:id:ddkd:20161218222233p:plain

これは、第 n-1 世代のドラゴン曲線を  1/\sqrt{2} 倍して、左へ45°回転したものになっています。

後半部分:

f:id:ddkd:20161218223319p:plain

これは、ドラゴン曲線とは少し作られ方が違います。 これを「右ドラゴン曲線」と呼ぶことにします。

右ドラゴン曲線の作られ方:

f:id:ddkd:20161218231121p:plain

第1世代を作るときに右側へ引き出すという点だけが、ドラゴン曲線と異なります。 第 n 世代のドラゴン曲線の後半部分は、第 n-1 世代の右ドラゴン曲線を  1/\sqrt{2} 倍して、右へ45°回転したものになっています。

第 n 世代のドラゴン曲線(n ≧ 1)についての観察と同様のことが、第 n 世代の右ドラゴン曲線(n ≧ 1)についても成り立っています:

  • 前半部分は、第 n-1 世代のドラゴン曲線を  1/\sqrt{2} 倍して、右へ45°回転したもの、
  • 後半部分は、第 n-1 世代の右ドラゴン曲線を  1/\sqrt{2} 倍して、左へ45°回転したもの。

Canvas でのプログラミング

  • (0, 0) を始点とし、(size, 0) を終点とする第 n-1 世代のドラゴン曲線が関数 dragonL(n-1, size) で、
  • (0, 0) を始点とし、(size, 0) を終点とする第 n-1 世代の右ドラゴン曲線が関数 dragonR(n-1, size) で、

描かれるとします。 描き終えた後には、終点 (size, 0) が原点となるように、座標系が平行移動されるものとします。

このとき、

  • (0, 0) を始点とし、(size, 0) を終点とする第 n 世代のドラゴン曲線を描く関数 dragonL(n, size) と
  • (0, 0) を始点とし、(size, 0) を終点とする第 n 世代の右ドラゴン曲線を描く関数 dragonR(n, size)

は、次のように定義されます:

var factor = 1 / Math.sqrt(2);

function dragonL(n, size) {
  ctx.rotate(-Math.PI/4);  // 左へ45°回転
  dragonL(n-1, size*factor);
  ctx.rotate(Math.PI/2);  // 右へ90°回転
  dragonR(n-1, size*factor);
  ctx.rotate(-Math.PI/4);  // 左へ45°回転
}

function dragonR(n, size) {
  ctx.rotate(Math.PI/4);  // 右へ45°回転
  dargonL(n-1, size*factor);
  ctx.rotate(-Math.PI/2);  // 左へ90°回転
  dragonR(n-1, size*factor);
  ctx.rotate(Math.PI/4);  // 右へ45°回転
}

2つの関数 dragonL(n, size) と dragonR(n, size) は、

  • 左へ回転 → 右へ回転、
  • 右へ回転 → 左へ回転

という置き換えで、お互いがお互いに入れ替わります。 そうすると、「実は一個の関数だけ考えればいいんじゃないか?」という考えが浮かびます。

実際、その通りで、

  • (0, 0) を始点とし、(size, 0) を終点とする第 n-1 世代のドラゴン曲線(sign = 1)と右ドラゴン曲線(sign = -1)が関数 dragon(n-1, size, sign) で

描かれるとすると、

  • (0, 0) を始点とし、(size, 0) を終点とする第 n 世代のドラゴン曲線(sign = 1)と右ドラゴン曲線(sign = -1)を描く関数 dragon(n, size, sign)

は、次のように定義されます:

var factor = 1 / Math.sqrt(2);

function dragon(n, size, sign) {
  ctx.rotate(-sign * Math.PI/4);  // 左(右)へ45°回転
  dragon(n-1, size*factor, 1);
  ctx.rotate(sign * Math.PI/2);  // 右(左)へ90°回転
  dragon(n-1, size*factor, -1);
  ctx.rotate(-sign * Math.PI/4);  // 左(右)へ45°回転
}

あとは、n = 0 のときの処理を加えてやれば、完成です。

プログラム例

html:

<!DOCTYPE html>
<html>
  <head>
    <title>Dragon Curve</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script src="dragon-curve.js"></script>
  </body>
</html>

javascript (dragon-curve.js):

var canvas = document.getElementById('canvas');
var w = canvas.width = 600;
var h = canvas.height = 400;
canvas.style.border = 'solid 1px black';

var ctx = canvas.getContext('2d');

// 描画設定
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';

// ドラゴン曲線の大きさ、始点
var size = w/2;
var start = {x: (w-size)/2, y: h*2/3};

// 世代が上がるときの線分の縮小率
var factor = 1 / Math.sqrt(2);

// 原点移動(曲線の始点が原点となるように座標系を平行移動します。)
ctx.translate(start.x, start.y);

// ドラゴン曲線描画
ctx.beginPath();
ctx.moveTo(0, 0);
dragon(8, size, 1); // お好きな世代で(ここでは第8世代を描画)
ctx.stroke();

// ドラゴン曲線作成
function dragon(generation, size, sign) {
    if (generation == 0) {
        ctx.lineTo(size, 0);
        ctx.translate(size, 0);
    } else {
        ctx.rotate(-sign * Math.PI/4);  // 左(右)へ45°回転
        dragon(generation-1, size*factor, 1);
        ctx.rotate(sign * Math.PI/2);  // 右(左)へ90°回転
        dragon(generation-1, size*factor, -1);
        ctx.rotate(-sign * Math.PI/4);  // 左(右)へ45°回転
    }
}
広告を非表示にする