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}