クォータニオンカメラ

入門書にクォータニオンのことは書いてあるけど、実際にカメラに使うときにどうしたらいいのという声を聞くので、とりあえずのっけます。改造してください。もちろん、修正BSDライセンスなんで、お気軽にご利用ください。
まずは、DirectInput等、Direct3D以外のものを使わない設定にしてから吐き出し、ティーポットが表示されることとを確認しておいてください。次に、Direct3D9 の AppWizard から吐き出される CMyD3DApplication::FrameMove()を、以下のように書き換えてください。事前に置換前と置換後で動作はあまり変わらないと思いますが、置換前はワールド行列を書き換えていたのに対し、置換後はビュー行列を書き換えています。これがでかい!
ちなみに、関数の中にドデンとたたずむ QuatCam がクォータニオンカメラクラスです。

4/22

あ、boost/assert.hpp のインクルードが必要です。BOOST_ASSERT を使わないのであれば、必要ありません。

HRESULT CMyD3DApplication::FrameMove()
{
    // TODO: update world

    // Update user input state
    UpdateInput( &m_UserInput );

    // Update the world state according to user input
 /*   D3DXMATRIX matWorld;
    D3DXMATRIX matRotY;
    D3DXMATRIX matRotX;

/*    if( m_UserInput.bRotateLeft && !m_UserInput.bRotateRight )
        m_fWorldRotY += m_fElapsedTime;
    else if( m_UserInput.bRotateRight && !m_UserInput.bRotateLeft )
        m_fWorldRotY -= m_fElapsedTime;

    if( m_UserInput.bRotateUp && !m_UserInput.bRotateDown )
        m_fWorldRotX += m_fElapsedTime;
    else if( m_UserInput.bRotateDown && !m_UserInput.bRotateUp )
        m_fWorldRotX -= m_fElapsedTime;

    D3DXMatrixRotationX( &matRotX, m_fWorldRotX );
    D3DXMatrixRotationY( &matRotY, m_fWorldRotY );

    D3DXMatrixMultiply( &matWorld, &matRotX, &matRotY );*/
    D3DXMATRIX matWorld;
    D3DXMatrixIdentity( &matWorld );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
    
    //  クォータニオンカメラクラス
    class QuatCam {
        typedef D3DXQUATERNION  Quat;
        typedef D3DXMATRIX      Mtx;
        float   dist_;
        Quat    quat_;
        
        Quat    rot( const Quat& q ) {
            Quat conj;
            D3DXQuaternionConjugate( &conj, &quat_ );
            return conj * q * quat_;
        }
        
        void    rotate( float x, float y, float theta ) {
            Quat axis = rot( Quat( x, y, 0, 0 ) );
            Quat q = sinf( theta ) * axis;
            q.w = cosf( theta );        
            quat_ *= q; 
            D3DXQuaternionNormalize( &quat_, &quat_ ); // 保険
        }
        
    public:
        QuatCam( float dist ) : dist_( dist ), quat_( 0, 0, 0, 1 ) {
            BOOST_ASSERT( dist_ > 0.0f );
        }
        float getDist() const {
            return dist_;
        }
        void setDist( float dist ) {
            BOOST_ASSERT( dist > 0.0f );
            dist_ = dist;
        }
        /// 行列を取得
        Mtx calc() const {
            
            Quat    right( 1, 0, 0, 0 );
            Quat    up( 0, 1, 0, 0 );
            Quat    pos( 0, 0, -dist_, 0 );
            Quat    conj;
            D3DXQuaternionConjugate( &conj, &quat_ );
            
            #define SANDWICH( q )   q = conj * q * quat_
            SANDWICH( right );
            SANDWICH( up );
            SANDWICH( pos );
            #undef SANDWICH
            
            Quat    dir = -pos; // 右手系のときは pos にする
            D3DXQuaternionNormalize( &dir, &dir );
            
            Mtx result(
                right.x, right.y, right.z, 0.0f,
                up.x, up.y, up.z, 0.0f,
                dir.x, dir.y, dir.z, 0.0f,
                pos.x, pos.y, pos.z, 1.0f ); // 右手系のときは...よきにはからって
            D3DXMatrixInverse( &result, 0, &result );
            return result;
        }
        
        /// 回転を加える
        /**
         *  @param  rotX    X 軸 ( 右 ) を中心に回る回転角度 ( ラジアン )
         *  @param  rotY    Y 軸 ( 上 ) を中心に回る回転角度 ( ラジアン )
         */
        void rotate(
            float rotX, float rotY ) {
            rotate( 1, 0, rotX );
            rotate( 0, 1, rotY );
        }
    };
    
    static QuatCam cam( 5.0f ); // これは手抜きです。勝手に変えてください。
    float rotX = 0.0f, rotY = 0.0f;
    if( m_UserInput.bRotateLeft && !m_UserInput.bRotateRight )
        rotY += m_fElapsedTime;
    else if( m_UserInput.bRotateRight && !m_UserInput.bRotateLeft )
        rotY -= m_fElapsedTime;

    if( m_UserInput.bRotateUp && !m_UserInput.bRotateDown )
        rotX += m_fElapsedTime;
    else if( m_UserInput.bRotateDown && !m_UserInput.bRotateUp )
        rotX -= m_fElapsedTime;
       
    cam.rotate( rotX, rotY );
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &cam.calc() );
        
    return S_OK;
}