Add Nahida model import and preview pipeline
This commit is contained in:
@@ -6,6 +6,7 @@ set(MODEL_TEST_SOURCES
|
||||
test_model.cpp
|
||||
test_model_loader.cpp
|
||||
test_model_import_pipeline.cpp
|
||||
test_model_scene_instantiation.cpp
|
||||
)
|
||||
|
||||
add_executable(model_tests ${MODEL_TEST_SOURCES})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
@@ -85,11 +86,24 @@ TEST(ModelImportPipeline, AssetDatabaseImportsObjAsModelArtifact) {
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Model, resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
EXPECT_EQ(fs::path(resolvedAsset.artifactMainPath.CStr()).filename().string(), "main.xcmodel");
|
||||
EXPECT_EQ(fs::path(resolvedAsset.artifactMainPath.CStr()).extension().string(), ".xcmodel");
|
||||
EXPECT_TRUE(fs::exists(resolvedAsset.artifactMainPath.CStr()));
|
||||
EXPECT_TRUE(fs::exists(fs::path(resolvedAsset.artifactDirectory.CStr()) / "mesh_0.xcmesh"));
|
||||
EXPECT_TRUE(fs::exists(fs::path(resolvedAsset.artifactDirectory.CStr()) / "material_0.xcmat"));
|
||||
EXPECT_TRUE(fs::exists(fs::path(resolvedAsset.artifactDirectory.CStr()) / "texture_0.xctex"));
|
||||
XCEngine::Containers::Array<XCEngine::Core::uint8> payload;
|
||||
EXPECT_TRUE(ReadArtifactContainerEntryPayload(
|
||||
resolvedAsset.artifactMainPath,
|
||||
"mesh_0.xcmesh",
|
||||
ResourceType::Mesh,
|
||||
payload));
|
||||
EXPECT_TRUE(ReadArtifactContainerEntryPayload(
|
||||
resolvedAsset.artifactMainPath,
|
||||
"material_0.xcmat",
|
||||
ResourceType::Material,
|
||||
payload));
|
||||
EXPECT_TRUE(ReadArtifactContainerEntryPayload(
|
||||
resolvedAsset.artifactMainPath,
|
||||
"texture_0.xctex",
|
||||
ResourceType::Texture,
|
||||
payload));
|
||||
|
||||
ModelLoader modelLoader;
|
||||
const LoadResult modelResult = modelLoader.Load(resolvedAsset.artifactMainPath);
|
||||
@@ -109,7 +123,8 @@ TEST(ModelImportPipeline, AssetDatabaseImportsObjAsModelArtifact) {
|
||||
|
||||
MeshLoader meshLoader;
|
||||
const LoadResult meshResult =
|
||||
meshLoader.Load((fs::path(resolvedAsset.artifactDirectory.CStr()) / "mesh_0.xcmesh").string().c_str());
|
||||
meshLoader.Load(
|
||||
BuildArtifactContainerEntryPath(resolvedAsset.artifactMainPath, "mesh_0.xcmesh"));
|
||||
ASSERT_TRUE(meshResult);
|
||||
ASSERT_NE(meshResult.resource, nullptr);
|
||||
|
||||
@@ -182,4 +197,64 @@ TEST(ModelImportPipeline, ResourceManagerLoadsModelFromProjectAsset) {
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(ModelImportPipeline, ProjectNahidaSampleArtifactPreservesFallbackUv1Semantic) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path repositoryRoot = GetRepositoryRoot();
|
||||
const fs::path projectRoot = repositoryRoot / "project";
|
||||
const fs::path nahidaModelPath =
|
||||
projectRoot / "Assets" / "Characters" / "Nahida" / "Model" / "Avatar_Loli_Catalyst_Nahida.fbx";
|
||||
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||
|
||||
if (!fs::exists(nahidaModelPath)) {
|
||||
GTEST_SKIP() << "Nahida sample model is not available in the local project fixture.";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(fs::exists(assimpDllPath));
|
||||
|
||||
#ifdef _WIN32
|
||||
AssimpDllGuard dllGuard;
|
||||
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||
ASSERT_NE(dllGuard.module, nullptr);
|
||||
#endif
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact(
|
||||
"Assets/Characters/Nahida/Model/Avatar_Loli_Catalyst_Nahida.fbx",
|
||||
ResourceType::Model,
|
||||
resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
EXPECT_EQ(fs::path(resolvedAsset.artifactMainPath.CStr()).extension().string(), ".xcmodel");
|
||||
|
||||
MeshLoader meshLoader;
|
||||
const LoadResult meshResult =
|
||||
meshLoader.Load(BuildArtifactContainerEntryPath(resolvedAsset.artifactMainPath, "mesh_0.xcmesh"));
|
||||
ASSERT_TRUE(meshResult);
|
||||
ASSERT_NE(meshResult.resource, nullptr);
|
||||
|
||||
auto* mesh = static_cast<Mesh*>(meshResult.resource);
|
||||
ASSERT_NE(mesh, nullptr);
|
||||
EXPECT_TRUE(HasVertexAttribute(mesh->GetVertexAttributes(), VertexAttribute::UV0));
|
||||
EXPECT_TRUE(HasVertexAttribute(mesh->GetVertexAttributes(), VertexAttribute::UV1));
|
||||
EXPECT_TRUE(HasVertexAttribute(mesh->GetVertexAttributes(), VertexAttribute::Color));
|
||||
|
||||
const auto* vertices = static_cast<const StaticMeshVertex*>(mesh->GetVertexData());
|
||||
ASSERT_NE(vertices, nullptr);
|
||||
ASSERT_GT(mesh->GetVertexCount(), 0u);
|
||||
EXPECT_FLOAT_EQ(vertices[0].uv0.x, vertices[0].uv1.x);
|
||||
EXPECT_FLOAT_EQ(vertices[0].uv0.y, vertices[0].uv1.y);
|
||||
|
||||
delete mesh;
|
||||
database.Shutdown();
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
173
tests/Resources/Model/test_model_scene_instantiation.cpp
Normal file
173
tests/Resources/Model/test_model_scene_instantiation.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/AssetRef.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Model/Model.h>
|
||||
#include <XCEngine/Scene/ModelSceneInstantiation.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace XCEngine;
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Resources;
|
||||
|
||||
namespace {
|
||||
|
||||
std::filesystem::path GetRepositoryRoot() {
|
||||
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path();
|
||||
}
|
||||
|
||||
std::string GetMeshFixturePath(const char* fileName) {
|
||||
return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Mesh" / fileName).string();
|
||||
}
|
||||
|
||||
void CopyTexturedTriangleFixture(const std::filesystem::path& assetsDir) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::copy_file(
|
||||
GetMeshFixturePath("textured_triangle.obj"),
|
||||
assetsDir / "textured_triangle.obj",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(
|
||||
GetMeshFixturePath("textured_triangle.mtl"),
|
||||
assetsDir / "textured_triangle.mtl",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(
|
||||
GetMeshFixturePath("checker.bmp"),
|
||||
assetsDir / "checker.bmp",
|
||||
fs::copy_options::overwrite_existing);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
struct AssimpDllGuard {
|
||||
HMODULE module = nullptr;
|
||||
|
||||
~AssimpDllGuard() {
|
||||
if (module != nullptr) {
|
||||
FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
TEST(ModelSceneInstantiation, BuildsHierarchyFromManualModelGraph) {
|
||||
Scene scene("Instantiation Scene");
|
||||
Model model;
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "ManualModel";
|
||||
params.path = "Assets/manual_model.fbx";
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
model.Initialize(params);
|
||||
|
||||
ModelNode rootNode;
|
||||
rootNode.name = "Root";
|
||||
rootNode.parentIndex = -1;
|
||||
rootNode.localPosition = Math::Vector3(1.0f, 2.0f, 3.0f);
|
||||
|
||||
ModelNode childNode;
|
||||
childNode.name = "Child";
|
||||
childNode.parentIndex = 0;
|
||||
childNode.localPosition = Math::Vector3(4.0f, 5.0f, 6.0f);
|
||||
childNode.localScale = Math::Vector3(2.0f, 2.0f, 2.0f);
|
||||
|
||||
model.AddNode(rootNode);
|
||||
model.AddNode(childNode);
|
||||
model.SetRootNodeIndex(0u);
|
||||
|
||||
ModelSceneInstantiationResult result;
|
||||
Containers::String errorMessage;
|
||||
ASSERT_TRUE(InstantiateModelHierarchy(scene, model, AssetRef(), nullptr, &result, &errorMessage))
|
||||
<< errorMessage.CStr();
|
||||
|
||||
ASSERT_NE(result.rootObject, nullptr);
|
||||
EXPECT_EQ(result.rootObject->GetName(), "Root");
|
||||
ASSERT_EQ(result.nodeObjects.size(), 2u);
|
||||
ASSERT_NE(result.nodeObjects[1], nullptr);
|
||||
EXPECT_EQ(result.nodeObjects[1]->GetParent(), result.rootObject);
|
||||
EXPECT_EQ(result.rootObject->GetTransform()->GetLocalPosition(), Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
EXPECT_EQ(result.nodeObjects[1]->GetTransform()->GetLocalPosition(), Math::Vector3(4.0f, 5.0f, 6.0f));
|
||||
EXPECT_EQ(result.nodeObjects[1]->GetTransform()->GetLocalScale(), Math::Vector3(2.0f, 2.0f, 2.0f));
|
||||
}
|
||||
|
||||
TEST(ModelSceneInstantiation, RestoresMeshAndMaterialSubAssetRefsFromImportedModel) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_model_scene_instantiation";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
CopyTexturedTriangleFixture(assetsDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
AssimpDllGuard dllGuard;
|
||||
const fs::path assimpDllPath = GetRepositoryRoot() / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||
ASSERT_TRUE(fs::exists(assimpDllPath));
|
||||
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||
ASSERT_NE(dllGuard.module, nullptr);
|
||||
#endif
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
const auto modelHandle = manager.Load<Model>("Assets/textured_triangle.obj");
|
||||
ASSERT_TRUE(modelHandle.IsValid());
|
||||
|
||||
AssetRef modelAssetRef;
|
||||
ASSERT_TRUE(manager.TryGetAssetRef("Assets/textured_triangle.obj", ResourceType::Model, modelAssetRef));
|
||||
|
||||
Scene scene("Instantiation Scene");
|
||||
ModelSceneInstantiationResult result;
|
||||
Containers::String errorMessage;
|
||||
ASSERT_TRUE(InstantiateModelHierarchy(scene, *modelHandle, modelAssetRef, nullptr, &result, &errorMessage))
|
||||
<< errorMessage.CStr();
|
||||
|
||||
ASSERT_NE(result.rootObject, nullptr);
|
||||
ASSERT_FALSE(result.meshObjects.empty());
|
||||
auto* meshObject = result.meshObjects.front();
|
||||
ASSERT_NE(meshObject, nullptr);
|
||||
|
||||
auto* meshFilter = meshObject->GetComponent<MeshFilterComponent>();
|
||||
auto* meshRenderer = meshObject->GetComponent<MeshRendererComponent>();
|
||||
ASSERT_NE(meshFilter, nullptr);
|
||||
ASSERT_NE(meshRenderer, nullptr);
|
||||
ASSERT_NE(meshFilter->GetMesh(), nullptr);
|
||||
ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u);
|
||||
ASSERT_NE(meshRenderer->GetMaterial(0), nullptr);
|
||||
|
||||
const AssetRef& meshRef = meshFilter->GetMeshAssetRef();
|
||||
EXPECT_TRUE(meshRef.IsValid());
|
||||
EXPECT_EQ(meshRef.assetGuid, modelAssetRef.assetGuid);
|
||||
EXPECT_EQ(meshRef.localID, modelHandle->GetMeshBindings()[0].meshLocalID);
|
||||
EXPECT_EQ(meshRef.resourceType, ResourceType::Mesh);
|
||||
|
||||
ASSERT_EQ(meshRenderer->GetMaterialAssetRefs().size(), 1u);
|
||||
const AssetRef& materialRef = meshRenderer->GetMaterialAssetRefs()[0];
|
||||
EXPECT_TRUE(materialRef.IsValid());
|
||||
EXPECT_EQ(materialRef.assetGuid, modelAssetRef.assetGuid);
|
||||
EXPECT_EQ(materialRef.localID, modelHandle->GetMaterialBindings()[0].materialLocalID);
|
||||
EXPECT_EQ(materialRef.resourceType, ResourceType::Material);
|
||||
|
||||
const std::string serializedScene = scene.SerializeToString();
|
||||
EXPECT_NE(serializedScene.find("meshRef="), std::string::npos);
|
||||
EXPECT_EQ(serializedScene.find("meshRef=;"), std::string::npos);
|
||||
EXPECT_NE(serializedScene.find("materialRefs="), std::string::npos);
|
||||
EXPECT_EQ(serializedScene.find("materialRefs=;"), std::string::npos);
|
||||
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user