// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumFeatureIdSet.h" #include "CesiumGltf/ExtensionExtMeshFeatures.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" BEGIN_DEFINE_SPEC( FCesiumFeatureIdSetSpec, "Cesium.Unit.FeatureIdSet", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ServerContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::ProductFilter) CesiumGltf::Model model; CesiumGltf::MeshPrimitive* pPrimitive; TObjectPtr pPrimitiveComponent; END_DEFINE_SPEC(FCesiumFeatureIdSetSpec) void FCesiumFeatureIdSetSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); pPrimitive->addExtension(); }); It("constructs from empty feature ID set", [this]() { // This is technically disallowed by the spec, but just make sure it's // handled reasonably. CesiumGltf::FeatureId featureId; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::None); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), 0); }); It("constructs implicit feature ID set", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::Implicit); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), static_cast(featureId.featureCount)); }); It("constructs set with feature ID attribute", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, 4, attributeIndex); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::Attribute); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), static_cast(featureID.featureCount)); }); It("constructs set with feature ID texture", [this]() { const std::vector featureIDs{0, 3, 1, 2}; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::Texture); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), static_cast(featureId.featureCount)); }); It("constructs with null feature ID", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; featureId.nullFeatureId = 0; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::Implicit); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), static_cast(featureId.featureCount)); TestEqual( "NullFeatureID", UCesiumFeatureIdSetBlueprintLibrary::GetNullFeatureID(featureIDSet), static_cast(*featureId.nullFeatureId)); }); It("constructs with property table index", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; featureId.propertyTable = 1; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDType", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType( featureIDSet), ECesiumFeatureIdSetType::Implicit); TestEqual( "FeatureCount", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet), static_cast(featureId.featureCount)); TestEqual( "PropertyTableIndex", UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex( featureIDSet), static_cast(featureId.propertyTable)); }); }); Describe("GetAsFeatureIDAttribute", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns empty instance for non-attribute feature ID set", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); const FCesiumFeatureIdAttribute attribute = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( featureIDSet); TestEqual( "AttributeStatus", UCesiumFeatureIdAttributeBlueprintLibrary:: GetFeatureIDAttributeStatus(attribute), ECesiumFeatureIdAttributeStatus::ErrorInvalidAttribute); TestEqual("AttributeIndex", attribute.getAttributeIndex(), -1); }); It("returns valid instance for attribute feature ID set", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, 4, attributeIndex); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); const FCesiumFeatureIdAttribute attribute = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( featureIDSet); TestEqual( "AttributeStatus", UCesiumFeatureIdAttributeBlueprintLibrary:: GetFeatureIDAttributeStatus(attribute), ECesiumFeatureIdAttributeStatus::Valid); TestEqual( "AttributeIndex", attribute.getAttributeIndex(), attributeIndex); }); }); Describe("GetAsFeatureIDTexture", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns empty instance for non-texture feature ID set", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); const FCesiumFeatureIdTexture texture = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture( featureIDSet); TestEqual( "TextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( texture), ECesiumFeatureIdTextureStatus::ErrorInvalidTexture); auto featureIDTextureView = texture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::ErrorUninitialized); }); It("returns valid instance for texture feature ID set", [this]() { const std::vector featureIDs{0, 3, 1, 2}; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); const FCesiumFeatureIdTexture texture = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture( featureIDSet); TestEqual( "TextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( texture), ECesiumFeatureIdTextureStatus::Valid); auto featureIDTextureView = texture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::Valid); }); }); Describe("GetFeatureIDForVertex", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns -1 for empty feature ID set", [this]() { FCesiumFeatureIdSet featureIDSet; TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, 0), -1); }); It("returns -1 for out of bounds index", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, -1), -1); TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, 11), -1); }); It("returns correct value for implicit set", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 10; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); for (int64 i = 0; i < featureId.featureCount; i++) { TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, i), i); } }); It("returns correct value for attribute set", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, 4, attributeIndex); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); for (size_t i = 0; i < featureIDs.size(); i++) { TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, static_cast(i)), featureIDs[i]); } }); It("returns correct value for texture set", [this]() { const std::vector featureIDs{0, 3, 1, 2}; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); for (size_t i = 0; i < featureIDs.size(); i++) { TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, static_cast(i)), featureIDs[i]); } }); }); Describe("GetFeatureIDFromHit", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); pPrimitive->mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; pPrimitiveComponent = NewObject(); pPrimitiveComponent->getPrimitiveData().pMeshPrimitive = pPrimitive; std::vector positions{ glm::vec3(-1, 0, 0), glm::vec3(0, 1, 0), glm::vec3(1, 0, 0), glm::vec3(-1, 3, 0), glm::vec3(0, 4, 0), glm::vec3(1, 3, 0), }; CreateAttributeForPrimitive( model, *pPrimitive, "POSITION", CesiumGltf::AccessorSpec::Type::VEC3, CesiumGltf::AccessorSpec::ComponentType::FLOAT, positions); }); It("returns -1 for empty feature ID set", [this]() { FCesiumFeatureIdSet featureIDSet; TestEqual( "FeatureIDForVertex", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDForVertex( featureIDSet, 0), -1); }); It("returns -1 for invalid hit component", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 6; CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView( model, static_cast(model.accessors.size() - 1)); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); FHitResult Hit; Hit.Component = nullptr; Hit.FaceIndex = 0; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( featureIDSet, Hit), -1); }); It("returns correct value for texture set", [this]() { int32 positionAccessorIndex = static_cast(model.accessors.size() - 1); // For convenience when testing, the UVs are the same as the positions // they correspond to. This means that the interpolated UV value should be // directly equal to the barycentric coordinates of the triangle. std::vector texCoords{ glm::vec2(-1, 0), glm::vec2(0, 1), glm::vec2(1, 0), glm::vec2(-1, 0), glm::vec2(0, 1), glm::vec2(1, 0)}; const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); FHitResult Hit; Hit.Component = pPrimitiveComponent; Hit.FaceIndex = 0; std::array locations{ FVector_NetQuantize(1, 0, 0), FVector_NetQuantize(0, -1, 0), FVector_NetQuantize(0.0, -0.25, 0)}; std::array expected{3, 1, 0}; for (size_t i = 0; i < locations.size(); i++) { Hit.Location = locations[i] * CesiumPrimitiveData::positionScaleFactor; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( featureIDSet, Hit), expected[i]); } }); It("returns correct value for implicit set", [this]() { CesiumGltf::FeatureId featureId; featureId.featureCount = 6; CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView( model, static_cast(model.accessors.size() - 1)); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); FHitResult Hit; Hit.Component = pPrimitiveComponent; Hit.FaceIndex = 0; std::array faceIndices{0, 1, 0}; std::array locations{ FVector_NetQuantize(1, 0, 0), FVector_NetQuantize(0, -4, 0), FVector_NetQuantize(-1, 0, 0)}; std::array expected{0, 3, 0}; for (size_t i = 0; i < locations.size(); i++) { Hit.FaceIndex = faceIndices[i]; Hit.Location = locations[i] * CesiumPrimitiveData::positionScaleFactor; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( featureIDSet, Hit), expected[i]); } }); It("returns correct value for attribute set", [this]() { int32_t positionAccessorIndex = static_cast(model.accessors.size() - 1); const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, 2, attributeIndex); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId); FHitResult Hit; Hit.Component = pPrimitiveComponent; Hit.FaceIndex = 0; Hit.Location = FVector_NetQuantize(0, -1, 0) * CesiumPrimitiveData::positionScaleFactor; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( featureIDSet, Hit), 0); Hit.FaceIndex = 1; Hit.Location = FVector_NetQuantize(0, -4, 0) * CesiumPrimitiveData::positionScaleFactor; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDFromHit( featureIDSet, Hit), 1); }); }); Describe("Deprecated", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("backwards compatibility for FCesiumFeatureIdAttribute.GetFeatureTableName", [this]() { const int64 attributeIndex = 0; const std::vector featureIDs{0, 0, 0, 1, 1, 1}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsAttributeToModel( model, *pPrimitive, featureIDs, 4, attributeIndex); featureID.propertyTable = 0; const std::string expectedName = "PropertyTableName"; CesiumGltf::ExtensionModelExtStructuralMetadata& metadataExtension = model.addExtension< CesiumGltf::ExtensionModelExtStructuralMetadata>(); CesiumGltf::PropertyTable& propertyTable = metadataExtension.propertyTables.emplace_back(); propertyTable.name = expectedName; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); const FCesiumFeatureIdAttribute attribute = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute( featureIDSet); TestEqual( "AttributeStatus", UCesiumFeatureIdAttributeBlueprintLibrary:: GetFeatureIDAttributeStatus(attribute), ECesiumFeatureIdAttributeStatus::Valid); TestEqual( "GetFeatureTableName", UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureTableName( attribute), FString(expectedName.c_str())); }); It("backwards compatibility for FCesiumFeatureIdTexture.GetFeatureTableName", [this]() { const std::vector featureIDs{0, 3, 1, 2}; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; CesiumGltf::FeatureId& featureID = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); featureID.propertyTable = 0; const std::string expectedName = "PropertyTableName"; CesiumGltf::ExtensionModelExtStructuralMetadata& metadataExtension = model.addExtension< CesiumGltf::ExtensionModelExtStructuralMetadata>(); CesiumGltf::PropertyTable& propertyTable = metadataExtension.propertyTables.emplace_back(); propertyTable.name = expectedName; FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID); const FCesiumFeatureIdTexture texture = UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture( featureIDSet); TestEqual( "TextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( texture), ECesiumFeatureIdTextureStatus::Valid); TestEqual( "GetFeatureTableName", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureTableName( texture), FString(expectedName.c_str())); }); }); }