diff --git a/CMakeLists.txt b/CMakeLists.txt index 19440bf..33062ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,9 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(SOURCE_FILES src/game/main.cpp src/game/gui.cpp + src/game/player.cpp + src/game/enemy.cpp src/game/world_gen/map.cpp - src/engine/utils/logging.cpp src/engine/Renderer.cpp src/engine/Camera.cpp src/engine/ecs/ecs.cpp @@ -25,16 +26,19 @@ set(SOURCE_FILES src/engine/ecs/systemmanager.cpp src/engine/AssetLoader.cpp src/engine/Input.cpp + src/engine/Prefs.cpp src/engine/scene/Scene.cpp src/engine/scene/Node.cpp src/engine/scene/AssetManifest.cpp src/engine/graphics/Pipeline.cpp src/engine/graphics/Image.cpp src/engine/graphics/Sampler.cpp + src/engine/graphics/GPURingBuffer.cpp + src/engine/Game.cpp vendor/glad/src/glad.c ) -set(COMMON_COMPILE_FLAGS -Wall -Wextra -Wunused-result -Wno-missing-field-initializers -Wno-unused-function -fno-exceptions) +set(COMMON_COMPILE_FLAGS -Wall -Wextra -Wunused-result -Wno-missing-field-initializers -Wno-unused-function -fno-exceptions -g) FetchContent_Declare( glm @@ -54,7 +58,13 @@ FetchContent_Declare( GIT_TAG 3.4 ) -FetchContent_MakeAvailable(glm glfw imgui) +FetchContent_Declare( + miniaudio + GIT_REPOSITORY https://github.com/mackron/miniaudio + GIT_TAG 0.11.22 +) + +FetchContent_MakeAvailable(glm glfw imgui miniaudio) add_executable(game_engine ${SOURCE_FILES}) set_target_properties(game_engine PROPERTIES @@ -92,5 +102,9 @@ target_sources(game_engine PRIVATE ${imgui_SOURCE_DIR}/backends/imgui_impl_openg # GLAD target_include_directories(game_engine PRIVATE vendor/glad/include) +# miniaudio +target_include_directories(game_engine PRIVATE ${miniaudio_SOURCE_DIR}) +target_link_libraries(game_engine PRIVATE miniaudio) + target_link_libraries(game_engine PRIVATE ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}) target_compile_options(game_engine PRIVATE ${COMMON_COMPILE_FLAGS}) diff --git a/README.md b/README.md new file mode 100644 index 0000000..91961c9 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +## Steps: +1. Download assets from [OneDrive](https://kth-my.sharepoint.com/:f:/g/personal/oscae_ug_kth_se/EkvNSq58i9VEh5Iqsg_l-UIB83xngGy-dJDav7ChcgHiJQ?e=J3qyYU) and put the assets directory in root. +2. Build asset_processor: +```bash +cd tools/asset_processor +cmake . -GNinja -Bbuild +cd build +ninja +cd ../../.. +./tools/asset_processor/build/asset_processor manifest.json +``` +3. Initial build: +```bash +cmake . -GNinja -Bbuild +cd build +ninja +cd .. +``` +4. Build and Run +```bash +cd build; ninja; cd ..; ./build/game_engine +``` + +### Note: +Build with clang, NOT MinGW: +```bash +CMAKE . -GNinja -B build -DCMAKE_CXX_COMPILER=clang -DCMAKE_C_COMPILER=clang +``` diff --git a/manifest.json b/manifest.json index 1467b86..4f5e86f 100644 --- a/manifest.json +++ b/manifest.json @@ -2,11 +2,7 @@ "meshes": [ { "name": "Helmet", - "path": "assets/SciFiHelmet/glTF/SciFiHelmet.gltf" - }, - { - "name": "Sponza", - "path": "assets/Sponza/Sponza.gltf" + "path": "assets/SciFiHelmet/SciFiHelmet.glb" }, { "name": "Sword", @@ -15,11 +11,14 @@ { "name": "Cube", "path": "assets/cube.glb" + }, + { + "name": "Ghost", + "path": "assets/ghost.glb" } ], "prefabs": [ "prefabs/Player.json", - "prefabs/Scene.json", - "prefabs/Enemy.json" + "prefabs/Ghost.json" ] } \ No newline at end of file diff --git a/prefabs/Enemy.json b/prefabs/Ghost.json similarity index 78% rename from prefabs/Enemy.json rename to prefabs/Ghost.json index 3fc6f49..cd1a29e 100644 --- a/prefabs/Enemy.json +++ b/prefabs/Ghost.json @@ -1,26 +1,26 @@ -{ - "name": "Enemy", - "nodes": [ - { - "mesh": "Helmet", - "name": "Enemy", - "rotation": [ - 0.0, - 1.0, - 0.0, - 0.0 - ], - "scale": [ - 1.0, - 1.0, - 1.0 - ], - "translation": [ - 0.0, - 0.0, - 0.0 - ] - } - ], - "root": 0 +{ + "name": "Ghost", + "nodes": [ + { + "mesh": "Ghost", + "name": "Ghost", + "rotation": [ + 0.0, + 1.0, + 0.0, + 0.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + } + ], + "root": 0 } \ No newline at end of file diff --git a/shaders/SPIRV/rect.frag.spv b/shaders/SPIRV/rect.frag.spv new file mode 100644 index 0000000..fcf8ded Binary files /dev/null and b/shaders/SPIRV/rect.frag.spv differ diff --git a/shaders/SPIRV/rect.vert.spv b/shaders/SPIRV/rect.vert.spv new file mode 100644 index 0000000..153737c Binary files /dev/null and b/shaders/SPIRV/rect.vert.spv differ diff --git a/shaders/rect.frag.glsl b/shaders/rect.frag.glsl new file mode 100644 index 0000000..dd59bbc --- /dev/null +++ b/shaders/rect.frag.glsl @@ -0,0 +1,8 @@ +#version 460 core + +layout (location = 0) in vec4 color; +layout (location = 0) out vec4 frag_color; + +void main() { + frag_color = color; +} \ No newline at end of file diff --git a/shaders/rect.vert.glsl b/shaders/rect.vert.glsl new file mode 100644 index 0000000..fa564d6 --- /dev/null +++ b/shaders/rect.vert.glsl @@ -0,0 +1,17 @@ +#version 460 core + +layout (location = 0) in vec2 a_pos; +layout (location = 1) in vec4 a_color; + +layout (location = 0) out vec4 color; + +layout (std140, binding = 0) uniform UBOMatrices { + mat4 model; + mat4 view; + mat4 projection; +}; + +void main() { + gl_Position = projection * vec4(a_pos, 0.0, 1.0); + color = a_color; +} \ No newline at end of file diff --git a/src/engine/Game.cpp b/src/engine/Game.cpp new file mode 100644 index 0000000..5e9d734 --- /dev/null +++ b/src/engine/Game.cpp @@ -0,0 +1,64 @@ +#include "Game.h" + +#include "utils/logging.h" + +static void error_callback(int error, const char *description) { + ERROR("GLFW error with code {}: {}", error, description); +} + +static void framebuffer_size_callback(GLFWwindow *window, int width, int height) { + (void)window; + glViewport(0, 0, width, height); +} + +game::GameHandle game::init(int width, int height, const std::string& title) +{ + if (!glfwInit()) { + FATAL("Failed to initiliaze glfw"); + } + INFO("Initialized GLFW"); + glfwSetErrorCallback(error_callback); + + f32 content_x_scale; + f32 content_y_scale; + glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &content_x_scale, + &content_y_scale); + INFO("Monitor content scale is ({}, {})", content_x_scale, content_y_scale); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); + glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); + glfwWindowHint(GLFW_SAMPLES, 8); +#ifndef NDEBUG + glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE); +#else + glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE); +#endif + + f32 content_scale = std::max(content_x_scale, content_y_scale); + auto window = glfwCreateWindow(1920 / content_scale, 1080 / content_scale, + title.c_str(), nullptr, nullptr); + if (!window) { + FATAL("Failed to create glfw window"); + } + + glfwMakeContextCurrent(window); + + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + return game::GameHandle { + .window = window, + }; +} + +bool game::should_run(GameHandle& handle) +{ + return !glfwWindowShouldClose(handle.window); +} + +void game::end_frame(GameHandle& handle) +{ + glfwSwapBuffers(handle.window); + glfwPollEvents(); +} diff --git a/src/engine/Game.h b/src/engine/Game.h new file mode 100644 index 0000000..1b0a50b --- /dev/null +++ b/src/engine/Game.h @@ -0,0 +1,16 @@ +#include + +#include +#include +#include "core.h" + +namespace game { + struct GameHandle { + GLFWwindow* window; + }; + + GameHandle init(int width, int height, const std::string& title); + + bool should_run(GameHandle& handle); + void end_frame(GameHandle& handle); +} \ No newline at end of file diff --git a/src/engine/Input.cpp b/src/engine/Input.cpp index bc88dd2..163950b 100644 --- a/src/engine/Input.cpp +++ b/src/engine/Input.cpp @@ -1,4 +1,5 @@ #include "Input.h" +#include engine::Input::Input(GLFWwindow* window) : window{window}, prevKeyStates{false}, prevMousePosition(get_mouse_position()) {} diff --git a/src/engine/Prefs.cpp b/src/engine/Prefs.cpp new file mode 100644 index 0000000..78f5f5a --- /dev/null +++ b/src/engine/Prefs.cpp @@ -0,0 +1,311 @@ +#include "Prefs.h" + +#include +#include +#include + +<<<<<<< HEAD +#include "utils/logging.h" + +======= +>>>>>>> 5b032684b60b1180c7e1fa78da916c4ed416136c +Prefs::Prefs(const std::string &path) : prefs_path(path) { + std::ifstream file(path); + if (!file) { + // Create new prefs file + std::ofstream new_file(path); + if (!new_file) { + ERROR("Failed to create prefs file"); + } + new_file.close(); + } else { + // Load existing prefs from file + std::string line; + while (std::getline(file, line)) { + std::istringstream line_stream(line); + std::string key, value, type; + if (std::getline(line_stream, key, ' ') && std::getline(line_stream, type, ' ') && + std::getline(line_stream, value)) { + PrefValueType value_type; + + // parse type + if (type == "int") + value_type = PrefValueType::INT; + else if (type == "float") + value_type = PrefValueType::FLOAT; + else if (type == "bool") + value_type = PrefValueType::BOOL; + else if (type == "string") + value_type = PrefValueType::STRING; + else if (type == "vec2") + value_type = PrefValueType::VEC2; + else if (type == "vec3") + value_type = PrefValueType::VEC3; + else if (type == "vec4") + value_type = PrefValueType::VEC4; + else { + WARN("Unknown type %s for prefs entry %s", type.c_str(), key.c_str()); + continue; + } + + // save to internal storage + internal_storage[key.c_str()] = PrefEntry{.type = value_type, .value = value}; + } + } + } +} + +Prefs::~Prefs() { flush(); } + +bool Prefs::contains(const std::string &key) { return internal_storage.find(key) != internal_storage.end(); } + +void Prefs::remove(const std::string &key) { internal_storage.erase(key); } + +void Prefs::clear() { internal_storage.clear(); } + +void Prefs::flush() { + std::ofstream file(this->prefs_path); + if (!file) + return; + + for (const auto &pair : internal_storage) { + // parse type + std::string type; + switch (pair.second.type) { + case PrefValueType::INT: + type = "int"; + break; + case PrefValueType::FLOAT: + type = "float"; + break; + case PrefValueType::BOOL: + type = "bool"; + break; + case PrefValueType::STRING: + type = "string"; + break; + case PrefValueType::VEC2: + type = "vec2"; + break; + case PrefValueType::VEC3: + type = "vec3"; + break; + case PrefValueType::VEC4: + type = "vec4"; + break; + } + + file << pair.first << ' ' << type << ' ' << pair.second.value << '\n'; + } + + file.close(); +} + +inline bool validate_key(const std::string &key) { + if (key.empty()) { + ERROR("Key cannot be empty"); + return false; + } + + if (key.find(' ') != std::string::npos) { + ERROR("Key cannot contain spaces"); + return false; + } + + return true; +} + +void Prefs::put_int(const std::string &key, int value) { + if (!validate_key(key)) + return; + + internal_storage[key] = PrefEntry{.type = PrefValueType::INT, .value = std::to_string(value)}; +} + +void Prefs::put_float(const std::string &key, float value) { + if (!validate_key(key)) + return; + + internal_storage[key] = PrefEntry{.type = PrefValueType::FLOAT, .value = std::to_string(value)}; +} + +void Prefs::put_bool(const std::string &key, bool value) { + if (!validate_key(key)) + return; + + internal_storage[key] = PrefEntry{.type = PrefValueType::BOOL, .value = value ? "true" : "false"}; +} + +void Prefs::put_string(const std::string &key, const std::string &value) { + if (!validate_key(key)) + return; + + internal_storage[key] = PrefEntry{.type = PrefValueType::STRING, .value = value}; +} + +void Prefs::put_vec2(const std::string &key, const glm::vec2 &value) { + if (!validate_key(key)) + return; + + internal_storage[key] = + PrefEntry{.type = PrefValueType::VEC2, .value = std::to_string(value.x) + ',' + std::to_string(value.y)}; +} + +void Prefs::put_vec3(const std::string &key, const glm::vec3 &value) { + if (!validate_key(key)) + return; + + internal_storage[key] = + PrefEntry{.type = PrefValueType::VEC3, + .value = std::to_string(value.x) + ',' + std::to_string(value.y) + ',' + std::to_string(value.z)}; +} + +void Prefs::put_vec4(const std::string &key, const glm::vec4 &value) { + if (!validate_key(key)) + return; + + internal_storage[key] = PrefEntry{.type = PrefValueType::VEC4, + .value = std::to_string(value.x) + ',' + std::to_string(value.y) + ',' + + std::to_string(value.z) + ',' + std::to_string(value.w)}; +} + +int Prefs::get_int(const std::string &key) { + if (!validate_key(key)) + return 0; + + return std::stoi(internal_storage[key].value); +} + +float Prefs::get_float(const std::string &key) { + if (!validate_key(key)) + return 0.f; + + return std::stof(internal_storage[key].value); +} + +bool Prefs::get_bool(const std::string &key) { + if (!validate_key(key)) + return false; + + return internal_storage[key].value == "true"; +} + +std::string Prefs::get_string(const std::string &key) { + if (!validate_key(key)) +<<<<<<< HEAD + return ""; +======= + return; +>>>>>>> 5b032684b60b1180c7e1fa78da916c4ed416136c + + return internal_storage[key].value; +} + +glm::vec2 Prefs::get_vec2(const std::string &key) { + if (!validate_key(key)) + return glm::vec2(0.f); + + std::istringstream iss(internal_storage[key].value); + std::string x_str, y_str; + std::getline(iss, x_str, ','); + std::getline(iss, y_str, ','); + + return glm::vec2(std::stof(x_str), std::stof(y_str)); +} + +glm::vec3 Prefs::get_vec3(const std::string &key) { + if (!validate_key(key)) + return glm::vec3(0.f); + + std::istringstream iss(internal_storage[key].value); + std::string x_str, y_str, z_str; + std::getline(iss, x_str, ','); + std::getline(iss, y_str, ','); + std::getline(iss, z_str, ','); + + return glm::vec3(std::stof(x_str), std::stof(y_str), std::stof(z_str)); +} + +glm::vec4 Prefs::get_vec4(const std::string &key) { + if (!validate_key(key)) + return glm::vec4(0.f); + + std::istringstream iss(internal_storage[key].value); + std::string x_str, y_str, z_str, w_str; + std::getline(iss, x_str, ','); + std::getline(iss, y_str, ','); + std::getline(iss, z_str, ','); + std::getline(iss, w_str, ','); + + return glm::vec4(std::stof(x_str), std::stof(y_str), std::stof(z_str), std::stof(w_str)); +} + +int Prefs::get_int_or(const std::string &key, int fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::INT) + return fallback; + + return get_int(key); +} + +float Prefs::get_float_or(const std::string &key, float fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::FLOAT) + return fallback; + + return get_float(key); +} + +bool Prefs::get_bool_or(const std::string &key, bool fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::BOOL) + return fallback; + + return get_bool(key); +} + +std::string Prefs::get_string_or(const std::string &key, const std::string &fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::STRING) + return fallback; + + return get_string(key); +} + +glm::vec2 Prefs::get_vec2_or(const std::string &key, const glm::vec2 &fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::VEC2) + return fallback; + + return get_vec2(key); +} + +glm::vec3 Prefs::get_vec3_or(const std::string &key, const glm::vec3 &fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::VEC3) + return fallback; + + return get_vec3(key); +} + +glm::vec4 Prefs::get_vec4_or(const std::string &key, const glm::vec4 &fallback) { + if (!validate_key(key) || !contains(key)) + return fallback; + + if (internal_storage[key].type != PrefValueType::VEC4) + return fallback; + + return get_vec4(key); +} \ No newline at end of file diff --git a/src/engine/Prefs.h b/src/engine/Prefs.h new file mode 100644 index 0000000..bff73c7 --- /dev/null +++ b/src/engine/Prefs.h @@ -0,0 +1,63 @@ +#pragma once + +<<<<<<< HEAD +#include +======= +#include "logging.h" +#include +#include "utils/logging.h" +>>>>>>> 5b032684b60b1180c7e1fa78da916c4ed416136c +#include +#include + +enum class PrefValueType { INT, FLOAT, BOOL, STRING, VEC2, VEC3, VEC4 }; + +struct PrefEntry { + PrefValueType type; + std::string value; +}; + +class Prefs { + public: + Prefs() = delete; + Prefs(const std::string &path); + ~Prefs(); + + bool contains(const std::string &key); + void remove(const std::string &key); + void clear(); + + // save all prefs to the file + void flush(); + + // write to prefs + void put_int(const std::string &key, int value); + void put_float(const std::string &key, float value); + void put_bool(const std::string &key, bool value); + void put_string(const std::string &key, const std::string &value); + void put_vec2(const std::string &key, const glm::vec2 &value); + void put_vec3(const std::string &key, const glm::vec3 &value); + void put_vec4(const std::string &key, const glm::vec4 &value); + + // get prefs (unsafe) + int get_int(const std::string &key); + float get_float(const std::string &key); + bool get_bool(const std::string &key); + std::string get_string(const std::string &key); + glm::vec2 get_vec2(const std::string &key); + glm::vec3 get_vec3(const std::string &key); + glm::vec4 get_vec4(const std::string &key); + + // get prefs with fallback value + int get_int_or(const std::string &key, int fallback); + float get_float_or(const std::string &key, float fallback); + bool get_bool_or(const std::string &key, bool fallback); + std::string get_string_or(const std::string &key, const std::string &fallback); + glm::vec2 get_vec2_or(const std::string &key, const glm::vec2 &fallback); + glm::vec3 get_vec3_or(const std::string &key, const glm::vec3 &fallback); + glm::vec4 get_vec4_or(const std::string &key, const glm::vec4 &fallback); + + private: + std::string prefs_path; + std::unordered_map internal_storage; +}; diff --git a/src/engine/Renderer.cpp b/src/engine/Renderer.cpp index 984650e..f9d8271 100644 --- a/src/engine/Renderer.cpp +++ b/src/engine/Renderer.cpp @@ -12,7 +12,10 @@ #include #include "AssetLoader.h" +#include "engine/graphics/Pipeline.h" +#include "glm/ext/matrix_clip_space.hpp" #include "glm/fwd.hpp" +#include "graphics/GPURingBuffer.h" #include "graphics/Image.h" #include "graphics/Pipeline.h" #include "graphics/Sampler.h" @@ -72,13 +75,14 @@ void Renderer::init(LoadProc load_proc) { create_ubos(); generate_offline_content(); create_skybox(); + create_rect_pass(); INFO("Initialized renderer"); } f32 Renderer::get_max_texture_filtering_level() const { return m_max_texture_filtering; } // FIXME: Hack -void Renderer::set_texture_filtering_level(Scene& scene, f32 level) { +void Renderer::set_texture_filtering_level(Scene &scene, f32 level) { for (size_t i = 0; i < scene.m_samplers.size(); ++i) { glSamplerParameterf(scene.m_samplers[i].m_handle, GL_TEXTURE_MAX_ANISOTROPY, level); } @@ -136,6 +140,8 @@ void Renderer::begin_pass(const Scene &scene, const Camera &camera, u32 width, u glBindTextureUnit(7, m_offline_images.prefiltered_cubemap.m_handle); glEnable(GL_FRAMEBUFFER_SRGB); + glEnable(GL_DEPTH_TEST); + glDisable(GL_BLEND); f32 aspect_ratio = (f32)width / (f32)height; UBOMatrices matrices; @@ -211,17 +217,16 @@ void Renderer::draw_mesh(u32 mesh_handle, const glm::mat4 &transform) { auto num_indices = prim.num_indices(); u64 byte_offset = prim.indices_start; - + if (material.is_double_sided) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } glDrawElementsBaseVertex(GL_TRIANGLES, num_indices, prim.index_type, (void *)byte_offset, prim.base_vertex); } } -void Renderer::update_light_positions(u32 index, glm::vec4 pos) { - glNamedBufferSubData(m_ubo_light_positions, sizeof(glm::vec4) * index, sizeof(glm::vec4), - glm::value_ptr(pos)); -} - void Renderer::create_ubos() { { glCreateBuffers(1, &m_ubo_matrices_handle); @@ -283,6 +288,71 @@ void Renderer::draw_skybox() { glDepthFunc(GL_LESS); } +void Renderer::create_rect_pass() { + m_rect_pass.capacity = 1024; + m_rect_pass.current_count = 0; + + m_rect_pass.pipeline.init(); + m_rect_pass.vertex_buffer.init(sizeof(RectVertex) * m_rect_pass.capacity * 6, 3); + + std::array attribs = { + VertexAttributeDescriptor{.type = VertexAttributeDescriptor::Type::f32, + .count = 2, + .offset = offsetof(RectVertex, pos)}, + VertexAttributeDescriptor{.type = VertexAttributeDescriptor::Type::f32, + .count = 4, + .offset = offsetof(RectVertex, color)}}; + m_rect_pass.pipeline.add_vertex_buffer_from_buffer(attribs, sizeof(RectVertex), + m_rect_pass.vertex_buffer.m_handle); + m_rect_pass.pipeline.add_vertex_shader("shaders/SPIRV/rect.vert.spv"); + m_rect_pass.pipeline.add_fragment_shader("shaders/SPIRV/rect.frag.spv"); + m_rect_pass.pipeline.compile(); +} + +void Renderer::begin_rect_pass() { + glDisable(GL_FRAMEBUFFER_SRGB); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + m_rect_pass.pipeline.bind(); + m_rect_pass.vertex_buffer.new_frame(); +} + +void Renderer::draw_rect(const Rect &rect, const glm::vec4 &color) { + if (m_rect_pass.current_count + 1 > m_rect_pass.capacity) { + ERROR("Cannot draw anymore rectangles. Rectangle pass vertex buffer is full!"); + return; + } + std::array vertices = { + RectVertex{.pos = {rect.x, rect.y}, .color = {color.x, color.y, color.z, color.w}}, + RectVertex{.pos = {rect.x + rect.width, rect.y}, + .color = {color.x, color.y, color.z, color.w}}, + RectVertex{.pos = {rect.x + rect.width, rect.y + rect.height}, + .color = {color.x, color.y, color.z, color.w}}, + + RectVertex{.pos = {rect.x, rect.y}, .color = {color.x, color.y, color.z, color.w}}, + RectVertex{.pos = {rect.x + rect.width, rect.y + rect.height}, + .color = {color.x, color.y, color.z, color.w}}, + RectVertex{.pos = {rect.x, rect.y + rect.height}, + .color = {color.x, color.y, color.z, color.w}}, + }; + m_rect_pass.vertex_buffer.write( + std::span((u8 *)vertices.data(), vertices.size() * sizeof(RectVertex))); + ++m_rect_pass.current_count; +} + +void Renderer::end_rect_pass(u32 fb_width, u32 fb_height) { + u64 offset = m_rect_pass.vertex_buffer.get_current_region_offset(); + assert(offset % sizeof(RectVertex) == 0); + u64 first = offset / sizeof(RectVertex); + + auto proj = glm::ortho(0.0f, (f32)fb_width, (f32)fb_height, 0.0f); + glNamedBufferSubData(m_ubo_matrices_handle, 2 * sizeof(glm::mat4), sizeof(glm::mat4), + glm::value_ptr(proj)); + + glDrawArrays(GL_TRIANGLES, first, m_rect_pass.current_count * 6); + m_rect_pass.current_count = 0; +} + void Renderer::draw_node(const NodeHierarchy &hierarchy, u32 node_index, const glm::mat4 &parent_transform) { const auto &node = hierarchy.m_nodes[node_index]; diff --git a/src/engine/Renderer.h b/src/engine/Renderer.h index 84b2863..61d9e83 100644 --- a/src/engine/Renderer.h +++ b/src/engine/Renderer.h @@ -5,6 +5,7 @@ #include "Camera.h" #include "core.h" +#include "graphics/GPURingBuffer.h" #include "graphics/Image.h" #include "graphics/Pipeline.h" #include "graphics/Sampler.h" @@ -15,14 +16,20 @@ namespace engine { class Renderer { public: - friend class Scene; + struct Rect { + f32 x; + f32 y; + f32 width; + f32 height; + }; + typedef void *(*LoadProc)(const char *name); void init(LoadProc load_proc); void make_resources_for_scene(const loader::AssetFileData &scene); // Will go away. - void set_texture_filtering_level(Scene& scene, f32 level); + void set_texture_filtering_level(Scene &scene, f32 level); f32 get_max_texture_filtering_level() const; void clear(); @@ -31,8 +38,10 @@ class Renderer { void draw_mesh(u32 mesh_handle, const glm::mat4 &transform); void draw_hierarchy(const Scene &scene, const NodeHierarchy &hierarchy); - // Temp - void update_light_positions(u32 index, glm::vec4 pos); + + void begin_rect_pass(); + void draw_rect(const Rect &rect, const glm::vec4& color); + void end_rect_pass(u32 fb_width, u32 fb_height); private: struct GeneratedImages { @@ -45,6 +54,7 @@ class Renderer { u32 load_shader(const char *path, u32 shader_type); void create_textures(const Scene &scene); void create_ubos(); + void create_rect_pass(); // Things we might want to move to a offline baking process. void generate_offline_content(); @@ -72,6 +82,11 @@ class Renderer { f32 m_max_texture_filtering; Sampler m_default_sampler; + struct RectVertex { + f32 pos[2]; + f32 color[4]; + }; + struct Pass { const Scene *scene; glm::mat4 projection_matrix; @@ -79,6 +94,13 @@ class Renderer { glm::vec3 camera_pos; }; + struct RectPass { + Pipeline pipeline; + GPURingBuffer vertex_buffer; + u32 capacity; + u32 current_count; + }; + struct Skybox { Pipeline pipeline; Image skybox_image; @@ -100,6 +122,7 @@ class Renderer { }; Pass m_curr_pass; + RectPass m_rect_pass; u32 m_ubo_material_handle; u32 m_ubo_matrices_handle; diff --git a/src/engine/audio/audio.cpp b/src/engine/audio/audio.cpp new file mode 100644 index 0000000..c3be4ba --- /dev/null +++ b/src/engine/audio/audio.cpp @@ -0,0 +1,2 @@ +#include "engine/audio/audio.h" + diff --git a/src/engine/audio/audio.h b/src/engine/audio/audio.h new file mode 100644 index 0000000..f4391c4 --- /dev/null +++ b/src/engine/audio/audio.h @@ -0,0 +1,103 @@ +#pragma once +#include + +#include "engine/Camera.h" +#include "engine/audio/listener/listener.h" +#include "engine/ecs/entity.hpp" +#include "engine/ecs/entityarray.hpp" +#include "engine/ecs/resource.hpp" +#include "engine/ecs/system.hpp" +#include "engine/ecs/component.hpp" +#include "engine/ecs/ecs.hpp" + +#include "engine/audio/source/source.h" +#include "engine/utils/logging.h" +#include "engine/audio/listener/device.h" + +class SAudioManager : public System { + private: + AudioListener listener; + Entity m_sources = ECS::create_entity(); + u32 active_sources = 0; + public: + SAudioManager() { + ECS::register_component(); + + queries[0] = Query(CAudioSource::get_id()); + query_count = 1; + } + + void add_source(CAudioSource &source) { + ECS::add_component(m_sources, source); + active_sources += 1; + } + + void play_sources(SOUND_BUF_TYPE * output, engine::Camera &camera, u32 frame_count) { + memset(output, 0, frame_count * CHANNELS); + + auto * entities = get_query(0)->get_entities(); + Iterator it = {.next = 0}; + Entity entity; + + float scale = 1.0f / active_sources; + active_sources = 0; + + glm::mat4 view_mat = camera.get_view_matrix(); + listener.set_position(glm::vec3(view_mat[3][0], view_mat[3][1], view_mat[3][2])); + listener.set_up(glm::vec3(view_mat[1][0], view_mat[1][1], view_mat[1][2])); + listener.set_forward(glm::vec3(view_mat[2][0], view_mat[2][1], view_mat[2][2])); + + while (entities->next(it, entity)) { + CAudioSource &source = ECS::get_component(entity); + source.play(listener, output, frame_count, scale); + active_sources += source.get_state() == SoundState::Playing; + } + } +}; + +static void data_callback(ma_device * pDevice, void * pOutput, const void * _, ma_uint32 frame_count); + +class RAudio : public Resource { + private: + ma_context context; + + CAudioDevice device; + engine::Camera &camera; + SAudioManager * manager = ECS::register_system(); + public: + RAudio(engine::Camera &camera) + : camera(camera) + { + if (ma_context_init(NULL, 0, NULL, &this->context) != MA_SUCCESS) { + FATAL("Unable to initialize miniaudio context"); + } + + ma_device_info default_device = get_default_device(get_available_devices(&this->context)); + this->device = CAudioDevice(&this->context, default_device, &data_callback); + } + + void set_device(ma_device_info device_info) { + this->device.deinit(); + this->device = CAudioDevice(&this->context, device_info, &data_callback); + } + + static void add_source(CAudioSource source) { + ECS::get_resource()->manager->add_source(source); + } + + void play_audio_callback(SOUND_BUF_TYPE * output, u32 frame_count) { + if (!this->device.is_playing) { + return; + } + + manager->play_sources(output, this->camera, frame_count); + } +}; + +static void data_callback(ma_device * _device, void * pOutput, const void * _input, ma_uint32 frame_count) { + RAudio * audio = ECS::get_resource(); + + if (audio != nullptr) { + audio->play_audio_callback((SOUND_BUF_TYPE *) pOutput, frame_count); + } +} diff --git a/src/engine/audio/constants.h b/src/engine/audio/constants.h new file mode 100644 index 0000000..b9c1b2c --- /dev/null +++ b/src/engine/audio/constants.h @@ -0,0 +1,22 @@ +#pragma once + +#define SAMPLE_RATE 44100 +#define CHANNELS 2 + +#define MAX_SOUND_BUF_SIZE 32 * 8 * 1024 // 50KB +#define SOUND_BUF_TYPE i16 +#define PCMS16LE // remove this if SOUND_BUF_TYPE is no longer i16 + +#define AUDIO_VOLUME_CUTOFF 2.5f + +#define CLAMP_VALUE_MAX_MIN(VALUE, MIN, MAX) { \ + if (MAX < VALUE) VALUE = MAX; \ + else if (VALUE < MIN) VALUE = MIN; \ +} +#define CLAMP_VALUE_BY_TYPE(VALUE, TYPE) CLAMP_VALUE_MAX_MIN(VALUE, TYPE##_MIN, TYPE##_MAX) + +#ifdef PCMS16LE +#define CLAMP_VALUE(VALUE) CLAMP_VALUE_BY_TYPE(VALUE, INT16) +#else +static_assert(0, "CLAMP_VALUE not implemented"); +#endif diff --git a/src/engine/audio/listener/device.h b/src/engine/audio/listener/device.h new file mode 100644 index 0000000..a5ec1a3 --- /dev/null +++ b/src/engine/audio/listener/device.h @@ -0,0 +1,107 @@ +#pragma once +#include +#include +#include "engine/audio/source/source.h" +#include "engine/ecs/component.hpp" +#include "engine/ecs/ecs.hpp" +#include "engine/utils/logging.h" + +class CMADevice : public Component { + public: + ma_device device; +}; + +class CMADeviceInfo : public Component { + public: + ma_device_info info; + public: + CMADeviceInfo() {}; + CMADeviceInfo(ma_device_info info) : info(info) {}; +}; + +class CAudioDevice : public Component { + private: + Entity m_info = ECS::create_entity(); + Entity m_device = ECS::create_entity(); + + public: + bool is_playing; + public: + CAudioDevice() {}; + CAudioDevice(ma_context * context, ma_device_info info, ma_device_data_proc callback) { + is_playing = true; + + ECS::register_component(); + ECS::register_component(); + + ECS::add_component(m_info, CMADeviceInfo(info)); + initialize_ma_device(context, callback); + } + + void deinit() { + auto &device = ECS::get_component(m_device); + ma_device_stop(&device.device); + ma_device_uninit(&device.device); + } + + void toggle_play() { + is_playing ^= true; + + auto &device = ECS::get_component(m_device); + if (is_playing) { + assert(ma_device_start(&device.device) == MA_SUCCESS); + } else { + assert(ma_device_stop(&device.device) == MA_SUCCESS); + } + } + private: + void initialize_ma_device(ma_context * context, ma_device_data_proc callback) { + auto info = ECS::get_component(m_info); + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = &info.info.id; + config.playback.format = ma_format_s16; + config.playback.channels = CHANNELS; + config.sampleRate = SAMPLE_RATE; + config.dataCallback = callback; + + ECS::add_component(m_device, CMADevice()); + auto &device = ECS::get_component(m_device); + + if (ma_device_init(context, &config, &device.device) != MA_SUCCESS) { + ma_device_uninit(&device.device); + FATAL("An error occured while initializing device \"{}\"", info.info.name); + } + + if (is_playing) { + assert(ma_device_start(&device.device) == MA_SUCCESS); + } + } +}; + +struct device_list { + ma_device_info * devices; + uint32_t device_count; +}; + +static struct device_list get_available_devices(ma_context * context) { + struct device_list list; + + if (ma_context_get_devices(context, &list.devices, &list.device_count, NULL, NULL) != MA_SUCCESS) { + puts("An error occured trying to retrieve devices"); + } + + return list; +} + +static ma_device_info get_default_device(struct device_list list) { + const uint32_t list_length = list.device_count; + for (uint32_t i = 0; i < list_length; ++i) { + if (list.devices[i].isDefault) { + return list.devices[i]; + } + } + + puts("There was no default audio device?!!?"); + exit(1); +} diff --git a/src/engine/audio/listener/listener.h b/src/engine/audio/listener/listener.h new file mode 100644 index 0000000..82b2187 --- /dev/null +++ b/src/engine/audio/listener/listener.h @@ -0,0 +1,47 @@ +#pragma once + +#include "engine/Camera.h" +#include "engine/audio/constants.h" +#include "glm/geometric.hpp" +#include + +class AudioListener { + private: + glm::vec3 position; + glm::vec3 forward; + glm::vec3 up; + public: + float pans[CHANNELS]; // [0]: Left, [1]: Right + float volume; + public: + AudioListener() {} + + void update(glm::vec3 source_position, float volume) { + glm::vec3 direction = source_position - position; + float distance = direction.length(); + glm::vec3 direction_norm = glm::normalize(direction); + + this->volume = volume / distance; + if (this->volume < AUDIO_VOLUME_CUTOFF) { + this->volume = 0.0f; + } + + glm::vec3 listener_right = glm::normalize(glm::cross(forward, up)); + float pan = glm::dot(direction_norm, listener_right); + + pans[0] = 0.5f - pan * 0.5f; + pans[1] = 0.5f + pan * 0.5f; + } + + void set_position(glm::vec3 position) { + this->position = position; + } + + void set_forward(glm::vec3 forward) { + this->forward = forward; + } + + void set_up(glm::vec3 up) { + this->up = up; + } +}; diff --git a/src/engine/audio/source/reverb.h b/src/engine/audio/source/reverb.h new file mode 100644 index 0000000..20d6daf --- /dev/null +++ b/src/engine/audio/source/reverb.h @@ -0,0 +1,101 @@ +#pragma once +#include +#include + +#include "engine/audio/constants.h" +#include "engine/ecs/component.hpp" +#include "engine/utils/logging.h" + +#define DELAY_LINES 4 + +static const i8 HADAMARD_MATRIX[DELAY_LINES][DELAY_LINES] = { + {1, 1, 1, 1}, + {1, -1, 1, -1}, + {1, 1, -1, -1}, + {1, -1, -1, 1}, +}; + +class ReverbEffect { + private: + SOUND_BUF_TYPE * delay_lines[DELAY_LINES]; + u32 delay_lengths[DELAY_LINES]; + u32 delay_indices[DELAY_LINES] = {0}; + SOUND_BUF_TYPE filtered_samples[DELAY_LINES] = {0}; + + float room_size; + float decay; + float damping; + float wet_k; + + bool enabled = true; + public: + ReverbEffect(float room_size, float decay, float damping, float wet_k) + : room_size(room_size), decay(decay), damping(damping), wet_k(wet_k) + { + static_assert(DELAY_LINES <= 4, "Update base delays array after having changed updated DELAY_LINES"); + u32 base_delays[DELAY_LINES] = {29, 37, 47, 59}; + float size_mult = 0.5f + room_size * 4.5f; + + for (size_t i = 0; i < DELAY_LINES; ++i) { + delay_lengths[i] = (SAMPLE_RATE * base_delays[i] * size_mult) / 1000; + + if (SAMPLE_RATE < delay_lengths[i]) { + FATAL("Delay length has overstrided the max delay of 1 second"); + } + + delay_lines[i] = (SOUND_BUF_TYPE *) calloc(delay_lengths[i], sizeof(SOUND_BUF_TYPE)); + } + } + + SOUND_BUF_TYPE process_sample_with_reverb(SOUND_BUF_TYPE sample) { + if (!enabled) { + return sample; + } + +#ifdef PCMS16LE + i32 wet_samples[DELAY_LINES]; + for (size_t i = 0; i < DELAY_LINES; ++i) { + wet_samples[i] = delay_lines[i][delay_indices[i]]; + } + + // Apply damping to each sample + for (size_t i = 0; i < DELAY_LINES; ++i) { + filtered_samples[i] = (1.0f - damping) * wet_samples[i] + damping * filtered_samples[i]; + } + + // Mix samples through Hadamard matrix for FDN + i32 mixed_samples[DELAY_LINES] = {0}; + for (size_t i = 0; i < DELAY_LINES; ++i) { + for (size_t j = 0; j < DELAY_LINES; ++j) { + mixed_samples[i] += HADAMARD_MATRIX[i][j] * filtered_samples[j]; + } + // Normalize for energy preservation + mixed_samples[i] = mixed_samples[i] / sqrtf(DELAY_LINES); + } + + + // Update state + const float feedback_scale = fminf(room_size, 1.0f) * decay; + for (size_t i = 0; i < DELAY_LINES; ++i) { + // Apply feedback gain + i32 feedback = (i32)(mixed_samples[i] * feedback_scale) + (i32)sample; + CLAMP_VALUE(feedback); + + delay_lines[i][delay_indices[i]] = feedback; + delay_indices[i] = (delay_indices[i] + 1) % delay_lengths[i]; + } + + i32 wet_sample = 0; + for (size_t i = 0; i < DELAY_LINES; ++i) { + wet_sample += filtered_samples[i]; + } + + wet_sample /= DELAY_LINES; + CLAMP_VALUE(wet_sample); +#else + static_assert(0, "process_reverb does not support encoding"); +#endif + + return (1.0f - wet_k) * sample + wet_sample * wet_k; + } +}; diff --git a/src/engine/audio/source/sound.h b/src/engine/audio/source/sound.h new file mode 100644 index 0000000..2cbedc5 --- /dev/null +++ b/src/engine/audio/source/sound.h @@ -0,0 +1,56 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "engine/audio/source/wav.h" +#include "engine/core.h" +#include "engine/ecs/component.hpp" +#include "engine/audio/constants.h" +#include "engine/utils/logging.h" + +enum SoundStreamReadType { Append, Overwrite }; + +class CSoundData : public Component { + private: + std::shared_ptr stream = nullptr; + u32 file_data_offset; + u32 size; + public: + CSoundData() {}; + CSoundData(const std::filesystem::path path) { + stream = std::make_shared(path, std::ios_base::binary); + + if (!stream->is_open() || !(*stream)) { + FATAL("Unable to open file: {}", path.string()); + } + + auto wav = WavFile(stream); + this->file_data_offset = wav.data.file_offset; + this->size = wav.data.size; + } + + CSoundData(const CSoundData&) = default; + CSoundData& operator=(CSoundData&&) = default; + CSoundData& operator=(const CSoundData&) = default; + + inline bool read(SOUND_BUF_TYPE * buf, size_t samples_to_read) { + if (stream->eof()) { + FATAL("Read EOF"); + } + + stream->read((char *)buf, samples_to_read * sizeof(SOUND_BUF_TYPE)); + return (u32)stream->gcount() < samples_to_read * sizeof(SOUND_BUF_TYPE); + } + + void set_playback(u32 offset) { + stream->seekg(file_data_offset + offset * CHANNELS, std::ios::beg); + } +}; diff --git a/src/engine/audio/source/source.h b/src/engine/audio/source/source.h new file mode 100644 index 0000000..e79beb4 --- /dev/null +++ b/src/engine/audio/source/source.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include + +#include "engine/audio/listener/listener.h" +#include "engine/audio/source/reverb.h" +#include "engine/audio/source/sound.h" +#include "engine/audio/source/wav.h" +#include "engine/ecs/component.hpp" +#include "engine/ecs/ecs.hpp" +#include "engine/ecs/system.hpp" +#include "engine/core.h" +#include "engine/audio/constants.h" +#include "engine/utils/logging.h" + +enum SoundState { Paused, Playing, Restart, Finished }; + +class CAudioSource : public Component { + private: + CSoundData data; + ReverbEffect reverb = ReverbEffect(1.0, 0.8, 0.8, 0.3); + glm::vec3 * position; + + enum SoundState state; + u8 volume; + public: + CAudioSource() : position(nullptr) {}; + CAudioSource(const std::filesystem::path path, glm::vec3 * position = nullptr) + : position(position), state(SoundState::Playing), volume(100) + { + this->data = CSoundData(path); + } + + void play(AudioListener &listener, SOUND_BUF_TYPE * output, u32 frame_count, float scale) { + switch (this->state) { + case SoundState::Restart: + this->restart(); + // This is meant to fall through + case SoundState::Playing: break; + default: return; + } + + SOUND_BUF_TYPE buf[frame_count * CHANNELS]; + if (this->data.read(buf, frame_count * CHANNELS)) { + this->state = SoundState::Finished; + } + + scale *= std::powf(volume / 100.0f, 2.0f); + + if (position != nullptr) { + listener.update(*this->position, this->volume); + DEBUG("pan: [{}, {}]", listener.pans[0], listener.pans[1]); + scale *= listener.volume / 100.0f; + } + + static_assert(CHANNELS == 2, "We only support stereo sound due to spatializer pan handling"); + for (size_t i = 0; i < frame_count; ++i) { + for (size_t c = 0; c < CHANNELS; ++c) { + SOUND_BUF_TYPE sample = this->reverb.process_sample_with_reverb(buf[i * CHANNELS + c]); + +#ifdef PCMS16LE + i32 mixed = (int32_t)output[i * CHANNELS + c] + (int32_t)sample * scale; + + if (this->position != nullptr) { + mixed *= listener.pans[c]; + } +#else + static_assert(0, "Encoding is not supported"); +#endif + CLAMP_VALUE(mixed); + output[i * CHANNELS + c] += mixed; + } + } + } + + inline SoundState get_state() { + return this->state; + } + + private: + void restart() { + DEBUG("Restarting playback"); + data.set_playback(0); + this->state = SoundState::Playing; + } +}; diff --git a/src/engine/audio/source/wav.h b/src/engine/audio/source/wav.h new file mode 100644 index 0000000..7c7f01d --- /dev/null +++ b/src/engine/audio/source/wav.h @@ -0,0 +1,83 @@ +#pragma once + +#include "engine/core.h" +#include "engine/ecs/component.hpp" +#include "engine/utils/logging.h" +#include +#include +#include +#include +#include +#include + +#define WAV_ENFORCE_VALID_CHUNK_ID(UNKNOWN_ID, ID) if (strncmp(UNKNOWN_ID, ID, 4)) {FATAL("Invalid chunk ID \"{:4}\", should be \"{:4}\"\n", UNKNOWN_ID, ID); } +#define READ_FROM_STREAM(stream, dest) (stream)->read((char *) (&dest), sizeof(dest)) + +struct wav_file_chunk { + char chunk_id[4]; + uint32_t chunk_size; +}; + +struct wav_file_header { + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +}; + +struct wav_file_data { + u32 file_offset; + u32 size; +}; + +class WavFile { + public: + struct wav_file_header header; + struct wav_file_data data; + public: + WavFile(std::shared_ptr &stream) { + struct wav_file_chunk riff_chunk = get_chunk(stream); + WAV_ENFORCE_VALID_CHUNK_ID(riff_chunk.chunk_id, "RIFF"); + + char format[4]; + READ_FROM_STREAM(stream, format); + WAV_ENFORCE_VALID_CHUNK_ID(format, "WAVE"); + + struct wav_file_chunk fmt_chunk = get_chunk(stream); + WAV_ENFORCE_VALID_CHUNK_ID(fmt_chunk.chunk_id, "fmt "); + + READ_FROM_STREAM(stream, header); + + // check that header is correctly formatted + if (!(header.block_align == (header.num_channels * header.bits_per_sample / 8) && + header.byte_rate == (header.sample_rate * header.block_align))) { + FATAL("¿Corrupt? .wav format section"); + } + + if (header.audio_format != 1 && header.bits_per_sample != 16) { + FATAL("We only support pcm_s16le wav files. Fix: ffmpeg -i [file] -c:a pcm_s16le [output]"); + } + + if (fmt_chunk.chunk_size == 18 || fmt_chunk.chunk_size == 40) { + uint16_t extension_size; + READ_FROM_STREAM(stream, extension_size); + stream->seekg(extension_size, std::ios::cur); + } else if (fmt_chunk.chunk_size != 16) { + FATAL("Invalid .wav file"); + } + + struct wav_file_chunk data_chunk = get_chunk(stream); + WAV_ENFORCE_VALID_CHUNK_ID(data_chunk.chunk_id, "data"); + + data.size = data_chunk.chunk_size; + data.file_offset = static_cast(stream->tellg()); + }; + private: + struct wav_file_chunk get_chunk(std::shared_ptr &stream) { + struct wav_file_chunk chunk; + stream->read((char *)&chunk, sizeof(chunk)); + return chunk; + } +}; diff --git a/src/engine/ecs/component.hpp b/src/engine/ecs/component.hpp index b070830..79b9fd9 100644 --- a/src/engine/ecs/component.hpp +++ b/src/engine/ecs/component.hpp @@ -87,7 +87,6 @@ class ComponentManager { template void add_component(Entity entity, T component) { - const u32 component_id = T::get_id(); get_component_array()->set_component(entity, component); } diff --git a/src/engine/ecs/ecs.cpp b/src/engine/ecs/ecs.cpp index 437c072..c7422a1 100644 --- a/src/engine/ecs/ecs.cpp +++ b/src/engine/ecs/ecs.cpp @@ -1,23 +1,8 @@ #include "ecs.hpp" +#include "engine/ecs/entity.hpp" #include "engine/ecs/resource.hpp" -ECS::ECS() { - entity_manager = new EntityManager(); - component_manager = new ComponentManager(); - system_manager = new SystemManager(); - resource_manager = new ResourceManager(); -} - -Entity ECS::create_entity() { - Entity e = entity_manager->create_entity(); - Signature signature = entity_manager->get_signature(e); - system_manager->update_components(e, signature); - return e; -} - -void ECS::destroy_entity(Entity entity) { - entity_manager->destroy_entity(entity); - component_manager->destroy_entity(entity); - system_manager->destroy_entity(entity); -} - +EntityManager ECS::entity_manager = EntityManager(); +ComponentManager ECS::component_manager = ComponentManager(); +SystemManager ECS::system_manager = SystemManager(); +ResourceManager ECS::resource_manager = ResourceManager(); diff --git a/src/engine/ecs/ecs.hpp b/src/engine/ecs/ecs.hpp index c73070f..ba8b49b 100644 --- a/src/engine/ecs/ecs.hpp +++ b/src/engine/ecs/ecs.hpp @@ -1,70 +1,81 @@ #pragma once #include "engine/ecs/resource.hpp" +#include "engine/utils/logging.h" #include "types.h" #include "entity.hpp" #include "component.hpp" #include "systemmanager.hpp" -#include + +// #define ECS_DEBUGGING class ECS { + private: + static EntityManager entity_manager; + static ComponentManager component_manager; + static SystemManager system_manager; + static ResourceManager resource_manager; public: ECS(); - Entity create_entity(); + static Entity create_entity() { + Entity e = entity_manager.create_entity(); + Signature signature = entity_manager.get_signature(e); + system_manager.update_components(e, signature); + return e; + } - void destroy_entity(Entity entity); + static void destroy_entity(Entity entity) { + entity_manager.destroy_entity(entity); + component_manager.destroy_entity(entity); + system_manager.destroy_entity(entity); + } template - void register_component() { - component_manager->register_component(); + static void register_component() { + component_manager.register_component(); } template - void add_component(Entity entity, T component) { - component_manager->add_component(entity, component); - Signature signature = entity_manager->get_signature(entity); - Signature new_signature = set_signature(signature, component_manager->get_component_id()); - entity_manager->set_signature(entity, new_signature); - system_manager->update_components(entity, new_signature); + static void add_component(Entity entity, T component) { + component_manager.add_component(entity, component); + Signature signature = entity_manager.get_signature(entity); + Signature new_signature = set_signature(signature, component_manager.get_component_id()); + entity_manager.set_signature(entity, new_signature); + system_manager.update_components(entity, new_signature); } template - void remove_component(Entity entity) { - component_manager->remove_component(entity); - Signature signature = entity_manager->get_signature(entity); - Signature new_signature = remove_signature(signature, component_manager->get_component_id()); - entity_manager->set_signature(entity, new_signature); - system_manager->update_components(entity, new_signature); + static void remove_component(Entity entity) { + component_manager.remove_component(entity); + Signature signature = entity_manager.get_signature(entity); + Signature new_signature = remove_signature(signature, component_manager.get_component_id()); + entity_manager.set_signature(entity, new_signature); + system_manager.update_components(entity, new_signature); } template - T& get_component(Entity entity) { - return component_manager->get_component(entity); + static T& get_component(Entity entity) { + return component_manager.get_component(entity); } template - T *register_system() { - return system_manager->register_system(); + static T *register_system() { + return system_manager.register_system(); } template - void set_system_signature(Signature signature) { - system_manager->set_signature(signature); + static void set_system_signature(Signature signature) { + system_manager.set_signature(signature); } template - T* register_resource(T* resource) { - return resource_manager->register_resource(resource); + static T* register_resource(T* resource) { + return resource_manager.register_resource(resource); } template - T* get_resource() { - return resource_manager->get_resource(); + static T* get_resource() { + return resource_manager.get_resource(); } - private: - EntityManager *entity_manager; - ComponentManager *component_manager; - SystemManager *system_manager; - ResourceManager *resource_manager; }; diff --git a/src/engine/ecs/entity.cpp b/src/engine/ecs/entity.cpp index 1ebb9f3..6fb75d7 100644 --- a/src/engine/ecs/entity.cpp +++ b/src/engine/ecs/entity.cpp @@ -7,8 +7,7 @@ EntityManager::EntityManager() : available_entities(MAX_ENTITIES) { } } -EntityManager::~EntityManager() { -} +EntityManager::~EntityManager() {} Entity EntityManager::create_entity() { Entity entity = available_entities.pop(); diff --git a/src/engine/ecs/query.hpp b/src/engine/ecs/query.hpp index 9740698..541fc2b 100644 --- a/src/engine/ecs/query.hpp +++ b/src/engine/ecs/query.hpp @@ -13,7 +13,7 @@ class Query { signature = Signature(); int i = 0; for (auto component_id : {component_ids...}) { - set_signature(signature, component_id); + signature = set_signature(signature, component_id); } } void add_entity(Entity entity) { diff --git a/src/engine/ecs/resource.cpp b/src/engine/ecs/resource.cpp index cbf5f25..bf95c92 100644 --- a/src/engine/ecs/resource.cpp +++ b/src/engine/ecs/resource.cpp @@ -5,6 +5,9 @@ u32 ResourceBase::id_counter = 0; ResourceManager::ResourceManager() { resource_count = 0; + for (size_t i = 0; i < MAX_RESOURCES; ++i) { + resources[i] = nullptr; + } } ResourceManager::~ResourceManager() { diff --git a/src/engine/ecs/resource.hpp b/src/engine/ecs/resource.hpp index 9078c36..e9fd8a9 100644 --- a/src/engine/ecs/resource.hpp +++ b/src/engine/ecs/resource.hpp @@ -1,6 +1,7 @@ #pragma once #include "engine/utils/logging.h" #include "types.h" +#include const u32 MAX_RESOURCES = 128; diff --git a/src/engine/ecs/system.hpp b/src/engine/ecs/system.hpp index 53df6d1..4016c9d 100644 --- a/src/engine/ecs/system.hpp +++ b/src/engine/ecs/system.hpp @@ -16,7 +16,7 @@ class SystemBase { Query queries[4]; u32 query_count; - SystemBase() { + SystemBase() : query_count{0} { } }; diff --git a/src/engine/ecs/systemmanager.cpp b/src/engine/ecs/systemmanager.cpp index 991f070..5f58435 100644 --- a/src/engine/ecs/systemmanager.cpp +++ b/src/engine/ecs/systemmanager.cpp @@ -32,11 +32,15 @@ void SystemManager::update_components(Entity entity, Signature signature) { Query *query = &system->queries[j]; Signature query_signature = query->get_signature(); if ((signature & query_signature) == query_signature) { +#ifdef ECS_DEBUGGING INFO("Added entity {} to query, {}", entity, j); +#endif // Entity matches query query->entities.insert(entity); } else { +#ifdef ECS_DEBUGGING INFO("Removed entity {} from query ", entity); +#endif // Entity does not match query query->entities.remove(entity); } diff --git a/src/engine/graphics/GPURingBuffer.cpp b/src/engine/graphics/GPURingBuffer.cpp new file mode 100644 index 0000000..959471b --- /dev/null +++ b/src/engine/graphics/GPURingBuffer.cpp @@ -0,0 +1,44 @@ +#include "GPURingBuffer.h" + +#include + +#include + +#include "../utils/logging.h" + + +namespace engine { + +void GPURingBuffer::init(u32 capacity, u32 num_frames_in_flight) { + glCreateBuffers(1, &m_handle); + u32 size = capacity * num_frames_in_flight; + u32 flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; + glNamedBufferStorage(m_handle, size, nullptr, flags); + void* ptr = glMapNamedBufferRange(m_handle, 0, size, flags); + m_gpu_allocation = std::span((u8*)ptr, capacity * num_frames_in_flight); + m_curr_slice = std::span(m_gpu_allocation.data(), capacity); + m_capacity = capacity; + m_num_frames_in_flight = num_frames_in_flight; +} + +void GPURingBuffer::deinit() { glDeleteBuffers(1, &m_handle); } + +u64 GPURingBuffer::get_current_region_offset() { return m_curr_frame * m_capacity; } + +void GPURingBuffer::new_frame() { + m_curr_frame = (m_curr_frame + 1) % m_num_frames_in_flight; + m_curr_slice = std::span(&m_gpu_allocation[m_curr_frame * m_capacity], m_capacity); + m_curr_pos = 0; +} + +void GPURingBuffer::write(std::span bytes) { + if (m_curr_pos + bytes.size() > m_capacity) { + ERROR("GPU Ringbuffer ran out of space. Allocation size {} MB, size per frame {}", + m_gpu_allocation.size() >> 20, m_capacity >> 20); + assert(0); + } + std::memcpy(&m_curr_slice[m_curr_pos], bytes.data(), bytes.size()); + m_curr_pos += bytes.size(); +} + +} // namespace engine \ No newline at end of file diff --git a/src/engine/graphics/GPURingBuffer.h b/src/engine/graphics/GPURingBuffer.h new file mode 100644 index 0000000..815cdbb --- /dev/null +++ b/src/engine/graphics/GPURingBuffer.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../core.h" + +#include +#include + +namespace engine { + +class GPURingBuffer { +public: + void init(u32 capacity, u32 num_frames_in_flight); + void deinit(); + void write(std::span bytes); + void new_frame(); + u64 get_current_region_offset(); + u32 m_handle; + u32 m_capacity; +private: + u32 m_num_frames_in_flight; + u32 m_curr_frame; + u32 m_curr_pos; + std::span m_curr_slice; + std::span m_gpu_allocation; +}; + + +}; diff --git a/src/engine/graphics/Pipeline.cpp b/src/engine/graphics/Pipeline.cpp index 20917a1..806cb75 100644 --- a/src/engine/graphics/Pipeline.cpp +++ b/src/engine/graphics/Pipeline.cpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include "../utils/logging.h" @@ -128,12 +130,10 @@ void Pipeline::bind() { glBindVertexArray(m_vao); } -void Pipeline::add_vertex_buffer(std::span attributes, u32 stride, std::span vertex_data) { - glCreateBuffers(1, &m_vbo); - glNamedBufferStorage(m_vbo, vertex_data.size(), vertex_data.data(), 0); - glVertexArrayVertexBuffer(m_vao, 0, m_vbo, 0, stride); - +void Pipeline::add_vertex_buffer_from_buffer(std::span attributes, u32 stride, u32 buffer) { + m_vbo = buffer; + glVertexArrayVertexBuffer(m_vao, 0, buffer, 0, stride); for (size_t i = 0; i < attributes.size(); ++i) { const auto& attrib = attributes[i]; u32 gl_type; @@ -151,6 +151,14 @@ void Pipeline::add_vertex_buffer(std::span attr } } +void Pipeline::add_vertex_buffer(std::span attributes, u32 stride, std::span vertex_data) { + u32 buffer; + glCreateBuffers(1, &buffer); + glNamedBufferStorage(buffer, vertex_data.size(), vertex_data.data(), 0); + + add_vertex_buffer_from_buffer(attributes, stride, buffer); +} + void Pipeline::add_index_buffer(std::span indices) { glCreateBuffers(1, &m_ibo); @@ -159,4 +167,4 @@ void Pipeline::add_index_buffer(std::span indices) { } -} \ No newline at end of file +} diff --git a/src/engine/graphics/Pipeline.h b/src/engine/graphics/Pipeline.h index ffb874f..321dc7e 100644 --- a/src/engine/graphics/Pipeline.h +++ b/src/engine/graphics/Pipeline.h @@ -38,6 +38,7 @@ class Pipeline { void deinit(); void add_vertex_buffer(std::span attributes, u32 stride, std::span vertex_data); + void add_vertex_buffer_from_buffer(std::span attributes, u32 stride, u32 buffer); void add_index_buffer(std::span indices); void add_vertex_shader(const std::string& path, std::span specialization_constants = {}); void add_fragment_shader(const std::string& path, std::span specialization_constants = {}); diff --git a/src/engine/scene/Scene.h b/src/engine/scene/Scene.h index 9240dc2..9d94f74 100644 --- a/src/engine/scene/Scene.h +++ b/src/engine/scene/Scene.h @@ -79,6 +79,7 @@ struct Material { f32 occlusion_strength; u32 emission_map; glm::vec3 emission_factor; + bool is_double_sided; }; diff --git a/src/engine/utils/logging.cpp b/src/engine/utils/logging.cpp deleted file mode 100644 index 7b3c447..0000000 --- a/src/engine/utils/logging.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "logging.h" - -#include - -#define SEC_IN_MS 1000 - -// Log::time but return a ostringstream& instead -std::ostringstream &Logger::time_stream(std::ostringstream &stream, bool include_ms) { - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::system_clock::to_time_t(now); - - stream << std::put_time(std::localtime(&time), "%H:%M:%S"); - - if (include_ms) { - auto ms = std::chrono::duration_cast(now.time_since_epoch()) % - SEC_IN_MS; - stream << "." << ms.count(); - } - - return stream; -} - -// Log::color but return a ostringstream& instead -std::ostringstream &Logger::color_stream(std::ostringstream &stream, int R, int G, int B, - bool bold) { - stream << ANSI_START "38:2:" << R << ":" << G << ":" << B << "m"; - if (bold) { - stream << ANSI_START "1m"; - } - - return stream; -} - -std::string Logger::time(bool include_ms) { - std::ostringstream os; - time_stream(os, include_ms); - return os.str(); -} - -std::string Logger::color(int R, int G, int B, bool bold) { - std::ostringstream os; - color_stream(os, R, G, B, bold); - return os.str(); -} \ No newline at end of file diff --git a/src/engine/utils/logging.h b/src/engine/utils/logging.h index 15bbafa..9ceee60 100644 --- a/src/engine/utils/logging.h +++ b/src/engine/utils/logging.h @@ -6,107 +6,106 @@ #undef DEBUG #endif -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #define ANSI_START "\033[" +#define ANSI_BOLD ANSI_START "1m" #define ANSI_RESET "\033[0m" -enum class LogLevels { - ERROR, - WARN, - INFO, - DEBUG, +#ifndef LOG_LEVELS_LIST +#define LOG_LEVELS_LIST(f) \ + f(LOG_LEVEL_ERROR, "Error", 230, 80, 70) \ + f(LOG_LEVEL_WARN, "Warn", 222, 172, 22) \ + f(LOG_LEVEL_INFO, "Info", 200, 200, 200) \ + f(LOG_LEVEL_DEBUG, "Debug", 160, 160, 160) - // Used to get enum member count - COUNT +#define GET_LOG_LEVEL_ENUM_NAME(NAME, ...) NAME, +enum log_levels_t { + LOG_LEVELS_LIST(GET_LOG_LEVEL_ENUM_NAME) }; -struct _LOG_RGB { +#define GENERATE_LOG_LEVEL_TO_STR(LEVEL, STR, ...) case LEVEL: return STR; +static inline const char * get_log_level_str(enum log_levels_t level) { \ + switch (level) { \ + LOG_LEVELS_LIST(GENERATE_LOG_LEVEL_TO_STR) \ + default: return "UNDEFINED"; \ + } \ +} + +struct _log_level_rgb { int R, G, B; }; -template -struct log_level_info { - // LogLevel String LogLevel RGB color - std::array, N> mappings; - - [[nodiscard]] - constexpr std::string_view to_string(EnumType value) const { - for (const auto &[enum_val, str, _] : mappings) { - if (enum_val == value) { - return str; - } - } - - return "UNDEFINED"; - } - - [[nodiscard]] - constexpr _LOG_RGB get_color(EnumType value) const { - for (const auto &[enum_val, _, color] : mappings) { - if (enum_val == value) { - return color; - } - } - - return _LOG_RGB(0, 0, 0); +#define GENERATE_LOG_LEVEL_RGB(LEVEL, STR, _R, _G, _B) case LEVEL: return (_log_level_rgb) {.R=_R, .G=_G, .B=_B}; +static inline const struct _log_level_rgb get_log_level_rgb(enum log_levels_t level) { \ + switch (level) { \ + LOG_LEVELS_LIST(GENERATE_LOG_LEVEL_RGB) \ + default: return (_log_level_rgb) {.R=0, .G=0, .B=0}; \ + } \ } -}; - -constexpr auto log_level_list = log_level_info(LogLevels::COUNT)>{{{ - {LogLevels::ERROR, "Error", _LOG_RGB(230, 80, 70)}, - {LogLevels::WARN, "Warn", _LOG_RGB(222, 172, 22)}, - {LogLevels::INFO, "Info", _LOG_RGB(200, 200, 200)}, - {LogLevels::DEBUG, "Debug", _LOG_RGB(160, 160, 160)}, -}}}; - -class Logger { - public: - static std::string time(bool include_ms = true); - static std::string color(int R, int G, int B, bool bold = true); - - template - static void log_with_position(const std::string file_name_str, const int line_number, - std::format_string fmt, Args &&...args); - - private: - static std::ostringstream &time_stream(std::ostringstream &stream, bool include_ms = true); - static std::ostringstream &color_stream(std::ostringstream &stream, int R, int G, int B, - bool bold = true); -}; - -template -void Logger::log_with_position(const std::string file_name_str, const int line_number, - std::format_string fmt, Args &&...args) { - std::ostringstream stream; - auto color = log_level_list.get_color(level); - stream << "["; - Logger::time_stream(stream) << "]"; - Logger::color_stream(stream, color.R, color.G, color.B) - << " [" << file_name_str << ":" << line_number << "] " << log_level_list.to_string(level) - << ": "; +#endif - std::cout << stream.str() << std::format(fmt, std::forward(args)...) << ANSI_RESET - << std::endl; +///* Windows-specific timing functions */ +//#ifdef _WIN32 +//#include +//static inline int get_milliseconds() { +// SYSTEMTIME st; +// GetLocalTime(&st); +// return st.wMilliseconds; +//} +//#else +//#include +//static inline int get_milliseconds() { +// struct timeval tv; +// gettimeofday(&tv, NULL); +// return tv.tv_usec / 1000; +//} +//#endif + +#define CSTR_LEN(str) (sizeof(str) - 1) + +//static inline struct tm get_time() { +// time_t time_now; +// struct tm time_info; +// +// time(&time_now); +//#ifdef _WIN32 +// localtime_s(&time_info, &time_now); +//#else +// localtime_r(&time_now, &time_info); +//#endif +// +// return time_info; +//} +// +#define TO_STR(x) #x +#define TO_STR_VALUE(x) TO_STR(x) +// +//#define LOGGER_LOG(level, FMT, ...) { \ +// struct tm time_info = get_time(); \ +// int ms = get_milliseconds(); \ +// auto color = get_log_level_rgb(level); \ +// std::print("[" ANSI_BOLD "{:02}:{:02}:{:02}.{:03}" ANSI_RESET "]" ANSI_START "38:2:{}:{}:{}m [" __FILE_NAME__ ":" TO_STR_VALUE(__LINE__) "]: " FMT ANSI_RESET "\n", time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms, color.R, color.G, color.B, ##__VA_ARGS__); \ +//} + +#define LOGGER_LOG(level, FMT, ...) { \ + auto now = std::chrono::system_clock::now(); \ + auto local = std::chrono::zoned_time{std::chrono::current_zone(), now}; \ + auto color = get_log_level_rgb(level); \ + std::print("[" ANSI_BOLD "{:%H:%M:%S}" ANSI_RESET "]" ANSI_START "38:2:{}:{}:{}m [" __FILE_NAME__ ":" TO_STR_VALUE(__LINE__) "]: " FMT ANSI_RESET "\n", local, color.R, color.G, color.B, ##__VA_ARGS__); \ } -#define LOGGER_LOG(level, fmt, ...) \ - Logger::log_with_position(__FILE_NAME__, __LINE__, fmt, ##__VA_ARGS__) -#define INFO(fmt, ...) LOGGER_LOG(LogLevels::INFO, fmt, ##__VA_ARGS__) -#define ERROR(fmt, ...) LOGGER_LOG(LogLevels::ERROR, fmt, ##__VA_ARGS__) -#define WARN(fmt, ...) LOGGER_LOG(LogLevels::WARN, fmt, ##__VA_ARGS__) -#define DEBUG(fmt, ...) LOGGER_LOG(LogLevels::DEBUG, fmt, ##__VA_ARGS__) + +#define INFO(fmt, ...) LOGGER_LOG(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__) +#define DEBUG(fmt, ...) LOGGER_LOG(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__) +#define WARN(fmt, ...) LOGGER_LOG(LOG_LEVEL_WARN, fmt, ##__VA_ARGS__) +#define ERROR(fmt, ...) LOGGER_LOG(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__) +#define FATAL(fmt, ...) ERROR(fmt, ##__VA_ARGS__); exit(1) #ifdef DEBUG #pragma pop_macro("DEBUG") #endif -#endif \ No newline at end of file +#endif diff --git a/src/game/enemy.cpp b/src/game/enemy.cpp new file mode 100644 index 0000000..ef8c3a0 --- /dev/null +++ b/src/game/enemy.cpp @@ -0,0 +1,45 @@ +#include "enemy.h" +#include "state.h" + +Enemy::Enemy() : position{glm::vec3(20, 0, 20)}, rotation{glm::quat(1.0f, 0.0f, 0.0f, 0.0f)}, scale{glm::vec3(1.0f)}, health{100}, speed{3}, hover_time(0), cooldown{0} {} + +void Enemy::update(State &state) { + if (cooldown > 0) { + cooldown -= state.delta_time; + return; + } + glm::vec3 enemy_target_look = state.player.position - position; + enemy_target_look.y = 0; + + float player_distance = glm::length(enemy_target_look); + + if (player_distance > 2) { + // Rotate + enemy_target_look = glm::normalize(enemy_target_look); + glm::quat target_rotation = glm::quatLookAt(enemy_target_look, glm::vec3(0, 1, 0)); + rotation = glm::slerp(rotation, target_rotation, state.delta_time * 2.0f); + + // Move in facing direction + glm::vec3 forward = rotation * glm::vec3(0, 0, -1); + position += forward * speed * state.delta_time; + + // hover effect sine function + position.y = glm::sin(hover_time) * 0.3; + hover_time += state.delta_time * speed; + } else { + // Damage player + state.player.take_damage(10); + cooldown = 0.5; + } +} + +void Enemy::take_damage(float damage) { + health -= damage; + if (health < 0.0f) { + health = 0.0f; + } +} + +bool Enemy::is_alive() const { + return health > 0.0f; +} \ No newline at end of file diff --git a/src/game/enemy.h b/src/game/enemy.h new file mode 100644 index 0000000..d9f7466 --- /dev/null +++ b/src/game/enemy.h @@ -0,0 +1,23 @@ +#pragma once + +#include "glm/glm.hpp" +#include "glm/gtc/quaternion.hpp" + +struct State; + +struct Enemy { + glm::vec3 position; + glm::quat rotation; + glm::vec3 scale; + float health; + float speed; + float hover_time; + + float cooldown; + + Enemy(); + + void update(State &state); + void take_damage(float damage); + bool is_alive() const; +}; \ No newline at end of file diff --git a/src/game/gui.cpp b/src/game/gui.cpp index 67dbf4b..54a7a4c 100644 --- a/src/game/gui.cpp +++ b/src/game/gui.cpp @@ -434,6 +434,11 @@ void gui::build(State& state) { } } ImGui::End(); + + ImGui::Begin("Player Stats"); + ImGui::InputFloat("Player Health", &state.player.health); + + ImGui::End(); } void gui::render() { diff --git a/src/game/main.cpp b/src/game/main.cpp index 8dc5dd2..940e950 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -1,257 +1,689 @@ -#include -#include -#include -#include - -#include "engine/AssetLoader.h" -#include "engine/Input.h" -#include "engine/Renderer.h" -#include "engine/core.h" -#include "engine/scene/Node.h" -#include "engine/scene/Scene.h" -#include "engine/utils/logging.h" -#include "glm/ext/quaternion_common.hpp" -#include "glm/fwd.hpp" -#include "glm/geometric.hpp" -#include "glm/gtc/quaternion.hpp" -#include "gui.h" -#include "state.h" -#include "world_gen/map.h" - - -template -void fatal(std::format_string fmt, Args &&...args) { - ERROR(fmt, std::forward(args)...); - exit(1); -} - -static void error_callback(int error, const char *description) { - ERROR("GLFW error with code {}: {}", error, description); -} - -static void framebuffer_size_callback(GLFWwindow *window, int width, int height) { - (void)window; - glViewport(0, 0, width, height); -} - -static void gen_world(State &state, engine::NodeHandle &root) { - Map map(100, 15, 5, 10, 1337); - - engine::NodeHandle map_node = state.hierarchy.add_node( - { - .name = "Map", - .rotation = glm::quat(1, 0, 0, 0), - .scale = glm::vec3(5, 1, 5), - }, - root); - - engine::MeshHandle cube_mesh = state.scene.mesh_by_name("Cube"); - - for (size_t i = 0; i < map.grid.size(); ++i) { - for (size_t j = 0; j < map.grid[i].size(); ++j) { - if (map.grid[i][j] != -1) { - state.hierarchy.add_node( - { - .kind = engine::Node::Kind::mesh, - .name = std::format("cube_{}_{}", i, j), - .rotation = glm::quat(1, 0, 0, 0), - .translation = glm::vec3(map.grid.size() * -0.5 + (f32)i, -2.5, map.grid[i].size() * -0.5 + (f32)j), - .scale = glm::vec3(1), - .mesh_index = cube_mesh.get_value(), - }, - map_node); - } - } - } -} - -int main(void) { - if (!glfwInit()) { - fatal("Failed to initiliaze glfw"); - } - INFO("Initialized GLFW"); - glfwSetErrorCallback(error_callback); - - f32 content_x_scale; - f32 content_y_scale; - glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &content_x_scale, &content_y_scale); - INFO("Monitor content scale is ({}, {})", content_x_scale, content_y_scale); - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); - glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); - glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); - glfwWindowHint(GLFW_SAMPLES, 8); -#ifndef NDEBUG - glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE); -#else - glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE); -#endif - - f32 content_scale = std::max(content_x_scale, content_y_scale); - auto window = glfwCreateWindow(1920 / content_scale, 1080 / content_scale, "Skeletal Animation", - nullptr, nullptr); - if (!window) { - fatal("Failed to create glfw window"); - } - - glfwMakeContextCurrent(window); - - engine::Input input(window); - State state = {}; - state.camera.init(glm::vec3(0.0f, 0.0f, 3.0f), 10.0f); - state.mouse_locked = true; - state.sensitivity = 0.001f; - - state.renderer.init((engine::Renderer::LoadProc)glfwGetProcAddress); - { - auto data = engine::loader::load_asset_file("scene_data.bin"); - state.scene.init(data); - state.renderer.make_resources_for_scene(data); - } - - engine::NodeHandle root_node = state.hierarchy.add_root_node({ - .name = "Game", - .rotation = glm::quat(1, 0, 0, 0), - .scale = glm::vec3(1), - }); - - gen_world(state, root_node); - - auto player_prefab = state.scene.prefab_by_name("Player"); - engine::NodeHandle player = - state.hierarchy.instantiate_prefab(state.scene, player_prefab, engine::NodeHandle(0)); - - auto enemy_prefab = state.scene.prefab_by_name("Enemy"); - engine::NodeHandle enemy = - state.hierarchy.instantiate_prefab(state.scene, enemy_prefab, engine::NodeHandle(0)); - - glfwSetWindowUserPointer(window, &state); - glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); - - int width; - int height; - glfwGetFramebufferSize(window, &width, &height); - glViewport(0, 0, width, height); - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - - gui::init(window, content_scale); - - state.player.position = glm::vec3(0.0f); - state.player.rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - // Should probably always be 1 - state.player.scale = glm::vec3(1.0f); - state.player.speed = 10; - - // Init enemy - state.enemy.position = glm::vec3(20, 0, 20); - state.enemy.rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - state.enemy.scale = glm::vec3(1.0f); - state.enemy.speed = 3; - - while (!glfwWindowShouldClose(window)) { - state.prev_time = state.curr_time; - state.curr_time = glfwGetTime(); - state.delta_time = state.curr_time - state.prev_time; - - // Update - glfwPollEvents(); - glfwGetFramebufferSize(window, &width, &height); - if (width == 0 || height == 0) { - continue; - } - state.fb_width = width; - state.fb_height = height; - - state.prev_delta_times[state.fps_counter_index] = state.delta_time; - state.fps_counter_index = (state.fps_counter_index + 1) % state.prev_delta_times.size(); - - // keyboard input - if (input.is_key_just_pressed(GLFW_KEY_ESCAPE)) { - state.mouse_locked = !state.mouse_locked; - glfwSetInputMode(window, GLFW_CURSOR, - state.mouse_locked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); - } - - glm::vec3 direction(0.0f); - - if (input.is_key_pressed(GLFW_KEY_W)) direction.z += 1.0f; - if (input.is_key_pressed(GLFW_KEY_S)) direction.z -= 1.0f; - if (input.is_key_pressed(GLFW_KEY_A)) direction.x -= 1.0f; - if (input.is_key_pressed(GLFW_KEY_D)) direction.x += 1.0f; - - if (glm::length(direction) > 0) { - if (input.is_key_pressed(GLFW_KEY_LEFT_SHIFT)) { - state.camera.move(glm::normalize(direction), state.delta_time); - } else { - auto forward = state.camera.m_orientation * glm::vec3(0, 0, -1); - auto right = state.camera.m_orientation * glm::vec3(1, 0, 0); - forward.y = 0; - right.y = 0; - - forward = glm::normalize(forward); - right = glm::normalize(right); - auto movement = (right * direction.x + forward * direction.z) * state.player.speed * - state.delta_time; - state.player.position += movement; - - glm::quat target_rotation = - glm::quatLookAt(glm::normalize(movement), glm::vec3(0, 1, 0)); - - state.player.rotation = - glm::slerp(state.player.rotation, target_rotation, state.delta_time * 8.0f); - - // camera movement - glm::vec3 camera_offset = glm::vec3(-5, 5, -5); - glm::vec3 camera_target_position = state.player.position + camera_offset; - state.camera.m_pos = - glm::mix(state.camera.m_pos, camera_target_position, state.delta_time * 5); - state.camera.m_orientation = - glm::quatLookAt(glm::normalize(-camera_offset), glm::vec3(0, 1, 0)); - } - } - - // ememy movement - { - glm::vec3 enemy_move = state.player.position - state.enemy.position; - enemy_move.y = 0; - if (enemy_move.x != 0 || enemy_move.z != 0) enemy_move = glm::normalize(enemy_move); - - glm::quat target_rotation = glm::quatLookAt(enemy_move, glm::vec3(0, 1, 0)); - state.enemy.position += enemy_move * state.enemy.speed * state.delta_time; - state.enemy.rotation = - glm::slerp(state.enemy.rotation, target_rotation, state.delta_time * 8.0f); - } - - // mouse input - if (state.mouse_locked) { - glm::vec2 mouse_delta = input.get_mouse_position_delta(); - state.camera.rotate(-mouse_delta.x * state.sensitivity, - -mouse_delta.y * state.sensitivity); - } - - glfwPollEvents(); - gui::build(state); - - // Not the formal way to set a node transform. Just temporary - state.hierarchy.m_nodes[player.get_value()].translation = state.player.position; - state.hierarchy.m_nodes[player.get_value()].rotation = state.player.rotation; - state.hierarchy.m_nodes[player.get_value()].scale = state.player.scale; - - state.hierarchy.m_nodes[enemy.get_value()].translation = state.enemy.position; - state.hierarchy.m_nodes[enemy.get_value()].rotation = state.enemy.rotation; - state.hierarchy.m_nodes[enemy.get_value()].scale = state.enemy.scale; - - // Draw - state.renderer.clear(); - state.renderer.begin_pass(state.scene, state.camera, width, height); - state.renderer.draw_hierarchy(state.scene, state.hierarchy); - state.renderer.end_pass(); - - gui::render(); - - glfwSwapBuffers(window); - - input.update(); - } -} +#include "engine/Camera.h" +#include +#include +#include +#include +#include + +#include "engine/AssetLoader.h" +#include "engine/Input.h" +#include "engine/Renderer.h" +#include "engine/audio/audio.h" +#include "engine/core.h" +#include "engine/ecs/component.hpp" +#include "engine/ecs/ecs.hpp" +#include "engine/ecs/entity.hpp" +#include "engine/ecs/entityarray.hpp" +#include "engine/ecs/resource.hpp" +#include "engine/ecs/signature.hpp" +#include "engine/ecs/system.hpp" +#include "engine/scene/Node.h" +#include "engine/scene/Scene.h" +#include "engine/utils/logging.h" +#include "engine/Game.h" +#include "glm/ext/quaternion_common.hpp" +#include "glm/fwd.hpp" +#include "glm/geometric.hpp" +#include "glm/gtc/quaternion.hpp" +#include "state.h" +#include "settings.h" +#include "world_gen/map.h" + +inline glm::vec4 from_hex(const char* h) { + unsigned int r, g, b, a = 255; + if (std::strlen(h) == 7) + std::sscanf(h, "#%02x%02x%02x", &r, &g, &b); + else + std::sscanf(h, "#%02x%02x%02x%02x", &r, &g, &b, &a); + return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f); +} + +class RDeltaTime : public Resource { +public: + f32 delta_time; + f32 prev_time; + RDeltaTime(f32 current_time): delta_time(0), prev_time(current_time) {} +}; + +class RInput : public Resource { +public: + engine::Input input; + Settings settings; + RInput(GLFWwindow *window) : input(window) {} + + // returns a vec3 with xz coordinates for movement keys in settings + glm::vec3 get_direction() { + glm::vec3 direction(0.0f); + + if (input.is_key_pressed(settings.key_forward)) + direction.z += 1.0f; + if (input.is_key_pressed(settings.key_backward)) + direction.z -= 1.0f; + if (input.is_key_pressed(settings.key_left)) + direction.x -= 1.0f; + if (input.is_key_pressed(settings.key_right)) + direction.x += 1.0f; + + return direction; + } +}; + +class RRenderer : public Resource { +public: + engine::Renderer m_renderer; + RRenderer(engine::Renderer renderer) : m_renderer(renderer) {} +}; + +class RScene : public Resource { +public: + engine::Scene m_scene; + engine::Camera m_camera; + engine::NodeHierarchy m_hierarchy; + GLFWwindow *m_window; + Entity m_player; // temporary way of getting the player in singleplayer + RScene(engine::Scene scene, engine::Camera camera, GLFWwindow *window, + engine::NodeHierarchy hierarchy, Entity player) + : m_scene(scene), m_camera(camera), m_hierarchy(hierarchy), + m_window(window), m_player(player) {} +}; + +class CPlayer : public Component { +public: + float attack_cooldown = 0.0f; + float attack_damage = 50.0f; + float attack_range_squared = 25.0f; +}; + +class CSpeed : public Component { +public: + float speed = 10; +}; + +class CHealth : public Component { +public: + float health = 100; + + void take_damage(float damage) { + health -= damage; + if (health < 0.0f) { + health = 0.0f; + } + } + + bool is_alive() { + return health > 0.0f; + } +}; + +class CEnemyGhost : public Component { +public: + float cooldown = 0.0f; +}; + +class CLocal : public Component { +public: +}; + +class CVelocity : public Component { +public: + glm::vec3 vel; + + void move_over_time(glm::vec3 move, float time) { + move_over_time_vel = move / time; + move_over_time_time = time; + } + + glm::vec3 move_over_time_vel; + float move_over_time_time = 0.0; +}; + +class CTranslation : public Component { +public: + glm::vec3 pos; + glm::quat rot; + glm::vec3 scale; +}; + +class CMesh : public Component { +public: + engine::NodeHandle m_node; + CMesh(engine::NodeHandle node) : m_node(node) {} + CMesh() {} +}; + +class SDeltaTime : public System { +public: + SDeltaTime() {} + + void update() { + auto time = ECS::get_resource(); + time->delta_time = glfwGetTime() - time->prev_time; + time->prev_time = glfwGetTime(); + } +}; + +class SInput : public System { +public: + SInput() {} + + void update() { + auto input = ECS::get_resource(); + input->input.update(); + } +}; + +class SMove : public System { +public: + SMove() { + queries[0] = Query(CTranslation::get_id(), CVelocity::get_id()); + query_count = 1; + } + + void update() { + auto entities = get_query(0)->get_entities(); + Iterator it = {.next = 0}; + Entity e; + while (entities->next(it, e)) { + CTranslation &translation = ECS::get_component(e); + CVelocity& vel = ECS::get_component(e); + translation.pos += vel.vel; + vel.vel = glm::vec3(0); + if (vel.move_over_time_time > 0.0f) { + float delta_time = ECS::get_resource()->delta_time; + translation.pos += vel.move_over_time_vel * delta_time; + vel.move_over_time_time -= delta_time; + } + } + } +}; + +class SUIRender : public System { +public: + SUIRender(){} + + void update() { + auto time = ECS::get_resource(); + auto renderer = &ECS::get_resource()->m_renderer; + auto scene = ECS::get_resource(); + auto window = scene->m_window; + + int height; + int width; + glfwGetFramebufferSize(window, &width, &height); + + // DRAW HEALTH BAR + + // TODO: Get nearest player instead of only player in single player + Entity& nearest_player = scene->m_player; + CHealth& nearest_player_health = ECS::get_component(nearest_player); + + // Draw + renderer->begin_rect_pass(); + { + float hpbar_width = 256.f; + float hpbar_height = 32.f; + float margin = 12.f; + float border_size = 4.f; + + static float smooth_health = nearest_player_health.health; + static float last_health = nearest_player_health.health; + static float damage_timer = 0.f; + + // smooth taken damage indicator bar + if(nearest_player_health.health < last_health) { + last_health = nearest_player_health.health; + damage_timer = 0.2f; + } + if(damage_timer > 0.f) { + damage_timer -= time->delta_time; + if(damage_timer <= 0.f) { + last_health = nearest_player_health.health; + } + }else { + smooth_health = glm::mix(smooth_health, nearest_player_health.health, time->delta_time * 10.f); + } + + float max_health = 100.f; // temporary + + // damage indicator bar (shows how much damage was taken) + float damage_bar_width = hpbar_width * (nearest_player_health.health / max_health); + renderer->draw_rect({ .x = (float)width - hpbar_width - margin, .y = margin, .width = damage_bar_width, .height = hpbar_height}, from_hex("#ff0000a0")); + + // hp bar + float bar_width = hpbar_width * (smooth_health / max_health); + renderer->draw_rect({ .x = (float)width - hpbar_width - margin, .y = margin, .width = bar_width, .height = hpbar_height}, from_hex("#ff8519a0")); + + // background + renderer->draw_rect({ .x = (float)width - hpbar_width - margin, .y = margin, .width = hpbar_width, .height = hpbar_height}, from_hex("#000000a0")); + + // border + renderer->draw_rect({ .x = (float)width - hpbar_width - margin - border_size, .y = margin - border_size, .width = hpbar_width + 2 * border_size, .height = hpbar_height + 2 * border_size}, from_hex("#2a2a2aff")); + } + renderer->end_rect_pass(width, height); + } +}; + +class SRender : public System { +public: + SRender() { + queries[0] = Query(CTranslation::get_id(), CMesh::get_id()); + query_count = 1; + } + + void update() { + auto renderer = &ECS::get_resource()->m_renderer; + auto scene = ECS::get_resource(); + auto hierarchy = scene->m_hierarchy; + auto camera = scene->m_camera; + auto window = scene->m_window; + int height; + int width; + + auto entities = get_query(0)->get_entities(); + Iterator it = {.next = 0}; + Entity e; + while (entities->next(it, e)) { + auto translation = ECS::get_component(e); + auto mesh = ECS::get_component(e); + hierarchy.m_nodes[mesh.m_node.get_value()].translation = translation.pos; + hierarchy.m_nodes[mesh.m_node.get_value()].rotation = translation.rot; + hierarchy.m_nodes[mesh.m_node.get_value()].scale = translation.scale; + } + + glfwPollEvents(); + glfwGetFramebufferSize(window, &width, &height); + + // Draw + renderer->clear(); + renderer->begin_pass(scene->m_scene, camera, width, height); + renderer->draw_hierarchy(scene->m_scene, hierarchy); + renderer->end_pass(); + } +}; + +class SCamera : public System { +public: + SCamera() { + queries[0] = Query(CTranslation::get_id(), CLocal::get_id(), CPlayer::get_id()); + query_count = 1; + } + + void update() { + float delta_time = ECS::get_resource()->delta_time; + auto scene = ECS::get_resource(); + auto window = scene->m_window; + RInput* input = ECS::get_resource();; + + engine::Camera& camera = scene->m_camera; + + // mouse locking + bool mouse_locked = glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; + if (input->input.is_key_just_pressed(GLFW_KEY_ESCAPE)) { + mouse_locked = !mouse_locked; + glfwSetInputMode(window, GLFW_CURSOR, + mouse_locked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); + } + + // camera rotation mouse input + if (mouse_locked) { + glm::vec2 mouse_delta = input->input.get_mouse_position_delta(); + camera.rotate(-mouse_delta.x * 0.00048f * input->settings.sensitivity, + -mouse_delta.y * 0.00048f * input->settings.sensitivity); + } + + // camera movement + if (input->input.is_key_pressed(GLFW_KEY_LEFT_SHIFT)) { + // free cam movement + // movement input + glm::vec3 direction = input->get_direction(); + + if (glm::dot(direction, direction) > 0) { + camera.move(glm::normalize(direction), delta_time); + } + } else { // this query could be changed to something like CCameraFollow instead of being hardcoded to local players + // follow player cam + // get players + auto players = get_query(0)->get_entities(); + glm::vec3 mean_player_position = glm::vec3(0.0f, 0.0f, 0.0f); + Iterator it = {.next = 0}; + Entity e; + int player_count = 0; + while (players->next(it, e)) { + mean_player_position += ECS::get_component(e).pos; + player_count++; + } + mean_player_position /= player_count; + + if (player_count > 0) + { + float camera_distance = 10; + + glm::vec3 camera_target_position = mean_player_position + - camera.m_orientation * glm::vec3(0, 0, -1) * camera_distance; + camera.m_pos = glm::mix(camera.m_pos, camera_target_position, delta_time * 5); + } + } + } +}; + +class SPlayerController : public System { +public: + SPlayerController() { + queries[0] = Query(CTranslation::get_id(), CVelocity::get_id(), CLocal::get_id(), CPlayer::get_id()); + queries[1] = Query(CTranslation::get_id(), CVelocity::get_id(), CEnemyGhost::get_id(), CHealth::get_id()); + query_count = 2; + } + + void update() { + float delta_time = ECS::get_resource()->delta_time; + auto scene = ECS::get_resource(); + engine::Camera& camera = scene->m_camera; + + RInput& input = *ECS::get_resource(); + //Settings settings = ECS::get_resource()->settings; + + // Get local player from query + Entity player; + { + auto player_ptr = get_query(0)->get_entities()->first(); + if (player_ptr == nullptr) { + ERROR("NO PLAYER FOUND!!"); + return; + } + player = *player_ptr; + } + + + CVelocity& velocity = ECS::get_component(player); + CPlayer& c_player = ECS::get_component(player); + float& player_speed = ECS::get_component(player).speed; + CTranslation &translation = + ECS::get_component(player); + + + // movement input + glm::vec3 direction = input.get_direction(); + + // player movement + if (!input.input.is_key_pressed(GLFW_KEY_LEFT_SHIFT)){ + auto forward = camera.m_orientation * glm::vec3(0, 0, -1); + auto right = camera.m_orientation * glm::vec3(1, 0, 0); + forward.y = 0; + right.y = 0; + + forward = glm::normalize(forward); + right = glm::normalize(right); + velocity.vel = (right * direction.x + forward * direction.z) * player_speed * delta_time;; + + if (glm::length(direction) > 0) { + glm::quat target_rotation = + glm::quatLookAt(glm::normalize(velocity.vel), glm::vec3(0, 1, 0)); + + translation.rot = + glm::slerp(translation.rot, target_rotation, delta_time * 8.0f); + } + } + + if (c_player.attack_cooldown <= 0.0f) { + if (input.input.is_key_pressed(input.settings.key_attack)) { + c_player.attack_cooldown += 0.3f; + // TODO: sword animation from 45 degrees to the right of player rotation + // to 45 degrees to the left of player rotation + // and adjust range + + auto ghosts = get_query(1)->get_entities(); + Iterator it = {.next = 0}; + Entity ghost; + while (ghosts->next(it, ghost)) { + CTranslation& ghost_translation = ECS::get_component(ghost); + + glm::vec3 diff_player_ghost = ghost_translation.pos - translation.pos; + diff_player_ghost.y = 0.0f; + + float distance_player_ghost_squared = glm::dot(diff_player_ghost, diff_player_ghost); + if (distance_player_ghost_squared <= c_player.attack_range_squared) { + // ghost was in range + // check angle + glm::vec3 forward_direction = translation.rot * glm::vec3(0, 0, -1); + glm::vec3 direction_to_ghost = glm::normalize(diff_player_ghost - forward_direction * 0.8f); + float cosine_angle = glm::dot(direction_to_ghost, forward_direction); + if (cosine_angle > 0.7f) { + // ghost was hit! + ECS::get_component(ghost).take_damage(c_player.attack_damage); + CVelocity& ghost_vel = ECS::get_component(ghost); + ghost_vel.move_over_time(forward_direction * 10.0f, 0.3f); + } + } + } + } + } else { + c_player.attack_cooldown -= delta_time; + } + } +}; + +class SEnemyGhost : public System { + public: + SEnemyGhost() { + queries[0] = Query(CTranslation::get_id(), CVelocity::get_id(), CEnemyGhost::get_id()); + query_count = 1; + } + + void update() { + auto time = ECS::get_resource(); + auto scene = ECS::get_resource(); + + auto entities = get_query(0)->get_entities(); + Iterator it = {.next = 0}; + Entity e; + while (entities->next(it, e)) { + CTranslation &translation = ECS::get_component(e); + CVelocity& vel = ECS::get_component(e); + CEnemyGhost& ghost = ECS::get_component(e); + float& speed = ECS::get_component(e).speed; + CHealth& health = ECS::get_component(e); + if (!health.is_alive()) { + // ECS::destroy_entity(e); this was problem + translation.pos.y = -3.0; + continue; + } + + // TODO: Get nearest player instead of only player in single player + Entity& nearest_player = scene->m_player; + CTranslation& nearest_player_translation = ECS::get_component(nearest_player); + CHealth& nearest_player_health = ECS::get_component(nearest_player); + + // update enemy + if (ghost.cooldown > 0) { + ghost.cooldown -= time->delta_time; + //INFO("debug {}", ghost.cooldown); + continue; + } + glm::vec3 enemy_target_look = nearest_player_translation.pos - translation.pos; + enemy_target_look.y = 0; + + float player_distance = glm::length(enemy_target_look); + + if (player_distance > 2.0f) { + // Rotate + enemy_target_look = glm::normalize(enemy_target_look); + glm::quat target_rotation = glm::quatLookAt(enemy_target_look, glm::vec3(0, 1, 0)); + translation.rot = glm::slerp(translation.rot, target_rotation, time->delta_time * 2.0f); + + // Move in facing direction + glm::vec3 forward = translation.rot * glm::vec3(0, 0, -1); + vel.vel = forward * speed * time->delta_time; + + // hover effect sine function + translation.pos.y = glm::sin(time->prev_time * speed) * 0.3; + } else { + // Damage player + nearest_player_health.take_damage(20); + ghost.cooldown = 1.0; + } + } + } +}; + +static void error_callback(int error, const char *description) { + ERROR("GLFW error with code {}: {}", error, description); +} + +static void framebuffer_size_callback(GLFWwindow *window, int width, int height) { + (void)window; + glViewport(0, 0, width, height); +} + +static void gen_world(engine::NodeHierarchy &hierarchy, engine::Scene &scene, engine::NodeHandle &root) { + Map map(100, 15, 5, 10, 1337); + + engine::NodeHandle map_node = hierarchy.add_node( + { + .name = "Map", + .rotation = glm::quat(1, 0, 0, 0), + .scale = glm::vec3(5, 1, 5), + }, + root); + + engine::MeshHandle cube_mesh = scene.mesh_by_name("Cube"); + + for (size_t i = 0; i < map.grid.size(); ++i) { + for (size_t j = 0; j < map.grid[i].size(); ++j) { + if (map.grid[i][j] != -1) { + hierarchy.add_node( + { + .kind = engine::Node::Kind::mesh, + .name = std::format("cube_{}_{}", i, j), + .rotation = glm::quat(1, 0, 0, 0), + .translation = glm::vec3(map.grid.size() * -0.5 + (f32)i, -2.5, + map.grid[i].size() * -0.5 + (f32)j), + .scale = glm::vec3(1), + .mesh_index = cube_mesh.get_value(), + }, + map_node); + } + } + } +} + +int main(void) { + auto handle = game::init(1920, 1080, "Game"); + + // Register resources + INFO("Create resources"); + engine::Scene scene; + engine::Input input(handle.window); + engine::NodeHierarchy hierarchy; + + engine::NodeHandle root_node = hierarchy.add_root_node({ + .name = "Game", + .rotation = glm::quat(1, 0, 0, 0), + .scale = glm::vec3(1), + }); + + engine::Camera camera; + camera.init(glm::vec3(0.0f, 0.0f, 3.0f), 10.0f); + + engine::Renderer renderer; + renderer.init((engine::Renderer::LoadProc)glfwGetProcAddress); + { + auto data = engine::loader::load_asset_file("scene_data.bin"); + scene.init(data); + renderer.make_resources_for_scene(data); + } + + int width; + int height; + glfwGetFramebufferSize(handle.window, &width, &height); + glViewport(0, 0, width, height); + glfwSetInputMode(handle.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + gen_world(hierarchy, scene, root_node); + + // Register componets + INFO("Create componets"); + ECS::register_component(); + ECS::register_component(); + ECS::register_component(); + ECS::register_component(); + ECS::register_component(); + ECS::register_component(); + ECS::register_component(); + + // Register systems + INFO("Create systems"); + auto move_system = ECS::register_system(); + auto render_system = ECS::register_system(); + auto ui_render_system = ECS::register_system(); + auto player_controller_system = ECS::register_system(); + auto camera_system = ECS::register_system(); + auto delta_time_system = ECS::register_system(); + auto enemy_ghost_system = ECS::register_system(); + auto input_system = ECS::register_system(); + + // Create local player + INFO("Create player"); + Entity player = ECS::create_entity(); + ECS::add_component(player, CPlayer()); + ECS::add_component(player, CLocal()); + ECS::add_component(player, CSpeed()); + ECS::add_component(player, CHealth()); + ECS::add_component( + player, CTranslation{.pos = glm::vec3(0.0f), + .rot = glm::quat(1.0f, 0.0f, 0.0f, 0.0f), + .scale = glm::vec3(1.0f)}); + ECS::add_component(player, CVelocity{.vel = glm::vec3(0.0f)}); + + auto player_prefab = scene.prefab_by_name("Player"); + engine::NodeHandle player_node = + hierarchy.instantiate_prefab(scene, player_prefab, engine::NodeHandle(0)); + + ECS::add_component(player, CMesh(player_node)); + + // Create ghosts + auto ghost_prefab = scene.prefab_by_name("Ghost"); + for (int i = 0; i < 2; i++) { + Entity ghost = ECS::create_entity(); + engine::NodeHandle ghost_node = + hierarchy.instantiate_prefab(scene, ghost_prefab, engine::NodeHandle(0)); + ECS::add_component(ghost, CEnemyGhost({.cooldown = 0.0f})); + ECS::add_component(ghost, CSpeed{.speed = (float)(3 + i)}); + ECS::add_component(ghost, CHealth()); + ECS::add_component(ghost, CTranslation{ + .pos = glm::vec3(10.0f, 0.0f, i * 10.0f), + .rot = glm::quat(1.0f, 0.0f, 0.0f, 0.0f), + .scale = glm::vec3(1.0f), + }); + ECS::add_component(ghost, CVelocity()); + ECS::add_component(ghost, CMesh(ghost_node)); + } + + // Register resources + ECS::register_resource(new RInput(handle.window)); + ECS::register_resource(new RRenderer(renderer)); + ECS::register_resource(new RScene(scene, camera, handle.window, hierarchy, player)); + ECS::register_resource(new RDeltaTime(glfwGetTime())); + ECS::register_resource(new RAudio(camera)); + + RAudio::add_source(CAudioSource("assets/sounds/ghost_strolling.wav")); + + INFO("Begin game loop"); + + while (game::should_run(handle)) { + // Update + delta_time_system->update(); // updates delta_time + player_controller_system->update(); // player controller for local player(movement and stuff) + camera_system->update(); // camera control + enemy_ghost_system->update(); // enemy ghost movement and stuff + move_system->update(); // moves entities with velocity + + input_system->update(); // stores this frames keys as previous frames keys (do this last, before draw) + + // Draw + render_system->update(); // handle rendering + ui_render_system->update(); // handle rendering of UI + + + game::end_frame(handle); + } +} diff --git a/src/game/player.cpp b/src/game/player.cpp new file mode 100644 index 0000000..3e3e42d --- /dev/null +++ b/src/game/player.cpp @@ -0,0 +1,14 @@ +#include "player.h" + +Player::Player() : position{glm::vec3(0.0f)}, rotation{glm::quat(1.0f, 0.0f, 0.0f, 0.0f)}, scale{glm::vec3(1.0f)}, health{100}, speed{10} {} + +void Player::take_damage(float damage) { + health -= damage; + if (health < 0.0f) { + health = 0.0f; + } +} + +bool Player::is_alive() const { + return health > 0.0f; +} \ No newline at end of file diff --git a/src/game/player.h b/src/game/player.h index 805bd77..74412d4 100644 --- a/src/game/player.h +++ b/src/game/player.h @@ -9,4 +9,9 @@ struct Player { glm::vec3 scale; float health; float speed; + + Player(); + + void take_damage(float damage); + bool is_alive() const; }; \ No newline at end of file diff --git a/src/game/settings.h b/src/game/settings.h new file mode 100644 index 0000000..f20394d --- /dev/null +++ b/src/game/settings.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +struct Settings { + float sensitivity = 1.5; + + int key_forward = GLFW_KEY_W; + int key_backward = GLFW_KEY_S; + int key_left = GLFW_KEY_A; + int key_right = GLFW_KEY_D; + + int key_attack = GLFW_KEY_SPACE; +}; \ No newline at end of file diff --git a/src/game/state.h b/src/game/state.h index 0a35cc1..e348887 100644 --- a/src/game/state.h +++ b/src/game/state.h @@ -5,6 +5,7 @@ #include "engine/scene/Node.h" #include "engine/scene/Scene.h" #include "game/player.h" +#include "game/enemy.h" struct State { @@ -27,5 +28,6 @@ struct State { f32 sensitivity; Player player; - Player enemy; // well maybe the struct should be something else + Enemy enemy; + Enemy enemy2; }; \ No newline at end of file diff --git a/tools/asset_processor/CMakeLists.txt b/tools/asset_processor/CMakeLists.txt index 24418a6..38531b2 100644 --- a/tools/asset_processor/CMakeLists.txt +++ b/tools/asset_processor/CMakeLists.txt @@ -9,7 +9,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(SOURCE_FILES src/main.cpp src/AssetImporter.cpp - ../../src/engine/utils/logging.cpp ../../src/engine/scene/Node.cpp ../../src/engine/scene/AssetManifest.cpp ) diff --git a/tools/asset_processor/src/AssetImporter.cpp b/tools/asset_processor/src/AssetImporter.cpp index affa6fd..8577ad6 100644 --- a/tools/asset_processor/src/AssetImporter.cpp +++ b/tools/asset_processor/src/AssetImporter.cpp @@ -787,10 +787,6 @@ void AssetImporter::load_materials(const tinygltf::Model &model) { flags |= (u32)Material::Flags::has_emission_map; } - if (material.doubleSided) { - WARN("glTF model has double sided material which will not be rendered correctly"); - } - m_materials.push_back({ .flags = (Material::Flags)flags, .base_color_factor = glm::vec4(base_color_factor[0], base_color_factor[1], @@ -808,6 +804,7 @@ void AssetImporter::load_materials(const tinygltf::Model &model) { .emission_map = m_base_texture + material.emissiveTexture.index, .emission_factor = glm::vec3(emissive_factor[0], emissive_factor[1], emissive_factor[1]), + .is_double_sided = material.doubleSided, }); } } diff --git a/tools/asset_processor/src/main.cpp b/tools/asset_processor/src/main.cpp index 248b971..f3cf4a7 100644 --- a/tools/asset_processor/src/main.cpp +++ b/tools/asset_processor/src/main.cpp @@ -16,12 +16,6 @@ void write_data(std::vector& data, std::ofstream& stream, u32& num_bytes_writ using json = nlohmann::json; -template -void fatal(std::format_string fmt, Args&&... args) { - ERROR(fmt, std::forward(args)...); - exit(1); -} - static void parse_prefabs(AssetImporter& importer, AssetManifest& manifest, std::unordered_map& mesh_names_to_indices, json& prefabs) { @@ -35,23 +29,23 @@ static void parse_prefabs(AssetImporter& importer, AssetManifest& manifest, std::ifstream file(path); if (!file.is_open()) { - fatal("Failed to open prefab file {}", path); + FATAL("Failed to open prefab file {}", path); } json prefab = json::parse(file, nullptr, false); if (prefab.is_discarded()) { - fatal("Failed to parse json of prefab file {}", path); + FATAL("Failed to parse json of prefab file {}", path); } if (!prefab.contains("name")) { - fatal("Prefab {} has no name", path); + FATAL("Prefab {} has no name", path); } if (!prefab.contains("nodes")) { - fatal("Prefab {} has no nodes", path); + FATAL("Prefab {} has no nodes", path); } if (!prefab.contains("root")) { - fatal("Prefab {} has no specified root node", path); + FATAL("Prefab {} has no specified root node", path); } u32 root_node = prefab["root"]; @@ -74,7 +68,7 @@ static void parse_prefabs(AssetImporter& importer, AssetManifest& manifest, if (json_node.contains("mesh")) { std::string mesh_name = json_node["mesh"]; if (mesh_names_to_indices.find(mesh_name) == mesh_names_to_indices.end()) { - fatal( + FATAL( "Node {} in prefab {} specifies mesh name {} which is not declared in " "manifest file", node.name, path, mesh_name);