bmh/FlightSimulation/Plugins/CesiumForUnreal_5.4/Source/CesiumRuntime/Private/CesiumSubLevelComponent.cpp
2025-02-07 22:52:32 +08:00

582 lines
18 KiB
C++

// Copyright 2020-2024 CesiumGS, Inc. and Contributors
#include "CesiumSubLevelComponent.h"
#include "Cesium3DTileset.h"
#include "CesiumActors.h"
#include "CesiumGeoreference.h"
#include "CesiumGeospatial/LocalHorizontalCoordinateSystem.h"
#include "CesiumRuntime.h"
#include "CesiumSubLevelSwitcherComponent.h"
#include "CesiumUtility/Math.h"
#include "EngineUtils.h"
#include "LevelInstance/LevelInstanceActor.h"
#include "VecMath.h"
#include <glm/gtc/matrix_inverse.hpp>
#if WITH_EDITOR
#include "EditorViewportClient.h"
#include "LevelInstance/LevelInstanceLevelStreaming.h"
#include "ScopedTransaction.h"
#endif
using namespace CesiumGeospatial;
bool UCesiumSubLevelComponent::GetEnabled() const { return this->Enabled; }
void UCesiumSubLevelComponent::SetEnabled(bool value) { this->Enabled = value; }
double UCesiumSubLevelComponent::GetOriginLongitude() const {
return this->OriginLongitude;
}
void UCesiumSubLevelComponent::SetOriginLongitude(double value) {
this->OriginLongitude = value;
this->UpdateGeoreferenceIfSubLevelIsActive();
}
double UCesiumSubLevelComponent::GetOriginLatitude() const {
return this->OriginLatitude;
}
void UCesiumSubLevelComponent::SetOriginLatitude(double value) {
this->OriginLatitude = value;
this->UpdateGeoreferenceIfSubLevelIsActive();
}
double UCesiumSubLevelComponent::GetOriginHeight() const {
return this->OriginHeight;
}
void UCesiumSubLevelComponent::SetOriginHeight(double value) {
this->OriginHeight = value;
this->UpdateGeoreferenceIfSubLevelIsActive();
}
double UCesiumSubLevelComponent::GetLoadRadius() const {
return this->LoadRadius;
}
void UCesiumSubLevelComponent::SetLoadRadius(double value) {
this->LoadRadius = value;
}
TSoftObjectPtr<ACesiumGeoreference>
UCesiumSubLevelComponent::GetGeoreference() const {
return this->Georeference;
}
void UCesiumSubLevelComponent::SetGeoreference(
TSoftObjectPtr<ACesiumGeoreference> NewGeoreference) {
this->Georeference = NewGeoreference;
this->_invalidateResolvedGeoreference();
ALevelInstance* pOwner = this->_getLevelInstance();
if (pOwner) {
this->ResolveGeoreference();
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
pSwitcher->RegisterSubLevel(pOwner);
}
}
ACesiumGeoreference* UCesiumSubLevelComponent::GetResolvedGeoreference() const {
return 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())) {
Next = this->Georeference.Get();
} else {
Next =
ACesiumGeoreference::GetDefaultGeoreferenceForActor(this->GetOwner());
}
if (Previous != Next) {
this->_invalidateResolvedGeoreference();
}
this->ResolvedGeoreference = Next;
return this->ResolvedGeoreference;
}
void UCesiumSubLevelComponent::SetOriginLongitudeLatitudeHeight(
const FVector& longitudeLatitudeHeight) {
if (this->OriginLongitude != longitudeLatitudeHeight.X ||
this->OriginLatitude != longitudeLatitudeHeight.Y ||
this->OriginHeight != longitudeLatitudeHeight.Z) {
this->OriginLongitude = longitudeLatitudeHeight.X;
this->OriginLatitude = longitudeLatitudeHeight.Y;
this->OriginHeight = longitudeLatitudeHeight.Z;
this->UpdateGeoreferenceIfSubLevelIsActive();
}
}
#if WITH_EDITOR
namespace {
ULevelStreaming* getLevelStreamingForSubLevel(ALevelInstance* SubLevel) {
if (!IsValid(SubLevel))
return nullptr;
ULevelStreaming* const* ppStreaming =
SubLevel->GetWorld()->GetStreamingLevels().FindByPredicate(
[SubLevel](ULevelStreaming* pStreaming) {
ULevelStreamingLevelInstance* pInstanceStreaming =
Cast<ULevelStreamingLevelInstance>(pStreaming);
if (!pInstanceStreaming)
return false;
return pInstanceStreaming->GetLevelInstance() == SubLevel;
});
return ppStreaming ? *ppStreaming : nullptr;
}
} // namespace
void UCesiumSubLevelComponent::PlaceGeoreferenceOriginAtSubLevelOrigin() {
ACesiumGeoreference* pGeoreference = this->ResolveGeoreference();
if (!IsValid(pGeoreference)) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot place the origin because the sub-level does not have a CesiumGeoreference."));
return;
}
ALevelInstance* pOwner = this->_getLevelInstance();
if (!IsValid(pOwner)) {
return;
}
USceneComponent* Root = pOwner->GetRootComponent();
if (!IsValid(Root)) {
return;
}
FVector UnrealPosition =
pGeoreference->GetActorTransform().InverseTransformPosition(
pOwner->GetActorLocation());
FVector NewOriginEcef =
pGeoreference->TransformUnrealPositionToEarthCenteredEarthFixed(
UnrealPosition);
this->PlaceOriginAtEcef(NewOriginEcef);
}
void UCesiumSubLevelComponent::PlaceGeoreferenceOriginHere() {
ACesiumGeoreference* pGeoreference = this->ResolveGeoreference();
if (!IsValid(pGeoreference)) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot place the origin because the sub-level does not have a CesiumGeoreference."));
return;
}
FViewport* pViewport = GEditor->GetActiveViewport();
if (!pViewport)
return;
FViewportClient* pViewportClient = pViewport->GetClient();
if (!pViewportClient)
return;
FEditorViewportClient* pEditorViewportClient =
static_cast<FEditorViewportClient*>(pViewportClient);
FVector ViewLocation = pEditorViewportClient->GetViewLocation();
// Transform the world-space view location to the CesiumGeoreference's frame.
ViewLocation =
pGeoreference->GetActorTransform().InverseTransformPosition(ViewLocation);
FVector CameraEcefPosition =
pGeoreference->TransformUnrealPositionToEarthCenteredEarthFixed(
ViewLocation);
this->PlaceOriginAtEcef(CameraEcefPosition);
}
void UCesiumSubLevelComponent::PlaceOriginAtEcef(const FVector& NewOriginEcef) {
ACesiumGeoreference* pGeoreference = this->ResolveGeoreference();
if (!IsValid(pGeoreference)) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot place the origin because the sub-level does not have a CesiumGeoreference."));
return;
}
ALevelInstance* pOwner = this->_getLevelInstance();
if (!IsValid(pOwner)) {
return;
}
if (pOwner->IsEditing()) {
UE_LOG(
LogCesium,
Error,
TEXT(
"The georeference origin cannot be moved while the sub-level is being edited."));
return;
}
UCesiumEllipsoid* pEllipsoid = pGeoreference->GetEllipsoid();
check(IsValid(pEllipsoid));
const Ellipsoid& pNativeEllipsoid = pEllipsoid->GetNativeEllipsoid();
// Another sub-level might be active right now, so we construct the correct
// GeoTransforms instead of using the CesiumGeoreference's.
FVector CurrentOriginEcef =
pEllipsoid->LongitudeLatitudeHeightToEllipsoidCenteredEllipsoidFixed(
FVector(
this->OriginLongitude,
this->OriginLatitude,
this->OriginHeight));
GeoTransforms CurrentTransforms(
pNativeEllipsoid,
VecMath::createVector3D(CurrentOriginEcef),
pGeoreference->GetScale() / 100.0);
// Construct new geotransforms at the new origin
GeoTransforms NewTransforms(
pNativeEllipsoid,
VecMath::createVector3D(NewOriginEcef),
pGeoreference->GetScale() / 100.0);
// Transform the level instance from the old origin to the new one.
glm::dmat4 OldToEcef =
CurrentTransforms.GetAbsoluteUnrealWorldToEllipsoidCenteredTransform();
glm::dmat4 EcefToNew =
NewTransforms.GetEllipsoidCenteredToAbsoluteUnrealWorldTransform();
glm::dmat4 OldToNew = EcefToNew * OldToEcef;
glm::dmat4 OldTransform =
VecMath::createMatrix4D(pOwner->GetActorTransform().ToMatrixWithScale());
glm::dmat4 NewLevelTransform = OldToNew * OldTransform;
FScopedTransaction transaction(FText::FromString("Place Origin At Location"));
ULevelStreaming* LevelStreaming = getLevelStreamingForSubLevel(pOwner);
ULevel* Level =
IsValid(LevelStreaming) ? LevelStreaming->GetLoadedLevel() : nullptr;
bool bHasTilesets = Level && IsValid(Level) &&
Level->Actors.FindByPredicate([](AActor* Actor) {
return Cast<ACesium3DTileset>(Actor) != nullptr;
}) != nullptr;
FTransform OldLevelTransform;
if (bHasTilesets) {
OldLevelTransform = LevelStreaming->LevelTransform;
}
pOwner->Modify();
pOwner->SetActorTransform(VecMath::createTransform(NewLevelTransform));
// Set the new sub-level georeference origin.
this->Modify();
this->SetOriginLongitudeLatitudeHeight(
pEllipsoid->EllipsoidCenteredEllipsoidFixedToLongitudeLatitudeHeight(
NewOriginEcef));
// Also update the viewport so the level doesn't appear to shift.
FViewport* pViewport = GEditor->GetActiveViewport();
FViewportClient* pViewportClient = pViewport->GetClient();
FEditorViewportClient* pEditorViewportClient =
static_cast<FEditorViewportClient*>(pViewportClient);
glm::dvec3 ViewLocation =
VecMath::createVector3D(pEditorViewportClient->GetViewLocation());
ViewLocation = glm::dvec3(OldToNew * glm::dvec4(ViewLocation, 1.0));
pEditorViewportClient->SetViewLocation(VecMath::createVector(ViewLocation));
glm::dmat4 ViewportRotation = VecMath::createMatrix4D(
pEditorViewportClient->GetViewRotation().Quaternion().ToMatrix());
ViewportRotation = OldToNew * ViewportRotation;
// At this point, viewportRotation will keep the viewport orientation in ECEF
// exactly as it was before. But that means if it was tilted before, it will
// still be tilted. We instead want an orientation that maintains the exact
// same forward direction but has an "up" direction aligned with +Z.
glm::dvec3 CameraFront = glm::normalize(glm::dvec3(ViewportRotation[0]));
glm::dvec3 CameraRight =
glm::normalize(glm::cross(glm::dvec3(0.0, 0.0, 1.0), CameraFront));
glm::dvec3 CameraUp = glm::normalize(glm::cross(CameraFront, CameraRight));
pEditorViewportClient->SetViewRotation(
FMatrix(
FVector(CameraFront.x, CameraFront.y, CameraFront.z),
FVector(CameraRight.x, CameraRight.y, CameraRight.z),
FVector(CameraUp.x, CameraUp.y, CameraUp.z),
FVector::ZeroVector)
.Rotator());
// Restore the previous tileset transforms. We'll enter Edit mode of the
// sub-level, make the modifications, and let the user choose whether to
// commit them.
if (bHasTilesets) {
pOwner->EnterEdit();
Level = pOwner->GetLoadedLevel();
for (AActor* Actor : Level->Actors) {
ACesium3DTileset* Tileset = Cast<ACesium3DTileset>(Actor);
if (!IsValid(Tileset))
continue;
USceneComponent* Root = Tileset->GetRootComponent();
if (!IsValid(Root))
continue;
// Change of basis of the old tileset relative transform to the new
// coordinate system.
glm::dmat4 NewToEcef =
NewTransforms.GetAbsoluteUnrealWorldToEllipsoidCenteredTransform();
glm::dmat4 oldRelativeTransform = VecMath::createMatrix4D(
(Root->GetRelativeTransform() * OldLevelTransform)
.ToMatrixWithScale());
glm::dmat4 NewToOld = glm::affineInverse(OldToNew);
glm::dmat4 RelativeTransformInNew =
glm::affineInverse(NewLevelTransform) * OldToNew *
oldRelativeTransform * NewToOld;
Tileset->Modify();
Root->Modify();
Root->SetRelativeTransform(
VecMath::createTransform(RelativeTransformInNew),
false,
nullptr,
ETeleportType::TeleportPhysics);
}
}
}
#endif // #if WITH_EDITOR
void UCesiumSubLevelComponent::UpdateGeoreferenceIfSubLevelIsActive() {
ALevelInstance* pOwner = this->_getLevelInstance();
if (!pOwner) {
return;
}
if (!IsValid(this->ResolvedGeoreference)) {
// This sub-level is not associated with a georeference yet.
return;
}
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (!pSwitcher)
return;
ALevelInstance* pCurrent = pSwitcher->GetCurrentSubLevel();
ALevelInstance* pTarget = pSwitcher->GetTargetSubLevel();
// This sub-level's origin is active if it is the current level or if it's the
// target level and there is no current level.
if (pCurrent == pOwner || (pCurrent == nullptr && pTarget == pOwner)) {
// Apply the sub-level's origin to the georeference, if it's different.
if (this->OriginLongitude !=
this->ResolvedGeoreference->GetOriginLongitude() ||
this->OriginLatitude !=
this->ResolvedGeoreference->GetOriginLatitude() ||
this->OriginHeight != this->ResolvedGeoreference->GetOriginHeight()) {
this->ResolvedGeoreference->SetOriginLongitudeLatitudeHeight(FVector(
this->OriginLongitude,
this->OriginLatitude,
this->OriginHeight));
}
}
}
void UCesiumSubLevelComponent::BeginDestroy() {
this->_invalidateResolvedGeoreference();
Super::BeginDestroy();
}
void UCesiumSubLevelComponent::OnComponentCreated() {
Super::OnComponentCreated();
this->ResolveGeoreference();
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (pSwitcher && this->ResolvedGeoreference) {
this->OriginLongitude = this->ResolvedGeoreference->GetOriginLongitude();
this->OriginLatitude = this->ResolvedGeoreference->GetOriginLatitude();
this->OriginHeight = this->ResolvedGeoreference->GetOriginHeight();
// In Editor worlds, make the newly-created sub-level the active one. Unless
// it's already hidden.
#if WITH_EDITOR
if (GEditor && IsValid(this->GetWorld()) &&
!this->GetWorld()->IsGameWorld()) {
ALevelInstance* pOwner = Cast<ALevelInstance>(this->GetOwner());
if (IsValid(pOwner) && !pOwner->IsTemporarilyHiddenInEditor(true)) {
pSwitcher->SetTargetSubLevel(pOwner);
}
}
#endif
}
}
#if WITH_EDITOR
void UCesiumSubLevelComponent::PostEditChangeProperty(
FPropertyChangedEvent& PropertyChangedEvent) {
Super::PostEditChangeProperty(PropertyChangedEvent);
if (!PropertyChangedEvent.Property) {
return;
}
FName propertyName = PropertyChangedEvent.Property->GetFName();
if (propertyName ==
GET_MEMBER_NAME_CHECKED(UCesiumSubLevelComponent, OriginLongitude) ||
propertyName ==
GET_MEMBER_NAME_CHECKED(UCesiumSubLevelComponent, OriginLatitude) ||
propertyName ==
GET_MEMBER_NAME_CHECKED(UCesiumSubLevelComponent, OriginHeight)) {
this->UpdateGeoreferenceIfSubLevelIsActive();
}
}
#endif
void UCesiumSubLevelComponent::BeginPlay() {
Super::BeginPlay();
this->ResolveGeoreference();
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (!pSwitcher)
return;
ALevelInstance* pLevel = this->_getLevelInstance();
if (!pLevel)
return;
pSwitcher->RegisterSubLevel(pLevel);
}
void UCesiumSubLevelComponent::OnRegister() {
Super::OnRegister();
// We set this to true here so that the CesiumEditorSubLevelMutex in the
// CesiumEditor module is invoked for this component when the
// ALevelInstance's visibility is toggled in the Editor.
bRenderStateCreated = true;
ALevelInstance* pOwner = this->_getLevelInstance();
if (!pOwner) {
return;
}
#if WITH_EDITOR
if (pOwner->GetIsSpatiallyLoaded() ||
pOwner->DesiredRuntimeBehavior !=
ELevelInstanceRuntimeBehavior::LevelStreaming) {
pOwner->Modify();
// Cesium sub-levels must not be loaded and unloaded by the World
// Partition system.
if (pOwner->GetIsSpatiallyLoaded()) {
pOwner->SetIsSpatiallyLoaded(false);
}
// Cesium sub-levels must use LevelStreaming behavior). The default
// (Partitioned), will dump the actors in the sub-level into the main
// level, which will prevent us from being to turn the sub-level on and
// off at runtime.
pOwner->DesiredRuntimeBehavior =
ELevelInstanceRuntimeBehavior::LevelStreaming;
UE_LOG(
LogCesium,
Warning,
TEXT(
"Cesium changed the \"Is Spatially Loaded\" or \"Desired Runtime Behavior\" "
"settings on Level Instance %s in order to work as a Cesium sub-level. If "
"you're using World Partition, you may need to reload the main level in order "
"for these changes to take effect."),
*pOwner->GetName());
}
#endif
this->ResolveGeoreference();
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (pSwitcher)
pSwitcher->RegisterSubLevel(pOwner);
this->UpdateGeoreferenceIfSubLevelIsActive();
}
void UCesiumSubLevelComponent::OnUnregister() {
Super::OnUnregister();
ALevelInstance* pOwner = this->_getLevelInstance();
if (!pOwner) {
return;
}
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (pSwitcher)
pSwitcher->UnregisterSubLevel(pOwner);
}
#if WITH_EDITOR
bool UCesiumSubLevelComponent::CanEditChange(
const FProperty* InProperty) const {
// Don't allow editing this property if the parent Actor isn't editable.
return Super::CanEditChange(InProperty) &&
(!IsValid(GetOwner()) || GetOwner()->CanEditChange(InProperty));
}
#endif
UCesiumSubLevelSwitcherComponent*
UCesiumSubLevelComponent::_getSwitcher() noexcept {
// Ignore transient level instances, like those that are created when
// dragging from Create Actors but before releasing the mouse button.
if (!IsValid(this->ResolvedGeoreference) || this->HasAllFlags(RF_Transient))
return nullptr;
return this->ResolvedGeoreference
->FindComponentByClass<UCesiumSubLevelSwitcherComponent>();
}
ALevelInstance* UCesiumSubLevelComponent::_getLevelInstance() const noexcept {
ALevelInstance* pOwner = Cast<ALevelInstance>(this->GetOwner());
if (!pOwner) {
UE_LOG(
LogCesium,
Warning,
TEXT(
"A CesiumSubLevelComponent can only be attached a LevelInstance Actor."));
}
return pOwner;
}
void UCesiumSubLevelComponent::_invalidateResolvedGeoreference() {
if (IsValid(this->ResolvedGeoreference)) {
UCesiumSubLevelSwitcherComponent* pSwitcher = this->_getSwitcher();
if (pSwitcher) {
ALevelInstance* pOwner = this->_getLevelInstance();
if (pOwner) {
pSwitcher->UnregisterSubLevel(Cast<ALevelInstance>(pOwner));
}
}
}
this->ResolvedGeoreference = nullptr;
}