Implement Bresenham's algorithm for line rasterization.

This is implemented as a static Line.Rasterize(Point p1, Point p2) function
that returns an array of Points. There's also a Line.Rasterize(x1, y1, x1, y2)
version for convenience.

Unit tests included.

GitOrigin-RevId: 525098f8c76c6c3d1a6ff2c32fa2206cff080a11
This commit is contained in:
Colin McMillen 2020-01-23 16:08:01 -05:00
parent 252fe5b243
commit bba9f643eb
4 changed files with 318 additions and 1 deletions

46
Shared/Line.cs Normal file
View File

@ -0,0 +1,46 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace SemiColinGames {
class Line {
public static Point[] Rasterize(Point p1, Point p2) {
return Line.Rasterize(p1.X, p1.Y, p2.X, p2.Y);
}
// Rasterizes a line using Bresenham's line-drawing algorithm.
//
// References:
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
// http://members.chello.at/~easyfilter/bresenham.html
// http://members.chello.at/~easyfilter/Bresenham.pdf (section 1.6)
public static Point[] Rasterize(int x1, int y1, int x2, int y2) {
int dx = Math.Abs(x2 - x1);
int stepX = x1 < x2 ? 1 : -1;
int dy = -Math.Abs(y2 - y1);
int stepY = y1 < y2 ? 1 : -1;
int error = dx + dy;
int errorXY = 0; // Error value e_xy from the PDF.
// The size of the output is the size of the longer dimension, plus one.
int resultSize = Math.Max(dx, -dy) + 1;
var result = new Point[resultSize];
int i = 0;
result[0] = new Point(x1, y1);
while (x1 != x2 || y1 != y2) {
i++;
errorXY = 2 * error;
if (errorXY >= dy) { // e_xy + e_x > 0
error += dy;
x1 += stepX;
}
if (errorXY <= dx) { // e_xy + e_y < 0
error += dx;
y1 += stepY;
}
result[i] = new Point(x1, y1);
}
return result;
}
}
}

View File

@ -15,6 +15,7 @@
<Compile Include="$(MSBuildThisFileDirectory)FpsCounter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)FpsCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)History.cs" /> <Compile Include="$(MSBuildThisFileDirectory)History.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IDisplay.cs" /> <Compile Include="$(MSBuildThisFileDirectory)IDisplay.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Line.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Player.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Player.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SneakGame.cs" /> <Compile Include="$(MSBuildThisFileDirectory)SneakGame.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World.cs" /> <Compile Include="$(MSBuildThisFileDirectory)World.cs" />

269
SharedTests/LineTests.cs Normal file
View File

