/**
* Copyright (c) 2011-2015, SpaceToad and the BuildCraft Team
* http://www.mod-buildcraft.com
* <p/>
* BuildCraft is distributed under the terms of the Minecraft Mod Public
* License 1.0, or MMPL. Please check the contents of the license located in
* http://www.mod-buildcraft.com/MMPL-1.0.txt
*/
package buildcraft.core.lib.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import net.minecraft.world.World;
import buildcraft.api.core.BlockIndex;
import buildcraft.api.core.BuildCraftAPI;
/**
* This class implements a 3D path finding based on the A* algorithm, following
* guidelines documented on http://www.policyalmanac.org/games/aStarTutorial.htm
* .
*/
public class PathFinding implements IIterableAlgorithm {
public static int PATH_ITERATIONS = 1000;
private World world;
private BlockIndex start;
private BlockIndex end;
private double maxDistanceToEndSq = 0;
private float maxTotalDistanceSq = 0;
private HashMap<BlockIndex, Node> openList = new HashMap<BlockIndex, PathFinding.Node>();
private HashMap<BlockIndex, Node> closedList = new HashMap<BlockIndex, PathFinding.Node>();
private Node nextIteration;
private LinkedList<BlockIndex> result;
private boolean endReached = false;
public PathFinding(World iWorld, BlockIndex iStart, BlockIndex iEnd) {
world = iWorld;
start = iStart;
end = iEnd;
Node startNode = new Node();
startNode.parent = null;
startNode.movementCost = 0;
startNode.destinationCost = distanceSq(start, end);
startNode.totalWeight = startNode.movementCost + startNode.destinationCost;
startNode.index = iStart;
openList.put(start, startNode);
nextIteration = startNode;
}
public PathFinding(World iWorld, BlockIndex iStart, BlockIndex iEnd, double iMaxDistanceToEnd) {
this(iWorld, iStart, iEnd);
maxDistanceToEndSq = iMaxDistanceToEnd * iMaxDistanceToEnd;
}
public PathFinding(World iWorld, BlockIndex iStart, BlockIndex iEnd, double iMaxDistanceToEnd,
float iMaxTotalDistance) {
this(iWorld, iStart, iEnd, iMaxDistanceToEnd);
maxTotalDistanceSq = iMaxTotalDistance * iMaxTotalDistance;
}
@Override
public void iterate() {
iterate(PATH_ITERATIONS);
}
public void iterate(int itNumber) {
for (int i = 0; i < itNumber; ++i) {
if (nextIteration == null) {
return;
}
if (endReached) {
result = new LinkedList<BlockIndex>();
while (nextIteration != null) {
result.addFirst(nextIteration.index);
nextIteration = nextIteration.parent;
}
return;
} else {
nextIteration = iterate(nextIteration);
}
}
}
@Override
public boolean isDone() {
return nextIteration == null;
}
public LinkedList<BlockIndex> getResult() {
if (result != null) {
return result;
} else {
return new LinkedList<BlockIndex>();
}
}
public BlockIndex end() {
return end;
}
private Node iterate(Node from) {
openList.remove(from.index);
closedList.put(from.index, from);
ArrayList<Node> nodes = new ArrayList<Node>();
byte[][][] resultMoves = movements(from);
for (int dx = -1; dx <= +1; ++dx) {
for (int dy = -1; dy <= +1; ++dy) {
for (int dz = -1; dz <= +1; ++dz) {
if (resultMoves[dx + 1][dy + 1][dz + 1] == 0) {
continue;
}
int x = from.index.x + dx;
int y = from.index.y + dy;
int z = from.index.z + dz;
Node nextNode = new Node();
nextNode.parent = from;
nextNode.index = new BlockIndex(x, y, z);
if (resultMoves[dx + 1][dy + 1][dz + 1] == 2) {
endReached = true;
return nextNode;
}
nextNode.movementCost = from.movementCost + distanceSq(nextNode.index, from.index);
nextNode.destinationCost = distanceSq(nextNode.index, end);
nextNode.totalWeight = nextNode.movementCost + nextNode.destinationCost;
if (maxTotalDistanceSq > 0 && nextNode.totalWeight > maxTotalDistanceSq) {
if (!closedList.containsKey(nextNode.index)) {
closedList.put(nextNode.index, nextNode);
}
continue;
}
if (closedList.containsKey(nextNode.index)) {
continue;
} else if (openList.containsKey(nextNode.index)) {
Node tentative = openList.get(nextNode.index);
if (tentative.movementCost < nextNode.movementCost) {
nextNode = tentative;
} else {
openList.put(nextNode.index, nextNode);
}
} else {
openList.put(nextNode.index, nextNode);
}
nodes.add(nextNode);
}
}
}
nodes.addAll(openList.values());
return findSmallerWeight(nodes);
}
private Node findSmallerWeight(Collection<Node> collection) {
Node found = null;
for (Node n : collection) {
if (found == null) {
found = n;
} else if (n.totalWeight < found.totalWeight) {
found = n;
}
}
return found;
}
private static class Node {
public Node parent;
public double movementCost;
public double destinationCost;
public double totalWeight;
public BlockIndex index;
}
private static double distanceSq(BlockIndex i1, BlockIndex i2) {
double dx = (double) i1.x - (double) i2.x;
double dy = (double) i1.y - (double) i2.y;
double dz = (double) i1.z - (double) i2.z;
return dx * dx + dy * dy + dz * dz;
}
private boolean endReached(int x, int y, int z) {
if (maxDistanceToEndSq == 0) {
return end.x == x && end.y == y && end.z == z;
} else {
return BuildCraftAPI.isSoftBlock(world, x, y, z)
&& distanceSq(new BlockIndex(x, y, z), end) <= maxDistanceToEndSq;
}
}
private byte[][][] movements(Node from) {
byte[][][] resultMoves = new byte[3][3][3];
for (int dx = -1; dx <= +1; ++dx) {
for (int dy = -1; dy <= +1; ++dy) {
for (int dz = -1; dz <= +1; ++dz) {
int x = from.index.x + dx;
int y = from.index.y + dy;
int z = from.index.z + dz;
if (y < 0) {
resultMoves[dx + 1][dy + 1][dz + 1] = 0;
} else if (endReached(x, y, z)) {
resultMoves[dx + 1][dy + 1][dz + 1] = 2;
} else if (!BuildCraftAPI.isSoftBlock(world, x, y, z)) {
resultMoves[dx + 1][dy + 1][dz + 1] = 0;
} else {
resultMoves[dx + 1][dy + 1][dz + 1] = 1;
}
}
}
}
resultMoves[1][1][1] = 0;
if (resultMoves[0][1][1] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[0][i][j] = 0;
}
}
}
if (resultMoves[2][1][1] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[2][i][j] = 0;
}
}
}
if (resultMoves[1][0][1] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[i][0][j] = 0;
}
}
}
if (resultMoves[1][2][1] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[i][2][j] = 0;
}
}
}
if (resultMoves[1][1][0] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[i][j][0] = 0;
}
}
}
if (resultMoves[1][1][2] == 0) {
for (int i = 0; i <= 2; ++i) {
for (int j = 0; j <= 2; ++j) {
resultMoves[i][j][2] = 0;
}
}
}
if (resultMoves[0][0][1] == 0) {
resultMoves[0][0][0] = 0;
resultMoves[0][0][2] = 0;
}
if (resultMoves[0][2][1] == 0) {
resultMoves[0][2][0] = 0;
resultMoves[0][2][2] = 0;
}
if (resultMoves[2][0][1] == 0) {
resultMoves[2][0][0] = 0;
resultMoves[2][0][2] = 0;
}
if (resultMoves[2][2][1] == 0) {
resultMoves[2][2][0] = 0;
resultMoves[2][2][2] = 0;
}
if (resultMoves[0][1][0] == 0) {
resultMoves[0][0][0] = 0;
resultMoves[0][2][0] = 0;
}
if (resultMoves[0][1][2] == 0) {
resultMoves[0][0][2] = 0;
resultMoves[0][2][2] = 0;
}
if (resultMoves[2][1][0] == 0) {
resultMoves[2][0][0] = 0;
resultMoves[2][2][0] = 0;
}
if (resultMoves[2][1][2] == 0) {
resultMoves[2][0][2] = 0;
resultMoves[2][2][2] = 0;
}
if (resultMoves[1][0][0] == 0) {
resultMoves[0][0][0] = 0;
resultMoves[2][0][0] = 0;
}
if (resultMoves[1][0][2] == 0) {
resultMoves[0][0][2] = 0;
resultMoves[2][0][2] = 0;
}
if (resultMoves[1][2][0] == 0) {
resultMoves[0][2][0] = 0;
resultMoves[2][2][0] = 0;
}
if (resultMoves[1][2][2] == 0) {
resultMoves[0][2][2] = 0;
resultMoves[2][2][2] = 0;
}
return resultMoves;
}
}