Home | History | Annotate | Download | only in g2d
      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