初始提交: UE5.3项目基础框架
This commit is contained in:
@ -0,0 +1,92 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "Cesium3DTileset.h"
|
||||
#include "CesiumGlobeAnchorComponent.h"
|
||||
#include "CesiumGltfComponent.h"
|
||||
#include "CesiumLoadTestCore.h"
|
||||
#include "CesiumSceneGeneration.h"
|
||||
#include "CesiumSunSky.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Tests/AutomationCommon.h"
|
||||
#include "Tests/AutomationTestSettings.h"
|
||||
#include <Cesium3DTilesSelection/TilesetSharedAssetSystem.h>
|
||||
#include <CesiumAsync/ICacheDatabase.h>
|
||||
|
||||
#define TEST_SCREEN_WIDTH 1280
|
||||
#define TEST_SCREEN_HEIGHT 720
|
||||
|
||||
namespace Cesium {
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FCesium3DTilesetSharedImages,
|
||||
"Cesium.Unit.3DTileset.SharedImages",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter);
|
||||
|
||||
static void setupForSharedImages(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(21.16677692, -67.38013505, -6375355.1944),
|
||||
FVector(-12, -1300, -5),
|
||||
FRotator(0, 90, 0),
|
||||
60.0f);
|
||||
|
||||
context.georeference->SetOriginEarthCenteredEarthFixed(FVector(0, 0, 0));
|
||||
context.pawn->SetActorLocation(FVector(485.0, 2400.0, 520.0));
|
||||
context.pawn->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270)));
|
||||
|
||||
context.sunSky->TimeZone = 9.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesiumGeoreference* georeference =
|
||||
context.world->SpawnActor<ACesiumGeoreference>();
|
||||
check(georeference != nullptr);
|
||||
georeference->SetOriginPlacement(EOriginPlacement::TrueOrigin);
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2757071);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
|
||||
tileset->SetActorLabel(TEXT("SharedImages"));
|
||||
tileset->SetGeoreference(georeference);
|
||||
tileset->SuspendUpdate = false;
|
||||
tileset->LogSelectionStats = true;
|
||||
context.tilesets.push_back(tileset);
|
||||
|
||||
UCesiumGlobeAnchorComponent* GlobeAnchorComponent =
|
||||
NewObject<UCesiumGlobeAnchorComponent>(tileset, TEXT("GlobeAnchor"));
|
||||
tileset->AddInstanceComponent(GlobeAnchorComponent);
|
||||
GlobeAnchorComponent->SetAdjustOrientationForGlobeWhenMoving(false);
|
||||
GlobeAnchorComponent->SetGeoreference(georeference);
|
||||
GlobeAnchorComponent->RegisterComponent();
|
||||
GlobeAnchorComponent->MoveToEarthCenteredEarthFixedPosition(
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
|
||||
ADirectionalLight* Light = context.world->SpawnActor<ADirectionalLight>();
|
||||
Light->SetActorRotation(FQuat::MakeFromEuler(FVector(0, 0, 270)));
|
||||
}
|
||||
|
||||
void tilesetPass(
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {}
|
||||
|
||||
bool FCesium3DTilesetSharedImages::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Refresh Pass", tilesetPass, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForSharedImages,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
} // namespace Cesium
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,90 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumCameraManager.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumCameraManagerSpec,
|
||||
"Cesium.Unit.CameraManager",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
END_DEFINE_SPEC(FCesiumCameraManagerSpec)
|
||||
|
||||
void FCesiumCameraManagerSpec::Define() {
|
||||
|
||||
Describe("GetDefaultCameraManager", [this]() {
|
||||
It("should get the default camera manager", [this]() {
|
||||
UWorld* world = CesiumTestHelpers::getGlobalWorldContext();
|
||||
ACesiumCameraManager* cameraManager =
|
||||
ACesiumCameraManager::GetDefaultCameraManager(world);
|
||||
TestNotNull("Returned pointer is valid", cameraManager);
|
||||
});
|
||||
|
||||
It("should fail to get the default camera manager, when world context is null",
|
||||
[this]() {
|
||||
ACesiumCameraManager* cameraManager =
|
||||
ACesiumCameraManager::GetDefaultCameraManager(nullptr);
|
||||
TestNull("Returned pointer should be null", cameraManager);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("AddCamera", [this]() {
|
||||
It("should add and remove a single camera", [this]() {
|
||||
UWorld* world = CesiumTestHelpers::getGlobalWorldContext();
|
||||
ACesiumCameraManager* cameraManager =
|
||||
ACesiumCameraManager::GetDefaultCameraManager(world);
|
||||
TestNotNull("Returned pointer is valid", cameraManager);
|
||||
|
||||
const TMap<int32, FCesiumCamera>& camerasMapRef =
|
||||
cameraManager->GetCameras();
|
||||
TestEqual("Starting camera count is 0", camerasMapRef.Num(), 0);
|
||||
|
||||
FCesiumCamera newCamera;
|
||||
int32 newCameraId = cameraManager->AddCamera(newCamera);
|
||||
TestEqual(
|
||||
"Camera count is 1 after camera is added",
|
||||
camerasMapRef.Num(),
|
||||
1);
|
||||
|
||||
bool removeSuccess = cameraManager->RemoveCamera(newCameraId);
|
||||
TestTrue("Remove function returns success", removeSuccess);
|
||||
TestEqual("Camera count returns to 0", camerasMapRef.Num(), 0);
|
||||
});
|
||||
|
||||
It("should fail to remove a camera, when the id is invalid", [this]() {
|
||||
UWorld* world = CesiumTestHelpers::getGlobalWorldContext();
|
||||
ACesiumCameraManager* cameraManager =
|
||||
ACesiumCameraManager::GetDefaultCameraManager(world);
|
||||
TestNotNull("Returned pointer is valid", cameraManager);
|
||||
|
||||
const TMap<int32, FCesiumCamera>& camerasMapRef =
|
||||
cameraManager->GetCameras();
|
||||
TestEqual("Starting camera count is 0", camerasMapRef.Num(), 0);
|
||||
|
||||
int32 bogusZeroCameraId = 0;
|
||||
bool removeSuccess = cameraManager->RemoveCamera(bogusZeroCameraId);
|
||||
TestFalse(
|
||||
"Remove function fails with bogus zero camera id",
|
||||
removeSuccess);
|
||||
TestEqual("Camera count remains at 0", camerasMapRef.Num(), 0);
|
||||
|
||||
int32 bogusPositiveCameraId = 5;
|
||||
removeSuccess = cameraManager->RemoveCamera(bogusPositiveCameraId);
|
||||
TestFalse(
|
||||
"Remove function fails with bogus positive camera id",
|
||||
removeSuccess);
|
||||
TestEqual("Camera count remains at 0", camerasMapRef.Num(), 0);
|
||||
|
||||
int32 bogusNegativeCameraId = -5;
|
||||
removeSuccess = cameraManager->RemoveCamera(bogusNegativeCameraId);
|
||||
TestFalse(
|
||||
"Remove function fails with bogus negative camera id",
|
||||
removeSuccess);
|
||||
TestEqual("Camera count remains at 0", camerasMapRef.Num(), 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,279 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumFeatureIdAttribute.h"
|
||||
#include "CesiumGltf/ExtensionExtMeshFeatures.h"
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumFeatureIdAttributeSpec,
|
||||
"Cesium.Unit.FeatureIdAttribute",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
CesiumGltf::Model model;
|
||||
CesiumGltf::MeshPrimitive* pPrimitive;
|
||||
END_DEFINE_SPEC(FCesiumFeatureIdAttributeSpec)
|
||||
|
||||
void FCesiumFeatureIdAttributeSpec::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 attribute", [this]() {
|
||||
FCesiumFeatureIdAttribute featureIDAttribute;
|
||||
|
||||
TestEqual("AttributeIndex", featureIDAttribute.getAttributeIndex(), -1);
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAttribute);
|
||||
});
|
||||
|
||||
It("constructs invalid instance for nonexistent attribute", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"AttributeIndex",
|
||||
featureIDAttribute.getAttributeIndex(),
|
||||
attributeIndex);
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAttribute);
|
||||
});
|
||||
|
||||
It("constructs invalid instance for attribute with nonexistent accessor",
|
||||
[this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
pPrimitive->attributes.insert({"_FEATURE_ID_0", 0});
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"AttributeIndex",
|
||||
featureIDAttribute.getAttributeIndex(),
|
||||
attributeIndex);
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor);
|
||||
});
|
||||
|
||||
It("constructs invalid instance for attribute with invalid accessor",
|
||||
[this]() {
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.type = CesiumGltf::AccessorSpec::Type::VEC2;
|
||||
accessor.componentType =
|
||||
CesiumGltf::AccessorSpec::ComponentType::FLOAT;
|
||||
const int64 attributeIndex = 0;
|
||||
pPrimitive->attributes.insert({"_FEATURE_ID_0", 0});
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"AttributeIndex",
|
||||
featureIDAttribute.getAttributeIndex(),
|
||||
attributeIndex);
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor);
|
||||
});
|
||||
|
||||
It("constructs valid instance", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> featureIDs{0, 0, 0, 3, 3, 3, 1, 1, 1, 2, 2, 2};
|
||||
AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
featureIDs,
|
||||
4,
|
||||
attributeIndex);
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"AttributeIndex",
|
||||
featureIDAttribute.getAttributeIndex(),
|
||||
attributeIndex);
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::Valid);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetCount", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
});
|
||||
|
||||
It("returns 0 for invalid attribute", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
pPrimitive->attributes.insert({"_FEATURE_ID_0", 0});
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor);
|
||||
TestEqual(
|
||||
"VertexCount",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetCount(
|
||||
featureIDAttribute),
|
||||
0);
|
||||
});
|
||||
|
||||
It("returns correct value for valid attribute", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> featureIDs{0, 0, 0, 3, 3, 3, 1, 1, 1, 2, 2, 2};
|
||||
const int64 vertexCount = static_cast<int64>(featureIDs.size());
|
||||
AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
featureIDs,
|
||||
4,
|
||||
attributeIndex);
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::Valid);
|
||||
TestEqual(
|
||||
"VertexCount",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetCount(
|
||||
featureIDAttribute),
|
||||
vertexCount);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetFeatureID", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
});
|
||||
|
||||
It("returns -1 for invalid attribute", [this]() {
|
||||
const int64 attribute = 0;
|
||||
pPrimitive->attributes.insert({"_FEATURE_ID_0", 0});
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attribute,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::ErrorInvalidAccessor);
|
||||
TestEqual(
|
||||
"FeatureIDForVertex",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
||||
featureIDAttribute,
|
||||
0),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns -1 for out-of-bounds index", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> featureIDs{0, 0, 0, 1, 1, 1};
|
||||
AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
featureIDs,
|
||||
2,
|
||||
attributeIndex);
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::Valid);
|
||||
TestEqual(
|
||||
"FeatureIDForNegativeVertex",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
||||
featureIDAttribute,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsVertex",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
||||
featureIDAttribute,
|
||||
10),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns correct value for valid attribute", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> featureIDs{0, 0, 0, 3, 3, 3, 1, 1, 1, 2, 2, 2};
|
||||
AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
featureIDs,
|
||||
4,
|
||||
attributeIndex);
|
||||
|
||||
FCesiumFeatureIdAttribute featureIDAttribute(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIndex,
|
||||
"PropertyTableName");
|
||||
TestEqual(
|
||||
"FeatureIDAttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(featureIDAttribute),
|
||||
ECesiumFeatureIdAttributeStatus::Valid);
|
||||
for (size_t i = 0; i < featureIDs.size(); i++) {
|
||||
TestEqual(
|
||||
"FeatureIDForVertex",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
||||
featureIDAttribute,
|
||||
static_cast<int64>(i)),
|
||||
featureIDs[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,655 @@
|
||||
// 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<UCesiumGltfPrimitiveComponent> 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<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
});
|
||||
|
||||
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<int64>(featureId.featureCount));
|
||||
});
|
||||
|
||||
It("constructs set with feature ID attribute", [this]() {
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> 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<int64>(featureID.featureCount));
|
||||
});
|
||||
|
||||
It("constructs set with feature ID texture", [this]() {
|
||||
const std::vector<uint8_t> featureIDs{0, 3, 1, 2};
|
||||
const std::vector<glm::vec2> 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<int64>(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<int64>(featureId.featureCount));
|
||||
TestEqual(
|
||||
"NullFeatureID",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetNullFeatureID(featureIDSet),
|
||||
static_cast<int64>(*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<int64>(featureId.featureCount));
|
||||
TestEqual(
|
||||
"PropertyTableIndex",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex(
|
||||
featureIDSet),
|
||||
static_cast<int64>(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<uint8_t> 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<uint8_t> featureIDs{0, 3, 1, 2};
|
||||
const std::vector<glm::vec2> 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<uint8_t> 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<int64>(i)),
|
||||
featureIDs[i]);
|
||||
}
|
||||
});
|
||||
|
||||
It("returns correct value for texture set", [this]() {
|
||||
const std::vector<uint8_t> featureIDs{0, 3, 1, 2};
|
||||
const std::vector<glm::vec2> 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<int64>(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<UCesiumGltfPrimitiveComponent>();
|
||||
pPrimitiveComponent->getPrimitiveData().pMeshPrimitive = pPrimitive;
|
||||
|
||||
std::vector<glm::vec3> 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<FVector3f>(
|
||||
model,
|
||||
static_cast<int32_t>(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<int32_t>(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<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(model.accessors.size() - 1)));
|
||||
|
||||
FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureID);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.Component = pPrimitiveComponent;
|
||||
Hit.FaceIndex = 0;
|
||||
|
||||
std::array<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 0, 0),
|
||||
FVector_NetQuantize(0, -1, 0),
|
||||
FVector_NetQuantize(0.0, -0.25, 0)};
|
||||
std::array<int64, 3> 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<FVector3f>(
|
||||
model,
|
||||
static_cast<int32_t>(model.accessors.size() - 1));
|
||||
|
||||
FCesiumFeatureIdSet featureIDSet(model, *pPrimitive, featureId);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.Component = pPrimitiveComponent;
|
||||
Hit.FaceIndex = 0;
|
||||
|
||||
std::array<int32, 3> faceIndices{0, 1, 0};
|
||||
std::array<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 0, 0),
|
||||
FVector_NetQuantize(0, -4, 0),
|
||||
FVector_NetQuantize(-1, 0, 0)};
|
||||
std::array<int64, 3> 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<int32_t>(model.accessors.size() - 1);
|
||||
const int64 attributeIndex = 0;
|
||||
const std::vector<uint8_t> featureIDs{0, 0, 0, 1, 1, 1};
|
||||
CesiumGltf::FeatureId& featureId = AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
featureIDs,
|
||||
2,
|
||||
attributeIndex);
|
||||
|
||||
CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData();
|
||||
primData.PositionAccessor =
|
||||
CesiumGltf::AccessorView<FVector3f>(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<uint8_t> 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<uint8_t> featureIDs{0, 3, 1, 2};
|
||||
const std::vector<glm::vec2> 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()));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,921 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumFeatureIdTexture.h"
|
||||
#include "CesiumGltfPrimitiveComponent.h"
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include <CesiumGltf/ExtensionExtMeshFeatures.h>
|
||||
#include <CesiumGltf/ExtensionKhrTextureTransform.h>
|
||||
#include <CesiumUtility/Math.h>
|
||||
|
||||
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<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.5, 0),
|
||||
glm::vec2(0, 0.5),
|
||||
glm::vec2(0.5, 0.5)};
|
||||
TObjectPtr<UCesiumGltfPrimitiveComponent> 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<uint8_t> 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<uint8_t> 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<uint8_t> featureIDs{1, 2, 0, 7};
|
||||
const std::vector<glm::vec2> 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<CesiumGltf::ExtensionKhrTextureTransform>();
|
||||
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<uint8_t> 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<uint8_t> 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<uint8_t> 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<int64>(i));
|
||||
TestEqual("FeatureIDForVertex", featureID, featureIDs[i]);
|
||||
}
|
||||
});
|
||||
|
||||
It("returns correct value for primitive with multiple texcoords", [this]() {
|
||||
const std::vector<glm::vec2> texCoord0{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.5, 0),
|
||||
glm::vec2(0, 0.5),
|
||||
glm::vec2(0.5, 0.5)};
|
||||
|
||||
std::vector<std::byte> 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<glm::vec2> 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<uint8_t> 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<uint8_t> expected{2, 0, 3, 1};
|
||||
for (size_t i = 0; i < featureIDs.size(); i++) {
|
||||
int64 featureID =
|
||||
UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDForVertex(
|
||||
featureIDTexture,
|
||||
static_cast<int64>(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<UCesiumGltfPrimitiveComponent>();
|
||||
pPrimitiveComponent->getPrimitiveData().pMeshPrimitive = pPrimitive;
|
||||
|
||||
std::vector<glm::vec3> 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<int32_t>(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<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(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<int32_t>(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<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
1,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(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<int32_t>(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<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(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<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 0, 0),
|
||||
FVector_NetQuantize(0, -1, 0),
|
||||
FVector_NetQuantize(0.0, -0.25, 0)};
|
||||
std::array<int64, 3> 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<int32_t>(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<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(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<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 3, 0),
|
||||
FVector_NetQuantize(0, -4, 0),
|
||||
FVector_NetQuantize(0.0, -3.25, 0)};
|
||||
std::array<int64, 3> 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<int32_t>(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<glm::vec2> 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<int32_t>(model.accessors.size() - 1);
|
||||
|
||||
std::vector<glm::vec2> 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<uint8_t> 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<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
texCoord0AccessorIndex));
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
1));
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
1,
|
||||
CesiumGltf::AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(model.accessors.size() - 1)));
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDTextureStatus",
|
||||
UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus(
|
||||
featureIDTexture),
|
||||
ECesiumFeatureIdTextureStatus::Valid);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.FaceIndex = 0;
|
||||
Hit.Component = pPrimitiveComponent;
|
||||
|
||||
std::array<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 0, 0),
|
||||
FVector_NetQuantize(0, -1, 0),
|
||||
FVector_NetQuantize(-1, 0, 0)};
|
||||
std::array<int64, 3> 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]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumGeospatial/Ellipsoid.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "CesiumUtility/Math.h"
|
||||
#include "GeoTransforms.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
using namespace CesiumGeospatial;
|
||||
using namespace CesiumUtility;
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumGeoreferenceSpec,
|
||||
"Cesium.Unit.Georeference",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
TObjectPtr<ACesiumGeoreference> pGeoreferenceNullIsland;
|
||||
TObjectPtr<ACesiumGeoreference> pGeoreference90Longitude;
|
||||
|
||||
END_DEFINE_SPEC(FCesiumGeoreferenceSpec)
|
||||
|
||||
void FCesiumGeoreferenceSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
pGeoreferenceNullIsland = pWorld->SpawnActor<ACesiumGeoreference>();
|
||||
pGeoreferenceNullIsland->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
pGeoreference90Longitude = pWorld->SpawnActor<ACesiumGeoreference>();
|
||||
pGeoreference90Longitude->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(90.0, 0.0, 0.0));
|
||||
});
|
||||
|
||||
Describe("Position Transformation", [this]() {
|
||||
It("transforms longitude-latitude-height positions to Unreal", [this]() {
|
||||
FVector nullIslandUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
TestEqual("nullIslandUnreal", nullIslandUnreal, FVector(0.0, 0.0, 0.0));
|
||||
|
||||
FVector antiMeridianUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(180.0, 0.0, 0.0));
|
||||
TestEqual(
|
||||
"antiMeridianUnreal",
|
||||
antiMeridianUnreal,
|
||||
FVector(
|
||||
0.0,
|
||||
0.0,
|
||||
-2.0 * Ellipsoid::WGS84.getMaximumRadius() * 100.0));
|
||||
});
|
||||
|
||||
It("transforms Earth-Centered, Earth-Fixed positions to Unreal", [this]() {
|
||||
FVector nullIslandUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformEarthCenteredEarthFixedPositionToUnreal(
|
||||
FVector(Ellipsoid::WGS84.getMaximumRadius(), 0.0, 0.0));
|
||||
TestEqual("nullIslandUnreal", nullIslandUnreal, FVector(0.0, 0.0, 0.0));
|
||||
|
||||
FVector antiMeridianUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformEarthCenteredEarthFixedPositionToUnreal(
|
||||
FVector(-Ellipsoid::WGS84.getMaximumRadius(), 0.0, 0.0));
|
||||
TestEqual(
|
||||
"antiMeridianUnreal",
|
||||
antiMeridianUnreal,
|
||||
FVector(
|
||||
0.0,
|
||||
0.0,
|
||||
-2.0 * Ellipsoid::WGS84.getMaximumRadius() * 100.0));
|
||||
});
|
||||
|
||||
It("transforms Unreal positions to longitude-latitude-height", [this]() {
|
||||
FVector nullIslandLLH =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealPositionToLongitudeLatitudeHeight(
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
TestEqual("nullIslandLLH", nullIslandLLH, FVector(0.0, 0.0, 0.0));
|
||||
|
||||
FVector antiMeridianLLH =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealPositionToLongitudeLatitudeHeight(FVector(
|
||||
0.0,
|
||||
0.0,
|
||||
-2.0 * Ellipsoid::WGS84.getMaximumRadius() * 100.0));
|
||||
TestEqual("antiMeridianLLH", antiMeridianLLH, FVector(180.0, 0.0, 0.0));
|
||||
});
|
||||
|
||||
It("transforms Unreal positions to Earth-Centered, Earth-Fixed", [this]() {
|
||||
FVector nullIslandEcef =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealPositionToEarthCenteredEarthFixed(
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
TestEqual(
|
||||
"nullIslandEcef",
|
||||
nullIslandEcef,
|
||||
FVector(Ellipsoid::WGS84.getMaximumRadius(), 0.0, 0.0));
|
||||
|
||||
FVector antiMeridianEcef =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealPositionToEarthCenteredEarthFixed(FVector(
|
||||
0.0,
|
||||
0.0,
|
||||
-2.0 * Ellipsoid::WGS84.getMaximumRadius() * 100.0));
|
||||
TestEqual(
|
||||
"antiMeridianEcef",
|
||||
antiMeridianEcef,
|
||||
FVector(-Ellipsoid::WGS84.getMaximumRadius(), 0.0, 0.0));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("Direction Transformation", [this]() {
|
||||
It("transforms Earth-Centered, Earth-Fixed directions to Unreal", [this]() {
|
||||
FVector northAtNullIslandUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformEarthCenteredEarthFixedDirectionToUnreal(
|
||||
FVector(0.0, 0.0, 1.0));
|
||||
TestEqual(
|
||||
"northAtNullIslandUnreal",
|
||||
northAtNullIslandUnreal,
|
||||
FVector(0.0, -100.0, 0.0)); // meters -> centimeters
|
||||
|
||||
FVector westAtAntiMeridianUnreal =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformEarthCenteredEarthFixedDirectionToUnreal(
|
||||
FVector(0.0, 1.0, 0.0));
|
||||
TestEqual(
|
||||
"westAtAntiMeridianUnreal",
|
||||
westAtAntiMeridianUnreal,
|
||||
FVector(
|
||||
100.0, // West at anti-merdian is East at null island
|
||||
0.0,
|
||||
0.0));
|
||||
});
|
||||
|
||||
It("transforms Unreal directions to Earth-Centered, Earth-Fixed", [this]() {
|
||||
FVector northAtNullIslandEcef =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
FVector(0.0, -1.0, 0.0));
|
||||
TestEqual(
|
||||
"northAtNullIslandEcef",
|
||||
northAtNullIslandEcef,
|
||||
FVector(0.0, 0.0, 1.0 / 100.0)); // centimeters -> meters
|
||||
|
||||
// West at anti-merdian is East at null island
|
||||
FVector westAtAntiMeridianEcef =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
FVector(1.0, 0.0, 0.0));
|
||||
TestEqual(
|
||||
"westAtAntiMeridianEcef",
|
||||
westAtAntiMeridianEcef,
|
||||
FVector(0.0, 1.0 / 100.0, 0.0));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("Rotator Transformation", [this]() {
|
||||
It("treats Unreal and East-South-Up as identical at the georeference origin",
|
||||
[this]() {
|
||||
FRotator atOrigin1 =
|
||||
pGeoreferenceNullIsland->TransformEastSouthUpRotatorToUnreal(
|
||||
FRotator(1.0, 2.0, 3.0),
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
TestEqual("atOrigin1", atOrigin1, FRotator(1.0, 2.0, 3.0));
|
||||
|
||||
FRotator atOrigin2 =
|
||||
pGeoreferenceNullIsland->TransformUnrealRotatorToEastSouthUp(
|
||||
FRotator(1.0, 2.0, 3.0),
|
||||
FVector(0.0, 0.0, 0.0));
|
||||
TestEqual("atOrigin2", atOrigin2, FRotator(1.0, 2.0, 3.0));
|
||||
});
|
||||
|
||||
It("transforms East-South-Up Rotators to Unreal", [this]() {
|
||||
FRotator rotationAt90DegreesLongitude = FRotator(1.0, 2.0, 3.0);
|
||||
FVector originOf90DegreesLongitudeInNullIslandCoordinates =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(90.0, 0.0, 0.0));
|
||||
|
||||
FRotator rotationAtNullIsland =
|
||||
pGeoreferenceNullIsland->TransformEastSouthUpRotatorToUnreal(
|
||||
rotationAt90DegreesLongitude,
|
||||
originOf90DegreesLongitudeInNullIslandCoordinates);
|
||||
|
||||
CesiumTestHelpers::TestRotatorsAreEquivalent(
|
||||
this,
|
||||
pGeoreference90Longitude,
|
||||
rotationAt90DegreesLongitude,
|
||||
pGeoreferenceNullIsland,
|
||||
rotationAtNullIsland);
|
||||
});
|
||||
|
||||
It("transforms Unreal Rotators to East-South-Up", [this]() {
|
||||
FRotator rotationAtNullIsland = FRotator(1.0, 2.0, 3.0);
|
||||
FVector originOf90DegreesLongitudeInNullIslandCoordinates =
|
||||
pGeoreferenceNullIsland
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(90.0, 0.0, 0.0));
|
||||
|
||||
FRotator rotationAt90DegreesLongitude =
|
||||
pGeoreferenceNullIsland->TransformUnrealRotatorToEastSouthUp(
|
||||
rotationAtNullIsland,
|
||||
originOf90DegreesLongitudeInNullIslandCoordinates);
|
||||
|
||||
CesiumTestHelpers::TestRotatorsAreEquivalent(
|
||||
this,
|
||||
pGeoreferenceNullIsland,
|
||||
rotationAtNullIsland,
|
||||
pGeoreference90Longitude,
|
||||
rotationAt90DegreesLongitude);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumGlobeAnchorComponent.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "CesiumWgs84Ellipsoid.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumGlobeAnchorSpec,
|
||||
"Cesium.Unit.GlobeAnchor",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
TObjectPtr<AActor> pActor;
|
||||
TObjectPtr<UCesiumGlobeAnchorComponent> pGlobeAnchor;
|
||||
TObjectPtr<UCesiumEllipsoid> pEllipsoid;
|
||||
|
||||
END_DEFINE_SPEC(FCesiumGlobeAnchorSpec)
|
||||
|
||||
void FCesiumGlobeAnchorSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
this->pActor = pWorld->SpawnActor<AActor>();
|
||||
this->pActor->AddComponentByClass(
|
||||
USceneComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false);
|
||||
this->pActor->SetActorRelativeTransform(FTransform());
|
||||
|
||||
this->pEllipsoid = NewObject<UCesiumEllipsoid>();
|
||||
this->pEllipsoid->SetRadii(UCesiumWgs84Ellipsoid::GetRadii());
|
||||
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
ACesiumGeoreference::GetDefaultGeoreferenceForActor(pActor);
|
||||
pGeoreference->SetOriginLongitudeLatitudeHeight(FVector(1.0, 2.0, 3.0));
|
||||
pGeoreference->SetEllipsoid(this->pEllipsoid);
|
||||
|
||||
this->pGlobeAnchor =
|
||||
Cast<UCesiumGlobeAnchorComponent>(pActor->AddComponentByClass(
|
||||
UCesiumGlobeAnchorComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
});
|
||||
|
||||
It("immediately syncs globe position from transform when added", [this]() {
|
||||
TestEqual("Longitude", this->pGlobeAnchor->GetLongitude(), 1.0);
|
||||
TestEqual("Latitude", this->pGlobeAnchor->GetLatitude(), 2.0);
|
||||
TestEqual("Height", this->pGlobeAnchor->GetHeight(), 3.0);
|
||||
});
|
||||
|
||||
It("maintains globe position when switching to a new georeference", [this]() {
|
||||
FTransform beforeTransform = this->pActor->GetActorTransform();
|
||||
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();
|
||||
|
||||
UWorld* pWorld = this->pActor->GetWorld();
|
||||
ACesiumGeoreference* pNewGeoref = pWorld->SpawnActor<ACesiumGeoreference>();
|
||||
pNewGeoref->SetOriginLongitudeLatitudeHeight(FVector(10.0, 20.0, 30.0));
|
||||
this->pGlobeAnchor->SetGeoreference(pNewGeoref);
|
||||
|
||||
TestEqual(
|
||||
"ResolvedGeoreference",
|
||||
this->pGlobeAnchor->GetResolvedGeoreference(),
|
||||
pNewGeoref);
|
||||
TestFalse(
|
||||
"Transforms are equal",
|
||||
this->pActor->GetActorTransform().Equals(beforeTransform));
|
||||
TestEqual(
|
||||
"Globe Position",
|
||||
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
beforeLLH);
|
||||
});
|
||||
|
||||
It("updates actor transform when globe anchor position is changed", [this]() {
|
||||
FTransform beforeTransform = this->pActor->GetActorTransform();
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(FVector(4.0, 5.0, 6.0));
|
||||
TestEqual(
|
||||
"LongitudeLatitudeHeight",
|
||||
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
FVector(4.0, 5.0, 6.0));
|
||||
TestFalse(
|
||||
"Transforms are equal",
|
||||
this->pActor->GetActorTransform().Equals(beforeTransform));
|
||||
});
|
||||
|
||||
It("updates globe anchor position when actor transform is changed", [this]() {
|
||||
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();
|
||||
this->pActor->SetActorLocation(FVector(1000.0, 2000.0, 3000.0));
|
||||
TestNotEqual(
|
||||
"globe position",
|
||||
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
beforeLLH);
|
||||
});
|
||||
|
||||
It("allows the actor transform to be set when not registered", [this]() {
|
||||
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();
|
||||
|
||||
this->pGlobeAnchor->UnregisterComponent();
|
||||
this->pActor->SetActorLocation(FVector(1000.0, 2000.0, 3000.0));
|
||||
|
||||
// Globe position doesn't initially update while unregistered
|
||||
TestEqual(
|
||||
"globe position",
|
||||
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
beforeLLH);
|
||||
|
||||
// After we re-register, actor transform should be maintained and globe
|
||||
// transform should be updated.
|
||||
this->pGlobeAnchor->RegisterComponent();
|
||||
TestEqual(
|
||||
"actor position",
|
||||
this->pActor->GetActorLocation(),
|
||||
FVector(1000.0, 2000.0, 3000.0));
|
||||
TestNotEqual(
|
||||
"globe position",
|
||||
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
beforeLLH);
|
||||
});
|
||||
|
||||
It("adjusts orientation for globe when actor position is set immediately after adding anchor",
|
||||
[this]() {
|
||||
FRotator beforeRotation = this->pActor->GetActorRotation();
|
||||
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
this->pGlobeAnchor->GetResolvedGeoreference();
|
||||
this->pActor->SetActorLocation(
|
||||
pGeoreference->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(90.0, 2.0, 3.0)));
|
||||
TestNotEqual(
|
||||
"rotation",
|
||||
this->pActor->GetActorRotation(),
|
||||
beforeRotation);
|
||||
});
|
||||
|
||||
It("adjusts orientation for globe when globe position is set immediately after adding anchor",
|
||||
[this]() {
|
||||
FRotator beforeRotation = this->pActor->GetActorRotation();
|
||||
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
|
||||
FVector(90.0, 2.0, 3.0));
|
||||
TestNotEqual(
|
||||
"rotation",
|
||||
this->pActor->GetActorRotation(),
|
||||
beforeRotation);
|
||||
});
|
||||
|
||||
It("does not adjust orientation for globe when that feature is disabled",
|
||||
[this]() {
|
||||
this->pGlobeAnchor->SetAdjustOrientationForGlobeWhenMoving(false);
|
||||
FRotator beforeRotation = this->pActor->GetActorRotation();
|
||||
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
this->pGlobeAnchor->GetResolvedGeoreference();
|
||||
this->pActor->SetActorLocation(
|
||||
pGeoreference->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
FVector(90.0, 2.0, 3.0)));
|
||||
TestEqual("rotation", this->pActor->GetActorRotation(), beforeRotation);
|
||||
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
|
||||
FVector(45.0, 25.0, 300.0));
|
||||
TestEqual("rotation", this->pActor->GetActorRotation(), beforeRotation);
|
||||
});
|
||||
|
||||
It("gains correct orientation on call to SnapToEastSouthUp", [this]() {
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
this->pGlobeAnchor->GetResolvedGeoreference();
|
||||
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
|
||||
FVector(-20.0, -10.0, 1000.0));
|
||||
this->pGlobeAnchor->SnapToEastSouthUp();
|
||||
|
||||
const FTransform& transform = this->pActor->GetActorTransform();
|
||||
FVector actualEcefEast =
|
||||
pGeoreference
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
transform.TransformVector(FVector::XAxisVector))
|
||||
.GetSafeNormal();
|
||||
FVector actualEcefSouth =
|
||||
pGeoreference
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
transform.TransformVector(FVector::YAxisVector))
|
||||
.GetSafeNormal();
|
||||
FVector actualEcefUp =
|
||||
pGeoreference
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
transform.TransformVector(FVector::ZAxisVector))
|
||||
.GetSafeNormal();
|
||||
|
||||
FMatrix enuToEcef =
|
||||
UCesiumWgs84Ellipsoid::EastNorthUpToEarthCenteredEarthFixed(
|
||||
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition());
|
||||
FVector expectedEcefEast =
|
||||
enuToEcef.TransformVector(FVector::XAxisVector).GetSafeNormal();
|
||||
FVector expectedEcefSouth =
|
||||
-enuToEcef.TransformVector(FVector::YAxisVector).GetSafeNormal();
|
||||
FVector expectedEcefUp =
|
||||
enuToEcef.TransformVector(FVector::ZAxisVector).GetSafeNormal();
|
||||
|
||||
TestEqual("east", actualEcefEast, expectedEcefEast);
|
||||
TestEqual("south", actualEcefSouth, expectedEcefSouth);
|
||||
TestEqual("up", actualEcefUp, expectedEcefUp);
|
||||
});
|
||||
|
||||
It("gains correct orientation on call to SnapLocalUpToEllipsoidNormal",
|
||||
[this]() {
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
this->pGlobeAnchor->GetResolvedGeoreference();
|
||||
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
|
||||
FVector(-20.0, -10.0, 1000.0));
|
||||
this->pActor->SetActorRotation(FQuat::Identity);
|
||||
this->pGlobeAnchor->SnapLocalUpToEllipsoidNormal();
|
||||
|
||||
const FTransform& transform = this->pActor->GetActorTransform();
|
||||
FVector actualEcefUp =
|
||||
pGeoreference
|
||||
->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
transform.TransformVector(FVector::ZAxisVector))
|
||||
.GetSafeNormal();
|
||||
|
||||
FVector surfaceNormal = UCesiumWgs84Ellipsoid::GeodeticSurfaceNormal(
|
||||
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition());
|
||||
|
||||
TestEqual("up", actualEcefUp, surfaceNormal);
|
||||
});
|
||||
|
||||
It("gives correct results for different ellipsoids", [this]() {
|
||||
const FVector Position = FVector(-20.0, -10.0, 1000.0);
|
||||
|
||||
// Check with WGS84 ellipsoid (the default)
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(Position);
|
||||
|
||||
FVector wgs84EcefPos =
|
||||
UCesiumWgs84Ellipsoid::LongitudeLatitudeHeightToEarthCenteredEarthFixed(
|
||||
Position);
|
||||
|
||||
TestEqual(
|
||||
"ecef",
|
||||
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition(),
|
||||
wgs84EcefPos);
|
||||
|
||||
// Check with unit ellipsoid
|
||||
TObjectPtr<UCesiumEllipsoid> pUnitEllipsoid = NewObject<UCesiumEllipsoid>();
|
||||
pUnitEllipsoid->SetRadii(FVector::One());
|
||||
|
||||
ACesiumGeoreference* pGeoreference =
|
||||
ACesiumGeoreference::GetDefaultGeoreferenceForActor(this->pActor);
|
||||
pGeoreference->SetEllipsoid(pUnitEllipsoid);
|
||||
|
||||
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(Position);
|
||||
|
||||
FVector unitEcefPos =
|
||||
pUnitEllipsoid
|
||||
->LongitudeLatitudeHeightToEllipsoidCenteredEllipsoidFixed(
|
||||
Position);
|
||||
|
||||
TestEqual(
|
||||
"ecef",
|
||||
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition(),
|
||||
unitEcefPos);
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
|
||||
int32_t AddBufferToModel(
|
||||
CesiumGltf::Model& model,
|
||||
const std::string& type,
|
||||
const int32_t componentType,
|
||||
const std::vector<std::byte>&& values) {
|
||||
CesiumGltf::Buffer& buffer = model.buffers.emplace_back();
|
||||
buffer.byteLength = values.size();
|
||||
buffer.cesium.data = std::move(values);
|
||||
|
||||
CesiumGltf::BufferView& bufferView = model.bufferViews.emplace_back();
|
||||
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
|
||||
bufferView.byteLength = buffer.byteLength;
|
||||
bufferView.byteOffset = 0;
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.bufferView = static_cast<int32_t>(model.bufferViews.size() - 1);
|
||||
accessor.type = type;
|
||||
accessor.componentType = componentType;
|
||||
|
||||
const int64_t elementByteSize =
|
||||
CesiumGltf::Accessor::computeByteSizeOfComponent(componentType) *
|
||||
CesiumGltf::Accessor::computeNumberOfComponents(type);
|
||||
accessor.count = buffer.byteLength / elementByteSize;
|
||||
|
||||
return static_cast<int32_t>(model.accessors.size() - 1);
|
||||
}
|
||||
|
||||
CesiumGltf::FeatureId& AddFeatureIDsAsAttributeToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const std::vector<uint8_t>& featureIDs,
|
||||
const int64_t featureCount,
|
||||
const int64_t setIndex) {
|
||||
std::vector<std::byte> values = GetValuesAsBytes(featureIDs);
|
||||
|
||||
CreateAttributeForPrimitive(
|
||||
model,
|
||||
primitive,
|
||||
"_FEATURE_ID_" + std::to_string(setIndex),
|
||||
CesiumGltf::AccessorSpec::Type::SCALAR,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
std::move(values));
|
||||
|
||||
CesiumGltf::ExtensionExtMeshFeatures* pExtension =
|
||||
primitive.getExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
if (pExtension == nullptr) {
|
||||
pExtension =
|
||||
&primitive.addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
}
|
||||
|
||||
CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back();
|
||||
featureID.featureCount = featureCount;
|
||||
featureID.attribute = setIndex;
|
||||
|
||||
return featureID;
|
||||
}
|
||||
|
||||
CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const std::vector<uint8_t>& featureIDs,
|
||||
const int64_t featureCount,
|
||||
const int32_t imageWidth,
|
||||
const int32_t imageHeight,
|
||||
const std::vector<glm::vec2>& texCoords,
|
||||
const int64_t texcoordSetIndex,
|
||||
const int32_t samplerWrapS,
|
||||
const int32_t samplerWrapT) {
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset.emplace();
|
||||
image.pAsset->bytesPerChannel = 1;
|
||||
image.pAsset->channels = 1;
|
||||
image.pAsset->width = imageWidth;
|
||||
image.pAsset->height = imageHeight;
|
||||
|
||||
std::vector<std::byte>& data = image.pAsset->pixelData;
|
||||
data.resize(imageWidth * imageHeight);
|
||||
std::memcpy(data.data(), featureIDs.data(), data.size());
|
||||
|
||||
CesiumGltf::Sampler& sampler = model.samplers.emplace_back();
|
||||
sampler.wrapS = samplerWrapS;
|
||||
sampler.wrapT = samplerWrapT;
|
||||
|
||||
CesiumGltf::Texture& texture = model.textures.emplace_back();
|
||||
texture.sampler = 0;
|
||||
texture.source = 0;
|
||||
|
||||
std::vector<std::byte> values = GetValuesAsBytes(texCoords);
|
||||
CreateAttributeForPrimitive(
|
||||
model,
|
||||
primitive,
|
||||
"TEXCOORD_" + std::to_string(texcoordSetIndex),
|
||||
CesiumGltf::AccessorSpec::Type::VEC2,
|
||||
CesiumGltf::AccessorSpec::ComponentType::FLOAT,
|
||||
std::move(values));
|
||||
|
||||
CesiumGltf::ExtensionExtMeshFeatures* pExtension =
|
||||
primitive.getExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
if (pExtension == nullptr) {
|
||||
pExtension =
|
||||
&primitive.addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
}
|
||||
|
||||
CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back();
|
||||
featureID.featureCount = featureCount;
|
||||
|
||||
CesiumGltf::FeatureIdTexture featureIDTexture;
|
||||
featureIDTexture.channels = {0};
|
||||
featureIDTexture.index = 0;
|
||||
featureIDTexture.texCoord = texcoordSetIndex;
|
||||
|
||||
featureID.texture = featureIDTexture;
|
||||
|
||||
return featureID;
|
||||
}
|
||||
@ -0,0 +1,227 @@
|
||||
#pragma once
|
||||
|
||||
#include "CesiumGltf/ExtensionExtMeshFeatures.h"
|
||||
#include "CesiumGltf/ExtensionModelExtStructuralMetadata.h"
|
||||
#include "CesiumGltf/Model.h"
|
||||
#include "CesiumGltf/PropertyTypeTraits.h"
|
||||
#include <glm/glm.hpp>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Converts the given vector of values into a std::vector of bytes.
|
||||
*
|
||||
* @returns The values as a std::vector of bytes.
|
||||
*/
|
||||
template <typename T>
|
||||
std::vector<std::byte> GetValuesAsBytes(const std::vector<T>& values) {
|
||||
std::vector<std::byte> bytes(values.size() * sizeof(T));
|
||||
std::memcpy(bytes.data(), values.data(), bytes.size());
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the buffer to the given model, creating a buffer view and
|
||||
* accessor in the process.
|
||||
* @returns The index of the accessor.
|
||||
*/
|
||||
int32_t AddBufferToModel(
|
||||
CesiumGltf::Model& model,
|
||||
const std::string& type,
|
||||
const int32_t componentType,
|
||||
const std::vector<std::byte>&& values);
|
||||
|
||||
/**
|
||||
* @brief Creates an attribute on the given primitive, including a buffer,
|
||||
* buffer view, and accessor for the given values.
|
||||
*/
|
||||
template <typename T>
|
||||
void CreateAttributeForPrimitive(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const std::string& attributeName,
|
||||
const std::string& type,
|
||||
const int32_t componentType,
|
||||
const std::vector<T>& values) {
|
||||
std::vector<std::byte> data = GetValuesAsBytes(values);
|
||||
const int32_t accessor =
|
||||
AddBufferToModel(model, type, componentType, std::move(data));
|
||||
primitive.attributes.insert({attributeName, accessor});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates indices for the given primitive, including a buffer,
|
||||
* buffer view, and accessor for the given values.
|
||||
*/
|
||||
template <typename T>
|
||||
void CreateIndicesForPrimitive(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const int32_t componentType,
|
||||
const std::vector<T>& indices) {
|
||||
std::vector<std::byte> values = GetValuesAsBytes(indices);
|
||||
const int32_t accessor = AddBufferToModel(
|
||||
model,
|
||||
CesiumGltf::AccessorSpec::Type::SCALAR,
|
||||
componentType,
|
||||
std::move(values));
|
||||
primitive.indices = accessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the feature IDs to the given primitive as a feature ID attribute
|
||||
* in EXT_mesh_features. If the primitive doesn't already contain
|
||||
* EXT_mesh_features, this function adds it.
|
||||
*
|
||||
* @returns The newly created feature ID in the primitive extension.
|
||||
*/
|
||||
CesiumGltf::FeatureId& AddFeatureIDsAsAttributeToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const std::vector<uint8_t>& featureIDs,
|
||||
const int64_t featureCount,
|
||||
const int64_t setIndex);
|
||||
|
||||
/**
|
||||
* @brief Adds the feature IDs to the given primitive as a feature ID texture
|
||||
* in EXT_mesh_features. This also adds the given texcoords to the primitive as
|
||||
* a TEXCOORD attribute. If the primitive doesn't already contain
|
||||
* EXT_mesh_features, this function adds it.
|
||||
*
|
||||
* @returns The newly created feature ID in the primitive extension.
|
||||
*/
|
||||
CesiumGltf::FeatureId& AddFeatureIDsAsTextureToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::MeshPrimitive& primitive,
|
||||
const std::vector<uint8_t>& featureIDs,
|
||||
const int64_t featureCount,
|
||||
const int32_t imageWidth,
|
||||
const int32_t imageHeight,
|
||||
const std::vector<glm::vec2>& texCoords,
|
||||
const int64_t texcoordSetIndex,
|
||||
const int32_t samplerWrapS = CesiumGltf::Sampler::WrapS::CLAMP_TO_EDGE,
|
||||
const int32_t samplerWrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE);
|
||||
|
||||
/**
|
||||
* @brief Adds the given values to the given model as a property table property
|
||||
* in EXT_structural_metadata. This also creates a class property definition for
|
||||
* the new property in the schema. If the model doesn't already contain
|
||||
* EXT_structural_metadata, this function adds it.
|
||||
*
|
||||
* This assumes the given values are not arrays or strings.
|
||||
*
|
||||
* @returns The newly created property table property in the model extension.
|
||||
*/
|
||||
template <typename T>
|
||||
CesiumGltf::PropertyTableProperty& AddPropertyTablePropertyToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::PropertyTable& propertyTable,
|
||||
const std::string& propertyName,
|
||||
const std::string& type,
|
||||
const std::optional<std::string>& componentType,
|
||||
const std::vector<T>& values) {
|
||||
CesiumGltf::ExtensionModelExtStructuralMetadata* pExtension =
|
||||
model.getExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
||||
if (pExtension == nullptr) {
|
||||
pExtension =
|
||||
&model.addExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
||||
}
|
||||
|
||||
if (!pExtension->schema) {
|
||||
pExtension->schema.emplace();
|
||||
}
|
||||
CesiumGltf::Schema& schema = *pExtension->schema;
|
||||
|
||||
const std::string& className = propertyTable.classProperty;
|
||||
CesiumGltf::Class& theClass = schema.classes[className];
|
||||
|
||||
CesiumGltf::ClassProperty& classProperty = theClass.properties[propertyName];
|
||||
classProperty.type = type;
|
||||
classProperty.componentType = componentType;
|
||||
|
||||
CesiumGltf::Buffer& buffer = model.buffers.emplace_back();
|
||||
buffer.cesium.data.resize(values.size() * sizeof(T));
|
||||
std::memcpy(
|
||||
buffer.cesium.data.data(),
|
||||
values.data(),
|
||||
buffer.cesium.data.size());
|
||||
buffer.byteLength = buffer.cesium.data.size();
|
||||
|
||||
CesiumGltf::BufferView& bufferView = model.bufferViews.emplace_back();
|
||||
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
|
||||
bufferView.byteLength = buffer.byteLength;
|
||||
bufferView.byteOffset = 0;
|
||||
|
||||
CesiumGltf::PropertyTableProperty& property =
|
||||
propertyTable.properties[propertyName];
|
||||
property.values = static_cast<int32_t>(model.bufferViews.size() - 1);
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the given values to the given model as a property texture
|
||||
* property in EXT_structural_metadata. This also creates a class property
|
||||
* definition for the new property in the schema. If the model doesn't already
|
||||
* contain EXT_structural_metadata, this function adds it.
|
||||
*
|
||||
* This assumes the given values are not arrays or strings. The values will be
|
||||
* stored in a 2x2 image with the correct number of channels.
|
||||
*
|
||||
* @returns The newly created property texture property in the model extension.
|
||||
*/
|
||||
template <typename T>
|
||||
CesiumGltf::PropertyTextureProperty& AddPropertyTexturePropertyToModel(
|
||||
CesiumGltf::Model& model,
|
||||
CesiumGltf::PropertyTexture& propertyTexture,
|
||||
const std::string& propertyName,
|
||||
const std::string& type,
|
||||
const std::optional<std::string>& componentType,
|
||||
const std::array<T, 4>& values,
|
||||
const std::vector<int64_t>& channels) {
|
||||
CesiumGltf::ExtensionModelExtStructuralMetadata* pExtension =
|
||||
model.getExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
||||
if (pExtension == nullptr) {
|
||||
pExtension =
|
||||
&model.addExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
||||
}
|
||||
|
||||
if (!pExtension->schema) {
|
||||
pExtension->schema.emplace();
|
||||
}
|
||||
CesiumGltf::Schema& schema = *pExtension->schema;
|
||||
|
||||
const std::string& className = propertyTexture.classProperty;
|
||||
CesiumGltf::Class& theClass = schema.classes[className];
|
||||
|
||||
CesiumGltf::ClassProperty& classProperty = theClass.properties[propertyName];
|
||||
classProperty.type = type;
|
||||
classProperty.componentType = componentType;
|
||||
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset.emplace();
|
||||
image.pAsset->width = 2;
|
||||
image.pAsset->height = 2;
|
||||
image.pAsset->channels = sizeof(T);
|
||||
image.pAsset->bytesPerChannel = 1;
|
||||
image.pAsset->pixelData.resize(values.size() * sizeof(T));
|
||||
std::memcpy(
|
||||
image.pAsset->pixelData.data(),
|
||||
values.data(),
|
||||
image.pAsset->pixelData.size());
|
||||
|
||||
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& texture = model.textures.emplace_back();
|
||||
texture.sampler = static_cast<int32_t>(model.samplers.size() - 1);
|
||||
texture.source = static_cast<int32_t>(model.images.size() - 1);
|
||||
|
||||
CesiumGltf::PropertyTextureProperty& property =
|
||||
propertyTexture.properties[propertyName];
|
||||
property.channels = channels;
|
||||
property.index = static_cast<int32_t>(model.textures.size() - 1);
|
||||
return property;
|
||||
}
|
||||
@ -0,0 +1,285 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumLoadTestCore.h"
|
||||
|
||||
#include "CesiumAsync/ICacheDatabase.h"
|
||||
#include "CesiumRuntime.h"
|
||||
|
||||
#include "Editor.h"
|
||||
#include "Settings/LevelEditorPlaySettings.h"
|
||||
#include "Tests/AutomationCommon.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "UnrealClient.h"
|
||||
|
||||
namespace Cesium {
|
||||
|
||||
struct LoadTestContext {
|
||||
FString testName;
|
||||
std::vector<TestPass> testPasses;
|
||||
|
||||
SceneGenerationContext creationContext;
|
||||
SceneGenerationContext playContext;
|
||||
|
||||
float cameraFieldOfView = 90.0f;
|
||||
|
||||
ReportCallback reportStep;
|
||||
|
||||
void reset() {
|
||||
testName.Reset();
|
||||
testPasses.clear();
|
||||
creationContext = playContext = SceneGenerationContext();
|
||||
reportStep = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
LoadTestContext gLoadTestContext;
|
||||
|
||||
DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(
|
||||
TimeLoadingCommand,
|
||||
FString,
|
||||
loggingName,
|
||||
SceneGenerationContext&,
|
||||
creationContext,
|
||||
SceneGenerationContext&,
|
||||
playContext,
|
||||
TestPass&,
|
||||
pass);
|
||||
bool TimeLoadingCommand::Update() {
|
||||
|
||||
if (!pass.testInProgress) {
|
||||
|
||||
// Set up the world for this pass
|
||||
playContext.syncWorldCamera();
|
||||
if (pass.setupStep)
|
||||
pass.setupStep(playContext, pass.optionalParameter);
|
||||
|
||||
// Start test mark, turn updates back on
|
||||
pass.startMark = FPlatformTime::Seconds();
|
||||
UE_LOG(LogCesium, Display, TEXT("-- Load start mark -- %s"), *loggingName);
|
||||
|
||||
playContext.setSuspendUpdate(false);
|
||||
|
||||
pass.testInProgress = true;
|
||||
|
||||
// Return, let world tick
|
||||
return false;
|
||||
}
|
||||
|
||||
double timeMark = FPlatformTime::Seconds();
|
||||
|
||||
pass.elapsedTime = timeMark - pass.startMark;
|
||||
|
||||
// The command is over if tilesets are loaded, or timed out
|
||||
// Wait for a maximum of 30 seconds
|
||||
const size_t testTimeout = 30;
|
||||
bool tilesetsloaded = playContext.areTilesetsDoneLoading();
|
||||
bool timedOut = pass.elapsedTime >= testTimeout;
|
||||
|
||||
if (timedOut) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Error,
|
||||
TEXT("TIMED OUT: Loading stopped after %.2f seconds"),
|
||||
pass.elapsedTime);
|
||||
// Command is done
|
||||
pass.testInProgress = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tilesetsloaded) {
|
||||
// Run verify step as part of timing
|
||||
// This is useful for running additional logic after a load, or if the step
|
||||
// exists in the pass solely, timing very specific functionality (like
|
||||
// terrain queries)
|
||||
bool verifyComplete = true;
|
||||
if (pass.verifyStep)
|
||||
verifyComplete =
|
||||
pass.verifyStep(creationContext, playContext, pass.optionalParameter);
|
||||
|
||||
if (verifyComplete) {
|
||||
pass.endMark = FPlatformTime::Seconds();
|
||||
UE_LOG(LogCesium, Display, TEXT("-- Load end mark -- %s"), *loggingName);
|
||||
|
||||
pass.elapsedTime = pass.endMark - pass.startMark;
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Display,
|
||||
TEXT("Pass completed in %.2f seconds"),
|
||||
pass.elapsedTime);
|
||||
|
||||
pass.testInProgress = false;
|
||||
|
||||
// Command is done
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Let world tick, we'll come back to this command
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(
|
||||
LoadTestScreenshotCommand,
|
||||
FString,
|
||||
screenshotName);
|
||||
bool LoadTestScreenshotCommand::Update() {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Display,
|
||||
TEXT("Requesting screenshot to /Saved/Screenshots/WindowsEditor..."));
|
||||
|
||||
// Add a dash to separate name from unique index of screen shot
|
||||
// Also add a dot to keep the base path logic from stripping away too much
|
||||
FString requestFilename = screenshotName + "-" + ".";
|
||||
FScreenshotRequest::RequestScreenshot(requestFilename, false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void defaultReportStep(const std::vector<TestPass>& testPasses) {
|
||||
FString reportStr;
|
||||
reportStr += "\n\nTest Results\n";
|
||||
reportStr += "-----------------------------\n";
|
||||
reportStr += "(measured time) - (pass name)\n";
|
||||
std::vector<TestPass>::const_iterator it;
|
||||
for (it = testPasses.begin(); it != testPasses.end(); ++it) {
|
||||
const TestPass& pass = *it;
|
||||
reportStr +=
|
||||
FString::Printf(TEXT("%.2f secs - %s\n"), pass.elapsedTime, *pass.name);
|
||||
}
|
||||
reportStr += "-----------------------------\n";
|
||||
|
||||
UE_LOG(LogCesium, Display, TEXT("%s"), *reportStr);
|
||||
}
|
||||
|
||||
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(
|
||||
TestCleanupCommand,
|
||||
LoadTestContext&,
|
||||
context);
|
||||
bool TestCleanupCommand::Update() {
|
||||
// Tag the fastest pass
|
||||
if (context.testPasses.size() > 0) {
|
||||
size_t fastestPass = 0;
|
||||
double fastestTime = -1.0;
|
||||
for (size_t index = 0; index < context.testPasses.size(); ++index) {
|
||||
const TestPass& pass = context.testPasses[index];
|
||||
if (fastestTime == -1.0 || pass.elapsedTime < fastestTime) {
|
||||
fastestPass = index;
|
||||
fastestTime = pass.elapsedTime;
|
||||
}
|
||||
}
|
||||
context.testPasses[fastestPass].isFastest = true;
|
||||
}
|
||||
|
||||
if (context.reportStep)
|
||||
context.reportStep(context.testPasses);
|
||||
else
|
||||
defaultReportStep(context.testPasses);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(
|
||||
InitForPlayWhenReady,
|
||||
SceneGenerationContext&,
|
||||
creationContext,
|
||||
SceneGenerationContext&,
|
||||
playContext);
|
||||
bool InitForPlayWhenReady::Update() {
|
||||
if (!GEditor || !GEditor->IsPlayingSessionInEditor())
|
||||
return false;
|
||||
UE_LOG(LogCesium, Display, TEXT("Play in Editor ready..."));
|
||||
playContext.initForPlay(creationContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunLoadTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> locationSetup,
|
||||
const std::vector<TestPass>& testPasses,
|
||||
int viewportWidth,
|
||||
int viewportHeight,
|
||||
ReportCallback optionalReportStep) {
|
||||
|
||||
LoadTestContext& context = gLoadTestContext;
|
||||
|
||||
context.reset();
|
||||
|
||||
context.testName = testName;
|
||||
context.testPasses = testPasses;
|
||||
context.reportStep = optionalReportStep;
|
||||
|
||||
//
|
||||
// Programmatically set up the world
|
||||
//
|
||||
UE_LOG(LogCesium, Display, TEXT("Creating common world objects..."));
|
||||
createCommonWorldObjects(context.creationContext);
|
||||
|
||||
// Configure location specific objects
|
||||
UE_LOG(LogCesium, Display, TEXT("Setting up location..."));
|
||||
locationSetup(context.creationContext);
|
||||
context.creationContext.trackForPlay();
|
||||
|
||||
// Halt tileset updates and reset them
|
||||
context.creationContext.setSuspendUpdate(true);
|
||||
context.creationContext.refreshTilesets();
|
||||
|
||||
// Let the editor viewports see the same thing the test will
|
||||
context.creationContext.syncWorldCamera();
|
||||
|
||||
//
|
||||
// Start async commands
|
||||
//
|
||||
|
||||
// Wait for shaders. Shader compiles could affect performance
|
||||
ADD_LATENT_AUTOMATION_COMMAND(FWaitForShadersToFinishCompiling);
|
||||
|
||||
// Queue play in editor and set desired viewport size
|
||||
FRequestPlaySessionParams Params;
|
||||
Params.WorldType = EPlaySessionWorldType::PlayInEditor;
|
||||
Params.EditorPlaySettings = NewObject<ULevelEditorPlaySettings>();
|
||||
Params.EditorPlaySettings->NewWindowWidth = viewportWidth;
|
||||
Params.EditorPlaySettings->NewWindowHeight = viewportHeight;
|
||||
Params.EditorPlaySettings->EnableGameSound = false;
|
||||
GEditor->RequestPlaySession(Params);
|
||||
|
||||
// Wait until PIE is ready
|
||||
ADD_LATENT_AUTOMATION_COMMAND(
|
||||
InitForPlayWhenReady(context.creationContext, context.playContext));
|
||||
|
||||
// Wait to show distinct gap in profiler
|
||||
ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f));
|
||||
|
||||
std::vector<TestPass>::iterator it;
|
||||
for (it = context.testPasses.begin(); it != context.testPasses.end(); ++it) {
|
||||
TestPass& pass = *it;
|
||||
|
||||
// Do our timing capture
|
||||
FString loggingName = testName + "-" + pass.name;
|
||||
|
||||
ADD_LATENT_AUTOMATION_COMMAND(TimeLoadingCommand(
|
||||
loggingName,
|
||||
context.creationContext,
|
||||
context.playContext,
|
||||
pass));
|
||||
|
||||
ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f));
|
||||
|
||||
FString screenshotName = testName + "-" + pass.name;
|
||||
ADD_LATENT_AUTOMATION_COMMAND(LoadTestScreenshotCommand(screenshotName))
|
||||
|
||||
ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f));
|
||||
}
|
||||
|
||||
// End play in editor
|
||||
ADD_LATENT_AUTOMATION_COMMAND(FEndPlayMapCommand());
|
||||
|
||||
ADD_LATENT_AUTOMATION_COMMAND(TestCleanupCommand(context));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace Cesium
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,47 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include <functional>
|
||||
#include <swl/variant.hpp>
|
||||
|
||||
#include "CesiumSceneGeneration.h"
|
||||
|
||||
namespace Cesium {
|
||||
|
||||
struct TestPass {
|
||||
typedef swl::variant<int, float> TestingParameter;
|
||||
typedef std::function<void(SceneGenerationContext&, TestingParameter)>
|
||||
SetupCallback;
|
||||
typedef std::function<
|
||||
bool(SceneGenerationContext&, SceneGenerationContext&, TestingParameter)>
|
||||
VerifyCallback;
|
||||
|
||||
FString name;
|
||||
SetupCallback setupStep;
|
||||
VerifyCallback verifyStep;
|
||||
TestingParameter optionalParameter;
|
||||
|
||||
bool testInProgress = false;
|
||||
double startMark = 0;
|
||||
double endMark = 0;
|
||||
double elapsedTime = 0;
|
||||
|
||||
bool isFastest = false;
|
||||
};
|
||||
|
||||
typedef std::function<void(const std::vector<TestPass>&)> ReportCallback;
|
||||
|
||||
bool RunLoadTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> locationSetup,
|
||||
const std::vector<TestPass>& testPasses,
|
||||
int viewportWidth,
|
||||
int viewportHeight,
|
||||
ReportCallback optionalReportStep = nullptr);
|
||||
|
||||
}; // namespace Cesium
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,169 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumOriginShiftComponent.h"
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumGlobeAnchorComponent.h"
|
||||
#include "CesiumSubLevelComponent.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Editor.h"
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
#include "Engine/World.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "LevelInstance/LevelInstanceActor.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumOriginShiftComponentSpec,
|
||||
"Cesium.Unit.OriginShiftComponent",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
TObjectPtr<UWorld> pWorld;
|
||||
TObjectPtr<ACesiumGeoreference> pGeoreference;
|
||||
TObjectPtr<AStaticMeshActor> pOriginShiftActor;
|
||||
TObjectPtr<UCesiumOriginShiftComponent> pOriginShiftComponent;
|
||||
FDelegateHandle subscriptionPostPIEStarted;
|
||||
|
||||
END_DEFINE_SPEC(FCesiumOriginShiftComponentSpec)
|
||||
|
||||
using namespace CesiumTestHelpers;
|
||||
|
||||
void FCesiumOriginShiftComponentSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
if (IsValid(pWorld)) {
|
||||
// Only run the below once in order to save time loading/unloading
|
||||
// levels for every little test.
|
||||
return;
|
||||
}
|
||||
|
||||
pWorld = FAutomationEditorCommonUtils::CreateNewMap();
|
||||
|
||||
pOriginShiftActor = pWorld->SpawnActor<AStaticMeshActor>();
|
||||
pOriginShiftActor->SetMobility(EComponentMobility::Movable);
|
||||
trackForPlay(pOriginShiftActor);
|
||||
|
||||
pOriginShiftComponent = Cast<UCesiumOriginShiftComponent>(
|
||||
pOriginShiftActor->AddComponentByClass(
|
||||
UCesiumOriginShiftComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
trackForPlay(pOriginShiftComponent);
|
||||
|
||||
pGeoreference = nullptr;
|
||||
for (TActorIterator<ACesiumGeoreference> it(pWorld); it; ++it) {
|
||||
pGeoreference = *it;
|
||||
}
|
||||
|
||||
trackForPlay(pGeoreference);
|
||||
});
|
||||
|
||||
AfterEach([this]() {});
|
||||
|
||||
It("automatically adds a globe anchor to go with the origin shift", [this]() {
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pOriginShiftActor->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
});
|
||||
|
||||
Describe(
|
||||
"does not shift origin when in between sub-levels when mode is SwitchSubLevelsOnly",
|
||||
[this]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
|
||||
findInPlay(pOriginShiftActor)
|
||||
->SetActorLocation(FVector(10000.0, 20000.0, 300.0));
|
||||
});
|
||||
It("", [this]() {
|
||||
TestEqual(
|
||||
"location",
|
||||
findInPlay(pOriginShiftActor)->GetActorLocation(),
|
||||
FVector(10000.0, 20000.0, 300.0));
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
|
||||
Describe(
|
||||
"shifts origin by changing georeference when mode is ChangeCesiumGeoreference",
|
||||
[this]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
|
||||
// Start with the Actor at the origin at LLH 0,0,0.
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
findInPlay(pOriginShiftActor)
|
||||
->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
pGlobeAnchor->MoveToLongitudeLatitudeHeight(FVector(0.0, 0.0, 0.0));
|
||||
findInPlay(pGeoreference)
|
||||
->SetOriginLongitudeLatitudeHeight(FVector(0.0, 0.0, 0.0));
|
||||
pGlobeAnchor->SnapToEastSouthUp();
|
||||
|
||||
// Activate georeference origin shifting
|
||||
findInPlay(pOriginShiftComponent)
|
||||
->SetMode(ECesiumOriginShiftMode::ChangeCesiumGeoreference);
|
||||
|
||||
// Move it to 90 degrees longitude.
|
||||
FVector location = FVector(
|
||||
CesiumGeospatial::Ellipsoid::WGS84.getMaximumRadius() * 100.0,
|
||||
0.0,
|
||||
-CesiumGeospatial::Ellipsoid::WGS84.getMaximumRadius() * 100.0);
|
||||
findInPlay(pOriginShiftActor)->SetActorLocation(location);
|
||||
TestEqual("Longitude", pGlobeAnchor->GetLongitude(), 90.0);
|
||||
TestEqual("Latitude", pGlobeAnchor->GetLatitude(), 0.0);
|
||||
TestEqual("Height", pGlobeAnchor->GetHeight(), 0.0);
|
||||
TestTrue(
|
||||
"Rotation",
|
||||
pGlobeAnchor->GetEastSouthUpRotation().Equals(FQuat::Identity));
|
||||
});
|
||||
It("", [this]() {
|
||||
TestEqual(
|
||||
"location",
|
||||
findInPlay(pOriginShiftActor)->GetActorLocation(),
|
||||
FVector::Zero());
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
findInPlay(pOriginShiftActor)
|
||||
->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestEqual("Longitude", pGlobeAnchor->GetLongitude(), 90.0);
|
||||
TestEqual("Latitude", pGlobeAnchor->GetLatitude(), 0.0);
|
||||
TestEqual("Height", pGlobeAnchor->GetHeight(), 0.0);
|
||||
|
||||
// The Actor should still be aligned with the new East-South-Up
|
||||
// because moving it will rotate it for globe curvature.
|
||||
TestTrue(
|
||||
"Rotation",
|
||||
pGlobeAnchor->GetEastSouthUpRotation().Equals(FQuat::Identity));
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
@ -0,0 +1,752 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumPrimitiveFeatures.h"
|
||||
#include "CesiumGltf/ExtensionExtMeshFeatures.h"
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumPrimitiveFeaturesSpec,
|
||||
"Cesium.Unit.PrimitiveFeatures",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
CesiumGltf::Model model;
|
||||
CesiumGltf::MeshPrimitive* pPrimitive;
|
||||
CesiumGltf::ExtensionExtMeshFeatures* pExtension;
|
||||
END_DEFINE_SPEC(FCesiumPrimitiveFeaturesSpec)
|
||||
|
||||
void FCesiumPrimitiveFeaturesSpec::Define() {
|
||||
Describe("Constructor", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
pExtension =
|
||||
&pPrimitive->addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
});
|
||||
|
||||
It("constructs with no feature ID sets", [this]() {
|
||||
// This is technically disallowed by the spec, but just make sure it's
|
||||
// handled reasonably.
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TArray<FCesiumFeatureIdSet> featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 0);
|
||||
});
|
||||
|
||||
It("constructs with single feature ID set", [this]() {
|
||||
CesiumGltf::FeatureId& featureID = pExtension->featureIds.emplace_back();
|
||||
featureID.featureCount = 10;
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 1);
|
||||
|
||||
const FCesiumFeatureIdSet& featureIDSet = featureIDSets[0];
|
||||
TestEqual(
|
||||
"Feature Count",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet),
|
||||
static_cast<int64>(featureID.featureCount));
|
||||
TestEqual(
|
||||
"FeatureIDType",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(
|
||||
featureIDSet),
|
||||
ECesiumFeatureIdSetType::Implicit);
|
||||
});
|
||||
|
||||
It("constructs with multiple feature ID sets", [this]() {
|
||||
const std::vector<uint8_t> attributeIDs{0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 1, 0);
|
||||
|
||||
const std::vector<uint8_t> textureIDs{1, 2, 3};
|
||||
const std::vector<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.34, 0),
|
||||
glm::vec2(0.67, 0)};
|
||||
AddFeatureIDsAsTextureToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
textureIDs,
|
||||
3,
|
||||
3,
|
||||
1,
|
||||
texCoords,
|
||||
0);
|
||||
|
||||
CesiumGltf::FeatureId& implicitIDs =
|
||||
pExtension->featureIds.emplace_back();
|
||||
implicitIDs.featureCount = 3;
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 3);
|
||||
|
||||
const std::vector<ECesiumFeatureIdSetType> expectedTypes{
|
||||
ECesiumFeatureIdSetType::Attribute,
|
||||
ECesiumFeatureIdSetType::Texture,
|
||||
ECesiumFeatureIdSetType::Implicit};
|
||||
|
||||
for (size_t i = 0; i < featureIDSets.Num(); i++) {
|
||||
const FCesiumFeatureIdSet& featureIDSet =
|
||||
featureIDSets[static_cast<int32>(i)];
|
||||
const CesiumGltf::FeatureId& gltfFeatureID = pExtension->featureIds[i];
|
||||
TestEqual(
|
||||
"Feature Count",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet),
|
||||
static_cast<int64>(gltfFeatureID.featureCount));
|
||||
TestEqual(
|
||||
"FeatureIDType",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(
|
||||
featureIDSet),
|
||||
expectedTypes[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetFeatureIDSetsOfType", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
pExtension =
|
||||
&pPrimitive->addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
|
||||
const std::vector<uint8_t> attributeIDs{0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 1, 0);
|
||||
|
||||
const std::vector<uint8_t> textureIDs{1, 2, 3};
|
||||
const std::vector<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.34, 0),
|
||||
glm::vec2(0.67, 0)};
|
||||
AddFeatureIDsAsTextureToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
textureIDs,
|
||||
3,
|
||||
3,
|
||||
1,
|
||||
texCoords,
|
||||
0);
|
||||
|
||||
CesiumGltf::FeatureId& implicitIDs =
|
||||
pExtension->featureIds.emplace_back();
|
||||
implicitIDs.featureCount = 3;
|
||||
});
|
||||
|
||||
It("gets feature ID attribute", [this]() {
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet> featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSetsOfType(
|
||||
primitiveFeatures,
|
||||
ECesiumFeatureIdSetType::Attribute);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 1);
|
||||
|
||||
const FCesiumFeatureIdSet& featureIDSet = featureIDSets[0];
|
||||
TestEqual(
|
||||
"FeatureIDType",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(
|
||||
featureIDSet),
|
||||
ECesiumFeatureIdSetType::Attribute);
|
||||
|
||||
const FCesiumFeatureIdAttribute& attribute =
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute(
|
||||
featureIDSet);
|
||||
TestEqual(
|
||||
"AttributeStatus",
|
||||
UCesiumFeatureIdAttributeBlueprintLibrary::
|
||||
GetFeatureIDAttributeStatus(attribute),
|
||||
ECesiumFeatureIdAttributeStatus::Valid);
|
||||
});
|
||||
|
||||
It("gets feature ID texture", [this]() {
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet> featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSetsOfType(
|
||||
primitiveFeatures,
|
||||
ECesiumFeatureIdSetType::Texture);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 1);
|
||||
|
||||
const FCesiumFeatureIdSet& featureIDSet = featureIDSets[0];
|
||||
TestEqual(
|
||||
"FeatureIDType",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(
|
||||
featureIDSet),
|
||||
ECesiumFeatureIdSetType::Texture);
|
||||
|
||||
const FCesiumFeatureIdTexture& texture =
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture(
|
||||
featureIDSet);
|
||||
TestEqual(
|
||||
"TextureStatus",
|
||||
UCesiumFeatureIdTextureBlueprintLibrary::GetFeatureIDTextureStatus(
|
||||
texture),
|
||||
ECesiumFeatureIdTextureStatus::Valid);
|
||||
});
|
||||
|
||||
It("gets implicit feature ID", [this]() {
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet> featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSetsOfType(
|
||||
primitiveFeatures,
|
||||
ECesiumFeatureIdSetType::Implicit);
|
||||
TestEqual("Number of FeatureIDSets", featureIDSets.Num(), 1);
|
||||
|
||||
const FCesiumFeatureIdSet& featureIDSet = featureIDSets[0];
|
||||
TestEqual(
|
||||
"FeatureIDType",
|
||||
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(
|
||||
featureIDSet),
|
||||
ECesiumFeatureIdSetType::Implicit);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetFirstVertexFromFace", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
pExtension =
|
||||
&pPrimitive->addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
});
|
||||
|
||||
It("returns -1 for out-of-bounds face index", [this]() {
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
TestEqual(
|
||||
"VertexIndexForNegativeFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFirstVertexFromFace(
|
||||
primitiveFeatures,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"VertexIndexForOutOfBoundsFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFirstVertexFromFace(
|
||||
primitiveFeatures,
|
||||
2),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns correct value for primitive without indices", [this]() {
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 9;
|
||||
const int64 numFaces = accessor.count / 3;
|
||||
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
for (int64 i = 0; i < numFaces; i++) {
|
||||
TestEqual(
|
||||
"VertexIndexForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFirstVertexFromFace(
|
||||
primitiveFeatures,
|
||||
i),
|
||||
i * 3);
|
||||
}
|
||||
});
|
||||
|
||||
It("returns correct value for primitive with indices", [this]() {
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3, 4, 5, 6};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 7;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const size_t numFaces = indices.size() / 3;
|
||||
for (size_t i = 0; i < numFaces; i++) {
|
||||
TestEqual(
|
||||
"VertexIndexForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFirstVertexFromFace(
|
||||
primitiveFeatures,
|
||||
static_cast<int64>(i)),
|
||||
indices[i * 3]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetFeatureIDFromFace", [this]() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
CesiumGltf::Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
pExtension =
|
||||
&pPrimitive->addExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
||||
});
|
||||
|
||||
It("returns -1 for primitive with empty feature ID sets", [this]() {
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 6;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
// Adds empty feature ID.
|
||||
pExtension->featureIds.emplace_back();
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForPrimitiveWithNoSets",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns -1 for out of bounds feature ID set index", [this]() {
|
||||
std::vector<uint8_t> attributeIDs{1, 1, 1, 1, 0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 2, 0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3, 4, 5, 6};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 7;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsSetIndex",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsSetIndex",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0,
|
||||
2),
|
||||
-1);
|
||||
});
|
||||
|
||||
Describe("FeatureIDAttribute", [this]() {
|
||||
It("returns -1 for out-of-bounds face index", [this]() {
|
||||
std::vector<uint8_t> attributeIDs{1, 1, 1};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 1, 0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 3;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForNegativeFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
2),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns correct values for primitive without indices", [this]() {
|
||||
std::vector<uint8_t> attributeIDs{1, 1, 1, 2, 2, 2, 0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 3, 0);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 9;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const size_t numFaces = static_cast<size_t>(accessor.count / 3);
|
||||
for (size_t i = 0; i < numFaces; i++) {
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
static_cast<int64>(i)),
|
||||
attributeIDs[i * 3]);
|
||||
}
|
||||
});
|
||||
|
||||
It("returns correct values for primitive with indices", [this]() {
|
||||
std::vector<uint8_t> attributeIDs{1, 1, 1, 1, 0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(model, *pPrimitive, attributeIDs, 2, 0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3, 4, 5, 6};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 7;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const size_t numFaces = indices.size() / 3;
|
||||
for (size_t i = 0; i < numFaces; i++) {
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
static_cast<int64>(i)),
|
||||
attributeIDs[i * 3]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FeatureIDTexture", [this]() {
|
||||
It("returns -1 for out-of-bounds face index", [this]() {
|
||||
const std::vector<uint8_t> textureIDs{0};
|
||||
const std::vector<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0, 0)};
|
||||
AddFeatureIDsAsTextureToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
textureIDs,
|
||||
4,
|
||||
4,
|
||||
1,
|
||||
texCoords,
|
||||
0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 3;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForNegativeFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
2),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns correct values for primitive without indices", [this]() {
|
||||
const std::vector<uint8_t> textureIDs{0, 1, 2, 3};
|
||||
const std::vector<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.75, 0),
|
||||
glm::vec2(0.75, 0),
|
||||
glm::vec2(0.75, 0)};
|
||||
AddFeatureIDsAsTextureToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
textureIDs,
|
||||
4,
|
||||
4,
|
||||
1,
|
||||
texCoords,
|
||||
0);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 6;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0),
|
||||
0);
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
1),
|
||||
3);
|
||||
});
|
||||
|
||||
It("returns correct values for primitive with indices", [this]() {
|
||||
const std::vector<uint8_t> textureIDs{0, 1, 2, 3};
|
||||
const std::vector<glm::vec2> texCoords{
|
||||
glm::vec2(0, 0),
|
||||
glm::vec2(0.25, 0),
|
||||
glm::vec2(0.5, 0),
|
||||
glm::vec2(0.75, 0)};
|
||||
AddFeatureIDsAsTextureToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
textureIDs,
|
||||
4,
|
||||
4,
|
||||
1,
|
||||
texCoords,
|
||||
0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 2, 0, 3};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 4;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0),
|
||||
0);
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
1),
|
||||
2);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("ImplicitFeatureIDs", [this]() {
|
||||
BeforeEach([this]() {
|
||||
CesiumGltf::FeatureId& implicitIDs =
|
||||
pExtension->featureIds.emplace_back();
|
||||
implicitIDs.featureCount = 6;
|
||||
});
|
||||
|
||||
It("returns -1 for out-of-bounds face index", [this]() {
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 6;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForNegativeFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
-1),
|
||||
-1);
|
||||
TestEqual(
|
||||
"FeatureIDForOutOfBoundsFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
10),
|
||||
-1);
|
||||
});
|
||||
|
||||
It("returns correct values for primitive without indices", [this]() {
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 6;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0),
|
||||
0);
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
1),
|
||||
3);
|
||||
});
|
||||
|
||||
It("returns correct values for primitive with indices", [this]() {
|
||||
const std::vector<uint8_t> indices{2, 1, 0, 3, 4, 5};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 4;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
0),
|
||||
2);
|
||||
TestEqual(
|
||||
"FeatureIDForFace",
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
1),
|
||||
3);
|
||||
});
|
||||
});
|
||||
|
||||
It("gets feature ID from correct set with specified feature ID set index",
|
||||
[this]() {
|
||||
// First feature ID set is attribute
|
||||
std::vector<uint8_t> attributeIDs{1, 1, 1, 1, 0, 0, 0};
|
||||
AddFeatureIDsAsAttributeToModel(
|
||||
model,
|
||||
*pPrimitive,
|
||||
attributeIDs,
|
||||
2,
|
||||
0);
|
||||
|
||||
const std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3, 4, 5, 6};
|
||||
CreateIndicesForPrimitive(
|
||||
model,
|
||||
*pPrimitive,
|
||||
CesiumGltf::AccessorSpec::ComponentType::UNSIGNED_BYTE,
|
||||
indices);
|
||||
|
||||
CesiumGltf::Accessor& accessor = model.accessors.emplace_back();
|
||||
accessor.count = 7;
|
||||
pPrimitive->attributes.insert(
|
||||
{"POSITION", static_cast<int32_t>(model.accessors.size() - 1)});
|
||||
|
||||
// Second feature ID set is implicit
|
||||
CesiumGltf::FeatureId& implicitIDs =
|
||||
pExtension->featureIds.emplace_back();
|
||||
implicitIDs.featureCount = 7;
|
||||
|
||||
FCesiumPrimitiveFeatures primitiveFeatures =
|
||||
FCesiumPrimitiveFeatures(model, *pPrimitive, *pExtension);
|
||||
|
||||
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
||||
primitiveFeatures);
|
||||
TestEqual("FeatureIDSetCount", featureIDSets.Num(), 2);
|
||||
|
||||
int64 setIndex = 0;
|
||||
for (size_t index = 0; index < indices.size(); index += 3) {
|
||||
std::string label("FeatureIDAttribute" + std::to_string(index));
|
||||
int64 faceIndex = static_cast<int64>(index) / 3;
|
||||
int64 featureID = static_cast<int64>(attributeIDs[indices[index]]);
|
||||
TestEqual(
|
||||
FString(label.c_str()),
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
faceIndex,
|
||||
setIndex),
|
||||
featureID);
|
||||
}
|
||||
|
||||
setIndex = 1;
|
||||
for (size_t index = 0; index < indices.size(); index += 3) {
|
||||
std::string label("ImplicitFeatureID" + std::to_string(index));
|
||||
int64 faceIndex = static_cast<int64>(index) / 3;
|
||||
int64 featureID = static_cast<int64>(indices[index]);
|
||||
TestEqual(
|
||||
FString(label.c_str()),
|
||||
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromFace(
|
||||
primitiveFeatures,
|
||||
faceIndex,
|
||||
setIndex),
|
||||
featureID);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumMetadataValue.h"
|
||||
#include "CesiumPropertyArrayBlueprintLibrary.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumPropertyArraySpec,
|
||||
"Cesium.Unit.PropertyArray",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
END_DEFINE_SPEC(FCesiumPropertyArraySpec)
|
||||
|
||||
void FCesiumPropertyArraySpec::Define() {
|
||||
Describe("Constructor", [this]() {
|
||||
It("constructs empty array by default", [this]() {
|
||||
FCesiumPropertyArray array;
|
||||
TestEqual(
|
||||
"size",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetSize(array),
|
||||
0);
|
||||
|
||||
FCesiumMetadataValueType valueType =
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementValueType(array);
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Invalid);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::None);
|
||||
|
||||
TestEqual(
|
||||
"blueprint type",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementBlueprintType(array),
|
||||
ECesiumMetadataBlueprintType::None);
|
||||
});
|
||||
|
||||
It("constructs empty array from empty view", [this]() {
|
||||
CesiumGltf::PropertyArrayCopy<uint8_t> arrayView;
|
||||
FCesiumPropertyArray array(arrayView);
|
||||
TestEqual(
|
||||
"size",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetSize(array),
|
||||
0);
|
||||
|
||||
FCesiumMetadataValueType valueType =
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementValueType(array);
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Scalar);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::Uint8);
|
||||
|
||||
TestEqual(
|
||||
"blueprint type",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementBlueprintType(array),
|
||||
ECesiumMetadataBlueprintType::Byte);
|
||||
});
|
||||
|
||||
It("constructs non-empty array", [this]() {
|
||||
std::vector<uint8_t> values{1, 2, 3, 4};
|
||||
CesiumGltf::PropertyArrayCopy<uint8_t> arrayView = std::vector(values);
|
||||
FCesiumPropertyArray array(arrayView);
|
||||
TestEqual(
|
||||
"size",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetSize(array),
|
||||
static_cast<int64>(values.size()));
|
||||
|
||||
FCesiumMetadataValueType valueType =
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementValueType(array);
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Scalar);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::Uint8);
|
||||
|
||||
TestEqual(
|
||||
"blueprint type",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetElementBlueprintType(array),
|
||||
ECesiumMetadataBlueprintType::Byte);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetValue", [this]() {
|
||||
It("gets bogus value for out-of-bounds index", [this]() {
|
||||
std::vector<uint8_t> values{1};
|
||||
CesiumGltf::PropertyArrayCopy<uint8_t> arrayView = std::vector(values);
|
||||
FCesiumPropertyArray array(arrayView);
|
||||
TestEqual(
|
||||
"size",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetSize(array),
|
||||
static_cast<int64>(values.size()));
|
||||
|
||||
FCesiumMetadataValue value =
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetValue(array, -1);
|
||||
FCesiumMetadataValueType valueType =
|
||||
UCesiumMetadataValueBlueprintLibrary::GetValueType(value);
|
||||
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Invalid);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::None);
|
||||
|
||||
value = UCesiumPropertyArrayBlueprintLibrary::GetValue(array, 1);
|
||||
valueType = UCesiumMetadataValueBlueprintLibrary::GetValueType(value);
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Invalid);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::None);
|
||||
});
|
||||
|
||||
It("gets value for valid index", [this]() {
|
||||
std::vector<uint8_t> values{1, 2, 3, 4};
|
||||
CesiumGltf::PropertyArrayCopy<uint8_t> arrayView = std::vector(values);
|
||||
FCesiumPropertyArray array(arrayView);
|
||||
TestEqual(
|
||||
"size",
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetSize(array),
|
||||
static_cast<int64>(values.size()));
|
||||
|
||||
for (size_t i = 0; i < values.size(); i++) {
|
||||
FCesiumMetadataValue value =
|
||||
UCesiumPropertyArrayBlueprintLibrary::GetValue(
|
||||
array,
|
||||
static_cast<int64>(i));
|
||||
|
||||
FCesiumMetadataValueType valueType =
|
||||
UCesiumMetadataValueBlueprintLibrary::GetValueType(value);
|
||||
TestEqual("type", valueType.Type, ECesiumMetadataType::Scalar);
|
||||
TestEqual(
|
||||
"componentType",
|
||||
valueType.ComponentType,
|
||||
ECesiumMetadataComponentType::Uint8);
|
||||
|
||||
TestEqual(
|
||||
"byte value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetByte(value, 0),
|
||||
values[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,844 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumPropertyTable.h"
|
||||
#include "CesiumGltf/ExtensionModelExtStructuralMetadata.h"
|
||||
#include "CesiumGltf/Model.h"
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include <limits>
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumPropertyTableSpec,
|
||||
"Cesium.Unit.PropertyTable",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
CesiumGltf::Model model;
|
||||
CesiumGltf::ExtensionModelExtStructuralMetadata* pExtension;
|
||||
CesiumGltf::PropertyTable* pPropertyTable;
|
||||
END_DEFINE_SPEC(FCesiumPropertyTableSpec)
|
||||
|
||||
void FCesiumPropertyTableSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
model = CesiumGltf::Model();
|
||||
pExtension =
|
||||
&model.addExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
||||
pExtension->schema.emplace();
|
||||
pPropertyTable = &pExtension->propertyTables.emplace_back();
|
||||
});
|
||||
|
||||
Describe("Constructor", [this]() {
|
||||
It("constructs invalid instance by default", [this]() {
|
||||
FCesiumPropertyTable propertyTable;
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTable);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(0));
|
||||
});
|
||||
|
||||
It("constructs invalid instance for missing schema", [this]() {
|
||||
pExtension->schema.reset();
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTableClass);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(0));
|
||||
});
|
||||
|
||||
It("constructs invalid instance for missing class", [this]() {
|
||||
pPropertyTable->classProperty = "nonexistent class";
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTableClass);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(0));
|
||||
});
|
||||
|
||||
It("constructs valid instance with valid property", [this]() {
|
||||
pPropertyTable->classProperty = "testClass";
|
||||
std::string propertyName("testProperty");
|
||||
std::vector<int32_t> values{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(values.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(values.size()));
|
||||
});
|
||||
|
||||
It("constructs valid instance with invalid property", [this]() {
|
||||
// Even if one of its properties is invalid, the property table itself is
|
||||
// still valid.
|
||||
pPropertyTable->classProperty = "testClass";
|
||||
std::string propertyName("testProperty");
|
||||
std::vector<int8_t> values{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(values.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32, // Incorrect
|
||||
// component type
|
||||
values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(values.size()));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetProperties", [this]() {
|
||||
BeforeEach([this]() { pPropertyTable->classProperty = "testClass"; });
|
||||
|
||||
It("returns no properties for invalid property table", [this]() {
|
||||
FCesiumPropertyTable propertyTable;
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTable);
|
||||
const auto properties =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable);
|
||||
TestTrue("properties are empty", properties.IsEmpty());
|
||||
});
|
||||
|
||||
It("gets valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
const auto properties =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable);
|
||||
|
||||
TestTrue(
|
||||
"has scalar property",
|
||||
properties.Contains(FString(scalarPropertyName.c_str())));
|
||||
const FCesiumPropertyTableProperty& scalarProperty =
|
||||
*properties.Find(FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(scalarProperty),
|
||||
ECesiumPropertyTablePropertyStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
scalarProperty),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
for (size_t i = 0; i < scalarValues.size(); i++) {
|
||||
std::string label("Property value " + std::to_string(i));
|
||||
TestEqual(
|
||||
label.c_str(),
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetInteger(
|
||||
scalarProperty,
|
||||
static_cast<int64>(i)),
|
||||
scalarValues[i]);
|
||||
}
|
||||
|
||||
TestTrue(
|
||||
"has vec2 property",
|
||||
properties.Contains(FString(vec2PropertyName.c_str())));
|
||||
const FCesiumPropertyTableProperty& vec2Property =
|
||||
*properties.Find(FString(vec2PropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(vec2Property),
|
||||
ECesiumPropertyTablePropertyStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
vec2Property),
|
||||
static_cast<int64>(vec2Values.size()));
|
||||
for (size_t i = 0; i < vec2Values.size(); i++) {
|
||||
std::string label("Property value " + std::to_string(i));
|
||||
FVector2D expected(
|
||||
static_cast<double>(vec2Values[i][0]),
|
||||
static_cast<double>(vec2Values[i][1]));
|
||||
TestEqual(
|
||||
label.c_str(),
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetVector2D(
|
||||
vec2Property,
|
||||
static_cast<int64>(i),
|
||||
FVector2D::Zero()),
|
||||
expected);
|
||||
}
|
||||
});
|
||||
|
||||
It("gets invalid property", [this]() {
|
||||
// Even invalid properties should still be retrieved.
|
||||
std::vector<int8_t> values{0, 1, 2};
|
||||
pPropertyTable->count = static_cast<int64_t>(values.size());
|
||||
std::string propertyName("badProperty");
|
||||
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(values.size()));
|
||||
|
||||
const auto properties =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable);
|
||||
|
||||
TestTrue(
|
||||
"has invalid property",
|
||||
properties.Contains(FString(propertyName.c_str())));
|
||||
const FCesiumPropertyTableProperty& property =
|
||||
*properties.Find(FString(propertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(property),
|
||||
ECesiumPropertyTablePropertyStatus::ErrorInvalidPropertyData);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
property),
|
||||
0);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetPropertyNames", [this]() {
|
||||
BeforeEach([this]() { pPropertyTable->classProperty = "testClass"; });
|
||||
|
||||
It("returns empty array for invalid property table", [this]() {
|
||||
FCesiumPropertyTable propertyTable;
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTable);
|
||||
const auto properties =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable);
|
||||
TestTrue("properties are empty", properties.IsEmpty());
|
||||
});
|
||||
|
||||
It("gets all property names", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
std::string invalidPropertyName("badProperty");
|
||||
std::vector<int8_t> invalidPropertyValues{0, 1, 2};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
invalidPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32, // Incorrect
|
||||
// component type
|
||||
invalidPropertyValues);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
const auto propertyNames =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyNames(propertyTable);
|
||||
TestEqual("number of names", propertyNames.Num(), 3);
|
||||
TestTrue(
|
||||
"has scalar property name",
|
||||
propertyNames.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"has vec2 property name",
|
||||
propertyNames.Contains(FString(vec2PropertyName.c_str())));
|
||||
TestTrue(
|
||||
"has invalid property name",
|
||||
propertyNames.Contains(FString(invalidPropertyName.c_str())));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FindProperty", [this]() {
|
||||
BeforeEach([this]() { pPropertyTable->classProperty = "testClass"; });
|
||||
|
||||
It("returns invalid instance for nonexistent property", [this]() {
|
||||
std::string propertyName("testProperty");
|
||||
std::vector<int32_t> values{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(values.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(values.size()));
|
||||
|
||||
FCesiumPropertyTableProperty property =
|
||||
UCesiumPropertyTableBlueprintLibrary::FindProperty(
|
||||
propertyTable,
|
||||
FString("nonexistent property"));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(property),
|
||||
ECesiumPropertyTablePropertyStatus::ErrorInvalidProperty);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
property),
|
||||
static_cast<int64_t>(0));
|
||||
});
|
||||
|
||||
It("finds existing properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
FCesiumPropertyTableProperty scalarProperty =
|
||||
UCesiumPropertyTableBlueprintLibrary::FindProperty(
|
||||
propertyTable,
|
||||
FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(scalarProperty),
|
||||
ECesiumPropertyTablePropertyStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
scalarProperty),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
FCesiumPropertyTableProperty vec2Property =
|
||||
UCesiumPropertyTableBlueprintLibrary::FindProperty(
|
||||
propertyTable,
|
||||
FString(vec2PropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTablePropertyStatus",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::
|
||||
GetPropertyTablePropertyStatus(vec2Property),
|
||||
ECesiumPropertyTablePropertyStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Size",
|
||||
UCesiumPropertyTablePropertyBlueprintLibrary::GetPropertySize(
|
||||
vec2Property),
|
||||
static_cast<int64>(vec2Values.size()));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetMetadataValuesForFeature", [this]() {
|
||||
BeforeEach([this]() { pPropertyTable->classProperty = "testClass"; });
|
||||
|
||||
It("returns empty map for invalid property table", [this]() {
|
||||
FCesiumPropertyTable propertyTable;
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTable);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(0));
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature(
|
||||
propertyTable,
|
||||
0);
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns empty map for out-of-bounds feature IDs", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(scalarValues.size()));
|
||||
|
||||
auto values =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature(
|
||||
propertyTable,
|
||||
-1);
|
||||
TestTrue("no values for for negative feature ID", values.IsEmpty());
|
||||
|
||||
values =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature(
|
||||
propertyTable,
|
||||
10);
|
||||
TestTrue(
|
||||
"no values for positive out-of-range feature ID",
|
||||
values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns values of valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
for (size_t i = 0; i < scalarValues.size(); i++) {
|
||||
const auto values =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature(
|
||||
propertyTable,
|
||||
static_cast<int64>(i));
|
||||
TestEqual("number of values", values.Num(), 2);
|
||||
|
||||
TestTrue(
|
||||
"contains scalar value",
|
||||
values.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"contains vec2 value",
|
||||
values.Contains(FString(vec2PropertyName.c_str())));
|
||||
|
||||
const FCesiumMetadataValue& scalarValue =
|
||||
*values.Find(FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"scalar value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetInteger(scalarValue, 0),
|
||||
scalarValues[i]);
|
||||
|
||||
const FCesiumMetadataValue& vec2Value =
|
||||
*values.Find(FString(vec2PropertyName.c_str()));
|
||||
FVector2D expected(vec2Values[i][0], vec2Values[i][1]);
|
||||
TestEqual(
|
||||
"vec2 value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetVector2D(
|
||||
vec2Value,
|
||||
FVector2D::Zero()),
|
||||
expected);
|
||||
}
|
||||
});
|
||||
|
||||
It("does not return value for invalid property", [this]() {
|
||||
std::vector<int8_t> propertyValues{0, 1, 2};
|
||||
pPropertyTable->count = static_cast<int64_t>(propertyValues.size());
|
||||
std::string propertyName("badProperty");
|
||||
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
propertyValues);
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(propertyValues.size()));
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTableBlueprintLibrary::GetMetadataValuesForFeature(
|
||||
propertyTable,
|
||||
0);
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetMetadataValuesForFeatureAsStrings", [this]() {
|
||||
BeforeEach([this]() { pPropertyTable->classProperty = "testClass"; });
|
||||
|
||||
It("returns empty map for invalid property table", [this]() {
|
||||
FCesiumPropertyTable propertyTable;
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::ErrorInvalidPropertyTable);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(0));
|
||||
|
||||
const auto values = UCesiumPropertyTableBlueprintLibrary::
|
||||
GetMetadataValuesForFeatureAsStrings(propertyTable, 0);
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns empty map for out-of-bounds feature IDs", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64_t>(scalarValues.size()));
|
||||
|
||||
auto values = UCesiumPropertyTableBlueprintLibrary::
|
||||
GetMetadataValuesForFeatureAsStrings(propertyTable, -1);
|
||||
TestTrue("no values for for negative feature ID", values.IsEmpty());
|
||||
|
||||
values = UCesiumPropertyTableBlueprintLibrary::
|
||||
GetMetadataValuesForFeatureAsStrings(propertyTable, 10);
|
||||
TestTrue(
|
||||
"no values for positive out-of-range feature ID",
|
||||
values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns values of valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::vector<int32_t> scalarValues{1, 2, 3, 4};
|
||||
pPropertyTable->count = static_cast<int64_t>(scalarValues.size());
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
scalarPropertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
scalarValues);
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::vector<glm::vec2> vec2Values{
|
||||
glm::vec2(1.0f, 2.5f),
|
||||
glm::vec2(-0.7f, 4.9f),
|
||||
glm::vec2(8.0f, 2.0f),
|
||||
glm::vec2(11.0f, 0.0f),
|
||||
};
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
vec2PropertyName,
|
||||
CesiumGltf::ClassProperty::Type::VEC2,
|
||||
CesiumGltf::ClassProperty::ComponentType::FLOAT32,
|
||||
vec2Values);
|
||||
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(scalarValues.size()));
|
||||
|
||||
for (size_t i = 0; i < scalarValues.size(); i++) {
|
||||
const auto values = UCesiumPropertyTableBlueprintLibrary::
|
||||
GetMetadataValuesForFeatureAsStrings(
|
||||
propertyTable,
|
||||
static_cast<int64>(i));
|
||||
TestEqual("number of values", values.Num(), 2);
|
||||
|
||||
TestTrue(
|
||||
"contains scalar value",
|
||||
values.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"contains vec2 value",
|
||||
values.Contains(FString(vec2PropertyName.c_str())));
|
||||
|
||||
const FString& scalarValue =
|
||||
*values.Find(FString(scalarPropertyName.c_str()));
|
||||
FString expected(std::to_string(scalarValues[i]).c_str());
|
||||
TestEqual("scalar value as string", scalarValue, expected);
|
||||
|
||||
const FString& vec2Value =
|
||||
*values.Find(FString(vec2PropertyName.c_str()));
|
||||
std::string expectedString(
|
||||
"X=" + std::to_string(vec2Values[i][0]) +
|
||||
" Y=" + std::to_string(vec2Values[i][1]));
|
||||
expected = FString(expectedString.c_str());
|
||||
TestEqual("vec2 value as string", vec2Value, expected);
|
||||
}
|
||||
});
|
||||
|
||||
It("does not return value for invalid property", [this]() {
|
||||
std::vector<int8_t> propertyValues{0, 1, 2};
|
||||
pPropertyTable->count = static_cast<int64_t>(propertyValues.size());
|
||||
std::string propertyName("badProperty");
|
||||
|
||||
AddPropertyTablePropertyToModel(
|
||||
model,
|
||||
*pPropertyTable,
|
||||
propertyName,
|
||||
CesiumGltf::ClassProperty::Type::SCALAR,
|
||||
CesiumGltf::ClassProperty::ComponentType::INT32,
|
||||
propertyValues);
|
||||
FCesiumPropertyTable propertyTable(model, *pPropertyTable);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTableStatus",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableStatus(
|
||||
propertyTable),
|
||||
ECesiumPropertyTableStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Count",
|
||||
UCesiumPropertyTableBlueprintLibrary::GetPropertyTableCount(
|
||||
propertyTable),
|
||||
static_cast<int64>(propertyValues.size()));
|
||||
|
||||
const auto values = UCesiumPropertyTableBlueprintLibrary::
|
||||
GetMetadataValuesForFeatureAsStrings(propertyTable, 0);
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,823 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumPropertyTexture.h"
|
||||
#include "CesiumGltf/ExtensionModelExtStructuralMetadata.h"
|
||||
#include "CesiumGltf/Model.h"
|
||||
#include "CesiumGltfComponent.h"
|
||||
#include "CesiumGltfPrimitiveComponent.h"
|
||||
#include "CesiumGltfSpecUtility.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include <limits>
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FCesiumPropertyTextureSpec,
|
||||
"Cesium.Unit.PropertyTexture",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
CesiumGltf::Model model;
|
||||
CesiumGltf::MeshPrimitive* pPrimitive;
|
||||
CesiumGltf::ExtensionModelExtStructuralMetadata* pExtension;
|
||||
CesiumGltf::PropertyTexture* pPropertyTexture;
|
||||
TObjectPtr<UCesiumGltfComponent> pModelComponent;
|
||||
TObjectPtr<UCesiumGltfPrimitiveComponent> pPrimitiveComponent;
|
||||
|
||||
const std::vector<FVector2D> texCoords{
|
||||
FVector2D(0, 0),
|
||||
FVector2D(0.5, 0),
|
||||
FVector2D(0, 0.5),
|
||||
FVector2D(0.5, 0.5)};
|
||||
END_DEFINE_SPEC(FCesiumPropertyTextureSpec)
|
||||
|
||||
void FCesiumPropertyTextureSpec::Define() {
|
||||
using namespace CesiumGltf;
|
||||
|
||||
BeforeEach([this]() {
|
||||
model = Model();
|
||||
pExtension = &model.addExtension<ExtensionModelExtStructuralMetadata>();
|
||||
pExtension->schema.emplace();
|
||||
pPropertyTexture = &pExtension->propertyTextures.emplace_back();
|
||||
});
|
||||
|
||||
Describe("Constructor", [this]() {
|
||||
It("constructs invalid instance by default", [this]() {
|
||||
FCesiumPropertyTexture propertyTexture;
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTexture);
|
||||
TestTrue(
|
||||
"Properties",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.IsEmpty());
|
||||
});
|
||||
|
||||
It("constructs invalid instance for missing schema", [this]() {
|
||||
pExtension->schema.reset();
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTextureClass);
|
||||
TestTrue(
|
||||
"Properties",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.IsEmpty());
|
||||
});
|
||||
|
||||
It("constructs invalid instance for missing class", [this]() {
|
||||
pPropertyTexture->classProperty = "nonexistent class";
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTextureClass);
|
||||
TestTrue(
|
||||
"Properties",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.IsEmpty());
|
||||
});
|
||||
|
||||
It("constructs valid instance with valid property", [this]() {
|
||||
pPropertyTexture->classProperty = "testClass";
|
||||
std::string propertyName("testProperty");
|
||||
std::array<int8_t, 4> values{1, 2, 3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
values,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
});
|
||||
|
||||
It("constructs valid instance with invalid property", [this]() {
|
||||
// Even if one of its properties is invalid, the property texture itself
|
||||
// is still valid.
|
||||
pPropertyTexture->classProperty = "testClass";
|
||||
std::string propertyName("testProperty");
|
||||
std::array<int8_t, 4> values{1, 2, 3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT32, // Incorrect component type
|
||||
values,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual<int64>(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetProperties", [this]() {
|
||||
BeforeEach([this]() { pPropertyTexture->classProperty = "testClass"; });
|
||||
|
||||
It("returns no properties for invalid property texture", [this]() {
|
||||
FCesiumPropertyTexture propertyTexture;
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTexture);
|
||||
const auto properties =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(
|
||||
propertyTexture);
|
||||
TestTrue("properties are empty", properties.IsEmpty());
|
||||
});
|
||||
|
||||
It("gets valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::array<glm::u8vec2, 4> vec2Values{
|
||||
glm::u8vec2(1, 2),
|
||||
glm::u8vec2(0, 4),
|
||||
glm::u8vec2(8, 9),
|
||||
glm::u8vec2(11, 0),
|
||||
};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
vec2PropertyName,
|
||||
ClassProperty::Type::VEC2,
|
||||
ClassProperty::ComponentType::UINT8,
|
||||
vec2Values,
|
||||
{0, 1});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
|
||||
const auto properties =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(
|
||||
propertyTexture);
|
||||
|
||||
TestTrue(
|
||||
"has scalar property",
|
||||
properties.Contains(FString(scalarPropertyName.c_str())));
|
||||
const FCesiumPropertyTextureProperty& scalarProperty =
|
||||
*properties.Find(FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(scalarProperty),
|
||||
ECesiumPropertyTexturePropertyStatus::Valid);
|
||||
for (size_t i = 0; i < texCoords.size(); i++) {
|
||||
std::string label("Property value " + std::to_string(i));
|
||||
TestEqual(
|
||||
label.c_str(),
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::GetInteger(
|
||||
scalarProperty,
|
||||
texCoords[i]),
|
||||
scalarValues[i]);
|
||||
}
|
||||
|
||||
TestTrue(
|
||||
"has vec2 property",
|
||||
properties.Contains(FString(vec2PropertyName.c_str())));
|
||||
const FCesiumPropertyTextureProperty& vec2Property =
|
||||
*properties.Find(FString(vec2PropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(vec2Property),
|
||||
ECesiumPropertyTexturePropertyStatus::Valid);
|
||||
for (size_t i = 0; i < texCoords.size(); i++) {
|
||||
std::string label("Property value " + std::to_string(i));
|
||||
FVector2D expected(
|
||||
static_cast<double>(vec2Values[i][0]),
|
||||
static_cast<double>(vec2Values[i][1]));
|
||||
TestEqual(
|
||||
label.c_str(),
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector2D(
|
||||
vec2Property,
|
||||
texCoords[i],
|
||||
FVector2D::Zero()),
|
||||
expected);
|
||||
}
|
||||
});
|
||||
|
||||
It("gets invalid property", [this]() {
|
||||
// Even invalid properties should still be retrieved.
|
||||
std::array<int8_t, 4> values{0, 1, 2, 3};
|
||||
std::string propertyName("badProperty");
|
||||
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT32,
|
||||
values,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
|
||||
const auto properties =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(
|
||||
propertyTexture);
|
||||
|
||||
TestTrue(
|
||||
"has invalid property",
|
||||
properties.Contains(FString(propertyName.c_str())));
|
||||
const FCesiumPropertyTextureProperty& property =
|
||||
*properties.Find(FString(propertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(property),
|
||||
ECesiumPropertyTexturePropertyStatus::ErrorInvalidPropertyData);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetPropertyNames", [this]() {
|
||||
BeforeEach([this]() { pPropertyTexture->classProperty = "testClass"; });
|
||||
|
||||
It("returns empty array for invalid property texture", [this]() {
|
||||
FCesiumPropertyTexture propertyTexture;
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTexture);
|
||||
const auto properties =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(
|
||||
propertyTexture);
|
||||
TestTrue("properties are empty", properties.IsEmpty());
|
||||
});
|
||||
|
||||
It("gets all property names", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::array<glm::u8vec2, 4> vec2Values{
|
||||
glm::u8vec2(1, 2),
|
||||
glm::u8vec2(0, 4),
|
||||
glm::u8vec2(8, 9),
|
||||
glm::u8vec2(11, 0),
|
||||
};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
vec2PropertyName,
|
||||
ClassProperty::Type::VEC2,
|
||||
ClassProperty::ComponentType::UINT8,
|
||||
vec2Values,
|
||||
{0, 1});
|
||||
|
||||
std::string invalidPropertyName("badProperty");
|
||||
std::array<uint8_t, 4> invalidPropertyValues{0, 1, 2, 3};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
invalidPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT32, // Incorrect component type
|
||||
invalidPropertyValues,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
|
||||
const auto propertyNames =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyNames(
|
||||
propertyTexture);
|
||||
TestEqual("number of names", propertyNames.Num(), 3);
|
||||
TestTrue(
|
||||
"has scalar property name",
|
||||
propertyNames.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"has vec2 property name",
|
||||
propertyNames.Contains(FString(vec2PropertyName.c_str())));
|
||||
TestTrue(
|
||||
"has invalid property name",
|
||||
propertyNames.Contains(FString(invalidPropertyName.c_str())));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FindProperty", [this]() {
|
||||
BeforeEach([this]() { pPropertyTexture->classProperty = "testClass"; });
|
||||
|
||||
It("returns invalid instance for nonexistent property", [this]() {
|
||||
std::string propertyName("testProperty");
|
||||
std::array<int8_t, 4> values{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
values,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
|
||||
FCesiumPropertyTextureProperty property =
|
||||
UCesiumPropertyTextureBlueprintLibrary::FindProperty(
|
||||
propertyTexture,
|
||||
FString("nonexistent property"));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(property),
|
||||
ECesiumPropertyTexturePropertyStatus::ErrorInvalidProperty);
|
||||
});
|
||||
|
||||
It("finds existing properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::array<glm::u8vec2, 4> vec2Values{
|
||||
glm::u8vec2(1, 2),
|
||||
glm::u8vec2(0, 4),
|
||||
glm::u8vec2(8, 9),
|
||||
glm::u8vec2(11, 0),
|
||||
};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
vec2PropertyName,
|
||||
ClassProperty::Type::VEC2,
|
||||
ClassProperty::ComponentType::UINT8,
|
||||
vec2Values,
|
||||
{0, 1});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
2);
|
||||
|
||||
FCesiumPropertyTextureProperty scalarProperty =
|
||||
UCesiumPropertyTextureBlueprintLibrary::FindProperty(
|
||||
propertyTexture,
|
||||
FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(scalarProperty),
|
||||
ECesiumPropertyTexturePropertyStatus::Valid);
|
||||
|
||||
FCesiumPropertyTextureProperty vec2Property =
|
||||
UCesiumPropertyTextureBlueprintLibrary::FindProperty(
|
||||
propertyTexture,
|
||||
FString(vec2PropertyName.c_str()));
|
||||
TestEqual(
|
||||
"PropertyTexturePropertyStatus",
|
||||
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
||||
GetPropertyTexturePropertyStatus(vec2Property),
|
||||
ECesiumPropertyTexturePropertyStatus::Valid);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetMetadataValuesForUV", [this]() {
|
||||
BeforeEach([this]() { pPropertyTexture->classProperty = "testClass"; });
|
||||
|
||||
It("returns empty map for invalid property texture", [this]() {
|
||||
FCesiumPropertyTexture propertyTexture;
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::ErrorInvalidPropertyTexture);
|
||||
TestTrue(
|
||||
"Properties",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.IsEmpty());
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesForUV(
|
||||
propertyTexture,
|
||||
FVector2D::Zero());
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns values of valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::array<glm::u8vec2, 4> vec2Values{
|
||||
glm::u8vec2(1, 2),
|
||||
glm::u8vec2(0, 4),
|
||||
glm::u8vec2(8, 9),
|
||||
glm::u8vec2(11, 0),
|
||||
};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
vec2PropertyName,
|
||||
ClassProperty::Type::VEC2,
|
||||
ClassProperty::ComponentType::UINT8,
|
||||
vec2Values,
|
||||
{0, 1});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
2);
|
||||
|
||||
for (size_t i = 0; i < texCoords.size(); i++) {
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesForUV(
|
||||
propertyTexture,
|
||||
texCoords[i]);
|
||||
TestEqual("number of values", values.Num(), 2);
|
||||
|
||||
TestTrue(
|
||||
"contains scalar value",
|
||||
values.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"contains vec2 value",
|
||||
values.Contains(FString(vec2PropertyName.c_str())));
|
||||
|
||||
const FCesiumMetadataValue& scalarValue =
|
||||
*values.Find(FString(scalarPropertyName.c_str()));
|
||||
TestEqual(
|
||||
"scalar value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetInteger(scalarValue, 0),
|
||||
scalarValues[i]);
|
||||
|
||||
const FCesiumMetadataValue& vec2Value =
|
||||
*values.Find(FString(vec2PropertyName.c_str()));
|
||||
FVector2D expected(vec2Values[i][0], vec2Values[i][1]);
|
||||
TestEqual(
|
||||
"vec2 value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetVector2D(
|
||||
vec2Value,
|
||||
FVector2D::Zero()),
|
||||
expected);
|
||||
}
|
||||
});
|
||||
|
||||
It("does not return value for invalid property", [this]() {
|
||||
std::string propertyName("badProperty");
|
||||
std::array<int8_t, 4> data{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT32,
|
||||
data,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesForUV(
|
||||
propertyTexture,
|
||||
FVector2D::Zero());
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
});
|
||||
|
||||
Describe("GetMetadataValuesFromHit", [this]() {
|
||||
BeforeEach([this]() {
|
||||
Mesh& mesh = model.meshes.emplace_back();
|
||||
pPrimitive = &mesh.primitives.emplace_back();
|
||||
pPrimitive->mode = MeshPrimitive::Mode::TRIANGLES;
|
||||
|
||||
std::vector<glm::vec3> 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",
|
||||
AccessorSpec::Type::VEC3,
|
||||
AccessorSpec::ComponentType::FLOAT,
|
||||
positions);
|
||||
|
||||
int32_t positionAccessorIndex =
|
||||
static_cast<int32_t>(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<glm::vec2> 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",
|
||||
AccessorSpec::Type::VEC2,
|
||||
AccessorSpec::ComponentType::FLOAT,
|
||||
texCoords0);
|
||||
|
||||
pModelComponent = NewObject<UCesiumGltfComponent>();
|
||||
pPrimitiveComponent =
|
||||
NewObject<UCesiumGltfPrimitiveComponent>(pModelComponent);
|
||||
pPrimitiveComponent->AttachToComponent(
|
||||
pModelComponent,
|
||||
FAttachmentTransformRules(EAttachmentRule::KeepRelative, false));
|
||||
|
||||
CesiumPrimitiveData& primData = pPrimitiveComponent->getPrimitiveData();
|
||||
primData.pMeshPrimitive = pPrimitive;
|
||||
primData.PositionAccessor =
|
||||
CesiumGltf::AccessorView<FVector3f>(model, positionAccessorIndex);
|
||||
primData.TexCoordAccessorMap.emplace(
|
||||
0,
|
||||
AccessorView<CesiumGltf::AccessorTypes::VEC2<float>>(
|
||||
model,
|
||||
static_cast<int32_t>(model.accessors.size() - 1)));
|
||||
|
||||
pPropertyTexture->classProperty = "testClass";
|
||||
});
|
||||
|
||||
It("returns empty map for invalid hit component", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.Component = nullptr;
|
||||
Hit.FaceIndex = 0;
|
||||
Hit.Location = FVector_NetQuantize{0, 0, 0} *
|
||||
CesiumPrimitiveData::positionScaleFactor;
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesFromHit(
|
||||
propertyTexture,
|
||||
Hit);
|
||||
TestTrue("values is empty", values.IsEmpty());
|
||||
});
|
||||
|
||||
It("returns values of valid properties", [this]() {
|
||||
std::string scalarPropertyName("scalarProperty");
|
||||
std::array<int8_t, 4> scalarValues{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
scalarPropertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT8,
|
||||
scalarValues,
|
||||
{0});
|
||||
|
||||
std::string vec2PropertyName("vec2Property");
|
||||
std::array<glm::u8vec2, 4> vec2Values{
|
||||
glm::u8vec2(1, 2),
|
||||
glm::u8vec2(0, 4),
|
||||
glm::u8vec2(8, 9),
|
||||
glm::u8vec2(11, 0),
|
||||
};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
vec2PropertyName,
|
||||
ClassProperty::Type::VEC2,
|
||||
ClassProperty::ComponentType::UINT8,
|
||||
vec2Values,
|
||||
{0, 1});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
2);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.Component = pPrimitiveComponent;
|
||||
Hit.FaceIndex = 0;
|
||||
|
||||
std::array<FVector_NetQuantize, 3> locations{
|
||||
FVector_NetQuantize(1, 0, 0),
|
||||
FVector_NetQuantize(0, -1, 0),
|
||||
FVector_NetQuantize(0, -0.25, 0)};
|
||||
std::array<int8_t, 3> expectedScalar{2, -3, -1};
|
||||
std::array<FIntPoint, 3> expectedVec2{
|
||||
FIntPoint(0, 4),
|
||||
FIntPoint(8, 9),
|
||||
FIntPoint(1, 2)};
|
||||
|
||||
for (size_t i = 0; i < locations.size(); i++) {
|
||||
Hit.Location = locations[i] * CesiumPrimitiveData::positionScaleFactor;
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesFromHit(
|
||||
propertyTexture,
|
||||
Hit);
|
||||
TestEqual("number of values", values.Num(), 2);
|
||||
|
||||
TestTrue(
|
||||
"contains scalar value",
|
||||
values.Contains(FString(scalarPropertyName.c_str())));
|
||||
TestTrue(
|
||||
"contains vec2 value",
|
||||
values.Contains(FString(vec2PropertyName.c_str())));
|
||||
|
||||
const FCesiumMetadataValue* pScalarValue =
|
||||
values.Find(FString(scalarPropertyName.c_str()));
|
||||
if (pScalarValue) {
|
||||
TestEqual(
|
||||
"scalar value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetInteger(
|
||||
*pScalarValue,
|
||||
0),
|
||||
expectedScalar[i]);
|
||||
}
|
||||
|
||||
const FCesiumMetadataValue* pVec2Value =
|
||||
values.Find(FString(vec2PropertyName.c_str()));
|
||||
if (pVec2Value) {
|
||||
TestEqual(
|
||||
"vec2 value",
|
||||
UCesiumMetadataValueBlueprintLibrary::GetIntPoint(
|
||||
*pVec2Value,
|
||||
FIntPoint(0)),
|
||||
expectedVec2[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
It("does not return value for invalid property", [this]() {
|
||||
std::string propertyName("badProperty");
|
||||
std::array<int8_t, 4> data{-1, 2, -3, 4};
|
||||
AddPropertyTexturePropertyToModel(
|
||||
model,
|
||||
*pPropertyTexture,
|
||||
propertyName,
|
||||
ClassProperty::Type::SCALAR,
|
||||
ClassProperty::ComponentType::INT32,
|
||||
data,
|
||||
{0});
|
||||
|
||||
FCesiumPropertyTexture propertyTexture(model, *pPropertyTexture);
|
||||
|
||||
TestEqual(
|
||||
"PropertyTextureStatus",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetPropertyTextureStatus(
|
||||
propertyTexture),
|
||||
ECesiumPropertyTextureStatus::Valid);
|
||||
TestEqual(
|
||||
"Property Count",
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture)
|
||||
.Num(),
|
||||
1);
|
||||
|
||||
FHitResult Hit;
|
||||
Hit.Component = pPrimitiveComponent;
|
||||
Hit.FaceIndex = 0;
|
||||
Hit.Location = FVector_NetQuantize{0, 0, 0} *
|
||||
CesiumPrimitiveData::positionScaleFactor;
|
||||
|
||||
const auto values =
|
||||
UCesiumPropertyTextureBlueprintLibrary::GetMetadataValuesFromHit(
|
||||
propertyTexture,
|
||||
Hit);
|
||||
TestTrue("values map is empty", values.IsEmpty());
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,170 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumSceneGeneration.h"
|
||||
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
|
||||
#include "GameFramework/PlayerStart.h"
|
||||
#include "LevelEditorViewport.h"
|
||||
|
||||
#include "Cesium3DTileset.h"
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumSunSky.h"
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
|
||||
#include "CesiumTestHelpers.h"
|
||||
|
||||
namespace Cesium {
|
||||
|
||||
FString SceneGenerationContext::testIonToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwNzA3YWIzNS0xMjI5LTQ3MWEtOTgyNS05OTk0YThlOTE4NzMiLCJpZCI6MjU5LCJpYXQiOjE3Mjc3MzgxNjJ9.vll2Xe-NPuNPiC0KSe8uN7hgG-ldlalcXfdDBxxDkXY");
|
||||
|
||||
void SceneGenerationContext::setCommonProperties(
|
||||
const FVector& origin,
|
||||
const FVector& position,
|
||||
const FRotator& rotation,
|
||||
float fieldOfView) {
|
||||
startPosition = position;
|
||||
startRotation = rotation;
|
||||
startFieldOfView = fieldOfView;
|
||||
|
||||
georeference->SetOriginLongitudeLatitudeHeight(origin);
|
||||
|
||||
pawn->SetActorLocation(startPosition);
|
||||
pawn->SetActorRotation(startRotation);
|
||||
|
||||
TInlineComponentArray<UCameraComponent*> cameras;
|
||||
pawn->GetComponents<UCameraComponent>(cameras);
|
||||
for (UCameraComponent* cameraComponent : cameras)
|
||||
cameraComponent->SetFieldOfView(startFieldOfView);
|
||||
}
|
||||
|
||||
void SceneGenerationContext::refreshTilesets() {
|
||||
std::vector<ACesium3DTileset*>::iterator it;
|
||||
for (it = tilesets.begin(); it != tilesets.end(); ++it)
|
||||
(*it)->RefreshTileset();
|
||||
}
|
||||
|
||||
void SceneGenerationContext::setSuspendUpdate(bool suspend) {
|
||||
std::vector<ACesium3DTileset*>::iterator it;
|
||||
for (it = tilesets.begin(); it != tilesets.end(); ++it)
|
||||
(*it)->SuspendUpdate = suspend;
|
||||
}
|
||||
|
||||
void SceneGenerationContext::setMaximumSimultaneousTileLoads(int value) {
|
||||
std::vector<ACesium3DTileset*>::iterator it;
|
||||
for (it = tilesets.begin(); it != tilesets.end(); ++it)
|
||||
(*it)->MaximumSimultaneousTileLoads = value;
|
||||
}
|
||||
|
||||
bool SceneGenerationContext::areTilesetsDoneLoading() {
|
||||
if (tilesets.empty())
|
||||
return false;
|
||||
|
||||
std::vector<ACesium3DTileset*>::const_iterator it;
|
||||
for (it = tilesets.begin(); it != tilesets.end(); ++it) {
|
||||
ACesium3DTileset* tileset = *it;
|
||||
|
||||
int progress = (int)tileset->GetLoadProgress();
|
||||
if (progress != 100) {
|
||||
// We aren't done
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneGenerationContext::trackForPlay() {
|
||||
CesiumTestHelpers::trackForPlay(sunSky);
|
||||
CesiumTestHelpers::trackForPlay(georeference);
|
||||
CesiumTestHelpers::trackForPlay(pawn);
|
||||
|
||||
std::vector<ACesium3DTileset*>::iterator it;
|
||||
for (it = tilesets.begin(); it != tilesets.end(); ++it) {
|
||||
ACesium3DTileset* tileset = *it;
|
||||
CesiumTestHelpers::trackForPlay(tileset);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGenerationContext::initForPlay(
|
||||
SceneGenerationContext& creationContext) {
|
||||
world = GEditor->PlayWorld;
|
||||
sunSky = CesiumTestHelpers::findInPlay(creationContext.sunSky);
|
||||
georeference = CesiumTestHelpers::findInPlay(creationContext.georeference);
|
||||
pawn = CesiumTestHelpers::findInPlay(creationContext.pawn);
|
||||
|
||||
startPosition = creationContext.startPosition;
|
||||
startRotation = creationContext.startRotation;
|
||||
startFieldOfView = creationContext.startFieldOfView;
|
||||
|
||||
tilesets.clear();
|
||||
|
||||
std::vector<ACesium3DTileset*>& creationTilesets = creationContext.tilesets;
|
||||
std::vector<ACesium3DTileset*>::iterator it;
|
||||
for (it = creationTilesets.begin(); it != creationTilesets.end(); ++it) {
|
||||
ACesium3DTileset* creationTileset = *it;
|
||||
ACesium3DTileset* tileset = CesiumTestHelpers::findInPlay(creationTileset);
|
||||
tilesets.push_back(tileset);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGenerationContext::syncWorldCamera() {
|
||||
assert(GEditor);
|
||||
|
||||
if (GEditor->IsPlayingSessionInEditor()) {
|
||||
// If in PIE, set the player
|
||||
assert(world->GetNumPlayerControllers() == 1);
|
||||
|
||||
APlayerController* controller = world->GetFirstPlayerController();
|
||||
assert(controller);
|
||||
|
||||
controller->ClientSetLocation(startPosition, startRotation);
|
||||
|
||||
APlayerCameraManager* cameraManager = controller->PlayerCameraManager;
|
||||
assert(cameraManager);
|
||||
|
||||
cameraManager->SetFOV(startFieldOfView);
|
||||
} else {
|
||||
// If editing, set any viewports
|
||||
for (FLevelEditorViewportClient* ViewportClient :
|
||||
GEditor->GetLevelViewportClients()) {
|
||||
if (ViewportClient == NULL)
|
||||
continue;
|
||||
ViewportClient->SetViewLocation(startPosition);
|
||||
ViewportClient->SetViewRotation(startRotation);
|
||||
if (ViewportClient->ViewportType == LVT_Perspective)
|
||||
ViewportClient->ViewFOV = startFieldOfView;
|
||||
ViewportClient->Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void createCommonWorldObjects(SceneGenerationContext& context) {
|
||||
|
||||
context.world = FAutomationEditorCommonUtils::CreateNewMap();
|
||||
|
||||
context.sunSky = context.world->SpawnActor<ACesiumSunSky>();
|
||||
|
||||
APlayerStart* playerStart = context.world->SpawnActor<APlayerStart>();
|
||||
|
||||
FSoftObjectPath objectPath(
|
||||
TEXT("Class'/CesiumForUnreal/DynamicPawn.DynamicPawn_C'"));
|
||||
TSoftObjectPtr<UObject> DynamicPawn = TSoftObjectPtr<UObject>(objectPath);
|
||||
|
||||
context.georeference =
|
||||
ACesiumGeoreference::GetDefaultGeoreference(context.world);
|
||||
context.pawn = context.world->SpawnActor<AGlobeAwareDefaultPawn>(
|
||||
Cast<UClass>(DynamicPawn.LoadSynchronous()));
|
||||
|
||||
context.pawn->AutoPossessPlayer = EAutoReceiveInput::Player0;
|
||||
|
||||
AWorldSettings* pWorldSettings = context.world->GetWorldSettings();
|
||||
if (pWorldSettings)
|
||||
pWorldSettings->bEnableWorldBoundsChecks = false;
|
||||
}
|
||||
|
||||
} // namespace Cesium
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
@ -0,0 +1,51 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include <vector>
|
||||
|
||||
class UWorld;
|
||||
class ACesiumSunSky;
|
||||
class ACesiumGeoreference;
|
||||
class AGlobeAwareDefaultPawn;
|
||||
class ACesium3DTileset;
|
||||
class ACesiumCameraManager;
|
||||
|
||||
namespace Cesium {
|
||||
|
||||
struct SceneGenerationContext {
|
||||
UWorld* world;
|
||||
ACesiumSunSky* sunSky;
|
||||
ACesiumGeoreference* georeference;
|
||||
AGlobeAwareDefaultPawn* pawn;
|
||||
std::vector<ACesium3DTileset*> tilesets;
|
||||
|
||||
FVector startPosition;
|
||||
FRotator startRotation;
|
||||
float startFieldOfView;
|
||||
|
||||
void setCommonProperties(
|
||||
const FVector& origin,
|
||||
const FVector& position,
|
||||
const FRotator& rotation,
|
||||
float fieldOfView);
|
||||
|
||||
void refreshTilesets();
|
||||
void setSuspendUpdate(bool suspend);
|
||||
void setMaximumSimultaneousTileLoads(int32 value);
|
||||
bool areTilesetsDoneLoading();
|
||||
|
||||
void trackForPlay();
|
||||
void initForPlay(SceneGenerationContext& creationContext);
|
||||
void syncWorldCamera();
|
||||
|
||||
static FString testIonToken;
|
||||
};
|
||||
|
||||
void createCommonWorldObjects(SceneGenerationContext& context);
|
||||
|
||||
}; // namespace Cesium
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
@ -0,0 +1,111 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor/EditorPerformanceSettings.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#endif
|
||||
|
||||
namespace CesiumTestHelpers {
|
||||
|
||||
UWorld* getGlobalWorldContext() {
|
||||
const TIndirectArray<FWorldContext>& worldContexts =
|
||||
GEngine->GetWorldContexts();
|
||||
FWorldContext firstWorldContext = worldContexts[0];
|
||||
return firstWorldContext.World();
|
||||
}
|
||||
|
||||
void TestRotatorsAreEquivalent(
|
||||
FAutomationTestBase* pSpec,
|
||||
ACesiumGeoreference* pGeoreferenceExpected,
|
||||
const FRotator& rotatorExpected,
|
||||
ACesiumGeoreference* pGeoreferenceActual,
|
||||
const FRotator& rotatorActual) {
|
||||
FVector xEcefExpected =
|
||||
pGeoreferenceExpected->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorExpected.RotateVector(FVector::XAxisVector));
|
||||
FVector yEcefExpected =
|
||||
pGeoreferenceExpected->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorExpected.RotateVector(FVector::YAxisVector));
|
||||
FVector zEcefExpected =
|
||||
pGeoreferenceExpected->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorExpected.RotateVector(FVector::ZAxisVector));
|
||||
|
||||
FVector xEcefActual =
|
||||
pGeoreferenceActual->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorActual.RotateVector(FVector::XAxisVector));
|
||||
FVector yEcefActual =
|
||||
pGeoreferenceActual->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorActual.RotateVector(FVector::YAxisVector));
|
||||
FVector zEcefActual =
|
||||
pGeoreferenceActual->TransformUnrealDirectionToEarthCenteredEarthFixed(
|
||||
rotatorActual.RotateVector(FVector::ZAxisVector));
|
||||
|
||||
pSpec->TestEqual("xEcefActual", xEcefActual, xEcefExpected);
|
||||
pSpec->TestEqual("yEcefActual", yEcefActual, yEcefExpected);
|
||||
pSpec->TestEqual("zEcefActual", zEcefActual, zEcefExpected);
|
||||
}
|
||||
|
||||
void waitForNextFrame(
|
||||
const FDoneDelegate& done,
|
||||
UWorld* pWorld,
|
||||
float timeoutSeconds) {
|
||||
uint64 start = GFrameCounter;
|
||||
waitFor(done, pWorld, timeoutSeconds, [start]() {
|
||||
uint64 current = GFrameCounter;
|
||||
return current > start;
|
||||
});
|
||||
}
|
||||
|
||||
FName getUniqueTag(AActor* pActor) {
|
||||
return FName(FString::Printf(TEXT("%lld"), pActor));
|
||||
}
|
||||
|
||||
FName getUniqueTag(UActorComponent* pComponent) {
|
||||
return FName(FString::Printf(TEXT("%lld"), pComponent));
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
namespace {
|
||||
size_t timesAllowingEditorTick = 0;
|
||||
bool originalEditorTickState = true;
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
void pushAllowTickInEditor() {
|
||||
#if WITH_EDITOR
|
||||
if (timesAllowingEditorTick == 0) {
|
||||
UEditorPerformanceSettings* pSettings =
|
||||
GetMutableDefault<UEditorPerformanceSettings>();
|
||||
originalEditorTickState = pSettings->bThrottleCPUWhenNotForeground;
|
||||
pSettings->bThrottleCPUWhenNotForeground = false;
|
||||
}
|
||||
|
||||
++timesAllowingEditorTick;
|
||||
#endif
|
||||
}
|
||||
|
||||
void popAllowTickInEditor() {
|
||||
#if WITH_EDITOR
|
||||
--timesAllowingEditorTick;
|
||||
if (timesAllowingEditorTick == 0) {
|
||||
UEditorPerformanceSettings* pSettings =
|
||||
GetMutableDefault<UEditorPerformanceSettings>();
|
||||
pSettings->bThrottleCPUWhenNotForeground = originalEditorTickState;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void trackForPlay(AActor* pEditorActor) {
|
||||
pEditorActor->Tags.Add(getUniqueTag(pEditorActor));
|
||||
}
|
||||
|
||||
void trackForPlay(UActorComponent* pEditorComponent) {
|
||||
trackForPlay(pEditorComponent->GetOwner());
|
||||
pEditorComponent->ComponentTags.Add(getUniqueTag(pEditorComponent));
|
||||
}
|
||||
|
||||
} // namespace CesiumTestHelpers
|
||||
@ -0,0 +1,217 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CesiumRuntime.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Math/MathFwd.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor.h"
|
||||
#endif
|
||||
|
||||
class UWorld;
|
||||
class ACesiumGeoreference;
|
||||
|
||||
namespace CesiumTestHelpers {
|
||||
|
||||
UWorld* getGlobalWorldContext();
|
||||
|
||||
/// <summary>
|
||||
/// Verify that two rotations (expressed relative to two different
|
||||
/// georeferences) are equivalent by using them to rotate the principal axis
|
||||
/// vectors and then transforming those vectors to ECEF. The ECEF vectors should
|
||||
/// be the same in both cases.
|
||||
/// </summary>
|
||||
void TestRotatorsAreEquivalent(
|
||||
FAutomationTestBase* pSpec,
|
||||
ACesiumGeoreference* pGeoreferenceExpected,
|
||||
const FRotator& rotatorExpected,
|
||||
ACesiumGeoreference* pGeoreferenceActual,
|
||||
const FRotator& rotatorActual);
|
||||
|
||||
template <typename T>
|
||||
void waitForImpl(
|
||||
const FDoneDelegate& done,
|
||||
UWorld* pWorld,
|
||||
T&& condition,
|
||||
FTimerHandle& timerHandle) {
|
||||
if (condition()) {
|
||||
pWorld->GetTimerManager().ClearTimer(timerHandle);
|
||||
done.Execute();
|
||||
} else if (pWorld->GetTimerManager().GetTimerRemaining(timerHandle) <= 0.0f) {
|
||||
// Timeout
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Error,
|
||||
TEXT("Timed out waiting for a condition to become true."));
|
||||
pWorld->GetTimerManager().ClearTimer(timerHandle);
|
||||
done.Execute();
|
||||
} else {
|
||||
pWorld->GetTimerManager().SetTimerForNextTick(
|
||||
[done,
|
||||
pWorld,
|
||||
condition = std::forward<T>(condition),
|
||||
timerHandle]() mutable {
|
||||
waitForImpl<T>(done, pWorld, std::move(condition), timerHandle);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a provided lambda function to become true, ticking through render
|
||||
/// frames in the meantime. If the timeout elapses before the condition becomes
|
||||
/// true, an error is logged (which will cause a test failure) and the done
|
||||
/// delegate is invoked anyway.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="done">The done delegate provided by a LatentIt or
|
||||
/// LatentBeforeEach. It will be invoked when the condition is true or when the
|
||||
/// timeout elapses.</param>
|
||||
/// <param name="pWorld">The world in which to check the condition.</param>
|
||||
/// <param name="timeoutSeconds">The maximum time to wait for the condition to
|
||||
/// become true.</param>
|
||||
/// <param name="condition">A lambda that is invoked each
|
||||
/// frame. If this function returns false, waiting continues.</param>
|
||||
template <typename T>
|
||||
void waitFor(
|
||||
const FDoneDelegate& done,
|
||||
UWorld* pWorld,
|
||||
float timeoutSeconds,
|
||||
T&& condition) {
|
||||
FTimerHandle timerHandle;
|
||||
pWorld->GetTimerManager().SetTimer(timerHandle, timeoutSeconds, false);
|
||||
waitForImpl<T>(done, pWorld, std::forward<T>(condition), timerHandle);
|
||||
}
|
||||
|
||||
void waitForNextFrame(
|
||||
const FDoneDelegate& done,
|
||||
UWorld* pWorld,
|
||||
float timeoutSeconds);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first Actor that has a given tag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Actor type.</typeparam>
|
||||
/// <param name="pWorld">The world in which to search for the Actor.</param>
|
||||
/// <param name="tag">The tag to look for.</param>
|
||||
/// <returns>The Actor, or nullptr if it does not exist.</returns>
|
||||
template <typename T> T* getActorWithTag(UWorld* pWorld, const FName& tag) {
|
||||
TArray<AActor*> temp;
|
||||
UGameplayStatics::GetAllActorsWithTag(pWorld, tag, temp);
|
||||
if (temp.Num() < 1)
|
||||
return nullptr;
|
||||
|
||||
return Cast<T>(temp[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first ActorComponent that has a given tag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The ActorComponent type.</typeparam>
|
||||
/// <param name="pOwner">The Actor whose components to search.</param>
|
||||
/// <param name="tag">The tag to look for.</param>
|
||||
/// <returns>The ActorComponent, or nullptr if it does not exist.</returns>
|
||||
template <typename T> T* getComponentWithTag(AActor* pOwner, const FName& tag) {
|
||||
TArray<UActorComponent*> components =
|
||||
pOwner->GetComponentsByTag(T::StaticClass(), tag);
|
||||
if (components.Num() < 1)
|
||||
return nullptr;
|
||||
|
||||
return Cast<T>(components[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tag that can be used to uniquely identify a given Actor.
|
||||
/// </summary>
|
||||
/// <param name="pActor">The actor.</param>
|
||||
/// <returns>The unique tag.</returns>
|
||||
FName getUniqueTag(AActor* pActor);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tag that can be used to uniquely identify a given ActorComponent.
|
||||
/// </summary>
|
||||
/// <param name="pActor">The actor.</param>
|
||||
/// <returns>The unique tag.</returns>
|
||||
FName getUniqueTag(UActorComponent* pComponent);
|
||||
|
||||
/// <summary>
|
||||
/// By default, UE 5.3+ don't tick in a headless Editor, which is often used to
|
||||
/// run tests. Call this at the start of a test that requires ticking to
|
||||
/// override this default. Call popAllowTickInEditor after the test to restore
|
||||
/// the default.
|
||||
/// </summary>
|
||||
void pushAllowTickInEditor();
|
||||
|
||||
/// <summary>
|
||||
/// Call this after a test that needs working ticking to restore the default
|
||||
/// state.
|
||||
/// </summary>
|
||||
void popAllowTickInEditor();
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a provided Edit-mode Actor, so the equivalent object can later be
|
||||
/// found in Play mode.
|
||||
/// </summary>
|
||||
/// <param name="pEditorActor">The Actor in the Editor world to track.</param>
|
||||
void trackForPlay(AActor* pEditorActor);
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a provided Edit-mode ActorComponent, so the equivalent object can
|
||||
/// later be found in Play mode.
|
||||
/// </summary>
|
||||
/// <param name="pEditorComponent">The ActorComponent in the Editor world to
|
||||
/// track.</param>
|
||||
void trackForPlay(UActorComponent* pEditorComponent);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a Play-mode object equivalent to a given Editor-mode one that was
|
||||
/// previously tracked with <see cref="TrackForPlay" />. This works on instances
|
||||
/// derived from AActor and UActorComponent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to find.</typeparam>
|
||||
/// <param name="pEditorObject">The Editor object for which to find a Play-mode
|
||||
/// equivalent.</param>
|
||||
/// <returns>The play mode equivalent, or nullptr is one is not found.</returns>
|
||||
template <typename T> T* findInPlay(T* pEditorObject) {
|
||||
if (!IsValid(pEditorObject))
|
||||
return nullptr;
|
||||
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
if constexpr (std::is_base_of_v<AActor, T>) {
|
||||
return getActorWithTag<T>(pWorld, getUniqueTag(pEditorObject));
|
||||
} else if constexpr (std::is_base_of_v<UActorComponent, T>) {
|
||||
AActor* pEditorOwner = pEditorObject->GetOwner();
|
||||
if (!pEditorOwner)
|
||||
return nullptr;
|
||||
AActor* pPlayOwner = findInPlay(pEditorOwner);
|
||||
if (!pPlayOwner)
|
||||
return nullptr;
|
||||
return getComponentWithTag<T>(pPlayOwner, getUniqueTag(pEditorObject));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a Play-mode object equivalent to a given Editor-mode one that was
|
||||
/// previously tracked with <see cref="TrackForPlay" />. This works on instances
|
||||
/// derived from AActor and UActorComponent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to find.</typeparam>
|
||||
/// <param name="pEditorObject">The Editor object for which to find a Play-mode
|
||||
/// equivalent.</param>
|
||||
/// <returns>The play mode equivalent, or nullptr is one is
|
||||
/// not found.</returns>
|
||||
template <typename T> T* findInPlay(TObjectPtr<T> pEditorObject) {
|
||||
return findInPlay<T>(pEditorObject.Get());
|
||||
}
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
|
||||
} // namespace CesiumTestHelpers
|
||||
@ -0,0 +1,547 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "CesiumTextureUtility.h"
|
||||
#include "CesiumAsync/AsyncSystem.h"
|
||||
#include "ExtensionImageAssetUnreal.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "RenderingThread.h"
|
||||
#include <CesiumGltfReader/GltfReader.h>
|
||||
#include <UnrealTaskProcessor.h>
|
||||
#include <memory>
|
||||
|
||||
using namespace CesiumTextureUtility;
|
||||
using namespace CesiumUtility;
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
CesiumTextureUtilitySpec,
|
||||
"Cesium.Unit.CesiumTextureUtility",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ProductFilter | EAutomationTestFlags::NonNullRHI)
|
||||
std::vector<uint8_t> originalPixels;
|
||||
std::vector<uint8_t> originalMipPixels;
|
||||
std::vector<uint8_t> expectedMipPixelsIfGenerated;
|
||||
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> pImageAsset;
|
||||
|
||||
void RunTests();
|
||||
|
||||
void CheckPixels(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
bool requireMips = false);
|
||||
void CheckSRGB(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
bool expectedSRGB);
|
||||
void CheckAddress(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureAddress expectedAddressX,
|
||||
TextureAddress expectedAddressY);
|
||||
void CheckFilter(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureFilter expectedFilter);
|
||||
void CheckGroup(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureGroup expectedGroup);
|
||||
END_DEFINE_SPEC(CesiumTextureUtilitySpec)
|
||||
|
||||
void CesiumTextureUtilitySpec::Define() {
|
||||
Describe("Without Mips", [this]() {
|
||||
BeforeEach([this]() {
|
||||
originalPixels = {0x20, 0x40, 0x80, 0xF0, 0x21, 0x41, 0x81, 0xF1,
|
||||
0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3,
|
||||
0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5};
|
||||
originalMipPixels.clear();
|
||||
|
||||
pImageAsset.emplace();
|
||||
pImageAsset->width = 3;
|
||||
pImageAsset->height = 2;
|
||||
TestEqual(
|
||||
"image buffer size is correct",
|
||||
originalPixels.size(),
|
||||
pImageAsset->width * pImageAsset->height *
|
||||
pImageAsset->bytesPerChannel * pImageAsset->channels);
|
||||
pImageAsset->pixelData.resize(originalPixels.size());
|
||||
|
||||
std::memcpy(
|
||||
pImageAsset->pixelData.data(),
|
||||
originalPixels.data(),
|
||||
originalPixels.size());
|
||||
|
||||
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> pCopy =
|
||||
new CesiumGltf::ImageAsset(*pImageAsset);
|
||||
CesiumGltfReader::ImageDecoder::generateMipMaps(*pCopy);
|
||||
|
||||
expectedMipPixelsIfGenerated.clear();
|
||||
|
||||
if (pCopy->mipPositions.size() >= 2) {
|
||||
expectedMipPixelsIfGenerated.resize(pCopy->mipPositions[1].byteSize);
|
||||
for (size_t iSrc = pCopy->mipPositions[1].byteOffset, iDest = 0;
|
||||
iDest < pCopy->mipPositions[1].byteSize;
|
||||
++iSrc, ++iDest) {
|
||||
expectedMipPixelsIfGenerated[iDest] = uint8_t(pCopy->pixelData[iSrc]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RunTests();
|
||||
});
|
||||
|
||||
Describe("With Mips", [this]() {
|
||||
BeforeEach([this]() {
|
||||
pImageAsset.emplace();
|
||||
pImageAsset->width = 3;
|
||||
pImageAsset->height = 2;
|
||||
|
||||
// Original image (3x2)
|
||||
originalPixels = {0x20, 0x40, 0x80, 0xF0, 0x21, 0x41, 0x81, 0xF1,
|
||||
0x22, 0x42, 0x82, 0xF2, 0x23, 0x43, 0x83, 0xF3,
|
||||
0x24, 0x44, 0x84, 0xF4, 0x25, 0x45, 0x85, 0xF5};
|
||||
pImageAsset->mipPositions.emplace_back(
|
||||
CesiumGltf::ImageAssetMipPosition{0, originalPixels.size()});
|
||||
|
||||
// Mip 1 (1x1)
|
||||
originalMipPixels = {0x26, 0x46, 0x86, 0xF6};
|
||||
pImageAsset->mipPositions.emplace_back(CesiumGltf::ImageAssetMipPosition{
|
||||
pImageAsset->mipPositions[0].byteSize,
|
||||
originalMipPixels.size()});
|
||||
|
||||
pImageAsset->pixelData.resize(
|
||||
originalPixels.size() + originalMipPixels.size());
|
||||
std::memcpy(
|
||||
pImageAsset->pixelData.data(),
|
||||
originalPixels.data(),
|
||||
originalPixels.size());
|
||||
std::memcpy(
|
||||
pImageAsset->pixelData.data() + originalPixels.size(),
|
||||
originalMipPixels.data(),
|
||||
originalMipPixels.size());
|
||||
});
|
||||
|
||||
RunTests();
|
||||
});
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::RunTests() {
|
||||
It("ImageAsset non-sRGB", [this]() {
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded = loadTextureAnyThreadPart(
|
||||
*pImageAsset,
|
||||
TextureAddress::TA_Mirror,
|
||||
TextureAddress::TA_Wrap,
|
||||
TextureFilter::TF_Bilinear,
|
||||
true,
|
||||
TextureGroup::TEXTUREGROUP_Cinematic,
|
||||
false,
|
||||
std::nullopt);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, true);
|
||||
CheckSRGB(pRefCountedTexture, false);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Mirror,
|
||||
TextureAddress::TA_Wrap);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Bilinear);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_Cinematic);
|
||||
});
|
||||
|
||||
It("ImageAsset sRGB", [this]() {
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded = loadTextureAnyThreadPart(
|
||||
*pImageAsset,
|
||||
TextureAddress::TA_Clamp,
|
||||
TextureAddress::TA_Mirror,
|
||||
TextureFilter::TF_Trilinear,
|
||||
true,
|
||||
TextureGroup::TEXTUREGROUP_Bokeh,
|
||||
true,
|
||||
std::nullopt);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, true);
|
||||
CheckSRGB(pRefCountedTexture, true);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Clamp,
|
||||
TextureAddress::TA_Mirror);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Trilinear);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_Bokeh);
|
||||
});
|
||||
|
||||
It("Image and Sampler", [this]() {
|
||||
CesiumGltf::Sampler sampler;
|
||||
sampler.minFilter = CesiumGltf::Sampler::MinFilter::NEAREST;
|
||||
sampler.magFilter = CesiumGltf::Sampler::MagFilter::NEAREST;
|
||||
sampler.wrapS = CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT;
|
||||
sampler.wrapT = CesiumGltf::Sampler::WrapT::CLAMP_TO_EDGE;
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded =
|
||||
loadTextureFromImageAndSamplerAnyThreadPart(
|
||||
*pImageAsset,
|
||||
sampler,
|
||||
false);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, false);
|
||||
CheckSRGB(pRefCountedTexture, false);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Mirror,
|
||||
TextureAddress::TA_Clamp);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Nearest);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World);
|
||||
});
|
||||
|
||||
It("Model", [this]() {
|
||||
CesiumGltf::Model model;
|
||||
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset = pImageAsset;
|
||||
|
||||
CesiumGltf::Sampler& sampler = model.samplers.emplace_back();
|
||||
sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR;
|
||||
sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR;
|
||||
sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT;
|
||||
sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT;
|
||||
|
||||
CesiumGltf::Texture& texture = model.textures.emplace_back();
|
||||
texture.source = 0;
|
||||
texture.sampler = 0;
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded =
|
||||
loadTextureFromModelAnyThreadPart(model, texture, true);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, true);
|
||||
CheckSRGB(pRefCountedTexture, true);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Wrap,
|
||||
TextureAddress::TA_Mirror);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Default);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World);
|
||||
});
|
||||
|
||||
It("Two textures referencing one image", [this]() {
|
||||
CesiumGltf::Model model;
|
||||
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset = pImageAsset;
|
||||
|
||||
CesiumGltf::Sampler& sampler1 = model.samplers.emplace_back();
|
||||
sampler1.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR;
|
||||
sampler1.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR;
|
||||
sampler1.wrapS = CesiumGltf::Sampler::WrapS::REPEAT;
|
||||
sampler1.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT;
|
||||
|
||||
CesiumGltf::Texture& texture1 = model.textures.emplace_back();
|
||||
texture1.source = 0;
|
||||
texture1.sampler = 0;
|
||||
|
||||
CesiumGltf::Sampler& sampler2 = model.samplers.emplace_back();
|
||||
sampler2.minFilter = CesiumGltf::Sampler::MinFilter::NEAREST;
|
||||
sampler2.magFilter = CesiumGltf::Sampler::MagFilter::NEAREST;
|
||||
sampler2.wrapS = CesiumGltf::Sampler::WrapS::MIRRORED_REPEAT;
|
||||
sampler2.wrapT = CesiumGltf::Sampler::WrapT::REPEAT;
|
||||
|
||||
CesiumGltf::Texture& texture2 = model.textures.emplace_back();
|
||||
texture2.source = 0;
|
||||
texture2.sampler = 1;
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded1 =
|
||||
loadTextureFromModelAnyThreadPart(model, model.textures[0], true);
|
||||
TestNotNull("pHalfLoaded1", pHalfLoaded1.Get());
|
||||
TestNotNull("pHalfLoaded1->pTexture", pHalfLoaded1->pTexture.get());
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded2 =
|
||||
loadTextureFromModelAnyThreadPart(model, model.textures[1], false);
|
||||
TestNotNull("pHalfLoaded2", pHalfLoaded2.Get());
|
||||
TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture1 =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded1.Get());
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture2 =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded2.Get());
|
||||
|
||||
CheckPixels(pRefCountedTexture1, true);
|
||||
CheckSRGB(pRefCountedTexture1, true);
|
||||
CheckAddress(
|
||||
pRefCountedTexture1,
|
||||
TextureAddress::TA_Wrap,
|
||||
TextureAddress::TA_Mirror);
|
||||
CheckFilter(pRefCountedTexture1, TextureFilter::TF_Default);
|
||||
CheckGroup(pRefCountedTexture1, TextureGroup::TEXTUREGROUP_World);
|
||||
|
||||
CheckPixels(pRefCountedTexture2, false);
|
||||
CheckSRGB(pRefCountedTexture2, false);
|
||||
CheckAddress(
|
||||
pRefCountedTexture2,
|
||||
TextureAddress::TA_Mirror,
|
||||
TextureAddress::TA_Wrap);
|
||||
CheckFilter(pRefCountedTexture2, TextureFilter::TF_Nearest);
|
||||
CheckGroup(pRefCountedTexture2, TextureGroup::TEXTUREGROUP_World);
|
||||
|
||||
TestEqual(
|
||||
"Textures share RHI resource",
|
||||
pRefCountedTexture1->getUnrealTexture()->GetResource()->GetTextureRHI(),
|
||||
pRefCountedTexture2->getUnrealTexture()
|
||||
->GetResource()
|
||||
->GetTextureRHI());
|
||||
});
|
||||
|
||||
It("Loading the same texture twice", [this]() {
|
||||
CesiumGltf::Model model;
|
||||
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset = pImageAsset;
|
||||
|
||||
CesiumGltf::Sampler& sampler = model.samplers.emplace_back();
|
||||
sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR;
|
||||
sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR;
|
||||
sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT;
|
||||
sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT;
|
||||
|
||||
CesiumGltf::Texture& texture = model.textures.emplace_back();
|
||||
texture.source = 0;
|
||||
texture.sampler = 0;
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded =
|
||||
loadTextureFromModelAnyThreadPart(model, texture, true);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, true);
|
||||
CheckSRGB(pRefCountedTexture, true);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Wrap,
|
||||
TextureAddress::TA_Mirror);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Default);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World);
|
||||
|
||||
// Copy the model and load the same texture again.
|
||||
// This time there's no more pixel data, so it's necessary to use the
|
||||
// previously-created texture.
|
||||
CesiumGltf::Model model2 = model;
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded2 =
|
||||
loadTextureFromModelAnyThreadPart(model2, model.textures[0], true);
|
||||
TestNotNull("pHalfLoaded2", pHalfLoaded2.Get());
|
||||
TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get());
|
||||
TestNull(
|
||||
"pHalfLoaded2->pTexture->getTextureResource()",
|
||||
pHalfLoaded2->pTexture->getTextureResource().Get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture2 =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded.Get());
|
||||
TestEqual("Same textures", pRefCountedTexture2, pRefCountedTexture);
|
||||
});
|
||||
|
||||
It("Loading the same texture twice from one model", [this]() {
|
||||
CesiumGltf::Model model;
|
||||
|
||||
CesiumGltf::Image& image = model.images.emplace_back();
|
||||
image.pAsset = pImageAsset;
|
||||
|
||||
CesiumGltf::Sampler& sampler = model.samplers.emplace_back();
|
||||
sampler.minFilter = CesiumGltf::Sampler::MinFilter::LINEAR_MIPMAP_LINEAR;
|
||||
sampler.magFilter = CesiumGltf::Sampler::MagFilter::LINEAR;
|
||||
sampler.wrapS = CesiumGltf::Sampler::WrapS::REPEAT;
|
||||
sampler.wrapT = CesiumGltf::Sampler::WrapT::MIRRORED_REPEAT;
|
||||
|
||||
CesiumGltf::Texture& texture = model.textures.emplace_back();
|
||||
texture.source = 0;
|
||||
texture.sampler = 0;
|
||||
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded =
|
||||
loadTextureFromModelAnyThreadPart(model, texture, true);
|
||||
TestNotNull("pHalfLoaded", pHalfLoaded.Get());
|
||||
TestNotNull("pHalfLoaded->pTexture", pHalfLoaded->pTexture.get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded.Get());
|
||||
CheckPixels(pRefCountedTexture, true);
|
||||
CheckSRGB(pRefCountedTexture, true);
|
||||
CheckAddress(
|
||||
pRefCountedTexture,
|
||||
TextureAddress::TA_Wrap,
|
||||
TextureAddress::TA_Mirror);
|
||||
CheckFilter(pRefCountedTexture, TextureFilter::TF_Default);
|
||||
CheckGroup(pRefCountedTexture, TextureGroup::TEXTUREGROUP_World);
|
||||
|
||||
// Load the same texture again.
|
||||
// This time there's no more pixel data, so it's necessary to use the
|
||||
// previously-created texture.
|
||||
TUniquePtr<LoadedTextureResult> pHalfLoaded2 =
|
||||
loadTextureFromModelAnyThreadPart(model, model.textures[0], true);
|
||||
TestNotNull("pHalfLoaded2", pHalfLoaded2.Get());
|
||||
TestNotNull("pHalfLoaded2->pTexture", pHalfLoaded2->pTexture.get());
|
||||
TestNull(
|
||||
"pHalfLoaded2->pTexture->getTextureResource()",
|
||||
pHalfLoaded2->pTexture->getTextureResource().Get());
|
||||
|
||||
IntrusivePointer<ReferenceCountedUnrealTexture> pRefCountedTexture2 =
|
||||
loadTextureGameThreadPart(model, pHalfLoaded.Get());
|
||||
TestEqual("Same textures", pRefCountedTexture2, pRefCountedTexture);
|
||||
});
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::CheckPixels(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
bool requireMips) {
|
||||
TestNotNull("pRefCountedTexture", pRefCountedTexture.get());
|
||||
TestNotNull(
|
||||
"pRefCountedTexture->getUnrealTexture()",
|
||||
pRefCountedTexture->getUnrealTexture().Get());
|
||||
|
||||
UTexture2D* pTexture = pRefCountedTexture->getUnrealTexture();
|
||||
TestNotNull("pTexture", pTexture);
|
||||
if (pTexture == nullptr)
|
||||
return;
|
||||
|
||||
FTextureResource* pResource = pTexture->GetResource();
|
||||
TestNotNull("pResource", pResource);
|
||||
if (pResource == nullptr)
|
||||
return;
|
||||
|
||||
TArray<FColor> readPixels;
|
||||
TArray<FColor> readPixelsMip1;
|
||||
|
||||
ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)
|
||||
([pResource, &readPixels, &readPixelsMip1](
|
||||
FRHICommandListImmediate& RHICmdList) {
|
||||
FRHITexture* pRHITexture = pResource->GetTextureRHI();
|
||||
if (pRHITexture == nullptr)
|
||||
return;
|
||||
|
||||
FReadSurfaceDataFlags flags{};
|
||||
flags.SetLinearToGamma(false);
|
||||
RHICmdList
|
||||
.ReadSurfaceData(pRHITexture, FIntRect(0, 0, 3, 2), readPixels, flags);
|
||||
|
||||
if (pRHITexture->GetNumMips() > 1) {
|
||||
flags.SetMip(1);
|
||||
RHICmdList.ReadSurfaceData(
|
||||
pRHITexture,
|
||||
FIntRect(0, 0, 1, 1),
|
||||
readPixelsMip1,
|
||||
flags);
|
||||
}
|
||||
});
|
||||
FlushRenderingCommands();
|
||||
|
||||
TestEqual("read buffer size", readPixels.Num() * 4, originalPixels.size());
|
||||
for (size_t i = 0; i < readPixels.Num(); ++i) {
|
||||
TestEqual("pixel-red", readPixels[i].R, originalPixels[i * 4]);
|
||||
TestEqual("pixel-green", readPixels[i].G, originalPixels[i * 4 + 1]);
|
||||
TestEqual("pixel-blue", readPixels[i].B, originalPixels[i * 4 + 2]);
|
||||
TestEqual("pixel-alpha", readPixels[i].A, originalPixels[i * 4 + 3]);
|
||||
}
|
||||
|
||||
if (requireMips) {
|
||||
TestTrue("Has Mips", !readPixelsMip1.IsEmpty());
|
||||
}
|
||||
|
||||
if (!readPixelsMip1.IsEmpty()) {
|
||||
std::vector<uint8_t>& pixelsToMatch = originalMipPixels.empty()
|
||||
? expectedMipPixelsIfGenerated
|
||||
: originalMipPixels;
|
||||
|
||||
TestEqual(
|
||||
"read buffer size",
|
||||
readPixelsMip1.Num() * 4,
|
||||
pixelsToMatch.size());
|
||||
for (size_t i = 0;
|
||||
i < readPixelsMip1.Num() && (i * 4 + 3) < pixelsToMatch.size();
|
||||
++i) {
|
||||
TestEqual("mip pixel-red", readPixelsMip1[i].R, pixelsToMatch[i * 4]);
|
||||
TestEqual(
|
||||
"mip pixel-green",
|
||||
readPixelsMip1[i].G,
|
||||
pixelsToMatch[i * 4 + 1]);
|
||||
TestEqual(
|
||||
"mip pixel-blue",
|
||||
readPixelsMip1[i].B,
|
||||
pixelsToMatch[i * 4 + 2]);
|
||||
TestEqual(
|
||||
"mip pixel-alpha",
|
||||
readPixelsMip1[i].A,
|
||||
pixelsToMatch[i * 4 + 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::CheckSRGB(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
bool expectedSRGB) {
|
||||
TestNotNull("pRefCountedTexture", pRefCountedTexture.get());
|
||||
if (!pRefCountedTexture)
|
||||
return;
|
||||
|
||||
UTexture2D* pTexture = pRefCountedTexture->getUnrealTexture();
|
||||
TestNotNull("pTexture", pTexture);
|
||||
if (!pTexture)
|
||||
return;
|
||||
|
||||
TestEqual("SRGB", pTexture->SRGB, expectedSRGB);
|
||||
|
||||
FTextureResource* pResource = pTexture->GetResource();
|
||||
TestNotNull("pResource", pResource);
|
||||
if (!pResource)
|
||||
return;
|
||||
|
||||
TestEqual("RHI sRGB", pResource->bSRGB, expectedSRGB);
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::CheckAddress(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureAddress expectedAddressX,
|
||||
TextureAddress expectedAddressY) {
|
||||
TestNotNull("pRefCountedTexture", pRefCountedTexture.get());
|
||||
if (!pRefCountedTexture)
|
||||
return;
|
||||
|
||||
UTexture2D* pTexture = pRefCountedTexture->getUnrealTexture();
|
||||
TestNotNull("pTexture", pTexture);
|
||||
if (!pTexture)
|
||||
return;
|
||||
|
||||
TestEqual("AddressX", pTexture->AddressX, expectedAddressX);
|
||||
TestEqual("AddressY", pTexture->AddressY, expectedAddressY);
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::CheckFilter(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureFilter expectedFilter) {
|
||||
TestNotNull("pRefCountedTexture", pRefCountedTexture.get());
|
||||
if (!pRefCountedTexture)
|
||||
return;
|
||||
|
||||
UTexture2D* pTexture = pRefCountedTexture->getUnrealTexture();
|
||||
TestNotNull("pTexture", pTexture);
|
||||
if (!pTexture)
|
||||
return;
|
||||
|
||||
TestEqual("Filter", pTexture->Filter, expectedFilter);
|
||||
}
|
||||
|
||||
void CesiumTextureUtilitySpec::CheckGroup(
|
||||
const IntrusivePointer<ReferenceCountedUnrealTexture>& pRefCountedTexture,
|
||||
TextureGroup expectedGroup) {
|
||||
TestNotNull("pRefCountedTexture", pRefCountedTexture.get());
|
||||
if (!pRefCountedTexture)
|
||||
return;
|
||||
|
||||
UTexture2D* pTexture = pRefCountedTexture->getUnrealTexture();
|
||||
TestNotNull("pTexture", pTexture);
|
||||
if (!pTexture)
|
||||
return;
|
||||
|
||||
TestEqual("LODGroup", pTexture->LODGroup, expectedGroup);
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "GeoTransforms.h"
|
||||
#include "CesiumGeospatial/Ellipsoid.h"
|
||||
#include "CesiumUtility/Math.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
using namespace CesiumGeospatial;
|
||||
using namespace CesiumUtility;
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FGeoTransformsSpec,
|
||||
"Cesium.Unit.GeoTransforms",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
END_DEFINE_SPEC(FGeoTransformsSpec)
|
||||
|
||||
void FGeoTransformsSpec::Define() {
|
||||
Describe("TransformLongitudeLatitudeHeightToUnreal", [this]() {
|
||||
It("returns the origin when given the origin LLH", [this]() {
|
||||
GeoTransforms geotransforms{};
|
||||
glm::dvec3 center = geotransforms.TransformLongitudeLatitudeHeightToEcef(
|
||||
glm::dvec3(12.0, 23.0, 1000.0));
|
||||
geotransforms.setCenter(center);
|
||||
glm::dvec3 ue = geotransforms.TransformLongitudeLatitudeHeightToUnreal(
|
||||
glm::dvec3(0.0),
|
||||
glm::dvec3(12.0, 23.0, 1000.0));
|
||||
TestEqual("is at the origin", ue, glm::dvec3(0.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,351 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
#include "CesiumFlyToComponent.h"
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumGlobeAnchorComponent.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "CesiumWgs84Ellipsoid.h"
|
||||
#include "Editor.h"
|
||||
#include "Engine/World.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FGlobeAwareDefaultPawnSpec,
|
||||
"Cesium.Unit.GlobeAwareDefaultPawn",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
FDelegateHandle subscriptionPostPIEStarted;
|
||||
|
||||
END_DEFINE_SPEC(FGlobeAwareDefaultPawnSpec)
|
||||
|
||||
void FGlobeAwareDefaultPawnSpec::Define() {
|
||||
const FVector philadelphiaEcef =
|
||||
FVector(1253264.69280105, -4732469.91065521, 4075112.40412297);
|
||||
|
||||
// The antipodal position from the philadelphia coordinates above
|
||||
const FVector philadelphiaAntipodeEcef =
|
||||
FVector(-1253369.920224856, 4732412.7444064, -4075146.2160252854);
|
||||
|
||||
const FVector tokyoEcef =
|
||||
FVector(-3960158.65587452, 3352568.87555906, 3697235.23506459);
|
||||
|
||||
Describe(
|
||||
"should not spike altitude when very close to final destination",
|
||||
[this]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
UWorld* pWorld = FAutomationEditorCommonUtils::CreateNewMap();
|
||||
|
||||
AGlobeAwareDefaultPawn* pPawn =
|
||||
pWorld->SpawnActor<AGlobeAwareDefaultPawn>();
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
Cast<UCesiumFlyToComponent>(pPawn->AddComponentByClass(
|
||||
UCesiumFlyToComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
pFlyTo->RotationToUse =
|
||||
ECesiumFlyToRotation::ControlRotationInEastSouthUp;
|
||||
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
It("", [this]() {
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
|
||||
TActorIterator<AGlobeAwareDefaultPawn> it(pWorld);
|
||||
AGlobeAwareDefaultPawn* pPawn = *it;
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
pPawn->FindComponentByClass<UCesiumFlyToComponent>();
|
||||
TestNotNull("pFlyTo", pFlyTo);
|
||||
pFlyTo->Duration = 5.0f;
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pPawn->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
|
||||
// Start flying somewhere else
|
||||
pFlyTo->FlyToLocationLongitudeLatitudeHeight(
|
||||
FVector(25.0, 10.0, 100.0),
|
||||
0.0,
|
||||
0.0,
|
||||
false);
|
||||
|
||||
// Tick almost to the end
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
4.9999f,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
// The height should be close to the final height.
|
||||
FVector llh = pGlobeAnchor->GetLongitudeLatitudeHeight();
|
||||
TestTrue(
|
||||
"Height is close to final height",
|
||||
FMath::IsNearlyEqual(llh.Z, 100.0, 10.0));
|
||||
|
||||
pPawn->Destroy();
|
||||
});
|
||||
});
|
||||
|
||||
Describe(
|
||||
"should interpolate between positions and rotations correctly",
|
||||
[this, tokyoEcef, philadelphiaEcef, philadelphiaAntipodeEcef]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
UWorld* pWorld = FAutomationEditorCommonUtils::CreateNewMap();
|
||||
|
||||
AGlobeAwareDefaultPawn* pPawn =
|
||||
pWorld->SpawnActor<AGlobeAwareDefaultPawn>();
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
Cast<UCesiumFlyToComponent>(pPawn->AddComponentByClass(
|
||||
UCesiumFlyToComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
pFlyTo->RotationToUse =
|
||||
ECesiumFlyToRotation::ControlRotationInEastSouthUp;
|
||||
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
It("should match the beginning and ending points of the fly-to",
|
||||
[this]() {
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
|
||||
TActorIterator<AGlobeAwareDefaultPawn> it(pWorld);
|
||||
AGlobeAwareDefaultPawn* pPawn = *it;
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
pPawn->FindComponentByClass<UCesiumFlyToComponent>();
|
||||
TestNotNull("pFlyTo", pFlyTo);
|
||||
pFlyTo->Duration = 5.0f;
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pPawn->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
|
||||
pGlobeAnchor->MoveToLongitudeLatitudeHeight(
|
||||
FVector(25.0, 10.0, 100.0));
|
||||
|
||||
// Start flying somewhere else
|
||||
pFlyTo->FlyToLocationLongitudeLatitudeHeight(
|
||||
FVector(25.0, 25.0, 100.0),
|
||||
0.0,
|
||||
0.0,
|
||||
false);
|
||||
|
||||
TestEqual(
|
||||
"Location is the same as the start point",
|
||||
pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
FVector(25.0, 10.0, 100.0));
|
||||
|
||||
// Tick to the end
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
5.0f,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
TestEqual(
|
||||
"Location is the same as the end point",
|
||||
pGlobeAnchor->GetLongitudeLatitudeHeight(),
|
||||
FVector(25.0, 25.0, 100.0));
|
||||
|
||||
pPawn->Destroy();
|
||||
});
|
||||
It("should correctly compute the midpoint of the flight",
|
||||
[this, tokyoEcef, philadelphiaEcef]() {
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
|
||||
TActorIterator<AGlobeAwareDefaultPawn> it(pWorld);
|
||||
AGlobeAwareDefaultPawn* pPawn = *it;
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
pPawn->FindComponentByClass<UCesiumFlyToComponent>();
|
||||
TestNotNull("pFlyTo", pFlyTo);
|
||||
pFlyTo->Duration = 5.0f;
|
||||
pFlyTo->HeightPercentageCurve = nullptr;
|
||||
pFlyTo->MaximumHeightByDistanceCurve = nullptr;
|
||||
pFlyTo->ProgressCurve = nullptr;
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pPawn->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
|
||||
pGlobeAnchor->MoveToEarthCenteredEarthFixedPosition(
|
||||
philadelphiaEcef);
|
||||
pFlyTo->FlyToLocationEarthCenteredEarthFixed(tokyoEcef, 0, 0, 0);
|
||||
|
||||
// Tick half way through
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
2.5f,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
FVector expectedResult = FVector(
|
||||
-2062499.3622640674,
|
||||
-1052346.4221710551,
|
||||
5923430.4378960524);
|
||||
|
||||
// calculate relative epsilon
|
||||
const float epsilon = FMath::Max3(
|
||||
expectedResult.X,
|
||||
expectedResult.Y,
|
||||
expectedResult.Z) *
|
||||
1e-6;
|
||||
|
||||
TestEqual(
|
||||
"Midpoint location is correct",
|
||||
pGlobeAnchor->GetEarthCenteredEarthFixedPosition(),
|
||||
expectedResult,
|
||||
epsilon);
|
||||
|
||||
pPawn->Destroy();
|
||||
});
|
||||
It("should match the start and end rotations",
|
||||
[this, tokyoEcef, philadelphiaEcef]() {
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
|
||||
TActorIterator<AGlobeAwareDefaultPawn> it(pWorld);
|
||||
AGlobeAwareDefaultPawn* pPawn = *it;
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
pPawn->FindComponentByClass<UCesiumFlyToComponent>();
|
||||
TestNotNull("pFlyTo", pFlyTo);
|
||||
pFlyTo->Duration = 5.0f;
|
||||
pFlyTo->HeightPercentageCurve = nullptr;
|
||||
pFlyTo->MaximumHeightByDistanceCurve = nullptr;
|
||||
pFlyTo->ProgressCurve = nullptr;
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pPawn->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
|
||||
FQuat sourceRotation = FRotator(0, 0, 0).Quaternion();
|
||||
FQuat targetRotation = FRotator(45, 180, 0).Quaternion();
|
||||
FQuat midpointRotation =
|
||||
FQuat::Slerp(sourceRotation, targetRotation, 0.5);
|
||||
|
||||
pGlobeAnchor->MoveToEarthCenteredEarthFixedPosition(
|
||||
philadelphiaEcef);
|
||||
pGlobeAnchor->SetEastSouthUpRotation(sourceRotation);
|
||||
pFlyTo->FlyToLocationEarthCenteredEarthFixed(
|
||||
tokyoEcef,
|
||||
180,
|
||||
45,
|
||||
false);
|
||||
|
||||
TestTrue(
|
||||
"Start rotation is correct",
|
||||
pPawn->Controller->GetControlRotation().Quaternion().Equals(
|
||||
sourceRotation,
|
||||
CesiumUtility::Math::Epsilon4));
|
||||
|
||||
// Tick half way through
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
2.5f,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
TestTrue(
|
||||
"Midpoint rotation is correct",
|
||||
pPawn->Controller->GetControlRotation().Quaternion().Equals(
|
||||
midpointRotation,
|
||||
CesiumUtility::Math::Epsilon4));
|
||||
|
||||
FVector currentEastSouthRotationEuler =
|
||||
pGlobeAnchor->GetEastSouthUpRotation().Euler();
|
||||
FVector midpointEuler = midpointRotation.Euler();
|
||||
|
||||
// Tick to the end
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
2.5f,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
TestTrue(
|
||||
"End location is correct",
|
||||
pPawn->Controller->GetControlRotation().Quaternion().Equals(
|
||||
targetRotation,
|
||||
CesiumUtility::Math::Epsilon4));
|
||||
|
||||
FVector endEastSouthRotationEuler =
|
||||
pGlobeAnchor->GetEastSouthUpRotation().Euler();
|
||||
FVector endpointEuler = targetRotation.Euler();
|
||||
|
||||
pPawn->Destroy();
|
||||
});
|
||||
It("shouldn't fly through the earth",
|
||||
[this, philadelphiaEcef, philadelphiaAntipodeEcef]() {
|
||||
UWorld* pWorld = GEditor->PlayWorld;
|
||||
|
||||
TActorIterator<AGlobeAwareDefaultPawn> it(pWorld);
|
||||
AGlobeAwareDefaultPawn* pPawn = *it;
|
||||
UCesiumFlyToComponent* pFlyTo =
|
||||
pPawn->FindComponentByClass<UCesiumFlyToComponent>();
|
||||
TestNotNull("pFlyTo", pFlyTo);
|
||||
|
||||
pFlyTo->Duration = 5.0f;
|
||||
pFlyTo->HeightPercentageCurve = nullptr;
|
||||
pFlyTo->MaximumHeightByDistanceCurve = nullptr;
|
||||
pFlyTo->ProgressCurve = nullptr;
|
||||
|
||||
UCesiumGlobeAnchorComponent* pGlobeAnchor =
|
||||
pPawn->FindComponentByClass<UCesiumGlobeAnchorComponent>();
|
||||
TestNotNull("pGlobeAnchor", pGlobeAnchor);
|
||||
|
||||
pGlobeAnchor->MoveToEarthCenteredEarthFixedPosition(
|
||||
philadelphiaEcef);
|
||||
pFlyTo->FlyToLocationEarthCenteredEarthFixed(
|
||||
philadelphiaAntipodeEcef,
|
||||
0,
|
||||
0,
|
||||
false);
|
||||
|
||||
const int steps = 100;
|
||||
const double timeStep = 5.0 / (double)steps;
|
||||
|
||||
double time = 0;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
Cast<UActorComponent>(pFlyTo)->TickComponent(
|
||||
(float)timeStep,
|
||||
ELevelTick::LEVELTICK_All,
|
||||
nullptr);
|
||||
|
||||
const FVector cartographic = UCesiumWgs84Ellipsoid::
|
||||
EarthCenteredEarthFixedToLongitudeLatitudeHeight(
|
||||
pGlobeAnchor->GetEarthCenteredEarthFixedPosition());
|
||||
|
||||
TestTrue("height above zero", cartographic.Z > 0);
|
||||
|
||||
time += timeStep;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,315 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumLoadTestCore.h"
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "Cesium3DTileset.h"
|
||||
#include "CesiumAsync/ICacheDatabase.h"
|
||||
#include "CesiumRuntime.h"
|
||||
#include "CesiumSunSky.h"
|
||||
|
||||
using namespace Cesium;
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGooglePompidou,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Pompidou",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleChrysler,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Chrysler",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleChryslerWarm,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Chrysler, warm cache",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleGuggenheim,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Guggenheim",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleDeathValley,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT DeathValley",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleDeathValleyWarm,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT DeathValley, warm cache",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleTokyo,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Tokyo",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleGoogleplex,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Googleplex",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetGoogleChryslerVaryMaxTileLoads,
|
||||
"Cesium.Performance.Tileset Loading.Google P3DT Chrysler, vary max tile loads",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
#define TEST_SCREEN_WIDTH 1280
|
||||
#define TEST_SCREEN_HEIGHT 720
|
||||
|
||||
void googleSetupRefreshTilesets(
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
context.refreshTilesets();
|
||||
}
|
||||
|
||||
void googleSetupClearCache(
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
}
|
||||
|
||||
void setupForPompidou(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(2.352200, 48.860600, 200),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-20.0, -90.0, 0.0),
|
||||
60.0f);
|
||||
|
||||
context.sunSky->TimeZone = 2.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(TEXT("Center Pompidou, Paris, France"));
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
void setupForChrysler(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-73.9752624659, 40.74697185903, 307.38),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-15.0, -90.0, 0.0),
|
||||
60.0f);
|
||||
|
||||
context.sunSky->TimeZone = -4.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(TEXT("Chrysler Building, NYC"));
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
void setupForGuggenheim(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-2.937, 43.2685, 150),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-15.0, 0.0, 0.0),
|
||||
60.0f);
|
||||
|
||||
context.sunSky->TimeZone = 2.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(TEXT("Guggenheim Museum, Bilbao, Spain"));
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
void setupForDeathValley(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-116.812278, 36.42, 300),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(0, 0.0, 0.0),
|
||||
60.0f);
|
||||
|
||||
context.sunSky->TimeZone = -7.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(
|
||||
TEXT("Zabriskie Point, Death Valley National Park, California"));
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
void setupForTokyo(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(139.7563178458, 35.652798383944, 525.62),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-15, -150, 0.0),
|
||||
60.0f);
|
||||
|
||||
context.sunSky->TimeZone = 9.0f;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(TEXT("Tokyo Tower, Tokyo, Japan"));
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
void setupForGoogleplex(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-122.083969, 37.424492, 142.859116),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-25, 95, 0),
|
||||
90.0f);
|
||||
|
||||
ACesium3DTileset* tileset = context.world->SpawnActor<ACesium3DTileset>();
|
||||
tileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
tileset->SetIonAssetID(2275207);
|
||||
tileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
tileset->SetActorLabel(TEXT("Google Photorealistic 3D Tiles"));
|
||||
|
||||
context.tilesets.push_back(tileset);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGooglePompidou::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForPompidou,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleChrysler::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForChrysler,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleChryslerWarm::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(
|
||||
TestPass{"Warm Cache", googleSetupRefreshTilesets, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForChrysler,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleGuggenheim::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForGuggenheim,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleDeathValley::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForDeathValley,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleDeathValleyWarm::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(
|
||||
TestPass{"Warm Cache", googleSetupRefreshTilesets, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForDeathValley,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleTokyo::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForTokyo,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleGoogleplex::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", googleSetupClearCache, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForGoogleplex,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
bool FLoadTilesetGoogleChryslerVaryMaxTileLoads::RunTest(
|
||||
const FString& Parameters) {
|
||||
auto setupPass = [this](
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
|
||||
int maxLoadsTarget = swl::get<int>(parameter);
|
||||
context.setMaximumSimultaneousTileLoads(maxLoadsTarget);
|
||||
|
||||
context.refreshTilesets();
|
||||
};
|
||||
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Default", googleSetupClearCache, NULL});
|
||||
testPasses.push_back(TestPass{"12", setupPass, NULL, 12});
|
||||
testPasses.push_back(TestPass{"16", setupPass, NULL, 16});
|
||||
testPasses.push_back(TestPass{"20", setupPass, NULL, 20});
|
||||
testPasses.push_back(TestPass{"24", setupPass, NULL, 24});
|
||||
testPasses.push_back(TestPass{"28", setupPass, NULL, 28});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForChrysler,
|
||||
testPasses,
|
||||
TEST_SCREEN_WIDTH,
|
||||
TEST_SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,39 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CesiumSampleHeightMostDetailedAsyncAction.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include <functional>
|
||||
#include "SampleHeightCallbackReceiver.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class USampleHeightCallbackReceiver : public UObject {
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
using TFunction = std::function<
|
||||
void(const TArray<FCesiumSampleHeightResult>&, const TArray<FString>&)>;
|
||||
|
||||
static void
|
||||
Bind(FCesiumSampleHeightMostDetailedComplete& delegate, TFunction callback) {
|
||||
USampleHeightCallbackReceiver* p =
|
||||
NewObject<USampleHeightCallbackReceiver>();
|
||||
p->_callback = callback;
|
||||
p->AddToRoot();
|
||||
|
||||
delegate.AddUniqueDynamic(p, &USampleHeightCallbackReceiver::Receiver);
|
||||
}
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void Receiver(
|
||||
const TArray<FCesiumSampleHeightResult>& Result,
|
||||
const TArray<FString>& Warnings) {
|
||||
this->_callback(Result, Warnings);
|
||||
this->RemoveFromRoot();
|
||||
}
|
||||
|
||||
TFunction _callback;
|
||||
};
|
||||
@ -0,0 +1,484 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumLoadTestCore.h"
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "CesiumAsync/ICacheDatabase.h"
|
||||
#include "CesiumGltfComponent.h"
|
||||
#include "CesiumIonRasterOverlay.h"
|
||||
#include "CesiumRuntime.h"
|
||||
#include "CesiumSunSky.h"
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
|
||||
using namespace Cesium;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
void setupDenverHillsCesiumWorldTerrain(SceneGenerationContext& context);
|
||||
void setupDenverHillsGoogle(SceneGenerationContext& context);
|
||||
bool RunSingleQueryTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> setup);
|
||||
bool RunMultipleQueryTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> setup);
|
||||
} // namespace
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FSampleHeightMostDetailedCesiumWorldTerrainSingle,
|
||||
"Cesium.Performance.SampleHeightMostDetailed.Single query against Cesium World Terrain",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FSampleHeightMostDetailedCesiumWorldTerrainMultiple,
|
||||
"Cesium.Performance.SampleHeightMostDetailed.Multiple queries against Cesium World Terrain",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FSampleHeightMostDetailedGoogleSingle,
|
||||
"Cesium.Performance.SampleHeightMostDetailed.Single query against Google Photorealistic 3D Tiles",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FSampleHeightMostDetailedGoogleMultiple,
|
||||
"Cesium.Performance.SampleHeightMostDetailed.Multiple queries against Google Photorealistic 3D Tiles",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
bool FSampleHeightMostDetailedCesiumWorldTerrainSingle::RunTest(
|
||||
const FString& Parameters) {
|
||||
return RunSingleQueryTest(
|
||||
this->GetBeautifiedTestName(),
|
||||
setupDenverHillsCesiumWorldTerrain);
|
||||
}
|
||||
|
||||
bool FSampleHeightMostDetailedCesiumWorldTerrainMultiple::RunTest(
|
||||
const FString& Parameters) {
|
||||
return RunMultipleQueryTest(
|
||||
this->GetBeautifiedTestName(),
|
||||
setupDenverHillsCesiumWorldTerrain);
|
||||
}
|
||||
|
||||
bool FSampleHeightMostDetailedGoogleSingle::RunTest(const FString& Parameters) {
|
||||
return RunSingleQueryTest(
|
||||
this->GetBeautifiedTestName(),
|
||||
setupDenverHillsGoogle);
|
||||
}
|
||||
|
||||
bool FSampleHeightMostDetailedGoogleMultiple::RunTest(
|
||||
const FString& Parameters) {
|
||||
return RunMultipleQueryTest(
|
||||
this->GetBeautifiedTestName(),
|
||||
setupDenverHillsGoogle);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Our test model path
|
||||
//
|
||||
// Uses a simple cube, but to see trees instead, download 'temperate Vegetation:
|
||||
// Spruce Forest' from the Unreal Engine Marketplace then use the following
|
||||
// path...
|
||||
// "'/Game/PN_interactiveSpruceForest/Meshes/full/low/spruce_full_01_low.spruce_full_01_low'"
|
||||
FString terrainQueryTestModelPath(
|
||||
TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));
|
||||
|
||||
void setupDenverHillsCesiumWorldTerrain(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-105.238887, 39.756177, 1887.175525),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-7, -226, -5),
|
||||
90.0f);
|
||||
|
||||
// Add Cesium World Terrain
|
||||
ACesium3DTileset* worldTerrainTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
worldTerrainTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
worldTerrainTileset->SetIonAssetID(1);
|
||||
worldTerrainTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
worldTerrainTileset->SetActorLabel(TEXT("Cesium World Terrain"));
|
||||
worldTerrainTileset->MaximumCachedBytes = 0;
|
||||
|
||||
context.tilesets.push_back(worldTerrainTileset);
|
||||
}
|
||||
|
||||
void setupDenverHillsGoogle(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-105.238887, 39.756177, 1887.175525),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-7, -226, -5),
|
||||
90.0f);
|
||||
|
||||
// Add Cesium World Terrain
|
||||
ACesium3DTileset* googleTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
googleTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
googleTileset->SetIonAssetID(2275207);
|
||||
googleTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
googleTileset->SetActorLabel(TEXT("Google Photorealistic 3D Tiles"));
|
||||
googleTileset->MaximumCachedBytes = 0;
|
||||
|
||||
context.tilesets.push_back(googleTileset);
|
||||
}
|
||||
|
||||
bool RunSingleQueryTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> setup) {
|
||||
auto clearCache = [](SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
};
|
||||
|
||||
struct TestResults {
|
||||
std::atomic<bool> queryFinished = false;
|
||||
TArray<FCesiumSampleHeightResult> heightResults;
|
||||
TArray<FString> warnings;
|
||||
};
|
||||
|
||||
static TestResults testResults;
|
||||
|
||||
auto issueQueries = [&testResults = testResults](
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
// Test right at camera position
|
||||
double testLongitude = -105.257595;
|
||||
double testLatitude = 39.743103;
|
||||
|
||||
// Make a grid of test points
|
||||
const size_t gridRowCount = 20;
|
||||
const size_t gridColumnCount = 20;
|
||||
double cartographicSpacing = 0.001;
|
||||
|
||||
TArray<FVector> queryInput;
|
||||
|
||||
for (size_t rowIndex = 0; rowIndex < gridRowCount; ++rowIndex) {
|
||||
double rowLatitude = testLatitude + (cartographicSpacing * rowIndex);
|
||||
|
||||
for (size_t columnIndex = 0; columnIndex < gridColumnCount;
|
||||
++columnIndex) {
|
||||
FVector queryInstance = {
|
||||
testLongitude + (cartographicSpacing * columnIndex),
|
||||
rowLatitude,
|
||||
0.0};
|
||||
|
||||
queryInput.Add(queryInstance);
|
||||
}
|
||||
}
|
||||
|
||||
ACesium3DTileset* tileset = context.tilesets[0];
|
||||
|
||||
tileset->SampleHeightMostDetailed(
|
||||
queryInput,
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[&testResults](
|
||||
ACesium3DTileset* Tileset,
|
||||
const TArray<FCesiumSampleHeightResult>& Results,
|
||||
const TArray<FString>& Warnings) {
|
||||
testResults.heightResults = Results;
|
||||
testResults.warnings = Warnings;
|
||||
testResults.queryFinished = true;
|
||||
}));
|
||||
};
|
||||
|
||||
auto waitForQueries = [&testResults = testResults](
|
||||
SceneGenerationContext& creationContext,
|
||||
SceneGenerationContext& playContext,
|
||||
TestPass::TestingParameter parameter) {
|
||||
return (bool)testResults.queryFinished;
|
||||
};
|
||||
|
||||
auto showResults = [&testResults = testResults](
|
||||
SceneGenerationContext& creationContext,
|
||||
SceneGenerationContext& playContext,
|
||||
TestPass::TestingParameter parameter) {
|
||||
// Turn on the editor tileset updates so we can see what we loaded
|
||||
creationContext.setSuspendUpdate(false);
|
||||
|
||||
// Place an object on the ground to verify position
|
||||
UWorld* World = creationContext.world;
|
||||
|
||||
UStaticMesh* testMesh =
|
||||
LoadObject<UStaticMesh>(nullptr, *terrainQueryTestModelPath);
|
||||
|
||||
ACesium3DTileset* tileset = playContext.tilesets[0];
|
||||
Cesium3DTilesSelection::Tileset* nativeTileset = tileset->GetTileset();
|
||||
|
||||
// Log any warnings
|
||||
for (const FString& warning : testResults.warnings) {
|
||||
UE_LOG(LogCesium, Warning, TEXT("Height query warning: %s"), *warning);
|
||||
}
|
||||
|
||||
int32 resultCount = testResults.heightResults.Num();
|
||||
for (int32 resultIndex = 0; resultIndex < resultCount; ++resultIndex) {
|
||||
const FVector& queryLongitudeLatitudeHeight =
|
||||
testResults.heightResults[resultIndex].LongitudeLatitudeHeight;
|
||||
|
||||
if (!testResults.heightResults[resultIndex].SampleSuccess) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Error,
|
||||
TEXT("The height at (%f,%f) was not sampled successfully."),
|
||||
queryLongitudeLatitudeHeight.X,
|
||||
queryLongitudeLatitudeHeight.Y);
|
||||
continue;
|
||||
}
|
||||
|
||||
FVector unrealPosition =
|
||||
tileset->ResolveGeoreference()
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
queryLongitudeLatitudeHeight);
|
||||
|
||||
// Now bring the hit point to unreal world coordinates
|
||||
FVector unrealWorldPosition =
|
||||
tileset->GetActorTransform().TransformFVector4(unrealPosition);
|
||||
|
||||
AStaticMeshActor* staticMeshActor = World->SpawnActor<AStaticMeshActor>();
|
||||
staticMeshActor->GetStaticMeshComponent()->SetStaticMesh(testMesh);
|
||||
staticMeshActor->SetActorLocation(unrealWorldPosition);
|
||||
staticMeshActor->SetActorScale3D(FVector(7, 7, 7));
|
||||
staticMeshActor->SetActorLabel(
|
||||
FString::Printf(TEXT("Hit %d"), resultIndex));
|
||||
staticMeshActor->SetFolderPath("/QueryResults");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(
|
||||
TestPass{"Load terrain from cold cache", clearCache, nullptr});
|
||||
testPasses.push_back(
|
||||
TestPass{"Issue height queries and wait", issueQueries, waitForQueries});
|
||||
testPasses.push_back(
|
||||
TestPass{"Populate scene with results", nullptr, showResults});
|
||||
|
||||
return RunLoadTest(testName, setup, testPasses, 1280, 768);
|
||||
}
|
||||
|
||||
bool RunMultipleQueryTest(
|
||||
const FString& testName,
|
||||
std::function<void(SceneGenerationContext&)> setup) {
|
||||
struct QueryObject {
|
||||
FVector coordinateDegrees;
|
||||
|
||||
AStaticMeshActor* creationMeshActor = nullptr;
|
||||
AStaticMeshActor* playMeshActor = nullptr;
|
||||
|
||||
bool queryFinished = false;
|
||||
};
|
||||
|
||||
struct TestProcess {
|
||||
std::vector<QueryObject> queryObjects;
|
||||
};
|
||||
|
||||
auto pProcess = std::make_shared<TestProcess>();
|
||||
|
||||
//
|
||||
// Setup all object positions that will receive queries
|
||||
//
|
||||
// Test right at camera position
|
||||
double testLongitude = -105.257595;
|
||||
double testLatitude = 39.743103;
|
||||
|
||||
// Make a grid of test points
|
||||
const size_t gridRowCount = 20;
|
||||
const size_t gridColumnCount = 20;
|
||||
double cartographicSpacing = 0.001;
|
||||
|
||||
for (size_t rowIndex = 0; rowIndex < gridRowCount; ++rowIndex) {
|
||||
double rowLatitude = testLatitude + (cartographicSpacing * rowIndex);
|
||||
|
||||
for (size_t columnIndex = 0; columnIndex < gridColumnCount; ++columnIndex) {
|
||||
FVector position(
|
||||
testLongitude + (cartographicSpacing * columnIndex),
|
||||
rowLatitude,
|
||||
2190.0);
|
||||
|
||||
QueryObject newQueryObject = {position};
|
||||
|
||||
pProcess->queryObjects.push_back(std::move(newQueryObject));
|
||||
}
|
||||
}
|
||||
|
||||
auto clearCache = [](SceneGenerationContext&, TestPass::TestingParameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
};
|
||||
|
||||
auto addTestObjects = [pProcess](
|
||||
SceneGenerationContext& creationContext,
|
||||
SceneGenerationContext& playContext,
|
||||
TestPass::TestingParameter) {
|
||||
// Place an object on the ground to verify position
|
||||
UWorld* creationWorld = creationContext.world;
|
||||
UWorld* playWorld = playContext.world;
|
||||
|
||||
UStaticMesh* testMesh =
|
||||
LoadObject<UStaticMesh>(nullptr, *terrainQueryTestModelPath);
|
||||
|
||||
ACesium3DTileset* tileset = playContext.tilesets[0];
|
||||
Cesium3DTilesSelection::Tileset* nativeTileset = tileset->GetTileset();
|
||||
|
||||
for (size_t queryIndex = 0; queryIndex < pProcess->queryObjects.size();
|
||||
++queryIndex) {
|
||||
QueryObject& queryObject = pProcess->queryObjects[queryIndex];
|
||||
|
||||
FVector unrealPosition =
|
||||
tileset->ResolveGeoreference()
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
queryObject.coordinateDegrees);
|
||||
|
||||
// Now bring the hit point to unreal world coordinates
|
||||
FVector unrealWorldPosition =
|
||||
tileset->GetActorTransform().TransformFVector4(unrealPosition);
|
||||
|
||||
{
|
||||
AStaticMeshActor* staticMeshActor =
|
||||
creationWorld->SpawnActor<AStaticMeshActor>();
|
||||
staticMeshActor->SetMobility(EComponentMobility::Movable);
|
||||
staticMeshActor->GetStaticMeshComponent()->SetStaticMesh(testMesh);
|
||||
staticMeshActor->SetActorLocation(unrealWorldPosition);
|
||||
staticMeshActor->SetActorScale3D(FVector(7, 7, 7));
|
||||
staticMeshActor->SetActorLabel(
|
||||
FString::Printf(TEXT("Hit %d"), queryIndex));
|
||||
staticMeshActor->SetFolderPath("/QueryResults");
|
||||
queryObject.creationMeshActor = staticMeshActor;
|
||||
}
|
||||
|
||||
{
|
||||
AStaticMeshActor* staticMeshActor =
|
||||
playWorld->SpawnActor<AStaticMeshActor>();
|
||||
staticMeshActor->SetMobility(EComponentMobility::Movable);
|
||||
staticMeshActor->GetStaticMeshComponent()->SetStaticMesh(testMesh);
|
||||
staticMeshActor->SetActorLocation(unrealWorldPosition);
|
||||
staticMeshActor->SetActorScale3D(FVector(7, 7, 7));
|
||||
staticMeshActor->SetActorLabel(
|
||||
FString::Printf(TEXT("Hit %d"), queryIndex));
|
||||
staticMeshActor->SetFolderPath("/QueryResults");
|
||||
queryObject.playMeshActor = staticMeshActor;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto issueQueries = [pProcess](
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter) {
|
||||
ACesium3DTileset* tileset = context.tilesets[0];
|
||||
|
||||
for (QueryObject& queryObject : pProcess->queryObjects) {
|
||||
tileset->SampleHeightMostDetailed(
|
||||
{queryObject.coordinateDegrees},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[tileset, &queryObject](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& results,
|
||||
const TArray<FString>& warnings) {
|
||||
queryObject.queryFinished = true;
|
||||
|
||||
// Log any warnings
|
||||
for (const FString& warning : warnings) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Warning,
|
||||
TEXT("Height query traversal warning: %s"),
|
||||
*warning);
|
||||
}
|
||||
|
||||
if (results.Num() != 1) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Warning,
|
||||
TEXT("Unexpected number of results received"));
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector& newCoordinate =
|
||||
results[0].LongitudeLatitudeHeight;
|
||||
if (!results[0].SampleSuccess) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Error,
|
||||
TEXT(
|
||||
"The height at (%f,%f) was not sampled successfully."),
|
||||
newCoordinate.X,
|
||||
newCoordinate.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector& originalCoordinate =
|
||||
queryObject.coordinateDegrees;
|
||||
|
||||
if (!FMath::IsNearlyEqual(
|
||||
originalCoordinate.X,
|
||||
newCoordinate.X,
|
||||
1e-12) ||
|
||||
!FMath::IsNearlyEqual(
|
||||
originalCoordinate.Y,
|
||||
newCoordinate.Y,
|
||||
1e-12)) {
|
||||
UE_LOG(
|
||||
LogCesium,
|
||||
Warning,
|
||||
TEXT("Hit result doesn't match original input"));
|
||||
return;
|
||||
}
|
||||
|
||||
FVector unrealPosition =
|
||||
tileset->ResolveGeoreference()
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(
|
||||
newCoordinate);
|
||||
|
||||
// Now bring the hit point to unreal world coordinates
|
||||
FVector unrealWorldPosition =
|
||||
tileset->GetActorTransform().TransformFVector4(
|
||||
unrealPosition);
|
||||
|
||||
queryObject.creationMeshActor->SetActorLocation(
|
||||
unrealWorldPosition);
|
||||
|
||||
queryObject.playMeshActor->SetActorLocation(
|
||||
unrealWorldPosition);
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
auto waitForQueries = [pProcess](
|
||||
SceneGenerationContext&,
|
||||
SceneGenerationContext&,
|
||||
TestPass::TestingParameter) {
|
||||
for (QueryObject& queryObject : pProcess->queryObjects) {
|
||||
if (!queryObject.queryFinished)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto showResults = [](SceneGenerationContext& creationContext,
|
||||
SceneGenerationContext&,
|
||||
TestPass::TestingParameter) {
|
||||
// Turn on the editor tileset updates so we can see what we loaded
|
||||
creationContext.setSuspendUpdate(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(
|
||||
TestPass{"Load terrain from cold cache", clearCache, nullptr});
|
||||
testPasses.push_back(TestPass{"Add test objects", nullptr, addTestObjects});
|
||||
testPasses.push_back(
|
||||
TestPass{"Issue height queries and wait", issueQueries, waitForQueries});
|
||||
testPasses.push_back(TestPass{"Show results", nullptr, showResults});
|
||||
|
||||
return RunLoadTest(testName, setup, testPasses, 1280, 720);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,358 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "Cesium3DTileset.h"
|
||||
#include "CesiumSampleHeightMostDetailedAsyncAction.h"
|
||||
#include "CesiumSceneGeneration.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "SampleHeightCallbackReceiver.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FSampleHeightMostDetailedSpec,
|
||||
"Cesium.Unit.SampleHeightMostDetailed",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
TObjectPtr<ACesium3DTileset> pTileset;
|
||||
|
||||
END_DEFINE_SPEC(FSampleHeightMostDetailedSpec)
|
||||
|
||||
// The intention of these tests is not to verify that height querying produces
|
||||
// correct heights, because the cesium-native tests already do that. It's just
|
||||
// to verify that the Unreal wrapper API around cesium-native is working
|
||||
// correctly.
|
||||
|
||||
void FSampleHeightMostDetailedSpec::Define() {
|
||||
Describe("Cesium World Terrain", [this]() {
|
||||
BeforeEach([this]() {
|
||||
CesiumTestHelpers::pushAllowTickInEditor();
|
||||
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
pTileset = pWorld->SpawnActor<ACesium3DTileset>();
|
||||
pTileset->SetIonAssetID(1);
|
||||
#if WITH_EDITOR
|
||||
pTileset->SetIonAccessToken(Cesium::SceneGenerationContext::testIonToken);
|
||||
pTileset->SetActorLabel(TEXT("Cesium World Terrain"));
|
||||
#endif
|
||||
});
|
||||
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
pTileset->Destroy();
|
||||
|
||||
CesiumTestHelpers::popAllowTickInEditor();
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"works with an empty array of positions",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
pTileset->SampleHeightMostDetailed(
|
||||
{},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 0);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"works with a single position",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
pTileset->SampleHeightMostDetailed(
|
||||
{FVector(-105.1, 40.1, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
TestTrue("SampleSuccess", result[0].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[0].LongitudeLatitudeHeight.X,
|
||||
-105.1,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[0].LongitudeLatitudeHeight.Y,
|
||||
40.1,
|
||||
1e-12);
|
||||
TestTrue(
|
||||
"Height",
|
||||
!FMath::IsNearlyEqual(
|
||||
result[0].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1.0));
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"works with multiple positions",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
pTileset->SampleHeightMostDetailed(
|
||||
{FVector(-105.1, 40.1, 1.0), FVector(105.1, -40.1, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 2);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
TestTrue("SampleSuccess", result[0].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[0].LongitudeLatitudeHeight.X,
|
||||
-105.1,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[0].LongitudeLatitudeHeight.Y,
|
||||
40.1,
|
||||
1e-12);
|
||||
TestTrue(
|
||||
"Height",
|
||||
!FMath::IsNearlyEqual(
|
||||
result[0].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1.0));
|
||||
TestTrue("SampleSuccess", result[1].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[1].LongitudeLatitudeHeight.X,
|
||||
105.1,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[1].LongitudeLatitudeHeight.Y,
|
||||
-40.1,
|
||||
1e-12);
|
||||
TestTrue(
|
||||
"Height",
|
||||
!FMath::IsNearlyEqual(
|
||||
result[1].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1.0));
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("Melbourne Photogrammetry", [this]() {
|
||||
BeforeEach([this]() {
|
||||
CesiumTestHelpers::pushAllowTickInEditor();
|
||||
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
pTileset = pWorld->SpawnActor<ACesium3DTileset>();
|
||||
pTileset->SetIonAssetID(69380);
|
||||
#if WITH_EDITOR
|
||||
pTileset->SetIonAccessToken(Cesium::SceneGenerationContext::testIonToken);
|
||||
pTileset->SetActorLabel(TEXT("Melbourne Photogrammetry"));
|
||||
#endif
|
||||
});
|
||||
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
pTileset->Destroy();
|
||||
|
||||
CesiumTestHelpers::popAllowTickInEditor();
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"indicates !HeightSampled for position outside tileset",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
pTileset->SampleHeightMostDetailed(
|
||||
// Somewhere in Sydney, not Melbourne
|
||||
{FVector(151.20972, -33.87100, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
TestTrue("SampleSuccess", !result[0].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[0].LongitudeLatitudeHeight.X,
|
||||
151.20972,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[0].LongitudeLatitudeHeight.Y,
|
||||
-33.87100,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Height",
|
||||
result[0].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1e-12);
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"can be queried via Blueprint interface",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
UCesiumSampleHeightMostDetailedAsyncAction* pAsync =
|
||||
UCesiumSampleHeightMostDetailedAsyncAction::
|
||||
SampleHeightMostDetailed(
|
||||
pTileset,
|
||||
{FVector(144.93406, -37.82457, 1.0)});
|
||||
|
||||
USampleHeightCallbackReceiver::Bind(
|
||||
pAsync->OnHeightsSampled,
|
||||
[this, done](
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
TestTrue("SampleSuccess", result[0].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[0].LongitudeLatitudeHeight.X,
|
||||
144.93406,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[0].LongitudeLatitudeHeight.Y,
|
||||
-37.82457,
|
||||
1e-12);
|
||||
TestTrue(
|
||||
"Height",
|
||||
!FMath::IsNearlyEqual(
|
||||
result[0].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1.0));
|
||||
done.ExecuteIfBound();
|
||||
});
|
||||
|
||||
pAsync->Activate();
|
||||
});
|
||||
});
|
||||
|
||||
Describe("Two tilesets in rapid succession", [this]() {
|
||||
BeforeEach([this]() { CesiumTestHelpers::pushAllowTickInEditor(); });
|
||||
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
CesiumTestHelpers::popAllowTickInEditor();
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
|
||||
ACesium3DTileset* pTileset1 = pWorld->SpawnActor<ACesium3DTileset>();
|
||||
pTileset1->SetIonAssetID(1);
|
||||
#if WITH_EDITOR
|
||||
pTileset1->SetIonAccessToken(
|
||||
Cesium::SceneGenerationContext::testIonToken);
|
||||
#endif
|
||||
|
||||
pTileset1->SampleHeightMostDetailed(
|
||||
{FVector(-105.1, 40.1, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, pWorld, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual("Number of warnings", warnings.Num(), 0);
|
||||
TestTrue("SampleSuccess", result[0].SampleSuccess);
|
||||
|
||||
ACesium3DTileset* pTileset2 =
|
||||
pWorld->SpawnActor<ACesium3DTileset>();
|
||||
pTileset2->SetIonAssetID(1);
|
||||
#if WITH_EDITOR
|
||||
pTileset2->SetIonAccessToken(
|
||||
Cesium::SceneGenerationContext::testIonToken);
|
||||
#endif
|
||||
pTileset2->SampleHeightMostDetailed(
|
||||
{FVector(105.1, 40.1, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, pWorld, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual(
|
||||
"Number of warnings",
|
||||
warnings.Num(),
|
||||
0);
|
||||
TestTrue(
|
||||
"SampleSuccess",
|
||||
result[0].SampleSuccess);
|
||||
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("Broken tileset", [this]() {
|
||||
BeforeEach([this]() { CesiumTestHelpers::pushAllowTickInEditor(); });
|
||||
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
CesiumTestHelpers::popAllowTickInEditor();
|
||||
});
|
||||
|
||||
LatentIt(
|
||||
"",
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
// Two slightly different error messages will occur, depending on
|
||||
// whether there's a web server running on localhost.
|
||||
this->AddExpectedError(
|
||||
TEXT("(Errors when loading)|(error occurred)"));
|
||||
|
||||
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
|
||||
|
||||
ACesium3DTileset* pTileset = pWorld->SpawnActor<ACesium3DTileset>();
|
||||
pTileset->SetTilesetSource(ETilesetSource::FromUrl);
|
||||
pTileset->SetUrl("http://localhost/notgonnawork");
|
||||
|
||||
pTileset->SampleHeightMostDetailed(
|
||||
{FVector(-105.1, 40.1, 1.0)},
|
||||
FCesiumSampleHeightMostDetailedCallback::CreateLambda(
|
||||
[this, done](
|
||||
ACesium3DTileset* pTileset,
|
||||
const TArray<FCesiumSampleHeightResult>& result,
|
||||
const TArray<FString>& warnings) {
|
||||
TestEqual("Number of results", result.Num(), 1);
|
||||
TestEqual("Number of warnings", warnings.Num(), 1);
|
||||
TestFalse("SampleSuccess", result[0].SampleSuccess);
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
result[0].LongitudeLatitudeHeight.X,
|
||||
-105.1,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
result[0].LongitudeLatitudeHeight.Y,
|
||||
40.1,
|
||||
1e-12);
|
||||
TestEqual(
|
||||
"Height",
|
||||
result[0].LongitudeLatitudeHeight.Z,
|
||||
1.0,
|
||||
1e-12);
|
||||
TestTrue(
|
||||
"Error message",
|
||||
warnings[0].Contains(TEXT("failed to load")));
|
||||
done.ExecuteIfBound();
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,280 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumLoadTestCore.h"
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "CesiumAsync/ICacheDatabase.h"
|
||||
#include "CesiumGltfComponent.h"
|
||||
#include "CesiumIonRasterOverlay.h"
|
||||
#include "CesiumRuntime.h"
|
||||
#include "CesiumSunSky.h"
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
|
||||
using namespace Cesium;
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetDenver,
|
||||
"Cesium.Performance.Tileset Loading.Aerometrex Denver",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetMelbourne,
|
||||
"Cesium.Performance.Tileset Loading.Melbourne photogrammetry (open data)",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetMontrealPointCloud,
|
||||
"Cesium.Performance.Tileset Loading.Montreal point cloud (open data)",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FLoadTilesetMelbourneVaryMaxTileLoads,
|
||||
"Cesium.Performance.Tileset Loading.Melbourne photogrammetry (open data), vary max tile loads",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter)
|
||||
|
||||
void samplesClearCache(SceneGenerationContext&, TestPass::TestingParameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
}
|
||||
|
||||
void samplesRefreshTilesets(
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
context.refreshTilesets();
|
||||
}
|
||||
|
||||
void setupForDenver(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-104.988892, 39.743462, 1798.679443),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-5.2, -149.4, 0),
|
||||
90.0f);
|
||||
|
||||
// Add Cesium World Terrain
|
||||
ACesium3DTileset* worldTerrainTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
worldTerrainTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
worldTerrainTileset->SetIonAssetID(1);
|
||||
worldTerrainTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
worldTerrainTileset->SetActorLabel(TEXT("Cesium World Terrain"));
|
||||
|
||||
// Bing Maps Aerial overlay
|
||||
UCesiumIonRasterOverlay* pOverlay = NewObject<UCesiumIonRasterOverlay>(
|
||||
worldTerrainTileset,
|
||||
FName("Bing Maps Aerial"),
|
||||
RF_Transactional);
|
||||
pOverlay->MaterialLayerKey = TEXT("Overlay0");
|
||||
pOverlay->IonAssetID = 2;
|
||||
pOverlay->SetActive(true);
|
||||
pOverlay->OnComponentCreated();
|
||||
worldTerrainTileset->AddInstanceComponent(pOverlay);
|
||||
|
||||
// Aerometrex Denver
|
||||
ACesium3DTileset* aerometrexTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
aerometrexTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
aerometrexTileset->SetIonAssetID(354307);
|
||||
aerometrexTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
aerometrexTileset->SetMaximumScreenSpaceError(2.0);
|
||||
aerometrexTileset->SetActorLabel(TEXT("Aerometrex Denver"));
|
||||
|
||||
context.tilesets.push_back(worldTerrainTileset);
|
||||
context.tilesets.push_back(aerometrexTileset);
|
||||
}
|
||||
|
||||
void setupForMelbourne(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(144.951538, -37.809871, 140.334974),
|
||||
FVector(1052, 506, 23651),
|
||||
FRotator(-32, 20, 0),
|
||||
90.0f);
|
||||
|
||||
context.sunSky->SolarTime = 16.8;
|
||||
context.sunSky->UpdateSun();
|
||||
|
||||
// Add Cesium World Terrain
|
||||
ACesium3DTileset* worldTerrainTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
worldTerrainTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
worldTerrainTileset->SetIonAssetID(1);
|
||||
worldTerrainTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
worldTerrainTileset->SetActorLabel(TEXT("Cesium World Terrain"));
|
||||
|
||||
// Bing Maps Aerial overlay
|
||||
UCesiumIonRasterOverlay* pOverlay = NewObject<UCesiumIonRasterOverlay>(
|
||||
worldTerrainTileset,
|
||||
FName("Bing Maps Aerial"),
|
||||
RF_Transactional);
|
||||
pOverlay->MaterialLayerKey = TEXT("Overlay0");
|
||||
pOverlay->IonAssetID = 2;
|
||||
pOverlay->SetActive(true);
|
||||
pOverlay->OnComponentCreated();
|
||||
worldTerrainTileset->AddInstanceComponent(pOverlay);
|
||||
|
||||
ACesium3DTileset* melbourneTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
melbourneTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
melbourneTileset->SetIonAssetID(69380);
|
||||
melbourneTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
melbourneTileset->SetMaximumScreenSpaceError(6.0);
|
||||
melbourneTileset->SetActorLabel(TEXT("Melbourne Photogrammetry"));
|
||||
melbourneTileset->SetActorLocation(FVector(0, 0, 900));
|
||||
|
||||
context.tilesets.push_back(worldTerrainTileset);
|
||||
context.tilesets.push_back(melbourneTileset);
|
||||
}
|
||||
|
||||
void setupForMontrealPointCloud(SceneGenerationContext& context) {
|
||||
context.setCommonProperties(
|
||||
FVector(-73.616526, 45.57335, 95.048859),
|
||||
FVector(0, 0, 0),
|
||||
FRotator(-90.0, 0.0, 0.0),
|
||||
90.0f);
|
||||
|
||||
ACesium3DTileset* montrealTileset =
|
||||
context.world->SpawnActor<ACesium3DTileset>();
|
||||
montrealTileset->SetTilesetSource(ETilesetSource::FromCesiumIon);
|
||||
montrealTileset->SetIonAssetID(28945);
|
||||
montrealTileset->SetIonAccessToken(SceneGenerationContext::testIonToken);
|
||||
montrealTileset->SetMaximumScreenSpaceError(16.0);
|
||||
montrealTileset->SetActorLabel(TEXT("Montreal Point Cloud"));
|
||||
|
||||
context.tilesets.push_back(montrealTileset);
|
||||
}
|
||||
|
||||
bool FLoadTilesetDenver::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", samplesClearCache, nullptr});
|
||||
testPasses.push_back(TestPass{"Warm Cache", samplesRefreshTilesets, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForDenver,
|
||||
testPasses,
|
||||
1024,
|
||||
768);
|
||||
}
|
||||
|
||||
bool FLoadTilesetMelbourne::RunTest(const FString& Parameters) {
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", samplesClearCache, nullptr});
|
||||
testPasses.push_back(TestPass{"Warm Cache", samplesRefreshTilesets, nullptr});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForMelbourne,
|
||||
testPasses,
|
||||
1024,
|
||||
768);
|
||||
}
|
||||
|
||||
bool FLoadTilesetMontrealPointCloud::RunTest(const FString& Parameters) {
|
||||
auto adjustCamera = [this](
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
// Zoom way out
|
||||
context.startPosition = FVector(0, 0, 7240000.0);
|
||||
context.startRotation = FRotator(-90.0, 0.0, 0.0);
|
||||
context.syncWorldCamera();
|
||||
|
||||
context.pawn->SetActorLocation(context.startPosition);
|
||||
};
|
||||
|
||||
auto verifyVisibleTiles = [this](
|
||||
SceneGenerationContext& creationContext,
|
||||
SceneGenerationContext& playContext,
|
||||
TestPass::TestingParameter parameter) {
|
||||
Cesium3DTilesSelection::Tileset* pTileset =
|
||||
playContext.tilesets[0]->GetTileset();
|
||||
if (TestNotNull("Tileset", pTileset)) {
|
||||
int visibleTiles = 0;
|
||||
pTileset->forEachLoadedTile([&](Cesium3DTilesSelection::Tile& tile) {
|
||||
if (tile.getState() != Cesium3DTilesSelection::TileLoadState::Done)
|
||||
return;
|
||||
const Cesium3DTilesSelection::TileContent& content = tile.getContent();
|
||||
const Cesium3DTilesSelection::TileRenderContent* pRenderContent =
|
||||
content.getRenderContent();
|
||||
if (!pRenderContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
UCesiumGltfComponent* Gltf = static_cast<UCesiumGltfComponent*>(
|
||||
pRenderContent->getRenderResources());
|
||||
if (Gltf && Gltf->IsVisible()) {
|
||||
++visibleTiles;
|
||||
}
|
||||
});
|
||||
|
||||
TestEqual("visibleTiles", visibleTiles, 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Cold Cache", samplesClearCache, nullptr});
|
||||
testPasses.push_back(TestPass{"Adjust", adjustCamera, verifyVisibleTiles});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForMontrealPointCloud,
|
||||
testPasses,
|
||||
512,
|
||||
512);
|
||||
}
|
||||
|
||||
bool FLoadTilesetMelbourneVaryMaxTileLoads::RunTest(const FString& Parameters) {
|
||||
|
||||
auto setupPass = [this](
|
||||
SceneGenerationContext& context,
|
||||
TestPass::TestingParameter parameter) {
|
||||
std::shared_ptr<CesiumAsync::ICacheDatabase> pCacheDatabase =
|
||||
getCacheDatabase();
|
||||
pCacheDatabase->clearAll();
|
||||
|
||||
int maxLoadsTarget = swl::get<int>(parameter);
|
||||
context.setMaximumSimultaneousTileLoads(maxLoadsTarget);
|
||||
|
||||
context.refreshTilesets();
|
||||
};
|
||||
|
||||
auto reportStep = [this](const std::vector<TestPass>& testPasses) {
|
||||
FString reportStr;
|
||||
reportStr += "\n\nTest Results\n";
|
||||
reportStr += "------------------------------------------------------\n";
|
||||
reportStr += "(measured time) - (MaximumSimultaneousTileLoads value)\n";
|
||||
reportStr += "------------------------------------------------------\n";
|
||||
std::vector<TestPass>::const_iterator it;
|
||||
for (it = testPasses.begin(); it != testPasses.end(); ++it) {
|
||||
const TestPass& pass = *it;
|
||||
reportStr +=
|
||||
FString::Printf(TEXT("%.2f secs - %s"), pass.elapsedTime, *pass.name);
|
||||
if (pass.isFastest)
|
||||
reportStr += " <-- fastest";
|
||||
reportStr += "\n";
|
||||
}
|
||||
reportStr += "------------------------------------------------------\n";
|
||||
UE_LOG(LogCesium, Display, TEXT("%s"), *reportStr);
|
||||
};
|
||||
|
||||
std::vector<TestPass> testPasses;
|
||||
testPasses.push_back(TestPass{"Default", samplesClearCache, NULL});
|
||||
testPasses.push_back(TestPass{"12", setupPass, NULL, 12});
|
||||
testPasses.push_back(TestPass{"16", setupPass, NULL, 16});
|
||||
testPasses.push_back(TestPass{"20", setupPass, NULL, 20});
|
||||
testPasses.push_back(TestPass{"24", setupPass, NULL, 24});
|
||||
testPasses.push_back(TestPass{"28", setupPass, NULL, 28});
|
||||
|
||||
return RunLoadTest(
|
||||
GetBeautifiedTestName(),
|
||||
setupForMelbourne,
|
||||
testPasses,
|
||||
1024,
|
||||
768,
|
||||
reportStep);
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,357 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CesiumGeoreference.h"
|
||||
#include "CesiumOriginShiftComponent.h"
|
||||
#include "CesiumSubLevelComponent.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Editor.h"
|
||||
#include "Engine/World.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GlobeAwareDefaultPawn.h"
|
||||
#include "LevelInstance/LevelInstanceActor.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FSubLevelsSpec,
|
||||
"Cesium.Unit.SubLevels",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
TObjectPtr<UWorld> pWorld;
|
||||
TObjectPtr<ALevelInstance> pSubLevel1;
|
||||
TObjectPtr<UCesiumSubLevelComponent> pLevelComponent1;
|
||||
TObjectPtr<ALevelInstance> pSubLevel2;
|
||||
TObjectPtr<UCesiumSubLevelComponent> pLevelComponent2;
|
||||
TObjectPtr<ACesiumGeoreference> pGeoreference;
|
||||
TObjectPtr<AGlobeAwareDefaultPawn> pPawn;
|
||||
FDelegateHandle subscriptionPostPIEStarted;
|
||||
|
||||
END_DEFINE_SPEC(FSubLevelsSpec)
|
||||
|
||||
using namespace CesiumTestHelpers;
|
||||
|
||||
void FSubLevelsSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
if (IsValid(pWorld)) {
|
||||
// Only run the below once in order to save time loading/unloading
|
||||
// levels for every little test.
|
||||
return;
|
||||
}
|
||||
|
||||
pWorld = FAutomationEditorCommonUtils::CreateNewMap();
|
||||
|
||||
pSubLevel1 = pWorld->SpawnActor<ALevelInstance>();
|
||||
trackForPlay(pSubLevel1);
|
||||
pSubLevel1->SetWorldAsset(TSoftObjectPtr<UWorld>(
|
||||
FSoftObjectPath("/CesiumForUnreal/Tests/Maps/SingleCube.SingleCube")));
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(true);
|
||||
pLevelComponent1 =
|
||||
Cast<UCesiumSubLevelComponent>(pSubLevel1->AddComponentByClass(
|
||||
UCesiumSubLevelComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
trackForPlay(pLevelComponent1);
|
||||
pLevelComponent1->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(10.0, 20.0, 1000.0));
|
||||
pSubLevel1->AddInstanceComponent(pLevelComponent1);
|
||||
|
||||
pSubLevel2 = pWorld->SpawnActor<ALevelInstance>();
|
||||
trackForPlay(pSubLevel2);
|
||||
pSubLevel2->SetWorldAsset(TSoftObjectPtr<UWorld>(FSoftObjectPath(
|
||||
"/CesiumForUnreal/Tests/Maps/ConeAndCylinder.ConeAndCylinder")));
|
||||
pSubLevel2->SetIsTemporarilyHiddenInEditor(true);
|
||||
pLevelComponent2 =
|
||||
Cast<UCesiumSubLevelComponent>(pSubLevel2->AddComponentByClass(
|
||||
UCesiumSubLevelComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false));
|
||||
trackForPlay(pLevelComponent2);
|
||||
pLevelComponent2->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(-25.0, 15.0, -5000.0));
|
||||
pSubLevel2->AddInstanceComponent(pLevelComponent2);
|
||||
|
||||
pSubLevel1->LoadLevelInstance();
|
||||
pSubLevel2->LoadLevelInstance();
|
||||
|
||||
pGeoreference = nullptr;
|
||||
for (TActorIterator<ACesiumGeoreference> it(pWorld); it; ++it) {
|
||||
pGeoreference = *it;
|
||||
}
|
||||
|
||||
trackForPlay(pGeoreference);
|
||||
|
||||
pPawn = pWorld->SpawnActor<AGlobeAwareDefaultPawn>();
|
||||
pPawn->AddComponentByClass(
|
||||
UCesiumOriginShiftComponent::StaticClass(),
|
||||
false,
|
||||
FTransform::Identity,
|
||||
false);
|
||||
trackForPlay(pPawn);
|
||||
pPawn->AutoPossessPlayer = EAutoReceiveInput::Player0;
|
||||
});
|
||||
|
||||
AfterEach([this]() {
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(true);
|
||||
pSubLevel2->SetIsTemporarilyHiddenInEditor(true);
|
||||
});
|
||||
|
||||
It("initially hides sub-levels in the Editor", [this]() {
|
||||
TestTrue("pGeoreference is valid", IsValid(pGeoreference));
|
||||
TestTrue("pSubLevel1 is valid", IsValid(pSubLevel1));
|
||||
TestTrue("pSubLevel2 is valid", IsValid(pSubLevel2));
|
||||
TestTrue(
|
||||
"pSubLevel1 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
TestTrue(
|
||||
"pSubLevel2 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
});
|
||||
|
||||
Describe(
|
||||
"copies CesiumGeoreference origin changes to the active sub-level in the Editor",
|
||||
[this]() {
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(false);
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestFalse(
|
||||
"pSubLevel1 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
|
||||
pGeoreference->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(1.0, 2.0, 3.0));
|
||||
TestEqual("Longitude", pLevelComponent1->GetOriginLongitude(), 1.0);
|
||||
TestEqual("Latitude", pLevelComponent1->GetOriginLatitude(), 2.0);
|
||||
TestEqual("Height", pLevelComponent1->GetOriginHeight(), 3.0);
|
||||
});
|
||||
});
|
||||
|
||||
Describe(
|
||||
"copies active sub-level origin changes to the CesiumGeoreference in the Editor",
|
||||
[this]() {
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(false);
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestFalse(
|
||||
"pSubLevel1 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
|
||||
pLevelComponent1->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(4.0, 5.0, 6.0));
|
||||
TestEqual("Longitude", pGeoreference->GetOriginLongitude(), 4.0);
|
||||
TestEqual("Latitude", pGeoreference->GetOriginLatitude(), 5.0);
|
||||
TestEqual("Height", pGeoreference->GetOriginHeight(), 6.0);
|
||||
});
|
||||
});
|
||||
|
||||
Describe(
|
||||
"does not copy inactive sub-level origin changes to the CesiumGeoreference in the Editor",
|
||||
[this]() {
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
// Set the sub-level active, wait a frame so it becomes so.
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(false);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
// Set the sub-level inactive, wait a frame so it becomes so.
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(true);
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
// Verify that the previously-active sub-level isn't affected by
|
||||
// georeference origin changes.
|
||||
double expectedLongitude = pGeoreference->GetOriginLongitude();
|
||||
double expectedLatitude = pGeoreference->GetOriginLatitude();
|
||||
double expectedHeight = pGeoreference->GetOriginHeight();
|
||||
|
||||
TestNotEqual("Longitude", expectedLongitude, 7.0);
|
||||
TestNotEqual("Latitude", expectedLatitude, 8.0);
|
||||
TestNotEqual("Height", expectedHeight, 9.0);
|
||||
|
||||
pLevelComponent1->SetOriginLongitudeLatitudeHeight(
|
||||
FVector(7.0, 8.0, 9.0));
|
||||
TestEqual(
|
||||
"Longitude",
|
||||
pGeoreference->GetOriginLongitude(),
|
||||
expectedLongitude);
|
||||
TestEqual(
|
||||
"Latitude",
|
||||
pGeoreference->GetOriginLatitude(),
|
||||
expectedLatitude);
|
||||
TestEqual("Height", pGeoreference->GetOriginHeight(), expectedHeight);
|
||||
});
|
||||
});
|
||||
|
||||
Describe(
|
||||
"ensures only one sub-level instance is visible in the editor at a time",
|
||||
[this]() {
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
pSubLevel1->SetIsTemporarilyHiddenInEditor(false);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestFalse(
|
||||
"pSubLevel1 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
TestTrue(
|
||||
"pSubLevel2 is hidden",
|
||||
pSubLevel2->IsTemporarilyHiddenInEditor(true));
|
||||
|
||||
pSubLevel2->SetIsTemporarilyHiddenInEditor(false);
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestTrue(
|
||||
"pSubLevel1 is hidden",
|
||||
pSubLevel1->IsTemporarilyHiddenInEditor(true));
|
||||
TestFalse(
|
||||
"pSubLevel2 is hidden",
|
||||
pSubLevel2->IsTemporarilyHiddenInEditor(true));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("activates the closest sub-level that is in range", [this]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
});
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
waitFor(done, GEditor->PlayWorld, 5.0f, [this]() {
|
||||
return !findInPlay(pSubLevel1)->IsLoaded() &&
|
||||
!findInPlay(pSubLevel2)->IsLoaded();
|
||||
});
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestFalse("pSubLevel1 is loaded", findInPlay(pSubLevel1)->IsLoaded());
|
||||
TestFalse("pSubLevel2 is loaded", findInPlay(pSubLevel2)->IsLoaded());
|
||||
|
||||
// Move the player within range of the first sub-level
|
||||
UCesiumSubLevelComponent* pPlayLevelComponent1 =
|
||||
findInPlay(pLevelComponent1);
|
||||
FVector origin1 =
|
||||
findInPlay(pGeoreference)
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(FVector(
|
||||
pPlayLevelComponent1->GetOriginLongitude(),
|
||||
pPlayLevelComponent1->GetOriginLatitude(),
|
||||
pPlayLevelComponent1->GetOriginHeight()));
|
||||
findInPlay(pPawn)->SetActorLocation(origin1);
|
||||
});
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
waitFor(done, GEditor->PlayWorld, 5.0f, [this]() {
|
||||
return findInPlay(pSubLevel1)->IsLoaded();
|
||||
});
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestTrue("pSubLevel1 is loaded", findInPlay(pSubLevel1)->IsLoaded());
|
||||
TestFalse("pSubLevel2 is loaded", findInPlay(pSubLevel2)->IsLoaded());
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
|
||||
Describe("handles a rapid load / unload / reload cycle", [this]() {
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
subscriptionPostPIEStarted =
|
||||
FEditorDelegates::PostPIEStarted.AddLambda(
|
||||
[done](bool isSimulating) { done.Execute(); });
|
||||
FRequestPlaySessionParams params{};
|
||||
GEditor->RequestPlaySession(params);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
FEditorDelegates::PostPIEStarted.Remove(subscriptionPostPIEStarted);
|
||||
});
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
waitFor(done, GEditor->PlayWorld, 5.0f, [this]() {
|
||||
return !findInPlay(pSubLevel1)->IsLoaded() &&
|
||||
!findInPlay(pSubLevel2)->IsLoaded();
|
||||
});
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestFalse("pSubLevel1 is loaded", findInPlay(pSubLevel1)->IsLoaded());
|
||||
TestFalse("pSubLevel2 is loaded", findInPlay(pSubLevel2)->IsLoaded());
|
||||
|
||||
// Move the player within range of the first sub-level
|
||||
UCesiumSubLevelComponent* pPlayLevelComponent1 =
|
||||
findInPlay(pLevelComponent1);
|
||||
FVector origin1 =
|
||||
findInPlay(pGeoreference)
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(FVector(
|
||||
pPlayLevelComponent1->GetOriginLongitude(),
|
||||
pPlayLevelComponent1->GetOriginLatitude(),
|
||||
pPlayLevelComponent1->GetOriginHeight()));
|
||||
findInPlay(pPawn)->SetActorLocation(origin1);
|
||||
});
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
// Wait for the level to load.
|
||||
waitFor(done, GEditor->PlayWorld, 5.0f, [this]() {
|
||||
return findInPlay(pSubLevel1)->IsLoaded();
|
||||
});
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
// Move the player outside the sub-level, triggering an unload.
|
||||
UCesiumSubLevelComponent* pPlayLevelComponent1 =
|
||||
findInPlay(pLevelComponent1);
|
||||
FVector origin1 =
|
||||
findInPlay(pGeoreference)
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(FVector(
|
||||
pPlayLevelComponent1->GetOriginLongitude(),
|
||||
pPlayLevelComponent1->GetOriginLatitude(),
|
||||
pPlayLevelComponent1->GetOriginHeight() + 100000.0));
|
||||
findInPlay(pPawn)->SetActorLocation(origin1);
|
||||
});
|
||||
BeforeEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
// Without waiting for the level to finish unloading, move the player back
|
||||
// inside it.
|
||||
UCesiumSubLevelComponent* pPlayLevelComponent1 =
|
||||
findInPlay(pLevelComponent1);
|
||||
FVector origin1 =
|
||||
findInPlay(pGeoreference)
|
||||
->TransformLongitudeLatitudeHeightPositionToUnreal(FVector(
|
||||
pPlayLevelComponent1->GetOriginLongitude(),
|
||||
pPlayLevelComponent1->GetOriginLatitude(),
|
||||
pPlayLevelComponent1->GetOriginHeight()));
|
||||
findInPlay(pPawn)->SetActorLocation(origin1);
|
||||
});
|
||||
LatentBeforeEach(
|
||||
EAsyncExecution::TaskGraphMainThread,
|
||||
[this](const FDoneDelegate& done) {
|
||||
// Wait for the level to load.
|
||||
waitFor(done, GEditor->PlayWorld, 5.0f, [this]() {
|
||||
return findInPlay(pSubLevel1)->IsLoaded();
|
||||
});
|
||||
});
|
||||
It("", EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
TestTrue("pSubLevel1 is loaded", findInPlay(pSubLevel1)->IsLoaded());
|
||||
TestFalse("pSubLevel2 is loaded", findInPlay(pSubLevel2)->IsLoaded());
|
||||
});
|
||||
AfterEach(EAsyncExecution::TaskGraphMainThread, [this]() {
|
||||
GEditor->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
@ -0,0 +1,92 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "UnrealAssetAccessor.h"
|
||||
#include "Async/Async.h"
|
||||
#include "CesiumAsync/IAssetResponse.h"
|
||||
#include "CesiumRuntime.h"
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FUnrealAssetAccessorSpec,
|
||||
"Cesium.Unit.UnrealAssetAccessor",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
|
||||
FString Filename;
|
||||
std::string randomText = "Some random text.";
|
||||
IPlatformFile* FileManager;
|
||||
|
||||
void TestAccessorRequest(const FString& Uri, const std::string& expectedData) {
|
||||
bool done = false;
|
||||
|
||||
UnrealAssetAccessor accessor{};
|
||||
accessor.get(getAsyncSystem(), TCHAR_TO_UTF8(*Uri), {})
|
||||
.thenInMainThread(
|
||||
[&](std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
||||
const CesiumAsync::IAssetResponse* Response = pRequest->response();
|
||||
TestNotNull("Response", Response);
|
||||
if (!Response)
|
||||
return;
|
||||
|
||||
std::span<const std::byte> data = Response->data();
|
||||
TestEqual("data length", data.size(), expectedData.size());
|
||||
std::string s(
|
||||
reinterpret_cast<const char*>(data.data()),
|
||||
data.size());
|
||||
TestEqual("data", s, expectedData);
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
accessor.tick();
|
||||
getAsyncSystem().dispatchMainThreadTasks();
|
||||
}
|
||||
}
|
||||
|
||||
END_DEFINE_SPEC(FUnrealAssetAccessorSpec)
|
||||
|
||||
void FUnrealAssetAccessorSpec::Define() {
|
||||
BeforeEach([this]() {
|
||||
Filename = FPaths::ConvertRelativePathToFull(
|
||||
FPaths::CreateTempFilename(*FPaths::ProjectSavedDir()));
|
||||
|
||||
FileManager = &FPlatformFileManager::Get().GetPlatformFile();
|
||||
FFileHelper::SaveStringToFile(
|
||||
UTF8_TO_TCHAR(randomText.c_str()),
|
||||
*Filename,
|
||||
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
||||
});
|
||||
|
||||
AfterEach([this]() { FileManager->DeleteFile(*Filename); });
|
||||
|
||||
It("Fails with non-existant file:/// URLs", [this]() {
|
||||
FString Uri = TEXT("file:///") + Filename;
|
||||
Uri.ReplaceCharInline('\\', '/');
|
||||
Uri.ReplaceInline(TEXT(" "), TEXT("%20"));
|
||||
Uri += ".bogusExtension";
|
||||
|
||||
TestAccessorRequest(Uri, "");
|
||||
});
|
||||
|
||||
It("Can access file:/// URLs", [this]() {
|
||||
FString Uri = TEXT("file:///") + Filename;
|
||||
Uri.ReplaceCharInline('\\', '/');
|
||||
Uri.ReplaceInline(TEXT(" "), TEXT("%20"));
|
||||
|
||||
TestAccessorRequest(Uri, randomText);
|
||||
});
|
||||
|
||||
It("Can access file:/// URLs with unnecessary query params", [this]() {
|
||||
FString Uri = TEXT("file:///") + Filename;
|
||||
Uri.ReplaceCharInline('\\', '/');
|
||||
Uri.ReplaceInline(TEXT(" "), TEXT("%20"));
|
||||
Uri.Append("?version=4.27.1");
|
||||
|
||||
TestAccessorRequest(Uri, randomText);
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,312 @@
|
||||
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
||||
|
||||
#include "UnrealMetadataConversions.h"
|
||||
#include "CesiumTestHelpers.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
BEGIN_DEFINE_SPEC(
|
||||
FUnrealMetadataConversionsSpec,
|
||||
"Cesium.Unit.MetadataConversions",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext |
|
||||
EAutomationTestFlags::ServerContext |
|
||||
EAutomationTestFlags::CommandletContext |
|
||||
EAutomationTestFlags::ProductFilter)
|
||||
END_DEFINE_SPEC(FUnrealMetadataConversionsSpec)
|
||||
|
||||
void FUnrealMetadataConversionsSpec::Define() {
|
||||
|
||||
Describe("FIntPoint", [this]() {
|
||||
It("converts from glm::ivec2", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toIntPoint(glm::ivec2(-1, 2)),
|
||||
FIntPoint(-1, 2));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1 Y=2");
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toIntPoint(str, FIntPoint(0)),
|
||||
FIntPoint(1, 2));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions::toIntPoint(str, FIntPoint(0)),
|
||||
FIntPoint(0));
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions::toIntPoint(str, FIntPoint(0)),
|
||||
FIntPoint(0));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FVector2D", [this]() {
|
||||
It("converts from glm::dvec2", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector2D(glm::dvec2(-1.0, 2.0)),
|
||||
FVector2D(-1.0, 2.0));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1.5 Y=2.5");
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector2D(str, FVector2D::Zero()),
|
||||
FVector2D(1.5, 2.5));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions::toVector2D(str, FVector2D::Zero()),
|
||||
FVector2D::Zero());
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions::toVector2D(str, FVector2D::Zero()),
|
||||
FVector2D::Zero());
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FIntVector", [this]() {
|
||||
It("converts from glm::ivec3", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toIntVector(glm::ivec3(-1, 2, 4)),
|
||||
FIntVector(-1, 2, 4));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1 Y=2 Z=4");
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toIntVector(str, FIntVector(0)),
|
||||
FIntVector(1, 2, 4));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1 Y=2");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions::toIntVector(str, FIntVector(0)),
|
||||
FIntVector(0));
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5 B=1");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions::toIntVector(str, FIntVector(0)),
|
||||
FIntVector(0));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FVector3f", [this]() {
|
||||
It("converts from glm::vec3", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector3f(glm::vec3(1.0f, 2.3f, 4.56f)),
|
||||
FVector3f(1.0f, 2.3f, 4.56f));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1 Y=2 Z=3");
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions ::toVector3f(str, FVector3f::Zero()),
|
||||
FVector3f(1.0f, 2.0f, 3.0f));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1 Y=2");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions ::toVector3f(str, FVector3f::Zero()),
|
||||
FVector3f(0.0f));
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5 B=0.5");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions ::toVector3f(str, FVector3f::Zero()),
|
||||
FVector3f(0.0f));
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FVector", [this]() {
|
||||
It("converts from glm::dvec3", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector(glm::dvec3(1.0, 2.3, 4.56)),
|
||||
FVector(1.0, 2.3, 4.56));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1.5 Y=2.5 Z=3.5");
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector(str, FVector::Zero()),
|
||||
FVector(1.5f, 2.5f, 3.5f));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1 Y=2");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions::toVector(str, FVector::Zero()),
|
||||
FVector::Zero());
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5 B=0.5");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions::toVector(str, FVector::Zero()),
|
||||
FVector::Zero());
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FVector4", [this]() {
|
||||
It("converts from glm::dvec4", [this]() {
|
||||
TestEqual(
|
||||
"value",
|
||||
UnrealMetadataConversions::toVector4(
|
||||
glm::dvec4(1.0, 2.3, 4.56, 7.89)),
|
||||
FVector4(1.0, 2.3, 4.56, 7.89));
|
||||
});
|
||||
|
||||
It("converts from string", [this]() {
|
||||
std::string_view str("X=1.5 Y=2.5 Z=3.5 W=4.5");
|
||||
TestEqual(
|
||||
"with W component",
|
||||
UnrealMetadataConversions::toVector4(str, FVector4::Zero()),
|
||||
FVector4(1.5, 2.5, 3.5, 4.5));
|
||||
|
||||
str = std::string_view("X=1.5 Y=2.5 Z=3.5");
|
||||
TestEqual(
|
||||
"without W component",
|
||||
UnrealMetadataConversions::toVector4(str, FVector4::Zero()),
|
||||
FVector4(1.5, 2.5, 3.5, 1.0));
|
||||
});
|
||||
|
||||
It("uses default value for invalid string", [this]() {
|
||||
std::string_view str("X=1 Y=2");
|
||||
TestEqual(
|
||||
"partial input",
|
||||
UnrealMetadataConversions::toVector4(str, FVector4::Zero()),
|
||||
FVector4::Zero());
|
||||
|
||||
str = std::string_view("R=0.5 G=0.5 B=0.5 A=1.0");
|
||||
TestEqual(
|
||||
"bad format",
|
||||
UnrealMetadataConversions::toVector4(str, FVector4::Zero()),
|
||||
FVector4::Zero());
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FMatrix", [this]() {
|
||||
It("converts from glm::dmat4", [this]() {
|
||||
// clang-format off
|
||||
glm::dmat4 input = glm::dmat4(
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
5.0, 6.0, 7.0, 8.0,
|
||||
0.0, 1.0, 0.0, 1.0,
|
||||
1.0, 0.0, 0.0, 1.0);
|
||||
// clang-format on
|
||||
input = glm::transpose(input);
|
||||
|
||||
FMatrix expected(
|
||||
FPlane4d(1.0, 2.0, 3.0, 4.0),
|
||||
FPlane4d(5.0, 6.0, 7.0, 8.0),
|
||||
FPlane4d(0.0, 1.0, 0.0, 1.0),
|
||||
FPlane4d(1.0, 0.0, 0.0, 1.0));
|
||||
TestEqual("value", UnrealMetadataConversions::toMatrix(input), expected);
|
||||
});
|
||||
});
|
||||
|
||||
Describe("FString", [this]() {
|
||||
It("converts from std::string_view", [this]() {
|
||||
TestEqual(
|
||||
"std::string_view",
|
||||
UnrealMetadataConversions::toString(std::string_view("Hello")),
|
||||
FString("Hello"));
|
||||
});
|
||||
|
||||
It("converts from vecN", [this]() {
|
||||
std::string expected("X=1 Y=2");
|
||||
TestEqual(
|
||||
"vec2",
|
||||
UnrealMetadataConversions::toString(glm::u8vec2(1, 2)),
|
||||
FString(expected.c_str()));
|
||||
|
||||
expected = "X=" + std::to_string(4.5f) + " Y=" + std::to_string(3.21f) +
|
||||
" Z=" + std::to_string(123.0f);
|
||||
TestEqual(
|
||||
"vec3",
|
||||
UnrealMetadataConversions::toString(glm::vec3(4.5f, 3.21f, 123.0f)),
|
||||
FString(expected.c_str()));
|
||||
|
||||
expected = "X=" + std::to_string(1.0f) + " Y=" + std::to_string(2.0f) +
|
||||
" Z=" + std::to_string(3.0f) + " W=" + std::to_string(4.0f);
|
||||
TestEqual(
|
||||
"vec4",
|
||||
UnrealMetadataConversions::toString(
|
||||
glm::vec4(1.0f, 2.0f, 3.0f, 4.0f)),
|
||||
FString(expected.c_str()));
|
||||
});
|
||||
|
||||
It("converts from matN", [this]() {
|
||||
// clang-format off
|
||||
glm::mat2 mat2(
|
||||
0.0f, 1.0f,
|
||||
2.0f, 3.0f);
|
||||
mat2 = glm::transpose(mat2);
|
||||
|
||||
std::string expected =
|
||||
"[" + std::to_string(0.0f) + " " + std::to_string(1.0f) + "] " +
|
||||
"[" + std::to_string(2.0f) + " " + std::to_string(3.0f) + "]";
|
||||
// clang-format on
|
||||
TestEqual(
|
||||
"mat2",
|
||||
UnrealMetadataConversions::toString(mat2),
|
||||
FString(expected.c_str()));
|
||||
|
||||
// This is written as transposed because glm::transpose only compiles for
|
||||
// floating point types.
|
||||
// clang-format off
|
||||
glm::i8mat3x3 mat3(
|
||||
-1, 4, 7,
|
||||
2, -5, 8,
|
||||
3, 6, -9);
|
||||
// clang-format on
|
||||
|
||||
expected = "[-1 2 3] [4 -5 6] [7 8 -9]";
|
||||
TestEqual(
|
||||
"mat3",
|
||||
UnrealMetadataConversions::toString(mat3),
|
||||
FString(expected.c_str()));
|
||||
|
||||
// This is written as transposed because glm::transpose only compiles for
|
||||
// floating point types.
|
||||
// clang-format off
|
||||
glm::u8mat4x4 mat4(
|
||||
0, 4, 8, 12,
|
||||
1, 5, 9, 13,
|
||||
2, 6, 10, 14,
|
||||
3, 7, 11, 15);
|
||||
// clang-format on
|
||||
|
||||
expected = "[0 1 2 3] [4 5 6 7] [8 9 10 11] [12 13 14 15]";
|
||||
TestEqual(
|
||||
"mat4",
|
||||
UnrealMetadataConversions::toString(mat4),
|
||||
FString(expected.c_str()));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user