水族館を作ろう:擬似ポテンシャル力による衝突判定


~流れるプールでぐ~るぐる~




■はじめに

今回は衝突判定です。壁にぶつからないようにする処理を入れます。
また、対流を起こす処理を入れます。これによって、水族館のように魚が一方向に泳ぎます。
今回のネタは、珍しくオリジナルです。

今回のソースは、次のものです。

ぐるぐるぐるぐる回ります。

■BOID の動きの追加

今回、BOID の動きに関して追加する関数は、2つです。
AvoidObstacle と、FeelFlowで、前者が障害物を避け、後者が対流を感じて動きます。
他は前回と同じです。

// ----------------------------------------------------------------------------
// フレームごとのアップデート
//-----------------------------------------------------------------------------
void CBoid::FlockIt (int flock_id, CBoid *first_boid)
{
    D3DXVECTOR3 acc = D3DXVECTOR3(0,0,0);

    // Step 1:  前の時間に決定した速度を使って位置の更新
    m_oldpos = m_pos;               // 前の座標を保存しておく
    m_pos += m_vel;                 // 移動

    this->SeeFriends (first_boid);  // Step 2:  仲間を探す

    if (m_num_flockmates_seen) {    // Step 3:  群れの動作
        中略
    }

    acc += FeelFlow();            // Step 6.5: 流れに身を寄せる
    
    acc += Cruising();              // Step 7:  巡航
    
    acc += AvoidObstacle();      // Step 7.5: 障害物を避ける

    // Step 8:  加速の制限
    if (D3DXVec3Length(&acc) > MAX_CHANGE) {
        D3DXVec3Normalize(&acc, &acc);
        acc *= MAX_CHANGE;
    }

   // Step 9:  速度変化
    m_oldvel = m_vel;    // 前の速さを取っておく
    m_vel += acc;        // 加速

   // Step 11:  速すぎたときに、速度を制限する
   if (MAX_SPEED < (m_speed = D3DXVec3Length(&m_vel))) {
        D3DXVec3Normalize(&m_vel, &m_vel);
        m_vel *= MAX_SPEED;
        m_speed = MAX_SPEED;
   }

    this->ComputeRPY();// Step 12:  回転の計算

    this->WorldBound();// Step 13:  世界の境界の処理
}

■障害物を避ける

では、実際の障害物の避け方です。
今回やりたかったことは、

障害物の距離に応じて、よけ方を変える

ということです。
つまり、『障害物が遠くにあるときは、あまり障害物を気にしないが、障害物が近くにあるときは、急激によける』ということです。
いくつか方法があると思いますが、私は、大学で物理をやっていたので、物理でよく知られた方法で扱おうと思います。
それがポテンシャル法です。

ポテンシャル法とは、障害物からの距離に応じて『ポテンシャル』と呼ばれる量が変化します。
力は、ポテンシャルの傾いている方向に働き、大きさはポテンシャルの傾きに比例します。
ポテンシャルの傾きが急なら力は大きくなり、傾きがなだらかなら、力は小さくなります。
イメージとしては、山の高さがポテンシャルで、急な山ほど上るのが大変ということです。

今回用いるポテンシャルは、物理で用いるポテンシャルそのものではありません。
壁からの距離に応じて決まる、擬似的なポテンシャルです。
実際のポテンシャルは、壁の場合には壁から1Å程度しか存在しません。
このポテンシャルは、壁が見える程度の範囲でも存在するもので、BOIDの感じる『氣』のポテンシャルといえます。

今回のポテンシャルは、距離の2乗に反比例する強さのものを用います。
このポテンシャルによって、BOIDは壁に近づきすぎたときに、壁から避けようとします。
次がソースです。

