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_F1 | F1 function key |
GLUT_KEY_F2 | F2 function key |
GLUT_KEY_F3 | F3 function key |
GLUT_KEY_F4 | F4 function key |
GLUT_KEY_F5 | F5 function key |
GLUT_KEY_F6 | F6 function key |
GLUT_KEY_F7 | F7 function key |
GLUT_KEY_F8 | F8 function key |
GLUT_KEY_F9 | F9 function key |
GLUT_KEY_F10 | F10 function key |
GLUT_KEY_F11 | F11 function key |
GLUT_KEY_F12 | F12 function key |
GLUT_KEY_LEFT | 左矢印キー |
GLUT_KEY_UP | 上矢印キー |
GLUT_KEY_RIGHT | 右矢印キー |
GLUT_KEY_DOWN | 下矢印キー |
GLUT_KEY_PAGE_UP | Page up キー |
GLUT_KEY_PAGE_DOWN | Page down キー |
GLUT_KEY_HOME | Home キー |
GLUT_KEY_END | End キー |
GLUT_KEY_INSERT | Inset キー |
これで、特殊キーの入力を処理することもできるようになりました
#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 ミリ秒ごとにタイマー コールバックを呼び出しています
やはり、コールバック関数は適切な処理を実行し、再描画しています
その結果、定期的に図が変化し、アニメーションするという仕組みです