ModelMatrix 解説


■ ModelMatrix 概略

 ModelMatrix は3Dの当たり判定オブジェクト EGL_MODEL_MATRIX を利用したサンプルプログラムです。
 箱の中をボールが落下しますが、床や壁にぶつかると跳ね返ります。
 マウスをドラッグすることでカメラの角度を変えることが出来ます。




■ 当たり判定オブジェクトの初期化

 プログラムのメインループは EModelMatrixApp::ThreadProc 関数にあります。
 この関数はセカンダリスレッドとして起動されるスレッド関数です。
 ここではこの関数内部を順を追って説明します。


    E3D_LIGHT_ENTRY    leLight ;
    leLight.dwLightType = E3D_VECTOR_LIGHT ;
    leLight.rgbColor.dwPixelCode = 0x00FFFFFF ;
    leLight.rBrightness = 1.0F ;
    leLight.vecLight = E3DVector4( 1, 1, 1 ) ;

 上記コードでは、光源のための構造体を初期化しています。白色の無限遠光源を設定します。
 光源を実際に設定しているのはループの中ですが、光源は常に固定しているので先頭で値を設定しています。


    E3DVector    vPos( 0, -300, 0 ), vMove( 0, 0, 0 ) ;
    E3DModelJoint    jntField ;
    HEGL_MODEL_MATRIX    hMatrix = ::eglCreateModelMatrix( ) ;
    jntField.RefreshJoint( ) ;
    jntField.TransformModel( m_render, m_mdlMatrix ) ;
    hMatrix->Initialize
        ( (PCE3D_PRIMITIVE_POLYGON*)
            m_mdlMatrix.GetPrimitiveList(),
                m_mdlMatrix.GetPrimitiveCount() ) ;

 上記コードでは、当たり判定用オブジェクトを生成し、初期化しています。
 ここで jntField はダミーのジョイントです。
 このサンプルでは当たり判定の対象のオブジェクトに特に座標変換を施す必要がありませんので特に何もしていませんが、何らかの変換された座標系で当たり 判定を行いたい場合には、ジョイントに座標変換を設定し、又は親に当たるジョイントを引数に

    jntField.TransformJoint( /* 親ジョイント */ ) ;

を呼び出してから TransformModel 関数でモデルデータを座標関数してください。
 この例では、ループの外側で当たり判定オブジェクトを1度初期化しているだけですが、もし当たり判定オブジェクト自体が移動したり、回転したりする場 合には hMatrix->Initialize 関数で必要に応じて初期化しなくてはなりません。