// ----------------------------------------------------------------------------
// Rule #4 (Obstacle avoidance) 障害物を避ける
//-----------------------------------------------------------------------------
D3DXVECTOR3 CBoid::AvoidObstacle (void)
{
   D3DXVECTOR3 change = D3DXVECTOR3(0,0,0);

   bool hit = false;
   // 障害物を検索して、ポテンシャル力を求める
   for(CObstacle *p = CObstacle::GetFirst(); NULL != p; p = p->GetNext()){
       D3DXVECTOR3 force = p->GetDistance(this->m_pos);
       float d = D3DXVec3Length(&force)+0.0001f;
       if(d < m_perception){              // 見えないものは判定しない
            hit = true;    
            D3DXVec3Normalize(&force, &force);
            change += force / d;          // 距離の2乗に反比例するポテンシャルの力は、
                                          // 微分して距離の一乗に反比例する
       }
   }
    
   if(hit){// 壁をよけたら、動きを制御
        // だんだん向きを変える
        float ratio = D3DXVec3Dot(&change, &change);
        if (ratio < MIN_URGENCY) ratio = MIN_URGENCY;
        if (ratio > 5.0f*MAX_URGENCY) ratio = 5.0f*MAX_URGENCY;
        
        D3DXVec3Normalize(&change, &change);
        change *= ratio;
   }
    
    return (change);
}

今回のために、障害物クラス CObstacle を導入しました。
やはり、線形リストで順番に呼び出せるようにしました。
BOID での判定の時と同じように、遠いものは処理しないようにします。
実際に壁との距離を求める関数は、次のようになります。

// ----------------------------------------------------------------------------
// pos と各障害物との距離を求める
// ----------------------------------------------------------------------------
D3DXVECTOR3 CObstacle::GetDistance(D3DXVECTOR3 pos)
{
    D3DXVECTOR3 dist;

    // m_pos:平面上の一点
    // m_dir:法線
    dist = pos - m_pos;
    float d = D3DXVec3Dot(&dist, &m_dir);
    dist = d * m_dir;

    return dist;
}

m_dir が、壁の法線ベクトルで、m_posは、壁上の一点です。距離は法線方向の距離を求めます。
これで、壁に近づくと、向きを変えて遠ざかろうとします。

■対流の効果

さて、実は上の効果を思い付いてプログラムを書いたら、
魚が壁に張り付くことは無かったのですが、やはり、壁の縁にいこうとします。
よく考えたら、魚は皆同じ方向へ泳ごうとするので、定位置に集まるか、一方向に進みます。
それを避けるために、強制的に進ませるます。
どんな動きがいいのかと考えましたが、とりあえず泳ぐプールのようにぐるぐる回します。

対流を上から見た図

また、真中をぐるぐる動くのはやだったので、対流の強さを、端に行くほど強くします。
プログラムは、次のようにしました。

// ----------------------------------------------------------------------------
// Rule #ex (Feel Flow) 流れに身を任せる
//-----------------------------------------------------------------------------
D3DXVECTOR3 CBoid::FeelFlow (void)
{
   D3DXVECTOR3 change = D3DXVECTOR3(0,0,0);

    float x = (float)fabs(m_pos.x-0.0f);
    float z = (float)fabs(m_pos.z-0.0f);

    if(x < z){
        // 横向きの流れ
        change.x = -(m_pos.z-0.0f);
    }else{
        // 縦向きの流れ
        change.z =  (m_pos.x-0.0f);
    }

    float ratio = 1.0f*D3DXVec3Length(&change);
    if (ratio < MIN_URGENCY) ratio = MIN_URGENCY;
    if (ratio > MAX_URGENCY) ratio = MAX_URGENCY;
    D3DXVec3Normalize(&change, &change);
    change *= ratio;
    
    return (change);
}

X と Z の絶対値から、上下左右どちらの位置にいるのかを判定して、 横方向、縦方向の流れを設定します。

■最後に

今回で、障害物をよけることができるようになりました。
障害物が壁の場合しか扱いませんでしたが、障害物からの距離がわかれば他の形の障害物も扱えます。
対流の項も、エリア分けして、エリアごとに適当な流れを設定すれば、まずまず使えると思います。
これで、ゲーム中に背景として適当に泳いでいる物体として使えるのではないでしょうか?





もどる

imagire@gmail.com