ホーム

はじめに

連載に連動したWEBページとして,プログラムのダウンロードや説明,補足情報などをまとめます. 本ページで紹介しているプログラムやダウンロードしたファイルは自由に使用して頂いて構いませんが, 本プログラムやその改良物の使用による損失、損害等については,いかなる場合においても一切の責任を負いかねます.


連載第1回

連載第1回では,GLUTを使ったプログラムの雛型として,sample1-1.cを例示して説明しました. ここでは,sample1-1.cの詳細を説明するとともに, 表示用のデータを生成するプログラムを紹介します.

sample1-1.c の説明

sample1-1.c はここからダウンロードできます.

以下,サンプルプログラムの各パートについて,説明します.

ヘッダ部分,グローバル変数宣言

プログラムの冒頭は,グローバル変数の宣言です. 関数のプロトタイプ宣言などもした方がよいですが, 今回は関数の記載順序を操作してプロトタイプ宣言無しで書いています.

//
// sample.c
//
#include 
#include 

GLboolean FLAG_DISP_AXES = GL_TRUE;      // 座標軸の表示フラグ  
GLboolean FLAG_DISP_OBJECT = GL_TRUE;    // オブジェクトの表示フラグ
GLboolean FLAG_DISP_SHADOW = GL_TRUE;    // 影の表示フラグ
GLboolean FLAG_MOVING = GL_FALSE;        // マウスドラッグ中フラグ
GLboolean FLAG_AUTO_ROTATE = GL_TRUE;    // 自動回転モードフラグ

int angx = 250, angy = -90; // 2軸の回転角
int bgnx, bgny;            // マウスドラッグ時の初期座標値
int maxval = 100;          // 表示データの最大値(初期値100)
int *pntnum;               // フレーム毎の点の数を格納する配列
int ***v;                  // フレーム毎の各点の XYZ 座標を格納する配列
int max_frames = 1;        // 最大フレーム数(初期値1)
int frame;                 // 現在の表示フレーム
int tcount = 0;            // フレーム更新のためのカウンタ
int acount = 0;            // 自動回転のためのカウンタ
int point_size = 3;        // 点の表示サイズ(初期値3)
int point_ratio = 3;       // 点の表示率(間引き率)(初期値3)

7〜10行目は,各種表示のON/OFF を司る,フラッグ変数の宣言と初期値の設定です. OpenGL で使われる,2値データ型の GLboolean 型とし,GL_TRUE, GL_FALSE で指定しています.

13〜23行目は,全体で用いるグローバル変数の宣言と初期値の設定です. それぞれの詳細は,利用される部分に譲ります.

ポイントデータの読み込み

点群のデータは,64x64x64のサイズのボクセル構造を想定しています. このため,三次元の整数座標値を読み込むスタイルですが, 実数座標値を用いるような点群への対応もそれほど難しくないと思われます. 提示の際には,入力した座標値を座標 maxval で割って正規 化するので,ボクセルサイズは適宜変更可能です.

// ポイントデータを読み込む(データを読みながらメモリを確保する)
void LoadPointData(char *fname)
{
  FILE *fp;
  int i, j;
  char dummy[100];
  
  fp = fopen(fname, "r");
  fgets(dummy, 100, fp);
  sscanf(dummy, "%d %d", &max_frames, &maxval);
  v = (int ***)malloc(sizeof(int **)*max_frames);
  pntnum = (int *)malloc(sizeof(int)*max_frames);
  for (i = 0; i < max_frames; i++) {
    fgets(dummy, 100, fp);
    fgets(dummy, 100, fp);
    sscanf(dummy, "%d", &(pntnum[i]));
    v[i] = (int **)malloc(sizeof(int *)*pntnum[i]);
    for (j = 0; j < pntnum[i]; j++) {
      v[i][j] = (int *)malloc(sizeof(int)*3);
      fgets(dummy, 100, fp);
      sscanf(dummy, "%d %d %d",
             &v[i][j][0], &v[i][j][1], &v[i][j][2]);
    }
  }
  fclose(fp);
}

35行目,41行目,43行目は,それぞれデータファイルにかかれた, フレームの総数,点の総数 pntnum[] や,点毎の座標(3次元)を用いて, メモリを動的に確保しています. v[i][j][0]は,「i 番目のフレームの j 番目の点の x 座標値(第0要素)」を表しています.

