diff --git a/CHANGES.md b/CHANGES.md index 7e475ab77..7fc0b011a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,6 +41,9 @@ - Added `CesiumOriginShiftComponent`. In addition to triggering transitions between sub-levels, this component optionally allows the Unreal world origin to be shifted as the Actor to which it is attached moves. The shifting may be done by either changing the `CesiumGeoreference` origin or by setting Unreal's `OriginLocation` property. - Sub-level transitions can now be triggered manually from Blueprints using functions on the `CesiumSubLevelSwitcherComponent` attached to the `CesiumGeoreference`. Be sure to disable any `CesiumOriginShiftComponent` instances in your level if you want manual control of sub-level switching. - Added `CesiumFlyToComponent` to allow animated flights of any Actor or Pawn. +- Globe aware objects now find their associated CesiumGeoreference by using `ACesiumGeoreference::GetDefaultDefaultGeoreferenceForActor`, which checks first for an attachment parent that is a CesiumGeoreference. This way a `Cesium3DTileset` or similar object will by associated with the CesiumGeoreference it is nested inside by default. +- The Quick Add panel now creates Actors nested inside a `CesiumGeoreference`. +- The `ResolvedGeoreference` is now shown in the Editor Details UI for georeferenced objects, next to the `Georeference` property. ##### Fixes :wrench: diff --git a/Source/CesiumEditor/Private/CesiumEditor.cpp b/Source/CesiumEditor/Private/CesiumEditor.cpp index 892fae7f3..63c3cb892 100644 --- a/Source/CesiumEditor/Private/CesiumEditor.cpp +++ b/Source/CesiumEditor/Private/CesiumEditor.cpp @@ -3,6 +3,7 @@ #include "CesiumEditor.h" #include "Cesium3DTilesSelection/Tileset.h" #include "Cesium3DTileset.h" +#include "CesiumCartographicPolygon.h" #include "CesiumCommands.h" #include "CesiumGeoreferenceCustomization.h" #include "CesiumGlobeAnchorCustomization.h" @@ -520,12 +521,24 @@ FCesiumEditorModule::CreateTileset(const std::string& name, int64_t assetID) { UWorld* pCurrentWorld = GEditor->GetEditorWorldContext().World(); ULevel* pCurrentLevel = pCurrentWorld->GetCurrentLevel(); + ACesiumGeoreference* Georeference = + ACesiumGeoreference::GetDefaultGeoreference(pCurrentWorld); + AActor* pNewActor = GEditor->AddActor( pCurrentLevel, ACesium3DTileset::StaticClass(), FTransform(), false, RF_Transactional); + + // Make the new Tileset a child of the CesiumGeoreference. Unless they're in + // different levels. + if (Georeference->GetLevel() == pCurrentLevel) { + pNewActor->AttachToActor( + Georeference, + FAttachmentTransformRules::KeepRelativeTransform); + } + ACesium3DTileset* pTilesetActor = Cast(pNewActor); pTilesetActor->SetActorLabel(UTF8_TO_TCHAR(name.c_str())); if (assetID != -1) { @@ -657,12 +670,25 @@ AActor* SpawnActorWithClass(UClass* actorClass) { UWorld* pCurrentWorld = GEditor->GetEditorWorldContext().World(); ULevel* pCurrentLevel = pCurrentWorld->GetCurrentLevel(); - return GEditor->AddActor( + ACesiumGeoreference* Georeference = + ACesiumGeoreference::GetDefaultGeoreference(pCurrentWorld); + + AActor* NewActor = GEditor->AddActor( pCurrentLevel, actorClass, FTransform(), false, RF_Transactional); + + // Make the new Actor a child of the CesiumGeoreference. Unless they're in + // different levels. + if (Georeference->GetLevel() == pCurrentLevel) { + NewActor->AttachToActor( + Georeference, + FAttachmentTransformRules::KeepRelativeTransform); + } + + return NewActor; } } // namespace @@ -686,6 +712,14 @@ UClass* FCesiumEditorModule::GetCesiumSunSkyClass() { return ACesiumSunSky::StaticClass(); } +AActor* FCesiumEditorModule::SpawnBlankTileset() { + return SpawnActorWithClass(ACesium3DTileset::StaticClass()); +} + +AActor* FCesiumEditorModule::SpawnCartographicPolygon() { + return SpawnActorWithClass(ACesiumCartographicPolygon::StaticClass()); +} + UClass* FCesiumEditorModule::GetDynamicPawnBlueprintClass() { static UClass* pResult = nullptr; diff --git a/Source/CesiumEditor/Private/CesiumEditor.h b/Source/CesiumEditor/Private/CesiumEditor.h index 955451227..2e0df1adc 100644 --- a/Source/CesiumEditor/Private/CesiumEditor.h +++ b/Source/CesiumEditor/Private/CesiumEditor.h @@ -2,6 +2,7 @@ #pragma once +#include "CesiumEditorReparentHandler.h" #include "CesiumEditorSubLevelMutex.h" #include "CesiumIonSession.h" #include "CoreMinimal.h" @@ -85,6 +86,18 @@ class FCesiumEditorModule : public IModuleInterface { */ static AActor* SpawnDynamicPawn(); + /** + * Spawns a new Cesium3DTileset with default values in the current level of + * the edited world. + */ + static AActor* SpawnBlankTileset(); + + /** + * Spawns a new CesiumCartographicPolygon in the current level of the edited + * world. + */ + static AActor* SpawnCartographicPolygon(); + private: TSharedRef SpawnCesiumTab(const FSpawnTabArgs& TabSpawnArgs); TSharedRef @@ -103,6 +116,7 @@ class FCesiumEditorModule : public IModuleInterface { FDelegateHandle _rasterOverlayIonTroubleshootingSubscription; CesiumEditorSubLevelMutex _subLevelMutex; + CesiumEditorReparentHandler _reparentHandler; static TSharedPtr StyleSet; static FCesiumEditorModule* _pModule; diff --git a/Source/CesiumEditor/Private/CesiumEditorReparentHandler.cpp b/Source/CesiumEditor/Private/CesiumEditorReparentHandler.cpp new file mode 100644 index 000000000..b81353429 --- /dev/null +++ b/Source/CesiumEditor/Private/CesiumEditorReparentHandler.cpp @@ -0,0 +1,43 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#include "CesiumEditorReparentHandler.h" +#include "Cesium3DTileset.h" +#include "CesiumGlobeAnchorComponent.h" +#include "CesiumSubLevelComponent.h" +#include "Engine/Engine.h" + +CesiumEditorReparentHandler::CesiumEditorReparentHandler() { + if (GEngine) { + this->_subscription = GEngine->OnLevelActorAttached().AddRaw( + this, + &CesiumEditorReparentHandler::OnLevelActorAttached); + } +} + +CesiumEditorReparentHandler::~CesiumEditorReparentHandler() { + if (GEngine) { + GEngine->OnLevelActorAttached().Remove(this->_subscription); + this->_subscription.Reset(); + } +} + +void CesiumEditorReparentHandler::OnLevelActorAttached( + AActor* Actor, + const AActor* Parent) { + ACesium3DTileset* Tileset = Cast(Actor); + if (IsValid(Tileset)) { + Tileset->InvalidateResolvedGeoreference(); + } + + UCesiumGlobeAnchorComponent* GlobeAnchor = + Actor->FindComponentByClass(); + if (IsValid(GlobeAnchor)) { + GlobeAnchor->ResolveGeoreference(true); + } + + UCesiumSubLevelComponent* SubLevel = + Actor->FindComponentByClass(); + if (IsValid(SubLevel)) { + SubLevel->ResolveGeoreference(true); + } +} diff --git a/Source/CesiumEditor/Private/CesiumEditorReparentHandler.h b/Source/CesiumEditor/Private/CesiumEditorReparentHandler.h new file mode 100644 index 000000000..37ca7f190 --- /dev/null +++ b/Source/CesiumEditor/Private/CesiumEditorReparentHandler.h @@ -0,0 +1,24 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#pragma once + +#include "Delegates/IDelegateInstance.h" + +class AActor; + +/** + * Detects when Actors are reparented in the Editor by subscribing to + * GEngine::OnLevelActorAttached and handling it appropriately. For example, + * when a Cesium3DTileset's parent changes, we need to re-resolve its + * CesiumGeoreference. + */ +class CesiumEditorReparentHandler { +public: + CesiumEditorReparentHandler(); + ~CesiumEditorReparentHandler(); + +private: + void OnLevelActorAttached(AActor* Actor, const AActor* Parent); + + FDelegateHandle _subscription; +}; diff --git a/Source/CesiumEditor/Private/CesiumGlobeAnchorCustomization.cpp b/Source/CesiumEditor/Private/CesiumGlobeAnchorCustomization.cpp index 23f896b7c..c4b88542b 100644 --- a/Source/CesiumEditor/Private/CesiumGlobeAnchorCustomization.cpp +++ b/Source/CesiumEditor/Private/CesiumGlobeAnchorCustomization.cpp @@ -51,6 +51,9 @@ void FCesiumGlobeAnchorCustomization::CustomizeDetails( CesiumCategory.AddProperty( GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorComponent, Georeference)); + CesiumCategory.AddProperty(GET_MEMBER_NAME_CHECKED( + UCesiumGlobeAnchorComponent, + ResolvedGeoreference)); CesiumCategory.AddProperty(GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorComponent, AdjustOrientationForGlobeWhenMoving)); diff --git a/Source/CesiumEditor/Private/IonQuickAddPanel.cpp b/Source/CesiumEditor/Private/IonQuickAddPanel.cpp index 2b0d9b19a..385ef0541 100644 --- a/Source/CesiumEditor/Private/IonQuickAddPanel.cpp +++ b/Source/CesiumEditor/Private/IonQuickAddPanel.cpp @@ -254,18 +254,6 @@ void IonQuickAddPanel::AddCesiumSunSkyToLevel() { } } -void IonQuickAddPanel::AddCartographicPolygonToLevel() { - UWorld* pCurrentWorld = GEditor->GetEditorWorldContext().World(); - ULevel* pCurrentLevel = pCurrentWorld->GetCurrentLevel(); - - GEditor->AddActor( - pCurrentLevel, - ACesiumCartographicPolygon::StaticClass(), - FTransform(), - false, - RF_Transactional); -} - namespace { /** * Set a byte property value in the given object. @@ -330,18 +318,6 @@ void IonQuickAddPanel::AddDynamicPawnToLevel() { } } -void IonQuickAddPanel::AddBlankTilesetToLevel() { - UWorld* pCurrentWorld = GEditor->GetEditorWorldContext().World(); - ULevel* pCurrentLevel = pCurrentWorld->GetCurrentLevel(); - - GEditor->AddActor( - pCurrentLevel, - ACesium3DTileset::StaticClass(), - FTransform(), - false, - RF_Transactional); -} - void IonQuickAddPanel::AddItemToLevel(TSharedRef item) { if (this->_itemsBeingAdded.find(item->name) != this->_itemsBeingAdded.end()) { // Add is already in progress. @@ -357,7 +333,7 @@ void IonQuickAddPanel::AddItemToLevel(TSharedRef item) { bool isBlankTileset = item->type == QuickAddItemType::TILESET && item->tilesetID == -1 && item->overlayID == -1; if (isBlankTileset) { - AddBlankTilesetToLevel(); + FCesiumEditorModule::SpawnBlankTileset(); this->_itemsBeingAdded.erase(item->name); } else { AddIonTilesetToLevel(item); @@ -369,7 +345,7 @@ void IonQuickAddPanel::AddItemToLevel(TSharedRef item) { AddDynamicPawnToLevel(); this->_itemsBeingAdded.erase(item->name); } else if (item->type == QuickAddItemType::CARTOGRAPHIC_POLYGON) { - AddCartographicPolygonToLevel(); + FCesiumEditorModule::SpawnCartographicPolygon(); this->_itemsBeingAdded.erase(item->name); } } diff --git a/Source/CesiumEditor/Private/IonQuickAddPanel.h b/Source/CesiumEditor/Private/IonQuickAddPanel.h index eb60936f3..b5a7d512d 100644 --- a/Source/CesiumEditor/Private/IonQuickAddPanel.h +++ b/Source/CesiumEditor/Private/IonQuickAddPanel.h @@ -45,11 +45,9 @@ class IonQuickAddPanel : public SCompoundWidget { const TSharedRef& list); void AddItemToLevel(TSharedRef item); - void AddBlankTilesetToLevel(); void AddIonTilesetToLevel(TSharedRef item); void AddCesiumSunSkyToLevel(); void AddDynamicPawnToLevel(); - void AddCartographicPolygonToLevel(); TArray> _quickAddItems; std::unordered_set _itemsBeingAdded; diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index b1b08b2d0..16d60d85a 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -135,7 +135,7 @@ ACesiumGeoreference* ACesium3DTileset::ResolveGeoreference() { this->ResolvedGeoreference = this->Georeference.Get(); } else { this->ResolvedGeoreference = - ACesiumGeoreference::GetDefaultGeoreference(this); + ACesiumGeoreference::GetDefaultGeoreferenceForActor(this); } UCesium3DTilesetRoot* pRoot = Cast(this->RootComponent); diff --git a/Source/CesiumRuntime/Private/CesiumGeoreference.cpp b/Source/CesiumRuntime/Private/CesiumGeoreference.cpp index abc9d66ab..e33b613fb 100644 --- a/Source/CesiumRuntime/Private/CesiumGeoreference.cpp +++ b/Source/CesiumRuntime/Private/CesiumGeoreference.cpp @@ -57,83 +57,124 @@ createCoordinateSystem(const FVector& center, double scale) { Ellipsoid::WGS84); } -} // namespace +ACesiumGeoreference* FindGeoreferenceAncestor(AActor* Actor) { + AActor* Current = Actor; -/*static*/ const double ACesiumGeoreference::kMinimumScale = 1.0e-6; + while (IsValid(Current)) { + ACesiumGeoreference* Georeference = Cast(Current); + if (IsValid(Georeference)) { + return Georeference; + } + Current = Current->GetAttachParentActor(); + } -/*static*/ ACesiumGeoreference* -ACesiumGeoreference::GetDefaultGeoreference(const UObject* WorldContextObject) { - UWorld* world = WorldContextObject->GetWorld(); - // This method can be called by actors even when opening the content browser. - if (!IsValid(world)) { + return nullptr; +} + +ACesiumGeoreference* +FindGeoreferenceWithTag(const UObject* WorldContextObject, const FName& Tag) { + UWorld* World = WorldContextObject->GetWorld(); + if (!IsValid(World)) return nullptr; - } - UE_LOG( - LogCesium, - Verbose, - TEXT("World name for GetDefaultGeoreference: %s"), - *world->GetFullName()); // Note: The actor iterator will be created with the // "EActorIteratorFlags::SkipPendingKill" flag, // meaning that we don't have to handle objects // that have been deleted. (This is the default, // but made explicit here) - ACesiumGeoreference* pGeoreference = nullptr; + ACesiumGeoreference* Georeference = nullptr; EActorIteratorFlags flags = EActorIteratorFlags::OnlyActiveLevels | EActorIteratorFlags::SkipPendingKill; for (TActorIterator actorIterator( - world, + World, ACesiumGeoreference::StaticClass(), flags); actorIterator; ++actorIterator) { AActor* actor = *actorIterator; - if (actor->GetLevel() == world->PersistentLevel && - actor->ActorHasTag(DEFAULT_GEOREFERENCE_TAG)) { - pGeoreference = Cast(actor); + if (actor->GetLevel() == World->PersistentLevel && + actor->ActorHasTag(Tag)) { + Georeference = Cast(actor); break; } } - if (!pGeoreference) { - // Legacy method of finding Georeference, for backwards compatibility with - // existing projects - ACesiumGeoreference* pGeoreferenceCandidate = - FindObject( - world->PersistentLevel, - TEXT("CesiumGeoreferenceDefault")); - // Test if PendingKill - if (IsValid(pGeoreferenceCandidate)) { - pGeoreference = pGeoreferenceCandidate; - } + + return Georeference; +} + +ACesiumGeoreference* +FindGeoreferenceWithDefaultName(const UObject* WorldContextObject) { + UWorld* World = WorldContextObject->GetWorld(); + if (!IsValid(World)) + return nullptr; + + ACesiumGeoreference* Candidate = FindObject( + World->PersistentLevel, + TEXT("CesiumGeoreferenceDefault")); + + // Test if PendingKill + if (IsValid(Candidate)) { + return Candidate; } - if (!pGeoreference) { - UE_LOG( - LogCesium, - Verbose, - TEXT("Creating default Georeference for actor %s"), - *WorldContextObject->GetName()); - // Spawn georeference in the persistent level - FActorSpawnParameters spawnParameters; - spawnParameters.SpawnCollisionHandlingOverride = - ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - spawnParameters.OverrideLevel = world->PersistentLevel; - pGeoreference = world->SpawnActor(spawnParameters); - // Null check so the editor doesn't crash when it makes arbitrary calls to - // this function without a valid world context object. - if (pGeoreference) { - pGeoreference->Tags.Add(DEFAULT_GEOREFERENCE_TAG); - } - } else { - UE_LOG( - LogCesium, - Verbose, - TEXT("Using existing Georeference %s for actor %s"), - *pGeoreference->GetName(), - *WorldContextObject->GetName()); + return nullptr; +} + +ACesiumGeoreference* +CreateDefaultGeoreference(const UObject* WorldContextObject, const FName& Tag) { + UWorld* World = WorldContextObject->GetWorld(); + if (!IsValid(World)) + return nullptr; + + // Spawn georeference in the persistent level + FActorSpawnParameters spawnParameters; + spawnParameters.SpawnCollisionHandlingOverride = + ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + spawnParameters.OverrideLevel = World->PersistentLevel; + + ACesiumGeoreference* Georeference = + World->SpawnActor(spawnParameters); + if (Georeference) { + Georeference->Tags.Add(Tag); } - return pGeoreference; + + return Georeference; +} + +} // namespace + +/*static*/ const double ACesiumGeoreference::kMinimumScale = 1.0e-6; + +/*static*/ ACesiumGeoreference* +ACesiumGeoreference::GetDefaultGeoreference(const UObject* WorldContextObject) { + if (!IsValid(WorldContextObject)) + return nullptr; + + ACesiumGeoreference* Georeference = + FindGeoreferenceWithTag(WorldContextObject, DEFAULT_GEOREFERENCE_TAG); + if (IsValid(Georeference)) + return Georeference; + + Georeference = FindGeoreferenceWithDefaultName(WorldContextObject); + if (IsValid(Georeference)) + return Georeference; + + Georeference = + CreateDefaultGeoreference(WorldContextObject, DEFAULT_GEOREFERENCE_TAG); + + return Georeference; +} + +/*static*/ ACesiumGeoreference* +ACesiumGeoreference::GetDefaultGeoreferenceForActor(AActor* Actor) { + if (!IsValid(Actor)) + return nullptr; + + ACesiumGeoreference* Georeference = FindGeoreferenceAncestor(Actor); + if (IsValid(Georeference)) + return Georeference; + + return ACesiumGeoreference::GetDefaultGeoreference(Actor); } FVector ACesiumGeoreference::GetOriginLongitudeLatitudeHeight() const { diff --git a/Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp b/Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp index 5a58bd043..7f89dc9f8 100644 --- a/Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp @@ -75,12 +75,6 @@ void UCesiumGlobeAnchorComponent::SetGeoreference( // georeference. If it's not, this will happen when it becomes registered. if (this->IsRegistered()) { this->ResolveGeoreference(); - - // If we switched to a different georeference, synchronize the state based - // on the new one. - if (pOriginal != this->Georeference && IsValid(pOriginal)) { - this->Sync(); - } } } @@ -233,22 +227,38 @@ void UCesiumGlobeAnchorComponent::Sync() { } } -ACesiumGeoreference* UCesiumGlobeAnchorComponent::ResolveGeoreference() { - if (IsValid(this->ResolvedGeoreference)) { +ACesiumGeoreference* +UCesiumGlobeAnchorComponent::ResolveGeoreference(bool bForceReresolve) { + if (IsValid(this->ResolvedGeoreference) && !bForceReresolve) { return this->ResolvedGeoreference; } - if (IsValid(this->Georeference.Get())) { - this->ResolvedGeoreference = this->Georeference.Get(); - } else { - this->ResolvedGeoreference = - ACesiumGeoreference::GetDefaultGeoreference(this); - } + ACesiumGeoreference* Previous = this->ResolvedGeoreference; + ACesiumGeoreference* Next = + IsValid(this->Georeference.Get()) + ? this->ResolvedGeoreference = this->Georeference.Get() + : ACesiumGeoreference::GetDefaultGeoreferenceForActor( + this->GetOwner()); - if (this->ResolvedGeoreference) { - this->ResolvedGeoreference->OnGeoreferenceUpdated.AddUniqueDynamic( - this, - &UCesiumGlobeAnchorComponent::_onGeoreferenceChanged); + if (Previous != Next) { + if (IsValid(Previous)) { + // If we previously had a valid georeference, first synchronize using the + // old one so that the ECEF and Actor transforms are both up-to-date. + this->Sync(); + + Previous->OnGeoreferenceUpdated.RemoveAll(this); + } + + this->ResolvedGeoreference = Next; + + if (this->ResolvedGeoreference) { + this->ResolvedGeoreference->OnGeoreferenceUpdated.AddUniqueDynamic( + this, + &UCesiumGlobeAnchorComponent::_onGeoreferenceChanged); + + // Now synchronize based on the new georeference. + this->Sync(); + } } return this->ResolvedGeoreference; @@ -494,7 +504,6 @@ void UCesiumGlobeAnchorComponent::OnRegister() { } this->ResolveGeoreference(); - this->Sync(); } void UCesiumGlobeAnchorComponent::OnUnregister() { diff --git a/Source/CesiumRuntime/Private/CesiumSubLevelComponent.cpp b/Source/CesiumRuntime/Private/CesiumSubLevelComponent.cpp index 3c2dc7205..b76d6f27f 100644 --- a/Source/CesiumRuntime/Private/CesiumSubLevelComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumSubLevelComponent.cpp @@ -62,7 +62,7 @@ UCesiumSubLevelComponent::GetGeoreference() const { void UCesiumSubLevelComponent::SetGeoreference( TSoftObjectPtr NewGeoreference) { this->Georeference = NewGeoreference; - this->InvalidateResolvedGeoreference(); + this->_invalidateResolvedGeoreference(); ALevelInstance* pOwner = this->_getLevelInstance(); if (pOwner) { @@ -77,32 +77,28 @@ ACesiumGeoreference* UCesiumSubLevelComponent::GetResolvedGeoreference() const { return this->ResolvedGeoreference; } -ACesiumGeoreference* UCesiumSubLevelComponent::ResolveGeoreference() { - if (IsValid(this->ResolvedGeoreference)) { +ACesiumGeoreference* +UCesiumSubLevelComponent::ResolveGeoreference(bool bForceReresolve) { + if (IsValid(this->ResolvedGeoreference) && !bForceReresolve) { return this->ResolvedGeoreference; } + ACesiumGeoreference* Previous = this->ResolvedGeoreference; + ACesiumGeoreference* Next = nullptr; + if (IsValid(this->Georeference.Get())) { - this->ResolvedGeoreference = this->Georeference.Get(); + Next = this->Georeference.Get(); } else { - this->ResolvedGeoreference = - ACesiumGeoreference::GetDefaultGeoreference(this); + Next = + ACesiumGeoreference::GetDefaultGeoreferenceForActor(this->GetOwner()); } - return this->ResolvedGeoreference; -} - -void UCesiumSubLevelComponent::InvalidateResolvedGeoreference() { - if (IsValid(this->ResolvedGeoreference)) { - UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher(); - if (pSwitcher) { - ALevelInstance* pOwner = this->_getLevelInstance(); - if (pOwner) { - pSwitcher->UnregisterSubLevel(Cast(pOwner)); - } - } + if (Previous != Next) { + this->_invalidateResolvedGeoreference(); } - this->ResolvedGeoreference = nullptr; + + this->ResolvedGeoreference = Next; + return this->ResolvedGeoreference; } void UCesiumSubLevelComponent::SetOriginLongitudeLatitudeHeight( @@ -268,7 +264,7 @@ void UCesiumSubLevelComponent::UpdateGeoreferenceIfSubLevelIsActive() { } void UCesiumSubLevelComponent::BeginDestroy() { - this->InvalidateResolvedGeoreference(); + this->_invalidateResolvedGeoreference(); Super::BeginDestroy(); } @@ -425,3 +421,16 @@ ALevelInstance* UCesiumSubLevelComponent::_getLevelInstance() const noexcept { } return pOwner; } + +void UCesiumSubLevelComponent::_invalidateResolvedGeoreference() { + if (IsValid(this->ResolvedGeoreference)) { + UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher(); + if (pSwitcher) { + ALevelInstance* pOwner = this->_getLevelInstance(); + if (pOwner) { + pSwitcher->UnregisterSubLevel(Cast(pOwner)); + } + } + } + this->ResolvedGeoreference = nullptr; +} diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp index 81d434d79..a21ee5be5 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp @@ -20,10 +20,6 @@ END_DEFINE_SPEC(FCesiumGlobeAnchorSpec) void FCesiumGlobeAnchorSpec::Define() { BeforeEach([this]() { UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext(); - ACesiumGeoreference* pGeoreference = - ACesiumGeoreference::GetDefaultGeoreference(pWorld); - pGeoreference->SetOriginLongitudeLatitudeHeight(FVector(1.0, 2.0, 3.0)); - this->pActor = pWorld->SpawnActor(); this->pActor->AddComponentByClass( USceneComponent::StaticClass(), @@ -32,6 +28,10 @@ void FCesiumGlobeAnchorSpec::Define() { false); this->pActor->SetActorRelativeTransform(FTransform()); + ACesiumGeoreference* pGeoreference = + ACesiumGeoreference::GetDefaultGeoreferenceForActor(pActor); + pGeoreference->SetOriginLongitudeLatitudeHeight(FVector(1.0, 2.0, 3.0)); + this->pGlobeAnchor = Cast(pActor->AddComponentByClass( UCesiumGlobeAnchorComponent::StaticClass(), diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index dd3732cbe..ee872edca 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -131,6 +131,7 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { */ UPROPERTY( Transient, + VisibleAnywhere, BlueprintReadOnly, Category = "Cesium", Meta = (AllowPrivateAccess)) diff --git a/Source/CesiumRuntime/Public/CesiumGeoreference.h b/Source/CesiumRuntime/Public/CesiumGeoreference.h index 6985c1cad..78f02b5dd 100644 --- a/Source/CesiumRuntime/Public/CesiumGeoreference.h +++ b/Source/CesiumRuntime/Public/CesiumGeoreference.h @@ -41,10 +41,17 @@ class CESIUMRUNTIME_API ACesiumGeoreference : public AActor { static const double kMinimumScale; /** - * Finds and returns the actor labeled `CesiumGeoreferenceDefault` in the - * persistent level of the calling object's world. If not found, it creates a - * new default Georeference. - * @param WorldContextObject Any `UObject`. + * Finds and returns a CesiumGeoreference in the world. It searches in the + * following order: + * + * 1. A CesiumGeoreference that is tagged with "DEFAULT_GEOREFERENCE" and + * found in the PersistentLevel. + * 2. A CesiumGeoreference with the name "CesiumGeoreferenceDefault" and found + * in the PersistentLevel. + * 3. Any CesiumGeoreference in the PersistentLevel. + * + * If no CesiumGeoreference is found with this search, a new one is created in + * the persistent level and given the "DEFAULT_GEOREFERENCE" tag. */ UFUNCTION( BlueprintCallable, @@ -53,6 +60,23 @@ class CESIUMRUNTIME_API ACesiumGeoreference : public AActor { static ACesiumGeoreference* GetDefaultGeoreference(const UObject* WorldContextObject); + /** + * Finds and returns the CesiumGeoreference suitable for use with the given + * Actor. It searches in the following order: + * + * 1. A CesiumGeoreference that is an attachment parent of the given Actor. + * 2. A CesiumGeoreference that is tagged with "DEFAULT_GEOREFERENCE" and + * found in the PersistentLevel. + * 3. A CesiumGeoreference with the name "CesiumGeoreferenceDefault" and found + * in the PersistentLevel. + * 4. Any CesiumGeoreference in the PersistentLevel. + * + * If no CesiumGeoreference is found with this search, a new one is created in + * the persistent level and given the "DEFAULT_GEOREFERENCE" tag. + */ + UFUNCTION(BlueprintCallable, Category = "Cesium") + static ACesiumGeoreference* GetDefaultGeoreferenceForActor(AActor* Actor); + /** * A delegate that will be called whenever the Georeference is * modified in a way that affects its computations. diff --git a/Source/CesiumRuntime/Public/CesiumGlobeAnchorComponent.h b/Source/CesiumRuntime/Public/CesiumGlobeAnchorComponent.h index fde576db4..91df37971 100644 --- a/Source/CesiumRuntime/Public/CesiumGlobeAnchorComponent.h +++ b/Source/CesiumRuntime/Public/CesiumGlobeAnchorComponent.h @@ -45,6 +45,24 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent { Meta = (AllowPrivateAccess)) TSoftObjectPtr Georeference = nullptr; + /** + * The resolved georeference used by this component. This is not serialized + * because it may point to a Georeference in the PersistentLevel while this + * component is in a sub-level. If the Georeference property is specified, + * however then this property will have the same value. + * + * This property will be null before ResolveGeoreference is called, which + * happens automatically when the component is registered. + */ + UPROPERTY( + Transient, + VisibleAnywhere, + BlueprintReadOnly, + BlueprintGetter = GetResolvedGeoreference, + Category = "Cesium", + Meta = (AllowPrivateAccess)) + ACesiumGeoreference* ResolvedGeoreference = nullptr; + /** * Whether to adjust the Actor's orientation based on globe curvature as the * Actor moves. @@ -88,23 +106,6 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent { Meta = (AllowPrivateAccess)) bool TeleportWhenUpdatingTransform = true; - /** - * The resolved georeference used by this component. This is not serialized - * because it may point to a Georeference in the PersistentLevel while this - * component is in a sub-level. If the Georeference property is specified, - * however then this property will have the same value. - * - * This property will be null before ResolveGeoreference is called, which - * happens automatically when the component is registered. - */ - UPROPERTY( - Transient, - BlueprintReadOnly, - BlueprintGetter = GetResolvedGeoreference, - Category = "Cesium", - Meta = (AllowPrivateAccess)) - ACesiumGeoreference* ResolvedGeoreference = nullptr; - /** * The 4x4 transformation matrix from the Actors's local coordinate system to * the Earth-Centered, Earth-Fixed (ECEF) coordinate system. @@ -172,10 +173,10 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent { * the value of the Georeference property if it is set. Otherwise, finds a * Georeference in the World and returns it, creating it if necessary. The * resolved Georeference is cached so subsequent calls to this function will - * return the same instance. + * return the same instance, unless ForceReresolve is true. */ UFUNCTION(BlueprintCallable, Category = "Cesium") - ACesiumGeoreference* ResolveGeoreference(); + ACesiumGeoreference* ResolveGeoreference(bool bForceReresolve = false); /** * Gets the 4x4 transformation matrix from the Actors's local coordinate diff --git a/Source/CesiumRuntime/Public/CesiumSubLevelComponent.h b/Source/CesiumRuntime/Public/CesiumSubLevelComponent.h index b7e713a66..fedca39c1 100644 --- a/Source/CesiumRuntime/Public/CesiumSubLevelComponent.h +++ b/Source/CesiumRuntime/Public/CesiumSubLevelComponent.h @@ -171,18 +171,10 @@ class CESIUMRUNTIME_API UCesiumSubLevelComponent : public UActorComponent { * the value of the Georeference property if it is set. Otherwise, finds a * Georeference in the World and returns it, creating it if necessary. The * resolved Georeference is cached so subsequent calls to this function will - * return the same instance. + * return the same instance, unless ForceReresolve is true. */ UFUNCTION(BlueprintCallable, Category = "Cesium") - ACesiumGeoreference* ResolveGeoreference(); - - /** - * Invalidates the cached resolved georeference, unsubscribing from it and - * setting it to null. The next time ResolveGeoreference is called, the - * Georeference will be re-resolved and re-subscribed. - */ - UFUNCTION(BlueprintCallable, Category = "Cesium") - void InvalidateResolvedGeoreference(); + ACesiumGeoreference* ResolveGeoreference(bool bForceReresolve = false); /** * Sets the longitude (X), latitude (Y), and height (Z) of this sub-level's @@ -346,6 +338,7 @@ class CESIUMRUNTIME_API UCesiumSubLevelComponent : public UActorComponent { */ UPROPERTY( Transient, + VisibleAnywhere, BlueprintReadOnly, BlueprintGetter = GetResolvedGeoreference, Category = "Cesium", @@ -365,4 +358,11 @@ class CESIUMRUNTIME_API UCesiumSubLevelComponent : public UActorComponent { * warning and returns nullptr. */ ALevelInstance* _getLevelInstance() const noexcept; + + /** + * Invalidates the cached resolved georeference, unsubscribing from it and + * setting it to null. The next time ResolveGeoreference is called, the + * Georeference will be re-resolved and re-subscribed. + */ + void _invalidateResolvedGeoreference(); };