All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.media.util.AStarPathUtil Maven / Gradle / Ivy

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.media.util;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import java.awt.Point;

import com.google.common.collect.Lists;

import com.samskivert.util.HashIntMap;

/**
 * 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 { /** * 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(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse the * specified tile coordinate. */ public boolean canTraverse (Object traverser, int x, int y); } /** * Provides extended traversibility information when computing paths. */ public static interface ExtendedTraversalPred extends TraversalPred { /** * Requests to know if the specific traverser (which was provided in the call to * {@link #getPath(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse from * the specified source tile coordinate to the specified destination tile coordinate. */ public boolean canTraverse (Object traverser, int sx, int sy, int dx, int dy); } /** * Considers all the possible steps the piece in question can take. */ public static class Stepper { public Stepper () { this(true); } public Stepper (boolean considerDiagonals) { _considerDiagonals = considerDiagonals; } public void init (Info info, Node n) { _info = info; _node = n; } /** * Should call {@link #considerStep} in turn on all possible steps from the specified * coordinates. No checking must be done as to whether the step is legal, that will be * handled later. Just enumerate all possible steps. */ public void considerSteps (int x, int y) { considerStep(x, y - 1, ADJACENT_COST); considerStep(x, y + 1, ADJACENT_COST); considerStep(x - 1, y, ADJACENT_COST); considerStep(x + 1, y, ADJACENT_COST); if (_considerDiagonals) { considerStep(x - 1, y - 1, DIAGONAL_COST); considerStep(x + 1, y - 1, DIAGONAL_COST); considerStep(x - 1, y + 1, DIAGONAL_COST); considerStep(x + 1, y + 1, DIAGONAL_COST); } } protected void considerStep (int x, int y, int cost) { AStarPathUtil.considerStep(_info, _node, x, y, cost); } protected boolean _considerDiagonals; protected Info _info; protected Node _node; } /** 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); /** * 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 scene's base tile layer. Assumes the starting and destination nodes * are traversable by the specified traverser. * * @param tpred lets us know what tiles are traversible. * @param stepper enumerates the possible steps. * @param trav the traverser to follow the path. * @param longest the longest allowable path in tile traversals. This arg must be less than * Integer.MAX_VALUE / ADJACENT_COST, even if your stepper uses a * different fucking adjacent cost. * @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. * @param partial if true, a partial path will be returned that gets us as close as we can to * the goal in the event that a complete path cannot be located. * * @return the list of points in the path, or null if no path could be found. */ public static List getPath ( TraversalPred tpred, Stepper stepper, Object trav, int longest, int ax, int ay, int bx, int by, boolean partial) { Info info = new Info(tpred, trav, longest, bx, by); // set up the starting node Node s = info.getNode(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); _considered = 1; // track the best path float bestdist = Float.MAX_VALUE; Node bestpath = null; // while there are more nodes on the open list while (info.open.size() > 0) { // pop the best node so far from open Node n = 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); } else if (partial) { float pathdist = MathUtil.distance(n.x, n.y, bx, by); if (pathdist < bestdist) { bestdist = pathdist; bestpath = n; } } // consider each successor of the node stepper.init(info, n); stepper.considerSteps(n.x, n.y); // push the node on the closed list n.closed = true; } // return the best path we could find if we were asked to do so if (bestpath != null) { return getNodePath(bestpath); } // no path found return null; } /** * Gets a path with the default stepper which assumes the piece can move one in any of the * eight cardinal directions. */ public static List getPath ( TraversalPred tpred, Object trav, int longest, int ax, int ay, int bx, int by, boolean partial) { return getPath(tpred, new Stepper(), trav, longest, ax, ay, bx, by, partial); } /** * Returns the number of nodes considered in computing the most recent path. */ public static int getConsidered () { return _considered; } /** * 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 (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; } // calculate the new cost for this node int newg = n.g + cost; // make sure the cost is reasonable if (newg > info.maxcost) { // Log.info("Rejected costly step."); return; } // retrieve the node corresponding to this location 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 if ((np.closed || info.open.contains(np)) && np.g <= newg) { return; } // remove the node from the open list since we're about to // modify its score which determines its placement in the list info.open.remove(np); // 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 np.closed = false; // add it to the open list for further consideration info.open.add(np); _considered++; } /** * 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 (Node n) { Node cur = n; ArrayList path = Lists.newArrayList(); 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 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) { // we're doing all of our cost calculations based on geometric distance times ten int xsq = bx - ax; int ysq = by - ay; 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 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(); } /** * 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 (tpred instanceof ExtendedTraversalPred) { if (!((ExtendedTraversalPred)tpred).canTraverse(trav, sx, sy, dx, dy)) { return false; } } else 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 = _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. */ public 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; /** Whether or not this node is on the closed list. */ public boolean closed; public Node (int x, int y) { this.x = x; this.y = y; id = _nextid++; } public int compareTo (Node o) { int bf = 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 - o.id); } return f - bf; } /** The next unique node id. */ protected static int _nextid = 0; } /** The number of nodes considered in computing our path. */ protected static int _considered = 0; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy