265 lines
10 KiB
C++
265 lines
10 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
|
#include <XCEngine/Core/Asset/AssetRef.h>
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Core/Math/Bounds.h>
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
#include <XCEngine/Resources/Volume/VolumeField.h>
|
|
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
|
|
|
|
#if defined(XCENGINE_HAS_NANOVDB)
|
|
#include <nanovdb/GridHandle.h>
|
|
#include <nanovdb/HostBuffer.h>
|
|
#include <nanovdb/io/IO.h>
|
|
#endif
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <thread>
|
|
|
|
using namespace XCEngine::Resources;
|
|
|
|
namespace {
|
|
|
|
struct GeneratedNanoVDBVolume {
|
|
size_t payloadSize = 0u;
|
|
XCEngine::Math::Bounds bounds;
|
|
XCEngine::Math::Vector3 voxelSize = XCEngine::Math::Vector3::Zero();
|
|
VolumeIndexBounds indexBounds = {};
|
|
XCEngine::Core::uint32 gridType = 0u;
|
|
XCEngine::Core::uint32 gridClass = 0u;
|
|
};
|
|
|
|
std::filesystem::path GetCloudVolumePath() {
|
|
return std::filesystem::path(XCENGINE_TEST_CLOUD_NVDB_PATH);
|
|
}
|
|
|
|
void ExpectVector3Near(
|
|
const XCEngine::Math::Vector3& actual,
|
|
const XCEngine::Math::Vector3& expected,
|
|
float epsilon = 1e-4f) {
|
|
EXPECT_NEAR(actual.x, expected.x, epsilon);
|
|
EXPECT_NEAR(actual.y, expected.y, epsilon);
|
|
EXPECT_NEAR(actual.z, expected.z, epsilon);
|
|
}
|
|
|
|
void ExpectIndexBoundsEq(
|
|
const VolumeIndexBounds& actual,
|
|
const VolumeIndexBounds& expected) {
|
|
EXPECT_EQ(actual.minX, expected.minX);
|
|
EXPECT_EQ(actual.minY, expected.minY);
|
|
EXPECT_EQ(actual.minZ, expected.minZ);
|
|
EXPECT_EQ(actual.maxX, expected.maxX);
|
|
EXPECT_EQ(actual.maxY, expected.maxY);
|
|
EXPECT_EQ(actual.maxZ, expected.maxZ);
|
|
}
|
|
|
|
#if defined(XCENGINE_HAS_NANOVDB)
|
|
|
|
XCEngine::Math::Vector3 ToVector3(const nanovdb::Vec3d& value) {
|
|
return XCEngine::Math::Vector3(
|
|
static_cast<float>(value[0]),
|
|
static_cast<float>(value[1]),
|
|
static_cast<float>(value[2]));
|
|
}
|
|
|
|
VolumeIndexBounds ToIndexBounds(const nanovdb::CoordBBox& value) {
|
|
VolumeIndexBounds bounds = {};
|
|
bounds.minX = value.min()[0];
|
|
bounds.minY = value.min()[1];
|
|
bounds.minZ = value.min()[2];
|
|
bounds.maxX = value.max()[0];
|
|
bounds.maxY = value.max()[1];
|
|
bounds.maxZ = value.max()[2];
|
|
return bounds;
|
|
}
|
|
|
|
GeneratedNanoVDBVolume ReadTestNanoVDBFileMetadata(const std::filesystem::path& path) {
|
|
nanovdb::GridHandle<nanovdb::HostBuffer> handle =
|
|
nanovdb::io::readGrid<nanovdb::HostBuffer>(path.string());
|
|
|
|
GeneratedNanoVDBVolume generated;
|
|
generated.payloadSize = static_cast<size_t>(handle.bufferSize());
|
|
|
|
const nanovdb::GridMetaData* metadata = handle.gridMetaData();
|
|
if (metadata != nullptr) {
|
|
generated.bounds.SetMinMax(
|
|
ToVector3(metadata->worldBBox().min()),
|
|
ToVector3(metadata->worldBBox().max()));
|
|
generated.voxelSize = ToVector3(metadata->voxelSize());
|
|
generated.indexBounds = ToIndexBounds(metadata->indexBBox());
|
|
generated.gridType = static_cast<XCEngine::Core::uint32>(metadata->gridType());
|
|
generated.gridClass = static_cast<XCEngine::Core::uint32>(metadata->gridClass());
|
|
}
|
|
|
|
return generated;
|
|
}
|
|
#endif
|
|
|
|
TEST(VolumeFieldLoader, GetResourceType) {
|
|
VolumeFieldLoader loader;
|
|
EXPECT_EQ(loader.GetResourceType(), ResourceType::VolumeField);
|
|
}
|
|
|
|
TEST(VolumeFieldLoader, CanLoad) {
|
|
VolumeFieldLoader loader;
|
|
EXPECT_TRUE(loader.CanLoad("cloud.nvdb"));
|
|
EXPECT_TRUE(loader.CanLoad("cloud.xcvol"));
|
|
EXPECT_FALSE(loader.CanLoad("cloud.txt"));
|
|
}
|
|
|
|
TEST(VolumeFieldLoader, LoadInvalidPath) {
|
|
VolumeFieldLoader loader;
|
|
LoadResult result = loader.Load("invalid/path/cloud.nvdb");
|
|
EXPECT_FALSE(result);
|
|
}
|
|
|
|
#if defined(XCENGINE_HAS_NANOVDB)
|
|
TEST(VolumeFieldLoader, LoadSourceNanoVDBGridPayload) {
|
|
namespace fs = std::filesystem;
|
|
|
|
const fs::path volumePath = GetCloudVolumePath();
|
|
ASSERT_TRUE(fs::exists(volumePath));
|
|
|
|
const GeneratedNanoVDBVolume generated = ReadTestNanoVDBFileMetadata(volumePath);
|
|
|
|
VolumeFieldLoader loader;
|
|
LoadResult result = loader.Load(volumePath.string().c_str());
|
|
ASSERT_TRUE(result);
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
auto* volumeField = static_cast<VolumeField*>(result.resource);
|
|
EXPECT_EQ(volumeField->GetStorageKind(), VolumeStorageKind::NanoVDB);
|
|
EXPECT_EQ(volumeField->GetPayloadSize(), generated.payloadSize);
|
|
ExpectVector3Near(volumeField->GetBounds().GetMin(), generated.bounds.GetMin());
|
|
ExpectVector3Near(volumeField->GetBounds().GetMax(), generated.bounds.GetMax());
|
|
ExpectVector3Near(volumeField->GetVoxelSize(), generated.voxelSize);
|
|
ExpectIndexBoundsEq(volumeField->GetIndexBounds(), generated.indexBounds);
|
|
EXPECT_EQ(volumeField->GetGridType(), generated.gridType);
|
|
EXPECT_EQ(volumeField->GetGridClass(), generated.gridClass);
|
|
|
|
delete volumeField;
|
|
}
|
|
#endif
|
|
|
|
#if defined(XCENGINE_HAS_NANOVDB)
|
|
TEST(VolumeFieldLoader, AssetDatabaseCreatesVolumeArtifactAndReusesItWithoutReimport) {
|
|
namespace fs = std::filesystem;
|
|
using namespace std::chrono_literals;
|
|
|
|
const fs::path projectRoot = fs::temp_directory_path() / "xc_volume_library_cache_test";
|
|
const fs::path assetsDir = projectRoot / "Assets";
|
|
const fs::path volumePath = assetsDir / "cloud.nvdb";
|
|
const fs::path fixturePath = GetCloudVolumePath();
|
|
|
|
fs::remove_all(projectRoot);
|
|
fs::create_directories(assetsDir);
|
|
ASSERT_TRUE(fs::exists(fixturePath));
|
|
fs::copy_file(fixturePath, volumePath, fs::copy_options::overwrite_existing);
|
|
const GeneratedNanoVDBVolume generated = ReadTestNanoVDBFileMetadata(fixturePath);
|
|
|
|
AssetDatabase database;
|
|
database.Initialize(projectRoot.string().c_str());
|
|
|
|
AssetDatabase::ResolvedAsset firstResolve;
|
|
ASSERT_TRUE(database.EnsureArtifact("Assets/cloud.nvdb", ResourceType::VolumeField, firstResolve));
|
|
ASSERT_TRUE(firstResolve.exists);
|
|
ASSERT_TRUE(firstResolve.artifactReady);
|
|
EXPECT_TRUE(fs::exists(firstResolve.artifactMainPath.CStr()));
|
|
EXPECT_EQ(fs::path(firstResolve.artifactMainPath.CStr()).extension().generic_string(), ".xcvol");
|
|
|
|
VolumeFieldLoader loader;
|
|
LoadResult artifactLoad = loader.Load(firstResolve.artifactMainPath);
|
|
ASSERT_TRUE(artifactLoad);
|
|
ASSERT_NE(artifactLoad.resource, nullptr);
|
|
auto* artifactVolume = static_cast<VolumeField*>(artifactLoad.resource);
|
|
EXPECT_EQ(artifactVolume->GetStorageKind(), VolumeStorageKind::NanoVDB);
|
|
EXPECT_EQ(artifactVolume->GetPayloadSize(), generated.payloadSize);
|
|
ExpectVector3Near(artifactVolume->GetBounds().GetMin(), generated.bounds.GetMin());
|
|
ExpectVector3Near(artifactVolume->GetBounds().GetMax(), generated.bounds.GetMax());
|
|
ExpectVector3Near(artifactVolume->GetVoxelSize(), generated.voxelSize);
|
|
ExpectIndexBoundsEq(artifactVolume->GetIndexBounds(), generated.indexBounds);
|
|
EXPECT_EQ(artifactVolume->GetGridType(), generated.gridType);
|
|
EXPECT_EQ(artifactVolume->GetGridClass(), generated.gridClass);
|
|
delete artifactVolume;
|
|
|
|
const auto originalArtifactWriteTime = fs::last_write_time(firstResolve.artifactMainPath.CStr());
|
|
std::this_thread::sleep_for(50ms);
|
|
|
|
AssetDatabase::ResolvedAsset secondResolve;
|
|
ASSERT_TRUE(database.EnsureArtifact("Assets/cloud.nvdb", ResourceType::VolumeField, secondResolve));
|
|
EXPECT_EQ(firstResolve.artifactMainPath, secondResolve.artifactMainPath);
|
|
EXPECT_EQ(originalArtifactWriteTime, fs::last_write_time(secondResolve.artifactMainPath.CStr()));
|
|
|
|
database.Shutdown();
|
|
fs::remove_all(projectRoot);
|
|
}
|
|
#else
|
|
TEST(VolumeFieldLoader, AssetDatabaseCreatesVolumeArtifactAndReusesItWithoutReimport) {
|
|
GTEST_SKIP() << "NanoVDB headers are unavailable in this build";
|
|
}
|
|
#endif
|
|
|
|
#if defined(XCENGINE_HAS_NANOVDB)
|
|
TEST(VolumeFieldLoader, ResourceManagerLoadsVolumeByAssetRefFromProjectAssets) {
|
|
namespace fs = std::filesystem;
|
|
|
|
ResourceManager& manager = ResourceManager::Get();
|
|
manager.Initialize();
|
|
|
|
const fs::path projectRoot = fs::temp_directory_path() / "xc_volume_asset_ref_test";
|
|
const fs::path assetsDir = projectRoot / "Assets";
|
|
const fs::path volumePath = assetsDir / "cloud.nvdb";
|
|
const fs::path fixturePath = GetCloudVolumePath();
|
|
|
|
fs::remove_all(projectRoot);
|
|
fs::create_directories(assetsDir);
|
|
ASSERT_TRUE(fs::exists(fixturePath));
|
|
fs::copy_file(fixturePath, volumePath, fs::copy_options::overwrite_existing);
|
|
const GeneratedNanoVDBVolume generated = ReadTestNanoVDBFileMetadata(fixturePath);
|
|
|
|
manager.SetResourceRoot(projectRoot.string().c_str());
|
|
|
|
{
|
|
const auto firstHandle = manager.Load<VolumeField>("Assets/cloud.nvdb");
|
|
ASSERT_TRUE(firstHandle.IsValid());
|
|
EXPECT_EQ(firstHandle->GetStorageKind(), VolumeStorageKind::NanoVDB);
|
|
EXPECT_EQ(firstHandle->GetPayloadSize(), generated.payloadSize);
|
|
ExpectVector3Near(firstHandle->GetBounds().GetMin(), generated.bounds.GetMin());
|
|
ExpectVector3Near(firstHandle->GetBounds().GetMax(), generated.bounds.GetMax());
|
|
ExpectVector3Near(firstHandle->GetVoxelSize(), generated.voxelSize);
|
|
ExpectIndexBoundsEq(firstHandle->GetIndexBounds(), generated.indexBounds);
|
|
EXPECT_EQ(firstHandle->GetGridType(), generated.gridType);
|
|
EXPECT_EQ(firstHandle->GetGridClass(), generated.gridClass);
|
|
|
|
AssetRef assetRef;
|
|
ASSERT_TRUE(manager.TryGetAssetRef("Assets/cloud.nvdb", ResourceType::VolumeField, assetRef));
|
|
EXPECT_TRUE(assetRef.IsValid());
|
|
|
|
manager.UnloadAll();
|
|
|
|
const auto secondHandle = manager.Load<VolumeField>(assetRef);
|
|
ASSERT_TRUE(secondHandle.IsValid());
|
|
EXPECT_EQ(secondHandle->GetStorageKind(), VolumeStorageKind::NanoVDB);
|
|
EXPECT_EQ(secondHandle->GetPayloadSize(), generated.payloadSize);
|
|
ExpectVector3Near(secondHandle->GetBounds().GetMin(), generated.bounds.GetMin());
|
|
ExpectVector3Near(secondHandle->GetBounds().GetMax(), generated.bounds.GetMax());
|
|
ExpectVector3Near(secondHandle->GetVoxelSize(), generated.voxelSize);
|
|
ExpectIndexBoundsEq(secondHandle->GetIndexBounds(), generated.indexBounds);
|
|
EXPECT_EQ(secondHandle->GetGridType(), generated.gridType);
|
|
EXPECT_EQ(secondHandle->GetGridClass(), generated.gridClass);
|
|
}
|
|
|
|
manager.SetResourceRoot("");
|
|
manager.Shutdown();
|
|
fs::remove_all(projectRoot);
|
|
}
|
|
#else
|
|
TEST(VolumeFieldLoader, ResourceManagerLoadsVolumeByAssetRefFromProjectAssets) {
|
|
GTEST_SKIP() << "NanoVDB headers are unavailable in this build";
|
|
}
|
|
#endif
|
|
|
|
} // namespace
|