AndroidのOpenGL ESで地球をこの手に

というわけで球体へのテクスチャマッピングができました。
それに伴ってSphereの頂点の決定法も変更。ほぼPlaneと同じ感じになりました。
つまりSphereクラスのcreateメソッドの中身をごそっと変えた。


作ってて頂点数が256以上になるとおかしくなることに気づいた。
これはindicesがバイト配列だから。とりあえず頂点数を256未満に押さえることでごまかしてみた。


というわけでデモは地球をマッピングしてみたところ。
世界地図はGoogleの地図のをスクリーンキャプチャした。
でもなぜか違和感を感じるのはなんでだろうか。


あとはテクスチャ周りをすっきりさせたい。
SphereとTextureSphereを分けるのも微妙だし。


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

ソース

package net.swelt.android.earth;


import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.OpenGLContext;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.nio.IntBuffer;
import java.util.Vector;

import javax.microedition.khronos.opengles.GL10;

public class Earth 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;
						
			Bitmap bp = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.merca);
			int texId = createTexture(bp);
			
			object = new TextureSphere(0x10000 << 1, 15, 15, texId);			
			m3DObjects.add(object);			
			
			bp = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.kirby);
			texId = createTexture(bp);
			
			object = new TextureSphere(0x10000 << 1, 15, 15, texId);
			m3DObjects.add(object);
			object.setVisibility(false);
		}
		
		public int createTexture(Bitmap bp) {
			GL10 gl = (GL10) mGLContext.getGL();
			
			int w = bp.width();
			int h = bp.height();
			int texid;
			int[] a = new int [1];
			int col, red, blue;
			
			gl.glGenTextures(1, a, 0);
			texid = a[0];
			
			a = new int[w * h];
			for( int i = 0; i < a.length; ++ i ) {
				col = bp.getPixel((i % w), (i / h));
				red = col & 0xff;
				blue = (col >> 16) & 0xff;
				col = col & 0xff00ff00; 
				col = col | (red << 16);
				col = col | blue;
				a[i] = col;
			}
			
			IntBuffer ib = GLUtil.createIntBuffer(a);

			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
			
			gl.glBindTexture(GL10.GL_TEXTURE_2D, texid);
			gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, w, h, 0,
					GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
			
			return texid;
		}
		
		@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) {
			
			draw3D(canvas);			
			draw2D(canvas);
			
		}
		
		private void draw3D(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);			
			
			mAngle += 1.2f;
			
			mGLContext.waitGL();
		}
		
		private void draw2D(Canvas canvas) {
		}
		
		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(0, 0, 0, 0);
			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.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
			
			gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE);
			
			gl.glCullFace(GL10.GL_BACK);
			gl.glEnable(GL10.GL_CULL_FACE);
		}
		
		private void drawObjects(GL10 gl) {
			for( int i = 0; i < m3DObjects.size(); ++ i ) {
				if( !m3DObjects.elementAt(i).getVisibility() ) continue;
				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 ) {
				if( !m3DObjects.elementAt(i).getVisibility() ) continue;				
				m3DObjects.elementAt(i).draw(gl);

				if( !m3DObjects.elementAt(i).getPicking() ) continue;
				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 ) {
				m3DObjects.elementAt(hitIndex).setVisibility(false);
				m3DObjects.elementAt(hitIndex == 0 ? 1 : 0).setVisibility(true);
			}			
		}

		@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;
				break;
			}
			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;
				}
			}
		};
	}
}
package net.swelt.android.earth;

import java.util.Random;

import javax.microedition.khronos.opengles.GL10;

public class Sphere extends DisplayObject3D {
	private int mRadius;
	
	public Sphere(int radius, int segW, int segH) {
		mRadius = radius;
		create(segW, segH);
	}
	
	private void create(int segW, int segH) {
		int gridU = segW;
		int gridV = segH;
		int gridU1 = gridU + 1;
		int gridV1 = gridV + 1;
		int incU = 360 / gridU;
		int incV = 2 * mRadius / gridV;
							
		//vertices
		int[] vertices = new int[gridU1 * gridV1 * 3];
		int cnt = 0;
		double y, r, t, d;
		d = mRadius;
		
		for( int iv = 0; iv < gridV1; ++ iv ) {
			y = iv * incV - d;			
			r = Math.sqrt(d * d - y * y);
			for( int iu = 0; iu < gridU1; ++ iu ) {
				if( iv == 0 || iv == gridV1-1 ) {
					vertices[cnt++] = 0;
					vertices[cnt++] = iv == 0 ? -mRadius : mRadius;
					vertices[cnt++] = 0;
				} else {
					t = iu * incU * Math.PI / 180; 
					vertices[cnt++] = (int)(r * Math.cos(t));
					vertices[cnt++] = (int)y;
					vertices[cnt++] = (int)(r * Math.sin(t));
				}
			}
		}
		
		//faces
		byte[] indices = new byte[gridU * (gridV-1) * 2 * 3];
		cnt = 0;
		for( int iv = 0; iv < gridV; ++ iv ) {
			for( int iu = 0; iu < gridU; ++ iu ) {
				if( iv != 0 ) {
					//Triangle A
					indices[cnt++] = (byte)(iu + iv * gridU1);
					indices[cnt++] = (byte)(iu + (iv+1) * gridU1);
					indices[cnt++] = (byte)((iu+1)  + iv * gridU1);
				}
				if( iv != gridV-1 ) {
					//Triangle B
					indices[cnt++] = (byte)(((iu+1) ) + (iv+1) * gridU1);
					indices[cnt++] = (byte)(((iu+1) ) + iv * gridU1);
					indices[cnt++] = (byte)(iu + (iv+1) * gridU1);
				}
			}
		}

		//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 = GLUtil.createIntBuffer(vertices);
		mColorBuffer = GLUtil.createIntBuffer(colors);
		mIndexBuffer = GLUtil.createByteBuffer(indices);
	}
	public void draw(GL10 gl) {
		gl.glDisable(GL10.GL_CULL_FACE);
		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);
		gl.glEnable(GL10.GL_CULL_FACE);
	}
}
package net.swelt.android.earth;

import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class TextureSphere extends Sphere {
	private FloatBuffer mTextureBuffer;
	private int texId;
		
	public TextureSphere(int radius, int segW, int segH, int texId) {
		super(radius, segW, segH);
		
		this.texId = texId;

		int gridU = segW;
		int gridV = segH;
		int gridU1 = gridU + 1;
		int gridV1 = gridV + 1;
		int size = gridU1 * gridV1;
		float incU = 1.0f / gridU;
		float incV = 1.0f / gridV;
		float[] textures = new float[size * 2];
		
		for( int i = 0; i < textures.length; i += 2 ) {
			int n = i / 2;
			textures[i] = incU * (n % gridU1);
			textures[i+1] = 1.0f - incV * (n / gridU1);
		}	

		mTextureBuffer = GLUtil.createFloatBuffer(textures);
	}

	public void draw(GL10 gl) {
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
				
		gl.glColor4f(1, 1, 1, 1);
		gl.glFrontFace(GL10.GL_CW);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texId);
		gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
		gl.glDrawElements(GL10.GL_TRIANGLES, mIndexBuffer.capacity(), GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
		
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glDisable(GL10.GL_TEXTURE_2D);
	}
		
}