Package com.rim.samples.device.openglspritegamedemo

Source Code of com.rim.samples.device.openglspritegamedemo.Sprite

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

Related Classes of com.rim.samples.device.openglspritegamedemo.Sprite

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.