/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cooliris.media; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; /** * A 2D rectangular mesh. Can be drawn textured or untextured. This version is * modified from the original Grid.java (found in the SpriteText package in the * APIDemos Android sample) to support hardware vertex buffers. */ final class GridQuad { private FloatBuffer mVertexBuffer; private FloatBuffer mOverlayTexCoordBuffer; private CharBuffer mIndexBuffer; private int mW; private int mH; private static final int INDEX_COUNT = 4; private static final int ORIENTATION_COUNT = 360; private int mVertBufferIndex; private int mIndexBufferIndex; private int mOverlayTextureCoordBufferIndex; private boolean mDynamicVBO; private float mU; private float mV; private float mAnimU; private float mAnimV; private float mWidth; private float mHeight; private float mAnimWidth; private float mAnimHeight; private boolean mQuadChanged; private float mDefaultAspectRatio; private int mBaseTextureCoordBufferIndex; private FloatBuffer mBaseTexCoordBuffer; private final boolean mOrientedQuad; private MatrixStack mMatrix; private float[] mCoordsIn = new float[4]; private float[] mCoordsOut = new float[4]; public static GridQuad createGridQuad(float width, float height, float xOffset, float yOffset, float uExtents, float vExtents, boolean generateOrientedQuads) { // generateOrientedQuads = false; GridQuad grid = new GridQuad(generateOrientedQuads); grid.mWidth = width; grid.mHeight = height; grid.mAnimWidth = width; grid.mAnimHeight = height; grid.mDefaultAspectRatio = width / height; float widthBy2 = width * 0.5f; float heightBy2 = height * 0.5f; final float v = vExtents; final float u = uExtents; if (!generateOrientedQuads) { grid.set(0, 0, -widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, u, v); grid.set(1, 0, widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, 0.0f, v); grid.set(0, 1, -widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, u, 0.0f); grid.set(1, 1, widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, 0.0f, 0.0f); } else { for (int i = 0; i < ORIENTATION_COUNT; ++i) { grid.set(0, 0, -widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, u, v, true, i); grid.set(1, 0, widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, 0.0f, v, true, i); grid.set(0, 1, -widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, u, 0.0f, true, i); grid.set(1, 1, widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, 0.0f, 0.0f, true, i); } } grid.mU = uExtents; grid.mV = uExtents; return grid; } public GridQuad(boolean generateOrientedQuads) { mOrientedQuad = generateOrientedQuads; if (mOrientedQuad) { mMatrix = new MatrixStack(); mMatrix.glLoadIdentity(); } int vertsAcross = 2; int vertsDown = 2; mW = vertsAcross; mH = vertsDown; int size = vertsAcross * vertsDown; final int FLOAT_SIZE = 4; final int CHAR_SIZE = 2; final int orientationCount = (!generateOrientedQuads) ? 1 : ORIENTATION_COUNT; mVertexBuffer = ByteBuffer.allocateDirect(FLOAT_SIZE * size * 3 * orientationCount).order(ByteOrder.nativeOrder()) .asFloatBuffer(); mOverlayTexCoordBuffer = ByteBuffer.allocateDirect(FLOAT_SIZE * size * 2 * orientationCount).order(ByteOrder.nativeOrder()) .asFloatBuffer(); mBaseTexCoordBuffer = ByteBuffer.allocateDirect(FLOAT_SIZE * size * 2 * orientationCount).order(ByteOrder.nativeOrder()) .asFloatBuffer(); int indexCount = INDEX_COUNT; // using tristrips mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount * orientationCount).order(ByteOrder.nativeOrder()) .asCharBuffer(); /* * Initialize triangle list mesh. * * [0]-----[ 1] ... | / | | / | | / | [w]-----[w+1] ... | | */ CharBuffer buffer = mIndexBuffer; for (int i = 0; i < INDEX_COUNT * orientationCount; ++i) { buffer.put(i, (char) i); } mVertBufferIndex = 0; } public void setDynamic(boolean dynamic) { mDynamicVBO = dynamic; if (mOrientedQuad) { throw new UnsupportedOperationException("Dynamic Quads can't have orientations"); } } public float getWidth() { return mWidth; } public float getHeight() { return mHeight; } public void update(float timeElapsed) { mAnimWidth = FloatUtils.animate(mAnimWidth, mWidth, timeElapsed); mAnimHeight = FloatUtils.animate(mAnimHeight, mHeight, timeElapsed); mAnimU = FloatUtils.animate(mAnimU, mU, timeElapsed); mAnimV = FloatUtils.animate(mAnimV, mV, timeElapsed); recomputeQuad(); } public void commit() { mAnimWidth = mWidth; mAnimHeight = mHeight; mAnimU = mU; mAnimV = mV; } public void recomputeQuad() { mVertexBuffer.clear(); mBaseTexCoordBuffer.clear(); float widthBy2 = mAnimWidth * 0.5f; float heightBy2 = mAnimHeight * 0.5f; float xOffset = 0.0f; float yOffset = 0.0f; float u = mU; float v = mV; set(0, 0, -widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, u, v, false, 0); set(1, 0, widthBy2 + xOffset, -heightBy2 + yOffset, 0.0f, 0.0f, v, false, 0); set(0, 1, -widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, u, 0.0f, false, 0); set(1, 1, widthBy2 + xOffset, heightBy2 + yOffset, 0.0f, 0.0f, 0.0f, false, 0); mQuadChanged = true; } public void resizeQuad(float viewAspect, float u, float v, float imageWidth, float imageHeight) { // given the u,v; we know the aspect ratio of the image // we have to change one of the co-ords depending upon the image and // viewport aspect ratio mU = u; mV = v; float imageAspect = imageWidth / imageHeight; float width = mDefaultAspectRatio; float height = 1.0f; if (viewAspect < 1.0f) { height = height * (mDefaultAspectRatio / imageAspect); float maxHeight = width / viewAspect; if (height > maxHeight) { // we need to reduce the width and height proportionately float ratio = height / maxHeight; height /= ratio; width /= ratio; } } else { width = width * (imageAspect / mDefaultAspectRatio); float maxWidth = height * viewAspect; if (width > maxWidth) { float ratio = width / maxWidth; width /= ratio; height /= ratio; } } mWidth = width; mHeight = height; commit(); recomputeQuad(); } public void set(int i, int j, float x, float y, float z, float u, float v) { set(i, j, x, y, z, u, v, true, 0); } private void set(int i, int j, float x, float y, float z, float u, float v, boolean modifyOverlay, int orientationId) { if (i < 0 || i >= mW) { throw new IllegalArgumentException("i"); } if (j < 0 || j >= mH) { throw new IllegalArgumentException("j"); } int index = orientationId * INDEX_COUNT + mW * j + i; int posIndex = index * 3; mVertexBuffer.put(posIndex, x); mVertexBuffer.put(posIndex + 1, y); mVertexBuffer.put(posIndex + 2, z); int baseTexIndex = index * 2; // we can calculate the u,v for the orientation here MatrixStack matrix = mMatrix; if (matrix != null) { orientationId *= 2; matrix.glLoadIdentity(); matrix.glTranslatef(0.5f, 0.5f, 0.0f); float itheta = (float) Math.toRadians(orientationId); float sini = (float) Math.sin(itheta); float scale = 1.0f + (sini * sini) * 0.33333333f; scale = 1.0f / scale; matrix.glRotatef(-orientationId, 0.0f, 0.0f, 1.0f); matrix.glScalef(scale, scale, 1.0f); matrix.glTranslatef(-0.5f + (float) (sini * 0.125f / scale), -0.5f + (float) (Math.abs(Math.sin(itheta * 0.5f) * 0.25f)), 0.0f); // now we have the desired matrix // populate s,t,r,q // http://glprogramming.com/red/chapter09.html mCoordsIn[0] = u; mCoordsIn[1] = v; mCoordsIn[2] = 0.0f; mCoordsIn[3] = 1.0f; matrix.apply(mCoordsIn, mCoordsOut); u = mCoordsOut[0] / mCoordsOut[3]; v = mCoordsOut[1] / mCoordsOut[3]; } mBaseTexCoordBuffer.put(baseTexIndex, u); mBaseTexCoordBuffer.put(baseTexIndex + 1, v); if (modifyOverlay) { int texIndex = index * 2; mOverlayTexCoordBuffer.put(texIndex, u); mOverlayTexCoordBuffer.put(texIndex + 1, v); } } public void bindArrays(GL10 gl) { GL11 gl11 = (GL11) gl; // draw using hardware buffers gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertBufferIndex); gl11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0); gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mOverlayTextureCoordBufferIndex); if (mDynamicVBO && mQuadChanged) { final int texCoordSize = mOverlayTexCoordBuffer.capacity() * 4; mOverlayTexCoordBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, texCoordSize, mOverlayTexCoordBuffer, GL11.GL_DYNAMIC_DRAW); } gl11.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); gl11.glClientActiveTexture(GL11.GL_TEXTURE1); if (mDynamicVBO && mQuadChanged) { final int texCoordSize = mBaseTexCoordBuffer.capacity() * 4; mBaseTexCoordBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, texCoordSize, mBaseTexCoordBuffer, GL11.GL_DYNAMIC_DRAW); } gl11.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); gl11.glClientActiveTexture(GL11.GL_TEXTURE0); if (mDynamicVBO && mQuadChanged) { mQuadChanged = false; gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertBufferIndex); final int vertexSize = mVertexBuffer.capacity() * 4; mVertexBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexSize, mVertexBuffer, GL11.GL_DYNAMIC_DRAW); } gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBufferIndex); } public static final void draw(GL11 gl11, float orientationDegrees) { // don't call this method unless bindArrays was called int orientation = (int) Shared.normalizePositive(orientationDegrees); gl11.glDrawElements(GL11.GL_TRIANGLE_STRIP, INDEX_COUNT, GL11.GL_UNSIGNED_SHORT, orientation * INDEX_COUNT); } public void unbindArrays(GL10 gl) { GL11 gl11 = (GL11) gl; gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0); gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0); } public boolean usingHardwareBuffers() { return mVertBufferIndex != 0; } /** * When the OpenGL ES device is lost, GL handles become invalidated. In that * case, we just want to "forget" the old handles (without explicitly * deleting them) and make new ones. */ public void forgetHardwareBuffers() { mVertBufferIndex = 0; mIndexBufferIndex = 0; mOverlayTextureCoordBufferIndex = 0; } /** * Deletes the hardware buffers allocated by this object (if any). */ public void freeHardwareBuffers(GL10 gl) { if (mVertBufferIndex != 0) { if (gl instanceof GL11) { GL11 gl11 = (GL11) gl; int[] buffer = new int[1]; buffer[0] = mVertBufferIndex; gl11.glDeleteBuffers(1, buffer, 0); buffer[0] = mOverlayTextureCoordBufferIndex; gl11.glDeleteBuffers(1, buffer, 0); buffer[0] = mIndexBufferIndex; gl11.glDeleteBuffers(1, buffer, 0); } forgetHardwareBuffers(); } } /** * Allocates hardware buffers on the graphics card and fills them with data * if a buffer has not already been previously allocated. Note that this * function uses the GL_OES_vertex_buffer_object extension, which is not * guaranteed to be supported on every device. * * @param gl * A pointer to the OpenGL ES context. */ public void generateHardwareBuffers(GL10 gl) { if (mVertBufferIndex == 0) { if (gl instanceof GL11) { GL11 gl11 = (GL11) gl; int[] buffer = new int[1]; // Allocate and fill the vertex buffer. gl11.glGenBuffers(1, buffer, 0); mVertBufferIndex = buffer[0]; gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertBufferIndex); final int vertexSize = mVertexBuffer.capacity() * 4; int bufferType = (mDynamicVBO) ? GL11.GL_DYNAMIC_DRAW : GL11.GL_STATIC_DRAW; mVertexBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexSize, mVertexBuffer, bufferType); // Allocate and fill the texture coordinate buffer. gl11.glGenBuffers(1, buffer, 0); mOverlayTextureCoordBufferIndex = buffer[0]; gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mOverlayTextureCoordBufferIndex); final int texCoordSize = mOverlayTexCoordBuffer.capacity() * 4; mOverlayTexCoordBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, texCoordSize, mOverlayTexCoordBuffer, bufferType); gl11.glGenBuffers(1, buffer, 0); mBaseTextureCoordBufferIndex = buffer[0]; gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBaseTextureCoordBufferIndex); mBaseTexCoordBuffer.position(0); gl11.glBufferData(GL11.GL_ARRAY_BUFFER, texCoordSize, mBaseTexCoordBuffer, bufferType); // Unbind the array buffer. gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0); // Allocate and fill the index buffer. gl11.glGenBuffers(1, buffer, 0); mIndexBufferIndex = buffer[0]; gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBufferIndex); // A char is 2 bytes. final int indexSize = mIndexBuffer.capacity() * 2; mIndexBuffer.position(0); gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, indexSize, mIndexBuffer, GL11.GL_STATIC_DRAW); // Unbind the element array buffer. gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0); } } } }