前回は、魚の移動方向と速度、位置の更新部分のプログラムについて説明しました。
今回は境界条件と初期条件の取り扱いについて説明し、プログラムを完成させます。
目次
境界条件
魚が行動する領域は、2次元の矩形の領域とします。これをregionというオブジェクト変数で定義します。
魚がregionの端から外に出た場合にどのような扱いにするか境界条件として決めておきましょう。境界条件はいろいろ考えられます。例えば、
- 外に出たら削除する(粒子法のプログラムではこうしていました)
- 反射する(壁で跳ね返るイメージです)
- 周期的にする(右から出ていったものは左から入ってくる)
などです。
今回は、周期境界条件としましょう。これは、regionの右端から出ていった魚は、左端から入ってくる。上端から出ていった魚は、下端から入ってくるというように周期的に移動するような条件です。こうするとくるくる回るようにいつまでも魚が泳いでくれます。
境界条件は、前回説明したmotionUpdate関数の最後にプログラムしておきます。
function motionUpdate() {
setParticleToCell();
particleForce();
for (let i = 0, n = p.length; i < n; i++) {
if (!p[i].active) continue;
p[i].velocity.x += p[i].force.x * timeDelta;
p[i].velocity.y += p[i].force.y * timeDelta;
p[i].velocity.limitMax(maxVelocity);
p[i].position.x += p[i].velocity.x * timeDelta;
p[i].position.y += p[i].velocity.y * timeDelta;
if (p[i].position.x < region.left) {
p[i].position.x = region.right - (region.left - p[i].position.x);
} else if (p[i].position.x > region.right) {
p[i].position.x = region.left + (p[i].position.x - region.right);
}
if (p[i].position.y < region.bottom) {
p[i].position.y = region.top - (region.bottom - p[i].position.y);
} else if (p[i].position.y > region.top) {
p[i].position.y = region.bottom + (p[i].position.y - region.top);
}
}
}
ここで以下の箇所では、
if (p[i].position.x < region.left) {
p[i].position.x = region.right - (region.left - p[i].position.x);
魚p[i]のx座標が領域の左端region.leftより小さくなる(左端より外に出る)と、外に出た分を領域の右端region.rightから内側に移動させるという操作を行っています。
これで、左端から出ると右端から入ってくるように見えます。同様に他の境界端でも同じ操作を行っています。
ちなみに今回は簡単にするため、行動規則を計算するときの近傍の魚を見つける円については、領域の中だけで考えます。つまり、左端の魚は右端の魚からの影響は受けないようになっています。
初期条件
次に初期条件を考えます。初期には領域の中にランダムに魚を配置することにします。そして、ランダムな方向に初速を与えます。
初期条件は、粒子法と同じくinitialParticle関数で設定します。
function initialParticle() {
const create = function(p, region) {
for (let i = 0; i < numBoid; i++) {
const x = region.left + Math.random() * region.width;
const y = region.bottom + Math.random() * region.height;
const vx = maxVelocity * (2 * Math.random() - 1);
const vy = maxVelocity * (2 * Math.random() - 1);
p.push(new Particle(x, y, vx, vy));
}
};
p = [];
create(p, region);
}
Math.random()は、0~1までの乱数が返ってくる関数です。初期位置x、yと、初速vx、vyは、この乱数を使ってランダムに設定しています。
パラメータ
様々なパラメータの設定は、粒子法と同じようにsetParameter関数で行います。
function setParameter() {
// number of boids
numBoid = 100;
// flock radius
radiusSeparation = 0.2;
radiusAlignment = 0.5;
radiusCohesion = 0.5;
// weight parameter
wSeparation = 1.0;
wAlignment = 1.0;
wCohesion = 0.5;
// velocity
maxVelocity = 0.4;
// region
region = new Rectangle(0.0, 0.0, 10.0, 10.0);
const h = Math.max(radiusSeparation, radiusAlignment, radiusCohesion);
cell = new Cell(region, h);
// time
timeDelta = 0.1;
// canvas
canvasWidth = 600;
canvasHeight = canvasWidth * region.height / region.width;
}
なお、空間分割法に入力するセル幅hは、行動規則の半径で一番大きなものを指定すると、近傍の探索が不足なくできます。
シミュレーションプログラムのソースコード
その他は、粒子法特有の箇所を削除したり、関数名を変えるだけです。あとは、粒子法のプログラムと同様に、p5.jsとHTMLファイルを用意すれば実行することができます。すべてのソースコードをGitHubにアップしておきますので、参考にしてみてください。
→プログラムはこちら(GitHub)
※baseのフォルダを参照してください。
実行すると、最初ランダムに散らばっていた魚たちが次第に群れをなして泳ぐ様子がシミュレーションできます。行動規則の半径や重み係数を変えてみると、また違った群れのパターンを形成するので、試してみてください。
なお、このプログラムは、Boidモデルの3つの行動規則だけで運動する場合です。第1回目でお見せした、カーソルに寄ってくるケースは、次回プログラミングしてみます。
まとめ
今回は、境界条件と初期条件の取り扱いについて説明し、Boidモデルのプログラムを完成させました。
次回は最終回です。魚がカーソルに寄ってくるプログラムを作り、インタラクティブなシミュレーションができるようにしてみましょう。