Package com.badlogic.gdx.graphics.g2d

Source Code of com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch

/*******************************************************************************
* 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.g2d;

import static com.badlogic.gdx.graphics.g2d.Sprite.*;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GL11;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Mesh.VertexDataType;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.NumberUtils;

/** A PolygonSpriteBatch is used to draw 2D polygons that reference a texture (region). The class will batch the drawing commands
* and optimize them for processing by the GPU.
* <p>
* To draw something with a PolygonSpriteBatch one has to first call the {@link PolygonSpriteBatch#begin()} method which will
* setup appropriate render states. When you are done with drawing you have to call {@link PolygonSpriteBatch#end()} which will
* actually draw the things you specified.
* <p>
* All drawing commands of the PolygonSpriteBatch operate in screen coordinates. The screen coordinate system has an x-axis
* pointing to the right, an y-axis pointing upwards and the origin is in the lower left corner of the screen. You can also
* provide your own transformation and projection matrices if you so wish.
* <p>
* A PolygonSpriteBatch is managed. In case the OpenGL context is lost all OpenGL resources a PolygonSpriteBatch uses internally
* get invalidated. A context is lost when a user switches to another application or receives an incoming call on Android. A
* SpritPolygonSpriteBatcheBatch will be automatically reloaded after the OpenGL context is restored.
* <p>
* A PolygonSpriteBatch is a pretty heavy object so you should only ever have one in your program.
* <p>
* A PolygonSpriteBatch works with OpenGL ES 1.x and 2.0. In the case of a 2.0 context it will use its own custom shader to draw
* all provided sprites. You can set your own custom shader via {@link #setShader(ShaderProgram)}.
* <p>
* A PolygonSpriteBatch has to be disposed if it is no longer used.
* @author mzechner
* @author Stefan Bachmann
* @author Nathan Sweet */
public class PolygonSpriteBatch {
  private Mesh mesh;
  private Mesh[] buffers;
  private int bufferIndex;

  private final float[] vertices;
  private final short[] triangles;
  private int vertexIndex, triangleIndex;
  private Texture lastTexture;
  private boolean drawing;

  private final Matrix4 transformMatrix = new Matrix4();
  private final Matrix4 projectionMatrix = new Matrix4();
  private final Matrix4 combinedMatrix = new Matrix4();

  private boolean blendingDisabled;
  private int blendSrcFunc = GL11.GL_SRC_ALPHA;
  private int blendDstFunc = GL11.GL_ONE_MINUS_SRC_ALPHA;

  private final ShaderProgram shader;
  private ShaderProgram customShader;
  private boolean ownsShader;

  float color = Color.WHITE.toFloatBits();
  private Color tempColor = new Color(1, 1, 1, 1);

  /** Number of render calls since the last {@link #begin()}. **/
  public int renderCalls = 0;

  /** Number of rendering calls, ever. Will not be reset unless set manually. **/
  public int totalRenderCalls = 0;

  /** The maximum number of triangles rendered in one batch so far. **/
  public int maxTrianglesInBatch = 0;

  /** Constructs a new PolygonSpriteBatch with a size of 2000, the default shader, and one buffer.
   * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
  public PolygonSpriteBatch () {
    this(2000, null);
  }

  /** Constructs a PolygonSpriteBatch with the default shader and one buffer.
   * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
  public PolygonSpriteBatch (int size) {
    this(size, 1, null);
  }

  /** Constructs a new PolygonSpriteBatch with one buffer.
   * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
  public PolygonSpriteBatch (int size, ShaderProgram defaultShader) {
    this(size, 1, defaultShader);
  }

  /** Constructs a PolygonSpriteBatch with the default shader.
   * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
  public PolygonSpriteBatch (int size, int buffers) {
    this(size, buffers, null);
  }

  /** Constructs a new PolygonSpriteBatch. Sets the projection matrix to an orthographic projection with y-axis point upwards,
   * x-axis point to the right and the origin being in the bottom left corner of the screen. The projection will be pixel perfect
   * with respect to the current screen resolution.
   * <p>
   * The defaultShader specifies the shader to use. Note that the names for uniforms for this default shader are different than
   * the ones expect for shaders set with {@link #setShader(ShaderProgram)}. See {@link SpriteBatch#createDefaultShader()}.
   * @param size The max number of vertices and number of triangles in a single batch. Max of 10920.
   * @param buffers The number of meshes to use. This is an expert function. It only makes sense with VBOs (see
   *           {@link Mesh#forceVBO}).
   * @param defaultShader The default shader to use. This is not owned by the PolygonSpriteBatch and must be disposed separately. */
  public PolygonSpriteBatch (int size, int buffers, ShaderProgram defaultShader) {
    // 32767 is max index, so 32767 / 3 - (32767 / 3 % 3) = 10920.
    if (size > 10920) throw new IllegalArgumentException("Can't have more than 10920 triangles per batch: " + size);

    this.buffers = new Mesh[buffers];
    for (int i = 0; i < buffers; i++) {
      this.buffers[i] = new Mesh(VertexDataType.VertexArray, false, size, size * 3, new VertexAttribute(Usage.Position, 2,
        ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
        new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));
    }
    mesh = this.buffers[0];

    vertices = new float[size * VERTEX_SIZE];
    triangles = new short[size * 3];

    if (Gdx.graphics.isGL20Available() && defaultShader == null) {
      shader = SpriteBatch.createDefaultShader();
      ownsShader = true;
    } else
      shader = defaultShader;

    projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
  }

