Line drawing on a grid

Graphics libraries provide line-drawing routines, sometimes with antialiasing[1] and variable width. On a grid map, line drawing is useful for for visibility, the path of an arrow/bullet, and enemy AI. I sometimes see people adapting the Bresenham line drawing algorithm to draw lines on a grid (especially for roguelike games), but I prefer simpler algorithms. I'll show the algorithms I use. I'm not a line-drawing expert so please let me know if there are better algorithms that are similarly simple.

Next we need to figure out which grid squares those points are in. We can round x and y to the nearest integer to find the grid tile:

We also need to decide how many points to include. Too few and the line has holes. Too many and the line has overdraw. How many do we need? The right number is the diagonal distance between the endpoints, which in this case is .

We can do interpolation on any vector space[2]. T can be a number, a 2d point, a 3d point, an angle, a time, a color, or other things too. My guide to hexagonal grids[3] uses interpolation to draw lines on a hex grid. Note that T and ΔT may be the same type, but sometimes they are different. For example, T may be a timestamp and ΔT may be a time difference, or T may be an orientation and ΔT may be a rotation. When T and ΔT are the same type, d = a - b can be implemented as d = a + (-1 * b).

Linear interpolation is calculating a position and then rounding it. What happens when the value is exactly 0.5? Rounding rules vary[4]. That, and floating point precision, make linear interpolation not always choose points in a way that preserves consistency with rotation, reversing, and other symmetry.

I think the thing to do would be to “nudge” the initial points by epsilon. However I haven't explored this.

You can optimize the regular line drawing algorithm with these steps; it will turn into the DDA algorithm[5]:

Floating point vs fixed point

Most likely, profiling will find that Math.round() is expensive. You can instead use fixed point arithmetic, and replace rounding with bit shifting.

Unrolling lerp

Instead of calculating t each time through the loop, and then x = (b.x-a.x)*t (a subtract and multiply), you can calculate Δx = (b.x-a.x)*Δt outside the loop, and then use x += Δx. Same for y. This will replace the multiplies with adds.

Unrolling loop

You can unroll the loop by keeping four x and y pairs, and then using t += 4*Δt. Would this allow the use of SSE instructions? I don't know.

Separate cases

Either Δx or Δy will be ±1. You might want to have a separate versions for each case. For diagonal lines, both will be ±1. For orthogonal lines, one or the other will be 0. If these cases are common, write a separate routine for them.

Before optimizing, profile and make sure linear interpolation is your bottleneck. In my projects, it rarely is, so I haven't bothered implementing these optimizations. (However, I may do that in a future version of this article.)

Interpolation is simple and general but doesn't take into account properties of the grid. Another approach to line drawing is to take one step at a time. This type of movement also allows putting walls on edges so that a wall could block line of sight or movement.

In the above lines, we took both orthogonal steps (north, east, south, west) and diagonal steps. Step-by-step algorithms give us more flexibility. What if we only want to take orthogonal steps?

The strategy here is to think about the grid edges being crossed, both vertical and horizontal. At every step we either cross a vertical edge (horizontal step) or cross a horizontal edge (vertical step).

It's more work to make it symmetric. Also, sometimes you'll want more control over whether you pick a horizontal step or a vertical step, especially if the choice is close, and there's a wall in one direction but not the other.

"Supercover" lines catch all the grid squares that a line passes through. I believe (but am not sure) that it is somewhere in between regular line drawing and grid movement line drawing. Unlike grid movement line drawing, we can take a diagonal step only if the line passes exactly through the corner.

This looks just like the grid movement line drawing, except when the line goes through a grid corner.

Implementation note: it's usually a bad idea to compare two floating point numbers for equality, as I do here. However, since ix, nx, iy, ny are all integers, we can ensure this works properly by cross-multiplying and rewriting the test with integer arithmetic.

if((1+2*ix) * ny == (1+2*iy) * nx){ ... }

It still feels somewhat fragile to me, and I'm guessing there are better algorithms out there.

There's lots written about line drawing but I haven't researched it extensively. When drawing graphical lines, I use the graphics library. It's only on grids that I needed to find some other algorithms.

Created December 2014 with Emacs Org-mode, from line-drawing.org, and D3.js v3, and a mix of Typescript and Javascript. As much as possible, the algorithms presented are used to power the page. Last modified: 02 Dec 2018.