mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-09 01:54:04 +09:00
added sources for Slick
Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054 Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The description of a class providing a cost for a given tile based
|
||||
* on a target location and entity being moved. This heuristic controls
|
||||
* what priority is placed on different tiles during the search for a path
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface AStarHeuristic {
|
||||
|
||||
/**
|
||||
* Get the additional heuristic cost of the given tile. This controls the
|
||||
* order in which tiles are searched while attempting to find a path to the
|
||||
* target location. The lower the cost the more likely the tile will
|
||||
* be searched.
|
||||
*
|
||||
* @param map The map on which the path is being found
|
||||
* @param mover The entity that is moving along the path
|
||||
* @param x The x coordinate of the tile being evaluated
|
||||
* @param y The y coordinate of the tile being evaluated
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty Teh y coordinate of the target location
|
||||
* @return The cost associated with the given tile
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.heuristics.ClosestHeuristic;
|
||||
|
||||
/**
|
||||
* A path finder implementation that uses the AStar heuristic based algorithm
|
||||
* to determine a path.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class AStarPathFinder implements PathFinder, PathFindingContext {
|
||||
/** The set of nodes that have been searched through */
|
||||
private ArrayList closed = new ArrayList();
|
||||
/** The set of nodes that we do not yet consider fully searched */
|
||||
private PriorityList open = new PriorityList();
|
||||
|
||||
/** The map being searched */
|
||||
private TileBasedMap map;
|
||||
/** The maximum depth of search we're willing to accept before giving up */
|
||||
private int maxSearchDistance;
|
||||
|
||||
/** The complete set of nodes across the map */
|
||||
private Node[][] nodes;
|
||||
/** True if we allow diaganol movement */
|
||||
private boolean allowDiagMovement;
|
||||
/** The heuristic we're applying to determine which nodes to search first */
|
||||
private AStarHeuristic heuristic;
|
||||
/** The node we're currently searching from */
|
||||
private Node current;
|
||||
|
||||
/** The mover going through the path */
|
||||
private Mover mover;
|
||||
/** The x coordinate of the source tile we're moving from */
|
||||
private int sourceX;
|
||||
/** The y coordinate of the source tile we're moving from */
|
||||
private int sourceY;
|
||||
/** The distance searched so far */
|
||||
private int distance;
|
||||
|
||||
/**
|
||||
* Create a path finder with the default heuristic - closest to target.
|
||||
*
|
||||
* @param map The map to be searched
|
||||
* @param maxSearchDistance The maximum depth we'll search before giving up
|
||||
* @param allowDiagMovement True if the search should try diaganol movement
|
||||
*/
|
||||
public AStarPathFinder(TileBasedMap map, int maxSearchDistance, boolean allowDiagMovement) {
|
||||
this(map, maxSearchDistance, allowDiagMovement, new ClosestHeuristic());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path finder
|
||||
*
|
||||
* @param heuristic The heuristic used to determine the search order of the map
|
||||
* @param map The map to be searched
|
||||
* @param maxSearchDistance The maximum depth we'll search before giving up
|
||||
* @param allowDiagMovement True if the search should try diaganol movement
|
||||
*/
|
||||
public AStarPathFinder(TileBasedMap map, int maxSearchDistance,
|
||||
boolean allowDiagMovement, AStarHeuristic heuristic) {
|
||||
this.heuristic = heuristic;
|
||||
this.map = map;
|
||||
this.maxSearchDistance = maxSearchDistance;
|
||||
this.allowDiagMovement = allowDiagMovement;
|
||||
|
||||
nodes = new Node[map.getWidthInTiles()][map.getHeightInTiles()];
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
nodes[x][y] = new Node(x,y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PathFinder#findPath(Mover, int, int, int, int)
|
||||
*/
|
||||
public Path findPath(Mover mover, int sx, int sy, int tx, int ty) {
|
||||
current = null;
|
||||
|
||||
// easy first check, if the destination is blocked, we can't get there
|
||||
this.mover = mover;
|
||||
this.sourceX = tx;
|
||||
this.sourceY = ty;
|
||||
this.distance = 0;
|
||||
|
||||
if (map.blocked(this, tx, ty)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
nodes[x][y].reset();
|
||||
}
|
||||
}
|
||||
|
||||
// initial state for A*. The closed group is empty. Only the starting
|
||||
// tile is in the open list and it's cost is zero, i.e. we're already there
|
||||
nodes[sx][sy].cost = 0;
|
||||
nodes[sx][sy].depth = 0;
|
||||
closed.clear();
|
||||
open.clear();
|
||||
addToOpen(nodes[sx][sy]);
|
||||
|
||||
nodes[tx][ty].parent = null;
|
||||
|
||||
// while we haven't found the goal and haven't exceeded our max search depth
|
||||
int maxDepth = 0;
|
||||
while ((maxDepth < maxSearchDistance) && (open.size() != 0)) {
|
||||
// pull out the first node in our open list, this is determined to
|
||||
// be the most likely to be the next step based on our heuristic
|
||||
int lx = sx;
|
||||
int ly = sy;
|
||||
if (current != null) {
|
||||
lx = current.x;
|
||||
ly = current.y;
|
||||
}
|
||||
|
||||
current = getFirstInOpen();
|
||||
distance = current.depth;
|
||||
|
||||
if (current == nodes[tx][ty]) {
|
||||
if (isValidLocation(mover,lx,ly,tx,ty)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removeFromOpen(current);
|
||||
addToClosed(current);
|
||||
|
||||
// search through all the neighbours of the current node evaluating
|
||||
// them as next steps
|
||||
for (int x=-1;x<2;x++) {
|
||||
for (int y=-1;y<2;y++) {
|
||||
// not a neighbour, its the current tile
|
||||
if ((x == 0) && (y == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we're not allowing diaganol movement then only
|
||||
// one of x or y can be set
|
||||
if (!allowDiagMovement) {
|
||||
if ((x != 0) && (y != 0)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// determine the location of the neighbour and evaluate it
|
||||
int xp = x + current.x;
|
||||
int yp = y + current.y;
|
||||
|
||||
if (isValidLocation(mover,current.x,current.y,xp,yp)) {
|
||||
// the cost to get to this node is cost the current plus the movement
|
||||
// cost to reach this node. Note that the heursitic value is only used
|
||||
// in the sorted open list
|
||||
float nextStepCost = current.cost + getMovementCost(mover, current.x, current.y, xp, yp);
|
||||
Node neighbour = nodes[xp][yp];
|
||||
map.pathFinderVisited(xp, yp);
|
||||
|
||||
// if the new cost we've determined for this node is lower than
|
||||
// it has been previously makes sure the node hasn't been discarded. We've
|
||||
// determined that there might have been a better path to get to
|
||||
// this node so it needs to be re-evaluated
|
||||
if (nextStepCost < neighbour.cost) {
|
||||
if (inOpenList(neighbour)) {
|
||||
removeFromOpen(neighbour);
|
||||
}
|
||||
if (inClosedList(neighbour)) {
|
||||
removeFromClosed(neighbour);
|
||||
}
|
||||
}
|
||||
|
||||
// if the node hasn't already been processed and discarded then
|
||||
// reset it's cost to our current cost and add it as a next possible
|
||||
// step (i.e. to the open list)
|
||||
if (!inOpenList(neighbour) && !(inClosedList(neighbour))) {
|
||||
neighbour.cost = nextStepCost;
|
||||
neighbour.heuristic = getHeuristicCost(mover, xp, yp, tx, ty);
|
||||
maxDepth = Math.max(maxDepth, neighbour.setParent(current));
|
||||
addToOpen(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since we've got an empty open list or we've run out of search
|
||||
// there was no path. Just return null
|
||||
if (nodes[tx][ty].parent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// At this point we've definitely found a path so we can uses the parent
|
||||
// references of the nodes to find out way from the target location back
|
||||
// to the start recording the nodes on the way.
|
||||
Path path = new Path();
|
||||
Node target = nodes[tx][ty];
|
||||
while (target != nodes[sx][sy]) {
|
||||
path.prependStep(target.x, target.y);
|
||||
target = target.parent;
|
||||
}
|
||||
path.prependStep(sx,sy);
|
||||
|
||||
// thats it, we have our path
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X coordinate of the node currently being evaluated
|
||||
*
|
||||
* @return The X coordinate of the node currently being evaluated
|
||||
*/
|
||||
public int getCurrentX() {
|
||||
if (current == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return current.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Y coordinate of the node currently being evaluated
|
||||
*
|
||||
* @return The Y coordinate of the node currently being evaluated
|
||||
*/
|
||||
public int getCurrentY() {
|
||||
if (current == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return current.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first element from the open list. This is the next
|
||||
* one to be searched.
|
||||
*
|
||||
* @return The first element in the open list
|
||||
*/
|
||||
protected Node getFirstInOpen() {
|
||||
return (Node) open.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the open list
|
||||
*
|
||||
* @param node The node to be added to the open list
|
||||
*/
|
||||
protected void addToOpen(Node node) {
|
||||
node.setOpen(true);
|
||||
open.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is in the open list
|
||||
*
|
||||
* @param node The node to check for
|
||||
* @return True if the node given is in the open list
|
||||
*/
|
||||
protected boolean inOpenList(Node node) {
|
||||
return node.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node from the open list
|
||||
*
|
||||
* @param node The node to remove from the open list
|
||||
*/
|
||||
protected void removeFromOpen(Node node) {
|
||||
node.setOpen(false);
|
||||
open.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the closed list
|
||||
*
|
||||
* @param node The node to add to the closed list
|
||||
*/
|
||||
protected void addToClosed(Node node) {
|
||||
node.setClosed(true);
|
||||
closed.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node supplied is in the closed list
|
||||
*
|
||||
* @param node The node to search for
|
||||
* @return True if the node specified is in the closed list
|
||||
*/
|
||||
protected boolean inClosedList(Node node) {
|
||||
return node.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node from the closed list
|
||||
*
|
||||
* @param node The node to remove from the closed list
|
||||
*/
|
||||
protected void removeFromClosed(Node node) {
|
||||
node.setClosed(false);
|
||||
closed.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given location is valid for the supplied mover
|
||||
*
|
||||
* @param mover The mover that would hold a given location
|
||||
* @param sx The starting x coordinate
|
||||
* @param sy The starting y coordinate
|
||||
* @param x The x coordinate of the location to check
|
||||
* @param y The y coordinate of the location to check
|
||||
* @return True if the location is valid for the given mover
|
||||
*/
|
||||
protected boolean isValidLocation(Mover mover, int sx, int sy, int x, int y) {
|
||||
boolean invalid = (x < 0) || (y < 0) || (x >= map.getWidthInTiles()) || (y >= map.getHeightInTiles());
|
||||
|
||||
if ((!invalid) && ((sx != x) || (sy != y))) {
|
||||
this.mover = mover;
|
||||
this.sourceX = sx;
|
||||
this.sourceY = sy;
|
||||
invalid = map.blocked(this, x, y);
|
||||
}
|
||||
|
||||
return !invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost to move through a given location
|
||||
*
|
||||
* @param mover The entity that is being moved
|
||||
* @param sx The x coordinate of the tile whose cost is being determined
|
||||
* @param sy The y coordiante of the tile whose cost is being determined
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The cost of movement through the given tile
|
||||
*/
|
||||
public float getMovementCost(Mover mover, int sx, int sy, int tx, int ty) {
|
||||
this.mover = mover;
|
||||
this.sourceX = sx;
|
||||
this.sourceY = sy;
|
||||
|
||||
return map.getCost(this, tx, ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heuristic cost for the given location. This determines in which
|
||||
* order the locations are processed.
|
||||
*
|
||||
* @param mover The entity that is being moved
|
||||
* @param x The x coordinate of the tile whose cost is being determined
|
||||
* @param y The y coordiante of the tile whose cost is being determined
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The heuristic cost assigned to the tile
|
||||
*/
|
||||
public float getHeuristicCost(Mover mover, int x, int y, int tx, int ty) {
|
||||
return heuristic.getCost(map, mover, x, y, tx, ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list that sorts any element provided into the list
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
private class PriorityList {
|
||||
/** The list of elements */
|
||||
private List list = new LinkedList();
|
||||
|
||||
/**
|
||||
* Retrieve the first element from the list
|
||||
*
|
||||
* @return The first element from the list
|
||||
*/
|
||||
public Object first() {
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the list
|
||||
*/
|
||||
public void clear() {
|
||||
list.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the list - causes sorting
|
||||
*
|
||||
* @param o The element to add
|
||||
*/
|
||||
public void add(Object o) {
|
||||
// float the new entry
|
||||
for (int i=0;i<list.size();i++) {
|
||||
if (((Comparable) list.get(i)).compareTo(o) > 0) {
|
||||
list.add(i, o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!list.contains(o)) {
|
||||
list.add(o);
|
||||
}
|
||||
//Collections.sort(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the list
|
||||
*
|
||||
* @param o The element to remove
|
||||
*/
|
||||
public void remove(Object o) {
|
||||
list.remove(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of elements in the list
|
||||
*
|
||||
* @return The number of element in the list
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is in the list
|
||||
*
|
||||
* @param o The element to search for
|
||||
* @return True if the element is in the list
|
||||
*/
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String temp = "{";
|
||||
for (int i=0;i<size();i++) {
|
||||
temp += list.get(i).toString()+",";
|
||||
}
|
||||
temp += "}";
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single node in the search graph
|
||||
*/
|
||||
private class Node implements Comparable {
|
||||
/** The x coordinate of the node */
|
||||
private int x;
|
||||
/** The y coordinate of the node */
|
||||
private int y;
|
||||
/** The path cost for this node */
|
||||
private float cost;
|
||||
/** The parent of this node, how we reached it in the search */
|
||||
private Node parent;
|
||||
/** The heuristic cost of this node */
|
||||
private float heuristic;
|
||||
/** The search depth of this node */
|
||||
private int depth;
|
||||
/** In the open list */
|
||||
private boolean open;
|
||||
/** In the closed list */
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* Create a new node
|
||||
*
|
||||
* @param x The x coordinate of the node
|
||||
* @param y The y coordinate of the node
|
||||
*/
|
||||
public Node(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent of this node
|
||||
*
|
||||
* @param parent The parent node which lead us to this node
|
||||
* @return The depth we have no reached in searching
|
||||
*/
|
||||
public int setParent(Node parent) {
|
||||
depth = parent.depth + 1;
|
||||
this.parent = parent;
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Comparable#compareTo(Object)
|
||||
*/
|
||||
public int compareTo(Object other) {
|
||||
Node o = (Node) other;
|
||||
|
||||
float f = heuristic + cost;
|
||||
float of = o.heuristic + o.cost;
|
||||
|
||||
if (f < of) {
|
||||
return -1;
|
||||
} else if (f > of) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the node is in the open list
|
||||
*
|
||||
* @param open True if the node is in the open list
|
||||
*/
|
||||
public void setOpen(boolean open) {
|
||||
this.open = open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is in the open list
|
||||
*
|
||||
* @return True if the node is in the open list
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the node is in the closed list
|
||||
*
|
||||
* @param closed True if the node is in the closed list
|
||||
*/
|
||||
public void setClosed(boolean closed) {
|
||||
this.closed = closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is in the closed list
|
||||
*
|
||||
* @return True if the node is in the closed list
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of this node
|
||||
*/
|
||||
public void reset() {
|
||||
closed = false;
|
||||
open = false;
|
||||
cost = 0;
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Node "+x+","+y+"]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getMover()
|
||||
*/
|
||||
public Mover getMover() {
|
||||
return mover;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSearchDistance()
|
||||
*/
|
||||
public int getSearchDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSourceX()
|
||||
*/
|
||||
public int getSourceX() {
|
||||
return sourceX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSourceY()
|
||||
*/
|
||||
public int getSourceY() {
|
||||
return sourceY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* A tagging interface for an object representing the entity in the game that
|
||||
* is going to moving along the path. This allows us to pass around entity/state
|
||||
* information to determine whether a particular tile is blocked, or how much
|
||||
* cost to apply on a particular tile.
|
||||
*
|
||||
* For instance, a Mover might represent a tank or plane on a game map. Passing round
|
||||
* this entity allows us to determine whether rough ground on a map should effect
|
||||
* the unit's cost for moving through the tile.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface Mover {
|
||||
|
||||
}
|
||||
158
lib/slick-source/org/newdawn/slick/util/pathfinding/Path.java
Normal file
158
lib/slick-source/org/newdawn/slick/util/pathfinding/Path.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A path determined by some path finding algorithm. A series of steps from
|
||||
* the starting location to the target location. This includes a step for the
|
||||
* initial location.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class Path implements Serializable {
|
||||
/** The serial identifier for this class */
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** The list of steps building up this path */
|
||||
private ArrayList steps = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create an empty path
|
||||
*/
|
||||
public Path() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the path, i.e. the number of steps
|
||||
*
|
||||
* @return The number of steps in this path
|
||||
*/
|
||||
public int getLength() {
|
||||
return steps.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the step at a given index in the path
|
||||
*
|
||||
* @param index The index of the step to retrieve. Note this should
|
||||
* be >= 0 and < getLength();
|
||||
* @return The step information, the position on the map.
|
||||
*/
|
||||
public Step getStep(int index) {
|
||||
return (Step) steps.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate for the step at the given index
|
||||
*
|
||||
* @param index The index of the step whose x coordinate should be retrieved
|
||||
* @return The x coordinate at the step
|
||||
*/
|
||||
public int getX(int index) {
|
||||
return getStep(index).x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate for the step at the given index
|
||||
*
|
||||
* @param index The index of the step whose y coordinate should be retrieved
|
||||
* @return The y coordinate at the step
|
||||
*/
|
||||
public int getY(int index) {
|
||||
return getStep(index).y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step to the path.
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public void appendStep(int x, int y) {
|
||||
steps.add(new Step(x,y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a step to the path.
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public void prependStep(int x, int y) {
|
||||
steps.add(0, new Step(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this path contains the given step
|
||||
*
|
||||
* @param x The x coordinate of the step to check for
|
||||
* @param y The y coordinate of the step to check for
|
||||
* @return True if the path contains the given step
|
||||
*/
|
||||
public boolean contains(int x, int y) {
|
||||
return steps.contains(new Step(x,y));
|
||||
}
|
||||
|
||||
/**
|
||||
* A single step within the path
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class Step implements Serializable {
|
||||
/** The x coordinate at the given step */
|
||||
private int x;
|
||||
/** The y coordinate at the given step */
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* Create a new step
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public Step(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the new step
|
||||
*
|
||||
* @return The x coodindate of the new step
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the new step
|
||||
*
|
||||
* @return The y coodindate of the new step
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
return x*y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Object#equals(Object)
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof Step) {
|
||||
Step o = (Step) other;
|
||||
|
||||
return (o.x == x) && (o.y == y);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* A description of an implementation that can find a path from one
|
||||
* location on a tile map to another based on information provided
|
||||
* by that tile map.
|
||||
*
|
||||
* @see TileBasedMap
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface PathFinder {
|
||||
|
||||
/**
|
||||
* Find a path from the starting location provided (sx,sy) to the target
|
||||
* location (tx,ty) avoiding blockages and attempting to honour costs
|
||||
* provided by the tile map.
|
||||
*
|
||||
* @param mover The entity that will be moving along the path. This provides
|
||||
* a place to pass context information about the game entity doing the moving, e.g.
|
||||
* can it fly? can it swim etc.
|
||||
*
|
||||
* @param sx The x coordinate of the start location
|
||||
* @param sy The y coordinate of the start location
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty Teh y coordinate of the target location
|
||||
* @return The path found from start to end, or null if no path can be found.
|
||||
*/
|
||||
public Path findPath(Mover mover, int sx, int sy, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The context describing the current path finding state
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface PathFindingContext {
|
||||
/**
|
||||
* Get the object being moved along the path if any
|
||||
*
|
||||
* @return The object being moved along the path
|
||||
*/
|
||||
public Mover getMover();
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the source location
|
||||
*
|
||||
* @return The x coordinate of the source location
|
||||
*/
|
||||
public int getSourceX();
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the source location
|
||||
*
|
||||
* @return The y coordinate of the source location
|
||||
*/
|
||||
public int getSourceY();
|
||||
|
||||
/**
|
||||
* Get the distance that has been searched to reach this point
|
||||
*
|
||||
* @return The distance that has been search to reach this point
|
||||
*/
|
||||
public int getSearchDistance();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The description for the data we're pathfinding over. This provides the contract
|
||||
* between the data being searched (i.e. the in game map) and the path finding
|
||||
* generic tools
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface TileBasedMap {
|
||||
/**
|
||||
* Get the width of the tile map. The slightly odd name is used
|
||||
* to distiguish this method from commonly used names in game maps.
|
||||
*
|
||||
* @return The number of tiles across the map
|
||||
*/
|
||||
public int getWidthInTiles();
|
||||
|
||||
/**
|
||||
* Get the height of the tile map. The slightly odd name is used
|
||||
* to distiguish this method from commonly used names in game maps.
|
||||
*
|
||||
* @return The number of tiles down the map
|
||||
*/
|
||||
public int getHeightInTiles();
|
||||
|
||||
/**
|
||||
* Notification that the path finder visited a given tile. This is
|
||||
* used for debugging new heuristics.
|
||||
*
|
||||
* @param x The x coordinate of the tile that was visited
|
||||
* @param y The y coordinate of the tile that was visited
|
||||
*/
|
||||
public void pathFinderVisited(int x, int y);
|
||||
|
||||
/**
|
||||
* Check if the given location is blocked, i.e. blocks movement of
|
||||
* the supplied mover.
|
||||
*
|
||||
* @param context The context describing the pathfinding at the time of this request
|
||||
* @param tx The x coordinate of the tile we're moving to
|
||||
* @param ty The y coordinate of the tile we're moving to
|
||||
* @return True if the location is blocked
|
||||
*/
|
||||
public boolean blocked(PathFindingContext context, int tx, int ty);
|
||||
|
||||
/**
|
||||
* Get the cost of moving through the given tile. This can be used to
|
||||
* make certain areas more desirable. A simple and valid implementation
|
||||
* of this method would be to return 1 in all cases.
|
||||
*
|
||||
* @param context The context describing the pathfinding at the time of this request
|
||||
* @param tx The x coordinate of the tile we're moving to
|
||||
* @param ty The y coordinate of the tile we're moving to
|
||||
* @return The relative cost of moving across the given tile
|
||||
*/
|
||||
public float getCost(PathFindingContext context, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that uses the tile that is closest to the target
|
||||
* as the next best tile.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ClosestHeuristic implements AStarHeuristic {
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty) {
|
||||
float dx = tx - x;
|
||||
float dy = ty - y;
|
||||
|
||||
float result = (float) (Math.sqrt((dx*dx)+(dy*dy)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that uses the tile that is closest to the target
|
||||
* as the next best tile. In this case the sqrt is removed
|
||||
* and the distance squared is used instead
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ClosestSquaredHeuristic implements AStarHeuristic {
|
||||
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty) {
|
||||
float dx = tx - x;
|
||||
float dy = ty - y;
|
||||
|
||||
return ((dx*dx)+(dy*dy));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that drives the search based on the Manhattan distance
|
||||
* between the current location and the target
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ManhattanHeuristic implements AStarHeuristic {
|
||||
/** The minimum movement cost from any one square to the next */
|
||||
private int minimumCost;
|
||||
|
||||
/**
|
||||
* Create a new heuristic
|
||||
*
|
||||
* @param minimumCost The minimum movement cost from any one square to the next
|
||||
*/
|
||||
public ManhattanHeuristic(int minimumCost) {
|
||||
this.minimumCost = minimumCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx,
|
||||
int ty) {
|
||||
return minimumCost * (Math.abs(x-tx) + Math.abs(y-ty));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
/**
|
||||
* A link between this space and another
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Link {
|
||||
/** The x coordinate of the joining point */
|
||||
private float px;
|
||||
/** The y coordinate of the joining point */
|
||||
private float py;
|
||||
/** The target space we'd be linking to */
|
||||
private Space target;
|
||||
|
||||
/**
|
||||
* Create a new link
|
||||
*
|
||||
* @param px The x coordinate of the linking point
|
||||
* @param py The y coordinate of the linking point
|
||||
* @param target The target space we're linking to
|
||||
*/
|
||||
public Link(float px, float py, Space target) {
|
||||
this.px = px;
|
||||
this.py = py;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance squared from this link to the given position
|
||||
*
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The distance squared from this link to the target
|
||||
*/
|
||||
public float distance2(float tx, float ty) {
|
||||
float dx = tx - px;
|
||||
float dy = ty - py;
|
||||
|
||||
return ((dx*dx) + (dy*dy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the link
|
||||
*
|
||||
* @return The x coordinate of the link
|
||||
*/
|
||||
public float getX() {
|
||||
return px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the link
|
||||
*
|
||||
* @return The y coordinate of the link
|
||||
*/
|
||||
public float getY() {
|
||||
return py;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space this object links to
|
||||
*
|
||||
* @return The space this object links to
|
||||
*/
|
||||
public Space getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A nav-mesh is a set of shapes that describe the navigation of a map. These
|
||||
* shapes are linked together allow path finding but without the high
|
||||
* resolution that tile maps require. This leads to fast path finding and
|
||||
* potentially much more accurate map definition.
|
||||
*
|
||||
* @author kevin
|
||||
*
|
||||
*/
|
||||
public class NavMesh {
|
||||
/** The list of spaces that build up this navigation mesh */
|
||||
private ArrayList spaces = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create a new empty mesh
|
||||
*/
|
||||
public NavMesh() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mesh with a set of spaces
|
||||
*
|
||||
* @param spaces The spaces included in the mesh
|
||||
*/
|
||||
public NavMesh(ArrayList spaces) {
|
||||
this.spaces.addAll(spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of spaces that are in the mesh
|
||||
*
|
||||
* @return The spaces in the mesh
|
||||
*/
|
||||
public int getSpaceCount() {
|
||||
return spaces.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space at a given index
|
||||
*
|
||||
* @param index The index of the space to retrieve
|
||||
* @return The space at the given index
|
||||
*/
|
||||
public Space getSpace(int index) {
|
||||
return (Space) spaces.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single space to the mesh
|
||||
*
|
||||
* @param space The space to be added
|
||||
*/
|
||||
public void addSpace(Space space) {
|
||||
spaces.add(space);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the space at a given location
|
||||
*
|
||||
* @param x The x coordinate at which to find the space
|
||||
* @param y The y coordinate at which to find the space
|
||||
* @return The space at the given location
|
||||
*/
|
||||
public Space findSpace(float x, float y) {
|
||||
for (int i=0;i<spaces.size();i++) {
|
||||
Space space = getSpace(i);
|
||||
if (space.contains(x,y)) {
|
||||
return space;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a path from the source to the target coordinates
|
||||
*
|
||||
* @param sx The x coordinate of the source location
|
||||
* @param sy The y coordinate of the source location
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @param optimize True if paths should be optimized
|
||||
* @return The path between the two spaces
|
||||
*/
|
||||
public NavPath findPath(float sx, float sy, float tx, float ty, boolean optimize) {
|
||||
Space source = findSpace(sx,sy);
|
||||
Space target = findSpace(tx,ty);
|
||||
|
||||
if ((source == null) || (target == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i=0;i<spaces.size();i++) {
|
||||
((Space) spaces.get(i)).clearCost();
|
||||
}
|
||||
target.fill(source,tx, ty, 0);
|
||||
if (target.getCost() == Float.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
if (source.getCost() == Float.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NavPath path = new NavPath();
|
||||
path.push(new Link(sx, sy, null));
|
||||
if (source.pickLowestCost(target, path)) {
|
||||
path.push(new Link(tx, ty, null));
|
||||
if (optimize) {
|
||||
optimize(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a particular path is clear
|
||||
*
|
||||
* @param x1 The x coordinate of the starting point
|
||||
* @param y1 The y coordinate of the starting point
|
||||
* @param x2 The x coordinate of the ending point
|
||||
* @param y2 The y coordinate of the ending point
|
||||
* @param step The size of the step between points
|
||||
* @return True if there are no blockages along the path
|
||||
*/
|
||||
private boolean isClear(float x1, float y1, float x2, float y2, float step) {
|
||||
float dx = (x2 - x1);
|
||||
float dy = (y2 - y1);
|
||||
float len = (float) Math.sqrt((dx*dx)+(dy*dy));
|
||||
dx *= step;
|
||||
dx /= len;
|
||||
dy *= step;
|
||||
dy /= len;
|
||||
int steps = (int) (len / step);
|
||||
|
||||
for (int i=0;i<steps;i++) {
|
||||
float x = x1 + (dx*i);
|
||||
float y = y1 + (dy*i);
|
||||
|
||||
if (findSpace(x,y) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize a path by removing segments that arn't required
|
||||
* to reach the end point
|
||||
*
|
||||
* @param path The path to optimize. Redundant segments will be removed
|
||||
*/
|
||||
private void optimize(NavPath path) {
|
||||
int pt = 0;
|
||||
|
||||
while (pt < path.length()-2) {
|
||||
float sx = path.getX(pt);
|
||||
float sy = path.getY(pt);
|
||||
float nx = path.getX(pt+2);
|
||||
float ny = path.getY(pt+2);
|
||||
|
||||
if (isClear(sx,sy,nx,ny,0.1f)) {
|
||||
path.remove(pt+1);
|
||||
} else {
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.PathFindingContext;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* The builder responsible for converting a tile based map into
|
||||
* a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class NavMeshBuilder implements PathFindingContext {
|
||||
/** The current x position we're searching */
|
||||
private int sx;
|
||||
/** The current y position we've searching */
|
||||
private int sy;
|
||||
/** The smallest space allowed */
|
||||
private float smallestSpace = 0.2f;
|
||||
/** True if we're working tile based */
|
||||
private boolean tileBased;
|
||||
|
||||
/**
|
||||
* Build a navigation mesh based on a tile map
|
||||
*
|
||||
* @param map The map to build the navigation mesh from
|
||||
*
|
||||
* @return The newly created navigation mesh
|
||||
*/
|
||||
public NavMesh build(TileBasedMap map) {
|
||||
return build(map, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a navigation mesh based on a tile map
|
||||
*
|
||||
* @param map The map to build the navigation mesh from
|
||||
* @param tileBased True if we'll use the tiles for the mesh initially
|
||||
* rather than quad spacing
|
||||
* @return The newly created navigation mesh
|
||||
*/
|
||||
public NavMesh build(TileBasedMap map, boolean tileBased) {
|
||||
this.tileBased = tileBased;
|
||||
|
||||
ArrayList spaces = new ArrayList();
|
||||
|
||||
if (tileBased) {
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
if (!map.blocked(this, x, y)) {
|
||||
spaces.add(new Space(x,y,1,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Space space = new Space(0,0,map.getWidthInTiles(),map.getHeightInTiles());
|
||||
|
||||
subsection(map, space, spaces);
|
||||
}
|
||||
|
||||
while (mergeSpaces(spaces)) {}
|
||||
linkSpaces(spaces);
|
||||
|
||||
return new NavMesh(spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the spaces that have been created to optimize out anywhere
|
||||
* we can.
|
||||
*
|
||||
* @param spaces The list of spaces to be merged
|
||||
* @return True if a merge occured and we'll have to start the merge
|
||||
* process again
|
||||
*/
|
||||
private boolean mergeSpaces(ArrayList spaces) {
|
||||
for (int source=0;source<spaces.size();source++) {
|
||||
Space a = (Space) spaces.get(source);
|
||||
|
||||
for (int target=source+1;target<spaces.size();target++) {
|
||||
Space b = (Space) spaces.get(target);
|
||||
|
||||
if (a.canMerge(b)) {
|
||||
spaces.remove(a);
|
||||
spaces.remove(b);
|
||||
spaces.add(a.merge(b));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the links between spaces
|
||||
*
|
||||
* @param spaces The spaces to link up
|
||||
*/
|
||||
private void linkSpaces(ArrayList spaces) {
|
||||
for (int source=0;source<spaces.size();source++) {
|
||||
Space a = (Space) spaces.get(source);
|
||||
|
||||
for (int target=source+1;target<spaces.size();target++) {
|
||||
Space b = (Space) spaces.get(target);
|
||||
|
||||
if (a.hasJoinedEdge(b)) {
|
||||
a.link(b);
|
||||
b.link(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a particular space is clear of blockages
|
||||
*
|
||||
* @param map The map the spaces are being built from
|
||||
* @param space The space to check
|
||||
* @return True if there are no blockages in the space
|
||||
*/
|
||||
public boolean clear(TileBasedMap map, Space space) {
|
||||
if (tileBased) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float x = 0;
|
||||
boolean donex = false;
|
||||
|
||||
while (x < space.getWidth()) {
|
||||
float y = 0;
|
||||
boolean doney = false;
|
||||
|
||||
while (y < space.getHeight()) {
|
||||
sx = (int) (space.getX()+x);
|
||||
sy = (int) (space.getY()+y);
|
||||
|
||||
if (map.blocked(this, sx, sy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
y += 0.1f;
|
||||
if ((y > space.getHeight()) && (!doney)) {
|
||||
y = space.getHeight();
|
||||
doney = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
x += 0.1f;
|
||||
if ((x > space.getWidth()) && (!donex)) {
|
||||
x = space.getWidth();
|
||||
donex = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subsection a space into smaller spaces if required to find a non-blocked
|
||||
* area.
|
||||
*
|
||||
* @param map The map being processed
|
||||
* @param space The space being sections
|
||||
* @param spaces The list of spaces that have been created
|
||||
*/
|
||||
private void subsection(TileBasedMap map, Space space, ArrayList spaces) {
|
||||
if (!clear(map, space)) {
|
||||
float width2 = space.getWidth()/2;
|
||||
float height2 = space.getHeight()/2;
|
||||
|
||||
if ((width2 < smallestSpace) && (height2 < smallestSpace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
subsection(map, new Space(space.getX(), space.getY(), width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX(), space.getY()+height2, width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX()+width2, space.getY(), width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX()+width2, space.getY()+height2, width2, height2), spaces);
|
||||
} else {
|
||||
spaces.add(space);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current mover
|
||||
*/
|
||||
public Mover getMover() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current search distance
|
||||
*/
|
||||
public int getSearchDistance() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current x location
|
||||
*/
|
||||
public int getSourceX() {
|
||||
return sx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current y location
|
||||
*/
|
||||
public int getSourceY() {
|
||||
return sy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A path across a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class NavPath {
|
||||
/** The list of links that form this path */
|
||||
private ArrayList links = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create a new path
|
||||
*/
|
||||
public NavPath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a link to the end of the path
|
||||
*
|
||||
* @param link The link to the end of the path
|
||||
*/
|
||||
public void push(Link link) {
|
||||
links.add(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the path
|
||||
*
|
||||
* @return The number of steps in the path
|
||||
*/
|
||||
public int length() {
|
||||
return links.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the given step
|
||||
*
|
||||
* @param step The index of the step to retrieve
|
||||
* @return The x coordinate at the given step index
|
||||
*/
|
||||
public float getX(int step) {
|
||||
return ((Link) links.get(step)).getX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the given step
|
||||
*
|
||||
* @param step The index of the step to retrieve
|
||||
* @return The y coordinate at the given step index
|
||||
*/
|
||||
public float getY(int step) {
|
||||
return ((Link) links.get(step)).getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of this instance
|
||||
*
|
||||
* @return The string representation of this instance
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Path length="+length()+"]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a step in the path
|
||||
*
|
||||
* @param i
|
||||
*/
|
||||
public void remove(int i) {
|
||||
links.remove(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A quad space within a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Space {
|
||||
/** The x coordinate of the top corner of the space */
|
||||
private float x;
|
||||
/** The y coordinate of the top corner of the space */
|
||||
private float y;
|
||||
/** The width of the space */
|
||||
private float width;
|
||||
/** The height of the space */
|
||||
private float height;
|
||||
|
||||
/** A map from spaces to the links that connect them to this space */
|
||||
private HashMap links = new HashMap();
|
||||
/** A list of the links from this space to others */
|
||||
private ArrayList linksList = new ArrayList();
|
||||
/** The cost to get to this node */
|
||||
private float cost;
|
||||
|
||||
/**
|
||||
* Create a new space
|
||||
*
|
||||
* @param x The x coordinate of the top corner of the space
|
||||
* @param y The y coordinate of the top corner of the space
|
||||
* @param width The width of the space
|
||||
* @param height The height of the space
|
||||
*/
|
||||
public Space(float x, float y, float width, float height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the space
|
||||
*
|
||||
* @return The width of the space
|
||||
*/
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the space
|
||||
*
|
||||
* @return The height of the space
|
||||
*/
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the top corner of the space
|
||||
*
|
||||
* @return The x coordinate of the top corner of the space
|
||||
*/
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the top corner of the space
|
||||
*
|
||||
* @return The y coordinate of the top corner of the space
|
||||
*/
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link this space to another by creating a link and finding the point
|
||||
* at which the spaces link up
|
||||
*
|
||||
* @param other The other space to link to
|
||||
*/
|
||||
public void link(Space other) {
|
||||
// aligned vertical edges
|
||||
if (inTolerance(x,other.x+other.width) || inTolerance(x+width, other.x)) {
|
||||
float linkx = x;
|
||||
if (x+width == other.x) {
|
||||
linkx = x+width;
|
||||
}
|
||||
|
||||
float top = Math.max(y, other.y);
|
||||
float bottom = Math.min(y+height, other.y+other.height);
|
||||
float linky = top + ((bottom-top)/2);
|
||||
|
||||
Link link = new Link(linkx, linky, other);
|
||||
links.put(other,link);
|
||||
linksList.add(link);
|
||||
}
|
||||
// aligned horizontal edges
|
||||
if (inTolerance(y, other.y+other.height) || inTolerance(y+height, other.y)) {
|
||||
float linky = y;
|
||||
if (y+height == other.y) {
|
||||
linky = y+height;
|
||||
}
|
||||
|
||||
float left = Math.max(x, other.x);
|
||||
float right = Math.min(x+width, other.x+other.width);
|
||||
float linkx = left + ((right-left)/2);
|
||||
|
||||
Link link = new Link(linkx, linky, other);
|
||||
links.put(other, link);
|
||||
linksList.add(link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether two locations are within tolerance distance. This is
|
||||
* used when finding aligned edges to remove float rounding errors
|
||||
*
|
||||
* @param a The first value
|
||||
* @param b The second value
|
||||
* @return True if the edges are close enough (tm)
|
||||
*/
|
||||
private boolean inTolerance(float a, float b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this space has an edge that is joined with another
|
||||
*
|
||||
* @param other The other space to check against
|
||||
* @return True if the spaces have a shared edge
|
||||
*/
|
||||
public boolean hasJoinedEdge(Space other) {
|
||||
// aligned vertical edges
|
||||
if (inTolerance(x,other.x+other.width) || inTolerance(x+width,other.x)) {
|
||||
if ((y >= other.y) && (y <= other.y + other.height)) {
|
||||
return true;
|
||||
}
|
||||
if ((y+height >= other.y) && (y+height <= other.y + other.height)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.y >= y) && (other.y <= y + height)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.y+other.height >= y) && (other.y+other.height <= y + height)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// aligned horizontal edges
|
||||
if (inTolerance(y, other.y+other.height) || inTolerance(y+height, other.y)) {
|
||||
if ((x >= other.x) && (x <= other.x + other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((x+width >= other.x) && (x+width <= other.x + other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.x >= x) && (other.x <= x + width)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.x+other.width >= x) && (other.x+other.width <= x + width)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this space with another
|
||||
*
|
||||
* @param other The other space to merge with
|
||||
* @return The result space created by joining the two
|
||||
*/
|
||||
public Space merge(Space other) {
|
||||
float minx = Math.min(x, other.x);
|
||||
float miny = Math.min(y, other.y);
|
||||
|
||||
float newwidth = width+other.width;
|
||||
float newheight = height+other.height;
|
||||
if (x == other.x) {
|
||||
newwidth = width;
|
||||
} else {
|
||||
newheight = height;
|
||||
}
|
||||
return new Space(minx, miny, newwidth, newheight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given space can be merged with this one. It must have
|
||||
* an adjacent edge and have the same height or width as this space.
|
||||
*
|
||||
* @param other The other space to be considered
|
||||
* @return True if the spaces can be joined together
|
||||
*/
|
||||
public boolean canMerge(Space other) {
|
||||
if (!hasJoinedEdge(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((x == other.x) && (width == other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((y == other.y) && (height == other.height)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of links
|
||||
*
|
||||
* @return The number of links from the space to others
|
||||
*/
|
||||
public int getLinkCount() {
|
||||
return linksList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link from this space to another at a particular index
|
||||
*
|
||||
* @param index The index of the link to retrieve
|
||||
* @return The link from this space to another
|
||||
*/
|
||||
public Link getLink(int index) {
|
||||
return (Link) linksList.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this space contains a given point
|
||||
*
|
||||
* @param xp The x coordinate to check
|
||||
* @param yp The y coordinate to check
|
||||
* @return True if this space container the coordinate given
|
||||
*/
|
||||
public boolean contains(float xp, float yp) {
|
||||
return (xp >= x) && (xp < x+width) && (yp >= y) && (yp < y+height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the spaces based on the cost from a given starting point
|
||||
*
|
||||
* @param target The target space we're heading for
|
||||
* @param sx The x coordinate of the starting point
|
||||
* @param sy The y coordinate of the starting point
|
||||
* @param cost The cost up to this point
|
||||
*/
|
||||
public void fill(Space target, float sx, float sy, float cost) {
|
||||
if (cost >= this.cost) {
|
||||
return;
|
||||
}
|
||||
this.cost = cost;
|
||||
if (target == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0;i<getLinkCount();i++) {
|
||||
Link link = getLink(i);
|
||||
float extraCost = link.distance2(sx,sy);
|
||||
float nextCost = cost + extraCost;
|
||||
link.getTarget().fill(target, link.getX(), link.getY(), nextCost);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the costing values across the whole map
|
||||
*/
|
||||
public void clearCost() {
|
||||
cost = Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost to get to this node at the moment
|
||||
*
|
||||
* @return The cost to get to this node
|
||||
*/
|
||||
public float getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the lowest cost route from this space to another on the path
|
||||
*
|
||||
* @param target The target space we're looking for
|
||||
* @param path The path to add the steps to
|
||||
* @return True if the path was found
|
||||
*/
|
||||
public boolean pickLowestCost(Space target, NavPath path) {
|
||||
if (target == this) {
|
||||
return true;
|
||||
}
|
||||
if (links.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Link bestLink = null;
|
||||
for (int i=0;i<getLinkCount();i++) {
|
||||
Link link = getLink(i);
|
||||
if ((bestLink == null) || (link.getTarget().getCost() < bestLink.getTarget().getCost())) {
|
||||
bestLink = link;
|
||||
}
|
||||
}
|
||||
|
||||
path.push(bestLink);
|
||||
return bestLink.getTarget().pickLowestCost(target, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation of this instance
|
||||
*
|
||||
* @return The string representation of this instance
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Space "+x+","+y+" "+width+","+height+"]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
A set of classes to provide configurable A* path finding on tilebased maps
|
||||
</BODY>
|
||||
Reference in New Issue
Block a user