tools.SpriteBatch.java Source code

Java tutorial

Introduction

Here is the source code for tools.SpriteBatch.java

Source

package tools;

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

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
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.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.NumberUtils;

/**
 * <p>
 * A SpriteBatch is used to draw 2D rectangles that reference a texture
 * (region). The class will batch the drawing commands and optimize them for
 * processing by the GPU.
 * </p>
 * 
 * <p>
 * To draw something with a SpriteBatch one has to first call the
 * {@link SpriteBatch#begin()} method which will setup appropriate render
 * states. When you are done with drawing you have to call
 * {@link SpriteBatch#end()} which will actually draw the things you specified.
 * </p>
 * 
 * <p>
 * All drawing commands of the SpriteBatch 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>
 * 
 * <p>
 * A SpriteBatch is managed. In case the OpenGL context is lost all OpenGL
 * resources a SpriteBatch uses internally get invalidated. A context is lost
 * when a user switches to another application or receives an incoming call on
 * Android. A SpriteBatch will be automatically reloaded after the OpenGL
 * context is restored.
 * </p>
 * 
 * <p>
 * A SpriteBatch is a pretty heavy object so you should only ever have one in
 * your program.
 * </p>
 * 
 * <p>
 * A SpriteBatch 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>
 * 
 * <p>
 * A SpriteBatch has to be disposed if it is no longer used.
 * </p>
 * 
 * @author mzechner
 */
public class SpriteBatch implements Disposable {
    static public final int C1 = 2;
    static public final int C2 = 7;

    static public final int C3 = 12;
    static public final int C4 = 17;
    static public final int U1 = 3;

    static public final int U2 = 8;
    static public final int U3 = 13;
    static public final int U4 = 18;

    static public final int V1 = 4;
    static public final int V2 = 9;
    static public final int V3 = 14;

    static public final int V4 = 19;

    static public final int X1 = 0;
    static public final int X2 = 5;
    static public final int X3 = 10;

    static public final int X4 = 15;
    static public final int Y1 = 1;

    static public final int Y2 = 6;
    static public final int Y3 = 11;

    static public final int Y4 = 16;

    /**
     * Returns a new instance of the default shader used by SpriteBatch for GL2
     * when no shader is specified.
     */
    static public ShaderProgram createDefaultShader() {
        String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
                + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
                + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
                + "uniform mat4 u_projectionViewMatrix;\n" //
                + "varying vec4 v_color;\n" //
                + "varying vec2 v_texCoords;\n" //
                + "\n" //
                + "void main()\n" //
                + "{\n" //
                + "   v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
                + "   v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
                + "   gl_Position =  u_projectionViewMatrix * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
                + "}\n";
        String fragmentShader = "#ifdef GL_ES\n" //
                + "#define LOWP lowp\n" //
                + "precision mediump float;\n" //
                + "#else\n" //
                + "#define LOWP \n" //
                + "#endif\n" //
                + "varying LOWP vec4 v_color;\n" //
                + "varying vec2 v_texCoords;\n" //
                + "uniform sampler2D u_texture;\n" //
                + "void main()\n"//
                + "{\n" //
                + "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //
                + "}";

        ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
        if (shader.isCompiled() == false) {
            throw new IllegalArgumentException("couldn't compile shader: " + shader.getLog());
        }
        return shader;
    }

    private int blendDstFunc = GL20.GL_ONE_MINUS_SRC_ALPHA;
    private boolean blendingDisabled = false;

    private int blendSrcFunc = GL20.GL_SRC_ALPHA;

    private Mesh[] buffers;

    float color = Color.WHITE.toFloatBits();

    private final Matrix4 combinedMatrix = new Matrix4();

    private int currBufferIdx = 0;

    private ShaderProgram customShader = null;

    private boolean drawing = false;

    private int idx = 0;

    private float invTexHeight = 0;

    private float invTexWidth = 0;

    private Texture lastTexture = null;

    /** the maximum number of sprites rendered in one batch so far **/
    public int maxSpritesInBatch = 0;

    private Mesh mesh;

    private boolean ownsShader;

    private final Matrix4 projectionMatrix = new Matrix4();

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

    private final ShaderProgram shader;

    private Color tempColor = new Color(1, 1, 1, 1);

    /**
     * number of rendering calls ever, will not be reset, unless it's done
     * manually
     **/
    public int totalRenderCalls = 0;

    private final Matrix4 transformMatrix = new Matrix4();

    private final float[] vertices;

    /**
     * Constructs a new SpriteBatch. 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 screen resolution.
     */
    public SpriteBatch() {
        this(1000);
    }

    /**
     * Constructs a SpriteBatch with the specified size and (if GL2) the default
     * shader. See {@link #SpriteBatch(int, ShaderProgram)}.
     */
    public SpriteBatch(int size) {
        this(size, null);
    }

