Package com.badlogic.gdx.graphics

Source Code of com.badlogic.gdx.graphics.Mesh

/*******************************************************************************
* 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 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, Array<Mesh>> meshes = new HashMap<Application, Array<Mesh>>();

  /** used for benchmarking **/
  public static boolean forceVBO = false;

  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) {
    if (Gdx.gl20 != null || Gdx.gl11 != null || Mesh.forceVBO) {
      vertices = new VertexBufferObject(isStatic, maxVertices, attributes);
      indices = new IndexBufferObject(isStatic, maxIndices);
      isVertexArray = false;
    } else {
      vertices = new VertexArray(maxVertices, attributes);
      indices = new IndexArray(maxIndices);
      isVertexArray = true;
    }

    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) {
    if (Gdx.gl20 != null || Gdx.gl11 != null || Mesh.forceVBO) {
      vertices = new VertexBufferObject(isStatic, maxVertices, attributes);
      indices = new IndexBufferObject(isStatic, maxIndices);
      isVertexArray = false;
    } else {
      vertices = new VertexArray(maxVertices, attributes);
      indices = new IndexArray(maxIndices);
      isVertexArray = true;
    }

    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) {
    if (Gdx.gl20 != null || Gdx.gl11 != null || Mesh.forceVBO) {
     
      // buffers do not update when initialized with ..ObjectSubData classes
      /*if (staticVertices)
        vertices = new VertexBufferObject(staticVertices, maxVertices, attributes);
      else
        vertices = new VertexBufferObjectSubData(staticVertices, maxVertices, attributes);  // when updating vertices - updates buffer instead recreating it

      if (staticIndices)
        indices = new IndexBufferObject(staticIndices, maxIndices);
      else
        indices = new IndexBufferObjectSubData(staticIndices, maxIndices);  // when updating indices - updates buffer instead recreating it
      */
     
      vertices = new VertexBufferObject(staticVertices, maxVertices, attributes);
      indices = new IndexBufferObject(staticIndices, maxIndices);
      isVertexArray = false;
    } else {
      vertices = new VertexArray(maxVertices, attributes);
      indices = new IndexArray(maxIndices);
      isVertexArray = true;
    }

    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.VertexArray && Gdx.graphics.isGL20Available()) type = VertexDataType.VertexBufferObject;

    if (type == VertexDataType.VertexBufferObject || Mesh.forceVBO) {
      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 voffset = meshes[0].getNumVertices() * vertexSize;
    int ioffset = meshes[0].getNumIndices();
    for (int i = 1; i < meshes.length; i++) {
      final Mesh mesh = meshes[i];
      final int vsize = mesh.getNumVertices() * vertexSize;
      final int isize = mesh.getNumIndices();
      mesh.getVertices(0, vsize, vertices, voffset);
      if (transformations != null)
        transform(transformations[i], vertices, vertexSize, offset, numComponents, voffset / vertexSize, vsize / vertexSize);
      mesh.getIndices(indices, ioffset);
      for (int j = 0; j < isize; j++)
        indices[ioffset+j] = (short)(indices[ioffset+j] + voffset);
      voffset += vsize;
      ioffset += isize;
    }
   
    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;
  }

  /** 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) {
    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 void getVertices (int srcOffset, float[] vertices) {
    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 void getVertices (int srcOffset, int count, float[] vertices) {
    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 void 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);
  }

  /** 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
   * {@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 VertexArray}/{@link VertexBufferObject} and {@link IndexBufferObject} if indices were given. Use
   * this with OpenGL ES 1.x and when auto-bind is disabled. */
  public void bind () {
    if (Gdx.graphics.isGL20Available()) throw new IllegalStateException("can't use this render method with OpenGL ES 2.0");
    vertices.bind();
    if (!isVertexArray && indices.getNumIndices() > 0) indices.bind();
  }

  /** Unbinds the underlying {@link VertexArray}/{@link VertexBufferObject} and {@link IndexBufferObject} is indices were given.
   * Use this with OpenGL ES 1.x and when auto-bind is disabled. */
  public void unbind () {
    if (Gdx.graphics.isGL20Available()) throw new IllegalStateException("can't use this render method with OpenGL ES 2.0");
    vertices.unbind();
    if (!isVertexArray && indices.getNumIndices() > 0) indices.unbind();
  }

  /** 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) {
    if (!Gdx.graphics.isGL20Available()) throw new IllegalStateException("can't use this render method with OpenGL ES 1.x");

    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) {
    if (!Gdx.graphics.isGL20Available()) {
      throw new IllegalStateException("can't use this render method with OpenGL ES 1.x");
    }

    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 is intended for use with OpenGL ES 1.x and will throw an IllegalStateException when OpenGL ES 2.0 is used.
   * </p>
   *
   * @param primitiveType the primitive type */
  public void render (int primitiveType) {
    render(primitiveType, 0, indices.getNumMaxIndices() > 0 ? getNumIndices() : getNumVertices(), autoBind);
  }

  /** <p>
   * Renders the mesh using the given primitive type. offset specifies the offset into vertex buffer and is ignored for the index
   * buffer. Count specifies the number of vertices or indices to use thus count / #vertices per primitive primitives are
   * rendered.
   * </p>
   *
   * <p>
   * This method is intended for use with OpenGL ES 1.x and will throw an IllegalStateException when OpenGL ES 2.0 is used.
   * </p>
   *
   * @param primitiveType the primitive type
   * @param offset the offset into the vertex buffer, ignored for indexed rendering
   * @param count number of vertices or indices to use */
  public void render (int primitiveType, int offset, int count) {
    render (primitiveType, offset, count, autoBind);
  }
 
  /** <p>
   * Renders the mesh using the given primitive type. offset specifies the offset into vertex buffer and is ignored for the index
   * buffer. Count specifies the number of vertices or indices to use thus count / #vertices per primitive primitives are
   * rendered.
   * </p>
   *
   * <p>
   * This method is intended for use with OpenGL ES 1.x and will throw an IllegalStateException when OpenGL ES 2.0 is used.
   * </p>
   *
   * @param primitiveType the primitive type
   * @param offset the offset into the vertex buffer, ignored for indexed rendering
   * @param count number of vertices or indices to use
   * @param autoBind overrides the autoBind member of this Mesh */
  public void render (int primitiveType, int offset, int count, boolean autoBind) {
    if (Gdx.graphics.isGL20Available()) throw new IllegalStateException("can't use this render method with OpenGL ES 2.0");
    if (count == 0) return;
    if (autoBind) bind();

    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.gl10.glDrawElements(primitiveType, count, GL10.GL_UNSIGNED_SHORT, buffer);
        buffer.position(oldPosition);
        buffer.limit(oldLimit);
      } else
        Gdx.gl10.glDrawArrays(primitiveType, offset, count);
    } else {
      if (indices.getNumIndices() > 0)
        Gdx.gl11.glDrawElements(primitiveType, count, GL10.GL_UNSIGNED_SHORT, offset * 2);
      else
        Gdx.gl11.glDrawArrays(primitiveType, offset, count);
    }

    if (autoBind) 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 (!Gdx.graphics.isGL20Available()) throw new IllegalStateException("can't use this render method with OpenGL ES 1.x");
    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, GL10.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, GL10.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");
   
    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;
  }

  /** @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
  protected void transform(final Matrix4 matrix, final int start, final int count) {
    final VertexAttribute posAttr = getVertexAttribute(Usage.Position);
    final int offset = posAttr.offset / 4;
    final int vertexSize = getVertexSize() / 4;
    final int numComponents = posAttr.numComponents;
    final int numVertices = getNumVertices();
   
    final float[] vertices = new float[numVertices * vertexSize];
    // TODO: getVertices(vertices, start * vertexSize, count * vertexSize);
    getVertices(0, vertices.length, vertices);
    transform(matrix, vertices, vertexSize, offset, numComponents, start, count);
    setVertices(vertices, 0, vertices.length);
    // TODO: setVertices(start * vertexSize, vertices, 0, vertices.length);
  }
 
  /**
   * 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);
  }
}
TOP

Related Classes of com.badlogic.gdx.graphics.Mesh

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.