diff --git a/src/java/com/threerings/miso/client/DisplayMisoScene.java b/src/java/com/threerings/miso/client/DisplayMisoScene.java index 711fdb3ed..2acfb30b6 100644 --- a/src/java/com/threerings/miso/client/DisplayMisoScene.java +++ b/src/java/com/threerings/miso/client/DisplayMisoScene.java @@ -1,9 +1,11 @@ // -// $Id: DisplayMisoScene.java,v 1.11 2003/04/01 02:17:58 mdb Exp $ +// $Id: DisplayMisoScene.java,v 1.12 2003/04/12 02:14:52 mdb Exp $ package com.threerings.miso.client; import com.threerings.media.tile.Tile; + +import com.threerings.miso.client.util.AStarPathUtil; import com.threerings.miso.data.MisoScene; import com.threerings.miso.tile.BaseTile; @@ -11,7 +13,8 @@ import com.threerings.miso.tile.BaseTile; * Extends the {@link MisoScene} with functionality needed only by * entities that plan to display a miso scene. */ -public interface DisplayMisoScene extends MisoScene +public interface DisplayMisoScene + extends MisoScene, AStarPathUtil.TraversalPred { /** * This will be called before the scene is displayed to give it a @@ -32,10 +35,11 @@ public interface DisplayMisoScene extends MisoScene /** * Returns true if the supplied traverser can traverse the specified * tile coordinate. The traverser is whatever object is passed along - * to the path finder when a path is being computed. Scene - * implementations which support custom traversal based on the type of - * the traverser will want to reflect the traverser's class and act - * acordingly. + * to the path finder when a path is being computed. + * + *

