Instantly share code, notes, and snippets.

Embed
What would you like to do?
import math
const
even = 1
odd = -1
hexDirections = @[(1, 0, -1), (1, -1, 0), (0, -1, 1), (-1, 0, 1), (-1, 1, 0), (0, 1, -1)]
hexDiagonals = @[(2, -1, -1), (1, -2, 1), (-1, -1, 2), (-2, 1, 1), (-1, 2, -1), (1, 1, -2)]
type
Point = tuple[x,y: float]
Hex = tuple[q, r, s: int]
FractionalHex = tuple[q, r, s: float]
OffsetCoord = tuple[col, row: int]
Orientation = tuple[f0, f1, f2, f3, b0, b1, b2, b3, startAngle: float]
Layout = object
orientation: Orientation
size: Point
origin: Point
const
layoutPointy: Orientation = (sqrt(3.0), sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0, sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5)
layoutFlat: Orientation = (3.0 / 2.0, 0.0, sqrt(3.0) / 2.0, sqrt(3.0), 2.0 / 3.0, 0.0, -1.0 / 3.0, sqrt(3.0) / 3.0, 0.0)
proc add(a, b: Hex): Hex =
result = (a.q + b.q, a.r + b.r, a.s + b.s)
proc subtract(a, b: Hex): Hex =
result = (a.q - b.q, a.r - b.r, a.s - b.s)
proc scale(a: Hex, k: int): Hex =
result = (a.q * k, a.r * k, a.s * k)
proc rotateLeft(a: Hex): Hex =
result = (-a.s, -a.q, -a.r)
proc rotateRight(a: Hex): Hex =
result = (-a.r, -a.s, -a.q)
proc direction(direction: int): Hex =
result = hexDirections[direction]
proc neighbor(hex: Hex, direction: int): Hex =
result = add(hex, direction(direction))
proc diagonalNeighbor(hex: Hex, direction: int): Hex =
result = add(hex, hexDiagonals[direction])
proc length(hex: Hex): int =
result = ((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2).int
proc distance(a, b: Hex): int =
result = length(subtract(a, b))
proc round(h: FractionalHex): Hex =
var q = round(h.q).int
var r = round(h.r).int
var s = round(h.s).int
let qDiff = abs(q.float - h.q)
let rDiff = abs(r.float - h.r)
let sDiff = abs(s.float - h.s)
if qDiff > rDiff and qDiff > sDiff:
q = -r - s
else:
if rDiff > sDiff:
r = -q - s
else:
s = -q - r
result = (q, r, s)
proc lerp(a, b: FractionalHex, t: float): FractionalHex =
return (a.q * (1 - t) + b.q * t, a.r * (1 - t) + b.r * t, a.s * (1 - t) + b.s * t)
proc lineDraw(a, b: Hex): seq[Hex] =
let n = distance(a, b)
let a_nudge = (a.q.float + 0.000001, a.r.float + 0.000001, a.s.float - 0.000002)
let bNudge = (b.q.float + 0.000001, b.r.float + 0.000001, b.s.float - 0.000002);
result = @[]
let step = 1.0 / max(n, 1).float
for i in 0..<n:
result.add(round(lerp(aNudge, bNudge, step * i.float)))
proc qOffsetFromCube(offset: int, h: Hex): OffsetCoord =
let col = h.q
let row = h.r + ((h.q + offset * (h.q and 1)) / 2).int
result = (col, row)
proc qOffsetToCube(offset: int, h: OffsetCoord): Hex =
let q = h.col
let r = h.row - ((h.col + offset * (h.col and 1)) / 2).int
let s = -q - r
result = (q, r, s)
proc rOffsetFromCube(offset: int, h: Hex): OffsetCoord =
let col = h.q + ((h.r + offset * (h.r and 1)) / 2).int
let row = h.r
result = (col, row)
proc rOffsetToCube(offset: int, h: OffsetCoord): Hex =
let q = h.col - ((h.row + offset * (h.row and 1)) / 2).int
let r = h.row
let s = -q - r
result = (q, r, s)
proc hexToPixel(layout: Layout, h: Hex): Point =
let m = layout.orientation
let size = layout.size
let origin = layout.origin
let x = (m.f0 * h.q.float + m.f1 * h.r.float) * size.x
let y = (m.f2 * h.q.float + m.f3 * h.r.float) * size.y
result = (x + origin.x, y + origin.y)
proc pixelToHex(layout: Layout, p: Point): FractionalHex =
let m = layout.orientation
let size = layout.size
let origin = layout.origin
let pt: Point = ((p.x - origin.x) / size.x, (p.y - origin.y) / size.y)
let q = m.b0 * pt.x + m.b1 * pt.y
let r = m.b2 * pt.x + m.b3 * pt.y
result = (q, r, -q - r)
proc hexCornerOffset(layout: Layout, corner: int): Point =
let m = layout.orientation
let size = layout.size
let angle = 2.0 * PI * (m.start_angle - corner.float) / 6
result = (size.x * cos(angle), size.y * sin(angle))
proc polygonCorners(layout: Layout, h: Hex): seq[Point] =
result = @[]
let center = hex_to_pixel(layout, h)
for i in 0..<6:
let offset = hexCornerOffset(layout, i)
result.add((center.x + offset.x, center.y + offset.y))
when isMainModule:
import unittest
suite "hex tests":
test "arithmetic":
check((4, -10, 6) == add((1, -3, 2), (3, -7, 4)))
test "direction":
check((0, -1, 1) == direction(2))
test "neighbor":
check((1, -3, 2) == neighbor((1, -2, 1), 2))
test "diagonal":
check((-1, -1, 2) == diagonalNeighbor((1, -2, 1), 3))
test "distance":
check(7 == distance((3, -7, 4), (0, 0, 0)))
test "rotate right":
check((3, -2, -1) == rotateRight((1, -3, 2)))
test "rotate right":
check((3, -2, -1) == rotateRight((1, -3, 2)))
test "rotate left":
check((-2, -1, 3) == rotateLeft((1, -3, 2)))
test "round":
let a: FractionalHex = (0.0, 0.0, 0.0)
let b: FractionalHex = (1.0, -1.0, 0.0)
let c: FractionalHex = (0.0, -1.0, 1.0)
check((5, -10, 5) == round(lerp((0.0, 0.0, 0.0), (10.0, -20.0, 10.0), 0.5)))
check(round(a) == round(lerp(a, b, 0.4999)))
check(round(b) == round(lerp(a, b, 0.501)))
check(round(a) == round((a.q * 0.4 + b.q * 0.3 + c.q * 0.3, a.r * 0.4 + b.r * 0.3 + c.r * 0.3, a.s * 0.4 + b.s * 0.3 + c.s * 0.3)))
check(round(c) == round((a.q * 0.3 + b.q * 0.3 + c.q * 0.4, a.r * 0.3 + b.r * 0.3 + c.r * 0.4, a.s * 0.3 + b.s * 0.3 + c.s * 0.4)))
test "layout":
let h = (3, 4, -7)
let flat = Layout(
orientation: layoutFlat,
size: (10.0, 15.0),
origin: (35.0, 71.0)
)
check(h == round(pixelToHex(flat, hexToPixel(flat, h))))
let pointy = Layout(
orientation: layoutPointy,
size: (10.0, 15.0),
origin: (35.0, 71.0)
)
check(h == round(pixelToHex(pointy, hexToPixel(pointy, h))))
test "conversion roundtrip":
let a = (3, 4, -7)
let b = (1, -3)
check(a == qOffsetToCube(even, qOffsetFromCube(even, a)))
check(b == qOffsetFromCube(even, qOffsetToCube(even, b)))
check(a == qOffsetToCube(odd, qOffsetFromCube(odd, a)))
check(b == qOffsetFromCube(odd, qOffsetToCube(odd, b)))
check(a == rOffsetToCube(even, rOffsetFromCube(even, a)))
check(b == rOffsetFromCube(even, rOffsetToCube(even, b)))
check(a == rOffsetToCube(odd, rOffsetFromCube(odd, a)))
check(b == rOffsetFromCube(odd, rOffsetToCube(odd, b)))
test "offset from cube":
check((1, 3) == qOffsetFromCube(even, (1, 2, -3)))
check((1, 2) == qOffsetFromCube(odd, (1, 2, -3)))
test "offset to cube":
check((1, 2, -3) == qOffsetToCube(even, (1, 3)))
check((1, 2, -3) == qOffsetToCube(odd, (1, 2)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment