2006年 03月 17日

CG研究のためのフレームワーク。(3)
CG研究のためのフレームワーク。(2)の続きです。

■その3「システム・ビュー・ドキュメント」

いきなり3つを同時消化です。
この3つは、相互に連携しないと役に立たないので一気に行きます。

ここでは、GLUTを利用したシステムを想定して、
(GLUTについては手抜き入門などを。)

●システム:ウインドウの作成及び管理
●ビュー:イベントの処理
●ドキュメント:データの管理

という3つの概念を実装上で説明します。
ソースを読むほうが気楽。という人用。)




さて。
GLUTを使うメリットは、

・ウインドウが簡単に作れる。
・マウスやタイマー、ディスプレイなんかのイベントが判りやすい。

だと思います。
GLUT自体は非常にシンプルで、使いやすいのですが
ここでは、
「複数ウインドウを開いた場合の処理」
を想定して、GLUTに機能拡張をしていきます。

■3.1 システム
システムはGLUTのコールバックをビュークラスに転送する機能を持ちます。
ついでに、ウインドウとタイマーの作成を仲介します。(こんな感じ)
a0000144_13262266.jpg


クラスはこんな感じです。(無駄に長いので読み飛ばしても。)

class CGLUTMaster
{
public:
CGLUTMaster()//初期化
virtual ~CGLUTMaster(){}

//窓作成関数(ViewClass,窓タイトル名,幅,高さ)
int CreateGLUTWindow(CGLUTView* pViewClass,char *WindowTitle=NULL,int WindowWidth=200,int WindowHeight=200)
{
int WindowID;
//窓サイズ
glutInitWindowSize(WindowWidth, WindowHeight);
//glutで窓作成
if(WindowTitle==NULL)WindowID = glutCreateWindow("GLUT");
else WindowID = glutCreateWindow(WindowTitle);

//ViewClassをリストに登録
m_pViewClassList.resize(WindowID+1,NULL);
m_pViewClassList[WindowID] = pViewClass;

//各種コールバック関数をGLUTに教える。
//全てこのクラスが呼ばれる
glutDisplayFunc(Display);
glutMouseFunc(Mouse);
glutMotionFunc(Motion);
glutReshapeFunc(Reshape);
glutKeyboardFunc(Keyboard);
glutSpecialFunc(SpecialKey);

return WindowID;
}

void MainLoop(){glutMainLoop();}

//ディスプレイ(表示)コールバック
static void Display()
{
//呼び出された窓IDからViewClassを検索
int Call_ID=glutGetWindow();
//Viewのメソッドを呼び出し
m_pViewClassList[Call_ID]->OnDraw();
}
//以下、同じような感じなので略(詳しくはソースを参照)

static void Reshape(int width, int height)//サイズ変更
static void Keyboard(unsigned char key, int x, int y)//キーボード
static void SpecialKey(int key, int x, int y)//特殊キー(F1~12,↑↓等)
static void Mouse(int button, int state, int x, int y)//マウスのボタン
static void Motion(int x, int y)//マウス移動
static void Idle()//アイドル中
static void Timer(int ID)//タイマー

//自動的に繰り返すタイマー
static void AutoRepeatTimer(int ID)
{
glutSetWindow(m_TimerList[ID].WindowID);
m_pViewClassList[m_TimerList[ID].WindowID]->OnTimer(ID);
glutTimerFunc(m_TimerList[ID].TimerInt,AutoRepeatTimer,ID);
}

//タイマー作成(呼び出し時間(ms),窓ID,繰り返しフラグ(true=自動繰り返し))
void SetTimer(int TimerInt,int WindowID,bool IsRepeat=false)

private:
static std::vector m_pViewClassList;//ViewClassリスト
static MouseState m_Mouse;//マウスの状態
static KeyState m_KeyState;//キーの状態
static std::vector m_TimerList;//動作中タイマーリスト
};


ウインドウを作成した時に、ウインドウIDとビュークラスを対応付けておきます。
GLUTには、作成したウインドウに関する全てのイベントをシステムに
コールバックするように指定しておきます。

システムはコールバックを受けた場合、どのウインドウからのイベントなのかをチェックし
対応するビューに丸投げします。

システムの役割は、適切なビュークラスにイベントを転送することです。


■3.2 ビュークラス

ここで、ビュークラスという概念を出します。
ビュークラスは、ある1つのウインドウの動作を管理するクラスです。
窓が3つあれば(メインの表示、ボタン類、補助(パラメータ等の)表示等)
3つのビュークラスを作ります。

