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); } }