1/* 2 * Copyright (c) 2009-2012 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32package jme3tools.optimize; 33 34import com.jme3.asset.AssetKey; 35import com.jme3.asset.AssetManager; 36import com.jme3.material.MatParamTexture; 37import com.jme3.material.Material; 38import com.jme3.math.Vector2f; 39import com.jme3.scene.Geometry; 40import com.jme3.scene.Mesh; 41import com.jme3.scene.Spatial; 42import com.jme3.scene.VertexBuffer; 43import com.jme3.scene.VertexBuffer.Type; 44import com.jme3.texture.Image; 45import com.jme3.texture.Image.Format; 46import com.jme3.texture.Texture; 47import com.jme3.texture.Texture2D; 48import com.jme3.util.BufferUtils; 49import java.lang.reflect.InvocationTargetException; 50import java.nio.ByteBuffer; 51import java.nio.FloatBuffer; 52import java.util.ArrayList; 53import java.util.HashMap; 54import java.util.List; 55import java.util.Map; 56import java.util.TreeMap; 57import java.util.logging.Level; 58import java.util.logging.Logger; 59 60/** 61 * <b><code>TextureAtlas</code></b> allows combining multiple textures to one texture atlas. 62 * 63 * <p>After the TextureAtlas has been created with a certain size, textures can be added for 64 * freely chosen "map names". The textures are automatically placed on the atlas map and the 65 * image data is stored in a byte array for each map name. Later each map can be retrieved as 66 * a Texture to be used further in materials.</p> 67 * 68 * <p>The first map name used is the "master map" that defines new locations on the atlas. Secondary 69 * textures (other map names) have to reference a texture of the master map to position the texture 70 * on the secondary map. This is necessary as the maps share texture coordinates and thus need to be 71 * placed at the same location on both maps.</p> 72 * 73 * <p>The helper methods that work with <code>Geometry</code> objects handle the <em>DiffuseMap</em> or <em>ColorMap</em> as the master map and 74 * additionally handle <em>NormalMap</em> and <em>SpecularMap</em> as secondary maps.</p> 75 * 76 * <p>The textures are referenced by their <b>asset key name</b> and for each texture the location 77 * inside the atlas is stored. A texture with an existing key name is never added more than once 78 * to the atlas. You can access the information for each texture or geometry texture via helper methods.</p> 79 * 80 * <p>The TextureAtlas also allows you to change the texture coordinates of a mesh or geometry 81 * to point at the new locations of its texture inside the atlas (if the texture exists inside the atlas).</p> 82 * 83 * <p>Note that models that use texture coordinates outside the 0-1 range (repeating/wrapping textures) 84 * will not work correctly as their new coordinates leak into other parts of the atlas and thus display 85 * other textures instead of repeating the texture.</p> 86 * 87 * <p>Also note that textures are not scaled and the atlas needs to be large enough to hold all textures. 88 * All methods that allow adding textures return false if the texture could not be added due to the 89 * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size 90 * as the main (e.g. DiffuseMap) texture.</p> 91 * 92 * <p><b>Usage examples</b></p> 93 * Create one geometry out of several geometries that are loaded from a j3o file: 94 * <pre> 95 * Node scene = assetManager.loadModel("Scenes/MyScene.j3o"); 96 * Geometry geom = TextureAtlas.makeAtlasBatch(scene); 97 * rootNode.attachChild(geom); 98 * </pre> 99 * Create a texture atlas and change the texture coordinates of one geometry: 100 * <pre> 101 * Node scene = assetManager.loadModel("Scenes/MyScene.j3o"); 102 * //either auto-create from node: 103 * TextureAtlas atlas = TextureAtlas.createAtlas(scene); 104 * //or create manually by adding textures or geometries with textures 105 * TextureAtlas atlas = new TextureAtlas(1024,1024); 106 * atlas.addTexture(myTexture, "DiffuseMap"); 107 * atlas.addGeometry(myGeometry); 108 * //create material and set texture 109 * Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md"); 110 * mat.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap")); 111 * //change one geometry to use atlas, apply texture coordinates and replace material. 112 * Geometry geom = scene.getChild("MyGeometry"); 113 * atlas.applyCoords(geom); 114 * geom.setMaterial(mat); 115 * </pre> 116 * 117 * @author normenhansen, Lukasz Bruun - lukasz.dk 118 */ 119public class TextureAtlas { 120 121 private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); 122 private Map<String, byte[]> images; 123 private int atlasWidth, atlasHeight; 124 private Format format = Format.ABGR8; 125 private Node root; 126 private Map<String, TextureAtlasTile> locationMap; 127 private Map<String, String> mapNameMap; 128 private String rootMapName; 129 130 public TextureAtlas(int width, int height) { 131 this.atlasWidth = width; 132 this.atlasHeight = height; 133 root = new Node(0, 0, width, height); 134 locationMap = new TreeMap<String, TextureAtlasTile>(); 135 mapNameMap = new HashMap<String, String>(); 136 } 137 138 /** 139 * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas. 140 * @param geometry 141 * @return false if the atlas is full. 142 */ 143 public boolean addGeometry(Geometry geometry) { 144 Texture diffuse = getMaterialTexture(geometry, "DiffuseMap"); 145 Texture normal = getMaterialTexture(geometry, "NormalMap"); 146 Texture specular = getMaterialTexture(geometry, "SpecularMap"); 147 if (diffuse == null) { 148 diffuse = getMaterialTexture(geometry, "ColorMap"); 149 150 } 151 if (diffuse != null && diffuse.getKey() != null) { 152 String keyName = diffuse.getKey().toString(); 153 if (!addTexture(diffuse, "DiffuseMap")) { 154 return false; 155 } else { 156 if (normal != null && normal.getKey() != null) { 157 addTexture(diffuse, "NormalMap", keyName); 158 } 159 if (specular != null && specular.getKey() != null) { 160 addTexture(specular, "SpecularMap", keyName); 161 } 162 } 163 return true; 164 } 165 return true; 166 } 167 168 /** 169 * Add a texture for a specific map name 170 * @param texture A texture to add to the atlas. 171 * @param mapName A freely chosen map name that can be later retrieved as a Texture. The first map name supplied will be the master map. 172 * @return false if the atlas is full. 173 */ 174 public boolean addTexture(Texture texture, String mapName) { 175 if (texture == null) { 176 throw new IllegalStateException("Texture cannot be null!"); 177 } 178 String name = textureName(texture); 179 if (texture.getImage() != null && name != null) { 180 return addImage(texture.getImage(), name, mapName, null); 181 } else { 182 throw new IllegalStateException("Texture has no asset key name!"); 183 } 184 } 185 186 /** 187 * Add a texture for a specific map name at the location of another existing texture on the master map. 188 * @param texture A texture to add to the atlas. 189 * @param mapName A freely chosen map name that can be later retrieved as a Texture. 190 * @param masterTexture The master texture for determining the location, it has to exist in tha master map. 191 */ 192 public void addTexture(Texture texture, String mapName, Texture masterTexture) { 193 String sourceTextureName = textureName(masterTexture); 194 if (sourceTextureName == null) { 195 throw new IllegalStateException("Supplied master map texture has no asset key name!"); 196 } else { 197 addTexture(texture, mapName, sourceTextureName); 198 } 199 } 200 201 /** 202 * Add a texture for a specific map name at the location of another existing texture (on the master map). 203 * @param texture A texture to add to the atlas. 204 * @param mapName A freely chosen map name that can be later retrieved as a Texture. 205 * @param sourceTextureName Name of the master map used for the location. 206 */ 207 public void addTexture(Texture texture, String mapName, String sourceTextureName) { 208 if (texture == null) { 209 throw new IllegalStateException("Texture cannot be null!"); 210 } 211 String name = textureName(texture); 212 if (texture.getImage() != null && name != null) { 213 addImage(texture.getImage(), name, mapName, sourceTextureName); 214 } else { 215 throw new IllegalStateException("Texture has no asset key name!"); 216 } 217 } 218 219 private String textureName(Texture texture) { 220 if (texture == null) { 221 return null; 222 } 223 AssetKey key = texture.getKey(); 224 if (key != null) { 225 return key.toString(); 226 } else { 227 return null; 228 } 229 } 230 231 private boolean addImage(Image image, String name, String mapName, String sourceTextureName) { 232 if (rootMapName == null) { 233 rootMapName = mapName; 234 } 235 if (sourceTextureName == null && !rootMapName.equals(mapName)) { 236 throw new IllegalStateException("Atlas already has a master map called " + rootMapName + "." 237 + " Textures for new maps have to use a texture from the master map for their location."); 238 } 239 TextureAtlasTile location = locationMap.get(name); 240 if (location != null) { 241 //have location for texture 242 if (!mapName.equals(mapNameMap.get(name))) { 243 logger.log(Level.WARNING, "Same texture " + name + " is used in different maps! (" + mapName + " and " + mapNameMap.get(name) + "). Location will be based on location in " + mapNameMap.get(name) + "!"); 244 drawImage(image, location.getX(), location.getY(), mapName); 245 return true; 246 } else { 247 return true; 248 } 249 } else if (sourceTextureName == null) { 250 //need to make new tile 251 Node node = root.insert(image); 252 if (node == null) { 253 return false; 254 } 255 location = node.location; 256 } else { 257 //got old tile to align to 258 location = locationMap.get(sourceTextureName); 259 if (location == null) { 260 throw new IllegalStateException("Cannot find master map texture for " + name + "."); 261 } else if (location.width != image.getWidth() || location.height != image.getHeight()) { 262 throw new IllegalStateException(mapName + " " + name + " does not fit " + rootMapName + " tile size. Make sure all textures (diffuse, normal, specular) for one model are the same size."); 263 } 264 } 265 mapNameMap.put(name, mapName); 266 locationMap.put(name, location); 267 drawImage(image, location.getX(), location.getY(), mapName); 268 return true; 269 } 270 271 private void drawImage(Image source, int x, int y, String mapName) { 272 if (images == null) { 273 images = new HashMap<String, byte[]>(); 274 } 275 byte[] image = images.get(mapName); 276 if (image == null) { 277 image = new byte[atlasWidth * atlasHeight * 4]; 278 images.put(mapName, image); 279 } 280 //TODO: all buffers? 281 ByteBuffer sourceData = source.getData(0); 282 int height = source.getHeight(); 283 int width = source.getWidth(); 284 Image newImage = null; 285 for (int yPos = 0; yPos < height; yPos++) { 286 for (int xPos = 0; xPos < width; xPos++) { 287 int i = ((xPos + x) + (yPos + y) * atlasWidth) * 4; 288 if (source.getFormat() == Format.ABGR8) { 289 int j = (xPos + yPos * width) * 4; 290 image[i] = sourceData.get(j); //a 291 image[i + 1] = sourceData.get(j + 1); //b 292 image[i + 2] = sourceData.get(j + 2); //g 293 image[i + 3] = sourceData.get(j + 3); //r 294 } else if (source.getFormat() == Format.BGR8) { 295 int j = (xPos + yPos * width) * 3; 296 image[i] = 1; //a 297 image[i + 1] = sourceData.get(j); //b 298 image[i + 2] = sourceData.get(j + 1); //g 299 image[i + 3] = sourceData.get(j + 2); //r 300 } else if (source.getFormat() == Format.RGB8) { 301 int j = (xPos + yPos * width) * 3; 302 image[i] = 1; //a 303 image[i + 1] = sourceData.get(j + 2); //b 304 image[i + 2] = sourceData.get(j + 1); //g 305 image[i + 3] = sourceData.get(j); //r 306 } else if (source.getFormat() == Format.RGBA8) { 307 int j = (xPos + yPos * width) * 4; 308 image[i] = sourceData.get(j + 3); //a 309 image[i + 1] = sourceData.get(j + 2); //b 310 image[i + 2] = sourceData.get(j + 1); //g 311 image[i + 3] = sourceData.get(j); //r 312 } else if (source.getFormat() == Format.Luminance8) { 313 int j = (xPos + yPos * width) * 1; 314 image[i] = 1; //a 315 image[i + 1] = sourceData.get(j); //b 316 image[i + 2] = sourceData.get(j); //g 317 image[i + 3] = sourceData.get(j); //r 318 } else if (source.getFormat() == Format.Luminance8Alpha8) { 319 int j = (xPos + yPos * width) * 2; 320 image[i] = sourceData.get(j + 1); //a 321 image[i + 1] = sourceData.get(j); //b 322 image[i + 2] = sourceData.get(j); //g 323 image[i + 3] = sourceData.get(j); //r 324 } else { 325 //ImageToAwt conversion 326 if (newImage == null) { 327 newImage = convertImageToAwt(source); 328 if (newImage != null) { 329 source = newImage; 330 sourceData = source.getData(0); 331 int j = (xPos + yPos * width) * 4; 332 image[i] = sourceData.get(j); //a 333 image[i + 1] = sourceData.get(j + 1); //b 334 image[i + 2] = sourceData.get(j + 2); //g 335 image[i + 3] = sourceData.get(j + 3); //r 336 }else{ 337 throw new UnsupportedOperationException("Cannot draw or convert textures with format " + source.getFormat()); 338 } 339 } else { 340 throw new UnsupportedOperationException("Cannot draw textures with format " + source.getFormat()); 341 } 342 } 343 } 344 } 345 } 346 347 private Image convertImageToAwt(Image source) { 348 //use awt dependent classes without actual dependency via reflection 349 try { 350 Class clazz = Class.forName("jme3tools.converters.ImageToAwt"); 351 if (clazz == null) { 352 return null; 353 } 354 Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4)); 355 clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage); 356 return newImage; 357 } catch (InstantiationException ex) { 358 } catch (IllegalAccessException ex) { 359 } catch (IllegalArgumentException ex) { 360 } catch (InvocationTargetException ex) { 361 } catch (NoSuchMethodException ex) { 362 } catch (SecurityException ex) { 363 } catch (ClassNotFoundException ex) { 364 } 365 return null; 366 } 367 368 /** 369 * Get the <code>TextureAtlasTile</code> for the given Texture 370 * @param texture The texture to retrieve the <code>TextureAtlasTile</code> for. 371 * @return 372 */ 373 public TextureAtlasTile getAtlasTile(Texture texture) { 374 String sourceTextureName = textureName(texture); 375 if (sourceTextureName != null) { 376 return getAtlasTile(sourceTextureName); 377 } 378 return null; 379 } 380 381 /** 382 * Get the <code>TextureAtlasTile</code> for the given Texture 383 * @param assetName The texture to retrieve the <code>TextureAtlasTile</code> for. 384 * @return 385 */ 386 private TextureAtlasTile getAtlasTile(String assetName) { 387 return locationMap.get(assetName); 388 } 389 390 /** 391 * Creates a new atlas texture for the given map name. 392 * @param mapName 393 * @return 394 */ 395 public Texture getAtlasTexture(String mapName) { 396 if (images == null) { 397 return null; 398 } 399 byte[] image = images.get(mapName); 400 if (image != null) { 401 Texture2D tex = new Texture2D(new Image(format, atlasWidth, atlasHeight, BufferUtils.createByteBuffer(image))); 402 tex.setMagFilter(Texture.MagFilter.Bilinear); 403 tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap); 404 tex.setWrap(Texture.WrapMode.Clamp); 405 return tex; 406 } 407 return null; 408 } 409 410 /** 411 * Applies the texture coordinates to the given geometry 412 * if its DiffuseMap or ColorMap exists in the atlas. 413 * @param geom The geometry to change the texture coordinate buffer on. 414 * @return true if texture has been found and coords have been changed, false otherwise. 415 */ 416 public boolean applyCoords(Geometry geom) { 417 return applyCoords(geom, 0, geom.getMesh()); 418 } 419 420 /** 421 * Applies the texture coordinates to the given output mesh 422 * if the DiffuseMap or ColorMap of the input geometry exist in the atlas. 423 * @param geom The geometry to change the texture coordinate buffer on. 424 * @param offset Target buffer offset. 425 * @param outMesh The mesh to set the coords in (can be same as input). 426 * @return true if texture has been found and coords have been changed, false otherwise. 427 */ 428 public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) { 429 Mesh inMesh = geom.getMesh(); 430 geom.computeWorldMatrix(); 431 432 VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); 433 VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); 434 435 if (inBuf == null || outBuf == null) { 436 throw new IllegalStateException("Geometry mesh has no texture coordinate buffer."); 437 } 438 439 Texture tex = getMaterialTexture(geom, "DiffuseMap"); 440 if (tex == null) { 441 tex = getMaterialTexture(geom, "ColorMap"); 442 443 } 444 if (tex != null) { 445 TextureAtlasTile tile = getAtlasTile(tex); 446 if (tile != null) { 447 FloatBuffer inPos = (FloatBuffer) inBuf.getData(); 448 FloatBuffer outPos = (FloatBuffer) outBuf.getData(); 449 tile.transformTextureCoords(inPos, offset, outPos); 450 return true; 451 } else { 452 return false; 453 } 454 } else { 455 throw new IllegalStateException("Geometry has no proper texture."); 456 } 457 } 458 459 /** 460 * Create a texture atlas for the given root node, containing DiffuseMap, NormalMap and SpecularMap. 461 * @param root The rootNode to create the atlas for. 462 * @param atlasSize The size of the atlas (width and height). 463 * @return Null if the atlas cannot be created because not all textures fit. 464 */ 465 public static TextureAtlas createAtlas(Spatial root, int atlasSize) { 466 List<Geometry> geometries = new ArrayList<Geometry>(); 467 GeometryBatchFactory.gatherGeoms(root, geometries); 468 TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize); 469 for (Geometry geometry : geometries) { 470 if (!atlas.addGeometry(geometry)) { 471 logger.log(Level.WARNING, "Texture atlas size too small, cannot add all textures"); 472 return null; 473 } 474 } 475 return atlas; 476 } 477 478 /** 479 * Creates one geometry out of the given root spatial and merges all single 480 * textures into one texture of the given size. 481 * @param spat The root spatial of the scene to batch 482 * @param mgr An assetmanager that can be used to create the material. 483 * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures. 484 * @return A new geometry that uses the generated texture atlas and merges all meshes of the root spatial, null if the atlas cannot be created because not all textures fit. 485 */ 486 public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) { 487 List<Geometry> geometries = new ArrayList<Geometry>(); 488 GeometryBatchFactory.gatherGeoms(spat, geometries); 489 TextureAtlas atlas = createAtlas(spat, atlasSize); 490 if (atlas == null) { 491 return null; 492 } 493 Geometry geom = new Geometry(); 494 Mesh mesh = new Mesh(); 495 GeometryBatchFactory.mergeGeometries(geometries, mesh); 496 applyAtlasCoords(geometries, mesh, atlas); 497 mesh.updateCounts(); 498 mesh.updateBound(); 499 geom.setMesh(mesh); 500 501 Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md"); 502 mat.getAdditionalRenderState().setAlphaTest(true); 503 Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap"); 504 Texture normalMap = atlas.getAtlasTexture("NormalMap"); 505 Texture specularMap = atlas.getAtlasTexture("SpecularMap"); 506 if (diffuseMap != null) { 507 mat.setTexture("DiffuseMap", diffuseMap); 508 } 509 if (normalMap != null) { 510 mat.setTexture("NormalMap", normalMap); 511 } 512 if (specularMap != null) { 513 mat.setTexture("SpecularMap", specularMap); 514 } 515 mat.setFloat("Shininess", 16.0f); 516 517 geom.setMaterial(mat); 518 return geom; 519 } 520 521 private static void applyAtlasCoords(List<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) { 522 int globalVertIndex = 0; 523 524 for (Geometry geom : geometries) { 525 Mesh inMesh = geom.getMesh(); 526 geom.computeWorldMatrix(); 527 528 int geomVertCount = inMesh.getVertexCount(); 529 530 VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); 531 VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); 532 533 if (inBuf == null || outBuf == null) { 534 continue; 535 } 536 537 atlas.applyCoords(geom, globalVertIndex, outMesh); 538 539 globalVertIndex += geomVertCount; 540 } 541 } 542 543 private static Texture getMaterialTexture(Geometry geometry, String mapName) { 544 Material mat = geometry.getMaterial(); 545 if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) { 546 return null; 547 } 548 MatParamTexture param = (MatParamTexture) mat.getParam(mapName); 549 Texture texture = param.getTextureValue(); 550 if (texture == null) { 551 return null; 552 } 553 return texture; 554 555 556 } 557 558 private class Node { 559 560 public TextureAtlasTile location; 561 public Node child[]; 562 public boolean occupied; 563 564 public Node(int x, int y, int width, int height) { 565 location = new TextureAtlasTile(x, y, width, height); 566 child = new Node[2]; 567 child[0] = null; 568 child[1] = null; 569 occupied = false; 570 } 571 572 public boolean isLeaf() { 573 return child[0] == null && child[1] == null; 574 } 575 576 // Algorithm from http://www.blackpawn.com/texts/lightmaps/ 577 public Node insert(Image image) { 578 if (!isLeaf()) { 579 Node newNode = child[0].insert(image); 580 581 if (newNode != null) { 582 return newNode; 583 } 584 585 return child[1].insert(image); 586 } else { 587 if (occupied) { 588 return null; // occupied 589 } 590 591 if (image.getWidth() > location.getWidth() || image.getHeight() > location.getHeight()) { 592 return null; // does not fit 593 } 594 595 if (image.getWidth() == location.getWidth() && image.getHeight() == location.getHeight()) { 596 occupied = true; // perfect fit 597 return this; 598 } 599 600 int dw = location.getWidth() - image.getWidth(); 601 int dh = location.getHeight() - image.getHeight(); 602 603 if (dw > dh) { 604 child[0] = new Node(location.getX(), location.getY(), image.getWidth(), location.getHeight()); 605 child[1] = new Node(location.getX() + image.getWidth(), location.getY(), location.getWidth() - image.getWidth(), location.getHeight()); 606 } else { 607 child[0] = new Node(location.getX(), location.getY(), location.getWidth(), image.getHeight()); 608 child[1] = new Node(location.getX(), location.getY() + image.getHeight(), location.getWidth(), location.getHeight() - image.getHeight()); 609 } 610 611 return child[0].insert(image); 612 } 613 } 614 } 615 616 public class TextureAtlasTile { 617 618 private int x; 619 private int y; 620 private int width; 621 private int height; 622 623 public TextureAtlasTile(int x, int y, int width, int height) { 624 this.x = x; 625 this.y = y; 626 this.width = width; 627 this.height = height; 628 } 629 630 /** 631 * Get the transformed texture coordinate for a given input location. 632 * @param previousLocation The old texture coordinate. 633 * @return The new texture coordinate inside the atlas. 634 */ 635 public Vector2f getLocation(Vector2f previousLocation) { 636 float x = (float) getX() / (float) atlasWidth; 637 float y = (float) getY() / (float) atlasHeight; 638 float w = (float) getWidth() / (float) atlasWidth; 639 float h = (float) getHeight() / (float) atlasHeight; 640 Vector2f location = new Vector2f(x, y); 641 float prevX = previousLocation.x; 642 float prevY = previousLocation.y; 643 location.addLocal(prevX * w, prevY * h); 644 return location; 645 } 646 647 /** 648 * Transforms a whole texture coordinates buffer. 649 * @param inBuf The input texture buffer. 650 * @param offset The offset in the output buffer 651 * @param outBuf The output buffer. 652 */ 653 public void transformTextureCoords(FloatBuffer inBuf, int offset, FloatBuffer outBuf) { 654 Vector2f tex = new Vector2f(); 655 656 // offset is given in element units 657 // convert to be in component units 658 offset *= 2; 659 660 for (int i = 0; i < inBuf.capacity() / 2; i++) { 661 tex.x = inBuf.get(i * 2 + 0); 662 tex.y = inBuf.get(i * 2 + 1); 663 Vector2f location = getLocation(tex); 664 //TODO: add proper texture wrapping for atlases.. 665 outBuf.put(offset + i * 2 + 0, location.x); 666 outBuf.put(offset + i * 2 + 1, location.y); 667 } 668 } 669 670 public int getX() { 671 return x; 672 } 673 674 public int getY() { 675 return y; 676 } 677 678 public int getWidth() { 679 return width; 680 } 681 682 public int getHeight() { 683 return height; 684 } 685 } 686} 687