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

3D衝突編
その18 直線とAABB


 直線とAABBの交差判定は概念がわかればそれほど難しくはありません。



@ スラブ

 2つの平行な平面で挟まれた厚みのあるプレートを「スラブ(slab)」と言います。OBBは互いに垂直な3つのスラブが交差した状態と言えます。AABBはさらに各スラブが軸に垂直になっています。

 スラブは無限に広がるプレートなので、スラブに平行でない直線は必ず交差する事になります。例えばX軸に垂直なスラブと直線の関係は下図のようになります:

 直線上の一点Pから双方のスラブ面までの到達間隔tは方向ベクトルd(正規化します)のX成分の大きさを単位として次のように計算ができます:

 近面: t_near = ( min - p.x ) / d.x
 遠面: t_far = ( max - p.x ) / d.x

この到達間隔が判定の鍵となります。



A 2DのAABB

 2DのAABBは2つのスラブで囲まれた範囲です。そこに直線を引くと次の2つのパターンが考えられます:


 直線PはAABBと交差しています。この時、「到達間隔にも交差」が起きているのがわかります。一方で直線QとAABBを見ると、到達間隔の交差が起こっていません。これを調べると衝突が判定できます。

 具体的なプロセスです。まず、X軸垂直のスラブについて到達時刻を算出します(P.X.tmin、P.X.tmax)。次にY軸垂直のスラブの到達時刻を調べます。到達時刻のうち入り側についてはより遅い方、出側についてはより早い方の到達間隔を取ります。

 直線Pについてみてみると、より遅い入り間隔はP.X.tmin、より早い出間隔はP.Y.maxです。出間隔から入り間隔を引くと(P.Y.max - P.X.min)プラスです。この場合交差が発生しています。

 一方直線Qについてみると、候補入り間隔はQ.X.tmin、候補出間隔はQ.Y.maxです。同様に出-入りを算出するとマイナスです。この場合は交差していません。



B 3DのAABB

 3DのAABBの場合パターンが増えますが、プロセスは変わりません。XYZそれぞれの入り間隔及び出間隔を調べ、候補入り間隔と出間隔を決定し、引き算します。結果がプラスならば交差しています。マイナスだったら交差していません。

 3DのAABBについてなんとか図を描こうと思ったのですが、どうやってもカオスになってしまいます(T_T)。すいませんがAの図を3次元にして頭で想像して下さい。



C 直線とAABBの交差判定関数公開

 最後はいつものように判定関数を公開します。ご自由にお使い下さい。

/**
* @brief 光線と境界ボックスとの交差判定
* @param pos ワールド空間での光線の基点
* @param dir_w ワールド空間での光線の方向
* @param aabb 境界ボックス(ローカル)
* @param mat 境界ボックスのワールド変換行列
* @param t 衝突間隔(出力)
* @param colPos 衝突位置
* @return 衝突していればtrue
*/
bool ColRayBox(
    D3DXVECTOR3* pos,
    D3DXVECTOR3* dir_w,
    AABB* aabb,
    D3DXMATRIX* mat,
    float& t,
    D3DXVECTOR3* colPos = 0 )
{

    // 光線を境界ボックスの空間へ移動
    D3DXMATRIX invMat;
    D3DXMatrixInverse( &invMat, 0, mat );

    D3DXVECTOR3 p_l, dir_l;
    D3DXVec3TransformCoord( &p_l, pos, &invMat );
    invMat._41 = 0.0f;
    invMat._42 = 0.0f;
    invMat._43 = 0.0f;
    D3DXVec3TransformCoord( &dir_l, dir_w, &invMat );

    // 交差判定
    float p[ 3 ], d[ 3 ], min[ 3 ], max[ 3 ];
    memcpy( p, &p_l, sizeof( D3DXVECTOR3 ) );
    memcpy( d, &dir_l, sizeof( D3DXVECTOR3 ) );
    memcpy( min, &aabb->min, sizeof( D3DXVECTOR3 ) );
    memcpy( max, &aabb->max, sizeof( D3DXVECTOR3 ) );

    t = -FLT_MAX;
    float t_max = FLT_MAX;

    for ( int i = 0; i < 3; ++i ) {
        if ( abs( d[ i ] ) < FLT_EPSILON ) {
            if ( p[ i ] < min[ i ] || p[ i ] > max[ i ] )
                return false; // 交差していない
            } else {
                // スラブとの距離を算出
                // t1が近スラブ、t2が遠スラブとの距離
                float odd = 1.0f / d[ i ];
                float t1 = ( min[ i ] - p[ i ] ) * odd;
                float t2 = ( max[ i ] - p[ i ] ) * odd;
                if ( t1 > t2 ) {
                    float tmp = t1; t1 = t2; t2 = tmp;
            }

            if ( t1 > t ) t = t1;
            if ( t2 < t_max ) t_max = t2;

            // スラブ交差チェック
            if ( t >= t_max )
                return false;
        }
    }

    // 交差している
    if ( colPos ) {
        *colPos = *pos + t * (*dir_w);
    }

    return true;
}



AABBはこちらです。

struct AABB {
    D3DXVECTOR3 min;
    D3DXVECTOR3 max;
};



D 参照

 ゲームプログラミングのためのリアルタイム衝突判定 p179 5.3.3 ボックスに対する光線や線分の交差