JavaScriptでお絵かき - 拡散律速凝集 (Diffusion-Limited Aggregation)

ここまでの試行錯誤と、ドットインストールの「はじめてのJavaScript」の 知識で、簡易なDLAシミュレーションを行ってみます。

ドットインストール「はじめてのJavaScripthttps://dotinstall.com/lessons/basic_javascript_v4

拡散律速凝集 (Diffusion-Limited Aggregation)の詳細は Wikipediaにお任せするとして、 ここでは似たような形を描ければよしという考えです。

ja.wikipedia.org

実は、20年以上前に同じようなプログラムを作ったことがあります。 そのときはDelphi(Object-Pascal)を使っていました。

ソフトの砂場(DLA)

その劣化版を作りました。実行結果はこんなの。 もっと大きい図を描きたいのですが、 一定時間以内に実行を終えないといけないようなので、 これくらいにしました。

f:id:yamamoto-works:20200215125706p:plain

ソースコードは次のとおりです。 途中でsleepなどにより実行権放棄ができればよいのですが、 やり方がよくわからないので断念しました。 他にも改善したいところはあるのですが、 経験値が足りないので、今はここまでにしておきます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>拡散律速凝集 (Diffusion-Limited Aggregation)</title>
    <style>
      #mycanvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas width="400" height="400" id="mycanvas"></canvas>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
      $(function() {
        const canvas = document.getElementById('mycanvas');
        if (!canvas || !canvas.getContext) return false;
        const ctx = canvas.getContext('2d');
        const maxW = canvas.width;
        const maxH = canvas.height;
        const imgData = ctx.createImageData(maxW, maxH);

        const h = maxW/2;
        const h2 = h*h - 5;
        const r = h * 0.75;

        let idx = (h + h*maxW) * 4;
        imgData.data[idx]   = 0;
        imgData.data[idx+1] = 0;
        imgData.data[idx+2] = 0;
        imgData.data[idx+3] = 255;

        for (let i = 0; i < 3000; ) {
          let t = Math.random() * Math.PI * 2;
          let x = Math.floor(Math.cos(t) * r) + h;
          let y = Math.floor(Math.sin(t) * r) + h;

          while ((x-h)*(x-h) + (y-h)*(y-h) < h2) {
            if (imgData.data[(x-1 +  y   *maxW)*4 + 3] > 0
             || imgData.data[(x+1 +  y   *maxW)*4 + 3] > 0
             || imgData.data[(x   + (y-1)*maxW)*4 + 3] > 0
             || imgData.data[(x   + (y+1)*maxW)*4 + 3] > 0) {

              i++;
              idx = (x + y*maxW) * 4;
              imgData.data[idx]   = 0;
              imgData.data[idx+1] = 0;
              imgData.data[idx+2] = 0;
              imgData.data[idx+3] = 255;
              ctx.putImageData(imgData, 0, 0);
              // ここで実行権放棄しないと描画されない??
              break;
            }

            let m = Math.random();
            if (m < 0.25) {
              x -= 1;
            } else if (m < 0.5) {
              x += 1;
            } else if (m < 0.75) {
              y -= 1;
            } else {
              y += 1;
            }
          }
        }
      });
    </script>
  </body>
</html>

JavaScriptでお絵かきは、とりあえず満足しました。

JavaScriptでお絵かき - Canvasでドット単位の操作 その4

今回は、下の図の内側にある円周上のランダムな1点からスタートして、 ランダムウォークを続けて、外側の円周上に到達したら終わり、 というのをやってみます。 ランダムウォークの軌跡を赤色で示しています。

https://yamamoto-works.jp/canvas/test05.html

