diff --git a/src/java/com/threerings/cast/CharacterSprite.java b/src/java/com/threerings/cast/CharacterSprite.java index a35b1bd9b..6d5884e22 100644 --- a/src/java/com/threerings/cast/CharacterSprite.java +++ b/src/java/com/threerings/cast/CharacterSprite.java @@ -1,16 +1,19 @@ // -// $Id: CharacterSprite.java,v 1.4 2001/08/14 23:35:22 mdb Exp $ +// $Id: CharacterSprite.java,v 1.5 2001/08/15 02:30:27 shaper Exp $ -package com.threerings.media.sprite; +package com.threerings.miso.scene; -import com.threerings.media.Log; +import com.threerings.media.sprite.*; +import com.threerings.miso.Log; +import com.threerings.miso.tile.Tile; +import com.threerings.miso.tile.Traverser; /** * An AmbulatorySprite is a sprite that can face in one of * the various compass directions and that can animate itself walking * along some chosen path. */ -public class AmbulatorySprite extends Sprite +public class AmbulatorySprite extends Sprite implements Traverser { /** * Construct an AmbulatorySprite, with a multi-frame @@ -61,6 +64,12 @@ public class AmbulatorySprite extends Sprite setAnimationDelay(0); } + public boolean canTraverse (Tile tile) + { + // by default, passability is solely the province of the tile + return tile.passable; + } + /** The animation frames for the sprite facing each direction. */ protected MultiFrameImage[] _anims; diff --git a/src/java/com/threerings/media/tile/Tile.java b/src/java/com/threerings/media/tile/Tile.java index 80f281771..78b507e48 100644 --- a/src/java/com/threerings/media/tile/Tile.java +++ b/src/java/com/threerings/media/tile/Tile.java @@ -1,5 +1,5 @@ // -// $Id: Tile.java,v 1.9 2001/08/13 19:54:39 shaper Exp $ +// $Id: Tile.java,v 1.10 2001/08/15 02:30:27 shaper Exp $ package com.threerings.miso.tile; @@ -30,10 +30,10 @@ public class Tile /** * Construct a new tile with the specified identifiers. Intended - * only for use by the TileManager. Do not call this + * only for use by the TileSet. Do not call this * method. * - * @see TileManager#getTile + * @see TileSet#getTile */ public Tile (int tsid, int tid) { diff --git a/src/java/com/threerings/miso/client/IsoSceneView.java b/src/java/com/threerings/miso/client/IsoSceneView.java index d485d5250..390c0523d 100644 --- a/src/java/com/threerings/miso/client/IsoSceneView.java +++ b/src/java/com/threerings/miso/client/IsoSceneView.java @@ -1,5 +1,5 @@ // -// $Id: IsoSceneView.java,v 1.44 2001/08/15 01:08:49 mdb Exp $ +// $Id: IsoSceneView.java,v 1.45 2001/08/15 02:30:27 shaper Exp $ package com.threerings.miso.scene; @@ -15,6 +15,7 @@ import com.threerings.media.sprite.*; import com.threerings.miso.Log; import com.threerings.miso.tile.Tile; import com.threerings.miso.tile.TileManager; +import com.threerings.miso.scene.util.AStarPathUtil; import com.threerings.miso.scene.util.IsoUtil; /** @@ -539,7 +540,7 @@ public class IsoSceneView implements EditableSceneView _model.precalculate(); } - public Path getPath (Sprite sprite, int x, int y) + public Path getPath (AmbulatorySprite sprite, int x, int y) { // make sure the destination point is within our bounds if (x < 0 || x >= _model.bounds.width || @@ -550,13 +551,43 @@ public class IsoSceneView implements EditableSceneView // constrain destination pixels to fine coordinates Point fpos = new Point(); IsoUtil.screenToFull(_model, x, y, fpos); - Point spos = new Point(); - IsoUtil.fullToScreen(_model, fpos.x, fpos.y, spos); +// Point spos = new Point(); +// IsoUtil.fullToScreen(_model, fpos.x, fpos.y, spos); - // create path from current loc to destination + // calculate tile coordinates for start and end position + Point stpos = new Point(); + IsoUtil.screenToTile(_model, sprite.x, sprite.y, stpos); + int tbx = IsoUtil.fullToTile(fpos.x); + int tby = IsoUtil.fullToTile(fpos.y); + + // get a reasonable path from start to end + List tilepath = + AStarPathUtil.getPath( + _scene.tiles, MisoScene.TILE_WIDTH, MisoScene.TILE_HEIGHT, + sprite, stpos.x, stpos.y, tbx, tby); + + // construct the path object and starting node Path path = new Path(sprite.x, sprite.y); - int dir = IsoUtil.getDirection(_model, sprite.x, sprite.y, x, y); - path.addNode(spos.x, spos.y, dir); + + // add all nodes on the calculated path + Point nspos = new Point(); + int size = tilepath.size(); + Point prev = stpos; + for (int ii = 0; ii < size; ii++) { + Point n = (Point)tilepath.get(ii); + + // determine the direction this node lies in from the + // previous node + int dir = IsoUtil.getIsoDirection(prev.x, prev.y, n.x, n.y); + + // determine the node's position in screen pixel coordinates + IsoUtil.tileToScreen(_model, n.x, n.y, nspos); + + // add the node to the path + path.addNode(nspos.x, nspos.y, dir); + + prev = n; + } return path; } diff --git a/src/java/com/threerings/miso/client/SceneView.java b/src/java/com/threerings/miso/client/SceneView.java index 99aed6eed..e00d8846e 100644 --- a/src/java/com/threerings/miso/client/SceneView.java +++ b/src/java/com/threerings/miso/client/SceneView.java @@ -1,5 +1,5 @@ // -// $Id: SceneView.java,v 1.11 2001/08/15 01:08:49 mdb Exp $ +// $Id: SceneView.java,v 1.12 2001/08/15 02:30:27 shaper Exp $ package com.threerings.miso.scene; @@ -41,5 +41,5 @@ public interface SceneView extends AnimatedView * * @return the sprite's path or null if no valid path exists. */ - public Path getPath (Sprite sprite, int x, int y); + public Path getPath (AmbulatorySprite sprite, int x, int y); } diff --git a/src/java/com/threerings/miso/client/Traverser.java b/src/java/com/threerings/miso/client/Traverser.java new file mode 100644 index 000000000..80f772b94 --- /dev/null +++ b/src/java/com/threerings/miso/client/Traverser.java @@ -0,0 +1,23 @@ +// +// $Id: Traverser.java,v 1.1 2001/08/15 02:30:27 shaper Exp $ + +package com.threerings.miso.tile; + +/** + * The Traverser interface should be implemented by + * sprites that are going to traverse some path in a scene. This + * allows path determination to take into account any special + * abilities the traverser may have that alter the traversability of + * tiles. + */ +public interface Traverser +{ + /** + * Return whether the traverser can traverse the specified tile. + * + * @param tile the tile to traverse. + * + * @return whether the tile is traversable. + */ + public boolean canTraverse (Tile tile); +} diff --git a/src/java/com/threerings/miso/client/util/AStarPathUtil.java b/src/java/com/threerings/miso/client/util/AStarPathUtil.java new file mode 100644 index 000000000..102e7a0b2 --- /dev/null +++ b/src/java/com/threerings/miso/client/util/AStarPathUtil.java @@ -0,0 +1,277 @@ +// +// $Id: AStarPathUtil.java,v 1.1 2001/08/15 02:30:27 shaper Exp $ + +package com.threerings.miso.scene.util; + +import java.awt.Point; +import java.util.*; + +import com.threerings.miso.Log; +import com.threerings.miso.tile.Tile; +import com.threerings.miso.tile.Traverser; +import com.threerings.media.util.MathUtil; + +/** + * The AStarPathUtil class provides a facility for + * finding a reasonable path between two points in a scene using the + * A* search algorithm. + * + *

