aboutsummaryrefslogtreecommitdiff
path: root/engine/src/tools/jme3tools/optimize/TextureAtlas.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/tools/jme3tools/optimize/TextureAtlas.java')
-rw-r--r--engine/src/tools/jme3tools/optimize/TextureAtlas.java686
1 files changed, 686 insertions, 0 deletions
diff --git a/engine/src/tools/jme3tools/optimize/TextureAtlas.java b/engine/src/tools/jme3tools/optimize/TextureAtlas.java
new file mode 100644
index 0000000..13c8b69
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/TextureAtlas.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3tools.optimize;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.MatParamTexture;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <b><code>TextureAtlas</code></b> allows combining multiple textures to one texture atlas.
+ *
+ * <p>After the TextureAtlas has been created with a certain size, textures can be added for
+ * freely chosen "map names". The textures are automatically placed on the atlas map and the
+ * image data is stored in a byte array for each map name. Later each map can be retrieved as
+ * a Texture to be used further in materials.</p>
+ *
+ * <p>The first map name used is the "master map" that defines new locations on the atlas. Secondary
+ * textures (other map names) have to reference a texture of the master map to position the texture
+ * on the secondary map. This is necessary as the maps share texture coordinates and thus need to be
+ * placed at the same location on both maps.</p>
+ *
+ * <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
+ * additionally handle <em>NormalMap</em> and <em>SpecularMap</em> as secondary maps.</p>
+ *
+ * <p>The textures are referenced by their <b>asset key name</b> and for each texture the location
+ * inside the atlas is stored. A texture with an existing key name is never added more than once
+ * to the atlas. You can access the information for each texture or geometry texture via helper methods.</p>
+ *
+ * <p>The TextureAtlas also allows you to change the texture coordinates of a mesh or geometry
+ * to point at the new locations of its texture inside the atlas (if the texture exists inside the atlas).</p>
+ *
+ * <p>Note that models that use texture coordinates outside the 0-1 range (repeating/wrapping textures)
+ * will not work correctly as their new coordinates leak into other parts of the atlas and thus display
+ * other textures instead of repeating the texture.</p>
+ *
+ * <p>Also note that textures are not scaled and the atlas needs to be large enough to hold all textures.
+ * All methods that allow adding textures return false if the texture could not be added due to the
+ * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size
+ * as the main (e.g. DiffuseMap) texture.</p>
+ *
+ * <p><b>Usage examples</b></p>
+ * Create one geometry out of several geometries that are loaded from a j3o file:
+ * <pre>
+ * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
+ * Geometry geom = TextureAtlas.makeAtlasBatch(scene);
+ * rootNode.attachChild(geom);
+ * </pre>
+ * Create a texture atlas and change the texture coordinates of one geometry:
+ * <pre>
+ * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
+ * //either auto-create from node:
+ * TextureAtlas atlas = TextureAtlas.createAtlas(scene);
+ * //or create manually by adding textures or geometries with textures
+ * TextureAtlas atlas = new TextureAtlas(1024,1024);
+ * atlas.addTexture(myTexture, "DiffuseMap");
+ * atlas.addGeometry(myGeometry);
+ * //create material and set texture
+ * Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
+ * mat.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap"));
+ * //change one geometry to use atlas, apply texture coordinates and replace material.
+ * Geometry geom = scene.getChild("MyGeometry");
+ * atlas.applyCoords(geom);
+ * geom.setMaterial(mat);
+ * </pre>
+ *
+ * @author normenhansen, Lukasz Bruun - lukasz.dk
+ */
+public class TextureAtlas {
+
+ private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName());
+ private Map<String, byte[]> images;
+ private int atlasWidth, atlasHeight;
+ private Format format = Format.ABGR8;
+ private Node root;
+ private Map<String, TextureAtlasTile> locationMap;
+ private Map<String, String> mapNameMap;
+ private String rootMapName;
+
+ public TextureAtlas(int width, int height) {
+ this.atlasWidth = width;
+ this.atlasHeight = height;
+ root = new Node(0, 0, width, height);
+ locationMap = new TreeMap<String, TextureAtlasTile>();
+ mapNameMap = new HashMap<String, String>();
+ }
+
+ /**
+ * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas.
+ * @param geometry
+ * @return false if the atlas is full.
+ */
+ public boolean addGeometry(Geometry geometry) {
+ Texture diffuse = getMaterialTexture(geometry, "DiffuseMap");
+ Texture normal = getMaterialTexture(geometry, "NormalMap");
+ Texture specular = getMaterialTexture(geometry, "SpecularMap");
+ if (diffuse == null) {
+ diffuse = getMaterialTexture(geometry, "ColorMap");
+
+ }
+ if (diffuse != null && diffuse.getKey() != null) {
+ String keyName = diffuse.getKey().toString();
+ if (!addTexture(diffuse, "DiffuseMap")) {
+ return false;
+ } else {
+ if (normal != null && normal.getKey() != null) {
+ addTexture(diffuse, "NormalMap", keyName);
+ }
+ if (specular != null && specular.getKey() != null) {
+ addTexture(specular, "SpecularMap", keyName);
+ }
+ }
+ return true;
+ }
+ return true;
+ }
+
+ /**
+ * Add a texture for a specific map name
+ * @param texture A texture to add to the atlas.
+ * @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.
+ * @return false if the atlas is full.
+ */
+ public boolean addTexture(Texture texture, String mapName) {
+ if (texture == null) {
+ throw new IllegalStateException("Texture cannot be null!");
+ }
+ String name = textureName(texture);
+ if (texture.getImage() != null && name != null) {
+ return addImage(texture.getImage(), name, mapName, null);
+ } else {
+ throw new IllegalStateException("Texture has no asset key name!");
+ }
+ }
+
+ /**
+ * Add a texture for a specific map name at the location of another existing texture on the master map.
+ * @param texture A texture to add to the atlas.
+ * @param mapName A freely chosen map name that can be later retrieved as a Texture.
+ * @param masterTexture The master texture for determining the location, it has to exist in tha master map.
+ */
+ public void addTexture(Texture texture, String mapName, Texture masterTexture) {
+ String sourceTextureName = textureName(masterTexture);
+ if (sourceTextureName == null) {
+ throw new IllegalStateException("Supplied master map texture has no asset key name!");
+ } else {
+ addTexture(texture, mapName, sourceTextureName);
+ }
+ }
+
+ /**
+ * Add a texture for a specific map name at the location of another existing texture (on the master map).
+ * @param texture A texture to add to the atlas.
+ * @param mapName A freely chosen map name that can be later retrieved as a Texture.
+ * @param sourceTextureName Name of the master map used for the location.
+ */
+ public void addTexture(Texture texture, String mapName, String sourceTextureName) {
+ if (texture == null) {
+ throw new IllegalStateException("Texture cannot be null!");
+ }
+ String name = textureName(texture);
+ if (texture.getImage() != null && name != null) {
+ addImage(texture.getImage(), name, mapName, sourceTextureName);
+ } else {
+ throw new IllegalStateException("Texture has no asset key name!");
+ }
+ }
+
+ private String textureName(Texture texture) {
+ if (texture == null) {
+ return null;
+ }
+ AssetKey key = texture.getKey();
+ if (key != null) {
+ return key.toString();
+ } else {
+ return null;
+ }
+ }
+
+ private boolean addImage(Image image, String name, String mapName, String sourceTextureName) {
+ if (rootMapName == null) {
+ rootMapName = mapName;
+ }
+ if (sourceTextureName == null && !rootMapName.equals(mapName)) {
+ throw new IllegalStateException("Atlas already has a master map called " + rootMapName + "."
+ + " Textures for new maps have to use a texture from the master map for their location.");
+ }
+ TextureAtlasTile location = locationMap.get(name);
+ if (location != null) {
+ //have location for texture
+ if (!mapName.equals(mapNameMap.get(name))) {
+ 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) + "!");
+ drawImage(image, location.getX(), location.getY(), mapName);
+ return true;
+ } else {
+ return true;
+ }
+ } else if (sourceTextureName == null) {
+ //need to make new tile
+ Node node = root.insert(image);
+ if (node == null) {
+ return false;
+ }
+ location = node.location;
+ } else {
+ //got old tile to align to
+ location = locationMap.get(sourceTextureName);
+ if (location == null) {
+ throw new IllegalStateException("Cannot find master map texture for " + name + ".");
+ } else if (location.width != image.getWidth() || location.height != image.getHeight()) {
+ 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.");
+ }
+ }
+ mapNameMap.put(name, mapName);
+ locationMap.put(name, location);
+ drawImage(image, location.getX(), location.getY(), mapName);
+ return true;
+ }
+
+ private void drawImage(Image source, int x, int y, String mapName) {
+ if (images == null) {
+ images = new HashMap<String, byte[]>();
+ }
+ byte[] image = images.get(mapName);
+ if (image == null) {
+ image = new byte[atlasWidth * atlasHeight * 4];
+ images.put(mapName, image);
+ }
+ //TODO: all buffers?
+ ByteBuffer sourceData = source.getData(0);
+ int height = source.getHeight();
+ int width = source.getWidth();
+ Image newImage = null;
+ for (int yPos = 0; yPos < height; yPos++) {
+ for (int xPos = 0; xPos < width; xPos++) {
+ int i = ((xPos + x) + (yPos + y) * atlasWidth) * 4;
+ if (source.getFormat() == Format.ABGR8) {
+ int j = (xPos + yPos * width) * 4;
+ image[i] = sourceData.get(j); //a
+ image[i + 1] = sourceData.get(j + 1); //b
+ image[i + 2] = sourceData.get(j + 2); //g
+ image[i + 3] = sourceData.get(j + 3); //r
+ } else if (source.getFormat() == Format.BGR8) {
+ int j = (xPos + yPos * width) * 3;
+ image[i] = 1; //a
+ image[i + 1] = sourceData.get(j); //b
+ image[i + 2] = sourceData.get(j + 1); //g
+ image[i + 3] = sourceData.get(j + 2); //r
+ } else if (source.getFormat() == Format.RGB8) {
+ int j = (xPos + yPos * width) * 3;
+ image[i] = 1; //a
+ image[i + 1] = sourceData.get(j + 2); //b
+ image[i + 2] = sourceData.get(j + 1); //g
+ image[i + 3] = sourceData.get(j); //r
+ } else if (source.getFormat() == Format.RGBA8) {
+ int j = (xPos + yPos * width) * 4;
+ image[i] = sourceData.get(j + 3); //a
+ image[i + 1] = sourceData.get(j + 2); //b
+ image[i + 2] = sourceData.get(j + 1); //g
+ image[i + 3] = sourceData.get(j); //r
+ } else if (source.getFormat() == Format.Luminance8) {
+ int j = (xPos + yPos * width) * 1;
+ image[i] = 1; //a
+ image[i + 1] = sourceData.get(j); //b
+ image[i + 2] = sourceData.get(j); //g
+ image[i + 3] = sourceData.get(j); //r
+ } else if (source.getFormat() == Format.Luminance8Alpha8) {
+ int j = (xPos + yPos * width) * 2;
+ image[i] = sourceData.get(j + 1); //a
+ image[i + 1] = sourceData.get(j); //b
+ image[i + 2] = sourceData.get(j); //g
+ image[i + 3] = sourceData.get(j); //r
+ } else {
+ //ImageToAwt conversion
+ if (newImage == null) {
+ newImage = convertImageToAwt(source);
+ if (newImage != null) {
+ source = newImage;
+ sourceData = source.getData(0);
+ int j = (xPos + yPos * width) * 4;
+ image[i] = sourceData.get(j); //a
+ image[i + 1] = sourceData.get(j + 1); //b
+ image[i + 2] = sourceData.get(j + 2); //g
+ image[i + 3] = sourceData.get(j + 3); //r
+ }else{
+ throw new UnsupportedOperationException("Cannot draw or convert textures with format " + source.getFormat());
+ }
+ } else {
+ throw new UnsupportedOperationException("Cannot draw textures with format " + source.getFormat());
+ }
+ }
+ }
+ }
+ }
+
+ private Image convertImageToAwt(Image source) {
+ //use awt dependent classes without actual dependency via reflection
+ try {
+ Class clazz = Class.forName("jme3tools.converters.ImageToAwt");
+ if (clazz == null) {
+ return null;
+ }
+ Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4));
+ clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage);
+ return newImage;
+ } catch (InstantiationException ex) {
+ } catch (IllegalAccessException ex) {
+ } catch (IllegalArgumentException ex) {
+ } catch (InvocationTargetException ex) {
+ } catch (NoSuchMethodException ex) {
+ } catch (SecurityException ex) {
+ } catch (ClassNotFoundException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Get the <code>TextureAtlasTile</code> for the given Texture
+ * @param texture The texture to retrieve the <code>TextureAtlasTile</code> for.
+ * @return
+ */
+ public TextureAtlasTile getAtlasTile(Texture texture) {
+ String sourceTextureName = textureName(texture);
+ if (sourceTextureName != null) {
+ return getAtlasTile(sourceTextureName);
+ }
+ return null;
+ }
+
+ /**
+ * Get the <code>TextureAtlasTile</code> for the given Texture
+ * @param assetName The texture to retrieve the <code>TextureAtlasTile</code> for.
+ * @return
+ */
+ private TextureAtlasTile getAtlasTile(String assetName) {
+ return locationMap.get(assetName);
+ }
+
+ /**
+ * Creates a new atlas texture for the given map name.
+ * @param mapName
+ * @return
+ */
+ public Texture getAtlasTexture(String mapName) {
+ if (images == null) {
+ return null;
+ }
+ byte[] image = images.get(mapName);
+ if (image != null) {
+ Texture2D tex = new Texture2D(new Image(format, atlasWidth, atlasHeight, BufferUtils.createByteBuffer(image)));
+ tex.setMagFilter(Texture.MagFilter.Bilinear);
+ tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap);
+ tex.setWrap(Texture.WrapMode.Clamp);
+ return tex;
+ }
+ return null;
+ }
+
+ /**
+ * Applies the texture coordinates to the given geometry
+ * if its DiffuseMap or ColorMap exists in the atlas.
+ * @param geom The geometry to change the texture coordinate buffer on.
+ * @return true if texture has been found and coords have been changed, false otherwise.
+ */
+ public boolean applyCoords(Geometry geom) {
+ return applyCoords(geom, 0, geom.getMesh());
+ }
+
+ /**
+ * Applies the texture coordinates to the given output mesh
+ * if the DiffuseMap or ColorMap of the input geometry exist in the atlas.
+ * @param geom The geometry to change the texture coordinate buffer on.
+ * @param offset Target buffer offset.
+ * @param outMesh The mesh to set the coords in (can be same as input).
+ * @return true if texture has been found and coords have been changed, false otherwise.
+ */
+ public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) {
+ Mesh inMesh = geom.getMesh();
+ geom.computeWorldMatrix();
+
+ VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord);
+ VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord);
+
+ if (inBuf == null || outBuf == null) {
+ throw new IllegalStateException("Geometry mesh has no texture coordinate buffer.");
+ }
+
+ Texture tex = getMaterialTexture(geom, "DiffuseMap");
+ if (tex == null) {
+ tex = getMaterialTexture(geom, "ColorMap");
+
+ }
+ if (tex != null) {
+ TextureAtlasTile tile = getAtlasTile(tex);
+ if (tile != null) {
+ FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+ FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+ tile.transformTextureCoords(inPos, offset, outPos);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ throw new IllegalStateException("Geometry has no proper texture.");
+ }
+ }
+
+ /**
+ * Create a texture atlas for the given root node, containing DiffuseMap, NormalMap and SpecularMap.
+ * @param root The rootNode to create the atlas for.
+ * @param atlasSize The size of the atlas (width and height).
+ * @return Null if the atlas cannot be created because not all textures fit.
+ */
+ public static TextureAtlas createAtlas(Spatial root, int atlasSize) {
+ List<Geometry> geometries = new ArrayList<Geometry>();
+ GeometryBatchFactory.gatherGeoms(root, geometries);
+ TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize);
+ for (Geometry geometry : geometries) {
+ if (!atlas.addGeometry(geometry)) {
+ logger.log(Level.WARNING, "Texture atlas size too small, cannot add all textures");
+ return null;
+ }
+ }
+ return atlas;
+ }
+
+ /**
+ * Creates one geometry out of the given root spatial and merges all single
+ * textures into one texture of the given size.
+ * @param spat The root spatial of the scene to batch
+ * @param mgr An assetmanager that can be used to create the material.
+ * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures.
+ * @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.
+ */
+ public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) {
+ List<Geometry> geometries = new ArrayList<Geometry>();
+ GeometryBatchFactory.gatherGeoms(spat, geometries);
+ TextureAtlas atlas = createAtlas(spat, atlasSize);
+ if (atlas == null) {
+ return null;
+ }
+ Geometry geom = new Geometry();
+ Mesh mesh = new Mesh();
+ GeometryBatchFactory.mergeGeometries(geometries, mesh);
+ applyAtlasCoords(geometries, mesh, atlas);
+ mesh.updateCounts();
+ mesh.updateBound();
+ geom.setMesh(mesh);
+
+ Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
+ mat.getAdditionalRenderState().setAlphaTest(true);
+ Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap");
+ Texture normalMap = atlas.getAtlasTexture("NormalMap");
+ Texture specularMap = atlas.getAtlasTexture("SpecularMap");
+ if (diffuseMap != null) {
+ mat.setTexture("DiffuseMap", diffuseMap);
+ }
+ if (normalMap != null) {
+ mat.setTexture("NormalMap", normalMap);
+ }
+ if (specularMap != null) {
+ mat.setTexture("SpecularMap", specularMap);
+ }
+ mat.setFloat("Shininess", 16.0f);
+
+ geom.setMaterial(mat);
+ return geom;
+ }
+
+ private static void applyAtlasCoords(List<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) {
+ int globalVertIndex = 0;
+
+ for (Geometry geom : geometries) {
+ Mesh inMesh = geom.getMesh();
+ geom.computeWorldMatrix();
+
+ int geomVertCount = inMesh.getVertexCount();
+
+ VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord);
+ VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord);
+
+ if (inBuf == null || outBuf == null) {
+ continue;
+ }
+
+ atlas.applyCoords(geom, globalVertIndex, outMesh);
+
+ globalVertIndex += geomVertCount;
+ }
+ }
+
+ private static Texture getMaterialTexture(Geometry geometry, String mapName) {
+ Material mat = geometry.getMaterial();
+ if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) {
+ return null;
+ }
+ MatParamTexture param = (MatParamTexture) mat.getParam(mapName);
+ Texture texture = param.getTextureValue();
+ if (texture == null) {
+ return null;
+ }
+ return texture;
+
+
+ }
+
+ private class Node {
+
+ public TextureAtlasTile location;
+ public Node child[];
+ public boolean occupied;
+
+ public Node(int x, int y, int width, int height) {
+ location = new TextureAtlasTile(x, y, width, height);
+ child = new Node[2];
+ child[0] = null;
+ child[1] = null;
+ occupied = false;
+ }
+
+ public boolean isLeaf() {
+ return child[0] == null && child[1] == null;
+ }
+
+ // Algorithm from http://www.blackpawn.com/texts/lightmaps/
+ public Node insert(Image image) {
+ if (!isLeaf()) {
+ Node newNode = child[0].insert(image);
+
+ if (newNode != null) {
+ return newNode;
+ }
+
+ return child[1].insert(image);
+ } else {
+ if (occupied) {
+ return null; // occupied
+ }
+
+ if (image.getWidth() > location.getWidth() || image.getHeight() > location.getHeight()) {
+ return null; // does not fit
+ }
+
+ if (image.getWidth() == location.getWidth() && image.getHeight() == location.getHeight()) {
+ occupied = true; // perfect fit
+ return this;
+ }
+
+ int dw = location.getWidth() - image.getWidth();
+ int dh = location.getHeight() - image.getHeight();
+
+ if (dw > dh) {
+ child[0] = new Node(location.getX(), location.getY(), image.getWidth(), location.getHeight());
+ child[1] = new Node(location.getX() + image.getWidth(), location.getY(), location.getWidth() - image.getWidth(), location.getHeight());
+ } else {
+ child[0] = new Node(location.getX(), location.getY(), location.getWidth(), image.getHeight());
+ child[1] = new Node(location.getX(), location.getY() + image.getHeight(), location.getWidth(), location.getHeight() - image.getHeight());
+ }
+
+ return child[0].insert(image);
+ }
+ }
+ }
+
+ public class TextureAtlasTile {
+
+ private int x;
+ private int y;
+ private int width;
+ private int height;
+
+ public TextureAtlasTile(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Get the transformed texture coordinate for a given input location.
+ * @param previousLocation The old texture coordinate.
+ * @return The new texture coordinate inside the atlas.
+ */
+ public Vector2f getLocation(Vector2f previousLocation) {
+ float x = (float) getX() / (float) atlasWidth;
+ float y = (float) getY() / (float) atlasHeight;
+ float w = (float) getWidth() / (float) atlasWidth;
+ float h = (float) getHeight() / (float) atlasHeight;
+ Vector2f location = new Vector2f(x, y);
+ float prevX = previousLocation.x;
+ float prevY = previousLocation.y;
+ location.addLocal(prevX * w, prevY * h);
+ return location;
+ }
+
+ /**
+ * Transforms a whole texture coordinates buffer.
+ * @param inBuf The input texture buffer.
+ * @param offset The offset in the output buffer
+ * @param outBuf The output buffer.
+ */
+ public void transformTextureCoords(FloatBuffer inBuf, int offset, FloatBuffer outBuf) {
+ Vector2f tex = new Vector2f();
+
+ // offset is given in element units
+ // convert to be in component units
+ offset *= 2;
+
+ for (int i = 0; i < inBuf.capacity() / 2; i++) {
+ tex.x = inBuf.get(i * 2 + 0);
+ tex.y = inBuf.get(i * 2 + 1);
+ Vector2f location = getLocation(tex);
+ //TODO: add proper texture wrapping for atlases..
+ outBuf.put(offset + i * 2 + 0, location.x);
+ outBuf.put(offset + i * 2 + 1, location.y);
+ }
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+ }
+}