001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Gdx;
004import com.badlogic.gdx.graphics.Color;
005import com.badlogic.gdx.graphics.GL20;
006import com.badlogic.gdx.graphics.Mesh;
007import com.badlogic.gdx.graphics.Texture;
008import com.badlogic.gdx.graphics.VertexAttribute;
009import com.badlogic.gdx.graphics.VertexAttributes;
010import com.badlogic.gdx.graphics.g2d.Batch;
011import com.badlogic.gdx.graphics.g2d.TextureRegion;
012import com.badlogic.gdx.graphics.glutils.ShaderProgram;
013import com.badlogic.gdx.math.Affine2;
014import com.badlogic.gdx.math.MathUtils;
015import com.badlogic.gdx.math.Matrix4;
016import squidpony.squidmath.NumberTools;
017
018/**
019 * A drop-in substitute for {@link com.badlogic.gdx.graphics.g2d.SpriteBatch} that filters any colors used to tint text
020 * or images using a {@link FloatFilter}. The filter may have an effect on speed in some cases, but even moderately
021 * complex filters like {@link FloatFilters.YCbCrFilter} seem to perform perfectly well, spiking at above 1000 FPS on
022 * SparseDemo with a filter that changes parameters. This class should be preferred over SpriteBatch when using
023 * SquidLib's style of frequently changing the Batch color; as of libGDX 1.9.9, SpriteBatch is somewhat slower than
024 * FilterBatch when FilterBatch is using the default {@link FloatFilters#identityFilter}. This is because 1.9.9 changed
025 * SpriteBatch to do more work when setting a color, and FilterBatch keeps the older, faster way of color changing. It
026 * may be slower if used for Tiled map renderers in libGDX or for {@link com.badlogic.gdx.graphics.g2d.NinePatch}
027 * instances, which both need to reset a Color object frequently, but not much else in libGDX specifically needs the
028 * Color object {@link #getColor()} returns, and many things do fine with {@link #getPackedColor()}, which is faster.
029 * Note that for games that use 2D sprite graphics, there's very low overhead to 1.9.9's SpriteBatch, and it mostly has
030 * issues with overdraw (which every glyph of text drawn over a background contributes towards).
031 * <br>
032 * Most FloatFilter varieties don't have much overhead, and I encourage you to try some to quickly produce graphical
033 * effects in a game. As a simple example, {@link FloatFilters#grayscaleFilter} is a constant FloatFilter that makes
034 * all colors, well, grayscale; you could apply it during things like flashback sequences or when the player is dazed.
035 * <br>
036 * As a more complex example, changing the warmth of a color (as well as its lightness) is relatively easy with a
037 * {@link FloatFilters.YCwCmFilter} that adds to or subtracts from Cw (chromatic warmth) and may change Y (lightness).
038 * With that same filter, if the protagonist is partially blinded, you could set the multipliers for Cw and Cm
039 * (chromatic mildness; determines whether a color is closer to mild colors like green/yellow or bold colors like
040 * red/blue) to floats between 0.1 and 0.5 or so, which takes even intense colors and makes them diluted and grayish.
041 * You could also change Y to be brighter or darker depending on whether a bright light blinded the protagonist, or some
042 * mud was thrown in their eyes, etc.
043 * <br>
044 * Created by Tommy Ettinger on 8/2/2018.
045 */
046public class FilterBatch implements Batch {
047    private static final int SPRITE_SIZE = 20;
048    
049    public FloatFilter filter;
050    
051    private final Mesh mesh;
052
053    final float[] vertices;
054    int idx;
055    Texture lastTexture;
056    float invTexWidth, invTexHeight;
057
058    boolean drawing;
059
060    private final Matrix4 transformMatrix = new Matrix4();
061    private final Matrix4 projectionMatrix = new Matrix4();
062    private final Matrix4 combinedMatrix = new Matrix4();
063
064    private boolean blendingDisabled;
065    private int blendSrcFunc = GL20.GL_SRC_ALPHA;
066    private int blendDstFunc = GL20.GL_ONE_MINUS_SRC_ALPHA;
067    private int blendSrcFuncAlpha = GL20.GL_SRC_ALPHA;
068    private int blendDstFuncAlpha = GL20.GL_ONE_MINUS_SRC_ALPHA;
069
070    private final ShaderProgram shader;
071    private ShaderProgram customShader;
072    private boolean ownsShader;
073
074    float color = Color.WHITE.toFloatBits();
075    private final Color tempColor = new Color(1, 1, 1, 1);
076
077    /** Number of render calls since the last {@link #begin()}. **/
078    public int renderCalls;
079
080    /** Number of rendering calls, ever. Will not be reset unless set manually. **/
081    public int totalRenderCalls;
082
083    /** The maximum number of sprites rendered in one batch so far. **/
084    public int maxSpritesInBatch;
085
086    /** Constructs a new FilterBatch with a size of 1000, one buffer, the default shader, and no changes in the
087     * color filter.
088     * @see FilterBatch#FilterBatch(int, ShaderProgram) */
089    public FilterBatch () {
090        this(1000, null, FloatFilters.identityFilter);
091    }
092
093    /** Constructs a new FilterBatch with a size of 1000, one buffer, the default shader, and the given
094     * color filter.
095     * @see FilterBatch#FilterBatch(int, ShaderProgram, FloatFilter) */
096    public FilterBatch (FloatFilter filter) {
097        this(1000, null, filter);
098    }
099
100    /** Constructs a FilterBatch with one buffer, the default shader, and no changes in the color filter.
101     * @see FilterBatch#FilterBatch(int, ShaderProgram, FloatFilter) */
102    public FilterBatch (int size) {
103        this(size, null, FloatFilters.identityFilter);
104    }
105
106    /** Constructs a new FilterBatch with a size of 1000, one buffer, the default shader, and the given
107     * color filter.
108     * @see FilterBatch#FilterBatch(int, ShaderProgram, FloatFilter) */
109    public FilterBatch (int size, FloatFilter filter) {
110        this(size, null, filter);
111    }
112
113    /** Constructs a new FilterBatch. Sets the projection matrix to an orthographic projection with y-axis point upwards, x-axis
114     * point to the right and the origin being in the bottom left corner of the screen. The projection will be pixel perfect with
115     * respect to the current screen resolution.
116     * <p>
117     * The defaultShader specifies the shader to use. Note that the names for uniforms for this default shader are different than
118     * the ones expect for shaders set with {@link #setShader(ShaderProgram)}. See {@link #createDefaultShader()}.
119     * @param size The max number of sprites in a single batch. Max of 8191.
120     * @param defaultShader The default shader to use. This is not owned by the FilterBatch and must be disposed separately. */
121    public FilterBatch (int size, ShaderProgram defaultShader) {
122        this(size, defaultShader, FloatFilters.identityFilter);
123    }
124
125    /** Constructs a new FilterBatch. Sets the projection matrix to an orthographic projection with y-axis point upwards, x-axis
126     * point to the right and the origin being in the bottom left corner of the screen. The projection will be pixel perfect with
127     * respect to the current screen resolution.
128     * <p>
129     * The defaultShader specifies the shader to use. Note that the names for uniforms for this default shader are different than
130     * the ones expect for shaders set with {@link #setShader(ShaderProgram)}. See {@link #createDefaultShader()}.
131     * @param size The max number of sprites in a single batch. Max of 8191.
132     * @param defaultShader The default shader to use. This is not owned by the FilterBatch and must be disposed separately. */
133    public FilterBatch (int size, ShaderProgram defaultShader, FloatFilter filter) {
134        // 32767 is max vertex index, so 32767 / 4 vertices per sprite = 8191 sprites max.
135        if (size > 8191) throw new IllegalArgumentException("Can't have more than 8191 sprites per batch: " + size);
136
137        Mesh.VertexDataType vertexDataType = (Gdx.gl30 != null) ? Mesh.VertexDataType.VertexBufferObjectWithVAO : Mesh.VertexDataType.VertexArray;
138
139        mesh = new Mesh(vertexDataType, false, size * 4, size * 6,
140                new VertexAttribute(VertexAttributes.Usage.Position, 2, ShaderProgram.POSITION_ATTRIBUTE),
141                new VertexAttribute(VertexAttributes.Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
142                new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));
143
144        projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
145
146        vertices = new float[size * SPRITE_SIZE];
147
148        int len = size * 6;
149        short[] indices = new short[len];
150        short j = 0;
151        for (int i = 0; i < len; i += 6, j += 4) {
152            indices[i] = j;
153            indices[i + 1] = (short)(j + 1);
154            indices[i + 2] = (short)(j + 2);
155            indices[i + 3] = (short)(j + 2);
156            indices[i + 4] = (short)(j + 3);
157            indices[i + 5] = j;
158        }
159        mesh.setIndices(indices);
160
161        if (defaultShader == null) {
162            shader = createDefaultShader();
163            ownsShader = true;
164        } else
165            shader = defaultShader;
166        this.filter = filter;
167    }
168
169    /** Returns a new instance of the default shader used by FilterBatch for GL2 when no shader is specified. */
170    static public ShaderProgram createDefaultShader () {
171        String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
172                + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
173                + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
174                + "uniform mat4 u_projTrans;\n" //
175                + "varying vec4 v_color;\n" //
176                + "varying vec2 v_texCoords;\n" //
177                + "\n" //
178                + "void main()\n" //
179                + "{\n" //
180                + "   v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
181                + "   v_color.a = v_color.a * (255.0/254.0);\n" //
182                + "   v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
183                + "   gl_Position =  u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
184                + "}\n";
185        String fragmentShader = "#ifdef GL_ES\n" //
186                + "#define LOWP lowp\n" //
187                + "precision mediump float;\n" //
188                + "#else\n" //
189                + "#define LOWP \n" //
190                + "#endif\n" //
191                + "varying LOWP vec4 v_color;\n" //
192                + "varying vec2 v_texCoords;\n" //
193                + "uniform sampler2D u_texture;\n" //
194                + "void main()\n"//
195                + "{\n" //
196                + "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //
197                + "}";
198
199        ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
200        if (!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());
201        return shader;
202    }
203
204    @Override
205    public void begin () {
206        if (drawing) throw new IllegalStateException("FilterBatch.end must be called before begin.");
207        renderCalls = 0;
208
209        Gdx.gl.glDepthMask(false);
210        if (customShader != null)
211            customShader.begin();
212        else
213            shader.begin();
214        setupMatrices();
215
216        drawing = true;
217    }
218
219    @Override
220    public void end () {
221        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before end.");
222        if (idx > 0) flush();
223        lastTexture = null;
224        drawing = false;
225
226        GL20 gl = Gdx.gl;
227        gl.glDepthMask(true);
228        if (isBlendingEnabled()) gl.glDisable(GL20.GL_BLEND);
229
230        if (customShader != null)
231            customShader.end();
232        else
233            shader.end();
234    }
235
236    @Override
237    public void setColor (Color tint) {
238        color = filter.alter(tint.toFloatBits());
239    }
240
241    @Override
242    public void setColor (float r, float g, float b, float a) {
243        color = filter.alter(NumberTools.intBitsToFloat(((int)(255 * a) << 24 & 0xFE000000)
244                | (int)(255 * b) << 16 | (int)(255 * g) << 8 | (int)(255 * r)));
245    }
246
247    public void setColor (final float color) {
248        this.color = filter.alter(color);
249    }
250
251    @Override
252    public void setPackedColor (final float color) {
253        this.color = filter.alter(color);
254    }
255
256    @Override
257    public Color getColor () {
258        final int intBits = NumberTools.floatToIntBits(color);
259        Color color = tempColor;
260        color.r = (intBits & 0xff) / 255f;
261        color.g = ((intBits >>> 8) & 0xff) / 255f;
262        color.b = ((intBits >>> 16) & 0xff) / 255f;
263        color.a = ((intBits >>> 24) & 0xff) / 255f;
264        return color;
265    }
266
267    @Override
268    public float getPackedColor () {
269        return color;
270    }
271
272    @Override
273    public void draw (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX,
274                      float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) {
275        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
276
277        float[] vertices = this.vertices;
278
279        if (texture != lastTexture)
280            switchTexture(texture);
281        else if (idx == vertices.length) //
282            flush();
283
284        // bottom left and top right corner points relative to origin
285        final float worldOriginX = x + originX;
286        final float worldOriginY = y + originY;
287        float fx = -originX;
288        float fy = -originY;
289        float fx2 = width - originX;
290        float fy2 = height - originY;
291
292        // scale
293        if (scaleX != 1 || scaleY != 1) {
294            fx *= scaleX;
295            fy *= scaleY;
296            fx2 *= scaleX;
297            fy2 *= scaleY;
298        }
299
300        // construct corner points, start from top left and go counter clockwise
301        final float p1x = fx;
302        final float p1y = fy;
303        final float p2x = fx;
304        final float p2y = fy2;
305        final float p3x = fx2;
306        final float p3y = fy2;
307        final float p4x = fx2;
308        final float p4y = fy;
309
310        float x1;
311        float y1;
312        float x2;
313        float y2;
314        float x3;
315        float y3;
316        float x4;
317        float y4;
318
319        // rotate
320        if (rotation != 0) {
321            final float cos = MathUtils.cosDeg(rotation);
322            final float sin = MathUtils.sinDeg(rotation);
323
324            x1 = cos * p1x - sin * p1y;
325            y1 = sin * p1x + cos * p1y;
326
327            x2 = cos * p2x - sin * p2y;
328            y2 = sin * p2x + cos * p2y;
329
330            x3 = cos * p3x - sin * p3y;
331            y3 = sin * p3x + cos * p3y;
332
333            x4 = x1 + (x3 - x2);
334            y4 = y3 - (y2 - y1);
335        } else {
336            x1 = p1x;
337            y1 = p1y;
338
339            x2 = p2x;
340            y2 = p2y;
341
342            x3 = p3x;
343            y3 = p3y;
344
345            x4 = p4x;
346            y4 = p4y;
347        }
348
349        x1 += worldOriginX;
350        y1 += worldOriginY;
351        x2 += worldOriginX;
352        y2 += worldOriginY;
353        x3 += worldOriginX;
354        y3 += worldOriginY;
355        x4 += worldOriginX;
356        y4 += worldOriginY;
357
358        float u = srcX * invTexWidth;
359        float v = (srcY + srcHeight) * invTexHeight;
360        float u2 = (srcX + srcWidth) * invTexWidth;
361        float v2 = srcY * invTexHeight;
362
363        if (flipX) {
364            float tmp = u;
365            u = u2;
366            u2 = tmp;
367        }
368
369        if (flipY) {
370            float tmp = v;
371            v = v2;
372            v2 = tmp;
373        }
374
375        float color = this.color;
376        int idx = this.idx;
377        vertices[idx] = x1;
378        vertices[idx + 1] = y1;
379        vertices[idx + 2] = color;
380        vertices[idx + 3] = u;
381        vertices[idx + 4] = v;
382
383        vertices[idx + 5] = x2;
384        vertices[idx + 6] = y2;
385        vertices[idx + 7] = color;
386        vertices[idx + 8] = u;
387        vertices[idx + 9] = v2;
388
389        vertices[idx + 10] = x3;
390        vertices[idx + 11] = y3;
391        vertices[idx + 12] = color;
392        vertices[idx + 13] = u2;
393        vertices[idx + 14] = v2;
394
395        vertices[idx + 15] = x4;
396        vertices[idx + 16] = y4;
397        vertices[idx + 17] = color;
398        vertices[idx + 18] = u2;
399        vertices[idx + 19] = v;
400        this.idx = idx + 20;
401    }
402
403    @Override
404    public void draw (Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth,
405                      int srcHeight, boolean flipX, boolean flipY) {
406        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
407
408        float[] vertices = this.vertices;
409
410        if (texture != lastTexture)
411            switchTexture(texture);
412        else if (idx == vertices.length) //
413            flush();
414
415        float u = srcX * invTexWidth;
416        float v = (srcY + srcHeight) * invTexHeight;
417        float u2 = (srcX + srcWidth) * invTexWidth;
418        float v2 = srcY * invTexHeight;
419        final float fx2 = x + width;
420        final float fy2 = y + height;
421
422        if (flipX) {
423            float tmp = u;
424            u = u2;
425            u2 = tmp;
426        }
427
428        if (flipY) {
429            float tmp = v;
430            v = v2;
431            v2 = tmp;
432        }
433
434        float color = this.color;
435        int idx = this.idx;
436        vertices[idx] = x;
437        vertices[idx + 1] = y;
438        vertices[idx + 2] = color;
439        vertices[idx + 3] = u;
440        vertices[idx + 4] = v;
441
442        vertices[idx + 5] = x;
443        vertices[idx + 6] = fy2;
444        vertices[idx + 7] = color;
445        vertices[idx + 8] = u;
446        vertices[idx + 9] = v2;
447
448        vertices[idx + 10] = fx2;
449        vertices[idx + 11] = fy2;
450        vertices[idx + 12] = color;
451        vertices[idx + 13] = u2;
452        vertices[idx + 14] = v2;
453
454        vertices[idx + 15] = fx2;
455        vertices[idx + 16] = y;
456        vertices[idx + 17] = color;
457        vertices[idx + 18] = u2;
458        vertices[idx + 19] = v;
459        this.idx = idx + 20;
460    }
461
462    @Override
463    public void draw (Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) {
464        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
465
466        float[] vertices = this.vertices;
467
468        if (texture != lastTexture)
469            switchTexture(texture);
470        else if (idx == vertices.length) //
471            flush();
472
473        final float u = srcX * invTexWidth;
474        final float v = (srcY + srcHeight) * invTexHeight;
475        final float u2 = (srcX + srcWidth) * invTexWidth;
476        final float v2 = srcY * invTexHeight;
477        final float fx2 = x + srcWidth;
478        final float fy2 = y + srcHeight;
479
480        float color = this.color;
481        int idx = this.idx;
482        vertices[idx] = x;
483        vertices[idx + 1] = y;
484        vertices[idx + 2] = color;
485        vertices[idx + 3] = u;
486        vertices[idx + 4] = v;
487
488        vertices[idx + 5] = x;
489        vertices[idx + 6] = fy2;
490        vertices[idx + 7] = color;
491        vertices[idx + 8] = u;
492        vertices[idx + 9] = v2;
493
494        vertices[idx + 10] = fx2;
495        vertices[idx + 11] = fy2;
496        vertices[idx + 12] = color;
497        vertices[idx + 13] = u2;
498        vertices[idx + 14] = v2;
499
500        vertices[idx + 15] = fx2;
501        vertices[idx + 16] = y;
502        vertices[idx + 17] = color;
503        vertices[idx + 18] = u2;
504        vertices[idx + 19] = v;
505        this.idx = idx + 20;
506    }
507
508    @Override
509    public void draw (Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2) {
510        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
511
512        float[] vertices = this.vertices;
513
514        if (texture != lastTexture)
515            switchTexture(texture);
516        else if (idx == vertices.length) //
517            flush();
518
519        final float fx2 = x + width;
520        final float fy2 = y + height;
521
522        float color = this.color;
523        int idx = this.idx;
524        vertices[idx] = x;
525        vertices[idx + 1] = y;
526        vertices[idx + 2] = color;
527        vertices[idx + 3] = u;
528        vertices[idx + 4] = v;
529
530        vertices[idx + 5] = x;
531        vertices[idx + 6] = fy2;
532        vertices[idx + 7] = color;
533        vertices[idx + 8] = u;
534        vertices[idx + 9] = v2;
535
536        vertices[idx + 10] = fx2;
537        vertices[idx + 11] = fy2;
538        vertices[idx + 12] = color;
539        vertices[idx + 13] = u2;
540        vertices[idx + 14] = v2;
541
542        vertices[idx + 15] = fx2;
543        vertices[idx + 16] = y;
544        vertices[idx + 17] = color;
545        vertices[idx + 18] = u2;
546        vertices[idx + 19] = v;
547        this.idx = idx + 20;
548    }
549
550    @Override
551    public void draw (Texture texture, float x, float y) {
552        draw(texture, x, y, texture.getWidth(), texture.getHeight());
553    }
554
555    @Override
556    public void draw (Texture texture, float x, float y, float width, float height) {
557        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
558
559        float[] vertices = this.vertices;
560
561        if (texture != lastTexture)
562            switchTexture(texture);
563        else if (idx == vertices.length) //
564            flush();
565
566        final float fx2 = x + width;
567        final float fy2 = y + height;
568        final float u = 0;
569        final float v = 1;
570        final float u2 = 1;
571        final float v2 = 0;
572
573        float color = this.color;
574        int idx = this.idx;
575        vertices[idx] = x;
576        vertices[idx + 1] = y;
577        vertices[idx + 2] = color;
578        vertices[idx + 3] = u;
579        vertices[idx + 4] = v;
580
581        vertices[idx + 5] = x;
582        vertices[idx + 6] = fy2;
583        vertices[idx + 7] = color;
584        vertices[idx + 8] = u;
585        vertices[idx + 9] = v2;
586
587        vertices[idx + 10] = fx2;
588        vertices[idx + 11] = fy2;
589        vertices[idx + 12] = color;
590        vertices[idx + 13] = u2;
591        vertices[idx + 14] = v2;
592
593        vertices[idx + 15] = fx2;
594        vertices[idx + 16] = y;
595        vertices[idx + 17] = color;
596        vertices[idx + 18] = u2;
597        vertices[idx + 19] = v;
598        this.idx = idx + 20;
599    }
600
601    @Override
602    public void draw (Texture texture, float[] spriteVertices, int offset, int count) {
603        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
604
605        int verticesLength = vertices.length;
606        int remainingVertices = verticesLength;
607        if (texture != lastTexture)
608            switchTexture(texture);
609        else {
610            remainingVertices -= idx;
611            if (remainingVertices == 0) {
612                flush();
613                remainingVertices = verticesLength;
614            }
615        }
616        int copyCount = Math.min(remainingVertices, count);
617
618        System.arraycopy(spriteVertices, offset, vertices, idx, copyCount);
619        idx += copyCount;
620        count -= copyCount;
621        while (count > 0) {
622            offset += copyCount;
623            flush();
624            copyCount = Math.min(verticesLength, count);
625            System.arraycopy(spriteVertices, offset, vertices, 0, copyCount);
626            idx += copyCount;
627            count -= copyCount;
628        }
629    }
630
631    @Override
632    public void draw (TextureRegion region, float x, float y) {
633        draw(region, x, y, region.getRegionWidth(), region.getRegionHeight());
634    }
635
636    @Override
637    public void draw (TextureRegion region, float x, float y, float width, float height) {
638        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
639
640        float[] vertices = this.vertices;
641
642        Texture texture = region.getTexture();
643        if (texture != lastTexture) {
644            switchTexture(texture);
645        } else if (idx == vertices.length) {
646            flush();
647        }
648        final float fx2 = x + width;
649        final float fy2 = y + height;
650        final float u = region.getU();
651        final float v = region.getV2();
652        final float u2 = region.getU2();
653        final float v2 = region.getV();
654
655        float color = this.color;
656        int idx = this.idx;
657        vertices[idx] = x;
658        vertices[idx + 1] = y;
659        vertices[idx + 2] = color;
660        vertices[idx + 3] = u;
661        vertices[idx + 4] = v;
662
663        vertices[idx + 5] = x;
664        vertices[idx + 6] = fy2;
665        vertices[idx + 7] = color;
666        vertices[idx + 8] = u;
667        vertices[idx + 9] = v2;
668
669        vertices[idx + 10] = fx2;
670        vertices[idx + 11] = fy2;
671        vertices[idx + 12] = color;
672        vertices[idx + 13] = u2;
673        vertices[idx + 14] = v2;
674
675        vertices[idx + 15] = fx2;
676        vertices[idx + 16] = y;
677        vertices[idx + 17] = color;
678        vertices[idx + 18] = u2;
679        vertices[idx + 19] = v;
680        this.idx = idx + 20;
681    }
682
683    @Override
684    public void draw (TextureRegion region, float x, float y, float originX, float originY, float width, float height,
685                      float scaleX, float scaleY, float rotation) {
686        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
687
688        float[] vertices = this.vertices;
689
690        Texture texture = region.getTexture();
691        if (texture != lastTexture) {
692            switchTexture(texture);
693        } else if (idx == vertices.length) //
694            flush();
695
696        // bottom left and top right corner points relative to origin
697        final float worldOriginX = x + originX;
698        final float worldOriginY = y + originY;
699        float fx = -originX;
700        float fy = -originY;
701        float fx2 = width - originX;
702        float fy2 = height - originY;
703
704        // scale
705        if (scaleX != 1 || scaleY != 1) {
706            fx *= scaleX;
707            fy *= scaleY;
708            fx2 *= scaleX;
709            fy2 *= scaleY;
710        }
711
712        // construct corner points, start from top left and go counter clockwise
713        final float p1x = fx;
714        final float p1y = fy;
715        final float p2x = fx;
716        final float p2y = fy2;
717        final float p3x = fx2;
718        final float p3y = fy2;
719        final float p4x = fx2;
720        final float p4y = fy;
721
722        float x1;
723        float y1;
724        float x2;
725        float y2;
726        float x3;
727        float y3;
728        float x4;
729        float y4;
730
731        // rotate
732        if (rotation != 0) {
733            final float cos = MathUtils.cosDeg(rotation);
734            final float sin = MathUtils.sinDeg(rotation);
735
736            x1 = cos * p1x - sin * p1y;
737            y1 = sin * p1x + cos * p1y;
738
739            x2 = cos * p2x - sin * p2y;
740            y2 = sin * p2x + cos * p2y;
741
742            x3 = cos * p3x - sin * p3y;
743            y3 = sin * p3x + cos * p3y;
744
745            x4 = x1 + (x3 - x2);
746            y4 = y3 - (y2 - y1);
747        } else {
748            x1 = p1x;
749            y1 = p1y;
750
751            x2 = p2x;
752            y2 = p2y;
753
754            x3 = p3x;
755            y3 = p3y;
756
757            x4 = p4x;
758            y4 = p4y;
759        }
760
761        x1 += worldOriginX;
762        y1 += worldOriginY;
763        x2 += worldOriginX;
764        y2 += worldOriginY;
765        x3 += worldOriginX;
766        y3 += worldOriginY;
767        x4 += worldOriginX;
768        y4 += worldOriginY;
769
770        final float u = region.getU();
771        final float v = region.getV2();
772        final float u2 = region.getU2();
773        final float v2 = region.getV();
774
775        float color = this.color;
776        int idx = this.idx;
777        vertices[idx] = x1;
778        vertices[idx + 1] = y1;
779        vertices[idx + 2] = color;
780        vertices[idx + 3] = u;
781        vertices[idx + 4] = v;
782
783        vertices[idx + 5] = x2;
784        vertices[idx + 6] = y2;
785        vertices[idx + 7] = color;
786        vertices[idx + 8] = u;
787        vertices[idx + 9] = v2;
788
789        vertices[idx + 10] = x3;
790        vertices[idx + 11] = y3;
791        vertices[idx + 12] = color;
792        vertices[idx + 13] = u2;
793        vertices[idx + 14] = v2;
794
795        vertices[idx + 15] = x4;
796        vertices[idx + 16] = y4;
797        vertices[idx + 17] = color;
798        vertices[idx + 18] = u2;
799        vertices[idx + 19] = v;
800        this.idx = idx + 20;
801    }
802
803    @Override
804    public void draw (TextureRegion region, float x, float y, float originX, float originY, float width, float height,
805                      float scaleX, float scaleY, float rotation, boolean clockwise) {
806        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
807
808        float[] vertices = this.vertices;
809
810        Texture texture = region.getTexture();
811        if (texture != lastTexture) {
812            switchTexture(texture);
813        } else if (idx == vertices.length) //
814            flush();
815
816        // bottom left and top right corner points relative to origin
817        final float worldOriginX = x + originX;
818        final float worldOriginY = y + originY;
819        float fx = -originX;
820        float fy = -originY;
821        float fx2 = width - originX;
822        float fy2 = height - originY;
823
824        // scale
825        if (scaleX != 1 || scaleY != 1) {
826            fx *= scaleX;
827            fy *= scaleY;
828            fx2 *= scaleX;
829            fy2 *= scaleY;
830        }
831
832        // construct corner points, start from top left and go counter clockwise
833        final float p1x = fx;
834        final float p1y = fy;
835        final float p2x = fx;
836        final float p2y = fy2;
837        final float p3x = fx2;
838        final float p3y = fy2;
839        final float p4x = fx2;
840        final float p4y = fy;
841
842        float x1;
843        float y1;
844        float x2;
845        float y2;
846        float x3;
847        float y3;
848        float x4;
849        float y4;
850
851        // rotate
852        if (rotation != 0) {
853            final float cos = MathUtils.cosDeg(rotation);
854            final float sin = MathUtils.sinDeg(rotation);
855
856            x1 = cos * p1x - sin * p1y;
857            y1 = sin * p1x + cos * p1y;
858
859            x2 = cos * p2x - sin * p2y;
860            y2 = sin * p2x + cos * p2y;
861
862            x3 = cos * p3x - sin * p3y;
863            y3 = sin * p3x + cos * p3y;
864
865            x4 = x1 + (x3 - x2);
866            y4 = y3 - (y2 - y1);
867        } else {
868            x1 = p1x;
869            y1 = p1y;
870
871            x2 = p2x;
872            y2 = p2y;
873
874            x3 = p3x;
875            y3 = p3y;
876
877            x4 = p4x;
878            y4 = p4y;
879        }
880
881        x1 += worldOriginX;
882        y1 += worldOriginY;
883        x2 += worldOriginX;
884        y2 += worldOriginY;
885        x3 += worldOriginX;
886        y3 += worldOriginY;
887        x4 += worldOriginX;
888        y4 += worldOriginY;
889
890        float u1, v1, u2, v2, u3, v3, u4, v4;
891        if (clockwise) {
892            u1 = region.getU2();
893            v1 = region.getV2();
894            u2 = region.getU();
895            v2 = region.getV2();
896            u3 = region.getU();
897            v3 = region.getV();
898            u4 = region.getU2();
899            v4 = region.getV();
900        } else {
901            u1 = region.getU();
902            v1 = region.getV();
903            u2 = region.getU2();
904            v2 = region.getV();
905            u3 = region.getU2();
906            v3 = region.getV2();
907            u4 = region.getU();
908            v4 = region.getV2();
909        }
910
911        float color = this.color;
912        int idx = this.idx;
913        vertices[idx] = x1;
914        vertices[idx + 1] = y1;
915        vertices[idx + 2] = color;
916        vertices[idx + 3] = u1;
917        vertices[idx + 4] = v1;
918
919        vertices[idx + 5] = x2;
920        vertices[idx + 6] = y2;
921        vertices[idx + 7] = color;
922        vertices[idx + 8] = u2;
923        vertices[idx + 9] = v2;
924
925        vertices[idx + 10] = x3;
926        vertices[idx + 11] = y3;
927        vertices[idx + 12] = color;
928        vertices[idx + 13] = u3;
929        vertices[idx + 14] = v3;
930
931        vertices[idx + 15] = x4;
932        vertices[idx + 16] = y4;
933        vertices[idx + 17] = color;
934        vertices[idx + 18] = u4;
935        vertices[idx + 19] = v4;
936        this.idx = idx + 20;
937    }
938
939    @Override
940    public void draw (TextureRegion region, float width, float height, Affine2 transform) {
941        if (!drawing) throw new IllegalStateException("FilterBatch.begin must be called before draw.");
942
943        float[] vertices = this.vertices;
944
945        Texture texture = region.getTexture();
946        if (texture != lastTexture) {
947            switchTexture(texture);
948        } else if (idx == vertices.length) {
949            flush();
950        }
951
952        // construct corner points
953        float x1 = transform.m02;
954        float y1 = transform.m12;
955        float x2 = transform.m01 * height + transform.m02;
956        float y2 = transform.m11 * height + transform.m12;
957        float x3 = transform.m00 * width + transform.m01 * height + transform.m02;
958        float y3 = transform.m10 * width + transform.m11 * height + transform.m12;
959        float x4 = transform.m00 * width + transform.m02;
960        float y4 = transform.m10 * width + transform.m12;
961
962        float u = region.getU();
963        float v = region.getV2();
964        float u2 = region.getV2();
965        float v2 = region.getV();
966
967        float color = this.color;
968        int idx = this.idx;
969        vertices[idx] = x1;
970        vertices[idx + 1] = y1;
971        vertices[idx + 2] = color;
972        vertices[idx + 3] = u;
973        vertices[idx + 4] = v;
974
975        vertices[idx + 5] = x2;
976        vertices[idx + 6] = y2;
977        vertices[idx + 7] = color;
978        vertices[idx + 8] = u;
979        vertices[idx + 9] = v2;
980
981        vertices[idx + 10] = x3;
982        vertices[idx + 11] = y3;
983        vertices[idx + 12] = color;
984        vertices[idx + 13] = u2;
985        vertices[idx + 14] = v2;
986
987        vertices[idx + 15] = x4;
988        vertices[idx + 16] = y4;
989        vertices[idx + 17] = color;
990        vertices[idx + 18] = u2;
991        vertices[idx + 19] = v;
992        this.idx = idx + 20;
993    }
994
995    @Override
996    public void flush () {
997        if (idx == 0) return;
998
999        renderCalls++;
1000        totalRenderCalls++;
1001        int spritesInBatch = idx / 20;
1002        if (spritesInBatch > maxSpritesInBatch) maxSpritesInBatch = spritesInBatch;
1003        int count = spritesInBatch * 6;
1004
1005        lastTexture.bind();
1006        Mesh mesh = this.mesh;
1007        mesh.setVertices(vertices, 0, idx);
1008        mesh.getIndicesBuffer().position(0);
1009        mesh.getIndicesBuffer().limit(count);
1010
1011        if (blendingDisabled) {
1012            Gdx.gl.glDisable(GL20.GL_BLEND);
1013        } else {
1014            Gdx.gl.glEnable(GL20.GL_BLEND);
1015            if (blendSrcFunc != -1) Gdx.gl.glBlendFuncSeparate(blendSrcFunc, blendDstFunc, blendSrcFuncAlpha, blendDstFuncAlpha);
1016        }
1017
1018        mesh.render(customShader != null ? customShader : shader, GL20.GL_TRIANGLES, 0, count);
1019
1020        idx = 0;
1021    }
1022
1023    @Override
1024    public void disableBlending () {
1025        if (blendingDisabled) return;
1026        flush();
1027        blendingDisabled = true;
1028    }
1029
1030    @Override
1031    public void enableBlending () {
1032        if (!blendingDisabled) return;
1033        flush();
1034        blendingDisabled = false;
1035    }
1036
1037    @Override
1038    public void setBlendFunction (int srcFunc, int dstFunc) {
1039        setBlendFunctionSeparate(srcFunc, dstFunc, srcFunc, dstFunc);
1040    }
1041
1042    @Override
1043    public void setBlendFunctionSeparate(int srcFuncColor, int dstFuncColor, int srcFuncAlpha, int dstFuncAlpha) {
1044        if (blendSrcFunc == srcFuncColor && blendDstFunc == dstFuncColor && blendSrcFuncAlpha == srcFuncAlpha && blendDstFuncAlpha == dstFuncAlpha) return;
1045        flush();
1046        blendSrcFunc = srcFuncColor;
1047        blendDstFunc = dstFuncColor;
1048        blendSrcFuncAlpha = srcFuncAlpha;
1049        blendDstFuncAlpha = dstFuncAlpha;
1050    }
1051
1052    @Override
1053    public int getBlendSrcFunc () {
1054        return blendSrcFunc;
1055    }
1056
1057    @Override
1058    public int getBlendDstFunc () {
1059        return blendDstFunc;
1060    }
1061
1062    @Override
1063    public int getBlendSrcFuncAlpha() {
1064        return blendSrcFuncAlpha;
1065    }
1066
1067    @Override
1068    public int getBlendDstFuncAlpha() {
1069        return blendDstFuncAlpha;
1070    }
1071
1072    @Override
1073    public void dispose () {
1074        mesh.dispose();
1075        if (ownsShader && shader != null) shader.dispose();
1076    }
1077
1078    @Override
1079    public Matrix4 getProjectionMatrix () {
1080        return projectionMatrix;
1081    }
1082
1083    @Override
1084    public Matrix4 getTransformMatrix () {
1085        return transformMatrix;
1086    }
1087
1088    @Override
1089    public void setProjectionMatrix (Matrix4 projection) {
1090        if (drawing) flush();
1091        projectionMatrix.set(projection);
1092        if (drawing) setupMatrices();
1093    }
1094
1095    @Override
1096    public void setTransformMatrix (Matrix4 transform) {
1097        if (drawing) flush();
1098        transformMatrix.set(transform);
1099        if (drawing) setupMatrices();
1100    }
1101
1102    private void setupMatrices () {
1103        combinedMatrix.set(projectionMatrix).mul(transformMatrix);
1104        if (customShader != null) {
1105            customShader.setUniformMatrix("u_projTrans", combinedMatrix);
1106            customShader.setUniformi("u_texture", 0);
1107        } else {
1108            shader.setUniformMatrix("u_projTrans", combinedMatrix);
1109            shader.setUniformi("u_texture", 0);
1110        }
1111    }
1112
1113    protected void switchTexture (Texture texture) {
1114        flush();
1115        lastTexture = texture;
1116        invTexWidth = 1.0f / texture.getWidth();
1117        invTexHeight = 1.0f / texture.getHeight();
1118    }
1119
1120    @Override
1121    public void setShader (ShaderProgram shader) {
1122        if (drawing) {
1123            flush();
1124            if (customShader != null)
1125                customShader.end();
1126            else
1127                this.shader.end();
1128        }
1129        customShader = shader;
1130        if (drawing) {
1131            if (customShader != null)
1132                customShader.begin();
1133            else
1134                this.shader.begin();
1135            setupMatrices();
1136        }
1137    }
1138
1139    @Override
1140    public ShaderProgram getShader () {
1141        if (customShader == null) {
1142            return shader;
1143        }
1144        return customShader;
1145    }
1146
1147    @Override
1148    public boolean isBlendingEnabled () {
1149        return !blendingDisabled;
1150    }
1151
1152    public boolean isDrawing () {
1153        return drawing;
1154    }
1155    public FloatFilter getFilter() {
1156        return filter;
1157    }
1158
1159    public void setFilter(FloatFilter filter) {
1160        this.filter = filter;
1161    }
1162
1163}