diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6bf870..f2d715a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(fever_engine resources/Model.cpp util/Log.cpp image.cpp + mesh.cpp gltf_loader.cpp ) diff --git a/src/Controller.cpp b/src/Controller.cpp index 1d82492..86b62e1 100644 --- a/src/Controller.cpp +++ b/src/Controller.cpp @@ -31,13 +31,6 @@ Controller::Controller() m_gameWindow->dimensions().second, postProcessingProgram) { - GltfLoader loader; - entt::resource_cache gltf_resource_cache; - entt::resource gltf_document = - gltf_resource_cache - .load("Lantern/glTF-Binary/Lantern.glb"_hs, "Lantern/glTF-Binary/Lantern.glb") - .first->second; - fx::gltf::ReadQuotas read_quotas{.MaxFileSize = 512 * 1024 * 1024, .MaxBufferByteLength = 512 * 1024 * 1024}; diff --git a/src/Scene.cpp b/src/Scene.cpp index cb4cc4c..725c234 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,20 +1,60 @@ #include "Scene.h" -#include -static void hallo() -{ - std::cout << "Testobj created!\n"; -} +#include "name.h" +#include "transform.h" +#include "util/Log.h" +using namespace entt::literals; + +// TODO: make scene initialization part of gltf loader as seen in bevy Scene::Scene() { - struct TestComponent - { - int x; - }; + GltfLoader loader{.image_cache = m_image_cache, + .material_cache = m_material_cache, + .mesh_cache = m_mesh_cache, + .gltf_mesh_cache = m_gltf_mesh_cache, + .gltf_node_cache = m_gltf_node_cache, + .gltf_scene_cache = m_gltf_scene_cache}; - auto entity = m_registry.create(); + entt::resource_cache gltf_resource_cache{loader}; - auto sink = m_registry.on_construct().before(); + std::filesystem::path document_path("ABeautifulGame.glb"); + // std::filesystem::path document_path("Lantern/glTF-Binary/Lantern.glb"); + entt::hashed_string document_hash(document_path.c_str()); - m_registry.emplace(entity, TestComponent{.x = 2}); -} \ No newline at end of file + entt::resource gltf_document = + gltf_resource_cache.load(document_hash, document_path).first->second; + + auto default_scene = gltf_document->default_scene; + + // Spawn an entity for every node in scene + for (auto const &node : default_scene->nodes) { + auto top_level_entity = m_registry.create(); + m_registry.emplace(top_level_entity, node->name); + m_registry.emplace(top_level_entity, node->transform); + + auto mesh = node->mesh; + if (mesh.has_value()) { + for (auto const &primitive : mesh.value()->primitives) { + auto mesh_entity = m_registry.create(); + m_registry.emplace(mesh_entity, Transform{}); + m_registry.emplace>(mesh_entity, primitive.mesh); + m_registry.emplace>(mesh_entity, primitive.material); + } + } + } + + auto name_view = m_registry.view(); + for (auto [entity, name] : name_view.each()) { + Log::logger().info("Hello entity {}!", name); + } + + auto mesh_view = m_registry.view(); + for (auto [entity, mesh] : mesh_view.each()) { + Log::logger().info("Convert mesh {}!"); + + } +} + +void Scene::update(std::chrono::duration delta) +{ +} diff --git a/src/Scene.h b/src/Scene.h index 5fc7985..740f8d7 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -1,12 +1,27 @@ #pragma once +#include "mesh.h" +#include "material.h" +#include "gltf_loader.h" + #include +#include class Scene { public: Scene(); + void update(std::chrono::duration delta); + private: entt::registry m_registry; + + // Resource caches + entt::resource_cache m_image_cache; + entt::resource_cache m_material_cache; + entt::resource_cache m_mesh_cache; + entt::resource_cache m_gltf_mesh_cache; + entt::resource_cache m_gltf_node_cache; + entt::resource_cache m_gltf_scene_cache; // May be moved out of Scene }; diff --git a/src/gltf_loader.cpp b/src/gltf_loader.cpp index ad61645..05d9eda 100644 --- a/src/gltf_loader.cpp +++ b/src/gltf_loader.cpp @@ -1,6 +1,8 @@ #include "gltf_loader.h" #include "util/Log.h" +#include + template static auto create_vertex_attribute_data(std::span vertex_attribute_data) -> VertexAttributeData @@ -89,6 +91,10 @@ static auto load_material(fx::gltf::Material const &material, load_texture(normal_texture, gltf, document_path, Image::ColorFormat::RGB, image_cache); } + if (material.name.empty()) { + Log::logger().warn("glTF material has no name."); + } + entt::hashed_string material_hash(material.name.c_str()); return material_cache .load(material_hash, @@ -170,6 +176,16 @@ auto load_gltf_primitive(fx::gltf::Primitive const &gltf_primitive, entt::resource_cache &mesh_cache) -> GltfPrimitive { // Load attributes + auto tangent_it = + std::find_if(gltf_primitive.attributes.cbegin(), + gltf_primitive.attributes.cend(), + [](auto const &attribute) { return attribute.first == "TANGENT"; }); + + if (tangent_it == gltf_primitive.attributes.cend()) { + Log::logger().critical("glTF scene has to include tangent and normal components!"); + std::terminate(); + } + std::map attributes; for (auto const &attribute : gltf_primitive.attributes) { auto vertex_attribute_data = load_attribute(attribute.first, attribute.second, gltf); @@ -214,7 +230,7 @@ auto load_gltf_primitive(fx::gltf::Primitive const &gltf_primitive, mesh_cache.load(mesh_hash, Mesh{.attributes = attributes, .indices = indices}) .first->second; - // Get material by name + // Get material by hash auto const &gltf_material = gltf.materials.at(gltf_primitive.material); entt::hashed_string material_hash(gltf_material.name.c_str()); entt::resource material = material_cache[material_hash]; @@ -262,6 +278,10 @@ auto GltfLoader::operator()(std::filesystem::path const &document_path) -> resul gltf_primitive, gltf, document_path, primitive_count, material_cache, mesh_cache)); } + if (gltf_mesh.name.empty()) { + Log::logger().warn("glTF mesh has no name."); + } + entt::hashed_string gltf_mesh_hash(gltf_mesh.name.c_str()); entt::resource gltf_mesh_resource = gltf_mesh_cache.load(gltf_mesh_hash, GltfMesh{.primitives = std::move(primitives)}) @@ -270,7 +290,96 @@ auto GltfLoader::operator()(std::filesystem::path const &document_path) -> resul gltf_meshes.push_back(gltf_mesh_resource); } + // Load nodes + std::unordered_map> nodes_map; + nodes_map.reserve(gltf.nodes.size()); + for (std::size_t i = 0; i < gltf.nodes.size(); ++i) { + auto const &node = gltf.nodes.at(i); + + auto mesh = [this, &node, &gltf]() -> std::optional> { + if (node.mesh != -1) { + // Get mesh by hash + auto const &gltf_mesh = gltf.meshes.at(node.mesh); + entt::hashed_string mesh_hash(gltf_mesh.name.c_str()); + entt::resource mesh = gltf_mesh_cache[mesh_hash]; + return {mesh}; + } + + return {}; + }(); + + glm::vec3 translation(node.translation[0], node.translation[1], node.translation[2]); + glm::quat rotation(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + glm::vec3 scale(node.scale[0], node.scale[1], node.scale[2]); + + Transform transform{.translation = translation, .rotation = rotation, .scale = scale}; + + if (node.name.empty()) { + Log::logger().warn("glTF node has no name."); + } + + entt::hashed_string node_hash(node.name.c_str()); + entt::resource node_resource = + gltf_node_cache + .load(node_hash, + GltfNode{ + .name = node.name, .transform = transform, .mesh = mesh, .children = {}}) + .first->second; + + nodes_map.emplace(i, node_resource); + } + + // Resolve child hierarchy + for (auto const &gltf_node : gltf.nodes) { + std::vector> children; + for (int child_node_id : gltf_node.children) { + auto child_node = nodes_map.extract(child_node_id); + children.push_back(child_node.mapped()); + } + } + + std::vector> nodes; + nodes.reserve(nodes_map.size()); + for (auto const &node : nodes_map) { + nodes.push_back(node.second); + } + + // Load scenes + std::vector> scenes; + for (auto const &scene : gltf.scenes) { + // Get nodes by hash + std::vector> nodes; + nodes.reserve(scene.nodes.size()); + + for (auto node_id : scene.nodes) { + auto const &node = gltf.nodes.at(node_id); + entt::hashed_string node_hash(node.name.c_str()); + nodes.push_back(gltf_node_cache[node_hash]); + } + + if (scene.name.empty()) { + Log::logger().warn("glTF scene has no name."); + } + + entt::hashed_string scene_hash(scene.name.c_str()); + entt::resource scene_resource = + gltf_scene_cache.load(scene_hash, GltfScene{.nodes = std::move(nodes)}).first->second; + scenes.push_back(scene_resource); + } + + // Default scene + entt::resource default_scene = [&gltf, &scenes]() { + if (gltf.scene != -1) { + return scenes.at(gltf.scene); + } + + return scenes.at(0); + }(); + return std::make_shared(Gltf{.materials = std::move(materials), .meshes = std::move(gltf_meshes), + .nodes = std::move(nodes), + .scenes = std::move(scenes), + .default_scene = default_scene, .document = std::move(gltf)}); } \ No newline at end of file diff --git a/src/gltf_loader.h b/src/gltf_loader.h index f8d8d24..9756fc0 100644 --- a/src/gltf_loader.h +++ b/src/gltf_loader.h @@ -3,6 +3,7 @@ #include "image.h" #include "material.h" #include "mesh.h" +#include "transform.h" #include #include @@ -21,11 +22,27 @@ struct GltfMesh std::vector primitives; }; +struct GltfNode +{ + std::string name; + Transform transform; + std::optional> mesh; + std::vector> children; +}; + +struct GltfScene { + std::vector> nodes; +}; + // Move to own file. struct Gltf { std::vector> materials; std::vector> meshes; + std::vector> nodes; + std::vector> scenes; + + entt::resource default_scene; fx::gltf::Document document; }; @@ -35,8 +52,11 @@ struct GltfLoader final auto operator()(std::filesystem::path const &document_path) -> result_type; - entt::resource_cache image_cache; - entt::resource_cache material_cache; - entt::resource_cache mesh_cache; - entt::resource_cache gltf_mesh_cache; + entt::resource_cache &image_cache; + entt::resource_cache &material_cache; + entt::resource_cache &mesh_cache; + + entt::resource_cache &gltf_mesh_cache; + entt::resource_cache &gltf_node_cache; + entt::resource_cache &gltf_scene_cache; }; diff --git a/src/image.h b/src/image.h index 1d15605..2abbf5e 100644 --- a/src/image.h +++ b/src/image.h @@ -43,7 +43,8 @@ struct Image std::vector data; Sampler sampler; - struct Extent { + struct Extent + { unsigned int width; unsigned int height; } extent{}; @@ -55,7 +56,8 @@ struct Image RGBA8Uint, } dataFormat; - enum class ColorFormat { + enum class ColorFormat + { SRGB, RGB } colorFormat; @@ -69,5 +71,18 @@ struct GpuImage GpuImage(Image const &image); + GpuImage(GpuImage const &) = delete; + auto operator=(GpuImage const &) -> GpuImage & = delete; + + GpuImage(GpuImage &&other) noexcept : texture(other.texture) { other.texture = 0; } + auto operator=(GpuImage &&other) noexcept -> GpuImage & + { + texture = other.texture; + other.texture = 0; + return *this; + }; + + ~GpuImage() { glDeleteTextures(1, &texture); }; + GLuint texture{}; }; diff --git a/src/mesh.cpp b/src/mesh.cpp new file mode 100644 index 0000000..6dbe52e --- /dev/null +++ b/src/mesh.cpp @@ -0,0 +1,30 @@ +#include "mesh.h" + +GpuMesh::GpuMesh(Mesh const &mesh) +{ + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + for (auto const &[attribute_id, attribute_data] : mesh.attributes) { + // BUG: https://github.com/llvm/llvm-project/issues/48582 + auto attr_id = attribute_id; + + std::visit([=](auto&& arg) { + GLuint vbo{}; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, arg.size(), arg.data(), GL_STATIC_DRAW); + + glEnableVertexAttribArray(attr_id); + glVertexAttribPointer( + attr_id, + accessor_byte_size(position_accessor.type), + static_cast(position_accessor.componentType), + position_accessor.normalized ? GL_TRUE : GL_FALSE, + position_buffer_view.byteStride, + reinterpret_cast( + position_accessor + .byteOffset)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast, + // performance-no-int-to-ptr) + } + } diff --git a/src/mesh.h b/src/mesh.h index bb07b1a..1ea816b 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include #include +#include struct VertexAttributeData { @@ -35,3 +37,24 @@ struct Mesh std::map attributes; Indices indices; }; + +struct GpuMesh +{ + GpuMesh(Mesh const &mesh); + + GpuMesh(GpuMesh const &) = delete; + auto operator=(GpuMesh const &) -> GpuMesh & = delete; + + GpuMesh(GpuMesh &&other) noexcept : vao(other.vao) { other.vao = 0; } + auto operator=(GpuMesh &&other) noexcept -> GpuMesh & + { + vao = other.vao; + other.vao = 0; + return *this; + }; + + ~GpuMesh() { glDeleteVertexArrays(1, &vao); }; + + // TODO: also store vertex buffers. + GLuint vao{}; +}; diff --git a/src/name.h b/src/name.h new file mode 100644 index 0000000..fc41130 --- /dev/null +++ b/src/name.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +using Name = std::string; diff --git a/src/transform.h b/src/transform.h new file mode 100644 index 0000000..85ba841 --- /dev/null +++ b/src/transform.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +struct Transform +{ + glm::vec3 translation; + glm::quat rotation; + glm::vec3 scale{1.0, 1.0, 1.0}; +}; + +using GlobalTransform = Transform;