【科学技術計算講座8-4】魚の移動方向のプログラム

前回はBoidモデルのプログラミング方法について説明しました。

Boidモデルのプログラミング方法について説明します。粒子法のプログラムを流用し効率的にプログラムを作成します。科学技術計算講座8「Boidモデルで魚の群れをシミュレーション」の第3回目です。

今回は魚の移動方向のプログラミングについてお話ししたいと思います。

魚の移動方向

魚はこれまで説明したように、Boidモデルの3つの行動規則によって移動方向が決まります。

  • 分離(Separation)
  • 整列(Alignment)
  • 結合(Cohesion)

それぞれの力(移動方向)を計算する式と速度や位置の更新方法は、第2回目で説明したとおりです。

Boidモデルの3つの行動規則を数式で表してみます。科学技術計算講座8「Boidモデルで魚の群れをシミュレーション」の第2回目です。

ここでは魚の移動方向と速度、位置の更新部分のプログラムを作成します。

魚の移動方向のプログラム

魚の移動方向(力)は、粒子法プログラムのparticleForce関数で計算します。粒子法では粒子は圧力や粘性力、重力によって力を受けますが、魚は先程の3つの行動規則による力を受けます。

function particleForce() {
  for (let i = 0, n = p.length; i < n; i++) {
    if (!p[i].active) continue;

    let forceSeparation = new Vector();
    let forceAlignment = new Vector();
    let forceCohesion = new Vector();
    let velAlignment = new Vector();
    let center = new Vector();
    let numAlignment = 0, numCohesion = 0;

    p[i].forNeighbor(cell, function(pNeighbor, rv) {
      if (p[i] !== pNeighbor) {
        const r = rv.magnitude();

        // separation force
        if (r > 0 && r <= radiusSeparation) {
          forceSeparation.x += rv.x / (r * r);
          forceSeparation.y += rv.y / (r * r);
        }

        // alignment force
        if (r <= radiusAlignment) {
          velAlignment.x += pNeighbor.velocity.x;
          velAlignment.y += pNeighbor.velocity.y;
          numAlignment++
        }

        // cohesion force
        if (r <= radiusCohesion) {
          center.x += pNeighbor.position.x;
          center.y += pNeighbor.position.y;
          numCohesion++;
        }
      }
    });

    if (numAlignment > 0) {
      velAlignment.x /= numAlignment;
      velAlignment.y /= numAlignment;
      forceAlignment = velAlignment.sub(p[i].velocity);
    }

    if (numCohesion > 0) {
      center.x /= numCohesion;
      center.y /= numCohesion;
      forceCohesion = center.sub(p[i].position);
    }

    // update
    forceSeparation.unit();
    forceAlignment.unit();
    forceCohesion.unit();
    p[i].force.x = wSeparation * forceSeparation.x + wAlignment * forceAlignment.x + wCohesion * forceCohesion.x;
    p[i].force.y = wSeparation * forceSeparation.y + wAlignment * forceAlignment.y + wCohesion * forceCohesion.y;
  }
}

pは魚を表しており、全ての魚でループして計算していきます。

魚にかかる力はそれぞれ、forceSeparationが分離、forceAlignmentが整列、forceCohesionが結合による力を表します。

p[i].forNeighbor(cell, function(pNeighbor, rv)は、粒子法と同じで、近傍の魚に対してループして和をとっていきます。

分離(Separation)

分離による力は先のページの(2-1)式で計算できます。

        // separation force
        if (r > 0 && r <= radiusSeparation) {
          forceSeparation.x += rv.x / (r * r);
          forceSeparation.y += rv.y / (r * r);
        }

rは近傍の魚と自分との距離で、半径radiusSeparationの円内にいる魚に対して、(2-1)式の和を計算します。

整列(Alignment)

整列による力は、(2-2)式、(2-3)式で計算します。

        // alignment force
        if (r <= radiusAlignment) {
          velAlignment.x += pNeighbor.velocity.x;
          velAlignment.y += pNeighbor.velocity.y;
          numAlignment++
        }

radiusAlignmentより距離の近い魚に対して(2-2)式で群れの速度velAlignmentを計算しています。numAlignmentは群れの魚の数をカウントしています。

    if (numAlignment > 0) {
      velAlignment.x /= numAlignment;
      velAlignment.y /= numAlignment;
      forceAlignment = velAlignment.sub(p[i].velocity);
    }

forceAlignmentは、(2-3)式で計算しています。

結合(Cohesion)

結合による力は(2-4)式、(2-5)式で表されます。

        // cohesion force
        if (r <= radiusCohesion) {
          center.x += pNeighbor.position.x;
          center.y += pNeighbor.position.y;
          numCohesion++;
        }

結合は、半径radiusCohesionの円内にいる他の魚の群れの中心centerを(2-4)式から計算し、

    if (numCohesion > 0) {
      center.x /= numCohesion;
      center.y /= numCohesion;
      forceCohesion = center.sub(p[i].position);
    }

(2-5)式から力forceCohesionを求めています。

力の合成

求めた3つの力は、(2-6)式で重み係数をかけて足し合わせます。

// update
    forceSeparation.unit();
    forceAlignment.unit();
    forceCohesion.unit();
    p[i].force.x = wSeparation * forceSeparation.x + wAlignment * forceAlignment.x + wCohesion * forceCohesion.x;
    p[i].force.y = wSeparation * forceSeparation.y + wAlignment * forceAlignment.y + wCohesion * forceCohesion.y;

各力は、.unit()で単位ベクトルにした後、重み係数wSeparationwAlignmentwCohesionをかけて合成しています。

速度と位置の更新

魚の速度と位置の更新は、粒子法と同じく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);
    }
  }
}

setParticleToCell()は、粒子を空間分割法のセルに分ける関数です。

    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;

粒子法の速度と位置はLeap-Frog法で更新しましたが、今回は単純に(2-7)式と(2-8)式よりオイラー的に更新します。ただ、魚の速度に関しては、大きくなりすぎないように.limitMaxメソッドで、速度に制限を設けています(最大値はmaxVelocity)。

motionUpdate関数では次に境界条件の取り扱いがプログラムされていますが、これは次回説明することにしましょう。

まとめ

今回は、魚の移動方向と速度、位置の更新部分のプログラムについて説明しました。

次回は、境界条件や初期条件の取り扱いについて説明します。


←前回 Boidモデルのプログラミング方法

→次回 境界条件と初期条件の取り扱い

全体の目次

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

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

お問い合わせはこちら

フォローする