座標軸などの表示

OpenGL の線分描画を利用して,XYZの各軸と,床面(XY平面)に格子を描いています.

// 座標軸と XY 平面格子を描画する
void DispXYZAxes(void)
{
  int i;
  glBegin(GL_LINES);
  glColor3f(1,0,0); glVertex3i(0,0,0); glVertex3i(1,0,0); // X軸
  glColor3f(0,1,0); glVertex3i(0,0,0); glVertex3i(0,1,0); // Y軸
  glColor3f(0,0,1); glVertex3i(0,0,0); glVertex3i(0,0,1); // Z軸
  for (i = 0; i <= 10; i++) { // XY平面上の格子模様
    glColor3f(1,0,0);
    glVertex3f(0, i/10.0, 0); glVertex3f(1, i/10.0, 0);
    glColor3f(0,1,0);
    glVertex3f(i/10.0, 0, 0); glVertex3f(i/10.0, 1, 0);
  }
  glEnd();
}    

GL_LINES(線分群)を用い,glColor3f() で指定した色で,glVertex3i()で指定する2点間の線分群を表示しています. 57〜59行目は,原点から(1,0,0), (0,1,0), (0,0,1) への線分としてXYZの各軸を表示しています.また, 60〜65行目は,Z=0 の XY平面上に格子を描いています.

点群データの表示

v[i][j][] に格納された点群データを,GL_POINTS(点群)として表示します.

// 点群データを表示する.色は高さで決める.
void DispObject(void)
{
  int i;
  float vx, vy, vz;
  glPointSize(point_size);
  glBegin(GL_POINTS);
  for (i = 0; i < pntnum[frame]; i = i + point_ratio) {
    vx = v[frame][i][0]/(float)maxval;
    vy = v[frame][i][1]/(float)maxval;
    vz = v[frame][i][2]/(float)maxval;
    
    if (vz > 0.5) { // Height color (L)R->G->B(H)
      glColor3f(0, 2*(1-vz), 2*(vz-0.5));
    } else {
      glColor3f(1-2*vz, 2*vz, 0);
    }
    glVertex3f(vx, vy, vz);
    
    if (FLAG_DISP_SHADOW == GL_TRUE) { // 影を点群で表示
      glColor3f(0.5, 0.5, 0.5);
      glVertex3f(vx-vz*0.3, vy-vz*0.3, -0.01);
    }
  }
  glEnd();    
}    

77〜79行目で,格納データを正規化して,各軸 0-1 の範囲に収めたのち, glColor3f() と glVertex3f() を使って,色つきの点を表示します. 色情報は,0〜1の高さに応じて決まるようにしています. 88〜91行目は,影の表示で,表示フラグに応じて影(平行光線で照らした時の影)を同じく「点群」として表示しています.

回転操作によるモデリング再計算

再描画の際に呼び出される関数で,設定された angx, angy の値に応じた, モデルの回転行列を再計算します.

// 回転操作におけるモデリング変換行列の再計算
void RecalcModelView(void)
{
  glPopMatrix();
  glPushMatrix();    
  glTranslatef(0.5, 0.5, 0.5);
  glRotatef(angy, 1, 0, 0); // angy で決まる角度で回す
  glRotatef(angx, 0, 0, 1); // angx で決まる角度で回す
  glTranslatef(-0.5, -0.5, -0.5);
}

99〜100行目の glPopMatrix(), glPushMatrix() はこれまで設定された行列の影響を一旦取り除くために使われます. その上で,(0.5, 0.5, 0.5)を中心にした回転を行うため,101行目と104行目の glTransfaltef() が使われています. 102行目,103行目は,それぞれ angy, angx で決まる角度だけ,y軸,z軸まわりに回す回転を glRotatef() で行います. angx, angy は今回は固定値ですが,キーボードやマウスの操作で値を変更することを想定しています. このため,angx は 水平回転=Z軸まわりの回転角,angy は垂直回転=x軸まわりの回転角にそれぞれ対応します. OpenGLでは,後で設定される変換ほど先に実行されますので,104, 103, 102,101行目の設定順に変換が施されます.

再描画のためのコールバック関数

ウィンドウの再描画がリクエストされた時に呼び出される関数です.

