You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
901 lines
28 KiB
C++
901 lines
28 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
EditorLevelUtils.cpp: Editor-specific level management routines
|
|
=============================================================================*/
|
|
|
|
#include "UnrealEd.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "EditorLevelUtils.h"
|
|
|
|
#include "BusyCursor.h"
|
|
#include "LevelUtils.h"
|
|
#include "Layers/ILayers.h"
|
|
|
|
#include "LevelEditor.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "ContentStreaming.h"
|
|
#include "PackageTools.h"
|
|
|
|
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
|
|
#include "Engine/LevelStreamingVolume.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Components/ModelComponent.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogLevelTools);
|
|
|
|
#define LOCTEXT_NAMESPACE "EditorLevelUtils"
|
|
|
|
namespace EditorLevelUtils
|
|
{
|
|
/**
|
|
* Moves the specified list of actors to the specified level
|
|
*
|
|
* @param ActorsToMove List of actors to move
|
|
* @param DestLevelStreaming The level streaming object associated with the destination level
|
|
* @param OutNumMovedActors The number of actors that were successfully moved to the new level
|
|
*/
|
|
void MovesActorsToLevel( TArray< AActor* >& ActorsToMove, ULevelStreaming* DestLevelStreaming, int32& OutNumMovedActors )
|
|
{
|
|
// Backup the current contents of the clipboard string as we'll be using cut/paste features to move actors
|
|
// between levels and this will trample over the clipboard data.
|
|
FString OriginalClipboardContent;
|
|
FPlatformMisc::ClipboardPaste(OriginalClipboardContent);
|
|
|
|
check( DestLevelStreaming != NULL );
|
|
ULevel* DestLevel = DestLevelStreaming->GetLoadedLevel();
|
|
check( DestLevel != NULL );
|
|
|
|
// Cache a the destination world
|
|
UWorld* World = DestLevel->OwningWorld;
|
|
|
|
// Deselect all actors
|
|
GEditor->Exec( World, TEXT("ACTOR SELECT NONE"));
|
|
|
|
const FString& NewLevelName = DestLevelStreaming->GetWorldAssetPackageName();
|
|
|
|
for( TArray< AActor* >::TIterator CurActorIt( ActorsToMove ); CurActorIt; ++CurActorIt )
|
|
{
|
|
AActor* CurActor = *CurActorIt;
|
|
check( CurActor != NULL );
|
|
|
|
if( !FLevelUtils::IsLevelLocked( DestLevel ) && !FLevelUtils::IsLevelLocked( CurActor ) )
|
|
{
|
|
ULevelStreaming* ActorPrevLevel = FLevelUtils::FindStreamingLevel( CurActor->GetLevel() );
|
|
const FString& PrevLevelName = ActorPrevLevel != NULL ? ActorPrevLevel->GetWorldAssetPackageName() : CurActor->GetLevel()->GetName();
|
|
UE_LOG(LogLevelTools, Warning, TEXT( "AutoLevel: Moving %s from %s to %s" ), *CurActor->GetName(), *PrevLevelName, *NewLevelName );
|
|
|
|
// Select this actor
|
|
GEditor->SelectActor( CurActor, true, false, true );
|
|
// Cache the world the actor is in, or if we already did ensure its the same world.
|
|
check( World == CurActor->GetWorld() );
|
|
}
|
|
else
|
|
{
|
|
// Either the source or destination level was locked!
|
|
// @todo: Display warning
|
|
}
|
|
}
|
|
|
|
OutNumMovedActors = 0;
|
|
if( GEditor->GetSelectedActorCount() > 0 )
|
|
{
|
|
// @todo: Perf: Not sure if this is needed here.
|
|
GEditor->NoteSelectionChange();
|
|
|
|
// Move the actors!
|
|
GEditor->MoveSelectedActorsToLevel( DestLevel );
|
|
|
|
// The moved (pasted) actors will now be selected
|
|
OutNumMovedActors += GEditor->GetSelectedActorCount();
|
|
}
|
|
|
|
// Restore the original clipboard contents
|
|
FPlatformMisc::ClipboardCopy( *OriginalClipboardContent );
|
|
}
|
|
|
|
static TArray<FString> GStreamingMethodStrings;
|
|
static TArray<UClass*> GStreamingMethodClassList;
|
|
|
|
/**
|
|
* Initializes the list of possible level streaming methods.
|
|
* Does nothing if the lists are already initialized.
|
|
*/
|
|
void InitializeStreamingMethods()
|
|
{
|
|
check( GStreamingMethodStrings.Num() == GStreamingMethodClassList.Num() );
|
|
if ( GStreamingMethodClassList.Num() == 0 )
|
|
{
|
|
// Assemble a list of possible level streaming methods.
|
|
for ( TObjectIterator<UClass> It ; It ; ++It )
|
|
{
|
|
if ( It->IsChildOf( ULevelStreaming::StaticClass() ) &&
|
|
(It->HasAnyClassFlags(CLASS_EditInlineNew)) &&
|
|
!(It->HasAnyClassFlags(CLASS_Hidden | CLASS_Abstract | CLASS_Deprecated | CLASS_Transient)))
|
|
{
|
|
const FString ClassName( It->GetName() );
|
|
// Strip the leading "LevelStreaming" text from the class name.
|
|
// @todo DB: This assumes the names of all ULevelStreaming-derived types begin with the string "LevelStreaming".
|
|
GStreamingMethodStrings.Add( ClassName.Mid( 14 ) );
|
|
GStreamingMethodClassList.Add( *It );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ULevel* AddLevelsToWorld(UWorld* InWorld, const TArray<FString>& LevelPackageNames, UClass* LevelStreamingClass)
|
|
{
|
|
if ( !ensure(InWorld) )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FScopedSlowTask SlowTask(LevelPackageNames.Num(), LOCTEXT("AddLevelsToWorldTask", "Adding Levels to World"));
|
|
SlowTask.MakeDialog();
|
|
|
|
TArray<FString> PackageNames = LevelPackageNames;
|
|
|
|
// Sort the level packages alphabetically by name.
|
|
PackageNames.Sort();
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Try to add the levels that were specified in the dialog.
|
|
ULevel* NewLevel = nullptr;
|
|
for (const auto& PackageName : PackageNames)
|
|
{
|
|
SlowTask.EnterProgressFrame();
|
|
|
|
NewLevel = AddLevelToWorld(InWorld, *PackageName, LevelStreamingClass);
|
|
if (NewLevel)
|
|
{
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
|
|
} // for each file
|
|
|
|
// Set the last loaded level to be the current level
|
|
if (NewLevel)
|
|
{
|
|
InWorld->SetCurrentLevel(NewLevel);
|
|
}
|
|
|
|
// For safety
|
|
if (GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Landscape))
|
|
{
|
|
GLevelEditorModeTools().ActivateDefaultMode();
|
|
}
|
|
|
|
// refresh editor windows
|
|
FEditorDelegates::RefreshAllBrowsers.Broadcast();
|
|
|
|
// Update volume actor visibility for each viewport since we loaded a level which could potentially contain volumes
|
|
GUnrealEd->UpdateVolumeActorVisibility(NULL);
|
|
|
|
return NewLevel;
|
|
}
|
|
|
|
ULevel* AddLevelToWorld( UWorld* InWorld, const TCHAR* LevelPackageName, UClass* LevelStreamingClass)
|
|
{
|
|
ULevel* NewLevel = NULL;
|
|
bool bIsPersistentLevel = (InWorld->PersistentLevel->GetOutermost()->GetName() == FString(LevelPackageName));
|
|
|
|
if ( bIsPersistentLevel || FLevelUtils::FindStreamingLevel( InWorld, LevelPackageName ) )
|
|
{
|
|
// Do nothing if the level already exists in the world.
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "LevelAlreadyExistsInWorld", "A level with that name already exists in the world.") );
|
|
}
|
|
else
|
|
{
|
|
// If the selected class is still NULL, abort the operation.
|
|
if ( LevelStreamingClass == NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
ULevelStreaming* StreamingLevel = NewObject<ULevelStreaming>(InWorld, LevelStreamingClass, NAME_None, RF_NoFlags, NULL);
|
|
|
|
// Associate a package name.
|
|
StreamingLevel->SetWorldAssetByPackageName(LevelPackageName);
|
|
|
|
// Seed the level's draw color.
|
|
StreamingLevel->LevelColor = FLinearColor::MakeRandomColor();
|
|
|
|
// Add the new level to world.
|
|
InWorld->StreamingLevels.Add( StreamingLevel );
|
|
|
|
// Refresh just the newly created level.
|
|
TArray<ULevelStreaming*> LevelsForRefresh;
|
|
LevelsForRefresh.Add( StreamingLevel );
|
|
InWorld->RefreshStreamingLevels( LevelsForRefresh );
|
|
InWorld->MarkPackageDirty();
|
|
|
|
NewLevel = StreamingLevel->GetLoadedLevel();
|
|
if ( NewLevel != NULL )
|
|
{
|
|
EditorLevelUtils::SetLevelVisibility( NewLevel, true, true );
|
|
}
|
|
|
|
// Levels migrated from other projects may fail to load their world settings
|
|
// If so we create a new AWorldSettings actor here.
|
|
if ( NewLevel->Actors[0] == NULL )
|
|
{
|
|
UWorld *SubLevelWorld = CastChecked<UWorld>(NewLevel->GetOuter());
|
|
if (SubLevelWorld != NULL)
|
|
{
|
|
FActorSpawnParameters SpawnInfo;
|
|
SpawnInfo.bNoCollisionFail = true;
|
|
SpawnInfo.Name = GEngine->WorldSettingsClass->GetFName();
|
|
AWorldSettings* NewWorldSettings = SubLevelWorld->SpawnActor<AWorldSettings>( GEngine->WorldSettingsClass, SpawnInfo );
|
|
NewLevel->Actors[0] = NewWorldSettings;
|
|
}
|
|
else
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "LevelHasNoWorldSettings", "AddLevelToWorld: The level has no World Settings.") );
|
|
}
|
|
}
|
|
}
|
|
|
|
if(NewLevel) // if the level was successfully added
|
|
{
|
|
FEditorDelegates::OnAddLevelToWorld.Broadcast(NewLevel);
|
|
}
|
|
|
|
return NewLevel;
|
|
}
|
|
|
|
ULevelStreaming* SetStreamingClassForLevel(ULevelStreaming* InLevel, UClass* LevelStreamingClass)
|
|
{
|
|
check(InLevel);
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
// Cache off the package name, as it will be lost when unloading the level
|
|
const FName CachedPackageName = InLevel->GetWorldAssetPackageFName();
|
|
|
|
// First hide and remove the level if it exists
|
|
ULevel* Level = InLevel->GetLoadedLevel();
|
|
check(Level);
|
|
SetLevelVisibility( Level, false, false );
|
|
check(Level->OwningWorld);
|
|
UWorld* World = Level->OwningWorld;
|
|
|
|
World->StreamingLevels.Remove(InLevel);
|
|
|
|
// re-add the level with the desired streaming class
|
|
AddLevelToWorld(World, *(CachedPackageName.ToString()), LevelStreamingClass);
|
|
|
|
// Transfer level streaming settings
|
|
ULevelStreaming* NewStreamingLevel = FLevelUtils::FindStreamingLevel( Level );
|
|
if ( NewStreamingLevel )
|
|
{
|
|
NewStreamingLevel->LevelTransform = InLevel->LevelTransform;
|
|
NewStreamingLevel->EditorStreamingVolumes = InLevel->EditorStreamingVolumes;
|
|
NewStreamingLevel->MinTimeBetweenVolumeUnloadRequests = InLevel->MinTimeBetweenVolumeUnloadRequests;
|
|
NewStreamingLevel->LevelColor = InLevel->LevelColor;
|
|
NewStreamingLevel->Keywords = InLevel->Keywords;
|
|
}
|
|
|
|
return NewStreamingLevel;
|
|
}
|
|
|
|
void MakeLevelCurrent(ULevel* InLevel)
|
|
{
|
|
// Locked levels can't be made current.
|
|
if ( !FLevelUtils::IsLevelLocked( InLevel ) )
|
|
{
|
|
// Make current broadcast if it changed
|
|
if( InLevel->OwningWorld->SetCurrentLevel( InLevel ) )
|
|
{
|
|
FEditorDelegates::NewCurrentLevel.Broadcast();
|
|
}
|
|
|
|
// Deselect all selected builder brushes.
|
|
bool bDeselectedSomething = false;
|
|
for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It )
|
|
{
|
|
AActor* Actor = static_cast<AActor*>( *It );
|
|
checkSlow( Actor->IsA(AActor::StaticClass()) );
|
|
ABrush* Brush = Cast< ABrush >( Actor );
|
|
if ( Brush && FActorEditorUtils::IsABuilderBrush(Actor) )
|
|
{
|
|
GEditor->SelectActor( Actor, /*bInSelected=*/ false, /*bNotify=*/ false );
|
|
bDeselectedSomething = true;
|
|
}
|
|
}
|
|
|
|
// Send a selection change callback if necessary.
|
|
if ( bDeselectedSomething )
|
|
{
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
|
|
// Force the current level to be visible.
|
|
SetLevelVisibility( InLevel, true, false );
|
|
}
|
|
else
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelMakeLevelCurrent", "MakeLevelCurrent: The requested operation could not be completed because the level is locked.") );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a LevelStreaming from the world. Returns true if the LevelStreaming was removed successfully.
|
|
*
|
|
* @param InLevelStreaming The LevelStreaming to remove.
|
|
* @return true if the LevelStreaming was removed successfully, false otherwise.
|
|
*/
|
|
bool PrivateRemoveInvalidLevelFromWorld(ULevelStreaming* InLevelStreaming)
|
|
{
|
|
bool bRemovedLevelStreaming = false;
|
|
if (InLevelStreaming != NULL)
|
|
{
|
|
check(InLevelStreaming->GetLoadedLevel() == NULL); // This method is designed to be used to remove left over references to null levels
|
|
|
|
InLevelStreaming->Modify();
|
|
|
|
// Disassociate the level from the volume.
|
|
for ( auto VolIter = InLevelStreaming->EditorStreamingVolumes.CreateIterator(); VolIter; VolIter++ )
|
|
{
|
|
ALevelStreamingVolume* LevelStreamingVolume = *VolIter;
|
|
if ( LevelStreamingVolume )
|
|
{
|
|
LevelStreamingVolume->Modify();
|
|
LevelStreamingVolume->StreamingLevelNames.Remove( InLevelStreaming->GetWorldAssetPackageFName() );
|
|
}
|
|
}
|
|
|
|
// Disassociate the volumes from the level.
|
|
InLevelStreaming->EditorStreamingVolumes.Empty();
|
|
|
|
UWorld* OwningWorld = Cast<UWorld>(InLevelStreaming->GetOuter());
|
|
if (OwningWorld != NULL)
|
|
{
|
|
OwningWorld->StreamingLevels.Remove(InLevelStreaming);
|
|
OwningWorld->RefreshStreamingLevels();
|
|
bRemovedLevelStreaming = true;
|
|
}
|
|
}
|
|
return bRemovedLevelStreaming;
|
|
}
|
|
|
|
bool RemoveInvalidLevelFromWorld(ULevelStreaming* InLevelStreaming)
|
|
{
|
|
bool bRemoveSuccessful = PrivateRemoveInvalidLevelFromWorld(InLevelStreaming);
|
|
if (bRemoveSuccessful)
|
|
{
|
|
// Redraw the main editor viewports.
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
|
|
// refresh editor windows
|
|
FEditorDelegates::RefreshAllBrowsers.Broadcast();
|
|
|
|
// Update selection for any selected actors that were in the level and are no longer valid
|
|
GEditor->NoteSelectionChange();
|
|
|
|
// Collect garbage to clear out the destroyed level
|
|
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );
|
|
}
|
|
return bRemoveSuccessful;
|
|
}
|
|
|
|
bool RemoveLevelFromWorld(ULevel* InLevel)
|
|
{
|
|
if (GEditor->Layers.IsValid())
|
|
{
|
|
GEditor->Layers->RemoveLevelLayerInformation( InLevel );
|
|
}
|
|
|
|
GEditor->CloseEditedWorldAssets(CastChecked<UWorld>(InLevel->GetOuter()));
|
|
|
|
UWorld* OwningWorld = InLevel->OwningWorld;
|
|
const FName LevelPackageName = InLevel->GetOutermost()->GetFName();
|
|
const bool bRemovingCurrentLevel = InLevel->IsCurrentLevel();
|
|
const bool bRemoveSuccessful = PrivateRemoveLevelFromWorld( InLevel );
|
|
if ( bRemoveSuccessful )
|
|
{
|
|
if (bRemovingCurrentLevel)
|
|
{
|
|
MakeLevelCurrent(OwningWorld->PersistentLevel);
|
|
}
|
|
|
|
|
|
EditorDestroyLevel(InLevel);
|
|
|
|
// Redraw the main editor viewports.
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
|
|
// refresh editor windows
|
|
FEditorDelegates::RefreshAllBrowsers.Broadcast();
|
|
|
|
// Reset transaction buffer and run GC to clear out the destroyed level
|
|
GEditor->Cleanse(true, false, LOCTEXT("RemoveLevelTransReset", "Removing Levels from World"));
|
|
|
|
// Ensure that world was removed
|
|
UPackage* LevelPackage = FindObjectFast<UPackage>(NULL, LevelPackageName);
|
|
if (LevelPackage != nullptr)
|
|
{
|
|
UWorld* TheWorld = UWorld::FindWorldInPackage(LevelPackage->GetOutermost());
|
|
if (TheWorld != nullptr)
|
|
{
|
|
StaticExec(NULL, *FString::Printf(TEXT("OBJ REFS CLASS=%s NAME=%s shortest"),*TheWorld->GetClass()->GetName(), *TheWorld->GetPathName()));
|
|
TMap<UObject*,UProperty*> Route = FArchiveTraceRoute::FindShortestRootPath( TheWorld, true, GARBAGE_COLLECTION_KEEPFLAGS );
|
|
FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, TheWorld );
|
|
UE_LOG(LogStreaming, Fatal, TEXT("%s didn't get garbage collected!") LINE_TERMINATOR TEXT("%s"), *TheWorld->GetFullName(), *ErrorString );
|
|
}
|
|
}
|
|
}
|
|
return bRemoveSuccessful;
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes a level from the world. Returns true if the level was removed successfully.
|
|
*
|
|
* @param InLevel The level to remove from the world.
|
|
* @return true if the level was removed successfully, false otherwise.
|
|
*/
|
|
bool PrivateRemoveLevelFromWorld(ULevel* InLevel)
|
|
{
|
|
if ( !InLevel || InLevel->IsPersistentLevel() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( FLevelUtils::IsLevelLocked(InLevel) )
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelRemoveLevelFromWorld", "RemoveLevelFromWorld: The requested operation could not be completed because the level is locked.") );
|
|
return false;
|
|
}
|
|
|
|
int32 StreamingLevelIndex = INDEX_NONE;
|
|
|
|
for( int32 LevelIndex = 0 ; LevelIndex < InLevel->OwningWorld->StreamingLevels.Num() ; ++LevelIndex )
|
|
{
|
|
ULevelStreaming* StreamingLevel = InLevel->OwningWorld->StreamingLevels[ LevelIndex ];
|
|
if( StreamingLevel && StreamingLevel->GetLoadedLevel() == InLevel )
|
|
{
|
|
StreamingLevelIndex = LevelIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (StreamingLevelIndex != INDEX_NONE)
|
|
{
|
|
InLevel->OwningWorld->StreamingLevels[StreamingLevelIndex]->MarkPendingKill();
|
|
InLevel->OwningWorld->StreamingLevels.RemoveAt( StreamingLevelIndex );
|
|
InLevel->OwningWorld->RefreshStreamingLevels();
|
|
}
|
|
else if (InLevel->bIsVisible)
|
|
{
|
|
InLevel->OwningWorld->RemoveFromWorld(InLevel);
|
|
check(InLevel->bIsVisible == false);
|
|
}
|
|
|
|
InLevel->ReleaseRenderingResources();
|
|
|
|
IStreamingManager::Get().RemoveLevel( InLevel );
|
|
UWorld* World = InLevel->OwningWorld;
|
|
World->RemoveLevel(InLevel);
|
|
InLevel->ClearLevelComponents();
|
|
|
|
// remove all group actors from the world in the level we are removing
|
|
// otherwise, this will cause group actors to not be garbage collected
|
|
for (int32 GroupIndex = World->ActiveGroupActors.Num()-1; GroupIndex >= 0; --GroupIndex)
|
|
{
|
|
AGroupActor* GroupActor = Cast<AGroupActor>(World->ActiveGroupActors[GroupIndex]);
|
|
if (GroupActor && GroupActor->IsInLevel(InLevel))
|
|
{
|
|
World->ActiveGroupActors.RemoveAt(GroupIndex);
|
|
}
|
|
}
|
|
|
|
// Mark all model components as pending kill so GC deletes references to them.
|
|
for (UModelComponent* ModelComponent : InLevel->ModelComponents)
|
|
{
|
|
if (ModelComponent != nullptr)
|
|
{
|
|
ModelComponent->MarkPendingKill();
|
|
}
|
|
}
|
|
|
|
// Mark all actors and their components as pending kill so GC will delete references to them.
|
|
for (AActor* Actor : InLevel->Actors)
|
|
{
|
|
if (Actor != nullptr)
|
|
{
|
|
Actor->MarkComponentsAsPendingKill();
|
|
Actor->MarkPendingKill();
|
|
}
|
|
}
|
|
|
|
World->MarkPackageDirty();
|
|
World->BroadcastLevelsChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EditorDestroyLevel(ULevel* InLevel)
|
|
{
|
|
UWorld* World = InLevel->OwningWorld;
|
|
|
|
InLevel->GetOuter()->MarkPendingKill();
|
|
InLevel->MarkPendingKill();
|
|
InLevel->GetOuter()->ClearFlags(RF_Public | RF_Standalone);
|
|
|
|
UPackage* Package = Cast<UPackage>(InLevel->GetOutermost());
|
|
// We want to unconditionally destroy the level, so clear the dirty flag here so it can be unloaded successfully
|
|
Package->SetDirtyFlag(false);
|
|
|
|
TArray<UPackage*> Packages;
|
|
Packages.Add(Package);
|
|
if (!PackageTools::UnloadPackages(Packages))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("Package"), FText::FromString(Package->GetName()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("UnloadPackagesFail", "Unable to unload package '{Package}'."), Args));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ULevel* CreateNewLevel( UWorld* InWorld, bool bMoveSelectedActorsIntoNewLevel, UClass* LevelStreamingClass, const FString& DefaultFilename )
|
|
{
|
|
// Editor modes cannot be active when any level saving occurs.
|
|
GLevelEditorModeTools().DeactivateAllModes();
|
|
|
|
UWorld* NewWorld = nullptr;
|
|
if (UEditorEngine::IsUsingWorldAssets())
|
|
{
|
|
// Create a new world
|
|
UWorldFactory* Factory = NewObject<UWorldFactory>();
|
|
Factory->WorldType = EWorldType::Inactive;
|
|
UPackage* Pkg = CreatePackage(NULL, NULL);
|
|
FName WorldName(TEXT("NewWorld"));
|
|
EObjectFlags Flags = RF_Public | RF_Standalone;
|
|
NewWorld = CastChecked<UWorld>(Factory->FactoryCreateNew(UWorld::StaticClass(), Pkg, WorldName, Flags, NULL, GWarn));
|
|
if ( NewWorld )
|
|
{
|
|
FAssetRegistryModule::AssetCreated(NewWorld);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create a new world - so we can 'borrow' its level
|
|
NewWorld = UWorld::CreateWorld(EWorldType::None, false );
|
|
check(NewWorld);
|
|
}
|
|
|
|
// Save the new world to disk.
|
|
const bool bNewWorldSaved = FEditorFileUtils::SaveLevel( NewWorld->PersistentLevel, DefaultFilename );
|
|
FString NewPackageName;
|
|
if ( bNewWorldSaved )
|
|
{
|
|
NewPackageName = NewWorld->GetOutermost()->GetName();
|
|
}
|
|
|
|
if (!UEditorEngine::IsUsingWorldAssets())
|
|
{
|
|
// Destroy the new world we created and collect the garbage
|
|
NewWorld->DestroyWorld( false );
|
|
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );
|
|
}
|
|
|
|
// If the new world was saved successfully, import it as a streaming level.
|
|
ULevel* NewLevel = NULL;
|
|
if ( bNewWorldSaved )
|
|
{
|
|
NewLevel = AddLevelToWorld( InWorld, *NewPackageName, LevelStreamingClass );
|
|
|
|
// If we are moving the selected actors to the new level move them now
|
|
if ( bMoveSelectedActorsIntoNewLevel )
|
|
{
|
|
GEditor->MoveSelectedActorsToLevel( NewLevel );
|
|
}
|
|
// Finally make the new level the current one
|
|
InWorld->SetCurrentLevel( NewLevel );
|
|
}
|
|
|
|
// Broadcast the levels have changed (new style)
|
|
InWorld->BroadcastLevelsChanged();
|
|
return NewLevel;
|
|
}
|
|
|
|
void DeselectAllSurfacesInLevel(ULevel* InLevel)
|
|
{
|
|
if(InLevel)
|
|
{
|
|
UModel* Model = InLevel->Model;
|
|
for( int32 SurfaceIndex = 0 ; SurfaceIndex < Model->Surfs.Num() ; ++SurfaceIndex )
|
|
{
|
|
FBspSurf& Surf = Model->Surfs[SurfaceIndex];
|
|
if( Surf.PolyFlags & PF_Selected )
|
|
{
|
|
Model->ModifySurf( SurfaceIndex, false );
|
|
Surf.PolyFlags &= ~PF_Selected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetLevelVisibility(ULevel* Level, bool bShouldBeVisible, bool bForceLayersVisible)
|
|
{
|
|
// Nothing to do
|
|
if ( Level == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// Handle the case of the p-level
|
|
// The p-level can't be unloaded, so its actors/BSP should just be temporarily hidden/unhidden
|
|
// Also, intentionally do not force layers visible for the p-level
|
|
if ( Level->IsPersistentLevel() )
|
|
{
|
|
//create a transaction so we can undo the visibilty toggle
|
|
const FScopedTransaction Transaction( LOCTEXT( "ToggleLevelVisibility", "Toggle Level Visibility" ) );
|
|
if ( Level->bIsVisible != bShouldBeVisible )
|
|
{
|
|
Level->Modify();
|
|
}
|
|
// Set the visibility of each actor in the p-level
|
|
for ( TArray<AActor*>::TIterator PLevelActorIter( Level->Actors ); PLevelActorIter; ++PLevelActorIter )
|
|
{
|
|
AActor* CurActor = *PLevelActorIter;
|
|
if ( CurActor && !FActorEditorUtils::IsABuilderBrush(CurActor) && CurActor->bHiddenEdLevel == bShouldBeVisible )
|
|
{
|
|
CurActor->Modify();
|
|
CurActor->bHiddenEdLevel = !bShouldBeVisible;
|
|
CurActor->RegisterAllComponents();
|
|
CurActor->MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
// Set the visibility of each BSP surface in the p-level
|
|
UModel* CurLevelModel = Level->Model;
|
|
if ( CurLevelModel )
|
|
{
|
|
CurLevelModel->Modify();
|
|
for ( TArray<FBspSurf>::TIterator SurfaceIterator( CurLevelModel->Surfs ); SurfaceIterator; ++SurfaceIterator )
|
|
{
|
|
FBspSurf& CurSurf = *SurfaceIterator;
|
|
CurSurf.bHiddenEdLevel = !bShouldBeVisible;
|
|
}
|
|
}
|
|
|
|
// Add/remove model components from the scene
|
|
for(int32 ComponentIndex = 0;ComponentIndex < Level->ModelComponents.Num();ComponentIndex++)
|
|
{
|
|
UModelComponent* CurLevelModelCmp = Level->ModelComponents[ComponentIndex];
|
|
if(CurLevelModelCmp)
|
|
{
|
|
if (bShouldBeVisible)
|
|
{
|
|
CurLevelModelCmp->RegisterComponentWithWorld(Level->OwningWorld);
|
|
}
|
|
else if (!bShouldBeVisible && CurLevelModelCmp->IsRegistered())
|
|
{
|
|
CurLevelModelCmp->UnregisterComponent();
|
|
}
|
|
}
|
|
}
|
|
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
ULevelStreaming* StreamingLevel = NULL;
|
|
if (Level->OwningWorld == NULL || Level->OwningWorld->PersistentLevel != Level )
|
|
{
|
|
StreamingLevel = FLevelUtils::FindStreamingLevel( Level );
|
|
}
|
|
|
|
//create a transaction so we can undo the visibilty toggle
|
|
const FScopedTransaction Transaction( LOCTEXT( "ToggleLevelVisibility", "Toggle Level Visibility" ) );
|
|
|
|
// Handle the case of a streaming level
|
|
if ( StreamingLevel )
|
|
{
|
|
// We need to set the RF_Transactional to make a streaming level serialize itself. so store the original ones, set the flag, and put the original flags back when done
|
|
EObjectFlags cachedFlags = StreamingLevel->GetFlags();
|
|
StreamingLevel->SetFlags( RF_Transactional );
|
|
StreamingLevel->Modify();
|
|
StreamingLevel->SetFlags( cachedFlags );
|
|
|
|
// Set the visibility state for this streaming level.
|
|
StreamingLevel->bShouldBeVisibleInEditor = bShouldBeVisible;
|
|
}
|
|
|
|
if (!bShouldBeVisible && GEditor->Layers.IsValid())
|
|
{
|
|
GEditor->Layers->RemoveLevelLayerInformation( Level );
|
|
}
|
|
|
|
// UpdateLevelStreaming sets Level->bIsVisible directly, so we need to make sure it gets saved to the transaction buffer.
|
|
if ( Level->bIsVisible != bShouldBeVisible )
|
|
{
|
|
Level->Modify();
|
|
}
|
|
|
|
if ( StreamingLevel )
|
|
{
|
|
Level->OwningWorld->FlushLevelStreaming();
|
|
|
|
// In the Editor we expect this operation will complete in a single call
|
|
check(Level->bIsVisible == bShouldBeVisible);
|
|
}
|
|
else if (Level->OwningWorld != NULL)
|
|
{
|
|
// In case we level has no associated StreamingLevel, remove or add to world directly
|
|
if (bShouldBeVisible)
|
|
{
|
|
if (!Level->bIsVisible)
|
|
{
|
|
Level->OwningWorld->AddToWorld(Level);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Level->OwningWorld->RemoveFromWorld(Level);
|
|
}
|
|
|
|
// In the Editor we expect this operation will complete in a single call
|
|
check(Level->bIsVisible == bShouldBeVisible);
|
|
}
|
|
|
|
if (bShouldBeVisible && GEditor->Layers.IsValid())
|
|
{
|
|
GEditor->Layers->AddLevelLayerInformation( Level );
|
|
}
|
|
|
|
// Force the level's layers to be visible, if desired
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
|
|
// Iterate over the level's actors, making a list of their layers and unhiding the layers.
|
|
TTransArray<AActor*>& Actors = Level->Actors;
|
|
for ( int32 ActorIndex = 0 ; ActorIndex < Actors.Num() ; ++ActorIndex )
|
|
{
|
|
AActor* Actor = Actors[ ActorIndex ];
|
|
if ( Actor )
|
|
{
|
|
bool bModified = false;
|
|
if (bShouldBeVisible && bForceLayersVisible &&
|
|
GEditor->Layers.IsValid() &&
|
|
GEditor->Layers->IsActorValidForLayer( Actor ))
|
|
{
|
|
// Make the actor layer visible, if it's not already.
|
|
if ( Actor->bHiddenEdLayer )
|
|
{
|
|
bModified = Actor->Modify();
|
|
Actor->bHiddenEdLayer = false;
|
|
}
|
|
|
|
const bool bIsVisible = true;
|
|
GEditor->Layers->SetLayersVisibility( Actor->Layers, bIsVisible );
|
|
}
|
|
|
|
// Set the visibility of each actor in the streaming level
|
|
if ( !FActorEditorUtils::IsABuilderBrush(Actor) && Actor->bHiddenEdLevel == bShouldBeVisible )
|
|
{
|
|
if ( !bModified )
|
|
{
|
|
bModified = Actor->Modify();
|
|
}
|
|
Actor->bHiddenEdLevel = !bShouldBeVisible;
|
|
|
|
if (bShouldBeVisible)
|
|
{
|
|
Actor->ReregisterAllComponents();
|
|
}
|
|
else
|
|
{
|
|
Actor->UnregisterAllComponents();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FEditorDelegates::RefreshLayerBrowser.Broadcast();
|
|
|
|
// Notify the Scene Outliner, as new Actors may be present in the world.
|
|
GEngine->BroadcastLevelActorListChanged();
|
|
|
|
// If the level is being hidden, deselect actors and surfaces that belong to this level.
|
|
if ( !bShouldBeVisible )
|
|
{
|
|
USelection* SelectedActors = GEditor->GetSelectedActors();
|
|
SelectedActors->Modify();
|
|
TTransArray<AActor*>& Actors = Level->Actors;
|
|
for ( int32 ActorIndex = 0 ; ActorIndex < Actors.Num() ; ++ActorIndex )
|
|
{
|
|
AActor* Actor = Actors[ ActorIndex ];
|
|
if ( Actor )
|
|
{
|
|
SelectedActors->Deselect( Actor );
|
|
}
|
|
}
|
|
|
|
DeselectAllSurfacesInLevel(Level);
|
|
|
|
// Tell the editor selection status was changed.
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
|
|
Level->bIsVisible = bShouldBeVisible;
|
|
}
|
|
|
|
/**
|
|
* Assembles the set of all referenced worlds.
|
|
*
|
|
* @param OutWorlds [out] The set of referenced worlds.
|
|
* @param bIncludeGWorld If true, include GWorld in the output list.
|
|
* @param bOnlyEditorVisible If true, only sub-levels that should be visible in-editor are included
|
|
*/
|
|
void GetWorlds(UWorld* InWorld, TArray<UWorld*>& OutWorlds, bool bIncludeInWorld, bool bOnlyEditorVisible)
|
|
{
|
|
OutWorlds.Empty();
|
|
|
|
if (!InWorld)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( bIncludeInWorld )
|
|
{
|
|
OutWorlds.AddUnique( InWorld );
|
|
}
|
|
|
|
// Iterate over the world's level array to find referenced levels ("worlds"). We don't
|
|
for ( int32 LevelIndex = 0 ; LevelIndex < InWorld->StreamingLevels.Num() ; ++LevelIndex )
|
|
{
|
|
ULevelStreaming* StreamingLevel = InWorld->StreamingLevels[LevelIndex];
|
|
if ( StreamingLevel )
|
|
{
|
|
// If we asked for only sub-levels that are editor-visible, then limit our results appropriately
|
|
bool bShouldAlwaysBeLoaded = false; // Cast< ULevelStreamingAlwaysLoaded >( StreamingLevel ) != NULL;
|
|
if( !bOnlyEditorVisible || bShouldAlwaysBeLoaded || StreamingLevel->bShouldBeVisibleInEditor )
|
|
{
|
|
const ULevel* Level = StreamingLevel->GetLoadedLevel();
|
|
|
|
// This should always be the case for valid level names as the Editor preloads all packages.
|
|
if ( Level != NULL )
|
|
{
|
|
// Newer levels have their packages' world as the outer.
|
|
UWorld* World = Cast<UWorld>( Level->GetOuter() );
|
|
if ( World != NULL )
|
|
{
|
|
OutWorlds.AddUnique( World );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Levels can be loaded directly without StreamingLevel facilities
|
|
for ( int32 LevelIndex = 0 ; LevelIndex < InWorld->GetLevels().Num() ; ++LevelIndex )
|
|
{
|
|
ULevel* Level = InWorld->GetLevel(LevelIndex);
|
|
if ( Level )
|
|
{
|
|
// Newer levels have their packages' world as the outer.
|
|
UWorld* World = Cast<UWorld>( Level->GetOuter() );
|
|
if ( World != NULL )
|
|
{
|
|
OutWorlds.AddUnique( World );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|