diff --git a/include/Nazara/Graphics/Graphics.hpp b/include/Nazara/Graphics/Graphics.hpp index 2e133351d..93f31776a 100644 --- a/include/Nazara/Graphics/Graphics.hpp +++ b/include/Nazara/Graphics/Graphics.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,8 @@ namespace Nz inline const MaterialInstanceLoader& GetMaterialInstanceLoader() const; inline MaterialLoader& GetMaterialLoader(); inline const MaterialLoader& GetMaterialLoader() const; + inline ModelLoader& GetModelLoader(); + inline const ModelLoader& GetModelLoader() const; inline PipelinePassListLoader& GetPipelinePassListLoader(); inline const PipelinePassListLoader& GetPipelinePassListLoader() const; inline PixelFormat GetPreferredDepthFormat() const; @@ -117,6 +120,7 @@ namespace Nz MaterialInstanceLoader m_materialInstanceLoader; MaterialLoader m_materialLoader; MaterialPassRegistry m_materialPassRegistry; + ModelLoader m_modelLoader; PipelinePassListLoader m_pipelinePassListLoader; PixelFormat m_preferredDepthFormat; PixelFormat m_preferredDepthStencilFormat; diff --git a/include/Nazara/Graphics/Graphics.inl b/include/Nazara/Graphics/Graphics.inl index 44d561330..6e4922fac 100644 --- a/include/Nazara/Graphics/Graphics.inl +++ b/include/Nazara/Graphics/Graphics.inl @@ -71,6 +71,16 @@ namespace Nz return m_materialLoader; } + inline ModelLoader& Graphics::GetModelLoader() + { + return m_modelLoader; + } + + inline const ModelLoader& Graphics::GetModelLoader() const + { + return m_modelLoader; + } + inline PipelinePassListLoader& Graphics::GetPipelinePassListLoader() { return m_pipelinePassListLoader; diff --git a/include/Nazara/Graphics/Model.hpp b/include/Nazara/Graphics/Model.hpp index 5311f5456..c778a8e06 100644 --- a/include/Nazara/Graphics/Model.hpp +++ b/include/Nazara/Graphics/Model.hpp @@ -8,6 +8,12 @@ #define NAZARA_GRAPHICS_MODEL_HPP #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -18,11 +24,26 @@ namespace Nz { - class Material; + struct NAZARA_GRAPHICS_API ModelParams : ResourceParameters + { + bool loadMaterials = true; + MeshParams mesh; - class NAZARA_GRAPHICS_API Model : public InstancedRenderable + bool IsValid() const; + }; + + class Model; + + using ModelLibrary = ObjectLibrary; + using ModelLoader = ResourceLoader; + using ModelManager = ResourceManager; + using ModelSaver = ResourceSaver; + + class NAZARA_GRAPHICS_API Model : public InstancedRenderable, public Resource { public: + using Params = ModelParams; + Model(std::shared_ptr graphicalMesh); Model(const Model&) = delete; Model(Model&&) noexcept = default; @@ -43,6 +64,10 @@ namespace Nz Model& operator=(const Model&) = delete; Model& operator=(Model&&) noexcept = default; + static std::shared_ptr LoadFromFile(const std::filesystem::path& filePath, const ModelParams& params = ModelParams()); + static std::shared_ptr LoadFromMemory(const void* data, std::size_t size, const ModelParams& params = ModelParams()); + static std::shared_ptr LoadFromStream(Stream& stream, const ModelParams& params = ModelParams()); + private: struct SubMeshData { diff --git a/src/Nazara/Graphics/Formats/ModelMeshLoader.cpp b/src/Nazara/Graphics/Formats/ModelMeshLoader.cpp new file mode 100644 index 000000000..9980042f9 --- /dev/null +++ b/src/Nazara/Graphics/Formats/ModelMeshLoader.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz::Loaders +{ + ModelLoader::Entry GetModelLoader_Mesh() + { + ModelLoader::Entry loaderEntry; + loaderEntry.extensionSupport = [](std::string_view extension) + { + return Utility::Instance()->GetMeshLoader().IsExtensionSupported(extension); + }; + + loaderEntry.streamLoader = [](Stream& stream, const ModelParams& parameters) -> Result, ResourceLoadingError> + { + std::shared_ptr mesh = Mesh::LoadFromStream(stream, parameters.mesh); + if (!mesh) + return Err(ResourceLoadingError::Unrecognized); + + std::shared_ptr gfxMesh = GraphicalMesh::BuildFromMesh(*mesh); + if (!gfxMesh) + return Err(ResourceLoadingError::Internal); + + std::shared_ptr model = std::make_shared(std::move(gfxMesh)); + if (parameters.loadMaterials) + { + for (std::size_t matIndex = 0; matIndex < model->GetMaterialCount(); ++matIndex) + { + if (std::shared_ptr matInstance = MaterialInstance::Build(mesh->GetMaterialData(matIndex))) + model->SetMaterial(matIndex, std::move(matInstance)); + } + } + + return model; + }; + + loaderEntry.parameterFilter = [](const ModelParams& parameters) + { + if (auto result = parameters.custom.GetBooleanParameter("SkipNativeMeshLoader"); result.GetValueOr(false)) + return false; + + return true; + }; + + return loaderEntry; + } +} diff --git a/src/Nazara/Graphics/Formats/ModelMeshLoader.hpp b/src/Nazara/Graphics/Formats/ModelMeshLoader.hpp new file mode 100644 index 000000000..76433c60f --- /dev/null +++ b/src/Nazara/Graphics/Formats/ModelMeshLoader.hpp @@ -0,0 +1,18 @@ +// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_GRAPHICS_FORMATS_MODELMESHLOADER_HPP +#define NAZARA_GRAPHICS_FORMATS_MODELMESHLOADER_HPP + +#include +#include + +namespace Nz::Loaders +{ + ModelLoader::Entry GetModelLoader_Mesh(); +} + +#endif // NAZARA_GRAPHICS_FORMATS_MODELMESHLOADER_HPP diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index 38e0e50e7..5618b92ce 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,7 @@ namespace Nz Font::SetDefaultAtlas(std::make_shared(*m_renderDevice)); m_materialInstanceLoader.RegisterLoader(Loaders::GetMaterialInstanceLoader_Texture()); // texture to material loader + m_modelLoader.RegisterLoader(Loaders::GetModelLoader_Mesh()); m_pipelinePassListLoader.RegisterLoader(Loaders::GetPipelinePassListLoader()); // texture to material loader } diff --git a/src/Nazara/Graphics/Model.cpp b/src/Nazara/Graphics/Model.cpp index eaab98731..c9bb2451d 100644 --- a/src/Nazara/Graphics/Model.cpp +++ b/src/Nazara/Graphics/Model.cpp @@ -13,6 +13,11 @@ namespace Nz { + bool ModelParams::IsValid() const + { + return mesh.IsValid(); + } + Model::Model(std::shared_ptr graphicalMesh) : m_graphicalMesh(std::move(graphicalMesh)) { @@ -94,4 +99,28 @@ namespace Nz { return m_graphicalMesh->GetVertexBuffer(subMeshIndex); } + + std::shared_ptr Model::LoadFromFile(const std::filesystem::path& filePath, const ModelParams& params) + { + Graphics* graphics = Graphics::Instance(); + NazaraAssert(graphics, "Graphics module has not been initialized"); + + return graphics->GetModelLoader().LoadFromFile(filePath, params); + } + + std::shared_ptr Model::LoadFromMemory(const void* data, std::size_t size, const ModelParams& params) + { + Graphics* graphics = Graphics::Instance(); + NazaraAssert(graphics, "Graphics module has not been initialized"); + + return graphics->GetModelLoader().LoadFromMemory(data, size, params); + } + + std::shared_ptr Model::LoadFromStream(Stream& stream, const ModelParams& params) + { + Graphics* graphics = Graphics::Instance(); + NazaraAssert(graphics, "Graphics module has not been initialized"); + + return graphics->GetModelLoader().LoadFromStream(stream, params); + } } diff --git a/tests/GraphicsTest/main.cpp b/tests/GraphicsTest/main.cpp index 95f3f1c51..32ec46eea 100644 --- a/tests/GraphicsTest/main.cpp +++ b/tests/GraphicsTest/main.cpp @@ -28,52 +28,29 @@ int main() Nz::Application app(rendererConfig); auto& windowingApp = app.AddComponent(); - Nz::MeshParams meshParams; - meshParams.center = true; - meshParams.vertexRotation = Nz::EulerAnglesf(0.f, -90.f, 0.f); - meshParams.vertexScale = Nz::Vector3f(0.002f); - meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV); - std::shared_ptr device = Nz::Graphics::Instance()->GetRenderDevice(); std::string windowTitle = "Graphics Test"; Nz::Window& window = windowingApp.CreateWindow(Nz::VideoMode(1280, 720), windowTitle); Nz::WindowSwapchain windowSwapchain(device, window); - std::shared_ptr spaceshipMesh = Nz::Mesh::LoadFromFile(resourceDir / "Spaceship/spaceship.obj", meshParams); - if (!spaceshipMesh) + Nz::ModelParams modelParams; + modelParams.mesh.center = true; + modelParams.mesh.vertexRotation = Nz::EulerAnglesf(0.f, -90.f, 0.f); + modelParams.mesh.vertexScale = Nz::Vector3f(0.002f); + modelParams.mesh.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV); + + std::shared_ptr spaceshipModel = Nz::Model::LoadFromFile(resourceDir / "Spaceship/spaceship.obj", modelParams); + if (!spaceshipModel) { NazaraError("failed to load model"); return __LINE__; } - std::shared_ptr gfxMesh = Nz::GraphicalMesh::BuildFromMesh(*spaceshipMesh); - - // Texture - std::shared_ptr diffuseImage = Nz::Image::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png"); - if (!diffuseImage || !diffuseImage->Convert(Nz::PixelFormat::RGBA8_SRGB)) - { - NazaraError("failed to load image"); - return __LINE__; - } - - Nz::TextureParams texParams; - texParams.renderDevice = device; - texParams.loadFormat = Nz::PixelFormat::RGBA8_SRGB; - - std::shared_ptr diffuseTexture = Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams); - - std::shared_ptr materialInstance = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Basic); - materialInstance->SetTextureProperty(0, diffuseTexture); - materialInstance->SetValueProperty(0, Nz::Color::White()); - + std::shared_ptr materialInstance = spaceshipModel->GetMaterial(0); std::shared_ptr materialInstance2 = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Basic); materialInstance2->SetValueProperty(0, Nz::Color::Green()); - Nz::Model model(std::move(gfxMesh)); - for (std::size_t i = 0; i < model.GetSubMeshCount(); ++i) - model.SetMaterial(i, materialInstance); - Nz::Vector2ui windowSize = window.GetSize(); Nz::Camera camera(std::make_shared(windowSwapchain)); @@ -97,8 +74,8 @@ int main() [[maybe_unused]] std::size_t cameraIndex = framePipeline.RegisterViewer(&camera, 0); std::size_t worldInstanceIndex1 = framePipeline.RegisterWorldInstance(modelInstance); std::size_t worldInstanceIndex2 = framePipeline.RegisterWorldInstance(modelInstance2); - framePipeline.RegisterRenderable(worldInstanceIndex1, Nz::FramePipeline::NoSkeletonInstance, &model, 0xFFFFFFFF, scissorBox); - framePipeline.RegisterRenderable(worldInstanceIndex2, Nz::FramePipeline::NoSkeletonInstance, &model, 0xFFFFFFFF, scissorBox); + framePipeline.RegisterRenderable(worldInstanceIndex1, Nz::FramePipeline::NoSkeletonInstance, spaceshipModel.get(), 0xFFFFFFFF, scissorBox); + framePipeline.RegisterRenderable(worldInstanceIndex2, Nz::FramePipeline::NoSkeletonInstance, spaceshipModel.get(), 0xFFFFFFFF, scissorBox); std::unique_ptr light = std::make_unique(); light->UpdateInnerAngle(Nz::DegreeAnglef(15.f)); @@ -128,13 +105,13 @@ int main() case Nz::WindowEventType::KeyPressed: if (event.key.virtualKey == Nz::Keyboard::VKey::A) { - for (std::size_t i = 0; i < model.GetSubMeshCount(); ++i) - model.SetMaterial(i, materialInstance); + for (std::size_t i = 0; i < spaceshipModel->GetSubMeshCount(); ++i) + spaceshipModel->SetMaterial(i, materialInstance); } else if (event.key.virtualKey == Nz::Keyboard::VKey::B) { - for (std::size_t i = 0; i < model.GetSubMeshCount(); ++i) - model.SetMaterial(i, materialInstance2); + for (std::size_t i = 0; i < spaceshipModel->GetSubMeshCount(); ++i) + spaceshipModel->SetMaterial(i, materialInstance2); } else if (event.key.virtualKey == Nz::Keyboard::VKey::Space) { @@ -216,7 +193,7 @@ int main() for (const Nz::WorldInstancePtr& worldInstance : { modelInstance, modelInstance2 }) { - Nz::Boxf aabb = model.GetAABB(); + Nz::Boxf aabb = spaceshipModel->GetAABB(); aabb.Transform(worldInstance->GetWorldMatrix()); framePipeline.GetDebugDrawer().DrawBox(aabb, Nz::Color::Green());