Planning to Find an Unpredictable Evader
Brian O'Connor
bgoc@leland.stanford.edu
Project Description
The goal of this project is to use visibility to search for an "evader"
with unbounded speed moving through a polygonal environment. The basic
strategy for computing this path is to: 1) decompose the environment into
convex polygonal cells; 2) compute a visibility polyon for each cell in the
environment; 3) build and search a visibility graph from this information
to find a solution for a single pursuer; and 4) break the environment into
subproblems and solve each with a single pursuer if the first search fails.
My solution to the first three parts is fairly robust but my solution to the
last problem is incomplete.
- Cell Decomposition.
To create a cell decomposition based on edge visibility I used the following
strategy.
- Given the points which define the border and any obstacles, compute the
line segments (a homogenous line equation and two endpoints) that make up
each obstacle.
- Extend rays as outlined in the paper A Visibility-Based
Pursuit-Evasion Problem.
- Combine any rays that are colinear and overlapping into a single ray to
eliminate singularities.
- Calculate the intersections of all rays and replace each of the resulting
unoriented segments with two oriented segments in opposite directions.
- Choose an oriented ray and traverse the ray set in a counterclockwise
fashion until a cycle is completed (we reach the original ray); remove
these rays from the set and define a cell bounded by these rays.
I use a quadedge-like structure, the CellEdge, in the last two sections in
order to map each cell edge to its dual (the edge with opposite orientation).
Then when I create a new cell I keep track of the cell that is reached by
crossing this cell edge; generation of a roadmap once this
adjacency information is calculated becomes trivial. I also need to
remove cells that were created in the last step but which actually
correspond to the border or an obstacle; these cells are artifacts caused
by the fact that every edge has a dual and can be safely removed.
This was definitely the most difficult part of the project; it took me more
time to get my cell decomposition working than it did to write the rest of
the program. There are still a couple known bugs in my decomposition
algorithm. There are a couple problems with extending rays through
environments with multiple "holes" or interior obstacles, although worlds
with fewer than three holes seem to work fine. Problems can also occur
when points on the border are visible to each other but are on the outside
of the environment, and in some of these cases a cell can be created that
is outside of the environment. This doesn't occur very often though and I
was able to create examples with very complex borders that work fine. I
created a bunch of line, point, and segment functions for this section that
I was able to reuse in the other sections and some error-tolarent floating
point routines which I used throughout the project to avoid the rounding errors
that occur when I use multiple vector cross-products to compute line
intersections. Finally, the fact that my algorithm handles singularities
such as multiple colinear points and edges made constructing clean sample
environments a lot easier. The downside to this was that I couldn't find an
easy way to do this using quadedges and therefore need to make an extra pass
over my data to convert from unoriented intersecting lines to oriented
segments that only intersect at endpoints.
- Computation of Visibility Polyons.
Computing a visibility polygon was fairly straightforward. First I shoot
rays through every vertex to calculate my gap edges, then I start with one
gap edges and follow it to the obstacle edge it ends at, and then I follow
that obstacle edge to the next edge it shares a vertex with, and continue
the process. If at any time I find that a gap edge intersects the
obstacle edge I am processing, then I skip to that gap edge and continue
at the other obstacle edge that this gap edge contacts. When I make a
cycle and return to the gap edge I started with the visibility polygon is
complete. Note that when I traverse a gap edge to an obstacle edge, there
will be two different possibilites for which edge I should continue to;
however, only one of these possibilities will correspond to a vertex that
can be seen from my starting point. Therefore I also keep track of all
the vertices that can be seen from the starting point in order to
make the right decision in these cases.
- Building and Searching a Visibility Graph.
My visibility graph consists of nodes represented by (cell,state) pairs,
and directed arcs that connect the nodes. I use Dijkstra's algorithm to
search the graph, with the enhancement that I don't construct nodes until
I've called relax() on a neighboring node. My program runs significantly
faster after this optimization, and uses much less memory.
When building the graph, my key observations
were that I could use a bitmap to represent the state of a node (1's for
dirty edges, 0's for clean edges) and that I can represent the change in
state when I move from node A to node B by an array of bitmaps. I use
the latter observation in conjunction with the adjacency information I
built in Part 1 to determine what nodes should be connected by arcs. For
example, suppose cell A has 5 gap edges a1,...a5 and cell B has 3 gap
edges b1,b2,b3. edges a1, a2, and a4 correspond to b1, and edge a5
corresponds to edge b2. Then given boolean values for a1,...,a5 I can
computer the values for B as:
b1 = a1 | a2 | a4
b2 = a5
b3 = 0
or using bitmaps,
state(A) = (a1 << 0) | (a2 << 1) | (a3 << 2) | (a4 << 3) | (a5 << 4)
b1 = 11010 & state(A)
b2 = 00001 & state(A)
b3 = 00000 & state(A)
state(B) = (b1 << 0) | (b2 << 1) | (b3 << 2).
The 3 bitmaps used in conjunction with the state of A to calculate the
state of B are independent of the state of A, so I reduce computation
time by sharing this information across arcs that correspond to the same
cells with different states. This information isn't symmetric, so I need
to calculate a different bitmap array to move from B to A.
- Planning for Multiple Pursuers.
I ran out of time before I could finish this section. My idea for
multiple pursuer planning was to augment my graph search to remember the
"best" node it had seen so far in case the search fails. Then I can try
to search again with an extra robot, but this time the first robot will
stop at its "best" node and the second robot will search the rest of the
graph. The second robot will start its search on the opposite side of one
of the dirty gap edges in the first robot's "best" node. This idea could
be generalized to start more than two pursuers if the first robot has
multiple dirty gap edges in its best node, or to continue starting
pursuers recursively if the second robot cannot search it's portion of the
environment (I didn't have time to implement these extensions though).
Right now I'm computing the second robot's environment with an algorithm
very similar to my Visibility Polygon algorithm from part 2; beginning
with the dirty edge the original pursuer ended on, I traverse all the
obstacle edges (and possibly other dirty gap edges) that bound the
unsearched region, and make all of these edges the border of a new
environment which I pass into my cell decomposition and graph search
functions.
Motion Planning Examples
Files suffixed with 'A' show a continuous path and the visibility polygon
at each point in the path. Files suffixed with 'B' show the path in cell
by cell steps, and also shows the gap edges at each step, color coded to
show if the gap is dirty or clean at this step.
Files suffixed with 'C' don't show any path planning and
instead show the cell decomposition that was used in the other examples.
I've had a lot of problems getting these examples to run through the Web,
possibly due to Java running out of memory because of the number of ghosts
used to illustrate gap edges and visibility polygons.
Here are Sun versions of the movies (.mv
format)
Source Code
- Makefile
This is my main program which managers all of my data and interfaces
between the different parts of the project.
- pursuer.h
- pursuer.C
These files dump the results of my graph search and cell decomposition
into a movie file.
- movieoutput.h
- movieoutput.C
Part-by-Part Implementation
Part 1: Builds the cell decomposition
- cell.h
- cell.C
Part 2: Generate a visibility polygon
- vispoly.h
- vispoly.C
Part 3: Build and search a visibility graph
- pgraph.h
- pgraph.C
Part 4: Coordinate multiple pursuers
- rebuild.h
- rebuild.C
Libraries
I wrote this line library to encapsulate functions which manipulate
homogenous representations of lines and points; for example, detecting
if a point is on a line segment, and calculating the intersection of two
lines.
- line.h
- line.C
These files encapsulate all my functions that directly process the input
file.
- input.h
- input.C
These files implement a priority queue which I use in my graph search
algorithm. Borrowed from the CS240A Nachos project code.
- queue.h
- queue.C
These files implement a bitmap type by providing macros and functions
for manipulating an integer as if it was a bitmap.
- bitmap.h
- bitmap.C
Last modified: Sat Mar 22 23:48:24 PST 1997