You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
583 lines
14 KiB
583 lines
14 KiB
/* |
|
* TODO: |
|
* - add some sweet unit models |
|
* - need to add indexed drawing for assimp models |
|
* - map generation |
|
* - pathfinding |
|
* - update imgui to v1.62 -- requires changes to example api |
|
*/ |
|
|
|
// Some defaults for the game layout |
|
#define HEX_SIZE 10 |
|
#define HEX_RADIUS 19 |
|
#define HEX_ORIENTATION layout_flat |
|
#define FILL_COLOR 0x565656FF |
|
#define SELECTED_FILL_COLOR 0xF46000FF |
|
#define DEBUG_DRAW true |
|
#define VIEWPORT_WIDTH 1280 |
|
#define VIEWPORT_HEIGHT 720 |
|
#define CONE_ANGLE 30 |
|
|
|
|
|
#include <vector> |
|
|
|
#if defined(_WIN32) |
|
#include <windows.h> |
|
#include <Winuser.h> |
|
#include <SDL.h> |
|
#else |
|
#include <SDL2/SDL.h> |
|
#endif |
|
|
|
#include <SDL_image.h> |
|
|
|
#include "aixlog.hpp" |
|
|
|
#include "hexgame.h" |
|
#include "hexlib.h" |
|
#include "renderer.h" |
|
#include "gooey.h" |
|
#include "mesh.h" |
|
|
|
using std::vector; |
|
|
|
game_state* g_game_state; |
|
render_state* g_render_state; |
|
vector<Point> g_polygon_select_vertices = {Point(), Point(), Point(), Point()}; |
|
|
|
void |
|
createHexes(vector<hex_info> *hxi_array, const Layout &layout, uint32 color) |
|
{ |
|
// create a hexagonal grid of hexagons |
|
int map_radius = HEX_RADIUS; |
|
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) hxi_array->size(); |
|
hxi.hex.q = q; hxi.hex.r = r; hxi.hex.s = -q-r; |
|
Point p = hex_to_pixel(layout, hxi.hex); |
|
hxi.XPos = p.x; |
|
hxi.YPos = p.y; |
|
hxi.current_color = color; |
|
hxi.stored_color = color; |
|
hxi.vertices = polygon_corners(layout, hxi.hex); |
|
hxi.vertices.shrink_to_fit(); |
|
hxi_array->push_back(hxi); |
|
} |
|
} |
|
} |
|
|
|
v2i |
|
mapMouseToViewport(int32 x, int32 y) |
|
{ |
|
v2i coords; |
|
coords.x = x; |
|
coords.y = g_render_state->viewport_dims.y - y; |
|
return coords; |
|
} |
|
|
|
void |
|
resetHexes() |
|
{ |
|
g_game_state->start_hex = g_game_state->current_hex = 0; |
|
|
|
for (hex_info &hxi : *g_game_state->hex_array) |
|
{ |
|
hxi.selected = false; |
|
hxi.current_color = hxi.stored_color; |
|
} |
|
} |
|
|
|
hex_info * |
|
getSingleHex(int32 x, int32 y) |
|
{ |
|
v2i dims = g_render_state->viewport_dims; |
|
v2f v = getUnprojectedCoords(x, y, dims.x, dims.y); |
|
Point p(v.x, v.y); |
|
Hex h = hex_round(pixel_to_hex(g_game_state->hex_layout, p)); |
|
|
|
for (hex_info &hxi : *g_game_state->hex_array) |
|
{ |
|
if (hex_equal(h, hxi.hex)) |
|
return &hxi; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void |
|
setStartHex(hex_info *hex) |
|
{ |
|
hex->selected = true; |
|
hex->current_color = g_render_state->selected_fill_color; |
|
g_game_state->start_hex = g_game_state->current_hex = hex; |
|
g_game_state->is_selecting = true; |
|
} |
|
|
|
void |
|
startDrawHelper(int32 x, int32 y) |
|
{ |
|
resetHexes(); |
|
hex_info *hex = getSingleHex(x, y); |
|
if (hex) |
|
setStartHex(hex); |
|
} |
|
|
|
void |
|
updateHexFill(int32 x, int32 y) |
|
{ |
|
hex_info *hxi = getSingleHex(x, y); |
|
if (hxi && (hxi != g_game_state->current_hex) && g_game_state->start_hex) |
|
{ |
|
g_game_state->current_hex = hxi; |
|
int l = hex_distance(g_game_state->start_hex->hex, g_game_state->current_hex->hex); |
|
|
|
for (hex_info &h : *g_game_state->hex_array) |
|
{ |
|
if (hex_distance(g_game_state->start_hex->hex, h.hex) <= l) |
|
{ |
|
h.selected = true; |
|
h.current_color = g_render_state->selected_fill_color; |
|
} |
|
else |
|
{ |
|
h.selected = false; |
|
h.current_color = h.stored_color; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void |
|
updateHexLineDraw(int32 x, int32 y) |
|
{ |
|
hex_info *hxi = getSingleHex(x, y); |
|
if (hxi && (hxi != g_game_state->current_hex) && g_game_state->start_hex) |
|
{ |
|
g_game_state->current_hex = hxi; |
|
vector<Hex> hexLine = hex_linedraw(g_game_state->start_hex->hex, hxi->hex); |
|
|
|
for (hex_info &h1 : *g_game_state->hex_array) |
|
{ |
|
for (uint i = 0; i < hexLine.size(); i++) |
|
{ |
|
Hex h2 = hexLine[i]; |
|
if (hex_equal(h1.hex, h2)) |
|
{ |
|
h1.selected = true; |
|
h1.current_color = g_render_state->selected_fill_color; |
|
break; |
|
} |
|
else if (i == hexLine.size() - 1) |
|
{ |
|
h1.selected = false; |
|
h1.current_color = h1.stored_color; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void |
|
updateHexConeFill(int32 x, int32 y) |
|
{ |
|
hex_info *hxi = getSingleHex(x, y); |
|
if (hxi && (hxi != g_game_state->current_hex) && g_game_state->start_hex) |
|
{ |
|
g_game_state->current_hex = hxi; |
|
vector<Hex> hexes = hex_conefill(g_game_state->start_hex->hex, hxi->hex); |
|
|
|
// TODO: Remove debug code here and from gooey.h |
|
Point p1(g_game_state->start_hex->XPos, g_game_state->start_hex->YPos); |
|
Point p2(g_game_state->current_hex->XPos, g_game_state->current_hex->YPos); |
|
real64 angle = std::atan2(p2.y - p1.y, p2.x - p1.x); |
|
real64 len = std::hypot(p2.y - p1.y, p2.x - p1.x); |
|
real64 coneAngle = CONE_ANGLE * M_PI / 180; // M_PI is non-standard and may not be portable |
|
|
|
real64 x1 = len * std::cos(angle); |
|
real64 y1 = len * std::sin(angle); |
|
// top of cone |
|
real64 topX = x1 * std::cos(coneAngle) - y1 * std::sin(coneAngle) + p1.x; |
|
real64 topY = x1 * std::sin(coneAngle) + y1 * std::cos(coneAngle) + p1.y; |
|
// bottom of cone |
|
real64 botX = x1 * std::cos(coneAngle) + y1 * std::sin(coneAngle) + p1.x; |
|
real64 botY = x1 * std::sin(-1 * coneAngle) + y1 * std::cos(coneAngle) + p1.y; |
|
|
|
Point test_p; |
|
Point vert2 = Point(botX, botY); |
|
Point vert4 = Point(topX, topY); |
|
std::vector<Point> vertices = {p1, vert2, p2, vert4}; |
|
|
|
for (hex_info &h : *g_game_state->hex_array) |
|
{ |
|
test_p.x = h.XPos; |
|
test_p.y = h.YPos; |
|
|
|
if (crossingTest(vertices, test_p)) |
|
{ |
|
h.selected = true; |
|
h.current_color = g_render_state->selected_fill_color; |
|
} |
|
else |
|
{ |
|
h.selected = false; |
|
h.current_color = h.stored_color; |
|
} |
|
} |
|
|
|
g_polygon_select_vertices[0].x = p1.x; g_polygon_select_vertices[0].y = p1.y; |
|
g_polygon_select_vertices[1].x = vert2.x; g_polygon_select_vertices[1].y = vert2.y; |
|
g_polygon_select_vertices[2].x = p2.x; g_polygon_select_vertices[2].y = p2.y; |
|
g_polygon_select_vertices[3].x = vert4.x; g_polygon_select_vertices[3].y = vert4.y; |
|
} |
|
} |
|
|
|
void |
|
handleMouseDown(SDL_MouseButtonEvent &e) |
|
{ |
|
v2i coords = mapMouseToViewport(e.x, e.y); |
|
|
|
switch (g_game_state->draw_mode) |
|
{ |
|
case FILL: |
|
startDrawHelper(coords.x, coords.y); |
|
break; |
|
|
|
case LINE: |
|
startDrawHelper(coords.x, coords.y); |
|
break; |
|
|
|
case CONE_FILL: |
|
startDrawHelper(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 = g_render_state->selected_fill_color; |
|
} |
|
else |
|
{ |
|
hex->current_color = hex->stored_color; |
|
} |
|
} |
|
}break; |
|
} |
|
} |
|
|
|
void |
|
handleMouseMove(SDL_MouseMotionEvent &e) |
|
{ |
|
v2i coords = mapMouseToViewport(e.x, e.y); |
|
|
|
switch (g_game_state->draw_mode) |
|
{ |
|
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 (g_game_state->draw_mode) |
|
{ |
|
case FILL: |
|
g_game_state->is_selecting = false; |
|
break; |
|
|
|
case LINE: |
|
g_game_state->is_selecting = false; |
|
break; |
|
|
|
case CONE_FILL: |
|
g_game_state->is_selecting = false; |
|
break; |
|
|
|
case PATHFINDING: |
|
break; |
|
|
|
case NONE: |
|
// fall through |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
bool |
|
processSDLEvents() |
|
{ |
|
SDL_Event e; |
|
bool run = true; |
|
|
|
while (SDL_PollEvent(&e)) |
|
{ |
|
// let gooey have event |
|
// TODO: need to check for both io.WantCaptureKeyboard and io.WantCaptureMouse |
|
// to fix bug with 'ESC' not passing through while in imgui |
|
bool gooey_wants = gooeyProcessEvent(e); |
|
game_state* g = g_game_state; |
|
|
|
switch (e.type) |
|
{ |
|
case SDL_QUIT: |
|
run = false; |
|
break; |
|
case SDL_KEYDOWN: |
|
switch (e.key.keysym.sym) |
|
{ |
|
case SDLK_ESCAPE: run = false; break; |
|
case SDLK_e: g->is_moveforward = true; break; |
|
case SDLK_s: g->is_moveleft = true; break; |
|
case SDLK_d: g->is_movebackward = true; break; |
|
case SDLK_f: g->is_moveright = true; break; |
|
case SDLK_SPACE: g->is_moveup = true; break; |
|
case SDLK_c: g->is_movedown = true; break; |
|
case SDLK_r: g->is_rotateCW = true; break; |
|
case SDLK_w: g->is_rotateCCW = true; break; |
|
} |
|
break; |
|
case SDL_KEYUP: |
|
switch (e.key.keysym.sym) |
|
{ |
|
case SDLK_e: g->is_moveforward = false; break; |
|
case SDLK_s: g->is_moveleft = false; break; |
|
case SDLK_d: g->is_movebackward = false; break; |
|
case SDLK_f: g->is_moveright = false; break; |
|
case SDLK_SPACE: g->is_moveup = false; break; |
|
case SDLK_c: g->is_movedown = false; break; |
|
case SDLK_r: g->is_rotateCW = false; break; |
|
case SDLK_w: g->is_rotateCCW = false; break; |
|
} |
|
break; |
|
case SDL_MOUSEBUTTONDOWN: |
|
if (!gooey_wants) |
|
{ |
|
if (e.button.button == SDL_BUTTON_LEFT) |
|
{ |
|
g->is_selecting = true; |
|
handleMouseDown(e.button); |
|
} |
|
else if (e.button.button == SDL_BUTTON_RIGHT) |
|
{ |
|
g->is_camera_rotate = true; |
|
} |
|
} |
|
break; |
|
case SDL_MOUSEBUTTONUP: |
|
if (!gooey_wants) |
|
{ |
|
if (e.button.button == SDL_BUTTON_LEFT) |
|
{ |
|
g->is_selecting = false; |
|
handleMouseUp(e.button); |
|
} |
|
else if (e.button.button == SDL_BUTTON_RIGHT) |
|
{ |
|
g->is_camera_rotate = false; |
|
} |
|
} |
|
break; |
|
case SDL_MOUSEMOTION: |
|
if (!gooey_wants && g->is_selecting) |
|
handleMouseMove(e.motion); |
|
else if(!gooey_wants && g->is_camera_rotate) |
|
rotateCamera(e.motion.xrel, e.motion.yrel); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
return run; |
|
} |
|
|
|
bool |
|
cleanUp(SDL_Handles &handles) |
|
{ |
|
shutdownGooey(); |
|
freeBuffers(); // renderer.h |
|
for (SDL_Surface *surface : handles.texSurfaces) |
|
SDL_FreeSurface(surface); |
|
shutdownAssimp(); // mesh.h |
|
IMG_Quit(); // SDL_image |
|
SDL_GL_DeleteContext(handles.glContext); |
|
SDL_DestroyWindow(handles.window); |
|
SDL_Quit(); |
|
|
|
if (g_game_state) |
|
{ |
|
if (g_game_state->hex_array) |
|
delete g_game_state->hex_array; |
|
delete g_game_state; |
|
} |
|
|
|
// TODO: C-style memory management for entities |
|
render_state* r = g_render_state; |
|
if (r) { |
|
// delete entities |
|
for (uint i = 0; i < r->entity_count; i++) { |
|
std::free(r->entities[i].vertices); |
|
r->entities[i].vertices = nullptr; |
|
std::free(r->entities[i].indices); |
|
r->entities[i].indices = nullptr; |
|
} |
|
|
|
std::free(r->entities); |
|
r->entities = nullptr; |
|
delete r; |
|
r = nullptr; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
#if defined(_WIN32) |
|
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, |
|
LPSTR lpCmdLine, int nShowCmd) |
|
#else |
|
int main(int argc, char* argv[]) |
|
#endif |
|
{ |
|
AixLog::Log::init<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::normal); |
|
LOG(INFO) << "Application started\n"; |
|
|
|
// init global game state |
|
Layout layout(HEX_ORIENTATION, Point(HEX_SIZE, HEX_SIZE), Point(VIEWPORT_WIDTH / 2, VIEWPORT_HEIGHT / 2)); |
|
g_game_state = new game_state(layout); |
|
g_game_state->hex_array = new vector<hex_info>; |
|
g_game_state->is_selecting = false; |
|
g_game_state->start_hex = 0x0; |
|
g_game_state->current_hex = 0x0; |
|
g_game_state->draw_mode = CONE_FILL; |
|
|
|
// init global render state |
|
g_render_state = new render_state(); |
|
g_render_state->is_debug_draw = DEBUG_DRAW; |
|
g_render_state->viewport_dims = v2i(VIEWPORT_WIDTH, VIEWPORT_HEIGHT); |
|
g_render_state->fill_color = FILL_COLOR; |
|
g_render_state->selected_fill_color = SELECTED_FILL_COLOR; |
|
|
|
|
|
createHexes(g_game_state->hex_array, g_game_state->hex_layout, g_render_state->fill_color); |
|
SDL_Handles handles; |
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) |
|
{ |
|
LOG(ERROR) << "Error, SDL_Init: " << SDL_GetError() << "\n"; |
|
return 1; |
|
} |
|
|
|
if (!initRenderer(handles, g_render_state->viewport_dims)) |
|
{ |
|
LOG(ERROR) << "Unable to initialize graphics, exiting\n"; |
|
return 1; |
|
} |
|
|
|
{ |
|
// load support for the JPG and PNG image formats |
|
int flags=IMG_INIT_JPG|IMG_INIT_PNG; |
|
int initted=IMG_Init(flags); |
|
if((initted&flags) != flags) |
|
{ |
|
LOG(ERROR) << "IMG_Init: Failed to init required jpg and png support!\n"; |
|
LOG(ERROR) << "IMG_Init: " << IMG_GetError() << "\n"; |
|
return 1; |
|
} |
|
|
|
// pre-load textures |
|
const char * path = "../data/coords.layout.flat.png"; |
|
if (!addTexture(handles, path)) |
|
{ |
|
LOG(ERROR) << "Error adding " << path << "\n"; |
|
return 1; |
|
} |
|
} |
|
|
|
if (!initAssimp()) |
|
{ |
|
LOG(ERROR) << "Error initializing assimp\n"; |
|
return 1; |
|
} |
|
|
|
{ |
|
// TODO: keep better track of entity memory |
|
Entity* e = (Entity*) std::calloc(1, sizeof(Entity)); |
|
e->num_vertices = getVertexCount(); |
|
e->num_indices = getIndexCount(); |
|
e->vertices = (glm::vec3 *) std::calloc(e->num_vertices, sizeof(glm::vec3)); |
|
e->indices = (uint *) std::calloc(e->num_indices, sizeof(uint)); |
|
convertMesh(e); |
|
g_render_state->entities = e; |
|
g_render_state->entity_count++; |
|
} |
|
|
|
if (!createScene(g_game_state->hex_array, g_render_state->entities, g_render_state->entity_count)) |
|
{ |
|
LOG(ERROR) << "Error in vertex data, exiting\n"; |
|
return 1; |
|
} |
|
|
|
if (!initGooey(handles, g_render_state->viewport_dims)) |
|
{ |
|
LOG(ERROR) << "Fooey, No Gooey!\n"; |
|
return 1; |
|
} |
|
|
|
// main loop |
|
while (processSDLEvents()) |
|
{ |
|
// TODO: remove hack to not peg CPU. replace with an actual frame timer |
|
SDL_Delay(16); // ~60hz |
|
game_state* g = g_game_state; |
|
render_state* r = g_render_state; |
|
moveCamera(g->is_moveup, g->is_moveleft, g->is_movedown, g->is_moveright, |
|
g->is_moveforward, g->is_movebackward); |
|
rollCamera(g->is_rotateCW, g->is_rotateCCW); |
|
renderFrame(g->hex_array); |
|
if (r->is_debug_draw && g->draw_mode == CONE_FILL) |
|
renderDebug(g_polygon_select_vertices); |
|
renderGooey(handles, g->draw_mode, r->is_debug_draw, g->start_hex, |
|
g->current_hex, g->is_selecting); |
|
SDL_GL_SwapWindow(handles.window); |
|
} |
|
|
|
cleanUp(handles); |
|
LOG(INFO) << "Application exiting\n"; |
|
return 0; |
|
} |
|
|
|
|