// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumFeatureIdTexture.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumGltfSpecUtility.h" #include "Misc/AutomationTest.h" #include #include #include BEGIN_DEFINE_SPEC( FCesiumFeatureIdTextureSpec, "Cesium.Unit.FeatureIdTexture", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ServerContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::ProductFilter) CesiumGltf::Model model; CesiumGltf::MeshPrimitive* pPrimitive; const std::vector texCoords{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; TObjectPtr pPrimitiveComponent; END_DEFINE_SPEC(FCesiumFeatureIdTextureSpec) void FCesiumFeatureIdTextureSpec::Define() { Describe("Constructor", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("constructs invalid instance for empty texture", [this]() { FCesiumFeatureIdTexture featureIDTexture; TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::ErrorInvalidTexture); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::ErrorUninitialized); }); It("constructs invalid instance for nonexistent texture", [this]() { CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::ErrorInvalidTexture); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::ErrorInvalidTexture); }); It("constructs invalid instance for texture with invalid image", [this]() { CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = -1; CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::ErrorInvalidTexture); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::ErrorInvalidImage); }); It("constructs valid instance", [this]() { const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::Valid); }); It("constructs valid instance for texture with nonexistent texcoord attribute", [this]() { CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset.emplace(); image.pAsset->width = image.pAsset->height = 1; image.pAsset->channels = 1; image.pAsset->pixelData.push_back(std::byte(42)); CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE; CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = 0; gltfTexture.sampler = 0; CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::Valid); }); It("constructs valid instance for texture with invalid texcoord accessor", [this]() { CesiumGltf::Image& image = model.images.emplace_back(); image.pAsset.emplace(); image.pAsset->width = image.pAsset->height = 1; image.pAsset->channels = 1; image.pAsset->pixelData.push_back(std::byte(42)); CesiumGltf::Sampler& sampler = model.samplers.emplace_back(); sampler.wrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE; sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE; CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = 0; gltfTexture.sampler = 0; CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; pPrimitive->attributes.insert({"TEXCOORD_0", 0}); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); auto featureIDTextureView = featureIDTexture.getFeatureIdTextureView(); TestEqual( "FeatureIDTextureViewStatus", featureIDTextureView.status(), CesiumGltf::FeatureIdTextureViewStatus::Valid); }); }); Describe("GetFeatureIDForUV", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns -1 for invalid texture", [this]() { CesiumGltf::Texture& gltfTexture = model.textures.emplace_back(); gltfTexture.source = -1; CesiumGltf::FeatureIdTexture texture; texture.index = 0; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestNotEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); TestEqual( "FeatureID", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForUV( featureIDTexture, FVector2D::Zero()), -1); }); It("returns correct value for valid attribute", [this]() { const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); for (size_t i = 0; i < texCoords.size(); i++) { const glm::vec2& texCoord = texCoords[i]; int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForUV( featureIDTexture, {texCoord.x, texCoord.y}); TestEqual("FeatureID", featureID, featureIDs[i]); } }); It("returns correct value with KHR_texture_transform", [this]() { const std::vector featureIDs{1, 2, 0, 7}; const std::vector rawTexCoords{ glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(0, 1), glm::vec2(1, 1)}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, rawTexCoords, 0, CesiumGltf::Sampler::WrapS::REPEAT, CesiumGltf::Sampler::WrapT::REPEAT); assert(featureId.texture != std::nullopt); CesiumGltf::ExtensionKhrTextureTransform& textureTransform = featureId.texture ->addExtension(); textureTransform.offset = {0.5, -0.5}; textureTransform.rotation = UE_DOUBLE_HALF_PI; textureTransform.scale = {0.5, 0.5}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); // (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5) // (1, 0) -> (0.5, -1) -> wraps to (0.5, 0) // (0, 1) -> (1, -0.5) -> wraps to (0, 0.5) // (1, 1) -> (1, -1) -> wraps to (0.0, 0.0) std::vector expected{7, 2, 0, 1}; for (size_t i = 0; i < texCoords.size(); i++) { const glm::vec2& texCoord = rawTexCoords[i]; int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForUV( featureIDTexture, {texCoord.x, texCoord.y}); TestEqual("FeatureID", featureID, expected[i]); } }); }); Describe("GetFeatureIDForVertex", [this]() { BeforeEach([this]() { model = CesiumGltf::Model(); CesiumGltf::Mesh& mesh = model.meshes.emplace_back(); pPrimitive = &mesh.primitives.emplace_back(); }); It("returns -1 for invalid texture", [this]() { CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestNotEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); TestEqual( "FeatureIDForVertex", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex( featureIDTexture, 0), -1); }); It("returns -1 for out-of-bounds index", [this]() { const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); TestEqual( "FeatureIDForNegativeVertex", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex( featureIDTexture, -1), -1); TestEqual( "FeatureIDForOutOfBoundsVertex", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex( featureIDTexture, 10), -1); }); It("returns correct value for valid texture", [this]() { const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords, 0); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); for (size_t i = 0; i < featureIDs.size(); i++) { int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex( featureIDTexture, static_cast(i)); TestEqual("FeatureIDForVertex", featureID, featureIDs[i]); } }); It("returns correct value for primitive with multiple texcoords", [this]() { const std::vector texCoord0{ glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0, 0.5), glm::vec2(0.5, 0.5)}; std::vector values(texCoord0.size()); std::memcpy(values.data(), texCoord0.data(), values.size()); CreateAttributeForPrimitive( model, *pPrimitive, "TEXCOORD_0", CesiumGltf::AccessorSpec::Type::VEC2, CesiumGltf::AccessorSpec::ComponentType::FLOAT, std::move(values)); const std::vector texCoord1{ glm::vec2(0.5, 0.5), glm::vec2(0, 0), glm::vec2(0.5, 0), glm::vec2(0.0, 0.5)}; const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoord1, 1); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); const std::vector expected{2, 0, 3, 1}; for (size_t i = 0; i < featureIDs.size(); i++) { int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex( featureIDTexture, static_cast(i)); TestEqual("FeatureIDForVertex", featureID, expected[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 invalid texture", [this]() { CesiumGltf::FeatureIdTexture texture; texture.index = -1; texture.texCoord = 0; texture.channels = {0}; FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, texture, "PropertyTableName"); TestNotEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.Location = FVector_NetQuantize::Zero() * CesiumPrimitiveData::positionScaleFactor; Hit.Component = pPrimitiveComponent; Hit.FaceIndex = 0; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit), -1); }); It("returns -1 if hit has no valid component", [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 texCoords0{ 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, texCoords0, 0); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.Location = FVector_NetQuantize(0, -1, 0) * CesiumPrimitiveData::positionScaleFactor; Hit.FaceIndex = 0; Hit.Component = nullptr; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit), -1); }); It("returns -1 if specified texcoord set does not exist", [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 texCoords0{ 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, texCoords0, 0); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 1, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.Location = FVector_NetQuantize(0, -1, 0) * CesiumPrimitiveData::positionScaleFactor; Hit.FaceIndex = 0; Hit.Component = pPrimitiveComponent; TestEqual( "FeatureIDFromHit", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit), -1); }); It("returns correct value for valid texture", [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 texCoords0{ 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, texCoords0, 0); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.FaceIndex = 0; Hit.Component = pPrimitiveComponent; 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; int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit); TestEqual("FeatureIDFromHit", featureID, expected[i]); } }); It("returns correct value for different face", [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 texCoords0{ 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, texCoords0, 0); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.FaceIndex = 1; Hit.Component = pPrimitiveComponent; std::array locations{ FVector_NetQuantize(1, 3, 0), FVector_NetQuantize(0, -4, 0), FVector_NetQuantize(0.0, -3.25, 0)}; std::array expected{3, 1, 0}; for (size_t i = 0; i < locations.size(); i++) { Hit.Location = locations[i] * CesiumPrimitiveData::positionScaleFactor; int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit); TestEqual("FeatureIDFromHit", featureID, expected[i]); } }); It("returns correct value for primitive with multiple texcoords", [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 texCoords0{ glm::vec2(-1, 0), glm::vec2(0, 1), glm::vec2(1, 0), glm::vec2(-1, 0), glm::vec2(0, 1), glm::vec2(1, 0)}; CreateAttributeForPrimitive( model, *pPrimitive, "TEXCOORD_0", CesiumGltf::AccessorSpec::Type::VEC2, CesiumGltf::AccessorSpec::ComponentType::FLOAT, GetValuesAsBytes(texCoords0)); int32 texCoord0AccessorIndex = static_cast(model.accessors.size() - 1); std::vector texCoords1{ glm::vec2(0.5, 0.5), glm::vec2(0, 1.0), glm::vec2(1, 0), glm::vec2(0.5, 0.5), glm::vec2(0, 1.0), glm::vec2(1, 0), }; const std::vector featureIDs{0, 3, 1, 2}; CesiumGltf::FeatureId& featureId = AddFeatureIDsAsTextureToModel( model, *pPrimitive, featureIDs, 4, 2, 2, texCoords1, 1); FCesiumFeatureIdTexture featureIDTexture( model, *pPrimitive, *featureId.texture, "PropertyTableName"); CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData(); primData.PositionAccessor = CesiumGltf::AccessorView(model, positionAccessorIndex); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, texCoord0AccessorIndex)); primData.TexCoordAccessorMap.emplace( 0, CesiumGltf::AccessorView>( model, 1)); primData.TexCoordAccessorMap.emplace( 1, CesiumGltf::AccessorView>( model, static_cast(model.accessors.size() - 1))); TestEqual( "FeatureIDTextureStatus", UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus( featureIDTexture), ECesiumFeatureIdTextureStatus::Valid); FHitResult Hit; Hit.FaceIndex = 0; Hit.Component = pPrimitiveComponent; std::array locations{ FVector_NetQuantize(1, 0, 0), FVector_NetQuantize(0, -1, 0), FVector_NetQuantize(-1, 0, 0)}; std::array expected{3, 1, 2}; for (size_t i = 0; i < locations.size(); i++) { Hit.Location = locations[i] * CesiumPrimitiveData::positionScaleFactor; int64 featureID = UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDFromHit( featureIDTexture, Hit); TestEqual("FeatureIDFromHit", featureID, expected[i]); } }); }); }