// 再描画のためのコールバック関数
void MyRedraw(void)
{
    RecalcModelView();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor( 1.0, 1.0, 1.0, 1.0);    
    if ( FLAG_DISP_AXES ) { DispXYZAxes(); }  // 座標軸の表示 
    if ( FLAG_DISP_OBJECT ) { DispObject(); } // 点群データの表示
    glutSwapBuffers();
}

内部では,RecalcModelView()がまず呼ばれます.次に, glClear(), glClearColor() によって,画面や描画の状態が初期化されます. つぎに,フラグの状態に応じて,DispXYZAxes() や DispObject() が呼ばれます. 最後に glutSwapBuffers() が呼ばれて,描画バッファと表示バッファが交換され, 結果がディスプレイ上に反映されます.

メイン関数部分

メイン関数では,glut と OpenGL の初期化が行われ,その後,点群データを読み込み, さらにイベントループ(イベントの発生を待ち, 発生イベントに応じて設定されたコールバック関数が呼び出されるループ)に入る.

// メイン関数
int main(int argc, char **argv)
{
  glutInit(&argc, argv); // GLUT の初期化
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutCreateWindow("GLUT TEST"); // ウィンドウの生成

  glutDisplayFunc(MyRedraw); // 再描画コールバック関数の設定
  
  // OpenGL での描画の初期化,視点設定など 
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
  glEnable(GL_CULL_FACE); 
  glEnable(GL_DEPTH_TEST);
  glMatrixMode(GL_PROJECTION);
  gluPerspective(30.0, 1.0, 1.0, 45.0);
  glMatrixMode(GL_MODELVIEW);
  gluLookAt(0.0, 1.5, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);  
  glPushMatrix();
  glTranslatef(-0.5, -0.5, -0.5); // 立方体の中心を原点に重ねる
  glPushMatrix();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glClearColor( 1.0, 1.0, 1.0, 1.0);    
  
  LoadPointData(argv[1]);   // 点群データの読み込み
  
  glutMainLoop();           // GLUTのイベントループ処理へ
  return 0;                 // ここには戻ってこない
}

121〜123行目はglutの初期化にあたります. glutInit() には main() の引数が渡され, ウィンドウ設定に関するオプションが自動的に解釈されます. glutInitDisplayMode()では,RGBモード,ダブルバッファモード,および, 奥行き判定モードを用いることが設定されています. さらに,glutCreateWindow()でウィンドウを生成しています.

125行目の glutDisplayFunc() では, ウィンドウ再描画イベントの際に呼ばれるコールバック関数を MyRedraw() に設定しています.

モデル生成プログラムの紹介

本アプリケーション用のデータを生成するプログラムを用意しました. 以下からそれぞれダウンロードしてください.

例えば,sphere.c をコンパイルして利用する場合は,

% gcc sphere.c -o sphere -lm
% sphere 20 > sphere.dat
% glut-disp sphere.dat
のようにして下さい.引数(上の例では20)は,64^3 のボクセルサイズにおける,球や円筒の半径サイズです.

球球, 円筒円筒, 3円筒円筒×3

プログラムの中身の説明は省略しますが,ボクセル空間を3次元スキャンしながら, 幾何学的条件を満たすかどうかで点群を生成していきます. 一旦,全体の点の総数をカウントした上で,同じ処理を繰り返しながら出力していきます. このプログラムでは,例えば球の内部の``詰まった''データが出力されます. 表面のみのデータなどは,条件を増やすことで生成可能と思われますので,工夫してみて下さい.


連載第2回

連載第2回では,キーボードとマウスを使ったインタラクション, タイマ割り込みを利用したアニメーションについて説明しました. 連載2回目までの機能を付け加えたサンプルプログラムを sample2-1.c とし, ここではその詳細を説明します.基本的には, いくつかの関数の追加で対応していますが, sample1-1.c にあった関数を一部修正(追加)している部分もあります.

sample2-1.c の説明

sample2-1.c はここからダウンロードできます.

以下,追加した部分を中心に各パートを説明します.

ヘッダ部分,グローバル変数宣言

sample1-1.c とほぼ同じですが, グローバル変数 REDRAW_INTERVAL が追加されています. ここでは 50 [ms] に設定しています.

GLint REDRAW_INTERVAL = 50;  // 再描画のための割り込み時間設定 [msec] 

