diff --git a/hex.lua b/hex.lua index e0c2031..66cf09a 100644 --- a/hex.lua +++ b/hex.lua @@ -1,81 +1,64 @@ ---[[ AXIAL/CUBE COORDINATE SYSTEM FOR AMULET/LUA]] ---[[ - - all hexes in functions are assumed to be amulet vectors. - in amulet, vector arithmetic works already with [ + - * / ] - things like equality and distance are implemented here. - - some algorithms use axial coordinates for hexes: vec2(s, t) - others use cube coordinates: vec3(s, t, z) where s + t + z = 0 - this is for simplicity - many algorithms don't care about the - third coordinate, and if they do, the missing coordinate can - be calculated from the other two. - - -- note on orientation: - because of the way amulet draws hexagons, it's much easier to assume - the user wants to use the flat map. rotation after the fact to - achieve other orienations is probably possible, but might have some - aliasing issues. TODO work on this. - - some of the primary resources used to develop this library: - - https://redblobgames.com/grid/hexagons - simply amazing. - - http://amulet.xyz/doc - amulet documentation - - TODO that place that had the inner circle/outer circle ratio?? - - ]] +----- [[ AXIAL/CUBE COORDINATE SYSTEM FOR AMULET/LUA]] ------------------------- +--[[ author@churchianity.ca + -- INTRODUCTION + this is a library for making grids of hexagons using lua. + it has made use of exclusively standard lua 5.2 functionality, + making it as portable as possible. it doesn't even use a point + class, (or classes/metatables at all) simply returning tables + of integers, which can later be unpacked into your amulet + vectors, or whatever else you want to use. + + this can result in some nasty looking lines with lots of table + unpacks, but if your graphics library likes traditional lua + types, you will be better off. + + it supports triangular, hexagonal, rectangular, and + parallelogram map shapes. + + it supports non-regular hexagons, though it's trickier to get + working in amulet. TODO work on this. + + -- NOTE ON ORIENTATION + AMULET + because of the way amulet draws hexagons (amulet essentially + draws a 6-sided circle from a centerpoint, instead of of a + series of lines connecting points), the flat orientation is + default and recommended. other orientations are possible + with am.rotate, but can cause aliasing issues. TODO work on this. + + -- RESOURCES USED TO DEVELOP THIS LIBRARY + https://redblobgames.com/grid/hexagons - simply amazing. amit is a god. + http://amulet.xyz/doc - amulet documentation + TODO that place that had the inner circle/outer circle ratio?? + ]] --- GENERALLY USEFUL FUNCTIONS -------------------------------------------------- +----- [[ GENERALLY USEFUL FUNCTIONS ]] ----------------------------------------- +-- just incase you don't already have a rounding function. function round(n) return n % 1 >= 0.5 and math.ceil(n) or math.floor(n) end +---- [[ HEX CONSTANTS ]] ------------------------------------------------------- --- HEX CONSTANTS --------------------------------------------------------------- - - -- all possible vector directions from a given hex by edge -HEX_DIRECTIONS = {vec2( 1 , 0), - vec2( 1 , -1), - vec2( 0 , -1), - vec2(-1 , 0), - vec2(-1 , 1), - vec2( 0 , 1)} +-- all possible vector directions from a given hex by edge +HEX_DIRECTIONS = {{ 1 , 0}, + { 1 , -1}, + { 0 , -1}, + {-1 , 0}, + {-1 , 1}, + { 0 , 1}} -- HEX UTILITY FUNCTIONS ------------------------------------------------------- -function hex_equals(a, b) - return a.s == a.t and b.s == b.t -end - -function hex_nequals(a, b) - return not hex_equals(a, b) -end - -function hex_length(hex) - return ((math.abs(hex.s) + math.abs(hex.t) + math.abs(-hex.s - hex.t)) / 2) -end - -function hex_distance(a, b) - return hex_length(a - b) -end - -function hex_direction(direction) - return HEX_DIRECTIONS[direction] -end - -function hex_neighbour(hex, direction) - return hex + HEX_DIRECTIONS[direction] -end - -function hex_round(hex) - rs = round(hex.s) - rt = round(hex.t) - rz = round(-hex.s + -hex.t) +function hex_round(s, t) + rs = round(s) + rt = round(t) + rz = round(-s - t) - sdelta = math.abs(rs - hex.s) - tdelta = math.abs(rt - hex.t) - zdelta = math.abs(rz + hex.s + hex.t) + sdelta = math.abs(rs - s) + tdelta = math.abs(rt - t) + zdelta = math.abs(rz + s + t) if sdelta > tdelta and sdelta > zdelta then rs = -rt - rz @@ -85,33 +68,106 @@ function hex_round(hex) rz = -rs - rt end - return vec2(rs, rt) + return {rs, rt} end --- COORDINATE CONVERSION FUNCTIONS --------------------------------------------- +----- [[ LAYOUT, ORIENTATION & COORDINATE CONVERSION ]] ----------------------- + +-- forward & inverse matrices used for the flat orientation. +FLAT_ORIENTATION = {3.0/2.0, 0.0, 3.0^0.5/2.0, 3.0^0.5, + 2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0} + +-- forward & inverse matrices used for the pointy orientation. +POINTY_ORIENTATION = {3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0, + 3.0^0.5/3.0, -1.0/3.0, 0.0, 2.0/3.0} + +-- layout. +function layout(size, orientation, origin, width, height, radius) + return {size = size or {11, 11}, + orientation = orientation or FLAT_ORIENTATION, + origin = origin or {0, 0}, + width = width or 45, + height = height or 31, + radius = radius or width or 6} +end + +-- hex to screen +function hex_to_pixel(s, t, layout) + M = layout.orientation + + x = (M[1] * s + M[2] * t) * layout.size[1] + y = (M[3] * s + M[4] * t) * layout.size[2] + + return {x + layout.origin[1], y + layout.origin[2]} +end - -- forward & inverse matrices used for coordinate conversion -local M = mat2(3.0/2.0, 0.0, 3.0^0.5/2.0, 3.0^0.5 ) -local W = mat2(2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0) +-- screen to hex +function pixel_to_hex(x, y, layout) + M = layout.orientation - -- hex to screen -function hex_to_pixel(hex) + px = {(x - layout.origin[1]) / layout.size[1], + (y - layout.origin[2]) / layout.size[2]} - x = (M[1][1] * hex.s + M[1][2] * hex.t) * map.size - y = (M[2][1] * hex.s + M[2][2] * hex.t) * map.size + s = M[5] * px[1] + M[6] * px[2] + t = M[7] * px[1] + M[8] * px[2] - return vec2(x + map.origin.x, y + map.origin.y) + return hex_round(s, t) end - -- screen to hex -function pixel_to_hex(pix) - pix = vec2(pix.x - map.origin.x) / map.size, - (pix.y - map.origin.y) / map.size +----- [[ MAP STORAGE & RETRIEVAL ]] -------------------------------------------- +--[[ all functions return a table of tables; a map of points + storage functions take a range of hex coordinates, and return pixel ones. + retrieval functions do the opposite. + everything except map shape is determined by layout. + pick a pair of functions based on the shape of map you want to use. + it is not advised to use a single layout instance with multiple shapes. ]] + +-- returns parallelogram-shaped map. width and height are used. +function ogram_map_store(layout) + map = {} + for s = 0, layout.width do + for t = 0, layout.height do + table.insert(map, hex_to_pixel(s, t, layout)) + end + end + return map +end - s = W[1][1] * pix.x + W[1][2] * pix.y - t = W[2][1] * pix.x + W[2][2] * pix.y +-- returns triangular map. radius is used as the triangle side length. +function tri_map_store(layout) + map = {} + for s = 0, layout.radius do + for t = layout.radius - s, layout.radius do + table.insert(map, hex_to_pixel(s, t, layout)) + end + end + return map +end - return hex_round(vec2(s, t)) +-- returns hexagonal map. length of map is radius * 2 + 1 +function hex_map_store(layout) + map = {} + for s = -layout.radius, layout.radius do + t1 = math.max(-layout.radius, -s - layout.radius) + t2 = math.min(layout.radius, -s + layout.radius) + + for t = t1, t2 do + table.insert(map, hex_to_pixel(s, t, layout)) + end + end + return map +end + +-- returns rectangular map. width and height are used. +function rect_map_store(layout) + map = {} + for s = 0, layout.width do + soffset = math.floor(s / 2) + + for t = -soffset, layout.height - soffset do + table.insert(map, hex_to_pixel(s, t, layout)) + end + end + return map end --- MAP FUNCTIONS --------------------------------------------------------------- diff --git a/main.lua b/main.lua index 8cb20c0..2a1f27a 100644 --- a/main.lua +++ b/main.lua @@ -1,32 +1,68 @@ ---[[ WARZONE 2 - HEXAGONAL GRID RESOURCE BASED TOWER DEFENSE GAME]] ---[[ +----- [[ WARZONE 2 - HEXAGONAL GRID RESOURCE BASED TOWER DEFENSE GAME]] ------- +--[[ author@churchianity.ca ]] require "hex" --- ENTRY POINT ----------------------------------------------------------------- -win = am.window { - title = "Warzone 2: Electric Boogaloo", +----- [[ DUMMY FUNCTIONS ]] ------------------------------------------------------ + +function draw_axes() + xaxis = am.line(vec2(-win.width / 2, 0) , vec2(win.width / 2, 0)) + yaxis = am.line(vec2(0, -win.height / 2), vec2(0, win.height / 2)) + + title_scene:append(xaxis) + title_scene:append(yaxis) +end + +function rcolor() + return vec4(math.random(20, 80) / 100) +end +function show_hex_coords() + win.scene:action(function() + x, y = unpack(pixel_to_hex(win:mouse_position().x, win:mouse_position().y, layout)) + test_scene = ( + am.translate(vec2(unpack(hex_to_pixel(x, y, layout)))) + ^ am.text(string.format("%d, %d", x, y))) + end) +end + +local win = am.window { + title = "Warzone 2: Electric Boogaloo", + -- BASE RESOLUTION = 3/4 * WXGA Standard 16:10 Aspect Ratio - width = 1280 * 3 / 4, - height = 800 * 3 / 4, + width = 1280 * 3 / 4, -- 960 + height = 800 * 3 / 4} -- 600 + +local layout = layout({11, 11}, + FLAT_ORIENTATION, + vec2(win.left, win.bottom), + 45, 31) - clear_color = vec4(0, 0, 0, 0) -} +----- [[ MAP RENDERING ]] ------------------------------------------------ --- GROUPS -local grid = am.group() +function render_map(layout) + coords = rect_map_store(layout) + map = am.group() ---[[ -xaxis = am.line(vec2(-win.width / 2, 0) , vec2(win.width / 2, 0)) -yaxis = am.line(vec2(0, -win.height / 2), vec2(0, win.height / 2)) + for _,v in pairs(coords) do + map:append(am.circle(vec2(unpack(v)), layout.size[1], rcolor(), 6)) + end + return map +end -grid:append(xaxis) -grid:append(yaxis) ---]] +----- [[ MAIN ]] ----------------------------------------------------------- -win.scene = grid +local game_scene = render_map(layout) +local test_scene = am.group() +win.scene = am.group{test_scene, game_scene} +test_scene:action(function() + x, y = unpack(pixel_to_hex(win:mouse_position().x, win:mouse_position().y, layout)) + test_scene:replace("text", + am.translate(vec2(unpack(hex_to_pixel(x, y, layout)))) + ^ am.text(string.format("%d, %d", x, y))) + am.delay(1) +end)