diff --git a/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp b/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp index 919f6f47a..c8045a59a 100644 --- a/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeatureIdAttribute.cpp @@ -21,13 +21,6 @@ FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( return; } - const CesiumGltf::Accessor* accessor = - Model.getSafe(&Model.accessors, featureID->second); - if (!accessor || accessor->type != CesiumGltf::Accessor::Type::SCALAR) { - this->_status = ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor; - return; - } - this->_featureIdAccessor = CesiumGltf::getFeatureIdAccessorView( Model, Primitive, @@ -44,6 +37,31 @@ FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( this->_featureIdAccessor); } +FCesiumFeatureIdAttribute::FCesiumFeatureIdAttribute( + const CesiumGltf::Model& Model, + const CesiumGltf::Node& Node, + const int64 FeatureIDAttribute, + const FString& PropertyTableName) + : _status(ECesiumFeatureIdAttributeStatus::ErrorInvalidAttribute), + _featureIdAccessor(), + _attributeIndex(FeatureIDAttribute), + _propertyTableName(PropertyTableName) { + this->_featureIdAccessor = CesiumGltf::getFeatureIdAccessorView( + Model, + Node, + static_cast(this->_attributeIndex)); + + this->_status = std::visit( + [](auto&& view) { + if (view.status() != CesiumGltf::AccessorViewStatus::Valid) { + return ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor; + } + + return ECesiumFeatureIdAttributeStatus::Valid; + }, + this->_featureIdAccessor); +} + const FString& UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureTableName( UPARAM(ref) const FCesiumFeatureIdAttribute& FeatureIDAttribute) { return FeatureIDAttribute._propertyTableName; diff --git a/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp b/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp index 6f846a6c6..36a45ce70 100644 --- a/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp +++ b/Source/CesiumRuntime/Private/CesiumFeatureIdSet.cpp @@ -2,6 +2,7 @@ #include "CesiumFeatureIdSet.h" #include "CesiumGltf/Accessor.h" +#include "CesiumGltf/ExtensionExtInstanceFeaturesFeatureId.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" #include "CesiumGltf/FeatureId.h" #include "CesiumGltf/Model.h" @@ -62,6 +63,42 @@ FCesiumFeatureIdSet::FCesiumFeatureIdSet( } } +FCesiumFeatureIdSet::FCesiumFeatureIdSet( + const CesiumGltf::Model& InModel, + const CesiumGltf::Node& Node, + const CesiumGltf::ExtensionExtInstanceFeaturesFeatureId& InstanceFeatureID) + : _featureID(), + _featureIDSetType(ECesiumFeatureIdSetType::Instance), + _featureCount(InstanceFeatureID.featureCount), + _nullFeatureID(InstanceFeatureID.nullFeatureId.value_or(-1)), + _propertyTableIndex(InstanceFeatureID.propertyTable.value_or(-1)), + _label(FString(InstanceFeatureID.label.value_or("").c_str())) { + FString propertyTableName; + + // For backwards compatibility with GetFeatureTableName. + const CesiumGltf::ExtensionModelExtStructuralMetadata* pMetadata = + InModel.getExtension(); + if (pMetadata && this->_propertyTableIndex >= 0) { + size_t index = static_cast(_propertyTableIndex); + if (index < pMetadata->propertyTables.size()) { + const CesiumGltf::PropertyTable& propertyTable = + pMetadata->propertyTables[index]; + std::string name = propertyTable.name.value_or(""); + propertyTableName = FString(name.c_str()); + } + } + + if (InstanceFeatureID.attribute) { + _featureID = FCesiumFeatureIdAttribute( + InModel, + Node, + *InstanceFeatureID.attribute, + propertyTableName); + } else if (_featureCount > 0) { + _featureIDSetType = ECesiumFeatureIdSetType::InstanceImplicit; + } +} + const ECesiumFeatureIdSetType UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet) { @@ -71,7 +108,8 @@ UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( const FCesiumFeatureIdAttribute& UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet) { - if (FeatureIDSet._featureIDSetType == ECesiumFeatureIdSetType::Attribute) { + if (FeatureIDSet._featureIDSetType == ECesiumFeatureIdSetType::Attribute || + FeatureIDSet._featureIDSetType == ECesiumFeatureIdSetType::Instance) { return std::get(FeatureIDSet._featureID); } @@ -136,9 +174,40 @@ int64 UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( return -1; } +int64 UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForInstance( + UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet, + int64 InstanceIndex) { + ECesiumFeatureIdSetType type = FeatureIDSet._featureIDSetType; + if (type == ECesiumFeatureIdSetType::InstanceImplicit) { + return InstanceIndex; + } else if ( + type != ECesiumFeatureIdSetType::Instance || + !std::holds_alternative( + FeatureIDSet._featureID) || + InstanceIndex < 0) { + return -1; + } + const auto& featureIdAttribute = + std::get(FeatureIDSet._featureID); + return UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureIDForVertex( + featureIdAttribute, + InstanceIndex); +} + int64 UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet, const FHitResult& Hit) { + // FeatureIDs from instanced geometry take precedence. + const auto* pInstancedComponent = + Cast(Hit.Component); + if (IsValid(pInstancedComponent)) { + const FCesiumPrimitiveFeatures& instanceFeatures = + *pInstancedComponent->pInstanceFeatures; + return UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromInstance( + instanceFeatures, + Hit.Item); + } + if (FeatureIDSet._featureIDSetType == ECesiumFeatureIdSetType::Texture) { FCesiumFeatureIdTexture texture = std::get(FeatureIDSet._featureID); diff --git a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp index c502adb2a..e2c2624c3 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGltfComponent.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -1913,8 +1914,10 @@ inline constexpr bool is_int_quat_v = is_int_quat::value; static void loadInstancingData( const CesiumGltf::Model& model, + const CesiumGltf::Node& node, LoadNodeResult& result, - const CesiumGltf::ExtensionExtMeshGpuInstancing* pGpuInstancing) { + const CesiumGltf::ExtensionExtMeshGpuInstancing* pGpuInstancing, + const CesiumGltf::ExtensionExtInstanceFeatures* pInstanceFeatures) { auto getInstanceAccessor = [&](const char* name) -> const CesiumGltf::Accessor* { if (auto accessorItr = pGpuInstancing->attributes.find(name); @@ -2034,6 +2037,10 @@ static void loadInstancingData( auto unrealFMatrix = VecMath::createMatrix(unrealMat); result.InstanceTransforms[i].SetFromMatrix(unrealFMatrix); } + if (pInstanceFeatures) { + result.pInstanceFeatures = + MakeShared(model, node, *pInstanceFeatures); + } } static void loadNode( @@ -2117,7 +2124,12 @@ static void loadNode( if (meshId >= 0 && meshId < model.meshes.size()) { if (const auto* pGpuInstancingExtension = node.getExtension()) { - loadInstancingData(model, result, pGpuInstancingExtension); + loadInstancingData( + model, + node, + result, + pGpuInstancingExtension, + node.getExtension()); } CreateMeshOptions meshOptions = {&options, &result, meshId}; loadMesh(result.meshResult, nodeTransform, meshOptions, ellipsoid); @@ -2879,7 +2891,8 @@ static void loadPrimitiveGameThreadPart( const Cesium3DTilesSelection::Tile& tile, bool createNavCollision, ACesium3DTileset* pTilesetActor, - const std::vector& instanceTransforms) { + const std::vector& instanceTransforms, + const TSharedPtr& pInstanceFeatures) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadPrimitive) #if DEBUG_GLTF_ASSET_NAMES @@ -2912,6 +2925,7 @@ static void loadPrimitiveGameThreadPart( for (const FTransform& transform : instanceTransforms) { pInstancedComponent->AddInstance(transform, false); } + pInstancedComponent->pInstanceFeatures = pInstanceFeatures; pCesiumPrimitive = pInstancedComponent; } else { auto* pComponent = @@ -3270,7 +3284,8 @@ UCesiumGltfComponent::CreateOffGameThread( tile, createNavCollision, pTilesetActor, - node.InstanceTransforms); + node.InstanceTransforms, + node.pInstanceFeatures); } } } diff --git a/Source/CesiumRuntime/Private/CesiumGltfPrimitiveComponent.h b/Source/CesiumRuntime/Private/CesiumGltfPrimitiveComponent.h index 03b89ee78..081fe682c 100644 --- a/Source/CesiumRuntime/Private/CesiumGltfPrimitiveComponent.h +++ b/Source/CesiumRuntime/Private/CesiumGltfPrimitiveComponent.h @@ -4,6 +4,7 @@ #include "Cesium3DTilesSelection/BoundingVolume.h" #include "CesiumPrimitive.h" +#include "CesiumPrimitiveFeatures.h" #include "Components/InstancedStaticMeshComponent.h" #include "Components/StaticMeshComponent.h" #include "CoreMinimal.h" @@ -62,6 +63,8 @@ class UCesiumGltfInstancedComponent : public UInstancedStaticMeshComponent, CesiumPrimitiveData& getPrimitiveData() override; const CesiumPrimitiveData& getPrimitiveData() const override; + TSharedPtr pInstanceFeatures; + private: CesiumPrimitiveData _cesiumData; }; diff --git a/Source/CesiumRuntime/Private/CesiumMetadataPickingBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumMetadataPickingBlueprintLibrary.cpp index cbb4b0106..8a8e0af69 100644 --- a/Source/CesiumRuntime/Private/CesiumMetadataPickingBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumMetadataPickingBlueprintLibrary.cpp @@ -5,6 +5,8 @@ #include "CesiumGltfPrimitiveComponent.h" #include "CesiumMetadataValue.h" +#include + static TMap EmptyCesiumMetadataValueMap; TMap @@ -148,23 +150,92 @@ bool UCesiumMetadataPickingBlueprintLibrary::FindUVFromHit( return true; } +namespace { +/* + * Returns std:nullopt if the component isn't an instanced static mesh or + * if it doesn't have instance feature IDs. This will prompt + * GetPropertyTableValuesFromHit() to search for feature IDs in the primitive's + * attributes. + */ +std::optional> +getInstancePropertyTableValues( + const FHitResult& Hit, + const UCesiumGltfComponent* pModel, + int64 FeatureIDSetIndex) { + const auto* pInstancedComponent = + Cast(Hit.Component); + if (!IsValid(pInstancedComponent)) { + return std::nullopt; + } + const TSharedPtr& pInstanceFeatures = + pInstancedComponent->pInstanceFeatures; + if (!pInstanceFeatures) { + return std::nullopt; + } + + const TArray& featureIDSets = + UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets( + *pInstanceFeatures); + if (FeatureIDSetIndex < 0 || FeatureIDSetIndex >= featureIDSets.Num()) { + return TMap(); + } + + const FCesiumFeatureIdSet& featureIDSet = featureIDSets[FeatureIDSetIndex]; + const int64 propertyTableIndex = + UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex(featureIDSet); + + const TArray& propertyTables = + UCesiumModelMetadataBlueprintLibrary::GetPropertyTables(pModel->Metadata); + if (propertyTableIndex < 0 || propertyTableIndex >= propertyTables.Num()) { + return TMap(); + } + const FCesiumPropertyTable& propertyTable = + propertyTables[propertyTableIndex]; + int64 featureID = + UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromInstance( + *pInstanceFeatures, + Hit.Item, + FeatureIDSetIndex); + if (featureID < 0) { + return TMap(); + } + return UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature( + propertyTable, + featureID); +} +} // namespace + TMap UCesiumMetadataPickingBlueprintLibrary::GetPropertyTableValuesFromHit( const FHitResult& Hit, int64 FeatureIDSetIndex) { - const UCesiumGltfPrimitiveComponent* pGltfComponent = - Cast(Hit.Component); - if (!IsValid(pGltfComponent)) { + const UCesiumGltfComponent* pModel = nullptr; + + if (const auto* pPrimComponent = Cast(Hit.Component); + !IsValid(pPrimComponent)) { return TMap(); + } else { + pModel = Cast(pPrimComponent->GetOuter()); } - const UCesiumGltfComponent* pModel = - Cast(pGltfComponent->GetOuter()); if (!IsValid(pModel)) { return TMap(); } - const CesiumPrimitiveData& primData = pGltfComponent->getPrimitiveData(); + // Query for instance-level metadata first. (EXT_instance_features) + std::optional> maybeProperties = + getInstancePropertyTableValues(Hit, pModel, FeatureIDSetIndex); + if (maybeProperties) { + return *maybeProperties; + } + + const auto* pCesiumPrimitive = Cast(Hit.Component); + if (!pCesiumPrimitive) { + return TMap(); + } + + // Query for primitive-level metadata. (EXT_mesh_features) + const CesiumPrimitiveData& primData = pCesiumPrimitive->getPrimitiveData(); const FCesiumPrimitiveFeatures& features = primData.Features; const TArray& featureIDSets = UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(features); diff --git a/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp b/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp index f0ec01c09..0abb7a933 100644 --- a/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp +++ b/Source/CesiumRuntime/Private/CesiumPrimitiveFeatures.cpp @@ -1,7 +1,9 @@ // Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumPrimitiveFeatures.h" +#include "CesiumGltf/ExtensionExtInstanceFeatures.h" #include "CesiumGltf/ExtensionExtMeshFeatures.h" +#include "CesiumGltf/ExtensionExtMeshGpuInstancing.h" #include "CesiumGltf/Model.h" #include "CesiumGltfPrimitiveComponent.h" @@ -26,6 +28,19 @@ FCesiumPrimitiveFeatures::FCesiumPrimitiveFeatures( } } +FCesiumPrimitiveFeatures::FCesiumPrimitiveFeatures( + const CesiumGltf::Model& Model, + const CesiumGltf::Node& Node, + const CesiumGltf::ExtensionExtInstanceFeatures& InstanceFeatures) + : _vertexCount(0), _primitiveMode(-1) { + if (Node.mesh < 0 || Node.mesh >= Model.meshes.size()) { + return; + } + for (const auto& featureId : InstanceFeatures.featureIds) { + this->_featureIdSets.Emplace(Model, Node, featureId); + } +} + const FCesiumPrimitiveFeatures& UCesiumPrimitiveFeaturesBlueprintLibrary::GetPrimitiveFeatures( const UPrimitiveComponent* component) { @@ -98,6 +113,20 @@ int64 UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace( FaceIndex)); } +int64 UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromInstance( + const FCesiumPrimitiveFeatures& InstanceFeatures, + int64 InstanceIndex, + int64 FeatureIDSetIndex) { + if (FeatureIDSetIndex < 0 || + FeatureIDSetIndex >= InstanceFeatures._featureIdSets.Num()) { + return -1; + } + const auto& featureIDSet = InstanceFeatures._featureIdSets[FeatureIDSetIndex]; + return UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForInstance( + featureIDSet, + InstanceIndex); +} + int64 UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromHit( UPARAM(ref) const FCesiumPrimitiveFeatures& PrimitiveFeatures, const FHitResult& Hit, diff --git a/Source/CesiumRuntime/Private/LoadGltfResult.h b/Source/CesiumRuntime/Private/LoadGltfResult.h index 72280855c..6d01fe2db 100644 --- a/Source/CesiumRuntime/Private/LoadGltfResult.h +++ b/Source/CesiumRuntime/Private/LoadGltfResult.h @@ -34,10 +34,17 @@ namespace LoadGltfResult { * Temporarily holds render data that will be used in the Unreal material, as * well as any data that needs to be transferred to the corresponding * CesiumGltfPrimitiveComponent after it is created on the main thread. + * + * This type is move-only due to the use of TUniquePtr. */ struct LoadPrimitiveResult { #pragma region Temporary render data + LoadPrimitiveResult(const LoadPrimitiveResult&) = delete; + + LoadPrimitiveResult() {} + LoadPrimitiveResult(LoadPrimitiveResult&& other) = default; + /** * The render data. This is populated so it can be set on the static mesh * created on the main thread. @@ -152,6 +159,12 @@ struct LoadPrimitiveResult { * Represents the result of loading a glTF mesh on a game thread. */ struct LoadMeshResult { + LoadMeshResult() {} + + LoadMeshResult(const LoadMeshResult&) = delete; + LoadMeshResult(LoadMeshResult&& other) = default; + LoadMeshResult& operator=(LoadMeshResult&& other) = default; + std::vector primitiveResults{}; }; @@ -159,11 +172,22 @@ struct LoadMeshResult { * Represents the result of loading a glTF node on a game thread. */ struct LoadNodeResult { + LoadNodeResult() {} + + LoadNodeResult(const LoadNodeResult&) = delete; + LoadNodeResult(LoadNodeResult&& other) = default; + std::optional meshResult = std::nullopt; /** * Array of instance transforms, if any. */ std::vector InstanceTransforms; + /** + * Features from EXT_instance_features. A pointer is used for shared ownership + * because there may be multiple primitives in the same mesh belonging to a + * single instance. + */ + TSharedPtr pInstanceFeatures = nullptr; }; /** diff --git a/Source/CesiumRuntime/Public/CesiumFeatureIdAttribute.h b/Source/CesiumRuntime/Public/CesiumFeatureIdAttribute.h index 14f493c3b..88b440751 100644 --- a/Source/CesiumRuntime/Public/CesiumFeatureIdAttribute.h +++ b/Source/CesiumRuntime/Public/CesiumFeatureIdAttribute.h @@ -9,6 +9,7 @@ namespace CesiumGltf { struct Model; struct Accessor; +struct Node; } // namespace CesiumGltf /** @@ -61,6 +62,22 @@ struct CESIUMRUNTIME_API FCesiumFeatureIdAttribute { const int64 FeatureIDAttribute, const FString& PropertyTableName); + /** + * @brief Constructs a feature ID attribute instance from + * EXT_instance_features data. + * + * @param Model The model. + * @param Node The node containing the feature ID attribute. + * @param FeatureIDAttribute The attribute index specified by the FeatureId. + * @param PropertyTableName The name of the property table this attribute + * corresponds to, if one exists, for backwards compatibility. + */ + FCesiumFeatureIdAttribute( + const CesiumGltf::Model& Model, + const CesiumGltf::Node& Node, + const int64 FeatureIDAttribute, + const FString& PropertyTableName); + /** * Gets the index of this feature ID attribute in the glTF primitive. */ diff --git a/Source/CesiumRuntime/Public/CesiumFeatureIdSet.h b/Source/CesiumRuntime/Public/CesiumFeatureIdSet.h index 92f9d2e33..ee6699094 100644 --- a/Source/CesiumRuntime/Public/CesiumFeatureIdSet.h +++ b/Source/CesiumRuntime/Public/CesiumFeatureIdSet.h @@ -11,6 +11,7 @@ namespace CesiumGltf { struct Model; struct FeatureId; +struct ExtensionExtInstanceFeaturesFeatureId; } // namespace CesiumGltf /** @@ -21,14 +22,17 @@ enum class ECesiumFeatureIdSetType : uint8 { None, Attribute, Texture, - Implicit + Implicit, + Instance, + InstanceImplicit }; /** * @brief A blueprint-accessible wrapper for a feature ID set from a glTF * primitive. A feature ID can be defined as a per-vertex attribute, as a - * feature texture, or implicitly via vertex ID. These can be used with the - * corresponding {@link FCesiumPropertyTable} to access per-vertex metadata. + * feature texture, implicitly via vertex ID, or associated with glTF + * instances. These can be used with the corresponding {@link + * FCesiumPropertyTable} to access the metadata. */ USTRUCT(BlueprintType) struct CESIUMRUNTIME_API FCesiumFeatureIdSet { @@ -50,6 +54,12 @@ struct CESIUMRUNTIME_API FCesiumFeatureIdSet { const CesiumGltf::MeshPrimitive& Primitive, const CesiumGltf::FeatureId& FeatureId); + FCesiumFeatureIdSet( + const CesiumGltf::Model& Model, + const CesiumGltf::Node& Node, + const CesiumGltf::ExtensionExtInstanceFeaturesFeatureId& + InstanceFeatureId); + private: FeatureIDType _featureID; ECesiumFeatureIdSetType _featureIDSetType; @@ -165,6 +175,24 @@ class CESIUMRUNTIME_API UCesiumFeatureIdSetBlueprintLibrary UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet, int64 VertexIndex); + /** + * Gets the feature ID associated with a given instance in glTF models using + * the EXT_mesh_gpu_instancing and EXT_instance_features extensions. The + * feature ID can be used with a FCesiumPropertyTable to retrieve the + * corresponding metadata. + * + * This returns -1 if the given instance is out-of-bounds, if the feature ID + * set is not for instances, or if the feature ID set is invalid (e.g., it + * contains an invalid feature ID attribute). + */ + UFUNCTION( + BlueprintCallable, + BlueprintPure, + Category = "Cesium|Features|FeatureIDSet") + static int64 GetFeatureIDForInstance( + UPARAM(ref) const FCesiumFeatureIdSet& FeatureIDSet, + int64 InstanceIndex); + /** * Given a trace hit result, gets the feature ID from the feature ID set on * the hit component. This returns a more accurate value for feature ID diff --git a/Source/CesiumRuntime/Public/CesiumPrimitiveFeatures.h b/Source/CesiumRuntime/Public/CesiumPrimitiveFeatures.h index 8753caacc..bff0eb823 100644 --- a/Source/CesiumRuntime/Public/CesiumPrimitiveFeatures.h +++ b/Source/CesiumRuntime/Public/CesiumPrimitiveFeatures.h @@ -11,11 +11,15 @@ namespace CesiumGltf { struct ExtensionExtMeshFeatures; +struct ExtensionExtInstanceFeatures; } // namespace CesiumGltf /** * A Blueprint-accessible wrapper for a glTF Primitive's mesh features. It holds - * views of the feature ID sets associated with this primitive. + * views of the feature ID sets associated with this primitive. The collection + * of features in the EXT_instance_features is very similar to that in + * EXT_mesh_features, so FCesiumPrimitiveFeatures can be used to handle those + * features too. */ USTRUCT(BlueprintType) struct CESIUMRUNTIME_API FCesiumPrimitiveFeatures { @@ -33,17 +37,31 @@ struct CESIUMRUNTIME_API FCesiumPrimitiveFeatures { * @param Model The model that contains the EXT_mesh_features extension * @param Primitive The mesh primitive that stores EXT_mesh_features * extension - * @param Features The EXT_mesh_features of the glTF mesh primitive. - * primitive + * @param Features The EXT_mesh_features of the glTF mesh primitive */ FCesiumPrimitiveFeatures( const CesiumGltf::Model& Model, const CesiumGltf::MeshPrimitive& Primitive, const CesiumGltf::ExtensionExtMeshFeatures& Features); + /** + * Constructs an instance feature object. + * + * @param Model The model that contains the EXT_instance_features extension + * @param Node The node that stores EXT_instance_features + * extension + * @param InstanceFeatures The EXT_Instance_features of the glTF mesh + * primitive + */ + FCesiumPrimitiveFeatures( + const CesiumGltf::Model& Model, + const CesiumGltf::Node& Node, + const CesiumGltf::ExtensionExtInstanceFeatures& InstanceFeatures); + private: TArray _featureIdSets; CesiumGltf::IndexAccessorType _indexAccessor; + // Vertex count = 0 and _primitiveMode = -1 indicates instance features int64_t _vertexCount; int32_t _primitiveMode; @@ -132,6 +150,18 @@ class CESIUMRUNTIME_API UCesiumPrimitiveFeaturesBlueprintLibrary int64 FaceIndex, int64 FeatureIDSetIndex = 0); + /** + * Gets the feature ID associated with the instance at the given index. + */ + UFUNCTION( + BlueprintCallable, + BlueprintPure, + Category = "Cesium|Primitive|Features") + static int64 GetFeatureIDFromInstance( + UPARAM(ref) const FCesiumPrimitiveFeatures& InstanceFeatures, + int64 InstanceIndex, + int64 FeatureIDSetIndex = 0); + /** * Gets the feature ID from the given line trace hit, assuming it * has hit a glTF primitive component containing this CesiumPrimitiveFeatures.