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.Texture.TextureWrap.*; 20 21 import com.badlogic.gdx.Files.FileType; 22 import com.badlogic.gdx.Gdx; 23 import com.badlogic.gdx.files.FileHandle; 24 import com.badlogic.gdx.graphics.Pixmap.Format; 25 import com.badlogic.gdx.graphics.Texture; 26 import com.badlogic.gdx.graphics.Texture.TextureFilter; 27 import com.badlogic.gdx.graphics.Texture.TextureWrap; 28 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page; 29 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Region; 30 import com.badlogic.gdx.utils.Array; 31 import com.badlogic.gdx.utils.Disposable; 32 import com.badlogic.gdx.utils.GdxRuntimeException; 33 import com.badlogic.gdx.utils.ObjectMap; 34 import com.badlogic.gdx.utils.ObjectSet; 35 import com.badlogic.gdx.utils.Sort; 36 import com.badlogic.gdx.utils.StreamUtils; 37 38 import java.io.BufferedReader; 39 import java.io.IOException; 40 import java.io.InputStreamReader; 41 import java.util.Comparator; 42 import java.util.HashSet; 43 import java.util.Set; 44 45 /** Loads images from texture atlases created by TexturePacker.<br> 46 * <br> 47 * A TextureAtlas must be disposed to free up the resources consumed by the backing textures. 48 * @author Nathan Sweet */ 49 public class TextureAtlas implements Disposable { 50 static final String[] tuple = new String[4]; 51 52 private final ObjectSet<Texture> textures = new ObjectSet(4); 53 private final Array<AtlasRegion> regions = new Array(); 54 55 public static class TextureAtlasData { 56 public static class Page { 57 public final FileHandle textureFile; 58 public Texture texture; 59 public final float width, height; 60 public final boolean useMipMaps; 61 public final Format format; 62 public final TextureFilter minFilter; 63 public final TextureFilter magFilter; 64 public final TextureWrap uWrap; 65 public final TextureWrap vWrap; 66 Page(FileHandle handle, float width, float height, boolean useMipMaps, Format format, TextureFilter minFilter, TextureFilter magFilter, TextureWrap uWrap, TextureWrap vWrap)67 public Page (FileHandle handle, float width, float height, boolean useMipMaps, Format format, TextureFilter minFilter, 68 TextureFilter magFilter, TextureWrap uWrap, TextureWrap vWrap) { 69 this.width = width; 70 this.height = height; 71 this.textureFile = handle; 72 this.useMipMaps = useMipMaps; 73 this.format = format; 74 this.minFilter = minFilter; 75 this.magFilter = magFilter; 76 this.uWrap = uWrap; 77 this.vWrap = vWrap; 78 } 79 } 80 81 public static class Region { 82 public Page page; 83 public int index; 84 public String name; 85 public float offsetX; 86 public float offsetY; 87 public int originalWidth; 88 public int originalHeight; 89 public boolean rotate; 90 public int left; 91 public int top; 92 public int width; 93 public int height; 94 public boolean flip; 95 public int[] splits; 96 public int[] pads; 97 } 98 99 final Array<Page> pages = new Array(); 100 final Array<Region> regions = new Array(); 101 TextureAtlasData(FileHandle packFile, FileHandle imagesDir, boolean flip)102 public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) { 103 BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 64); 104 try { 105 Page pageImage = null; 106 while (true) { 107 String line = reader.readLine(); 108 if (line == null) break; 109 if (line.trim().length() == 0) 110 pageImage = null; 111 else if (pageImage == null) { 112 FileHandle file = imagesDir.child(line); 113 114 float width = 0, height = 0; 115 if (readTuple(reader) == 2) { // size is only optional for an atlas packed with an old TexturePacker. 116 width = Integer.parseInt(tuple[0]); 117 height = Integer.parseInt(tuple[1]); 118 readTuple(reader); 119 } 120 Format format = Format.valueOf(tuple[0]); 121 122 readTuple(reader); 123 TextureFilter min = TextureFilter.valueOf(tuple[0]); 124 TextureFilter max = TextureFilter.valueOf(tuple[1]); 125 126 String direction = readValue(reader); 127 TextureWrap repeatX = ClampToEdge; 128 TextureWrap repeatY = ClampToEdge; 129 if (direction.equals("x")) 130 repeatX = Repeat; 131 else if (direction.equals("y")) 132 repeatY = Repeat; 133 else if (direction.equals("xy")) { 134 repeatX = Repeat; 135 repeatY = Repeat; 136 } 137 138 pageImage = new Page(file, width, height, min.isMipMap(), format, min, max, repeatX, repeatY); 139 pages.add(pageImage); 140 } else { 141 boolean rotate = Boolean.valueOf(readValue(reader)); 142 143 readTuple(reader); 144 int left = Integer.parseInt(tuple[0]); 145 int top = Integer.parseInt(tuple[1]); 146 147 readTuple(reader); 148 int width = Integer.parseInt(tuple[0]); 149 int height = Integer.parseInt(tuple[1]); 150 151 Region region = new Region(); 152 region.page = pageImage; 153 region.left = left; 154 region.top = top; 155 region.width = width; 156 region.height = height; 157 region.name = line; 158 region.rotate = rotate; 159 160 if (readTuple(reader) == 4) { // split is optional 161 region.splits = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]), 162 Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])}; 163 164 if (readTuple(reader) == 4) { // pad is optional, but only present with splits 165 region.pads = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]), 166 Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])}; 167 168 readTuple(reader); 169 } 170 } 171 172 region.originalWidth = Integer.parseInt(tuple[0]); 173 region.originalHeight = Integer.parseInt(tuple[1]); 174 175 readTuple(reader); 176 region.offsetX = Integer.parseInt(tuple[0]); 177 region.offsetY = Integer.parseInt(tuple[1]); 178 179 region.index = Integer.parseInt(readValue(reader)); 180 181 if (flip) region.flip = true; 182 183 regions.add(region); 184 } 185 } 186 } catch (Exception ex) { 187 throw new GdxRuntimeException("Error reading pack file: " + packFile, ex); 188 } finally { 189 StreamUtils.closeQuietly(reader); 190 } 191 192 regions.sort(indexComparator); 193 } 194 getPages()195 public Array<Page> getPages () { 196 return pages; 197 } 198 getRegions()199 public Array<Region> getRegions () { 200 return regions; 201 } 202 } 203 204 /** Creates an empty atlas to which regions can be added. */ TextureAtlas()205 public TextureAtlas () { 206 } 207 208 /** Loads the specified pack file using {@link FileType#Internal}, using the parent directory of the pack file to find the page 209 * images. */ TextureAtlas(String internalPackFile)210 public TextureAtlas (String internalPackFile) { 211 this(Gdx.files.internal(internalPackFile)); 212 } 213 214 /** Loads the specified pack file, using the parent directory of the pack file to find the page images. */ TextureAtlas(FileHandle packFile)215 public TextureAtlas (FileHandle packFile) { 216 this(packFile, packFile.parent()); 217 } 218 219 /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. 220 * @see #TextureAtlas(FileHandle) */ TextureAtlas(FileHandle packFile, boolean flip)221 public TextureAtlas (FileHandle packFile, boolean flip) { 222 this(packFile, packFile.parent(), flip); 223 } 224 TextureAtlas(FileHandle packFile, FileHandle imagesDir)225 public TextureAtlas (FileHandle packFile, FileHandle imagesDir) { 226 this(packFile, imagesDir, false); 227 } 228 229 /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. */ TextureAtlas(FileHandle packFile, FileHandle imagesDir, boolean flip)230 public TextureAtlas (FileHandle packFile, FileHandle imagesDir, boolean flip) { 231 this(new TextureAtlasData(packFile, imagesDir, flip)); 232 } 233 234 /** @param data May be null. */ TextureAtlas(TextureAtlasData data)235 public TextureAtlas (TextureAtlasData data) { 236 if (data != null) load(data); 237 } 238 load(TextureAtlasData data)239 private void load (TextureAtlasData data) { 240 ObjectMap<Page, Texture> pageToTexture = new ObjectMap<Page, Texture>(); 241 for (Page page : data.pages) { 242 Texture texture = null; 243 if (page.texture == null) { 244 texture = new Texture(page.textureFile, page.format, page.useMipMaps); 245 texture.setFilter(page.minFilter, page.magFilter); 246 texture.setWrap(page.uWrap, page.vWrap); 247 } else { 248 texture = page.texture; 249 texture.setFilter(page.minFilter, page.magFilter); 250 texture.setWrap(page.uWrap, page.vWrap); 251 } 252 textures.add(texture); 253 pageToTexture.put(page, texture); 254 } 255 256 for (Region region : data.regions) { 257 int width = region.width; 258 int height = region.height; 259 AtlasRegion atlasRegion = new AtlasRegion(pageToTexture.get(region.page), region.left, region.top, 260 region.rotate ? height : width, region.rotate ? width : height); 261 atlasRegion.index = region.index; 262 atlasRegion.name = region.name; 263 atlasRegion.offsetX = region.offsetX; 264 atlasRegion.offsetY = region.offsetY; 265 atlasRegion.originalHeight = region.originalHeight; 266 atlasRegion.originalWidth = region.originalWidth; 267 atlasRegion.rotate = region.rotate; 268 atlasRegion.splits = region.splits; 269 atlasRegion.pads = region.pads; 270 if (region.flip) atlasRegion.flip(false, true); 271 regions.add(atlasRegion); 272 } 273 } 274 275 /** Adds a region to the atlas. The specified texture will be disposed when the atlas is disposed. */ addRegion(String name, Texture texture, int x, int y, int width, int height)276 public AtlasRegion addRegion (String name, Texture texture, int x, int y, int width, int height) { 277 textures.add(texture); 278 AtlasRegion region = new AtlasRegion(texture, x, y, width, height); 279 region.name = name; 280 region.originalWidth = width; 281 region.originalHeight = height; 282 region.index = -1; 283 regions.add(region); 284 return region; 285 } 286 287 /** Adds a region to the atlas. The texture for the specified region will be disposed when the atlas is disposed. */ addRegion(String name, TextureRegion textureRegion)288 public AtlasRegion addRegion (String name, TextureRegion textureRegion) { 289 return addRegion(name, textureRegion.texture, textureRegion.getRegionX(), textureRegion.getRegionY(), 290 textureRegion.getRegionWidth(), textureRegion.getRegionHeight()); 291 } 292 293 /** Returns all regions in the atlas. */ getRegions()294 public Array<AtlasRegion> getRegions () { 295 return regions; 296 } 297 298 /** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result 299 * should be cached rather than calling this method multiple times. 300 * @return The region, or null. */ findRegion(String name)301 public AtlasRegion findRegion (String name) { 302 for (int i = 0, n = regions.size; i < n; i++) 303 if (regions.get(i).name.equals(name)) return regions.get(i); 304 return null; 305 } 306 307 /** Returns the first region found with the specified name and index. This method uses string comparison to find the region, so 308 * the result should be cached rather than calling this method multiple times. 309 * @return The region, or null. */ findRegion(String name, int index)310 public AtlasRegion findRegion (String name, int index) { 311 for (int i = 0, n = regions.size; i < n; i++) { 312 AtlasRegion region = regions.get(i); 313 if (!region.name.equals(name)) continue; 314 if (region.index != index) continue; 315 return region; 316 } 317 return null; 318 } 319 320 /** Returns all regions with the specified name, ordered by smallest to largest {@link AtlasRegion#index index}. This method 321 * uses string comparison to find the regions, so the result should be cached rather than calling this method multiple times. */ findRegions(String name)322 public Array<AtlasRegion> findRegions (String name) { 323 Array<AtlasRegion> matched = new Array(); 324 for (int i = 0, n = regions.size; i < n; i++) { 325 AtlasRegion region = regions.get(i); 326 if (region.name.equals(name)) matched.add(new AtlasRegion(region)); 327 } 328 return matched; 329 } 330 331 /** Returns all regions in the atlas as sprites. This method creates a new sprite for each region, so the result should be 332 * stored rather than calling this method multiple times. 333 * @see #createSprite(String) */ createSprites()334 public Array<Sprite> createSprites () { 335 Array sprites = new Array(regions.size); 336 for (int i = 0, n = regions.size; i < n; i++) 337 sprites.add(newSprite(regions.get(i))); 338 return sprites; 339 } 340 341 /** Returns the first region found with the specified name as a sprite. If whitespace was stripped from the region when it was 342 * packed, the sprite is automatically positioned as if whitespace had not been stripped. This method uses string comparison to 343 * find the region and constructs a new sprite, so the result should be cached rather than calling this method multiple times. 344 * @return The sprite, or null. */ createSprite(String name)345 public Sprite createSprite (String name) { 346 for (int i = 0, n = regions.size; i < n; i++) 347 if (regions.get(i).name.equals(name)) return newSprite(regions.get(i)); 348 return null; 349 } 350 351 /** Returns the first region found with the specified name and index as a sprite. This method uses string comparison to find the 352 * region and constructs a new sprite, so the result should be cached rather than calling this method multiple times. 353 * @return The sprite, or null. 354 * @see #createSprite(String) */ createSprite(String name, int index)355 public Sprite createSprite (String name, int index) { 356 for (int i = 0, n = regions.size; i < n; i++) { 357 AtlasRegion region = regions.get(i); 358 if (!region.name.equals(name)) continue; 359 if (region.index != index) continue; 360 return newSprite(regions.get(i)); 361 } 362 return null; 363 } 364 365 /** Returns all regions with the specified name as sprites, ordered by smallest to largest {@link AtlasRegion#index index}. This 366 * method uses string comparison to find the regions and constructs new sprites, so the result should be cached rather than 367 * calling this method multiple times. 368 * @see #createSprite(String) */ createSprites(String name)369 public Array<Sprite> createSprites (String name) { 370 Array<Sprite> matched = new Array(); 371 for (int i = 0, n = regions.size; i < n; i++) { 372 AtlasRegion region = regions.get(i); 373 if (region.name.equals(name)) matched.add(newSprite(region)); 374 } 375 return matched; 376 } 377 newSprite(AtlasRegion region)378 private Sprite newSprite (AtlasRegion region) { 379 if (region.packedWidth == region.originalWidth && region.packedHeight == region.originalHeight) { 380 if (region.rotate) { 381 Sprite sprite = new Sprite(region); 382 sprite.setBounds(0, 0, region.getRegionHeight(), region.getRegionWidth()); 383 sprite.rotate90(true); 384 return sprite; 385 } 386 return new Sprite(region); 387 } 388 return new AtlasSprite(region); 389 } 390 391 /** Returns the first region found with the specified name as a {@link NinePatch}. The region must have been packed with 392 * ninepatch splits. This method uses string comparison to find the region and constructs a new ninepatch, so the result should 393 * be cached rather than calling this method multiple times. 394 * @return The ninepatch, or null. */ createPatch(String name)395 public NinePatch createPatch (String name) { 396 for (int i = 0, n = regions.size; i < n; i++) { 397 AtlasRegion region = regions.get(i); 398 if (region.name.equals(name)) { 399 int[] splits = region.splits; 400 if (splits == null) throw new IllegalArgumentException("Region does not have ninepatch splits: " + name); 401 NinePatch patch = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]); 402 if (region.pads != null) patch.setPadding(region.pads[0], region.pads[1], region.pads[2], region.pads[3]); 403 return patch; 404 } 405 } 406 return null; 407 } 408 409 /** @return the textures of the pages, unordered */ getTextures()410 public ObjectSet<Texture> getTextures () { 411 return textures; 412 } 413 414 /** Releases all resources associated with this TextureAtlas instance. This releases all the textures backing all TextureRegions 415 * and Sprites, which should no longer be used after calling dispose. */ dispose()416 public void dispose () { 417 for (Texture texture : textures) 418 texture.dispose(); 419 textures.clear(); 420 } 421 422 static final Comparator<Region> indexComparator = new Comparator<Region>() { 423 public int compare (Region region1, Region region2) { 424 int i1 = region1.index; 425 if (i1 == -1) i1 = Integer.MAX_VALUE; 426 int i2 = region2.index; 427 if (i2 == -1) i2 = Integer.MAX_VALUE; 428 return i1 - i2; 429 } 430 }; 431 readValue(BufferedReader reader)432 static String readValue (BufferedReader reader) throws IOException { 433 String line = reader.readLine(); 434 int colon = line.indexOf(':'); 435 if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line); 436 return line.substring(colon + 1).trim(); 437 } 438 439 /** Returns the number of tuple values read (1, 2 or 4). */ readTuple(BufferedReader reader)440 static int readTuple (BufferedReader reader) throws IOException { 441 String line = reader.readLine(); 442 int colon = line.indexOf(':'); 443 if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line); 444 int i = 0, lastMatch = colon + 1; 445 for (i = 0; i < 3; i++) { 446 int comma = line.indexOf(',', lastMatch); 447 if (comma == -1) break; 448 tuple[i] = line.substring(lastMatch, comma).trim(); 449 lastMatch = comma + 1; 450 } 451 tuple[i] = line.substring(lastMatch).trim(); 452 return i + 1; 453 } 454 455 /** Describes the region of a packed image and provides information about the original image before it was packed. */ 456 static public class AtlasRegion extends TextureRegion { 457 /** The number at the end of the original image file name, or -1 if none.<br> 458 * <br> 459 * When sprites are packed, if the original file name ends with a number, it is stored as the index and is not considered as 460 * part of the sprite's name. This is useful for keeping animation frames in order. 461 * @see TextureAtlas#findRegions(String) */ 462 public int index; 463 464 /** The name of the original image file, up to the first underscore. Underscores denote special instructions to the texture 465 * packer. */ 466 public String name; 467 468 /** The offset from the left of the original image to the left of the packed image, after whitespace was removed for packing. */ 469 public float offsetX; 470 471 /** The offset from the bottom of the original image to the bottom of the packed image, after whitespace was removed for 472 * packing. */ 473 public float offsetY; 474 475 /** The width of the image, after whitespace was removed for packing. */ 476 public int packedWidth; 477 478 /** The height of the image, after whitespace was removed for packing. */ 479 public int packedHeight; 480 481 /** The width of the image, before whitespace was removed and rotation was applied for packing. */ 482 public int originalWidth; 483 484 /** The height of the image, before whitespace was removed for packing. */ 485 public int originalHeight; 486 487 /** If true, the region has been rotated 90 degrees counter clockwise. */ 488 public boolean rotate; 489 490 /** The ninepatch splits, or null if not a ninepatch. Has 4 elements: left, right, top, bottom. */ 491 public int[] splits; 492 493 /** The ninepatch pads, or null if not a ninepatch or the has no padding. Has 4 elements: left, right, top, bottom. */ 494 public int[] pads; 495 AtlasRegion(Texture texture, int x, int y, int width, int height)496 public AtlasRegion (Texture texture, int x, int y, int width, int height) { 497 super(texture, x, y, width, height); 498 originalWidth = width; 499 originalHeight = height; 500 packedWidth = width; 501 packedHeight = height; 502 } 503 AtlasRegion(AtlasRegion region)504 public AtlasRegion (AtlasRegion region) { 505 setRegion(region); 506 index = region.index; 507 name = region.name; 508 offsetX = region.offsetX; 509 offsetY = region.offsetY; 510 packedWidth = region.packedWidth; 511 packedHeight = region.packedHeight; 512 originalWidth = region.originalWidth; 513 originalHeight = region.originalHeight; 514 rotate = region.rotate; 515 splits = region.splits; 516 } 517 518 @Override 519 /** Flips the region, adjusting the offset so the image appears to be flip as if no whitespace has been removed for packing. */ flip(boolean x, boolean y)520 public void flip (boolean x, boolean y) { 521 super.flip(x, y); 522 if (x) offsetX = originalWidth - offsetX - getRotatedPackedWidth(); 523 if (y) offsetY = originalHeight - offsetY - getRotatedPackedHeight(); 524 } 525 526 /** Returns the packed width considering the rotate value, if it is true then it returns the packedHeight, otherwise it 527 * returns the packedWidth. */ getRotatedPackedWidth()528 public float getRotatedPackedWidth () { 529 return rotate ? packedHeight : packedWidth; 530 } 531 532 /** Returns the packed height considering the rotate value, if it is true then it returns the packedWidth, otherwise it 533 * returns the packedHeight. */ getRotatedPackedHeight()534 public float getRotatedPackedHeight () { 535 return rotate ? packedWidth : packedHeight; 536 } 537 toString()538 public String toString () { 539 return name; 540 } 541 } 542 543 /** A sprite that, if whitespace was stripped from the region when it was packed, is automatically positioned as if whitespace 544 * had not been stripped. */ 545 static public class AtlasSprite extends Sprite { 546 final AtlasRegion region; 547 float originalOffsetX, originalOffsetY; 548 AtlasSprite(AtlasRegion region)549 public AtlasSprite (AtlasRegion region) { 550 this.region = new AtlasRegion(region); 551 originalOffsetX = region.offsetX; 552 originalOffsetY = region.offsetY; 553 setRegion(region); 554 setOrigin(region.originalWidth / 2f, region.originalHeight / 2f); 555 int width = region.getRegionWidth(); 556 int height = region.getRegionHeight(); 557 if (region.rotate) { 558 super.rotate90(true); 559 super.setBounds(region.offsetX, region.offsetY, height, width); 560 } else 561 super.setBounds(region.offsetX, region.offsetY, width, height); 562 setColor(1, 1, 1, 1); 563 } 564 AtlasSprite(AtlasSprite sprite)565 public AtlasSprite (AtlasSprite sprite) { 566 region = sprite.region; 567 this.originalOffsetX = sprite.originalOffsetX; 568 this.originalOffsetY = sprite.originalOffsetY; 569 set(sprite); 570 } 571 572 @Override setPosition(float x, float y)573 public void setPosition (float x, float y) { 574 super.setPosition(x + region.offsetX, y + region.offsetY); 575 } 576 577 @Override setX(float x)578 public void setX (float x) { 579 super.setX(x + region.offsetX); 580 } 581 582 @Override setY(float y)583 public void setY (float y) { 584 super.setY(y + region.offsetY); 585 } 586 587 @Override setBounds(float x, float y, float width, float height)588 public void setBounds (float x, float y, float width, float height) { 589 float widthRatio = width / region.originalWidth; 590 float heightRatio = height / region.originalHeight; 591 region.offsetX = originalOffsetX * widthRatio; 592 region.offsetY = originalOffsetY * heightRatio; 593 int packedWidth = region.rotate ? region.packedHeight : region.packedWidth; 594 int packedHeight = region.rotate ? region.packedWidth : region.packedHeight; 595 super.setBounds(x + region.offsetX, y + region.offsetY, packedWidth * widthRatio, packedHeight * heightRatio); 596 } 597 598 @Override setSize(float width, float height)599 public void setSize (float width, float height) { 600 setBounds(getX(), getY(), width, height); 601 } 602 603 @Override setOrigin(float originX, float originY)604 public void setOrigin (float originX, float originY) { 605 super.setOrigin(originX - region.offsetX, originY - region.offsetY); 606 } 607 608 @Override setOriginCenter()609 public void setOriginCenter () { 610 super.setOrigin(width / 2 - region.offsetX, height / 2 - region.offsetY); 611 } 612 613 @Override flip(boolean x, boolean y)614 public void flip (boolean x, boolean y) { 615 // Flip texture. 616 if (region.rotate) 617 super.flip(y, x); 618 else 619 super.flip(x, y); 620 621 float oldOriginX = getOriginX(); 622 float oldOriginY = getOriginY(); 623 float oldOffsetX = region.offsetX; 624 float oldOffsetY = region.offsetY; 625 626 float widthRatio = getWidthRatio(); 627 float heightRatio = getHeightRatio(); 628 629 region.offsetX = originalOffsetX; 630 region.offsetY = originalOffsetY; 631 region.flip(x, y); // Updates x and y offsets. 632 originalOffsetX = region.offsetX; 633 originalOffsetY = region.offsetY; 634 region.offsetX *= widthRatio; 635 region.offsetY *= heightRatio; 636 637 // Update position and origin with new offsets. 638 translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY); 639 setOrigin(oldOriginX, oldOriginY); 640 } 641 642 @Override rotate90(boolean clockwise)643 public void rotate90 (boolean clockwise) { 644 // Rotate texture. 645 super.rotate90(clockwise); 646 647 float oldOriginX = getOriginX(); 648 float oldOriginY = getOriginY(); 649 float oldOffsetX = region.offsetX; 650 float oldOffsetY = region.offsetY; 651 652 float widthRatio = getWidthRatio(); 653 float heightRatio = getHeightRatio(); 654 655 if (clockwise) { 656 region.offsetX = oldOffsetY; 657 region.offsetY = region.originalHeight * heightRatio - oldOffsetX - region.packedWidth * widthRatio; 658 } else { 659 region.offsetX = region.originalWidth * widthRatio - oldOffsetY - region.packedHeight * heightRatio; 660 region.offsetY = oldOffsetX; 661 } 662 663 // Update position and origin with new offsets. 664 translate(region.offsetX - oldOffsetX, region.offsetY - oldOffsetY); 665 setOrigin(oldOriginX, oldOriginY); 666 } 667 668 @Override getX()669 public float getX () { 670 return super.getX() - region.offsetX; 671 } 672 673 @Override getY()674 public float getY () { 675 return super.getY() - region.offsetY; 676 } 677 678 @Override getOriginX()679 public float getOriginX () { 680 return super.getOriginX() + region.offsetX; 681 } 682 683 @Override getOriginY()684 public float getOriginY () { 685 return super.getOriginY() + region.offsetY; 686 } 687 688 @Override getWidth()689 public float getWidth () { 690 return super.getWidth() / region.getRotatedPackedWidth() * region.originalWidth; 691 } 692 693 @Override getHeight()694 public float getHeight () { 695 return super.getHeight() / region.getRotatedPackedHeight() * region.originalHeight; 696 } 697 getWidthRatio()698 public float getWidthRatio () { 699 return super.getWidth() / region.getRotatedPackedWidth(); 700 } 701 getHeightRatio()702 public float getHeightRatio () { 703 return super.getHeight() / region.getRotatedPackedHeight(); 704 } 705 getAtlasRegion()706 public AtlasRegion getAtlasRegion () { 707 return region; 708 } 709 toString()710 public String toString () { 711 return region.toString(); 712 } 713 } 714 } 715