See the path-finding article on + * + * Gamasutra for more detailed information. + */ +public class AStarPathUtil +{ + /** + * Return a list of Point objects representing a path + * from coordinates (ax, by) to + * (bx, by), inclusive, determined by performing an + * A* search in the given array of tiles. Assumes the starting + * and destination nodes are traversable by the specified + * traverser. + * + * @param tiles the tile array. + * @param tilewid the tile array width. + * @param tilehei the tile array height. + * @param trav the traverser to follow the path. + * @param ax the starting x-position in tile coordinates. + * @param ay the starting y-position in tile coordinates. + * @param bx the ending x-position in tile coordinates. + * @param by the ending y-position in tile coordinates. + * + * @return the list of points in the path. + */ + public static List getPath ( + Tile tiles[][][], int tilewid, int tilehei, Traverser trav, + int ax, int ay, int bx, int by) + { + AStarInfo info = new AStarInfo(tiles, tilewid, tilehei, trav, bx, by); + + // set up the starting node + AStarNode s = getNode(info, ax, ay); + s.g = 0; + s.h = getDistanceEstimate(ax, ay, bx, by); + s.f = s.g + s.h; + + // push starting node on the open list + info.open.add(s); + + // while there are more nodes on the open list + while (info.open.size() > 0) { + + // pop the best node so far from open + AStarNode n = (AStarNode)info.open.first(); + info.open.remove(n); + + // if node is a goal node + if (n.x == bx && n.y == by) { + // construct and return the acceptable path + return getNodePath(n); + } + + // TODO: don't allow diagonal traversal if horiz and vert + // are impassable. + + // consider each successor of the node + considerStep(info, n, n.x - 1, n.y - 1); + considerStep(info, n, n.x, n.y - 1); + considerStep(info, n, n.x + 1, n.y - 1); + considerStep(info, n, n.x - 1, n.y); + considerStep(info, n, n.x + 1, n.y); + considerStep(info, n, n.x - 1, n.y + 1); + considerStep(info, n, n.x, n.y + 1); + considerStep(info, n, n.x + 1, n.y + 1); + + // push the node on the closed list + info.closed.add(n); + } + + // no path found + return null; + } + + /** + * Consider the step (n.x, n.y) to (x, y) + * for possible inclusion in the path. + * + * @param info the info object. + * @param n the originating node for the step. + * @param x the x-coordinate for the destination step. + * @param y the y-coordinate for the destination step. + */ + protected static void considerStep ( + AStarInfo info, AStarNode n, int x, int y) + { + // skip node if it's outside the map bounds + if (x < 0 || y < 0 || x >= info.tilewid || y >= info.tilehei) { + return; + } + + // skip node if it's impassable + // TODO: fix hard-coded consideration of only the base layer + if (!info.trav.canTraverse(info.tiles[x][y][0])) return; + + // calculate the new cost for this node + int newg = n.g + 1; // getCost() is always 1? + + // retrieve the node corresponding to this location + AStarNode np = getNode(info, x, y); + + // skip if it's already in the open or closed list or if its + // actual cost is less than the just-calculated cost + // TODO: shouldn't <= below be >=? + if ((info.open.contains(np) || info.closed.contains(np)) && + np.g <= newg) { + return; + } + + // update the node's information + np.parent = n; + np.g = newg; + np.h = getDistanceEstimate(np.x, np.y, info.destx, info.desty); + np.f = np.g + np.h; + + // remove it from the closed list if it's present + info.closed.remove(np); + + // add it to the open list in case it's not already there + info.open.add(np); + } + + /** + * Return a list of Point objects detailing the path + * from the first node (the given node's ultimate parent) to the + * ending node (the given node itself.) + * + * @param n the ending node in the path. + * + * @return the list detailing the path. + */ + protected static List getNodePath (AStarNode n) + { + AStarNode cur = n; + ArrayList path = new ArrayList(); + + while (cur != null) { + // add to the head of the list since we're traversing from + // the end to the beginning + path.add(0, new Point(cur.x, cur.y)); + + // advance to the next node in the path + cur = cur.parent; + } + + return path; + } + + /** + * Return the AStarNode object corresponding to the + * specified tile coordinate. Creates the node and saves it in + * the node array if this is its first reference. + */ + protected static AStarNode getNode (AStarInfo info, int x, int y) + { + AStarNode n = info.nodes[x][y]; + return (n == null) ? (info.nodes[x][y] = new AStarNode(x, y)) : n; + } + + /** + * Return a heuristic estimate of the cost to get from + * (ax, ay) to (bx, by). + */ + protected static int getDistanceEstimate (int ax, int ay, int bx, int by) + { + return (int)MathUtil.distance(ax, ay, bx, by); + } +} + +/** + * A holding class to contain the wealth of information referenced + * while performing an A* search for a path through a tile array. + */ +class AStarInfo +{ + /** The array of tiles being traversed. */ + public Tile tiles[][][]; + + /** The tile array dimensions. */ + public int tilewid, tilehei; + + /** The traverser moving along the path. */ + public Traverser trav; + + /** The array of A*-specific node info to match the tile array. */ + public AStarNode nodes[][]; + + /** The set of open nodes being searched. */ + public SortedSet open; + + /** The set of closed nodes being searched. */ + public ArrayList closed; + + /** The destination coordinates in the tile array. */ + public int destx, desty; + + public AStarInfo ( + Tile tiles[][][], int tilewid, int tilehei, Traverser trav, + int destx, int desty) + { + // save off references + this.tiles = tiles; + this.tilewid = tilewid; + this.tilehei = tilehei; + this.trav = trav; + this.destx = destx; + this.desty = desty; + + // construct the node array + nodes = new AStarNode[tilewid][tilehei]; + + // construct the open and closed lists + open = new TreeSet(); + closed = new ArrayList(); + } +} + +/** + * A class that represents a single traversable node in the tile array + * along with its current A*-specific search information. + */ +class AStarNode implements Comparable +{ + /** The node coordinates. */ + public int x, y; + + /** The actual cheapest cost of arriving here from the start. */ + public int g; + + /** The heuristic estimate of the cost to the goal from here. */ + public int h; + + /** The score assigned to this node. */ + public int f; + + /** The node from which we reached this node. */ + public AStarNode parent; + + public AStarNode (int x, int y) + { + this.x = x; + this.y = y; + } + + public int compareTo (Object o) + { + int bf = ((AStarNode)o).f; + + if (f == bf){ + return 0; + } + + if (f < bf) { + return -1; + } + + return 1; + } +} diff --git a/src/java/com/threerings/miso/client/util/IsoUtil.java b/src/java/com/threerings/miso/client/util/IsoUtil.java index 4deea2a46..c2de4038e 100644 --- a/src/java/com/threerings/miso/client/util/IsoUtil.java +++ b/src/java/com/threerings/miso/client/util/IsoUtil.java @@ -1,5 +1,5 @@ // -// $Id: IsoUtil.java,v 1.2 2001/08/14 23:35:22 mdb Exp $ +// $Id: IsoUtil.java,v 1.3 2001/08/15 02:30:27 shaper Exp $ package com.threerings.miso.scene.util; @@ -48,7 +48,7 @@ public class IsoUtil int tby = bfpos.y / FULL_TILE_FACTOR; // compare tile coordinates to determine direction - int dir = getIsoDirection(model, tax, tay, tbx, tby); + int dir = getIsoDirection(tax, tay, tbx, tby); if (dir != Path.DIR_NONE) return dir; // destination point is in the same tile as the @@ -62,7 +62,7 @@ public class IsoUtil int fby = bfpos.y - (tby * FULL_TILE_FACTOR); // compare fine coordinates to determine direction - dir = getIsoDirection(model, fax, fay, fbx, fby); + dir = getIsoDirection(fax, fay, fbx, fby); // arbitrarily return southwest if fine coords were also equivalent return (dir == -1) ? Path.DIR_SOUTHWEST : dir; @@ -84,8 +84,7 @@ public class IsoUtil * class's direction constants, or Path.DIR_NONE * if point B is equivalent to point A. */ - public static int getIsoDirection ( - IsoSceneModel model, int ax, int ay, int bx, int by) + public static int getIsoDirection (int ax, int ay, int bx, int by) { if (bx > ax) { if (by == ay) return Path.DIR_SOUTH; @@ -101,6 +100,16 @@ public class IsoUtil } } + public static int fullToTile (int val) + { + return (val / FULL_TILE_FACTOR); + } + + public static int fullToFine (int val) + { + return (val - ((val / FULL_TILE_FACTOR) * FULL_TILE_FACTOR)); + } + /** * Convert the given screen-based pixel coordinates to their * corresponding tile-based coordinates. Converted coordinates diff --git a/src/java/com/threerings/miso/client/util/PathUtil.java b/src/java/com/threerings/miso/client/util/PathUtil.java deleted file mode 100644 index 3f5745a4d..000000000 --- a/src/java/com/threerings/miso/client/util/PathUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -// -// $Id: PathUtil.java,v 1.1 2001/08/14 21:29:40 shaper Exp $ - -package com.threerings.miso.scene.util; - -import java.util.ArrayList; - -import com.threerings.miso.Log; -import com.threerings.miso.tile.Tile; - -/** - * The PathUtil class provides utility routines for - * finding a reasonable path between two points in a scene. - */ -public class PathUtil -{ - /** - * Return a list of Point objects representing a path - * from coordinates (ax, by) to (bx, by), inclusive, determined by - * performing an A* search in the given array of tiles. - * - * @param tiles the tile array. - * @param ax the starting x-position in tile coordinates. - * @param ay the starting y-position in tile coordinates. - * @param bx the ending x-position in tile coordinates. - * @param by the ending y-position in tile coordinates. - * - * @return the list of points in the path. - */ - public static ArrayList getAStarPath ( - Tile tiles[][], int ax, int ay, int bx, int by) - { - return null; - } -} diff --git a/src/java/com/threerings/miso/tile/TileUtil.java b/src/java/com/threerings/miso/tile/TileUtil.java index 89cfc0dbc..3a53ef55c 100644 --- a/src/java/com/threerings/miso/tile/TileUtil.java +++ b/src/java/com/threerings/miso/tile/TileUtil.java @@ -1,13 +1,13 @@ // -// $Id: TileUtil.java,v 1.1 2001/08/14 23:35:22 mdb Exp $ +// $Id: TileUtil.java,v 1.2 2001/08/15 02:30:27 shaper Exp $ package com.threerings.miso.tile; import java.awt.Image; -import com.threerings.media.sprite.AmbulatorySprite; import com.threerings.media.sprite.MultiFrameImage; import com.threerings.media.sprite.Path; +import com.threerings.miso.scene.AmbulatorySprite; /** * Tile-related utility functions. diff --git a/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java b/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java index 3a54aee2f..e7d51f405 100644 --- a/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java +++ b/tests/src/java/com/threerings/miso/viewer/ViewerFrame.java @@ -1,5 +1,5 @@ // -// $Id: ViewerFrame.java,v 1.11 2001/08/15 01:08:49 mdb Exp $ +// $Id: ViewerFrame.java,v 1.12 2001/08/15 02:30:28 shaper Exp $ package com.threerings.miso.viewer; @@ -13,6 +13,7 @@ import com.samskivert.swing.*; import com.threerings.media.sprite.*; import com.threerings.miso.Log; +import com.threerings.miso.scene.AmbulatorySprite; import com.threerings.miso.tile.TileManager; import com.threerings.miso.tile.TileUtil; import com.threerings.miso.viewer.util.ViewerContext; diff --git a/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java b/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java index 4b447eac0..ee7af4999 100644 --- a/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java +++ b/tests/src/java/com/threerings/miso/viewer/ViewerSceneViewPanel.java @@ -1,5 +1,5 @@ // -// $Id: ViewerSceneViewPanel.java,v 1.9 2001/08/15 00:10:58 mdb Exp $ +// $Id: ViewerSceneViewPanel.java,v 1.10 2001/08/15 02:30:28 shaper Exp $ package com.threerings.miso.viewer; @@ -24,7 +24,7 @@ public class ViewerSceneViewPanel extends SceneViewPanel * Construct the panel and initialize it with a context. */ public ViewerSceneViewPanel ( - ViewerContext ctx, SpriteManager spritemgr, Sprite sprite) + ViewerContext ctx, SpriteManager spritemgr, AmbulatorySprite sprite) { super(ctx.getTileManager(), spritemgr); @@ -120,7 +120,7 @@ public class ViewerSceneViewPanel extends SceneViewPanel AnimationManager _animmgr; /** The sprite we're manipulating within the view. */ - protected Sprite _sprite; + protected AmbulatorySprite _sprite; /** The context object. */ protected ViewerContext _ctx;