Skip to content

Commit

Permalink
Merge pull request #1421 from CesiumGS/terrain-query-test
Browse files Browse the repository at this point in the history
Add Unreal API for terrain height querying
  • Loading branch information
j9liu authored Sep 27, 2024
2 parents 8b0f050 + 5ec5ea4 commit f032377
Show file tree
Hide file tree
Showing 22 changed files with 1,209 additions and 73 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### Next version (not released yet)

##### Additions :tada:

- Added `SampleHeightMostDetailed` function to `Cesium3DTileset`, which asynchronously queries the height of a tileset at a list of positions. It is callable from both C++ and Blueprints.

##### Fixes :wrench:

- Drastically reduced tile mesh memory usage in UE 5.3 and 5.4 by working around a bug that causes those engine versions to add more texture coordinate sets than necessary.
Expand Down
66 changes: 66 additions & 0 deletions Source/CesiumRuntime/Private/Cesium3DTileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,72 @@ void ACesium3DTileset::SetMobility(EComponentMobility::Type NewMobility) {
}
}

void ACesium3DTileset::SampleHeightMostDetailed(
const TArray<FVector>& LongitudeLatitudeHeightArray,
FCesiumSampleHeightMostDetailedCallback OnHeightsSampled) {
if (this->_pTileset == nullptr) {
this->LoadTileset();
}

std::vector<CesiumGeospatial::Cartographic> positions;
positions.reserve(LongitudeLatitudeHeightArray.Num());

for (const FVector& position : LongitudeLatitudeHeightArray) {
positions.emplace_back(CesiumGeospatial::Cartographic::fromDegrees(
position.X,
position.Y,
position.Z));
}

CesiumAsync::Future<Cesium3DTilesSelection::SampleHeightResult> future =
this->_pTileset
? this->_pTileset->sampleHeightMostDetailed(positions)
: getAsyncSystem().createResolvedFuture(
Cesium3DTilesSelection::SampleHeightResult{
std::move(positions),
std::vector<bool>(positions.size(), false),
{"Could not sample heights from tileset because it has not "
"been created."}});

std::move(future).thenImmediately(
[this, OnHeightsSampled = std::move(OnHeightsSampled)](
Cesium3DTilesSelection::SampleHeightResult&& result) {
if (!IsValid(this))
return;

check(result.positions.size() == result.sampleSuccess.size());

// This should do nothing, but will prevent undefined behavior if
// the array sizes are unexpectedly different.
result.sampleSuccess.resize(result.positions.size(), false);

TArray<FCesiumSampleHeightResult> sampleHeightResults;
sampleHeightResults.Reserve(result.positions.size());

for (size_t i = 0; i < result.positions.size(); ++i) {
const CesiumGeospatial::Cartographic& position = result.positions[i];

FCesiumSampleHeightResult unrealResult;
unrealResult.LongitudeLatitudeHeight = FVector(
CesiumUtility::Math::radiansToDegrees(position.longitude),
CesiumUtility::Math::radiansToDegrees(position.latitude),
position.height);
unrealResult.SampleSuccess = result.sampleSuccess[i];

sampleHeightResults.Emplace(std::move(unrealResult));
}

TArray<FString> warnings;
warnings.Reserve(result.warnings.size());

for (const std::string& warning : result.warnings) {
warnings.Emplace(UTF8_TO_TCHAR(warning.c_str()));
}

OnHeightsSampled.ExecuteIfBound(this, sampleHeightResults, warnings);
});
}

