/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package javax.microedition.lcdui.game;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
/**
* A TiledLayer is a visual element composed of a grid of cells that
* can be filled with a set of
* tile images. This class allows large virtual layers to be created
* without the need for an
* extremely large Image. This technique is commonly used in 2D
* gaming platforms to create
* very large scrolling backgrounds,
* <P>
* <h3>Tiles</h3>
* The tiles used to fill the TiledLayer's cells are provided in a
* single Image object which
* may be mutable or immutable. The Image is broken up into a series
* of equally-sized tiles;
* the tile size is specified along with the Image. As shown in the
* figure below, the same
* tile set can be stored in several different arrangements depending
* on what is the most
* convenient for the game developer.
* <br>
* <center><img src="doc-files/tiles.gif" width=588 height=412
* ALT="Tiles"></center>
* <br>
* Each tile is assigned a unique index number. The tile located in
* the upper-left corner
* of the Image is assigned an index of 1. The remaining tiles are
* then numbered consecutively
* in row-major order (indices are assigned across the first row, then
* the second row, and so on).
* These tiles are regarded as <em>static tiles</em> because there is
* a fixed link between
* the tile and the image data associated with it.
* <P>
* A static tile set is created when the TiledLayer is instantiated;
* it can also be updated
* at any time using the {@link #setStaticTileSet} method.
* <P>
* In addition to the static tile set, the developer can also define
* several <em>animated tiles</em>.
* An animated tile is a virtual tile that is dynamically associated
* with a static tile; the appearance
* of an animated tile will be that of the static tile that it is
* currently associated with.
* <P>
* Animated tiles allow the developer to change the appearance of a
* group of cells
* very easily. With the group of cells all filled with the animated
* tile, the appearance
* of the entire group can be changed by simply changing the static
* tile associated with the
* animated tile. This technique is very useful for animating large
* repeating areas without
* having to explicitly change the contents of numerous cells.
* <P>
* Animated tiles are created using the {@link #createAnimatedTile}
* method, which returns the
* index to be used for the new animated tile. The animated tile
* indices are always negative
* and consecutive, beginning with -1. Once created, the static tile
* associated with an
* animated tile can be changed using the {@link #setAnimatedTile}
* method.
* <P>
* <h3>Cells</h3>
* The TiledLayer's grid is made up of equally sized cells; the number
* of rows and
* columns in the grid are specified in the constructor, and the
* physical size of the cells
* is defined by the size of the tiles.
* <P>
* The contents of each cell is specified by means of a tile index; a
* positive tile index refers
* to a static tile, and a negative tile index refers to an animated
* tile. A tile index of 0
* indicates that the cell is empty; an empty cell is fully
* transparent and nothing is drawn
* in that area by the TiledLayer. By default, all cells contain tile
* index 0.
* <P>
* The contents of cells may be changed using {@link #setCell} and
* {@link #fillCells}. Several
* cells may contain the same tile; however, a single cell cannot
* contain more than one tile.
* The following example illustrates how a simple background can be
* created using a TiledLayer.
* <br>
* <center><img src="doc-files/grid.gif" width=735 height=193
* ALT="TiledLayer Grid"></center>
* <br>
* In this example, the area of water is filled with an animated tile
* having an index of -1, which
* is initially associated with static tile 5. The entire area of
* water may be animated by simply
* changing the associated static tile using <code>setAnimatedTile(-1,
* 7)</code>.
* <br>
* <center><img src="doc-files/grid2.gif" width=735 height=193
* ALT="TiledLayer Grid 2"></center>
* <br>
* <P>
* <h3>Rendering a TiledLayer</h3>
* A TiledLayer can be rendered by manually calling its paint method;
* it can also be rendered
* automatically using a LayerManager object.
* <P>
* The paint method will attempt to render the entire TiledLayer
* subject to the
* clip region of the Graphics object; the upper left corner of the
* TiledLayer is rendered at
* its current (x,y) position relative to the Graphics object's
* origin. The rendered region
* may be controlled by setting the clip region of the Graphics object
* accordingly.
* <P>
*/
public class TiledLayer extends Layer {
/**
* Creates a new TiledLayer. <p>
*
* The TiledLayer's grid will be <code>rows</code> cells high and
* <code>columns</code> cells wide. All cells in the grid are initially
* empty (i.e. they contain tile index 0). The contents of the grid may
* be modified through the use of {@link #setCell} and {@link #fillCells}.
* <P>
* The static tile set for the TiledLayer is created from the specified
* Image with each tile having the dimensions of tileWidth x tileHeight.
* The width of the source image must be an integer multiple of
* the tile width, and the height of the source image must be an integer
* multiple of the tile height; otherwise, an IllegalArgumentException
* is thrown;<p>
*
* The entire static tile set can be changed using
* {@link #setStaticTileSet(Image, int, int)}.
* These methods should be used sparingly since they are both
* memory and time consuming.
* Where possible, animated tiles should be used instead to
* animate tile appearance.<p>
*
* @param columns the width of the <code>TiledLayer</code>,
* expressed as a number of cells
* @param rows the height of the <code>TiledLayer</code>,
* expressed as a number of cells
* @param image the <code>Image</code> to use for creating
* the static tile set
* @param tileWidth the width in pixels of a single tile
* @param tileHeight the height in pixels of a single tile
* @throws NullPointerException if <code>image</code> is <code>null</code>
* @throws IllegalArgumentException if the number of <code>rows</code>
* or <code>columns</code> is less than <code>1</code>
* @throws IllegalArgumentException if <code>tileHeight</code>
* or <code>tileWidth</code> is less than <code>1</code>
* @throws IllegalArgumentException if the <code>image</code>
* width is not an integer multiple of the <code>tileWidth</code>
* @throws IllegalArgumentException if the <code>image</code>
* height is not an integer multiple of the <code>tileHeight</code>
*/
public TiledLayer(int columns, int rows, Image image, int tileWidth,
int tileHeight) {
// IllegalArgumentException will be thrown
// in the Layer super-class constructor
super(columns < 1 || tileWidth < 1 ? -1 : columns * tileWidth,
rows < 1 || tileHeight < 1 ? -1 : rows * tileHeight);
// if img is null img.getWidth() will throw NullPointerException
if (((image.getWidth() % tileWidth) != 0) ||
((image.getHeight() % tileHeight) != 0)) {
throw new IllegalArgumentException();
}
this.columns = columns;
this.rows = rows;
cellMatrix = new int[rows][columns];
int noOfFrames =
(image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);
// the zero th index is left empty for transparent tile
// so it is passed in createStaticSet as noOfFrames + 1
// Also maintain static indices is true
// all elements of cellMatrix[][]
// are set to zero by new, so maintainIndices = true
createStaticSet(image, noOfFrames + 1, tileWidth, tileHeight, true);
}
/**
* Creates a new animated tile and returns the index that refers
* to the new animated tile. It is initially associated with
* the specified tile index (either a static tile or 0).
* <P>
* The indices for animated tiles are always negative. The first
* animated tile shall have the index -1, the second, -2, etc.
*
* @param staticTileIndex the index of the associated tile
* (must be <code>0</code> or a valid static tile index)
* @return the index of newly created animated tile
* @throws IndexOutOfBoundsException if the
* <code>staticTileIndex</code> is invalid
*/
public int createAnimatedTile(int staticTileIndex) {
// checks static tile
if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) {
throw new IndexOutOfBoundsException();
}
if (anim_to_static == null) {
anim_to_static = new int[4];
numOfAnimTiles = 1;
} else if (numOfAnimTiles == anim_to_static.length) {
// grow anim_to_static table if needed
int new_anim_tbl[] = new int[anim_to_static.length * 2];
System.arraycopy(anim_to_static, 0,
new_anim_tbl, 0, anim_to_static.length);
anim_to_static = new_anim_tbl;
}
anim_to_static[numOfAnimTiles] = staticTileIndex;
numOfAnimTiles++;
return (-(numOfAnimTiles - 1));
}
/**
* Associates an animated tile with the specified static tile. <p>
*
* @param animatedTileIndex the index of the animated tile
* @param staticTileIndex the index of the associated tile
* (must be <code>0</code> or a valid static tile index)
* @throws IndexOutOfBoundsException if the
* <code>staticTileIndex</code> is invalid
* @throws IndexOutOfBoundsException if the animated tile index
* is invalid
* @see #getAnimatedTile
*
*/
public void setAnimatedTile(int animatedTileIndex, int staticTileIndex) {
// checks static tile
if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) {
throw new IndexOutOfBoundsException();
}
// do animated tile index check
animatedTileIndex = - animatedTileIndex;
if (anim_to_static == null || animatedTileIndex <= 0
|| animatedTileIndex >= numOfAnimTiles) {
throw new IndexOutOfBoundsException();
}
anim_to_static[animatedTileIndex] = staticTileIndex;
}
/**
* Gets the tile referenced by an animated tile. <p>
*
* Returns the tile index currently associated with the
* animated tile.
*
* @param animatedTileIndex the index of the animated tile
* @return the index of the tile reference by the animated tile
* @throws IndexOutOfBoundsException if the animated tile index
* is invalid
* @see #setAnimatedTile
*/
public int getAnimatedTile(int animatedTileIndex) {
animatedTileIndex = - animatedTileIndex;
if (anim_to_static == null || animatedTileIndex <= 0
|| animatedTileIndex >= numOfAnimTiles) {
throw new IndexOutOfBoundsException();
}
return anim_to_static[animatedTileIndex];
}
/**
* Sets the contents of a cell. <P>
*
* The contents may be set to a static tile index, an animated
* tile index, or it may be left empty (index 0)
* @param col the column of cell to set
* @param row the row of cell to set
* @param tileIndex the index of tile to place in cell
* @throws IndexOutOfBoundsException if there is no tile with index
* <code>tileIndex</code>
* @throws IndexOutOfBoundsException if <code>row</code> or
* <code>col</code> is outside the bounds of the
* <code>TiledLayer</code> grid
* @see #getCell
* @see #fillCells
*/
public void setCell(int col, int row, int tileIndex) {
if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
throw new IndexOutOfBoundsException();
}
if (tileIndex > 0) {
// do checks for static tile
if (tileIndex >= numberOfTiles) {
throw new IndexOutOfBoundsException();
}
} else if (tileIndex < 0) {
// do animated tile index check
if (anim_to_static == null ||
(-tileIndex) >= numOfAnimTiles) {
throw new IndexOutOfBoundsException();
}
}
cellMatrix[row][col] = tileIndex;
}
/**
* Gets the contents of a cell. <p>
*
* Gets the index of the static or animated tile currently displayed in
* a cell. The returned index will be 0 if the cell is empty.
*
* @param col the column of cell to check
* @param row the row of cell to check
* @return the index of tile in cell
* @throws IndexOutOfBoundsException if <code>row</code> or
* <code>col</code> is outside the bounds of the
* <code>TiledLayer</code> grid
* @see #setCell
* @see #fillCells
*/
public int getCell(int col, int row) {
if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
throw new IndexOutOfBoundsException();
}
return cellMatrix[row][col];
}
/**
* Fills a region cells with the specific tile. The cells may be filled
* with a static tile index, an animated tile index, or they may be left
* empty (index <code>0</code>).
*
* @param col the column of top-left cell in the region
* @param row the row of top-left cell in the region
* @param numCols the number of columns in the region
* @param numRows the number of rows in the region
* @param tileIndex the Index of the tile to place in all cells in the
* specified region
* @throws IndexOutOfBoundsException if the rectangular region
* defined by the parameters extends beyond the bounds of the
* <code>TiledLayer</code> grid
* @throws IllegalArgumentException if <code>numCols</code> is less
* than zero
* @throws IllegalArgumentException if <code>numRows</code> is less
* than zero
* @throws IndexOutOfBoundsException if there is no tile with
* index <code>tileIndex</code>
* @see #setCell
* @see #getCell
*/
public void fillCells(int col, int row, int numCols, int numRows,
int tileIndex) {
if (numCols < 0 || numRows < 0) {
throw new IllegalArgumentException();
}
if (col < 0 || col >= this.columns || row < 0 || row >= this.rows ||
col + numCols > this.columns || row + numRows > this.rows) {
throw new IndexOutOfBoundsException();
}
if (tileIndex > 0) {
// do checks for static tile
if (tileIndex >= numberOfTiles) {
throw new IndexOutOfBoundsException();
}
} else if (tileIndex < 0) {
// do animated tile index check
if (anim_to_static == null ||
(-tileIndex) >= numOfAnimTiles) {
throw new IndexOutOfBoundsException();
}
}
for (int rowCount = row; rowCount < row + numRows; rowCount++) {
for (int columnCount = col;
columnCount < col + numCols; columnCount++) {
cellMatrix[rowCount][columnCount] = tileIndex;
}
}
}
/**
* Gets the width of a single cell, in pixels.
* @return the width in pixels of a single cell in the
* <code>TiledLayer</code> grid
*/
public final int getCellWidth() {
return cellWidth;
}
/**
* Gets the height of a single cell, in pixels.
* @return the height in pixels of a single cell in the
* <code>TiledLayer</code> grid
*/
public final int getCellHeight() {
return cellHeight;
}
/**
* Gets the number of columns in the TiledLayer grid.
* The overall width of the TiledLayer, in pixels,
* may be obtained by calling {@link #getWidth}.
* @return the width in columns of the
* <code>TiledLayer</code> grid
*/
public final int getColumns() {
return columns;
}
/**
* Gets the number of rows in the TiledLayer grid. The overall
* height of the TiledLayer, in pixels, may be obtained by
* calling {@link #getHeight}.
* @return the height in rows of the
* <code>TiledLayer</code> grid
*/
public final int getRows() {
return rows;
}
/**
* Change the static tile set. <p>
*
* Replaces the current static tile set with a new static tile set.
* See the constructor {@link #TiledLayer(int, int, Image, int, int)}
* for information on how the tiles are created from the
* image.<p>
*
* If the new static tile set has as many or more tiles than the
* previous static tile set,
* the the animated tiles and cell contents will be preserve. If
* not, the contents of
* the grid will be cleared (all cells will contain index 0) and
* all animated tiles
* will be deleted.
* <P>
* @param image the <code>Image</code> to use for creating the
* static tile set
* @param tileWidth the width in pixels of a single tile
* @param tileHeight the height in pixels of a single tile
* @throws NullPointerException if <code>image</code> is <code>null</code>
* @throws IllegalArgumentException if <code>tileHeight</code>
* or <code>tileWidth</code> is less than <code>1</code>
* @throws IllegalArgumentException if the <code>image</code>
* width is not an integer multiple of the <code>tileWidth</code>
* @throws IllegalArgumentException if the <code>image</code>
* height is not an integer multiple of the <code>tileHeight</code>
*/
public void setStaticTileSet(Image image, int tileWidth, int tileHeight) {
// if img is null img.getWidth() will throw NullPointerException
if (tileWidth < 1 || tileHeight < 1 ||
((image.getWidth() % tileWidth) != 0) ||
((image.getHeight() % tileHeight) != 0)) {
throw new IllegalArgumentException();
}
setWidthImpl(columns * tileWidth);
setHeightImpl(rows * tileHeight);
int noOfFrames =
(image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);
// the zero th index is left empty for transparent tile
// so it is passed in createStaticSet as noOfFrames + 1
if (noOfFrames >= (numberOfTiles - 1)) {
// maintain static indices
createStaticSet(image, noOfFrames + 1, tileWidth, tileHeight, true);
} else {
createStaticSet(image, noOfFrames + 1, tileWidth,
tileHeight, false);
}
}
/**
* Draws the TiledLayer.
*
* The entire TiledLayer is rendered subject to the clip region of
* the Graphics object.
* The TiledLayer's upper left corner is rendered at the
* TiledLayer's current
* position relative to the origin of the Graphics object. The current
* position of the TiledLayer's upper-left corner can be retrieved by
* calling {@link #getX()} and {@link #getY()}.
* The appropriate use of a clip region and/or translation allows
* an arbitrary region
* of the TiledLayer to be rendered.
* <p>
* If the TiledLayer's Image is mutable, the TiledLayer is rendered
* using the current contents of the Image.
* @param g the graphics object to draw the <code>TiledLayer</code>
* @throws NullPointerException if <code>g</code> is <code>null</code>
*/
public final void paint(Graphics g) {
if (g == null) {
throw new NullPointerException();
}
if (visible) {
int startColumn = 0;
int endColumn = this.columns;
int startRow = 0;
int endRow = this.rows;
// calculate the number of columns left of the clip
int number = (g.getClipX() - this.x) / cellWidth;
if (number > 0) {
startColumn = number;
}
// calculate the number of columns right of the clip
int endX = this.x + (this.columns * cellWidth);
int endClipX = g.getClipX() + g.getClipWidth();
number = (endX - endClipX) / cellWidth;
if (number > 0) {
endColumn -= number;
}
// calculate the number of rows above the clip
number = (g.getClipY() - this.y) / cellHeight;
if (number > 0) {
startRow = number;
}
// calculate the number of rows below the clip
int endY = this.y + (this.rows * cellHeight);
int endClipY = g.getClipY() + g.getClipHeight();
number = (endY - endClipY) / cellHeight;
if (number > 0) {
endRow -= number;
}
// paint all visible cells
int tileIndex = 0;
// y-coordinate
int ty = this.y + (startRow * cellHeight);
for (int row = startRow;
row < endRow; row++, ty += cellHeight) {
// reset the x-coordinate at the beginning of every row
// x-coordinate to draw tile into
int tx = this.x + (startColumn * cellWidth);
for (int column = startColumn; column < endColumn;
column++, tx += cellWidth) {
tileIndex = cellMatrix[row][column];
// check the indices
// if animated get the corresponding
// static index from anim_to_static table
if (tileIndex == 0) { // transparent tile
continue;
} else if (tileIndex < 0) {
tileIndex = getAnimatedTile(tileIndex);
}
g.drawRegion(sourceImage,
tileSetX[tileIndex],
tileSetY[tileIndex],
cellWidth, cellHeight,
Sprite.TRANS_NONE,
tx, ty,
Graphics.TOP | Graphics.LEFT);
}
}
}
}
// private implementation
/**
* create the Image Array.
*
* @param image Image to use for creating the static tile set
* @param noOfFrames total number of frames
* @param tileWidth The width, in pixels, of a single tile
* @param tileHeight The height, in pixels, of a single tile
* @param maintainIndices
*/
private void createStaticSet(Image image, int noOfFrames, int tileWidth,
int tileHeight, boolean maintainIndices) {
cellWidth = tileWidth;
cellHeight = tileHeight;
int imageW = image.getWidth();
int imageH = image.getHeight();
sourceImage = image;
numberOfTiles = noOfFrames;
tileSetX = new int[numberOfTiles];
tileSetY = new int[numberOfTiles];
if (!maintainIndices) {
// populate cell matrix, all the indices are 0 to begin with
for (rows = 0; rows < cellMatrix.length; rows++) {
int totalCols = cellMatrix[rows].length;
for (columns = 0; columns < totalCols; columns++) {
cellMatrix[rows][columns] = 0;
}
}
// delete animated tiles
anim_to_static = null;
}
int currentTile = 1;
for (int locY = 0; locY < imageH; locY += tileHeight) {
for (int locX = 0; locX < imageW; locX += tileWidth) {
tileSetX[currentTile] = locX;
tileSetY[currentTile] = locY;
currentTile++;
}
}
}
/**
* the overall height of the TiledLayer grid
*/
private int cellHeight; // = 0;
/**
* the overall cell width of the TiledLayer grid
*/
private int cellWidth; // = 0;
/**
* The num of rows of the TiledLayer grid.
*/
private int rows; // = 0;
/**
* the num of columns in the TiledLayer grid
*/
private int columns; // = 0;
/**
* int array for storing row and column of cell
*
* it contains the tile Index for both static and animated tiles
*/
private int[][] cellMatrix; // = null;
/**
* Source image for tiles
*/
// package access as it is used by Pixel level Collision
// detection with a Sprite
Image sourceImage; // = null;
/**
* no. of tiles
*/
private int numberOfTiles; // = 0;
/**
* X co-ordinate definitions for individual frames into the source image
*/
// package access as it is used by Pixel level Collision
// detection with a Sprite
int[] tileSetX;
/**
* Y co-ordinate definitions for individual frames into the source image
*/
// package access as it is used by Pixel level Collision
// detection with a Sprite
int[] tileSetY;
/**
* Table to map from animated Index to static Index
* 0th location is unused.
* anim --> static Index
* -1 --> 21
* -2 --> 34
* -3 --> 45
* for now keep 0 the location of the table empty instead of computing
* -index make index +ve and access this Table.
*
*/
private int[] anim_to_static; // = null;
/**
* total number of animated tiles. This variable is also used as
* index in the above table to add new entries to the anim_to_static table.
* initialized to 1 when table is created.
*/
private int numOfAnimTiles; // = 0
}