FixedTileSizeAtlas class

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]