【科学技術計算講座5-12】粒子の可視化

前回は粒子の初期条件と境界条件について解説しました。

SPH法プログラムの初期条件と境界条件について説明します。科学技術計算講座5「粒子法(SPH法)で流体シミュレーション」の第11回目です。

今回は粒子の可視化部分を作り、全体を整理してプログラムを完成させたいと思います。

粒子の可視化

【5-3】章で説明したとおり、粒子の可視化はp5.jsdraw関数で行います。運動方程式を解いて得られた時々刻々変化する粒子の位置に、円を描いていくことによって、アニメーションが表示されます。draw関数内に直接書いてもよいですが、可視化部分も関数にしておきましょう。関数名はdrawParticleとしておきます。

function drawParticle() {
  background(220);

  // time 
  fill("black");
  textSize(32);
  text('time = ' + time.toFixed(2), 10, 50);

  // fluid particle
  const drawEllipse = function(p, scale, d) {
    for (let i = 0, n = p.length; i < n; i++) {
      if (!p[i].active) continue;
      const x = (p[i].position.x - regionAll.left) * scale;
      const y = canvasHeight - (p[i].position.y - regionAll.bottom) * scale;
      ellipse(x, y, d);
    }
  };

  const scale = canvasWidth / regionAll.width
  const d = particleSize * scale;
  noStroke();
  fill("blue");
  drawEllipse(p, scale, d);

  // wall particle
  fill("red");
  drawEllipse(pWall, scale, d);
}

最初のbackgroundは前に説明したとおり、背景を灰色に塗りつぶす命令です。

  // time 
  fill("black");
  textSize(32);
  text('time = ' + time.toFixed(2), 10, 50);

textはp5.jsの命令でテキスト文字を画面に出力します。fillは色指定で黒に、textSizeは文字の大きさの設定です。ここで、timeは時刻を表すグローバル変数です。toFixed(2)は数値を小数点以下2桁までとして表します。text10, 50はキャンバスのx=10px, y=50pxの位置に出力するという意味です。

※p5.js特有の関数とJavaScriptの命令が混じっているので注意してください。ここでは、fill, textSize, text関数はp5.js、.toFixedはJavaScriptの命令です。

  // fluid particle
  const drawEllipse = function(p, scale, d) {
    for (let i = 0, n = p.length; i < n; i++) {
      if (!p[i].active) continue;
      const x = (p[i].position.x - regionAll.left) * scale;
      const y = canvasHeight - (p[i].position.y - regionAll.bottom) * scale;
      ellipse(x, y, d);
    }
  };

drawEllipseという関数は、粒子配列pに対して円を描いています。scaleは計算上のサイズを画面上のピクセルサイズに変換する係数です。dは描く円の直径を表しています。

ここで、画面のキャンバスの位置座標x、yと計算上の座標が異なるので変換しています。キャンバスは、左上角が原点で下向きにy軸が伸びるような座標になっています。

Canvasの座標

  const scale = canvasWidth / regionAll.width
  const d = particleSize * scale;
  noStroke();
  fill("blue");
  drawEllipse(p, scale, d);

noStrokeは円の外形線を表示しない設定です。流体粒子pは青色として、先程定義したdrawEllipse関数で表示しています。

  // wall particle
  fill("red");
  drawEllipse(pWall, scale, d);

壁粒子pWallは赤色で、同じくdrawEllipse関数で表示します。

さてこれで、可視化のメイン部分ができたので、draw関数の中に書いてやります。

function draw() {
  if (isRun) {
    time += timeDelta;
  
    motionUpdate();
    drawParticle()
  }
}

isRunのif文ですが、isRunというのは別途定義するグローバル変数で、truefalseの値をとります。isRuntrueならば計算を実行し、falseなら計算しないというスイッチになります(後述)。

draw関数はp5.jsで繰り返し呼ばれるので、粒子の運動方程式を解くmotionUpdate関数と粒子を可視化するdrawParticle関数を実行しています。時間timeは時間刻みtimeDeltaずつ増分してやります。

ボタンの作成

画面には[再生/停止][リセット]という2つのボタンが配置されています。このボタンを作りましょう。p5.jsにボタンを簡単に作る機能があるので、これを使って作ることにします。

まず、ボタンを押したときの挙動を決めておきます。

今回のプログラムでは以下のような挙動をするものとします。

  1. index.htmlが読み込まれたときに、初期位置の粒子を表示します。
  2. [再生/停止]ボタンを押すと、計算がスタートし、粒子のアニメーションが始まります。
  3. もう一度、[再生/停止]を押すと、計算が停止します。
  4. さらに、[再生/停止]を押すと、計算を再開します。
  5. [リセット]ボタンを押すと、初期位置に戻ります。

2~5はユーザーがいつのタイミングで、どの順番でボタンを押すかわかりません。これまでの講座のプログラムでは上から順番にプログラムが実行され、下までいくとプログラムが終了するものでした。これだと、順番を予め決めておかないとプログラムできません。

