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