Pathfinding refers to the techniques used in games to allow Non-Player Characters (NPC) to navigate the game world.
Most games use some variant of Dijkstra's algorithm, a technique pioneered by Dutch computer scientist Edsger Dijkstra. Dijkstra's algorithm produces a shortest path tree for its result by solving the shortest path problem for a graph with non-negative edge length (the distance between the vertices of the graph are never negative). It is one of a myriad of graph search (or graph traversal) algorithms.
Dijkstra's algorithm is often used in routing problems, such as network routing protocols, most notably IS-IS and OSPF (Open Shortest Path First), and transportation map shortest path solutions.
Perhaps the most commonly used variant in games is the A* search algorithm. Instead of using the distance from the start node (vertex) of the graph, this variant chooses nodes based on an estimate of the distance from the start node to the destination node. This estimate is formed by adding the known distance to the start node to a guess of the distance to the destination node. This guess is called the heuristic of the algorithm.
By utilizing such a technique, the A* algorithm provides improved performance when compare to the behavior of Dijkstra's algorithm. If the guess is set to zero, the two algorithms are equivalent. When the guess is made positive but less than the true distance to the goal, A* continues to find optimal paths, but because fewer nodes are examined, performance is improved. When the guess exactly matches the actual distance, A* finds the optimal path and does so while examining the minimal possible number of nodes. If the guess is increased yet more, A* continues to find paths, examining fewer nodes still, but no longer guaranteeing an optimal path. In most games, since it is not practical to guarantee a correct heuristic at all times, this is acceptable due to the increase in algorithm performance.
With extremely complex environments (like the maze we'll be looking at), these algorithms can prove to be impractical, with other solutions preferred. A nice overview of such issues can be seen at Sean Barrett's Path Finding blog entry.
I'm going to go through a technique that is conceptually beautiful to me: Using image processing to solve a maze path finding problem.
I'm sure every reader is familiar with mazes. Perhaps some of you have found yourself lost in a particularly fiendish one, having to ring the nearby "rescue bell'!
I'll be using the superb Matlab system from Mathworks.
From their web site: "MATLAB® is a high-level language and interactive environment that enables you to perform computationally intensive tasks faster than with traditional programming languages such as C, C++, and Fortran."
It most certainly is! Along with the equally superb Mathematica it is the staple of my tool set for doing complex mathematical programming, analysis, graphing, and data manipulation. These two products would be my desert island choices to take with me if I could only have two programming environments.
I'll be using the Matlab image processing package DIPimage, developed by Cris Luengo, Professor in multidimensional image analysis at the Swedish University of Agricultural Sciences and Uppsala University. I've borrowed copiously from his examples. Users of Matlab can obtain a license for DIPimage free of charge (normally a $3200.00 and up cost) for academic, non-commercial use. I highly recommend it!
Let's begin.
We will start with the following simple (no holes, loops, etc.) maze:
Note the entry and exit apertures in the lower right and upper right areas of the outer call of the maze. Try solving it manually if you'd like!
Next, we use our image processing functions to convert our maze to grey-valued, and threshold it to obtain the binary image. The maze walls are now shown in red, with the path areas in black:
maze = colorspace(inputmaze,'grey');
maze = ~threshold(maze)
maze = ~threshold(maze)
We use image processing functions to "label" any found walls and show them with differing colors. In this case, we clearly see the maze comprises two walls:
maze = label(maze);
dipshow(maze*2,'labels')
From this we can easily see the path is the area between the two maze walls. By applying a little mathematical morphology, we can isolate one of the walls:
path = maze==1
We then can do a binary dilation on the wall and fill in any holes (effectively a smearing of the wall):
pathwidth = 5;
path = bdilation(path,pathwidth);
path = fillholes(path)
Next, let us erode this smeared version of the wall. This operation effectively "shrinks" the wall by subtracting components smaller than the erosion radius and in the case of a binary image such as ours by removing the perimeter pixels. We then take the difference of the eroded image and the original image:
path = path - berosion(path,pathwidth);
Finally, overlaying the result with our original maze shows our desired goal:
overlay(inputmaze,path)
I think you'll agree, a most fascinating use of tools to solve a problem from a domain you'd not likely think to be image processing.
The interested reader can find details of techniques such as these in these excellent references:
The Image Processing Handbook by John C. Russ
Digital Image Processing Using MATLAB by Rafael C. Gonzalez
Feature Extraction & Image Processing by Mark Nixon
Happy trails to you!
looks complicated..
ReplyDeletebut nice tips..