Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
@@ -0,0 +1,73 @@
//
// $Id: IMImageProvider.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
/**
* Provides images to a tileset given a reference to the image manager and
* an image data provider.
*/
public class IMImageProvider implements ImageProvider
{
public IMImageProvider (ImageManager imgr, ImageDataProvider dprov)
{
_imgr = imgr;
_dprov = dprov;
}
public IMImageProvider (ImageManager imgr, String rset)
{
_imgr = imgr;
_rset = rset;
}
// documentation inherited from interface
public BufferedImage getTileSetImage (String path, Colorization[] zations)
{
return _imgr.getImage(getImageKey(path), zations);
}
// documentation inherited from interface
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations)
{
return _imgr.getMirage(getImageKey(path), bounds, zations);
}
protected final ImageManager.ImageKey getImageKey (String path)
{
return (_dprov == null) ?
_imgr.getImageKey(_rset, path) :
_imgr.getImageKey(_dprov, path);
}
protected ImageManager _imgr;
protected ImageDataProvider _dprov;
protected String _rset;
}
@@ -0,0 +1,64 @@
//
// $Id: ImageProvider.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
/**
* Provides a generic interface via which tileset images may be loaded. In
* most cases, a running application will want to obtain images via the
* {@link ImageManager}, but in some circumstances a simpler image
* provider may be desirable to avoid the overhead of the image manager
* infrastructure when simple image loading is all that is desired.
*/
public interface ImageProvider
{
/**
* Returns the raw tileset image with the specified path.
*
* @param path the path that identifies the desired image (corresponds
* to the image path from the tileset).
* @param zations if non-null, colorizations to apply to the source
* image before returning it.
*/
public BufferedImage getTileSetImage (String path, Colorization[] zations);
/**
* Obtains the tile image with the specified path in the form of a
* {@link Mirage}. It should be cropped from the tileset image
* identified by the supplied path.
*
* @param path the path that identifies the desired image (corresponds
* to the image path from the tileset).
* @param bounds if non-null, the region of the image to be returned
* as a mirage. If null, the entire image should be returned.
* @param zations if non-null, colorizations to apply to the image
* before converting it into a mirage.
*/
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations);
}
@@ -0,0 +1,39 @@
//
// $Id: NoSuchTileSetException.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
/**
* Thrown when an attempt is made to retrieve a non-existent tile set from
* the tile set manager.
*/
public class NoSuchTileSetException extends Exception
{
public NoSuchTileSetException (String tileSetName)
{
super("No tile set named '" + tileSetName + "'");
}
public NoSuchTileSetException (int tileSetId)
{
super("No tile set with id '" + tileSetId + "'");
}
}
@@ -0,0 +1,232 @@
//
// $Id: ObjectTile.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Dimension;
import java.awt.Point;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.util.DirectionUtil;
/**
* An object tile extends the base tile to provide support for objects
* whose image spans more than one unit tile.
*
* <p> An object tile is generally positioned based on its origin rather
* than the upper left of its image. Generally this origin is in the
* bottom center of the object image, but can be configured to be anywhere
* that the natural center point of "contact" is for the object. Note that
* this does not automatically adjust the semantics of {@link #paint}, it
* is just expected that the caller will account for the object tile's
* origin when painting, if appropriate.
*
* <p> An object tile has dimensions (in tile units) that represent its
* footprint or "shadow".
*/
public class ObjectTile extends Tile
{
/**
* Returns the object footprint width in tile units.
*/
public int getBaseWidth ()
{
return _base.width;
}
/**
* Returns the object footprint height in tile units.
*/
public int getBaseHeight ()
{
return _base.height;
}
/**
* Sets the object footprint in tile units.
*/
protected void setBase (int width, int height)
{
_base.width = width;
_base.height = height;
}
/**
* Returns the x offset into the tile image of the origin (which will
* be aligned with the bottom center of the origin tile) or
* <code>Integer.MIN_VALUE</code> if the origin is not explicitly
* specified and should be computed from the image size and tile
* footprint.
*/
public int getOriginX ()
{
return _origin.x;
}
/**
* Returns the y offset into the tile image of the origin (which will
* be aligned with the bottom center of the origin tile) or
* <code>Integer.MIN_VALUE</code> if the origin is not explicitly
* specified and should be computed from the image size and tile
* footprint.
*/
public int getOriginY ()
{
return _origin.y;
}
/**
* Sets the offset in pixels from the origin of the tile image to the
* origin of the object. The object will be rendered such that its
* origin is at the bottom center of its origin tile. If no origin is
* specified, the bottom of the image is aligned with the bottom of
* the origin tile and the left side of the image is aligned with the
* left edge of the left-most base tile.
*/
protected void setOrigin (int x, int y)
{
_origin.x = x;
_origin.y = y;
}
/**
* Returns this object tile's default render priority.
*/
public int getPriority ()
{
return _priority;
}
/**
* Sets this object tile's default render priority.
*/
protected void setPriority (int priority)
{
_priority = priority;
}
/**
* Configures the "spot" associated with this object.
*/
public void setSpot (int x, int y, byte orient)
{
_spot = new Point(x, y);
_sorient = orient;
}
/**
* Returns true if this object has a spot.
*/
public boolean hasSpot ()
{
return (_spot != null);
}
/**
* Returns the x-coordinate of the "spot" associated with this object.
*/
public int getSpotX ()
{
return (_spot == null) ? 0 : _spot.x;
}
/**
* Returns the x-coordinate of the "spot" associated with this object.
*/
public int getSpotY ()
{
return (_spot == null) ? 0 : _spot.y;
}
/**
* Returns the orientation of the "spot" associated with this object.
*/
public int getSpotOrient ()
{
return _sorient;
}
/**
* Returns the list of constraints associated with this object, or
* <code>null</code> if the object has no constraints.
*/
public String[] getConstraints ()
{
return _constraints;
}
/**
* Checks whether this object has the given constraint.
*/
public boolean hasConstraint (String constraint)
{
return (_constraints == null) ? false :
ListUtil.contains(_constraints, constraint);
}
/**
* Configures this object's constraints.
*/
public void setConstraints (String[] constraints)
{
_constraints = constraints;
}
// documentation inherited
public void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", base=").append(StringUtil.toString(_base));
buf.append(", origin=").append(StringUtil.toString(_origin));
buf.append(", priority=").append(_priority);
if (_spot != null) {
buf.append(", spot=").append(StringUtil.toString(_spot));
buf.append(", sorient=");
buf.append(DirectionUtil.toShortString(_sorient));
}
if (_constraints != null) {
buf.append(", constraints=").append(StringUtil.toString(
_constraints));
}
}
/** The object footprint width in unit tile units. */
protected Dimension _base = new Dimension(1, 1);
/** The offset from the origin of the tile image to the object's
* origin or MIN_VALUE if the origin should be calculated based on the
* footprint. */
protected Point _origin = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
/** This object tile's default render priority. */
protected int _priority;
/** The coordinates of the "spot" associated with this object. */
protected Point _spot;
/** The orientation of the "spot" associated with this object. */
protected byte _sorient;
/** The list of constraints associated with this object. */
protected String[] _constraints;
}
@@ -0,0 +1,295 @@
//
// $Id: ObjectTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
/**
* The object tileset supports the specification of object information for
* object tiles in addition to all of the features of the swiss army
* tileset.
*
* @see ObjectTile
*/
public class ObjectTileSet extends SwissArmyTileSet
implements RecolorableTileSet
{
/** A constraint prefix indicating that the object must have empty space in
* the suffixed direction (N, E, S, or W). */
public static final String SPACE = "SPACE_";
/** A constraint indicating that the object is a surface (e.g., table). */
public static final String SURFACE = "SURFACE";
/** A constraint indicating that the object must be placed on a surface. */
public static final String ON_SURFACE = "ON_SURFACE";
/** A constraint prefix indicating that the object is a wall facing the
* suffixed direction (N, E, S, or W). */
public static final String WALL = "WALL_";
/** A constraint prefix indicating that the object must be placed on a
* wall facing the suffixed direction (N, E, S, or W). */
public static final String ON_WALL = "ON_WALL_";
/** A constraint prefix indicating that the object must be attached to a
* wall facing the suffixed direction (N, E, S, or W). */
public static final String ATTACH = "ATTACH_";
/** The low suffix for walls and attachments. Low attachments can be placed
* on low or normal walls; normal attachments can only be placed on normal
* walls. */
public static final String LOW = "_LOW";
/**
* Sets the widths (in unit tile count) of the objects in this
* tileset. This must be accompanied by a call to {@link
* #setObjectHeights}.
*/
public void setObjectWidths (int[] objectWidths)
{
_owidths = objectWidths;
}
/**
* Sets the heights (in unit tile count) of the objects in this
* tileset. This must be accompanied by a call to {@link
* #setObjectWidths}.
*/
public void setObjectHeights (int[] objectHeights)
{
_oheights = objectHeights;
}
/**
* Sets the x offset in pixels to the image origin.
*/
public void setXOrigins (int[] xorigins)
{
_xorigins = xorigins;
}
/**
* Sets the y offset in pixels to the image origin.
*/
public void setYOrigins (int[] yorigins)
{
_yorigins = yorigins;
}
/**
* Sets the default render priorities for our object tiles.
*/
public void setPriorities (byte[] priorities)
{
_priorities = priorities;
}
/**
* Provides a set of colorization classes that apply to objects in
* this tileset.
*/
public void setColorizations (String[] zations)
{
_zations = zations;
}
/**
* Sets the x offset to the "spots" associated with our object tiles.
*/
public void setXSpots (short[] xspots)
{
_xspots = xspots;
}
/**
* Sets the y offset to the "spots" associated with our object tiles.
*/
public void setYSpots (short[] yspots)
{
_yspots = yspots;
}
/**
* Sets the orientation of the "spots" associated with our object
* tiles.
*/
public void setSpotOrients (byte[] sorients)
{
_sorients = sorients;
}
/**
* Sets the lists of constraints associated with our object tiles.
*/
public void setConstraints (String[][] constraints)
{
_constraints = constraints;
}
/**
* Returns the x coordinate of the spot associated with the specified
* tile index.
*/
public int getXSpot (int tileIdx)
{
return (_xspots == null) ? 0 : _xspots[tileIdx];
}
/**
* Returns the y coordinate of the spot associated with the specified
* tile index.
*/
public int getYSpot (int tileIdx)
{
return (_yspots == null) ? 0 : _yspots[tileIdx];
}
/**
* Returns the orientation of the spot associated with the specified
* tile index.
*/
public int getSpotOrient (int tileIdx)
{
return (_sorients == null) ? 0 : _sorients[tileIdx];
}
/**
* Returns the list of constraints associated with the specified tile
* index, or <code>null</code> if the index has no constraints.
*/
public String[] getConstraints (int tileIdx)
{
return (_constraints == null) ? null : _constraints[tileIdx];
}
/**
* Checks whether the tile at the specified index has the given constraint.
*/
public boolean hasConstraint (int tileIdx, String constraint)
{
return (_constraints == null) ? false :
ListUtil.contains(_constraints[tileIdx], constraint);
}
// documentation inherited from interface RecolorableTileSet
public String[] getColorizations ()
{
return _zations;
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", owidths=").append(StringUtil.toString(_owidths));
buf.append(", oheights=").append(StringUtil.toString(_oheights));
buf.append(", xorigins=").append(StringUtil.toString(_xorigins));
buf.append(", yorigins=").append(StringUtil.toString(_yorigins));
buf.append(", prios=").append(StringUtil.toString(_priorities));
buf.append(", zations=").append(StringUtil.toString(_zations));
buf.append(", xspots=").append(StringUtil.toString(_xspots));
buf.append(", yspots=").append(StringUtil.toString(_yspots));
buf.append(", sorients=").append(StringUtil.toString(_sorients));
buf.append(", constraints=").append(StringUtil.toString(_constraints));
}
// documentation inherited
protected Colorization[] getColorizations (int tileIndex, Colorizer rizer)
{
Colorization[] zations = null;
if (rizer != null && _zations != null) {
zations = new Colorization[_zations.length];
for (int ii = 0; ii < _zations.length; ii++) {
zations[ii] = rizer.getColorization(ii, _zations[ii]);
}
}
return zations;
}
// documentation inherited
protected Tile createTile ()
{
return new ObjectTile();
}
// documentation inherited
protected void initTile (Tile tile, int tileIndex, Colorization[] zations)
{
super.initTile(tile, tileIndex, zations);
ObjectTile otile = (ObjectTile)tile;
if (_owidths != null) {
otile.setBase(_owidths[tileIndex], _oheights[tileIndex]);
}
if (_xorigins != null) {
otile.setOrigin(_xorigins[tileIndex], _yorigins[tileIndex]);
}
if (_priorities != null) {
otile.setPriority(_priorities[tileIndex]);
}
if (_xspots != null) {
otile.setSpot(_xspots[tileIndex], _yspots[tileIndex],
_sorients[tileIndex]);
}
if (_constraints != null) {
otile.setConstraints(_constraints[tileIndex]);
}
}
/** The width (in tile units) of our object tiles. */
protected int[] _owidths;
/** The height (in tile units) of our object tiles. */
protected int[] _oheights;
/** The x offset in pixels to the origin of the tile images. */
protected int[] _xorigins;
/** The y offset in pixels to the origin of the tile images. */
protected int[] _yorigins;
/** The default render priorities of our objects. */
protected byte[] _priorities;
/** Colorization classes that apply to our objects. */
protected String[] _zations;
/** The x offset to the "spots" associated with our tiles. */
protected short[] _xspots;
/** The y offset to the "spots" associated with our tiles. */
protected short[] _yspots;
/** The orientation of the "spots" associated with our tiles. */
protected byte[] _sorients;
/** Lists of constraints associated with our tiles. */
protected String[][] _constraints;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 2;
}
@@ -0,0 +1,16 @@
//
// $Id: RecolorableTileSet.java 4191 2006-06-13 22:42:20Z ray $
package com.threerings.media.tile;
/**
* Indicates that a tileset has recolorization classes defined.
*/
public interface RecolorableTileSet
{
/**
* Returns the colorization classes that should be used to recolor
* objects in this tileset.
*/
public String[] getColorizations ();
}
@@ -0,0 +1,77 @@
//
// $Id: SimpleCachingImageProvider.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.samskivert.util.LRUHashMap;
import com.threerings.media.Log;
import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
/**
* An image provider that can be used by command line tools to load images
* and provide them to tilesets when doing things like preprocessing
* tileset images.
*/
public abstract class SimpleCachingImageProvider implements ImageProvider
{
// documentation inherited from interface
public BufferedImage getTileSetImage (String path, Colorization[] zations)
{
BufferedImage image = (BufferedImage)_cache.get(path);
if (image == null) {
try {
image = loadImage(path);
_cache.put(path, image);
} catch (IOException ioe) {
Log.warning("Failed to load image [path=" + path +
", ioe=" + ioe + "].");
}
}
return image;
}
// documentation inherited from interface
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations)
{
// mostly fake it
BufferedImage tsimg = getTileSetImage(path, zations);
tsimg = tsimg.getSubimage(bounds.x, bounds.y,
bounds.width, bounds.height);
return new BufferedMirage(tsimg);
}
/**
* Derived classes must implement this method to actually load the raw
* source images.
*/
protected abstract BufferedImage loadImage (String path)
throws IOException;
protected LRUHashMap _cache = new LRUHashMap(10);
}
@@ -0,0 +1,207 @@
//
// $Id: SwissArmyTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.ObjectInputStream;
import com.samskivert.util.StringUtil;
/**
* The swiss army tileset supports a diverse variety of tiles in the
* tileset image. Each row can contain varying numbers of tiles and each
* row can have its own width and height. Tiles can be separated from the
* edge of the tileset image by some border offset and can be separated
* from one another by a gap distance.
*/
public class SwissArmyTileSet extends TileSet
{
// documentation inherited
public int getTileCount ()
{
return _numTiles;
}
/**
* Sets the tile counts which are the number of tiles in each row of
* the tileset image. Each row can have an arbitrary number of tiles.
*/
public void setTileCounts (int[] tileCounts)
{
_tileCounts = tileCounts;
// compute our total tile count
computeTileCount();
}
/**
* Returns the tile count settings.
*/
public int[] getTileCounts ()
{
return _tileCounts;
}
/**
* Computes our total tile count from the individual counts for each
* row.
*/
protected void computeTileCount ()
{
// compute our number of tiles
_numTiles = 0;
for (int i = 0; i < _tileCounts.length; i++) {
_numTiles += _tileCounts[i];
}
}
/**
* Sets the tile widths for each row. Each row can have tiles of a
* different width.
*/
public void setWidths (int[] widths)
{
_widths = widths;
}
/**
* Returns the width settings.
*/
public int[] getWidths ()
{
return _widths;
}
/**
* Sets the tile heights for each row. Each row can have tiles of a
* different height.
*/
public void setHeights (int[] heights)
{
_heights = heights;
}
/**
* Returns the height settings.
*/
public int[] getHeights ()
{
return _heights;
}
/**
* Sets the offset in pixels of the upper left corner of the first
* tile in the first row. If the tileset image has a border, this can
* be set to account for it.
*/
public void setOffsetPos (Point offsetPos)
{
_offsetPos = offsetPos;
}
/**
* Sets the size of the gap between tiles (in pixels). If the tiles
* have space between them, this can be set to account for it.
*/
public void setGapSize (Dimension gapSize)
{
_gapSize = gapSize;
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", widths=").append(StringUtil.toString(_widths));
buf.append(", heights=").append(StringUtil.toString(_heights));
buf.append(", tileCounts=").append(StringUtil.toString(_tileCounts));
buf.append(", offsetPos=").append(StringUtil.toString(_offsetPos));
buf.append(", gapSize=").append(StringUtil.toString(_gapSize));
}
// documentation inherited
protected Rectangle computeTileBounds (int tileIndex)
{
// find the row number containing the sought-after tile
int ridx, tcount, ty, tx;
ridx = tcount = 0;
// start tile image position at image start offset
tx = _offsetPos.x;
ty = _offsetPos.y;
while ((tcount += _tileCounts[ridx]) < tileIndex + 1) {
// increment tile image position by row height and gap distance
ty += (_heights[ridx++] + _gapSize.height);
}
// determine the horizontal index of this tile in the row
int xidx = tileIndex - (tcount - _tileCounts[ridx]);
// final image x-position is based on tile width and gap distance
tx += (xidx * (_widths[ridx] + _gapSize.width));
// Log.info("Computed tile bounds [tileIndex=" + tileIndex +
// ", ridx=" + ridx + ", xidx=" + xidx +
// ", tx=" + tx + ", ty=" + ty + "].");
// crop the tile-sized image chunk from the full image
return new Rectangle(tx, ty, _widths[ridx], _heights[ridx]);
}
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
// compute our total tile count
computeTileCount();
}
/** The number of tiles in each row. */
protected int[] _tileCounts;
/** The number of tiles in the tileset. */
protected int _numTiles;
/** The width of the tiles in each row in pixels. */
protected int[] _widths;
/** The height of the tiles in each row in pixels. */
protected int[] _heights;
/** The offset distance (x, y) in pixels from the top-left of the
* image to the start of the first tile image. */
protected Point _offsetPos = new Point();
/** The distance (x, y) in pixels between each tile in each row
* horizontally, and between each row of tiles vertically. */
protected Dimension _gapSize = new Dimension();
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,178 @@
//
// $Id: Tile.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Graphics2D;
import java.util.Arrays;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
/**
* A tile represents a single square in a single layer in a scene.
*/
public class Tile // implements Cloneable
{
/** Used when caching tiles. */
public static class Key
{
public TileSet tileSet;
public int tileIndex;
public Colorization[] zations;
public Key (TileSet tileSet, int tileIndex, Colorization[] zations) {
this.tileSet = tileSet;
this.tileIndex = tileIndex;
this.zations = zations;
}
public boolean equals (Object other) {
if (other instanceof Key) {
Key okey = (Key)other;
return (tileSet == okey.tileSet &&
tileIndex == okey.tileIndex &&
Arrays.equals(zations, okey.zations));
} else {
return false;
}
}
public int hashCode () {
int code = (tileSet == null) ? tileIndex :
(tileSet.hashCode() ^ tileIndex);
int zcount = (zations == null) ? 0 : zations.length;
for (int ii = 0; ii < zcount; ii++) {
if (zations[ii] != null) {
code ^= zations[ii].hashCode();
}
}
return code;
}
}
/** The key associated with this tile. */
public Key key;
/**
* Configures this tile with its tile image.
*/
public void setImage (Mirage image)
{
if (_mirage != null) {
_totalTileMemory -= _mirage.getEstimatedMemoryUsage();
}
_mirage = image;
if (_mirage != null) {
_totalTileMemory += _mirage.getEstimatedMemoryUsage();
}
}
/**
* Returns the width of this tile.
*/
public int getWidth ()
{
return _mirage.getWidth();
}
/**
* Returns the height of this tile.
*/
public int getHeight ()
{
return _mirage.getHeight();
}
/**
* Returns the estimated memory usage of our underlying tile image.
*/
public long getEstimatedMemoryUsage ()
{
return _mirage.getEstimatedMemoryUsage();
}
/**
* Render the tile image at the specified position in the given
* graphics context.
*/
public void paint (Graphics2D gfx, int x, int y)
{
_mirage.paint(gfx, x, y);
}
/**
* Returns true if the specified coordinates within this tile contains
* a non-transparent pixel.
*/
public boolean hitTest (int x, int y)
{
return _mirage.hitTest(x, y);
}
// /**
// * Creates a shallow copy of this tile object.
// */
// public Object clone ()
// {
// try {
// return (Tile)super.clone();
// } catch (CloneNotSupportedException cnse) {
// String errmsg = "All is wrong with the universe: " + cnse;
// throw new RuntimeException(errmsg);
// }
// }
/**
* Return a string representation of this tile.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder("[");
toString(buf);
return buf.append("]").toString();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific tile information to the string buffer.
*/
protected void toString (StringBuilder buf)
{
buf.append(_mirage.getWidth()).append("x");
buf.append(_mirage.getHeight());
}
/** Decrement total tile memory by our value. */
protected void finalize ()
{
if (_mirage != null) {
_totalTileMemory -= _mirage.getEstimatedMemoryUsage();
}
}
/** Our tileset image. */
protected Mirage _mirage;
/** Used to track total (estimated) memory in use by tiles. */
protected static long _totalTileMemory = 0L;
}
@@ -0,0 +1,64 @@
//
// $Id: TileIcon.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Graphics;
import javax.swing.Icon;
/**
* Implements the icon interface, using a {@link Tile} to render the icon
* image.
*/
public class TileIcon implements Icon
{
/**
* Creates a tile icon that will use the supplied tile to render itself.
*/
public TileIcon (Tile tile)
{
_tile = tile;
}
// documentation inherited from interface
public void paintIcon (Component c, Graphics g, int x, int y)
{
_tile.paint((Graphics2D)g, x, y);
}
// documentation inherited from interface
public int getIconWidth ()
{
return _tile.getWidth();
}
// documentation inherited from interface
public int getIconHeight ()
{
return _tile.getHeight();
}
/** The tile used to render this icon. */
protected Tile _tile;
}
@@ -0,0 +1,247 @@
//
// $Id: TileManager.java 3392 2005-03-10 01:30:34Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import com.samskivert.io.PersistenceException;
import com.threerings.media.Log;
import com.threerings.media.image.ImageManager;
/**
* The tile manager provides a simplified interface for retrieving and
* caching tiles. Tiles can be loaded in two different ways. An
* application can load a tileset by hand, specifying the path to the
* tileset image and all of the tileset metadata necessary for extracting
* the image tiles, or it can provide a tileset repository which loads up
* tilesets using whatever repository mechanism is implemented by the
* supplied repository. In the latter case, tilesets are loaded by a
* unique identifier.
*
* <p> Loading tilesets by hand is intended for things like toolbar icons
* or games with a single set of tiles (think Stratego, for example).
* Loading tilesets from a repository supports games with vast numbers of
* tiles to which more tiles may be added on the fly (think the tiles for
* an isometric-display graphical MUD).
*/
public class TileManager
{
/**
* Creates a tile manager and provides it with a reference to the
* image manager from which it will load tileset images.
*
* @param imgr the image manager via which the tile manager will
* decode and cache images.
*/
public TileManager (ImageManager imgr)
{
_imgr = imgr;
_defaultProvider = new IMImageProvider(_imgr, (String)null);
}
/**
* Loads up a tileset from the specified image with the specified
* metadata parameters.
*/
public UniformTileSet loadTileSet (String imgPath, int width, int height)
{
return loadCachedTileSet("", imgPath, width, height);
}
/**
* Loads up a tileset from the specified image (located in the
* specified resource set) with the specified metadata parameters.
*/
public UniformTileSet loadTileSet (
String rset, String imgPath, int width, int height)
{
return loadTileSet(
getImageProvider(rset), rset, imgPath, width, height);
}
/**
*/
public UniformTileSet loadTileSet (
ImageProvider improv, String improvKey, String imgPath,
int width, int height)
{
UniformTileSet uts = loadCachedTileSet(
improvKey, imgPath, width, height);
uts.setImageProvider(improv);
return uts;
}
/**
* Returns an image provider that will load images from the specified
* resource set.
*/
public ImageProvider getImageProvider (String rset)
{
return new IMImageProvider(_imgr, rset);
}
/**
* Used to load and cache tilesets loaded via {@link #loadTileSet}.
*/
protected UniformTileSet loadCachedTileSet (
String bundle, String imgPath, int width, int height)
{
String key = bundle + "::" + imgPath;
SoftReference ref = (SoftReference) _handcache.get(key);
UniformTileSet uts = (ref == null) ? null
: (UniformTileSet) ref.get();
if (uts == null) {
uts = new UniformTileSet();
uts.setImageProvider(_defaultProvider);
uts.setImagePath(imgPath);
uts.setWidth(width);
uts.setHeight(height);
_handcache.put(key, new SoftReference(uts));
}
return uts;
}
/**
* Sets the tileset repository that will be used by the tile manager
* when tiles are requested by tileset id.
*/
public void setTileSetRepository (TileSetRepository setrep)
{
_setrep = setrep;
}
/**
* Returns the tileset repository currently in use.
*/
public TileSetRepository getTileSetRepository ()
{
return _setrep;
}
/**
* Returns the tileset with the specified id. Tilesets are fetched
* from the tileset repository supplied via {@link
* #setTileSetRepository}, and are subsequently cached.
*
* @param tileSetId the unique identifier for the desired tileset.
*
* @exception NoSuchTileSetException thrown if no tileset exists with
* the specified id or if an underlying error occurs with the tileset
* repository's persistence mechanism.
*/
public TileSet getTileSet (int tileSetId)
throws NoSuchTileSetException
{
// make sure we have a repository configured
if (_setrep == null) {
throw new NoSuchTileSetException(tileSetId);
}
try {
return _setrep.getTileSet(tileSetId);
} catch (PersistenceException pe) {
Log.warning("Failure loading tileset [id=" + tileSetId +
", error=" + pe + "].");
throw new NoSuchTileSetException(tileSetId);
}
}
/**
* Returns the tileset with the specified name.
*
* @throws NoSuchTileSetException if no tileset with the specified
* name is available via our configured tile set repository.
*/
public TileSet getTileSet (String name)
throws NoSuchTileSetException
{
// make sure we have a repository configured
if (_setrep == null) {
throw new NoSuchTileSetException(name);
}
try {
return _setrep.getTileSet(name);
} catch (PersistenceException pe) {
Log.warning("Failure loading tileset [name=" + name +
", error=" + pe + "].");
throw new NoSuchTileSetException(name);
}
}
/**
* Returns the {@link Tile} object with the specified fully qualified
* tile id.
*
* @see TileUtil#getFQTileId
*/
public Tile getTile (int fqTileId)
throws NoSuchTileSetException
{
return getTile(TileUtil.getTileSetId(fqTileId),
TileUtil.getTileIndex(fqTileId), null);
}
/**
* Returns the {@link Tile} object with the specified fully qualified
* tile id. The supplied colorizer will be used to recolor the tile.
*
* @see TileUtil#getFQTileId
*/
public Tile getTile (int fqTileId, TileSet.Colorizer rizer)
throws NoSuchTileSetException
{
return getTile(TileUtil.getTileSetId(fqTileId),
TileUtil.getTileIndex(fqTileId), rizer);
}
/**
* Returns the {@link Tile} object from the specified tileset at the
* specified index.
*
* @param tileSetId the tileset id.
* @param tileIndex the index of the tile to be retrieved.
*
* @return the tile object.
*/
public Tile getTile (int tileSetId, int tileIndex, TileSet.Colorizer rizer)
throws NoSuchTileSetException
{
TileSet set = getTileSet(tileSetId);
return set.getTile(tileIndex, rizer);
}
/** The entity through which we decode and cache images. */
protected ImageManager _imgr;
/** A cache of tilesets that have been loaded by hand. */
protected HashMap _handcache = new HashMap();
/** The tile set repository. */
protected TileSetRepository _setrep;
/** Used to load tileset images from the default resource source. */
protected ImageProvider _defaultProvider;
}
@@ -0,0 +1,84 @@
//
// $Id: TileMultiFrameImage.java 3559 2005-05-17 00:42:12Z tedv $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Graphics2D;
import com.threerings.media.image.Colorization;
import com.threerings.media.util.MultiFrameImage;
/**
* A {@link MultiFrameImage} implementation that obtains its image frames
* from a tileset.
*/
public class TileMultiFrameImage implements MultiFrameImage
{
/**
* Creates a tile MFI which will obtain its image frames from the
* specified source tileset.
*/
public TileMultiFrameImage (TileSet source)
{
_source = source;
}
/**
* Creates a recoolored tile MFI which will obtain its image frames
* from the specified source tileset.
*/
public TileMultiFrameImage (TileSet source, Colorization[] zations)
{
this(source.clone(zations));
}
// documentation inherited from interface
public int getFrameCount ()
{
return _source.getTileCount();
}
// documentation inherited from interface
public int getWidth (int index)
{
return _source.getTile(index).getWidth();
}
// documentation inherited from interface
public int getHeight (int index)
{
return _source.getTile(index).getHeight();
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y)
{
_source.getTile(index).paint(g, x, y);
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y)
{
return _source.getTile(index).hitTest(x, y);
}
protected TileSet _source;
}
@@ -0,0 +1,454 @@
//
// $Id: TileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Throttle;
import com.threerings.media.Log;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.image.BufferedMirage;
import com.threerings.media.util.MultiFrameImage;
import com.threerings.media.util.MultiFrameImageImpl;
/**
* A tileset stores information on a single logical set of tiles. It
* provides a clean interface for the {@link TileManager} or other
* entities to retrieve individual tiles from the tile set and
* encapsulates the potentially sophisticated process of extracting the
* tile image from a composite tileset image.
*
* <p> Tiles are referenced by their tile id. The tile id is essentially
* the tile number, assuming the tile at the top-left of the image is tile
* id zero and tiles are numbered, in ascending order, left to right, top
* to bottom.
*
* <p> This class is serializable and will be serialized, so derived
* classes should be sure to mark non-persistent fields as
* <code>transient</code>.
*/
public abstract class TileSet
implements Cloneable, Serializable
{
/** Used to assign colorizations to tiles that require them. */
public static interface Colorizer
{
/**
* Returns the colorization to be used for the specified named
* colorization class.
*
* @param index the index of the colorization being requested in
* the tileset's colorization list.
*/
public Colorization getColorization (int index, String zation);
}
/**
* Configures this tileset with an image provider that it can use to
* load its tileset image. This will be called automatically when the
* tileset is fetched via the {@link TileManager}.
*/
public void setImageProvider (ImageProvider improv)
{
_improv = improv;
}
/**
* Returns the tileset name.
*/
public String getName ()
{
return (_name == null) ? _imagePath : _name;
}
/**
* Specifies the tileset name.
*/
public void setName (String name)
{
_name = name;
}
/**
* Sets the path to the image that will be used by this tileset. This
* must be called before the first call to {@link #getTile}.
*/
public void setImagePath (String imagePath)
{
_imagePath = imagePath;
}
/**
* Returns the path to the composite image used by this tileset.
*/
public String getImagePath ()
{
return _imagePath;
}
/**
* Returns the number of tiles in the tileset.
*/
public abstract int getTileCount ();
/**
* Creates a copy of this tileset which will apply the supplied
* colorizations to its tileset image when creating tiles.
*/
public TileSet clone (Colorization[] zations)
{
try {
TileSet tset = (TileSet)clone();
tset._zations = zations;
return tset;
} catch (CloneNotSupportedException cnse) {
Log.warning("Unable to clone tileset prior to colorization " +
"[tset=" + this +
", zations=" + StringUtil.toString(zations) +
", error=" + cnse + "].");
return null;
}
}
/**
* Returns a new tileset that is a clone of this tileset with the
* image path updated to reference the given path. Useful for
* configuring a single tileset and then generating additional
* tilesets with new images with the same configuration.
*/
public TileSet clone (String imagePath)
throws CloneNotSupportedException
{
TileSet dup = (TileSet)clone();
dup.setImagePath(imagePath);
return dup;
}
/**
* Equivalent to {@link #getTile(int,Colorizer)} with a null
* <code>Colorizer</code> argument.
*/
public Tile getTile (int tileIndex)
{
return getTile(tileIndex, _zations);
}
/**
* Creates a {@link Tile} object from this tileset corresponding to
* the specified tile id and returns that tile. A null tile will never
* be returned, but one with an error image may be returned if a
* problem occurs loading the underlying tileset image.
*
* @param tileIndex the index of the tile in the tileset. Tile indexes
* start with zero as the upper left tile and increase by one as the
* tiles move left to right and top to bottom over the source image.
* @param rizer an entity that will be used to obtain colorizations
* for tilesets that are recolorizable. Passing null if the tileset is
* known not to be recolorizable is valid.
*
* @return the tile object.
*/
public Tile getTile (int tileIndex, Colorizer rizer)
{
return getTile(tileIndex, getColorizations(tileIndex, rizer));
}
/**
* Creates a {@link Tile} object from this tileset corresponding to
* the specified tile id and returns that tile. A null tile will never
* be returned, but one with an error image may be returned if a
* problem occurs loading the underlying tileset image.
*
* @param tileIndex the index of the tile in the tileset. Tile indexes
* start with zero as the upper left tile and increase by one as the
* tiles move left to right and top to bottom over the source image.
* @param zations colorizations to be applied to the tile image prior
* to returning it. These may be null for uncolorized images.
*
* @return the tile object.
*/
public Tile getTile (int tileIndex, Colorization[] zations)
{
Tile tile = null;
// first look in the active set; if it's in use by anyone or in
// the cache, it will be in the active set
synchronized (_atiles) {
_key.tileSet = this;
_key.tileIndex = tileIndex;
_key.zations = zations;
SoftReference sref = (SoftReference)_atiles.get(_key);
if (sref != null) {
tile = (Tile)sref.get();
}
}
// if it's not in the active set, it's not in memory; so load it
if (tile == null) {
tile = createTile();
tile.key = new Tile.Key(this, tileIndex, zations);
initTile(tile, tileIndex, zations);
synchronized (_atiles) {
_atiles.put(tile.key, new SoftReference(tile));
}
}
// periodically report our image cache performance
reportCachePerformance();
return tile;
}
/**
* Returns a prepared version of the image that would be used by the
* tile at the specified index. Because tilesets are often used simply
* to provide access to a collection of uniform images, this method is
* provided to bypass the creation of a {@link Tile} object when all
* that is desired is access to the underlying image.
*/
public Mirage getTileMirage (int tileIndex)
{
return getTileMirage(tileIndex, getColorizations(tileIndex, null));
}
/**
* Returns a prepared version of the image that would be used by the
* tile at the specified index. Because tilesets are often used simply
* to provide access to a collection of uniform images, this method is
* provided to bypass the creation of a {@link Tile} object when all
* that is desired is access to the underlying image.
*/
public Mirage getTileMirage (int tileIndex, Colorization[] zations)
{
Rectangle bounds = computeTileBounds(tileIndex);
Mirage mirage = null;
if (checkTileIndex(tileIndex)) {
if (_improv == null) {
Log.warning("Aiya! Tile set missing image provider " +
"[path=" + _imagePath + "].");
} else {
mirage = _improv.getTileImage(_imagePath, bounds, zations);
}
}
if (mirage == null) {
mirage = new BufferedMirage(
ImageUtil.createErrorImage(bounds.width, bounds.height));
}
return mirage;
}
/**
* Returns the entire, raw, uncut, unprepared tileset source image.
* Don't use this method unless you know what you're doing! This image
* should not be rendered directly to the screen, you should obtain a
* tile ({@link #getTile}), or a tile mirage ({@link #getTileMirage}).
*/
public BufferedImage getRawTileSetImage ()
{
return _improv.getTileSetImage(_imagePath, _zations);
}
/**
* Returns the raw (unprepared) image that would be used by the tile
* at the specified index. Don't use this method unless you know what
* you're doing! If you're going to be painting this image onto the
* screen directly, use {@link #getTileMirage} because that prepares
* the image for display. Only use this if you're going to do further
* processing and prepare the subsequent image for display onscreen.
*/
public BufferedImage getRawTileImage (int tileIndex)
{
Rectangle bounds = computeTileBounds(tileIndex);
BufferedImage img = null;
if (checkTileIndex(tileIndex)) {
BufferedImage timg = getRawTileSetImage();
if (timg != null) {
img = timg.getSubimage(bounds.x, bounds.y,
bounds.width, bounds.height);
} else {
Log.warning("Missing source image " + this);
}
}
if (img == null) {
img = ImageUtil.createErrorImage(bounds.width, bounds.height);
}
return img;
}
/**
* Returns colorizations for the specified tile image. The default is
* to return any colorizations associated with the tileset via a call
* to {@link #clone(Colorization[])}, however derived classes may have
* dynamic colorization policies that look up colorization assignments
* from the supplied colorizer.
*/
protected Colorization[] getColorizations (int tileIndex, Colorizer rizer)
{
return _zations;
}
/**
* Used to ensure that the specified tile index is valid.
*/
protected boolean checkTileIndex (int tileIndex)
{
int tcount = getTileCount();
if (tileIndex >= 0 && tileIndex < tcount) {
return true;
} else {
Log.warning("Requested invalid tile [tset=" + this +
", index=" + tileIndex + "].");
Thread.dumpStack();
return false;
}
}
/**
* Computes and returns the bounds for the specified tile based on the
* mechanism used by the derived class to do such things. The width
* and height of the bounds should be the size of the tile image and
* the x and y offset should be the offset in the tileset image for
* the image data of the specified tile.
*
* @param tileIndex the index of the tile whose bounds are to be
* computed.
*/
protected abstract Rectangle computeTileBounds (int tileIndex);
/**
* Creates a blank tile of the appropriate type for this tileset.
*
* @return a blank tile ready to be populated with its image and
* metadata.
*/
protected Tile createTile ()
{
return new Tile();
}
/**
* Initializes the supplied tile. Derived classes can override this
* method to add in their own tile information, but should be sure to
* call <code>super.initTile()</code>.
*
* @param tile the tile to initialize.
* @param tileIndex the index of the tile.
* @param zations the colorizations to be used when generating the
* tile image.
*/
protected void initTile (Tile tile, int tileIndex, Colorization[] zations)
{
if (_improv != null) {
tile.setImage(getTileMirage(tileIndex, zations));
}
}
/**
* Generates a string representation of the tileset information.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder("[");
toString(buf);
return buf.append("]").toString();
}
/**
* Reports statistics detailing the image manager cache performance
* and the current size of the cached images.
*/
protected void reportCachePerformance ()
{
if (/* Log.getLevel() != Log.log.DEBUG || */
_improv == null ||
_cacheStatThrottle.throttleOp()) {
return;
}
// compute our estimated memory usage
long amem = 0;
int asize = 0;
synchronized (_atiles) {
// first total up the active tiles
Iterator iter = _atiles.values().iterator();
while (iter.hasNext()) {
SoftReference sref = (SoftReference)iter.next();
Tile tile = (Tile)sref.get();
if (tile != null) {
asize++;
amem += tile.getEstimatedMemoryUsage();
}
}
}
Log.info("Tile caches [amem=" + (amem / 1024) + "k" +
", tmem=" + (Tile._totalTileMemory / 1024) + "k" +
", seen=" + _atiles.size() + ", asize=" + asize + "].");
}
/**
* Derived classes can override this, calling
* <code>super.toString(buf)</code> and then appending additional
* information to the buffer.
*/
protected void toString (StringBuilder buf)
{
buf.append("name=").append(_name);
buf.append(", path=").append(_imagePath);
buf.append(", tileCount=").append(getTileCount());
}
/** The path to the file containing the tile images. */
protected String _imagePath;
/** The tileset name. */
protected String _name;
/** Colorizations to be applied to tiles created from this tileset. */
protected transient Colorization[] _zations;
/** The entity from which we obtain our tile image. */
protected transient ImageProvider _improv;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
/** A map containing weak references to all "active" tiles. */
protected static HashMap _atiles = new HashMap();
/** A key used to look things up in the cache without creating
* craploads of keys unduly. */
protected static Tile.Key _key = new Tile.Key(null, 0, null);
/** Throttle our cache status logging to once every 300 seconds. */
protected static Throttle _cacheStatThrottle = new Throttle(1, 300000L);
}
@@ -0,0 +1,70 @@
//
// $Id: TileSetIDBroker.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import com.samskivert.io.PersistenceException;
/**
* Brokers tileset ids. The tileset repository interface makes available a
* collection of tilesets based on a unique identifier. The expectation is
* that a collection of tilesets will be used to populate a repository and
* in that population process, tileset ids will be assigned to the
* tilesets. The tileset id broker system provides a means by which named
* tilesets can be mapped consistently to a set of tileset ids. Humans can
* then be responsible for assigning unique names to the tilesets and the
* broker will ensure that those names map to unique ids that won't change
* if the repository is rebuilt from the source tilesets.
*/
public interface TileSetIDBroker
{
/**
* Returns the unique identifier for the named tileset. If no
* identifier has yet been assigned to the specified named tileset,
* one should be assigned and returned.
*
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism used to
* store the name to id mappings.
*/
public int getTileSetID (String tileSetName)
throws PersistenceException;
/**
* Returns true if the specified tileset name is currently mapped to
* some value by this broker.
*
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism used to
* store the name to id mappings.
*/
public boolean tileSetMapped (String tileSetName)
throws PersistenceException;
/**
* When the user of a tilset id broker is done obtaining tileset ids,
* it must call this method to give the tileset id broker an
* opportunity to flush any newly created tileset ids back to its
* persistent store.
*/
public void commit ()
throws PersistenceException;
}
@@ -0,0 +1,86 @@
//
// $Id: TileSetRepository.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.util.Iterator;
import com.samskivert.io.PersistenceException;
/**
* The tileset repository interface should be implemented by classes that
* provide access to tilesets keyed on a unique tileset identifier. The
* tileset id space is up to the repository implementation, which may or
* may not desire to use a {@link TileSetIDBroker} to manage the space.
*/
public interface TileSetRepository
{
/**
* Returns an iterator over the identifiers of all {@link TileSet}
* objects available.
*/
public Iterator enumerateTileSetIds ()
throws PersistenceException;
/**
* Returns an iterator over all {@link TileSet} objects available.
*/
public Iterator enumerateTileSets ()
throws PersistenceException;
/**
* Returns the {@link TileSet} with the specified tile set
* identifier. The repository is responsible for configuring the tile
* set with an image provider.
*
* @exception NoSuchTileSetException thrown if no tileset exists with
* the specified identifier.
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism.
*/
public TileSet getTileSet (int tileSetId)
throws NoSuchTileSetException, PersistenceException;
/**
* Returns the unique identifier of the {@link TileSet} with the
* specified tile set name.
*
* @exception NoSuchTileSetException thrown if no tileset exists with
* the specified name.
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism.
*/
public int getTileSetId (String setName)
throws NoSuchTileSetException, PersistenceException;
/**
* Returns the {@link TileSet} with the specified tile set name. The
* repository is responsible for configuring the tile set with an
* image provider.
*
* @exception NoSuchTileSetException thrown if no tileset exists with
* the specified name.
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism.
*/
public TileSet getTileSet (String setName)
throws NoSuchTileSetException, PersistenceException;
}
@@ -0,0 +1,70 @@
//
// $Id: TileUtil.java 3628 2005-06-28 17:42:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
/**
* Miscellaneous utility routines for working with tiles.
*/
public class TileUtil
{
/**
* Generates a fully-qualified tile id given the supplied tileset id
* and tile index.
*/
public static int getFQTileId (int tileSetId, int tileIndex)
{
return (tileSetId << 16) | tileIndex;
}
/**
* Extracts the tile set id from the supplied fully qualified tile id.
*/
public static int getTileSetId (int fqTileId)
{
return (fqTileId >> 16);
}
/**
* Extracts the tile index from the supplied fully qualified tile id.
*/
public static int getTileIndex (int fqTileId)
{
return (fqTileId & 0xFFFF);
}
/**
* Compute some hash value for "randomizing" tileset picks
* based on x and y coordinates.
*
* @return a positive, seemingly random number based on x and y.
*/
public static int getTileHash (int x, int y)
{
long seed = ((x ^ y) ^ MULTIPLIER) & MASK;
long hash = (seed * MULTIPLIER + ADDEND) & MASK;
return (int) (hash >>> 30);
}
protected static final long MULTIPLIER = 0x5DEECE66DL;
protected static final long ADDEND = 0xBL;
protected static final long MASK = (1L << 48) - 1;
}
@@ -0,0 +1,301 @@
//
// $Id: TrimmedObjectTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
import com.threerings.media.tile.util.TileSetTrimmer;
/**
* An object tileset in which the objects have been trimmed to the
* smallest possible images that still contain all of their
* non-transparent pixels. The objects' origins are adjusted so that the
* objects otherwise behave exactly as the untrimmed objects and are thus
* interchangeable (and more memory efficient).
*/
public class TrimmedObjectTileSet extends TileSet
implements RecolorableTileSet
{
// documentation inherited
public int getTileCount ()
{
return _bounds.length;
}
/**
* Returns the x coordinate of the spot associated with the specified
* tile index.
*/
public int getXSpot (int tileIdx)
{
return (_bits == null) ? 0 : _bits[tileIdx].xspot;
}
/**
* Returns the y coordinate of the spot associated with the specified
* tile index.
*/
public int getYSpot (int tileIdx)
{
return (_bits == null) ? 0 : _bits[tileIdx].yspot;
}
/**
* Returns the orientation of the spot associated with the specified
* tile index, or <code>-1</code> if the object has no associated
* spot.
*/
public int getSpotOrient (int tileIdx)
{
return (_bits == null) ? -1 : _bits[tileIdx].sorient;
}
/**
* Returns the constraints associated with the specified tile index, or
* <code>null</code> if the object has no associated constraints.
*/
public String[] getConstraints (int tileIdx)
{
return (_bits == null) ? null : _bits[tileIdx].constraints;
}
/**
* Checks whether the tile at the specified index has the given constraint.
*/
public boolean hasConstraint (int tileIdx, String constraint)
{
return (_bits == null || _bits[tileIdx].constraints == null) ? false :
ListUtil.contains(_bits[tileIdx].constraints, constraint);
}
// documentation inherited from interface RecolorableTileSet
public String[] getColorizations ()
{
return _zations;
}
/**
* Returns the base width for the specified object index.
*/
public int getBaseWidth (int tileIdx)
{
return _ometrics[tileIdx].width;
}
/**
* Returns the base height for the specified object index.
*/
public int getBaseHeight (int tileIdx)
{
return _ometrics[tileIdx].height;
}
// documentation inherited
protected Rectangle computeTileBounds (int tileIndex)
{
return _bounds[tileIndex];
}
// documentation inherited
protected Colorization[] getColorizations (int tileIndex, Colorizer rizer)
{
Colorization[] zations = null;
if (rizer != null && _zations != null) {
zations = new Colorization[_zations.length];
for (int ii = 0; ii < _zations.length; ii++) {
zations[ii] = rizer.getColorization(ii, _zations[ii]);
}
}
return zations;
}
// documentation inherited
protected Tile createTile ()
{
return new ObjectTile();
}
// documentation inherited
protected void initTile (Tile tile, int tileIndex, Colorization[] zations)
{
super.initTile(tile, tileIndex, zations);
ObjectTile otile = (ObjectTile)tile;
otile.setBase(_ometrics[tileIndex].width, _ometrics[tileIndex].height);
otile.setOrigin(_ometrics[tileIndex].x, _ometrics[tileIndex].y);
if (_bits != null) {
Bits bits = _bits[tileIndex];
otile.setPriority(bits.priority);
if (bits.sorient != -1) {
otile.setSpot(bits.xspot, bits.yspot, bits.sorient);
}
otile.setConstraints(bits.constraints);
}
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", ometrics=").append(StringUtil.toString(_ometrics));
buf.append(", bounds=").append(StringUtil.toString(_bounds));
buf.append(", bits=").append(StringUtil.toString(_bits));
buf.append(", zations=").append(StringUtil.toString(_zations));
}
/**
* Creates a trimmed object tileset from the supplied source object
* tileset. The image path must be set by hand to the appropriate path
* based on where the image data that is written to the
* <code>destImage</code> parameter is actually stored on the file
* system. See {@link TileSetTrimmer#trimTileSet} for further
* information.
*/
public static TrimmedObjectTileSet trimObjectTileSet (
ObjectTileSet source, OutputStream destImage)
throws IOException
{
final TrimmedObjectTileSet tset = new TrimmedObjectTileSet();
tset.setName(source.getName());
int tcount = source.getTileCount();
// System.out.println("Trimming object tile set " +
// "[source=" + source + "].");
// create our metrics arrays
tset._bounds = new Rectangle[tcount];
tset._ometrics = new Rectangle[tcount];
// create our bits if needed
if (source._priorities != null ||
source._xspots != null ||
source._constraints != null) {
tset._bits = new Bits[tcount];
}
// copy our colorizations
tset._zations = source.getColorizations();
// fill in the original object metrics
for (int ii = 0; ii < tcount; ii++) {
tset._ometrics[ii] = new Rectangle();
if (source._xorigins != null) {
tset._ometrics[ii].x = source._xorigins[ii];
}
if (source._yorigins != null) {
tset._ometrics[ii].y = source._yorigins[ii];
}
tset._ometrics[ii].width = source._owidths[ii];
tset._ometrics[ii].height = source._oheights[ii];
// fill in our bits
if (tset._bits != null) {
tset._bits[ii] = new Bits();
}
if (source._priorities != null) {
tset._bits[ii].priority = source._priorities[ii];
}
if (source._xspots != null) {
tset._bits[ii].xspot = source._xspots[ii];
tset._bits[ii].yspot = source._yspots[ii];
tset._bits[ii].sorient = source._sorients[ii];
}
if (source._constraints != null) {
tset._bits[ii].constraints = source._constraints[ii];
}
}
// create the trimmed tileset image
TileSetTrimmer.TrimMetricsReceiver tmr =
new TileSetTrimmer.TrimMetricsReceiver() {
public void trimmedTile (int tileIndex, int imageX, int imageY,
int trimX, int trimY,
int trimWidth, int trimHeight) {
tset._ometrics[tileIndex].x -= trimX;
tset._ometrics[tileIndex].y -= trimY;
tset._bounds[tileIndex] =
new Rectangle(imageX, imageY, trimWidth, trimHeight);
}
};
TileSetTrimmer.trimTileSet(source, destImage, tmr);
// Log.info("Trimmed object tileset " +
// "[bounds=" + StringUtil.toString(tset._bounds) +
// ", metrics=" + StringUtil.toString(tset._ometrics) + "].");
return tset;
}
/** Extra bits related to object tiles. */
protected static class Bits implements Serializable
{
/** The default render priority for this object. */
public byte priority;
/** The x coordinate of the "spot" associated with this object. */
public short xspot;
/** The y coordinate of the "spot" associated with this object. */
public short yspot;
/** The orientation of the "spot" associated with this object. */
public byte sorient = -1;
/** The constraints associated with this object. */
public String[] constraints;
/** Generates a string representation of this instance. */
public String toString ()
{
return StringUtil.fieldsToString(this);
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 2;
}
/** Contains the width and height of each object tile and the offset
* into the tileset image of their image data. */
protected Rectangle[] _bounds;
/** Contains the origin offset for each object tile and the object
* footprint width and height (in tile units). */
protected Rectangle[] _ometrics;
/** Extra bits relating to our objects. */
protected Bits[] _bits;
/** Colorization classes that apply to our objects. */
protected String[] _zations;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,91 @@
//
// $Id: TrimmedTile.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.samskivert.util.StringUtil;
/**
* Behaves just like a regular tile, but contains a "trimmed" image which
* is one where the source image has been trimmed to the smallest
* rectangle that contains all the non-transparent pixels of the original
* image.
*/
public class TrimmedTile extends Tile
{
/**
* Sets the trimmed bounds of this tile.
*
* @param tbounds contains the width and height of the
* <em>untrimmed</em> tile, but the x and y offset of the
* <em>trimmed</em> tile image in the original untrimmed tile image.
*/
public void setTrimmedBounds (Rectangle tbounds)
{
_tbounds = tbounds;
}
// documentation inherited
public int getWidth ()
{
return _tbounds.width;
}
// documentation inherited
public int getHeight ()
{
return _tbounds.height;
}
// documentation inherited
public void paint (Graphics2D gfx, int x, int y)
{
_mirage.paint(gfx, x + _tbounds.x, y + _tbounds.y);
}
/**
* Fills in the bounds of the trimmed image within the coordinate
* system defined by the complete virtual tile.
*/
public void getTrimmedBounds (Rectangle tbounds)
{
tbounds.setBounds(_tbounds.x, _tbounds.y,
_mirage.getWidth(), _mirage.getHeight());
}
// documentation inherited
public boolean hitTest (int x, int y)
{
return super.hitTest(x - _tbounds.x, y - _tbounds.y);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
buf.append(", tbounds=").append(StringUtil.toString(_tbounds));
}
/** Our extra trimmed image dimension information. */
protected Rectangle _tbounds;
}
@@ -0,0 +1,114 @@
//
// $Id: TrimmedTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.OutputStream;
import com.threerings.media.image.Colorization;
import com.threerings.media.tile.util.TileSetTrimmer;
/**
* Contains the necessary information to create a set of trimmed tiles
* from a base image and the associated trim metrics.
*/
public class TrimmedTileSet extends TileSet
{
// documentation inherited
public int getTileCount ()
{
return _obounds.length;
}
// documentation inherited
protected Rectangle computeTileBounds (int tileIndex)
{
return _obounds[tileIndex];
}
// documentation inherited
protected Tile createTile ()
{
return new TrimmedTile();
}
// documentation inherited
protected void initTile (Tile tile, int tileIndex, Colorization[] zations)
{
super.initTile(tile, tileIndex, zations);
((TrimmedTile)tile).setTrimmedBounds(_tbounds[tileIndex]);
}
/**
* Creates a trimmed tileset from the supplied source tileset. The
* image path must be set by hand to the appropriate path based on
* where the image data that is written to the <code>destImage</code>
* parameter is actually stored on the file system. See {@link
* TileSetTrimmer#trimTileSet} for further information.
*/
public static TrimmedTileSet trimTileSet (
TileSet source, OutputStream destImage)
throws IOException
{
final TrimmedTileSet tset = new TrimmedTileSet();
tset.setName(source.getName());
int tcount = source.getTileCount();
tset._tbounds = new Rectangle[tcount];
tset._obounds = new Rectangle[tcount];
// grab the dimensions of the original tiles
for (int ii = 0; ii < tcount; ii++) {
tset._tbounds[ii] = source.computeTileBounds(ii);
}
// create the trimmed tileset image
TileSetTrimmer.TrimMetricsReceiver tmr =
new TileSetTrimmer.TrimMetricsReceiver() {
public void trimmedTile (int tileIndex, int imageX, int imageY,
int trimX, int trimY,
int trimWidth, int trimHeight) {
tset._tbounds[tileIndex].x = trimX;
tset._tbounds[tileIndex].y = trimY;
tset._obounds[tileIndex] =
new Rectangle(imageX, imageY, trimWidth, trimHeight);
}
};
TileSetTrimmer.trimTileSet(source, destImage, tmr);
return tset;
}
/** The width and height of the trimmed tile, and the x and y offset
* of the trimmed image within our tileset image. */
protected Rectangle[] _obounds;
/** The width and height of the untrimmed image and the x and y offset
* within the untrimmed image at which the trimmed image should be
* rendered. */
protected Rectangle[] _tbounds;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,103 @@
//
// $Id: UniformTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.geom.GeomUtil;
/**
* A uniform tileset is one that is composed of tiles that are all the
* same width and height and are arranged into rows, with each row having
* the same number of tiles except possibly the final row which can
* contain the same as or less than the number of tiles contained by the
* previous rows.
*/
public class UniformTileSet extends TileSet
{
// documentation inherited
public int getTileCount ()
{
BufferedImage tsimg = getRawTileSetImage();
int perRow = tsimg.getWidth() / _width;
int perCol = tsimg.getHeight() / _height;
return perRow * perCol;
}
/**
* Specifies the width of the tiles in this tileset.
*/
public void setWidth (int width)
{
_width = width;
}
/**
* Returns the width of the tiles in this tileset.
*/
public int getWidth ()
{
return _width;
}
/**
* Specifies the height of the tiles in this tileset.
*/
public void setHeight (int height)
{
_height = height;
}
/**
* Returns the height of the tiles in this tileset.
*/
public int getHeight ()
{
return _height;
}
// documentation inherited
protected Rectangle computeTileBounds (int tileIndex)
{
BufferedImage tsimg = getRawTileSetImage();
return GeomUtil.getTile(tsimg.getWidth(), tsimg.getHeight(),
_width, _height, tileIndex);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", width=").append(_width);
buf.append(", height=").append(_height);
}
/** The width (in pixels) of the tiles in this tileset. */
protected int _width;
/** The height (in pixels) of the tiles in this tileset. */
protected int _height;
/** Our historic serialization version id. */
private static final long serialVersionUID = 3536616655149232917L;
}
@@ -0,0 +1,82 @@
//
// $Id: BundleUtil.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import com.samskivert.io.StreamUtil;
import com.threerings.resource.ResourceBundle;
/**
* Bundle related utility functions.
*/
public class BundleUtil
{
/** The path to the metadata resource that we will attempt to load
* from our resource set. */
public static final String METADATA_PATH = "tsbundles.dat";
/**
* Extracts, but does not initialize, a serialized tileset bundle
* instance from the supplied resource bundle.
*/
public static TileSetBundle extractBundle (ResourceBundle bundle)
throws IOException, ClassNotFoundException
{
// unserialize the tileset bundles array
InputStream tbin = null;
try {
tbin = bundle.getResource(METADATA_PATH);
ObjectInputStream oin = new ObjectInputStream(
new BufferedInputStream(tbin));
TileSetBundle tsb = (TileSetBundle)oin.readObject();
return tsb;
} finally {
StreamUtil.close(tbin);
}
}
/**
* Extracts, but does not initialize, a serialized tileset bundle
* instance from the supplied file.
*/
public static TileSetBundle extractBundle (File file)
throws IOException, ClassNotFoundException
{
// unserialize the tileset bundles array
FileInputStream fin = new FileInputStream(file);
try {
ObjectInputStream oin = new ObjectInputStream(
new BufferedInputStream(fin));
TileSetBundle tsb = (TileSetBundle)oin.readObject();
return tsb;
} finally {
StreamUtil.close(fin);
}
}
}
@@ -0,0 +1,232 @@
//
// $Id: BundledTileSetRepository.java 3608 2005-06-20 22:59:07Z andrzej $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.HashIntMap;
import com.threerings.resource.ResourceBundle;
import com.threerings.resource.ResourceManager;
import com.threerings.media.Log;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.IMImageProvider;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileSetRepository;
/**
* Loads tileset data from a set of resource bundles.
*
* @see ResourceManager
*/
public class BundledTileSetRepository
implements TileSetRepository
{
/**
* Constructs a repository which will obtain its resource set from the
* supplied resource manager.
*
* @param rmgr the resource manager from which to obtain our resource
* set.
* @param imgr the image manager through which we will configure the
* tile sets to load their images, or <code>null</code> if image tiles
* should not be loaded (only the tile metadata)
* @param name the name of the resource set from which we will be
* loading our tile data.
*/
public BundledTileSetRepository (final ResourceManager rmgr,
final ImageManager imgr,
final String name)
{
_imgr = imgr;
// unpack our bundles in the background
new Thread(new Runnable() {
public void run () {
initBundles(rmgr, name);
}
}).start();
}
/**
* Initializes our bundles,
*/
protected void initBundles (ResourceManager rmgr, String name)
{
// first we obtain the resource set from which we will load up our
// tileset bundles
ResourceBundle[] rbundles = rmgr.getResourceSet(name);
// sanity check
if (rbundles == null) {
Log.warning("Unable to fetch tileset resource set " +
"[name=" + name + "]. Perhaps it's not defined " +
"in the resource manager config?");
return;
}
HashIntMap idmap = new HashIntMap();
HashMap namemap = new HashMap();
// iterate over the resource bundles in the set, loading up the
// tileset bundles in each resource bundle
for (int i = 0; i < rbundles.length; i++) {
addBundle(idmap, namemap, rbundles[i]);
}
// fill in our bundles array and wake up any waiters
synchronized (this) {
_idmap = idmap;
_namemap = namemap;
notifyAll();
}
}
/**
* Registers the bundle with the tileset repository, overriding any
* bundle with the same id or name.
*/
public void addBundle (ResourceBundle bundle)
{
addBundle(_idmap, _namemap, bundle);
}
/**
* Extracts the tileset bundle from the supplied resource bundle
* and registers it.
*/
protected void addBundle (HashIntMap idmap, HashMap namemap,
ResourceBundle bundle)
{
try {
TileSetBundle tsb = BundleUtil.extractBundle(bundle);
// initialize it and add it to the list
tsb.init(bundle);
addBundle(idmap, namemap, tsb);
} catch (Exception e) {
Log.warning("Unable to load tileset bundle '" +
BundleUtil.METADATA_PATH + "' from resource " +
"bundle [rbundle=" + bundle +
", error=" + e + "].");
Log.logStackTrace(e);
}
}
/**
* Adds the tilesets in the supplied bundle to our tileset mapping
* tables. Any tilesets with the same name or id will be overwritten.
*/
protected void addBundle (HashIntMap idmap, HashMap namemap,
TileSetBundle bundle)
{
IMImageProvider improv = (_imgr == null) ?
null : new IMImageProvider(_imgr, bundle);
// map all of the tilesets in this bundle
for (Iterator iter = bundle.entrySet().iterator(); iter.hasNext(); ) {
HashIntMap.Entry entry = (HashIntMap.Entry)iter.next();
Integer tsid = (Integer)entry.getKey();
TileSet tset = (TileSet)entry.getValue();
tset.setImageProvider(improv);
idmap.put(tsid.intValue(), tset);
namemap.put(tset.getName(), tsid);
}
}
// documentation inherited from interface
public Iterator enumerateTileSetIds ()
throws PersistenceException
{
waitForBundles();
return _idmap.keySet().iterator();
}
// documentation inherited from interface
public Iterator enumerateTileSets ()
throws PersistenceException
{
waitForBundles();
return _idmap.values().iterator();
}
// documentation inherited from interface
public TileSet getTileSet (int tileSetId)
throws NoSuchTileSetException, PersistenceException
{
waitForBundles();
TileSet tset = (TileSet)_idmap.get(tileSetId);
if (tset == null) {
throw new NoSuchTileSetException(tileSetId);
}
return tset;
}
// documentation inherited from interface
public int getTileSetId (String setName)
throws NoSuchTileSetException, PersistenceException
{
waitForBundles();
Integer tsid = (Integer)_namemap.get(setName);
if (tsid != null) {
return tsid.intValue();
}
throw new NoSuchTileSetException(setName);
}
// documentation inherited from interface
public TileSet getTileSet (String setName)
throws NoSuchTileSetException, PersistenceException
{
waitForBundles();
TileSet tset = null;
Integer tsid = (Integer)_namemap.get(setName);
if (tsid != null) {
return getTileSet(tsid.intValue());
}
throw new NoSuchTileSetException(setName);
}
/** Used to allow bundle unpacking to proceed asynchronously. */
protected synchronized void waitForBundles ()
{
while (_idmap == null) {
try {
wait();
} catch (InterruptedException ie) {
Log.warning("Interrupted waiting for bundles " + ie);
}
}
}
/** The image manager via which we load our images. */
protected ImageManager _imgr;
/** A mapping from tileset id to tileset. */
protected HashIntMap _idmap;
/** A mapping from tileset name to tileset id. */
protected HashMap _namemap;
}
@@ -0,0 +1,148 @@
//
// $Id: TileSetBundle.java 4007 2006-04-10 08:59:30Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import javax.imageio.ImageIO;
import com.samskivert.util.HashIntMap;
import com.threerings.resource.ResourceBundle;
import com.threerings.media.image.FastImageIO;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.tile.TileSet;
/**
* A tileset bundle is used to load up tilesets by id from a persistent
* bundle of tilesets stored on the local filesystem.
*/
public class TileSetBundle extends HashIntMap
implements Serializable, ImageDataProvider
{
/**
* Initializes this resource bundle with a reference to the jarfile
* from which it was loaded and from which it can load image data. The
* image manager will be used to decode the images.
*/
public void init (ResourceBundle bundle)
{
_bundle = bundle;
}
/**
* Returns the bundle file from which our tiles are fetched.
*/
public File getSource ()
{
return _bundle.getSource();
}
/**
* Adds a tileset to this tileset bundle.
*/
public final void addTileSet (int tileSetId, TileSet set)
{
put(tileSetId, set);
}
/**
* Retrieves a tileset from this tileset bundle.
*/
public final TileSet getTileSet (int tileSetId)
{
return (TileSet)get(tileSetId);
}
/**
* Enumerates the tileset ids in this tileset bundle.
*/
public Iterator enumerateTileSetIds ()
{
return keySet().iterator();
}
/**
* Enumerates the tilesets in this tileset bundle.
*/
public Iterator enumerateTileSets ()
{
return values().iterator();
}
// documentation inherited from interface
public String getIdent ()
{
return "tsb:" + _bundle.getSource();
}
// documentation inherited from interface
public BufferedImage loadImage (String path)
throws IOException
{
if (path.endsWith(FastImageIO.FILE_SUFFIX)) {
return FastImageIO.read(_bundle.getResourceFile(path));
} else {
return ImageIO.read(_bundle.getResourceFile(path));
}
}
// custom serialization process
private void writeObject (ObjectOutputStream out)
throws IOException
{
out.writeInt(size());
Iterator entries = intEntrySet().iterator();
while (entries.hasNext()) {
IntEntry entry = (IntEntry)entries.next();
out.writeInt(entry.getIntKey());
out.writeObject(entry.getValue());
}
}
// custom unserialization process
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
int count = in.readInt();
for (int i = 0; i < count; i++) {
int tileSetId = in.readInt();
TileSet set = (TileSet)in.readObject();
put(tileSetId, set);
}
}
/** That from which we load our tile images. */
protected transient ResourceBundle _bundle;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 2;
}
@@ -0,0 +1,93 @@
//
// $Id: DumpBundle.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle.tools;
import java.io.File;
import java.util.Iterator;
import com.threerings.resource.ResourceManager;
import com.threerings.resource.ResourceBundle;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.bundle.BundleUtil;
import com.threerings.media.tile.bundle.TileSetBundle;
/**
* Dumps the contents of a tileset bundle to stdout (just the serialized
* object info, not the image data).
*/
public class DumpBundle
{
public static void main (String[] args)
{
boolean dumpTiles = false;
if (args.length < 1) {
String usage = "Usage: DumpBundle [-tiles] " +
"(bundle.jar|tsbundle.dat) [...]";
System.err.println(usage);
System.exit(-1);
}
// create a resource and image manager in case they want to dump
// the tiles
ResourceManager rmgr = new ResourceManager("rsrc");
for (int i = 0; i < args.length; i++) {
// oh the hackery
if (args[i].equals("-tiles")) {
dumpTiles = true;
continue;
}
File file = new File(args[i]);
try {
TileSetBundle tsb = null;
if (args[i].endsWith(".jar")) {
ResourceBundle bundle = new ResourceBundle(file);
tsb = BundleUtil.extractBundle(bundle);
tsb.init(bundle);
} else {
tsb = BundleUtil.extractBundle(file);
}
Iterator tsids = tsb.enumerateTileSetIds();
while (tsids.hasNext()) {
Integer tsid = (Integer)tsids.next();
TileSet set = tsb.getTileSet(tsid.intValue());
System.out.println(tsid + " => " + set);
if (dumpTiles) {
for (int t = 0, nn = set.getTileCount(); t < nn; t++) {
System.out.println(" " + t + " => " +
set.getTile(t));
}
}
}
} catch (Exception e) {
System.err.println("Error dumping bundle [path=" + args[i] +
", error=" + e + "].");
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,482 @@
//
// $Id: TileSetBundler.java 3788 2005-12-20 02:09:18Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle.tools;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.Deflater;
import javax.imageio.ImageIO;
import org.apache.commons.digester.Digester;
import org.apache.commons.io.CopyUtils;
import org.xml.sax.SAXException;
import com.samskivert.io.PersistenceException;
import com.threerings.media.Log;
import com.threerings.media.image.FastImageIO;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.ImageProvider;
import com.threerings.media.tile.ObjectTileSet;
import com.threerings.media.tile.SimpleCachingImageProvider;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileSetIDBroker;
import com.threerings.media.tile.TrimmedObjectTileSet;
import com.threerings.media.tile.UniformTileSet;
import com.threerings.media.tile.bundle.BundleUtil;
import com.threerings.media.tile.bundle.TileSetBundle;
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
/**
* The tileset bundler is used to create tileset bundles from a set of XML
* tileset descriptions in a bundle description file. The bundles contain
* a serialized representation of the tileset objects along with the
* actual image files referenced by those tilesets.
*
* <p> The organization of the bundle description file is customizable
* based on the an XML configuration file provided to the tileset bundler
* when constructed. The bundler configuration maps XML paths to tileset
* parsers. An example configuration follows:
*
* <pre>
* &lt;bundler-config&gt;
* &lt;mapping&gt;
* &lt;path&gt;bundle.tilesets.uniform&lt;/path&gt;
* &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.UniformTileSetRuleSet
* &lt;/ruleset&gt;
* &lt;/mapping&gt;
* &lt;mapping&gt;
* &lt;path&gt;bundle.tilesets.object&lt;/path&gt;
* &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.ObjectTileSetRuleSet
* &lt;/ruleset&gt;
* &lt;/mapping&gt;
* &lt;/bundler-config&gt;
* </pre>
*
* This configuration would be used to parse a bundle description that
* looked something like the following:
*
* <pre>
* &lt;bundle&gt;
* &lt;tilesets&gt;
* &lt;uniform&gt;
* &lt;tileset&gt;
* &lt;!-- ... --&gt;
* &lt;/tileset&gt;
* &lt;/uniform&gt;
* &lt;object&gt;
* &lt;tileset&gt;
* &lt;!-- ... --&gt;
* &lt;/tileset&gt;
* &lt;/object&gt;
* &lt;/tilesets&gt;
* </pre>
*
* The class specified in the <code>ruleset</code> element must derive
* from {@link TileSetRuleSet}. The images that will be included in the
* bundle must be in the same directory as the bundle description file and
* the tileset descriptions must reference the images without a preceding
* path.
*/
public class TileSetBundler
{
/**
* Constructs a tileset bundler with the specified path to a bundler
* configuration file. The configuration file will be loaded and used
* to configure this tileset bundler.
*/
public TileSetBundler (String configPath)
throws IOException
{
this(new File(configPath));
}
/**
* Constructs a tileset bundler with the specified bundler config
* file.
*/
public TileSetBundler (File configFile)
throws IOException
{
// we parse our configuration with a digester
Digester digester = new Digester();
// push our mappings array onto the stack
ArrayList mappings = new ArrayList();
digester.push(mappings);
// create a mapping object for each mapping entry and append it to
// our mapping list
digester.addObjectCreate("bundler-config/mapping",
Mapping.class.getName());
digester.addSetNext("bundler-config/mapping",
"add", "java.lang.Object");
// configure each mapping object with the path and ruleset
digester.addCallMethod("bundler-config/mapping", "init", 2);
digester.addCallParam("bundler-config/mapping/path", 0);
digester.addCallParam("bundler-config/mapping/ruleset", 1);
// now go like the wind
FileInputStream fin = new FileInputStream(configFile);
try {
digester.parse(fin);
} catch (SAXException saxe) {
String errmsg = "Failure parsing bundler config file " +
"[file=" + configFile.getPath() + "]";
throw (IOException) new IOException(errmsg).initCause(saxe);
}
fin.close();
// create our digester
_digester = new Digester();
// use the mappings we parsed to configure our actual digester
int msize = mappings.size();
for (int i = 0; i < msize; i++) {
Mapping map = (Mapping)mappings.get(i);
try {
TileSetRuleSet ruleset = (TileSetRuleSet)
Class.forName(map.ruleset).newInstance();
// configure the ruleset
ruleset.setPrefix(map.path);
// add it to the digester
_digester.addRuleSet(ruleset);
// and add a rule to stick the parsed tilesets onto the
// end of an array list that we'll put on the stack
_digester.addSetNext(map.path + TileSetRuleSet.TILESET_PATH,
"add", "java.lang.Object");
} catch (Exception e) {
String errmsg = "Unable to create tileset rule set " +
"instance [mapping=" + map + "].";
throw (IOException) new IOException(errmsg).initCause(e);
}
}
}
/**
* Creates a tileset bundle at the location specified by the
* <code>targetPath</code> parameter, based on the description
* provided via the <code>bundleDesc</code> parameter.
*
* @param idBroker the tileset id broker that will be used to map
* tileset names to tileset ids.
* @param bundleDesc a file object pointing to the bundle description
* file.
* @param targetPath the path of the tileset bundle file that will be
* created.
*
* @exception IOException thrown if an error occurs reading, writing
* or processing anything.
*/
public void createBundle (
TileSetIDBroker idBroker, File bundleDesc, String targetPath)
throws IOException
{
createBundle(idBroker, bundleDesc, new File(targetPath));
}
/**
* Creates a tileset bundle at the location specified by the
* <code>targetPath</code> parameter, based on the description
* provided via the <code>bundleDesc</code> parameter.
*
* @param idBroker the tileset id broker that will be used to map
* tileset names to tileset ids.
* @param bundleDesc a file object pointing to the bundle description
* file.
* @param target the tileset bundle file that will be created.
*
* @return true if the bundle was rebuilt, false if it was not because
* the bundle file was newer than all involved source files.
*
* @exception IOException thrown if an error occurs reading, writing
* or processing anything.
*/
public boolean createBundle (
TileSetIDBroker idBroker, final File bundleDesc, File target)
throws IOException
{
// stick an array list on the top of the stack into which we will
// collect parsed tilesets
ArrayList sets = new ArrayList();
_digester.push(sets);
// parse the tilesets
FileInputStream fin = new FileInputStream(bundleDesc);
try {
_digester.parse(fin);
} catch (SAXException saxe) {
String errmsg = "Failure parsing bundle description file " +
"[path=" + bundleDesc.getPath() + "]";
throw (IOException) new IOException(errmsg).initCause(saxe);
} finally {
fin.close();
}
// we want to make sure that at least one of the tileset image
// files or the bundle definition file is newer than the bundle
// file, otherwise consider the bundle up to date
long newest = bundleDesc.lastModified();
// create a tileset bundle to hold our tilesets
TileSetBundle bundle = new TileSetBundle();
// add all of the parsed tilesets to the tileset bundle
try {
for (int i = 0; i < sets.size(); i++) {
TileSet set = (TileSet)sets.get(i);
String name = set.getName();
// let's be robust
if (name == null) {
Log.warning("Tileset was parsed, but received no name " +
"[set=" + set + "]. Skipping.");
continue;
}
// make sure this tileset's image file exists and check
// it's last modified date
File tsfile = new File(bundleDesc.getParent(),
set.getImagePath());
if (!tsfile.exists()) {
System.err.println("Tile set missing image file " +
"[bundle=" + bundleDesc.getPath() +
", name=" + set.getName() +
", imgpath=" + tsfile.getPath() + "].");
continue;
}
if (tsfile.lastModified() > newest) {
newest = tsfile.lastModified();
}
// assign a tilset id to the tileset and bundle it
try {
int tileSetId = idBroker.getTileSetID(name);
bundle.addTileSet(tileSetId, set);
} catch (PersistenceException pe) {
String errmsg = "Failure obtaining a tileset id for " +
"tileset [set=" + set + "].";
throw (IOException) new IOException(errmsg).initCause(pe);
}
}
// clear out our array list in preparation for another go
sets.clear();
} finally {
// before we go, we have to commit our brokered tileset ids
// back to the broker's persistent store
try {
idBroker.commit();
} catch (PersistenceException pe) {
Log.warning("Failure committing brokered tileset ids " +
"back to broker's persistent store " +
"[error=" + pe + "].");
}
}
// see if our newest file is newer than the tileset bundle
if (newest < target.lastModified()) {
return false;
}
// create an image provider for loading our tileset images
SimpleCachingImageProvider improv = new SimpleCachingImageProvider() {
protected BufferedImage loadImage (String path)
throws IOException {
return ImageIO.read(new File(bundleDesc.getParent(), path));
}
};
return createBundle(target, bundle, improv, bundleDesc.getParent());
}
/**
* Finish the creation of a tileset bundle jar file.
*
* @param target the tileset bundle file that will be created.
* @param bundle contains the tilesets we'd like to save out to the
* bundle.
* @param improv the image provider.
* @param imageBase the base directory for getting images for non
* ObjectTileSet tilesets.
*/
public static boolean createBundle (
File target, TileSetBundle bundle, ImageProvider improv,
String imageBase)
throws IOException
{
// now we have to create the actual bundle file
FileOutputStream fout = new FileOutputStream(target);
Manifest manifest = new Manifest();
JarOutputStream jar = new JarOutputStream(fout, manifest);
jar.setLevel(Deflater.BEST_COMPRESSION);
try {
// write all of the image files to the bundle, converting the
// tilesets to trimmed tilesets in the process
Iterator iditer = bundle.enumerateTileSetIds();
while (iditer.hasNext()) {
int tileSetId = ((Integer)iditer.next()).intValue();
TileSet set = bundle.getTileSet(tileSetId);
String imagePath = set.getImagePath();
// sanity checks
if (imagePath == null) {
Log.warning("Tileset contains no image path " +
"[set=" + set + "]. It ain't gonna work.");
continue;
}
// if this is an object tileset, we can't trim it!
if (set instanceof ObjectTileSet) {
// set the tileset up with an image provider; we
// need to do this so that we can trim it!
set.setImageProvider(improv);
// we're going to trim it, so adjust the path
imagePath = adjustImagePath(imagePath);
jar.putNextEntry(new JarEntry(imagePath));
try {
// create a trimmed object tileset, which will
// write the trimmed tileset image to the jar
// output stream
TrimmedObjectTileSet tset =
TrimmedObjectTileSet.trimObjectTileSet(
(ObjectTileSet)set, jar);
tset.setImagePath(imagePath);
// replace the original set with the trimmed
// tileset in the tileset bundle
bundle.addTileSet(tileSetId, tset);
} catch (Exception e) {
System.err.println("Error adding tileset to bundle " +
"[set=" + set.getName() +
", ipath=" + imagePath + "].");
e.printStackTrace(System.err);
// replace the tileset with an error tileset
UniformTileSet ets = new UniformTileSet();
ets.setName(set.getName());
ets.setWidth(50);
ets.setHeight(50);
ets.setImagePath(imagePath);
bundle.addTileSet(tileSetId, ets);
// and write an error image to the jar file
ImageIO.write(ImageUtil.createErrorImage(50, 50),
"PNG", jar);
}
} else {
// read the image file and convert it to our custom
// format in the bundle
File ifile = new File(imageBase, imagePath);
try {
BufferedImage image = ImageIO.read(ifile);
if (FastImageIO.canWrite(image)) {
imagePath = adjustImagePath(imagePath);
jar.putNextEntry(new JarEntry(imagePath));
set.setImagePath(imagePath);
FastImageIO.write(image, jar);
} else {
jar.putNextEntry(new JarEntry(imagePath));
FileInputStream imgin = new FileInputStream(ifile);
CopyUtils.copy(imgin, jar);
}
} catch (Exception e) {
String msg = "Failure bundling image " + ifile +
": " + e;
throw (IOException) new IOException(msg).initCause(e);
}
}
}
// now write a serialized representation of the tileset bundle
// object to the bundle jar file
JarEntry entry = new JarEntry(BundleUtil.METADATA_PATH);
jar.putNextEntry(entry);
ObjectOutputStream oout = new ObjectOutputStream(jar);
oout.writeObject(bundle);
oout.flush();
// finally close up the jar file and call ourself done
jar.close();
return true;
} catch (Exception e) {
// remove the incomplete jar file and rethrow the exception
jar.close();
if (!target.delete()) {
Log.warning("Failed to close botched bundle '" + target + "'.");
}
String errmsg = "Failed to create bundle " + target + ": " + e;
throw (IOException) new IOException(errmsg).initCause(e);
}
}
/** Replaces the image suffix with <code>.raw</code>. */
protected static String adjustImagePath (String imagePath)
{
int didx = imagePath.lastIndexOf(".");
return ((didx == -1) ? imagePath :
imagePath.substring(0, didx)) + ".raw";
}
/** Used to parse our configuration. */
public static class Mapping
{
public String path;
public String ruleset;
public void init (String path, String ruleset)
{
this.path = path;
this.ruleset = ruleset;
}
public String toString ()
{
return "[path=" + path + ", ruleset=" + ruleset + "]";
}
}
/** The digester we use to parse bundle descriptions. */
protected Digester _digester;
}
@@ -0,0 +1,143 @@
//
// $Id: TileSetBundlerTask.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.bundle.tools;
import java.io.File;
import java.util.ArrayList;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import com.threerings.media.tile.tools.MapFileTileSetIDBroker;
/**
* Ant task for creating tilset bundles.
*/
public class TileSetBundlerTask extends Task
{
/**
* Sets the path to the bundler configuration file that we'll use when
* creating the bundle.
*/
public void setConfig (File config)
{
_config = config;
}
/**
* Sets the path to the tileset id mapping file we'll use when
* creating the bundle.
*/
public void setMapfile (File mapfile)
{
_mapfile = mapfile;
}
/**
* Adds a nested &lt;fileset&gt; element.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Performs the actual work of the task.
*/
public void execute () throws BuildException
{
// make sure everything was set up properly
ensureSet(_config, "Must specify the path to the bundler config " +
"file via the 'config' attribute.");
ensureSet(_mapfile, "Must specify the path to the tileset id map " +
"file via the 'mapfile' attribute.");
File cfile = null;
try {
// create a tileset bundler
TileSetBundler bundler = new TileSetBundler(_config);
// create our tileset id broker
MapFileTileSetIDBroker broker =
new MapFileTileSetIDBroker(_mapfile);
// deal with the filesets
for (int i = 0; i < _filesets.size(); i++) {
FileSet fs = (FileSet)_filesets.get(i);
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (int f = 0; f < srcFiles.length; f++) {
cfile = new File(fromDir, srcFiles[f]);
// figure out the bundle file based on the definition
// file
String cpath = cfile.getPath();
if (!cpath.endsWith(".xml")) {
System.err.println("Can't infer bundle name from " +
"bundle config name " +
"[path=" + cpath + "].\n" +
"Config file should end with .xml.");
continue;
}
String bpath =
cpath.substring(0, cpath.length()-4) + ".jar";
File bfile = new File(bpath);
// create the bundle
if (bundler.createBundle(broker, cfile, bfile)) {
System.out.println(
"Created bundle from '" + cpath + "'...");
} else {
System.out.println(
"Tileset bundle up to date '" + bpath + "'.");
}
}
}
// commit changes to the tileset id mapping
broker.commit();
} catch (Exception e) {
String errmsg = "Failure creating tileset bundle [source=" + cfile +
"]: " + e.getMessage();
throw new BuildException(errmsg, e);
}
}
protected void ensureSet (Object value, String errmsg)
throws BuildException
{
if (value == null) {
throw new BuildException(errmsg);
}
}
protected File _config;
protected File _mapfile;
/** A list of filesets that contain tileset bundle definitions. */
protected ArrayList _filesets = new ArrayList();
}
@@ -0,0 +1,56 @@
//
// $Id: DumpTileSetMap.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools;
import java.io.File;
import java.util.Iterator;
import com.samskivert.io.PersistenceException;
/**
* Prints out the tileset mappings in a {@link MapFileTileSetIDBroker}.
*/
public class DumpTileSetMap
{
public static void main (String[] args)
{
if (args.length < 1) {
System.err.println("Usage: DumpTileSetMap tileset.map");
System.exit(-1);
}
try {
MapFileTileSetIDBroker broker =
new MapFileTileSetIDBroker(new File(args[0]));
Iterator iter = broker.enumerateMappings();
while (iter.hasNext()) {
String tsname = iter.next().toString();
System.out.println(tsname + " => " +
broker.getTileSetID(tsname));
}
} catch (PersistenceException pe) {
System.err.println("Unable to dump mapping: " + pe);
System.exit(-1);
}
}
}
@@ -0,0 +1,224 @@
//
// $Id: MapFileTileSetIDBroker.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.QuickSort;
import com.threerings.media.tile.TileSetIDBroker;
/**
* Stores a set of tileset name to id mappings in a map file.
*/
public class MapFileTileSetIDBroker implements TileSetIDBroker
{
/**
* Creates a broker that will use the specified file as its persistent
* store. The persistent store will be created if it does not yet
* exist.
*/
public MapFileTileSetIDBroker (File mapfile)
throws PersistenceException
{
// keep this for later
_mapfile = mapfile;
// load up our map data
try {
BufferedReader bin = new BufferedReader(new FileReader(mapfile));
// read in our metadata
_nextTileSetID = readInt(bin);
_storedTileSetID = _nextTileSetID;
// read in our mappings
_map = new HashMap();
readMapFile(bin, _map);
bin.close();
} catch (FileNotFoundException fnfe) {
// create a blank map if our map file doesn't exist
_map = new HashMap();
} catch (Exception e) {
// other errors are more fatal
String errmsg = "Failure reading map file.";
throw new PersistenceException(errmsg, e);
}
}
protected int readInt (BufferedReader bin)
throws IOException
{
String line = bin.readLine();
try {
return Integer.parseInt(line);
} catch (NumberFormatException nfe) {
throw new IOException("Expected number, got '" + line + "'");
}
}
// documentation inherited
public int getTileSetID (String tileSetName)
throws PersistenceException
{
Integer tsid = (Integer)_map.get(tileSetName);
if (tsid == null) {
tsid = Integer.valueOf(++_nextTileSetID);
_map.put(tileSetName, tsid);
}
return tsid.intValue();
}
// documentation inherited from interface
public boolean tileSetMapped (String tileSetName)
throws PersistenceException
{
return _map.containsKey(tileSetName);
}
// documentation inherited
public void commit ()
throws PersistenceException
{
// only write ourselves out if we've changed
if (_storedTileSetID == _nextTileSetID) {
return;
}
try {
BufferedWriter bout = new BufferedWriter(new FileWriter(_mapfile));
// write out our metadata
String tline = "" + _nextTileSetID;
bout.write(tline, 0, tline.length());
bout.newLine();
// write out our mappings
writeMapFile(bout, _map);
bout.close();
} catch (IOException ioe) {
String errmsg = "Failure writing map file.";
throw new PersistenceException(errmsg, ioe);
}
}
/**
* Reads in a mapping from strings to integers, which should have been
* written via {@link #writeMapFile}.
*/
public static void readMapFile (BufferedReader bin, HashMap map)
throws IOException
{
String line;
while ((line = bin.readLine()) != null) {
int eidx = line.indexOf(SEP_STR);
if (eidx == -1) {
throw new IOException("Malformed line, no '" + SEP_STR +
"': '" + line + "'");
}
try {
String code = line.substring(eidx+SEP_STR.length());
map.put(line.substring(0, eidx), Integer.valueOf(code));
} catch (NumberFormatException nfe) {
String errmsg = "Malformed line, invalid code: '" + line + "'";
throw new IOException(errmsg);
}
}
}
/**
* Writes out a mapping from strings to integers in a manner that can
* be read back in via {@link #readMapFile}.
*/
public static void writeMapFile (BufferedWriter bout, HashMap map)
throws IOException
{
String[] lines = new String[map.size()];
Iterator iter = map.keySet().iterator();
for (int ii = 0; iter.hasNext(); ii++) {
String key = (String)iter.next();
Integer value = (Integer)map.get(key);
lines[ii] = key + SEP_STR + value;
}
QuickSort.sort(lines);
for (int ii = 0; ii < lines.length; ii++) {
bout.write(lines[ii], 0, lines[ii].length());
bout.newLine();
}
bout.flush();
}
/**
* Copies the ID from the old tileset to the new tileset which is
* useful when a tileset is renamed. This is called by the {@link
* RenameTileSet} utility.
*/
protected boolean renameTileSet (String oldName, String newName)
{
Integer tsid = (Integer)_map.get(oldName);
if (tsid != null) {
_map.put(newName, tsid);
// fudge our stored tileset ID so that we flush ourselves when
// the rename tool requests that we commit the changes
_storedTileSetID--;
return true;
} else {
return false;
}
}
/**
* Used by {@link DumpTileSetMap} to enumerate our tileset ID
* mappings.
*/
protected Iterator enumerateMappings ()
{
return _map.keySet().iterator();
}
/** Our persistent map file. */
protected File _mapfile;
/** The next tileset id that we'll assign. */
protected int _nextTileSetID;
/** The last tileset id assigned when we were unserialized. */
protected int _storedTileSetID;
/** Our mapping from tileset names to ids. */
protected HashMap _map;
/** The character we use to separate tileset name from code in the map
* file. */
protected static final String SEP_STR = " := ";
}
@@ -0,0 +1,74 @@
//
// $Id: RenameTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools;
import java.io.File;
import com.samskivert.io.PersistenceException;
/**
* Used to map a tileset name to the same ID as a pre-existing tileset.
* This only works for tileset mappings stored using a {@link
* MapFileTileSetIDBroker}. If a tileset is renamed, this utility must be
* used to map the new name to the old ID, otherwise scenes created with
* the renamed tileset will cease to work as the tileset will live under a
* new ID.
*/
public class RenameTileSet
{
/**
* Loads up the tileset map file with the specified path and copies
* the tileset ID from the old tileset name to the new tileset name.
* This is necessary when a tileset is renamed so that the new name
* does not cause the tileset to be assigned a new tileset ID. Bear in
* mind that the old name should never again be used as it will
* conflict with a tileset provided under the new name.
*/
public static void renameTileSet (
String mapPath, String oldName, String newName)
throws PersistenceException
{
MapFileTileSetIDBroker broker =
new MapFileTileSetIDBroker(new File(mapPath));
if (!broker.renameTileSet(oldName, newName)) {
throw new PersistenceException(
"No such old tileset '" + oldName + "'.");
}
broker.commit();
}
public static void main (String[] args)
{
if (args.length < 3) {
System.err.println("Usage: RenameTileSet tileset.map " +
"old_name new_name");
System.exit(-1);
}
try {
renameTileSet(args[0], args[1], args[2]);
} catch (PersistenceException pe) {
System.err.println("Unable to rename tileset: " + pe);
System.exit(-1);
}
}
}
@@ -0,0 +1,188 @@
//
// $Id: ObjectTileSetRuleSet.java 3607 2005-06-20 21:18:55Z andrzej $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools.xml;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.CallMethodSpecialRule;
import com.threerings.media.tile.ObjectTileSet;
import com.threerings.util.DirectionUtil;
/**
* Parses {@link ObjectTileSet} instances from a tileset description. An
* object tileset description looks like so:
*
* <pre>
* &lt;tileset name="Sample Object Tileset"&gt;
* &lt;imagePath&gt;path/to/image.png&lt;/imagePath&gt;
* &lt;!-- the widths (per row) of each tile in pixels --&gt;
* &lt;widths&gt;265&lt;/widths&gt;
* &lt;!-- the heights (per row) of each tile in pixels --&gt;
* &lt;heights&gt;224&lt;/heights&gt;
* &lt;!-- the number of tiles in each row --&gt;
* &lt;tileCounts&gt;4&lt;/tileCounts&gt;
* &lt;!-- the offset in pixels to the upper left tile --&gt;
* &lt;offsetPos&gt;0, 0&lt;/offsetPos&gt;
* &lt;!-- the gap between tiles in pixels --&gt;
* &lt;gapSize&gt;0, 0&lt;/gapSize&gt;
* &lt;!-- the widths (in unit tile count) of the objects --&gt;
* &lt;objectWidths&gt;4, 3, 4, 3&lt;/objectWidths&gt;
* &lt;!-- the heights (in unit tile count) of the objects --&gt;
* &lt;objectHeights&gt;3, 4, 3, 4&lt;/objectHeights&gt;
* &lt;!-- the default render priorities for these object tiles --&gt;
* &lt;priorities&gt;0, 0, -1, 0&lt;/priorities&gt;
* &lt;!-- the constraints for these object tiles --&gt;
* &lt;constraints&gt;ATTACH_N, ATTACH_E, ATTACH_S, ATTACH_W&lt;/constraints&gt;
* &lt;/tileset&gt;
* </pre>
*/
public class ObjectTileSetRuleSet extends SwissArmyTileSetRuleSet
{
// documentation inherited
public void addRuleInstances (Digester digester)
{
super.addRuleInstances(digester);
digester.addRule(
_prefix + TILESET_PATH + "/objectWidths",
new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] widths = StringUtil.parseIntArray(bodyText);
((ObjectTileSet)target).setObjectWidths(widths);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/objectHeights",
new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] heights = StringUtil.parseIntArray(bodyText);
((ObjectTileSet)target).setObjectHeights(heights);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/xOrigins", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] xorigins = StringUtil.parseIntArray(bodyText);
((ObjectTileSet)target).setXOrigins(xorigins);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/yOrigins", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] yorigins = StringUtil.parseIntArray(bodyText);
((ObjectTileSet)target).setYOrigins(yorigins);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/priorities",
new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
byte[] prios = StringUtil.parseByteArray(bodyText);
((ObjectTileSet)target).setPriorities(prios);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/zations", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
String[] zations = StringUtil.parseStringArray(bodyText);
((ObjectTileSet)target).setColorizations(zations);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/xspots", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
short[] xspots = StringUtil.parseShortArray(bodyText);
((ObjectTileSet)target).setXSpots(xspots);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/yspots", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
short[] yspots = StringUtil.parseShortArray(bodyText);
((ObjectTileSet)target).setYSpots(yspots);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/sorients", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
ObjectTileSet set = (ObjectTileSet)target;
String[] ostrs = StringUtil.parseStringArray(bodyText);
byte[] sorients = new byte[ostrs.length];
for (int ii = 0; ii < sorients.length; ii++) {
sorients[ii] = (byte)
DirectionUtil.fromShortString(ostrs[ii]);
if ((sorients[ii] == DirectionUtil.NONE) &&
// don't complain if they didn't even try to
// specify a valid direction
(! ostrs[ii].equals("-1"))) {
System.err.println("Invalid spot orientation " +
"[set=" + set.getName() +
", idx=" + ii +
", orient=" + ostrs[ii] + "].");
}
}
set.setSpotOrients(sorients);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/constraints",
new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
String[] constrs = StringUtil.parseStringArray(
bodyText);
String[][] constraints = new String[constrs.length][];
for (int ii = 0; ii < constrs.length; ii++) {
constraints[ii] = constrs[ii].split("\\s*\\|\\s*");
}
((ObjectTileSet)target).setConstraints(constraints);
}
});
}
// documentation inherited
protected Class getTileSetClass ()
{
return ObjectTileSet.class;
}
}
@@ -0,0 +1,156 @@
//
// $Id: SwissArmyTileSetRuleSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools.xml;
import java.awt.Dimension;
import java.awt.Point;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.CallMethodSpecialRule;
import com.threerings.media.Log;
import com.threerings.media.tile.SwissArmyTileSet;
/**
* Parses {@link SwissArmyTileSet} instances from a tileset description. A
* swiss army tileset description looks like so:
*
* <pre>
* &lt;tileset name="Sample Swiss Army Tileset"&gt;
* &lt;imagePath&gt;path/to/image.png&lt;/imagePath&gt;
* &lt;!-- the widths (per row) of each tile in pixels --&gt;
* &lt;widths&gt;64, 64, 64, 64&lt;/widths&gt;
* &lt;!-- the heights (per row) of each tile in pixels --&gt;
* &lt;heights&gt;48, 48, 48, 64&lt;/heights&gt;
* &lt;!-- the number of tiles in each row --&gt;
* &lt;tileCounts&gt;16, 5, 3, 10&lt;/tileCounts&gt;
* &lt;!-- the offset in pixels to the upper left tile --&gt;
* &lt;offsetPos&gt;8, 8&lt;/offsetPos&gt;
* &lt;!-- the gap between tiles in pixels --&gt;
* &lt;gapSize&gt;12, 12&lt;/gapSize&gt;
* &lt;/tileset&gt;
* </pre>
*/
public class SwissArmyTileSetRuleSet extends TileSetRuleSet
{
// documentation inherited
public void addRuleInstances (Digester digester)
{
super.addRuleInstances(digester);
digester.addRule(
_prefix + TILESET_PATH + "/widths", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] widths = StringUtil.parseIntArray(bodyText);
((SwissArmyTileSet)target).setWidths(widths);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/heights", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] heights = StringUtil.parseIntArray(bodyText);
((SwissArmyTileSet)target).setHeights(heights);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/tileCounts",
new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] tileCounts = StringUtil.parseIntArray(bodyText);
((SwissArmyTileSet)target).setTileCounts(tileCounts);
}
});
digester.addRule(
_prefix + TILESET_PATH + "/offsetPos", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] values = StringUtil.parseIntArray(bodyText);
SwissArmyTileSet starget = (SwissArmyTileSet)target;
if (values.length == 2) {
starget.setOffsetPos(new Point(values[0], values[1]));
} else {
Log.warning("Invalid 'offsetPos' definition '" +
bodyText + "'.");
}
}
});
digester.addRule(
_prefix + TILESET_PATH + "/gapSize", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] values = StringUtil.parseIntArray(bodyText);
SwissArmyTileSet starget = (SwissArmyTileSet)target;
if (values.length == 2) {
starget.setGapSize(new Dimension(values[0], values[1]));
} else {
Log.warning("Invalid 'gapSize' definition '" +
bodyText + "'.");
}
}
});
}
// documentation inherited
public boolean isValid (Object target)
{
SwissArmyTileSet set = (SwissArmyTileSet)target;
boolean valid = super.isValid(target);
// check for a <widths> element
if (set.getWidths() == null) {
Log.warning("Tile set definition missing valid <widths> " +
"element [set=" + set + "].");
valid = false;
}
// check for a <heights> element
if (set.getHeights() == null) {
Log.warning("Tile set definition missing valid <heights> " +
"element [set=" + set + "].");
valid = false;
}
// check for a <tileCounts> element
if (set.getTileCounts() == null) {
Log.warning("Tile set definition missing valid <tileCounts> " +
"element [set=" + set + "].");
valid = false;
}
return valid;
}
// documentation inherited
protected Class getTileSetClass ()
{
return SwissArmyTileSet.class;
}
}
@@ -0,0 +1,128 @@
//
// $Id: TileSetRuleSet.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools.xml;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.RuleSetBase;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.ValidatedSetNextRule.Validator;
import com.samskivert.xml.ValidatedSetNextRule;
import com.threerings.media.Log;
import com.threerings.media.tile.TileSet;
/**
* The tileset rule set is used to parse the base attributes of a tileset
* instance. Derived classes would extend this and add rules for their own
* special tilesets.
*/
public abstract class TileSetRuleSet
extends RuleSetBase implements Validator
{
/** The component of the digester path that is appended by the tileset
* rule set to match a tileset. This is appended to whatever prefix is
* provided to the tileset rule set to obtain the complete XML path to
* a matched tileset. */
public static final String TILESET_PATH = "/tileset";
/**
* Instructs the tileset rule set to match tilesets with the supplied
* prefix. For example, passing a prefix of
* <code>tilesets.objectsets</code> will match tilesets in the
* following XML file:
*
* <pre>
* &lt;tilesets&gt;
* &lt;objectsets&gt;
* &lt;tileset&gt;
* // ...
* &lt;/tileset&gt;
* &lt;/objectsets&gt;
* &lt;/tilesets&gt;
* </pre>
*
* This must be called before adding the ruleset to a digester.
*/
public void setPrefix (String prefix)
{
_prefix = prefix;
}
/**
* Adds the necessary rules to the digester to parse our tilesets.
* Derived classes should override this method, being sure to call the
* superclass method and then adding their own rule instances (which
* should register themselves relative to the <code>_prefix</code>
* member).
*/
public void addRuleInstances (Digester digester)
{
// this creates the appropriate instance when we encounter a
// <tileset> tag
digester.addObjectCreate(_prefix + TILESET_PATH,
getTileSetClass().getName());
// grab the name attribute from the <tileset> tag
digester.addSetProperties(_prefix + TILESET_PATH);
// grab the image path from an element
digester.addCallMethod(
_prefix + TILESET_PATH + "/imagePath", "setImagePath", 0);
}
/**
* The ruleset can be provided to a {@link ValidatedSetNextRule} to
* ensure that the tileset was fully parsed before doing something
* with it.
*/
public boolean isValid (Object target)
{
TileSet set = (TileSet)target;
boolean valid = true;
// check for the 'name' attribute
if (StringUtil.isBlank(set.getName())) {
Log.warning("Tile set definition missing 'name' attribute " +
"[set=" + set + "].");
valid = false;
}
// check for an <imagePath> element
if (StringUtil.isBlank(set.getImagePath())) {
Log.warning("Tile set definition missing <imagePath> element " +
"[set=" + set + "].");
valid = false;
}
return valid;
}
/**
* A tileset rule set will create tilesets of a particular class,
* which must be provided by the derived class via this method.
*/
protected abstract Class getTileSetClass ();
/** The prefix at which me match our tilesets. */
protected String _prefix;
}
@@ -0,0 +1,86 @@
//
// $Id: UniformTileSetRuleSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools.xml;
import org.apache.commons.digester.Digester;
import com.threerings.media.Log;
import com.threerings.media.tile.UniformTileSet;
/**
* Parses {@link UniformTileSet} instances from a tileset description. A
* uniform tileset description looks like so:
*
* <pre>
* &lt;tileset name="Sample Uniform Tileset"&gt;
* &lt;imagePath&gt;path/to/image.png&lt;/imagePath&gt;
* &lt;!-- the width of each tile in pixels --&gt;
* &lt;width&gt;64&lt;/width&gt;
* &lt;!-- the height of each tile in pixels --&gt;
* &lt;height&gt;48&lt;/height&gt;
* &lt;/tileset&gt;
* </pre>
*/
public class UniformTileSetRuleSet extends TileSetRuleSet
{
// documentation inherited
public void addRuleInstances (Digester digester)
{
super.addRuleInstances(digester);
digester.addCallMethod(
_prefix + TILESET_PATH + "/width", "setWidth", 0,
new Class[] { java.lang.Integer.TYPE });
digester.addCallMethod(
_prefix + TILESET_PATH + "/height", "setHeight", 0,
new Class[] { java.lang.Integer.TYPE });
}
// documentation inherited
public boolean isValid (Object target)
{
UniformTileSet set = (UniformTileSet)target;
boolean valid = super.isValid(target);
// check for a <width> element
if (set.getWidth() == 0) {
Log.warning("Tile set definition missing valid <width> " +
"element [set=" + set + "].");
valid = false;
}
// check for a <height> element
if (set.getHeight() == 0) {
Log.warning("Tile set definition missing valid <height> " +
"element [set=" + set + "].");
valid = false;
}
return valid;
}
// documentation inherited
protected Class getTileSetClass ()
{
return UniformTileSet.class;
}
}
@@ -0,0 +1,172 @@
//
// $Id: XMLTileSetParser.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.tools.xml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.commons.digester.Digester;
import org.xml.sax.SAXException;
import com.samskivert.util.ConfigUtil;
import com.samskivert.xml.ValidatedSetNextRule;
import com.threerings.media.Log;
import com.threerings.media.tile.TileSet;
/**
* Parse an XML tileset description file and construct tileset objects for
* each valid description. Does not currently perform validation on the
* input XML stream, though the parsing code assumes the XML document is
* well-formed.
*/
public class XMLTileSetParser
{
/**
* Constructs an xml tile set parser.
*/
public XMLTileSetParser ()
{
// create our digester
_digester = new Digester();
}
/**
* Adds a ruleset to be used when parsing tiles. This should be an
* instance of a class derived from {@link TileSetRuleSet}. The prefix
* will be used to configure the ruleset so that it matches elements
* at a particular point in the XML hierarchy. For example:
*
* <pre>
* _parser.addRuleSet("tilesets", new UniformTileSetRuleSet());
* </pre>
*/
public void addRuleSet (String prefix, TileSetRuleSet ruleset)
{
// configure the ruleset with the appropriate prefix
ruleset.setPrefix(prefix);
// and have it set itself up with the digester
_digester.addRuleSet(ruleset);
// add a set next rule which will put tilesets with this prefix
// into the array list that'll be on the top of the stack
_digester.addRule(prefix + TileSetRuleSet.TILESET_PATH,
new ValidatedSetNextRule("add", Object.class,
ruleset));
}
/**
* Loads all of the tilesets specified in the supplied XML tileset
* description file and places them into the supplied hashmap indexed
* by tileset name. This method is not reentrant, so don't go calling
* it from multiple threads.
*
* @param path a path, relative to the classpath, at which the tileset
* definition file can be found.
* @param tilesets the hashmap into which the tilesets will be placed,
* indexed by tileset name.
*/
public void loadTileSets (String path, HashMap tilesets)
throws IOException
{
// get an input stream for this XML file
InputStream is = ConfigUtil.getStream(path);
if (is == null) {
String errmsg = "Can't load tileset description file from " +
"classpath [path=" + path + "].";
throw new FileNotFoundException(errmsg);
}
// load up the tilesets
loadTileSets(is, tilesets);
}
/**
* Loads all of the tilesets specified in the supplied XML tileset
* description file and places them into the supplied hashmap indexed
* by tileset name. This method is not reentrant, so don't go calling
* it from multiple threads.
*
* @param file the file in which the tileset definition file can be
* found.
* @param tilesets the hashmap into which the tilesets will be placed,
* indexed by tileset name.
*/
public void loadTileSets (File file, HashMap tilesets)
throws IOException
{
// load up the tilesets
loadTileSets(new FileInputStream(file), tilesets);
}
/**
* Loads all of the tilesets specified in the supplied XML tileset
* description file and places them into the supplied hashmap indexed
* by tileset name. This method is not reentrant, so don't go calling
* it from multiple threads.
*
* @param source an input stream from which the tileset definition
* file can be read.
* @param tilesets the hashmap into which the tilesets will be placed,
* indexed by tileset name.
*/
public void loadTileSets (InputStream source, HashMap tilesets)
throws IOException
{
// stick an array list on the top of the stack for collecting
// parsed tilesets
ArrayList setlist = new ArrayList();
_digester.push(setlist);
// now fire up the digester to parse the stream
try {
_digester.parse(source);
} catch (SAXException saxe) {
Log.warning("Exception parsing tile set descriptions " +
"[error=" + saxe + "].");
Log.logStackTrace(saxe);
}
// stick the tilesets from the list into the hashtable
for (int i = 0; i < setlist.size(); i++) {
TileSet set = (TileSet)setlist.get(i);
if (set.getName() == null) {
Log.warning("Tileset did not receive name during " +
"parsing process [set=" + set + "].");
} else {
tilesets.put(set.getName(), set);
}
}
// and clear out the list for next time
setlist.clear();
}
/** Our XML digester. */
protected Digester _digester;
}
@@ -0,0 +1,146 @@
//
// $Id: TileSetTrimmer.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.tile.util;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.RasterFormatException;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.OutputStream;
import com.threerings.media.image.FastImageIO;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.TileSet;
/**
* Contains routines for trimming the images from an existing tileset
* which means that each tile is converted to an image that contains the
* smallest rectangular region of the original image that contains all
* non-transparent pixels. These trimmed images are then written out to a
* single image, packed together left to right.
*/
public class TileSetTrimmer
{
/**
* Used to communicate the metrics of the trimmed tiles back to the
* caller so that they can do what they like with them.
*/
public static interface TrimMetricsReceiver
{
/**
* Called for each trimmed tile.
*
* @param tileIndex the index of the tile in the original tileset.
* @param imageX the x offset into the newly created tileset image
* of the trimmed image data.
* @param imageY the y offset into the newly created tileset image
* of the trimmed image data.
* @param trimX the x offset into the untrimmed tile image where
* the trimmed data begins.
* @param trimY the y offset into the untrimmed tile image where
* the trimmed data begins.
* @param trimWidth the width of the trimmed tile image.
* @param trimHeight the height of the trimmed tile image.
*/
public void trimmedTile (int tileIndex, int imageX, int imageY,
int trimX, int trimY,
int trimWidth, int trimHeight);
}
/**
* Generates a trimmed tileset image from the supplied source
* tileset. The source tileset must be configured with an image
* provider so that the tile images can be obtained. The tile images
* will be trimmed and a new tileset image generated and written to
* the <code>destImage</code> output stream argument.
*
* @param source the source tileset.
* @param destImage an output stream to which the new trimmed image
* will be written.
* @param tmr a callback object that will be used to inform the caller
* of the trimmed tile metrics.
*/
public static void trimTileSet (
TileSet source, OutputStream destImage, TrimMetricsReceiver tmr)
throws IOException
{
int tcount = source.getTileCount();
BufferedImage[] timgs = new BufferedImage[tcount];
// these will contain the bounds of the trimmed image in the
// coordinate system defined by the untrimmed image
Rectangle[] tbounds = new Rectangle[tcount];
// compute some tile metrics
int nextx = 0, maxy = 0;
for (int ii = 0; ii < tcount; ii++) {
// extract the image from the original tileset
try {
timgs[ii] = source.getRawTileImage(ii);
} catch (RasterFormatException rfe) {
throw new IOException("Failed to get tile image " +
"[tidx=" + ii + ", tset=" + source +
", rfe=" + rfe + "].");
}
// figure out how tightly we can trim it
tbounds[ii] = new Rectangle();
ImageUtil.computeTrimmedBounds(timgs[ii], tbounds[ii]);
// let our caller know what we did
tmr.trimmedTile(ii, nextx, 0, tbounds[ii].x, tbounds[ii].y,
tbounds[ii].width, tbounds[ii].height);
// adjust the new tileset image dimensions
maxy = Math.max(maxy, tbounds[ii].height);
nextx += tbounds[ii].width;
}
// create the new tileset image
BufferedImage image = null;
try {
image = ImageUtil.createCompatibleImage(
(BufferedImage)source.getRawTileSetImage(), nextx, maxy);
} catch (RasterFormatException rfe) {
throw new IOException("Failed to create trimmed tileset image " +
"[wid=" + nextx + ", hei=" + maxy +
", tset=" + source + ", rfe=" + rfe + "].");
}
// copy the tile data
WritableRaster drast = image.getRaster();
int xoff = 0;
for (int ii = 0; ii < tcount; ii++) {
Rectangle tb = tbounds[ii];
Raster srast = timgs[ii].getRaster().createChild(
tb.x, tb.y, tb.width, tb.height, 0, 0, null);
drast.setRect(xoff, 0, srast);
xoff += tb.width;
}
// write out trimmed image
FastImageIO.write(image, destImage);
}
}