Fix NanoVDB volume loading and rendering
This commit is contained in:
@@ -17,7 +17,10 @@ bool VolumeField::Create(VolumeStorageKind storageKind,
|
||||
const void* payload,
|
||||
size_t payloadSize,
|
||||
const Math::Bounds& bounds,
|
||||
const Math::Vector3& voxelSize) {
|
||||
const Math::Vector3& voxelSize,
|
||||
const VolumeIndexBounds& indexBounds,
|
||||
Core::uint32 gridType,
|
||||
Core::uint32 gridClass) {
|
||||
if (payload == nullptr || payloadSize == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -25,6 +28,9 @@ bool VolumeField::Create(VolumeStorageKind storageKind,
|
||||
m_storageKind = storageKind;
|
||||
m_bounds = bounds;
|
||||
m_voxelSize = voxelSize;
|
||||
m_indexBounds = indexBounds;
|
||||
m_gridType = gridType;
|
||||
m_gridClass = gridClass;
|
||||
m_payload.Resize(payloadSize);
|
||||
std::memcpy(m_payload.Data(), payload, payloadSize);
|
||||
m_isValid = true;
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Volume/VolumeField.h>
|
||||
|
||||
#if defined(XCENGINE_HAS_NANOVDB)
|
||||
#include <nanovdb/GridHandle.h>
|
||||
#include <nanovdb/HostBuffer.h>
|
||||
#include <nanovdb/io/IO.h>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -26,6 +33,9 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
|
||||
VolumeStorageKind storageKind,
|
||||
const Math::Bounds& bounds,
|
||||
const Math::Vector3& voxelSize,
|
||||
const VolumeIndexBounds& indexBounds,
|
||||
Core::uint32 gridType,
|
||||
Core::uint32 gridClass,
|
||||
const void* payload,
|
||||
size_t payloadSize) {
|
||||
auto* volumeField = new VolumeField();
|
||||
@@ -37,7 +47,15 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
|
||||
params.memorySize = payloadSize;
|
||||
volumeField->Initialize(params);
|
||||
|
||||
if (!volumeField->Create(storageKind, payload, payloadSize, bounds, voxelSize)) {
|
||||
if (!volumeField->Create(
|
||||
storageKind,
|
||||
payload,
|
||||
payloadSize,
|
||||
bounds,
|
||||
voxelSize,
|
||||
indexBounds,
|
||||
gridType,
|
||||
gridClass)) {
|
||||
delete volumeField;
|
||||
return LoadResult(Containers::String("Failed to create volume field resource: ") + path);
|
||||
}
|
||||
@@ -45,7 +63,7 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
|
||||
return LoadResult(volumeField);
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
std::filesystem::path ResolveVolumeFieldPath(const Containers::String& path) {
|
||||
std::filesystem::path resolvedPath(path.CStr());
|
||||
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
@@ -54,6 +72,12 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read volume artifact: ") + path);
|
||||
@@ -66,7 +90,7 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
const bool validHeader =
|
||||
std::memcmp(header.magic, "XCVOL01", 7) == 0 &&
|
||||
std::memcmp(header.magic, "XCVOL02", 7) == 0 &&
|
||||
header.schemaVersion == kVolumeFieldArtifactSchemaVersion &&
|
||||
header.payloadSize > 0;
|
||||
if (!validHeader) {
|
||||
@@ -83,14 +107,118 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
Math::Bounds bounds;
|
||||
bounds.SetMinMax(header.boundsMin, header.boundsMax);
|
||||
|
||||
VolumeIndexBounds indexBounds = {};
|
||||
indexBounds.minX = header.indexBoundsMin[0];
|
||||
indexBounds.minY = header.indexBoundsMin[1];
|
||||
indexBounds.minZ = header.indexBoundsMin[2];
|
||||
indexBounds.maxX = header.indexBoundsMax[0];
|
||||
indexBounds.maxY = header.indexBoundsMax[1];
|
||||
indexBounds.maxZ = header.indexBoundsMax[2];
|
||||
|
||||
return CreateVolumeFieldResource(path,
|
||||
static_cast<VolumeStorageKind>(header.storageKind),
|
||||
bounds,
|
||||
header.voxelSize,
|
||||
indexBounds,
|
||||
header.gridType,
|
||||
header.gridClass,
|
||||
payload.Data(),
|
||||
payload.Size());
|
||||
}
|
||||
|
||||
#if defined(XCENGINE_HAS_NANOVDB)
|
||||
Math::Vector3 ToEngineVector3(const nanovdb::Vec3d& value) {
|
||||
return Math::Vector3(
|
||||
static_cast<float>(value[0]),
|
||||
static_cast<float>(value[1]),
|
||||
static_cast<float>(value[2]));
|
||||
}
|
||||
|
||||
VolumeIndexBounds ToEngineIndexBounds(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;
|
||||
}
|
||||
|
||||
bool IsFiniteVector3(const Math::Vector3& value) {
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
|
||||
}
|
||||
|
||||
Math::Bounds BuildNanoVDBBounds(const nanovdb::GridMetaData& metadata) {
|
||||
const Math::Vector3 minPoint = ToEngineVector3(metadata.worldBBox().min());
|
||||
const Math::Vector3 maxPoint = ToEngineVector3(metadata.worldBBox().max());
|
||||
if (!IsFiniteVector3(minPoint) || !IsFiniteVector3(maxPoint)) {
|
||||
return Math::Bounds();
|
||||
}
|
||||
|
||||
Math::Bounds bounds;
|
||||
bounds.SetMinMax(
|
||||
Math::Vector3(
|
||||
std::min(minPoint.x, maxPoint.x),
|
||||
std::min(minPoint.y, maxPoint.y),
|
||||
std::min(minPoint.z, maxPoint.z)),
|
||||
Math::Vector3(
|
||||
std::max(minPoint.x, maxPoint.x),
|
||||
std::max(minPoint.y, maxPoint.y),
|
||||
std::max(minPoint.z, maxPoint.z)));
|
||||
return bounds;
|
||||
}
|
||||
|
||||
LoadResult LoadNanoVDBSourceFile(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
|
||||
if (!std::filesystem::exists(resolvedPath)) {
|
||||
return LoadResult(Containers::String("Failed to read file: ") + path);
|
||||
}
|
||||
|
||||
try {
|
||||
nanovdb::GridHandle<nanovdb::HostBuffer> handle =
|
||||
nanovdb::io::readGrid<nanovdb::HostBuffer>(resolvedPath.string());
|
||||
if (!handle || handle.data() == nullptr || handle.bufferSize() == 0u) {
|
||||
return LoadResult(Containers::String("Failed to parse NanoVDB grid payload: ") + path);
|
||||
}
|
||||
|
||||
const nanovdb::GridMetaData* metadata = handle.gridMetaData();
|
||||
Math::Bounds bounds;
|
||||
Math::Vector3 voxelSize = Math::Vector3::Zero();
|
||||
VolumeIndexBounds indexBounds = {};
|
||||
Core::uint32 gridType = 0u;
|
||||
Core::uint32 gridClass = 0u;
|
||||
if (metadata != nullptr && metadata->isValid()) {
|
||||
bounds = BuildNanoVDBBounds(*metadata);
|
||||
voxelSize = ToEngineVector3(metadata->voxelSize());
|
||||
indexBounds = ToEngineIndexBounds(metadata->indexBBox());
|
||||
gridType = static_cast<Core::uint32>(metadata->gridType());
|
||||
gridClass = static_cast<Core::uint32>(metadata->gridClass());
|
||||
if (!IsFiniteVector3(voxelSize)) {
|
||||
voxelSize = Math::Vector3::Zero();
|
||||
}
|
||||
}
|
||||
|
||||
return CreateVolumeFieldResource(
|
||||
path,
|
||||
VolumeStorageKind::NanoVDB,
|
||||
bounds,
|
||||
voxelSize,
|
||||
indexBounds,
|
||||
gridType,
|
||||
gridClass,
|
||||
handle.data(),
|
||||
static_cast<size_t>(handle.bufferSize()));
|
||||
} catch (const std::exception& e) {
|
||||
return LoadResult(
|
||||
Containers::String("Failed to parse NanoVDB file: ") +
|
||||
path +
|
||||
" - " +
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
VolumeFieldLoader::VolumeFieldLoader() = default;
|
||||
@@ -121,17 +249,12 @@ LoadResult VolumeFieldLoader::Load(const Containers::String& path, const ImportS
|
||||
return LoadVolumeFieldArtifact(path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload = ReadFileData(path);
|
||||
if (payload.Empty()) {
|
||||
return LoadResult(Containers::String("Failed to read file: ") + path);
|
||||
}
|
||||
|
||||
return CreateVolumeFieldResource(path,
|
||||
VolumeStorageKind::NanoVDB,
|
||||
Math::Bounds(),
|
||||
Math::Vector3::Zero(),
|
||||
payload.Data(),
|
||||
payload.Size());
|
||||
#if defined(XCENGINE_HAS_NANOVDB)
|
||||
return LoadNanoVDBSourceFile(path);
|
||||
#else
|
||||
return LoadResult(
|
||||
Containers::String("NanoVDB source-file support is unavailable in this build: ") + path);
|
||||
#endif
|
||||
}
|
||||
|
||||
ImportSettings* VolumeFieldLoader::GetDefaultSettings() const {
|
||||
|
||||
Reference in New Issue
Block a user