    /**
     * Constructs a SpriteBatch with the specified size and number of buffers
     * and (if GL2) the default shader. See
     * {@link #SpriteBatch(int, int, ShaderProgram)}.
     */
    public SpriteBatch(int size, int buffers) {
        this(size, buffers, null);
    }

    /**
     * <p>
     * Constructs a new SpriteBatch. 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 screen resolution.
     * </p>
     * 
     * <p>
     * The size parameter specifies the maximum size of a single batch in number
     * of sprites
     * </p>
     * 
     * <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 the
     * {@link #createDefaultShader()} method.
     * </p>
     * 
     * @param size
     *            the batch size in number of sprites
     * @param buffers
     *            the number of buffers to use. only makes sense with VBOs. This
     *            is an expert function.
     * @param defaultShader
     *            the default shader to use. This is not owned by the
     *            SpriteBatch and must be disposed separately.
     */
    public SpriteBatch(int size, int buffers, ShaderProgram defaultShader) {
        this.buffers = new Mesh[buffers];

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

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

        vertices = new float[size * 24];

        int len = size * 6;
        short[] indices = new short[len];
        short j = 0;
        for (int i = 0; i < len; i += 6, j += 4) {
            indices[i + 0] = (short) (j + 0);
            indices[i + 1] = (short) (j + 1);
            indices[i + 2] = (short) (j + 2);
            indices[i + 3] = (short) (j + 2);
            indices[i + 4] = (short) (j + 3);
            indices[i + 5] = (short) (j + 0);
        }
        for (int i = 0; i < buffers; i++) {
            this.buffers[i].setIndices(indices);
        }
        mesh = this.buffers[0];

        if (defaultShader == null) {
            shader = createDefaultShader();
            ownsShader = true;
        } else {
            shader = defaultShader;
        }
    }

    /**
     * <p>
     * Constructs a new SpriteBatch. 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 screen resolution.
     * </p>
     * 
     * <p>
     * The size parameter specifies the maximum size of a single batch in number
     * of sprites
     * </p>
     * 
     * <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 the
     * {@link #createDefaultShader()} method.
     * </p>
     * 
     * @param size
     *            the batch size in number of sprites
     * @param defaultShader
     *            the default shader to use. This is not owned by the
     *            SpriteBatch and must be disposed separately.
     */
    public SpriteBatch(int size, ShaderProgram defaultShader) {
        this(size, 1, defaultShader);
    }

    /**
     * Sets up the SpriteBatch 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("you have to call SpriteBatch.end() first");
        }
        renderCalls = 0;

        Gdx.gl.glDepthMask(false);
        if (customShader != null) {
            customShader.begin();
        } else {
            shader.begin();
        }
        setupMatrices();

        idx = 0;
        lastTexture = null;
        drawing = true;
    }

    /**
     * Disables blending for drawing sprites. Does not disable blending for text
     * rendering
     */
    public void disableBlending() {
        renderMesh();
        blendingDisabled = true;
    }

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

    public void drawWithSize(Texture texture, float[] spriteVertices, int verts) {
        if (!drawing) {
            throw new IllegalStateException("SpriteBatch.begin must be called before draw.");
        }

        if (texture != lastTexture) {
            switchTexture(texture);
        }

        int remainingVertices = vertices.length - idx;
        if (remainingVertices == 0) {
            renderMesh();
            remainingVertices = vertices.length;
        }

        for (int c = 0; c < verts; c++) {
            vertices[idx++] = spriteVertices[c * 5 + 0];
            vertices[idx++] = spriteVertices[c * 5 + 1];
            vertices[idx++] = 0;
            vertices[idx++] = spriteVertices[c * 5 + 2];
            vertices[idx++] = spriteVertices[c * 5 + 3];
            vertices[idx++] = spriteVertices[c * 5 + 4];
        }
    }

    public void draw(TextureRegion region, float x, float y, float z, float width, float height) {
        if (!drawing)
            throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

        Texture texture = region.getTexture();
        if (texture != lastTexture) {
            switchTexture(texture);
        } else if (idx == vertices.length) //
            renderMesh();

        final float fx2 = x + width;
        final float fy2 = y + height;
        final float u = region.getU();
        final float v = region.getV2();
        final float u2 = region.getU2();
        final float v2 = region.getV();

        vertices[idx++] = x;
        vertices[idx++] = y;
        vertices[idx++] = z;
        vertices[idx++] = color;
        vertices[idx++] = u;
        vertices[idx++] = v;

        vertices[idx++] = x;
        vertices[idx++] = fy2;
        vertices[idx++] = z;
        vertices[idx++] = color;
        vertices[idx++] = u;
        vertices[idx++] = v2;

        vertices[idx++] = fx2;
        vertices[idx++] = fy2;
        vertices[idx++] = z;
        vertices[idx++] = color;
        vertices[idx++] = u2;
        vertices[idx++] = v2;

        vertices[idx++] = fx2;
        vertices[idx++] = y;
        vertices[idx++] = z;
        vertices[idx++] = color;
        vertices[idx++] = u2;
        vertices[idx++] = v;
    }

