球とレイとの交差判定のバグ修正


勘違いしていた点が一点ありました。

前回の解説の段階から、一歩進んで球とレイとの交点を求めるように交差判定を修正していました。
このとき、レイ上の一点を

P(t)=Q+tV

こんな風に表現していたのでした。

そして、球の数学的な表現と、レイの表現を用いて、次のような二次方程式を解けば交点となるレイ上の一点を示すパラメータtを手に入れることができるのでした。

|V|^2t^2 + 2QVt + |Q|^2 - r^2 = 0

ここから、二次方程式の解の公式を用いればtの値が求まります。

二次方程式の解の公式は、

ax^2+2bx+c=0の時の解の公式

x=\frac{-b\pm\sqrt{b^2-ac}}{a}

こんな形をしていて

D=b^2-ac

を判別式として用いることで、交差しているかしていないのかが判定できます。

 D < 0 の場合、 \sqrt{ D }虚数解となるため交差は起きないことがわかります。

 D = 0 の場合、レイは球に一点で接していることがわかります(解が一つだから)

 D > 0 の場合、レイと球の交点は二点有ります。(解が二つ得られるから)

さて、 D > 0 の場合には、次の二つの解が考えられます。

t_0=\frac{-b-\sqrt{D}}{a}

t_1=\frac{-b+\sqrt{D}}{a}

この時には、どちらのtを交点とみなすべきなのでしょうか?

当然レイに近い方の点すなわち値が小さい方のtを採用すべきです。

実際に二つのtを計算して比較しても良いのですが、a=|V|^2は正の値であることが確定しているため、小さい方のtは必ず

t_0=\frac{-b-\sqrt{D}}{a}

の式で得られます。

 t < 0 の場合、レイの基準点からレイの進行方向とは逆方向に交点が存在しているため、レイと球との交差は起きていないと考えることが出来ます。

当然、t_0 < 0t_1 >= 0の場合も考えられますが、これってレイの基準点が球の内部に入り込んでいる状態ですよね?

要するに裏側からあたっていることになるので、交差は起きないと判断することにしておきます。

...ここで、バグってて、近い方のtをt_1=\frac{-b+\sqrt{D}}{a}として求めていました。
(値が大きくなるから勘違いしたと思われ(恥))

そんなわけで実装は以下の通り。

/// 交差判定
bool Sphere::Intersect(
                       const geometry::Ray& i_ray,     ///< 交差判定を行うレイ
                       f32* i_pHitT,                   ///< 交差した場合、開始点からの距離
                       geometry::DifferentialGeometry* i_pDG     ///< 交差したときの情報が入る
                       ) const
{
    math::Vec3f a_vQ = i_ray.GetPos() - m_ptPos;
    const math::Vec3f& a_vV = i_ray.GetDir();

    f32 a_fQVdot = math::Vec3f::Dot( a_vV, a_vQ );
    f32 a_a = math::Vec3f::Dot( a_vV, a_vV );
    f32 a_b = a_fQVdot;
    f32 a_c = math::Vec3f::Dot( a_vQ, a_vQ ) - (m_fRadius * m_fRadius);

    f32 a_fD = a_b * a_b - a_a * a_c; // 判定式
    if( a_fD < 0.0f ){
        return false;
    }

    f32 a_fT = (a_b * -1.0f - math::Sqrt( a_fD )) / a_a;

    if( a_fT < 0.0f ){
        return false;
    }

    *i_pHitT = a_fT;

    if( i_pDG ){
        i_pDG->ptPos = i_ray( a_fT );
        i_pDG->vNormal = math::Normalize( i_pDG->ptPos - m_ptPos );
    }

    return true;
}

レイの進行方向を示しているベクトルは正規済み(=長さが1)だとすると、こんなコードにすることも出来るはずです

/// 交差判定
bool Sphere::Intersect(
                       const geometry::Ray& i_ray,     ///< 交差判定を行うレイ
                       f32* i_pHitT,                   ///< 交差した場合、開始点からの距離
                       geometry::DifferentialGeometry* i_pDG     ///< 交差したときの情報が入る
                       ) const
{
    math::Vec3f a_vQ = i_ray.GetPos() - m_ptPos;
    const math::Vec3f& a_vV = i_ray.GetDir();  // 正規済みとする(よって a_a = 1.0f としてよい)

    f32 a_fQVdot = math::Vec3f::Dot( a_vV, a_vQ );
    f32 a_a = 1.0f; // math::Vec3f::Dot( a_vV, a_vV );
    f32 a_b = a_fQVdot;
    f32 a_c = math::Vec3f::Dot( a_vQ, a_vQ ) - (m_fRadius * m_fRadius);

    // f32 a_fD = a_b * a_b - a_a * a_c; // 判定式
    f32 a_fD = a_b * a_b - a_c; // 判定式
    if( a_fD < 0.0f ){
        return false;
    }

    // f32 a_fT = (a_b * -1.0f - math::Sqrt( a_fD )) / a_a;
    f32 a_fT = (a_b * -1.0f - math::Sqrt( a_fD ));

    if( a_fT < 0.0f ){
        return false;
    }

    *i_pHitT = a_fT;

    if( i_pDG ){
        i_pDG->ptPos = i_ray( a_fT );
        i_pDG->vNormal = (i_pDG->ptPos - m_ptPos) / m_fRadius;
    }

    return true;
}

...3Dグラフィックス数学に丁寧に書かれていましたね

ゲームプログラミングのための3Dグラフィックス数学

ゲームプログラミングのための3Dグラフィックス数学