  /** Sets up the PolygonSpriteBatch for drawing. This will disable depth buffer writting. It enables blending and texturing. If
   * you have more texture units enabled than the first one you have to disable them before calling this. Uses a screen
   * coordinate system by default where everything is given in pixels. You can specify your own projection and modelview matrices
   * via {@link #setProjectionMatrix(Matrix4)} and {@link #setTransformMatrix(Matrix4)}. */
  public void begin () {
    if (drawing) throw new IllegalStateException("PolygonSpriteBatch.end must be called before begin.");
    renderCalls = 0;

    Gdx.gl.glDepthMask(false);
    if (Gdx.graphics.isGL20Available()) {
      if (customShader != null)
        customShader.begin();
      else
        shader.begin();
    } else {
      Gdx.gl.glEnable(GL10.GL_TEXTURE_2D);
    }
    setupMatrices();

    drawing = true;
  }

  /** Finishes off rendering. Enables depth writes, disables blending and texturing. Must always be called after a call to
   * {@link #begin()} */
  public void end () {
    if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before end.");
    if (vertexIndex > 0) flush();
    lastTexture = null;
    drawing = false;

    GLCommon gl = Gdx.gl;
    gl.glDepthMask(true);
    if (isBlendingEnabled()) gl.glDisable(GL10.GL_BLEND);

    if (Gdx.graphics.isGL20Available()) {
      if (customShader != null)
        customShader.end();
      else
        shader.end();
    } else {
      gl.glDisable(GL10.GL_TEXTURE_2D);
    }
  }

  /** Sets the color used to tint images when they are added to the PolygonSpriteBatch. Default is {@link Color#WHITE}. */
  public void setColor (Color tint) {
    color = tint.toFloatBits();
  }

  /** @see #setColor(Color) */
  public void setColor (float r, float g, float b, float a) {
    int intBits = (int)(255 * a) << 24 | (int)(255 * b) << 16 | (int)(255 * g) << 8 | (int)(255 * r);
    color = NumberUtils.intToFloatColor(intBits);
  }

  /** @see #setColor(Color)
   * @see Color#toFloatBits() */
  public void setColor (float color) {
    this.color = color;
  }

  /** @return the rendering color of this PolygonSpriteBatch. Manipulating the returned instance has no effect. */
  public Color getColor () {
    int intBits = NumberUtils.floatToIntColor(color);
    Color color = this.tempColor;
    color.r = (intBits & 0xff) / 255f;
    color.g = ((intBits >>> 8) & 0xff) / 255f;
    color.b = ((intBits >>> 16) & 0xff) / 255f;
    color.a = ((intBits >>> 24) & 0xff) / 255f;
    return color;
  }

  /** Draws a polygon region with the bottom left corner at x,y having the width and height of the region. */
  public void draw (PolygonRegion region, float x, float y) {
    if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");

    final short[] triangles = this.triangles;
    final short[] regionTriangles = region.triangles;
    final int regionTrianglesLength = regionTriangles.length;
    final float[] regionVertices = region.vertices;
    final int regionVerticesLength = regionVertices.length;

    final Texture texture = region.region.texture;
    if (texture != lastTexture)
      switchTexture(texture);
    else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
      flush();

    int triangleIndex = this.triangleIndex;
    int vertexIndex = this.vertexIndex;
    final int startVertex = vertexIndex / VERTEX_SIZE;

    for (int i = 0; i < regionTrianglesLength; i++)
      triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
    this.triangleIndex = triangleIndex;

    final float[] vertices = this.vertices;
    final float color = this.color;
    final float[] textureCoords = region.textureCoords;

    for (int i = 0; i < regionVerticesLength; i += 2) {
      vertices[vertexIndex++] = regionVertices[i] + x;
      vertices[vertexIndex++] = regionVertices[i + 1] + y;
      vertices[vertexIndex++] = color;
      vertices[vertexIndex++] = textureCoords[i];
      vertices[vertexIndex++] = textureCoords[i + 1];
    }
    this.vertexIndex = vertexIndex;
  }

