diff --git a/Editor/src/ComponentsPanel/MeshPanel.h b/Editor/src/ComponentsPanel/MeshPanel.h index 5fac696c..a28c328d 100644 --- a/Editor/src/ComponentsPanel/MeshPanel.h +++ b/Editor/src/ComponentsPanel/MeshPanel.h @@ -12,15 +12,17 @@ #include #include +#include "src/Resource/Bakers/AssetBakerManager.h" +#include "src/Resource/ResourceManager.h" class MeshPanel : ComponentPanel { -private: + private: Scope _modelInspector; bool _expanded = false; - + std::string _importedPathMesh; -public: + public: MeshPanel() { CreateScope(); @@ -73,55 +75,31 @@ public: { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("_Model")) { - char* file = (char*)payload->Data; - std::string fullPath = std::string(file, 256); + // Convert payload to relative path + std::string fullPath = std::string(static_cast(payload->Data), 256); fullPath = Nuake::FileSystem::AbsoluteToRelative(fullPath); - if (Nuake::String::EndsWith(fullPath, ".mesh")) - { - component.ModelPath = fullPath; - //component.ModelResource = ResourceLoader::LoadModel(fullPath); - } - else - { - // Convert to .Model - //component.ModelPath = fullPath; - //component.LoadModel(); - // - //_importedPathMesh = fullPath; - // - //auto loader = ModelLoader(); - //auto modelResource = loader.LoadModel(fullPath); - //shouldConvert = true; - } + auto file = Nuake::FileSystem::GetFile(fullPath); + if(file) + { + // TODO(antopilo) use file type instead of extension + if(file->GetExtension() == ".nkmesh") + { + auto modelResource = ResourceManager::GetResourceFromFile(file); + component.ModelResource = modelResource->ID; + } + else + { + // Check if we can bake this filetype + AssetBakerManager& bakerMgr = AssetBakerManager::Get(); + bakerMgr.Bake(file); + } + + } } ImGui::EndDragDropTarget(); } - - if (PopupHelper::DefineConfirmationDialog("##ConvertAsset", "Convert Asset")) - { - // Convert to disk - auto loader = ModelLoader(); - Ref modelResource = loader.LoadModel(_importedPathMesh); - //json serializedData = modelResource->SerializeData(); - // - //const std::string exportedMeshPath = _importedPathMesh + ".mesh"; - //FileSystem::BeginWriteFile(exportedMeshPath); - //FileSystem::WriteLine(serializedData.dump()); - //FileSystem::EndWriteFile(); - // - //ResourceManager::RegisterResource(modelResource); - // - //// Update component - //component.ModelPath = exportedMeshPath; - //component.ModelResource = modelResource; - } - - if (shouldConvert) - { - PopupHelper::OpenPopup("##ConvertAsset"); - } - + ImGui::TableNextColumn(); ComponentTableReset(component.ModelPath, ""); } diff --git a/Editor/src/EditorApplication.cpp b/Editor/src/EditorApplication.cpp index ae70e258..c3005674 100644 --- a/Editor/src/EditorApplication.cpp +++ b/Editor/src/EditorApplication.cpp @@ -10,13 +10,18 @@ #include "src/UI/NuakeUI.h" #include "src/UI/UIInputManager.h" - +#include "src/Resource/Bakers/AssetBakerManager.h" +#include "src/Resource/Bakers/GLTFBaker.h" void EditorApplication::OnInit() { using namespace Nuake; - Engine::Init(); + + // Register bakers, these can convert files into nuake resources + AssetBakerManager& assetBakerMgr = AssetBakerManager::Get(); + assetBakerMgr.RegisterBaker(CreateRef()); + m_Window = Engine::GetCurrentWindow(); m_Window->SetSize({ m_Specification.WindowWidth, m_Specification.WindowHeight }); m_Window->SetTitle(m_Specification.Name); diff --git a/Editor/src/Windows/FileSystemUI.cpp b/Editor/src/Windows/FileSystemUI.cpp index fe929339..fec33dea 100644 --- a/Editor/src/Windows/FileSystemUI.cpp +++ b/Editor/src/Windows/FileSystemUI.cpp @@ -305,7 +305,7 @@ namespace Nuake { dragType = "_Material"; } - else if (fileExtension == ".mesh" || fileExtension == ".obj" || fileExtension == ".mdl" || fileExtension == ".gltf" || fileExtension == ".md3" || fileExtension == ".fbx" || fileExtension == ".glb") + else if (fileExtension == ".nkmesh" || fileExtension == ".obj" || fileExtension == ".mdl" || fileExtension == ".gltf" || fileExtension == ".md3" || fileExtension == ".fbx" || fileExtension == ".glb") { dragType = "_Model"; } diff --git a/Nuake/src/Rendering/Mesh/Mesh.cpp b/Nuake/src/Rendering/Mesh/Mesh.cpp index 94c05b6c..c151961e 100644 --- a/Nuake/src/Rendering/Mesh/Mesh.cpp +++ b/Nuake/src/Rendering/Mesh/Mesh.cpp @@ -20,11 +20,19 @@ namespace Nuake { Mesh::Mesh() {} Mesh::~Mesh() {} - + + void Mesh::SetData(std::vector& vertices, std::vector& indices) + { + m_Vertices = vertices; + m_Indices = indices; + m_IndicesCount = static_cast(m_Indices.size()); + m_VerticesCount = static_cast(m_Vertices.size()); + } + void Mesh::AddSurface(std::vector vertices, std::vector indices) { - this->m_Vertices = vertices; - this->m_Indices = indices; + SetData(vertices, indices); + SetupMesh(); CalculateAABB(); @@ -34,8 +42,7 @@ namespace Nuake m_Material = MaterialManager::Get()->GetMaterial("default"); } - m_IndicesCount = static_cast(m_Indices.size()); - m_VerticesCount = static_cast(m_Vertices.size()); + //m_Indices.clear(); } @@ -237,5 +244,5 @@ namespace Nuake SetupMesh(); return true; - } + } } diff --git a/Nuake/src/Rendering/Mesh/Mesh.h b/Nuake/src/Rendering/Mesh/Mesh.h index 22e2c95b..14d330ed 100644 --- a/Nuake/src/Rendering/Mesh/Mesh.h +++ b/Nuake/src/Rendering/Mesh/Mesh.h @@ -19,7 +19,12 @@ namespace Nuake public: Mesh(); ~Mesh(); - + + // This sets the data of the mesh without uploading it to the GPU. + // Useful for async asset baking + void SetData(std::vector& vertices, std::vector& indices); + + // This sets the data and uploads it to the GPU void AddSurface(std::vector vertices, std::vector indices); std::vector& GetVertices(); std::vector& GetIndices(); diff --git a/Nuake/src/Resource/Bakers/AssetBakerManager.h b/Nuake/src/Resource/Bakers/AssetBakerManager.h index ada9328d..92eb0346 100644 --- a/Nuake/src/Resource/Bakers/AssetBakerManager.h +++ b/Nuake/src/Resource/Bakers/AssetBakerManager.h @@ -1,5 +1,7 @@ #pragma once #include "IAssetBaker.h" +#include "src/Core/Core.h" +#include "src/FileSystem/File.h" #include #include @@ -8,19 +10,31 @@ namespace Nuake { class AssetBakerManager { - private: + private: std::map> Bakers; - - public: + + public: static AssetBakerManager& Get() { static AssetBakerManager instance; return instance; } - + void RegisterBaker(Ref baker) { Bakers[baker->GetExtension()] = baker; } + + Ref Bake(const Ref& file) + { + const std::string extension = file->GetExtension(); + if(Bakers.find(extension) == Bakers.end()) + { + assert(false && "Baker not found for asset type"); + return nullptr; + } + + return Bakers[extension]->Bake(file); + } }; } \ No newline at end of file diff --git a/Nuake/src/Resource/Bakers/GLTFBaker.cpp b/Nuake/src/Resource/Bakers/GLTFBaker.cpp index 53058c6a..e14bed5c 100644 --- a/Nuake/src/Resource/Bakers/GLTFBaker.cpp +++ b/Nuake/src/Resource/Bakers/GLTFBaker.cpp @@ -1,9 +1,274 @@ #include "GLTFBaker.h" +#include "src/Core/Logger.h" +#include "src/Core/String.h" + +#include "src/Rendering/Textures/Material.h" +#include "src/Rendering/Mesh/Mesh.h" +#include "src/Resource/Model.h" +#include "src/Resource/Serializer/BinarySerializer.h" + using namespace Nuake; // Converts a GLTF file to a .nkmesh binary file +// TODO(antopilo): Add support for multiple file extensions Ref GLTFBaker::Bake(const Ref& file) { - + // 1. Read file + // 2. Convert + // 3. return output file + // NOTE: This function should not interact with the rest + // of the engine and be thread-safe in order to be ran from jobs. + + // Load into assimp + Assimp::Importer importer; + importer.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", 90); + + const std::string absolutePath = file->GetAbsolutePath(); + auto importFlags = + aiProcess_Triangulate | + aiProcess_GenSmoothNormals | + aiProcess_FixInfacingNormals | + aiProcess_CalcTangentSpace; + const aiScene* scene = importer.ReadFile(absolutePath, importFlags); + + if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // Assimp failed + { + std::string assimpErrorMsg = std::string(importer.GetErrorString()); + std::string logMsg = "" + assimpErrorMsg; + Logger::Log(logMsg, "GLTF Baker", CRITICAL); + return nullptr; + } + + // Processing of the data + std::vector meshesData; + ProcessNode(scene->mRootNode, scene, meshesData); + + importer.FreeScene(); + + // Write those meshes to disk! + std::vector meshes; + + Ref model = CreateRef(); + for(auto& meshData : meshesData) + { + Ref mesh = CreateRef(); + mesh->SetData(meshData.vertices, meshData.indices); + + const BakerMaterialData materialData = meshData.material; + Ref material = CreateRef(); + + if(!materialData.albedo.empty()) + { + material->SetAlbedo(materialData.albedo); + } + + if(!materialData.normal.empty()) + { + material->SetNormal(materialData.normal); + } + + if(!materialData.ao.empty()) + { + material->SetAO(materialData.ao); + } + + if(!materialData.metallic.empty()) + { + material->SetMetalness(materialData.metallic); + } + + if(!materialData.roughness.empty()) + { + material->SetRoughness(materialData.roughness); + } + + model->AddMesh(std::move(mesh)); + } + + // Bake + BinarySerializer serializer; + + const std::string outputPath = file->GetAbsolutePath() + ".nkmesh"; + serializer.SerializeModel(outputPath, model); + + //Ref model = CreateRef(); + return file; +} + +void GLTFBaker::ProcessNode(aiNode* node, const aiScene* scene, std::vector& meshes) +{ + for(uint32_t i = 0; i < node->mNumMeshes; i++) + { + aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; + meshes.push_back(ProcessMesh(node, mesh, scene)); + } + + for(uint32_t i = 0; i < node->mNumChildren; i++) + { + ProcessNode(node->mChildren[i], scene, meshes); + } +} + +MeshData GLTFBaker::ProcessMesh(aiNode* node, aiMesh* meshNode, const aiScene* scene) +{ + std::vector vertices = ProcessVertices(meshNode); + for(auto& vertex : vertices) + { + const Matrix4& modelTransform = ConvertAssimpToGLM(node->mTransformation); + vertex.position = modelTransform * Vector4(vertex.position, 1.0f); + } + + std::vector indices = ProcessIndices(meshNode); + MeshData meshData + { + std::move(vertices), + std::move(indices), + ProcessMaterials(scene, meshNode) + }; + + return std::move(meshData); +} + +std::vector GLTFBaker::ProcessVertices(aiMesh* mesh) +{ + auto vertices = std::vector(); + + const uint32_t numVertices = mesh->mNumVertices; + vertices.reserve(numVertices); + + for(uint32_t i = 0; i < numVertices; i++) + { + Vertex vertex {}; + vertex.uv_x = 0.0f; + vertex.uv_y = 0.0f; + + // Position + auto assimpVec3 = mesh->mVertices[i]; + Vector3 position + { + assimpVec3.x, + assimpVec3.y, + assimpVec3.z + }; + vertex.position = std::move(position); + + // Normal + assimpVec3 = mesh->mNormals[i]; + Vector3 normal + { + assimpVec3.x, + assimpVec3.y, + assimpVec3.z + }; + vertex.normal = std::move(normal); + + if(mesh->mTangents) + { + vertex.tangent = + Vector4 { + mesh->mTangents[i].x, + mesh->mTangents[i].y, + mesh->mTangents[i].z, + 0.0f + }; + } + + if(mesh->mBitangents) + { + vertex.bitangent = + Vector4 { + mesh->mBitangents[i].x, + mesh->mBitangents[i].y, + mesh->mBitangents[i].z, + 0.0f + }; + } + + if(mesh->mTextureCoords[0]) + { + vertex.uv_x = mesh->mTextureCoords[0][i].x; + vertex.uv_y = mesh->mTextureCoords[0][i].y; + } + + vertices.push_back(vertex); + } + + return std::move(vertices); +} + +std::vector GLTFBaker::ProcessIndices(aiMesh* mesh) +{ + auto indices = std::vector(); + for (uint32_t i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + for (uint32_t j = 0; j < face.mNumIndices; j++) + { + indices.push_back(face.mIndices[j]); + } + } + + return std::move(indices); +} + +BakerMaterialData GLTFBaker::ProcessMaterials(const aiScene* scene, aiMesh* mesh) +{ + BakerMaterialData materialData { }; + + if(mesh->mMaterialIndex < 0) + { + return materialData; + } + + aiMaterial* materialNode = scene->mMaterials[mesh->mMaterialIndex]; + + aiString aiMaterialName; + materialNode->Get(AI_MATKEY_NAME, aiMaterialName); + + const std::string materialName = std::string(aiMaterialName.C_Str()); + if(Materials.find(materialName) != Materials.end()) + { + return materialData; + } + + aiString str; + if(materialNode->GetTextureCount(aiTextureType_DIFFUSE) > 0) + { + materialNode->GetTexture(aiTextureType_DIFFUSE, 0, &str); + materialData.albedo = std::string(str.C_Str()); + } + + if(materialNode->GetTextureCount(aiTextureType_NORMALS) > 0) + { + materialNode->GetTexture(aiTextureType_NORMALS, 0, &str); + materialData.normal = std::string(str.C_Str()); + } + + if(materialNode->GetTextureCount(aiTextureType_AMBIENT_OCCLUSION) > 0) + { + materialNode->GetTexture(aiTextureType_AMBIENT_OCCLUSION, 0, &str); + materialData.ao = std::string(str.C_Str()); + } + + if(materialNode->GetTextureCount(aiTextureType_DIFFUSE_ROUGHNESS) > 0) + { + materialNode->GetTexture(aiTextureType_DIFFUSE_ROUGHNESS, 0, &str); + materialData.roughness = std::string(str.C_Str()); + } + + return std::move(materialData); +} + +std::string GLTFBaker::ProcessTextures(const aiScene* scene, const std::string& path) +{ + if(String::BeginsWith(path, "*")) + { + // TODO(antopilo): Figure out how to handle embedded textures. + + Logger::Log("Embedded textures not supported", "GLTFBaker", WARNING); + return ""; + } + + return std::move(path); } diff --git a/Nuake/src/Resource/Bakers/GLTFBaker.h b/Nuake/src/Resource/Bakers/GLTFBaker.h index 2ad0b3c0..ad34ee46 100644 --- a/Nuake/src/Resource/Bakers/GLTFBaker.h +++ b/Nuake/src/Resource/Bakers/GLTFBaker.h @@ -1,12 +1,60 @@ #include "IAssetBaker.h" +#include "src/Rendering/Vertex.h" + +// Assimp +#include "assimp/Importer.hpp" +#include +#include + +#include + namespace Nuake { + struct BakerMaterialData + { + std::string albedo; + std::string normal; + std::string ao; + std::string roughness; + std::string metallic; + }; + + struct MeshData + { + std::vector vertices; + std::vector indices; + BakerMaterialData material; + }; + class GLTFBaker : public IAssetBaker { public: GLTFBaker() : IAssetBaker(".glb") {} - + Ref Bake(const Ref& file) override; + + private: + std::map Materials; + + void ProcessNode(aiNode* node, const aiScene* scene, std::vector& meshes); + MeshData ProcessMesh(aiNode* node, aiMesh* meshNode, const aiScene* scene); + std::vector ProcessVertices(aiMesh* mesh); + std::vector ProcessIndices(aiMesh* mesh); + BakerMaterialData ProcessMaterials(const aiScene* scene, aiMesh* mesh); + std::string ProcessTextures(const aiScene* scene, const std::string& path); + + private: + static inline Matrix4 ConvertAssimpToGLM(const aiMatrix4x4& from) + { + Matrix4 to; + + to[0][0] = from.a1; to[0][1] = from.b1; to[0][2] = from.c1; to[0][3] = from.d1; + to[1][0] = from.a2; to[1][1] = from.b2; to[1][2] = from.c2; to[1][3] = from.d2; + to[2][0] = from.a3; to[2][1] = from.b3; to[2][2] = from.c3; to[2][3] = from.d3; + to[3][0] = from.a4; to[3][1] = from.b4; to[3][2] = from.c4; to[3][3] = from.d4; + + return to; + } }; } \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 00000000..12c4f402 --- /dev/null +++ b/build.bat @@ -0,0 +1,30 @@ +@echo off + +REM Set default values for configuration and platform +set CONFIG=Debug +set PLATFORM= + +REM Get solution file path +set SOLUTION=Nuake.sln + +REM Check if Configuration is provided +if not "%~2"=="" ( + set CONFIG=%~2 +) + +REM Check if Platform is provided +if not "%~3"=="" ( + set PLATFORM=%~3 +) + +REM Build the solution +echo Building solution "%SOLUTION%" with Configuration=%CONFIG% and Platform=%PLATFORM%... +"C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\MSBuild.exe" "Nuake.sln" -verbosity:minimal +PAUSE +REM Check if build succeeded +if %ERRORLEVEL%==0 ( + echo Build succeeded. +) else ( + echo Build failed. + exit /b 1 +) \ No newline at end of file diff --git a/project.4coder b/project.4coder new file mode 100644 index 00000000..196e3483 --- /dev/null +++ b/project.4coder @@ -0,0 +1,58 @@ +version(1); + +project_name = "Nuake"; + +patterns = { + "*.c", + "*.cpp", + "*.h", + "*.hpp", + "*.lua", + "*.cs", + "*.hlsl", + "*.frag", + "*.vert", + "*.4coder" +}; + +blacklist_patterns = { + ".*", +}; + +load_paths = { + { + { {"."}, .recursive = false, .relative = true }, .os = "win" + }, + { + { {"Nuake"}, .recursive = false, .relative = true }, .os = "win" + }, + { + { {"Nuake/src"}, .recursive = true, .relative = true }, .os = "win" + } +}; + +command_list = { + { + .name = "build", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = true, + .cmd = { + { "build.bat Debug Active", .os = "win" }, + }, + }, + { + .name = "run", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = true, + .cmd = { + { "run.bat", .os = "win" }, + }, + }, +}; + +fkey_command[1] = "build"; +fkey_command[5] = "run"; diff --git a/run.bat b/run.bat new file mode 100644 index 00000000..89f3a038 --- /dev/null +++ b/run.bat @@ -0,0 +1,2 @@ +cd Editor +"../bin\Debug-windows-x86_64\Editor\Nuake Engine.exe" \ No newline at end of file