ホームゲームつくろー!衝突判定編

2D衝突編
その11 円とレイの衝突点


 2Dゲームではキャラクタのコリジョンを円で定義する事があります。そのキャラクタに対してある点からビームを打つとして、そのビームが当たるかどうか?それを調べるには円とレイとの衝突が必要です。また衝突したとしてそこから爆発のパーティクルなどを発生させたい場合、衝突点を計算できなければなりません。



@ 衝突判定と衝突点をいっぺんに求めてしまおう!

 円とレイが衝突しているかどうかだけを考える時には、円の中心点とレイとの距離を求めれば良いのですが、今回は衝突点も求めようとしています。衝突している事とその衝突点をいっぺんに求めてしまうご機嫌な計算があります(^-^)

 円の半径をr、レイの始点をA、撃つ方向ベクトルをVとしましょう:

 点Oを円の中心点として、始点AからOに向けたベクトルをVaとしておきます。また円とレイとの衝突点をCとしておきましょう。簡単のために点Oを原点としておきます。この時Cを決める要因を考えてみます。

 まずOCの長さはrです。点CはOから距離rの位置のどこかにあるわけです。もう一つの条件として、点Cはレイ上にあります。この2つの条件を数式にすると、

となりますね。||C||^2はベクトルOC同士の内積です。よって、この二つの式から次のような連立方程式が出来ます:

これをちまちまと展開すると、

となります。今求めたいのはtです。その他はすべて既知の定数ですね。よって、解の公式からtを求められます:

このtを先のCの式に入れればたちどころに衝突点(2点)が出てきます。もしベクトルVを正規化(長さを1)していれば、Dot(V,V)=1なので、上の式はもっと簡単になります:

 もしレイが円に接しているなら、ルートの中がゼロになり、tは重解となります。衝突していない場合、ルートの中はマイナスとなり実数解が存在しません。これで衝突判定が出来てしまうわけです。お得ですね(^-^)



A 衝突点算出関数

 では衝突点を算出する関数を作りましょう:

円とレイとの衝突点算出関数
// 円とレイとの衝突点
// ox, oy : 円の中心点
// r : 円の半径
// ax, ay : レイの始点
// vx, vy : レイの方向ベクトル
// outC1x, outC1y: 衝突点C1(近い方)の座標。必要無い場合はNULL
// outC2x, outC2y: 衝突点C2(遠い方)の座標。必要無い場合はNULL
// 戻り値: 衝突している時true、していないもしくは引数が不正の時false

bool calcColPos_2DCircleRay(
float ox, float oy, float r,
float ax, float ay,
float vx, float vy,
float *outC1x, float *outC1y,
float *outC2x, float *outC2y
) {
    // 半径がマイナスはエラー(半径ゼロは許容)
    if ( r < 0.0f )
        return false;

    if ( vx == 0.0f && vy == 0.0f )
        return false;

    // 円の中心点が原点になるように始点をオフセット
    ax -= ox;
    ay -= oy;

    // レイの方向ベクトルを正規化
    float len = sqrtf( vx * vx + vy * vy );
    vx /= len;
    vy /= len;

    // 係数tを算出
    float dotAV = ax * vx + ay * vy;
    float dotAA = ax * ax + ay * ay;
    float s = dotAV * dotAV - dotAA + r * r;
    if ( fabs(s) < 0.000001f )
        s = 0.0f; // 誤差修正

    if ( s < 0.0f )
        return false; // 衝突していない

    float sq = sqrtf( s );
    float t1 = -dotAV - sq;
    float t2 = -dotAV + sq;

    // もしt1及びt2がマイナスだったら始点が
    // 円内にめり込んでいるのでエラーとする
    if ( t1 < 0.0f || t2 < 0.0f )
        return false;

    // 衝突座標を出力
    if ( outC1x != 0 && outC1y != 0 ) {
        *outC1x = ax + t1 * vx + ox;
        *outC1y = ay + t1 * vy + oy;
    }
    if ( outC2x != 0 && outC2y != 0 ) {
        *outC2x = ax + t2 * vx + ox;
        *outC2y = ay + t2 * vy + oy;
    }

    return true;
}

2Dゲームの衝突にお使い下さい〜。