/* * 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 #if defined(_WIN32) #include #include #include #else #include #endif #include #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 g_polygon_select_vertices = {Point(), Point(), Point(), Point()}; void createHexes(vector *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 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 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 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::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; 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; }