Androidアプリ開発 OpenGL 視点をスクロール(スライド)で操作する

みなさん、こんにちは、本日もAndroidアプリを作っていこうと思います。
視点位置をgluLookAtで変更することができた。端末をタップすることでこれを動的に変えていこうと思う。OpenGLというかandroidの方のネタになる。
OpenGLは、iPhoneやPCでも使用できる。プラットフォームを選ばない、まさしくオープンなものである。なので、androidでの決まりは、OpenGLには持ち込むことはできない。
「モデルを作ったからクリックとかタップは、そっちでなんとかして」
というのは「まかりとおらない」話なのである。
そういった、感じなので、タップなどのイベントは、androidの世界で処理してやらなければならない。アクティビティでonTouchをオーバーライドすればタップした位置は取れるが、ドラッグ(スライド)はゼスチャーっていうのを使ってイベントを別途処理してやる必要がある。
androidは日々進化している。めざましい進化なのではあるが、すぐに「レガシー(時代遅れ)」になってしまう。まぁ、嘆いてもしょうがないことではある。そのうち程良く「枯れる」ことになるであろう。
「早くコードがみたいなぁ」
そうでした。まずは、アクティビティでゼスチャー検出機を作る。
ゼスチャー検出機といっても、ハードウェアではなくSDK中のクラスで提供されているもの。イベントを拾ったら、これをゼスチャーに処理してもらう。ゼスチャーが、イベントを処理して、これはダブルタップだな、とか時間が開いているからロングタップにしよう、といった処理が行われて、リスナーのonLongPressが呼び出される、といった具合。
久しぶりにアクティビティのコードに登場してもらおう。

public class TestOpenGLActivity extends Activity {
private GestureDetector gesDetector = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
// OpenGL用のビューを作る
SampleGLSurfaceView view = new SampleGLSurfaceView(this);
// アクティビティにビューを設定
setContentView(view);
// ゼスチャー
gesDetector = new GestureDetector(this, view);
}
@Override
public boolean onTouchEvent(MotionEvent event){
return gesDetector.onTouchEvent(event);
}
}

GestureDetector gesDetectorがゼスチャー検出機。コンストラクタで作成している。GestureDetectorのコンストラクタには引数がふたつ必要。最初の引数は、Context。アクティビティを入れればOK。ふたつ目の引数は、イベントを受けるためのリスナー。OpenGLのビューで受けたいのでviewを指定する。
onTouchEventをオーバーライドしてイベントを一度アクティビティで受けるようにする。イベントがどんどん上がってくるので、そのままゼスチャー検出機にかけてしまう。戻り値もそのまま返してしまう。
アクティビティはこれでOK。SampleGLSurfaceViewの方にイベント処理部分を作っていく。GestureDetector(this, view)のところがviewがリスナーになっていないので、エラーになっているはず。クイックフィックスを使って、SampleGLSurfaceViewクラスにインターフェース「OnGestureListener」の実装を指示する。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {

今度は、SampleGLSurfaceViewにメソッドが足りないエラーになるので、必要なメソッドをクイックフィックスで作成する。
とりあえず、ドラッグ(スライド)したときに視点を変えたいので、onScrollのみ内容を書く。後はほっておく。

@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distx, float disty) {
eyepos[0] += distx * 0.01;
eyepos[1] += disty * 0.01;
return true;
}

引数の名前も変えている。onScrollには4つの引数でイベントが渡ってくる。詳細はSDKのマニュアルを参照して欲しい。移動量がdistxとdistyでわかるので、視点位置も移動させている。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
private Model model = new Model();
private float angle = 0.0f;
private float eyepos[] = new float[3];

eyeposは、クラスのフィールドとして定義した。floatの配列で、x,y,z座標の値を記憶する。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
eyepos[0] = 0;
eyepos[1] = 0;
eyepos[2] = 3;
}

初期値は、レンダラーのonSurfaceCreatedで設定するようにした。
おっと、カメラ位置もeyeposにしなくては。

@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// カメラ位置を設定
GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);

これで、スライドさせれば、視点が動くはず...
しかし、期待外れに終わってしまった。
いくらスライドさせても一向に変化しない。何か間違えたか...
イベントは来ている。eyeposの値も変化している。いろいろ調べった結果、gluLookAtの場所がまずいことが判明。glMatrixMode(GL_MODELVIEW)の後でやるとうまく動いた。
PROJECTIONを選択した後で、やらないといけないことはわかっていたのだが... どっちが正解なんだろうか。
とりあえず動いたので、このままでいくことにする。以下の動画は画面上でスライドさせて三角形を動かしている。三角形のローテーションは止めてある。screen castによる動画キャプチャなので、指が映っていないので、なんか実感がないが... スマホのカメラで動画撮影もやってはみたものの、やっぱりきれいじゃない。これで、かんべんして。

念のため、SampleGLSurfaceViewの全体も貼っておく。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
private Model model = new Model();
private float angle = 0.0f;
private float eyepos[] = new float[3];
// レンダラークラス
class OpenGLRenderer implements Renderer {
float lightpos[] = {0.0f, 0.0f, 4.0f, 0.0f};
float red[] = {1.0f, 0.0f, 0.0f, 1.0f};
float green[] = {0.0f, 1.0f, 0.0f, 1.0f};
float blue[] = {0.0f, 0.0f, 1.0f, 1.0f};
float white[] = {1.0f, 1.0f, 1.0f, 1.0f};
float gray[] = {0.5f, 0.5f, 0.5f, 1.0f};
float yellow[] = {1.0f, 1.0f, 0.0f, 1.0f};
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// ライティングをON
gl.glEnable(GL10.GL_LIGHTING);
// 光源を有効にして位置を設定
gl.glEnable(GL10.GL_LIGHT0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, red, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, blue, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
// カメラ位置を設定
GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(1, 0, 0);
gl.glRotatef(angle, 0, 1, 0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);
// デプステスト
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glDepthMask(true);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
gl.glShadeModel(GL10.GL_FLAT);
model.draw(gl);
gl.glPopMatrix();		// マトリックスを戻す
// ふたつめの描画
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(-1, 0, 0);
gl.glRotatef(angle, 0, 1, 0);
gl.glCullFace(GL10.GL_BACK);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
// スムースシェーディング
gl.glShadeModel(GL10.GL_SMOOTH);
model.draw(gl);
gl.glPopMatrix();		// マトリックスを戻す
//angle += 0.5;			// 回転角度は最後に計算
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 50f, (float)width / height, 0.01f, 100f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
eyepos[0] = 0;
eyepos[1] = 0;
eyepos[2] = 3;
}
}
private OpenGLRenderer renderer;
// サーフェースビューのコンストラクタ
public SampleGLSurfaceView(Context context) {
super(context);
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}
@Override
public boolean onDown(MotionEvent arg0) {
// TODO 自動生成されたメソッド・スタブ
return false;
}
@Override
public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
float arg3) {
// TODO 自動生成されたメソッド・スタブ
return false;
}
@Override
public void onLongPress(MotionEvent arg0) {
// TODO 自動生成されたメソッド・スタブ
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distx, float disty) {
eyepos[0] += distx * 0.01;
eyepos[1] += disty * 0.01;
return true;
}
@Override
public void onShowPress(MotionEvent arg0) {
// TODO 自動生成されたメソッド・スタブ
}
@Override
public boolean onSingleTapUp(MotionEvent arg0) {
// TODO 自動生成されたメソッド・スタブ
return false;
}
}

投稿者プロフィール

asai
asai
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript