トラックボールカメラの実装

やあ子どもたち。ビューマトリックスで遊んでいるか?今日もビューマトリックスの話題だよ。ビューマトリックスの考え方や実装方法については過去日記その1過去日記その2で考えてきたね。今日はユーザーの入力に対して実際にビューマトリックスをどう決めどう計算するのかという一例の紹介だ。
ガラス球の中にモデルが埋め込まれていて、そのガラス球の表面を手で触れ回転させることで、中のモデルも一緒に回転する状況を思い浮かべよう。地球儀のようなものを思い浮かべるかも知れないが、地球儀は軸が固定されていて決まった方向にしか回転しないのでちょっと違うぞ。ここで考えたいのはトラックボールマウスのようにあらゆる方向に回転するようになっている球だ。あるいはもっとシンプルに自分の手の中にガラス球を持っていてそれを自在に回転させるイメージかも知れない。
以上の話だけだと、手の中の、あるいは目の前のガラス球を回転させる話なので、モデルマトリックスの問題であるとも言えるのだけど、1つのモデルだけではなくて、世界全体を上述のガラス球の中に入れてしまったと考えて、あらゆるものを、上述のガラス球を回すような操作で見れるようなカメラを実装することを考えるとどうだろうか。
操作上はあたかもガラス球を回しているかのごとき感覚だが、実際に動いているのはモデルではなく、それを見ている自分自身。。つまりこれはそのような動作をビューマトリックスとして実装する問題に帰着されることに気付く。
さらに回転だけではなく、注視点でもある回転の中心となる場所を簡単に移動できるようになっているといろんな場所を見ることができて便利そうだ。
というわけで、マウス操作情報を受け取ってそのようなビューマトリックスを一気に返してくれるクラスを実装したので見てくれ。

    class TrackBallCamera
    {
    public:
        enum EnumPanOrDollyOrTruck
        {
            TR_PAN=0, TR_TRUCK, TR_DOLLY, TR_NONE,
        };
    public:
        TrackBallCamera( void );
        void ForMouseMove( EnumPanOrDollyOrTruck mode,
                          const glm::vec3& prev, const glm::vec3& now,
                          const int view_width, const int view_height );
    public:
        // 以下がトラックボールカメラとして持つ必要がある情報だ。。
        glm::vec3 _interest;
        glm::vec3 _position;
        glm::vec3 _updir;
        // 以下が実質上の出力となるビューマトリックスだ。
        glm::mat4 _view_matrix;
    };

そして、気になるForMouseMoveの中は以下だよ。

TrackBallCamera::TrackBallCamera( void )
{
    _interest = glm::vec3( 0, 0, 0 );
    _position = glm::vec3( 0, 0, 5 );
    _updir = glm::vec3( 0, 1, 0 );
    _view_matrix = glm::lookAt( _position, _interest, _updir );
}
void TrackBallCamera::ForMouseMove(
                      TrackBallCamera::EnumPanOrDollyOrTruck mode,
                           const glm::vec3 &prev, const glm::vec3 &now,
                           const int view_width, const int view_height)
{
    switch( mode )
    {
        case TR_PAN:
        {
            glm::vec3 p( 
                2*prev.x/view_width - 1.0,
                2*prev.y/view_height - 1.0, .0 );
            glm::vec3 c( 
                2*now.x/view_width - 1.0, 
                2*now.y/view_height - 1.0, .0 );
            glm::mat4 rot;
            {
                float pxy2 = p.x*p.x + p.y*p.y;
                float cxy2 = c.x*c.x + c.y*c.y;
                if( pxy2>=1.0 || cxy2>=1.0 )
                    return;
                p[2] = sqrt( 1-( pxy2 ) );
                c[2] = sqrt( 1-( cxy2 ) );
                glm::vec3 axis =
                (glm::inverse( _view_matrix )*
                       glm::vec4( glm::cross( c, p ), .0 ) ).xyz();
                const float speed = 50.0;
                rot = glm::rotate( glm::length( axis )*speed, axis );
            }
            glm::vec3 pos_rel = 
               ( rot * glm::vec4( _position - _interest, 0.0f ) ).xyz();
            _position = _interest + pos_rel;
            _updir = glm::vec3( rot * glm::vec4( _updir, 0.0f ) );
            _view_matrix = glm::lookAt( _position, _interest, _updir );
        }
            break;
        case TR_DOLLY:
        {
            glm::vec3 dvec =  - ( now - prev ) * .01f ;
            dvec = 
              ( glm::inverse( _view_matrix ) * 
                    glm::vec4( .0,.0,dvec.y,.0 ) ).xyz();
            _position += dvec;
            _view_matrix = glm::lookAt( _position, _interest, _updir );
        }
            break;
        case TR_TRUCK:
        {
            glm::vec3 dvec = - ( now - prev ) * .01f ;
            dvec = 
               ( glm::inverse( _view_matrix ) * glm::vec4( dvec, .0 ) ).xyz();
            _position += dvec;
            _interest += dvec;
            _view_matrix = glm::lookAt( _position, _interest, _updir );
        }
            break;
        default:
            break;
    }// switch
    return;
}

いかがだろうか。何をやってるかはさておき、使用例をみて行こう。まずは本クラスのインスタンスを、ビューのメンバとして作成しよう。そうして作った本クラスのインスタンスにマウスの動き情報をどう入力すべきかのサンプルはこう。

// ...
// マウス移動のコールバックの中にて。。
// Pan or Dolly or Truck
_trackball.ForMouseMove( 
  mode, // TR_DOLLY, TR_PAN, TR_TRUCK のいずれかを指定
  glm::vec3(p.x, p.y, .0), glm::vec3(c.x, c.y, .0), // 前回と今回のマウス位置
  width, height );// ビューウィンドウのサイズ
// 画面描画を更新。。
//

ここで、Panはカメラがその向きだけを変えようとする様を、Dollyは、カメラ自体が正面に進んだり後ずさったりすること、Truckは、カメラとその注視点とが、ともに横方向(画面内方向)に移動する様を指すぞ。
ForMouseMove()を呼べばその時点でTrackBallCamera::_view_matrix の内容が更新されるので、以下のように描画コードの中のビューマトリックスをこれで更新しよう。

// 描画コードの中にて。。
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
//
// <描画コード>
const auto& tr = _universe->_trackball;
glUniformMatrix4fv( uniform_view_matrix, 1, 0,
                glm::value_ptr( _trackball._view_matrix ) );   
// --<ここで一連のモデルを描画>--
glFlush();   // 描画終了