I wrote my texture atlas class based on JME3 texture atlas with some optimizations. It can be useful for someone.
[java]
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
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;
public class FixedTileSizeAtlas {
private int tileSize;
private int tileCount;
private boolean normalMapMode;
private float textureTileSize;
private Format imageFormat = Format.ABGR8;
private int AIndex = 0;
private int BIndex = 1;
private int GIndex = 2;
private int RIndex = 3;
private byte[] imageData;
private ArrayList<FixedTileSizeAtlasTile> tiles;
private LinkedList<FixedTileSizeAtlasTile> freeTiles;
private static final byte[] EmptyTileColor = new byte[]{-1,-83,-83}; // pink
private static final byte[] EmptyTileNormalColor = new byte[]{80,80,120}; // normal map color for pink
public FixedTileSizeAtlas(int tileCount, int tileSize, boolean normalMap) {
this.tileCount = tileCount;
this.tileSize = tileSize;
this.normalMapMode = normalMap;
this.textureTileSize = (float)tileSize / (float)(tileCount/2*tileSize);
this.tiles = new ArrayList<FixedTileSizeAtlasTile>();
imageData = new byte[tileCount*tileSize*tileSize*4];
generateTiles();
}
private void generateTiles() {
freeTiles = new LinkedList<FixedTileSizeAtlasTile>();
int tilesInLine = tileCount/2;
float tileX = 0f;
float tileY = 0f;
for (int tileRow = 0; tileRow < tilesInLine; ++tileRow) {
for (int tileCol = 0; tileCol < tilesInLine ; ++tileCol) {
freeTiles.add(new FixedTileSizeAtlasTile(tileX,tileY));
tileX += textureTileSize;
}
tileX = 0f;
tileY += textureTileSize;
}
}
public short addTexture(Texture sourceTexture) {
Image sourceImage = sourceTexture.getImage();
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
FixedTileSizeAtlasTile tile = getNextFreeTile();
tiles.add(tile);
++tileCount;
if (width != tileSize || height != tileSize) {
drawEmptyImage(tile);
} else {
drawImage(sourceImage,tile);
}
return (short) (tileCount-1);
}
public FixedTileSizeAtlasTile getTile(int index) {
return tiles.get(index);
}
public void applyTextureCoords(Geometry geometry, Type textureType, int index) {
FixedTileSizeAtlasTile tile = getTile(index);
geometry.computeWorldMatrix();
Mesh mesh = geometry.getMesh();
FloatBuffer buffer = (FloatBuffer) mesh.getBuffer(textureType).getData();
tile.transformTextureCoords(buffer);
}
private FixedTileSizeAtlasTile getNextFreeTile() {
return freeTiles.poll();
}
private void drawEmptyImage(FixedTileSizeAtlasTile tile) {
int tileSize = getTileSize();
int atlasSize = getAtlasSize();
int tileX = tile.getX();
int tileY = tile.getY();
int i = 0;
byte[] color = EmptyTileColor;
if (normalMapMode) {
color = EmptyTileNormalColor;
}
for (int y = 0; y < tileSize; ++y) {
for (int x = 0; x < tileSize; ++x) {
i = ((x+tileX)+(y+tileY)*atlasSize)*4;
imageData[i+AIndex] = 1;
imageData[i+BIndex] = color[2];
imageData[i+GIndex] = color[1];
imageData[i+RIndex] = color[0];
}
}
}
private void drawImage(Image sourceImage, FixedTileSizeAtlasTile tile) {
Format sourceImageFormat = sourceImage.getFormat();
int ai = AIndex;
int bi = BIndex;
int gi = GIndex;
int ri = RIndex;
if (sourceImageFormat != imageFormat) {
if (sourceImageFormat == Format.RGBA8) {
ai = 3;
bi = 2;
gi = 1;
ri = 0;
} else if (sourceImageFormat == Format.RGB8) {
ai = -1;
bi = 2;
gi = 1;
ri = 0;
} else if (sourceImageFormat == Format.BGR8) {
ai = -1;
bi = 0;
gi = 1;
ri = 2;
}
}
ByteBuffer sourceImageData = sourceImage.getData(0);
int tileX = tile.getX();
int tileY = tile.getY();
int tileSize = getTileSize();
int atlasSize = getAtlasSize();
int i,j;
if (ai != -1) {
for (int y = 0; y < tileSize; ++y) {
for (int x = 0; x < tileSize; ++x) {
i = ((x+tileX)+(y+tileY)*atlasSize)*4;
j = (x + y *tileSize) * 4;
imageData[i+AIndex] = sourceImageData.get(j+ai);
imageData[i+BIndex] = sourceImageData.get(j+bi);
imageData[i+GIndex] = sourceImageData.get(j+gi);
imageData[i+RIndex] = sourceImageData.get(j+ri);
}
}
} else {
for (int y = 0; y < tileSize; ++y) {
for (int x = 0; x < tileSize; ++x) {
i = ((x+tileX)+(y+tileY)*atlasSize)*4;
j = (x + y *tileSize) * 3;
imageData[i+AIndex] = 1;
imageData[i+BIndex] = sourceImageData.get(j+bi);
imageData[i+GIndex] = sourceImageData.get(j+gi);
imageData[i+RIndex] = sourceImageData.get(j+ri);
}
}
}
}
public int getTilesAmount() {
return tileCount;
}
public int getTilesInLineAmount() {
return getTilesAmount()/2;
}
public int getAtlasSize() {
return getTilesInLineAmount()*getTileSize();
}
public int getTileSize() {
return tileSize;
}
public float getTextureTileSize() {
return textureTileSize;
}
public Texture getAtlasTexture() {
Texture texture = new Texture2D(
new Image(imageFormat,getAtlasSize(),getAtlasSize(),
BufferUtils.createByteBuffer(getImageData())));
texture.setMagFilter(Texture.MagFilter.Bilinear);
texture.setMinFilter(Texture.MinFilter.BilinearNearestMipMap);
texture.setWrap(Texture.WrapMode.Clamp);
return texture;
}
public byte[] getImageData() {
return imageData;
}
public List<FixedTileSizeAtlasTile> getFreeTiles() {
return freeTiles;
}
public class FixedTileSizeAtlasTile {
private Vector2f location;
private float textureX;
private float textureY;
public FixedTileSizeAtlasTile(float textureX, float textureY) {
location = new Vector2f();
this.textureX = textureX;
this.textureY = textureY;
}
public Vector2f getLocation(float prevTextureX, float prevTextureY) {
location.x = getTextureX();
location.y = getTextureY();
location.addLocal(prevTextureX*textureTileSize,prevTextureY*textureTileSize);
return location;
}
public void transformTextureCoords(FloatBuffer inoutBuffer) {
transformTextureCoords(inoutBuffer,inoutBuffer);
}
public void transformTextureCoords(FloatBuffer inBuffer, FloatBuffer outBuffer) {
int limit = inBuffer.limit()>>1;
int index = 0;
for (int i = 0; i < limit; i++) {
index = i<<1;
Vector2f location = getLocation(inBuffer.get(index),inBuffer.get(index + 1));
outBuffer.put(index, location.x);
outBuffer.put(index + 1, location.y);
}
}
public int getX() {
return (int) (getTextureX()*(float)getAtlasSize());
}
public int getY() {
return (int) (getTextureY()*(float)getAtlasSize());
}
public float getTextureX() {
return textureX;
}
public float getTextureY() {
return textureY;
}
}
}
[/java]