「世代」に 0 〜 16 の数字、「角度」に 60 〜 180 の数字を入力してください:
ドラゴン曲線とは
「第 n 世代 のドラゴン曲線」を次の図のように描いていきます。 その極限として得られる曲線が「ドラゴン曲線」です。

- 前の世代を構成する各線分(グレー)を片側に引き出し、直角をなす2本の線分(赤)に置き換えます。
- 引き出す方向は、曲線の進行方向に対して 左、右、左、右、... と交互になるようにします (左の端点が始点で、右の端点が終点のとき)。
観察
第 n 世代のドラゴン曲線(n ≧ 1)を前半部分と後半部分に分けてみます。
前半部分:

これは、第 n-1 世代のドラゴン曲線を 倍して、左へ45°回転したものになっています。
後半部分:

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

第1世代を作るときに右側へ引き出すという点だけが、ドラゴン曲線と異なります。
第 n 世代のドラゴン曲線の後半部分は、第 n-1 世代の右ドラゴン曲線を 倍して、右へ45°回転したものになっています。
第 n 世代のドラゴン曲線(n ≧ 1)についての観察と同様のことが、第 n 世代の右ドラゴン曲線(n ≧ 1)についても成り立っています:
- 前半部分は、第 n-1 世代のドラゴン曲線を
倍して、右へ45°回転したもの、
- 後半部分は、第 n-1 世代の右ドラゴン曲線を
倍して、左へ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°回転 } }