diff --git a/application/include/overlay.hpp b/application/include/overlay.hpp new file mode 100644 index 0000000..40e3b8a --- /dev/null +++ b/application/include/overlay.hpp @@ -0,0 +1,223 @@ +#ifndef OVERLAY_HPP +#define OVERLAY_HPP + +/* + """ + Portions of this software are copyright © 2025 The FreeType + Project (https://freetype.org). All rights reserved. + """ +*/ + +#include +#include + +#include "freetype/fttypes.h" +#include "open_engine/events/event.hpp" +#include "open_engine/logging.hpp" +#include "open_engine/renderer/shader.hpp" +#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() + {}; + + ~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; + //RenderText("Test String", 0.0f, 0.0f, 1.0f, {0.8f, 0.3f, 0.2f}); + }; + + void OnEvent(OpenEngine::Event& event) override + { + }; + + private: + std::map characters; + OpenEngine::Ref text_shader; + glm::mat4 projection; + + unsigned int VAO, VBO; +}; + +#endif // OVERLAY_HPP diff --git a/application/include/sandbox.hpp b/application/include/sandbox.hpp index 129b719..29d5d10 100755 --- a/application/include/sandbox.hpp +++ b/application/include/sandbox.hpp @@ -101,6 +101,7 @@ class SandboxLayer : public OpenEngine::Layer texture_shader->Bind(); texture_shader->SetInt("u_Texture", 0); texture_shader->SetVec4("u_Color", glm::vec4(1.0f)); + texture_shader->SetFloat("u_TilingFactor", 1.0f); } { diff --git a/application/include/sandbox2d.hpp b/application/include/sandbox2d.hpp index fe938d9..0d2a20e 100755 --- a/application/include/sandbox2d.hpp +++ b/application/include/sandbox2d.hpp @@ -5,6 +5,7 @@ #include #include "imgui.h" +#include "open_engine/renderer/renderer2d.hpp" #include #include @@ -50,10 +51,15 @@ class Sandbox2DLayer : public OpenEngine::Layer } { OE_PROFILE_SCOPE("Drawing Quads"); - OpenEngine::Renderer2D::DrawQuad(glm::vec2(0.5f, 0.5f), glm::vec2(0.3f), glm::vec4(color[0], color[1], color[2], color[3])); - OpenEngine::Renderer2D::DrawQuad(glm::vec2(-0.2f, -0.2f), glm::vec2(0.5f, 0.2f), {0.5f, 0.3f, 0.8f, 1.0f}); - OpenEngine::Renderer2D::DrawQuad(glm::vec3(0.0f, 0.0f, -0.1f), glm::vec2(1.0f, 1.0f), face); + OpenEngine::Transform tr1 = {glm::vec3(0.5f, 0.5f, 1.0f), glm::vec3(0.3f), 20.0f}; + OpenEngine::Transform tr2 = {glm::vec3(-0.2f, -0.2f, 1.0f), glm::vec3(0.5f, 0.2f, 1.0f), 10.0f}; + OpenEngine::Transform tr3 = {glm::vec3(0.0f, 0.0f, -0.1f), glm::vec3(1.0f, 1.0f, 1.0f), 45.0f}; + + OpenEngine::Renderer2D::DrawQuad({glm::vec3(0.5f, 0.5f, 1.0f), glm::vec3(0.3f), 20.0f}, glm::vec4(color[0], color[1], color[2], color[3])); + OpenEngine::Renderer2D::DrawQuad(tr2, {0.5f, 0.3f, 0.8f, 1.0f}); + OpenEngine::Renderer2D::DrawQuad(tr3, face); } + OpenEngine::Renderer2D::EndScene(); } diff --git a/application/include/shmup.hpp b/application/include/shmup.hpp new file mode 100755 index 0000000..d977dc0 --- /dev/null +++ b/application/include/shmup.hpp @@ -0,0 +1,360 @@ +#ifndef SHMUP_HPP +#define SHMUP_HPP + +#include +#include + +#include "imgui.h" +#include "overlay.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +class Shmup : public OpenEngine::Layer +{ + public: + Shmup(OpenEngine::Ref& overlay) + : OpenEngine::Layer("shmup"), overlay(overlay), + camera((float)OpenEngine::Application::Get().GetWindow().GetWidth() / + OpenEngine::Application::Get().GetWindow().GetHeight(), 1.0f) + { + } + ~Shmup() {}; + + void OnAttach() override + { + OE_PROFILE_FUNCTION(); + { + OE_PROFILE_SCOPE("Texture2D Creation"); + ship = OpenEngine::Texture2D::Create("assets/textures/shmup/ship.png"); + enemy_ship = OpenEngine::Texture2D::Create("assets/textures/shmup/enemy_ship.png"); + bullet_tx = OpenEngine::Texture2D::Create("assets/textures/shmup/bullet.png"); + } + + tr1 = {glm::vec3(0.0f, -0.9f, 1.0f), glm::vec3(0.1f), 0.0f}; + + LoadMap(); + } + + void OnDetach() override + { + } + + struct Bounds { + glm::vec2 min; // Bottom-Left + glm::vec2 max; // Top-Right + }; + + Bounds GetBounds(glm::vec2 position, glm::vec2 scale) { + Bounds b; + // Because the original vertices are -0.5 to 0.5, + // the distance from center to edge is exactly scale * 0.5 + glm::vec2 halfScale = scale * 0.5f; + + b.min = position - halfScale; + b.max = position + halfScale; + return b; + } + + bool CheckCollision(const Bounds& a, const Bounds& b) { + bool overlapX = a.max.x >= b.min.x && b.max.x >= a.min.x; + bool overlapY = a.max.y >= b.min.y && b.max.y >= a.min.y; + + return overlapX && overlapY; + } + + void MoveShip() + { + double delta = OpenEngine::Time::DeltaTime(); + if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Up)) { + tr1.position.y += 1 * delta; + } + if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Down)) { + tr1.position.y -= 1 * delta; + } + if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Right)) { + tr1.position.x += 1 * delta; + } + if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Left)) { + tr1.position.x -= 1 * delta; + } + + if (tr1.position.y <= -0.9) + tr1.position.y = -0.9; + if (tr1.position.y >= -0.3) + tr1.position.y = -0.3; + if (tr1.position.x <= -0.8) + tr1.position.x = -0.8; + if (tr1.position.x >= 0.8) + tr1.position.x = 0.8; + } + + void UpdateEntity() + { + double delta = OpenEngine::Time::DeltaTime(); + + for (auto& enemy : enemies) { + enemy.position.y -= 0.3 * delta; + if (enemy.position.y <= -1.1 || CheckCollision( + GetBounds(enemy.position, enemy.size), + GetBounds(tr1.position, tr1.size))){ + lives--; + } + } + + for (auto& bullet : bullets) { + bullet.position.y += 1.1 * delta; + } + + std::erase_if(enemies, [](const auto& enemy) { + return enemy.position.y <= -1.1f; + }); + std::erase_if(bullets, [](const auto& bullet) { + return bullet.position.y >= 1.2f; + }); + + for (auto& bullet : bullets) { + for (auto& enemy : enemies) { + Bounds bullet_bounds = GetBounds(bullet.position, bullet.size); + Bounds enemy_bounds = GetBounds(enemy.position, enemy.size); + + if (CheckCollision(bullet_bounds, enemy_bounds)) { + std::erase_if(bullets, [&](const OpenEngine::Transform& _bullet) { + return _bullet.position == bullet.position; + }); + std::erase_if(enemies, [&](const auto& _enemy) { + return _enemy.position == enemy.position; + }); + score++; + } + } + } + } + + void GameOver() + { + paused = true; + + auto& window = OpenEngine::Application::Get().GetWindow(); + int width = window.GetWidth(); + int height = window.GetHeight(); + + enemies.clear(); + bullets.clear(); + + float x = width / 2.0f; + float y = height / 2.0f; + + overlay->RenderTextCentered("Game Over!", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f)); + }; + + void OnUpdate() override + { + float delta = OpenEngine::Time::DeltaTime(); + OE_PROFILE_FUNCTION() + { + OE_PROFILE_SCOPE("Setting up Rendering"); + //OpenEngine::RenderCommand::SetClearColor({0.11f, 0.11f, 0.15f, 1.0f}); + OpenEngine::RenderCommand::SetClearColor({1.0f, 1.0f, 1.0f, 1.0f}); + OpenEngine::RenderCommand::Clear(); + OpenEngine::Renderer2D::BeginScene(camera.GetCamera()); + } + { + OE_PROFILE_SCOPE("Drawing Quads"); + OpenEngine::Renderer2D::DrawQuad(tr1, ship); + + for (auto& enemy : enemies) + OpenEngine::Renderer2D::DrawQuad(enemy, enemy_ship); + for (auto& bullet : bullets) + OpenEngine::Renderer2D::DrawQuad(bullet, bullet_tx); + } + + OpenEngine::Renderer2D::EndScene(); + + if (!paused) { + if (spawn_cooldown >= spawn_min_time) { + SpawnEnemyRandom(); + //ReadMap(); + spawn_cooldown = 0; + if (spawn_min_time > 0.5) + spawn_min_time -= 0.01; + } + + MoveShip(); + UpdateEntity(); + + if (shooting) + SpawnBullet(); + + for (auto& enemy : enemies) { + if (CheckCollision(GetBounds(enemy.position, enemy.size), GetBounds(tr1.position, tr1.size))) + game_over = true; + } + + shot_cooldown += delta; + spawn_cooldown += delta; + } + if (lives <= 0) + game_over = true; + + if (game_over) + GameOver(); + + std::string s_score = "Score: " + std::to_string(score); + std::string s_lives = std::to_string(lives) + " lives"; + overlay->RenderText(s_score, 10, OpenEngine::Application::Get().GetWindow().GetHeight() - 50, 1.0f, {0.8f, 0.2f, 0.3f}); + overlay->RenderText( + s_lives, + OpenEngine::Application::Get().GetWindow().GetWidth() - 220, + OpenEngine::Application::Get().GetWindow().GetHeight() - 50, + 1.0f, + {0.8f, 0.2f, 0.3f}); + } + + void Reset() + { + tr1 = {glm::vec3(0.0f, -0.9f, 1.0f), glm::vec3(0.1f), 0.0f}; + score = 0; + lives = 5; + game_over = false; + paused = false; + } + + void SpawnEnemyRandom() + { + static std::uniform_int_distribution dist{-8, 8}; + + OpenEngine::Transform enemy_tr({glm::vec3(dist(rd)/10.0f, 1.2f, -0.1f), glm::vec3(0.1f), 180.0f}); + enemies.emplace_back(enemy_tr); + + }; + + void SpawnEnemy(int x) + { + OpenEngine::Transform enemy_tr({{x/10.0f, 1.2f, 0.0f}, glm::vec3(0.1f), 180.0f}); + enemies.emplace_back(enemy_tr); + }; + + void LoadMap() + { + std::string buffer; + + std::ifstream map_file("assets/maps/lvl1.txt"); + + while (std::getline(map_file, buffer)) { + map.emplace_back(buffer); + } + }; + + void ReadMap() + { + static int line_n = 0; + if (line_n >= map.size()) + return; + + auto line = map[line_n]; + + int position = -8; + for (int i = 0; i < line.length(); i++) { + if (line[i] == '#') + SpawnEnemy(position); + position++; + } + + line_n++; + }; + + void SpawnBullet() + { + if (shot_cooldown >= 0.2) { + OpenEngine::Transform bullet_tr; + bullet_tr.size = glm::vec3(0.03f); + bullet_tr.position.x = tr1.position.x; + bullet_tr.position.y = tr1.position.y + 0.1; + + bullets.emplace_back(bullet_tr); + + shot_cooldown = 0; + } + } + + bool ProcessKeyPressedEvents(OpenEngine::KeyPressedEvent& event) + { + if (event.GetKeyCode() == OpenEngine::KeyCode::Space) { + shooting = true; + } + if (game_over && event.GetKeyCode() == OpenEngine::KeyCode::Enter) { + Reset(); + } + + return false; + }; + + bool ProcessKeyReleased(OpenEngine::KeyReleasedEvent& event) + { + if (event.GetKeyCode() == OpenEngine::KeyCode::Space) + shooting = false; + + return false; + } + + void OnEvent(OpenEngine::Event& event) override + { + OE_PROFILE_FUNCTION(); + OpenEngine::EventDispatcher dispatcher(event); + + dispatcher.Dispatch(BIND_EVENT_FN(Shmup::ProcessKeyPressedEvents)); + dispatcher.Dispatch(BIND_EVENT_FN(Shmup::ProcessKeyReleased)); + { + OE_PROFILE_SCOPE("Camera OnEvent"); + camera.OnEvent(event); + } + } + + void OnImGuiRender() override + { + ImGuiIO io = ImGui::GetIO(); + static auto m_Font = io.Fonts->AddFontFromFileTTF("/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 120.0f); + + ImGui::GetForegroundDrawList()->AddText(m_Font, 48.0f, {5.0f, 5.0f}, 0xffffffff, "test"); + } + + private: + //glm::vec3 square_pos = glm::vec3(0.0f); + //glm::vec4 square_color = glm::vec4(1.0f); + + bool paused = false; + bool game_over = false; + + //float color[4] = {0.5f, 0.3f, 0.4f, 1.0f}; + OpenEngine::Ref ship; + OpenEngine::Ref enemy_ship; + OpenEngine::Ref bullet_tx; + OpenEngine::Transform tr1; + std::vector enemies, bullets; + int score = 0; + int lives = 5; + + bool shooting = false; + float spawn_cooldown = 0, shot_cooldown = 0; + float spawn_min_time = 1.0f; + std::vector map; + + std::random_device rd; + std::mt19937 e{rd()}; // or std::default_random_engine e{rd()}; + + //std::unordered_map bindings; + OpenEngine::OrthographicCameraController camera; + + OpenEngine::Ref overlay; + + //std::vector profiling_results; +}; + +#endif // SHMUP_HPP diff --git a/application/src/sandbox.cpp b/application/src/sandbox.cpp index 55ae25f..a03eb83 100644 --- a/application/src/sandbox.cpp +++ b/application/src/sandbox.cpp @@ -1,20 +1,26 @@ +#include "open_engine/ref_scope.hpp" #include #include #include #include +#include #include +#include #include Sandbox::Sandbox() { - OpenEngine::Ref initial_layer = std::make_shared(); - OpenEngine::Ref control_layer = std::make_shared(initial_layer); + //OpenEngine::Ref initial_layer = std::make_shared(); + OpenEngine::Ref overlay = OpenEngine::CreateRef(); + OpenEngine::Ref shmup = OpenEngine::CreateRef(overlay); + OpenEngine::Ref control_layer = std::make_shared(shmup); OpenEngine::Ref modding_layer = std::make_shared(); + QueueLayerPush(shmup); QueueLayerPush(modding_layer); QueueLayerPush(control_layer); - QueueLayerPush(initial_layer); + QueueOverlayPush(overlay); } Sandbox::~Sandbox()