Files
narya/src/java/com/threerings/miso/client/SceneBlock.java
T
Michael Bayne e628a8e429 Added support for specifying a default tileset for scenes.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2439 542714f4-19e9-0310-aa3c-eee0fc999fb1
2003-04-21 17:08:57 +00:00

374 lines
12 KiB
Java

//
// $Id: SceneBlock.java,v 1.5 2003/04/21 17:08:56 mdb Exp $
package com.threerings.miso.client;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.util.Arrays;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.media.tile.NoSuchTileException;
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.miso.Log;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTile;
import com.threerings.miso.util.MisoUtil;
import com.threerings.miso.util.ObjectSet;
/**
* Contains the base and object tile information on a particular
* rectangular region of a scene.
*/
public class SceneBlock
{
/**
* Creates a scene block and resolves the base and object tiles that
* reside therein.
*/
public SceneBlock (
MisoScenePanel panel, int tx, int ty, int width, int height)
{
_panel = panel;
_bounds = new Rectangle(tx, ty, width, height);
_base = new BaseTile[width*height];
_fringe = new Tile[width*height];
_covered = new boolean[width*height];
// compute our screen-coordinate footprint polygon
_footprint = MisoUtil.getFootprintPolygon(
panel.getSceneMetrics(), tx, ty, width, height);
// resolve our base tiles
MisoSceneModel model = panel.getSceneModel();
for (int yy = 0; yy < height; yy++) {
for (int xx = 0; xx < width; xx++) {
int x = tx + xx, y = ty + yy;
int fqTileId = model.getBaseTileId(x, y);
if (fqTileId <= 0) {
continue;
}
updateBaseTile(fqTileId, x, y);
// if there's no tile here, we don't need no fringe
int tidx = index(x, y);
if (_base[tidx] == null) {
continue;
}
// compute the fringe for this tile
_fringe[tidx] = panel.computeFringeTile(x, y);
}
}
// resolve our objects
ObjectSet set = new ObjectSet();
model.getObjects(_bounds, set);
_objects = new SceneObject[set.size()];
for (int ii = 0; ii < _objects.length; ii++) {
_objects[ii] = new SceneObject(panel, set.get(ii));
}
// resolve our default tileset
int bsetid = model.getDefaultBaseTileSet();
try {
if (bsetid > 0) {
_defset = _panel.getTileManager().getTileSet(bsetid);
}
} catch (Exception e) {
Log.warning("Unable to fetch default base tileset [tsid=" + bsetid +
", error=" + e + "].");
}
}
/**
* Returns the bounds of this block, in tile coordinates.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Returns the screen-coordinate polygon bounding the footprint of
* this block.
*/
public Polygon getFootprint ()
{
return _footprint;
}
/**
* Returns an array of all resolved scene objects in this block.
*/
public SceneObject[] getObjects ()
{
return _objects;
}
/**
* Returns the base tile at the specified coordinates or null if
* there's no tile at said coordinates.
*/
public BaseTile getBaseTile (int tx, int ty)
{
BaseTile tile = _base[index(tx, ty)];
if (tile == null && _defset != null) {
long seed = ((tx^ty) ^ multiplier) & mask;
long hash = (seed * multiplier + addend) & mask;
int tidx = (int)((hash >> 10) % _defset.getTileCount());
tile = (BaseTile)_defset.getTile(tidx);
}
return tile;
}
/**
* Returns the fringe tile at the specified coordinates or null if
* there's no tile at said coordinates.
*/
public Tile getFringeTile (int tx, int ty)
{
return _fringe[index(tx, ty)];
}
/**
* Informs this scene block that the specified base tile has been
* changed.
*/
public void updateBaseTile (int fqTileId, int tx, int ty)
{
String errmsg = null;
// this is a bit magical: we pass the fully qualified tile id to
// the tile manager which loads up from the configured tileset
// repository the appropriate tileset (which should be a
// BaseTileSet) and then extracts the appropriate base tile (the
// index of which is also in the fqTileId)
try {
_base[index(tx, ty)] = (BaseTile)
_panel.getTileManager().getTile(fqTileId);
} catch (ClassCastException cce) {
errmsg = "Scene contains non-base tile in base layer";
} catch (NoSuchTileSetException nste) {
errmsg = "Scene contains non-existent tileset";
} catch (NoSuchTileException nste) {
errmsg = "Scene contains non-existent tile";
}
if (errmsg != null) {
Log.warning(errmsg + " [fqtid=" + fqTileId +
", x=" + tx + ", y=" + ty + "].");
}
}
/**
* Instructs this block to recompute its fringe at the specified
* location.
*/
public void updateFringe (int tx, int ty)
{
int tidx = index(tx, ty);
if (_base[tidx] != null) {
_fringe[tidx] = _panel.computeFringeTile(tx, ty);
}
}
/**
* Adds the supplied object to this block. Coverage is not computed
* for the added object, a subsequent call to {@link #update} will be
* needed.
*/
public void addObject (ObjectInfo info)
{
_objects = (SceneObject[])
ArrayUtil.append(_objects, new SceneObject(_panel, info));
// clear out our neighbors array so that the subsequent update
// causes us to recompute our coverage
Arrays.fill(_neighbors, null);
}
/**
* Removes the specified object from this block. Coverage is not
* recomputed, so a subsequent call to {@link #update} will be needed.
*
* @return true if the object was deleted, false if it was not found
* in our object list.
*/
public boolean deleteObject (ObjectInfo info)
{
int oidx = -1;
for (int ii = 0; ii < _objects.length; ii++) {
if (_objects[ii].info.equals(info)) {
oidx = ii;
break;
}
}
if (oidx == -1) {
return false;
}
_objects = (SceneObject[])ArrayUtil.splice(_objects, oidx, 1);
// clear out our neighbors array so that the subsequent update
// causes us to recompute our coverage
Arrays.fill(_neighbors, null);
return true;
}
/**
* Returns true if the specified traverser can traverse the specified
* tile (which is assumed to be in the bounds of this scene block).
*/
public boolean canTraverse (Object traverser, int tx, int ty)
{
BaseTile base = getBaseTile(tx, ty);
return !_covered[index(tx, ty)] && (base != null && base.isPassable());
}
/**
* Returns a string representation of this instance.
*/
public String toString ()
{
return StringUtil.toString(_bounds) + ":" + _objects.length;
}
/**
* Returns the index into our arrays of the specified tile.
*/
protected final int index (int tx, int ty)
{
// if (!_bounds.contains(tx, ty)) {
// String errmsg = "Coordinates out of bounds: +" + tx + "+" + ty +
// " not in " + StringUtil.toString(_bounds);
// throw new IllegalArgumentException(errmsg);
// }
return (ty-_bounds.y)*_bounds.width + (tx-_bounds.x);
}
/**
* Links this block to its neighbors; informs neighboring blocks of
* object coverage.
*/
protected void update (HashIntMap blocks)
{
boolean recover = false;
// link up to our neighbors
for (int ii = 0; ii < DX.length; ii++) {
SceneBlock neigh = (SceneBlock)
blocks.get(neighborKey(DX[ii], DY[ii]));
if (neigh != _neighbors[ii]) {
_neighbors[ii] = neigh;
// if we're linking up to a neighbor for the first time;
// we need to recalculate our coverage
recover = recover || (neigh != null);
// Log.info(this + " was introduced to " + neigh + ".");
}
}
// if we need to regenerate the set of tiles covered by our
// objects, do so
if (recover) {
for (int ii = 0; ii < _objects.length; ii++) {
setCovered(blocks, _objects[ii]);
}
}
}
/** Computes the key of our neighbor. */
protected final int neighborKey (int dx, int dy)
{
return ((short)(_bounds.x/_bounds.width+dx) << 16 |
(short)(_bounds.y/_bounds.height+dy));
}
/** Computes the key for the block that holds the specified tile. */
protected final int blockKey (int tx, int ty)
{
return ((short)(tx/_bounds.width) << 16 |
(short)(ty/_bounds.height));
}
/**
* Sets the footprint of this object tile
*/
protected void setCovered (HashIntMap blocks, SceneObject scobj)
{
int endx = Math.max(0, (scobj.info.x - scobj.tile.getBaseWidth() + 1));
int endy = Math.max(0, (scobj.info.y - scobj.tile.getBaseHeight() + 1));
for (int xx = scobj.info.x; xx >= endx; xx--) {
if ((xx < 0) || (xx >= Short.MAX_VALUE)) {
continue;
}
for (int yy = scobj.info.y; yy >= endy; yy--) {
if ((yy < 0) || (yy >= Short.MAX_VALUE)) {
continue;
}
SceneBlock block = (SceneBlock)blocks.get(blockKey(xx, yy));
if (block != null) {
block.setCovered(xx, yy);
}
}
}
// Log.info("Updated coverage " + scobj.info + ".");
}
/**
* Indicates that this tile is covered by an object footprint.
*/
protected void setCovered (int tx, int ty)
{
_covered[index(tx, ty)] = true;
}
/** The panel for which we contain a block. */
protected MisoScenePanel _panel;
/** The bounds of (in tile coordinates) of this block. */
protected Rectangle _bounds;
/** A polygon bounding the footprint of this block. */
protected Polygon _footprint;
/** Used to return a tile where we have none. */
protected TileSet _defset;
/** Our base tiles. */
protected BaseTile[] _base;
/** Our fringe tiles. */
protected Tile[] _fringe;
/** Indicates whether our tiles are covered by an object. */
protected boolean[] _covered;
/** Info on our objects. */
protected SceneObject[] _objects;
/** Our neighbors in the eight cardinal directions. */
protected SceneBlock[] _neighbors = new SceneBlock[DX.length];
// used to link up to our neighbors
protected static final int[] DX = { -1, -1, 0, 1, 1, 1, 0, -1 };
protected static final int[] DY = { 0, -1, -1, -1, 0, 1, 1, 1 };
// for mapping tile coordinates to a pseudo-random tile
protected final static long multiplier = 0x5DEECE66DL;
protected final static long addend = 0xBL;
protected final static long mask = (1L << 48) - 1;
}