OpenGL ESも使えなくちゃ



やっぱり3Dというのは魅力的で。使えるなら使いたいものです。
ということで色々試してみた。


最初に思ったのは「こりゃめんどくさい」ということ。いちいち頂点自分で決めるの?とか。
元々ActionScriptPapervision3Dを使ってたので余計にめんどくさく感じる。
cheprogrammingのchephesさんも同じ考えのようで。

少し前にPaperVision3Dを使って3D系の物を作っていたので、 OpenGL流の頂点がなんたらかんたら、という扱いが非常にめんどくさく感じる。


あとオブジェクトをひとまとめに扱いたいと思った。
ので作ったのが今回のもの。頂点の設定がものすごい簡単になったと思う。


前述のcheprogrammingはものすごい参考にしました。ソースが似てるのはそのせい笑
あと頂点の決定に関してはPapervision3Dのものと同じ考え方で作りました。
というわけで今回のやつはいろんなとこからの寄せ集めみたいな感じ。


あとはPlaneだけでなくCubeとかSphereとか作ろう。
以下動作画面とソース。

http://screencast.com/t/didOBlr4

ソース

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 java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Random;
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 OpenGLContext mGLContext;
		private Vector<DisplayObject3D> m3DObjects;
		private float mAngle;
		private long mNextTime;
		private boolean mAnimate;
		
		public GLView(Context context) {
			super(context);
			
			mGLContext = new OpenGLContext(0);
		
			createObjects();
			
			mAnimate = false;
		}
		
		private void createObjects() {
			m3DObjects = new Vector<DisplayObject3D>();
			DisplayObject3D object;
			
			object = new Plane(0x10000 << 1, 0x10000 << 2, 10, 10);
			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();
		}
		
		@Override
		protected void onDraw(Canvas canvas) {
			GL10 gl = (GL10) mGLContext.getGL();
			
			prepare(canvas, gl);
			
			drawObjects(gl);
			
			mAngle += 1.2f;
			
			mGLContext.waitGL();
		}
		
		private void prepare(Canvas canvas, GL10 gl) {
			mGLContext.waitNative(canvas, this);
			
			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);
			
			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.glDisable(GL10.GL_CULL_FACE);
		}
		
		private void drawObjects(GL10 gl) {
			for( int i = 0; i < m3DObjects.size(); ++ i ) {
				m3DObjects.elementAt(i).draw(gl);
			}
				
		}
		
		//---------
		private static final int INVALIDATE = 1;
		private final Handler mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				if( mAnimate && msg.what == INVALIDATE ) {
					invalidate();
					msg = obtainMessage(INVALIDATE);
					long current = SystemClock.uptimeMillis();
					if( mNextTime < current ) {
						mNextTime = current + 20;
					}
					sendMessageAtTime(msg, mNextTime);
					mNextTime += 20;
				}
			}
		};
	}
	
	abstract class DisplayObject3D {		
		abstract void create(int segX, int segY);
		abstract void draw(GL10 gl);
		
	    IntBuffer createIntBuffer(int[] data){
	    	ByteBuffer buffer = ByteBuffer.allocateDirect(data.length * 4);
	    	buffer.order(ByteOrder.nativeOrder());
	    	IntBuffer ret = buffer.asIntBuffer();
	    	ret.put(data);
	    	ret.position(0);
	    	
	    	return ret;
	    }

	    ByteBuffer createByteBuffer(byte[] data){
	    	ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
	    	buffer.order(ByteOrder.nativeOrder());
	    	buffer.put(data);
	    	buffer.position(0);
	    	
	    	return buffer;
	    }
	}
	
	public class Plane extends DisplayObject3D {
		private IntBuffer mVertexBuffer;
		private IntBuffer mColorBuffer;
		private ByteBuffer mIndexBuffer;
		private int mWidth;
		private int mHeight;
		private int x;
		private int y;
		private int z;
		
		public Plane(int width, int height, int segX, int segY) {
			mWidth = width;
			mHeight = height;
			create(segX, segY);
		}
		
		protected void create(int segX, int segY) {
			int oneX = mWidth >> 1;
			int oneY = mHeight >> 1;
			int gridU = segX;
			int gridV = segY;
			int gridU1 = gridU + 1;
			int gridV1 = gridV + 1;
			int incU = mWidth / gridU;
			int incV = mHeight / gridV;
			int depth = 0;
			
			//vertices
			int[] vertices = new int[gridU1 * gridV1 * 3];
			int cnt = 0;
			for( int iu = 0; iu < gridU1; ++ iu ) {
				for( int iv = 0; iv < gridV1; ++ iv ) {
					vertices[cnt++] = iu * incU - oneX;
					vertices[cnt++] = iv * incV - oneY;
					vertices[cnt++] = depth;
				}
			}
			
			//faces
			byte[] indices = new byte[gridU * gridV * 2 * 3];
			cnt = 0;
			for( int iu = 0; iu < gridU; ++ iu ) {
				for( int iv = 0; iv < gridV; ++ iv ) {
					//Triangle A
					indices[cnt++] = (byte)(iu * gridV1 + iv);
					indices[cnt++] = (byte)(iu * gridV1 + (iv+1));
					indices[cnt++] = (byte)((iu+1) * gridV1 + iv);
					
					//Triangle B
					indices[cnt++] = (byte)((iu+1) * gridV1 + (iv+1));
					indices[cnt++] = (byte)((iu+1) * gridV1 + iv);
					indices[cnt++] = (byte)(iu * gridV1 + (iv+1));
				}
			}
						
			//colors
			int[] colors = new int[gridU1 * gridV1 * 4];
			Random rand = new Random();
			for( int i = 0; i < colors.length; i += 4 ) {
				colors[i] = rand.nextInt();
				colors[i+1] = rand.nextInt();
				colors[i+2] = rand.nextInt();
				colors[i+3] = 0x10000;
			}

			mVertexBuffer = createIntBuffer(vertices);
			mColorBuffer = createIntBuffer(colors);
			mIndexBuffer = createByteBuffer(indices);
		}

		public void setX(int x) {
			int d = x - this.x;
			this.x = x;
			
			for( int i = 0; i < mVertexBuffer.capacity(); i += 3 ) {
				mVertexBuffer.put(i, mVertexBuffer.get(i) + d);
			}			
		}
		
		public void setY(int y) {
			int d = y - this.y;
			this.y = y;
			
			for( int i = 1; i < mVertexBuffer.capacity(); i += 3 ) {
				mVertexBuffer.put(i, mVertexBuffer.get(i) +d );
			}
		}
		
		public void setZ(int z) {
			int d = z - this.z;
			this.z = z;
			
			for( int i = 2; i < mVertexBuffer.capacity(); i += 3 ) {
				mVertexBuffer.put(i, mVertexBuffer.get(i) + d);
			}
		}
		
		public void draw(GL10 gl) {
			gl.glFrontFace(GL10.GL_CW);
			gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
			gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
			gl.glDrawElements(GL10.GL_TRIANGLES, mIndexBuffer.capacity(), GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
		}
	}
}