package org.anddev.andengine.entity.layer.tiled.tmx; import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_X; import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_Y; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import org.anddev.andengine.collision.RectangularShapeCollisionChecker; import org.anddev.andengine.engine.camera.Camera; import org.anddev.andengine.entity.layer.tiled.tmx.TMXLoader.ITMXTilePropertiesListener; import org.anddev.andengine.entity.layer.tiled.tmx.util.constants.TMXConstants; import org.anddev.andengine.entity.shape.RectangularShape; import org.anddev.andengine.opengl.texture.region.TextureRegion; import org.anddev.andengine.opengl.util.GLHelper; import org.anddev.andengine.util.Base64; import org.anddev.andengine.util.Base64InputStream; import org.anddev.andengine.util.MathUtils; import org.anddev.andengine.util.SAXUtils; import org.anddev.andengine.util.StreamUtils; import org.xml.sax.Attributes; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 20:27:31 - 20.07.2010 */ public class TMXLayer extends RectangularShape 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 * 4]; private final TMXProperties<TMXLayerProperty> mTMXLayerProperties = new TMXProperties<TMXLayerProperty>(); // =========================================================== // Constructors // =========================================================== public TMXLayer(final TMXTiledMap pTMXTiledMap, final Attributes pAttributes) { super(0, 0, 0, 0, null); this.mTMXTiledMap = pTMXTiledMap; this.mName = pAttributes.getValue("", TAG_LAYER_ATTRIBUTE_NAME); this.mTileColumns = SAXUtils.getIntAttributeOrThrow(pAttributes, TAG_LAYER_ATTRIBUTE_WIDTH); this.mTileRows = SAXUtils.getIntAttributeOrThrow(pAttributes, TAG_LAYER_ATTRIBUTE_HEIGHT); this.mTMXTiles = new TMXTile[this.mTileRows][this.mTileColumns]; super.mWidth = pTMXTiledMap.getTileWidth() * this.mTileColumns; final float width = super.mWidth; super.mBaseWidth = width; super.mHeight = pTMXTiledMap.getTileHeight() * this.mTileRows; final float height = super.mHeight; super.mBaseHeight = height; this.mRotationCenterX = width * 0.5f; this.mRotationCenterY = height * 0.5f; this.mScaleCenterX = this.mRotationCenterX; this.mScaleCenterY = this.mRotationCenterY; this.mGlobalTileIDsExpected = this.mTileColumns * this.mTileRows; this.setVisible(SAXUtils.getIntAttribute(pAttributes, TAG_LAYER_ATTRIBUTE_VISIBLE, TAG_LAYER_ATTRIBUTE_VISIBLE_VALUE_DEFAULT) == 1); this.setAlpha(SAXUtils.getFloatAttribute(pAttributes, TAG_LAYER_ATTRIBUTE_OPACITY, TAG_LAYER_ATTRIBUTE_OPACITY_VALUE_DEFAULT)); } // =========================================================== // Getter & Setter // =========================================================== public String getName() { return this.mName; } 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[VERTEX_INDEX_X] / tmxTiledMap.getTileWidth()); if(tileColumn < 0 || tileColumn > this.mTileColumns - 1) { return null; } final int tileRow = (int)(localCoords[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 @Deprecated public void setRotation(final float pRotation) { } @Override protected void onUpdateVertexBuffer() { /* Nothing. */ } @Override protected void onInitDraw(final GL10 pGL) { super.onInitDraw(pGL); GLHelper.enableTextures(pGL); GLHelper.enableTexCoordArray(pGL); } @Override protected void onApplyVertices(final GL10 pGL) { if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) { final GL11 gl11 = (GL11)pGL; this.mTMXTiledMap.getSharedVertexBuffer().selectOnHardware(gl11); GLHelper.vertexZeroPointer(gl11); } else { GLHelper.vertexPointer(pGL, this.mTMXTiledMap.getSharedVertexBuffer().getFloatBuffer()); } } @Override protected void drawVertices(final GL10 pGL, final Camera pCamera) { final TMXTile[][] tmxTiles = this.mTMXTiles; 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(this, cullingVertices); final float layerMinX = cullingVertices[VERTEX_INDEX_X]; final float layerMinY = cullingVertices[VERTEX_INDEX_Y]; final float cameraMinX = pCamera.getMinX(); final float cameraMinY = pCamera.getMinY(); 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)); final int visibleTilesTotalWidth = (lastColumn - firstColumn + 1) * tileWidth; pGL.glTranslatef(firstColumn * tileWidth, firstRow * tileHeight, 0); for(int row = firstRow; row <= lastRow; row++) { final TMXTile[] tmxTileRow = tmxTiles[row]; for(int column = firstColumn; column <= lastColumn; column++) { final TextureRegion textureRegion = tmxTileRow[column].mTextureRegion; if(textureRegion != null) { textureRegion.onApply(pGL); pGL.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); } pGL.glTranslatef(tileWidth, 0, 0); } /* Translate one row downwards and the back left to the first column. * Just like the 'Carriage Return' + 'New Line' (\r\n) on a typewriter. */ pGL.glTranslatef(-visibleTilesTotalWidth, tileHeight, 0); } pGL.glLoadIdentity(); } @Override protected void onManagedUpdate(final float pSecondsElapsed) { /* Nothing. */ } // =========================================================== // Methods // =========================================================== void initializeTMXTileFromXML(final Attributes pAttributes, final ITMXTilePropertiesListener pTMXTilePropertyListener) { this.addTileByGlobalTileID(SAXUtils.getIntAttributeOrThrow(pAttributes, 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 && pDataEncoding.equals(TAG_DATA_ATTRIBUTE_ENCODING_VALUE_BASE64)) { in = new Base64InputStream(in, Base64.DEFAULT); } if(pDataCompression != null){ if(pDataCompression.equals(TAG_DATA_ATTRIBUTE_COMPRESSION_VALUE_GZIP)) { in = new GZIPInputStream(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 TextureRegion tmxTileTextureRegion; if(pGlobalTileID == 0) { tmxTileTextureRegion = null; } else { tmxTileTextureRegion = tmxTiledMap.getTextureRegionFromGlobalTileID(pGlobalTileID); } final TMXTile tmxTile = new TMXTile(pGlobalTileID, column, row, this.mTMXTiledMap.getTileWidth(), this.mTMXTiledMap.getTileHeight(), tmxTileTextureRegion); tmxTiles[row][column] = tmxTile; if(pGlobalTileID != 0) { /* 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 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 // =========================================================== }