GLUT のイベント


入力イベントの処理

実用的な3次元グラフィックソフトウェアは、必然的に動的にならざるを得ません
また、ユーザーの要求を受け、適切に処理できなければならないでしょう
これを実現するためには、イベント処理を行う必要があります

OpenGL はプラットフォーム固有の処理には関与しません
そのため、イベントを処理するためにはシステム固有の API を呼び出す必要があります
しかし、この場合は何らかのシステムに依存したコードになってしまうため
この場では GLUT を駆使して、GLUT に準拠したコードを記述することにしましょう

まず、基本的なポインティング デバイスの入力を処理するプログラムを実現します
イベント処理は、ディスプレイ コールバック同様に、専用の関数にコールバック関数を登録します
マウスイベントには glutMouseFunc() 関数が対応しています

void glutMouseFunc(void (*func)(int button, int state, int x, int y));

func には、ユーザー定義コールバック関数を指定します
マウスの入力があると、ここで登録したコールバック関数が呼び出されます

コールバック関数は 4 つの数値型パラメータを宣言しています
button には、左ボタンを表す GLUT_LEFT_BUTTON、中央ボタンを表す GLUT_MIDDLE_BUTTON
そして、右ボタンを表す GLUT_RIGHT_BUTTON のいずれかを指定します
state は、ボタンが押された場合は GLUT_DOWN、話された場合は GLUT_UP を指定します
x と y には、イベント発生時のマウスのウィンドウ座標が格納されています

これで、マウスの入力を処理することができます
引数を調べれば、マウスのどのボタンが押されたのか、または離されたのかを知ることができます
処理の結果、描画内容に変更があれば、ウィンドウを再描画する必要があります

レンダリング情報を変更しても、ディスプレイ コールバックが呼び出されなければ
ウィンドウに表示されている画像に変化はありません
ディスプレイ コールバックを呼び出すには glutPostRedisplay() 関数を使います

void glutPostRedisplay(void);

この関数を呼び出せば、OpenGL に再描画の必要を訴えることができます
その結果、glutMainLoop() を通してディスプレイ コールバックが呼び出されます
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = { -0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9 };
GLboolean isLine = GL_FALSE;

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);
	glDrawArrays((isLine == GL_TRUE ? GL_LINE_LOOP : GL_POLYGON) , 0 , 3);

	glFlush();
}

void mouse(int button , int state , int x , int y) {
	if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN) return;
	isLine = (isLine == GL_TRUE ? GL_FALSE : GL_TRUE);
	glutPostRedisplay();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMouseFunc(mouse);

	glutMainLoop();
	return 0;
}
このプログラムは、ウィンドウを左クリックするたびにプリミティブの種類を変更し
ポリゴンと繰り返し線を入れ替えて再描画します

マウスの移動やドラッグを検出する方法もあります
ドラッグは glutMotionFunc()関数で
ボタンが押されていない移動は glutPassiveMotionFunc() で行います
ボタンが押されている状態の移動を GLUT ではモーションと呼び
ボタンが押されていない状態の移動をパッシブ・モーションと呼んでいます

void glutMotionFunc(void (*func)(int x, int y));
void glutPassiveMotionFunc(void (*func)(int x, int y));

func には、それぞれコールバック関数を指定します
コールバック関数の x と y にはウィンドウ座標を指定します
ボタンがどれか一つでも押されていればモーション コールバックが呼び出されます
そうでなければパッシブ・モーション コールバックが呼び出されるでしょう
これらのイベント処理は、視点変更などの3次元の醍醐味に使うことができるでしょう
#include <stdio.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);
}

void motion(int x , int y) {
	printf("X = %d : Y = %d\n" , x , y);
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMotionFunc(motion);

	glutMainLoop();
	return 0;
}
このプログラムは、ウィンドウ上でマウスのボタンを押してドラッグすると
標準出力に、現在のカーソルの座標を表示するというものです

キーボードのコールバックは glutKeyboardFunc() 関数で登録します
キーボードのイベントは、このキーボード コールバック関数で処理します

void glutKeyboardFunc(void (*func)(unsigned char key , int x , int y));

key には生成された ASCII コード、x と y にはキーが押された時のマウス座標を指定します
ただしこれでは、文字キー以外の特殊キーを処理することができません
特殊キーの処理には glutSpecialFunc() にコールバックを登録してます

void glutSpecialFunc(void (*func)(int key, int x, int y));

x と y の意味は同じで、マウスの座標が指定されます
kei には特殊キーを表す定数を指定します

