Improved path finding algorithm to A*

This commit is contained in:
Collin Smith 2019-02-18 00:47:50 -08:00
parent 95d61fc4d7
commit 9e0df071f2
7 changed files with 390 additions and 5 deletions

View File

@ -0,0 +1,222 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package gdx.diablo.map;
/** @author Nathan Sweet */
public class BinaryHeap<T extends BinaryHeap.Node> {
public int size;
private Node[] nodes;
private final boolean isMaxHeap;
public BinaryHeap () {
this(16, false);
}
public BinaryHeap (int capacity, boolean isMaxHeap) {
this.isMaxHeap = isMaxHeap;
nodes = new Node[capacity];
}
public T add (T node) {
// Expand if necessary.
if (size == nodes.length) {
Node[] newNodes = new Node[size << 1];
System.arraycopy(nodes, 0, newNodes, 0, size);
nodes = newNodes;
}
// Insert at end and bubble up.
node.index = size;
nodes[size] = node;
up(size++);
return node;
}
public T add (T node, float value) {
node.value = value;
return add(node);
}
/** Returns if binary heap contains the provided node.
* @param node May be null.
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */
public boolean contains (T node, boolean identity) {
if (identity || node == null) {
for (Node n : nodes)
if (n == node) return true;
} else {
for (Node n : nodes)
if (n != null && n.equals(node)) return true;
}
return false;
}
public T peek () {
if (size == 0) throw new IllegalStateException("The heap is empty.");
return (T)nodes[0];
}
public T pop () {
return remove(0);
}
public T remove (T node) {
return remove(node.index);
}
private T remove (int index) {
Node[] nodes = this.nodes;
Node removed = nodes[index];
nodes[index] = nodes[--size];
nodes[size] = null;
if (size > 0 && index < size) down(index);
return (T)removed;
}
/** Returns true if the heap is empty. */
public boolean isEmpty () {
return size == 0;
}
public void clear () {
Node[] nodes = this.nodes;
for (int i = 0, n = size; i < n; i++)
nodes[i] = null;
size = 0;
}
public void setValue (T node, float value) {
float oldValue = node.value;
node.value = value;
if (value < oldValue ^ isMaxHeap)
up(node.index);
else
down(node.index);
}
private void up (int index) {
Node[] nodes = this.nodes;
Node node = nodes[index];
float value = node.value;
while (index > 0) {
int parentIndex = (index - 1) >> 1;
Node parent = nodes[parentIndex];
if (value < parent.value ^ isMaxHeap) {
nodes[index] = parent;
parent.index = index;
index = parentIndex;
} else
break;
}
nodes[index] = node;
node.index = index;
}
private void down (int index) {
Node[] nodes = this.nodes;
int size = this.size;
Node node = nodes[index];
float value = node.value;
while (true) {
int leftIndex = 1 + (index << 1);
if (leftIndex >= size) break;
int rightIndex = leftIndex + 1;
// Always have a left child.
Node leftNode = nodes[leftIndex];
float leftValue = leftNode.value;
// May have a right child.
Node rightNode;
float rightValue;
if (rightIndex >= size) {
rightNode = null;
rightValue = isMaxHeap ? Float.MIN_VALUE : Float.MAX_VALUE;
} else {
rightNode = nodes[rightIndex];
rightValue = rightNode.value;
}
// The smallest of the three values is the parent.
if (leftValue < rightValue ^ isMaxHeap) {
if (leftValue == value || (leftValue > value ^ isMaxHeap)) break;
nodes[index] = leftNode;
leftNode.index = index;
index = leftIndex;
} else {
if (rightValue == value || (rightValue > value ^ isMaxHeap)) break;
nodes[index] = rightNode;
rightNode.index = index;
index = rightIndex;
}
}
nodes[index] = node;
node.index = index;
}
@Override
public boolean equals (Object obj) {
if (!(obj instanceof BinaryHeap)) return false;
BinaryHeap other = (BinaryHeap)obj;
if (other.size != size) return false;
for (int i = 0, n = size; i < n; i++)
if (other.nodes[i].value != nodes[i].value) return false;
return true;
}
public int hashCode () {
int h = 1;
for (int i = 0, n = size; i < n; i++)
h = h * 31 + Float.floatToIntBits(nodes[i].value);
return h;
}
public String toString () {
if (size == 0) return "[]";
Node[] nodes = this.nodes;
StringBuilder buffer = new StringBuilder(32);
buffer.append('[');
buffer.append(nodes[0].value);
for (int i = 1; i < size; i++) {
buffer.append(", ");
buffer.append(nodes[i].value);
}
buffer.append(']');
return buffer.toString();
}
/** @author Nathan Sweet */
static public class Node {
float value;
int index;
public Node (float value) {
this.value = value;
}
public float getValue () {
return value;
}
public String toString () {
return Float.toString(value);
}
}
}

View File

