OpenGL ESで似非マウスピッキング

やはりマウスピッキングOpenGLでやってるような方法は使えないみたい。
なので違う方法を考えた。けどちょいちょい穴があるので似非マウスピッキングということで。


結局何をしてるかというとglReadPixelsメソッドでクリック位置の色情報を取得し比較してるだけ。
詳しいやり方はソースを見てもらえればと思います。


色の比較しかしてないのでもちろん穴があるわけで。
具体的には背景と同じ色のオブジェクトには反応しない、
同じ色のオブジェクトが重なっていた場合うまく重なりを判定できないなどなど。
他にも色々あると思います。


他の人はどんなやり方をするんだろうか。


以下動作画面とソース。
http://screencast.com/t/wjiwUNfpM

ソース

package net.swelt.android.opengltest;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.OpenGLContext;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.View;
import android.view.MotionEvent;

import java.nio.ByteBuffer;
import java.util.Vector;

import javax.microedition.khronos.opengles.GL10;

public class OpenGLTest extends Activity {
	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);				
		setContentView(new GLView(this));
	}

	public class GLView extends View {
		private final int MOUSE_UP = 0;
		private final int MOUSE_DOWN = 1;
		private OpenGLContext mGLContext;
		private Vector<DisplayObject3D> m3DObjects;
		private float mAngle;
		private long mNextTime;
		private boolean mAnimate;
		private boolean mClickFlag = false;
		private int mMouseX;
		private int mMouseY;
		private int mMouseState = MOUSE_UP;
		private int mMousePrevState = MOUSE_UP;
		
		public GLView(Context context) {
			super(context);
			
			mGLContext = new OpenGLContext(OpenGLContext.DEPTH_BUFFER);
		
			createObjects();
			
			mAnimate = false;
		}
		
		private void createObjects() {
			m3DObjects = new Vector<DisplayObject3D>();
			DisplayObject3D object;
			
			object = new Cube(0x10000 << 1, 0x10000 << 1, 0x10000 << 1,  2, 2, 2);
			m3DObjects.add(object);
			
			object = new Cube(0x10000 >> 1, 0x10000 << 2, 0x10000 >> 1, 1, 1, 1);
			m3DObjects.add(object);
			
		}
		
		@Override
		protected void onAttachedToWindow() {
			mAnimate = true;
			Message msg = mHandler.obtainMessage(INVALIDATE);
			mNextTime = SystemClock.uptimeMillis();
			mHandler.sendMessageAtTime(msg, mNextTime);
			super.onAttachedToWindow();
		}
		
		@Override
		protected void onDetachedFromWindow() {
			mAnimate = false;
			super.onDetachedFromWindow();
		}
		
		public void update() {
			
			mClickFlag = mMouseState == MOUSE_UP && mMousePrevState == MOUSE_DOWN;
			
			mMousePrevState = mMouseState;
		}
		
		@Override
		protected void onDraw(Canvas canvas) {
			GL10 gl = (GL10) mGLContext.getGL();
			
			mGLContext.waitNative(canvas, this);

			prepare(canvas, gl);
						
			gl.glEnable(GL10.GL_DEPTH_TEST);
			if( mClickFlag ) {
				drawObjectsWithMousePicking(gl);
			} else {
				drawObjects(gl);
			}
			gl.glDisable(GL10.GL_DEPTH_TEST);			
			
			mGLContext.waitGL();
			
		}
		
		private void prepare(Canvas canvas, GL10 gl) {
			int w = getWidth();
			int h = getHeight();
			
			gl.glViewport(0, 0, w, h);
			
			float ratio = (float)w / h;
			gl.glMatrixMode(GL10.GL_PROJECTION);
			gl.glLoadIdentity();
			gl.glFrustumf(-ratio, ratio, -1, 1, 2, 12);
			
			gl.glDisable(GL10.GL_DITHER);
			
			gl.glClearColor(1, 1, 1, 1);
			gl.glEnable(GL10.GL_SCISSOR_TEST);
			gl.glScissor(0, 0, w, h);
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
			
			gl.glMatrixMode(GL10.GL_MODELVIEW);
			gl.glLoadIdentity();
			gl.glTranslatef(0, 0, -3.0f);
			gl.glScalef(0.5f, 0.5f, 0.5f);
			gl.glRotatef(mAngle, 0, 1, 0);
			gl.glRotatef(mAngle*0.25f, 1, 0, 0);

			gl.glColor4f(0.7f, 0.7f, 0.7f, 1.0f);
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glEnableClientState(GL10.GL_COLOR_ARRAY);			
			gl.glCullFace(GL10.GL_BACK);
			gl.glEnable(GL10.GL_CULL_FACE);
		}
		
		private void drawObjects(GL10 gl) {
			for( int i = 0; i < m3DObjects.size(); ++ i ) {
				m3DObjects.elementAt(i).draw(gl);
			}
		}
		
		private void drawObjectsWithMousePicking(GL10 gl) {
			int hitIndex = -1;
			ByteBuffer bf1, bf2;
			bf1 = ByteBuffer.allocateDirect(4);
			bf2 = ByteBuffer.allocateDirect(4);
			gl.glReadPixels(mMouseX, getHeight()-mMouseY, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bf1);			
			for( int i = 0; i < m3DObjects.size(); ++ i ) {
				m3DObjects.elementAt(i).draw(gl);

				bf2.position(0);
				gl.glReadPixels(mMouseX, getHeight()-mMouseY, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bf2);
				if( bf2.compareTo(bf1) != 0 ) {
					for( int j = 0; j < 4; ++ j ) {
						bf1.put(j, bf2.get());
					}
					hitIndex = i;
				}
			}
			if( hitIndex >= 0 ) {
				DisplayObject3D obj = m3DObjects.elementAt(hitIndex);
				obj.setX(obj.getX() - 0x1000);
			}			
		}
				
		@Override
		public boolean onMotionEvent(MotionEvent e) {
			switch( e.getAction() ) {
			case MotionEvent.ACTION_DOWN:
				mMouseState = MOUSE_DOWN;
				mMouseX = (int) e.getX();
				mMouseY = (int) e.getY();
				break;
			case MotionEvent.ACTION_UP:
				mMouseState = MOUSE_UP;
			}
			return true;
		}
		
		//---------
		private static final int INVALIDATE = 1;
		private final Handler mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				if( mAnimate && msg.what == INVALIDATE ) {
					update();
					invalidate();
					msg = obtainMessage(INVALIDATE);
					long current = SystemClock.uptimeMillis();
					if( mNextTime < current ) {
						mNextTime = current + 20;
					}
					sendMessageAtTime(msg, mNextTime);
					mNextTime += 20;
				}
			}
		};
	}
}