void ACesium3DTileset::SetGeoreference(
TSoftObjectPtr<ACesiumGeoreference> NewGeoreference) {
this->Georeference = NewGeoreference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#pragma once

#include "HAL/Platform.h"
#include <gsl/span>

enum class ECesiumMetadataType : uint8;
Expand Down
4 changes: 2 additions & 2 deletions Source/CesiumRuntime/Private/CesiumMetadataPrimitive.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2020-2024 CesiumGS, Inc. and Contributors

PRAGMA_DISABLE_DEPRECATION_WARNINGS

#include "CesiumMetadataPrimitive.h"
#include "CesiumGltf/Model.h"

PRAGMA_DISABLE_DEPRECATION_WARNINGS

FCesiumMetadataPrimitive::FCesiumMetadataPrimitive(
const FCesiumPrimitiveFeatures& PrimitiveFeatures,
const FCesiumPrimitiveMetadata& PrimitiveMetadata,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
#include "CesiumSampleHeightMostDetailedAsyncAction.h"
#include "Cesium3DTileset.h"
#include "CesiumRuntime.h"

/*static*/ UCesiumSampleHeightMostDetailedAsyncAction*
UCesiumSampleHeightMostDetailedAsyncAction::SampleHeightMostDetailed(
ACesium3DTileset* Tileset,
const TArray<FVector>& LongitudeLatitudeHeightArray) {
UCesiumSampleHeightMostDetailedAsyncAction* pAsyncAction =
NewObject<UCesiumSampleHeightMostDetailedAsyncAction>();
pAsyncAction->_pTileset = Tileset;
pAsyncAction->_longitudeLatitudeHeightArray = LongitudeLatitudeHeightArray;

return pAsyncAction;
}

void UCesiumSampleHeightMostDetailedAsyncAction::Activate() {
this->RegisterWithGameInstance(this->_pTileset);

this->_pTileset->SampleHeightMostDetailed(
this->_longitudeLatitudeHeightArray,
FCesiumSampleHeightMostDetailedCallback::CreateUObject(
this,
&UCesiumSampleHeightMostDetailedAsyncAction::RaiseOnHeightsSampled));
}

void UCesiumSampleHeightMostDetailedAsyncAction::RaiseOnHeightsSampled(
ACesium3DTileset* Tileset,
const TArray<FCesiumSampleHeightResult>& Result,
const TArray<FString>& Warnings) {
this->OnHeightsSampled.Broadcast(Result, Warnings);
this->SetReadyToDestroy();
}
61 changes: 38 additions & 23 deletions Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ struct LoadTestContext {

LoadTestContext gLoadTestContext;

DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(
DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(
TimeLoadingCommand,
FString,
loggingName,
SceneGenerationContext&,
creationContext,
SceneGenerationContext&,
playContext,
TestPass&,
pass);
Expand Down Expand Up @@ -75,31 +77,43 @@ bool TimeLoadingCommand::Update() {
bool tilesetsloaded = playContext.areTilesetsDoneLoading();
bool timedOut = pass.elapsedTime >= testTimeout;

if (tilesetsloaded || timedOut) {
pass.endMark = timeMark;
UE_LOG(LogCesium, Display, TEXT("-- Load end mark -- %s"), *loggingName);
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 (timedOut) {
UE_LOG(
LogCesium,
Error,
TEXT("TIMED OUT: Loading stopped after %.2f seconds"),
pass.elapsedTime);
} else {
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("Tileset load completed in %.2f seconds"),
TEXT("Pass completed in %.2f seconds"),
pass.elapsedTime);
}

if (pass.verifyStep)
pass.verifyStep(playContext, pass.optionalParameter);
pass.testInProgress = false;

pass.testInProgress = false;

// Command is done
return true;
// Command is done
return true;
}
}

// Let world tick, we'll come back to this command
Expand Down Expand Up @@ -163,8 +177,6 @@ bool TestCleanupCommand::Update() {
else
defaultReportStep(context.testPasses);

// Turn on the editor tileset updates so we can see what we loaded
// gLoadTestContext.creationContext.setSuspendUpdate(false);
return true;
}

Expand Down Expand Up @@ -246,8 +258,11 @@ bool RunLoadTest(
// Do our timing capture
FString loggingName = testName + "-" + pass.name;

ADD_LATENT_AUTOMATION_COMMAND(
TimeLoadingCommand(loggingName, context.playContext, pass));
ADD_LATENT_AUTOMATION_COMMAND(TimeLoadingCommand(
loggingName,
context.creationContext,
context.playContext,
pass));

ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f));

Expand Down
9 changes: 6 additions & 3 deletions Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ namespace Cesium {
struct TestPass {
typedef swl::variant<int, float> TestingParameter;
typedef std::function<void(SceneGenerationContext&, TestingParameter)>
PassCallback;
SetupCallback;
typedef std::function<
bool(SceneGenerationContext&, SceneGenerationContext&, TestingParameter)>
VerifyCallback;

FString name;
PassCallback setupStep;
PassCallback verifyStep;
SetupCallback setupStep;
VerifyCallback verifyStep;
TestingParameter optionalParameter;

bool testInProgress = false;
Expand Down
35 changes: 35 additions & 0 deletions Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include "CesiumGeoreference.h"
#include "Engine/Engine.h"

#if WITH_EDITOR
#include "Editor/EditorPerformanceSettings.h"
#endif

namespace CesiumTestHelpers {

UWorld* getGlobalWorldContext() {
Expand Down Expand Up @@ -63,6 +67,37 @@ 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));
}
Expand Down
19 changes: 18 additions & 1 deletion Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ void waitForImpl(
done.Execute();
} else {
pWorld->GetTimerManager().SetTimerForNextTick(
[done, pWorld, condition, timerHandle]() mutable {
[done,
pWorld,
condition = std::forward<T>(condition),
timerHandle]() mutable {
waitForImpl<T>(done, pWorld, std::move(condition), timerHandle);
});
}
Expand Down Expand Up @@ -135,6 +138,20 @@ FName getUniqueTag(AActor* pActor);
/// <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>
Expand Down
Loading

0 comments on commit f032377

Please sign in to comment.