    public void draw(Texture texture, float[] spriteVertices, float z) {
        if (!drawing) {
            throw new IllegalStateException("SpriteBatch.begin must be called before draw.");
        }

        if (texture != lastTexture) {
            switchTexture(texture);
        }

        int remainingVertices = vertices.length - idx;
        if (remainingVertices == 0) {
            renderMesh();
            remainingVertices = vertices.length;
        }

        vertices[idx++] = spriteVertices[0];
        vertices[idx++] = spriteVertices[1];
        vertices[idx++] = z;
        vertices[idx++] = spriteVertices[2];
        vertices[idx++] = spriteVertices[3];
        vertices[idx++] = spriteVertices[4];

        vertices[idx++] = spriteVertices[5];
        vertices[idx++] = spriteVertices[6];
        vertices[idx++] = z;
        vertices[idx++] = spriteVertices[7];
        vertices[idx++] = spriteVertices[8];
        vertices[idx++] = spriteVertices[9];

        vertices[idx++] = spriteVertices[10];
        vertices[idx++] = spriteVertices[11];
        vertices[idx++] = z;
        vertices[idx++] = spriteVertices[12];
        vertices[idx++] = spriteVertices[13];
        vertices[idx++] = spriteVertices[14];

        vertices[idx++] = spriteVertices[15];
        vertices[idx++] = spriteVertices[16];
        vertices[idx++] = z;
        vertices[idx++] = spriteVertices[17];
        vertices[idx++] = spriteVertices[18];
        vertices[idx++] = spriteVertices[19];
    }

    public void drawTriangles(Texture texture, float[] spriteVertices, float r, float g, float b, float a) {
        if (!drawing) {
            throw new IllegalStateException("SpriteBatch.begin must be called before draw.");
        }

        if (texture != lastTexture) {
            switchTexture(texture);
        }

        int remainingVertices = vertices.length - idx;
        if (remainingVertices == 0) {
            renderMesh();
            remainingVertices = vertices.length;
        }

        vertices[idx++] = spriteVertices[0];
        vertices[idx++] = spriteVertices[1];
        vertices[idx++] = 0;
        vertices[idx++] = Color.toFloatBits(r, g, b, a);
        vertices[idx++] = spriteVertices[2];
        vertices[idx++] = spriteVertices[3];

        vertices[idx++] = spriteVertices[4];
        vertices[idx++] = spriteVertices[5];
        vertices[idx++] = 0;
        vertices[idx++] = Color.toFloatBits(r, g, b, a);
        vertices[idx++] = spriteVertices[6];
        vertices[idx++] = spriteVertices[7];

        vertices[idx++] = spriteVertices[8];
        vertices[idx++] = spriteVertices[9];
        vertices[idx++] = 0;
        vertices[idx++] = Color.toFloatBits(r, g, b, a);
        vertices[idx++] = spriteVertices[10];
        vertices[idx++] = spriteVertices[11];

        vertices[idx++] = spriteVertices[12];
        vertices[idx++] = spriteVertices[13];
        vertices[idx++] = 0;
        vertices[idx++] = Color.toFloatBits(r, g, b, a);
        vertices[idx++] = spriteVertices[14];
        vertices[idx++] = spriteVertices[15];

    }

    /**
     * Draws a rectangle using the given vertices. There must be 4 vertices,
     * each made up of 5 elements in this order: x, y, color, u, v.
     */
    public void draw(Texture texture, float[] spriteVertices, int offset, int length) {
        if (!drawing) {
            throw new IllegalStateException("SpriteBatch.begin must be called before draw.");
        }

        if (texture != lastTexture) {
            switchTexture(texture);
        }

        int remainingVertices = vertices.length - idx;
        if (remainingVertices == 0) {
            renderMesh();
            remainingVertices = vertices.length;
        }
        int vertexCount = Math.min(remainingVertices, length - offset);
        System.arraycopy(spriteVertices, offset, vertices, idx, vertexCount);
        offset += vertexCount;
        idx += vertexCount;

        while (offset < length) {
            renderMesh();
            vertexCount = Math.min(vertices.length, length - offset);
            System.arraycopy(spriteVertices, offset, vertices, 0, vertexCount);
            offset += vertexCount;
            idx += vertexCount;
        }
    }

