Shortest Path

January 29, 2009

Clearance-based Pathfinding

Filed under: pathfinding — dharabor @ 23:50
Tags:

Real-time strategy (RTS) games often feature a wide number unit types for the player to control. One of my favourite titles from the past, Westwood’s seminal Red Alert, had many classes of differently sized units: small infantry soldiers, medium-sized Jeeps and large tanks. In Red Alert 3, the most recent incarnation of the series, the diversity is increased even further by the introduction of units with terrain-specific movement capabilities; for example, new amphibious units can move across both ground and water areas. From a pathfinding perspective this introduces an interesting question: how can we efficiently search for valid routes for variable-sized agents in rich environments with many types of terrain?

This was the topic of a recent paper which I presented at CIG’08. In the talk I outlined Hierarchical Annotated A* (HAA*), a path planner which is able to efficiently address this problem by first analysing the terrain of a game map and then building a much smaller approximate representation that captures the essential topographical features of the original. In this article (the first in a series of two) I’d like to talk about the terrain analysis aspects of HAA* in some detail and outline how one can automatically extract topological information from a map in order to solve the variable-sized multi-terrain pathfinding problem. Next time, I’ll explain how HAA* is able to use this information to build space-efficient abstractions which allow an agent to very quickly find a high quality path through a static environment.

Clearance Values and the Brushfire Algorithm

Simply put, a clearance value is a distance-to-obstacle metric which is concerned with the amount of traversable space at a discrete point in the environment. Clearance values are useful because they allow us to quickly determine which areas of a map are traversable by an agent of some arbitrary size. The idea of measuring distances to obstacles in pathfinding applications is not a new one. The Brushfire algorithm for instance is particularly well known to robotics researchers (though for different reasons than those motivating this article). This simple method, which is applicable to grid worlds, proceeds as so:

First, to each traversable tile immediately adjacent to a static obstacle in the environment (for example, a large rock or along the perimeter of a building) assign a value of 1. Next, each traversable neighbour of an assigned tile is itself assigned a value of 2 (unless a smaller value has already been assigned). The process continues in this fashion until all tiles have been considered; Figure 1 highlights this idea; here white tiles are traversable and black tiles represent obstacles (NB: The original algorithm actually assigns tiles adjacent to obstacles a value of 2; I use 1 here because it makes more sense for our purposes).

Figure 2. Caption caption caption.

Figure 1. A small toy map annotated with values computed by the Brushfire algorithm. Black tiles are not traversable.

Figure 2. Caption caption caption.

Figure 2. A 1x1 agent finds a path to the goal. All traversable tiles are included in the search space.

Figure 3.

Figure 3. A 2x2 agent finds a path to the goal. Only tiles with clearance > 1 are considered.

Figure 4. A 2x2 agent fails to find a path due to incorrect clearance values near the goal. Notice that the highlighted tiles are infact traversable by this agent.

Figure 4. A 2x2 agent fails to find a path due to incorrect clearance values near the goal. Notice that the highlighted tiles are infact traversable by this agent.

Brushfire makes use of a minimum obstacle distance metric to compute clearances which works reasonably well in many situations. If we assume our agents  (I use the term agent and unit interchangeably) are surrounded by a square bounding box we can immediately use the computed values to identify traversable locations from non-traversable ones by comparing the size of an agent with the corresponding clearance of particular tile. Figure 2 shows the search space for an agent of size 1×1; in this case, any tile with clearance equal to at least 1 is traversable. Similarly, in Figure 3 the search space is shown for an agent of size 2×2. Notice that the bigger agent can occupy much fewer locations on the map; only tiles with clearance values at least equal to 2. Because this approach is so simple it has seen widespread use, even in video games. At GDC 2007 Chris Jurney (from Relic Entertainment) described a pathfinding system for dealing with variable-sized agents in Company of Heroes — which happens to make use of a variant of the Brushfire algorithm.

Unfortunately, clearances computed using minimum obstacle distance do not accurately represent the amount of traversable space at each tile. Consider the example in Figure 4; Here our 2×2 agent incorrectly fails to find a path because all tiles in the bottleneck region are assigned clearance values less than 2. To overcome this limitation we will focus on an alternative obstacle-distance metric: true clearance.

The True Clearance Metric

The process of measuring true clearance for a given map tile is very straightforward: Surround each tile with a clearance square (bounding box really) of size 1×1. If the tile is traversable, assign it an inital clearance of 1. Next, expand the square symmetrically down and to the right, incrementing the clearance value each time, until no further expansions are possible. An expansion is successful only if all tiles within the square are traversable. If the clearance square intersects with an obstacle or with the edge of the map the expansion fails and the algorithm selects another tile to process. The algorithm terminates when all tiles on the map have been considered. Figure 5 highlights the process and Figure 6  shows the result on our running example.

Figure 5. Computing true clearance. (a) Initial clearance. (b). First expansion. (c) Second Expansion. (d) Third expansion (which fails)

Figure 5. After selecting a tile (a) the square is expanded twice (b, c) before intersecting an obstacle (d).

Figure 6.

Figure 6. The toymap from Figure 1, annotated with true clearance values.

Notice that by using true clearance the example from Figure 4 now succeeds in finding a solution. Infact, one can prove that using the true clearance metric it is always possible to find a solution for any agent size if a valid path exists to begin with (i.e. the method is complete; see my paper for the details).

Dealing With Multiple Terrain Types

