diff --git a/Source/CesiumRuntime/CesiumRuntime.Build.cs b/Source/CesiumRuntime/CesiumRuntime.Build.cs index 215f0eb0f..c22952f17 100644 --- a/Source/CesiumRuntime/CesiumRuntime.Build.cs +++ b/Source/CesiumRuntime/CesiumRuntime.Build.cs @@ -80,8 +80,10 @@ public CesiumRuntime(ReadOnlyTargetRules Target) : base(Target) "CesiumGeometry", "CesiumGeospatial", "CesiumGltfReader", + "CesiumGltfWriter", "CesiumGltf", "CesiumJsonReader", + "CesiumJsonWriter", "CesiumUtility", "draco", "ktx_read", @@ -133,7 +135,7 @@ public CesiumRuntime(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "Core", + "Core" // ... add other public dependencies that you statically link with here ... } ); diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index fc06749f3..e85958121 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -25,6 +25,8 @@ #include "CesiumGltf/Ktx2TranscodeTargets.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPrimitiveComponent.h" +#include "CesiumGltfReader/GltfReader.h" +#include "CesiumGltfWriter/GltfWriter.h" #include "CesiumLifetime.h" #include "CesiumRasterOverlay.h" #include "CesiumRuntime.h" @@ -611,6 +613,8 @@ void ACesium3DTileset::NotifyHit( // std::cout << "Hit face index 2: " << detailedHit.FaceIndex << std::endl; } +namespace {} // namespace + class UnrealResourcePreparer : public Cesium3DTilesSelection::IPrepareRendererResources { public: @@ -624,24 +628,49 @@ class UnrealResourcePreparer #endif { } - - virtual CesiumAsync::Future< - Cesium3DTilesSelection::TileLoadResultAndRenderResources> + virtual CesiumAsync::Future prepareInLoadThread( const CesiumAsync::AsyncSystem& asyncSystem, Cesium3DTilesSelection::TileLoadResult&& tileLoadResult, const glm::dmat4& transform, const std::any& rendererOptions) override { - CesiumGltf::Model* pModel = + // TODO: serialize model options into DDC so we can check cache hits + // to see if they used the same options. If the cached derived content + // used different options, we may need to invalidate or update the cache. + CreateGltfOptions::CreateModelOptions options; + options.pModel = std::get_if(&tileLoadResult.contentKind); - if (!pModel) - return asyncSystem.createResolvedFuture( - Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(tileLoadResult), - nullptr}); - CreateGltfOptions::CreateModelOptions options; - options.pModel = pModel; + if (!options.pModel) { + if (std::get_if( + &tileLoadResult.contentKind)) { + // Custom serialized data was found in the cache. + const CesiumAsync::IAssetResponse* pResponse = + tileLoadResult.pCompletedRequest->response(); + if (pResponse && !pResponse->clientData().empty()) { + // We have cached derived data + options.derivedDataCache = pResponse->clientData(); + + // Add empty model in the tile content, the gltf loader may populate + // it when deserializing the DDC. + tileLoadResult.contentKind = CesiumGltf::Model(); + options.pModel = + std::get_if(&tileLoadResult.contentKind); + } + } + + if (!options.pModel) { + // We have no model and no derived content, just write + // back the original response data. + return asyncSystem.createResolvedFuture( + Cesium3DTilesSelection::ClientTileLoadResult{ + std::move(tileLoadResult), + nullptr, + true, + {}}); + } + } + options.alwaysIncludeTangents = this->_pActor->GetAlwaysIncludeTangents(); #if PHYSICS_INTERFACE_PHYSX @@ -651,12 +680,14 @@ class UnrealResourcePreparer options.pEncodedMetadataDescription = &this->_pActor->_encodedMetadataDescription; - TUniquePtr pHalf = - UCesiumGltfComponent::CreateOffGameThread(transform, options); + UCesiumGltfComponent::HalfConstructed* pHalf = + UCesiumGltfComponent::CreateOffGameThread(transform, options).Release(); return asyncSystem.createResolvedFuture( - Cesium3DTilesSelection::TileLoadResultAndRenderResources{ + Cesium3DTilesSelection::ClientTileLoadResult{ std::move(tileLoadResult), - pHalf.Release()}); + pHalf, + false, + std::move(pHalf->derivedDataToCache)}); } virtual void* prepareInMainThread( diff --git a/Source/CesiumRuntime/Private/CesiumDerivedDataCache.cpp b/Source/CesiumRuntime/Private/CesiumDerivedDataCache.cpp new file mode 100644 index 000000000..ed254bfba --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumDerivedDataCache.cpp @@ -0,0 +1,362 @@ +// Copyright 2020-2021 CesiumGS, Inc. and Contributors + +#include "CesiumDerivedDataCache.h" + +//#include "Compression/lz4.h" + +#include +#include + +namespace CesiumDerivedDataCache { +namespace { +/* + Format Outline: + 1. CachedGltfHeader + 2. glTF JSON + 3. CachedBufferDescription 1, 2, 3... + 4. CachedImageDescription 1, 2, 3... + 5. CachedPhysicsMeshDescription 1, 2, 3... + 5. Binary Chunk with decoded buffers / images / cooked physics meshes + */ +// TODO: Formalize the format +// TODO: Encode current "options", so we can invalidate on +// options changing. + +enum ECachedPhysicsType : uint32_t { + PHYSX = 0, + CHAOS = 1 // TODO: not supported yet +}; + +struct CachedGltfHeader { + unsigned char magic[4]; + uint32_t version; + uint32_t gltfJsonSize; + uint32_t cachedBuffersCount; + uint32_t cachedImagesCount; + uint32_t cachedPhysicsType; + uint32_t cachedPhysicsMeshesCount; +}; + +struct CachedBufferDescription { + uint32_t byteOffset; + uint32_t byteSize; +}; + +struct CachedImageDescription { + uint32_t width; + uint32_t height; + uint32_t channels; + uint32_t bytesPerChannel; + uint32_t mipCount; + uint32_t byteOffset; + uint32_t byteSize; +}; + +struct CachedPhysicsMeshDescription { + uint32_t byteOffset; + uint32_t byteSize; +}; +} // namespace + +// Convenient macro for runtime "asserting" - fails the function otherwise. +#define PROCEED_IF(stmt) \ + if (!(stmt)) { \ + return std::nullopt; \ + } +std::optional +deserialize(const gsl::span& cache) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::DeserializeGltf) + + // TODO: should be static thread-local? + // std::vector cache; + //{ + // TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LZ4_Decompress) + // cache.resize(4096); + + //} + + PROCEED_IF(cache.size() >= sizeof(CachedGltfHeader)) + + size_t readPos = 0; + + const CachedGltfHeader& header = + *reinterpret_cast(&cache[readPos]); + readPos += sizeof(CachedGltfHeader); + + // TODO: look for a more elegant magic check! + PROCEED_IF(header.magic[0] == 'C') + PROCEED_IF(header.magic[1] == '4') + PROCEED_IF(header.magic[2] == 'U') + PROCEED_IF(header.magic[3] == 'E') + PROCEED_IF(header.version == 1) + + PROCEED_IF(cache.size() >= readPos + header.gltfJsonSize); + + gsl::span gltfJsonBytes( + &cache[readPos], + header.gltfJsonSize); + readPos += header.gltfJsonSize; + + CesiumGltfReader::GltfReader reader; + CesiumGltfReader::GltfReaderResult gltfJsonResult = + reader.readGltf(gltfJsonBytes); + + PROCEED_IF(gltfJsonResult.errors.empty() && gltfJsonResult.model) + PROCEED_IF( + gltfJsonResult.model->buffers.size() == header.cachedBuffersCount && + gltfJsonResult.model->images.size() == header.cachedImagesCount) + + size_t bufferDescriptionsSize = + header.cachedBuffersCount * sizeof(CachedBufferDescription) + + header.cachedImagesCount * sizeof(CachedImageDescription); + + PROCEED_IF(cache.size() >= readPos + bufferDescriptionsSize); + + for (CesiumGltf::Buffer& buffer : gltfJsonResult.model->buffers) { + const CachedBufferDescription& description = + *reinterpret_cast(&cache[readPos]); + readPos += sizeof(CachedBufferDescription); + + PROCEED_IF(cache.size() >= description.byteOffset + description.byteSize) + buffer.cesium.data.resize(description.byteSize); + std::memcpy( + buffer.cesium.data.data(), + &cache[description.byteOffset], + description.byteSize); + } + + for (CesiumGltf::Image& image : gltfJsonResult.model->images) { + const CachedImageDescription& description = + *reinterpret_cast(&cache[readPos]); + readPos += sizeof(CachedImageDescription); + + PROCEED_IF(cache.size() >= description.byteOffset + description.byteSize) + image.cesium.pixelData.resize(description.byteSize); + std::memcpy( + image.cesium.pixelData.data(), + &cache[description.byteOffset], + description.byteSize); + + image.cesium.width = static_cast(description.width); + image.cesium.height = static_cast(description.height); + image.cesium.channels = static_cast(description.channels); + image.cesium.bytesPerChannel = + static_cast(description.bytesPerChannel); + + image.cesium.mipPositions.resize(description.mipCount); + + size_t mipByteOffset = 0; + + // A mip count of 0 indicates there is only one mip and it is within the + // pixelData. + if (description.mipCount != 0) { + CesiumGltf::ImageCesiumMipPosition& mip0 = image.cesium.mipPositions[0]; + mip0.byteOffset = mipByteOffset; + mip0.byteSize = description.width * description.height * + description.channels * description.bytesPerChannel; + + mipByteOffset += mip0.byteSize; + } + + for (uint32_t mipIndex = 1; mipIndex < description.mipCount; ++mipIndex) { + uint32_t mipWidth = description.width >> mipIndex; + uint32_t mipHeight = description.height >> mipIndex; + + if (mipWidth < 1) { + mipWidth = 1; + } + + if (mipHeight < 1) { + mipHeight = 1; + } + + CesiumGltf::ImageCesiumMipPosition& mip = + image.cesium.mipPositions[mipIndex]; + mip.byteOffset = mipByteOffset; + mip.byteSize = mipWidth * mipHeight * description.channels * + description.bytesPerChannel; + + mipByteOffset += mip.byteSize; + } + } + // TODO: physics!! + // https://github.com/EpicGames/UnrealEngine/blob/46544fa5e0aa9e6740c19b44b0628b72e7bbd5ce/Engine/Source/Runtime/Engine/Private/PhysicsEngine/BodySetup.cpp#L531 + +#if PHYSICS_INTERFACE_PHYSX + PROCEED_IF(header.cachedPhysicsType == ECachedPhysicsType::PHYSX); +#else + PROCEED_IF(header.cachedPhysicsType == ECachedPhysicsType::CHAOS); +#endif + + DerivedDataResult result; + result.model = std::move(*gltfJsonResult.model); + + size_t physicsMeshDescriptionsSize = + header.cachedPhysicsMeshesCount * sizeof(CachedPhysicsMeshDescription); + PROCEED_IF(cache.size() >= readPos + physicsMeshDescriptionsSize); + + result.cookedPhysicsMeshViews.reserve(header.cachedPhysicsMeshesCount); + + for (uint32_t i = 0; i < header.cachedPhysicsMeshesCount; ++i) { + const CachedPhysicsMeshDescription& description = + *reinterpret_cast(&cache[readPos]); + readPos += sizeof(CachedPhysicsMeshDescription); + + PROCEED_IF(cache.size() >= description.byteOffset + description.byteSize); + + result.cookedPhysicsMeshViews.push_back(gsl::span( + &cache[description.byteOffset], + description.byteSize)); + } + + // TODO: Would RVO happen if I just "return result"? + // ... it gets implicitly converted to an optional, + // but does it implicitly "move" the result into the + // optional? But "return std::move(result)" also seems + // non-idiomatic... + return std::make_optional(std::move(result)); +} +#undef PROCEED_IF + +std::vector serialize(const DerivedDataToCache& derivedData) { + check(derivedData.pModel != nullptr); + + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::serializeGltf) + + const CesiumGltf::Model& model = *derivedData.pModel; + + uint32_t bufferCount = static_cast(model.buffers.size()); + uint32_t imageCount = static_cast(model.images.size()); + uint32_t physicsMeshCount = + static_cast(derivedData.cookedPhysicsMeshViews.size()); + + CesiumGltfWriter::GltfWriter writer; + CesiumGltfWriter::GltfWriterResult gltfJsonResult = writer.writeGltf(model); + + if (!gltfJsonResult.errors.empty()) { + return {}; + } + + size_t gltfJsonSize = gltfJsonResult.gltfBytes.size(); + + uint32_t binaryChunkSize = 0; + for (uint32_t i = 0; i < bufferCount; ++i) { + binaryChunkSize += model.buffers[i].cesium.data.size(); + } + + for (uint32_t i = 0; i < imageCount; ++i) { + binaryChunkSize += model.images[i].cesium.pixelData.size(); + } + + for (uint32_t i = 0; i < physicsMeshCount; ++i) { + binaryChunkSize += derivedData.cookedPhysicsMeshViews[i].size(); + } + + // TODO: alignment? + size_t binaryChunkOffset = + sizeof(CachedGltfHeader) + gltfJsonSize + + bufferCount * sizeof(CachedBufferDescription) + + imageCount * sizeof(CachedImageDescription) + + physicsMeshCount * sizeof(CachedPhysicsMeshDescription); + size_t totalAllocation = binaryChunkOffset + binaryChunkSize; + + std::vector result(totalAllocation); + std::byte* pWritePos = result.data(); + + CachedGltfHeader& header = *reinterpret_cast(pWritePos); + header.magic[0] = 'C'; + header.magic[1] = '4'; + header.magic[2] = 'U'; + header.magic[3] = 'E'; + header.version = 1; + header.gltfJsonSize = static_cast(gltfJsonSize); + header.cachedBuffersCount = bufferCount; + header.cachedImagesCount = imageCount; + header.cachedPhysicsMeshesCount = physicsMeshCount; + pWritePos += sizeof(CachedGltfHeader); + + // Copy gltf json + std::memcpy(pWritePos, gltfJsonResult.gltfBytes.data(), gltfJsonSize); + pWritePos += gltfJsonSize; + + size_t binaryChunkWritePos = binaryChunkOffset; + + for (const CesiumGltf::Buffer& buffer : model.buffers) { + CachedBufferDescription& description = + *reinterpret_cast(pWritePos); + description.byteOffset = static_cast(binaryChunkWritePos); + description.byteSize = static_cast(buffer.cesium.data.size()); + pWritePos += sizeof(CachedBufferDescription); + + std::memcpy( + &result[binaryChunkWritePos], + buffer.cesium.data.data(), + description.byteSize); + binaryChunkWritePos += description.byteSize; + } + + for (const CesiumGltf::Image& image : model.images) { + CachedImageDescription& description = + *reinterpret_cast(pWritePos); + description.width = static_cast(image.cesium.width); + description.height = static_cast(image.cesium.height); + description.channels = static_cast(image.cesium.channels); + description.bytesPerChannel = + static_cast(image.cesium.bytesPerChannel); + description.mipCount = + static_cast(image.cesium.mipPositions.size()); + description.byteOffset = static_cast(binaryChunkWritePos); + description.byteSize = static_cast(image.cesium.pixelData.size()); + pWritePos += sizeof(CachedImageDescription); + + std::memcpy( + &result[binaryChunkWritePos], + image.cesium.pixelData.data(), + description.byteSize); + binaryChunkWritePos += description.byteSize; + } + + for (const gsl::span& cookedPhysicsMesh : + derivedData.cookedPhysicsMeshViews) { + CachedPhysicsMeshDescription& description = + *reinterpret_cast(pWritePos); + description.byteOffset = static_cast(binaryChunkWritePos); + description.byteSize = static_cast(cookedPhysicsMesh.size()); + pWritePos += sizeof(CachedPhysicsMeshDescription); + + std::memcpy( + &result[binaryChunkWritePos], + cookedPhysicsMesh.data(), + cookedPhysicsMesh.size()); + binaryChunkWritePos += description.byteSize; + } + + // The description and json writing should end at the start of the binary + // chunk. + check(pWritePos == &result[binaryChunkOffset]); + + // The written binary chunk should end as the expected at the very end of the + // allocation. + check(binaryChunkWritePos == totalAllocation); + + // TODO: if we are returning a separate compressed vector of bytes, then the + // "result" vector is just a scratch vector. So we should keep around the + // allocation. Maybe result should be static thread-local? + /*/ std::vector lz4CompressedBuffer; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LZ4_Compress) + + lz4CompressedBuffer.resize(LZ4_compressBound(result.size())); + + LZ4_compress_default( + (const char*)result.data(), + (char*)lz4CompressedBuffer.data(), + result.size(), + lz4CompressedBuffer.size()); + }*/ + + return result; + // return lz4CompressedBuffer; +} +} // namespace CesiumDerivedDataCache diff --git a/Source/CesiumRuntime/Private/CesiumDerivedDataCache.h b/Source/CesiumRuntime/Private/CesiumDerivedDataCache.h new file mode 100644 index 000000000..bcc67d2f3 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumDerivedDataCache.h @@ -0,0 +1,27 @@ +// Copyright 2020-2021 CesiumGS, Inc. and Contributors + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace CesiumDerivedDataCache { +struct DerivedDataResult { + CesiumGltf::Model model; + std::vector> cookedPhysicsMeshViews; +}; + +struct DerivedDataToCache { + const CesiumGltf::Model* pModel; + std::vector> cookedPhysicsMeshViews; +}; + +std::optional +deserialize(const gsl::span& cache); +std::vector serialize(const DerivedDataToCache& derivedData); +} // namespace CesiumDerivedDataCache diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index aa7e4f11b..b82df7587 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -6,6 +6,7 @@ #include "Cesium3DTilesSelection/RasterOverlay.h" #include "Cesium3DTilesSelection/RasterOverlayTile.h" #include "CesiumCommon.h" +#include "CesiumDerivedDataCache.h" #include "CesiumEncodedMetadataUtility.h" #include "CesiumFeatureIdAttribute.h" #include "CesiumFeatureIdTexture.h" @@ -22,6 +23,7 @@ #include "CesiumGltf/TextureInfo.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumMaterialUserData.h" +#include "CesiumPhysicsUtility.h" #include "CesiumRasterOverlays.h" #include "CesiumRuntime.h" #include "CesiumTextureUtility.h" @@ -51,17 +53,6 @@ #include #include -#if PHYSICS_INTERFACE_PHYSX -#include "IPhysXCooking.h" -#include "IPhysXCookingModule.h" -#include "Interfaces/Interface_CollisionDataProvider.h" -#include "PhysXCookHelper.h" -#else -#include "Chaos/AABBTree.h" -#include "Chaos/CollisionConvexMesh.h" -#include "Chaos/TriangleMeshImplicitObject.h" -#endif - #if WITH_EDITOR #include "ScopedTransaction.h" #endif @@ -69,6 +60,7 @@ using namespace CesiumGltf; using namespace CesiumTextureUtility; using namespace CesiumEncodedMetadataUtility; +using namespace CesiumPhysicsUtility; using namespace CreateGltfOptions; using namespace LoadGltfResult; @@ -305,20 +297,6 @@ static void computeFlatNormals( } } -#if PHYSICS_INTERFACE_PHYSX -static void BuildPhysXTriangleMeshes( - PxTriangleMesh*& pCollisionMesh, - FBodySetupUVInfo& uvInfo, - IPhysXCookingModule* pPhysXCooking, - const TArray& vertexData, - const TArray& indices); -#else -static TSharedPtr -BuildChaosTriangleMeshes( - const TArray& vertexData, - const TArray& indices); -#endif - static const Material defaultMaterial; static const MaterialPBRMetallicRoughness defaultPbrMetallicRoughness; @@ -669,6 +647,8 @@ FName createSafeName( } // namespace +// TODO: this function is getting way too complicated, might be worth splitting +// functionality out into utility functions. template static void loadPrimitive( LoadPrimitiveResult& primitiveResult, @@ -1189,11 +1169,46 @@ static void loadPrimitive( section.MaterialIndex = 0; - primitiveResult.pCollisionMesh = nullptr; + // primitiveResult.pCollisionMesh = nullptr; if (StaticMeshBuildVertices.Num() != 0 && indices.Num() != 0) { #if PHYSICS_INTERFACE_PHYSX - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::PhysXCook) + const auto& preCookedMeshes = options.pMeshOptions->pNodeOptions + ->pModelOptions->preCookedPhysicsMeshes; + + if (!preCookedMeshes.empty()) { + // Create the physX mesh from pre-cooked bulk data. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UnpackPreCookedPhysXMesh); + if (preCookedMeshes.size() <= options.primitiveIndex) { + // If there are pre-cooked physics mesh, there should be exactly + // one for each primitive in the model. + UE_LOG( + LogCesium, + Error, + TEXT("Missing pre-cooked physics mesh for a primitive.")); + return; + } + + primitiveResult.physxMesh = + createPhysxMesh(preCookedMeshes[options.primitiveIndex]); + } else { + // No pre-cooked bulk data available. Cook the physics mesh and save the + // bulk data in case we want to cache it. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::PhysXCook) + + primitiveResult.cookedPhysicsMesh = cookPhysxMesh( + options.pMeshOptions->pNodeOptions->pModelOptions + ->pPhysXCookingModule, + StaticMeshBuildVertices, + indices); + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreatePhysXMesh) + primitiveResult.physxMesh = + createPhysxMesh(primitiveResult.cookedPhysicsMesh); + } + } + + /*TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::PhysXCook) PxTriangleMesh* createdCollisionMesh = nullptr; BuildPhysXTriangleMeshes( createdCollisionMesh, @@ -1201,8 +1216,9 @@ static void loadPrimitive( options.pMeshOptions->pNodeOptions->pModelOptions->pPhysXCookingModule, StaticMeshBuildVertices, indices); - primitiveResult.pCollisionMesh.Reset(createdCollisionMesh); + primitiveResult.pCollisionMesh.Reset(createdCollisionMesh);*/ #else + // TODO: implement cooked physics caching for chaos TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) primitiveResult.pCollisionMesh = BuildChaosTriangleMeshes(StaticMeshBuildVertices, indices); @@ -1331,6 +1347,7 @@ static void loadPrimitive( static void loadMesh( std::optional& result, + uint32_t& primitiveCount, const glm::dmat4x4& transform, const CreateMeshOptions& options) { @@ -1342,7 +1359,8 @@ static void loadMesh( result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); for (const CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { - CreatePrimitiveOptions primitiveOptions = {&options, &*result, &primitive}; + CreatePrimitiveOptions primitiveOptions = + {primitiveCount++, &options, &*result, &primitive}; auto& primitiveResult = result->primitiveResults.emplace_back(); loadPrimitive(primitiveResult, transform, primitiveOptions); @@ -1355,6 +1373,7 @@ static void loadMesh( static void loadNode( std::vector& loadNodeResults, + uint32_t& primitiveCount, const glm::dmat4x4& transform, const CreateNodeOptions& options) { @@ -1432,7 +1451,7 @@ static void loadNode( int meshId = node.mesh; if (meshId >= 0 && meshId < model.meshes.size()) { CreateMeshOptions meshOptions = {&options, &result, &model.meshes[meshId]}; - loadMesh(result.meshResult, nodeTransform, meshOptions); + loadMesh(result.meshResult, primitiveCount, nodeTransform, meshOptions); } for (int childNodeId : node.children) { @@ -1441,7 +1460,11 @@ static void loadNode( options.pModelOptions, options.pHalfConstructedModelResult, &model.nodes[childNodeId]}; - loadNode(loadNodeResults, nodeTransform, childNodeOptions); + loadNode( + loadNodeResults, + primitiveCount, + nodeTransform, + childNodeOptions); } } } @@ -1520,24 +1543,25 @@ static void loadModelAnyThreadPart( applyGltfUpAxisTransform(model, rootTransform); } + uint32_t primitiveCount = 0; if (model.scene >= 0 && model.scene < model.scenes.size()) { // Show the default scene const Scene& defaultScene = model.scenes[model.scene]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = {&options, &result, &model.nodes[nodeId]}; - loadNode(result.nodeResults, rootTransform, nodeOptions); + loadNode(result.nodeResults, primitiveCount, rootTransform, nodeOptions); } } else if (model.scenes.size() > 0) { // There's no default, so show the first scene const Scene& defaultScene = model.scenes[0]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = {&options, &result, &model.nodes[nodeId]}; - loadNode(result.nodeResults, rootTransform, nodeOptions); + loadNode(result.nodeResults, primitiveCount, rootTransform, nodeOptions); } } else if (model.nodes.size() > 0) { // No scenes at all, use the first node as the root node. CreateNodeOptions nodeOptions = {&options, &result, &model.nodes[0]}; - loadNode(result.nodeResults, rootTransform, nodeOptions); + loadNode(result.nodeResults, primitiveCount, rootTransform, nodeOptions); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. for (const Mesh& mesh : model.meshes) { @@ -1547,7 +1571,11 @@ static void loadModelAnyThreadPart( &dummyNodeOptions, &dummyNodeResult, &mesh}; - loadMesh(dummyNodeResult.meshResult, rootTransform, meshOptions); + loadMesh( + dummyNodeResult.meshResult, + primitiveCount, + rootTransform, + meshOptions); } } } @@ -2060,10 +2088,10 @@ static void loadPrimitiveGameThreadPart( // pMesh->UpdateCollisionFromStaticMesh(); pBodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - if (loadResult.pCollisionMesh) { + if (loadResult.physxMesh.pTriMesh) { #if PHYSICS_INTERFACE_PHYSX - pBodySetup->TriMeshes.Add(loadResult.pCollisionMesh.Release()); - pBodySetup->UVInfo = std::move(loadResult.uvInfo); + pBodySetup->TriMeshes.Add(loadResult.physxMesh.pTriMesh.Release()); + pBodySetup->UVInfo = std::move(loadResult.physxMesh.uvInfo); #else pBodySetup->ChaosTriMeshes.Add(loadResult.pCollisionMesh); #endif @@ -2087,10 +2115,57 @@ static void loadPrimitiveGameThreadPart( /*static*/ TUniquePtr UCesiumGltfComponent::CreateOffGameThread( const glm::dmat4x4& Transform, - const CreateModelOptions& Options) { + CreateModelOptions& Options) { + // Deserialize the cached model if available. + if (!Options.derivedDataCache.empty()) { + std::optional derivedDataResult = + CesiumDerivedDataCache::deserialize(Options.derivedDataCache); + if (derivedDataResult) { + *Options.pModel = std::move(derivedDataResult->model); + Options.preCookedPhysicsMeshes = + std::move(derivedDataResult->cookedPhysicsMeshViews); + } + } + auto pResult = MakeUnique(); loadModelAnyThreadPart(pResult->loadModelResult, Transform, Options); + // TODO: this logic needs to respect whether the derived content _should_ + // be cached. There may be cases where the native tile loaders need the + // original data (e.g., embedded availability in quantized mesh tiles). + + // If we didn't have derived content before, this is newly loaded content - + // so serialize the derived data cache. + if (Options.derivedDataCache.empty()) { + CesiumDerivedDataCache::DerivedDataToCache derivedDataToCache; + derivedDataToCache.pModel = Options.pModel; + + // Collect cooked physics meshes. + for (const LoadNodeResult& node : pResult->loadModelResult.nodeResults) { + if (node.meshResult) { + for (const LoadPrimitiveResult& primitive : + node.meshResult->primitiveResults) { + if (primitive.cookedPhysicsMesh.empty()) { + // If any of the cooked meshes are missing, we shouldn't cache any + // - otherwise we won't be able to infer which cooked physics mesh + // corresponds to which glTF primitive. + derivedDataToCache.cookedPhysicsMeshViews.clear(); + break; + } + + derivedDataToCache.cookedPhysicsMeshViews.push_back( + gsl::span(primitive.cookedPhysicsMesh)); + } + } + } + + pResult->derivedDataToCache = + CesiumDerivedDataCache::serialize(derivedDataToCache); + } + + // TODO: set this if we detect that options have changed + pResult->invalidateDerivedDataCache = false; + return pResult; } @@ -2416,136 +2491,3 @@ void UCesiumGltfComponent::UpdateFade(float fadePercentage, bool fadingIn) { fadingIn ? 0.0f : 1.0f); } } - -#if PHYSICS_INTERFACE_PHYSX -static void BuildPhysXTriangleMeshes( - PxTriangleMesh*& pCollisionMesh, - FBodySetupUVInfo& uvInfo, - IPhysXCookingModule* pPhysXCookingModule, - const TArray& vertexData, - const TArray& indices) { - - if (pPhysXCookingModule) { - // TODO: use PhysX interface directly so we don't need to copy the - // vertices (it takes a stride parameter). - - FPhysXCookHelper cookHelper(pPhysXCookingModule); - - bool copyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults; - - cookHelper.CookInfo.TriMeshCookFlags = EPhysXMeshCookFlags::Default; - cookHelper.CookInfo.OuterDebugName = "CesiumGltfComponent"; - cookHelper.CookInfo.TriangleMeshDesc.bFlipNormals = true; - cookHelper.CookInfo.bCookTriMesh = true; - cookHelper.CookInfo.bSupportFaceRemap = true; - cookHelper.CookInfo.bSupportUVFromHitResults = copyUVs; - - TArray& vertices = cookHelper.CookInfo.TriangleMeshDesc.Vertices; - vertices.SetNum(vertexData.Num()); - for (size_t i = 0; i < vertexData.Num(); ++i) { - vertices[i] = vertexData[i].Position; - } - - if (copyUVs) { - TArray>& uvs = cookHelper.CookInfo.TriangleMeshDesc.UVs; - uvs.SetNum(8); - - for (size_t i = 0; i < 8; ++i) { - uvs[i].SetNum(vertices.Num()); - } - for (size_t i = 0; i < vertexData.Num(); ++i) { - for (size_t j = 0; j < 8; ++j) { - uvs[j][i] = vertexData[i].UVs[j]; - } - } - } - - TArray& physicsIndices = - cookHelper.CookInfo.TriangleMeshDesc.Indices; - physicsIndices.SetNum(indices.Num() / 3); - - for (size_t i = 0; i < indices.Num() / 3; ++i) { - physicsIndices[i].v0 = indices[3 * i]; - physicsIndices[i].v1 = indices[3 * i + 1]; - physicsIndices[i].v2 = indices[3 * i + 2]; - } - - cookHelper.CreatePhysicsMeshes_Concurrent(); - if (cookHelper.OutTriangleMeshes.Num() > 0) { - pCollisionMesh = cookHelper.OutTriangleMeshes[0]; - } - if (copyUVs) { - uvInfo = std::move(cookHelper.OutUVInfo); - } - } -} - -#else -template -static void fillTriangles( - TArray>& triangles, - const TArray& vertexData, - const TArray& indices, - int32 triangleCount) { - - triangles.Reserve(triangleCount); - - for (int32 i = 0; i < triangleCount; ++i) { - const int32 index0 = 3 * i; - triangles.Add(Chaos::TVector( - indices[index0 + 1], - indices[index0], - indices[index0 + 2])); - } -} - -static TSharedPtr -BuildChaosTriangleMeshes( - const TArray& vertexData, - const TArray& indices) { - - int32 vertexCount = vertexData.Num(); - int32 triangleCount = indices.Num() / 3; - - Chaos::TParticles vertices; - vertices.AddParticles(vertexCount); - - for (int32 i = 0; i < vertexCount; ++i) { - vertices.X(i) = vertexData[i].Position; - } - - TArray materials; - materials.SetNum(triangleCount); - - TArray faceRemap; - faceRemap.SetNum(triangleCount); - - for (int32 i = 0; i < triangleCount; ++i) { - faceRemap[i] = i; - } - - TUniquePtr> pFaceRemap = MakeUnique>(faceRemap); - - if (vertexCount < TNumericLimits::Max()) { - TArray> triangles; - fillTriangles(triangles, vertexData, indices, triangleCount); - return MakeShared( - MoveTemp(vertices), - MoveTemp(triangles), - MoveTemp(materials), - MoveTemp(pFaceRemap), - nullptr, - false); - } else { - TArray> triangles; - fillTriangles(triangles, vertexData, indices, triangleCount); - return MakeShared( - MoveTemp(vertices), - MoveTemp(triangles), - MoveTemp(materials), - MoveTemp(pFaceRemap), - nullptr, - false); - } -} -#endif diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.h b/Source/CesiumRuntime/Private/CesiumGltfComponent.h index df0a4c5af..a068a29eb 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.h @@ -12,6 +12,7 @@ #include "Interfaces/IHttpRequest.h" #include #include +#include #include "CesiumGltfComponent.generated.h" class UMaterialInterface; @@ -60,12 +61,15 @@ class UCesiumGltfComponent : public USceneComponent { public: class HalfConstructed { public: + std::vector derivedDataToCache{}; + bool invalidateDerivedDataCache = false; + virtual ~HalfConstructed() = default; }; static TUniquePtr CreateOffGameThread( const glm::dmat4x4& Transform, - const CreateGltfOptions::CreateModelOptions& Options); + CreateGltfOptions::CreateModelOptions& Options); static UCesiumGltfComponent* CreateOnGameThread( const CesiumGltf::Model& model, diff --git a/Source/CesiumRuntime/Private/CesiumPhysicsUtility.cpp b/Source/CesiumRuntime/Private/CesiumPhysicsUtility.cpp new file mode 100644 index 000000000..0f432501b --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumPhysicsUtility.cpp @@ -0,0 +1,241 @@ +// Copyright 2020-2021 CesiumGS, Inc. and Contributors + +#include "CesiumPhysicsUtility.h" + +#include "PhysicsPublic.h" + +namespace CesiumPhysicsUtility { +#if PHYSICS_INTERFACE_PHYSX +namespace { +class CesiumPxInputStream : public PxInputStream { +private: + gsl::span _bulkData; + uint32_t _readPos = 0; + +public: + CesiumPxInputStream(const gsl::span& bulkData) + : _bulkData(bulkData) {} + + virtual uint32_t read(void* dest, uint32_t count) override { + uint32_t bytesToRead = count; + if (this->_readPos + count > this->_bulkData.size()) { + bytesToRead = + static_cast(this->_bulkData.size() - this->_readPos); + } + + if (bytesToRead > 0) { + std::memcpy(dest, (void*)&this->_bulkData[this->_readPos], bytesToRead); + } + + this->_readPos += bytesToRead; + + return bytesToRead; + } +}; + +class CesiumPxOutputStream : public PxOutputStream { +public: + std::vector bulkData; + + virtual uint32_t write(const void* src, uint32_t count) override { + if (count > 0) { + size_t currentSize = this->bulkData.size(); + this->bulkData.resize(currentSize + count); + std::memcpy((void*)&this->bulkData[currentSize], src, count); + } + + return count; + } +}; +} // namespace + +CesiumPhysxMesh createPhysxMesh(const gsl::span& bulkData) { + CesiumPhysxMesh result; + + CesiumPxInputStream inputStream(bulkData); + result.pTriMesh.Reset(GPhysXSDK->createTriangleMesh(inputStream)); + // TODO: uvs, if needed + // result.uvInfo.FillFromTriMesh(result.pTriMesh); + + return result; +} + +std::vector cookPhysxMesh( + IPhysXCookingModule* pPhysXCookingModule, + const TArray& vertexData, + const TArray& indices) { + if (!pPhysXCookingModule) { + return {}; + } + // bool copyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults; + + PxTriangleMeshDesc mesh; + mesh.triangles.count = indices.Num() / 3; + // A "triangle" here is 3 uint32_t indices, so 12 byte stride. + mesh.triangles.stride = 12; + mesh.triangles.data = (void*)&indices[0]; + mesh.points.count = vertexData.Num(); + mesh.points.stride = sizeof(FStaticMeshBuildVertex); + mesh.points.data = (void*)&vertexData[0]; + // Material indices are optional. + mesh.materialIndices.data = nullptr; + mesh.materialIndices.stride = 0; + + // Since our meshes switched from a right-handed to + // left-handed CRS. + // TODO: ?? + mesh.flags = PxMeshFlag::eFLIPNORMALS; + + // TODO: + // consider disabling active edge pre-compute, faster cooking, slower + // contact generation + + PxCooking* pCooking = pPhysXCookingModule->GetPhysXCooking()->GetCooking(); + PxCookingParams oldParams = pCooking->getParams(); + PxCookingParams newParams = oldParams; + newParams.targetPlatform = PxPlatform::ePC; + newParams.suppressTriangleMeshRemapTable = + UPhysicsSettings::Get()->bSuppressFaceRemapTable; + pCooking->setParams(newParams); + + CesiumPxOutputStream outputStream; + PxTriangleMeshCookingResult::Enum cookResult; + pCooking->cookTriangleMesh(mesh, outputStream, &cookResult); + check(cookResult == PxTriangleMeshCookingResult::eSUCCESS); + + pCooking->setParams(oldParams); + return std::move(outputStream.bulkData); +} + +void BuildPhysXTriangleMeshes( + PxTriangleMesh*& pCollisionMesh, + FBodySetupUVInfo& uvInfo, + IPhysXCookingModule* pPhysXCookingModule, + const TArray& vertexData, + const TArray& indices) { + + if (pPhysXCookingModule) { + // TODO: use PhysX interface directly so we don't need to copy the + // vertices (it takes a stride parameter). + + FPhysXCookHelper cookHelper(pPhysXCookingModule); + + bool copyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults; + + cookHelper.CookInfo.TriMeshCookFlags = EPhysXMeshCookFlags::Default; + cookHelper.CookInfo.OuterDebugName = "CesiumGltfComponent"; + cookHelper.CookInfo.TriangleMeshDesc.bFlipNormals = true; + cookHelper.CookInfo.bCookTriMesh = true; + cookHelper.CookInfo.bSupportFaceRemap = true; + cookHelper.CookInfo.bSupportUVFromHitResults = copyUVs; + + TArray& vertices = cookHelper.CookInfo.TriangleMeshDesc.Vertices; + vertices.SetNum(vertexData.Num()); + for (size_t i = 0; i < vertexData.Num(); ++i) { + vertices[i] = vertexData[i].Position; + } + + if (copyUVs) { + TArray>& uvs = cookHelper.CookInfo.TriangleMeshDesc.UVs; + uvs.SetNum(8); + + for (size_t i = 0; i < 8; ++i) { + uvs[i].SetNum(vertices.Num()); + } + for (size_t i = 0; i < vertexData.Num(); ++i) { + for (size_t j = 0; j < 8; ++j) { + uvs[j][i] = vertexData[i].UVs[j]; + } + } + } + + TArray& physicsIndices = + cookHelper.CookInfo.TriangleMeshDesc.Indices; + physicsIndices.SetNum(indices.Num() / 3); + + for (size_t i = 0; i < indices.Num() / 3; ++i) { + physicsIndices[i].v0 = indices[3 * i]; + physicsIndices[i].v1 = indices[3 * i + 1]; + physicsIndices[i].v2 = indices[3 * i + 2]; + } + + cookHelper.CreatePhysicsMeshes_Concurrent(); + if (cookHelper.OutTriangleMeshes.Num() > 0) { + pCollisionMesh = cookHelper.OutTriangleMeshes[0]; + } + if (copyUVs) { + uvInfo = std::move(cookHelper.OutUVInfo); + } + } +} + +#else +template +static void fillTriangles( + TArray>& triangles, + const TArray& vertexData, + const TArray& indices, + int32 triangleCount) { + + triangles.Reserve(triangleCount); + + for (int32 i = 0; i < triangleCount; ++i) { + const int32 index0 = 3 * i; + triangles.Add(Chaos::TVector( + indices[index0 + 1], + indices[index0], + indices[index0 + 2])); + } +} + +TSharedPtr +BuildChaosTriangleMeshes( + const TArray& vertexData, + const TArray& indices) { + + int32 vertexCount = vertexData.Num(); + int32 triangleCount = indices.Num() / 3; + + Chaos::TParticles vertices; + vertices.AddParticles(vertexCount); + + for (int32 i = 0; i < vertexCount; ++i) { + vertices.X(i) = vertexData[i].Position; + } + + TArray materials; + materials.SetNum(triangleCount); + + TArray faceRemap; + faceRemap.SetNum(triangleCount); + + for (int32 i = 0; i < triangleCount; ++i) { + faceRemap[i] = i; + } + + TUniquePtr> pFaceRemap = MakeUnique>(faceRemap); + + if (vertexCount < TNumericLimits::Max()) { + TArray> triangles; + fillTriangles(triangles, vertexData, indices, triangleCount); + return MakeShared( + MoveTemp(vertices), + MoveTemp(triangles), + MoveTemp(materials), + MoveTemp(pFaceRemap), + nullptr, + false); + } else { + TArray> triangles; + fillTriangles(triangles, vertexData, indices, triangleCount); + return MakeShared( + MoveTemp(vertices), + MoveTemp(triangles), + MoveTemp(materials), + MoveTemp(pFaceRemap), + nullptr, + false); + } +} +#endif +} // namespace CesiumPhysicsUtility diff --git a/Source/CesiumRuntime/Private/CesiumPhysicsUtility.h b/Source/CesiumRuntime/Private/CesiumPhysicsUtility.h new file mode 100644 index 000000000..f74928162 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumPhysicsUtility.h @@ -0,0 +1,67 @@ +// Copyright 2020-2021 CesiumGS, Inc. and Contributors + +#pragma once + +#include "Containers/Array.h" +#include "PhysicsEngine/BodySetup.h" +#include "PhysicsEngine/PhysicsSettings.h" +#include "Templates/SharedPointer.h" +#include "Templates/UniquePtr.h" + +#if PHYSICS_INTERFACE_PHYSX +#include "IPhysXCooking.h" +#include "IPhysXCookingModule.h" +#include "Interfaces/Interface_CollisionDataProvider.h" +#include "PhysXCookHelper.h" +#include "PhysicsPublicCore.h" +#include "PxCollection.h" +#include "PxCooking.h" +#include "PxIO.h" +#include "PxPhysics.h" +#include "PxSerialFramework.h" +#include "PxSerialization.h" +#include +#else +#include "Chaos/AABBTree.h" +#include "Chaos/CollisionConvexMesh.h" +#include "Chaos/TriangleMeshImplicitObject.h" +#endif + +#include + +#include +#include + +namespace CesiumPhysicsUtility { +#if PHYSICS_INTERFACE_PHYSX +struct PxTriangleMeshDeleter { + void operator()(PxTriangleMesh* pCollisionMesh) { + if (pCollisionMesh) { + pCollisionMesh->release(); + } + } +}; + +struct CesiumPhysxMesh { + TUniquePtr pTriMesh = nullptr; + FBodySetupUVInfo uvInfo{}; +}; + +CesiumPhysxMesh createPhysxMesh(const gsl::span& bulkData); +std::vector cookPhysxMesh( + IPhysXCookingModule* pPhysXCookingModule, + const TArray& vertexData, + const TArray& indices); +void BuildPhysXTriangleMeshes( + PxTriangleMesh*& pCollisionMesh, + FBodySetupUVInfo& uvInfo, + IPhysXCookingModule* pPhysXCookingModule, + const TArray& vertexData, + const TArray& indices); +#else +TSharedPtr +BuildChaosTriangleMeshes( + const TArray& vertexData, + const TArray& indices); +#endif +} // namespace CesiumPhysicsUtility diff --git a/Source/CesiumRuntime/Private/CreateGltfOptions.h b/Source/CesiumRuntime/Private/CreateGltfOptions.h index 8a423fc21..d9d90fc37 100644 --- a/Source/CesiumRuntime/Private/CreateGltfOptions.h +++ b/Source/CesiumRuntime/Private/CreateGltfOptions.h @@ -10,12 +10,21 @@ #include "LoadGltfResult.h" #if PHYSICS_INTERFACE_PHYSX #include "IPhysXCookingModule.h" +#include #endif +#include + +#include +#include +#include + // TODO: internal documentation namespace CreateGltfOptions { struct CreateModelOptions { CesiumGltf::Model* pModel = nullptr; + gsl::span derivedDataCache{}; + std::vector> preCookedPhysicsMeshes{}; const FMetadataDescription* pEncodedMetadataDescription = nullptr; bool alwaysIncludeTangents = false; #if PHYSICS_INTERFACE_PHYSX @@ -36,6 +45,7 @@ struct CreateMeshOptions { }; struct CreatePrimitiveOptions { + uint32_t primitiveIndex = 0; const CreateMeshOptions* pMeshOptions = nullptr; const LoadGltfResult::LoadMeshResult* pHalfConstructedMeshResult = nullptr; const CesiumGltf::MeshPrimitive* pPrimitive = nullptr; diff --git a/Source/CesiumRuntime/Private/LoadGltfResult.h b/Source/CesiumRuntime/Private/LoadGltfResult.h index dccdbee2f..097cec824 100644 --- a/Source/CesiumRuntime/Private/LoadGltfResult.h +++ b/Source/CesiumRuntime/Private/LoadGltfResult.h @@ -8,6 +8,7 @@ #include "CesiumGltf/Model.h" #include "CesiumMetadataModel.h" #include "CesiumMetadataPrimitive.h" +#include "CesiumPhysicsUtility.h" #include "CesiumRasterOverlays.h" #include "CesiumTextureUtility.h" #include "Containers/Map.h" @@ -16,54 +17,100 @@ #include "Templates/SharedPointer.h" #include #include +#include #include #include #include +#include -#if PHYSICS_INTERFACE_PHYSX -#include "IPhysXCooking.h" -#include "PhysicsEngine/BodySetup.h" -#include - -struct PxTriangleMeshDeleter { - void operator()(PxTriangleMesh* pCollisionMesh) { - if (pCollisionMesh) { - pCollisionMesh->release(); - } - } -}; -#else -#include "Chaos/TriangleMeshImplicitObject.h" -#endif - -// TODO: internal documentation namespace LoadGltfResult { struct LoadPrimitiveResult { + /** + * @brief Accessor for primitive-level metadata. + */ FCesiumMetadataPrimitive Metadata{}; + + /** + * @brief Render resources for primitive-level metadata - used for GPU + * metadata styling. + */ CesiumEncodedMetadataUtility::EncodedMetadataPrimitive EncodedMetadata{}; + + /** + * @brief Texture coordinate set paramaters needed for metadata styling. + * These may contain feature ID attributes packed into texture coordinates or + * texture coordinates corresponding to metadata textures. + * + * This maps the uniform parameter name to the index of the corresponding + * texture coordinate set within the static mesh. + * + * See CesiumGltfComponent::SetMetadataParameterValues for implementation + * details and documentation on how these paramater names are constructed. + */ TMap metadataTextureCoordinateParameters; + + /** + * @brief Render resource for this primitive's static mesh. + */ TUniquePtr RenderData = nullptr; + + // Stashed pointers to sections of the in-memory glTF corresponding to this + // primitive. + // TODO: Investigate if saving these between worker thread loading and main + // thread loading is completely safe. + const CesiumGltf::Model* pModel = nullptr; const CesiumGltf::MeshPrimitive* pMeshPrimitive = nullptr; const CesiumGltf::Material* pMaterial = nullptr; + + /** + * @brief Double-precision transform of this primitive in ECEF. + */ glm::dmat4x4 transform{1.0}; + + /** + * @brief Cooked physics mesh buffer to cache, corresponding to this + * primitive. + * + * Might be PhysX or Chaos data. + */ + std::vector cookedPhysicsMesh{}; + #if PHYSICS_INTERFACE_PHYSX - TUniquePtr pCollisionMesh; - FBodySetupUVInfo uvInfo{}; + /** + * @brief Created PhysX mesh for this primitive. + */ + CesiumPhysicsUtility::CesiumPhysxMesh physxMesh{}; #else + + /** + * @brief Created Chaos mesh for this primitive. + */ TSharedPtr pCollisionMesh = nullptr; #endif + + /** + * @brief The name string that will be used for this primitive component + * within Unreal. + */ std::string name{}; + // Render resources for the base glTF material textures. + TUniquePtr baseColorTexture; TUniquePtr metallicRoughnessTexture; TUniquePtr normalTexture; TUniquePtr emissiveTexture; TUniquePtr occlusionTexture; + + // Render resources and UV scaling / translation info for the water mask. + // Only applies to glTFs created from quantized mesh tiles with the water + // mask extension. + // TODO: wrap these into some sort of struct. + TUniquePtr waterMaskTexture; - std::unordered_map textureCoordinateParameters; bool onlyLand = true; bool onlyWater = false; @@ -72,7 +119,33 @@ struct LoadPrimitiveResult { double waterMaskTranslationY = 0.0; double waterMaskScale = 1.0; + /** + * @brief Texture coordinate set paramaters needed for the base glTF material + * textures and the water mask texture. + * + * This maps the uniform parameter name to the index of the corresponding + * texture coordinate set within the static mesh. + */ + std::unordered_map textureCoordinateParameters; + + /** + * @brief Texture coordinate sets needed for attached raster overlays. + * + * This maps the overlay ID (e.g., overlay 0 is the glTF primitive attribute + * with the name _CESIUMOVERLAY_0) to the index of the corresponding texture + * coordinate set within the final static mesh. + */ OverlayTextureCoordinateIDMap overlayTextureCoordinateIDToUVIndex{}; + + /** + * @brief Texture coordinate sets needed for base glTF material textures, + * metadata textures, metadata attributes (packed into UV coordinates), the + * water mask, and raster overlays. + * + * This maps the index of the wanted glTF attribute within + * meshPrimitive.attributes list to the index of the corresponding texture + * coordinate set within the final static mesh. + */ std::unordered_map textureCoordinateMap; }; @@ -86,7 +159,16 @@ struct LoadNodeResult { struct LoadModelResult { std::vector nodeResults{}; + + /** + * @brief Accessor for model-level metadata. + */ FCesiumMetadataModel Metadata{}; + + /** + * @brief Render resources for model-level metadata - used for GPU metadata + * styling. + */ CesiumEncodedMetadataUtility::EncodedMetadata EncodedMetadata{}; }; } // namespace LoadGltfResult diff --git a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp index e8b35bbe2..9c4a8658d 100644 --- a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp +++ b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp @@ -122,7 +122,8 @@ CesiumAsync::Future> UnrealAssetAccessor::get( const CesiumAsync::AsyncSystem& asyncSystem, const std::string& url, - const std::vector& headers) { + const std::vector& headers, + bool /*writeThrough*/) { CESIUM_TRACE_BEGIN_IN_TRACK("requestAsset"); diff --git a/Source/CesiumRuntime/Public/UnrealAssetAccessor.h b/Source/CesiumRuntime/Public/UnrealAssetAccessor.h index c24314bdd..da463d7a1 100644 --- a/Source/CesiumRuntime/Public/UnrealAssetAccessor.h +++ b/Source/CesiumRuntime/Public/UnrealAssetAccessor.h @@ -16,8 +16,8 @@ class CESIUMRUNTIME_API UnrealAssetAccessor virtual CesiumAsync::Future> get(const CesiumAsync::AsyncSystem& asyncSystem, const std::string& url, - const std::vector& headers) - override; + const std::vector& headers, + bool writeThrough) override; virtual CesiumAsync::Future> request( diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 96ffb309c..1df052191 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -68,7 +68,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zp8") endif() -option(CESIUM_USE_UNREAL_TRACING "Whether to trace cesium-native events using Unreal's tracing framework.") +option(CESIUM_USE_UNREAL_TRACING "Whether to trace cesium-native events using Unreal's tracing framework." OFF) if (CESIUM_USE_UNREAL_TRACING) # Trace Cesium Native with Unreal Insights. Need to include Unreal Engine headers when building # Cesium Native. @@ -79,7 +79,7 @@ if (CESIUM_USE_UNREAL_TRACING) list(APPEND CESIUM_EXTRA_INCLUDES "${UNREAL_ENGINE_DIR}/Engine/Source/Runtime/TraceLog/Public") list(APPEND CESIUM_EXTRA_INCLUDES "${UNREAL_ENGINE_DIR}/Engine/Source/Runtime/Core/Public") # Change this line depending on where your project's PCH is. - list(APPEND CESIUM_EXTRA_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../Intermediate/Build/Win64/UnrealEditor/Development/CesiumRuntime") + list(APPEND CESIUM_EXTRA_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../Intermediate/Build/Win64/UE4Editor/Development/CesiumRuntime") # Let Cesium Native know that we are tracing and overriding the default tracing macros. add_compile_definitions(CESIUM_TRACING_ENABLED) add_compile_definitions(CESIUM_OVERRIDE_TRACING) diff --git a/extern/cesium-native b/extern/cesium-native index 6dc07d91e..c90292979 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 6dc07d91ea7e97b1446d9cd3d3115ffac8f2ecba +Subproject commit c90292979f899c8ce3e0f87c99a1928922f8df55