@ -0,0 +1,269 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace SemiColinGames.Tests {
[TestClass]
public class LineTests {
[TestMethod]
public void TestRasterizeSinglePoint() {
var p1 = new Point(10, 10);
var expected = new Point[] { p1 };
var result = Line.Rasterize(p1, p1);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeHorizontal() {
var p1 = new Point(10, 10);
var p2 = new Point(15, 10);
var expected = new Point[] {
new Point(10, 10),
new Point(11, 10),
new Point(12, 10),
new Point(13, 10),
new Point(14, 10),
new Point(15, 10)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeHorizontalReverse() {
var p1 = new Point(15, 10);
var p2 = new Point(10, 10);
var expected = new Point[] {
new Point(15, 10),
new Point(14, 10),
new Point(13, 10),
new Point(12, 10),
new Point(11, 10),
new Point(10, 10)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeVertical() {
var p1 = new Point(10, 10);
var p2 = new Point(10, 15);
var expected = new Point[] {
new Point(10, 10),
new Point(10, 11),
new Point(10, 12),
new Point(10, 13),
new Point(10, 14),
new Point(10, 15)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeVerticalReverse() {
var p1 = new Point(10, 15);
var p2 = new Point(10, 10);
var expected = new Point[] {
new Point(10, 15),
new Point(10, 14),
new Point(10, 13),
new Point(10, 12),
new Point(10, 11),
new Point(10, 10)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDiagonalPosPos() {
var p1 = new Point(0, 0);
var p2 = new Point(5, 5);
var expected = new Point[] {
new Point(0, 0),
new Point(1, 1),
new Point(2, 2),
new Point(3, 3),
new Point(4, 4),
new Point(5, 5)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDiagonalPosNeg() {
var p1 = new Point(0, 5);
var p2 = new Point(5, 0);
var expected = new Point[] {
new Point(0, 5),
new Point(1, 4),
new Point(2, 3),
new Point(3, 2),
new Point(4, 1),
new Point(5, 0)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDiagonalNegPos() {
var p1 = new Point(5, 0);
var p2 = new Point(0, 5);
var expected = new Point[] {
new Point(5, 0),
new Point(4, 1),
new Point(3, 2),
new Point(2, 3),
new Point(1, 4),
new Point(0, 5)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDiagonalNegNeg() {
var p1 = new Point(5, 5);
var p2 = new Point(0, 0);
var expected = new Point[] {
new Point(5, 5),
new Point(4, 4),
new Point(3, 3),
new Point(2, 2),
new Point(1, 1),
new Point(0, 0)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDoubleSlope() {
var p1 = new Point(0, 0);
var p2 = new Point(4, 9);
var expected = new Point[] {
new Point(0, 0),
new Point(0, 1),
new Point(1, 2),
new Point(1, 3),
new Point(2, 4),
new Point(2, 5),
new Point(3, 6),
new Point(3, 7),
new Point(4, 8),
new Point(4, 9)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDoubleSlopePlusOne() {
var p1 = new Point(0, 0);
var p2 = new Point(5, 10);
var expected = new Point[] {
new Point(0, 0),
new Point(1, 1),
new Point(1, 2),
new Point(2, 3),
new Point(2, 4),
new Point(3, 5),
new Point(3, 6),
new Point(4, 7),
new Point(4, 8),
new Point(5, 9),
new Point(5, 10)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeDoubleSlopePlusOneReverse() {
var p1 = new Point(5, 10);
var p2 = new Point(0, 0);
var expected = new Point[] {
new Point(5, 10),
new Point(4, 9),
new Point(4, 8),
new Point(3, 7),
new Point(3, 6),
new Point(2, 5),
new Point(2, 4),
new Point(1, 3),
new Point(1, 2),
new Point(0, 1),
new Point(0, 0)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeHalfSlope() {
var p1 = new Point(0, 0);
var p2 = new Point(9, 4);
var expected = new Point[] {
new Point(0, 0),
new Point(1, 0),
new Point(2, 1),
new Point(3, 1),
new Point(4, 2),
new Point(5, 2),
new Point(6, 3),
new Point(7, 3),
new Point(8, 4),
new Point(9, 4)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeHalfSlopePlusOne() {
var p1 = new Point(0, 0);
var p2 = new Point(10, 5);
var expected = new Point[] {
new Point(0, 0),
new Point(1, 1),
new Point(2, 1),
new Point(3, 2),
new Point(4, 2),
new Point(5, 3),
new Point(6, 3),
new Point(7, 4),
new Point(8, 4),
new Point(9, 5),
new Point(10, 5)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
[TestMethod]
public void TestRasterizeHalfSlopePlusOneReverse() {
var p1 = new Point(10, 5);
var p2 = new Point(0, 0);
var expected = new Point[] {
new Point(10, 5),
new Point(9, 4),
new Point(8, 4),
new Point(7, 3),
new Point(6, 3),
new Point(5, 2),
new Point(4, 2),
new Point(3, 1),
new Point(2, 1),
new Point(1, 0),
new Point(0, 0)
};
var result = Line.Rasterize(p1, p2);
CollectionAssert.AreEqual(expected, result);
}
}
}

View File

@ -8,7 +8,7 @@
<ProjectGuid>{C86694A5-DD99-4421-AA2C-1230F11C10F8}</ProjectGuid> <ProjectGuid>{C86694A5-DD99-4421-AA2C-1230F11C10F8}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SharedTests</RootNamespace> <RootNamespace>SemiColinGames.Tests</RootNamespace>
<AssemblyName>SharedTests</AssemblyName> <AssemblyName>SharedTests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
@ -53,6 +53,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="HistoryTests.cs" /> <Compile Include="HistoryTests.cs" />
<Compile Include="LineTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>