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

/*
* 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;
}