マウス操作のためのコールバック関数

GLUT のイベントとしては,マウスのボタンの状態によるイベントと, マウスの位置(動き)によるイベントの2種類があります. main() の中で指定された, それぞれのイベントのコールバック関数を示します. main() に追加したコールバック関数の設定部分も参照してください.

// マウス操作(クリック)のためのコールバック関数
void MyMouse(int button, int state, int x, int y)
{
    // ボタン操作でフラグを切り替える
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        FLAG_MOVING = GL_TRUE;
        bgnx = x; // ボタンを押した時の座標で初期化
        bgny = y; // ボタンを押した時の座標で初期化
    }
    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
        FLAG_MOVING = GL_FALSE;
    }
}

// マウス操作(ドラッグ)のためのコールバック関数
void MyMotion(int x, int y)
{
    if (FLAG_MOVING) {
        angx = angx + (x - bgnx); // 相対的な移動量を角度として利用
        angy = angy + (y - bgny); // 相対的な移動量を角度として利用
        bgnx = x;
        bgny = y;
        glutPostRedisplay();
    }
}

ボタン操作で呼び出されるコールバック関数が MyMouse() です. 押されたボタン,その状態(押した/離した),さらに, その時のマウスカーソル座標(OpenGL ではウィンドウの左下が原点)が引数として渡されます.

132行目の判定では,左ボタン(GLUT_LEFT_BUTTON)が, 押された(GLUT_DOWN)状態であれば, マウス操作による回転移動フラグ(GFLAG_MOVING)をオン(GL_TRUE)にし, さらに,134,135行目でその時のマウス座標を bgnx, bgny に格納します.

137行目の判定では,左ボタン(GLUT_LEFT_BUTTON)が, 離された(GLUT_UP)状態であれば, マウス操作による回転移動フラグ(GFLAG_MOVING)をオフ(GL_FALSE)にします.

マウスを動かすことで呼び出されるコールバック関数が MyMotion() です. その時のマウスカーソル座標が引数として渡されます. マウスが動くたびにこの関数が呼び出されますが, 145行目の判定で,FLAG_MOVING がオフのときは何もしません.

FLAG_MOVING がオンのときは,一つ前の瞬間のマウス位置 bgnx, bgny と, 現在のマウス位置の差を使って, オブジェクトの回転の際の2軸の回転角 angx, angy を計算しています. さらにその後,bgnx, bgny に現在のマウス位置を格納し, 次の呼び出し時に利用します.

時間割り込み

時間割り込みを使って,自動回転モードを実現しています. main() に追加したコールバック関数の設定部分も参照してください.

// 時間割り込みのコールバック関数
void MyIdle(int i)
{
    if (FLAG_AUTO_ROTATE) {
        acount++; // 自動回転時の角度更新
    }
    glutPostRedisplay();
    glutTimerFunc(REDRAW_INTERVAL, MyIdle, 1); // 次の割り込み設定
}

時間割り込みのコールバック関数は,ここでは,MyIdle() としています. 引数は,時間割り込みの際の識別番号で, glutTimerFunc() の設定時にしていしますが,ここでは使っていません.

中身としては,FLAG_AUTO_ROTATE フラグがオンの時だけ, acount という変数をインクリメントします.この値を使って, 回転角を自動的に変化させ,自動回転モードが実現されます.

これに対応するため,RecalcModelView() に, 106〜108行目の部分を追加し,自動回転モードの時は, acount の値を見て水平方向の回転を行うようにしています.

// 回転操作におけるモデリング変換行列の再計算
void RecalcModelView(void)
{
    glPopMatrix();
    glPushMatrix();    
    glTranslatef(0.5, 0.5, 0.5);
    if ( FLAG_AUTO_ROTATE ) {                         // 追加部分
        glRotatef(-90, 1, 0, 0);                      // 追加部分
        glRotatef(((float)acount)/60.0*360, 0, 0, 1); // 追加部分
    } else {
        glRotatef(angy, 1, 0, 0); // angy で決まる角度で回す
        glRotatef(angx, 0, 0, 1); // angx で決まる角度で回す
    }
    glTranslatef(-0.5, -0.5, -0.5);
}

キーボード操作

キーボードが押された時に, そのキーに対応したインタラクションを行うための関数です. main() に追加したコールバック関数の設定部分も参照してください.