  /** Draws a polygon region with the bottom left corner at x,y and stretching the region to cover the given width and height. */
  public void draw (PolygonRegion region, float x, float y, float width, float height) {
    if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");

    final short[] triangles = this.triangles;
    final short[] regionTriangles = region.triangles;
    final int regionTrianglesLength = regionTriangles.length;
    final float[] regionVertices = region.vertices;
    final int regionVerticesLength = regionVertices.length;
    final TextureRegion textureRegion = region.region;

    final Texture texture = textureRegion.texture;
    if (texture != lastTexture)
      switchTexture(texture);
    else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
      flush();

    int triangleIndex = this.triangleIndex;
    int vertexIndex = this.vertexIndex;
    final int startVertex = vertexIndex / VERTEX_SIZE;

    for (int i = 0, n = regionTriangles.length; i < n; i++)
      triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
    this.triangleIndex = triangleIndex;

    final float[] vertices = this.vertices;
    final float color = this.color;
    final float[] textureCoords = region.textureCoords;
    final float sX = width / textureRegion.regionWidth;
    final float sY = height / textureRegion.regionHeight;

    for (int i = 0; i < regionVerticesLength; i += 2) {
      vertices[vertexIndex++] = regionVertices[i] * sX + x;
      vertices[vertexIndex++] = regionVertices[i + 1] * sY + y;
      vertices[vertexIndex++] = color;
      vertices[vertexIndex++] = textureCoords[i];
      vertices[vertexIndex++] = textureCoords[i + 1];
    }
    this.vertexIndex = vertexIndex;
  }

  /** Draws the polygon region with the bottom left corner at x,y and stretching the region to cover the given width and height.
   * The polygon region is offset by originX, originY relative to the origin. Scale specifies the scaling factor by which the
   * polygon region should be scaled around originX, originY. Rotation specifies the angle of counter clockwise rotation of the
   * rectangle around originX, originY. */
  public void draw (PolygonRegion region, float x, float y, float originX, float originY, float width, float height,
    float scaleX, float scaleY, float rotation) {
    if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");

    final short[] triangles = this.triangles;
    final short[] regionTriangles = region.triangles;
    final int regionTrianglesLength = regionTriangles.length;
    final float[] regionVertices = region.vertices;
    final int regionVerticesLength = regionVertices.length;
    final TextureRegion textureRegion = region.region;

    Texture texture = textureRegion.texture;
    if (texture != lastTexture)
      switchTexture(texture);
    else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
      flush();

    int triangleIndex = this.triangleIndex;
    int vertexIndex = this.vertexIndex;
    final int startVertex = vertexIndex / VERTEX_SIZE;

    for (int i = 0; i < regionTrianglesLength; i++)
      triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
    this.triangleIndex = triangleIndex;

    final float[] vertices = this.vertices;
    final float color = this.color;
    final float[] textureCoords = region.textureCoords;

    final float worldOriginX = x + originX;
    final float worldOriginY = y + originY;
    final float sX = width / textureRegion.regionWidth;
    final float sY = height / textureRegion.regionHeight;
    final float cos = MathUtils.cosDeg(rotation);
    final float sin = MathUtils.sinDeg(rotation);

    float fx, fy;
    for (int i = 0; i < regionVerticesLength; i += 2) {
      fx = (regionVertices[i] * sX - originX) * scaleX;
      fy = (regionVertices[i + 1] * sY - originY) * scaleY;
      vertices[vertexIndex++] = cos * fx - sin * fy + worldOriginX;
      vertices[vertexIndex++] = sin * fx + cos * fy + worldOriginY;
      vertices[vertexIndex++] = color;
      vertices[vertexIndex++] = textureCoords[i];
      vertices[vertexIndex++] = textureCoords[i + 1];
    }
    this.vertexIndex = vertexIndex;
  }