定数解説
GLUT_KEY_F1F1 function key
GLUT_KEY_F2F2 function key
GLUT_KEY_F3F3 function key
GLUT_KEY_F4F4 function key
GLUT_KEY_F5F5 function key
GLUT_KEY_F6F6 function key
GLUT_KEY_F7F7 function key
GLUT_KEY_F8F8 function key
GLUT_KEY_F9F9 function key
GLUT_KEY_F10F10 function key
GLUT_KEY_F11F11 function key
GLUT_KEY_F12F12 function key
GLUT_KEY_LEFT左矢印キー
GLUT_KEY_UP上矢印キー
GLUT_KEY_RIGHT右矢印キー
GLUT_KEY_DOWN下矢印キー
GLUT_KEY_PAGE_UPPage up キー
GLUT_KEY_PAGE_DOWNPage down キー
GLUT_KEY_HOMEHome キー
GLUT_KEY_ENDEnd キー
GLUT_KEY_INSERTInset キー

これで、特殊キーの入力を処理することもできるようになりました
#include <stdio.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);
}

void key(unsigned char key , int x , int y) {
	printf("Key = %c\n" , key);
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutKeyboardFunc(key);

	glutMainLoop();
	return 0;
}
このプログラムは、ウィンドウがフォーカスを取得している状態で
何らかの文字キーを押すと、その文字が標準出力に出力されます

ウィンドウのサイズが変更された場合は glutReshapeFunc() を使います
例えば、2次元座標系を使う場合は、ウィンドウのサイズをチェックする必要があります
GLUT において、ウィンドウの変形をリシェイプと呼んでいます

void glutReshapeFunc(void (*func)(int width, int height));

func にはリシェイプ コールバック関数を指定します
width には新しい幅、height には高さを指定します


アニメーション

アニメーションのような処理を実現するためには
入力を待つのではなく、一定間隔で特定のメソッドを呼び出す必要があります
これに該当する手段は、待ち時間を利用するか、タイマーを使う方法です

待ち時間を有効利用して、何らかの計算処理等を行う場合は
glutIdleFunc() 関数にアイドル コールバックを登録します

void glutIdleFunc(void (*func)(void));

func には、アイドル コールバックを指定します
このコールバック関数は、処理するべきイベントが何もない状態の時に発行され続けます
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	static GLboolean isUp = GL_TRUE;
	static GLfloat top = -0.9;

	glClear(GL_COLOR_BUFFER_BIT);

	glBegin(GL_POLYGON);
		glVertex2f(-0.9 , -0.9);
		glVertex2f(0 , top);
		glVertex2f(0.9 , -0.9);
	glEnd();

	if (top > 0.9F) isUp = GL_FALSE;
	else if (top <= -0.9F) isUp = GL_TRUE;
	top += (isUp == GL_TRUE ? 0.01 : -0.01);

	glFlush();
}

void Idle() {
	glutPostRedisplay();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutIdleFunc(Idle);

	glutMainLoop();
	return 0;
}
このプログラムは、待ち時間を利用したアニメーションを実現しています
描画を行うたびに三角形の座標を変更するように仕組み
アイドル コールバック関数で、常に再描画するように処理しています

しかし、これにはいくつもの問題点が存在します
ひとつは、待ち時間は保障されるものではなく、しかも、その速度はコンピュータに依存します
超並列処理のスーパーコンピュータで動かせば、速すぎて見えなくなるかもしれません
音声など、他の要素と同期するのも不可能です

また、ディスプレイ コールバックはこれまでの映像を一度クリアするため
その過程が描画されてしまうと、画面がちらつくという問題もあります

前者の問題はタイマーを使うことで解決することができます
後者のちらつきの問題は、後ほど紹介するダブルバッファを使うことで解決できるでしょう

タイマーは glutTimerFunc() 関数を使います
この関数は、一定時間が経過するとコールバック関数を呼び出すというものです

void glutTimerFunc(
	unsigned int msecs , void (*func)(int value), value
);
msecs には、関数を呼び出すまでの時間をミリ秒単位で指定します
func に呼び出すべきコールバック関数へのポインタを指定します
value は単純にコールバック関数の value に渡される値です

一度発行したタイマーは破棄できません
また、複数のタイマー処理を同時進行させることが可能です
引数 value はコールバック関数が、タイマーの区別をするための情報です
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

GLfloat top = -0.9;
void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glBegin(GL_POLYGON);
		glVertex2f(-0.9 , -0.9);
		glVertex2f(0 , top);
		glVertex2f(0.9 , -0.9);
	glEnd();

	glFlush();
}

void timer(int value) {
	static GLboolean isUp = GL_TRUE;

	if (top > 0.9F) isUp = GL_FALSE;
	else if (top <= -0.9F) isUp = GL_TRUE;
	top += (isUp == GL_TRUE ? 0.01 : -0.01);

	glutPostRedisplay();
	glutTimerFunc(50 , timer , 0);
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutTimerFunc(100 , timer , 0);

	glutMainLoop();
	return 0;
}
このプログラムは、50 ミリ秒ごとにタイマー コールバックを呼び出しています
やはり、コールバック関数は適切な処理を実行し、再描画しています
その結果、定期的に図が変化し、アニメーションするという仕組みです



前のページへ戻る次のページへ