■ 終了判定

    MSG    msg ;
    if ( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
    {
        if ( msg.message == WM_QUIT )
            break ;
    }

 メインループの先頭でスレッドメッセージを受け取り、終了判定を行っています。
 エスケープキーが押されるとスレッドに対して WM_QUIT メッセージが送られるようにインプリメントされていることを確認してください。


■ 球の移動と当たり判定

    for ( int i = 0; i < 8; i ++ )
    {
        int    nHitResult = 0 ;
        E3DVector    vReflection = vMove, vHitPos, vHitNormal ;
        hMatrix->IsHitAgainstSphere
            ( &vPos, 99, &nHitResult, &vHitPos, &vHitNormal, &vReflection ) ;
        if ( nHitResult )
        {
            if ( vMove.InnerProduct( vHitNormal ) > 0 )
            {
                break ;
            }
            vMove = vReflection * 0.99F ;
            vPos = vHitPos + vHitNormal * 100.0F ;
        }
        else
        {
            E3DVector    vPos2 = vPos + vMove ;
            hMatrix->IsCrossingSegment
                ( &vPos, &vPos2, 1,
                    &nHitResult, &vHitPos, &vHitNormal, &vReflection ) ;
            if ( nHitResult )
            {
                if ( vMove.InnerProduct( vHitNormal ) > 0 )
                {
                    break ;
                }
                vMove = vReflection * 0.95F ;
                vPos = vHitPos + vHitNormal * 100.0F ;
            }
            else
            {
                break ;
            }
        }
    }

 ここでは球の当たり判定を行っています。
 まず、緑色の部分に注目してください。
 ここでは、モデルと球との当たり判定を行っています。
 IsHitAgainstSphere 関数は球体とモデル(三角ポリゴン)との当たり判定を行います。
 一方、IsCrossingSegement 関数は線分とモデル(三角ポリゴン)との当たり判定を行っています。
 なぜこの2つを両方用いるかというと、IsHitAgainstSphere 関数では、vMove(=移動ベクトル)が大きいと壁や床をすり抜けてしまう可能性があるからです。もっとも、このサンプルではその心配があるような値に は(vMoveは)なりませんが、ここではサンプルとして IsCrossingSegment 関数も併記しています。
 通常はこのどちらかだけで十分事足りるでしょう。
 このサンプルように、ある点(この場合は球の中心点)の周辺の一定の幅を持った当たり判定を行いたい場合には IsHitAgainstSphere 関数が向いています。

 では次に橙色の部分に注目してください。
 ここでは球体と接触した(又は移動ベクトルと交差した)ポリゴンの法線を使って追い出し処理を行っています。
 球体は一定の半径がありますから、その中心点と壁や床とが接触した座標で跳ね返っては不自然です。(跳ね返るときに半分めり込んだようになります) もちろん、IsHitAgainstSphere 関数で当たり判定を行う場合には、一定の距離が離れていても当たり判定がなされますので、目だって不自然な跳ね返り方はしませんが、やはりvMoveが大 きな値になると一瞬めり込む場合がありえます。
 そこで、球とポリゴンとの接触点(vHitPos)から、ポリゴンに対して垂直方向(vHitNormal)に一定距離(=100.0)離れた座標へ座 標を修正しています。
 この処理は、跳ね返った後に、再び同じポリゴンに対して当たり判定がなされてしまう(IsHitAgainstSphere関数は一定の半径内であれば 当たり判定されてしまうことに注意しよう)ことを防止するための保障にもなります。(もっとも、そのような現象が発生しないように次に説明する別の処理が 含まれているわけですが)

 次に黄色の部分に注目してください。
 ここでは、移動ベクトルとポリゴンの法線との内積を判定しています。内積が0以上の値になるのは、2つのベクトルがなす角が90〜-90度の範囲にある (つまり同じ方向を向いている)ことを意味しています。
 このサンプルでは当たり判定は片面のみ、つまり法線の向きを考慮し、片側から移動してきてものだけを跳ね返し、その逆向きには跳ね返さない法則にしてい ますが、例えば次のようなコードにすると、両側からの跳ね返りを実現できます。

    if ( vMove.InnerProduct( vHitNormal ) > 0 )
    {
        vPos = vHitPos - vHitNormal * 100.0F ;
    }
    else
    {
        vPos = vHitPos + vHitNormal * 100.0F ;
    }
    vMove = vReflection * 0.99F ;


 ところで、当たり判定は for ループの中で行われていますがなぜでしょうか?
 これは当たり判定を行ったときの追い出し処理の結果、別のポリゴンに当たってしまう可能性があるので、繰り返し判定を行っています。

    vPos += vMove ;
    vMove.y += 1 ;
    m_jntSphere.Position() = vPos ;

 当たり判定を行った後は、移動ベクトル(vMove)を使って座標を移動し、ジョイントに座標を設定します。
 vMove.y += 1 は重力による加速です。


■ レンダリング

    m_mmwMain.Lock( ) ;
    m_render.FlushAllPolygon( ) ;
    m_render.SetViewPoint
        ( m_mmwMain.m_vViewPoint, E3DVector( 0, 0, 0 ), 0 ) ;
    m_render.SetLightEntries( 1, &leLight ) ;
    m_render.AddModel( m_jntField ) ;
    m_render.AddModel( m_jntSphere ) ;
    m_render.PrepareRendering( ) ;
    m_mmwMain.Unlock( ) ;
    m_wndMain.UpdateWindow( ) ;
    Sleep( 30 ) ;


 上記コードでフレーム表示を更新しています。
 このサンプルではレンダリングに ERenderSprite クラスを利用していますので、明示的に RenderAllPolygon 関数を呼び出す必要はありません。
 この例は非常に手抜きなので、あまり参考にしないほうがよいでしょう。(笑)


■ 終了

    hMatrix->Release( ) ;
    return    0 ;

 EGL_MODEL_MATRIX オブジェクトは、必要なくなったら Release 関数で破棄してください。



Copyright (C) 2004 Leshade Entis, Entis-soft. All rights rederved.