
com.threerings.miso.tile.AutoFringer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nenya Show documentation
Show all versions of nenya Show documentation
Facilities for making networked multiplayer games.
The newest version!
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// 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.miso.tile;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import com.google.common.collect.Lists;
import com.samskivert.util.CheapIntMap;
import com.samskivert.util.QuickSort;
import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.miso.data.MisoSceneModel;
import static com.threerings.miso.Log.log;
/**
* Automatically fringes a scene according to the rules in the supplied fringe configuration.
*/
public class AutoFringer
{
public static class FringeTile extends BaseTile
{
public FringeTile (long[] fringeId, boolean passable) {
setPassable(passable);
_fringeId = fringeId;
}
@Override
public boolean equals (Object obj) {
if (!(obj instanceof FringeTile)) {
return false;
}
FringeTile fObj = (FringeTile)obj;
return _passable == fObj._passable && Arrays.equals(_fringeId, fObj._fringeId);
}
@Override
public int hashCode () {
return Arrays.hashCode(_fringeId);
}
/** The fringe keys of the tiles that went into this tile in the order they were drawn. */
protected long[] _fringeId;
}
/**
* Constructs an instance that will fringe according to the rules in the supplied fringe
* configuration.
*/
public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr, TileManager tmgr)
{
_fringeconf = fringeconf;
_imgr = imgr;
_tmgr = tmgr;
}
/**
* Returns the fringe configuration used by this fringer.
*/
public FringeConfiguration getFringeConf ()
{
return _fringeconf;
}
/**
* Compute and return the fringe tile to be inserted at the specified location.
*/
public BaseTile getFringeTile (MisoSceneModel scene, int col, int row,
Map> fringes, Map masks)
{
// get the tileset id of the base tile we are considering
int underset = adjustTileSetId(scene.getBaseTileId(col, row) >> 16);
// start with a clean temporary fringer map
_fringers.clear();
boolean passable = true;
// 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 tileset for this tile
int btid = scene.getBaseTileId(x, y);
int baseset = adjustTileSetId((btid <= 0) ?
scene.getDefaultBaseTileSet() : (btid >> 16));
// determine if it fringes on our tile
int pri = _fringeconf.fringesOn(baseset, underset);
if (pri == -1) {
continue;
}
FringerRec fringer = (FringerRec)_fringers.get(baseset);
if (fringer == null) {
fringer = new FringerRec(baseset, pri);
_fringers.put(baseset, fringer);
}
// now turn on the appropriate fringebits
fringer.bits |= FLAGMATRIX[y - row + 1][x - col + 1];
// See if a tile that fringes on us kills our passability,
// but don't count the default base tile against us, as
// we allow users to splash in the water.
if (passable && (btid > 0)) {
try {
BaseTile bt = (BaseTile)_tmgr.getTile(btid);
passable = bt.isPassable();
} catch (NoSuchTileSetException nstse) {
log.warning("Autofringer couldn't find a base set while attempting to " +
"figure passability", nstse);
}
}
}
}
// if nothing fringed, we're done
int numfringers = _fringers.size();
if (numfringers == 0) {
return null;
}
// otherwise compose a FringeTile from the specified fringes
FringerRec[] frecs = new FringerRec[numfringers];
for (int ii = 0, pp = 0; ii < 16; ii++) {
FringerRec rec = (FringerRec)_fringers.getValue(ii);
if (rec != null) {
frecs[pp++] = rec;
}
}
return composeFringeTile(frecs, fringes, TileUtil.getTileHash(col, row), passable, masks);
}
/**
* Compose a FringeTile out of the various fringe images needed.
*/
protected FringeTile composeFringeTile (FringerRec[] fringers,
Map> fringes, int hashValue, boolean passable,
Map masks)
{
// sort the array so that higher priority fringers get drawn first
QuickSort.sort(fringers);
// Generate an identifier for the fringe tile being created as an array of the keys of its
// component tiles in the order they'll be drawn in the fringe tile.
List keys = Lists.newArrayList();
for (FringerRec fringer : fringers) {
int[] indexes = getFringeIndexes(fringer.bits);
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(
fringer.baseset, hashValue);
int fringeset = tsr.fringe_tsid;
for (int index : indexes) {
// Add a key for this tile as a long containing its base tile, the fringe set it's
// working with and the index used in that set.
keys.add((((long)fringer.baseset) << 32) + (fringeset << 16) + index);
}
}
long[] fringeId = new long[keys.size()];
for (int ii = 0; ii < fringeId.length; ii++) {
fringeId[ii] = keys.get(ii);
}
FringeTile frTile = new FringeTile(fringeId, passable);
// If the fringes map contains something with the same fringe identifier, this will pull
// it out and we can use it instead.
WeakReference result = fringes.get(frTile);
if (result != null) {
FringeTile fringe = result.get();
if (fringe != null) {
return fringe;
}
}
// There's no fringe with he same identifier, so we need to create the tile.
BufferedImage img = null;
for (FringerRec fringer : fringers) {
int[] indexes = getFringeIndexes(fringer.bits);
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(
fringer.baseset, hashValue);
for (int index : indexes) {
try {
img = getTileImage(img, tsr, fringer.baseset, index, hashValue, masks);
} catch (NoSuchTileSetException nstse) {
log.warning("Autofringer couldn't find a needed tileset", nstse);
}
}
}
frTile.setImage(new BufferedMirage(img));
fringes.put(frTile, new WeakReference(frTile));
return frTile;
}
/**
* Retrieve or compose an image for the specified fringe.
*/
protected BufferedImage getTileImage (BufferedImage img,
FringeConfiguration.FringeTileSetRecord tsr, int baseset, int index, int hashValue,
Map masks)
throws NoSuchTileSetException
{
int fringeset = tsr.fringe_tsid;
TileSet fset = _tmgr.getTileSet(fringeset);
if (!tsr.mask) {
// oh good, this is easy
Tile stamp = fset.getTile(index);
return stampTileImage(stamp, img, stamp.getWidth(), stamp.getHeight());
}
// otherwise, it's a mask..
Long maskkey = Long.valueOf((((long)baseset) << 32) + (fringeset << 16) + index);
BufferedImage mask = masks.get(maskkey);
if (mask == null) {
BufferedImage fsrc = _tmgr.getTileSet(fringeset).getRawTileImage(index);
BufferedImage bsrc = _tmgr.getTileSet(baseset).getRawTileImage(0);
mask = ImageUtil.composeMaskedImage(_imgr, fsrc, bsrc);
masks.put(maskkey, mask);
}
return stampTileImage(mask, img, mask.getWidth(null), mask.getHeight(null));
}
/** Helper function for {@link #getTileImage}. */
protected BufferedImage stampTileImage (Object stamp, BufferedImage ftimg, int width,
int height)
{
// create the target image if necessary
if (ftimg == null) {
ftimg = _imgr.createImage(width, height, Transparency.BITMASK);
}
Graphics2D gfx = (Graphics2D)ftimg.getGraphics();
try {
if (stamp instanceof Tile) {
((Tile)stamp).paint(gfx, 0, 0);
} else {
gfx.drawImage((BufferedImage)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 = Lists.newArrayList();
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(index);
}
weebits = 0;
}
}
if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(index);
}
}
int[] ret = new int[indexes.size()];
for (int ii = 0; ii < ret.length; ii++) {
ret[ii] = indexes.get(ii);
}
return ret;
}
/**
* Allow subclasses to apply arbitrary modifications to tileset ids for whatever nefarious
* purposes they may have.
*/
protected int adjustTileSetId (int tileSetId)
{
// by default, nothing.
return tileSetId;
}
/**
* A record for holding information about a particular fringe as we're computing what it will
* look like.
*/
static protected class FringerRec
implements Comparable
{
int baseset;
int priority;
int bits;
public FringerRec (int base, int pri)
{
baseset = base;
priority = pri;
}
public int compareTo (FringerRec o)
{
return priority - o.priority;
}
@Override
public String toString ()
{
return "[base=" + baseset + ", pri=" + priority + ", bits="
+ Integer.toString(bits, 16) + "]";
}
}
// 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 above 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 ImageManager _imgr;
protected TileManager _tmgr;
protected FringeConfiguration _fringeconf;
protected CheapIntMap _fringers = new CheapIntMap(16);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy