// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumMetadataPickingBlueprintLibrary.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumMetadataValue.h" #include static TMap EmptyCesiumMetadataValueMap; TMap UCesiumMetadataPickingBlueprintLibrary::GetMetadataValuesForFace( const UPrimitiveComponent* Component, int64 FaceIndex, int64 FeatureIDSetIndex) { const UCesiumGltfPrimitiveComponent* pGltfComponent = Cast(Component); if (!IsValid(pGltfComponent)) { return TMap(); } const UCesiumGltfComponent* pModel = Cast(pGltfComponent->GetOuter()); if (!IsValid(pModel)) { return TMap(); } const CesiumPrimitiveData& primData = pGltfComponent->getPrimitiveData(); const FCesiumPrimitiveFeatures& features = primData.Features; const TArray& featureIDSets = UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(features); 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::GetFeatureIDFromFace( features, FaceIndex, FeatureIDSetIndex); if (featureID < 0) { return TMap(); } return UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature( propertyTable, featureID); } TMap UCesiumMetadataPickingBlueprintLibrary::GetMetadataValuesForFaceAsStrings( const UPrimitiveComponent* Component, int64 FaceIndex, int64 FeatureIDSetIndex) { TMap values = UCesiumMetadataPickingBlueprintLibrary::GetMetadataValuesForFace( Component, FaceIndex, FeatureIDSetIndex); TMap strings; for (auto valuesIt : values) { strings.Add( valuesIt.Key, UCesiumMetadataValueBlueprintLibrary::GetString(valuesIt.Value, "")); } return strings; } bool UCesiumMetadataPickingBlueprintLibrary::FindUVFromHit( const FHitResult& Hit, int64 GltfTexCoordSetIndex, FVector2D& UV) { const UCesiumGltfPrimitiveComponent* pGltfComponent = Cast(Hit.Component); if (!IsValid(pGltfComponent)) { return false; } const CesiumPrimitiveData& primData = pGltfComponent->getPrimitiveData(); if (primData.PositionAccessor.status() != CesiumGltf::AccessorViewStatus::Valid) { return false; } auto accessorIt = primData.TexCoordAccessorMap.find(GltfTexCoordSetIndex); if (accessorIt == primData.TexCoordAccessorMap.end()) { return false; } auto VertexIndices = std::visit( CesiumGltf::IndicesForFaceFromAccessor{ Hit.FaceIndex, primData.PositionAccessor.size(), primData.pMeshPrimitive->mode}, primData.IndexAccessor); // Adapted from UBodySetup::CalcUVAtLocation. Compute the barycentric // coordinates of the point relative to the face, then use those to // interpolate the UVs. std::array UVs; const CesiumGltf::TexCoordAccessorType& accessor = accessorIt->second; for (size_t i = 0; i < UVs.size(); i++) { auto maybeTexCoord = std::visit( CesiumGltf::TexCoordFromAccessor{VertexIndices[i]}, accessor); if (!maybeTexCoord) { return false; } const glm::dvec2& texCoord = *maybeTexCoord; UVs[i] = FVector2D(texCoord[0], texCoord[1]); } std::array Positions; for (size_t i = 0; i < Positions.size(); i++) { auto& Position = primData.PositionAccessor[VertexIndices[i]]; // The Y-component of glTF positions must be inverted, and the positions // must be scaled to match the UE meshes. Positions[i] = FVector(Position[0], -Position[1], Position[2]) * CesiumPrimitiveData::positionScaleFactor; } const FVector Location = pGltfComponent->GetComponentToWorld().InverseTransformPosition( Hit.Location); FVector BaryCoords = FMath::ComputeBaryCentric2D( Location, Positions[0], Positions[1], Positions[2]); UV = (BaryCoords.X * UVs[0]) + (BaryCoords.Y * UVs[1]) + (BaryCoords.Z * UVs[2]); 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 UCesiumGltfComponent* pModel = nullptr; if (const auto* pPrimComponent = Cast(Hit.Component); !IsValid(pPrimComponent)) { return TMap(); } else { pModel = Cast(pPrimComponent->GetOuter()); } if (!IsValid(pModel)) { return TMap(); } // 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); 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::GetFeatureIDFromHit( features, Hit, FeatureIDSetIndex); if (featureID < 0) { return TMap(); } return UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature( propertyTable, featureID); } TMap UCesiumMetadataPickingBlueprintLibrary::GetPropertyTextureValuesFromHit( const FHitResult& Hit, int64 PropertyTextureIndex) { if (!Hit.Component.IsValid()) { return EmptyCesiumMetadataValueMap; } const UCesiumGltfPrimitiveComponent* pGltfComponent = Cast(Hit.Component.Get()); if (!IsValid(pGltfComponent)) { return EmptyCesiumMetadataValueMap; } const UCesiumGltfComponent* pModel = Cast(pGltfComponent->GetOuter()); if (!IsValid(pModel)) { return EmptyCesiumMetadataValueMap; } const TArray& propertyTextures = UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures( pModel->Metadata); if (PropertyTextureIndex < 0 || PropertyTextureIndex >= propertyTextures.Num()) { return EmptyCesiumMetadataValueMap; } return UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesFromHit( propertyTextures[PropertyTextureIndex], Hit); }