Created
October 2, 2017 13:09
-
-
Save zacharycarter/c5565930ba57af5554bb8180d566f067 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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