リアス式海岸をコッホ曲線で作るというのが、数字の国のミステリー(マーカス・デュ・ソートイ著)に載っていたので、javascriptで実装しました。
コッホ曲線を描く
とりあえずコッホ曲線を描きましょう。
コッホ曲線の説明はウィキペディアに乗っています。
処理の手順は
- 初期値となる2点(下図P01,P1)を設定する。
- 2点を三分割にする点(Q01,Q11)を設定する。
- 2の2点を頂点とする直角三角形を作成する
- 1〜3を繰り返す。
繰り返すことでどんどん細かい凸が作られます。
イメージ(左:処理ロジック、右:コッホ曲線の作成過程)
点Q2は以下で計算します。
$$
\begin{align}
\begin{bmatrix}
q_2x \\
q_2y
\end{bmatrix} &=
\begin{bmatrix}
cos(60^\circ) & -sin(60^\circ) \\
sin(60^\circ) & cos(60^\circ)
\end{bmatrix}\begin{bmatrix}
q_1x - q_0x \\
q_1y - q_0y
\end{bmatrix} +
\begin{bmatrix}
q_0x \\
q_0y
\end{bmatrix}
\end{align}
$$
//座標系
function Cood(x,y){
this.x = x;
this.y = y;
}
function koch(p1, p2){
var q0 = new Cood( ( p2.x + 2*p1.x)/3, ( p2.y + 2*p1.y)/3 );
var q1 = new Cood( (2*p2.x + p1.x)/3, (2*p2.y + p1.y)/3 );
var q2 = new Cood( ( (q1.x+q0.x) - Math.sqrt(3)*(q1.y-q0.y))/2,
(Math.sqrt(3)*(q1.x-q0.x) + (q1.y+q0.y))/2);
return [q0, q2, q1, p2];
}
function koch_list(k){
var ret_arr = [k[0]];
for(var i=0, len=k.length; i<len-1; i++){
Array.prototype.push.apply(ret_arr, koch(k[i], k[i+1]));
}
return ret_arr;
}
koch(p1, p2)はコッホ曲線の凸部分を作成します。
4点返しているのはプログラムの都合です。もっと上手くやれると思います。
koch_list(k)はこれまでのコッホ曲線を引数として、一つ細かいコッホ曲線を作成します。
コッホ曲線の凸を乱数で決める
上記はすべて外向きに凸がありますが、内向きにも凸を作ります。
function koch(p1, p2){
var rand = Math.floor( Math.random() * 2 ) ;
var q0 = new Cood( ( p2.x + 2*p1.x)/3, ( p2.y + 2*p1.y)/3 );
var q1 = new Cood( (2*p2.x + p1.x)/3, (2*p2.y + p1.y)/3 );
var q2 = new Cood( ( (q1.x+q0.x) - Math.sqrt(3)*(q1.y-q0.y))/2,
(Math.sqrt(3)*(q1.x-q0.x) + (q1.y+q0.y))/2);
if(rand==1){
q2 = new Cood( ( (q1.x+q0.x) + Math.sqrt(3)*(q1.y-q0.y))/2,
(-Math.sqrt(3)*(q1.x-q0.x) + (q1.y+q0.y))/2);
}
return [q0, q2, q1, p2];
}
2値を取る乱数で、1の時は回転行列を-60°にしてQ2を計算します。
ソースコード
<canvas id="koch" width="1200" height="600"></canvas>
<script type="text/javascript">
var canvas = document.getElementById("koch");
var ctx = canvas.getContext("2d");
var rand = Math.floor( Math.random() * 2 ) ;
var p;
var c_w = canvas.width;
var c_h = canvas.height;
//座標系
function Cood(x,y){
this.x = x;
this.y = y;
}
var ini1 = new Cood(0 , c_h*0.75);
var ini2 = new Cood(c_w, c_h*0.75);
var koch_path = [ini1, ini2];
function koch(p1, p2){
var rand = Math.floor( Math.random() * 2 ) ;
var q0 = new Cood( ( p2.x + 2*p1.x)/3, ( p2.y + 2*p1.y)/3 );
var q1 = new Cood( (2*p2.x + p1.x)/3, (2*p2.y + p1.y)/3 );
var q2 = new Cood( ( (q1.x+q0.x) - Math.sqrt(3)*(q1.y-q0.y))/2,
(Math.sqrt(3)*(q1.x-q0.x) + (q1.y+q0.y))/2);
if(rand==1){
q2 = new Cood( ( (q1.x+q0.x) + Math.sqrt(3)*(q1.y-q0.y))/2,
(-Math.sqrt(3)*(q1.x-q0.x) + (q1.y+q0.y))/2);
}
return [q0, q2, q1, p2];
}
function koch_list(k){
var ret_arr = [k[0]];
for(var i=0, len=k.length; i<len-1; i++){
Array.prototype.push.apply(ret_arr, koch(k[i], k[i+1]));
}
return ret_arr;
}
function init(){
p = new Animation();
}
init();
var tm;
tm = setInterval(main,10);
function main(){
//画面のクリア
ctx.clearRect(0,0,c_w,c_h);
p.view();
}
var cnt = 0;
function Animation(){
var obj = this;
obj.view = function(){
ctx.beginPath()
ctx.moveTo(koch_path[0].x, koch_path[0].y);
if(cnt%20==0){
koch_path = koch_list(koch_path);
}
for(var i = 1, k_len = koch_path.length ; i < k_len; i++){
ctx.lineTo(koch_path[i].x, koch_path[i].y);
}
ctx.stroke();
if(cnt<200){
cnt ++;
}
}
}
</script>
RPGの世界地図作ったりするときには便利かもしれませんね。