// キーボード操作のコールバック関数(キーに応じて処理を振り分ける)
void MyKeyboard(unsigned char key, int x, int y)
{
    switch (key) {
    case '1':
        FLAG_DISP_AXES = !FLAG_DISP_AXES;      break;
    case '2':
        FLAG_DISP_OBJECT = !FLAG_DISP_OBJECT;  break;
    case '3':
        FLAG_DISP_SHADOW =! FLAG_DISP_SHADOW;  break;
    case '9':
        REDRAW_INTERVAL += 5;
        if (REDRAW_INTERVAL > 300) REDRAW_INTERVAL = 300;
        break;
    case '0':
        REDRAW_INTERVAL -= 5;
        if (REDRAW_INTERVAL < 10) REDRAW_INTERVAL = 10;
        break;

    case 'z':
        point_size -= 1;
        if (point_size < 1) point_size = 1;
        break;
    case 'x':
        point_size += 1;
        if (point_size > 20) point_size = 20;
        break;
    case 'c':
        point_ratio -= 1;
        if (point_ratio < 1) point_ratio =1;
        break;
    case 'v':
        point_ratio += 1;
        if (point_ratio > 20) point_ratio =20;
        break;

    case 'a':
        FLAG_AUTO_ROTATE =! FLAG_AUTO_ROTATE;  break;
    case 'h':
        angx += 5; break;
    case 'l':
        angx -= 5; break;
    case 'j':
        angy += 5; break;
    case 'k':
        angy -= 5; break;
    case 27:
    case 'q':
        exit(0);
    }
    glutPostRedisplay();
}

キーボード操作に対するコールバック関数が,MyKeyboard() です. 押されたキーの文字コード(ASCII)が引数として渡されます. あとは,switch-case 文を使って,それぞれのキー操作に対する処理を記述しています.

例えば,168〜173行目では,「1」「2」「3」のキー操作に合わせて, 座標軸,対象物,影つけの表示フラグをオン・オフでトグルしています.

この他は,グローバル変数として宣言されている, 表示のためのパラメータ(角度や設定値)を キー操作に応じて増減することで,表示を制御しています.

メイン関数部分

メイン関数には,マウス操作,タイマ割り込み, キーボード操作のそれぞれのイベントに対応するコールバック関数の設定が追加されています.

// メイン関数
int main(int argc, char **argv)
{
    glutInit(&argc, argv);// GLUT の初期化
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutCreateWindow("GLUT TEST"); // ウィンドウの生成
    
    glutDisplayFunc(MyRedraw);    // 再描画コールバック関数設定
    glutKeyboardFunc(MyKeyboard); // キーボードのコールバック関数設定
    glutMouseFunc(MyMouse);       // マウスボタンのコールバック関数設定
    glutMotionFunc(MyMotion);     // マウス移動のコールバック関数設定

    // OpenGL での描画の初期化,視点設定など 
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    gluPerspective(30.0, 1.0, 1.0, 45.0);
    glMatrixMode(GL_MODELVIEW);
    gluLookAt(0.0, 1.5, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);  
    glPushMatrix();
    glTranslatef(-0.5, -0.5, -0.5);  // 立方体の中心を原点に重ねる
    glPushMatrix();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor( 1.0, 1.0, 1.0, 1.0);    

    LoadPointData(argv[1]);  // 点群データの読み込み

    glutTimerFunc(REDRAW_INTERVAL, MyIdle, 1);  // タイマ割り込みの設定

    glutMainLoop(); // GLUTのイベントループ処理へ
    return 0;       // ここには戻ってこない

}

225〜227行目が,それぞれのイベントのコールバック関数の追加設定になりま す.コールバック関数として,MyKeyboard(), MyMouse(), MyMotion() が設定 されています.

また,248行目に,タイマ割り込みの設定があります.次の割り込みまでの時 間設定(REDRAW_INTERVALで指定)と,呼び出されるコールバック関数 MyIdle() ,そして,識別番号1(未使用)が設定されています.


サンプル映像

マウス操作のサンプル映像

自動回転モードのサンプル映像


連載第3回

