// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "Cesium3DTileset.h" #include "Async/Async.h" #include "Camera/CameraTypes.h" #include "Camera/PlayerCameraManager.h" #include "Cesium3DTilesSelection/EllipsoidTilesetLoader.h" #include "Cesium3DTilesSelection/Tile.h" #include "Cesium3DTilesSelection/TilesetLoadFailureDetails.h" #include "Cesium3DTilesSelection/TilesetOptions.h" #include "Cesium3DTilesSelection/TilesetSharedAssetSystem.h" #include "Cesium3DTilesetLoadFailureDetails.h" #include "Cesium3DTilesetRoot.h" #include "CesiumActors.h" #include "CesiumAsync/SharedAssetDepot.h" #include "CesiumBoundingVolumeComponent.h" #include "CesiumCamera.h" #include "CesiumCameraManager.h" #include "CesiumCommon.h" #include "CesiumCustomVersion.h" #include "CesiumGeospatial/GlobeTransforms.h" #include "CesiumGltf/ImageAsset.h" #include "CesiumGltf/Ktx2TranscodeTargets.h" #include "CesiumGltfComponent.h" #include "CesiumGltfPointsSceneProxyUpdater.h" #include "CesiumGltfPrimitiveComponent.h" #include "CesiumIonClient/Connection.h" #include "CesiumRasterOverlay.h" #include "CesiumRuntime.h" #include "CesiumRuntimeSettings.h" #include "CesiumTileExcluder.h" #include "CesiumViewExtension.h" #include "Components/SceneCaptureComponent2D.h" #include "Engine/Engine.h" #include "Engine/LocalPlayer.h" #include "Engine/SceneCapture2D.h" #include "Engine/Texture.h" #include "Engine/Texture2D.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/World.h" #include "EngineUtils.h" #include "GameFramework/PlayerController.h" #include "Kismet/GameplayStatics.h" #include "LevelSequenceActor.h" #include "LevelSequencePlayer.h" #include "Math/UnrealMathUtility.h" #include "PixelFormat.h" #include "StereoRendering.h" #include "UnrealPrepareRendererResources.h" #include "VecMath.h" #include #include #include #ifdef CESIUM_DEBUG_TILE_STATES #include "HAL/PlatformFileManager.h" #include #endif FCesium3DTilesetLoadFailure OnCesium3DTilesetLoadFailure{}; #if WITH_EDITOR #include "Editor.h" #include "EditorViewportClient.h" #include "FileHelpers.h" #include "LevelEditorViewport.h" #endif // Avoid complaining about the deprecated metadata struct PRAGMA_DISABLE_DEPRECATION_WARNINGS // Sets default values ACesium3DTileset::ACesium3DTileset() : AActor(), Georeference(nullptr), ResolvedGeoreference(nullptr), CreditSystem(nullptr), _pTileset(nullptr), #ifdef CESIUM_DEBUG_TILE_STATES _pStateDebug(nullptr), #endif _lastTilesRendered(0), _lastWorkerThreadTileLoadQueueLength(0), _lastMainThreadTileLoadQueueLength(0), _lastTilesVisited(0), _lastTilesCulled(0), _lastTilesOccluded(0), _lastTilesWaitingForOcclusionResults(0), _lastMaxDepthVisited(0), _captureMovieMode{false}, _beforeMoviePreloadAncestors{PreloadAncestors}, _beforeMoviePreloadSiblings{PreloadSiblings}, _beforeMovieLoadingDescendantLimit{LoadingDescendantLimit}, _beforeMovieUseLodTransitions{true}, _tilesetsBeingDestroyed(0) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickGroup = ETickingGroup::TG_PostUpdateWork; #if WITH_EDITOR this->SetIsSpatiallyLoaded(false); #endif this->SetActorEnableCollision(true); this->RootComponent = CreateDefaultSubobject(TEXT("Tileset")); this->Root = this->RootComponent; PlatformName = UGameplayStatics::GetPlatformName(); } ACesium3DTileset::~ACesium3DTileset() { this->DestroyTileset(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS TSoftObjectPtr ACesium3DTileset::GetGeoreference() const { return this->Georeference; } void ACesium3DTileset::SetMobility(EComponentMobility::Type NewMobility) { if (NewMobility != this->RootComponent->Mobility) { this->RootComponent->SetMobility(NewMobility); DestroyTileset(); } } void ACesium3DTileset::SampleHeightMostDetailed( const TArray& LongitudeLatitudeHeightArray, FCesiumSampleHeightMostDetailedCallback OnHeightsSampled) { // It's possible to call this function before a Tick happens, so make sure // that the necessary variables are resolved. this->ResolveGeoreference(); this->ResolveCameraManager(); this->ResolveCreditSystem(); if (this->_pTileset == nullptr) { this->LoadTileset(); } std::vector positions; positions.reserve(LongitudeLatitudeHeightArray.Num()); for (const FVector& position : LongitudeLatitudeHeightArray) { positions.emplace_back(CesiumGeospatial::Cartographic::fromDegrees( position.X, position.Y, position.Z)); } auto sampleHeights = [this, &positions]() mutable { if (this->_pTileset) { return this->_pTileset->sampleHeightMostDetailed(positions) .catchImmediately([positions = std::move(positions)]( std::exception&& exception) mutable { std::vector sampleSuccess(positions.size(), false); return Cesium3DTilesSelection::SampleHeightResult{ std::move(positions), std::move(sampleSuccess), {exception.what()}}; }); } else { std::vector sampleSuccess(positions.size(), false); return getAsyncSystem().createResolvedFuture( Cesium3DTilesSelection::SampleHeightResult{ std::move(positions), std::move(sampleSuccess), {"Could not sample heights from tileset because it has not " "been created."}}); } }; sampleHeights().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 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 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 NewGeoreference) { this->Georeference = NewGeoreference; this->InvalidateResolvedGeoreference(); this->ResolveGeoreference(); } ACesiumGeoreference* ACesium3DTileset::ResolveGeoreference() { if (IsValid(this->ResolvedGeoreference)) { return this->ResolvedGeoreference; } if (IsValid(this->Georeference.Get())) { this->ResolvedGeoreference = this->Georeference.Get(); } else { this->ResolvedGeoreference = ACesiumGeoreference::GetDefaultGeoreferenceForActor(this); } UCesium3DTilesetRoot* pRoot = Cast(this->RootComponent); if (pRoot) { this->ResolvedGeoreference->OnGeoreferenceUpdated.AddUniqueDynamic( pRoot, &UCesium3DTilesetRoot::HandleGeoreferenceUpdated); this->ResolvedGeoreference->OnEllipsoidChanged.AddUniqueDynamic( this, &ACesium3DTileset::HandleOnGeoreferenceEllipsoidChanged); // Update existing tile positions, if any. pRoot->HandleGeoreferenceUpdated(); } return this->ResolvedGeoreference; } void ACesium3DTileset::InvalidateResolvedGeoreference() { if (IsValid(this->ResolvedGeoreference)) { this->ResolvedGeoreference->OnGeoreferenceUpdated.RemoveAll( this->RootComponent); } this->ResolvedGeoreference = nullptr; } TSoftObjectPtr ACesium3DTileset::GetCreditSystem() const { return this->CreditSystem; } void ACesium3DTileset::SetCreditSystem( TSoftObjectPtr NewCreditSystem) { this->CreditSystem = NewCreditSystem; this->InvalidateResolvedCreditSystem(); this->ResolveCreditSystem(); } ACesiumCreditSystem* ACesium3DTileset::ResolveCreditSystem() { if (IsValid(this->ResolvedCreditSystem)) { return this->ResolvedCreditSystem; } if (IsValid(this->CreditSystem.Get())) { this->ResolvedCreditSystem = this->CreditSystem.Get(); } else { this->ResolvedCreditSystem = ACesiumCreditSystem::GetDefaultCreditSystem(this); } // Refresh the tileset so it uses the new credit system. this->RefreshTileset(); return this->ResolvedCreditSystem; } void ACesium3DTileset::InvalidateResolvedCreditSystem() { this->ResolvedCreditSystem = nullptr; this->RefreshTileset(); } TSoftObjectPtr ACesium3DTileset::GetCameraManager() const { return this->CameraManager; } void ACesium3DTileset::SetCameraManager( TSoftObjectPtr NewCameraManager) { this->CameraManager = NewCameraManager; this->InvalidateResolvedCameraManager(); this->ResolveCameraManager(); } ACesiumCameraManager* ACesium3DTileset::ResolveCameraManager() { if (IsValid(this->ResolvedCameraManager)) { return this->ResolvedCameraManager; } if (IsValid(this->CameraManager.Get())) { this->ResolvedCameraManager = this->CameraManager.Get(); } else { this->ResolvedCameraManager = ACesiumCameraManager::GetDefaultCameraManager(this); } return this->ResolvedCameraManager; } void ACesium3DTileset::InvalidateResolvedCameraManager() { this->ResolvedCameraManager = nullptr; this->RefreshTileset(); } void ACesium3DTileset::RefreshTileset() { this->DestroyTileset(); } void ACesium3DTileset::TroubleshootToken() { OnCesium3DTilesetIonTroubleshooting.Broadcast(this); } void ACesium3DTileset::AddFocusViewportDelegate() { #if WITH_EDITOR FEditorDelegates::OnFocusViewportOnActors.AddLambda( [this](const TArray& actors) { if (actors.Num() == 1 && actors[0] == this) { this->OnFocusEditorViewportOnThis(); } }); #endif // WITH_EDITOR } void ACesium3DTileset::PostInitProperties() { UE_LOG( LogCesium, Verbose, TEXT("Called PostInitProperties on actor %s"), *this->GetName()); Super::PostInitProperties(); AddFocusViewportDelegate(); UCesiumRuntimeSettings* pSettings = GetMutableDefault(); if (pSettings) { CanEnableOcclusionCulling = pSettings->EnableExperimentalOcclusionCullingFeature; #if WITH_EDITOR pSettings->OnSettingChanged().AddUObject( this, &ACesium3DTileset::RuntimeSettingsChanged); #endif } } void ACesium3DTileset::SetUseLodTransitions(bool InUseLodTransitions) { if (InUseLodTransitions != this->UseLodTransitions) { this->UseLodTransitions = InUseLodTransitions; this->DestroyTileset(); } } void ACesium3DTileset::SetTilesetSource(ETilesetSource InSource) { if (InSource != this->TilesetSource) { this->DestroyTileset(); this->TilesetSource = InSource; } } namespace { bool MapsAreEqual( const TMap& Lhs, const TMap& Rhs) { if (Lhs.Num() != Rhs.Num()) { return false; } for (const auto& [Key, Value] : Lhs) { const FString* RhsVal = Rhs.Find(Key); if (!RhsVal || *RhsVal != Value) { return false; } } return true; } } // namespace void ACesium3DTileset::SetRequestHeaders( const TMap& InRequestHeaders) { if (!MapsAreEqual(InRequestHeaders, this->RequestHeaders)) { this->DestroyTileset(); this->RequestHeaders = InRequestHeaders; } } void ACesium3DTileset::SetUrl(const FString& InUrl) { if (InUrl != this->Url) { if (this->TilesetSource == ETilesetSource::FromUrl) { this->DestroyTileset(); } this->Url = InUrl; } } void ACesium3DTileset::SetIonAssetID(int64 InAssetID) { if (InAssetID >= 0 && InAssetID != this->IonAssetID) { if (this->TilesetSource == ETilesetSource::FromCesiumIon) { this->DestroyTileset(); } this->IonAssetID = InAssetID; } } void ACesium3DTileset::SetIonAccessToken(const FString& InAccessToken) { if (this->IonAccessToken != InAccessToken) { if (this->TilesetSource == ETilesetSource::FromCesiumIon) { this->DestroyTileset(); } this->IonAccessToken = InAccessToken; } } void ACesium3DTileset::SetCesiumIonServer(UCesiumIonServer* Server) { if (this->CesiumIonServer != Server) { if (this->TilesetSource == ETilesetSource::FromCesiumIon) { this->DestroyTileset(); } this->CesiumIonServer = Server; } } void ACesium3DTileset::SetMaximumScreenSpaceError( double InMaximumScreenSpaceError) { if (MaximumScreenSpaceError != InMaximumScreenSpaceError) { MaximumScreenSpaceError = InMaximumScreenSpaceError; FCesiumGltfPointsSceneProxyUpdater::UpdateSettingsInProxies(this); } } bool ACesium3DTileset::GetEnableOcclusionCulling() const { return GetDefault() ->EnableExperimentalOcclusionCullingFeature && EnableOcclusionCulling; } void ACesium3DTileset::SetEnableOcclusionCulling(bool bEnableOcclusionCulling) { if (this->EnableOcclusionCulling != bEnableOcclusionCulling) { this->EnableOcclusionCulling = bEnableOcclusionCulling; this->DestroyTileset(); } } void ACesium3DTileset::SetOcclusionPoolSize(int32 newOcclusionPoolSize) { if (this->OcclusionPoolSize != newOcclusionPoolSize) { this->OcclusionPoolSize = newOcclusionPoolSize; this->DestroyTileset(); } } void ACesium3DTileset::SetDelayRefinementForOcclusion( bool bDelayRefinementForOcclusion) { if (this->DelayRefinementForOcclusion != bDelayRefinementForOcclusion) { this->DelayRefinementForOcclusion = bDelayRefinementForOcclusion; this->DestroyTileset(); } } void ACesium3DTileset::SetCreatePhysicsMeshes(bool bCreatePhysicsMeshes) { if (this->CreatePhysicsMeshes != bCreatePhysicsMeshes) { this->CreatePhysicsMeshes = bCreatePhysicsMeshes; this->DestroyTileset(); } } void ACesium3DTileset::SetCreateNavCollision(bool bCreateNavCollision) { if (this->CreateNavCollision != bCreateNavCollision) { this->CreateNavCollision = bCreateNavCollision; this->DestroyTileset(); } } void ACesium3DTileset::SetAlwaysIncludeTangents(bool bAlwaysIncludeTangents) { if (this->AlwaysIncludeTangents != bAlwaysIncludeTangents) { this->AlwaysIncludeTangents = bAlwaysIncludeTangents; this->DestroyTileset(); } } void ACesium3DTileset::SetGenerateSmoothNormals(bool bGenerateSmoothNormals) { if (this->GenerateSmoothNormals != bGenerateSmoothNormals) { this->GenerateSmoothNormals = bGenerateSmoothNormals; this->DestroyTileset(); } } void ACesium3DTileset::SetEnableWaterMask(bool bEnableMask) { if (this->EnableWaterMask != bEnableMask) { this->EnableWaterMask = bEnableMask; this->DestroyTileset(); } } void ACesium3DTileset::SetIgnoreKhrMaterialsUnlit( bool bIgnoreKhrMaterialsUnlit) { if (this->IgnoreKhrMaterialsUnlit != bIgnoreKhrMaterialsUnlit) { this->IgnoreKhrMaterialsUnlit = bIgnoreKhrMaterialsUnlit; this->DestroyTileset(); } } void ACesium3DTileset::SetMaterial(UMaterialInterface* InMaterial) { if (this->Material != InMaterial) { this->Material = InMaterial; this->DestroyTileset(); } } void ACesium3DTileset::SetTranslucentMaterial(UMaterialInterface* InMaterial) { if (this->TranslucentMaterial != InMaterial) { this->TranslucentMaterial = InMaterial; this->DestroyTileset(); } } void ACesium3DTileset::SetWaterMaterial(UMaterialInterface* InMaterial) { if (this->WaterMaterial != InMaterial) { this->WaterMaterial = InMaterial; this->DestroyTileset(); } } void ACesium3DTileset::SetCustomDepthParameters( FCustomDepthParameters InCustomDepthParameters) { if (this->CustomDepthParameters != InCustomDepthParameters) { this->CustomDepthParameters = InCustomDepthParameters; this->DestroyTileset(); } } void ACesium3DTileset::SetPointCloudShading( FCesiumPointCloudShading InPointCloudShading) { if (PointCloudShading != InPointCloudShading) { PointCloudShading = InPointCloudShading; FCesiumGltfPointsSceneProxyUpdater::UpdateSettingsInProxies(this); } } void ACesium3DTileset::PlayMovieSequencer() { this->_beforeMoviePreloadAncestors = this->PreloadAncestors; this->_beforeMoviePreloadSiblings = this->PreloadSiblings; this->_beforeMovieLoadingDescendantLimit = this->LoadingDescendantLimit; this->_beforeMovieUseLodTransitions = this->UseLodTransitions; this->_captureMovieMode = true; this->PreloadAncestors = false; this->PreloadSiblings = false; this->LoadingDescendantLimit = 10000; this->UseLodTransitions = false; } void ACesium3DTileset::StopMovieSequencer() { this->_captureMovieMode = false; this->PreloadAncestors = this->_beforeMoviePreloadAncestors; this->PreloadSiblings = this->_beforeMoviePreloadSiblings; this->LoadingDescendantLimit = this->_beforeMovieLoadingDescendantLimit; this->UseLodTransitions = this->_beforeMovieUseLodTransitions; } void ACesium3DTileset::PauseMovieSequencer() { this->StopMovieSequencer(); } #if WITH_EDITOR void ACesium3DTileset::OnFocusEditorViewportOnThis() { UE_LOG( LogCesium, Verbose, TEXT("Called OnFocusEditorViewportOnThis on actor %s"), *this->GetName()); struct CalculateECEFCameraPosition { const CesiumGeospatial::Ellipsoid& ellipsoid; glm::dvec3 operator()(const CesiumGeometry::BoundingSphere& sphere) { const glm::dvec3& center = sphere.getCenter(); glm::dmat4 ENU = CesiumGeospatial::GlobeTransforms::eastNorthUpToFixedFrame( center, ellipsoid); glm::dvec3 offset = sphere.getRadius() * glm::normalize( glm::dvec3(ENU[0]) + glm::dvec3(ENU[1]) + glm::dvec3(ENU[2])); glm::dvec3 position = center + offset; return position; } glm::dvec3 operator()(const CesiumGeometry::OrientedBoundingBox& orientedBoundingBox) { const glm::dvec3& center = orientedBoundingBox.getCenter(); glm::dmat4 ENU = CesiumGeospatial::GlobeTransforms::eastNorthUpToFixedFrame( center, ellipsoid); const glm::dmat3& halfAxes = orientedBoundingBox.getHalfAxes(); glm::dvec3 offset = glm::length(halfAxes[0] + halfAxes[1] + halfAxes[2]) * glm::normalize( glm::dvec3(ENU[0]) + glm::dvec3(ENU[1]) + glm::dvec3(ENU[2])); glm::dvec3 position = center + offset; return position; } glm::dvec3 operator()(const CesiumGeospatial::BoundingRegion& boundingRegion) { return (*this)(boundingRegion.getBoundingBox()); } glm::dvec3 operator()(const CesiumGeospatial::BoundingRegionWithLooseFittingHeights& boundingRegionWithLooseFittingHeights) { return (*this)(boundingRegionWithLooseFittingHeights.getBoundingRegion() .getBoundingBox()); } glm::dvec3 operator()(const CesiumGeospatial::S2CellBoundingVolume& s2) { return (*this)(s2.computeBoundingRegion()); } }; const Cesium3DTilesSelection::Tile* pRootTile = this->_pTileset->getRootTile(); if (!pRootTile) { return; } const Cesium3DTilesSelection::BoundingVolume& boundingVolume = pRootTile->getBoundingVolume(); ACesiumGeoreference* pGeoreference = this->ResolveGeoreference(); const CesiumGeospatial::Ellipsoid& ellipsoid = pGeoreference->GetEllipsoid()->GetNativeEllipsoid(); // calculate unreal camera position glm::dvec3 ecefCameraPosition = std::visit(CalculateECEFCameraPosition{ellipsoid}, boundingVolume); FVector unrealCameraPosition = pGeoreference->TransformEarthCenteredEarthFixedPositionToUnreal( VecMath::createVector(ecefCameraPosition)); // calculate unreal camera orientation glm::dvec3 ecefCenter = Cesium3DTilesSelection::getBoundingVolumeCenter(boundingVolume); FVector unrealCenter = pGeoreference->TransformEarthCenteredEarthFixedPositionToUnreal( VecMath::createVector(ecefCenter)); FVector unrealCameraFront = (unrealCenter - unrealCameraPosition).GetSafeNormal(); FVector unrealCameraRight = FVector::CrossProduct(FVector::ZAxisVector, unrealCameraFront) .GetSafeNormal(); FVector unrealCameraUp = FVector::CrossProduct(unrealCameraFront, unrealCameraRight) .GetSafeNormal(); FRotator cameraRotator = FMatrix( unrealCameraFront, unrealCameraRight, unrealCameraUp, FVector::ZeroVector) .Rotator(); // Update all viewports. for (FLevelEditorViewportClient* LinkedViewportClient : GEditor->GetLevelViewportClients()) { // Dont move camera attach to an actor if (!LinkedViewportClient->IsAnyActorLocked()) { FViewportCameraTransform& ViewTransform = LinkedViewportClient->GetViewTransform(); LinkedViewportClient->SetViewRotation(cameraRotator); LinkedViewportClient->SetViewLocation(unrealCameraPosition); LinkedViewportClient->Invalidate(); } } } #endif const glm::dmat4& ACesium3DTileset::GetCesiumTilesetToUnrealRelativeWorldTransform() const { return Cast(this->RootComponent) ->GetCesiumTilesetToUnrealRelativeWorldTransform(); } void ACesium3DTileset::UpdateTransformFromCesium() { const glm::dmat4& CesiumToUnreal = this->GetCesiumTilesetToUnrealRelativeWorldTransform(); TArray gltfComponents; this->GetComponents(gltfComponents); for (UCesiumGltfComponent* pGltf : gltfComponents) { pGltf->UpdateTransformFromCesium(CesiumToUnreal); } if (this->BoundingVolumePoolComponent) { this->BoundingVolumePoolComponent->UpdateTransformFromCesium( CesiumToUnreal); } } void ACesium3DTileset::HandleOnGeoreferenceEllipsoidChanged( UCesiumEllipsoid* OldEllipsoid, UCesiumEllipsoid* NewEllpisoid) { UE_LOG(LogCesium, Warning, TEXT("Ellipsoid changed")); this->RefreshTileset(); } // Called when the game starts or when spawned void ACesium3DTileset::BeginPlay() { Super::BeginPlay(); this->ResolveGeoreference(); this->ResolveCameraManager(); this->ResolveCreditSystem(); this->LoadTileset(); // Search for level sequence. for (auto sequenceActorIt = TActorIterator(GetWorld()); sequenceActorIt; ++sequenceActorIt) { ALevelSequenceActor* sequenceActor = *sequenceActorIt; if (!IsValid(sequenceActor->GetSequencePlayer())) { continue; } FScriptDelegate playMovieSequencerDelegate; playMovieSequencerDelegate.BindUFunction(this, FName("PlayMovieSequencer")); sequenceActor->GetSequencePlayer()->OnPlay.Add(playMovieSequencerDelegate); FScriptDelegate stopMovieSequencerDelegate; stopMovieSequencerDelegate.BindUFunction(this, FName("StopMovieSequencer")); sequenceActor->GetSequencePlayer()->OnStop.Add(stopMovieSequencerDelegate); FScriptDelegate pauseMovieSequencerDelegate; pauseMovieSequencerDelegate.BindUFunction( this, FName("PauseMovieSequencer")); sequenceActor->GetSequencePlayer()->OnPause.Add( pauseMovieSequencerDelegate); } } void ACesium3DTileset::OnConstruction(const FTransform& Transform) { this->ResolveGeoreference(); this->ResolveCameraManager(); this->ResolveCreditSystem(); this->LoadTileset(); // Hide all existing tiles. The still-visible ones will be shown next time we // tick. But if update is suspended, leave the components in their current // state. if (!this->SuspendUpdate) { TArray gltfComponents; this->GetComponents(gltfComponents); for (UCesiumGltfComponent* pGltf : gltfComponents) { if (pGltf && IsValid(pGltf) && pGltf->IsVisible()) { pGltf->SetVisibility(false, true); pGltf->SetCollisionEnabled(ECollisionEnabled::NoCollision); } } } } void ACesium3DTileset::NotifyHit( UPrimitiveComponent* MyComp, AActor* Other, UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) { // std::cout << "Hit face index: " << Hit.FaceIndex << std::endl; // FHitResult detailedHit; // FCollisionQueryParams params; // params.bReturnFaceIndex = true; // params.bTraceComplex = true; // MyComp->LineTraceComponent(detailedHit, Hit.TraceStart, Hit.TraceEnd, // params); // std::cout << "Hit face index 2: " << detailedHit.FaceIndex << std::endl; } void ACesium3DTileset::UpdateLoadStatus() { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateLoadStatus) float nativeLoadProgress = this->_pTileset->computeLoadProgress(); // If native tileset still loading, just copy its progress if (nativeLoadProgress < 100) { this->LoadProgress = nativeLoadProgress; return; } // Native tileset is 100% loaded, but there might be a few frames where // nothing needs to be loaded as we are waiting for occlusion results to come // back, which means we are not done with loading all the tiles in the tileset // yet. Interpret this as 99% (almost) done if (this->_lastTilesWaitingForOcclusionResults > 0) { this->LoadProgress = 99; return; } // If we have tiles to hide next frame, we haven't completely finished loading // yet. We need to tick once more. We're really close to done. if (!this->_tilesToHideNextFrame.empty()) { this->LoadProgress = glm::min(this->LoadProgress, 99.9999f); return; } // We can now report 100 percent loaded float lastLoadProgress = this->LoadProgress; this->LoadProgress = 100; // Only broadcast the update when we first hit 100%, not everytime if (lastLoadProgress != LoadProgress) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::BroadcastOnTilesetLoaded) // Tileset just finished loading, we broadcast the update UE_LOG(LogCesium, Verbose, TEXT("Broadcasting OnTileLoaded")); OnTilesetLoaded.Broadcast(); } } namespace { const TSharedRef& getCesiumViewExtension() { static TSharedRef cesiumViewExtension = GEngine->ViewExtensions->NewExtension(); return cesiumViewExtension; } } // namespace void ACesium3DTileset::LoadTileset() { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadTileset) if (this->_pTileset) { // Tileset already loaded, do nothing. return; } UWorld* pWorld = this->GetWorld(); if (!pWorld) { return; } AWorldSettings* pWorldSettings = pWorld->GetWorldSettings(); if (pWorldSettings && pWorldSettings->bEnableWorldBoundsChecks) { UE_LOG( LogCesium, Warning, TEXT( "\"Enable World Bounds Checks\" in the world settings is currently enabled. Please consider disabling it to avoid potential issues."), *this->Url); } // Make sure we have a valid Cesium ion server if we need one. if (this->TilesetSource == ETilesetSource::FromCesiumIon && !IsValid(this->CesiumIonServer)) { this->Modify(); this->CesiumIonServer = UCesiumIonServer::GetServerForNewObjects(); } const TSharedRef& cesiumViewExtension = getCesiumViewExtension(); const std::shared_ptr& pAssetAccessor = getAssetAccessor(); const CesiumAsync::AsyncSystem& asyncSystem = getAsyncSystem(); // Both the feature flag and the CesiumViewExtension are global, not owned by // the Tileset. We're just applying one to the other here out of convenience. cesiumViewExtension->SetEnabled( GetDefault() ->EnableExperimentalOcclusionCullingFeature); TArray rasterOverlays; this->GetComponents(rasterOverlays); TArray tileExcluders; this->GetComponents(tileExcluders); const UCesiumFeaturesMetadataComponent* pFeaturesMetadataComponent = this->FindComponentByClass(); // Check if this component exists for backwards compatibility. PRAGMA_DISABLE_DEPRECATION_WARNINGS const UDEPRECATED_CesiumEncodedMetadataComponent* pEncodedMetadataComponent = this->FindComponentByClass(); this->_featuresMetadataDescription = std::nullopt; this->_metadataDescription_DEPRECATED = std::nullopt; if (pFeaturesMetadataComponent) { FCesiumFeaturesMetadataDescription& description = this->_featuresMetadataDescription.emplace(); description.Features = {pFeaturesMetadataComponent->FeatureIdSets}; description.PrimitiveMetadata = { pFeaturesMetadataComponent->PropertyTextureNames}; description.ModelMetadata = { pFeaturesMetadataComponent->PropertyTables, pFeaturesMetadataComponent->PropertyTextures}; } else if (pEncodedMetadataComponent) { UE_LOG( LogCesium, Warning, TEXT( "CesiumEncodedMetadataComponent is deprecated. Use CesiumFeaturesMetadataComponent instead.")); this->_metadataDescription_DEPRECATED = { pEncodedMetadataComponent->FeatureTables, pEncodedMetadataComponent->FeatureTextures}; } PRAGMA_ENABLE_DEPRECATION_WARNINGS this->_cesiumViewExtension = cesiumViewExtension; if (GetDefault() ->EnableExperimentalOcclusionCullingFeature && this->EnableOcclusionCulling && !this->BoundingVolumePoolComponent) { const glm::dmat4& cesiumToUnreal = GetCesiumTilesetToUnrealRelativeWorldTransform(); this->BoundingVolumePoolComponent = NewObject(this); this->BoundingVolumePoolComponent->SetFlags( RF_Transient | RF_DuplicateTransient | RF_TextExportTransient); this->BoundingVolumePoolComponent->RegisterComponent(); this->BoundingVolumePoolComponent->UpdateTransformFromCesium( cesiumToUnreal); } if (this->BoundingVolumePoolComponent) { this->BoundingVolumePoolComponent->initPool(this->OcclusionPoolSize); } CesiumGeospatial::Ellipsoid pNativeEllipsoid = this->ResolveGeoreference()->GetEllipsoid()->GetNativeEllipsoid(); ACesiumCreditSystem* pCreditSystem = this->ResolvedCreditSystem; Cesium3DTilesSelection::TilesetExternals externals{ pAssetAccessor, std::make_shared(this), asyncSystem, pCreditSystem ? pCreditSystem->GetExternalCreditSystem() : nullptr, spdlog::default_logger(), (GetDefault() ->EnableExperimentalOcclusionCullingFeature && this->EnableOcclusionCulling && this->BoundingVolumePoolComponent) ? this->BoundingVolumePoolComponent->getPool() : nullptr}; this->_startTime = std::chrono::high_resolution_clock::now(); this->LoadProgress = 0; Cesium3DTilesSelection::TilesetOptions options; options.ellipsoid = pNativeEllipsoid; options.enableOcclusionCulling = GetDefault() ->EnableExperimentalOcclusionCullingFeature && this->EnableOcclusionCulling; options.delayRefinementForOcclusion = this->DelayRefinementForOcclusion; options.showCreditsOnScreen = ShowCreditsOnScreen; options.loadErrorCallback = [this](const Cesium3DTilesSelection::TilesetLoadFailureDetails& details) { static_assert( uint8_t(ECesium3DTilesetLoadType::CesiumIon) == uint8_t(Cesium3DTilesSelection::TilesetLoadType::CesiumIon)); static_assert( uint8_t(ECesium3DTilesetLoadType::TilesetJson) == uint8_t(Cesium3DTilesSelection::TilesetLoadType::TilesetJson)); static_assert( uint8_t(ECesium3DTilesetLoadType::Unknown) == uint8_t(Cesium3DTilesSelection::TilesetLoadType::Unknown)); uint8_t typeValue = uint8_t(details.type); assert( uint8_t(details.type) <= uint8_t(Cesium3DTilesSelection::TilesetLoadType::TilesetJson)); assert(this->_pTileset == details.pTileset); FCesium3DTilesetLoadFailureDetails ueDetails{}; ueDetails.Tileset = this; ueDetails.Type = ECesium3DTilesetLoadType(typeValue); ueDetails.HttpStatusCode = details.statusCode; ueDetails.Message = UTF8_TO_TCHAR(details.message.c_str()); // Broadcast the event from the game thread. // Even if we're already in the game thread, let the stack unwind. // Otherwise actions that destroy the Tileset will cause a deadlock. AsyncTask( ENamedThreads::GameThread, [ueDetails = std::move(ueDetails)]() { OnCesium3DTilesetLoadFailure.Broadcast(ueDetails); }); }; // Generous per-frame time limits for loading / unloading on main thread. options.mainThreadLoadingTimeLimit = 5.0; options.tileCacheUnloadTimeLimit = 5.0; options.contentOptions.generateMissingNormalsSmooth = this->GenerateSmoothNormals; // TODO: figure out why water material crashes mac #if PLATFORM_MAC #else options.contentOptions.enableWaterMask = this->EnableWaterMask; #endif CesiumGltf::SupportedGpuCompressedPixelFormats supportedFormats; supportedFormats.ETC1_RGB = GPixelFormats[EPixelFormat::PF_ETC1].Supported; supportedFormats.ETC2_RGBA = GPixelFormats[EPixelFormat::PF_ETC2_RGBA].Supported; supportedFormats.BC1_RGB = GPixelFormats[EPixelFormat::PF_DXT1].Supported; supportedFormats.BC3_RGBA = GPixelFormats[EPixelFormat::PF_DXT5].Supported; supportedFormats.BC4_R = GPixelFormats[EPixelFormat::PF_BC4].Supported; supportedFormats.BC5_RG = GPixelFormats[EPixelFormat::PF_BC5].Supported; supportedFormats.BC7_RGBA = GPixelFormats[EPixelFormat::PF_BC7].Supported; supportedFormats.ASTC_4x4_RGBA = GPixelFormats[EPixelFormat::PF_ASTC_4x4].Supported; supportedFormats.PVRTC2_4_RGBA = GPixelFormats[EPixelFormat::PF_PVRTC2].Supported; supportedFormats.ETC2_EAC_R11 = GPixelFormats[EPixelFormat::PF_ETC2_R11_EAC].Supported; supportedFormats.ETC2_EAC_RG11 = GPixelFormats[EPixelFormat::PF_ETC2_RG11_EAC].Supported; options.contentOptions.ktx2TranscodeTargets = CesiumGltf::Ktx2TranscodeTargets(supportedFormats, false); options.contentOptions.applyTextureTransform = false; options.requestHeaders.reserve(this->RequestHeaders.Num()); for (const auto& [Key, Value] : this->RequestHeaders) { options.requestHeaders.emplace_back(CesiumAsync::IAssetAccessor::THeader{ TCHAR_TO_UTF8(*Key), TCHAR_TO_UTF8(*Value)}); } switch (this->TilesetSource) { case ETilesetSource::FromEllipsoid: UE_LOG(LogCesium, Log, TEXT("Loading tileset from ellipsoid")); this->_pTileset = TUniquePtr( Cesium3DTilesSelection::EllipsoidTilesetLoader::createTileset( externals, options) .release()); break; case ETilesetSource::FromUrl: UE_LOG(LogCesium, Log, TEXT("Loading tileset from URL %s"), *this->Url); this->_pTileset = MakeUnique( externals, TCHAR_TO_UTF8(*this->Url), options); break; case ETilesetSource::FromCesiumIon: UE_LOG( LogCesium, Log, TEXT("Loading tileset for asset ID %d"), this->IonAssetID); FString token = this->IonAccessToken.IsEmpty() ? this->CesiumIonServer->DefaultIonAccessToken : this->IonAccessToken; #if WITH_EDITOR this->CesiumIonServer->ResolveApiUrl(); #endif std::string ionAssetEndpointUrl = TCHAR_TO_UTF8(*this->CesiumIonServer->ApiUrl); if (!ionAssetEndpointUrl.empty()) { // Make sure the URL ends with a slash if (!ionAssetEndpointUrl.empty() && *ionAssetEndpointUrl.rbegin() != '/') ionAssetEndpointUrl += '/'; this->_pTileset = MakeUnique( externals, static_cast(this->IonAssetID), TCHAR_TO_UTF8(*token), options, ionAssetEndpointUrl); } break; } #ifdef CESIUM_DEBUG_TILE_STATES FString dbDirectory = FPaths::Combine( FPaths::ProjectSavedDir(), TEXT("CesiumDebugTileStateDatabase")); IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); if (!PlatformFile.DirectoryExists(*dbDirectory)) { PlatformFile.CreateDirectory(*dbDirectory); } FString dbFile = FPaths::Combine(dbDirectory, this->GetName() + TEXT(".sqlite")); this->_pStateDebug = MakeUnique( TCHAR_TO_UTF8(*dbFile)); #endif for (UCesiumRasterOverlay* pOverlay : rasterOverlays) { if (pOverlay->IsActive()) { pOverlay->AddToTileset(); } } for (UCesiumTileExcluder* pTileExcluder : tileExcluders) { if (pTileExcluder->IsActive()) { pTileExcluder->AddToTileset(); } } switch (this->TilesetSource) { case ETilesetSource::FromEllipsoid: UE_LOG(LogCesium, Log, TEXT("Loading tileset from ellipsoid done")); break; case ETilesetSource::FromUrl: UE_LOG( LogCesium, Log, TEXT("Loading tileset from URL %s done"), *this->Url); break; case ETilesetSource::FromCesiumIon: UE_LOG( LogCesium, Log, TEXT("Loading tileset for asset ID %d done"), this->IonAssetID); break; } switch (ApplyDpiScaling) { case (EApplyDpiScaling::UseProjectDefault): _scaleUsingDPI = GetDefault()->ScaleLevelOfDetailByDPI; break; case (EApplyDpiScaling::Yes): _scaleUsingDPI = true; break; case (EApplyDpiScaling::No): _scaleUsingDPI = false; break; default: _scaleUsingDPI = true; } } void ACesium3DTileset::DestroyTileset() { if (this->_cesiumViewExtension) { this->_cesiumViewExtension = nullptr; } switch (this->TilesetSource) { case ETilesetSource::FromEllipsoid: UE_LOG(LogCesium, Verbose, TEXT("Destroying tileset from ellipsoid")); break; case ETilesetSource::FromUrl: UE_LOG( LogCesium, Verbose, TEXT("Destroying tileset from URL %s"), *this->Url); break; case ETilesetSource::FromCesiumIon: UE_LOG( LogCesium, Verbose, TEXT("Destroying tileset for asset ID %d"), this->IonAssetID); break; } // The way CesiumRasterOverlay::add is currently implemented, destroying the // tileset without removing overlays will make it impossible to add it again // once a new tileset is created (e.g. when switching between terrain // assets) TArray rasterOverlays; this->GetComponents(rasterOverlays); for (UCesiumRasterOverlay* pOverlay : rasterOverlays) { if (pOverlay->IsActive()) { pOverlay->RemoveFromTileset(); } } TArray tileExcluders; this->GetComponents(tileExcluders); for (UCesiumTileExcluder* pTileExcluder : tileExcluders) { if (pTileExcluder->IsActive()) { pTileExcluder->RemoveFromTileset(); } } if (!this->_pTileset) { return; } // Don't allow this Cesium3DTileset to be fully destroyed until // any cesium-native Tilesets it created have wrapped up any async // operations in progress and have been fully destroyed. // See IsReadyForFinishDestroy. ++this->_tilesetsBeingDestroyed; this->_pTileset->getAsyncDestructionCompleteEvent().thenInMainThread( [this]() { --this->_tilesetsBeingDestroyed; }); this->_pTileset.Reset(); switch (this->TilesetSource) { case ETilesetSource::FromEllipsoid: UE_LOG(LogCesium, Verbose, TEXT("Destroying tileset from ellipsoid done")); break; case ETilesetSource::FromUrl: UE_LOG( LogCesium, Verbose, TEXT("Destroying tileset from URL %s done"), *this->Url); break; case ETilesetSource::FromCesiumIon: UE_LOG( LogCesium, Verbose, TEXT("Destroying tileset for asset ID %d done"), this->IonAssetID); break; } } std::vector ACesium3DTileset::GetCameras() const { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CollectCameras) std::vector cameras = this->GetPlayerCameras(); std::vector sceneCaptures = this->GetSceneCaptures(); cameras.insert( cameras.end(), std::make_move_iterator(sceneCaptures.begin()), std::make_move_iterator(sceneCaptures.end())); #if WITH_EDITOR std::vector editorCameras = this->GetEditorCameras(); cameras.insert( cameras.end(), std::make_move_iterator(editorCameras.begin()), std::make_move_iterator(editorCameras.end())); #endif ACesiumCameraManager* pCameraManager = this->ResolvedCameraManager; if (pCameraManager) { const TMap& extraCameras = pCameraManager->GetCameras(); cameras.reserve(cameras.size() + extraCameras.Num()); for (auto cameraIt : extraCameras) { cameras.push_back(cameraIt.Value); } } return cameras; } std::vector ACesium3DTileset::GetPlayerCameras() const { UWorld* pWorld = this->GetWorld(); if (!pWorld) { return {}; } double worldToMeters = 100.0; AWorldSettings* pWorldSettings = pWorld->GetWorldSettings(); if (pWorldSettings) { worldToMeters = pWorldSettings->WorldToMeters; } TSharedPtr pStereoRendering = nullptr; if (GEngine) { pStereoRendering = GEngine->StereoRenderingDevice; } bool useStereoRendering = false; if (pStereoRendering && pStereoRendering->IsStereoEnabled()) { useStereoRendering = true; } std::vector cameras; cameras.reserve(pWorld->GetNumPlayerControllers()); for (auto playerControllerIt = pWorld->GetPlayerControllerIterator(); playerControllerIt; playerControllerIt++) { const TWeakObjectPtr pPlayerController = *playerControllerIt; if (pPlayerController == nullptr) { continue; } const APlayerCameraManager* pPlayerCameraManager = pPlayerController->PlayerCameraManager; if (!pPlayerCameraManager) { continue; } double fov = pPlayerCameraManager->GetFOVAngle(); FVector location; FRotator rotation; pPlayerController->GetPlayerViewPoint(location, rotation); int32 sizeX, sizeY; pPlayerController->GetViewportSize(sizeX, sizeY); if (sizeX < 1 || sizeY < 1) { continue; } float dpiScalingFactor = 1.0f; if (this->_scaleUsingDPI) { ULocalPlayer* LocPlayer = Cast(pPlayerController->Player); if (LocPlayer && LocPlayer->ViewportClient) { dpiScalingFactor = LocPlayer->ViewportClient->GetDPIScale(); } } if (useStereoRendering) { const auto leftEye = EStereoscopicEye::eSSE_LEFT_EYE; const auto rightEye = EStereoscopicEye::eSSE_RIGHT_EYE; uint32 stereoLeftSizeX = static_cast(sizeX); uint32 stereoLeftSizeY = static_cast(sizeY); uint32 stereoRightSizeX = static_cast(sizeX); uint32 stereoRightSizeY = static_cast(sizeY); if (useStereoRendering) { int32 _x; int32 _y; pStereoRendering ->AdjustViewRect(leftEye, _x, _y, stereoLeftSizeX, stereoLeftSizeY); pStereoRendering->AdjustViewRect( rightEye, _x, _y, stereoRightSizeX, stereoRightSizeY); } FVector2D stereoLeftSize(stereoLeftSizeX, stereoLeftSizeY); FVector2D stereoRightSize(stereoRightSizeX, stereoRightSizeY); if (stereoLeftSize.X >= 1.0 && stereoLeftSize.Y >= 1.0) { FVector leftEyeLocation = location; FRotator leftEyeRotation = rotation; pStereoRendering->CalculateStereoViewOffset( leftEye, leftEyeRotation, worldToMeters, leftEyeLocation); FMatrix projection = pStereoRendering->GetStereoProjectionMatrix(leftEye); // TODO: consider assymetric frustums using 4 fovs double one_over_tan_half_hfov = projection.M[0][0]; double hfov = glm::degrees(2.0 * glm::atan(1.0 / one_over_tan_half_hfov)); cameras.emplace_back( stereoLeftSize, leftEyeLocation, leftEyeRotation, hfov); } if (stereoRightSize.X >= 1.0 && stereoRightSize.Y >= 1.0) { FVector rightEyeLocation = location; FRotator rightEyeRotation = rotation; pStereoRendering->CalculateStereoViewOffset( rightEye, rightEyeRotation, worldToMeters, rightEyeLocation); FMatrix projection = pStereoRendering->GetStereoProjectionMatrix(rightEye); double one_over_tan_half_hfov = projection.M[0][0]; double hfov = glm::degrees(2.0f * glm::atan(1.0f / one_over_tan_half_hfov)); cameras.emplace_back( stereoRightSize, rightEyeLocation, rightEyeRotation, hfov); } } else { cameras.emplace_back( FVector2D(sizeX / dpiScalingFactor, sizeY / dpiScalingFactor), location, rotation, fov); } } return cameras; } std::vector ACesium3DTileset::GetSceneCaptures() const { // TODO: really USceneCaptureComponent2D can be attached to any actor, is it // worth searching every actor? Might it be better to provide an interface // where users can volunteer cameras to be used with the tile selection as // needed? TArray sceneCaptures; static TSubclassOf SceneCapture2D = ASceneCapture2D::StaticClass(); UGameplayStatics::GetAllActorsOfClass(this, SceneCapture2D, sceneCaptures); std::vector cameras; cameras.reserve(sceneCaptures.Num()); for (AActor* pActor : sceneCaptures) { ASceneCapture2D* pSceneCapture = static_cast(pActor); if (!pSceneCapture) { continue; } USceneCaptureComponent2D* pSceneCaptureComponent = pSceneCapture->GetCaptureComponent2D(); if (!pSceneCaptureComponent) { continue; } if (pSceneCaptureComponent->ProjectionType != ECameraProjectionMode::Type::Perspective) { continue; } UTextureRenderTarget2D* pRenderTarget = pSceneCaptureComponent->TextureTarget; if (!pRenderTarget) { continue; } FVector2D renderTargetSize(pRenderTarget->SizeX, pRenderTarget->SizeY); if (renderTargetSize.X < 1.0 || renderTargetSize.Y < 1.0) { continue; } FVector captureLocation = pSceneCaptureComponent->GetComponentLocation(); FRotator captureRotation = pSceneCaptureComponent->GetComponentRotation(); double captureFov = pSceneCaptureComponent->FOVAngle; cameras.emplace_back( renderTargetSize, captureLocation, captureRotation, captureFov); } return cameras; } /*static*/ Cesium3DTilesSelection::ViewState ACesium3DTileset::CreateViewStateFromViewParameters( const FCesiumCamera& camera, const glm::dmat4& unrealWorldToTileset, UCesiumEllipsoid* ellipsoid) { double horizontalFieldOfView = FMath::DegreesToRadians(camera.FieldOfViewDegrees); double actualAspectRatio; glm::dvec2 size(camera.ViewportSize.X, camera.ViewportSize.Y); if (camera.OverrideAspectRatio != 0.0f) { // Use aspect ratio and recompute effective viewport size after black bars // are added. actualAspectRatio = camera.OverrideAspectRatio; double computedX = actualAspectRatio * camera.ViewportSize.Y; double computedY = camera.ViewportSize.Y / actualAspectRatio; double barWidth = camera.ViewportSize.X - computedX; double barHeight = camera.ViewportSize.Y - computedY; if (barWidth > 0.0 && barWidth > barHeight) { // Black bars on the sides size.x = computedX; } else if (barHeight > 0.0 && barHeight > barWidth) { // Black bars on the top and bottom size.y = computedY; } } else { actualAspectRatio = camera.ViewportSize.X / camera.ViewportSize.Y; } double verticalFieldOfView = atan(tan(horizontalFieldOfView * 0.5) / actualAspectRatio) * 2.0; FVector direction = camera.Rotation.RotateVector(FVector(1.0f, 0.0f, 0.0f)); FVector up = camera.Rotation.RotateVector(FVector(0.0f, 0.0f, 1.0f)); glm::dvec3 tilesetCameraLocation = glm::dvec3( unrealWorldToTileset * glm::dvec4(camera.Location.X, camera.Location.Y, camera.Location.Z, 1.0)); glm::dvec3 tilesetCameraFront = glm::normalize(glm::dvec3( unrealWorldToTileset * glm::dvec4(direction.X, direction.Y, direction.Z, 0.0))); glm::dvec3 tilesetCameraUp = glm::normalize( glm::dvec3(unrealWorldToTileset * glm::dvec4(up.X, up.Y, up.Z, 0.0))); return Cesium3DTilesSelection::ViewState::create( tilesetCameraLocation, tilesetCameraFront, tilesetCameraUp, size, horizontalFieldOfView, verticalFieldOfView, ellipsoid->GetNativeEllipsoid()); } #if WITH_EDITOR std::vector ACesium3DTileset::GetEditorCameras() const { if (!GEditor) { return {}; } UWorld* pWorld = this->GetWorld(); if (!IsValid(pWorld)) { return {}; } // Do not include editor cameras when running in a game world (which includes // Play-in-Editor) if (pWorld->IsGameWorld()) { return {}; } const TArray& viewportClients = GEditor->GetAllViewportClients(); std::vector cameras; cameras.reserve(viewportClients.Num()); for (FEditorViewportClient* pEditorViewportClient : viewportClients) { if (!pEditorViewportClient) { continue; } if (!pEditorViewportClient->IsVisible() || !pEditorViewportClient->IsRealtime() || !pEditorViewportClient->IsPerspective()) { continue; } FRotator rotation; if (pEditorViewportClient->bUsingOrbitCamera) { rotation = (pEditorViewportClient->GetLookAtLocation() - pEditorViewportClient->GetViewLocation()) .Rotation(); } else { rotation = pEditorViewportClient->GetViewRotation(); } const FVector& location = pEditorViewportClient->GetViewLocation(); double fov = pEditorViewportClient->ViewFOV; FIntPoint offset; FIntPoint size; pEditorViewportClient->GetViewportDimensions(offset, size); if (size.X < 1 || size.Y < 1) { continue; } if (this->_scaleUsingDPI) { float dpiScalingFactor = pEditorViewportClient->GetDPIScale(); size.X = static_cast(size.X) / dpiScalingFactor; size.Y = static_cast(size.Y) / dpiScalingFactor; } if (pEditorViewportClient->IsAspectRatioConstrained()) { cameras.emplace_back( size, location, rotation, fov, pEditorViewportClient->AspectRatio); } else { cameras.emplace_back(size, location, rotation, fov); } } return cameras; } #endif bool ACesium3DTileset::ShouldTickIfViewportsOnly() const { return this->UpdateInEditor; } namespace { template void forEachRenderableTile(const auto& tiles, Func&& f) { for (Cesium3DTilesSelection::Tile* pTile : tiles) { if (!pTile || pTile->getState() != Cesium3DTilesSelection::TileLoadState::Done) { continue; } const Cesium3DTilesSelection::TileContent& content = pTile->getContent(); const Cesium3DTilesSelection::TileRenderContent* pRenderContent = content.getRenderContent(); if (!pRenderContent) { continue; } UCesiumGltfComponent* Gltf = static_cast( pRenderContent->getRenderResources()); if (!Gltf) { // When a tile does not have render resources (i.e. a glTF), then // the resources either have not yet been loaded or prepared, // or the tile is from an external tileset and does not directly // own renderable content. In both cases, the tile is ignored here. continue; } f(pTile, Gltf); } } void removeVisibleTilesFromList( std::vector& list, const std::vector& visibleTiles) { if (list.empty()) { return; } for (Cesium3DTilesSelection::Tile* pTile : visibleTiles) { auto it = std::find(list.begin(), list.end(), pTile); if (it != list.end()) { list.erase(it); } } } /** * @brief Hides the visual representations of the given tiles. * * The visual representations (i.e. the `getRendererResources` of the * tiles) are assumed to be `UCesiumGltfComponent` instances that * are made invisible by this call. * * @param tiles The tiles to hide */ void hideTiles(const std::vector& tiles) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::HideTiles) forEachRenderableTile( tiles, [](Cesium3DTilesSelection::Tile* /*pTile*/, UCesiumGltfComponent* pGltf) { if (pGltf->IsVisible()) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetVisibilityFalse) pGltf->SetVisibility(false, true); } else { // TODO: why is this happening? UE_LOG( LogCesium, Verbose, TEXT("Tile to no longer render does not have a visible Gltf")); } }); } /** * @brief Removes collision for tiles that have been removed from the render * list. This includes tiles that are fading out. */ void removeCollisionForTiles( const std::unordered_set& tiles) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::RemoveCollisionForTiles) forEachRenderableTile( tiles, [](Cesium3DTilesSelection::Tile* /*pTile*/, UCesiumGltfComponent* pGltf) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetCollisionDisabled) pGltf->SetCollisionEnabled(ECollisionEnabled::NoCollision); }); } /** * @brief Applies the actor collision settings for a newly created glTF * component * * TODO Add details here what that means * @param BodyInstance ... * @param Gltf ... */ void applyActorCollisionSettings( const FBodyInstance& BodyInstance, UCesiumGltfComponent* Gltf) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ApplyActorCollisionSettings) const TArray& ChildrenComponents = Gltf->GetAttachChildren(); for (USceneComponent* ChildComponent : ChildrenComponents) { UCesiumGltfPrimitiveComponent* PrimitiveComponent = Cast(ChildComponent); if (PrimitiveComponent != nullptr) { if (PrimitiveComponent->GetCollisionObjectType() != BodyInstance.GetObjectType()) { PrimitiveComponent->SetCollisionObjectType( BodyInstance.GetObjectType()); } const UEnum* ChannelEnum = StaticEnum(); if (ChannelEnum) { FCollisionResponseContainer responseContainer = BodyInstance.GetResponseToChannels(); PrimitiveComponent->SetCollisionResponseToChannels(responseContainer); } } } } } // namespace void ACesium3DTileset::updateTilesetOptionsFromProperties() { Cesium3DTilesSelection::TilesetOptions& options = this->_pTileset->getOptions(); options.maximumScreenSpaceError = static_cast(this->MaximumScreenSpaceError); options.maximumCachedBytes = this->MaximumCachedBytes; options.preloadAncestors = this->PreloadAncestors; options.preloadSiblings = this->PreloadSiblings; options.forbidHoles = this->ForbidHoles; options.maximumSimultaneousTileLoads = this->MaximumSimultaneousTileLoads; options.loadingDescendantLimit = this->LoadingDescendantLimit; options.enableFrustumCulling = this->EnableFrustumCulling; options.enableOcclusionCulling = GetDefault() ->EnableExperimentalOcclusionCullingFeature && this->EnableOcclusionCulling; options.showCreditsOnScreen = this->ShowCreditsOnScreen; options.delayRefinementForOcclusion = this->DelayRefinementForOcclusion; options.enableFogCulling = this->EnableFogCulling; options.enforceCulledScreenSpaceError = this->EnforceCulledScreenSpaceError; options.culledScreenSpaceError = static_cast(this->CulledScreenSpaceError); options.enableLodTransitionPeriod = this->UseLodTransitions; options.lodTransitionLength = this->LodTransitionLength; // options.kickDescendantsWhileFadingIn = false; } void ACesium3DTileset::updateLastViewUpdateResultState( const Cesium3DTilesSelection::ViewUpdateResult& result) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateLastViewUpdateResultState) if (this->DrawTileInfo) { const UWorld* World = GetWorld(); check(World); const TSoftObjectPtr Georeference = ResolveGeoreference(); check(Georeference); for (Cesium3DTilesSelection::Tile* tile : result.tilesToRenderThisFrame) { CesiumGeometry::OrientedBoundingBox obb = Cesium3DTilesSelection::getOrientedBoundingBoxFromBoundingVolume( tile->getBoundingVolume(), Georeference->GetEllipsoid()->GetNativeEllipsoid()); FVector unrealCenter = Georeference->TransformEarthCenteredEarthFixedPositionToUnreal( VecMath::createVector(obb.getCenter())); FString text = FString::Printf( TEXT("ID %s (%p)"), UTF8_TO_TCHAR( Cesium3DTilesSelection::TileIdUtilities::createTileIdString( tile->getTileID()) .c_str()), tile); DrawDebugString(World, unrealCenter, text, nullptr, FColor::Red, 0, true); } } #ifdef CESIUM_DEBUG_TILE_STATES if (this->_pStateDebug && GetWorld()->IsPlayInEditor()) { this->_pStateDebug->recordAllTileStates( result.frameNumber, *this->_pTileset); } #endif if (!this->LogSelectionStats && !this->LogSharedAssetStats) { return; } if (result.tilesToRenderThisFrame.size() != this->_lastTilesRendered || result.workerThreadTileLoadQueueLength != this->_lastWorkerThreadTileLoadQueueLength || result.mainThreadTileLoadQueueLength != this->_lastMainThreadTileLoadQueueLength || result.tilesVisited != this->_lastTilesVisited || result.culledTilesVisited != this->_lastCulledTilesVisited || result.tilesCulled != this->_lastTilesCulled || result.tilesOccluded != this->_lastTilesOccluded || result.tilesWaitingForOcclusionResults != this->_lastTilesWaitingForOcclusionResults || result.maxDepthVisited != this->_lastMaxDepthVisited) { this->_lastTilesRendered = result.tilesToRenderThisFrame.size(); this->_lastWorkerThreadTileLoadQueueLength = result.workerThreadTileLoadQueueLength; this->_lastMainThreadTileLoadQueueLength = result.mainThreadTileLoadQueueLength; this->_lastTilesVisited = result.tilesVisited; this->_lastCulledTilesVisited = result.culledTilesVisited; this->_lastTilesCulled = result.tilesCulled; this->_lastTilesOccluded = result.tilesOccluded; this->_lastTilesWaitingForOcclusionResults = result.tilesWaitingForOcclusionResults; this->_lastMaxDepthVisited = result.maxDepthVisited; if (this->LogSelectionStats) { UE_LOG( LogCesium, Display, TEXT( "%s: %d ms, Unreal Frame #%d, Tileset Frame: #%d, Visited %d, Culled Visited %d, Rendered %d, Culled %d, Occluded %d, Waiting For Occlusion Results %d, Max Depth Visited: %d, Loading-Worker %d, Loading-Main %d, Loaded tiles %g%%"), *this->GetName(), (std::chrono::high_resolution_clock::now() - this->_startTime) .count() / 1000000, GFrameCounter, result.frameNumber, result.tilesVisited, result.culledTilesVisited, result.tilesToRenderThisFrame.size(), result.tilesCulled, result.tilesOccluded, result.tilesWaitingForOcclusionResults, result.maxDepthVisited, result.workerThreadTileLoadQueueLength, result.mainThreadTileLoadQueueLength, this->LoadProgress); } if (this->LogSharedAssetStats && this->_pTileset) { const Cesium3DTilesSelection::TilesetSharedAssetSystem::ImageDepot& imageDepot = *this->_pTileset->getSharedAssetSystem().pImage; UE_LOG( LogCesium, Display, TEXT( "Images shared asset depot: %d distinct assets, %d inactive assets pending deletion (%d bytes)"), imageDepot.getAssetCount(), imageDepot.getInactiveAssetCount(), imageDepot.getInactiveAssetTotalSizeBytes()); } } } void ACesium3DTileset::showTilesToRender( const std::vector& tiles) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ShowTilesToRender) forEachRenderableTile( tiles, [&RootComponent = this->RootComponent, &BodyInstance = this->BodyInstance]( Cesium3DTilesSelection::Tile* pTile, UCesiumGltfComponent* pGltf) { applyActorCollisionSettings(BodyInstance, pGltf); if (pGltf->GetAttachParent() == nullptr) { // The AttachToComponent method is ridiculously complex, // so print a warning if attaching fails for some reason bool attached = pGltf->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepRelativeTransform); if (!attached) { FString tileIdString( Cesium3DTilesSelection::TileIdUtilities::createTileIdString( pTile->getTileID()) .c_str()); UE_LOG( LogCesium, Warning, TEXT("Tile %s could not be attached to root"), *tileIdString); } } if (!pGltf->IsVisible()) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetVisibilityTrue) pGltf->SetVisibility(true, true); } { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetCollisionEnabled) pGltf->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); } }); } static void updateTileFades(const auto& tiles, bool fadingIn) { forEachRenderableTile( tiles, [fadingIn]( Cesium3DTilesSelection::Tile* pTile, UCesiumGltfComponent* pGltf) { float percentage = pTile->getContent() .getRenderContent() ->getLodTransitionFadePercentage(); pGltf->UpdateFade(percentage, fadingIn); }); } // Called every frame void ACesium3DTileset::Tick(float DeltaTime) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::TilesetTick) Super::Tick(DeltaTime); this->ResolveGeoreference(); this->ResolveCameraManager(); this->ResolveCreditSystem(); UCesium3DTilesetRoot* pRoot = Cast(this->RootComponent); if (!pRoot) { return; } if (this->SuspendUpdate) { return; } if (!this->_pTileset) { LoadTileset(); // In the unlikely event that we _still_ don't have a tileset, stop here so // we don't crash below. This shouldn't happen. if (!this->_pTileset) { assert(false); return; } } if (this->BoundingVolumePoolComponent && this->_cesiumViewExtension) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateOcclusion) const TArray& children = this->BoundingVolumePoolComponent->GetAttachChildren(); for (USceneComponent* pChild : children) { UCesiumBoundingVolumeComponent* pBoundingVolume = Cast(pChild); if (!pBoundingVolume) { continue; } pBoundingVolume->UpdateOcclusion(*this->_cesiumViewExtension.Get()); } } updateTilesetOptionsFromProperties(); std::vector cameras = this->GetCameras(); glm::dmat4 ueTilesetToUeWorld = VecMath::createMatrix4D(this->GetActorTransform().ToMatrixWithScale()); const glm::dmat4& cesiumTilesetToUeTileset = this->GetCesiumTilesetToUnrealRelativeWorldTransform(); glm::dmat4 unrealWorldToCesiumTileset = glm::affineInverse(ueTilesetToUeWorld * cesiumTilesetToUeTileset); if (glm::isnan(unrealWorldToCesiumTileset[3].x) || glm::isnan(unrealWorldToCesiumTileset[3].y) || glm::isnan(unrealWorldToCesiumTileset[3].z)) { // Probably caused by a zero scale. return; } UCesiumEllipsoid* ellipsoid = this->ResolveGeoreference()->GetEllipsoid(); std::vector frustums; for (const FCesiumCamera& camera : cameras) { frustums.push_back(CreateViewStateFromViewParameters( camera, unrealWorldToCesiumTileset, ellipsoid)); } const Cesium3DTilesSelection::ViewUpdateResult* pResult; if (this->_captureMovieMode) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateViewOffline) pResult = &this->_pTileset->updateViewOffline(frustums); } else { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateView) pResult = &this->_pTileset->updateView(frustums, DeltaTime); } updateLastViewUpdateResultState(*pResult); removeCollisionForTiles(pResult->tilesFadingOut); removeVisibleTilesFromList( _tilesToHideNextFrame, pResult->tilesToRenderThisFrame); hideTiles(_tilesToHideNextFrame); _tilesToHideNextFrame.clear(); for (Cesium3DTilesSelection::Tile* pTile : pResult->tilesFadingOut) { Cesium3DTilesSelection::TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!this->UseLodTransitions || (pRenderContent && pRenderContent->getLodTransitionFadePercentage() >= 1.0f)) { _tilesToHideNextFrame.push_back(pTile); } } showTilesToRender(pResult->tilesToRenderThisFrame); if (this->UseLodTransitions) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTileFades) updateTileFades(pResult->tilesToRenderThisFrame, true); updateTileFades(pResult->tilesFadingOut, false); } this->UpdateLoadStatus(); } void ACesium3DTileset::EndPlay(const EEndPlayReason::Type EndPlayReason) { this->DestroyTileset(); AActor::EndPlay(EndPlayReason); } void ACesium3DTileset::PostLoad() { BodyInstance.FixupData(this); // We need to call this one after Loading the // actor to have correct BodyInstance values. Super::PostLoad(); if (CesiumActors::shouldValidateFlags(this)) CesiumActors::validateActorFlags(this); #if WITH_EDITOR const int32 CesiumVersion = this->GetLinkerCustomVersion(FCesiumCustomVersion::GUID); PRAGMA_DISABLE_DEPRECATION_WARNINGS if (CesiumVersion < FCesiumCustomVersion::CesiumIonServer) { this->CesiumIonServer = UCesiumIonServer::GetBackwardCompatibleServer( this->IonAssetEndpointUrl_DEPRECATED); } PRAGMA_ENABLE_DEPRECATION_WARNINGS #endif } void ACesium3DTileset::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FCesiumCustomVersion::GUID); const int32 CesiumVersion = Ar.CustomVer(FCesiumCustomVersion::GUID); if (CesiumVersion < FCesiumCustomVersion::TilesetExplicitSource) { // In previous versions, the tileset source was inferred from the presence // of a non-empty URL property, rather than being explicitly specified. if (this->Url.Len() > 0) { this->TilesetSource = ETilesetSource::FromUrl; } else { this->TilesetSource = ETilesetSource::FromCesiumIon; } } if (CesiumVersion < FCesiumCustomVersion::TilesetMobilityRemoved) { this->RootComponent->SetMobility(this->Mobility_DEPRECATED); } } #if WITH_EDITOR void ACesium3DTileset::PostEditChangeProperty( FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (!PropertyChangedEvent.Property) { return; } FName PropName = PropertyChangedEvent.Property->GetFName(); FString PropNameAsString = PropertyChangedEvent.Property->GetName(); if (PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, TilesetSource) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Url) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAssetID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAccessToken) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreatePhysicsMeshes) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreateNavCollision) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, AlwaysIncludeTangents) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, GenerateSmoothNormals) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, EnableWaterMask) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IgnoreKhrMaterialsUnlit) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Material) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, TranslucentMaterial) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, WaterMaterial) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ApplyDpiScaling) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, EnableOcclusionCulling) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, UseLodTransitions) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ShowCreditsOnScreen) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Root) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CesiumIonServer) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, RequestHeaders) || // For properties nested in structs, GET_MEMBER_NAME_CHECKED will prefix // with the struct name, so just do a manual string comparison. PropNameAsString == TEXT("RenderCustomDepth") || PropNameAsString == TEXT("CustomDepthStencilValue") || PropNameAsString == TEXT("CustomDepthStencilWriteMask")) { this->DestroyTileset(); } else if ( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Georeference)) { this->InvalidateResolvedGeoreference(); } else if ( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreditSystem)) { this->InvalidateResolvedCreditSystem(); } else if ( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, MaximumScreenSpaceError)) { TArray rasterOverlays; this->GetComponents(rasterOverlays); for (UCesiumRasterOverlay* pOverlay : rasterOverlays) { pOverlay->Refresh(); } TArray tileExcluders; this->GetComponents(tileExcluders); for (UCesiumTileExcluder* pTileExcluder : tileExcluders) { pTileExcluder->Refresh(); } // Maximum Screen Space Error can affect how attenuated points are rendered, // so propagate the new value to the render proxies for this tileset. FCesiumGltfPointsSceneProxyUpdater::UpdateSettingsInProxies(this); } } void ACesium3DTileset::PostEditChangeChainProperty( FPropertyChangedChainEvent& PropertyChangedChainEvent) { Super::PostEditChangeChainProperty(PropertyChangedChainEvent); if (!PropertyChangedChainEvent.Property || PropertyChangedChainEvent.PropertyChain.IsEmpty()) { return; } FName PropName = PropertyChangedChainEvent.PropertyChain.GetHead()->GetValue()->GetFName(); if (PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, PointCloudShading)) { FCesiumGltfPointsSceneProxyUpdater::UpdateSettingsInProxies(this); } } void ACesium3DTileset::PostEditUndo() { Super::PostEditUndo(); // It doesn't appear to be possible to get detailed information about what // changed in the undo/redo operation, so we have to assume the worst and // recreate the tileset. this->DestroyTileset(); } void ACesium3DTileset::PostEditImport() { Super::PostEditImport(); // Recreate the tileset on Paste. this->DestroyTileset(); } bool ACesium3DTileset::CanEditChange(const FProperty* InProperty) const { if (InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, EnableWaterMask)) { // Disable this option on Mac return PlatformName != TEXT("Mac"); } return true; } #endif void ACesium3DTileset::BeginDestroy() { this->InvalidateResolvedGeoreference(); this->DestroyTileset(); AActor::BeginDestroy(); } bool ACesium3DTileset::IsReadyForFinishDestroy() { bool ready = AActor::IsReadyForFinishDestroy(); ready &= this->_tilesetsBeingDestroyed == 0; if (!ready) { getAssetAccessor()->tick(); getAsyncSystem().dispatchMainThreadTasks(); } return ready; } void ACesium3DTileset::Destroyed() { this->DestroyTileset(); AActor::Destroyed(); } #if WITH_EDITOR void ACesium3DTileset::RuntimeSettingsChanged( UObject* pObject, struct FPropertyChangedEvent& changed) { bool occlusionCullingAvailable = GetDefault() ->EnableExperimentalOcclusionCullingFeature; if (occlusionCullingAvailable != this->CanEnableOcclusionCulling) { this->CanEnableOcclusionCulling = occlusionCullingAvailable; this->RefreshTileset(); } } #endif