Canvasでコッホ曲線描いてHerokuで公開してみた

id:keyesberry さんの記事 CanvasアニメーションをHerokuで公開しようよ! - hp12c を見て面白そうだったので作ってみました。

コッホ曲線を書くだけのものです。クリックすると細かい曲線になっていきます。ある程度までいくとまた粗くなっていって最終的に直線になります。ただそれだけ。

これ http://fractals.heroku.com/

アプリケーションの構成

JavaScript以外はほぼ丸パクリですが一応書くと

具体的なWebアプリケーションの作り方は id:keyesberry さんが詳しく書いてくれているのでそちらを参照して下さい。

以下、雑な解説。

fractals.js

メインのJavaScriptコードです。

var canvas = {};

$(document).ready(function() {
    canvas.c = $("#fractals");
    canvas.ctx = canvas.c[0].getContext('2d');
    canvas.width = canvas.c.width();
    canvas.height = canvas.c.height();

    canvas.ctx.save();
    clearCanvas();

    var iteration = 1, limit = 7, delta = 1;
    drawKoch(iteration);

    $(canvas.c).click(function() {
        clearCanvas();
        iteration += delta;
        drawKoch(iteration);
        if (iteration >= limit || iteration <= 0) {
            delta = -1 * delta;
        }
    });
});

function clearCanvas() {
    canvas.ctx.restore();
    canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
    canvas.ctx.save();
    canvas.ctx.translate(canvas.width/2, canvas.height*3/4);
}

この辺は元のコードとだいたい同じです。

座標系の変換を少し変えました。コッホ曲線のベースの直線をx軸上とし、またx軸をCanvasの下から1/4に置きます。
Canvas上の原点は左上でx軸、y軸の正の向きはそれぞれ右方向、下方向です。上のようにして変換後の原点がCanvasの中央、上端から3/4の位置にくるようにします。
Canvasのクリア方法ですが、座標を変換したままクリアするのは面倒なので、restore()で座標変換前の状態に戻してからクリアするようにしました。
jQueryでクリックイベントの処理を書きます。

function kochify(start, end) {
    var ps = trisect(start, end);
    var vertex = rotate60(ps[1], ps[2]);
    return [ps[0], ps[1], vertex, ps[2], ps[3]];
}

function trisect(start, end) {
    var p1 = [start[0] + (end[0]-start[0])*1/3, start[1] + (end[1]-start[1])*1/3];
    var p2 = [start[0] + (end[0]-start[0])*2/3, start[1] + (end[1]-start[1])*2/3];
    return [start, p1, p2, end];
}

function rotate60(start, end) {
    var v = [end[0] - start[0],
             end[1] - start[1]];
    var rotated =[ v[0] * Math.cos(-Math.PI/3) - v[1] * Math.sin(-Math.PI/3),
                   v[0] * Math.sin(-Math.PI/3) + v[1] * Math.cos(-Math.PI/3) ];
    return [ start[0] + rotated[0],
             start[1] + rotated[1] ];
}

function flatten() {
    var points = [];
    for (var i = 0, l = arguments.length; i < l; i++) {
        var ps = arguments[i];
        for (var j = 0, m = ps.length; j < m; j++) {
            points.push(ps[j]);
        }
    }
    return points;
}

この辺は補助関数です。
trisect()は始点と終点を受け取り、直線を3等分する4つの座標を返します。
rotate60()は同じく始点と終点を受け取り、それをベクトルと見て、60度回転した終点の座標を返します。回転行列を使う例のやつですね。
気を付けないといけないのは、y軸が下向きなため逆向きに(-60度)回転する必要があることです。はまりました。
変な名前のkochify()は、上の関数を使ってコッホ関数を描くのに必要な5つの座標を返します。

function drawKoch(iteration) {
    var left  = -canvas.width/2;
    var right = +canvas.width/2;

    var start = [left,0], end = [right,0];
    var points = koch(iteration, start, end);

    var ctx = canvas.ctx;
    ctx.strokeStyle = "black";
    ctx.lineWidth = 1;

    for (var i = 0, len = points.length; i < len - 1; i++) {
        var s = points[i];
        var e = points[i+1];

        ctx.beginPath();
        ctx.moveTo(s[0], s[1]);
        ctx.lineTo(e[0], e[1]);
        ctx.stroke();
    }
}

function koch(iteration, start, end) {
    var points = [];
    var recur = function(iter, s, e) {
        if (iter === 0) {
            points.push([start, end]);
        } else if (iter === 1) {
            points.push(kochify(s, e));
        } else if (iter > 0) {
            var ps = kochify(s, e);
            recur(iter-1, ps[0], ps[1]);
            recur(iter-1, ps[1], ps[2]);
            recur(iter-1, ps[2], ps[3]);
            recur(iter-1, ps[3], ps[4]);
        } else {
            throw "must not happen";
        }
    };
    recur(iteration, start, end);
    return flatten.apply(null, points);
}

drawKoch()はkoch()関数を呼び出して実際の描画を行います。koch()関数は点の座標を配列で返します。
koch()関数は、指定された回数に従って、線分を分割し再帰的に座標を計算します。変数pointsを破壊的に変更するのが何だか気に入らないかも。

以上、こんな説明で通じるかどうか分かりませんが終わりです。

ソースコードこのあたり


久しぶりにカビの生えた幾何学脳をフル回転させて楽しかったです。
サイト名がfractalsと複数形なのは、今後また他のフラクタル図形を描くかもしれないから。
本当はマンデルブロ集合を描きたかったけど、とりあえず簡単なのからやってみました。

マンデルブロGoogleの中の人がとっくにやってますけどね。