0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ubuntu20.04で発覚したglutのmouse pick問題

Last updated at Posted at 2021-03-29

GLUTで表示したオブジェクトをマウスクリックにより選択する方法(マウスによるセレクション、あるいは mouse pick などとも呼ばれる?)は、和歌山大学 床井先生のページにあった解説を流用させていただいていた。(こちらのページです)

Ubuntu 19.10までは全く問題なかったが、Ubuntu 20.04にアップグレードしたところ何故かマウスクリック時に segmentation fault で落ちるようになってしまった(オブジェクトを触っていなくても、とにかくウィンドウ上でマウスをクリックすると落ちる)。

色々と悪戦苦闘した結果、

glRenderMode(GL_SELECT) に切り替えた後に glCallList(...) でオブジェクトを描画しようとしたときに落ちることが判明。

この不具合は、

glRenderMode(GL_SELECT) の直前に

glDisable(GL_LIGHTING)

をしておき、描画(glCallList)が終わったあとに glEnable(GL_LIGHTING) と戻してやることで回避することができた。

修正したコードはこちら。

select_modified.cpp
select_modified.cpp

# include <stdio.h>
# include <math.h>
# include <GL/glut.h>

/* セレクションバッファのサイズ */
# define SELECTIONS 100

/* オブジェクトの数 */
# define NOBJECTS 5

/* ディスプレイリストの識別子 */
GLuint objects;

/* 各オブジェクトの色 */
GLfloat *color[NOBJECTS];

/* 色データ */
GLfloat gray[] = { 0.7, 0.7, 0.7 };
GLfloat blue[] = { 0.1, 0.1, 0.9 };

void display(void)
{
  int i;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  /* 一旦シーンを描画する */
  for (i = 0; i < NOBJECTS; i++) {
    glMaterialfv(GL_FRONT, GL_DIFFUSE, color[i]);
    glCallList(objects + i);
  }

  glFlush();
}

void mouse(int button, int state, int x, int y)
{
  static GLuint selection[SELECTIONS];  /* セレクションバッファ      */
  static GLint hits = 0;                /* セレクトされたオブジェクトの数 */

  switch (button) {
  case GLUT_LEFT_BUTTON:
    if (state == GLUT_DOWN) {
      GLuint *ptr;
      GLint vp[4];
      int i;

      /* セレクションに使うバッファの設定.これはセレクショ
         ンモード以外の時(glRenderMode(GL_SELECT) より前)
         に実行する必要がある.セレクションバッファには,入
         るだけのデータが詰め込まれる */
      glSelectBuffer(SELECTIONS, selection);

      /* YU20200520:
	 Ubuntu 20.04 で、マウスクリック時にsegmentation fault
	 で落ちるという問題が発覚(19.10迄は問題なかった)。
	 GL_SELECTモードに入る前にGL_LIGHTINGをdisalbeしておく
	 ことでこの問題が回避できる。
	 GL_LIGHTINGはオブジェクトを描画(画面には非表示)した後に
	 再びenableする必要がある。*/
      glDisable(GL_LIGHTING);

      /* レンダリングモードをセレクションモードに切替える */
      glRenderMode(GL_SELECT);
      
      /* セレクションバッファの初期化,これはセレクションモー
         ドになってないと無視される */
      glInitNames();

      /* ネームスタックの先頭に仮の名前を詰めておく.ネーム
         スタック自体は複数のオブジェクトが選択できるように
         スタック構造になっているが,今回は1個のオブジェク
         トしか選択しないので,ネームスタックの先頭の要素だ
         けを取り替えながら描画すればよい.そこで,あらかじ
         めネームスタックの先頭に仮の名前 (-1) に詰めておい
         て,そこを使い回す. */
      glPushName(-1);

      /* セレクションの処理は視点座標系で行う */
      glMatrixMode(GL_PROJECTION);

      /* 現在の透視変換マトリクスを保存する */
      glPushMatrix();

      /* 透視変換マトリクスを初期化する */
      glLoadIdentity();

      /* 現在のビューポート設定を得る */
      glGetIntegerv(GL_VIEWPORT, vp);

      /* 表示領域がマウスポインタの周囲だけになるように変換
         行列を設定する.マウスの座標系は,スクリーンの座標
         系に対して上下が反転しているので,それを補正する */
      gluPickMatrix(x, vp[3] - y - 1, 1, 1, vp);

      /* 通常の描画と同じ透視変換マトリクスを設定する.ウィ
         ンドウ全体をビューポートにしているので,アスペクト
         比は vp[2] / vp[3] で得られる.*/
      gluPerspective(30.0, (double)vp[2] / (double)vp[3], 1.0, 100.0);

      /* モデルビューマトリクスに切替える */
      glMatrixMode(GL_MODELVIEW);

      /* ここで一旦モデルビューマトリクスに切替えて,ビュー
         イング変換やモデリング変換の設定をするのだが,直前
         に描画された図形に対してセレクションを行なうなら,
         その時に使ったモデルビューマトリクスがそのまま使え
         る(はず).だから今は以下の処理 (#if 0 ~ #endif) 
         を省略しても大丈夫(だと思う)*/

# if 0
      /* モデルビュー変換行列の初期化 */
      glLoadIdentity();

      /* 視点の設定 */
      gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
# endif

      /* もう一度シーンを描画する */
      for (i = 0; i < NOBJECTS; i++) {
	/* ネームスタックの先頭にこれから描くオブジェクトの
           番号を設定する */
	glLoadName(i);
	/* オブジェクトを描画する(画面には表示されない)*/
	glCallList(objects + i);
      }

      /* YU20200520:disableしたGL_LIGHTINGをもとに戻しておく。 */
      glEnable(GL_LIGHTING);

      /* 再び透視変換マトリクスに切替える */
      glMatrixMode(GL_PROJECTION);

      /* 透視変換マトリクスを元に戻す */
      glPopMatrix();

      /* モデルビューマトリクスに戻す */
      glMatrixMode(GL_MODELVIEW);

      /* レンダリングモードを元に戻す */
      hits = glRenderMode(GL_RENDER);

      /* セレクションバッファにはいくつのデータが入っている
         のかわからないので,とりあえず先頭のポインタを取り
         出しておく */
      ptr = selection;

      /* hits にはセレクションバッファに格納されたデータの数
         が入る.データがセレクションバッファに入り切らなけ
         れば hits < 0 となる */
      for (i = 0; i < hits; i++) {
	/* セレクションバッファの先頭の要素は、選択されたオ
           ブジェクトの数 */
	unsigned int j, n = ptr[0];

	/* 続く2つの要素は、選択された位置に置ける奥行き値
           の最小値と最大値を符号なし整数で表したもの */
	double near = (double)ptr[1] / (double)0x7fffffff;
	double far  = (double)ptr[2] / (double)0x7fffffff;

	/* セレクションバッファの4つ目の要素(添字=3)か
           ら選択されたオブジェクトの番号が入っている */
	ptr += 3;
	for (j = 0; j < n; j++) {
	  color[*(ptr++)] = blue; /* オブジェクトの色を青にする */
	}
      }
    }
    else {
      GLuint *ptr = selection;
      int i;

      /* selection[] の内容はマウスの左ボタンをクリックした
         時にしか変更されないはずだから,まだ残っているはず */
      for (i = 0; i < hits; i++) {
	unsigned int j, n = ptr[0];
	
	ptr += 3;
	for (j = 0; j < n; j++) {
	  color[*(ptr++)] = gray;
	}
      }
    }

    /* 画面を書き換えてみる */
    glutPostRedisplay();
    break;
  case GLUT_MIDDLE_BUTTON:
    break;
  case GLUT_RIGHT_BUTTON:
    break;
  default:
    break;
  }
}

