For my roguelike project this year I'm implementing "thin walls". The walls will be along edges instead of occupying a full tile. I had to build a new map generator to support this style of wall, and I also had to build a new field of view algorithm. I also have page about visibility with polygon obstacles.

I want to calculate which **tiles** are visible, with the walls blocking your vision:

I'm calculating this for one 90° sector at a time, as it simplifies the logic.

## 1 Range expansion#

The main idea here is that the range will *expand* as we move away from the player:

This comes from "similar triangles" in geometry class. Any subrange will expand the same way:

That means we can start with the full range, then reduce it every time we see a wall:

## 2 Range subtraction#

One of the main ideas I wanted to pursue was maintaining an *visible set of ranges*, \((a_{1}, b_{1}), (a_{2}, b_{2}), …\), where \(a_{1} ≤ x ≤ b_{1}\) OR \(a_{2} ≤ x ≤ b_{2}\) OR …. These would be the horizontal green lines in the above diagrams.

On each horizontal edge, I subtract the set of walls \((p_{1}, q_{1}), …\) from the visible set.

Then to move to the next horizontal row down, I need to widen the visible set by multiplying each value by \((y+1) / y\). For example in the above diagram, the visible range is \(-0.5 ≤ x ≤ +0.5\) at \(y = 0.5\). Then moving up a row, it expands to \(-1.5 ≤ x ≤ +1.5\) at \(y = 1.5\).

What is the algorithm for range difference?

for (let shaded in wallSet) { visible = visible.flatMap(lit => subtract(lit, shaded)); } function subtract(A, B) { return [ {left: A.left, right: Math.min(A.right, B.left)}, {left: Math.max(A.left, B.right), right: A.right} ].filter(range => range.left < range.right); }

Test cases and visualizations for a single segment subtracted from a single segment:

Case | a–b minus c–d | |
---|---|---|

{{rule[0]}} | {{format([rule[1]])}} \ {{format([rule[2]])}} = {{format(subtract(rule[1], rule[2]))}} |

Also see https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html^{[1]}

## 3 Horizontal walls#

TODO: this should be fairly simple, as I already have that diagram above, but I just haven't coded up the algorithm yet

As we walk down the rows, subtract out the range of the walls from the current visible range:

## 4 Vertical walls#

TODO: this is trickier

For the output, we need to evaluate the *middle* of each row. A vertical wall casts a shadow like this:

So we need to know if the shadow triggers before or after the middle of the tile. I haven't worked out the details yet.

## 5 Symmetry#

TODO: Like the albertford tutorial, I will make 4 copies of this using rotation to calculate the visibility in each direction

First take a coordinate and subtract (player.x, player.y). Then rotate (dx, dy) one of four ways: (dx, dy), (dy, -dx), (-dx, -dy), (-dy, dx). Then add back (player.x, player.y). Doing this for thin walls is slightly trickier but not hard.

## 6 More#

- https://gist.github.com/Slogo/2f1f1eed8231445a66c9a3aa14d31f27
^{[2]} - albertford page https://www.albertford.com/shadowcasting/
^{[3]} - roguebasin pages http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
^{[4]}

Note that my algorithm is *not recursive* but seems to behave similarly to the recursive shadowcasting approaches.