#include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #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("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(); auto* meshRenderer = meshObject->GetComponent(); 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