#include #include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Components; using namespace XCEngine::Math; using namespace XCEngine::Resources; namespace { VolumeField* CreateTestVolumeField(const char* name, const char* path) { auto* volumeField = new VolumeField(); IResource::ConstructParams params = {}; params.name = name; params.path = path; params.guid = ResourceGUID::Generate(path); volumeField->Initialize(params); const unsigned char payload[8] = { 1, 3, 5, 7, 9, 11, 13, 15 }; EXPECT_TRUE(volumeField->Create( VolumeStorageKind::NanoVDB, payload, sizeof(payload), Bounds(Vector3::Zero(), Vector3::One()), Vector3(0.5f, 0.5f, 0.5f))); return volumeField; } Material* CreateTestMaterial(const char* name, const char* path) { auto* material = new Material(); IResource::ConstructParams params = {}; params.name = name; params.path = path; params.guid = ResourceGUID::Generate(path); material->Initialize(params); return material; } class FakeAsyncVolumeFieldLoader : public IResourceLoader { public: ResourceType GetResourceType() const override { return ResourceType::VolumeField; } XCEngine::Containers::Array GetSupportedExtensions() const override { XCEngine::Containers::Array extensions; extensions.PushBack("nvdb"); return extensions; } bool CanLoad(const XCEngine::Containers::String& path) const override { (void)path; return true; } LoadResult Load( const XCEngine::Containers::String& path, const ImportSettings* settings = nullptr) override { (void)settings; return LoadResult(CreateTestVolumeField("AsyncVolume", path.CStr())); } ImportSettings* GetDefaultSettings() const override { return nullptr; } }; bool PumpAsyncLoadsUntilIdle( ResourceManager& manager, std::chrono::milliseconds timeout = std::chrono::milliseconds(2000)) { const auto deadline = std::chrono::steady_clock::now() + timeout; while (manager.IsAsyncLoading() && std::chrono::steady_clock::now() < deadline) { manager.UpdateAsyncLoads(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } manager.UpdateAsyncLoads(); return !manager.IsAsyncLoading(); } TEST(VolumeRendererComponent_Test, SetResourcesCachesHandlesPathsAndFlags) { GameObject gameObject("VolumeHolder"); auto* component = gameObject.AddComponent(); VolumeField* volumeField = CreateTestVolumeField("Cloud", "Volumes/cloud.nvdb"); Material* material = CreateTestMaterial("VolumeMaterial", "Materials/volume.mat"); component->SetVolumeField(volumeField); component->SetMaterial(material); component->SetCastShadows(false); component->SetReceiveShadows(false); EXPECT_EQ(component->GetVolumeField(), volumeField); EXPECT_EQ(component->GetVolumeFieldPath(), "Volumes/cloud.nvdb"); EXPECT_EQ(component->GetMaterial(), material); EXPECT_EQ(component->GetMaterialPath(), "Materials/volume.mat"); EXPECT_FALSE(component->GetCastShadows()); EXPECT_FALSE(component->GetReceiveShadows()); component->ClearVolumeField(); component->ClearMaterial(); delete volumeField; delete material; } TEST(VolumeRendererComponent_Test, SerializeAndDeserializePreservesVirtualPathsAndFlags) { VolumeRendererComponent source; source.SetVolumeFieldPath("test://volumes/cloud.nvdb"); source.SetMaterialPath("test://materials/volume.material"); source.SetCastShadows(false); source.SetReceiveShadows(true); std::stringstream stream; source.Serialize(stream); const std::string serialized = stream.str(); EXPECT_NE(serialized.find("volumePath=test://volumes/cloud.nvdb;"), std::string::npos); EXPECT_NE(serialized.find("materialPath=test://materials/volume.material;"), std::string::npos); EXPECT_NE(serialized.find("castShadows=0;"), std::string::npos); EXPECT_NE(serialized.find("receiveShadows=1;"), std::string::npos); VolumeRendererComponent target; std::stringstream deserializeStream(serialized); target.Deserialize(deserializeStream); EXPECT_EQ(target.GetVolumeFieldPath(), "test://volumes/cloud.nvdb"); EXPECT_EQ(target.GetMaterialPath(), "test://materials/volume.material"); EXPECT_FALSE(target.GetCastShadows()); EXPECT_TRUE(target.GetReceiveShadows()); EXPECT_FALSE(target.GetVolumeFieldAssetRef().IsValid()); EXPECT_FALSE(target.GetMaterialAssetRef().IsValid()); } TEST(VolumeRendererComponent_Test, DeferredSceneDeserializeLoadsVolumeFieldAsyncByPath) { ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); IResourceLoader* originalLoader = manager.GetLoader(ResourceType::VolumeField); FakeAsyncVolumeFieldLoader fakeLoader; manager.RegisterLoader(&fakeLoader); VolumeRendererComponent target; const auto pendingBeforeDeserialize = manager.GetAsyncPendingCount(); { ResourceManager::ScopedDeferredSceneLoad deferredLoadScope; EXPECT_TRUE(manager.IsDeferredSceneLoadEnabled()); std::stringstream stream( "volumePath=test://volumes/async.nvdb;volumeRef=;materialRef=;castShadows=1;receiveShadows=1;"); target.Deserialize(stream); } EXPECT_EQ(target.GetVolumeFieldPath(), "test://volumes/async.nvdb"); EXPECT_EQ(target.GetVolumeField(), nullptr); EXPECT_GT(manager.GetAsyncPendingCount(), pendingBeforeDeserialize); ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager)); ASSERT_NE(target.GetVolumeField(), nullptr); EXPECT_EQ(target.GetVolumeFieldPath(), "test://volumes/async.nvdb"); EXPECT_EQ(target.GetVolumeField()->GetStorageKind(), VolumeStorageKind::NanoVDB); manager.RegisterLoader(originalLoader); manager.Shutdown(); } } // namespace