======Hexagon Grid Tutorial (Axial/Cubial Coordinates)======
=====Introduction=====
This tutorial showcases how to draw a hexagon grid using a shader, as well as using the mouse position to highlight specific hexagon tiles.\\ \\
{{tutorials:shaders:hex_shader_screen.png?200x150}}\\ \\
It is based off of the this [[en:tutorials:shaders:hexagongrid|Hexagon Grid Tutorial]], the key difference being that this example makes use of an axial/cubial coordinate system for pixel-to-hex calculations, as opposed to the grid-based system used by the old tutorial.
=====Details=====
The axial/cubial coordinate system and associated mathematics used in this example are derived from the theory presented in this blog post: https://www.redblobgames.com/grids/hexagons/
=====Source Code=====
A complete version of this tutorial's source code can be found at the following git repository: https://github.com/LudiG/tut_hex\\ \\
This repository also contains hexagon resource files that can be used as textures for your hexagon shader.\\ \\
**__NOTE:__** Pointy-top and flat-top hexagons use different texture files, so you will need to ensure that you match the right files with your hexagon layout.
====INI File (tut_hex.ini)====
**ORX Config File**
[Display]
ScreenWidth = 800
ScreenHeight = 600
Title = Hexagon Grid Tutorial
[Input]
SetList = MainInput
[MainInput]
KEY_ESCAPE = Quit
[Viewport]
Camera = Camera
BackgroundColor = (210, 180, 140)
[Camera]
FrustumWidth = @Display.ScreenWidth
FrustumHeight = @Display.ScreenHeight
FrustumFar = 1.0
FrustumNear = 0.0
Position = (0.0, 0.0, -1.0)
[Object]
Graphic = OnePixel
Position = (-400.0, -300.0, 0.0)
Scale = (800, 600, 1.0)
ShaderList = Shader
[OnePixel]
Texture = pixel
[Resource]
Texture = ../data/texture
[Shader]
ParamList = radius # highlight # textures # texturesCount
UseCustomParam = true
radius = 50.0
highlight = (0.0, 0.0, 0.0)
textures = Hex_Water_PointyTop.png # Hex_Earth_PointyTop.png
texturesCount = 2
Code = "
**GLSL Shader Code**
#define HEX_SIZE radius
#define HEX_WIDTH_POINTYTOP (sqrt(3.0) * HEX_SIZE)
#define HEX_WIDTH_FLATTOP (2 * HEX_SIZE)
#define HEX_HEIGHT_POINTYTOP (2 * HEX_SIZE)
#define HEX_HEIGHT_FLATTOP (sqrt(3.0) * HEX_SIZE)
// Function to convert cubial coords to axial coords.
vec2 cubeToAxial(vec3 cube)
{
return vec2(cube.x, cube.y);
}
// Function to convert axial coords to cubial coords.
vec3 axialToCube(vec2 axial)
{
float x = axial.x;
float y = axial.y;
float z = -x - y;
return vec3(x, y, z);
}
// Function to round float cubial coords to int cubial coords.
vec3 cubeRound(vec3 cube)
{
int rx = int(round(cube.x));
int ry = int(round(cube.y));
int rz = int(round(cube.z));
float xDiff = abs(rx - cube.x);
float yDiff = abs(ry - cube.y);
float zDiff = abs(rz - cube.z);
if ((xDiff > yDiff) && (xDiff > zDiff))
rx = -ry - rz;
else if (yDiff > zDiff)
ry = -rx - rz;
else
rz = -rx - ry;
return vec3(rx, ry, rz);
}
// Function to round float axial coords to int axial coords.
vec2 axialRound(vec2 axial)
{
return cubeToAxial(cubeRound(axialToCube(axial)));
}
// Function to return axial hex-grid coords, given a screen position (horizontal, pointy-top hex layout).
vec2 pixelToHex_PointyTop(vec2 point)
{
return vec2(((sqrt(3.0)/3.0 * point.x) + (-1.0/3.0 * point.y)) / HEX_SIZE, (2.0/3.0 * point.y) / HEX_SIZE);
}
// Function to return axial hex-grid coords, given a screen position (vertical, flat-top hex layout).
vec2 pixelToHex_FlatTop(vec2 point)
{
return vec2((2.0/3.0 * point.x) / HEX_SIZE, ((-1.0/3.0 * point.x) + (sqrt(3.0)/3.0 * point.y)) / HEX_SIZE);
}
// Function to return a screen position, given axial hex-grid coords (horizontal, pointy-top hex layout).
vec2 hexToPixel_PointyTop(vec2 hex)
{
return vec2(((sqrt(3.0) * hex.x) + (sqrt(3.0)/2.0 * hex.y)) * HEX_SIZE, (3.0/2.0 * hex.y) * HEX_SIZE);
}
// Function to return a screen position, given axial hex-grid coords (vertical, flat-top hex layout).
vec2 hexToPixel_FlatTop(vec2 hex)
{
return vec2((3.0/2.0 * hex.x) * HEX_SIZE, ((sqrt(3.0)/2.0 * hex.x) + (sqrt(3.0) * hex.y)) * HEX_SIZE);
}
// Main shader.
void main()
{
vec2 point = vec2(gl_FragCoord.x, gl_FragCoord.y);
vec2 hex = axialRound(pixelToHex_PointyTop(point));
float width = HEX_WIDTH_POINTYTOP;
float height = HEX_HEIGHT_POINTYTOP;
vec2 center = hexToPixel_PointyTop(hex);
vec2 origin = vec2(center.x - (width/2.0), center.y - (height/2.0));
vec2 textureCoord = vec2(((point.x - origin.x) / width), 1.0 - ((point.y - origin.y) / height));
int index = int(mod(hex.x * hex.y, texturesCount));
vec4 color = texture2D(textures[index], textureCoord);
if (highlight.xy == hex)
color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), 0.5);
gl_FragColor = color;
}
"
====CPP File (tut_hex.cpp)====
/**
* @file tut_hex.cpp
* @date 2019/09/06
* @author LudiG
*
* Axial/Cubial coord-based hexagon shader and mouse tracker.
*/
/* Orx - Portable Game Engine
*
* Copyright (c) 2008-2010 Orx-Project
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*/
#include "orx.h"
orxFLOAT _screenHeight; // The screen height.
orxFLOAT _screenWidth; // The screen width.
orxFLOAT _tileRadius; // The tile radius in screen coordinates (pixels).
orxVECTOR _screenCoord; // The current screen coordinates of the mouse.
orxVECTOR _tilePos; // The current tile position.
// HEX
// Function to convert cubial coords to axial coords.
orxVECTOR cubeToAxial(const orxVECTOR& cube)
{
orxVECTOR result;
orxVector_Set(&result, cube.fX, cube.fY, 0.0);
return result;
}
// Function to convert axial coords to cubial coords.
orxVECTOR axialToCube(const orxVECTOR& axial)
{
orxFLOAT x = axial.fX;
orxFLOAT y = axial.fY;
orxFLOAT z = -x - y;
orxVECTOR result;
orxVector_Set(&result, x, y, z);
return result;
}
// Function to round float cubial coords to int cubial coords.
orxVECTOR cubeRound(const orxVECTOR& cube)
{
orxFLOAT rx = orxMath_Round(cube.fX);
orxFLOAT ry = orxMath_Round(cube.fY);
orxFLOAT rz = orxMath_Round(cube.fZ);
orxFLOAT xDiff = orxMath_Abs(rx - cube.fX);
orxFLOAT yDiff = orxMath_Abs(ry - cube.fY);
orxFLOAT zDiff = orxMath_Abs(rz - cube.fZ);
if ((xDiff > yDiff) && (xDiff > zDiff))
rx = -ry - rz;
else if (yDiff > zDiff)
ry = -rx - rz;
else
rz = -rx - ry;
orxVECTOR result;
orxVector_Set(&result, rx, ry, rz);
return result;
}
// Function to round float axial coords to int axial coords.
orxVECTOR axialRound(const orxVECTOR& axial)
{
return cubeToAxial(cubeRound(axialToCube(axial)));
}
// Function to return axial hex-grid coords, given a screen position (horizontal, pointy-top hex layout).
orxVECTOR pixelToHex_PointyTop(const orxVECTOR& point)
{
orxFLOAT size = _tileRadius;
orxVECTOR result;
orxVector_Set(&result, ((orxMath_Pow(3.0, 0.5)/3.0 * point.fX) + (-1.0/3.0 * point.fY)) / size, (2.0/3.0 * point.fY) / size, 0.0);
return result;
}
// Function to return axial hex-grid coords, given a screen position (vertical, flat-top hex layout).
orxVECTOR pixelToHex_FlatTop(const orxVECTOR& point)
{
orxFLOAT size = _tileRadius;
orxVECTOR result;
orxVector_Set(&result, (2.0/3.0 * point.fX) / size, ((-1.0/3.0 * point.fX) + (orxMath_Pow(3.0, 0.5)/3.0 * point.fY)) / size, 0.0);
return result;
}
// Function to return a screen position, given axial hex-grid coords (horizontal, pointy-top hex layout).
orxVECTOR hexToPixel_PointyTop(const orxVECTOR& hex)
{
orxFLOAT size = _tileRadius;
orxVECTOR result;
orxVector_Set(&result, ((orxMath_Pow(3.0, 0.5) * hex.fX) + (orxMath_Pow(3.0, 0.5)/2.0 * hex.fY)) * size, (3.0/2.0 * hex.fY) * size, 0.0);
return result;
}
// Function to return a screen position, given axial hex-grid coords (vertical, flat-top hex layout).
orxVECTOR hexToPixel_FlatTop(const orxVECTOR& hex)
{
orxFLOAT size = _tileRadius;
orxVECTOR result;
orxVector_Set(&result, (3.0/2.0 * hex.fX) * size, ((orxMath_Pow(3.0, 0.5)/2.0 * hex.fX) + (orxMath_Pow(3.0, 0.5) * hex.fY)) * size, 0.0);
return result;
}
// ORX
static orxSTATUS orxFASTCALL handleShaderEvent(const orxEVENT* currentEvent)
{
switch(currentEvent->eID)
{
case orxSHADER_EVENT_SET_PARAM:
{
// Get the event payload.
orxSHADER_EVENT_PAYLOAD *pstPayload = (orxSHADER_EVENT_PAYLOAD*)currentEvent->pstPayload;
// look for the parameter of interest.
if (!orxString_Compare(pstPayload->zParamName, "highlight"))
orxVector_Copy(&pstPayload->vValue, &_tilePos);
}
}
return orxSTATUS_SUCCESS;
}
orxSTATUS orxFASTCALL Init()
{
orxDisplay_GetScreenSize(&_screenWidth, &_screenHeight);
orxConfig_PushSection("Shader");
_tileRadius = orxConfig_GetFloat("radius");
orxConfig_PopSection();
orxViewport_CreateFromConfig("Viewport");
orxObject_CreateFromConfig("Object");
orxEvent_AddHandler(orxEVENT_TYPE_SHADER, handleShaderEvent);
return orxSTATUS_SUCCESS;
}
orxSTATUS orxFASTCALL Run()
{
orxSTATUS result = orxSTATUS_SUCCESS;
// INPUT: Quit
if(orxInput_IsActive("Quit"))
result = orxSTATUS_FAILURE;
// INPUT: Mouse
orxVECTOR mouse;
orxMouse_GetPosition(&mouse);
_screenCoord.fX = mouse.fX;
_screenCoord.fY = _screenHeight - mouse.fY;
_screenCoord.fZ = 0.0;
// Calculate the tile position from the mouse position.
orxVECTOR tilePosOld;
orxVector_Copy(&tilePosOld, &_tilePos);
_tilePos = axialRound(pixelToHex_PointyTop(_screenCoord));
// Print tile and screen position if mouse moves.
if ((tilePosOld.fX != _tilePos.fX) || (tilePosOld.fY != _tilePos.fY))
orxLOG("TILE: %f, %f FOR SHADER: %f, %f.", _tilePos.fX, _tilePos.fY, _screenCoord.fX, _screenCoord.fY);
return result;
}
void orxFASTCALL Exit()
{
// No specific garbage-collection requirements.
}
orxSTATUS orxFASTCALL Bootstrap()
{
// Add the config directory as a resource path.
orxResource_AddStorage(orxCONFIG_KZ_RESOURCE_GROUP, "../data/config", orxFALSE);
return orxSTATUS_SUCCESS;
}
int main(int argc, char** argv)
{
orxConfig_SetBootstrap(Bootstrap);
orx_Execute(argc, argv, Init, Run, Exit);
return EXIT_SUCCESS;
}