Until now the discussion has been limited to a single type of traversable terrain (white tiles). As it turns out however, it is relatively easy to apply any clearance metric to maps involving arbitrarily many terrain types. Given a map with n terrain types we begin by first identifying the set of possible terrain traversal capabilities an agent may possess. A capability is simply a disjunction of terrains used to specify where each agent can traverse. So, on a map with 2 terrains such as {Ground, Water} the corresponding list of all possible capabilities is given by a set of sets; in this case {{Ground}, {Water}, {Ground, Water}}. Note that, for simplicity, I assume the traveling speed across all terrains is constant (but this constraint is easily lifted).

Next, we calculate and assign a clearance value to each tile for every capability. Figures 7-9 show the corresponding clearance values for each capability on our toy map; notice that we’ve converted some of the black (obstacle) tiles to blue to represent the Water terrain type (which some agents can traverse).

Figure 7. True clearance annotations for the {Ground} capability (only white tiles are traversable).

Figure 7. True clearance annotations for the {Ground} capability (only white tiles are traversable).

Figure 8. True clearance annotations for the {Water} capability (only blue tiles are traversable).

Figure 8. True clearance annotations for the {Water} capability (only blue tiles are traversable).

Figure 9. True clearance annotations for the {Ground, Water} capability (both white and blue tiles are traversable).

Figure 9. True clearance annotations for the {Ground, Water} capability (both white and blue tiles are traversable).

Theoretically this means that, at most, each tile will store 2^(n-1) clearance value annotations (again, see the paper for details). I suspect this overhead can probably be improved with clever use of compression optimisations though I did not attempt more than a naive implementation. Alternatively, if memory is very limited (as is the case with many robot-related applications) one can simply compute true clearance on demand for each tile, thus trading off a little processing speed for more space.

Annotated A*

In order to actually find a path for an agent with arbitrary size and capability we can use the venerable A* algorithm, albeit with some minor modifications. First, we must pass two extra parameters to A*’s search function: the agent’s size and capability. Next, we augment the search function slightly so that before a tile is added to A*’s open list we first verify that it is infact traversable for the given size and capability; everything else remains the same. A tile is traversable only if its terrain type appears in the set of terrains that comprise the agent’s capability and if the corresponding clearance value is at least equal to the size of the agent. To illustrate these ideas I’ve put together a simplified pseudocode implementation of the algorithm, Annotated A*:

Function: getPath
Parameters: start, goal, size, capability

push start onto open list.
for each node on the open list
        if current node is the goal, return path.
        else,
                for each neighbour of the newly opened node
                     if neighbour is on the closed list, skip it
                     else,
                          if neighbour is already on the open list, update weights
                          else,
                               if clearance(neighbour, capability) > size, push neighbour on the open list
                               else, skip neighbour
        push current node on closed list
        if openlist is null, return failure

So, that’s it! If you’re interested in playing with a working implementation of Annotated A* you can check out the source code I wrote to evaluate it. The code itself is written in C++ and based on the University of Alberta’s freely available pathfinding library, Hierarchical Open Graph (or HOG). HOG compiles on most platforms; I’ve personally tested it on both OSX and Linux and I’m told it works on Windows too. The classes of most interest are probably AnnotatedMapAbstraction, which deals with computing true clearance values for a map, and AnnotatedAStar which is the reference implementation of the search algorithm described here.

In the second part of this article I’m going to expand on the ideas presented here and explain how one can build an efficient hierarchical representation of an annotated map that requires much less memory to store and retains the completeness characteristics of the original. I’ll talk about why this is important and how one can use such a graph to answer pathfinding queries much faster than Annotated A*. At some point soon I also hope to discuss how one can use clearance-based pathfinding on continuous (non-grid) maps.

About these ads

6 Comments »

  1. [...] Filed under: haa*, pathfinding — dharabor @ 17:23 In my last article I discussed Annotated A*, a search algorithm able to solve pathfinding problems for variable-sized [...]

    Pingback by Hierarchical Clearance-based Pathfinding « Shortest Path — February 5, 2009 @ 17:23

  2. [...] On Continuous (non-grid) Maps Filed under: Uncategorized — dharabor @ 00:06 In my last two articles I discussed HAA*, a clearance-based pathfinding technique applicable to grid maps that [...]

    Pingback by Clearance-based Pathfinding On Continuous (non-grid) Maps « Shortest Path — February 12, 2009 @ 00:06

  3. [...] Filed under: pathfinding, roadmaps — dharabor @ 00:06 In my last two articles I discussed HAA*, a clearance-based pathfinding technique applicable to grid maps that [...]

    Pingback by Clearance-based Pathfinding with Probabilistic Roadmaps « Shortest Path — February 12, 2009 @ 00:13

  4. You fixed the clearance value for the goal in Figure 4, but what about the tile beneath it? The value is still 1 with the ‘True Metric’. Is that normal?

    Comment by Cygal — September 2, 2009 @ 07:37

  5. Hi Cygal,

    Using the True Clearance metric, the value for the tile beneath the goal in Figure 4 remains 1 (as shown in Figure 7).
    If we switch the goal to this tile instead it may appear that the problem has no solution.
    However, there is another nearby tile (the north-west neighbour in this case) which we can use instead and still have the agent “reach” the original goal tile.

    More formally, a tile t with clearance value c is reachable by an agent of size x if c >= x.
    In the case where c < x, the tile is only reachable by the agent if its location is subsumed by the clearance square of another tile t’ such that c’ >= x.

    The key is to remember that we’re working with units that can occupy several tiles at the same time. :)

    Comment by dharabor — September 2, 2009 @ 13:35

  6. [...] Redditor ashlykos pointed me to the original article that inspired me to work this out in the first [...]

    Pingback by Pathfinding progress « reservedblogname — August 19, 2011 @ 16:58


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: