package net.kennux.cubicworld.pathfinder;
import java.util.ArrayList;
import java.util.LinkedList;
import net.kennux.cubicworld.util.ConsoleHelper;
import net.kennux.cubicworld.util.ObjectMap;
import net.kennux.cubicworld.util.VectorHelper;
import net.kennux.cubicworld.voxel.VoxelWorld;
import com.badlogic.gdx.math.Vector3;
/**
* <pre>
* The pathfinder class.
* It uses a slightly modified version of A-Star pathfinding.
*
* The pathfinder will run in it's own thread.
*
* TODO implement character height consideration in findPath().
* </pre>
*
* @author kennux
*
*/
public class Pathfinder implements Runnable
{
/**
* This thread runs the pathfinder main loop.
*/
private Thread pathfinderThread;
/**
* VoxelWorld instance from the main class.
* Gets used to perform the pathfinding.
*/
private VoxelWorld world;
/**
* Contains all tasks still to process.
*/
private LinkedList<PathfinderTask> taskStack;
private Object taskStackLockObject = new Object();
public Pathfinder(VoxelWorld world)
{
// Start the pathfinder thread
this.pathfinderThread = new Thread(this);
this.pathfinderThread.setName("Pathfinder worker thread");
// Initialize
this.world = world;
this.taskStack = new LinkedList<PathfinderTask>();
this.pathfinderThread.start();
}
/**
* Enquenes a new path generation.
*
* @param p
*/
public void addPathfinderTask(Path p)
{
synchronized (this.taskStackLockObject)
{
this.taskStack.push(new PathfinderTask(p));
}
}
/**
* Performs the A-Star pathfinding for given path p.
* TODO Consider character height!
*
* @param p
*/
private void findPath(Path p)
{
// Declare lists
ObjectMap<Vector3, PathNode> openList = new ObjectMap<Vector3, PathNode>(Vector3.class, PathNode.class);
ArrayList<Vector3> closedList = new ArrayList<Vector3>();
// Start pathfinding
PathNode startNode = new PathNode(p.getStartPosition(), null, 0, p.getEndPosition());
startNode.owner = startNode;
PathNode currentNode = startNode;
boolean pathFound = false;
boolean noPath = false;
int movementCost = 0;
int stepsMade = 0;
try
{
while (!pathFound && !noPath)
{
// Calculate voxel direction positions
Vector3[] positions = new Vector3[] {
// Front
new Vector3(currentNode.position).add(VectorHelper.forward),
// Back
new Vector3(currentNode.position).add(VectorHelper.back),
// Left
new Vector3(currentNode.position).add(VectorHelper.left),
// Right
new Vector3(currentNode.position).add(VectorHelper.right),
// Front right
new Vector3(currentNode.position).add(VectorHelper.forward).add(VectorHelper.right),
// Front left
new Vector3(currentNode.position).add(VectorHelper.forward).add(VectorHelper.left),
// Back right
new Vector3(currentNode.position).add(VectorHelper.back).add(VectorHelper.right),
// Back left
new Vector3(currentNode.position).add(VectorHelper.back).add(VectorHelper.left),
// Up Front
new Vector3(currentNode.position).add(VectorHelper.forward).add(VectorHelper.up),
// Up Back
new Vector3(currentNode.position).add(VectorHelper.back).add(VectorHelper.up),
// Up Left
new Vector3(currentNode.position).add(VectorHelper.left).add(VectorHelper.up),
// Up Right
new Vector3(currentNode.position).add(VectorHelper.right).add(VectorHelper.up),
// Down Front
new Vector3(currentNode.position).add(VectorHelper.forward).add(VectorHelper.down),
// Down Back
new Vector3(currentNode.position).add(VectorHelper.back).add(VectorHelper.down),
// Down Left
new Vector3(currentNode.position).add(VectorHelper.left).add(VectorHelper.down),
// Down Right
new Vector3(currentNode.position).add(VectorHelper.right).add(VectorHelper.down), };
// Analyze Surrounding path nodes
PathNode[] nodes = new PathNode[positions.length];
PathNode lowestCostNode = null;
// Check which ones are walkable and add them to the nodes-array
for (int i = 0; i < positions.length; i++)
{
int currentMovementCost = (int) VectorHelper.distance(positions[i], currentNode.position);
// Check if node is walkable
if (!closedList.contains(positions[i]) && !this.world.hasVoxel((int) positions[i].x, (int) positions[i].y, (int) positions[i].z) &&
// Walkable / ground check
(!p.needsGround() || this.world.hasVoxel((int) positions[i].x, (int) positions[i].y - 1, (int) positions[i].z)))
{
// Add node to the nodes-array
if (openList.containsKey(positions[i]))
{
nodes[i] = openList.get(positions[i]);
}
else
{
nodes[i] = new PathNode(positions[i], currentNode, movementCost + currentMovementCost, p.getEndPosition());
openList.put(positions[i], nodes[i]);
}
}
// Check for lowest cost
if (nodes[i] != null && (lowestCostNode == null || nodes[i].completeCost < lowestCostNode.completeCost))
{
lowestCostNode = nodes[i];
}
}
// Failed? o_O
if (lowestCostNode == null)
{
noPath = true;
break;
}
if (currentNode.position.equals(p.getEndPosition()))
pathFound = true;
// Put the lowest cost node on the closed list
if (currentNode.owner.position.equals(lowestCostNode.owner.position))
{
currentNode.owner.nextNode = lowestCostNode;
}
else
currentNode.nextNode = lowestCostNode;
closedList.add(currentNode.position);
currentNode = lowestCostNode;
stepsMade++;
if (stepsMade == 100)
{
noPath = true;
break;
}
}
}
catch (Exception e)
{
ConsoleHelper.writeLog("error", "Exception in pathfinder: ", "Pathfinder");
ConsoleHelper.logError(e);
noPath = true;
}
// Path found?
if (noPath)
{
// No... :'(
p.setStepData(null);
}
else
{
// :^)
// This is needed because in the closedlist there can be movements
// which are like
// front, right
// this should be done in one step frontright and this gets achieved
// by generating an array from the path node's linked list.
ArrayList<Vector3> steps = new ArrayList<Vector3>();
PathNode cNode = startNode;
while (cNode != null)
{
steps.add(cNode.position);
cNode = cNode.nextNode;
}
p.setStepData(steps.toArray(new Vector3[steps.size()]));
}
}
/**
* The pathfinder main loop.
*/
@Override
public void run()
{
while (true)
{
// Work available?
synchronized (this.taskStackLockObject)
{
if (this.taskStack.size() > 0)
{
// Get first object
PathfinderTask task = this.taskStack.removeFirst();
// Process all tasks
while (task != null)
{
// Actually find the path
this.findPath(task.pathInstance);
// Get next object
if (this.taskStack.size() > 0)
task = this.taskStack.removeFirst();
else
task = null;
}
}
}
// Wait some time before re-loop
try
{
Thread.sleep(2);
}
catch (InterruptedException e)
{
}
}
}
}