diff --git a/Shared/Line.cs b/Shared/Line.cs new file mode 100644 index 0000000..0a4c44c --- /dev/null +++ b/Shared/Line.cs @@ -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; + } + } +} diff --git a/Shared/Shared.projitems b/Shared/Shared.projitems index 12c1794..009b974 100644 --- a/Shared/Shared.projitems +++ b/Shared/Shared.projitems @@ -15,6 +15,7 @@ + diff --git a/SharedTests/LineTests.cs b/SharedTests/LineTests.cs new file mode 100644 index 0000000..5036aa7 --- /dev/null +++ b/SharedTests/LineTests.cs @@ -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); + } + } +} diff --git a/SharedTests/SharedTests.csproj b/SharedTests/SharedTests.csproj index 741829e..c60ff41 100644 --- a/SharedTests/SharedTests.csproj +++ b/SharedTests/SharedTests.csproj @@ -8,7 +8,7 @@ {C86694A5-DD99-4421-AA2C-1230F11C10F8} Library Properties - SharedTests + SemiColinGames.Tests SharedTests v4.7.2 512 @@ -53,6 +53,7 @@ +