しかし、世の中のプログラムの多くはユーザーが自由に操作できるようになっています。つまり、「ボタンが押された」という状態になれば何かの処理をするというプログラムが必要です。この「ボタンが押された」のように何かこれまでと異なった状態になることをプログラミングではイベントと呼んでいます。

プログラム

このボタン作成の部分は【5-3】章でお話したp5.jsのsetup関数の中に書いておきます。setupは初期化のために最初に一度だけ呼ばれる関数でした。

  const buttonRun = createButton("再生 / 停止");
  buttonRun.position(10, canvasHeight + 20)
  buttonRun.mousePressed(runSPH);

  const buttonReset = createButton("リセット");
  buttonReset.position(100, canvasHeight + 20)
  buttonReset.mousePressed(resetSPH);

ボタンを作るのはcreateButton関数です。これはp5.jsの関数です。この関数はボタンのオブジェクトを返すので、最初のbuttonRunという変数には[再生/停止]ボタンのオブジェクトが格納されます。.positionはボタンの位置をキャンバス座標で指定します。

そして、.mousePressed()は「ボタンが押された」というイベントが起こったときには、その引数の関数を呼び出すという命令です。ここでは、runSPHという関数を呼び出します。

function runSPH() {
  isRun = !isRun;
}

runSPHは簡単な関数です。isRun = !isRunこの文は、上述のisRuntrueならfalseにする、falseならtrueにするというスイッチの役目をはたします。これで、[再生/停止]ボタンを押すごとに、計算を開始したり停止したりすることができるようになります。

同様に、buttonReset変数に、[リセット]ボタンオブジェクトを定義します。こちらはボタンが押されるとresetSPH関数を呼び出します。

function resetSPH() {
  isRun = false;
  time = 0;
  initialParticle();
  drawParticle();
}

resetSPH関数は初期状態に戻すための関数です。isRunfalse(計算停止)、時間timeをゼロにリセット、initialParticle関数で粒子を初期状態にする、そしてその状態で描画(drawParticle関数)しています。

最後になりましたが、setup関数を全て示しておきます。

function setup() {
  setParameter();
  createCanvas(canvasWidth, canvasHeight);
  resetSPH();

  const buttonRun = createButton("再生 / 停止");
  buttonRun.position(10, canvasHeight + 20)
  buttonRun.mousePressed(runSPH);

  const buttonReset = createButton("リセット");
  buttonReset.position(100, canvasHeight + 20)
  buttonReset.mousePressed(resetSPH);
}

setParameter関数で条件を設定し、createCanvasでキャンバスを定義し、resetSPH関数で初期状態にしています。後は前述のボタンの作成です。

グローバル変数

話が前後しますが、最後にグローバル変数の説明をしておきます。グローバル変数は、関数の外で宣言する変数で、ここで宣言された変数はどの関数からも参照することができます。通常はプログラムの先頭で宣言しておきます

"use strict";

// global variables ***************************************************
// canvas
let canvasWidth, canvasHeight;

// particle
let p, pWall;
let particleSize, h;
let massParticle, stiffness, density0, viscosity;
let w;
let grv;

// region
let regionAll, regionInner, regionInitial;
let thicknessWall;
let cell;

// time
let time, timeDelta;

// control
let isRun;

最初の"use strict"は、「strict(厳格)なモードにする」ことを意味し、より厳格なエラーチェックがなされます。とりあえず先頭に入れておくとよいでしょう。あとは、グローバル変数の宣言です。変数はこれまでに全て説明してきました。いちおう変数名をわかりやすくしていますので、変数名を見ると大体意味がわかると思います。

SPH法プログラムのソースコード

どうもお疲れさまでした。これでSPH法でダム崩壊問題を解くJavaScriptプログラムが完成しました。これまで説明したのと同様に、JavaScriptのプログラムとp5.js、index.htmlを用意すれば実行することができます。

SPH法ダム崩壊

→実行するとこうなります(別ウィンドウが開きます)

今まで、小出しにコードを示して来ましたが、全てのソースコードをGitHubにあげておきますので、参考にしてください。全てを眺めたうえで、もう一度復習してみると理解しやすいかも知れません。

→プログラムはこちら(GitHub)
※dambreakのフォルダーを参照してください。

まとめ

粒子の可視化部分を作成し、全てのプログラムを完成させました。全体のソースコードも載せているので復習してみてください。

さて、次回はシリーズ最終回です。回転するブレードのプログラムの説明をして終わりにします。


←前回 初期条件と境界条件
→次回 回転するブレードのプログラム

全体の目次

スポンサーリンク
科学技術計算のご相談は「キャットテックラボ」へ

科学技術計算やCAEに関するご相談、計算用プログラムの開発などお困りのことは「株式会社キャットテックラボ」へお問い合わせください。

お問い合わせはこちら

フォローする