diff --git a/CHANGES.md b/CHANGES.md index 33b3fb3f2..f32e073be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ### ? - ? +##### Additions :tada: + +- Replaced the "Auto Fill" button on `UCesiumFeaturesMetadataComponent` with "Add Properties". This allows metadata properties and feature ID sets to be selectively added to the component, rather than adding them all in bulk. + ##### Fixes :wrench: - Fixed a bug that prevented `UCesiumPrimitiveFeaturesBlueprintLibrary::GetPrimitiveFeatures` from retrieving the features of instanced meshes. diff --git a/Source/CesiumEditor/Private/CesiumEditor.cpp b/Source/CesiumEditor/Private/CesiumEditor.cpp index 1f51d162c..b5d38b5df 100644 --- a/Source/CesiumEditor/Private/CesiumEditor.cpp +++ b/Source/CesiumEditor/Private/CesiumEditor.cpp @@ -1,11 +1,11 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #include "CesiumEditor.h" -#include "Cesium3DTilesSelection/Tileset.h" #include "Cesium3DTileset.h" #include "Cesium3DTilesetCustomization.h" #include "CesiumCartographicPolygon.h" #include "CesiumCommands.h" +#include "CesiumFeaturesMetadataViewer.h" #include "CesiumGeoreferenceCustomization.h" #include "CesiumGlobeAnchorCustomization.h" #include "CesiumIonPanel.h" @@ -29,6 +29,10 @@ #include "Styling/SlateStyle.h" #include "Styling/SlateStyleRegistry.h" +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END + constexpr int MaximumOverlaysWithDefaultMaterial = 3; IMPLEMENT_MODULE(FCesiumEditorModule, CesiumEditor) @@ -364,6 +368,11 @@ void FCesiumEditorModule::StartupModule() { OnCesiumRasterOverlayIonTroubleshooting.AddRaw( this, &FCesiumEditorModule::OnRasterOverlayIonTroubleshooting); + + this->_featuresMetadataAddPropertiesSubscription = + OnCesiumFeaturesMetadataAddProperties.AddRaw( + this, + &FCesiumEditorModule::OnFeaturesMetadataAddProperties); } void FCesiumEditorModule::ShutdownModule() { @@ -386,6 +395,12 @@ void FCesiumEditorModule::ShutdownModule() { this->_rasterOverlayIonTroubleshootingSubscription); this->_rasterOverlayIonTroubleshootingSubscription.Reset(); } + if (this->_featuresMetadataAddPropertiesSubscription.IsValid()) { + OnCesiumFeaturesMetadataAddProperties.Remove( + this->_featuresMetadataAddPropertiesSubscription); + this->_featuresMetadataAddPropertiesSubscription.Reset(); + } + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TEXT("Cesium")); FCesiumCommands::Unregister(); IModuleInterface::ShutdownModule(); @@ -477,6 +492,11 @@ void FCesiumEditorModule::OnRasterOverlayIonTroubleshooting( CesiumIonTokenTroubleshooting::Open(pOverlay, false); } +void FCesiumEditorModule::OnFeaturesMetadataAddProperties( + ACesium3DTileset* pTileset) { + CesiumFeaturesMetadataViewer::Open(pTileset); +} + TSharedPtr FCesiumEditorModule::GetStyle() { return StyleSet; } const FName& FCesiumEditorModule::GetStyleSetName() { diff --git a/Source/CesiumEditor/Private/CesiumEditor.h b/Source/CesiumEditor/Private/CesiumEditor.h index 2eabd162e..f1e16d8a5 100644 --- a/Source/CesiumEditor/Private/CesiumEditor.h +++ b/Source/CesiumEditor/Private/CesiumEditor.h @@ -1,4 +1,4 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #pragma once @@ -110,12 +110,14 @@ class FCesiumEditorModule : public IModuleInterface { const FCesiumRasterOverlayLoadFailureDetails& details); void OnTilesetIonTroubleshooting(ACesium3DTileset* pTileset); void OnRasterOverlayIonTroubleshooting(UCesiumRasterOverlay* pOverlay); + void OnFeaturesMetadataAddProperties(ACesium3DTileset* pTileset); CesiumIonServerManager _serverManager; FDelegateHandle _tilesetLoadFailureSubscription; FDelegateHandle _rasterOverlayLoadFailureSubscription; FDelegateHandle _tilesetIonTroubleshootingSubscription; FDelegateHandle _rasterOverlayIonTroubleshootingSubscription; + FDelegateHandle _featuresMetadataAddPropertiesSubscription; CesiumEditorSubLevelMutex _subLevelMutex; CesiumEditorReparentHandler _reparentHandler; diff --git a/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.cpp b/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.cpp new file mode 100644 index 000000000..e2a4761a0 --- /dev/null +++ b/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.cpp @@ -0,0 +1,1185 @@ +// Copyright 2020-2025 CesiumGS, Inc. and Contributors + +#include "CesiumFeaturesMetadataViewer.h" + +#include "Cesium3DTileset.h" +#include "CesiumCommon.h" +#include "CesiumEditor.h" +#include "CesiumFeaturesMetadataComponent.h" +#include "CesiumMetadataEncodingDetails.h" +#include "CesiumModelMetadata.h" +#include "CesiumPrimitiveFeatures.h" +#include "CesiumPrimitiveMetadata.h" +#include "CesiumRuntimeSettings.h" +#include "EditorStyleSet.h" +#include "LevelEditor.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Images/SThrobber.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Layout/SHeader.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Views/SListView.h" + +THIRD_PARTY_INCLUDES_START +#include +#include +#include +#include +THIRD_PARTY_INCLUDES_END + +/*static*/ TSharedPtr + CesiumFeaturesMetadataViewer::_pExistingWindow = nullptr; +/*static*/ TArray> + CesiumFeaturesMetadataViewer::_conversionOptions = {}; +/*static*/ TArray> + CesiumFeaturesMetadataViewer::_encodedTypeOptions = {}; +/*static*/ TArray> + CesiumFeaturesMetadataViewer::_encodedComponentTypeOptions = {}; +/*static*/ TMap> + CesiumFeaturesMetadataViewer::_stringMap = {}; + +/*static*/ void +CesiumFeaturesMetadataViewer::Open(TWeakObjectPtr pTileset) { + if (_pExistingWindow.IsValid()) { + _pExistingWindow->_pTileset = pTileset; + _pExistingWindow->SyncAndRebuildUI(); + } else { + // Open a new panel + TSharedRef viewer = + SNew(CesiumFeaturesMetadataViewer).Tileset(pTileset); + + _pExistingWindow = viewer; + _pExistingWindow->GetOnWindowClosedEvent().AddLambda( + [&pExistingWindow = CesiumFeaturesMetadataViewer::_pExistingWindow]( + const TSharedRef& pWindow) { pExistingWindow = nullptr; }); + + FSlateApplication::Get().AddWindow(viewer); + } + + if (pTileset.IsValid()) { + _pExistingWindow->_pFeaturesMetadataComponent = + pTileset->GetComponentByClass(); + } + + _pExistingWindow->BringToFront(); +} + +void CesiumFeaturesMetadataViewer::Construct(const FArguments& InArgs) { + CesiumFeaturesMetadataViewer::initializeStaticVariables(); + + SAssignNew(this->_pContent, SVerticalBox); + + const TWeakObjectPtr& pTileset = InArgs._Tileset; + FString label = + pTileset.IsValid() ? pTileset->GetActorLabel() : TEXT("Unknown"); + + this->_pTileset = pTileset; + this->SyncAndRebuildUI(); + + SWindow::Construct( + SWindow::FArguments() + .Title(FText::FromString(FString::Format( + TEXT("{0}: Features and Metadata Properties"), + {label}))) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D( + 800, + 600))[SNew(SBorder) + .Visibility(EVisibility::Visible) + .BorderImage(FAppStyle::GetBrush("Menu.Background")) + .Padding(FMargin(10.0f))[this->_pContent->AsShared()]]); +} + +namespace { +template +void populateEnumOptions(TArray>& options) { + UEnum* pEnum = StaticEnum(); + if (pEnum) { + // "NumEnums" also includes the "_MAX" value, which indicates the number of + // different values in the enum. Exclude it here. + const int32 num = pEnum->NumEnums() - 1; + options.Reserve(num); + + for (int32 i = 0; i < num; i++) { + TEnum value = TEnum(pEnum->GetValueByIndex(i)); + options.Emplace(MakeShared(value)); + } + } +} +} // namespace + +void CesiumFeaturesMetadataViewer::SyncAndRebuildUI() { + this->_metadataSources.Empty(); + this->_featureIdSets.Empty(); + this->_stringMap.Empty(); + this->_propertyTextureNames.Empty(); + + this->gatherGltfFeaturesMetadata(); + + TSharedRef pContent = this->_pContent.ToSharedRef(); + pContent->ClearChildren(); + + pContent->AddSlot().AutoHeight() + [SNew(SHeader) + .Content()[SNew(STextBlock) + .TextStyle(FCesiumEditorModule::GetStyle(), "Heading") + .Text(FText::FromString(TEXT("glTF Features"))) + .Margin(FMargin(0.f, 10.f))]]; + + if (!this->_featureIdSets.IsEmpty()) { + TSharedRef pGltfFeatures = SNew(SScrollBox); + for (const FeatureIdSetView& featureIdSet : this->_featureIdSets) { + this->createGltfFeatureIdSetDropdown(pGltfFeatures, featureIdSet); + } + pContent->AddSlot().MaxHeight(400.0f).AutoHeight()[pGltfFeatures]; + } else { + pContent->AddSlot().AutoHeight() + [SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(0.05f) + + SHorizontalBox::Slot() + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(TEXT( + "This tileset does not contain any glTF features in this view.")))]]; + } + + pContent->AddSlot().AutoHeight() + [SNew(SHeader) + .Content()[SNew(STextBlock) + .TextStyle(FCesiumEditorModule::GetStyle(), "Heading") + .Text(FText::FromString(TEXT("glTF Metadata"))) + .Margin(FMargin(0.f, 10.f))]]; + + if (!this->_metadataSources.IsEmpty()) { + TSharedRef pGltfContent = SNew(SScrollBox); + for (const PropertySourceView& source : this->_metadataSources) { + this->createGltfPropertySourceDropdown(pGltfContent, source); + } + pContent->AddSlot().MaxHeight(400.0f).AutoHeight()[pGltfContent]; + } else { + pContent->AddSlot().AutoHeight() + [SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(0.05f) + + SHorizontalBox::Slot() + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(TEXT( + "This tileset does not contain any glTF metadata in this view.")))]]; + } + + pContent->AddSlot() + .AutoHeight() + .Padding(0.0f, 10.0f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Center) + [SNew(SButton) + .ButtonStyle(FCesiumEditorModule::GetStyle(), "CesiumButton") + .TextStyle(FCesiumEditorModule::GetStyle(), "CesiumButtonText") + .ContentPadding(FMargin(1.0, 1.0)) + .HAlign(EHorizontalAlignment::HAlign_Center) + .Text(FText::FromString(TEXT("Sync with View"))) + .ToolTipText(FText::FromString(TEXT( + "Syncs the feature ID sets and metadata from currently loaded tiles in the ACesium3DTileset."))) + .OnClicked_Lambda([this]() { + this->SyncAndRebuildUI(); + return FReply::Handled(); + })]; +} + +namespace { +// These are copies of functions in EncodedFeaturesMetadata.h. The file is +// unfortunately too entangled in Private code to pull into Public. +FString getNameForPropertySource(const FCesiumPropertyTable& propertyTable) { + FString propertyTableName = + UCesiumPropertyTableBlueprintLibrary::GetPropertyTableName(propertyTable); + + if (propertyTableName.IsEmpty()) { + // Substitute the name with the property table's class. + propertyTableName = propertyTable.getClassName(); + } + + return propertyTableName; +} + +FString +getNameForPropertySource(const FCesiumPropertyTexture& propertyTexture) { + FString propertyTextureName = + UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureName( + propertyTexture); + + if (propertyTextureName.IsEmpty()) { + // Substitute the name with the property texture's class. + propertyTextureName = propertyTexture.getClassName(); + } + + return propertyTextureName; +} + +FString getNameForFeatureIdSet( + const FCesiumFeatureIdSet& featureIDSet, + int32& featureIdTextureCounter) { + FString label = UCesiumFeatureIdSetBlueprintLibrary::GetLabel(featureIDSet); + if (!label.IsEmpty()) { + return label; + } + + ECesiumFeatureIdSetType type = + UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(featureIDSet); + + if (type == ECesiumFeatureIdSetType::Attribute) { + FCesiumFeatureIdAttribute attribute = + UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( + featureIDSet); + ECesiumFeatureIdAttributeStatus status = + UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureIDAttributeStatus( + attribute); + if (status == ECesiumFeatureIdAttributeStatus::Valid) { + std::string generatedName = + "_FEATURE_ID_" + std::to_string(attribute.getAttributeIndex()); + return FString(generatedName.c_str()); + } + } + + if (type == ECesiumFeatureIdSetType::Instance) { + FCesiumFeatureIdAttribute attribute = + UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( + featureIDSet); + ECesiumFeatureIdAttributeStatus status = + UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureIDAttributeStatus( + attribute); + if (status == ECesiumFeatureIdAttributeStatus::Valid) { + std::string generatedName = "_FEATURE_INSTANCE_ID_" + + std::to_string(attribute.getAttributeIndex()); + return FString(generatedName.c_str()); + } + } + + if (type == ECesiumFeatureIdSetType::Texture) { + std::string generatedName = + "_FEATURE_ID_TEXTURE_" + std::to_string(featureIdTextureCounter); + featureIdTextureCounter++; + return FString(generatedName.c_str()); + } + + if (type == ECesiumFeatureIdSetType::Implicit) { + return FString("_IMPLICIT_FEATURE_ID"); + } + + if (type == ECesiumFeatureIdSetType::InstanceImplicit) { + return FString("_IMPLICIT_FEATURE_INSTANCE_ID"); + } + + // If for some reason an empty / invalid feature ID set was constructed, + // return an empty name. + return FString(); +} +} // namespace + +void CesiumFeaturesMetadataViewer::gatherGltfFeaturesMetadata() { + if (!this->_pTileset.IsValid()) { + return; + } + + ACesium3DTileset& tileset = *this->_pTileset; + + for (const UActorComponent* pComponent : tileset.GetComponents()) { + const auto* pPrimitive = Cast(pComponent); + if (!pPrimitive) { + continue; + } + + const FCesiumModelMetadata& modelMetadata = + UCesiumModelMetadataBlueprintLibrary::GetModelMetadata(pPrimitive); + + const TArray& propertyTables = + UCesiumModelMetadataBlueprintLibrary::GetPropertyTables(modelMetadata); + this->gatherGltfPropertySources< + FCesiumPropertyTable, + UCesiumPropertyTableBlueprintLibrary, + FCesiumPropertyTableProperty, + UCesiumPropertyTablePropertyBlueprintLibrary>(propertyTables); + + const TArray& propertyTextures = + UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures( + modelMetadata); + this->gatherGltfPropertySources< + FCesiumPropertyTexture, + UCesiumPropertyTextureBlueprintLibrary, + FCesiumPropertyTextureProperty, + UCesiumPropertyTexturePropertyBlueprintLibrary>(propertyTextures); + + const FCesiumPrimitiveMetadata& primitiveMetadata = + UCesiumPrimitiveMetadataBlueprintLibrary::GetPrimitiveMetadata( + pPrimitive); + + const TArray propertyTextureIndices = + UCesiumPrimitiveMetadataBlueprintLibrary::GetPropertyTextureIndices( + primitiveMetadata); + for (int64 propertyTextureIndex : propertyTextureIndices) { + if (!propertyTextures.IsValidIndex(propertyTextureIndex)) { + continue; + } + + const FCesiumPropertyTexture& propertyTexture = + propertyTextures[propertyTextureIndex]; + FString propertyTextureName = getNameForPropertySource(propertyTexture); + this->_propertyTextureNames.Emplace(propertyTextureName); + } + + const FCesiumPrimitiveFeatures& primitiveFeatures = + UCesiumPrimitiveFeaturesBlueprintLibrary::GetPrimitiveFeatures( + pPrimitive); + + const TArray& featureIdSets = + UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets( + primitiveFeatures); + + int32 featureIdTextureCounter = 0; + + for (const FCesiumFeatureIdSet& featureIdSet : featureIdSets) { + ECesiumFeatureIdSetType type = + UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( + featureIdSet); + int64 count = + UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIdSet); + if (type == ECesiumFeatureIdSetType::None || count == 0) { + // Empty or invalid feature ID set. Skip. + continue; + } + + FString name = + getNameForFeatureIdSet(featureIdSet, featureIdTextureCounter); + + FeatureIdSetView* pFeatureIdSetView = + this->_featureIdSets.FindByPredicate( + [&name](const FeatureIdSetView& existing) { + return *existing.pName == name; + }); + + if (!pFeatureIdSetView) { + int32 index = this->_featureIdSets.Emplace( + FeatureIdSetView{.pName = getSharedRef(name)}); + pFeatureIdSetView = &this->_featureIdSets[index]; + } + + const int64 propertyTableIndex = + UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex( + featureIdSet); + FString propertyTableName; + if (propertyTables.IsValidIndex(propertyTableIndex)) { + propertyTableName = + getNameForPropertySource(propertyTables[propertyTableIndex]); + } + + FeatureIdSetInstance Instance{ + .pFeatureIdSetName = pFeatureIdSetView->pName, + .type = type, + .pPropertyTableName = getSharedRef(propertyTableName)}; + + TSharedRef* pExistingInstance = + pFeatureIdSetView->instances.FindByPredicate( + [&Instance](const TSharedRef& pExisting) { + return Instance == *pExisting; + }); + if (!pExistingInstance) { + pFeatureIdSetView->instances.Emplace( + MakeShared(std::move(Instance))); + } + } + } +} + +bool CesiumFeaturesMetadataViewer::PropertyInstance::operator==( + const PropertyInstance& rhs) const { + if (*pPropertyId != *rhs.pPropertyId || + propertyDetails != rhs.propertyDetails) { + return false; + } + + if (encodingDetails.has_value() != rhs.encodingDetails.has_value()) { + return false; + } else if (encodingDetails) { + return encodingDetails->conversionMethods == + rhs.encodingDetails->conversionMethods; + } + + return true; +} + +bool CesiumFeaturesMetadataViewer::PropertyInstance::operator!=( + const PropertyInstance& property) const { + return !operator==(property); +} + +bool CesiumFeaturesMetadataViewer::FeatureIdSetInstance::operator==( + const FeatureIdSetInstance& rhs) const { + return *pFeatureIdSetName == *rhs.pFeatureIdSetName && type == rhs.type && + *pPropertyTableName == *rhs.pPropertyTableName; +} + +bool CesiumFeaturesMetadataViewer::FeatureIdSetInstance::operator!=( + const FeatureIdSetInstance& property) const { + return !operator==(property); +} + +namespace { + +template +TArray> getSharedRefs( + const TArray>& options, + const TArray& selection) { + TArray> result; + UEnum* pEnum = StaticEnum(); + if (!pEnum) { + return result; + } + + // Assumes populateEnumOptions will be initialized in enum order! + for (TEnum value : selection) { + int32 index = pEnum->GetIndexByValue(int64(value)); + CESIUM_ASSERT(index >= 0 && index < options.Num()); + result.Add(options[index]); + } + + return result; +} + +TArray getSupportedConversionsForProperty( + const FCesiumMetadataPropertyDetails& PropertyDetails) { + TArray result; + if (PropertyDetails.Type == ECesiumMetadataType::Invalid) { + return result; + } + + result.Reserve(2); + result.Add(ECesiumEncodedMetadataConversion::Coerce); + + if (PropertyDetails.Type == ECesiumMetadataType::String) { + result.Add(ECesiumEncodedMetadataConversion::ParseColorFromString); + } + + return result; +} +} // namespace + +template < + typename TSource, + typename TSourceBlueprintLibrary, + typename TSourceProperty, + typename TSourcePropertyBlueprintLibrary> +void CesiumFeaturesMetadataViewer::gatherGltfPropertySources( + const TArray& sources) { + + for (const TSource& source : sources) { + FString sourceName = getNameForPropertySource(source); + TSharedRef pSourceName = this->getSharedRef(sourceName); + + constexpr EPropertySource sourceType = + std::is_same_v + ? EPropertySource::PropertyTable + : EPropertySource::PropertyTexture; + + PropertySourceView* pSource = this->_metadataSources.FindByPredicate( + [&sourceName, sourceType](const PropertySourceView& existingSource) { + return *existingSource.pName == sourceName && + existingSource.type == sourceType; + }); + + if (!pSource) { + int32 index = this->_metadataSources.Emplace( + PropertySourceView{.pName = pSourceName, .type = sourceType}); + pSource = &this->_metadataSources[index]; + } + + const TMap& properties = + TSourceBlueprintLibrary::GetProperties(source); + for (const auto& propertyIt : properties) { + TSharedRef pPropertyId = this->getSharedRef(propertyIt.Key); + TSharedRef* pProperty = pSource->properties.FindByPredicate( + [&pPropertyId](const TSharedRef& pExistingProperty) { + return **pExistingProperty->pId == *pPropertyId; + }); + + if (!pProperty) { + PropertyView newProperty{.pId = pPropertyId, .instances = {}}; + int32 index = pSource->properties.Emplace( + MakeShared(std::move(newProperty))); + pProperty = &pSource->properties[index]; + } + + PropertyView& property = **pProperty; + + FCesiumMetadataPropertyDetails propertyDetails; + + const FCesiumMetadataValueType valueType = + TSourcePropertyBlueprintLibrary::GetValueType(propertyIt.Value); + + // Skip any invalid type properties. + if (valueType.Type == ECesiumMetadataType::Invalid) + continue; + + propertyDetails.Type = valueType.Type; + propertyDetails.ComponentType = valueType.ComponentType; + propertyDetails.bIsArray = valueType.bIsArray; + propertyDetails.ArraySize = + TSourcePropertyBlueprintLibrary::GetArraySize(propertyIt.Value); + propertyDetails.bIsNormalized = + TSourcePropertyBlueprintLibrary::IsNormalized(propertyIt.Value); + + FCesiumMetadataValue offset = + TSourcePropertyBlueprintLibrary::GetOffset(propertyIt.Value); + propertyDetails.bHasOffset = + !UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset); + + FCesiumMetadataValue scale = + TSourcePropertyBlueprintLibrary::GetScale(propertyIt.Value); + propertyDetails.bHasScale = + !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); + + FCesiumMetadataValue noData = + TSourcePropertyBlueprintLibrary::GetNoDataValue(propertyIt.Value); + propertyDetails.bHasNoDataValue = + !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); + + FCesiumMetadataValue defaultValue = + TSourcePropertyBlueprintLibrary::GetDefaultValue(propertyIt.Value); + propertyDetails.bHasDefaultValue = + !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); + + PropertyInstance instance{ + .pPropertyId = pPropertyId, + .propertyDetails = std::move(propertyDetails), + .pSourceName = pSourceName}; + + if constexpr (std::is_same_v< + TSourceProperty, + FCesiumPropertyTableProperty>) { + // Do some silly TSharedRef lookup since it's required by SComboBox. + TArray> + supportedConversions = getSharedRefs( + this->_conversionOptions, + getSupportedConversionsForProperty(propertyDetails)); + instance.encodingDetails = PropertyInstanceEncodingDetails{ + .conversionMethods = std::move(supportedConversions)}; + } + + TSharedRef* pExistingInstance = + property.instances.FindByPredicate( + [&instance]( + const TSharedRef& existingInstance) { + return instance == *existingInstance; + }); + if (!pExistingInstance) { + property.instances.Emplace( + MakeShared(std::move(instance))); + } + } + } +} + +namespace { +template FString enumToNameString(TEnum value) { + const UEnum* pEnum = StaticEnum(); + return pEnum ? pEnum->GetNameStringByValue((int64)value) : FString(); +} +} // namespace + +namespace { +template FText getEnumDisplayNameText(TEnum value) { + UEnum* pEnum = StaticEnum(); + if (pEnum) { + return pEnum->GetDisplayNameTextByValue(int64(value)); + } + + return FText::FromString(FString()); +} +} // namespace + +TSharedRef CesiumFeaturesMetadataViewer::createPropertyInstanceRow( + TSharedRef pItem, + const TSharedRef& list) { + FString typeString = pItem->propertyDetails.GetValueType().ToString(); + if (pItem->propertyDetails.bIsNormalized) { + typeString += TEXT(" (Normalized)"); + } + + if (pItem->propertyDetails.bIsArray) { + int64 arraySize = pItem->propertyDetails.ArraySize; + typeString += arraySize > 0 + ? FString::Printf(TEXT(" with %d elements"), arraySize) + : TEXT(" of variable size"); + } + + TArray qualifierList; + if (pItem->propertyDetails.bHasOffset) { + qualifierList.Add("Offset"); + } + if (pItem->propertyDetails.bHasScale) { + qualifierList.Add("Scale"); + } + if (pItem->propertyDetails.bHasNoDataValue) { + qualifierList.Add("'No Data' Value"); + } + if (pItem->propertyDetails.bHasDefaultValue) { + qualifierList.Add("Default Value"); + } + + FString qualifierString = + qualifierList.IsEmpty() + ? FString() + : "Contains " + FString::Join(qualifierList, TEXT(", ")); + + TSharedRef content = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(0.45f).Padding(5.0f).VAlign( + EVerticalAlignment::VAlign_Center) + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(typeString)) + .ToolTipText(FText::FromString(FString( + "The type of the property as defined in the EXT_structural_metadata extension.")))] + + SHorizontalBox::Slot() + .AutoWidth() + .MaxWidth(1.0f) + .Padding(5.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + .VAlign(EVerticalAlignment::VAlign_Center) + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(qualifierString)) + .ToolTipText(FText::FromString( + "Notable qualities of the property that require additional nodes to be generated for the material."))]; + + if (pItem->encodingDetails) { + FCesiumMetadataEncodingDetails bestFitEncodingDetails = + FCesiumMetadataEncodingDetails::GetBestFitForProperty( + pItem->propertyDetails); + + createEnumComboBox( + pItem->encodingDetails->pConversionCombo, + pItem->encodingDetails->conversionMethods, + bestFitEncodingDetails.Conversion, + FString()); + + createEnumComboBox( + pItem->encodingDetails->pEncodedTypeCombo, + this->_encodedTypeOptions, + bestFitEncodingDetails.Type, + TEXT( + "The type to which to coerce the property's data. Affects the texture format that is used to encode the data.")); + createEnumComboBox( + pItem->encodingDetails->pEncodedComponentTypeCombo, + this->_encodedComponentTypeOptions, + bestFitEncodingDetails.ComponentType, + TEXT( + "The component type to which to coerce the property's data. Affects the texture format that is used to encode the data.")); + + if (pItem->encodingDetails->pConversionCombo.IsValid()) { + content->AddSlot().FillWidth(0.65).Padding(5.0f).VAlign( + EVerticalAlignment::VAlign_Center) + [pItem->encodingDetails->pConversionCombo->AsShared()]; + } + + auto visibilityLambda = TAttribute::Create([pItem]() { + if (!pItem->encodingDetails) { + return EVisibility::Hidden; + } + + bool show = false; + if (pItem->encodingDetails->pConversionCombo.IsValid()) { + show = pItem->encodingDetails->pConversionCombo->GetSelectedItem() + .IsValid(); + } + return show ? EVisibility::Visible : EVisibility::Hidden; + }); + + if (pItem->encodingDetails->pEncodedTypeCombo.IsValid()) { + pItem->encodingDetails->pEncodedTypeCombo->SetVisibility( + visibilityLambda); + content->AddSlot().AutoWidth().Padding(5.0f).VAlign( + EVerticalAlignment::VAlign_Center) + [pItem->encodingDetails->pEncodedTypeCombo->AsShared()]; + } + + if (pItem->encodingDetails->pEncodedComponentTypeCombo.IsValid()) { + pItem->encodingDetails->pEncodedComponentTypeCombo->SetVisibility( + visibilityLambda); + content->AddSlot().AutoWidth().Padding(5.0f).VAlign( + EVerticalAlignment::VAlign_Center) + [pItem->encodingDetails->pEncodedComponentTypeCombo->AsShared()]; + } + } + + content->AddSlot() + .AutoWidth() + .HAlign(EHorizontalAlignment::HAlign_Right) + .VAlign( + EVerticalAlignment:: + VAlign_Center)[PropertyCustomizationHelpers::MakeNewBlueprintButton( + FSimpleDelegate::CreateLambda( + [this, pItem]() { this->registerPropertyInstance(pItem); }), + FText::FromString(TEXT( + "Add this property to the tileset's CesiumFeaturesMetadataComponent.")), + TAttribute::Create( + [this, pItem]() { return this->canBeRegistered(pItem); }))]; + + return SNew(STableRow>, list) + .Content()[SNew(SBox) + .HAlign(EHorizontalAlignment::HAlign_Fill) + .Content()[content]]; +} + +TSharedRef CesiumFeaturesMetadataViewer::createGltfPropertyDropdown( + TSharedRef pItem, + const TSharedRef& list) { + return SNew(STableRow>, list) + .Content()[SNew(SExpandableArea) + .InitiallyCollapsed(true) + .HeaderContent()[SNew(STextBlock) + .Text(FText::FromString(*pItem->pId))] + .BodyContent()[SNew( + SListView>) + .ListItemsSource(&pItem->instances) + .SelectionMode(ESelectionMode::None) + .OnGenerateRow( + this, + &CesiumFeaturesMetadataViewer:: + createPropertyInstanceRow)]]; +} + +void CesiumFeaturesMetadataViewer::createGltfPropertySourceDropdown( + TSharedRef& pContent, + const PropertySourceView& source) { + FString sourceDisplayName = FString::Printf(TEXT("\"%s\""), **source.pName); + switch (source.type) { + case EPropertySource::PropertyTable: + sourceDisplayName += " (Property Table)"; + break; + case EPropertySource::PropertyTexture: + sourceDisplayName += " (Property Texture)"; + break; + } + + pContent->AddSlot() + [SNew(SExpandableArea) + .InitiallyCollapsed(false) + .HeaderContent()[SNew(STextBlock) + .Text(FText::FromString(sourceDisplayName))] + .BodyContent() + [SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(0.05f) + + SHorizontalBox::Slot() + [SNew(SListView>) + .ListItemsSource(&source.properties) + .SelectionMode(ESelectionMode::None) + .OnGenerateRow( + this, + &CesiumFeaturesMetadataViewer:: + createGltfPropertyDropdown)]]]; +} + +template +TSharedRef CesiumFeaturesMetadataViewer::createEnumDropdownOption( + TSharedRef pOption) { + return SNew(STextBlock).Text(getEnumDisplayNameText(*pOption)); +} + +template +void CesiumFeaturesMetadataViewer::createEnumComboBox( + TSharedPtr>>& pComboBox, + const TArray>& options, + TEnum initialValue, + const FString& tooltip) { + CESIUM_ASSERT(options.Num() > 0); + + int32 initialIndex = 0; + for (int32 i = 0; i < options.Num(); i++) { + if (initialValue == *options[i]) { + initialIndex = i; + break; + } + } + + SAssignNew(pComboBox, SComboBox>) + .OptionsSource(&options) + .InitiallySelectedItem(options[initialIndex]) + .OnGenerateWidget( + this, + &CesiumFeaturesMetadataViewer::createEnumDropdownOption) + .Content()[SNew(STextBlock) + .MinDesiredWidth(50.0f) + .Text_Lambda([&pComboBox]() { + return pComboBox->GetSelectedItem().IsValid() + ? getEnumDisplayNameText( + *pComboBox->GetSelectedItem()) + : FText::FromString(FString()); + }) + .ToolTipText_Lambda([&pComboBox, tooltip]() { + if constexpr (std::is_same_v< + TEnum, + ECesiumEncodedMetadataConversion>) { + UEnum* pEnum = + StaticEnum(); + if (pEnum) { + return pComboBox->GetSelectedItem().IsValid() + ? pEnum->GetToolTipTextByIndex(int64( + *pComboBox->GetSelectedItem())) + : FText::FromString(FString()); + } + } + return FText::FromString(tooltip); + })]; +} + +TSharedRef +CesiumFeaturesMetadataViewer::createFeatureIdSetInstanceRow( + TSharedRef pItem, + const TSharedRef& list) { + TSharedRef pBox = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(0.5f).Padding(5.0f).VAlign( + EVerticalAlignment::VAlign_Center) + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(enumToNameString(pItem->type)))]; + + if (!pItem->pPropertyTableName->IsEmpty()) { + FString sourceString = FString::Printf( + TEXT("Used with \"%s\" (Property Table)"), + **pItem->pPropertyTableName); + pBox->AddSlot() + .FillWidth(1.0f) + .Padding(5.0f) + .HAlign(HAlign_Fill) + .VAlign(EVerticalAlignment::VAlign_Center) + [SNew(STextBlock) + .AutoWrapText(true) + .Text(FText::FromString(sourceString)) + .ToolTipText(FText::FromString( + "The property table with which this feature ID set should be used. " + "Add properties from the corresponding property table under \"glTF Metadata\"."))]; + } + + pBox->AddSlot() + .AutoWidth() + .HAlign(EHorizontalAlignment::HAlign_Right) + .VAlign( + EVerticalAlignment:: + VAlign_Center)[PropertyCustomizationHelpers::MakeNewBlueprintButton( + FSimpleDelegate::CreateLambda( + [this, pItem]() { this->registerFeatureIdSetInstance(pItem); }), + FText::FromString(TEXT( + "Add this feature ID set to the tileset's CesiumFeaturesMetadataComponent.")), + TAttribute::Create( + [this, pItem]() { return this->canBeRegistered(pItem); }))]; + + return SNew(STableRow>, list) + .Content()[SNew(SBox) + .HAlign(EHorizontalAlignment::HAlign_Fill) + .Content()[std::move(pBox)]]; +} + +void CesiumFeaturesMetadataViewer::createGltfFeatureIdSetDropdown( + TSharedRef& pContent, + const FeatureIdSetView& featureIdSet) { + pContent->AddSlot() + [SNew(SExpandableArea) + .InitiallyCollapsed(false) + .HeaderContent()[SNew(STextBlock) + .Text(FText::FromString(*featureIdSet.pName))] + .BodyContent()[SNew(SListView>) + .ListItemsSource(&featureIdSet.instances) + .SelectionMode(ESelectionMode::None) + .OnGenerateRow( + this, + &CesiumFeaturesMetadataViewer:: + createFeatureIdSetInstanceRow)]]; +} + +namespace { +template +TProperty* findProperty( + TArray& sources, + const FString& sourceName, + const FString& propertyName, + bool createIfMissing) { + TPropertySource* pPropertySource = sources.FindByPredicate( + [&sourceName](const TPropertySource& existingSource) { + return sourceName == existingSource.Name; + }); + if (!pPropertySource && !createIfMissing) { + return nullptr; + } + + if (!pPropertySource) { + int32 index = + sources.Emplace(TPropertySource{sourceName, TArray()}); + pPropertySource = &sources[index]; + } + + TProperty* pProperty = pPropertySource->Properties.FindByPredicate( + [&propertyName](const TProperty& existingProperty) { + return propertyName == existingProperty.Name; + }); + + if (!pProperty && createIfMissing) { + int32 index = pPropertySource->Properties.Emplace(); + pProperty = &pPropertySource->Properties[index]; + pProperty->Name = propertyName; + } + + return pProperty; +} + +FCesiumFeatureIdSetDescription* findFeatureIdSet( + TArray& featureIdSets, + const FString& name, + bool createIfMissing) { + FCesiumFeatureIdSetDescription* pFeatureIdSet = featureIdSets.FindByPredicate( + [&name](const FCesiumFeatureIdSetDescription& existingSet) { + return name == existingSet.Name; + }); + + if (!pFeatureIdSet && createIfMissing) { + int32 index = featureIdSets.Emplace(); + pFeatureIdSet = &featureIdSets[index]; + pFeatureIdSet->Name = name; + } + + return pFeatureIdSet; +} +} // namespace + +namespace { +FCesiumMetadataEncodingDetails getSelectedEncodingDetails( + const TSharedPtr>>& + pConversionCombo, + const TSharedPtr>>& + pEncodedTypeCombo, + const TSharedPtr< + SComboBox>>& + pEncodedComponentTypeCombo) { + if (!pConversionCombo || !pEncodedTypeCombo || !pEncodedComponentTypeCombo) + return FCesiumMetadataEncodingDetails(); + + TSharedPtr pConversion = + pConversionCombo->GetSelectedItem(); + TSharedPtr pEncodedType = + pEncodedTypeCombo->GetSelectedItem(); + TSharedPtr pEncodedComponentType = + pEncodedComponentTypeCombo->GetSelectedItem(); + + return FCesiumMetadataEncodingDetails( + pEncodedType.IsValid() ? *pEncodedType : ECesiumEncodedMetadataType::None, + pEncodedComponentType.IsValid() + ? *pEncodedComponentType + : ECesiumEncodedMetadataComponentType::None, + pConversion.IsValid() ? *pConversion + : ECesiumEncodedMetadataConversion::None); +} +} // namespace + +bool CesiumFeaturesMetadataViewer::canBeRegistered( + TSharedRef pItem) { + if (pItem->propertyDetails.Type == ECesiumMetadataType::Invalid) { + return false; + } + + if (!this->_pFeaturesMetadataComponent.IsValid()) { + return false; + } + + UCesiumFeaturesMetadataComponent& featuresMetadata = + *this->_pFeaturesMetadataComponent; + + if (pItem->encodingDetails) { + // Validate encoding details first. + FCesiumMetadataEncodingDetails selectedEncodingDetails = + getSelectedEncodingDetails( + pItem->encodingDetails->pConversionCombo, + pItem->encodingDetails->pEncodedTypeCombo, + pItem->encodingDetails->pEncodedComponentTypeCombo); + + switch (selectedEncodingDetails.Conversion) { + case ECesiumEncodedMetadataConversion::Coerce: + case ECesiumEncodedMetadataConversion::ParseColorFromString: + // Ensure that we're coercing to a valid type. + if (!selectedEncodingDetails.HasValidType()) + return false; + else + break; + case ECesiumEncodedMetadataConversion::None: + default: + return false; + } + + // Then, check whether the property already exists with the same + // information. + FCesiumPropertyTablePropertyDescription* pProperty = findProperty< + FCesiumPropertyTableDescription, + FCesiumPropertyTablePropertyDescription>( + featuresMetadata.Description.ModelMetadata.PropertyTables, + *pItem->pSourceName, + *pItem->pPropertyId, + false); + + return !pProperty || pProperty->PropertyDetails != pItem->propertyDetails || + pProperty->EncodingDetails != selectedEncodingDetails; + } else { + FCesiumPropertyTexturePropertyDescription* pProperty = findProperty< + FCesiumPropertyTextureDescription, + FCesiumPropertyTexturePropertyDescription>( + featuresMetadata.Description.ModelMetadata.PropertyTextures, + *pItem->pSourceName, + *pItem->pPropertyId, + false); + + return !pProperty || pProperty->PropertyDetails != pItem->propertyDetails; + } + + return false; +} + +bool CesiumFeaturesMetadataViewer::canBeRegistered( + TSharedRef pItem) { + if (pItem->type == ECesiumFeatureIdSetType::None) { + return false; + } + + if (!this->_pFeaturesMetadataComponent.IsValid()) { + return false; + } + + UCesiumFeaturesMetadataComponent& featuresMetadata = + *this->_pFeaturesMetadataComponent; + FCesiumFeatureIdSetDescription* pFeatureIdSet = findFeatureIdSet( + featuresMetadata.Description.PrimitiveFeatures.FeatureIdSets, + *pItem->pFeatureIdSetName, + false); + + return !pFeatureIdSet || + pFeatureIdSet->PropertyTableName != *pItem->pPropertyTableName; +} + +void CesiumFeaturesMetadataViewer::registerPropertyInstance( + TSharedRef pItem) { + if (!this->_pFeaturesMetadataComponent.IsValid()) { + UE_LOG( + LogCesiumEditor, + Error, + TEXT( + "This window was opened for a now invalid CesiumFeaturesMetadataComponent.")) + return; + } + + UKismetSystemLibrary::BeginTransaction( + TEXT("Cesium Features / Metadata Viewer"), + FText::FromString( + FString("Register property instance with ACesium3DTileset")), + this->_pFeaturesMetadataComponent.Get()); + this->_pFeaturesMetadataComponent->PreEditChange(NULL); + + FCesiumFeaturesMetadataDescription& description = + this->_pFeaturesMetadataComponent->Description; + + if (pItem->encodingDetails) { + CESIUM_ASSERT( + pItem->encodingDetails->pConversionCombo && + pItem->encodingDetails->pEncodedTypeCombo && + pItem->encodingDetails->pEncodedComponentTypeCombo); + + FCesiumPropertyTablePropertyDescription* pProperty = findProperty< + FCesiumPropertyTableDescription, + FCesiumPropertyTablePropertyDescription>( + description.ModelMetadata.PropertyTables, + *pItem->pSourceName, + *pItem->pPropertyId, + true); + + CESIUM_ASSERT(pProperty != nullptr); + + FCesiumPropertyTablePropertyDescription& property = *pProperty; + property.PropertyDetails = pItem->propertyDetails; + property.EncodingDetails = getSelectedEncodingDetails( + pItem->encodingDetails->pConversionCombo, + pItem->encodingDetails->pEncodedTypeCombo, + pItem->encodingDetails->pEncodedComponentTypeCombo); + } else { + FCesiumPropertyTexturePropertyDescription* pProperty = findProperty< + FCesiumPropertyTextureDescription, + FCesiumPropertyTexturePropertyDescription>( + description.ModelMetadata.PropertyTextures, + *pItem->pSourceName, + *pItem->pPropertyId, + true); + + CESIUM_ASSERT(pProperty != nullptr); + + FCesiumPropertyTexturePropertyDescription& property = *pProperty; + property.PropertyDetails = pItem->propertyDetails; + + if (this->_propertyTextureNames.Contains(*pItem->pSourceName)) { + description.PrimitiveMetadata.PropertyTextureNames.Add( + *pItem->pSourceName); + } + } + + this->_pFeaturesMetadataComponent->PostEditChange(); + UKismetSystemLibrary::EndTransaction(); +} + +void CesiumFeaturesMetadataViewer::registerFeatureIdSetInstance( + TSharedRef pItem) { + if (!this->_pFeaturesMetadataComponent.IsValid()) { + UE_LOG( + LogCesiumEditor, + Error, + TEXT( + "This window was opened for a now invalid CesiumFeaturesMetadataComponent.")) + return; + } + + UKismetSystemLibrary::BeginTransaction( + TEXT("Cesium Features / Metadata Viewer"), + FText::FromString( + FString("Register feature ID set instance with ACesium3DTileset")), + this->_pFeaturesMetadataComponent.Get()); + this->_pFeaturesMetadataComponent->PreEditChange(NULL); + + FCesiumFeaturesMetadataDescription& description = + this->_pFeaturesMetadataComponent->Description; + + FCesiumFeatureIdSetDescription* pFeatureIdSet = findFeatureIdSet( + description.PrimitiveFeatures.FeatureIdSets, + *pItem->pFeatureIdSetName, + true); + CESIUM_ASSERT(pFeatureIdSet != nullptr); + + pFeatureIdSet->Type = pItem->type; + pFeatureIdSet->PropertyTableName = *pItem->pPropertyTableName; + + this->_pFeaturesMetadataComponent->PostEditChange(); + UKismetSystemLibrary::EndTransaction(); +} + +TSharedRef +CesiumFeaturesMetadataViewer::getSharedRef(const FString& string) { + return _stringMap.Contains(string) + ? _stringMap[string] + : _stringMap.Emplace(string, MakeShared(string)); +} + +void CesiumFeaturesMetadataViewer::initializeStaticVariables() { + if (_conversionOptions.IsEmpty()) { + populateEnumOptions(_conversionOptions); + } + if (_encodedTypeOptions.IsEmpty()) { + populateEnumOptions(_encodedTypeOptions); + } + if (_encodedComponentTypeOptions.IsEmpty()) { + populateEnumOptions( + _encodedComponentTypeOptions); + } +} diff --git a/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.h b/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.h new file mode 100644 index 000000000..54d744c38 --- /dev/null +++ b/Source/CesiumEditor/Private/CesiumFeaturesMetadataViewer.h @@ -0,0 +1,240 @@ +// Copyright 2020-2025 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CesiumFeatureIdSet.h" +#include "CesiumMetadataEncodingDetails.h" +#include "CesiumMetadataPropertyDetails.h" +#include "Containers/Array.h" +#include "Containers/Map.h" +#include "Templates/SharedPointer.h" +#include "Templates/UniquePtr.h" +#include "UObject/WeakObjectPtr.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/SWindow.h" + +#include + +class ACesium3DTileset; +class UCesiumFeaturesMetadataComponent; +struct FCesiumModelMetadata; + +class CesiumFeaturesMetadataViewer : public SWindow { + SLATE_BEGIN_ARGS(CesiumFeaturesMetadataViewer) {} + /** + * The tileset that is being queried for features and metadata. + */ + SLATE_ARGUMENT(TWeakObjectPtr, Tileset) + SLATE_END_ARGS() + +public: + static void Open(TWeakObjectPtr pTileset); + void Construct(const FArguments& InArgs); + + /** + * Syncs the window's UI with the current view of the tileset. + */ + void SyncAndRebuildUI(); + +private: + /** + * Encoding details for a `CesiumGltf::PropertyTableProperty` instance. + */ + struct PropertyInstanceEncodingDetails { + /** + * The possible conversion methods for this property. Contains a subset of + * the values in ECesiumEncodedMetadataConversion. + */ + TArray> conversionMethods; + /** + * The combo box widget for selecting the conversion method. + */ + TSharedPtr>> + pConversionCombo; + /** + * The combo box widget for selecting the encoded type. + */ + TSharedPtr>> + pEncodedTypeCombo; + /** + * The combo box widget for selecting the encoded component type. + */ + TSharedPtr>> + pEncodedComponentTypeCombo; + }; + + /** + * An instance of a CesiumGltf::PropertyTableProperty for a particular glTF in + * the tileset. + * + * It is technically possible for a tileset to have models with the same + * property, but different schema definitions. This attempts to capture each + * different instance so the user can make an informed choice about the + * behavior. + */ + struct PropertyInstance { + /** + * The ID of the property of which this is an instance. + */ + TSharedRef pPropertyId; + /** + * The type and other details of this property instance. + */ + FCesiumMetadataPropertyDetails propertyDetails; + /** + * The name of the source with which this property is associated. + */ + TSharedRef pSourceName; + /** + * Additional details encoding this property instance. Only used for + * `CesiumGltf::PropertyTableProperty`. + */ + std::optional encodingDetails; + + bool operator==(const PropertyInstance& rhs) const; + bool operator!=(const PropertyInstance& rhs) const; + }; + + /** + * A view of a class property in EXT_structural_metadata. + */ + struct PropertyView { + /** + * The ID of the property. + */ + TSharedRef pId; + /** + * The instances of this property. + */ + TArray> instances; + }; + + enum EPropertySource { PropertyTable, PropertyTexture }; + + /** + * A view of either a CesiumGltf::PropertyTable or + * CesiumGltf::PropertyTexture. + */ + struct PropertySourceView { + /** + * The name generated for this property source. + */ + TSharedRef pName; + /** + * The type of this source. + */ + EPropertySource type; + /** + * The properties belonging to this source. + */ + TArray> properties; + }; + + /** + * A view of an instance of a CesiumGltf::FeatureId for a particular glTF in + * the tileset. Feature IDs in EXT_mesh_features are referenced by index, not + * by name, so it is technically possible for two feature ID sets with the + * same index to be differently typed, or to reference different property + * tables. This attempts to capture each different instance so the user can + * make an informed choice about the behavior. + */ + struct FeatureIdSetInstance { + /** + * The name of the feature ID set for which this is an instance. + */ + TSharedRef pFeatureIdSetName; + /** + * The type of this instance. + */ + ECesiumFeatureIdSetType type; + /** + * The name of the property table that this instance references. + */ + TSharedRef pPropertyTableName; + + bool operator==(const FeatureIdSetInstance& rhs) const; + bool operator!=(const FeatureIdSetInstance& rhs) const; + }; + + /** + * A view of a CesiumGltf::FeatureId. + */ + struct FeatureIdSetView { + /** + * The name generated for this feature ID set. + */ + TSharedRef pName; + /** + * The instances of this feature ID set. + */ + TArray> instances; + }; + + template < + typename TSource, + typename TSourceBlueprintLibrary, + typename TSourceProperty, + typename TSourcePropertyBlueprintLibrary> + void gatherGltfPropertySources(const TArray& sources); + void gatherGltfFeaturesMetadata(); + + TSharedRef createPropertyInstanceRow( + TSharedRef pItem, + const TSharedRef& list); + TSharedRef createGltfPropertyDropdown( + TSharedRef pItem, + const TSharedRef& list); + void createGltfPropertySourceDropdown( + TSharedRef& pContent, + const PropertySourceView& source); + + template + TSharedRef createEnumDropdownOption(TSharedRef pOption); + template + void createEnumComboBox( + TSharedPtr>>& pComboBox, + const TArray>& options, + TEnum initialValue, + const FString& tooltip); + + TSharedRef createFeatureIdSetInstanceRow( + TSharedRef pItem, + const TSharedRef& list); + void createGltfFeatureIdSetDropdown( + TSharedRef& pContent, + const FeatureIdSetView& property); + + bool canBeRegistered(TSharedRef pItem); + bool canBeRegistered(TSharedRef pItem); + + void registerPropertyInstance(TSharedRef pItem); + void registerFeatureIdSetInstance(TSharedRef pItem); + + static TSharedRef getSharedRef(const FString& string); + static void initializeStaticVariables(); + + static TSharedPtr _pExistingWindow; + TSharedPtr _pContent; + + TWeakObjectPtr _pTileset; + TWeakObjectPtr _pFeaturesMetadataComponent; + + // The current Features / Metadata implementation folds the class / property + // schemas into each implementation of PropertyTable, PropertyTableProperty, + // etc. So this functions as a property source-centric view instead of a + // class-based one. + TArray _metadataSources; + TArray _featureIdSets; + TSet _propertyTextureNames; + + // Avoid allocating numerous instances of simple enum values (because shared + // pointers /refs are required for SComboBox). + static TArray> + _conversionOptions; + static TArray> _encodedTypeOptions; + static TArray> + _encodedComponentTypeOptions; + // Lookup map to reduce the number of strings allocated for duplicate property + // names. + static TMap> _stringMap; +}; diff --git a/Source/CesiumRuntime/Private/CesiumFeaturesMetadataComponent.cpp b/Source/CesiumRuntime/Private/CesiumFeaturesMetadataComponent.cpp index 6f44a316c..f85dcf688 100644 --- a/Source/CesiumRuntime/Private/CesiumFeaturesMetadataComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeaturesMetadataComponent.cpp @@ -1,4 +1,4 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #include "CesiumFeaturesMetadataComponent.h" #include "Cesium3DTileset.h" @@ -45,358 +45,9 @@ extern UNREALED_API class UEditorEngine* GEditor; using namespace EncodedFeaturesMetadata; using namespace GenerateMaterialUtility; -namespace { -void AutoFillPropertyTableDescriptions( - TArray& Descriptions, - const FCesiumModelMetadata& ModelMetadata) { - const TArray& propertyTables = - UCesiumModelMetadataBlueprintLibrary::GetPropertyTables(ModelMetadata); - - for (const auto& propertyTable : propertyTables) { - FString propertyTableName = getNameForPropertyTable(propertyTable); - - FCesiumPropertyTableDescription* pDescription = - Descriptions.FindByPredicate( - [&name = propertyTableName]( - const FCesiumPropertyTableDescription& existingPropertyTable) { - return existingPropertyTable.Name == name; - }); - - if (!pDescription) { - pDescription = &Descriptions.Emplace_GetRef(); - pDescription->Name = propertyTableName; - } - - const TMap& properties = - UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable); - for (const auto& propertyIt : properties) { - auto pExistingProperty = pDescription->Properties.FindByPredicate( - [&propertyName = propertyIt.Key]( - const FCesiumPropertyTablePropertyDescription& existingProperty) { - return existingProperty.Name == propertyName; - }); - - if (pExistingProperty) { - // We have already accounted for this property, but we may need to check - // for its offset / scale, since they can differ from the class - // property's definition. - ECesiumMetadataType type = pExistingProperty->PropertyDetails.Type; - switch (type) { - case ECesiumMetadataType::Scalar: - case ECesiumMetadataType::Vec2: - case ECesiumMetadataType::Vec3: - case ECesiumMetadataType::Vec4: - case ECesiumMetadataType::Mat2: - case ECesiumMetadataType::Mat3: - case ECesiumMetadataType::Mat4: - break; - default: - continue; - } - - FCesiumMetadataValue offset = - UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - pExistingProperty->PropertyDetails.bHasOffset |= - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset); - - FCesiumMetadataValue scale = - UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - pExistingProperty->PropertyDetails.bHasScale |= - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); - - continue; - } - - FCesiumPropertyTablePropertyDescription& property = - pDescription->Properties.Emplace_GetRef(); - property.Name = propertyIt.Key; - - const FCesiumMetadataValueType ValueType = - UCesiumPropertyTablePropertyBlueprintLibrary::GetValueType( - propertyIt.Value); - property.PropertyDetails.SetValueType(ValueType); - property.PropertyDetails.ArraySize = - UCesiumPropertyTablePropertyBlueprintLibrary::GetArraySize( - propertyIt.Value); - property.PropertyDetails.bIsNormalized = - UCesiumPropertyTablePropertyBlueprintLibrary::IsNormalized( - propertyIt.Value); - - FCesiumMetadataValue offset = - UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - property.PropertyDetails.bHasOffset = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset); - - FCesiumMetadataValue scale = - UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - property.PropertyDetails.bHasScale = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); - - FCesiumMetadataValue noData = - UCesiumPropertyTablePropertyBlueprintLibrary::GetNoDataValue( - propertyIt.Value); - property.PropertyDetails.bHasNoDataValue = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(noData); - - FCesiumMetadataValue defaultValue = - UCesiumPropertyTablePropertyBlueprintLibrary::GetDefaultValue( - propertyIt.Value); - property.PropertyDetails.bHasDefaultValue = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(defaultValue); - - property.EncodingDetails = CesiumMetadataPropertyDetailsToEncodingDetails( - property.PropertyDetails); - } - } -} - -void AutoFillPropertyTextureDescriptions( - TArray& Descriptions, - const FCesiumModelMetadata& ModelMetadata) { - const TArray& propertyTextures = - UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures(ModelMetadata); - - for (const auto& propertyTexture : propertyTextures) { - FString propertyTextureName = getNameForPropertyTexture(propertyTexture); - FCesiumPropertyTextureDescription* pDescription = - Descriptions.FindByPredicate( - [&propertyTextureName = - propertyTextureName](const FCesiumPropertyTextureDescription& - existingPropertyTexture) { - return existingPropertyTexture.Name == propertyTextureName; - }); - - if (!pDescription) { - pDescription = &Descriptions.Emplace_GetRef(); - pDescription->Name = propertyTextureName; - } - - const TMap& properties = - UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture); - for (const auto& propertyIt : properties) { - auto pExistingProperty = pDescription->Properties.FindByPredicate( - [&propertyName = - propertyIt.Key](const FCesiumPropertyTexturePropertyDescription& - existingProperty) { - return propertyName == existingProperty.Name; - }); - - if (pExistingProperty) { - // We have already accounted for this property, but we may need to check - // for its offset / scale, since they can differ from the class - // property's definition. - ECesiumMetadataType type = pExistingProperty->PropertyDetails.Type; - switch (type) { - case ECesiumMetadataType::Scalar: - case ECesiumMetadataType::Vec2: - case ECesiumMetadataType::Vec3: - case ECesiumMetadataType::Vec4: - case ECesiumMetadataType::Mat2: - case ECesiumMetadataType::Mat3: - case ECesiumMetadataType::Mat4: - break; - default: - continue; - } - - FCesiumMetadataValue offset = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - pExistingProperty->PropertyDetails.bHasOffset |= - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset); - - FCesiumMetadataValue scale = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - pExistingProperty->PropertyDetails.bHasScale |= - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); - - continue; - } - - FCesiumPropertyTexturePropertyDescription& property = - pDescription->Properties.Emplace_GetRef(); - property.Name = propertyIt.Key; - - const FCesiumMetadataValueType ValueType = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetValueType( - propertyIt.Value); - property.PropertyDetails.SetValueType(ValueType); - property.PropertyDetails.ArraySize = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetArraySize( - propertyIt.Value); - property.PropertyDetails.bIsNormalized = - UCesiumPropertyTexturePropertyBlueprintLibrary::IsNormalized( - propertyIt.Value); - - FCesiumMetadataValue offset = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - property.PropertyDetails.bHasOffset = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset); - - FCesiumMetadataValue scale = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset( - propertyIt.Value); - property.PropertyDetails.bHasScale = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale); - - FCesiumMetadataValue noData = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetNoDataValue( - propertyIt.Value); - property.PropertyDetails.bHasNoDataValue = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(noData); - - FCesiumMetadataValue defaultValue = - UCesiumPropertyTexturePropertyBlueprintLibrary::GetDefaultValue( - propertyIt.Value); - property.PropertyDetails.bHasDefaultValue = - !UCesiumMetadataValueBlueprintLibrary::IsEmpty(defaultValue); - } - } -} - -void AutoFillFeatureIdSetDescriptions( - TArray& Descriptions, - const FCesiumPrimitiveFeatures& Features, - const FCesiumPrimitiveFeatures* InstanceFeatures, - const TArray& PropertyTables) { - TArray featureIDSets = - UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(Features); - if (InstanceFeatures) { - featureIDSets.Append( - UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets( - *InstanceFeatures)); - } - int32 featureIDTextureCounter = 0; - - for (const FCesiumFeatureIdSet& featureIDSet : featureIDSets) { - ECesiumFeatureIdSetType type = - UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(featureIDSet); - int64 count = - UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet); - if (type == ECesiumFeatureIdSetType::None || count == 0) { - // Empty or invalid feature ID set. Skip. - continue; - } - - FString featureIDSetName = - getNameForFeatureIDSet(featureIDSet, featureIDTextureCounter); - FCesiumFeatureIdSetDescription* pDescription = Descriptions.FindByPredicate( - [&name = featureIDSetName]( - const FCesiumFeatureIdSetDescription& existingFeatureIDSet) { - return existingFeatureIDSet.Name == name; - }); - - if (pDescription) { - // We have already accounted for a feature ID set of this name; skip. - continue; - } - - pDescription = &Descriptions.Emplace_GetRef(); - pDescription->Name = featureIDSetName; - pDescription->Type = type; - - const int64 propertyTableIndex = - UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex( - featureIDSet); - if (propertyTableIndex >= 0 && propertyTableIndex < PropertyTables.Num()) { - const FCesiumPropertyTable& propertyTable = - PropertyTables[propertyTableIndex]; - pDescription->PropertyTableName = getNameForPropertyTable(propertyTable); - } - } -} - -void AutoFillPropertyTextureNames( - TSet& Names, - const FCesiumPrimitiveMetadata& PrimitiveMetadata, - const TArray& PropertyTextures) { - const TArray propertyTextureIndices = - UCesiumPrimitiveMetadataBlueprintLibrary::GetPropertyTextureIndices( - PrimitiveMetadata); - - for (const int64& propertyTextureIndex : propertyTextureIndices) { - if (propertyTextureIndex < 0 || - propertyTextureIndex >= PropertyTextures.Num()) { - continue; - } - - const FCesiumPropertyTexture& propertyTexture = - PropertyTextures[propertyTextureIndex]; - FString propertyTextureName = getNameForPropertyTexture(propertyTexture); - Names.Emplace(propertyTextureName); - } -} - -} // namespace - -void UCesiumFeaturesMetadataComponent::AutoFill() { - const ACesium3DTileset* pOwner = this->GetOwner(); - if (!pOwner) { - return; - } - - Super::PreEditChange(NULL); - - // This assumes that the property tables are the same across all models in the - // tileset, and that they all have the same schema. - for (const UActorComponent* pComponent : pOwner->GetComponents()) { - const UCesiumGltfComponent* pGltf = Cast(pComponent); - if (!pGltf) { - continue; - } - - const FCesiumModelMetadata& modelMetadata = pGltf->Metadata; - AutoFillPropertyTableDescriptions( - this->Description.ModelMetadata.PropertyTables, - modelMetadata); - AutoFillPropertyTextureDescriptions( - this->Description.ModelMetadata.PropertyTextures, - modelMetadata); - - TArray childComponents; - pGltf->GetChildrenComponents(false, childComponents); - - for (const USceneComponent* pChildComponent : childComponents) { - const auto* pCesiumPrimitive = Cast(pChildComponent); - if (!pCesiumPrimitive) { - continue; - } - const CesiumPrimitiveData& primData = - pCesiumPrimitive->getPrimitiveData(); - const FCesiumPrimitiveFeatures& primitiveFeatures = primData.Features; - const TArray& propertyTables = - UCesiumModelMetadataBlueprintLibrary::GetPropertyTables( - modelMetadata); - const FCesiumPrimitiveFeatures* pInstanceFeatures = nullptr; - const auto* pInstancedComponent = - Cast(pChildComponent); - if (pInstancedComponent) { - pInstanceFeatures = pInstancedComponent->pInstanceFeatures.Get(); - } - AutoFillFeatureIdSetDescriptions( - this->Description.PrimitiveFeatures.FeatureIdSets, - primitiveFeatures, - pInstanceFeatures, - propertyTables); - - const FCesiumPrimitiveMetadata& primitiveMetadata = primData.Metadata; - const TArray& propertyTextures = - UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures( - modelMetadata); - AutoFillPropertyTextureNames( - this->Description.PrimitiveMetadata.PropertyTextureNames, - primitiveMetadata, - propertyTextures); - } - } - - Super::PostEditChange(); +void UCesiumFeaturesMetadataComponent::AddProperties() { + ACesium3DTileset* pOwner = this->GetOwner(); + OnCesiumFeaturesMetadataAddProperties.Broadcast(pOwner); } static FORCEINLINE UMaterialFunction* LoadMaterialFunction(const FName& Path) { @@ -1946,7 +1597,8 @@ void GenerateNodesForPropertyTexture( PropertyTextureName, PropertyName); ECesiumEncodedMetadataType Type = - CesiumMetadataPropertyDetailsToEncodingDetails(Property.PropertyDetails) + FCesiumMetadataEncodingDetails::GetBestFitForProperty( + Property.PropertyDetails) .Type; if (!foundFirstProperty) { @@ -2583,24 +2235,24 @@ void UCesiumFeaturesMetadataComponent::PostLoad() { // These deprecated variables should only be non-empty on the first load, in // which case the contents of Description should be empty. if (this->FeatureIdSets.Num() > 0) { - CESIUM_ASSERT(this->Description.PrimitiveFeatures.FeatureIdSets.Num == 0); + CESIUM_ASSERT(this->Description.PrimitiveFeatures.FeatureIdSets.Num() == 0); Swap( this->FeatureIdSets, this->Description.PrimitiveFeatures.FeatureIdSets); } if (this->PropertyTextureNames.Num() > 0) { CESIUM_ASSERT( - this->Description.PrimitiveMetadata.PropertyTextureNames.Num == 0); + this->Description.PrimitiveMetadata.PropertyTextureNames.Num() == 0); Swap( this->PropertyTextureNames, this->Description.PrimitiveMetadata.PropertyTextureNames); } if (this->PropertyTables.Num() > 0) { - CESIUM_ASSERT(this->Description.ModelMetadata.PropertyTables.Num == 0); + CESIUM_ASSERT(this->Description.ModelMetadata.PropertyTables.Num() == 0); Swap(this->PropertyTables, this->Description.ModelMetadata.PropertyTables); } if (this->PropertyTextures.Num() > 0) { - CESIUM_ASSERT(this->Description.ModelMetadata.PropertyTextures.Num == 0); + CESIUM_ASSERT(this->Description.ModelMetadata.PropertyTextures.Num() == 0); Swap( this->PropertyTextures, this->Description.ModelMetadata.PropertyTextures); diff --git a/Source/CesiumRuntime/Private/CesiumMetadataEncodingDetails.cpp b/Source/CesiumRuntime/Private/CesiumMetadataEncodingDetails.cpp new file mode 100644 index 000000000..830a2ddba --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumMetadataEncodingDetails.cpp @@ -0,0 +1,151 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#include "CesiumMetadataEncodingDetails.h" +#include "CesiumMetadataPropertyDetails.h" + +ECesiumEncodedMetadataComponentType CesiumMetadataComponentTypeToEncodingType( + ECesiumMetadataComponentType ComponentType) { + switch (ComponentType) { + case ECesiumMetadataComponentType::Int8: // lossy or reinterpreted + case ECesiumMetadataComponentType::Uint8: + return ECesiumEncodedMetadataComponentType::Uint8; + case ECesiumMetadataComponentType::Int16: + case ECesiumMetadataComponentType::Uint16: + case ECesiumMetadataComponentType::Int32: // lossy or reinterpreted + case ECesiumMetadataComponentType::Uint32: // lossy or reinterpreted + case ECesiumMetadataComponentType::Int64: // lossy + case ECesiumMetadataComponentType::Uint64: // lossy + case ECesiumMetadataComponentType::Float32: + case ECesiumMetadataComponentType::Float64: // lossy + return ECesiumEncodedMetadataComponentType::Float; + default: + return ECesiumEncodedMetadataComponentType::None; + } +} + +ECesiumEncodedMetadataType +CesiumMetadataTypeToEncodingType(ECesiumMetadataType Type) { + switch (Type) { + case ECesiumMetadataType::Scalar: + return ECesiumEncodedMetadataType::Scalar; + case ECesiumMetadataType::Vec2: + return ECesiumEncodedMetadataType::Vec2; + case ECesiumMetadataType::Vec3: + return ECesiumEncodedMetadataType::Vec3; + case ECesiumMetadataType::Vec4: + return ECesiumEncodedMetadataType::Vec4; + default: + return ECesiumEncodedMetadataType::None; + } +} + +size_t +CesiumGetEncodedMetadataTypeComponentCount(ECesiumEncodedMetadataType Type) { + switch (Type) { + case ECesiumEncodedMetadataType::Scalar: + return 1; + case ECesiumEncodedMetadataType::Vec2: + return 2; + case ECesiumEncodedMetadataType::Vec3: + return 3; + case ECesiumEncodedMetadataType::Vec4: + return 4; + default: + return 0; + } +} + +FCesiumMetadataEncodingDetails::FCesiumMetadataEncodingDetails() + : Type(ECesiumEncodedMetadataType::None), + ComponentType(ECesiumEncodedMetadataComponentType::None), + Conversion(ECesiumEncodedMetadataConversion::None) {} + +FCesiumMetadataEncodingDetails::FCesiumMetadataEncodingDetails( + ECesiumEncodedMetadataType InType, + ECesiumEncodedMetadataComponentType InComponentType, + ECesiumEncodedMetadataConversion InConversion) + : Type(InType), ComponentType(InComponentType), Conversion(InConversion) {} + +bool FCesiumMetadataEncodingDetails::operator==( + const FCesiumMetadataEncodingDetails& Info) const { + return Type == Info.Type && ComponentType == Info.ComponentType && + Conversion == Info.Conversion; +} + +bool FCesiumMetadataEncodingDetails::operator!=( + const FCesiumMetadataEncodingDetails& Info) const { + return !operator==(Info); +} + +bool FCesiumMetadataEncodingDetails::HasValidType() const { + return Type != ECesiumEncodedMetadataType::None && + ComponentType != ECesiumEncodedMetadataComponentType::None; +} + +namespace { +ECesiumEncodedMetadataType +getBestFittingEncodedType(FCesiumMetadataPropertyDetails PropertyDetails) { + ECesiumMetadataType type = PropertyDetails.Type; + if (PropertyDetails.bIsArray) { + if (PropertyDetails.ArraySize <= 0) { + // Variable-length array properties are unsupported. + return ECesiumEncodedMetadataType::None; + } + + if (type != ECesiumMetadataType::Boolean && + type != ECesiumMetadataType::Scalar) { + // Only boolean and scalar array properties are supported. + return ECesiumEncodedMetadataType::None; + } + + int64 componentCount = + std::min(PropertyDetails.ArraySize, static_cast(4)); + switch (componentCount) { + case 1: + return ECesiumEncodedMetadataType::Scalar; + case 2: + return ECesiumEncodedMetadataType::Vec2; + case 3: + return ECesiumEncodedMetadataType::Vec3; + case 4: + return ECesiumEncodedMetadataType::Vec4; + default: + return ECesiumEncodedMetadataType::None; + } + } + + switch (type) { + case ECesiumMetadataType::Boolean: + case ECesiumMetadataType::Scalar: + return ECesiumEncodedMetadataType::Scalar; + case ECesiumMetadataType::Vec2: + return ECesiumEncodedMetadataType::Vec2; + case ECesiumMetadataType::Vec3: + return ECesiumEncodedMetadataType::Vec3; + case ECesiumMetadataType::Vec4: + return ECesiumEncodedMetadataType::Vec4; + default: + return ECesiumEncodedMetadataType::None; + } +} +} // namespace + +/*static*/ +FCesiumMetadataEncodingDetails +FCesiumMetadataEncodingDetails::GetBestFitForProperty( + const FCesiumMetadataPropertyDetails& PropertyDetails) { + ECesiumEncodedMetadataType type = getBestFittingEncodedType(PropertyDetails); + + if (type == ECesiumEncodedMetadataType::None) { + // The type cannot be encoded at all; return. + return FCesiumMetadataEncodingDetails(); + } + + ECesiumEncodedMetadataComponentType componentType = + CesiumMetadataComponentTypeToEncodingType(PropertyDetails.ComponentType); + + return FCesiumMetadataEncodingDetails( + type, + componentType, + ECesiumEncodedMetadataConversion::Coerce); +} diff --git a/Source/CesiumRuntime/Private/CesiumMetadataValueType.cpp b/Source/CesiumRuntime/Private/CesiumMetadataValueType.cpp new file mode 100644 index 000000000..5db392348 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumMetadataValueType.cpp @@ -0,0 +1,44 @@ +// Copyright 2020-2025 CesiumGS, Inc. and Contributors + +#include "CesiumMetadataValueType.h" + +using namespace CesiumGltf; + +FCesiumMetadataValueType::FCesiumMetadataValueType() + : Type(ECesiumMetadataType::Invalid), + ComponentType(ECesiumMetadataComponentType::None), + bIsArray(false) {} + +FCesiumMetadataValueType::FCesiumMetadataValueType( + ECesiumMetadataType InType, + ECesiumMetadataComponentType InComponentType, + bool IsArray) + : Type(InType), ComponentType(InComponentType), bIsArray(IsArray) {} + +namespace { +template FString enumToNameString(TEnum value) { + const UEnum* pEnum = StaticEnum(); + return pEnum ? pEnum->GetNameStringByValue((int64)value) : FString(); +} +} // namespace + +FString FCesiumMetadataValueType::ToString() const { + if (Type == ECesiumMetadataType::Invalid) { + return TEXT("Invalid Type"); + } + + TArray strings; + strings.Reserve(3); + + if (ComponentType != ECesiumMetadataComponentType::None) { + strings.Emplace(enumToNameString(ComponentType)); + } + + strings.Emplace(enumToNameString(Type)); + + if (bIsArray) { + strings.Emplace("Array"); + } + + return FString::Join(strings, TEXT(" ")); +} diff --git a/Source/CesiumRuntime/Private/CesiumRuntime.cpp b/Source/CesiumRuntime/Private/CesiumRuntime.cpp index f29007339..3cc1a99bc 100644 --- a/Source/CesiumRuntime/Private/CesiumRuntime.cpp +++ b/Source/CesiumRuntime/Private/CesiumRuntime.cpp @@ -1,12 +1,7 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #include "CesiumRuntime.h" -#include "Cesium3DTilesContent/registerAllTileContentTypes.h" -#include "CesiumAsync/CachingAssetAccessor.h" -#include "CesiumAsync/GunzipAssetAccessor.h" -#include "CesiumAsync/SqliteCache.h" #include "CesiumRuntimeSettings.h" -#include "CesiumUtility/Tracing.h" #include "HAL/FileManager.h" #include "HttpModule.h" #include "Interfaces/IPluginManager.h" @@ -15,8 +10,14 @@ #include "SpdlogUnrealLoggerSink.h" #include "UnrealAssetAccessor.h" #include "UnrealTaskProcessor.h" + +#include #include +#include +#include #include +#include +#include #include #include @@ -61,6 +62,7 @@ IMPLEMENT_MODULE(FCesiumRuntimeModule, CesiumRuntime) FCesium3DTilesetIonTroubleshooting OnCesium3DTilesetIonTroubleshooting{}; FCesiumRasterOverlayIonTroubleshooting OnCesiumRasterOverlayIonTroubleshooting{}; +FCesiumFeaturesMetadataAddProperties OnCesiumFeaturesMetadataAddProperties{}; CesiumAsync::AsyncSystem& getAsyncSystem() noexcept { static CesiumAsync::AsyncSystem asyncSystem( diff --git a/Source/CesiumRuntime/Private/EncodedMetadataConversions.cpp b/Source/CesiumRuntime/Private/EncodedMetadataConversions.cpp index 01b60d64d..6bf316543 100644 --- a/Source/CesiumRuntime/Private/EncodedMetadataConversions.cpp +++ b/Source/CesiumRuntime/Private/EncodedMetadataConversions.cpp @@ -1,7 +1,6 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "EncodedMetadataConversions.h" -#include "CesiumFeaturesMetadataComponent.h" #include "CesiumMetadataEncodingDetails.h" #include "CesiumMetadataPropertyDetails.h" #include "CesiumPropertyArrayBlueprintLibrary.h" @@ -11,126 +10,6 @@ #include #include -namespace { -ECesiumEncodedMetadataType -GetBestFittingEncodedType(FCesiumMetadataPropertyDetails PropertyDetails) { - ECesiumMetadataType type = PropertyDetails.Type; - if (PropertyDetails.bIsArray) { - if (PropertyDetails.ArraySize <= 0) { - // Variable-length array properties are unsupported. - return ECesiumEncodedMetadataType::None; - } - - if (type != ECesiumMetadataType::Boolean && - type != ECesiumMetadataType::Scalar) { - // Only boolean and scalar array properties are supported. - return ECesiumEncodedMetadataType::None; - } - - int64 componentCount = - std::min(PropertyDetails.ArraySize, static_cast(4)); - switch (componentCount) { - case 1: - return ECesiumEncodedMetadataType::Scalar; - case 2: - return ECesiumEncodedMetadataType::Vec2; - case 3: - return ECesiumEncodedMetadataType::Vec3; - case 4: - return ECesiumEncodedMetadataType::Vec4; - default: - return ECesiumEncodedMetadataType::None; - } - } - - switch (type) { - case ECesiumMetadataType::Boolean: - case ECesiumMetadataType::Scalar: - return ECesiumEncodedMetadataType::Scalar; - case ECesiumMetadataType::Vec2: - return ECesiumEncodedMetadataType::Vec2; - case ECesiumMetadataType::Vec3: - return ECesiumEncodedMetadataType::Vec3; - case ECesiumMetadataType::Vec4: - return ECesiumEncodedMetadataType::Vec4; - default: - return ECesiumEncodedMetadataType::None; - } -} - -} // namespace - -ECesiumEncodedMetadataType - -CesiumMetadataTypeToEncodingType(ECesiumMetadataType Type) { - switch (Type) { - case ECesiumMetadataType::Scalar: - return ECesiumEncodedMetadataType::Scalar; - case ECesiumMetadataType::Vec2: - return ECesiumEncodedMetadataType::Vec2; - case ECesiumMetadataType::Vec3: - return ECesiumEncodedMetadataType::Vec3; - case ECesiumMetadataType::Vec4: - return ECesiumEncodedMetadataType::Vec4; - default: - return ECesiumEncodedMetadataType::None; - } -} - -ECesiumEncodedMetadataComponentType CesiumMetadataComponentTypeToEncodingType( - ECesiumMetadataComponentType ComponentType) { - switch (ComponentType) { - case ECesiumMetadataComponentType::Int8: // lossy or reinterpreted - case ECesiumMetadataComponentType::Uint8: - return ECesiumEncodedMetadataComponentType::Uint8; - case ECesiumMetadataComponentType::Int16: - case ECesiumMetadataComponentType::Uint16: - case ECesiumMetadataComponentType::Int32: // lossy or reinterpreted - case ECesiumMetadataComponentType::Uint32: // lossy or reinterpreted - case ECesiumMetadataComponentType::Int64: // lossy - case ECesiumMetadataComponentType::Uint64: // lossy - case ECesiumMetadataComponentType::Float32: - case ECesiumMetadataComponentType::Float64: // lossy - return ECesiumEncodedMetadataComponentType::Float; - default: - return ECesiumEncodedMetadataComponentType::None; - } -} - -FCesiumMetadataEncodingDetails CesiumMetadataPropertyDetailsToEncodingDetails( - FCesiumMetadataPropertyDetails PropertyDetails) { - ECesiumEncodedMetadataType type = GetBestFittingEncodedType(PropertyDetails); - - if (type == ECesiumEncodedMetadataType::None) { - // The type cannot be encoded at all; return. - return FCesiumMetadataEncodingDetails(); - } - - ECesiumEncodedMetadataComponentType componentType = - CesiumMetadataComponentTypeToEncodingType(PropertyDetails.ComponentType); - - return FCesiumMetadataEncodingDetails( - type, - componentType, - ECesiumEncodedMetadataConversion::Coerce); -} - -size_t -CesiumGetEncodedMetadataTypeComponentCount(ECesiumEncodedMetadataType Type) { - switch (Type) { - case ECesiumEncodedMetadataType::Scalar: - return 1; - case ECesiumEncodedMetadataType::Vec2: - return 2; - case ECesiumEncodedMetadataType::Vec3: - return 3; - case ECesiumEncodedMetadataType::Vec4: - return 4; - default: - return 0; - } -} - namespace { template void coerceAndEncodeArrays( diff --git a/Source/CesiumRuntime/Private/EncodedMetadataConversions.h b/Source/CesiumRuntime/Private/EncodedMetadataConversions.h index 32dac2713..824fc1798 100644 --- a/Source/CesiumRuntime/Private/EncodedMetadataConversions.h +++ b/Source/CesiumRuntime/Private/EncodedMetadataConversions.h @@ -2,52 +2,12 @@ #pragma once +#include "CesiumCommon.h" #include "HAL/Platform.h" #include -enum class ECesiumMetadataType : uint8; -enum class ECesiumMetadataComponentType : uint8; -enum class ECesiumEncodedMetadataType : uint8; -enum class ECesiumEncodedMetadataComponentType : uint8; struct FCesiumPropertyTablePropertyDescription; struct FCesiumPropertyTableProperty; -struct FCesiumMetadataPropertyDetails; -struct FCesiumMetadataEncodingDetails; - -/** - * @brief Gets the best-fitting encoded type for the given metadata type. - */ -ECesiumEncodedMetadataType -CesiumMetadataTypeToEncodingType(ECesiumMetadataType Type); - -/** - * @brief Gets the best-fitting encoded type for the given metadata component - * type. - */ -ECesiumEncodedMetadataComponentType -CesiumMetadataComponentTypeToEncodingType(ECesiumMetadataComponentType Type); - -/** - * @brief Gets the best-fitting encoded types and conversion method for a given - * metadata type. This determines the best way (if one is possible) to transfer - * values of the given type to the GPU, for access in Unreal materials. - * - * An array size can also be supplied if bIsArray is true on the given value - * type. If bIsArray is true, but the given array size is zero, this indicates - * the arrays of the property vary in length. Variable-length array properties - * are unsupported. - * - * @param PropertyDetails The metadata property details - */ -FCesiumMetadataEncodingDetails CesiumMetadataPropertyDetailsToEncodingDetails( - FCesiumMetadataPropertyDetails PropertyDetails); - -/** - * @brief Gets the number of components associated with the given encoded type. - * @param type The encoded metadata type. - */ -size_t -CesiumGetEncodedMetadataTypeComponentCount(ECesiumEncodedMetadataType Type); /** * Any custom encoding behavior, e.g., special encoding of unsupported diff --git a/Source/CesiumRuntime/Public/CesiumFeaturesMetadataComponent.h b/Source/CesiumRuntime/Public/CesiumFeaturesMetadataComponent.h index 265f861e4..d3cd6909f 100644 --- a/Source/CesiumRuntime/Public/CesiumFeaturesMetadataComponent.h +++ b/Source/CesiumRuntime/Public/CesiumFeaturesMetadataComponent.h @@ -1,4 +1,4 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #pragma once @@ -13,11 +13,11 @@ /** * @brief A component that can be added to Cesium3DTileset actors to - * dictate what metadata to encode for access on the GPU. The selection can be - * automatically populated based on available metadata by clicking the - * "Auto Fill" button. Once a selection of desired metadata is made, the - * boiler-plate material code to access the selected properties can be - * auto-generated using the "Generate Material" button. + * dictate what feature ID sets or metadata to encode for access on the GPU. + * "Add Properties" allows users to find and select desired feature ID sets and + * metadata properties. Once a selection is made, "Generate Material" can be + * used to auto-generated the boiler-plate code to access the selected + * properties in the Unreal material. */ UCLASS(ClassGroup = Cesium, Meta = (BlueprintSpawnableComponent)) class CESIUMRUNTIME_API UCesiumFeaturesMetadataComponent @@ -27,15 +27,11 @@ class CESIUMRUNTIME_API UCesiumFeaturesMetadataComponent public: #if WITH_EDITOR /** - * Populate the description of metadata and feature IDs using the current view - * of the tileset. This determines what to encode to the GPU based on the - * existing metadata. - * - * Warning: Using Auto Fill may populate the description with a large amount - * of metadata. Make sure to delete the properties that aren't relevant. + * Opens a window to add feature ID sets and metadata properties from the + * current view of the tileset. */ UFUNCTION(CallInEditor, Category = "Cesium") - void AutoFill(); + void AddProperties(); /** * This button can be used to create a boiler-plate material layer that diff --git a/Source/CesiumRuntime/Public/CesiumFeaturesMetadataDescription.h b/Source/CesiumRuntime/Public/CesiumFeaturesMetadataDescription.h index a36fcca60..f56a76556 100644 --- a/Source/CesiumRuntime/Public/CesiumFeaturesMetadataDescription.h +++ b/Source/CesiumRuntime/Public/CesiumFeaturesMetadataDescription.h @@ -1,4 +1,4 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #pragma once @@ -44,19 +44,19 @@ struct CESIUMRUNTIME_API FCesiumFeatureIdSetDescription { * This name will also be used to represent the feature ID set in the * generated material. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Features") FString Name; /** * The type of the feature ID set. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Features") ECesiumFeatureIdSetType Type = ECesiumFeatureIdSetType::None; /** * The name of the property table that this feature ID set corresponds to. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Features") FString PropertyTableName; }; @@ -80,7 +80,7 @@ struct CESIUMRUNTIME_API FCesiumPrimitiveFeaturesDescription { */ UPROPERTY( EditAnywhere, - Category = "Features", + Category = "Cesium|Features", Meta = (TitleProperty = "Name")) TArray FeatureIdSets; }; @@ -112,7 +112,7 @@ struct CESIUMRUNTIME_API FCesiumPropertyTablePropertyDescription { * The name of this property. This will be how it is referenced in the * material. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FString Name; /** @@ -120,13 +120,13 @@ struct CESIUMRUNTIME_API FCesiumPropertyTablePropertyDescription { * information from its EXT_structural_metadata definition. Not all types of * properties can be encoded to the GPU, or coerced to GPU-compatible types. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FCesiumMetadataPropertyDetails PropertyDetails; /** * Describes how the property will be encoded as data on the GPU, if possible. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FCesiumMetadataEncodingDetails EncodingDetails; }; @@ -143,13 +143,16 @@ struct CESIUMRUNTIME_API FCesiumPropertyTableDescription { * in the EXT_structural_metadata extension, then its class name is used * instead. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FString Name; /** * @brief Descriptions of the properties to upload to the GPU. */ - UPROPERTY(EditAnywhere, Category = "Cesium", Meta = (TitleProperty = "Name")) + UPROPERTY( + EditAnywhere, + Category = "Cesium|Metadata", + Meta = (TitleProperty = "Name")) TArray Properties; }; @@ -167,14 +170,14 @@ struct CESIUMRUNTIME_API FCesiumPropertyTexturePropertyDescription { * The name of this property. This will be how it is referenced in the * material. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FString Name; /** * Describes the underlying type of this property and other relevant * information from its EXT_structural_metadata definition. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FCesiumMetadataPropertyDetails PropertyDetails; }; @@ -189,13 +192,16 @@ struct CESIUMRUNTIME_API FCesiumPropertyTextureDescription { /** * @brief The name of this property texture. */ - UPROPERTY(EditAnywhere, Category = "Cesium") + UPROPERTY(EditAnywhere, Category = "Cesium|Metadata") FString Name; /** * @brief Descriptions of the properties to upload to the GPU. */ - UPROPERTY(EditAnywhere, Category = "Cesium", Meta = (TitleProperty = "Name")) + UPROPERTY( + EditAnywhere, + Category = "Cesium|Metadata", + Meta = (TitleProperty = "Name")) TArray Properties; }; @@ -223,7 +229,7 @@ struct CESIUMRUNTIME_API FCesiumPrimitiveMetadataDescription { */ UPROPERTY( EditAnywhere, - Category = "Metadata", + Category = "Cesium|Metadata", Meta = (TitleProperty = "Name")) TSet PropertyTextureNames; }; @@ -242,7 +248,7 @@ struct CESIUMRUNTIME_API FCesiumModelMetadataDescription { */ UPROPERTY( EditAnywhere, - Category = "Metadata", + Category = "Cesium|Metadata", Meta = (TitleProperty = "Name")) TArray PropertyTables; @@ -252,7 +258,7 @@ struct CESIUMRUNTIME_API FCesiumModelMetadataDescription { */ UPROPERTY( EditAnywhere, - Category = "Metadata", + Category = "Cesium|Metadata", Meta = (TitleProperty = "Name")) TArray PropertyTextures; }; diff --git a/Source/CesiumRuntime/Public/CesiumMetadataEncodingDetails.h b/Source/CesiumRuntime/Public/CesiumMetadataEncodingDetails.h index f4ceb12f3..a14d872b5 100644 --- a/Source/CesiumRuntime/Public/CesiumMetadataEncodingDetails.h +++ b/Source/CesiumRuntime/Public/CesiumMetadataEncodingDetails.h @@ -8,6 +8,8 @@ #include "CesiumMetadataEncodingDetails.generated.h" +struct FCesiumMetadataPropertyDetails; + /** * @brief The component type that a metadata property's values will be encoded * as. These correspond to the pixel component types that are supported in @@ -16,6 +18,13 @@ UENUM() enum class ECesiumEncodedMetadataComponentType : uint8 { None, Uint8, Float }; +/** + * @brief Gets the best-fitting encoded type for the given metadata component + * type. + */ +ECesiumEncodedMetadataComponentType +CesiumMetadataComponentTypeToEncodingType(ECesiumMetadataComponentType Type); + /** * @brief The type that a metadata property's values will be encoded as. */ @@ -28,6 +37,19 @@ enum class ECesiumEncodedMetadataType : uint8 { Vec4 }; +/** + * @brief Gets the best-fitting encoded type for the given metadata type. + */ +ECesiumEncodedMetadataType +CesiumMetadataTypeToEncodingType(ECesiumMetadataType Type); + +/** + * @brief Gets the number of components associated with the given encoded type. + * @param type The encoded metadata type. + */ +size_t +CesiumGetEncodedMetadataTypeComponentCount(ECesiumEncodedMetadataType Type); + /** * @brief Indicates how a property value from EXT_structural_metadata should be * converted to a GPU-accessible type, if possible. @@ -59,21 +81,15 @@ enum class ECesiumEncodedMetadataConversion : uint8 { * access in Unreal materials. */ USTRUCT() -struct FCesiumMetadataEncodingDetails { +struct CESIUMRUNTIME_API FCesiumMetadataEncodingDetails { GENERATED_USTRUCT_BODY() - FCesiumMetadataEncodingDetails() - : Type(ECesiumEncodedMetadataType::None), - ComponentType(ECesiumEncodedMetadataComponentType::None), - Conversion(ECesiumEncodedMetadataConversion::None) {} + FCesiumMetadataEncodingDetails(); FCesiumMetadataEncodingDetails( ECesiumEncodedMetadataType InType, ECesiumEncodedMetadataComponentType InComponentType, - ECesiumEncodedMetadataConversion InConversion) - : Type(InType), - ComponentType(InComponentType), - Conversion(InConversion) {} + ECesiumEncodedMetadataConversion InConversion); /** * The GPU-compatible type that this property's values will be encoded as. @@ -97,18 +113,25 @@ struct FCesiumMetadataEncodingDetails { UPROPERTY(EditAnywhere, Category = "Cesium") ECesiumEncodedMetadataConversion Conversion; - inline bool operator==(const FCesiumMetadataEncodingDetails& Info) const { - return Type == Info.Type && ComponentType == Info.ComponentType && - Conversion == Info.Conversion; - } + bool operator==(const FCesiumMetadataEncodingDetails& Info) const; - inline bool operator!=(const FCesiumMetadataEncodingDetails& Info) const { - return Type != Info.Type || ComponentType != Info.ComponentType || - Conversion != Info.Conversion; - } + bool operator!=(const FCesiumMetadataEncodingDetails& Info) const; - bool HasValidType() const { - return Type != ECesiumEncodedMetadataType::None && - ComponentType != ECesiumEncodedMetadataComponentType::None; - } + bool HasValidType() const; + + /** + * @brief Gets the best-fitting encoded types and conversion method for a + * given metadata type. This determines the best way (if one is possible) to + * transfer values of the given type to the GPU, for access in Unreal + * materials. + * + * An array size can also be supplied if bIsArray is true on the given value + * type. If bIsArray is true, but the given array size is zero, this indicates + * the arrays of the property vary in length. Variable-length array properties + * are unsupported. + * + * @param PropertyDetails The metadata property details + */ + static FCesiumMetadataEncodingDetails + GetBestFitForProperty(const FCesiumMetadataPropertyDetails& PropertyDetails); }; diff --git a/Source/CesiumRuntime/Public/CesiumMetadataValueType.h b/Source/CesiumRuntime/Public/CesiumMetadataValueType.h index 7b2eed250..77dd4cc09 100644 --- a/Source/CesiumRuntime/Public/CesiumMetadataValueType.h +++ b/Source/CesiumRuntime/Public/CesiumMetadataValueType.h @@ -1,12 +1,13 @@ -// Copyright 2020-2024 CesiumGS, Inc. and Contributors +// Copyright 2020-2025 CesiumGS, Inc. and Contributors #pragma once -#include "CesiumGltf/Enum.h" -#include "CesiumGltf/PropertyArrayView.h" -#include "CesiumGltf/PropertyType.h" -#include "CesiumGltf/PropertyTypeTraits.h" #include "CesiumMetadataEnum.h" +#include +#include +#include +#include + #include "CesiumMetadataValueType.generated.h" /** @@ -139,16 +140,12 @@ USTRUCT(BlueprintType) struct CESIUMRUNTIME_API FCesiumMetadataValueType { GENERATED_USTRUCT_BODY() - FCesiumMetadataValueType() - : Type(ECesiumMetadataType::Invalid), - ComponentType(ECesiumMetadataComponentType::None), - bIsArray(false) {} + FCesiumMetadataValueType(); FCesiumMetadataValueType( ECesiumMetadataType InType, ECesiumMetadataComponentType InComponentType, - bool IsArray = false) - : Type(InType), ComponentType(InComponentType), bIsArray(IsArray) {} + bool IsArray = false); /** * The type of the metadata property or value. @@ -186,6 +183,12 @@ struct CESIUMRUNTIME_API FCesiumMetadataValueType { return Type != ValueType.Type || ComponentType != ValueType.ComponentType || bIsArray != ValueType.bIsArray; } + + /** + * Prints this value type in the format "(Component Type) (Type) (Array)". + * For example, "Int16 Scalar", "Float32 Mat4 Array", "String Array". + */ + FString ToString() const; }; template @@ -264,14 +267,3 @@ static size_t GetMetadataTypeByteSize( return byteSize; } - -static FString MetadataTypeToString(ECesiumMetadataType type) { - const UEnum* pEnum = StaticEnum(); - return pEnum ? pEnum->GetNameByValue((int64)type).ToString() : FString(); -} - -static FString -MetadataComponentTypeToString(ECesiumMetadataComponentType type) { - const UEnum* pEnum = StaticEnum(); - return pEnum ? pEnum->GetNameByValue((int64)type).ToString() : FString(); -} diff --git a/Source/CesiumRuntime/Public/CesiumRuntime.h b/Source/CesiumRuntime/Public/CesiumRuntime.h index cecfa9eb6..960a3aabe 100644 --- a/Source/CesiumRuntime/Public/CesiumRuntime.h +++ b/Source/CesiumRuntime/Public/CesiumRuntime.h @@ -46,6 +46,18 @@ DECLARE_MULTICAST_DELEGATE_OneParam( CESIUMRUNTIME_API extern FCesiumRasterOverlayIonTroubleshooting OnCesiumRasterOverlayIonTroubleshooting; +/** + * The delegate for the OnCesiumFeaturesMetadataAddProperties, which is + * triggered when "Add Properties" is clicked on + * UCesiumFeaturesMetadataComponent. + */ +DECLARE_MULTICAST_DELEGATE_OneParam( + FCesiumFeaturesMetadataAddProperties, + ACesium3DTileset*); + +CESIUMRUNTIME_API extern FCesiumFeaturesMetadataAddProperties + OnCesiumFeaturesMetadataAddProperties; + CESIUMRUNTIME_API CesiumAsync::AsyncSystem& getAsyncSystem() noexcept; CESIUMRUNTIME_API const std::shared_ptr& getAssetAccessor();