package net.kennux.cubicworld.assets;
import java.util.ArrayList;
import java.util.HashMap;
import net.kennux.cubicworld.util.Mathf;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
/**
* <pre>
* Texture atlas implementation.
* It can build a texture atlas based on multiple textures given or it can load
* already packed atlases.
*
* Important thing about this class is, all your textures need to have the same size (power of 2)!
* </pre>
*
* @author KennuX
*
*/
public class TextureAtlas
{
/**
* The already builded atlas.
*/
public Texture atlasTexture;
/**
* Single atlas textures used when compiling this texture atlas.
*/
private ArrayList<Texture> textures;
/**
* Contains the texture coordinates for the texture id in the key.
*/
private static HashMap<Integer, Vector2[]> textureCoordinates;
/**
* Single texture width.
*/
private int textureWidth;
/**
* Single texture height.
*/
private int textureHeight;
/**
* Gets set to the maximum number of textures this atlas can hold.
*/
private int spaceForTextures;
/**
* The maximum atlas size x and y
*/
private static final int maximumAtlasSize = 4096;
// Atlas dimensions calculated in compile function
private int atlasWidth;
private int atlasHeight;
/**
* <pre>
* Initializes the texture atlas in texture compilation mode.
* Add textures by addTextures().
* If you're done call compile().
* </pre>
*
* @param textureHeight
* @param textureWidth
*/
public TextureAtlas(int textureWidth, int textureHeight)
{
// Init compilation mode
this.textures = new ArrayList<Texture>();
// Init texture width
this.textureWidth = textureWidth;
this.textureHeight = textureHeight;
textureCoordinates = new HashMap<Integer, Vector2[]>();
this.spaceForTextures = (this.textureWidth * this.textureHeight) / (maximumAtlasSize * maximumAtlasSize);
}
/**
* <pre>
* Adds a texture to the texture atlas in compilation mode.
* You cannot edit this after you compiled the texture atlas.
* Or if you are not in compilation mode.
*
* Returns -1 in case of an error, otherwise the texture's id.
* </pre>
*
* @param t
*/
public int addTexture(Texture t)
{
if (this.textures != null)
{
this.textures.add(t);
return this.textures.size() - 1;
}
else
return -1;
}
/**
* <pre>
* Compiles all textures in this.textures.
* After calling this function you cannot add any more textures.
* </pre>
*/
public void compileTexture()
{
// Max textures per axis
int maxTexturesOnX = Mathf.floorToInt((float) maximumAtlasSize / (float) textureWidth);
int maxTexturesOnY = Mathf.floorToInt((float) maximumAtlasSize / (float) textureHeight);
// texture per axis
int texturesOnX = Mathf.min(textures.size(), maxTexturesOnX);
int texturesOnY = Mathf.ceilToInt((float) textures.size() / (float) maxTexturesOnY);
// Calculate atlas dimensions
this.atlasWidth = Mathf.min(textures.size(), maxTexturesOnX) * textureWidth;
this.atlasHeight = Mathf.ceilToInt((float) textures.size() / (float) maxTexturesOnY) * textureWidth;
// Create texture
Pixmap atlasData = new Pixmap(atlasWidth, atlasHeight, Format.RGBA8888);
// Build atlas
int texture = 0;
for (int x = 0; x < texturesOnX; x++)
{
for (int y = 0; y < texturesOnY; y++)
{
// Get current starting position
int xPosition = x * textureWidth;
int yPosition = y * textureHeight;
// Get current texture
Texture currentTexture = textures.get(texture);
currentTexture.getTextureData().prepare();
// Calculate the texture coordinates
textureCoordinates.put(texture, new Vector2[] { new Vector2((float) xPosition / (float) atlasWidth, ((float) yPosition / (float) atlasHeight) + ((float) textureHeight / (float) atlasHeight)), new Vector2(((float) xPosition / (float) atlasWidth) + ((float) textureWidth / (float) atlasWidth), ((float) yPosition / (float) atlasHeight) + ((float) textureHeight / (float) atlasHeight)), new Vector2(((float) xPosition / (float) atlasWidth) + ((float) textureWidth / (float) atlasWidth), (float) yPosition / (float) atlasHeight), new Vector2((float) xPosition / (float) atlasWidth, (float) yPosition / (float) atlasHeight) });
// Write texture to atlas
atlasData.drawPixmap(currentTexture.getTextureData().consumePixmap(), xPosition, yPosition);
texture++;
}
}
this.atlasTexture = new Texture(atlasData, Format.RGBA8888, false);
this.atlasTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
/**
* <pre>
* Gets the atlas region for the texture texture at position n.
* n is the number of the texture id.
* the ids are given in the order you called addTexture.
* </pre>
*
* @param n
*/
public Rectangle getAtlasRegion(int textureId)
{
int maxTexturesOnX = Mathf.floorToInt((float) maximumAtlasSize / (float) textureWidth);
int maxTexturesOnY = Mathf.floorToInt((float) maximumAtlasSize / (float) textureHeight);
int textureY = textureId % maxTexturesOnY;
int textureX = Mathf.floorToInt(textureId / (float) maxTexturesOnX);
// build rectangle
return new Rectangle(textureX * this.textureWidth, textureY * this.textureHeight, this.textureWidth, this.textureHeight);
}
/**
* <pre>
* Returns the current atlas texture.
* Returns null if in compilation state and not compiled yet.
*
* </pre>
*
* @return
*/
public Texture getAtlasTexture()
{
return this.atlasTexture;
}
/**
* Returns the texture coordinates for the given texture id.
*
* @param textureId
* @return
*/
public Vector2[] getUvForTexture(int textureId)
{
/*
* int maxTexturesOnX = Mathf.floorToInt((float) maximumAtlasSize / (float) textureWidth);
* int maxTexturesOnY = Mathf.floorToInt((float) maximumAtlasSize / (float) textureHeight);
*
* int xPosition = textureId % maxTexturesOnY;
* int yPosition = Mathf.floorToInt(textureId / (float) maxTexturesOnX);
*
* return new Vector2[] { new Vector2((float) xPosition / (float) this.atlasWidth,
* ((float) yPosition / (float) this.atlasHeight) + ((float) this.textureHeight / (float) this.atlasHeight)),
* new Vector2(((float) xPosition / (float) this.atlasWidth) + ((float) this.textureWidth / (float) this.atlasWidth),
* ((float) yPosition / (float) this.atlasHeight) + ((float) this.textureHeight / (float) this.atlasHeight)),
* new Vector2(((float) xPosition / (float) this.atlasWidth) + ((float) this.textureWidth / (float) this.atlasWidth),
* (float) yPosition / (float) this.atlasHeight), new Vector2((float) xPosition / (float) this.atlasWidth,
* (float) yPosition / (float) this.atlasHeight) };
*/
return textureCoordinates.get(new Integer(textureId));
}
/**
* Returns true if this texture atlas has space for another texture.
*
* @return
*/
public boolean hasSpace()
{
return this.textures.size() < this.spaceForTextures;
}
}