  /** Draws the polygon using the given vertices and triangles. Each vertices must be made up of 5 elements in this order: x, y,
   * color, u, v. */
  public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
    int trianglesOffset, int trianglesCount) {
    if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");

    final short[] triangles = this.triangles;
    final float[] vertices = this.vertices;

    if (texture != lastTexture)
      switchTexture(texture);
    else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
      flush();

    int triangleIndex = this.triangleIndex;
    final int vertexIndex = this.vertexIndex;
    final int startVertex = vertexIndex / VERTEX_SIZE;

    for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
      triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
    this.triangleIndex = triangleIndex;

    System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
    this.vertexIndex += verticesCount;
  }

  /** Causes any pending sprites to be rendered, without ending the PolygonSpriteBatch. */
  public void flush () {
    if (vertexIndex == 0) return;

    renderCalls++;
    totalRenderCalls++;
    int trianglesInBatch = triangleIndex;
    if (trianglesInBatch > maxTrianglesInBatch) maxTrianglesInBatch = trianglesInBatch;

    lastTexture.bind();
    Mesh mesh = this.mesh;
    mesh.setVertices(vertices, 0, vertexIndex);
    mesh.setIndices(triangles, 0, triangleIndex);

    if (blendingDisabled) {
      Gdx.gl.glDisable(GL20.GL_BLEND);
    } else {
      Gdx.gl.glEnable(GL20.GL_BLEND);
      if (blendSrcFunc != -1) Gdx.gl.glBlendFunc(blendSrcFunc, blendDstFunc);
    }

    if (Gdx.graphics.isGL20Available())
      mesh.render(customShader != null ? customShader : shader, GL10.GL_TRIANGLES, 0, trianglesInBatch);
    else
      mesh.render(GL10.GL_TRIANGLES, 0, trianglesInBatch);

    vertexIndex = 0;
    triangleIndex = 0;
    bufferIndex++;
    if (bufferIndex == buffers.length) bufferIndex = 0;
    this.mesh = buffers[bufferIndex];
  }

  /** Disables blending for drawing sprites. Calling this within {@link #begin()}/{@link #end()} will flush the batch. */
  public void disableBlending () {
    flush();
    blendingDisabled = true;
  }

  /** Enables blending for sprites. Calling this within {@link #begin()}/{@link #end()} will flush the batch. */
  public void enableBlending () {
    flush();
    blendingDisabled = false;
  }

  /** Sets the blending function to be used when rendering sprites.
   * @param srcFunc the source function, e.g. GL11.GL_SRC_ALPHA. If set to -1, PolygonSpriteBatch won't change the blending
   *           function.
   * @param dstFunc the destination function, e.g. GL11.GL_ONE_MINUS_SRC_ALPHA */
  public void setBlendFunction (int srcFunc, int dstFunc) {
    if (blendSrcFunc == srcFunc && blendDstFunc == dstFunc) return;
    flush();
    blendSrcFunc = srcFunc;
    blendDstFunc = dstFunc;
  }

  /** Disposes all resources associated with this PolygonSpriteBatch. */
  public void dispose () {
    for (int i = 0; i < buffers.length; i++)
      buffers[i].dispose();
    if (ownsShader && shader != null) shader.dispose();
  }

  /** Returns the current projection matrix. Changing this within {@link #begin()}/{@link #end()} results in undefined behaviour. */
  public Matrix4 getProjectionMatrix () {
    return projectionMatrix;
  }

  /** Returns the current transform matrix. Changing this within {@link #begin()}/{@link #end()} results in undefined behaviour. */
  public Matrix4 getTransformMatrix () {
    return transformMatrix;
  }

  /** Sets the projection matrix to be used by this PolygonSpriteBatch. If this is called inside a {@link #begin()}/{@link #end()}
   * block, the current batch is flushed to the gpu. */
  public void setProjectionMatrix (Matrix4 projection) {
    if (drawing) flush();
    projectionMatrix.set(projection);
    if (drawing) setupMatrices();
  }

  /** Sets the transform matrix to be used by this PolygonSpriteBatch. If this is called inside a {@link #begin()}/{@link #end()}
   * block, the current batch is flushed to the gpu. */
  public void setTransformMatrix (Matrix4 transform) {
    if (drawing) flush();
    transformMatrix.set(transform);
    if (drawing) setupMatrices();
  }

  private void setupMatrices () {
    if (!Gdx.graphics.isGL20Available()) {
      GL10 gl = Gdx.gl10;
      gl.glMatrixMode(GL10.GL_PROJECTION);
      gl.glLoadMatrixf(projectionMatrix.val, 0);
      gl.glMatrixMode(GL10.GL_MODELVIEW);
      gl.glLoadMatrixf(transformMatrix.val, 0);
    } else {
      combinedMatrix.set(projectionMatrix).mul(transformMatrix);
      if (customShader != null) {
        customShader.setUniformMatrix("u_projTrans", combinedMatrix);
        customShader.setUniformi("u_texture", 0);
      } else {
        shader.setUniformMatrix("u_projTrans", combinedMatrix);
        shader.setUniformi("u_texture", 0);
      }
    }
  }

  private void switchTexture (Texture texture) {
    flush();
    lastTexture = texture;
  }

  /** @see SpriteBatch#setShader(ShaderProgram) */
  public void setShader (ShaderProgram shader) {
    if (drawing) {
      flush();
      if (customShader != null)
        customShader.end();
      else
        this.shader.end();
    }
    customShader = shader;
    if (drawing) {
      if (customShader != null)
        customShader.begin();
      else
        this.shader.begin();
      setupMatrices();
    }
  }

  /** @return whether blending for sprites is enabled */
  public boolean isBlendingEnabled () {
    return !blendingDisabled;
  }
}
TOP

Related Classes of com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch

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.