---- #contents ---- *Fl_Gl_Windowでのアニメーション [#t692a1c6] [[Fl_Gl_Windowでのキーボード・マウス入力]]にアニメーション処理関数を追加する. ***アニメーションのやり方 [#cba48a63] 連続した動きを描画した画像があり,それを高速に切り替えて表示すると人間は動画としてそれを認識する. これは仮現運動と呼ばれる現象であり,人間の場合,22〜23fps (fps : frames per second, 一秒間に表示する画像数) 以上でこの現象が発生する.そのため,一般的な動画は24fps以上が用いられる. よく使われるのは,24fps, 30fps, 60fpsなどである.動きが激しいアニメーションの場合,60fpsが必要とされているが, 通常は30fpsあれば問題ない. OpenGLプログラムでアニメーションを作る際は,呼び出されるたびに動きを更新する関数を作り,その関数を30fps以上で呼び出し実行→再描画を繰り返す. 関数の呼び出し方には一般的に以下の二種類の方法が用意されている. -アイドル関数 : PCが暇なときに呼ばれる.処理内容やそのときのCPU,GPUの状態によってタイミングが変化する(GLUTの場合,glutIdleFunc()で指定). -タイマー関数 : ある一定の時間幅ごとに呼ばれる.処理内容にかかわらずタイミングは固定だが処理が間に合わなければフレームドロップが起こる(GLUTの場合,glutTimerFunc()で指定). アプリケーションによってこれら2種類を使い分けてアニメーションを行う. 以下ではFLTKでのアイドル関数,タイマー関数の作り方を説明する. ***クラスの宣言 [#y438054c] [[Fl_Gl_Windowでのキーボード・マウス入力]]で作成したrxFlGLWindowクラスを以下のように変更する. #code(C){{ class rxFlGLWindow : public Fl_Gl_Window { // 描画フラグ enum DrawObject { RX_IDD_VERTEX = 0, //!< 頂点描画 RX_IDD_EDGE, //!< エッジ描画 RX_IDD_FACE, //!< 面描画 }; int m_iDraw; //!< 描画フラグ rxTrackball m_tbView; //!< トラックボール bool m_bAnimation; //!< アニメーションの状態 public: //! コンストラクタ rxFlGLWindow(int x_, int y_, int w_, int h_, const char* l) : Fl_Gl_Window(x_, y_, w_, h_, l) { m_bAnimation = false; m_iDraw = RX_IDD_FACE; resizable(this); // ウィンドウリサイズを可能に end(); } //! デストラクタ virtual ~rxFlGLWindow() { } public: void InitGL(void); void Resize(int w, int h); void Display(void); void Mouse(int button, int state, int x, int y); void Motion(int x, int y); void PassiveMotion(int x, int y); void Keyboard(int key, int x, int y); static void OnIdle_s(void* x); static void OnTimer_s(void* x); inline void Idle(void) inline void Timer(void) private: void draw(void) void resize(int x_, int y_, int w_, int h_); int handle(int ev); }; }} Idle,Timer関数がそれぞれアイドル関数,タイマー関数である. OnIdle_s,OnTimer_s関数はFLTKに関数を登録するための引数を1つだけ持つstatic関数である. 何故,staticな関数と通常のメンバ関数の両方を定義しているかは次節で説明する. アニメーションの状態は変数m_bAnimationに登録する. そして,キーボードでアニメーションのON/OFFを行う. ***キーボードによる切り替え [#g9eecbe7] アニメーションのON/OFFはキーボードの"s","t"で行うこととする. "s"キーでアイドル関数,"t"キーでタイマー関数によるアニメーションをON/OFFする. #code(C){{ void rxFlGLWindow::Keyboard(int key, int x, int y) { make_current(); switch(key){ case 's': m_bAnimation ^= 1; if(m_bAnimation){ Fl::add_idle(OnIdle_s, this); } else{ Fl::remove_idle(OnIdle_s, this); } break; case 't': m_bAnimation ^= 1; if(m_bAnimation){ Fl::add_timeout(0.033, OnTimer_s, this); } break; } redraw(); } }} FLTKのadd_idle関数に関数ポインタ(OnIdle_s)とその関数に渡される引数の値(this)を指定して実行することで, OnIdle_s関数が指定される. 登録を解除してアニメーションを止める場合は,remove_idle関数を用いる. ここで,Idle()でなく,staticなメンバ関数であるOnIdle_s()を指定しているのには理由がある. 通常,クラスのメンバ関数はクラスのオブジェクトを通して呼び出す(たとえば,obj.func()やpObj->func()のように). そのため,メンバ関数はアイドル/タイマー関数としてFLTKに登録することはできない (通常,グローバル関数が指定される). なぜならば,FLTK側はその関数がグローバル関数なのかメンバ関数なのか判断できず,さらにクラスのオブジェクトも持っていないためである. そこで,staticなメンバ関数を利用する. static関数はクラスのオブジェクトなしでもアクセス可能な関数である. クラスのオブジェクトを通さなくてもアクセスできるため,アイドル/タイマー関数の登録に使える. ただし,staticなメンバ関数はクラスの中身にはアクセスできない. つまり,オブジェクトがなければメンバ変数や他の(staticでない)メンバ関数を呼び出すことができない. つまり,オブジェクトがなければメンバ変数や他の(staticでない)メンバ関数を呼び出すことができないということである. そのため,関数登録時に第2引数にクラスオブジェクト(this)を渡し,それを引数を通じてstaticな関数で受け取ることでメンバ関数であるIdle,Timer関数を呼び出す.ちなみにこのstatic関数を使った方法はその他FLTKウィジットにコールバック関数を設定したい際にも使える. 上記の例では同じクラス内の関数なので関数名だけ記述しているが,他クラスのstatic関数を指定する場合は, Fl::add_idle(rxTest::OnIdle_s, pTest); のように記述すればよい(pTestはrxTestクラスのオブジェクト). OnIdle_s,Idle関数の実装は以下. #code(C){{ static void rxFlGLWindow::OnIdle_s(void* x) { ((rxFlGLWindow*)x)->Idle(); } inline void rxFlGLWindow::Idle(void) { make_current(); m_tbView.AddRotation(1.0, 0, 1, 0); redraw(); } }} OnIdle_s関数では引数で受け取ったrxFlGLWindowクラスのインスタンスを利用して,Idleメンバ関数を呼び出している. Idle関数内では視点変更用のトラックボールに回転を加え,再描画命令を発効することで動きを変更するごとに描画を繰り返し, アニメーションを実行している. 一方,add_timeout関数は単純に第一引数の秒数(ここでは0.033秒)後に, 第二引数で指定した関数(OnTimer_s)を第三引数で指定した値(this)を引数として渡して実行する. add_timeout関数は指定した秒数後に関数を一回実行するだけなので, 一方,add_timeout関数は単純に指定した秒数後に関数を一回実行する. 第一引数の秒数(ここでは0.033秒),第二引数で呼び出す関数(OnTimer_s),第三引数でその関数に渡す引数値(this)を指定する. add_timeout関数は関数を一回実行するだけなので, 呼び出された関数内でさらに,repeat_timeout関数で同様に登録し続けることで, タイマーによるアニメーションが可能となる. OnTimer_s,Timer関数の実装は以下. #code(C){{ static void rxFlGLWindow::OnTimer_s(void* x) { ((rxFlGLWindow*)x)->Timer(); } inline void rxFlGLWindow::Timer(void) { make_current(); m_tbView.AddRotation(1.0, 0, 1, 0); redraw(); if(m_bAnimation){ Fl::repeat_timeout(0.033, OnTimer_s, this); } } }} アイドル関数の時と同様にして,OnTimer_sからTimer関数を呼び出す形である. アニメーションを止めるときは単純にrepeat_timeout関数を実行するのをやめればよい. この判断をするために,m_bAnimation変数の値を参照している. ***ソースコード [#n1341dce] 上記例のソースコードは以下(Viual Studio 2010用プロジェクトファイル含む). #ref(fltk_simple_opengl3.zip) 実行結果は静止画では [[Fl_Gl_Windowでのキーボード・マウス入力]] と同じなので省略.