diff --git a/src/java/com/threerings/jme/tile/FringeConfiguration.java b/src/java/com/threerings/jme/tile/FringeConfiguration.java new file mode 100644 index 000000000..a1240bee9 --- /dev/null +++ b/src/java/com/threerings/jme/tile/FringeConfiguration.java @@ -0,0 +1,160 @@ +// +// $Id: FringeConfiguration.java 3403 2005-03-14 23:58:02Z 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.jme.tile; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; + +import com.samskivert.util.StringUtil; + +import com.threerings.jme.Log; + +/** + * Used to manage data about which tiles fringe on which others and how + * they fringe. + */ +public class FringeConfiguration implements Serializable +{ + /** Contains information on a type of tile and all of the fringe + * records associated with it. */ + public static class TileRecord implements Serializable + { + /** The type of tile to which this applies. */ + public String type; + + /** The fringe priority of this type. */ + public int priority; + + /** A list of the fringe records that can be used for fringing. */ + public ArrayList fringes = new ArrayList(); + + /** Used when parsing from an XML definition. */ + public void addFringe (FringeRecord record) + { + if (record.isValid()) { + fringes.add(record); + } else { + Log.warning("Not adding invalid fringe record [tile=" + this + + ", fringe=" + record + "]."); + } + } + + /** Did everything parse well? */ + public boolean isValid () + { + return ((type != null) && (priority > 0)); + } + + /** Generates a string representation of this instance. */ + public String toString () + { + return "[type=" + type + ", priority=" + priority + + ", fringes=" + StringUtil.toString(fringes) + "]"; + } + + /** Increase this value when object's serialized state is impacted + * by a class change (modification of fields, inheritance). */ + private static final long serialVersionUID = 1; + } + + /** Used to parse the type fringe definitions. */ + public static class FringeRecord implements Serializable + { + /** The name of the fringe tileset image. */ + public String name; + + /** Is this a mask? */ + public boolean mask; + + /** Did everything parse well? */ + public boolean isValid () + { + return (name != null); + } + + /** Generates a string representation of this instance. */ + public String toString () + { + return "[name=" + name + ", mask=" + mask + "]"; + } + + /** Increase this value when object's serialized state is impacted + * by a class change (modification of fields, inheritance). */ + private static final long serialVersionUID = 1; + } + + /** + * Adds a parsed fringe record to this instance. This is used when + * parsing the fringe records from xml. + */ + public void addTileRecord (TileRecord record) + { + if (!record.isValid()) { + _trecs.put(record.type, record); + } else { + Log.warning("Refusing to add invalid tile record " + + "[tile=" + record + "]."); + } + } + + /** + * If the first type fringes upon the second, return the fringe + * priority of the first type, otherwise return -1. + */ + public int fringesOn (String fringer, String fringed) + { + TileRecord f1 = (TileRecord)_trecs.get(fringer); + + // we better have a fringe record for the fringer + if (null != f1) { + // it had better have some types defined + if (f1.fringes.size() > 0) { + TileRecord f2 = (TileRecord)_trecs.get(fringed); + // and we only fringe if fringed doesn't have a fringe + // record or has a lower priority + if ((null == f2) || (f1.priority > f2.priority)) { + return f1.priority; + } + } + } + + return -1; + } + + /** + * Get a random FringeRecord from amongst the ones listed for the + * specified type. + */ + public FringeRecord getFringe (String type, int hashValue) + { + TileRecord t = (TileRecord)_trecs.get(type); + return (FringeRecord)t.fringes.get(hashValue % t.fringes.size()); + } + + /** The mapping from tile type to tile record. */ + protected HashMap _trecs = new HashMap(); + + /** Increase this value when object's serialized state is impacted by + * a class change (modification of fields, inheritance). */ + private static final long serialVersionUID = 1; +} diff --git a/src/java/com/threerings/jme/tile/TileFringer.java b/src/java/com/threerings/jme/tile/TileFringer.java new file mode 100644 index 000000000..a76213119 --- /dev/null +++ b/src/java/com/threerings/jme/tile/TileFringer.java @@ -0,0 +1,402 @@ +// +// $Id: TileFringer.java 3177 2004-10-28 17:49:02Z 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.jme.tile; + +import java.awt.Graphics2D; +import java.awt.Transparency; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashMap; + +import com.samskivert.util.QuickSort; + +import com.threerings.media.image.ImageUtil; +import com.threerings.media.tile.TileUtil; + +import com.threerings.jme.Log; + +/** + * Computes fringe tile images according to the rules in an associated + * fringe configuration. + */ +public class TileFringer +{ + public static interface TileSource + { + /** Returns the type of tile at the specified coordinates or -1 if + * there is no tile at this coordinate. */ + public String getTileType (int x, int y); + + /** Returns the tile type to use when a coordinate has no tile. */ + public String getDefaultType (); + } + + public static interface ImageSource + { + /** Creates a blank image into which various fringe images will be + * composited. */ + public BufferedImage createImage (int width, int height); + + /** Returns the source image for a tile of the specified type. + * This can be randomly selected and change from call to call. */ + public BufferedImage getTileSource (String type); + + /** Returns the named fringe source image (one long strip). */ + public BufferedImage getFringeSource (String name); + } + + /** + * Creates a fringer that will fringe according to the rules in the + * supplied configuration. + */ + public TileFringer (FringeConfiguration config, ImageSource isrc) + { + _config = config; + _isrc = isrc; + } + + /** + * Compute and return the fringe tile to be inserted at the specified + * location. + * + * @param masks used to cache intermediate images of tiles cut out + * using a fringe mask. + */ + public BufferedImage getFringeTile ( + TileSource tiles, int col, int row, HashMap masks) + { + // get the type of the tile we are considering + String baseType = tiles.getTileType(col, row); + + // start with an empty fringer list + FringerRec fringers = null; + + // walk through our influence tiles + for (int y = row - 1, maxy = row + 2; y < maxy; y++) { + for (int x = col - 1, maxx = col + 2; x < maxx; x++) { + // we sensibly do not consider ourselves + if ((x == col) && (y == row)) { + continue; + } + + // determine the type of our fringing neighbor + String fringerType = tiles.getTileType(x, y); + if (fringerType == null) { + fringerType = tiles.getDefaultType(); + } + + // determine if it fringes on our tile + int pri = _config.fringesOn(fringerType, baseType); + if (pri == -1) { + continue; + } + + FringerRec fringer = (fringers == null) ? + null : fringers.find(fringerType); + if (fringer == null) { + fringers = new FringerRec(fringerType, pri, fringers); + } + + // now turn on the appropriate fringebits + fringer.bits |= FLAGMATRIX[y - row + 1][x - col + 1]; + } + } + + // if nothing fringed, we're done + if (fringers == null) { + return null; + } + + // otherwise compose a fringe tile from the specified fringes + return composeFringeTile( + fringers.toArray(), masks, TileUtil.getTileHash(col, row)); + } + + /** + * Compose a fringe tile out of the various fringe images needed. + */ + protected BufferedImage composeFringeTile ( + FringerRec[] fringers, HashMap masks, int hashValue) + { + // sort the array so that higher priority fringers get drawn first + QuickSort.sort(fringers); + + BufferedImage ftimg = null; + for (int ii = 0; ii < fringers.length; ii++) { + int[] indexes = getFringeIndexes(fringers[ii].bits); + for (int jj = 0; jj < indexes.length; jj++) { + ftimg = getTileImage(ftimg, fringers[ii].fringerType, + indexes[jj], masks, hashValue); + } + } + + return ftimg; + } + + /** + * Retrieve or compose an image for the specified fringe. + */ + protected BufferedImage getTileImage ( + BufferedImage ftimg, String fringerType, int index, + HashMap masks, int hashValue) + { + FringeConfiguration.FringeRecord frec = + _config.getFringe(fringerType, hashValue); + BufferedImage fsimg = (frec == null) ? null : + _isrc.getFringeSource(frec.name); + if (fsimg == null) { + Log.warning("Missing fringe source image [type=" + fringerType + + ", hash=" + hashValue + ", frec=" + frec + "]."); + return ftimg; + } + + if (!frec.mask) { + // this is a non-mask image so just use the data from the + // fringe source image directly + BufferedImage stamp = getSubimage(fsimg, index); + return stampTileImage(stamp, ftimg, stamp.getWidth(), + stamp.getHeight()); + } + + // otherwise, it's a mask; look for it in the cache + String maskkey = fringerType + ":" + frec.name + ":" + index; + BufferedImage mimg = (BufferedImage)masks.get(maskkey); + if (mimg == null) { + BufferedImage fsrc = getSubimage(fsimg, index); + BufferedImage bsrc = _isrc.getTileSource(fringerType); + mimg = ImageUtil.composeMaskedImage(null, fsrc, bsrc); // TODO + masks.put(maskkey, mimg); + } + return stampTileImage(mimg, ftimg, mimg.getWidth(null), + mimg.getHeight(null)); + } + + /** + * Returns the indexth tile image from the supplied + * source image. The source image is assumed to be a single strip of + * tile images, each with equal width and height. + */ + protected BufferedImage getSubimage (BufferedImage source, int index) + { + int size = source.getHeight(), x = size * index; + return source.getSubimage(x, 0, size, size); + } + + /** Helper function for {@link #getTileImage}. */ + protected BufferedImage stampTileImage ( + BufferedImage stamp, BufferedImage ftimg, int width, int height) + { + // create the target image if necessary + if (ftimg == null) { + ftimg = _isrc.createImage(width, height); + } + Graphics2D gfx = (Graphics2D)ftimg.getGraphics(); + try { + gfx.drawImage(stamp, 0, 0, null); + } finally { + gfx.dispose(); + } + return ftimg; + } + + /** + * Get the fringe index specified by the fringebits. If no index + * is available, try breaking down the bits into contiguous regions of + * bits and look for indexes for those. + */ + protected int[] getFringeIndexes (int bits) + { + int index = BITS_TO_INDEX[bits]; + if (index != -1) { + int[] ret = new int[1]; + ret[0] = index; + return ret; + } + + // otherwise, split the bits into contiguous components + + // look for a zero and start our first split + int start = 0; + while ((((1 << start) & bits) != 0) && (start < NUM_FRINGEBITS)) { + start++; + } + + if (start == NUM_FRINGEBITS) { + // we never found an empty fringebit, and since index (above) + // was already -1, we have no fringe tile for these bits.. sad. + return new int[0]; + } + + ArrayList indexes = new ArrayList(); + int weebits = 0; + for (int ii=(start + 1) % NUM_FRINGEBITS; ii != start; + ii = (ii + 1) % NUM_FRINGEBITS) { + + if (((1 << ii) & bits) != 0) { + weebits |= (1 << ii); + } else if (weebits != 0) { + index = BITS_TO_INDEX[weebits]; + if (index != -1) { + indexes.add(new Integer(index)); + } + weebits = 0; + } + } + if (weebits != 0) { + index = BITS_TO_INDEX[weebits]; + if (index != -1) { + indexes.add(new Integer(index)); + } + } + + int[] ret = new int[indexes.size()]; + for (int ii=0; ii < ret.length; ii++) { + ret[ii] = ((Integer) indexes.get(ii)).intValue(); + } + return ret; + } + + /** A record for holding information about a particular fringe as + * we're computing what it will look like. */ + protected static class FringerRec implements Comparable + { + public String fringerType; + public int priority; + public int bits; + public FringerRec next; + + public FringerRec (String type, int pri, FringerRec next) { + fringerType = type; + priority = pri; + this.next = next; + } + + public FringerRec find (String type) + { + if (fringerType.equals(type)) { + return this; + } else if (next != null) { + return next.find(type); + } else { + return null; + } + } + + public FringerRec[] toArray () + { + return toArray(0); + } + + public int compareTo (Object o) { + return priority - ((FringerRec) o).priority; + } + + public String toString () { + return "[type=" + fringerType + ", pri=" + priority + + ", bits=" + Integer.toString(bits, 16) + "]"; + } + + protected FringerRec[] toArray (int index) + { + FringerRec[] array; + if (next == null) { + array = new FringerRec[index+1]; + } else { + array = next.toArray(index+1); + } + array[index] = this; + return array; + } + } + + // fringe bits: see docs/miso/fringebits.png + protected static final int NORTH = 1 << 0; + protected static final int NORTHEAST = 1 << 1; + protected static final int EAST = 1 << 2; + protected static final int SOUTHEAST = 1 << 3; + protected static final int SOUTH = 1 << 4; + protected static final int SOUTHWEST = 1 << 5; + protected static final int WEST = 1 << 6; + protected static final int NORTHWEST = 1 << 7; + + protected static final int NUM_FRINGEBITS = 8; + + /** A matrix mapping adjacent tiles to which fringe bits they affect. + * (x and y are offset by +1, since we can't have -1 as an array + * index) again, see docs/miso/fringebits.png */ + protected static final int[][] FLAGMATRIX = { + { NORTHEAST, (NORTHEAST | EAST | SOUTHEAST), SOUTHEAST }, + { (NORTHWEST | NORTH | NORTHEAST), 0, (SOUTHEAST | SOUTH | SOUTHWEST) }, + { NORTHWEST, (NORTHWEST | WEST | SOUTHWEST), SOUTHWEST } + }; + + /** The fringe tiles we use. These are the 17 possible tiles made up + * of continuous fringebits sections. Huh? see + * docs/miso/fringebits.png */ + protected static final int[] FRINGETILES = { + SOUTHEAST, + SOUTHWEST | SOUTH | SOUTHEAST, + SOUTHWEST, + NORTHEAST | EAST | SOUTHEAST, + NORTHWEST | WEST | SOUTHWEST, + NORTHEAST, + NORTHWEST | NORTH | NORTHEAST, + NORTHWEST, + + SOUTHWEST | WEST | NORTHWEST | NORTH | NORTHEAST, + NORTHWEST | NORTH | NORTHEAST | EAST | SOUTHEAST, + NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST, + SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST, + + NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST, + SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST, + SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST, + NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST, + + // all the directions! + NORTH | NORTHEAST | EAST | SOUTHEAST | SOUTH | SOUTHWEST | + WEST | NORTHWEST + }; + + /** A reverse map of the {@link #FRINGETILES} array, for quickly + * looking up which tile we want. */ + protected static final int[] BITS_TO_INDEX; + + // Construct the BITS_TO_INDEX array. + static { + int num = (1 << NUM_FRINGEBITS); + BITS_TO_INDEX = new int[num]; + + // first clear everything to -1 (meaning there is no tile defined) + for (int ii=0; ii < num; ii++) { + BITS_TO_INDEX[ii] = -1; + } + + // then fill in with the defined tiles. + for (int ii=0; ii < FRINGETILES.length; ii++) { + BITS_TO_INDEX[FRINGETILES[ii]] = ii; + } + } + + protected ImageSource _isrc; + protected FringeConfiguration _config; +} diff --git a/src/java/com/threerings/jme/tile/tools/CompileFringeConfigurationTask.java b/src/java/com/threerings/jme/tile/tools/CompileFringeConfigurationTask.java new file mode 100644 index 000000000..6e220862f --- /dev/null +++ b/src/java/com/threerings/jme/tile/tools/CompileFringeConfigurationTask.java @@ -0,0 +1,76 @@ +// +// $Id: CompileFringeConfigurationTask.java 3099 2004-08-27 02:21:06Z 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.jme.tile.tools; + +import java.io.File; +import java.io.Serializable; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +import com.samskivert.io.PersistenceException; +import com.threerings.util.CompiledConfig; + +import com.threerings.jme.tile.tools.xml.FringeConfigurationParser; + +/** + * Compiles a fringe configuration from XML format into binary format. + */ +public class CompileFringeConfigurationTask extends Task +{ + public void setConfig (File config) + { + _config = config; + } + + public void setTarget (File target) + { + _target = target; + } + + public void execute () throws BuildException + { + // make sure the config file exists + if (!_config.exists()) { + throw new BuildException( + "Fringe configuration file not found [path=" + _config + "]."); + } + + FringeConfigurationParser parser = new FringeConfigurationParser(); + Serializable config; + try { + config = parser.parseConfig(_config); + } catch (Exception e) { + throw new BuildException("Failure parsing fringe config", e); + } + + try { + // and write it on out + CompiledConfig.saveConfig(_target, config); + } catch (Exception e) { + throw new BuildException("Failure writing serialized config", e); + } + } + + protected File _config; + protected File _target; +} diff --git a/src/java/com/threerings/jme/tile/tools/xml/FringeConfigurationParser.java b/src/java/com/threerings/jme/tile/tools/xml/FringeConfigurationParser.java new file mode 100644 index 000000000..a769ef5ce --- /dev/null +++ b/src/java/com/threerings/jme/tile/tools/xml/FringeConfigurationParser.java @@ -0,0 +1,84 @@ +// +// $Id: FringeConfigurationParser.java 3254 2004-11-30 20:03:47Z 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.jme.tile.tools.xml; + +import java.io.Serializable; + +import org.apache.commons.digester.Digester; + +import com.samskivert.util.StringUtil; +import com.samskivert.xml.SetPropertyFieldsRule; + +import com.threerings.tools.xml.CompiledConfigParser; + +import com.threerings.jme.Log; +import com.threerings.jme.tile.FringeConfiguration.TileRecord; +import com.threerings.jme.tile.FringeConfiguration.FringeRecord; +import com.threerings.jme.tile.FringeConfiguration; + +/** + * Parses fringe config definitions, which look like so (with angle + * brackets instead of square): + *
+ * [fringe]
+ *   [tile type="water" priority="100"]
+ *      [fringe name="water_fringe_1"/]
+ *      [fringe name="water_fringe_2"/]
+ *      [fringe name="water_fringe_3"/]
+ *   [/tile]
+ *   [tile type="dirt" priority="10"]
+ *      [fringe name="dirt_fringe_1" mask="true"/]
+ *   [/tile]
+ *   [tile type="cobble" priority="100"]
+ *      [fringe name="cobble_fringe_1" mask="false"/]
+ *   [/tile]
+ * [/fringe]
+ * 
+ */ +public class FringeConfigurationParser extends CompiledConfigParser +{ + // documentation inherited + protected Serializable createConfigObject () + { + return new FringeConfiguration(); + } + + // documentation inherited + protected void addRules (Digester digest) + { + // configure top-level constraints + String prefix = "fringe"; + digest.addRule(prefix, new SetPropertyFieldsRule()); + + // create and configure fringe config instances + prefix += "/tile"; + digest.addObjectCreate(prefix, TileRecord.class.getName()); + digest.addRule(prefix, new SetPropertyFieldsRule()); + digest.addSetNext(prefix, "addTileRecord"); + + // create the fringe type records in each tile record + prefix += "/fringe"; + digest.addObjectCreate(prefix, FringeRecord.class.getName()); + digest.addRule(prefix, new SetPropertyFieldsRule()); + digest.addSetNext(prefix, "addFringe"); + } +}