@ -546,9 +546,14 @@ public class Map implements Disposable {
}
}
public GraphPath<MapUtils.Point2> path(Vector3 src, Vector3 dst) {
public GraphPath<Point2> path(Vector3 src, Vector3 dst) {
//return new MapGraph(this).path(src, dst);
return MapUtils.path(this, src, dst, new DefaultGraphPath<MapUtils.Point2>());
//return MapUtils.path(this, src, dst, new DefaultGraphPath<MapUtils.Point2>());
long start = System.currentTimeMillis();
GraphPath<Point2> path = new DefaultGraphPath<>();
new MapPather(this).path(src, dst, path);
System.out.println("time = " + (System.currentTimeMillis() - start) + "ms");
return path;
}
static class Zone {

View File

@ -0,0 +1,109 @@
package gdx.diablo.map;
import com.badlogic.gdx.ai.pfa.GraphPath;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectFloatMap;
import com.badlogic.gdx.utils.ObjectMap;
public class MapPather {
Map map;
Heuristic heuristic = new EuclideanHeuristic();
public MapPather(Map map) {
this.map = map;
}
public boolean path(Vector3 src, Vector3 dst, GraphPath<Point2> path) {
return path(new Point2(src), new Point2(dst), path);
}
public boolean path(Point2 src, Point2 dst, GraphPath<Point2> path) {
BinaryHeap<Point2> closedSet = new BinaryHeap<>();
BinaryHeap<Point2> openSet = new BinaryHeap<>();
openSet.add(src);
closedSet.contains(dst, false);
ObjectMap<Point2, Point2> cameFrom = new ObjectMap<>();
ObjectFloatMap<Point2> gScore = new ObjectFloatMap<>();
gScore.put(src, 0);
ObjectFloatMap<Point2> fScore = new ObjectFloatMap<>();
fScore.put(src, heuristic.estimate(src, dst));
Array<Point2> neighbors = new Array<>(8);
while (openSet.size > 0) {
Point2 current = openSet.pop();
if (current.equals(dst)) {
buildPath(current, cameFrom, path);
return true;
}
getNeighbors(current, neighbors);
for (Point2 neighbor : neighbors) {
if (closedSet.contains(neighbor, false)) {
continue;
}
float tent_gScore = gScore.get(current, Float.POSITIVE_INFINITY) + Point2.dst(current, neighbor);
if (!openSet.contains(neighbor, false)) {
openSet.add(neighbor);
} else if (tent_gScore >= gScore.get(neighbor, Float.POSITIVE_INFINITY)) {
continue;
}
cameFrom.put(neighbor, current);
gScore.put(neighbor, tent_gScore);
fScore.put(neighbor, gScore.get(neighbor, Float.POSITIVE_INFINITY) + heuristic.estimate(neighbor, dst));
}
closedSet.add(current);
}
return false;
}
private void buildPath(Point2 src, ObjectMap<Point2, Point2> cameFrom, GraphPath<Point2> path) {
path.add(src);
while (cameFrom.containsKey(src)) {
src = cameFrom.get(src);
path.add(src);
}
}
private void getNeighbors(Point2 src, Array<Point2> dst) {
dst.size = 0;
addNeighbor(src, src.x - 1, src.y - 1, dst);
addNeighbor(src, src.x - 1, src.y , dst);
addNeighbor(src, src.x - 1, src.y + 1, dst);
addNeighbor(src, src.x , src.y - 1, dst);
addNeighbor(src, src.x , src.y + 1, dst);
addNeighbor(src, src.x + 1, src.y - 1, dst);
addNeighbor(src, src.x + 1, src.y , dst);
addNeighbor(src, src.x + 1, src.y + 1, dst);
}
private void addNeighbor(Point2 src, int x, int y, Array<Point2> dst) {
Map.Zone zone = map.getZone(x, y);
if (zone == null) return;
if (zone.flags(x, y) == 0) {
float cost = src.getValue() + ((x != src.x && y != src.y) ? 1.414213562373095f : 1f);
Point2 point = new Point2(x, y, cost);
dst.add(point);
}
}
interface Heuristic {
float estimate(Point2 src, Point2 dst);
}
static class EuclideanHeuristic implements Heuristic {
@Override
public float estimate(Point2 src, Point2 dst) {
return Point2.dst(src, dst);
}
}
}

View File

@ -937,12 +937,12 @@ public class MapRenderer {
}
}
public void renderDebugPath2(ShapeRenderer shapes, GraphPath<MapUtils.Point2> path) {
public void renderDebugPath2(ShapeRenderer shapes, GraphPath<Point2> path) {
shapes.setColor(Color.TAN);
shapes.set(ShapeRenderer.ShapeType.Filled);
final int size = path.getCount();
for (int i = 0; i < size; i++) {
MapUtils.Point2 point = path.get(i);
Point2 point = path.get(i);
float px = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50) - Tile.SUBTILE_WIDTH50;
float py = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50) - Tile.SUBTILE_HEIGHT50;
drawDiamondSolid(shapes, px, py, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT);

View File

@ -30,6 +30,7 @@ public class MapUtils {
Point2 next = null;
Point2 last = coords.removeIndex(coords.size - 1);
path.add(last);
while (true) {
for (Point2 coord : coords) {
if (last.adjacent(coord)) {

View File

@ -0,0 +1,48 @@
package gdx.diablo.map;
import com.badlogic.gdx.math.Vector3;
public class Point2 extends BinaryHeap.Node {
final int x;
final int y;
final int hash;
Point2(int x, int y, float cost) {
super(cost);
this.x = x;
this.y = y;
this.hash = hash();
}
Point2(Vector3 src) {
this((int) src.x, (int) src.y, 0);
}
private int hash() {
return 31 * x + y;
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (!(obj instanceof Point2)) return false;
Point2 other = (Point2) obj;
return x == other.x && y == other.y;
}
public boolean equals(int x, int y) {
return this.x == x && this.y == y;
}
public static float dst(Point2 src, Point2 dst) {
final float dx = dst.x - src.x;
final float dy = dst.y - src.y;
return (float) Math.sqrt(dx * dx + dy * dy);
}
}

View File

@ -76,7 +76,7 @@ public class MapViewer extends ApplicationAdapter {
Vector3 src;
Vector3 dst;
GraphPath<MapUtils.Point2> path;
GraphPath<Point2> path;
boolean drawCrosshair;
boolean drawGrid;