|
| 1 | +## Bresenham's line algorithm |
| 2 | + |
| 3 | +func computeStep(inStart, inEnd: int): int = |
| 4 | + if inStart < inEnd: 1 else: -1 |
| 5 | + |
| 6 | +type Point = tuple[x, y: int] |
| 7 | + |
| 8 | +func drawBresenhamLine*(posA, posB: Point): seq[Point] = |
| 9 | + ## returns a sequence of coordinates approximating the straight line |
| 10 | + ## between points `posA` and `posB`. |
| 11 | + ## These points are determined using the |
| 12 | + ## [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). |
| 13 | + ## This implementation balances the positive and negative errors between `x` and `y` coordinates. |
| 14 | + let |
| 15 | + dx = abs(posA.x-posB.x) |
| 16 | + dy = -abs(posA.y-posB.y) |
| 17 | + xStep = computeStep(posA.x, posB.x) |
| 18 | + yStep = computeStep(posA.y, posB.y) |
| 19 | + |
| 20 | + var |
| 21 | + difference = dx + dy |
| 22 | + res: seq[Point] = @[] |
| 23 | + x = posA.x |
| 24 | + y = posA.y |
| 25 | + |
| 26 | + res.add((x, y)) |
| 27 | + while (x, y) != posB: |
| 28 | + let doubleDifference = 2 * difference |
| 29 | + if doubleDifference >= dy: |
| 30 | + difference += dy |
| 31 | + x += xStep |
| 32 | + if doubleDifference <= dx: |
| 33 | + difference += dx |
| 34 | + y += yStep |
| 35 | + res.add((x, y)) |
| 36 | + |
| 37 | + res |
| 38 | + |
| 39 | +when isMainModule: |
| 40 | + import std/[unittest, sequtils, algorithm] |
| 41 | + suite "bresenhamLine": |
| 42 | + const testCases = [ |
| 43 | + ("horizontal", (1, 0), (3, 0), @[(1, 0), (2, 0), (3, 0)]), |
| 44 | + ("vertical", (0, -1), (0, 1), @[(0, -1), (0, 0), (0, 1)]), |
| 45 | + ("trivial", (0, 0), (0, 0), @[(0, 0)]), |
| 46 | + ("diagonal", (0, 0), (3, 3), @[(0, 0), (1, 1), (2, 2), (3, 3)]), |
| 47 | + ("shiftedDiagonal", (5, 1), (8, 4), @[(5, 1), (6, 2), (7, 3), (8, 4)]), |
| 48 | + ("halfDiagonal", |
| 49 | + (0, 0), (5, 2), |
| 50 | + @[(0, 0), (1, 0), (2, 1), (3, 1), (4, 2), (5, 2)]), |
| 51 | + ("doubleDiagonal", |
| 52 | + (0, 0), (2, 5), |
| 53 | + @[(0, 0), (0, 1), (1, 2), (1, 3), (2, 4), (2, 5)]), |
| 54 | + ("line1", |
| 55 | + (2, 3), (8, 7), |
| 56 | + @[(2, 3), (3, 4), (4, 4), (5, 5), (6, 6), (7, 6), (8, 7)]), |
| 57 | + ("line2", |
| 58 | + (2, 1), (8, 5), |
| 59 | + @[(2, 1), (3, 2), (4, 2), (5, 3), (6, 4), (7, 4), (8, 5)]), |
| 60 | + ].mapIt: |
| 61 | + (name: it[0], posA: it[1], posB: it[2], path: it[3]) |
| 62 | + |
| 63 | + func swapCoordinates(inPos: Point): Point = |
| 64 | + (inPos.y, inPos.x) |
| 65 | + |
| 66 | + func swapCoordinates(path: openArray[Point]): seq[Point] = |
| 67 | + path.map(swapCoordinates) |
| 68 | + |
| 69 | + for tc in testCases: |
| 70 | + test tc.name: |
| 71 | + checkpoint("returns expected result") |
| 72 | + check drawBresenhamLine(tc.posA, tc.posB) == tc.path |
| 73 | + |
| 74 | + checkpoint("is symmetric") |
| 75 | + check drawBresenhamLine(tc.posB, tc.posA) == reversed(tc.path) |
| 76 | + |
| 77 | + checkpoint("returns expected result when coordinates are swapped") |
| 78 | + check drawBresenhamLine(swapCoordinates(tc.posA), |
| 79 | + swapCoordinates(tc.posB)) == swapCoordinates(tc.path) |
| 80 | + |
| 81 | + checkpoint("is symmetric when coordinates are swapped") |
| 82 | + check drawBresenhamLine(swapCoordinates(tc.posB), |
| 83 | + swapCoordinates(tc.posA)) == reversed(swapCoordinates(tc.path)) |
0 commit comments