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:
parent
252fe5b243
commit
bba9f643eb
46
Shared/Line.cs
Normal file
46
Shared/Line.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
269
SharedTests/LineTests.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user