// stdlib #include // graphics #define GLFW_INCLUDE_NONE #include #include "glad.c" #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #include "imgui/imgui.cpp" #include "imgui/imgui_demo.cpp" #include "imgui/imgui_draw.cpp" #include "imgui/imgui_tables.cpp" #include "imgui/imgui_widgets.cpp" #include "imgui/backends/imgui_impl_opengl3.cpp" #include "imgui/backends/imgui_impl_glfw.cpp" // fonts #include #include FT_FREETYPE_H // math types #include #include #include #include // STL #include struct charglyph_t { uint32_t tex; // handle to glyph texture glm::ivec2 size; // 2d dimensions glm::ivec2 bearing; // offset from baseline to left/top of glyph uint32_t advance; // offset to next glyph }; struct hex_t { glm::vec3 position; float radius; glm::vec3 vertices[7]; glm::vec4 color; uint32_t vao; uint32_t vbo; uint32_t ebo; }; struct grid_t { int rows; int columns; hex_t *hexes; }; struct camera_t { glm::vec3 position; glm::vec2 size; }; charglyph_t glyphmap[128]; uint32_t text_program; uint32_t textvao, textvbo; uint32_t hex_program; // error processing enum err_type { OK = 0, ERR_GLFW_INIT, ERR_GLFW_WINDOW_CREATION, ERR_GLAD_LOAD, ERR_FREETYPE_INIT, ERR_FONT_INIT, ERR_CHAR_LOAD, }; uint32_t main_errcode = 0; const char *errcode_string = ""; double mouse_scroll = 0.0; #define MAIN_ERROR(e) do { main_errcode = e; errcode_string = #e; goto exit_main_with_error; } while(0) void glfw_error_callback(int error, const char* description) { fprintf(stderr, "GLFW error %d: %s\n", error, description); } void scroll_callback([[maybe_unused]]GLFWwindow* window, [[maybe_unused]]double xoffset, double yoffset) { mouse_scroll = yoffset; } char* load_file(const char *pathname) { FILE *text_file = fopen(pathname, "r"); if (!text_file) { fprintf(stderr, "Failed trying to read file at %s", pathname); return NULL; } fseek(text_file, 0, SEEK_END); long file_size = ftell(text_file); fseek(text_file, 0, SEEK_SET); /* same as rewind(f); */ char *file_contents = (char*)calloc(sizeof(char), file_size + 1); fread(file_contents, 1, file_size, text_file); fclose(text_file); return file_contents; } uint32_t build_shader_from_source(const char *source, int32_t type) { char infoLog[512]; unsigned int shader = glCreateShader(type); glShaderSource(shader, 1, (const GLchar * const *)(&source), NULL); glCompileShader(shader); int success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 512, NULL, infoLog); const char *type_str = type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT"; fprintf(stderr, "Error: %s shader compilation failed: %s\n", type_str, infoLog); } // TODO: Maybe better error handling? return shader; } uint32_t make_gl_program(const char *pathname_vertex, const char *pathname_fragment) { char infoLog[512]; char *vertex_source = load_file(pathname_vertex); char *fragment_source = load_file(pathname_fragment); unsigned int vertex_shader = build_shader_from_source(vertex_source, GL_VERTEX_SHADER); unsigned int fragment_shader = build_shader_from_source(fragment_source, GL_FRAGMENT_SHADER); uint32_t program = glCreateProgram(); glAttachShader(program, vertex_shader); glAttachShader(program, fragment_shader); glLinkProgram(program); // print linking errors if any int32_t success; glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { // TODO: Maybe better error handling? glGetProgramInfoLog(program, 512, NULL, infoLog); fprintf(stderr, "Error: shader linking failed: %s\n", infoLog); } else { // Cleanup of unneeded structures glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); } return program; } void render_text(const char *text, float x, float y, float scale, glm::vec3 color) { // activate corresponding render state glUseProgram(text_program); glUniform3f(glGetUniformLocation(text_program, "text_color"), color.x, color.y, color.z); glActiveTexture(GL_TEXTURE0); glBindVertexArray(textvao); // iterate through 128 ASCI characters for (const char *p = text; *p != '\0'; ++p) { char c = *p; charglyph_t charglyph = glyphmap[(size_t)c]; float xpos = x + charglyph.bearing.x * scale; float ypos = y - (charglyph.size.y - charglyph.bearing.y) * scale; float w = charglyph.size.x * scale; float h = charglyph.size.y * scale; // update VBO for each character float vertices[6][4] = { { xpos, ypos + h, 0.0f, 0.0f }, { xpos, ypos, 0.0f, 1.0f }, { xpos + w, ypos, 1.0f, 1.0f }, { xpos, ypos + h, 0.0f, 0.0f }, { xpos + w, ypos, 1.0f, 1.0f }, { xpos + w, ypos + h, 1.0f, 0.0f } }; // render glyph texture over quad glBindTexture(GL_TEXTURE_2D, charglyph.tex); // update content of VBO memory glBindBuffer(GL_ARRAY_BUFFER, textvbo); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); // render quad glDrawArrays(GL_TRIANGLES, 0, 6); // now advance cursors for next glyph (note that advance is number of 1/64 pixels) x += (charglyph.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64) } glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } bool inside_hex(hex_t *hex, glm::vec2 point) { for (int i = 1; i < 7; ++i) { glm::vec2 outer_edge = glm::vec2(hex->vertices[(i % 6) +1].x, hex->vertices[(i % 6) + 1].y) - glm::vec2(hex->vertices[i].x, hex->vertices[i].y); glm::vec2 inner_vec = glm::vec2(point - glm::vec2(hex->vertices[i].x, hex->vertices[i].y)); float angle = glm::orientedAngle(glm::normalize(outer_edge), glm::normalize(inner_vec)); if (angle > 0.0f) return false; } return true; } // global state GLFWwindow *window = nullptr; hex_t hexes[4]; int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) { fprintf(stdout, "Hexnando\n"); // Window initialization glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { MAIN_ERROR(ERR_GLFW_INIT); } window = glfwCreateWindow(640, 480, "Hexnando", NULL, NULL); if (!window) { MAIN_ERROR(ERR_GLFW_WINDOW_CREATION); } glfwMakeContextCurrent(window); glfwSetScrollCallback(window, scroll_callback); if (!gladLoadGL()) { MAIN_ERROR(ERR_GLAD_LOAD); } int gl_version_major, gl_version_minor; glGetIntegerv(GL_MAJOR_VERSION, &gl_version_major); glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor); fprintf(stdout, "Using OpenGL %d.%d\n", gl_version_major, gl_version_minor); // font library initialization FT_Library ft; if (FT_Init_FreeType(&ft)) { MAIN_ERROR(ERR_FREETYPE_INIT); } FT_Face face; if (FT_New_Face(ft, "fonts/Inconsolata-Regular.ttf", 0, &face)) { MAIN_ERROR(ERR_FONT_INIT); } FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction for (unsigned char c = 0; c < 128; c++) { // load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { MAIN_ERROR(ERR_CHAR_LOAD); } // generate texture uint32_t texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer ); // set texture options glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // now store character for later use charglyph_t charglyph = { texture, glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), (uint32_t)face->glyph->advance.x }; glyphmap[c] = charglyph; } // clear ft2 resources FT_Done_Face(face); FT_Done_FreeType(ft); // text initialization text_program = make_gl_program("shaders/text.vert", "shaders/text.frag"); glGenVertexArrays(1, &textvao); glGenBuffers(1, &textvbo); glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); grid_t grid; grid.rows = 10; grid.columns = 10; grid.hexes = (hex_t*)calloc(grid.rows * grid.columns, sizeof(hex_t)); for (int i = 0; i < grid.rows; ++i) for (int j = 0; j < grid.columns; ++j) { hex_t *the_hexagon = &grid.hexes[i*grid.columns+j]; float radius = 0.2; the_hexagon->position = glm::vec3(0,0,0) + glm::vec3(j * radius * 3.0f * glm::cos(glm::radians(60.0)) , (i * radius * 2.0f * glm::sin(glm::radians(60.0))) + (j % 2 ? 0 : radius * glm::sin(glm::radians(60.0))), 0); the_hexagon->radius = radius; the_hexagon->color = glm::vec4(1.0 - i * 0.1, 1.0 - j * 0.1, 1.0, 1.0); the_hexagon->vertices[0] = the_hexagon->position; the_hexagon->vertices[1] = the_hexagon->position + glm::vec3(-radius, 0, 0); the_hexagon->vertices[2] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[3] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[4] = the_hexagon->position + glm::vec3(radius, 0, 0); the_hexagon->vertices[5] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[6] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0); glGenVertexArrays(1, &the_hexagon->vao); glGenBuffers(1, &the_hexagon->vbo); glBindVertexArray(the_hexagon->vao); glBindBuffer(GL_ARRAY_BUFFER, the_hexagon->vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 7 * 3, the_hexagon->vertices, GL_STATIC_DRAW); // EBO setup unsigned int indices[18]; indices[0] = 0; indices[1] = 2; indices[2] = 1; indices[3] = 0; indices[4] = 3; indices[5] = 2; indices[6] = 0; indices[7] = 4; indices[8] = 3; indices[9] = 0; indices[10] = 5; indices[11] = 4; indices[12] = 0; indices[13] = 6; indices[14] = 5; indices[15] = 0; indices[16] = 1; indices[17] = 6; glGenBuffers(1, &the_hexagon->ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, the_hexagon->ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*3 * sizeof(unsigned int), indices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } hex_program = make_gl_program("shaders/hex.vert", "shaders/hex.frag"); /* IMGui initalization */ IMGUI_CHECKVERSION(); ImGui::CreateContext(); static ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 330 core"); // same as shader version double cursor_x; double cursor_y; camera_t the_camera; bool mouse_pressed; bool mouse_over_gui; the_camera.position = glm::vec3(0.0f, 0.0f, 1.0f); the_camera.size = glm::vec2(1.0f, 1.0f); // main loop while (!glfwWindowShouldClose(window)) { double cursor_dx = 0.0; double cursor_dy = 0.0; int hovered_hex = -1; int32_t window_width, window_height; glfwGetWindowSize(window, &window_width, &window_height); glViewport(0, 0, window_width, window_height); float aspect_ratio = (float)window_width/window_height; /* Input handling */ if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) the_camera.position.y += 0.1; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) the_camera.position.y -= 0.1; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) the_camera.position.x -= 0.1; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) the_camera.position.x += 0.1; if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) the_camera.size -= glm::vec2(0.1); if (glfwGetKey(window, GLFW_KEY_F) == GLFW_PRESS) the_camera.size += glm::vec2(0.1); if (mouse_scroll > 0.0) { the_camera.size -= glm::vec2(0.1); mouse_scroll = 0.0; } if (mouse_scroll < 0.0) { the_camera.size += glm::vec2(0.1); mouse_scroll = 0.0; } mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); cursor_dx = cursor_x; cursor_dy = cursor_y; glfwGetCursorPos(window, &cursor_x, &cursor_y); cursor_dx -= cursor_x; cursor_dy -= cursor_y; // Mouse movement if (!mouse_over_gui && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) { fprintf(stdout, "Moving camera by %f %f\n", cursor_dx, cursor_dx); // mouse delta! the_camera.position.x += cursor_dx * the_camera.size.x/window_width; // The Y coord is inverted the_camera.position.y -= cursor_dy * the_camera.size.y/window_height; } if (the_camera.size.x < 0.1) the_camera.size.x = 0.1; if (the_camera.size.y < 0.1) the_camera.size.y = 0.1; /* Rendering */ glClearColor(0.1f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glm::mat4 model_matrix = glm::mat4(1.0f); glm::mat4 view_matrix = glm::mat4(1.0f); glm::mat4 proj_matrix = glm::ortho((the_camera.position.x - the_camera.size.x/2) * aspect_ratio, (the_camera.position.x + the_camera.size.x/2) * aspect_ratio, the_camera.position.y - the_camera.size.y/2, the_camera.position.y + the_camera.size.y/2); glm::vec4 aux((float)cursor_x, (float)cursor_y, 0.0f, 1.0f); // move screen coordinates to NDC aux.x -= window_width/2.0f; aux.x /= window_width/2.0f; aux.y -= window_height/2.0f; aux.y /= -window_height/2.0f; aux = glm::inverse(proj_matrix) * aux; glm::vec2 cursor_world = glm::vec2(aux.x, aux.y); glUseProgram(hex_program); glUniformMatrix4fv(glGetUniformLocation(hex_program, "model"), 1, GL_FALSE, &model_matrix[0][0]); glUniformMatrix4fv(glGetUniformLocation(hex_program, "view"), 1, GL_FALSE, &view_matrix[0][0]); glUniformMatrix4fv(glGetUniformLocation(hex_program, "proj"), 1, GL_FALSE, &proj_matrix[0][0]); for (int i = 0; i < grid.rows; ++i) for (int j = 0; j < grid.columns; j++) { int idx = i * grid.columns + j; if (!mouse_over_gui && inside_hex(&grid.hexes[idx], cursor_world)) // draw hovered hex differently { hovered_hex = i * grid.columns + j; if (mouse_pressed) glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); else glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); } else { glUniform4f(glGetUniformLocation(hex_program, "hex_color"), grid.hexes[idx].color.x, grid.hexes[idx].color.y, grid.hexes[idx].color.z, grid.hexes[idx].color.w); } glBindVertexArray(grid.hexes[idx].vao); glBindBuffer(GL_ARRAY_BUFFER, grid.hexes[idx].vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, grid.hexes[idx].ebo); glEnable(GL_CULL_FACE); glDrawElements(GL_TRIANGLES, 3*6, GL_UNSIGNED_INT, 0); glDisable(GL_CULL_FACE); glBindVertexArray(0); } glUseProgram(text_program); // text rendering glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)window_width, 0.0f, (float)window_height); glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]); static char debug_text_buf[256]; snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x, cursor_y, cursor_world.x, cursor_world.y); render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f)); for (int i = 0; i < grid.rows * grid.columns; ++i) { // Get position of hexes in screen glm::vec4 v = glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0); v = proj_matrix * view_matrix * model_matrix * v; v.x += 1; v.x *= window_width/2.0f; v.y += 1; v.y *= window_height/2.0f; char number_string[11]; snprintf(number_string, 11, "%d", i); render_text(number_string, v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f)); } if (hovered_hex != -1) { char buf[30]; snprintf(buf, 30, "Cursor INSIDE hex %d", hovered_hex); render_text(buf, 25.0f, window_height - 25.0f, .5f, glm::vec3(1.0f, 1.0f, 1.0f)); } glDisable(GL_BLEND); glfwPollEvents(); // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::Begin("Hi there"); ImGui::End(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); } // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); return 0; exit_main_with_error: if (window) glfwDestroyWindow(window); glfwTerminate(); fprintf(stderr, "Hexnando failed with errcode %d: %s\n", main_errcode, errcode_string); return main_errcode; }