    /** Enables blending for sprites */
    public void enableBlending() {
        renderMesh();
        blendingDisabled = false;
    }

    /**
     * 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("SpriteBatch.begin must be called before end.");
        }
        if (idx > 0) {
            renderMesh();
        }
        lastTexture = null;
        idx = 0;
        drawing = false;

        GL20 gl = Gdx.gl;
        gl.glDepthMask(true);
        if (isBlendingEnabled()) {
            gl.glDisable(GL20.GL_BLEND);
        }

        if (customShader != null) {
            customShader.end();
        } else {
            shader.end();
        }
    }

    /**
     * Causes any pending sprites to be rendered, without ending the
     * SpriteBatch.
     */
    public void flush() {
        renderMesh();
    }

    /**
     * @return the rendering color of this SpriteBatch. Manipulating the
     *         returned instance has no effect.
     */
    public Color getColor() {
        int intBits = NumberUtils.floatToIntColor(color);
        Color color = 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;
    }

    /**
     * Returns the current projection matrix. Changing this will result in
     * undefined behaviour.
     * 
     * @return the currently set projection matrix
     */
    public Matrix4 getProjectionMatrix() {
        return projectionMatrix;
    }

    /**
     * Returns the current transform matrix. Changing this will result in
     * undefined behaviour.
     * 
     * @return the currently set transform matrix
     */
    public Matrix4 getTransformMatrix() {
        return transformMatrix;
    }

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

    private void renderMesh() {
        if (idx == 0) {
            return;
        }

        renderCalls++;
        totalRenderCalls++;
        int spritesInBatch = idx / 24;
        if (spritesInBatch > maxSpritesInBatch) {
            maxSpritesInBatch = spritesInBatch;
        }

        lastTexture.bind();
        mesh.setVertices(vertices, 0, idx);
        mesh.getIndicesBuffer().position(0);
        mesh.getIndicesBuffer().limit(spritesInBatch * 6);

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

        if (customShader != null) {
            mesh.render(customShader, GL20.GL_TRIANGLES, 0, spritesInBatch * 6);
        } else {
            mesh.render(shader, GL20.GL_TRIANGLES, 0, spritesInBatch * 6);
        }

        idx = 0;
        currBufferIdx++;
        if (currBufferIdx == buffers.length) {
            currBufferIdx = 0;
        }
        mesh = buffers[currBufferIdx];
    }

    /**
     * Sets the blending function to be used when rendering sprites.
     * 
     * @param srcFunc
     *            the source function, e.g. GL20.GL_SRC_ALPHA
     * @param dstFunc
     *            the destination function, e.g. GL20.GL_ONE_MINUS_SRC_ALPHA
     */
    public void setBlendFunction(int srcFunc, int dstFunc) {
        renderMesh();
        blendSrcFunc = srcFunc;
        blendDstFunc = dstFunc;
    }

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

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

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

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

    /**
     * Sets the shader to be used in a GLES 2.0 environment. Vertex position
     * attribute is called "a_position", the texture coordinates attribute is
     * called called "a_texCoords0", the color attribute is called "a_color".
     * See {@link ShaderProgram#POSITION_ATTRIBUTE},
     * {@link ShaderProgram#COLOR_ATTRIBUTE} and
     * {@link ShaderProgram#TEXCOORD_ATTRIBUTE} which gets "0" appened to
     * indicate the use of the first texture unit. The projection matrix is
     * uploaded via a mat4 uniform called "u_proj", the transform matrix is
     * uploaded via a uniform called "u_trans", the combined transform and
     * projection matrx is is uploaded via a mat4 uniform called "u_projTrans".
     * The texture sampler is passed via a uniform called "u_texture".</p>
     * 
     * Call this method with a null argument to use the default shader.</p>
     * 
     * This method will flush the batch before setting the new shader, you can
     * call it in between {@link #begin()} and {@link #end()}.
     * 
     * @param shader
     *            the {@link ShaderProgram} or null to use the default shader.
     */
    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();
        }
    }

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

    private void setupMatrices() {
        combinedMatrix.set(projectionMatrix).mul(transformMatrix);
        if (customShader != null) {
            customShader.setUniformMatrix("u_proj", projectionMatrix);
            customShader.setUniformMatrix("u_trans", transformMatrix);
            customShader.setUniformMatrix("u_projTrans", combinedMatrix);
            customShader.setUniformi("u_texture", 0);
        } else {
            shader.setUniformMatrix("u_projectionViewMatrix", combinedMatrix);
            shader.setUniformi("u_texture", 0);
        }
    }

    private void switchTexture(Texture texture) {
        renderMesh();
        lastTexture = texture;
        invTexWidth = 1.0f / texture.getWidth();
        invTexHeight = 1.0f / texture.getHeight();
    }
}