From 7fe04414bd25604c747379f141de964fb8a9181c Mon Sep 17 00:00:00 2001 From: jnyfah Date: Tue, 26 Nov 2024 18:19:02 +0100 Subject: [PATCH 1/8] checkpoint - at this point i am still confused lol --- .gitmodules | 3 +++ __externals/rapidobj | 1 + 2 files changed, 4 insertions(+) create mode 160000 __externals/rapidobj diff --git a/.gitmodules b/.gitmodules index 35c1ff68..a482f9fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,3 +58,6 @@ [submodule "__externals/Vulkan-Headers"] path = __externals/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers +[submodule "__externals/rapidobj"] + path = __externals/rapidobj + url = https://github.com/guybrush77/rapidobj diff --git a/__externals/rapidobj b/__externals/rapidobj new file mode 160000 index 00000000..744374a5 --- /dev/null +++ b/__externals/rapidobj @@ -0,0 +1 @@ +Subproject commit 744374a5d21fe01704eab0f36e633e8d620265e5 From 5d8fb19d1f4fac402eff20fa3226380b8a07e0b8 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Tue, 26 Nov 2024 18:19:20 +0100 Subject: [PATCH 2/8] checkpoint --- CMakeLists.txt | 1 + Scripts/BuildEngine.ps1 | 1 + .../Components/DockspaceUIComponent.cpp | 4 +- Tetragrama/Importers/RapidobjImporter.cpp | 596 ++++++++++++++++++ Tetragrama/Importers/RapidobjImporter.h | 33 + 5 files changed, 633 insertions(+), 2 deletions(-) create mode 100644 Tetragrama/Importers/RapidobjImporter.cpp create mode 100644 Tetragrama/Importers/RapidobjImporter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b33f0fa..b082b2c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ if (NOT LAUNCHER_ONLY) add_subdirectory (${EXTERNAL_DIR}/glm) add_subdirectory (${EXTERNAL_DIR}/entt) add_subdirectory (${EXTERNAL_DIR}/assimp) + add_subdirectory (${EXTERNAL_DIR}/rapidobj) add_subdirectory (${EXTERNAL_DIR}/stduuid) add_subdirectory (${EXTERNAL_DIR}/yaml-cpp) add_subdirectory (${EXTERNAL_DIR}/SPIRV-headers) diff --git a/Scripts/BuildEngine.ps1 b/Scripts/BuildEngine.ps1 index e96e26fd..d87949c6 100644 --- a/Scripts/BuildEngine.ps1 +++ b/Scripts/BuildEngine.ps1 @@ -148,6 +148,7 @@ function Build([string]$configuration, [int]$VsVersion , [bool]$runBuild) { 'SPDLOG' = @("-DSPDLOG_BUILD_SHARED=OFF", "-DBUILD_STATIC_LIBS=ON", "-DSPDLOG_FMT_EXTERNAL=ON", "-DSPDLOG_FMT_EXTERNAL_HO=OFF"); 'GLFW ' = @("-DGLFW_BUILD_DOCS=OFF", "-DGLFW_BUILD_EXAMPLES=OFF", "-DGLFW_INSTALL=OFF"); 'ASSIMP' = @("-DASSIMP_BUILD_TESTS=OFF", "-DASSIMP_INSTALL=OFF", "-DASSIMP_BUILD_SAMPLES=OFF", "-DASSIMP_BUILD_ASSIMP_TOOLS=OFF"); + 'RAPIDOBJ' = @("-DRAPIDOBJ_BuildTests=OFF", "-DRAPIDOBJ_BuildTools=OFF", "-DRAPIDOBJ_BuildExampleS=OFF"); 'STDUUID' = @("-DUUID_BUILD_TESTS=OFF", "-DUUID_USING_CXX20_SPAN=ON", "-DUUID_SYSTEM_GENERATOR=OFF"); 'YAMLCPP' = @("-DYAML_CPP_BUILD_TOOLS=OFF", "-DYAML_CPP_BUILD_TESTS=OFF", "-DYAML_CPP_FORMAT_SOURCE=OFF", "-DYAML_BUILD_SHARED_LIBS=OFF"); 'FRAMEWORK' = @("-DBUILD_FRAMEWORK=ON"); diff --git a/Tetragrama/Components/DockspaceUIComponent.cpp b/Tetragrama/Components/DockspaceUIComponent.cpp index cf718432..48f17e68 100644 --- a/Tetragrama/Components/DockspaceUIComponent.cpp +++ b/Tetragrama/Components/DockspaceUIComponent.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -23,7 +23,7 @@ namespace Tetragrama::Components float DockspaceUIComponent::s_editor_scene_serializer_progress = 0.0f; DockspaceUIComponent::DockspaceUIComponent(std::string_view name, bool visibility) - : UIComponent(name, visibility, false), m_asset_importer(CreateScope()), m_editor_serializer(CreateScope()) + : UIComponent(name, visibility, false), m_asset_importer(CreateScope()), m_editor_serializer(CreateScope()) { m_dockspace_node_flag = ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_PassthruCentralNode; m_window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | diff --git a/Tetragrama/Importers/RapidobjImporter.cpp b/Tetragrama/Importers/RapidobjImporter.cpp new file mode 100644 index 00000000..9022d5ed --- /dev/null +++ b/Tetragrama/Importers/RapidobjImporter.cpp @@ -0,0 +1,596 @@ +#include +#include +#include +#include +#include +#include + +namespace Tetragrama::Importers +{ + RapidobjImporter::RapidobjImporter() {} + + RapidobjImporter::~RapidobjImporter() {} + + std::future RapidobjImporter::ImportAsync(std::string_view filename, ImportConfiguration config) + { + std::filesystem::path obj_path = filename.data(); + m_base_dir = obj_path.parent_path(); // Save the base directory + + ThreadPoolHelper::Submit([this, path = std::string(filename.data()), config] { + { + std::unique_lock l(m_mutex); + m_is_importing = true; + } + + rapidobj::Result result = rapidobj::ParseFile(path); + if (result.error) + { + if (m_error_callback) + { + m_error_callback(result.error.line); + } + } + else + { + ImporterData import_data = {}; + PreprocessMesh(result); + ExtractMeshes(result, import_data); + ExtractMaterials(result, import_data); + ExtractTextures(result, import_data); + CreateHierarchyScene(result, import_data); + /* + * Serialization of ImporterData + */ + ZENGINE_CORE_INFO("Serializing model...") + SerializeImporterData(import_data, config); + + if (m_complete_callback) + { + m_complete_callback(std::move(import_data)); + } + } + { + std::unique_lock l(m_mutex); + m_is_importing = false; + } + }); + co_return; + } + + void RapidobjImporter::PreprocessMesh(rapidobj::Result& result) + { + if (!rapidobj::Triangulate(result)) + { + ZENGINE_CORE_INFO("Cannot triangulate model...") + } + // Step 1: Generate normals if they don't exist + if (result.attributes.normals.empty()) + { + ZENGINE_CORE_INFO("Generating normals..."); + rapidobj::Array normals(result.attributes.positions.size()); + + // Initialize normals to zero + for (size_t i = 0; i < normals.size(); ++i) + { + normals[i] = 0.0f; + } + + for (const auto& shape : result.shapes) + { + for (size_t f = 0; f < shape.mesh.indices.size(); f += 3) + { + const auto& idx0 = shape.mesh.indices[f]; + const auto& idx1 = shape.mesh.indices[f + 1]; + const auto& idx2 = shape.mesh.indices[f + 2]; + + glm::vec3 v0( + result.attributes.positions[idx0.position_index * 3], + result.attributes.positions[idx0.position_index * 3 + 1], + result.attributes.positions[idx0.position_index * 3 + 2]); + glm::vec3 v1( + result.attributes.positions[idx1.position_index * 3], + result.attributes.positions[idx1.position_index * 3 + 1], + result.attributes.positions[idx1.position_index * 3 + 2]); + glm::vec3 v2( + result.attributes.positions[idx2.position_index * 3], + result.attributes.positions[idx2.position_index * 3 + 1], + result.attributes.positions[idx2.position_index * 3 + 2]); + + glm::vec3 edge1 = v1 - v0; + glm::vec3 edge2 = v2 - v0; + glm::vec3 normal = glm::cross(edge1, edge2); + + if (glm::dot(normal, normal) > 0.0f) + { + normal = glm::normalize(normal); + for (int i = 0; i < 3; i++) + { + size_t idx = shape.mesh.indices[f + i].position_index * 3; + normals[idx] += normal.x; + normals[idx + 1] += normal.y; + normals[idx + 2] += normal.z; + } + } + } + } + + for (size_t i = 0; i < normals.size(); i += 3) + { + glm::vec3 n(normals[i], normals[i + 1], normals[i + 2]); + if (glm::dot(n, n) > 0.0f) + { + n = glm::normalize(n); + normals[i] = n.x; + normals[i + 1] = n.y; + normals[i + 2] = n.z; + } + else + { + // Default to "up" vector + normals[i] = 0.0f; + normals[i + 1] = 1.0f; + normals[i + 2] = 0.0f; + } + } + + result.attributes.normals = std::move(normals); + } + + // Step 2: Generate texture coordinates if they don't exist + if (result.attributes.texcoords.empty()) + { + ZENGINE_CORE_INFO("Generating default UV coordinates..."); + + glm::vec3 min_pos(std::numeric_limits::max()); + glm::vec3 max_pos(std::numeric_limits::lowest()); + + for (size_t i = 0; i < result.attributes.positions.size(); i += 3) + { + glm::vec3 pos(result.attributes.positions[i], result.attributes.positions[i + 1], result.attributes.positions[i + 2]); + min_pos = glm::min(min_pos, pos); + max_pos = glm::max(max_pos, pos); + } + + glm::vec3 size = max_pos - min_pos; + + // Ensure the UV array is the correct size + rapidobj::Array texcoords(result.attributes.positions.size() / 3 * 2); + + for (size_t i = 0; i < result.attributes.positions.size(); i += 3) + { + float u = (result.attributes.positions[i] - min_pos.x) / size.x; + float v = (result.attributes.positions[i + 2] - min_pos.z) / size.z; + + texcoords[(i / 3) * 2] = u; + texcoords[(i / 3) * 2 + 1] = v; + } + + result.attributes.texcoords = std::move(texcoords); + } + } + + void RapidobjImporter::ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data) + { + const auto& attributes = result.attributes; + const auto& shapes = result.shapes; + + if (shapes.empty() || attributes.positions.empty()) + { + ZENGINE_CORE_INFO("No shapes or vertices found in the OBJ file."); + return; + } + + importer_data.Scene.Meshes.reserve(shapes.size()); + + for (size_t shape_idx = 0; shape_idx < shapes.size(); ++shape_idx) + { + const auto& shape = shapes[shape_idx]; + ZENGINE_CORE_INFO(fmt::format("Processing shape {}/{}: {}", shape_idx + 1, shapes.size(), !shape.name.empty() ? shape.name : "").c_str()); + + // Track mesh statistics + uint32_t vertex_count{0}; + uint32_t index_count{0}; + + // Process each face + for (size_t face = 0; face < shape.mesh.indices.size(); face += 3) + { + for (size_t v = 0; v < 3; ++v) + { + const auto& index = shape.mesh.indices[face + v]; + + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3]); + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 1]); + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 2]); + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3]); + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 1]); + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 2]); + importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2]); + importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2 + 1]); + + // Add index + importer_data.Scene.Indices.push_back(importer_data.VertexOffset + vertex_count); + vertex_count++; + index_count++; + } + } + + // Create mesh entry + MeshVNext& mesh = importer_data.Scene.Meshes.emplace_back(); + mesh.VertexCount = vertex_count; + mesh.VertexOffset = importer_data.VertexOffset; + mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); // pos(3) + normal(3) + uv(2) + mesh.StreamOffset = mesh.VertexUnitStreamSize * mesh.VertexOffset; + mesh.IndexOffset = importer_data.IndexOffset; + mesh.IndexCount = index_count; + mesh.IndexUnitStreamSize = sizeof(uint32_t); + mesh.IndexStreamOffset = mesh.IndexUnitStreamSize * mesh.IndexOffset; + mesh.TotalByteSize = (mesh.VertexCount * mesh.VertexUnitStreamSize) + (mesh.IndexCount * mesh.IndexUnitStreamSize); + + // Update offsets for next mesh + importer_data.VertexOffset += vertex_count; + importer_data.IndexOffset += index_count; + } + } + + void RapidobjImporter::ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data) + { + static constexpr float OPAQUENESS_THRESHOLD = 0.05f; + const uint32_t number_of_materials = static_cast(result.materials.size()); + + ZENGINE_CORE_INFO(fmt::format("Processing {} materials", number_of_materials).c_str()); + + // Pre-allocate space + importer_data.Scene.Materials.reserve(number_of_materials); + importer_data.Scene.MaterialNames.reserve(number_of_materials); + + // Helper function to convert Float3 to vec4 + auto toVec4 = [](const rapidobj::Float3& rgb, float alpha = 1.0f) -> ZEngine::Rendering::gpuvec4 { + return ZEngine::Rendering::gpuvec4{std::clamp(rgb[0], 0.0f, 1.0f), std::clamp(rgb[1], 0.0f, 1.0f), std::clamp(rgb[2], 0.0f, 1.0f), std::clamp(alpha, 0.0f, 1.0f)}; + }; + + // Process each material + for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) + { + const auto& obj_material = result.materials[mat_idx]; + MeshMaterial& material = importer_data.Scene.Materials.emplace_back(); + + // Store material name + std::string material_name = obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name; + importer_data.Scene.MaterialNames.push_back(std::move(material_name)); + + // Convert material properties + material.AmbientColor = toVec4(obj_material.ambient); + material.AlbedoColor = toVec4(obj_material.diffuse); + material.SpecularColor = toVec4(obj_material.specular); + material.EmissiveColor = toVec4(obj_material.emission); + + // Handle transparency/opacity + float opacity = obj_material.dissolve; + material.Factors.x = (opacity < 1.0f) ? std::clamp(1.0f - opacity, 0.0f, 1.0f) : 0.0f; + if (material.Factors.x >= (1.0f - OPAQUENESS_THRESHOLD)) + { + material.Factors.x = 0.0f; + } + + // Handle transmission filter + float transmission = std::max({obj_material.transmittance[0], obj_material.transmittance[1], obj_material.transmittance[2]}); + if (transmission > 0.0f) + { + material.Factors.x = std::clamp(transmission, 0.0f, 1.0f); + material.Factors.z = 0.5f; + } + + // Handle shininess and illumination model + material.Factors.y = std::clamp(obj_material.shininess / 1000.0f, 0.0f, 1.0f); + material.Factors.w = static_cast(obj_material.illum) / 10.0f; + + // Initialize texture indices + material.AlbedoMap = 0xFFFFFFFF; + material.NormalMap = 0xFFFFFFFF; + material.SpecularMap = 0xFFFFFFFF; + material.EmissiveMap = 0xFFFFFFFF; + material.OpacityMap = 0xFFFFFFFF; + } + + // Map materials to shapes + for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) + { + const auto& shape = result.shapes[shape_idx]; + + if (!shape.mesh.material_ids.empty()) + { + std::vector material_counts(number_of_materials, 0); + for (int32_t mat_id : shape.mesh.material_ids) + { + if (mat_id >= 0 && static_cast(mat_id) < material_counts.size()) + { + material_counts[mat_id]++; + } + } + + auto most_frequent = std::max_element(material_counts.begin(), material_counts.end()); + if (most_frequent != material_counts.end()) + { + size_t material_id = std::distance(material_counts.begin(), most_frequent); + for (const auto& [node_id, mesh_idx] : importer_data.Scene.NodeMeshes) + { + if (mesh_idx == shape_idx) + { + importer_data.Scene.NodeMaterials[node_id] = static_cast(material_id); + } + } + } + } + } + } + + int RapidobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) + { + auto find = std::find(std::begin(data), std::end(data), filename); + if (find != std::end(data)) + { + return std::distance(std::begin(data), find); + } + + data.push_back(filename.data()); + return (data.size() - 1); + } + + void RapidobjImporter::ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data) + { + if (result.materials.empty()) + { + ZENGINE_CORE_INFO("No materials found for texture extraction"); + return; + } + + const uint32_t number_of_materials = static_cast(result.materials.size()); + ZENGINE_CORE_INFO(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); + + // Helper function to process texture paths and generate file index + auto processTexturePath = [this, &importer_data](const std::string& texname, const std::string& type) -> uint32_t { + if (texname.empty()) + { + ZENGINE_CORE_INFO(fmt::format("No {} texture specified", type)); + return 0xFFFFFFFF; // Invalid texture index + } + + // Normalize and resolve the path relative to the base directory + std::filesystem::path texture_path = texname; + if (!texture_path.is_absolute()) + { + texture_path = m_base_dir / texture_path; + } + + if (!std::filesystem::exists(texture_path)) + { + ZENGINE_CORE_INFO(fmt::format("Warning: {} texture not found at path: {}", type, texture_path.string())); + return 0xFFFFFFFF; // Invalid texture index + } + + ZENGINE_CORE_INFO(fmt::format("Found {} texture: {}", type, texture_path.string())); + return GenerateFileIndex(importer_data.Scene.Files, texname); + }; + + for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) + { + const auto& obj_material = result.materials[mat_idx]; + auto& material = importer_data.Scene.Materials[mat_idx]; + + ZENGINE_CORE_INFO(fmt::format( + "Processing textures for material {}/{}: {}", + mat_idx + 1, + number_of_materials, + obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name) + .c_str()); + + // Diffuse/Albedo texture + ZENGINE_CORE_INFO(fmt::format("Raw texname value: {}", obj_material.diffuse_texname)); + material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); + + // Specular texture + ZENGINE_CORE_INFO(fmt::format("Raw texname value: {}", obj_material.specular_texname)); + material.SpecularMap = processTexturePath(obj_material.specular_texname, "specular"); + + // Emissive texture + material.EmissiveMap = processTexturePath(obj_material.emissive_texname, "emissive"); + + // Normal/Bump map + material.NormalMap = !obj_material.bump_texname.empty() ? processTexturePath(obj_material.bump_texname, "bump") + : !obj_material.normal_texname.empty() ? processTexturePath(obj_material.normal_texname, "normal") + : !obj_material.displacement_texname.empty() ? processTexturePath(obj_material.displacement_texname, "displacement as normal") + : 0xFFFFFFFF; + + // Opacity/Alpha texture + material.OpacityMap = processTexturePath(obj_material.alpha_texname, "opacity"); + + // Process additional textures (PBR, unsupported types) + ProcessAdditionalTextures(obj_material, material); + + // Validate texture assignments + ValidateTextureAssignments(material); + } + + ZENGINE_CORE_INFO(fmt::format("Total unique textures processed: {}", importer_data.Scene.Files.size()).c_str()); + } + void RapidobjImporter::ProcessAdditionalTextures(const rapidobj::Material& obj_material, MeshMaterial& material) + { + // Process ambient occlusion texture if available + if (!obj_material.ambient_texname.empty()) + { + ZENGINE_CORE_INFO("Ambient occlusion texture found but not supported in current material format"); + } + + // Check for PBR textures + bool has_pbr_textures = false; + if (!obj_material.roughness_texname.empty()) + { + has_pbr_textures = true; + ZENGINE_CORE_INFO("Roughness texture found"); + } + if (!obj_material.metallic_texname.empty()) + { + has_pbr_textures = true; + ZENGINE_CORE_INFO("Metallic texture found"); + } + if (!obj_material.sheen_texname.empty()) + { + has_pbr_textures = true; + ZENGINE_CORE_INFO("Sheen texture found"); + } + + if (has_pbr_textures) + { + ZENGINE_CORE_INFO("Note: Some PBR textures found but not supported in current material format"); + } + } + + void RapidobjImporter::ValidateTextureAssignments(MeshMaterial& material) + { + // Check for invalid texture combinations + if (material.OpacityMap != 0xFFFFFFFF) + { + // If we have an opacity map, ensure the material is marked as having transparency + if (material.Factors.x == 0.0f) + { + material.Factors.x = 0.5f; + ZENGINE_CORE_INFO("Adjusted material transparency factor due to opacity map"); + } + } + + // Check for normal map without proper factors + if (material.NormalMap != 0xFFFFFFFF && material.Factors.w == 0.0f) + { + material.Factors.w = 1.0f; // Default normal map intensity + ZENGINE_CORE_INFO("Set default normal map intensity factor"); + } + } + bool RapidobjImporter::ValidateFileExists(const std::string& filepath) + { + std::ifstream file(filepath); + return file.good(); + } + + void RapidobjImporter::CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data) + { + if (result.shapes.empty()) + { + ZENGINE_CORE_INFO("No shapes found in the OBJ file for hierarchy creation."); + return; + } + + // Create root node + auto root_node_id = SceneRawData::AddNode(&importer_data.Scene, -1, 0); + importer_data.Scene.NodeNames[root_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back("Root"); + importer_data.Scene.GlobalTransformCollection[root_node_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[root_node_id] = glm::mat4(1.0f); + + // Group shapes by material and calculate shape bounding boxes + struct MaterialGroup + { + std::string name; + std::vector shape_indices; + int32_t material_id{-1}; + glm::vec3 min_bounds{std::numeric_limits::max()}; + glm::vec3 max_bounds{std::numeric_limits::lowest()}; + }; + + std::map material_groups; + + for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) + { + const auto& shape = result.shapes[shape_idx]; + int32_t primary_material_id = -1; + + // Determine primary material + if (!shape.mesh.material_ids.empty()) + { + std::map material_counts; + for (int32_t mat_id : shape.mesh.material_ids) + { + material_counts[mat_id]++; + } + + auto most_frequent = std::max_element(material_counts.begin(), material_counts.end(), [](const auto& p1, const auto& p2) { + return p1.second < p2.second; + }); + + if (most_frequent != material_counts.end()) + { + primary_material_id = most_frequent->first; + } + } + + // Assign to material group + auto& group = material_groups[primary_material_id]; + group.material_id = primary_material_id; + group.shape_indices.push_back(shape_idx); + + if (group.name.empty()) + { + group.name = (primary_material_id >= 0 && primary_material_id < static_cast(result.materials.size())) ? result.materials[primary_material_id].name + : ""; + } + + // Calculate shape bounds + for (const auto& index : shape.mesh.indices) + { + if (index.position_index >= 0 && index.position_index * 3 + 2 < result.attributes.positions.size()) + { + glm::vec3 position( + result.attributes.positions[index.position_index * 3], + result.attributes.positions[index.position_index * 3 + 1], + result.attributes.positions[index.position_index * 3 + 2]); + group.min_bounds = glm::min(group.min_bounds, position); + group.max_bounds = glm::max(group.max_bounds, position); + } + } + } + + // Create material group nodes + for (const auto& [material_id, group] : material_groups) + { + auto material_node_id = SceneRawData::AddNode(&importer_data.Scene, root_node_id, 1); + importer_data.Scene.NodeNames[material_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back(fmt::format("Material_Group_{}", group.name)); + + glm::vec3 group_center = (group.max_bounds + group.min_bounds) * 0.5f; + glm::vec3 group_scale = group.max_bounds - group.min_bounds; + + // Construct translation matrix + glm::mat4 translation_matrix = glm::mat4(1.0f); + translation_matrix[3] = glm::vec4(group_center, 1.0f); // Set translation vector + + // Construct scaling matrix + glm::mat4 scaling_matrix = glm::mat4(1.0f); + scaling_matrix[0][0] = group_scale.x; // Scale X + scaling_matrix[1][1] = group_scale.y; // Scale Y + scaling_matrix[2][2] = group_scale.z; // Scale Z + + // Combine scaling and translation into a single transform + glm::mat4 group_transform = scaling_matrix * translation_matrix; + + // Assign transforms + importer_data.Scene.GlobalTransformCollection[material_node_id] = group_transform; + importer_data.Scene.LocalTransformCollection[material_node_id] = group_transform; + + // Create shape nodes + for (size_t shape_idx : group.shape_indices) + { + const auto& shape = result.shapes[shape_idx]; + auto shape_node_id = SceneRawData::AddNode(&importer_data.Scene, material_node_id, 2); + + importer_data.Scene.NodeNames[shape_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back(!shape.name.empty() ? shape.name : fmt::format("Shape_{}", shape_idx)); + + importer_data.Scene.NodeMeshes[shape_node_id] = static_cast(shape_idx); + importer_data.Scene.NodeMaterials[shape_node_id] = static_cast(material_id >= 0 ? material_id : 0); + } + } + + ZENGINE_CORE_INFO(fmt::format("Created scene hierarchy with {} material groups and {} shapes.", material_groups.size(), result.shapes.size()).c_str()); + // ValidateSceneHierarchy(importer_data.Scene); + } + +} // namespace Tetragrama::Importers \ No newline at end of file diff --git a/Tetragrama/Importers/RapidobjImporter.h b/Tetragrama/Importers/RapidobjImporter.h new file mode 100644 index 00000000..a8fd9244 --- /dev/null +++ b/Tetragrama/Importers/RapidobjImporter.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +using namespace ZEngine::Helpers; +using namespace ZEngine::Rendering::Meshes; +using namespace ZEngine::Rendering::Scenes; + +namespace Tetragrama::Importers +{ + + class RapidobjImporter : public IAssetImporter + { + public: + RapidobjImporter(); + virtual ~RapidobjImporter(); + std::future ImportAsync(std::string_view filename, ImportConfiguration config = {}) override; + + private: + void PreprocessMesh(rapidobj::Result&); + void ExtractMeshes(const rapidobj::Result&, ImporterData&); + void ExtractMaterials(const rapidobj::Result&, ImporterData&); + void ExtractTextures(const rapidobj::Result&, ImporterData&); + int GenerateFileIndex(std::vector& data, std::string_view filename); + void ValidateTextureAssignments(MeshMaterial& material); + void ProcessAdditionalTextures(const rapidobj::Material&, MeshMaterial& material); + bool ValidateFileExists(const std::string& filepath); + void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); + void ValidateSceneHierarchy(const SceneRawData& scene); + std::filesystem::path m_base_dir; + }; +} // namespace Tetragrama::Importers \ No newline at end of file From e8eab04e993a208a390ce8797284fe0901a58c75 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Wed, 27 Nov 2024 15:49:53 +0100 Subject: [PATCH 3/8] preprocess and extract mesh checkpoint --- Tetragrama/Importers/RapidobjImporter.cpp | 395 ++++++++++------------ Tetragrama/Importers/RapidobjImporter.h | 77 ++++- 2 files changed, 236 insertions(+), 236 deletions(-) diff --git a/Tetragrama/Importers/RapidobjImporter.cpp b/Tetragrama/Importers/RapidobjImporter.cpp index 9022d5ed..9bdfb36b 100644 --- a/Tetragrama/Importers/RapidobjImporter.cpp +++ b/Tetragrama/Importers/RapidobjImporter.cpp @@ -7,22 +7,19 @@ namespace Tetragrama::Importers { - RapidobjImporter::RapidobjImporter() {} + RapidobjImporter::RapidobjImporter() = default; - RapidobjImporter::~RapidobjImporter() {} + RapidobjImporter::~RapidobjImporter() = default; std::future RapidobjImporter::ImportAsync(std::string_view filename, ImportConfiguration config) { - std::filesystem::path obj_path = filename.data(); - m_base_dir = obj_path.parent_path(); // Save the base directory - ThreadPoolHelper::Submit([this, path = std::string(filename.data()), config] { { std::unique_lock l(m_mutex); m_is_importing = true; } + auto result = rapidobj::ParseFile(path); - rapidobj::Result result = rapidobj::ParseFile(path); if (result.error) { if (m_error_callback) @@ -41,7 +38,7 @@ namespace Tetragrama::Importers /* * Serialization of ImporterData */ - ZENGINE_CORE_INFO("Serializing model...") + REPORT_LOG("Serializing model...") SerializeImporterData(import_data, config); if (m_complete_callback) @@ -59,17 +56,10 @@ namespace Tetragrama::Importers void RapidobjImporter::PreprocessMesh(rapidobj::Result& result) { - if (!rapidobj::Triangulate(result)) - { - ZENGINE_CORE_INFO("Cannot triangulate model...") - } - // Step 1: Generate normals if they don't exist + // Generate normals if they don't exist if (result.attributes.normals.empty()) { - ZENGINE_CORE_INFO("Generating normals..."); rapidobj::Array normals(result.attributes.positions.size()); - - // Initialize normals to zero for (size_t i = 0; i < normals.size(); ++i) { normals[i] = 0.0f; @@ -96,9 +86,7 @@ namespace Tetragrama::Importers result.attributes.positions[idx2.position_index * 3 + 1], result.attributes.positions[idx2.position_index * 3 + 2]); - glm::vec3 edge1 = v1 - v0; - glm::vec3 edge2 = v2 - v0; - glm::vec3 normal = glm::cross(edge1, edge2); + glm::vec3 normal = glm::cross(v1 - v0, v2 - v0); if (glm::dot(normal, normal) > 0.0f) { @@ -119,24 +107,21 @@ namespace Tetragrama::Importers glm::vec3 n(normals[i], normals[i + 1], normals[i + 2]); if (glm::dot(n, n) > 0.0f) { - n = glm::normalize(n); - normals[i] = n.x; - normals[i + 1] = n.y; - normals[i + 2] = n.z; + n = glm::normalize(n); } else { - // Default to "up" vector - normals[i] = 0.0f; - normals[i + 1] = 1.0f; - normals[i + 2] = 0.0f; + n = glm::vec3(0.0f, 1.0f, 0.0f); } + normals[i] = n.x; + normals[i + 1] = n.y; + normals[i + 2] = n.z; } result.attributes.normals = std::move(normals); } - // Step 2: Generate texture coordinates if they don't exist + // Generate texture coordinates if they don't exist if (result.attributes.texcoords.empty()) { ZENGINE_CORE_INFO("Generating default UV coordinates..."); @@ -153,7 +138,6 @@ namespace Tetragrama::Importers glm::vec3 size = max_pos - min_pos; - // Ensure the UV array is the correct size rapidobj::Array texcoords(result.attributes.positions.size() / 3 * 2); for (size_t i = 0; i < result.attributes.positions.size(); i += 3) @@ -167,6 +151,13 @@ namespace Tetragrama::Importers result.attributes.texcoords = std::move(texcoords); } + // Triangulate + if (!rapidobj::Triangulate(result)) + { + REPORT_LOG("Cannot triangulate model...") + } + + REPORT_LOG("Preprocessing complete."); } void RapidobjImporter::ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data) @@ -176,7 +167,7 @@ namespace Tetragrama::Importers if (shapes.empty() || attributes.positions.empty()) { - ZENGINE_CORE_INFO("No shapes or vertices found in the OBJ file."); + REPORT_LOG("No shapes or vertices found in the OBJ file."); return; } @@ -185,94 +176,109 @@ namespace Tetragrama::Importers for (size_t shape_idx = 0; shape_idx < shapes.size(); ++shape_idx) { const auto& shape = shapes[shape_idx]; - ZENGINE_CORE_INFO(fmt::format("Processing shape {}/{}: {}", shape_idx + 1, shapes.size(), !shape.name.empty() ? shape.name : "").c_str()); - // Track mesh statistics - uint32_t vertex_count{0}; - uint32_t index_count{0}; + std::unordered_map unique_vertices; + const size_t face_count = shape.mesh.indices.size() / 3; - // Process each face - for (size_t face = 0; face < shape.mesh.indices.size(); face += 3) + for (size_t f = 0; f < face_count; ++f) { for (size_t v = 0; v < 3; ++v) { - const auto& index = shape.mesh.indices[face + v]; - - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3]); - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 1]); - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 2]); - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3]); - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 1]); - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 2]); - importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2]); - importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2 + 1]); - - // Add index - importer_data.Scene.Indices.push_back(importer_data.VertexOffset + vertex_count); - vertex_count++; - index_count++; + const auto& index = shape.mesh.indices[f * 3 + v]; + VertexData vertex{}; + + const size_t pos_idx = index.position_index * 3; + vertex.px = attributes.positions[pos_idx]; + vertex.py = attributes.positions[pos_idx + 1]; + vertex.pz = attributes.positions[pos_idx + 2]; + + const size_t norm_idx = index.normal_index * 3; + vertex.nx = attributes.normals[norm_idx]; + vertex.ny = attributes.normals[norm_idx + 1]; + vertex.nz = attributes.normals[norm_idx + 2]; + + const size_t tex_idx = index.texcoord_index * 2; + vertex.u = attributes.texcoords[tex_idx]; + vertex.v = attributes.texcoords[tex_idx + 1]; + + // Add to unique_vertices or reuse + auto it = unique_vertices.find(vertex); + uint32_t vertex_index; + + if (it != unique_vertices.end()) + { + vertex_index = it->second; + } + else + { + vertex_index = static_cast(unique_vertices.size()); + unique_vertices[vertex] = vertex_index; + + importer_data.Scene.Vertices.insert( + importer_data.Scene.Vertices.end(), {vertex.px, vertex.py, vertex.pz, vertex.nx, vertex.ny, vertex.nz, vertex.u, vertex.v}); + } + + importer_data.Scene.Indices.push_back(importer_data.VertexOffset + vertex_index); } } - // Create mesh entry MeshVNext& mesh = importer_data.Scene.Meshes.emplace_back(); - mesh.VertexCount = vertex_count; + mesh.VertexCount = static_cast(unique_vertices.size()); mesh.VertexOffset = importer_data.VertexOffset; - mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); // pos(3) + normal(3) + uv(2) + mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); mesh.StreamOffset = mesh.VertexUnitStreamSize * mesh.VertexOffset; mesh.IndexOffset = importer_data.IndexOffset; - mesh.IndexCount = index_count; + mesh.IndexCount = static_cast(shape.mesh.indices.size()); mesh.IndexUnitStreamSize = sizeof(uint32_t); mesh.IndexStreamOffset = mesh.IndexUnitStreamSize * mesh.IndexOffset; mesh.TotalByteSize = (mesh.VertexCount * mesh.VertexUnitStreamSize) + (mesh.IndexCount * mesh.IndexUnitStreamSize); - // Update offsets for next mesh - importer_data.VertexOffset += vertex_count; - importer_data.IndexOffset += index_count; + importer_data.VertexOffset += mesh.VertexCount; + importer_data.IndexOffset += mesh.IndexCount; } } void RapidobjImporter::ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data) { static constexpr float OPAQUENESS_THRESHOLD = 0.05f; - const uint32_t number_of_materials = static_cast(result.materials.size()); - ZENGINE_CORE_INFO(fmt::format("Processing {} materials", number_of_materials).c_str()); + // Handle case where no materials exist + if (result.materials.empty()) + { + MeshMaterial& default_material = importer_data.Scene.Materials.emplace_back(); + default_material.AlbedoColor = ZEngine::Rendering::gpuvec4{0.7f, 0.7f, 0.7f, 1.0f}; + default_material.AmbientColor = ZEngine::Rendering::gpuvec4{0.2f, 0.2f, 0.2f, 1.0f}; + default_material.SpecularColor = ZEngine::Rendering::gpuvec4{0.5f, 0.5f, 0.5f, 1.0f}; + default_material.EmissiveColor = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 1.0f}; + default_material.Factors = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 0.0f}; + importer_data.Scene.MaterialNames.push_back(""); + return; + } - // Pre-allocate space + const uint32_t number_of_materials = static_cast(result.materials.size()); importer_data.Scene.Materials.reserve(number_of_materials); importer_data.Scene.MaterialNames.reserve(number_of_materials); - // Helper function to convert Float3 to vec4 auto toVec4 = [](const rapidobj::Float3& rgb, float alpha = 1.0f) -> ZEngine::Rendering::gpuvec4 { return ZEngine::Rendering::gpuvec4{std::clamp(rgb[0], 0.0f, 1.0f), std::clamp(rgb[1], 0.0f, 1.0f), std::clamp(rgb[2], 0.0f, 1.0f), std::clamp(alpha, 0.0f, 1.0f)}; }; - // Process each material + // Process materials for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) { const auto& obj_material = result.materials[mat_idx]; MeshMaterial& material = importer_data.Scene.Materials.emplace_back(); - // Store material name - std::string material_name = obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name; - importer_data.Scene.MaterialNames.push_back(std::move(material_name)); + importer_data.Scene.MaterialNames.push_back(obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name); - // Convert material properties material.AmbientColor = toVec4(obj_material.ambient); material.AlbedoColor = toVec4(obj_material.diffuse); material.SpecularColor = toVec4(obj_material.specular); material.EmissiveColor = toVec4(obj_material.emission); - // Handle transparency/opacity float opacity = obj_material.dissolve; material.Factors.x = (opacity < 1.0f) ? std::clamp(1.0f - opacity, 0.0f, 1.0f) : 0.0f; - if (material.Factors.x >= (1.0f - OPAQUENESS_THRESHOLD)) - { - material.Factors.x = 0.0f; - } - // Handle transmission filter float transmission = std::max({obj_material.transmittance[0], obj_material.transmittance[1], obj_material.transmittance[2]}); if (transmission > 0.0f) { @@ -280,7 +286,6 @@ namespace Tetragrama::Importers material.Factors.z = 0.5f; } - // Handle shininess and illumination model material.Factors.y = std::clamp(obj_material.shininess / 1000.0f, 0.0f, 1.0f); material.Factors.w = static_cast(obj_material.illum) / 10.0f; @@ -292,31 +297,22 @@ namespace Tetragrama::Importers material.OpacityMap = 0xFFFFFFFF; } - // Map materials to shapes + // Map each material in the shapes to individual nodes for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) { const auto& shape = result.shapes[shape_idx]; - if (!shape.mesh.material_ids.empty()) + // Assign all materials used by the shape to individual nodes + for (size_t material_idx = 0; material_idx < shape.mesh.material_ids.size(); ++material_idx) { - std::vector material_counts(number_of_materials, 0); - for (int32_t mat_id : shape.mesh.material_ids) - { - if (mat_id >= 0 && static_cast(mat_id) < material_counts.size()) - { - material_counts[mat_id]++; - } - } - - auto most_frequent = std::max_element(material_counts.begin(), material_counts.end()); - if (most_frequent != material_counts.end()) + int32_t mat_id = shape.mesh.material_ids[material_idx]; + if (mat_id >= 0 && static_cast(mat_id) < result.materials.size()) { - size_t material_id = std::distance(material_counts.begin(), most_frequent); for (const auto& [node_id, mesh_idx] : importer_data.Scene.NodeMeshes) { if (mesh_idx == shape_idx) { - importer_data.Scene.NodeMaterials[node_id] = static_cast(material_id); + importer_data.Scene.NodeMaterials[node_id] = static_cast(mat_id); } } } @@ -324,52 +320,32 @@ namespace Tetragrama::Importers } } - int RapidobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) - { - auto find = std::find(std::begin(data), std::end(data), filename); - if (find != std::end(data)) - { - return std::distance(std::begin(data), find); - } - - data.push_back(filename.data()); - return (data.size() - 1); - } - void RapidobjImporter::ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data) { if (result.materials.empty()) { - ZENGINE_CORE_INFO("No materials found for texture extraction"); + ZENGINE_CORE_INFO("No materials found for texture extraction."); return; } const uint32_t number_of_materials = static_cast(result.materials.size()); ZENGINE_CORE_INFO(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); - // Helper function to process texture paths and generate file index + // Helper function to process texture paths and generate file indices auto processTexturePath = [this, &importer_data](const std::string& texname, const std::string& type) -> uint32_t { if (texname.empty()) { - ZENGINE_CORE_INFO(fmt::format("No {} texture specified", type)); + ZENGINE_CORE_INFO(fmt::format("No {} texture specified.", type)); return 0xFFFFFFFF; // Invalid texture index } - // Normalize and resolve the path relative to the base directory - std::filesystem::path texture_path = texname; - if (!texture_path.is_absolute()) - { - texture_path = m_base_dir / texture_path; - } + ZENGINE_CORE_INFO(fmt::format("Found texname: {}", texname)); - if (!std::filesystem::exists(texture_path)) - { - ZENGINE_CORE_INFO(fmt::format("Warning: {} texture not found at path: {}", type, texture_path.string())); - return 0xFFFFFFFF; // Invalid texture index - } + std::string normalized_path = texname; + std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); - ZENGINE_CORE_INFO(fmt::format("Found {} texture: {}", type, texture_path.string())); - return GenerateFileIndex(importer_data.Scene.Files, texname); + ZENGINE_CORE_INFO(fmt::format("Found {} texture: {}", type, normalized_path)); + return GenerateFileIndex(importer_data.Scene.Files, normalized_path); }; for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) @@ -384,27 +360,21 @@ namespace Tetragrama::Importers obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name) .c_str()); - // Diffuse/Albedo texture - ZENGINE_CORE_INFO(fmt::format("Raw texname value: {}", obj_material.diffuse_texname)); - material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); - - // Specular texture - ZENGINE_CORE_INFO(fmt::format("Raw texname value: {}", obj_material.specular_texname)); + // Process standard textures + material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); material.SpecularMap = processTexturePath(obj_material.specular_texname, "specular"); - - // Emissive texture material.EmissiveMap = processTexturePath(obj_material.emissive_texname, "emissive"); - // Normal/Bump map + // Handle multiple normal map definitions material.NormalMap = !obj_material.bump_texname.empty() ? processTexturePath(obj_material.bump_texname, "bump") : !obj_material.normal_texname.empty() ? processTexturePath(obj_material.normal_texname, "normal") : !obj_material.displacement_texname.empty() ? processTexturePath(obj_material.displacement_texname, "displacement as normal") : 0xFFFFFFFF; - // Opacity/Alpha texture + // Process opacity texture material.OpacityMap = processTexturePath(obj_material.alpha_texname, "opacity"); - // Process additional textures (PBR, unsupported types) + // Handle additional textures (e.g., PBR maps) ProcessAdditionalTextures(obj_material, material); // Validate texture assignments @@ -413,6 +383,7 @@ namespace Tetragrama::Importers ZENGINE_CORE_INFO(fmt::format("Total unique textures processed: {}", importer_data.Scene.Files.size()).c_str()); } + void RapidobjImporter::ProcessAdditionalTextures(const rapidobj::Material& obj_material, MeshMaterial& material) { // Process ambient occlusion texture if available @@ -465,132 +436,112 @@ namespace Tetragrama::Importers ZENGINE_CORE_INFO("Set default normal map intensity factor"); } } - bool RapidobjImporter::ValidateFileExists(const std::string& filepath) + int RapidobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) { - std::ifstream file(filepath); - return file.good(); + auto find = std::find(std::begin(data), std::end(data), filename); + if (find != std::end(data)) + { + return std::distance(std::begin(data), find); + } + + data.push_back(filename.data()); + return (data.size() - 1); } void RapidobjImporter::CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data) { if (result.shapes.empty()) { - ZENGINE_CORE_INFO("No shapes found in the OBJ file for hierarchy creation."); + ZENGINE_CORE_INFO("No shapes found in the OBJ file."); return; } - // Create root node - auto root_node_id = SceneRawData::AddNode(&importer_data.Scene, -1, 0); - importer_data.Scene.NodeNames[root_node_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back("Root"); - importer_data.Scene.GlobalTransformCollection[root_node_id] = glm::mat4(1.0f); - importer_data.Scene.LocalTransformCollection[root_node_id] = glm::mat4(1.0f); - - // Group shapes by material and calculate shape bounding boxes - struct MaterialGroup - { - std::string name; - std::vector shape_indices; - int32_t material_id{-1}; - glm::vec3 min_bounds{std::numeric_limits::max()}; - glm::vec3 max_bounds{std::numeric_limits::lowest()}; - }; + TraverseNode(result, &importer_data.Scene, "filename", -1, 0); + } - std::map material_groups; + void RapidobjImporter::TraverseNode(const rapidobj::Result& result, SceneRawData* const scene, const std::string& nodeName, int parent_node_id, int depth_level) + { + // Create root node + auto root_id = SceneRawData::AddNode(scene, parent_node_id, depth_level); + scene->NodeNames[root_id] = scene->Names.size(); + scene->Names.push_back(!nodeName.empty() ? nodeName : ""); + scene->GlobalTransformCollection[root_id] = glm::mat4(1.0f); + scene->LocalTransformCollection[root_id] = glm::mat4(1.0f); - for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) + if (depth_level == 0) { - const auto& shape = result.shapes[shape_idx]; - int32_t primary_material_id = -1; - - // Determine primary material - if (!shape.mesh.material_ids.empty()) + // Create mesh group node + auto mesh_group_id = SceneRawData::AddNode(scene, root_id, depth_level + 1); + scene->NodeNames[mesh_group_id] = scene->Names.size(); + scene->Names.push_back(nodeName + "_Mesh1_Model"); + scene->GlobalTransformCollection[mesh_group_id] = glm::mat4(1.0f); + scene->LocalTransformCollection[mesh_group_id] = glm::mat4(1.0f); + + // First, organize shapes by their materials + std::map> material_to_shapes; + + // Group shapes by material + for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) { + const auto& shape = result.shapes[shape_idx]; + + // Find the dominant material for this shape std::map material_counts; for (int32_t mat_id : shape.mesh.material_ids) { - material_counts[mat_id]++; + if (mat_id >= 0 && mat_id < static_cast(result.materials.size())) + { + material_counts[mat_id]++; + } } - auto most_frequent = std::max_element(material_counts.begin(), material_counts.end(), [](const auto& p1, const auto& p2) { - return p1.second < p2.second; - }); - - if (most_frequent != material_counts.end()) + int32_t primary_material_id = -1; + if (!material_counts.empty()) { + auto most_frequent = std::max_element(material_counts.begin(), material_counts.end(), [](const auto& p1, const auto& p2) { + return p1.second < p2.second; + }); primary_material_id = most_frequent->first; } - } - - // Assign to material group - auto& group = material_groups[primary_material_id]; - group.material_id = primary_material_id; - group.shape_indices.push_back(shape_idx); - if (group.name.empty()) - { - group.name = (primary_material_id >= 0 && primary_material_id < static_cast(result.materials.size())) ? result.materials[primary_material_id].name - : ""; + // Group shapes by their primary material + material_to_shapes[primary_material_id].push_back(shape_idx); + ZENGINE_CORE_INFO(fmt::format("Shape {} assigned to material {}", shape_idx, primary_material_id).c_str()); } - // Calculate shape bounds - for (const auto& index : shape.mesh.indices) + // Create nodes for each material group + for (const auto& [material_id, shape_indices] : material_to_shapes) { - if (index.position_index >= 0 && index.position_index * 3 + 2 < result.attributes.positions.size()) + for (size_t shape_idx : shape_indices) { - glm::vec3 position( - result.attributes.positions[index.position_index * 3], - result.attributes.positions[index.position_index * 3 + 1], - result.attributes.positions[index.position_index * 3 + 2]); - group.min_bounds = glm::min(group.min_bounds, position); - group.max_bounds = glm::max(group.max_bounds, position); - } - } - } - - // Create material group nodes - for (const auto& [material_id, group] : material_groups) - { - auto material_node_id = SceneRawData::AddNode(&importer_data.Scene, root_node_id, 1); - importer_data.Scene.NodeNames[material_node_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back(fmt::format("Material_Group_{}", group.name)); - - glm::vec3 group_center = (group.max_bounds + group.min_bounds) * 0.5f; - glm::vec3 group_scale = group.max_bounds - group.min_bounds; + const auto& shape = result.shapes[shape_idx]; + auto sub_node_id = SceneRawData::AddNode(scene, mesh_group_id, depth_level + 2); + scene->NodeNames[sub_node_id] = scene->Names.size(); - // Construct translation matrix - glm::mat4 translation_matrix = glm::mat4(1.0f); - translation_matrix[3] = glm::vec4(group_center, 1.0f); // Set translation vector - - // Construct scaling matrix - glm::mat4 scaling_matrix = glm::mat4(1.0f); - scaling_matrix[0][0] = group_scale.x; // Scale X - scaling_matrix[1][1] = group_scale.y; // Scale Y - scaling_matrix[2][2] = group_scale.z; // Scale Z - - // Combine scaling and translation into a single transform - glm::mat4 group_transform = scaling_matrix * translation_matrix; - - // Assign transforms - importer_data.Scene.GlobalTransformCollection[material_node_id] = group_transform; - importer_data.Scene.LocalTransformCollection[material_node_id] = group_transform; + // Get material name + std::string material_name; + if (material_id >= 0 && material_id < static_cast(result.materials.size())) + { + material_name = !result.materials[material_id].name.empty() ? result.materials[material_id].name : fmt::format("material_{}", material_id); - // Create shape nodes - for (size_t shape_idx : group.shape_indices) - { - const auto& shape = result.shapes[shape_idx]; - auto shape_node_id = SceneRawData::AddNode(&importer_data.Scene, material_node_id, 2); + ZENGINE_CORE_INFO(fmt::format("Creating node with material: {} (ID: {})", material_name, material_id).c_str()); + } + else + { + material_name = ""; + } - importer_data.Scene.NodeNames[shape_node_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back(!shape.name.empty() ? shape.name : fmt::format("Shape_{}", shape_idx)); + scene->Names.push_back(material_name); + scene->NodeMeshes[sub_node_id] = static_cast(shape_idx); + scene->NodeMaterials[sub_node_id] = static_cast(material_id >= 0 ? material_id : 0); + scene->GlobalTransformCollection[sub_node_id] = glm::mat4(1.0f); + scene->LocalTransformCollection[sub_node_id] = glm::mat4(1.0f); - importer_data.Scene.NodeMeshes[shape_node_id] = static_cast(shape_idx); - importer_data.Scene.NodeMaterials[shape_node_id] = static_cast(material_id >= 0 ? material_id : 0); + ZENGINE_CORE_INFO(fmt::format("Created node for shape {} with material {}", shape_idx, material_id).c_str()); + } } - } - ZENGINE_CORE_INFO(fmt::format("Created scene hierarchy with {} material groups and {} shapes.", material_groups.size(), result.shapes.size()).c_str()); - // ValidateSceneHierarchy(importer_data.Scene); + ZENGINE_CORE_INFO(fmt::format("Created hierarchy with {} material groups", material_to_shapes.size()).c_str()); + } } - -} // namespace Tetragrama::Importers \ No newline at end of file +} // namespace Tetragrama::Importers diff --git a/Tetragrama/Importers/RapidobjImporter.h b/Tetragrama/Importers/RapidobjImporter.h index a8fd9244..9608a730 100644 --- a/Tetragrama/Importers/RapidobjImporter.h +++ b/Tetragrama/Importers/RapidobjImporter.h @@ -1,7 +1,8 @@ #pragma once + #include #include -#include +#include // Add this for path handling using namespace ZEngine::Helpers; using namespace ZEngine::Rendering::Meshes; @@ -9,7 +10,6 @@ using namespace ZEngine::Rendering::Scenes; namespace Tetragrama::Importers { - class RapidobjImporter : public IAssetImporter { public: @@ -17,17 +17,66 @@ namespace Tetragrama::Importers virtual ~RapidobjImporter(); std::future ImportAsync(std::string_view filename, ImportConfiguration config = {}) override; + // To aid in Vertex deduplication + struct VertexData + { + float px, py, pz; // Position + float nx, ny, nz; // Normal + float u, v; // Texture + + bool operator==(const VertexData& other) const noexcept + { + return px == other.px && py == other.py && pz == other.pz && nx == other.nx && ny == other.ny && nz == other.nz && u == other.u && v == other.v; + } + }; + private: - void PreprocessMesh(rapidobj::Result&); - void ExtractMeshes(const rapidobj::Result&, ImporterData&); - void ExtractMaterials(const rapidobj::Result&, ImporterData&); - void ExtractTextures(const rapidobj::Result&, ImporterData&); - int GenerateFileIndex(std::vector& data, std::string_view filename); - void ValidateTextureAssignments(MeshMaterial& material); - void ProcessAdditionalTextures(const rapidobj::Material&, MeshMaterial& material); - bool ValidateFileExists(const std::string& filepath); - void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); - void ValidateSceneHierarchy(const SceneRawData& scene); - std::filesystem::path m_base_dir; + // Core processing functions + void PreprocessMesh(rapidobj::Result& result); + void ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data); + void ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data); + void ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data); + void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); + + // Helper functions + void TraverseNode(const rapidobj::Result& result, SceneRawData* const scene, const std::string& nodeName, int parent_node_id, int depth_level); + int GenerateFileIndex(std::vector& data, std::string_view filename); + void ValidateTextureAssignments(MeshMaterial& material); + void ProcessAdditionalTextures(const rapidobj::Material& obj_material, MeshMaterial& material); + }; +} // namespace Tetragrama::Importers + + +namespace std +{ + template <> + struct hash + { + size_t operator()(const Tetragrama::Importers::RapidobjImporter::VertexData& v) const noexcept + { + // Better hash combining function + size_t seed = 0; + auto hash_combine = [&seed](size_t hash) { + seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); + }; + + hash_combine(std::hash{}(v.px)); + hash_combine(std::hash{}(v.py)); + hash_combine(std::hash{}(v.pz)); + hash_combine(std::hash{}(v.nx)); + hash_combine(std::hash{}(v.ny)); + hash_combine(std::hash{}(v.nz)); + hash_combine(std::hash{}(v.u)); + hash_combine(std::hash{}(v.v)); + + return seed; + } }; -} // namespace Tetragrama::Importers \ No newline at end of file +} // namespace std + + +// Todo +// 1. replace zengine core with log! +// remove unnecassy comments +// if materials is empty ?? +// does texture need full file path ?? \ No newline at end of file From d8463026619bf6b0a066308978b817ce69d78823 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Wed, 27 Nov 2024 21:22:49 +0100 Subject: [PATCH 4/8] node hierarchy - does not really work --- Tetragrama/Importers/RapidobjImporter.cpp | 229 +++++----------------- Tetragrama/Importers/RapidobjImporter.h | 15 +- 2 files changed, 53 insertions(+), 191 deletions(-) diff --git a/Tetragrama/Importers/RapidobjImporter.cpp b/Tetragrama/Importers/RapidobjImporter.cpp index 9bdfb36b..6a2f0691 100644 --- a/Tetragrama/Importers/RapidobjImporter.cpp +++ b/Tetragrama/Importers/RapidobjImporter.cpp @@ -124,7 +124,7 @@ namespace Tetragrama::Importers // Generate texture coordinates if they don't exist if (result.attributes.texcoords.empty()) { - ZENGINE_CORE_INFO("Generating default UV coordinates..."); + REPORT_LOG("Generating default UV coordinates..."); glm::vec3 min_pos(std::numeric_limits::max()); glm::vec3 max_pos(std::numeric_limits::lowest()); @@ -263,7 +263,6 @@ namespace Tetragrama::Importers return ZEngine::Rendering::gpuvec4{std::clamp(rgb[0], 0.0f, 1.0f), std::clamp(rgb[1], 0.0f, 1.0f), std::clamp(rgb[2], 0.0f, 1.0f), std::clamp(alpha, 0.0f, 1.0f)}; }; - // Process materials for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) { const auto& obj_material = result.materials[mat_idx]; @@ -296,55 +295,29 @@ namespace Tetragrama::Importers material.EmissiveMap = 0xFFFFFFFF; material.OpacityMap = 0xFFFFFFFF; } - - // Map each material in the shapes to individual nodes - for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) - { - const auto& shape = result.shapes[shape_idx]; - - // Assign all materials used by the shape to individual nodes - for (size_t material_idx = 0; material_idx < shape.mesh.material_ids.size(); ++material_idx) - { - int32_t mat_id = shape.mesh.material_ids[material_idx]; - if (mat_id >= 0 && static_cast(mat_id) < result.materials.size()) - { - for (const auto& [node_id, mesh_idx] : importer_data.Scene.NodeMeshes) - { - if (mesh_idx == shape_idx) - { - importer_data.Scene.NodeMaterials[node_id] = static_cast(mat_id); - } - } - } - } - } } void RapidobjImporter::ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data) { if (result.materials.empty()) { - ZENGINE_CORE_INFO("No materials found for texture extraction."); + REPORT_LOG("No materials found for texture extraction."); return; } const uint32_t number_of_materials = static_cast(result.materials.size()); - ZENGINE_CORE_INFO(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); + REPORT_LOG(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); - // Helper function to process texture paths and generate file indices auto processTexturePath = [this, &importer_data](const std::string& texname, const std::string& type) -> uint32_t { if (texname.empty()) { - ZENGINE_CORE_INFO(fmt::format("No {} texture specified.", type)); - return 0xFFFFFFFF; // Invalid texture index + REPORT_LOG(fmt::format("No {} texture specified.", type)); + return 0xFFFFFFFF; } - ZENGINE_CORE_INFO(fmt::format("Found texname: {}", texname)); - std::string normalized_path = texname; std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); - ZENGINE_CORE_INFO(fmt::format("Found {} texture: {}", type, normalized_path)); return GenerateFileIndex(importer_data.Scene.Files, normalized_path); }; @@ -353,89 +326,19 @@ namespace Tetragrama::Importers const auto& obj_material = result.materials[mat_idx]; auto& material = importer_data.Scene.Materials[mat_idx]; - ZENGINE_CORE_INFO(fmt::format( - "Processing textures for material {}/{}: {}", - mat_idx + 1, - number_of_materials, - obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name) - .c_str()); - - // Process standard textures material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); material.SpecularMap = processTexturePath(obj_material.specular_texname, "specular"); material.EmissiveMap = processTexturePath(obj_material.emissive_texname, "emissive"); - // Handle multiple normal map definitions material.NormalMap = !obj_material.bump_texname.empty() ? processTexturePath(obj_material.bump_texname, "bump") : !obj_material.normal_texname.empty() ? processTexturePath(obj_material.normal_texname, "normal") : !obj_material.displacement_texname.empty() ? processTexturePath(obj_material.displacement_texname, "displacement as normal") : 0xFFFFFFFF; - // Process opacity texture material.OpacityMap = processTexturePath(obj_material.alpha_texname, "opacity"); - - // Handle additional textures (e.g., PBR maps) - ProcessAdditionalTextures(obj_material, material); - - // Validate texture assignments - ValidateTextureAssignments(material); } - - ZENGINE_CORE_INFO(fmt::format("Total unique textures processed: {}", importer_data.Scene.Files.size()).c_str()); } - void RapidobjImporter::ProcessAdditionalTextures(const rapidobj::Material& obj_material, MeshMaterial& material) - { - // Process ambient occlusion texture if available - if (!obj_material.ambient_texname.empty()) - { - ZENGINE_CORE_INFO("Ambient occlusion texture found but not supported in current material format"); - } - - // Check for PBR textures - bool has_pbr_textures = false; - if (!obj_material.roughness_texname.empty()) - { - has_pbr_textures = true; - ZENGINE_CORE_INFO("Roughness texture found"); - } - if (!obj_material.metallic_texname.empty()) - { - has_pbr_textures = true; - ZENGINE_CORE_INFO("Metallic texture found"); - } - if (!obj_material.sheen_texname.empty()) - { - has_pbr_textures = true; - ZENGINE_CORE_INFO("Sheen texture found"); - } - - if (has_pbr_textures) - { - ZENGINE_CORE_INFO("Note: Some PBR textures found but not supported in current material format"); - } - } - - void RapidobjImporter::ValidateTextureAssignments(MeshMaterial& material) - { - // Check for invalid texture combinations - if (material.OpacityMap != 0xFFFFFFFF) - { - // If we have an opacity map, ensure the material is marked as having transparency - if (material.Factors.x == 0.0f) - { - material.Factors.x = 0.5f; - ZENGINE_CORE_INFO("Adjusted material transparency factor due to opacity map"); - } - } - - // Check for normal map without proper factors - if (material.NormalMap != 0xFFFFFFFF && material.Factors.w == 0.0f) - { - material.Factors.w = 1.0f; // Default normal map intensity - ZENGINE_CORE_INFO("Set default normal map intensity factor"); - } - } int RapidobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) { auto find = std::find(std::begin(data), std::end(data), filename); @@ -452,96 +355,66 @@ namespace Tetragrama::Importers { if (result.shapes.empty()) { - ZENGINE_CORE_INFO("No shapes found in the OBJ file."); return; } - TraverseNode(result, &importer_data.Scene, "filename", -1, 0); - } + // Create root node ?? + auto root_id = SceneRawData::AddNode(&importer_data.Scene, -1, 0); + importer_data.Scene.NodeNames[root_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back("Root"); + importer_data.Scene.GlobalTransformCollection[root_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[root_id] = glm::mat4(1.0f); - void RapidobjImporter::TraverseNode(const rapidobj::Result& result, SceneRawData* const scene, const std::string& nodeName, int parent_node_id, int depth_level) - { - // Create root node - auto root_id = SceneRawData::AddNode(scene, parent_node_id, depth_level); - scene->NodeNames[root_id] = scene->Names.size(); - scene->Names.push_back(!nodeName.empty() ? nodeName : ""); - scene->GlobalTransformCollection[root_id] = glm::mat4(1.0f); - scene->LocalTransformCollection[root_id] = glm::mat4(1.0f); - - if (depth_level == 0) - { - // Create mesh group node - auto mesh_group_id = SceneRawData::AddNode(scene, root_id, depth_level + 1); - scene->NodeNames[mesh_group_id] = scene->Names.size(); - scene->Names.push_back(nodeName + "_Mesh1_Model"); - scene->GlobalTransformCollection[mesh_group_id] = glm::mat4(1.0f); - scene->LocalTransformCollection[mesh_group_id] = glm::mat4(1.0f); - - // First, organize shapes by their materials - std::map> material_to_shapes; - - // Group shapes by material - for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) - { - const auto& shape = result.shapes[shape_idx]; + // Create model node ?? + auto model_node_id = SceneRawData::AddNode(&importer_data.Scene, -1, 1); + importer_data.Scene.NodeNames[model_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back("model_Mesh1_Model"); + importer_data.Scene.GlobalTransformCollection[model_node_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[model_node_id] = glm::mat4(1.0f); - // Find the dominant material for this shape - std::map material_counts; - for (int32_t mat_id : shape.mesh.material_ids) - { - if (mat_id >= 0 && mat_id < static_cast(result.materials.size())) - { - material_counts[mat_id]++; - } - } + // Collect all unique materials across all shapes + std::map> material_to_shapes; - int32_t primary_material_id = -1; - if (!material_counts.empty()) - { - auto most_frequent = std::max_element(material_counts.begin(), material_counts.end(), [](const auto& p1, const auto& p2) { - return p1.second < p2.second; - }); - primary_material_id = most_frequent->first; - } + for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) + { + const auto& shape = result.shapes[shape_idx]; + std::set shape_materials; - // Group shapes by their primary material - material_to_shapes[primary_material_id].push_back(shape_idx); - ZENGINE_CORE_INFO(fmt::format("Shape {} assigned to material {}", shape_idx, primary_material_id).c_str()); + for (int32_t mat_id : shape.mesh.material_ids) + { + shape_materials.insert(mat_id); } - // Create nodes for each material group - for (const auto& [material_id, shape_indices] : material_to_shapes) + for (int32_t mat_id : shape_materials) { - for (size_t shape_idx : shape_indices) - { - const auto& shape = result.shapes[shape_idx]; - auto sub_node_id = SceneRawData::AddNode(scene, mesh_group_id, depth_level + 2); - scene->NodeNames[sub_node_id] = scene->Names.size(); + material_to_shapes[mat_id].push_back(shape_idx); + } + } - // Get material name - std::string material_name; - if (material_id >= 0 && material_id < static_cast(result.materials.size())) - { - material_name = !result.materials[material_id].name.empty() ? result.materials[material_id].name : fmt::format("material_{}", material_id); + // Create a node for each material + for (const auto& [material_id, shape_indices] : material_to_shapes) + { + auto material_node_id = SceneRawData::AddNode(&importer_data.Scene, model_node_id, 2); + importer_data.Scene.NodeNames[material_node_id] = importer_data.Scene.Names.size(); - ZENGINE_CORE_INFO(fmt::format("Creating node with material: {} (ID: {})", material_name, material_id).c_str()); - } - else - { - material_name = ""; - } + std::string material_name = material_id < static_cast(importer_data.Scene.MaterialNames.size()) ? importer_data.Scene.MaterialNames[material_id] + : fmt::format("material_{}", material_id); - scene->Names.push_back(material_name); - scene->NodeMeshes[sub_node_id] = static_cast(shape_idx); - scene->NodeMaterials[sub_node_id] = static_cast(material_id >= 0 ? material_id : 0); - scene->GlobalTransformCollection[sub_node_id] = glm::mat4(1.0f); - scene->LocalTransformCollection[sub_node_id] = glm::mat4(1.0f); + importer_data.Scene.Names.push_back(material_name); - ZENGINE_CORE_INFO(fmt::format("Created node for shape {} with material {}", shape_idx, material_id).c_str()); - } - } + // Create child nodes for each shape using this material + for (size_t shape_idx : shape_indices) + { + auto shape_node_id = SceneRawData::AddNode(&importer_data.Scene, material_node_id, 3); + importer_data.Scene.NodeNames[shape_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back(fmt::format("{}_{}", material_name, shape_idx)); - ZENGINE_CORE_INFO(fmt::format("Created hierarchy with {} material groups", material_to_shapes.size()).c_str()); + importer_data.Scene.NodeMeshes[shape_node_id] = static_cast(shape_idx); + importer_data.Scene.NodeMaterials[shape_node_id] = static_cast(material_id); + + importer_data.Scene.GlobalTransformCollection[shape_node_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[shape_node_id] = glm::mat4(1.0f); + } } } -} // namespace Tetragrama::Importers +} // namespace Tetragrama::Importers \ No newline at end of file diff --git a/Tetragrama/Importers/RapidobjImporter.h b/Tetragrama/Importers/RapidobjImporter.h index 9608a730..2eff13f8 100644 --- a/Tetragrama/Importers/RapidobjImporter.h +++ b/Tetragrama/Importers/RapidobjImporter.h @@ -22,7 +22,7 @@ namespace Tetragrama::Importers { float px, py, pz; // Position float nx, ny, nz; // Normal - float u, v; // Texture + float u, v; // Texture bool operator==(const VertexData& other) const noexcept { @@ -39,14 +39,10 @@ namespace Tetragrama::Importers void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); // Helper functions - void TraverseNode(const rapidobj::Result& result, SceneRawData* const scene, const std::string& nodeName, int parent_node_id, int depth_level); - int GenerateFileIndex(std::vector& data, std::string_view filename); - void ValidateTextureAssignments(MeshMaterial& material); - void ProcessAdditionalTextures(const rapidobj::Material& obj_material, MeshMaterial& material); + int GenerateFileIndex(std::vector& data, std::string_view filename); }; } // namespace Tetragrama::Importers - namespace std { template <> @@ -73,10 +69,3 @@ namespace std } }; } // namespace std - - -// Todo -// 1. replace zengine core with log! -// remove unnecassy comments -// if materials is empty ?? -// does texture need full file path ?? \ No newline at end of file From 81f5370c630fd1fba3be369e20aa2786f84b3652 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Wed, 27 Nov 2024 21:28:58 +0100 Subject: [PATCH 5/8] comments --- Tetragrama/Importers/RapidobjImporter.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tetragrama/Importers/RapidobjImporter.h b/Tetragrama/Importers/RapidobjImporter.h index 2eff13f8..01613001 100644 --- a/Tetragrama/Importers/RapidobjImporter.h +++ b/Tetragrama/Importers/RapidobjImporter.h @@ -2,7 +2,6 @@ #include #include -#include // Add this for path handling using namespace ZEngine::Helpers; using namespace ZEngine::Rendering::Meshes; @@ -31,15 +30,12 @@ namespace Tetragrama::Importers }; private: - // Core processing functions void PreprocessMesh(rapidobj::Result& result); void ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data); void ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data); void ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data); void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); - - // Helper functions - int GenerateFileIndex(std::vector& data, std::string_view filename); + int GenerateFileIndex(std::vector& data, std::string_view filename); }; } // namespace Tetragrama::Importers @@ -50,7 +46,6 @@ namespace std { size_t operator()(const Tetragrama::Importers::RapidobjImporter::VertexData& v) const noexcept { - // Better hash combining function size_t seed = 0; auto hash_combine = [&seed](size_t hash) { seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); From 55330fa12b912a58c2af047131f3f6003abcf2f5 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Thu, 28 Nov 2024 01:35:23 +0100 Subject: [PATCH 6/8] move deduplication to preprocess --- Tetragrama/Importers/RapidobjImporter.cpp | 136 +++++++++++++++------- 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/Tetragrama/Importers/RapidobjImporter.cpp b/Tetragrama/Importers/RapidobjImporter.cpp index 6a2f0691..e273fe23 100644 --- a/Tetragrama/Importers/RapidobjImporter.cpp +++ b/Tetragrama/Importers/RapidobjImporter.cpp @@ -157,6 +157,83 @@ namespace Tetragrama::Importers REPORT_LOG("Cannot triangulate model...") } + // vertex deduplication + for (auto& shape : result.shapes) + { + std::unordered_map unique_vertices; + std::vector new_indices; + new_indices.reserve(shape.mesh.indices.size()); + + const size_t num_indices = shape.mesh.indices.size(); + for (size_t i = 0; i < num_indices; ++i) + { + const auto& index = shape.mesh.indices[i]; + VertexData vertex{}; + + // Position + vertex.px = result.attributes.positions[index.position_index * 3]; + vertex.py = result.attributes.positions[index.position_index * 3 + 1]; + vertex.pz = result.attributes.positions[index.position_index * 3 + 2]; + + // Normal + vertex.nx = result.attributes.normals[index.normal_index * 3]; + vertex.ny = result.attributes.normals[index.normal_index * 3 + 1]; + vertex.nz = result.attributes.normals[index.normal_index * 3 + 2]; + + // UV + vertex.u = result.attributes.texcoords[index.texcoord_index * 2]; + vertex.v = result.attributes.texcoords[index.texcoord_index * 2 + 1]; + + auto it = unique_vertices.find(vertex); + if (it != unique_vertices.end()) + { + rapidobj::Index new_index; + new_index.position_index = it->second; + new_index.normal_index = it->second; + new_index.texcoord_index = it->second; + new_indices.push_back(new_index); + } + else + { + uint32_t new_index_value = static_cast(unique_vertices.size()); + unique_vertices[vertex] = new_index_value; + + rapidobj::Index new_index; + new_index.position_index = new_index_value; + new_index.normal_index = new_index_value; + new_index.texcoord_index = new_index_value; + new_indices.push_back(new_index); + } + } + + rapidobj::Array new_positions(unique_vertices.size() * 3); + rapidobj::Array new_normals(unique_vertices.size() * 3); + rapidobj::Array new_texcoords(unique_vertices.size() * 2); + + size_t vert_idx = 0; + for (const auto& [vert, idx] : unique_vertices) + { + new_positions[idx * 3] = vert.px; + new_positions[idx * 3 + 1] = vert.py; + new_positions[idx * 3 + 2] = vert.pz; + + new_normals[idx * 3] = vert.nx; + new_normals[idx * 3 + 1] = vert.ny; + new_normals[idx * 3 + 2] = vert.nz; + + new_texcoords[idx * 2] = vert.u; + new_texcoords[idx * 2 + 1] = vert.v; + } + + rapidobj::Array final_indices(new_indices.size()); + memcpy(final_indices.data(), new_indices.data(), new_indices.size() * sizeof(rapidobj::Index)); + + // Update shape and attributes + result.attributes.positions = std::move(new_positions); + result.attributes.normals = std::move(new_normals); + result.attributes.texcoords = std::move(new_texcoords); + shape.mesh.indices = std::move(final_indices); + } REPORT_LOG("Preprocessing complete."); } @@ -175,56 +252,28 @@ namespace Tetragrama::Importers for (size_t shape_idx = 0; shape_idx < shapes.size(); ++shape_idx) { - const auto& shape = shapes[shape_idx]; + const auto& shape = shapes[shape_idx]; + const size_t face_count = shape.mesh.indices.size() / 3; - std::unordered_map unique_vertices; - const size_t face_count = shape.mesh.indices.size() / 3; - - for (size_t f = 0; f < face_count; ++f) + for (const auto& index : shape.mesh.indices) { - for (size_t v = 0; v < 3; ++v) - { - const auto& index = shape.mesh.indices[f * 3 + v]; - VertexData vertex{}; - - const size_t pos_idx = index.position_index * 3; - vertex.px = attributes.positions[pos_idx]; - vertex.py = attributes.positions[pos_idx + 1]; - vertex.pz = attributes.positions[pos_idx + 2]; + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3]); + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 1]); + importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 2]); - const size_t norm_idx = index.normal_index * 3; - vertex.nx = attributes.normals[norm_idx]; - vertex.ny = attributes.normals[norm_idx + 1]; - vertex.nz = attributes.normals[norm_idx + 2]; + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3]); + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 1]); + importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 2]); - const size_t tex_idx = index.texcoord_index * 2; - vertex.u = attributes.texcoords[tex_idx]; - vertex.v = attributes.texcoords[tex_idx + 1]; + importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2]); + importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2 + 1]); - // Add to unique_vertices or reuse - auto it = unique_vertices.find(vertex); - uint32_t vertex_index; - - if (it != unique_vertices.end()) - { - vertex_index = it->second; - } - else - { - vertex_index = static_cast(unique_vertices.size()); - unique_vertices[vertex] = vertex_index; - - importer_data.Scene.Vertices.insert( - importer_data.Scene.Vertices.end(), {vertex.px, vertex.py, vertex.pz, vertex.nx, vertex.ny, vertex.nz, vertex.u, vertex.v}); - } - - importer_data.Scene.Indices.push_back(importer_data.VertexOffset + vertex_index); - } + importer_data.Scene.Indices.push_back(importer_data.VertexOffset++); } MeshVNext& mesh = importer_data.Scene.Meshes.emplace_back(); - mesh.VertexCount = static_cast(unique_vertices.size()); - mesh.VertexOffset = importer_data.VertexOffset; + mesh.VertexCount = static_cast(shape.mesh.indices.size()); + mesh.VertexOffset = importer_data.VertexOffset - mesh.VertexCount; mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); mesh.StreamOffset = mesh.VertexUnitStreamSize * mesh.VertexOffset; mesh.IndexOffset = importer_data.IndexOffset; @@ -233,7 +282,6 @@ namespace Tetragrama::Importers mesh.IndexStreamOffset = mesh.IndexUnitStreamSize * mesh.IndexOffset; mesh.TotalByteSize = (mesh.VertexCount * mesh.VertexUnitStreamSize) + (mesh.IndexCount * mesh.IndexUnitStreamSize); - importer_data.VertexOffset += mesh.VertexCount; importer_data.IndexOffset += mesh.IndexCount; } } @@ -366,7 +414,7 @@ namespace Tetragrama::Importers importer_data.Scene.LocalTransformCollection[root_id] = glm::mat4(1.0f); // Create model node ?? - auto model_node_id = SceneRawData::AddNode(&importer_data.Scene, -1, 1); + auto model_node_id = SceneRawData::AddNode(&importer_data.Scene, root_id, 1); importer_data.Scene.NodeNames[model_node_id] = importer_data.Scene.Names.size(); importer_data.Scene.Names.push_back("model_Mesh1_Model"); importer_data.Scene.GlobalTransformCollection[model_node_id] = glm::mat4(1.0f); From 85ae7214b222cb3174c436fe960e47ad8b0adfd3 Mon Sep 17 00:00:00 2001 From: jnyfah Date: Thu, 28 Nov 2024 20:27:04 +0100 Subject: [PATCH 7/8] moved to tinyobj --- .gitmodules | 3 +++ __externals/tinyobjloader | 1 + 2 files changed, 4 insertions(+) create mode 160000 __externals/tinyobjloader diff --git a/.gitmodules b/.gitmodules index a482f9fd..2af51a0b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,3 +61,6 @@ [submodule "__externals/rapidobj"] path = __externals/rapidobj url = https://github.com/guybrush77/rapidobj +[submodule "__externals/tinyobjloader"] + path = __externals/tinyobjloader + url = https://github.com/tinyobjloader/tinyobjloader diff --git a/__externals/tinyobjloader b/__externals/tinyobjloader new file mode 160000 index 00000000..fe9e7130 --- /dev/null +++ b/__externals/tinyobjloader @@ -0,0 +1 @@ +Subproject commit fe9e7130a0eee720a28f39b33852108217114076 From 9f989d0ef388406403d6e94c770e3ecd42d4fd0e Mon Sep 17 00:00:00 2001 From: jnyfah Date: Thu, 28 Nov 2024 20:27:17 +0100 Subject: [PATCH 8/8] moved to tinyobj --- CMakeLists.txt | 2 +- Scripts/BuildEngine.ps1 | 2 +- .../Components/DockspaceUIComponent.cpp | 4 +- Tetragrama/Importers/RapidobjImporter.cpp | 468 ------------------ Tetragrama/Importers/TinyobjImporter.cpp | 322 ++++++++++++ .../{RapidobjImporter.h => TinyobjImporter.h} | 21 +- 6 files changed, 336 insertions(+), 483 deletions(-) delete mode 100644 Tetragrama/Importers/RapidobjImporter.cpp create mode 100644 Tetragrama/Importers/TinyobjImporter.cpp rename Tetragrama/Importers/{RapidobjImporter.h => TinyobjImporter.h} (69%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b082b2c4..ed81d1a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ if (NOT LAUNCHER_ONLY) add_subdirectory (${EXTERNAL_DIR}/glm) add_subdirectory (${EXTERNAL_DIR}/entt) add_subdirectory (${EXTERNAL_DIR}/assimp) - add_subdirectory (${EXTERNAL_DIR}/rapidobj) + add_subdirectory (${EXTERNAL_DIR}/tinyobjloader) add_subdirectory (${EXTERNAL_DIR}/stduuid) add_subdirectory (${EXTERNAL_DIR}/yaml-cpp) add_subdirectory (${EXTERNAL_DIR}/SPIRV-headers) diff --git a/Scripts/BuildEngine.ps1 b/Scripts/BuildEngine.ps1 index d87949c6..f2f97456 100644 --- a/Scripts/BuildEngine.ps1 +++ b/Scripts/BuildEngine.ps1 @@ -148,7 +148,7 @@ function Build([string]$configuration, [int]$VsVersion , [bool]$runBuild) { 'SPDLOG' = @("-DSPDLOG_BUILD_SHARED=OFF", "-DBUILD_STATIC_LIBS=ON", "-DSPDLOG_FMT_EXTERNAL=ON", "-DSPDLOG_FMT_EXTERNAL_HO=OFF"); 'GLFW ' = @("-DGLFW_BUILD_DOCS=OFF", "-DGLFW_BUILD_EXAMPLES=OFF", "-DGLFW_INSTALL=OFF"); 'ASSIMP' = @("-DASSIMP_BUILD_TESTS=OFF", "-DASSIMP_INSTALL=OFF", "-DASSIMP_BUILD_SAMPLES=OFF", "-DASSIMP_BUILD_ASSIMP_TOOLS=OFF"); - 'RAPIDOBJ' = @("-DRAPIDOBJ_BuildTests=OFF", "-DRAPIDOBJ_BuildTools=OFF", "-DRAPIDOBJ_BuildExampleS=OFF"); + 'TINYOBJLOADER' = @("-DTINYOBJLOADER_USE_DOUBLE=OFF", "-DTINYOBJLOADER_WITH_PYTHON=OFF", "-DTINYOBJLOADER_PREFER_LOCAL_PYTHON_INSTALLATION=OFF", "-DTINYOBJLOADER_BUILD_TEST_LOADER=OFF", "-DTINYOBJLOADER_BUILD_OBJ_STICHER=OFF"); 'STDUUID' = @("-DUUID_BUILD_TESTS=OFF", "-DUUID_USING_CXX20_SPAN=ON", "-DUUID_SYSTEM_GENERATOR=OFF"); 'YAMLCPP' = @("-DYAML_CPP_BUILD_TOOLS=OFF", "-DYAML_CPP_BUILD_TESTS=OFF", "-DYAML_CPP_FORMAT_SOURCE=OFF", "-DYAML_BUILD_SHARED_LIBS=OFF"); 'FRAMEWORK' = @("-DBUILD_FRAMEWORK=ON"); diff --git a/Tetragrama/Components/DockspaceUIComponent.cpp b/Tetragrama/Components/DockspaceUIComponent.cpp index 48f17e68..c1a03efc 100644 --- a/Tetragrama/Components/DockspaceUIComponent.cpp +++ b/Tetragrama/Components/DockspaceUIComponent.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -23,7 +23,7 @@ namespace Tetragrama::Components float DockspaceUIComponent::s_editor_scene_serializer_progress = 0.0f; DockspaceUIComponent::DockspaceUIComponent(std::string_view name, bool visibility) - : UIComponent(name, visibility, false), m_asset_importer(CreateScope()), m_editor_serializer(CreateScope()) + : UIComponent(name, visibility, false), m_asset_importer(CreateScope()), m_editor_serializer(CreateScope()) { m_dockspace_node_flag = ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_PassthruCentralNode; m_window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | diff --git a/Tetragrama/Importers/RapidobjImporter.cpp b/Tetragrama/Importers/RapidobjImporter.cpp deleted file mode 100644 index e273fe23..00000000 --- a/Tetragrama/Importers/RapidobjImporter.cpp +++ /dev/null @@ -1,468 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace Tetragrama::Importers -{ - RapidobjImporter::RapidobjImporter() = default; - - RapidobjImporter::~RapidobjImporter() = default; - - std::future RapidobjImporter::ImportAsync(std::string_view filename, ImportConfiguration config) - { - ThreadPoolHelper::Submit([this, path = std::string(filename.data()), config] { - { - std::unique_lock l(m_mutex); - m_is_importing = true; - } - auto result = rapidobj::ParseFile(path); - - if (result.error) - { - if (m_error_callback) - { - m_error_callback(result.error.line); - } - } - else - { - ImporterData import_data = {}; - PreprocessMesh(result); - ExtractMeshes(result, import_data); - ExtractMaterials(result, import_data); - ExtractTextures(result, import_data); - CreateHierarchyScene(result, import_data); - /* - * Serialization of ImporterData - */ - REPORT_LOG("Serializing model...") - SerializeImporterData(import_data, config); - - if (m_complete_callback) - { - m_complete_callback(std::move(import_data)); - } - } - { - std::unique_lock l(m_mutex); - m_is_importing = false; - } - }); - co_return; - } - - void RapidobjImporter::PreprocessMesh(rapidobj::Result& result) - { - // Generate normals if they don't exist - if (result.attributes.normals.empty()) - { - rapidobj::Array normals(result.attributes.positions.size()); - for (size_t i = 0; i < normals.size(); ++i) - { - normals[i] = 0.0f; - } - - for (const auto& shape : result.shapes) - { - for (size_t f = 0; f < shape.mesh.indices.size(); f += 3) - { - const auto& idx0 = shape.mesh.indices[f]; - const auto& idx1 = shape.mesh.indices[f + 1]; - const auto& idx2 = shape.mesh.indices[f + 2]; - - glm::vec3 v0( - result.attributes.positions[idx0.position_index * 3], - result.attributes.positions[idx0.position_index * 3 + 1], - result.attributes.positions[idx0.position_index * 3 + 2]); - glm::vec3 v1( - result.attributes.positions[idx1.position_index * 3], - result.attributes.positions[idx1.position_index * 3 + 1], - result.attributes.positions[idx1.position_index * 3 + 2]); - glm::vec3 v2( - result.attributes.positions[idx2.position_index * 3], - result.attributes.positions[idx2.position_index * 3 + 1], - result.attributes.positions[idx2.position_index * 3 + 2]); - - glm::vec3 normal = glm::cross(v1 - v0, v2 - v0); - - if (glm::dot(normal, normal) > 0.0f) - { - normal = glm::normalize(normal); - for (int i = 0; i < 3; i++) - { - size_t idx = shape.mesh.indices[f + i].position_index * 3; - normals[idx] += normal.x; - normals[idx + 1] += normal.y; - normals[idx + 2] += normal.z; - } - } - } - } - - for (size_t i = 0; i < normals.size(); i += 3) - { - glm::vec3 n(normals[i], normals[i + 1], normals[i + 2]); - if (glm::dot(n, n) > 0.0f) - { - n = glm::normalize(n); - } - else - { - n = glm::vec3(0.0f, 1.0f, 0.0f); - } - normals[i] = n.x; - normals[i + 1] = n.y; - normals[i + 2] = n.z; - } - - result.attributes.normals = std::move(normals); - } - - // Generate texture coordinates if they don't exist - if (result.attributes.texcoords.empty()) - { - REPORT_LOG("Generating default UV coordinates..."); - - glm::vec3 min_pos(std::numeric_limits::max()); - glm::vec3 max_pos(std::numeric_limits::lowest()); - - for (size_t i = 0; i < result.attributes.positions.size(); i += 3) - { - glm::vec3 pos(result.attributes.positions[i], result.attributes.positions[i + 1], result.attributes.positions[i + 2]); - min_pos = glm::min(min_pos, pos); - max_pos = glm::max(max_pos, pos); - } - - glm::vec3 size = max_pos - min_pos; - - rapidobj::Array texcoords(result.attributes.positions.size() / 3 * 2); - - for (size_t i = 0; i < result.attributes.positions.size(); i += 3) - { - float u = (result.attributes.positions[i] - min_pos.x) / size.x; - float v = (result.attributes.positions[i + 2] - min_pos.z) / size.z; - - texcoords[(i / 3) * 2] = u; - texcoords[(i / 3) * 2 + 1] = v; - } - - result.attributes.texcoords = std::move(texcoords); - } - // Triangulate - if (!rapidobj::Triangulate(result)) - { - REPORT_LOG("Cannot triangulate model...") - } - - // vertex deduplication - for (auto& shape : result.shapes) - { - std::unordered_map unique_vertices; - std::vector new_indices; - new_indices.reserve(shape.mesh.indices.size()); - - const size_t num_indices = shape.mesh.indices.size(); - for (size_t i = 0; i < num_indices; ++i) - { - const auto& index = shape.mesh.indices[i]; - VertexData vertex{}; - - // Position - vertex.px = result.attributes.positions[index.position_index * 3]; - vertex.py = result.attributes.positions[index.position_index * 3 + 1]; - vertex.pz = result.attributes.positions[index.position_index * 3 + 2]; - - // Normal - vertex.nx = result.attributes.normals[index.normal_index * 3]; - vertex.ny = result.attributes.normals[index.normal_index * 3 + 1]; - vertex.nz = result.attributes.normals[index.normal_index * 3 + 2]; - - // UV - vertex.u = result.attributes.texcoords[index.texcoord_index * 2]; - vertex.v = result.attributes.texcoords[index.texcoord_index * 2 + 1]; - - auto it = unique_vertices.find(vertex); - if (it != unique_vertices.end()) - { - rapidobj::Index new_index; - new_index.position_index = it->second; - new_index.normal_index = it->second; - new_index.texcoord_index = it->second; - new_indices.push_back(new_index); - } - else - { - uint32_t new_index_value = static_cast(unique_vertices.size()); - unique_vertices[vertex] = new_index_value; - - rapidobj::Index new_index; - new_index.position_index = new_index_value; - new_index.normal_index = new_index_value; - new_index.texcoord_index = new_index_value; - new_indices.push_back(new_index); - } - } - - rapidobj::Array new_positions(unique_vertices.size() * 3); - rapidobj::Array new_normals(unique_vertices.size() * 3); - rapidobj::Array new_texcoords(unique_vertices.size() * 2); - - size_t vert_idx = 0; - for (const auto& [vert, idx] : unique_vertices) - { - new_positions[idx * 3] = vert.px; - new_positions[idx * 3 + 1] = vert.py; - new_positions[idx * 3 + 2] = vert.pz; - - new_normals[idx * 3] = vert.nx; - new_normals[idx * 3 + 1] = vert.ny; - new_normals[idx * 3 + 2] = vert.nz; - - new_texcoords[idx * 2] = vert.u; - new_texcoords[idx * 2 + 1] = vert.v; - } - - rapidobj::Array final_indices(new_indices.size()); - memcpy(final_indices.data(), new_indices.data(), new_indices.size() * sizeof(rapidobj::Index)); - - // Update shape and attributes - result.attributes.positions = std::move(new_positions); - result.attributes.normals = std::move(new_normals); - result.attributes.texcoords = std::move(new_texcoords); - shape.mesh.indices = std::move(final_indices); - } - REPORT_LOG("Preprocessing complete."); - } - - void RapidobjImporter::ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data) - { - const auto& attributes = result.attributes; - const auto& shapes = result.shapes; - - if (shapes.empty() || attributes.positions.empty()) - { - REPORT_LOG("No shapes or vertices found in the OBJ file."); - return; - } - - importer_data.Scene.Meshes.reserve(shapes.size()); - - for (size_t shape_idx = 0; shape_idx < shapes.size(); ++shape_idx) - { - const auto& shape = shapes[shape_idx]; - const size_t face_count = shape.mesh.indices.size() / 3; - - for (const auto& index : shape.mesh.indices) - { - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3]); - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 1]); - importer_data.Scene.Vertices.push_back(attributes.positions[index.position_index * 3 + 2]); - - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3]); - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 1]); - importer_data.Scene.Vertices.push_back(attributes.normals[index.normal_index * 3 + 2]); - - importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2]); - importer_data.Scene.Vertices.push_back(attributes.texcoords[index.texcoord_index * 2 + 1]); - - importer_data.Scene.Indices.push_back(importer_data.VertexOffset++); - } - - MeshVNext& mesh = importer_data.Scene.Meshes.emplace_back(); - mesh.VertexCount = static_cast(shape.mesh.indices.size()); - mesh.VertexOffset = importer_data.VertexOffset - mesh.VertexCount; - mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); - mesh.StreamOffset = mesh.VertexUnitStreamSize * mesh.VertexOffset; - mesh.IndexOffset = importer_data.IndexOffset; - mesh.IndexCount = static_cast(shape.mesh.indices.size()); - mesh.IndexUnitStreamSize = sizeof(uint32_t); - mesh.IndexStreamOffset = mesh.IndexUnitStreamSize * mesh.IndexOffset; - mesh.TotalByteSize = (mesh.VertexCount * mesh.VertexUnitStreamSize) + (mesh.IndexCount * mesh.IndexUnitStreamSize); - - importer_data.IndexOffset += mesh.IndexCount; - } - } - - void RapidobjImporter::ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data) - { - static constexpr float OPAQUENESS_THRESHOLD = 0.05f; - - // Handle case where no materials exist - if (result.materials.empty()) - { - MeshMaterial& default_material = importer_data.Scene.Materials.emplace_back(); - default_material.AlbedoColor = ZEngine::Rendering::gpuvec4{0.7f, 0.7f, 0.7f, 1.0f}; - default_material.AmbientColor = ZEngine::Rendering::gpuvec4{0.2f, 0.2f, 0.2f, 1.0f}; - default_material.SpecularColor = ZEngine::Rendering::gpuvec4{0.5f, 0.5f, 0.5f, 1.0f}; - default_material.EmissiveColor = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 1.0f}; - default_material.Factors = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 0.0f}; - importer_data.Scene.MaterialNames.push_back(""); - return; - } - - const uint32_t number_of_materials = static_cast(result.materials.size()); - importer_data.Scene.Materials.reserve(number_of_materials); - importer_data.Scene.MaterialNames.reserve(number_of_materials); - - auto toVec4 = [](const rapidobj::Float3& rgb, float alpha = 1.0f) -> ZEngine::Rendering::gpuvec4 { - return ZEngine::Rendering::gpuvec4{std::clamp(rgb[0], 0.0f, 1.0f), std::clamp(rgb[1], 0.0f, 1.0f), std::clamp(rgb[2], 0.0f, 1.0f), std::clamp(alpha, 0.0f, 1.0f)}; - }; - - for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) - { - const auto& obj_material = result.materials[mat_idx]; - MeshMaterial& material = importer_data.Scene.Materials.emplace_back(); - - importer_data.Scene.MaterialNames.push_back(obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name); - - material.AmbientColor = toVec4(obj_material.ambient); - material.AlbedoColor = toVec4(obj_material.diffuse); - material.SpecularColor = toVec4(obj_material.specular); - material.EmissiveColor = toVec4(obj_material.emission); - - float opacity = obj_material.dissolve; - material.Factors.x = (opacity < 1.0f) ? std::clamp(1.0f - opacity, 0.0f, 1.0f) : 0.0f; - - float transmission = std::max({obj_material.transmittance[0], obj_material.transmittance[1], obj_material.transmittance[2]}); - if (transmission > 0.0f) - { - material.Factors.x = std::clamp(transmission, 0.0f, 1.0f); - material.Factors.z = 0.5f; - } - - material.Factors.y = std::clamp(obj_material.shininess / 1000.0f, 0.0f, 1.0f); - material.Factors.w = static_cast(obj_material.illum) / 10.0f; - - // Initialize texture indices - material.AlbedoMap = 0xFFFFFFFF; - material.NormalMap = 0xFFFFFFFF; - material.SpecularMap = 0xFFFFFFFF; - material.EmissiveMap = 0xFFFFFFFF; - material.OpacityMap = 0xFFFFFFFF; - } - } - - void RapidobjImporter::ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data) - { - if (result.materials.empty()) - { - REPORT_LOG("No materials found for texture extraction."); - return; - } - - const uint32_t number_of_materials = static_cast(result.materials.size()); - REPORT_LOG(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); - - auto processTexturePath = [this, &importer_data](const std::string& texname, const std::string& type) -> uint32_t { - if (texname.empty()) - { - REPORT_LOG(fmt::format("No {} texture specified.", type)); - return 0xFFFFFFFF; - } - - std::string normalized_path = texname; - std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); - - return GenerateFileIndex(importer_data.Scene.Files, normalized_path); - }; - - for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) - { - const auto& obj_material = result.materials[mat_idx]; - auto& material = importer_data.Scene.Materials[mat_idx]; - - material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); - material.SpecularMap = processTexturePath(obj_material.specular_texname, "specular"); - material.EmissiveMap = processTexturePath(obj_material.emissive_texname, "emissive"); - - material.NormalMap = !obj_material.bump_texname.empty() ? processTexturePath(obj_material.bump_texname, "bump") - : !obj_material.normal_texname.empty() ? processTexturePath(obj_material.normal_texname, "normal") - : !obj_material.displacement_texname.empty() ? processTexturePath(obj_material.displacement_texname, "displacement as normal") - : 0xFFFFFFFF; - - material.OpacityMap = processTexturePath(obj_material.alpha_texname, "opacity"); - } - } - - int RapidobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) - { - auto find = std::find(std::begin(data), std::end(data), filename); - if (find != std::end(data)) - { - return std::distance(std::begin(data), find); - } - - data.push_back(filename.data()); - return (data.size() - 1); - } - - void RapidobjImporter::CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data) - { - if (result.shapes.empty()) - { - return; - } - - // Create root node ?? - auto root_id = SceneRawData::AddNode(&importer_data.Scene, -1, 0); - importer_data.Scene.NodeNames[root_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back("Root"); - importer_data.Scene.GlobalTransformCollection[root_id] = glm::mat4(1.0f); - importer_data.Scene.LocalTransformCollection[root_id] = glm::mat4(1.0f); - - // Create model node ?? - auto model_node_id = SceneRawData::AddNode(&importer_data.Scene, root_id, 1); - importer_data.Scene.NodeNames[model_node_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back("model_Mesh1_Model"); - importer_data.Scene.GlobalTransformCollection[model_node_id] = glm::mat4(1.0f); - importer_data.Scene.LocalTransformCollection[model_node_id] = glm::mat4(1.0f); - - // Collect all unique materials across all shapes - std::map> material_to_shapes; - - for (size_t shape_idx = 0; shape_idx < result.shapes.size(); ++shape_idx) - { - const auto& shape = result.shapes[shape_idx]; - std::set shape_materials; - - for (int32_t mat_id : shape.mesh.material_ids) - { - shape_materials.insert(mat_id); - } - - for (int32_t mat_id : shape_materials) - { - material_to_shapes[mat_id].push_back(shape_idx); - } - } - - // Create a node for each material - for (const auto& [material_id, shape_indices] : material_to_shapes) - { - auto material_node_id = SceneRawData::AddNode(&importer_data.Scene, model_node_id, 2); - importer_data.Scene.NodeNames[material_node_id] = importer_data.Scene.Names.size(); - - std::string material_name = material_id < static_cast(importer_data.Scene.MaterialNames.size()) ? importer_data.Scene.MaterialNames[material_id] - : fmt::format("material_{}", material_id); - - importer_data.Scene.Names.push_back(material_name); - - // Create child nodes for each shape using this material - for (size_t shape_idx : shape_indices) - { - auto shape_node_id = SceneRawData::AddNode(&importer_data.Scene, material_node_id, 3); - importer_data.Scene.NodeNames[shape_node_id] = importer_data.Scene.Names.size(); - importer_data.Scene.Names.push_back(fmt::format("{}_{}", material_name, shape_idx)); - - importer_data.Scene.NodeMeshes[shape_node_id] = static_cast(shape_idx); - importer_data.Scene.NodeMaterials[shape_node_id] = static_cast(material_id); - - importer_data.Scene.GlobalTransformCollection[shape_node_id] = glm::mat4(1.0f); - importer_data.Scene.LocalTransformCollection[shape_node_id] = glm::mat4(1.0f); - } - } - } -} // namespace Tetragrama::Importers \ No newline at end of file diff --git a/Tetragrama/Importers/TinyobjImporter.cpp b/Tetragrama/Importers/TinyobjImporter.cpp new file mode 100644 index 00000000..8e72c306 --- /dev/null +++ b/Tetragrama/Importers/TinyobjImporter.cpp @@ -0,0 +1,322 @@ +#define TINYOBJLOADER_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +namespace Tetragrama::Importers +{ + TInyobjImporter::TInyobjImporter() = default; + + TInyobjImporter::~TInyobjImporter() = default; + + std::future TInyobjImporter::ImportAsync(std::string_view filename, ImportConfiguration config) + { + ThreadPoolHelper::Submit([this, path = std::string(filename.data()), config] { + { + std::unique_lock l(m_mutex); + m_is_importing = true; + } + + tinyobj::ObjReader reader; + + if (!reader.ParseFromFile(path)) + { + if (m_error_callback) + { + m_error_callback(reader.Error()); + } + } + else + { + ImporterData import_data = {}; + ExtractMeshes(reader, import_data); + ExtractMaterials(reader, import_data); + ExtractTextures(reader, import_data); + CreateHierarchyScene(reader, import_data); + /* + * Serialization of ImporterData + */ + REPORT_LOG("Serializing model...") + SerializeImporterData(import_data, config); + + if (m_complete_callback) + { + m_complete_callback(std::move(import_data)); + } + } + { + std::unique_lock l(m_mutex); + m_is_importing = false; + } + }); + co_return; + } + + void TInyobjImporter::ExtractMeshes(const tinyobj::ObjReader& reader, ImporterData& importer_data) + { + const auto& attributes = reader.GetAttrib(); + const auto& shapes = reader.GetShapes(); + + if (shapes.empty() || attributes.vertices.empty()) + { + REPORT_LOG("No shapes or vertices found in the OBJ file."); + return; + } + + importer_data.Scene.Meshes.reserve(shapes.size()); + + for (const auto& shape : shapes) + { + std::unordered_map unique_vertices; + const size_t face_count = shape.mesh.indices.size() / 3; + + size_t vertex_start = importer_data.Scene.Vertices.size() / 8; + size_t index_start = importer_data.Scene.Indices.size(); + + for (size_t f = 0; f < face_count; ++f) + { + for (size_t v = 0; v < 3; ++v) + { + const auto& index = shape.mesh.indices[f * 3 + v]; + VertexData vertex{}; + + // Position + vertex.px = attributes.vertices[3 * index.vertex_index]; + vertex.py = attributes.vertices[3 * index.vertex_index + 1]; + vertex.pz = attributes.vertices[3 * index.vertex_index + 2]; + + // Normal + vertex.nx = attributes.normals[3 * index.normal_index]; + vertex.ny = attributes.normals[3 * index.normal_index + 1]; + vertex.nz = attributes.normals[3 * index.normal_index + 2]; + + // UV + vertex.u = attributes.texcoords[2 * index.texcoord_index]; + vertex.v = attributes.texcoords[2 * index.texcoord_index + 1]; + + auto it = unique_vertices.find(vertex); + uint32_t vertex_index; + + if (it != unique_vertices.end()) + { + vertex_index = it->second; + } + else + { + vertex_index = static_cast(unique_vertices.size()); + unique_vertices[vertex] = vertex_index; + + importer_data.Scene.Vertices.insert( + importer_data.Scene.Vertices.end(), {vertex.px, vertex.py, vertex.pz, vertex.nx, vertex.ny, vertex.nz, vertex.u, vertex.v}); + } + + importer_data.Scene.Indices.push_back(importer_data.VertexOffset + vertex_index); + } + } + + // Create mesh entry + MeshVNext& mesh = importer_data.Scene.Meshes.emplace_back(); + mesh.VertexCount = static_cast(unique_vertices.size()); + mesh.VertexOffset = importer_data.VertexOffset; + mesh.VertexUnitStreamSize = sizeof(float) * (3 + 3 + 2); + mesh.StreamOffset = mesh.VertexUnitStreamSize * mesh.VertexOffset; + mesh.IndexOffset = importer_data.IndexOffset; + mesh.IndexCount = static_cast(shape.mesh.indices.size()); + mesh.IndexUnitStreamSize = sizeof(uint32_t); + mesh.IndexStreamOffset = mesh.IndexUnitStreamSize * mesh.IndexOffset; + mesh.TotalByteSize = (mesh.VertexCount * mesh.VertexUnitStreamSize) + (mesh.IndexCount * mesh.IndexUnitStreamSize); + + importer_data.VertexOffset += mesh.VertexCount; + importer_data.IndexOffset += mesh.IndexCount; + } + } + + void TInyobjImporter::ExtractMaterials(const tinyobj::ObjReader& reader, ImporterData& importer_data) + { + static constexpr float OPAQUENESS_THRESHOLD = 0.05f; + + auto& materials = reader.GetMaterials(); + + // Handle case where no materials exist + if (materials.empty()) + { + MeshMaterial& default_material = importer_data.Scene.Materials.emplace_back(); + default_material.AlbedoColor = ZEngine::Rendering::gpuvec4{0.7f, 0.7f, 0.7f, 1.0f}; + default_material.AmbientColor = ZEngine::Rendering::gpuvec4{0.2f, 0.2f, 0.2f, 1.0f}; + default_material.SpecularColor = ZEngine::Rendering::gpuvec4{0.5f, 0.5f, 0.5f, 1.0f}; + default_material.EmissiveColor = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 1.0f}; + default_material.Factors = ZEngine::Rendering::gpuvec4{0.0f, 0.0f, 0.0f, 0.0f}; + importer_data.Scene.MaterialNames.push_back(""); + return; + } + + const uint32_t number_of_materials = static_cast(materials.size()); + importer_data.Scene.Materials.reserve(number_of_materials); + importer_data.Scene.MaterialNames.reserve(number_of_materials); + + auto toVec4 = [](const float* rgb, float alpha = 1.0f) -> ZEngine::Rendering::gpuvec4 { + return ZEngine::Rendering::gpuvec4{std::clamp(rgb[0], 0.0f, 1.0f), std::clamp(rgb[1], 0.0f, 1.0f), std::clamp(rgb[2], 0.0f, 1.0f), std::clamp(alpha, 0.0f, 1.0f)}; + }; + + for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) + { + const auto& obj_material = materials[mat_idx]; + MeshMaterial& material = importer_data.Scene.Materials.emplace_back(); + + importer_data.Scene.MaterialNames.push_back(obj_material.name.empty() ? fmt::format("", mat_idx) : obj_material.name); + + material.AmbientColor = toVec4(obj_material.ambient); + material.AlbedoColor = toVec4(obj_material.diffuse); + material.SpecularColor = toVec4(obj_material.specular); + material.EmissiveColor = toVec4(obj_material.emission); + + float opacity = obj_material.dissolve; + material.Factors.x = (opacity < 1.0f) ? std::clamp(1.0f - opacity, 0.0f, 1.0f) : 0.0f; + + float transmission = std::max({obj_material.transmittance[0], obj_material.transmittance[1], obj_material.transmittance[2]}); + if (transmission > 0.0f) + { + material.Factors.x = std::clamp(transmission, 0.0f, 1.0f); + material.Factors.z = 0.5f; + } + + material.Factors.y = std::clamp(obj_material.shininess / 1000.0f, 0.0f, 1.0f); + material.Factors.w = static_cast(obj_material.illum) / 10.0f; + + // Initialize texture indices + material.AlbedoMap = 0xFFFFFFFF; + material.NormalMap = 0xFFFFFFFF; + material.SpecularMap = 0xFFFFFFFF; + material.EmissiveMap = 0xFFFFFFFF; + material.OpacityMap = 0xFFFFFFFF; + } + } + + void TInyobjImporter::ExtractTextures(const tinyobj::ObjReader& reader, ImporterData& importer_data) + { + auto& materials = reader.GetMaterials(); + if (materials.empty()) + { + REPORT_LOG("No materials found for texture extraction."); + return; + } + + const uint32_t number_of_materials = static_cast(materials.size()); + REPORT_LOG(fmt::format("Processing textures for {} materials", number_of_materials).c_str()); + + auto processTexturePath = [this, &importer_data](const std::string& texname, const std::string& type) -> uint32_t { + if (texname.empty()) + { + REPORT_LOG(fmt::format("No {} texture specified.", type)); + return 0xFFFFFFFF; + } + + std::string normalized_path = texname; + std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); + + return GenerateFileIndex(importer_data.Scene.Files, normalized_path); + }; + + for (uint32_t mat_idx = 0; mat_idx < number_of_materials; ++mat_idx) + { + const auto& obj_material = materials[mat_idx]; + auto& material = importer_data.Scene.Materials[mat_idx]; + + material.AlbedoMap = processTexturePath(obj_material.diffuse_texname, "albedo"); + material.SpecularMap = processTexturePath(obj_material.specular_texname, "specular"); + material.EmissiveMap = processTexturePath(obj_material.emissive_texname, "emissive"); + + material.NormalMap = !obj_material.bump_texname.empty() ? processTexturePath(obj_material.bump_texname, "bump") + : !obj_material.normal_texname.empty() ? processTexturePath(obj_material.normal_texname, "normal") + : !obj_material.displacement_texname.empty() ? processTexturePath(obj_material.displacement_texname, "displacement as normal") + : 0xFFFFFFFF; + + material.OpacityMap = processTexturePath(obj_material.alpha_texname, "opacity"); + } + } + + int TInyobjImporter::GenerateFileIndex(std::vector& data, std::string_view filename) + { + auto find = std::find(std::begin(data), std::end(data), filename); + if (find != std::end(data)) + { + return std::distance(std::begin(data), find); + } + + data.push_back(filename.data()); + return (data.size() - 1); + } + + void TInyobjImporter::CreateHierarchyScene(const tinyobj::ObjReader& reader, ImporterData& importer_data) + { + auto& shapes = reader.GetShapes(); + + if (shapes.empty()) + { + return; + } + + // Create root node ?? + auto root_id = SceneRawData::AddNode(&importer_data.Scene, -1, 0); + importer_data.Scene.NodeNames[root_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back("Root"); + importer_data.Scene.GlobalTransformCollection[root_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[root_id] = glm::mat4(1.0f); + + // Create model node ?? + auto model_node_id = SceneRawData::AddNode(&importer_data.Scene, root_id, 1); + importer_data.Scene.NodeNames[model_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back("model_Mesh1_Model"); + importer_data.Scene.GlobalTransformCollection[model_node_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[model_node_id] = glm::mat4(1.0f); + + // Collect all unique materials across all shapes + std::map> material_to_shapes; + + for (size_t shape_idx = 0; shape_idx < shapes.size(); ++shape_idx) + { + const auto& shape = shapes[shape_idx]; + std::set shape_materials; + + for (int32_t mat_id : shape.mesh.material_ids) + { + shape_materials.insert(mat_id); + } + + for (int32_t mat_id : shape_materials) + { + material_to_shapes[mat_id].push_back(shape_idx); + } + } + + // Create a node for each material + for (const auto& [material_id, shape_indices] : material_to_shapes) + { + auto material_node_id = SceneRawData::AddNode(&importer_data.Scene, model_node_id, 2); + importer_data.Scene.NodeNames[material_node_id] = importer_data.Scene.Names.size(); + + std::string material_name = material_id < static_cast(importer_data.Scene.MaterialNames.size()) ? importer_data.Scene.MaterialNames[material_id] + : fmt::format("material_{}", material_id); + + importer_data.Scene.Names.push_back(material_name); + + // Create child nodes for each shape using this material + for (size_t shape_idx : shape_indices) + { + auto shape_node_id = SceneRawData::AddNode(&importer_data.Scene, material_node_id, 3); + importer_data.Scene.NodeNames[shape_node_id] = importer_data.Scene.Names.size(); + importer_data.Scene.Names.push_back(fmt::format("{}_{}", material_name, shape_idx)); + + importer_data.Scene.NodeMeshes[shape_node_id] = static_cast(shape_idx); + importer_data.Scene.NodeMaterials[shape_node_id] = static_cast(material_id); + + importer_data.Scene.GlobalTransformCollection[shape_node_id] = glm::mat4(1.0f); + importer_data.Scene.LocalTransformCollection[shape_node_id] = glm::mat4(1.0f); + } + } + } +} // namespace Tetragrama::Importers \ No newline at end of file diff --git a/Tetragrama/Importers/RapidobjImporter.h b/Tetragrama/Importers/TinyobjImporter.h similarity index 69% rename from Tetragrama/Importers/RapidobjImporter.h rename to Tetragrama/Importers/TinyobjImporter.h index 01613001..8d4ae546 100644 --- a/Tetragrama/Importers/RapidobjImporter.h +++ b/Tetragrama/Importers/TinyobjImporter.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include using namespace ZEngine::Helpers; using namespace ZEngine::Rendering::Meshes; @@ -9,11 +9,11 @@ using namespace ZEngine::Rendering::Scenes; namespace Tetragrama::Importers { - class RapidobjImporter : public IAssetImporter + class TInyobjImporter : public IAssetImporter { public: - RapidobjImporter(); - virtual ~RapidobjImporter(); + TInyobjImporter(); + virtual ~TInyobjImporter(); std::future ImportAsync(std::string_view filename, ImportConfiguration config = {}) override; // To aid in Vertex deduplication @@ -30,11 +30,10 @@ namespace Tetragrama::Importers }; private: - void PreprocessMesh(rapidobj::Result& result); - void ExtractMeshes(const rapidobj::Result& result, ImporterData& importer_data); - void ExtractMaterials(const rapidobj::Result& result, ImporterData& importer_data); - void ExtractTextures(const rapidobj::Result& result, ImporterData& importer_data); - void CreateHierarchyScene(const rapidobj::Result& result, ImporterData& importer_data); + void ExtractMeshes(const tinyobj::ObjReader& reader, ImporterData& importer_data); + void ExtractMaterials(const tinyobj::ObjReader& reader, ImporterData& importer_data); + void ExtractTextures(const tinyobj::ObjReader& reader, ImporterData& importer_data); + void CreateHierarchyScene(const tinyobj::ObjReader& reader, ImporterData& importer_data); int GenerateFileIndex(std::vector& data, std::string_view filename); }; } // namespace Tetragrama::Importers @@ -42,9 +41,9 @@ namespace Tetragrama::Importers namespace std { template <> - struct hash + struct hash { - size_t operator()(const Tetragrama::Importers::RapidobjImporter::VertexData& v) const noexcept + size_t operator()(const Tetragrama::Importers::TInyobjImporter::VertexData& v) const noexcept { size_t seed = 0; auto hash_combine = [&seed](size_t hash) {