I want to clamp PCG graph points to cesium terrain in UE5.4. I have created a custom node in pcg graph to achieve this functionality. This node was meant update the points position using SampleHeightMostDetailedFunction of cesium tileset. The problem I am getting is that pcg graph finishes it execution and this function is not fully executed. due to its asynchronous nature. I have tried to block the main game thread until the points are fully updated but this results in the crash of the game. Here is the detailed code for the ExecuteInternal function of the my custom pcg node.:
bool FPCGCesiumHeightAdjusterElement::ExecuteInternal(FPCGContext* Context) const
{
TRACE_CPUPROFILER_EVENT_SCOPE(FPCGCesiumHeightAdjusterElement::Execute);
check(Context);
if (!Context || !Context->SourceComponent.Get())
{
UE_LOG(LogTemp, Error, TEXT("PCG Context or Source Component is NULL!"));
return false;
}
UPCGComponent* SourceComponent = Context->SourceComponent.Get();
if (!SourceComponent)
{
UE_LOG(LogTemp, Error, TEXT("PCG Source Component is NULL!"));
return false;
}
const UPCGCesiumHeightAdjusterSettings* Settings = Context->GetInputSettings<UPCGCesiumHeightAdjusterSettings>();
if (!Settings)
{
UE_LOG(LogTemp, Error, TEXT("PCG Settings are NULL!"));
return false;
}
UWorld* World = SourceComponent->GetWorld();
if (!World)
{
UE_LOG(LogTemp, Error, TEXT("World reference is NULL!"));
return false;
}
ACesiumGeoreference* Georeference = ACesiumGeoreference::GetDefaultGeoreference(World);
if (!Georeference)
{
UE_LOG(LogTemp, Error, TEXT("CesiumGeoreference is NULL!"));
return false;
}
ACesium3DTileset* TilesetActor = nullptr;
for (TActorIterator<ACesium3DTileset> It(World); It; ++It)
{
TilesetActor = *It;
break;
}
if (!TilesetActor || !TilesetActor->GetTileset())
{
UE_LOG(LogTemp, Error, TEXT("No valid Cesium Tileset found."));
return false;
}
Cesium3DTilesSelection::Tileset* NativeTileset = TilesetActor->GetTileset();
if (!NativeTileset)
{
UE_LOG(LogTemp, Error, TEXT("Native Cesium Tileset pointer is NULL."));
return false;
}
int32 TotalPointsQueued = 0;
for (FPCGTaggedData& InputData : Context->InputData.TaggedData)
{
const UPCGSpatialData* SpatialData = Cast<UPCGSpatialData>(InputData.Data);
if (!SpatialData)
continue;
const UPCGPointData* PointData = SpatialData->ToPointData(Context);
if (!PointData)
continue;
UPCGPointData* MutablePointData = NewObject<UPCGPointData>(SourceComponent);
if (!MutablePointData)
{
UE_LOG(LogTemp, Error, TEXT("Failed to create MutablePointData!"));
continue;
}
MutablePointData->InitializeFromData(PointData);
// ❗️ Copy the points manually
TArray<FPCGPoint> OriginalPoints = PointData->GetPoints();
MutablePointData->GetMutablePoints() = OriginalPoints;
TArray<FPCGPoint>& Points = MutablePointData->GetMutablePoints();
std::vector<CesiumGeospatial::Cartographic> CesiumPositions;
for (const FPCGPoint& Point : Points)
{
FVector WorldLocation = Point.Transform.GetLocation();
FVector LLH = Georeference->TransformUnrealPositionToLongitudeLatitudeHeight(WorldLocation);
double Longitude = CesiumUtility::Math::degreesToRadians(LLH.X);
double Latitude = CesiumUtility::Math::degreesToRadians(LLH.Y);
CesiumPositions.emplace_back(Longitude, Latitude, 0.0);
}
TotalPointsQueued += CesiumPositions.size();
// Async height sampling
NativeTileset->sampleHeightMostDetailed(CesiumPositions).thenInMainThread(
[MutablePointData, Georeference](const Cesium3DTilesSelection::SampleHeightResult& Result)
{
TArray<FPCGPoint>& PointsRef = MutablePointData->GetMutablePoints();
if (Result.positions.size() != PointsRef.Num() || Result.sampleSuccess.size() != PointsRef.Num())
{
UE_LOG(LogTemp, Error, TEXT("Mismatch between Cesium result count and PCG points."));
return;
}
int32 UpdatedCount = 0;
for (int32 i = 0; i < PointsRef.Num(); ++i)
{
if (Result.sampleSuccess[i])
{
const auto& Carto = Result.positions[i];
// Convert back to degrees for Unreal
double LongitudeDeg = Carto.longitude * 180.0 / PI;
double LatitudeDeg = Carto.latitude * 180.0 / PI;
double Height = Carto.height;
FVector LLH(LongitudeDeg, LatitudeDeg, Height);
FVector Before = PointsRef[i].Transform.GetLocation();
FVector AdjustedLocation = Georeference->TransformLongitudeLatitudeHeightPositionToUnreal(LLH);
PointsRef[i].Transform.SetLocation(AdjustedLocation);
UpdatedCount++;
UE_LOG(LogTemp, Log,
TEXT("Point %d:\n - BEFORE: %s\n - AFTER: %s\n - Sampled LLH: Lon=%.6f, Lat=%.6f, Height=%.2f m"),
i,
*Before.ToString(),
*AdjustedLocation.ToString(),
LongitudeDeg,
LatitudeDeg,
Height
);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Point %d: Height sampling failed."), i);
}
}
UE_LOG(LogTemp, Warning, TEXT("Cesium Async Height Adjustment Complete: %d points updated."), UpdatedCount);
}
);
// Output result
FPCGTaggedData& OutputData = Context->OutputData.TaggedData.Add_GetRef(InputData);
OutputData.Data = MutablePointData;
}
UE_LOG(LogTemp, Warning, TEXT("Queued %d total points for Cesium height sampling."), TotalPointsQueued);
return true;
}