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 com.badlogic.gdx.graphics; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.Application; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.glutils.IndexArray; import com.badlogic.gdx.graphics.glutils.IndexBufferObject; import com.badlogic.gdx.graphics.glutils.IndexBufferObjectSubData; import com.badlogic.gdx.graphics.glutils.IndexData; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.VertexArray; import com.badlogic.gdx.graphics.glutils.VertexBufferObject; import com.badlogic.gdx.graphics.glutils.VertexBufferObjectSubData; import com.badlogic.gdx.graphics.glutils.VertexData; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; /** <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 preferred * 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. * </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, Array<Mesh>> meshes = new HashMap<Application, Array<Mesh>>(); final VertexData vertices; final IndexData indices; boolean autoBind = true; final boolean isVertexArray; /** 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; addManagedMesh(Gdx.app, this); } /** 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; addManagedMesh(Gdx.app, this); } /** by jw: Creates a new Mesh with the given attributes. Adds extra optimizations for dynamic (frequently modified) meshes. * * @param staticVertices whether vertices of this mesh are static or not. Allows for internal optimizations. * @param staticIndices whether indices of this mesh are 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 * * @author Jaroslaw Wisniewski <j.wisniewski@appsisle.com> **/ public Mesh(boolean staticVertices, boolean staticIndices, int maxVertices, int maxIndices, VertexAttributes attributes) { vertices = new VertexBufferObject(staticVertices, maxVertices, attributes); indices = new IndexBufferObject(staticIndices, maxIndices); isVertexArray = false; addManagedMesh(Gdx.app, this); } /** 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.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 IndexArray(maxIndices); isVertexArray = true; } addManagedMesh(Gdx.app, this); } /** Create a new Mesh that is a combination of transformations of the supplied base mesh. Not all primitive types, like line * strip and triangle strip, can be combined. * @param isStatic whether this mesh is static or not. Allows for internal optimizations. * @param transformations the transformations to apply to the meshes * @return the combined mesh */ public static Mesh create(boolean isStatic, final Mesh base, final Matrix4[] transformations) { final VertexAttribute posAttr = base.getVertexAttribute(Usage.Position); final int offset = posAttr.offset / 4; final int numComponents = posAttr.numComponents; final int numVertices = base.getNumVertices(); final int vertexSize = base.getVertexSize() / 4; final int baseSize = numVertices * vertexSize; final int numIndices = base.getNumIndices(); final float vertices[] = new float[numVertices * vertexSize * transformations.length]; final short indices[] = new short[numIndices * transformations.length]; base.getIndices(indices); for (int i = 0; i < transformations.length; i++) { base.getVertices(0, baseSize, vertices, baseSize * i); transform(transformations[i], vertices, vertexSize, offset, numComponents, numVertices * i, numVertices); if (i > 0) for (int j = 0; j < numIndices; j++) indices[(numIndices * i) + j] = (short) (indices[j] + (numVertices * i)); } final Mesh result = new Mesh(isStatic, vertices.length / vertexSize, indices.length, base.getVertexAttributes()); result.setVertices(vertices); result.setIndices(indices); return result; } /** Create a new Mesh that is a combination of the supplied meshes. The meshes must have the same VertexAttributes signature. * Not all primitive types, like line strip and triangle strip, can be combined. * @param isStatic whether this mesh is static or not. Allows for internal optimizations. * @param meshes the meshes to combine * @return the combined mesh */ public static Mesh create(boolean isStatic, final Mesh[] meshes) { return create(isStatic, meshes, null); } /** Create a new Mesh that is a combination of the supplied meshes. The meshes must have the same VertexAttributes signature. If * transformations is supplied, it must have the same length as meshes. Not all primitive types, like line strip and triangle * strip, can be combined. * @param isStatic whether this mesh is static or not. Allows for internal optimizations. * @param meshes the meshes to combine * @param transformations the transformations to apply to the meshes * @return the combined mesh */ public static Mesh create(boolean isStatic, final Mesh[] meshes, final Matrix4[] transformations) { if (transformations != null && transformations.length < meshes.length) throw new IllegalArgumentException("Not enough transformations specified"); final VertexAttributes attributes = meshes[0].getVertexAttributes(); int vertCount = meshes[0].getNumVertices(); int idxCount = meshes[0].getNumIndices(); for (int i = 1; i < meshes.length; i++) { if (!meshes[i].getVertexAttributes().equals(attributes)) throw new IllegalArgumentException("Inconsistent VertexAttributes"); vertCount += meshes[i].getNumVertices(); idxCount += meshes[i].getNumIndices(); } final VertexAttribute posAttr = meshes[0].getVertexAttribute(Usage.Position); final int offset = posAttr.offset / 4; final int numComponents = posAttr.numComponents; final int vertexSize = attributes.vertexSize / 4; final float vertices[] = new float[vertCount * vertexSize]; final short indices[] = new short[idxCount]; meshes[0].getVertices(vertices); meshes[0].getIndices(indices); int vcount = meshes[0].getNumVertices(); if (transformations != null) transform(transformations[0], vertices, vertexSize, offset, numComponents, 0, vcount); int voffset = vcount; int ioffset = meshes[0].getNumIndices(); for (int i = 1; i < meshes.length; i++) { final Mesh mesh = meshes[i]; vcount = mesh.getNumVertices(); final int isize = mesh.getNumIndices(); mesh.getVertices(0, vcount * vertexSize, vertices, voffset * vertexSize); if (transformations != null) transform(transformations[i], vertices, vertexSize, offset, numComponents, voffset, vcount); mesh.getIndices(indices, ioffset); for (int j = 0; j < isize; j++) indices[ioffset + j] = (short) (indices[ioffset + j] + voffset); ioffset += isize; voffset += vcount; } final Mesh result = new Mesh(isStatic, vertices.length / vertexSize, indices.length, attributes); result.setVertices(vertices); result.setIndices(indices); return result; } /** Sets the vertices of this Mesh. The attributes are assumed to be given in float format. * * @param vertices the vertices. * @return the mesh for invocation chaining. */ public Mesh setVertices(float[] vertices) { this.vertices.setVertices(vertices, 0, vertices.length); return this; } /** Sets the vertices of this Mesh. The attributes are assumed to be given in float format. * * @param vertices the vertices. * @param offset the offset into the vertices array * @param count the number of floats to use * @return the mesh for invocation chaining. */ public Mesh setVertices(float[] vertices, int offset, int count) { this.vertices.setVertices(vertices, offset, count); return this; } /** Update (a portion of) the vertices. Does not resize the backing buffer. * @param targetOffset the offset in number of floats of the mesh part. * @param source the vertex data to update the mesh part with */ public Mesh updateVertices(int targetOffset, float[] source) { return updateVertices(targetOffset, source, 0, source.length); } /** Update (a portion of) the vertices. Does not resize the backing buffer. * @param targetOffset the offset in number of floats of the mesh part. * @param source the vertex data to update the mesh part with * @param sourceOffset the offset in number of floats within the source array * @param count the number of floats to update */ public Mesh updateVertices(int targetOffset, float[] source, int sourceOffset, int count) { this.vertices.updateVertices(targetOffset, source, sourceOffset, count); return this; } /** 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 float[] getVertices(float[] vertices) { return getVertices(0, -1, vertices); } /** Copies the the remaining vertices from the Mesh to the float array. The float array must be large enough to hold the * remaining vertices. * @param srcOffset the offset (in number of floats) of the vertices in the mesh to copy * @param vertices the array to copy the vertices to */ public float[] getVertices(int srcOffset, float[] vertices) { return getVertices(srcOffset, -1, vertices); } /** Copies the specified vertices from the Mesh to the float array. The float array must be large enough to hold count vertices. * @param srcOffset the offset (in number of floats) of the vertices in the mesh to copy * @param count the amount of floats to copy * @param vertices the array to copy the vertices to */ public float[] getVertices(int srcOffset, int count, float[] vertices) { return getVertices(srcOffset, count, vertices, 0); } /** Copies the specified vertices from the Mesh to the float array. The float array must be large enough to hold * destOffset+count vertices. * @param srcOffset the offset (in number of floats) of the vertices in the mesh to copy * @param count the amount of floats to copy * @param vertices the array to copy the vertices to * @param destOffset the offset (in floats) in the vertices array to start copying */ public float[] getVertices(int srcOffset, int count, float[] vertices, int destOffset) { // TODO: Perhaps this method should be vertexSize aware?? final int max = getNumVertices() * getVertexSize() / 4; if (count == -1) { count = max - srcOffset; if (count > vertices.length - destOffset) count = vertices.length - destOffset; } if (srcOffset < 0 || count <= 0 || (srcOffset + count) > max || destOffset < 0 || destOffset >= vertices.length) throw new IndexOutOfBoundsException(); if ((vertices.length - destOffset) < count) throw new IllegalArgumentException( "not enough room in vertices array, has " + vertices.length + " floats, needs " + count); int pos = getVerticesBuffer().position(); getVerticesBuffer().position(srcOffset); getVerticesBuffer().get(vertices, destOffset, count); getVerticesBuffer().position(pos); return vertices; } /** Sets the indices of this Mesh * * @param indices the indices * @return the mesh for invocation chaining. */ public Mesh setIndices(short[] indices) { this.indices.setIndices(indices, 0, indices.length); return this; } /** 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 * @return the mesh for invocation chaining. */ public Mesh setIndices(short[] indices, int offset, int count) { this.indices.setIndices(indices, offset, count); return this; } /** 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) { getIndices(indices, 0); } /** Copies the indices from the Mesh to the short array. The short array must be large enough to hold destOffset + all the * Mesh's indices. * @param indices the array to copy the indices to * @param destOffset the offset in the indices array to start copying */ public void getIndices(short[] indices, int destOffset) { if ((indices.length - destOffset) < 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, destOffset, 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 * render methods. 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(final ShaderProgram shader) { bind(shader, null); } /** 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) * @param locations array containing the attribute locations. */ public void bind(final ShaderProgram shader, final int[] locations) { vertices.bind(shader, locations); 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(final ShaderProgram shader) { unbind(shader, null); } /** 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) * @param locations array containing the attribute locations. */ public void unbind(final ShaderProgram shader, final int[] locations) { vertices.unbind(shader, locations); 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(), autoBind); } /** <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) { render(shader, primitiveType, offset, count, autoBind); } /** <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 * @param autoBind overrides the autoBind member of this Mesh */ public void render(ShaderProgram shader, int primitiveType, int offset, int count, boolean autoBind) { if (count == 0) return; if (autoBind) bind(shader); if (isVertexArray) { if (indices.getNumIndices() > 0) { ShortBuffer buffer = indices.getBuffer(); int oldPosition = buffer.position(); int oldLimit = buffer.limit(); buffer.position(offset); buffer.limit(offset + count); Gdx.gl20.glDrawElements(primitiveType, count, GL20.GL_UNSIGNED_SHORT, buffer); buffer.position(oldPosition); buffer.limit(oldLimit); } else { Gdx.gl20.glDrawArrays(primitiveType, offset, count); } } else { if (indices.getNumIndices() > 0) Gdx.gl20.glDrawElements(primitiveType, count, GL20.GL_UNSIGNED_SHORT, offset * 2); else Gdx.gl20.glDrawArrays(primitiveType, offset, count); } if (autoBind) unbind(shader); } /** Frees all resources associated with this Mesh */ public void dispose() { if (meshes.get(Gdx.app) != null) meshes.get(Gdx.app).removeValue(this, true); 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 GdxRuntimeException("No vertices defined"); final FloatBuffer verts = vertices.getBuffer(); bbox.inf(); final VertexAttribute posAttrib = getVertexAttribute(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; } } /** Calculate the {@link BoundingBox} of the specified part. * @param out the bounding box to store the result in. * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the value specified by out. */ public BoundingBox calculateBoundingBox(final BoundingBox out, int offset, int count) { return extendBoundingBox(out.inf(), offset, count); } /** Calculate the {@link BoundingBox} of the specified part. * @param out the bounding box to store the result in. * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the value specified by out. */ public BoundingBox calculateBoundingBox(final BoundingBox out, int offset, int count, final Matrix4 transform) { return extendBoundingBox(out.inf(), offset, count, transform); } /** Extends the specified {@link BoundingBox} with the specified part. * @param out the bounding box to store the result in. * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the value specified by out. */ public BoundingBox extendBoundingBox(final BoundingBox out, int offset, int count) { return extendBoundingBox(out, offset, count, null); } private final Vector3 tmpV = new Vector3(); /** Extends the specified {@link BoundingBox} with the specified part. * @param out the bounding box to store the result in. * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the value specified by out. */ public BoundingBox extendBoundingBox(final BoundingBox out, int offset, int count, final Matrix4 transform) { int numIndices = getNumIndices(); if (offset < 0 || count < 1 || offset + count > numIndices) throw new GdxRuntimeException( "Not enough indices ( offset=" + offset + ", count=" + count + ", max=" + numIndices + " )"); final FloatBuffer verts = vertices.getBuffer(); final ShortBuffer index = indices.getBuffer(); final VertexAttribute posAttrib = getVertexAttribute(Usage.Position); final int posoff = posAttrib.offset / 4; final int vertexSize = vertices.getAttributes().vertexSize / 4; final int end = offset + count; switch (posAttrib.numComponents) { case 1: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), 0, 0); if (transform != null) tmpV.mul(transform); out.ext(tmpV); } break; case 2: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), verts.get(idx + 1), 0); if (transform != null) tmpV.mul(transform); out.ext(tmpV); } break; case 3: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), verts.get(idx + 1), verts.get(idx + 2)); if (transform != null) tmpV.mul(transform); out.ext(tmpV); } break; } return out; } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param centerX The X coordinate of the center of the bounding sphere * @param centerY The Y coordinate of the center of the bounding sphere * @param centerZ The Z coordinate of the center of the bounding sphere * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the squared radius of the bounding sphere. */ public float calculateRadiusSquared(final float centerX, final float centerY, final float centerZ, int offset, int count, final Matrix4 transform) { int numIndices = getNumIndices(); if (offset < 0 || count < 1 || offset + count > numIndices) throw new GdxRuntimeException("Not enough indices"); final FloatBuffer verts = vertices.getBuffer(); final ShortBuffer index = indices.getBuffer(); final VertexAttribute posAttrib = getVertexAttribute(Usage.Position); final int posoff = posAttrib.offset / 4; final int vertexSize = vertices.getAttributes().vertexSize / 4; final int end = offset + count; float result = 0; switch (posAttrib.numComponents) { case 1: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), 0, 0); if (transform != null) tmpV.mul(transform); final float r = tmpV.sub(centerX, centerY, centerZ).len2(); if (r > result) result = r; } break; case 2: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), verts.get(idx + 1), 0); if (transform != null) tmpV.mul(transform); final float r = tmpV.sub(centerX, centerY, centerZ).len2(); if (r > result) result = r; } break; case 3: for (int i = offset; i < end; i++) { final int idx = index.get(i) * vertexSize + posoff; tmpV.set(verts.get(idx), verts.get(idx + 1), verts.get(idx + 2)); if (transform != null) tmpV.mul(transform); final float r = tmpV.sub(centerX, centerY, centerZ).len2(); if (r > result) result = r; } break; } return result; } /** Calculates the radius of the bounding sphere around the specified center for the specified part. * @param centerX The X coordinate of the center of the bounding sphere * @param centerY The Y coordinate of the center of the bounding sphere * @param centerZ The Z coordinate of the center of the bounding sphere * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the radius of the bounding sphere. */ public float calculateRadius(final float centerX, final float centerY, final float centerZ, int offset, int count, final Matrix4 transform) { return (float) Math.sqrt(calculateRadiusSquared(centerX, centerY, centerZ, offset, count, transform)); } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param center The center of the bounding sphere * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the squared radius of the bounding sphere. */ public float calculateRadius(final Vector3 center, int offset, int count, final Matrix4 transform) { return calculateRadius(center.x, center.y, center.z, offset, count, transform); } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param centerX The X coordinate of the center of the bounding sphere * @param centerY The Y coordinate of the center of the bounding sphere * @param centerZ The Z coordinate of the center of the bounding sphere * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the squared radius of the bounding sphere. */ public float calculateRadius(final float centerX, final float centerY, final float centerZ, int offset, int count) { return calculateRadius(centerX, centerY, centerZ, offset, count, null); } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param center The center of the bounding sphere * @param offset the start index of the part. * @param count the amount of indices the part contains. * @return the squared radius of the bounding sphere. */ public float calculateRadius(final Vector3 center, int offset, int count) { return calculateRadius(center.x, center.y, center.z, offset, count, null); } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param centerX The X coordinate of the center of the bounding sphere * @param centerY The Y coordinate of the center of the bounding sphere * @param centerZ The Z coordinate of the center of the bounding sphere * @return the squared radius of the bounding sphere. */ public float calculateRadius(final float centerX, final float centerY, final float centerZ) { return calculateRadius(centerX, centerY, centerZ, 0, getNumIndices(), null); } /** Calculates the squared radius of the bounding sphere around the specified center for the specified part. * @param center The center of the bounding sphere * @return the squared radius of the bounding sphere. */ public float calculateRadius(final Vector3 center) { return calculateRadius(center.x, center.y, center.z, 0, getNumIndices(), null); } /** @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) { Array<Mesh> managedResources = meshes.get(app); if (managedResources == null) managedResources = new Array<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) { Array<Mesh> meshesArray = meshes.get(app); if (meshesArray == null) return; for (int i = 0; i < meshesArray.size; i++) { if (meshesArray.get(i).vertices instanceof VertexBufferObject) { ((VertexBufferObject) meshesArray.get(i).vertices).invalidate(); } meshesArray.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) { final VertexAttribute posAttr = getVertexAttribute(Usage.Position); final int offset = posAttr.offset / 4; final int numComponents = posAttr.numComponents; final int numVertices = getNumVertices(); final int vertexSize = getVertexSize() / 4; final 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); } /** Method to transform 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 matrix the transformation matrix */ public void transform(final Matrix4 matrix) { transform(matrix, 0, getNumVertices()); } // TODO: Protected for now, because transforming a portion works but still copies all vertices public void transform(final Matrix4 matrix, final int start, final int count) { final VertexAttribute posAttr = getVertexAttribute(Usage.Position); final int posOffset = posAttr.offset / 4; final int stride = getVertexSize() / 4; final int numComponents = posAttr.numComponents; final int numVertices = getNumVertices(); final float[] vertices = new float[count * stride]; getVertices(start * stride, count * stride, vertices); // getVertices(0, vertices.length, vertices); transform(matrix, vertices, stride, posOffset, numComponents, 0, count); // setVertices(vertices, 0, vertices.length); updateVertices(start * stride, vertices); } /** Method to transform the positions in the float array. Normals will be kept as is. This is a potentially slow operation, use * with care. * @param matrix the transformation matrix * @param vertices the float array * @param vertexSize the number of floats in each vertex * @param offset the offset within a vertex to the position * @param dimensions the size of the position * @param start the vertex to start with * @param count the amount of vertices to transform */ public static void transform(final Matrix4 matrix, final float[] vertices, int vertexSize, int offset, int dimensions, int start, int count) { if (offset < 0 || dimensions < 1 || (offset + dimensions) > vertexSize) throw new IndexOutOfBoundsException(); if (start < 0 || count < 1 || ((start + count) * vertexSize) > vertices.length) throw new IndexOutOfBoundsException("start = " + start + ", count = " + count + ", vertexSize = " + vertexSize + ", length = " + vertices.length); final Vector3 tmp = new Vector3(); int idx = offset + (start * vertexSize); switch (dimensions) { case 1: for (int i = 0; i < count; i++) { tmp.set(vertices[idx], 0, 0).mul(matrix); vertices[idx] = tmp.x; idx += vertexSize; } break; case 2: for (int i = 0; i < count; i++) { tmp.set(vertices[idx], vertices[idx + 1], 0).mul(matrix); vertices[idx] = tmp.x; vertices[idx + 1] = tmp.y; idx += vertexSize; } break; case 3: for (int i = 0; i < count; i++) { tmp.set(vertices[idx], vertices[idx + 1], vertices[idx + 2]).mul(matrix); vertices[idx] = tmp.x; vertices[idx + 1] = tmp.y; vertices[idx + 2] = tmp.z; idx += vertexSize; } break; } } /** Method to transform the texture coordinates in the mesh. This is a potentially slow operation, use with care. It will also * create a temporary float[] which will be garbage collected. * * @param matrix the transformation matrix */ public void transformUV(final Matrix3 matrix) { transformUV(matrix, 0, getNumVertices()); } // TODO: Protected for now, because transforming a portion works but still copies all vertices protected void transformUV(final Matrix3 matrix, final int start, final int count) { final VertexAttribute posAttr = getVertexAttribute(Usage.TextureCoordinates); final int offset = posAttr.offset / 4; final int vertexSize = getVertexSize() / 4; final int numVertices = getNumVertices(); final float[] vertices = new float[numVertices * vertexSize]; // TODO: getVertices(vertices, start * vertexSize, count * vertexSize); getVertices(0, vertices.length, vertices); transformUV(matrix, vertices, vertexSize, offset, start, count); setVertices(vertices, 0, vertices.length); // TODO: setVertices(start * vertexSize, vertices, 0, vertices.length); } /** Method to transform the texture coordinates (UV) in the float array. This is a potentially slow operation, use with care. * @param matrix the transformation matrix * @param vertices the float array * @param vertexSize the number of floats in each vertex * @param offset the offset within a vertex to the texture location * @param start the vertex to start with * @param count the amount of vertices to transform */ public static void transformUV(final Matrix3 matrix, final float[] vertices, int vertexSize, int offset, int start, int count) { if (start < 0 || count < 1 || ((start + count) * vertexSize) > vertices.length) throw new IndexOutOfBoundsException("start = " + start + ", count = " + count + ", vertexSize = " + vertexSize + ", length = " + vertices.length); final Vector2 tmp = new Vector2(); int idx = offset + (start * vertexSize); for (int i = 0; i < count; i++) { tmp.set(vertices[idx], vertices[idx + 1]).mul(matrix); vertices[idx] = tmp.x; vertices[idx + 1] = tmp.y; idx += vertexSize; } } /** Copies this mesh optionally removing duplicate vertices and/or reducing the amount of attributes. * @param isStatic whether the new mesh is static or not. Allows for internal optimizations. * @param removeDuplicates whether to remove duplicate vertices if possible. Only the vertices specified by usage are checked. * @param usage which attributes (if available) to copy * @return the copy of this mesh */ public Mesh copy(boolean isStatic, boolean removeDuplicates, final int[] usage) { // TODO move this to a copy constructor? // TODO duplicate the buffers without double copying the data if possible. // TODO perhaps move this code to JNI if it turns out being too slow. final int vertexSize = getVertexSize() / 4; int numVertices = getNumVertices(); float[] vertices = new float[numVertices * vertexSize]; getVertices(0, vertices.length, vertices); short[] checks = null; VertexAttribute[] attrs = null; int newVertexSize = 0; if (usage != null) { int size = 0; int as = 0; for (int i = 0; i < usage.length; i++) if (getVertexAttribute(usage[i]) != null) { size += getVertexAttribute(usage[i]).numComponents; as++; } if (size > 0) { attrs = new VertexAttribute[as]; checks = new short[size]; int idx = -1; int ai = -1; for (int i = 0; i < usage.length; i++) { VertexAttribute a = getVertexAttribute(usage[i]); if (a == null) continue; for (int j = 0; j < a.numComponents; j++) checks[++idx] = (short) (a.offset + j); attrs[++ai] = new VertexAttribute(a.usage, a.numComponents, a.alias); newVertexSize += a.numComponents; } } } if (checks == null) { checks = new short[vertexSize]; for (short i = 0; i < vertexSize; i++) checks[i] = i; newVertexSize = vertexSize; } int numIndices = getNumIndices(); short[] indices = null; if (numIndices > 0) { indices = new short[numIndices]; getIndices(indices); if (removeDuplicates || newVertexSize != vertexSize) { float[] tmp = new float[vertices.length]; int size = 0; for (int i = 0; i < numIndices; i++) { final int idx1 = indices[i] * vertexSize; short newIndex = -1; if (removeDuplicates) { for (short j = 0; j < size && newIndex < 0; j++) { final int idx2 = j * newVertexSize; boolean found = true; for (int k = 0; k < checks.length && found; k++) { if (tmp[idx2 + k] != vertices[idx1 + checks[k]]) found = false; } if (found) newIndex = j; } } if (newIndex > 0) indices[i] = newIndex; else { final int idx = size * newVertexSize; for (int j = 0; j < checks.length; j++) tmp[idx + j] = vertices[idx1 + checks[j]]; indices[i] = (short) size; size++; } } vertices = tmp; numVertices = size; } } Mesh result; if (attrs == null) result = new Mesh(isStatic, numVertices, indices == null ? 0 : indices.length, getVertexAttributes()); else result = new Mesh(isStatic, numVertices, indices == null ? 0 : indices.length, attrs); result.setVertices(vertices, 0, numVertices * newVertexSize); result.setIndices(indices); return result; } /** Copies this mesh. * @param isStatic whether the new mesh is static or not. Allows for internal optimizations. * @return the copy of this mesh */ public Mesh copy(boolean isStatic) { return copy(isStatic, false, null); } }