f:id:yamamoto-works:20200214234705p:plain

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>test5</title>
    <style>
      #mycanvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas width="400" height="400" id="mycanvas"></canvas>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
      $(function() {
        const canvas = document.getElementById('mycanvas');
        if (!canvas || !canvas.getContext) return false;
        const ctx = canvas.getContext('2d');
        const maxW = canvas.width;
        const maxH = canvas.height;
        const imgData = ctx.createImageData(maxW, maxH);

        const t = Math.random() * Math.PI * 2;
        const h = maxW/2;
        const h2 = h*h;
        const r = h * 0.75;

        let x = Math.floor(Math.cos(t) * r) + h;
        let y = Math.floor(Math.sin(t) * r) + h;

        while ((x-h)*(x-h) + (y-h)*(y-h) < h2) {
          let m = Math.random();
          if (m < 0.25) {
            x -= 1;
          } else if (m < 0.5) {
            x += 1;
          } else if (m < 0.75) {
            y -= 1;
          } else {
            y += 1;
          }

          let idx = (x + y*maxW) * 4;
          imgData.data[idx]   = 255;
          imgData.data[idx+1] = 0;
          imgData.data[idx+2] = 0;
          imgData.data[idx+3] = 255;
        }
        ctx.putImageData(imgData, 0, 0);

        ctx.strokeStyle = "blue";
        ctx.beginPath();
        ctx.arc(h, h, h, 0, Math.PI*2, false);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(h, h, r, 0, Math.PI*2, false);
        ctx.stroke();
      });
    </script>
  </body>
</html>

JavaScriptでお絵かき - Canvasでドット単位の操作 その3

引き続き、Canvasで絵を描いてみます。 今回は円周上にランダムに点を打っていきます。 円を描くなら別の方法がありますが、 ここでのやり方は、ランダムに点をうって、 結果的に円になっています。

単に座標を計算して点をうつだけなので、 あまり説明することはないように思います。

https://yamamoto-works.jp/canvas/test04.html

f:id:yamamoto-works:20200214000836p:plain

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>test4</title>
    <style>
      #mycanvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas width="400" height="400" id="mycanvas"></canvas>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
      $(function() {
        const canvas = document.getElementById('mycanvas');
        if (!canvas || !canvas.getContext) return false;
        const ctx = canvas.getContext('2d');
        const maxW = canvas.width;
        const maxH = canvas.height;
        const imgData = ctx.createImageData(maxW, maxH);

        for (let i = 0; i < 10000; i++) {
          let t = Math.random() * Math.PI * 2;
          let r = maxW/2 * 0.75;
          let x = Math.floor(Math.cos(t) * r) + maxW/2;
          let y = Math.floor(Math.sin(t) * r) + maxH/2;

          let idx = (x + y*maxW) * 4;
          imgData.data[idx]   = 0;
          imgData.data[idx+1] = 0;
          imgData.data[idx+2] = 0;
          imgData.data[idx+3] = 255;
        }
        ctx.putImageData(imgData, 0, 0);
      });
    </script>
  </body>
</html>

JavaScriptでお絵かき - Canvasでドット単位の操作 その2

昨日のプログラムで、putImageDataを呼び出した後で 絵を書き換えるとどうなるのか試してみました。

canvasを塗りつぶしてputImageDataを呼び出した後、 白い対角線を描いてみました。そのまま 2回目のputImageData呼び出しがなければ、 その対角線は表示されませんでした。 下の画像は、2回目のputImageDataを 呼び出した後の状態です。

こんな感じで、RGBA配列で絵を準備して、 必要な時にputImageDataを呼び出すのだと思います。

https://yamamoto-works.jp/canvas/test03.html

f:id:yamamoto-works:20200212211734p:plain

JavaScriptでお絵かき - Canvasでドット単位の操作

JavaScriptでお絵かきの続きです。 ドット単位で操作したいので、その方法を探しました。 createImageDataでRGBAの配列を取得できます。 それに描画して、putImageDataで書き戻せばできあがりです。

作ってみたプログラムはこちら。

https://yamamoto-works.jp/canvas/test02.html

実行イメージはこんなの。

f:id:yamamoto-works:20200211221047p:plain

ソースコードは以下のとおり。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>test2</title>
    <style>
      #mycanvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas width="400" height="400" id="mycanvas"></canvas>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
      $(function() {
        const canvas = document.getElementById('mycanvas');
        if (!canvas || !canvas.getContext) return false;
        const ctx = canvas.getContext('2d');
        const maxW = canvas.width;
        const maxH = canvas.height;
        const imgData = ctx.createImageData(maxW, maxH);

        for (let y = 0; y < maxH; y++) {
          for (let x = 0; x < maxW; x++) {
            let idx = (x + y*maxW) * 4;
            imgData.data[idx]   = x % 256;  // r
            imgData.data[idx+1] = 100;      // g
            imgData.data[idx+2] = y % 256;  // b
            imgData.data[idx+3] = 255;      // a
          }
        }
        ctx.putImageData(imgData, 0, 0);
      });
    </script>
  </body>
</html>