// stdlib #include // graphics #define GLFW_INCLUDE_NONE #include #include "glad.c" // math types #include #include #include #include #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 // STL #include // STB #define STB_SPRINTF_IMPLEMENTATION #include "stb_sprintf.h" #undef STB_SPRINTF_IMPLEMENTATION // libpng #include // POSIX #include struct debug_timer_t { const char *name; int64_t usec; }; extern debug_timer_t debug_timers[]; extern int ntimers; struct debug_clock_t { const char *name; int id; int64_t start; int64_t end; debug_clock_t(const char *name, int id) { this->name = name; this->id = id; struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); start = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000; } ~debug_clock_t() { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); end = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000; debug_timers[id].name = this->name; debug_timers[id].usec = end - start; } }; #define TIME_BLOCK(name) debug_clock_t _name_##timer(#name, __COUNTER__); struct charglyph_t { int32_t tex; // index of character in tex array 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 { int32_t id; glm::vec3 position; float radius; glm::vec3 vertices[7]; glm::vec2 uvs[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; }; struct selection_t { int32_t *indices; int32_t nselected; }; struct texture_t { uint32_t gl_id; // OpenGL GLuint ID uint32_t w; uint32_t h; }; struct input_t { uint8_t keys[GLFW_KEY_LAST]; }; const int32_t ARRAY_LIMIT = 400; charglyph_t glyphmap[128]; uint32_t text_program; uint32_t textvao, textvbo; uint32_t hex_program; uint32_t texture_array; glm::mat4 transforms[ARRAY_LIMIT]; int32_t letter_map[ARRAY_LIMIT]; int32_t text_frame_idx; texture_t pngtex = {}; camera_t the_camera = {}; bool dirty_window = true; input_t frame_input = {}; // 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) bool is_selected(int idx, selection_t *sel) { for (int i = 0; i < sel->nselected; ++i) if (sel->indices[i] == idx) return true; return false; } void select(int idx, selection_t *sel) { if (!is_selected(idx, sel)) sel->indices[sel->nselected++] = idx; } void deselect(int idx, selection_t *sel) { for (int i = 0; i < sel->nselected; ++i) if (sel->indices[i] == idx) { sel->indices[i] = -1; --sel->nselected; } } 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; dirty_window = true; } void key_callback([[maybe_unused]]GLFWwindow *window, int key, [[maybe_unused]]int scancode, [[maybe_unused]]int action, [[maybe_unused]]int mods) { frame_input.keys[key] = action; } int path_callback([[maybe_unused]]ImGuiInputTextCallbackData *data) { fprintf(stderr, "Path callback\n"); return 0; } void ogl_debugcb(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param) { auto const src_str = [source]() { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; } }(); auto const type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; } }(); auto const severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; } }(); std::cout << src_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << '\n'; } int read_png(const char *path) { FILE *fd = fopen(path, "rb"); if (!fd) return -1; png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) return 4; png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) return 4; if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); fclose(fd); fprintf(stderr, "Error routine\n"); return -1; } png_init_io(png_ptr, fd); png_set_crc_action(png_ptr, PNG_CRC_WARN_USE, PNG_CRC_WARN_USE); png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0); // TODO: Use low-level interface to read textures in inverted order as an optimization png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL); fprintf(stdout, "File %s read correctly\n", path); uint32_t row_bytes = png_get_rowbytes(png_ptr, info_ptr); uint32_t width = png_get_image_width(png_ptr, info_ptr); uint32_t height = png_get_image_height(png_ptr, info_ptr); fprintf(stdout, "\tWidth: %d px; Height: %d px; Rowbytes: %d\n", width, height, row_bytes); uint8_t **row_pointers = png_get_rows(png_ptr, info_ptr); uint8_t *data = (uint8_t*) calloc(1, row_bytes * height); for (uint32_t i = 0; i < height; ++i) memcpy(data + i * row_bytes, row_pointers[height - i - 1], row_bytes); GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); GLenum tex_format = GL_RGBA; glTexImage2D(GL_TEXTURE_2D, 0, tex_format, width, height, 0, tex_format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); pngtex.gl_id = tex_id; pngtex.w = width; pngtex.h = height; return 0; } 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_queued_text(glm::vec3 color) { //glActiveTexture(GL_TEXTURE0); //glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); //glBindVertexArray(textvao); //glBindBuffer(GL_ARRAY_BUFFER, textvbo); glUniform3f(glGetUniformLocation(text_program, "text_color"), color.x, color.y, color.z); glUniformMatrix4fv(glGetUniformLocation(text_program, "transforms"), text_frame_idx, GL_FALSE, &transforms[0][0][0]); glUniform1iv(glGetUniformLocation(text_program, "letter_map"), text_frame_idx, &letter_map[0]); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, text_frame_idx); //glBindBuffer(GL_ARRAY_BUFFER, 0); //glBindVertexArray(0); //glBindTexture(GL_TEXTURE_2D, 0); text_frame_idx = 0; } void render_text(const char *text, float x, float y, float scale, glm::vec3 color, bool render_immediately) { // TODO: Group draw calls together instead of doing 1 draw call/character. Use an atlas with GL_TEXTURE_ARRAY // Take tips from https://www.youtube.com/watch?v=S0PyZKX4lyI scale *= 48.0f/256; // activate corresponding render state //glUseProgram(text_program); //glActiveTexture(GL_TEXTURE0); //glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); //glBindVertexArray(textvao); //glBindBuffer(GL_ARRAY_BUFFER, textvbo); // iterate through 128 ASCI characters for (const char *p = text; *p != '\0'; ++p) { // Avoid drawing more than ARRAY_LIMIT chars at a time if (text_frame_idx == ARRAY_LIMIT) render_queued_text(color); char c = *p; charglyph_t charglyph = glyphmap[(size_t)c]; if (c == ' ') // Just advance by normal horizontal distance and prepare for next char { x += (charglyph.advance >> 6) * scale; continue; } float xpos = x + charglyph.bearing.x * scale; float ypos = y - (256 - charglyph.bearing.y) * scale; transforms[text_frame_idx] = glm::translate(glm::mat4(1.0f), glm::vec3(xpos, ypos, 0)) * glm::scale(glm::mat4(1.0f), glm::vec3(256 * scale, 256 * scale, 0)); letter_map[text_frame_idx] = charglyph.tex; // 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) ++text_frame_idx; } if (render_immediately) { render_queued_text(color); } // glBindBuffer(GL_ARRAY_BUFFER, 0); // 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; } char *hex_labels = nullptr; const uint32_t label_size = 8; void create_grid(grid_t *grid, int rows, int columns) { grid->rows = rows; grid->columns = columns; if (!grid->hexes) grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t)); else { hex_t *new_ptr = (hex_t*)realloc(grid->hexes, grid->rows * grid->columns * sizeof(hex_t)); if (new_ptr) grid->hexes = new_ptr; else { free(grid->hexes); grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t)); } } if (!hex_labels) { hex_labels = (char*)calloc(grid->rows * grid->columns * label_size, 1); } else { char *new_ptr = (char*)realloc(hex_labels, grid->rows * grid->columns * label_size); if (new_ptr) hex_labels = new_ptr; else { free(hex_labels); hex_labels = (char*)calloc(grid->rows * grid->columns * label_size, 1); } } // Fill labels buffer for (int n = 0; n < grid->rows * grid->columns; n++) stbsp_snprintf(&hex_labels[n*label_size], label_size, "%d", n); 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->id = i * grid->columns + j; the_hexagon->color = glm::vec4(1.0 - i * 1.0/rows, 1.0 - j * 1.0/columns, 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); // default uv coordinates are a simple 1 unit-sized hexagon mask the_hexagon->uvs[0] = glm::vec2(0, 0); the_hexagon->uvs[1] = glm::vec2(-radius, 0); the_hexagon->uvs[2] = glm::vec2(-radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0))); the_hexagon->uvs[3] = glm::vec2(radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0))); the_hexagon->uvs[4] = glm::vec2(radius, 0); the_hexagon->uvs[5] = glm::vec2(radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0))); the_hexagon->uvs[6] = glm::vec2(-radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0))); // copy verts + uvs to a temp ordered buffer before sending to GPU float databuf[7 * 3 + 7 * 2]; int n = 0; for (int i = 0; i < 7; ++i) { databuf[n++] = the_hexagon->vertices[i].x; databuf[n++] = the_hexagon->vertices[i].y; databuf[n++] = the_hexagon->vertices[i].z; databuf[n++] = the_hexagon->uvs[i].x; databuf[n++] = the_hexagon->uvs[i].y; } 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 + // vertices sizeof(float) * 7 * 2 // normals , databuf, GL_STATIC_DRAW); unsigned int indices[6]; indices[0] = 1; indices[1] = 6; indices[2] = 2; indices[3] = 5; indices[4] = 3; indices[5] = 4; glGenBuffers(1, &the_hexagon->ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, the_hexagon->ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); // vertex position data glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)0); // uv data glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)(sizeof(float) * 3)); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } } // 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); glfwSwapInterval(1); glfwSetScrollCallback(window, scroll_callback); glfwSetKeyCallback(window, key_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); fprintf(stdout, "Compiled using libPNG version %s\n", PNG_LIBPNG_VER_STRING); fprintf(stdout, "Runtime using libPNG version %s\n", png_libpng_ver); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(ogl_debugcb, nullptr); // 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, 256, 256); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction glGenTextures(1, &texture_array); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8, 256, 256, 128, 0, GL_RED, GL_UNSIGNED_BYTE, 0); 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); } glTexSubImage3D(GL_TEXTURE_2D_ARRAY,0, 0, 0, int(c), face->glyph->bitmap.width, face->glyph->bitmap.rows, 1, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); // set texture options glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // now store character for later use charglyph_t charglyph = { int(c), 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"); for (int i = 0; i < ARRAY_LIMIT; ++i) { letter_map[i] = 0; transforms[i] = glm::mat4(1.0f); } static GLfloat vertex_data[] = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; glGenVertexArrays(1, &textvao); glGenBuffers(1, &textvbo); glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); static grid_t grid = {}; create_grid(&grid, 10, 10); 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; double cursor_x_scaled; double cursor_y_scaled; bool mouse_pressed; bool right_mouse_pressed; bool mouse_over_gui; static bool show_hex_numbers = true; static selection_t selection; selection.indices = (int32_t*)calloc(1, sizeof(int32_t) * grid.rows * grid.columns); selection.nselected = 0; 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)) { TIME_BLOCK(full_frame); double cursor_dx = 0.0; double cursor_dy = 0.0; int hovered_hex = -1; int32_t window_width, window_height; int lf, tf, rf, bf; float content_scale_x, content_scale_y; int32_t scaled_window_width, scaled_window_height; { TIME_BLOCK(window_size_polling); glfwGetWindowSize(window, &window_width, &window_height); glfwGetWindowFrameSize(window, &lf, &tf, &rf, &bf); glfwGetWindowContentScale(window, &content_scale_x, &content_scale_y); scaled_window_width = (int)(window_width * content_scale_x); scaled_window_height = (int)(window_height * content_scale_y); glViewport(0, 0, scaled_window_width, scaled_window_height); } float aspect_ratio = (float)scaled_window_width/scaled_window_height; { TIME_BLOCK(input_handling); /* Input handling */ glfwPollEvents(); if (frame_input.keys[GLFW_KEY_W] == GLFW_PRESS) { the_camera.position.y += 0.1; dirty_window = true; } if (frame_input.keys[GLFW_KEY_S] == GLFW_PRESS) { the_camera.position.y -= 0.1; dirty_window = true; } if (frame_input.keys[GLFW_KEY_A] == GLFW_PRESS) { the_camera.position.x -= 0.1; dirty_window = true; } if (frame_input.keys[GLFW_KEY_D] == GLFW_PRESS) { the_camera.position.x += 0.1; dirty_window = true; } if (frame_input.keys[GLFW_KEY_R] == GLFW_PRESS) { the_camera.size -= glm::vec2(0.1); dirty_window = true; } if (frame_input.keys[GLFW_KEY_F] == GLFW_PRESS) { the_camera.size += glm::vec2(0.1); dirty_window = true; } 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; right_mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); cursor_dx = cursor_x * content_scale_x; cursor_dy = cursor_y * content_scale_y; glfwGetCursorPos(window, &cursor_x, &cursor_y); cursor_x_scaled = cursor_x * content_scale_x; cursor_y_scaled = cursor_y * content_scale_y; cursor_dx -= cursor_x_scaled; cursor_dy -= cursor_y_scaled; // 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/scaled_window_width; // The Y coord is inverted the_camera.position.y -= cursor_dy * the_camera.size.y/scaled_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; } glm::mat4 model_matrix; glm::mat4 view_matrix; glm::mat4 proj_matrix; glm::vec2 cursor_world; static char image_path[PATH_MAX]; static int grid_rows = grid.rows; static int grid_columns = grid.columns; /* Rendering */ { TIME_BLOCK(hex_render); glClearColor(0.1f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); model_matrix = glm::mat4(1.0f); view_matrix = glm::mat4(1.0f); 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_scaled, (float)cursor_y_scaled, 0.0f, 1.0f); // move screen coordinates to NDC aux.x -= scaled_window_width/2.0f; aux.x /= scaled_window_width/2.0f; aux.y -= scaled_window_height/2.0f; aux.y /= -scaled_window_height/2.0f; aux = glm::inverse(proj_matrix) * aux; 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) { select(idx, &selection); } else if (right_mouse_pressed) { deselect(idx, &selection); } if (!is_selected(idx, &selection)) glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); // red else glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow } else { if (is_selected(idx, &selection)) { glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow } 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_TRIANGLE_STRIP, 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)scaled_window_width, 0.0f, (float)scaled_window_height); glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]); static char debug_text_buf[256]; { TIME_BLOCK(text_render); // Prepare GL state for text rendering glUseProgram(text_program); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x_scaled, cursor_y_scaled, cursor_world.x, cursor_world.y); render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f), true); glm::mat4 camera_transform = proj_matrix * view_matrix * model_matrix; for (int i = 0; show_hex_numbers && i < grid.rows * grid.columns; ++i) { // Get position of hexes in screen glm::vec4 v = camera_transform * glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0); v.x += 1; v.x *= scaled_window_width/2.0f; v.y += 1; v.y *= scaled_window_height/2.0f; // Do not draw text outside visible window if (v.x > scaled_window_width || v.x < 0 || v.y > scaled_window_height || v.y < 0) continue; render_text(&hex_labels[i*label_size], v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f), false); } render_queued_text(glm::vec3(0.0f, 0.0f, 0.0f)); if (hovered_hex != -1) { static char buf[30]; stbsp_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), true); } // Restore GL state glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } glDisable(GL_BLEND); if (grid_rows != grid.rows || grid_columns != grid.columns) { create_grid(&grid, grid_rows, grid_columns); } // Start the Dear ImGui frame { TIME_BLOCK(gui_render); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::Begin("Hi there"); ImGui::SliderInt("Rows", &grid_rows, 1, 100); ImGui::SliderInt("Columns", &grid_columns, 1, 100); ImGui::Checkbox("Show hex numbers", &show_hex_numbers); if (ImGui::CollapsingHeader("Image")) { if (ImGui::InputText("Input image", image_path, PATH_MAX, ImGuiInputTextFlags_EnterReturnsTrue, path_callback)) { // TODO: Remove testing code fprintf(stdout, "Trying to read file %s\n", image_path); if (read_png(image_path)) { fprintf(stdout, "Could not read file %s\n", image_path); pngtex = {}; } } if (pngtex.gl_id) { glBindTexture(GL_TEXTURE_2D, pngtex.gl_id); if (ImGui::CollapsingHeader("Preview")) ImGui::Image((void*)pngtex.gl_id, ImVec2(pngtex.w, pngtex.h), {0,1}, {1,0}); } } if (ImGui::CollapsingHeader("Debug timers")) { for (int i = 0; i < ntimers; ++i) { ImGui::Text("%s: %d nsecs", debug_timers[i].name, debug_timers[i].usec); } } if (ImGui::CollapsingHeader("Debug vars")) { ImGui::Text("Window width %d", window_width); ImGui::Text("Window height %d", window_height); ImGui::Text("Window borders: left %d top %d right %d bottom %d", lf, tf, rf, bf); ImGui::Text("Window content scale: %f X %f Y", content_scale_x, content_scale_y); ImGui::Text("True (scaled) width %d", scaled_window_width); ImGui::Text("True (scaled) height %d", scaled_window_height); } if (ImGui::CollapsingHeader("Debug selection")) { ImGui::Text("Selected %d indices", selection.nselected); std::string aux; for (int i = 0; i < selection.nselected - 1; ++i) aux += std::to_string(selection.indices[i]) + ","; if (selection.nselected) aux += std::to_string(selection.indices[selection.nselected - 1]); ImGui::Text(aux.c_str()); } ImGui::End(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } glfwSwapBuffers(window); dirty_window = false; } // 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; } #define NTIMERS __COUNTER__ int ntimers = NTIMERS; debug_timer_t debug_timers[NTIMERS];