連載第3回では,ポップアップメニューの利用と, 複数のフレームを順次表示することによる時系列データ表示アニメーションについて説明しました. 連載3回目までの機能を付け加えたサンプルプログラムを sample3-1.c とし, ここではその詳細を説明します.基本的には, いくつかの関数の追加で対応していますが, sample1-1.c,sample2-1.c にあった関数を一部修正(追加)している部分もあります.

sample3-1.c の説明

sample3-1.c はここからダウンロードできます.

以下,追加した部分を中心に各パートを説明します.

ヘッダ部分,グローバル変数宣言

sample1-1.c,sample2-1.c とほぼ同じですが, グローバル変数 FRAME_INTERVAL が追加されています. 時系列アニメーションの際のフレーム更新を行う再描画回数を設定します. ここでは 20 [回] に設定しています.

GLint FRAME_INTERVAL = 20;  // フレーム更新のための再描画回数

ポップアップメニューの利用

ウィンドウの中でマウスのボタンを押したときに現れるポップアップメニューによって, モードの切り替えや値の設定などをメニュー選択で行うのは,GUI操作として良く利用されます. 手動回転モードで左ボタンを利用していることもあり, ここでは,右ボタンでポップアップメニューを操作することにします.

まず,構成として,2段の階層を持つメニューを考える事にします. 1段目は主な操作として,表示物のオン・オフと,回転モードの自動・手動切り替えを, 2段目は数値を選択して設定するタイプの操作として, 表示点の大きさ設定と表示率(間引きによる)の設定を行うメニューとします.

main() 関数には,メニューの設定を追加しています.

    int point_size_menu, point_ratio_menu;

ここで宣言した2つの変数は,サブメニューの識別子となります.

    // 点の表示サイズ操作サブメニューの生成
    point_size_menu = glutCreateMenu(point_size_select);
    glutAddMenuEntry("1", 1);
    glutAddMenuEntry("2", 2);
    glutAddMenuEntry("3", 3);
    glutAddMenuEntry("5", 5);
    glutAddMenuEntry("10", 10); // 点のサイズ値で設定
    
    // 点の表示率操作サブメニューの生成
    point_ratio_menu = glutCreateMenu(point_ratio_select);
    glutAddMenuEntry("Full", 1);
    glutAddMenuEntry("1/2", 2);
    glutAddMenuEntry("1/3", 3);
    glutAddMenuEntry("1/5", 5);
    glutAddMenuEntry("1/10", 10); // 点の表示率(間引き数)で設定
    
    // ポップアップメニューの生成
    glutCreateMenu(MyMenuSelect);
    glutAddMenuEntry("Toggle XYZ axes", 1);
    glutAddMenuEntry("Toggle Object", 2);
    glutAddMenuEntry("Toggle Shadow", 3);   
    glutAddMenuEntry("Auto Rotate", 4);
    glutAddMenuEntry("Manual Rotate", 5);
    glutAddMenuEntry("----------", 0);       // セパレータ
    glutAddSubMenu("Point Size", point_size_menu);   // サブMenu連結
    glutAddSubMenu("Point Ratio", point_ratio_menu); // サブMenu連結
    glutAddMenuEntry("----------", 0);       // セパレータ
    glutAddMenuEntry("exit", 999);
    
    glutAttachMenu(GLUT_RIGHT_BUTTON); // マウスの右ボタン操作に関連付け

273〜279行目は,点の表示サイズを操作するサブメニューの設定です. 274行目の glutCreateMenu() でメニューの生成とコールバック関数との関連付けを行い, 続いて,glutAddMenuEntry() を繰り返し使って,メニュー項目のエントリを追加していきます. ここでは,「1」,「2」,「3」,「5」,「10」のメニューを追加し, それぞにメニュー値 1,2,3,5,10 を割り当てています. メニュー値は,そのメニュー項目が選択された場合に, コールバック関数(ここでは,point_size_select())に渡される引数となります.

同様に,281〜287行目は,点の表示率(間引き数)を操作するサブメニュー設定です. コールバック関数は,point_ratio_select() になっています.

メインのメニューも同様に設定します.289行目〜300行目がそれに当たります. サブメニューの場合と同様にメニュー項目を追加していきます. 297行目,298行目のエントリでは,上で設定した2つのサブメニューを連結しています. メニューの区切りとして,メニュー値が 0となるメニュー項目を利用しています.

最後に,302行目で glutAttachMenu() で,メニュー操作をマウスの右ボタンに割り当てています.

