/*
* Sprite.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* 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.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.openglspritegamedemo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import net.rim.device.api.animation.Animation;
import net.rim.device.api.math.BoundingBox;
import net.rim.device.api.math.BoundingSphere;
import net.rim.device.api.math.Bounds;
import net.rim.device.api.math.Matrix3f;
import net.rim.device.api.math.Matrix4f;
import net.rim.device.api.math.Transform3D;
import net.rim.device.api.math.Vector3f;
import net.rim.device.api.opengles.GL20;
import net.rim.device.api.opengles.GLUtils;
import net.rim.device.api.system.Bitmap;
/**
* Represents a basic model for the sample application (with a 3D textured quad
* mesh, local transformation and bounds).
*/
public class Sprite {
/** The default vertex positions for a model (a textured quad) */
public static final float[] VERTICES = { -1.0f, -1.0f, 0.0f, +1.0f, -1.0f,
0.0f, +1.0f, +1.0f, 0.0f, -1.0f, +1.0f, 0.0f };
/** The default vertex normals for a model (a textured quad) */
public static final float[] NORMALS = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f };
/** The default vertex texture coordinates for a model (a textured quad) */
public static final float[] TEXCOORDS = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 0.0f };
/** The default indices for a model (a textured quad) */
public static final byte[] INDICES = { 0, 1, 2, 2, 3, 0 };
/** The offset of the vertex positions within the vertex data */
protected static final int VERTICES_OFFSET = 0;
/** The offset of the vertex normals within the vertex data */
protected static final int NORMALS_OFFSET = VERTICES.length * 4;
/** The offset of the vertex texture coordinates within the vertex data */
protected static final int TEXCOORDS_OFFSET =
(VERTICES.length + NORMALS.length) * 4;
/**
* Holds all of the OpenGL texture handles (used so that textures are only
* loaded once).
*/
private static Hashtable _textureHandles = new Hashtable();
/** Holds the path to the texture used to skin this model */
private final String _textureString;
/** Holds whether the model is rendered using a batch */
protected boolean _isBatchedRender = false;
/** Holds the model's batch */
protected Batch _batch;
/** Holds the model's vertex texture coordinates */
protected float[] _texcoords;
/**
* Holds handles to the vertex (_buffers[0]) and index (_buffers[1])
* buffers.
*/
protected int[] _buffers;
/** Holds the handle to the model's texture */
protected int _texture;
/** Holds the model's local transformation */
protected Transform3D _transform;
/** Holds the model's original bounds */
protected Bounds _originalBounds;
/** Holds the model's current bounds */
protected Bounds _bounds;
/** Holds the shader program used to render */
private static int _program;
/** Holds the location of the modelview matrix uniform variable */
private static int _mLoc;
/** Holds the location of the projection matrix uniform variable */
private static int _pLoc;
/** Holds the location of the normal matrix uniform variable */
private static int _nLoc;
/** Holds the location of the texture uniform variable */
private static int _tLoc;
/** Holds all the animations targeting this sprite */
private final Vector _animations;
/**
* Holds the inverse transpose of the modelview matrix (used to calculate
* the matrix used to transform vertex normals).
*/
private static Matrix4f _mti;
/** Holds the matrix used to transform vertex normals. */
private static Matrix3f _normalMatrix;
/** Holds the translation vector representing the sprite's initial position */
Vector3f _initialPosition;
/**
* Constructs a new Sprite object
*
* @param texture
* The filename of the mesh's texture
*/
public Sprite(final String texture) {
this(TEXCOORDS, texture, null, new Vector3f(0.0f, 0.0f, 0.0f));
}
/**
* Creates a new Sprite object
*
* @param texture
* The filename of the mesh's texture
* @param position
* The Vector3f holding the position of the Sprite
*/
public Sprite(final String texture, final Vector3f position) {
this(TEXCOORDS, texture, null, position);
}
/**
* Creates a new Sprite object
*
* @param texture
* The filename of the mesh's texture
* @param bounds
* The mesh's bounds
*/
public Sprite(final String texture, final Bounds bounds,
final Vector3f position) {
this(TEXCOORDS, texture, bounds, position);
}
/**
* Creates a new Sprite object
*
* @param texcoords
* The mesh's texture coordinates
* @param texture
* The filename of the mesh's texture
* @param bounds
* The mesh's bounds
* @param position
* The Vector3f holding the position of the Sprite
*/
public Sprite(final float[] texcoords, final String texture,
final Bounds bounds, final Vector3f position) {
// Create a new BoundingBox if the Sprite doesn't have one yet
if (bounds == null) {
final Vector3f[] vertices = new Vector3f[VERTICES.length / 3];
for (int i = 0; i < vertices.length; i++) {
vertices[i] =
new Vector3f(VERTICES[i * 3], VERTICES[i * 3 + 1],
VERTICES[i * 3 + 2]);
}
_originalBounds = new BoundingBox(vertices);
_bounds = new BoundingBox((BoundingBox) _originalBounds);
} else {
// Determine if the sprite's bounds should be a box or a sphere
if (bounds instanceof BoundingBox) {
_originalBounds = new BoundingBox((BoundingBox) bounds);
_bounds = new BoundingBox((BoundingBox) bounds);
} else {
_originalBounds = new BoundingSphere((BoundingSphere) bounds);
_bounds = new BoundingSphere((BoundingSphere) bounds);
}
}
_texcoords = texcoords;
_textureString = texture;
_animations = new Vector();
_mti = new Matrix4f();
_normalMatrix = new Matrix3f();
_transform = new Transform3D();
// Set the initial position of the sprite
setInitialPosition(position);
}
/**
* Initializes the sprite using OpenGL v1.1
*
* @param gl
* The OpenGL v1.1 object
*/
public void initialize(final GL11 gl) {
_texture = getTexture(gl);
startAnimations();
}
/**
* Initializes the sprite using OpenGL v2.0
*
* @param gl
* The OpenGL v2.0 object
*/
public void initialize(final GL20 gl) {
_texture = getTexture(gl);
startAnimations();
}
/**
* Sets the initial position of the sprite
*
* @param position
* The Vector3f holding the position of the Sprite
*/
public void setInitialPosition(final Vector3f position) {
_initialPosition = position;
_transform.setTranslation(position);
}
/**
* Gets this model's updated bounds
*
* @return The current bounds
*/
public Bounds getBounds() {
_transform.transformBounds(_originalBounds, _bounds);
return _bounds;
}
/**
* Sets whether this model is batched or not
*
* @param gl
* The reference to the GL
* @param isBatched
* Whether the model should be batched or not
*/
public void setIsBatched(final GL11 gl, final boolean isBatched) {
_isBatchedRender = isBatched;
if (_isBatchedRender && _batch == null) {
// Pre-transform the vertices before adding them to the batch
final Vector3f v = new Vector3f();
final float[] vertexFloatData = new float[VERTICES.length];
for (int i = 0; i < VERTICES.length / 3; i++) {
v.set(VERTICES[i * 3], VERTICES[i * 3 + 1], VERTICES[i * 3 + 2]);
_transform.transformPoint(v);
vertexFloatData[i * 3] = v.x;
vertexFloatData[i * 3 + 1] = v.y;
vertexFloatData[i * 3 + 2] = v.z;
}
_batch =
Batch.getBatch(gl, vertexFloatData, NORMALS, _texcoords,
INDICES, _texture);
}
}
/**
* Sets whether this model is batched or not
*
* @param gl
* The reference to the GL
* @param isBatched
* Whether the model should be batched or not
*/
public void setIsBatched(final GL20 gl, final boolean isBatched) {
_isBatchedRender = isBatched;
if (_isBatchedRender && _batch == null) {
// Pre-transform the vertices before adding them to the batch
final Vector3f v = new Vector3f();
final float[] vertexFloatData = new float[VERTICES.length];
for (int i = 0; i < VERTICES.length / 3; i++) {
v.set(VERTICES[i * 3], VERTICES[i * 3 + 1], VERTICES[i * 3 + 2]);
_transform.transformPoint(v);
vertexFloatData[i * 3] = v.x;
vertexFloatData[i * 3 + 1] = v.y;
vertexFloatData[i * 3 + 2] = v.z;
}
_batch =
Batch.getBatch(gl, vertexFloatData, NORMALS, _texcoords,
INDICES, _texture);
}
}
/**
* Renders this model with OpenGL v1.1
*
* @param gl
* The reference to the OpenGL v1.1 object used for rendering
*/
public void render(final GL11 gl) {
if (_isBatchedRender) {
_batch.render(gl);
} else {
if (_buffers == null) {
final int vertexDataSize =
(VERTICES.length + NORMALS.length + _texcoords.length) * 4;
// Create the buffer for the vertex data.
FloatBuffer vertexData;
vertexData =
ByteBuffer.allocateDirect(vertexDataSize)
.asFloatBuffer();
vertexData.put(VERTICES);
vertexData.put(NORMALS);
vertexData.put(_texcoords);
vertexData.rewind();
ByteBuffer indices;
indices = ByteBuffer.allocateDirect(INDICES.length);
indices.put(INDICES);
indices.rewind();
// Create the vertex and index buffers for the model's geometry
_buffers = new int[2];
gl.glGenBuffers(2, _buffers, 0);
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexDataSize,
vertexData, GL11.GL_STATIC_DRAW);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, INDICES.length,
indices, GL11.GL_STATIC_DRAW);
}
// Save the old model view matrix
gl.glPushMatrix();
// Apply the local transformation
gl.glMultMatrixf(_transform.getMatrix().getArray(), 0);
// Bind the vertex and index buffers
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
// Enable the arrays for the VBO
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Set the pointers for the vertex buffer data.
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VERTICES_OFFSET);
gl.glNormalPointer(GL10.GL_FLOAT, 0, NORMALS_OFFSET);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, TEXCOORDS_OFFSET);
// Bind the mesh's texture
gl.glBindTexture(GL10.GL_TEXTURE_2D, _texture);
// Draw the mesh's geometry
gl.glDrawElements(GL10.GL_TRIANGLES, INDICES.length,
GL10.GL_UNSIGNED_BYTE, 0);
// Restore the old model view matrix
gl.glPopMatrix();
}
}
/**
* Renders this model using OpenGL v2.0
*
* @param gl
* The reference to the OpenGL v2.0 object used for rendering
*/
public void render(final GL20 gl, final Matrix4f modelview) {
if (_isBatchedRender) {
_batch.render(gl, modelview);
} else {
if (_buffers == null) {
final int vertexDataSize =
(VERTICES.length + NORMALS.length + _texcoords.length) * 4;
// Create the buffer for the vertex data
FloatBuffer vertexData;
vertexData =
ByteBuffer.allocateDirect(vertexDataSize)
.asFloatBuffer();
vertexData.put(VERTICES);
vertexData.put(NORMALS);
vertexData.put(_texcoords);
vertexData.rewind();
ByteBuffer indices;
indices = ByteBuffer.allocateDirect(INDICES.length);
indices.put(INDICES);
indices.rewind();
// Create the vertex and index buffers for the model's geometry
_buffers = new int[2];
gl.glGenBuffers(2, _buffers, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, vertexDataSize,
vertexData, GL20.GL_STATIC_DRAW);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, INDICES.length,
indices, GL20.GL_STATIC_DRAW);
}
// Save the old model view matrix
final Matrix4f m = new Matrix4f(modelview);
// Apply the local transformation
m.multiply(_transform.getMatrix());
// Bind the vertex and index buffers
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
// Set the pointers for the vertex buffer data
gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0,
VERTICES_OFFSET);
gl.glVertexAttribPointer(1, 3, GL20.GL_FLOAT, false, 0,
NORMALS_OFFSET);
gl.glVertexAttribPointer(2, 2, GL20.GL_FLOAT, false, 0,
TEXCOORDS_OFFSET);
// Enable the generic vertex attribute arrays
gl.glEnableVertexAttribArray(0);
gl.glEnableVertexAttribArray(1);
gl.glEnableVertexAttribArray(2);
// Load the modelview and normal matrices used for rendering
gl.glUniformMatrix4fv(_mLoc, 1, false, m.getArray(), 0);
gl.glUniformMatrix3fv(_nLoc, 1, false, getNormalMatrix(m), 0);
// Set up the mesh's texture for rendering
gl.glBindTexture(GL20.GL_TEXTURE_2D, _texture);
gl.glUniform1i(_tLoc, 0);
// Draw the mesh's geometry
gl.glDrawElements(GL20.GL_TRIANGLES, INDICES.length,
GL20.GL_UNSIGNED_BYTE, 0);
}
}
/**
* Resets the model to its initial state for starting the level
*/
public void reset() {
_transform.setTranslation(_initialPosition);
}
/**
* Adds an animation to the sprite
*
* @param animation
* The animation to add to the sprite
*/
public void addAnimation(final Animation animation) {
_animations.addElement(animation);
}
/**
* Starts all the animations for the sprite
*/
public void startAnimations() {
final int count = _animations.size();
// Go through all the sprite's animations and start them
for (int i = 0; i < count; i++) {
((Animation) _animations.elementAt(i)).begin(0);
}
}
/**
* Gets the OpenGL v1.1 handle for the given texture
*
* @param gl
* The reference to the OpenGL v1.1
*/
private int getTexture(final GL11 gl) {
// Load the texture if it has not already been loaded
if (!_textureHandles.containsKey(_textureString)) {
final Bitmap bitmap = Bitmap.getBitmapResource(_textureString);
final int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
final Integer textureHandle = new Integer(textures[0]);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureHandle.intValue());
GLUtils.glTexImage2D(gl, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE,
bitmap, null);
final int error = gl.glGetError();
if (error != GL10.GL_NO_ERROR) {
throw new RuntimeException(
"Failed to load GL texture with error " + error + ".");
}
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
_textureHandles.put(_textureString, textureHandle);
return textureHandle.intValue();
} else {
final Integer textureHandle =
(Integer) _textureHandles.get(_textureString);
return textureHandle.intValue();
}
}
/**
* Gets the OpenGL v2.0 handle for the given texture
*
* @param gl
* The reference to the OpenGL v2.0
*/
private int getTexture(final GL20 gl) {
// Load the texture if it has not already been loaded
if (!_textureHandles.containsKey(_textureString)) {
final Bitmap bitmap = Bitmap.getBitmapResource(_textureString);
final int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
final Integer textureHandle = new Integer(textures[0]);
gl.glBindTexture(GL20.GL_TEXTURE_2D, textureHandle.intValue());
GLUtils.glTexImage2D(gl, 0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE,
bitmap, null);
final int error = gl.glGetError();
if (error != GL20.GL_NO_ERROR) {
throw new RuntimeException(
"Failed to load GL texture with error " + error + ".");
}
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER,
GL20.GL_LINEAR);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER,
GL20.GL_LINEAR);
_textureHandles.put(_textureString, textureHandle);
return textureHandle.intValue();
} else {
final Integer textureHandle =
(Integer) _textureHandles.get(_textureString);
return textureHandle.intValue();
}
}
/**
* Loads the shader used to render models in the application
*
* @param gl
* The OpenGL v2.0 used for rendering
*/
static void loadShader(final GL20 gl) {
final StringBuffer vShaderStr = new StringBuffer();
vShaderStr.append("uniform mat3 normalMatrix;\n");
vShaderStr.append("uniform mat4 modelview;\n");
vShaderStr.append("uniform mat4 projection;\n");
vShaderStr.append("attribute vec4 position;\n");
vShaderStr.append("attribute vec3 normal;\n");
vShaderStr.append("attribute vec2 texcoord;\n");
vShaderStr.append("varying vec3 lightDir, vnormal;\n");
vShaderStr.append("varying vec2 vtexcoord;\n");
vShaderStr.append("void main()\n");
vShaderStr.append("{\n");
vShaderStr.append(" lightDir = normalize(vec3(1.0, 0.0, 1.0));\n");
vShaderStr.append(" vnormal = normalMatrix * normal;\n");
vShaderStr
.append(" gl_Position = projection * modelview * position;\n");
vShaderStr.append(" vtexcoord = texcoord;\n");
vShaderStr.append("}\n");
final StringBuffer fShaderStr = new StringBuffer();
fShaderStr.append("precision mediump float;\n");
fShaderStr.append("varying vec3 lightDir, vnormal;\n");
fShaderStr.append("varying vec2 vtexcoord;\n");
fShaderStr.append("uniform sampler2D texture;\n");
fShaderStr.append("void main()\n");
fShaderStr.append("{\n");
fShaderStr.append(" vec4 color = texture2D(texture, vtexcoord);\n");
fShaderStr.append(" vec3 n = normalize(vnormal);\n");
fShaderStr.append(" gl_FragColor = color;\n");
fShaderStr.append("}\n");
// Load the vertex and fragment shaders
final String[] infolog = new String[2];
final int vertexShader =
GLUtils.glLoadShader(gl, GL20.GL_VERTEX_SHADER, vShaderStr
.toString(), infolog, 0);
final int fragmentShader =
GLUtils.glLoadShader(gl, GL20.GL_FRAGMENT_SHADER, fShaderStr
.toString(), infolog, 1);
if (vertexShader == 0) {
throw new RuntimeException("Vertex shader compile error. "
+ infolog[0]);
}
if (fragmentShader == 0) {
throw new RuntimeException("Fragment shader compile error. "
+ infolog[1]);
}
// Create the program object
_program = gl.glCreateProgram();
if (_program == 0) {
return;
}
// Attach the shaders
gl.glAttachShader(_program, vertexShader);
gl.glAttachShader(_program, fragmentShader);
// Bind attributes
gl.glBindAttribLocation(_program, 0, "position");
gl.glBindAttribLocation(_program, 1, "normal");
gl.glBindAttribLocation(_program, 2, "texcoord");
// Link the program
gl.glLinkProgram(_program);
// Check the link status
final int[] linked = new int[1];
gl.glGetProgramiv(_program, GL20.GL_LINK_STATUS, linked, 0);
if (linked[0] == GL20.GL_FALSE) {
final String log = gl.glGetProgramInfoLog(_program);
gl.glDeleteProgram(_program);
throw new RuntimeException("Shader link error. " + log);
}
_mLoc = gl.glGetUniformLocation(_program, "modelview");
_pLoc = gl.glGetUniformLocation(_program, "projection");
_nLoc = gl.glGetUniformLocation(_program, "normalMatrix");
_tLoc = gl.glGetUniformLocation(_program, "texture");
}
/**
* Retrieves the shader program handle
*
* @return The shader program handle
*/
public static int getProgram() {
return _program;
}
/**
* Retrieves the location of the projection matrix uniform variable
*
* @return The location of the projection matrix uniform
*/
public static int getProjectionMatrixLocation() {
return _pLoc;
}
/**
* Calculates the normal matrix from the given modelview matrix and returns
* the normal matrix as a float array.
*
* @param modelview
* The modelview matrix
* @return A float array representing the 3x3 matrix used to transform
* vertex normals
*/
private static float[] getNormalMatrix(final Matrix4f modelview) {
_mti.set(modelview);
_mti.invert();
_mti.transpose();
_normalMatrix.set(_mti.get(0, 0), _mti.get(0, 1), _mti.get(0, 2), _mti
.get(1, 0), _mti.get(1, 1), _mti.get(1, 2), _mti.get(2, 0),
_mti.get(2, 1), _mti.get(2, 2));
return _normalMatrix.getArray();
}
/**
* Represents a render batch that uses a single texture
*
*/
public static final class Batch {
/**
* Encapsulates a float array
*/
private static final class FloatArray {
public float[] array;
public FloatArray(final float[] array) {
this.array = new float[array.length];
System.arraycopy(array, 0, this.array, 0, array.length);
}
}
/**
* Encapsulates a byte array
*/
private static final class ByteArray {
public ByteArray(final byte[] array) {
this.array = new byte[array.length];
System.arraycopy(array, 0, this.array, 0, array.length);
}
public byte[] array;
}
/** Holds the render batches */
private static Hashtable _batches = new Hashtable();
/** Holds the vertex positions */
private final Vector _positions = new Vector();
/** Holds the vertex normals */
private final Vector _normals = new Vector();
/** Holds the vertex texture coordinates */
private final Vector _texcoords = new Vector();
/** Holds the indices into the vertex data */
private final Vector _indices = new Vector();
/** Holds the actual vertex data used by the GL */
private FloatBuffer _vertexData = null;
/** Holds the actual index data used by the GL */
private ByteBuffer _indexData = null;
/**
* Holds the offset into the vertex data where new data can be appended
*/
private int _vertexDataOffset;
/** Holds the offset into the index data where new data can be appended */
private int _indexDataOffset;
/** The offset of the vertex positions within the vertex data */
private int _positionsOffset;
/** The offset of the vertex normals within the vertex data */
private int _normalsOffset;
/** The offset of the texture coordinates within the vertex data */
private int _texcoordsOffset;
/**
* Holds handles to the vertex (_buffers[0]) and index (_buffers[1])
* buffers.
*/
private final int _buffers[] = new int[2];
/** Holds the OpenGL handle to the texture object */
private int _texture = 0;
/** Holds the number of meshes held by this batch */
private int _meshCount = 0;
/**
* Holds the index of the current mesh being "rendered" by the batch
* (this is really just the number of meshes that have called render for
* a particular frame.
*/
private int _meshRenderIndex = 0;
/**
* Constructs a render batch that using the given texture
*
* @param gl
* The reference to the OpenGL v1.1 object
* @param texture
* The texture's OpenGL handle
*/
public Batch(final GL11 gl, final int texture) {
// Store the texture handle
_texture = texture;
// Create the vertex and index buffers
gl.glGenBuffers(2, _buffers, 0);
_vertexDataOffset = 0;
_indexDataOffset = 0;
_positionsOffset = 0;
_normalsOffset = 0;
_texcoordsOffset = 0;
}
/**
* Constructs a render batch that using the given texture
*
* @param gl
* The reference to the OpenGL v2.0 object
* @param texture
* The texture's OpenGL handle
*/
public Batch(final GL20 gl, final int texture) {
// Store the texture handle
_texture = texture;
// Create the vertex and index buffers
gl.glGenBuffers(2, _buffers, 0);
_vertexDataOffset = 0;
_indexDataOffset = 0;
_positionsOffset = 0;
_normalsOffset = 0;
_texcoordsOffset = 0;
}
/**
* Clears the batch
*
* @param gl
* The reference to the OpenGL v1.1 object
*/
public static void clearBatch(final GL11 gl) {
final Enumeration e = _batches.elements();
int[] buffers;
// Go through everything in the batch and remove it
while (e.hasMoreElements()) {
buffers = ((Batch[]) e.nextElement())[0]._buffers;
gl.glDeleteBuffers(2, buffers, 0);
}
_batches.clear();
}
/**
* Clears the batch
*
* @param gl
* The reference to the OpenGL v2.0 object
*/
public static void clearBatch(final GL20 gl) {
final Enumeration e = _batches.elements();
int[] buffers;
// Go through everything in the batch and remove it
while (e.hasMoreElements()) {
buffers = ((Batch[]) e.nextElement())[0]._buffers;
gl.glDeleteBuffers(2, buffers, 0);
}
_batches.clear();
}
/**
* Renders the batch using OpenGL v1.1
*
* @param gl
* The reference to the OpenGl v1.1 object
*/
public void render(final GL11 gl) {
// Check if all the batched meshes have called render()
// (meaning we should actually draw them all now).
if (_meshRenderIndex == _meshCount - 1) {
if (_vertexData == null) {
// Update the direct byte buffers
_vertexData =
ByteBuffer.allocateDirect(_vertexDataOffset)
.asFloatBuffer();
for (int i = 0; i < _positions.size(); i++) {
_vertexData
.put(((FloatArray) _positions.elementAt(i)).array);
}
for (int i = 0; i < _normals.size(); i++) {
_vertexData
.put(((FloatArray) _normals.elementAt(i)).array);
}
for (int i = 0; i < _texcoords.size(); i++) {
_vertexData
.put(((FloatArray) _texcoords.elementAt(i)).array);
}
_vertexData.rewind();
_indexData = ByteBuffer.allocateDirect(_indexDataOffset);
for (int i = 0; i < _indices.size(); i++) {
_indexData
.put(((ByteArray) _indices.elementAt(i)).array);
}
_indexData.rewind();
// Update the buffers in the GL
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBufferData(GL11.GL_ARRAY_BUFFER, _vertexDataOffset,
_vertexData, GL11.GL_STATIC_DRAW);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER,
_indexDataOffset, _indexData, GL11.GL_STATIC_DRAW);
}
// Save the old model view matrix and clear the current matrix
gl.glPushMatrix();
// Bind the vertex and index buffers
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
// Enable the arrays for the VBO
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Set the pointers for the vertex buffer data
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _positionsOffset);
gl.glNormalPointer(GL10.GL_FLOAT, 0, _normalsOffset);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, _texcoordsOffset);
// Bind the mesh's texture
gl.glBindTexture(GL10.GL_TEXTURE_2D, _texture);
// Draw the batch's geometry
gl.glDrawElements(GL10.GL_TRIANGLES, _indexDataOffset,
GL10.GL_UNSIGNED_BYTE, 0);
// Restore the old model view matrix
gl.glPopMatrix();
// Reset the render index
_meshRenderIndex = 0;
} else {
// Update the render index
_meshRenderIndex++;
}
}
/**
* Renders the batch using OpenGL v2.0
*
* @param gl
* The reference to the OpenGL v2.0 object
* @param modelview
* The modelview matrix
*/
public void render(final GL20 gl, final Matrix4f modelview) {
// Check if all the batched meshes have called render()
// (meaning we should actually draw them all now).
if (_meshRenderIndex == _meshCount - 1) {
if (_vertexData == null) {
// Update the direct byte buffers
_vertexData =
ByteBuffer.allocateDirect(_vertexDataOffset)
.asFloatBuffer();
for (int i = 0; i < _positions.size(); i++) {
_vertexData
.put(((FloatArray) _positions.elementAt(i)).array);
}
for (int i = 0; i < _normals.size(); i++) {
_vertexData
.put(((FloatArray) _normals.elementAt(i)).array);
}
for (int i = 0; i < _texcoords.size(); i++) {
_vertexData
.put(((FloatArray) _texcoords.elementAt(i)).array);
}
_vertexData.rewind();
_indexData = ByteBuffer.allocateDirect(_indexDataOffset);
for (int i = 0; i < _indices.size(); i++) {
_indexData
.put(((ByteArray) _indices.elementAt(i)).array);
}
_indexData.rewind();
// Update the buffers in the GL
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, _vertexDataOffset,
_vertexData, GL20.GL_STATIC_DRAW);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER,
_indexDataOffset, _indexData, GL20.GL_STATIC_DRAW);
}
// Bind the vertex and index buffers
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, _buffers[0]);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
// Set the pointers for the vertex buffer data
gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0,
_positionsOffset);
gl.glVertexAttribPointer(1, 3, GL20.GL_FLOAT, false, 0,
_normalsOffset);
gl.glVertexAttribPointer(2, 2, GL20.GL_FLOAT, false, 0,
_texcoordsOffset);
// Enable the generic vertex attribute arrays
gl.glEnableVertexAttribArray(0);
gl.glEnableVertexAttribArray(1);
gl.glEnableVertexAttribArray(2);
// Load the modelview and normal matrices used for rendering
gl.glUniformMatrix4fv(_mLoc, 1, false, modelview.getArray(), 0);
gl.glUniformMatrix3fv(_nLoc, 1, false, Sprite
.getNormalMatrix(modelview), 0);
// Set up the mesh's texture for rendering
gl.glBindTexture(GL20.GL_TEXTURE_2D, _texture);
gl.glUniform1i(_tLoc, 0);
// Draw the batch's geometry
gl.glDrawElements(GL20.GL_TRIANGLES, _indexDataOffset,
GL20.GL_UNSIGNED_BYTE, 0);
// Reset the render index
_meshRenderIndex = 0;
} else {
// Update the render index
_meshRenderIndex++;
}
}
/**
* Gets the first available batch that corresponds to the given texture
* and can hold the given mesh data, adding the mesh data to the batch
* before returning it.
*
* @param gl
* The reference to the OpenGL v1.1 object
* @param vertices
* The mesh's vertices
* @param normals
* The mesh's vertice normals
* @param texcoords
* The mesh's vertice texture coordinates
* @param indices
* The mesh's indices
* @param texture
* The OpenGL handle of the texture
* @return The batch using the given texture
*/
public static Batch getBatch(final GL11 gl, final float[] vertices,
final float[] normals, final float[] texcoords,
final byte[] indices, final int texture) {
final Integer key = new Integer(texture);
// If a render batch corresponding to the given
// texture does not exist, create it.
if (!_batches.containsKey(key)) {
final Batch[] batches = new Batch[1];
batches[0] = new Batch(gl, texture);
batches[0].addMesh(gl, vertices, normals, texcoords, indices);
_batches.put(key, batches);
return batches[0];
} else {
// Find a batch that can hold the requested vertices
final Batch[] batches = (Batch[]) _batches.get(key);
for (int i = 0; i < batches.length; i++) {
if (!batches[i].isFull(vertices.length)) {
batches[i].addMesh(gl, vertices, normals, texcoords,
indices);
return batches[i];
}
}
// All the batches (for the given texture)
// are full, so add a new batch.
final Batch[] newBatches = new Batch[batches.length + 1];
System.arraycopy(batches, 0, newBatches, 0, batches.length);
newBatches[batches.length] = new Batch(gl, texture);
newBatches[batches.length].addMesh(gl, vertices, normals,
texcoords, indices);
_batches.put(key, newBatches);
return newBatches[batches.length];
}
}
/**
* Gets the first available batch that corresponds to the given texture
* and can hold the given mesh data, adding the mesh data to the batch
* before returning it.
*
* @param gl
* The reference to the OpenGL. v2.0 object
* @param vertices
* The mesh's vertices
* @param normals
* The mesh's vertices normals
* @param texcoords
* The mesh's vertice texture coordinates
* @param indices
* The mesh's indices
* @param texture
* The OpenGL handle of the texture
* @return The batch using the given texture
*/
public static Batch getBatch(final GL20 gl, final float[] vertices,
final float[] normals, final float[] texcoords,
final byte[] indices, final int texture) {
final Integer key = new Integer(texture);
// If a render batch corresponding to the given
// texture does not exist, create it.
if (!_batches.containsKey(key)) {
final Batch[] batches = new Batch[1];
batches[0] = new Batch(gl, texture);
batches[0].addMesh(gl, vertices, normals, texcoords, indices);
_batches.put(key, batches);
return batches[0];
} else {
// Find a batch that can hold the requested vertices
final Batch[] batches = (Batch[]) _batches.get(key);
for (int i = 0; i < batches.length; i++) {
if (!batches[i].isFull(vertices.length)) {
batches[i].addMesh(gl, vertices, normals, texcoords,
indices);
return batches[i];
}
}
// All the batches (for the given texture) are full, so add a
// new batch
final Batch[] newBatches = new Batch[batches.length + 1];
System.arraycopy(batches, 0, newBatches, 0, batches.length);
newBatches[batches.length] = new Batch(gl, texture);
newBatches[batches.length].addMesh(gl, vertices, normals,
texcoords, indices);
_batches.put(key, newBatches);
return newBatches[batches.length];
}
}
/**
* Retrieves a boolean indicating whether the batch is full or not
*
* @param vertexCount
* The number of vertices to be added to the batch
* @return <code>true</code> if the batch is full; <code>false</code>
* otherwise
*/
protected boolean isFull(final int vertexCount) {
return _vertexDataOffset + vertexCount * 32 > 8192;
}
/**
* Adds the given mesh to the batch
*
* @param gl
* The reference to the OpenGL v1.1 object
* @param vertices
* The mesh's vertices
* @param normals
* The mesh's vertice normals
* @param texcoords
* The mesh's vertice texture coordinates
* @param indices
* The mesh's indices
*/
private void addMesh(final GL11 gl, final float[] vertices,
final float[] normals, final float[] texcoords,
final byte[] indices) {
// Add the vertex data to the batch's internal data
_positions.addElement(new FloatArray(vertices));
_normals.addElement(new FloatArray(normals));
_texcoords.addElement(new FloatArray(texcoords));
// Add the index data to the batch's internal data after offsetting
// it correctly.
// The offset is calculated as follows:
// VertexDataOffset / ((3 + 3 + 2) * 4) = VertexDataOffset / 32
// (3 floats(position) + 3 floats(normal) + 2 floats(texture
// coordinate)) * 4 bytes per float
final int indexOffset = _vertexDataOffset / 32;
final ByteArray newIndices = new ByteArray(indices);
for (int i = 0; i < newIndices.array.length; i++) {
newIndices.array[i] += indexOffset;
}
_indices.addElement(newIndices);
// Update the offsets
_vertexDataOffset +=
(vertices.length + normals.length + texcoords.length) * 4;
_indexDataOffset += indices.length;
// Update the vertex array offsets used for drawing
_positionsOffset = 0;
_normalsOffset = 0;
for (int i = 0; i < _positions.size(); i++) {
_normalsOffset +=
((FloatArray) _positions.elementAt(i)).array.length * 4;
}
_texcoordsOffset = _normalsOffset;
for (int i = 0; i < _normals.size(); i++) {
_texcoordsOffset +=
((FloatArray) _normals.elementAt(i)).array.length * 4;
}
_meshCount++;
}
/**
* Adds the given mesh to the batch.
*
* @param gl
* The reference to the OpenGL v2.0 object
* @param vertices
* The mesh's vertices
* @param normals
* The mesh's vertice normals
* @param texcoords
* The mesh's vertice texture coordinates
* @param indices
* The mesh's indices
*/
private void addMesh(final GL20 gl, final float[] vertices,
final float[] normals, final float[] texcoords,
final byte[] indices) {
// Add the vertex data to the batch's internal data
_positions.addElement(new FloatArray(vertices));
_normals.addElement(new FloatArray(normals));
_texcoords.addElement(new FloatArray(texcoords));
// Add the index data to the batch's internal data after offsetting
// it correctly.
// The offset is calculated as follows:
// VertexDataOffset / ((3 + 3 + 2) * 4) = VertexDataOffset / 32
// (3 floats(position) + 3 floats(normal) + 2 floats(texture
// coordinate)) * 4 bytes per float
final int indexOffset = _vertexDataOffset / 32;
final ByteArray newIndices = new ByteArray(indices);
for (int i = 0; i < newIndices.array.length; i++) {
newIndices.array[i] += indexOffset;
}
_indices.addElement(newIndices);
// Update the offsets
_vertexDataOffset +=
(vertices.length + normals.length + texcoords.length) * 4;
_indexDataOffset += indices.length;
// Update the vertex array offsets used for drawing
_positionsOffset = 0;
_normalsOffset = 0;
for (int i = 0; i < _positions.size(); i++) {
_normalsOffset +=
((FloatArray) _positions.elementAt(i)).array.length * 4;
}
_texcoordsOffset = _normalsOffset;
for (int i = 0; i < _normals.size(); i++) {
_texcoordsOffset +=
((FloatArray) _normals.elementAt(i)).array.length * 4;
}
_meshCount++;
}
}
/**
* Clears texture handles when game screen is closed
*/
static void cleanup() {
// Wipe out the table of texture handles so they'll be reloaded
// the next time the game is initialized.
_textureHandles = new Hashtable();
}
}