では、GLUTのビュークラスはどんな感じになるかといえば、こうなります

class CGLUTView
{
public:
CGLUTView(){}
virtual ~CGLUTView(){}

//仮想関数・各種コールバックを受け取る関数を空宣言
virtual void OnDraw(){}
virtual void OnMouseMove(MouseState mouse){}
virtual void OnMouseEvent(MouseState mouse){}
virtual void OnKeyEvent(KeyState State){}
virtual void OnTimer(int ID){}
virtual void OnMenu (unsigned int ID){}
virtual void OnReshape(int width, int height){}
};


クラスの中に、GLUTで使えるコールバック関数と同じようなメソッドが
何の機能も無しに並べて書いてあります。
システムはGLUTの呼ぶ全てのコールバックを受け取りますが、
各ビューがどの機能を必要としているのかを、いちいち管理するのは
面倒なので、何もしない基本クラスを用意します。

そして、各ウインドウ専用のクラスで継承してあげれば、
自分が機能を作ったメソッドだけが呼ばれているように”見えます”。

まぁ、実際どう使うかを見たほうが早いですね。

//ビュークラス・コールバックして欲しい物を書く。
class MyView :public CGLUTView
{
public:
MyView(){}
virtual ~MyView(){}

//とりあえず、簡単に3角形を書く。
void OnDraw()
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);//三角形
glVertex3d(1.0,0.0,0.0);
glVertex3d(0.0,1.0,0.0);
glVertex3d(0.0,0.0,0.0);
glEnd();
glutSwapBuffers();
}
};


MyViewには描画機能だけを実際に書いてあります。
それ以外の部分は、継承もとの「何もしないメソッド」が呼び出されます。

この、システムとビューを組み合わせることで、
ある窓で起こったイベントを特定のクラスの中に転送することが可能になります。

■3.3 ドキュメントクラス

ドキュメントクラスはアプリケーションで操作する情報を管理するためのクラスです。
例えば、物体の頂点情報とか、動きの情報です。
この手の情報は、ウインドウに出したり、マウスやボタンに連動して変化してこそ
意味があるので、ビュークラスからドキュメントを参照させてあげる必要があります。

これも実装例を書いたほうが早そうです。
(ソースそのままです。)

//ドキュメントクラス・自由に定義
class MyDocument{
public:
MyDocument(){m_Size = 1.0;}
virtual ~MyDocument(){}
//サイズなる怪しいメンバを持っている。
double m_Size;
};

//ビュークラス・コールバックして欲しい物を書く。
class MyView :public CGLUTView<MyDocument>{
public:
MyView(){}
virtual ~MyView(){}
//とりあえず、簡単に3角形を書く。
void OnDraw()
{//ドキュメントからサイズを取ってくる
double size = m_pDoc->m_Size;

glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);//三角形
glVertex3d(size,0.0,0.0);
glVertex3d(0.0,size,0.0);
glVertex3d(0.0,0.0,0.0);
glEnd();
glutSwapBuffers();
}
};

int main(void){
MyView view;//ビューを作成

//GLUTの管理クラス
CGLUTMaster<MyDocument> MyMaster;
//中でドキュメントを作っているので<ドキュメント名>をくっつける
//詳しいことは「C++ template」とかで検索すると大人の事情が見え隠れ。

//サイズを設定
MyMaster.GetDoc()->m_Size = 0.5;

//窓作成
MyMaster.CreateGLUTWindow(&view);
//メインループ(各種コールバックを実行)
MyMaster.MainLoop();

//この辺は実行されません。(GLUTの仕様です。Thx。)
return 0;
}


システムとビューの後ろに<MyDocument>とありますが、
これは、「このシステムとビューが、どのドキュメントクラスを使うか」というのを指定しています。
ドキュメントクラス本体はシステムが持ち、各ビュークラスはそれへのポインタ(m_pDoc)を
持っています。

と。
こんな感じで、システム・ビュー・ドキュメントを連動させると
「プログラムがすっきりするんじゃないのかな?」
っていう、提案でしかないのですが・・・

ドキュメントを誰に持たせるのか、templateなんか使うんじゃないっ!とか
ていうか、GLUTを重くしただけじゃん。とか
そもそもGLUTだけでもできるし。とか
大体全体的に意味不明。とか
色々な感想があるでしょうが、私は大好きです。
[PR]
by V-Scout | 2006-03-17 01:38 | プログラム
<< VertexBufferObject 空の軌跡SC >>