hexnando/main.cpp

564 lines
20 KiB
C++

// stdlib
#include <stdio.h>
// graphics
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#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 <ft2build.h>
#include FT_FREETYPE_H
// math types
#include <glm/matrix.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/string_cast.hpp>
// STL
#include <iostream>
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;
}