In a 2D top-down map it is sometimes useful to calculate which areas are visible from a given point. For example you might want to hide what’s not visible from the player’s location, or you might want to know what areas would be lit by a torch.
Drag the circle around to see what’s visible from the player’s location:
This algorithm can also calculate what areas are lit given a light source. Run once per light, we can build a light map showing which areas are lit up. What if we put 24 lights into the above maze?
The roguelike community has collected several algorithms, especially for grids. Subtractive algorithms start with everything visible then subtract the hidden areas; additive algorithms start with nothing visible then add the visible areas. I’ll describe an additive algorithm that works with line segments, not only solid blocks or grids.
A simple approach is to cast rays from the center. It’s a reasonable first step to get an approximate answer:
To be smarter about it, let’s cast the rays only at angles where the walls begin or end. The triangles produced by these rays are the visible areas:
That’s it! The algorithm is:
- Calculate the angles where walls begin or end.
- Cast a ray from the center along each angle.
- Fill in the triangles generated by those rays.
We could stop there, especially if we have a fast ray-casting algorithm that uses a spatial hash to avoid intersecting with every wall. However, a more efficient approach is to combine the ray casting and wall intersection into a single algorithm.
For the area between consecutive rays, we want to find the nearest wall. This wall is lit up; all others are hidden. Our strategy will be to sweep around 360° and process all of the wall endpoints. As we go, we’ll keep track of the walls that intersect the sweep line.
Press Play to see the sweep of the endpoints:
The next step is to keep track of which walls the sweep ray passes through. Only the nearest wall is visible. How do you figure out which wall is nearest? The simplest thing is to calculate the distance from the center to the wall. However, this approach doesn’t work well if the walls are of different sizes, so the demo uses a slightly more complicated approach, which I won’t explain here.
Press Play to see the sweep with the nearest wall drawn in white and the other walls drawn in black.
Whenever the nearest wall ends, or if a new wall is nearer than the others, we create a triangle showing a visible region. The union of these triangles is the area that is visible from the central point.
var endpoints; # list of endpoints, sorted by angle var open = ; # list of walls, sorted by distance loop over endpoints: remember which wall is nearest add any walls that BEGIN at this endpoint to 'walls' remove any walls that END at this endpoint from 'walls' SORT the open list if the nearest wall changed: fill the current triangle and begin a new one
Note that creating a triangle involves intersecting the previously active wall with the sweep ray. As a result, the new edge of the triangle may be longer or shorter than the sweep ray, and the far edge of the triangle may be shorter than the previously active wall.
Here’s a playground with more blocks. Drag blocks into the grid area. Press play/pause to see the algorithm in action, or drag the center point around to see what would be visible as the player walks around.
We can use set operations to combine outputs of this algorithm in interesting ways. These are implemented either as boolean operations in algorithms that analyze the output, or as bitmap operations in algorithms that render the output.
The simplest operation is to limit the player’s vision by intersecting the output with the limit of visibility. For example, intersect the output of the algorithm with a circle to limit the radius you can see. Intersect with a gradient-filled circle to make the light fall off with distance. Intersect with a cone to build a “flashlight” effect that lets you see farther in front of you but not much behind you (see the trailed for Dynamite Jack for an example of this).
Player vision would also look better if it took into account both eyes instead of treating the player as a single point. I’d expect that you can take the union of the visibility output from each eye but I haven’t tried this.
Visibility can also be used for calculating what areas a torch lights up. The demo at the top of the page first takes the union of the areas lit up by each torch, then intersects that with the area the player can see. (Note that this algorithm produces hard shadows and you’ll have to post-process the output to get soft shadows.)
The same calculation could be used for determining what areas a security camera can see, what’s protected by a shield, or what’s magic objects are close enough to give you a bonus or curse.
Visibility can also be used to build AI behaviors.
For example, let’s suppose the enemy AI wants to throw a grenade to hit one of the players, but also wants to stand in a place where the players can’t shoot back. Grenades need to be close enough to hit the player, and also not behind an obstruction.
This diagram shows the map annotations an AI unit might calculate:
Grenades thrown into purple areas will successfully hit a player. Yellow and purple areas are dangerous to stand in; the player can shoot the AI unit from there. The AI needs to stand in a safe (dark blue) zone and throw a grenade into a purple zone, then take cover. How do you calculate cover? Run the visibility algorithm again from the place the AI plans to throw the grenade, making cabinets and tables block line of sight.
Although the main part of the algorithm is CPU bound, the GPU could be used for triangle rendering to a bitmap and compositing operations for combining bitmap outputs. (A boolean AND operation becomes a bitmap multiply; a boolean OR becomes a bitmap add and clamp.) The performance hasn’t been enough of a problem in my projects so I haven’t built a GPU version. If your game is CPU bound, consider using a subtractive algorithm (instead of the additive one shown here), rendering the shadow of each line segment as a quad. It will increase the rendering load on the GPU but it doesn’t require sorting on the CPU. If fill rate is an issue, consider rendering a light bitmap that’s at a lower resolution than the game screen, and scaling it up.