1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.graphics.g2d; 18 19 import static com.badlogic.gdx.graphics.g2d.Sprite.SPRITE_SIZE; 20 import static com.badlogic.gdx.graphics.g2d.Sprite.VERTEX_SIZE; 21 22 import java.nio.FloatBuffer; 23 24 import com.badlogic.gdx.ApplicationListener; 25 import com.badlogic.gdx.Gdx; 26 import com.badlogic.gdx.graphics.Color; 27 import com.badlogic.gdx.graphics.GL20; 28 import com.badlogic.gdx.graphics.Mesh; 29 import com.badlogic.gdx.graphics.Texture; 30 import com.badlogic.gdx.graphics.VertexAttribute; 31 import com.badlogic.gdx.graphics.VertexAttributes.Usage; 32 import com.badlogic.gdx.graphics.glutils.ShaderProgram; 33 import com.badlogic.gdx.math.MathUtils; 34 import com.badlogic.gdx.math.Matrix4; 35 import com.badlogic.gdx.utils.Array; 36 import com.badlogic.gdx.utils.Disposable; 37 import com.badlogic.gdx.utils.GdxRuntimeException; 38 import com.badlogic.gdx.utils.IntArray; 39 import com.badlogic.gdx.utils.NumberUtils; 40 41 /** Draws 2D images, optimized for geometry that does not change. Sprites and/or textures are cached and given an ID, which can 42 * later be used for drawing. The size, color, and texture region for each cached image cannot be modified. This information is 43 * stored in video memory and does not have to be sent to the GPU each time it is drawn.<br> 44 * <br> 45 * To cache {@link Sprite sprites} or {@link Texture textures}, first call {@link SpriteCache#beginCache()}, then call the 46 * appropriate add method to define the images. To complete the cache, call {@link SpriteCache#endCache()} and store the returned 47 * cache ID.<br> 48 * <br> 49 * To draw with SpriteCache, first call {@link #begin()}, then call {@link #draw(int)} with a cache ID. When SpriteCache drawing 50 * is complete, call {@link #end()}.<br> 51 * <br> 52 * By default, SpriteCache draws using screen coordinates and uses an x-axis pointing to the right, an y-axis pointing upwards and 53 * the origin is the bottom left corner of the screen. The default transformation and projection matrices can be changed. If the 54 * screen is {@link ApplicationListener#resize(int, int) resized}, the SpriteCache's matrices must be updated. For example:<br> 55 * <code>cache.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());</code><br> 56 * <br> 57 * Note that SpriteCache does not manage blending. You will need to enable blending (<i>Gdx.gl.glEnable(GL10.GL_BLEND);</i>) and 58 * set the blend func as needed before or between calls to {@link #draw(int)}.<br> 59 * <br> 60 * SpriteCache is managed. If the OpenGL context is lost and the restored, all OpenGL resources a SpriteCache uses internally are 61 * restored.<br> 62 * <br> 63 * SpriteCache is a reasonably heavyweight object. Typically only one instance should be used for an entire application.<br> 64 * <br> 65 * SpriteCache works with OpenGL ES 1.x and 2.0. For 2.0, it uses its own custom shader to draw.<br> 66 * <br> 67 * SpriteCache must be disposed once it is no longer needed. 68 * @author Nathan Sweet */ 69 public class SpriteCache implements Disposable { 70 static private final float[] tempVertices = new float[VERTEX_SIZE * 6]; 71 72 private final Mesh mesh; 73 private boolean drawing; 74 private final Matrix4 transformMatrix = new Matrix4(); 75 private final Matrix4 projectionMatrix = new Matrix4(); 76 private Array<Cache> caches = new Array(); 77 78 private final Matrix4 combinedMatrix = new Matrix4(); 79 private final ShaderProgram shader; 80 81 private Cache currentCache; 82 private final Array<Texture> textures = new Array(8); 83 private final IntArray counts = new IntArray(8); 84 85 private float color = Color.WHITE.toFloatBits(); 86 private Color tempColor = new Color(1, 1, 1, 1); 87 88 private ShaderProgram customShader = null; 89 90 /** Number of render calls since the last {@link #begin()}. **/ 91 public int renderCalls = 0; 92 93 /** Number of rendering calls, ever. Will not be reset unless set manually. **/ 94 public int totalRenderCalls = 0; 95 96 /** Creates a cache that uses indexed geometry and can contain up to 1000 images. */ 97 public SpriteCache () { 98 this(1000, false); 99 } 100 101 /** Creates a cache with the specified size, using a default shader if OpenGL ES 2.0 is being used. 102 * @param size The maximum number of images this cache can hold. The memory required to hold the images is allocated up front. 103 * Max of 5460 if indices are used. 104 * @param useIndices If true, indexed geometry will be used. */ 105 public SpriteCache (int size, boolean useIndices) { 106 this(size, createDefaultShader(), useIndices); 107 } 108 109 /** Creates a cache with the specified size and OpenGL ES 2.0 shader. 110 * @param size The maximum number of images this cache can hold. The memory required to hold the images is allocated up front. 111 * Max of 5460 if indices are used. 112 * @param useIndices If true, indexed geometry will be used. */ 113 public SpriteCache (int size, ShaderProgram shader, boolean useIndices) { 114 this.shader = shader; 115 116 if (useIndices && size > 5460) throw new IllegalArgumentException("Can't have more than 5460 sprites per batch: " + size); 117 118 mesh = new Mesh(true, size * (useIndices ? 4 : 6), useIndices ? size * 6 : 0, new VertexAttribute(Usage.Position, 2, 119 ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE), 120 new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0")); 121 mesh.setAutoBind(false); 122 123 if (useIndices) { 124 int length = size * 6; 125 short[] indices = new short[length]; 126 short j = 0; 127 for (int i = 0; i < length; i += 6, j += 4) { 128 indices[i + 0] = (short)j; 129 indices[i + 1] = (short)(j + 1); 130 indices[i + 2] = (short)(j + 2); 131 indices[i + 3] = (short)(j + 2); 132 indices[i + 4] = (short)(j + 3); 133 indices[i + 5] = (short)j; 134 } 135 mesh.setIndices(indices); 136 } 137 138 projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 139 } 140 141 /** Sets the color used to tint images when they are added to the SpriteCache. Default is {@link Color#WHITE}. */ 142 public void setColor (Color tint) { 143 color = tint.toFloatBits(); 144 } 145 146 /** @see #setColor(Color) */ 147 public void setColor (float r, float g, float b, float a) { 148 int intBits = (int)(255 * a) << 24 | (int)(255 * b) << 16 | (int)(255 * g) << 8 | (int)(255 * r); 149 color = NumberUtils.intToFloatColor(intBits); 150 } 151 152 /** @see #setColor(Color) 153 * @see Color#toFloatBits() */ 154 public void setColor (float color) { 155 this.color = color; 156 } 157 158 public Color getColor () { 159 int intBits = NumberUtils.floatToIntColor(color); 160 Color color = this.tempColor; 161 color.r = (intBits & 0xff) / 255f; 162 color.g = ((intBits >>> 8) & 0xff) / 255f; 163 color.b = ((intBits >>> 16) & 0xff) / 255f; 164 color.a = ((intBits >>> 24) & 0xff) / 255f; 165 return color; 166 } 167 168 /** Starts the definition of a new cache, allowing the add and {@link #endCache()} methods to be called. */ 169 public void beginCache () { 170 if (currentCache != null) throw new IllegalStateException("endCache must be called before begin."); 171 int verticesPerImage = mesh.getNumIndices() > 0 ? 4 : 6; 172 currentCache = new Cache(caches.size, mesh.getVerticesBuffer().limit()); 173 caches.add(currentCache); 174 mesh.getVerticesBuffer().compact(); 175 } 176 177 /** Starts the redefinition of an existing cache, allowing the add and {@link #endCache()} methods to be called. If this is not 178 * the last cache created, it cannot have more entries added to it than when it was first created. To do that, use 179 * {@link #clear()} and then {@link #begin()}. */ 180 public void beginCache (int cacheID) { 181 if (currentCache != null) throw new IllegalStateException("endCache must be called before begin."); 182 if (cacheID == caches.size - 1) { 183 Cache oldCache = caches.removeIndex(cacheID); 184 mesh.getVerticesBuffer().limit(oldCache.offset); 185 beginCache(); 186 return; 187 } 188 currentCache = caches.get(cacheID); 189 mesh.getVerticesBuffer().position(currentCache.offset); 190 } 191 192 /** Ends the definition of a cache, returning the cache ID to be used with {@link #draw(int)}. */ 193 public int endCache () { 194 if (currentCache == null) throw new IllegalStateException("beginCache must be called before endCache."); 195 Cache cache = currentCache; 196 int cacheCount = mesh.getVerticesBuffer().position() - cache.offset; 197 if (cache.textures == null) { 198 // New cache. 199 cache.maxCount = cacheCount; 200 cache.textureCount = textures.size; 201 cache.textures = textures.toArray(Texture.class); 202 cache.counts = new int[cache.textureCount]; 203 for (int i = 0, n = counts.size; i < n; i++) 204 cache.counts[i] = counts.get(i); 205 206 mesh.getVerticesBuffer().flip(); 207 } else { 208 // Redefine existing cache. 209 if (cacheCount > cache.maxCount) { 210 throw new GdxRuntimeException( 211 "If a cache is not the last created, it cannot be redefined with more entries than when it was first created: " 212 + cacheCount + " (" + cache.maxCount + " max)"); 213 } 214 215 cache.textureCount = textures.size; 216 217 if (cache.textures.length < cache.textureCount) cache.textures = new Texture[cache.textureCount]; 218 for (int i = 0, n = cache.textureCount; i < n; i++) 219 cache.textures[i] = textures.get(i); 220 221 if (cache.counts.length < cache.textureCount) cache.counts = new int[cache.textureCount]; 222 for (int i = 0, n = cache.textureCount; i < n; i++) 223 cache.counts[i] = counts.get(i); 224 225 FloatBuffer vertices = mesh.getVerticesBuffer(); 226 vertices.position(0); 227 Cache lastCache = caches.get(caches.size - 1); 228 vertices.limit(lastCache.offset + lastCache.maxCount); 229 } 230 231 currentCache = null; 232 textures.clear(); 233 counts.clear(); 234 235 return cache.id; 236 } 237 238 /** Invalidates all cache IDs and resets the SpriteCache so new caches can be added. */ 239 public void clear () { 240 caches.clear(); 241 mesh.getVerticesBuffer().clear().flip(); 242 } 243 244 /** Adds the specified vertices to the cache. Each vertex should have 5 elements, one for each of the attributes: x, y, color, 245 * u, and v. If indexed geometry is used, each image should be specified as 4 vertices, otherwise each image should be 246 * specified as 6 vertices. */ 247 public void add (Texture texture, float[] vertices, int offset, int length) { 248 if (currentCache == null) throw new IllegalStateException("beginCache must be called before add."); 249 250 int verticesPerImage = mesh.getNumIndices() > 0 ? 4 : 6; 251 int count = length / (verticesPerImage * VERTEX_SIZE) * 6; 252 int lastIndex = textures.size - 1; 253 if (lastIndex < 0 || textures.get(lastIndex) != texture) { 254 textures.add(texture); 255 counts.add(count); 256 } else 257 counts.incr(lastIndex, count); 258 259 mesh.getVerticesBuffer().put(vertices, offset, length); 260 } 261 262 /** Adds the specified texture to the cache. */ 263 public void add (Texture texture, float x, float y) { 264 final float fx2 = x + texture.getWidth(); 265 final float fy2 = y + texture.getHeight(); 266 267 tempVertices[0] = x; 268 tempVertices[1] = y; 269 tempVertices[2] = color; 270 tempVertices[3] = 0; 271 tempVertices[4] = 1; 272 273 tempVertices[5] = x; 274 tempVertices[6] = fy2; 275 tempVertices[7] = color; 276 tempVertices[8] = 0; 277 tempVertices[9] = 0; 278 279 tempVertices[10] = fx2; 280 tempVertices[11] = fy2; 281 tempVertices[12] = color; 282 tempVertices[13] = 1; 283 tempVertices[14] = 0; 284 285 if (mesh.getNumIndices() > 0) { 286 tempVertices[15] = fx2; 287 tempVertices[16] = y; 288 tempVertices[17] = color; 289 tempVertices[18] = 1; 290 tempVertices[19] = 1; 291 add(texture, tempVertices, 0, 20); 292 } else { 293 tempVertices[15] = fx2; 294 tempVertices[16] = fy2; 295 tempVertices[17] = color; 296 tempVertices[18] = 1; 297 tempVertices[19] = 0; 298 299 tempVertices[20] = fx2; 300 tempVertices[21] = y; 301 tempVertices[22] = color; 302 tempVertices[23] = 1; 303 tempVertices[24] = 1; 304 305 tempVertices[25] = x; 306 tempVertices[26] = y; 307 tempVertices[27] = color; 308 tempVertices[28] = 0; 309 tempVertices[29] = 1; 310 add(texture, tempVertices, 0, 30); 311 } 312 } 313 314 /** Adds the specified texture to the cache. */ 315 public void add (Texture texture, float x, float y, int srcWidth, int srcHeight, float u, float v, float u2, float v2, 316 float color) { 317 final float fx2 = x + srcWidth; 318 final float fy2 = y + srcHeight; 319 320 tempVertices[0] = x; 321 tempVertices[1] = y; 322 tempVertices[2] = color; 323 tempVertices[3] = u; 324 tempVertices[4] = v; 325 326 tempVertices[5] = x; 327 tempVertices[6] = fy2; 328 tempVertices[7] = color; 329 tempVertices[8] = u; 330 tempVertices[9] = v2; 331 332 tempVertices[10] = fx2; 333 tempVertices[11] = fy2; 334 tempVertices[12] = color; 335 tempVertices[13] = u2; 336 tempVertices[14] = v2; 337 338 if (mesh.getNumIndices() > 0) { 339 tempVertices[15] = fx2; 340 tempVertices[16] = y; 341 tempVertices[17] = color; 342 tempVertices[18] = u2; 343 tempVertices[19] = v; 344 add(texture, tempVertices, 0, 20); 345 } else { 346 tempVertices[15] = fx2; 347 tempVertices[16] = fy2; 348 tempVertices[17] = color; 349 tempVertices[18] = u2; 350 tempVertices[19] = v2; 351 352 tempVertices[20] = fx2; 353 tempVertices[21] = y; 354 tempVertices[22] = color; 355 tempVertices[23] = u2; 356 tempVertices[24] = v; 357 358 tempVertices[25] = x; 359 tempVertices[26] = y; 360 tempVertices[27] = color; 361 tempVertices[28] = u; 362 tempVertices[29] = v; 363 add(texture, tempVertices, 0, 30); 364 } 365 } 366 367 /** Adds the specified texture to the cache. */ 368 public void add (Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) { 369 float invTexWidth = 1.0f / texture.getWidth(); 370 float invTexHeight = 1.0f / texture.getHeight(); 371 final float u = srcX * invTexWidth; 372 final float v = (srcY + srcHeight) * invTexHeight; 373 final float u2 = (srcX + srcWidth) * invTexWidth; 374 final float v2 = srcY * invTexHeight; 375 final float fx2 = x + srcWidth; 376 final float fy2 = y + srcHeight; 377 378 tempVertices[0] = x; 379 tempVertices[1] = y; 380 tempVertices[2] = color; 381 tempVertices[3] = u; 382 tempVertices[4] = v; 383 384 tempVertices[5] = x; 385 tempVertices[6] = fy2; 386 tempVertices[7] = color; 387 tempVertices[8] = u; 388 tempVertices[9] = v2; 389 390 tempVertices[10] = fx2; 391 tempVertices[11] = fy2; 392 tempVertices[12] = color; 393 tempVertices[13] = u2; 394 tempVertices[14] = v2; 395 396 if (mesh.getNumIndices() > 0) { 397 tempVertices[15] = fx2; 398 tempVertices[16] = y; 399 tempVertices[17] = color; 400 tempVertices[18] = u2; 401 tempVertices[19] = v; 402 add(texture, tempVertices, 0, 20); 403 } else { 404 tempVertices[15] = fx2; 405 tempVertices[16] = fy2; 406 tempVertices[17] = color; 407 tempVertices[18] = u2; 408 tempVertices[19] = v2; 409 410 tempVertices[20] = fx2; 411 tempVertices[21] = y; 412 tempVertices[22] = color; 413 tempVertices[23] = u2; 414 tempVertices[24] = v; 415 416 tempVertices[25] = x; 417 tempVertices[26] = y; 418 tempVertices[27] = color; 419 tempVertices[28] = u; 420 tempVertices[29] = v; 421 add(texture, tempVertices, 0, 30); 422 } 423 } 424 425 /** Adds the specified texture to the cache. */ 426 public void add (Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth, 427 int srcHeight, boolean flipX, boolean flipY) { 428 429 float invTexWidth = 1.0f / texture.getWidth(); 430 float invTexHeight = 1.0f / texture.getHeight(); 431 float u = srcX * invTexWidth; 432 float v = (srcY + srcHeight) * invTexHeight; 433 float u2 = (srcX + srcWidth) * invTexWidth; 434 float v2 = srcY * invTexHeight; 435 final float fx2 = x + width; 436 final float fy2 = y + height; 437 438 if (flipX) { 439 float tmp = u; 440 u = u2; 441 u2 = tmp; 442 } 443 if (flipY) { 444 float tmp = v; 445 v = v2; 446 v2 = tmp; 447 } 448 449 tempVertices[0] = x; 450 tempVertices[1] = y; 451 tempVertices[2] = color; 452 tempVertices[3] = u; 453 tempVertices[4] = v; 454 455 tempVertices[5] = x; 456 tempVertices[6] = fy2; 457 tempVertices[7] = color; 458 tempVertices[8] = u; 459 tempVertices[9] = v2; 460 461 tempVertices[10] = fx2; 462 tempVertices[11] = fy2; 463 tempVertices[12] = color; 464 tempVertices[13] = u2; 465 tempVertices[14] = v2; 466 467 if (mesh.getNumIndices() > 0) { 468 tempVertices[15] = fx2; 469 tempVertices[16] = y; 470 tempVertices[17] = color; 471 tempVertices[18] = u2; 472 tempVertices[19] = v; 473 add(texture, tempVertices, 0, 20); 474 } else { 475 tempVertices[15] = fx2; 476 tempVertices[16] = fy2; 477 tempVertices[17] = color; 478 tempVertices[18] = u2; 479 tempVertices[19] = v2; 480 481 tempVertices[20] = fx2; 482 tempVertices[21] = y; 483 tempVertices[22] = color; 484 tempVertices[23] = u2; 485 tempVertices[24] = v; 486 487 tempVertices[25] = x; 488 tempVertices[26] = y; 489 tempVertices[27] = color; 490 tempVertices[28] = u; 491 tempVertices[29] = v; 492 add(texture, tempVertices, 0, 30); 493 } 494 } 495 496 /** Adds the specified texture to the cache. */ 497 public void add (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX, 498 float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { 499 500 // bottom left and top right corner points relative to origin 501 final float worldOriginX = x + originX; 502 final float worldOriginY = y + originY; 503 float fx = -originX; 504 float fy = -originY; 505 float fx2 = width - originX; 506 float fy2 = height - originY; 507 508 // scale 509 if (scaleX != 1 || scaleY != 1) { 510 fx *= scaleX; 511 fy *= scaleY; 512 fx2 *= scaleX; 513 fy2 *= scaleY; 514 } 515 516 // construct corner points, start from top left and go counter clockwise 517 final float p1x = fx; 518 final float p1y = fy; 519 final float p2x = fx; 520 final float p2y = fy2; 521 final float p3x = fx2; 522 final float p3y = fy2; 523 final float p4x = fx2; 524 final float p4y = fy; 525 526 float x1; 527 float y1; 528 float x2; 529 float y2; 530 float x3; 531 float y3; 532 float x4; 533 float y4; 534 535 // rotate 536 if (rotation != 0) { 537 final float cos = MathUtils.cosDeg(rotation); 538 final float sin = MathUtils.sinDeg(rotation); 539 540 x1 = cos * p1x - sin * p1y; 541 y1 = sin * p1x + cos * p1y; 542 543 x2 = cos * p2x - sin * p2y; 544 y2 = sin * p2x + cos * p2y; 545 546 x3 = cos * p3x - sin * p3y; 547 y3 = sin * p3x + cos * p3y; 548 549 x4 = x1 + (x3 - x2); 550 y4 = y3 - (y2 - y1); 551 } else { 552 x1 = p1x; 553 y1 = p1y; 554 555 x2 = p2x; 556 y2 = p2y; 557 558 x3 = p3x; 559 y3 = p3y; 560 561 x4 = p4x; 562 y4 = p4y; 563 } 564 565 x1 += worldOriginX; 566 y1 += worldOriginY; 567 x2 += worldOriginX; 568 y2 += worldOriginY; 569 x3 += worldOriginX; 570 y3 += worldOriginY; 571 x4 += worldOriginX; 572 y4 += worldOriginY; 573 574 float invTexWidth = 1.0f / texture.getWidth(); 575 float invTexHeight = 1.0f / texture.getHeight(); 576 float u = srcX * invTexWidth; 577 float v = (srcY + srcHeight) * invTexHeight; 578 float u2 = (srcX + srcWidth) * invTexWidth; 579 float v2 = srcY * invTexHeight; 580 581 if (flipX) { 582 float tmp = u; 583 u = u2; 584 u2 = tmp; 585 } 586 587 if (flipY) { 588 float tmp = v; 589 v = v2; 590 v2 = tmp; 591 } 592 593 tempVertices[0] = x1; 594 tempVertices[1] = y1; 595 tempVertices[2] = color; 596 tempVertices[3] = u; 597 tempVertices[4] = v; 598 599 tempVertices[5] = x2; 600 tempVertices[6] = y2; 601 tempVertices[7] = color; 602 tempVertices[8] = u; 603 tempVertices[9] = v2; 604 605 tempVertices[10] = x3; 606 tempVertices[11] = y3; 607 tempVertices[12] = color; 608 tempVertices[13] = u2; 609 tempVertices[14] = v2; 610 611 if (mesh.getNumIndices() > 0) { 612 tempVertices[15] = x4; 613 tempVertices[16] = y4; 614 tempVertices[17] = color; 615 tempVertices[18] = u2; 616 tempVertices[19] = v; 617 add(texture, tempVertices, 0, 20); 618 } else { 619 tempVertices[15] = x3; 620 tempVertices[16] = y3; 621 tempVertices[17] = color; 622 tempVertices[18] = u2; 623 tempVertices[19] = v2; 624 625 tempVertices[20] = x4; 626 tempVertices[21] = y4; 627 tempVertices[22] = color; 628 tempVertices[23] = u2; 629 tempVertices[24] = v; 630 631 tempVertices[25] = x1; 632 tempVertices[26] = y1; 633 tempVertices[27] = color; 634 tempVertices[28] = u; 635 tempVertices[29] = v; 636 add(texture, tempVertices, 0, 30); 637 } 638 } 639 640 /** Adds the specified region to the cache. */ 641 public void add (TextureRegion region, float x, float y) { 642 add(region, x, y, region.getRegionWidth(), region.getRegionHeight()); 643 } 644 645 /** Adds the specified region to the cache. */ 646 public void add (TextureRegion region, float x, float y, float width, float height) { 647 final float fx2 = x + width; 648 final float fy2 = y + height; 649 final float u = region.u; 650 final float v = region.v2; 651 final float u2 = region.u2; 652 final float v2 = region.v; 653 654 tempVertices[0] = x; 655 tempVertices[1] = y; 656 tempVertices[2] = color; 657 tempVertices[3] = u; 658 tempVertices[4] = v; 659 660 tempVertices[5] = x; 661 tempVertices[6] = fy2; 662 tempVertices[7] = color; 663 tempVertices[8] = u; 664 tempVertices[9] = v2; 665 666 tempVertices[10] = fx2; 667 tempVertices[11] = fy2; 668 tempVertices[12] = color; 669 tempVertices[13] = u2; 670 tempVertices[14] = v2; 671 672 if (mesh.getNumIndices() > 0) { 673 tempVertices[15] = fx2; 674 tempVertices[16] = y; 675 tempVertices[17] = color; 676 tempVertices[18] = u2; 677 tempVertices[19] = v; 678 add(region.texture, tempVertices, 0, 20); 679 } else { 680 tempVertices[15] = fx2; 681 tempVertices[16] = fy2; 682 tempVertices[17] = color; 683 tempVertices[18] = u2; 684 tempVertices[19] = v2; 685 686 tempVertices[20] = fx2; 687 tempVertices[21] = y; 688 tempVertices[22] = color; 689 tempVertices[23] = u2; 690 tempVertices[24] = v; 691 692 tempVertices[25] = x; 693 tempVertices[26] = y; 694 tempVertices[27] = color; 695 tempVertices[28] = u; 696 tempVertices[29] = v; 697 add(region.texture, tempVertices, 0, 30); 698 } 699 } 700 701 /** Adds the specified region to the cache. */ 702 public void add (TextureRegion region, float x, float y, float originX, float originY, float width, float height, 703 float scaleX, float scaleY, float rotation) { 704 705 // bottom left and top right corner points relative to origin 706 final float worldOriginX = x + originX; 707 final float worldOriginY = y + originY; 708 float fx = -originX; 709 float fy = -originY; 710 float fx2 = width - originX; 711 float fy2 = height - originY; 712 713 // scale 714 if (scaleX != 1 || scaleY != 1) { 715 fx *= scaleX; 716 fy *= scaleY; 717 fx2 *= scaleX; 718 fy2 *= scaleY; 719 } 720 721 // construct corner points, start from top left and go counter clockwise 722 final float p1x = fx; 723 final float p1y = fy; 724 final float p2x = fx; 725 final float p2y = fy2; 726 final float p3x = fx2; 727 final float p3y = fy2; 728 final float p4x = fx2; 729 final float p4y = fy; 730 731 float x1; 732 float y1; 733 float x2; 734 float y2; 735 float x3; 736 float y3; 737 float x4; 738 float y4; 739 740 // rotate 741 if (rotation != 0) { 742 final float cos = MathUtils.cosDeg(rotation); 743 final float sin = MathUtils.sinDeg(rotation); 744 745 x1 = cos * p1x - sin * p1y; 746 y1 = sin * p1x + cos * p1y; 747 748 x2 = cos * p2x - sin * p2y; 749 y2 = sin * p2x + cos * p2y; 750 751 x3 = cos * p3x - sin * p3y; 752 y3 = sin * p3x + cos * p3y; 753 754 x4 = x1 + (x3 - x2); 755 y4 = y3 - (y2 - y1); 756 } else { 757 x1 = p1x; 758 y1 = p1y; 759 760 x2 = p2x; 761 y2 = p2y; 762 763 x3 = p3x; 764 y3 = p3y; 765 766 x4 = p4x; 767 y4 = p4y; 768 } 769 770 x1 += worldOriginX; 771 y1 += worldOriginY; 772 x2 += worldOriginX; 773 y2 += worldOriginY; 774 x3 += worldOriginX; 775 y3 += worldOriginY; 776 x4 += worldOriginX; 777 y4 += worldOriginY; 778 779 final float u = region.u; 780 final float v = region.v2; 781 final float u2 = region.u2; 782 final float v2 = region.v; 783 784 tempVertices[0] = x1; 785 tempVertices[1] = y1; 786 tempVertices[2] = color; 787 tempVertices[3] = u; 788 tempVertices[4] = v; 789 790 tempVertices[5] = x2; 791 tempVertices[6] = y2; 792 tempVertices[7] = color; 793 tempVertices[8] = u; 794 tempVertices[9] = v2; 795 796 tempVertices[10] = x3; 797 tempVertices[11] = y3; 798 tempVertices[12] = color; 799 tempVertices[13] = u2; 800 tempVertices[14] = v2; 801 802 if (mesh.getNumIndices() > 0) { 803 tempVertices[15] = x4; 804 tempVertices[16] = y4; 805 tempVertices[17] = color; 806 tempVertices[18] = u2; 807 tempVertices[19] = v; 808 add(region.texture, tempVertices, 0, 20); 809 } else { 810 tempVertices[15] = x3; 811 tempVertices[16] = y3; 812 tempVertices[17] = color; 813 tempVertices[18] = u2; 814 tempVertices[19] = v2; 815 816 tempVertices[20] = x4; 817 tempVertices[21] = y4; 818 tempVertices[22] = color; 819 tempVertices[23] = u2; 820 tempVertices[24] = v; 821 822 tempVertices[25] = x1; 823 tempVertices[26] = y1; 824 tempVertices[27] = color; 825 tempVertices[28] = u; 826 tempVertices[29] = v; 827 add(region.texture, tempVertices, 0, 30); 828 } 829 } 830 831 /** Adds the specified sprite to the cache. */ 832 public void add (Sprite sprite) { 833 if (mesh.getNumIndices() > 0) { 834 add(sprite.getTexture(), sprite.getVertices(), 0, SPRITE_SIZE); 835 return; 836 } 837 838 float[] spriteVertices = sprite.getVertices(); 839 System.arraycopy(spriteVertices, 0, tempVertices, 0, 3 * VERTEX_SIZE); // temp0,1,2=sprite0,1,2 840 System.arraycopy(spriteVertices, 2 * VERTEX_SIZE, tempVertices, 3 * VERTEX_SIZE, VERTEX_SIZE); // temp3=sprite2 841 System.arraycopy(spriteVertices, 3 * VERTEX_SIZE, tempVertices, 4 * VERTEX_SIZE, VERTEX_SIZE); // temp4=sprite3 842 System.arraycopy(spriteVertices, 0, tempVertices, 5 * VERTEX_SIZE, VERTEX_SIZE); // temp5=sprite0 843 add(sprite.getTexture(), tempVertices, 0, 30); 844 } 845 846 /** Prepares the OpenGL state for SpriteCache rendering. */ 847 public void begin () { 848 if (drawing) throw new IllegalStateException("end must be called before begin."); 849 renderCalls = 0; 850 combinedMatrix.set(projectionMatrix).mul(transformMatrix); 851 852 Gdx.gl20.glDepthMask(false); 853 854 if (customShader != null) { 855 customShader.begin(); 856 customShader.setUniformMatrix("u_proj", projectionMatrix); 857 customShader.setUniformMatrix("u_trans", transformMatrix); 858 customShader.setUniformMatrix("u_projTrans", combinedMatrix); 859 customShader.setUniformi("u_texture", 0); 860 mesh.bind(customShader); 861 } else { 862 shader.begin(); 863 shader.setUniformMatrix("u_projectionViewMatrix", combinedMatrix); 864 shader.setUniformi("u_texture", 0); 865 mesh.bind(shader); 866 } 867 drawing = true; 868 } 869 870 /** Completes rendering for this SpriteCache. */ 871 public void end () { 872 if (!drawing) throw new IllegalStateException("begin must be called before end."); 873 drawing = false; 874 875 shader.end(); 876 GL20 gl = Gdx.gl20; 877 gl.glDepthMask(true); 878 if (customShader != null) 879 mesh.unbind(customShader); 880 else 881 mesh.unbind(shader); 882 } 883 884 /** Draws all the images defined for the specified cache ID. */ 885 public void draw (int cacheID) { 886 if (!drawing) throw new IllegalStateException("SpriteCache.begin must be called before draw."); 887 888 Cache cache = caches.get(cacheID); 889 int verticesPerImage = mesh.getNumIndices() > 0 ? 4 : 6; 890 int offset = cache.offset / (verticesPerImage * VERTEX_SIZE) * 6; 891 Texture[] textures = cache.textures; 892 int[] counts = cache.counts; 893 int textureCount = cache.textureCount; 894 for (int i = 0; i < textureCount; i++) { 895 int count = counts[i]; 896 textures[i].bind(); 897 if (customShader != null) 898 mesh.render(customShader, GL20.GL_TRIANGLES, offset, count); 899 else 900 mesh.render(shader, GL20.GL_TRIANGLES, offset, count); 901 offset += count; 902 } 903 renderCalls += textureCount; 904 totalRenderCalls += textureCount; 905 } 906 907 /** Draws a subset of images defined for the specified cache ID. 908 * @param offset The first image to render. 909 * @param length The number of images from the first image (inclusive) to render. */ 910 public void draw (int cacheID, int offset, int length) { 911 if (!drawing) throw new IllegalStateException("SpriteCache.begin must be called before draw."); 912 913 Cache cache = caches.get(cacheID); 914 offset = offset * 6 + cache.offset; 915 length *= 6; 916 Texture[] textures = cache.textures; 917 int[] counts = cache.counts; 918 int textureCount = cache.textureCount; 919 for (int i = 0; i < textureCount; i++) { 920 textures[i].bind(); 921 int count = counts[i]; 922 if (count > length) { 923 i = textureCount; 924 count = length; 925 } else 926 length -= count; 927 if (customShader != null) 928 mesh.render(customShader, GL20.GL_TRIANGLES, offset, count); 929 else 930 mesh.render(shader, GL20.GL_TRIANGLES, offset, count); 931 offset += count; 932 } 933 renderCalls += cache.textureCount; 934 totalRenderCalls += textureCount; 935 } 936 937 /** Releases all resources held by this SpriteCache. */ 938 public void dispose () { 939 mesh.dispose(); 940 if (shader != null) shader.dispose(); 941 } 942 943 public Matrix4 getProjectionMatrix () { 944 return projectionMatrix; 945 } 946 947 public void setProjectionMatrix (Matrix4 projection) { 948 if (drawing) throw new IllegalStateException("Can't set the matrix within begin/end."); 949 projectionMatrix.set(projection); 950 } 951 952 public Matrix4 getTransformMatrix () { 953 return transformMatrix; 954 } 955 956 public void setTransformMatrix (Matrix4 transform) { 957 if (drawing) throw new IllegalStateException("Can't set the matrix within begin/end."); 958 transformMatrix.set(transform); 959 } 960 961 static private class Cache { 962 final int id; 963 final int offset; 964 int maxCount; 965 int textureCount; 966 Texture[] textures; 967 int[] counts; 968 969 public Cache (int id, int offset) { 970 this.id = id; 971 this.offset = offset; 972 } 973 } 974 975 static ShaderProgram createDefaultShader () { 976 String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // 977 + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" // 978 + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // 979 + "uniform mat4 u_projectionViewMatrix;\n" // 980 + "varying vec4 v_color;\n" // 981 + "varying vec2 v_texCoords;\n" // 982 + "\n" // 983 + "void main()\n" // 984 + "{\n" // 985 + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" // 986 + " v_color.a = v_color.a * (255.0/254.0);\n" // 987 + " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // 988 + " gl_Position = u_projectionViewMatrix * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // 989 + "}\n"; 990 String fragmentShader = "#ifdef GL_ES\n" // 991 + "precision mediump float;\n" // 992 + "#endif\n" // 993 + "varying vec4 v_color;\n" // 994 + "varying vec2 v_texCoords;\n" // 995 + "uniform sampler2D u_texture;\n" // 996 + "void main()\n"// 997 + "{\n" // 998 + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" // 999 + "}"; 1000 ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader); 1001 if (shader.isCompiled() == false) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog()); 1002 return shader; 1003 } 1004 1005 /** Sets the shader to be used in a GLES 2.0 environment. Vertex position attribute is called "a_position", the texture 1006 * coordinates attribute is called called "a_texCoords", the color attribute is called "a_color". The projection matrix is 1007 * uploaded via a mat4 uniform called "u_proj", the transform matrix is uploaded via a uniform called "u_trans", the combined 1008 * transform and projection matrx is is uploaded via a mat4 uniform called "u_projTrans". The texture sampler is passed via a 1009 * uniform called "u_texture". 1010 * 1011 * Call this method with a null argument to use the default shader. 1012 * 1013 * @param shader the {@link ShaderProgram} or null to use the default shader. */ 1014 public void setShader (ShaderProgram shader) { 1015 customShader = shader; 1016 } 1017 } 1018