Scene implementations which support custom traversal based on + * the type of the traverser will want to reflect the traverser's + * class and act acordingly. */ public boolean canTraverse (Object traverser, int x, int y); } diff --git a/src/java/com/threerings/miso/client/IsoSceneView.java b/src/java/com/threerings/miso/client/IsoSceneView.java index a61f61eab..c87f8137a 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.136 2003/04/07 21:43:00 mdb Exp $ +// $Id: IsoSceneView.java,v 1.137 2003/04/12 02:14:52 mdb Exp $ package com.threerings.miso.client; @@ -515,10 +515,12 @@ public class IsoSceneView implements SceneView _model, sprite.getX(), sprite.getY(), new Point()); Point dest = IsoUtil.screenToTile(_model, x, y, new Point()); + // TODO: compute this value from the screen size or something + int longestPath = 50; + // get a reasonable tile path through the scene List points = AStarPathUtil.getPath( - _scene, _model.scenewid, _model.scenehei, - sprite, src.x, src.y, dest.x, dest.y); + _scene, sprite, longestPath, src.x, src.y, dest.x, dest.y); // construct a path object to guide the sprite on its merry way return (points == null) ? null : diff --git a/src/java/com/threerings/miso/client/IsoSceneViewModel.java b/src/java/com/threerings/miso/client/IsoSceneViewModel.java index 2bb1130d7..04e33cf28 100644 --- a/src/java/com/threerings/miso/client/IsoSceneViewModel.java +++ b/src/java/com/threerings/miso/client/IsoSceneViewModel.java @@ -1,5 +1,5 @@ // -// $Id: IsoSceneViewModel.java,v 1.28 2003/01/31 23:10:45 mdb Exp $ +// $Id: IsoSceneViewModel.java,v 1.29 2003/04/12 02:14:52 mdb Exp $ package com.threerings.miso.client; @@ -30,15 +30,12 @@ public class IsoSceneViewModel /** Number of fine coordinates on each axis within a tile. */ public int finegran; - /** Scene dimensions in tile count. */ - public int scenewid, scenehei; - /** Size of the view in tile count. */ public int scenevwid, scenevhei; /** Whether or not this view can extend beyond the bounds defined by * the view width and height. True if it cannot, false if it can. */ - public boolean bounded = true; + public boolean bounded = false; /** The bounds of the view in screen pixel coordinates. */ public Rectangle bounds; @@ -46,21 +43,12 @@ public class IsoSceneViewModel /** The position in pixels at which tile (0, 0) is drawn. */ public Point origin; - /** The total number of tile rows to render the full view. */ - public int tilerows; - /** The length of a tile edge in pixels. */ public float tilelen; - /** The y-intercept of the x-axis line. */ - public int bX; - /** The slope of the x- and y-axis lines. */ public float slopeX, slopeY; - /** The x-axis line. */ - public Point[] lineX; - /** The length between fine coordinates in pixels. */ public float finelen; @@ -78,12 +66,6 @@ public class IsoSceneViewModel */ public IsoSceneViewModel () { - // set the scene tile dimensions - scenewid = MisoConfig.config.getValue( - SCENE_WIDTH_KEY, DEF_SCENE_WIDTH); - scenehei = MisoConfig.config.getValue( - SCENE_HEIGHT_KEY, DEF_SCENE_HEIGHT); - // and the view dimensions scenevwid = MisoConfig.config.getValue( SCENE_VWIDTH_KEY, DEF_SCENE_VWIDTH); @@ -122,8 +104,6 @@ public class IsoSceneViewModel int svwid, int svhei, int offy) { // keep track of this stuff - this.scenewid = scenewid; - this.scenehei = scenehei; this.tilewid = tilewid; this.tilehei = tilehei; this.finegran = finegran; @@ -137,13 +117,14 @@ public class IsoSceneViewModel } /** - * Returns whether the given tile coordinate is a valid coordinate - * within the scene. + * Returns whether the given tile coordinate is a valid coordinate in + * our coordinate system (which allows tile coordinates from 0 to + * 2^15-1). */ public boolean isCoordinateValid (int x, int y) { - return (x >= 0 && x < scenewid && - y >= 0 && y < scenehei); + return (x >= 0 && x < Short.MAX_VALUE && + y >= 0 && y < Short.MAX_VALUE); } /** @@ -174,26 +155,6 @@ public class IsoSceneViewModel // pre-calculate tile-related data precalculateTiles(); - // calculate scene-based x-axis line for conversion from - // screen to tile coordinates - - // create the x- and y-axis lines - lineX = new Point[2]; - for (int ii = 0; ii < 2; ii++) { - lineX[ii] = new Point(); - } - - // determine the starting point - lineX[0].setLocation(origin.x, origin.y); - bX = (int)-(slopeX * origin.x); - - // determine the ending point - lineX[1].x = lineX[0].x + (tilehwid * scenewid); - lineX[1].y = lineX[0].y + (int)((slopeX * lineX[1].x) + bX); - - // calculate tile-based x-axis line for conversion from - // tile-based pixel to fine coordinates - // calculate the edge length separating each fine coordinate finelen = tilelen / (float)finegran; @@ -220,9 +181,6 @@ public class IsoSceneViewModel tilelen = (float) Math.sqrt( (tilehwid * tilehwid) + (tilehhei * tilehhei)); - // calculate the number of tile rows to render - tilerows = (scenewid * scenehei) - 1; - // calculate the slope of the x- and y-axis lines slopeX = (float)tilehei / (float)tilewid; slopeY = -slopeX; diff --git a/src/java/com/threerings/miso/client/util/AStarPathUtil.java b/src/java/com/threerings/miso/client/util/AStarPathUtil.java index 50c3561a7..839c64124 100644 --- a/src/java/com/threerings/miso/client/util/AStarPathUtil.java +++ b/src/java/com/threerings/miso/client/util/AStarPathUtil.java @@ -1,5 +1,5 @@ // -// $Id: AStarPathUtil.java,v 1.26 2003/04/07 23:53:41 mdb Exp $ +// $Id: AStarPathUtil.java,v 1.27 2003/04/12 02:14:52 mdb Exp $ package com.threerings.miso.client.util; @@ -11,7 +11,6 @@ import com.samskivert.util.HashIntMap; import com.threerings.media.util.MathUtil; import com.threerings.miso.Log; -import com.threerings.miso.client.DisplayMisoScene; import com.threerings.miso.tile.BaseTile; /** @@ -25,6 +24,19 @@ import com.threerings.miso.tile.BaseTile; */ public class AStarPathUtil { + /** + * Provides traversibility information when computing paths. + */ + public static interface TraversalPred + { + /** + * Requests to know if the specified traverser (which was provided + * in the call to {@link #getPath}) can traverse the specified + * tile coordinate. + */ + public boolean canTraverse (Object traverser, int x, int y); + } + /** * Return a list of Point objects representing a path * from coordinates (ax, by) to (bx, by), @@ -32,10 +44,9 @@ public class AStarPathUtil * scene's base tile layer. Assumes the starting and destination nodes * are traversable by the specified traverser. * - * @param scene the scene in which a path is to be computed. - * @param tilewid the scene width in tiles. - * @param tilehei the scene height in tiles. + * @param tpred lets us know what tiles are traversible. * @param trav the traverser to follow the path. + * @param longest the longest allowable path in tile traversals. * @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. @@ -43,14 +54,13 @@ public class AStarPathUtil * * @return the list of points in the path. */ - public static List getPath ( - DisplayMisoScene scene, int tilewid, int tilehei, Object trav, - int ax, int ay, int bx, int by) + public static List getPath (TraversalPred tpred, Object trav, + int longest, int ax, int ay, int bx, int by) { - AStarInfo info = new AStarInfo(scene, tilewid, tilehei, trav, bx, by); + Info info = new Info(tpred, trav, longest, bx, by); // set up the starting node - AStarNode s = info.getNode(ax, ay); + Node s = info.getNode(ax, ay); s.g = 0; s.h = getDistanceEstimate(ax, ay, bx, by); s.f = s.g + s.h; @@ -62,7 +72,7 @@ public class AStarPathUtil while (info.open.size() > 0) { // pop the best node so far from open - AStarNode n = (AStarNode)info.open.first(); + Node n = (Node)info.open.first(); info.open.remove(n); // if node is a goal node @@ -99,30 +109,24 @@ public class AStarPathUtil * @param y the y-coordinate for the destination step. */ protected static void considerStep ( - AStarInfo info, AStarNode n, int x, int y, int cost) + Info info, Node n, int x, int y, int cost) { // skip node if it's outside the map bounds or otherwise impassable if (!info.isStepValid(n.x, n.y, x, y)) { return; } - // if it's offscreen, bang up the cost considerably - if (!info.isCoordinateValid(x, y)) { - cost += OFFSCREEN_COST; - } - // calculate the new cost for this node int newg = n.g + cost; - // make sure the cost is reasonable (so we don't go crazy computing - // offscreen costs) + // make sure the cost is reasonable if (newg > info.maxcost) { // Log.info("Rejected costly step."); return; } // retrieve the node corresponding to this location - AStarNode np = info.getNode(x, y); + Node np = info.getNode(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 @@ -157,9 +161,9 @@ public class AStarPathUtil * * @return the list detailing the path. */ - protected static List getNodePath (AStarNode n) + protected static List getNodePath (Node n) { - AStarNode cur = n; + Node cur = n; ArrayList path = new ArrayList(); while (cur != null) { @@ -187,176 +191,155 @@ public class AStarPathUtil return (int) (ADJACENT_COST * Math.sqrt(xsq * xsq + ysq * ysq)); } + /** + * A holding class to contain the wealth of information referenced + * while performing an A* search for a path through a tile array. + */ + protected static class Info + { + /** Knows whether or not tiles are traversable. */ + public TraversalPred tpred; + + /** The tile array dimensions. */ + public int tilewid, tilehei; + + /** The traverser moving along the path. */ + public Object trav; + + /** 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; + + /** The maximum cost of any path that we'll consider. */ + public int maxcost; + + public Info (TraversalPred tpred, Object trav, + int longest, int destx, int desty) + { + // save off references + this.tpred = tpred; + this.trav = trav; + this.destx = destx; + this.desty = desty; + + // compute our maximum path cost + this.maxcost = longest * ADJACENT_COST; + + // construct the open and closed lists + open = new TreeSet(); + closed = new ArrayList(); + } + + /** + * Returns whether moving from the given source to destination + * coordinates is a valid move. + */ + protected boolean isStepValid (int sx, int sy, int dx, int dy) + { + // not traversable if the destination itself fails test + if (!isTraversable(dx, dy)) { + return false; + } + + // if the step is diagonal, make sure the corners don't impede + // our progress + if ((Math.abs(dx - sx) == 1) && (Math.abs(dy - sy) == 1)) { + return isTraversable(dx, sy) && isTraversable(sx, dy); + } + + // non-diagonals are always traversable + return true; + } + + /** + * Returns whether the given coordinate is valid and traversable. + */ + protected boolean isTraversable (int x, int y) + { + return tpred.canTraverse(trav, x, y); + } + + /** + * Get or create the node for the specified point. + */ + public Node getNode (int x, int y) + { + // note: this _could_ break for unusual values of x and y. + // perhaps use a IntTuple as a key? Bleah. + int key = (x << 16) | (y & 0xffff); + Node node = (Node) _nodes.get(key); + if (node == null) { + node = new Node(x, y); + _nodes.put(key, node); + } + return node; + } + + /** The nodes being considered in the path. */ + protected HashIntMap _nodes = new HashIntMap(); + } + + /** + * A class that represents a single traversable node in the tile array + * along with its current A*-specific search information. + */ + protected static class Node 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 Node parent; + + /** The node's monotonically-increasing unique identifier. */ + public int id; + + public Node (int x, int y) + { + this.x = x; + this.y = y; + id = _nextid++; + } + + public int compareTo (Object o) + { + int bf = ((Node)o).f; + + // since the set contract is fulfilled using the equality results + // returned here, and we'd like to allow multiple nodes with + // equivalent scores in our set, we explicitly define object + // equivalence as the result of object.equals(), else we use the + // unique node id since it will return a consistent ordering for + // the objects. + if (f == bf) { + return (this == o) ? 0 : (id - ((Node)o).id); + } + + return f - bf; + } + + /** The next unique node id. */ + protected static int _nextid = 0; + } + /** The standard cost to move between nodes. */ public static final int ADJACENT_COST = 10; /** The cost to move diagonally. */ - public static final int DIAGONAL_COST = (int) Math.sqrt( - (ADJACENT_COST * ADJACENT_COST) * 2); - - /** A big old additional cost incurred for offscreen movement. */ - public static final int OFFSCREEN_COST = 1000; -} - -/** - * 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 scene whose base tile layer is being traversed. */ - public DisplayMisoScene scene; - - /** The tile array dimensions. */ - public int tilewid, tilehei; - - /** The traverser moving along the path. */ - public Object trav; - - /** 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; - - /** The maximum cost of any path that we'll consider. */ - public int maxcost; - - public AStarInfo ( - DisplayMisoScene scene, int tilewid, int tilehei, Object trav, - int destx, int desty) - { - // save off references - this.scene = scene; - this.tilewid = tilewid; - this.tilehei = tilehei; - this.trav = trav; - this.destx = destx; - this.desty = desty; - - // compute the maximum cost as the maximum onscreen path plus - // the maximum offscreen cost - this.maxcost = ((tilewid + tilehei) * AStarPathUtil.ADJACENT_COST) + - MAX_OFFSCREEN * AStarPathUtil.OFFSCREEN_COST; - - // construct the open and closed lists - open = new TreeSet(); - closed = new ArrayList(); - } - - /** - * Returns whether the given coordinate is valid based on the - * dimensions of the map being traversed. - */ - protected boolean isCoordinateValid (int x, int y) - { - return (x >= 0 && y >= 0 && x < tilewid && y < tilehei && - (scene.getBaseTile(x, y) != null)); - } - - /** - * Returns whether moving from the given source to destination - * coordinates is a valid move. - */ - protected boolean isStepValid (int sx, int sy, int dx, int dy) - { - // not traversable if the destination itself fails test - if (!isTraversable(dx, dy)) { - return false; - } - - // if the step is diagonal, make sure the corners don't impede - // our progress - if ((Math.abs(dx - sx) == 1) && (Math.abs(dy - sy) == 1)) { - return isTraversable(dx, sy) && isTraversable(sx, dy); - } - - // non-diagonals are always traversable - return true; - } - - /** - * Returns whether the given coordinate is valid and traversable. - */ - protected boolean isTraversable (int x, int y) - { - return scene.canTraverse(trav, x, y); - } - - /** - * Get or create the node for the specified point. - */ - public AStarNode getNode (int x, int y) - { - // note: this _could_ break for unusual values of x and y. - // perhaps use a IntTuple as a key? Bleah. - int key = (x << 16) | (y & 0xffff); - AStarNode node = (AStarNode) _nodes.get(key); - if (node == null) { - node = new AStarNode(x, y); - _nodes.put(key, node); - } - return node; - } - - /** The nodes being considered in the path. */ - protected HashIntMap _nodes = new HashIntMap(); - - /** The maximum number of offscreen points we'll consider. */ - protected static final int MAX_OFFSCREEN = 6; -} - -/** - * 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; - - /** The node's monotonically-increasing unique identifier. */ - public int id; - - public AStarNode (int x, int y) - { - this.x = x; - this.y = y; - id = _nextid++; - } - - public int compareTo (Object o) - { - int bf = ((AStarNode)o).f; - - // since the set contract is fulfilled using the equality results - // returned here, and we'd like to allow multiple nodes with - // equivalent scores in our set, we explicitly define object - // equivalence as the result of object.equals(), else we use the - // unique node id since it will return a consistent ordering for - // the objects. - if (f == bf) { - return (this == o) ? 0 : (id - ((AStarNode)o).id); - } - - return f - bf; - } - - /** The next unique node id. */ - protected static int _nextid = 0; + public static final int DIAGONAL_COST = (int)Math.sqrt( + (ADJACENT_COST * ADJACENT_COST) * 2); }