Skip to content

Commit 25683fe

Browse files
vil02ZoomRmc
andauthored
feat: add bresenhams_line (#71)
* feat: add `bresenhams_line` * style: apply suggestions from the review Co-authored-by: Zoom <[email protected]> --------- Co-authored-by: Zoom <[email protected]>
1 parent 7bfca7b commit 25683fe

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

graphics/bresenhams_line.nim

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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

Comments
 (0)