void resize(int w, int h)
{
  /* ウィンドウ全体をビューポートにする */
  glViewport(0, 0, w, h);

  /* 透視変換行列を設定する */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30.0, (double)w / (double)h, 1.0, 100.0);

  /* モデルビュー変換行列を指定しておく */
  glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
  case 'q':
  case 'Q':
  case '\033':
    exit(0);  /* '\033' は ESC の ASCII コード */
  default:
    break;
  }
}

void init(void)
{
  int i;

  /* 初期設定 */
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);

  /* 各オブジェクトのディスプレイリストを作る */
  objects = glGenLists(NOBJECTS);
  if (objects <= 0) {
    fprintf(stderr, "Can't create so many objects: %d.\n", NOBJECTS);
    exit(1);
  }

  /* 各オブジェクトの色を初期化 */
  for (i = 0; i < NOBJECTS; i++) {
    color[i] = gray;
  }
}

void scene(void)
{
  const GLdouble width = 4.0;
  const GLdouble center = width / 2.0;
  const GLdouble step = width / (NOBJECTS - 1);
  int i;

  /* セレクションの対象になるオブジェクトを生成して,ディス
     プレイリストを作っておく*/
  for (i = 0; i < NOBJECTS; i++) {

    /* 図形をディスプレイリストに登録 */
    glNewList(objects + i, GL_COMPILE);

    /* 球を描く */
    glPushMatrix();
    glTranslated((double)i * step - center, ((i & 1) * 2 - 1) * step, 0.0);
    glutSolidSphere(0.5, 10, 5);
    glPopMatrix();

    glEndList();
  }
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);
  glutCreateWindow(argv[0]);
  glutDisplayFunc(display);
  glutMouseFunc(mouse);
  glutReshapeFunc(resize);
  glutKeyboardFunc(keyboard);
  init();
  scene();
  glutMainLoop();
  return 0;
}

(床井先生サイトのサンプルコードをもとにしております。掲載に問題があればお知らせください)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?