ポップアップメニューのコールバック関数

メインメニューと2つのサブメニューに,それぞれコールバック関数を用意しています.

// メニュー操作のコールバック関数1(メニュー値でグローバル変数を操作)
void MyMenuSelect(int value)
{
    switch (value) {
    case 1:
        FLAG_DISP_AXES = !FLAG_DISP_AXES;      break;
    case 2:
        FLAG_DISP_OBJECT = !FLAG_DISP_OBJECT;  break;
    case 3:
        FLAG_DISP_SHADOW =! FLAG_DISP_SHADOW;  break;
    case 4:
        FLAG_AUTO_ROTATE = GL_TRUE;  break;
    case 5:
        FLAG_AUTO_ROTATE = GL_FALSE; break;
    case 999:
        exit(0);
    }
    glutPostRedisplay();
}

// メニュー操作のコールバック関数2(サイズ)
void point_size_select(int value)
{
    point_size = (GLint)value; // メニュー値を代入
    glutPostRedisplay();
}

// メニュー操作のコールバック関数3(表示率)
void point_ratio_select(int value)
{
    point_ratio = (GLint)value; // メニュー値を代入
    glutPostRedisplay();
}

メインメニューから呼ばれる MySelectMenu() では,メニュー値に設定された値を switch-case で振り分けて, それぞれのメニューに対応するように,グローバル変数の値をトグルしたり,設定したいしています. 例えば,「Toggle XYZ axis」のメニューが選ばれると,メニュー値1が渡され,FLAG_DISP_AXES の値がトグルし, 結果として,表示のオン・オフが切り替わります.

サブメニューから呼ばれる point_size_select() や point_ratio_select() では, メニュー値をそのままグローバル変数に代入する操作が行われます. 結果として,DispObject() の中で point_size や point_ratio の値が使われることで,操作が表示に反映します.

時系列アニメーション

このプログラムでは,動画像のフレームと同様な考え方で, 複数の点群データを時系列に再生し,時間変化を伴う状況の表示を行えるようにしています.

まずデータフォーマットは,以下の通りです.

32 64     <-- 総フレーム数 表示対象サイズ(表示時に 0-1 に正規化するため)
0:        <-- 0番目のフレーム
4096      <--    点の総数
0 0 32    <--    0番目の点の座標
1 0 36    <--    1番目の点の座標
  (中略)          :
63 63 27  <--    4095番目の点の座標
1:        <-- 1番目のフレーム
4096      <--    点の総数
0 0 41    <--    0番目の点の座標
  (中略)          :
63 63 27  <--    4095番目の点の座標
31:       <-- 31番目のフレームの
4096      <--    点の総数
0 0 32    <--    0番目の点の座標
  (中略)          :
63 63 27  <--    4095番目の点の座標

この例では,32フレームの時系列データを扱っており,それぞれ 4096点からなるデータとなっています.

表示の関数 DispObject() では,フレーム番号に応じたデータの表示を行うように作ってありましたので, あとは,タイマ割り込みを利用して,表示するフレーム番号 frame の値をインクリメントすればよいことになります. タイマ割り込みのコールバック関数 MyIdle() の中身を以下のように改造しました.

// 時間割り込みのコールバック関数
void MyIdle(int i)
{
    if ( tcount > FRAME_INTERVAL ) { // 指定数の再描画が行われたら
        tcount = 0;                  // カウンタを初期化し
        frame ++;                    // フレーム番号をインクリメント
        if (frame >= max_frames ) { frame = 0; }
    } else {
        tcount++;
    }
    if (FLAG_AUTO_ROTATE) {
        acount++; // 自動回転時の角度更新
    }
    glutPostRedisplay();
    glutTimerFunc(REDRAW_INTERVAL, MyIdle, 1); // 次の割り込み設定
}

192〜198行目の部分で,自動回転などで使うタイマ割り込みによる再描画の際に, tcount という変数をインクリメントし,その値が,グローバル変数 FRAME_INTERVAL になれば, frame をインクリメントするという方法です.最大値になれば frame を 0 に初期化することで, 用意したフレーム分の時系列データをループ再生します.


サンプル映像

ポップアップメニューのサンプル映像

時系列アニメーションのサンプル映像

この時系列データを生成するプログラム wave.c はここからダウンロードできます.

その他

準備中