#ifndef OVERLAY_HPP #define OVERLAY_HPP /* """ Portions of this software are copyright © 2025 The FreeType Project (https://freetype.org). All rights reserved. """ */ #include #include "shmup.hpp" #include #include "freetype/fttypes.h" #include #include FT_FREETYPE_H #include #include struct Character { unsigned int texture_id; // ID handle of the glyph texture glm::ivec2 size; // Size of glyph glm::ivec2 bearing; // Offset from baseline to left/top of glyph FT_Long advance; // Offset to advance to next glyph }; class Overlay : public OpenEngine::Layer { public: Overlay(OpenEngine::Ref& shmup) : game_layer(shmup) {}; ~Overlay() {}; void RenderText(std::string text, float x, float y, float scale, glm::vec3 color) { // activate corresponding render state text_shader->Bind(); text_shader->SetVec3("textColor", glm::vec3(color.x, color.y, color.z)); glActiveTexture(GL_TEXTURE0); glBindVertexArray(VAO); // iterate through all characters std::string::const_iterator c; for (c = text.begin(); c != text.end(); c++) { Character ch = characters[*c]; float xpos = x + ch.bearing.x * scale; float ypos = y - (ch.size.y - ch.bearing.y) * scale; float w = ch.size.x * scale; float h = ch.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, ch.texture_id); // update content of VBO memory glBindBuffer(GL_ARRAY_BUFFER, VBO); 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 += (ch.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64) } glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } glm::vec2 GetTextSize(const std::string& text, float scale) { float width = 0.0f; float height = 0.0f; for (char c : text) { Character ch = characters[c]; width += (ch.advance >> 6) * scale; // Accumulate total width // Track the maximum height float char_height = ch.size.y * scale; if (char_height > height) height = char_height; } return glm::vec2(width, height); } void RenderTextCentered(std::string text, float center_x, float center_y, float scale, glm::vec3 color) { glm::vec2 text_size = GetTextSize(text, scale); // Calculate top-left position to make text centered float x = center_x - (text_size.x / 2.0f); float y = center_y - (text_size.y / 2.0f); RenderText(text, x, y, scale, color); } private: void OnAttach() override { OE_INFO("Loading Freetype..."); int error; FT_Library ft; error = FT_Init_FreeType(&ft); if (error != 0) { OE_ERROR("FREETYPE: Could not init FreeType Library"); return; } FT_Face face; error = FT_New_Face(ft, "/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 0, &face); if (error != 0) { OE_ERROR("FREETYPE: Failed to load font"); return; } FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for (unsigned char c = 0; c < 128; c++) { // load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { OE_ERROR("FREETYTPE: Failed to load Glyph: {}", c); continue; } // generate texture unsigned int 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 Character character = { texture, glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), face->glyph->advance.x }; characters.insert(std::pair(c, character)); } glPixelStorei(GL_UNPACK_ALIGNMENT, 1); FT_Done_Face(face); FT_Done_FreeType(ft); text_shader = OpenEngine::Shader::Create("assets/shaders/text.glsl"); glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); 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); }; void OnUpdate() override { text_shader->Bind(); int width = OpenEngine::Application::Get().GetWindow().GetWidth(); int height = OpenEngine::Application::Get().GetWindow().GetHeight(); projection = glm::ortho(0.0f, static_cast(width), 0.0f, static_cast(height)); text_shader->SetMat4("u_ViewProjection", projection); float x = height - 40; float y = 0; switch (game_layer->GetGameState()) { case GameState::GameOver: { auto& window = OpenEngine::Application::Get().GetWindow(); int width = window.GetWidth(); int height = window.GetHeight(); float x = width / 2.0f; float y = height / 2.0f; RenderTextCentered("Game Over!", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f)); break; } case GameState::Paused: RenderTextCentered("Paused", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f)); break; case GameState::Playing: break; } std::string s_score = "Score: " + std::to_string(game_layer->GetScore()); std::string s_lives = std::to_string(game_layer->GetLives()) + " lives"; RenderText(s_score, 10, OpenEngine::Application::Get().GetWindow().GetHeight() - 50, 1.0f, {0.8f, 0.2f, 0.3f}); RenderText( s_lives, OpenEngine::Application::Get().GetWindow().GetWidth() - 220, OpenEngine::Application::Get().GetWindow().GetHeight() - 50, 1.0f, {0.8f, 0.2f, 0.3f}); }; void OnEvent(OpenEngine::Event& event) override { }; private: std::map characters; OpenEngine::Ref text_shader; glm::mat4 projection; OpenEngine::Ref game_layer; unsigned int VAO, VBO; }; #endif // OVERLAY_HPP