Java tutorial
/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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 net.neilcsmith.praxis.video.opengl.internal; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import org.lwjgl.opengl.GL11; /** <p> * A Mesh holds vertices composed of attributes specified by a {@link VertexAttributes} instance. The vertices are held either in * VRAM in form of vertex buffer objects or in RAM in form of vertex arrays. The former variant is more performant and is prefered * over vertex arrays if hardware supports it. * </p> * * <p> * Meshes are automatically managed. If the OpenGL context is lost all vertex buffer objects get invalidated and must be reloaded * when the context is recreated. This only happens on Android when a user switches to another application or receives an incoming * call. A managed Mesh will be reloaded automagically so you don't have to do this manually. * </p> * * <p> * A Mesh consists of vertices and optionally indices which specify which vertices define a triangle. Each vertex is composed of * attributes such as position, normal, color or texture coordinate. Note that not all of this attributes must be given, except * for position which is non-optional. Each attribute has an alias which is used when rendering a Mesh in OpenGL ES 2.0. The alias * is used to bind a specific vertex attribute to a shader attribute. The shader source and the alias of the attribute must match * exactly for this to work. For OpenGL ES 1.x rendering this aliases are irrelevant. * </p> * * <p> * Meshes can be used with either OpenGL ES 1.x or OpenGL ES 2.0. * </p> * * @author mzechner, Dave Clayton <contact@redskyforge.com> */ public class Mesh implements Disposable { public enum VertexDataType { VertexArray, VertexBufferObject, VertexBufferObjectSubData, } /** list of all meshes **/ // static final Map<Application, List<Mesh>> meshes = new HashMap<Application, List<Mesh>>(); /** used for benchmarking **/ public static boolean forceVBO = false; final VertexBufferObject vertices; final IndexBufferObject indices; boolean autoBind = true; final boolean isVertexArray; int refCount = 0; /** Creates a new Mesh with the given attributes. * * @param isStatic whether this mesh is static or not. Allows for internal optimizations. * @param maxVertices the maximum number of vertices this mesh can hold * @param maxIndices the maximum number of indices this mesh can hold * @param attributes the {@link VertexAttribute}s. Each vertex attribute defines one property of a vertex such as position, * normal or texture coordinate */ public Mesh(boolean isStatic, int maxVertices, int maxIndices, VertexAttribute... attributes) { vertices = new VertexBufferObject(isStatic, maxVertices, attributes); indices = new IndexBufferObject(isStatic, maxIndices); isVertexArray = false; } /** Creates a new Mesh with the given attributes. * * @param isStatic whether this mesh is static or not. Allows for internal optimizations. * @param maxVertices the maximum number of vertices this mesh can hold * @param maxIndices the maximum number of indices this mesh can hold * @param attributes the {@link VertexAttributes}. Each vertex attribute defines one property of a vertex such as position, * normal or texture coordinate */ public Mesh(boolean isStatic, int maxVertices, int maxIndices, VertexAttributes attributes) { vertices = new VertexBufferObject(isStatic, maxVertices, attributes); indices = new IndexBufferObject(isStatic, maxIndices); isVertexArray = false; } // /** Creates a new Mesh with the given attributes. This is an expert method with no error checking. Use at your own risk. // * // * @param type the {@link VertexDataType} to be used, VBO or VA. // * @param isStatic whether this mesh is static or not. Allows for internal optimizations. // * @param maxVertices the maximum number of vertices this mesh can hold // * @param maxIndices the maximum number of indices this mesh can hold // * @param attributes the {@link VertexAttribute}s. Each vertex attribute defines one property of a vertex such as position, // * normal or texture coordinate */ // public Mesh(VertexDataType type, boolean isStatic, int maxVertices, int maxIndices, VertexAttribute... attributes) { // if (type == VertexDataType.VertexArray && Gdx.graphics.isGL20Available()) { // type = VertexDataType.VertexBufferObject; // } // // if (type == VertexDataType.VertexBufferObject) { // vertices = new VertexBufferObject(isStatic, maxVertices, attributes); // indices = new IndexBufferObject(isStatic, maxIndices); // isVertexArray = false; // } else if (type == VertexDataType.VertexBufferObjectSubData) { // vertices = new VertexBufferObjectSubData(isStatic, maxVertices, attributes); // indices = new IndexBufferObjectSubData(isStatic, maxIndices); // isVertexArray = false; // } else { // vertices = new VertexArray(maxVertices, attributes); // indices = new IndexBufferObject(maxIndices); // isVertexArray = true; // } // addManagedMesh(Gdx.app, this); // } /** Sets the vertices of this Mesh. The attributes are assumed to be given in float format. If this mesh is configured to use * fixed point an IllegalArgumentException will be thrown. * * @param vertices the vertices. */ public void setVertices(float[] vertices) { this.vertices.setVertices(vertices, 0, vertices.length); } /** Sets the vertices of this Mesh. The attributes are assumed to be given in float format. If this mesh is configured to use * fixed point an IllegalArgumentException will be thrown. * * @param vertices the vertices. * @param offset the offset into the vertices array * @param count the number of floats to use */ public void setVertices(float[] vertices, int offset, int count) { this.vertices.setVertices(vertices, offset, count); } /** Copies the vertices from the Mesh to the float array. The float array must be large enough to hold all the Mesh's vertices. * @param vertices the array to copy the vertices to */ public void getVertices(float[] vertices) { if (vertices.length < getNumVertices() * getVertexSize() / 4) { throw new IllegalArgumentException("not enough room in vertices array, has " + vertices.length + " floats, needs " + getNumVertices() * getVertexSize() / 4); } int pos = getVerticesBuffer().position(); getVerticesBuffer().position(0); getVerticesBuffer().get(vertices, 0, getNumVertices() * getVertexSize() / 4); getVerticesBuffer().position(pos); } /** Sets the indices of this Mesh * * @param indices the indices */ public void setIndices(short[] indices) { this.indices.setIndices(indices, 0, indices.length); } /** Sets the indices of this Mesh. * * @param indices the indices * @param offset the offset into the indices array * @param count the number of indices to copy */ public void setIndices(short[] indices, int offset, int count) { this.indices.setIndices(indices, offset, count); } /** Copies the indices from the Mesh to the short array. The short array must be large enough to hold all the Mesh's indices. * @param indices the array to copy the indices to */ public void getIndices(short[] indices) { if (indices.length < getNumIndices()) { throw new IllegalArgumentException("not enough room in indices array, has " + indices.length + " floats, needs " + getNumIndices()); } int pos = getIndicesBuffer().position(); getIndicesBuffer().position(0); getIndicesBuffer().get(indices, 0, getNumIndices()); getIndicesBuffer().position(pos); } /** @return the number of defined indices */ public int getNumIndices() { return indices.getNumIndices(); } /** @return the number of defined vertices */ public int getNumVertices() { return vertices.getNumVertices(); } /** @return the maximum number of vertices this mesh can hold */ public int getMaxVertices() { return vertices.getNumMaxVertices(); } /** @return the maximum number of indices this mesh can hold */ public int getMaxIndices() { return indices.getNumMaxIndices(); } /** @return the size of a single vertex in bytes */ public int getVertexSize() { return vertices.getAttributes().vertexSize; } /** Sets whether to bind the underlying {@link VertexArray} or {@link VertexBufferObject} automatically on a call to one of the * {@link #render(int)} methods or not. Usually you want to use autobind. Manual binding is an expert functionality. There is a * driver bug on the MSM720xa chips that will fuck up memory if you manipulate the vertices and indices of a Mesh multiple * times while it is bound. Keep this in mind. * * @param autoBind whether to autobind meshes. */ public void setAutoBind(boolean autoBind) { this.autoBind = autoBind; } /** Binds the underlying {@link VertexBufferObject} and {@link IndexBufferObject} if indices where given. Use this with OpenGL * ES 2.0 and when auto-bind is disabled. * * @param shader the shader (does not bind the shader) */ public void bind(ShaderProgram shader) { vertices.bind(shader); if (indices.getNumIndices() > 0) { indices.bind(); } } /** Unbinds the underlying {@link VertexBufferObject} and {@link IndexBufferObject} is indices were given. Use this with OpenGL * ES 1.x and when auto-bind is disabled. * * @param shader the shader (does not unbind the shader) */ public void unbind(ShaderProgram shader) { vertices.unbind(shader); if (indices.getNumIndices() > 0) { indices.unbind(); } } /** <p> * Renders the mesh using the given primitive type. If indices are set for this mesh then getNumIndices() / #vertices per * primitive primitives are rendered. If no indices are set then getNumVertices() / #vertices per primitive are rendered. * </p> * * <p> * This method will automatically bind each vertex attribute as specified at construction time via {@link VertexAttributes} to * the respective shader attributes. The binding is based on the alias defined for each VertexAttribute. * </p> * * <p> * This method must only be called after the {@link ShaderProgram#begin()} method has been called! * </p> * * <p> * This method is intended for use with OpenGL ES 2.0 and will throw an IllegalStateException when OpenGL ES 1.x is used. * </p> * * @param primitiveType the primitive type */ public void render(ShaderProgram shader, int primitiveType) { render(shader, primitiveType, 0, indices.getNumMaxIndices() > 0 ? getNumIndices() : getNumVertices()); } /** <p> * Renders the mesh using the given primitive type. offset specifies the offset into either the vertex buffer or the index * buffer depending on whether indices are defined. count specifies the number of vertices or indices to use thus count / * #vertices per primitive primitives are rendered. * </p> * * <p> * This method will automatically bind each vertex attribute as specified at construction time via {@link VertexAttributes} to * the respective shader attributes. The binding is based on the alias defined for each VertexAttribute. * </p> * * <p> * This method must only be called after the {@link ShaderProgram#begin()} method has been called! * </p> * * <p> * This method is intended for use with OpenGL ES 2.0 and will throw an IllegalStateException when OpenGL ES 1.x is used. * </p> * * @param shader the shader to be used * @param primitiveType the primitive type * @param offset the offset into the vertex or index buffer * @param count number of vertices or indices to use */ public void render(ShaderProgram shader, int primitiveType, int offset, int count) { if (autoBind) { bind(shader); } if (indices.getNumIndices() > 0) { GL11.glDrawElements(primitiveType, count, GL11.GL_UNSIGNED_SHORT, offset * 2); } else { GL11.glDrawArrays(primitiveType, offset, count); } if (autoBind) { unbind(shader); } } /** Frees all resources associated with this Mesh */ public void dispose() { refCount--; if (refCount > 0) { return; } // if (meshes.get(Gdx.app) != null) { // meshes.get(Gdx.app).remove(this); // } vertices.dispose(); indices.dispose(); } /** Returns the first {@link VertexAttribute} having the given {@link Usage}. * * @param usage the Usage. * @return the VertexAttribute or null if no attribute with that usage was found. */ public VertexAttribute getVertexAttribute(int usage) { VertexAttributes attributes = vertices.getAttributes(); int len = attributes.size(); for (int i = 0; i < len; i++) { if (attributes.get(i).usage == usage) { return attributes.get(i); } } return null; } /** @return the vertex attributes of this Mesh */ public VertexAttributes getVertexAttributes() { return vertices.getAttributes(); } /** @return the backing FloatBuffer holding the vertices. Does not have to be a direct buffer on Android! */ public FloatBuffer getVerticesBuffer() { return vertices.getBuffer(); } /** Calculates the {@link BoundingBox} of the vertices contained in this mesh. In case no vertices are defined yet a * {@link GdxRuntimeException} is thrown. This method creates a new BoundingBox instance. * * @return the bounding box. */ public BoundingBox calculateBoundingBox() { BoundingBox bbox = new BoundingBox(); calculateBoundingBox(bbox); return bbox; } /** Calculates the {@link BoundingBox} of the vertices contained in this mesh. In case no vertices are defined yet a * {@link GdxRuntimeException} is thrown. * * @param bbox the bounding box to store the result in. */ public void calculateBoundingBox(BoundingBox bbox) { final int numVertices = getNumVertices(); if (numVertices == 0) { throw new RuntimeException("No vertices defined"); } final FloatBuffer verts = vertices.getBuffer(); bbox.inf(); final VertexAttribute posAttrib = getVertexAttribute(VertexAttributes.Usage.Position); final int offset = posAttrib.offset / 4; final int vertexSize = vertices.getAttributes().vertexSize / 4; int idx = offset; switch (posAttrib.numComponents) { case 1: for (int i = 0; i < numVertices; i++) { bbox.ext(verts.get(idx), 0, 0); idx += vertexSize; } break; case 2: for (int i = 0; i < numVertices; i++) { bbox.ext(verts.get(idx), verts.get(idx + 1), 0); idx += vertexSize; } break; case 3: for (int i = 0; i < numVertices; i++) { bbox.ext(verts.get(idx), verts.get(idx + 1), verts.get(idx + 2)); idx += vertexSize; } break; } } /** @return the backing shortbuffer holding the indices. Does not have to be a direct buffer on Android! */ public ShortBuffer getIndicesBuffer() { return indices.getBuffer(); } // private static void addManagedMesh(Application app, Mesh mesh) { // List<Mesh> managedResources = meshes.get(app); // if (managedResources == null) { // managedResources = new ArrayList<Mesh>(); // } // managedResources.add(mesh); // meshes.put(app, managedResources); // } // /** Invalidates all meshes so the next time they are rendered new VBO handles are generated. // * @param app */ // public static void invalidateAllMeshes(Application app) { // List<Mesh> meshesList = meshes.get(app); // if (meshesList == null) { // return; // } // for (int i = 0; i < meshesList.size(); i++) { // if (meshesList.get(i).vertices instanceof VertexBufferObject) { // ((VertexBufferObject) meshesList.get(i).vertices).invalidate(); // meshesList.get(i).indices.invalidate(); // } // } // } // /** Will clear the managed mesh cache. I wouldn't use this if i was you :) */ // public static void clearAllMeshes(Application app) { // meshes.remove(app); // } // // public static String getManagedStatus() { // StringBuilder builder = new StringBuilder(); // int i = 0; // builder.append("Managed meshes/app: { "); // for (Application app : meshes.keySet()) { // builder.append(meshes.get(app).size()); // builder.append(" "); // } // builder.append("}"); // return builder.toString(); // } /** Method to scale the positions in the mesh. Normals will be kept as is. This is a potentially slow operation, use with care. * It will also create a temporary float[] which will be garbage collected. * * @param scaleX scale on x * @param scaleY scale on y * @param scaleZ scale on z */ public void scale(float scaleX, float scaleY, float scaleZ) { VertexAttribute posAttr = getVertexAttribute(VertexAttributes.Usage.Position); int offset = posAttr.offset / 4; int numComponents = posAttr.numComponents; int numVertices = getNumVertices(); int vertexSize = getVertexSize() / 4; float[] vertices = new float[numVertices * vertexSize]; getVertices(vertices); int idx = offset; switch (numComponents) { case 1: for (int i = 0; i < numVertices; i++) { vertices[idx] *= scaleX; idx += vertexSize; } break; case 2: for (int i = 0; i < numVertices; i++) { vertices[idx] *= scaleX; vertices[idx + 1] *= scaleY; idx += vertexSize; } break; case 3: for (int i = 0; i < numVertices; i++) { vertices[idx] *= scaleX; vertices[idx + 1] *= scaleY; vertices[idx + 2] *= scaleZ; idx += vertexSize; } break; } setVertices(vertices); } }