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+"]";
}
}