#include #include // trig functions #include // calloc // TODO: decide on extension library //#include #include #if defined (_WIN32) #include #else #include #endif #include #include #include #include #include "aixlog.hpp" #include "hexlib.h" #include "hexgame.h" #include "renderer.h" #include "render_group.h" #define MOVE_SPEED 5.f #define ROTATE_SPEED 0.005f #define CAMERA_Z_CLAMP_ANGLE 85.f //#define PROJ_TYPE ORTHOGRAPHIC #define PROJ_TYPE PERSPECTIVE #define DEFAULT_VERTEX_SHADER_FILE "../data/default.vs" #define DEFAULT_FRAGMENT_SHADER_FILE "../data/default.fs" const char * LINE_FRAGMENT_SHADER_CODE = "#version 330 core\n" "out vec3 color;\n" "void main()\n" "{\n" " color = vec3(0,0,0);\n" "}"; const char * DEBUG_FRAGMENT_SHADER_CODE = "#version 330 core\n" "out vec3 color;\n" "void main()\n" "{\n" " color = vec3(1,0,0);\n" "}"; typedef struct clear_col { real32 R; real32 G; real32 B; real32 A; } clear_col; clear_col g_clear_col { 75.f / 255.f, 135.f / 255.f, 135.f / 255.f, 1.f }; typedef struct gl_matrix_info { glm::mat4 projection; glm::mat4 view; glm::mat4 model; glm::mat4 MVP; } gl_matrix_info; enum projection_type { PERSPECTIVE, ORTHOGRAPHIC, }; struct camera { glm::vec3 position; float hAngle; float vAngle; glm::vec3 target; glm::vec3 forward; glm::vec3 up; glm::vec3 left; }; // forward declarations void initMatrices(projection_type p); void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); bool initHexGridBuffers(std::vector* hexes); void fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex); void fillColorBuffer(GLfloat buf[], int len, std::vector* hexes); void fillHexLineBuffer(GLfloat buf[], int len, std::vector* hexes); // globals static gl_matrix_info g_scene_matrices; static render_group* g_filled_hex_render_group; static render_group* g_hex_line_render_group; static render_group* g_debug_render_group; static rg_shader_program g_default_shader; static rg_shader_program g_line_shader; // NOTE: entity render_group pointers are kept in the entity struct static camera g_camera; // TODO: testing lighting static renPointLight g_test_light; // interface bool initRenderer(SDL_Handles &handles, v2i vpDims) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GetCurrentDisplayMode(0, &handles.currentDisplayMode); handles.window = SDL_CreateWindow("hexgame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, vpDims.x, vpDims.y, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if (!handles.window) { LOG(ERROR) << "Error creating window: " << SDL_GetError() << "\n"; return false; } handles.glContext = SDL_GL_CreateContext(handles.window); if (!handles.glContext) { LOG(ERROR) << "Error creating glContext: " << SDL_GetError() << "\n"; return false; } if (SDL_GL_SetSwapInterval(1) != 0) { // vsync LOG(ERROR) << "SDL Errors: " << SDL_GetError() << "\n"; return false; } if (gl3wInit()) { // TODO: decide on extension library LOG(ERROR) << "failed to initialize OpenGL\n"; return false; } LOG(INFO) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n"; LOG(INFO)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n"; LOG(INFO) << "opengl version: " << glGetString(GL_VERSION) << "\n"; glEnable(GL_DEPTH_TEST); glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_ONE, GL_SRC_ALPHA); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // TODO: glDebugMessageCallback is only availabe from >v4.3 // check and warn if context doesn't support this function here glEnable (GL_DEBUG_OUTPUT); glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0); // hide VRAM debug messages glDebugMessageControl(GL_DONT_CARE, 33361, GL_DONT_CARE, 0, 0, GL_FALSE); const char* vs_code = utilDumpTextFile(DEFAULT_VERTEX_SHADER_FILE); const char* fs_code = utilDumpTextFile(DEFAULT_FRAGMENT_SHADER_FILE); bool shader_error = !rgInitShaderProgram(g_default_shader, vs_code, fs_code); utilSafeFree(vs_code); utilSafeFree(fs_code); if (shader_error) { LOG(ERROR) << "Error initializing shader program\n"; return false; } return true; } bool addTexture(SDL_Handles &handles, const char * path) { // testing LOG(INFO) << "Loading image: " << path << "\n"; SDL_Surface* image = IMG_Load(path); if (!image) { LOG(ERROR) << "IMG_Load: " << IMG_GetError() << "\n"; return false; } GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // store opengl id in SDL_Surface.userdat image->userdata = (void*)(intptr_t) tex_id; handles.texSurfaces.push_back(image); return true; } v2f getUnprojectedCoords(int32 x, int32 y, int32 vp_width, int32 vp_height) { // NOTE: using depth buffer may not be as accurate as doing ray-cast GLfloat depth; glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); glm::vec4 viewport = glm::vec4(0, 0, vp_width, vp_height); glm::vec3 wincoord = glm::vec3(x, y, depth); glm::vec3 vU = glm::unProject(wincoord, g_scene_matrices.view, g_scene_matrices.projection, viewport); v2f v(vU.x, vU.y); return v; } v3f getCameraPosition() { return v3f(g_camera.position.x, g_camera.position.y, g_camera.position.z); } bool createScene(std::vector* hexes, Entity* entities, uint32 entity_count) { // entities for (uint i = 0; i < entity_count; i++) { rgInitEntity(&entities[i]); entities[i].ren_group->shader = g_default_shader; } initMatrices(PROJ_TYPE); if (!initHexGridBuffers(hexes)) return false; // debug draw vertices uint debug_buf_len = 12; // 4 vertices, 3 floats per vertex g_debug_render_group = rgInitSingle(g_default_shader, debug_buf_len, true, 0, GL_LINE_LOOP); if (g_debug_render_group == nullptr) return false; gl_buffer& debug_vertex_buf = g_debug_render_group->render_objects[0]->vertex_buffer; gl_buffer& debug_color_buf = g_debug_render_group->render_objects[0]->color_buffer; gl_buffer& debug_normal_buf = g_debug_render_group->render_objects[0]->normal_buffer; for (uint i = 0; i < debug_buf_len; i += 3) { debug_color_buf.buffer[i] = 1.f; debug_color_buf.buffer[i + 1] = 0.f; debug_color_buf.buffer[i + 2] = 0.f; debug_normal_buf.buffer[i] = 0.f; debug_normal_buf.buffer[i + 1] = 0.f; debug_normal_buf.buffer[i + 2] = 1.f; } rgBufferData(&debug_vertex_buf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER); rgBufferData(&debug_color_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); rgBufferData(&debug_normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); // lights // TODO: load light properties from scene/level files //g_test_light.light_ID = glGetUniformLocation(g_entity_render_group.program_id, "light_position"); if (entities[0].ren_group != nullptr) { g_test_light.light_ID = glGetUniformLocation(entities[0].ren_group->shader.program_id, "light_position"); g_test_light.position = glm::vec3(800, 300, 400); // above center of hexgrid g_test_light.direction = glm::vec3(0, 0, 0) - g_test_light.position; // back towards test entity g_test_light.color = glm::vec3(1.f, 1.f, 1.f); g_test_light.intensity = 1.f; } return true; } void moveCamera(bool up, bool left, bool down, bool right, bool forward, bool backward) { if (!up && !left && !down && !right && !forward && !backward) return; glm::vec3 f = g_camera.forward; glm::vec3 u = g_camera.up; glm::vec3 old = g_camera.position; glm::vec3 &p = g_camera.position; glm::vec3 v(0.f); // normalized direction // TODO: still seems like we're adding magnitude when moving in 2 directions #if 0 if (forward) v = glm::normalize(v + f); if (backward) v = glm::normalize(v - f); if (up) v = glm::normalize(v + u); if (down) v = glm::normalize(v - u); if (left) v -= glm::normalize(glm::cross(f, u)); if (right) v -= glm::normalize(glm::cross(u, f)); #else if (forward) v += f; if (backward) v -= f; if (up) v += u; if (down) v -= u; if (left) v -= glm::cross(f, u); if (right) v -= glm::cross(u, f); #endif p += (v * MOVE_SPEED); glm::vec3 diff = old - p; g_scene_matrices.view = glm::translate(g_scene_matrices.view, diff); g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model; } void rotateCamera(int32 xrel, int32 yrel) { camera &c = g_camera; float &h = c.hAngle; float &v = c.vAngle; h += ROTATE_SPEED * xrel; v -= ROTATE_SPEED * yrel; // clamp vAngle to prevent gimbal lock float a = glm::radians(CAMERA_Z_CLAMP_ANGLE); if (v < (-1 * a)) v = (-1 * a); if (v > a) v = a; c.forward = glm::vec3( glm::cos(v) * glm::sin(h), glm::cos(v) * glm::cos(h), glm::sin(v) ); glm::normalize(c.forward); c.up = glm::vec3(0,0,1); c.left = glm::normalize(glm::cross(c.forward, c.up)); c.up = glm::normalize(glm::cross(c.left, c.forward)); g_scene_matrices.view = glm::lookAt(c.position, c.position + c.forward, c.up); g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model; } // NOTE: don't need this yet void rollCamera(bool CW, bool CCW) { #if 0 if ((!CW && !CCW) || (CW && CCW)) return; float a = 0.005f; if (CW) a *= 1; if (CCW) a *= -1; camera &c = g_camera; glm::mat4 m = glm::rotate(glm::mat4(1.f), a, c.forward); glm::vec4 v(c.up.x, c.up.y, c.up.z, 0); v = v * m; g_camera.up = glm::vec3(v.x, v.y, v.z); g_scene_matrices.view *= m; g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model; #endif } void renderFrame(std::vector *hexes, Entity* entities, uint32 entity_count) { glClearColor(g_clear_col.R, g_clear_col.G, g_clear_col.B, g_clear_col.A); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glm::mat4 m_model = g_scene_matrices.model; glm::mat4 m_view = g_scene_matrices.view; glm::mat4 m_projection = g_scene_matrices.projection; // filled hexes // get new colors every frame render_group* rg = g_filled_hex_render_group; gl_buffer& color_buf = rg->render_objects[0]->color_buffer; fillColorBuffer(color_buf.buffer, color_buf.buffer_len, hexes); rgDraw(rg, m_model, m_view, m_projection, g_test_light.position, g_test_light.light_ID); // hex lines rgDraw(g_hex_line_render_group, m_model, m_view, m_projection, g_test_light.position, g_test_light.light_ID); // entities for (uint i = 0; i < entity_count; i++) { rgDraw(entities[i].ren_group, entities[i].world_transform , m_view, m_projection, g_test_light.position, g_test_light.light_ID); } } void renderDebug(std::vector &vertices) { GLfloat* buf = g_debug_render_group->render_objects[0]->vertex_buffer.buffer; buf[0] = vertices[0].x; buf[1] = vertices[0].y; buf[2] = 0; buf[3] = vertices[1].x; buf[4] = vertices[1].y; buf[2] = 0; buf[6] = vertices[2].x; buf[7] = vertices[2].y; buf[8] = 0; buf[9] = vertices[3].x; buf[10] = vertices[3].y; buf[11] = 0; rgDraw(g_debug_render_group, g_scene_matrices.model, g_scene_matrices.view, g_scene_matrices.projection, g_test_light.position, g_test_light.light_ID, true); } void freeBuffers() { std::vector groups = { g_filled_hex_render_group, g_hex_line_render_group, g_debug_render_group, }; for (render_group* group : groups) rgFree(group); } // internal void initMatrices(projection_type p) { // TODO: many constants used here should be passed as args if (p == PERSPECTIVE) { g_scene_matrices.projection = glm::infinitePerspective( glm::radians(60.f), // FoV 16.f / 9.f, // ascpect ratio 0.1f // near clip plane ); g_camera.position = glm::vec3(640,0,100); g_camera.target = glm::vec3(640,500,0); //g_camera.target = glm::vec3(0,0,0); // inital rotation should match target direction glm::vec3 &p = g_camera.position; glm::vec3 &t = g_camera.target; g_camera.hAngle = 0; g_camera.vAngle = glm::atan((t.z - p.z) / (t.y - p.y)); ////// // TODO: add call to rotate camera here to remove duplicate code camera &c = g_camera; float &h = c.hAngle; float &v = c.vAngle; c.forward = glm::vec3( glm::cos(v) * glm::sin(h), glm::cos(v) * glm::cos(h), glm::sin(v) ); glm::normalize(c.forward); ////// g_camera.up = glm::vec3(0,1,0); g_camera.left = glm::normalize(glm::cross(g_camera.up, g_camera.forward)); g_camera.up = glm::normalize(glm::cross(g_camera.forward, g_camera.left)); g_scene_matrices.view = glm::lookAt( g_camera.position, // camera position g_camera.position + g_camera.forward, g_camera.up // "up" vector ); } else // ORTHO { // left, right, bottom, top, zNear, zFar g_scene_matrices.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f); g_scene_matrices.view = glm::lookAt( glm::vec3(0.0f, 0.0f, 1.0f), // camera position glm::vec3(0.0f, 0.0f, 0.0f), // look at position glm::vec3(0,1,0) // "up" vector ); } g_scene_matrices.model = glm::mat4(1.0f); g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model; } void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { LOG((type == GL_DEBUG_TYPE_ERROR) ? ERROR : DEBUG) << (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "") << ", type: " << type << ", severity: " << severity << ", message: " << message << "\n"; } bool initHexGridBuffers(std::vector* hexes) { // TODO: index duplicate vertices // 6 triangles * 3 vertices per triangle * 3 floats per vertex = 54 uint line_vertices_per_hex = 6 * 2; // 12 vertices since we're using line segments atm uint line_buf_len = hexes->size() * line_vertices_per_hex * 3; // 3 floats per vertex uint vbuf_len = hexes->size() * 6 * 3 * 3; g_filled_hex_render_group = rgInitSingle(g_default_shader, vbuf_len, true); if (g_filled_hex_render_group == nullptr) return false; gl_buffer& vbuf = g_filled_hex_render_group->render_objects[0]->vertex_buffer; gl_buffer& cbuf = g_filled_hex_render_group->render_objects[0]->color_buffer; gl_buffer& normal_buf = g_filled_hex_render_group->render_objects[0]->normal_buffer; for (uint i = 0; i < hexes->size(); i++) fillTriangleBufferFromHex(vbuf.buffer, i * 54, (*hexes)[i]); rgBufferData(&vbuf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER); fillColorBuffer(cbuf.buffer, vbuf_len, hexes); rgBufferData(&cbuf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER); // cheat at vertex normals since all hexes lay flat on z-axis for (uint i = 0; i < vbuf_len; i += 3) { normal_buf.buffer[i] = 0.f; normal_buf.buffer[i + 1] = 0.f; normal_buf.buffer[i + 2] = 1.f; } rgBufferData(&normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); // hex lines g_hex_line_render_group = rgInitSingle(g_default_shader, line_buf_len, true, 0, GL_LINES); if (g_hex_line_render_group == nullptr) return false; gl_buffer& line_buf = g_hex_line_render_group->render_objects[0]->vertex_buffer; gl_buffer& line_color_buf = g_hex_line_render_group->render_objects[0]->color_buffer; gl_buffer& line_normal_buf = g_hex_line_render_group->render_objects[0]->normal_buffer; fillHexLineBuffer(line_buf.buffer, line_buf_len, hexes); rgBufferData(&line_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); for (uint i = 0; i < line_buf_len; i++) line_color_buf.buffer[i] = 0.f; rgBufferData(&line_color_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); for (uint i = 0; i < line_buf_len; i += 3) { line_normal_buf.buffer[i] = 0.f; line_normal_buf.buffer[i + 1] = 0.f; line_normal_buf.buffer[i + 2] = 1.f; } rgBufferData(&line_normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER); return true; } void fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex) { // triangles for (int i = 0; i < 6; i++) { // vertex 0 buf[idx + 0] = (GLfloat) hex.XPos; buf[idx + 1] = (GLfloat) hex.YPos; buf[idx + 2] = (GLfloat) 0.f; // vertex 1 buf[idx + 3] = (GLfloat) hex.vertices[i].x; buf[idx + 4] = (GLfloat) hex.vertices[i].y; buf[idx + 5] = (GLfloat) 0.f; if (i == 5) // re-use the first point for the last triangle { // vertex 2 buf[idx + 6] = (GLfloat) hex.vertices[0].x; buf[idx + 7] = (GLfloat) hex.vertices[0].y; buf[idx + 8] = (GLfloat) 0.f; } else { // vertex 2 buf[idx + 6] = (GLfloat) hex.vertices[i + 1].x; buf[idx + 7] = (GLfloat) hex.vertices[i + 1].y; buf[idx + 8] = (GLfloat) 0.f; } // we've added 9 GLfloats per loop idx += 9; } } void fillColorBuffer(GLfloat buf[], int len, std::vector* hexes) { int buf_idx; int buf_len_per_hex = 54; // NOTE: 3 * 3 * 6 GLfloat color_buf[3]; for (int i = 0; i < (int) hexes->size(); i++) { buf_idx = i * buf_len_per_hex; hex_info hxi = (*hexes)[i]; // TODO: check performance of this since we call multiple times per frame // maybe can cache glfloat triplets somewhere utilConvertColor(color_buf, hxi.current_color); for (int j = 0; j < buf_len_per_hex; j+=3) { buf[buf_idx + j] = color_buf[0]; buf[buf_idx + j + 1] = color_buf[1]; buf[buf_idx + j + 2] = color_buf[2]; } } } void fillHexLineBuffer(GLfloat buf[], int len, std::vector* hexes) { Point p1, p2; int idx = 0; for (int i = 0; i < (int) hexes->size(); i++) { hex_info hxi = (*hexes)[i]; for (int j = 0; j < 6; j ++) { if (j == 5) // wrap { p1 = hxi.vertices[j]; p2 = hxi.vertices[0]; } else { p1 = hxi.vertices[j]; p2 = hxi.vertices[j + 1]; } buf[idx + 0] = p1.x; buf[idx + 1] = p1.y; buf[idx + 2] = 0.f; buf[idx + 3] = p2.x; buf[idx + 4] = p2.y; buf[idx + 5] = 0.f; idx += 6; } } }