Skip to content

Instantly share code, notes, and snippets.

@Pyr3z
Last active January 22, 2023 08:42
Show Gist options
  • Save Pyr3z/46884d67641094d6cf353358566db566 to your computer and use it in GitHub Desktop.
Save Pyr3z/46884d67641094d6cf353358566db566 to your computer and use it in GitHub Desktop.
(C#) An adapted Bresenham procedure to calculate integral cells forming the raster line between two points.
/*!****************************************************************************
* \file RasterLineTo.cs
* \author Levi Perez (levianperez\@gmail.com)
* \author Jack Elton Bresenham (IBM, 1962)
*
* \copyright None. I didn't invent this algorithm, and neither did you.
* If I had to choose a license, it would be <https://unlicense.org>.
*****************************************************************************/
using System.Collections.Generic; // For coroutine/generator-like return types.
public static partial class Raster
{
public static IEnumerable<(int x, int y)> Line(int ax, int ay, int bx, int by)
{
// declare all locals at the top so it's obvious how big the footprint is
int dx, dy, xinc, yinc, side, i, error;
// starting cell is always returned
yield return (ax,ay);
xinc = (bx < ax) ? -1 : 1;
yinc = (by < ay) ? -1 : 1;
dx = xinc * (bx - ax);
dy = yinc * (by - ay);
if (dx == dy) // Handle perfect diagonals
{
// I include this "optimization" for more aesthetic reasons, actually.
// While Bresenham's Line can handle perfect diagonals just fine, it adds
// additional cells to the line that make it not a perfect diagonal
// anymore. So, while this branch is ~twice as fast as the next branch,
// the real reason it is here is for style.
// Also, there *is* the reason of performance. If used for cell-based
// raycasts, for example, then perfect diagonals will check half as many
// cells.
while (dx --> 0)
{
ax += xinc;
ay += yinc;
yield return (ax,ay);
}
yield break;
}
// Handle all other lines
side = -1 * ((dx == 0 ? yinc : xinc) - 1);
i = dx + dy;
error = dx - dy;
dx *= 2;
dy *= 2;
while (i --> 0)
{
if (error > 0 || error == side)
{
ax += xinc;
error -= dy;
}
else
{
ay += yinc;
error += dx;
}
yield return (ax,ay);
}
}
}
@Pyr3z
Copy link
Author

Pyr3z commented Sep 25, 2022

This is a very cheap algorithm, however the largest measurable cost comes with GC alloc associated with constructing implicit IEnumerable objects. You could unravel this procedure to avoid this, however the function call becomes much more tied to specific use cases, and thus much less maintainable.

(Trust me, I know this from experience: it has always been deemed by my teams to be more worthwhile to have maintainable code here and just deal with the small payment to GC alloc.)

@Pyr3z
Copy link
Author

Pyr3z commented Sep 26, 2022

Decided a nice visual test was (finally) appropriate for this particular algo.

bresenham-test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment