package org.andengine.extension.tmx;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import org.andengine.engine.camera.Camera;
import org.andengine.entity.sprite.Sprite;
import org.andengine.entity.sprite.batch.SpriteBatch;
import org.andengine.extension.tmx.TMXLoader.ITMXTilePropertiesListener;
import org.andengine.extension.tmx.util.constants.TMXConstants;
import org.andengine.opengl.texture.ITexture;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.util.GLState;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
import org.andengine.util.SAXUtils;
import org.andengine.util.StreamUtils;
import org.andengine.util.algorithm.collision.RectangularShapeCollisionChecker;
import org.andengine.util.base64.Base64;
import org.andengine.util.base64.Base64InputStream;
import org.andengine.util.color.Color;
import org.andengine.util.exception.AndEngineRuntimeException;
import org.andengine.util.exception.MethodNotSupportedException;
import org.andengine.util.math.MathUtils;
import org.xml.sax.Attributes;
import android.opengl.GLES20;
import android.util.Log;
/**
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 20:27:31 - 20.07.2010
*/
public class TMXLayer extends SpriteBatch implements TMXConstants {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private final TMXTiledMap mTMXTiledMap;
private final String mName;
private final int mTileColumns;
private final int mTileRows;
private final TMXTile[][] mTMXTiles;
private int mTilesAdded;
private final int mGlobalTileIDsExpected;
private final float[] mCullingVertices = new float[2 * Sprite.VERTICES_PER_SPRITE];
private final TMXProperties<TMXLayerProperty> mTMXLayerProperties = new TMXProperties<TMXLayerProperty>();
private final int mWidth;
private final int mHeight;
// ===========================================================
// Constructors
// ===========================================================
public TMXLayer(final TMXTiledMap pTMXTiledMap, final Attributes pAttributes, final VertexBufferObjectManager pVertexBufferObjectManager) {
super(null, SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_WIDTH) * SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_HEIGHT), pVertexBufferObjectManager);
Log.v("FEBI","w: "+SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_WIDTH)+"h: "+SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_HEIGHT));
this.mTMXTiledMap = pTMXTiledMap;
this.mName = pAttributes.getValue("", TMXConstants.TAG_LAYER_ATTRIBUTE_NAME);
this.mTileColumns = SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_WIDTH);
this.mTileRows = SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_HEIGHT);
this.mTMXTiles = new TMXTile[this.mTileRows][this.mTileColumns];
this.mWidth = pTMXTiledMap.getTileWidth() * this.mTileColumns;
this.mHeight = pTMXTiledMap.getTileHeight() * this.mTileRows;
this.mRotationCenterX = this.mWidth * 0.5f;
this.mRotationCenterY = this.mHeight * 0.5f;
this.mScaleCenterX = this.mRotationCenterX;
this.mScaleCenterY = this.mRotationCenterY;
this.mGlobalTileIDsExpected = this.mTileColumns * this.mTileRows;
this.setVisible(SAXUtils.getIntAttribute(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_VISIBLE, TMXConstants.TAG_LAYER_ATTRIBUTE_VISIBLE_VALUE_DEFAULT) == 1);
this.setAlpha(SAXUtils.getFloatAttribute(pAttributes, TMXConstants.TAG_LAYER_ATTRIBUTE_OPACITY, TMXConstants.TAG_LAYER_ATTRIBUTE_OPACITY_VALUE_DEFAULT));
}
// ===========================================================
// Getter & Setter
// ===========================================================
public String getName() {
return this.mName;
}
public int getWidth() {
return this.mWidth;
}
public int getHeight() {
return this.mHeight;
}
public int getTileColumns() {
return this.mTileColumns;
}
public int getTileRows() {
return this.mTileRows;
}
public TMXTile[][] getTMXTiles() {
return this.mTMXTiles;
}
public TMXTile getTMXTile(final int pTileColumn, final int pTileRow) throws ArrayIndexOutOfBoundsException {
return this.mTMXTiles[pTileRow][pTileColumn];
}
/**
* @param pX in SceneCoordinates.
* @param pY in SceneCoordinates.
* @return the {@link TMXTile} located at <code>pX/pY</code>.
*/
public TMXTile getTMXTileAt(final float pX, final float pY) {
final float[] localCoords = this.convertSceneToLocalCoordinates(pX, pY);
final TMXTiledMap tmxTiledMap = this.mTMXTiledMap;
final int tileColumn = (int)(localCoords[SpriteBatch.VERTEX_INDEX_X] / tmxTiledMap.getTileWidth());
if(tileColumn < 0 || tileColumn > this.mTileColumns - 1) {
return null;
}
final int tileRow = (int)(localCoords[SpriteBatch.VERTEX_INDEX_Y] / tmxTiledMap.getTileWidth());
if(tileRow < 0 || tileRow > this.mTileRows - 1) {
return null;
}
return this.mTMXTiles[tileRow][tileColumn];
}
public void addTMXLayerProperty(final TMXLayerProperty pTMXLayerProperty) {
this.mTMXLayerProperties.add(pTMXLayerProperty);
}
public TMXProperties<TMXLayerProperty> getTMXLayerProperties() {
return this.mTMXLayerProperties;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
protected void initBlendFunction(final ITexture pTexture) {
}
@Override
@Deprecated
public void setRotation(final float pRotation) throws MethodNotSupportedException {
throw new MethodNotSupportedException();
}
@Override
protected void onManagedUpdate(final float pSecondsElapsed) {
/* Nothing. */
}
@Override
protected void draw(final GLState pGLState, final Camera pCamera) {
final int tileColumns = this.mTileColumns;
final int tileRows = this.mTileRows;
final int tileWidth = this.mTMXTiledMap.getTileWidth();
final int tileHeight = this.mTMXTiledMap.getTileHeight();
final float scaledTileWidth = tileWidth * this.mScaleX;
final float scaledTileHeight = tileHeight * this.mScaleY;
final float[] cullingVertices = this.mCullingVertices;
RectangularShapeCollisionChecker.fillVertices(0, 0, this.mWidth, this.mHeight, this.getLocalToSceneTransformation(), cullingVertices);
final float layerMinX = cullingVertices[SpriteBatch.VERTEX_INDEX_X];
final float layerMinY = cullingVertices[SpriteBatch.VERTEX_INDEX_Y];
final float cameraMinX = pCamera.getXMin();
final float cameraMinY = pCamera.getYMin();
final float cameraWidth = pCamera.getWidth();
final float cameraHeight = pCamera.getHeight();
/* Determine the area that is visible in the camera. */
final float firstColumnRaw = (cameraMinX - layerMinX) / scaledTileWidth;
final int firstColumn = MathUtils.bringToBounds(0, tileColumns - 1, (int)Math.floor(firstColumnRaw));
final int lastColumn = MathUtils.bringToBounds(0, tileColumns - 1, (int)Math.ceil(firstColumnRaw + cameraWidth / scaledTileWidth));
final float firstRowRaw = (cameraMinY - layerMinY) / scaledTileHeight;
final int firstRow = MathUtils.bringToBounds(0, tileRows - 1, (int)Math.floor(firstRowRaw));
final int lastRow = MathUtils.bringToBounds(0, tileRows - 1, (int)Math.floor(firstRowRaw + cameraHeight / scaledTileHeight));
for(int row = firstRow; row <= lastRow; row++) {
for(int column = firstColumn; column <= lastColumn; column++) {
this.mSpriteBatchVertexBufferObject.draw(GLES20.GL_TRIANGLE_STRIP, this.getSpriteBatchIndex(column, row) * SpriteBatch.VERTICES_PER_SPRITE, SpriteBatch.VERTICES_PER_SPRITE);
}
}
}
// ===========================================================
// Methods
// ===========================================================
void initializeTMXTileFromXML(final Attributes pAttributes, final ITMXTilePropertiesListener pTMXTilePropertyListener) {
this.addTileByGlobalTileID(SAXUtils.getIntAttributeOrThrow(pAttributes, TMXConstants.TAG_TILE_ATTRIBUTE_GID), pTMXTilePropertyListener);
}
void initializeTMXTilesFromDataString(final String pDataString, final String pDataEncoding, final String pDataCompression, final ITMXTilePropertiesListener pTMXTilePropertyListener) throws IOException, IllegalArgumentException {
DataInputStream dataIn = null;
try{
InputStream in = new ByteArrayInputStream(pDataString.getBytes("UTF-8"));
/* Wrap decoding Streams if necessary. */
if(pDataEncoding != null) {
if(pDataEncoding.equals(TMXConstants.TAG_DATA_ATTRIBUTE_ENCODING_VALUE_BASE64)) {
in = new Base64InputStream(in, Base64.DEFAULT);
} else {
throw new IllegalArgumentException("Supplied encoding '" + pDataEncoding + "' is not supported yet.");
}
}
if(pDataCompression != null) {
if(pDataCompression.equals(TMXConstants.TAG_DATA_ATTRIBUTE_COMPRESSION_VALUE_GZIP)) {
in = new GZIPInputStream(in);
} else if(pDataCompression.equals(TMXConstants.TAG_DATA_ATTRIBUTE_COMPRESSION_VALUE_ZLIB)) {
in = new InflaterInputStream(in);
} else {
throw new IllegalArgumentException("Supplied compression '" + pDataCompression + "' is not supported yet.");
}
}
dataIn = new DataInputStream(in);
while(this.mTilesAdded < this.mGlobalTileIDsExpected) {
final int globalTileID = this.readGlobalTileID(dataIn);
this.addTileByGlobalTileID(globalTileID, pTMXTilePropertyListener);
}
} finally {
StreamUtils.close(dataIn);
}
}
private void addTileByGlobalTileID(final int pGlobalTileID, final ITMXTilePropertiesListener pTMXTilePropertyListener) {
final TMXTiledMap tmxTiledMap = this.mTMXTiledMap;
final int tilesHorizontal = this.mTileColumns;
final int column = this.mTilesAdded % tilesHorizontal;
final int row = this.mTilesAdded / tilesHorizontal;
final TMXTile[][] tmxTiles = this.mTMXTiles;
final ITextureRegion tmxTileTextureRegion;
if(pGlobalTileID == 0) {
tmxTileTextureRegion = null;
} else {
tmxTileTextureRegion = tmxTiledMap.getTextureRegionFromGlobalTileID(pGlobalTileID);
if(this.mTexture == null) {
this.mTexture = tmxTileTextureRegion.getTexture();
super.initBlendFunction(this.mTexture);
} else {
if(this.mTexture != tmxTileTextureRegion.getTexture()) {
throw new AndEngineRuntimeException("All TMXTiles in a TMXLayer need to be in the same TMXTileSet.");
}
}
}
final int tileHeight = this.mTMXTiledMap.getTileHeight();
final int tileWidth = this.mTMXTiledMap.getTileWidth();
final TMXTile tmxTile = new TMXTile(pGlobalTileID, column, row, tileWidth, tileHeight, tmxTileTextureRegion);
tmxTiles[row][column] = tmxTile;
//this.setIndex(this.getSpriteBatchIndex(column, row));
//this.drawWithoutChecks(tmxTileTextureRegion, tmxTile.getTileX(), tmxTile.getTileY(), tileWidth, tileHeight, Color.WHITE_ABGR_PACKED_FLOAT);
//this.submit(); // TODO Doesn't need to be called here, but should rather be called in a "init" step, when parsing the XML is complete.
if(pGlobalTileID != 0) {
this.setIndex(this.getSpriteBatchIndex(column, row));
this.drawWithoutChecks(tmxTileTextureRegion, tmxTile.getTileX(), tmxTile.getTileY(), tileWidth, tileHeight, Color.WHITE_ABGR_PACKED_FLOAT);
this.submit(); // TODO Doesn't need to be called here, but should rather be called in a "init" step, when parsing the XML is complete.
/* Notify the ITMXTilePropertiesListener if it exists. */
if(pTMXTilePropertyListener != null) {
final TMXProperties<TMXTileProperty> tmxTileProperties = tmxTiledMap.getTMXTileProperties(pGlobalTileID);
if(tmxTileProperties != null) {
pTMXTilePropertyListener.onTMXTileWithPropertiesCreated(tmxTiledMap, this, tmxTile, tmxTileProperties);
}
}
}
this.mTilesAdded++;
}
private int getSpriteBatchIndex(final int pColumn, final int pRow) {
return pRow * this.mTileColumns + pColumn;
}
private int readGlobalTileID(final DataInputStream pDataIn) throws IOException {
final int lowestByte = pDataIn.read();
final int secondLowestByte = pDataIn.read();
final int secondHighestByte = pDataIn.read();
final int highestByte = pDataIn.read();
if(lowestByte < 0 || secondLowestByte < 0 || secondHighestByte < 0 || highestByte < 0) {
throw new IllegalArgumentException("Couldn't read global Tile ID.");
}
return lowestByte | secondLowestByte << 8 |secondHighestByte << 16 | highestByte << 24;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}