Files
BXSSP_Andriod/Plugins/CesiumForUnreal/Source/CesiumRuntime/Private/Tests/SampleHeightMostDetailed.perf.cpp

485 lines
17 KiB
C++

// 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