commit
60e1513c20
10 changed files with 5592 additions and 0 deletions
@ -0,0 +1,3 @@
|
||||
[submodule "ext/imgui"] |
||||
path = ext/imgui |
||||
url = https://github.com/ocornut/imgui.git |
||||
@ -0,0 +1,13 @@
|
||||
|
||||
CXXFLAGS = -std=c++11 -g -Wall
|
||||
INCLUDES =
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
|
||||
all: |
||||
g++ $(CXXFLAGS) $(INCLUDES) -o hexgame hexgame.cpp $(LDFLAGS)
|
||||
mkdir -p ../bin
|
||||
mv hexgame ../bin
|
||||
|
||||
clean: |
||||
rm ../bin/hexgame
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
@echo off |
||||
|
||||
cls |
||||
|
||||
IF "%VisualStudioVersion%" == "" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64 |
||||
|
||||
SETLOCAL |
||||
SET CFLAGS=%CFLAGS% -MT -nologo -Gm- -Oi -WX -W4 -wd4100 -wd4189 -Zi /EHsc |
||||
SET INCLUDES=%INCLUDES% /I ..\SDL2\include |
||||
SET INCLUDES=%INCLUDES% /I ..\glew\include |
||||
SET INCLUDES=%INCLUDES% /I ..\freetype\include |
||||
SET _LIBPATH=%_LIBPATH% /LIBPATH:..\SDL2\lib |
||||
SET _LIBPATH=%_LIBPATH% /LIBPATH:..\glew\lib |
||||
REM SET _LIBPATH=%_LIBPATH% /LIBPATH:..\freetype\lib |
||||
REM SET LIBS=%LIBS% SDL2.lib glew32sd.lib opengl32.lib User32.lib libfreetype.dll.a |
||||
SET LIBS=%LIBS% SDL2.lib glew32sd.lib opengl32.lib User32.lib |
||||
|
||||
IF NOT EXIST ..\build mkdir ..\build |
||||
pushd ..\build |
||||
cl %CFLAGS% %INCLUDES% ..\src\hexgame.cpp %LIBS% /link %_LIBPATH% |
||||
popd |
||||
|
||||
ENDLOCAL |
||||
|
||||
@ -0,0 +1,441 @@
|
||||
|
||||
#ifndef GOOEY_H |
||||
#define GOOEY_H |
||||
|
||||
#include <string> |
||||
#include <sstream> |
||||
#include <string.h> // for strlen |
||||
#include <assert.h> |
||||
#include <math.h> // for round |
||||
|
||||
#include <GL/glew.h> |
||||
#include <SDL2/SDL_opengl.h> |
||||
#define STB_TRUETYPE_IMPLEMENTATION |
||||
#include "stb_truetype.h" |
||||
|
||||
#include "hexgame.h" |
||||
|
||||
using std::string; |
||||
using std::stringstream; |
||||
using std::lround; |
||||
|
||||
struct bitmap_info |
||||
{ |
||||
int32 w; |
||||
int32 h; |
||||
int32 buffer_size; |
||||
uint8 *bitmap_data; |
||||
}; |
||||
|
||||
struct button |
||||
{ |
||||
bool selected; |
||||
uint32 color; |
||||
uint32 current_color; |
||||
v4i coords; // x0,y0 top left, x1,y1 bottom right
|
||||
char *text; |
||||
HexDrawMode mode; |
||||
}; |
||||
|
||||
// TODO: heap globals
|
||||
uint8 *font_filedata; |
||||
bitmap_info test_bitmap; |
||||
stbtt_aligned_quad *line_buffer; |
||||
button *button_array; |
||||
uint32 button_count = 0; |
||||
uint32 MAX_BUTTONS = 128; |
||||
|
||||
// TODO: stack globals
|
||||
stbtt_bakedchar font_chardata[96]; // ASCII 32..126 is 95 glyphs
|
||||
GLuint texture_atlas; |
||||
float font_height = 24; |
||||
v2i font_atlas_dims = v2i(512, 512); |
||||
v4i gooey_extents = {}; |
||||
|
||||
// forward declarations
|
||||
inline void setProjectionMatrix(int w, int h); |
||||
void debugLog(string error); |
||||
|
||||
// TODO: move file i/o to asset system
|
||||
void * |
||||
readEntireFile(char *filename) |
||||
{ |
||||
void *buffer = 0; |
||||
|
||||
#if defined _WIN32 |
||||
stringstream ss; |
||||
HANDLE filehandle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, |
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
||||
if (filehandle != INVALID_HANDLE_VALUE) |
||||
{ |
||||
LARGE_INTEGER filesize; |
||||
if (GetFileSizeEx(filehandle, &filesize)) |
||||
{ |
||||
buffer = VirtualAlloc(0, filesize.QuadPart, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); |
||||
if (buffer) |
||||
{ |
||||
assert(filesize.QuadPart <= 0xFFFFFFFF); // don't read files larger than 4gb
|
||||
uint32 filesize32 = (uint32) filesize.QuadPart; |
||||
|
||||
if (ReadFile(filehandle, buffer, filesize32, NULL, NULL)) |
||||
{ |
||||
// success
|
||||
} |
||||
else |
||||
{ |
||||
DWORD win_err = GetLastError(); |
||||
ss << "error reading file " << filename << " . error code: " << win_err |
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
CloseHandle(filehandle); |
||||
VirtualFree(buffer, 0, MEM_RELEASE); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
ss << "error allocating memory for " << filename |
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
CloseHandle(filehandle); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
ss << "failed to get filesize of " << filename |
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
CloseHandle(filehandle); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
ss << "failed to open " << filename
|
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
} |
||||
|
||||
#else // TODO: other platforms IO
|
||||
return NULL; |
||||
#endif |
||||
|
||||
return buffer; |
||||
} |
||||
|
||||
|
||||
v2f |
||||
setTextLine(stbtt_aligned_quad *buffer, char *lineText) |
||||
{ |
||||
// NOTE: not accounting for font metrics at all
|
||||
v2f lineDims = {}; |
||||
|
||||
if (buffer && lineText) |
||||
{ |
||||
float x = 0, y = 0; |
||||
int i = 0; |
||||
while (*lineText) |
||||
{ |
||||
stbtt_GetBakedQuad(font_chardata, test_bitmap.w, test_bitmap.h, |
||||
*lineText - 32, &x, &y, &buffer[i], 1); |
||||
|
||||
stbtt_aligned_quad q = buffer[i]; |
||||
|
||||
lineDims.x = q.x1 + font_chardata[i].xadvance; |
||||
if (lineDims.y > q.y0) |
||||
{ |
||||
lineDims.y = -1 * q.y0; |
||||
} |
||||
|
||||
lineText++; |
||||
i++; |
||||
} |
||||
} |
||||
|
||||
return lineDims; |
||||
} |
||||
|
||||
// Xoff, Yoff are offsets in screen coords from bottom left
|
||||
void |
||||
renderTextLine(stbtt_aligned_quad *buffer, int32 numQuads, float Xoff, float Yoff, |
||||
v2i screen_dims) |
||||
{ |
||||
|
||||
int w = screen_dims.x, h = screen_dims.y; |
||||
w = (w == 0) ? 1 : w; // don't divide by zero
|
||||
h = (h == 0) ? 1 : h; |
||||
|
||||
// stb_truetype uses top-down coordinates, so we reverse our projection matrix
|
||||
// when rendering the text
|
||||
glMatrixMode(GL_PROJECTION); |
||||
real32 a = 2.0f / (real32) w; |
||||
real32 b = 2.0f / (real32) h; |
||||
real32 matrix[16] = { |
||||
a, 0, 0, 0, |
||||
0, -b, 0, 0, |
||||
0, 0, 1, 0, |
||||
-1, -1, 0, 1, |
||||
}; |
||||
glLoadMatrixf(matrix); |
||||
|
||||
glEnable(GL_TEXTURE_2D); |
||||
glBindTexture(GL_TEXTURE_2D, texture_atlas); |
||||
glColor4f(0,0,0,1); |
||||
glBegin(GL_QUADS); |
||||
stbtt_aligned_quad q; |
||||
for (int i = 0; i < numQuads; i++) |
||||
{ |
||||
q = buffer[i]; |
||||
glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0 + Xoff,q.y0 - Yoff); |
||||
glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1 + Xoff,q.y0 - Yoff); |
||||
glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1 + Xoff,q.y1 - Yoff); |
||||
glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0 + Xoff,q.y1 - Yoff); |
||||
} |
||||
glEnd(); |
||||
glDisable(GL_TEXTURE_2D); |
||||
|
||||
// reset normal projection matrix
|
||||
setProjectionMatrix(screen_dims.x, screen_dims.y); |
||||
} |
||||
|
||||
void |
||||
addButton(char *text, int32 x, int32 y, HexDrawMode mode) |
||||
{ |
||||
if (button_count >= MAX_BUTTONS) |
||||
{ |
||||
// allocate some more memory?
|
||||
return; |
||||
} |
||||
|
||||
v2f lineDims = setTextLine(line_buffer, text); |
||||
gooey_extents.x1 = (gooey_extents.x1 >= lineDims.x) ? gooey_extents.x1 : lround(lineDims.x); |
||||
gooey_extents.y0 = (gooey_extents.y0 <= y) ? gooey_extents.y0 : y; |
||||
|
||||
v4i coords = { x, y, lround(lineDims.x), y + lround(lineDims.y) }; |
||||
button b; |
||||
b.selected = false; |
||||
b.color = b.current_color = 0xCCCCCCFF; |
||||
b.coords = coords; |
||||
b.text = text; |
||||
b.mode = mode; |
||||
|
||||
button_array[button_count] = b; |
||||
button_count++; |
||||
} |
||||
|
||||
void |
||||
createGooeyControls(v2i screenDims) |
||||
{ |
||||
gooey_extents.x0 = 0; |
||||
gooey_extents.y1 = screenDims.y; |
||||
gooey_extents.y0 = screenDims.y - (int32) font_height; |
||||
|
||||
int32 x = 10; |
||||
int32 y = screenDims.y - (int32) font_height - 5; |
||||
|
||||
addButton("Hex Fill", x, y, FILL); |
||||
addButton("Draw Line", x, lround(y - font_height), LINE); |
||||
addButton("Cone Fill", x, lround(y - 2*font_height), CONE_FILL); |
||||
addButton("Pathfinding", x, lround(y - 3*font_height), PATHFINDING); |
||||
} |
||||
|
||||
bool |
||||
InitGooey(v2i screenDims) |
||||
{ |
||||
bool retVal = false; |
||||
stringstream ss; |
||||
// TODO: hard coded fontname loaded at runtime
|
||||
char *fontname = "LiberationMono-Regular.ttf"; |
||||
font_filedata = (uint8 *) readEntireFile(fontname); |
||||
if (font_filedata) |
||||
{ |
||||
test_bitmap.w = font_atlas_dims.x; |
||||
test_bitmap.h = font_atlas_dims.y; |
||||
test_bitmap.bitmap_data = (uint8 *) VirtualAlloc(0, test_bitmap.w * test_bitmap.h, |
||||
MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); |
||||
if (test_bitmap.bitmap_data) |
||||
{ |
||||
int bake_result = stbtt_BakeFontBitmap(font_filedata, 0, font_height, |
||||
test_bitmap.bitmap_data, test_bitmap.w, test_bitmap.h, 32, 96, |
||||
font_chardata |
||||
); |
||||
|
||||
if (bake_result !=0) |
||||
{ |
||||
glGenTextures(1, &texture_atlas); |
||||
glBindTexture(GL_TEXTURE_2D, texture_atlas); |
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, test_bitmap.w, test_bitmap.h, 0, |
||||
GL_ALPHA, GL_UNSIGNED_BYTE, test_bitmap.bitmap_data); |
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
||||
|
||||
// TODO: get screen width / font metrics width for memory allocation
|
||||
line_buffer = (stbtt_aligned_quad *) VirtualAlloc(0, sizeof(stbtt_aligned_quad)*128, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); |
||||
|
||||
// TODO: probably a better way to dynamically allocate memory for an
|
||||
// amount of 'button' objects we don't know the count of
|
||||
button_array = (button *) VirtualAlloc(0, |
||||
sizeof(button) * MAX_BUTTONS, |
||||
MEM_COMMIT|MEM_RESERVE, |
||||
PAGE_READWRITE |
||||
); |
||||
if (line_buffer) |
||||
{ |
||||
retVal = true; |
||||
createGooeyControls(screenDims); |
||||
} |
||||
else |
||||
{ |
||||
ss << "error allocating memory for " << fontname |
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
retVal = false; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
retVal = false; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
ss << "error allocating memory for " << fontname |
||||
<< ". file:" << __FILE__ << ", line: " << __LINE__ - 3; |
||||
debugLog(ss.str()); |
||||
ss.str(""); |
||||
retVal = false; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// file read error
|
||||
return retVal; |
||||
} |
||||
|
||||
return retVal; |
||||
} |
||||
|
||||
// TODO: global
|
||||
struct |
||||
{ |
||||
real32 R = 90.0f/255.0f; |
||||
real32 G = 90.0f/255.0f; |
||||
real32 B = 90.0f/255.0f; |
||||
} G_COLORS; |
||||
|
||||
void |
||||
renderGooey(v2i screenDims) |
||||
{ |
||||
real32 x0 = (real32) gooey_extents.x0; |
||||
real32 y0 = (real32) gooey_extents.y0; |
||||
real32 x1 = (real32) gooey_extents.x1; |
||||
real32 y1 = (real32) gooey_extents.y1; |
||||
|
||||
glColor4f(G_COLORS.R, G_COLORS.G, G_COLORS.B, 1); |
||||
glBegin(GL_QUADS); |
||||
glVertex2f(x0, y0); |
||||
glVertex2f(x1, y0); |
||||
glVertex2f(x1, y1); |
||||
glVertex2f(x0, y1); |
||||
glEnd(); |
||||
|
||||
for (uint32 i = 0; i < button_count; i++) |
||||
{ |
||||
button b = button_array[i]; |
||||
|
||||
// render colored background of button
|
||||
glColor4ub( |
||||
GLubyte((b.current_color & 0xFF000000) >> 24), |
||||
GLubyte((b.current_color & 0x00FF0000) >> 16), |
||||
GLubyte((b.current_color & 0x0000FF00) >> 8), |
||||
GLubyte((b.current_color & 0x000000FF)) |
||||
); |
||||
|
||||
glBegin(GL_QUADS); |
||||
glVertex2i(b.coords.x0, b.coords.y0); |
||||
glVertex2i(b.coords.x1, b.coords.y0); |
||||
glVertex2i(b.coords.x1, b.coords.y1); |
||||
glVertex2i(b.coords.x0, b.coords.y1); |
||||
glEnd(); |
||||
|
||||
setTextLine(line_buffer, b.text); |
||||
renderTextLine(line_buffer, (int32) strlen(b.text), (float) b.coords.x0,
|
||||
(float) b.coords.y0, screenDims); |
||||
} |
||||
} |
||||
|
||||
bool |
||||
gooeyHitTest(v2i screenCoords, v4i extents = gooey_extents) |
||||
{ |
||||
if (screenCoords.x >= extents.x0 && |
||||
screenCoords.y >= extents.y0 && |
||||
screenCoords.x <= extents.x1 && |
||||
screenCoords.y <= extents.y1) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
HexDrawMode |
||||
gooeyPressButton(v2i screenCoords) |
||||
{ |
||||
// operate as a modal dialog
|
||||
button *b; |
||||
HexDrawMode retVal = NONE; |
||||
|
||||
for (uint32 i = 0; i < button_count; i++) |
||||
{ |
||||
b = &button_array[i]; |
||||
|
||||
if (gooeyHitTest(screenCoords, b->coords)) |
||||
{ |
||||
b->selected = true; |
||||
b->current_color = 0xFFFFFFFF; |
||||
retVal = b->mode; |
||||
|
||||
for (uint32 j = 0; j < button_count; j++) |
||||
{ |
||||
if (i != j) |
||||
{ |
||||
b = &button_array[j]; |
||||
b->selected = false; |
||||
b->current_color = b->color; |
||||
} |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
return retVal; |
||||
} |
||||
|
||||
void |
||||
freeGooey() |
||||
{ |
||||
if (font_filedata) |
||||
{ |
||||
VirtualFree(font_filedata, 0, MEM_RELEASE); |
||||
} |
||||
|
||||
if (test_bitmap.bitmap_data) |
||||
{ |
||||
VirtualFree(test_bitmap.bitmap_data, 0, MEM_RELEASE); |
||||
} |
||||
|
||||
if (line_buffer) |
||||
{ |
||||
VirtualFree(line_buffer, 0, MEM_RELEASE); |
||||
} |
||||
|
||||
if (button_array) |
||||
{ |
||||
VirtualFree(button_array, 0, MEM_RELEASE); |
||||
} |
||||
} |
||||
|
||||
#endif // GOOEY_H
|
||||
@ -0,0 +1,677 @@
|
||||
|
||||
/*
|
||||
* TODO: |
||||
* |
||||
* - Platform Layer: |
||||
* - make a global debug logging function in hexgame.h |
||||
* - centralize memory allocation/deallocation |
||||
* - custom dynamic arrays in C: |
||||
* https://stackoverflow.com/questions/3536153/c-dynamically-growing-array
|
||||
* - File IO |
||||
* |
||||
* - Debug UI: |
||||
* - font metrics |
||||
* - render lines of glyphs to texture |
||||
* - simple buttons to select game states |
||||
* |
||||
* - game states: |
||||
* - hex fill, line draw, 'cone' fill, pathfinding, line of sight |
||||
*
|
||||
* - utility functions: |
||||
* - SafeRatio(): don't divide by zero |
||||
* - SafeTruncate() for various types: check for input size before losing high bits |
||||
*/ |
||||
|
||||
#include <string> |
||||
#include <iostream> |
||||
#include <vector> |
||||
#include <sstream> |
||||
#include <cmath> |
||||
#include <random> |
||||
|
||||
#if defined(_WIN32) |
||||
#include <windows.h> |
||||
#include <Winuser.h> |
||||
#endif |
||||
|
||||
#include <SDL2/SDL.h> |
||||
#include <GL/glew.h> |
||||
#include <SDL2/SDL_opengl.h> |
||||
|
||||
#include "hexgame.h" |
||||
#include "hexlib.h" |
||||
//#include "gooey.h"
|
||||
|
||||
using std::string; |
||||
using std::stringstream; |
||||
using std::vector; |
||||
|
||||
// TODO: maybe switch to c-style strings for errors? working with stringstream
|
||||
// is annoying and too verbose
|
||||
void |
||||
debugLog(string error) |
||||
{ |
||||
#if defined(_WIN32) && !defined(__MINGW32__) |
||||
error.push_back('\n'); |
||||
OutputDebugString((LPCSTR) error.c_str()); |
||||
#else |
||||
std::cout << error << '\n'; |
||||
#endif |
||||
} |
||||
|
||||
// TODO: global variables
|
||||
v2i viewportDims(1280, 720); |
||||
vector<hex_info> *hexInfoArray; |
||||
Layout global_layout(layout_flat, Point(25, 25), Point(viewportDims.x/2,viewportDims.y/2)); |
||||
uint32 selected_line_color = 0xFFFFFFFF; |
||||
uint32 line_color = 0xFFFFFFFF; |
||||
uint32 selected_fill_color = 0xF46000FF; |
||||
uint32 fill_color = 0x5C5C5CFF; |
||||
|
||||
struct { |
||||
real32 R = 75.0f / 255.0f; |
||||
real32 G = 135.0f / 255.0f; |
||||
real32 B = 135.0f / 255.0f; |
||||
real32 A = 1; |
||||
} gl_clear_color; |
||||
|
||||
void |
||||
renderHexagon(vector<Point> &vertices, uint32 fillColor, uint32 lineColor) |
||||
{ |
||||
glColor4ub( |
||||
uint8((fillColor & 0xFF000000) >> 24), |
||||
uint8((fillColor & 0x00FF0000) >> 16), |
||||
uint8((fillColor & 0x0000FF00) >> 8), |
||||
uint8((fillColor & 0x000000FF)) |
||||
); |
||||
|
||||
glBegin(GL_POLYGON); |
||||
glVertex3d(vertices[0].x, vertices[0].y, 0); |
||||
glVertex3d(vertices[1].x, vertices[1].y, 0); |
||||
glVertex3d(vertices[2].x, vertices[2].y, 0); |
||||
glVertex3d(vertices[3].x, vertices[3].y, 0); |
||||
glVertex3d(vertices[4].x, vertices[4].y, 0); |
||||
glVertex3d(vertices[5].x, vertices[5].y, 0); |
||||
glEnd(); |
||||
|
||||
glColor4ub( |
||||
uint8((lineColor & 0xFF000000) >> 24), |
||||
uint8((lineColor & 0x00FF0000) >> 16), |
||||
uint8((lineColor & 0x0000FF00) >> 8), |
||||
uint8((lineColor & 0x000000FF)) |
||||
); |
||||
glLineWidth(1.0f); |
||||
|
||||
glBegin(GL_LINE_LOOP); |
||||
glVertex3d(vertices[0].x, vertices[0].y, 0); |
||||
glVertex3d(vertices[1].x, vertices[1].y, 0); |
||||
glVertex3d(vertices[2].x, vertices[2].y, 0); |
||||
glVertex3d(vertices[3].x, vertices[3].y, 0); |
||||
glVertex3d(vertices[4].x, vertices[4].y, 0); |
||||
glVertex3d(vertices[5].x, vertices[5].y, 0); |
||||
glEnd(); |
||||
} |
||||
|
||||
void |
||||
setProjectionMatrix(int w, int h) |
||||
{ |
||||
w = (w == 0) ? 1 : w; // don't divide by zero
|
||||
h = (h == 0) ? 1 : h; |
||||
glMatrixMode(GL_PROJECTION); |
||||
real32 a = 2.0f / (real32) w; |
||||
real32 b = 2.0f / (real32) h; |
||||
real32 matrix[16] = { |
||||
a, 0, 0, 0, |
||||
0, b, 0, 0, |
||||
0, 0, 1, 0, |
||||
-1, -1, 0, 1, |
||||
}; |
||||
glLoadMatrixf(matrix); |
||||
// TODO: Make helper function for glGetError(), also map codes to
|
||||
// error strings
|
||||
GLenum e = glGetError(); |
||||
if (e != GL_NO_ERROR) |
||||
{ |
||||
stringstream ss; |
||||
ss << "setProjectionMatrix(), GLerror: " << e; |
||||
debugLog(ss.str()); |
||||
} |
||||
} |
||||
|
||||
uint32 |
||||
createRandomColor(uint8 alpha = 205) |
||||
{ |
||||
// http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
|
||||
// NOTE: gcc on mingw-w54 produces the same results every time without a
|
||||
// non-deterministic source
|
||||
std::random_device rd; |
||||
std::mt19937 gen(rd()); |
||||
std::uniform_int_distribution<int> uniI(0, 255); |
||||
|
||||
uint32 color = ( |
||||
(uniI(gen) << 24) // red
|
||||
| (uniI(gen) << 16) // green
|
||||
| (uniI(gen) << 8) // blue
|
||||
| alpha // alpha
|
||||
); |
||||
return color; |
||||
} |
||||
|
||||
bool |
||||
InitGraphics(SDL_Handles &handles) |
||||
{ |
||||
// TODO: pretty much all of these calls need error handling :(
|
||||
SDL_Init(SDL_INIT_VIDEO); |
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); |
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); |
||||
SDL_Window *window = SDL_CreateWindow("hexgame", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, |
||||
viewportDims.x, viewportDims.y, SDL_WINDOW_OPENGL); |
||||
if (window == NULL) { |
||||
stringstream e; |
||||
e << "Could not create window: " << SDL_GetError(); |
||||
debugLog(e.str()); |
||||
return false; |
||||
} |
||||
handles.window = window; |
||||
handles.glContext = SDL_GL_CreateContext(window); |
||||
SDL_GL_SetSwapInterval(1); // vsync
|
||||
// enable blending
|
||||
glEnable (GL_BLEND); |
||||
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
||||
// init opengl projection/model matrices
|
||||
glMatrixMode(GL_MODELVIEW); |
||||
glLoadIdentity(); |
||||
setProjectionMatrix(viewportDims.x, viewportDims.y); |
||||
|
||||
// TODO: Check for errors and return false
|
||||
return true; |
||||
} |
||||
|
||||
void |
||||
createScene() |
||||
{ |
||||
// create a hexagonal grid of hexagons
|
||||
int map_radius = 7; |
||||
for (int q = -map_radius; q <= map_radius; q++) |
||||
{ |
||||
int r1 = std::max(-map_radius, -q - map_radius); |
||||
int r2 = std::min(map_radius, -q + map_radius); |
||||
|
||||
for (int r = r1; r <= r2; r++) |
||||
{ |
||||
hex_info hxi; |
||||
hxi.hexID = (int32) hexInfoArray->size(); |
||||
hxi.hex.q = q; hxi.hex.r = r; hxi.hex.s = -q-r; |
||||
Point p = hex_to_pixel(global_layout, hxi.hex); |
||||
hxi.XPos = p.x; |
||||
hxi.YPos = p.y; |
||||
hxi.current_color = fill_color; |
||||
hxi.stored_color = hxi.current_color; |
||||
hxi.vertices = polygon_corners(global_layout, hxi.hex); |
||||
hxi.vertices.shrink_to_fit(); |
||||
hexInfoArray->push_back(hxi); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// TODO: global variables
|
||||
bool isSelecting = false; |
||||
hex_info *startHex; |
||||
hex_info *currentHex; |
||||
vector<hex_info> selectedHexes; |
||||
HexDrawMode globalDrawMode = NONE; |
||||
|
||||
|
||||
// TODO: many globals used here
|
||||
void |
||||
debugRender() |
||||
{ |
||||
if (globalDrawMode == CONE_FILL) |
||||
{ |
||||
if (startHex && currentHex) |
||||
{ |
||||
Point p1 = hex_to_pixel(global_layout, startHex->hex); |
||||
Point p2 = hex_to_pixel(global_layout, currentHex->hex); |
||||
|
||||
// NOTE: some cone drawing shenanigans
|
||||
real32 angle = (real32) atan2(p2.y - p1.y, p2.x - p1.x); |
||||
real32 len = (real32) hypot(p2.y - p1.y, p2.x - p1.x); |
||||
real32 coneAngle = 24.5f * (real32) M_PI / 180; |
||||
|
||||
|
||||
// TODO: add matrix math library for rotation transform?
|
||||
// |x| | cos(a + b) -sin(a + b) 0 |
|
||||
// |y| * | sin(a + b) cos(a + b) 0 |
|
||||
// |0| | 0 0 1 |
|
||||
//
|
||||
real32 x1 = len * cosf(angle); |
||||
real32 y1 = len * sinf(angle); |
||||
// top of cone
|
||||
real32 topX = x1 * cosf(coneAngle) - y1 * sinf(coneAngle) + (real32) p1.x; |
||||
real32 topY = x1 * sinf(coneAngle) + y1 * cosf(coneAngle) + (real32) p1.y; |
||||
// bottom of cone
|
||||
real32 botX = x1 * cosf(coneAngle) + y1 * sinf(coneAngle) + (real32) p1.x; |
||||
real32 botY = x1 * sinf(-1 * coneAngle) + y1 * cosf(coneAngle) + (real32) p1.y; |
||||
|
||||
glColor4f(1.0f, 1.0f, 0.5f, 1.0f); |
||||
glLineWidth(1.0f); |
||||
glBegin(GL_LINE_LOOP); |
||||
glVertex3d(p1.x, p1.y, 0); |
||||
glVertex3f(botX, botY, 0); |
||||
glVertex3d(p2.x, p2.y, 0); |
||||
glVertex3f(topX, topY, 0); |
||||
glEnd(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
render() |
||||
{ |
||||
glClearColor(gl_clear_color.R, gl_clear_color.G, gl_clear_color.B, |
||||
gl_clear_color.A); |
||||
glClear(GL_COLOR_BUFFER_BIT); |
||||
|
||||
// hexagons
|
||||
for (hex_info hxi : *hexInfoArray) |
||||
{ |
||||
renderHexagon(hxi.vertices, hxi.current_color, |
||||
(hxi.selected) ? selected_line_color : line_color); |
||||
} |
||||
|
||||
debugRender(); |
||||
//renderGooey(viewportDims);
|
||||
|
||||
// TODO: Make helper function for glGetError(), also map codes to
|
||||
// error strings
|
||||
GLenum e = glGetError(); |
||||
if (e != GL_NO_ERROR) |
||||
{ |
||||
stringstream ss; |
||||
ss << "render(), GLerror: " << e; |
||||
debugLog(ss.str()); |
||||
} |
||||
} |
||||
|
||||
v2i |
||||
mapMouseToViewport(int32 x, int32 y) |
||||
{ |
||||
v2i coords; |
||||
coords.x = x; |
||||
coords.y = viewportDims.y - y; |
||||
return coords; |
||||
} |
||||
|
||||
void |
||||
resetHexes() |
||||
{ |
||||
startHex = currentHex = 0; |
||||
|
||||
for (hex_info &hxi : *hexInfoArray) |
||||
{ |
||||
hxi.selected = false; |
||||
hxi.current_color = hxi.stored_color; |
||||
} |
||||
} |
||||
|
||||
hex_info * |
||||
getSingleHex(int32 x, int32 y) |
||||
{ |
||||
Point p(x, y); |
||||
Hex h = hex_round(pixel_to_hex(global_layout, p)); |
||||
|
||||
for (hex_info &hxi : *hexInfoArray) |
||||
{ |
||||
if (hex_equal(h, hxi.hex)) |
||||
{ |
||||
return &hxi; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void |
||||
setStartHex(hex_info *hex) |
||||
{ |
||||
hex->selected = true; |
||||
hex->current_color = selected_fill_color; |
||||
startHex = currentHex = hex; |
||||
isSelecting = true; |
||||
} |
||||
|
||||
void |
||||
startHexFill(int32 x, int32 y) |
||||
{ |
||||
resetHexes(); |
||||
hex_info *hex = getSingleHex(x, y); |
||||
if (hex) |
||||
{ |
||||
setStartHex(hex); |
||||
} |
||||
} |
||||
|
||||
void |
||||
updateHexFill(int32 x, int32 y) |
||||
{ |
||||
if (isSelecting) |
||||
{ |
||||
hex_info *hxi = getSingleHex(x, y); |
||||
if (hxi && (hxi != currentHex)) |
||||
{ |
||||
currentHex = hxi; |
||||
int l = hex_distance(startHex->hex, currentHex->hex); |
||||
|
||||
for (hex_info &h : *hexInfoArray) |
||||
{ |
||||
if (hex_distance(startHex->hex, h.hex) <= l) |
||||
{ |
||||
h.selected = true; |
||||
h.current_color = selected_fill_color; |
||||
} |
||||
else |
||||
{ |
||||
h.selected = false; |
||||
h.current_color = h.stored_color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
startHexLineDraw(int32 x, int32 y) |
||||
{ |
||||
resetHexes(); |
||||
hex_info *hex = getSingleHex(x, y); |
||||
if (hex) |
||||
{ |
||||
setStartHex(hex); |
||||
} |
||||
} |
||||
|
||||
void |
||||
updateHexLineDraw(int32 x, int32 y) |
||||
{ |
||||
if (isSelecting) |
||||
{ |
||||
hex_info *hxi = getSingleHex(x, y); |
||||
if (hxi && (hxi != currentHex)) |
||||
{ |
||||
currentHex = hxi; |
||||
vector<Hex> hexLine = hex_linedraw(startHex->hex, hxi->hex); |
||||
|
||||
for (hex_info &h1 : *hexInfoArray) |
||||
{ |
||||
for (uint i = 0; i < hexLine.size(); i++) |
||||
{
|
||||
Hex h2 = hexLine[i]; |
||||
if (hex_equal(h1.hex, h2)) |
||||
{ |
||||
h1.selected = true; |
||||
h1.current_color = selected_fill_color; |
||||
break; |
||||
} |
||||
else if (i == hexLine.size() - 1) |
||||
{ |
||||
h1.selected = false; |
||||
h1.current_color = h1.stored_color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
startHexConeFill(int32 x, int32 y) |
||||
{ |
||||
resetHexes(); |
||||
hex_info *hex = getSingleHex(x, y); |
||||
if (hex) |
||||
{ |
||||
setStartHex(hex); |
||||
} |
||||
} |
||||
|
||||
void |
||||
updateHexConeFill(int32 x, int32 y) |
||||
{ |
||||
if (isSelecting) |
||||
{ |
||||
hex_info *hxi = getSingleHex(x, y); |
||||
if (hxi && (hxi != currentHex)) |
||||
{ |
||||
currentHex = hxi; |
||||
|
||||
for (hex_info &h : *hexInfoArray) |
||||
{ |
||||
if (&h == startHex) |
||||
{ |
||||
continue; |
||||
} |
||||
if (hex_equal(h.hex, hxi->hex)) |
||||
{ |
||||
h.selected = true; |
||||
h.current_color = selected_fill_color; |
||||
} |
||||
else |
||||
{ |
||||
h.selected = false; |
||||
h.current_color = h.stored_color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
handleMouseDown(SDL_MouseButtonEvent &e) |
||||
{ |
||||
v2i coords = mapMouseToViewport(e.x, e.y); |
||||
|
||||
// mouse click handled by gui
|
||||
/*
|
||||
if (gooeyHitTest(coords)) |
||||
{ |
||||
HexDrawMode mode = gooeyPressButton(coords); |
||||
if (mode != NONE) |
||||
{ |
||||
resetHexes(); |
||||
globalDrawMode = mode; |
||||
} |
||||
|
||||
return; |
||||
} |
||||
*/ |
||||
|
||||
switch (globalDrawMode) |
||||
{ |
||||
case FILL: |
||||
startHexFill(coords.x, coords.y); |
||||
break; |
||||
|
||||
case LINE: |
||||
startHexLineDraw(coords.x, coords.y); |
||||
break; |
||||
|
||||
case CONE_FILL: |
||||
startHexConeFill(coords.x, coords.y); |
||||
break; |
||||
|
||||
case PATHFINDING: |
||||
break; |
||||
|
||||
case NONE: |
||||
// fall through
|
||||
default: |
||||
{ |
||||
hex_info *hex = getSingleHex(coords.x, coords.y); |
||||
if (hex) |
||||
{ |
||||
hex->selected = !hex->selected; |
||||
if (hex->selected) |
||||
{ |
||||
hex->current_color = selected_fill_color; |
||||
} |
||||
else |
||||
{ |
||||
hex->current_color = hex->stored_color; |
||||
}
|
||||
} |
||||
}break; |
||||
} |
||||
} |
||||
|
||||
void |
||||
handleMouseMove(SDL_MouseMotionEvent &e) |
||||
{ |
||||
v2i coords = mapMouseToViewport(e.x, e.y); |
||||
|
||||
switch (globalDrawMode) |
||||
{ |
||||
case FILL: |
||||
updateHexFill(coords.x, coords.y); |
||||
break; |
||||
|
||||
case LINE: |
||||
updateHexLineDraw(coords.x, coords.y); |
||||
break; |
||||
|
||||
case CONE_FILL: |
||||
updateHexConeFill(coords.x, coords.y); |
||||
break; |
||||
|
||||
case PATHFINDING: |
||||
break; |
||||
|
||||
case NONE: |
||||
// fall through
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void |
||||
handleMouseUp(SDL_MouseButtonEvent &e) |
||||
{ |
||||
//v2i coords = mapMouseToViewport(e.x, e.y);
|
||||
|
||||
switch (globalDrawMode) |
||||
{ |
||||
case FILL: |
||||
isSelecting = false; |
||||
break; |
||||
|
||||
case LINE: |
||||
isSelecting = false; |
||||
break; |
||||
|
||||
case CONE_FILL: |
||||
isSelecting = false; |
||||
break; |
||||
|
||||
case PATHFINDING: |
||||
break; |
||||
|
||||
case NONE: |
||||
// fall through
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// TODO: use timer in render loop, and call SDL event handler
|
||||
// per frame with event pump
|
||||
bool |
||||
enterLoop(SDL_Handles &handles) |
||||
{ |
||||
SDL_Event e; |
||||
bool quit = false; |
||||
while(!quit) |
||||
{ |
||||
// TODO: remove hack to not peg CPU. replace with an actual frame timer
|
||||
SDL_Delay(16); // ~60hz
|
||||
|
||||
while (SDL_PollEvent(&e)) |
||||
{ |
||||
switch (e.type) |
||||
{ |
||||
case SDL_QUIT: |
||||
quit = true; |
||||
break; |
||||
case SDL_KEYDOWN: |
||||
if ( e.key.keysym.sym == SDLK_ESCAPE) |
||||
quit = true; |
||||
break; |
||||
case SDL_MOUSEBUTTONDOWN: |
||||
isSelecting = true; |
||||
handleMouseDown(e.button); |
||||
break; |
||||
case SDL_MOUSEBUTTONUP: |
||||
isSelecting = false; |
||||
handleMouseUp(e.button); |
||||
break; |
||||
case SDL_MOUSEMOTION: |
||||
handleMouseMove(e.motion); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
render(); |
||||
SDL_GL_SwapWindow(handles.window); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool |
||||
cleanUp(SDL_Handles &handles) |
||||
{ |
||||
// TODO: make a generic cleanup function that can free other types of SDL objects too
|
||||
//freeGooey();
|
||||
for (SDL_Surface *surface : handles.texSurfaces) |
||||
SDL_FreeSurface(surface); |
||||
SDL_GL_DeleteContext(handles.glContext); |
||||
SDL_DestroyWindow(handles.window); |
||||
SDL_Quit(); |
||||
return true; |
||||
} |
||||
|
||||
#if defined(_WIN32) |
||||
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, |
||||
LPSTR lpCmdLine, int nShowCmd) |
||||
#else |
||||
int main(int argc, char* argv[]) |
||||
#endif |
||||
{ |
||||
hexInfoArray = new vector<hex_info>; |
||||
SDL_Handles handles; |
||||
if (InitGraphics(handles) == false) |
||||
{ |
||||
stringstream ss; |
||||
ss << "Unable to initialize graphics, exiting"; |
||||
debugLog(ss.str()); |
||||
return 1; |
||||
} |
||||
|
||||
/*
|
||||
if (InitGooey(viewportDims) == false) |
||||
{ |
||||
stringstream ss; |
||||
ss << "Unable to initialize gooey, exiting"; |
||||
debugLog(ss.str()); |
||||
return 1; |
||||
} |
||||
*/ |
||||
|
||||
createScene(); |
||||
enterLoop(handles); |
||||
cleanUp(handles); |
||||
delete hexInfoArray; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
@ -0,0 +1,99 @@
|
||||
|
||||
#ifndef HEXGAME_H |
||||
#define HEXGAME_H |
||||
|
||||
#include <vector> |
||||
#include <cassert> |
||||
|
||||
#include <SDL2/SDL.h> |
||||
#include "hexlib.h" |
||||
|
||||
typedef float real32; |
||||
typedef double real64; |
||||
typedef int32_t bool32; |
||||
typedef int32_t int32; |
||||
typedef int64_t int64; |
||||
typedef uint8_t uint8; |
||||
typedef uint32_t uint32; |
||||
|
||||
struct v2f |
||||
{ |
||||
v2f(): x(0), y(0) {} |
||||
v2f(real64 a, real64 b): x(a), y(b) {} |
||||
real64 x; |
||||
real64 y; |
||||
}; |
||||
struct v2i |
||||
{ |
||||
v2i(int a, int b): x(a), y(b) {} |
||||
v2i() : x(0), y(0) {} |
||||
int32 x; |
||||
int32 y; |
||||
}; |
||||
|
||||
struct v4i |
||||
{ |
||||
int32 x0; |
||||
int32 y0; |
||||
int32 x1; |
||||
int32 y1; |
||||
}; |
||||
|
||||
struct SDL_Handles |
||||
{ |
||||
SDL_Window *window; |
||||
SDL_GLContext glContext; |
||||
std::vector<SDL_Surface*> texSurfaces; |
||||
}; |
||||
|
||||
struct hex_info |
||||
{ |
||||
int32 hexID = 0; |
||||
Hex hex = {}; |
||||
real64 XPos = 0; |
||||
real64 YPos = 0; |
||||
uint32 current_color = 0; // RGBA
|
||||
uint32 stored_color = 0; // RGBA
|
||||
bool selected = false; |
||||
std::vector<Point> vertices; |
||||
}; |
||||
|
||||
enum HexDrawMode |
||||
{ |
||||
NONE, |
||||
FILL, |
||||
LINE, |
||||
CONE_FILL, |
||||
PATHFINDING |
||||
}; |
||||
|
||||
struct game_state |
||||
{ |
||||
// TODO:
|
||||
/*
|
||||
bool isSelecting = false; |
||||
hex_info *startHex; |
||||
hex_info *currentHex; |
||||
vector<hex_info> selectedHexes; |
||||
HexDrawMode globalDrawMode = NONE; |
||||
Layout global_layout; |
||||
*/ |
||||
}; |
||||
|
||||
real32 |
||||
SafeRatio(real32 dividend, real32 divisor) |
||||
{ |
||||
if (divisor == 0) |
||||
return dividend; |
||||
else |
||||
return dividend / divisor; |
||||
} |
||||
|
||||
int32 |
||||
SafeTruncateToInt32(int64 val) |
||||
{ |
||||
assert(val <= INT32_MAX && val >= INT32_MIN); |
||||
return (int32) val; |
||||
} |
||||
|
||||
#endif // HEXGAME_H
|
||||
@ -0,0 +1,270 @@
|
||||
|
||||
#ifndef HEXLIB_H |
||||
#define HEXLIB_H |
||||
|
||||
// Generated code -- http://www.redblobgames.com/grids/hexagons/
|
||||
|
||||
#include <cmath> |
||||
#include <cstdlib> |
||||
#include <vector> |
||||
#include <algorithm> |
||||
#include <iterator> |
||||
using std::abs; |
||||
using std::max; |
||||
using std::vector; |
||||
|
||||
|
||||
struct Point |
||||
{ |
||||
const double x; |
||||
const double y; |
||||
Point(double x_, double y_): x(x_), y(y_) {} |
||||
}; |
||||
|
||||
|
||||
struct Hex |
||||
{ |
||||
int q; |
||||
int r; |
||||
int s; |
||||
Hex(int q_, int r_, int s_): q(q_), r(r_), s(s_) {} |
||||
Hex(): q(0), r(0), s(0) {} |
||||
}; |
||||
|
||||
|
||||
struct FractionalHex |
||||
{ |
||||
const double q; |
||||
const double r; |
||||
const double s; |
||||
FractionalHex(double q_, double r_, double s_): q(q_), r(r_), s(s_) {} |
||||
}; |
||||
|
||||
|
||||
struct OffsetCoord |
||||
{ |
||||
const int col; |
||||
const int row; |
||||
OffsetCoord(int col_, int row_): col(col_), row(row_) {} |
||||
}; |
||||
|
||||
|
||||
struct Orientation |
||||
{ |
||||
const double f0; |
||||
const double f1; |
||||
const double f2; |
||||
const double f3; |
||||
const double b0; |
||||
const double b1; |
||||
const double b2; |
||||
const double b3; |
||||
const double start_angle; |
||||
Orientation(double f0_, double f1_, double f2_, double f3_, double b0_, double b1_, double b2_, double b3_, double start_angle_): f0(f0_), f1(f1_), f2(f2_), f3(f3_), b0(b0_), b1(b1_), b2(b2_), b3(b3_), start_angle(start_angle_) {} |
||||
}; |
||||
|
||||
|
||||
struct Layout |
||||
{ |
||||
const Orientation orientation; |
||||
const Point size; |
||||
const Point origin; |
||||
Layout(Orientation orientation_, Point size_, Point origin_): orientation(orientation_), size(size_), origin(origin_) {} |
||||
}; |
||||
|
||||
|
||||
// Forward declarations
|
||||
|
||||
|
||||
Hex hex_add(Hex a, Hex b) |
||||
{ |
||||
return Hex(a.q + b.q, a.r + b.r, a.s + b.s); |
||||
} |
||||
|
||||
|
||||
Hex hex_subtract(Hex a, Hex b) |
||||
{ |
||||
return Hex(a.q - b.q, a.r - b.r, a.s - b.s); |
||||
} |
||||
|
||||
|
||||
Hex hex_scale(Hex a, int k) |
||||
{ |
||||
return Hex(a.q * k, a.r * k, a.s * k); |
||||
} |
||||
|
||||
|
||||
const vector<Hex> hex_directions = {Hex(1, 0, -1), Hex(1, -1, 0), Hex(0, -1, 1), Hex(-1, 0, 1), Hex(-1, 1, 0), Hex(0, 1, -1)}; |
||||
Hex hex_direction(int direction) |
||||
{ |
||||
return hex_directions[direction]; |
||||
} |
||||
|
||||
|
||||
Hex hex_neighbor(Hex hex, int direction) |
||||
{ |
||||
return hex_add(hex, hex_direction(direction)); |
||||
} |
||||
|
||||
|
||||
const vector<Hex> hex_diagonals = {Hex(2, -1, -1), Hex(1, -2, 1), Hex(-1, -1, 2), Hex(-2, 1, 1), Hex(-1, 2, -1), Hex(1, 1, -2)}; |
||||
Hex hex_diagonal_neighbor(Hex hex, int direction) |
||||
{ |
||||
return hex_add(hex, hex_diagonals[direction]); |
||||
} |
||||
|
||||
|
||||
int hex_length(Hex hex) |
||||
{ |
||||
return int((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2); |
||||
} |
||||
|
||||
|
||||
int hex_distance(Hex a, Hex b) |
||||
{ |
||||
return hex_length(hex_subtract(a, b)); |
||||
} |
||||
|
||||
|
||||
|
||||
Hex hex_round(FractionalHex h) |
||||
{ |
||||
int q = int(round(h.q)); |
||||
int r = int(round(h.r)); |
||||
int s = int(round(h.s)); |
||||
double q_diff = abs(q - h.q); |
||||
double r_diff = abs(r - h.r); |
||||
double s_diff = abs(s - h.s); |
||||
if (q_diff > r_diff && q_diff > s_diff) |
||||
{ |
||||
q = -r - s; |
||||
} |
||||
else |
||||
if (r_diff > s_diff) |
||||
{ |
||||
r = -q - s; |
||||
} |
||||
else |
||||
{ |
||||
s = -q - r; |
||||
} |
||||
return Hex(q, r, s); |
||||
} |
||||
|
||||
|
||||
FractionalHex hex_lerp(FractionalHex a, FractionalHex b, double t) |
||||
{ |
||||
return FractionalHex(a.q * (1 - t) + b.q * t, a.r * (1 - t) + b.r * t, a.s * (1 - t) + b.s * t); |
||||
} |
||||
|
||||
|
||||
vector<Hex> hex_linedraw(Hex a, Hex b) |
||||
{ |
||||
int N = hex_distance(a, b); |
||||
FractionalHex a_nudge = FractionalHex(a.q + 0.000001, a.r + 0.000001, a.s - 0.000002); |
||||
FractionalHex b_nudge = FractionalHex(b.q + 0.000001, b.r + 0.000001, b.s - 0.000002); |
||||
vector<Hex> results = {}; |
||||
double step = 1.0 / max(N, 1); |
||||
for (int i = 0; i <= N; i++) |
||||
{ |
||||
results.push_back(hex_round(hex_lerp(a_nudge, b_nudge, step * i))); |
||||
} |
||||
return results; |
||||
} |
||||
|
||||
|
||||
|
||||
const int EVEN = 1; |
||||
const int ODD = -1; |
||||
OffsetCoord qoffset_from_cube(int offset, Hex h) |
||||
{ |
||||
int col = h.q; |
||||
int row = h.r + int((h.q + offset * (h.q & 1)) / 2); |
||||
return OffsetCoord(col, row); |
||||
} |
||||
|
||||
|
||||
Hex qoffset_to_cube(int offset, OffsetCoord h) |
||||
{ |
||||
int q = h.col; |
||||
int r = h.row - int((h.col + offset * (h.col & 1)) / 2); |
||||
int s = -q - r; |
||||
return Hex(q, r, s); |
||||
} |
||||
|
||||
|
||||
OffsetCoord roffset_from_cube(int offset, Hex h) |
||||
{ |
||||
int col = h.q + int((h.r + offset * (h.r & 1)) / 2); |
||||
int row = h.r; |
||||
return OffsetCoord(col, row); |
||||
} |
||||
|
||||
|
||||
Hex roffset_to_cube(int offset, OffsetCoord h) |
||||
{ |
||||
int q = h.col - int((h.row + offset * (h.row & 1)) / 2); |
||||
int r = h.row; |
||||
int s = -q - r; |
||||
return Hex(q, r, s); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
const Orientation layout_pointy = 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); |
||||
const Orientation layout_flat = 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); |
||||
Point hex_to_pixel(Layout layout, Hex h) |
||||
{ |
||||
Orientation M = layout.orientation; |
||||
Point size = layout.size; |
||||
Point origin = layout.origin; |
||||
double x = (M.f0 * h.q + M.f1 * h.r) * size.x; |
||||
double y = (M.f2 * h.q + M.f3 * h.r) * size.y; |
||||
return Point(x + origin.x, y + origin.y); |
||||
} |
||||
|
||||
|
||||
FractionalHex pixel_to_hex(Layout layout, Point p) |
||||
{ |
||||
Orientation M = layout.orientation; |
||||
Point size = layout.size; |
||||
Point origin = layout.origin; |
||||
Point pt = Point((p.x - origin.x) / size.x, (p.y - origin.y) / size.y); |
||||
double q = M.b0 * pt.x + M.b1 * pt.y; |
||||
double r = M.b2 * pt.x + M.b3 * pt.y; |
||||
return FractionalHex(q, r, -q - r); |
||||
} |
||||
|
||||
|
||||
Point hex_corner_offset(Layout layout, int corner) |
||||
{ |
||||
Orientation M = layout.orientation; |
||||
Point size = layout.size; |
||||
double angle = 2.0 * M_PI * (M.start_angle - corner) / 6; |
||||
return Point(size.x * cos(angle), size.y * sin(angle)); |
||||
} |
||||
|
||||
|
||||
vector<Point> polygon_corners(Layout layout, Hex h) |
||||
{ |
||||
vector<Point> corners = {}; |
||||
Point center = hex_to_pixel(layout, h); |
||||
for (int i = 0; i < 6; i++) |
||||
{ |
||||
Point offset = hex_corner_offset(layout, i); |
||||
corners.push_back(Point(center.x + offset.x, center.y + offset.y)); |
||||
} |
||||
return corners; |
||||
} |
||||
|
||||
|
||||
// custom hex functions
|
||||
|
||||
|
||||
bool hex_equal(Hex a, Hex b) |
||||
{ |
||||
return (a.q == b.q && a.r == b.r && a.s == b.s); |
||||
} |
||||
|
||||
#endif // HEXLIB_H
|
||||
Loading…
Reference in new issue