hexnando/main.cpp
2025-05-12 00:57:25 +02:00

922 lines
32 KiB
C++

// stdlib
#include <stdio.h>
// graphics
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "glad.c"
// math types
#include <glm/matrix.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/string_cast.hpp>
#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 <ft2build.h>
#include FT_FREETYPE_H
// STL
#include <iostream>
// STB
#define STB_SPRINTF_IMPLEMENTATION
#include "stb_sprintf.h"
#undef STB_SPRINTF_IMPLEMENTATION
// libpng
#include <png.h>
// POSIX
#include <time.h>
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::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;
};
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 = {};
// 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;
}
int path_callback([[maybe_unused]]ImGuiInputTextCallbackData *data)
{
fprintf(stderr, "Path callback\n");
return 0;
}
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);
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);
}
}
// 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);
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);
// 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;
camera_t the_camera;
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 */
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;
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;
/* 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_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)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);
glfwPollEvents();
static int grid_rows = grid.rows;
static int grid_columns = grid.columns;
static char image_path[PATH_MAX];
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);
}
// 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];