From 5c4d926ef1af29cc813882acc2c69cc95f17bfbc Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 16 Aug 2024 14:25:47 -0400 Subject: [PATCH 01/68] Tests passing. --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 4 +- .../Private/CesiumEncodedFeaturesMetadata.cpp | 12 +- .../Private/CesiumEncodedMetadataUtility.cpp | 12 +- .../Private/CesiumGltfComponent.cpp | 123 +++++------------ .../Private/CesiumTextureResource.cpp | 2 +- .../Private/CesiumTextureResource.h | 3 +- .../Private/CesiumTextureUtility.cpp | 84 +++++------ .../Private/CesiumTextureUtility.h | 17 ++- .../Private/Tests/Cesium3DTileset.spec.cpp | 30 ++++ .../Tests/CesiumFeatureIdTexture.spec.cpp | 12 +- .../Private/Tests/CesiumGltfSpecUtility.cpp | 10 +- .../Private/Tests/CesiumGltfSpecUtility.h | 14 +- .../Tests/CesiumTextureUtility.spec.cpp | 130 ++++-------------- extern/cesium-native | 2 +- 14 files changed, 182 insertions(+), 273 deletions(-) create mode 100644 Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 26ec2cb82..a75563ad1 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -19,6 +19,7 @@ #include "CesiumGeospatial/GlobeTransforms.h" #include "CesiumGltf/ImageCesium.h" #include "CesiumGltf/Ktx2TranscodeTargets.h" +#include "CesiumGltf/SharedAssetDepot.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPointsSceneProxyUpdater.h" #include "CesiumGltfPrimitiveComponent.h" @@ -792,8 +793,7 @@ class UnrealResourcePreparer pOptions->group, // TODO: sRGB should probably be configurable on the raster overlay. true, - std::nullopt, - nullptr); + std::nullopt); return texture.Release(); } diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index d419da552..97e53942b 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -153,8 +153,7 @@ std::optional encodeFeatureIdTexture( TEXTUREGROUP_8BitData, false, // TODO: currently this is always the case, but doesn't have to be - EPixelFormat::PF_R8G8B8A8_UINT, - nullptr))); + EPixelFormat::PF_R8G8B8A8_UINT))); featureIdTextureMap.Emplace( pFeatureIdImage, encodedFeatureIdTexture.pTexture); @@ -568,16 +567,16 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( encodedFormat.bytesPerChannel * encodedFormat.channels); } + CesiumGltf::ImageCesium imageCopy(image); encodedProperty.pTexture = loadTextureAnyThreadPart( - image, + imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, false, TEXTUREGROUP_8BitData, false, - encodedFormat.format, - nullptr); + encodedFormat.format); } if (pDescription->PropertyDetails.bHasOffset) { @@ -706,8 +705,7 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( false, // This assumes that the texture's image only contains one byte // per channel. - EPixelFormat::PF_R8G8B8A8_UINT, - nullptr))); + EPixelFormat::PF_R8G8B8A8_UINT))); propertyTexturePropertyMap.Emplace(pImage, encodedProperty.pTexture); } }; diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index 53fe9a576..4ec4068f8 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -277,16 +277,16 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } + CesiumGltf::ImageCesium imageCopy(image); encodedProperty.pTexture = loadTextureAnyThreadPart( - image, + imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, false, TEXTUREGROUP_8BitData, false, - encodedFormat.format, - nullptr); + encodedFormat.format); } return encodedFeatureTable; @@ -425,8 +425,7 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( // R8G8B8A8 form, but this does not necessarily need to be the // case in the future. isNormalized ? EPixelFormat::PF_R8G8B8A8 - : EPixelFormat::PF_R8G8B8A8_UINT, - nullptr))); + : EPixelFormat::PF_R8G8B8A8_UINT))); featureTexturePropertyMap.Emplace( pImage, encodedFeatureTextureProperty.pTexture); @@ -529,8 +528,7 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( false, // TODO: currently this is always the case, but doesn't have // to be - EPixelFormat::PF_R8G8B8A8_UINT, - nullptr))); + EPixelFormat::PF_R8G8B8A8_UINT))); featureIdTextureMap.Emplace( pFeatureIdImage, encodedFeatureIdTexture.pTexture); diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 036f52816..f0d6a9dd8 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -401,8 +401,7 @@ template static TUniquePtr loadTexture( CesiumGltf::Model& model, const std::optional& gltfTexture, - bool sRGB, - std::vector& textureResources) { + bool sRGB) { if (!gltfTexture || gltfTexture.value().index < 0 || gltfTexture.value().index >= model.textures.size()) { if (gltfTexture && gltfTexture.value().index >= 0) { @@ -418,18 +417,13 @@ static TUniquePtr loadTexture( int32_t textureIndex = gltfTexture.value().index; CesiumGltf::Texture& texture = model.textures[textureIndex]; - return loadTextureFromModelAnyThreadPart( - model, - texture, - sRGB, - textureResources); + return loadTextureFromModelAnyThreadPart(model, texture, sRGB); } static void applyWaterMask( Model& model, const MeshPrimitive& primitive, - LoadPrimitiveResult& primitiveResult, - std::vector& textureResources) { + LoadPrimitiveResult& primitiveResult) { // Initialize water mask if needed. auto onlyWaterIt = primitive.extras.find("OnlyWater"); auto onlyLandIt = primitive.extras.find("OnlyLand"); @@ -451,11 +445,8 @@ static void applyWaterMask( waterMaskInfo.index = waterMaskTextureId; if (waterMaskTextureId >= 0 && waterMaskTextureId < model.textures.size()) { - primitiveResult.waterMaskTexture = loadTexture( - model, - std::make_optional(waterMaskInfo), - false, - textureResources); + primitiveResult.waterMaskTexture = + loadTexture(model, std::make_optional(waterMaskInfo), false); } } } @@ -911,8 +902,7 @@ static void loadPrimitiveFeaturesMetadata( CesiumGltf::MeshPrimitive& primitive, bool duplicateVertices, TArray& vertices, - const TArray& indices, - std::vector& textureResources) { + const TArray& indices) { ExtensionExtMeshFeatures* pFeatures = primitive.getExtension(); @@ -1130,8 +1120,7 @@ static void loadPrimitive( const CreatePrimitiveOptions& options, const Accessor& positionAccessor, const AccessorView& positionView, - const TIndexAccessor& indicesView, - std::vector& textureResources) { + const TIndexAccessor& indicesView) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) @@ -1272,7 +1261,7 @@ static void loadPrimitive( } } - applyWaterMask(model, primitive, primitiveResult, textureResources); + applyWaterMask(model, primitive, primitiveResult); // The water effect works by animating the normal, and the normal is // expressed in tangent space. So if we have water, we need tangents. @@ -1424,27 +1413,22 @@ static void loadPrimitive( primitive, duplicateVertices, StaticMeshBuildVertices, - indices, - textureResources); + indices); { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) - primitiveResult.baseColorTexture = loadTexture( - model, - pbrMetallicRoughness.baseColorTexture, - true, - textureResources); + primitiveResult.baseColorTexture = + loadTexture(model, pbrMetallicRoughness.baseColorTexture, true); primitiveResult.metallicRoughnessTexture = loadTexture( model, pbrMetallicRoughness.metallicRoughnessTexture, - false, - textureResources); + false); primitiveResult.normalTexture = - loadTexture(model, material.normalTexture, false, textureResources); + loadTexture(model, material.normalTexture, false); primitiveResult.occlusionTexture = - loadTexture(model, material.occlusionTexture, false, textureResources); + loadTexture(model, material.occlusionTexture, false); primitiveResult.emissiveTexture = - loadTexture(model, material.emissiveTexture, true, textureResources); + loadTexture(model, material.emissiveTexture, true); } { @@ -1692,8 +1676,7 @@ static void loadIndexedPrimitive( const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, const Accessor& positionAccessor, - const AccessorView& positionView, - std::vector& textureResources) { + const AccessorView& positionView) { const Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; const MeshPrimitive& primitive = *options.pPrimitive; @@ -1708,8 +1691,7 @@ static void loadIndexedPrimitive( options, positionAccessor, positionView, - indexAccessor, - textureResources); + indexAccessor); primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == @@ -1721,8 +1703,7 @@ static void loadIndexedPrimitive( options, positionAccessor, positionView, - indexAccessor, - textureResources); + indexAccessor); primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == @@ -1734,8 +1715,7 @@ static void loadIndexedPrimitive( options, positionAccessor, positionView, - indexAccessor, - textureResources); + indexAccessor); primitiveResult.IndexAccessor = indexAccessor; } else { UE_LOG( @@ -1749,8 +1729,7 @@ static void loadIndexedPrimitive( static void loadPrimitive( LoadPrimitiveResult& result, const glm::dmat4x4& transform, - const CreatePrimitiveOptions& options, - std::vector& textureResources) { + const CreatePrimitiveOptions& options) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) const Model& model = @@ -1785,16 +1764,14 @@ static void loadPrimitive( options, *pPositionAccessor, positionView, - syntheticIndexBuffer, - textureResources); + syntheticIndexBuffer); } else { loadIndexedPrimitive( result, transform, options, *pPositionAccessor, - positionView, - textureResources); + positionView); } result.PositionAccessor = std::move(positionView); } @@ -1802,8 +1779,7 @@ static void loadPrimitive( static void loadMesh( std::optional& result, const glm::dmat4x4& transform, - const CreateMeshOptions& options, - std::vector& textureResources) { + const CreateMeshOptions& options) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh) @@ -1815,11 +1791,7 @@ static void loadMesh( for (CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { CreatePrimitiveOptions primitiveOptions = {&options, &*result, &primitive}; auto& primitiveResult = result->primitiveResults.emplace_back(); - loadPrimitive( - primitiveResult, - transform, - primitiveOptions, - textureResources); + loadPrimitive(primitiveResult, transform, primitiveOptions); // if it doesn't have render data, then it can't be loaded if (!primitiveResult.RenderData) { @@ -1974,8 +1946,7 @@ static void loadInstancingData( static void loadNode( std::vector& loadNodeResults, const glm::dmat4x4& transform, - const CreateNodeOptions& options, - std::vector& textureResources) { + const CreateNodeOptions& options) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadNode) @@ -2055,7 +2026,7 @@ static void loadNode( loadInstancingData(model, result, pGpuInstancingExtension); } CreateMeshOptions meshOptions = {&options, &result, &model.meshes[meshId]}; - loadMesh(result.meshResult, nodeTransform, meshOptions, textureResources); + loadMesh(result.meshResult, nodeTransform, meshOptions); } for (int childNodeId : node.children) { @@ -2064,11 +2035,7 @@ static void loadNode( options.pModelOptions, options.pHalfConstructedModelResult, &model.nodes[childNodeId]}; - loadNode( - loadNodeResults, - nodeTransform, - childNodeOptions, - textureResources); + loadNode(loadNodeResults, nodeTransform, childNodeOptions); } } } @@ -2214,8 +2181,7 @@ loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { static void loadModelAnyThreadPart( LoadModelResult& result, const glm::dmat4x4& transform, - const CreateModelOptions& options, - std::vector& textureResources) { + const CreateModelOptions& options) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) Model& model = *options.pModel; @@ -2249,11 +2215,11 @@ static void loadModelAnyThreadPart( continue; Image* pImage = model.getSafe(&model.images, texture.source); - if (!pImage || pImage->cesium.pixelData.empty()) + if (!pImage || pImage->cesium->pixelData.empty()) continue; std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(pImage->cesium); + CesiumGltfReader::GltfReader::generateMipMaps(*pImage->cesium); if (errorMessage) { UE_LOG( LogCesium, @@ -2278,27 +2244,19 @@ static void loadModelAnyThreadPart( const Scene& defaultScene = model.scenes[model.scene]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = {&options, &result, &model.nodes[nodeId]}; - loadNode( - result.nodeResults, - rootTransform, - nodeOptions, - textureResources); + loadNode(result.nodeResults, 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, - textureResources); + loadNode(result.nodeResults, 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, textureResources); + loadNode(result.nodeResults, rootTransform, nodeOptions); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. for (Mesh& mesh : model.meshes) { @@ -2308,11 +2266,7 @@ static void loadModelAnyThreadPart( &dummyNodeOptions, &dummyNodeResult, &mesh}; - loadMesh( - dummyNodeResult.meshResult, - rootTransform, - meshOptions, - textureResources); + loadMesh(dummyNodeResult.meshResult, rootTransform, meshOptions); } } } @@ -3446,15 +3400,8 @@ static void loadPrimitiveGameThreadPart( UCesiumGltfComponent::CreateOffGameThread( const glm::dmat4x4& Transform, const CreateModelOptions& Options) { - std::vector textureResources; - textureResources.resize(Options.pModel->images.size(), nullptr); - auto pResult = MakeUnique(); - loadModelAnyThreadPart( - pResult->loadModelResult, - Transform, - Options, - textureResources); + loadModelAnyThreadPart(pResult->loadModelResult, Transform, Options); return pResult; } diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index 1bbeb5a59..cbfe302a5 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -285,7 +285,7 @@ FTextureRHIRef FCesiumUseExistingTextureResource::InitializeTextureRHI() { } FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium&& image, + CesiumGltf::ImageCesium& image, TextureGroup textureGroup, uint32 width, uint32 height, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index 1ee0e5889..91a47b5ee 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -6,6 +6,7 @@ #include "Engine/Texture.h" #include "TextureResource.h" #include +#include /** * The base class for Cesium texture resources, making Cesium's texture data @@ -110,7 +111,7 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResourceBase { class FCesiumCreateNewTextureResource : public FCesiumTextureResourceBase { public: FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium&& image, + CesiumGltf::ImageCesium& image, TextureGroup textureGroup, uint32 width, uint32 height, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 5aceb4bb4..f7e084adb 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -139,6 +139,15 @@ struct ExtensionUnrealTexture { pTexture; }; +struct ExtensionUnrealTextureResource { + static inline constexpr const char* TypeName = + "ExtensionUnrealTextureResource"; + static inline constexpr const char* ExtensionName = + "PRIVATE_unreal_texture_resource"; + + FCesiumTextureResourceBase* pTextureResource; +}; + } // namespace namespace CesiumTextureUtility { @@ -198,13 +207,20 @@ void ReferenceCountedUnrealTexture::setTextureResource( this->_pTextureResource = std::move(p); } +CesiumGltf::SharedAsset +ReferenceCountedUnrealTexture::getSharedImage() const { + return this->_pImageCesium; +} + +void ReferenceCountedUnrealTexture::setSharedImage( + CesiumGltf::SharedAsset& image) { + this->_pImageCesium = image; +} + TUniquePtr loadTextureFromModelAnyThreadPart( CesiumGltf::Model& model, CesiumGltf::Texture& texture, - bool sRGB, - std::vector& textureResources) { - check(textureResources.size() == model.images.size()); - + bool sRGB) { int64_t textureIndex = model.textures.empty() ? -1 : &texture - &model.textures[0]; if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { @@ -213,13 +229,12 @@ TUniquePtr loadTextureFromModelAnyThreadPart( ExtensionUnrealTexture& extension = texture.addExtension(); - if (extension.pTexture && (extension.pTexture->getUnrealTexture() || extension.pTexture->getTextureResource())) { // There's an existing Unreal texture for this glTF texture. This will - // happen if this texture is used by multiple primitives on the same model. - // It will also be the case when this model was upsampled from a parent - // tile. + // happen if this texture is used by multiple primitives on the same + // model. It will also be the case when this model was upsampled from a + // parent tile. TUniquePtr pResult = MakeUnique(); pResult->pTexture = extension.pTexture; pResult->textureIndex = textureIndex; @@ -273,44 +288,25 @@ TUniquePtr loadTextureFromModelAnyThreadPart( } CesiumGltf::Image& image = model.images[source]; - const CesiumGltf::ImageCesium& imageCesium = image.cesium; const CesiumGltf::Sampler& sampler = model.getSafe(model.samplers, texture.sampler); - FCesiumTextureResourceBase* pExistingImageResource = nullptr; - - if (image.cesium.pixelData.empty() && source >= 0 && - source < textureResources.size()) { - // An RHI texture has already been created for this image; reuse it. - pExistingImageResource = textureResources[source]; - } - TUniquePtr pResult = - loadTextureFromImageAndSamplerAnyThreadPart( - image, - sampler, - sRGB, - pExistingImageResource); + loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); + if (pResult) { extension.pTexture = pResult->pTexture; // Note the index of this texture within the glTF. pResult->textureIndex = textureIndex; - - if (source >= 0 && source < textureResources.size()) { - // Make the RHI resource known so it can be used by other textures that - // reference this same image. - textureResources[source] = pResult->pTexture->getTextureResource().Get(); - } } return pResult; } TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::Image& image, + CesiumGltf::SharedAsset& image, const CesiumGltf::Sampler& sampler, - bool sRGB, - FCesiumTextureResourceBase* pExistingImageResource) { + bool sRGB) { TextureAddress addressX = convertGltfWrapSToUnreal(sampler.wrapS); TextureAddress addressY = convertGltfWrapTToUnreal(sampler.wrapT); @@ -369,8 +365,8 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( break; } - return loadTextureAnyThreadPart( - image.cesium, + auto loadResult = loadTextureAnyThreadPart( + *image, addressX, addressY, filter, @@ -378,8 +374,13 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( // TODO: allow texture group to be configured on Cesium3DTileset. TEXTUREGROUP_World, sRGB, - std::nullopt, - pExistingImageResource); + std::nullopt); + + if (loadResult != nullptr) { + loadResult->pTexture->setSharedImage(image); + } + + return loadResult; } static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { @@ -419,8 +420,7 @@ TUniquePtr loadTextureAnyThreadPart( bool useMipMapsIfAvailable, TextureGroup group, bool sRGB, - std::optional overridePixelFormat, - FCesiumTextureResourceBase* pExistingImageResource) { + std::optional overridePixelFormat) { EPixelFormat pixelFormat; if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { switch (imageCesium.compressedPixelFormat) { @@ -492,10 +492,13 @@ TUniquePtr loadTextureAnyThreadPart( // for caching purposes. imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - if (pExistingImageResource) { + ExtensionUnrealTextureResource& extension = + imageCesium.addExtension(); + + if (extension.pTextureResource != nullptr) { pResult->pTexture->setTextureResource( MakeUnique( - pExistingImageResource, + extension.pTextureResource, group, imageCesium.width, imageCesium.height, @@ -544,7 +547,7 @@ TUniquePtr loadTextureAnyThreadPart( pResult->pTexture->setTextureResource( MakeUnique( - std::move(imageCesium), + imageCesium, group, imageCesium.width, imageCesium.height, @@ -558,6 +561,7 @@ TUniquePtr loadTextureAnyThreadPart( } check(pResult->pTexture->getTextureResource() != nullptr); + extension.pTextureResource = pResult->pTexture->getTextureResource().Get(); return pResult; } diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 2592dabc3..bc657eaa4 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -3,6 +3,7 @@ #pragma once #include "CesiumGltf/Model.h" +#include "CesiumGltf/SharedAssetDepot.h" #include "CesiumMetadataValueType.h" #include "CesiumTextureResource.h" #include "Engine/Texture.h" @@ -50,9 +51,14 @@ struct ReferenceCountedUnrealTexture TUniquePtr& getTextureResource(); void setTextureResource(TUniquePtr&& p); + /// The SharedAsset that this texture was created from. + CesiumGltf::SharedAsset getSharedImage() const; + void setSharedImage(CesiumGltf::SharedAsset& image); + private: TObjectPtr _pUnrealTexture; TUniquePtr _pTextureResource; + CesiumGltf::SharedAsset _pImageCesium; }; /** @@ -97,8 +103,7 @@ struct LoadedTextureResult { TUniquePtr loadTextureFromModelAnyThreadPart( CesiumGltf::Model& model, CesiumGltf::Texture& texture, - bool sRGB, - std::vector& textureResources); + bool sRGB); /** * Does the asynchronous part of renderer resource preparation for a glTF @@ -117,10 +122,9 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * and can be empty. */ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::Image& image, + CesiumGltf::SharedAsset& image, const CesiumGltf::Sampler& sampler, - bool sRGB, - FCesiumTextureResourceBase* pExistingImageResource); + bool sRGB); /** * @brief Does the asynchronous part of renderer resource preparation for @@ -153,8 +157,7 @@ TUniquePtr loadTextureAnyThreadPart( bool useMipMapsIfAvailable, TextureGroup group, bool sRGB, - std::optional overridePixelFormat, - FCesiumTextureResourceBase* pExistingImageResource); + std::optional overridePixelFormat); /** * @brief Does the main-thread part of render resource preparation for this diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp new file mode 100644 index 000000000..a9d3d53af --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -0,0 +1,30 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#include "Cesium3DTileset.h" +#include "CesiumTestHelpers.h" +#include "Engine/World.h" +#include "Misc/AutomationTest.h" + +BEGIN_DEFINE_SPEC( + FCesium3DTilesetSpec, + "Cesium.Unit.3DTileset", + EAutomationTestFlags::ApplicationContextMask | + EAutomationTestFlags::ProductFilter); +END_DEFINE_SPEC(FCesium3DTilesetSpec); + +void FCesium3DTilesetSpec::Define() { + Describe("SharedImages", [this]() { + It("should only load two textures", [this]() { + UWorld* World = GEditor->PlayWorld; + check(World); + + ACesium3DTileset* tileset = World->SpawnActor(); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetUrl(FString::Printf( + TEXT( + "file:///%s/cesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages"), + *FPaths::ProjectPluginsDir())); + tileset->SetActorLabel(TEXT("SharedImages")); + }); + }); +} diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 4daa30cda..9ff986f6e 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -136,9 +136,9 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { Image& image = model.images.emplace_back(); - image.cesium.width = image.cesium.height = 1; - image.cesium.channels = 1; - image.cesium.pixelData.push_back(std::byte(42)); + image.cesium->width = image.cesium->height = 1; + image.cesium->channels = 1; + image.cesium->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; @@ -175,9 +175,9 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with invalid texcoord accessor", [this]() { Image& image = model.images.emplace_back(); - image.cesium.width = image.cesium.height = 1; - image.cesium.channels = 1; - image.cesium.pixelData.push_back(std::byte(42)); + image.cesium->width = image.cesium->height = 1; + image.cesium->channels = 1; + image.cesium->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp index 45c4d8149..47cb9a9d7 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp @@ -70,12 +70,12 @@ CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel( const int32_t samplerWrapS, const int32_t samplerWrapT) { CesiumGltf::Image& image = model.images.emplace_back(); - image.cesium.bytesPerChannel = 1; - image.cesium.channels = 1; - image.cesium.width = imageWidth; - image.cesium.height = imageHeight; + image.cesium->bytesPerChannel = 1; + image.cesium->channels = 1; + image.cesium->width = imageWidth; + image.cesium->height = imageHeight; - std::vector& data = image.cesium.pixelData; + std::vector& data = image.cesium->pixelData; data.resize(imageWidth * imageHeight); std::memcpy(data.data(), featureIDs.data(), data.size()); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h index b5dd72df8..325b85c25 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h @@ -200,15 +200,15 @@ CesiumGltf::PropertyTextureProperty& AddPropertyTexturePropertyToModel( classProperty.componentType = componentType; CesiumGltf::Image& image = model.images.emplace_back(); - image.cesium.width = 2; - image.cesium.height = 2; - image.cesium.channels = sizeof(T); - image.cesium.bytesPerChannel = 1; - image.cesium.pixelData.resize(values.size() * sizeof(T)); + image.cesium->width = 2; + image.cesium->height = 2; + image.cesium->channels = sizeof(T); + image.cesium->bytesPerChannel = 1; + image.cesium->pixelData.resize(values.size() * sizeof(T)); std::memcpy( - image.cesium.pixelData.data(), + image.cesium->pixelData.data(), values.data(), - image.cesium.pixelData.size()); + image.cesium->pixelData.size()); CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 10b8ba83b..b6204f49d 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -15,7 +15,7 @@ BEGIN_DEFINE_SPEC( EAutomationTestFlags::ProductFilter | EAutomationTestFlags::NonNullRHI) std::vector originalPixels; std::vector originalMipPixels; -ImageCesium imageCesium; +SharedAsset imageCesium; void RunTests(); @@ -44,18 +44,18 @@ void CesiumTextureUtilitySpec::Define() { 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; originalMipPixels.clear(); - imageCesium = {}; - imageCesium.width = 3; - imageCesium.height = 2; + imageCesium = SharedAsset(ImageCesium{}); + imageCesium->width = 3; + imageCesium->height = 2; TestEqual( "image buffer size is correct", originalPixels.size(), - imageCesium.width * imageCesium.height * imageCesium.bytesPerChannel * - imageCesium.channels); - imageCesium.pixelData.resize(originalPixels.size()); + imageCesium->width * imageCesium->height * + imageCesium->bytesPerChannel * imageCesium->channels); + imageCesium->pixelData.resize(originalPixels.size()); std::memcpy( - imageCesium.pixelData.data(), + imageCesium->pixelData.data(), originalPixels.data(), originalPixels.size()); }); @@ -66,30 +66,30 @@ void CesiumTextureUtilitySpec::Define() { Describe("With Mips", [this]() { BeforeEach([this]() { imageCesium = {}; - imageCesium.width = 3; - imageCesium.height = 2; + imageCesium->width = 3; + imageCesium->height = 2; // Original image (3x2) originalPixels = {0x20, 0x40, 0x80, 0xF0, 0x21, 0x41, 0x81, 0xF1, 0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3, 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; - imageCesium.mipPositions.emplace_back( + imageCesium->mipPositions.emplace_back( ImageCesiumMipPosition{0, originalPixels.size()}); // Mip 1 (1x1) originalMipPixels = {0x26, 0x46, 0x86, 0xF6}; - imageCesium.mipPositions.emplace_back(ImageCesiumMipPosition{ - imageCesium.mipPositions[0].byteSize, + imageCesium->mipPositions.emplace_back(ImageCesiumMipPosition{ + imageCesium->mipPositions[0].byteSize, originalMipPixels.size()}); - imageCesium.pixelData.resize( + imageCesium->pixelData.resize( originalPixels.size() + originalMipPixels.size()); std::memcpy( - imageCesium.pixelData.data(), + imageCesium->pixelData.data(), originalPixels.data(), originalPixels.size()); std::memcpy( - imageCesium.pixelData.data() + originalPixels.size(), + imageCesium->pixelData.data() + originalPixels.size(), originalMipPixels.data(), originalMipPixels.size()); }); @@ -101,15 +101,14 @@ void CesiumTextureUtilitySpec::Define() { void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium non-sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - imageCesium, + *imageCesium, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, TextureFilter::TF_Bilinear, true, TextureGroup::TEXTUREGROUP_Cinematic, false, - std::nullopt, - nullptr); + std::nullopt); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); IntrusivePointer pRefCountedTexture = @@ -126,15 +125,14 @@ void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - imageCesium, + *imageCesium, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, TextureFilter::TF_Trilinear, true, TextureGroup::TEXTUREGROUP_Bokeh, true, - std::nullopt, - nullptr); + std::nullopt); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); IntrusivePointer pRefCountedTexture = @@ -150,9 +148,6 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Image and Sampler", [this]() { - Image image; - image.cesium = imageCesium; - Sampler sampler; sampler.minFilter = Sampler::MinFilter::NEAREST; sampler.magFilter = Sampler::MagFilter::NEAREST; @@ -161,10 +156,9 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( - image, + imageCesium, sampler, - false, - nullptr); + false); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); IntrusivePointer pRefCountedTexture = @@ -195,21 +189,10 @@ void CesiumTextureUtilitySpec::RunTests() { texture.source = 0; texture.sampler = 0; - std::vector textureResources; - textureResources.resize(model.images.size(), nullptr); - TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart( - model, - texture, - true, - textureResources); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); - TestEqual( - "textureResources[0]", - textureResources[0], - pHalfLoaded->pTexture->getTextureResource().Get()); IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); @@ -249,34 +232,15 @@ void CesiumTextureUtilitySpec::RunTests() { texture2.source = 0; texture2.sampler = 1; - std::vector textureResources; - textureResources.resize(model.images.size(), nullptr); - TUniquePtr pHalfLoaded1 = - loadTextureFromModelAnyThreadPart( - model, - model.textures[0], - true, - textureResources); + loadTextureFromModelAnyThreadPart(model, model.textures[0], true); TestNotNull("pHalfLoaded1", pHalfLoaded1.Get()); TestNotNull("pHalfLoaded1->pTexture", pHalfLoaded1->pTexture.get()); - TestEqual( - "textureResources[0]", - textureResources[0], - pHalfLoaded1->pTexture->getTextureResource().Get()); TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - model, - model.textures[1], - false, - textureResources); + loadTextureFromModelAnyThreadPart(model, model.textures[1], false); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); - TestEqual( - "textureResources[0]", - textureResources[0], - pHalfLoaded2->pTexture->getTextureResource().Get()); IntrusivePointer pRefCountedTexture1 = loadTextureGameThreadPart(model, pHalfLoaded1.Get()); @@ -325,21 +289,10 @@ void CesiumTextureUtilitySpec::RunTests() { texture.source = 0; texture.sampler = 0; - std::vector textureResources; - textureResources.resize(model.images.size(), nullptr); - TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart( - model, - texture, - true, - textureResources); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); - TestEqual( - "textureResources[0]", - textureResources[0], - pHalfLoaded->pTexture->getTextureResource().Get()); IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); @@ -352,19 +305,12 @@ void CesiumTextureUtilitySpec::RunTests() { CheckFilter(pRefCountedTexture, TextureFilter::TF_Default); CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World); - std::vector textureResources2; - textureResources2.resize(model.images.size(), nullptr); - // Copy the model and load the same texture again. // This time there's no more pixel data, so it's necessary to use the // previously-created texture. Model model2 = model; TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - model2, - model.textures[0], - true, - textureResources2); + loadTextureFromModelAnyThreadPart(model2, model.textures[0], true); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( @@ -392,21 +338,10 @@ void CesiumTextureUtilitySpec::RunTests() { texture.source = 0; texture.sampler = 0; - std::vector textureResources; - textureResources.resize(model.images.size(), nullptr); - TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart( - model, - texture, - true, - textureResources); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); - TestEqual( - "textureResources[0]", - textureResources[0], - pHalfLoaded->pTexture->getTextureResource().Get()); IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); @@ -419,18 +354,11 @@ void CesiumTextureUtilitySpec::RunTests() { CheckFilter(pRefCountedTexture, TextureFilter::TF_Default); CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World); - std::vector textureResources2; - textureResources2.resize(model.images.size(), nullptr); - // Load the same texture again. // This time there's no more pixel data, so it's necessary to use the // previously-created texture. TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - model, - model.textures[0], - true, - textureResources2); + loadTextureFromModelAnyThreadPart(model, model.textures[0], true); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( diff --git a/extern/cesium-native b/extern/cesium-native index 43187540a..e79b31379 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 43187540a5199d3b5fa8693fca90a1b81bf73c80 +Subproject commit e79b31379c22e1e91b321cda06cf44e79ca543d4 From 07486384e54d487f6aaa2ece974f109f972a0989 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 16 Aug 2024 14:42:06 -0400 Subject: [PATCH 02/68] Update cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index e79b31379..8ae21507f 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit e79b31379c22e1e91b321cda06cf44e79ca543d4 +Subproject commit 8ae21507f72045bdb46efb278926d52c3f9cb5e2 From e08581ebc4fbd0d2c4ab3baea41ca3d1c335df7f Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 16 Aug 2024 15:07:49 -0400 Subject: [PATCH 03/68] Update cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index 8ae21507f..001849307 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 8ae21507f72045bdb46efb278926d52c3f9cb5e2 +Subproject commit 0018493071c837977ad9ecbf5ed06cf7958608a4 From 1d85d7b56400c3e42394e985c72f3a3e5b2069eb Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 20 Aug 2024 11:20:09 -0400 Subject: [PATCH 04/68] Start of test for shared images. --- .../Private/Tests/Cesium3DTileset.spec.cpp | 96 ++++++++++++++----- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index a9d3d53af..1813e8f4d 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -1,30 +1,82 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "Cesium3DTileset.h" +#include "CesiumGlobeAnchorComponent.h" +#include "CesiumLoadTestCore.h" +#include "CesiumSceneGeneration.h" +#include "CesiumSunSky.h" #include "CesiumTestHelpers.h" #include "Engine/World.h" +#include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" -BEGIN_DEFINE_SPEC( - FCesium3DTilesetSpec, - "Cesium.Unit.3DTileset", - EAutomationTestFlags::ApplicationContextMask | - EAutomationTestFlags::ProductFilter); -END_DEFINE_SPEC(FCesium3DTilesetSpec); - -void FCesium3DTilesetSpec::Define() { - Describe("SharedImages", [this]() { - It("should only load two textures", [this]() { - UWorld* World = GEditor->PlayWorld; - check(World); - - ACesium3DTileset* tileset = World->SpawnActor(); - tileset->SetTilesetSource(ETilesetSource::FromUrl); - tileset->SetUrl(FString::Printf( - TEXT( - "file:///%s/cesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages"), - *FPaths::ProjectPluginsDir())); - tileset->SetActorLabel(TEXT("SharedImages")); - }); - }); +#define TEST_SCREEN_WIDTH 1280 +#define TEST_SCREEN_HEIGHT 720 + +namespace Cesium { + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FCesium3DTilesetSharedImages, + "Cesium.Unit.3DTileset.SharedImages", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); + +static void setupForSharedImages(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(21.16677692, -67.38013505, -6375355.1944), + FVector(-12, 5, -5), + FRotator(0, 90, 0), + 60.0f); + + context.georeference->SetOriginEarthCenteredEarthFixed(FVector(0, 0, 0)); + context.pawn->SetActorLocation(FVector(-12, 5, -5)); + + context.sunSky->TimeZone = 9.0f; + context.sunSky->UpdateSun(); + + ACesiumGeoreference* georeference = + context.world->SpawnActor(); + check(georeference != nullptr); + georeference->SetOriginPlacement(EOriginPlacement::TrueOrigin); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + // Unreal returns the relative path of the plugins directory by default + FString FullPluginsPath = + IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead( + *FPaths::ProjectPluginsDir()); + tileset->SetUrl(FString::Printf( + TEXT( + "file:///%scesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages/tileset.json"), + *FullPluginsPath)); + + tileset->SetActorLabel(TEXT("SharedImages")); + context.tilesets.push_back(tileset); + + UCesiumGlobeAnchorComponent* anchor = + NewObject(tileset); + anchor->SetGeoreference(georeference); + anchor->RegisterComponent(); + + anchor->MoveToEarthCenteredEarthFixedPosition(FVector::ZeroVector); +} + +void googleSetupRefreshTilesets( + SceneGenerationContext& context, + TestPass::TestingParameter parameter) { + context.refreshTilesets(); } + +bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back( + TestPass{"Warm Cache", googleSetupRefreshTilesets, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForSharedImages, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +} // namespace Cesium From fcb53eaafd4d04e31ab7c82454a565179ca8608f Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 30 Aug 2024 19:22:40 -0400 Subject: [PATCH 05/68] Almost fully asynced glTF loading --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 20 +- .../Private/CesiumEncodedFeaturesMetadata.cpp | 8 +- .../Private/CesiumEncodedMetadataUtility.cpp | 8 +- .../Private/CesiumGltfComponent.cpp | 1603 ++++++++++------- .../Private/CesiumGltfComponent.h | 4 +- .../Private/CesiumTextureUtility.cpp | 687 ++++--- .../Private/CesiumTextureUtility.h | 99 +- .../Private/Tests/Cesium3DTileset.spec.cpp | 29 +- .../Tests/CesiumTextureUtility.spec.cpp | 53 +- extern/cesium-native | 2 +- 10 files changed, 1578 insertions(+), 935 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 86224931f..f56982b28 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -730,16 +730,22 @@ class UnrealResourcePreparer const CesiumGeospatial::Ellipsoid& ellipsoid = tileLoadResult.ellipsoid; - TUniquePtr pHalf = - UCesiumGltfComponent::CreateOffGameThread( + CesiumAsync::Future> + pHalfFuture = UCesiumGltfComponent::CreateOffGameThread( + asyncSystem, transform, options, ellipsoid); - return asyncSystem.createResolvedFuture( - Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(tileLoadResult), - pHalf.Release()}); + return MoveTemp(pHalfFuture) + .thenImmediately( + [tileLoadResult]( + TUniquePtr&& pHalf) + -> Cesium3DTilesSelection::TileLoadResultAndRenderResources { + return Cesium3DTilesSelection::TileLoadResultAndRenderResources{ + std::move(tileLoadResult), + pHalf.Release()}; + }); } virtual void* prepareInMainThread( @@ -808,7 +814,7 @@ class UnrealResourcePreparer } } - auto texture = CesiumTextureUtility::loadTextureAnyThreadPart( + auto texture = CesiumTextureUtility::loadTextureAnyThreadPartSync( image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index 50b7d8293..96e51a046 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -143,7 +143,7 @@ std::optional encodeFeatureIdTexture( // Copy the image, so that we can keep a copy of it in the glTF. CesiumGltf::ImageCesium imageCopy(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = - MakeShared(std::move(*loadTextureAnyThreadPart( + MakeShared(std::move(*loadTextureAnyThreadPartSync( imageCopy, addressX, addressY, @@ -567,7 +567,7 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( } CesiumGltf::ImageCesium imageCopy(image); - encodedProperty.pTexture = loadTextureAnyThreadPart( + encodedProperty.pTexture = loadTextureAnyThreadPartSync( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, @@ -692,8 +692,8 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( // Copy the image, so that we can keep a copy of it in the glTF. CesiumGltf::ImageCesium imageCopy(*pImage); - encodedProperty.pTexture = - MakeShared(std::move(*loadTextureAnyThreadPart( + encodedProperty.pTexture = MakeShared( + std::move(*loadTextureAnyThreadPartSync( imageCopy, addressX, addressY, diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index 97b23d9e8..6dfa1b750 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -277,7 +277,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } CesiumGltf::ImageCesium imageCopy(image); - encodedProperty.pTexture = loadTextureAnyThreadPart( + encodedProperty.pTexture = loadTextureAnyThreadPartSync( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, @@ -411,8 +411,8 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( encodedFeatureTextureProperty.pTexture = pMappedUnrealImageIt->Pin(); } else { CesiumGltf::ImageCesium imageCopy(*pImage); - encodedFeatureTextureProperty.pTexture = - MakeShared(std::move(*loadTextureAnyThreadPart( + encodedFeatureTextureProperty.pTexture = MakeShared( + std::move(*loadTextureAnyThreadPartSync( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, @@ -517,7 +517,7 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( } else { CesiumGltf::ImageCesium imageCopy(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = MakeShared( - std::move(*loadTextureAnyThreadPart( + std::move(*loadTextureAnyThreadPartSync( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 31dc470aa..56f83c529 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #if WITH_EDITOR #include "ScopedTransaction.h" @@ -398,7 +399,10 @@ struct ColorVisitor { }; template -static TUniquePtr loadTexture( +static CesiumAsync::Future< + TUniquePtr> +loadTexture( + const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::Model& model, const std::optional& gltfTexture, bool sRGB) { @@ -412,15 +416,17 @@ static TUniquePtr loadTexture( model.textures.size(), gltfTexture.value().index); } - return nullptr; + return asyncSystem.createResolvedFuture< + TUniquePtr>(nullptr); } int32_t textureIndex = gltfTexture.value().index; CesiumGltf::Texture& texture = model.textures[textureIndex]; - return loadTextureFromModelAnyThreadPart(model, texture, sRGB); + return loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, sRGB); } -static void applyWaterMask( +static CesiumAsync::SharedFuture applyWaterMaskTexture( + const CesiumAsync::AsyncSystem& asyncSystem, Model& model, const MeshPrimitive& primitive, LoadPrimitiveResult& primitiveResult) { @@ -445,8 +451,16 @@ static void applyWaterMask( waterMaskInfo.index = waterMaskTextureId; if (waterMaskTextureId >= 0 && waterMaskTextureId < model.textures.size()) { - primitiveResult.waterMaskTexture = - loadTexture(model, std::make_optional(waterMaskInfo), false); + return loadTexture( + asyncSystem, + model, + std::make_optional(waterMaskInfo), + false) + .thenImmediately([pPrimitiveResult = &primitiveResult]( + TUniquePtr result) { + pPrimitiveResult->waterMaskTexture = MoveTemp(result); + }) + .share(); } } } @@ -455,23 +469,37 @@ static void applyWaterMask( primitiveResult.onlyLand = true; } - auto waterMaskTranslationXIt = primitive.extras.find("WaterMaskTranslationX"); - auto waterMaskTranslationYIt = primitive.extras.find("WaterMaskTranslationY"); - auto waterMaskScaleIt = primitive.extras.find("WaterMaskScale"); + return asyncSystem.createResolvedFuture().share(); +} - if (waterMaskTranslationXIt != primitive.extras.end() && - waterMaskTranslationXIt->second.isDouble() && - waterMaskTranslationYIt != primitive.extras.end() && - waterMaskTranslationYIt->second.isDouble() && - waterMaskScaleIt != primitive.extras.end() && - waterMaskScaleIt->second.isDouble()) { - primitiveResult.waterMaskTranslationX = - waterMaskTranslationXIt->second.getDoubleOrDefault(0.0); - primitiveResult.waterMaskTranslationY = - waterMaskTranslationYIt->second.getDoubleOrDefault(0.0); - primitiveResult.waterMaskScale = - waterMaskScaleIt->second.getDoubleOrDefault(1.0); - } +static CesiumAsync::SharedFuture applyWaterMask( + const CesiumAsync::AsyncSystem& asyncSystem, + Model& model, + const MeshPrimitive& primitive, + LoadPrimitiveResult& primitiveResult) { + return applyWaterMaskTexture(asyncSystem, model, primitive, primitiveResult) + .thenImmediately([primitive, pPrimitiveResult = &primitiveResult]() { + auto waterMaskTranslationXIt = + primitive.extras.find("WaterMaskTranslationX"); + auto waterMaskTranslationYIt = + primitive.extras.find("WaterMaskTranslationY"); + auto waterMaskScaleIt = primitive.extras.find("WaterMaskScale"); + + if (waterMaskTranslationXIt != primitive.extras.end() && + waterMaskTranslationXIt->second.isDouble() && + waterMaskTranslationYIt != primitive.extras.end() && + waterMaskTranslationYIt->second.isDouble() && + waterMaskScaleIt != primitive.extras.end() && + waterMaskScaleIt->second.isDouble()) { + pPrimitiveResult->waterMaskTranslationX = + waterMaskTranslationXIt->second.getDoubleOrDefault(0.0); + pPrimitiveResult->waterMaskTranslationY = + waterMaskTranslationYIt->second.getDoubleOrDefault(0.0); + pPrimitiveResult->waterMaskScale = + waterMaskScaleIt->second.getDoubleOrDefault(1.0); + } + }) + .share(); } #pragma region Features Metadata helper functions(load thread) @@ -1114,7 +1142,8 @@ constexpr glm::dmat4 yInvertMatrix = { } // namespace template -static void loadPrimitive( +static CesiumAsync::Future loadPrimitive( + const CesiumAsync::AsyncSystem& asyncSystem, LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1122,566 +1151,729 @@ static void loadPrimitive( const AccessorView& positionView, const TIndexAccessor& indicesView, const CesiumGeospatial::Ellipsoid& ellipsoid) { + struct PrimitiveLoadingWorkingData { + TArray StaticMeshBuildVertices; + bool hasTangents; + bool needsTangents; + bool hasNormals; + AccessorView normalAccessor; + AccessorView tangentAccessor; + const Material& material; + const MaterialPBRMetallicRoughness& pbrMetallicRoughness; + TUniquePtr RenderData = nullptr; + bool duplicateVertices; + TArray indices; + bool hasVertexColors; + + PrimitiveLoadingWorkingData( + const Material& material_, + const MaterialPBRMetallicRoughness& pbrMetallicRoughness_) + : material(material_), pbrMetallicRoughness(pbrMetallicRoughness_) {} + }; - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - - Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; - - if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && - primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && - primitive.mode != MeshPrimitive::Mode::POINTS) { - // TODO: add support for other primitive types. - UE_LOG( - LogCesium, - Warning, - TEXT("Primitive mode %d is not supported"), - primitive.mode); - return; - } - - std::string name = "glTF"; - - auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); - if (urlIt != model.extras.end()) { - name = urlIt->second.getStringOrDefault("glTF"); - name = constrainLength(name, 256); - } - - auto meshIt = std::find_if( - model.meshes.begin(), - model.meshes.end(), - [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); - if (meshIt != model.meshes.end()) { - int64_t meshIndex = meshIt - model.meshes.begin(); - name += " mesh " + std::to_string(meshIndex); - } - - auto primitiveIt = std::find_if( - mesh.primitives.begin(), - mesh.primitives.end(), - [&primitive](const MeshPrimitive& candidate) { - return &candidate == &primitive; - }); - if (primitiveIt != mesh.primitives.end()) { - int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); - name += " primitive " + std::to_string(primitiveIndex); - } - - primitiveResult.name = name; - - if (positionView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid position buffer"), - UTF8_TO_TCHAR(name.c_str())); - return; - } - - if constexpr (IsAccessorView::value) { - if (indicesView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid indices buffer"), - UTF8_TO_TCHAR(name.c_str())); - return; - } - } - - auto normalAccessorIt = primitive.attributes.find("NORMAL"); - AccessorView normalAccessor; - bool hasNormals = false; - if (normalAccessorIt != primitive.attributes.end()) { - int normalAccessorID = normalAccessorIt->second; - normalAccessor = AccessorView(model, normalAccessorID); - hasNormals = normalAccessor.status() == AccessorViewStatus::Valid; - if (!hasNormals) { + return asyncSystem.runInWorkerThread([asyncSystem, + pPrimitiveResult = &primitiveResult, + transform, + options, + positionAccessor, + positionView, + indicesView, + ellipsoid]() { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) + + LoadPrimitiveResult& primitiveResult = *pPrimitiveResult; + + Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && + primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && + primitive.mode != MeshPrimitive::Mode::POINTS) { + // TODO: add support for other primitive types. UE_LOG( LogCesium, Warning, - TEXT( - "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), - UTF8_TO_TCHAR(name.c_str())); + TEXT("Primitive mode %d is not supported"), + primitive.mode); + return asyncSystem.createResolvedFuture(); } - } - int materialID = primitive.material; - const Material& material = - materialID >= 0 && materialID < model.materials.size() - ? model.materials[materialID] - : defaultMaterial; + std::string name = "glTF"; - primitiveResult.isUnlit = - material.hasExtension() && - !options.pMeshOptions->pNodeOptions->pModelOptions - ->ignoreKhrMaterialsUnlit; + auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); + if (urlIt != model.extras.end()) { + name = urlIt->second.getStringOrDefault("glTF"); + name = constrainLength(name, 256); + } - // We can't calculate flat normals for points or lines, so we have to force - // them to be unlit if no normals are specified. Otherwise this causes a - // crash when attempting to calculate flat normals. - bool isTriangles = primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; + auto meshIt = std::find_if( + model.meshes.begin(), + model.meshes.end(), + [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); + if (meshIt != model.meshes.end()) { + int64_t meshIndex = meshIt - model.meshes.begin(); + name += " mesh " + std::to_string(meshIndex); + } - if (!isTriangles && !hasNormals) { - primitiveResult.isUnlit = true; - } + auto primitiveIt = std::find_if( + mesh.primitives.begin(), + mesh.primitives.end(), + [&primitive](const MeshPrimitive& candidate) { + return &candidate == &primitive; + }); + if (primitiveIt != mesh.primitives.end()) { + int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); + name += " primitive " + std::to_string(primitiveIndex); + } - const MaterialPBRMetallicRoughness& pbrMetallicRoughness = - material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() - : defaultPbrMetallicRoughness; + primitiveResult.name = name; - bool hasNormalMap = material.normalTexture.has_value(); - if (hasNormalMap) { - const CesiumGltf::Texture* pTexture = - Model::getSafe(&model.textures, material.normalTexture->index); - hasNormalMap = pTexture != nullptr && - Model::getSafe(&model.images, pTexture->source) != nullptr; - } - - bool needsTangents = - hasNormalMap || - options.pMeshOptions->pNodeOptions->pModelOptions->alwaysIncludeTangents; - - bool hasTangents = false; - auto tangentAccessorIt = primitive.attributes.find("TANGENT"); - AccessorView tangentAccessor; - if (tangentAccessorIt != primitive.attributes.end()) { - int tangentAccessorID = tangentAccessorIt->second; - tangentAccessor = AccessorView(model, tangentAccessorID); - hasTangents = tangentAccessor.status() == AccessorViewStatus::Valid; - if (!hasTangents) { + if (positionView.status() != AccessorViewStatus::Valid) { UE_LOG( LogCesium, Warning, - TEXT("%s: Invalid tangent buffer."), + TEXT("%s: Invalid position buffer"), UTF8_TO_TCHAR(name.c_str())); + return asyncSystem.createResolvedFuture(); } - } - - applyWaterMask(model, primitive, primitiveResult); - - // The water effect works by animating the normal, and the normal is - // expressed in tangent space. So if we have water, we need tangents. - if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { - needsTangents = true; - } - TUniquePtr RenderData = - MakeUnique(); - RenderData->AllocateLODResources(1); - - FStaticMeshLODResources& LODResources = RenderData->LODResources[0]; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) - - const std::vector& min = positionAccessor.min; - const std::vector& max = positionAccessor.max; - glm::dvec3 minPosition{std::numeric_limits::max()}; - glm::dvec3 maxPosition{std::numeric_limits::lowest()}; - if (min.size() != 3 || max.size() != 3) { - for (int32_t i = 0; i < positionView.size(); ++i) { - minPosition.x = glm::min(minPosition.x, positionView[i].X); - minPosition.y = glm::min(minPosition.y, positionView[i].Y); - minPosition.z = glm::min(minPosition.z, positionView[i].Z); - - maxPosition.x = glm::max(maxPosition.x, positionView[i].X); - maxPosition.y = glm::max(maxPosition.y, positionView[i].Y); - maxPosition.z = glm::max(maxPosition.z, positionView[i].Z); + if constexpr (IsAccessorView::value) { + if (indicesView.status() != AccessorViewStatus::Valid) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid indices buffer"), + UTF8_TO_TCHAR(name.c_str())); + return asyncSystem.createResolvedFuture(); } - } else { - minPosition = glm::dvec3(min[0], min[1], min[2]); - maxPosition = glm::dvec3(max[0], max[1], max[2]); } - minPosition *= CesiumPrimitiveData::positionScaleFactor; - maxPosition *= CesiumPrimitiveData::positionScaleFactor; - - primitiveResult.dimensions = - glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); - - FBox aaBox( - FVector3d(minPosition.x, -minPosition.y, minPosition.z), - FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); - - aaBox.GetCenterAndExtents( - RenderData->Bounds.Origin, - RenderData->Bounds.BoxExtent); - RenderData->Bounds.SphereRadius = 0.0f; - } - - TArray indices; - if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::POINTS) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - indices.SetNum(static_cast::SizeType>(indicesView.size())); - - for (int32 i = 0; i < indicesView.size(); ++i) { - indices[i] = indicesView[i]; - } - } else { - // assume TRIANGLE_STRIP because all others are rejected earlier. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - indices.SetNum( - static_cast::SizeType>(3 * (indicesView.size() - 2))); - for (int32 i = 0; i < indicesView.size() - 2; ++i) { - if (i % 2) { - indices[3 * i] = indicesView[i]; - indices[3 * i + 1] = indicesView[i + 2]; - indices[3 * i + 2] = indicesView[i + 1]; - } else { - indices[3 * i] = indicesView[i]; - indices[3 * i + 1] = indicesView[i + 1]; - indices[3 * i + 2] = indicesView[i + 2]; + int materialID = primitive.material; + const Material& material = + materialID >= 0 && materialID < model.materials.size() + ? model.materials[materialID] + : defaultMaterial; + + const MaterialPBRMetallicRoughness& pbrMetallicRoughness = + material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() + : defaultPbrMetallicRoughness; + + TSharedPtr workingData = + MakeShared(material, pbrMetallicRoughness); + + auto normalAccessorIt = primitive.attributes.find("NORMAL"); + if (normalAccessorIt != primitive.attributes.end()) { + int normalAccessorID = normalAccessorIt->second; + workingData->normalAccessor = + AccessorView(model, normalAccessorID); + workingData->hasNormals = + workingData->normalAccessor.status() == AccessorViewStatus::Valid; + if (!workingData->hasNormals) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), + UTF8_TO_TCHAR(name.c_str())); } } - } - - // If we don't have normals, the gltf spec prescribes that the client - // implementation must generate flat normals, which requires duplicating - // vertices shared by multiple triangles. If we don't have tangents, but - // need them, we need to use a tangent space generation algorithm which - // requires duplicated vertices. - bool duplicateVertices = !hasNormals || (needsTangents && !hasTangents); - duplicateVertices = - duplicateVertices && primitive.mode != MeshPrimitive::Mode::POINTS; - TArray StaticMeshBuildVertices; - StaticMeshBuildVertices.SetNum( - duplicateVertices ? indices.Num() - : static_cast(positionView.size())); - - { - if (duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) - for (int i = 0; i < indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - uint32 vertexIndex = indices[i]; - const TMeshVector3& pos = positionView[vertexIndex]; - vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - RenderData->Bounds.Origin).Size(), - RenderData->Bounds.SphereRadius); - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) - for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - const TMeshVector3& pos = positionView[i]; - vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - RenderData->Bounds.Origin).Size(), - RenderData->Bounds.SphereRadius); + primitiveResult.isUnlit = + workingData->material.hasExtension() && + !options.pMeshOptions->pNodeOptions->pModelOptions + ->ignoreKhrMaterialsUnlit; + + // We can't calculate flat normals for points or lines, so we have to + // force them to be unlit if no normals are specified. Otherwise this + // causes a crash when attempting to calculate flat normals. + bool isTriangles = primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; + + if (!isTriangles && !workingData->hasNormals) { + primitiveResult.isUnlit = true; + } + + bool hasNormalMap = workingData->material.normalTexture.has_value(); + if (hasNormalMap) { + const CesiumGltf::Texture* pTexture = Model::getSafe( + &model.textures, + workingData->material.normalTexture->index); + hasNormalMap = pTexture != nullptr && + Model::getSafe(&model.images, pTexture->source) != nullptr; + } + + workingData->needsTangents = + hasNormalMap || options.pMeshOptions->pNodeOptions->pModelOptions + ->alwaysIncludeTangents; + + workingData->hasTangents = false; + auto tangentAccessorIt = primitive.attributes.find("TANGENT"); + if (tangentAccessorIt != primitive.attributes.end()) { + int tangentAccessorID = tangentAccessorIt->second; + workingData->tangentAccessor = + AccessorView(model, tangentAccessorID); + workingData->hasTangents = + workingData->tangentAccessor.status() == AccessorViewStatus::Valid; + if (!workingData->hasTangents) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid tangent buffer."), + UTF8_TO_TCHAR(name.c_str())); } } - } - - bool hasVertexColors = false; - - auto colorAccessorIt = primitive.attributes.find("COLOR_0"); - if (colorAccessorIt != primitive.attributes.end()) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) - int colorAccessorID = colorAccessorIt->second; - hasVertexColors = createAccessorView( - model, - colorAccessorID, - ColorVisitor{duplicateVertices, StaticMeshBuildVertices, indices}); - } - - LODResources.bHasColorVertexData = hasVertexColors; - // We need to copy the texture coordinates associated with each texture (if - // any) into the the appropriate UVs slot in FStaticMeshBuildVertex. + return applyWaterMask(asyncSystem, model, primitive, primitiveResult) + .thenImmediately([asyncSystem, + &primitiveResult, + workingData, + transform, + options, + positionAccessor, + positionView, + indicesView, + ellipsoid]() { + Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + // The water effect works by animating the normal, and the normal + // is expressed in tangent space. So if we have water, we need + // tangents. + if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { + workingData->needsTangents = true; + } - std::unordered_map& gltfToUnrealTexCoordMap = - primitiveResult.GltfToUnrealTexCoordMap; + workingData->RenderData = MakeUnique(); + workingData->RenderData->AllocateLODResources(1); + + FStaticMeshLODResources& LODResources = + workingData->RenderData->LODResources[0]; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) + + const std::vector& min = positionAccessor.min; + const std::vector& max = positionAccessor.max; + glm::dvec3 minPosition{std::numeric_limits::max()}; + glm::dvec3 maxPosition{std::numeric_limits::lowest()}; + if (min.size() != 3 || max.size() != 3) { + for (int32_t i = 0; i < positionView.size(); ++i) { + minPosition.x = + glm::min(minPosition.x, positionView[i].X); + minPosition.y = + glm::min(minPosition.y, positionView[i].Y); + minPosition.z = + glm::min(minPosition.z, positionView[i].Z); + + maxPosition.x = + glm::max(maxPosition.x, positionView[i].X); + maxPosition.y = + glm::max(maxPosition.y, positionView[i].Y); + maxPosition.z = + glm::max(maxPosition.z, positionView[i].Z); + } + } else { + minPosition = glm::dvec3(min[0], min[1], min[2]); + maxPosition = glm::dvec3(max[0], max[1], max[2]); + } - // This must be done before material textures are loaded, in case any of the - // material textures are also used for features + metadata. - loadPrimitiveFeaturesMetadata( - primitiveResult, - options, - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices); + minPosition *= CesiumPrimitiveData::positionScaleFactor; + maxPosition *= CesiumPrimitiveData::positionScaleFactor; - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) - primitiveResult.baseColorTexture = - loadTexture(model, pbrMetallicRoughness.baseColorTexture, true); - primitiveResult.metallicRoughnessTexture = loadTexture( - model, - pbrMetallicRoughness.metallicRoughnessTexture, - false); - primitiveResult.normalTexture = - loadTexture(model, material.normalTexture, false); - primitiveResult.occlusionTexture = - loadTexture(model, material.occlusionTexture, false); - primitiveResult.emissiveTexture = - loadTexture(model, material.emissiveTexture, true); - } + primitiveResult.dimensions = + glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinates) + FBox aaBox( + FVector3d(minPosition.x, -minPosition.y, minPosition.z), + FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); - primitiveResult - .textureCoordinateParameters["baseColorTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - pbrMetallicRoughness.baseColorTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["metallicRoughnessTextureCoordinateIndex"] = updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - pbrMetallicRoughness.metallicRoughnessTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["normalTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - material.normalTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["occlusionTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - material.occlusionTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["emissiveTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - material.emissiveTexture, - gltfToUnrealTexCoordMap); - - for (size_t i = 0; - i < primitiveResult.overlayTextureCoordinateIDToUVIndex.size(); - ++i) { - std::string attributeName = "_CESIUMOVERLAY_" + std::to_string(i); - auto overlayIt = primitive.attributes.find(attributeName); - if (overlayIt != primitive.attributes.end()) { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = - updateTextureCoordinates( - model, - primitive, - duplicateVertices, - StaticMeshBuildVertices, - indices, - attributeName, - gltfToUnrealTexCoordMap); - } else { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0; - } - } - } + aaBox.GetCenterAndExtents( + workingData->RenderData->Bounds.Origin, + workingData->RenderData->Bounds.BoxExtent); + workingData->RenderData->Bounds.SphereRadius = 0.0f; + } - // TangentX: Tangent - // TangentY: Bi-tangent - // TangentZ: Normal + if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::POINTS) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + workingData->indices.SetNum( + static_cast::SizeType>(indicesView.size())); - if (hasNormals) { - if (duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormalsForDuplicatedVertices) - for (int i = 0; i < indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - uint32 vertexIndex = indices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = normalAccessor[vertexIndex]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) - for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = normalAccessor[i]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } - } else { - if (primitiveResult.isUnlit) { - glm::dvec3 ecefCenter = glm::dvec3( - transform * - glm::dvec4(VecMath::createVector3D(RenderData->Bounds.Origin), 1.0)); - TMeshVector3 upDir = TMeshVector3(VecMath::createVector( - glm::affineInverse(transform) * - glm::dvec4( - ellipsoid.geodeticSurfaceNormal(glm::dvec3(ecefCenter)), - 0.0))); - upDir.Y *= -1; - setUniformNormals(StaticMeshBuildVertices, upDir); - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) - computeFlatNormals(StaticMeshBuildVertices); - } - } + for (int32 i = 0; i < indicesView.size(); ++i) { + workingData->indices[i] = indicesView[i]; + } + } else { + // assume TRIANGLE_STRIP because all others are rejected + // earlier. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + workingData->indices.SetNum(static_cast::SizeType>( + 3 * (indicesView.size() - 2))); + for (int32 i = 0; i < indicesView.size() - 2; ++i) { + if (i % 2) { + workingData->indices[3 * i] = indicesView[i]; + workingData->indices[3 * i + 1] = indicesView[i + 2]; + workingData->indices[3 * i + 2] = indicesView[i + 1]; + } else { + workingData->indices[3 * i] = indicesView[i]; + workingData->indices[3 * i + 1] = indicesView[i + 1]; + workingData->indices[3 * i + 2] = indicesView[i + 2]; + } + } + } - if (hasTangents) { - if (duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangentsForDuplicatedVertices) - for (int i = 0; i < indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - uint32 vertexIndex = indices[i]; - const TMeshVector4& tangent = tangentAccessor[vertexIndex]; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = - TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * - tangent.W; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) - for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; - const TMeshVector4& tangent = tangentAccessor[i]; - vertex.TangentX = tangent; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = - TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * - tangent.W; - } - } - } + // If we don't have normals, the gltf spec prescribes that the + // client implementation must generate flat normals, which + // requires duplicating vertices shared by multiple triangles. If + // we don't have tangents, but need them, we need to use a tangent + // space generation algorithm which requires duplicated vertices. + workingData->duplicateVertices = + !workingData->hasNormals || + (workingData->needsTangents && !workingData->hasTangents); + workingData->duplicateVertices = + workingData->duplicateVertices && + primitive.mode != MeshPrimitive::Mode::POINTS; + + workingData->StaticMeshBuildVertices.SetNum( + workingData->duplicateVertices + ? workingData->indices.Num() + : static_cast(positionView.size())); + + { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + const TMeshVector3& pos = positionView[vertexIndex]; + vertex.Position.X = + pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = + -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = + pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + workingData->RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - + workingData->RenderData->Bounds.Origin) + .Size(), + workingData->RenderData->Bounds.SphereRadius); + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) + for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + const TMeshVector3& pos = positionView[i]; + vertex.Position.X = + pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = + -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = + pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + workingData->RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - + workingData->RenderData->Bounds.Origin) + .Size(), + workingData->RenderData->Bounds.SphereRadius); + } + } + } - if (needsTangents && !hasTangents) { - // Use mikktspace to calculate the tangents. - // Note that this assumes normals and UVs are already populated. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) - computeTangentSpace(StaticMeshBuildVertices); - } + workingData->hasVertexColors = false; - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) - - // Set to full precision (32-bit) UVs. This is especially important for - // metadata because integer feature IDs can and will lose meaningful - // precision when using 16-bit floats. - LODResources.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs( - true); - - LODResources.VertexBuffers.PositionVertexBuffer.Init( - StaticMeshBuildVertices, - false); - - FColorVertexBuffer& ColorVertexBuffer = - LODResources.VertexBuffers.ColorVertexBuffer; - if (hasVertexColors) { - ColorVertexBuffer.Init(StaticMeshBuildVertices, false); - } - - LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( - StaticMeshBuildVertices, - gltfToUnrealTexCoordMap.size() == 0 ? 1 - : gltfToUnrealTexCoordMap.size(), - false); - } - - FStaticMeshSectionArray& Sections = LODResources.Sections; - FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); - // This will be ignored if the primitive contains points. - section.NumTriangles = indices.Num() / 3; - section.FirstIndex = 0; - section.MinVertexIndex = 0; - section.MaxVertexIndex = StaticMeshBuildVertices.Num() - 1; - section.bEnableCollision = primitive.mode != MeshPrimitive::Mode::POINTS; - section.bCastShadow = true; - section.MaterialIndex = 0; + auto colorAccessorIt = primitive.attributes.find("COLOR_0"); + if (colorAccessorIt != primitive.attributes.end()) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) + int colorAccessorID = colorAccessorIt->second; + workingData->hasVertexColors = createAccessorView( + model, + colorAccessorID, + ColorVisitor{ + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices}); + } - if (duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) - for (int32 i = 0; i < indices.Num(); i++) { - indices[i] = i; - } - } + LODResources.bHasColorVertexData = workingData->hasVertexColors; - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) - LODResources.IndexBuffer.SetIndices( - indices, - StaticMeshBuildVertices.Num() >= std::numeric_limits::max() - ? EIndexBufferStride::Type::Force32Bit - : EIndexBufferStride::Type::Force16Bit); - } - - LODResources.bHasDepthOnlyIndices = false; - LODResources.bHasReversedIndices = false; - LODResources.bHasReversedDepthOnlyIndices = false; - - primitiveResult.pModel = &model; - primitiveResult.pMeshPrimitive = &primitive; - primitiveResult.RenderData = std::move(RenderData); - primitiveResult.pMaterial = &material; - primitiveResult.pCollisionMesh = nullptr; - - double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; - glm::dmat4 scaleMatrix = glm::dmat4( - glm::dvec4(scale, 0.0, 0.0, 0.0), - glm::dvec4(0.0, scale, 0.0, 0.0), - glm::dvec4(0.0, 0.0, scale, 0.0), - glm::dvec4(0.0, 0.0, 0.0, 1.0)); - - primitiveResult.transform = transform * yInvertMatrix * scaleMatrix; - - if (primitive.mode != MeshPrimitive::Mode::POINTS && - options.pMeshOptions->pNodeOptions->pModelOptions->createPhysicsMeshes) { - if (StaticMeshBuildVertices.Num() != 0 && indices.Num() != 0) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) - primitiveResult.pCollisionMesh = - StaticMeshBuildVertices.Num() < TNumericLimits::Max() - ? BuildChaosTriangleMeshes( - StaticMeshBuildVertices, - indices) - : BuildChaosTriangleMeshes( - StaticMeshBuildVertices, - indices); - } - } + // This must be done before material textures are loaded, in case + // any of the material textures are also used for features + + // metadata. + loadPrimitiveFeaturesMetadata( + primitiveResult, + options, + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices); + + std::vector>> + textureLoads{ + loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness.baseColorTexture, + true), + loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness + .metallicRoughnessTexture, + false), + loadTexture( + asyncSystem, + model, + workingData->material.normalTexture, + false), + loadTexture( + asyncSystem, + model, + workingData->material.occlusionTexture, + false), + loadTexture( + asyncSystem, + model, + workingData->material.emissiveTexture, + true)}; + + return asyncSystem.all(std::move(textureLoads)) + .thenImmediately([&primitiveResult, + workingData, + transform, + options, + positionAccessor, + positionView, + indicesView, + &ellipsoid](std::vector< + TUniquePtr>&& + results) { + Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + // We need to copy the texture coordinates associated with + // each texture (if any) into the the appropriate UVs slot + // in FStaticMeshBuildVertex. + + std::unordered_map& gltfToUnrealTexCoordMap = + primitiveResult.GltfToUnrealTexCoordMap; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) + primitiveResult.baseColorTexture = MoveTemp(results[0]); + primitiveResult.metallicRoughnessTexture = + MoveTemp(results[1]); + primitiveResult.normalTexture = MoveTemp(results[2]); + primitiveResult.occlusionTexture = MoveTemp(results[3]); + primitiveResult.emissiveTexture = MoveTemp(results[4]); + } + + { + TRACE_CPUPROFILER_EVENT_SCOPE( + Cesium::UpdateTextureCoordinates) + + primitiveResult.textureCoordinateParameters + ["baseColorTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->pbrMetallicRoughness.baseColorTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["metallicRoughnessTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->pbrMetallicRoughness + .metallicRoughnessTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["normalTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material.normalTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["occlusionTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material.occlusionTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["emissiveTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material.emissiveTexture, + gltfToUnrealTexCoordMap); + + for (size_t i = 0; + i < primitiveResult.overlayTextureCoordinateIDToUVIndex + .size(); + ++i) { + std::string attributeName = + "_CESIUMOVERLAY_" + std::to_string(i); + auto overlayIt = primitive.attributes.find(attributeName); + if (overlayIt != primitive.attributes.end()) { + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + attributeName, + gltfToUnrealTexCoordMap); + } else { + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = + 0; + } + } + } + + // TangentX: Tangent + // TangentY: Bi-tangent + // TangentZ: Normal + + if (workingData->hasNormals) { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE( + Cesium::CopyNormalsForDuplicatedVertices) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = + workingData->normalAccessor[vertexIndex]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) + for (int i = 0; + i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = + workingData->normalAccessor[i]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } + } else { + if (primitiveResult.isUnlit) { + glm::dvec3 ecefCenter = glm::dvec3( + transform * + glm::dvec4( + VecMath::createVector3D( + workingData->RenderData->Bounds.Origin), + 1.0)); + TMeshVector3 upDir = TMeshVector3(VecMath::createVector( + glm::affineInverse(transform) * + glm::dvec4( + ellipsoid.geodeticSurfaceNormal( + glm::dvec3(ecefCenter)), + 0.0))); + upDir.Y *= -1; + setUniformNormals( + workingData->StaticMeshBuildVertices, + upDir); + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) + computeFlatNormals(workingData->StaticMeshBuildVertices); + } + } + + if (workingData->hasTangents) { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE( + Cesium::CopyTangentsForDuplicatedVertices) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + const TMeshVector4& tangent = + workingData->tangentAccessor[vertexIndex]; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = TMeshVector3::CrossProduct( + vertex.TangentZ, + vertex.TangentX) * + tangent.W; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) + for (int i = 0; + i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + const TMeshVector4& tangent = + workingData->tangentAccessor[i]; + vertex.TangentX = tangent; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = TMeshVector3::CrossProduct( + vertex.TangentZ, + vertex.TangentX) * + tangent.W; + } + } + } + + if (workingData->needsTangents && !workingData->hasTangents) { + // Use mikktspace to calculate the tangents. + // Note that this assumes normals and UVs are already + // populated. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) + computeTangentSpace(workingData->StaticMeshBuildVertices); + } + + FStaticMeshLODResources& LODResources = + workingData->RenderData->LODResources[0]; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) + + // Set to full precision (32-bit) UVs. This is especially + // important for metadata because integer feature IDs can + // and will lose meaningful precision when using 16-bit + // floats. + LODResources.VertexBuffers.StaticMeshVertexBuffer + .SetUseFullPrecisionUVs(true); + + LODResources.VertexBuffers.PositionVertexBuffer.Init( + workingData->StaticMeshBuildVertices, + false); + + FColorVertexBuffer& ColorVertexBuffer = + LODResources.VertexBuffers.ColorVertexBuffer; + if (workingData->hasVertexColors) { + ColorVertexBuffer.Init( + workingData->StaticMeshBuildVertices, + false); + } + + LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( + workingData->StaticMeshBuildVertices, + gltfToUnrealTexCoordMap.size() == 0 + ? 1 + : gltfToUnrealTexCoordMap.size(), + false); + } + + FStaticMeshSectionArray& Sections = LODResources.Sections; + FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); + // This will be ignored if the primitive contains points. + section.NumTriangles = workingData->indices.Num() / 3; + section.FirstIndex = 0; + section.MinVertexIndex = 0; + section.MaxVertexIndex = + workingData->StaticMeshBuildVertices.Num() - 1; + section.bEnableCollision = + primitive.mode != MeshPrimitive::Mode::POINTS; + section.bCastShadow = true; + section.MaterialIndex = 0; + + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) + for (int32 i = 0; i < workingData->indices.Num(); i++) { + workingData->indices[i] = i; + } + } + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) + LODResources.IndexBuffer.SetIndices( + workingData->indices, + workingData->StaticMeshBuildVertices.Num() >= + std::numeric_limits::max() + ? EIndexBufferStride::Type::Force32Bit + : EIndexBufferStride::Type::Force16Bit); + } + + LODResources.bHasDepthOnlyIndices = false; + LODResources.bHasReversedIndices = false; + LODResources.bHasReversedDepthOnlyIndices = false; + + primitiveResult.pModel = &model; + primitiveResult.pMeshPrimitive = &primitive; + primitiveResult.RenderData = MoveTemp(workingData->RenderData); + primitiveResult.pMaterial = &workingData->material; + primitiveResult.pCollisionMesh = nullptr; + + double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; + glm::dmat4 scaleMatrix = glm::dmat4( + glm::dvec4(scale, 0.0, 0.0, 0.0), + glm::dvec4(0.0, scale, 0.0, 0.0), + glm::dvec4(0.0, 0.0, scale, 0.0), + glm::dvec4(0.0, 0.0, 0.0, 1.0)); + + primitiveResult.transform = + transform * yInvertMatrix * scaleMatrix; + + if (primitive.mode != MeshPrimitive::Mode::POINTS && + options.pMeshOptions->pNodeOptions->pModelOptions + ->createPhysicsMeshes) { + if (workingData->StaticMeshBuildVertices.Num() != 0 && + workingData->indices.Num() != 0) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) + primitiveResult.pCollisionMesh = + workingData->StaticMeshBuildVertices.Num() < + TNumericLimits::Max() + ? BuildChaosTriangleMeshes( + workingData->StaticMeshBuildVertices, + workingData->indices) + : BuildChaosTriangleMeshes( + workingData->StaticMeshBuildVertices, + workingData->indices); + } + } + }); + }); + }); } -static void loadIndexedPrimitive( +static CesiumAsync::SharedFuture loadIndexedPrimitive( + const CesiumAsync::AsyncSystem& asyncSystem, LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1696,51 +1888,69 @@ static void loadIndexedPrimitive( if (indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_BYTE) { AccessorView indexAccessor(model, primitive.indices); - loadPrimitive( - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid); - primitiveResult.IndexAccessor = indexAccessor; + return loadPrimitive( + asyncSystem, + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid) + .thenImmediately( + [indexAccessor, pPrimitiveResult = &primitiveResult]() { + pPrimitiveResult->IndexAccessor = indexAccessor; + }) + .share(); } else if ( indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_SHORT) { AccessorView indexAccessor(model, primitive.indices); - loadPrimitive( - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid); - primitiveResult.IndexAccessor = indexAccessor; + return loadPrimitive( + asyncSystem, + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid) + .thenImmediately( + [indexAccessor, pPrimitiveResult = &primitiveResult]() { + pPrimitiveResult->IndexAccessor = indexAccessor; + }) + .share(); } else if ( indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_INT) { AccessorView indexAccessor(model, primitive.indices); - loadPrimitive( - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid); - primitiveResult.IndexAccessor = indexAccessor; + return loadPrimitive( + asyncSystem, + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid) + .thenImmediately( + [indexAccessor, pPrimitiveResult = &primitiveResult]() { + pPrimitiveResult->IndexAccessor = indexAccessor; + }) + .share(); } else { UE_LOG( LogCesium, VeryVerbose, TEXT( "Skip loading primitive due to invalid component type in its index accessor.")); + + return asyncSystem.createResolvedFuture().share(); } } -static void loadPrimitive( +static CesiumAsync::SharedFuture loadPrimitive( + const CesiumAsync::AsyncSystem& asyncSystem, LoadPrimitiveResult& result, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1754,7 +1964,7 @@ static void loadPrimitive( auto positionAccessorIt = primitive.attributes.find("POSITION"); if (positionAccessorIt == primitive.attributes.end()) { // This primitive doesn't have a POSITION semantic, ignore it. - return; + return asyncSystem.createResolvedFuture(0).share(); } int positionAccessorID = positionAccessorIt->second; @@ -1762,7 +1972,7 @@ static void loadPrimitive( Model::getSafe(&model.accessors, positionAccessorID); if (!pPositionAccessor) { // Position accessor does not exist, so ignore this primitive. - return; + return asyncSystem.createResolvedFuture(0).share(); } AccessorView positionView(model, *pPositionAccessor); @@ -1773,27 +1983,39 @@ static void loadPrimitive( for (uint32_t i = 0; i < positionView.size(); ++i) { syntheticIndexBuffer[i] = i; } - loadPrimitive( - result, - transform, - options, - *pPositionAccessor, - positionView, - syntheticIndexBuffer, - ellipsoid); + return loadPrimitive( + asyncSystem, + result, + transform, + options, + *pPositionAccessor, + positionView, + syntheticIndexBuffer, + ellipsoid) + .thenImmediately([positionView, pResult = &result]() { + pResult->PositionAccessor = std::move(positionView); + return 0; + }) + .share(); } else { - loadIndexedPrimitive( - result, - transform, - options, - *pPositionAccessor, - positionView, - ellipsoid); + return loadIndexedPrimitive( + asyncSystem, + result, + transform, + options, + *pPositionAccessor, + positionView, + ellipsoid) + .thenImmediately([positionView, pResult = &result]() { + pResult->PositionAccessor = std::move(positionView); + return 0; + }) + .share(); } - result.PositionAccessor = std::move(positionView); } -static void loadMesh( +static CesiumAsync::SharedFuture loadMesh( + const CesiumAsync::AsyncSystem& asyncSystem, std::optional& result, const glm::dmat4x4& transform, const CreateMeshOptions& options, @@ -1806,16 +2028,26 @@ static void loadMesh( result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); + std::vector> futures; for (CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { CreatePrimitiveOptions primitiveOptions = {&options, &*result, &primitive}; auto& primitiveResult = result->primitiveResults.emplace_back(); - loadPrimitive(primitiveResult, transform, primitiveOptions, ellipsoid); - - // if it doesn't have render data, then it can't be loaded - if (!primitiveResult.RenderData) { - result->primitiveResults.pop_back(); - } - } + futures.push_back(loadPrimitive( + asyncSystem, + primitiveResult, + transform, + primitiveOptions, + ellipsoid)); + } + + return asyncSystem.all(std::move(futures)) + .thenImmediately([pResult = &result](std::vector&& result) { + std::erase_if( + (*pResult)->primitiveResults, + [](LoadPrimitiveResult& result) { return !result.RenderData; }); + return 0; + }) + .share(); } // Helpers for different instancing rotation formats @@ -1962,7 +2194,8 @@ static void loadInstancingData( } } -static void loadNode( +static CesiumAsync::SharedFuture loadNode( + const CesiumAsync::AsyncSystem& asyncSystem, std::vector& loadNodeResults, const glm::dmat4x4& transform, const CreateNodeOptions& options, @@ -2040,24 +2273,51 @@ static void loadNode( } int meshId = node.mesh; + CesiumAsync::SharedFuture loadMeshFuture = + asyncSystem.createResolvedFuture(0).share(); if (meshId >= 0 && meshId < model.meshes.size()) { if (const auto* pGpuInstancingExtension = node.getExtension()) { loadInstancingData(model, result, pGpuInstancingExtension); } CreateMeshOptions meshOptions = {&options, &result, &model.meshes[meshId]}; - loadMesh(result.meshResult, nodeTransform, meshOptions, ellipsoid); + loadMeshFuture = loadMesh( + asyncSystem, + result.meshResult, + nodeTransform, + meshOptions, + ellipsoid); } - for (int childNodeId : node.children) { - if (childNodeId >= 0 && childNodeId < model.nodes.size()) { - CreateNodeOptions childNodeOptions = { - options.pModelOptions, - options.pHalfConstructedModelResult, - &model.nodes[childNodeId]}; - loadNode(loadNodeResults, nodeTransform, childNodeOptions, ellipsoid); - } - } + return loadMeshFuture + .thenImmediately([node, + model, + asyncSystem, + options, + pLoadNodeResults = &loadNodeResults, + nodeTransform, + ellipsoid](int result) { + std::vector> futures; + for (int childNodeId : node.children) { + if (childNodeId >= 0 && childNodeId < model.nodes.size()) { + CreateNodeOptions childNodeOptions = { + options.pModelOptions, + options.pHalfConstructedModelResult, + &model.nodes[childNodeId]}; + futures.push_back(loadNode( + asyncSystem, + *pLoadNodeResults, + nodeTransform, + childNodeOptions, + ellipsoid)); + } + } + + return asyncSystem.all(std::move(futures)) + .thenImmediately([](std::vector&& results) { return 0; }) + .share(); + }) + .share(); } namespace { @@ -2198,59 +2458,32 @@ loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { PRAGMA_ENABLE_DEPRECATION_WARNINGS } -static void loadModelAnyThreadPart( - LoadModelResult& result, +static CesiumAsync::Future> +loadModelAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, const glm::dmat4x4& transform, const CreateModelOptions& options, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) + auto pResult = MakeUnique(); + Model& model = *options.pModel; - // Generate mipmaps if needed. - // An image needs mipmaps generated for it if: - // 1. It is used by a Texture that has a Sampler with a mipmap filtering - // mode, and - // 2. It does not already have mipmaps. - // It's ok if an image has mipmaps even if not all textures will use them. - // There's no reason to have two RHI textures, one with and one without - // mips. for (const Texture& texture : model.textures) { const Sampler& sampler = model.getSafe(model.samplers, texture.sampler); + ImageCesium& image = *model.images[texture.source].cesium; - bool needsMipmaps; - switch (sampler.minFilter.value_or( - CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - needsMipmaps = true; - break; - default: // LINEAR and NEAREST - needsMipmaps = false; - break; - } - - if (!needsMipmaps) - continue; - - Image* pImage = model.getSafe(&model.images, texture.source); - if (!pImage || pImage->cesium->pixelData.empty()) - continue; - - std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(*pImage->cesium); - if (errorMessage) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s"), - UTF8_TO_TCHAR(errorMessage->c_str())); - } + // Creates a future on the extension that generates mipmaps if necessary. + // We don't need to wait on this future now. That will happen when the + // texture is generated. + ExtensionUnrealTextureResource::preprocessImage( + asyncSystem, + sampler, + image); } - loadModelMetadata(result, options); + loadModelMetadata(pResult->loadModelResult, options); glm::dmat4x4 rootTransform = transform; @@ -2260,40 +2493,78 @@ static void loadModelAnyThreadPart( applyGltfUpAxisTransform(model, rootTransform); } + std::vector> futures; + 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, ellipsoid); + CreateNodeOptions nodeOptions = { + &options, + &pResult->loadModelResult, + &model.nodes[nodeId]}; + futures.push_back(loadNode( + asyncSystem, + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid)); } } 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, ellipsoid); + CreateNodeOptions nodeOptions = { + &options, + &pResult->loadModelResult, + &model.nodes[nodeId]}; + futures.push_back(loadNode( + asyncSystem, + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid)); } } 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, ellipsoid); + CreateNodeOptions nodeOptions = { + &options, + &pResult->loadModelResult, + &model.nodes[0]}; + futures.push_back(loadNode( + asyncSystem, + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid)); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. for (Mesh& mesh : model.meshes) { - CreateNodeOptions dummyNodeOptions = {&options, &result, nullptr}; - LoadNodeResult& dummyNodeResult = result.nodeResults.emplace_back(); + CreateNodeOptions dummyNodeOptions = { + &options, + &pResult->loadModelResult, + nullptr}; + LoadNodeResult& dummyNodeResult = + pResult->loadModelResult.nodeResults.emplace_back(); CreateMeshOptions meshOptions = { &dummyNodeOptions, &dummyNodeResult, &mesh}; - loadMesh( + futures.push_back(loadMesh( + asyncSystem, dummyNodeResult.meshResult, rootTransform, meshOptions, - ellipsoid); + ellipsoid)); } } + + return asyncSystem.all(std::move(futures)) + .thenImmediately( + [ppResult = &pResult](std::vector&& results) + -> TUniquePtr { + return MoveTemp(*ppResult); + }); } bool applyTexture( @@ -3421,20 +3692,14 @@ static void loadPrimitiveGameThreadPart( } } -/*static*/ TUniquePtr +/*static*/ CesiumAsync::Future< + TUniquePtr> UCesiumGltfComponent::CreateOffGameThread( + const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, const CreateModelOptions& Options, const CesiumGeospatial::Ellipsoid& Ellipsoid) { - - auto pResult = MakeUnique(); - loadModelAnyThreadPart( - pResult->loadModelResult, - Transform, - Options, - Ellipsoid); - - return pResult; + return loadModelAnyThreadPart(AsyncSystem, Transform, Options, Ellipsoid); } /*static*/ UCesiumGltfComponent* UCesiumGltfComponent::CreateOnGameThread( diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.h b/Source/CesiumRuntime/Private/CesiumGltfComponent.h index dcefb5ecd..8c6d11ae7 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.h @@ -12,6 +12,7 @@ #include "CoreMinimal.h" #include "CustomDepthParameters.h" #include "Interfaces/IHttpRequest.h" +#include #include #include #include "CesiumGltfComponent.generated.h" @@ -64,7 +65,8 @@ class UCesiumGltfComponent : public USceneComponent { virtual ~HalfConstructed() = default; }; - static TUniquePtr CreateOffGameThread( + static CesiumAsync::Future> CreateOffGameThread( + const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, const CreateGltfOptions::CreateModelOptions& Options, const CesiumGeospatial::Ellipsoid& Ellipsoid = diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 4ca4a6eee..27d887a03 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -150,24 +150,6 @@ FTexture2DRHIRef CreateRHITexture2D_Async( } } -struct ExtensionUnrealTexture { - static inline constexpr const char* TypeName = "ExtensionUnrealTexture"; - static inline constexpr const char* ExtensionName = "PRIVATE_unreal_texture"; - - CesiumUtility::IntrusivePointer< - CesiumTextureUtility::ReferenceCountedUnrealTexture> - pTexture; -}; - -struct ExtensionUnrealTextureResource { - static inline constexpr const char* TypeName = - "ExtensionUnrealTextureResource"; - static inline constexpr const char* ExtensionName = - "PRIVATE_unreal_texture_resource"; - - FCesiumTextureResourceBase* pTextureResource; -}; - } // namespace namespace CesiumTextureUtility { @@ -237,30 +219,135 @@ void ReferenceCountedUnrealTexture::setSharedImage( this->_pImageCesium = image; } -TUniquePtr loadTextureFromModelAnyThreadPart( - CesiumGltf::Model& model, - CesiumGltf::Texture& texture, - bool sRGB) { - int64_t textureIndex = - model.textures.empty() ? -1 : &texture - &model.textures[0]; - if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { - textureIndex = -1; +std::optional getPixelFormatForImageCesium( + const ImageCesium& imageCesium, + const std::optional overridePixelFormat) { + if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { + switch (imageCesium.compressedPixelFormat) { + case GpuCompressedPixelFormat::ETC1_RGB: + return EPixelFormat::PF_ETC1; + break; + case GpuCompressedPixelFormat::ETC2_RGBA: + return EPixelFormat::PF_ETC2_RGBA; + break; + case GpuCompressedPixelFormat::BC1_RGB: + return EPixelFormat::PF_DXT1; + break; + case GpuCompressedPixelFormat::BC3_RGBA: + return EPixelFormat::PF_DXT5; + break; + case GpuCompressedPixelFormat::BC4_R: + return EPixelFormat::PF_BC4; + break; + case GpuCompressedPixelFormat::BC5_RG: + return EPixelFormat::PF_BC5; + break; + case GpuCompressedPixelFormat::BC7_RGBA: + return EPixelFormat::PF_BC7; + break; + case GpuCompressedPixelFormat::ASTC_4x4_RGBA: + return EPixelFormat::PF_ASTC_4x4; + break; + case GpuCompressedPixelFormat::PVRTC2_4_RGBA: + return EPixelFormat::PF_PVRTC2; + break; + case GpuCompressedPixelFormat::ETC2_EAC_R11: + return EPixelFormat::PF_ETC2_R11_EAC; + break; + case GpuCompressedPixelFormat::ETC2_EAC_RG11: + return EPixelFormat::PF_ETC2_RG11_EAC; + break; + default: + // Unsupported compressed texture format. + return std::nullopt; + }; + } else if (overridePixelFormat) { + return *overridePixelFormat; + } else { + switch (imageCesium.channels) { + case 1: + return PF_R8; + break; + case 2: + return PF_R8G8; + break; + case 3: + case 4: + default: + return PF_R8G8B8A8; + }; } - ExtensionUnrealTexture& extension = - texture.addExtension(); - if (extension.pTexture && (extension.pTexture->getUnrealTexture() || - extension.pTexture->getTextureResource())) { - // There's an existing Unreal texture for this glTF texture. This will - // happen if this texture is used by multiple primitives on the same - // model. It will also be the case when this model was upsampled from a - // parent tile. - TUniquePtr pResult = MakeUnique(); - pResult->pTexture = extension.pTexture; - pResult->textureIndex = textureIndex; - return pResult; + return std::nullopt; +} + +TUniquePtr createTextureResourceFromImageCesium( + CesiumGltf::ImageCesium& imageCesium, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + EPixelFormat pixelFormat) { + if (GRHISupportsAsyncTextureCreation && !imageCesium.pixelData.empty()) { + // Create RHI texture resource on this worker + // thread, and then hand it off to the renderer + // thread. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreateRHITexture2D) + + FTexture2DRHIRef textureReference = + CreateRHITexture2D_Async(imageCesium, pixelFormat, sRGB); + TUniquePtr textureResource = + MakeUnique( + textureReference, + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0); + + // Clear the now-unnecessary copy of the pixel data. + // Calling clear() isn't good enough because it + // won't actually release the memory. + std::vector pixelData; + imageCesium.pixelData.swap(pixelData); + + std::vector mipPositions; + imageCesium.mipPositions.swap(mipPositions); + + return textureResource; + } else { + // The RHI texture will be created later on the + // render thread, directly from this texture source. + // We need valid pixelData here, though. + if (imageCesium.pixelData.empty()) { + return nullptr; + } + + return MakeUnique( + imageCesium, + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0); } +} +std::optional getSourceIndexFromModelAndTexture( + const CesiumGltf::Model& model, + const CesiumGltf::Texture& texture) { const CesiumGltf::ExtensionKhrTextureBasisu* pKtxExtension = texture.getExtension(); const CesiumGltf::ExtensionTextureWebp* pWebpExtension = @@ -277,9 +364,9 @@ TUniquePtr loadTextureFromModelAnyThreadPart( "KTX texture source index must be non-negative and less than %d, but is %d"), model.images.size(), pKtxExtension->source); - return nullptr; + return std::nullopt; } - source = pKtxExtension->source; + return std::optional(pKtxExtension->source); } else if (pWebpExtension) { if (pWebpExtension->source < 0 || pWebpExtension->source >= model.images.size()) { @@ -290,9 +377,9 @@ TUniquePtr loadTextureFromModelAnyThreadPart( "WebP texture source index must be non-negative and less than %d, but is %d"), model.images.size(), pWebpExtension->source); - return nullptr; + return std::nullopt; } - source = pWebpExtension->source; + return std::optional(pWebpExtension->source); } else { if (texture.source < 0 || texture.source >= model.images.size()) { UE_LOG( @@ -302,37 +389,71 @@ TUniquePtr loadTextureFromModelAnyThreadPart( "Texture source index must be non-negative and less than %d, but is %d"), model.images.size(), texture.source); - return nullptr; + return std::nullopt; } - source = texture.source; + return std::optional(texture.source); } +} - CesiumGltf::Image& image = model.images[source]; - const CesiumGltf::Sampler& sampler = - model.getSafe(model.samplers, texture.sampler); - - TUniquePtr pResult = - loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); +CesiumAsync::Future> +loadTextureFromModelAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::Model& model, + CesiumGltf::Texture& texture, + bool sRGB) { + int64_t textureIndex = + model.textures.empty() ? -1 : &texture - &model.textures[0]; + if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { + textureIndex = -1; + } - if (pResult) { - extension.pTexture = pResult->pTexture; + std::lock_guard lock(ExtensionUnrealTextureResource::textureResourceMutex); - // Note the index of this texture within the glTF. + ExtensionUnrealTexture& extension = + texture.addExtension(); + if (extension.pTexture && (extension.pTexture->getUnrealTexture() || + extension.pTexture->getTextureResource())) { + // There's an existing Unreal texture for this glTF texture. This will + // happen if this texture is used by multiple primitives on the same + // model. It will also be the case when this model was upsampled from a + // parent tile. + TUniquePtr pResult = MakeUnique(); + pResult->pTexture = extension.pTexture; pResult->textureIndex = textureIndex; + return asyncSystem.createResolvedFuture>( + MoveTemp(pResult)); } - return pResult; -} -TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::SharedAsset& image, - const CesiumGltf::Sampler& sampler, - bool sRGB) { - TextureAddress addressX = convertGltfWrapSToUnreal(sampler.wrapS); - TextureAddress addressY = convertGltfWrapTToUnreal(sampler.wrapT); + std::optional optionalSourceIndex = + getSourceIndexFromModelAndTexture(model, texture); + if (!optionalSourceIndex.has_value()) { + return asyncSystem.createResolvedFuture>( + nullptr); + }; - TextureFilter filter = TextureFilter::TF_Default; - bool useMipMapsIfAvailable = false; + CesiumGltf::Image& image = model.images[*optionalSourceIndex]; + const CesiumGltf::Sampler& sampler = + model.getSafe(model.samplers, texture.sampler); + + return loadTextureFromImageAndSamplerAnyThreadPart( + asyncSystem, + image.cesium, + sampler, + sRGB) + .thenImmediately([pExtension = &extension, + textureIndex](TUniquePtr pResult) { + std::lock_guard lock( + ExtensionUnrealTextureResource::textureResourceMutex); + if (pResult) { + pExtension->pTexture = pResult->pTexture; + pResult->textureIndex = textureIndex; + } + + return pResult; + }); +} +TextureFilter getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { // Unreal Engine's available filtering modes are only nearest, bilinear, // trilinear, and "default". Default means "use the texture group settings", // and the texture group settings are defined in a config file and can @@ -348,59 +469,74 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( if (sampler.magFilter && !sampler.minFilter) { // Only a magnification filter is specified, so use it. - filter = - sampler.magFilter.value() == CesiumGltf::Sampler::MagFilter::NEAREST - ? TextureFilter::TF_Nearest - : TextureFilter::TF_Default; + return sampler.magFilter.value() == CesiumGltf::Sampler::MagFilter::NEAREST + ? TextureFilter::TF_Nearest + : TextureFilter::TF_Default; } else if (sampler.minFilter) { // Use specified minFilter. switch (sampler.minFilter.value()) { case CesiumGltf::Sampler::MinFilter::NEAREST: case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - filter = TextureFilter::TF_Nearest; - break; + return TextureFilter::TF_Nearest; case CesiumGltf::Sampler::MinFilter::LINEAR: case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - filter = TextureFilter::TF_Bilinear; - break; + return TextureFilter::TF_Bilinear; default: - filter = TextureFilter::TF_Default; - break; + return TextureFilter::TF_Default; } } else { // No filtering specified at all, let the texture group decide. - filter = TextureFilter::TF_Default; + return TextureFilter::TF_Default; } +} +bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { switch (sampler.minFilter.value_or( CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - useMipMapsIfAvailable = true; + return true; break; default: // LINEAR and NEAREST - useMipMapsIfAvailable = false; - break; + return false; } +} - auto loadResult = loadTextureAnyThreadPart( - *image, - addressX, - addressY, - filter, - useMipMapsIfAvailable, +CesiumAsync::Future> +loadTextureFromImageAndSamplerAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::SharedAsset& image, + const CesiumGltf::Sampler& sampler, + bool sRGB) { + return loadTextureAnyThreadPart( + asyncSystem, + image, + convertGltfWrapSToUnreal(sampler.wrapS), + convertGltfWrapTToUnreal(sampler.wrapT), + getTextureFilterFromSampler(sampler), + getUseMipmapsIfAvailableFromSampler(sampler), // TODO: allow texture group to be configured on Cesium3DTileset. TEXTUREGROUP_World, sRGB, std::nullopt); +} - if (loadResult != nullptr) { - loadResult->pTexture->setSharedImage(image); - } - - return loadResult; +TUniquePtr loadTextureFromImageAndSamplerAnyThreadPartSync( + CesiumGltf::ImageCesium& image, + const CesiumGltf::Sampler& sampler, + bool sRGB) { + return loadTextureAnyThreadPartSync( + image, + convertGltfWrapSToUnreal(sampler.wrapS), + convertGltfWrapTToUnreal(sampler.wrapT), + getTextureFilterFromSampler(sampler), + getUseMipmapsIfAvailableFromSampler(sampler), + // TODO: allow texture group to be configured on Cesium3DTileset. + TEXTUREGROUP_World, + sRGB, + std::nullopt); } static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { @@ -432,8 +568,9 @@ static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { return pTexture; } -TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::ImageCesium& imageCesium, +CesiumAsync::Future> loadTextureAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::SharedAsset& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -441,63 +578,63 @@ TUniquePtr loadTextureAnyThreadPart( TextureGroup group, bool sRGB, std::optional overridePixelFormat) { - EPixelFormat pixelFormat; - if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { - switch (imageCesium.compressedPixelFormat) { - case GpuCompressedPixelFormat::ETC1_RGB: - pixelFormat = EPixelFormat::PF_ETC1; - break; - case GpuCompressedPixelFormat::ETC2_RGBA: - pixelFormat = EPixelFormat::PF_ETC2_RGBA; - break; - case GpuCompressedPixelFormat::BC1_RGB: - pixelFormat = EPixelFormat::PF_DXT1; - break; - case GpuCompressedPixelFormat::BC3_RGBA: - pixelFormat = EPixelFormat::PF_DXT5; - break; - case GpuCompressedPixelFormat::BC4_R: - pixelFormat = EPixelFormat::PF_BC4; - break; - case GpuCompressedPixelFormat::BC5_RG: - pixelFormat = EPixelFormat::PF_BC5; - break; - case GpuCompressedPixelFormat::BC7_RGBA: - pixelFormat = EPixelFormat::PF_BC7; - break; - case GpuCompressedPixelFormat::ASTC_4x4_RGBA: - pixelFormat = EPixelFormat::PF_ASTC_4x4; - break; - case GpuCompressedPixelFormat::PVRTC2_4_RGBA: - pixelFormat = EPixelFormat::PF_PVRTC2; - break; - case GpuCompressedPixelFormat::ETC2_EAC_R11: - pixelFormat = EPixelFormat::PF_ETC2_R11_EAC; - break; - case GpuCompressedPixelFormat::ETC2_EAC_RG11: - pixelFormat = EPixelFormat::PF_ETC2_RG11_EAC; - break; - default: - // Unsupported compressed texture format. - return nullptr; - }; - } else if (overridePixelFormat) { - pixelFormat = *overridePixelFormat; - } else { - switch (imageCesium.channels) { - case 1: - pixelFormat = PF_R8; - break; - case 2: - pixelFormat = PF_R8G8; - break; - case 3: - case 4: - default: - pixelFormat = PF_R8G8B8A8; - }; + return ExtensionUnrealTextureResource::loadTextureResource( + asyncSystem, + *image, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + overridePixelFormat) + .thenImmediately( + [addressX, addressY, filter, group, sRGB, pImage = &image]( + TUniquePtr textureResource) + -> TUniquePtr { + TUniquePtr pResult = + MakeUnique(); + pResult->pTexture = new ReferenceCountedUnrealTexture(); + + pResult->addressX = addressX; + pResult->addressY = addressY; + pResult->filter = filter; + pResult->group = group; + pResult->sRGB = sRGB; + + pResult->pTexture->setTextureResource(MoveTemp(textureResource)); + pResult->pTexture->setSharedImage(*pImage); + return pResult; + }); +} + +TUniquePtr loadTextureAnyThreadPartSync( + CesiumGltf::ImageCesium& image, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat) { + std::optional optionalPixelFormat = + getPixelFormatForImageCesium(image, overridePixelFormat); + if (!optionalPixelFormat.has_value()) { + return nullptr; } + EPixelFormat pixelFormat = optionalPixelFormat.value(); + TUniquePtr textureResource = + createTextureResourceFromImageCesium( + image, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + pixelFormat); + TUniquePtr pResult = MakeUnique(); pResult->pTexture = new ReferenceCountedUnrealTexture(); @@ -507,82 +644,7 @@ TUniquePtr loadTextureAnyThreadPart( pResult->group = group; pResult->sRGB = sRGB; - // Store the current size of the pixel data, because we're about to clear it - // but we still want to have an accurate estimation of the size of the image - // for caching purposes. - imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - - ExtensionUnrealTextureResource& extension = - imageCesium.addExtension(); - - if (extension.pTextureResource != nullptr) { - pResult->pTexture->setTextureResource( - MakeUnique( - extension.pTextureResource, - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0)); - } else if ( - GRHISupportsAsyncTextureCreation && !imageCesium.pixelData.empty()) { - // Create RHI texture resource on this worker thread, and then hand it off - // to the renderer thread. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreateRHITexture2D) - - FTexture2DRHIRef textureReference = - CreateRHITexture2D_Async(imageCesium, pixelFormat, sRGB); - pResult->pTexture->setTextureResource( - MakeUnique( - textureReference, - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0)); - - // Clear the now-unnecessary copy of the pixel data. Calling clear() isn't - // good enough because it won't actually release the memory. - std::vector pixelData; - imageCesium.pixelData.swap(pixelData); - - std::vector mipPositions; - imageCesium.mipPositions.swap(mipPositions); - } else { - // The RHI texture will be created later on the render thread, directly - // from this texture source. We need valid pixelData here, though. - if (imageCesium.pixelData.empty()) { - return nullptr; - } - - pResult->pTexture->setTextureResource( - MakeUnique( - imageCesium, - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0)); - } - - check(pResult->pTexture->getTextureResource() != nullptr); - extension.pTextureResource = pResult->pTexture->getTextureResource().Get(); - + pResult->pTexture->setTextureResource(MoveTemp(textureResource)); return pResult; } @@ -674,4 +736,187 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } } +std::mutex ExtensionUnrealTextureResource::textureResourceMutex; + +inline void ExtensionUnrealTextureResource::preprocessImage( + const CesiumAsync::AsyncSystem& asyncSystem, + const CesiumGltf::Sampler& sampler, + CesiumGltf::ImageCesium& image) { + std::lock_guard lock(textureResourceMutex); + + ExtensionUnrealTextureResource& extension = + image.addExtension(); + + // Future already exists, we don't need to do anything else. + if (extension.preprocessFuture != nullptr) { + return; + } + + // Generate mipmaps if needed. + // An image needs mipmaps generated for it if: + // 1. It is used by a Texture that has a Sampler with a mipmap filtering + // mode, and + // 2. It does not already have mipmaps. + // It's ok if an image has mipmaps even if not all textures will use them. + // There's no reason to have two RHI textures, one with and one without + // mips. + bool needsMipmaps; + switch (sampler.minFilter.value_or( + CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + needsMipmaps = true; + break; + default: // LINEAR and NEAREST + needsMipmaps = false; + break; + } + + if (!needsMipmaps || image.pixelData.empty()) { + extension.preprocessFuture = + MakeShared>( + asyncSystem.createResolvedFuture(&image).share()); + return; + } + + // We need mipmaps generated. + extension.preprocessFuture = + MakeShared>( + asyncSystem + .runInWorkerThread([pImage = &image]() { + std::optional errorMessage = + CesiumGltfReader::GltfReader::generateMipMaps(*pImage); + if (errorMessage) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s"), + UTF8_TO_TCHAR(errorMessage->c_str())); + } + return pImage; + }) + .share()); +} + +CesiumAsync::Future> +ExtensionUnrealTextureResource::loadTextureResource( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::ImageCesium& imageCesium, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat) { + std::optional optionalPixelFormat = + getPixelFormatForImageCesium(imageCesium, overridePixelFormat); + if (!optionalPixelFormat.has_value()) { + return asyncSystem + .createResolvedFuture>(nullptr); + } + + EPixelFormat pixelFormat = optionalPixelFormat.value(); + + std::lock_guard lock(textureResourceMutex); + + ExtensionUnrealTextureResource& extension = + imageCesium.addExtension(); + + // Already have a texture resource, just use that. + if (extension.pTextureResource != nullptr) { + return asyncSystem + .createResolvedFuture>( + MakeUnique( + extension.pTextureResource.Get(), + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0)); + } + + if (extension.resourceLoadingFuture == nullptr) { + // We need to start loading the texture resource + check(extension.preprocessFuture != nullptr); + + extension.resourceLoadingFuture = + MakeShared>( + extension.preprocessFuture + ->thenImmediately( + [addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + pixelFormat, + sRGB](ImageCesium* imageCesium) + -> FCesiumTextureResourceBase* { + // Store the current size of the pixel data, because + // we're about to clear it but we still want to have + // an accurate estimation of the size of the image for + // caching purposes. + imageCesium->sizeBytes = + int64_t(imageCesium->pixelData.size()); + + TUniquePtr textureResource = + createTextureResourceFromImageCesium( + *imageCesium, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + pixelFormat); + + check(textureResource != nullptr); + + { + std::lock_guard lock(textureResourceMutex); + ExtensionUnrealTextureResource& extension = + imageCesium->addExtension< + ExtensionUnrealTextureResource>(); + + extension.pTextureResource = + TSharedPtr( + textureResource.Release()); + return extension.pTextureResource.Get(); + } + }) + .share()); + } + + return extension.resourceLoadingFuture->thenImmediately( + [group, + imageCesium, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable](FCesiumTextureResourceBase* existingResource) + -> TUniquePtr { + return MakeUnique( + existingResource, + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0); + }); +} + } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index bc657eaa4..25423a141 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -100,7 +100,9 @@ struct LoadedTextureResult { * nullptr before the first call to `loadTextureFromModelAnyThreadPart` during * the glTF load process. */ -TUniquePtr loadTextureFromModelAnyThreadPart( +CesiumAsync::Future> +loadTextureFromModelAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::Model& model, CesiumGltf::Texture& texture, bool sRGB); @@ -121,11 +123,34 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * parameter is not nullptr, the provided image's `pixelData` is not required * and can be empty. */ -TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( +CesiumAsync::Future> +loadTextureFromImageAndSamplerAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::SharedAsset& image, const CesiumGltf::Sampler& sampler, bool sRGB); +/** + * Does the asynchronous part of renderer resource preparation for a glTF + * `Image` with the given `Sampler` settings. + * + * The `cesium.pixelData` will be removed from the image so that it can be + * passed to Unreal's renderer thread without copying it. + * + * @param image The glTF image for each to create a texture. + * @param sampler The sampler settings to use with the texture. + * @param sRGB True if the texture should be treated as sRGB; false if it should + * be treated as linear. + * @param pExistingImageResource An existing RHI texture resource that has been + * created for this image, or nullptr if one hasn't been created yet. When this + * parameter is not nullptr, the provided image's `pixelData` is not required + * and can be empty. + */ +TUniquePtr loadTextureFromImageAndSamplerAnyThreadPartSync( + CesiumGltf::ImageCesium& image, + const CesiumGltf::Sampler& sampler, + bool sRGB); + /** * @brief Does the asynchronous part of renderer resource preparation for * this image. Should be called in a background thread. @@ -147,10 +172,26 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * created for this image, or nullptr if one hasn't been created yet. When this * parameter is not nullptr, the provided image's `pixelData` is not required * and can be empty. - * @return The loaded texture. + * @return A future that resolves to the loaded texture. + */ +CesiumAsync::Future> loadTextureAnyThreadPart( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::SharedAsset& image, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat); + +/** + * @brief Equivalent to \link{loadTextureAnyThreadPart}, but returns the texture + * itself rather than a future. Intended to be used for loading raster overlay + * textures. */ -TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::ImageCesium& imageCesium, +TUniquePtr loadTextureAnyThreadPartSync( + CesiumGltf::ImageCesium& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -206,4 +247,52 @@ TextureAddress convertGltfWrapSToUnreal(int32_t wrapS); */ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT); +struct ExtensionUnrealTexture { + static inline constexpr const char* TypeName = "ExtensionUnrealTexture"; + static inline constexpr const char* ExtensionName = "PRIVATE_unreal_texture"; + + CesiumUtility::IntrusivePointer< + CesiumTextureUtility::ReferenceCountedUnrealTexture> + pTexture = nullptr; +}; + +struct ExtensionUnrealTextureResource { + static inline constexpr const char* TypeName = + "ExtensionUnrealTextureResource"; + static inline constexpr const char* ExtensionName = + "PRIVATE_unreal_texture_resource"; + + ExtensionUnrealTextureResource() {} + + TSharedPtr pTextureResource = nullptr; + + // If a preprocessing step is required (such as generating mipmaps), this + // future returns the preprocessed image. If no preprocessing is required, + // this just passes the image through. + TSharedPtr> + preprocessFuture = nullptr; + + TSharedPtr> + resourceLoadingFuture = nullptr; + + static void preprocessImage( + const CesiumAsync::AsyncSystem& asyncSystem, + const CesiumGltf::Sampler& sampler, + CesiumGltf::ImageCesium& image); + + static CesiumAsync::Future> + loadTextureResource( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::ImageCesium& imageCesium, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat); + + static std::mutex textureResourceMutex; +}; + } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 1813e8f4d..41b6a10d9 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -9,6 +9,7 @@ #include "Engine/World.h" #include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" +#include #define TEST_SCREEN_WIDTH 1280 #define TEST_SCREEN_HEIGHT 720 @@ -23,12 +24,12 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST( static void setupForSharedImages(SceneGenerationContext& context) { context.setCommonProperties( FVector(21.16677692, -67.38013505, -6375355.1944), - FVector(-12, 5, -5), + FVector(-12, -1300, -5), FRotator(0, 90, 0), 60.0f); context.georeference->SetOriginEarthCenteredEarthFixed(FVector(0, 0, 0)); - context.pawn->SetActorLocation(FVector(-12, 5, -5)); + context.pawn->SetActorLocation(FVector(-12, -1300, -5)); context.sunSky->TimeZone = 9.0f; context.sunSky->UpdateSun(); @@ -46,30 +47,34 @@ static void setupForSharedImages(SceneGenerationContext& context) { *FPaths::ProjectPluginsDir()); tileset->SetUrl(FString::Printf( TEXT( - "file:///%scesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages/tileset.json"), + "file://%scesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages/tileset.json"), *FullPluginsPath)); tileset->SetActorLabel(TEXT("SharedImages")); + tileset->SetGeoreference(georeference); + tileset->SuspendUpdate = false; context.tilesets.push_back(tileset); - UCesiumGlobeAnchorComponent* anchor = - NewObject(tileset); - anchor->SetGeoreference(georeference); - anchor->RegisterComponent(); - - anchor->MoveToEarthCenteredEarthFixedPosition(FVector::ZeroVector); + UCesiumGlobeAnchorComponent* GlobeAnchorComponent = + NewObject(tileset, TEXT("GlobeAnchor")); + tileset->AddInstanceComponent(GlobeAnchorComponent); + GlobeAnchorComponent->SetAdjustOrientationForGlobeWhenMoving(false); + GlobeAnchorComponent->SetGeoreference(georeference); + GlobeAnchorComponent->RegisterComponent(); + GlobeAnchorComponent->MoveToEarthCenteredEarthFixedPosition( + FVector(0.0, 0.0, 0.0)); } -void googleSetupRefreshTilesets( +void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { context.refreshTilesets(); + context.setSuspendUpdate(false); } bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { std::vector testPasses; - testPasses.push_back( - TestPass{"Warm Cache", googleSetupRefreshTilesets, nullptr}); + testPasses.push_back(TestPass{"Refresh Pass", tilesetPass, nullptr}); return RunLoadTest( GetBeautifiedTestName(), diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index b6204f49d..0670c26d7 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -1,8 +1,11 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumTextureUtility.h" +#include "CesiumAsync/AsyncSystem.h" #include "Misc/AutomationTest.h" #include "RenderingThread.h" +#include +#include using namespace CesiumGltf; using namespace CesiumTextureUtility; @@ -16,6 +19,8 @@ BEGIN_DEFINE_SPEC( std::vector originalPixels; std::vector originalMipPixels; SharedAsset imageCesium; +CesiumAsync::AsyncSystem asyncSystem = + CesiumAsync::AsyncSystem(std::make_shared()); void RunTests(); @@ -44,7 +49,8 @@ void CesiumTextureUtilitySpec::Define() { 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; originalMipPixels.clear(); - imageCesium = SharedAsset(ImageCesium{}); + ImageCesium image{}; + imageCesium = SharedAsset(image); imageCesium->width = 3; imageCesium->height = 2; TestEqual( @@ -100,7 +106,7 @@ void CesiumTextureUtilitySpec::Define() { void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium non-sRGB", [this]() { - TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( + TUniquePtr pHalfLoaded = loadTextureAnyThreadPartSync( *imageCesium, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, @@ -124,7 +130,7 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("ImageCesium sRGB", [this]() { - TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( + TUniquePtr pHalfLoaded = loadTextureAnyThreadPartSync( *imageCesium, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, @@ -156,9 +162,11 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( + asyncSystem, imageCesium, sampler, - false); + false) + .waitInMainThread(); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); IntrusivePointer pRefCountedTexture = @@ -190,7 +198,8 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(model, texture, true); + loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) + .waitInMainThread(); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -233,12 +242,22 @@ void CesiumTextureUtilitySpec::RunTests() { texture2.sampler = 1; TUniquePtr pHalfLoaded1 = - loadTextureFromModelAnyThreadPart(model, model.textures[0], true); + loadTextureFromModelAnyThreadPart( + asyncSystem, + model, + model.textures[0], + true) + .waitInMainThread(); TestNotNull("pHalfLoaded1", pHalfLoaded1.Get()); TestNotNull("pHalfLoaded1->pTexture", pHalfLoaded1->pTexture.get()); TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart(model, model.textures[1], false); + loadTextureFromModelAnyThreadPart( + asyncSystem, + model, + model.textures[1], + false) + .waitInMainThread(); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); @@ -290,7 +309,8 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(model, texture, true); + loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) + .waitInMainThread(); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -310,7 +330,12 @@ void CesiumTextureUtilitySpec::RunTests() { // previously-created texture. Model model2 = model; TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart(model2, model.textures[0], true); + loadTextureFromModelAnyThreadPart( + asyncSystem, + model2, + model.textures[0], + true) + .waitInMainThread(); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( @@ -339,7 +364,8 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(model, texture, true); + loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) + .waitInMainThread(); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -358,7 +384,12 @@ void CesiumTextureUtilitySpec::RunTests() { // This time there's no more pixel data, so it's necessary to use the // previously-created texture. TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart(model, model.textures[0], true); + loadTextureFromModelAnyThreadPart( + asyncSystem, + model, + model.textures[0], + true) + .waitInMainThread(); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( diff --git a/extern/cesium-native b/extern/cesium-native index 001849307..2f852d0b9 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 0018493071c837977ad9ecbf5ed06cf7958608a4 +Subproject commit 2f852d0b9fab5553960a09f2f18e307ef172622c From 21f034fd2b6697ec71748060f4b3acaa43c23868 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 3 Sep 2024 11:30:04 -0400 Subject: [PATCH 06/68] Fixed UniquePtr issues --- .../Private/CesiumGltfComponent.cpp | 60 +++++++++---------- .../Private/CesiumTextureUtility.cpp | 6 +- extern/cesium-native | 2 +- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 56f83c529..b96605b6b 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1519,33 +1519,33 @@ static CesiumAsync::Future loadPrimitive( std::vector>> - textureLoads{ - loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness.baseColorTexture, - true), - loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness - .metallicRoughnessTexture, - false), - loadTexture( - asyncSystem, - model, - workingData->material.normalTexture, - false), - loadTexture( - asyncSystem, - model, - workingData->material.occlusionTexture, - false), - loadTexture( - asyncSystem, - model, - workingData->material.emissiveTexture, - true)}; + textureLoads; + + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness.baseColorTexture, + true)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness.metallicRoughnessTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material.normalTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material.occlusionTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material.emissiveTexture, + true)); return asyncSystem.all(std::move(textureLoads)) .thenImmediately([&primitiveResult, @@ -1555,9 +1555,9 @@ static CesiumAsync::Future loadPrimitive( positionAccessor, positionView, indicesView, - &ellipsoid](std::vector< - TUniquePtr>&& - results) { + &ellipsoid]( + std::vector> + results) { Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; Mesh& mesh = *options.pMeshOptions->pMesh; diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 27d887a03..9847dfddc 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -449,7 +449,7 @@ loadTextureFromModelAnyThreadPart( pResult->textureIndex = textureIndex; } - return pResult; + return std::move(pResult); }); } @@ -604,7 +604,7 @@ CesiumAsync::Future> loadTextureAnyThreadPart( pResult->pTexture->setTextureResource(MoveTemp(textureResource)); pResult->pTexture->setSharedImage(*pImage); - return pResult; + return std::move(pResult); }); } @@ -738,7 +738,7 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { std::mutex ExtensionUnrealTextureResource::textureResourceMutex; -inline void ExtensionUnrealTextureResource::preprocessImage( +void ExtensionUnrealTextureResource::preprocessImage( const CesiumAsync::AsyncSystem& asyncSystem, const CesiumGltf::Sampler& sampler, CesiumGltf::ImageCesium& image) { diff --git a/extern/cesium-native b/extern/cesium-native index 2f852d0b9..a5829effb 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 2f852d0b9fab5553960a09f2f18e307ef172622c +Subproject commit a5829effbcf1f7172de1325af5b0aef5f8047643 From 164379e876abf0f0e7dc176bc4fd287a6819ccd0 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 3 Sep 2024 16:15:26 -0400 Subject: [PATCH 07/68] Attempting to resolve invalid pointer issue with material --- .../Private/CesiumGltfComponent.cpp | 116 +++++++++++------- .../Private/CesiumTextureUtility.cpp | 11 +- .../Private/CesiumTextureUtility.h | 2 +- .../Private/UnrealAssetAccessor.cpp | 4 + 4 files changed, 83 insertions(+), 50 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index b96605b6b..35f0d4f77 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1158,27 +1158,27 @@ static CesiumAsync::Future loadPrimitive( bool hasNormals; AccessorView normalAccessor; AccessorView tangentAccessor; - const Material& material; - const MaterialPBRMetallicRoughness& pbrMetallicRoughness; + const Material* material; + const MaterialPBRMetallicRoughness* pbrMetallicRoughness; TUniquePtr RenderData = nullptr; bool duplicateVertices; TArray indices; bool hasVertexColors; PrimitiveLoadingWorkingData( - const Material& material_, - const MaterialPBRMetallicRoughness& pbrMetallicRoughness_) + const Material* material_, + const MaterialPBRMetallicRoughness* pbrMetallicRoughness_) : material(material_), pbrMetallicRoughness(pbrMetallicRoughness_) {} }; - return asyncSystem.runInWorkerThread([asyncSystem, + return asyncSystem.runInWorkerThread([&asyncSystem, pPrimitiveResult = &primitiveResult, - transform, - options, - positionAccessor, - positionView, - indicesView, - ellipsoid]() { + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, + &ellipsoid]() { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) LoadPrimitiveResult& primitiveResult = *pPrimitiveResult; @@ -1260,7 +1260,13 @@ static CesiumAsync::Future loadPrimitive( : defaultPbrMetallicRoughness; TSharedPtr workingData = - MakeShared(material, pbrMetallicRoughness); + MakeShared( + &material, + &pbrMetallicRoughness); + + UE_LOG(LogCesium, Log, TEXT("material: %p"), &material); + + check(workingData->material == &model.materials[materialID]); auto normalAccessorIt = primitive.attributes.find("NORMAL"); if (normalAccessorIt != primitive.attributes.end()) { @@ -1280,7 +1286,7 @@ static CesiumAsync::Future loadPrimitive( } primitiveResult.isUnlit = - workingData->material.hasExtension() && + workingData->material->hasExtension() && !options.pMeshOptions->pNodeOptions->pModelOptions ->ignoreKhrMaterialsUnlit; @@ -1295,11 +1301,11 @@ static CesiumAsync::Future loadPrimitive( primitiveResult.isUnlit = true; } - bool hasNormalMap = workingData->material.normalTexture.has_value(); + bool hasNormalMap = workingData->material->normalTexture.has_value(); if (hasNormalMap) { const CesiumGltf::Texture* pTexture = Model::getSafe( &model.textures, - workingData->material.normalTexture->index); + workingData->material->normalTexture->index); hasNormalMap = pTexture != nullptr && Model::getSafe(&model.images, pTexture->source) != nullptr; } @@ -1326,15 +1332,15 @@ static CesiumAsync::Future loadPrimitive( } return applyWaterMask(asyncSystem, model, primitive, primitiveResult) - .thenImmediately([asyncSystem, + .thenImmediately([&asyncSystem, &primitiveResult, workingData, - transform, - options, - positionAccessor, - positionView, - indicesView, - ellipsoid]() { + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, + &ellipsoid]() { Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; Mesh& mesh = *options.pMeshOptions->pMesh; @@ -1524,37 +1530,37 @@ static CesiumAsync::Future loadPrimitive( textureLoads.push_back(loadTexture( asyncSystem, model, - workingData->pbrMetallicRoughness.baseColorTexture, + workingData->pbrMetallicRoughness->baseColorTexture, true)); textureLoads.push_back(loadTexture( asyncSystem, model, - workingData->pbrMetallicRoughness.metallicRoughnessTexture, + workingData->pbrMetallicRoughness->metallicRoughnessTexture, false)); textureLoads.push_back(loadTexture( asyncSystem, model, - workingData->material.normalTexture, + workingData->material->normalTexture, false)); textureLoads.push_back(loadTexture( asyncSystem, model, - workingData->material.occlusionTexture, + workingData->material->occlusionTexture, false)); textureLoads.push_back(loadTexture( asyncSystem, model, - workingData->material.emissiveTexture, + workingData->material->emissiveTexture, true)); return asyncSystem.all(std::move(textureLoads)) .thenImmediately([&primitiveResult, workingData, - transform, - options, - positionAccessor, - positionView, - indicesView, + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, &ellipsoid]( std::vector> results) { @@ -1592,7 +1598,7 @@ static CesiumAsync::Future loadPrimitive( workingData->duplicateVertices, workingData->StaticMeshBuildVertices, workingData->indices, - workingData->pbrMetallicRoughness.baseColorTexture, + workingData->pbrMetallicRoughness->baseColorTexture, gltfToUnrealTexCoordMap); primitiveResult.textureCoordinateParameters ["metallicRoughnessTextureCoordinateIndex"] = @@ -1603,7 +1609,7 @@ static CesiumAsync::Future loadPrimitive( workingData->StaticMeshBuildVertices, workingData->indices, workingData->pbrMetallicRoughness - .metallicRoughnessTexture, + ->metallicRoughnessTexture, gltfToUnrealTexCoordMap); primitiveResult.textureCoordinateParameters ["normalTextureCoordinateIndex"] = @@ -1613,7 +1619,7 @@ static CesiumAsync::Future loadPrimitive( workingData->duplicateVertices, workingData->StaticMeshBuildVertices, workingData->indices, - workingData->material.normalTexture, + workingData->material->normalTexture, gltfToUnrealTexCoordMap); primitiveResult.textureCoordinateParameters ["occlusionTextureCoordinateIndex"] = @@ -1623,7 +1629,7 @@ static CesiumAsync::Future loadPrimitive( workingData->duplicateVertices, workingData->StaticMeshBuildVertices, workingData->indices, - workingData->material.occlusionTexture, + workingData->material->occlusionTexture, gltfToUnrealTexCoordMap); primitiveResult.textureCoordinateParameters ["emissiveTextureCoordinateIndex"] = @@ -1633,7 +1639,7 @@ static CesiumAsync::Future loadPrimitive( workingData->duplicateVertices, workingData->StaticMeshBuildVertices, workingData->indices, - workingData->material.emissiveTexture, + workingData->material->emissiveTexture, gltfToUnrealTexCoordMap); for (size_t i = 0; @@ -1834,10 +1840,11 @@ static CesiumAsync::Future loadPrimitive( LODResources.bHasReversedIndices = false; LODResources.bHasReversedDepthOnlyIndices = false; - primitiveResult.pModel = &model; + primitiveResult.pModel = + options.pMeshOptions->pNodeOptions->pModelOptions->pModel; primitiveResult.pMeshPrimitive = &primitive; primitiveResult.RenderData = MoveTemp(workingData->RenderData); - primitiveResult.pMaterial = &workingData->material; + primitiveResult.pMaterial = workingData->material; primitiveResult.pCollisionMesh = nullptr; double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; @@ -2194,12 +2201,20 @@ static void loadInstancingData( } } +static LoadNodeResult& addLoadNodeResult( + std::vector& loadNodeResults, + std::mutex& mutex) { + std::lock_guard lock(mutex); + return loadNodeResults.emplace_back(); +} + static CesiumAsync::SharedFuture loadNode( const CesiumAsync::AsyncSystem& asyncSystem, std::vector& loadNodeResults, const glm::dmat4x4& transform, const CreateNodeOptions& options, - const CesiumGeospatial::Ellipsoid& ellipsoid) { + const CesiumGeospatial::Ellipsoid& ellipsoid, + std::mutex& loadNodeResultsMutex) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadNode) @@ -2224,7 +2239,8 @@ static CesiumAsync::SharedFuture loadNode( Model& model = *options.pModelOptions->pModel; const Node& node = *options.pNode; - LoadNodeResult& result = loadNodeResults.emplace_back(); + LoadNodeResult& result = + addLoadNodeResult(loadNodeResults, loadNodeResultsMutex); glm::dmat4x4 nodeTransform = transform; @@ -2294,6 +2310,7 @@ static CesiumAsync::SharedFuture loadNode( model, asyncSystem, options, + &loadNodeResultsMutex, pLoadNodeResults = &loadNodeResults, nodeTransform, ellipsoid](int result) { @@ -2309,7 +2326,8 @@ static CesiumAsync::SharedFuture loadNode( *pLoadNodeResults, nodeTransform, childNodeOptions, - ellipsoid)); + ellipsoid, + loadNodeResultsMutex)); } } @@ -2494,6 +2512,7 @@ loadModelAnyThreadPart( } std::vector> futures; + std::mutex loadNodeResultsMutex; if (model.scene >= 0 && model.scene < model.scenes.size()) { // Show the default scene @@ -2508,7 +2527,8 @@ loadModelAnyThreadPart( pResult->loadModelResult.nodeResults, rootTransform, nodeOptions, - ellipsoid)); + ellipsoid, + loadNodeResultsMutex)); } } else if (model.scenes.size() > 0) { // There's no default, so show the first scene @@ -2523,7 +2543,8 @@ loadModelAnyThreadPart( pResult->loadModelResult.nodeResults, rootTransform, nodeOptions, - ellipsoid)); + ellipsoid, + loadNodeResultsMutex)); } } else if (model.nodes.size() > 0) { // No scenes at all, use the first node as the root node. @@ -2536,7 +2557,8 @@ loadModelAnyThreadPart( pResult->loadModelResult.nodeResults, rootTransform, nodeOptions, - ellipsoid)); + ellipsoid, + loadNodeResultsMutex)); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. for (Mesh& mesh : model.meshes) { @@ -2561,9 +2583,9 @@ loadModelAnyThreadPart( return asyncSystem.all(std::move(futures)) .thenImmediately( - [ppResult = &pResult](std::vector&& results) + [pResult = pResult.Release()](std::vector&& results) -> TUniquePtr { - return MoveTemp(*ppResult); + return TUniquePtr(pResult); }); } diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 9847dfddc..6dff2b49a 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -736,7 +736,7 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } } -std::mutex ExtensionUnrealTextureResource::textureResourceMutex; +std::recursive_mutex ExtensionUnrealTextureResource::textureResourceMutex; void ExtensionUnrealTextureResource::preprocessImage( const CesiumAsync::AsyncSystem& asyncSystem, @@ -844,13 +844,20 @@ ExtensionUnrealTextureResource::loadTextureResource( } if (extension.resourceLoadingFuture == nullptr) { + // It's possible we got to this path without going through any + // pre-processing steps (like from a test) + if (extension.preprocessFuture == nullptr) { + extension.preprocessFuture = + MakeShared>( + asyncSystem.createResolvedFuture(&imageCesium).share()); + } // We need to start loading the texture resource check(extension.preprocessFuture != nullptr); extension.resourceLoadingFuture = MakeShared>( extension.preprocessFuture - ->thenImmediately( + ->thenInWorkerThread( [addressX, addressY, filter, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 25423a141..dd6e8d033 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -292,7 +292,7 @@ struct ExtensionUnrealTextureResource { bool sRGB, std::optional overridePixelFormat); - static std::mutex textureResourceMutex; + static std::recursive_mutex textureResourceMutex; }; } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp index b1bfccb72..472f83f22 100644 --- a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp +++ b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp @@ -151,6 +151,10 @@ void rejectPromiseOnUnsuccessfulConnection( const CesiumAsync::Promise>& promise, FHttpRequestPtr pRequest) { +#ifndef ENGINE_VERSION_5_4_OR_HIGHER +#define ENGINE_VERSION_5_4_OR_HIGHER 0 +#endif + #if ENGINE_VERSION_5_4_OR_HIGHER if (pRequest->GetStatus() == EHttpRequestStatus::Failed) { EHttpFailureReason failureReason = pRequest->GetFailureReason(); From dd14eb1cdf2a2cebea03959dc016438b16b0478c Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 4 Sep 2024 16:44:39 -0400 Subject: [PATCH 08/68] Fixed crash --- .../Private/CesiumGltfComponent.cpp | 1328 ++++++++--------- Source/CesiumRuntime/Private/LoadGltfResult.h | 7 + 2 files changed, 661 insertions(+), 674 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 35f0d4f77..6ac5102f8 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -472,7 +472,7 @@ static CesiumAsync::SharedFuture applyWaterMaskTexture( return asyncSystem.createResolvedFuture().share(); } -static CesiumAsync::SharedFuture applyWaterMask( +static CesiumAsync::Future applyWaterMask( const CesiumAsync::AsyncSystem& asyncSystem, Model& model, const MeshPrimitive& primitive, @@ -498,8 +498,7 @@ static CesiumAsync::SharedFuture applyWaterMask( pPrimitiveResult->waterMaskScale = waterMaskScaleIt->second.getDoubleOrDefault(1.0); } - }) - .share(); + }); } #pragma region Features Metadata helper functions(load thread) @@ -1165,718 +1164,696 @@ static CesiumAsync::Future loadPrimitive( TArray indices; bool hasVertexColors; - PrimitiveLoadingWorkingData( - const Material* material_, - const MaterialPBRMetallicRoughness* pbrMetallicRoughness_) - : material(material_), pbrMetallicRoughness(pbrMetallicRoughness_) {} + PrimitiveLoadingWorkingData() {} }; - return asyncSystem.runInWorkerThread([&asyncSystem, - pPrimitiveResult = &primitiveResult, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid]() { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - - LoadPrimitiveResult& primitiveResult = *pPrimitiveResult; - - Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; - - if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && - primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && - primitive.mode != MeshPrimitive::Mode::POINTS) { - // TODO: add support for other primitive types. - UE_LOG( - LogCesium, - Warning, - TEXT("Primitive mode %d is not supported"), - primitive.mode); - return asyncSystem.createResolvedFuture(); - } + TSharedPtr workingData = + MakeShared(); - std::string name = "glTF"; + return asyncSystem + .runInWorkerThread([&asyncSystem, + pPrimitiveResult = &primitiveResult, + workingData, + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, + &ellipsoid]() { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) + + LoadPrimitiveResult& primitiveResult = *pPrimitiveResult; + + Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && + primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && + primitive.mode != MeshPrimitive::Mode::POINTS) { + // TODO: add support for other primitive types. + UE_LOG( + LogCesium, + Warning, + TEXT("Primitive mode %d is not supported"), + primitive.mode); + return asyncSystem.createResolvedFuture(); + } - auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); - if (urlIt != model.extras.end()) { - name = urlIt->second.getStringOrDefault("glTF"); - name = constrainLength(name, 256); - } + std::string name = "glTF"; - auto meshIt = std::find_if( - model.meshes.begin(), - model.meshes.end(), - [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); - if (meshIt != model.meshes.end()) { - int64_t meshIndex = meshIt - model.meshes.begin(); - name += " mesh " + std::to_string(meshIndex); - } + auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); + if (urlIt != model.extras.end()) { + name = urlIt->second.getStringOrDefault("glTF"); + name = constrainLength(name, 256); + } - auto primitiveIt = std::find_if( - mesh.primitives.begin(), - mesh.primitives.end(), - [&primitive](const MeshPrimitive& candidate) { - return &candidate == &primitive; - }); - if (primitiveIt != mesh.primitives.end()) { - int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); - name += " primitive " + std::to_string(primitiveIndex); - } + auto meshIt = std::find_if( + model.meshes.begin(), + model.meshes.end(), + [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); + if (meshIt != model.meshes.end()) { + int64_t meshIndex = meshIt - model.meshes.begin(); + name += " mesh " + std::to_string(meshIndex); + } - primitiveResult.name = name; + auto primitiveIt = std::find_if( + mesh.primitives.begin(), + mesh.primitives.end(), + [&primitive](const MeshPrimitive& candidate) { + return &candidate == &primitive; + }); + if (primitiveIt != mesh.primitives.end()) { + int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); + name += " primitive " + std::to_string(primitiveIndex); + } - if (positionView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid position buffer"), - UTF8_TO_TCHAR(name.c_str())); - return asyncSystem.createResolvedFuture(); - } + primitiveResult.name = name; - if constexpr (IsAccessorView::value) { - if (indicesView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid indices buffer"), - UTF8_TO_TCHAR(name.c_str())); - return asyncSystem.createResolvedFuture(); - } - } + if (positionView.status() != AccessorViewStatus::Valid) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid position buffer"), + UTF8_TO_TCHAR(name.c_str())); + return asyncSystem.createResolvedFuture(); + } - int materialID = primitive.material; - const Material& material = - materialID >= 0 && materialID < model.materials.size() - ? model.materials[materialID] - : defaultMaterial; - - const MaterialPBRMetallicRoughness& pbrMetallicRoughness = - material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() - : defaultPbrMetallicRoughness; - - TSharedPtr workingData = - MakeShared( - &material, - &pbrMetallicRoughness); - - UE_LOG(LogCesium, Log, TEXT("material: %p"), &material); - - check(workingData->material == &model.materials[materialID]); - - auto normalAccessorIt = primitive.attributes.find("NORMAL"); - if (normalAccessorIt != primitive.attributes.end()) { - int normalAccessorID = normalAccessorIt->second; - workingData->normalAccessor = - AccessorView(model, normalAccessorID); - workingData->hasNormals = - workingData->normalAccessor.status() == AccessorViewStatus::Valid; - if (!workingData->hasNormals) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), - UTF8_TO_TCHAR(name.c_str())); - } - } + if constexpr (IsAccessorView::value) { + if (indicesView.status() != AccessorViewStatus::Valid) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid indices buffer"), + UTF8_TO_TCHAR(name.c_str())); + return asyncSystem.createResolvedFuture(); + } + } - primitiveResult.isUnlit = - workingData->material->hasExtension() && - !options.pMeshOptions->pNodeOptions->pModelOptions - ->ignoreKhrMaterialsUnlit; + int materialID = primitive.material; + workingData->material = + materialID >= 0 && materialID < model.materials.size() + ? &model.materials[materialID] + : &defaultMaterial; + + primitiveResult.materialId = materialID; + + workingData->pbrMetallicRoughness = + workingData->material->pbrMetallicRoughness + ? &workingData->material->pbrMetallicRoughness.value() + : &defaultPbrMetallicRoughness; + + auto normalAccessorIt = primitive.attributes.find("NORMAL"); + if (normalAccessorIt != primitive.attributes.end()) { + int normalAccessorID = normalAccessorIt->second; + workingData->normalAccessor = + AccessorView(model, normalAccessorID); + workingData->hasNormals = + workingData->normalAccessor.status() == AccessorViewStatus::Valid; + if (!workingData->hasNormals) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), + UTF8_TO_TCHAR(name.c_str())); + } + } - // We can't calculate flat normals for points or lines, so we have to - // force them to be unlit if no normals are specified. Otherwise this - // causes a crash when attempting to calculate flat normals. - bool isTriangles = primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; + primitiveResult.isUnlit = + workingData->material->hasExtension() && + !options.pMeshOptions->pNodeOptions->pModelOptions + ->ignoreKhrMaterialsUnlit; + + // We can't calculate flat normals for points or lines, so we have to + // force them to be unlit if no normals are specified. Otherwise this + // causes a crash when attempting to calculate flat normals. + bool isTriangles = + primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; + + if (!isTriangles && !workingData->hasNormals) { + primitiveResult.isUnlit = true; + } - if (!isTriangles && !workingData->hasNormals) { - primitiveResult.isUnlit = true; - } + bool hasNormalMap = workingData->material->normalTexture.has_value(); + if (hasNormalMap) { + const CesiumGltf::Texture* pTexture = Model::getSafe( + &model.textures, + workingData->material->normalTexture->index); + hasNormalMap = + pTexture != nullptr && + Model::getSafe(&model.images, pTexture->source) != nullptr; + } - bool hasNormalMap = workingData->material->normalTexture.has_value(); - if (hasNormalMap) { - const CesiumGltf::Texture* pTexture = Model::getSafe( - &model.textures, - workingData->material->normalTexture->index); - hasNormalMap = pTexture != nullptr && - Model::getSafe(&model.images, pTexture->source) != nullptr; - } + workingData->needsTangents = + hasNormalMap || options.pMeshOptions->pNodeOptions->pModelOptions + ->alwaysIncludeTangents; + + workingData->hasTangents = false; + auto tangentAccessorIt = primitive.attributes.find("TANGENT"); + if (tangentAccessorIt != primitive.attributes.end()) { + int tangentAccessorID = tangentAccessorIt->second; + workingData->tangentAccessor = + AccessorView(model, tangentAccessorID); + workingData->hasTangents = workingData->tangentAccessor.status() == + AccessorViewStatus::Valid; + if (!workingData->hasTangents) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid tangent buffer."), + UTF8_TO_TCHAR(name.c_str())); + } + } - workingData->needsTangents = - hasNormalMap || options.pMeshOptions->pNodeOptions->pModelOptions - ->alwaysIncludeTangents; - - workingData->hasTangents = false; - auto tangentAccessorIt = primitive.attributes.find("TANGENT"); - if (tangentAccessorIt != primitive.attributes.end()) { - int tangentAccessorID = tangentAccessorIt->second; - workingData->tangentAccessor = - AccessorView(model, tangentAccessorID); - workingData->hasTangents = - workingData->tangentAccessor.status() == AccessorViewStatus::Valid; - if (!workingData->hasTangents) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid tangent buffer."), - UTF8_TO_TCHAR(name.c_str())); - } - } + return applyWaterMask(asyncSystem, model, primitive, primitiveResult); + }) + .thenImmediately([&asyncSystem, + &primitiveResult, + workingData, + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, + &ellipsoid]() { + Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + // The water effect works by animating the normal, and the normal + // is expressed in tangent space. So if we have water, we need + // tangents. + if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { + workingData->needsTangents = true; + } - return applyWaterMask(asyncSystem, model, primitive, primitiveResult) - .thenImmediately([&asyncSystem, - &primitiveResult, - workingData, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid]() { - Model& model = - *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; - - // The water effect works by animating the normal, and the normal - // is expressed in tangent space. So if we have water, we need - // tangents. - if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { - workingData->needsTangents = true; + workingData->RenderData = MakeUnique(); + workingData->RenderData->AllocateLODResources(1); + + FStaticMeshLODResources& LODResources = + workingData->RenderData->LODResources[0]; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) + + const std::vector& min = positionAccessor.min; + const std::vector& max = positionAccessor.max; + glm::dvec3 minPosition{std::numeric_limits::max()}; + glm::dvec3 maxPosition{std::numeric_limits::lowest()}; + if (min.size() != 3 || max.size() != 3) { + for (int32_t i = 0; i < positionView.size(); ++i) { + minPosition.x = + glm::min(minPosition.x, positionView[i].X); + minPosition.y = + glm::min(minPosition.y, positionView[i].Y); + minPosition.z = + glm::min(minPosition.z, positionView[i].Z); + + maxPosition.x = + glm::max(maxPosition.x, positionView[i].X); + maxPosition.y = + glm::max(maxPosition.y, positionView[i].Y); + maxPosition.z = + glm::max(maxPosition.z, positionView[i].Z); + } + } else { + minPosition = glm::dvec3(min[0], min[1], min[2]); + maxPosition = glm::dvec3(max[0], max[1], max[2]); } - workingData->RenderData = MakeUnique(); - workingData->RenderData->AllocateLODResources(1); - - FStaticMeshLODResources& LODResources = - workingData->RenderData->LODResources[0]; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) - - const std::vector& min = positionAccessor.min; - const std::vector& max = positionAccessor.max; - glm::dvec3 minPosition{std::numeric_limits::max()}; - glm::dvec3 maxPosition{std::numeric_limits::lowest()}; - if (min.size() != 3 || max.size() != 3) { - for (int32_t i = 0; i < positionView.size(); ++i) { - minPosition.x = - glm::min(minPosition.x, positionView[i].X); - minPosition.y = - glm::min(minPosition.y, positionView[i].Y); - minPosition.z = - glm::min(minPosition.z, positionView[i].Z); - - maxPosition.x = - glm::max(maxPosition.x, positionView[i].X); - maxPosition.y = - glm::max(maxPosition.y, positionView[i].Y); - maxPosition.z = - glm::max(maxPosition.z, positionView[i].Z); - } - } else { - minPosition = glm::dvec3(min[0], min[1], min[2]); - maxPosition = glm::dvec3(max[0], max[1], max[2]); - } + minPosition *= CesiumPrimitiveData::positionScaleFactor; + maxPosition *= CesiumPrimitiveData::positionScaleFactor; - minPosition *= CesiumPrimitiveData::positionScaleFactor; - maxPosition *= CesiumPrimitiveData::positionScaleFactor; + primitiveResult.dimensions = + glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); - primitiveResult.dimensions = - glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); + FBox aaBox( + FVector3d(minPosition.x, -minPosition.y, minPosition.z), + FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); - FBox aaBox( - FVector3d(minPosition.x, -minPosition.y, minPosition.z), - FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); + aaBox.GetCenterAndExtents( + workingData->RenderData->Bounds.Origin, + workingData->RenderData->Bounds.BoxExtent); + workingData->RenderData->Bounds.SphereRadius = 0.0f; + } - aaBox.GetCenterAndExtents( - workingData->RenderData->Bounds.Origin, - workingData->RenderData->Bounds.BoxExtent); - workingData->RenderData->Bounds.SphereRadius = 0.0f; - } + if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::POINTS) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + workingData->indices.SetNum( + static_cast::SizeType>(indicesView.size())); - if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::POINTS) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - workingData->indices.SetNum( - static_cast::SizeType>(indicesView.size())); + for (int32 i = 0; i < indicesView.size(); ++i) { + workingData->indices[i] = indicesView[i]; + } + } else { + // assume TRIANGLE_STRIP because all others are rejected + // earlier. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + workingData->indices.SetNum(static_cast::SizeType>( + 3 * (indicesView.size() - 2))); + for (int32 i = 0; i < indicesView.size() - 2; ++i) { + if (i % 2) { + workingData->indices[3 * i] = indicesView[i]; + workingData->indices[3 * i + 1] = indicesView[i + 2]; + workingData->indices[3 * i + 2] = indicesView[i + 1]; + } else { + workingData->indices[3 * i] = indicesView[i]; + workingData->indices[3 * i + 1] = indicesView[i + 1]; + workingData->indices[3 * i + 2] = indicesView[i + 2]; + } + } + } - for (int32 i = 0; i < indicesView.size(); ++i) { - workingData->indices[i] = indicesView[i]; + // If we don't have normals, the gltf spec prescribes that the + // client implementation must generate flat normals, which + // requires duplicating vertices shared by multiple triangles. If + // we don't have tangents, but need them, we need to use a tangent + // space generation algorithm which requires duplicated vertices. + workingData->duplicateVertices = + !workingData->hasNormals || + (workingData->needsTangents && !workingData->hasTangents); + workingData->duplicateVertices = + workingData->duplicateVertices && + primitive.mode != MeshPrimitive::Mode::POINTS; + + workingData->StaticMeshBuildVertices.SetNum( + workingData->duplicateVertices + ? workingData->indices.Num() + : static_cast(positionView.size())); + + { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + const TMeshVector3& pos = positionView[vertexIndex]; + vertex.Position.X = + pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = + -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = + pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + workingData->RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - + workingData->RenderData->Bounds.Origin) + .Size(), + workingData->RenderData->Bounds.SphereRadius); } } else { - // assume TRIANGLE_STRIP because all others are rejected - // earlier. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - workingData->indices.SetNum(static_cast::SizeType>( - 3 * (indicesView.size() - 2))); - for (int32 i = 0; i < indicesView.size() - 2; ++i) { - if (i % 2) { - workingData->indices[3 * i] = indicesView[i]; - workingData->indices[3 * i + 1] = indicesView[i + 2]; - workingData->indices[3 * i + 2] = indicesView[i + 1]; - } else { - workingData->indices[3 * i] = indicesView[i]; - workingData->indices[3 * i + 1] = indicesView[i + 1]; - workingData->indices[3 * i + 2] = indicesView[i + 2]; - } + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) + for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + const TMeshVector3& pos = positionView[i]; + vertex.Position.X = + pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = + -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = + pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + workingData->RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - + workingData->RenderData->Bounds.Origin) + .Size(), + workingData->RenderData->Bounds.SphereRadius); } } + } + + workingData->hasVertexColors = false; - // If we don't have normals, the gltf spec prescribes that the - // client implementation must generate flat normals, which - // requires duplicating vertices shared by multiple triangles. If - // we don't have tangents, but need them, we need to use a tangent - // space generation algorithm which requires duplicated vertices. - workingData->duplicateVertices = - !workingData->hasNormals || - (workingData->needsTangents && !workingData->hasTangents); - workingData->duplicateVertices = - workingData->duplicateVertices && - primitive.mode != MeshPrimitive::Mode::POINTS; - - workingData->StaticMeshBuildVertices.SetNum( - workingData->duplicateVertices - ? workingData->indices.Num() - : static_cast(positionView.size())); - - { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - const TMeshVector3& pos = positionView[vertexIndex]; - vertex.Position.X = - pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = - -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = - pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - workingData->RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - - workingData->RenderData->Bounds.Origin) - .Size(), - workingData->RenderData->Bounds.SphereRadius); - } + auto colorAccessorIt = primitive.attributes.find("COLOR_0"); + if (colorAccessorIt != primitive.attributes.end()) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) + int colorAccessorID = colorAccessorIt->second; + workingData->hasVertexColors = createAccessorView( + model, + colorAccessorID, + ColorVisitor{ + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices}); + } + + LODResources.bHasColorVertexData = workingData->hasVertexColors; + + // This must be done before material textures are loaded, in case + // any of the material textures are also used for features + + // metadata. + loadPrimitiveFeaturesMetadata( + primitiveResult, + options, + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices); + + std::vector>> + textureLoads; + + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness->baseColorTexture, + true)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->pbrMetallicRoughness->metallicRoughnessTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material->normalTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material->occlusionTexture, + false)); + textureLoads.push_back(loadTexture( + asyncSystem, + model, + workingData->material->emissiveTexture, + true)); + + return asyncSystem.all(std::move(textureLoads)); + }) + .thenImmediately([&primitiveResult, + workingData, + &transform, + &options, + &positionAccessor, + &positionView, + &indicesView, + &ellipsoid](std::vector> + results) { + Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = *options.pMeshOptions->pMesh; + MeshPrimitive& primitive = *options.pPrimitive; + + // We need to copy the texture coordinates associated with + // each texture (if any) into the the appropriate UVs slot + // in FStaticMeshBuildVertex. + + std::unordered_map& gltfToUnrealTexCoordMap = + primitiveResult.GltfToUnrealTexCoordMap; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) + primitiveResult.baseColorTexture = MoveTemp(results[0]); + primitiveResult.metallicRoughnessTexture = MoveTemp(results[1]); + primitiveResult.normalTexture = MoveTemp(results[2]); + primitiveResult.occlusionTexture = MoveTemp(results[3]); + primitiveResult.emissiveTexture = MoveTemp(results[4]); + } + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinates) + + primitiveResult + .textureCoordinateParameters["baseColorTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->pbrMetallicRoughness->baseColorTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["metallicRoughnessTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->pbrMetallicRoughness->metallicRoughnessTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["normalTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material->normalTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["occlusionTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material->occlusionTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["emissiveTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + workingData->material->emissiveTexture, + gltfToUnrealTexCoordMap); + + for (size_t i = 0; + i < primitiveResult.overlayTextureCoordinateIDToUVIndex.size(); + ++i) { + std::string attributeName = "_CESIUMOVERLAY_" + std::to_string(i); + auto overlayIt = primitive.attributes.find(attributeName); + if (overlayIt != primitive.attributes.end()) { + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = + updateTextureCoordinates( + model, + primitive, + workingData->duplicateVertices, + workingData->StaticMeshBuildVertices, + workingData->indices, + attributeName, + gltfToUnrealTexCoordMap); } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) - for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - const TMeshVector3& pos = positionView[i]; - vertex.Position.X = - pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = - -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = - pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - workingData->RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - - workingData->RenderData->Bounds.Origin) - .Size(), - workingData->RenderData->Bounds.SphereRadius); - } + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0; } } + } - workingData->hasVertexColors = false; + // TangentX: Tangent + // TangentY: Bi-tangent + // TangentZ: Normal + + if (workingData->hasNormals) { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE( + Cesium::CopyNormalsForDuplicatedVertices) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = + workingData->normalAccessor[vertexIndex]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) + for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = workingData->normalAccessor[i]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } + } else { + if (primitiveResult.isUnlit) { + glm::dvec3 ecefCenter = glm::dvec3( + transform * glm::dvec4( + VecMath::createVector3D( + workingData->RenderData->Bounds.Origin), + 1.0)); + TMeshVector3 upDir = TMeshVector3(VecMath::createVector( + glm::affineInverse(transform) * + glm::dvec4( + ellipsoid.geodeticSurfaceNormal(glm::dvec3(ecefCenter)), + 0.0))); + upDir.Y *= -1; + setUniformNormals(workingData->StaticMeshBuildVertices, upDir); + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) + computeFlatNormals(workingData->StaticMeshBuildVertices); + } + } - auto colorAccessorIt = primitive.attributes.find("COLOR_0"); - if (colorAccessorIt != primitive.attributes.end()) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) - int colorAccessorID = colorAccessorIt->second; - workingData->hasVertexColors = createAccessorView( - model, - colorAccessorID, - ColorVisitor{ - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices}); + if (workingData->hasTangents) { + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE( + Cesium::CopyTangentsForDuplicatedVertices) + for (int i = 0; i < workingData->indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + uint32 vertexIndex = workingData->indices[i]; + const TMeshVector4& tangent = + workingData->tangentAccessor[vertexIndex]; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = + TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * + tangent.W; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) + for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); + ++i) { + FStaticMeshBuildVertex& vertex = + workingData->StaticMeshBuildVertices[i]; + const TMeshVector4& tangent = workingData->tangentAccessor[i]; + vertex.TangentX = tangent; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = + TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * + tangent.W; + } } + } - LODResources.bHasColorVertexData = workingData->hasVertexColors; + if (workingData->needsTangents && !workingData->hasTangents) { + // Use mikktspace to calculate the tangents. + // Note that this assumes normals and UVs are already + // populated. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) + computeTangentSpace(workingData->StaticMeshBuildVertices); + } - // This must be done before material textures are loaded, in case - // any of the material textures are also used for features + - // metadata. - loadPrimitiveFeaturesMetadata( - primitiveResult, - options, - model, - primitive, - workingData->duplicateVertices, + FStaticMeshLODResources& LODResources = + workingData->RenderData->LODResources[0]; + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) + + // Set to full precision (32-bit) UVs. This is especially + // important for metadata because integer feature IDs can + // and will lose meaningful precision when using 16-bit + // floats. + LODResources.VertexBuffers.StaticMeshVertexBuffer + .SetUseFullPrecisionUVs(true); + + LODResources.VertexBuffers.PositionVertexBuffer.Init( workingData->StaticMeshBuildVertices, - workingData->indices); + false); - std::vector>> - textureLoads; + FColorVertexBuffer& ColorVertexBuffer = + LODResources.VertexBuffers.ColorVertexBuffer; + if (workingData->hasVertexColors) { + ColorVertexBuffer.Init(workingData->StaticMeshBuildVertices, false); + } - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness->baseColorTexture, - true)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness->metallicRoughnessTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->normalTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->occlusionTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->emissiveTexture, - true)); - - return asyncSystem.all(std::move(textureLoads)) - .thenImmediately([&primitiveResult, - workingData, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid]( - std::vector> - results) { - Model& model = - *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; - - // We need to copy the texture coordinates associated with - // each texture (if any) into the the appropriate UVs slot - // in FStaticMeshBuildVertex. - - std::unordered_map& gltfToUnrealTexCoordMap = - primitiveResult.GltfToUnrealTexCoordMap; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) - primitiveResult.baseColorTexture = MoveTemp(results[0]); - primitiveResult.metallicRoughnessTexture = - MoveTemp(results[1]); - primitiveResult.normalTexture = MoveTemp(results[2]); - primitiveResult.occlusionTexture = MoveTemp(results[3]); - primitiveResult.emissiveTexture = MoveTemp(results[4]); - } - - { - TRACE_CPUPROFILER_EVENT_SCOPE( - Cesium::UpdateTextureCoordinates) - - primitiveResult.textureCoordinateParameters - ["baseColorTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->pbrMetallicRoughness->baseColorTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["metallicRoughnessTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->pbrMetallicRoughness - ->metallicRoughnessTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["normalTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->normalTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["occlusionTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->occlusionTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["emissiveTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->emissiveTexture, - gltfToUnrealTexCoordMap); - - for (size_t i = 0; - i < primitiveResult.overlayTextureCoordinateIDToUVIndex - .size(); - ++i) { - std::string attributeName = - "_CESIUMOVERLAY_" + std::to_string(i); - auto overlayIt = primitive.attributes.find(attributeName); - if (overlayIt != primitive.attributes.end()) { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - attributeName, - gltfToUnrealTexCoordMap); - } else { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = - 0; - } - } - } - - // TangentX: Tangent - // TangentY: Bi-tangent - // TangentZ: Normal - - if (workingData->hasNormals) { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE( - Cesium::CopyNormalsForDuplicatedVertices) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = - workingData->normalAccessor[vertexIndex]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) - for (int i = 0; - i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = - workingData->normalAccessor[i]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } - } else { - if (primitiveResult.isUnlit) { - glm::dvec3 ecefCenter = glm::dvec3( - transform * - glm::dvec4( - VecMath::createVector3D( - workingData->RenderData->Bounds.Origin), - 1.0)); - TMeshVector3 upDir = TMeshVector3(VecMath::createVector( - glm::affineInverse(transform) * - glm::dvec4( - ellipsoid.geodeticSurfaceNormal( - glm::dvec3(ecefCenter)), - 0.0))); - upDir.Y *= -1; - setUniformNormals( - workingData->StaticMeshBuildVertices, - upDir); - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) - computeFlatNormals(workingData->StaticMeshBuildVertices); - } - } - - if (workingData->hasTangents) { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE( - Cesium::CopyTangentsForDuplicatedVertices) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - const TMeshVector4& tangent = - workingData->tangentAccessor[vertexIndex]; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = TMeshVector3::CrossProduct( - vertex.TangentZ, - vertex.TangentX) * - tangent.W; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) - for (int i = 0; - i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - const TMeshVector4& tangent = - workingData->tangentAccessor[i]; - vertex.TangentX = tangent; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = TMeshVector3::CrossProduct( - vertex.TangentZ, - vertex.TangentX) * - tangent.W; - } - } - } - - if (workingData->needsTangents && !workingData->hasTangents) { - // Use mikktspace to calculate the tangents. - // Note that this assumes normals and UVs are already - // populated. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) - computeTangentSpace(workingData->StaticMeshBuildVertices); - } - - FStaticMeshLODResources& LODResources = - workingData->RenderData->LODResources[0]; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) - - // Set to full precision (32-bit) UVs. This is especially - // important for metadata because integer feature IDs can - // and will lose meaningful precision when using 16-bit - // floats. - LODResources.VertexBuffers.StaticMeshVertexBuffer - .SetUseFullPrecisionUVs(true); - - LODResources.VertexBuffers.PositionVertexBuffer.Init( - workingData->StaticMeshBuildVertices, - false); + LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( + workingData->StaticMeshBuildVertices, + gltfToUnrealTexCoordMap.size() == 0 + ? 1 + : gltfToUnrealTexCoordMap.size(), + false); + } - FColorVertexBuffer& ColorVertexBuffer = - LODResources.VertexBuffers.ColorVertexBuffer; - if (workingData->hasVertexColors) { - ColorVertexBuffer.Init( - workingData->StaticMeshBuildVertices, - false); - } + FStaticMeshSectionArray& Sections = LODResources.Sections; + FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); + // This will be ignored if the primitive contains points. + section.NumTriangles = workingData->indices.Num() / 3; + section.FirstIndex = 0; + section.MinVertexIndex = 0; + section.MaxVertexIndex = workingData->StaticMeshBuildVertices.Num() - 1; + section.bEnableCollision = + primitive.mode != MeshPrimitive::Mode::POINTS; + section.bCastShadow = true; + section.MaterialIndex = 0; + + if (workingData->duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) + for (int32 i = 0; i < workingData->indices.Num(); i++) { + workingData->indices[i] = i; + } + } - LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( - workingData->StaticMeshBuildVertices, - gltfToUnrealTexCoordMap.size() == 0 - ? 1 - : gltfToUnrealTexCoordMap.size(), - false); - } - - FStaticMeshSectionArray& Sections = LODResources.Sections; - FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); - // This will be ignored if the primitive contains points. - section.NumTriangles = workingData->indices.Num() / 3; - section.FirstIndex = 0; - section.MinVertexIndex = 0; - section.MaxVertexIndex = - workingData->StaticMeshBuildVertices.Num() - 1; - section.bEnableCollision = - primitive.mode != MeshPrimitive::Mode::POINTS; - section.bCastShadow = true; - section.MaterialIndex = 0; - - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) - for (int32 i = 0; i < workingData->indices.Num(); i++) { - workingData->indices[i] = i; - } - } - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) - LODResources.IndexBuffer.SetIndices( - workingData->indices, - workingData->StaticMeshBuildVertices.Num() >= - std::numeric_limits::max() - ? EIndexBufferStride::Type::Force32Bit - : EIndexBufferStride::Type::Force16Bit); - } - - LODResources.bHasDepthOnlyIndices = false; - LODResources.bHasReversedIndices = false; - LODResources.bHasReversedDepthOnlyIndices = false; - - primitiveResult.pModel = - options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - primitiveResult.pMeshPrimitive = &primitive; - primitiveResult.RenderData = MoveTemp(workingData->RenderData); - primitiveResult.pMaterial = workingData->material; - primitiveResult.pCollisionMesh = nullptr; - - double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; - glm::dmat4 scaleMatrix = glm::dmat4( - glm::dvec4(scale, 0.0, 0.0, 0.0), - glm::dvec4(0.0, scale, 0.0, 0.0), - glm::dvec4(0.0, 0.0, scale, 0.0), - glm::dvec4(0.0, 0.0, 0.0, 1.0)); - - primitiveResult.transform = - transform * yInvertMatrix * scaleMatrix; - - if (primitive.mode != MeshPrimitive::Mode::POINTS && - options.pMeshOptions->pNodeOptions->pModelOptions - ->createPhysicsMeshes) { - if (workingData->StaticMeshBuildVertices.Num() != 0 && - workingData->indices.Num() != 0) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) - primitiveResult.pCollisionMesh = - workingData->StaticMeshBuildVertices.Num() < - TNumericLimits::Max() - ? BuildChaosTriangleMeshes( - workingData->StaticMeshBuildVertices, - workingData->indices) - : BuildChaosTriangleMeshes( - workingData->StaticMeshBuildVertices, - workingData->indices); - } - } - }); - }); - }); + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) + LODResources.IndexBuffer.SetIndices( + workingData->indices, + workingData->StaticMeshBuildVertices.Num() >= + std::numeric_limits::max() + ? EIndexBufferStride::Type::Force32Bit + : EIndexBufferStride::Type::Force16Bit); + } + + LODResources.bHasDepthOnlyIndices = false; + LODResources.bHasReversedIndices = false; + LODResources.bHasReversedDepthOnlyIndices = false; + + primitiveResult.pModel = + options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + primitiveResult.pMeshPrimitive = &primitive; + primitiveResult.RenderData = MoveTemp(workingData->RenderData); + primitiveResult.pMaterial = workingData->material; + primitiveResult.pCollisionMesh = nullptr; + + double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; + glm::dmat4 scaleMatrix = glm::dmat4( + glm::dvec4(scale, 0.0, 0.0, 0.0), + glm::dvec4(0.0, scale, 0.0, 0.0), + glm::dvec4(0.0, 0.0, scale, 0.0), + glm::dvec4(0.0, 0.0, 0.0, 1.0)); + + primitiveResult.transform = transform * yInvertMatrix * scaleMatrix; + + if (primitive.mode != MeshPrimitive::Mode::POINTS && + options.pMeshOptions->pNodeOptions->pModelOptions + ->createPhysicsMeshes) { + if (workingData->StaticMeshBuildVertices.Num() != 0 && + workingData->indices.Num() != 0) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) + primitiveResult.pCollisionMesh = + workingData->StaticMeshBuildVertices.Num() < + TNumericLimits::Max() + ? BuildChaosTriangleMeshes( + workingData->StaticMeshBuildVertices, + workingData->indices) + : BuildChaosTriangleMeshes( + workingData->StaticMeshBuildVertices, + workingData->indices); + } + } + }); } static CesiumAsync::SharedFuture loadIndexedPrimitive( @@ -3482,7 +3459,10 @@ static void loadPrimitiveGameThreadPart( } const Material& material = - loadResult.pMaterial ? *loadResult.pMaterial : defaultMaterial; + loadResult.materialId >= 0 && + loadResult.materialId < model.materials.size() + ? model.materials[loadResult.materialId] + : defaultMaterial; const MaterialPBRMetallicRoughness& pbr = material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() diff --git a/Source/CesiumRuntime/Private/LoadGltfResult.h b/Source/CesiumRuntime/Private/LoadGltfResult.h index f9f55843a..6864177f4 100644 --- a/Source/CesiumRuntime/Private/LoadGltfResult.h +++ b/Source/CesiumRuntime/Private/LoadGltfResult.h @@ -48,6 +48,13 @@ struct LoadPrimitiveResult { * A pointer to the glTF material. */ const CesiumGltf::Material* pMaterial = nullptr; + + /** + * The index of the material for this primitive within the parent model, or -1 + * if none. + */ + int32_t materialId = -1; + glm::dmat4x4 transform{1.0}; #if ENGINE_VERSION_5_4_OR_HIGHER Chaos::FTriangleMeshImplicitObjectPtr pCollisionMesh = nullptr; From d9bf27ef1a5cf1d492587d42bedabcd85e174f5b Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 4 Sep 2024 17:41:32 -0400 Subject: [PATCH 09/68] Tile debug overlay --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 29 +++++++++++++++++++ Source/CesiumRuntime/Public/Cesium3DTileset.h | 7 +++++ 2 files changed, 36 insertions(+) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index f56982b28..c81667f30 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1865,6 +1865,35 @@ void ACesium3DTileset::updateLastViewUpdateResultState( const Cesium3DTilesSelection::ViewUpdateResult& result) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateLastViewUpdateResultState) + if (this->DrawTileInfo) { + const UWorld* World = GetWorld(); + check(World); + + const TSoftObjectPtr Georeference = GetGeoreference(); + check(Georeference); + + for (auto& tile : result.tilesToRenderThisFrame) { + + CesiumGeometry::OrientedBoundingBox obb = + Cesium3DTilesSelection::getOrientedBoundingBoxFromBoundingVolume( + tile->getBoundingVolume(), + Georeference->GetEllipsoid()->GetNativeEllipsoid()); + + FVector unrealCenter = + Georeference->TransformEarthCenteredEarthFixedPositionToUnreal( + VecMath::createVector(obb.getCenter())); + + FString text = FString::Printf( + TEXT("ID %s (%p)"), + Cesium3DTilesSelection::TileIdUtilities::createTileIdString( + tile->getTileID()) + .c_str(), + tile); + + DrawDebugString(World, unrealCenter, text, nullptr, FColor::Red, 0, true); + } + } + if (!this->LogSelectionStats) { return; } diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 385694264..6edbd5b94 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -598,6 +598,13 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UPROPERTY(EditAnywhere, Category = "Cesium|Debug") bool LogSelectionStats = false; + /** + * If true, draws debug text above each tile being rendered with information + * about that tile. + */ + UPROPERTY(EditAnywhere, Category = "Cesium|Debug") + bool DrawTileInfo = false; + /** * Define the collision profile for all the 3D tiles created inside this * actor. From 5c486cff8b1dc033092f5cacdfe0d762748387cb Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Mon, 9 Sep 2024 15:05:26 -0400 Subject: [PATCH 10/68] Working! All tests passing --- .../Private/CesiumGltfComponent.cpp | 66 +++++++++++-------- .../CesiumRuntime/Private/CreateGltfOptions.h | 4 +- Source/CesiumRuntime/Private/LoadGltfResult.h | 11 +--- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 6ac5102f8..d555b2fd7 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1186,8 +1186,8 @@ static CesiumAsync::Future loadPrimitive( Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; + Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; + MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && @@ -1257,7 +1257,7 @@ static CesiumAsync::Future loadPrimitive( ? &model.materials[materialID] : &defaultMaterial; - primitiveResult.materialId = materialID; + primitiveResult.materialIndex = materialID; workingData->pbrMetallicRoughness = workingData->material->pbrMetallicRoughness @@ -1342,8 +1342,8 @@ static CesiumAsync::Future loadPrimitive( &ellipsoid]() { Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; + Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; + MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; // The water effect works by animating the normal, and the normal // is expressed in tangent space. So if we have water, we need @@ -1565,8 +1565,9 @@ static CesiumAsync::Future loadPrimitive( results) { Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMeshOptions->pMesh; - MeshPrimitive& primitive = *options.pPrimitive; + const Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; + const MeshPrimitive& primitive = + mesh.primitives[options.primitiveIndex]; // We need to copy the texture coordinates associated with // each texture (if any) into the the appropriate UVs slot @@ -1820,11 +1821,9 @@ static CesiumAsync::Future loadPrimitive( LODResources.bHasReversedIndices = false; LODResources.bHasReversedDepthOnlyIndices = false; - primitiveResult.pModel = - options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - primitiveResult.pMeshPrimitive = &primitive; + primitiveResult.meshIndex = options.pMeshOptions->meshIndex; + primitiveResult.primitiveIndex = options.primitiveIndex; primitiveResult.RenderData = MoveTemp(workingData->RenderData); - primitiveResult.pMaterial = workingData->material; primitiveResult.pCollisionMesh = nullptr; double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; @@ -1866,7 +1865,8 @@ static CesiumAsync::SharedFuture loadIndexedPrimitive( const CesiumGeospatial::Ellipsoid& ellipsoid) { const Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - const MeshPrimitive& primitive = *options.pPrimitive; + const MeshPrimitive& primitive = model.meshes[options.pMeshOptions->meshIndex] + .primitives[options.primitiveIndex]; const Accessor& indexAccessorGltf = model.accessors[primitive.indices]; if (indexAccessorGltf.componentType == @@ -1943,7 +1943,8 @@ static CesiumAsync::SharedFuture loadPrimitive( const Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - const MeshPrimitive& primitive = *options.pPrimitive; + const MeshPrimitive& primitive = model.meshes[options.pMeshOptions->meshIndex] + .primitives[options.primitiveIndex]; auto positionAccessorIt = primitive.attributes.find("POSITION"); if (positionAccessorIt == primitive.attributes.end()) { @@ -2008,13 +2009,14 @@ static CesiumAsync::SharedFuture loadMesh( TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh) const Model& model = *options.pNodeOptions->pModelOptions->pModel; - Mesh& mesh = *options.pMesh; + const Mesh& mesh = model.meshes[options.meshIndex]; result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); std::vector> futures; - for (CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { - CreatePrimitiveOptions primitiveOptions = {&options, &*result, &primitive}; + int32_t i = 0; + for (const CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { + CreatePrimitiveOptions primitiveOptions = {&options, &*result, i++}; auto& primitiveResult = result->primitiveResults.emplace_back(); futures.push_back(loadPrimitive( asyncSystem, @@ -2265,15 +2267,15 @@ static CesiumAsync::SharedFuture loadNode( nodeTransform * translation * glm::dmat4(rotationQuat) * scale; } - int meshId = node.mesh; + int meshIndex = node.mesh; CesiumAsync::SharedFuture loadMeshFuture = asyncSystem.createResolvedFuture(0).share(); - if (meshId >= 0 && meshId < model.meshes.size()) { + if (meshIndex >= 0 && meshIndex < model.meshes.size()) { if (const auto* pGpuInstancingExtension = node.getExtension()) { loadInstancingData(model, result, pGpuInstancingExtension); } - CreateMeshOptions meshOptions = {&options, &result, &model.meshes[meshId]}; + CreateMeshOptions meshOptions = {&options, &result, meshIndex}; loadMeshFuture = loadMesh( asyncSystem, result.meshResult, @@ -2538,6 +2540,7 @@ loadModelAnyThreadPart( loadNodeResultsMutex)); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. + int32_t i = 0; for (Mesh& mesh : model.meshes) { CreateNodeOptions dummyNodeOptions = { &options, @@ -2548,7 +2551,7 @@ loadModelAnyThreadPart( CreateMeshOptions meshOptions = { &dummyNodeOptions, &dummyNodeResult, - &mesh}; + i++}; futures.push_back(loadMesh( asyncSystem, dummyNodeResult.meshResult, @@ -3392,9 +3395,12 @@ static void loadPrimitiveGameThreadPart( const Cesium3DTilesSelection::BoundingVolume& boundingVolume = tile.getContentBoundingVolume().value_or(tile.getBoundingVolume()); + MeshPrimitive* pMeshPrimitive = + &model.meshes[loadResult.meshIndex].primitives[loadResult.primitiveIndex]; + UStaticMeshComponent* pMesh = nullptr; ICesiumPrimitive* pCesiumPrimitive = nullptr; - if (loadResult.pMeshPrimitive->mode == MeshPrimitive::Mode::POINTS) { + if (pMeshPrimitive->mode == MeshPrimitive::Mode::POINTS) { UCesiumGltfPointsComponent* pPointMesh = NewObject(pGltf, componentName); pPointMesh->UsesAdditiveRefinement = @@ -3436,8 +3442,9 @@ static void loadPrimitiveGameThreadPart( pMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic); pMesh->SetFlags( RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); - primData.pModel = loadResult.pModel; - primData.pMeshPrimitive = loadResult.pMeshPrimitive; + primData.pModel = &model; + primData.pMeshPrimitive = &model.meshes[loadResult.meshIndex] + .primitives[loadResult.primitiveIndex]; primData.boundingVolume = boundingVolume; pMesh->SetRenderCustomDepth(pGltf->CustomDepthParameters.RenderCustomDepth); pMesh->SetCustomDepthStencilWriteMask( @@ -3459,9 +3466,9 @@ static void loadPrimitiveGameThreadPart( } const Material& material = - loadResult.materialId >= 0 && - loadResult.materialId < model.materials.size() - ? model.materials[loadResult.materialId] + loadResult.materialIndex >= 0 && + loadResult.materialIndex < model.materials.size() + ? model.materials[loadResult.materialIndex] : defaultMaterial; const MaterialPBRMetallicRoughness& pbr = @@ -3471,9 +3478,10 @@ static void loadPrimitiveGameThreadPart( const FName ImportedSlotName( *(TEXT("CesiumMaterial") + FString::FromInt(nextMaterialId++))); - const auto is_in_blend_mode = [](auto& result) { - return !!result.pMaterial && result.pMaterial->alphaMode == - CesiumGltf::Material::AlphaMode::BLEND; + const auto is_in_blend_mode = [&model](auto& result) { + return result.materialIndex != -1 && + model.materials[result.materialIndex].alphaMode == + CesiumGltf::Material::AlphaMode::BLEND; }; #if PLATFORM_MAC diff --git a/Source/CesiumRuntime/Private/CreateGltfOptions.h b/Source/CesiumRuntime/Private/CreateGltfOptions.h index a09d6914f..6019a92bf 100644 --- a/Source/CesiumRuntime/Private/CreateGltfOptions.h +++ b/Source/CesiumRuntime/Private/CreateGltfOptions.h @@ -35,12 +35,12 @@ struct CreateNodeOptions { struct CreateMeshOptions { const CreateNodeOptions* pNodeOptions = nullptr; const LoadGltfResult::LoadNodeResult* pHalfConstructedNodeResult = nullptr; - CesiumGltf::Mesh* pMesh = nullptr; + int32_t meshIndex = -1; }; struct CreatePrimitiveOptions { const CreateMeshOptions* pMeshOptions = nullptr; const LoadGltfResult::LoadMeshResult* pHalfConstructedMeshResult = nullptr; - CesiumGltf::MeshPrimitive* pPrimitive = nullptr; + int32_t primitiveIndex = -1; }; } // namespace CreateGltfOptions diff --git a/Source/CesiumRuntime/Private/LoadGltfResult.h b/Source/CesiumRuntime/Private/LoadGltfResult.h index 6864177f4..72280855c 100644 --- a/Source/CesiumRuntime/Private/LoadGltfResult.h +++ b/Source/CesiumRuntime/Private/LoadGltfResult.h @@ -44,16 +44,11 @@ struct LoadPrimitiveResult { */ TUniquePtr RenderData = nullptr; - /** - * A pointer to the glTF material. - */ - const CesiumGltf::Material* pMaterial = nullptr; - /** * The index of the material for this primitive within the parent model, or -1 * if none. */ - int32_t materialId = -1; + int32_t materialIndex = -1; glm::dmat4x4 transform{1.0}; #if ENGINE_VERSION_5_4_OR_HIGHER @@ -96,8 +91,8 @@ struct LoadPrimitiveResult { #pragma endregion #pragma region CesiumGltfPrimitiveComponent data - const CesiumGltf::Model* pModel = nullptr; - const CesiumGltf::MeshPrimitive* pMeshPrimitive = nullptr; + int32_t meshIndex = -1; + int32_t primitiveIndex = -1; /** Parses EXT_mesh_features from a mesh primitive.*/ FCesiumPrimitiveFeatures Features{}; From 30eeee789d8e51a4d18dbbabcd9ec8cd475d4e22 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 11 Sep 2024 14:58:35 -0400 Subject: [PATCH 11/68] Fix test, update cesium-native --- .../Private/Tests/Cesium3DTileset.spec.cpp | 13 ++++++++++--- extern/cesium-native | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 41b6a10d9..8cc0719e7 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -9,6 +9,7 @@ #include "Engine/World.h" #include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" +#include "Tests/AutomationEditorCommon.h" #include #define TEST_SCREEN_WIDTH 1280 @@ -29,7 +30,8 @@ static void setupForSharedImages(SceneGenerationContext& context) { 60.0f); context.georeference->SetOriginEarthCenteredEarthFixed(FVector(0, 0, 0)); - context.pawn->SetActorLocation(FVector(-12, -1300, -5)); + context.pawn->SetActorLocation(FVector(485.0, 2400.0, 520.0)); + context.pawn->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270))); context.sunSky->TimeZone = 9.0f; context.sunSky->UpdateSun(); @@ -53,6 +55,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { tileset->SetActorLabel(TEXT("SharedImages")); tileset->SetGeoreference(georeference); tileset->SuspendUpdate = false; + tileset->LogSelectionStats = true; context.tilesets.push_back(tileset); UCesiumGlobeAnchorComponent* GlobeAnchorComponent = @@ -63,13 +66,17 @@ static void setupForSharedImages(SceneGenerationContext& context) { GlobeAnchorComponent->RegisterComponent(); GlobeAnchorComponent->MoveToEarthCenteredEarthFixedPosition( FVector(0.0, 0.0, 0.0)); + + ADirectionalLight* Light = context.world->SpawnActor(); + Light->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270))); } void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - context.refreshTilesets(); - context.setSuspendUpdate(false); + CesiumGltf::SharedAssetDepot& assetDepot = + context.tilesets[0]->GetTileset()->getSharedAssetDepot(); + assert(assetDepot.getImagesCount() == 2); } bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { diff --git a/extern/cesium-native b/extern/cesium-native index a5829effb..8d38927a6 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit a5829effbcf1f7172de1325af5b0aef5f8047643 +Subproject commit 8d38927a6cfa4087d9e83a74bb8fe8428c859a46 From cc1bd440d2392dc1e37de0b10d03cbb90b2e410e Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 11 Sep 2024 15:11:41 -0400 Subject: [PATCH 12/68] Re-add workaround from 396f78f --- .../Private/CesiumGltfComponent.cpp | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index d555b2fd7..f001d1f9c 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1780,12 +1780,44 @@ static CesiumAsync::Future loadPrimitive( ColorVertexBuffer.Init(workingData->StaticMeshBuildVertices, false); } - LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( - workingData->StaticMeshBuildVertices, + uint32 numberOfTextureCoordinates = gltfToUnrealTexCoordMap.size() == 0 ? 1 - : gltfToUnrealTexCoordMap.size(), + : uint32(gltfToUnrealTexCoordMap.size()); + + FStaticMeshVertexBuffer& vertexBuffer = + LODResources.VertexBuffers.StaticMeshVertexBuffer; + vertexBuffer.Init( + workingData->StaticMeshBuildVertices.Num(), + numberOfTextureCoordinates, false); + + // Manually copy the vertices into the buffer. We do this because + // UE 5.3 and 5.4 have a bug where the overload of + // `FStaticMeshVertexBuffer::Init` taking an array of + // `FStaticMeshBuildVertex` will create a mesh with all 8 sets of + // texture coordinates, even when we usually only need one or two. See + // https://github.com/CesiumGS/cesium-unreal/issues/1513 + for (uint32 vertexIndex = 0; + vertexIndex < uint32(workingData->StaticMeshBuildVertices.Num()); + ++vertexIndex) { + const FStaticMeshBuildVertex& source = + workingData->StaticMeshBuildVertices[vertexIndex]; + + vertexBuffer.SetVertexTangents( + vertexIndex, + source.TangentX, + source.TangentY, + source.TangentZ); + for (uint32 uvIndex = 0; uvIndex < numberOfTextureCoordinates; + uvIndex++) { + vertexBuffer.SetVertexUV( + vertexIndex, + uvIndex, + source.UVs[uvIndex], + false); + } + } } FStaticMeshSectionArray& Sections = LODResources.Sections; From 1b7b940ec53df7d173f34dfdba31589c9cad3f66 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 11 Sep 2024 15:12:49 -0400 Subject: [PATCH 13/68] clang-format --- .../Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp | 2 +- .../Private/Tests/CesiumOriginShiftComponent.spec.cpp | 2 +- .../Private/Tests/CesiumPrimitiveFeatures.spec.cpp | 2 +- Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp | 2 +- .../Private/Tests/CesiumPropertyTableProperty.spec.cpp | 2 +- .../CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp | 2 +- .../Private/Tests/CesiumPropertyTextureProperty.spec.cpp | 2 +- Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp | 2 +- .../CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp | 2 +- Source/CesiumRuntime/Private/Tests/UnrealAssetAccessor.spec.cpp | 2 +- .../Private/Tests/UnrealMetadataConversions.spec.cpp | 2 +- extern/cesium-native | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp index 2ce9db0ff..cf4098e89 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp @@ -1,5 +1,6 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumMetadataPickingBlueprintLibrary.h" #include "CesiumGltf/ExtensionExtMeshFeatures.h" #include "CesiumGltf/ExtensionMeshPrimitiveExtStructuralMetadata.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" @@ -7,7 +8,6 @@ #include "CesiumGltfComponent.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumGltfSpecUtility.h" -#include "CesiumMetadataPickingBlueprintLibrary.h" #include "Misc/AutomationTest.h" using namespace CesiumGltf; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp index 5f76fd5ed..e9a2d1efd 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp @@ -2,9 +2,9 @@ #if WITH_EDITOR +#include "CesiumOriginShiftComponent.h" #include "CesiumGeoreference.h" #include "CesiumGlobeAnchorComponent.h" -#include "CesiumOriginShiftComponent.h" #include "CesiumSubLevelComponent.h" #include "CesiumTestHelpers.h" #include "Editor.h" diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp index bdd3741db..cf16aa069 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumPrimitiveFeatures.h" #include "CesiumGltf/ExtensionExtMeshFeatures.h" #include "CesiumGltfSpecUtility.h" -#include "CesiumPrimitiveFeatures.h" #include "Misc/AutomationTest.h" using namespace CesiumGltf; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp index b196ec3ea..c2d9ded27 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp @@ -1,9 +1,9 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumPropertyTable.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" #include "CesiumGltf/Model.h" #include "CesiumGltfSpecUtility.h" -#include "CesiumPropertyTable.h" #include "Misc/AutomationTest.h" #include diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp index cfb476b03..43b246f30 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumPropertyTableProperty.h" #include "CesiumGltfSpecUtility.h" #include "CesiumPropertyArrayBlueprintLibrary.h" -#include "CesiumPropertyTableProperty.h" #include "Misc/AutomationTest.h" #include diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp index ae7e097b5..7b329481c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp @@ -1,11 +1,11 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumPropertyTexture.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" #include "CesiumGltf/Model.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumGltfSpecUtility.h" -#include "CesiumPropertyTexture.h" #include "Misc/AutomationTest.h" #include diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp index 41e728f16..d9361976c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "CesiumPropertyTextureProperty.h" #include "CesiumGltfSpecUtility.h" #include "CesiumPropertyArrayBlueprintLibrary.h" -#include "CesiumPropertyTextureProperty.h" #include "Misc/AutomationTest.h" #include diff --git a/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp b/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp index cc8bd38b1..2ab9e6f9d 100644 --- a/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "GeoTransforms.h" #include "CesiumGeospatial/Ellipsoid.h" #include "CesiumUtility/Math.h" -#include "GeoTransforms.h" #include "Misc/AutomationTest.h" using namespace CesiumGeospatial; diff --git a/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp b/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp index 8e5517ebb..fbe751339 100644 --- a/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp @@ -2,6 +2,7 @@ #if WITH_EDITOR +#include "GlobeAwareDefaultPawn.h" #include "CesiumFlyToComponent.h" #include "CesiumGeoreference.h" #include "CesiumGlobeAnchorComponent.h" @@ -10,7 +11,6 @@ #include "Editor.h" #include "Engine/World.h" #include "EngineUtils.h" -#include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" #include "Tests/AutomationEditorCommon.h" diff --git a/Source/CesiumRuntime/Private/Tests/UnrealAssetAccessor.spec.cpp b/Source/CesiumRuntime/Private/Tests/UnrealAssetAccessor.spec.cpp index 539949770..23ad0b24d 100644 --- a/Source/CesiumRuntime/Private/Tests/UnrealAssetAccessor.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/UnrealAssetAccessor.spec.cpp @@ -1,5 +1,6 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "UnrealAssetAccessor.h" #include "Async/Async.h" #include "CesiumAsync/IAssetResponse.h" #include "CesiumRuntime.h" @@ -7,7 +8,6 @@ #include "Misc/AutomationTest.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" -#include "UnrealAssetAccessor.h" BEGIN_DEFINE_SPEC( FUnrealAssetAccessorSpec, diff --git a/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp b/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp index a39999677..a21798d28 100644 --- a/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#include "UnrealMetadataConversions.h" #include "CesiumTestHelpers.h" #include "Misc/AutomationTest.h" -#include "UnrealMetadataConversions.h" #include diff --git a/extern/cesium-native b/extern/cesium-native index 8d38927a6..0e44d9248 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 8d38927a6cfa4087d9e83a74bb8fe8428c859a46 +Subproject commit 0e44d92482da3e3b85ccafee0d872be192813004 From 38042e4a8516cec100e4a57caccee2367fdb2f25 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 12 Sep 2024 13:06:15 +1000 Subject: [PATCH 14/68] Fix clang-formatting by letting npm dependencies update. --- package-lock.json | 112 ++++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1873851a..7cd411f99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,27 @@ { "name": "cesium-unreal", - "version": "2.4.0", + "version": "2.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cesium-unreal", - "version": "2.4.0", + "version": "2.8.0", "license": "Apache-2.0", "devDependencies": { "clang-format": "^1.5.0" } }, "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "node_modules/brace-expansion": { @@ -35,12 +35,12 @@ } }, "node_modules/clang-format": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.5.0.tgz", - "integrity": "sha512-C1LucFX7E+ABVYcPEbBHM4PYQ2+WInXsqsLpFlQ9cmRfSbk7A7b1I06h/nE4bQ3MsyEkb31jY2gC0Dtc76b4IA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.8.0.tgz", + "integrity": "sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw==", "dev": true, "dependencies": { - "async": "^1.5.2", + "async": "^3.2.3", "glob": "^7.0.0", "resolve": "^1.1.6" }, @@ -53,31 +53,35 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -88,22 +92,23 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" } }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -117,12 +122,15 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -143,7 +151,7 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { "wrappy": "1" @@ -152,26 +160,42 @@ "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -180,7 +204,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true } } From 4d68a89de4c80a04ea9b33ad33504bf1a592b6a2 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 13 Sep 2024 11:00:53 -0400 Subject: [PATCH 15/68] Update cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index 0e44d9248..8e664a18b 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 0e44d92482da3e3b85ccafee0d872be192813004 +Subproject commit 8e664a18b34128cad0e55aafcc7289aa03fc95f9 From 1ff1fd352109144afb9b018b2cc90e10d81e48ca Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 13 Sep 2024 16:28:04 -0400 Subject: [PATCH 16/68] De-asyncify most of the gltf loading. Still seeing mysterious compilation errors. --- .../Private/CesiumGltfComponent.cpp | 1732 +++++++---------- .../Private/CesiumTextureUtility.cpp | 509 +++-- .../Private/CesiumTextureUtility.h | 61 +- .../Tests/CesiumTextureUtility.spec.cpp | 43 +- 4 files changed, 980 insertions(+), 1365 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index f001d1f9c..c9a51e5da 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -61,7 +61,6 @@ #include #include #include -#include #if WITH_EDITOR #include "ScopedTransaction.h" @@ -399,10 +398,7 @@ struct ColorVisitor { }; template -static CesiumAsync::Future< - TUniquePtr> -loadTexture( - const CesiumAsync::AsyncSystem& asyncSystem, +static TUniquePtr loadTexture( CesiumGltf::Model& model, const std::optional& gltfTexture, bool sRGB) { @@ -416,17 +412,15 @@ loadTexture( model.textures.size(), gltfTexture.value().index); } - return asyncSystem.createResolvedFuture< - TUniquePtr>(nullptr); + return nullptr; } int32_t textureIndex = gltfTexture.value().index; CesiumGltf::Texture& texture = model.textures[textureIndex]; - return loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, sRGB); + return loadTextureFromModelAnyThreadPart(model, texture, sRGB); } -static CesiumAsync::SharedFuture applyWaterMaskTexture( - const CesiumAsync::AsyncSystem& asyncSystem, +static void applyWaterMask( Model& model, const MeshPrimitive& primitive, LoadPrimitiveResult& primitiveResult) { @@ -451,16 +445,8 @@ static CesiumAsync::SharedFuture applyWaterMaskTexture( waterMaskInfo.index = waterMaskTextureId; if (waterMaskTextureId >= 0 && waterMaskTextureId < model.textures.size()) { - return loadTexture( - asyncSystem, - model, - std::make_optional(waterMaskInfo), - false) - .thenImmediately([pPrimitiveResult = &primitiveResult]( - TUniquePtr result) { - pPrimitiveResult->waterMaskTexture = MoveTemp(result); - }) - .share(); + primitiveResult.waterMaskTexture = + loadTexture(model, std::make_optional(waterMaskInfo), false); } } } @@ -469,36 +455,23 @@ static CesiumAsync::SharedFuture applyWaterMaskTexture( primitiveResult.onlyLand = true; } - return asyncSystem.createResolvedFuture().share(); -} + auto waterMaskTranslationXIt = primitive.extras.find("WaterMaskTranslationX"); + auto waterMaskTranslationYIt = primitive.extras.find("WaterMaskTranslationY"); + auto waterMaskScaleIt = primitive.extras.find("WaterMaskScale"); -static CesiumAsync::Future applyWaterMask( - const CesiumAsync::AsyncSystem& asyncSystem, - Model& model, - const MeshPrimitive& primitive, - LoadPrimitiveResult& primitiveResult) { - return applyWaterMaskTexture(asyncSystem, model, primitive, primitiveResult) - .thenImmediately([primitive, pPrimitiveResult = &primitiveResult]() { - auto waterMaskTranslationXIt = - primitive.extras.find("WaterMaskTranslationX"); - auto waterMaskTranslationYIt = - primitive.extras.find("WaterMaskTranslationY"); - auto waterMaskScaleIt = primitive.extras.find("WaterMaskScale"); - - if (waterMaskTranslationXIt != primitive.extras.end() && - waterMaskTranslationXIt->second.isDouble() && - waterMaskTranslationYIt != primitive.extras.end() && - waterMaskTranslationYIt->second.isDouble() && - waterMaskScaleIt != primitive.extras.end() && - waterMaskScaleIt->second.isDouble()) { - pPrimitiveResult->waterMaskTranslationX = - waterMaskTranslationXIt->second.getDoubleOrDefault(0.0); - pPrimitiveResult->waterMaskTranslationY = - waterMaskTranslationYIt->second.getDoubleOrDefault(0.0); - pPrimitiveResult->waterMaskScale = - waterMaskScaleIt->second.getDoubleOrDefault(1.0); - } - }); + if (waterMaskTranslationXIt != primitive.extras.end() && + waterMaskTranslationXIt->second.isDouble() && + waterMaskTranslationYIt != primitive.extras.end() && + waterMaskTranslationYIt->second.isDouble() && + waterMaskScaleIt != primitive.extras.end() && + waterMaskScaleIt->second.isDouble()) { + primitiveResult.waterMaskTranslationX = + waterMaskTranslationXIt->second.getDoubleOrDefault(0.0); + primitiveResult.waterMaskTranslationY = + waterMaskTranslationYIt->second.getDoubleOrDefault(0.0); + primitiveResult.waterMaskScale = + waterMaskScaleIt->second.getDoubleOrDefault(1.0); + } } #pragma region Features Metadata helper functions(load thread) @@ -1141,8 +1114,7 @@ constexpr glm::dmat4 yInvertMatrix = { } // namespace template -static CesiumAsync::Future loadPrimitive( - const CesiumAsync::AsyncSystem& asyncSystem, +static void loadPrimitive( LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1150,745 +1122,595 @@ static CesiumAsync::Future loadPrimitive( const AccessorView& positionView, const TIndexAccessor& indicesView, const CesiumGeospatial::Ellipsoid& ellipsoid) { - struct PrimitiveLoadingWorkingData { - TArray StaticMeshBuildVertices; - bool hasTangents; - bool needsTangents; - bool hasNormals; - AccessorView normalAccessor; - AccessorView tangentAccessor; - const Material* material; - const MaterialPBRMetallicRoughness* pbrMetallicRoughness; - TUniquePtr RenderData = nullptr; - bool duplicateVertices; - TArray indices; - bool hasVertexColors; - - PrimitiveLoadingWorkingData() {} - }; - TSharedPtr workingData = - MakeShared(); - - return asyncSystem - .runInWorkerThread([&asyncSystem, - pPrimitiveResult = &primitiveResult, - workingData, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid]() { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - - LoadPrimitiveResult& primitiveResult = *pPrimitiveResult; - - Model& model = - *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; - MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; - - if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && - primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && - primitive.mode != MeshPrimitive::Mode::POINTS) { - // TODO: add support for other primitive types. - UE_LOG( - LogCesium, - Warning, - TEXT("Primitive mode %d is not supported"), - primitive.mode); - return asyncSystem.createResolvedFuture(); - } + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - std::string name = "glTF"; + Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; + MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; - auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); - if (urlIt != model.extras.end()) { - name = urlIt->second.getStringOrDefault("glTF"); - name = constrainLength(name, 256); - } + if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && + primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && + primitive.mode != MeshPrimitive::Mode::POINTS) { + // TODO: add support for other primitive types. + UE_LOG( + LogCesium, + Warning, + TEXT("Primitive mode %d is not supported"), + primitive.mode); + return; + } - auto meshIt = std::find_if( - model.meshes.begin(), - model.meshes.end(), - [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); - if (meshIt != model.meshes.end()) { - int64_t meshIndex = meshIt - model.meshes.begin(); - name += " mesh " + std::to_string(meshIndex); - } + std::string name = "glTF"; - auto primitiveIt = std::find_if( - mesh.primitives.begin(), - mesh.primitives.end(), - [&primitive](const MeshPrimitive& candidate) { - return &candidate == &primitive; - }); - if (primitiveIt != mesh.primitives.end()) { - int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); - name += " primitive " + std::to_string(primitiveIndex); - } + auto urlIt = model.extras.find("Cesium3DTiles_TileUrl"); + if (urlIt != model.extras.end()) { + name = urlIt->second.getStringOrDefault("glTF"); + name = constrainLength(name, 256); + } - primitiveResult.name = name; + auto meshIt = std::find_if( + model.meshes.begin(), + model.meshes.end(), + [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); + if (meshIt != model.meshes.end()) { + int64_t meshIndex = meshIt - model.meshes.begin(); + name += " mesh " + std::to_string(meshIndex); + } - if (positionView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid position buffer"), - UTF8_TO_TCHAR(name.c_str())); - return asyncSystem.createResolvedFuture(); - } + auto primitiveIt = std::find_if( + mesh.primitives.begin(), + mesh.primitives.end(), + [&primitive](const MeshPrimitive& candidate) { + return &candidate == &primitive; + }); + if (primitiveIt != mesh.primitives.end()) { + int64_t primitiveIndex = primitiveIt - mesh.primitives.begin(); + name += " primitive " + std::to_string(primitiveIndex); + } - if constexpr (IsAccessorView::value) { - if (indicesView.status() != AccessorViewStatus::Valid) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid indices buffer"), - UTF8_TO_TCHAR(name.c_str())); - return asyncSystem.createResolvedFuture(); - } - } + primitiveResult.name = name; - int materialID = primitive.material; - workingData->material = - materialID >= 0 && materialID < model.materials.size() - ? &model.materials[materialID] - : &defaultMaterial; - - primitiveResult.materialIndex = materialID; - - workingData->pbrMetallicRoughness = - workingData->material->pbrMetallicRoughness - ? &workingData->material->pbrMetallicRoughness.value() - : &defaultPbrMetallicRoughness; - - auto normalAccessorIt = primitive.attributes.find("NORMAL"); - if (normalAccessorIt != primitive.attributes.end()) { - int normalAccessorID = normalAccessorIt->second; - workingData->normalAccessor = - AccessorView(model, normalAccessorID); - workingData->hasNormals = - workingData->normalAccessor.status() == AccessorViewStatus::Valid; - if (!workingData->hasNormals) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), - UTF8_TO_TCHAR(name.c_str())); - } - } + if (positionView.status() != AccessorViewStatus::Valid) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid position buffer"), + UTF8_TO_TCHAR(name.c_str())); + return; + } - primitiveResult.isUnlit = - workingData->material->hasExtension() && - !options.pMeshOptions->pNodeOptions->pModelOptions - ->ignoreKhrMaterialsUnlit; - - // We can't calculate flat normals for points or lines, so we have to - // force them to be unlit if no normals are specified. Otherwise this - // causes a crash when attempting to calculate flat normals. - bool isTriangles = - primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; - - if (!isTriangles && !workingData->hasNormals) { - primitiveResult.isUnlit = true; - } + if constexpr (IsAccessorView::value) { + if (indicesView.status() != AccessorViewStatus::Valid) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid indices buffer"), + UTF8_TO_TCHAR(name.c_str())); + return; + } + } - bool hasNormalMap = workingData->material->normalTexture.has_value(); - if (hasNormalMap) { - const CesiumGltf::Texture* pTexture = Model::getSafe( - &model.textures, - workingData->material->normalTexture->index); - hasNormalMap = - pTexture != nullptr && - Model::getSafe(&model.images, pTexture->source) != nullptr; - } + auto normalAccessorIt = primitive.attributes.find("NORMAL"); + AccessorView normalAccessor; + bool hasNormals = false; + if (normalAccessorIt != primitive.attributes.end()) { + int normalAccessorID = normalAccessorIt->second; + normalAccessor = AccessorView(model, normalAccessorID); + hasNormals = normalAccessor.status() == AccessorViewStatus::Valid; + if (!hasNormals) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "%s: Invalid normal buffer. Flat normals will be auto-generated instead."), + UTF8_TO_TCHAR(name.c_str())); + } + } - workingData->needsTangents = - hasNormalMap || options.pMeshOptions->pNodeOptions->pModelOptions - ->alwaysIncludeTangents; - - workingData->hasTangents = false; - auto tangentAccessorIt = primitive.attributes.find("TANGENT"); - if (tangentAccessorIt != primitive.attributes.end()) { - int tangentAccessorID = tangentAccessorIt->second; - workingData->tangentAccessor = - AccessorView(model, tangentAccessorID); - workingData->hasTangents = workingData->tangentAccessor.status() == - AccessorViewStatus::Valid; - if (!workingData->hasTangents) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s: Invalid tangent buffer."), - UTF8_TO_TCHAR(name.c_str())); - } - } + int materialID = primitive.material; + const Material& material = + materialID >= 0 && materialID < model.materials.size() + ? model.materials[materialID] + : defaultMaterial; - return applyWaterMask(asyncSystem, model, primitive, primitiveResult); - }) - .thenImmediately([&asyncSystem, - &primitiveResult, - workingData, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid]() { - Model& model = - *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; - MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; - - // The water effect works by animating the normal, and the normal - // is expressed in tangent space. So if we have water, we need - // tangents. - if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { - workingData->needsTangents = true; - } + primitiveResult.materialIndex = materialID; - workingData->RenderData = MakeUnique(); - workingData->RenderData->AllocateLODResources(1); - - FStaticMeshLODResources& LODResources = - workingData->RenderData->LODResources[0]; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) - - const std::vector& min = positionAccessor.min; - const std::vector& max = positionAccessor.max; - glm::dvec3 minPosition{std::numeric_limits::max()}; - glm::dvec3 maxPosition{std::numeric_limits::lowest()}; - if (min.size() != 3 || max.size() != 3) { - for (int32_t i = 0; i < positionView.size(); ++i) { - minPosition.x = - glm::min(minPosition.x, positionView[i].X); - minPosition.y = - glm::min(minPosition.y, positionView[i].Y); - minPosition.z = - glm::min(minPosition.z, positionView[i].Z); - - maxPosition.x = - glm::max(maxPosition.x, positionView[i].X); - maxPosition.y = - glm::max(maxPosition.y, positionView[i].Y); - maxPosition.z = - glm::max(maxPosition.z, positionView[i].Z); - } - } else { - minPosition = glm::dvec3(min[0], min[1], min[2]); - maxPosition = glm::dvec3(max[0], max[1], max[2]); - } + primitiveResult.isUnlit = + material.hasExtension() && + !options.pMeshOptions->pNodeOptions->pModelOptions + ->ignoreKhrMaterialsUnlit; - minPosition *= CesiumPrimitiveData::positionScaleFactor; - maxPosition *= CesiumPrimitiveData::positionScaleFactor; + // We can't calculate flat normals for points or lines, so we have to force + // them to be unlit if no normals are specified. Otherwise this causes a + // crash when attempting to calculate flat normals. + bool isTriangles = primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || + primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; - primitiveResult.dimensions = - glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); + if (!isTriangles && !hasNormals) { + primitiveResult.isUnlit = true; + } - FBox aaBox( - FVector3d(minPosition.x, -minPosition.y, minPosition.z), - FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); + const MaterialPBRMetallicRoughness& pbrMetallicRoughness = + material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() + : defaultPbrMetallicRoughness; - aaBox.GetCenterAndExtents( - workingData->RenderData->Bounds.Origin, - workingData->RenderData->Bounds.BoxExtent); - workingData->RenderData->Bounds.SphereRadius = 0.0f; - } + bool hasNormalMap = material.normalTexture.has_value(); + if (hasNormalMap) { + const CesiumGltf::Texture* pTexture = + Model::getSafe(&model.textures, material.normalTexture->index); + hasNormalMap = pTexture != nullptr && + Model::getSafe(&model.images, pTexture->source) != nullptr; + } + + bool needsTangents = + hasNormalMap || + options.pMeshOptions->pNodeOptions->pModelOptions->alwaysIncludeTangents; + + bool hasTangents = false; + auto tangentAccessorIt = primitive.attributes.find("TANGENT"); + AccessorView tangentAccessor; + if (tangentAccessorIt != primitive.attributes.end()) { + int tangentAccessorID = tangentAccessorIt->second; + tangentAccessor = AccessorView(model, tangentAccessorID); + hasTangents = tangentAccessor.status() == AccessorViewStatus::Valid; + if (!hasTangents) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s: Invalid tangent buffer."), + UTF8_TO_TCHAR(name.c_str())); + } + } - if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::POINTS) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - workingData->indices.SetNum( - static_cast::SizeType>(indicesView.size())); + applyWaterMask(model, primitive, primitiveResult); - for (int32 i = 0; i < indicesView.size(); ++i) { - workingData->indices[i] = indicesView[i]; - } - } else { - // assume TRIANGLE_STRIP because all others are rejected - // earlier. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) - workingData->indices.SetNum(static_cast::SizeType>( - 3 * (indicesView.size() - 2))); - for (int32 i = 0; i < indicesView.size() - 2; ++i) { - if (i % 2) { - workingData->indices[3 * i] = indicesView[i]; - workingData->indices[3 * i + 1] = indicesView[i + 2]; - workingData->indices[3 * i + 2] = indicesView[i + 1]; - } else { - workingData->indices[3 * i] = indicesView[i]; - workingData->indices[3 * i + 1] = indicesView[i + 1]; - workingData->indices[3 * i + 2] = indicesView[i + 2]; - } - } - } + // The water effect works by animating the normal, and the normal is + // expressed in tangent space. So if we have water, we need tangents. + if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) { + needsTangents = true; + } - // If we don't have normals, the gltf spec prescribes that the - // client implementation must generate flat normals, which - // requires duplicating vertices shared by multiple triangles. If - // we don't have tangents, but need them, we need to use a tangent - // space generation algorithm which requires duplicated vertices. - workingData->duplicateVertices = - !workingData->hasNormals || - (workingData->needsTangents && !workingData->hasTangents); - workingData->duplicateVertices = - workingData->duplicateVertices && - primitive.mode != MeshPrimitive::Mode::POINTS; - - workingData->StaticMeshBuildVertices.SetNum( - workingData->duplicateVertices - ? workingData->indices.Num() - : static_cast(positionView.size())); - - { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - const TMeshVector3& pos = positionView[vertexIndex]; - vertex.Position.X = - pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = - -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = - pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - workingData->RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - - workingData->RenderData->Bounds.Origin) - .Size(), - workingData->RenderData->Bounds.SphereRadius); - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) - for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - const TMeshVector3& pos = positionView[i]; - vertex.Position.X = - pos.X * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Y = - -pos.Y * CesiumPrimitiveData::positionScaleFactor; - vertex.Position.Z = - pos.Z * CesiumPrimitiveData::positionScaleFactor; - vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); - vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); - workingData->RenderData->Bounds.SphereRadius = FMath::Max( - (FVector(vertex.Position) - - workingData->RenderData->Bounds.Origin) - .Size(), - workingData->RenderData->Bounds.SphereRadius); - } - } - } + TUniquePtr RenderData = + MakeUnique(); + RenderData->AllocateLODResources(1); - workingData->hasVertexColors = false; + FStaticMeshLODResources& LODResources = RenderData->LODResources[0]; - auto colorAccessorIt = primitive.attributes.find("COLOR_0"); - if (colorAccessorIt != primitive.attributes.end()) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) - int colorAccessorID = colorAccessorIt->second; - workingData->hasVertexColors = createAccessorView( - model, - colorAccessorID, - ColorVisitor{ - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices}); - } + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB) + + const std::vector& min = positionAccessor.min; + const std::vector& max = positionAccessor.max; + glm::dvec3 minPosition{std::numeric_limits::max()}; + glm::dvec3 maxPosition{std::numeric_limits::lowest()}; + if (min.size() != 3 || max.size() != 3) { + for (int32_t i = 0; i < positionView.size(); ++i) { + minPosition.x = glm::min(minPosition.x, positionView[i].X); + minPosition.y = glm::min(minPosition.y, positionView[i].Y); + minPosition.z = glm::min(minPosition.z, positionView[i].Z); + + maxPosition.x = glm::max(maxPosition.x, positionView[i].X); + maxPosition.y = glm::max(maxPosition.y, positionView[i].Y); + maxPosition.z = glm::max(maxPosition.z, positionView[i].Z); + } + } else { + minPosition = glm::dvec3(min[0], min[1], min[2]); + maxPosition = glm::dvec3(max[0], max[1], max[2]); + } - LODResources.bHasColorVertexData = workingData->hasVertexColors; + minPosition *= CesiumPrimitiveData::positionScaleFactor; + maxPosition *= CesiumPrimitiveData::positionScaleFactor; - // This must be done before material textures are loaded, in case - // any of the material textures are also used for features + - // metadata. - loadPrimitiveFeaturesMetadata( - primitiveResult, - options, - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices); + primitiveResult.dimensions = + glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0)); - std::vector>> - textureLoads; + FBox aaBox( + FVector3d(minPosition.x, -minPosition.y, minPosition.z), + FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness->baseColorTexture, - true)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->pbrMetallicRoughness->metallicRoughnessTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->normalTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->occlusionTexture, - false)); - textureLoads.push_back(loadTexture( - asyncSystem, - model, - workingData->material->emissiveTexture, - true)); - - return asyncSystem.all(std::move(textureLoads)); - }) - .thenImmediately([&primitiveResult, - workingData, - &transform, - &options, - &positionAccessor, - &positionView, - &indicesView, - &ellipsoid](std::vector> - results) { - Model& model = - *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - const Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; - const MeshPrimitive& primitive = - mesh.primitives[options.primitiveIndex]; - - // We need to copy the texture coordinates associated with - // each texture (if any) into the the appropriate UVs slot - // in FStaticMeshBuildVertex. - - std::unordered_map& gltfToUnrealTexCoordMap = - primitiveResult.GltfToUnrealTexCoordMap; - - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) - primitiveResult.baseColorTexture = MoveTemp(results[0]); - primitiveResult.metallicRoughnessTexture = MoveTemp(results[1]); - primitiveResult.normalTexture = MoveTemp(results[2]); - primitiveResult.occlusionTexture = MoveTemp(results[3]); - primitiveResult.emissiveTexture = MoveTemp(results[4]); - } + aaBox.GetCenterAndExtents( + RenderData->Bounds.Origin, + RenderData->Bounds.BoxExtent); + RenderData->Bounds.SphereRadius = 0.0f; + } - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinates) + TArray indices; + if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || + primitive.mode == MeshPrimitive::Mode::POINTS) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + indices.SetNum(static_cast::SizeType>(indicesView.size())); - primitiveResult - .textureCoordinateParameters["baseColorTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->pbrMetallicRoughness->baseColorTexture, - gltfToUnrealTexCoordMap); - primitiveResult.textureCoordinateParameters - ["metallicRoughnessTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->pbrMetallicRoughness->metallicRoughnessTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["normalTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->normalTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["occlusionTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->occlusionTexture, - gltfToUnrealTexCoordMap); - primitiveResult - .textureCoordinateParameters["emissiveTextureCoordinateIndex"] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - workingData->material->emissiveTexture, - gltfToUnrealTexCoordMap); - - for (size_t i = 0; - i < primitiveResult.overlayTextureCoordinateIDToUVIndex.size(); - ++i) { - std::string attributeName = "_CESIUMOVERLAY_" + std::to_string(i); - auto overlayIt = primitive.attributes.find(attributeName); - if (overlayIt != primitive.attributes.end()) { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = - updateTextureCoordinates( - model, - primitive, - workingData->duplicateVertices, - workingData->StaticMeshBuildVertices, - workingData->indices, - attributeName, - gltfToUnrealTexCoordMap); - } else { - primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0; - } - } - } + for (int32 i = 0; i < indicesView.size(); ++i) { + indices[i] = indicesView[i]; + } + } else { + // assume TRIANGLE_STRIP because all others are rejected earlier. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) + indices.SetNum( + static_cast::SizeType>(3 * (indicesView.size() - 2))); + for (int32 i = 0; i < indicesView.size() - 2; ++i) { + if (i % 2) { + indices[3 * i] = indicesView[i]; + indices[3 * i + 1] = indicesView[i + 2]; + indices[3 * i + 2] = indicesView[i + 1]; + } else { + indices[3 * i] = indicesView[i]; + indices[3 * i + 1] = indicesView[i + 1]; + indices[3 * i + 2] = indicesView[i + 2]; + } + } + } - // TangentX: Tangent - // TangentY: Bi-tangent - // TangentZ: Normal - - if (workingData->hasNormals) { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE( - Cesium::CopyNormalsForDuplicatedVertices) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = - workingData->normalAccessor[vertexIndex]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) - for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); - vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); - const TMeshVector3& normal = workingData->normalAccessor[i]; - vertex.TangentZ.X = normal.X; - vertex.TangentZ.Y = -normal.Y; - vertex.TangentZ.Z = normal.Z; - } - } - } else { - if (primitiveResult.isUnlit) { - glm::dvec3 ecefCenter = glm::dvec3( - transform * glm::dvec4( - VecMath::createVector3D( - workingData->RenderData->Bounds.Origin), - 1.0)); - TMeshVector3 upDir = TMeshVector3(VecMath::createVector( - glm::affineInverse(transform) * - glm::dvec4( - ellipsoid.geodeticSurfaceNormal(glm::dvec3(ecefCenter)), - 0.0))); - upDir.Y *= -1; - setUniformNormals(workingData->StaticMeshBuildVertices, upDir); - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) - computeFlatNormals(workingData->StaticMeshBuildVertices); - } - } + // If we don't have normals, the gltf spec prescribes that the client + // implementation must generate flat normals, which requires duplicating + // vertices shared by multiple triangles. If we don't have tangents, but + // need them, we need to use a tangent space generation algorithm which + // requires duplicated vertices. + bool duplicateVertices = !hasNormals || (needsTangents && !hasTangents); + duplicateVertices = + duplicateVertices && primitive.mode != MeshPrimitive::Mode::POINTS; - if (workingData->hasTangents) { - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE( - Cesium::CopyTangentsForDuplicatedVertices) - for (int i = 0; i < workingData->indices.Num(); ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - uint32 vertexIndex = workingData->indices[i]; - const TMeshVector4& tangent = - workingData->tangentAccessor[vertexIndex]; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = - TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * - tangent.W; - } - } else { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) - for (int i = 0; i < workingData->StaticMeshBuildVertices.Num(); - ++i) { - FStaticMeshBuildVertex& vertex = - workingData->StaticMeshBuildVertices[i]; - const TMeshVector4& tangent = workingData->tangentAccessor[i]; - vertex.TangentX = tangent; - vertex.TangentX.X = tangent.X; - vertex.TangentX.Y = -tangent.Y; - vertex.TangentX.Z = tangent.Z; - vertex.TangentY = - TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * - tangent.W; - } - } - } + TArray StaticMeshBuildVertices; + StaticMeshBuildVertices.SetNum( + duplicateVertices ? indices.Num() + : static_cast(positionView.size())); - if (workingData->needsTangents && !workingData->hasTangents) { - // Use mikktspace to calculate the tangents. - // Note that this assumes normals and UVs are already - // populated. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) - computeTangentSpace(workingData->StaticMeshBuildVertices); - } + { + if (duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions) + for (int i = 0; i < indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + uint32 vertexIndex = indices[i]; + const TMeshVector3& pos = positionView[vertexIndex]; + vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - RenderData->Bounds.Origin).Size(), + RenderData->Bounds.SphereRadius); + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions) + for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + const TMeshVector3& pos = positionView[i]; + vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor; + vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor; + vertex.UVs[0] = TMeshVector2(0.0f, 0.0f); + vertex.UVs[2] = TMeshVector2(0.0f, 0.0f); + RenderData->Bounds.SphereRadius = FMath::Max( + (FVector(vertex.Position) - RenderData->Bounds.Origin).Size(), + RenderData->Bounds.SphereRadius); + } + } + } - FStaticMeshLODResources& LODResources = - workingData->RenderData->LODResources[0]; + bool hasVertexColors = false; - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) + auto colorAccessorIt = primitive.attributes.find("COLOR_0"); + if (colorAccessorIt != primitive.attributes.end()) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors) + int colorAccessorID = colorAccessorIt->second; + hasVertexColors = createAccessorView( + model, + colorAccessorID, + ColorVisitor{duplicateVertices, StaticMeshBuildVertices, indices}); + } - // Set to full precision (32-bit) UVs. This is especially - // important for metadata because integer feature IDs can - // and will lose meaningful precision when using 16-bit - // floats. - LODResources.VertexBuffers.StaticMeshVertexBuffer - .SetUseFullPrecisionUVs(true); + LODResources.bHasColorVertexData = hasVertexColors; - LODResources.VertexBuffers.PositionVertexBuffer.Init( - workingData->StaticMeshBuildVertices, - false); + // We need to copy the texture coordinates associated with each texture (if + // any) into the the appropriate UVs slot in FStaticMeshBuildVertex. - FColorVertexBuffer& ColorVertexBuffer = - LODResources.VertexBuffers.ColorVertexBuffer; - if (workingData->hasVertexColors) { - ColorVertexBuffer.Init(workingData->StaticMeshBuildVertices, false); - } + std::unordered_map& gltfToUnrealTexCoordMap = + primitiveResult.GltfToUnrealTexCoordMap; - uint32 numberOfTextureCoordinates = - gltfToUnrealTexCoordMap.size() == 0 - ? 1 - : uint32(gltfToUnrealTexCoordMap.size()); - - FStaticMeshVertexBuffer& vertexBuffer = - LODResources.VertexBuffers.StaticMeshVertexBuffer; - vertexBuffer.Init( - workingData->StaticMeshBuildVertices.Num(), - numberOfTextureCoordinates, - false); - - // Manually copy the vertices into the buffer. We do this because - // UE 5.3 and 5.4 have a bug where the overload of - // `FStaticMeshVertexBuffer::Init` taking an array of - // `FStaticMeshBuildVertex` will create a mesh with all 8 sets of - // texture coordinates, even when we usually only need one or two. See - // https://github.com/CesiumGS/cesium-unreal/issues/1513 - for (uint32 vertexIndex = 0; - vertexIndex < uint32(workingData->StaticMeshBuildVertices.Num()); - ++vertexIndex) { - const FStaticMeshBuildVertex& source = - workingData->StaticMeshBuildVertices[vertexIndex]; - - vertexBuffer.SetVertexTangents( - vertexIndex, - source.TangentX, - source.TangentY, - source.TangentZ); - for (uint32 uvIndex = 0; uvIndex < numberOfTextureCoordinates; - uvIndex++) { - vertexBuffer.SetVertexUV( - vertexIndex, - uvIndex, - source.UVs[uvIndex], - false); - } - } - } + // This must be done before material textures are loaded, in case any of the + // material textures are also used for features + metadata. + loadPrimitiveFeaturesMetadata( + primitiveResult, + options, + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices); - FStaticMeshSectionArray& Sections = LODResources.Sections; - FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); - // This will be ignored if the primitive contains points. - section.NumTriangles = workingData->indices.Num() / 3; - section.FirstIndex = 0; - section.MinVertexIndex = 0; - section.MaxVertexIndex = workingData->StaticMeshBuildVertices.Num() - 1; - section.bEnableCollision = - primitive.mode != MeshPrimitive::Mode::POINTS; - section.bCastShadow = true; - section.MaterialIndex = 0; - - if (workingData->duplicateVertices) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) - for (int32 i = 0; i < workingData->indices.Num(); i++) { - workingData->indices[i] = i; - } - } + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures) + primitiveResult.baseColorTexture = + loadTexture(model, pbrMetallicRoughness.baseColorTexture, true); + primitiveResult.metallicRoughnessTexture = loadTexture( + model, + pbrMetallicRoughness.metallicRoughnessTexture, + false); + primitiveResult.normalTexture = + loadTexture(model, material.normalTexture, false); + primitiveResult.occlusionTexture = + loadTexture(model, material.occlusionTexture, false); + primitiveResult.emissiveTexture = + loadTexture(model, material.emissiveTexture, true); + } - { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) - LODResources.IndexBuffer.SetIndices( - workingData->indices, - workingData->StaticMeshBuildVertices.Num() >= - std::numeric_limits::max() - ? EIndexBufferStride::Type::Force32Bit - : EIndexBufferStride::Type::Force16Bit); - } + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinates) - LODResources.bHasDepthOnlyIndices = false; - LODResources.bHasReversedIndices = false; - LODResources.bHasReversedDepthOnlyIndices = false; - - primitiveResult.meshIndex = options.pMeshOptions->meshIndex; - primitiveResult.primitiveIndex = options.primitiveIndex; - primitiveResult.RenderData = MoveTemp(workingData->RenderData); - primitiveResult.pCollisionMesh = nullptr; - - double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; - glm::dmat4 scaleMatrix = glm::dmat4( - glm::dvec4(scale, 0.0, 0.0, 0.0), - glm::dvec4(0.0, scale, 0.0, 0.0), - glm::dvec4(0.0, 0.0, scale, 0.0), - glm::dvec4(0.0, 0.0, 0.0, 1.0)); - - primitiveResult.transform = transform * yInvertMatrix * scaleMatrix; - - if (primitive.mode != MeshPrimitive::Mode::POINTS && - options.pMeshOptions->pNodeOptions->pModelOptions - ->createPhysicsMeshes) { - if (workingData->StaticMeshBuildVertices.Num() != 0 && - workingData->indices.Num() != 0) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) - primitiveResult.pCollisionMesh = - workingData->StaticMeshBuildVertices.Num() < - TNumericLimits::Max() - ? BuildChaosTriangleMeshes( - workingData->StaticMeshBuildVertices, - workingData->indices) - : BuildChaosTriangleMeshes( - workingData->StaticMeshBuildVertices, - workingData->indices); - } - } - }); + primitiveResult + .textureCoordinateParameters["baseColorTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + pbrMetallicRoughness.baseColorTexture, + gltfToUnrealTexCoordMap); + primitiveResult.textureCoordinateParameters + ["metallicRoughnessTextureCoordinateIndex"] = updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + pbrMetallicRoughness.metallicRoughnessTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["normalTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + material.normalTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["occlusionTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + material.occlusionTexture, + gltfToUnrealTexCoordMap); + primitiveResult + .textureCoordinateParameters["emissiveTextureCoordinateIndex"] = + updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + material.emissiveTexture, + gltfToUnrealTexCoordMap); + + for (size_t i = 0; + i < primitiveResult.overlayTextureCoordinateIDToUVIndex.size(); + ++i) { + std::string attributeName = "_CESIUMOVERLAY_" + std::to_string(i); + auto overlayIt = primitive.attributes.find(attributeName); + if (overlayIt != primitive.attributes.end()) { + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = + updateTextureCoordinates( + model, + primitive, + duplicateVertices, + StaticMeshBuildVertices, + indices, + attributeName, + gltfToUnrealTexCoordMap); + } else { + primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0; + } + } + } + + // TangentX: Tangent + // TangentY: Bi-tangent + // TangentZ: Normal + + if (hasNormals) { + if (duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormalsForDuplicatedVertices) + for (int i = 0; i < indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + uint32 vertexIndex = indices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = normalAccessor[vertexIndex]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals) + for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f); + vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f); + const TMeshVector3& normal = normalAccessor[i]; + vertex.TangentZ.X = normal.X; + vertex.TangentZ.Y = -normal.Y; + vertex.TangentZ.Z = normal.Z; + } + } + } else { + if (primitiveResult.isUnlit) { + glm::dvec3 ecefCenter = glm::dvec3( + transform * + glm::dvec4(VecMath::createVector3D(RenderData->Bounds.Origin), 1.0)); + TMeshVector3 upDir = TMeshVector3(VecMath::createVector( + glm::affineInverse(transform) * + glm::dvec4( + ellipsoid.geodeticSurfaceNormal(glm::dvec3(ecefCenter)), + 0.0))); + upDir.Y *= -1; + setUniformNormals(StaticMeshBuildVertices, upDir); + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals) + computeFlatNormals(StaticMeshBuildVertices); + } + } + + if (hasTangents) { + if (duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangentsForDuplicatedVertices) + for (int i = 0; i < indices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + uint32 vertexIndex = indices[i]; + const TMeshVector4& tangent = tangentAccessor[vertexIndex]; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = + TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * + tangent.W; + } + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents) + for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) { + FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i]; + const TMeshVector4& tangent = tangentAccessor[i]; + vertex.TangentX = tangent; + vertex.TangentX.X = tangent.X; + vertex.TangentX.Y = -tangent.Y; + vertex.TangentX.Z = tangent.Z; + vertex.TangentY = + TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) * + tangent.W; + } + } + } + + if (needsTangents && !hasTangents) { + // Use mikktspace to calculate the tangents. + // Note that this assumes normals and UVs are already populated. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents) + computeTangentSpace(StaticMeshBuildVertices); + } + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers) + + // Set to full precision (32-bit) UVs. This is especially important for + // metadata because integer feature IDs can and will lose meaningful + // precision when using 16-bit floats. + LODResources.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs( + true); + + LODResources.VertexBuffers.PositionVertexBuffer.Init( + StaticMeshBuildVertices, + false); + + FColorVertexBuffer& ColorVertexBuffer = + LODResources.VertexBuffers.ColorVertexBuffer; + if (hasVertexColors) { + ColorVertexBuffer.Init(StaticMeshBuildVertices, false); + } + + uint32 numberOfTextureCoordinates = + gltfToUnrealTexCoordMap.size() == 0 + ? 1 + : uint32(gltfToUnrealTexCoordMap.size()); + + FStaticMeshVertexBuffer& vertexBuffer = + LODResources.VertexBuffers.StaticMeshVertexBuffer; + vertexBuffer.Init( + StaticMeshBuildVertices.Num(), + numberOfTextureCoordinates, + false); + + // Manually copy the vertices into the buffer. We do this because UE 5.3 + // and 5.4 have a bug where the overload of `FStaticMeshVertexBuffer::Init` + // taking an array of `FStaticMeshBuildVertex` will create a mesh with all 8 + // sets of texture coordinates, even when we usually only need one or two. + // See https://github.com/CesiumGS/cesium-unreal/issues/1513 + for (uint32 vertexIndex = 0; + vertexIndex < uint32(StaticMeshBuildVertices.Num()); + ++vertexIndex) { + const FStaticMeshBuildVertex& source = + StaticMeshBuildVertices[vertexIndex]; + + vertexBuffer.SetVertexTangents( + vertexIndex, + source.TangentX, + source.TangentY, + source.TangentZ); + for (uint32 uvIndex = 0; uvIndex < numberOfTextureCoordinates; + uvIndex++) { + vertexBuffer + .SetVertexUV(vertexIndex, uvIndex, source.UVs[uvIndex], false); + } + } + } + + FStaticMeshSectionArray& Sections = LODResources.Sections; + FStaticMeshSection& section = Sections.AddDefaulted_GetRef(); + // This will be ignored if the primitive contains points. + section.NumTriangles = indices.Num() / 3; + section.FirstIndex = 0; + section.MinVertexIndex = 0; + section.MaxVertexIndex = StaticMeshBuildVertices.Num() - 1; + section.bEnableCollision = primitive.mode != MeshPrimitive::Mode::POINTS; + section.bCastShadow = true; + section.MaterialIndex = 0; + + if (duplicateVertices) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder) + for (int32 i = 0; i < indices.Num(); i++) { + indices[i] = i; + } + } + + { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices) + LODResources.IndexBuffer.SetIndices( + indices, + StaticMeshBuildVertices.Num() >= std::numeric_limits::max() + ? EIndexBufferStride::Type::Force32Bit + : EIndexBufferStride::Type::Force16Bit); + } + + LODResources.bHasDepthOnlyIndices = false; + LODResources.bHasReversedIndices = false; + LODResources.bHasReversedDepthOnlyIndices = false; + + primitiveResult.primitiveIndex = options.primitiveIndex; + primitiveResult.RenderData = std::move(RenderData); + primitiveResult.pCollisionMesh = nullptr; + + double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor; + glm::dmat4 scaleMatrix = glm::dmat4( + glm::dvec4(scale, 0.0, 0.0, 0.0), + glm::dvec4(0.0, scale, 0.0, 0.0), + glm::dvec4(0.0, 0.0, scale, 0.0), + glm::dvec4(0.0, 0.0, 0.0, 1.0)); + + primitiveResult.transform = transform * yInvertMatrix * scaleMatrix; + + if (primitive.mode != MeshPrimitive::Mode::POINTS && + options.pMeshOptions->pNodeOptions->pModelOptions->createPhysicsMeshes) { + if (StaticMeshBuildVertices.Num() != 0 && indices.Num() != 0) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) + primitiveResult.pCollisionMesh = + StaticMeshBuildVertices.Num() < TNumericLimits::Max() + ? BuildChaosTriangleMeshes( + StaticMeshBuildVertices, + indices) + : BuildChaosTriangleMeshes( + StaticMeshBuildVertices, + indices); + } + } } -static CesiumAsync::SharedFuture loadIndexedPrimitive( - const CesiumAsync::AsyncSystem& asyncSystem, +static void loadIndexedPrimitive( LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1904,69 +1726,51 @@ static CesiumAsync::SharedFuture loadIndexedPrimitive( if (indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_BYTE) { AccessorView indexAccessor(model, primitive.indices); - return loadPrimitive( - asyncSystem, - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid) - .thenImmediately( - [indexAccessor, pPrimitiveResult = &primitiveResult]() { - pPrimitiveResult->IndexAccessor = indexAccessor; - }) - .share(); + loadPrimitive( + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid); + primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_SHORT) { AccessorView indexAccessor(model, primitive.indices); - return loadPrimitive( - asyncSystem, - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid) - .thenImmediately( - [indexAccessor, pPrimitiveResult = &primitiveResult]() { - pPrimitiveResult->IndexAccessor = indexAccessor; - }) - .share(); + loadPrimitive( + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid); + primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == Accessor::ComponentType::UNSIGNED_INT) { AccessorView indexAccessor(model, primitive.indices); - return loadPrimitive( - asyncSystem, - primitiveResult, - transform, - options, - positionAccessor, - positionView, - indexAccessor, - ellipsoid) - .thenImmediately( - [indexAccessor, pPrimitiveResult = &primitiveResult]() { - pPrimitiveResult->IndexAccessor = indexAccessor; - }) - .share(); + loadPrimitive( + primitiveResult, + transform, + options, + positionAccessor, + positionView, + indexAccessor, + ellipsoid); + primitiveResult.IndexAccessor = indexAccessor; } else { UE_LOG( LogCesium, VeryVerbose, TEXT( "Skip loading primitive due to invalid component type in its index accessor.")); - - return asyncSystem.createResolvedFuture().share(); } } -static CesiumAsync::SharedFuture loadPrimitive( - const CesiumAsync::AsyncSystem& asyncSystem, +static void loadPrimitive( LoadPrimitiveResult& result, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, @@ -1981,7 +1785,7 @@ static CesiumAsync::SharedFuture loadPrimitive( auto positionAccessorIt = primitive.attributes.find("POSITION"); if (positionAccessorIt == primitive.attributes.end()) { // This primitive doesn't have a POSITION semantic, ignore it. - return asyncSystem.createResolvedFuture(0).share(); + return; } int positionAccessorID = positionAccessorIt->second; @@ -1989,7 +1793,7 @@ static CesiumAsync::SharedFuture loadPrimitive( Model::getSafe(&model.accessors, positionAccessorID); if (!pPositionAccessor) { // Position accessor does not exist, so ignore this primitive. - return asyncSystem.createResolvedFuture(0).share(); + return; } AccessorView positionView(model, *pPositionAccessor); @@ -2000,39 +1804,27 @@ static CesiumAsync::SharedFuture loadPrimitive( for (uint32_t i = 0; i < positionView.size(); ++i) { syntheticIndexBuffer[i] = i; } - return loadPrimitive( - asyncSystem, - result, - transform, - options, - *pPositionAccessor, - positionView, - syntheticIndexBuffer, - ellipsoid) - .thenImmediately([positionView, pResult = &result]() { - pResult->PositionAccessor = std::move(positionView); - return 0; - }) - .share(); + loadPrimitive( + result, + transform, + options, + *pPositionAccessor, + positionView, + syntheticIndexBuffer, + ellipsoid); } else { - return loadIndexedPrimitive( - asyncSystem, - result, - transform, - options, - *pPositionAccessor, - positionView, - ellipsoid) - .thenImmediately([positionView, pResult = &result]() { - pResult->PositionAccessor = std::move(positionView); - return 0; - }) - .share(); + loadIndexedPrimitive( + result, + transform, + options, + *pPositionAccessor, + positionView, + ellipsoid); } + result.PositionAccessor = std::move(positionView); } -static CesiumAsync::SharedFuture loadMesh( - const CesiumAsync::AsyncSystem& asyncSystem, +static void loadMesh( std::optional& result, const glm::dmat4x4& transform, const CreateMeshOptions& options, @@ -2040,32 +1832,21 @@ static CesiumAsync::SharedFuture loadMesh( TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh) - const Model& model = *options.pNodeOptions->pModelOptions->pModel; - const Mesh& mesh = model.meshes[options.meshIndex]; + Model& model = *options.pNodeOptions->pModelOptions->pModel; + Mesh& mesh = model.meshes[options.meshIndex]; result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); - std::vector> futures; - int32_t i = 0; - for (const CesiumGltf::MeshPrimitive& primitive : mesh.primitives) { - CreatePrimitiveOptions primitiveOptions = {&options, &*result, i++}; + for (int32_t i = 0; i < mesh.primitives.size(); i++) { + CreatePrimitiveOptions primitiveOptions = {&options, &*result, i}; auto& primitiveResult = result->primitiveResults.emplace_back(); - futures.push_back(loadPrimitive( - asyncSystem, - primitiveResult, - transform, - primitiveOptions, - ellipsoid)); - } + loadPrimitive(primitiveResult, transform, primitiveOptions, ellipsoid); - return asyncSystem.all(std::move(futures)) - .thenImmediately([pResult = &result](std::vector&& result) { - std::erase_if( - (*pResult)->primitiveResults, - [](LoadPrimitiveResult& result) { return !result.RenderData; }); - return 0; - }) - .share(); + // if it doesn't have render data, then it can't be loaded + if (!primitiveResult.RenderData) { + result->primitiveResults.pop_back(); + } + } } // Helpers for different instancing rotation formats @@ -2212,20 +1993,11 @@ static void loadInstancingData( } } -static LoadNodeResult& addLoadNodeResult( - std::vector& loadNodeResults, - std::mutex& mutex) { - std::lock_guard lock(mutex); - return loadNodeResults.emplace_back(); -} - -static CesiumAsync::SharedFuture loadNode( - const CesiumAsync::AsyncSystem& asyncSystem, +static void loadNode( std::vector& loadNodeResults, const glm::dmat4x4& transform, const CreateNodeOptions& options, - const CesiumGeospatial::Ellipsoid& ellipsoid, - std::mutex& loadNodeResultsMutex) { + const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadNode) @@ -2250,8 +2022,7 @@ static CesiumAsync::SharedFuture loadNode( Model& model = *options.pModelOptions->pModel; const Node& node = *options.pNode; - LoadNodeResult& result = - addLoadNodeResult(loadNodeResults, loadNodeResultsMutex); + LoadNodeResult& result = loadNodeResults.emplace_back(); glm::dmat4x4 nodeTransform = transform; @@ -2299,54 +2070,25 @@ static CesiumAsync::SharedFuture loadNode( nodeTransform * translation * glm::dmat4(rotationQuat) * scale; } - int meshIndex = node.mesh; - CesiumAsync::SharedFuture loadMeshFuture = - asyncSystem.createResolvedFuture(0).share(); - if (meshIndex >= 0 && meshIndex < model.meshes.size()) { + int meshId = node.mesh; + if (meshId >= 0 && meshId < model.meshes.size()) { if (const auto* pGpuInstancingExtension = node.getExtension()) { loadInstancingData(model, result, pGpuInstancingExtension); } - CreateMeshOptions meshOptions = {&options, &result, meshIndex}; - loadMeshFuture = loadMesh( - asyncSystem, - result.meshResult, - nodeTransform, - meshOptions, - ellipsoid); + CreateMeshOptions meshOptions = {&options, &result, meshId}; + loadMesh(result.meshResult, nodeTransform, meshOptions, ellipsoid); } - return loadMeshFuture - .thenImmediately([node, - model, - asyncSystem, - options, - &loadNodeResultsMutex, - pLoadNodeResults = &loadNodeResults, - nodeTransform, - ellipsoid](int result) { - std::vector> futures; - for (int childNodeId : node.children) { - if (childNodeId >= 0 && childNodeId < model.nodes.size()) { - CreateNodeOptions childNodeOptions = { - options.pModelOptions, - options.pHalfConstructedModelResult, - &model.nodes[childNodeId]}; - futures.push_back(loadNode( - asyncSystem, - *pLoadNodeResults, - nodeTransform, - childNodeOptions, - ellipsoid, - loadNodeResultsMutex)); - } - } - - return asyncSystem.all(std::move(futures)) - .thenImmediately([](std::vector&& results) { return 0; }) - .share(); - }) - .share(); + for (int childNodeId : node.children) { + if (childNodeId >= 0 && childNodeId < model.nodes.size()) { + CreateNodeOptions childNodeOptions = { + options.pModelOptions, + options.pHalfConstructedModelResult, + &model.nodes[childNodeId]}; + loadNode(loadNodeResults, nodeTransform, childNodeOptions, ellipsoid); + } + } } namespace { @@ -2495,109 +2237,86 @@ loadModelAnyThreadPart( const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) - auto pResult = MakeUnique(); - - Model& model = *options.pModel; - - for (const Texture& texture : model.textures) { - const Sampler& sampler = model.getSafe(model.samplers, texture.sampler); - ImageCesium& image = *model.images[texture.source].cesium; - - // Creates a future on the extension that generates mipmaps if necessary. - // We don't need to wait on this future now. That will happen when the - // texture is generated. - ExtensionUnrealTextureResource::preprocessImage( - asyncSystem, - sampler, - image); - } + return createMipMapsForAllTextures(asyncSystem, *options.pModel) + .thenInWorkerThread( + [transform, + ellipsoid, + &options]() -> TUniquePtr { + auto pResult = MakeUnique(); - loadModelMetadata(pResult->loadModelResult, options); + Model& model = *options.pModel; + loadModelMetadata(pResult->loadModelResult, options); - glm::dmat4x4 rootTransform = transform; + glm::dmat4x4 rootTransform = transform; - { - rootTransform = - CesiumGltfContent::GltfUtilities::applyRtcCenter(model, rootTransform); - applyGltfUpAxisTransform(model, rootTransform); - } + { + rootTransform = CesiumGltfContent::GltfUtilities::applyRtcCenter( + model, + rootTransform); + applyGltfUpAxisTransform(model, rootTransform); + } - std::vector> futures; - std::mutex loadNodeResultsMutex; - - 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, - &pResult->loadModelResult, - &model.nodes[nodeId]}; - futures.push_back(loadNode( - asyncSystem, - pResult->loadModelResult.nodeResults, - rootTransform, - nodeOptions, - ellipsoid, - loadNodeResultsMutex)); - } - } 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, - &pResult->loadModelResult, - &model.nodes[nodeId]}; - futures.push_back(loadNode( - asyncSystem, - pResult->loadModelResult.nodeResults, - rootTransform, - nodeOptions, - ellipsoid, - loadNodeResultsMutex)); - } - } else if (model.nodes.size() > 0) { - // No scenes at all, use the first node as the root node. - CreateNodeOptions nodeOptions = { - &options, - &pResult->loadModelResult, - &model.nodes[0]}; - futures.push_back(loadNode( - asyncSystem, - pResult->loadModelResult.nodeResults, - rootTransform, - nodeOptions, - ellipsoid, - loadNodeResultsMutex)); - } else if (model.meshes.size() > 0) { - // No nodes either, show all the meshes. - int32_t i = 0; - for (Mesh& mesh : model.meshes) { - CreateNodeOptions dummyNodeOptions = { - &options, - &pResult->loadModelResult, - nullptr}; - LoadNodeResult& dummyNodeResult = - pResult->loadModelResult.nodeResults.emplace_back(); - CreateMeshOptions meshOptions = { - &dummyNodeOptions, - &dummyNodeResult, - i++}; - futures.push_back(loadMesh( - asyncSystem, - dummyNodeResult.meshResult, - rootTransform, - meshOptions, - ellipsoid)); - } - } + 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, + &pResult->loadModelResult, + &model.nodes[nodeId]}; + loadNode( + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid); + } + } 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, + &pResult->loadModelResult, + &model.nodes[nodeId]}; + loadNode( + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid); + } + } else if (model.nodes.size() > 0) { + // No scenes at all, use the first node as the root node. + CreateNodeOptions nodeOptions = { + &options, + &pResult->loadModelResult, + &model.nodes[0]}; + loadNode( + pResult->loadModelResult.nodeResults, + rootTransform, + nodeOptions, + ellipsoid); + } else if (model.meshes.size() > 0) { + // No nodes either, show all the meshes. + for (int32_t i = 0; i < model.meshes.size(); i++) { + CreateNodeOptions dummyNodeOptions = { + &options, + &pResult->loadModelResult, + nullptr}; + LoadNodeResult& dummyNodeResult = + pResult->loadModelResult.nodeResults.emplace_back(); + CreateMeshOptions meshOptions = { + &dummyNodeOptions, + &dummyNodeResult, + i}; + loadMesh( + dummyNodeResult.meshResult, + rootTransform, + meshOptions, + ellipsoid); + } + } - return asyncSystem.all(std::move(futures)) - .thenImmediately( - [pResult = pResult.Release()](std::vector&& results) - -> TUniquePtr { - return TUniquePtr(pResult); + return MoveTemp(pResult); }); } @@ -3427,12 +3146,12 @@ static void loadPrimitiveGameThreadPart( const Cesium3DTilesSelection::BoundingVolume& boundingVolume = tile.getContentBoundingVolume().value_or(tile.getBoundingVolume()); - MeshPrimitive* pMeshPrimitive = - &model.meshes[loadResult.meshIndex].primitives[loadResult.primitiveIndex]; + MeshPrimitive& meshPrimitive = + model.meshes[loadResult.meshIndex].primitives[loadResult.primitiveIndex]; UStaticMeshComponent* pMesh = nullptr; ICesiumPrimitive* pCesiumPrimitive = nullptr; - if (pMeshPrimitive->mode == MeshPrimitive::Mode::POINTS) { + if (meshPrimitive.mode == MeshPrimitive::Mode::POINTS) { UCesiumGltfPointsComponent* pPointMesh = NewObject(pGltf, componentName); pPointMesh->UsesAdditiveRefinement = @@ -3475,8 +3194,7 @@ static void loadPrimitiveGameThreadPart( pMesh->SetFlags( RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); primData.pModel = &model; - primData.pMeshPrimitive = &model.meshes[loadResult.meshIndex] - .primitives[loadResult.primitiveIndex]; + primData.pMeshPrimitive = &meshPrimitive; primData.boundingVolume = boundingVolume; pMesh->SetRenderCustomDepth(pGltf->CustomDepthParameters.RenderCustomDepth); pMesh->SetCustomDepthStencilWriteMask( @@ -3497,11 +3215,9 @@ static void loadPrimitiveGameThreadPart( pStaticMesh->SetRenderData(std::move(loadResult.RenderData)); } - const Material& material = - loadResult.materialIndex >= 0 && - loadResult.materialIndex < model.materials.size() - ? model.materials[loadResult.materialIndex] - : defaultMaterial; + const Material& material = loadResult.materialIndex != -1 + ? model.materials[loadResult.materialIndex] + : defaultMaterial; const MaterialPBRMetallicRoughness& pbr = material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 6dff2b49a..87be5dbcd 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -31,6 +31,77 @@ using namespace CesiumGltf; namespace { +struct ExtensionUnrealTexture { + static inline constexpr const char* TypeName = "ExtensionUnrealTexture"; + static inline constexpr const char* ExtensionName = "PRIVATE_unreal_texture"; + + CesiumUtility::IntrusivePointer< + CesiumTextureUtility::ReferenceCountedUnrealTexture> + pTexture = nullptr; +}; + +std::optional getPixelFormatForImageCesium( + const ImageCesium& imageCesium, + const std::optional overridePixelFormat) { + if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { + switch (imageCesium.compressedPixelFormat) { + case GpuCompressedPixelFormat::ETC1_RGB: + return EPixelFormat::PF_ETC1; + break; + case GpuCompressedPixelFormat::ETC2_RGBA: + return EPixelFormat::PF_ETC2_RGBA; + break; + case GpuCompressedPixelFormat::BC1_RGB: + return EPixelFormat::PF_DXT1; + break; + case GpuCompressedPixelFormat::BC3_RGBA: + return EPixelFormat::PF_DXT5; + break; + case GpuCompressedPixelFormat::BC4_R: + return EPixelFormat::PF_BC4; + break; + case GpuCompressedPixelFormat::BC5_RG: + return EPixelFormat::PF_BC5; + break; + case GpuCompressedPixelFormat::BC7_RGBA: + return EPixelFormat::PF_BC7; + break; + case GpuCompressedPixelFormat::ASTC_4x4_RGBA: + return EPixelFormat::PF_ASTC_4x4; + break; + case GpuCompressedPixelFormat::PVRTC2_4_RGBA: + return EPixelFormat::PF_PVRTC2; + break; + case GpuCompressedPixelFormat::ETC2_EAC_R11: + return EPixelFormat::PF_ETC2_R11_EAC; + break; + case GpuCompressedPixelFormat::ETC2_EAC_RG11: + return EPixelFormat::PF_ETC2_RG11_EAC; + break; + default: + // Unsupported compressed texture format. + return std::nullopt; + }; + } else if (overridePixelFormat) { + return *overridePixelFormat; + } else { + switch (imageCesium.channels) { + case 1: + return PF_R8; + break; + case 2: + return PF_R8G8; + break; + case 3: + case 4: + default: + return PF_R8G8B8A8; + }; + } + + return std::nullopt; +} + FTexture2DRHIRef createAsyncTextureAndWait( uint32 SizeX, uint32 SizeY, @@ -219,68 +290,6 @@ void ReferenceCountedUnrealTexture::setSharedImage( this->_pImageCesium = image; } -std::optional getPixelFormatForImageCesium( - const ImageCesium& imageCesium, - const std::optional overridePixelFormat) { - if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { - switch (imageCesium.compressedPixelFormat) { - case GpuCompressedPixelFormat::ETC1_RGB: - return EPixelFormat::PF_ETC1; - break; - case GpuCompressedPixelFormat::ETC2_RGBA: - return EPixelFormat::PF_ETC2_RGBA; - break; - case GpuCompressedPixelFormat::BC1_RGB: - return EPixelFormat::PF_DXT1; - break; - case GpuCompressedPixelFormat::BC3_RGBA: - return EPixelFormat::PF_DXT5; - break; - case GpuCompressedPixelFormat::BC4_R: - return EPixelFormat::PF_BC4; - break; - case GpuCompressedPixelFormat::BC5_RG: - return EPixelFormat::PF_BC5; - break; - case GpuCompressedPixelFormat::BC7_RGBA: - return EPixelFormat::PF_BC7; - break; - case GpuCompressedPixelFormat::ASTC_4x4_RGBA: - return EPixelFormat::PF_ASTC_4x4; - break; - case GpuCompressedPixelFormat::PVRTC2_4_RGBA: - return EPixelFormat::PF_PVRTC2; - break; - case GpuCompressedPixelFormat::ETC2_EAC_R11: - return EPixelFormat::PF_ETC2_R11_EAC; - break; - case GpuCompressedPixelFormat::ETC2_EAC_RG11: - return EPixelFormat::PF_ETC2_RG11_EAC; - break; - default: - // Unsupported compressed texture format. - return std::nullopt; - }; - } else if (overridePixelFormat) { - return *overridePixelFormat; - } else { - switch (imageCesium.channels) { - case 1: - return PF_R8; - break; - case 2: - return PF_R8G8; - break; - case 3: - case 4: - default: - return PF_R8G8B8A8; - }; - } - - return std::nullopt; -} - TUniquePtr createTextureResourceFromImageCesium( CesiumGltf::ImageCesium& imageCesium, TextureAddress addressX, @@ -345,6 +354,101 @@ TUniquePtr createTextureResourceFromImageCesium( } } +static std::mutex textureResourceMutex; + +struct ExtensionUnrealTextureResource { + static inline constexpr const char* TypeName = + "ExtensionUnrealTextureResource"; + static inline constexpr const char* ExtensionName = + "PRIVATE_unreal_texture_resource"; + + ExtensionUnrealTextureResource() {} + + TSharedPtr pTextureResource = nullptr; + + // If a preprocessing step is required (such as generating mipmaps), this + // future returns the preprocessed image. If no preprocessing is required, + // this just passes the image through. + std::optional> + preprocessFuture = std::nullopt; + + std::optional> + resourceLoadingFuture = std::nullopt; + + static TUniquePtr loadTextureResource( + CesiumGltf::ImageCesium& imageCesium, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat) { + std::optional optionalPixelFormat = + getPixelFormatForImageCesium(imageCesium, overridePixelFormat); + if (!optionalPixelFormat.has_value()) { + return nullptr; + } + + EPixelFormat pixelFormat = optionalPixelFormat.value(); + + std::lock_guard lock(textureResourceMutex); + + ExtensionUnrealTextureResource& extension = + imageCesium.addExtension(); + + // Already have a texture resource, just use that. + if (extension.pTextureResource != nullptr) { + return MakeUnique( + extension.pTextureResource.Get(), + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0); + } + + // Store the current size of the pixel data, because + // we're about to clear it but we still want to have + // an accurate estimation of the size of the image for + // caching purposes. + imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); + + TUniquePtr textureResource = + createTextureResourceFromImageCesium( + imageCesium, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + pixelFormat); + + check(textureResource != nullptr); + + extension.pTextureResource = + TSharedPtr(textureResource.Release()); + return MakeUnique( + extension.pTextureResource.Get(), + group, + imageCesium.width, + imageCesium.height, + pixelFormat, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0); + } +}; + std::optional getSourceIndexFromModelAndTexture( const CesiumGltf::Model& model, const CesiumGltf::Texture& texture) { @@ -395,9 +499,7 @@ std::optional getSourceIndexFromModelAndTexture( } } -CesiumAsync::Future> -loadTextureFromModelAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureFromModelAnyThreadPart( CesiumGltf::Model& model, CesiumGltf::Texture& texture, bool sRGB) { @@ -407,7 +509,7 @@ loadTextureFromModelAnyThreadPart( textureIndex = -1; } - std::lock_guard lock(ExtensionUnrealTextureResource::textureResourceMutex); + std::lock_guard lock(textureResourceMutex); ExtensionUnrealTexture& extension = texture.addExtension(); @@ -420,37 +522,29 @@ loadTextureFromModelAnyThreadPart( TUniquePtr pResult = MakeUnique(); pResult->pTexture = extension.pTexture; pResult->textureIndex = textureIndex; - return asyncSystem.createResolvedFuture>( - MoveTemp(pResult)); + return pResult; } std::optional optionalSourceIndex = getSourceIndexFromModelAndTexture(model, texture); if (!optionalSourceIndex.has_value()) { - return asyncSystem.createResolvedFuture>( - nullptr); + return nullptr; }; CesiumGltf::Image& image = model.images[*optionalSourceIndex]; const CesiumGltf::Sampler& sampler = model.getSafe(model.samplers, texture.sampler); - return loadTextureFromImageAndSamplerAnyThreadPart( - asyncSystem, - image.cesium, - sampler, - sRGB) - .thenImmediately([pExtension = &extension, - textureIndex](TUniquePtr pResult) { - std::lock_guard lock( - ExtensionUnrealTextureResource::textureResourceMutex); - if (pResult) { - pExtension->pTexture = pResult->pTexture; - pResult->textureIndex = textureIndex; - } - - return std::move(pResult); - }); + TUniquePtr result = + loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); + + if (result) { + std::lock_guard lock(textureResourceMutex); + extension.pTexture = result->pTexture; + result->textureIndex = textureIndex; + } + + return result; } TextureFilter getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { @@ -504,14 +598,11 @@ bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { } } -CesiumAsync::Future> -loadTextureFromImageAndSamplerAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( CesiumGltf::SharedAsset& image, const CesiumGltf::Sampler& sampler, bool sRGB) { return loadTextureAnyThreadPart( - asyncSystem, image, convertGltfWrapSToUnreal(sampler.wrapS), convertGltfWrapTToUnreal(sampler.wrapT), @@ -568,8 +659,7 @@ static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { return pTexture; } -CesiumAsync::Future> loadTextureAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureAnyThreadPart( CesiumGltf::SharedAsset& image, TextureAddress addressX, TextureAddress addressY, @@ -578,34 +668,29 @@ CesiumAsync::Future> loadTextureAnyThreadPart( TextureGroup group, bool sRGB, std::optional overridePixelFormat) { - return ExtensionUnrealTextureResource::loadTextureResource( - asyncSystem, - *image, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - overridePixelFormat) - .thenImmediately( - [addressX, addressY, filter, group, sRGB, pImage = &image]( - TUniquePtr textureResource) - -> TUniquePtr { - TUniquePtr pResult = - MakeUnique(); - pResult->pTexture = new ReferenceCountedUnrealTexture(); - - pResult->addressX = addressX; - pResult->addressY = addressY; - pResult->filter = filter; - pResult->group = group; - pResult->sRGB = sRGB; - - pResult->pTexture->setTextureResource(MoveTemp(textureResource)); - pResult->pTexture->setSharedImage(*pImage); - return std::move(pResult); - }); + TUniquePtr textureResource = + ExtensionUnrealTextureResource::loadTextureResource( + *image, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + overridePixelFormat); + + TUniquePtr pResult = MakeUnique(); + pResult->pTexture = new ReferenceCountedUnrealTexture(); + + pResult->addressX = addressX; + pResult->addressY = addressY; + pResult->filter = filter; + pResult->group = group; + pResult->sRGB = sRGB; + + pResult->pTexture->setTextureResource(MoveTemp(textureResource)); + pResult->pTexture->setSharedImage(image); + return pResult; } TUniquePtr loadTextureAnyThreadPartSync( @@ -736,22 +821,27 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } } -std::recursive_mutex ExtensionUnrealTextureResource::textureResourceMutex; - -void ExtensionUnrealTextureResource::preprocessImage( +CesiumAsync::SharedFuture createMipMapsForSampler( const CesiumAsync::AsyncSystem& asyncSystem, const CesiumGltf::Sampler& sampler, CesiumGltf::ImageCesium& image) { - std::lock_guard lock(textureResourceMutex); + std::unique_lock lock(textureResourceMutex); ExtensionUnrealTextureResource& extension = image.addExtension(); // Future already exists, we don't need to do anything else. - if (extension.preprocessFuture != nullptr) { - return; + if (extension.preprocessFuture.has_value()) { + return extension.preprocessFuture.value(); } + CesiumAsync::Promise promise = + asyncSystem.createPromise(); + + extension.preprocessFuture = promise.getFuture().share(); + + lock.unlock(); + // Generate mipmaps if needed. // An image needs mipmaps generated for it if: // 1. It is used by a Texture that has a Sampler with a mipmap filtering @@ -775,155 +865,40 @@ void ExtensionUnrealTextureResource::preprocessImage( } if (!needsMipmaps || image.pixelData.empty()) { - extension.preprocessFuture = - MakeShared>( - asyncSystem.createResolvedFuture(&image).share()); - return; + promise.resolve(&image); + return *extension.preprocessFuture; } // We need mipmaps generated. - extension.preprocessFuture = - MakeShared>( - asyncSystem - .runInWorkerThread([pImage = &image]() { - std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(*pImage); - if (errorMessage) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s"), - UTF8_TO_TCHAR(errorMessage->c_str())); - } - return pImage; - }) - .share()); + std::optional errorMessage = + CesiumGltfReader::GltfReader::generateMipMaps(image); + if (errorMessage) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s"), + UTF8_TO_TCHAR(errorMessage->c_str())); + } + promise.resolve(&image); + return *extension.preprocessFuture; } -CesiumAsync::Future> -ExtensionUnrealTextureResource::loadTextureResource( +CesiumAsync::SharedFuture createMipMapsForAllTextures( const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::ImageCesium& imageCesium, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat) { - std::optional optionalPixelFormat = - getPixelFormatForImageCesium(imageCesium, overridePixelFormat); - if (!optionalPixelFormat.has_value()) { - return asyncSystem - .createResolvedFuture>(nullptr); + CesiumGltf::Model& model) { + std::vector> futures; + for (const Texture& texture : model.textures) { + futures.push_back(createMipMapsForSampler( + asyncSystem, + model.getSafe(model.samplers, texture.sampler), + *model.images[texture.source].cesium)); } - EPixelFormat pixelFormat = optionalPixelFormat.value(); - - std::lock_guard lock(textureResourceMutex); - - ExtensionUnrealTextureResource& extension = - imageCesium.addExtension(); - - // Already have a texture resource, just use that. - if (extension.pTextureResource != nullptr) { - return asyncSystem - .createResolvedFuture>( - MakeUnique( - extension.pTextureResource.Get(), - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0)); - } - - if (extension.resourceLoadingFuture == nullptr) { - // It's possible we got to this path without going through any - // pre-processing steps (like from a test) - if (extension.preprocessFuture == nullptr) { - extension.preprocessFuture = - MakeShared>( - asyncSystem.createResolvedFuture(&imageCesium).share()); - } - // We need to start loading the texture resource - check(extension.preprocessFuture != nullptr); - - extension.resourceLoadingFuture = - MakeShared>( - extension.preprocessFuture - ->thenInWorkerThread( - [addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - pixelFormat, - sRGB](ImageCesium* imageCesium) - -> FCesiumTextureResourceBase* { - // Store the current size of the pixel data, because - // we're about to clear it but we still want to have - // an accurate estimation of the size of the image for - // caching purposes. - imageCesium->sizeBytes = - int64_t(imageCesium->pixelData.size()); - - TUniquePtr textureResource = - createTextureResourceFromImageCesium( - *imageCesium, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - pixelFormat); - - check(textureResource != nullptr); - - { - std::lock_guard lock(textureResourceMutex); - ExtensionUnrealTextureResource& extension = - imageCesium->addExtension< - ExtensionUnrealTextureResource>(); - - extension.pTextureResource = - TSharedPtr( - textureResource.Release()); - return extension.pTextureResource.Get(); - } - }) - .share()); - } - - return extension.resourceLoadingFuture->thenImmediately( - [group, - imageCesium, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable](FCesiumTextureResourceBase* existingResource) - -> TUniquePtr { - return MakeUnique( - existingResource, - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0); - }); + return asyncSystem.all(std::move(futures)) + .thenImmediately( + []([[maybe_unused]] std::vector& results) + -> void {}) + .share(); } } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index dd6e8d033..45917b333 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -100,9 +100,7 @@ struct LoadedTextureResult { * nullptr before the first call to `loadTextureFromModelAnyThreadPart` during * the glTF load process. */ -CesiumAsync::Future> -loadTextureFromModelAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureFromModelAnyThreadPart( CesiumGltf::Model& model, CesiumGltf::Texture& texture, bool sRGB); @@ -123,9 +121,7 @@ loadTextureFromModelAnyThreadPart( * parameter is not nullptr, the provided image's `pixelData` is not required * and can be empty. */ -CesiumAsync::Future> -loadTextureFromImageAndSamplerAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( CesiumGltf::SharedAsset& image, const CesiumGltf::Sampler& sampler, bool sRGB); @@ -174,8 +170,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPartSync( * and can be empty. * @return A future that resolves to the loaded texture. */ -CesiumAsync::Future> loadTextureAnyThreadPart( - const CesiumAsync::AsyncSystem& asyncSystem, +TUniquePtr loadTextureAnyThreadPart( CesiumGltf::SharedAsset& image, TextureAddress addressX, TextureAddress addressY, @@ -247,52 +242,8 @@ TextureAddress convertGltfWrapSToUnreal(int32_t wrapS); */ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT); -struct ExtensionUnrealTexture { - static inline constexpr const char* TypeName = "ExtensionUnrealTexture"; - static inline constexpr const char* ExtensionName = "PRIVATE_unreal_texture"; - - CesiumUtility::IntrusivePointer< - CesiumTextureUtility::ReferenceCountedUnrealTexture> - pTexture = nullptr; -}; - -struct ExtensionUnrealTextureResource { - static inline constexpr const char* TypeName = - "ExtensionUnrealTextureResource"; - static inline constexpr const char* ExtensionName = - "PRIVATE_unreal_texture_resource"; - - ExtensionUnrealTextureResource() {} - - TSharedPtr pTextureResource = nullptr; - - // If a preprocessing step is required (such as generating mipmaps), this - // future returns the preprocessed image. If no preprocessing is required, - // this just passes the image through. - TSharedPtr> - preprocessFuture = nullptr; - - TSharedPtr> - resourceLoadingFuture = nullptr; - - static void preprocessImage( - const CesiumAsync::AsyncSystem& asyncSystem, - const CesiumGltf::Sampler& sampler, - CesiumGltf::ImageCesium& image); - - static CesiumAsync::Future> - loadTextureResource( - const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::ImageCesium& imageCesium, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat); - - static std::recursive_mutex textureResourceMutex; -}; +CesiumAsync::SharedFuture createMipMapsForAllTextures( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::Model& model); } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 0670c26d7..683a9107c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -19,8 +19,6 @@ BEGIN_DEFINE_SPEC( std::vector originalPixels; std::vector originalMipPixels; SharedAsset imageCesium; -CesiumAsync::AsyncSystem asyncSystem = - CesiumAsync::AsyncSystem(std::make_shared()); void RunTests(); @@ -162,11 +160,9 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( - asyncSystem, imageCesium, sampler, - false) - .waitInMainThread(); + false); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); IntrusivePointer pRefCountedTexture = @@ -198,8 +194,7 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -242,22 +237,12 @@ void CesiumTextureUtilitySpec::RunTests() { texture2.sampler = 1; TUniquePtr pHalfLoaded1 = - loadTextureFromModelAnyThreadPart( - asyncSystem, - model, - model.textures[0], - true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, model.textures[0], true); TestNotNull("pHalfLoaded1", pHalfLoaded1.Get()); TestNotNull("pHalfLoaded1->pTexture", pHalfLoaded1->pTexture.get()); TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - asyncSystem, - model, - model.textures[1], - false) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, model.textures[1], false); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); @@ -309,8 +294,7 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -330,12 +314,7 @@ void CesiumTextureUtilitySpec::RunTests() { // previously-created texture. Model model2 = model; TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - asyncSystem, - model2, - model.textures[0], - true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model2, model.textures[0], true); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( @@ -364,8 +343,7 @@ void CesiumTextureUtilitySpec::RunTests() { texture.sampler = 0; TUniquePtr pHalfLoaded = - loadTextureFromModelAnyThreadPart(asyncSystem, model, texture, true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, texture, true); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get()); @@ -384,12 +362,7 @@ void CesiumTextureUtilitySpec::RunTests() { // This time there's no more pixel data, so it's necessary to use the // previously-created texture. TUniquePtr pHalfLoaded2 = - loadTextureFromModelAnyThreadPart( - asyncSystem, - model, - model.textures[0], - true) - .waitInMainThread(); + loadTextureFromModelAnyThreadPart(model, model.textures[0], true); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get()); TestNull( From b0f37ecdd819ab208da97cac861353c114f1b110 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 18 Sep 2024 14:07:12 -0400 Subject: [PATCH 17/68] Fix lifetime issues --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 41 ++++++------ .../Private/CesiumEncodedFeaturesMetadata.cpp | 31 ++++----- .../Private/CesiumEncodedMetadataUtility.cpp | 41 ++++++------ .../Private/CesiumGltfComponent.cpp | 65 +++++++++++-------- .../Private/CesiumGltfComponent.h | 11 +++- .../Private/CesiumTextureUtility.cpp | 61 +---------------- .../Private/CesiumTextureUtility.h | 36 ---------- .../CesiumRuntime/Private/CreateGltfOptions.h | 11 +++- .../Tests/CesiumTextureUtility.spec.cpp | 8 +-- extern/cesium-native | 2 +- 10 files changed, 123 insertions(+), 184 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index f85dd3f13..d9897c5c9 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -704,47 +704,48 @@ class UnrealResourcePreparer Cesium3DTilesSelection::TileLoadResult&& tileLoadResult, const glm::dmat4& transform, const std::any& rendererOptions) override { - CesiumGltf::Model* pModel = - std::get_if(&tileLoadResult.contentKind); - if (!pModel) + TUniquePtr options = + MakeUnique( + std::move(tileLoadResult)); + if (!options->getModel()) { return asyncSystem.createResolvedFuture( Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(tileLoadResult), + std::move(options->tileLoadResult), nullptr}); + } - CreateGltfOptions::CreateModelOptions options; - options.pModel = pModel; - options.alwaysIncludeTangents = this->_pActor->GetAlwaysIncludeTangents(); - options.createPhysicsMeshes = this->_pActor->GetCreatePhysicsMeshes(); + options->alwaysIncludeTangents = this->_pActor->GetAlwaysIncludeTangents(); + options->createPhysicsMeshes = this->_pActor->GetCreatePhysicsMeshes(); - options.ignoreKhrMaterialsUnlit = + options->ignoreKhrMaterialsUnlit = this->_pActor->GetIgnoreKhrMaterialsUnlit(); if (this->_pActor->_featuresMetadataDescription) { - options.pFeaturesMetadataDescription = + options->pFeaturesMetadataDescription = &(*this->_pActor->_featuresMetadataDescription); } else if (this->_pActor->_metadataDescription_DEPRECATED) { - options.pEncodedMetadataDescription_DEPRECATED = + options->pEncodedMetadataDescription_DEPRECATED = &(*this->_pActor->_metadataDescription_DEPRECATED); } const CesiumGeospatial::Ellipsoid& ellipsoid = tileLoadResult.ellipsoid; - CesiumAsync::Future> + CesiumAsync::Future< + TUniquePtr> pHalfFuture = UCesiumGltfComponent::CreateOffGameThread( asyncSystem, transform, - options, + std::move(options), ellipsoid); return MoveTemp(pHalfFuture) .thenImmediately( - [tileLoadResult]( - TUniquePtr&& pHalf) + [](TUniquePtr&& + pResult) -> Cesium3DTilesSelection::TileLoadResultAndRenderResources { return Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(tileLoadResult), - pHalf.Release()}; + std::move(pResult->TileLoadResult), + pResult->HalfConstructed.Release()}; }); } @@ -814,8 +815,10 @@ class UnrealResourcePreparer } } - auto texture = CesiumTextureUtility::loadTextureAnyThreadPartSync( - image, + CesiumGltf::SharedAsset imageAsset(image); + + auto texture = CesiumTextureUtility::loadTextureAnyThreadPart( + imageAsset, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, pOptions->filter, diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index 96e51a046..abb6a0799 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -141,9 +141,10 @@ std::optional encodeFeatureIdTexture( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumGltf::ImageCesium imageCopy(*pFeatureIdImage); + CesiumGltf::SharedAsset imageCopy = + CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pFeatureIdImage)); encodedFeatureIdTexture.pTexture = - MakeShared(std::move(*loadTextureAnyThreadPartSync( + MakeShared(std::move(*loadTextureAnyThreadPart( imageCopy, addressX, addressY, @@ -543,11 +544,11 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumGltf::ImageCesium image; - image.width = image.height = textureDimension; - image.bytesPerChannel = encodedFormat.bytesPerChannel; - image.channels = encodedFormat.channels; - image.pixelData.resize( + CesiumGltf::SharedAsset image; + image->width = image->height = textureDimension; + image->bytesPerChannel = encodedFormat.bytesPerChannel; + image->channels = encodedFormat.channels; + image->pixelData.resize( textureDimension * textureDimension * encodedFormat.bytesPerChannel * encodedFormat.channels); @@ -556,19 +557,18 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( CesiumEncodedMetadataParseColorFromString::encode( *pDescription, property, - gsl::span(image.pixelData), + gsl::span(image->pixelData), encodedFormat.bytesPerChannel * encodedFormat.channels); } else /* info.Conversion == ECesiumEncodedMetadataConversion::Coerce */ { CesiumEncodedMetadataCoerce::encode( *pDescription, property, - gsl::span(image.pixelData), + gsl::span(image->pixelData), encodedFormat.bytesPerChannel * encodedFormat.channels); } - CesiumGltf::ImageCesium imageCopy(image); - encodedProperty.pTexture = loadTextureAnyThreadPartSync( - imageCopy, + encodedProperty.pTexture = loadTextureAnyThreadPart( + image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -691,9 +691,10 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumGltf::ImageCesium imageCopy(*pImage); - encodedProperty.pTexture = MakeShared( - std::move(*loadTextureAnyThreadPartSync( + CesiumGltf::SharedAsset imageCopy = + CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); + encodedProperty.pTexture = + MakeShared(std::move(*loadTextureAnyThreadPart( imageCopy, addressX, addressY, diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index 6dfa1b750..7c36b7e70 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -205,18 +205,19 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumGltf::ImageCesium image; - image.bytesPerChannel = encodedFormat.bytesPerChannel; - image.channels = encodedFormat.channels; - image.compressedPixelFormat = CesiumGltf::GpuCompressedPixelFormat::NONE; - image.height = image.width = ceilSqrtFeatureCount; - image.pixelData.resize(size_t( - image.width * image.height * image.channels * image.bytesPerChannel)); + CesiumGltf::SharedAsset image; + image->bytesPerChannel = encodedFormat.bytesPerChannel; + image->channels = encodedFormat.channels; + image->compressedPixelFormat = CesiumGltf::GpuCompressedPixelFormat::NONE; + image->height = image->width = ceilSqrtFeatureCount; + image->pixelData.resize(size_t( + image->width * image->height * image->channels * + image->bytesPerChannel)); if (isArray) { switch (gpuType) { case ECesiumMetadataPackedGpuType_DEPRECATED::Uint8_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image.pixelData.data()); + uint8* pWritePos = reinterpret_cast(image->pixelData.data()); int64_t pixelSize = encodedFormat.channels * encodedFormat.bytesPerChannel; for (int64 i = 0; i < featureCount; ++i) { @@ -232,7 +233,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } break; case ECesiumMetadataPackedGpuType_DEPRECATED::Float_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image.pixelData.data()); + uint8* pWritePos = reinterpret_cast(image->pixelData.data()); int64_t pixelSize = encodedFormat.channels * encodedFormat.bytesPerChannel; for (int64 i = 0; i < featureCount; ++i) { @@ -256,7 +257,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } else { switch (gpuType) { case ECesiumMetadataPackedGpuType_DEPRECATED::Uint8_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image.pixelData.data()); + uint8* pWritePos = reinterpret_cast(image->pixelData.data()); for (int64 i = 0; i < featureCount; ++i) { *pWritePos = UCesiumPropertyTablePropertyBlueprintLibrary::GetByte( property, @@ -265,7 +266,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } break; case ECesiumMetadataPackedGpuType_DEPRECATED::Float_DEPRECATED: { - float* pWritePosF = reinterpret_cast(image.pixelData.data()); + float* pWritePosF = reinterpret_cast(image->pixelData.data()); for (int64 i = 0; i < featureCount; ++i) { *pWritePosF = UCesiumPropertyTablePropertyBlueprintLibrary::GetFloat( property, @@ -276,9 +277,8 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } - CesiumGltf::ImageCesium imageCopy(image); - encodedProperty.pTexture = loadTextureAnyThreadPartSync( - imageCopy, + encodedProperty.pTexture = loadTextureAnyThreadPart( + image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -410,9 +410,10 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureTextureProperty.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumGltf::ImageCesium imageCopy(*pImage); - encodedFeatureTextureProperty.pTexture = MakeShared( - std::move(*loadTextureAnyThreadPartSync( + CesiumGltf::SharedAsset imageCopy = + CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); + encodedFeatureTextureProperty.pTexture = + MakeShared(std::move(*loadTextureAnyThreadPart( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, @@ -515,9 +516,11 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureIdTexture.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumGltf::ImageCesium imageCopy(*pFeatureIdImage); + CesiumGltf::SharedAsset imageCopy = + CesiumGltf::SharedAsset( + CesiumGltf::ImageCesium(*pFeatureIdImage)); encodedFeatureIdTexture.pTexture = MakeShared( - std::move(*loadTextureAnyThreadPartSync( + std::move(*loadTextureAnyThreadPart( imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index c9a51e5da..bb91bd1c4 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1681,6 +1681,7 @@ static void loadPrimitive( LODResources.bHasReversedIndices = false; LODResources.bHasReversedDepthOnlyIndices = false; + primitiveResult.meshIndex = options.pMeshOptions->meshIndex; primitiveResult.primitiveIndex = options.primitiveIndex; primitiveResult.RenderData = std::move(RenderData); primitiveResult.pCollisionMesh = nullptr; @@ -1827,7 +1828,7 @@ static void loadPrimitive( static void loadMesh( std::optional& result, const glm::dmat4x4& transform, - const CreateMeshOptions& options, + CreateMeshOptions& options, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh) @@ -1996,7 +1997,7 @@ static void loadInstancingData( static void loadNode( std::vector& loadNodeResults, const glm::dmat4x4& transform, - const CreateNodeOptions& options, + CreateNodeOptions& options, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadNode) @@ -2139,7 +2140,7 @@ void applyGltfUpAxisTransform(const Model& model, glm::dmat4x4& rootTransform) { } // namespace static void -loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { +loadModelMetadata(LoadModelResult& result, CreateModelOptions& options) { Model& model = *options.pModel; ExtensionModelExtStructuralMetadata* pModelMetadata = @@ -2229,26 +2230,27 @@ loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { PRAGMA_ENABLE_DEPRECATION_WARNINGS } -static CesiumAsync::Future> +static CesiumAsync::Future< + TUniquePtr> loadModelAnyThreadPart( const CesiumAsync::AsyncSystem& asyncSystem, const glm::dmat4x4& transform, - const CreateModelOptions& options, + TUniquePtr options, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) - return createMipMapsForAllTextures(asyncSystem, *options.pModel) + return createMipMapsForAllTextures(asyncSystem, *options->pModel) .thenInWorkerThread( - [transform, - ellipsoid, - &options]() -> TUniquePtr { - auto pResult = MakeUnique(); + [transform, ellipsoid, options = std::move(options)]() + -> TUniquePtr { + auto pHalf = MakeUnique(); - Model& model = *options.pModel; - loadModelMetadata(pResult->loadModelResult, options); + loadModelMetadata(pHalf->loadModelResult, *options.Get()); glm::dmat4x4 rootTransform = transform; + Model& model = *options->pModel; + { rootTransform = CesiumGltfContent::GltfUtilities::applyRtcCenter( model, @@ -2261,11 +2263,11 @@ loadModelAnyThreadPart( const Scene& defaultScene = model.scenes[model.scene]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { - &options, - &pResult->loadModelResult, + options.Get(), + &pHalf->loadModelResult, &model.nodes[nodeId]}; loadNode( - pResult->loadModelResult.nodeResults, + pHalf->loadModelResult.nodeResults, rootTransform, nodeOptions, ellipsoid); @@ -2275,11 +2277,11 @@ loadModelAnyThreadPart( const Scene& defaultScene = model.scenes[0]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { - &options, - &pResult->loadModelResult, + options.Get(), + &pHalf->loadModelResult, &model.nodes[nodeId]}; loadNode( - pResult->loadModelResult.nodeResults, + pHalf->loadModelResult.nodeResults, rootTransform, nodeOptions, ellipsoid); @@ -2287,11 +2289,11 @@ loadModelAnyThreadPart( } else if (model.nodes.size() > 0) { // No scenes at all, use the first node as the root node. CreateNodeOptions nodeOptions = { - &options, - &pResult->loadModelResult, + options.Get(), + &pHalf->loadModelResult, &model.nodes[0]}; loadNode( - pResult->loadModelResult.nodeResults, + pHalf->loadModelResult.nodeResults, rootTransform, nodeOptions, ellipsoid); @@ -2299,11 +2301,11 @@ loadModelAnyThreadPart( // No nodes either, show all the meshes. for (int32_t i = 0; i < model.meshes.size(); i++) { CreateNodeOptions dummyNodeOptions = { - &options, - &pResult->loadModelResult, + options.Get(), + &pHalf->loadModelResult, nullptr}; LoadNodeResult& dummyNodeResult = - pResult->loadModelResult.nodeResults.emplace_back(); + pHalf->loadModelResult.nodeResults.emplace_back(); CreateMeshOptions meshOptions = { &dummyNodeOptions, &dummyNodeResult, @@ -2316,6 +2318,11 @@ loadModelAnyThreadPart( } } + auto pResult = + MakeUnique(); + pResult->HalfConstructed = std::move(pHalf); + pResult->TileLoadResult = std::move(options->tileLoadResult); + return MoveTemp(pResult); }); } @@ -3451,13 +3458,17 @@ static void loadPrimitiveGameThreadPart( } /*static*/ CesiumAsync::Future< - TUniquePtr> + TUniquePtr> UCesiumGltfComponent::CreateOffGameThread( const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, - const CreateModelOptions& Options, + TUniquePtr Options, const CesiumGeospatial::Ellipsoid& Ellipsoid) { - return loadModelAnyThreadPart(AsyncSystem, Transform, Options, Ellipsoid); + return loadModelAnyThreadPart( + AsyncSystem, + Transform, + std::move(Options), + Ellipsoid); } /*static*/ UCesiumGltfComponent* UCesiumGltfComponent::CreateOnGameThread( diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.h b/Source/CesiumRuntime/Private/CesiumGltfComponent.h index 8c6d11ae7..7f579f161 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.h @@ -65,10 +65,17 @@ class UCesiumGltfComponent : public USceneComponent { virtual ~HalfConstructed() = default; }; - static CesiumAsync::Future> CreateOffGameThread( + class CreateOffGameThreadResult { + public: + TUniquePtr HalfConstructed; + Cesium3DTilesSelection::TileLoadResult TileLoadResult; + }; + + static CesiumAsync::Future> + CreateOffGameThread( const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, - const CreateGltfOptions::CreateModelOptions& Options, + const TUniquePtr Options, const CesiumGeospatial::Ellipsoid& Ellipsoid = CesiumGeospatial::Ellipsoid::WGS84); diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 87be5dbcd..a8f2f8c3b 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -509,8 +509,6 @@ TUniquePtr loadTextureFromModelAnyThreadPart( textureIndex = -1; } - std::lock_guard lock(textureResourceMutex); - ExtensionUnrealTexture& extension = texture.addExtension(); if (extension.pTexture && (extension.pTexture->getUnrealTexture() || @@ -539,7 +537,6 @@ TUniquePtr loadTextureFromModelAnyThreadPart( loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); if (result) { - std::lock_guard lock(textureResourceMutex); extension.pTexture = result->pTexture; result->textureIndex = textureIndex; } @@ -614,22 +611,6 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( std::nullopt); } -TUniquePtr loadTextureFromImageAndSamplerAnyThreadPartSync( - CesiumGltf::ImageCesium& image, - const CesiumGltf::Sampler& sampler, - bool sRGB) { - return loadTextureAnyThreadPartSync( - image, - convertGltfWrapSToUnreal(sampler.wrapS), - convertGltfWrapTToUnreal(sampler.wrapT), - getTextureFilterFromSampler(sampler), - getUseMipmapsIfAvailableFromSampler(sampler), - // TODO: allow texture group to be configured on Cesium3DTileset. - TEXTUREGROUP_World, - sRGB, - std::nullopt); -} - static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { if (!pHalfLoadedTexture || !pHalfLoadedTexture->pTexture) { return nullptr; @@ -693,46 +674,6 @@ TUniquePtr loadTextureAnyThreadPart( return pResult; } -TUniquePtr loadTextureAnyThreadPartSync( - CesiumGltf::ImageCesium& image, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat) { - std::optional optionalPixelFormat = - getPixelFormatForImageCesium(image, overridePixelFormat); - if (!optionalPixelFormat.has_value()) { - return nullptr; - } - - EPixelFormat pixelFormat = optionalPixelFormat.value(); - TUniquePtr textureResource = - createTextureResourceFromImageCesium( - image, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - pixelFormat); - - TUniquePtr pResult = MakeUnique(); - pResult->pTexture = new ReferenceCountedUnrealTexture(); - - pResult->addressX = addressX; - pResult->addressY = addressY; - pResult->filter = filter; - pResult->group = group; - pResult->sRGB = sRGB; - - pResult->pTexture->setTextureResource(MoveTemp(textureResource)); - return pResult; -} - CesiumUtility::IntrusivePointer loadTextureGameThreadPart( CesiumGltf::Model& model, @@ -896,7 +837,7 @@ CesiumAsync::SharedFuture createMipMapsForAllTextures( return asyncSystem.all(std::move(futures)) .thenImmediately( - []([[maybe_unused]] std::vector& results) + []([[maybe_unused]] std::vector&& results) -> void {}) .share(); } diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 45917b333..22cf7b639 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -126,27 +126,6 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( const CesiumGltf::Sampler& sampler, bool sRGB); -/** - * Does the asynchronous part of renderer resource preparation for a glTF - * `Image` with the given `Sampler` settings. - * - * The `cesium.pixelData` will be removed from the image so that it can be - * passed to Unreal's renderer thread without copying it. - * - * @param image The glTF image for each to create a texture. - * @param sampler The sampler settings to use with the texture. - * @param sRGB True if the texture should be treated as sRGB; false if it should - * be treated as linear. - * @param pExistingImageResource An existing RHI texture resource that has been - * created for this image, or nullptr if one hasn't been created yet. When this - * parameter is not nullptr, the provided image's `pixelData` is not required - * and can be empty. - */ -TUniquePtr loadTextureFromImageAndSamplerAnyThreadPartSync( - CesiumGltf::ImageCesium& image, - const CesiumGltf::Sampler& sampler, - bool sRGB); - /** * @brief Does the asynchronous part of renderer resource preparation for * this image. Should be called in a background thread. @@ -180,21 +159,6 @@ TUniquePtr loadTextureAnyThreadPart( bool sRGB, std::optional overridePixelFormat); -/** - * @brief Equivalent to \link{loadTextureAnyThreadPart}, but returns the texture - * itself rather than a future. Intended to be used for loading raster overlay - * textures. - */ -TUniquePtr loadTextureAnyThreadPartSync( - CesiumGltf::ImageCesium& image, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat); - /** * @brief Does the main-thread part of render resource preparation for this * image and queues up any required render-thread tasks to finish preparing the diff --git a/Source/CesiumRuntime/Private/CreateGltfOptions.h b/Source/CesiumRuntime/Private/CreateGltfOptions.h index 6019a92bf..1ceb14c45 100644 --- a/Source/CesiumRuntime/Private/CreateGltfOptions.h +++ b/Source/CesiumRuntime/Private/CreateGltfOptions.h @@ -15,15 +15,24 @@ struct CreateModelOptions { /** * A pointer to the glTF model. */ - CesiumGltf::Model* pModel = nullptr; const FCesiumFeaturesMetadataDescription* pFeaturesMetadataDescription = nullptr; + CesiumGltf::Model* pModel = nullptr; + PRAGMA_DISABLE_DEPRECATION_WARNINGS const FMetadataDescription* pEncodedMetadataDescription_DEPRECATED = nullptr; PRAGMA_ENABLE_DEPRECATION_WARNINGS bool alwaysIncludeTangents = false; bool createPhysicsMeshes = true; bool ignoreKhrMaterialsUnlit = false; + + Cesium3DTilesSelection::TileLoadResult tileLoadResult; + +public: + CreateModelOptions(Cesium3DTilesSelection::TileLoadResult&& tileLoadResult_) + : tileLoadResult(std::move(tileLoadResult_)) { + pModel = std::get_if(&this->tileLoadResult.contentKind); + } }; struct CreateNodeOptions { diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 683a9107c..515bdc0fe 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -104,8 +104,8 @@ void CesiumTextureUtilitySpec::Define() { void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium non-sRGB", [this]() { - TUniquePtr pHalfLoaded = loadTextureAnyThreadPartSync( - *imageCesium, + TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( + imageCesium, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, TextureFilter::TF_Bilinear, @@ -128,8 +128,8 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("ImageCesium sRGB", [this]() { - TUniquePtr pHalfLoaded = loadTextureAnyThreadPartSync( - *imageCesium, + TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( + imageCesium, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, TextureFilter::TF_Trilinear, diff --git a/extern/cesium-native b/extern/cesium-native index 8e664a18b..5ae553947 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 8e664a18b34128cad0e55aafcc7289aa03fc95f9 +Subproject commit 5ae5539478e20d4a6f48b03db0b971c8b4dba1e9 From 6b43702aadf817d9249d49446aa6582bd047c92a Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 18 Sep 2024 16:20:42 -0400 Subject: [PATCH 18/68] Log asset stats --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 13 ++++++++++++- Source/CesiumRuntime/Public/Cesium3DTileset.h | 7 +++++++ extern/cesium-native | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index d9897c5c9..ce61a882e 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -707,7 +707,7 @@ class UnrealResourcePreparer TUniquePtr options = MakeUnique( std::move(tileLoadResult)); - if (!options->getModel()) { + if (!options->pModel) { return asyncSystem.createResolvedFuture( Cesium3DTilesSelection::TileLoadResultAndRenderResources{ std::move(options->tileLoadResult), @@ -1901,6 +1901,17 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } } + if (this->LogAssetStats && this->_pTileset) { + const CesiumGltf::SingleAssetDepot* imageDepot = + this->_pTileset->getSharedAssetDepot().getImageDepot(); + UE_LOG( + LogCesium, + Display, + TEXT("Images depot: %d distinct assets, %d total usages"), + imageDepot->getDistinctCount(), + imageDepot->getUsageCount()); + } + if (!this->LogSelectionStats) { return; } diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 6edbd5b94..3fdc7b128 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -598,6 +598,13 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UPROPERTY(EditAnywhere, Category = "Cesium|Debug") bool LogSelectionStats = false; + /** + * If true, logs stats on the assets in this tileset's asset depot to the + * Output Log. + */ + UPROPERTY(EditAnywhere, Category = "Cesium|Debug") + bool LogAssetStats = false; + /** * If true, draws debug text above each tile being rendered with information * about that tile. diff --git a/extern/cesium-native b/extern/cesium-native index 5ae553947..4851ed2dc 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 5ae5539478e20d4a6f48b03db0b971c8b4dba1e9 +Subproject commit 4851ed2dc2b28365ebe4022a18f3275cf852c552 From 6fcce71a32dda2a14fc27e923ee1d338854e1f41 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 19 Sep 2024 15:57:59 -0400 Subject: [PATCH 19/68] Fix mipmap generation --- .../Private/CesiumTextureUtility.cpp | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index a8f2f8c3b..f1a13b663 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -762,7 +762,8 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } } -CesiumAsync::SharedFuture createMipMapsForSampler( +std::optional> +createMipMapsForSampler( const CesiumAsync::AsyncSystem& asyncSystem, const CesiumGltf::Sampler& sampler, CesiumGltf::ImageCesium& image) { @@ -776,13 +777,6 @@ CesiumAsync::SharedFuture createMipMapsForSampler( return extension.preprocessFuture.value(); } - CesiumAsync::Promise promise = - asyncSystem.createPromise(); - - extension.preprocessFuture = promise.getFuture().share(); - - lock.unlock(); - // Generate mipmaps if needed. // An image needs mipmaps generated for it if: // 1. It is used by a Texture that has a Sampler with a mipmap filtering @@ -806,10 +800,19 @@ CesiumAsync::SharedFuture createMipMapsForSampler( } if (!needsMipmaps || image.pixelData.empty()) { - promise.resolve(&image); - return *extension.preprocessFuture; + // If we don't need mipmaps, we don't want to create a future for them. + // This allows a future sampler using this image that does need mipmaps to + // generate them. + return std::nullopt; } + CesiumAsync::Promise promise = + asyncSystem.createPromise(); + + extension.preprocessFuture = promise.getFuture().share(); + + lock.unlock(); + // We need mipmaps generated. std::optional errorMessage = CesiumGltfReader::GltfReader::generateMipMaps(image); @@ -829,10 +832,16 @@ CesiumAsync::SharedFuture createMipMapsForAllTextures( CesiumGltf::Model& model) { std::vector> futures; for (const Texture& texture : model.textures) { - futures.push_back(createMipMapsForSampler( - asyncSystem, - model.getSafe(model.samplers, texture.sampler), - *model.images[texture.source].cesium)); + const CesiumGltf::Sampler& sampler = + model.getSafe(model.samplers, texture.sampler); + std::optional> + optionalFuture = createMipMapsForSampler( + asyncSystem, + sampler, + *model.images[texture.source].cesium); + if (optionalFuture.has_value()) { + futures.push_back(optionalFuture.value()); + } } return asyncSystem.all(std::move(futures)) From ac6dbbbe36098efe78c28e3b70f5033609c7761b Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 19 Sep 2024 16:12:39 -0400 Subject: [PATCH 20/68] Hopefully fix CI errors --- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 1 - Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 8cc0719e7..fd493e310 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -9,7 +9,6 @@ #include "Engine/World.h" #include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" -#include "Tests/AutomationEditorCommon.h" #include #define TEST_SCREEN_WIDTH 1280 diff --git a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp index 54247d571..6760b70e4 100644 --- a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp +++ b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp @@ -9,6 +9,7 @@ #include "CesiumAsync/IAssetResponse.h" #include "CesiumCommon.h" #include "CesiumRuntime.h" +#include "Containers/UnrealString.h" #include "HttpManager.h" #include "HttpModule.h" #include "Interfaces/IHttpRequest.h" From 1ef546f08c0ca7f9183a808320c4a33653c435f4 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 19 Sep 2024 16:15:00 -0400 Subject: [PATCH 21/68] Add Map header --- Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp | 1 - Source/CesiumRuntime/Public/UnrealAssetAccessor.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp index 6760b70e4..54247d571 100644 --- a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp +++ b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp @@ -9,7 +9,6 @@ #include "CesiumAsync/IAssetResponse.h" #include "CesiumCommon.h" #include "CesiumRuntime.h" -#include "Containers/UnrealString.h" #include "HttpManager.h" #include "HttpModule.h" #include "Interfaces/IHttpRequest.h" diff --git a/Source/CesiumRuntime/Public/UnrealAssetAccessor.h b/Source/CesiumRuntime/Public/UnrealAssetAccessor.h index cb06d0892..0a76c3fe6 100644 --- a/Source/CesiumRuntime/Public/UnrealAssetAccessor.h +++ b/Source/CesiumRuntime/Public/UnrealAssetAccessor.h @@ -4,6 +4,7 @@ #include "CesiumAsync/AsyncSystem.h" #include "CesiumAsync/IAssetAccessor.h" +#include "Containers/Map.h" #include "Containers/UnrealString.h" #include "HAL/Platform.h" #include From 4da4e838b65d15fad2f267e61548e54fd1d41dcf Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 19 Sep 2024 17:02:59 -0400 Subject: [PATCH 22/68] Add automation test headers --- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index fd493e310..112a65b69 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -9,6 +9,8 @@ #include "Engine/World.h" #include "GlobeAwareDefaultPawn.h" #include "Misc/AutomationTest.h" +#include "Tests/AutomationCommon.h" +#include "Tests/AutomationTestSettings.h" #include #define TEST_SCREEN_WIDTH 1280 From 0637307922421742bdc5fbd7388e0ea34e12f7c5 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 19 Sep 2024 17:07:31 -0400 Subject: [PATCH 23/68] Add WITH_EDITOR block --- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 112a65b69..d40159b08 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -1,5 +1,7 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors +#if WITH_EDITOR + #include "Cesium3DTileset.h" #include "CesiumGlobeAnchorComponent.h" #include "CesiumLoadTestCore.h" @@ -93,3 +95,5 @@ bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { } } // namespace Cesium + +#endif From 9530e72dbf78442fa172989834e85db6cdc9906d Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 25 Sep 2024 17:18:56 -0400 Subject: [PATCH 24/68] Update based on review --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 116 ++++++++++-------- .../Private/CesiumGltfComponent.cpp | 43 +++---- .../Private/CesiumGltfComponent.h | 5 +- .../Private/CesiumTextureResource.cpp | 2 +- .../Private/CesiumTextureResource.h | 2 +- .../Private/CesiumTextureUtility.cpp | 9 +- .../CesiumRuntime/Private/CreateGltfOptions.h | 14 ++- .../Private/UnrealAssetAccessor.cpp | 4 - extern/cesium-native | 2 +- 9 files changed, 103 insertions(+), 94 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index ce61a882e..7afcfa0ec 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -704,34 +704,31 @@ class UnrealResourcePreparer Cesium3DTilesSelection::TileLoadResult&& tileLoadResult, const glm::dmat4& transform, const std::any& rendererOptions) override { - TUniquePtr options = - MakeUnique( - std::move(tileLoadResult)); - if (!options->pModel) { + CreateGltfOptions::CreateModelOptions options(std::move(tileLoadResult)); + if (!options.pModel) { return asyncSystem.createResolvedFuture( Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(options->tileLoadResult), + std::move(options.tileLoadResult), nullptr}); } - options->alwaysIncludeTangents = this->_pActor->GetAlwaysIncludeTangents(); - options->createPhysicsMeshes = this->_pActor->GetCreatePhysicsMeshes(); + options.alwaysIncludeTangents = this->_pActor->GetAlwaysIncludeTangents(); + options.createPhysicsMeshes = this->_pActor->GetCreatePhysicsMeshes(); - options->ignoreKhrMaterialsUnlit = + options.ignoreKhrMaterialsUnlit = this->_pActor->GetIgnoreKhrMaterialsUnlit(); if (this->_pActor->_featuresMetadataDescription) { - options->pFeaturesMetadataDescription = + options.pFeaturesMetadataDescription = &(*this->_pActor->_featuresMetadataDescription); } else if (this->_pActor->_metadataDescription_DEPRECATED) { - options->pEncodedMetadataDescription_DEPRECATED = + options.pEncodedMetadataDescription_DEPRECATED = &(*this->_pActor->_metadataDescription_DEPRECATED); } const CesiumGeospatial::Ellipsoid& ellipsoid = tileLoadResult.ellipsoid; - CesiumAsync::Future< - TUniquePtr> + CesiumAsync::Future pHalfFuture = UCesiumGltfComponent::CreateOffGameThread( asyncSystem, transform, @@ -740,12 +737,11 @@ class UnrealResourcePreparer return MoveTemp(pHalfFuture) .thenImmediately( - [](TUniquePtr&& - pResult) + [](UCesiumGltfComponent::CreateOffGameThreadResult&& result) -> Cesium3DTilesSelection::TileLoadResultAndRenderResources { return Cesium3DTilesSelection::TileLoadResultAndRenderResources{ - std::move(pResult->TileLoadResult), - pResult->HalfConstructed.Release()}; + std::move(result.TileLoadResult), + result.HalfConstructed.Release()}; }); } @@ -815,7 +811,8 @@ class UnrealResourcePreparer } } - CesiumGltf::SharedAsset imageAsset(image); + CesiumGltf::SharedAsset imageAsset( + std::move(image)); auto texture = CesiumTextureUtility::loadTextureAnyThreadPart( imageAsset, @@ -1876,10 +1873,11 @@ void ACesium3DTileset::updateLastViewUpdateResultState( const UWorld* World = GetWorld(); check(World); - const TSoftObjectPtr Georeference = GetGeoreference(); + const TSoftObjectPtr Georeference = + ResolveGeoreference(); check(Georeference); - for (auto& tile : result.tilesToRenderThisFrame) { + for (Cesium3DTilesSelection::Tile* tile : result.tilesToRenderThisFrame) { CesiumGeometry::OrientedBoundingBox obb = Cesium3DTilesSelection::getOrientedBoundingBoxFromBoundingVolume( @@ -1892,27 +1890,17 @@ void ACesium3DTileset::updateLastViewUpdateResultState( FString text = FString::Printf( TEXT("ID %s (%p)"), - Cesium3DTilesSelection::TileIdUtilities::createTileIdString( - tile->getTileID()) - .c_str(), + UTF8_TO_TCHAR( + Cesium3DTilesSelection::TileIdUtilities::createTileIdString( + tile->getTileID()) + .c_str()), tile); DrawDebugString(World, unrealCenter, text, nullptr, FColor::Red, 0, true); } } - if (this->LogAssetStats && this->_pTileset) { - const CesiumGltf::SingleAssetDepot* imageDepot = - this->_pTileset->getSharedAssetDepot().getImageDepot(); - UE_LOG( - LogCesium, - Display, - TEXT("Images depot: %d distinct assets, %d total usages"), - imageDepot->getDistinctCount(), - imageDepot->getUsageCount()); - } - - if (!this->LogSelectionStats) { + if (!this->LogSelectionStats && !this->LogAssetStats) { return; } @@ -1943,24 +1931,44 @@ void ACesium3DTileset::updateLastViewUpdateResultState( result.tilesWaitingForOcclusionResults; this->_lastMaxDepthVisited = result.maxDepthVisited; - UE_LOG( - LogCesium, - Display, - TEXT( - "%s: %d ms, Visited %d, Culled Visited %d, Rendered %d, Culled %d, Occluded %d, Waiting For Occlusion Results %d, Max Depth Visited: %d, Loading-Worker %d, Loading-Main %d, Loaded tiles %g%%"), - *this->GetName(), - (std::chrono::high_resolution_clock::now() - this->_startTime).count() / - 1000000, - result.tilesVisited, - result.culledTilesVisited, - result.tilesToRenderThisFrame.size(), - result.tilesCulled, - result.tilesOccluded, - result.tilesWaitingForOcclusionResults, - result.maxDepthVisited, - result.workerThreadTileLoadQueueLength, - result.mainThreadTileLoadQueueLength, - this->LoadProgress); + if (this->LogSelectionStats) { + UE_LOG( + LogCesium, + Display, + TEXT( + "%s: %d ms, Visited %d, Culled Visited %d, Rendered %d, Culled %d, Occluded %d, Waiting For Occlusion Results %d, Max Depth Visited: %d, Loading-Worker %d, Loading-Main %d, Loaded tiles %g%%"), + *this->GetName(), + (std::chrono::high_resolution_clock::now() - this->_startTime) + .count() / + 1000000, + result.tilesVisited, + result.culledTilesVisited, + result.tilesToRenderThisFrame.size(), + result.tilesCulled, + result.tilesOccluded, + result.tilesWaitingForOcclusionResults, + result.maxDepthVisited, + result.workerThreadTileLoadQueueLength, + result.mainThreadTileLoadQueueLength, + this->LoadProgress); + } + + if (this->LogAssetStats && this->_pTileset) { + const CesiumGltf::SingleAssetDepot* imageDepot = + this->_pTileset->getSharedAssetDepot().getImageDepot(); + float averageAge; + size_t deletionCount; + imageDepot->getDeletionStats(averageAge, deletionCount); + UE_LOG( + LogCesium, + Display, + TEXT( + "Images depot: %d distinct assets, %d total usages, %d assets pending deletion, %f average age"), + imageDepot->getDistinctCount(), + imageDepot->getUsageCount(), + deletionCount, + averageAge); + } } } @@ -2134,6 +2142,10 @@ void ACesium3DTileset::Tick(float DeltaTime) { } this->UpdateLoadStatus(); + + if (this->_pTileset) { + this->_pTileset->getSharedAssetDepot().deletionTick(); + } } void ACesium3DTileset::EndPlay(const EEndPlayReason::Type EndPlayReason) { diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index bb91bd1c4..8517741f1 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1286,7 +1286,7 @@ static void loadPrimitive( glm::dvec3 minPosition{std::numeric_limits::max()}; glm::dvec3 maxPosition{std::numeric_limits::lowest()}; if (min.size() != 3 || max.size() != 3) { - for (int32_t i = 0; i < positionView.size(); ++i) { + for (int64_t i = 0; i < positionView.size(); ++i) { minPosition.x = glm::min(minPosition.x, positionView[i].X); minPosition.y = glm::min(minPosition.y, positionView[i].Y); minPosition.z = glm::min(minPosition.z, positionView[i].Z); @@ -1838,7 +1838,7 @@ static void loadMesh( result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); - for (int32_t i = 0; i < mesh.primitives.size(); i++) { + for (size_t i = 0; i < mesh.primitives.size(); i++) { CreatePrimitiveOptions primitiveOptions = {&options, &*result, i}; auto& primitiveResult = result->primitiveResults.emplace_back(); loadPrimitive(primitiveResult, transform, primitiveOptions, ellipsoid); @@ -2140,7 +2140,7 @@ void applyGltfUpAxisTransform(const Model& model, glm::dmat4x4& rootTransform) { } // namespace static void -loadModelMetadata(LoadModelResult& result, CreateModelOptions& options) { +loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { Model& model = *options.pModel; ExtensionModelExtStructuralMetadata* pModelMetadata = @@ -2230,26 +2230,25 @@ loadModelMetadata(LoadModelResult& result, CreateModelOptions& options) { PRAGMA_ENABLE_DEPRECATION_WARNINGS } -static CesiumAsync::Future< - TUniquePtr> +static CesiumAsync::Future loadModelAnyThreadPart( const CesiumAsync::AsyncSystem& asyncSystem, const glm::dmat4x4& transform, - TUniquePtr options, + CreateModelOptions&& options, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) - return createMipMapsForAllTextures(asyncSystem, *options->pModel) + return createMipMapsForAllTextures(asyncSystem, *options.pModel) .thenInWorkerThread( [transform, ellipsoid, options = std::move(options)]() - -> TUniquePtr { + -> UCesiumGltfComponent::CreateOffGameThreadResult { auto pHalf = MakeUnique(); - loadModelMetadata(pHalf->loadModelResult, *options.Get()); + loadModelMetadata(pHalf->loadModelResult, options); glm::dmat4x4 rootTransform = transform; - Model& model = *options->pModel; + Model& model = *options.pModel; { rootTransform = CesiumGltfContent::GltfUtilities::applyRtcCenter( @@ -2263,7 +2262,7 @@ loadModelAnyThreadPart( const Scene& defaultScene = model.scenes[model.scene]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { - options.Get(), + &options, &pHalf->loadModelResult, &model.nodes[nodeId]}; loadNode( @@ -2277,7 +2276,7 @@ loadModelAnyThreadPart( const Scene& defaultScene = model.scenes[0]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { - options.Get(), + &options, &pHalf->loadModelResult, &model.nodes[nodeId]}; loadNode( @@ -2289,7 +2288,7 @@ loadModelAnyThreadPart( } else if (model.nodes.size() > 0) { // No scenes at all, use the first node as the root node. CreateNodeOptions nodeOptions = { - options.Get(), + &options, &pHalf->loadModelResult, &model.nodes[0]}; loadNode( @@ -2299,9 +2298,9 @@ loadModelAnyThreadPart( ellipsoid); } else if (model.meshes.size() > 0) { // No nodes either, show all the meshes. - for (int32_t i = 0; i < model.meshes.size(); i++) { + for (size_t i = 0; i < model.meshes.size(); i++) { CreateNodeOptions dummyNodeOptions = { - options.Get(), + &options, &pHalf->loadModelResult, nullptr}; LoadNodeResult& dummyNodeResult = @@ -2318,12 +2317,11 @@ loadModelAnyThreadPart( } } - auto pResult = - MakeUnique(); - pResult->HalfConstructed = std::move(pHalf); - pResult->TileLoadResult = std::move(options->tileLoadResult); + UCesiumGltfComponent::CreateOffGameThreadResult result; + result.HalfConstructed = std::move(pHalf); + result.TileLoadResult = std::move(options.tileLoadResult); - return MoveTemp(pResult); + return result; }); } @@ -3457,12 +3455,11 @@ static void loadPrimitiveGameThreadPart( } } -/*static*/ CesiumAsync::Future< - TUniquePtr> +/*static*/ CesiumAsync::Future UCesiumGltfComponent::CreateOffGameThread( const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, - TUniquePtr Options, + CreateModelOptions&& Options, const CesiumGeospatial::Ellipsoid& Ellipsoid) { return loadModelAnyThreadPart( AsyncSystem, diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.h b/Source/CesiumRuntime/Private/CesiumGltfComponent.h index 7f579f161..a55d8b57c 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.h @@ -71,11 +71,10 @@ class UCesiumGltfComponent : public USceneComponent { Cesium3DTilesSelection::TileLoadResult TileLoadResult; }; - static CesiumAsync::Future> - CreateOffGameThread( + static CesiumAsync::Future CreateOffGameThread( const CesiumAsync::AsyncSystem& AsyncSystem, const glm::dmat4x4& Transform, - const TUniquePtr Options, + CreateGltfOptions::CreateModelOptions&& Options, const CesiumGeospatial::Ellipsoid& Ellipsoid = CesiumGeospatial::Ellipsoid::WGS84); diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index cbfe302a5..1bbeb5a59 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -285,7 +285,7 @@ FTextureRHIRef FCesiumUseExistingTextureResource::InitializeTextureRHI() { } FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium&& image, TextureGroup textureGroup, uint32 width, uint32 height, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index 91a47b5ee..83b75d503 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -111,7 +111,7 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResourceBase { class FCesiumCreateNewTextureResource : public FCesiumTextureResourceBase { public: FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium&& image, TextureGroup textureGroup, uint32 width, uint32 height, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index f1a13b663..b7aa9e52f 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -340,7 +340,7 @@ TUniquePtr createTextureResourceFromImageCesium( } return MakeUnique( - imageCesium, + std::move(imageCesium), group, imageCesium.width, imageCesium.height, @@ -372,9 +372,6 @@ struct ExtensionUnrealTextureResource { std::optional> preprocessFuture = std::nullopt; - std::optional> - resourceLoadingFuture = std::nullopt; - static TUniquePtr loadTextureResource( CesiumGltf::ImageCesium& imageCesium, TextureAddress addressX, @@ -846,8 +843,8 @@ CesiumAsync::SharedFuture createMipMapsForAllTextures( return asyncSystem.all(std::move(futures)) .thenImmediately( - []([[maybe_unused]] std::vector&& results) - -> void {}) + []([[maybe_unused]] std::vector< + CesiumGltf::ImageCesium*>&& /*results*/) -> void {}) .share(); } diff --git a/Source/CesiumRuntime/Private/CreateGltfOptions.h b/Source/CesiumRuntime/Private/CreateGltfOptions.h index 1ceb14c45..57923497a 100644 --- a/Source/CesiumRuntime/Private/CreateGltfOptions.h +++ b/Source/CesiumRuntime/Private/CreateGltfOptions.h @@ -12,9 +12,6 @@ // TODO: internal documentation namespace CreateGltfOptions { struct CreateModelOptions { - /** - * A pointer to the glTF model. - */ const FCesiumFeaturesMetadataDescription* pFeaturesMetadataDescription = nullptr; CesiumGltf::Model* pModel = nullptr; @@ -33,6 +30,17 @@ struct CreateModelOptions { : tileLoadResult(std::move(tileLoadResult_)) { pModel = std::get_if(&this->tileLoadResult.contentKind); } + + CreateModelOptions(CreateModelOptions&& other) + : pFeaturesMetadataDescription(other.pFeaturesMetadataDescription), + pEncodedMetadataDescription_DEPRECATED( + other.pEncodedMetadataDescription_DEPRECATED), + alwaysIncludeTangents(other.alwaysIncludeTangents), + createPhysicsMeshes(other.createPhysicsMeshes), + ignoreKhrMaterialsUnlit(other.ignoreKhrMaterialsUnlit), + tileLoadResult(std::move(other.tileLoadResult)) { + pModel = std::get_if(&this->tileLoadResult.contentKind); + } }; struct CreateNodeOptions { diff --git a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp index 54247d571..d26da1e2b 100644 --- a/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp +++ b/Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp @@ -152,10 +152,6 @@ void rejectPromiseOnUnsuccessfulConnection( const CesiumAsync::Promise>& promise, FHttpRequestPtr pRequest) { -#ifndef ENGINE_VERSION_5_4_OR_HIGHER -#define ENGINE_VERSION_5_4_OR_HIGHER 0 -#endif - #if ENGINE_VERSION_5_4_OR_HIGHER if (pRequest->GetStatus() == EHttpRequestStatus::Failed) { EHttpFailureReason failureReason = pRequest->GetFailureReason(); diff --git a/extern/cesium-native b/extern/cesium-native index 4851ed2dc..31d0048b5 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 4851ed2dc2b28365ebe4022a18f3275cf852c552 +Subproject commit 31d0048b58044546182d2bcd9d72106bb5ca8991 From eefb1138533e0476c9bfb8a2557923359066667f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 07:22:35 +1000 Subject: [PATCH 25/68] WIP texture loading reorg. Mostly just broken so far. --- .../Private/CesiumGltfTextures.cpp | 267 ++++++ .../Private/CesiumGltfTextures.h | 24 + .../Private/CesiumTextureUtility.cpp | 767 +++++++++--------- .../Private/CesiumTextureUtility.h | 18 +- extern/cesium-native | 2 +- 5 files changed, 702 insertions(+), 376 deletions(-) create mode 100644 Source/CesiumRuntime/Private/CesiumGltfTextures.cpp create mode 100644 Source/CesiumRuntime/Private/CesiumGltfTextures.h diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp new file mode 100644 index 000000000..a6fff0bbb --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -0,0 +1,267 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#include "CesiumGltfTextures.h" +#include "CesiumRuntime.h" +#include "CesiumTextureResource.h" +#include "CesiumTextureUtility.h" +#include +#include +#include +#include + +using namespace CesiumAsync; +using namespace CesiumGltf; + +namespace { + +// Determines if a glTF primitive is usable for our purposes. +bool isValidPrimitive(Model& gltf, MeshPrimitive& primitive); + +// Determines if an Accessor's componentType is valid for an index buffer. +bool isSupportedIndexComponentType(int32_t componentType); + +// Determines if the given Primitive mode is one that we support. +bool isSupportedPrimitiveMode(int32_t primitiveMode); + +// Determines if the given texture uses mipmaps. +bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture); + +// Creates a single texture in the load thread. +SharedFuture createTextureInLoadThread( + const AsyncSystem& asyncSystem, + Model& gltf, + TextureInfo& textureInfo, + bool sRGB, + const std::vector& imageNeedsMipmaps); + +} // namespace + +/*static*/ CesiumAsync::Future CesiumGltfTextures::createInLoadThread( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::Model& model) { + // This array is parallel to model.images and indicates whether each image + // requires mipmaps. An image requires mipmaps if any of its textures have a + // sampler that will use them. + std::vector imageNeedsMipmaps(model.images.size(), false); + for (const Texture& texture : model.textures) { + int32_t imageIndex = texture.source; + if (imageIndex < 0 || imageIndex >= model.images.size()) { + continue; + } + + if (!imageNeedsMipmaps[imageIndex]) { + imageNeedsMipmaps[imageIndex] = doesTextureUseMipmaps(model, texture); + } + } + + std::vector> futures; + + model.forEachPrimitiveInScene( + -1, + [&imageNeedsMipmaps, &asyncSystem, &futures]( + Model& gltf, + Node& node, + Mesh& mesh, + MeshPrimitive& primitive, + const glm::dmat4& transform) { + if (!isValidPrimitive(gltf, primitive)) { + return; + } + + Material* pMaterial = + Model::getSafe(&gltf.materials, primitive.material); + if (!pMaterial) { + // A primitive using the default material will not have any textures. + return; + } + + if (pMaterial->pbrMetallicRoughness) { + if (pMaterial->pbrMetallicRoughness->baseColorTexture) { + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + *pMaterial->pbrMetallicRoughness->baseColorTexture, + true, + imageNeedsMipmaps)); + } + if (pMaterial->pbrMetallicRoughness->metallicRoughnessTexture) { + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + *pMaterial->pbrMetallicRoughness->metallicRoughnessTexture, + false, + imageNeedsMipmaps)); + } + } + + if (pMaterial->emissiveTexture) + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + *pMaterial->emissiveTexture, + true, + imageNeedsMipmaps)); + if (pMaterial->normalTexture) + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + *pMaterial->normalTexture, + false, + imageNeedsMipmaps)); + if (pMaterial->occlusionTexture) + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + *pMaterial->occlusionTexture, + false, + imageNeedsMipmaps)); + }); + + return asyncSystem.all(std::move(futures)); +} + +namespace { + +bool isSupportedIndexComponentType(int32_t componentType) { + return componentType == Accessor::ComponentType::UNSIGNED_BYTE || + componentType == Accessor::ComponentType::UNSIGNED_SHORT || + componentType == Accessor::ComponentType::UNSIGNED_INT; +} + +bool isSupportedPrimitiveMode(int32_t primitiveMode) { + return primitiveMode == MeshPrimitive::Mode::TRIANGLES || + primitiveMode == MeshPrimitive::Mode::TRIANGLE_STRIP || + primitiveMode == MeshPrimitive::Mode::POINTS; +} + +// Determines if a glTF primitive is usable for our purposes. +bool isValidPrimitive( + CesiumGltf::Model& gltf, + CesiumGltf::MeshPrimitive& primitive) { + if (!isSupportedPrimitiveMode(primitive.mode)) { + // This primitive's mode is not supported. + return false; + } + + auto positionAccessorIt = + primitive.attributes.find(AttributeSemantics::POSITION); + if (positionAccessorIt == primitive.attributes.end()) { + // This primitive doesn't have a POSITION semantic, so it's not valid. + return false; + } + + AccessorView positionView(gltf, positionAccessorIt->second); + if (positionView.status() != AccessorViewStatus::Valid) { + // This primitive's POSITION accessor is invalid, so the primitive is not + // valid. + return false; + } + + Accessor* pIndexAccessor = Model::getSafe(&gltf.accessors, primitive.indices); + if (pIndexAccessor && + !isSupportedIndexComponentType(pIndexAccessor->componentType)) { + // This primitive's indices are not a supported type, so the primitive is + // not valid. + return false; + } + + return true; +} + +bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture) { + const Sampler& sampler = Model::getSafe(gltf.samplers, texture.sampler); + + switch (sampler.minFilter.value_or( + CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + return true; + default: // LINEAR and NEAREST + return false; + } +} + +struct ExtensionUnrealTextureResource { + static inline constexpr const char* TypeName = + "ExtensionUnrealTextureResource"; + static inline constexpr const char* ExtensionName = + "PRIVATE_unreal_texture_resource"; + + ExtensionUnrealTextureResource() {} + + TSharedPtr pTextureResource = nullptr; + std::optional> createFuture = std::nullopt; +}; + +std::mutex textureResourceMutex; + +// Returns a Future that will resolve when the image is loaded. It _may_ also +// return a Promise, in which case the calling thread is responsible for doing +// the loading and should resolve the Promise when it's done. +std::pair, std::optional>> +getOrCreateImageFuture(const AsyncSystem& asyncSystem, Image& image) { + std::scoped_lock lock(textureResourceMutex); + + ExtensionUnrealTextureResource& extension = + image.cesium->addExtension(); + if (extension.createFuture) { + // Another thread is already working on this image. + return {*extension.createFuture, std::nullopt}; + } else { + // This thread will work on this image. + Promise promise = asyncSystem.createPromise(); + return {promise.getFuture().share(), promise}; + } +} + +SharedFuture createTextureInLoadThread( + const AsyncSystem& asyncSystem, + Model& gltf, + TextureInfo& textureInfo, + bool sRGB, + const std::vector& imageNeedsMipmaps) { + Texture* pTexture = Model::getSafe(&gltf.textures, textureInfo.index); + if (pTexture == nullptr) + return asyncSystem.createResolvedFuture().share(); + + Image* pImage = Model::getSafe(&gltf.images, pTexture->source); + if (pImage == nullptr) + return asyncSystem.createResolvedFuture().share(); + + check(pTexture->source >= 0 && pTexture->source < imageNeedsMipmaps.size()); + bool needsMips = imageNeedsMipmaps[pTexture->source]; + + auto [future, maybePromise] = getOrCreateImageFuture(asyncSystem, *pImage); + if (!maybePromise) { + // Another thread is already loading this image. + return future; + } + + // Proceed to load the image in this thread. + ImageCesium& cesium = *pImage->cesium; + + if (needsMips && !cesium.pixelData.empty()) { + std::optional errorMessage = + CesiumGltfReader::GltfReader::generateMipMaps(cesium); + if (errorMessage) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s"), + UTF8_TO_TCHAR(errorMessage->c_str())); + } + } + + CesiumTextureUtility::loadTextureFromModelAnyThreadPart( + gltf, + *pTexture, + sRGB); + + maybePromise->resolve(); + + return future; +} + +} // namespace diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.h b/Source/CesiumRuntime/Private/CesiumGltfTextures.h new file mode 100644 index 000000000..339481b1b --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.h @@ -0,0 +1,24 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#pragma once + +#include + +namespace CesiumAsync { +class AsyncSystem; +} + +namespace CesiumGltf { +struct Model; +} + +class CesiumGltfTextures { +public: + /** + * Creates all of the textures that are required by the given glTF, and adds + * `ExtensionUnrealTexture` to each. + */ + static CesiumAsync::Future createInLoadThread( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::Model& model); +}; diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index b7aa9e52f..19c3ea3f6 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -265,18 +265,18 @@ void ReferenceCountedUnrealTexture::setUnrealTexture( this->_pUnrealTexture = p; } -const TUniquePtr& +const TSharedPtr& ReferenceCountedUnrealTexture::getTextureResource() const { return this->_pTextureResource; } -TUniquePtr& +TSharedPtr& ReferenceCountedUnrealTexture::getTextureResource() { return this->_pTextureResource; } void ReferenceCountedUnrealTexture::setTextureResource( - TUniquePtr&& p) { + TSharedPtr&& p) { this->_pTextureResource = std::move(p); } @@ -290,7 +290,7 @@ void ReferenceCountedUnrealTexture::setSharedImage( this->_pImageCesium = image; } -TUniquePtr createTextureResourceFromImageCesium( +TSharedPtr createTextureResourceFromImageCesium( CesiumGltf::ImageCesium& imageCesium, TextureAddress addressX, TextureAddress addressY, @@ -307,8 +307,8 @@ TUniquePtr createTextureResourceFromImageCesium( FTexture2DRHIRef textureReference = CreateRHITexture2D_Async(imageCesium, pixelFormat, sRGB); - TUniquePtr textureResource = - MakeUnique( + TSharedPtr textureResource = + MakeShared( textureReference, group, imageCesium.width, @@ -339,7 +339,7 @@ TUniquePtr createTextureResourceFromImageCesium( return nullptr; } - return MakeUnique( + return MakeShared( std::move(imageCesium), group, imageCesium.width, @@ -372,7 +372,7 @@ struct ExtensionUnrealTextureResource { std::optional> preprocessFuture = std::nullopt; - static TUniquePtr loadTextureResource( + static TSharedPtr loadTextureResource( CesiumGltf::ImageCesium& imageCesium, TextureAddress addressX, TextureAddress addressY, @@ -396,7 +396,7 @@ struct ExtensionUnrealTextureResource { // Already have a texture resource, just use that. if (extension.pTextureResource != nullptr) { - return MakeUnique( + return MakeShared( extension.pTextureResource.Get(), group, imageCesium.width, @@ -416,7 +416,7 @@ struct ExtensionUnrealTextureResource { // caching purposes. imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - TUniquePtr textureResource = + TSharedPtr textureResource = createTextureResourceFromImageCesium( imageCesium, addressX, @@ -429,423 +429,448 @@ struct ExtensionUnrealTextureResource { check(textureResource != nullptr); - extension.pTextureResource = - TSharedPtr(textureResource.Release()); - return MakeUnique( - extension.pTextureResource.Get(), - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0); + extension.pTextureResource = textureResource; + + return textureResource; } -}; -std::optional getSourceIndexFromModelAndTexture( - const CesiumGltf::Model& model, - const CesiumGltf::Texture& texture) { - const CesiumGltf::ExtensionKhrTextureBasisu* pKtxExtension = - texture.getExtension(); - const CesiumGltf::ExtensionTextureWebp* pWebpExtension = - texture.getExtension(); - - int32_t source = -1; - if (pKtxExtension) { - if (pKtxExtension->source < 0 || - pKtxExtension->source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "KTX texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - pKtxExtension->source); - return std::nullopt; - } - return std::optional(pKtxExtension->source); - } else if (pWebpExtension) { - if (pWebpExtension->source < 0 || - pWebpExtension->source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "WebP texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - pWebpExtension->source); - return std::nullopt; - } - return std::optional(pWebpExtension->source); - } else { - if (texture.source < 0 || texture.source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "Texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - texture.source); - return std::nullopt; + std::optional getSourceIndexFromModelAndTexture( + const CesiumGltf::Model& model, + const CesiumGltf::Texture& texture) { + const CesiumGltf::ExtensionKhrTextureBasisu* pKtxExtension = + texture.getExtension(); + const CesiumGltf::ExtensionTextureWebp* pWebpExtension = + texture.getExtension(); + + int32_t source = -1; + if (pKtxExtension) { + if (pKtxExtension->source < 0 || + pKtxExtension->source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "KTX texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + pKtxExtension->source); + return std::nullopt; + } + return std::optional(pKtxExtension->source); + } else if (pWebpExtension) { + if (pWebpExtension->source < 0 || + pWebpExtension->source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "WebP texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + pWebpExtension->source); + return std::nullopt; + } + return std::optional(pWebpExtension->source); + } else { + if (texture.source < 0 || texture.source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + texture.source); + return std::nullopt; + } + return std::optional(texture.source); } - return std::optional(texture.source); } -} -TUniquePtr loadTextureFromModelAnyThreadPart( - CesiumGltf::Model& model, - CesiumGltf::Texture& texture, - bool sRGB) { - int64_t textureIndex = - model.textures.empty() ? -1 : &texture - &model.textures[0]; - if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { - textureIndex = -1; - } + TUniquePtr loadTextureFromModelAnyThreadPart( + CesiumGltf::Model& model, + CesiumGltf::Texture& texture, + bool sRGB) { + int64_t textureIndex = + model.textures.empty() ? -1 : &texture - &model.textures[0]; + if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { + textureIndex = -1; + } - ExtensionUnrealTexture& extension = - texture.addExtension(); - if (extension.pTexture && (extension.pTexture->getUnrealTexture() || - extension.pTexture->getTextureResource())) { - // There's an existing Unreal texture for this glTF texture. This will - // happen if this texture is used by multiple primitives on the same - // model. It will also be the case when this model was upsampled from a - // parent tile. - TUniquePtr pResult = MakeUnique(); - pResult->pTexture = extension.pTexture; - pResult->textureIndex = textureIndex; - return pResult; - } + ExtensionUnrealTexture& extension = + texture.addExtension(); + if (extension.pTexture && (extension.pTexture->getUnrealTexture() || + extension.pTexture->getTextureResource())) { + // There's an existing Unreal texture for this glTF texture. This will + // happen if this texture is used by multiple primitives on the same + // model. It will also be the case when this model was upsampled from a + // parent tile. + TUniquePtr pResult = + MakeUnique(); + pResult->pTexture = extension.pTexture; + pResult->textureIndex = textureIndex; + return pResult; + } - std::optional optionalSourceIndex = - getSourceIndexFromModelAndTexture(model, texture); - if (!optionalSourceIndex.has_value()) { - return nullptr; - }; + std::optional optionalSourceIndex = + getSourceIndexFromModelAndTexture(model, texture); + if (!optionalSourceIndex.has_value()) { + return nullptr; + }; - CesiumGltf::Image& image = model.images[*optionalSourceIndex]; - const CesiumGltf::Sampler& sampler = - model.getSafe(model.samplers, texture.sampler); + CesiumGltf::Image& image = model.images[*optionalSourceIndex]; + const CesiumGltf::Sampler& sampler = + model.getSafe(model.samplers, texture.sampler); - TUniquePtr result = - loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); + TUniquePtr result = + loadTextureFromImageAndSamplerAnyThreadPart( + image.cesium, + sampler, + sRGB); - if (result) { - extension.pTexture = result->pTexture; - result->textureIndex = textureIndex; - } + if (result) { + extension.pTexture = result->pTexture; + result->textureIndex = textureIndex; + } - return result; -} + return result; + } -TextureFilter getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { - // Unreal Engine's available filtering modes are only nearest, bilinear, - // trilinear, and "default". Default means "use the texture group settings", - // and the texture group settings are defined in a config file and can - // vary per platform. All filter modes can use mipmaps if they're available, - // but only TF_Default will ever use anisotropic texture filtering. - // - // Unreal also doesn't separate the minification filter from the - // magnification filter. So we'll just ignore the magFilter unless it's the - // only filter specified. - // - // Generally our bias is toward TF_Default, because that gives the user more - // control via texture groups. - - if (sampler.magFilter && !sampler.minFilter) { - // Only a magnification filter is specified, so use it. - return sampler.magFilter.value() == CesiumGltf::Sampler::MagFilter::NEAREST - ? TextureFilter::TF_Nearest - : TextureFilter::TF_Default; - } else if (sampler.minFilter) { - // Use specified minFilter. - switch (sampler.minFilter.value()) { - case CesiumGltf::Sampler::MinFilter::NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - return TextureFilter::TF_Nearest; - case CesiumGltf::Sampler::MinFilter::LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - return TextureFilter::TF_Bilinear; - default: + TextureFilter + getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { + // Unreal Engine's available filtering modes are only nearest, bilinear, + // trilinear, and "default". Default means "use the texture group settings", + // and the texture group settings are defined in a config file and can + // vary per platform. All filter modes can use mipmaps if they're available, + // but only TF_Default will ever use anisotropic texture filtering. + // + // Unreal also doesn't separate the minification filter from the + // magnification filter. So we'll just ignore the magFilter unless it's the + // only filter specified. + // + // Generally our bias is toward TF_Default, because that gives the user more + // control via texture groups. + + if (sampler.magFilter && !sampler.minFilter) { + // Only a magnification filter is specified, so use it. + return sampler.magFilter.value() == + CesiumGltf::Sampler::MagFilter::NEAREST + ? TextureFilter::TF_Nearest + : TextureFilter::TF_Default; + } else if (sampler.minFilter) { + // Use specified minFilter. + switch (sampler.minFilter.value()) { + case CesiumGltf::Sampler::MinFilter::NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + return TextureFilter::TF_Nearest; + case CesiumGltf::Sampler::MinFilter::LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + return TextureFilter::TF_Bilinear; + default: + return TextureFilter::TF_Default; + } + } else { + // No filtering specified at all, let the texture group decide. return TextureFilter::TF_Default; } - } else { - // No filtering specified at all, let the texture group decide. - return TextureFilter::TF_Default; } -} - -bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { - switch (sampler.minFilter.value_or( - CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - return true; - break; - default: // LINEAR and NEAREST - return false; - } -} -TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::SharedAsset& image, - const CesiumGltf::Sampler& sampler, - bool sRGB) { - return loadTextureAnyThreadPart( - image, - convertGltfWrapSToUnreal(sampler.wrapS), - convertGltfWrapTToUnreal(sampler.wrapT), - getTextureFilterFromSampler(sampler), - getUseMipmapsIfAvailableFromSampler(sampler), - // TODO: allow texture group to be configured on Cesium3DTileset. - TEXTUREGROUP_World, - sRGB, - std::nullopt); -} - -static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { - if (!pHalfLoadedTexture || !pHalfLoadedTexture->pTexture) { - return nullptr; + bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { + switch (sampler.minFilter.value_or( + CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + return true; + break; + default: // LINEAR and NEAREST + return false; + } } - UTexture2D* pTexture = pHalfLoadedTexture->pTexture->getUnrealTexture(); - if (!pTexture) { - pTexture = NewObject( - GetTransientPackage(), - MakeUniqueObjectName( - GetTransientPackage(), - UTexture2D::StaticClass(), - "CesiumRuntimeTexture"), - RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); - - pTexture->AddressX = pHalfLoadedTexture->addressX; - pTexture->AddressY = pHalfLoadedTexture->addressY; - pTexture->Filter = pHalfLoadedTexture->filter; - pTexture->LODGroup = pHalfLoadedTexture->group; - pTexture->SRGB = pHalfLoadedTexture->sRGB; - - pTexture->NeverStream = true; - - pHalfLoadedTexture->pTexture->setUnrealTexture(pTexture); + TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( + CesiumGltf::SharedAsset& image, + const CesiumGltf::Sampler& sampler, + bool sRGB) { + return loadTextureAnyThreadPart( + image, + convertGltfWrapSToUnreal(sampler.wrapS), + convertGltfWrapTToUnreal(sampler.wrapT), + getTextureFilterFromSampler(sampler), + getUseMipmapsIfAvailableFromSampler(sampler), + // TODO: allow texture group to be configured on Cesium3DTileset. + TEXTUREGROUP_World, + sRGB, + std::nullopt); } - return pTexture; -} - -TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::SharedAsset& image, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat) { - TUniquePtr textureResource = - ExtensionUnrealTextureResource::loadTextureResource( - *image, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - overridePixelFormat); + static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { + if (!pHalfLoadedTexture || !pHalfLoadedTexture->pTexture) { + return nullptr; + } - TUniquePtr pResult = MakeUnique(); - pResult->pTexture = new ReferenceCountedUnrealTexture(); + UTexture2D* pTexture = pHalfLoadedTexture->pTexture->getUnrealTexture(); + if (!pTexture) { + pTexture = NewObject( + GetTransientPackage(), + MakeUniqueObjectName( + GetTransientPackage(), + UTexture2D::StaticClass(), + "CesiumRuntimeTexture"), + RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); + + pTexture->AddressX = pHalfLoadedTexture->addressX; + pTexture->AddressY = pHalfLoadedTexture->addressY; + pTexture->Filter = pHalfLoadedTexture->filter; + pTexture->LODGroup = pHalfLoadedTexture->group; + pTexture->SRGB = pHalfLoadedTexture->sRGB; + + pTexture->NeverStream = true; + + pHalfLoadedTexture->pTexture->setUnrealTexture(pTexture); + } - pResult->addressX = addressX; - pResult->addressY = addressY; - pResult->filter = filter; - pResult->group = group; - pResult->sRGB = sRGB; + return pTexture; + } - pResult->pTexture->setTextureResource(MoveTemp(textureResource)); - pResult->pTexture->setSharedImage(image); - return pResult; -} + TUniquePtr loadTextureAnyThreadPart( + CesiumGltf::SharedAsset& image, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat) { + TSharedPtr textureResource = + ExtensionUnrealTextureResource::loadTextureResource( + *image, + addressX, + addressY, + filter, + useMipMapsIfAvailable, + group, + sRGB, + overridePixelFormat); -CesiumUtility::IntrusivePointer -loadTextureGameThreadPart( - CesiumGltf::Model& model, - LoadedTextureResult* pHalfLoadedTexture) { - if (pHalfLoadedTexture == nullptr) - return nullptr; + TUniquePtr pResult = MakeUnique(); + pResult->pTexture = new ReferenceCountedUnrealTexture(); - CesiumUtility::IntrusivePointer pResult = - loadTextureGameThreadPart(pHalfLoadedTexture); + pResult->addressX = addressX; + pResult->addressY = addressY; + pResult->filter = filter; + pResult->group = group; + pResult->sRGB = sRGB; - if (pResult && pHalfLoadedTexture && pHalfLoadedTexture->textureIndex >= 0 && - size_t(pHalfLoadedTexture->textureIndex) < model.textures.size()) { - CesiumGltf::Texture& texture = - model.textures[pHalfLoadedTexture->textureIndex]; - ExtensionUnrealTexture& extension = - texture.addExtension(); - extension.pTexture = pHalfLoadedTexture->pTexture; + pResult->pTexture->setTextureResource(MoveTemp(textureResource)); + pResult->pTexture->setSharedImage(image); + return pResult; } - return pHalfLoadedTexture->pTexture; -} + CesiumUtility::IntrusivePointer + loadTextureGameThreadPart( + CesiumGltf::Model& model, + LoadedTextureResult* pHalfLoadedTexture) { + if (pHalfLoadedTexture == nullptr) + return nullptr; -CesiumUtility::IntrusivePointer -loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadTexture) + CesiumUtility::IntrusivePointer pResult = + loadTextureGameThreadPart(pHalfLoadedTexture); + + if (pResult && pHalfLoadedTexture && + pHalfLoadedTexture->textureIndex >= 0 && + size_t(pHalfLoadedTexture->textureIndex) < model.textures.size()) { + CesiumGltf::Texture& texture = + model.textures[pHalfLoadedTexture->textureIndex]; + ExtensionUnrealTexture& extension = + texture.addExtension(); + extension.pTexture = pHalfLoadedTexture->pTexture; + } - TUniquePtr& pTextureResource = - pHalfLoadedTexture->pTexture->getTextureResource(); - if (pTextureResource == nullptr) { - // Texture is already loaded (or unloadable). return pHalfLoadedTexture->pTexture; } - UTexture2D* pTexture = CreateTexture2D(pHalfLoadedTexture); - if (pTexture == nullptr) { - return nullptr; - } + CesiumUtility::IntrusivePointer + loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadTexture) + + TSharedPtr& pTextureResource = + pHalfLoadedTexture->pTexture->getTextureResource(); + if (pTextureResource == nullptr) { + // Texture is already loaded (or unloadable). + return pHalfLoadedTexture->pTexture; + } - FCesiumTextureResourceBase* pCesiumTextureResource = - pTextureResource.Release(); - if (pCesiumTextureResource) { - pTexture->SetResource(pCesiumTextureResource); + UTexture2D* pTexture = CreateTexture2D(pHalfLoadedTexture); + if (pTexture == nullptr) { + return nullptr; + } - ENQUEUE_RENDER_COMMAND(Cesium_InitResource) - ([pTexture, pCesiumTextureResource](FRHICommandListImmediate& RHICmdList) { - pCesiumTextureResource->SetTextureReference( - pTexture->TextureReference.TextureReferenceRHI); + FCesiumTextureResourceBase* pCesiumTextureResource = + pTextureResource.Release(); + if (pCesiumTextureResource) { + pTexture->SetResource(pCesiumTextureResource); + + ENQUEUE_RENDER_COMMAND(Cesium_InitResource) + ([pTexture, + pCesiumTextureResource](FRHICommandListImmediate& RHICmdList) { + pCesiumTextureResource->SetTextureReference( + pTexture->TextureReference.TextureReferenceRHI); #if ENGINE_VERSION_5_3_OR_HIGHER - pCesiumTextureResource->InitResource( - FRHICommandListImmediate::Get()); // Init Resource now requires a - // command list. + pCesiumTextureResource->InitResource( + FRHICommandListImmediate::Get()); // Init Resource now requires a + // command list. #else - pCesiumTextureResource->InitResource(); + pCesiumTextureResource->InitResource(); #endif - }); - } - - return pHalfLoadedTexture->pTexture; -} + }); + } -TextureAddress convertGltfWrapSToUnreal(int32_t wrapS) { - // glTF spec: "When undefined, a sampler with repeat wrapping and auto - // filtering should be used." - switch (wrapS) { - case CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE: - return TextureAddress::TA_Clamp; - case CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT: - return TextureAddress::TA_Mirror; - case CesiumGltf::Sampler::WrapS::REPEAT: - default: - return TextureAddress::TA_Wrap; + return pHalfLoadedTexture->pTexture; } -} -TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { - // glTF spec: "When undefined, a sampler with repeat wrapping and auto - // filtering should be used." - switch (wrapT) { - case CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE: - return TextureAddress::TA_Clamp; - case CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT: - return TextureAddress::TA_Mirror; - case CesiumGltf::Sampler::WrapT::REPEAT: - default: - return TextureAddress::TA_Wrap; - } -} + TSharedPtr createTextureResource( + CesiumGltf::ImageCesium& imageCesium, + bool sRGB, + std::optional overridePixelFormat) { + std::optional optionalPixelFormat = + getPixelFormatForImageCesium(imageCesium, overridePixelFormat); + if (!optionalPixelFormat.has_value()) { + return nullptr; + } -std::optional> -createMipMapsForSampler( - const CesiumAsync::AsyncSystem& asyncSystem, - const CesiumGltf::Sampler& sampler, - CesiumGltf::ImageCesium& image) { - std::unique_lock lock(textureResourceMutex); + EPixelFormat pixelFormat = optionalPixelFormat.value(); - ExtensionUnrealTextureResource& extension = - image.addExtension(); + // Store the current size of the pixel data, because + // we're about to clear it but we still want to have + // an accurate estimation of the size of the image for + // caching purposes. + imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - // Future already exists, we don't need to do anything else. - if (extension.preprocessFuture.has_value()) { - return extension.preprocessFuture.value(); + return createTextureResourceFromImageCesium( + imageCesium, + TextureAddress::TA_Wrap, + TextureAddress::TA_Wrap, + TextureFilter::TF_Default, + true, + TextureGroup::TEXTUREGROUP_World, + sRGB, + pixelFormat); } - // Generate mipmaps if needed. - // An image needs mipmaps generated for it if: - // 1. It is used by a Texture that has a Sampler with a mipmap filtering - // mode, and - // 2. It does not already have mipmaps. - // It's ok if an image has mipmaps even if not all textures will use them. - // There's no reason to have two RHI textures, one with and one without - // mips. - bool needsMipmaps; - switch (sampler.minFilter.value_or( - CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - needsMipmaps = true; - break; - default: // LINEAR and NEAREST - needsMipmaps = false; - break; + TextureAddress convertGltfWrapSToUnreal(int32_t wrapS) { + // glTF spec: "When undefined, a sampler with repeat wrapping and auto + // filtering should be used." + switch (wrapS) { + case CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE: + return TextureAddress::TA_Clamp; + case CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT: + return TextureAddress::TA_Mirror; + case CesiumGltf::Sampler::WrapS::REPEAT: + default: + return TextureAddress::TA_Wrap; + } } - if (!needsMipmaps || image.pixelData.empty()) { - // If we don't need mipmaps, we don't want to create a future for them. - // This allows a future sampler using this image that does need mipmaps to - // generate them. - return std::nullopt; + TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { + // glTF spec: "When undefined, a sampler with repeat wrapping and auto + // filtering should be used." + switch (wrapT) { + case CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE: + return TextureAddress::TA_Clamp; + case CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT: + return TextureAddress::TA_Mirror; + case CesiumGltf::Sampler::WrapT::REPEAT: + default: + return TextureAddress::TA_Wrap; + } } - CesiumAsync::Promise promise = - asyncSystem.createPromise(); + std::optional> + createMipMapsForSampler( + const CesiumAsync::AsyncSystem& asyncSystem, + const CesiumGltf::Sampler& sampler, + CesiumGltf::ImageCesium& image) { + std::unique_lock lock(textureResourceMutex); - extension.preprocessFuture = promise.getFuture().share(); + ExtensionUnrealTextureResource& extension = + image.addExtension(); - lock.unlock(); + // Future already exists, we don't need to do anything else. + if (extension.preprocessFuture.has_value()) { + return extension.preprocessFuture.value(); + } - // We need mipmaps generated. - std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(image); - if (errorMessage) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s"), - UTF8_TO_TCHAR(errorMessage->c_str())); - } - promise.resolve(&image); - return *extension.preprocessFuture; -} + // Generate mipmaps if needed. + // An image needs mipmaps generated for it if: + // 1. It is used by a Texture that has a Sampler with a mipmap filtering + // mode, and + // 2. It does not already have mipmaps. + // It's ok if an image has mipmaps even if not all textures will use them. + // There's no reason to have two RHI textures, one with and one without + // mips. + bool needsMipmaps; + switch (sampler.minFilter.value_or( + CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + needsMipmaps = true; + break; + default: // LINEAR and NEAREST + needsMipmaps = false; + break; + } -CesiumAsync::SharedFuture createMipMapsForAllTextures( - const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::Model& model) { - std::vector> futures; - for (const Texture& texture : model.textures) { - const CesiumGltf::Sampler& sampler = - model.getSafe(model.samplers, texture.sampler); - std::optional> - optionalFuture = createMipMapsForSampler( - asyncSystem, - sampler, - *model.images[texture.source].cesium); - if (optionalFuture.has_value()) { - futures.push_back(optionalFuture.value()); + if (!needsMipmaps || image.pixelData.empty()) { + // If we don't need mipmaps, we don't want to create a future for them. + // This allows a future sampler using this image that does need mipmaps to + // generate them. + return std::nullopt; + } + + CesiumAsync::Promise promise = + asyncSystem.createPromise(); + + extension.preprocessFuture = promise.getFuture().share(); + + lock.unlock(); + + // We need mipmaps generated. + std::optional errorMessage = + CesiumGltfReader::GltfReader::generateMipMaps(image); + if (errorMessage) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s"), + UTF8_TO_TCHAR(errorMessage->c_str())); } + promise.resolve(&image); + return *extension.preprocessFuture; } - return asyncSystem.all(std::move(futures)) - .thenImmediately( - []([[maybe_unused]] std::vector< - CesiumGltf::ImageCesium*>&& /*results*/) -> void {}) - .share(); -} + CesiumAsync::SharedFuture createMipMapsForAllTextures( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::Model& model) { + std::vector> futures; + for (const Texture& texture : model.textures) { + const CesiumGltf::Sampler& sampler = + model.getSafe(model.samplers, texture.sampler); + std::optional> + optionalFuture = createMipMapsForSampler( + asyncSystem, + sampler, + *model.images[texture.source].cesium); + if (optionalFuture.has_value()) { + futures.push_back(optionalFuture.value()); + } + } + + return asyncSystem.all(std::move(futures)) + .thenImmediately( + []([[maybe_unused]] std::vector< + CesiumGltf::ImageCesium*>&& /*results*/) -> void {}) + .share(); + } } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 22cf7b639..767b6da4e 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -47,9 +47,9 @@ struct ReferenceCountedUnrealTexture void setUnrealTexture(const TObjectPtr& p); // The renderer / RHI FTextureResource holding the pixel data. - const TUniquePtr& getTextureResource() const; - TUniquePtr& getTextureResource(); - void setTextureResource(TUniquePtr&& p); + const TSharedPtr& getTextureResource() const; + TSharedPtr& getTextureResource(); + void setTextureResource(TSharedPtr&& p); /// The SharedAsset that this texture was created from. CesiumGltf::SharedAsset getSharedImage() const; @@ -57,7 +57,7 @@ struct ReferenceCountedUnrealTexture private: TObjectPtr _pUnrealTexture; - TUniquePtr _pTextureResource; + TSharedPtr _pTextureResource; CesiumGltf::SharedAsset _pImageCesium; }; @@ -186,6 +186,16 @@ loadTextureGameThreadPart( CesiumUtility::IntrusivePointer loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture); +/** + * Creates a `FCesiumTextureResourceBase` for an image. This texture resource is + * intended to be later used with `FCesiumUseExistingTextureResource`, which + * will supply sampler, texture group, and other settings. + */ +TSharedPtr createTextureResource( + CesiumGltf::ImageCesium& imageCesium, + bool sRGB, + std::optional overridePixelFormat); + /** * @brief Convert a glTF {@link CesiumGltf::Sampler::WrapS} value to an Unreal * `TextureAddress` value. diff --git a/extern/cesium-native b/extern/cesium-native index 31d0048b5..e78188244 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 31d0048b58044546182d2bcd9d72106bb5ca8991 +Subproject commit e7818824412a68c3e6027b9e131cb39a33bed15b From 8b0bcdc8cfc77093421210a609c4822fd209389f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 14:56:09 +1000 Subject: [PATCH 26/68] Sort of working again after reorg. But raster overlays don't work, and there appears to be an sRGB problem. --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 4 +- .../Private/CesiumGltfComponent.cpp | 17 +- .../Private/CesiumGltfTextures.cpp | 70 +- .../Private/CesiumGltfTextures.h | 2 +- .../Private/CesiumTextureResource.cpp | 351 ++++++- .../Private/CesiumTextureResource.h | 142 ++- .../Private/CesiumTextureUtility.cpp | 944 +++++++----------- .../Private/CesiumTextureUtility.h | 21 +- .../Private/ExtensionImageCesiumUnreal.cpp | 101 ++ .../Private/ExtensionImageCesiumUnreal.h | 68 ++ extern/cesium-native | 2 +- 11 files changed, 943 insertions(+), 779 deletions(-) create mode 100644 Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp create mode 100644 Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 39b58fa61..af8a85824 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -948,7 +948,7 @@ class UnrealResourcePreparer const Cesium3DTilesSelection::TileContent& content = tile.getContent(); const Cesium3DTilesSelection::TileRenderContent* pRenderContent = content.getRenderContent(); - if (pRenderContent) { + if (pMainThreadRendererResources != nullptr && pRenderContent != nullptr) { UCesiumGltfComponent* pGltfContent = reinterpret_cast( pRenderContent->getRenderResources()); @@ -978,7 +978,7 @@ class UnrealResourcePreparer UCesiumGltfComponent* pGltfContent = reinterpret_cast( pRenderContent->getRenderResources()); - if (pGltfContent) { + if (pMainThreadRendererResources != nullptr && pGltfContent != nullptr) { pGltfContent->DetachRasterTile( tile, rasterTile, diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 2f07cd875..e8d2c5620 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -8,6 +8,7 @@ #include "CesiumFeatureIdSet.h" #include "CesiumGltfPointsComponent.h" #include "CesiumGltfPrimitiveComponent.h" +#include "CesiumGltfTextures.h" #include "CesiumMaterialUserData.h" #include "CesiumRasterOverlays.h" #include "CesiumRuntime.h" @@ -400,22 +401,22 @@ struct ColorVisitor { template static TUniquePtr loadTexture( CesiumGltf::Model& model, - const std::optional& gltfTexture, + const std::optional& gltfTextureInfo, bool sRGB) { - if (!gltfTexture || gltfTexture.value().index < 0 || - gltfTexture.value().index >= model.textures.size()) { - if (gltfTexture && gltfTexture.value().index >= 0) { + if (!gltfTextureInfo || gltfTextureInfo.value().index < 0 || + gltfTextureInfo.value().index >= model.textures.size()) { + if (gltfTextureInfo && gltfTextureInfo.value().index >= 0) { UE_LOG( LogCesium, Warning, TEXT("Texture index must be less than %d, but is %d"), model.textures.size(), - gltfTexture.value().index); + gltfTextureInfo.value().index); } return nullptr; } - int32_t textureIndex = gltfTexture.value().index; + int32_t textureIndex = gltfTextureInfo.value().index; CesiumGltf::Texture& texture = model.textures[textureIndex]; return loadTextureFromModelAnyThreadPart(model, texture, sRGB); } @@ -1061,7 +1062,7 @@ std::string constrainLength(const std::string& s, const size_t maxLength) { } /** - * @brief Create an FName from the given strings. + * @brief CreateNew an FName from the given strings. * * This will combine the prefix and the suffix and create an FName. * If the string would be longer than the given length, then @@ -2238,7 +2239,7 @@ loadModelAnyThreadPart( const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart) - return createMipMapsForAllTextures(asyncSystem, *options.pModel) + return CesiumGltfTextures::createInWorkerThread(asyncSystem, *options.pModel) .thenInWorkerThread( [transform, ellipsoid, options = std::move(options)]() -> UCesiumGltfComponent::CreateOffGameThreadResult { diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index a6fff0bbb..95cfe003c 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -4,6 +4,7 @@ #include "CesiumRuntime.h" #include "CesiumTextureResource.h" #include "CesiumTextureUtility.h" +#include "ExtensionImageCesiumUnreal.h" #include #include #include @@ -36,7 +37,7 @@ SharedFuture createTextureInLoadThread( } // namespace -/*static*/ CesiumAsync::Future CesiumGltfTextures::createInLoadThread( +/*static*/ CesiumAsync::Future CesiumGltfTextures::createInWorkerThread( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::Model& model) { // This array is parallel to model.images and indicates whether each image @@ -183,39 +184,6 @@ bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture) { } } -struct ExtensionUnrealTextureResource { - static inline constexpr const char* TypeName = - "ExtensionUnrealTextureResource"; - static inline constexpr const char* ExtensionName = - "PRIVATE_unreal_texture_resource"; - - ExtensionUnrealTextureResource() {} - - TSharedPtr pTextureResource = nullptr; - std::optional> createFuture = std::nullopt; -}; - -std::mutex textureResourceMutex; - -// Returns a Future that will resolve when the image is loaded. It _may_ also -// return a Promise, in which case the calling thread is responsible for doing -// the loading and should resolve the Promise when it's done. -std::pair, std::optional>> -getOrCreateImageFuture(const AsyncSystem& asyncSystem, Image& image) { - std::scoped_lock lock(textureResourceMutex); - - ExtensionUnrealTextureResource& extension = - image.cesium->addExtension(); - if (extension.createFuture) { - // Another thread is already working on this image. - return {*extension.createFuture, std::nullopt}; - } else { - // This thread will work on this image. - Promise promise = asyncSystem.createPromise(); - return {promise.getFuture().share(), promise}; - } -} - SharedFuture createTextureInLoadThread( const AsyncSystem& asyncSystem, Model& gltf, @@ -233,35 +201,13 @@ SharedFuture createTextureInLoadThread( check(pTexture->source >= 0 && pTexture->source < imageNeedsMipmaps.size()); bool needsMips = imageNeedsMipmaps[pTexture->source]; - auto [future, maybePromise] = getOrCreateImageFuture(asyncSystem, *pImage); - if (!maybePromise) { - // Another thread is already loading this image. - return future; - } - - // Proceed to load the image in this thread. - ImageCesium& cesium = *pImage->cesium; - - if (needsMips && !cesium.pixelData.empty()) { - std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(cesium); - if (errorMessage) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s"), - UTF8_TO_TCHAR(errorMessage->c_str())); - } - } - - CesiumTextureUtility::loadTextureFromModelAnyThreadPart( - gltf, - *pTexture, - sRGB); - - maybePromise->resolve(); + const ExtensionImageCesiumUnreal& extension = ExtensionImageCesiumUnreal::GetOrCreate( + asyncSystem, + *pImage->cesium, + needsMips, + std::nullopt); - return future; + return extension.getFuture(); } } // namespace diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.h b/Source/CesiumRuntime/Private/CesiumGltfTextures.h index 339481b1b..7f0326ad1 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.h +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.h @@ -18,7 +18,7 @@ class CesiumGltfTextures { * Creates all of the textures that are required by the given glTF, and adds * `ExtensionUnrealTexture` to each. */ - static CesiumAsync::Future createInLoadThread( + static CesiumAsync::Future createInWorkerThread( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::Model& model); }; diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index 1bbeb5a59..b3ca34a8b 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -1,11 +1,87 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumTextureResource.h" +#include "CesiumRuntime.h" +#include "CesiumTextureUtility.h" #include "Misc/CoreStats.h" #include "RenderUtils.h" +#include namespace { +/** + * A Cesium texture resource that uses an already-created `FRHITexture`. This is + * used when `GRHISupportsAsyncTextureCreation` is true and so we were already + * able to create the FRHITexture in a worker thread. It is also used when a + * single glTF `Image` is referenced by multiple glTF `Texture` instances. We + * only need one `FRHITexture` is this case, but we need multiple + * `FTextureResource` instances to support the different sampler settings that + * are likely used in the different textures. + */ +class FCesiumUseExistingTextureResource : public FCesiumTextureResource { +public: + FCesiumUseExistingTextureResource( + FTextureRHIRef existingTexture, + TextureGroup textureGroup, + uint32 width, + uint32 height, + EPixelFormat format, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool useMipsIfAvailable, + uint32 extData); + + FCesiumUseExistingTextureResource( + const TSharedPtr& pExistingTexture, + TextureGroup textureGroup, + uint32 width, + uint32 height, + EPixelFormat format, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool useMipsIfAvailable, + uint32 extData); + +protected: + virtual FTextureRHIRef InitializeTextureRHI() override; + +private: + TSharedPtr _pExistingTexture; +}; + +/** + * A Cesium texture resource that creates an `FRHITexture` from a glTF + * `ImageCesium` when `InitRHI` is called from the render thread. When + * `GRHISupportsAsyncTextureCreation` is false (everywhere but Direct3D), we can + * only create a `FRHITexture` on the render thread, so this is the code that + * does it. + */ +class FCesiumCreateNewTextureResource : public FCesiumTextureResource { +public: + FCesiumCreateNewTextureResource( + CesiumGltf::ImageCesium&& image, + TextureGroup textureGroup, + uint32 width, + uint32 height, + EPixelFormat format, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool useMipsIfAvailable, + uint32 extData); + +protected: + virtual FTextureRHIRef InitializeTextureRHI() override; + +private: + CesiumGltf::ImageCesium _image; +}; + ESamplerFilter convertFilter(TextureFilter filter) { switch (filter) { case TF_Nearest: @@ -98,9 +174,268 @@ void CopyMip( } } +FTexture2DRHIRef createAsyncTextureAndWait( + uint32 SizeX, + uint32 SizeY, + uint8 Format, + uint32 NumMips, + ETextureCreateFlags Flags, + void** InitialMipData, + uint32 NumInitialMips) { +#if ENGINE_VERSION_5_4_OR_HIGHER + FGraphEventRef CompletionEvent; + + FTexture2DRHIRef result = RHIAsyncCreateTexture2D( + SizeX, + SizeY, + Format, + NumMips, + Flags, + ERHIAccess::Unknown, + InitialMipData, + NumInitialMips, + TEXT("CesiumTexture"), + CompletionEvent); + + if (CompletionEvent) { + CompletionEvent->Wait(); + } + + return result; +#elif ENGINE_VERSION_5_3_OR_HIGHER + FGraphEventRef CompletionEvent; + + FTexture2DRHIRef result = RHIAsyncCreateTexture2D( + SizeX, + SizeY, + Format, + NumMips, + Flags, + InitialMipData, + NumInitialMips, + CompletionEvent); + + if (CompletionEvent) { + CompletionEvent->Wait(); + } + + return result; +#else + return RHIAsyncCreateTexture2D( + SizeX, + SizeY, + Format, + NumMips, + Flags, + InitialMipData, + NumInitialMips); +#endif +} + +/** + * @brief Create an RHI texture on this thread. This requires + * GRHISupportsAsyncTextureCreation to be true. + * + * @param image The CPU image to create on the GPU. + * @param format The pixel format of the image. + * @param Whether to use a sRGB color-space. + * @return The RHI texture reference. + */ +FTexture2DRHIRef CreateRHITexture2D_Async( + const CesiumGltf::ImageCesium& image, + EPixelFormat format, + bool sRGB) { + check(GRHISupportsAsyncTextureCreation); + + ETextureCreateFlags textureFlags = TexCreate_ShaderResource; + + // Just like in FCesiumCreateNewTextureResource, we're assuming here that we + // can create an FRHITexture as sRGB, and later create another + // UTexture2D / FTextureResource pointing to the same FRHITexture that is not + // sRGB (or vice-versa), and that Unreal will effectively ignore the flag on + // FRHITexture. + if (sRGB) { + textureFlags |= TexCreate_SRGB; + } + + if (!image.mipPositions.empty()) { + // Here 16 is a generously large (but arbitrary) hard limit for number of + // mips. + uint32 mipCount = static_cast(image.mipPositions.size()); + if (mipCount > 16) { + mipCount = 16; + } + + void* mipsData[16]; + for (size_t i = 0; i < mipCount; ++i) { + const CesiumGltf::ImageCesiumMipPosition& mipPos = image.mipPositions[i]; + mipsData[i] = (void*)(&image.pixelData[mipPos.byteOffset]); + } + + return createAsyncTextureAndWait( + static_cast(image.width), + static_cast(image.height), + format, + mipCount, + textureFlags, + mipsData, + mipCount); + } else { + void* pTextureData = (void*)(image.pixelData.data()); + return createAsyncTextureAndWait( + static_cast(image.width), + static_cast(image.height), + format, + 1, + textureFlags, + &pTextureData, + 1); + } +} + } // namespace -FCesiumTextureResourceBase::FCesiumTextureResourceBase( +void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { + FCesiumTextureResource::Destroy(p); +} + +/*static*/ FCesiumTextureResourceUniquePtr FCesiumTextureResource::CreateNew( + CesiumGltf::ImageCesium& imageCesium, + TextureGroup textureGroup, + const std::optional& overridePixelFormat, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool needsMipMaps) { + if (imageCesium.pixelData.empty()) { + return nullptr; + } + + if (needsMipMaps) { + std::optional errorMessage = + CesiumGltfReader::GltfReader::generateMipMaps(imageCesium); + if (errorMessage) { + UE_LOG( + LogCesium, + Warning, + TEXT("%s"), + UTF8_TO_TCHAR(errorMessage->c_str())); + } + } + + std::optional maybePixelFormat = + CesiumTextureUtility::getPixelFormatForImageCesium( + imageCesium, + overridePixelFormat); + if (!maybePixelFormat) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Image cannot be created because it has an unsupported compressed pixel format (%d)."), + imageCesium.compressedPixelFormat); + return nullptr; + } + + // Store the current size of the pixel data, because + // we're about to clear it but we still want to have + // an accurate estimation of the size of the image for + // caching purposes. + imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); + + if (GRHISupportsAsyncTextureCreation) { + // Create RHI texture resource on this worker + // thread, and then hand it off to the renderer + // thread. + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreateRHITexture2D) + + FTexture2DRHIRef textureReference = + CreateRHITexture2D_Async(imageCesium, *maybePixelFormat, sRGB); + auto pResult = TUniquePtr< + FCesiumUseExistingTextureResource, + FCesiumTextureResourceDeleter>(new FCesiumUseExistingTextureResource( + textureReference, + textureGroup, + imageCesium.width, + imageCesium.height, + *maybePixelFormat, + filter, + addressX, + addressY, + sRGB, + needsMipMaps, + 0)); + + // Clear the now-unnecessary copy of the pixel data. + // Calling clear() isn't good enough because it + // won't actually release the memory. + std::vector pixelData; + imageCesium.pixelData.swap(pixelData); + + std::vector mipPositions; + imageCesium.mipPositions.swap(mipPositions); + + return pResult; + } else { + // The RHI texture will be created later on the + // render thread, directly from this texture source. + // We need valid pixelData here, though. + auto pResult = TUniquePtr< + FCesiumCreateNewTextureResource, + FCesiumTextureResourceDeleter>(new FCesiumCreateNewTextureResource( + std::move(imageCesium), + textureGroup, + imageCesium.width, + imageCesium.height, + *maybePixelFormat, + filter, + addressX, + addressY, + sRGB, + needsMipMaps, + 0)); + return pResult; + } +} + +FCesiumTextureResourceUniquePtr FCesiumTextureResource::CreateWrapped( + const TSharedPtr& pExistingResource, + TextureGroup textureGroup, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool useMipMapsIfAvailable) { + if (pExistingResource == nullptr) + return nullptr; + + return FCesiumTextureResourceUniquePtr(new FCesiumUseExistingTextureResource( + pExistingResource, + textureGroup, + pExistingResource->_width, + pExistingResource->_height, + pExistingResource->_format, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable, + 0)); +} + +/*static*/ void FCesiumTextureResource::Destroy(FCesiumTextureResource* p) { + if (p == nullptr) + return; + + ENQUEUE_RENDER_COMMAND(DeleteResource) + ([p](FRHICommandListImmediate& RHICmdList) { + p->ReleaseResource(); + delete p; + }); +} + +FCesiumTextureResource::FCesiumTextureResource( TextureGroup textureGroup, uint32 width, uint32 height, @@ -128,7 +463,7 @@ FCesiumTextureResourceBase::FCesiumTextureResourceBase( #if ENGINE_VERSION_5_3_OR_HIGHER void FCesiumTextureResourceBase::InitRHI(FRHICommandListBase& RHICmdList) { #else -void FCesiumTextureResourceBase::InitRHI() { +void FCesiumTextureResource::InitRHI() { #endif FSamplerStateInitializerRHI samplerStateInitializer( this->_filter, @@ -188,7 +523,7 @@ void FCesiumTextureResourceBase::InitRHI() { #endif } -void FCesiumTextureResourceBase::ReleaseRHI() { +void FCesiumTextureResource::ReleaseRHI() { DEC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); DEC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); @@ -216,7 +551,7 @@ FOREACH_ENUM_TEXTUREGROUP(DECLARETEXTUREGROUPSTAT) #undef DECLARETEXTUREGROUPSTAT } // namespace -FName FCesiumTextureResourceBase::TextureGroupStatFNames[TEXTUREGROUP_MAX] = { +FName FCesiumTextureResource::TextureGroupStatFNames[TEXTUREGROUP_MAX] = { #define ASSIGNTEXTUREGROUPSTATNAME(Group) GET_STATFNAME(STAT_##Group), FOREACH_ENUM_TEXTUREGROUP(ASSIGNTEXTUREGROUPSTATNAME) #undef ASSIGNTEXTUREGROUPSTATNAME @@ -236,7 +571,7 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( bool sRGB, bool useMipsIfAvailable, uint32 extData) - : FCesiumTextureResourceBase( + : FCesiumTextureResource( textureGroup, width, height, @@ -252,7 +587,7 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( } FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( - FTextureResource* pExistingTexture, + const TSharedPtr& pExistingTexture, TextureGroup textureGroup, uint32 width, uint32 height, @@ -263,7 +598,7 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( bool sRGB, bool useMipsIfAvailable, uint32 extData) - : FCesiumTextureResourceBase( + : FCesiumTextureResource( textureGroup, width, height, @@ -296,7 +631,7 @@ FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( bool sRGB, bool useMipsIfAvailable, uint32 extData) - : FCesiumTextureResourceBase( + : FCesiumTextureResource( textureGroup, width, height, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index 83b75d503..cba96bc6b 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -8,14 +8,79 @@ #include #include +class FCesiumTextureResource; + +struct FCesiumTextureResourceDeleter { + void operator()(FCesiumTextureResource* p); +}; + +using FCesiumTextureResourceUniquePtr = + TUniquePtr; + /** * The base class for Cesium texture resources, making Cesium's texture data * available to Unreal's RHI. The actual creation of the RHI texture is deferred * to a pure virtual method, `InitializeTextureRHI`. */ -class FCesiumTextureResourceBase : public FTextureResource { +class FCesiumTextureResource : public FTextureResource { public: - FCesiumTextureResourceBase( + /** + * Create a new FCesiumTextureResource from an ImageCesium and the given + * sampling parameters. This method is intended to be called from a worker + * thread, not from the game or render thread. + * + * @param imageCesium The image data from which to create the texture + * resource. After this method returns, the `pixelData` will be empty, and + * `sizeBytes` will be set to its previous size. + * @param textureGroup The texture group in which to create this texture. + * @param overridePixelFormat Overrides the pixel format. If std::nullopt, the + * format is inferred from the `ImageCesium`. + * @param filter The texture filtering to use when sampling this texture. + * @param addressX The X texture addressing mode to use when sampling this + * texture. + * @param addressY The Y texture addressing mode to use when sampling this + * texture. + * @param sRGB True if the image data stored in this texture should be treated + * as sRGB. + * @param needsMipMaps True if this texture requires mipmaps. They will be + * generated if they don't already exist. + * @return The created texture resource, or nullptr if a texture could not be + * created. + */ + static FCesiumTextureResourceUniquePtr CreateNew( + CesiumGltf::ImageCesium& imageCesium, + TextureGroup textureGroup, + const std::optional& overridePixelFormat, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool needsMipMaps); + + /** + * Create a new FCesiumTextureResource wrapping an existing one and providing + * new sampling parameters. This method is intended to be called from a worker + * thread, not from the game or render thread. + */ + static FCesiumTextureResourceUniquePtr CreateWrapped( + const TSharedPtr& pExistingResource, + TextureGroup textureGroup, + TextureFilter filter, + TextureAddress addressX, + TextureAddress addressY, + bool sRGB, + bool useMipMapsIfAvailable); + + /** + * Destroys an FCesiumTextureResource. Unreal TextureResources must be + * destroyed on the render thread, so it is important not to call `delete` + * directly. + * + * \param p + */ + static void Destroy(FCesiumTextureResource* p); + + FCesiumTextureResource( TextureGroup textureGroup, uint32 width, uint32 height, @@ -56,76 +121,3 @@ class FCesiumTextureResourceBase : public FTextureResource { FName _lodGroupStatName; uint64 _textureSize; }; - -/** - * A Cesium texture resource that uses an already-created `FRHITexture`. This is - * used when `GRHISupportsAsyncTextureCreation` is true and so we were already - * able to create the FRHITexture in a worker thread. It is also used when a - * single glTF `Image` is referenced by multiple glTF `Texture` instances. We - * only need one `FRHITexture` is this case, but we need multiple - * `FTextureResource` instances to support the different sampler settings that - * are likely used in the different textures. - */ -class FCesiumUseExistingTextureResource : public FCesiumTextureResourceBase { -public: - FCesiumUseExistingTextureResource( - FTextureRHIRef existingTexture, - TextureGroup textureGroup, - uint32 width, - uint32 height, - EPixelFormat format, - TextureFilter filter, - TextureAddress addressX, - TextureAddress addressY, - bool sRGB, - bool useMipsIfAvailable, - uint32 extData); - - FCesiumUseExistingTextureResource( - FTextureResource* pExistingTexture, - TextureGroup textureGroup, - uint32 width, - uint32 height, - EPixelFormat format, - TextureFilter filter, - TextureAddress addressX, - TextureAddress addressY, - bool sRGB, - bool useMipsIfAvailable, - uint32 extData); - -protected: - virtual FTextureRHIRef InitializeTextureRHI() override; - -private: - FTextureResource* _pExistingTexture; -}; - -/** - * A Cesium texture resource that creates an `FRHITexture` from a glTF - * `ImageCesium` when `InitRHI` is called from the render thread. When - * `GRHISupportsAsyncTextureCreation` is false (everywhere but Direct3D), we can - * only create a `FRHITexture` on the render thread, so this is the code that - * does it. - */ -class FCesiumCreateNewTextureResource : public FCesiumTextureResourceBase { -public: - FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium&& image, - TextureGroup textureGroup, - uint32 width, - uint32 height, - EPixelFormat format, - TextureFilter filter, - TextureAddress addressX, - TextureAddress addressY, - bool sRGB, - bool useMipsIfAvailable, - uint32 extData); - -protected: - virtual FTextureRHIRef InitializeTextureRHI() override; - -private: - CesiumGltf::ImageCesium _image; -}; diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 19c3ea3f6..edad7fd09 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -10,6 +10,7 @@ #include "CesiumTextureResource.h" #include "Containers/ResourceArray.h" #include "DynamicRHI.h" +#include "ExtensionImageCesiumUnreal.h" #include "GenericPlatform/GenericPlatformProcess.h" #include "PixelFormat.h" #include "RHICommandList.h" @@ -40,69 +41,7 @@ struct ExtensionUnrealTexture { pTexture = nullptr; }; -std::optional getPixelFormatForImageCesium( - const ImageCesium& imageCesium, - const std::optional overridePixelFormat) { - if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { - switch (imageCesium.compressedPixelFormat) { - case GpuCompressedPixelFormat::ETC1_RGB: - return EPixelFormat::PF_ETC1; - break; - case GpuCompressedPixelFormat::ETC2_RGBA: - return EPixelFormat::PF_ETC2_RGBA; - break; - case GpuCompressedPixelFormat::BC1_RGB: - return EPixelFormat::PF_DXT1; - break; - case GpuCompressedPixelFormat::BC3_RGBA: - return EPixelFormat::PF_DXT5; - break; - case GpuCompressedPixelFormat::BC4_R: - return EPixelFormat::PF_BC4; - break; - case GpuCompressedPixelFormat::BC5_RG: - return EPixelFormat::PF_BC5; - break; - case GpuCompressedPixelFormat::BC7_RGBA: - return EPixelFormat::PF_BC7; - break; - case GpuCompressedPixelFormat::ASTC_4x4_RGBA: - return EPixelFormat::PF_ASTC_4x4; - break; - case GpuCompressedPixelFormat::PVRTC2_4_RGBA: - return EPixelFormat::PF_PVRTC2; - break; - case GpuCompressedPixelFormat::ETC2_EAC_R11: - return EPixelFormat::PF_ETC2_R11_EAC; - break; - case GpuCompressedPixelFormat::ETC2_EAC_RG11: - return EPixelFormat::PF_ETC2_RG11_EAC; - break; - default: - // Unsupported compressed texture format. - return std::nullopt; - }; - } else if (overridePixelFormat) { - return *overridePixelFormat; - } else { - switch (imageCesium.channels) { - case 1: - return PF_R8; - break; - case 2: - return PF_R8G8; - break; - case 3: - case 4: - default: - return PF_R8G8B8A8; - }; - } - - return std::nullopt; -} - -FTexture2DRHIRef createAsyncTextureAndWait( +FTexture2DRHIRef createAsyncTextureAndWaitOld( uint32 SizeX, uint32 SizeY, uint8 Format, @@ -169,7 +108,7 @@ FTexture2DRHIRef createAsyncTextureAndWait( * @param Whether to use a sRGB color-space. * @return The RHI texture reference. */ -FTexture2DRHIRef CreateRHITexture2D_Async( +FTexture2DRHIRef CreateRHITexture2D_AsyncOld( const CesiumGltf::ImageCesium& image, EPixelFormat format, bool sRGB) { @@ -200,7 +139,7 @@ FTexture2DRHIRef CreateRHITexture2D_Async( mipsData[i] = (void*)(&image.pixelData[mipPos.byteOffset]); } - return createAsyncTextureAndWait( + return createAsyncTextureAndWaitOld( static_cast(image.width), static_cast(image.height), format, @@ -210,7 +149,7 @@ FTexture2DRHIRef CreateRHITexture2D_Async( mipCount); } else { void* pTextureData = (void*)(image.pixelData.data()); - return createAsyncTextureAndWait( + return createAsyncTextureAndWaitOld( static_cast(image.width), static_cast(image.height), format, @@ -265,612 +204,399 @@ void ReferenceCountedUnrealTexture::setUnrealTexture( this->_pUnrealTexture = p; } -const TSharedPtr& +const FCesiumTextureResourceUniquePtr& ReferenceCountedUnrealTexture::getTextureResource() const { return this->_pTextureResource; } -TSharedPtr& +FCesiumTextureResourceUniquePtr& ReferenceCountedUnrealTexture::getTextureResource() { return this->_pTextureResource; } void ReferenceCountedUnrealTexture::setTextureResource( - TSharedPtr&& p) { + FCesiumTextureResourceUniquePtr&& p) { this->_pTextureResource = std::move(p); } -CesiumGltf::SharedAsset -ReferenceCountedUnrealTexture::getSharedImage() const { - return this->_pImageCesium; -} - -void ReferenceCountedUnrealTexture::setSharedImage( - CesiumGltf::SharedAsset& image) { - this->_pImageCesium = image; -} - -TSharedPtr createTextureResourceFromImageCesium( - CesiumGltf::ImageCesium& imageCesium, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - EPixelFormat pixelFormat) { - if (GRHISupportsAsyncTextureCreation && !imageCesium.pixelData.empty()) { - // Create RHI texture resource on this worker - // thread, and then hand it off to the renderer - // thread. - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreateRHITexture2D) - - FTexture2DRHIRef textureReference = - CreateRHITexture2D_Async(imageCesium, pixelFormat, sRGB); - TSharedPtr textureResource = - MakeShared( - textureReference, - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0); - - // Clear the now-unnecessary copy of the pixel data. - // Calling clear() isn't good enough because it - // won't actually release the memory. - std::vector pixelData; - imageCesium.pixelData.swap(pixelData); - - std::vector mipPositions; - imageCesium.mipPositions.swap(mipPositions); - - return textureResource; +std::optional getSourceIndexFromModelAndTexture( + const CesiumGltf::Model& model, + const CesiumGltf::Texture& texture) { + const CesiumGltf::ExtensionKhrTextureBasisu* pKtxExtension = + texture.getExtension(); + const CesiumGltf::ExtensionTextureWebp* pWebpExtension = + texture.getExtension(); + + int32_t source = -1; + if (pKtxExtension) { + if (pKtxExtension->source < 0 || + pKtxExtension->source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "KTX texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + pKtxExtension->source); + return std::nullopt; + } + return std::optional(pKtxExtension->source); + } else if (pWebpExtension) { + if (pWebpExtension->source < 0 || + pWebpExtension->source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "WebP texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + pWebpExtension->source); + return std::nullopt; + } + return std::optional(pWebpExtension->source); } else { - // The RHI texture will be created later on the - // render thread, directly from this texture source. - // We need valid pixelData here, though. - if (imageCesium.pixelData.empty()) { - return nullptr; + if (texture.source < 0 || texture.source >= model.images.size()) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Texture source index must be non-negative and less than %d, but is %d"), + model.images.size(), + texture.source); + return std::nullopt; } - - return MakeShared( - std::move(imageCesium), - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0); + return std::optional(texture.source); } } -static std::mutex textureResourceMutex; - -struct ExtensionUnrealTextureResource { - static inline constexpr const char* TypeName = - "ExtensionUnrealTextureResource"; - static inline constexpr const char* ExtensionName = - "PRIVATE_unreal_texture_resource"; - - ExtensionUnrealTextureResource() {} - - TSharedPtr pTextureResource = nullptr; - - // If a preprocessing step is required (such as generating mipmaps), this - // future returns the preprocessed image. If no preprocessing is required, - // this just passes the image through. - std::optional> - preprocessFuture = std::nullopt; - - static TSharedPtr loadTextureResource( - CesiumGltf::ImageCesium& imageCesium, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat) { - std::optional optionalPixelFormat = - getPixelFormatForImageCesium(imageCesium, overridePixelFormat); - if (!optionalPixelFormat.has_value()) { - return nullptr; - } - - EPixelFormat pixelFormat = optionalPixelFormat.value(); - - std::lock_guard lock(textureResourceMutex); - - ExtensionUnrealTextureResource& extension = - imageCesium.addExtension(); - - // Already have a texture resource, just use that. - if (extension.pTextureResource != nullptr) { - return MakeShared( - extension.pTextureResource.Get(), - group, - imageCesium.width, - imageCesium.height, - pixelFormat, - filter, - addressX, - addressY, - sRGB, - useMipMapsIfAvailable, - 0); - } - - // Store the current size of the pixel data, because - // we're about to clear it but we still want to have - // an accurate estimation of the size of the image for - // caching purposes. - imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - - TSharedPtr textureResource = - createTextureResourceFromImageCesium( - imageCesium, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - pixelFormat); - - check(textureResource != nullptr); - - extension.pTextureResource = textureResource; - - return textureResource; +TUniquePtr loadTextureFromModelAnyThreadPart( + CesiumGltf::Model& model, + CesiumGltf::Texture& texture, + bool sRGB) { + int64_t textureIndex = + model.textures.empty() ? -1 : &texture - &model.textures[0]; + if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { + textureIndex = -1; } - std::optional getSourceIndexFromModelAndTexture( - const CesiumGltf::Model& model, - const CesiumGltf::Texture& texture) { - const CesiumGltf::ExtensionKhrTextureBasisu* pKtxExtension = - texture.getExtension(); - const CesiumGltf::ExtensionTextureWebp* pWebpExtension = - texture.getExtension(); - - int32_t source = -1; - if (pKtxExtension) { - if (pKtxExtension->source < 0 || - pKtxExtension->source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "KTX texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - pKtxExtension->source); - return std::nullopt; - } - return std::optional(pKtxExtension->source); - } else if (pWebpExtension) { - if (pWebpExtension->source < 0 || - pWebpExtension->source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "WebP texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - pWebpExtension->source); - return std::nullopt; - } - return std::optional(pWebpExtension->source); - } else { - if (texture.source < 0 || texture.source >= model.images.size()) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "Texture source index must be non-negative and less than %d, but is %d"), - model.images.size(), - texture.source); - return std::nullopt; - } - return std::optional(texture.source); - } + ExtensionUnrealTexture& extension = + texture.addExtension(); + if (extension.pTexture && (extension.pTexture->getUnrealTexture() || + extension.pTexture->getTextureResource())) { + // There's an existing Unreal texture for this glTF texture. This will + // happen if this texture is used by multiple primitives on the same + // model. It will also be the case when this model was upsampled from a + // parent tile. + TUniquePtr pResult = MakeUnique(); + pResult->pTexture = extension.pTexture; + pResult->textureIndex = textureIndex; + return pResult; } - TUniquePtr loadTextureFromModelAnyThreadPart( - CesiumGltf::Model& model, - CesiumGltf::Texture& texture, - bool sRGB) { - int64_t textureIndex = - model.textures.empty() ? -1 : &texture - &model.textures[0]; - if (textureIndex < 0 || size_t(textureIndex) >= model.textures.size()) { - textureIndex = -1; - } - - ExtensionUnrealTexture& extension = - texture.addExtension(); - if (extension.pTexture && (extension.pTexture->getUnrealTexture() || - extension.pTexture->getTextureResource())) { - // There's an existing Unreal texture for this glTF texture. This will - // happen if this texture is used by multiple primitives on the same - // model. It will also be the case when this model was upsampled from a - // parent tile. - TUniquePtr pResult = - MakeUnique(); - pResult->pTexture = extension.pTexture; - pResult->textureIndex = textureIndex; - return pResult; - } - - std::optional optionalSourceIndex = - getSourceIndexFromModelAndTexture(model, texture); - if (!optionalSourceIndex.has_value()) { - return nullptr; - }; - - CesiumGltf::Image& image = model.images[*optionalSourceIndex]; - const CesiumGltf::Sampler& sampler = - model.getSafe(model.samplers, texture.sampler); + std::optional optionalSourceIndex = + getSourceIndexFromModelAndTexture(model, texture); + if (!optionalSourceIndex.has_value()) { + return nullptr; + }; - TUniquePtr result = - loadTextureFromImageAndSamplerAnyThreadPart( - image.cesium, - sampler, - sRGB); + CesiumGltf::Image& image = model.images[*optionalSourceIndex]; + const CesiumGltf::Sampler& sampler = + model.getSafe(model.samplers, texture.sampler); - if (result) { - extension.pTexture = result->pTexture; - result->textureIndex = textureIndex; - } + TUniquePtr result = + loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); - return result; + if (result) { + extension.pTexture = result->pTexture; + result->textureIndex = textureIndex; } - TextureFilter - getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { - // Unreal Engine's available filtering modes are only nearest, bilinear, - // trilinear, and "default". Default means "use the texture group settings", - // and the texture group settings are defined in a config file and can - // vary per platform. All filter modes can use mipmaps if they're available, - // but only TF_Default will ever use anisotropic texture filtering. - // - // Unreal also doesn't separate the minification filter from the - // magnification filter. So we'll just ignore the magFilter unless it's the - // only filter specified. - // - // Generally our bias is toward TF_Default, because that gives the user more - // control via texture groups. - - if (sampler.magFilter && !sampler.minFilter) { - // Only a magnification filter is specified, so use it. - return sampler.magFilter.value() == - CesiumGltf::Sampler::MagFilter::NEAREST - ? TextureFilter::TF_Nearest - : TextureFilter::TF_Default; - } else if (sampler.minFilter) { - // Use specified minFilter. - switch (sampler.minFilter.value()) { - case CesiumGltf::Sampler::MinFilter::NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - return TextureFilter::TF_Nearest; - case CesiumGltf::Sampler::MinFilter::LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - return TextureFilter::TF_Bilinear; - default: - return TextureFilter::TF_Default; - } - } else { - // No filtering specified at all, let the texture group decide. + return result; +} + +TextureFilter getTextureFilterFromSampler(const CesiumGltf::Sampler& sampler) { + // Unreal Engine's available filtering modes are only nearest, bilinear, + // trilinear, and "default". Default means "use the texture group settings", + // and the texture group settings are defined in a config file and can + // vary per platform. All filter modes can use mipmaps if they're available, + // but only TF_Default will ever use anisotropic texture filtering. + // + // Unreal also doesn't separate the minification filter from the + // magnification filter. So we'll just ignore the magFilter unless it's the + // only filter specified. + // + // Generally our bias is toward TF_Default, because that gives the user more + // control via texture groups. + + if (sampler.magFilter && !sampler.minFilter) { + // Only a magnification filter is specified, so use it. + return sampler.magFilter.value() == CesiumGltf::Sampler::MagFilter::NEAREST + ? TextureFilter::TF_Nearest + : TextureFilter::TF_Default; + } else if (sampler.minFilter) { + // Use specified minFilter. + switch (sampler.minFilter.value()) { + case CesiumGltf::Sampler::MinFilter::NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + return TextureFilter::TF_Nearest; + case CesiumGltf::Sampler::MinFilter::LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + return TextureFilter::TF_Bilinear; + default: return TextureFilter::TF_Default; } + } else { + // No filtering specified at all, let the texture group decide. + return TextureFilter::TF_Default; } +} - bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { - switch (sampler.minFilter.value_or( - CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - return true; - break; - default: // LINEAR and NEAREST - return false; - } +bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { + switch (sampler.minFilter.value_or( + CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: + case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: + return true; + break; + default: // LINEAR and NEAREST + return false; } +} + +TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( + CesiumGltf::SharedAsset& image, + const CesiumGltf::Sampler& sampler, + bool sRGB) { + return loadTextureAnyThreadPart( + image, + convertGltfWrapSToUnreal(sampler.wrapS), + convertGltfWrapTToUnreal(sampler.wrapT), + getTextureFilterFromSampler(sampler), + getUseMipmapsIfAvailableFromSampler(sampler), + // TODO: allow texture group to be configured on Cesium3DTileset. + TEXTUREGROUP_World, + sRGB, + std::nullopt); +} - TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::SharedAsset& image, - const CesiumGltf::Sampler& sampler, - bool sRGB) { - return loadTextureAnyThreadPart( - image, - convertGltfWrapSToUnreal(sampler.wrapS), - convertGltfWrapTToUnreal(sampler.wrapT), - getTextureFilterFromSampler(sampler), - getUseMipmapsIfAvailableFromSampler(sampler), - // TODO: allow texture group to be configured on Cesium3DTileset. - TEXTUREGROUP_World, - sRGB, - std::nullopt); +static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { + if (!pHalfLoadedTexture || !pHalfLoadedTexture->pTexture) { + return nullptr; } - static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { - if (!pHalfLoadedTexture || !pHalfLoadedTexture->pTexture) { - return nullptr; - } + UTexture2D* pTexture = pHalfLoadedTexture->pTexture->getUnrealTexture(); + if (!pTexture) { + pTexture = NewObject( + GetTransientPackage(), + MakeUniqueObjectName( + GetTransientPackage(), + UTexture2D::StaticClass(), + "CesiumRuntimeTexture"), + RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); + + pTexture->AddressX = pHalfLoadedTexture->addressX; + pTexture->AddressY = pHalfLoadedTexture->addressY; + pTexture->Filter = pHalfLoadedTexture->filter; + pTexture->LODGroup = pHalfLoadedTexture->group; + pTexture->SRGB = pHalfLoadedTexture->sRGB; + + pTexture->NeverStream = true; + + pHalfLoadedTexture->pTexture->setUnrealTexture(pTexture); + } - UTexture2D* pTexture = pHalfLoadedTexture->pTexture->getUnrealTexture(); - if (!pTexture) { - pTexture = NewObject( - GetTransientPackage(), - MakeUniqueObjectName( - GetTransientPackage(), - UTexture2D::StaticClass(), - "CesiumRuntimeTexture"), - RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); - - pTexture->AddressX = pHalfLoadedTexture->addressX; - pTexture->AddressY = pHalfLoadedTexture->addressY; - pTexture->Filter = pHalfLoadedTexture->filter; - pTexture->LODGroup = pHalfLoadedTexture->group; - pTexture->SRGB = pHalfLoadedTexture->sRGB; - - pTexture->NeverStream = true; - - pHalfLoadedTexture->pTexture->setUnrealTexture(pTexture); - } + return pTexture; +} - return pTexture; +TUniquePtr loadTextureAnyThreadPart( + CesiumGltf::SharedAsset& image, + TextureAddress addressX, + TextureAddress addressY, + TextureFilter filter, + bool useMipMapsIfAvailable, + TextureGroup group, + bool sRGB, + std::optional overridePixelFormat) { + // The FCesiumTextureResource for the ImageCesium should already be created at + // this point, if it can be. + ExtensionImageCesiumUnreal* pExtension = + image->getExtension(); + if (pExtension == nullptr || pExtension->getTextureResource() == nullptr) { + return nullptr; } - TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::SharedAsset& image, - TextureAddress addressX, - TextureAddress addressY, - TextureFilter filter, - bool useMipMapsIfAvailable, - TextureGroup group, - bool sRGB, - std::optional overridePixelFormat) { - TSharedPtr textureResource = - ExtensionUnrealTextureResource::loadTextureResource( - *image, - addressX, - addressY, - filter, - useMipMapsIfAvailable, - group, - sRGB, - overridePixelFormat); + auto pResource = FCesiumTextureResource::CreateWrapped( + pExtension->getTextureResource(), + group, + filter, + addressX, + addressY, + sRGB, + useMipMapsIfAvailable); + + TUniquePtr pResult = MakeUnique(); + pResult->pTexture = new ReferenceCountedUnrealTexture(); + + pResult->addressX = addressX; + pResult->addressY = addressY; + pResult->filter = filter; + pResult->group = group; + pResult->sRGB = sRGB; + pResult->pTexture->setTextureResource(MoveTemp(pResource)); + + return pResult; +} - TUniquePtr pResult = MakeUnique(); - pResult->pTexture = new ReferenceCountedUnrealTexture(); +CesiumUtility::IntrusivePointer +loadTextureGameThreadPart( + CesiumGltf::Model& model, + LoadedTextureResult* pHalfLoadedTexture) { + if (pHalfLoadedTexture == nullptr) + return nullptr; - pResult->addressX = addressX; - pResult->addressY = addressY; - pResult->filter = filter; - pResult->group = group; - pResult->sRGB = sRGB; + CesiumUtility::IntrusivePointer pResult = + loadTextureGameThreadPart(pHalfLoadedTexture); - pResult->pTexture->setTextureResource(MoveTemp(textureResource)); - pResult->pTexture->setSharedImage(image); - return pResult; + if (pResult && pHalfLoadedTexture && pHalfLoadedTexture->textureIndex >= 0 && + size_t(pHalfLoadedTexture->textureIndex) < model.textures.size()) { + CesiumGltf::Texture& texture = + model.textures[pHalfLoadedTexture->textureIndex]; + ExtensionUnrealTexture& extension = + texture.addExtension(); + extension.pTexture = pHalfLoadedTexture->pTexture; } - CesiumUtility::IntrusivePointer - loadTextureGameThreadPart( - CesiumGltf::Model& model, - LoadedTextureResult* pHalfLoadedTexture) { - if (pHalfLoadedTexture == nullptr) - return nullptr; - - CesiumUtility::IntrusivePointer pResult = - loadTextureGameThreadPart(pHalfLoadedTexture); - - if (pResult && pHalfLoadedTexture && - pHalfLoadedTexture->textureIndex >= 0 && - size_t(pHalfLoadedTexture->textureIndex) < model.textures.size()) { - CesiumGltf::Texture& texture = - model.textures[pHalfLoadedTexture->textureIndex]; - ExtensionUnrealTexture& extension = - texture.addExtension(); - extension.pTexture = pHalfLoadedTexture->pTexture; - } + return pHalfLoadedTexture->pTexture; +} +CesiumUtility::IntrusivePointer +loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadTexture) + + FCesiumTextureResourceUniquePtr& pTextureResource = + pHalfLoadedTexture->pTexture->getTextureResource(); + if (pTextureResource == nullptr) { + // Texture is already loaded (or unloadable). return pHalfLoadedTexture->pTexture; } - CesiumUtility::IntrusivePointer - loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadTexture) - - TSharedPtr& pTextureResource = - pHalfLoadedTexture->pTexture->getTextureResource(); - if (pTextureResource == nullptr) { - // Texture is already loaded (or unloadable). - return pHalfLoadedTexture->pTexture; - } - - UTexture2D* pTexture = CreateTexture2D(pHalfLoadedTexture); - if (pTexture == nullptr) { - return nullptr; - } + UTexture2D* pTexture = CreateTexture2D(pHalfLoadedTexture); + if (pTexture == nullptr) { + return nullptr; + } - FCesiumTextureResourceBase* pCesiumTextureResource = - pTextureResource.Release(); - if (pCesiumTextureResource) { - pTexture->SetResource(pCesiumTextureResource); + if (pTextureResource) { + // Give the UTexture2D exclusive ownership of this FCesiumTextureResource. + pTexture->SetResource(pTextureResource.Release()); - ENQUEUE_RENDER_COMMAND(Cesium_InitResource) - ([pTexture, - pCesiumTextureResource](FRHICommandListImmediate& RHICmdList) { - pCesiumTextureResource->SetTextureReference( - pTexture->TextureReference.TextureReferenceRHI); + ENQUEUE_RENDER_COMMAND(Cesium_InitResource) + ([pTexture, pTextureResource = pTexture->GetResource()]( + FRHICommandListImmediate& RHICmdList) { + pTextureResource->SetTextureReference( + pTexture->TextureReference.TextureReferenceRHI); #if ENGINE_VERSION_5_3_OR_HIGHER - pCesiumTextureResource->InitResource( - FRHICommandListImmediate::Get()); // Init Resource now requires a - // command list. + pCesiumTextureResource->InitResource( + FRHICommandListImmediate::Get()); // Init Resource now requires a + // command list. #else - pCesiumTextureResource->InitResource(); + pTextureResource->InitResource(); #endif - }); - } - - return pHalfLoadedTexture->pTexture; + }); } - TSharedPtr createTextureResource( - CesiumGltf::ImageCesium& imageCesium, - bool sRGB, - std::optional overridePixelFormat) { - std::optional optionalPixelFormat = - getPixelFormatForImageCesium(imageCesium, overridePixelFormat); - if (!optionalPixelFormat.has_value()) { - return nullptr; - } + return pHalfLoadedTexture->pTexture; +} - EPixelFormat pixelFormat = optionalPixelFormat.value(); - - // Store the current size of the pixel data, because - // we're about to clear it but we still want to have - // an accurate estimation of the size of the image for - // caching purposes. - imageCesium.sizeBytes = int64_t(imageCesium.pixelData.size()); - - return createTextureResourceFromImageCesium( - imageCesium, - TextureAddress::TA_Wrap, - TextureAddress::TA_Wrap, - TextureFilter::TF_Default, - true, - TextureGroup::TEXTUREGROUP_World, - sRGB, - pixelFormat); - } - - TextureAddress convertGltfWrapSToUnreal(int32_t wrapS) { - // glTF spec: "When undefined, a sampler with repeat wrapping and auto - // filtering should be used." - switch (wrapS) { - case CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE: - return TextureAddress::TA_Clamp; - case CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT: - return TextureAddress::TA_Mirror; - case CesiumGltf::Sampler::WrapS::REPEAT: - default: - return TextureAddress::TA_Wrap; - } +TextureAddress convertGltfWrapSToUnreal(int32_t wrapS) { + // glTF spec: "When undefined, a sampler with repeat wrapping and auto + // filtering should be used." + switch (wrapS) { + case CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE: + return TextureAddress::TA_Clamp; + case CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT: + return TextureAddress::TA_Mirror; + case CesiumGltf::Sampler::WrapS::REPEAT: + default: + return TextureAddress::TA_Wrap; } +} - TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { - // glTF spec: "When undefined, a sampler with repeat wrapping and auto - // filtering should be used." - switch (wrapT) { - case CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE: - return TextureAddress::TA_Clamp; - case CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT: - return TextureAddress::TA_Mirror; - case CesiumGltf::Sampler::WrapT::REPEAT: - default: - return TextureAddress::TA_Wrap; - } +TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { + // glTF spec: "When undefined, a sampler with repeat wrapping and auto + // filtering should be used." + switch (wrapT) { + case CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE: + return TextureAddress::TA_Clamp; + case CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT: + return TextureAddress::TA_Mirror; + case CesiumGltf::Sampler::WrapT::REPEAT: + default: + return TextureAddress::TA_Wrap; } +} - std::optional> - createMipMapsForSampler( - const CesiumAsync::AsyncSystem& asyncSystem, - const CesiumGltf::Sampler& sampler, - CesiumGltf::ImageCesium& image) { - std::unique_lock lock(textureResourceMutex); - - ExtensionUnrealTextureResource& extension = - image.addExtension(); - - // Future already exists, we don't need to do anything else. - if (extension.preprocessFuture.has_value()) { - return extension.preprocessFuture.value(); - } - - // Generate mipmaps if needed. - // An image needs mipmaps generated for it if: - // 1. It is used by a Texture that has a Sampler with a mipmap filtering - // mode, and - // 2. It does not already have mipmaps. - // It's ok if an image has mipmaps even if not all textures will use them. - // There's no reason to have two RHI textures, one with and one without - // mips. - bool needsMipmaps; - switch (sampler.minFilter.value_or( - CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_NEAREST: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: - case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: - needsMipmaps = true; +std::optional getPixelFormatForImageCesium( + const ImageCesium& imageCesium, + const std::optional overridePixelFormat) { + if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { + switch (imageCesium.compressedPixelFormat) { + case GpuCompressedPixelFormat::ETC1_RGB: + return EPixelFormat::PF_ETC1; break; - default: // LINEAR and NEAREST - needsMipmaps = false; + case GpuCompressedPixelFormat::ETC2_RGBA: + return EPixelFormat::PF_ETC2_RGBA; break; - } - - if (!needsMipmaps || image.pixelData.empty()) { - // If we don't need mipmaps, we don't want to create a future for them. - // This allows a future sampler using this image that does need mipmaps to - // generate them. + case GpuCompressedPixelFormat::BC1_RGB: + return EPixelFormat::PF_DXT1; + break; + case GpuCompressedPixelFormat::BC3_RGBA: + return EPixelFormat::PF_DXT5; + break; + case GpuCompressedPixelFormat::BC4_R: + return EPixelFormat::PF_BC4; + break; + case GpuCompressedPixelFormat::BC5_RG: + return EPixelFormat::PF_BC5; + break; + case GpuCompressedPixelFormat::BC7_RGBA: + return EPixelFormat::PF_BC7; + break; + case GpuCompressedPixelFormat::ASTC_4x4_RGBA: + return EPixelFormat::PF_ASTC_4x4; + break; + case GpuCompressedPixelFormat::PVRTC2_4_RGBA: + return EPixelFormat::PF_PVRTC2; + break; + case GpuCompressedPixelFormat::ETC2_EAC_R11: + return EPixelFormat::PF_ETC2_R11_EAC; + break; + case GpuCompressedPixelFormat::ETC2_EAC_RG11: + return EPixelFormat::PF_ETC2_RG11_EAC; + break; + default: + // Unsupported compressed texture format. return std::nullopt; - } - - CesiumAsync::Promise promise = - asyncSystem.createPromise(); - - extension.preprocessFuture = promise.getFuture().share(); - - lock.unlock(); - - // We need mipmaps generated. - std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(image); - if (errorMessage) { - UE_LOG( - LogCesium, - Warning, - TEXT("%s"), - UTF8_TO_TCHAR(errorMessage->c_str())); - } - promise.resolve(&image); - return *extension.preprocessFuture; + }; + } else if (overridePixelFormat) { + return *overridePixelFormat; + } else { + switch (imageCesium.channels) { + case 1: + return PF_R8; + break; + case 2: + return PF_R8G8; + break; + case 3: + case 4: + default: + return PF_R8G8B8A8; + }; } - CesiumAsync::SharedFuture createMipMapsForAllTextures( - const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::Model& model) { - std::vector> futures; - for (const Texture& texture : model.textures) { - const CesiumGltf::Sampler& sampler = - model.getSafe(model.samplers, texture.sampler); - std::optional> - optionalFuture = createMipMapsForSampler( - asyncSystem, - sampler, - *model.images[texture.source].cesium); - if (optionalFuture.has_value()) { - futures.push_back(optionalFuture.value()); - } - } - - return asyncSystem.all(std::move(futures)) - .thenImmediately( - []([[maybe_unused]] std::vector< - CesiumGltf::ImageCesium*>&& /*results*/) -> void {}) - .share(); - } + return std::nullopt; +} } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 767b6da4e..e037c4e95 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -47,18 +47,13 @@ struct ReferenceCountedUnrealTexture void setUnrealTexture(const TObjectPtr& p); // The renderer / RHI FTextureResource holding the pixel data. - const TSharedPtr& getTextureResource() const; - TSharedPtr& getTextureResource(); - void setTextureResource(TSharedPtr&& p); - - /// The SharedAsset that this texture was created from. - CesiumGltf::SharedAsset getSharedImage() const; - void setSharedImage(CesiumGltf::SharedAsset& image); + const FCesiumTextureResourceUniquePtr& getTextureResource() const; + FCesiumTextureResourceUniquePtr& getTextureResource(); + void setTextureResource(FCesiumTextureResourceUniquePtr&& p); private: TObjectPtr _pUnrealTexture; - TSharedPtr _pTextureResource; - CesiumGltf::SharedAsset _pImageCesium; + FCesiumTextureResourceUniquePtr _pTextureResource; }; /** @@ -191,7 +186,7 @@ loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture); * intended to be later used with `FCesiumUseExistingTextureResource`, which * will supply sampler, texture group, and other settings. */ -TSharedPtr createTextureResource( +TSharedPtr createTextureResource( CesiumGltf::ImageCesium& imageCesium, bool sRGB, std::optional overridePixelFormat); @@ -216,8 +211,8 @@ TextureAddress convertGltfWrapSToUnreal(int32_t wrapS); */ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT); -CesiumAsync::SharedFuture createMipMapsForAllTextures( - const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::Model& model); +std::optional getPixelFormatForImageCesium( + const CesiumGltf::ImageCesium& imageCesium, + const std::optional overridePixelFormat); } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp new file mode 100644 index 000000000..e77409a24 --- /dev/null +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp @@ -0,0 +1,101 @@ +#include "ExtensionImageCesiumUnreal.h" +#include "CesiumRuntime.h" +#include "CesiumTextureUtility.h" +#include +#include + +using namespace CesiumAsync; +using namespace CesiumGltf; +using namespace CesiumGltfReader; + +namespace { + +std::mutex createExtensionMutex; + +std::pair>> +getOrCreateImageFuture( + const AsyncSystem& asyncSystem, + ImageCesium& imageCesium); + +} // namespace + +/*static*/ const ExtensionImageCesiumUnreal& +ExtensionImageCesiumUnreal::GetOrCreate( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::ImageCesium& imageCesium, + bool needsMipMaps, + const std::optional& overridePixelFormat) { + auto [extension, maybePromise] = + getOrCreateImageFuture(asyncSystem, imageCesium); + if (!maybePromise) { + // Another thread is already working on this image. + return extension; + } + + // Proceed to load the image in this thread. + TUniquePtr pResource = + FCesiumTextureResource::CreateNew( + imageCesium, + TextureGroup::TEXTUREGROUP_World, + overridePixelFormat, + TextureFilter::TF_Default, + TextureAddress::TA_Clamp, + TextureAddress::TA_Clamp, + false, + needsMipMaps); + + extension._pTextureResource = + MakeShareable(pResource.Release(), [](FCesiumTextureResource* p) { + FCesiumTextureResource ::Destroy(p); + }); + + maybePromise->resolve(); + + return extension; +} + +ExtensionImageCesiumUnreal::ExtensionImageCesiumUnreal( + const CesiumAsync::SharedFuture& future) + : _pTextureResource(nullptr), _futureCreateResource(future) {} + +const TSharedPtr& +ExtensionImageCesiumUnreal::getTextureResource() const { + return this->_pTextureResource; +} + +CesiumAsync::SharedFuture& ExtensionImageCesiumUnreal::getFuture() { + return this->_futureCreateResource; +} + +const CesiumAsync::SharedFuture& +ExtensionImageCesiumUnreal::getFuture() const { + return this->_futureCreateResource; +} + +namespace { + +// Returns a Future that will resolve when the image is loaded. It _may_ also +// return a Promise, in which case the calling thread is responsible for doing +// the loading and should resolve the Promise when it's done. +std::pair>> +getOrCreateImageFuture( + const AsyncSystem& asyncSystem, + ImageCesium& imageCesium) { + std::scoped_lock lock(createExtensionMutex); + + ExtensionImageCesiumUnreal* pExtension = + imageCesium.getExtension(); + if (!pExtension) { + // This thread will work on this image. + Promise promise = asyncSystem.createPromise(); + ExtensionImageCesiumUnreal& extension = + imageCesium.addExtension( + promise.getFuture().share()); + return {extension, std::move(promise)}; + } else { + // Another thread is already working on this image. + return {*pExtension, std::nullopt}; + } +} + +} // namespace diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h new file mode 100644 index 000000000..84b7618a6 --- /dev/null +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h @@ -0,0 +1,68 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CesiumTextureResource.h" +#include "PixelFormat.h" +#include "Templates/SharedPointer.h" +#include +#include + +namespace CesiumGltf { +struct ImageCesium; +} + +/** + * @brief An extension attached to an ImageCesium in order to hold + * Unreal-specific information about it. + * + * ImageCesium instances are shared between multiple textures on a single model, + * and even between models in some cases, but we strive to have only one copy of + * the image bytes in GPU memory. + * + * The Unreal / GPU resource is held in `pTextureResource`, which may be either + * a `FCesiumCreateNewTextureResource` or a `FCesiumUseExistingTextureResource` + * depending on how it was created. We'll never actually sample directly from + * this resource, however. Instead, a separate + * `FCesiumUseExistingTextureResource` will be created for each glTF Texture + * that references this image, and it will point to the instance managed by this + * extension. + * + * Because we'll never be sampling from this texture resource, the texture + * filtering and addressing parameters have default values. + */ +struct ExtensionImageCesiumUnreal { + static inline constexpr const char* TypeName = "ExtensionImageCesiumUnreal"; + static inline constexpr const char* ExtensionName = + "PRIVATE_ImageCesium_Unreal"; + + /** + * @brief Gets an Unreal texture resource from the given `ImageCesium`, + * creating it if necessary. + * + * When this function is called for the first time on a particular + * `ImageCesium`, the asynchronous process to create an Unreal + * `FTextureResource` from it is kicked off. On successive invocations + * (perhaps from other threads), the existing instance is returned. It is safe + * to call this method on the same `ImageCesium` instance from multiple + * threads simultaneously. + * + * To determine if the asynchronous `FTextureResource` creation process has + * completed, use {@link getFuture}. + */ + static const ExtensionImageCesiumUnreal& GetOrCreate( + const CesiumAsync::AsyncSystem& asyncSystem, + CesiumGltf::ImageCesium& imageCesium, + bool needsMipMaps, + const std::optional& overridePixelFormat); + + ExtensionImageCesiumUnreal(const CesiumAsync::SharedFuture& future); + + const TSharedPtr& getTextureResource() const; + CesiumAsync::SharedFuture& getFuture(); + const CesiumAsync::SharedFuture& getFuture() const; + +private: + TSharedPtr _pTextureResource; + CesiumAsync::SharedFuture _futureCreateResource; +}; diff --git a/extern/cesium-native b/extern/cesium-native index 6de6b3860..86995ffd4 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 6de6b386035be9cee3e4ede5c50391acd27f921e +Subproject commit 86995ffd49c33774ea3a7b6f3613cfeebd402e2e From ceca50472d56c6d66d9f442f786c5ef46ddb6689 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 14:57:30 +1000 Subject: [PATCH 27/68] Undo accidental rename. --- Source/CesiumRuntime/Private/CesiumGltfComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index e8d2c5620..cc535c220 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1062,7 +1062,7 @@ std::string constrainLength(const std::string& s, const size_t maxLength) { } /** - * @brief CreateNew an FName from the given strings. + * @brief Create an FName from the given strings. * * This will combine the prefix and the suffix and create an FName. * If the string would be longer than the given length, then From 306728406c5928ecd3b9ece0d4d7ea1fc8bcf7b2 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 15:47:22 +1000 Subject: [PATCH 28/68] Use correct sRGB setting when initially creating texture. --- Source/CesiumRuntime/Private/CesiumGltfTextures.cpp | 12 +++++++----- .../Private/ExtensionImageCesiumUnreal.cpp | 3 ++- .../Private/ExtensionImageCesiumUnreal.h | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index 95cfe003c..d7147b797 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -201,11 +201,13 @@ SharedFuture createTextureInLoadThread( check(pTexture->source >= 0 && pTexture->source < imageNeedsMipmaps.size()); bool needsMips = imageNeedsMipmaps[pTexture->source]; - const ExtensionImageCesiumUnreal& extension = ExtensionImageCesiumUnreal::GetOrCreate( - asyncSystem, - *pImage->cesium, - needsMips, - std::nullopt); + const ExtensionImageCesiumUnreal& extension = + ExtensionImageCesiumUnreal::GetOrCreate( + asyncSystem, + *pImage->cesium, + sRGB, + needsMips, + std::nullopt); return extension.getFuture(); } diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp index e77409a24..f24698f52 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp @@ -23,6 +23,7 @@ getOrCreateImageFuture( ExtensionImageCesiumUnreal::GetOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::ImageCesium& imageCesium, + bool sRGB, bool needsMipMaps, const std::optional& overridePixelFormat) { auto [extension, maybePromise] = @@ -41,7 +42,7 @@ ExtensionImageCesiumUnreal::GetOrCreate( TextureFilter::TF_Default, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, - false, + sRGB, needsMipMaps); extension._pTextureResource = diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h index 84b7618a6..d01ece599 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h @@ -53,6 +53,7 @@ struct ExtensionImageCesiumUnreal { static const ExtensionImageCesiumUnreal& GetOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::ImageCesium& imageCesium, + bool sRGB, bool needsMipMaps, const std::optional& overridePixelFormat); From aa58bd932bdd8acb2c2ff854a2fe5bcfcf77a7b8 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 16:28:20 +1000 Subject: [PATCH 29/68] Fix UE 5.3/5.4 compile error. --- Source/CesiumRuntime/Private/CesiumTextureResource.cpp | 2 +- Source/CesiumRuntime/Private/CesiumTextureUtility.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index b3ca34a8b..7fa9cd680 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -461,7 +461,7 @@ FCesiumTextureResource::FCesiumTextureResource( } #if ENGINE_VERSION_5_3_OR_HIGHER -void FCesiumTextureResourceBase::InitRHI(FRHICommandListBase& RHICmdList) { +void FCesiumTextureResource::InitRHI(FRHICommandListBase& RHICmdList) { #else void FCesiumTextureResource::InitRHI() { #endif diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index e037c4e95..c37fd3e05 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -182,7 +182,7 @@ CesiumUtility::IntrusivePointer loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture); /** - * Creates a `FCesiumTextureResourceBase` for an image. This texture resource is + * Creates a `FCesiumTextureResource` for an image. This texture resource is * intended to be later used with `FCesiumUseExistingTextureResource`, which * will supply sampler, texture group, and other settings. */ From 1246c2d8f787ddd76386b639ee160c5578f2ac03 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 17:13:05 +1000 Subject: [PATCH 30/68] Fix raster overlays and water mask. --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 23 ++++++++++--- .../Private/CesiumEncodedFeaturesMetadata.cpp | 6 ++-- .../Private/CesiumEncodedMetadataUtility.cpp | 6 ++-- .../Private/CesiumGltfTextures.cpp | 32 +++++++++++++++++++ .../Private/CesiumTextureUtility.cpp | 11 ++++--- .../Private/CesiumTextureUtility.h | 28 ++++------------ .../Tests/CesiumTextureUtility.spec.cpp | 6 ++-- 7 files changed, 72 insertions(+), 40 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index af8a85824..2cd14ad49 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -41,6 +41,7 @@ #include "Engine/TextureRenderTarget2D.h" #include "Engine/World.h" #include "EngineUtils.h" +#include "ExtensionImageCesiumUnreal.h" #include "GameFramework/PlayerController.h" #include "Kismet/GameplayStatics.h" #include "LevelSequenceActor.h" @@ -877,19 +878,31 @@ class UnrealResourcePreparer } } - CesiumGltf::SharedAsset imageAsset( - std::move(image)); + // TODO: sRGB should probably be configurable on the raster overlay. + bool sRGB = true; + + const ExtensionImageCesiumUnreal& extension = + ExtensionImageCesiumUnreal::GetOrCreate( + CesiumAsync::AsyncSystem(nullptr), // TODO + image, + sRGB, + pOptions->useMipmaps, + std::nullopt); + + // Because raster overlay images are never shared (at least currently!), the + // future should already be resolved by the time we get here. + check(extension.getFuture().isReady()); auto texture = CesiumTextureUtility::loadTextureAnyThreadPart( - imageAsset, + image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, pOptions->filter, pOptions->useMipmaps, pOptions->group, - // TODO: sRGB should probably be configurable on the raster overlay. - true, + sRGB, std::nullopt); + return texture.Release(); } diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index c4b9963b2..475729dc8 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -147,7 +147,7 @@ std::optional encodeFeatureIdTexture( CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pFeatureIdImage)); encodedFeatureIdTexture.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - imageCopy, + *imageCopy, addressX, addressY, TextureFilter::TF_Nearest, @@ -534,7 +534,7 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( } encodedProperty.pTexture = loadTextureAnyThreadPart( - image, + *image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -661,7 +661,7 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); encodedProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - imageCopy, + *imageCopy, addressX, addressY, // TODO: account for texture filter diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index 7c36b7e70..bc54d4249 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -278,7 +278,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } encodedProperty.pTexture = loadTextureAnyThreadPart( - image, + *image, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -414,7 +414,7 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); encodedFeatureTextureProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - imageCopy, + *imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -521,7 +521,7 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( CesiumGltf::ImageCesium(*pFeatureIdImage)); encodedFeatureIdTexture.pTexture = MakeShared( std::move(*loadTextureAnyThreadPart( - imageCopy, + *imageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index d7147b797..0f022414e 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -116,6 +116,38 @@ SharedFuture createTextureInLoadThread( *pMaterial->occlusionTexture, false, imageNeedsMipmaps)); + + // Initialize water mask if needed. + auto onlyWaterIt = primitive.extras.find("OnlyWater"); + auto onlyLandIt = primitive.extras.find("OnlyLand"); + if (onlyWaterIt != primitive.extras.end() && + onlyWaterIt->second.isBool() && + onlyLandIt != primitive.extras.end() && + onlyLandIt->second.isBool()) { + bool onlyWater = onlyWaterIt->second.getBoolOrDefault(false); + bool onlyLand = onlyLandIt->second.getBoolOrDefault(true); + + if (!onlyWater && !onlyLand) { + // We have to use the water mask + auto waterMaskTextureIdIt = primitive.extras.find("WaterMaskTex"); + if (waterMaskTextureIdIt != primitive.extras.end() && + waterMaskTextureIdIt->second.isInt64()) { + int32_t waterMaskTextureId = static_cast( + waterMaskTextureIdIt->second.getInt64OrDefault(-1)); + TextureInfo waterMaskInfo; + waterMaskInfo.index = waterMaskTextureId; + if (waterMaskTextureId >= 0 && + waterMaskTextureId < gltf.textures.size()) { + futures.emplace_back(createTextureInLoadThread( + asyncSystem, + gltf, + waterMaskInfo, + false, + imageNeedsMipmaps)); + } + } + } + } }); return asyncSystem.all(std::move(futures)); diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index edad7fd09..81139f54e 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -304,7 +304,7 @@ TUniquePtr loadTextureFromModelAnyThreadPart( model.getSafe(model.samplers, texture.sampler); TUniquePtr result = - loadTextureFromImageAndSamplerAnyThreadPart(image.cesium, sampler, sRGB); + loadTextureFromImageAndSamplerAnyThreadPart(*image.cesium, sampler, sRGB); if (result) { extension.pTexture = result->pTexture; @@ -366,7 +366,7 @@ bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { } TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::SharedAsset& image, + const CesiumGltf::ImageCesium& image, const CesiumGltf::Sampler& sampler, bool sRGB) { return loadTextureAnyThreadPart( @@ -411,7 +411,7 @@ static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { } TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::SharedAsset& image, + const CesiumGltf::ImageCesium& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -421,8 +421,9 @@ TUniquePtr loadTextureAnyThreadPart( std::optional overridePixelFormat) { // The FCesiumTextureResource for the ImageCesium should already be created at // this point, if it can be. - ExtensionImageCesiumUnreal* pExtension = - image->getExtension(); + const ExtensionImageCesiumUnreal* pExtension = + image.getExtension(); + check(pExtension != nullptr); if (pExtension == nullptr || pExtension->getTextureResource() == nullptr) { return nullptr; } diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index c37fd3e05..ff250ac44 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -117,16 +117,16 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * and can be empty. */ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::SharedAsset& image, + const CesiumGltf::ImageCesium& image, const CesiumGltf::Sampler& sampler, bool sRGB); /** * @brief Does the asynchronous part of renderer resource preparation for - * this image. Should be called in a background thread. - * - * The `pixelData` will be removed from the image so that it can be - * passed to Unreal's renderer thread without copying it. + * a texture. The given image _must_ be prepared before calling this method by + * calling {@link ExtensionImageCesiumUnreal::GetOrCreate} and then waiting + * for {@link ExtensionImageCesiumUnreal::getFuture} to resolve. This method + * should be called in a background thread. * * @param imageCesium The image. * @param addressX The X addressing mode. @@ -138,14 +138,10 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * @param sRGB Whether this texture uses a sRGB color space. * @param overridePixelFormat The explicit pixel format to use. If std::nullopt, * the pixel format is inferred from the image. - * @param pExistingImageResource An existing RHI texture resource that has been - * created for this image, or nullptr if one hasn't been created yet. When this - * parameter is not nullptr, the provided image's `pixelData` is not required - * and can be empty. - * @return A future that resolves to the loaded texture. + * @return The loaded texture. */ TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::SharedAsset& image, + const CesiumGltf::ImageCesium& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -181,16 +177,6 @@ loadTextureGameThreadPart( CesiumUtility::IntrusivePointer loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture); -/** - * Creates a `FCesiumTextureResource` for an image. This texture resource is - * intended to be later used with `FCesiumUseExistingTextureResource`, which - * will supply sampler, texture group, and other settings. - */ -TSharedPtr createTextureResource( - CesiumGltf::ImageCesium& imageCesium, - bool sRGB, - std::optional overridePixelFormat); - /** * @brief Convert a glTF {@link CesiumGltf::Sampler::WrapS} value to an Unreal * `TextureAddress` value. diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 515bdc0fe..0e9cac98f 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -105,7 +105,7 @@ void CesiumTextureUtilitySpec::Define() { void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium non-sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - imageCesium, + *imageCesium, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, TextureFilter::TF_Bilinear, @@ -129,7 +129,7 @@ void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - imageCesium, + *imageCesium, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, TextureFilter::TF_Trilinear, @@ -160,7 +160,7 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( - imageCesium, + *imageCesium, sampler, false); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); From b75d27441c478b0c68be4e40159f69510102b3bb Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 18:11:17 +1000 Subject: [PATCH 31/68] GetOrCreate -> getOrCreate. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 2 +- Source/CesiumRuntime/Private/CesiumGltfTextures.cpp | 2 +- Source/CesiumRuntime/Private/CesiumTextureUtility.h | 2 +- Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp | 2 +- Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 2cd14ad49..3e2dcc9ef 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -882,7 +882,7 @@ class UnrealResourcePreparer bool sRGB = true; const ExtensionImageCesiumUnreal& extension = - ExtensionImageCesiumUnreal::GetOrCreate( + ExtensionImageCesiumUnreal::getOrCreate( CesiumAsync::AsyncSystem(nullptr), // TODO image, sRGB, diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index 0f022414e..a9c8b20fd 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -234,7 +234,7 @@ SharedFuture createTextureInLoadThread( bool needsMips = imageNeedsMipmaps[pTexture->source]; const ExtensionImageCesiumUnreal& extension = - ExtensionImageCesiumUnreal::GetOrCreate( + ExtensionImageCesiumUnreal::getOrCreate( asyncSystem, *pImage->cesium, sRGB, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index ff250ac44..4a9b3bbe7 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -124,7 +124,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( /** * @brief Does the asynchronous part of renderer resource preparation for * a texture. The given image _must_ be prepared before calling this method by - * calling {@link ExtensionImageCesiumUnreal::GetOrCreate} and then waiting + * calling {@link ExtensionImageCesiumUnreal::getOrCreate} and then waiting * for {@link ExtensionImageCesiumUnreal::getFuture} to resolve. This method * should be called in a background thread. * diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp index f24698f52..bb0df6f8d 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp @@ -20,7 +20,7 @@ getOrCreateImageFuture( } // namespace /*static*/ const ExtensionImageCesiumUnreal& -ExtensionImageCesiumUnreal::GetOrCreate( +ExtensionImageCesiumUnreal::getOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::ImageCesium& imageCesium, bool sRGB, diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h index d01ece599..88ea0b16a 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h @@ -50,7 +50,7 @@ struct ExtensionImageCesiumUnreal { * To determine if the asynchronous `FTextureResource` creation process has * completed, use {@link getFuture}. */ - static const ExtensionImageCesiumUnreal& GetOrCreate( + static const ExtensionImageCesiumUnreal& getOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, CesiumGltf::ImageCesium& imageCesium, bool sRGB, From 8141c5ca48e61f3762b83037ae98e4db08de6b6f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 18:13:40 +1000 Subject: [PATCH 32/68] Fix another UE 5.3/5.4 compile error. --- Source/CesiumRuntime/Private/CesiumTextureUtility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 81139f54e..2e47c46b7 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -498,7 +498,7 @@ loadTextureGameThreadPart(LoadedTextureResult* pHalfLoadedTexture) { pTextureResource->SetTextureReference( pTexture->TextureReference.TextureReferenceRHI); #if ENGINE_VERSION_5_3_OR_HIGHER - pCesiumTextureResource->InitResource( + pTextureResource->InitResource( FRHICommandListImmediate::Get()); // Init Resource now requires a // command list. #else From e4734e36f23346ae7dc51d21bf88e2870c0424de Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 20:53:31 +1000 Subject: [PATCH 33/68] Fix test failures. --- .../Private/CesiumTextureUtility.cpp | 19 +++--- .../Private/CesiumTextureUtility.h | 8 +-- .../Private/ExtensionImageCesiumUnreal.h | 3 +- .../Tests/CesiumTextureUtility.spec.cpp | 59 ++++++++++++++----- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 2e47c46b7..6d2a7d0e8 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -366,7 +366,7 @@ bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { } TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - const CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium& image, const CesiumGltf::Sampler& sampler, bool sRGB) { return loadTextureAnyThreadPart( @@ -411,7 +411,7 @@ static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { } TUniquePtr loadTextureAnyThreadPart( - const CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -421,15 +421,20 @@ TUniquePtr loadTextureAnyThreadPart( std::optional overridePixelFormat) { // The FCesiumTextureResource for the ImageCesium should already be created at // this point, if it can be. - const ExtensionImageCesiumUnreal* pExtension = - image.getExtension(); - check(pExtension != nullptr); - if (pExtension == nullptr || pExtension->getTextureResource() == nullptr) { + const ExtensionImageCesiumUnreal& extension = + ExtensionImageCesiumUnreal::getOrCreate( + CesiumAsync::AsyncSystem(nullptr), + image, + sRGB, + useMipMapsIfAvailable, + overridePixelFormat); + check(extension.getFuture().isReady()); + if (extension.getTextureResource() == nullptr) { return nullptr; } auto pResource = FCesiumTextureResource::CreateWrapped( - pExtension->getTextureResource(), + extension.getTextureResource(), group, filter, addressX, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 4a9b3bbe7..472920fc7 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -117,17 +117,17 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * and can be empty. */ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - const CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium& image, const CesiumGltf::Sampler& sampler, bool sRGB); /** * @brief Does the asynchronous part of renderer resource preparation for - * a texture. The given image _must_ be prepared before calling this method by + * a texture.The given image _must_ be prepared before calling this method by * calling {@link ExtensionImageCesiumUnreal::getOrCreate} and then waiting * for {@link ExtensionImageCesiumUnreal::getFuture} to resolve. This method * should be called in a background thread. - * + * * @param imageCesium The image. * @param addressX The X addressing mode. * @param addressY The Y addressing mode. @@ -141,7 +141,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * @return The loaded texture. */ TUniquePtr loadTextureAnyThreadPart( - const CesiumGltf::ImageCesium& image, + CesiumGltf::ImageCesium& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h index 88ea0b16a..a9aeeda21 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h @@ -45,7 +45,8 @@ struct ExtensionImageCesiumUnreal { * `FTextureResource` from it is kicked off. On successive invocations * (perhaps from other threads), the existing instance is returned. It is safe * to call this method on the same `ImageCesium` instance from multiple - * threads simultaneously. + * threads simultaneously as long as no other thread is modifying the instance + * at the same time. * * To determine if the asynchronous `FTextureResource` creation process has * completed, use {@link getFuture}. diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 0e9cac98f..2fd0b54bf 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -2,8 +2,10 @@ #include "CesiumTextureUtility.h" #include "CesiumAsync/AsyncSystem.h" +#include "ExtensionImageCesiumUnreal.h" #include "Misc/AutomationTest.h" #include "RenderingThread.h" +#include #include #include @@ -18,12 +20,14 @@ BEGIN_DEFINE_SPEC( EAutomationTestFlags::ProductFilter | EAutomationTestFlags::NonNullRHI) std::vector originalPixels; std::vector originalMipPixels; +std::vector expectedMipPixelsIfGenerated; SharedAsset imageCesium; void RunTests(); void CheckPixels( - const IntrusivePointer& pRefCountedTexture); + const IntrusivePointer& pRefCountedTexture, + bool requireMips = false); void CheckSRGB( const IntrusivePointer& pRefCountedTexture, bool expectedSRGB); @@ -62,6 +66,20 @@ void CesiumTextureUtilitySpec::Define() { imageCesium->pixelData.data(), originalPixels.data(), originalPixels.size()); + + ImageCesium copy = *imageCesium; + CesiumGltfReader::GltfReader::generateMipMaps(copy); + + expectedMipPixelsIfGenerated.clear(); + + if (copy.mipPositions.size() >= 2) { + expectedMipPixelsIfGenerated.resize(copy.mipPositions[1].byteSize); + for (size_t iSrc = copy.mipPositions[1].byteOffset, iDest = 0; + iDest < copy.mipPositions[1].byteSize; + ++iSrc, ++iDest) { + expectedMipPixelsIfGenerated[iDest] = uint8_t(copy.pixelData[iSrc]); + } + } }); RunTests(); @@ -117,7 +135,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, true); CheckSRGB(pRefCountedTexture, false); CheckAddress( pRefCountedTexture, @@ -141,7 +159,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, true); CheckSRGB(pRefCountedTexture, true); CheckAddress( pRefCountedTexture, @@ -167,7 +185,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, false); CheckSRGB(pRefCountedTexture, false); CheckAddress( pRefCountedTexture, @@ -200,7 +218,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, true); CheckSRGB(pRefCountedTexture, true); CheckAddress( pRefCountedTexture, @@ -251,7 +269,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture2 = loadTextureGameThreadPart(model, pHalfLoaded2.Get()); - CheckPixels(pRefCountedTexture1); + CheckPixels(pRefCountedTexture1, true); CheckSRGB(pRefCountedTexture1, true); CheckAddress( pRefCountedTexture1, @@ -260,7 +278,7 @@ void CesiumTextureUtilitySpec::RunTests() { CheckFilter(pRefCountedTexture1, TextureFilter::TF_Default); CheckGroup(pRefCountedTexture1, TextureGroup::TEXTUREGROUP_World); - CheckPixels(pRefCountedTexture2); + CheckPixels(pRefCountedTexture2, false); CheckSRGB(pRefCountedTexture2, false); CheckAddress( pRefCountedTexture2, @@ -300,7 +318,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, true); CheckSRGB(pRefCountedTexture, true); CheckAddress( pRefCountedTexture, @@ -349,7 +367,7 @@ void CesiumTextureUtilitySpec::RunTests() { IntrusivePointer pRefCountedTexture = loadTextureGameThreadPart(model, pHalfLoaded.Get()); - CheckPixels(pRefCountedTexture); + CheckPixels(pRefCountedTexture, true); CheckSRGB(pRefCountedTexture, true); CheckAddress( pRefCountedTexture, @@ -376,7 +394,8 @@ void CesiumTextureUtilitySpec::RunTests() { } void CesiumTextureUtilitySpec::CheckPixels( - const IntrusivePointer& pRefCountedTexture) { + const IntrusivePointer& pRefCountedTexture, + bool requireMips) { TestNotNull("pRefCountedTexture", pRefCountedTexture.get()); TestNotNull( "pRefCountedTexture->getUnrealTexture()", @@ -426,27 +445,35 @@ void CesiumTextureUtilitySpec::CheckPixels( TestEqual("pixel-alpha", readPixels[i].A, originalPixels[i * 4 + 3]); } + if (requireMips) { + TestTrue("Has Mips", !readPixelsMip1.IsEmpty()); + } + if (!readPixelsMip1.IsEmpty()) { + std::vector& pixelsToMatch = originalMipPixels.empty() + ? expectedMipPixelsIfGenerated + : originalMipPixels; + TestEqual( "read buffer size", readPixelsMip1.Num() * 4, - originalMipPixels.size()); + pixelsToMatch.size()); for (size_t i = 0; - i < readPixelsMip1.Num() && (i * 4 + 3) < originalMipPixels.size(); + i < readPixelsMip1.Num() && (i * 4 + 3) < pixelsToMatch.size(); ++i) { - TestEqual("mip pixel-red", readPixelsMip1[i].R, originalMipPixels[i * 4]); + TestEqual("mip pixel-red", readPixelsMip1[i].R, pixelsToMatch[i * 4]); TestEqual( "mip pixel-green", readPixelsMip1[i].G, - originalMipPixels[i * 4 + 1]); + pixelsToMatch[i * 4 + 1]); TestEqual( "mip pixel-blue", readPixelsMip1[i].B, - originalMipPixels[i * 4 + 2]); + pixelsToMatch[i * 4 + 2]); TestEqual( "mip pixel-alpha", readPixelsMip1[i].A, - originalMipPixels[i * 4 + 3]); + pixelsToMatch[i * 4 + 3]); } } } From 1777db071aa86232a2e3a740fd2859f953930459 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 30 Sep 2024 20:58:06 +1000 Subject: [PATCH 34/68] Formatting. --- Source/CesiumRuntime/Private/CesiumTextureUtility.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 472920fc7..97ee85e43 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -127,7 +127,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * calling {@link ExtensionImageCesiumUnreal::getOrCreate} and then waiting * for {@link ExtensionImageCesiumUnreal::getFuture} to resolve. This method * should be called in a background thread. - * + * * @param imageCesium The image. * @param addressX The X addressing mode. * @param addressY The Y addressing mode. From b03d1f6320766ede28fd782799e3bd53c6244dc2 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 1 Oct 2024 14:29:27 -0400 Subject: [PATCH 35/68] SharedImages test working on CI --- Resources/Tests/SharedImages/plane-0-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-0-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-0-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-0-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-0-9.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-1-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-1-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-1-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-1-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-1-9.glb | Bin 0 -> 1504 bytes Resources/Tests/SharedImages/plane-2-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-2-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-2-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-2-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-2-9.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-0.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-1.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-2.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-3.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-3-4.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-5.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-6.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-3-7.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-3-8.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-3-9.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-4-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-4-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-4-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-4-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-4-9.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-5-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-5-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-5-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-5-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-5-9.glb | Bin 0 -> 1504 bytes Resources/Tests/SharedImages/plane-6-0.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-1.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-2.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-3.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-6-4.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-5.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-6.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-6-7.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-6-8.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-6-9.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-7-0.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-1.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-2.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-3.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-7-4.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-5.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-6.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-7-7.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-7-8.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-7-9.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-8-0.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-1.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-2.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-3.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-8-4.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-5.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-6.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-8-7.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-8-8.glb | Bin 0 -> 1500 bytes Resources/Tests/SharedImages/plane-8-9.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-9-0.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-9-1.glb | Bin 0 -> 1504 bytes Resources/Tests/SharedImages/plane-9-2.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-9-3.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-9-4.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-9-5.glb | Bin 0 -> 1504 bytes Resources/Tests/SharedImages/plane-9-6.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-9-7.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/plane-9-8.glb | Bin 0 -> 1508 bytes Resources/Tests/SharedImages/plane-9-9.glb | Bin 0 -> 1512 bytes Resources/Tests/SharedImages/texture0.png | Bin 0 -> 58902 bytes Resources/Tests/SharedImages/texture1.png | Bin 0 -> 57706 bytes Resources/Tests/SharedImages/tileset.json | 2228 +++++++++++++++++ .../Private/Tests/Cesium3DTileset.spec.cpp | 20 +- .../Private/Tests/CesiumTestHelpers.cpp | 1 + 105 files changed, 2241 insertions(+), 8 deletions(-) create mode 100644 Resources/Tests/SharedImages/plane-0-0.glb create mode 100644 Resources/Tests/SharedImages/plane-0-1.glb create mode 100644 Resources/Tests/SharedImages/plane-0-2.glb create mode 100644 Resources/Tests/SharedImages/plane-0-3.glb create mode 100644 Resources/Tests/SharedImages/plane-0-4.glb create mode 100644 Resources/Tests/SharedImages/plane-0-5.glb create mode 100644 Resources/Tests/SharedImages/plane-0-6.glb create mode 100644 Resources/Tests/SharedImages/plane-0-7.glb create mode 100644 Resources/Tests/SharedImages/plane-0-8.glb create mode 100644 Resources/Tests/SharedImages/plane-0-9.glb create mode 100644 Resources/Tests/SharedImages/plane-1-0.glb create mode 100644 Resources/Tests/SharedImages/plane-1-1.glb create mode 100644 Resources/Tests/SharedImages/plane-1-2.glb create mode 100644 Resources/Tests/SharedImages/plane-1-3.glb create mode 100644 Resources/Tests/SharedImages/plane-1-4.glb create mode 100644 Resources/Tests/SharedImages/plane-1-5.glb create mode 100644 Resources/Tests/SharedImages/plane-1-6.glb create mode 100644 Resources/Tests/SharedImages/plane-1-7.glb create mode 100644 Resources/Tests/SharedImages/plane-1-8.glb create mode 100644 Resources/Tests/SharedImages/plane-1-9.glb create mode 100644 Resources/Tests/SharedImages/plane-2-0.glb create mode 100644 Resources/Tests/SharedImages/plane-2-1.glb create mode 100644 Resources/Tests/SharedImages/plane-2-2.glb create mode 100644 Resources/Tests/SharedImages/plane-2-3.glb create mode 100644 Resources/Tests/SharedImages/plane-2-4.glb create mode 100644 Resources/Tests/SharedImages/plane-2-5.glb create mode 100644 Resources/Tests/SharedImages/plane-2-6.glb create mode 100644 Resources/Tests/SharedImages/plane-2-7.glb create mode 100644 Resources/Tests/SharedImages/plane-2-8.glb create mode 100644 Resources/Tests/SharedImages/plane-2-9.glb create mode 100644 Resources/Tests/SharedImages/plane-3-0.glb create mode 100644 Resources/Tests/SharedImages/plane-3-1.glb create mode 100644 Resources/Tests/SharedImages/plane-3-2.glb create mode 100644 Resources/Tests/SharedImages/plane-3-3.glb create mode 100644 Resources/Tests/SharedImages/plane-3-4.glb create mode 100644 Resources/Tests/SharedImages/plane-3-5.glb create mode 100644 Resources/Tests/SharedImages/plane-3-6.glb create mode 100644 Resources/Tests/SharedImages/plane-3-7.glb create mode 100644 Resources/Tests/SharedImages/plane-3-8.glb create mode 100644 Resources/Tests/SharedImages/plane-3-9.glb create mode 100644 Resources/Tests/SharedImages/plane-4-0.glb create mode 100644 Resources/Tests/SharedImages/plane-4-1.glb create mode 100644 Resources/Tests/SharedImages/plane-4-2.glb create mode 100644 Resources/Tests/SharedImages/plane-4-3.glb create mode 100644 Resources/Tests/SharedImages/plane-4-4.glb create mode 100644 Resources/Tests/SharedImages/plane-4-5.glb create mode 100644 Resources/Tests/SharedImages/plane-4-6.glb create mode 100644 Resources/Tests/SharedImages/plane-4-7.glb create mode 100644 Resources/Tests/SharedImages/plane-4-8.glb create mode 100644 Resources/Tests/SharedImages/plane-4-9.glb create mode 100644 Resources/Tests/SharedImages/plane-5-0.glb create mode 100644 Resources/Tests/SharedImages/plane-5-1.glb create mode 100644 Resources/Tests/SharedImages/plane-5-2.glb create mode 100644 Resources/Tests/SharedImages/plane-5-3.glb create mode 100644 Resources/Tests/SharedImages/plane-5-4.glb create mode 100644 Resources/Tests/SharedImages/plane-5-5.glb create mode 100644 Resources/Tests/SharedImages/plane-5-6.glb create mode 100644 Resources/Tests/SharedImages/plane-5-7.glb create mode 100644 Resources/Tests/SharedImages/plane-5-8.glb create mode 100644 Resources/Tests/SharedImages/plane-5-9.glb create mode 100644 Resources/Tests/SharedImages/plane-6-0.glb create mode 100644 Resources/Tests/SharedImages/plane-6-1.glb create mode 100644 Resources/Tests/SharedImages/plane-6-2.glb create mode 100644 Resources/Tests/SharedImages/plane-6-3.glb create mode 100644 Resources/Tests/SharedImages/plane-6-4.glb create mode 100644 Resources/Tests/SharedImages/plane-6-5.glb create mode 100644 Resources/Tests/SharedImages/plane-6-6.glb create mode 100644 Resources/Tests/SharedImages/plane-6-7.glb create mode 100644 Resources/Tests/SharedImages/plane-6-8.glb create mode 100644 Resources/Tests/SharedImages/plane-6-9.glb create mode 100644 Resources/Tests/SharedImages/plane-7-0.glb create mode 100644 Resources/Tests/SharedImages/plane-7-1.glb create mode 100644 Resources/Tests/SharedImages/plane-7-2.glb create mode 100644 Resources/Tests/SharedImages/plane-7-3.glb create mode 100644 Resources/Tests/SharedImages/plane-7-4.glb create mode 100644 Resources/Tests/SharedImages/plane-7-5.glb create mode 100644 Resources/Tests/SharedImages/plane-7-6.glb create mode 100644 Resources/Tests/SharedImages/plane-7-7.glb create mode 100644 Resources/Tests/SharedImages/plane-7-8.glb create mode 100644 Resources/Tests/SharedImages/plane-7-9.glb create mode 100644 Resources/Tests/SharedImages/plane-8-0.glb create mode 100644 Resources/Tests/SharedImages/plane-8-1.glb create mode 100644 Resources/Tests/SharedImages/plane-8-2.glb create mode 100644 Resources/Tests/SharedImages/plane-8-3.glb create mode 100644 Resources/Tests/SharedImages/plane-8-4.glb create mode 100644 Resources/Tests/SharedImages/plane-8-5.glb create mode 100644 Resources/Tests/SharedImages/plane-8-6.glb create mode 100644 Resources/Tests/SharedImages/plane-8-7.glb create mode 100644 Resources/Tests/SharedImages/plane-8-8.glb create mode 100644 Resources/Tests/SharedImages/plane-8-9.glb create mode 100644 Resources/Tests/SharedImages/plane-9-0.glb create mode 100644 Resources/Tests/SharedImages/plane-9-1.glb create mode 100644 Resources/Tests/SharedImages/plane-9-2.glb create mode 100644 Resources/Tests/SharedImages/plane-9-3.glb create mode 100644 Resources/Tests/SharedImages/plane-9-4.glb create mode 100644 Resources/Tests/SharedImages/plane-9-5.glb create mode 100644 Resources/Tests/SharedImages/plane-9-6.glb create mode 100644 Resources/Tests/SharedImages/plane-9-7.glb create mode 100644 Resources/Tests/SharedImages/plane-9-8.glb create mode 100644 Resources/Tests/SharedImages/plane-9-9.glb create mode 100644 Resources/Tests/SharedImages/texture0.png create mode 100644 Resources/Tests/SharedImages/texture1.png create mode 100644 Resources/Tests/SharedImages/tileset.json diff --git a/Resources/Tests/SharedImages/plane-0-0.glb b/Resources/Tests/SharedImages/plane-0-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..b3ac6b99d5b04e561dd1493cad7d5d7fae3e5415 GIT binary patch literal 1500 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 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-0-5.glb b/Resources/Tests/SharedImages/plane-0-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..b81e52b3c9da06b1f6fc10699d0b4dab12b3084d GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE=A8edjw@#p(R!O4GETZ#2z?bvyGX8-&ANYo#b`9h@j zX{4uw%PnYdsj9R=G06$Xf^p=b+Zbwo9dVXjCqfjZ*XhJmBvoKQDLdcr8pmlzT>_3Z z;U#5IC}bNZ+JeKX8Jfm`P8gFs776k!*OZL=UODWYOMN3F-+>|h0`pk5l;ghZIr~hV zL9M1#E!KT|jJ1D*?KSQDFIdY2qC=;h6Q^w%Kh1DV>d-4rA#H*@iHZ};DDqg=jAo=B zl=^B%swFVPf^dq{8e0VXjtHElG@P<3PI4GW;Vw>#1mCe0%r)}w`=34!A%qpHf|ShYimZ?qfyYGo zPs*eTyx=sWg03483&Ckn2`O&Sy+0exNB#slMn@BWdeCC)ei``w^y60(S^%iQ zV+xOuDu0`sT2St|Th-t>2zACX;8*8@k_}VDRM!yrCaP6ZpQ;u3)`}@A2>Z`^&P_zjP)Mhi8}e IE&lxM7gtxa`2YX_ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-0-6.glb b/Resources/Tests/SharedImages/plane-0-6.glb new file mode 100644 index 0000000000000000000000000000000000000000..0927e2f83f5a3700e80fa79ec3b52ec82328e3d3 GIT binary patch literal 1508 zcmb_cZHn745Oufo9znm2_Iw` z?0j!qN)5I(lHR=0%p1k&?E1z=-BB zvKL$Pn8NuB3UFLgbaoW?gTA*Ta4g%DuWi|TDs8K|Q&RVTkOG->E{V@b<=iPLBkTyXcMa7A26?rUc zMl+%tMQ?4(X$j1*Ae`bu_H zduEzi;%L4k(0BG0h-$x$2Ps&@8!I)?qOh&z1bW(T0kqvsL=e!O#iIfM2Z(j`K3bLWy?XwYs)^I;t<_WgvqsaaN=t zY#VN8aMd!wO?+kwL$;@ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-0-7.glb b/Resources/Tests/SharedImages/plane-0-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..42f64e1c345b96d513ebfb5a46ff912d86822503 GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$AjzCF^PTTpCraiwSDL2%e5Gmc;JuxBlMTW_K#GF#0=bI~^2;zJ{GO5r$U}Xl|T^$LfMYDcy)SDt5rT7tyI!jp53|8iH zs~=NXe?b9;Yl_Z};(pL~b_AAT>hd)Wb5B*tN_R?X{|{0ilg1_S8L8Brl0u^Xn9OG) zwNE2GC0uSng9}xq4T?xcI2Mc}7u`ls^H#`N+KPq93%A{lsEAA7fKs-<;WdtuwmJkH zYr+f4pisy*Otb}uRWsC$0i7@+StMfQS{+j|?tA61cP{mfjC==%^b5=**-}pDZP(dn z>I`btrE0L9H^*4}H`s37zW;)?OdvXR+BtFBmhsaRN2Ch9w62@Ar~?^AAIS1-t^;F6IuYM z!D9-SkSc$Zn@UjbbhfI&GZ3nbrNFQD1;<&DV4=Epacx|i@^RE)%G*FXTi`TLptI%m zrsM$TL!k?~sw_6PD@a|(YOBUq2v+hyg2AI!>j&JWABGe7zR<4VeF@*==llE1veZAf OClQBxm(~sbeD4>AHMMX6 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-0-8.glb b/Resources/Tests/SharedImages/plane-0-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..307a4df5d40b7bc3026355f61227d9f19fc50ddb GIT binary patch literal 1500 zcmb_cTZ-E-6m_R`A3@*7@v9;APf61ZWJuy^k`9FsitH%1iftKL&dd-3T|k%E#q>(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ?_YuS#DLRMz5-O-Uyt7z8mje1k0qZB`ZQ)dY?n!(Im z?(|a%>n|w4aZTawDINxWXHQ@mrY?tRm^_WvLSGU;3rUy#bRb5bbOAB*`y zruKQJ=Y-20tl&a*X^SF~5sn4p$U}D#^t`p^EN#U?a7@t~Ds?OfB1%zO`q@(au(DJjQ&*KrP& zx`0}Bs~W7^J;mC;!FFo-{tMPJf#^7B=PYPj=1)@`kt+3)Qz)AtPom_+H1aHxJ)JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2`K~ED zgo~kYg`8Es8m|?kZeX=l<0}L!c__gUOX~-`p&y15_`cF^V7!L!>F@jd>$Y5fX-^`K L_pZ$w{N>&+N5Hev literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-0-9.glb b/Resources/Tests/SharedImages/plane-0-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..e351c781242929e76e0185d40602d68e0c1697dc GIT binary patch literal 1508 zcmb_c>x!E|6t-LX9$|iq@sfr{3MEamkR^%RBwY$2l+lbjMI9N(be9n51N1TbAU!i` z?%S49L7j7EzVn^yM9JdzM$@#PuQcr~ymxbNx@KTz@wxu%Z% zCqcY0EQnBIz|x#$Bom9LoFMnLWji_wSee0gS4To=(R|P!_h(2)DSiZ_&I(pEgO$13 z>8BLdUr>PInxeC(co+_xJ%MGIx_nK;JWy4#(w&pq|AQ3Bq;W}nK`M3Uq>!jTCi9s{ z?ej>_30FJN;6hbtiz1Q{js@e$MRyU@ycKemwqhaj!fm%BD&o>Npp@;ec!Q&)tquXl zhVX(iC={{{6YaoZ)eLoGKqrhz7Ks?SR>zc#hh91Eoojs~Bj1A|{Q~nywv^N9b)7?| zE}&Ljss`J6bBeWpgYDMs`!86_1fpZ7oinFx89z;NM5@qBP9bfAJc^PN%gD1x){Lg4 z8kFj2TdE~6#e#5(lM0*rd_n|H5*o}{8O0flBXVI*a6twvVf+>cs+!BD(d)k~L33tN zQA-@n)&%Cx+yPN-x3M6Bh&LohS~!c7JjN5YhPg)GegEU9A%w7IrJs;FU6VC(L-3d= z|49+of#;m2RM1T=Vj(#7OCiNAy7%Uz#mJjN$LMJ4%_jXZav_t8;pc(p%|3iFp#^{% zJf?67sq#0usRZRtXQvuG1EI=T3jAtcaGVth7OHEPJ)>vBW2w26mx1)Pz-gX9W6Rr3 z$pMUq0vB>s8Ei~fkh+Z3R*kO^tmJ_NgF~%W>j&JW??+SkzR+&qeF@*w=li?sveZAf OClSYc*VYaGa_<+70JUNO literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-0.glb b/Resources/Tests/SharedImages/plane-1-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..53f8c61939e17278517655b238cb899b2652d9de GIT binary patch literal 1500 zcmb_c+lt#T5OufoKNdZk#J7glFC|U0kR=;$lXNMBP-I88Rcy=1a=J?h^aJ{d{g{p< zJNIo%X~2#~(wQ@tGfuMk&6QyoKVKP!2hZ&+m~0S^B2rb1SIA#%P*}$?;SZF2L%wCA z@JWy$jw>RR8L_-%1u4Y*sU*mMZ9A@sB32i0+%u6-yJ$A(kNZ<(q8vYhQ+Ej~TENO$ zZp~u~`!6WKaYNzlC?19bcSm4%EK@#K$J$eATkV~aI{$+d$fR>gd`2qwPD!Cqe=O!R znL4MLo)Rv%u!Aerr433*K{ytSBOl!+(DUnOo~LL$hSR9GVZlT|Z+q^( zQfE-RX;q8$-W+3{-(Y)c))t5wzpV!eM7$+A*oC_|D^q;ORxsBnxbJ`bG=vmZtPV3WqbssPehd*4 z6+fxcCh?NfoC>;bOe_SaVJ)P&LHEIIG#>>MP>hKt!St>_Mm|(>KKwigg6W4Z7Dxc7 zAz}(2A=Uo2Hri0(cv~g#0t{WT9Qf6_;JBzVER<>&dK_&%^H7`$=Vb<(mLE15b``;7 zD4bepU#(Y|Sh&)3P#-mTjbJs8Bp71(0pHN~qY1oU7+3JTg!l39`@74w+<)#&A`Z_k Jty}#0*)LCbv+)1` literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-1.glb b/Resources/Tests/SharedImages/plane-1-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..655d287f64c970ee9693547e4a96e00af035ded7 GIT binary patch literal 1500 zcmb_c+lt#T5OufoKZ2f3;!7G@zmzo1LY8d2P12>Xgd#hNtzuh7lG9y6pdZjr?8kH@ z*|~39N&|K@lFppDoNQ5WK~c3@5q?j}HxxKF zik>71S1UoJHe;5TtRRJ)Ka~UpuU*f#QOv3Wj(awedKb+G{c(SaY?R|iaOy8$MGIIt zi>-Z3;r;~$IIbzWJBs_^z~2$LZO2xR({}b$#@2hMq~8A^1v2Ga5ucH&y;D*s)E|ra zOs3vxrl*99E$m>SyR<sB?`?wez*_Zw`lmhZn{T?dE`gZ58?_Ei2f#|bg1SDZrGBzcq-Cr-O864f)B z6Ei60YDaA=V2&l>6lVroM*NmYoMkkgvMNan7)KPqoM1r)EMxo{$GV#jjnM0V)q>{E z+)yhVEtUl4&e;M{?YHru1dDh>((HmexQnwg#kXt;bB)5g{>M*4NMXsUC?hkvBuf-X zUJ)HW5ow)w$!Sg{UDqa-lGCV?%G{v4a5kEc!U+h*Mw4)Q+aIF<3OOHs9)#ia!xsk> z0L&0Dg>R7Vep4D_=xDlIt?vR1Q?MNP^||D@5E+(Qv``%vrqMMc5490EFEfy|`mU+4 zMGS+Xae8HZHC|z0;Y!^=b5!FEf>k_LV2Gvl1HPf}M-zCzu&&^F3Gd_I_ji|Vwg23i NLL8o5S~vLfvtK*tv&H}b literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-2.glb b/Resources/Tests/SharedImages/plane-1-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..b5047f4619f4d6687c104577a3971d9153b3225b GIT binary patch literal 1500 zcmb_c+lt#T5OufoKNdY3$Cos;ekp01g)G^4o1{x2gd#h#tzuh7lG9y6pdZjr?8kH@ z+qrLBN&|K@lFppDoN|i(`1#5(y71i2g2@KqC?Y~IE|9<2ps-j2YrY*Rh9W$&qKTkoBcy8nX|$dq$Md`7DFPD!Cqe=O!R znYyQ$o)Rv%u!Du}(gr1@ARJ4^k&kW@==pWbS$>^LSqi_?NvKS#(1KQWzT!1bvW~t4 z9BaY_WzZ<(S~l83z*-EAFhB`oQY11(zSFf8<6bL=+PS0~mH7?~<(HTzN>X0;ZO_|R z>I~{Mt!lB|H^*4_H`rby-+#e6HV_>K?VSYes{Cn=6H=#MaSCOV@5&Aep?R;h>MP>hKt!St>_Mm|(>KKwigg6W4ZHb?-d zAz}(2A>IDAHnpL{>29^a3oz=6<-o7cCC7!xu+*wuxRwj!=+rY0#Su6!Gtjj9u&J;` z1e2k0dR6;sy~4!8m8OIBQG>4$tm2UZLo7ew8~T1Uf%gmJ3Z9qnKK^}wciC3^&z&j6 M;n}5ii$6d61yf_Q^#A|> literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-3.glb b/Resources/Tests/SharedImages/plane-1-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..1c796fb9019f726c22d4563ca4cac0c1276d57fb GIT binary patch literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZ4Qp zeWlKzPTi^o+j(=0b$^5H*7E%qtYZVwVbI=5(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aiqFAZ-id|%N8_u zW|~^!Xuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_CT}%ry$`dY?WIAcZBX!jw$uk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&61=Hbd7>q$MCK?BmQSTP{P{`TfOFsxEAHUk5 z0APlIDSU%e_nXpaLq*fsDt+f*=z?Xyug(R>d6{CNL_7B^&z6r1GwJBThYFQAD^d`) z{JP2D%Lv9pq18%zY<$Dm!j-y>`l!Zh1gm%?!4Ofa)%pS7(htKi{Jt=*;C%_d$Itio Tmu0JOM_w$y!B`_5gdtJ|i(`1#5(Ja}$r!DNGQ6cHg97sy|1P*}w=;SZF2L%waI z@JW&&js=n0j96Z>f)sN8R1)OBcHFLsB32b}+%u8XyJ$A(kNZ<(q8vYh)9w;hw1Abp z+?vM}&R-5r5r*`|7I%idGfw%$7>b^iw`kSXVi_>5HTosvSK{#eXs zGIdWgJtbUjVFwG{r433*K{%F-BOl!+(DUn=WF378 zIM##<%AirmwQRJ7fVCJJVSp0Gq)23ne8;mD<6bL=+PS0~mH7?~<(HTzN>W|#ZLhnp z)EU%iTGe8`H^*4_H`rby-+#e6HV_>K-8~7~Rr%8#C!|ij;uOjz$)l_|ajddPRL^Km z>OrZmcGR{4=2#L=aaLo?klzuBvy4VlRwZcx7eU>EM|q!%8Z1gYJXbXg&%ipcoTPg6Um+Y>)s@ zL&Ow5Lc0BJZE8b@<88IT3oz=6<-o7cCC7!xu+*wuc$Np_=+rY0#Su6!Gtjj9u&J;` z1e2k0dR6;sy~4!8m8OIBQG>4$tm2UZLo7ew8~T1Uf%gmJ3Z9qnKK^}wciC3^&z&j6 M;n}5ii$6d61y@hA`2YX_ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-5.glb b/Resources/Tests/SharedImages/plane-1-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..fed618ea974ede9b0bd7c06fca5a1bf5ead9ba57 GIT binary patch literal 1500 zcmb_c>x$Yy6i&DFJ!F1|n48k(PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-gv1~_2AuBUD?&?UWT{P?WM!hN0QHr0ysk4L?&0u9N zcls%X^%oT2xTbLT6c2;GvnQ|&Qa7@t~Ds?OfB1%zO`q@(au(DJjQ&*L4n+ zx`0}Bs~W8P_7rRX2HUOW`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE;>dBrqQZK9%@tIG*3X%^1CL( zEwJ_x=5KTkgMf OCK1PH*VYaG^6VEw$g|D> literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-6.glb b/Resources/Tests/SharedImages/plane-1-6.glb new file mode 100644 index 0000000000000000000000000000000000000000..783d80d0007864368467dff5e4434c185df6c79b GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dA^o*V<9~XwwswW>xRN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%a}wQ&Fd literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-7.glb b/Resources/Tests/SharedImages/plane-1-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..d143bd58e73a56fd7dc7299f2eb7f08e5aee1422 GIT binary patch literal 1508 zcmb_c?TXq!6iv7EJ!F1|I5F03L!nsff{U81+NB^;CUH!rB$+UoxVwnZ2k2w=L3(GB z_`Pi@4MgY8%(>@&oSQJ7-CP-l@$;2oyo2|4>Ww#u1Od%+E^_3~H^?tmD=O|8eLyZY zk^dyAw_4>?YBS(T##5Tg*;7W5``T_fCJK0w!g0q$QtzT^uRH8ckckrV2u_^^tXK*w zyx5w@6!u?Gfa8jyy`#AA_naMpZDCWr*us0N($;&Yq^0ifm3;pNYhxff4B9yfx~1}G2?=SLdLbB;P0~kM2#T#N4OP!r zLd!uZuQt`T0wzRK!AM+UGhd9TByr4w2`|DZg>ghK%n8Y9kH=ixkU)2HUJJeYS1qXT z%t~s7W9gE@+~F+{Reoy^O0Y=OB=s)1gS#ZoA~NDjm}}(Sbw7RXLkde?_%WTbC0!y{ zih}C!X&zO1XM!b6vUOz=DFyQjsmu+!^QME@z#D^LOf>c;qwWy7P{>*TOV9HrAHQNy z05C(q48B3S`*mqbLr2rzYJI0*lm$zGU!O}s(mW+7fXtZ9H9&6t)ws56tV|i5JO9U@OpuiAOqtW;Q-_j3*G5o$TuHbzMzsJw_ U_m^$8|J<2E9G+cT*ZA|ZUva6mMF0Q* literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-8.glb b/Resources/Tests/SharedImages/plane-1-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..c94389505158c66c3f81a64625c5672c076d41d8 GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9!0;6w!Qt6OIMr$V0a=^!z&FEW1vGC`zx>iK$4cz<^eEzTq{F(~i0X z9BaZ$%AirmHcYgIfRz{;VSp0GB#%XcJj*pD<6bL=+PS0~nfVS3ZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&J);NXXw@?h#VK)Cq@Zc}VUuB( zAxwtCsa5T(^$HUUSDFsiM-9G4u!@Hg46*!xZ|H~71m3T-D|lYR`}p_${bgJ3zjP)M Mhi8}8E&lxM7hfl|0RR91 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-1-9.glb b/Resources/Tests/SharedImages/plane-1-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..3f2257e8a1d8191d1c1da07f6faeee83c945defe GIT binary patch literal 1504 zcmb_c>uTFD6!tpy9!0;6<4YDA{~1}9LP{EUNmd3S4B1g^4cju3oVA3&9$*i-N7<2V z=iY6M8Jy@y`p$PQ-*KGIZm$f(`1!^#Zs5I}2ICFFQAC7bTp)kGL1DRC5&lTYH{{zU z3ZEqjRx3fIHY1i5EGM~~Jr@M|Zynb&QN+p|jyoojdKXRmy-{z1OqAg#aOy2!MRQo$ zi>-M~;rs;!IIbz$JBr6a-`f#5mTjunw(LDsW$V3DQulw50-18Ih|fsX-YF>*>W{^I zAyfA>(^JC57Iv`EUD}|StQWnB*wPGrhGPIzTt*>~E_QEeue?h~%+Mkngl@#kkkXp>{6mMrFPOL-{4bl2a&~Bu}#B#I}k&Rz0H` zsRpIG+EUvJm|;mc#c72tLViyqPE#69SQ#fdj3e@4POu<-mNI^eBi+q;BlP-TwV=5( ztEd%@=1T%|XK#V1_S<++fu4@xZ$!S}?~RZTg`5pO_k&>a;foCl z0A>i7!Z%2FzbQ>+=xEwot?wL+s$d!L>vPF*E>bMDXy;w4Ys2HfNIKQrLxBX&iWGFM zK5i;@5y5n5uwGRT8`m(kaHZ~IbyVXk1S@%@z!1<6_>R6Gj^X!}aRu*d_&t8Uzq@R! R{g=)Z;_&R!y1}2H{Q^|?w66dF literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-0.glb b/Resources/Tests/SharedImages/plane-2-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..582ddad0f62c63992412408c7b1cdbbff783612c GIT binary patch literal 1500 zcmb_c+lt#T5OufoKZ2f(<4YP^zmzo1LY5@nCh1ZLp~#M6tJs#2i!Q>AXCm2@dc^cJ12!g{jr$O zWa^%0dQQ09!44L>OIs9^oNz1|M?SiXq35kNXIU$evJif|9aEW^wVaav)EkdKMPX-cChE8`@GaYR1M2^OT!QpRs_q`SFjgkJxv7BqL} z6}7_Ad_`dH>>Uu*ej5);u!uJ#%`UiuyErWpJZ3AHYZTn~K7JZN3M*EIDVfm~Ss`EY zlIZY>Nb0-`PBSX$rZ%yZoQ9=T<`&%tv*COgOh7Oungr8vZ-jg(W1eu#!g#46(F+z&G^$a02fa#tl3#;eGo1{_eW1_Mbab Nh~u+s>jr;$_6s}Uv&H}b literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-1.glb b/Resources/Tests/SharedImages/plane-2-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..21e6499216d916c745904fbf11f379f5bc5666a5 GIT binary patch literal 1500 zcmb_c+lt#T5OufoKNdY3$Cos;ekp01g)B+DP12>Xgd#h#tzuh7lG9y6pdZjr?8kH@ z+qrLBN&|K@lFppDoNb^iw`kSXVi_>5HTosvSK{#eXs zGIdWgJtbUjVFwG{r433*K{%F-BOl!+(DUn=WF378 zIM##<%AirmwQRJ7fVCJJVSp0Gq)23ne5Y$G#=TY!wR1@~D)Su}$}cfbl%%}w+n%?t z)EU%iTGe8^Z;r9OrZmcGR{4=2#L=aaLo?kWYxjSw^E7tCF;UaYR1M2^M6)GRALkq`Udh3cdMPEokp7 zYHEd}#fre(*;^oL{I(tx5b>7eU>EMhMD!EDkWBOfZc7=9iE!R*5q8zcbK z5HW?1kZyllo7&LfbhldI1sHY3a^TnJlH)>TSZdWmc^uQV+ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-2.glb b/Resources/Tests/SharedImages/plane-2-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..661b2c5dc047ffa113379cf11fb25a1d2d357646 GIT binary patch literal 1500 zcmb_cTaME(6b*yeN9FG*&7)M1A5=<5>Zomowv3Rf3OT8p7$tFJJ7q>yB^JOESq#@s z()R#DRC4NT``mLM$Bxta?Tuj=KVKO}2gcnjm~0V_A|eFi0{M$A3d{AH@CQo1A>TGp z_#{cNUJD|%8L_NjImzYxsUXOI?YN$aB39KV;Q zby2FLZMCd`8J2`ooL1N(>$~ej4IwBwL2^OT!QpRs_q`SFjgkJxv7Bpw( z6}7_Ad_~~i**hSr{WczyU=eRfnpJQIXK`92c+6ICuTgN{`}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLXc!PBJo6=NuTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&OOVs<>Q*JD?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcHvo`=~`|z@=zOrvmymatM8f$ zTSPDz8mCv4uf{73EL^D@SRK{)3c*SqDKNy+`T^h2_rnRiUl=#=yoC4Z@B6#!w%UL0 OOd*cXuB{vV<=HPoz_ZQ( literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-5.glb b/Resources/Tests/SharedImages/plane-2-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..3651e61c49c03f274df4739630870374af22d247 GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&U8AephF#A*6sN>lk%Fe>hfRiE zhA<(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQSc?{tsB?`&YNSb^BZipmhZn{Z45+*LAxhGJ1T#cl89ERmx4jrBz=^npxDZ@NcD`R zv>KG^YFlk9U`iwvj3gB{55$N{k|Zpg@G^=s7)Rv6oREU{dBVjt33WH;jnM0V)q>{E ztfE#pmMtmF9o_;_?YHru1dBvN((HmexJ!~eCL_Ltxkmn7@6+c2q_E^=kkBbx(k1ew zD5(yg7IB?-E?CMWTh}I$Qm~+u%G{tke>$8E{V@o}L}Pz4>fIs_3OO5m>HGfV<5vs{ z0A>i7!8b^EzbQ>+=x91yt?vwss$ePb>vJhcRwP7f(ayV87ptdZI+jxnK2)e6X`X7jlxdMgRZ+ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-7.glb b/Resources/Tests/SharedImages/plane-2-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..b4a800a3be438c4d548546a3a6f9d345029751c6 GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7uUwMDIZ6-4ZE6rC{cmaJOO3P z&zlUs2w*-GTCFONjc=G+xKj7AI;!y%f|WdwV2G&I`T^h455o!kzR<4VeF?wE&-eG2 SZMpy4nM53(U0OHz^Rr)y8MSl( literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-8.glb b/Resources/Tests/SharedImages/plane-2-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..aa370f486fdf1f14b3a66a3d04fb93f0915a7c6d GIT binary patch literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXF1kinw+*`*d8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lgptIHh literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-2-9.glb b/Resources/Tests/SharedImages/plane-2-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..e7b47423138bb6cddbaa3d402cd3bf03e0bd438b GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mQ=(T15 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-0.glb b/Resources/Tests/SharedImages/plane-3-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..b7052e366bde9dfb5141a8e19608a2d0d6392613 GIT binary patch literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&gD3ImS@Yy)sqh;Dsfh%pltbh zlfjn}%!fj&mG;>9hPj0+bszOnjn@cP@koLpqE_n%d`mwJ$ME~YxPtd3{2o8w-(R-n R{&Qy%ad>uV-Qds9egS;lwPyeT literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-1.glb b/Resources/Tests/SharedImages/plane-3-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..9d072930811898df4fe326b11696917848dd5b9c GIT binary patch literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZMNmd3W4B1g^4cjuZoVJ9(9$=5T2icKi z=iY6M8Jy@y`p$PQ-*KGIudfWl`1#5(-obk_3nm+cqllCx<0bO%Hz=%DE5h$6c|g8x zqVP$OV6`fVP-eukg5@L^^QVF!|Fz?KCW=^T2-N?*$U?{)9JeHF3I&Zt) zzEWpUr*2h)?Yue0y1&78Yx({Q*0F);Flg^2XjkS>GaM70dc`S}O^`=XabjCV9?PE5 zjOanpSKD%10y8WKr#RKvBIIKtaGKI+%BncYVH}YUbAn6KXDQ>?I8xobZ-id|%N8_u z=9*gKXuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_Ca0%ry#bd!IfJAcZBX!j#PDk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&62D9OO7)(GgCYl7(ac_itDCB(br5^;-k6&$2 z05C(q6uv>K`%P)Ip`z(*mA-Q@bip#re}G!d|W;FP@xiMMGC@} zUpEuTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&RxqjJ9uPB literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-3.glb b/Resources/Tests/SharedImages/plane-3-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..888ce036664489bc196958e792820e62efca084c GIT binary patch literal 1512 zcmb_c>u%F96b?hYN9Es9noFr5f1s33>a;eaEfZ2zAt!Ycqa=3x6@k&^h>{?2zUpB<;O>np=Be!en{_we3KgYgF8C?aLac!~V^28GpXMfg1>56HJo z6g~+OtX3rv%8Xc6u$<&#_EZq$zjj>DL=mfUSnirgs97}a_ilR=WTFf|f>Un+Bbvj= zUTn={3g<5e2?qBqtmT#*vS1V(58m#aY%$geXeC-Hxe9s?dT~w!h&uj?=c< z1RQI^OUj^8$hB;=g@Bb9YGHs9Zb=@C1o=+KmW+F?9BSv1Ze->=FqB_l9!p7iowr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8IFlgz2X$gCdi|xII*oFk7dtj zM)X0^TibG40y8WKr#RKvBIF|?aGKI+!m2pQ;W#26&Iv9_pQVgn<4ARL-Uz+^%N8_y zW|~^!Xuc$H?(8iP)qWcfQm}|OB+V?igS|K{5wWq>fE1Rj3R5zrOR_}1 z;1yBflQOCEE;!AopzGSiLU0;ZLYf(T?l+~;hKi=MRr=1s&;`qYU#$y{^D@OkiFWQ;o-H5O+>hjyAVV&3R-_x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dAxQ44chGWXdt|uQ#RN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%UfwQm3b literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-5.glb b/Resources/Tests/SharedImages/plane-3-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..d9ac63cbf573e4342135140ac6082ab832ebf22d GIT binary patch literal 1508 zcmb_c?TXq!6iv7EJ!F1|I7w96P$<^A;G$-$b}5LINgU&pBoihRyNd{YfIem)q<1EX z-`kebKy>cRoO|xaxryT0?Tuj=KVKQfTX^rL{&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&C+P34GDC9*7#~l+%y^E&3?yx&SCQ8U7ICU4WVkxZf zVrQOG*ndF*jw_1Rp5me3bN2+cg-!Kh3m>RTTkoBdI{$+d$dq$Md_k)A&PkzAe=O!R znL6j0o)a#1utRd)r7enRN(GTzAP?O|(DTMh@T9SpGRwVYGh%XG1QxWi`IT%)6gTxH z;Mh=+GY*YHj)l<<0@h-vgaJwz(lnB5B(7!O)G*3LEEsLc0ZD8D2kQj&68Z#wRw zQWsFWYE_MGy*|Y{zrl7Y`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04wDY#rHeJib>T$}!hYIB+$zl+; z`nsv$^AN^EqxGuvSo?;tg)3DX%cBZkB6uM}1%`+kjm8i7mcAd1;rE4c1Mf@tJ$=5v UyKbxf=gt)3`0U!c#$TTO0&sk_L;wH) literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-6.glb b/Resources/Tests/SharedImages/plane-3-6.glb new file mode 100644 index 0000000000000000000000000000000000000000..2779bf34712a29ae2800aaf1bdadb4026c2bc172 GIT binary patch literal 1512 zcmb_c+m6#P5Dkm?kCmUJG&d^93zX88x@w!HEi0s|LQd)=MoAnwPIp&TB|d-;5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^M3q372TXW4ZkL{WO3PE18o1qQUT^9`?YoOaYD z;8+u0QU;Aewqc?z1gyl+2m_QbB6%zl*WzT3v z>OrZmcI37MW>^qTaav=GfRBm5X-dN>tKuYwaYP=>2`)*WrHtR;P<35b>7eU>EMpP_SQVsXMptBoya*yD zDt=NXP2vTo85MNhm{L|g5$hQu~4dAbd9blA6It_N3HC7{-IDM&WaTD zEkANHg))RGQ9jhF_S-VR^um>0lZeB!OY0VYe)bEOUbbHV literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-7.glb b/Resources/Tests/SharedImages/plane-3-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..b3912f5656640be1c409c0e96f5cfd856ffe203b GIT binary patch literal 1512 zcmb_c+m6~W5KXu0e;_|cAs5QFd1)zSC00PTKv$Izs+?egt0azWhusxI>Id{g`xzZO z3HNQQs*wU7+h@*P&LmEzH&=#X{Cs5?@8P|j1fvbYQAC7bTp)k8L1D365`Is~1M+PX zg-?+J{}%Qn?(TlSu+wDsO8srx@jflN79#Al>x@01h@^~Yj9 zlc{@}=_%oS3p-foE^SauGQzQB9Qo)rhMqT;oTZJGl)3Pm&6vtn5n9m7<~O{?anjV6 zfMZR#pbQ#?T+2pV2w01u5(X$?K(bh_kngl?#kkkXp>{6mMrFPOL-{4&-FN{SCHV$@gEdjtxYIL3<}byDER0;+T}F7o0-bBzcqtC$^PmvFaI3 zNjWIx)u!53z!XcuDNagk9`YfPI7w(UW<|WpU>uPTbAkowvV`#)9O-V(YN1#Ess;6( zX-TbcG+PjuJ9`U6mEYQf5-j31Nxcj1;4V(`6&|t$%ry$`I-frGAcX}h!h}rdf-I0P zc|mmeM69a3b52t#>AEtpl$?fzROSZV1(W`?AB;dSCK?6fVP}ARDCD&Fr5gm}k6&$2 z05C(q6uv>a`*mqbLr2rvYJF#5lm$zHU!O~kGm&7aMLWB;u5I;rre}G2<(A_QbrLwu z6Ogz1#;FuU1Vf^I=vC>rW`N;^D^)YgqY7UlSivI&hPWC(;CuR^KZ4&E#udCT;rICY V{_e7^_Mbabh{Ll>>l%N4_6vcawle?# literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-8.glb b/Resources/Tests/SharedImages/plane-3-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..f4ac1a0480c3f7654b06afdcccae72dc8b1c56ce GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Qw~3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq678Qj-(b77eE$V&nLu(-2Zvv(it%?qUK>}v9%LTezF}_RO5MlmsK!?aR`NiCA);362YgH4kEZbZLc4+YCH$T~-``!g S<^FSL5^;QXZQbB6&wc@o4YhXw literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-3-9.glb b/Resources/Tests/SharedImages/plane-3-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..73333b2175f1e3bd3baa36d9bb94df456638efc5 GIT binary patch literal 1512 zcmb_c?T*qw6s>D~4>G@-GHsDa|0p7xumQRPZcGR<(=wDXZKs(|aW{m-2k=3C4DU=^ zzE_R0Nn!5HoO|xaxsBr4?Tuj=KVKQfJ9zJ={&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&CUfYgqqL3FU9Jfs*^)8zBy2I`SnJ6KT;M85filwl^ zi=BB&VgCgMIIbvKdy0pC&)pN)7BiiEX$}IPq&4|f$5m?a5=2x;IQQXv* zfMY{N&Nws*ITl7c2w01u5(X$?NYhBJk!QECVmxT&SUcBrqcY!vq5P7FNJ+|Vb=vNs zQWsFWYE_MGy*b4?zrnUE`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04wDXSD!SL9oYq@HHopSo2OgTxi z7}TvkaVmp6gc;E;^s01Q6Ts}km8zHJQH3uNybz%RLs*T*5BQ$GAB^Gmg>eJ#OZYu~ WzQ4V0tNrKB6yo^o+PcPHp8W!djkYBK literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-0.glb b/Resources/Tests/SharedImages/plane-4-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..17b6f8b5e8bf2f2e30eea240041d383dfbda60c6 GIT binary patch literal 1500 zcmb_c?T(W`6vZ{ZM>D?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcA=8 N5XWcN)(!sh>=!@4v&sMf literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-1.glb b/Resources/Tests/SharedImages/plane-4-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..69b3a0cc968c9af542ae3cac6890766754727527 GIT binary patch literal 1500 zcmb_c?QWYe6lI0JOM_w$y!B`_5gdtJsU*mM?YLbNMXV~|xMw1%chP*%ANOa-L^*y0r`;v2XaOsG zxiya|oWGy|#|?$IqqrXqx;p~LvQ72amc6H{ZM}C&>i!Q>AXCm2@foSwJ0*oe{jr$O zWa^$~dP=z5!VVU?OB|q!%8Z1gYJU)XfX<=pcoTPgW04%Mm|(>G5kCTg4u^JHb?-d zAz}(2A>IDAHnpL{@wQsv1sHY3a^TnJlH)>TSZdWmc^uQTyn5!LI0ENo2AWnMHWjvr zU@|mLuWDbdSD0A1(sZysYVb9JRXkE)h~)=-L*I|4@P1)j!SfQ{$G`9IF57DVxif`0 MJiD}R@#km109QA&`2YX_ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-2.glb b/Resources/Tests/SharedImages/plane-4-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..3666813a657a2c1670e48e5c7d05ea3d6c7255ba GIT binary patch literal 1500 zcmb_c?T(W`6vZ{ZM>D?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcHvsC=~-Si@=zOrvmymatM8f$ zTSPDz8mCv4uf{73EL^D@SRK{)3c*SqDKNy+`T^h2_rnRiUl=#=yoC4Z@B6#!w%UL0 OOd*cXuB{vV<=HPou(Qqp literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-3.glb b/Resources/Tests/SharedImages/plane-4-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..81484dcb5c90b81a4b52576268e3f5de7ecb0582 GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dAIEG`&$JSlLttTH!RN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%UTwQm3b literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-4.glb b/Resources/Tests/SharedImages/plane-4-4.glb new file mode 100644 index 0000000000000000000000000000000000000000..0e156d7f198d315c5ee44e5b4c61433c86cacfa1 GIT binary patch literal 1500 zcmb_c?W)r-7_LXWkCES{Yr8tMe`L-b*kN0b+dK$kNZMs>q-{!)&T|+8FThLmVtkXf z`#vh-AYI-h&-=U|PZFo|+Z)3$e!emc560arm~0V_A|eFi0{M$A3d{AH@CQo1A>TGp z_#{cNUJD|%8L_NjImzYxsUXOI?YJEiMXbzWyK5q;SJABB8}+8hL>Ybrr=2CtXbv-b zxie2GoWGy|$2Enwr+66jJ9`4hvQ0H?%RW$5wq83Yb^iw`kSXVi_<~feos&YL{#eXs zGIh^0Jtti5U#~WzZ<(S~l82z*-EoFhB_-lE*SZzT?@7@t~Ds?OfB1%6t!o@=MHPC8>`0w%a*W z>H_N2t!l8|n^Ua&8*I0h@4sLj8;FjB?wkefs{Cn&V^XDFatdXW^wVaav)EkdKMPX-cChE8`@G>xg`~Cs>d^OBuh#k?!WA5qkZvTF{)C zSJVne^A&-6XYYWh_S<++fZfX-t$!SQw{YDs4xZ2iWDTR-ZgDU za4|Gas9EK!@mfLZ23A`&zCy5)M+ywFw0^)F`hGZp?+fDw#!L8~{=UDvZmad@_7viH L@7lbuTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&U8AeJhFi}(6sN>lk%Fe>hfRiE zhA<(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQScuPLb3zK*=Lr|rB-GuUH$t!fRSTLs zvx-{bShl1vcX$g#wco~r5-bu8NwW*?;4Vq>n2h)m<{J5Ty-%M9kiwFeK|-f&Nteiz zqNF-}TEunUxnL=iY+ai~O2L9sDszMG{ONEu^v56=6OH}JsCSDzDCBJLrSJQbk6$q; z0GJ_Q2Hzmv{iZaPp`&rPTHhHMRl!o=*XL4@tVoE|qMdiGE>@3gx|UlFK2)e6X`X7j}BJN&o-= literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-7.glb b/Resources/Tests/SharedImages/plane-4-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..e1d4befddbb889c33bee1545db0938bb327038b5 GIT binary patch literal 1508 zcmb_c?TVW~6t!FW9$|iq(P*=+6iS+AAxjdsNxBq5D5Dv5iaIim*db{d}cq@8G?id6NyoK|qRv@dCMv4f4w{B>bL|2jrSM z@}C6p!muDhnE^|4mXS=%pK^lS*H*{YQNYR!j=MS%Y8TD=y-{z9bd=&paB44MMKf5L z%dLJ)Vf_UKIIbz29mV~iZ|?{!!_?(#8s?s=ven)xY3F~C0-1C!iO)#o-YF>*>W{^I zCezMorl*9KG>4LcmH4wJ<;lBa%fTMy}3GygPPAntOBH1&V zl4?+@t8KY0fhiV*Q=C-T+~;E=aFWnq%E~CtU>uPPbAk)fX9?psI8fbOG(xZcWeb`+ z^NL#HXtp9Scjgv|YQK#K2}HahIoO4}ILTu?W-FL$x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXF1kincMZ21d8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lkfV0{F literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-4-9.glb b/Resources/Tests/SharedImages/plane-4-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..b4369264d79c146b7bb3feb055fa49e08a601d8d GIT binary patch literal 1508 zcmb_c?TVW~6t!FW9$|iqQDZ|Rg_5RO$dbfuk}ic1%4kNNqK=GXx=RT30s5GIklqOJq9M=@ip5kFR=qjven)>sr^4lflNA=#22J;@0=70^~Yj9 zlc{~4={ezQ2Rpb>UD~3EWQ1eEIC9Zl1U+wsoTaT;h`eyy?TCuF^bKfb`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`OtMJC$h90(G9I*YtetDRk(uwoP=0}VBqh~xdfm>U zQWsFGZdHSI-kf6X-(b77eE$V&nLuCe#u2$NC%7O3mN0&c1J%uCBlP-TwxGGQ zsHi25W@`d-XYPQg_S<-nK*Sr8qg}X*lRU-~wuZSz-hKb$ry-=UW~HByIbD-Aazlui zsQ5_{*NNwxrc}^PZDJuf^-CemExPySqs7RZf?{+u^=6a)7`af%#qjgM^JX8um>>b5 zhKMPAgjD;R+Ej)L$Jr@?XJAwnOMzdV3y!lQ!9uBa*)w`3JeKYlPBr*Yp#rCQ0>YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mU(6wp+ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-5-0.glb b/Resources/Tests/SharedImages/plane-5-0.glb new file mode 100644 index 0000000000000000000000000000000000000000..c95fcb4cffbebdc8db0dcc30fbcd8d2523e404d6 GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&avZxxx1M<@PKmQ31x?Ejn+&@Q zVKNj>t!iJbSD0A1(sZysYVb9JRXmhnh~)=-LqCir@P4IT!Sfp4$G`9IFWYker89{* MJiD}R@#km109P-w`2YX_ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-5-1.glb b/Resources/Tests/SharedImages/plane-5-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..16c81779b71e36a80f91e67bd31e0bfe891fea33 GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9znm2<4Y1Y{~1}9LP`>MNmd3W4B1g^4cju3oVJ9(9$=5ShuM*A zH`i`sEMP}R(s#ad`HtgsaeZYN#?Lo~(She?9!$3gM-dT%ae@5%Eegx^n(#YH9*}RF zD14G6Sg!?<+KgCMu$<&_@l+7xcOBO=QN+p|j(aAOdKb+H{c(SWOqAhAaOy2#MRQo$ z%bj^l;rs;!IIbx=&lGpVf%i<{ShlGi+p_mmm96(qN!|ZJ3S`Q;B0eKkd#9vOs6Q6- zg-qSkOiu}yJJ`WOcWH}ak`s<4_QEeue?nB=icknePC#kkkXp>{6mMrHmChVo0yVQ*(_&f8c2)i~!!fB+FFA#>N%AO5PHd~lW7RX7 zk!nz?t8KNdfEkvAQ=C@VBIFYyahlR-#>zO!VH}YUbAkmKu$1v@9O-V}H$t!fRSTLs zi;7y|XucvaclHj5YQK#KC0N88l4cj&!Cjmd37)VO%ry#b`=34!A%zty!<5YFimZ?? zc}aBmL?m_I1*aL6bW@vHN>0O4Dszi&gZXGN3Z@_!6HSBJq(4SJ6ml{AG6;g%$FDXh z0GJ_Q3f~~z{iZaPp`+>Sw7zpNs)A*}ug@jNxk$0pqJ`=>X4mRgBM-F^I4e?+wEC{8 zutfxep>cXu`D(nvz`~Wffz?rsuMn){kpe?3tsn3W{VuTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&wqfgCqg&5B6sN>lk%Fe>hfRiE zhAcRoO|xaxryT0?Tuj=KVKQfTX^rL{&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&C+P34GDC9*7#~l+%y^E&3?yx&SCQ8U7ICU4WVkxZf zVrQOG*ndF*jw_1Rp5me3bN2+cg-!Kh3m>RTTkoBdI{$+d$dq$Md_k)A&PkzAe=O!R znL6j0o)a#1utRd)r7enRN(GTzAP?O|(DTMh@T9SpGRwVYGh%XG1QxWi`IT%)6gTxH z;Mh=+GY*YHj)l<<0@h-vgaJwz(lnB5B(7!O)G*3LEEsLc0ZD8D2kQj&68Z#wRw zQWsFWYE_MGy*|Y{zrl7Y`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04v~$;Tv3eY{ZMDn6hYIB+$zl+; z`nsv$^AN^EqxGuvSo?;tg)3DX%cBZkB6uM}1%`+kjm8i7mcAd1;rE4c1Mf@tJ$=5v UyKbxf=gt)3`0U!c#$TTO0&rZlL;wH) literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-5-4.glb b/Resources/Tests/SharedImages/plane-5-4.glb new file mode 100644 index 0000000000000000000000000000000000000000..f85acea0917ecd50f4d2b422fa66a50c4b4bee9b GIT binary patch literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&uHoukqg&5B6sN>lk%Fe>hfRiE zhA(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ^LShl01kd+y1cXcGxDw_3squvzhD8*0U)LFueW-v3C zJN=Zx`U?thTvNDviibhp*%MfXsmoy+=7FlR)!I3!{Xa;7Ogfju7o>9SoD>T6$6~&a zsePX5IpJ~#E4WZy+MJT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2<_SnzzH7=3 z;bJIUA!n7Z#%l$s8(3}C_zJ;F9!fC8()s~!=!fA1zOS?!7_Z@b`uqO=x-HjV+LMUm Ly=(IZf4TPyONg`9 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-5-6.glb b/Resources/Tests/SharedImages/plane-5-6.glb new file mode 100644 index 0000000000000000000000000000000000000000..c33df9153a2f8959d33c6428253e2f61bb4d82bd GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Smq3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdPzu*RmZQ1+2{AxThnbcF}y$ANOZSM=5>;r_KsiG=r77 z+Uchh)?ZM7zgr+63+oIQbMn7VvT!#q$`w%R)(-2Zvv(it%?tB`R>5C!lQk zd6U5x0nCR&t5xN(@eOkeSL!}iM>W1eu#yK73=y?jKj2&Xel&&O7upTHFX8v}`Tp*@ SE%%>0lZfN9YwHGodG-sA<+XVL literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-5-7.glb b/Resources/Tests/SharedImages/plane-5-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..b5542077807f81832e30683d6bfee9261830caf6 GIT binary patch literal 1508 zcmb_c?TXq!6iv7EJ!F1|I5FzBp-`-K!9~qh?NSgalQ@Fho0s5GIklvXj z>*uzmG!UITGv}WBac;tResg6Q#?M!V@fP0OnK#)Y5(G5QxyX^b*do7Juc^3a^c!-q ziTp=Nz4bb$Qkwx!GM>^@&L1<1+>YIHOcd}Uh2yS?q~1ldes9#9A`>O#0h~HZSg{mV zc)2r=DeS+X0LK+Y`-$Ry(085)Yzv#}#TMREmA2kHC2jo=QXo^#74aFV+B+qMLjAFr z&t%#<&GeLTxq}^&>n?3kNK-0^Lg<2LyI|x{dp%MltVMNnVM##0>STXLka;Tk4x>1=wfua18h)_w&X}{?@ z`%0Zb?W$EZw*C4T+xiW*Tgmrdur>yw!=Rm$pj#?`mXMH^sTYDl*(80Cg`n8V(opq` zCA1ur@@i9UD_}w-6^z6sHuJ@pN)pE`nDQcwQW!_%!kmzt_Ib?34GDBN7q!r|vg&)%yThSGA zr6{NlpXO1OcP3cEB->Obky0?fkjmVmJ8w3e54{Np#zYfuI_`~-3x%8yKKDIu`r!)( z1pqSy%-|cOyI+^4G;}oWoz`~>Mp>`~`1QFIB+X+YwP+XD)-_g-V|J`gIrvbaoFrKc z!d7256?`7Rcxbdx$Yy6i&DFJ(l?$VlFB+e~Ps(xTx8xT?!&)5+})&BoihRyNd{YfIea$re`LJ z_ian5kT_>%zVn^ScM>IwyBkf@e!kJPHaz!pZ@NV|2uM*dULbe5MSdBEgg;X94Y{U{ z{AWSDFf52rX28;%Wh4`e=bRw7W7&?50#;^l+|`j#yJ$Y>kNY#EqZB`ZQ)dM$n!(Ck z?etR$>n|w4aZSgd_gMr&PkzAe=Oz; zncC->o)fNiu!9TLr7em`MmQFXBNyFA(DU1nv-CC=A}`!lE21JUeFIwA`ieI=N?Pg? zaBK)KD1$~J+c41%0#;(Eg#k(!lPnT3a;>&084p@H*3LEE$jtX(D8Ilwl9F=TZ@bQ+ zQWsFGZdHSA_fE0)Z?N53zW;)?OdvWA+Bpl_mif~ZN2E%<cavDHU{6n^*`={ZdGCiyplBXfg7ppcoxZz1gHcMlMuxG5kF6yxE5@CP)CN zAz}(2A=Un-HkF~mY44Q4Gcc-(rNFPw1;<&DV4+mI>=`}1V|1#ShvF1C%@feH{IJQe zivT7=;nb?~)p&)8g)4OjtD_oUAy~-+35HmHz&G^$XbSIF+6_Ff;eGo1{_eUh_g^}b Nh~u+s>jr;$_6ug;vuTFD6!tpy9!0;6V>=fb{~1}9LP{EUNmd3S4B1g^4cjuZoVJ9(9$*i-N7<2N z=iY6M8Jy@y`p$PQ-*J@8Zm$f(`1#H-Zs5I}`r{44VMvOC@dA1C4GPNDittBDo{(po zD0mUXU#$uvlo_%#XBo-F>?J42Yddb!L?J6PIPRE8s9iMe_eQ-5GEs`3!D({=E1JQ| zUTn={3g<5)uj!JNJcmoj3W=-MbPu>6=&&nEJR*-jYdR8Tm}}jvhfYCag;RF zCE!>SUQh;&Lat?_Ed;E@PzeK+Fd|tbV&pk3TQcsoa;Tk4x{;ahz)*gHc_by(Y;`-$ zeWlKzPSvU!+qyZ%y1&78D*65k*0F);FzDt<(5}p%rZ^%x^^#L4n;_4kG2`i&GgKq+FxSX`=zaP;fE1Rj3=%S>OR_|s z;3ZMvlOnG2&N)q~pzF%SLU0@FMZ#ieEe#I z0)QC;rtl3?-LFfd4HZpmtMr|Lp$nD*zd9EjXGMaA679Teb!~VYvu(BY+(Us1oaPDW zT7KMQ>_rIEp}=aT9oDX4YT-)NMSWD^HG-8qlwb(x2Yg3A49D>M#<+s_E&Luo-``)h S<^F4D5^;ETXuTFD6!tpy9znm2<4YDcGZ4Qp zeWlKzPTi^o+j(=0b$^5H*7E%qtYZVwVbI=5(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aiqFAZ-id|%N8_u zW|~^!Xuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_CT}%ry$`dY?WIAcZBX!jw$uk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&61=Hbd7>q$MCK?BmQSTP{P{`TfOFsxEAHUk5 z0APlIDSU%e_nXpaLq*fsDt+f*=z?Xyug(R>d6{CNL_3$sb*-)~pN<}Us8ETsA_ZZ~ zubT|Mj9@$zTCKFl#y5;DT&dfrk7~R|u!=_#3=y?jtsn3${V*KE?+fD!-k0!u{Ct0Z T*_Qjyok_&u*`;-ZKR^2gZbr32 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-1.glb b/Resources/Tests/SharedImages/plane-6-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..2c03127adf883478932512a62de7c3901aaffed3 GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYrES)>F2Yh|l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(l`byKZpRY9S9lSSlf4V_93`tosULx;)gMuoG2*0D`0ePm5 zf+s=zC@P6iX2`OFn7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=?VNgj&?d6sKR#=TY!wR1@~GV>i6$}cdFrKBA9ZO_?P z>I`Z%t!lCEn`5l~8*HzU@4sLz6NnCjc20t}W&Sk7F{x9pIEAta@+c}!EThO{*)y7v zdQj@CUAZlR85V?7oYvSP;1eQnn$mE_syNAE9FYfef=e=BDdX2TRNcI9h2H$j7PNO3 zHMPXid_`dH%qyPj0(DLOe_SaK_#TQLAU;Vv>5qQP>hbI{%q18BM&OM7=9V}{_Nvd6C?oC z5HW?1kZONho7zy}xLYOg9E`eR8Stxf!Es)uSSZy(c`UtW^i27<_2ff|N}LrbC|iEs zWbkDO^P$jcReNlG!`#A^rjPYegRc>+;-LgXM4iqL_?CVcP2u;2b_MTC_&t8UzrSqD R{pZdk;_&R!y2YQL{Q`!~wQ&Fd literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-2.glb b/Resources/Tests/SharedImages/plane-6-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..d41d5b589e5caf6801b9d2dab35c045befd86181 GIT binary patch literal 1508 zcmb_c?P}XF6!kjx9znm2BimWn%wS|$3Moz8C0QASFl0xuHEhesa@rCCdw@OW9%NUt zo$qdA%-}><(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQSc?{tsB?`&YNSb^BZipmhZn{Z45+*LAxhGJ1T#cl89ERmx4jrBz=^npxDZ@NcD`R zv>KG^YFlk9U`iwvj3gB{55$N{k|Zpg@G^=s7)Rv6oREU{dBVjt33WH;jnM0V)q>{E ztfE#pmMtmF9o_;_?YHru1dBvN((HmexJ!~eCL_Ltxkmn7@6+c2q_E^=kkBbx(k1ew zD5(yg7IB?-E?CMWTh}I$Qm~+u%G{tke>$8E{V@o}L}Pz4>fIs_3OO5m>HGfV<5vs{ z0A>i7!8b^EzbQ>+=x91yt?vwss$ePb>vJhcRwP7f(as&qF}qe5tEW>9K2)e6X`X7jjm$MgRZ+ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-3.glb b/Resources/Tests/SharedImages/plane-6-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..91e683543dcf883c76eb7327245c6c0413489bf9 GIT binary patch literal 1512 zcmb_c+m6#P5Dkm?kCmUJG&d^93zX88x@w!HEi0s|LQd)=MoAnwPIp&TB|d-;5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^M3q372TXW4ZkL{WO3PE18o1qQUT^9`?YoOaYD z;8+u0QU;Aewqc?z1gyl+2m_QbB6%zl*WzT3v z>OrZmcI37MW>^qTaav=GfRBm5X-dN>tKuYwaYP=>2`)*WrHtR;P<35b>7eU>EMpP_SQVsXMptBoya*yD zDt=NXP2vTo85MNhm{L|g5$hQu~4dAIEG`&$JV<>SFPN7{-IDM&WaTD zEkANHg))RGQ9jhF_S-VR^um>0lZeB!OY0VYe)bEO4z^za literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-4.glb b/Resources/Tests/SharedImages/plane-6-4.glb new file mode 100644 index 0000000000000000000000000000000000000000..7336fd6535a6ed885281ababd7c3292d5e9c42a0 GIT binary patch literal 1508 zcmb_c?P}XF6!kjx9znm2BRgK$%wS|$3Moz8C0QASFl0xuHEhesa@rCCdw@OW9%NUt zo$qdA%-}><(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQScuPLb3zK*=Lr|rB-GuUH$t!fRSTLs zvx-{bShl1vcX$g#wco~r5-bu8NwW*?;4Vq>n2h)m<{J5Ty-%M9kiwFeK|-f&Nteiz zqNF-}TEunUxnL=iY+ai~O2L9sDszMG{ONEu^v56=6OH}JsCSDzDCBJLrSJQbk6$q; z0GJ_Q2Hzmv{iZaPp`&rPTHhHMRl!o=*XL4@tVoE|qMf^zYj&+JR*zc^K2)e6X`X7j|Z~N&o-= literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-5.glb b/Resources/Tests/SharedImages/plane-6-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..5283d5b7c43f57378feb3665a551e40b91ae0bf0 GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Smq3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdPzu*RmZQ1+2{AxThnbcF}y$ANOZSM=5>;r_KsiG=r77 z+Uchh)?ZM7zgr+63+oIQbMn7VvT!#q$`w%R)(-2Zvv(it%?5C!lQk zd6U5x0nCR&t5xN(@eOkeSL!}iM>W1eu#yK73=y?jKj2&Xel&&O7upTHFX8v}`Tp*@ SE%%>0lZfN9YwHGodG-sA<+XVL literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-6.glb b/Resources/Tests/SharedImages/plane-6-6.glb new file mode 100644 index 0000000000000000000000000000000000000000..ac18670568591b3027f4cdbd632c0b9b00560afe GIT binary patch literal 1512 zcmb_c>u%F96b?hYN9Es9noFr5f1s33>a;eaEfZ2zAt!Ycqa=3x6@k&^h>{?2zUpB<;O>np=Be!en{_we3KgYgF8C?aLac!~V^28GpXMfg1>56HJo z6g~+OtX3rv%8Xc6u$<&#_EZq$zjj>DL=mfUSnirgs97}a_ilR=WTFf|f>Un+Bbvj= zUTn={3g<5e2?qBqtmT#*vS1V(58m#aY%$geXeC-Hxe9s?dT~w!h&uj?=c< z1RQI^OUj^8$hB;=g@Bb9YGHs9Zb=@C1o=+KmW+F?9BSv1Ze->=FqB_l9!p7iowr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8IFlgz2X$gCdi|xII*oFk7dtj zM)X0^TibG40y8WKr#RKvBIF|?aGKI+!m2pQ;W#26&Iv9_pQVgn<4ARL-Uz+^%N8_y zW|~^!Xuc$H?(8iP)qWcfQm}|OB+V?igS|K{5wWq>fE1Rj3R5zrOR_}1 z;1yBflQOCEE;!AopzGSiLU0;ZLYf(T?l+~;hKi=MRr=1s&;`qYU#$y{^D@OkiFV$#y0&~e=6)ov1Q~LPvmym~ z%R5fl5gZcbqm(1Z#%lztcqG9PSL+Adryqu6_`Wc%;C%_-u%F96b?hY$K>Bpnj0PD50uhLo7!fyWkRYd*WzT3v z>OrZmcI37MW>^qTaav=GfRBj4X-dNhtKuYwaYP=>2`)*WrHo(WP<35b>5|Zx`<3v`FxXEn%*af7ko;c>pOaSrw#YN|$7bya*yD zDt=NXP2vTo85MNZm{gJN_v_9vs>5P49^+2Bjx_a`5}njit7 zhKMPAgjD<6+SG;$$K5D_=U~(o%Ya{<3y$+L#X_leeq~&l@^SR8(N!zAo_{D*iL)XF zeanxWOrZ>6N|X<^s{OVMFuic4>1KV@;A;e{cqqXT*AMufez+OK?+fh$-k0!u_ephmW6 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-8.glb b/Resources/Tests/SharedImages/plane-6-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..2a1b3ae5e9acf0b6794054820f6ae5710761b8dd GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|m|Mk$Lb28b7d2b8OF^Ve;+RZHGGQ{YyNJ*S=wtRldS;S% z-?o$nqH|{EJKwo{Cs8uLz0ox7=PONn3-8^`pKK8hLsArs7sy*|QBbbegg;R74SA-H zf+s=z^|~NJnITJamXS=%pK^k{*Ou+*C}d>@#~mFBwTou`-l#W4I!f^)ICYk=q8Y5r zOJq9M=@?p5kH9clHF9Ve0ZV4f8-%*=p~c)czl&Kqj3_;tNu_cTNh0`eQMl z$<#j2^qg?HgB@I`E^SdnGQzQ79C_$2f}Xe5oTaT;h`jLH?TCuF3=C*x`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`M6yW4$g^BiG9I*YtetDRk(uwoP=0}VBqimzZ#vGQ zQWsFGZdHSIyQf(DH`q=s-+#ecCJ-G5?VJT|%lv7IBT}VaatdV=Y+j3h1Q!EIlIH|CCz{f=3B%$Gyl~J6*I3f?`1Q(>w62@JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob%N-}k2`)1xtZnoePe$BEdq5cF{GuddKLP@^P!dhYA%q%@Yu| z{JP2Dix9>`q1CGL*!YIAg)4O%tD_oUAy~;n35JMTt=13tmcAcO;P-`g1Mf@tJ$=5v UyKc+<=guVJ`0U!c!C#*J0)B?IQUCw| literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-6-9.glb b/Resources/Tests/SharedImages/plane-6-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..975cf35dcce6f3b63f8a644e39a22f963942f6b5 GIT binary patch literal 1512 zcmb_c>yFYu6s~K04>G@-(hCylA4OynHb7UvjR_%UTBhxow$n_fxEn&^1NfjmhG(WN z_f=zTQkXL{-}%nvJB^aX?Tw~sKVNCuJ9zKr-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?Sk6)HNHZyk_QqDVYPn1_w@Z}3coM38+c#B@9FdX U?R8u3KX)b($7k2p4gT`%7pFx$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7jhi0jcZdrPBr;Zq5`LR0?L-3 zHyL~pzuTFD6!tpy9znm2<4cxqW-ziWg_b1llB^6$7_y_-8n$I*Ic*7nJ-{Aw53(c4 z&b`|hGdR(a^qucqzT-HZU0oQ4@$;2oyo2{<8jRNnM-eGY#!KYiuTfYnmxSL@@_>BX zMB$Sl!E#v=q0ES71Zes9>DAQNTy5uAE+SkW9- z_IzU=QaFD>0gh{m&X(eC(D$|kj%AzjwJm!`rERr$OzQp*QXrGgCGiQV+&dQiT?@vi%LOaGbW) zCE!>QUQz~)Lat?_4Fs&jPzwW;FeG^_668A_TQcsnvag+Ux{;Z0!BBpIc`PO6b>4Qp zU8PQ-PTi^o+j(<{b$^5H*7E%qtYZVwe$d`g(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aHP6<-w3__mn~@S z%rv#c(R@K*?(7W^)qWcfQm}|OB+V|kgS$8_5Avg^yAtGCmG0`}fjCw=lLm_8_Fa02xeEe#I z0)QC;rtl3?-ET^x4HZpiqx7AFp$nD)zd9Ej=Vgk85-n86F)yu4TRxs1e5g=~vmymy z%deXZzKmcz6k4sc$Hq5|EnKPFsE=yAMzD%U5)2WwTCE@OE&XskhTj*)1-vie_wf1t U{=6;spF5L?{j+oH27h|?3vpGoMF0Q* literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-7-2.glb b/Resources/Tests/SharedImages/plane-7-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..4277e4d44c9356deb4077a77ec817b0de31f6a7b GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7q(&R*T%IeAE%mpC{cmaJOO3P z&zlUs2w*-GTCFONjc=G+xKj7AI;!y%f|WdwV2G&I`T^h455o!kzR<4VeF?wE&-eG2 SZMpy4nM53(U0OHz^Rr)x*|l^4 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-7-3.glb b/Resources/Tests/SharedImages/plane-7-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..a16497fd90219fcb5389bcf970dcf4cd62ff4844 GIT binary patch literal 1512 zcmb_c+m6~W5KXu0e;_|cAs5QFd1)zSC00PTKv$Izs+?egt0azWhusxI>Id{g`xzZO z3HNQQs*wU7+h@*P&LmEzH&=#X{Cs5?@8P|j1fvbYQAC7bTp)k8L1D365`Is~1M+PX zg-?+J{}%Qn?(TlSu+wDsO8srx@jflN79#Al>x@01h@^~Yj9 zlc{@}=_%oS3p-foE^SauGQzQB9Qo)rhMqT;oTZJGl)3Pm&6vtn5n9m7<~O{?anjV6 zfMZR#pbQ#?T+2pV2w01u5(X$?K(bh_kngl?#kkkXp>{6mMrFPOL-{4&-FN{SCHV$@gEdjtxYIL3<}byDER0;+T}F7o0-bBzcqtC$^PmvFaI3 zNjWIx)u!53z!XcuDNagk9`YfPI7w(UW<|WpU>uPTbAkowvV`#)9O-V(YN1#Ess;6( zX-TbcG+PjuJ9`U6mEYQf5-j31Nxcj1;4V(`6&|t$%ry$`I-frGAcX}h!h}rdf-I0P zc|mmeM69a3b52t#>AEtpl$?fzROSZV1(W`?AB;dSCK?6fVP}ARDCD&Fr5gm}k6&$2 z05C(q6uv>a`*mqbLr2rvYJF#5lm$zHU!O~kGm&7aMLY8>&sL9XUR&3C<(1rW`N;^D^)YgqY7UlSivI&hPWC(;CuR^KZ4&E#udCT;rICY V{_e7^_Mbabh{Ll>>l%N4_6vbVwle?# literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-7-4.glb b/Resources/Tests/SharedImages/plane-7-4.glb new file mode 100644 index 0000000000000000000000000000000000000000..df1f59836c150937f33d2530494a1f532a1bfb44 GIT binary patch literal 1508 zcmb_c?TVW~6t!FW9$|iq(P*=+6iS+AAxjdsNxBq5D5Dv5iaIim*db{d}cq@8G?id6NyoK|qRv@dCMv4f4w{B>bL|2jrSM z@}C6p!muDhnE^|4mXS=%pK^lS*H*{YQNYR!j=MS%Y8TD=y-{z9bd=&paB44MMKf5L z%dLJ)Vf_UKIIbz29mV~iZ|?{!!_?(#8s?s=ven)xY3F~C0-1C!iO)#o-YF>*>W{^I zCezMorl*9KG>4LcmH4wJ<;lBa%fTMy}3GygPPAntOBH1&V zl4?+@t8KY0fhiV*Q=C-T+~;E=aFWnq%E~CtU>uPPbAk)fX9?psI8fbOG(xZcWeb`+ z^NL#HXtp9Scjgv|YQK#K2}HahIoO4}ILTu?W-FL$@Fho0s5GIklvXj z>*uzmG!UITGv}WBac;tResg6Q#?M!V@fP0OnK#)Y5(G5QxyX^b*do7Juc^3a^c!-q ziTp=Nz4bb$Qkwx!GM>^@&L1<1+>YIHOcd}Uh2yS?q~1ldes9#9A`>O#0h~HZSg{mV zc)2r=DeS+X0LK+Y`-$Ry(085)Yzv#}#TMREmA2kHC2jo=QXo^#74aFV+B+qMLjAFr z&t%#<&GeLTxq}^&>n?3kNK-0^Lg<2LyI|x{dp%MltVMNnVM##0>STXLka;Tk4x>1=wfua18h)_w&X}{?@ z`%0Zb?W$EZw*C4T+xiW*Tgmrdur>yw!=Rm$pj#?`mXMH^sTYDl*(80Cg`n8V(opq` zCA1ur@@i9UD_}w-6^z6sHuJ@pN)pE`nDQcwQW!_%!kmzt_Ib?34GDBN7q!r|vg&)%yThSGA zr6{NlpXO1OcP3cEB->Obky0?fkjmVmJ8w3e54{Np#zYfuI_`~-3x%8yKKDIu`r!)( z1pqSy%-|cOyI+^4G;}oWoz`~>Mp>`~`1QFIB+X+YwP+U|t7BeU*H}GHIrvbaoFrKc z!d7256?`7Rcxbdu%F96b?hY$K>Bpnj0PD50uhLo7!fyWkRYd*WzT3v z>OrZmcI37MW>^qTaav=GfRBj4X-dNhtKuYwaYP=>2`)*WrHo(WP<35b>5|Zx`<3v`FxXEn%*af7ko;c>pOaSrw#YN|$7bya*yD zDt=NXP2vTo85MNZm{gJN_v_9vs>5P49^+2Bjx_a`5}njit7 zhKMPAgjD<6+SG;$$K5D_=U~(o%Ya{<3y$+L#X_le-Zi?Wd|ds?xKb;po_{D*iL)XF zeanxWOrZ>6N|X<^s{OVMFuic4>1KV@;A;e{cqqXT*AMufez+OK?+fh$-k0!u_epggu{ literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-7-7.glb b/Resources/Tests/SharedImages/plane-7-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..00de2f419649b4ce14431bdeecfca344225e6826 GIT binary patch literal 1512 zcmb_c?QYXB6b(bXN9Es9>NKT;{DD$BsZ-mGwoFJ>g`Ct)jFLEVoNiN9i3i|8c?_H&=#X{Cs5?@8P|jdgBcuK|u4IiyXQ04f2cCii&$iACQYp zkhjUWTJ#Tf>UP!BbLGl zFSh0}h5Z*4;JBh_?+wDsI6Y3qNG0-18Ih|fsX+$kv(>W{^I zCezkwrl*99EzFQycWHw{no>a|7sy4oA@sbl5OMCo8QQqgmF`E z0**BmIpfeM)Uq(zLcm%Kl`udFLz;#%LayD$igB-%L+xDBjmmrnhVn}yLM17u{kG%m zD|H67t5((6_M2mD>o?d=CEtI++8BrqgLY1WZmIlPLPA=mUI+$dlk`y*f?_L6L)9~u z(DI;^TbpWH0TUvrU?eWFnJ-3Ek~n6;gco6y!f`|{oD-7M9*?=WA%X7ZycT-(S1qXb z%t~s7W9gE@xx-r^s{Ga-lwgslN$Oc}2YX4JMP$U6aITSe*ZuUl4=F5p;m35!mUM|+ zDGI8?r+HN6oe7pO$<~!gq!i3Aq%t??&YKQq18)q1G11tYjJiYQLLq1UFFntjeEf<* z0l*9aGq{6v_v_M>hK{Da)%s4sC<~SVzh0Muq77R2`c2ABv}md zR(G7X12`nwN3KSt-RLv}xDtw9Hg$NWF;%fYW`}D(L4Br>V6}&Iud;ENVciC3+ Q&#fuM;o7BfjXz)e1&W8ZJOBUy literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-7-8.glb b/Resources/Tests/SharedImages/plane-7-8.glb new file mode 100644 index 0000000000000000000000000000000000000000..5d110b0a72afb7f166191f2500c13843254be4e4 GIT binary patch literal 1508 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 M@a)pM#h;)30$%U50RR91 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-2.glb b/Resources/Tests/SharedImages/plane-8-2.glb new file mode 100644 index 0000000000000000000000000000000000000000..996dcf6ea7039612203326b0d5c612b438d9a532 GIT binary patch literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE^NcryGFMfd8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lgaI@9` literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-3.glb b/Resources/Tests/SharedImages/plane-8-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..c01f821a031a193fb260cb7b123ad7b58de769ab GIT binary patch literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Qw~3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq678Qj-(b77eE$V&nLu(-2Zvv(it%?TezF}_RO5MlmsK!?aR`NiCA);362YgH4kEZbZLc4+YCH$T~-``!g S<^FSL5^;QXZQbB6&wc@nv9)&q literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-4.glb b/Resources/Tests/SharedImages/plane-8-4.glb new file mode 100644 index 0000000000000000000000000000000000000000..7c325c9d370634b6fb762edee6f31173813eac16 GIT binary patch literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE?mRayGFMfd8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lkV6)l) literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-5.glb b/Resources/Tests/SharedImages/plane-8-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..8916d5b2a57d2a1b5270e12acc065b34352fb112 GIT binary patch literal 1500 zcmb_c>x$Yy6i&DFJ(l?$VlFB+e~Ps(xTx8xT?!&)5+})&BoihRyNd{YfIea$re`LJ z_ian5kT_>%zVn^ScM>IwyBkf@e!kJPHaz!pZ@NV|2uM*dULbe5MSdBEgg;X94Y{U{ z{AWSDFf52rX28;%Wh4`e=bRw7W7&?50#;^l+|`j#yJ$Y>kNY#EqZB`ZQ)dM$n!(Ck z?etR$>n|w4aZSgd_gMr&PkzAe=Oz; zncC->o)fNiu!9TLr7em`MmQFXBNyFA(DU1nv-CC=A}`!lE21JUeFIwA`ieI=N?Pg? zaBK)KD1$~J+c41%0#;(Eg#k(!lPnT3a;>&084p@H*3LEE$jtX(D8Ilwl9F=TZ@bQ+ zQWsFGZdHSA_fE0)Z?N53zW;)?OdvWA+Bpl_mif~ZN2E%<cavDHU{6n^*`={ZdGCiyplBXfg7ppcoxZz1gHcMlMuxG5kF6yxE5@CP)CN zAz}(2A=Un-HkF~mY44Q4Gcc-(rNFPw1;<&DV4+mI>=+%rXY{I>hvF1C%@feH{IJQe zivT7=;nb?~)p&)8g)4OjtD_oUAy~-+35HmHz&G^$XbSIF+6_Ff;eGo1{_eUh_g^}b Nh~u+s>jr;$_6ugmvx$Yy6i&DFJ!F1|m|Mk$Lb28b7d2b8OF^Ve;+RZHGGQ{YyNJ*S=wtRldS;S% z-?o$nqH|{EJKwo{Cs8uLz0ox7=PONn3-8^`pKK8hLsArs7sy*|QBbbegg;R74SA-H zf+s=z^|~NJnITJamXS=%pK^k{*Ou+*C}d>@#~mFBwTou`-l#W4I!f^)ICYk=q8Y5r zOJq9M=@?p5kH9clHF9Ve0ZV4f8-%*=p~c)czl&Kqj3_;tNu_cTNh0`eQMl z$<#j2^qg?HgB@I`E^SdnGQzQ79C_$2f}Xe5oTaT;h`jLH?TCuF3=C*x`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`M6yW4$g^BiG9I*YtetDRk(uwoP=0}VBqimzZ#vGQ zQWsFGZdHSIyQf(DH`q=s-+#ecCJ-G5?VJT|%lv7IBT}VaatdV=Y+j3h1Q!EIlIH|CCz{f=3B%$Gyl~J6*I3f?`1Q(>w62@JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob%N-}k2`)1xtZnoePe$BEdq5cF{39rhHtzYjmr@hYA%q%@Yu| z{JP2Dix9>`q1CGL*!YIAg)4O%tD_oUAy~;n35JMTt=13tmcAcO;P-`g1Mf@tJ$=5v UyKc+<=guVJ`0U!c!C#*J0)9%hQUCw| literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-7.glb b/Resources/Tests/SharedImages/plane-8-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..1b60bb7f7c9f36e5d4e3b2037bc507b808074840 GIT binary patch literal 1508 zcmb_c>x$Dr6pkytN15Lt&82p05Tw>FbZMKlt&32KOwvg*l4QbUQgjTxi{S+90a5&7%z~!+##0_NJlAt0H@9hRy2c^ zx!UQ+6xLr*fa98?^F(n!95_z|mSO7hH4Sr5RoQCql+^woq(CN}OX4$9xpztmh5BPL zpUKod&GeLTwSyg8s4i_$L^8s$U>v#VHiDkFLeA1wEJR+o?RG>(T>1vIvi%iraFn#w zCE(Z)UQh;&LbhR|9R#ezPzwW;FeX_fV&qyKQ!?(ga;Tk4x{;YbfuZ~Y^GHg{>AdMW z`%0Zbt-4hWw$nSt+P}eeYx({Q)-r+UFlgr_Xj|q_Qyh^h^^#L4n;;LOJT!IQ2^*%`Lj~=A*^Pn}T9=H1%ea{usGX$;I&V!1HDwzL+2Z zpoWMke1uf{o7z-{3a7JE0?)vxDwYDjIu{&gMS_J=?ef~VHs#~!J)>7mK9s1yX`XyTXUj#583awU^$Hq6zEnKPlSRK{)3c*SqNH9dyYW;w3>HE(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ?_YuS#DLRMz5-O-Uyt7z8mje1k0qZB`ZQ)dY?n!(Im z?(|a%>n|w4aZTawDINxWXHQ@mrY?tRm^_WvLSGU;3rUy#bRb5bbOAB*`y zruKQJ=Y-20tl&a*X^SF~5sn4p$U}D#^t`p^EN#U?a7@t~Ds?OfB1%zO`q@(au(DJjQ&*KrP& zx`0}Bs~W7^J;mC;!FFo-{tMPJf#^7B=PYPj=1)@`kt+3)Qz)AtPom_+H1aHxJ)JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2<_SnzzH7=3 z;bJIUA!n7Z#%l$s8(3}C_zJ;F9!fC8()s~!=!fA1zOS?!7_Z@b`uqO=x-HjV+LMUm Ly=(IZf4TPyS3tAk literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-8-9.glb b/Resources/Tests/SharedImages/plane-8-9.glb new file mode 100644 index 0000000000000000000000000000000000000000..469cb3393075aa6e206313ef7b611c2e513e8faa GIT binary patch literal 1508 zcmb_c?TXq!6iv7EJ!F1|m_#cY3dLF%T-0pUE(MV?iIZeXk_nTEyNd{YfIem)q<1EX z-`kebKy>cRoO|xaxe4RN?Tw~sKi_EDJ9zKr?sSW=?~^=dJV(xQi@YKT2!EjD5jm!g zyk|k&AjpYOrq7a$r6d)L=Zqldt!1}#OJq99IW{^I zAyfN2({sYr4t8*^y0k?hNeRb-apa)85PIGSI7=Fl5Lxatn;{iZ;Th1%<~O{-Vcb-g zfMY{=P8l=`*@lUB5U>(MB@9r)n53bIkYlw?$#~Gpv39QMMrOVTL-_^fp_Ejs-Rrat zmAZghRjX=jyL*bYe}nB*^8FXAWdhN0(5_Ek5Rwa}}7*@F7c zqNJ8Mnyv}Vow);|%5UvK0uirCj&|WLjn3B*eHae~R;$us?Ha}wu2gL-k1BkLUx$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?ge|{r zGWH^X@las3svI`1VQk?_-Nx#u##abd@<4(ipjNB(1HPs2M^pHHq20jy5`Isg@9(bL Sa{swAi8wyHwr=p3XTJc9mbGF4 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-9-1.glb b/Resources/Tests/SharedImages/plane-9-1.glb new file mode 100644 index 0000000000000000000000000000000000000000..9ea21e3509d3cdea2abbf97aa592d9f5b01117fa GIT binary patch literal 1504 zcmb_c>uTFD6!tpy9!0;6<4YDA{~1}9LP`>MNmd3W4B1g^4cju3oVA3&9$*i-N7<2V z=iY6M8Jy@y`p$PQ-*KGIZ?6o)`1!^#Zs5I}1(OZJQAC7bTp<5(gTivPBK(1pZ^*Yz z6h2E5tX6_ZZAL6BSWa>|e=Z2}-#V^mqKK6_9Cu74^)8zAd!ybInJB|g;M7~disrDg z7hCg~!ubmda9mTgcN7nUzPBTAEZbDCZP|OO%GP_Qr0)M91v2Ga5ucH&y;D*s)E|ra zLZSyR<XIfeRGU;e}nDR^8FXAV*}A)(B4VVuF9WgI3`u zQVmLVwWYQdFvF5?iqi^PgnUdSPE#69Ss5oej3e@4POu<-mNI^eBi+r%M(FjwYC&^n zUQsI?&6fn`&fWr1?YHru1dDh>((HmexQo*w!DF_BxkkZ#@8hQdq_AXVn35S?k|pvb zFNqGHh@{TD;54I>u4@xZ$!S^B^||CY7b%unv``(#>{?wL9;cdnD3HKek%F$( z$4$j9BA5;h)~m{4;~J(GuGC$uj%s{`U?qx$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mQqP1oK literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-9-3.glb b/Resources/Tests/SharedImages/plane-9-3.glb new file mode 100644 index 0000000000000000000000000000000000000000..5f40c7b6e365db2f31169f8b1c45e43b392f27a6 GIT binary patch literal 1512 zcmb_c?T*qw6s>D~4>G@-GHsDa|0p7xumQRPZcGR<(=wDXZKs(|aW{m-2k=3C4DU=^ zzE_R0Nn!5HoO|xaxsBr4?Tuj=KVKQfJ9zJ={&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&CUfYgqqL3FU9Jfs*^)8zBy2I`SnJ6KT;M85filwl^ zi=BB&VgCgMIIbvKdy0pC&)pN)7BiiEX$}IPq&4|f$5m?a5=2x;IQQXv* zfMY{N&Nws*ITl7c2w01u5(X$?NYhBJk!QECVmxT&SUcBrqcY!vq5P7FNJ+|Vb=vNs zQWsFWYE_MGy*b4?zrnUE`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04v~$;Tv3eY{V|CQRE~g*Ll#?Wj zLEY*Tr!vSxm=WzluS&Ny0n9F3sd`x+RrnIY3lSOJq9M=@ip5kFR=qjven)>sr^4lflNA=#22J;@0=70^~Yj9 zlc{~4={ezQ2Rpb>UD~3EWQ1eEIC9Zl1U+wsoTaT;h`eyy?TCuF^bKfb`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`OtMJC$h90(G9I*YtetDRk(uwoP=0}VBqh~xdfm>U zQWsFGZdHSI-kf6X-(b77eE$V&nLuCe#u2$NC%7O3mN0&c1J%uCBlP-TwxGGQ zsHi25W@`d-XYPQg_S<-nK*Sr8qg}X*lRU-~wuZSz-hKb$ry-=UW~HByIbD-Aazlui zsQ5_{*NNwxrc}^PZDJuf^-CemExPySqs7RZf?{+u^=6a)7`af%#qjgM^JX8um>>b5 zhKMPAgjD;R+Ej)L$Jr@?XJAwnOMzdV3y!lQ!9uBa=@^dQGkPXGRyFugp#rCQ0>YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mUuC;0a literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-9-5.glb b/Resources/Tests/SharedImages/plane-9-5.glb new file mode 100644 index 0000000000000000000000000000000000000000..22909852b90854c08f146b388ed637922a7defdb GIT binary patch literal 1504 zcmb_c>uTFD6!tpy9!0;6V>=fb{~1}9LP{EUNmd3S4B1g^4cjuZoVJ9(9$*i-N7<2N z=iY6M8Jy@y`p$PQ-*J@8Zm$f(`1#H-Zs5I}`r{44VMvOC@dA1C4GPNDittBDo{(po zD0mUXU#$uvlo_%#XBo-F>?J42Yddb!L?J6PIPRE8s9iMe_eQ-5GEs`3!D({=E1JQ| zUTn={3g<5)uj!JNJcmoj3W=-MbPu>6=&&nEJR*-jYdR8Tm}}jvhfYCag;RF zCE!>SUQh;&Lat?_Ed;E@PzeK+Fd|tbV&pk3TQcsoa;Tk4x{;ahz)*gHc_by(Y;`-$ zeWlKzPSvU!+qyZ%y1&78D*65k*0F);FzDt<(5}p%rZ^%x^^#L4n;_4kG2`i&GgKq+FxSX`=zaP;fE1Rj3=%S>OR_|s z;3ZMvlOnG2&N)q~pzF%SLU0@FMZ#ieEe#I z0)QC;rtl3?-LFfd4HZpmtMr|Lp$nD*zd9EjXGMaA679Tgwau>8wc&B}+(Us1oaPDW zT7KMQ>_rIEp}=aT9oDX4YT-)NMSWD^HG-8qlwb(x2Yg3A49D>M#<+s_E&Luo-``)h S<^F4D5^;ETXyFYu6s~K04>G@-(hCylA4OynHb7UvjR_%UTBhxow$n_fxEn&^1NfjmhG(WN z_f=zTQkXL{-}%nvJB^aX?Tw~sKVNCuJ9zKr-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?z-gX< zyyZ7eW>5q$B+7+aRc;#s7+$ziH?umb@fCuVJdj`rtMvoEr|(Bo_!21$@PoM8^ UuiJ9}xig74KD)MV@Rw)50H-0gS^xk5 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/plane-9-7.glb b/Resources/Tests/SharedImages/plane-9-7.glb new file mode 100644 index 0000000000000000000000000000000000000000..77b088bfe57108c448c7760fed648c1872a09bc6 GIT binary patch literal 1512 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 literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/texture1.png b/Resources/Tests/SharedImages/texture1.png new file mode 100644 index 0000000000000000000000000000000000000000..9594525097353c3fefa248b0cc9732700e301425 GIT binary patch literal 57706 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 Y$@7zpj^cR82v(Y@vBf8uMvkGs0ipHazyJUM literal 0 HcmV?d00001 diff --git a/Resources/Tests/SharedImages/tileset.json b/Resources/Tests/SharedImages/tileset.json new file mode 100644 index 000000000..b7569c61b --- /dev/null +++ b/Resources/Tests/SharedImages/tileset.json @@ -0,0 +1,2228 @@ +{ + "asset": { + "version": "1.1" + }, + "geometricError": 4096, + "root": { + "boundingVolume": { + "box": [ + 5.4500005, + 0, + 5.4500005, + 5.450000286102295, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5.450000286102295 + ] + }, + "geometricError": 1024, + "children": [ + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 0.5, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-0-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 1.6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-1-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 2.7, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-2-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 3.8000001999999995, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-3-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 4.9, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-4-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 6, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-5-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 7.1000004, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-6-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 8.2000003, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-7-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 9.3, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-8-9.glb" + } + }, + { + "boundingVolume": { + "box": [ + 0.5, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-0.glb" + } + }, + { + "boundingVolume": { + "box": [ + 1.6, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-1.glb" + } + }, + { + "boundingVolume": { + "box": [ + 2.7, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-2.glb" + } + }, + { + "boundingVolume": { + "box": [ + 3.8000001999999995, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-3.glb" + } + }, + { + "boundingVolume": { + "box": [ + 4.9, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-4.glb" + } + }, + { + "boundingVolume": { + "box": [ + 6, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-5.glb" + } + }, + { + "boundingVolume": { + "box": [ + 7.1000004, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-6.glb" + } + }, + { + "boundingVolume": { + "box": [ + 8.2000003, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-7.glb" + } + }, + { + "boundingVolume": { + "box": [ + 9.3, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-8.glb" + } + }, + { + "boundingVolume": { + "box": [ + 10.400001, + 0, + 10.400001, + 0.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.5 + ] + }, + "geometricError": 512, + "content": { + "uri": "plane-9-9.glb" + } + } + ], + "refine": "ADD" + } +} \ No newline at end of file diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index d40159b08..92a5bf922 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -10,6 +10,7 @@ #include "CesiumTestHelpers.h" #include "Engine/World.h" #include "GlobeAwareDefaultPawn.h" +#include "Interfaces/IPluginManager.h" #include "Misc/AutomationTest.h" #include "Tests/AutomationCommon.h" #include "Tests/AutomationTestSettings.h" @@ -26,6 +27,16 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST( EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); static void setupForSharedImages(SceneGenerationContext& context) { + static FString Path = + IPluginManager::Get().FindPlugin(TEXT("CesiumForUnreal"))->GetBaseDir(); + // IPluginManager returns a relative path by default - convert it to an + // absolute path. + const FString FullPluginsPath = + IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*Path); + static FString TilesetPath = + TEXT("file://") / FullPluginsPath / + TEXT("Resources/Tests/SharedImages/tileset.json"); + context.setCommonProperties( FVector(21.16677692, -67.38013505, -6375355.1944), FVector(-12, -1300, -5), @@ -46,14 +57,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { ACesium3DTileset* tileset = context.world->SpawnActor(); tileset->SetTilesetSource(ETilesetSource::FromUrl); - // Unreal returns the relative path of the plugins directory by default - FString FullPluginsPath = - IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead( - *FPaths::ProjectPluginsDir()); - tileset->SetUrl(FString::Printf( - TEXT( - "file://%scesium-unreal/extern/cesium-native/Cesium3DTilesSelection/test/data/SharedImages/tileset.json"), - *FullPluginsPath)); + tileset->SetUrl(TilesetPath); tileset->SetActorLabel(TEXT("SharedImages")); tileset->SetGeoreference(georeference); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.cpp index 01fda781b..34dcdf0a0 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.cpp @@ -6,6 +6,7 @@ #if WITH_EDITOR #include "Editor/EditorPerformanceSettings.h" +#include "Interfaces/IPluginManager.h" #endif namespace CesiumTestHelpers { From 8020401328e6cd311d59184d0e41f9658751758f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 3 Oct 2024 13:18:53 +1000 Subject: [PATCH 36/68] Const correctness, doc. --- .../Private/CesiumGltfTextures.cpp | 9 +++++---- .../Private/ExtensionImageCesiumUnreal.cpp | 7 ++++--- .../Private/ExtensionImageCesiumUnreal.h | 20 +++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index a9c8b20fd..a6d93f014 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -16,7 +16,7 @@ using namespace CesiumGltf; namespace { // Determines if a glTF primitive is usable for our purposes. -bool isValidPrimitive(Model& gltf, MeshPrimitive& primitive); +bool isValidPrimitive(const Model& gltf, const MeshPrimitive& primitive); // Determines if an Accessor's componentType is valid for an index buffer. bool isSupportedIndexComponentType(int32_t componentType); @@ -169,8 +169,8 @@ bool isSupportedPrimitiveMode(int32_t primitiveMode) { // Determines if a glTF primitive is usable for our purposes. bool isValidPrimitive( - CesiumGltf::Model& gltf, - CesiumGltf::MeshPrimitive& primitive) { + const CesiumGltf::Model& gltf, + const CesiumGltf::MeshPrimitive& primitive) { if (!isSupportedPrimitiveMode(primitive.mode)) { // This primitive's mode is not supported. return false; @@ -190,7 +190,8 @@ bool isValidPrimitive( return false; } - Accessor* pIndexAccessor = Model::getSafe(&gltf.accessors, primitive.indices); + const Accessor* pIndexAccessor = + Model::getSafe(&gltf.accessors, primitive.indices); if (pIndexAccessor && !isSupportedIndexComponentType(pIndexAccessor->componentType)) { // This primitive's indices are not a supported type, so the primitive is diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp index bb0df6f8d..f1081af7e 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp @@ -75,9 +75,10 @@ ExtensionImageCesiumUnreal::getFuture() const { namespace { -// Returns a Future that will resolve when the image is loaded. It _may_ also -// return a Promise, in which case the calling thread is responsible for doing -// the loading and should resolve the Promise when it's done. +// Returns the ExtensionImageCesiumUnreal, which is created if it does not +// already exist. It _may_ also return a Promise, in which case the calling +// thread is responsible for doing the loading and should resolve the Promise +// when it's done. std::pair>> getOrCreateImageFuture( const AsyncSystem& asyncSystem, diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h index a9aeeda21..133361c94 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h +++ b/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h @@ -58,10 +58,30 @@ struct ExtensionImageCesiumUnreal { bool needsMipMaps, const std::optional& overridePixelFormat); + /** + * Constructs a new instance. + * + * @param future The future that will resolve when loading of the + * {@link getTextureResource} is complete. + */ ExtensionImageCesiumUnreal(const CesiumAsync::SharedFuture& future); + /** + * Gets the created texture resource. This resource should not be accessed or + * used before the future returned by {@link getFuture} resolves. + */ const TSharedPtr& getTextureResource() const; + + /** + * Gets the future that will resolve when loading of the + * {@link getTextureResource} is complete. This future will not reject. + */ CesiumAsync::SharedFuture& getFuture(); + + /** + * Gets the future that will resolve when loading of the + * {@link getTextureResource} is complete. This future will not reject. + */ const CesiumAsync::SharedFuture& getFuture() const; private: From b1b036855de657e74934b8daca0ce94c859de4dc Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 3 Oct 2024 15:26:52 +1000 Subject: [PATCH 37/68] Cleanup, make some useful warnings visible. --- .../CesiumRuntime/Private/CesiumGltfComponent.cpp | 13 +++++-------- Source/CesiumRuntime/Private/CesiumGltfComponent.h | 1 - Source/CesiumRuntime/Private/CesiumGltfTextures.h | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index cc535c220..931249e0d 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -1766,9 +1766,10 @@ static void loadIndexedPrimitive( } else { UE_LOG( LogCesium, - VeryVerbose, + Warning, TEXT( - "Skip loading primitive due to invalid component type in its index accessor.")); + "Ignoring a glTF primitive because the componentType (%d) of its indices is not supported."), + indexAccessorGltf.componentType); } } @@ -2132,8 +2133,8 @@ void applyGltfUpAxisTransform(const Model& model, glm::dmat4x4& rootTransform) { } else { UE_LOG( LogCesium, - VeryVerbose, - TEXT("Unknown gltfUpAxis value: {}"), + Warning, + TEXT("Ignoring unknown gltfUpAxis value: {}"), gltfUpAxisValue); } } @@ -3258,10 +3259,6 @@ UCesiumGltfComponent::UCesiumGltfComponent() : USceneComponent() { PrimaryComponentTick.bCanEverTick = false; } -UCesiumGltfComponent::~UCesiumGltfComponent() { - UE_LOG(LogCesium, VeryVerbose, TEXT("~UCesiumGltfComponent")); -} - void UCesiumGltfComponent::UpdateTransformFromCesium( const glm::dmat4& cesiumToUnrealTransform) { for (USceneComponent* pSceneComponent : this->GetAttachChildren()) { diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.h b/Source/CesiumRuntime/Private/CesiumGltfComponent.h index a55d8b57c..e78b2d273 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.h @@ -91,7 +91,6 @@ class UCesiumGltfComponent : public USceneComponent { bool createNavCollision); UCesiumGltfComponent(); - virtual ~UCesiumGltfComponent(); UPROPERTY(EditAnywhere, Category = "Cesium") UMaterialInterface* BaseMaterial = nullptr; diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.h b/Source/CesiumRuntime/Private/CesiumGltfTextures.h index 7f0326ad1..d3a7544d1 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.h +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.h @@ -15,8 +15,8 @@ struct Model; class CesiumGltfTextures { public: /** - * Creates all of the textures that are required by the given glTF, and adds - * `ExtensionUnrealTexture` to each. + * Creates all of the texture resources that are required by the given glTF, + * and adds `ExtensionImageCesiumUnreal` to each. */ static CesiumAsync::Future createInWorkerThread( const CesiumAsync::AsyncSystem& asyncSystem, From d9dffcd20bfad814aee9ba645f4e3fc46a53a673 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 3 Oct 2024 15:27:06 +1000 Subject: [PATCH 38/68] Fix metadata crash caused by failed move from const. --- Source/CesiumRuntime/Private/CesiumGltfComponent.cpp | 4 ++-- Source/CesiumRuntime/Private/CreateGltfOptions.h | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index 931249e0d..ea7d9821a 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -2242,8 +2242,8 @@ loadModelAnyThreadPart( return CesiumGltfTextures::createInWorkerThread(asyncSystem, *options.pModel) .thenInWorkerThread( - [transform, ellipsoid, options = std::move(options)]() - -> UCesiumGltfComponent::CreateOffGameThreadResult { + [transform, ellipsoid, options = std::move(options)]() mutable + -> UCesiumGltfComponent::CreateOffGameThreadResult { auto pHalf = MakeUnique(); loadModelMetadata(pHalf->loadModelResult, options); diff --git a/Source/CesiumRuntime/Private/CreateGltfOptions.h b/Source/CesiumRuntime/Private/CreateGltfOptions.h index 57923497a..c809652e7 100644 --- a/Source/CesiumRuntime/Private/CreateGltfOptions.h +++ b/Source/CesiumRuntime/Private/CreateGltfOptions.h @@ -41,6 +41,10 @@ struct CreateModelOptions { tileLoadResult(std::move(other.tileLoadResult)) { pModel = std::get_if(&this->tileLoadResult.contentKind); } + + CreateModelOptions(const CreateModelOptions&) = delete; + CreateModelOptions& operator=(const CreateModelOptions&) = delete; + CreateModelOptions& operator=(CreateModelOptions&&) = delete; }; struct CreateNodeOptions { From 6451ef3866780ff9681e788ca5003e8e07f97510 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 3 Oct 2024 15:54:48 -0400 Subject: [PATCH 39/68] Use ion for shared images test --- Resources/Tests/SharedImages/plane-0-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-0-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-0-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-0-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-0-9.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-1-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-1-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-1-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-1-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-1-9.glb | Bin 1504 -> 0 bytes Resources/Tests/SharedImages/plane-2-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-2-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-2-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-2-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-2-9.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-0.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-1.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-2.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-3.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-3-4.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-5.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-6.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-3-7.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-3-8.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-3-9.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-4-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-4-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-4-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-4-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-4-9.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-5-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-5-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-5-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-5-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-5-9.glb | Bin 1504 -> 0 bytes Resources/Tests/SharedImages/plane-6-0.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-1.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-2.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-3.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-6-4.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-5.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-6.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-6-7.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-6-8.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-6-9.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-7-0.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-1.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-2.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-3.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-7-4.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-5.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-6.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-7-7.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-7-8.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-7-9.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-8-0.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-1.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-2.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-3.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-8-4.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-5.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-6.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-8-7.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-8-8.glb | Bin 1500 -> 0 bytes Resources/Tests/SharedImages/plane-8-9.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-9-0.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-9-1.glb | Bin 1504 -> 0 bytes Resources/Tests/SharedImages/plane-9-2.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-9-3.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-9-4.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-9-5.glb | Bin 1504 -> 0 bytes Resources/Tests/SharedImages/plane-9-6.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-9-7.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/plane-9-8.glb | Bin 1508 -> 0 bytes Resources/Tests/SharedImages/plane-9-9.glb | Bin 1512 -> 0 bytes Resources/Tests/SharedImages/texture0.png | Bin 58902 -> 0 bytes Resources/Tests/SharedImages/texture1.png | Bin 57706 -> 0 bytes Resources/Tests/SharedImages/tileset.json | 2228 ----------------- .../Private/Tests/Cesium3DTileset.spec.cpp | 14 +- 104 files changed, 2 insertions(+), 2240 deletions(-) delete mode 100644 Resources/Tests/SharedImages/plane-0-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-0-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-1-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-2-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-3-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-4-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-5-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-6-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-7-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-8-9.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-0.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-1.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-2.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-3.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-4.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-5.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-6.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-7.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-8.glb delete mode 100644 Resources/Tests/SharedImages/plane-9-9.glb delete mode 100644 Resources/Tests/SharedImages/texture0.png delete mode 100644 Resources/Tests/SharedImages/texture1.png delete mode 100644 Resources/Tests/SharedImages/tileset.json diff --git a/Resources/Tests/SharedImages/plane-0-0.glb b/Resources/Tests/SharedImages/plane-0-0.glb deleted file mode 100644 index b3ac6b99d5b04e561dd1493cad7d5d7fae3e5415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 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 diff --git a/Resources/Tests/SharedImages/plane-0-5.glb b/Resources/Tests/SharedImages/plane-0-5.glb deleted file mode 100644 index b81e52b3c9da06b1f6fc10699d0b4dab12b3084d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE=A8edjw@#p(R!O4GETZ#2z?bvyGX8-&ANYo#b`9h@j zX{4uw%PnYdsj9R=G06$Xf^p=b+Zbwo9dVXjCqfjZ*XhJmBvoKQDLdcr8pmlzT>_3Z z;U#5IC}bNZ+JeKX8Jfm`P8gFs776k!*OZL=UODWYOMN3F-+>|h0`pk5l;ghZIr~hV zL9M1#E!KT|jJ1D*?KSQDFIdY2qC=;h6Q^w%Kh1DV>d-4rA#H*@iHZ};DDqg=jAo=B zl=^B%swFVPf^dq{8e0VXjtHElG@P<3PI4GW;Vw>#1mCe0%r)}w`=34!A%qpHf|ShYimZ?qfyYGo zPs*eTyx=sWg03483&Ckn2`O&Sy+0exNB#slMn@BWdeCC)ei``w^y60(S^%iQ zV+xOuDu0`sT2St|Th-t>2zACX;8*8@k_}VDRM!yrCaP6ZpQ;u3)`}@A2>Z`^&P_zjP)Mhi8}e IE&lxM7gtxa`2YX_ diff --git a/Resources/Tests/SharedImages/plane-0-6.glb b/Resources/Tests/SharedImages/plane-0-6.glb deleted file mode 100644 index 0927e2f83f5a3700e80fa79ec3b52ec82328e3d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_cZHn745Oufo9znm2_Iw` z?0j!qN)5I(lHR=0%p1k&?E1z=-BB zvKL$Pn8NuB3UFLgbaoW?gTA*Ta4g%DuWi|TDs8K|Q&RVTkOG->E{V@b<=iPLBkTyXcMa7A26?rUc zMl+%tMQ?4(X$j1*Ae`bu_H zduEzi;%L4k(0BG0h-$x$2Ps&@8!I)?qOh&z1bW(T0kqvsL=e!O#iIfM2Z(j`K3bLWy?XwYs)^I;t<_WgvqsaaN=t zY#VN8aMd!wO?+kwL$;@ diff --git a/Resources/Tests/SharedImages/plane-0-7.glb b/Resources/Tests/SharedImages/plane-0-7.glb deleted file mode 100644 index 42f64e1c345b96d513ebfb5a46ff912d86822503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$AjzCF^PTTpCraiwSDL2%e5Gmc;JuxBlMTW_K#GF#0=bI~^2;zJ{GO5r$U}Xl|T^$LfMYDcy)SDt5rT7tyI!jp53|8iH zs~=NXe?b9;Yl_Z};(pL~b_AAT>hd)Wb5B*tN_R?X{|{0ilg1_S8L8Brl0u^Xn9OG) zwNE2GC0uSng9}xq4T?xcI2Mc}7u`ls^H#`N+KPq93%A{lsEAA7fKs-<;WdtuwmJkH zYr+f4pisy*Otb}uRWsC$0i7@+StMfQS{+j|?tA61cP{mfjC==%^b5=**-}pDZP(dn z>I`btrE0L9H^*4}H`s37zW;)?OdvXR+BtFBmhsaRN2Ch9w62@Ar~?^AAIS1-t^;F6IuYM z!D9-SkSc$Zn@UjbbhfI&GZ3nbrNFQD1;<&DV4=Epacx|i@^RE)%G*FXTi`TLptI%m zrsM$TL!k?~sw_6PD@a|(YOBUq2v+hyg2AI!>j&JWABGe7zR<4VeF@*==llE1veZAf OClQBxm(~sbeD4>AHMMX6 diff --git a/Resources/Tests/SharedImages/plane-0-8.glb b/Resources/Tests/SharedImages/plane-0-8.glb deleted file mode 100644 index 307a4df5d40b7bc3026355f61227d9f19fc50ddb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_cTZ-E-6m_R`A3@*7@v9;APf61ZWJuy^k`9FsitH%1iftKL&dd-3T|k%E#q>(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ?_YuS#DLRMz5-O-Uyt7z8mje1k0qZB`ZQ)dY?n!(Im z?(|a%>n|w4aZTawDINxWXHQ@mrY?tRm^_WvLSGU;3rUy#bRb5bbOAB*`y zruKQJ=Y-20tl&a*X^SF~5sn4p$U}D#^t`p^EN#U?a7@t~Ds?OfB1%zO`q@(au(DJjQ&*KrP& zx`0}Bs~W7^J;mC;!FFo-{tMPJf#^7B=PYPj=1)@`kt+3)Qz)AtPom_+H1aHxJ)JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2`K~ED zgo~kYg`8Es8m|?kZeX=l<0}L!c__gUOX~-`p&y15_`cF^V7!L!>F@jd>$Y5fX-^`K L_pZ$w{N>&+N5Hev diff --git a/Resources/Tests/SharedImages/plane-0-9.glb b/Resources/Tests/SharedImages/plane-0-9.glb deleted file mode 100644 index e351c781242929e76e0185d40602d68e0c1697dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x!E|6t-LX9$|iq@sfr{3MEamkR^%RBwY$2l+lbjMI9N(be9n51N1TbAU!i` z?%S49L7j7EzVn^yM9JdzM$@#PuQcr~ymxbNx@KTz@wxu%Z% zCqcY0EQnBIz|x#$Bom9LoFMnLWji_wSee0gS4To=(R|P!_h(2)DSiZ_&I(pEgO$13 z>8BLdUr>PInxeC(co+_xJ%MGIx_nK;JWy4#(w&pq|AQ3Bq;W}nK`M3Uq>!jTCi9s{ z?ej>_30FJN;6hbtiz1Q{js@e$MRyU@ycKemwqhaj!fm%BD&o>Npp@;ec!Q&)tquXl zhVX(iC={{{6YaoZ)eLoGKqrhz7Ks?SR>zc#hh91Eoojs~Bj1A|{Q~nywv^N9b)7?| zE}&Ljss`J6bBeWpgYDMs`!86_1fpZ7oinFx89z;NM5@qBP9bfAJc^PN%gD1x){Lg4 z8kFj2TdE~6#e#5(lM0*rd_n|H5*o}{8O0flBXVI*a6twvVf+>cs+!BD(d)k~L33tN zQA-@n)&%Cx+yPN-x3M6Bh&LohS~!c7JjN5YhPg)GegEU9A%w7IrJs;FU6VC(L-3d= z|49+of#;m2RM1T=Vj(#7OCiNAy7%Uz#mJjN$LMJ4%_jXZav_t8;pc(p%|3iFp#^{% zJf?67sq#0usRZRtXQvuG1EI=T3jAtcaGVth7OHEPJ)>vBW2w26mx1)Pz-gX9W6Rr3 z$pMUq0vB>s8Ei~fkh+Z3R*kO^tmJ_NgF~%W>j&JW??+SkzR+&qeF@*w=li?sveZAf OClSYc*VYaGa_<+70JUNO diff --git a/Resources/Tests/SharedImages/plane-1-0.glb b/Resources/Tests/SharedImages/plane-1-0.glb deleted file mode 100644 index 53f8c61939e17278517655b238cb899b2652d9de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c+lt#T5OufoKNdZk#J7glFC|U0kR=;$lXNMBP-I88Rcy=1a=J?h^aJ{d{g{p< zJNIo%X~2#~(wQ@tGfuMk&6QyoKVKP!2hZ&+m~0S^B2rb1SIA#%P*}$?;SZF2L%wCA z@JWy$jw>RR8L_-%1u4Y*sU*mMZ9A@sB32i0+%u6-yJ$A(kNZ<(q8vYhQ+Ej~TENO$ zZp~u~`!6WKaYNzlC?19bcSm4%EK@#K$J$eATkV~aI{$+d$fR>gd`2qwPD!Cqe=O!R znL4MLo)Rv%u!Aerr433*K{ytSBOl!+(DUnOo~LL$hSR9GVZlT|Z+q^( zQfE-RX;q8$-W+3{-(Y)c))t5wzpV!eM7$+A*oC_|D^q;ORxsBnxbJ`bG=vmZtPV3WqbssPehd*4 z6+fxcCh?NfoC>;bOe_SaVJ)P&LHEIIG#>>MP>hKt!St>_Mm|(>KKwigg6W4Z7Dxc7 zAz}(2A=Uo2Hri0(cv~g#0t{WT9Qf6_;JBzVER<>&dK_&%^H7`$=Vb<(mLE15b``;7 zD4bepU#(Y|Sh&)3P#-mTjbJs8Bp71(0pHN~qY1oU7+3JTg!l39`@74w+<)#&A`Z_k Jty}#0*)LCbv+)1` diff --git a/Resources/Tests/SharedImages/plane-1-1.glb b/Resources/Tests/SharedImages/plane-1-1.glb deleted file mode 100644 index 655d287f64c970ee9693547e4a96e00af035ded7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c+lt#T5OufoKZ2f3;!7G@zmzo1LY8d2P12>Xgd#hNtzuh7lG9y6pdZjr?8kH@ z*|~39N&|K@lFppDoNQ5WK~c3@5q?j}HxxKF zik>71S1UoJHe;5TtRRJ)Ka~UpuU*f#QOv3Wj(awedKb+G{c(SaY?R|iaOy8$MGIIt zi>-Z3;r;~$IIbzWJBs_^z~2$LZO2xR({}b$#@2hMq~8A^1v2Ga5ucH&y;D*s)E|ra zOs3vxrl*99E$m>SyR<sB?`?wez*_Zw`lmhZn{T?dE`gZ58?_Ei2f#|bg1SDZrGBzcq-Cr-O864f)B z6Ei60YDaA=V2&l>6lVroM*NmYoMkkgvMNan7)KPqoM1r)EMxo{$GV#jjnM0V)q>{E z+)yhVEtUl4&e;M{?YHru1dDh>((HmexQnwg#kXt;bB)5g{>M*4NMXsUC?hkvBuf-X zUJ)HW5ow)w$!Sg{UDqa-lGCV?%G{v4a5kEc!U+h*Mw4)Q+aIF<3OOHs9)#ia!xsk> z0L&0Dg>R7Vep4D_=xDlIt?vR1Q?MNP^||D@5E+(Qv``%vrqMMc5490EFEfy|`mU+4 zMGS+Xae8HZHC|z0;Y!^=b5!FEf>k_LV2Gvl1HPf}M-zCzu&&^F3Gd_I_ji|Vwg23i NLL8o5S~vLfvtK*tv&H}b diff --git a/Resources/Tests/SharedImages/plane-1-2.glb b/Resources/Tests/SharedImages/plane-1-2.glb deleted file mode 100644 index b5047f4619f4d6687c104577a3971d9153b3225b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c+lt#T5OufoKNdY3$Cos;ekp01g)G^4o1{x2gd#h#tzuh7lG9y6pdZjr?8kH@ z+qrLBN&|K@lFppDoN|i(`1#5(y71i2g2@KqC?Y~IE|9<2ps-j2YrY*Rh9W$&qKTkoBcy8nX|$dq$Md`7DFPD!Cqe=O!R znYyQ$o)Rv%u!Du}(gr1@ARJ4^k&kW@==pWbS$>^LSqi_?NvKS#(1KQWzT!1bvW~t4 z9BaY_WzZ<(S~l83z*-EAFhB`oQY11(zSFf8<6bL=+PS0~mH7?~<(HTzN>X0;ZO_|R z>I~{Mt!lB|H^*4_H`rby-+#e6HV_>K?VSYes{Cn=6H=#MaSCOV@5&Aep?R;h>MP>hKt!St>_Mm|(>KKwigg6W4ZHb?-d zAz}(2A>IDAHnpL{>29^a3oz=6<-o7cCC7!xu+*wuxRwj!=+rY0#Su6!Gtjj9u&J;` z1e2k0dR6;sy~4!8m8OIBQG>4$tm2UZLo7ew8~T1Uf%gmJ3Z9qnKK^}wciC3^&z&j6 M;n}5ii$6d61yf_Q^#A|> diff --git a/Resources/Tests/SharedImages/plane-1-3.glb b/Resources/Tests/SharedImages/plane-1-3.glb deleted file mode 100644 index 1c796fb9019f726c22d4563ca4cac0c1276d57fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZ4Qp zeWlKzPTi^o+j(=0b$^5H*7E%qtYZVwVbI=5(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aiqFAZ-id|%N8_u zW|~^!Xuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_CT}%ry$`dY?WIAcZBX!jw$uk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&61=Hbd7>q$MCK?BmQSTP{P{`TfOFsxEAHUk5 z0APlIDSU%e_nXpaLq*fsDt+f*=z?Xyug(R>d6{CNL_7B^&z6r1GwJBThYFQAD^d`) z{JP2D%Lv9pq18%zY<$Dm!j-y>`l!Zh1gm%?!4Ofa)%pS7(htKi{Jt=*;C%_d$Itio Tmu0JOM_w$y!B`_5gdtJ|i(`1#5(Ja}$r!DNGQ6cHg97sy|1P*}w=;SZF2L%waI z@JW&&js=n0j96Z>f)sN8R1)OBcHFLsB32b}+%u8XyJ$A(kNZ<(q8vYh)9w;hw1Abp z+?vM}&R-5r5r*`|7I%idGfw%$7>b^iw`kSXVi_>5HTosvSK{#eXs zGIdWgJtbUjVFwG{r433*K{%F-BOl!+(DUn=WF378 zIM##<%AirmwQRJ7fVCJJVSp0Gq)23ne8;mD<6bL=+PS0~mH7?~<(HTzN>W|#ZLhnp z)EU%iTGe8`H^*4_H`rby-+#e6HV_>K-8~7~Rr%8#C!|ij;uOjz$)l_|ajddPRL^Km z>OrZmcGR{4=2#L=aaLo?klzuBvy4VlRwZcx7eU>EM|q!%8Z1gYJXbXg&%ipcoTPg6Um+Y>)s@ zL&Ow5Lc0BJZE8b@<88IT3oz=6<-o7cCC7!xu+*wuc$Np_=+rY0#Su6!Gtjj9u&J;` z1e2k0dR6;sy~4!8m8OIBQG>4$tm2UZLo7ew8~T1Uf%gmJ3Z9qnKK^}wciC3^&z&j6 M;n}5ii$6d61y@hA`2YX_ diff --git a/Resources/Tests/SharedImages/plane-1-5.glb b/Resources/Tests/SharedImages/plane-1-5.glb deleted file mode 100644 index fed618ea974ede9b0bd7c06fca5a1bf5ead9ba57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>x$Yy6i&DFJ!F1|n48k(PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-gv1~_2AuBUD?&?UWT{P?WM!hN0QHr0ysk4L?&0u9N zcls%X^%oT2xTbLT6c2;GvnQ|&Qa7@t~Ds?OfB1%zO`q@(au(DJjQ&*L4n+ zx`0}Bs~W8P_7rRX2HUOW`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE;>dBrqQZK9%@tIG*3X%^1CL( zEwJ_x=5KTkgMf OCK1PH*VYaG^6VEw$g|D> diff --git a/Resources/Tests/SharedImages/plane-1-6.glb b/Resources/Tests/SharedImages/plane-1-6.glb deleted file mode 100644 index 783d80d0007864368467dff5e4434c185df6c79b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dA^o*V<9~XwwswW>xRN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%a}wQ&Fd diff --git a/Resources/Tests/SharedImages/plane-1-7.glb b/Resources/Tests/SharedImages/plane-1-7.glb deleted file mode 100644 index d143bd58e73a56fd7dc7299f2eb7f08e5aee1422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TXq!6iv7EJ!F1|I5F03L!nsff{U81+NB^;CUH!rB$+UoxVwnZ2k2w=L3(GB z_`Pi@4MgY8%(>@&oSQJ7-CP-l@$;2oyo2|4>Ww#u1Od%+E^_3~H^?tmD=O|8eLyZY zk^dyAw_4>?YBS(T##5Tg*;7W5``T_fCJK0w!g0q$QtzT^uRH8ckckrV2u_^^tXK*w zyx5w@6!u?Gfa8jyy`#AA_naMpZDCWr*us0N($;&Yq^0ifm3;pNYhxff4B9yfx~1}G2?=SLdLbB;P0~kM2#T#N4OP!r zLd!uZuQt`T0wzRK!AM+UGhd9TByr4w2`|DZg>ghK%n8Y9kH=ixkU)2HUJJeYS1qXT z%t~s7W9gE@+~F+{Reoy^O0Y=OB=s)1gS#ZoA~NDjm}}(Sbw7RXLkde?_%WTbC0!y{ zih}C!X&zO1XM!b6vUOz=DFyQjsmu+!^QME@z#D^LOf>c;qwWy7P{>*TOV9HrAHQNy z05C(q48B3S`*mqbLr2rzYJI0*lm$zGU!O}s(mW+7fXtZ9H9&6t)ws56tV|i5JO9U@OpuiAOqtW;Q-_j3*G5o$TuHbzMzsJw_ U_m^$8|J<2E9G+cT*ZA|ZUva6mMF0Q* diff --git a/Resources/Tests/SharedImages/plane-1-8.glb b/Resources/Tests/SharedImages/plane-1-8.glb deleted file mode 100644 index c94389505158c66c3f81a64625c5672c076d41d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9!0;6w!Qt6OIMr$V0a=^!z&FEW1vGC`zx>iK$4cz<^eEzTq{F(~i0X z9BaZ$%AirmHcYgIfRz{;VSp0GB#%XcJj*pD<6bL=+PS0~nfVS3ZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&J);NXXw@?h#VK)Cq@Zc}VUuB( zAxwtCsa5T(^$HUUSDFsiM-9G4u!@Hg46*!xZ|H~71m3T-D|lYR`}p_${bgJ3zjP)M Mhi8}8E&lxM7hfl|0RR91 diff --git a/Resources/Tests/SharedImages/plane-1-9.glb b/Resources/Tests/SharedImages/plane-1-9.glb deleted file mode 100644 index 3f2257e8a1d8191d1c1da07f6faeee83c945defe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1504 zcmb_c>uTFD6!tpy9!0;6<4YDA{~1}9LP{EUNmd3S4B1g^4cju3oVA3&9$*i-N7<2V z=iY6M8Jy@y`p$PQ-*KGIZm$f(`1!^#Zs5I}2ICFFQAC7bTp)kGL1DRC5&lTYH{{zU z3ZEqjRx3fIHY1i5EGM~~Jr@M|Zynb&QN+p|jyoojdKXRmy-{z1OqAg#aOy2!MRQo$ zi>-M~;rs;!IIbz$JBr6a-`f#5mTjunw(LDsW$V3DQulw50-18Ih|fsX-YF>*>W{^I zAyfA>(^JC57Iv`EUD}|StQWnB*wPGrhGPIzTt*>~E_QEeue?h~%+Mkngl@#kkkXp>{6mMrFPOL-{4bl2a&~Bu}#B#I}k&Rz0H` zsRpIG+EUvJm|;mc#c72tLViyqPE#69SQ#fdj3e@4POu<-mNI^eBi+q;BlP-TwV=5( ztEd%@=1T%|XK#V1_S<++fu4@xZ$!S}?~RZTg`5pO_k&>a;foCl z0A>i7!Z%2FzbQ>+=xEwot?wL+s$d!L>vPF*E>bMDXy;w4Ys2HfNIKQrLxBX&iWGFM zK5i;@5y5n5uwGRT8`m(kaHZ~IbyVXk1S@%@z!1<6_>R6Gj^X!}aRu*d_&t8Uzq@R! R{g=)Z;_&R!y1}2H{Q^|?w66dF diff --git a/Resources/Tests/SharedImages/plane-2-0.glb b/Resources/Tests/SharedImages/plane-2-0.glb deleted file mode 100644 index 582ddad0f62c63992412408c7b1cdbbff783612c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c+lt#T5OufoKZ2f(<4YP^zmzo1LY5@nCh1ZLp~#M6tJs#2i!Q>AXCm2@dc^cJ12!g{jr$O zWa^%0dQQ09!44L>OIs9^oNz1|M?SiXq35kNXIU$evJif|9aEW^wVaav)EkdKMPX-cChE8`@GaYR1M2^OT!QpRs_q`SFjgkJxv7BqL} z6}7_Ad_`dH>>Uu*ej5);u!uJ#%`UiuyErWpJZ3AHYZTn~K7JZN3M*EIDVfm~Ss`EY zlIZY>Nb0-`PBSX$rZ%yZoQ9=T<`&%tv*COgOh7Oungr8vZ-jg(W1eu#!g#46(F+z&G^$a02fa#tl3#;eGo1{_eW1_Mbab Nh~u+s>jr;$_6s}Uv&H}b diff --git a/Resources/Tests/SharedImages/plane-2-1.glb b/Resources/Tests/SharedImages/plane-2-1.glb deleted file mode 100644 index 21e6499216d916c745904fbf11f379f5bc5666a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c+lt#T5OufoKNdY3$Cos;ekp01g)B+DP12>Xgd#h#tzuh7lG9y6pdZjr?8kH@ z+qrLBN&|K@lFppDoNb^iw`kSXVi_>5HTosvSK{#eXs zGIdWgJtbUjVFwG{r433*K{%F-BOl!+(DUn=WF378 zIM##<%AirmwQRJ7fVCJJVSp0Gq)23ne5Y$G#=TY!wR1@~D)Su}$}cfbl%%}w+n%?t z)EU%iTGe8^Z;r9OrZmcGR{4=2#L=aaLo?kWYxjSw^E7tCF;UaYR1M2^M6)GRALkq`Udh3cdMPEokp7 zYHEd}#fre(*;^oL{I(tx5b>7eU>EMhMD!EDkWBOfZc7=9iE!R*5q8zcbK z5HW?1kZyllo7&LfbhldI1sHY3a^TnJlH)>TSZdWmc^uQV+ diff --git a/Resources/Tests/SharedImages/plane-2-2.glb b/Resources/Tests/SharedImages/plane-2-2.glb deleted file mode 100644 index 661b2c5dc047ffa113379cf11fb25a1d2d357646..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_cTaME(6b*yeN9FG*&7)M1A5=<5>Zomowv3Rf3OT8p7$tFJJ7q>yB^JOESq#@s z()R#DRC4NT``mLM$Bxta?Tuj=KVKO}2gcnjm~0V_A|eFi0{M$A3d{AH@CQo1A>TGp z_#{cNUJD|%8L_NjImzYxsUXOI?YN$aB39KV;Q zby2FLZMCd`8J2`ooL1N(>$~ej4IwBwL2^OT!QpRs_q`SFjgkJxv7Bpw( z6}7_Ad_~~i**hSr{WczyU=eRfnpJQIXK`92c+6ICuTgN{`}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLXc!PBJo6=NuTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&OOVs<>Q*JD?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcHvo`=~`|z@=zOrvmymatM8f$ zTSPDz8mCv4uf{73EL^D@SRK{)3c*SqDKNy+`T^h2_rnRiUl=#=yoC4Z@B6#!w%UL0 OOd*cXuB{vV<=HPoz_ZQ( diff --git a/Resources/Tests/SharedImages/plane-2-5.glb b/Resources/Tests/SharedImages/plane-2-5.glb deleted file mode 100644 index 3651e61c49c03f274df4739630870374af22d247..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&U8AephF#A*6sN>lk%Fe>hfRiE zhA<(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQSc?{tsB?`&YNSb^BZipmhZn{Z45+*LAxhGJ1T#cl89ERmx4jrBz=^npxDZ@NcD`R zv>KG^YFlk9U`iwvj3gB{55$N{k|Zpg@G^=s7)Rv6oREU{dBVjt33WH;jnM0V)q>{E ztfE#pmMtmF9o_;_?YHru1dBvN((HmexJ!~eCL_Ltxkmn7@6+c2q_E^=kkBbx(k1ew zD5(yg7IB?-E?CMWTh}I$Qm~+u%G{tke>$8E{V@o}L}Pz4>fIs_3OO5m>HGfV<5vs{ z0A>i7!8b^EzbQ>+=x91yt?vwss$ePb>vJhcRwP7f(ayV87ptdZI+jxnK2)e6X`X7jlxdMgRZ+ diff --git a/Resources/Tests/SharedImages/plane-2-7.glb b/Resources/Tests/SharedImages/plane-2-7.glb deleted file mode 100644 index b4a800a3be438c4d548546a3a6f9d345029751c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7uUwMDIZ6-4ZE6rC{cmaJOO3P z&zlUs2w*-GTCFONjc=G+xKj7AI;!y%f|WdwV2G&I`T^h455o!kzR<4VeF?wE&-eG2 SZMpy4nM53(U0OHz^Rr)y8MSl( diff --git a/Resources/Tests/SharedImages/plane-2-8.glb b/Resources/Tests/SharedImages/plane-2-8.glb deleted file mode 100644 index aa370f486fdf1f14b3a66a3d04fb93f0915a7c6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXF1kinw+*`*d8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lgptIHh diff --git a/Resources/Tests/SharedImages/plane-2-9.glb b/Resources/Tests/SharedImages/plane-2-9.glb deleted file mode 100644 index e7b47423138bb6cddbaa3d402cd3bf03e0bd438b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mQ=(T15 diff --git a/Resources/Tests/SharedImages/plane-3-0.glb b/Resources/Tests/SharedImages/plane-3-0.glb deleted file mode 100644 index b7052e366bde9dfb5141a8e19608a2d0d6392613..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&gD3ImS@Yy)sqh;Dsfh%pltbh zlfjn}%!fj&mG;>9hPj0+bszOnjn@cP@koLpqE_n%d`mwJ$ME~YxPtd3{2o8w-(R-n R{&Qy%ad>uV-Qds9egS;lwPyeT diff --git a/Resources/Tests/SharedImages/plane-3-1.glb b/Resources/Tests/SharedImages/plane-3-1.glb deleted file mode 100644 index 9d072930811898df4fe326b11696917848dd5b9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>uTFD6!tpy9znm2<4YDcGZMNmd3W4B1g^4cjuZoVJ9(9$=5T2icKi z=iY6M8Jy@y`p$PQ-*KGIudfWl`1#5(-obk_3nm+cqllCx<0bO%Hz=%DE5h$6c|g8x zqVP$OV6`fVP-eukg5@L^^QVF!|Fz?KCW=^T2-N?*$U?{)9JeHF3I&Zt) zzEWpUr*2h)?Yue0y1&78Yx({Q*0F);Flg^2XjkS>GaM70dc`S}O^`=XabjCV9?PE5 zjOanpSKD%10y8WKr#RKvBIIKtaGKI+%BncYVH}YUbAn6KXDQ>?I8xobZ-id|%N8_u z=9*gKXuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_Ca0%ry#bd!IfJAcZBX!j#PDk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&62D9OO7)(GgCYl7(ac_itDCB(br5^;-k6&$2 z05C(q6uv>K`%P)Ip`z(*mA-Q@bip#re}G!d|W;FP@xiMMGC@} zUpEuTFD6!tpy9znm2<4YDcGZ*>W{^I zCR6t`(^JC57Itu{y0k$F$qC1Tapa?$1bW_zIm=orA&Sy(w-YK>RcJvg+u!gSCuv(< z0**D|C1ubk=FqB_lo=8b~owr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8BT~!z2X$gCdi|xIB~2ZPh`(% zM)aWQt8KY0ff*KrQ=DpS5%LicI8A9ZVO6rqVH}YUbAn6KXDQ>?I8xo5H$t!fWeb`+ zGfgdVG+z>!J9`U6wco~r1R~y$9PGkfoE9rQVoR876x{VbeI7sxOIC#`nbIX$B0q+R ziHe_;t2*(5(~Jtbu1zcir(q?exj}cqbT}IZV^EBV#=&INyG1@!ayIzV4}!_ZuQo^k zs3BqsA0gHLrZ(D8;dHi2;5it&Vj1wObHQ<5rdTM|&RxqjJ9uPB diff --git a/Resources/Tests/SharedImages/plane-3-3.glb b/Resources/Tests/SharedImages/plane-3-3.glb deleted file mode 100644 index 888ce036664489bc196958e792820e62efca084c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c>u%F96b?hYN9Es9noFr5f1s33>a;eaEfZ2zAt!Ycqa=3x6@k&^h>{?2zUpB<;O>np=Be!en{_we3KgYgF8C?aLac!~V^28GpXMfg1>56HJo z6g~+OtX3rv%8Xc6u$<&#_EZq$zjj>DL=mfUSnirgs97}a_ilR=WTFf|f>Un+Bbvj= zUTn={3g<5e2?qBqtmT#*vS1V(58m#aY%$geXeC-Hxe9s?dT~w!h&uj?=c< z1RQI^OUj^8$hB;=g@Bb9YGHs9Zb=@C1o=+KmW+F?9BSv1Ze->=FqB_l9!p7iowr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8IFlgz2X$gCdi|xII*oFk7dtj zM)X0^TibG40y8WKr#RKvBIF|?aGKI+!m2pQ;W#26&Iv9_pQVgn<4ARL-Uz+^%N8_y zW|~^!Xuc$H?(8iP)qWcfQm}|OB+V?igS|K{5wWq>fE1Rj3R5zrOR_}1 z;1yBflQOCEE;!AopzGSiLU0;ZLYf(T?l+~;hKi=MRr=1s&;`qYU#$y{^D@OkiFWQ;o-H5O+>hjyAVV&3R-_x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dAxQ44chGWXdt|uQ#RN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%UfwQm3b diff --git a/Resources/Tests/SharedImages/plane-3-5.glb b/Resources/Tests/SharedImages/plane-3-5.glb deleted file mode 100644 index d9ac63cbf573e4342135140ac6082ab832ebf22d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TXq!6iv7EJ!F1|I7w96P$<^A;G$-$b}5LINgU&pBoihRyNd{YfIem)q<1EX z-`kebKy>cRoO|xaxryT0?Tuj=KVKQfTX^rL{&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&C+P34GDC9*7#~l+%y^E&3?yx&SCQ8U7ICU4WVkxZf zVrQOG*ndF*jw_1Rp5me3bN2+cg-!Kh3m>RTTkoBdI{$+d$dq$Md_k)A&PkzAe=O!R znL6j0o)a#1utRd)r7enRN(GTzAP?O|(DTMh@T9SpGRwVYGh%XG1QxWi`IT%)6gTxH z;Mh=+GY*YHj)l<<0@h-vgaJwz(lnB5B(7!O)G*3LEEsLc0ZD8D2kQj&68Z#wRw zQWsFWYE_MGy*|Y{zrl7Y`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04wDY#rHeJib>T$}!hYIB+$zl+; z`nsv$^AN^EqxGuvSo?;tg)3DX%cBZkB6uM}1%`+kjm8i7mcAd1;rE4c1Mf@tJ$=5v UyKbxf=gt)3`0U!c#$TTO0&sk_L;wH) diff --git a/Resources/Tests/SharedImages/plane-3-6.glb b/Resources/Tests/SharedImages/plane-3-6.glb deleted file mode 100644 index 2779bf34712a29ae2800aaf1bdadb4026c2bc172..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c+m6#P5Dkm?kCmUJG&d^93zX88x@w!HEi0s|LQd)=MoAnwPIp&TB|d-;5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^M3q372TXW4ZkL{WO3PE18o1qQUT^9`?YoOaYD z;8+u0QU;Aewqc?z1gyl+2m_QbB6%zl*WzT3v z>OrZmcI37MW>^qTaav=GfRBm5X-dN>tKuYwaYP=>2`)*WrHtR;P<35b>7eU>EMpP_SQVsXMptBoya*yD zDt=NXP2vTo85MNhm{L|g5$hQu~4dAbd9blA6It_N3HC7{-IDM&WaTD zEkANHg))RGQ9jhF_S-VR^um>0lZeB!OY0VYe)bEOUbbHV diff --git a/Resources/Tests/SharedImages/plane-3-7.glb b/Resources/Tests/SharedImages/plane-3-7.glb deleted file mode 100644 index b3912f5656640be1c409c0e96f5cfd856ffe203b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c+m6~W5KXu0e;_|cAs5QFd1)zSC00PTKv$Izs+?egt0azWhusxI>Id{g`xzZO z3HNQQs*wU7+h@*P&LmEzH&=#X{Cs5?@8P|j1fvbYQAC7bTp)k8L1D365`Is~1M+PX zg-?+J{}%Qn?(TlSu+wDsO8srx@jflN79#Al>x@01h@^~Yj9 zlc{@}=_%oS3p-foE^SauGQzQB9Qo)rhMqT;oTZJGl)3Pm&6vtn5n9m7<~O{?anjV6 zfMZR#pbQ#?T+2pV2w01u5(X$?K(bh_kngl?#kkkXp>{6mMrFPOL-{4&-FN{SCHV$@gEdjtxYIL3<}byDER0;+T}F7o0-bBzcqtC$^PmvFaI3 zNjWIx)u!53z!XcuDNagk9`YfPI7w(UW<|WpU>uPTbAkowvV`#)9O-V(YN1#Ess;6( zX-TbcG+PjuJ9`U6mEYQf5-j31Nxcj1;4V(`6&|t$%ry$`I-frGAcX}h!h}rdf-I0P zc|mmeM69a3b52t#>AEtpl$?fzROSZV1(W`?AB;dSCK?6fVP}ARDCD&Fr5gm}k6&$2 z05C(q6uv>a`*mqbLr2rvYJF#5lm$zHU!O~kGm&7aMLWB;u5I;rre}G2<(A_QbrLwu z6Ogz1#;FuU1Vf^I=vC>rW`N;^D^)YgqY7UlSivI&hPWC(;CuR^KZ4&E#udCT;rICY V{_e7^_Mbabh{Ll>>l%N4_6vcawle?# diff --git a/Resources/Tests/SharedImages/plane-3-8.glb b/Resources/Tests/SharedImages/plane-3-8.glb deleted file mode 100644 index f4ac1a0480c3f7654b06afdcccae72dc8b1c56ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Qw~3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq678Qj-(b77eE$V&nLu(-2Zvv(it%?qUK>}v9%LTezF}_RO5MlmsK!?aR`NiCA);362YgH4kEZbZLc4+YCH$T~-``!g S<^FSL5^;QXZQbB6&wc@o4YhXw diff --git a/Resources/Tests/SharedImages/plane-3-9.glb b/Resources/Tests/SharedImages/plane-3-9.glb deleted file mode 100644 index 73333b2175f1e3bd3baa36d9bb94df456638efc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c?T*qw6s>D~4>G@-GHsDa|0p7xumQRPZcGR<(=wDXZKs(|aW{m-2k=3C4DU=^ zzE_R0Nn!5HoO|xaxsBr4?Tuj=KVKQfJ9zJ={&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&CUfYgqqL3FU9Jfs*^)8zBy2I`SnJ6KT;M85filwl^ zi=BB&VgCgMIIbvKdy0pC&)pN)7BiiEX$}IPq&4|f$5m?a5=2x;IQQXv* zfMY{N&Nws*ITl7c2w01u5(X$?NYhBJk!QECVmxT&SUcBrqcY!vq5P7FNJ+|Vb=vNs zQWsFWYE_MGy*b4?zrnUE`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04wDXSD!SL9oYq@HHopSo2OgTxi z7}TvkaVmp6gc;E;^s01Q6Ts}km8zHJQH3uNybz%RLs*T*5BQ$GAB^Gmg>eJ#OZYu~ WzQ4V0tNrKB6yo^o+PcPHp8W!djkYBK diff --git a/Resources/Tests/SharedImages/plane-4-0.glb b/Resources/Tests/SharedImages/plane-4-0.glb deleted file mode 100644 index 17b6f8b5e8bf2f2e30eea240041d383dfbda60c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c?T(W`6vZ{ZM>D?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcA=8 N5XWcN)(!sh>=!@4v&sMf diff --git a/Resources/Tests/SharedImages/plane-4-1.glb b/Resources/Tests/SharedImages/plane-4-1.glb deleted file mode 100644 index 69b3a0cc968c9af542ae3cac6890766754727527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c?QWYe6lI0JOM_w$y!B`_5gdtJsU*mM?YLbNMXV~|xMw1%chP*%ANOa-L^*y0r`;v2XaOsG zxiya|oWGy|#|?$IqqrXqx;p~LvQ72amc6H{ZM}C&>i!Q>AXCm2@foSwJ0*oe{jr$O zWa^$~dP=z5!VVU?OB|q!%8Z1gYJU)XfX<=pcoTPgW04%Mm|(>G5kCTg4u^JHb?-d zAz}(2A>IDAHnpL{@wQsv1sHY3a^TnJlH)>TSZdWmc^uQTyn5!LI0ENo2AWnMHWjvr zU@|mLuWDbdSD0A1(sZysYVb9JRXkE)h~)=-L*I|4@P1)j!SfQ{$G`9IF57DVxif`0 MJiD}R@#km109QA&`2YX_ diff --git a/Resources/Tests/SharedImages/plane-4-2.glb b/Resources/Tests/SharedImages/plane-4-2.glb deleted file mode 100644 index 3666813a657a2c1670e48e5c7d05ea3d6c7255ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c?T(W`6vZ{ZM>D?(1&WRG$JE+Qx@pVW){RNin1N1V3@~J7sJm&J_y9hl596Hy z`n_t5O`vmU=G=2X&LvLgw>O4i{Cs5?9z1umV6sIxiii-53*;}hC@j}&!XGI4hJ4#Z z;gcl6dM$|5X2i0BB7>9Qo)jhMu?9oMo*<%0l?VD@k>{x82U6 zQWsFCZdHTz-kf6H-(b77eE$XO*g$j~bmuH+SLIJL9Fr>bl2a&~B#*M>#I}k&Rz0H` zsRpIG+E&{Nm|;mc#c72tLOv!Erzwr5tc;T!#u522Cs>d^OBuh#k?!WA5qkZvTF~5? zSJVne^A&-)vv)vL`)xcZ!6M#}G`rvq?&7pa@R+S&u2FE``}k=9DXdr-resD}WQBam zOQORkBB}E(IL)Y}o7%)uavGLWnOk%p%!cz}Fag1sXcA1vy%F-Ekn_Rkeh^GQe6c|R zzzhLX_y+0jH>Ifz9gVls`p&_q3YG!CK9?NlBE?dRcHvsC=~-Si@=zOrvmymatM8f$ zTSPDz8mCv4uf{73EL^D@SRK{)3c*SqDKNy+`T^h2_rnRiUl=#=yoC4Z@B6#!w%UL0 OOd*cXuB{vV<=HPou(Qqp diff --git a/Resources/Tests/SharedImages/plane-4-3.glb b/Resources/Tests/SharedImages/plane-4-3.glb deleted file mode 100644 index 81484dcb5c90b81a4b52576268e3f5de7ecb0582..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYW!tQ6U4&9(l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(<`byKZpRY9S9lSR)f3iV13`tosULtR?K|vKogx^#0fIL%2 z!IL0<6qQ6MGh|u8a*~VrQ$djT+Oi!Tg{;cqxThnbcF}CmzwJ+vjxziRPMsyJXbvlL zxz&#;tiPZD#|?$MqqrXqoE?E>n7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=@Qk~|g(@+{YsjC-vdYUh$}Wac|ClwV*TOG!EI+n%$p z)EU%jTGe9RH^*4}H`rby-+#ecCJ-G4?VJQ{%lv7EV^XJHaSCM<yPj0(DLOe_SaK_#TQL3jRaG#~jBP>hZy{&d{GMIKagKKwH9{prWACP)CN zAz}(2A=Uo2HnpL`akom~IT&@tGT>L|g5$hQu~4dAIEG`&$JSlLttTH!RN|~iLD};2 zCW9|Sm=A?ktJ-7h8|D_SG<~d(8hnjl6%QpCBI=%UTwQm3b diff --git a/Resources/Tests/SharedImages/plane-4-4.glb b/Resources/Tests/SharedImages/plane-4-4.glb deleted file mode 100644 index 0e156d7f198d315c5ee44e5b4c61433c86cacfa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c?W)r-7_LXWkCES{Yr8tMe`L-b*kN0b+dK$kNZMs>q-{!)&T|+8FThLmVtkXf z`#vh-AYI-h&-=U|PZFo|+Z)3$e!emc560arm~0V_A|eFi0{M$A3d{AH@CQo1A>TGp z_#{cNUJD|%8L_NjImzYxsUXOI?YJEiMXbzWyK5q;SJABB8}+8hL>Ybrr=2CtXbv-b zxie2GoWGy|$2Enwr+66jJ9`4hvQ0H?%RW$5wq83Yb^iw`kSXVi_<~feos&YL{#eXs zGIh^0Jtti5U#~WzZ<(S~l82z*-EoFhB_-lE*SZzT?@7@t~Ds?OfB1%6t!o@=MHPC8>`0w%a*W z>H_N2t!l8|n^Ua&8*I0h@4sLj8;FjB?wkefs{Cn&V^XDFatdXW^wVaav)EkdKMPX-cChE8`@G>xg`~Cs>d^OBuh#k?!WA5qkZvTF{)C zSJVne^A&-6XYYWh_S<++fZfX-t$!SQw{YDs4xZ2iWDTR-ZgDU za4|Gas9EK!@mfLZ23A`&zCy5)M+ywFw0^)F`hGZp?+fDw#!L8~{=UDvZmad@_7viH L@7lbuTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&U8AeJhFi}(6sN>lk%Fe>hfRiE zhA<(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQScuPLb3zK*=Lr|rB-GuUH$t!fRSTLs zvx-{bShl1vcX$g#wco~r5-bu8NwW*?;4Vq>n2h)m<{J5Ty-%M9kiwFeK|-f&Nteiz zqNF-}TEunUxnL=iY+ai~O2L9sDszMG{ONEu^v56=6OH}JsCSDzDCBJLrSJQbk6$q; z0GJ_Q2Hzmv{iZaPp`&rPTHhHMRl!o=*XL4@tVoE|qMdiGE>@3gx|UlFK2)e6X`X7j}BJN&o-= diff --git a/Resources/Tests/SharedImages/plane-4-7.glb b/Resources/Tests/SharedImages/plane-4-7.glb deleted file mode 100644 index e1d4befddbb889c33bee1545db0938bb327038b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TVW~6t!FW9$|iq(P*=+6iS+AAxjdsNxBq5D5Dv5iaIim*db{d}cq@8G?id6NyoK|qRv@dCMv4f4w{B>bL|2jrSM z@}C6p!muDhnE^|4mXS=%pK^lS*H*{YQNYR!j=MS%Y8TD=y-{z9bd=&paB44MMKf5L z%dLJ)Vf_UKIIbz29mV~iZ|?{!!_?(#8s?s=ven)xY3F~C0-1C!iO)#o-YF>*>W{^I zCezMorl*9KG>4LcmH4wJ<;lBa%fTMy}3GygPPAntOBH1&V zl4?+@t8KY0fhiV*Q=C-T+~;E=aFWnq%E~CtU>uPPbAk)fX9?psI8fbOG(xZcWeb`+ z^NL#HXtp9Scjgv|YQK#K2}HahIoO4}ILTu?W-FL$x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXF1kincMZ21d8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lkfV0{F diff --git a/Resources/Tests/SharedImages/plane-4-9.glb b/Resources/Tests/SharedImages/plane-4-9.glb deleted file mode 100644 index b4369264d79c146b7bb3feb055fa49e08a601d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TVW~6t!FW9$|iqQDZ|Rg_5RO$dbfuk}ic1%4kNNqK=GXx=RT30s5GIklqOJq9M=@ip5kFR=qjven)>sr^4lflNA=#22J;@0=70^~Yj9 zlc{~4={ezQ2Rpb>UD~3EWQ1eEIC9Zl1U+wsoTaT;h`eyy?TCuF^bKfb`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`OtMJC$h90(G9I*YtetDRk(uwoP=0}VBqh~xdfm>U zQWsFGZdHSI-kf6X-(b77eE$V&nLuCe#u2$NC%7O3mN0&c1J%uCBlP-TwxGGQ zsHi25W@`d-XYPQg_S<-nK*Sr8qg}X*lRU-~wuZSz-hKb$ry-=UW~HByIbD-Aazlui zsQ5_{*NNwxrc}^PZDJuf^-CemExPySqs7RZf?{+u^=6a)7`af%#qjgM^JX8um>>b5 zhKMPAgjD;R+Ej)L$Jr@?XJAwnOMzdV3y!lQ!9uBa*)w`3JeKYlPBr*Yp#rCQ0>YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mU(6wp+ diff --git a/Resources/Tests/SharedImages/plane-5-0.glb b/Resources/Tests/SharedImages/plane-5-0.glb deleted file mode 100644 index c95fcb4cffbebdc8db0dcc30fbcd8d2523e404d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&avZxxx1M<@PKmQ31x?Ejn+&@Q zVKNj>t!iJbSD0A1(sZysYVb9JRXmhnh~)=-LqCir@P4IT!Sfp4$G`9IFWYker89{* MJiD}R@#km109P-w`2YX_ diff --git a/Resources/Tests/SharedImages/plane-5-1.glb b/Resources/Tests/SharedImages/plane-5-1.glb deleted file mode 100644 index 16c81779b71e36a80f91e67bd31e0bfe891fea33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9znm2<4Y1Y{~1}9LP`>MNmd3W4B1g^4cju3oVJ9(9$=5ShuM*A zH`i`sEMP}R(s#ad`HtgsaeZYN#?Lo~(She?9!$3gM-dT%ae@5%Eegx^n(#YH9*}RF zD14G6Sg!?<+KgCMu$<&_@l+7xcOBO=QN+p|j(aAOdKb+H{c(SWOqAhAaOy2#MRQo$ z%bj^l;rs;!IIbx=&lGpVf%i<{ShlGi+p_mmm96(qN!|ZJ3S`Q;B0eKkd#9vOs6Q6- zg-qSkOiu}yJJ`WOcWH}ak`s<4_QEeue?nB=icknePC#kkkXp>{6mMrHmChVo0yVQ*(_&f8c2)i~!!fB+FFA#>N%AO5PHd~lW7RX7 zk!nz?t8KNdfEkvAQ=C@VBIFYyahlR-#>zO!VH}YUbAkmKu$1v@9O-V}H$t!fRSTLs zi;7y|XucvaclHj5YQK#KC0N88l4cj&!Cjmd37)VO%ry#b`=34!A%zty!<5YFimZ?? zc}aBmL?m_I1*aL6bW@vHN>0O4Dszi&gZXGN3Z@_!6HSBJq(4SJ6ml{AG6;g%$FDXh z0GJ_Q3f~~z{iZaPp`+>Sw7zpNs)A*}ug@jNxk$0pqJ`=>X4mRgBM-F^I4e?+wEC{8 zutfxep>cXu`D(nvz`~Wffz?rsuMn){kpe?3tsn3W{VuTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&wqfgCqg&5B6sN>lk%Fe>hfRiE zhAcRoO|xaxryT0?Tuj=KVKQfTX^rL{&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&C+P34GDC9*7#~l+%y^E&3?yx&SCQ8U7ICU4WVkxZf zVrQOG*ndF*jw_1Rp5me3bN2+cg-!Kh3m>RTTkoBdI{$+d$dq$Md_k)A&PkzAe=O!R znL6j0o)a#1utRd)r7enRN(GTzAP?O|(DTMh@T9SpGRwVYGh%XG1QxWi`IT%)6gTxH z;Mh=+GY*YHj)l<<0@h-vgaJwz(lnB5B(7!O)G*3LEEsLc0ZD8D2kQj&68Z#wRw zQWsFWYE_MGy*|Y{zrl7Y`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04v~$;Tv3eY{ZMDn6hYIB+$zl+; z`nsv$^AN^EqxGuvSo?;tg)3DX%cBZkB6uM}1%`+kjm8i7mcAd1;rE4c1Mf@tJ$=5v UyKbxf=gt)3`0U!c#$TTO0&rZlL;wH) diff --git a/Resources/Tests/SharedImages/plane-5-4.glb b/Resources/Tests/SharedImages/plane-5-4.glb deleted file mode 100644 index f85acea0917ecd50f4d2b422fa66a50c4b4bee9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>uTFD6!tpy9!0;6<6FY!KO@UhNJ--^$;u#vAv?0IVOvI))0Pm}1MCs^Fgud% z+`EmjfE^u4-}%nvJC4)&&6TEUKi_DY3(xJ$pKK5gLsFKEm&jXeP*6n?;SZENBG1%O z@GOWQMI{l+3|UsNoaAEuToB}SE!)vi$f_KUdpZ(o7tIF!aes<*l;J0E>MUVJb6A|b}s2gX1)VM`32^&l$7JX>pA;M zok6XpRV~(idyKVzgY7l){THld0?}d6&PmX=%%5gBCUxo+r%*OQoZqRW}!{(3^kRg7(h5 zrj|IGuL#VYxdo!eZ|gw<5pPKjcHu5giv-`X70fmA@B5!V4L-f+^rIL4n|$E4EWW#;5aW+ER<>&uHoukqg&5B6sN>lk%Fe>hfRiE zhA(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ^LShl01kd+y1cXcGxDw_3squvzhD8*0U)LFueW-v3C zJN=Zx`U?thTvNDviibhp*%MfXsmoy+=7FlR)!I3!{Xa;7Ogfju7o>9SoD>T6$6~&a zsePX5IpJ~#E4WZy+MJT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2<_SnzzH7=3 z;bJIUA!n7Z#%l$s8(3}C_zJ;F9!fC8()s~!=!fA1zOS?!7_Z@b`uqO=x-HjV+LMUm Ly=(IZf4TPyONg`9 diff --git a/Resources/Tests/SharedImages/plane-5-6.glb b/Resources/Tests/SharedImages/plane-5-6.glb deleted file mode 100644 index c33df9153a2f8959d33c6428253e2f61bb4d82bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Smq3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdPzu*RmZQ1+2{AxThnbcF}y$ANOZSM=5>;r_KsiG=r77 z+Uchh)?ZM7zgr+63+oIQbMn7VvT!#q$`w%R)(-2Zvv(it%?tB`R>5C!lQk zd6U5x0nCR&t5xN(@eOkeSL!}iM>W1eu#yK73=y?jKj2&Xel&&O7upTHFX8v}`Tp*@ SE%%>0lZfN9YwHGodG-sA<+XVL diff --git a/Resources/Tests/SharedImages/plane-5-7.glb b/Resources/Tests/SharedImages/plane-5-7.glb deleted file mode 100644 index b5542077807f81832e30683d6bfee9261830caf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TXq!6iv7EJ!F1|I5FzBp-`-K!9~qh?NSgalQ@Fho0s5GIklvXj z>*uzmG!UITGv}WBac;tResg6Q#?M!V@fP0OnK#)Y5(G5QxyX^b*do7Juc^3a^c!-q ziTp=Nz4bb$Qkwx!GM>^@&L1<1+>YIHOcd}Uh2yS?q~1ldes9#9A`>O#0h~HZSg{mV zc)2r=DeS+X0LK+Y`-$Ry(085)Yzv#}#TMREmA2kHC2jo=QXo^#74aFV+B+qMLjAFr z&t%#<&GeLTxq}^&>n?3kNK-0^Lg<2LyI|x{dp%MltVMNnVM##0>STXLka;Tk4x>1=wfua18h)_w&X}{?@ z`%0Zb?W$EZw*C4T+xiW*Tgmrdur>yw!=Rm$pj#?`mXMH^sTYDl*(80Cg`n8V(opq` zCA1ur@@i9UD_}w-6^z6sHuJ@pN)pE`nDQcwQW!_%!kmzt_Ib?34GDBN7q!r|vg&)%yThSGA zr6{NlpXO1OcP3cEB->Obky0?fkjmVmJ8w3e54{Np#zYfuI_`~-3x%8yKKDIu`r!)( z1pqSy%-|cOyI+^4G;}oWoz`~>Mp>`~`1QFIB+X+YwP+XD)-_g-V|J`gIrvbaoFrKc z!d7256?`7Rcxbdx$Yy6i&DFJ(l?$VlFB+e~Ps(xTx8xT?!&)5+})&BoihRyNd{YfIea$re`LJ z_ian5kT_>%zVn^ScM>IwyBkf@e!kJPHaz!pZ@NV|2uM*dULbe5MSdBEgg;X94Y{U{ z{AWSDFf52rX28;%Wh4`e=bRw7W7&?50#;^l+|`j#yJ$Y>kNY#EqZB`ZQ)dM$n!(Ck z?etR$>n|w4aZSgd_gMr&PkzAe=Oz; zncC->o)fNiu!9TLr7em`MmQFXBNyFA(DU1nv-CC=A}`!lE21JUeFIwA`ieI=N?Pg? zaBK)KD1$~J+c41%0#;(Eg#k(!lPnT3a;>&084p@H*3LEE$jtX(D8Ilwl9F=TZ@bQ+ zQWsFGZdHSA_fE0)Z?N53zW;)?OdvWA+Bpl_mif~ZN2E%<cavDHU{6n^*`={ZdGCiyplBXfg7ppcoxZz1gHcMlMuxG5kF6yxE5@CP)CN zAz}(2A=Un-HkF~mY44Q4Gcc-(rNFPw1;<&DV4+mI>=`}1V|1#ShvF1C%@feH{IJQe zivT7=;nb?~)p&)8g)4OjtD_oUAy~-+35HmHz&G^$XbSIF+6_Ff;eGo1{_eUh_g^}b Nh~u+s>jr;$_6ug;vuTFD6!tpy9!0;6V>=fb{~1}9LP{EUNmd3S4B1g^4cjuZoVJ9(9$*i-N7<2N z=iY6M8Jy@y`p$PQ-*J@8Zm$f(`1#H-Zs5I}`r{44VMvOC@dA1C4GPNDittBDo{(po zD0mUXU#$uvlo_%#XBo-F>?J42Yddb!L?J6PIPRE8s9iMe_eQ-5GEs`3!D({=E1JQ| zUTn={3g<5)uj!JNJcmoj3W=-MbPu>6=&&nEJR*-jYdR8Tm}}jvhfYCag;RF zCE!>SUQh;&Lat?_Ed;E@PzeK+Fd|tbV&pk3TQcsoa;Tk4x{;ahz)*gHc_by(Y;`-$ zeWlKzPSvU!+qyZ%y1&78D*65k*0F);FzDt<(5}p%rZ^%x^^#L4n;_4kG2`i&GgKq+FxSX`=zaP;fE1Rj3=%S>OR_|s z;3ZMvlOnG2&N)q~pzF%SLU0@FMZ#ieEe#I z0)QC;rtl3?-LFfd4HZpmtMr|Lp$nD*zd9EjXGMaA679Teb!~VYvu(BY+(Us1oaPDW zT7KMQ>_rIEp}=aT9oDX4YT-)NMSWD^HG-8qlwb(x2Yg3A49D>M#<+s_E&Luo-``)h S<^F4D5^;ETXuTFD6!tpy9znm2<4YDcGZ4Qp zeWlKzPTi^o+j(=0b$^5H*7E%qtYZVwVbI=5(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aiqFAZ-id|%N8_u zW|~^!Xuc#cclH*DYQK#KDOkiCl4cj&!Cjmd2_CT}%ry$`dY?WIAcZBX!jw$uk}Q!g zctuqBq)h6(3r;gC=(;ws5S)gUkmd&61=Hbd7>q$MCK?BmQSTP{P{`TfOFsxEAHUk5 z0APlIDSU%e_nXpaLq*fsDt+f*=z?Xyug(R>d6{CNL_3$sb*-)~pN<}Us8ETsA_ZZ~ zubT|Mj9@$zTCKFl#y5;DT&dfrk7~R|u!=_#3=y?jtsn3${V*KE?+fD!-k0!u{Ct0Z T*_Qjyok_&u*`;-ZKR^2gZbr32 diff --git a/Resources/Tests/SharedImages/plane-6-1.glb b/Resources/Tests/SharedImages/plane-6-1.glb deleted file mode 100644 index 2c03127adf883478932512a62de7c3901aaffed3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&CQ}gkXpOYrES)>F2Yh|l1`G5Boii+?k=U^1NfLeh-W5A z@2esP($1Ng?|kR-oy6(l`byKZpRY9S9lSSlf4V_93`tosULx;)gMuoG2*0D`0ePm5 zf+s=zC@P6iX2`OFn7VvT!`xHVw%R)-wf_exkV)r~_>5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^L~q34~5v#gT{QIuY{8&i=~fdQ@Te#2`Vr(Jak zIM#%hltH7AZJ1~a0V^>y!T=?VNgj&?d6sKR#=TY!wR1@~GV>i6$}cdFrKBA9ZO_?P z>I`Z%t!lCEn`5l~8*HzU@4sLz6NnCjc20t}W&Sk7F{x9pIEAta@+c}!EThO{*)y7v zdQj@CUAZlR85V?7oYvSP;1eQnn$mE_syNAE9FYfef=e=BDdX2TRNcI9h2H$j7PNO3 zHMPXid_`dH%qyPj0(DLOe_SaK_#TQLAU;Vv>5qQP>hbI{%q18BM&OM7=9V}{_Nvd6C?oC z5HW?1kZONho7zy}xLYOg9E`eR8Stxf!Es)uSSZy(c`UtW^i27<_2ff|N}LrbC|iEs zWbkDO^P$jcReNlG!`#A^rjPYegRc>+;-LgXM4iqL_?CVcP2u;2b_MTC_&t8UzrSqD R{pZdk;_&R!y2YQL{Q`!~wQ&Fd diff --git a/Resources/Tests/SharedImages/plane-6-2.glb b/Resources/Tests/SharedImages/plane-6-2.glb deleted file mode 100644 index d41d5b589e5caf6801b9d2dab35c045befd86181..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?P}XF6!kjx9znm2BimWn%wS|$3Moz8C0QASFl0xuHEhesa@rCCdw@OW9%NUt zo$qdA%-}><(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQSc?{tsB?`&YNSb^BZipmhZn{Z45+*LAxhGJ1T#cl89ERmx4jrBz=^npxDZ@NcD`R zv>KG^YFlk9U`iwvj3gB{55$N{k|Zpg@G^=s7)Rv6oREU{dBVjt33WH;jnM0V)q>{E ztfE#pmMtmF9o_;_?YHru1dBvN((HmexJ!~eCL_Ltxkmn7@6+c2q_E^=kkBbx(k1ew zD5(yg7IB?-E?CMWTh}I$Qm~+u%G{tke>$8E{V@o}L}Pz4>fIs_3OO5m>HGfV<5vs{ z0A>i7!8b^EzbQ>+=x91yt?vwss$ePb>vJhcRwP7f(as&qF}qe5tEW>9K2)e6X`X7jjm$MgRZ+ diff --git a/Resources/Tests/SharedImages/plane-6-3.glb b/Resources/Tests/SharedImages/plane-6-3.glb deleted file mode 100644 index 91e683543dcf883c76eb7327245c6c0413489bf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c+m6#P5Dkm?kCmUJG&d^93zX88x@w!HEi0s|LQd)=MoAnwPIp&TB|d-;5HUosvSK{#eXs zGPO@LJtbUjVF#D0OB)oEoNz1{M;^M3q372TXW4ZkL{WO3PE18o1qQUT^9`?YoOaYD z;8+u0QU;Aewqc?z1gyl+2m_QbB6%zl*WzT3v z>OrZmcI37MW>^qTaav=GfRBm5X-dN>tKuYwaYP=>2`)*WrHtR;P<35b>7eU>EMpP_SQVsXMptBoya*yD zDt=NXP2vTo85MNhm{L|g5$hQu~4dAIEG`&$JV<>SFPN7{-IDM&WaTD zEkANHg))RGQ9jhF_S-VR^um>0lZeB!OY0VYe)bEO4z^za diff --git a/Resources/Tests/SharedImages/plane-6-4.glb b/Resources/Tests/SharedImages/plane-6-4.glb deleted file mode 100644 index 7336fd6535a6ed885281ababd7c3292d5e9c42a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?P}XF6!kjx9znm2BRgK$%wS|$3Moz8C0QASFl0xuHEhesa@rCCdw@OW9%NUt zo$qdA%-}><(z)k;oa-o=U0)f7@$;2oyo2{<>W?>wgdr^oE(+w$Hz+7qD=O|8eLx;I zQScuPLb3zK*=Lr|rB-GuUH$t!fRSTLs zvx-{bShl1vcX$g#wco~r5-bu8NwW*?;4Vq>n2h)m<{J5Ty-%M9kiwFeK|-f&Nteiz zqNF-}TEunUxnL=iY+ai~O2L9sDszMG{ONEu^v56=6OH}JsCSDzDCBJLrSJQbk6$q; z0GJ_Q2Hzmv{iZaPp`&rPTHhHMRl!o=*XL4@tVoE|qMf^zYj&+JR*zc^K2)e6X`X7j|Z~N&o-= diff --git a/Resources/Tests/SharedImages/plane-6-5.glb b/Resources/Tests/SharedImages/plane-6-5.glb deleted file mode 100644 index 5283d5b7c43f57378feb3665a551e40b91ae0bf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Smq3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdPzu*RmZQ1+2{AxThnbcF}y$ANOZSM=5>;r_KsiG=r77 z+Uchh)?ZM7zgr+63+oIQbMn7VvT!#q$`w%R)(-2Zvv(it%?5C!lQk zd6U5x0nCR&t5xN(@eOkeSL!}iM>W1eu#yK73=y?jKj2&Xel&&O7upTHFX8v}`Tp*@ SE%%>0lZfN9YwHGodG-sA<+XVL diff --git a/Resources/Tests/SharedImages/plane-6-6.glb b/Resources/Tests/SharedImages/plane-6-6.glb deleted file mode 100644 index ac18670568591b3027f4cdbd632c0b9b00560afe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c>u%F96b?hYN9Es9noFr5f1s33>a;eaEfZ2zAt!Ycqa=3x6@k&^h>{?2zUpB<;O>np=Be!en{_we3KgYgF8C?aLac!~V^28GpXMfg1>56HJo z6g~+OtX3rv%8Xc6u$<&#_EZq$zjj>DL=mfUSnirgs97}a_ilR=WTFf|f>Un+Bbvj= zUTn={3g<5e2?qBqtmT#*vS1V(58m#aY%$geXeC-Hxe9s?dT~w!h&uj?=c< z1RQI^OUj^8$hB;=g@Bb9YGHs9Zb=@C1o=+KmW+F?9BSv1Ze->=FqB_l9!p7iowr?Y zU#T;wQ@5(YcHSIg-QQrlwS4~t>)1ea7_@g1v@7$c8IFlgz2X$gCdi|xII*oFk7dtj zM)X0^TibG40y8WKr#RKvBIF|?aGKI+!m2pQ;W#26&Iv9_pQVgn<4ARL-Uz+^%N8_y zW|~^!Xuc$H?(8iP)qWcfQm}|OB+V?igS|K{5wWq>fE1Rj3R5zrOR_}1 z;1yBflQOCEE;!AopzGSiLU0;ZLYf(T?l+~;hKi=MRr=1s&;`qYU#$y{^D@OkiFV$#y0&~e=6)ov1Q~LPvmym~ z%R5fl5gZcbqm(1Z#%lztcqG9PSL+Adryqu6_`Wc%;C%_-u%F96b?hY$K>Bpnj0PD50uhLo7!fyWkRYd*WzT3v z>OrZmcI37MW>^qTaav=GfRBj4X-dNhtKuYwaYP=>2`)*WrHo(WP<35b>5|Zx`<3v`FxXEn%*af7ko;c>pOaSrw#YN|$7bya*yD zDt=NXP2vTo85MNZm{gJN_v_9vs>5P49^+2Bjx_a`5}njit7 zhKMPAgjD<6+SG;$$K5D_=U~(o%Ya{<3y$+L#X_leeq~&l@^SR8(N!zAo_{D*iL)XF zeanxWOrZ>6N|X<^s{OVMFuic4>1KV@;A;e{cqqXT*AMufez+OK?+fh$-k0!u_ephmW6 diff --git a/Resources/Tests/SharedImages/plane-6-8.glb b/Resources/Tests/SharedImages/plane-6-8.glb deleted file mode 100644 index 2a1b3ae5e9acf0b6794054820f6ae5710761b8dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|m|Mk$Lb28b7d2b8OF^Ve;+RZHGGQ{YyNJ*S=wtRldS;S% z-?o$nqH|{EJKwo{Cs8uLz0ox7=PONn3-8^`pKK8hLsArs7sy*|QBbbegg;R74SA-H zf+s=z^|~NJnITJamXS=%pK^k{*Ou+*C}d>@#~mFBwTou`-l#W4I!f^)ICYk=q8Y5r zOJq9M=@?p5kH9clHF9Ve0ZV4f8-%*=p~c)czl&Kqj3_;tNu_cTNh0`eQMl z$<#j2^qg?HgB@I`E^SdnGQzQ79C_$2f}Xe5oTaT;h`jLH?TCuF3=C*x`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`M6yW4$g^BiG9I*YtetDRk(uwoP=0}VBqimzZ#vGQ zQWsFGZdHSIyQf(DH`q=s-+#ecCJ-G5?VJT|%lv7IBT}VaatdV=Y+j3h1Q!EIlIH|CCz{f=3B%$Gyl~J6*I3f?`1Q(>w62@JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob%N-}k2`)1xtZnoePe$BEdq5cF{GuddKLP@^P!dhYA%q%@Yu| z{JP2Dix9>`q1CGL*!YIAg)4O%tD_oUAy~;n35JMTt=13tmcAcO;P-`g1Mf@tJ$=5v UyKc+<=guVJ`0U!c!C#*J0)B?IQUCw| diff --git a/Resources/Tests/SharedImages/plane-6-9.glb b/Resources/Tests/SharedImages/plane-6-9.glb deleted file mode 100644 index 975cf35dcce6f3b63f8a644e39a22f963942f6b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c>yFYu6s~K04>G@-(hCylA4OynHb7UvjR_%UTBhxow$n_fxEn&^1NfjmhG(WN z_f=zTQkXL{-}%nvJB^aX?Tw~sKVNCuJ9zKr-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?Sk6)HNHZyk_QqDVYPn1_w@Z}3coM38+c#B@9FdX U?R8u3KX)b($7k2p4gT`%7pFx$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7jhi0jcZdrPBr;Zq5`LR0?L-3 zHyL~pzuTFD6!tpy9znm2<4cxqW-ziWg_b1llB^6$7_y_-8n$I*Ic*7nJ-{Aw53(c4 z&b`|hGdR(a^qucqzT-HZU0oQ4@$;2oyo2{<8jRNnM-eGY#!KYiuTfYnmxSL@@_>BX zMB$Sl!E#v=q0ES71Zes9>DAQNTy5uAE+SkW9- z_IzU=QaFD>0gh{m&X(eC(D$|kj%AzjwJm!`rERr$OzQp*QXrGgCGiQV+&dQiT?@vi%LOaGbW) zCE!>QUQz~)Lat?_4Fs&jPzwW;FeG^_668A_TQcsnvag+Ux{;Z0!BBpIc`PO6b>4Qp zU8PQ-PTi^o+j(<{b$^5H*7E%qtYZVwe$d`g(5}p%W;iA~^@>v{n;?&(;>5O!JeED9 z8PS8HueRm31ZG$ePI0QSMaV});54PtgjI2p!#E-z<^-3d&r-&(aHP6<-w3__mn~@S z%rv#c(R@K*?(7W^)qWcfQm}|OB+V|kgS$8_5Avg^yAtGCmG0`}fjCw=lLm_8_Fa02xeEe#I z0)QC;rtl3?-ET^x4HZpiqx7AFp$nD)zd9Ej=Vgk85-n86F)yu4TRxs1e5g=~vmymy z%deXZzKmcz6k4sc$Hq5|EnKPFsE=yAMzD%U5)2WwTCE@OE&XskhTj*)1-vie_wf1t U{=6;spF5L?{j+oH27h|?3vpGoMF0Q* diff --git a/Resources/Tests/SharedImages/plane-7-2.glb b/Resources/Tests/SharedImages/plane-7-2.glb deleted file mode 100644 index 4277e4d44c9356deb4077a77ec817b0de31f6a7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&82oV2vTboy0p#O)F!brK7fzugLr0= z^u8)$Anlx)`ObGP-$|6rZ>}^=`}s=K-obl2^ClaFgMbtT;{|dT8|0T^NccS^56Crj z*>W{^I zCR6(~(^JCb7Itu_BXu7QPNhI zfMZQ~K^Zg(*@lU>5U>(MEeue?h-8t7k!y8K$+*|bp>{6mMrOVPL-_^fk(89vdE0gN zl{$l3b*ma|=gl$J{tdQU%lBWfmI*|MK|3cw+cJNe;)qnKmz+Y`1bGxCCzg?Ck?a{w zNi`_d)wbN0z!VF@DNZVE?(;DbI7w(QWn~m+FpkKDIl%?#vxM;*9H?$C8ll(!vIWhZ zc||R8G+PmvJ97&}wco~r1R~y$9PGkfoa8YcvlYxW^6q+{J`W&;6)XLO%;<`&kQ+k8 zM8!{vxK2FhG^K*BYZD8>sb30dZqS`K8_tK`1QesAi8mehM#zOq&Ie!mo;UsY)dUFu zHAGC|Bc$5j)TT02IGwE$cm_sQu@v~#x!^b}5-gNz7q(&R*T%IeAE%mpC{cmaJOO3P z&zlUs2w*-GTCFONjc=G+xKj7AI;!y%f|WdwV2G&I`T^h455o!kzR<4VeF?wE&-eG2 SZMpy4nM53(U0OHz^Rr)x*|l^4 diff --git a/Resources/Tests/SharedImages/plane-7-3.glb b/Resources/Tests/SharedImages/plane-7-3.glb deleted file mode 100644 index a16497fd90219fcb5389bcf970dcf4cd62ff4844..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c+m6~W5KXu0e;_|cAs5QFd1)zSC00PTKv$Izs+?egt0azWhusxI>Id{g`xzZO z3HNQQs*wU7+h@*P&LmEzH&=#X{Cs5?@8P|j1fvbYQAC7bTp)k8L1D365`Is~1M+PX zg-?+J{}%Qn?(TlSu+wDsO8srx@jflN79#Al>x@01h@^~Yj9 zlc{@}=_%oS3p-foE^SauGQzQB9Qo)rhMqT;oTZJGl)3Pm&6vtn5n9m7<~O{?anjV6 zfMZR#pbQ#?T+2pV2w01u5(X$?K(bh_kngl?#kkkXp>{6mMrFPOL-{4&-FN{SCHV$@gEdjtxYIL3<}byDER0;+T}F7o0-bBzcqtC$^PmvFaI3 zNjWIx)u!53z!XcuDNagk9`YfPI7w(UW<|WpU>uPTbAkowvV`#)9O-V(YN1#Ess;6( zX-TbcG+PjuJ9`U6mEYQf5-j31Nxcj1;4V(`6&|t$%ry$`I-frGAcX}h!h}rdf-I0P zc|mmeM69a3b52t#>AEtpl$?fzROSZV1(W`?AB;dSCK?6fVP}ARDCD&Fr5gm}k6&$2 z05C(q6uv>a`*mqbLr2rvYJF#5lm$zHU!O~kGm&7aMLY8>&sL9XUR&3C<(1rW`N;^D^)YgqY7UlSivI&hPWC(;CuR^KZ4&E#udCT;rICY V{_e7^_Mbabh{Ll>>l%N4_6vbVwle?# diff --git a/Resources/Tests/SharedImages/plane-7-4.glb b/Resources/Tests/SharedImages/plane-7-4.glb deleted file mode 100644 index df1f59836c150937f33d2530494a1f532a1bfb44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TVW~6t!FW9$|iq(P*=+6iS+AAxjdsNxBq5D5Dv5iaIim*db{d}cq@8G?id6NyoK|qRv@dCMv4f4w{B>bL|2jrSM z@}C6p!muDhnE^|4mXS=%pK^lS*H*{YQNYR!j=MS%Y8TD=y-{z9bd=&paB44MMKf5L z%dLJ)Vf_UKIIbz29mV~iZ|?{!!_?(#8s?s=ven)xY3F~C0-1C!iO)#o-YF>*>W{^I zCezMorl*9KG>4LcmH4wJ<;lBa%fTMy}3GygPPAntOBH1&V zl4?+@t8KY0fhiV*Q=C-T+~;E=aFWnq%E~CtU>uPPbAk)fX9?psI8fbOG(xZcWeb`+ z^NL#HXtp9Scjgv|YQK#K2}HahIoO4}ILTu?W-FL$@Fho0s5GIklvXj z>*uzmG!UITGv}WBac;tResg6Q#?M!V@fP0OnK#)Y5(G5QxyX^b*do7Juc^3a^c!-q ziTp=Nz4bb$Qkwx!GM>^@&L1<1+>YIHOcd}Uh2yS?q~1ldes9#9A`>O#0h~HZSg{mV zc)2r=DeS+X0LK+Y`-$Ry(085)Yzv#}#TMREmA2kHC2jo=QXo^#74aFV+B+qMLjAFr z&t%#<&GeLTxq}^&>n?3kNK-0^Lg<2LyI|x{dp%MltVMNnVM##0>STXLka;Tk4x>1=wfua18h)_w&X}{?@ z`%0Zb?W$EZw*C4T+xiW*Tgmrdur>yw!=Rm$pj#?`mXMH^sTYDl*(80Cg`n8V(opq` zCA1ur@@i9UD_}w-6^z6sHuJ@pN)pE`nDQcwQW!_%!kmzt_Ib?34GDBN7q!r|vg&)%yThSGA zr6{NlpXO1OcP3cEB->Obky0?fkjmVmJ8w3e54{Np#zYfuI_`~-3x%8yKKDIu`r!)( z1pqSy%-|cOyI+^4G;}oWoz`~>Mp>`~`1QFIB+X+YwP+U|t7BeU*H}GHIrvbaoFrKc z!d7256?`7Rcxbdu%F96b?hY$K>Bpnj0PD50uhLo7!fyWkRYd*WzT3v z>OrZmcI37MW>^qTaav=GfRBj4X-dNhtKuYwaYP=>2`)*WrHo(WP<35b>5|Zx`<3v`FxXEn%*af7ko;c>pOaSrw#YN|$7bya*yD zDt=NXP2vTo85MNZm{gJN_v_9vs>5P49^+2Bjx_a`5}njit7 zhKMPAgjD<6+SG;$$K5D_=U~(o%Ya{<3y$+L#X_le-Zi?Wd|ds?xKb;po_{D*iL)XF zeanxWOrZ>6N|X<^s{OVMFuic4>1KV@;A;e{cqqXT*AMufez+OK?+fh$-k0!u_epggu{ diff --git a/Resources/Tests/SharedImages/plane-7-7.glb b/Resources/Tests/SharedImages/plane-7-7.glb deleted file mode 100644 index 00de2f419649b4ce14431bdeecfca344225e6826..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c?QYXB6b(bXN9Es9>NKT;{DD$BsZ-mGwoFJ>g`Ct)jFLEVoNiN9i3i|8c?_H&=#X{Cs5?@8P|jdgBcuK|u4IiyXQ04f2cCii&$iACQYp zkhjUWTJ#Tf>UP!BbLGl zFSh0}h5Z*4;JBh_?+wDsI6Y3qNG0-18Ih|fsX+$kv(>W{^I zCezkwrl*99EzFQycWHw{no>a|7sy4oA@sbl5OMCo8QQqgmF`E z0**BmIpfeM)Uq(zLcm%Kl`udFLz;#%LayD$igB-%L+xDBjmmrnhVn}yLM17u{kG%m zD|H67t5((6_M2mD>o?d=CEtI++8BrqgLY1WZmIlPLPA=mUI+$dlk`y*f?_L6L)9~u z(DI;^TbpWH0TUvrU?eWFnJ-3Ek~n6;gco6y!f`|{oD-7M9*?=WA%X7ZycT-(S1qXb z%t~s7W9gE@xx-r^s{Ga-lwgslN$Oc}2YX4JMP$U6aITSe*ZuUl4=F5p;m35!mUM|+ zDGI8?r+HN6oe7pO$<~!gq!i3Aq%t??&YKQq18)q1G11tYjJiYQLLq1UFFntjeEf<* z0l*9aGq{6v_v_M>hK{Da)%s4sC<~SVzh0Muq77R2`c2ABv}md zR(G7X12`nwN3KSt-RLv}xDtw9Hg$NWF;%fYW`}D(L4Br>V6}&Iud;ENVciC3+ Q&#fuM;o7BfjXz)e1&W8ZJOBUy diff --git a/Resources/Tests/SharedImages/plane-7-8.glb b/Resources/Tests/SharedImages/plane-7-8.glb deleted file mode 100644 index 5d110b0a72afb7f166191f2500c13843254be4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&82p05Tw>FbZMKlt&32KOwvg*l4QbUQgjTxi{S+90a5&7%z~!+##0_NJlAt0H@9hRy2c^ zx!UQ+6xLr*fa98?^F(n!95_z|mSO7hH4Sr5RoQCql+^woq(CN}OX4$9xpztmh5BPL zpUKod&GeLTwSyg8s4i_$L^8s$U>v#VHiDkFLeA1wEJR+o?RG>(T>1vIvi%iraFn#w zCE(Z)UQh;&LbhR|9R#ezPzwW;FeX_fV&qyKQ!?(ga;Tk4x{;YbfuZ~Y^GHg{>AdMW z`%0Zbt-4hWw$nSt+P}eeYx({Q)-r+UFlgr_Xj|q_Qyh^h^^#L4n;;LOJT!IQ2^*%`Lj~=A*^Pn}T9=H1%ea{usGX$;I&V!1HDwzL+2Z zpoWMke1uf{o7z-{3a7JE0?)vxDwYDjIu{&gMS_J=?XqX|^lRhVl#f$QK9s1yX`XyTXUj#583awU^$Hq6zEnKPlSRK{)3c*SqNH9dyYW;w3>HE{8A*Hqjy`VG0* zME;|s-g=!=sm*{V8Bb{{=Z_gh?rXc{m?+>y3dbE2Nxh3^{obfIMJ7tf12}b-uwp5! z@N#D!Q`mn&0gfw*_7lbZpzk~p*cLX`i!HpTDs8=YO4|A#q(G*eE8;U!wRcJih5BPL zpUJd!n&~OwatAvk*InA8kfu}+$pv!JZ3sPYtOZXRk(62PHk%=nQQ=$A%H~(HAz|Fq zmw;nKMb0=h3bibZb`Y=@LnRDQ!ic7!jF4-$v0~h7&F5uuWl)9!Yh zeWlKzcGapH+kSJ5ZT$w@spR`FSQ`V;VbIP=&@GieOGrq|)C<9&Y?406LQrgFX{dU} z5?T&Qd9|sw6)+)^3P$1*oB3i)C5dAeOnDJTDU2g>VNOU+`#k32h6K8si(2T_ziL5! zXI@e(97|Ue<___B5 zQWR8&PxGkCI}Jz6j$OD)W?Lx0gw>1IGE?lX4SsqpR62S`*C@_T8X#9Zh>HFaXeqR_@@Vx$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE@X0Dqg#zU)TY2`o`9s~cTI*} zgfJKir&g7(#w!dgT&Wva9o6^>!Ac%VFvQaO0pHLM!wI}!X*ck^hWF|3`}^y*+<)mz NB970ltsDI1*)KuTFD6!tpy9!0;6kNY#EqYOWRQ)dY)n#0Om zZuMgd>n|w4aYNzmDDH;?XGdTerY;}TF!xlot@chy?f*dvWYW1LJ|mTTr=(D*KNj*ukah(gwvOCmajLk%w+$==pWTS$3TWQIuY%6H}2?fdQ@Te8X!TryX?( zIM#%hltH7AZJ1~a0V^>y!T=?VNgj&?d6sKR#=TY!wR1@~GV>i6$}cdFrKBA9UDw%H z>I`Z%t!lAu?-*ZqRW~16p*R1s1?`58n77eT~C z#ZStlNxa}Rqk^s*6AQsl$q}t!srZ!YK?p6sr2cxc72K?$=aGaMZ7D}~H9!u{Ty?W-MI3>=C6f`YAY%=UJ zgvn4iwW@u!USVS4O4GsmsKM6=R`F1RA(kKT4gD~h!uyqW1m(C>O M@a)pM#h;)30$%U50RR91 diff --git a/Resources/Tests/SharedImages/plane-8-2.glb b/Resources/Tests/SharedImages/plane-8-2.glb deleted file mode 100644 index 996dcf6ea7039612203326b0d5c612b438d9a532..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE^NcryGFMfd8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lgaI@9` diff --git a/Resources/Tests/SharedImages/plane-8-3.glb b/Resources/Tests/SharedImages/plane-8-3.glb deleted file mode 100644 index c01f821a031a193fb260cb7b123ad7b58de769ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Yy6i&DFJ!F1|n2Qw~3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq678Qj-(b77eE$V&nLu(-2Zvv(it%?TezF}_RO5MlmsK!?aR`NiCA);362YgH4kEZbZLc4+YCH$T~-``!g S<^FSL5^;QXZQbB6&wc@nv9)&q diff --git a/Resources/Tests/SharedImages/plane-8-4.glb b/Resources/Tests/SharedImages/plane-8-4.glb deleted file mode 100644 index 7c325c9d370634b6fb762edee6f31173813eac16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>x$Yy6i&DFJ!F1|m|Ml>PqEeo7d2b8OF^Ve;+RZHGGQ`tcM+iv&`0dU^vopj zzHKQL66eg!cfND^PNHOfd!uRE&o`Rp!gDwCCtHNWkQ4>u1@abK6qM^V;SZENBG1%O z@GOYGUKd0tGh}JbGLnh;b54-=*0LQPg{;irxT7PXcG0Zg8}+70M=5>+r_K^qG=r77 z-07zj)?ZM7gd_gMr&PkzAe=Oz; zncC->o)a#2u!9TLr7em`MmQFXBM;q0(DT-sv$Pcpkr!UO9Z?aNfdQ>-f5RIbC2e&H zI5vbAltH7AZJ1~W0V^@o!T=?VNEV40d6sKR#)DRlwR253GV?tc$}cdFq@*18UB@|8 z>H=!jt!l7t_Y`aY2HUCS`!86_1ft`howJ~AnLkZ&M5@$FPN8gqJc*JM)5x<(_Kc>a z8kFj4TW(8WiUr{mClxji_?QTsBs83|GKw=8N94hr;DYp7!uTx?RW}!n(CdHMg67V= zqLw(Ctq9DWxdWowZ{tA<7V(Cp*#&oS7bkg)$7}_2jr{xGr_TdOVa3WIAv3xnE941Y z5*0ov;yUk~)07IjsZA^dr$H&CxkdN>Y&akK6A+A!CjNBX8zB!0IUjuK`~LLfR}&Nf z%n&ezZ;fXE?mRayGFMfd8kc+(>wu5%kP>D zy9i-06i%%wUyWB7Sh!L*usW*o6@ryKlwgRZ^#i`4ABGcnztV2tc@6K=-}m>|ZMpx_ OnM53)U0XN!%d=lkV6)l) diff --git a/Resources/Tests/SharedImages/plane-8-5.glb b/Resources/Tests/SharedImages/plane-8-5.glb deleted file mode 100644 index 8916d5b2a57d2a1b5270e12acc065b34352fb112..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1500 zcmb_c>x$Yy6i&DFJ(l?$VlFB+e~Ps(xTx8xT?!&)5+})&BoihRyNd{YfIea$re`LJ z_ian5kT_>%zVn^ScM>IwyBkf@e!kJPHaz!pZ@NV|2uM*dULbe5MSdBEgg;X94Y{U{ z{AWSDFf52rX28;%Wh4`e=bRw7W7&?50#;^l+|`j#yJ$Y>kNY#EqZB`ZQ)dM$n!(Ck z?etR$>n|w4aZSgd_gMr&PkzAe=Oz; zncC->o)fNiu!9TLr7em`MmQFXBNyFA(DU1nv-CC=A}`!lE21JUeFIwA`ieI=N?Pg? zaBK)KD1$~J+c41%0#;(Eg#k(!lPnT3a;>&084p@H*3LEE$jtX(D8Ilwl9F=TZ@bQ+ zQWsFGZdHSA_fE0)Z?N53zW;)?OdvWA+Bpl_mif~ZN2E%<cavDHU{6n^*`={ZdGCiyplBXfg7ppcoxZz1gHcMlMuxG5kF6yxE5@CP)CN zAz}(2A=Un-HkF~mY44Q4Gcc-(rNFPw1;<&DV4+mI>=+%rXY{I>hvF1C%@feH{IJQe zivT7=;nb?~)p&)8g)4OjtD_oUAy~-+35HmHz&G^$XbSIF+6_Ff;eGo1{_eUh_g^}b Nh~u+s>jr;$_6ugmvx$Yy6i&DFJ!F1|m|Mk$Lb28b7d2b8OF^Ve;+RZHGGQ{YyNJ*S=wtRldS;S% z-?o$nqH|{EJKwo{Cs8uLz0ox7=PONn3-8^`pKK8hLsArs7sy*|QBbbegg;R74SA-H zf+s=z^|~NJnITJamXS=%pK^k{*Ou+*C}d>@#~mFBwTou`-l#W4I!f^)ICYk=q8Y5r zOJq9M=@?p5kH9clHF9Ve0ZV4f8-%*=p~c)czl&Kqj3_;tNu_cTNh0`eQMl z$<#j2^qg?HgB@I`E^SdnGQzQ79C_$2f}Xe5oTaT;h`jLH?TCuF3=C*x`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`M6yW4$g^BiG9I*YtetDRk(uwoP=0}VBqimzZ#vGQ zQWsFGZdHSIyQf(DH`q=s-+#ecCJ-G5?VJT|%lv7IBT}VaatdV=Y+j3h1Q!EIlIH|CCz{f=3B%$Gyl~J6*I3f?`1Q(>w62@JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob%N-}k2`)1xtZnoePe$BEdq5cF{39rhHtzYjmr@hYA%q%@Yu| z{JP2Dix9>`q1CGL*!YIAg)4O%tD_oUAy~;n35JMTt=13tmcAcO;P-`g1Mf@tJ$=5v UyKc+<=guVJ`0U!c!C#*J0)9%hQUCw| diff --git a/Resources/Tests/SharedImages/plane-8-7.glb b/Resources/Tests/SharedImages/plane-8-7.glb deleted file mode 100644 index 1b60bb7f7c9f36e5d4e3b2037bc507b808074840..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c>x$Dr6pkytN15Lt&82p05Tw>FbZMKlt&32KOwvg*l4QbUQgjTxi{S+90a5&7%z~!+##0_NJlAt0H@9hRy2c^ zx!UQ+6xLr*fa98?^F(n!95_z|mSO7hH4Sr5RoQCql+^woq(CN}OX4$9xpztmh5BPL zpUKod&GeLTwSyg8s4i_$L^8s$U>v#VHiDkFLeA1wEJR+o?RG>(T>1vIvi%iraFn#w zCE(Z)UQh;&LbhR|9R#ezPzwW;FeX_fV&qyKQ!?(ga;Tk4x{;YbfuZ~Y^GHg{>AdMW z`%0Zbt-4hWw$nSt+P}eeYx({Q)-r+UFlgr_Xj|q_Qyh^h^^#L4n;;LOJT!IQ2^*%`Lj~=A*^Pn}T9=H1%ea{usGX$;I&V!1HDwzL+2Z zpoWMke1uf{o7z-{3a7JE0?)vxDwYDjIu{&gMS_J=?ef~VHs#~!J)>7mK9s1yX`XyTXUj#583awU^$Hq6zEnKPlSRK{)3c*SqNH9dyYW;w3>HE(H z^PaYp2IS~UI``bik)mXNd!uRE&o`Rp!nm9HlP$twNQ#2-0(pxq3d;4G@CQmBk!R{C zcoxK8uL~lS8L~8I8Og-_IVZ?_YuS#DLRMz5-O-Uyt7z8mje1k0qZB`ZQ)dY?n!(Im z?(|a%>n|w4aZTawDINxWXHQ@mrY?tRm^_WvLSGU;3rUy#bRb5bbOAB*`y zruKQJ=Y-20tl&a*X^SF~5sn4p$U}D#^t`p^EN#U?a7@t~Ds?OfB1%zO`q@(au(DJjQ&*KrP& zx`0}Bs~W7^J;mC;!FFo-{tMPJf#^7B=PYPj=1)@`kt+3)Qz)AtPom_+H1aHxJ)JT!I1Nf6%`Lk3XT$l>pMYR=H1Vh7-UxY6$ob$)-}k2<_SnzzH7=3 z;bJIUA!n7Z#%l$s8(3}C_zJ;F9!fC8()s~!=!fA1zOS?!7_Z@b`uqO=x-HjV+LMUm Ly=(IZf4TPyS3tAk diff --git a/Resources/Tests/SharedImages/plane-8-9.glb b/Resources/Tests/SharedImages/plane-8-9.glb deleted file mode 100644 index 469cb3393075aa6e206313ef7b611c2e513e8faa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmb_c?TXq!6iv7EJ!F1|m_#cY3dLF%T-0pUE(MV?iIZeXk_nTEyNd{YfIem)q<1EX z-`kebKy>cRoO|xaxe4RN?Tw~sKi_EDJ9zKr?sSW=?~^=dJV(xQi@YKT2!EjD5jm!g zyk|k&AjpYOrq7a$r6d)L=Zqldt!1}#OJq99IW{^I zAyfN2({sYr4t8*^y0k?hNeRb-apa)85PIGSI7=Fl5Lxatn;{iZ;Th1%<~O{-Vcb-g zfMY{=P8l=`*@lUB5U>(MB@9r)n53bIkYlw?$#~Gpv39QMMrOVTL-_^fp_Ejs-Rrat zmAZghRjX=jyL*bYe}nB*^8FXAWdhN0(5_Ek5Rwa}}7*@F7c zqNJ8Mnyv}Vow);|%5UvK0uirCj&|WLjn3B*eHae~R;$us?Ha}wu2gL-k1BkLUx$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?ge|{r zGWH^X@las3svI`1VQk?_-Nx#u##abd@<4(ipjNB(1HPs2M^pHHq20jy5`Isg@9(bL Sa{swAi8wyHwr=p3XTJc9mbGF4 diff --git a/Resources/Tests/SharedImages/plane-9-1.glb b/Resources/Tests/SharedImages/plane-9-1.glb deleted file mode 100644 index 9ea21e3509d3cdea2abbf97aa592d9f5b01117fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1504 zcmb_c>uTFD6!tpy9!0;6<4YDA{~1}9LP`>MNmd3W4B1g^4cju3oVA3&9$*i-N7<2V z=iY6M8Jy@y`p$PQ-*KGIZ?6o)`1!^#Zs5I}1(OZJQAC7bTp<5(gTivPBK(1pZ^*Yz z6h2E5tX6_ZZAL6BSWa>|e=Z2}-#V^mqKK6_9Cu74^)8zAd!ybInJB|g;M7~disrDg z7hCg~!ubmda9mTgcN7nUzPBTAEZbDCZP|OO%GP_Qr0)M91v2Ga5ucH&y;D*s)E|ra zLZSyR<XIfeRGU;e}nDR^8FXAV*}A)(B4VVuF9WgI3`u zQVmLVwWYQdFvF5?iqi^PgnUdSPE#69Ss5oej3e@4POu<-mNI^eBi+r%M(FjwYC&^n zUQsI?&6fn`&fWr1?YHru1dDh>((HmexQo*w!DF_BxkkZ#@8hQdq_AXVn35S?k|pvb zFNqGHh@{TD;54I>u4@xZ$!S^B^||CY7b%unv``(#>{?wL9;cdnD3HKek%F$( z$4$j9BA5;h)~m{4;~J(GuGC$uj%s{`U?qx$Yy6i&DFJ!F1|n2VJf3dLF%T-0pUE(MV?iIZeXk_nTE-9>~xKp(RY(le99 z`?jSt5S=qK-}%nvJBgCT?Tw~sKVNCuTX^s0-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mQqP1oK diff --git a/Resources/Tests/SharedImages/plane-9-3.glb b/Resources/Tests/SharedImages/plane-9-3.glb deleted file mode 100644 index 5f40c7b6e365db2f31169f8b1c45e43b392f27a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmb_c?T*qw6s>D~4>G@-GHsDa|0p7xumQRPZcGR<(=wDXZKs(|aW{m-2k=3C4DU=^ zzE_R0Nn!5HoO|xaxsBr4?Tuj=KVKQfJ9zJ={&7}7lFB1hhQi-KacqT+$kZ^*+Y z3Z5kOSF4;#ZH7F_cuG?_d&(&CUfYgqqL3FU9Jfs*^)8zBy2I`SnJ6KT;M85filwl^ zi=BB&VgCgMIIbvKdy0pC&)pN)7BiiEX$}IPq&4|f$5m?a5=2x;IQQXv* zfMY{N&Nws*ITl7c2w01u5(X$?NYhBJk!QECVmxT&SUcBrqcY!vq5P7FNJ+|Vb=vNs zQWsFWYE_MGy*b4?zrnUE`Th&m#z1r&w0jn`qw;47iD;R6AsCcR(nnbcimfb-RL@vK z%RwoxHr2KQCPY%fNL*sGK#Zs)am>OAFQRn{RoULcS)SB$%rpuu91J={rIU5DJ*#r#B|D*bcsAE z3aZ1W`MSzG6D(npZ7P#UDOgZQWp2^EKOM{l{ul&fqOm_2b%)4pKObELZ~k`dkW<<}s04v~$;Tv3eY{V|CQRE~g*Ll#?Wj zLEY*Tr!vSxm=WzluS&Ny0n9F3sd`x+RrnIY3lSOJq9M=@ip5kFR=qjven)>sr^4lflNA=#22J;@0=70^~Yj9 zlc{~4={ezQ2Rpb>UD~3EWQ1eEIC9Zl1U+wsoTaT;h`eyy?TCuF^bKfb`zzkyC~2!p zz_B5`pbQ#?Y{Nu52v~`s76vF`OtMJC$h90(G9I*YtetDRk(uwoP=0}VBqh~xdfm>U zQWsFGZdHSI-kf6X-(b77eE$V&nLuCe#u2$NC%7O3mN0&c1J%uCBlP-TwxGGQ zsHi25W@`d-XYPQg_S<-nK*Sr8qg}X*lRU-~wuZSz-hKb$ry-=UW~HByIbD-Aazlui zsQ5_{*NNwxrc}^PZDJuf^-CemExPySqs7RZf?{+u^=6a)7`af%#qjgM^JX8um>>b5 zhKMPAgjD;R+Ej)L$Jr@?XJAwnOMzdV3y!lQ!9uBa=@^dQGkPXGRyFugp#rCQ0>YMG zHyL{oz<4OIT2&4k*D$tlrEX(&RO2fID|sNn5Kybt`T^h4_oFHNzR+&qeF?v(&-ZuN SZMpy4nM53)U0XN!%d=mUuC;0a diff --git a/Resources/Tests/SharedImages/plane-9-5.glb b/Resources/Tests/SharedImages/plane-9-5.glb deleted file mode 100644 index 22909852b90854c08f146b388ed637922a7defdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1504 zcmb_c>uTFD6!tpy9!0;6V>=fb{~1}9LP{EUNmd3S4B1g^4cjuZoVJ9(9$*i-N7<2N z=iY6M8Jy@y`p$PQ-*J@8Zm$f(`1#H-Zs5I}`r{44VMvOC@dA1C4GPNDittBDo{(po zD0mUXU#$uvlo_%#XBo-F>?J42Yddb!L?J6PIPRE8s9iMe_eQ-5GEs`3!D({=E1JQ| zUTn={3g<5)uj!JNJcmoj3W=-MbPu>6=&&nEJR*-jYdR8Tm}}jvhfYCag;RF zCE!>SUQh;&Lat?_Ed;E@PzeK+Fd|tbV&pk3TQcsoa;Tk4x{;ahz)*gHc_by(Y;`-$ zeWlKzPSvU!+qyZ%y1&78D*65k*0F);FzDt<(5}p%rZ^%x^^#L4n;_4kG2`i&GgKq+FxSX`=zaP;fE1Rj3=%S>OR_|s z;3ZMvlOnG2&N)q~pzF%SLU0@FMZ#ieEe#I z0)QC;rtl3?-LFfd4HZpmtMr|Lp$nD*zd9EjXGMaA679Tgwau>8wc&B}+(Us1oaPDW zT7KMQ>_rIEp}=aT9oDX4YT-)NMSWD^HG-8qlwb(x2Yg3A49D>M#<+s_E&Luo-``)h S<^F4D5^;ETXyFYu6s~K04>G@-(hCylA4OynHb7UvjR_%UTBhxow$n_fxEn&^1NfjmhG(WN z_f=zTQkXL{-}%nvJB^aX?Tw~sKVNCuJ9zKr-gJv_5Rjr^yg=@9i~KSS34fsE8*)t@ z`A>p)VOS8M%z&ji%Sa{`PdP#EYs+?Y6tFUbreCRrq6(-2Zvv(it%?z-gX< zyyZ7eW>5q$B+7+aRc;#s7+$ziH?umb@fCuVJdj`rtMvoEr|(Bo_!21$@PoM8^ UuiJ9}xig74KD)MV@Rw)50H-0gS^xk5 diff --git a/Resources/Tests/SharedImages/plane-9-7.glb b/Resources/Tests/SharedImages/plane-9-7.glb deleted file mode 100644 index 77b088bfe57108c448c7760fed648c1872a09bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 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 diff --git a/Resources/Tests/SharedImages/texture1.png b/Resources/Tests/SharedImages/texture1.png deleted file mode 100644 index 9594525097353c3fefa248b0cc9732700e301425..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57706 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 Y$@7zpj^cR82v(Y@vBf8uMvkGs0ipHazyJUM diff --git a/Resources/Tests/SharedImages/tileset.json b/Resources/Tests/SharedImages/tileset.json deleted file mode 100644 index b7569c61b..000000000 --- a/Resources/Tests/SharedImages/tileset.json +++ /dev/null @@ -1,2228 +0,0 @@ -{ - "asset": { - "version": "1.1" - }, - "geometricError": 4096, - "root": { - "boundingVolume": { - "box": [ - 5.4500005, - 0, - 5.4500005, - 5.450000286102295, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 5.450000286102295 - ] - }, - "geometricError": 1024, - "children": [ - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-0-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 1.6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-1-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 2.7, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-2-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 3.8000001999999995, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-3-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 4.9, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-4-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 6, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-5-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 7.1000004, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-6-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 8.2000003, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-7-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 9.3, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-8-9.glb" - } - }, - { - "boundingVolume": { - "box": [ - 0.5, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-0.glb" - } - }, - { - "boundingVolume": { - "box": [ - 1.6, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-1.glb" - } - }, - { - "boundingVolume": { - "box": [ - 2.7, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-2.glb" - } - }, - { - "boundingVolume": { - "box": [ - 3.8000001999999995, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-3.glb" - } - }, - { - "boundingVolume": { - "box": [ - 4.9, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-4.glb" - } - }, - { - "boundingVolume": { - "box": [ - 6, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-5.glb" - } - }, - { - "boundingVolume": { - "box": [ - 7.1000004, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-6.glb" - } - }, - { - "boundingVolume": { - "box": [ - 8.2000003, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-7.glb" - } - }, - { - "boundingVolume": { - "box": [ - 9.3, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-8.glb" - } - }, - { - "boundingVolume": { - "box": [ - 10.400001, - 0, - 10.400001, - 0.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.5 - ] - }, - "geometricError": 512, - "content": { - "uri": "plane-9-9.glb" - } - } - ], - "refine": "ADD" - } -} \ No newline at end of file diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 92a5bf922..eaa00bdc5 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -27,16 +27,6 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST( EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); static void setupForSharedImages(SceneGenerationContext& context) { - static FString Path = - IPluginManager::Get().FindPlugin(TEXT("CesiumForUnreal"))->GetBaseDir(); - // IPluginManager returns a relative path by default - convert it to an - // absolute path. - const FString FullPluginsPath = - IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*Path); - static FString TilesetPath = - TEXT("file://") / FullPluginsPath / - TEXT("Resources/Tests/SharedImages/tileset.json"); - context.setCommonProperties( FVector(21.16677692, -67.38013505, -6375355.1944), FVector(-12, -1300, -5), @@ -56,8 +46,8 @@ static void setupForSharedImages(SceneGenerationContext& context) { georeference->SetOriginPlacement(EOriginPlacement::TrueOrigin); ACesium3DTileset* tileset = context.world->SpawnActor(); - tileset->SetTilesetSource(ETilesetSource::FromUrl); - tileset->SetUrl(TilesetPath); + tileset->SetTilesetSource(ETilesetSource::FromCesiumIon); + tileset->SetIonAssetID(2757071); tileset->SetActorLabel(TEXT("SharedImages")); tileset->SetGeoreference(georeference); From d3fbccce2fed84bcb1624dd7b6a9d3f12d3ddb49 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 4 Oct 2024 08:13:02 +1000 Subject: [PATCH 40/68] AttributeSemantics -> VertexAttributeSemantics --- Source/CesiumRuntime/Private/CesiumGltfTextures.cpp | 4 ++-- Source/CesiumRuntime/Private/CesiumGltfTextures.h | 3 ++- extern/cesium-native | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index a6d93f014..0a8310db4 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -6,8 +6,8 @@ #include "CesiumTextureUtility.h" #include "ExtensionImageCesiumUnreal.h" #include -#include #include +#include #include using namespace CesiumAsync; @@ -177,7 +177,7 @@ bool isValidPrimitive( } auto positionAccessorIt = - primitive.attributes.find(AttributeSemantics::POSITION); + primitive.attributes.find(VertexAttributeSemantics::POSITION); if (positionAccessorIt == primitive.attributes.end()) { // This primitive doesn't have a POSITION semantic, so it's not valid. return false; diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.h b/Source/CesiumRuntime/Private/CesiumGltfTextures.h index d3a7544d1..64540f7e9 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.h +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.h @@ -16,7 +16,8 @@ class CesiumGltfTextures { public: /** * Creates all of the texture resources that are required by the given glTF, - * and adds `ExtensionImageCesiumUnreal` to each. + * and adds `ExtensionImageCesiumUnreal` to each. This is intended to be + * called from a worker thread. */ static CesiumAsync::Future createInWorkerThread( const CesiumAsync::AsyncSystem& asyncSystem, diff --git a/extern/cesium-native b/extern/cesium-native index 82fb7e8fb..e781a9c4b 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 82fb7e8fb38a98163a96df806f5ed21d4f7117e8 +Subproject commit e781a9c4bc4cf67a1108755df847e6bd1636a792 From ddff9db2b5b5cb313f703bdcad201a73fb0eb014 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 4 Oct 2024 18:51:42 +1000 Subject: [PATCH 41/68] Adapt for ImageCesium only design. --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 2 +- .../Private/CesiumEncodedFeaturesMetadata.cpp | 29 +++++---- .../Private/CesiumEncodedMetadataUtility.cpp | 40 ++++++------ .../Private/CesiumGltfTextures.cpp | 2 +- .../Private/CesiumTextureUtility.cpp | 5 +- .../Private/Tests/Cesium3DTileset.spec.cpp | 2 +- .../Tests/CesiumFeatureIdTexture.spec.cpp | 12 ++-- .../Private/Tests/CesiumGltfSpecUtility.cpp | 11 ++-- .../Private/Tests/CesiumGltfSpecUtility.h | 15 +++-- .../Tests/CesiumTextureUtility.spec.cpp | 64 +++++++++---------- extern/cesium-native | 2 +- 11 files changed, 95 insertions(+), 89 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index c1aa0d900..3a9c3dc6b 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -2045,7 +2045,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumGltf::SingleAssetDepot* imageDepot = + const CesiumGltf::SharedAssetDepot* imageDepot = this->_pTileset->getSharedAssetDepot().getImageDepot(); float averageAge; size_t deletionCount; diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index 475729dc8..77d1d5b53 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -143,11 +143,11 @@ std::optional encodeFeatureIdTexture( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumGltf::SharedAsset imageCopy = - CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pFeatureIdImage)); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageCesium(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - *imageCopy, + *pImageCopy, addressX, addressY, TextureFilter::TF_Nearest, @@ -510,11 +510,12 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumGltf::SharedAsset image; - image->width = image->height = textureDimension; - image->bytesPerChannel = encodedFormat.bytesPerChannel; - image->channels = encodedFormat.channels; - image->pixelData.resize( + CesiumUtility::IntrusivePointer pImage = + new CesiumGltf::ImageCesium(); + pImage->width = pImage->height = textureDimension; + pImage->bytesPerChannel = encodedFormat.bytesPerChannel; + pImage->channels = encodedFormat.channels; + pImage->pixelData.resize( textureDimension * textureDimension * encodedFormat.bytesPerChannel * encodedFormat.channels); @@ -523,18 +524,18 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( CesiumEncodedMetadataParseColorFromString::encode( *pDescription, property, - gsl::span(image->pixelData), + gsl::span(pImage->pixelData), encodedFormat.bytesPerChannel * encodedFormat.channels); } else /* info.Conversion == ECesiumEncodedMetadataConversion::Coerce */ { CesiumEncodedMetadataCoerce::encode( *pDescription, property, - gsl::span(image->pixelData), + gsl::span(pImage->pixelData), encodedFormat.bytesPerChannel * encodedFormat.channels); } encodedProperty.pTexture = loadTextureAnyThreadPart( - *image, + *pImage, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -657,11 +658,11 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumGltf::SharedAsset imageCopy = - CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageCesium(*pImage); encodedProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - *imageCopy, + *pImageCopy, addressX, addressY, // TODO: account for texture filter diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index bc54d4249..f5c8d9b0f 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -205,19 +205,20 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumGltf::SharedAsset image; - image->bytesPerChannel = encodedFormat.bytesPerChannel; - image->channels = encodedFormat.channels; - image->compressedPixelFormat = CesiumGltf::GpuCompressedPixelFormat::NONE; - image->height = image->width = ceilSqrtFeatureCount; - image->pixelData.resize(size_t( - image->width * image->height * image->channels * - image->bytesPerChannel)); + CesiumUtility::IntrusivePointer pImage = + new CesiumGltf::ImageCesium(); + pImage->bytesPerChannel = encodedFormat.bytesPerChannel; + pImage->channels = encodedFormat.channels; + pImage->compressedPixelFormat = CesiumGltf::GpuCompressedPixelFormat::NONE; + pImage->height = pImage->width = ceilSqrtFeatureCount; + pImage->pixelData.resize(size_t( + pImage->width * pImage->height * pImage->channels * + pImage->bytesPerChannel)); if (isArray) { switch (gpuType) { case ECesiumMetadataPackedGpuType_DEPRECATED::Uint8_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image->pixelData.data()); + uint8* pWritePos = reinterpret_cast(pImage->pixelData.data()); int64_t pixelSize = encodedFormat.channels * encodedFormat.bytesPerChannel; for (int64 i = 0; i < featureCount; ++i) { @@ -233,7 +234,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } break; case ECesiumMetadataPackedGpuType_DEPRECATED::Float_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image->pixelData.data()); + uint8* pWritePos = reinterpret_cast(pImage->pixelData.data()); int64_t pixelSize = encodedFormat.channels * encodedFormat.bytesPerChannel; for (int64 i = 0; i < featureCount; ++i) { @@ -257,7 +258,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } else { switch (gpuType) { case ECesiumMetadataPackedGpuType_DEPRECATED::Uint8_DEPRECATED: { - uint8* pWritePos = reinterpret_cast(image->pixelData.data()); + uint8* pWritePos = reinterpret_cast(pImage->pixelData.data()); for (int64 i = 0; i < featureCount; ++i) { *pWritePos = UCesiumPropertyTablePropertyBlueprintLibrary::GetByte( property, @@ -266,7 +267,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } } break; case ECesiumMetadataPackedGpuType_DEPRECATED::Float_DEPRECATED: { - float* pWritePosF = reinterpret_cast(image->pixelData.data()); + float* pWritePosF = reinterpret_cast(pImage->pixelData.data()); for (int64 i = 0; i < featureCount; ++i) { *pWritePosF = UCesiumPropertyTablePropertyBlueprintLibrary::GetFloat( property, @@ -278,7 +279,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } encodedProperty.pTexture = loadTextureAnyThreadPart( - *image, + *pImage, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -410,11 +411,11 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureTextureProperty.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumGltf::SharedAsset imageCopy = - CesiumGltf::SharedAsset(CesiumGltf::ImageCesium(*pImage)); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageCesium(*pImage); encodedFeatureTextureProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( - *imageCopy, + *pImageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, @@ -516,12 +517,11 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureIdTexture.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumGltf::SharedAsset imageCopy = - CesiumGltf::SharedAsset( - CesiumGltf::ImageCesium(*pFeatureIdImage)); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageCesium(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = MakeShared( std::move(*loadTextureAnyThreadPart( - *imageCopy, + *pImageCopy, TextureAddress::TA_Clamp, TextureAddress::TA_Clamp, TextureFilter::TF_Nearest, diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index 0a8310db4..2101c8790 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -237,7 +237,7 @@ SharedFuture createTextureInLoadThread( const ExtensionImageCesiumUnreal& extension = ExtensionImageCesiumUnreal::getOrCreate( asyncSystem, - *pImage->cesium, + *pImage->pCesium, sRGB, needsMips, std::nullopt); diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 6d2a7d0e8..9c0ba9b00 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -304,7 +304,10 @@ TUniquePtr loadTextureFromModelAnyThreadPart( model.getSafe(model.samplers, texture.sampler); TUniquePtr result = - loadTextureFromImageAndSamplerAnyThreadPart(*image.cesium, sampler, sRGB); + loadTextureFromImageAndSamplerAnyThreadPart( + *image.pCesium, + sampler, + sRGB); if (result) { extension.pTexture = result->pTexture; diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index eaa00bdc5..03773bb09 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -71,7 +71,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - CesiumGltf::SharedAssetDepot& assetDepot = + CesiumGltf::SharedAssetDepots& assetDepot = context.tilesets[0]->GetTileset()->getSharedAssetDepot(); assert(assetDepot.getImagesCount() == 2); } diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 50d6e20d0..8d01319bf 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -136,9 +136,9 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { Image& image = model.images.emplace_back(); - image.cesium->width = image.cesium->height = 1; - image.cesium->channels = 1; - image.cesium->pixelData.push_back(std::byte(42)); + image.pCesium->width = image.pCesium->height = 1; + image.pCesium->channels = 1; + image.pCesium->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; @@ -175,9 +175,9 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with invalid texcoord accessor", [this]() { Image& image = model.images.emplace_back(); - image.cesium->width = image.cesium->height = 1; - image.cesium->channels = 1; - image.cesium->pixelData.push_back(std::byte(42)); + image.pCesium->width = image.pCesium->height = 1; + image.pCesium->channels = 1; + image.pCesium->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp index 47cb9a9d7..db6ded13f 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp @@ -70,12 +70,13 @@ CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel( const int32_t samplerWrapS, const int32_t samplerWrapT) { CesiumGltf::Image& image = model.images.emplace_back(); - image.cesium->bytesPerChannel = 1; - image.cesium->channels = 1; - image.cesium->width = imageWidth; - image.cesium->height = imageHeight; + image.pCesium.emplace(); + image.pCesium->bytesPerChannel = 1; + image.pCesium->channels = 1; + image.pCesium->width = imageWidth; + image.pCesium->height = imageHeight; - std::vector& data = image.cesium->pixelData; + std::vector& data = image.pCesium->pixelData; data.resize(imageWidth * imageHeight); std::memcpy(data.data(), featureIDs.data(), data.size()); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h index 325b85c25..270b01992 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h @@ -200,15 +200,16 @@ CesiumGltf::PropertyTextureProperty& AddPropertyTexturePropertyToModel( classProperty.componentType = componentType; CesiumGltf::Image& image = model.images.emplace_back(); - image.cesium->width = 2; - image.cesium->height = 2; - image.cesium->channels = sizeof(T); - image.cesium->bytesPerChannel = 1; - image.cesium->pixelData.resize(values.size() * sizeof(T)); + image.pCesium.emplace(); + image.pCesium->width = 2; + image.pCesium->height = 2; + image.pCesium->channels = sizeof(T); + image.pCesium->bytesPerChannel = 1; + image.pCesium->pixelData.resize(values.size() * sizeof(T)); std::memcpy( - image.cesium->pixelData.data(), + image.pCesium->pixelData.data(), values.data(), - image.cesium->pixelData.size()); + image.pCesium->pixelData.size()); CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 2fd0b54bf..975275d66 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -21,7 +21,7 @@ BEGIN_DEFINE_SPEC( std::vector originalPixels; std::vector originalMipPixels; std::vector expectedMipPixelsIfGenerated; -SharedAsset imageCesium; +CesiumUtility::IntrusivePointer pImageCesium; void RunTests(); @@ -51,33 +51,33 @@ void CesiumTextureUtilitySpec::Define() { 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; originalMipPixels.clear(); - ImageCesium image{}; - imageCesium = SharedAsset(image); - imageCesium->width = 3; - imageCesium->height = 2; + pImageCesium.emplace(); + pImageCesium->width = 3; + pImageCesium->height = 2; TestEqual( "image buffer size is correct", originalPixels.size(), - imageCesium->width * imageCesium->height * - imageCesium->bytesPerChannel * imageCesium->channels); - imageCesium->pixelData.resize(originalPixels.size()); + pImageCesium->width * pImageCesium->height * + pImageCesium->bytesPerChannel * pImageCesium->channels); + pImageCesium->pixelData.resize(originalPixels.size()); std::memcpy( - imageCesium->pixelData.data(), + pImageCesium->pixelData.data(), originalPixels.data(), originalPixels.size()); - ImageCesium copy = *imageCesium; - CesiumGltfReader::GltfReader::generateMipMaps(copy); + CesiumUtility::IntrusivePointer pCopy = + new ImageCesium(*pImageCesium); + CesiumGltfReader::GltfReader::generateMipMaps(*pCopy); expectedMipPixelsIfGenerated.clear(); - if (copy.mipPositions.size() >= 2) { - expectedMipPixelsIfGenerated.resize(copy.mipPositions[1].byteSize); - for (size_t iSrc = copy.mipPositions[1].byteOffset, iDest = 0; - iDest < copy.mipPositions[1].byteSize; + if (pCopy->mipPositions.size() >= 2) { + expectedMipPixelsIfGenerated.resize(pCopy->mipPositions[1].byteSize); + for (size_t iSrc = pCopy->mipPositions[1].byteOffset, iDest = 0; + iDest < pCopy->mipPositions[1].byteSize; ++iSrc, ++iDest) { - expectedMipPixelsIfGenerated[iDest] = uint8_t(copy.pixelData[iSrc]); + expectedMipPixelsIfGenerated[iDest] = uint8_t(pCopy->pixelData[iSrc]); } } }); @@ -87,31 +87,31 @@ void CesiumTextureUtilitySpec::Define() { Describe("With Mips", [this]() { BeforeEach([this]() { - imageCesium = {}; - imageCesium->width = 3; - imageCesium->height = 2; + pImageCesium.emplace(); + pImageCesium->width = 3; + pImageCesium->height = 2; // Original image (3x2) originalPixels = {0x20, 0x40, 0x80, 0xF0, 0x21, 0x41, 0x81, 0xF1, 0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3, 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; - imageCesium->mipPositions.emplace_back( + pImageCesium->mipPositions.emplace_back( ImageCesiumMipPosition{0, originalPixels.size()}); // Mip 1 (1x1) originalMipPixels = {0x26, 0x46, 0x86, 0xF6}; - imageCesium->mipPositions.emplace_back(ImageCesiumMipPosition{ - imageCesium->mipPositions[0].byteSize, + pImageCesium->mipPositions.emplace_back(ImageCesiumMipPosition{ + pImageCesium->mipPositions[0].byteSize, originalMipPixels.size()}); - imageCesium->pixelData.resize( + pImageCesium->pixelData.resize( originalPixels.size() + originalMipPixels.size()); std::memcpy( - imageCesium->pixelData.data(), + pImageCesium->pixelData.data(), originalPixels.data(), originalPixels.size()); std::memcpy( - imageCesium->pixelData.data() + originalPixels.size(), + pImageCesium->pixelData.data() + originalPixels.size(), originalMipPixels.data(), originalMipPixels.size()); }); @@ -123,7 +123,7 @@ void CesiumTextureUtilitySpec::Define() { void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium non-sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - *imageCesium, + *pImageCesium, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, TextureFilter::TF_Bilinear, @@ -147,7 +147,7 @@ void CesiumTextureUtilitySpec::RunTests() { It("ImageCesium sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - *imageCesium, + *pImageCesium, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, TextureFilter::TF_Trilinear, @@ -178,7 +178,7 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( - *imageCesium, + *pImageCesium, sampler, false); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); @@ -199,7 +199,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.cesium = imageCesium; + image.pCesium = pImageCesium; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -232,7 +232,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.cesium = imageCesium; + image.pCesium = pImageCesium; Sampler& sampler1 = model.samplers.emplace_back(); sampler1.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -299,7 +299,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.cesium = imageCesium; + image.pCesium = pImageCesium; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -348,7 +348,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.cesium = imageCesium; + image.pCesium = pImageCesium; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; diff --git a/extern/cesium-native b/extern/cesium-native index e781a9c4b..ab21441ce 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit e781a9c4bc4cf67a1108755df847e6bd1636a792 +Subproject commit ab21441ce42bc508c99f0badd6a50612bb5995c8 From edd822680345fa246e7b4cd1776a6d828fb8505a Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 4 Oct 2024 21:16:58 +1000 Subject: [PATCH 42/68] More updates for changes in Native. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 12 ++++++------ .../Private/Tests/Cesium3DTileset.spec.cpp | 4 ++-- .../Private/Tests/CesiumFeatureIdTexture.spec.cpp | 2 ++ extern/cesium-native | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 3a9c3dc6b..6b50d9d61 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -2045,18 +2045,18 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumGltf::SharedAssetDepot* imageDepot = - this->_pTileset->getSharedAssetDepot().getImageDepot(); + const CesiumGltf::SharedAssetDepot& imageDepot = + this->_pTileset->getSharedAssetSystem().image(); float averageAge; size_t deletionCount; - imageDepot->getDeletionStats(averageAge, deletionCount); + imageDepot.getDeletionStats(averageAge, deletionCount); UE_LOG( LogCesium, Display, TEXT( "Images depot: %d distinct assets, %d total usages, %d assets pending deletion, %f average age"), - imageDepot->getDistinctCount(), - imageDepot->getUsageCount(), + imageDepot.getDistinctCount(), + imageDepot.getUsageCount(), deletionCount, averageAge); } @@ -2235,7 +2235,7 @@ void ACesium3DTileset::Tick(float DeltaTime) { this->UpdateLoadStatus(); if (this->_pTileset) { - this->_pTileset->getSharedAssetDepot().deletionTick(); + this->_pTileset->getSharedAssetSystem().deletionTick(); } } diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 03773bb09..bfa843276 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -71,8 +71,8 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - CesiumGltf::SharedAssetDepots& assetDepot = - context.tilesets[0]->GetTileset()->getSharedAssetDepot(); + CesiumGltf::SharedAssetSystem& assetSystem = + context.tilesets[0]->GetTileset()->getSharedAssetSystem(); assert(assetDepot.getImagesCount() == 2); } diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 8d01319bf..36da73f40 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -136,6 +136,7 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { Image& image = model.images.emplace_back(); + image.pCesium.emplace(); image.pCesium->width = image.pCesium->height = 1; image.pCesium->channels = 1; image.pCesium->pixelData.push_back(std::byte(42)); @@ -175,6 +176,7 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with invalid texcoord accessor", [this]() { Image& image = model.images.emplace_back(); + image.pCesium.emplace(); image.pCesium->width = image.pCesium->height = 1; image.pCesium->channels = 1; image.pCesium->pixelData.push_back(std::byte(42)); diff --git a/extern/cesium-native b/extern/cesium-native index ab21441ce..275bac032 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit ab21441ce42bc508c99f0badd6a50612bb5995c8 +Subproject commit 275bac03251ccb1e01d419e402036d97893a77e0 From 0a14600737b3264b1dc0dd0d9273265eb7927268 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 4 Oct 2024 13:50:42 -0400 Subject: [PATCH 43/68] Update cesium-native to shared-assets branch --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index 275bac032..aa81cbd67 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 275bac03251ccb1e01d419e402036d97893a77e0 +Subproject commit aa81cbd679db78d166fd2bb0fbfab91f61ed683d From 90ea7e470fe3f840a7caa76ea61b500a20c96b4b Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 4 Oct 2024 15:13:12 -0400 Subject: [PATCH 44/68] Integrate deletion rework --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 6b50d9d61..e73c2b4f3 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -2047,18 +2047,15 @@ void ACesium3DTileset::updateLastViewUpdateResultState( if (this->LogAssetStats && this->_pTileset) { const CesiumGltf::SharedAssetDepot& imageDepot = this->_pTileset->getSharedAssetSystem().image(); - float averageAge; - size_t deletionCount; - imageDepot.getDeletionStats(averageAge, deletionCount); UE_LOG( LogCesium, Display, TEXT( - "Images depot: %d distinct assets, %d total usages, %d assets pending deletion, %f average age"), + "Images depot: %d distinct assets, %d total usages, %d assets pending deletion, %d total size in bytes"), imageDepot.getDistinctCount(), imageDepot.getUsageCount(), - deletionCount, - averageAge); + imageDepot.getDeletionCandidateCount(), + imageDepot.getDeletionCandidateTotalSizeBytes()); } } } @@ -2233,10 +2230,6 @@ void ACesium3DTileset::Tick(float DeltaTime) { } this->UpdateLoadStatus(); - - if (this->_pTileset) { - this->_pTileset->getSharedAssetSystem().deletionTick(); - } } void ACesium3DTileset::EndPlay(const EEndPlayReason::Type EndPlayReason) { From 9a6328278ae7a93389ad7986524ed7b0a2a64a28 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 8 Oct 2024 15:28:18 -0400 Subject: [PATCH 45/68] Add skeleton of a Snowdon test, properly report texture stats --- .../Private/CesiumTextureResource.cpp | 97 ++++++++++++------- .../Private/CesiumTextureResource.h | 4 +- .../Private/CesiumTextureUtility.h | 4 + .../Private/Tests/Cesium3DTileset.spec.cpp | 45 +++++++++ extern/cesium-native | 2 +- 5 files changed, 117 insertions(+), 35 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index 7fa9cd680..ae9438bfd 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -31,7 +31,8 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResource { TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData); + uint32 extData, + bool isPrimary); FCesiumUseExistingTextureResource( const TSharedPtr& pExistingTexture, @@ -44,7 +45,8 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResource { TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData); + uint32 extData, + bool isPrimary); protected: virtual FTextureRHIRef InitializeTextureRHI() override; @@ -182,6 +184,7 @@ FTexture2DRHIRef createAsyncTextureAndWait( ETextureCreateFlags Flags, void** InitialMipData, uint32 NumInitialMips) { + #if ENGINE_VERSION_5_4_OR_HIGHER FGraphEventRef CompletionEvent; @@ -352,6 +355,8 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { FTexture2DRHIRef textureReference = CreateRHITexture2D_Async(imageCesium, *maybePixelFormat, sRGB); + textureReference->SetName( + FName(UTF8_TO_TCHAR(imageCesium.getUniqueAssetId().c_str()))); auto pResult = TUniquePtr< FCesiumUseExistingTextureResource, FCesiumTextureResourceDeleter>(new FCesiumUseExistingTextureResource( @@ -365,7 +370,8 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { addressY, sRGB, needsMipMaps, - 0)); + 0, + true)); // Clear the now-unnecessary copy of the pixel data. // Calling clear() isn't good enough because it @@ -421,7 +427,8 @@ FCesiumTextureResourceUniquePtr FCesiumTextureResource::CreateWrapped( addressY, sRGB, useMipMapsIfAvailable, - 0)); + 0, + false)); } /*static*/ void FCesiumTextureResource::Destroy(FCesiumTextureResource* p) { @@ -445,7 +452,8 @@ FCesiumTextureResource::FCesiumTextureResource( TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData) + uint32 extData, + bool isPrimary) : _textureGroup(textureGroup), _width(width), _height(height), @@ -454,7 +462,8 @@ FCesiumTextureResource::FCesiumTextureResource( _addressX(convertAddressMode(addressX)), _addressY(convertAddressMode(addressY)), _useMipsIfAvailable(useMipsIfAvailable), - _platformExtData(extData) { + _platformExtData(extData), + _isPrimary(isPrimary) { this->bGreyScaleFormat = (_format == PF_G8) || (_format == PF_BC4); this->bSRGB = sRGB; STAT(this->_lodGroupStatName = TextureGroupStatFNames[this->_textureGroup]); @@ -500,32 +509,42 @@ void FCesiumTextureResource::InitRHI() { RHIUpdateTextureReference(TextureReferenceRHI, this->TextureRHI); #if STATS - ETextureCreateFlags textureFlags = TexCreate_ShaderResource; - if (this->bSRGB) { - textureFlags |= TexCreate_SRGB; - } - - const FIntPoint MipExtents = - CalcMipMapExtent(this->_width, this->_height, this->_format, 0); - uint32 alignment; - this->_textureSize = RHICalcTexture2DPlatformSize( - MipExtents.X, - MipExtents.Y, - this->_format, - this->GetCurrentMipCount(), - 1, - textureFlags, - FRHIResourceCreateInfo(this->_platformExtData), - alignment); + if (this->_isPrimary) { + ETextureCreateFlags textureFlags = TexCreate_ShaderResource; + if (this->bSRGB) { + textureFlags |= TexCreate_SRGB; + } - INC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); - INC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); + const FIntPoint MipExtents = + CalcMipMapExtent(this->_width, this->_height, this->_format, 0); + const FRHIResourceCreateInfo CreateInfo(this->_platformExtData); + + FDynamicRHI::FRHICalcTextureSizeResult result = RHICalcTexturePlatformSize( + FRHITextureDesc::Create2D( + MipExtents, + this->_format, + CreateInfo.ClearValueBinding, + textureFlags, + this->GetCurrentMipCount(), + 1, + CreateInfo.ExtData), + 0); + + this->_textureSize = result.Size; + + INC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); + INC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); + } #endif } void FCesiumTextureResource::ReleaseRHI() { - DEC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); - DEC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); +#if STATS + if (this->_isPrimary) { + DEC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); + DEC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); + } +#endif RHIUpdateTextureReference(TextureReferenceRHI, nullptr); @@ -570,7 +589,8 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData) + uint32 extData, + bool isPrimary) : FCesiumTextureResource( textureGroup, width, @@ -581,7 +601,8 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( addressY, sRGB, useMipsIfAvailable, - extData), + extData, + isPrimary), _pExistingTexture(nullptr) { this->TextureRHI = std::move(existingTexture); } @@ -597,7 +618,8 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData) + uint32 extData, + bool isPrimary) : FCesiumTextureResource( textureGroup, width, @@ -608,7 +630,8 @@ FCesiumUseExistingTextureResource::FCesiumUseExistingTextureResource( addressY, sRGB, useMipsIfAvailable, - extData), + extData, + isPrimary), _pExistingTexture(pExistingTexture) {} FTextureRHIRef FCesiumUseExistingTextureResource::InitializeTextureRHI() { @@ -641,11 +664,19 @@ FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( addressY, sRGB, useMipsIfAvailable, - extData), + extData, + true), _image(std::move(image)) {} FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { - FRHIResourceCreateInfo createInfo{TEXT("CesiumTextureUtility")}; + // Use the asset ID as the name of the texture so it will be visible in the + // Render Resource Viewer. + FString debugName = TEXT("CesiumTextureUtility"); + if (!this->_image.getUniqueAssetId().empty()) { + debugName = UTF8_TO_TCHAR(this->_image.getUniqueAssetId().c_str()); + } + + FRHIResourceCreateInfo createInfo{*debugName}; createInfo.BulkData = nullptr; createInfo.ExtData = _platformExtData; diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index cba96bc6b..4cbd8521a 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -90,7 +90,8 @@ class FCesiumTextureResource : public FTextureResource { TextureAddress addressY, bool sRGB, bool useMipsIfAvailable, - uint32 extData); + uint32 extData, + bool isPrimary); uint32 GetSizeX() const override { return this->_width; } uint32 GetSizeY() const override { return this->_height; } @@ -120,4 +121,5 @@ class FCesiumTextureResource : public FTextureResource { uint32 _platformExtData; FName _lodGroupStatName; uint64 _textureSize; + bool _isPrimary; }; diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 97ee85e43..401643d41 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -4,6 +4,7 @@ #include "CesiumGltf/Model.h" #include "CesiumGltf/SharedAssetDepot.h" +#include "CesiumGltf/Texture.h" #include "CesiumMetadataValueType.h" #include "CesiumTextureResource.h" #include "Engine/Texture.h" @@ -201,4 +202,7 @@ std::optional getPixelFormatForImageCesium( const CesiumGltf::ImageCesium& imageCesium, const std::optional overridePixelFormat); +std::optional +getUnrealTextureFromGltfTexture(const CesiumGltf::Texture& texture); + } // namespace CesiumTextureUtility diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index bfa843276..23de71875 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -4,6 +4,7 @@ #include "Cesium3DTileset.h" #include "CesiumGlobeAnchorComponent.h" +#include "CesiumGltfComponent.h" #include "CesiumLoadTestCore.h" #include "CesiumSceneGeneration.h" #include "CesiumSunSky.h" @@ -88,6 +89,50 @@ bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { TEST_SCREEN_HEIGHT); } +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FCesium3DTilesetSnowdonBenchmark, + "Cesium.Performance.3DTileset.SnowdonBenchmark", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter); + +static void setupForSnowdon(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-79.8867314431, 40.0223377722, 197.1008007424), + FVector(-293.823058, 6736.144397, 2730.501500), + FRotator(-13.400000, -87.799997, 0.000000), + 60.0f); + + context.sunSky->TimeZone = 5.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetTilesetSource(ETilesetSource::FromCesiumIon); + tileset->SetIonAssetID(2758251); + + tileset->SetActorLabel(TEXT("Snowdon")); + tileset->SuspendUpdate = false; + tileset->LogSelectionStats = true; + context.tilesets.push_back(tileset); + + ADirectionalLight* Light = context.world->SpawnActor(); + Light->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270))); +} + +void snowdonPass( + SceneGenerationContext& context, + TestPass::TestingParameter parameter) {} + +bool FCesium3DTilesetSnowdonBenchmark::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Refresh Pass", snowdonPass, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForSnowdon, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + } // namespace Cesium #endif diff --git a/extern/cesium-native b/extern/cesium-native index aa81cbd67..924b000e0 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit aa81cbd679db78d166fd2bb0fbfab91f61ed683d +Subproject commit 924b000e00fc9054d60591bae494c71532a8ec61 From fc60c1e92d7bedf86cf31bacdb8d7f94952b7515 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 10 Oct 2024 22:20:23 +1100 Subject: [PATCH 46/68] Update cesium-native. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 6 +++--- Source/CesiumRuntime/Private/CesiumTextureResource.h | 2 +- Source/CesiumRuntime/Private/CesiumTextureUtility.h | 2 +- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 2 +- extern/cesium-native | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index e73c2b4f3..54d32122a 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -11,6 +11,7 @@ #include "Cesium3DTilesetLoadFailureDetails.h" #include "Cesium3DTilesetRoot.h" #include "CesiumActors.h" +#include "CesiumAsync/SharedAssetDepot.h" #include "CesiumBoundingVolumeComponent.h" #include "CesiumCamera.h" #include "CesiumCameraManager.h" @@ -19,7 +20,6 @@ #include "CesiumGeospatial/GlobeTransforms.h" #include "CesiumGltf/ImageCesium.h" #include "CesiumGltf/Ktx2TranscodeTargets.h" -#include "CesiumGltf/SharedAssetDepot.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPointsSceneProxyUpdater.h" #include "CesiumGltfPrimitiveComponent.h" @@ -2045,8 +2045,8 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumGltf::SharedAssetDepot& imageDepot = - this->_pTileset->getSharedAssetSystem().image(); + const CesiumAsync::SharedAssetDepot& imageDepot = + *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, Display, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index 4cbd8521a..567abec86 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -5,8 +5,8 @@ #include "CesiumCommon.h" #include "Engine/Texture.h" #include "TextureResource.h" +#include #include -#include class FCesiumTextureResource; diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 401643d41..bcb835db4 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -2,8 +2,8 @@ #pragma once +#include "CesiumAsync/SharedAssetDepot.h" #include "CesiumGltf/Model.h" -#include "CesiumGltf/SharedAssetDepot.h" #include "CesiumGltf/Texture.h" #include "CesiumMetadataValueType.h" #include "CesiumTextureResource.h" diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 23de71875..da746bd60 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -72,7 +72,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - CesiumGltf::SharedAssetSystem& assetSystem = + CesiumGltfReader::GltfSharedAssetSystem& assetSystem = context.tilesets[0]->GetTileset()->getSharedAssetSystem(); assert(assetDepot.getImagesCount() == 2); } diff --git a/extern/cesium-native b/extern/cesium-native index 924b000e0..526e66068 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 924b000e00fc9054d60591bae494c71532a8ec61 +Subproject commit 526e660687d64d98b3f1aea3b8338dafbac0b1db From 1b59597d65d60bffecb94efd9e434e94401247da Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 10 Oct 2024 15:45:04 -0400 Subject: [PATCH 47/68] Rename ImageCesium --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 12 +-- .../Private/CesiumEncodedFeaturesMetadata.cpp | 24 +++--- .../Private/CesiumEncodedFeaturesMetadata.h | 2 +- .../Private/CesiumEncodedMetadataUtility.cpp | 22 +++--- .../Private/CesiumEncodedMetadataUtility.h | 2 +- .../Private/CesiumGltfTextures.cpp | 6 +- .../Private/CesiumPropertyTextureProperty.cpp | 6 +- .../Private/CesiumTextureResource.cpp | 44 +++++------ .../Private/CesiumTextureResource.h | 8 +- .../Private/CesiumTextureUtility.cpp | 22 +++--- .../Private/CesiumTextureUtility.h | 14 ++-- ...real.cpp => ExtensionImageAssetUnreal.cpp} | 38 +++++---- ...umUnreal.h => ExtensionImageAssetUnreal.h} | 24 +++--- .../CesiumPropertyTextureProperty.spec.cpp | 78 +++++++++---------- .../Tests/CesiumTextureUtility.spec.cpp | 60 +++++++------- .../Public/CesiumPropertyTextureProperty.h | 2 +- extern/cesium-native | 2 +- 17 files changed, 181 insertions(+), 185 deletions(-) rename Source/CesiumRuntime/Private/{ExtensionImageCesiumUnreal.cpp => ExtensionImageAssetUnreal.cpp} (68%) rename Source/CesiumRuntime/Private/{ExtensionImageCesiumUnreal.h => ExtensionImageAssetUnreal.h} (82%) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 54d32122a..1be3fbf0d 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -18,7 +18,7 @@ #include "CesiumCommon.h" #include "CesiumCustomVersion.h" #include "CesiumGeospatial/GlobeTransforms.h" -#include "CesiumGltf/ImageCesium.h" +#include "CesiumGltf/ImageAsset.h" #include "CesiumGltf/Ktx2TranscodeTargets.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPointsSceneProxyUpdater.h" @@ -41,7 +41,7 @@ #include "Engine/TextureRenderTarget2D.h" #include "Engine/World.h" #include "EngineUtils.h" -#include "ExtensionImageCesiumUnreal.h" +#include "ExtensionImageAssetUnreal.h" #include "GameFramework/PlayerController.h" #include "Kismet/GameplayStatics.h" #include "LevelSequenceActor.h" @@ -867,7 +867,7 @@ class UnrealResourcePreparer } virtual void* prepareRasterInLoadThread( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, const std::any& rendererOptions) override { auto ppOptions = std::any_cast(&rendererOptions); @@ -893,8 +893,8 @@ class UnrealResourcePreparer // TODO: sRGB should probably be configurable on the raster overlay. bool sRGB = true; - const ExtensionImageCesiumUnreal& extension = - ExtensionImageCesiumUnreal::getOrCreate( + const ExtensionImageAssetUnreal& extension = + ExtensionImageAssetUnreal::getOrCreate( CesiumAsync::AsyncSystem(nullptr), // TODO image, sRGB, @@ -2045,7 +2045,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumAsync::SharedAssetDepot& imageDepot = + const CesiumAsync::SharedAssetDepot& imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp index 77d1d5b53..9528d8734 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.cpp @@ -99,7 +99,7 @@ encodeFeatureIdAttribute(const FCesiumFeatureIdAttribute& attribute) { std::optional encodeFeatureIdTexture( const FCesiumFeatureIdTexture& texture, - TMap>& + TMap>& featureIdTextureMap) { const ECesiumFeatureIdTextureStatus status = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( @@ -114,7 +114,7 @@ std::optional encodeFeatureIdTexture( const CesiumGltf::FeatureIdTextureView& featureIdTextureView = texture.getFeatureIdTextureView(); - const CesiumGltf::ImageCesium* pFeatureIdImage = + const CesiumGltf::ImageAsset* pFeatureIdImage = featureIdTextureView.getImage(); TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::EncodeFeatureIdTexture) @@ -143,8 +143,8 @@ std::optional encodeFeatureIdTexture( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumUtility::IntrusivePointer pImageCopy = - new CesiumGltf::ImageCesium(*pFeatureIdImage); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageAsset(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( *pImageCopy, @@ -177,7 +177,7 @@ EncodedPrimitiveFeatures encodePrimitiveFeaturesAnyThreadPart( // Not all feature ID sets are necessarily textures, but reserve the max // amount just in case. - TMap> + TMap> featureIdTextureMap; featureIdTextureMap.Reserve(featureIDSetDescriptions.Num()); @@ -510,8 +510,8 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumUtility::IntrusivePointer pImage = - new CesiumGltf::ImageCesium(); + CesiumUtility::IntrusivePointer pImage = + new CesiumGltf::ImageAsset(); pImage->width = pImage->height = textureDimension; pImage->bytesPerChannel = encodedFormat.bytesPerChannel; pImage->channels = encodedFormat.channels; @@ -592,7 +592,7 @@ EncodedPropertyTable encodePropertyTableAnyThreadPart( EncodedPropertyTexture encodePropertyTextureAnyThreadPart( const FCesiumPropertyTextureDescription& propertyTextureDescription, const FCesiumPropertyTexture& propertyTexture, - TMap>& + TMap>& propertyTexturePropertyMap) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::EncodePropertyTexture) @@ -641,7 +641,7 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( encodedProperty.channels[i] = channels[i]; } - const CesiumGltf::ImageCesium* pImage = property.getImage(); + const CesiumGltf::ImageAsset* pImage = property.getImage(); TWeakPtr* pMappedUnrealImageIt = propertyTexturePropertyMap.Find(pImage); @@ -658,8 +658,8 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( } // Copy the image, so that we can keep a copy of it in the glTF. - CesiumUtility::IntrusivePointer pImageCopy = - new CesiumGltf::ImageCesium(*pImage); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageAsset(*pImage); encodedProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( *pImageCopy, @@ -772,7 +772,7 @@ EncodedModelMetadata encodeModelMetadataAnyThreadPart( UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures(metadata); result.propertyTextures.Reserve(propertyTextures.Num()); - TMap> + TMap> propertyTexturePropertyMap; propertyTexturePropertyMap.Reserve(propertyTextures.Num()); diff --git a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.h b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.h index 31ec20866..0bb838505 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.h +++ b/Source/CesiumRuntime/Private/CesiumEncodedFeaturesMetadata.h @@ -476,7 +476,7 @@ EncodedPropertyTexture encodePropertyTextureAnyThreadPart( const FCesiumPropertyTextureDescription& propertyTextureDescription, const FCesiumPropertyTexture& propertyTexture, TMap< - const CesiumGltf::ImageCesium*, + const CesiumGltf::ImageAsset*, TWeakPtr>& propertyTexturePropertyMap); diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp index f5c8d9b0f..28f6fa3d1 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.cpp @@ -205,8 +205,8 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( ? floorSqrtFeatureCount : (floorSqrtFeatureCount + 1); - CesiumUtility::IntrusivePointer pImage = - new CesiumGltf::ImageCesium(); + CesiumUtility::IntrusivePointer pImage = + new CesiumGltf::ImageAsset(); pImage->bytesPerChannel = encodedFormat.bytesPerChannel; pImage->channels = encodedFormat.channels; pImage->compressedPixelFormat = CesiumGltf::GpuCompressedPixelFormat::NONE; @@ -293,7 +293,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( } EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( - TMap>& + TMap>& featureTexturePropertyMap, const FFeatureTextureDescription& featureTextureDescription, const FString& featureTextureName, @@ -322,7 +322,7 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( const FCesiumPropertyTextureProperty& featureTextureProperty = propertyIt.Value; - const CesiumGltf::ImageCesium* pImage = featureTextureProperty.getImage(); + const CesiumGltf::ImageAsset* pImage = featureTextureProperty.getImage(); if (!pImage) { UE_LOG( @@ -411,8 +411,8 @@ EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureTextureProperty.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumUtility::IntrusivePointer pImageCopy = - new CesiumGltf::ImageCesium(*pImage); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageAsset(*pImage); encodedFeatureTextureProperty.pTexture = MakeShared(std::move(*loadTextureAnyThreadPart( *pImageCopy, @@ -462,7 +462,7 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( } } - TMap> + TMap> featureIdTextureMap; featureIdTextureMap.Reserve(featureIdTextures.Num()); @@ -490,7 +490,7 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( if (pFeatureIdTexture) { const CesiumGltf::FeatureIdTextureView& featureIdTextureView = pFeatureIdTexture->getFeatureIdTextureView(); - const CesiumGltf::ImageCesium* pFeatureIdImage = + const CesiumGltf::ImageAsset* pFeatureIdImage = featureIdTextureView.getImage(); if (!pFeatureIdImage) { @@ -517,8 +517,8 @@ EncodedMetadataPrimitive encodeMetadataPrimitiveAnyThreadPart( if (pMappedUnrealImageIt) { encodedFeatureIdTexture.pTexture = pMappedUnrealImageIt->Pin(); } else { - CesiumUtility::IntrusivePointer pImageCopy = - new CesiumGltf::ImageCesium(*pFeatureIdImage); + CesiumUtility::IntrusivePointer pImageCopy = + new CesiumGltf::ImageAsset(*pFeatureIdImage); encodedFeatureIdTexture.pTexture = MakeShared( std::move(*loadTextureAnyThreadPart( *pImageCopy, @@ -599,7 +599,7 @@ EncodedMetadata encodeMetadataAnyThreadPart( const TMap& featureTextures = UCesiumModelMetadataBlueprintLibrary::GetFeatureTextures(metadata); result.encodedFeatureTextures.Reserve(featureTextures.Num()); - TMap> + TMap> featureTexturePropertyMap; featureTexturePropertyMap.Reserve(featureTextures.Num()); for (const auto& featureTextureIt : featureTextures) { diff --git a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.h b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.h index 070f2b00c..84c4b3d23 100644 --- a/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.h +++ b/Source/CesiumRuntime/Private/CesiumEncodedMetadataUtility.h @@ -106,7 +106,7 @@ EncodedMetadataFeatureTable encodeMetadataFeatureTableAnyThreadPart( EncodedFeatureTexture encodeFeatureTextureAnyThreadPart( TMap< - const CesiumGltf::ImageCesium*, + const CesiumGltf::ImageAsset*, TWeakPtr>& featureTexturePropertyMap, const FFeatureTextureDescription& featureTextureDescription, diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index 2101c8790..bc52d55bf 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -4,7 +4,7 @@ #include "CesiumRuntime.h" #include "CesiumTextureResource.h" #include "CesiumTextureUtility.h" -#include "ExtensionImageCesiumUnreal.h" +#include "ExtensionImageAssetUnreal.h" #include #include #include @@ -234,8 +234,8 @@ SharedFuture createTextureInLoadThread( check(pTexture->source >= 0 && pTexture->source < imageNeedsMipmaps.size()); bool needsMips = imageNeedsMipmaps[pTexture->source]; - const ExtensionImageCesiumUnreal& extension = - ExtensionImageCesiumUnreal::getOrCreate( + const ExtensionImageAssetUnreal& extension = + ExtensionImageAssetUnreal::getOrCreate( asyncSystem, *pImage->pCesium, sRGB, diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp index 0cd8653fa..cc96ff84b 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp @@ -333,13 +333,13 @@ const CesiumGltf::Sampler* FCesiumPropertyTextureProperty::getSampler() const { }); } -const CesiumGltf::ImageCesium* +const CesiumGltf::ImageAsset* FCesiumPropertyTextureProperty::getImage() const { - return propertyTexturePropertyCallback( + return propertyTexturePropertyCallback( this->_property, this->_valueType, this->_normalized, - [](const auto& view) -> const CesiumGltf::ImageCesium* { + [](const auto& view) -> const CesiumGltf::ImageAsset* { return view.getImage(); }); } diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index ae9438bfd..e86e79986 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -65,7 +65,7 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResource { class FCesiumCreateNewTextureResource : public FCesiumTextureResource { public: FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium&& image, + CesiumGltf::ImageAsset&& image, TextureGroup textureGroup, uint32 width, uint32 height, @@ -81,7 +81,7 @@ class FCesiumCreateNewTextureResource : public FCesiumTextureResource { virtual FTextureRHIRef InitializeTextureRHI() override; private: - CesiumGltf::ImageCesium _image; + CesiumGltf::ImageAsset _image; }; ESamplerFilter convertFilter(TextureFilter filter) { @@ -126,7 +126,7 @@ void CopyMip( void* pDest, uint32 destPitch, EPixelFormat format, - const CesiumGltf::ImageCesium& src, + const CesiumGltf::ImageAsset& src, uint32 mipIndex) { size_t byteOffset = 0; size_t byteSize = 0; @@ -134,7 +134,7 @@ void CopyMip( byteOffset = 0; byteSize = src.pixelData.size(); } else { - const CesiumGltf::ImageCesiumMipPosition& mipPos = + const CesiumGltf::ImageAssetMipPosition& mipPos = src.mipPositions[mipIndex]; byteOffset = mipPos.byteOffset; byteSize = mipPos.byteSize; @@ -245,7 +245,7 @@ FTexture2DRHIRef createAsyncTextureAndWait( * @return The RHI texture reference. */ FTexture2DRHIRef CreateRHITexture2D_Async( - const CesiumGltf::ImageCesium& image, + const CesiumGltf::ImageAsset& image, EPixelFormat format, bool sRGB) { check(GRHISupportsAsyncTextureCreation); @@ -271,7 +271,7 @@ FTexture2DRHIRef CreateRHITexture2D_Async( void* mipsData[16]; for (size_t i = 0; i < mipCount; ++i) { - const CesiumGltf::ImageCesiumMipPosition& mipPos = image.mipPositions[i]; + const CesiumGltf::ImageAssetMipPosition& mipPos = image.mipPositions[i]; mipsData[i] = (void*)(&image.pixelData[mipPos.byteOffset]); } @@ -303,7 +303,7 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { } /*static*/ FCesiumTextureResourceUniquePtr FCesiumTextureResource::CreateNew( - CesiumGltf::ImageCesium& imageCesium, + CesiumGltf::ImageAsset& imageCesium, TextureGroup textureGroup, const std::optional& overridePixelFormat, TextureFilter filter, @@ -328,7 +328,7 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { } std::optional maybePixelFormat = - CesiumTextureUtility::getPixelFormatForImageCesium( + CesiumTextureUtility::getPixelFormatForImageAsset( imageCesium, overridePixelFormat); if (!maybePixelFormat) { @@ -379,7 +379,7 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { std::vector pixelData; imageCesium.pixelData.swap(pixelData); - std::vector mipPositions; + std::vector mipPositions; imageCesium.mipPositions.swap(mipPositions); return pResult; @@ -519,18 +519,16 @@ void FCesiumTextureResource::InitRHI() { CalcMipMapExtent(this->_width, this->_height, this->_format, 0); const FRHIResourceCreateInfo CreateInfo(this->_platformExtData); - FDynamicRHI::FRHICalcTextureSizeResult result = RHICalcTexturePlatformSize( - FRHITextureDesc::Create2D( - MipExtents, - this->_format, - CreateInfo.ClearValueBinding, - textureFlags, - this->GetCurrentMipCount(), - 1, - CreateInfo.ExtData), - 0); - - this->_textureSize = result.Size; + uint32 alignment; + this->_textureSize = RHICalcTexture2DPlatformSize( + MipExtents.X, + MipExtents.Y, + this->_format, + this->GetCurrentMipCount(), + 1, + textureFlags, + FRHIResourceCreateInfo(this->_platformExtData), + alignment); INC_DWORD_STAT_BY(STAT_TextureMemory, this->_textureSize); INC_DWORD_STAT_FNAME_BY(this->_lodGroupStatName, this->_textureSize); @@ -643,7 +641,7 @@ FTextureRHIRef FCesiumUseExistingTextureResource::InitializeTextureRHI() { } FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( - CesiumGltf::ImageCesium&& image, + CesiumGltf::ImageAsset&& image, TextureGroup textureGroup, uint32 width, uint32 height, @@ -732,7 +730,7 @@ FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { std::vector pixelData; this->_image.pixelData.swap(pixelData); - std::vector mipPositions; + std::vector mipPositions; this->_image.mipPositions.swap(mipPositions); return rhiTexture; diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.h b/Source/CesiumRuntime/Private/CesiumTextureResource.h index 567abec86..4e7906c18 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.h +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.h @@ -6,7 +6,7 @@ #include "Engine/Texture.h" #include "TextureResource.h" #include -#include +#include class FCesiumTextureResource; @@ -25,7 +25,7 @@ using FCesiumTextureResourceUniquePtr = class FCesiumTextureResource : public FTextureResource { public: /** - * Create a new FCesiumTextureResource from an ImageCesium and the given + * Create a new FCesiumTextureResource from an `ImageAsset` and the given * sampling parameters. This method is intended to be called from a worker * thread, not from the game or render thread. * @@ -34,7 +34,7 @@ class FCesiumTextureResource : public FTextureResource { * `sizeBytes` will be set to its previous size. * @param textureGroup The texture group in which to create this texture. * @param overridePixelFormat Overrides the pixel format. If std::nullopt, the - * format is inferred from the `ImageCesium`. + * format is inferred from the `ImageAsset`. * @param filter The texture filtering to use when sampling this texture. * @param addressX The X texture addressing mode to use when sampling this * texture. @@ -48,7 +48,7 @@ class FCesiumTextureResource : public FTextureResource { * created. */ static FCesiumTextureResourceUniquePtr CreateNew( - CesiumGltf::ImageCesium& imageCesium, + CesiumGltf::ImageAsset& imageCesium, TextureGroup textureGroup, const std::optional& overridePixelFormat, TextureFilter filter, diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 9c0ba9b00..603b6bde9 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -10,7 +10,7 @@ #include "CesiumTextureResource.h" #include "Containers/ResourceArray.h" #include "DynamicRHI.h" -#include "ExtensionImageCesiumUnreal.h" +#include "ExtensionImageAssetUnreal.h" #include "GenericPlatform/GenericPlatformProcess.h" #include "PixelFormat.h" #include "RHICommandList.h" @@ -23,7 +23,7 @@ #include "UObject/Package.h" #include #include -#include +#include #include #include #include @@ -109,7 +109,7 @@ FTexture2DRHIRef createAsyncTextureAndWaitOld( * @return The RHI texture reference. */ FTexture2DRHIRef CreateRHITexture2D_AsyncOld( - const CesiumGltf::ImageCesium& image, + const CesiumGltf::ImageAsset& image, EPixelFormat format, bool sRGB) { check(GRHISupportsAsyncTextureCreation); @@ -135,7 +135,7 @@ FTexture2DRHIRef CreateRHITexture2D_AsyncOld( void* mipsData[16]; for (size_t i = 0; i < mipCount; ++i) { - const CesiumGltf::ImageCesiumMipPosition& mipPos = image.mipPositions[i]; + const CesiumGltf::ImageAssetMipPosition& mipPos = image.mipPositions[i]; mipsData[i] = (void*)(&image.pixelData[mipPos.byteOffset]); } @@ -369,7 +369,7 @@ bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { } TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, const CesiumGltf::Sampler& sampler, bool sRGB) { return loadTextureAnyThreadPart( @@ -414,7 +414,7 @@ static UTexture2D* CreateTexture2D(LoadedTextureResult* pHalfLoadedTexture) { } TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -422,10 +422,10 @@ TUniquePtr loadTextureAnyThreadPart( TextureGroup group, bool sRGB, std::optional overridePixelFormat) { - // The FCesiumTextureResource for the ImageCesium should already be created at + // The FCesiumTextureResource for the ImageAsset should already be created at // this point, if it can be. - const ExtensionImageCesiumUnreal& extension = - ExtensionImageCesiumUnreal::getOrCreate( + const ExtensionImageAssetUnreal& extension = + ExtensionImageAssetUnreal::getOrCreate( CesiumAsync::AsyncSystem(nullptr), image, sRGB, @@ -546,8 +546,8 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } } -std::optional getPixelFormatForImageCesium( - const ImageCesium& imageCesium, +std::optional getPixelFormatForImageAsset( + const ImageAsset& imageCesium, const std::optional overridePixelFormat) { if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { switch (imageCesium.compressedPixelFormat) { diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index bcb835db4..3c1b9d8e5 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -17,7 +17,7 @@ #include namespace CesiumGltf { -struct ImageCesium; +struct ImageAsset; struct Texture; } // namespace CesiumGltf @@ -118,15 +118,15 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * and can be empty. */ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, const CesiumGltf::Sampler& sampler, bool sRGB); /** * @brief Does the asynchronous part of renderer resource preparation for * a texture.The given image _must_ be prepared before calling this method by - * calling {@link ExtensionImageCesiumUnreal::getOrCreate} and then waiting - * for {@link ExtensionImageCesiumUnreal::getFuture} to resolve. This method + * calling {@link ExtensionImageAssetUnreal::getOrCreate} and then waiting + * for {@link ExtensionImageAssetUnreal::getFuture} to resolve. This method * should be called in a background thread. * * @param imageCesium The image. @@ -142,7 +142,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * @return The loaded texture. */ TUniquePtr loadTextureAnyThreadPart( - CesiumGltf::ImageCesium& image, + CesiumGltf::ImageAsset& image, TextureAddress addressX, TextureAddress addressY, TextureFilter filter, @@ -198,8 +198,8 @@ TextureAddress convertGltfWrapSToUnreal(int32_t wrapS); */ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT); -std::optional getPixelFormatForImageCesium( - const CesiumGltf::ImageCesium& imageCesium, +std::optional getPixelFormatForImageAsset( + const CesiumGltf::ImageAsset& imageCesium, const std::optional overridePixelFormat); std::optional diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp similarity index 68% rename from Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp rename to Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp index f1081af7e..9f2120a9b 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp @@ -1,7 +1,7 @@ -#include "ExtensionImageCesiumUnreal.h" +#include "ExtensionImageAssetUnreal.h" #include "CesiumRuntime.h" #include "CesiumTextureUtility.h" -#include +#include #include using namespace CesiumAsync; @@ -12,17 +12,15 @@ namespace { std::mutex createExtensionMutex; -std::pair>> -getOrCreateImageFuture( - const AsyncSystem& asyncSystem, - ImageCesium& imageCesium); +std::pair>> +getOrCreateImageFuture(const AsyncSystem& asyncSystem, ImageAsset& imageCesium); } // namespace -/*static*/ const ExtensionImageCesiumUnreal& -ExtensionImageCesiumUnreal::getOrCreate( +/*static*/ const ExtensionImageAssetUnreal& +ExtensionImageAssetUnreal::getOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::ImageCesium& imageCesium, + CesiumGltf::ImageAsset& imageCesium, bool sRGB, bool needsMipMaps, const std::optional& overridePixelFormat) { @@ -55,43 +53,43 @@ ExtensionImageCesiumUnreal::getOrCreate( return extension; } -ExtensionImageCesiumUnreal::ExtensionImageCesiumUnreal( +ExtensionImageAssetUnreal::ExtensionImageAssetUnreal( const CesiumAsync::SharedFuture& future) : _pTextureResource(nullptr), _futureCreateResource(future) {} const TSharedPtr& -ExtensionImageCesiumUnreal::getTextureResource() const { +ExtensionImageAssetUnreal::getTextureResource() const { return this->_pTextureResource; } -CesiumAsync::SharedFuture& ExtensionImageCesiumUnreal::getFuture() { +CesiumAsync::SharedFuture& ExtensionImageAssetUnreal::getFuture() { return this->_futureCreateResource; } const CesiumAsync::SharedFuture& -ExtensionImageCesiumUnreal::getFuture() const { +ExtensionImageAssetUnreal::getFuture() const { return this->_futureCreateResource; } namespace { -// Returns the ExtensionImageCesiumUnreal, which is created if it does not +// Returns the ExtensionImageAssetUnreal, which is created if it does not // already exist. It _may_ also return a Promise, in which case the calling // thread is responsible for doing the loading and should resolve the Promise // when it's done. -std::pair>> +std::pair>> getOrCreateImageFuture( const AsyncSystem& asyncSystem, - ImageCesium& imageCesium) { + ImageAsset& imageCesium) { std::scoped_lock lock(createExtensionMutex); - ExtensionImageCesiumUnreal* pExtension = - imageCesium.getExtension(); + ExtensionImageAssetUnreal* pExtension = + imageCesium.getExtension(); if (!pExtension) { // This thread will work on this image. Promise promise = asyncSystem.createPromise(); - ExtensionImageCesiumUnreal& extension = - imageCesium.addExtension( + ExtensionImageAssetUnreal& extension = + imageCesium.addExtension( promise.getFuture().share()); return {extension, std::move(promise)}; } else { diff --git a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.h similarity index 82% rename from Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h rename to Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.h index 133361c94..824bc4d27 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageCesiumUnreal.h +++ b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.h @@ -9,14 +9,14 @@ #include namespace CesiumGltf { -struct ImageCesium; +struct ImageAsset; } /** - * @brief An extension attached to an ImageCesium in order to hold + * @brief An extension attached to an ImageAsset in order to hold * Unreal-specific information about it. * - * ImageCesium instances are shared between multiple textures on a single model, + * ImageAsset instances are shared between multiple textures on a single model, * and even between models in some cases, but we strive to have only one copy of * the image bytes in GPU memory. * @@ -31,29 +31,29 @@ struct ImageCesium; * Because we'll never be sampling from this texture resource, the texture * filtering and addressing parameters have default values. */ -struct ExtensionImageCesiumUnreal { - static inline constexpr const char* TypeName = "ExtensionImageCesiumUnreal"; +struct ExtensionImageAssetUnreal { + static inline constexpr const char* TypeName = "ExtensionImageAssetUnreal"; static inline constexpr const char* ExtensionName = - "PRIVATE_ImageCesium_Unreal"; + "PRIVATE_ImageAsset_Unreal"; /** - * @brief Gets an Unreal texture resource from the given `ImageCesium`, + * @brief Gets an Unreal texture resource from the given `ImageAsset`, * creating it if necessary. * * When this function is called for the first time on a particular - * `ImageCesium`, the asynchronous process to create an Unreal + * `ImageAsset`, the asynchronous process to create an Unreal * `FTextureResource` from it is kicked off. On successive invocations * (perhaps from other threads), the existing instance is returned. It is safe - * to call this method on the same `ImageCesium` instance from multiple + * to call this method on the same `ImageAsset` instance from multiple * threads simultaneously as long as no other thread is modifying the instance * at the same time. * * To determine if the asynchronous `FTextureResource` creation process has * completed, use {@link getFuture}. */ - static const ExtensionImageCesiumUnreal& getOrCreate( + static const ExtensionImageAssetUnreal& getOrCreate( const CesiumAsync::AsyncSystem& asyncSystem, - CesiumGltf::ImageCesium& imageCesium, + CesiumGltf::ImageAsset& imageCesium, bool sRGB, bool needsMipMaps, const std::optional& overridePixelFormat); @@ -64,7 +64,7 @@ struct ExtensionImageCesiumUnreal { * @param future The future that will resolve when loading of the * {@link getTextureResource} is complete. */ - ExtensionImageCesiumUnreal(const CesiumAsync::SharedFuture& future); + ExtensionImageAssetUnreal(const CesiumAsync::SharedFuture& future); /** * Gets the created texture resource. This resource should not be accessed or diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp index d9361976c..d20bc855c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp @@ -81,7 +81,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -170,7 +170,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.normalized = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -235,7 +235,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.count = 2; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -312,7 +312,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -434,7 +434,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -472,7 +472,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT16; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -526,7 +526,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -590,7 +590,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -634,7 +634,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::FLOAT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -688,7 +688,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -758,7 +758,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::FLOAT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -804,7 +804,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -848,7 +848,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = scale; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -912,7 +912,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.normalized = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -955,7 +955,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::FLOAT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1008,7 +1008,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = scale; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 1; @@ -1066,7 +1066,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -1110,7 +1110,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::FLOAT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1168,7 +1168,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = {defaultValue[0], defaultValue[1]}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -1239,7 +1239,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.normalized = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -1290,7 +1290,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::UINT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -1342,7 +1342,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = {scale[0], scale[1]}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -1409,7 +1409,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 3; @@ -1453,7 +1453,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::FLOAT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1514,7 +1514,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { defaultValue[2]}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 3; @@ -1585,7 +1585,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.normalized = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 3; @@ -1637,7 +1637,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 3; @@ -1689,7 +1689,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = {scale[0], scale[1], scale[2]}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 3; @@ -1757,7 +1757,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.normalized = true; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1814,7 +1814,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT8; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1872,7 +1872,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = {scale[0], scale[1], scale[2], scale[3]}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1923,7 +1923,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -1999,7 +1999,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.count = 2; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -2067,7 +2067,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.noData = {0, 0}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -2155,7 +2155,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = {10, 20}; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 2; @@ -2256,7 +2256,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.componentType = ClassProperty::ComponentType::INT32; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -2316,7 +2316,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.scale = scale; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -2373,7 +2373,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.noData = noData; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; @@ -2441,7 +2441,7 @@ void FCesiumPropertyTexturePropertySpec::Define() { classProperty.defaultProperty = defaultValue; Sampler sampler; - ImageCesium image; + ImageAsset image; image.width = 2; image.height = 2; image.channels = 4; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 975275d66..c5dbd9862 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -2,7 +2,7 @@ #include "CesiumTextureUtility.h" #include "CesiumAsync/AsyncSystem.h" -#include "ExtensionImageCesiumUnreal.h" +#include "ExtensionImageAssetUnreal.h" #include "Misc/AutomationTest.h" #include "RenderingThread.h" #include @@ -21,7 +21,7 @@ BEGIN_DEFINE_SPEC( std::vector originalPixels; std::vector originalMipPixels; std::vector expectedMipPixelsIfGenerated; -CesiumUtility::IntrusivePointer pImageCesium; +CesiumUtility::IntrusivePointer pImageAsset; void RunTests(); @@ -51,23 +51,23 @@ void CesiumTextureUtilitySpec::Define() { 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; originalMipPixels.clear(); - pImageCesium.emplace(); - pImageCesium->width = 3; - pImageCesium->height = 2; + pImageAsset.emplace(); + pImageAsset->width = 3; + pImageAsset->height = 2; TestEqual( "image buffer size is correct", originalPixels.size(), - pImageCesium->width * pImageCesium->height * - pImageCesium->bytesPerChannel * pImageCesium->channels); - pImageCesium->pixelData.resize(originalPixels.size()); + pImageAsset->width * pImageAsset->height * + pImageAsset->bytesPerChannel * pImageAsset->channels); + pImageAsset->pixelData.resize(originalPixels.size()); std::memcpy( - pImageCesium->pixelData.data(), + pImageAsset->pixelData.data(), originalPixels.data(), originalPixels.size()); - CesiumUtility::IntrusivePointer pCopy = - new ImageCesium(*pImageCesium); + CesiumUtility::IntrusivePointer pCopy = + new ImageAsset(*pImageAsset); CesiumGltfReader::GltfReader::generateMipMaps(*pCopy); expectedMipPixelsIfGenerated.clear(); @@ -87,31 +87,31 @@ void CesiumTextureUtilitySpec::Define() { Describe("With Mips", [this]() { BeforeEach([this]() { - pImageCesium.emplace(); - pImageCesium->width = 3; - pImageCesium->height = 2; + pImageAsset.emplace(); + pImageAsset->width = 3; + pImageAsset->height = 2; // Original image (3x2) originalPixels = {0x20, 0x40, 0x80, 0xF0, 0x21, 0x41, 0x81, 0xF1, 0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3, 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; - pImageCesium->mipPositions.emplace_back( - ImageCesiumMipPosition{0, originalPixels.size()}); + pImageAsset->mipPositions.emplace_back( + ImageAssetMipPosition{0, originalPixels.size()}); // Mip 1 (1x1) originalMipPixels = {0x26, 0x46, 0x86, 0xF6}; - pImageCesium->mipPositions.emplace_back(ImageCesiumMipPosition{ - pImageCesium->mipPositions[0].byteSize, + pImageAsset->mipPositions.emplace_back(ImageAssetMipPosition{ + pImageAsset->mipPositions[0].byteSize, originalMipPixels.size()}); - pImageCesium->pixelData.resize( + pImageAsset->pixelData.resize( originalPixels.size() + originalMipPixels.size()); std::memcpy( - pImageCesium->pixelData.data(), + pImageAsset->pixelData.data(), originalPixels.data(), originalPixels.size()); std::memcpy( - pImageCesium->pixelData.data() + originalPixels.size(), + pImageAsset->pixelData.data() + originalPixels.size(), originalMipPixels.data(), originalMipPixels.size()); }); @@ -121,9 +121,9 @@ void CesiumTextureUtilitySpec::Define() { } void CesiumTextureUtilitySpec::RunTests() { - It("ImageCesium non-sRGB", [this]() { + It("ImageAsset non-sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - *pImageCesium, + *pImageAsset, TextureAddress::TA_Mirror, TextureAddress::TA_Wrap, TextureFilter::TF_Bilinear, @@ -145,9 +145,9 @@ void CesiumTextureUtilitySpec::RunTests() { CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_Cinematic); }); - It("ImageCesium sRGB", [this]() { + It("ImageAsset sRGB", [this]() { TUniquePtr pHalfLoaded = loadTextureAnyThreadPart( - *pImageCesium, + *pImageAsset, TextureAddress::TA_Clamp, TextureAddress::TA_Mirror, TextureFilter::TF_Trilinear, @@ -178,7 +178,7 @@ void CesiumTextureUtilitySpec::RunTests() { TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( - *pImageCesium, + *pImageAsset, sampler, false); TestNotNull("pHalfLoaded", pHalfLoaded.Get()); @@ -199,7 +199,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageCesium; + image.pCesium = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -232,7 +232,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageCesium; + image.pCesium = pImageAsset; Sampler& sampler1 = model.samplers.emplace_back(); sampler1.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -299,7 +299,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageCesium; + image.pCesium = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -348,7 +348,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageCesium; + image.pCesium = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; diff --git a/Source/CesiumRuntime/Public/CesiumPropertyTextureProperty.h b/Source/CesiumRuntime/Public/CesiumPropertyTextureProperty.h index c9c855b5a..7d4b9a056 100644 --- a/Source/CesiumRuntime/Public/CesiumPropertyTextureProperty.h +++ b/Source/CesiumRuntime/Public/CesiumPropertyTextureProperty.h @@ -98,7 +98,7 @@ struct CESIUMRUNTIME_API FCesiumPropertyTextureProperty { const int64 getTexCoordSetIndex() const; const CesiumGltf::Sampler* getSampler() const; - const CesiumGltf::ImageCesium* getImage() const; + const CesiumGltf::ImageAsset* getImage() const; const std::optional getTextureTransform() const; diff --git a/extern/cesium-native b/extern/cesium-native index 526e66068..883e36f10 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 526e660687d64d98b3f1aea3b8338dafbac0b1db +Subproject commit 883e36f100c6a1487bf1103bafb937a66578705b From 9a675e2ba2de2d0b643003727b7a81999ad99e6d Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 10 Oct 2024 15:54:49 -0400 Subject: [PATCH 48/68] Format, update cesium-native --- Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp | 3 +-- extern/cesium-native | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp index cc96ff84b..ce122edc1 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp @@ -333,8 +333,7 @@ const CesiumGltf::Sampler* FCesiumPropertyTextureProperty::getSampler() const { }); } -const CesiumGltf::ImageAsset* -FCesiumPropertyTextureProperty::getImage() const { +const CesiumGltf::ImageAsset* FCesiumPropertyTextureProperty::getImage() const { return propertyTexturePropertyCallback( this->_property, this->_valueType, diff --git a/extern/cesium-native b/extern/cesium-native index 883e36f10..3e5eb717b 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 883e36f100c6a1487bf1103bafb937a66578705b +Subproject commit 3e5eb717b8e28959c9be80c4db82dc1e21ca5aba From 85d6b39039d317c6b6c1a3f63cb591377784f90e Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 10 Oct 2024 15:58:36 -0400 Subject: [PATCH 49/68] Clean up CesiumTextureUtility --- Source/CesiumRuntime/Private/CesiumTextureUtility.cpp | 1 - Source/CesiumRuntime/Private/CesiumTextureUtility.h | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 603b6bde9..3e2ccb908 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -362,7 +362,6 @@ bool getUseMipmapsIfAvailableFromSampler(const CesiumGltf::Sampler& sampler) { case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_LINEAR: case CesiumGltf::Sampler::MinFilter::NEAREST_MIPMAP_NEAREST: return true; - break; default: // LINEAR and NEAREST return false; } diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.h b/Source/CesiumRuntime/Private/CesiumTextureUtility.h index 3c1b9d8e5..a6989684b 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.h +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.h @@ -90,8 +90,6 @@ struct LoadedTextureResult { * associated Unreal texture. * @param sRGB True if the texture should be treated as sRGB; false if it should * be treated as linear. - * @param textureResources Unreal RHI texture resources that have already been - * created for this model. This array must have the same size as `model`'s * {@link CesiumGltf::Model::images}, and all pointers must be initialized to * nullptr before the first call to `loadTextureFromModelAnyThreadPart` during * the glTF load process. @@ -112,10 +110,6 @@ TUniquePtr loadTextureFromModelAnyThreadPart( * @param sampler The sampler settings to use with the texture. * @param sRGB True if the texture should be treated as sRGB; false if it should * be treated as linear. - * @param pExistingImageResource An existing RHI texture resource that has been - * created for this image, or nullptr if one hasn't been created yet. When this - * parameter is not nullptr, the provided image's `pixelData` is not required - * and can be empty. */ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( CesiumGltf::ImageAsset& image, @@ -129,7 +123,7 @@ TUniquePtr loadTextureFromImageAndSamplerAnyThreadPart( * for {@link ExtensionImageAssetUnreal::getFuture} to resolve. This method * should be called in a background thread. * - * @param imageCesium The image. + * @param image The image. * @param addressX The X addressing mode. * @param addressY The Y addressing mode. * @param filter The sampler filtering to use for this texture. From d8f27c6bdad85ea4d44efc1d8156ec7924668f2a Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 28 Oct 2024 18:58:00 +1100 Subject: [PATCH 50/68] Use shared-assets-wip branch of cesium-native. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 2 +- Source/CesiumRuntime/Private/CesiumTextureResource.cpp | 10 +++++----- extern/cesium-native | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 1be3fbf0d..f6ffe21a3 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -2045,7 +2045,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumAsync::SharedAssetDepot& imageDepot = + const CesiumGltfReader::NetworkImageAssetDepot& imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index e86e79986..2165d6edf 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -355,8 +355,8 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { FTexture2DRHIRef textureReference = CreateRHITexture2D_Async(imageCesium, *maybePixelFormat, sRGB); - textureReference->SetName( - FName(UTF8_TO_TCHAR(imageCesium.getUniqueAssetId().c_str()))); + // textureReference->SetName( + // FName(UTF8_TO_TCHAR(imageCesium.getUniqueAssetId().c_str()))); auto pResult = TUniquePtr< FCesiumUseExistingTextureResource, FCesiumTextureResourceDeleter>(new FCesiumUseExistingTextureResource( @@ -670,9 +670,9 @@ FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { // Use the asset ID as the name of the texture so it will be visible in the // Render Resource Viewer. FString debugName = TEXT("CesiumTextureUtility"); - if (!this->_image.getUniqueAssetId().empty()) { - debugName = UTF8_TO_TCHAR(this->_image.getUniqueAssetId().c_str()); - } + // if (!this->_image.getUniqueAssetId().empty()) { + // debugName = UTF8_TO_TCHAR(this->_image.getUniqueAssetId().c_str()); + // } FRHIResourceCreateInfo createInfo{*debugName}; createInfo.BulkData = nullptr; diff --git a/extern/cesium-native b/extern/cesium-native index 3e5eb717b..c75831ff2 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 3e5eb717b8e28959c9be80c4db82dc1e21ca5aba +Subproject commit c75831ff2471a4cf49d452e1a8bf0135d894860e From 4a96eeac57fe78d8b1953a40b0c9b6284296c90e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 28 Oct 2024 21:07:30 +1100 Subject: [PATCH 51/68] Update cesium-native. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 2 +- extern/cesium-native | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index f6ffe21a3..57048273f 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -2045,7 +2045,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumGltfReader::NetworkImageAssetDepot& imageDepot = + const CesiumGltfReader::GltfSharedAssetSystem::ImageDepot& imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, diff --git a/extern/cesium-native b/extern/cesium-native index c75831ff2..c5533b7ea 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit c75831ff2471a4cf49d452e1a8bf0135d894860e +Subproject commit c5533b7eada631c70ef045694b6253d787341fff From 2d1ccd840ddb72c0bfcaa290b3ccfee8aabf3d0a Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 29 Oct 2024 16:16:51 +1100 Subject: [PATCH 52/68] Update cesium-native. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 14 +++++++------- .../Private/Tests/Cesium3DTileset.spec.cpp | 5 +++-- extern/cesium-native | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 57048273f..68c5d44c2 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -8,6 +8,7 @@ #include "Cesium3DTilesSelection/Tile.h" #include "Cesium3DTilesSelection/TilesetLoadFailureDetails.h" #include "Cesium3DTilesSelection/TilesetOptions.h" +#include "Cesium3DTilesSelection/TilesetSharedAssetSystem.h" #include "Cesium3DTilesetLoadFailureDetails.h" #include "Cesium3DTilesetRoot.h" #include "CesiumActors.h" @@ -2045,17 +2046,16 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } if (this->LogAssetStats && this->_pTileset) { - const CesiumGltfReader::GltfSharedAssetSystem::ImageDepot& imageDepot = - *this->_pTileset->getSharedAssetSystem().pImage; + const Cesium3DTilesSelection::TilesetSharedAssetSystem::ImageDepot& + imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, Display, TEXT( - "Images depot: %d distinct assets, %d total usages, %d assets pending deletion, %d total size in bytes"), - imageDepot.getDistinctCount(), - imageDepot.getUsageCount(), - imageDepot.getDeletionCandidateCount(), - imageDepot.getDeletionCandidateTotalSizeBytes()); + "Images shared asset depot: %d distinct assets, %d inactive assets pending deletion (%d bytes)"), + imageDepot.getAssetCount(), + imageDepot.getInactiveAssetCount(), + imageDepot.getInactiveAssetTotalSizeBytes()); } } } diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index da746bd60..f91a623c9 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -15,6 +15,7 @@ #include "Misc/AutomationTest.h" #include "Tests/AutomationCommon.h" #include "Tests/AutomationTestSettings.h" +#include #include #define TEST_SCREEN_WIDTH 1280 @@ -72,9 +73,9 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - CesiumGltfReader::GltfSharedAssetSystem& assetSystem = + Cesium3DTilesSelection::TilesetSharedAssetSystem& assetSystem = context.tilesets[0]->GetTileset()->getSharedAssetSystem(); - assert(assetDepot.getImagesCount() == 2); + check(assetSystem.pImage->getAssetCount() == 2); } bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { diff --git a/extern/cesium-native b/extern/cesium-native index c5533b7ea..ce212af37 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit c5533b7eada631c70ef045694b6253d787341fff +Subproject commit ce212af3782d778a8bb5538fcd080202a4b99b43 From 463e0271d786b96f846411ea36f5b2622e75224e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 29 Oct 2024 21:05:49 +1100 Subject: [PATCH 53/68] Update cesium-native. --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index ce212af37..1eee18f71 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit ce212af3782d778a8bb5538fcd080202a4b99b43 +Subproject commit 1eee18f71ceca783ce00f234a6a212a960ae2f61 From 6b7f4af41906233badb441c7706748767b2e9861 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 29 Oct 2024 15:35:38 -0400 Subject: [PATCH 54/68] Return to shared-assets branch of cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index 1eee18f71..f9459d130 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 1eee18f71ceca783ce00f234a6a212a960ae2f61 +Subproject commit f9459d1308da630e5106a762f02f4263c9daf552 From adec72ea53e80b1ef3201e6307acb548ba99bda1 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 10:51:46 +1100 Subject: [PATCH 55/68] pCesium -> pAsset. --- .../CesiumRuntime/Private/CesiumGltfTextures.cpp | 2 +- .../Private/CesiumTextureUtility.cpp | 5 +---- .../Tests/CesiumFeatureIdTexture.spec.cpp | 16 ++++++++-------- .../Private/Tests/CesiumGltfSpecUtility.cpp | 12 ++++++------ .../Private/Tests/CesiumGltfSpecUtility.h | 16 ++++++++-------- .../Private/Tests/CesiumTextureUtility.spec.cpp | 8 ++++---- extern/cesium-native | 2 +- 7 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index bc52d55bf..2d94da1a4 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -237,7 +237,7 @@ SharedFuture createTextureInLoadThread( const ExtensionImageAssetUnreal& extension = ExtensionImageAssetUnreal::getOrCreate( asyncSystem, - *pImage->pCesium, + *pImage->pAsset, sRGB, needsMips, std::nullopt); diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index 3e2ccb908..abb0b99d5 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -304,10 +304,7 @@ TUniquePtr loadTextureFromModelAnyThreadPart( model.getSafe(model.samplers, texture.sampler); TUniquePtr result = - loadTextureFromImageAndSamplerAnyThreadPart( - *image.pCesium, - sampler, - sRGB); + loadTextureFromImageAndSamplerAnyThreadPart(*image.pAsset, sampler, sRGB); if (result) { extension.pTexture = result->pTexture; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 36da73f40..21712ae9b 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -136,10 +136,10 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { Image& image = model.images.emplace_back(); - image.pCesium.emplace(); - image.pCesium->width = image.pCesium->height = 1; - image.pCesium->channels = 1; - image.pCesium->pixelData.push_back(std::byte(42)); + image.pAsset.emplace(); + image.pAsset->width = image.pAsset->height = 1; + image.pAsset->channels = 1; + image.pAsset->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; @@ -176,10 +176,10 @@ void FCesiumFeatureIdTextureSpec::Define() { It("constructs valid instance for texture with invalid texcoord accessor", [this]() { Image& image = model.images.emplace_back(); - image.pCesium.emplace(); - image.pCesium->width = image.pCesium->height = 1; - image.pCesium->channels = 1; - image.pCesium->pixelData.push_back(std::byte(42)); + image.pAsset.emplace(); + image.pAsset->width = image.pAsset->height = 1; + image.pAsset->channels = 1; + image.pAsset->pixelData.push_back(std::byte(42)); Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp index db6ded13f..93107ac13 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp @@ -70,13 +70,13 @@ CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel( const int32_t samplerWrapS, const int32_t samplerWrapT) { CesiumGltf::Image& image = model.images.emplace_back(); - image.pCesium.emplace(); - image.pCesium->bytesPerChannel = 1; - image.pCesium->channels = 1; - image.pCesium->width = imageWidth; - image.pCesium->height = imageHeight; + image.pAsset.emplace(); + image.pAsset->bytesPerChannel = 1; + image.pAsset->channels = 1; + image.pAsset->width = imageWidth; + image.pAsset->height = imageHeight; - std::vector& data = image.pCesium->pixelData; + std::vector& data = image.pAsset->pixelData; data.resize(imageWidth * imageHeight); std::memcpy(data.data(), featureIDs.data(), data.size()); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h index 270b01992..a166f8d8c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.h @@ -200,16 +200,16 @@ CesiumGltf::PropertyTextureProperty& AddPropertyTexturePropertyToModel( classProperty.componentType = componentType; CesiumGltf::Image& image = model.images.emplace_back(); - image.pCesium.emplace(); - image.pCesium->width = 2; - image.pCesium->height = 2; - image.pCesium->channels = sizeof(T); - image.pCesium->bytesPerChannel = 1; - image.pCesium->pixelData.resize(values.size() * sizeof(T)); + image.pAsset.emplace(); + image.pAsset->width = 2; + image.pAsset->height = 2; + image.pAsset->channels = sizeof(T); + image.pAsset->bytesPerChannel = 1; + image.pAsset->pixelData.resize(values.size() * sizeof(T)); std::memcpy( - image.pCesium->pixelData.data(), + image.pAsset->pixelData.data(), values.data(), - image.pCesium->pixelData.size()); + image.pAsset->pixelData.size()); CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index c5dbd9862..84eb8c808 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -199,7 +199,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageAsset; + image.pAsset = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -232,7 +232,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageAsset; + image.pAsset = pImageAsset; Sampler& sampler1 = model.samplers.emplace_back(); sampler1.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -299,7 +299,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageAsset; + image.pAsset = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; @@ -348,7 +348,7 @@ void CesiumTextureUtilitySpec::RunTests() { Model model; Image& image = model.images.emplace_back(); - image.pCesium = pImageAsset; + image.pAsset = pImageAsset; Sampler& sampler = model.samplers.emplace_back(); sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; diff --git a/extern/cesium-native b/extern/cesium-native index f9459d130..33d494863 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit f9459d1308da630e5106a762f02f4263c9daf552 +Subproject commit 33d494863457adf5187b07639fff44ef44020640 From f16dffbde61526d439ae7480d073fd5dd607fdfa Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 14:29:56 +1100 Subject: [PATCH 56/68] Fix test failure. --- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 3 --- extern/cesium-native | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index f91a623c9..75bf8402d 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -73,9 +73,6 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, TestPass::TestingParameter parameter) { - Cesium3DTilesSelection::TilesetSharedAssetSystem& assetSystem = - context.tilesets[0]->GetTileset()->getSharedAssetSystem(); - check(assetSystem.pImage->getAssetCount() == 2); } bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { diff --git a/extern/cesium-native b/extern/cesium-native index 33d494863..750d5f91d 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 33d494863457adf5187b07639fff44ef44020640 +Subproject commit 750d5f91d18f7d8e203d89e6906edc67c1a7101c From c45f82f39cfffde5754dc6885d88bd7c9ea1259d Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 14:37:14 +1100 Subject: [PATCH 57/68] Formatting. --- Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 75bf8402d..8ae6f994e 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -72,8 +72,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { void tilesetPass( SceneGenerationContext& context, - TestPass::TestingParameter parameter) { -} + TestPass::TestingParameter parameter) {} bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { std::vector testPasses; From 04bac3621cf3463e2b530ed398c59b6c210e371f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 16:10:34 +1100 Subject: [PATCH 58/68] Don't use deprecated methods. --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 2 +- Source/CesiumRuntime/Private/CesiumTextureResource.cpp | 2 +- .../CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 68c5d44c2..748fb9e36 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -881,7 +881,7 @@ class UnrealResourcePreparer if (pOptions->useMipmaps) { std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(image); + CesiumGltfReader::ImageDecoder::generateMipMaps(image); if (errorMessage) { UE_LOG( LogCesium, diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index 2165d6edf..759b23bd8 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -317,7 +317,7 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { if (needsMipMaps) { std::optional errorMessage = - CesiumGltfReader::GltfReader::generateMipMaps(imageCesium); + CesiumGltfReader::ImageDecoder::generateMipMaps(imageCesium); if (errorMessage) { UE_LOG( LogCesium, diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 84eb8c808..9a16b5af3 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -68,7 +68,7 @@ void CesiumTextureUtilitySpec::Define() { CesiumUtility::IntrusivePointer pCopy = new ImageAsset(*pImageAsset); - CesiumGltfReader::GltfReader::generateMipMaps(*pCopy); + CesiumGltfReader::ImageDecoder::generateMipMaps(*pCopy); expectedMipPixelsIfGenerated.clear(); From 71d029718122fd4cd61e32461cefcc3239368215 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 16:16:40 +1100 Subject: [PATCH 59/68] Fix compiler error on macOS, hopefully. --- Source/CesiumRuntime/Public/CesiumMetadataValueType.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/CesiumRuntime/Public/CesiumMetadataValueType.h b/Source/CesiumRuntime/Public/CesiumMetadataValueType.h index e01f6a6ee..ce6c3dd3b 100644 --- a/Source/CesiumRuntime/Public/CesiumMetadataValueType.h +++ b/Source/CesiumRuntime/Public/CesiumMetadataValueType.h @@ -5,7 +5,6 @@ #include "CesiumGltf/PropertyArrayView.h" #include "CesiumGltf/PropertyType.h" #include "CesiumGltf/PropertyTypeTraits.h" -#include "UObject/ObjectMacros.h" #include "CesiumMetadataValueType.generated.h" /** From d61b912b0c881346647ae3394592c5809db45ede Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 18:41:55 +1100 Subject: [PATCH 60/68] Remove unused code. --- .../Private/CesiumTextureUtility.cpp | 119 ------------------ 1 file changed, 119 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index abb0b99d5..cc94b1946 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -41,125 +41,6 @@ struct ExtensionUnrealTexture { pTexture = nullptr; }; -FTexture2DRHIRef createAsyncTextureAndWaitOld( - uint32 SizeX, - uint32 SizeY, - uint8 Format, - uint32 NumMips, - ETextureCreateFlags Flags, - void** InitialMipData, - uint32 NumInitialMips) { -#if ENGINE_VERSION_5_4_OR_HIGHER - FGraphEventRef CompletionEvent; - - FTexture2DRHIRef result = RHIAsyncCreateTexture2D( - SizeX, - SizeY, - Format, - NumMips, - Flags, - ERHIAccess::Unknown, - InitialMipData, - NumInitialMips, - TEXT("CesiumTexture"), - CompletionEvent); - - if (CompletionEvent) { - CompletionEvent->Wait(); - } - - return result; -#elif ENGINE_VERSION_5_3_OR_HIGHER - FGraphEventRef CompletionEvent; - - FTexture2DRHIRef result = RHIAsyncCreateTexture2D( - SizeX, - SizeY, - Format, - NumMips, - Flags, - InitialMipData, - NumInitialMips, - CompletionEvent); - - if (CompletionEvent) { - CompletionEvent->Wait(); - } - - return result; -#else - return RHIAsyncCreateTexture2D( - SizeX, - SizeY, - Format, - NumMips, - Flags, - InitialMipData, - NumInitialMips); -#endif -} - -/** - * @brief Create an RHI texture on this thread. This requires - * GRHISupportsAsyncTextureCreation to be true. - * - * @param image The CPU image to create on the GPU. - * @param format The pixel format of the image. - * @param Whether to use a sRGB color-space. - * @return The RHI texture reference. - */ -FTexture2DRHIRef CreateRHITexture2D_AsyncOld( - const CesiumGltf::ImageAsset& image, - EPixelFormat format, - bool sRGB) { - check(GRHISupportsAsyncTextureCreation); - - ETextureCreateFlags textureFlags = TexCreate_ShaderResource; - - // Just like in FCesiumCreateNewTextureResource, we're assuming here that we - // can create an FRHITexture as sRGB, and later create another - // UTexture2D / FTextureResource pointing to the same FRHITexture that is not - // sRGB (or vice-versa), and that Unreal will effectively ignore the flag on - // FRHITexture. - if (sRGB) { - textureFlags |= TexCreate_SRGB; - } - - if (!image.mipPositions.empty()) { - // Here 16 is a generously large (but arbitrary) hard limit for number of - // mips. - uint32 mipCount = static_cast(image.mipPositions.size()); - if (mipCount > 16) { - mipCount = 16; - } - - void* mipsData[16]; - for (size_t i = 0; i < mipCount; ++i) { - const CesiumGltf::ImageAssetMipPosition& mipPos = image.mipPositions[i]; - mipsData[i] = (void*)(&image.pixelData[mipPos.byteOffset]); - } - - return createAsyncTextureAndWaitOld( - static_cast(image.width), - static_cast(image.height), - format, - mipCount, - textureFlags, - mipsData, - mipCount); - } else { - void* pTextureData = (void*)(image.pixelData.data()); - return createAsyncTextureAndWaitOld( - static_cast(image.width), - static_cast(image.height), - format, - 1, - textureFlags, - &pTextureData, - 1); - } -} - } // namespace namespace CesiumTextureUtility { From 2ec8b600c8a6f65c8f1d8a7d024631bf162b2d55 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 30 Oct 2024 18:42:10 +1100 Subject: [PATCH 61/68] LogAssetStats -> LogSharedAssetStats --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 4 ++-- Source/CesiumRuntime/Public/Cesium3DTileset.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 748fb9e36..26b8b2d14 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1992,7 +1992,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( } } - if (!this->LogSelectionStats && !this->LogAssetStats) { + if (!this->LogSelectionStats && !this->LogSharedAssetStats) { return; } @@ -2045,7 +2045,7 @@ void ACesium3DTileset::updateLastViewUpdateResultState( this->LoadProgress); } - if (this->LogAssetStats && this->_pTileset) { + if (this->LogSharedAssetStats && this->_pTileset) { const Cesium3DTilesSelection::TilesetSharedAssetSystem::ImageDepot& imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 8a94301a9..8fa0b4c6c 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -627,11 +627,11 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { bool LogSelectionStats = false; /** - * If true, logs stats on the assets in this tileset's asset depot to the - * Output Log. + * If true, logs stats on the assets in this tileset's shared asset system to + * the Output Log. */ UPROPERTY(EditAnywhere, Category = "Cesium|Debug") - bool LogAssetStats = false; + bool LogSharedAssetStats = false; /** * If true, draws debug text above each tile being rendered with information From da6f64cd2f29db29474e28330edf2898ad12d8e7 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 30 Oct 2024 16:50:20 -0400 Subject: [PATCH 62/68] Remove CesiumGltf usings to try to fix OSX build --- .../Private/CesiumFeatureIdAttribute.cpp | 20 ++++++------- .../Private/CesiumFeatureIdSet.cpp | 15 +++++----- .../Private/CesiumFeatureIdTexture.cpp | 16 +++++----- .../Private/CesiumMetadataValue.cpp | 4 +-- .../Private/CesiumModelMetadata.cpp | 6 ++-- .../Private/CesiumPrimitiveFeatures.cpp | 10 +++---- .../Private/CesiumPropertyArray.cpp | 4 +-- .../Private/CesiumPropertyTable.cpp | 10 +++---- .../Private/CesiumPropertyTableProperty.cpp | 2 -- .../Private/CesiumPropertyTexture.cpp | 8 ++--- .../Private/CesiumPropertyTextureProperty.cpp | 2 -- .../Private/CesiumTextureUtility.cpp | 29 +++++++++---------- .../Private/ExtensionImageAssetUnreal.cpp | 7 +++-- 13 files changed, 57 insertions(+), 76 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp b/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp index 4b0b07d1c..919f6f47a 100644 --- a/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp @@ -4,11 +4,9 @@ #include #include -using namespace CesiumGltf; - FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( - const Model& Model, - const MeshPrimitive& Primitive, + const CesiumGltf::Model& Model, + const CesiumGltf::MeshPrimitive& Primitive, const int64 FeatureIDAttribute, const FString& PropertyTableName) : _status(ECesiumFeatureIdAttributeStatus::ErrorInvalidAttribute), @@ -23,9 +21,9 @@ FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( return; } - const Accessor* accessor = - Model.getSafe(&Model.accessors, featureID->second); - if (!accessor || accessor->type != Accessor::Type::SCALAR) { + const CesiumGltf::Accessor* accessor = + Model.getSafe(&Model.accessors, featureID->second); + if (!accessor || accessor->type != CesiumGltf::Accessor::Type::SCALAR) { this->_status = ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor; return; } @@ -37,7 +35,7 @@ FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( this->_status = std::visit( [](auto view) { - if (view.status() != AccessorViewStatus::Valid) { + if (view.status() != CesiumGltf::AccessorViewStatus::Valid) { return ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor; } @@ -59,13 +57,15 @@ UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureIDAttributeStatus( int64 UCesiumFeatureIdAttributeBlueprintLibrary::GetVertexCount( UPARAM(ref) const FCesiumFeatureIdAttribute& FeatureIDAttribute) { - return std::visit(CountFromAccessor{}, FeatureIDAttribute._featureIdAccessor); + return std::visit( + CesiumGltf::CountFromAccessor{}, + FeatureIDAttribute._featureIdAccessor); } int64 UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureIDForVertex( UPARAM(ref) const FCesiumFeatureIdAttribute& FeatureIDAttribute, int64 VertexIndex) { return std::visit( - FeatureIdFromAccessor{VertexIndex}, + CesiumGltf::FeatureIdFromAccessor{VertexIndex}, FeatureIDAttribute._featureIdAccessor); } diff --git a/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp b/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp index 4adc304ab..6f846a6c6 100644 --- a/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp @@ -7,15 +7,13 @@ #include "CesiumGltf/Model.h" #include "CesiumGltfPrimitiveComponent.h" -using namespace CesiumGltf; - static FCesiumFeatureIdAttribute EmptyFeatureIDAttribute; static FCesiumFeatureIdTexture EmptyFeatureIDTexture; FCesiumFeatureIdSet::FCesiumFeatureIdSet( - const Model& InModel, - const MeshPrimitive& Primitive, - const FeatureId& FeatureID) + const CesiumGltf::Model& InModel, + const CesiumGltf::MeshPrimitive& Primitive, + const CesiumGltf::FeatureId& FeatureID) : _featureID(), _featureIDSetType(ECesiumFeatureIdSetType::None), _featureCount(FeatureID.featureCount), @@ -25,12 +23,13 @@ FCesiumFeatureIdSet::FCesiumFeatureIdSet( FString propertyTableName; // For backwards compatibility with GetFeatureTableName. - const ExtensionModelExtStructuralMetadata* pMetadata = - InModel.getExtension(); + const CesiumGltf::ExtensionModelExtStructuralMetadata* pMetadata = + InModel.getExtension(); if (pMetadata && _propertyTableIndex >= 0) { size_t index = static_cast(_propertyTableIndex); if (index < pMetadata->propertyTables.size()) { - const PropertyTable& propertyTable = pMetadata->propertyTables[index]; + const CesiumGltf::PropertyTable& propertyTable = + pMetadata->propertyTables[index]; std::string name = propertyTable.name.value_or(""); propertyTableName = FString(name.c_str()); } diff --git a/Source/CesiumRuntime/Private/CesiumFeatureIdTexture.cpp b/Source/CesiumRuntime/Private/CesiumFeatureIdTexture.cpp index 7b5125dcb..8ee2edb37 100644 --- a/Source/CesiumRuntime/Private/CesiumFeatureIdTexture.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeatureIdTexture.cpp @@ -8,19 +8,17 @@ #include -using namespace CesiumGltf; - FCesiumFeatureIdTexture::FCesiumFeatureIdTexture( - const Model& Model, - const MeshPrimitive& Primitive, - const FeatureIdTexture& FeatureIdTexture, + const CesiumGltf::Model& Model, + const CesiumGltf::MeshPrimitive& Primitive, + const CesiumGltf::FeatureIdTexture& FeatureIdTexture, const FString& PropertyTableName) : _status(ECesiumFeatureIdTextureStatus::ErrorInvalidTexture), _featureIdTextureView(), _texCoordAccessor(), _textureCoordinateSetIndex(FeatureIdTexture.texCoord), _propertyTableName(PropertyTableName) { - TextureViewOptions options; + CesiumGltf::TextureViewOptions options; options.applyKhrTextureTransformExtension = true; if (FeatureIdTexture.extras.find("makeImageCopy") != @@ -30,13 +28,13 @@ FCesiumFeatureIdTexture::FCesiumFeatureIdTexture( } this->_featureIdTextureView = - FeatureIdTextureView(Model, FeatureIdTexture, options); + CesiumGltf::FeatureIdTextureView(Model, FeatureIdTexture, options); switch (_featureIdTextureView.status()) { - case FeatureIdTextureViewStatus::Valid: + case CesiumGltf::FeatureIdTextureViewStatus::Valid: this->_status = ECesiumFeatureIdTextureStatus::Valid; break; - case FeatureIdTextureViewStatus::ErrorInvalidChannels: + case CesiumGltf::FeatureIdTextureViewStatus::ErrorInvalidChannels: this->_status = ECesiumFeatureIdTextureStatus::ErrorInvalidTextureAccess; return; default: diff --git a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp index 42d351535..f27e6d28d 100644 --- a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp +++ b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp @@ -6,8 +6,6 @@ #include #include -using namespace CesiumGltf; - FCesiumMetadataValue::FCesiumMetadataValue(FCesiumMetadataValue&& rhs) = default; @@ -18,7 +16,7 @@ FCesiumMetadataValue::FCesiumMetadataValue(const FCesiumMetadataValue& rhs) : _value(), _valueType(rhs._valueType), _storage(rhs._storage) { swl::visit( [this](const auto& value) { - if constexpr (IsMetadataArray::value) { + if constexpr (CesiumGltf::IsMetadataArray::value) { if (!this->_storage.empty()) { this->_value = decltype(value)(this->_storage); } else { diff --git a/Source/CesiumRuntime/Private/CesiumModelMetadata.cpp b/Source/CesiumRuntime/Private/CesiumModelMetadata.cpp index 63866a624..4deb6615b 100644 --- a/Source/CesiumRuntime/Private/CesiumModelMetadata.cpp +++ b/Source/CesiumRuntime/Private/CesiumModelMetadata.cpp @@ -6,16 +6,14 @@ #include "CesiumGltfComponent.h" #include "CesiumGltfPrimitiveComponent.h" -using namespace CesiumGltf; - static FCesiumModelMetadata EmptyModelMetadata; static FCesiumPropertyTable EmptyPropertyTable; static FCesiumPropertyTexture EmptyPropertyTexture; FCesiumModelMetadata::FCesiumModelMetadata( - const Model& InModel, - const ExtensionModelExtStructuralMetadata& Metadata) { + const CesiumGltf::Model& InModel, + const CesiumGltf::ExtensionModelExtStructuralMetadata& Metadata) { this->_propertyTables.Reserve(Metadata.propertyTables.size()); for (const auto& propertyTable : Metadata.propertyTables) { this->_propertyTables.Emplace(FCesiumPropertyTable(InModel, propertyTable)); diff --git a/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp b/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp index ee31b4e58..f0ec01c09 100644 --- a/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp +++ b/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp @@ -5,20 +5,18 @@ #include "CesiumGltf/Model.h" #include "CesiumGltfPrimitiveComponent.h" -using namespace CesiumGltf; - static FCesiumPrimitiveFeatures EmptyPrimitiveFeatures; FCesiumPrimitiveFeatures::FCesiumPrimitiveFeatures( - const Model& Model, - const MeshPrimitive& Primitive, - const ExtensionExtMeshFeatures& Features) + const CesiumGltf::Model& Model, + const CesiumGltf::MeshPrimitive& Primitive, + const CesiumGltf::ExtensionExtMeshFeatures& Features) : _vertexCount(0), _primitiveMode(Primitive.mode) { this->_indexAccessor = CesiumGltf::getIndexAccessorView(Model, Primitive); auto positionIt = Primitive.attributes.find("POSITION"); if (positionIt != Primitive.attributes.end()) { - const Accessor& positionAccessor = + const CesiumGltf::Accessor& positionAccessor = Model.getSafe(Model.accessors, positionIt->second); _vertexCount = positionAccessor.count; } diff --git a/Source/CesiumRuntime/Private/CesiumPropertyArray.cpp b/Source/CesiumRuntime/Private/CesiumPropertyArray.cpp index a40fc0c7e..9003116c3 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyArray.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyArray.cpp @@ -3,8 +3,6 @@ #include "CesiumPropertyArray.h" #include -using namespace CesiumGltf; - FCesiumPropertyArray::FCesiumPropertyArray(FCesiumPropertyArray&& rhs) = default; @@ -15,7 +13,7 @@ FCesiumPropertyArray::FCesiumPropertyArray(const FCesiumPropertyArray& rhs) : _value(), _elementType(rhs._elementType), _storage(rhs._storage) { swl::visit( [this](const auto& value) { - if constexpr (IsMetadataArray::value) { + if constexpr (CesiumGltf::IsMetadataArray::value) { if (!this->_storage.empty()) { this->_value = decltype(value)(this->_storage); } else { diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTable.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTable.cpp index 9f803e9f6..83a16af47 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTable.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTable.cpp @@ -3,21 +3,19 @@ #include "CesiumPropertyTable.h" #include "CesiumGltf/PropertyTableView.h" -using namespace CesiumGltf; - static FCesiumPropertyTableProperty EmptyPropertyTableProperty; FCesiumPropertyTable::FCesiumPropertyTable( - const Model& Model, - const PropertyTable& PropertyTable) + const CesiumGltf::Model& Model, + const CesiumGltf::PropertyTable& PropertyTable) : _status(ECesiumPropertyTableStatus::ErrorInvalidPropertyTableClass), _name(PropertyTable.name.value_or("").c_str()), _className(PropertyTable.classProperty.c_str()), _count(PropertyTable.count), _properties() { - PropertyTableView propertyTableView{Model, PropertyTable}; + CesiumGltf::PropertyTableView propertyTableView{Model, PropertyTable}; switch (propertyTableView.status()) { - case PropertyTableViewStatus::Valid: + case CesiumGltf::PropertyTableViewStatus::Valid: _status = ECesiumPropertyTableStatus::Valid; break; default: diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp index 6e086f752..794ce3810 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp @@ -6,8 +6,6 @@ #include "UnrealMetadataConversions.h" #include -using namespace CesiumGltf; - namespace { /** * Callback on a std::any, assuming that it contains a PropertyTablePropertyView diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTexture.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTexture.cpp index e69be7349..ffefd3e3b 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTexture.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTexture.cpp @@ -6,8 +6,6 @@ #include "CesiumGltf/PropertyTextureView.h" #include "CesiumMetadataPickingBlueprintLibrary.h" -using namespace CesiumGltf; - static FCesiumPropertyTextureProperty EmptyPropertyTextureProperty; FCesiumPropertyTexture::FCesiumPropertyTexture( @@ -16,9 +14,9 @@ FCesiumPropertyTexture::FCesiumPropertyTexture( : _status(ECesiumPropertyTextureStatus::ErrorInvalidPropertyTextureClass), _name(PropertyTexture.name.value_or("").c_str()), _className(PropertyTexture.classProperty.c_str()) { - PropertyTextureView propertyTextureView(Model, PropertyTexture); + CesiumGltf::PropertyTextureView propertyTextureView(Model, PropertyTexture); switch (propertyTextureView.status()) { - case PropertyTextureViewStatus::Valid: + case CesiumGltf::PropertyTextureViewStatus::Valid: _status = ECesiumPropertyTextureStatus::Valid; break; default: @@ -35,7 +33,7 @@ FCesiumPropertyTexture::FCesiumPropertyTexture( continue; } - TextureViewOptions options; + CesiumGltf::TextureViewOptions options; options.applyKhrTextureTransformExtension = true; if (propertyPair->second.extras.find("makeImageCopy") != diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp index ce122edc1..0c3e93efa 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp @@ -8,8 +8,6 @@ #include #include -using namespace CesiumGltf; - namespace { /** * Callback on a std::any, assuming that it contains a diff --git a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp index cc94b1946..4522422c1 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureUtility.cpp @@ -28,8 +28,6 @@ #include #include -using namespace CesiumGltf; - namespace { struct ExtensionUnrealTexture { @@ -424,41 +422,42 @@ TextureAddress convertGltfWrapTToUnreal(int32_t wrapT) { } std::optional getPixelFormatForImageAsset( - const ImageAsset& imageCesium, + const CesiumGltf::ImageAsset& imageCesium, const std::optional overridePixelFormat) { - if (imageCesium.compressedPixelFormat != GpuCompressedPixelFormat::NONE) { + if (imageCesium.compressedPixelFormat != + CesiumGltf::GpuCompressedPixelFormat::NONE) { switch (imageCesium.compressedPixelFormat) { - case GpuCompressedPixelFormat::ETC1_RGB: + case CesiumGltf::GpuCompressedPixelFormat::ETC1_RGB: return EPixelFormat::PF_ETC1; break; - case GpuCompressedPixelFormat::ETC2_RGBA: + case CesiumGltf::GpuCompressedPixelFormat::ETC2_RGBA: return EPixelFormat::PF_ETC2_RGBA; break; - case GpuCompressedPixelFormat::BC1_RGB: + case CesiumGltf::GpuCompressedPixelFormat::BC1_RGB: return EPixelFormat::PF_DXT1; break; - case GpuCompressedPixelFormat::BC3_RGBA: + case CesiumGltf::GpuCompressedPixelFormat::BC3_RGBA: return EPixelFormat::PF_DXT5; break; - case GpuCompressedPixelFormat::BC4_R: + case CesiumGltf::GpuCompressedPixelFormat::BC4_R: return EPixelFormat::PF_BC4; break; - case GpuCompressedPixelFormat::BC5_RG: + case CesiumGltf::GpuCompressedPixelFormat::BC5_RG: return EPixelFormat::PF_BC5; break; - case GpuCompressedPixelFormat::BC7_RGBA: + case CesiumGltf::GpuCompressedPixelFormat::BC7_RGBA: return EPixelFormat::PF_BC7; break; - case GpuCompressedPixelFormat::ASTC_4x4_RGBA: + case CesiumGltf::GpuCompressedPixelFormat::ASTC_4x4_RGBA: return EPixelFormat::PF_ASTC_4x4; break; - case GpuCompressedPixelFormat::PVRTC2_4_RGBA: + case CesiumGltf::GpuCompressedPixelFormat::PVRTC2_4_RGBA: return EPixelFormat::PF_PVRTC2; break; - case GpuCompressedPixelFormat::ETC2_EAC_R11: + case CesiumGltf::GpuCompressedPixelFormat::ETC2_EAC_R11: return EPixelFormat::PF_ETC2_R11_EAC; break; - case GpuCompressedPixelFormat::ETC2_EAC_RG11: + case CesiumGltf::GpuCompressedPixelFormat::ETC2_EAC_RG11: return EPixelFormat::PF_ETC2_RG11_EAC; break; default: diff --git a/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp index 9f2120a9b..3675cd21c 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp @@ -5,7 +5,6 @@ #include using namespace CesiumAsync; -using namespace CesiumGltf; using namespace CesiumGltfReader; namespace { @@ -13,7 +12,9 @@ namespace { std::mutex createExtensionMutex; std::pair>> -getOrCreateImageFuture(const AsyncSystem& asyncSystem, ImageAsset& imageCesium); +getOrCreateImageFuture( + const AsyncSystem& asyncSystem, + CesiumGltf::ImageAsset& imageCesium); } // namespace @@ -80,7 +81,7 @@ namespace { std::pair>> getOrCreateImageFuture( const AsyncSystem& asyncSystem, - ImageAsset& imageCesium) { + CesiumGltf::ImageAsset& imageCesium) { std::scoped_lock lock(createExtensionMutex); ExtensionImageAssetUnreal* pExtension = From 935e0db93a05112c5dda020f93a4f604be90a951 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 31 Oct 2024 20:37:13 +1100 Subject: [PATCH 63/68] Fix memory leak and non-async texture creation. --- .../CesiumRuntime/Private/CesiumLifetime.cpp | 1 + .../Private/CesiumTextureResource.cpp | 104 ++++++++++-------- .../Private/ExtensionImageAssetUnreal.cpp | 16 +++ 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumLifetime.cpp b/Source/CesiumRuntime/Private/CesiumLifetime.cpp index f2ec767aa..6ea2c2a21 100644 --- a/Source/CesiumRuntime/Private/CesiumLifetime.cpp +++ b/Source/CesiumRuntime/Private/CesiumLifetime.cpp @@ -129,6 +129,7 @@ void AmortizedDestructor::finalizeDestroy(UObject* pObject) const { UTexture2D* pTexture2D = Cast(pObject); if (pTexture2D) { + pTexture2D->ReleaseResource(); FTexturePlatformData* pPlatformData = pTexture2D->GetPlatformData(); pTexture2D->SetPlatformData(nullptr); delete pPlatformData; diff --git a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp index 759b23bd8..ea715d522 100644 --- a/Source/CesiumRuntime/Private/CesiumTextureResource.cpp +++ b/Source/CesiumRuntime/Private/CesiumTextureResource.cpp @@ -61,11 +61,15 @@ class FCesiumUseExistingTextureResource : public FCesiumTextureResource { * `GRHISupportsAsyncTextureCreation` is false (everywhere but Direct3D), we can * only create a `FRHITexture` on the render thread, so this is the code that * does it. + * + * Upon passing an `ImageAsset` to this class's constructor, its `pixelData` and + * `mipPositions` fields are cleared. That is, this class takes ownership of + * that data. */ class FCesiumCreateNewTextureResource : public FCesiumTextureResource { public: FCesiumCreateNewTextureResource( - CesiumGltf::ImageAsset&& image, + CesiumGltf::ImageAsset& image, TextureGroup textureGroup, uint32 width, uint32 height, @@ -81,7 +85,8 @@ class FCesiumCreateNewTextureResource : public FCesiumTextureResource { virtual FTextureRHIRef InitializeTextureRHI() override; private: - CesiumGltf::ImageAsset _image; + std::vector _mipPositions; + std::vector _pixelData; }; ESamplerFilter convertFilter(TextureFilter filter) { @@ -126,25 +131,27 @@ void CopyMip( void* pDest, uint32 destPitch, EPixelFormat format, - const CesiumGltf::ImageAsset& src, + int32_t width, + int32_t height, + const std::vector& srcPixelData, + const std::vector& srcMipPositions, uint32 mipIndex) { size_t byteOffset = 0; size_t byteSize = 0; - if (src.mipPositions.empty()) { + if (srcMipPositions.empty()) { byteOffset = 0; - byteSize = src.pixelData.size(); + byteSize = srcPixelData.size(); } else { - const CesiumGltf::ImageAssetMipPosition& mipPos = - src.mipPositions[mipIndex]; + const CesiumGltf::ImageAssetMipPosition& mipPos = srcMipPositions[mipIndex]; byteOffset = mipPos.byteOffset; byteSize = mipPos.byteSize; } uint32 mipWidth = - FMath::Max(static_cast(src.width) >> mipIndex, 1); + FMath::Max(static_cast(width) >> mipIndex, 1); uint32 mipHeight = - FMath::Max(static_cast(src.height) >> mipIndex, 1); + FMath::Max(static_cast(height) >> mipIndex, 1); - const void* pSrcData = static_cast(&src.pixelData[byteOffset]); + const void* pSrcData = static_cast(&srcPixelData[byteOffset]); // for platforms that returned 0 pitch from Lock, we need to just use the bulk // data directly, never do runtime block size checking, conversion, or the @@ -357,21 +364,20 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { CreateRHITexture2D_Async(imageCesium, *maybePixelFormat, sRGB); // textureReference->SetName( // FName(UTF8_TO_TCHAR(imageCesium.getUniqueAssetId().c_str()))); - auto pResult = TUniquePtr< - FCesiumUseExistingTextureResource, - FCesiumTextureResourceDeleter>(new FCesiumUseExistingTextureResource( - textureReference, - textureGroup, - imageCesium.width, - imageCesium.height, - *maybePixelFormat, - filter, - addressX, - addressY, - sRGB, - needsMipMaps, - 0, - true)); + auto pResult = + FCesiumTextureResourceUniquePtr(new FCesiumUseExistingTextureResource( + textureReference, + textureGroup, + imageCesium.width, + imageCesium.height, + *maybePixelFormat, + filter, + addressX, + addressY, + sRGB, + needsMipMaps, + 0, + true)); // Clear the now-unnecessary copy of the pixel data. // Calling clear() isn't good enough because it @@ -387,20 +393,19 @@ void FCesiumTextureResourceDeleter::operator()(FCesiumTextureResource* p) { // The RHI texture will be created later on the // render thread, directly from this texture source. // We need valid pixelData here, though. - auto pResult = TUniquePtr< - FCesiumCreateNewTextureResource, - FCesiumTextureResourceDeleter>(new FCesiumCreateNewTextureResource( - std::move(imageCesium), - textureGroup, - imageCesium.width, - imageCesium.height, - *maybePixelFormat, - filter, - addressX, - addressY, - sRGB, - needsMipMaps, - 0)); + auto pResult = + FCesiumTextureResourceUniquePtr(new FCesiumCreateNewTextureResource( + imageCesium, + textureGroup, + imageCesium.width, + imageCesium.height, + *maybePixelFormat, + filter, + addressX, + addressY, + sRGB, + needsMipMaps, + 0)); return pResult; } } @@ -641,7 +646,7 @@ FTextureRHIRef FCesiumUseExistingTextureResource::InitializeTextureRHI() { } FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( - CesiumGltf::ImageAsset&& image, + CesiumGltf::ImageAsset& image, TextureGroup textureGroup, uint32 width, uint32 height, @@ -664,7 +669,8 @@ FCesiumCreateNewTextureResource::FCesiumCreateNewTextureResource( useMipsIfAvailable, extData, true), - _image(std::move(image)) {} + _mipPositions(std::move(image.mipPositions)), + _pixelData(std::move(image.pixelData)) {} FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { // Use the asset ID as the name of the texture so it will be visible in the @@ -697,7 +703,7 @@ FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { } uint32 mipCount = - FMath::Max(1, static_cast(this->_image.mipPositions.size())); + FMath::Max(1, static_cast(this->_mipPositions.size())); // Create a new RHI texture, initially empty. @@ -721,17 +727,25 @@ FTextureRHIRef FCesiumCreateNewTextureResource::InitializeTextureRHI() { uint32 DestPitch; void* pDestination = RHILockTexture2D(rhiTexture, i, RLM_WriteOnly, DestPitch, false); - CopyMip(pDestination, DestPitch, _format, this->_image, i); + CopyMip( + pDestination, + DestPitch, + this->_format, + this->_width, + this->_height, + this->_pixelData, + this->_mipPositions, + i); RHIUnlockTexture2D(rhiTexture, i, false); } // Clear the now-unnecessary copy of the pixel data. Calling clear() isn't // good enough because it won't actually release the memory. std::vector pixelData; - this->_image.pixelData.swap(pixelData); + this->_pixelData.swap(pixelData); std::vector mipPositions; - this->_image.mipPositions.swap(mipPositions); + this->_mipPositions.swap(mipPositions); return rhiTexture; } diff --git a/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp index 3675cd21c..c9f75ae97 100644 --- a/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp +++ b/Source/CesiumRuntime/Private/ExtensionImageAssetUnreal.cpp @@ -49,6 +49,22 @@ ExtensionImageAssetUnreal::getOrCreate( FCesiumTextureResource ::Destroy(p); }); + // For texture resources created from glTF _textures_, this will happen later + // (after we created the UTexture2D). But this texture resource, created for + // an ImageAsset, will never have a UTexture2D, so we initialize its resources + // here. + ENQUEUE_RENDER_COMMAND(Cesium_InitResource) + ([pResource = extension._pTextureResource]( + FRHICommandListImmediate& RHICmdList) mutable { +#if ENGINE_VERSION_5_3_OR_HIGHER + pResource->InitResource( + FRHICommandListImmediate::Get()); // Init Resource now requires a + // command list. +#else + pResource->InitResource(); +#endif + }); + maybePromise->resolve(); return extension; From 26eff5eaacb43afa61cf355dc13f30b560c8a20e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 31 Oct 2024 20:53:56 +1100 Subject: [PATCH 64/68] More namespacing. --- .../Private/CesiumMetadataValue.cpp | 6 +- .../Private/CesiumPropertyTableProperty.cpp | 136 +++++++++--------- .../Private/CesiumPropertyTextureProperty.cpp | 94 +++++++----- 3 files changed, 130 insertions(+), 106 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp index f27e6d28d..8fac58f33 100644 --- a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp +++ b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp @@ -272,9 +272,9 @@ FString UCesiumMetadataValueBlueprintLibrary::GetString( [&DefaultValue](auto value) -> FString { using ValueType = decltype(value); if constexpr ( - IsMetadataVecN::value || - IsMetadataMatN::value || - IsMetadataString::value) { + CesiumGltf::IsMetadataVecN::value || + CesiumGltf::IsMetadataMatN::value || + CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toString(value); } else { auto maybeString = CesiumGltf:: diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp index 794ce3810..dda61f6f2 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTableProperty.cpp @@ -27,14 +27,15 @@ template < typename Callback> TResult propertyTablePropertyCallback(const std::any& property, Callback&& callback) { - const PropertyTablePropertyView* pProperty = - std::any_cast>( + const CesiumGltf::PropertyTablePropertyView* + pProperty = std::any_cast< + CesiumGltf::PropertyTablePropertyView>( &property); if (pProperty) { return callback(*pProperty); } - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } /** @@ -111,7 +112,7 @@ TResult scalarPropertyTablePropertyCallback( property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -137,66 +138,66 @@ TResult scalarArrayPropertyTablePropertyCallback( switch (valueType.ComponentType) { case ECesiumMetadataComponentType::Int8: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint8: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int16: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint16: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int32: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint32: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int64: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint64: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float32: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, false, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float64: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, false, TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -281,7 +282,7 @@ TResult vecNPropertyTablePropertyCallback( TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -324,7 +325,7 @@ TResult vecNPropertyTablePropertyCallback( std::forward(callback)); } - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } /** @@ -350,66 +351,66 @@ TResult vecNArrayPropertyTablePropertyCallback( switch (valueType.ComponentType) { case ECesiumMetadataComponentType::Int8: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint8: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int16: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint16: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, false, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, false, TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -456,7 +457,7 @@ TResult vecNArrayPropertyTablePropertyCallback( Callback>(property, valueType, std::forward(callback)); } - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } /** @@ -540,7 +541,7 @@ TResult matNPropertyTablePropertyCallback( TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -583,7 +584,7 @@ TResult matNPropertyTablePropertyCallback( std::forward(callback)); } - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } /** @@ -609,66 +610,66 @@ TResult matNArrayPropertyTablePropertyCallback( switch (valueType.ComponentType) { case ECesiumMetadataComponentType::Int8: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint8: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int16: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint16: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float32: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, false, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Float64: return propertyTablePropertyCallback< - PropertyArrayView>, + CesiumGltf::PropertyArrayView>, false, TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -715,7 +716,7 @@ TResult matNArrayPropertyTablePropertyCallback( Callback>(property, valueType, std::forward(callback)); } - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } template @@ -745,18 +746,18 @@ TResult arrayPropertyTablePropertyCallback( Callback>(property, valueType, std::forward(callback)); case ECesiumMetadataType::Boolean: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, false, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataType::String: return propertyTablePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, false, TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -824,7 +825,7 @@ TResult propertyTablePropertyCallback( TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTablePropertyView()); + return callback(CesiumGltf::PropertyTablePropertyView()); } } @@ -1042,7 +1043,7 @@ FIntPoint UCesiumPropertyTablePropertyBlueprintLibrary::GetIntPoint( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toIntPoint(value, DefaultValue); } else { auto maybeVec2 = CesiumGltf:: @@ -1072,7 +1073,7 @@ FVector2D UCesiumPropertyTablePropertyBlueprintLibrary::GetVector2D( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector2D(value, DefaultValue); } else { auto maybeVec2 = CesiumGltf:: @@ -1102,7 +1103,7 @@ FIntVector UCesiumPropertyTablePropertyBlueprintLibrary::GetIntVector( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toIntVector(value, DefaultValue); } else { auto maybeVec3 = CesiumGltf:: @@ -1132,7 +1133,7 @@ FVector3f UCesiumPropertyTablePropertyBlueprintLibrary::GetVector3f( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector3f(value, DefaultValue); } else { auto maybeVec3 = CesiumGltf:: @@ -1162,7 +1163,7 @@ FVector UCesiumPropertyTablePropertyBlueprintLibrary::GetVector( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector(value, DefaultValue); } else { auto maybeVec3 = CesiumGltf:: @@ -1192,7 +1193,7 @@ FVector4 UCesiumPropertyTablePropertyBlueprintLibrary::GetVector4( } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector4(value, DefaultValue); } else { auto maybeVec4 = CesiumGltf:: @@ -1252,9 +1253,9 @@ FString UCesiumPropertyTablePropertyBlueprintLibrary::GetString( using ValueType = decltype(value); if constexpr ( - IsMetadataVecN::value || - IsMetadataMatN::value || - IsMetadataString::value) { + CesiumGltf::IsMetadataVecN::value || + CesiumGltf::IsMetadataMatN::value || + CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toString(value); } else { auto maybeString = CesiumGltf:: @@ -1314,15 +1315,15 @@ FCesiumMetadataValue UCesiumPropertyTablePropertyBlueprintLibrary::GetRawValue( Property._normalized, [FeatureID](const auto& view) -> FCesiumMetadataValue { // Return an empty value if the property is empty. - if (view.status() == - PropertyTablePropertyViewStatus::EmptyPropertyWithDefault) { + if (view.status() == CesiumGltf::PropertyTablePropertyViewStatus:: + EmptyPropertyWithDefault) { return FCesiumMetadataValue(); } // size() returns zero if the view is invalid. if (FeatureID >= 0 && FeatureID < view.size()) { return FCesiumMetadataValue( - propertyValueViewToCopy(view.getRaw(FeatureID))); + CesiumGltf::propertyValueViewToCopy(view.getRaw(FeatureID))); } return FCesiumMetadataValue(); @@ -1342,7 +1343,8 @@ FCesiumMetadataValue UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no offset is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.offset())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.offset())); }); } @@ -1354,7 +1356,8 @@ FCesiumMetadataValue UCesiumPropertyTablePropertyBlueprintLibrary::GetScale( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no scale is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.scale())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.scale())); }); } @@ -1367,7 +1370,8 @@ UCesiumPropertyTablePropertyBlueprintLibrary::GetMinimumValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no min is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.min())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.min())); }); } @@ -1380,7 +1384,8 @@ UCesiumPropertyTablePropertyBlueprintLibrary::GetMaximumValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no max is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.max())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.max())); }); } @@ -1393,7 +1398,8 @@ UCesiumPropertyTablePropertyBlueprintLibrary::GetNoDataValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no "no data" value is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.noData())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.noData())); }); } @@ -1407,7 +1413,7 @@ UCesiumPropertyTablePropertyBlueprintLibrary::GetDefaultValue( [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no default value is specified. return FCesiumMetadataValue( - propertyValueViewToCopy(view.defaultValue())); + CesiumGltf::propertyValueViewToCopy(view.defaultValue())); }); } diff --git a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp index 0c3e93efa..082e2ffb1 100644 --- a/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp +++ b/Source/CesiumRuntime/Private/CesiumPropertyTextureProperty.cpp @@ -30,14 +30,15 @@ template < typename Callback> TResult propertyTexturePropertyCallback(const std::any& property, Callback&& callback) { - const PropertyTexturePropertyView* pProperty = - std::any_cast>( + const CesiumGltf::PropertyTexturePropertyView* + pProperty = std::any_cast< + CesiumGltf::PropertyTexturePropertyView>( &property); if (pProperty) { return callback(*pProperty); } - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } /** @@ -101,7 +102,7 @@ TResult scalarPropertyTexturePropertyCallback( property, std::forward(callback)); default: - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } } @@ -127,30 +128,30 @@ TResult scalarArrayPropertyTexturePropertyCallback( switch (valueType.ComponentType) { case ECesiumMetadataComponentType::Int8: return propertyTexturePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint8: return propertyTexturePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Int16: return propertyTexturePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); case ECesiumMetadataComponentType::Uint16: return propertyTexturePropertyCallback< - PropertyArrayView, + CesiumGltf::PropertyArrayView, Normalized, TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } } @@ -200,7 +201,7 @@ TResult vecNPropertyTexturePropertyCallback( TResult, Callback>(property, std::forward(callback)); default: - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } } @@ -247,7 +248,7 @@ TResult vecNPropertyTexturePropertyCallback( Callback>(property, valueType, std::forward(callback)); } - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } template @@ -260,7 +261,7 @@ TResult propertyTexturePropertyCallback( if (valueType.bIsArray && valueType.Type != ECesiumMetadataType::Scalar) { // Only scalar property arrays are supported. - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } if (valueType.bIsArray) { @@ -307,7 +308,7 @@ TResult propertyTexturePropertyCallback( valueType, std::forward(callback)); default: - return callback(PropertyTexturePropertyView()); + return callback(CesiumGltf::PropertyTexturePropertyView()); } } @@ -472,7 +473,8 @@ uint8 UCesiumPropertyTexturePropertyBlueprintLibrary::GetByte( Property._valueType, Property._normalized, [&UV, DefaultValue](const auto& view) -> uint8 { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -495,7 +497,8 @@ int32 UCesiumPropertyTexturePropertyBlueprintLibrary::GetInteger( Property._valueType, Property._normalized, [&UV, DefaultValue](const auto& view) -> int32 { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -518,7 +521,8 @@ float UCesiumPropertyTexturePropertyBlueprintLibrary::GetFloat( Property._valueType, Property._normalized, [&UV, DefaultValue](const auto& view) -> float { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -541,7 +545,8 @@ double UCesiumPropertyTexturePropertyBlueprintLibrary::GetFloat64( Property._valueType, Property._normalized, [&UV, DefaultValue](const auto& view) -> double { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -564,7 +569,8 @@ FIntPoint UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntPoint( Property._valueType, Property._normalized, [&UV, &DefaultValue](const auto& view) -> FIntPoint { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -572,7 +578,7 @@ FIntPoint UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntPoint( return DefaultValue; } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toIntPoint( *maybeValue, DefaultValue); @@ -594,7 +600,8 @@ FVector2D UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector2D( Property._valueType, Property._normalized, [&UV, &DefaultValue](const auto& view) -> FVector2D { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -602,7 +609,7 @@ FVector2D UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector2D( return DefaultValue; } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector2D(value, DefaultValue); } else { auto maybeVec2 = CesiumGltf:: @@ -622,7 +629,8 @@ FIntVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntVector( Property._valueType, Property._normalized, [&UV, &DefaultValue](const auto& view) -> FIntVector { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -630,7 +638,7 @@ FIntVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntVector( return DefaultValue; } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toIntVector(value, DefaultValue); } else { auto maybeVec3 = CesiumGltf:: @@ -650,7 +658,8 @@ FVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector( Property._valueType, Property._normalized, [&UV, &DefaultValue](const auto& view) -> FVector { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -658,7 +667,7 @@ FVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector( return DefaultValue; } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector(value, DefaultValue); } else { auto maybeVec3 = CesiumGltf:: @@ -678,7 +687,8 @@ FVector4 UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector4( Property._valueType, Property._normalized, [&UV, &DefaultValue](const auto& view) -> FVector4 { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return DefaultValue; } auto maybeValue = view.get(UV.X, UV.Y); @@ -686,7 +696,7 @@ FVector4 UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector4( return DefaultValue; } auto value = *maybeValue; - if constexpr (IsMetadataString::value) { + if constexpr (CesiumGltf::IsMetadataString::value) { return UnrealMetadataConversions::toVector(value, DefaultValue); } else { auto maybeVec4 = CesiumGltf:: @@ -705,7 +715,8 @@ FCesiumPropertyArray UCesiumPropertyTexturePropertyBlueprintLibrary::GetArray( Property._valueType, Property._normalized, [&UV](const auto& view) -> FCesiumPropertyArray { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return FCesiumPropertyArray(); } auto maybeValue = view.get(UV.X, UV.Y); @@ -727,9 +738,10 @@ FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetValue( Property._valueType, Property._normalized, [&UV](const auto& view) -> FCesiumMetadataValue { - if (view.status() != PropertyTexturePropertyViewStatus::Valid && - view.status() != - PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid && + view.status() != CesiumGltf::PropertyTexturePropertyViewStatus:: + EmptyPropertyWithDefault) { return FCesiumMetadataValue(); } @@ -746,7 +758,8 @@ UCesiumPropertyTexturePropertyBlueprintLibrary::GetRawValue( Property._valueType, Property._normalized, [&UV](const auto& view) -> FCesiumMetadataValue { - if (view.status() != PropertyTexturePropertyViewStatus::Valid) { + if (view.status() != + CesiumGltf::PropertyTexturePropertyViewStatus::Valid) { return FCesiumMetadataValue(); } @@ -767,7 +780,8 @@ FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no offset is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.offset())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.offset())); }); } @@ -779,7 +793,8 @@ FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetScale( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no scale is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.scale())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.scale())); }); } @@ -792,7 +807,8 @@ UCesiumPropertyTexturePropertyBlueprintLibrary::GetMinimumValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no min is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.min())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.min())); }); } @@ -805,7 +821,8 @@ UCesiumPropertyTexturePropertyBlueprintLibrary::GetMaximumValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no max is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.max())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.max())); }); } @@ -818,7 +835,8 @@ UCesiumPropertyTexturePropertyBlueprintLibrary::GetNoDataValue( Property._normalized, [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no "no data" value is specified. - return FCesiumMetadataValue(propertyValueViewToCopy(view.noData())); + return FCesiumMetadataValue( + CesiumGltf::propertyValueViewToCopy(view.noData())); }); } @@ -832,6 +850,6 @@ UCesiumPropertyTexturePropertyBlueprintLibrary::GetDefaultValue( [](const auto& view) -> FCesiumMetadataValue { // Returns an empty value if no default value is specified. return FCesiumMetadataValue( - propertyValueViewToCopy(view.defaultValue())); + CesiumGltf::propertyValueViewToCopy(view.defaultValue())); }); } From 783bb4e035fe23bee75744ba38568e83d38c8af9 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 31 Oct 2024 13:10:11 -0400 Subject: [PATCH 65/68] Fix tests --- .../Private/Tests/Cesium3DTileset.spec.cpp | 2 + ...umMetadataPickingBlueprintLibrary.spec.cpp | 242 +++++++++--------- 2 files changed, 124 insertions(+), 120 deletions(-) diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 8ae6f994e..79d6123eb 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -50,6 +50,7 @@ static void setupForSharedImages(SceneGenerationContext& context) { ACesium3DTileset* tileset = context.world->SpawnActor(); tileset->SetTilesetSource(ETilesetSource::FromCesiumIon); tileset->SetIonAssetID(2757071); + tileset->SetIonAccessToken(SceneGenerationContext::testIonToken); tileset->SetActorLabel(TEXT("SharedImages")); tileset->SetGeoreference(georeference); @@ -104,6 +105,7 @@ static void setupForSnowdon(SceneGenerationContext& context) { ACesium3DTileset* tileset = context.world->SpawnActor(); tileset->SetTilesetSource(ETilesetSource::FromCesiumIon); tileset->SetIonAssetID(2758251); + tileset->SetIonAccessToken(SceneGenerationContext::testIonToken); tileset->SetActorLabel(TEXT("Snowdon")); tileset->SuspendUpdate = false; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp index cf4098e89..ff8c52312 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp @@ -10,20 +10,18 @@ #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumMetadataPickingSpec, "Cesium.Unit.MetadataPicking", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -MeshPrimitive* pPrimitive; -ExtensionExtMeshFeatures* pMeshFeatures; -ExtensionModelExtStructuralMetadata* pModelMetadata; -ExtensionMeshPrimitiveExtStructuralMetadata* pPrimitiveMetadata; -PropertyTable* pPropertyTable; -PropertyTexture* pPropertyTexture; +CesiumGltf::Model model; +CesiumGltf::MeshPrimitive* pPrimitive; +CesiumGltf::ExtensionExtMeshFeatures* pMeshFeatures; +CesiumGltf::ExtensionModelExtStructuralMetadata* pModelMetadata; +CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata* pPrimitiveMetadata; +CesiumGltf::PropertyTable* pPropertyTable; +CesiumGltf::PropertyTexture* pPropertyTexture; TObjectPtr pModelComponent; TObjectPtr pPrimitiveComponent; END_DEFINE_SPEC(FCesiumMetadataPickingSpec) @@ -31,8 +29,8 @@ END_DEFINE_SPEC(FCesiumMetadataPickingSpec) void FCesiumMetadataPickingSpec::Define() { Describe("FindUVFromHit", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; pPrimitiveComponent = NewObject(); @@ -50,8 +48,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, positions); int32_t positionAccessorIndex = static_cast(model.accessors.size() - 1); @@ -70,16 +68,16 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "TEXCOORD_0", - AccessorSpec::Type::VEC2, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC2, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, texCoords); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); }); @@ -152,11 +150,11 @@ void FCesiumMetadataPickingSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); - primData.IndexAccessor = AccessorView( + primData.IndexAccessor = CesiumGltf::AccessorView( model, static_cast(model.accessors.size() - 1)); @@ -199,10 +197,10 @@ void FCesiumMetadataPickingSpec::Define() { Describe("GetPropertyTableValuesFromHit", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pPrimitive->mode = MeshPrimitive::Mode::TRIANGLES; + pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; // Two disconnected triangles. std::vector positions{ @@ -219,13 +217,15 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(positionData)); - pMeshFeatures = &pPrimitive->addExtension(); + pMeshFeatures = + &pPrimitive->addExtension(); pModelMetadata = - &model.addExtension(); + &model + .addExtension(); std::string className = "testClass"; pModelMetadata->schema.emplace(); @@ -248,13 +248,13 @@ void FCesiumMetadataPickingSpec::Define() { static_cast(model.accessors.size() - 1); std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel(model, *pPrimitive, featureIDs, 2, 0); featureId.propertyTable = static_cast(pModelMetadata->propertyTables.size() - 1); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); std::vector scalarValues{1, 2}; pPropertyTable->count = static_cast(scalarValues.size()); @@ -263,8 +263,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); pModelComponent->Metadata = FCesiumModelMetadata(model, *pModelMetadata); @@ -286,14 +286,14 @@ void FCesiumMetadataPickingSpec::Define() { static_cast(model.accessors.size() - 1); std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel(model, *pPrimitive, featureIDs, 2, 0); featureId.propertyTable = static_cast(pModelMetadata->propertyTables.size() - 1); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); std::vector scalarValues{1, 2}; pPropertyTable->count = static_cast(scalarValues.size()); @@ -302,8 +302,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); pModelComponent->Metadata = FCesiumModelMetadata(model, *pModelMetadata); @@ -340,7 +340,7 @@ void FCesiumMetadataPickingSpec::Define() { CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); std::vector scalarValues{1, 2}; pPropertyTable->count = static_cast(scalarValues.size()); @@ -349,8 +349,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -361,8 +361,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = @@ -384,14 +384,14 @@ void FCesiumMetadataPickingSpec::Define() { static_cast(model.accessors.size() - 1); std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel(model, *pPrimitive, featureIDs, 2, 0); featureId.propertyTable = static_cast(pModelMetadata->propertyTables.size() - 1); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); std::vector scalarValues{1, 2}; pPropertyTable->count = static_cast(scalarValues.size()); @@ -400,8 +400,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -412,8 +412,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = FCesiumModelMetadata(model, *pModelMetadata); @@ -469,14 +469,14 @@ void FCesiumMetadataPickingSpec::Define() { static_cast(model.accessors.size() - 1); std::vector featureIDs0{1, 1, 1, 0, 0, 0}; - FeatureId& featureId0 = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId0 = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs0, 2, 0); std::vector featureIDs1{0, 0, 0, 1, 1, 1}; - FeatureId& featureId1 = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId1 = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs1, @@ -487,7 +487,7 @@ void FCesiumMetadataPickingSpec::Define() { CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); std::vector scalarValues{1, 2}; pPropertyTable->count = static_cast(scalarValues.size()); @@ -496,8 +496,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -508,8 +508,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = FCesiumModelMetadata(model, *pModelMetadata); @@ -562,10 +562,10 @@ void FCesiumMetadataPickingSpec::Define() { Describe("GetPropertyTextureValuesFromHit", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pPrimitive->mode = MeshPrimitive::Mode::TRIANGLES; + pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; std::vector positions{ glm::vec3(-1, 0, 0), @@ -580,8 +580,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, GetValuesAsBytes(positions)); int32_t positionAccessorIndex = @@ -602,12 +602,13 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "TEXCOORD_0", - AccessorSpec::Type::VEC2, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC2, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, texCoords0); pModelMetadata = - &model.addExtension(); + &model + .addExtension(); std::string className = "testClass"; pModelMetadata->schema.emplace(); @@ -616,9 +617,8 @@ void FCesiumMetadataPickingSpec::Define() { pPropertyTexture = &pModelMetadata->propertyTextures.emplace_back(); pPropertyTexture->classProperty = className; - pPrimitiveMetadata = - &pPrimitive - ->addExtension(); + pPrimitiveMetadata = &pPrimitive->addExtension< + CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata>(); pPrimitiveMetadata->propertyTextures.push_back(0); pModelComponent = NewObject(); @@ -632,10 +632,10 @@ void FCesiumMetadataPickingSpec::Define() { CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.pMeshPrimitive = pPrimitive; primData.PositionAccessor = - AccessorView(model, positionAccessorIndex); + CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); }); @@ -647,8 +647,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, scalarValues, {0}); @@ -675,8 +675,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, scalarValues, {0}); @@ -710,8 +710,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, scalarValues, {0}); @@ -741,8 +741,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, scalarValues, {0}); @@ -756,8 +756,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::UINT8, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::UINT8, vec2Values, {0, 1}); @@ -831,13 +831,13 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, scalarValues, {0}); // Make another property texture - PropertyTexture& propertyTexture = + CesiumGltf::PropertyTexture& propertyTexture = pModelMetadata->propertyTextures.emplace_back(); propertyTexture.classProperty = "testClass"; std::array newScalarValues = {100, -20, 33, -4}; @@ -845,8 +845,8 @@ void FCesiumMetadataPickingSpec::Define() { model, propertyTexture, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT8, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT8, newScalarValues, {0}); @@ -897,8 +897,8 @@ void FCesiumMetadataPickingSpec::Define() { Describe("Deprecated", [this]() { Describe("GetMetadataValuesForFace", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); // Two disconnected triangles. @@ -917,13 +917,14 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(positionData)); - pMeshFeatures = &pPrimitive->addExtension(); - pModelMetadata = - &model.addExtension(); + pMeshFeatures = + &pPrimitive->addExtension(); + pModelMetadata = &model.addExtension< + CesiumGltf::ExtensionModelExtStructuralMetadata>(); std::string className = "testClass"; pModelMetadata->schema.emplace(); @@ -944,7 +945,7 @@ void FCesiumMetadataPickingSpec::Define() { It("returns empty map for invalid face index", [this]() { std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -960,8 +961,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); pModelComponent->Metadata = @@ -987,7 +988,7 @@ void FCesiumMetadataPickingSpec::Define() { It("returns empty map for invalid feature ID set index", [this]() { std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -1003,8 +1004,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); pModelComponent->Metadata = @@ -1047,8 +1048,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -1059,8 +1060,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = @@ -1079,7 +1080,7 @@ void FCesiumMetadataPickingSpec::Define() { It("returns values for first feature ID set by default", [this]() { std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -1095,8 +1096,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -1107,8 +1108,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = @@ -1159,14 +1160,14 @@ void FCesiumMetadataPickingSpec::Define() { It("returns values for specified feature ID set", [this]() { std::vector featureIDs0{1, 1, 1, 0, 0, 0}; - FeatureId& featureId0 = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId0 = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs0, 2, 0); std::vector featureIDs1{0, 0, 0, 1, 1, 1}; - FeatureId& featureId1 = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId1 = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs1, @@ -1182,8 +1183,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -1194,8 +1195,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = @@ -1242,8 +1243,8 @@ void FCesiumMetadataPickingSpec::Define() { Describe("GetMetadataValuesForFaceAsStrings", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); // Two disconnected triangles. @@ -1262,13 +1263,14 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(positionData)); - pMeshFeatures = &pPrimitive->addExtension(); - pModelMetadata = - &model.addExtension(); + pMeshFeatures = + &pPrimitive->addExtension(); + pModelMetadata = &model.addExtension< + CesiumGltf::ExtensionModelExtStructuralMetadata>(); std::string className = "testClass"; pModelMetadata->schema.emplace(); @@ -1289,7 +1291,7 @@ void FCesiumMetadataPickingSpec::Define() { It("returns values for first feature ID set by default", [this]() { std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -1305,8 +1307,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::vector vec2Values{ @@ -1317,8 +1319,8 @@ void FCesiumMetadataPickingSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); pModelComponent->Metadata = From 358d779c25cbbd67009cf2d55ab16e120a3afe39 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 31 Oct 2024 13:34:47 -0400 Subject: [PATCH 66/68] Update CHANGES, remove Snowdon test --- CHANGES.md | 13 +++--- .../Private/Tests/Cesium3DTileset.spec.cpp | 45 ------------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e7a58d762..9a72c231f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ##### Additions :tada: - Added `CircumscribedGroundHeight` property to `CesiumSunSky`. It defaults to 0, which is consistent with the previous behavior. It can be set to a larger value (like 40) to avoid dark splotchy artifacts when zoomed out far from the globe in certain tilesets where geometry extends very far beyond the ellipsoid in the low-detail representation, such as Google Photorealistic 3D Tiles. +- Added a shared assets system that ensures external images referenced by different glTFs will only be loaded once per image. Previously, these images would be loaded again for each glTF that referenced them. This significantly reduces memory usage for tilesets that reuse the same textures. ##### Fixes :wrench: @@ -164,7 +165,7 @@ In addition to the above, this release updates [cesium-native](https://github.co ##### Additions :tada: - - Added support for multiple Cesium ion servers by creating `CesiumIonServer` data assets. +- Added support for multiple Cesium ion servers by creating `CesiumIonServer` data assets. In addition to the above, this release updates [cesium-native](https://github.com/CesiumGS/cesium-native) from v0.30.0 to v0.31.0. See the [changelog](https://github.com/CesiumGS/cesium-native/blob/main/CHANGES.md) for a complete list of changes in cesium-native. @@ -172,8 +173,8 @@ In addition to the above, this release updates [cesium-native](https://github.co ##### Additions :tada: - - Added support for styling with property textures in `EXT_structural_metadata`. - - Significantly improved tile download performance by adding `HttpThreadActiveFrameTimeInSeconds=0.001` to `Engine.ini`. This results in a major performance improvement for all tilesets, particularly Google Photorealistic 3D Tiles. +- Added support for styling with property textures in `EXT_structural_metadata`. +- Significantly improved tile download performance by adding `HttpThreadActiveFrameTimeInSeconds=0.001` to `Engine.ini`. This results in a major performance improvement for all tilesets, particularly Google Photorealistic 3D Tiles. - Added `HttpMaxConnectionsPerServer=40` to `Engine.ini`. By default, only 16 connections are allowed, which limits the performance when downloading tiles. ##### Fixes :wrench: @@ -193,7 +194,7 @@ This release no longer supports Unreal Engine v5.0. Unreal Engine v5.1, v5.2, or ##### Breaking Changes :mega: - Removed `FCesiumIntegerColor`, `FCesiumFloatColor`, `UCesiumFeatureTexturePropertyBlueprintLibrary::GetIntegerColorFromTextureCoordinates` and `UCesiumFeatureTexturePropertyBlueprintLibrary::GetFloatColorFromTextureCoordinates`. Check out the [upgrade guide](Documentation/upgrade-to-2.0-guide.md) for how retrieve metadata from property textures with the new API. -- Renamed `GetTextureCoordinateIndex` to `GetUnrealUVChannel` in both `UCesiumFeatureIdTextureBlueprintLibrary` and `UCesiumPropertyTexturePropertyBlueprintLibrary`. Contrary to what the documentation claimed, this function retrieved the index of the texture coordinate set in the *Unreal static mesh*, which is not necessarily equal to the texture coordinate set index in the *glTF primitive*. For the latter value, use `GetGltfTextureCoordinateSetIndex` instead. +- Renamed `GetTextureCoordinateIndex` to `GetUnrealUVChannel` in both `UCesiumFeatureIdTextureBlueprintLibrary` and `UCesiumPropertyTexturePropertyBlueprintLibrary`. Contrary to what the documentation claimed, this function retrieved the index of the texture coordinate set in the _Unreal static mesh_, which is not necessarily equal to the texture coordinate set index in the _glTF primitive_. For the latter value, use `GetGltfTextureCoordinateSetIndex` instead. - Removed the old "exclusion zones" feature, which has been deprecated since v1.11.0. Use `CesiumCartographicPolygon` or `CesiumTileExcluder` instead. ##### Additions :tada: @@ -342,7 +343,7 @@ In addition to the above, this release updates [cesium-native](https://github.co ##### Fixes :wrench: - Added a workaround for an apparent bug in Unreal Engine 5.1 that prevented collisions from working with Cesium3DTilesets. -- Fixed a bug that could cause the `AGlobeAwareDefaultPawn` / `DynamicPawn` to suddenly move to a very high height for one render frame just as it arrives at its destination during a flight. +- Fixed a bug that could cause the `AGlobeAwareDefaultPawn` / `DynamicPawn` to suddenly move to a very high height for one render frame just as it arrives at its destination during a flight. In addition to the above, this release updates [cesium-native](https://github.com/CesiumGS/cesium-native) from v0.25.0 to v0.25.1. See the [changelog](https://github.com/CesiumGS/cesium-native/blob/main/CHANGES.md) for a complete list of changes in cesium-native. @@ -405,7 +406,7 @@ This will be the _last_ release that supports Unreal Engine v4.27. Future versio ##### Additions :tada: -- The `FlyToAltitudeProfileCurve`, `FlyToProgressCurve`, `FlyToMaximumAltitudeCurve`, `FlyToDuration`, and `FlyToGranularityDegrees` properties of `GlobeAwareDefaultPawn` / `DynamicPawn` may now be read and written from Blueprints. +- The `FlyToAltitudeProfileCurve`, `FlyToProgressCurve`, `FlyToMaximumAltitudeCurve`, `FlyToDuration`, and `FlyToGranularityDegrees` properties of `GlobeAwareDefaultPawn` / `DynamicPawn` may now be read and written from Blueprints. - Added an option on `Cesium3DTileset` to ignore the `KHR_materials_unlit` extension entirely and use normal lighting and shadows. - Added `CreateNavCollision` property to `Cesium3DTileset`. When enabled, `CreateNavCollision` is called on the static meshes created for tiles. diff --git a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp index 79d6123eb..3b123524c 100644 --- a/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/Cesium3DTileset.spec.cpp @@ -87,51 +87,6 @@ bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) { TEST_SCREEN_HEIGHT); } -IMPLEMENT_SIMPLE_AUTOMATION_TEST( - FCesium3DTilesetSnowdonBenchmark, - "Cesium.Performance.3DTileset.SnowdonBenchmark", - EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter); - -static void setupForSnowdon(SceneGenerationContext& context) { - context.setCommonProperties( - FVector(-79.8867314431, 40.0223377722, 197.1008007424), - FVector(-293.823058, 6736.144397, 2730.501500), - FRotator(-13.400000, -87.799997, 0.000000), - 60.0f); - - context.sunSky->TimeZone = 5.0f; - context.sunSky->UpdateSun(); - - ACesium3DTileset* tileset = context.world->SpawnActor(); - tileset->SetTilesetSource(ETilesetSource::FromCesiumIon); - tileset->SetIonAssetID(2758251); - tileset->SetIonAccessToken(SceneGenerationContext::testIonToken); - - tileset->SetActorLabel(TEXT("Snowdon")); - tileset->SuspendUpdate = false; - tileset->LogSelectionStats = true; - context.tilesets.push_back(tileset); - - ADirectionalLight* Light = context.world->SpawnActor(); - Light->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270))); -} - -void snowdonPass( - SceneGenerationContext& context, - TestPass::TestingParameter parameter) {} - -bool FCesium3DTilesetSnowdonBenchmark::RunTest(const FString& Parameters) { - std::vector testPasses; - testPasses.push_back(TestPass{"Refresh Pass", snowdonPass, nullptr}); - - return RunLoadTest( - GetBeautifiedTestName(), - setupForSnowdon, - testPasses, - TEST_SCREEN_WIDTH, - TEST_SCREEN_HEIGHT); -} - } // namespace Cesium #endif From a6d7b3b7cfb7e18206a2b914216d9fbdea1f2419 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 31 Oct 2024 14:13:38 -0400 Subject: [PATCH 67/68] Remove Class.h include --- Source/CesiumRuntime/Public/CesiumPropertyTable.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/CesiumRuntime/Public/CesiumPropertyTable.h b/Source/CesiumRuntime/Public/CesiumPropertyTable.h index c2c1304de..5312f75e6 100644 --- a/Source/CesiumRuntime/Public/CesiumPropertyTable.h +++ b/Source/CesiumRuntime/Public/CesiumPropertyTable.h @@ -2,7 +2,6 @@ #pragma once -#include "CesiumGltf/Class.h" #include "CesiumMetadataValue.h" #include "CesiumPropertyTableProperty.h" #include "Kismet/BlueprintFunctionLibrary.h" From 28333a131baa9008991a4975e419f0db21b6ded5 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 1 Nov 2024 09:28:46 +1100 Subject: [PATCH 68/68] Eliminate another round of `using namespace CesiumGltf`. --- .../Private/CesiumGltfComponent.cpp | 318 +++++----- .../Private/CesiumGltfTextures.cpp | 70 ++- .../Tests/CesiumFeatureIdAttribute.spec.cpp | 25 +- .../Private/Tests/CesiumFeatureIdSet.spec.cpp | 96 +-- .../Tests/CesiumFeatureIdTexture.spec.cpp | 123 ++-- .../Private/Tests/CesiumGltfSpecUtility.cpp | 28 +- .../Tests/CesiumMetadataValue.spec.cpp | 13 +- .../Tests/CesiumPrimitiveFeatures.spec.cpp | 100 +-- .../Tests/CesiumPropertyArray.spec.cpp | 10 +- .../Tests/CesiumPropertyTable.spec.cpp | 101 +-- .../CesiumPropertyTableProperty.spec.cpp | 586 +++++++++--------- .../Tests/CesiumPropertyTexture.spec.cpp | 2 - .../CesiumPropertyTextureProperty.spec.cpp | 2 - .../Tests/CesiumTextureUtility.spec.cpp | 99 ++- .../Tests/UnrealMetadataConversions.spec.cpp | 2 - 15 files changed, 818 insertions(+), 757 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index f321d578d..b2781a59a 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -67,7 +67,6 @@ #include "ScopedTransaction.h" #endif -using namespace CesiumGltf; using namespace CesiumTextureUtility; using namespace CreateGltfOptions; using namespace LoadGltfResult; @@ -99,12 +98,13 @@ template struct IsAccessorView; template struct IsAccessorView : std::false_type {}; -template struct IsAccessorView> : std::true_type {}; +template +struct IsAccessorView> : std::true_type {}; template static uint32_t updateTextureCoordinates( - const Model& model, - const MeshPrimitive& primitive, + const CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, bool duplicateVertices, TArray& vertices, const TArray& indices, @@ -125,8 +125,8 @@ static uint32_t updateTextureCoordinates( } uint32_t updateTextureCoordinates( - const Model& model, - const MeshPrimitive& primitive, + const CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, bool duplicateVertices, TArray& vertices, const TArray& indices, @@ -148,8 +148,8 @@ uint32_t updateTextureCoordinates( size_t textureCoordinateIndex = gltfToUnrealTexCoordMap.size(); gltfToUnrealTexCoordMap[uvAccessorID] = textureCoordinateIndex; - AccessorView uvAccessor(model, uvAccessorID); - if (uvAccessor.status() != AccessorViewStatus::Valid) { + CesiumGltf::AccessorView uvAccessor(model, uvAccessorID); + if (uvAccessor.status() != CesiumGltf::AccessorViewStatus::Valid) { return 0; } @@ -324,18 +324,21 @@ BuildChaosTriangleMeshes( const TArray& vertexData, const TArray& indices); -static const Material defaultMaterial; -static const MaterialPBRMetallicRoughness defaultPbrMetallicRoughness; +static const CesiumGltf::Material defaultMaterial; +static const CesiumGltf::MaterialPBRMetallicRoughness + defaultPbrMetallicRoughness; struct ColorVisitor { bool duplicateVertices; TArray& StaticMeshBuildVertices; const TArray& indices; - bool operator()(AccessorView&& invalidView) { return false; } + bool operator()(CesiumGltf::AccessorView&& invalidView) { + return false; + } template bool operator()(TColorView&& colorView) { - if (colorView.status() != AccessorViewStatus::Valid) { + if (colorView.status() != CesiumGltf::AccessorViewStatus::Valid) { return false; } @@ -366,8 +369,9 @@ struct ColorVisitor { } template - static bool - convertColor(const AccessorTypes::VEC3& color, FColor& out) { + static bool convertColor( + const CesiumGltf::AccessorTypes::VEC3& color, + FColor& out) { out.A = 255; return convertElement(color.value[0], out.R) && convertElement(color.value[1], out.G) && @@ -375,8 +379,9 @@ struct ColorVisitor { } template - static bool - convertColor(const AccessorTypes::VEC4& color, FColor& out) { + static bool convertColor( + const CesiumGltf::AccessorTypes::VEC4& color, + FColor& out) { return convertElement(color.value[0], out.R) && convertElement(color.value[1], out.G) && convertElement(color.value[2], out.B) && @@ -432,8 +437,8 @@ static TUniquePtr loadTexture( } static void applyWaterMask( - Model& model, - const MeshPrimitive& primitive, + CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, LoadPrimitiveResult& primitiveResult) { // Initialize water mask if needed. auto onlyWaterIt = primitive.extras.find("OnlyWater"); @@ -452,7 +457,7 @@ static void applyWaterMask( waterMaskTextureIdIt->second.isInt64()) { int32_t waterMaskTextureId = static_cast( waterMaskTextureIdIt->second.getInt64OrDefault(-1)); - TextureInfo waterMaskInfo; + CesiumGltf::TextureInfo waterMaskInfo; waterMaskInfo.index = waterMaskTextureId; if (waterMaskTextureId >= 0 && waterMaskTextureId < model.textures.size()) { @@ -504,7 +509,7 @@ static bool hasMaterialTextureConflicts( const CesiumGltf::Material& material, int32_t imageIndex) { if (material.pbrMetallicRoughness) { - const std::optional& maybeBaseColorTexture = + const std::optional& maybeBaseColorTexture = material.pbrMetallicRoughness->baseColorTexture; if (maybeBaseColorTexture && textureUsesSpecifiedImage( model, @@ -513,8 +518,9 @@ static bool hasMaterialTextureConflicts( return true; } - const std::optional& maybeMetallicRoughnessTexture = - material.pbrMetallicRoughness->metallicRoughnessTexture; + const std::optional& + maybeMetallicRoughnessTexture = + material.pbrMetallicRoughness->metallicRoughnessTexture; if (maybeMetallicRoughnessTexture && textureUsesSpecifiedImage( model, @@ -554,8 +560,8 @@ static bool hasMaterialTextureConflicts( * without requiring UVs in the physics bodies. */ static void createTexCoordAccessorsForFeaturesMetadata( - const Model& model, - const MeshPrimitive& primitive, + const CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, const FCesiumPrimitiveFeatures& primitiveFeatures, const FCesiumPrimitiveMetadata& primitiveMetadata, const FCesiumModelMetadata& modelMetadata, @@ -626,8 +632,8 @@ static void createTexCoordAccessorsForFeaturesMetadata( * coordinates for attribute and implicit feature ID sets. */ static void updateTextureCoordinatesForFeaturesMetadata( - const Model& model, - const MeshPrimitive& primitive, + const CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, bool duplicateVertices, TArray& vertices, const TArray& indices, @@ -789,8 +795,8 @@ static void updateTextureCoordinatesForFeaturesMetadata( PRAGMA_DISABLE_DEPRECATION_WARNINGS static void updateTextureCoordinatesForMetadata_DEPRECATED( - const Model& model, - const MeshPrimitive& primitive, + const CesiumGltf::Model& model, + const CesiumGltf::MeshPrimitive& primitive, bool duplicateVertices, TArray& vertices, const TArray& indices, @@ -843,8 +849,8 @@ static void updateTextureCoordinatesForMetadata_DEPRECATED( } } - const ExtensionExtMeshFeatures* pFeatures = - primitive.getExtension(); + const CesiumGltf::ExtensionExtMeshFeatures* pFeatures = + primitive.getExtension(); if (pFeatures) { for (const CesiumEncodedMetadataUtility::EncodedFeatureIdAttribute& @@ -915,8 +921,8 @@ static void loadPrimitiveFeaturesMetadata( TArray& vertices, const TArray& indices) { - ExtensionExtMeshFeatures* pFeatures = - primitive.getExtension(); + CesiumGltf::ExtensionExtMeshFeatures* pFeatures = + primitive.getExtension(); if (pFeatures) { int32_t materialIndex = primitive.material; @@ -954,8 +960,9 @@ static void loadPrimitiveFeaturesMetadata( } } - const ExtensionMeshPrimitiveExtStructuralMetadata* pMetadata = - primitive.getExtension(); + const CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata* pMetadata = + primitive.getExtension< + CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata>(); const CreateGltfOptions::CreateModelOptions* pModelOptions = options.pMeshOptions->pNodeOptions->pModelOptions; @@ -1129,20 +1136,22 @@ static void loadPrimitive( LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, - const Accessor& positionAccessor, - const AccessorView& positionView, + const CesiumGltf::Accessor& positionAccessor, + const CesiumGltf::AccessorView& positionView, const TIndexAccessor& indicesView, const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; - MeshPrimitive& primitive = mesh.primitives[options.primitiveIndex]; + CesiumGltf::Model& model = + *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; + CesiumGltf::Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex]; + CesiumGltf::MeshPrimitive& primitive = + mesh.primitives[options.primitiveIndex]; - if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && - primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP && - primitive.mode != MeshPrimitive::Mode::POINTS) { + if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::TRIANGLES && + primitive.mode != CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP && + primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS) { // TODO: add support for other primitive types. UE_LOG( LogCesium, @@ -1163,7 +1172,9 @@ static void loadPrimitive( auto meshIt = std::find_if( model.meshes.begin(), model.meshes.end(), - [&mesh](const Mesh& candidate) { return &candidate == &mesh; }); + [&mesh](const CesiumGltf::Mesh& candidate) { + return &candidate == &mesh; + }); if (meshIt != model.meshes.end()) { int64_t meshIndex = meshIt - model.meshes.begin(); name += " mesh " + std::to_string(meshIndex); @@ -1172,7 +1183,7 @@ static void loadPrimitive( auto primitiveIt = std::find_if( mesh.primitives.begin(), mesh.primitives.end(), - [&primitive](const MeshPrimitive& candidate) { + [&primitive](const CesiumGltf::MeshPrimitive& candidate) { return &candidate == &primitive; }); if (primitiveIt != mesh.primitives.end()) { @@ -1182,7 +1193,7 @@ static void loadPrimitive( primitiveResult.name = name; - if (positionView.status() != AccessorViewStatus::Valid) { + if (positionView.status() != CesiumGltf::AccessorViewStatus::Valid) { UE_LOG( LogCesium, Warning, @@ -1192,7 +1203,7 @@ static void loadPrimitive( } if constexpr (IsAccessorView::value) { - if (indicesView.status() != AccessorViewStatus::Valid) { + if (indicesView.status() != CesiumGltf::AccessorViewStatus::Valid) { UE_LOG( LogCesium, Warning, @@ -1203,12 +1214,14 @@ static void loadPrimitive( } auto normalAccessorIt = primitive.attributes.find("NORMAL"); - AccessorView normalAccessor; + CesiumGltf::AccessorView normalAccessor; bool hasNormals = false; if (normalAccessorIt != primitive.attributes.end()) { int normalAccessorID = normalAccessorIt->second; - normalAccessor = AccessorView(model, normalAccessorID); - hasNormals = normalAccessor.status() == AccessorViewStatus::Valid; + normalAccessor = + CesiumGltf::AccessorView(model, normalAccessorID); + hasNormals = + normalAccessor.status() == CesiumGltf::AccessorViewStatus::Valid; if (!hasNormals) { UE_LOG( LogCesium, @@ -1220,7 +1233,7 @@ static void loadPrimitive( } int materialID = primitive.material; - const Material& material = + const CesiumGltf::Material& material = materialID >= 0 && materialID < model.materials.size() ? model.materials[materialID] : defaultMaterial; @@ -1228,31 +1241,34 @@ static void loadPrimitive( primitiveResult.materialIndex = materialID; primitiveResult.isUnlit = - material.hasExtension() && + material.hasExtension() && !options.pMeshOptions->pNodeOptions->pModelOptions ->ignoreKhrMaterialsUnlit; // We can't calculate flat normals for points or lines, so we have to force // them to be unlit if no normals are specified. Otherwise this causes a // crash when attempting to calculate flat normals. - bool isTriangles = primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_FAN || - primitive.mode == MeshPrimitive::Mode::TRIANGLE_STRIP; + bool isTriangles = + primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES || + primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN || + primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP; if (!isTriangles && !hasNormals) { primitiveResult.isUnlit = true; } - const MaterialPBRMetallicRoughness& pbrMetallicRoughness = + const CesiumGltf::MaterialPBRMetallicRoughness& pbrMetallicRoughness = material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() : defaultPbrMetallicRoughness; bool hasNormalMap = material.normalTexture.has_value(); if (hasNormalMap) { - const CesiumGltf::Texture* pTexture = - Model::getSafe(&model.textures, material.normalTexture->index); - hasNormalMap = pTexture != nullptr && - Model::getSafe(&model.images, pTexture->source) != nullptr; + const CesiumGltf::Texture* pTexture = CesiumGltf::Model::getSafe( + &model.textures, + material.normalTexture->index); + hasNormalMap = + pTexture != nullptr && + CesiumGltf::Model::getSafe(&model.images, pTexture->source) != nullptr; } bool needsTangents = @@ -1261,11 +1277,13 @@ static void loadPrimitive( bool hasTangents = false; auto tangentAccessorIt = primitive.attributes.find("TANGENT"); - AccessorView tangentAccessor; + CesiumGltf::AccessorView tangentAccessor; if (tangentAccessorIt != primitive.attributes.end()) { int tangentAccessorID = tangentAccessorIt->second; - tangentAccessor = AccessorView(model, tangentAccessorID); - hasTangents = tangentAccessor.status() == AccessorViewStatus::Valid; + tangentAccessor = + CesiumGltf::AccessorView(model, tangentAccessorID); + hasTangents = + tangentAccessor.status() == CesiumGltf::AccessorViewStatus::Valid; if (!hasTangents) { UE_LOG( LogCesium, @@ -1328,8 +1346,8 @@ static void loadPrimitive( } TArray indices; - if (primitive.mode == MeshPrimitive::Mode::TRIANGLES || - primitive.mode == MeshPrimitive::Mode::POINTS) { + if (primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES || + primitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices) indices.SetNum(static_cast::SizeType>(indicesView.size())); @@ -1363,8 +1381,8 @@ static void loadPrimitive( bool needToGenerateFlatNormals = normalsAreRequired && !hasNormals; bool needToGenerateTangents = needsTangents && !hasTangents; bool duplicateVertices = needToGenerateFlatNormals || needToGenerateTangents; - duplicateVertices = - duplicateVertices && primitive.mode != MeshPrimitive::Mode::POINTS; + duplicateVertices = duplicateVertices && + primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS; TArray StaticMeshBuildVertices; StaticMeshBuildVertices.SetNum( @@ -1672,7 +1690,8 @@ static void loadPrimitive( section.FirstIndex = 0; section.MinVertexIndex = 0; section.MaxVertexIndex = StaticMeshBuildVertices.Num() - 1; - section.bEnableCollision = primitive.mode != MeshPrimitive::Mode::POINTS; + section.bEnableCollision = + primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS; section.bCastShadow = true; section.MaterialIndex = 0; @@ -1703,7 +1722,7 @@ static void loadPrimitive( primitiveResult.transform = transform * yInvertMatrix * scaleMatrix; - if (primitive.mode != MeshPrimitive::Mode::POINTS && + if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS && options.pMeshOptions->pNodeOptions->pModelOptions->createPhysicsMeshes) { if (StaticMeshBuildVertices.Num() != 0 && indices.Num() != 0) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook) @@ -1723,18 +1742,20 @@ static void loadIndexedPrimitive( LoadPrimitiveResult& primitiveResult, const glm::dmat4x4& transform, const CreatePrimitiveOptions& options, - const Accessor& positionAccessor, - const AccessorView& positionView, + const CesiumGltf::Accessor& positionAccessor, + const CesiumGltf::AccessorView& positionView, const CesiumGeospatial::Ellipsoid& ellipsoid) { - const Model& model = + const CesiumGltf::Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - const MeshPrimitive& primitive = model.meshes[options.pMeshOptions->meshIndex] - .primitives[options.primitiveIndex]; + const CesiumGltf::MeshPrimitive& primitive = + model.meshes[options.pMeshOptions->meshIndex] + .primitives[options.primitiveIndex]; - const Accessor& indexAccessorGltf = model.accessors[primitive.indices]; + const CesiumGltf::Accessor& indexAccessorGltf = + model.accessors[primitive.indices]; if (indexAccessorGltf.componentType == - Accessor::ComponentType::UNSIGNED_BYTE) { - AccessorView indexAccessor(model, primitive.indices); + CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE) { + CesiumGltf::AccessorView indexAccessor(model, primitive.indices); loadPrimitive( primitiveResult, transform, @@ -1746,8 +1767,8 @@ static void loadIndexedPrimitive( primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == - Accessor::ComponentType::UNSIGNED_SHORT) { - AccessorView indexAccessor(model, primitive.indices); + CesiumGltf::Accessor::ComponentType::UNSIGNED_SHORT) { + CesiumGltf::AccessorView indexAccessor(model, primitive.indices); loadPrimitive( primitiveResult, transform, @@ -1759,8 +1780,8 @@ static void loadIndexedPrimitive( primitiveResult.IndexAccessor = indexAccessor; } else if ( indexAccessorGltf.componentType == - Accessor::ComponentType::UNSIGNED_INT) { - AccessorView indexAccessor(model, primitive.indices); + CesiumGltf::Accessor::ComponentType::UNSIGNED_INT) { + CesiumGltf::AccessorView indexAccessor(model, primitive.indices); loadPrimitive( primitiveResult, transform, @@ -1787,10 +1808,11 @@ static void loadPrimitive( const CesiumGeospatial::Ellipsoid& ellipsoid) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive) - const Model& model = + const CesiumGltf::Model& model = *options.pMeshOptions->pNodeOptions->pModelOptions->pModel; - const MeshPrimitive& primitive = model.meshes[options.pMeshOptions->meshIndex] - .primitives[options.primitiveIndex]; + const CesiumGltf::MeshPrimitive& primitive = + model.meshes[options.pMeshOptions->meshIndex] + .primitives[options.primitiveIndex]; auto positionAccessorIt = primitive.attributes.find("POSITION"); if (positionAccessorIt == primitive.attributes.end()) { @@ -1799,14 +1821,16 @@ static void loadPrimitive( } int positionAccessorID = positionAccessorIt->second; - const Accessor* pPositionAccessor = - Model::getSafe(&model.accessors, positionAccessorID); + const CesiumGltf::Accessor* pPositionAccessor = + CesiumGltf::Model::getSafe(&model.accessors, positionAccessorID); if (!pPositionAccessor) { // Position accessor does not exist, so ignore this primitive. return; } - AccessorView positionView(model, *pPositionAccessor); + CesiumGltf::AccessorView positionView( + model, + *pPositionAccessor); if (primitive.indices < 0 || primitive.indices >= model.accessors.size()) { std::vector syntheticIndexBuffer(positionView.size()); @@ -1842,8 +1866,8 @@ static void loadMesh( TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh) - Model& model = *options.pNodeOptions->pModelOptions->pModel; - Mesh& mesh = model.meshes[options.meshIndex]; + CesiumGltf::Model& model = *options.pNodeOptions->pModelOptions->pModel; + CesiumGltf::Mesh& mesh = model.meshes[options.meshIndex]; result = LoadMeshResult(); result->primitiveResults.reserve(mesh.primitives.size()); @@ -1882,19 +1906,20 @@ inline constexpr bool is_int_quat_v = is_int_quat::value; } // namespace static void loadInstancingData( - const Model& model, + const CesiumGltf::Model& model, LoadNodeResult& result, - const ExtensionExtMeshGpuInstancing* pGpuInstancing) { - auto getInstanceAccessor = [&](const char* name) -> const Accessor* { + const CesiumGltf::ExtensionExtMeshGpuInstancing* pGpuInstancing) { + auto getInstanceAccessor = + [&](const char* name) -> const CesiumGltf::Accessor* { if (auto accessorItr = pGpuInstancing->attributes.find(name); accessorItr != pGpuInstancing->attributes.end()) { - return Model::getSafe(&model.accessors, accessorItr->second); + return CesiumGltf::Model::getSafe(&model.accessors, accessorItr->second); } return nullptr; }; - const Accessor* translations = getInstanceAccessor("TRANSLATION"); - const Accessor* rotations = getInstanceAccessor("ROTATION"); - const Accessor* scales = getInstanceAccessor("SCALE"); + const CesiumGltf::Accessor* translations = getInstanceAccessor("TRANSLATION"); + const CesiumGltf::Accessor* rotations = getInstanceAccessor("ROTATION"); + const CesiumGltf::Accessor* scales = getInstanceAccessor("SCALE"); int64_t count = 0; if (translations) { @@ -1945,8 +1970,10 @@ static void loadInstancingData( // argument by the new transform. E.g., translate() does *not* translate the // matrix. if (translations) { - AccessorView translationAccessor(model, *translations); - if (translationAccessor.status() == AccessorViewStatus::Valid) { + CesiumGltf::AccessorView translationAccessor( + model, + *translations); + if (translationAccessor.status() == CesiumGltf::AccessorViewStatus::Valid) { for (int64_t i = 0; i < count; ++i) { glm::dvec3 translation(translationAccessor[i]); instanceTransforms[i] = glm::translate( @@ -1986,7 +2013,7 @@ static void loadInstancingData( }); } if (scales) { - AccessorView scaleAccessor(model, *scales); + CesiumGltf::AccessorView scaleAccessor(model, *scales); for (int64_t i = 0; i < count; ++i) { glm::dvec3 scaleFactors(scaleAccessor[i]); instanceTransforms[i] = glm::scale(instanceTransforms[i], scaleFactors); @@ -2029,8 +2056,8 @@ static void loadNode( 0.0, 1.0}; - Model& model = *options.pModelOptions->pModel; - const Node& node = *options.pNode; + CesiumGltf::Model& model = *options.pModelOptions->pModel; + const CesiumGltf::Node& node = *options.pNode; LoadNodeResult& result = loadNodeResults.emplace_back(); @@ -2083,7 +2110,7 @@ static void loadNode( int meshId = node.mesh; if (meshId >= 0 && meshId < model.meshes.size()) { if (const auto* pGpuInstancingExtension = - node.getExtension()) { + node.getExtension()) { loadInstancingData(model, result, pGpuInstancingExtension); } CreateMeshOptions meshOptions = {&options, &result, meshId}; @@ -2119,7 +2146,9 @@ namespace { * @param model The glTF model * @param rootTransform The matrix that will be multiplied with the transform */ -void applyGltfUpAxisTransform(const Model& model, glm::dmat4x4& rootTransform) { +void applyGltfUpAxisTransform( + const CesiumGltf::Model& model, + glm::dmat4x4& rootTransform) { auto gltfUpAxisIt = model.extras.find("gltfUpAxis"); if (gltfUpAxisIt == model.extras.end()) { @@ -2150,10 +2179,10 @@ void applyGltfUpAxisTransform(const Model& model, glm::dmat4x4& rootTransform) { static void loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { - Model& model = *options.pModel; + CesiumGltf::Model& model = *options.pModel; - ExtensionModelExtStructuralMetadata* pModelMetadata = - model.getExtension(); + CesiumGltf::ExtensionModelExtStructuralMetadata* pModelMetadata = + model.getExtension(); if (!pModelMetadata) { return; } @@ -2166,9 +2195,9 @@ loadModelMetadata(LoadModelResult& result, const CreateModelOptions& options) { CesiumGltf::Mesh& /*mesh*/, CesiumGltf::MeshPrimitive& primitive, const glm::dmat4& /*nodeTransform*/) { - const ExtensionMeshPrimitiveExtStructuralMetadata* pPrimitiveMetadata = - primitive - .getExtension(); + const CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata* + pPrimitiveMetadata = primitive.getExtension< + CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata>(); if (!pPrimitiveMetadata) { return; } @@ -2257,7 +2286,7 @@ loadModelAnyThreadPart( glm::dmat4x4 rootTransform = transform; - Model& model = *options.pModel; + CesiumGltf::Model& model = *options.pModel; { rootTransform = CesiumGltfContent::GltfUtilities::applyRtcCenter( @@ -2268,7 +2297,7 @@ loadModelAnyThreadPart( if (model.scene >= 0 && model.scene < model.scenes.size()) { // Show the default scene - const Scene& defaultScene = model.scenes[model.scene]; + const CesiumGltf::Scene& defaultScene = model.scenes[model.scene]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { &options, @@ -2282,7 +2311,7 @@ loadModelAnyThreadPart( } } else if (model.scenes.size() > 0) { // There's no default, so show the first scene - const Scene& defaultScene = model.scenes[0]; + const CesiumGltf::Scene& defaultScene = model.scenes[0]; for (int nodeId : defaultScene.nodes) { CreateNodeOptions nodeOptions = { &options, @@ -2358,8 +2387,8 @@ bool applyTexture( static void SetGltfParameterValues( CesiumGltf::Model& model, LoadPrimitiveResult& loadResult, - const Material& material, - const MaterialPBRMetallicRoughness& pbr, + const CesiumGltf::Material& material, + const CesiumGltf::MaterialPBRMetallicRoughness& pbr, UMaterialInstanceDynamic* pMaterial, EMaterialParameterAssociation association, int32 index) { @@ -2429,16 +2458,19 @@ static void SetGltfParameterValues( FMaterialParameterInfo("occlusionTexture", association, index), loadResult.occlusionTexture.Get()); - KhrTextureTransform textureTransform; + CesiumGltf::KhrTextureTransform textureTransform; FLinearColor baseColorMetallicRoughnessRotation(0.0f, 1.0f, 0.0f, 1.0f); - const ExtensionKhrTextureTransform* pBaseColorTextureTransform = + const CesiumGltf::ExtensionKhrTextureTransform* pBaseColorTextureTransform = pbr.baseColorTexture - ? pbr.baseColorTexture->getExtension() + ? pbr.baseColorTexture + ->getExtension() : nullptr; if (pBaseColorTextureTransform) { - textureTransform = KhrTextureTransform(*pBaseColorTextureTransform); - if (textureTransform.status() == KhrTextureTransformStatus::Valid) { + textureTransform = + CesiumGltf::KhrTextureTransform(*pBaseColorTextureTransform); + if (textureTransform.status() == + CesiumGltf::KhrTextureTransformStatus::Valid) { const glm::dvec2& scale = textureTransform.scale(); const glm::dvec2& offset = textureTransform.offset(); pMaterial->SetVectorParameterValueByInfo( @@ -2452,15 +2484,18 @@ static void SetGltfParameterValues( } } - const ExtensionKhrTextureTransform* pMetallicRoughnessTextureTransform = - pbr.metallicRoughnessTexture - ? pbr.metallicRoughnessTexture - ->getExtension() - : nullptr; + const CesiumGltf::ExtensionKhrTextureTransform* + pMetallicRoughnessTextureTransform = + pbr.metallicRoughnessTexture + ? pbr.metallicRoughnessTexture + ->getExtension() + : nullptr; if (pMetallicRoughnessTextureTransform) { - textureTransform = KhrTextureTransform(*pMetallicRoughnessTextureTransform); - if (textureTransform.status() == KhrTextureTransformStatus::Valid) { + textureTransform = + CesiumGltf::KhrTextureTransform(*pMetallicRoughnessTextureTransform); + if (textureTransform.status() == + CesiumGltf::KhrTextureTransformStatus::Valid) { const glm::dvec2& scale = textureTransform.scale(); const glm::dvec2& offset = textureTransform.offset(); pMaterial->SetVectorParameterValueByInfo( @@ -2488,14 +2523,15 @@ static void SetGltfParameterValues( FLinearColor emissiveNormalRotation(0.0f, 1.0f, 0.0f, 1.0f); - const ExtensionKhrTextureTransform* pEmissiveTextureTransform = + const CesiumGltf::ExtensionKhrTextureTransform* pEmissiveTextureTransform = material.emissiveTexture ? material.emissiveTexture - ->getExtension() + ->getExtension() : nullptr; if (pEmissiveTextureTransform) { - textureTransform = KhrTextureTransform(*pEmissiveTextureTransform); + textureTransform = + CesiumGltf::KhrTextureTransform(*pEmissiveTextureTransform); const glm::dvec2& scale = textureTransform.scale(); const glm::dvec2& offset = textureTransform.offset(); pMaterial->SetVectorParameterValueByInfo( @@ -2508,13 +2544,15 @@ static void SetGltfParameterValues( emissiveNormalRotation.G = rotationSineCosine[1]; } - const ExtensionKhrTextureTransform* pNormalTextureTransform = + const CesiumGltf::ExtensionKhrTextureTransform* pNormalTextureTransform = material.normalTexture - ? material.normalTexture->getExtension() + ? material.normalTexture + ->getExtension() : nullptr; if (pNormalTextureTransform) { - textureTransform = KhrTextureTransform(*pNormalTextureTransform); + textureTransform = + CesiumGltf::KhrTextureTransform(*pNormalTextureTransform); const glm::dvec2& scale = textureTransform.scale(); const glm::dvec2& offset = textureTransform.offset(); pMaterial->SetVectorParameterValueByInfo( @@ -2532,14 +2570,14 @@ static void SetGltfParameterValues( emissiveNormalRotation); } - const ExtensionKhrTextureTransform* pOcclusionTransform = + const CesiumGltf::ExtensionKhrTextureTransform* pOcclusionTransform = material.occlusionTexture ? material.occlusionTexture - ->getExtension() + ->getExtension() : nullptr; if (pOcclusionTransform) { - textureTransform = KhrTextureTransform(*pOcclusionTransform); + textureTransform = CesiumGltf::KhrTextureTransform(*pOcclusionTransform); const glm::dvec2& scale = textureTransform.scale(); const glm::dvec2& offset = textureTransform.offset(); pMaterial->SetVectorParameterValueByInfo( @@ -2847,12 +2885,12 @@ static void loadPrimitiveGameThreadPart( const Cesium3DTilesSelection::BoundingVolume& boundingVolume = tile.getContentBoundingVolume().value_or(tile.getBoundingVolume()); - MeshPrimitive& meshPrimitive = + CesiumGltf::MeshPrimitive& meshPrimitive = model.meshes[loadResult.meshIndex].primitives[loadResult.primitiveIndex]; UStaticMeshComponent* pMesh = nullptr; ICesiumPrimitive* pCesiumPrimitive = nullptr; - if (meshPrimitive.mode == MeshPrimitive::Mode::POINTS) { + if (meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) { UCesiumGltfPointsComponent* pPointMesh = NewObject(pGltf, componentName); pPointMesh->UsesAdditiveRefinement = @@ -2916,11 +2954,11 @@ static void loadPrimitiveGameThreadPart( pStaticMesh->SetRenderData(std::move(loadResult.RenderData)); } - const Material& material = loadResult.materialIndex != -1 - ? model.materials[loadResult.materialIndex] - : defaultMaterial; + const CesiumGltf::Material& material = + loadResult.materialIndex != -1 ? model.materials[loadResult.materialIndex] + : defaultMaterial; - const MaterialPBRMetallicRoughness& pbr = + const CesiumGltf::MaterialPBRMetallicRoughness& pbr = material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value() : defaultPbrMetallicRoughness; diff --git a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp index 2d94da1a4..d0787ccc6 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfTextures.cpp @@ -11,12 +11,13 @@ #include using namespace CesiumAsync; -using namespace CesiumGltf; namespace { // Determines if a glTF primitive is usable for our purposes. -bool isValidPrimitive(const Model& gltf, const MeshPrimitive& primitive); +bool isValidPrimitive( + const CesiumGltf::Model& gltf, + const CesiumGltf::MeshPrimitive& primitive); // Determines if an Accessor's componentType is valid for an index buffer. bool isSupportedIndexComponentType(int32_t componentType); @@ -25,13 +26,15 @@ bool isSupportedIndexComponentType(int32_t componentType); bool isSupportedPrimitiveMode(int32_t primitiveMode); // Determines if the given texture uses mipmaps. -bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture); +bool doesTextureUseMipmaps( + const CesiumGltf::Model& gltf, + const CesiumGltf::Texture& texture); // Creates a single texture in the load thread. SharedFuture createTextureInLoadThread( const AsyncSystem& asyncSystem, - Model& gltf, - TextureInfo& textureInfo, + CesiumGltf::Model& gltf, + CesiumGltf::TextureInfo& textureInfo, bool sRGB, const std::vector& imageNeedsMipmaps); @@ -44,7 +47,7 @@ SharedFuture createTextureInLoadThread( // requires mipmaps. An image requires mipmaps if any of its textures have a // sampler that will use them. std::vector imageNeedsMipmaps(model.images.size(), false); - for (const Texture& texture : model.textures) { + for (const CesiumGltf::Texture& texture : model.textures) { int32_t imageIndex = texture.source; if (imageIndex < 0 || imageIndex >= model.images.size()) { continue; @@ -60,17 +63,17 @@ SharedFuture createTextureInLoadThread( model.forEachPrimitiveInScene( -1, [&imageNeedsMipmaps, &asyncSystem, &futures]( - Model& gltf, - Node& node, - Mesh& mesh, - MeshPrimitive& primitive, + CesiumGltf::Model& gltf, + CesiumGltf::Node& node, + CesiumGltf::Mesh& mesh, + CesiumGltf::MeshPrimitive& primitive, const glm::dmat4& transform) { if (!isValidPrimitive(gltf, primitive)) { return; } - Material* pMaterial = - Model::getSafe(&gltf.materials, primitive.material); + CesiumGltf::Material* pMaterial = + CesiumGltf::Model::getSafe(&gltf.materials, primitive.material); if (!pMaterial) { // A primitive using the default material will not have any textures. return; @@ -134,7 +137,7 @@ SharedFuture createTextureInLoadThread( waterMaskTextureIdIt->second.isInt64()) { int32_t waterMaskTextureId = static_cast( waterMaskTextureIdIt->second.getInt64OrDefault(-1)); - TextureInfo waterMaskInfo; + CesiumGltf::TextureInfo waterMaskInfo; waterMaskInfo.index = waterMaskTextureId; if (waterMaskTextureId >= 0 && waterMaskTextureId < gltf.textures.size()) { @@ -156,15 +159,15 @@ SharedFuture createTextureInLoadThread( namespace { bool isSupportedIndexComponentType(int32_t componentType) { - return componentType == Accessor::ComponentType::UNSIGNED_BYTE || - componentType == Accessor::ComponentType::UNSIGNED_SHORT || - componentType == Accessor::ComponentType::UNSIGNED_INT; + return componentType == CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE || + componentType == CesiumGltf::Accessor::ComponentType::UNSIGNED_SHORT || + componentType == CesiumGltf::Accessor::ComponentType::UNSIGNED_INT; } bool isSupportedPrimitiveMode(int32_t primitiveMode) { - return primitiveMode == MeshPrimitive::Mode::TRIANGLES || - primitiveMode == MeshPrimitive::Mode::TRIANGLE_STRIP || - primitiveMode == MeshPrimitive::Mode::POINTS; + return primitiveMode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES || + primitiveMode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP || + primitiveMode == CesiumGltf::MeshPrimitive::Mode::POINTS; } // Determines if a glTF primitive is usable for our purposes. @@ -177,21 +180,23 @@ bool isValidPrimitive( } auto positionAccessorIt = - primitive.attributes.find(VertexAttributeSemantics::POSITION); + primitive.attributes.find(CesiumGltf::VertexAttributeSemantics::POSITION); if (positionAccessorIt == primitive.attributes.end()) { // This primitive doesn't have a POSITION semantic, so it's not valid. return false; } - AccessorView positionView(gltf, positionAccessorIt->second); - if (positionView.status() != AccessorViewStatus::Valid) { + CesiumGltf::AccessorView positionView( + gltf, + positionAccessorIt->second); + if (positionView.status() != CesiumGltf::AccessorViewStatus::Valid) { // This primitive's POSITION accessor is invalid, so the primitive is not // valid. return false; } - const Accessor* pIndexAccessor = - Model::getSafe(&gltf.accessors, primitive.indices); + const CesiumGltf::Accessor* pIndexAccessor = + CesiumGltf::Model::getSafe(&gltf.accessors, primitive.indices); if (pIndexAccessor && !isSupportedIndexComponentType(pIndexAccessor->componentType)) { // This primitive's indices are not a supported type, so the primitive is @@ -202,8 +207,11 @@ bool isValidPrimitive( return true; } -bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture) { - const Sampler& sampler = Model::getSafe(gltf.samplers, texture.sampler); +bool doesTextureUseMipmaps( + const CesiumGltf::Model& gltf, + const CesiumGltf::Texture& texture) { + const CesiumGltf::Sampler& sampler = + CesiumGltf::Model::getSafe(gltf.samplers, texture.sampler); switch (sampler.minFilter.value_or( CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR)) { @@ -219,15 +227,17 @@ bool doesTextureUseMipmaps(const Model& gltf, const Texture& texture) { SharedFuture createTextureInLoadThread( const AsyncSystem& asyncSystem, - Model& gltf, - TextureInfo& textureInfo, + CesiumGltf::Model& gltf, + CesiumGltf::TextureInfo& textureInfo, bool sRGB, const std::vector& imageNeedsMipmaps) { - Texture* pTexture = Model::getSafe(&gltf.textures, textureInfo.index); + CesiumGltf::Texture* pTexture = + CesiumGltf::Model::getSafe(&gltf.textures, textureInfo.index); if (pTexture == nullptr) return asyncSystem.createResolvedFuture().share(); - Image* pImage = Model::getSafe(&gltf.images, pTexture->source); + CesiumGltf::Image* pImage = + CesiumGltf::Model::getSafe(&gltf.images, pTexture->source); if (pImage == nullptr) return asyncSystem.createResolvedFuture().share(); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp index c2b283985..4cb47a251 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp @@ -5,22 +5,20 @@ #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumFeatureIdAttributeSpec, "Cesium.Unit.FeatureIdAttribute", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -MeshPrimitive* pPrimitive; +CesiumGltf::Model model; +CesiumGltf::MeshPrimitive* pPrimitive; END_DEFINE_SPEC(FCesiumFeatureIdAttributeSpec) void FCesiumFeatureIdAttributeSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -76,9 +74,10 @@ void FCesiumFeatureIdAttributeSpec::Define() { It("constructs invalid instance for attribute with invalid accessor", [this]() { - Accessor& accessor = model.accessors.emplace_back(); - accessor.type = AccessorSpec::Type::VEC2; - accessor.componentType = AccessorSpec::ComponentType::FLOAT; + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); + accessor.type = CesiumGltf::AccessorSpec::Type::VEC2; + accessor.componentType = + CesiumGltf::AccessorSpec::ComponentType::FLOAT; const int64 attributeIndex = 0; pPrimitive->attributes.insert({"_FEATURE_ID_0", 0}); @@ -127,8 +126,8 @@ void FCesiumFeatureIdAttributeSpec::Define() { Describe("GetVertexCount", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -184,8 +183,8 @@ void FCesiumFeatureIdAttributeSpec::Define() { Describe("GetFeatureIDForVertex", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp index e44678a02..881fe1d0c 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp @@ -7,31 +7,29 @@ #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumFeatureIdSetSpec, "Cesium.Unit.FeatureIdSet", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -MeshPrimitive* pPrimitive; +CesiumGltf::Model model; +CesiumGltf::MeshPrimitive* pPrimitive; TObjectPtr pPrimitiveComponent; END_DEFINE_SPEC(FCesiumFeatureIdSetSpec) void FCesiumFeatureIdSetSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pPrimitive->addExtension(); + pPrimitive->addExtension(); }); It("constructs from empty feature ID set", [this]() { // This is technically disallowed by the spec, but just make sure it's // handled reasonably. - FeatureId featureId; + CesiumGltf::FeatureId featureId; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( @@ -46,7 +44,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("constructs implicit feature ID set", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); @@ -64,7 +62,7 @@ void FCesiumFeatureIdSetSpec::Define() { It("constructs set with feature ID attribute", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureID = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -91,7 +89,7 @@ void FCesiumFeatureIdSetSpec::Define() { glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -114,7 +112,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("constructs with null feature ID", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; featureId.nullFeatureId = 0; @@ -135,7 +133,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("constructs with property table index", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; featureId.propertyTable = 1; @@ -159,13 +157,13 @@ void FCesiumFeatureIdSetSpec::Define() { Describe("GetAsFeatureIDAttribute", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns empty instance for non-attribute feature ID set", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); @@ -183,7 +181,7 @@ void FCesiumFeatureIdSetSpec::Define() { It("returns valid instance for attribute feature ID set", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureID = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -208,13 +206,13 @@ void FCesiumFeatureIdSetSpec::Define() { Describe("GetAsFeatureIDTexture", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns empty instance for non-texture feature ID set", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); @@ -231,7 +229,7 @@ void FCesiumFeatureIdSetSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::ErrorUninitialized); + CesiumGltf::FeatureIdTextureViewStatus::ErrorUninitialized); }); It("returns valid instance for texture feature ID set", [this]() { @@ -242,7 +240,7 @@ void FCesiumFeatureIdSetSpec::Define() { glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -266,14 +264,14 @@ void FCesiumFeatureIdSetSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::Valid); + CesiumGltf::FeatureIdTextureViewStatus::Valid); }); }); Describe("GetFeatureIDForVertex", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -288,7 +286,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("returns -1 for out of bounds index", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); @@ -307,7 +305,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("returns correct value for implicit set", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); @@ -324,7 +322,7 @@ void FCesiumFeatureIdSetSpec::Define() { It("returns correct value for attribute set", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureID = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -350,7 +348,7 @@ void FCesiumFeatureIdSetSpec::Define() { glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; - FeatureId& featureID = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -374,8 +372,8 @@ void FCesiumFeatureIdSetSpec::Define() { Describe("GetFeatureIDFromHit", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; pPrimitiveComponent = NewObject(); @@ -394,8 +392,8 @@ void FCesiumFeatureIdSetSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, positions); }); @@ -410,7 +408,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("returns -1 for invalid hit component", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 6; CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView( @@ -446,7 +444,7 @@ void FCesiumFeatureIdSetSpec::Define() { glm::vec2(0, 1), glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureID = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -461,7 +459,7 @@ void FCesiumFeatureIdSetSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); @@ -489,7 +487,7 @@ void FCesiumFeatureIdSetSpec::Define() { }); It("returns correct value for implicit set", [this]() { - FeatureId featureId; + CesiumGltf::FeatureId featureId; featureId.featureCount = 6; CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); @@ -526,7 +524,7 @@ void FCesiumFeatureIdSetSpec::Define() { static_cast(model.accessors.size() - 1); const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureId = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -565,8 +563,8 @@ void FCesiumFeatureIdSetSpec::Define() { Describe("Deprecated", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -574,7 +572,7 @@ void FCesiumFeatureIdSetSpec::Define() { [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; - FeatureId& featureID = AddFeatureIDsAsAttributeToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, @@ -584,9 +582,10 @@ void FCesiumFeatureIdSetSpec::Define() { const std::string expectedName = "PropertyTableName"; - ExtensionModelExtStructuralMetadata& metadataExtension = - model.addExtension(); - PropertyTable& propertyTable = + CesiumGltf::ExtensionModelExtStructuralMetadata& metadataExtension = + model.addExtension< + CesiumGltf::ExtensionModelExtStructuralMetadata>(); + CesiumGltf::PropertyTable& propertyTable = metadataExtension.propertyTables.emplace_back(); propertyTable.name = expectedName; @@ -615,7 +614,7 @@ void FCesiumFeatureIdSetSpec::Define() { glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; - FeatureId& featureID = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -628,9 +627,10 @@ void FCesiumFeatureIdSetSpec::Define() { const std::string expectedName = "PropertyTableName"; - ExtensionModelExtStructuralMetadata& metadataExtension = - model.addExtension(); - PropertyTable& propertyTable = + CesiumGltf::ExtensionModelExtStructuralMetadata& metadataExtension = + model.addExtension< + CesiumGltf::ExtensionModelExtStructuralMetadata>(); + CesiumGltf::PropertyTable& propertyTable = metadataExtension.propertyTables.emplace_back(); propertyTable.name = expectedName; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 21712ae9b..370dd3688 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -8,15 +8,13 @@ #include #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumFeatureIdTextureSpec, "Cesium.Unit.FeatureIdTexture", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -MeshPrimitive* pPrimitive; +CesiumGltf::Model model; +CesiumGltf::MeshPrimitive* pPrimitive; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), @@ -28,8 +26,8 @@ END_DEFINE_SPEC(FCesiumFeatureIdTextureSpec) void FCesiumFeatureIdTextureSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -45,11 +43,11 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::ErrorUninitialized); + CesiumGltf::FeatureIdTextureViewStatus::ErrorUninitialized); }); It("constructs invalid instance for nonexistent texture", [this]() { - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; @@ -70,14 +68,14 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::ErrorInvalidTexture); + CesiumGltf::FeatureIdTextureViewStatus::ErrorInvalidTexture); }); It("constructs invalid instance for texture with invalid image", [this]() { CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = -1; - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; @@ -98,13 +96,13 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::ErrorInvalidImage); + CesiumGltf::FeatureIdTextureViewStatus::ErrorInvalidImage); }); It("constructs valid instance", [this]() { const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -130,26 +128,26 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::Valid); + CesiumGltf::FeatureIdTextureViewStatus::Valid); }); It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset.emplace(); image.pAsset->width = image.pAsset->height = 1; image.pAsset->channels = 1; image.pAsset->pixelData.push_back(std::byte(42)); - Sampler& sampler = model.samplers.emplace_back(); - sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; - sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); + sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; + sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE; CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = 0; gltfTexture.sampler = 0; - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; @@ -170,26 +168,26 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::Valid); + CesiumGltf::FeatureIdTextureViewStatus::Valid); }); It("constructs valid instance for texture with invalid texcoord accessor", [this]() { - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset.emplace(); image.pAsset->width = image.pAsset->height = 1; image.pAsset->channels = 1; image.pAsset->pixelData.push_back(std::byte(42)); - Sampler& sampler = model.samplers.emplace_back(); - sampler.wrapS = Sampler::WrapS::CLAMP_TO_EDGE; - sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); + sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; + sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE; CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = 0; gltfTexture.sampler = 0; - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; @@ -212,14 +210,14 @@ void FCesiumFeatureIdTextureSpec::Define() { TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), - FeatureIdTextureViewStatus::Valid); + CesiumGltf::FeatureIdTextureViewStatus::Valid); }); }); Describe("GetFeatureIDForUV", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); @@ -227,7 +225,7 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = -1; - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; @@ -255,7 +253,7 @@ void FCesiumFeatureIdTextureSpec::Define() { It("returns correct value for valid attribute", [this]() { const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -295,7 +293,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(0, 1), glm::vec2(1, 1)}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -304,12 +302,13 @@ void FCesiumFeatureIdTextureSpec::Define() { 2, rawTexCoords, 0, - Sampler::WrapS::REPEAT, - Sampler::WrapT::REPEAT); + CesiumGltf::Sampler::WrapS::REPEAT, + CesiumGltf::Sampler::WrapT::REPEAT); assert(featureId.texture != std::nullopt); - ExtensionKhrTextureTransform& textureTransform = - featureId.texture->addExtension(); + CesiumGltf::ExtensionKhrTextureTransform& textureTransform = + featureId.texture + ->addExtension(); textureTransform.offset = {0.5, -0.5}; textureTransform.rotation = UE_DOUBLE_HALF_PI; textureTransform.scale = {0.5, 0.5}; @@ -345,13 +344,13 @@ void FCesiumFeatureIdTextureSpec::Define() { Describe("GetFeatureIDForVertex", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns -1 for invalid texture", [this]() { - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; @@ -379,7 +378,7 @@ void FCesiumFeatureIdTextureSpec::Define() { It("returns -1 for out-of-bounds index", [this]() { const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -419,7 +418,7 @@ void FCesiumFeatureIdTextureSpec::Define() { It("returns correct value for valid texture", [this]() { const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -464,8 +463,8 @@ void FCesiumFeatureIdTextureSpec::Define() { model, *pPrimitive, "TEXCOORD_0", - AccessorSpec::Type::VEC2, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC2, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(values)); const std::vector texCoord1{ @@ -475,7 +474,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(0.0, 0.5)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -510,8 +509,8 @@ void FCesiumFeatureIdTextureSpec::Define() { Describe("GetFeatureIDFromHit", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; pPrimitiveComponent = NewObject(); @@ -530,13 +529,13 @@ void FCesiumFeatureIdTextureSpec::Define() { model, *pPrimitive, "POSITION", - AccessorSpec::Type::VEC3, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC3, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, positions); }); It("returns -1 for invalid texture", [this]() { - FeatureIdTexture texture; + CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; @@ -583,7 +582,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -597,7 +596,7 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); @@ -643,7 +642,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -658,7 +657,7 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 1, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); @@ -704,7 +703,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -719,7 +718,7 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); @@ -771,7 +770,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -786,7 +785,7 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); @@ -841,8 +840,8 @@ void FCesiumFeatureIdTextureSpec::Define() { model, *pPrimitive, "TEXCOORD_0", - AccessorSpec::Type::VEC2, - AccessorSpec::ComponentType::FLOAT, + CesiumGltf::AccessorSpec::Type::VEC2, + CesiumGltf::AccessorSpec::ComponentType::FLOAT, GetValuesAsBytes(texCoords0)); int32 texCoord0AccessorIndex = static_cast(model.accessors.size() - 1); @@ -856,7 +855,7 @@ void FCesiumFeatureIdTextureSpec::Define() { glm::vec2(1, 0), }; const std::vector featureIDs{0, 3, 1, 2}; - FeatureId& featureId = AddFeatureIDsAsTextureToModel( + CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, @@ -877,15 +876,17 @@ void FCesiumFeatureIdTextureSpec::Define() { CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>( + CesiumGltf::AccessorView>( model, texCoord0AccessorIndex)); primData.TexCoordAccessorMap.emplace( 0, - AccessorView>(model, 1)); + CesiumGltf::AccessorView>( + model, + 1)); primData.TexCoordAccessorMap.emplace( 1, - AccessorView>( + CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp index 93107ac13..3af0ffc69 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGltfSpecUtility.cpp @@ -1,7 +1,5 @@ #include "CesiumGltfSpecUtility.h" -using namespace CesiumGltf; - int32_t AddBufferToModel( CesiumGltf::Model& model, const std::string& type, @@ -22,8 +20,8 @@ int32_t AddBufferToModel( accessor.componentType = componentType; const int64_t elementByteSize = - Accessor::computeByteSizeOfComponent(componentType) * - Accessor::computeNumberOfComponents(type); + CesiumGltf::Accessor::computeByteSizeOfComponent(componentType) * + CesiumGltf::Accessor::computeNumberOfComponents(type); accessor.count = buffer.byteLength / elementByteSize; return static_cast(model.accessors.size() - 1); @@ -45,13 +43,14 @@ CesiumGltf::FeatureId& AddFeatureIDsAsAttributeToModel( CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, std::move(values)); - ExtensionExtMeshFeatures* pExtension = - primitive.getExtension(); + CesiumGltf::ExtensionExtMeshFeatures* pExtension = + primitive.getExtension(); if (pExtension == nullptr) { - pExtension = &primitive.addExtension(); + pExtension = + &primitive.addExtension(); } - FeatureId& featureID = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back(); featureID.featureCount = featureCount; featureID.attribute = setIndex; @@ -80,7 +79,7 @@ CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel( data.resize(imageWidth * imageHeight); std::memcpy(data.data(), featureIDs.data(), data.size()); - Sampler& sampler = model.samplers.emplace_back(); + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = samplerWrapS; sampler.wrapT = samplerWrapT; @@ -97,16 +96,17 @@ CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel( CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(values)); - ExtensionExtMeshFeatures* pExtension = - primitive.getExtension(); + CesiumGltf::ExtensionExtMeshFeatures* pExtension = + primitive.getExtension(); if (pExtension == nullptr) { - pExtension = &primitive.addExtension(); + pExtension = + &primitive.addExtension(); } - FeatureId& featureID = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back(); featureID.featureCount = featureCount; - FeatureIdTexture featureIDTexture; + CesiumGltf::FeatureIdTexture featureIDTexture; featureIDTexture.channels = {0}; featureIDTexture.index = 0; featureIDTexture.texCoord = texcoordSetIndex; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp index bc3c37589..49858c73e 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp @@ -6,8 +6,6 @@ #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumMetadataValueSpec, "Cesium.Unit.MetadataValue", @@ -90,7 +88,7 @@ void FCesiumMetadataValueSpec::Define() { }); It("constructs array value with correct type", [this]() { - PropertyArrayCopy arrayView; + CesiumGltf::PropertyArrayCopy arrayView; FCesiumMetadataValue value(arrayView); FCesiumMetadataValueType valueType = UCesiumMetadataValueBlueprintLibrary::GetValueType(value); @@ -1131,7 +1129,8 @@ void FCesiumMetadataValueSpec::Define() { It("gets array from array value", [this]() { std::vector arrayValues{1, 2}; - PropertyArrayCopy arrayView = std::vector(arrayValues); + CesiumGltf::PropertyArrayCopy arrayView = + std::vector(arrayValues); FCesiumMetadataValue value(arrayView); FCesiumPropertyArray array = @@ -1196,7 +1195,7 @@ void FCesiumMetadataValueSpec::Define() { }); It("returns false for array value", [this]() { - PropertyArrayCopy arrayView; + CesiumGltf::PropertyArrayCopy arrayView; FCesiumMetadataValue value(arrayView); TestFalse( "IsEmpty", @@ -1217,7 +1216,9 @@ void FCesiumMetadataValueSpec::Define() { values.Add({"scalar", FCesiumMetadataValue(-1)}); values.Add({"vec2", FCesiumMetadataValue(glm::u8vec2(2, 3))}); values.Add( - {"array", FCesiumMetadataValue(PropertyArrayCopy({1, 2, 3}))}); + {"array", + FCesiumMetadataValue( + CesiumGltf::PropertyArrayCopy({1, 2, 3}))}); const auto strings = UCesiumMetadataValueBlueprintLibrary::GetValuesAsStrings(values); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp index cf16aa069..63fac56c6 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp @@ -5,25 +5,24 @@ #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumPrimitiveFeaturesSpec, "Cesium.Unit.PrimitiveFeatures", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -MeshPrimitive* pPrimitive; -ExtensionExtMeshFeatures* pExtension; +CesiumGltf::Model model; +CesiumGltf::MeshPrimitive* pPrimitive; +CesiumGltf::ExtensionExtMeshFeatures* pExtension; END_DEFINE_SPEC(FCesiumPrimitiveFeaturesSpec) void FCesiumPrimitiveFeaturesSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pExtension = &pPrimitive->addExtension(); + pExtension = + &pPrimitive->addExtension(); }); It("constructs with no feature ID sets", [this]() { @@ -39,7 +38,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { }); It("constructs with single feature ID set", [this]() { - FeatureId& featureID = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back(); featureID.featureCount = 10; FCesiumPrimitiveFeatures primitiveFeatures = @@ -81,7 +80,8 @@ void FCesiumPrimitiveFeaturesSpec::Define() { texCoords, 0); - FeatureId& implicitIDs = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& implicitIDs = + pExtension->featureIds.emplace_back(); implicitIDs.featureCount = 3; FCesiumPrimitiveFeatures primitiveFeatures = @@ -100,7 +100,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { for (size_t i = 0; i < featureIDSets.Num(); i++) { const FCesiumFeatureIdSet& featureIDSet = featureIDSets[static_cast(i)]; - const FeatureId& gltfFeatureID = pExtension->featureIds[i]; + const CesiumGltf::FeatureId& gltfFeatureID = pExtension->featureIds[i]; TestEqual( "Feature Count", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), @@ -116,10 +116,11 @@ void FCesiumPrimitiveFeaturesSpec::Define() { Describe("GetFeatureIDSetsOfType", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pExtension = &pPrimitive->addExtension(); + pExtension = + &pPrimitive->addExtension(); const std::vector attributeIDs{0, 0, 0}; AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 1, 0); @@ -139,7 +140,8 @@ void FCesiumPrimitiveFeaturesSpec::Define() { texCoords, 0); - FeatureId& implicitIDs = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& implicitIDs = + pExtension->featureIds.emplace_back(); implicitIDs.featureCount = 3; }); @@ -218,10 +220,11 @@ void FCesiumPrimitiveFeaturesSpec::Define() { Describe("GetFirstVertexFromFace", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pExtension = &pPrimitive->addExtension(); + pExtension = + &pPrimitive->addExtension(); }); It("returns -1 for out-of-bounds face index", [this]() { @@ -229,7 +232,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); FCesiumPrimitiveFeatures primitiveFeatures = @@ -249,7 +252,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { }); It("returns correct value for primitive without indices", [this]() { - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 9; const int64 numFaces = accessor.count / 3; @@ -273,10 +276,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 7; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -298,10 +301,11 @@ void FCesiumPrimitiveFeaturesSpec::Define() { Describe("GetFeatureIDFromFace", [this]() { BeforeEach([this]() { - model = Model(); - Mesh& mesh = model.meshes.emplace_back(); + model = CesiumGltf::Model(); + CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); - pExtension = &pPrimitive->addExtension(); + pExtension = + &pPrimitive->addExtension(); }); It("returns -1 for primitive with empty feature ID sets", [this]() { @@ -309,10 +313,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 6; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -342,10 +346,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 7; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -381,10 +385,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 3; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -410,7 +414,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { std::vector attributeIDs{1, 1, 1, 2, 2, 2, 0, 0, 0}; AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 3, 0); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 9; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -437,10 +441,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 7; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -481,10 +485,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 3; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -525,7 +529,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { texCoords, 0); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 6; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -568,10 +572,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 4; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -596,12 +600,13 @@ void FCesiumPrimitiveFeaturesSpec::Define() { Describe("ImplicitFeatureIDs", [this]() { BeforeEach([this]() { - FeatureId& implicitIDs = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& implicitIDs = + pExtension->featureIds.emplace_back(); implicitIDs.featureCount = 6; }); It("returns -1 for out-of-bounds face index", [this]() { - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 6; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -624,7 +629,7 @@ void FCesiumPrimitiveFeaturesSpec::Define() { }); It("returns correct values for primitive without indices", [this]() { - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 6; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -651,10 +656,10 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 4; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); @@ -692,16 +697,17 @@ void FCesiumPrimitiveFeaturesSpec::Define() { CreateIndicesForPrimitive( model, *pPrimitive, - AccessorSpec::ComponentType::UNSIGNED_BYTE, + CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE, indices); - Accessor& accessor = model.accessors.emplace_back(); + CesiumGltf::Accessor& accessor = model.accessors.emplace_back(); accessor.count = 7; pPrimitive->attributes.insert( {"POSITION", static_cast(model.accessors.size() - 1)}); // Second feature ID set is implicit - FeatureId& implicitIDs = pExtension->featureIds.emplace_back(); + CesiumGltf::FeatureId& implicitIDs = + pExtension->featureIds.emplace_back(); implicitIDs.featureCount = 7; FCesiumPrimitiveFeatures primitiveFeatures = diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp index c631610da..6eae0eb5d 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp @@ -4,8 +4,6 @@ #include "CesiumPropertyArrayBlueprintLibrary.h" #include "Misc/AutomationTest.h" -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumPropertyArraySpec, "Cesium.Unit.PropertyArray", @@ -37,7 +35,7 @@ void FCesiumPropertyArraySpec::Define() { }); It("constructs empty array from empty view", [this]() { - PropertyArrayCopy arrayView; + CesiumGltf::PropertyArrayCopy arrayView; FCesiumPropertyArray array(arrayView); TestEqual( "size", @@ -60,7 +58,7 @@ void FCesiumPropertyArraySpec::Define() { It("constructs non-empty array", [this]() { std::vector values{1, 2, 3, 4}; - PropertyArrayCopy arrayView = std::vector(values); + CesiumGltf::PropertyArrayCopy arrayView = std::vector(values); FCesiumPropertyArray array(arrayView); TestEqual( "size", @@ -85,7 +83,7 @@ void FCesiumPropertyArraySpec::Define() { Describe("GetValue", [this]() { It("gets bogus value for out-of-bounds index", [this]() { std::vector values{1}; - PropertyArrayCopy arrayView = std::vector(values); + CesiumGltf::PropertyArrayCopy arrayView = std::vector(values); FCesiumPropertyArray array(arrayView); TestEqual( "size", @@ -114,7 +112,7 @@ void FCesiumPropertyArraySpec::Define() { It("gets value for valid index", [this]() { std::vector values{1, 2, 3, 4}; - PropertyArrayCopy arrayView = std::vector(values); + CesiumGltf::PropertyArrayCopy arrayView = std::vector(values); FCesiumPropertyArray array(arrayView); TestEqual( "size", diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp index c2d9ded27..6f3a89394 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp @@ -7,23 +7,22 @@ #include "Misc/AutomationTest.h" #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumPropertyTableSpec, "Cesium.Unit.PropertyTable", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) -Model model; -ExtensionModelExtStructuralMetadata* pExtension; -PropertyTable* pPropertyTable; +CesiumGltf::Model model; +CesiumGltf::ExtensionModelExtStructuralMetadata* pExtension; +CesiumGltf::PropertyTable* pPropertyTable; END_DEFINE_SPEC(FCesiumPropertyTableSpec) void FCesiumPropertyTableSpec::Define() { BeforeEach([this]() { - model = Model(); - pExtension = &model.addExtension(); - pExtension->schema = Schema(); + model = CesiumGltf::Model(); + pExtension = + &model.addExtension(); + pExtension->schema = CesiumGltf::Schema(); pPropertyTable = &pExtension->propertyTables.emplace_back(); }); @@ -83,8 +82,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -111,8 +110,9 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, // Incorrect component type + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, // Incorrect + // component type values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -152,8 +152,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -167,8 +167,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -251,8 +251,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -311,8 +311,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -326,8 +326,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); std::string invalidPropertyName("badProperty"); @@ -336,8 +336,9 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, invalidPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, // Incorrect component type + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, // Incorrect + // component type invalidPropertyValues); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -378,8 +379,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -418,8 +419,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -433,8 +434,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -513,8 +514,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -528,8 +529,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -568,8 +569,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -583,8 +584,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -642,8 +643,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, propertyValues); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -696,8 +697,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -711,8 +712,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -747,8 +748,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, scalarPropertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, scalarValues); std::string vec2PropertyName("vec2Property"); @@ -762,8 +763,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, vec2PropertyName, - ClassProperty::Type::VEC2, - ClassProperty::ComponentType::FLOAT32, + CesiumGltf::ClassProperty::Type::VEC2, + CesiumGltf::ClassProperty::ComponentType::FLOAT32, vec2Values); FCesiumPropertyTable propertyTable(model, *pPropertyTable); @@ -817,8 +818,8 @@ void FCesiumPropertyTableSpec::Define() { model, *pPropertyTable, propertyName, - ClassProperty::Type::SCALAR, - ClassProperty::ComponentType::INT32, + CesiumGltf::ClassProperty::Type::SCALAR, + CesiumGltf::ClassProperty::ComponentType::INT32, propertyValues); FCesiumPropertyTable propertyTable(model, *pPropertyTable); diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp index 43b246f30..1ed3f52b4 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp @@ -39,8 +39,9 @@ void FCesiumPropertyTablePropertySpec::Define() { It("constructs invalid instance from view with invalid definition", [this]() { - PropertyTablePropertyView propertyView( - PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch); + CesiumGltf::PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyViewStatus:: + ErrorArrayTypeMismatch); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -61,8 +62,9 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs invalid instance from view with invalid data", [this]() { - PropertyTablePropertyView propertyView( - PropertyTablePropertyViewStatus::ErrorBufferViewOutOfBounds); + CesiumGltf::PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyViewStatus:: + ErrorBufferViewOutOfBounds); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -83,14 +85,14 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs valid instance", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{1, 2, 3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -163,15 +165,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs valid normalized instance", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; classProperty.normalized = true; std::vector values{0, 1, 255, 128}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -220,8 +222,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs instance for fixed-length array property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -232,15 +234,16 @@ void FCesiumPropertyTablePropertySpec::Define() { static_cast(values.size()) / *classProperty.count; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(), - gsl::span(), - PropertyComponentType::None, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span(), + gsl::span(), + PropertyComponentType::None, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( @@ -284,8 +287,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs instance for variable-length array property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -297,15 +300,18 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector offsetsData = GetValuesAsBytes(offsets); int64_t size = static_cast(offsets.size()) - 1; - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(offsetsData.data(), offsetsData.size()), - gsl::span(), - PropertyComponentType::Uint16, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span( + offsetsData.data(), + offsetsData.size()), + gsl::span(), + PropertyComponentType::Uint16, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( @@ -350,8 +356,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs valid instance with additional properties", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.normalized = true; @@ -372,7 +378,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{1, 2, 3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -462,8 +468,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("constructs valid array instance with additional properties", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.normalized = true; @@ -486,11 +492,12 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{1, 2, 3, 4, 5, 6, -1, -1}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView, true> propertyView( - propertyTableProperty, - classProperty, - static_cast(values.size()), - gsl::span(data.data(), data.size())); + CesiumGltf::PropertyTablePropertyView, true> + propertyView( + propertyTableProperty, + classProperty, + static_cast(values.size()), + gsl::span(data.data(), data.size())); FCesiumPropertyTableProperty property(propertyView); TestEqual( @@ -676,13 +683,13 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::BOOLEAN; std::vector data{static_cast(0b10110001)}; - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(8), @@ -712,13 +719,13 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from boolean property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::BOOLEAN; std::vector data{static_cast(0b10110001)}; - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(8), @@ -771,11 +778,11 @@ void FCesiumPropertyTablePropertySpec::Define() { offsets[offsets.size() - 1] = currentOffset; std::vector offsetsData = GetValuesAsBytes(offsets); - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::STRING; - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -825,15 +832,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; std::vector values{1, 2, 3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -861,15 +868,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from uint8 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; std::vector values{1, 2, 3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -898,15 +905,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{1, 24, 255, 256, -1, 28}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -936,8 +943,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; @@ -950,7 +957,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{1, 2, 3, 0, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1004,15 +1011,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1044,15 +1051,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from int32 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1081,8 +1088,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -1095,7 +1102,7 @@ void FCesiumPropertyTablePropertySpec::Define() { }; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1125,8 +1132,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -1139,7 +1146,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1, 2, -3, 0, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1197,15 +1204,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this, defaultInt64]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT64; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1237,15 +1244,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from int64 property", [this, defaultInt64]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT64; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1274,8 +1281,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this, defaultInt64]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT64; @@ -1286,7 +1293,7 @@ void FCesiumPropertyTablePropertySpec::Define() { static_cast(std::numeric_limits::max()) + 100}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1316,8 +1323,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this, defaultInt64]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT64; @@ -1330,7 +1337,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1, 2, 0, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1386,15 +1393,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; std::vector values{-1.1f, 2.2f, -3.3f, 4.0f}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1422,15 +1429,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from float property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; std::vector values{-1.1f, 2.2f, -3.3f, 4.0f}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1466,12 +1473,12 @@ void FCesiumPropertyTablePropertySpec::Define() { std::numeric_limits::max()}; std::vector data = GetValuesAsBytes(values); - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1506,8 +1513,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -1520,7 +1527,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1.1f, 2.2f, -3.3f, 4.0f}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1566,15 +1573,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; std::vector values{-1.1, 2.2, -3.3, 4.0}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1606,15 +1613,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from double property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; std::vector values{-1.1, 2.2, -3.3, 4.0}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1643,8 +1650,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from normalized uint8 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::UINT8; classProperty.normalized = true; @@ -1652,7 +1659,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{0, 128, 255, 0}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1685,8 +1692,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::STRING; std::vector values{"not a number", "10", "-2"}; @@ -1709,7 +1716,7 @@ void FCesiumPropertyTablePropertySpec::Define() { offsets[offsets.size() - 1] = currentOffset; std::vector offsetsData = GetValuesAsBytes(offsets); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1746,8 +1753,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -1760,7 +1767,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1.1, 2.2, -3.3, 4.0}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1807,8 +1814,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -1818,7 +1825,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec2(10, 4)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1852,8 +1859,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::ivec2 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -1863,7 +1870,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec2(10, 4)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1893,8 +1900,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -1904,7 +1911,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec3(std::numeric_limits::max(), -1.0f, 2.0f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -1937,8 +1944,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -1954,7 +1961,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec2(10, 4)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2008,8 +2015,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2019,7 +2026,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec2(1.5, -1.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2053,8 +2060,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::dvec2 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2064,7 +2071,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec2(1.5, -1.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2094,8 +2101,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from normalized glm::u8vec2 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::UINT8; classProperty.normalized = true; @@ -2106,7 +2113,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::u8vec2(10, 4)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2140,8 +2147,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::STRING; std::vector values{"X=10 Y=3", "not a vector", "X=-2 Y=4"}; @@ -2164,7 +2171,7 @@ void FCesiumPropertyTablePropertySpec::Define() { offsets[offsets.size() - 1] = currentOffset; std::vector offsetsData = GetValuesAsBytes(offsets); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2201,8 +2208,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2218,7 +2225,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec2(1.5, -1.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2269,8 +2276,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -2280,7 +2287,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec3(10, 4, 5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2314,8 +2321,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::ivec3 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -2325,7 +2332,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec3(10, 4, 5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2355,8 +2362,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -2367,7 +2374,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec3(std::numeric_limits::max(), -1.0f, 2.0f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2401,8 +2408,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -2422,7 +2429,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::ivec3(10, 4, 5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2476,8 +2483,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -2487,7 +2494,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec3(10.0f, 4.4f, 5.4f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2521,8 +2528,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::vec3 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -2532,7 +2539,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec3(10.0f, 4.4f, 5.4f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2562,8 +2569,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2574,7 +2581,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec2(std::numeric_limits::max(), -1.0)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2612,8 +2619,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -2629,7 +2636,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec3(10.0f, 4.4f, 5.4f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2680,8 +2687,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2691,7 +2698,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec3(1.5, -1.5, -2.01)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2725,8 +2732,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::dvec3 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2736,7 +2743,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec3(1.5, -1.5, -2.01)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2766,8 +2773,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from normalized glm::i8vec3 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::INT8; classProperty.normalized = true; @@ -2778,7 +2785,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::i8vec3(1, -1, -2)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2813,8 +2820,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::STRING; std::vector values{ @@ -2840,7 +2847,7 @@ void FCesiumPropertyTablePropertySpec::Define() { offsets[offsets.size() - 1] = currentOffset; std::vector offsetsData = GetValuesAsBytes(offsets); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2877,8 +2884,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC3; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2894,7 +2901,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec3(1.5, -1.5, -2.01)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2945,8 +2952,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -2956,7 +2963,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec4(1.5, -1.5, -2.01, 5.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -2990,8 +2997,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::dvec4 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -3001,7 +3008,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec4(1.5, -1.5, -2.01, 5.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3035,8 +3042,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::i8vec4 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC4; classProperty.componentType = ClassProperty::ComponentType::INT8; classProperty.normalized = true; @@ -3047,7 +3054,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::i8vec4(1, -1, -2, 5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3087,8 +3094,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::STRING; std::vector values{ @@ -3114,7 +3121,7 @@ void FCesiumPropertyTablePropertySpec::Define() { offsets[offsets.size() - 1] = currentOffset; std::vector offsetsData = GetValuesAsBytes(offsets); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3151,8 +3158,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -3168,7 +3175,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::dvec4(1.5, -1.5, -2.01, 5.5)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3220,8 +3227,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::MAT4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -3241,7 +3248,7 @@ void FCesiumPropertyTablePropertySpec::Define() { // clang-format on std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3276,8 +3283,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::dmat4 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::MAT4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -3297,7 +3304,7 @@ void FCesiumPropertyTablePropertySpec::Define() { // clang-format on std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3338,8 +3345,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets from glm::u8mat4x4 property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::MAT4; classProperty.componentType = ClassProperty::ComponentType::INT8; classProperty.normalized = true; @@ -3360,7 +3367,7 @@ void FCesiumPropertyTablePropertySpec::Define() { // clang-format on std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3405,15 +3412,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("converts compatible values", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; std::vector values{-2.0, 10.5}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3453,8 +3460,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns default values for incompatible type", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::VEC2; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -3463,7 +3470,7 @@ void FCesiumPropertyTablePropertySpec::Define() { glm::vec2(1.5f, 0.1f)}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3492,8 +3499,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::MAT4; classProperty.componentType = ClassProperty::ComponentType::FLOAT64; @@ -3526,7 +3533,7 @@ void FCesiumPropertyTablePropertySpec::Define() { // clang-format on std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3569,15 +3576,15 @@ void FCesiumPropertyTablePropertySpec::Define() { Describe("GetArray", [this]() { It("returns empty array for non-array property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{1, 2, 3, 4, 5, 6}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -3629,8 +3636,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns empty array for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -3640,15 +3647,16 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector data = GetValuesAsBytes(values); int64 size = static_cast(values.size()) / *classProperty.count; - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(), - gsl::span(), - PropertyComponentType::None, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span(), + gsl::span(), + PropertyComponentType::None, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -3686,8 +3694,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns array for fixed-length array property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -3697,15 +3705,16 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector data = GetValuesAsBytes(values); int64 size = static_cast(values.size()) / *classProperty.count; - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(), - gsl::span(), - PropertyComponentType::None, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span(), + gsl::span(), + PropertyComponentType::None, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -3751,8 +3760,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns array for variable-length array property", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -3764,15 +3773,18 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector offsetsData = GetValuesAsBytes(offsets); int64 size = static_cast(offsets.size() - 1); - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(offsetsData.data(), offsetsData.size()), - gsl::span(), - PropertyComponentType::Uint16, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span( + offsetsData.data(), + offsetsData.size()), + gsl::span(), + PropertyComponentType::Uint16, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -3824,8 +3836,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -3837,15 +3849,16 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector data = GetValuesAsBytes(values); int64 size = static_cast(values.size()) / *classProperty.count; - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(), - gsl::span(), - PropertyComponentType::None, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span(), + gsl::span(), + PropertyComponentType::None, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -3910,8 +3923,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; classProperty.array = true; @@ -3924,15 +3937,16 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector data = GetValuesAsBytes(values); int64 size = static_cast(values.size()) / *classProperty.count; - PropertyTablePropertyView> propertyView( - propertyTableProperty, - classProperty, - size, - gsl::span(data.data(), data.size()), - gsl::span(), - gsl::span(), - PropertyComponentType::None, - PropertyComponentType::None); + CesiumGltf::PropertyTablePropertyView> + propertyView( + propertyTableProperty, + classProperty, + size, + gsl::span(data.data(), data.size()), + gsl::span(), + gsl::span(), + PropertyComponentType::None, + PropertyComponentType::None); FCesiumPropertyTableProperty property(propertyView); TestEqual( "PropertyTablePropertyStatus", @@ -4019,15 +4033,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("returns empty value for invalid feature ID", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -4061,15 +4075,15 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets value for valid feature IDs", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -4105,8 +4119,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with offset / scale", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::FLOAT32; @@ -4119,7 +4133,7 @@ void FCesiumPropertyTablePropertySpec::Define() { classProperty.offset = offset; classProperty.scale = scale; - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -4155,8 +4169,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -4166,7 +4180,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), @@ -4210,8 +4224,8 @@ void FCesiumPropertyTablePropertySpec::Define() { }); It("gets with noData / default value", [this]() { - PropertyTableProperty propertyTableProperty; - ClassProperty classProperty; + CesiumGltf::PropertyTableProperty propertyTableProperty; + CesiumGltf::ClassProperty classProperty; classProperty.type = ClassProperty::Type::SCALAR; classProperty.componentType = ClassProperty::ComponentType::INT32; @@ -4224,7 +4238,7 @@ void FCesiumPropertyTablePropertySpec::Define() { std::vector values{-1, 2, -3, 4}; std::vector data = GetValuesAsBytes(values); - PropertyTablePropertyView propertyView( + CesiumGltf::PropertyTablePropertyView propertyView( propertyTableProperty, classProperty, static_cast(values.size()), diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp index 7b329481c..512597a5a 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTexture.spec.cpp @@ -9,8 +9,6 @@ #include "Misc/AutomationTest.h" #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumPropertyTextureSpec, "Cesium.Unit.PropertyTexture", diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp index d20bc855c..8229b5ef8 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTextureProperty.spec.cpp @@ -6,8 +6,6 @@ #include "Misc/AutomationTest.h" #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FCesiumPropertyTexturePropertySpec, "Cesium.Unit.PropertyTextureProperty", diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp index 9a16b5af3..764efa69d 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumTextureUtility.spec.cpp @@ -9,7 +9,6 @@ #include #include -using namespace CesiumGltf; using namespace CesiumTextureUtility; using namespace CesiumUtility; @@ -21,7 +20,7 @@ BEGIN_DEFINE_SPEC( std::vector originalPixels; std::vector originalMipPixels; std::vector expectedMipPixelsIfGenerated; -CesiumUtility::IntrusivePointer pImageAsset; +CesiumUtility::IntrusivePointer pImageAsset; void RunTests(); @@ -66,8 +65,8 @@ void CesiumTextureUtilitySpec::Define() { originalPixels.data(), originalPixels.size()); - CesiumUtility::IntrusivePointer pCopy = - new ImageAsset(*pImageAsset); + CesiumUtility::IntrusivePointer pCopy = + new CesiumGltf::ImageAsset(*pImageAsset); CesiumGltfReader::ImageDecoder::generateMipMaps(*pCopy); expectedMipPixelsIfGenerated.clear(); @@ -96,11 +95,11 @@ void CesiumTextureUtilitySpec::Define() { 0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3, 0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5}; pImageAsset->mipPositions.emplace_back( - ImageAssetMipPosition{0, originalPixels.size()}); + CesiumGltf::ImageAssetMipPosition{0, originalPixels.size()}); // Mip 1 (1x1) originalMipPixels = {0x26, 0x46, 0x86, 0xF6}; - pImageAsset->mipPositions.emplace_back(ImageAssetMipPosition{ + pImageAsset->mipPositions.emplace_back(CesiumGltf::ImageAssetMipPosition{ pImageAsset->mipPositions[0].byteSize, originalMipPixels.size()}); @@ -170,11 +169,11 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Image and Sampler", [this]() { - Sampler sampler; - sampler.minFilter = Sampler::MinFilter::NEAREST; - sampler.magFilter = Sampler::MagFilter::NEAREST; - sampler.wrapS = Sampler::WrapS::MIRRORED_REPEAT; - sampler.wrapT = Sampler::WrapT::CLAMP_TO_EDGE; + CesiumGltf::Sampler sampler; + sampler.minFilter = CesiumGltf::Sampler::MinFilter::NEAREST; + sampler.magFilter = CesiumGltf::Sampler::MagFilter::NEAREST; + sampler.wrapS = CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT; + sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE; TUniquePtr pHalfLoaded = loadTextureFromImageAndSamplerAnyThreadPart( @@ -196,18 +195,18 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Model", [this]() { - Model model; + CesiumGltf::Model model; - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset = pImageAsset; - Sampler& sampler = model.samplers.emplace_back(); - sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; - sampler.magFilter = Sampler::MagFilter::LINEAR; - sampler.wrapS = Sampler::WrapS::REPEAT; - sampler.wrapT = Sampler::WrapT::MIRRORED_REPEAT; + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); + sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; + sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR; + sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT; + sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT; - Texture& texture = model.textures.emplace_back(); + CesiumGltf::Texture& texture = model.textures.emplace_back(); texture.source = 0; texture.sampler = 0; @@ -229,28 +228,28 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Two textures referencing one image", [this]() { - Model model; + CesiumGltf::Model model; - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset = pImageAsset; - Sampler& sampler1 = model.samplers.emplace_back(); - sampler1.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; - sampler1.magFilter = Sampler::MagFilter::LINEAR; - sampler1.wrapS = Sampler::WrapS::REPEAT; - sampler1.wrapT = Sampler::WrapT::MIRRORED_REPEAT; + CesiumGltf::Sampler& sampler1 = model.samplers.emplace_back(); + sampler1.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; + sampler1.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR; + sampler1.wrapS = CesiumGltf::Sampler::WrapS::REPEAT; + sampler1.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT; - Texture& texture1 = model.textures.emplace_back(); + CesiumGltf::Texture& texture1 = model.textures.emplace_back(); texture1.source = 0; texture1.sampler = 0; - Sampler& sampler2 = model.samplers.emplace_back(); - sampler2.minFilter = Sampler::MinFilter::NEAREST; - sampler2.magFilter = Sampler::MagFilter::NEAREST; - sampler2.wrapS = Sampler::WrapS::MIRRORED_REPEAT; - sampler2.wrapT = Sampler::WrapT::REPEAT; + CesiumGltf::Sampler& sampler2 = model.samplers.emplace_back(); + sampler2.minFilter = CesiumGltf::Sampler::MinFilter::NEAREST; + sampler2.magFilter = CesiumGltf::Sampler::MagFilter::NEAREST; + sampler2.wrapS = CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT; + sampler2.wrapT = CesiumGltf::Sampler::WrapT::REPEAT; - Texture& texture2 = model.textures.emplace_back(); + CesiumGltf::Texture& texture2 = model.textures.emplace_back(); texture2.source = 0; texture2.sampler = 1; @@ -296,18 +295,18 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Loading the same texture twice", [this]() { - Model model; + CesiumGltf::Model model; - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset = pImageAsset; - Sampler& sampler = model.samplers.emplace_back(); - sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; - sampler.magFilter = Sampler::MagFilter::LINEAR; - sampler.wrapS = Sampler::WrapS::REPEAT; - sampler.wrapT = Sampler::WrapT::MIRRORED_REPEAT; + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); + sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; + sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR; + sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT; + sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT; - Texture& texture = model.textures.emplace_back(); + CesiumGltf::Texture& texture = model.textures.emplace_back(); texture.source = 0; texture.sampler = 0; @@ -330,7 +329,7 @@ void CesiumTextureUtilitySpec::RunTests() { // Copy the model and load the same texture again. // This time there's no more pixel data, so it's necessary to use the // previously-created texture. - Model model2 = model; + CesiumGltf::Model model2 = model; TUniquePtr pHalfLoaded2 = loadTextureFromModelAnyThreadPart(model2, model.textures[0], true); TestNotNull("pHalfLoaded2", pHalfLoaded2.Get()); @@ -345,18 +344,18 @@ void CesiumTextureUtilitySpec::RunTests() { }); It("Loading the same texture twice from one model", [this]() { - Model model; + CesiumGltf::Model model; - Image& image = model.images.emplace_back(); + CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset = pImageAsset; - Sampler& sampler = model.samplers.emplace_back(); - sampler.minFilter = Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; - sampler.magFilter = Sampler::MagFilter::LINEAR; - sampler.wrapS = Sampler::WrapS::REPEAT; - sampler.wrapT = Sampler::WrapT::MIRRORED_REPEAT; + CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); + sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR; + sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR; + sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT; + sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT; - Texture& texture = model.textures.emplace_back(); + CesiumGltf::Texture& texture = model.textures.emplace_back(); texture.source = 0; texture.sampler = 0; diff --git a/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp b/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp index a21798d28..f4c9e9af0 100644 --- a/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/UnrealMetadataConversions.spec.cpp @@ -6,8 +6,6 @@ #include -using namespace CesiumGltf; - BEGIN_DEFINE_SPEC( FUnrealMetadataConversionsSpec, "Cesium.Unit.MetadataConversions",