Difference between revisions of "Digital field of view implementation"

From RogueBasin
Jump to navigation Jump to search
(First picature)
m
Line 32: Line 32:
Let's take a break from field of view algorithms... with a line of sight algorithm!
Let's take a break from field of view algorithms... with a line of sight algorithm!


At this point, we need a little bit of geometric intuition. As far as digital lines in the first octant are concerned, all of the action happens at integral x coordinates - each obstruction can be thought of as a horizontal line segment of length one, as can the source and destination. We want to know whether there is a line connecting the source line segment to the destination line segment that doesn't cross any of the obstructing segments. Thus, we can restrict attention to the parallelogram formed by the source and destination segments. Just like the previous algorithm, for each value of x there are only two relevant segments intersecting the parallelogram, each of which may or may not be obstructing.
At this point, we need a little bit of geometric intuition. As far as digital lines in the first octant are concerned, all of the action happens at integral x coordinates (actually integral + 0.5, but we can shift our coordinates) - each obstruction can be thought of as a horizontal line segment of length one, as can the source and destination. We want to know whether there is a line connecting the source line segment to the destination line segment that doesn't cross any of the obstructing segments. Thus, we can restrict attention to the parallelogram formed by the source and destination segments. Just like the previous algorithm, for each value of x there are only two relevant segments intersecting the parallelogram, each of which may or may not be obstructing.


A picture is worth a thousand words: http://img523.imageshack.us/img523/1424/dlosip2.gif
A picture is worth a thousand words: http://img523.imageshack.us/img523/1424/dlosip2.gif

Revision as of 17:44, 25 January 2008

This is an explanation of how to implement the Digital field of view algorithm.

Unlikely as it seems, there are no artifacts here. You can check out the demo program at http://reflexivelos.googlecode.com/svn/trunk/los2.cpp. It has an implementation of the not-so-dumb algorithm and the clever algorithm, and they produce identical results.

Dumb algorithm

The algorithm is simple: generate each digital line in turn, and walk along each one until you hit an obstruction. Here's a simple pseudocode for later reference:

for 0 <= p <= q <= n and 0 <= s < q
  eps = s, y = 0
  for x = 1 to n
    eps = eps + p
    if (eps >= q)
      eps = eps - q
      y = y + 1

    mark_visible(x, y)

    if (obstruction(x, y))
      leave inner loop

Obviously, this is extremely slow - running time is O(N4).

A little bit smarter

Notice that if we fix p and q, and let s vary, there are only two possible heights for the line at any particular x coordinate. If both of those squares are blocked, no value of s will work, and if both are empty, they don't impose any extra conditions on s. If the bottom one is blocked, then smaller values of s will be excluded, and similarly for the top. So at each x coordinate, there will be an interval of possible values for s, which can be maintained easily.

This does a little better - running time is O(N3). But best possible is O(N2)...

Between two points

Let's take a break from field of view algorithms... with a line of sight algorithm!

At this point, we need a little bit of geometric intuition. As far as digital lines in the first octant are concerned, all of the action happens at integral x coordinates (actually integral + 0.5, but we can shift our coordinates) - each obstruction can be thought of as a horizontal line segment of length one, as can the source and destination. We want to know whether there is a line connecting the source line segment to the destination line segment that doesn't cross any of the obstructing segments. Thus, we can restrict attention to the parallelogram formed by the source and destination segments. Just like the previous algorithm, for each value of x there are only two relevant segments intersecting the parallelogram, each of which may or may not be obstructing.

A picture is worth a thousand words: dlosip2.gif

Thus, obstructions can be classified as upper obstructions and lower obstructions, depending on which edge of the parallelogram they intersect. We want to know whether there is a line going underneath all of the upper obstructions and above all of the lower obstructions, that is, whether the convex hull of the upper obstructions (the blue area in the picture) intersects the convex hull of the lower obstructions (the green area). This is easy to check, though - we can find the convex hulls of each set in O(N) time, and then check this condition at every integral x coordinate from 1 to n.

A Beam Casting idea

In the previous algorithm, we could have been a little bit more ambitious. Instead of simply checking if we can see the destination, why don't we check if we can see the intermediate segments (or at least, their intersections with the parallelogram we're restricting ourselves to)?

If we can do this, then we can solve the field of view problem in O(N2) time! We simply call the previous algorithm with destinations (n, 0), (n, 1), ..., (n, n). This works because the parallelograms formed from the source and each destination completely cover the first octant, so if an intermediate segment is visible, a visible part of it will be contained in one particular parallelogram, and the segment will be marked visible while running the previous algorithm with the corresponding destination.

To check whether a lower segment is visible, given that we've calculated the convex hull of the obstructions we've seen so far (I really need to put some illustrations in... they'll come eventually, I promise), we can try pretending that the corresponding upper segment was an obstruction. If we then realize that the updated convex hulls would intersect, this tells us that our lower segment wasn't visible anyways. Unfortunately for this method, pretending something's an obstruction when it's not could make us use more than O(N) time in the end. We have to be trickier!

The trick is to also keep in memory the two unobstructed lines from the source segment with greatest and least slope. Let's go back to processing the lower segment. If the top point of the lower segment is below the line of least slope (which necessarily starts from the source segment, is tangent to the upper convex hull, then tangent to the lower convex hull), then pretending the upper segment is an obstruction implies that the updated convex hulls would intersect, so in this case the lower segment is invisible. If the top point of the lower segment is above the line of least slope, then the part of the lower segment just above that line is visible. So this tells us whether the lower segment is visible (one subtlety - we need to check that at least part of the visible region is still within the parallelogram, or we will come to grief).

To update the lines of greatest and least slope, let's assume that the lower segment is an obstruction. If it is invisible, it doesn't affect anything, so let's also assume it's visible. If the line of greatest slope changes here, then the convex hulls intersect, so we'll quit anyways. The line of least slope changes, though - the point of tangency to the lower convex hull moves to the top of the lower segment, and the point of tangency to the upper convex hull has to move to the right (since the endpoints of the lines of greatest and least slope always move to the right, we spend at most O(N) time updating them).

And that's all there is to it!

If you were paying close attention, you'll notice that if the actual size of the grid is M*M, and we tell the algorithm it's N*N, then the running time is O(NM).

A Permissive Field of View algorithm

The algorithm just described can be very easily transformed into a Permissive Field of View algorithm! For simplicity, we restrict attention to the first quadrant. Then, as far as we are concerned, each square may as well be replaced by a (negative slope) diagonal line segment. Just as before, each parallelogram from the source segment to a destination segment intersects an upper and a lower segment along each intermediate (negative slope) diagonal line, and the parallelograms from the source to the destinations (0, n), (1,n), ..., (n, n), (n, n-1), ..., (n, 0) completely cover the first quadrant.

The only other modification we need to make to our previous algorithm is allowing it to see between corners. While the previous algorithm would count the upper and lower convex hulls touching tangentially as obstructing our view, the new algorithm will count them touching tangentially as not obstructing the view, so a couple of >= signs will be switched to > signs, and vice versa.

I'd be very interested to see if the output of this algorithm differs at all from the existing Precise Permissive Field of View algorithm. A demo implementation can be found at http://reflexivelos.googlecode.com/svn/trunk/pfov.cpp.