You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Removed deprecated or dead code paths * Simplified build system setup for physics support * Deprecated build system flags and unsupported macros #jira none #rb Chris.Caulfield, Kriss.Gossart #preflight 62963ec0fe779f23c8ea0c5e [CL 20450744 by Benn Gallagher in ue5-main branch]
5410 lines
150 KiB
C++
5410 lines
150 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Cascade.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Editor.h"
|
|
#include "Components/VectorFieldComponent.h"
|
|
#include "Engine/InterpCurveEdSetup.h"
|
|
#include "CascadeConfiguration.h"
|
|
#include "Particles/ParticleSystem.h"
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
#include "CascadeParticleSystemComponent.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Framework/Application/MenuStack.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Preferences/CascadeOptions.h"
|
|
#include "Distributions/DistributionFloatUniform.h"
|
|
#include "Distributions/DistributionFloatUniformCurve.h"
|
|
#include "Distributions/DistributionVectorUniform.h"
|
|
#include "Distributions/DistributionVectorUniformCurve.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "Engine/Selection.h"
|
|
#include "CascadeModule.h"
|
|
#include "FXSystem.h"
|
|
#include "ObjectTools.h"
|
|
#include "DistCurveEditorModule.h"
|
|
#include "CascadePreviewViewportClient.h"
|
|
#include "SCascadeEmitterCanvas.h"
|
|
#include "CascadeEmitterCanvasClient.h"
|
|
#include "SCascadePreviewViewport.h"
|
|
#include "CascadeActions.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Widgets/Colors/SColorPicker.h"
|
|
|
|
#include "PropertyEditorModule.h"
|
|
#include "IDetailsView.h"
|
|
|
|
#include "Particles/Event/ParticleModuleEventGenerator.h"
|
|
#include "Particles/Parameter/ParticleModuleParameterDynamic.h"
|
|
#include "Particles/Spawn/ParticleModuleSpawn.h"
|
|
#include "Particles/TypeData/ParticleModuleTypeDataBase.h"
|
|
#include "Particles/TypeData/ParticleModuleTypeDataMesh.h"
|
|
#include "Particles/VectorField/ParticleModuleVectorFieldLocal.h"
|
|
#include "Particles/ParticleLODLevel.h"
|
|
#include "Particles/ParticleSpriteEmitter.h"
|
|
#include "Particles/ParticleModuleRequired.h"
|
|
|
|
#include "Runtime/Analytics/Analytics/Public/AnalyticsEventAttribute.h"
|
|
#include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
|
|
#include "EngineAnalytics.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "Widgets/Input/STextEntryPopup.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "UnrealEngine.h"
|
|
#include "Physics/PhysicsInterfaceCore.h"
|
|
|
|
|
|
static const FName Cascade_PreviewViewportTab("Cascade_PreviewViewport");
|
|
static const FName Cascade_EmmitterCanvasTab("Cascade_EmitterCanvas");
|
|
static const FName Cascade_PropertiesTab("Cascade_Properties");
|
|
static const FName Cascade_CurveEditorTab("Cascade_CurveEditor");
|
|
|
|
DEFINE_LOG_CATEGORY(LogCascade);
|
|
|
|
FRandomStream FCascade::RandomStream(FPlatformTime::Cycles());
|
|
|
|
FCascade::FCascade()
|
|
: ParticleSystem(nullptr)
|
|
, ParticleSystemComponent(nullptr)
|
|
, LocalVectorFieldPreviewComponent(nullptr)
|
|
, EditorOptions(nullptr)
|
|
, EditorConfig(nullptr)
|
|
, SelectedModule(nullptr)
|
|
, SelectedEmitter(nullptr)
|
|
, CopyModule(nullptr)
|
|
, CopyEmitter(nullptr)
|
|
, CurveToReplace(nullptr)
|
|
{
|
|
}
|
|
|
|
void FCascade::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(NSLOCTEXT("Cascade", "WorkspaceMenu_Cascade", "Cascade"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->RegisterTabSpawner( Cascade_PreviewViewportTab, FOnSpawnTab::CreateSP( this, &FCascade::SpawnTab, Cascade_PreviewViewportTab ) )
|
|
.SetDisplayName(NSLOCTEXT("Cascade", "SummonViewport", "Viewport"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner( Cascade_EmmitterCanvasTab, FOnSpawnTab::CreateSP( this, &FCascade::SpawnTab, Cascade_EmmitterCanvasTab ) )
|
|
.SetDisplayName(NSLOCTEXT("Cascade", "SummonCanvas", "Emitters"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Emitter"));
|
|
|
|
InTabManager->RegisterTabSpawner( Cascade_PropertiesTab, FOnSpawnTab::CreateSP( this, &FCascade::SpawnTab, Cascade_PropertiesTab ) )
|
|
.SetDisplayName(NSLOCTEXT("Cascade", "SummonProperties", "Details"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner( Cascade_CurveEditorTab, FOnSpawnTab::CreateSP( this, &FCascade::SpawnTab, Cascade_CurveEditorTab ) )
|
|
.SetDisplayName(NSLOCTEXT("Cascade", "SummonCurveEditor", "CurveEditor"))
|
|
.SetGroup( WorkspaceMenuCategoryRef )
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase"));
|
|
}
|
|
|
|
void FCascade::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->UnregisterTabSpawner( Cascade_PreviewViewportTab );
|
|
InTabManager->UnregisterTabSpawner( Cascade_EmmitterCanvasTab );
|
|
InTabManager->UnregisterTabSpawner( Cascade_PropertiesTab );
|
|
InTabManager->UnregisterTabSpawner( Cascade_CurveEditorTab );
|
|
}
|
|
|
|
FCascade::~FCascade()
|
|
{
|
|
UE_LOG(LogCascade,Log,TEXT("Quitting Cascade. FXSystem=0x%p"),GetFXSystem());
|
|
|
|
GEditor->UnregisterForUndo(this);
|
|
// If the user opened the geometry properties window, we request it be destroyed.
|
|
TSharedPtr<SWindow> WindowPtr = GeometryPropertiesWindow.Pin();
|
|
GeometryPropertiesWindow = NULL;
|
|
|
|
if ( WindowPtr.IsValid() )
|
|
{
|
|
WindowPtr->RequestDestroyWindow();
|
|
}
|
|
|
|
if (ParticleSystemComponent)
|
|
{
|
|
ParticleSystemComponent->ResetParticles(/*bEmptyInstances=*/ true);
|
|
ParticleSystemComponent->CascadePreviewViewportPtr = nullptr;
|
|
|
|
// Reset the detail mode values
|
|
for (TObjectIterator<UParticleSystemComponent> It; It; ++It)
|
|
{
|
|
if (It->Template == ParticleSystemComponent->Template)
|
|
{
|
|
It->EditorDetailMode = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ParticleSystem != NULL)
|
|
{
|
|
ParticleSystem->TurnOffSoloing();
|
|
}
|
|
|
|
DestroyColorPicker();
|
|
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
// Save the preview scene
|
|
PreviewViewport->GetViewportClient()->GetPreviewScene().SaveSettings(TEXT("CascadeEditor"));
|
|
|
|
UStaticMeshComponent* FloorComponent = PreviewViewport->GetViewportClient()->GetFloorComponent();
|
|
if (FloorComponent)
|
|
{
|
|
EditorOptions->FloorPosition = FloorComponent->GetRelativeLocation();
|
|
EditorOptions->FloorRotation = FloorComponent->GetRelativeRotation();
|
|
EditorOptions->FloorScale3D = FloorComponent->GetRelativeScale3D();
|
|
|
|
if (FloorComponent->GetStaticMesh())
|
|
{
|
|
if (FloorComponent->GetStaticMesh()->GetOuter())
|
|
{
|
|
EditorOptions->FloorMesh = FloorComponent->GetStaticMesh()->GetOuter()->GetName();
|
|
EditorOptions->FloorMesh += TEXT(".");
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Unable to locate Cascade floor mesh outer..."));
|
|
EditorOptions->FloorMesh = TEXT("");
|
|
}
|
|
|
|
EditorOptions->FloorMesh += FloorComponent->GetStaticMesh()->GetName();
|
|
}
|
|
else
|
|
{
|
|
EditorOptions->FloorMesh += FString::Printf(TEXT("/Engine/EditorMeshes/AnimTreeEd_PreviewFloor.AnimTreeEd_PreviewFloor"));
|
|
}
|
|
|
|
FString Name;
|
|
|
|
Name = EditorOptions->FloorMesh;
|
|
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
}
|
|
|
|
ICascadeModule* CascadeModule = &FModuleManager::GetModuleChecked<ICascadeModule>("Cascade");
|
|
CascadeModule->CascadeClosed(this);
|
|
}
|
|
|
|
void FCascade::OnComponentActivationChange(UParticleSystemComponent* PSC, bool bActivated)
|
|
{
|
|
check(PSC);
|
|
|
|
UCascadeParticleSystemComponent* CPSC = Cast<UCascadeParticleSystemComponent>(PSC);
|
|
|
|
if (CPSC && CPSC->CascadePreviewViewportPtr)
|
|
{
|
|
if (FCascade* Cascade = CPSC->CascadePreviewViewportPtr->GetCascade())
|
|
{
|
|
PSC->SetManagingSignificance(true);
|
|
PSC->SetRequiredSignificance(Cascade->GetRequiredSignificance());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::InitCascade(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit)
|
|
{
|
|
ParticleSystem = CastChecked<UParticleSystem>(ObjectToEdit);
|
|
|
|
ParticleSystem->EditorLODSetting = 0;
|
|
ParticleSystem->SetupLODValidity();
|
|
|
|
// Support undo/redo
|
|
ParticleSystem->SetFlags(RF_Transactional);
|
|
|
|
CurrentLODIdx = 0;
|
|
|
|
EditorOptions = NewObject<UCascadeOptions>();
|
|
check(EditorOptions);
|
|
EditorConfig = NewObject<UCascadeConfiguration>();
|
|
check(EditorConfig);
|
|
|
|
FString Description;
|
|
for (int32 EmitterIdx = 0; EmitterIdx < ParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIdx];
|
|
if (Emitter)
|
|
{
|
|
Description += FString::Printf(TEXT("Emitter%d["), EmitterIdx);
|
|
Emitter->SetFlags(RF_Transactional);
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->GetLODLevel(LODIndex);
|
|
if (LODLevel)
|
|
{
|
|
Description += FString::Printf(TEXT("LOD%d("), LODIndex);
|
|
LODLevel->SetFlags(RF_Transactional);
|
|
check(LODLevel->RequiredModule);
|
|
LODLevel->RequiredModule->SetTransactionFlag();
|
|
check(LODLevel->SpawnModule);
|
|
LODLevel->SpawnModule->SetTransactionFlag();
|
|
if (LODLevel->Modules.Num() > 0)
|
|
{
|
|
Description += FString::Printf(TEXT("Modules%d"), LODLevel->Modules.Num());
|
|
for (int32 ModuleIdx = 0; ModuleIdx < LODLevel->Modules.Num(); ModuleIdx++)
|
|
{
|
|
UParticleModule* pkModule = LODLevel->Modules[ModuleIdx];
|
|
pkModule->SetTransactionFlag();
|
|
}
|
|
}
|
|
Description += TEXT(")");
|
|
if (Emitter->LODLevels.Num() > LODIndex + 1)
|
|
{
|
|
Description += TEXT(",");
|
|
}
|
|
}
|
|
}
|
|
Description += TEXT("]");
|
|
if (ParticleSystem->Emitters.Num() > EmitterIdx + 1)
|
|
{
|
|
Description += TEXT(",");
|
|
}
|
|
}
|
|
}
|
|
if (Description.Len() > 0 && FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.Init"), TEXT("Overview"), Description);
|
|
}
|
|
|
|
ParticleSystemComponent = NewObject<UCascadeParticleSystemComponent>();
|
|
|
|
LocalVectorFieldPreviewComponent = NewObject<UVectorFieldComponent>();
|
|
|
|
bIsSoloing = false;
|
|
|
|
bTransactionInProgress = false;
|
|
|
|
SetSelectedModule(NULL, NULL);
|
|
|
|
CopyModule = NULL;
|
|
CopyEmitter = NULL;
|
|
|
|
CurveToReplace = NULL;
|
|
DetailMode = GlobalDetailMode = GetCachedScalabilityCVars().DetailMode;
|
|
RequiredSignificance = EParticleSignificanceLevel::Low;
|
|
|
|
bIsToggleMotion = false;
|
|
MotionModeRadius = EditorOptions->MotionModeRadius;
|
|
AccumulatedMotionTime = 0.0f;
|
|
TimeScale = CachedTimeScale = 1.0f;
|
|
bIsToggleLoopSystem = true;
|
|
bIsPendingReset = false;
|
|
ResetTime = BIG_NUMBER;
|
|
TotalTime = 0.0;
|
|
ParticleMemoryUpdateTime = 5.0f;
|
|
|
|
bParticleModuleClassesInitialized = false;
|
|
|
|
InitParticleModuleClasses();
|
|
|
|
// Create a new emitter if the particle system is empty...
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
OnNewEmitter();
|
|
}
|
|
|
|
GEditor->RegisterForUndo(this);
|
|
|
|
// Register our commands. This will only register them if not previously registered
|
|
FCascadeCommands::Register();
|
|
|
|
BindCommands();
|
|
|
|
CreateInternalWidgets();
|
|
|
|
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_Cascade_Layout_v3" )
|
|
->AddArea(
|
|
FTabManager::NewPrimaryArea()
|
|
->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()
|
|
->SetSizeCoefficient(0.9f)
|
|
->SetOrientation(Orient_Horizontal)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()
|
|
->SetSizeCoefficient(0.3f)
|
|
->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack() ->AddTab( Cascade_PreviewViewportTab, ETabState::OpenedTab )
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack() ->AddTab( Cascade_PropertiesTab, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()
|
|
->SetSizeCoefficient(0.7f)
|
|
->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack() ->SetSizeCoefficient(0.6f) ->AddTab( Cascade_EmmitterCanvasTab, ETabState::OpenedTab )
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack() ->SetSizeCoefficient(0.4f) ->AddTab( Cascade_CurveEditorTab, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
const bool bCreateDefaultStandaloneMenu = true;
|
|
const bool bCreateDefaultToolbar = true;
|
|
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, CascadeAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit);
|
|
|
|
ICascadeModule* CascadeModule = &FModuleManager::LoadModuleChecked<ICascadeModule>( "Cascade" );
|
|
AddMenuExtender(CascadeModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
ExtendToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
// @todo toolkit world centric editing
|
|
/*if(IsWorldCentricAssetEditor())
|
|
{
|
|
SpawnToolkitTab(GetToolbarTabId(), FString(), EToolkitTabSpot::ToolBar);
|
|
SpawnToolkitTab(FName(TEXT("Cascade_PreviewViewport")), FString(), EToolkitTabSpot::Viewport);
|
|
SpawnToolkitTab(FName(TEXT("Cascade_EmitterCanvas")), FString(), EToolkitTabSpot::Viewport);
|
|
SpawnToolkitTab(FName(TEXT("Cascade_Properties")), FString(), EToolkitTabSpot::Details);
|
|
SpawnToolkitTab(FName(TEXT("Cascade_CurveEditor")), FString(), EToolkitTabSpot::Details);
|
|
}*/
|
|
}
|
|
|
|
UParticleSystem* FCascade::GetParticleSystem() const
|
|
{
|
|
return ParticleSystem;
|
|
}
|
|
|
|
UCascadeParticleSystemComponent* FCascade::GetParticleSystemComponent() const
|
|
{
|
|
return ParticleSystemComponent;
|
|
}
|
|
|
|
UVectorFieldComponent* FCascade::GetLocalVectorFieldComponent() const
|
|
{
|
|
return LocalVectorFieldPreviewComponent;
|
|
}
|
|
|
|
FFXSystemInterface* FCascade::GetFXSystem() const
|
|
{
|
|
check(PreviewViewport.IsValid());
|
|
auto World = PreviewViewport->GetViewportClient()->GetPreviewScene().GetWorld();
|
|
check(World);
|
|
return World->FXSystem;
|
|
}
|
|
|
|
UParticleEmitter* FCascade::GetSelectedEmitter() const
|
|
{
|
|
return SelectedEmitter;
|
|
}
|
|
|
|
UParticleModule* FCascade::GetSelectedModule() const
|
|
{
|
|
return SelectedModule;
|
|
}
|
|
|
|
int32 FCascade::GetSelectedModuleIndex()
|
|
{
|
|
return SelectedModuleIndex;
|
|
}
|
|
|
|
int32 FCascade::GetCurrentlySelectedLODLevelIndex() const
|
|
{
|
|
int32 SetLODLevelIndex = CurrentLODIdx;
|
|
if (SetLODLevelIndex < 0)
|
|
{
|
|
SetLODLevelIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
if (ParticleSystem && (ParticleSystem->Emitters.Num() > 0))
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter)
|
|
{
|
|
if (SetLODLevelIndex >= Emitter->LODLevels.Num())
|
|
{
|
|
SetLODLevelIndex = Emitter->LODLevels.Num() - 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLODLevelIndex = 0;
|
|
}
|
|
}
|
|
|
|
return SetLODLevelIndex;
|
|
}
|
|
|
|
UParticleLODLevel* FCascade::GetCurrentlySelectedLODLevel() const
|
|
{
|
|
int32 CurrentLODLevel = GetCurrentlySelectedLODLevelIndex();
|
|
if ((CurrentLODLevel >= 0) && SelectedEmitter)
|
|
{
|
|
return SelectedEmitter->GetLODLevel(CurrentLODLevel);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UParticleLODLevel* FCascade::GetCurrentlySelectedLODLevel(UParticleEmitter* InEmitter)
|
|
{
|
|
if (InEmitter)
|
|
{
|
|
UParticleEmitter* SaveSelectedEmitter = SelectedEmitter;
|
|
SelectedEmitter = InEmitter;
|
|
UParticleLODLevel* ReturnLODLevel = GetCurrentlySelectedLODLevel();
|
|
SelectedEmitter = SaveSelectedEmitter;
|
|
return ReturnLODLevel;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UCascadeOptions* FCascade::GetEditorOptions() const
|
|
{
|
|
return EditorOptions;
|
|
}
|
|
|
|
UCascadeConfiguration* FCascade::GetEditorConfiguration() const
|
|
{
|
|
return EditorConfig;
|
|
}
|
|
|
|
TSharedPtr<IDistributionCurveEditor> FCascade::GetCurveEditor()
|
|
{
|
|
return CurveEditor;
|
|
}
|
|
|
|
TSharedPtr<SCascadePreviewViewport> FCascade::GetPreviewViewport()
|
|
{
|
|
return PreviewViewport;
|
|
}
|
|
|
|
bool FCascade::GetIsSoloing() const
|
|
{
|
|
return bIsSoloing;
|
|
}
|
|
|
|
void FCascade::SetIsSoloing(bool bInIsSoloing)
|
|
{
|
|
bIsSoloing = bInIsSoloing;
|
|
}
|
|
|
|
int32 FCascade::GetDetailMode() const
|
|
{
|
|
return DetailMode;
|
|
}
|
|
|
|
EParticleSignificanceLevel FCascade::GetRequiredSignificance() const
|
|
{
|
|
return RequiredSignificance;
|
|
}
|
|
|
|
bool FCascade::GetIsModuleShared(UParticleModule* Module)
|
|
{
|
|
int32 FindCount = 0;
|
|
|
|
UParticleModuleSpawn* SpawnModule = Cast<UParticleModuleSpawn>(Module);
|
|
UParticleModuleRequired* RequiredModule = Cast<UParticleModuleRequired>(Module);
|
|
UParticleModuleTypeDataBase* TypeDataModule = Cast<UParticleModuleTypeDataBase>(Module);
|
|
|
|
int32 CurrLODSetting = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODSetting < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int32 i = 0; i < ParticleSystem->Emitters.Num(); i++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[i];
|
|
UParticleLODLevel* LODLevel = Emitter->GetLODLevel(CurrLODSetting);
|
|
if (LODLevel == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SpawnModule)
|
|
{
|
|
if (SpawnModule == LODLevel->SpawnModule)
|
|
{
|
|
FindCount++;
|
|
if (FindCount >= 2)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (RequiredModule)
|
|
{
|
|
if (RequiredModule == LODLevel->RequiredModule)
|
|
{
|
|
FindCount++;
|
|
if (FindCount >= 2)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (TypeDataModule)
|
|
{
|
|
if (TypeDataModule == LODLevel->TypeDataModule)
|
|
{
|
|
FindCount++;
|
|
if (FindCount >= 2)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 j = 0; j < LODLevel->Modules.Num(); j++)
|
|
{
|
|
if (LODLevel->Modules[j] == Module)
|
|
{
|
|
FindCount++;
|
|
if (FindCount == 2)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FCascade::ToggleEnableOnSelectedEmitter(UParticleEmitter* InEmitter)
|
|
{
|
|
if (!InEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "ToggleEnableOnSelectedEmitter", "Toggle Enable on Emitter");
|
|
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( Transaction ) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make them toggle again in this case as the setting may/maynot be what they think it is...
|
|
ParticleSystem->SetupSoloing();
|
|
return;
|
|
}
|
|
|
|
UParticleLODLevel* LODLevel = GetCurrentlySelectedLODLevel(InEmitter);
|
|
if (LODLevel)
|
|
{
|
|
BeginTransaction( Transaction );
|
|
|
|
ModifyParticleSystem();
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
LODLevel->bEnabled = !LODLevel->bEnabled;
|
|
LODLevel->RequiredModule->bEnabled = LODLevel->bEnabled;
|
|
|
|
ParticleSystem->PostEditChange();
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
OnRestartInLevel();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
EndTransaction( Transaction );
|
|
ParticleSystem->MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
bool FCascade::AddSelectedToGraph(TArray<const FCurveEdEntry*>& OutCurveEntries)
|
|
{
|
|
bool bNewCurve = false;
|
|
if (SelectedEmitter)
|
|
{
|
|
int32 CurrLODSetting = GetCurrentlySelectedLODLevelIndex();
|
|
if (SelectedEmitter->IsLODLevelValid(CurrLODSetting) )
|
|
{
|
|
if (SelectedModule)
|
|
{
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(CurrLODSetting);
|
|
if (LODLevel->IsModuleEditable(SelectedModule))
|
|
{
|
|
bNewCurve = SelectedModule->AddModuleCurvesToEditor(ParticleSystem->CurveEdSetup, OutCurveEntries);
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
}
|
|
|
|
SetSelectedInCurveEditor();
|
|
CurveEditor->RefreshViewport();
|
|
}
|
|
}
|
|
return bNewCurve;
|
|
}
|
|
|
|
void FCascade::ShowDesiredCurvesOnly(const TArray<const FCurveEdEntry*>& InCurveEntries)
|
|
{
|
|
CurveEditor->ClearAllVisibleCurves();
|
|
for( int32 CurveIndex = 0; CurveIndex < InCurveEntries.Num(); CurveIndex++ )
|
|
{
|
|
const FCurveEdEntry* CurveEntry = InCurveEntries[CurveIndex];
|
|
check( CurveEntry );
|
|
CurveEditor->SetCurveVisible( CurveEntry->CurveObject, true );
|
|
}
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
|
|
void FCascade::SetSelectedEmitter(UParticleEmitter* NewSelectedEmitter, bool bIsSimpleAssignment/* = false*/)
|
|
{
|
|
if (!bIsSimpleAssignment)
|
|
{
|
|
SetSelectedModule(NewSelectedEmitter, NULL);
|
|
}
|
|
else
|
|
{
|
|
SelectedEmitter = NewSelectedEmitter;
|
|
}
|
|
}
|
|
|
|
void FCascade::SetSelectedModule(UParticleModule* NewSelectedModule)
|
|
{
|
|
SelectedModule = NewSelectedModule;
|
|
}
|
|
|
|
void FCascade::SetSelectedModule(UParticleEmitter* NewSelectedEmitter, UParticleModule* NewSelectedModule)
|
|
{
|
|
bool bIsNewEmitter = SelectedEmitter != NewSelectedEmitter;
|
|
bool bIsNewModule = SelectedModule != NewSelectedModule;
|
|
|
|
SelectedEmitter = NewSelectedEmitter;
|
|
|
|
int32 CurrLODIndex = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODIndex < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UParticleLODLevel* LODLevel = NULL;
|
|
// Make sure it's the correct LOD level...
|
|
if (SelectedEmitter)
|
|
{
|
|
LODLevel = SelectedEmitter->GetLODLevel(CurrLODIndex);
|
|
if (NewSelectedModule)
|
|
{
|
|
int32 ModuleIndex = INDEX_NONE;
|
|
for (int32 LODLevelCheck = 0; LODLevelCheck < SelectedEmitter->LODLevels.Num(); LODLevelCheck++)
|
|
{
|
|
UParticleLODLevel* CheckLODLevel = SelectedEmitter->LODLevels[LODLevelCheck];
|
|
if (LODLevel)
|
|
{
|
|
// Check the type data...
|
|
if (CheckLODLevel->TypeDataModule &&
|
|
(CheckLODLevel->TypeDataModule == NewSelectedModule))
|
|
{
|
|
ModuleIndex = INDEX_TYPEDATAMODULE;
|
|
}
|
|
|
|
// Check the required module...
|
|
if (ModuleIndex == INDEX_NONE)
|
|
{
|
|
if (CheckLODLevel->RequiredModule == NewSelectedModule)
|
|
{
|
|
ModuleIndex = INDEX_REQUIREDMODULE;
|
|
}
|
|
}
|
|
|
|
// Check the spawn...
|
|
if (ModuleIndex == INDEX_NONE)
|
|
{
|
|
if (CheckLODLevel->SpawnModule == NewSelectedModule)
|
|
{
|
|
ModuleIndex = INDEX_SPAWNMODULE;
|
|
}
|
|
}
|
|
|
|
// Check the rest...
|
|
if (ModuleIndex == INDEX_NONE)
|
|
{
|
|
for (int32 ModuleCheck = 0; ModuleCheck < CheckLODLevel->Modules.Num(); ModuleCheck++)
|
|
{
|
|
if (CheckLODLevel->Modules[ModuleCheck] == NewSelectedModule)
|
|
{
|
|
ModuleIndex = ModuleCheck;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ModuleIndex != INDEX_NONE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (ModuleIndex)
|
|
{
|
|
case INDEX_NONE:
|
|
break;
|
|
case INDEX_TYPEDATAMODULE:
|
|
NewSelectedModule = LODLevel->TypeDataModule;
|
|
break;
|
|
case INDEX_REQUIREDMODULE:
|
|
NewSelectedModule = LODLevel->RequiredModule;
|
|
break;
|
|
case INDEX_SPAWNMODULE:
|
|
NewSelectedModule = LODLevel->SpawnModule;
|
|
break;
|
|
default:
|
|
NewSelectedModule = LODLevel->Modules[ModuleIndex];
|
|
break;
|
|
}
|
|
SelectedModuleIndex = ModuleIndex;
|
|
}
|
|
}
|
|
|
|
SelectedModule = NewSelectedModule;
|
|
|
|
TArray<UObject*> NewSelection;
|
|
bool bReadOnly = false;
|
|
UObject* PropObj = ParticleSystem;
|
|
if (SelectedEmitter)
|
|
{
|
|
if (SelectedModule)
|
|
{
|
|
if (LODLevel != NULL)
|
|
{
|
|
if (bReadOnly == false) //-V547
|
|
{
|
|
if (LODLevel->Level != CurrLODIndex)
|
|
{
|
|
bReadOnly = true;
|
|
}
|
|
else
|
|
{
|
|
bReadOnly = !LODLevel->IsModuleEditable(SelectedModule);
|
|
}
|
|
}
|
|
}
|
|
PropObj = SelectedModule;
|
|
}
|
|
else
|
|
{
|
|
if (bReadOnly == false) //-V547
|
|
{
|
|
// Only allowing editing the SelectedEmitter
|
|
// properties when at the highest LOD level.
|
|
if (!(LODLevel && (LODLevel->Level == 0)))
|
|
{
|
|
bReadOnly = true;
|
|
}
|
|
}
|
|
PropObj = SelectedEmitter;
|
|
}
|
|
|
|
// If soloing and NOT an emitter that is soloing, mark it Read-only!
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (SelectedEmitter->bIsSoloing == false)
|
|
{
|
|
bReadOnly = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
NewSelection.Add(PropObj);
|
|
SetSelection(NewSelection);
|
|
|
|
if (Details.IsValid())
|
|
{
|
|
Details->SetEnabled(!bReadOnly);
|
|
}
|
|
|
|
SetSelectedInCurveEditor();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::SetSelection(TArray<UObject*> SelectedObjects)
|
|
{
|
|
if (Details.IsValid())
|
|
{
|
|
for (int32 Idx = 0; Idx < SelectedObjects.Num(); ++Idx)
|
|
{
|
|
if (!SelectedObjects[Idx])
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
Details->SetObjects(SelectedObjects);
|
|
}
|
|
}
|
|
|
|
TArray<UClass*>& FCascade::GetParticleModuleBaseClasses()
|
|
{
|
|
return ParticleModuleBaseClasses;
|
|
}
|
|
|
|
TArray<UClass*>& FCascade::GetParticleModuleClasses()
|
|
{
|
|
return ParticleModuleClasses;
|
|
}
|
|
|
|
void FCascade::OnCustomModuleOption(int32 Idx)
|
|
{
|
|
if (SelectedModule != NULL)
|
|
{
|
|
// Run it on the selected module
|
|
if (SelectedModule->PerformCustomMenuEntry(Idx) == true)
|
|
{
|
|
UParticleModule* SaveModule = SelectedModule;
|
|
SetSelectedModule(SelectedEmitter, NULL);
|
|
SetSelectedModule(SelectedEmitter, SaveModule);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnNewModule(int32 Idx)
|
|
{
|
|
if (SelectedEmitter == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 CurrLODLevel = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODLevel != 0)
|
|
{
|
|
// Don't allow creating modules if not at highest LOD
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Cascade", "CascadeLODAddError", "Must be on lowest LOD (0) to create modules"));
|
|
return;
|
|
}
|
|
|
|
UClass* NewModClass = ParticleModuleClasses[Idx];
|
|
check(NewModClass->IsChildOf(UParticleModule::StaticClass()));
|
|
|
|
bool bIsEventGenerator = false;
|
|
|
|
if (NewModClass->IsChildOf(UParticleModuleTypeDataBase::StaticClass()))
|
|
{
|
|
// Make sure there isn't already a TypeData module applied!
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(0);
|
|
if (LODLevel->TypeDataModule != 0)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_TypeDataModuleAlreadyPresent", "A TypeData module is already present.\nPlease remove it first."));
|
|
return;
|
|
}
|
|
}
|
|
else if (NewModClass == UParticleModuleEventGenerator::StaticClass())
|
|
{
|
|
bIsEventGenerator = true;
|
|
// Make sure there isn't already an EventGenerator module applied!
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(0);
|
|
if (LODLevel->EventGenerator != NULL)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_EventGeneratorModuleAlreadyPresent", "An EventGenerator module is already present.\nPlease remove it first."));
|
|
return;
|
|
}
|
|
}
|
|
else if (NewModClass == UParticleModuleParameterDynamic::StaticClass())
|
|
{
|
|
// Make sure there isn't already an DynamicParameter module applied!
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(0);
|
|
for (int32 CheckMod = 0; CheckMod < LODLevel->Modules.Num(); CheckMod++)
|
|
{
|
|
UParticleModuleParameterDynamic* DynamicParamMod = Cast<UParticleModuleParameterDynamic>(LODLevel->Modules[CheckMod]);
|
|
if (DynamicParamMod)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_DynamicParameterModuleAlreadyPresent", "A DynamicParameter module is already present.\nPlease remove it first."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CreateNewModule", "Create New Module");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem();
|
|
ModifySelectedObjects();
|
|
|
|
if( NewModClass == UParticleModuleTypeDataMesh::StaticClass() )
|
|
{
|
|
// TypeDataMeshes update the LOD levels RequiredModule material so mark it for transaction
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel( 0 );
|
|
if( LODLevel->RequiredModule )
|
|
{
|
|
LODLevel->RequiredModule->Modify();
|
|
}
|
|
}
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
ParticleSystemComponent->PreEditChange(NULL);
|
|
|
|
// Construct it and add to selected emitter.
|
|
UParticleModule* NewModule = NewObject<UParticleModule>(ParticleSystem, NewModClass, NAME_None, RF_Transactional);
|
|
NewModule->ModuleEditorColor = FColor::MakeRandomColor();
|
|
NewModule->SetToSensibleDefaults(SelectedEmitter);
|
|
NewModule->LODValidity = 1;
|
|
NewModule->SetTransactionFlag();
|
|
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(0);
|
|
if (bIsEventGenerator == true)
|
|
{
|
|
LODLevel->Modules.Insert(NewModule, 0);
|
|
LODLevel->EventGenerator = Cast<UParticleModuleEventGenerator>(NewModule);
|
|
}
|
|
else
|
|
{
|
|
LODLevel->Modules.Add(NewModule);
|
|
}
|
|
|
|
for (int32 LODIndex = 1; LODIndex < SelectedEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
LODLevel = SelectedEmitter->GetLODLevel(LODIndex);
|
|
NewModule->LODValidity |= (1 << LODIndex);
|
|
if (bIsEventGenerator == true)
|
|
{
|
|
LODLevel->Modules.Insert(NewModule, 0);
|
|
LODLevel->EventGenerator = Cast<UParticleModuleEventGenerator>(NewModule);
|
|
}
|
|
else
|
|
{
|
|
LODLevel->Modules.Add(NewModule);
|
|
}
|
|
}
|
|
|
|
SelectedEmitter->UpdateModuleLists();
|
|
|
|
ParticleSystemComponent->PostEditChange();
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.NewModule"), TEXT("Class"), NewModClass->GetName());
|
|
}
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
|
|
// Refresh viewport
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnNewEmitter()
|
|
{
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "NewEmitter", "Create New Emitter");
|
|
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( Transaction ) == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
BeginTransaction( Transaction );
|
|
ParticleSystem->PreEditChange(NULL);
|
|
ParticleSystemComponent->PreEditChange(NULL);
|
|
|
|
UClass* NewEmitClass = UParticleSpriteEmitter::StaticClass();
|
|
|
|
// Construct it
|
|
UParticleEmitter* NewEmitter = NewObject<UParticleEmitter>(ParticleSystem, NewEmitClass, NAME_None, RF_Transactional);
|
|
UParticleLODLevel* LODLevel = NewEmitter->GetLODLevel(0);
|
|
if (LODLevel == NULL)
|
|
{
|
|
// Generate the HighLOD level, and the default lowest level
|
|
int32 Index = NewEmitter->CreateLODLevel(0);
|
|
LODLevel = NewEmitter->GetLODLevel(0);
|
|
}
|
|
|
|
check(LODLevel);
|
|
|
|
NewEmitter->EmitterEditorColor = FColor::MakeRandomColor();
|
|
NewEmitter->EmitterEditorColor.A = 255;
|
|
|
|
// Set to sensible default values
|
|
NewEmitter->SetToSensibleDefaults();
|
|
|
|
// Handle special cases...
|
|
if (NewEmitClass == UParticleSpriteEmitter::StaticClass())
|
|
{
|
|
// For handyness- use currently selected material for new emitter (or default if none selected)
|
|
UParticleSpriteEmitter* NewSpriteEmitter = (UParticleSpriteEmitter*)NewEmitter;
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
UMaterialInterface* CurrentMaterial = GEditor->GetSelectedObjects()->GetTop<UMaterialInterface>();
|
|
if (CurrentMaterial)
|
|
{
|
|
LODLevel->RequiredModule->Material = CurrentMaterial;
|
|
}
|
|
else
|
|
{
|
|
LODLevel->RequiredModule->Material = LoadObject<UMaterialInterface>(NULL, TEXT("/Engine/EngineMaterials/DefaultParticle.DefaultParticle"), NULL, LOAD_None, NULL);
|
|
}
|
|
}
|
|
|
|
// Generate all the levels that are present in other emitters...
|
|
if (ParticleSystem->Emitters.Num() > 0)
|
|
{
|
|
UParticleEmitter* ExistingEmitter = ParticleSystem->Emitters[0];
|
|
|
|
if (ExistingEmitter->LODLevels.Num() > 1)
|
|
{
|
|
if (NewEmitter->AutogenerateLowestLODLevel(ParticleSystem->bRegenerateLODDuplicate) == false)
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Failed to autogenerate lowest LOD level!"));
|
|
}
|
|
}
|
|
|
|
if (ExistingEmitter->LODLevels.Num() > 2)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Generating existing LOD levels..."));
|
|
|
|
// Walk the LOD levels of the existing emitter...
|
|
UParticleLODLevel* ExistingLOD;
|
|
UParticleLODLevel* NewLOD_Prev = NewEmitter->LODLevels[0];
|
|
UParticleLODLevel* NewLOD_Next = NewEmitter->LODLevels[1];
|
|
|
|
check(NewLOD_Prev);
|
|
check(NewLOD_Next);
|
|
|
|
for (int32 LODIndex = 1; LODIndex < ExistingEmitter->LODLevels.Num() - 1; LODIndex++)
|
|
{
|
|
ExistingLOD = ExistingEmitter->LODLevels[LODIndex];
|
|
|
|
// Add this one
|
|
int32 ResultIndex = NewEmitter->CreateLODLevel(ExistingLOD->Level, true);
|
|
|
|
UParticleLODLevel* NewLODLevel = NewEmitter->LODLevels[ResultIndex];
|
|
check(NewLODLevel);
|
|
NewLODLevel->UpdateModuleLists();
|
|
}
|
|
}
|
|
}
|
|
|
|
NewEmitter->UpdateModuleLists();
|
|
|
|
NewEmitter->PostEditChange();
|
|
|
|
NewEmitter->SetFlags(RF_Transactional);
|
|
for (int32 LODIndex = 0; LODIndex < NewEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* NewEmitterLODLevel = NewEmitter->GetLODLevel(LODIndex);
|
|
if (NewEmitterLODLevel)
|
|
{
|
|
NewEmitterLODLevel->SetFlags(RF_Transactional);
|
|
check(NewEmitterLODLevel->RequiredModule);
|
|
NewEmitterLODLevel->RequiredModule->SetTransactionFlag();
|
|
check(NewEmitterLODLevel->SpawnModule);
|
|
NewEmitterLODLevel->SpawnModule->SetTransactionFlag();
|
|
for (int32 jj = 0; jj < NewEmitterLODLevel->Modules.Num(); jj++)
|
|
{
|
|
UParticleModule* pkModule = NewEmitterLODLevel->Modules[jj];
|
|
pkModule->SetTransactionFlag();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to selected emitter
|
|
ParticleSystem->Emitters.Add(NewEmitter);
|
|
|
|
// Setup the LOD distances
|
|
if (ParticleSystem->LODDistances.Num() == 0)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter)
|
|
{
|
|
ParticleSystem->LODDistances.AddUninitialized(Emitter->LODLevels.Num());
|
|
for (int32 LODIndex = 0; LODIndex < ParticleSystem->LODDistances.Num(); LODIndex++)
|
|
{
|
|
ParticleSystem->LODDistances[LODIndex] = LODIndex * 2500.0f;
|
|
}
|
|
}
|
|
}
|
|
if (ParticleSystem->LODSettings.Num() == 0)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter)
|
|
{
|
|
ParticleSystem->LODSettings.AddUninitialized(Emitter->LODLevels.Num());
|
|
for (int32 LODIndex = 0; LODIndex < ParticleSystem->LODSettings.Num(); LODIndex++)
|
|
{
|
|
ParticleSystem->LODSettings[LODIndex] = FParticleSystemLOD::CreateParticleSystemLOD();
|
|
}
|
|
}
|
|
}
|
|
|
|
ParticleSystemComponent->PostEditChange();
|
|
ParticleSystem->PostEditChange();
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.NewEmitter"));
|
|
}
|
|
|
|
// Refresh viewport
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::SetCopyEmitter(UParticleEmitter* NewEmitter)
|
|
{
|
|
CopyEmitter = NewEmitter;
|
|
}
|
|
|
|
void FCascade::SetCopyModule(UParticleEmitter* NewEmitter, UParticleModule* NewModule)
|
|
{
|
|
CopyEmitter = NewEmitter;
|
|
CopyModule = NewModule;
|
|
}
|
|
|
|
TArray<UParticleModule*>& FCascade::GetDraggedModuleList()
|
|
{
|
|
return DraggedModuleList;
|
|
}
|
|
|
|
bool FCascade::InsertModule(UParticleModule* Module, UParticleEmitter* TargetEmitter, int32 TargetIndex, bool bSetSelected)
|
|
{
|
|
if (!Module || !TargetEmitter || TargetIndex == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 CurrLODIndex = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODIndex != 0)
|
|
{
|
|
// Don't allow moving modules if not at highest LOD
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Cascade", "CascadeLODMoveError", "Must be on lowest LOD (0) to move modules"));
|
|
return false;
|
|
}
|
|
|
|
// Cannot insert the same module more than once into the same emitter.
|
|
UParticleLODLevel* LODLevel = TargetEmitter->GetLODLevel(0);
|
|
for(int32 i = 0; i < LODLevel->Modules.Num(); i++)
|
|
{
|
|
if (LODLevel->Modules[i] == Module)
|
|
{
|
|
FNotificationInfo Info( NSLOCTEXT("UnrealEd", "Error_ModuleCanOnlyBeUsedInEmitterOnce", "A Module can only be used in each Emitter once.") );
|
|
Info.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (Module->IsA(UParticleModuleParameterDynamic::StaticClass()))
|
|
{
|
|
// Make sure there isn't already an DynamicParameter module applied!
|
|
for (int32 CheckMod = 0; CheckMod < LODLevel->Modules.Num(); CheckMod++)
|
|
{
|
|
UParticleModuleParameterDynamic* DynamicParamMod = Cast<UParticleModuleParameterDynamic>(LODLevel->Modules[CheckMod]);
|
|
if (DynamicParamMod)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_DynamicParameterModuleAlreadyPresent", "A DynamicParameter module is already present.\nPlease remove it first."));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the Spawn or Required modules are being 're-inserted', do nothing!
|
|
if ((LODLevel->SpawnModule == Module) ||
|
|
(LODLevel->RequiredModule == Module))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "InsertModule", "Insert Module");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyEmitter(TargetEmitter);
|
|
ModifyParticleSystem();
|
|
|
|
// Insert in desired location in new Emitter
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
TArray<UParticleModule*>& DraggedModules = EmitterCanvas->GetViewportClient()->GetDraggedModules();
|
|
|
|
if (Module->IsA(UParticleModuleTypeDataBase::StaticClass()))
|
|
{
|
|
bool bInsert = true;
|
|
if (LODLevel->TypeDataModule != NULL)
|
|
{
|
|
// Prompt to ensure they want to replace the TDModule
|
|
bInsert = EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, NSLOCTEXT("UnrealEd", "Cascade_ReplaceTypeDataModule", "Replace the existing type data module?"));
|
|
}
|
|
|
|
if (bInsert == true)
|
|
{
|
|
LODLevel->TypeDataModule = CastChecked<UParticleModuleTypeDataBase>(Module);
|
|
|
|
if (DraggedModules.Num() > 0)
|
|
{
|
|
// Swap the modules in all the LOD levels
|
|
for (int32 LODIndex = 1; LODIndex < TargetEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* TargetEmitterLODLevel = TargetEmitter->GetLODLevel(LODIndex);
|
|
UParticleModule* DraggedModule = DraggedModules[LODIndex];
|
|
|
|
TargetEmitterLODLevel->TypeDataModule = CastChecked<UParticleModuleTypeDataBase>(DraggedModule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (Module->IsA(UParticleModuleSpawn::StaticClass()))
|
|
{
|
|
// There can be only one...
|
|
LODLevel->SpawnModule = CastChecked<UParticleModuleSpawn>(Module);
|
|
if (DraggedModules.Num() > 0)
|
|
{
|
|
// Swap the modules in all the LOD levels
|
|
for (int32 LODIndex = 1; LODIndex < TargetEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* TargetEmitterLODLevel = TargetEmitter->GetLODLevel(LODIndex);
|
|
UParticleModuleSpawn* DraggedModule = CastChecked<UParticleModuleSpawn>(DraggedModules[LODIndex]);
|
|
TargetEmitterLODLevel->SpawnModule = DraggedModule;
|
|
}
|
|
}
|
|
}
|
|
else if (Module->IsA(UParticleModuleRequired::StaticClass()))
|
|
{
|
|
// There can be only one...
|
|
LODLevel->RequiredModule = CastChecked<UParticleModuleRequired>(Module);
|
|
if (DraggedModules.Num() > 0)
|
|
{
|
|
// Swap the modules in all the LOD levels
|
|
for (int32 LODIndex = 1; LODIndex < TargetEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* TargetEmitterLODLevel = TargetEmitter->GetLODLevel(LODIndex);
|
|
UParticleModuleRequired* DraggedModule = CastChecked<UParticleModuleRequired>(DraggedModules[LODIndex]);
|
|
TargetEmitterLODLevel->RequiredModule = DraggedModule;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 NewModuleIndex = FMath::Clamp<int32>(TargetIndex, 0, LODLevel->Modules.Num());
|
|
LODLevel->Modules.InsertUninitialized(NewModuleIndex);
|
|
LODLevel->Modules[NewModuleIndex] = Module;
|
|
|
|
if (DraggedModules.Num() > 0)
|
|
{
|
|
// Swap the modules in all the LOD levels
|
|
for (int32 LODIndex = 1; LODIndex < TargetEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* TargetEmitterLODLevel = TargetEmitter->GetLODLevel(LODIndex);
|
|
UParticleModule* DraggedModule = DraggedModules[LODIndex];
|
|
|
|
TargetEmitterLODLevel->Modules.InsertUninitialized(NewModuleIndex);
|
|
TargetEmitterLODLevel->Modules[NewModuleIndex] = DraggedModule;
|
|
}
|
|
}
|
|
}
|
|
|
|
DraggedModules.Empty();
|
|
|
|
TargetEmitter->UpdateModuleLists();
|
|
|
|
ParticleSystem->PostEditChange();
|
|
|
|
// Update selection
|
|
if (bSetSelected)
|
|
{
|
|
SetSelectedModule(TargetEmitter, Module);
|
|
}
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FCascade::CopyModuleToEmitter(UParticleModule* pkSourceModule, UParticleEmitter* pkTargetEmitter, UParticleSystem* pkTargetSystem, int32 TargetIndex)
|
|
{
|
|
check(pkSourceModule);
|
|
check(pkTargetEmitter);
|
|
check(pkTargetSystem);
|
|
|
|
int32 CurrLODIndex = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODIndex != 0)
|
|
{
|
|
// Don't allow copying modules if not at highest LOD
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Cascade", "CascadeLODCopyError", "Must be on lowest LOD (0) to copy modules"));
|
|
return;
|
|
}
|
|
|
|
UObject* DupObject = StaticDuplicateObject(pkSourceModule, pkTargetSystem);
|
|
if (DupObject)
|
|
{
|
|
UParticleModule* Module = Cast<UParticleModule>(DupObject);
|
|
Module->ModuleEditorColor = FColor::MakeRandomColor();
|
|
|
|
UParticleLODLevel* LODLevel;
|
|
UParticleModule* DraggedModule = EmitterCanvas->GetViewportClient()->GetDraggedModule();
|
|
TArray<UParticleModule*>& DraggedModules = EmitterCanvas->GetViewportClient()->GetDraggedModules();
|
|
|
|
if (DraggedModule == pkSourceModule)
|
|
{
|
|
DraggedModules[0] = Module;
|
|
// If we are dragging, we need to copy all the LOD modules
|
|
for (int32 LODIndex = 1; LODIndex < pkTargetEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
LODLevel = pkTargetEmitter->GetLODLevel(LODIndex);
|
|
|
|
UParticleModule* CopySource = DraggedModules[LODIndex];
|
|
if (CopySource)
|
|
{
|
|
DupObject = StaticDuplicateObject(CopySource, pkTargetSystem);
|
|
if (DupObject)
|
|
{
|
|
UParticleModule* NewModule = Cast<UParticleModule>(DupObject);
|
|
NewModule->ModuleEditorColor = Module->ModuleEditorColor;
|
|
DraggedModules[LODIndex] = NewModule;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Missing dragged module!"));
|
|
}
|
|
}
|
|
}
|
|
|
|
LODLevel = pkTargetEmitter->GetLODLevel(0);
|
|
InsertModule(Module, pkTargetEmitter, (TargetIndex != INDEX_NONE) ? TargetIndex : LODLevel->Modules.Num(), false);
|
|
}
|
|
}
|
|
|
|
FName FCascade::GetToolkitFName() const
|
|
{
|
|
return FName("Cascade");
|
|
}
|
|
|
|
FText FCascade::GetBaseToolkitName() const
|
|
{
|
|
return NSLOCTEXT("Cascade", "AppLabel", "Cascade" );
|
|
}
|
|
|
|
FString FCascade::GetWorldCentricTabPrefix() const
|
|
{
|
|
return NSLOCTEXT("Cascade", "WorldCentricTabPrefix", "Cascade ").ToString();
|
|
}
|
|
|
|
FLinearColor FCascade::GetWorldCentricTabColorScale() const
|
|
{
|
|
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
|
|
}
|
|
|
|
TSharedRef<SDockTab> FCascade::SpawnTab(const FSpawnTabArgs& SpawnTabArgs, FName TabIdentifier)
|
|
{
|
|
if (TabIdentifier == FName(TEXT("Cascade_PreviewViewport")))
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label( NSLOCTEXT("Cascade", "CascadeViewportTitle", "Viewport"))
|
|
[
|
|
PreviewViewport.ToSharedRef()
|
|
];
|
|
|
|
PreviewViewport->ParentTab = SpawnedTab;
|
|
|
|
// Set emitter to use the particle system we are editing.
|
|
ParticleSystemComponent->SetTemplate(ParticleSystem);
|
|
|
|
ParticleSystemComponent->InitializeSystem();
|
|
ParticleSystemComponent->ActivateSystem();
|
|
|
|
// Set camera position/orientation
|
|
PreviewViewport->GetViewportClient()->SetPreviewCamera(ParticleSystem->ThumbnailAngle, ParticleSystem->ThumbnailDistance);
|
|
PreviewViewport->GetViewportClient()->SetViewLocationForOrbiting(FVector::ZeroVector);
|
|
|
|
return SpawnedTab;
|
|
}
|
|
else if (TabIdentifier == FName(TEXT("Cascade_EmitterCanvas")))
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(NSLOCTEXT("Cascade", "CascadeGraphCanvasTitle", "Emitters"))
|
|
[
|
|
EmitterCanvas.ToSharedRef()
|
|
];
|
|
|
|
EmitterCanvas->ParentTab = SpawnedTab;
|
|
|
|
return SpawnedTab;
|
|
}
|
|
else if(TabIdentifier == FName(TEXT("Cascade_Properties")))
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(NSLOCTEXT("Cascade", "CascadePropertiesTitle", "Details"))
|
|
[
|
|
Details.ToSharedRef()
|
|
];
|
|
|
|
return SpawnedTab;
|
|
}
|
|
else if(TabIdentifier == FName(TEXT("Cascade_CurveEditor")))
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(NSLOCTEXT("Cascade", "CascadeCurveEditorTitle", "Curve Editor"))
|
|
[
|
|
CurveEditor.ToSharedRef()
|
|
];
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
return SNew(SDockTab);
|
|
}
|
|
}
|
|
|
|
void FCascade::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->GetPreviewScene().AddReferencedObjects(Collector);
|
|
}
|
|
|
|
Collector.AddReferencedObject(ParticleSystem);
|
|
Collector.AddReferencedObject(ParticleSystemComponent);
|
|
Collector.AddReferencedObject(LocalVectorFieldPreviewComponent);
|
|
Collector.AddReferencedObject(EditorOptions);
|
|
Collector.AddReferencedObject(EditorConfig);
|
|
Collector.AddReferencedObject(SelectedModule);
|
|
Collector.AddReferencedObject(SelectedEmitter);
|
|
Collector.AddReferencedObject(CopyModule);
|
|
Collector.AddReferencedObject(CopyEmitter);
|
|
Collector.AddReferencedObject(CurveToReplace);
|
|
|
|
for (auto& Pair : EmitterToThumbnailMap)
|
|
{
|
|
Collector.AddReferencedObject(Pair.Value.Texture);
|
|
}
|
|
}
|
|
|
|
FString FCascade::GetReferencerName() const
|
|
{
|
|
return TEXT("FCascade");
|
|
}
|
|
|
|
void FCascade::Tick(float DeltaTime)
|
|
{
|
|
// This is a bit of a hack. In order to not tick all open Cascade editors (which tick through engine tick) even when not visible,
|
|
// the preview viewport keeps track of whether it has been ticked in the last frame. Slate widgets aren't ticked if invisible, so
|
|
// this will tell us if we should run simulation in this instance. If it hasn't ticked, we skip ticking Cascade as well and clear
|
|
// the flag for the next frame
|
|
if ( !PreviewViewport->HasJustTicked() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
PreviewViewport->ClearTickFlag();
|
|
|
|
|
|
|
|
static const double ResetInterval = 0.5f;
|
|
|
|
// Clamp delta time.
|
|
DeltaTime = FMath::Min<float>(DeltaTime, 0.040f);
|
|
|
|
int32 DetailModeCVar = GetCachedScalabilityCVars().DetailMode;
|
|
if (GlobalDetailMode != DetailModeCVar)
|
|
{
|
|
GlobalDetailMode = DetailModeCVar;
|
|
OnDetailMode((EDetailMode)GlobalDetailMode);
|
|
}
|
|
|
|
static float LastMemUpdateTime = 0.0f;
|
|
bool bCurrentlySoloing = false;
|
|
if (ParticleSystem)
|
|
{
|
|
for (int32 EmitterIdx = 0; EmitterIdx < ParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIdx];
|
|
if (Emitter && (Emitter->bIsSoloing == true))
|
|
{
|
|
bCurrentlySoloing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
LastMemUpdateTime += DeltaTime;
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid() &&
|
|
LastMemUpdateTime > ParticleMemoryUpdateTime)
|
|
{
|
|
PreviewViewport->GetViewportClient()->UpdateMemoryInformation();
|
|
LastMemUpdateTime = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Don't bother ticking at all if paused.
|
|
if (TimeScale > KINDA_SMALL_NUMBER)
|
|
{
|
|
check(ParticleSystem != NULL);
|
|
const float fSaveUpdateDelta = ParticleSystem->UpdateTime_Delta;
|
|
if (TimeScale < 1.0f)
|
|
{
|
|
ParticleSystem->UpdateTime_Delta *= TimeScale;
|
|
}
|
|
|
|
float CurrDeltaTime = TimeScale * DeltaTime;
|
|
|
|
if (bIsToggleMotion == true)
|
|
{
|
|
AccumulatedMotionTime += CurrDeltaTime;
|
|
FVector Position;
|
|
Position.X = MotionModeRadius * FMath::Sin(AccumulatedMotionTime);
|
|
Position.Y = MotionModeRadius * FMath::Cos(AccumulatedMotionTime);
|
|
Position.Z = 0.0f;
|
|
ParticleSystemComponent->SetComponentToWorld(FTransform(Position));
|
|
}
|
|
|
|
if (ParticleSystemComponent->IsComponentTickEnabled())
|
|
{
|
|
ParticleSystemComponent->CascadeTickComponent(CurrDeltaTime, LEVELTICK_All);
|
|
}
|
|
ParticleSystemComponent->DoDeferredRenderUpdates_Concurrent();
|
|
UWorld* World = PreviewViewport->GetViewportClient()->GetPreviewScene().GetWorld();
|
|
GetFXSystem()->Tick(World, CurrDeltaTime);
|
|
TotalTime += CurrDeltaTime;
|
|
ParticleSystem->UpdateTime_Delta = fSaveUpdateDelta;
|
|
|
|
// Tick the physics scene
|
|
FPhysScene* PhysScene = World->GetPhysicsScene();
|
|
AWorldSettings * WorldSettings = World->GetWorldSettings();
|
|
check(WorldSettings);
|
|
//@todo phys_thread do we need this?
|
|
World->SetupPhysicsTickFunctions(DeltaTime);
|
|
PhysScene->StartFrame();
|
|
PhysScene->WaitPhysScenes();
|
|
PhysScene->EndFrame();
|
|
}
|
|
|
|
// If a vector field module is selected, update the preview visualization.
|
|
if (SelectedModule && SelectedModule->IsA(UParticleModuleVectorFieldLocal::StaticClass()))
|
|
{
|
|
UParticleModuleVectorFieldLocal* VectorFieldModule = (UParticleModuleVectorFieldLocal*)SelectedModule;
|
|
LocalVectorFieldPreviewComponent->VectorField = VectorFieldModule->VectorField;
|
|
LocalVectorFieldPreviewComponent->SetRelativeLocation_Direct(VectorFieldModule->RelativeTranslation);
|
|
LocalVectorFieldPreviewComponent->SetRelativeRotation_Direct(VectorFieldModule->RelativeRotation);
|
|
LocalVectorFieldPreviewComponent->SetRelativeScale3D_Direct(VectorFieldModule->RelativeScale3D);
|
|
LocalVectorFieldPreviewComponent->Intensity = VectorFieldModule->Intensity;
|
|
LocalVectorFieldPreviewComponent->SetVisibleFlag(true);
|
|
LocalVectorFieldPreviewComponent->bHiddenInGame = false;
|
|
LocalVectorFieldPreviewComponent->ReregisterComponent();
|
|
}
|
|
else if(LocalVectorFieldPreviewComponent->GetVisibleFlag())
|
|
{
|
|
LocalVectorFieldPreviewComponent->SetVisibleFlag(false);
|
|
LocalVectorFieldPreviewComponent->ReregisterComponent();
|
|
}
|
|
|
|
// If we are doing auto-reset
|
|
if(bIsToggleLoopSystem)
|
|
{
|
|
UParticleSystemComponent* PartComp = ParticleSystemComponent;
|
|
|
|
// If system has finish, pause for a bit before resetting.
|
|
if(bIsPendingReset)
|
|
{
|
|
if(TotalTime > ResetTime)
|
|
{
|
|
PartComp->ResetParticles();
|
|
PartComp->ActivateSystem();
|
|
|
|
bIsPendingReset = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(PartComp->HasCompleted())
|
|
{
|
|
bIsPendingReset = true;
|
|
ResetTime = TotalTime + ResetInterval;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bCurrentlySoloing != bIsSoloing)
|
|
{
|
|
bIsSoloing = bCurrentlySoloing;
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
}
|
|
|
|
TStatId FCascade::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FCascade, STATGROUP_Tickables);
|
|
}
|
|
|
|
void FCascade::PostUndo(bool bSuccess)
|
|
{
|
|
ForceUpdate();
|
|
}
|
|
|
|
TSharedRef<SWidget> FCascade::GenerateAnimSpeedMenuContent(TSharedRef<FUICommandList> InCommandList)
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, InCommandList);
|
|
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().AnimSpeed_100);
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().AnimSpeed_50);
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().AnimSpeed_25);
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().AnimSpeed_10);
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().AnimSpeed_1);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FCascade::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged)
|
|
{
|
|
FPropertyChangedEvent Event = PropertyChangedEvent;
|
|
if (SelectedModule)
|
|
{
|
|
SelectedModule->PostEditChangeProperty(Event);
|
|
}
|
|
else if (SelectedEmitter)
|
|
{
|
|
SelectedEmitter->PostEditChangeProperty(Event);
|
|
}
|
|
else if (ParticleSystem)
|
|
{
|
|
ParticleSystem->PostEditChangeProperty(Event);
|
|
}
|
|
|
|
OnRestartInLevel();
|
|
}
|
|
|
|
void FCascade::NotifyPreChange(FEditPropertyChain* PropertyChain)
|
|
{
|
|
//Needs to stay in-sync with the text in NotifyPostChange
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadePropertyChange", "Change Property");
|
|
|
|
BeginTransaction(Transaction);
|
|
ModifyParticleSystem();
|
|
|
|
CurveToReplace = NULL;
|
|
|
|
// get the property that is being edited
|
|
FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(PropertyChain->GetActiveNode()->GetValue());
|
|
if (ObjProp &&
|
|
(ObjProp->PropertyClass->IsChildOf(UDistributionFloat::StaticClass()) ||
|
|
ObjProp->PropertyClass->IsChildOf(UDistributionVector::StaticClass()))
|
|
)
|
|
{
|
|
UParticleModuleParameterDynamic* DynParamModule = Cast<UParticleModuleParameterDynamic>(SelectedModule);
|
|
if (DynParamModule)
|
|
{
|
|
// Grab the curves...
|
|
DynParamModule->GetCurveObjects(DynParamCurves);
|
|
}
|
|
else
|
|
{
|
|
UObject* EditedObject = NULL;
|
|
if (SelectedModule)
|
|
{
|
|
EditedObject = SelectedModule;
|
|
}
|
|
else
|
|
{
|
|
EditedObject = SelectedEmitter;
|
|
}
|
|
|
|
// calculate offset from object to property being edited
|
|
void* BaseObject = EditedObject;
|
|
for (FEditPropertyChain::TIterator It(PropertyChain->GetHead()); It; ++It)
|
|
{
|
|
// don't go past the active property
|
|
if (*It == ObjProp)
|
|
{
|
|
break;
|
|
}
|
|
|
|
BaseObject = It->ContainerPtrToValuePtr<void>(BaseObject);
|
|
|
|
// If it is an object property, then reset our base pointer/offset
|
|
FObjectPropertyBase* ObjectPropertyBase = CastField<FObjectPropertyBase>(*It);
|
|
if (ObjectPropertyBase)
|
|
{
|
|
BaseObject = ObjectPropertyBase->GetObjectPropertyValue(BaseObject);
|
|
}
|
|
}
|
|
|
|
UObject* ObjPtr = ObjProp->GetObjectPropertyValue(ObjProp->ContainerPtrToValuePtr<void>(BaseObject));
|
|
CurveToReplace = ObjPtr;
|
|
}
|
|
}
|
|
|
|
if (SelectedModule)
|
|
{
|
|
if (PropertyChain->GetActiveNode()->GetValue()->GetName() == TEXT("InterpolationMethod"))
|
|
{
|
|
UParticleModuleRequired* ReqMod = Cast<UParticleModuleRequired>(SelectedModule);
|
|
if (ReqMod)
|
|
{
|
|
PreviousInterpolationMethod = (EParticleSubUVInterpMethod)(ReqMod->InterpolationMethod);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void FCascade::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FEditPropertyChain* PropertyChain)
|
|
{
|
|
UParticleModuleParameterDynamic* DynParamModule = Cast<UParticleModuleParameterDynamic>(SelectedModule);
|
|
if (DynParamModule)
|
|
{
|
|
if (DynParamCurves.Num() > 0)
|
|
{
|
|
// Grab the curves...
|
|
TArray<FParticleCurvePair> DPCurves;
|
|
DynParamModule->GetCurveObjects(DPCurves);
|
|
|
|
check(DPCurves.Num() == DynParamCurves.Num());
|
|
for (int32 CurveIndex = 0; CurveIndex < DynParamCurves.Num(); CurveIndex++)
|
|
{
|
|
UObject* OldCurve = DynParamCurves[CurveIndex].CurveObject;
|
|
UObject* NewCurve = DPCurves[CurveIndex].CurveObject;
|
|
if (OldCurve != NewCurve)
|
|
{
|
|
ParticleSystem->CurveEdSetup->ReplaceCurve(OldCurve, NewCurve);
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
}
|
|
DynParamCurves.Empty();
|
|
}
|
|
}
|
|
|
|
if (CurveToReplace)
|
|
{
|
|
// This should be the same property we just got in NotifyPreChange!
|
|
FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(PropertyChain->GetActiveNode()->GetValue());
|
|
check(ObjProp);
|
|
check(ObjProp->PropertyClass->IsChildOf(UDistributionFloat::StaticClass()) || ObjProp->PropertyClass->IsChildOf(UDistributionVector::StaticClass()));
|
|
|
|
UObject* EditedObject = NULL;
|
|
if (SelectedModule)
|
|
{
|
|
EditedObject = SelectedModule;
|
|
}
|
|
else
|
|
{
|
|
EditedObject = SelectedEmitter;
|
|
}
|
|
|
|
// calculate offset from object to property being edited
|
|
void* BaseObject = EditedObject;
|
|
for (FEditPropertyChain::TIterator It(PropertyChain->GetHead()); It; ++It)
|
|
{
|
|
// don't go past the active property
|
|
if (*It == ObjProp)
|
|
{
|
|
break;
|
|
}
|
|
|
|
BaseObject = It->ContainerPtrToValuePtr<void>(BaseObject);
|
|
|
|
// If it is an object property, then reset our base pointer/offset
|
|
FObjectPropertyBase* ObjectPropertyBase = CastField<FObjectPropertyBase>(*It);
|
|
if (ObjectPropertyBase)
|
|
{
|
|
BaseObject = ObjectPropertyBase->GetObjectPropertyValue(BaseObject);
|
|
}
|
|
}
|
|
|
|
UObject* NewCurve = ObjProp->GetObjectPropertyValue(ObjProp->ContainerPtrToValuePtr<void>(BaseObject));
|
|
|
|
if (NewCurve)
|
|
{
|
|
ParticleSystem->CurveEdSetup->ReplaceCurve(CurveToReplace, NewCurve);
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
}
|
|
|
|
if (SelectedModule || SelectedEmitter)
|
|
{
|
|
if (PropertyChain->GetActiveNode()->GetValue()->GetName() == TEXT("InterpolationMethod"))
|
|
{
|
|
UParticleModuleRequired* ReqMod = Cast<UParticleModuleRequired>(SelectedModule);
|
|
if (ReqMod && SelectedEmitter)
|
|
{
|
|
if (ReqMod->InterpolationMethod != PreviousInterpolationMethod)
|
|
{
|
|
int32 CurrentLODLevel = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrentLODLevel == 0)
|
|
{
|
|
// The main on is being changed...
|
|
// Check all other LOD levels...
|
|
for (int32 LODIndex = 1; LODIndex < SelectedEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* CheckLOD = SelectedEmitter->LODLevels[LODIndex];
|
|
if (CheckLOD)
|
|
{
|
|
UParticleModuleRequired* CheckReq = CheckLOD->RequiredModule;
|
|
if (CheckReq)
|
|
{
|
|
if (ReqMod->InterpolationMethod == PSUVIM_None)
|
|
{
|
|
CheckReq->InterpolationMethod = PSUVIM_None;
|
|
}
|
|
else
|
|
{
|
|
if (CheckReq->InterpolationMethod == PSUVIM_None)
|
|
{
|
|
CheckReq->InterpolationMethod = ReqMod->InterpolationMethod;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The main on is being changed...
|
|
// Check all other LOD levels...
|
|
UParticleLODLevel* CheckLOD = SelectedEmitter->LODLevels[0];
|
|
if (CheckLOD)
|
|
{
|
|
bool bWarn = false;
|
|
UParticleModuleRequired* CheckReq = CheckLOD->RequiredModule;
|
|
if (CheckReq)
|
|
{
|
|
if (ReqMod->InterpolationMethod == PSUVIM_None)
|
|
{
|
|
if (CheckReq->InterpolationMethod != PSUVIM_None)
|
|
{
|
|
ReqMod->InterpolationMethod = PreviousInterpolationMethod;
|
|
bWarn = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CheckReq->InterpolationMethod == PSUVIM_None)
|
|
{
|
|
ReqMod->InterpolationMethod = PreviousInterpolationMethod;
|
|
bWarn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bWarn == true)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Cascade_InterpolationMethodLODWarning", "Unable to change InterpolationMethod due to LOD level 0 setting."));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyEvent(PropertyChain->GetActiveNode()->GetValue());
|
|
ParticleSystem->PostEditChangeProperty(PropertyEvent);
|
|
|
|
if (SelectedModule)
|
|
{
|
|
if (SelectedModule->IsDisplayedInCurveEd(CurveEditor->GetEdSetup()))
|
|
{
|
|
TArray<FParticleCurvePair> Curves;
|
|
SelectedModule->GetCurveObjects(Curves);
|
|
|
|
for (int32 i=0; i<Curves.Num(); i++)
|
|
{
|
|
CurveEditor->GetEdSetup()->ChangeCurveColor(Curves[i].CurveObject, SelectedModule->ModuleEditorColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ParticleSystem->ThumbnailImageOutOfDate = true;
|
|
|
|
//Needs to stay in-sync with the text in NotifyPreChange
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadePropertyChange", "Change Property");
|
|
|
|
check(bTransactionInProgress);
|
|
EndTransaction( Transaction );
|
|
|
|
CurveEditor->CurveChanged();
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
OnRestartInLevel();
|
|
}
|
|
|
|
|
|
void FCascade::PreEditCurve(TArray<UObject*> CurvesAboutToChange)
|
|
{
|
|
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse);
|
|
|
|
//Need to keep text in-sync with PostEditCurve
|
|
BeginTransaction( NSLOCTEXT("UnrealEd", "EditCurve", "Edit Curve") );
|
|
ModifyParticleSystem();
|
|
ModifySelectedObjects();
|
|
|
|
// Call Modify on all tracks with keys selected
|
|
for (int32 i = 0; i < CurvesAboutToChange.Num(); i++)
|
|
{
|
|
// If this keypoint is from a distribution, call Modify on it to back up its state.
|
|
UDistributionFloat* DistFloat = Cast<UDistributionFloat>(CurvesAboutToChange[i]);
|
|
if (DistFloat)
|
|
{
|
|
DistFloat->SetFlags(RF_Transactional);
|
|
DistFloat->Modify();
|
|
}
|
|
UDistributionVector* DistVector = Cast<UDistributionVector>(CurvesAboutToChange[i]);
|
|
if (DistVector)
|
|
{
|
|
DistVector->SetFlags(RF_Transactional);
|
|
DistVector->Modify();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::PostEditCurve()
|
|
{
|
|
ParticleSystem->BuildEmitters();
|
|
|
|
//Need to keep text in-sync with PreEditCurve
|
|
EndTransaction( NSLOCTEXT("UnrealEd", "EditCurve", "Edit Curve") );
|
|
}
|
|
|
|
void FCascade::MovedKey()
|
|
{
|
|
}
|
|
|
|
void FCascade::DesireUndo()
|
|
{
|
|
OnUndo();
|
|
}
|
|
|
|
void FCascade::DesireRedo()
|
|
{
|
|
OnRedo();
|
|
}
|
|
|
|
void FCascade::CreateInternalWidgets()
|
|
{
|
|
PreviewViewport =
|
|
SNew(SCascadePreviewViewport)
|
|
.Cascade(SharedThis(this));
|
|
|
|
EmitterCanvas =
|
|
SNew(SCascadeEmitterCanvas)
|
|
.Cascade(SharedThis(this));
|
|
|
|
FDetailsViewArgs Args;
|
|
Args.bHideSelectionTip = true;
|
|
Args.NotifyHook = this;
|
|
|
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
Details = PropertyModule.CreateDetailView(Args);
|
|
Details->SetObject( ParticleSystem );
|
|
|
|
if (!ParticleSystem->CurveEdSetup)
|
|
{
|
|
ParticleSystem->CurveEdSetup = NewObject<UInterpCurveEdSetup>(ParticleSystem, NAME_None, RF_Transactional);
|
|
}
|
|
|
|
IDistributionCurveEditorModule* CurveEditorModule = &FModuleManager::LoadModuleChecked<IDistributionCurveEditorModule>( "DistCurveEditor" );
|
|
CurveEditor = CurveEditorModule->CreateCurveEditorWidget(ParticleSystem->CurveEdSetup, this);
|
|
}
|
|
|
|
#define LOCTEXT_NAMESPACE "CascadeToolbar"
|
|
void FCascade::ExtendToolbar()
|
|
{
|
|
struct Local
|
|
{
|
|
static void FillToolbar(FToolBarBuilder& ToolbarBuilder, TSharedRef< FUICommandList > ToolkitCommands, TSharedRef<SWidget> CurrentLOD)
|
|
{
|
|
ToolbarBuilder.BeginSection("CascadeRestart");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().RestartSimulation);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().RestartInLevel);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("CascadeHistory");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FGenericCommands::Get().Undo, NAME_None, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon( FAppStyle::GetAppStyleSetName(), TEXT("Cascade.Undo") ) );
|
|
ToolbarBuilder.AddToolBarButton(FGenericCommands::Get().Redo, NAME_None, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon( FAppStyle::GetAppStyleSetName(), TEXT("Cascade.Redo") ) );
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("CascadeThumbnail");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().SaveThumbnailImage);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("CascadePreviewOptions");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().ToggleBounds);
|
|
ToolbarBuilder.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateStatic(&FCascade::GenerateBoundsMenuContent, ToolkitCommands),
|
|
LOCTEXT( "BoundsMenuCombo_Label", "Bounds Options" ),
|
|
LOCTEXT( "BoundsMenuCombo_ToolTip", "Bounds options"),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Cascade.ToggleBounds"),
|
|
true
|
|
);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().ToggleOriginAxis);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().CascadeBackgroundColor);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("CascadeLOD");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().RegenerateLowestLODDuplicatingHighest);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().RegenerateLowestLOD);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().JumpToLowestLOD);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().JumpToLowerLOD);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().AddLODBeforeCurrent);
|
|
ToolbarBuilder.AddSeparator();
|
|
|
|
// Show the current and total LODs between the buttons for clearer messaging to the user
|
|
ToolbarBuilder.AddWidget(CurrentLOD);
|
|
|
|
ToolbarBuilder.AddSeparator();
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().AddLODAfterCurrent);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().JumpToHigherLOD);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().JumpToHighestLOD);
|
|
ToolbarBuilder.AddToolBarButton(FCascadeCommands::Get().DeleteLOD);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
}
|
|
};
|
|
|
|
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
TSharedRef<SWidget> CurrentLOD = SNew(SBox)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.AddMetaData<FTagMetaData>(TEXT("Cascade.LODBOx"))
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CurrentLOD", "LOD: "))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SNumericEntryBox<int32>)
|
|
.AllowSpin(true)
|
|
.MinValue(0)
|
|
.MaxValue(this, &FCascade::GetMaxLOD)
|
|
.MinSliderValue(0)
|
|
.MaxSliderValue(this, &FCascade::GetMaxLOD)
|
|
.Value(this, &FCascade::GetCurrentLOD)
|
|
.OnValueChanged(this, &FCascade::OnCurrentLODChanged)
|
|
]
|
|
];
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, ToolkitCommands, CurrentLOD )
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
ICascadeModule* CascadeModule = &FModuleManager::LoadModuleChecked<ICascadeModule>( "Cascade" );
|
|
AddToolbarExtender(CascadeModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
#undef LOCTEXT_NAMESPACE
|
|
|
|
void FCascade::BindCommands()
|
|
{
|
|
const FCascadeCommands& Commands = FCascadeCommands::Get();
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleOriginAxis,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewOriginAxis),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewOriginAxisChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_ParticleCounts,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewParticleCounts),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewParticleCountsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_ParticleEventCounts,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewParticleEventCounts),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewParticleEventCountsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_ParticleTimes,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewParticleTimes),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewParticleTimesChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_ParticleMemory,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewParticleMemory),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewParticleMemoryChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_SystemCompleted,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewSystemCompleted),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewSystemCompletedChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.View_EmitterTickTimes,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewEmitterTickTimes),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewEmitterTickTimesChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleGeometry,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewGeometry),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewGeometryChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleGeometry_Properties,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewGeometryProperties));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleLocalVectorFields,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewLocalVectorFields),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewLocalVectorFieldsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RestartSimulation,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRestartSimulation));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RestartInLevel,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRestartInLevel));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SaveThumbnailImage,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSaveThumbnailImage));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleOrbitMode,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleOrbitMode),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleOrbitModeChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleMotion,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleMotion),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleMotionChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SetMotionRadius,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSetMotionRadius));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ViewMode_Wireframe,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewMode, VMI_Wireframe),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewModeChecked, VMI_Wireframe));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ViewMode_Unlit,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewMode, VMI_Unlit),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewModeChecked, VMI_Unlit));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ViewMode_Lit,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewMode, VMI_Lit),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewModeChecked, VMI_Lit));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ViewMode_ShaderComplexity,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnViewMode, VMI_ShaderComplexity),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsViewModeChecked, VMI_ShaderComplexity));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleBounds,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleBounds),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleBoundsChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleBounds_SetFixedBounds,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleBoundsSetFixedBounds));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.TogglePostProcess,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnTogglePostProcess),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsTogglePostProcessChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleGrid,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleGrid),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleGridChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.CascadePlay,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnPlay),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsPlayChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AnimSpeed_100,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAnimSpeed, 1.0f),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsAnimSpeedChecked, 1.0f));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AnimSpeed_50,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAnimSpeed, 0.5f),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsAnimSpeedChecked, 0.5f));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AnimSpeed_25,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAnimSpeed, 0.25f),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsAnimSpeedChecked, 0.25f));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AnimSpeed_10,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAnimSpeed, 0.1f),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsAnimSpeedChecked, 0.1f));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AnimSpeed_1,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAnimSpeed, 0.01f),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsAnimSpeedChecked, 0.01f));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleLoopSystem,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleLoopSystem),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleLoopSystemChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleRealtime,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleRealtime),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleRealtimeChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.CascadeBackgroundColor,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnBackgroundColor));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ToggleWireframeSphere,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnToggleWireframeSphere),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsToggleWireframeSphereChecked));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnUndo));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRedo));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DetailMode_Low,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDetailMode, DM_Low),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsDetailModeChecked, DM_Low));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DetailMode_Medium,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDetailMode, DM_Medium),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsDetailModeChecked, DM_Medium));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DetailMode_High,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDetailMode, DM_High),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsDetailModeChecked, DM_High));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.Significance_Critical,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSignificance, EParticleSignificanceLevel::Critical),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsSignificanceChecked, EParticleSignificanceLevel::Critical));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.Significance_High,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSignificance, EParticleSignificanceLevel::High),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsSignificanceChecked, EParticleSignificanceLevel::High));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.Significance_Medium,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSignificance, EParticleSignificanceLevel::Medium),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsSignificanceChecked, EParticleSignificanceLevel::Medium));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.Significance_Low,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSignificance, EParticleSignificanceLevel::Low),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FCascade::IsSignificanceChecked, EParticleSignificanceLevel::Low));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RegenerateLowestLODDuplicatingHighest,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRegenerateLowestLODDuplicatingHighest));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RegenerateLowestLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRegenerateLowestLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToHighestLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToHighestLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToHigherLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToHigherLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AddLODBeforeCurrent,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAddLODBeforeCurrent));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.AddLODAfterCurrent,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnAddLODAfterCurrent));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLowerLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLowerLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLowestLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLowestLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DeleteLOD,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDeleteLOD));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLOD0,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLODIndex, 0));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLOD1,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLODIndex, 1));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLOD2,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLODIndex, 2));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.JumpToLOD3,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnJumpToLODIndex, 3));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DeleteModule,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDeleteModule, true));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RefreshModule,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRefreshModule));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SyncMaterial,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSyncMaterial));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.UseMaterial,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnUseMaterial));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DupeFromHigher,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDupeFromHigher));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ShareFromHigher,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnShareFromHigher));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DupeFromHighest,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDupeFromHighest));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SetRandomSeed,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSetRandomSeed));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ConvertToSeeded,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnConvertToSeeded));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RenameEmitter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRenameEmitter));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DuplicateEmitter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDuplicateEmitter, false));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DuplicateShareEmitter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDuplicateEmitter, true));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.DeleteEmitter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnDeleteEmitter));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ExportEmitter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnExportEmitter));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.ExportAllEmitters,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnExportAll));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.SelectParticleSystem,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnSelectParticleSystem));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.NewEmitterBefore,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnNewEmitterBefore));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.NewEmitterAfter,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnNewEmitterAfter));
|
|
|
|
ToolkitCommands->MapAction(
|
|
Commands.RemoveDuplicateModules,
|
|
FExecuteAction::CreateSP(this, &FCascade::OnRemoveDuplicateModules));
|
|
}
|
|
|
|
void FCascade::InitParticleModuleClasses()
|
|
{
|
|
if (bParticleModuleClassesInitialized)
|
|
return;
|
|
|
|
for(TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
// Find all ParticleModule classes (ignoring abstract or ParticleTrailModule classes
|
|
if (It->IsChildOf(UParticleModule::StaticClass()))
|
|
{
|
|
if (!(It->HasAnyClassFlags(CLASS_Abstract)))
|
|
{
|
|
ParticleModuleClasses.Add(*It);
|
|
}
|
|
else
|
|
{
|
|
ParticleModuleBaseClasses.Add(*It);
|
|
}
|
|
}
|
|
}
|
|
|
|
bParticleModuleClassesInitialized = true;
|
|
}
|
|
|
|
TOptional<int32> FCascade::GetMaxLOD() const
|
|
{
|
|
int32 LODCount = ParticleSystem ? (ParticleSystem->Emitters.Num() > 0) ? (ParticleSystem->Emitters[0]->LODLevels.Num() > 0 ) ? ParticleSystem->Emitters[0]->LODLevels.Num() - 1 : 0 : 0 : 0;
|
|
return LODCount;
|
|
}
|
|
|
|
TOptional<int32> FCascade::GetCurrentLOD() const
|
|
{
|
|
return CurrentLODIdx;
|
|
}
|
|
|
|
void FCascade::OnCurrentLODChanged(int32 NewLOD)
|
|
{
|
|
SetLODValue(NewLOD);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::MotionRadiusCommitted(const FText& CommentText, ETextCommit::Type CommitInfo)
|
|
{
|
|
if (CommitInfo == ETextCommit::OnEnter)
|
|
{
|
|
MotionModeRadius = atof(TCHAR_TO_ANSI( *CommentText.ToString() ));
|
|
}
|
|
|
|
CloseEntryPopup();
|
|
}
|
|
|
|
void FCascade::SphereRadiusCommitted(const FText& CommentText, ETextCommit::Type CommitInfo)
|
|
{
|
|
if (CommitInfo == ETextCommit::OnEnter)
|
|
{
|
|
PreviewViewport->GetViewportClient()->GetWireSphereRadius() = atof(TCHAR_TO_ANSI(*CommentText.ToString()));
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::WireSphere);
|
|
}
|
|
|
|
CloseEntryPopup();
|
|
}
|
|
|
|
void FCascade::EmitterNameCommitted(const FText& CommentText, ETextCommit::Type CommitInfo)
|
|
{
|
|
if ((CommitInfo == ETextCommit::OnEnter) && SelectedEmitter)
|
|
{
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "EmitterRename", "Rename Emitter");
|
|
|
|
BeginTransaction( Transaction );
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
ParticleSystemComponent->PreEditChange(NULL);
|
|
|
|
SelectedEmitter->Modify();
|
|
SelectedEmitter->SetEmitterName( *CommentText.ToString() );
|
|
|
|
ParticleSystemComponent->PostEditChange();
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
// Refresh viewport
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
CloseEntryPopup();
|
|
}
|
|
|
|
void FCascade::UpdateLODLevel()
|
|
{
|
|
int32 CurrentLODLevel = GetCurrentlySelectedLODLevelIndex();
|
|
SetLODValue(CurrentLODLevel);
|
|
}
|
|
|
|
void FCascade::SetLODValue(int32 LODSetting)
|
|
{
|
|
if (LODSetting >= 0)
|
|
{
|
|
if (ParticleSystem)
|
|
{
|
|
ParticleSystem->EditorLODSetting = LODSetting;
|
|
}
|
|
if (ParticleSystemComponent)
|
|
{
|
|
const int32 OldEditorLODLevel = ParticleSystemComponent->EditorLODLevel;
|
|
ParticleSystemComponent->EditorLODLevel = LODSetting;
|
|
ParticleSystemComponent->SetLODLevel(LODSetting);
|
|
}
|
|
|
|
CurrentLODIdx = LODSetting;
|
|
}
|
|
|
|
if (!GEngine->bEnableEditorPSysRealtimeLOD && ParticleSystemComponent != NULL)
|
|
{
|
|
for (TObjectIterator<UParticleSystemComponent> It;It;++It)
|
|
{
|
|
if (It->Template == ParticleSystemComponent->Template)
|
|
{
|
|
It->EditorLODLevel = LODSetting;
|
|
It->SetLODLevel(LODSetting);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::ReassociateParticleSystem() const
|
|
{
|
|
if (ParticleSystemComponent)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->GetPreviewScene().RemoveComponent(ParticleSystemComponent);
|
|
PreviewViewport->GetViewportClient()->GetPreviewScene().AddComponent(ParticleSystemComponent, FTransform::Identity);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::RestartParticleSystem()
|
|
{
|
|
if (ParticleSystemComponent)
|
|
{
|
|
ParticleSystemComponent->ResetParticles();
|
|
ParticleSystemComponent->SetManagingSignificance(true);
|
|
ParticleSystemComponent->SetRequiredSignificance(RequiredSignificance);
|
|
ParticleSystemComponent->ActivateSystem();
|
|
if (ParticleSystemComponent->Template)
|
|
{
|
|
ParticleSystemComponent->Template->bShouldResetPeakCounts = true;
|
|
}
|
|
ParticleSystemComponent->bIsViewRelevanceDirty = true;
|
|
ParticleSystemComponent->CachedViewRelevanceFlags.Empty();
|
|
ParticleSystemComponent->ConditionalCacheViewRelevanceFlags();
|
|
|
|
ReassociateParticleSystem();
|
|
}
|
|
|
|
if (ParticleSystem)
|
|
{
|
|
ParticleSystem->CalculateMaxActiveParticleCounts();
|
|
}
|
|
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->UpdateMemoryInformation();
|
|
}
|
|
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
void FCascade::ForceUpdate()
|
|
{
|
|
// Touch the module lists in each emitter.
|
|
ParticleSystem->UpdateAllModuleLists();
|
|
UpdateLODLevel();
|
|
ParticleSystemComponent->ResetParticles();
|
|
ParticleSystemComponent->InitializeSystem();
|
|
|
|
// 'Refresh' the viewport
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
if (CurveEditor.IsValid())
|
|
{
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
|
|
if( Details.IsValid() )
|
|
{
|
|
const TArray< TWeakObjectPtr<UObject> > NewSelectedObjects = Details->GetSelectedObjects();
|
|
Details->SetObjects( NewSelectedObjects, true );
|
|
}
|
|
}
|
|
|
|
bool FCascade::PromptForCancellingSoloingMode( const FText& InOperationDesc)
|
|
{
|
|
FText DisplayMessage = FText::Format( NSLOCTEXT("UnrealEd", "CASCADE_CancelSoloing", "Disable soloing to perform the following:\n{0}"), InOperationDesc );
|
|
|
|
bool bCancelSoloing = EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, DisplayMessage);
|
|
if (bCancelSoloing)
|
|
{
|
|
ParticleSystem->TurnOffSoloing();
|
|
bIsSoloing = false;
|
|
}
|
|
|
|
ForceUpdate();
|
|
return bCancelSoloing;
|
|
}
|
|
|
|
bool FCascade::DuplicateEmitter(UParticleEmitter* SourceEmitter, UParticleSystem* DestSystem, bool bShare)
|
|
{
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( NSLOCTEXT("UnrealEd", "DuplicateEmitter", "Duplicate Emitter") ) == false)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UObject* SourceOuter = SourceEmitter->GetOuter();
|
|
if (SourceOuter != DestSystem)
|
|
{
|
|
if (bShare == true)
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Can't share modules across particle systems!"));
|
|
bShare = false;
|
|
}
|
|
}
|
|
|
|
int32 InsertionIndex = -1;
|
|
if (SourceOuter == DestSystem)
|
|
{
|
|
UParticleSystem* SourcePSys = Cast<UParticleSystem>(SourceOuter);
|
|
if (SourcePSys)
|
|
{
|
|
// Find the source emitter in the SourcePSys emitter array
|
|
for (int32 CheckSourceIndex = 0; CheckSourceIndex < SourcePSys->Emitters.Num(); CheckSourceIndex++)
|
|
{
|
|
if (SourcePSys->Emitters[CheckSourceIndex] == SourceEmitter)
|
|
{
|
|
InsertionIndex = CheckSourceIndex + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find desired class of new module.
|
|
UClass* NewEmitClass = SourceEmitter->GetClass();
|
|
if (NewEmitClass == UParticleSpriteEmitter::StaticClass())
|
|
{
|
|
// Construct it
|
|
UParticleEmitter* NewEmitter = NewObject<UParticleEmitter>(DestSystem, NewEmitClass, NAME_None, RF_Transactional);
|
|
|
|
check(NewEmitter);
|
|
|
|
FString NewName = SourceEmitter->GetEmitterName().ToString();
|
|
NewEmitter->SetEmitterName(FName(*NewName));
|
|
NewEmitter->EmitterEditorColor = FColor::MakeRandomColor();
|
|
NewEmitter->EmitterEditorColor.A = 255;
|
|
|
|
NewEmitter->InitialAllocationCount = SourceEmitter->InitialAllocationCount;
|
|
NewEmitter->QualityLevelSpawnRateScale = SourceEmitter->QualityLevelSpawnRateScale;
|
|
NewEmitter->DetailModeBitmask = SourceEmitter->DetailModeBitmask;
|
|
NewEmitter->DetailModeDisplay = SourceEmitter->DetailModeDisplay;
|
|
NewEmitter->bDisabledLODsKeepEmitterAlive = SourceEmitter->bDisabledLODsKeepEmitterAlive;
|
|
NewEmitter->bUseLegacySpawningBehavior = SourceEmitter->bUseLegacySpawningBehavior;
|
|
NewEmitter->bDisableWhenInsignficant = SourceEmitter->bDisableWhenInsignficant;
|
|
NewEmitter->SignificanceLevel = SourceEmitter->SignificanceLevel;
|
|
NewEmitter->EmitterRenderMode = SourceEmitter->EmitterRenderMode;
|
|
NewEmitter->bCollapsed = false;
|
|
|
|
// 'Private' data - not required by the editor
|
|
UObject* DupObject;
|
|
UParticleLODLevel* SourceLODLevel;
|
|
UParticleLODLevel* NewLODLevel;
|
|
UParticleLODLevel* PrevSourceLODLevel = NULL;
|
|
UParticleLODLevel* PrevLODLevel = NULL;
|
|
|
|
NewEmitter->LODLevels.InsertZeroed(0, SourceEmitter->LODLevels.Num());
|
|
for (int32 LODIndex = 0; LODIndex < SourceEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
SourceLODLevel = SourceEmitter->LODLevels[LODIndex];
|
|
NewLODLevel = NewObject<UParticleLODLevel>(NewEmitter, NAME_None, RF_Transactional);
|
|
check(NewLODLevel);
|
|
|
|
NewLODLevel->Level = SourceLODLevel->Level;
|
|
NewLODLevel->bEnabled = SourceLODLevel->bEnabled;
|
|
|
|
// The RequiredModule
|
|
if (bShare)
|
|
{
|
|
NewLODLevel->RequiredModule = SourceLODLevel->RequiredModule;
|
|
}
|
|
else
|
|
{
|
|
if ((LODIndex > 0) && (PrevSourceLODLevel->RequiredModule == SourceLODLevel->RequiredModule))
|
|
{
|
|
PrevLODLevel->RequiredModule->LODValidity |= (1 << LODIndex);
|
|
NewLODLevel->RequiredModule = PrevLODLevel->RequiredModule;
|
|
}
|
|
else
|
|
{
|
|
DupObject = StaticDuplicateObject(SourceLODLevel->RequiredModule, DestSystem);
|
|
check(DupObject);
|
|
NewLODLevel->RequiredModule = Cast<UParticleModuleRequired>(DupObject);
|
|
NewLODLevel->RequiredModule->ModuleEditorColor = FColor::MakeRandomColor();
|
|
NewLODLevel->RequiredModule->LODValidity = (1 << LODIndex);
|
|
}
|
|
}
|
|
|
|
// The SpawnModule
|
|
if (bShare)
|
|
{
|
|
NewLODLevel->SpawnModule = SourceLODLevel->SpawnModule;
|
|
}
|
|
else
|
|
{
|
|
if ((LODIndex > 0) && (PrevSourceLODLevel->SpawnModule == SourceLODLevel->SpawnModule))
|
|
{
|
|
PrevLODLevel->SpawnModule->LODValidity |= (1 << LODIndex);
|
|
NewLODLevel->SpawnModule = PrevLODLevel->SpawnModule;
|
|
}
|
|
else
|
|
{
|
|
DupObject = StaticDuplicateObject(SourceLODLevel->SpawnModule, DestSystem);
|
|
check(DupObject);
|
|
NewLODLevel->SpawnModule = Cast<UParticleModuleSpawn>(DupObject);
|
|
NewLODLevel->SpawnModule->ModuleEditorColor = FColor::MakeRandomColor();
|
|
NewLODLevel->SpawnModule->LODValidity = (1 << LODIndex);
|
|
}
|
|
}
|
|
|
|
// Copy each module
|
|
NewLODLevel->Modules.InsertZeroed(0, SourceLODLevel->Modules.Num());
|
|
for (int32 ModuleIndex = 0; ModuleIndex < SourceLODLevel->Modules.Num(); ModuleIndex++)
|
|
{
|
|
UParticleModule* SourceModule = SourceLODLevel->Modules[ModuleIndex];
|
|
if (bShare)
|
|
{
|
|
NewLODLevel->Modules[ModuleIndex] = SourceModule;
|
|
}
|
|
else
|
|
{
|
|
if ((LODIndex > 0) && (PrevSourceLODLevel->Modules[ModuleIndex] == SourceLODLevel->Modules[ModuleIndex]))
|
|
{
|
|
PrevLODLevel->Modules[ModuleIndex]->LODValidity |= (1 << LODIndex);
|
|
NewLODLevel->Modules[ModuleIndex] = PrevLODLevel->Modules[ModuleIndex];
|
|
}
|
|
else
|
|
{
|
|
DupObject = StaticDuplicateObject(SourceModule, DestSystem);
|
|
if (DupObject)
|
|
{
|
|
UParticleModule* Module = Cast<UParticleModule>(DupObject);
|
|
Module->ModuleEditorColor = FColor::MakeRandomColor();
|
|
NewLODLevel->Modules[ModuleIndex] = Module;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TypeData module as well...
|
|
if (SourceLODLevel->TypeDataModule)
|
|
{
|
|
if (bShare)
|
|
{
|
|
NewLODLevel->TypeDataModule = SourceLODLevel->TypeDataModule;
|
|
}
|
|
else
|
|
{
|
|
if ((LODIndex > 0) && (PrevSourceLODLevel->TypeDataModule == SourceLODLevel->TypeDataModule))
|
|
{
|
|
PrevLODLevel->TypeDataModule->LODValidity |= (1 << LODIndex);
|
|
NewLODLevel->TypeDataModule = PrevLODLevel->TypeDataModule;
|
|
}
|
|
else
|
|
{
|
|
DupObject = StaticDuplicateObject(SourceLODLevel->TypeDataModule, DestSystem);
|
|
if (DupObject)
|
|
{
|
|
UParticleModule* Module = Cast<UParticleModule>(DupObject);
|
|
Module->ModuleEditorColor = FColor::MakeRandomColor();
|
|
NewLODLevel->TypeDataModule = CastChecked<UParticleModuleTypeDataBase>(Module);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
NewLODLevel->ConvertedModules = true;
|
|
NewLODLevel->PeakActiveParticles = SourceLODLevel->PeakActiveParticles;
|
|
|
|
NewEmitter->LODLevels[LODIndex] = NewLODLevel;
|
|
|
|
PrevLODLevel = NewLODLevel;
|
|
PrevSourceLODLevel = SourceLODLevel;
|
|
}
|
|
|
|
// Generate all the levels that are present in other emitters...
|
|
// NOTE: Big assumptions - the highest and lowest are 0,100 respectively and they MUST exist.
|
|
if (DestSystem->Emitters.Num() > 0)
|
|
{
|
|
int32 DestLODCount = 0;
|
|
int32 NewLODCount = 0;
|
|
|
|
{
|
|
UParticleEmitter* DestEmitter = DestSystem->Emitters[0];
|
|
DestLODCount = DestEmitter->LODLevels.Num();
|
|
NewLODCount = NewEmitter->LODLevels.Num();
|
|
}
|
|
|
|
if (DestLODCount != NewLODCount)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Generating existing LOD levels..."));
|
|
|
|
if (DestLODCount < NewLODCount)
|
|
{
|
|
for (int32 DestEmitIndex = 0; DestEmitIndex < DestSystem->Emitters.Num(); DestEmitIndex++)
|
|
{
|
|
UParticleEmitter* DestEmitter = DestSystem->Emitters[DestEmitIndex];
|
|
for (int32 InsertIndex = DestLODCount; InsertIndex < NewLODCount; InsertIndex++)
|
|
{
|
|
DestEmitter->CreateLODLevel(InsertIndex);
|
|
}
|
|
DestEmitter->UpdateModuleLists();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 InsertIndex = NewLODCount; InsertIndex < DestLODCount; InsertIndex++)
|
|
{
|
|
NewEmitter->CreateLODLevel(InsertIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NewEmitter->UpdateModuleLists();
|
|
|
|
// Add to selected emitter
|
|
if ((InsertionIndex >= 0) && (InsertionIndex < DestSystem->Emitters.Num()))
|
|
{
|
|
DestSystem->Emitters.Insert(NewEmitter, InsertionIndex);
|
|
}
|
|
else
|
|
{
|
|
DestSystem->Emitters.Add(NewEmitter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FText Message = FText::Format( NSLOCTEXT("UnrealEd", "Prompt_4", "{0} support coming soon."), FText::FromString( NewEmitClass->GetDesc() ) );
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
return false;
|
|
}
|
|
|
|
DestSystem->SetupSoloing();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FCascade::MoveSelectedEmitter(int32 MoveAmount)
|
|
{
|
|
if (!SelectedEmitter)
|
|
return;
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "MoveEmitter", "Move Selected Emitter");
|
|
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( Transaction ) == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem();
|
|
|
|
int32 CurrentEmitterIndex = ParticleSystem->Emitters.Find(SelectedEmitter);
|
|
check(CurrentEmitterIndex != INDEX_NONE);
|
|
|
|
int32 NewEmitterIndex = FMath::Clamp<int32>(CurrentEmitterIndex + MoveAmount, 0, ParticleSystem->Emitters.Num() - 1);
|
|
|
|
if (NewEmitterIndex != CurrentEmitterIndex)
|
|
{
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
ParticleSystem->Emitters.Remove(SelectedEmitter);
|
|
ParticleSystem->Emitters.InsertZeroed(NewEmitterIndex);
|
|
ParticleSystem->Emitters[NewEmitterIndex] = SelectedEmitter;
|
|
|
|
ParticleSystem->PostEditChange();
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
}
|
|
|
|
void FCascade::AddNewEmitter(int32 PositionOffset)
|
|
{
|
|
if (SelectedEmitter == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 EmitterCount = ParticleSystem->Emitters.Num();
|
|
int32 EmitterIndex = -1;
|
|
for (int32 Index = 0; Index < EmitterCount; Index++)
|
|
{
|
|
UParticleEmitter* CheckEmitter = ParticleSystem->Emitters[Index];
|
|
if (SelectedEmitter == CheckEmitter)
|
|
{
|
|
EmitterIndex = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (EmitterIndex != -1)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Insert New Emitter Before %d"), EmitterIndex);
|
|
|
|
// Fake create it at the end
|
|
OnNewEmitter();
|
|
|
|
if (EmitterCount + 1 == ParticleSystem->Emitters.Num())
|
|
{
|
|
UParticleEmitter* NewEmitter = ParticleSystem->Emitters[EmitterCount];
|
|
SetSelectedEmitter(NewEmitter);
|
|
if ((PositionOffset == 0) || (EmitterIndex + PositionOffset < EmitterCount))
|
|
{
|
|
MoveSelectedEmitter(EmitterIndex - EmitterCount + PositionOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::DuplicateModule(bool bDoShare, bool bUseHighest)
|
|
{
|
|
if (!SelectedModule && !SelectedEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 CurrLODSetting = GetCurrentlySelectedLODLevelIndex();
|
|
if (SelectedEmitter->IsLODLevelValid(CurrLODSetting) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CurrLODSetting == 0)
|
|
{
|
|
// High LOD modules don't allow this.
|
|
return;
|
|
}
|
|
|
|
UParticleLODLevel* SourceLODLevel = SelectedEmitter->GetLODLevel(bUseHighest? 0: CurrLODSetting - 1);
|
|
check(SourceLODLevel);
|
|
UParticleModule* HighModule = SourceLODLevel->GetModuleAtIndex(SelectedModuleIndex);
|
|
if (HighModule == NULL)
|
|
{
|
|
// Couldn't find the highest module???
|
|
return;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "DupeSelectedModule", "Duplicate Selected Module");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifySelectedObjects();
|
|
ModifyParticleSystem();
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
UParticleModule* NewModule = NULL;
|
|
UParticleLODLevel* DestLODLevel;
|
|
|
|
bool bIsShared = GetIsModuleShared(SelectedModule);
|
|
// Store the index of the selected module...
|
|
// Force copy the source module...
|
|
DestLODLevel = SelectedEmitter->GetLODLevel(CurrLODSetting);
|
|
NewModule = HighModule->GenerateLODModule(SourceLODLevel, DestLODLevel, 100.0f, false, !bDoShare);
|
|
check(NewModule);
|
|
|
|
for (int32 LODIndex = CurrLODSetting; LODIndex < SelectedEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
DestLODLevel = SelectedEmitter->GetLODLevel(LODIndex);
|
|
if (SelectedModule->IsUsedInLODLevel(LODIndex))
|
|
{
|
|
if (bIsShared == false)
|
|
{
|
|
// Turn off the LOD validity in the original module...
|
|
int32 LODIndexToUse = bDoShare? DestLODLevel->Level: LODIndex;
|
|
SelectedModule->LODValidity &= ~(1 << LODIndexToUse);
|
|
}
|
|
// Turn on the LOD validity int he new module...
|
|
NewModule->LODValidity |= (1 << LODIndex);
|
|
|
|
// Store the new module
|
|
switch (SelectedModuleIndex)
|
|
{
|
|
case INDEX_NONE:
|
|
break;
|
|
case INDEX_REQUIREDMODULE:
|
|
DestLODLevel->RequiredModule = CastChecked<UParticleModuleRequired>(NewModule);
|
|
break;
|
|
case INDEX_SPAWNMODULE:
|
|
DestLODLevel->SpawnModule = CastChecked<UParticleModuleSpawn>(NewModule);
|
|
break;
|
|
case INDEX_TYPEDATAMODULE:
|
|
DestLODLevel->TypeDataModule = CastChecked<UParticleModuleTypeDataBase>(NewModule);
|
|
break;
|
|
default:
|
|
DestLODLevel->Modules[SelectedModuleIndex] = NewModule;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedModule = NewModule;
|
|
if (SelectedEmitter)
|
|
{
|
|
SelectedEmitter->UpdateModuleLists();
|
|
}
|
|
|
|
ParticleSystem->PostEditChange();
|
|
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
EndTransaction( Transaction );
|
|
ForceUpdate();
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::ExportSelectedEmitter()
|
|
{
|
|
if (!SelectedEmitter)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_NoEmitterSelectedForExport", "No emitter selected for export"));
|
|
return;
|
|
}
|
|
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
for (FSelectionIterator Itor(*GEditor->GetSelectedObjects()) ; Itor ; ++Itor)
|
|
{
|
|
UParticleSystem* DestPartSys = Cast<UParticleSystem>(*Itor);
|
|
if (DestPartSys && (DestPartSys != ParticleSystem))
|
|
{
|
|
int32 NewCount = 0;
|
|
if (DestPartSys->Emitters.Num() > 0)
|
|
{
|
|
UParticleEmitter* DestEmitter0 = DestPartSys->Emitters[0];
|
|
|
|
NewCount = DestEmitter0->LODLevels.Num() - SelectedEmitter->LODLevels.Num();
|
|
if (NewCount > 0)
|
|
{
|
|
// There are more LODs in the destination than the source... Add enough to cover.
|
|
int32 StartIndex = SelectedEmitter->LODLevels.Num();
|
|
for (int32 InsertIndex = 0; InsertIndex < NewCount; InsertIndex++)
|
|
{
|
|
SelectedEmitter->CreateLODLevel(StartIndex + InsertIndex, true);
|
|
}
|
|
SelectedEmitter->UpdateModuleLists();
|
|
}
|
|
else if (NewCount < 0)
|
|
{
|
|
int32 InsertCount = -NewCount;
|
|
// There are fewer LODs in the destination than the source... Add enough to cover.
|
|
int32 StartIndex = DestEmitter0->LODLevels.Num();
|
|
for (int32 EmitterIndex = 0; EmitterIndex < DestPartSys->Emitters.Num(); EmitterIndex++)
|
|
{
|
|
UParticleEmitter* DestEmitter = DestPartSys->Emitters[EmitterIndex];
|
|
if (DestEmitter)
|
|
{
|
|
for (int32 InsertIndex = 0; InsertIndex < InsertCount; InsertIndex++)
|
|
{
|
|
DestEmitter->CreateLODLevel(StartIndex + InsertIndex, false);
|
|
}
|
|
DestEmitter->UpdateModuleLists();
|
|
}
|
|
}
|
|
|
|
// Add the slots in the LODDistances array
|
|
DestPartSys->LODDistances.AddZeroed(InsertCount);
|
|
for (int32 DistIndex = StartIndex; DistIndex < DestPartSys->LODDistances.Num(); DistIndex++)
|
|
{
|
|
DestPartSys->LODDistances[DistIndex] = DistIndex * 2500.0f;
|
|
}
|
|
DestPartSys->LODSettings.AddZeroed(InsertCount);
|
|
for (int32 DistIndex = StartIndex; DistIndex < DestPartSys->LODSettings.Num(); DistIndex++)
|
|
{
|
|
DestPartSys->LODSettings[DistIndex] = FParticleSystemLOD::CreateParticleSystemLOD();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 InsertCount = SelectedEmitter->LODLevels.Num();
|
|
// Reset LODSettings and LODDistances arrays
|
|
DestPartSys->LODSettings.Empty();
|
|
DestPartSys->LODDistances.Empty();
|
|
DestPartSys->LODDistances.AddZeroed(InsertCount);
|
|
for (int32 DistIndex = 0; DistIndex < InsertCount; DistIndex++)
|
|
{
|
|
DestPartSys->LODDistances[DistIndex] = DistIndex * 2500.0f;
|
|
}
|
|
DestPartSys->LODSettings.AddZeroed(InsertCount);
|
|
for (int32 DistIndex = 0; DistIndex < InsertCount; DistIndex++)
|
|
{
|
|
DestPartSys->LODSettings[DistIndex] = FParticleSystemLOD::CreateParticleSystemLOD();
|
|
}
|
|
}
|
|
|
|
ParticleSystem->SetupSoloing(); // we may have changed the number of LODs, so our soloing information could be invalid
|
|
|
|
|
|
if (!DuplicateEmitter(SelectedEmitter, DestPartSys, false))
|
|
{
|
|
FText Message = FText::Format( NSLOCTEXT("UnrealEd", "Error_FailedToCopyFormatting", "Failed to copy {0} to {1}"),
|
|
FText::FromName( SelectedEmitter->GetEmitterName() ),
|
|
FText::FromString( DestPartSys->GetName() ) );
|
|
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
}
|
|
|
|
DestPartSys->MarkPackageDirty();
|
|
|
|
// If we temporarily inserted LOD levels into the selected emitter,
|
|
// we need to remove them now...
|
|
if (NewCount > 0)
|
|
{
|
|
int32 CurrCount = SelectedEmitter->LODLevels.Num();
|
|
for (int32 RemoveIndex = CurrCount - 1; RemoveIndex >= (CurrCount - NewCount); RemoveIndex--)
|
|
{
|
|
SelectedEmitter->LODLevels.RemoveAt(RemoveIndex);
|
|
}
|
|
SelectedEmitter->UpdateModuleLists();
|
|
}
|
|
|
|
// Find instances of this particle system and reset them...
|
|
for (TObjectIterator<UParticleSystemComponent> It;It;++It)
|
|
{
|
|
if (It->Template == DestPartSys)
|
|
{
|
|
UParticleSystemComponent* PSysComp = *It;
|
|
|
|
// If the preview window the system component belonged to has been
|
|
// destroyed, but garbage collection has not yet run, we will be
|
|
// able to find the system but it won't have a world nor does it need
|
|
// to be reactivated
|
|
if (PSysComp->GetWorld())
|
|
{
|
|
// Force a recache of the view relevance
|
|
PSysComp->bIsViewRelevanceDirty = true;
|
|
bool bIsActive = PSysComp->IsActive();
|
|
PSysComp->DeactivateSystem();
|
|
PSysComp->ResetParticles();
|
|
if (bIsActive)
|
|
{
|
|
PSysComp->ActivateSystem();
|
|
}
|
|
PSysComp->ReregisterComponent();
|
|
}
|
|
}
|
|
}
|
|
|
|
ICascadeModule* CascadeModule = &FModuleManager::GetModuleChecked<ICascadeModule>("Cascade");
|
|
CascadeModule->RefreshCascade(DestPartSys);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::RegenerateLowestLOD(bool bDupeHighest)
|
|
{
|
|
if ((ParticleSystem == NULL) || (ParticleSystem->Emitters.Num() == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ParticleSystem->bRegenerateLODDuplicate = bDupeHighest;
|
|
|
|
const FText WarningMessage = NSLOCTEXT("UnrealEd", "CascadeRegenLowLODWarningLine1", "*** WARNING ***\nRegenerating the lowest LOD level will delete\nall other LOD levels from the particle system!\nAre you sure you want to do so?");
|
|
|
|
if ( EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, WarningMessage) )
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Regenerate Lowest LOD levels!"));
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadeRegenerateLowestLOD", "Regenerate Lowest LOD");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem(true);
|
|
|
|
// Delete all LOD levels from each emitter...
|
|
for (int32 EmitterIndex = 0; EmitterIndex < ParticleSystem->Emitters.Num(); EmitterIndex++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIndex];
|
|
if (Emitter)
|
|
{
|
|
for (int32 LODIndex = Emitter->LODLevels.Num() - 1; LODIndex > 0; LODIndex--)
|
|
{
|
|
Emitter->LODLevels.RemoveAt(LODIndex);
|
|
}
|
|
if (Emitter->AutogenerateLowestLODLevel(ParticleSystem->bRegenerateLODDuplicate) == false)
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Failed to autogenerate lowest LOD level!"));
|
|
}
|
|
|
|
Emitter->UpdateModuleLists();
|
|
}
|
|
}
|
|
|
|
// Reset the LOD distances
|
|
ParticleSystem->LODDistances.Empty();
|
|
ParticleSystem->LODSettings.Empty();
|
|
UParticleEmitter* SourceEmitter = ParticleSystem->Emitters[0];
|
|
if (SourceEmitter)
|
|
{
|
|
ParticleSystem->LODDistances.AddUninitialized(SourceEmitter->LODLevels.Num());
|
|
for (int32 LODIndex = 0; LODIndex < ParticleSystem->LODDistances.Num(); LODIndex++)
|
|
{
|
|
ParticleSystem->LODDistances[LODIndex] = LODIndex * 2500.0f;
|
|
}
|
|
ParticleSystem->LODSettings.AddUninitialized(SourceEmitter->LODLevels.Num());
|
|
for (int32 LODIndex = 0; LODIndex < ParticleSystem->LODSettings.Num(); LODIndex++)
|
|
{
|
|
ParticleSystem->LODSettings[LODIndex] = FParticleSystemLOD::CreateParticleSystemLOD();
|
|
}
|
|
}
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
OnRestartInLevel();
|
|
|
|
check(bTransactionInProgress);
|
|
EndTransaction( Transaction );
|
|
|
|
// Re-fill the LODCombo so that deleted LODLevels are removed.
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
if (ParticleSystemComponent)
|
|
{
|
|
ParticleSystemComponent->ResetParticles();
|
|
ParticleSystemComponent->InitializeSystem();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("CANCELLED Regenerate Lowest LOD levels!"));
|
|
}
|
|
|
|
UpdateLODLevel();
|
|
}
|
|
|
|
void FCascade::AddLOD(bool bBeforeCurrent)
|
|
{
|
|
if (bIsSoloing == true)
|
|
{
|
|
FText Description = bBeforeCurrent ? NSLOCTEXT("UnrealEd", "CascadeLODAddBefore", "Add LOD Before Current") : NSLOCTEXT("UnrealEd", "CascadeLODAddAfter", "Add LOD After Current");
|
|
if (PromptForCancellingSoloingMode( Description ) == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// See if there is already a LOD level for this value...
|
|
if (ParticleSystem->Emitters.Num() > 0)
|
|
{
|
|
UParticleEmitter* FirstEmitter = ParticleSystem->Emitters[0];
|
|
if (FirstEmitter)
|
|
{
|
|
if (FirstEmitter->LODLevels.Num() >= 8)
|
|
{
|
|
FNotificationInfo Info( NSLOCTEXT("UnrealEd", "CascadeTooManyLODs", "Max LOD levels (8) already present") );
|
|
Info.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int32 CurrentLODIndex = GetCurrentlySelectedLODLevelIndex();
|
|
if (bBeforeCurrent && CurrentLODIndex < 0)
|
|
{
|
|
return;
|
|
}
|
|
else if (!bBeforeCurrent)
|
|
{
|
|
CurrentLODIndex++;
|
|
}
|
|
|
|
UE_LOG(LogCascade, Log, TEXT("Inserting LOD level at %d"), CurrentLODIndex);
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadeLODAdd", "Add LOD");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem(true);
|
|
|
|
for (int32 EmitterIndex = 0; EmitterIndex < ParticleSystem->Emitters.Num(); EmitterIndex++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIndex];
|
|
if (Emitter)
|
|
{
|
|
Emitter->CreateLODLevel(CurrentLODIndex);
|
|
}
|
|
}
|
|
|
|
//This should probably have fixed size and behave like LODDistances but for now just avoid the crash.
|
|
ParticleSystem->LODSettings.SetNumZeroed(FMath::Max(CurrentLODIndex, ParticleSystem->LODSettings.Num()));
|
|
|
|
ParticleSystem->LODDistances.InsertZeroed(CurrentLODIndex, 1);
|
|
if (CurrentLODIndex == 0)
|
|
{
|
|
ParticleSystem->LODDistances[CurrentLODIndex] = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
ParticleSystem->LODDistances[CurrentLODIndex] = ParticleSystem->LODDistances[CurrentLODIndex - 1];
|
|
}
|
|
|
|
ParticleSystem->LODSettings.InsertZeroed(CurrentLODIndex, 1);
|
|
if (CurrentLODIndex == 0)
|
|
{
|
|
ParticleSystem->LODSettings[CurrentLODIndex] = FParticleSystemLOD::CreateParticleSystemLOD();
|
|
}
|
|
else
|
|
{
|
|
ParticleSystem->LODSettings[CurrentLODIndex] = ParticleSystem->LODSettings[CurrentLODIndex - 1];
|
|
}
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
check(bTransactionInProgress);
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.NewLOD"), FAnalyticsEventAttribute(TEXT("Index"), CurrentLODIndex));
|
|
}
|
|
|
|
UpdateLODLevel();
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
ForceUpdate();
|
|
|
|
OnRestartInLevel();
|
|
}
|
|
}
|
|
|
|
void FCascade::SetSelectedInCurveEditor()
|
|
{
|
|
if (!CurveEditor.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CurveEditor->ClearAllSelectedCurves();
|
|
if (SelectedModule)
|
|
{
|
|
TArray<FParticleCurvePair> Curves;
|
|
SelectedModule->GetCurveObjects(Curves);
|
|
for (int32 CurveIndex = 0; CurveIndex < Curves.Num(); CurveIndex++)
|
|
{
|
|
UObject* Distribution = Curves[CurveIndex].CurveObject;
|
|
if (Distribution)
|
|
{
|
|
CurveEditor->SetCurveSelected(Distribution, true);
|
|
}
|
|
}
|
|
CurveEditor->SetActiveTabToFirstSelected();
|
|
CurveEditor->ScrollToFirstSelected();
|
|
}
|
|
CurveEditor->RefreshViewport();
|
|
}
|
|
|
|
bool FCascade::BeginTransaction(const FText& Description)
|
|
{
|
|
if (bTransactionInProgress)
|
|
{
|
|
FString kError = TEXT("UNREALCASCADE: Failed to begin transaction - ");
|
|
kError += Description.ToString();
|
|
checkf(0, TEXT("%s"), *kError);
|
|
return false;
|
|
}
|
|
|
|
GEditor->Trans->Begin( NULL, Description );
|
|
TransactionDescription = Description;
|
|
bTransactionInProgress = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FCascade::EndTransaction(const FText& Description)
|
|
{
|
|
if (!bTransactionInProgress)
|
|
{
|
|
FString kError = TEXT("UNREALCASCADE: Failed to end transaction - ");
|
|
kError += Description.ToString();
|
|
checkf(0, TEXT("%s"), *kError);
|
|
return false;
|
|
}
|
|
|
|
if ( !Description.EqualTo( TransactionDescription ) )
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Cascade - EndTransaction = %s --- Curr = %s"), *Description.ToString(), *TransactionDescription.ToString());
|
|
return false;
|
|
}
|
|
|
|
GEditor->Trans->End();
|
|
|
|
TransactionDescription = FText::GetEmpty();
|
|
bTransactionInProgress = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void FCascade::ModifySelectedObjects()
|
|
{
|
|
if (SelectedEmitter)
|
|
{
|
|
ModifyEmitter(SelectedEmitter);
|
|
}
|
|
if (SelectedModule)
|
|
{
|
|
SelectedModule->Modify();
|
|
}
|
|
}
|
|
|
|
void FCascade::ModifyParticleSystem(bool bInModifyEmitters)
|
|
{
|
|
ParticleSystem->Modify();
|
|
if (bInModifyEmitters == true)
|
|
{
|
|
for (int32 EmitterIdx = 0; EmitterIdx < ParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIdx];
|
|
if (Emitter != NULL)
|
|
{
|
|
ModifyEmitter(Emitter);
|
|
}
|
|
}
|
|
}
|
|
ParticleSystemComponent->Modify();
|
|
}
|
|
|
|
void FCascade::ModifyEmitter(UParticleEmitter* Emitter)
|
|
{
|
|
if (Emitter)
|
|
{
|
|
Emitter->Modify();
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
LODLevel->Modify();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> FCascade::GenerateBoundsMenuContent(TSharedRef<FUICommandList> InCommandList)
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, InCommandList);
|
|
|
|
MenuBuilder.AddMenuEntry(FCascadeCommands::Get().ToggleBounds_SetFixedBounds);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FCascade::ToggleDrawOption(int32 Element)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->ToggleDrawElement((FCascadeEdPreviewViewportClient::EDrawElements)Element);
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsDrawOptionEnabled(int32 Element) const
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->GetDrawElement((FCascadeEdPreviewViewportClient::EDrawElements)Element);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnViewEmitterTickTimes()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::EmitterTickTimes);
|
|
}
|
|
|
|
bool FCascade::IsViewEmitterTickTimesChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::EmitterTickTimes);
|
|
}
|
|
|
|
|
|
void FCascade::OnViewOriginAxis()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::OriginAxis);
|
|
}
|
|
|
|
bool FCascade::IsViewOriginAxisChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::OriginAxis);
|
|
}
|
|
|
|
void FCascade::OnViewParticleCounts()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::ParticleCounts);
|
|
}
|
|
|
|
bool FCascade::IsViewParticleCountsChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::ParticleCounts);
|
|
}
|
|
|
|
void FCascade::OnViewParticleEventCounts()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::ParticleEvents);
|
|
}
|
|
|
|
bool FCascade::IsViewParticleEventCountsChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::ParticleEvents);
|
|
}
|
|
|
|
void FCascade::OnViewParticleTimes()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::ParticleTimes);
|
|
}
|
|
|
|
bool FCascade::IsViewParticleTimesChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::ParticleTimes);
|
|
}
|
|
|
|
void FCascade::OnViewParticleMemory()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::ParticleMemory);
|
|
}
|
|
|
|
bool FCascade::IsViewParticleMemoryChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::ParticleMemory);
|
|
}
|
|
|
|
void FCascade::OnViewSystemCompleted()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::ParticleSystemCompleted);
|
|
}
|
|
|
|
bool FCascade::IsViewSystemCompletedChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::ParticleSystemCompleted);
|
|
}
|
|
|
|
void FCascade::OnViewGeometry()
|
|
{
|
|
if ((PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid()) &&
|
|
PreviewViewport->GetViewportClient()->GetFloorComponent())
|
|
{
|
|
UStaticMeshComponent* FloorComponent = PreviewViewport->GetViewportClient()->GetFloorComponent();
|
|
FPreviewScene& PreviewScene = PreviewViewport->GetViewportClient()->GetPreviewScene();
|
|
bool bIsVisible = !FloorComponent->IsVisible();
|
|
|
|
FloorComponent->SetVisibility(bIsVisible);
|
|
|
|
EditorOptions->bShowFloor = bIsVisible;
|
|
EditorOptions->SaveConfig();
|
|
|
|
PreviewScene.RemoveComponent(FloorComponent);
|
|
PreviewScene.AddComponent(FloorComponent, FTransform::Identity);
|
|
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsViewGeometryChecked() const
|
|
{
|
|
if ((PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid()) &&
|
|
PreviewViewport->GetViewportClient()->GetFloorComponent())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->GetFloorComponent()->IsVisible();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnViewGeometryProperties()
|
|
{
|
|
TSharedPtr<SWindow> PinnedGeometryPropertiesWindow = GeometryPropertiesWindow.Pin();
|
|
|
|
if ((PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid()) &&
|
|
PreviewViewport->GetViewportClient()->GetFloorComponent() &&
|
|
!PinnedGeometryPropertiesWindow.IsValid() )
|
|
{
|
|
UStaticMeshComponent* FloorComponent = PreviewViewport->GetViewportClient()->GetFloorComponent();
|
|
TArray<UObject*> SelectedObjects;
|
|
SelectedObjects.Add(FloorComponent);
|
|
|
|
GeometryPropertiesWindow = ObjectTools::OpenPropertiesForSelectedObjects(SelectedObjects);
|
|
}
|
|
else
|
|
{
|
|
PinnedGeometryPropertiesWindow->BringToFront(true);
|
|
}
|
|
}
|
|
|
|
void FCascade::OnViewLocalVectorFields()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::VectorFields);
|
|
}
|
|
|
|
bool FCascade::IsViewLocalVectorFieldsChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::VectorFields);
|
|
}
|
|
|
|
void FCascade::OnRestartSimulation()
|
|
{
|
|
RestartParticleSystem();
|
|
}
|
|
|
|
void FCascade::OnRestartInLevel()
|
|
{
|
|
RestartParticleSystem();
|
|
|
|
for (TObjectIterator<UParticleSystemComponent> It;It;++It)
|
|
{
|
|
if (It->Template && It->Template == ParticleSystemComponent->Template)
|
|
{
|
|
UParticleSystemComponent* PSysComp = *It;
|
|
|
|
if (PSysComp->IsRegistered())
|
|
{
|
|
// Check for a valid template
|
|
check(PSysComp->Template);
|
|
PSysComp->ResetParticles(true);
|
|
PSysComp->bIsViewRelevanceDirty = true;
|
|
PSysComp->CachedViewRelevanceFlags.Empty();
|
|
PSysComp->Template->bShouldResetPeakCounts = true;
|
|
PSysComp->ActivateSystem();
|
|
PSysComp->ConditionalCacheViewRelevanceFlags();
|
|
PSysComp->MarkRenderStateDirty();
|
|
PSysComp->SetManagingSignificance(true);
|
|
PSysComp->SetRequiredSignificance(RequiredSignificance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnSaveThumbnailImage()
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->CreateThumbnail();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnToggleOrbitMode()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::Orbit);
|
|
}
|
|
|
|
bool FCascade::IsToggleOrbitModeChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::Orbit);
|
|
}
|
|
|
|
void FCascade::OnToggleMotion()
|
|
{
|
|
bIsToggleMotion = !bIsToggleMotion;
|
|
}
|
|
|
|
bool FCascade::IsToggleMotionChecked() const
|
|
{
|
|
return bIsToggleMotion;
|
|
}
|
|
|
|
void FCascade::OnSetMotionRadius()
|
|
{
|
|
FString DefaultText = FString::Printf(TEXT("%.2f"), MotionModeRadius);
|
|
TSharedRef<STextEntryPopup> TextEntry =
|
|
SNew(STextEntryPopup)
|
|
.Label(NSLOCTEXT("Cascade", "MotionRadius", "Motion Radius: "))
|
|
.DefaultText(FText::FromString( DefaultText ) )
|
|
.OnTextCommitted(this, &FCascade::MotionRadiusCommitted)
|
|
.SelectAllTextWhenFocused(true)
|
|
.ClearKeyboardFocusOnCommit(false);
|
|
|
|
EntryMenu = FSlateApplication::Get().PushMenu(
|
|
PreviewViewport.ToSharedRef(),
|
|
FWidgetPath(),
|
|
TextEntry,
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
|
|
void FCascade::OnViewMode(EViewModeIndex ViewMode)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->SetViewMode(ViewMode);
|
|
|
|
ReassociateParticleSystem();
|
|
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsViewModeChecked(EViewModeIndex ViewMode) const
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->IsViewModeEnabled( ViewMode );
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnToggleBounds()
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::Bounds);
|
|
}
|
|
|
|
bool FCascade::IsToggleBoundsChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::Bounds);
|
|
}
|
|
|
|
void FCascade::OnToggleBoundsSetFixedBounds()
|
|
{
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadeSetFixedBounds", "Set Fixed Bounds");
|
|
|
|
BeginTransaction( Transaction );
|
|
|
|
// Force the component to update its bounds.
|
|
ParticleSystemComponent->ForceUpdateBounds();
|
|
|
|
// Grab the current bounds of the PSysComp & set it on the PSystem itself
|
|
ParticleSystem->Modify();
|
|
ParticleSystem->FixedRelativeBoundingBox.Min = ParticleSystemComponent->Bounds.GetBoxExtrema(0);
|
|
ParticleSystem->FixedRelativeBoundingBox.Max = ParticleSystemComponent->Bounds.GetBoxExtrema(1);
|
|
ParticleSystem->FixedRelativeBoundingBox.IsValid = true;
|
|
ParticleSystem->bUseFixedRelativeBoundingBox = true;
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
if ((SelectedModule == NULL) && (SelectedEmitter == NULL))
|
|
{
|
|
TArray<UObject*> NewSelection;
|
|
NewSelection.Add(ParticleSystem);
|
|
SetSelection(NewSelection);
|
|
}
|
|
|
|
ReassociateParticleSystem();
|
|
}
|
|
|
|
void FCascade::OnTogglePostProcess()
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->EngineShowFlags.PostProcessing = !PreviewViewport->GetViewportClient()->EngineShowFlags.PostProcessing;
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsTogglePostProcessChecked() const
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->EngineShowFlags.PostProcessing;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnToggleGrid()
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
// Toggle the grid and worldbox.
|
|
FEditorCommonDrawHelper& DrawHelper = PreviewViewport->GetViewportClient()->GetDrawHelper();
|
|
bool bShowGrid = !DrawHelper.bDrawGrid;
|
|
EditorOptions->bShowGrid = bShowGrid;
|
|
EditorOptions->SaveConfig();
|
|
DrawHelper.bDrawGrid = bShowGrid;
|
|
|
|
PreviewViewport->GetViewportClient()->EngineShowFlags.SetGrid(bShowGrid);
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsToggleGridChecked() const
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->GetDrawHelper().bDrawGrid;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnPlay()
|
|
{
|
|
if (!FMath::IsNearlyZero(TimeScale))
|
|
{
|
|
CachedTimeScale = TimeScale;
|
|
TimeScale = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
TimeScale = CachedTimeScale;
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsPlayChecked() const
|
|
{
|
|
return TimeScale > KINDA_SMALL_NUMBER;
|
|
}
|
|
|
|
void FCascade::OnAnimSpeed(float Speed)
|
|
{
|
|
TimeScale = Speed;
|
|
}
|
|
|
|
bool FCascade::IsAnimSpeedChecked(float Speed) const
|
|
{
|
|
if (TimeScale > KINDA_SMALL_NUMBER)
|
|
{
|
|
return FMath::IsNearlyEqual(TimeScale, Speed);
|
|
}
|
|
else
|
|
{
|
|
return FMath::IsNearlyEqual(CachedTimeScale, Speed);
|
|
}
|
|
}
|
|
|
|
void FCascade::OnToggleLoopSystem()
|
|
{
|
|
bIsToggleLoopSystem = !bIsToggleLoopSystem;
|
|
|
|
if (!bIsToggleLoopSystem)
|
|
{
|
|
bIsPendingReset = false;
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsToggleLoopSystemChecked() const
|
|
{
|
|
return bIsToggleLoopSystem;
|
|
}
|
|
|
|
void FCascade::OnToggleRealtime()
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
PreviewViewport->GetViewportClient()->ToggleRealtime();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsToggleRealtimeChecked() const
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
return PreviewViewport->GetViewportClient()->IsRealtime();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FCascade::OnBackgroundColor()
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
TArray<FColor*> FColorArray;
|
|
|
|
FColorArray.Add(&GetParticleSystem()->BackgroundColor);
|
|
|
|
FColorPickerArgs PickerArgs;
|
|
PickerArgs.ParentWidget = PreviewViewport;
|
|
PickerArgs.DisplayGamma = TAttribute<float>::Create( TAttribute<float>::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) );
|
|
PickerArgs.ColorArray = &FColorArray;
|
|
|
|
OpenColorPicker(PickerArgs);
|
|
}
|
|
}
|
|
|
|
void FCascade::OnToggleWireframeSphere()
|
|
{
|
|
if (!IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::WireSphere))
|
|
{
|
|
FString DefaultText = FString::Printf(TEXT("%.2f"), PreviewViewport->GetViewportClient()->GetWireSphereRadius());
|
|
TSharedRef<STextEntryPopup> TextEntry =
|
|
SNew(STextEntryPopup)
|
|
.Label(NSLOCTEXT("Cascade", "SphereRadius", "Sphere Radius: "))
|
|
.DefaultText(FText::FromString(DefaultText))
|
|
.OnTextCommitted(this, &FCascade::SphereRadiusCommitted)
|
|
.SelectAllTextWhenFocused(true)
|
|
.ClearKeyboardFocusOnCommit(false);
|
|
|
|
EntryMenu = FSlateApplication::Get().PushMenu(
|
|
PreviewViewport.ToSharedRef(),
|
|
FWidgetPath(),
|
|
TextEntry,
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ToggleDrawOption(FCascadeEdPreviewViewportClient::WireSphere);
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsToggleWireframeSphereChecked() const
|
|
{
|
|
return IsDrawOptionEnabled(FCascadeEdPreviewViewportClient::WireSphere);
|
|
}
|
|
|
|
void FCascade::OnUndo()
|
|
{
|
|
if (GEditor->Trans->Undo())
|
|
{
|
|
ForceUpdate();
|
|
OnRestartInLevel();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnRedo()
|
|
{
|
|
if (GEditor->Trans->Redo())
|
|
{
|
|
ForceUpdate();
|
|
OnRestartInLevel();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnRegenerateLowestLODDuplicatingHighest()
|
|
{
|
|
bool bDupeHighest = true;
|
|
RegenerateLowestLOD(bDupeHighest);
|
|
}
|
|
|
|
void FCascade::OnRegenerateLowestLOD()
|
|
{
|
|
bool bDupeHighest = false;
|
|
RegenerateLowestLOD(bDupeHighest);
|
|
}
|
|
|
|
void FCascade::OnDetailMode(EDetailMode InDetailMode)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
if (DetailMode == InDetailMode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set the detail mode values on in-level particle systems
|
|
for (TObjectIterator<UParticleSystemComponent> It;It;++It)
|
|
{
|
|
if (It->Template == ParticleSystemComponent->Template)
|
|
{
|
|
It->EditorDetailMode = GEngine->bEnableEditorPSysRealtimeLOD ? GetCachedScalabilityCVars().DetailMode : InDetailMode;
|
|
}
|
|
}
|
|
|
|
DetailMode = InDetailMode;
|
|
|
|
RestartParticleSystem();
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsDetailModeChecked(EDetailMode InDetailMode) const
|
|
{
|
|
return DetailMode == InDetailMode;
|
|
}
|
|
|
|
void FCascade::OnSignificance(EParticleSignificanceLevel InSignificance)
|
|
{
|
|
if (PreviewViewport.IsValid() && PreviewViewport->GetViewportClient().IsValid())
|
|
{
|
|
if (RequiredSignificance == InSignificance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set the detail mode values on in-level particle systems
|
|
for (TObjectIterator<UParticleSystemComponent> It; It; ++It)
|
|
{
|
|
if (It->Template == ParticleSystemComponent->Template)
|
|
{
|
|
It->SetManagingSignificance(true);
|
|
It->SetRequiredSignificance(InSignificance);
|
|
}
|
|
}
|
|
|
|
RequiredSignificance = InSignificance;
|
|
}
|
|
}
|
|
|
|
bool FCascade::IsSignificanceChecked(EParticleSignificanceLevel InSignificance) const
|
|
{
|
|
return RequiredSignificance == InSignificance;
|
|
}
|
|
|
|
void FCascade::OnJumpToLowestLOD()
|
|
{
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 Value = 0;
|
|
|
|
SetLODValue(Value);
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnJumpToLowerLOD()
|
|
{
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 LODValue = GetCurrentlySelectedLODLevelIndex();
|
|
|
|
// Find the next lower LOD...
|
|
// We can use any emitter, since they will all have the same number of LOD levels
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter)
|
|
{
|
|
// Go from the low to the high...
|
|
for (int32 LODIndex = Emitter->LODLevels.Num() - 1; LODIndex >= 0; LODIndex--)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
if (LODLevel->Level < LODValue)
|
|
{
|
|
SetLODValue(LODLevel->Level);
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnAddLODAfterCurrent()
|
|
{
|
|
bool bBeforeCurrent = false;
|
|
AddLOD(bBeforeCurrent);
|
|
}
|
|
|
|
void FCascade::OnAddLODBeforeCurrent()
|
|
{
|
|
bool bBeforeCurrent = true;
|
|
AddLOD(bBeforeCurrent);
|
|
}
|
|
|
|
void FCascade::OnJumpToHigherLOD()
|
|
{
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 LODValue = GetCurrentlySelectedLODLevelIndex();
|
|
// Find the next higher LOD...
|
|
// We can use any emitter, since they will all have the same number of LOD levels
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter)
|
|
{
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
if (LODLevel->Level > LODValue)
|
|
{
|
|
SetLODValue(LODLevel->Level);
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnJumpToHighestLOD()
|
|
{
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 Value = ParticleSystem->Emitters[0]->LODLevels.Num() - 1;
|
|
|
|
SetLODValue(Value);
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnJumpToLODIndex(int32 LODLevel)
|
|
{
|
|
if (ParticleSystem->Emitters.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 Value = FMath::Clamp(LODLevel, 0, ParticleSystem->Emitters[0]->LODLevels.Num() - 1);
|
|
|
|
SetLODValue(Value);
|
|
SetSelectedModule(SelectedEmitter, SelectedModule);
|
|
|
|
if (PreviewViewport.IsValid())
|
|
{
|
|
PreviewViewport->RefreshViewport();
|
|
}
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnDeleteLOD()
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[0];
|
|
if (Emitter == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( NSLOCTEXT("UnrealEd", "CascadeLODDelete", "Delete LOD") ) == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
int32 Selection = GetCurrentlySelectedLODLevelIndex();
|
|
if ((Selection < 0) || ((Selection == 0) && (Emitter->LODLevels.Num() == 1)))
|
|
{
|
|
FNotificationInfo Info( NSLOCTEXT("UnrealEd", "CascadeCantDeleteLOD", "Can't delete - only LOD level") );
|
|
Info.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
return;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CascadeDeleteLOD", "Delete LOD");
|
|
|
|
// Delete the setting...
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem(true);
|
|
|
|
// Remove the LOD entry from the distance array
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
if ((LODLevel->Level == Selection) && (ParticleSystem->LODDistances.Num() > LODLevel->Level))
|
|
{
|
|
ParticleSystem->LODDistances.RemoveAt(LODLevel->Level);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
if ((LODLevel->Level == Selection) && (ParticleSystem->LODSettings.Num() > LODLevel->Level))
|
|
{
|
|
ParticleSystem->LODSettings.RemoveAt(LODLevel->Level);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the level from each emitter in the system
|
|
for (int32 EmitterIndex = 0; EmitterIndex < ParticleSystem->Emitters.Num(); EmitterIndex++)
|
|
{
|
|
Emitter = ParticleSystem->Emitters[EmitterIndex];
|
|
if (Emitter)
|
|
{
|
|
for (int32 LODIndex = 0; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIndex];
|
|
if (LODLevel)
|
|
{
|
|
if (LODLevel->Level == Selection)
|
|
{
|
|
// Clear out the flags from the modules.
|
|
LODLevel->RequiredModule->LODValidity &= ~(1 << LODLevel->Level);
|
|
LODLevel->SpawnModule->LODValidity &= ~(1 << LODLevel->Level);
|
|
if (LODLevel->TypeDataModule)
|
|
{
|
|
LODLevel->TypeDataModule->LODValidity &= ~(1 << LODLevel->Level);
|
|
}
|
|
|
|
for (int32 ModuleIndex = 0; ModuleIndex < LODLevel->Modules.Num(); ModuleIndex++)
|
|
{
|
|
UParticleModule* PModule = LODLevel->Modules[ModuleIndex];
|
|
if (PModule)
|
|
{
|
|
PModule->LODValidity &= ~(1 << LODLevel->Level);
|
|
}
|
|
}
|
|
|
|
// Delete it and shift all down
|
|
Emitter->LODLevels.RemoveAt(LODIndex);
|
|
|
|
for (; LODIndex < Emitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* RemapLODLevel = Emitter->LODLevels[LODIndex];
|
|
if (RemapLODLevel)
|
|
{
|
|
RemapLODLevel->SetLevelIndex(RemapLODLevel->Level - 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
check(bTransactionInProgress);
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.DeleteLOD"), FAnalyticsEventAttribute(TEXT("Index"), Selection));
|
|
}
|
|
|
|
ForceUpdate();
|
|
|
|
OnRestartInLevel();
|
|
}
|
|
|
|
void FCascade::OnDeleteModule(bool bConfirm)
|
|
{
|
|
if (!SelectedModule || !SelectedEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (SelectedEmitter->bCollapsed == true)
|
|
{
|
|
// Should never get in here!
|
|
return;
|
|
}
|
|
|
|
if (SelectedModuleIndex == INDEX_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((SelectedModuleIndex == INDEX_REQUIREDMODULE) ||
|
|
(SelectedModuleIndex == INDEX_SPAWNMODULE))
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Cascade_NoDeleteRequiredOrSpawn", "The Required and Spawn modules may not be deleted."));
|
|
return;
|
|
}
|
|
|
|
int32 CurrLODSetting = GetCurrentlySelectedLODLevelIndex();
|
|
if (CurrLODSetting != 0)
|
|
{
|
|
// Don't allow deleting modules if not at highest LOD
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Cascade_ModuleDeleteLODWarning", "Attempting to delete module while not on highest LOD (0)"));
|
|
return;
|
|
}
|
|
|
|
// If there are differences in the enabled states of the LOD levels for an emitter,
|
|
// prompt the user to ensure they want to delete it...
|
|
{
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->LODLevels[0];
|
|
UParticleModule* CheckModule;
|
|
bool bEnabledStateDifferent = false;
|
|
bool bEnabled = SelectedModule->bEnabled;
|
|
for (int32 LODIndex = 1; (LODIndex < SelectedEmitter->LODLevels.Num()) && !bEnabledStateDifferent; LODIndex++)
|
|
{
|
|
LODLevel = SelectedEmitter->LODLevels[LODIndex];
|
|
switch (SelectedModuleIndex)
|
|
{
|
|
case INDEX_TYPEDATAMODULE:
|
|
CheckModule = LODLevel->TypeDataModule;
|
|
break;
|
|
default:
|
|
CheckModule = LODLevel->Modules[SelectedModuleIndex];
|
|
break;
|
|
}
|
|
|
|
check(CheckModule);
|
|
|
|
if (LODLevel->IsModuleEditable(CheckModule))
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
}
|
|
|
|
if ((bConfirm == true) && (bEnabledStateDifferent == true))
|
|
{
|
|
if ( EAppReturnType::Yes != FMessageDialog::Open(EAppMsgType::YesNo, NSLOCTEXT("UnrealEd", "ModuleDeleteConfirm", "Module state is different in other LOD levels.\nAre you sure you want to delete it?")) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "DeleteSelectedModule", "Delete Selected Module");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifySelectedObjects();
|
|
ModifyParticleSystem();
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
// Find the module index...
|
|
int32 DeleteModuleIndex = -1;
|
|
FString ModuleName;
|
|
|
|
UParticleLODLevel* HighLODLevel = SelectedEmitter->GetLODLevel(0);
|
|
check(HighLODLevel);
|
|
for (int32 ModuleIndex = 0; ModuleIndex < HighLODLevel->Modules.Num(); ModuleIndex++)
|
|
{
|
|
UParticleModule* CheckModule = HighLODLevel->Modules[ModuleIndex];
|
|
if (CheckModule == SelectedModule)
|
|
{
|
|
DeleteModuleIndex = ModuleIndex;
|
|
ModuleName = CheckModule->GetClass()->GetName();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SelectedModule->IsDisplayedInCurveEd(CurveEditor->GetEdSetup()) && !GetIsModuleShared(SelectedModule))
|
|
{
|
|
// Remove it from the curve editor!
|
|
SelectedModule->RemoveModuleCurvesFromEditor(CurveEditor->GetEdSetup());
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
|
|
// Check all the others...
|
|
for (int32 LODIndex = 1; LODIndex < SelectedEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(LODIndex);
|
|
if (LODLevel)
|
|
{
|
|
UParticleModule* Module;
|
|
|
|
if (DeleteModuleIndex >= 0)
|
|
{
|
|
Module = LODLevel->Modules[DeleteModuleIndex];
|
|
}
|
|
else
|
|
{
|
|
Module = LODLevel->TypeDataModule;
|
|
}
|
|
|
|
if (Module)
|
|
{
|
|
Module->RemoveModuleCurvesFromEditor(CurveEditor->GetEdSetup());
|
|
CurveEditor->CurveChanged();
|
|
}
|
|
}
|
|
|
|
}
|
|
CurveEditor->RefreshViewport();
|
|
|
|
bool bNeedsListUpdated = false;
|
|
|
|
for (int32 LODIndex = 0; LODIndex < SelectedEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->GetLODLevel(LODIndex);
|
|
|
|
// See if it is in this LODs level...
|
|
UParticleModule* CheckModule;
|
|
|
|
if (DeleteModuleIndex >= 0)
|
|
{
|
|
CheckModule = LODLevel->Modules[DeleteModuleIndex];
|
|
}
|
|
else
|
|
{
|
|
CheckModule = LODLevel->TypeDataModule;
|
|
}
|
|
|
|
if (CheckModule)
|
|
{
|
|
if (CheckModule->IsA(UParticleModuleTypeDataBase::StaticClass()))
|
|
{
|
|
check(LODLevel->TypeDataModule == CheckModule);
|
|
LODLevel->TypeDataModule = NULL;
|
|
}
|
|
else if (CheckModule->IsA(UParticleModuleEventGenerator::StaticClass()))
|
|
{
|
|
LODLevel->EventGenerator = NULL;
|
|
}
|
|
LODLevel->Modules.Remove(CheckModule);
|
|
bNeedsListUpdated = true;
|
|
}
|
|
}
|
|
|
|
if (bNeedsListUpdated)
|
|
{
|
|
SelectedEmitter->UpdateModuleLists();
|
|
}
|
|
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.DeleteModule"), TEXT("Class"), ModuleName);
|
|
}
|
|
|
|
SetSelectedEmitter(SelectedEmitter);
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
}
|
|
|
|
void FCascade::OnRefreshModule()
|
|
{
|
|
if (SelectedModule && SelectedEmitter)
|
|
{
|
|
SelectedModule->RefreshModule(ParticleSystem->CurveEdSetup, SelectedEmitter, GetCurrentlySelectedLODLevelIndex());
|
|
}
|
|
}
|
|
|
|
void FCascade::OnSyncMaterial()
|
|
{
|
|
TArray<UObject*> Objects;
|
|
|
|
if (SelectedModule)
|
|
{
|
|
UParticleModuleRequired* RequiredModule = Cast<UParticleModuleRequired>(SelectedModule);
|
|
if (RequiredModule)
|
|
{
|
|
Objects.Add(RequiredModule->Material);
|
|
}
|
|
}
|
|
|
|
// Sync the generic browser to the object list.
|
|
GEditor->SyncBrowserToObjects(Objects);
|
|
}
|
|
|
|
void FCascade::OnUseMaterial()
|
|
{
|
|
if (SelectedModule && SelectedEmitter)
|
|
{
|
|
UParticleModuleRequired* RequiredModule = Cast<UParticleModuleRequired>(SelectedModule);
|
|
if (RequiredModule)
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
UObject* Obj = GEditor->GetSelectedObjects()->GetTop(UMaterialInterface::StaticClass());
|
|
if (Obj)
|
|
{
|
|
UMaterialInterface* SelectedMaterial = Cast<UMaterialInterface>(Obj);
|
|
if (SelectedMaterial)
|
|
{
|
|
RequiredModule->Material = SelectedMaterial;
|
|
SelectedEmitter->PostEditChange();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnDupeFromHigher()
|
|
{
|
|
bool bDoShare = false;
|
|
bool bUseHighest = false;
|
|
DuplicateModule(bDoShare, bUseHighest);
|
|
}
|
|
|
|
void FCascade::OnShareFromHigher()
|
|
{
|
|
bool bDoShare = true;
|
|
bool bUseHighest = false;
|
|
DuplicateModule(bDoShare, bUseHighest);
|
|
}
|
|
|
|
void FCascade::OnDupeFromHighest()
|
|
{
|
|
bool bDoShare = false;
|
|
bool bUseHighest = true;
|
|
DuplicateModule(bDoShare, bUseHighest);
|
|
}
|
|
|
|
void FCascade::OnSetRandomSeed()
|
|
{
|
|
if ((SelectedModule != NULL) && (SelectedModule->SupportsRandomSeed()))
|
|
{
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CASC_SetRandomSeed", "Set Random Seed");
|
|
|
|
BeginTransaction( Transaction );
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
ParticleSystemComponent->PreEditChange(NULL);
|
|
|
|
int32 RandomSeed = FMath::RoundToInt(static_cast<float>(RAND_MAX) * RandomStream.FRand());
|
|
if (SelectedModule->SetRandomSeedEntry(0, RandomSeed) == false)
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Failed to set random seed entry on module %s"), *(SelectedModule->GetClass()->GetName()));
|
|
}
|
|
|
|
ParticleSystemComponent->PostEditChange();
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
// Refresh viewport
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCascade::OnConvertToSeeded()
|
|
{
|
|
if ((SelectedModule != NULL) && (SelectedModule->SupportsRandomSeed() == false))
|
|
{
|
|
// See if there is a seeded version of this module...
|
|
UClass* CurrentClass = SelectedModule->GetClass();
|
|
check(CurrentClass);
|
|
FString ClassName = CurrentClass->GetName();
|
|
UE_LOG(LogCascade, Log, TEXT("Non-seeded module %s"), *ClassName);
|
|
// This only works if the seeded version is names <ClassName>_Seeded!!!!
|
|
FString SeededClassName = ClassName + TEXT("_Seeded");
|
|
UClass* SeededClass = UClass::TryFindTypeSlow<UClass>(SeededClassName);
|
|
if (SeededClass != NULL)
|
|
{
|
|
// Find the module index
|
|
UParticleLODLevel* BaseLODLevel = GetCurrentlySelectedLODLevel();
|
|
if (BaseLODLevel != NULL)
|
|
{
|
|
check(BaseLODLevel->Level == 0);
|
|
|
|
int32 ConvertModuleIdx = INDEX_NONE;
|
|
for (int32 CheckModuleIdx = 0; CheckModuleIdx < BaseLODLevel->Modules.Num(); CheckModuleIdx++)
|
|
{
|
|
if (BaseLODLevel->Modules[CheckModuleIdx] == SelectedModule)
|
|
{
|
|
ConvertModuleIdx = CheckModuleIdx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
check(ConvertModuleIdx != INDEX_NONE);
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "CASC_ConvertToSeeded", "Convert To Seeded");
|
|
|
|
// We need to do this for *all* copies of this module.
|
|
BeginTransaction( Transaction );
|
|
if (ConvertModuleToSeeded(ParticleSystem, SelectedEmitter, ConvertModuleIdx, SeededClass, true) == false)
|
|
{
|
|
UE_LOG(LogCascade, Warning, TEXT("Failed to convert module!"));
|
|
}
|
|
EndTransaction(Transaction);
|
|
|
|
//Have to reset all existing component using this system.
|
|
FParticleResetContext ResetCtx;
|
|
ResetCtx.AddTemplate(ParticleSystem);
|
|
|
|
SetSelectedModule(SelectedEmitter, BaseLODLevel->Modules[ConvertModuleIdx]);
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParticleSystem_DumpInfo(UParticleSystem* InParticleSystem)
|
|
{
|
|
#if UE_BUILD_DEBUG
|
|
if (InParticleSystem != NULL)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("Dumping info for %s"), *(InParticleSystem->GetPathName()));
|
|
UE_LOG(LogCascade, Log, TEXT("\tEmitterCount = %d"), InParticleSystem->Emitters.Num());
|
|
for (int32 EmitterIdx = 0; EmitterIdx < InParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("\t\tEmitter %d"), EmitterIdx);
|
|
UParticleEmitter* Emitter = InParticleSystem->Emitters[EmitterIdx];
|
|
if (Emitter != NULL)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("\t\t\tLODLevels %d"), Emitter->LODLevels.Num());
|
|
for (int32 LODIdx = 0; LODIdx < Emitter->LODLevels.Num(); LODIdx++)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[LODIdx];
|
|
if (LODLevel != NULL)
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("\t\t\t\tLODLevel %d"), LODIdx);
|
|
FString ModuleDump = TEXT("\t\t\t\t");
|
|
|
|
for (int32 ModuleIdx = 0; ModuleIdx < LODLevel->Modules.Num(); ModuleIdx++)
|
|
{
|
|
UParticleModule* Module = LODLevel->Modules[ModuleIdx];
|
|
ModuleDump += FString::Printf(TEXT("0x%08x,"), PTRINT(Module));
|
|
}
|
|
UE_LOG(LogCascade, Log, TEXT("%s"), *ModuleDump);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("\t\t\t\t*** NULL"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCascade, Log, TEXT("\t\t\t*** NULL"));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool FCascade::ConvertModuleToSeeded(UParticleSystem* ParticleSystem, UParticleEmitter* InEmitter, int32 InModuleIdx, UClass* InSeededClass, bool bInUpdateModuleLists)
|
|
{
|
|
ParticleSystem_DumpInfo(ParticleSystem);
|
|
//
|
|
for (int32 LODIdx = 0; LODIdx < InEmitter->LODLevels.Num(); LODIdx++)
|
|
{
|
|
UParticleLODLevel* LODLevel = InEmitter->LODLevels[LODIdx];
|
|
UParticleModule* ConvertModule = LODLevel->Modules[InModuleIdx];
|
|
check(ConvertModule != NULL);
|
|
|
|
UParticleModule* NewModule = ConvertModule;
|
|
if ((LODIdx == 0) || ((ConvertModule->LODValidity & (1 << (LODIdx - 1))) == 0))
|
|
{
|
|
NewModule = CastChecked<UParticleModule>(StaticDuplicateObject(ConvertModule, ParticleSystem, NAME_None, RF_AllFlags, InSeededClass));
|
|
|
|
// Since we used the non-randomseed module to create, this flag won't be set during construction...
|
|
NewModule->bSupportsRandomSeed = true;
|
|
|
|
FParticleRandomSeedInfo* RandSeedInfo = NewModule->GetRandomSeedInfo();
|
|
if (RandSeedInfo != NULL)
|
|
{
|
|
RandSeedInfo->bResetSeedOnEmitterLooping = true;
|
|
RandSeedInfo->RandomSeeds.Add(FMath::TruncToInt(RandomStream.FRand() * static_cast<float>(UINT_MAX)));
|
|
}
|
|
}
|
|
|
|
// Now we have to replace all instances of the module
|
|
LODLevel->Modify();
|
|
LODLevel->Modules[InModuleIdx] = NewModule;
|
|
for (int32 SubLODIdx = LODIdx + 1; SubLODIdx < InEmitter->LODLevels.Num(); SubLODIdx++)
|
|
{
|
|
// If the module is shared, replace it
|
|
UParticleLODLevel* SubLODLevel = InEmitter->LODLevels[SubLODIdx];
|
|
if (SubLODLevel != NULL)
|
|
{
|
|
if (SubLODLevel->Modules[InModuleIdx] == ConvertModule)
|
|
{
|
|
SubLODLevel->Modify();
|
|
SubLODLevel->Modules[InModuleIdx] = NewModule;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the module in the array
|
|
for (int32 EmitterIdx = 0; EmitterIdx < ParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UParticleEmitter* OtherEmitter = ParticleSystem->Emitters[EmitterIdx];
|
|
if (OtherEmitter != InEmitter)
|
|
{
|
|
UParticleLODLevel* OtherLODLevel = OtherEmitter->LODLevels[LODIdx];
|
|
if (OtherLODLevel != NULL)
|
|
{
|
|
for (int32 OtherModuleIdx = 0; OtherModuleIdx < OtherLODLevel->Modules.Num(); OtherModuleIdx++)
|
|
{
|
|
UParticleModule* OtherModule = OtherLODLevel->Modules[OtherModuleIdx];
|
|
if (OtherModule == ConvertModule)
|
|
{
|
|
OtherLODLevel->Modify();
|
|
OtherLODLevel->Modules[OtherModuleIdx] = NewModule;
|
|
for (int32 OtherSubLODIdx = LODIdx + 1; OtherSubLODIdx < OtherEmitter->LODLevels.Num(); OtherSubLODIdx++)
|
|
{
|
|
// If the module is shared, replace it
|
|
UParticleLODLevel* OtherSubLODLevel = OtherEmitter->LODLevels[OtherSubLODIdx];
|
|
if (OtherSubLODLevel != NULL)
|
|
{
|
|
if (OtherSubLODLevel->Modules[InModuleIdx] == ConvertModule)
|
|
{
|
|
OtherSubLODLevel->Modify();
|
|
OtherSubLODLevel->Modules[InModuleIdx] = NewModule;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bInUpdateModuleLists == true)
|
|
{
|
|
ParticleSystem->UpdateAllModuleLists();
|
|
}
|
|
|
|
ParticleSystem_DumpInfo(ParticleSystem);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FCascade::ConvertAllModulesToSeeded(UParticleSystem* ParticleSystem)
|
|
{
|
|
bool bResult = true;
|
|
for (int32 EmitterIdx = 0; EmitterIdx < ParticleSystem->Emitters.Num(); EmitterIdx++)
|
|
{
|
|
UParticleEmitter* Emitter = ParticleSystem->Emitters[EmitterIdx];
|
|
if (Emitter != NULL)
|
|
{
|
|
UParticleLODLevel* LODLevel = Emitter->LODLevels[0];
|
|
if (LODLevel != NULL)
|
|
{
|
|
for (int32 ModuleIdx = 0; ModuleIdx < LODLevel->Modules.Num(); ModuleIdx++)
|
|
{
|
|
UParticleModule* Module = LODLevel->Modules[ModuleIdx];
|
|
if ((Module != NULL) && (Module->SupportsRandomSeed() == false))
|
|
{
|
|
// See if there is a seeded version of this module...
|
|
UClass* CurrentClass = Module->GetClass();
|
|
check(CurrentClass);
|
|
FString ClassName = CurrentClass->GetName();
|
|
UE_LOG(LogCascade, Log, TEXT("Non-seeded module %s"), *ClassName);
|
|
// This only works if the seeded version is names <ClassName>_Seeded!!!!
|
|
FString SeededClassName = ClassName + TEXT("_Seeded");
|
|
UClass* SeededClass = UClass::TryFindTypeSlow<UClass>(SeededClassName);
|
|
if (SeededClass != NULL)
|
|
{
|
|
TArray<FParticleCurvePair> DistCurves;
|
|
Module->GetCurveObjects(DistCurves);
|
|
bool bHasUniformDistributions = false;
|
|
for (int32 DistCurveIdx = 0; DistCurveIdx < DistCurves.Num(); DistCurveIdx++)
|
|
{
|
|
FParticleCurvePair& Pair = DistCurves[DistCurveIdx];
|
|
UDistributionFloatUniform* FloatUniform = Cast<UDistributionFloatUniform>(Pair.CurveObject);
|
|
UDistributionFloatUniformCurve* FloatUniformCurve = Cast<UDistributionFloatUniformCurve>(Pair.CurveObject);
|
|
UDistributionVectorUniform* VectorUniform = Cast<UDistributionVectorUniform>(Pair.CurveObject);
|
|
UDistributionVectorUniformCurve* VectorUniformCurve = Cast<UDistributionVectorUniformCurve>(Pair.CurveObject);
|
|
if (FloatUniform || FloatUniformCurve || VectorUniform || VectorUniformCurve)
|
|
{
|
|
bHasUniformDistributions = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHasUniformDistributions == true)
|
|
{
|
|
if (ConvertModuleToSeeded(ParticleSystem, Emitter, ModuleIdx, SeededClass, false) == false)
|
|
{
|
|
bResult = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ParticleSystem->UpdateAllModuleLists();
|
|
|
|
if( bResult )
|
|
{
|
|
ParticleSystem->MarkPackageDirty();
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void FCascade::OnRenameEmitter()
|
|
{
|
|
if (!SelectedEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedRef<STextEntryPopup> TextEntry =
|
|
SNew(STextEntryPopup)
|
|
.Label(NSLOCTEXT("Cascade", "SetEmitterName", "Emitter Name: "))
|
|
.DefaultText(FText::FromName( SelectedEmitter->GetEmitterName() ))
|
|
.OnTextCommitted(this, &FCascade::EmitterNameCommitted)
|
|
.SelectAllTextWhenFocused(true)
|
|
.ClearKeyboardFocusOnCommit(false);
|
|
|
|
EntryMenu = FSlateApplication::Get().PushMenu(
|
|
EmitterCanvas.ToSharedRef(),
|
|
FWidgetPath(),
|
|
TextEntry,
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
|
|
void FCascade::OnDuplicateEmitter(bool bIsShared)
|
|
{
|
|
// Make sure there is a selected emitter
|
|
if (!SelectedEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "EmitterDuplicate", "Duplicate Emitter");
|
|
|
|
BeginTransaction( Transaction );
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
ParticleSystemComponent->PreEditChange(NULL);
|
|
|
|
DuplicateEmitter(SelectedEmitter, ParticleSystem, bIsShared);
|
|
|
|
ParticleSystemComponent->PostEditChange();
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
// Refresh viewport
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnDeleteEmitter()
|
|
{
|
|
if (!SelectedEmitter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(ParticleSystem->Emitters.Contains(SelectedEmitter));
|
|
|
|
int32 CurrLODSetting = GetCurrentlySelectedLODLevelIndex();
|
|
if (SelectedEmitter->IsLODLevelValid(CurrLODSetting) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (SelectedEmitter->bCollapsed == true)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "EmitterDeleteCollapsed", "Can not delete a collapsed emitter.\nExpand it and then delete."));
|
|
return;
|
|
}
|
|
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "DeleteEmitter", "Delete Emitter");
|
|
|
|
if (bIsSoloing == true)
|
|
{
|
|
if (PromptForCancellingSoloingMode( Transaction ) == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there are differences in the enabled states of the LOD levels for an emitter,
|
|
// prompt the user to ensure they want to delete it...
|
|
UParticleLODLevel* LODLevel = SelectedEmitter->LODLevels[0];
|
|
bool bEnabledStateDifferent = false;
|
|
bool bEnabled = LODLevel->bEnabled;
|
|
for (int32 LODIndex = 1; (LODIndex < SelectedEmitter->LODLevels.Num()) && !bEnabledStateDifferent; LODIndex++)
|
|
{
|
|
LODLevel = SelectedEmitter->LODLevels[LODIndex];
|
|
if (bEnabled != LODLevel->bEnabled)
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
else
|
|
{
|
|
if (LODLevel->IsModuleEditable(LODLevel->RequiredModule))
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
if (LODLevel->IsModuleEditable(LODLevel->SpawnModule))
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
if (LODLevel->TypeDataModule && LODLevel->IsModuleEditable(LODLevel->TypeDataModule))
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
|
|
for (int32 CheckModIndex = 0; CheckModIndex < LODLevel->Modules.Num(); CheckModIndex++)
|
|
{
|
|
if (LODLevel->IsModuleEditable(LODLevel->Modules[CheckModIndex]))
|
|
{
|
|
bEnabledStateDifferent = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bEnabledStateDifferent == true)
|
|
{
|
|
if ( EAppReturnType::Yes != FMessageDialog::Open(EAppMsgType::YesNo, NSLOCTEXT("UnrealEd", "EmitterDeleteConfirm", "Emitter state is different in other LOD levels.\nAre you sure you want to delete it?")) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem();
|
|
|
|
ParticleSystem->PreEditChange(NULL);
|
|
|
|
SelectedEmitter->RemoveEmitterCurvesFromEditor(CurveEditor->GetEdSetup());
|
|
CurveEditor->CurveChanged();
|
|
|
|
ParticleSystem->Emitters.Remove(SelectedEmitter);
|
|
|
|
ParticleSystem->PostEditChange();
|
|
|
|
EmitterToThumbnailMap.Remove(SelectedEmitter);
|
|
|
|
SetSelectedEmitter(NULL);
|
|
|
|
ParticleSystem->SetupSoloing();
|
|
|
|
EndTransaction( Transaction );
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Cascade.DeleteEmitter"));
|
|
}
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
|
|
if (EmitterCanvas.IsValid())
|
|
{
|
|
EmitterCanvas->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
void FCascade::OnExportEmitter()
|
|
{
|
|
ExportSelectedEmitter();
|
|
}
|
|
|
|
void FCascade::OnExportAll()
|
|
{
|
|
if (ParticleSystem->Emitters.Num() <= 0)
|
|
{
|
|
// Can't export empty PSys!
|
|
return;
|
|
}
|
|
|
|
UParticleEmitter* SaveSelectedEmitter = SelectedEmitter;
|
|
// There are more LODs in the destination than the source... Add enough to cover.
|
|
for (int32 SrcIndex = 0; SrcIndex < ParticleSystem->Emitters.Num(); SrcIndex++)
|
|
{
|
|
UParticleEmitter* SrcEmitter = ParticleSystem->Emitters[SrcIndex];
|
|
if (SrcEmitter)
|
|
{
|
|
bool bSkipIt = true;
|
|
for (int32 LODIndex = 0; LODIndex < SrcEmitter->LODLevels.Num(); LODIndex++)
|
|
{
|
|
UParticleLODLevel* LODLevel = SrcEmitter->LODLevels[LODIndex];
|
|
if (LODLevel && LODLevel->bEnabled)
|
|
{
|
|
bSkipIt = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bSkipIt)
|
|
{
|
|
SelectedEmitter = SrcEmitter;
|
|
ExportSelectedEmitter();
|
|
}
|
|
}
|
|
}
|
|
SelectedEmitter = SaveSelectedEmitter;
|
|
}
|
|
|
|
void FCascade::OnSelectParticleSystem()
|
|
{
|
|
SetSelectedEmitter(NULL);
|
|
}
|
|
|
|
void FCascade::OnNewEmitterBefore()
|
|
{
|
|
int32 PositionOffset = 0;
|
|
AddNewEmitter(PositionOffset);
|
|
}
|
|
|
|
void FCascade::OnNewEmitterAfter()
|
|
{
|
|
int32 PositionOffset = 1;
|
|
AddNewEmitter(PositionOffset);
|
|
}
|
|
|
|
void FCascade::OnRemoveDuplicateModules()
|
|
{
|
|
FText Transaction = NSLOCTEXT("UnrealEd", "RemoveDuplicateModules", "Remove Duplicate Modules");
|
|
|
|
BeginTransaction( Transaction );
|
|
ModifyParticleSystem(true);
|
|
|
|
ParticleSystem->RemoveAllDuplicateModules(false, NULL);
|
|
|
|
check(bTransactionInProgress);
|
|
EndTransaction( Transaction );
|
|
|
|
ParticleSystem->MarkPackageDirty();
|
|
ForceUpdate();
|
|
|
|
OnRestartInLevel();
|
|
}
|
|
|
|
void FCascade::CloseEntryPopup()
|
|
{
|
|
if (EntryMenu.IsValid())
|
|
{
|
|
EntryMenu.Pin()->Dismiss();
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UCascadeParticleSystemComponent
|
|
-----------------------------------------------------------------------------*/
|
|
UCascadeParticleSystemComponent::UCascadeParticleSystemComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
void UCascadeParticleSystemComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
|
|
{
|
|
// Don't tick cascade components during the usual level tick. Cascade will tick the component as needed.
|
|
if ( bWarmingUp )
|
|
{
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
}
|
|
}
|
|
|
|
|
|
void UCascadeParticleSystemComponent::CascadeTickComponent(float DeltaTime, enum ELevelTick TickType)
|
|
{
|
|
// Tick the particle system component when ticked from within Cascade.
|
|
Super::TickComponent( DeltaTime, TickType, NULL );
|
|
}
|
|
|
|
const static FName CascadeParticleSystemComponentParticleLineCheckName(TEXT("ParticleLineCheck"));
|
|
|
|
bool UCascadeParticleSystemComponent::ParticleLineCheck(FHitResult& Hit, AActor* SourceActor, const FVector& End, const FVector& Start, const FVector& Extent, const FCollisionObjectQueryParams&)
|
|
{
|
|
if (bWarmingUp == false)
|
|
{
|
|
if (CascadePreviewViewportPtr && CascadePreviewViewportPtr->GetFloorComponent() && CascadePreviewViewportPtr->GetFloorComponent()->IsVisibleInEditor())
|
|
{
|
|
Hit = FHitResult(1.f);
|
|
return CascadePreviewViewportPtr->GetFloorComponent()->SweepComponent( Hit, Start, End, FQuat::Identity, FCollisionShape::MakeBox(Extent) );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UCascadeParticleSystemComponent::UpdateLODInformation()
|
|
{
|
|
if (GetLODLevel() != EditorLODLevel)
|
|
{
|
|
SetLODLevel(EditorLODLevel);
|
|
}
|
|
}
|
|
|
|
UCascadeConfiguration::UCascadeConfiguration(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UCascadeConfiguration
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
bool UCascadeConfiguration::IsModuleTypeValid(FName TypeDataName, FName ModuleName)
|
|
{
|
|
CacheModuleRejections();
|
|
return !ModuleRejections.Contains(ModuleName)
|
|
&& !TypeDataModuleRejections.FindOrAdd(TypeDataName).Contains(ModuleName);
|
|
}
|
|
|
|
void UCascadeConfiguration::CacheModuleRejections()
|
|
{
|
|
if (ModuleRejections.Num() == 0 && TypeDataModuleRejections.Num() == 0)
|
|
{
|
|
TArray<UClass*> ParticleModuleClasses;
|
|
TArray<UClass*> ParticleModuleBaseClasses;
|
|
|
|
for(TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
// Find all ParticleModule classes (ignoring abstract or ParticleTrailModule classes
|
|
if (It->IsChildOf(UParticleModule::StaticClass()))
|
|
{
|
|
if (!(It->HasAnyClassFlags(CLASS_Abstract)))
|
|
{
|
|
ParticleModuleClasses.Add(*It);
|
|
}
|
|
else
|
|
{
|
|
ParticleModuleBaseClasses.Add(*It);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 ModuleIndex = 0; ModuleIndex < ModuleMenu_ModuleRejections.Num(); ++ModuleIndex)
|
|
{
|
|
ModuleRejections.Add(FName(*ModuleMenu_ModuleRejections[ModuleIndex]));
|
|
}
|
|
|
|
for (int32 TypeDataIndex = 0; TypeDataIndex < ModuleMenu_TypeDataToBaseModuleRejections.Num(); ++TypeDataIndex)
|
|
{
|
|
FModuleMenuMapper& MenuMapper = ModuleMenu_TypeDataToBaseModuleRejections[TypeDataIndex];
|
|
FName TypeDataName = FName(*MenuMapper.ObjName);
|
|
TSet<FName>& Rejections = TypeDataModuleRejections.FindOrAdd(TypeDataName);
|
|
for (int32 BaseModuleIndex = 0; BaseModuleIndex < MenuMapper.InvalidObjNames.Num(); ++BaseModuleIndex)
|
|
{
|
|
FName BaseClassName = *MenuMapper.InvalidObjNames[BaseModuleIndex];
|
|
UClass* BaseClass = NULL;
|
|
for (int32 BaseClassIndex = 0; BaseClassIndex < ParticleModuleBaseClasses.Num(); ++BaseClassIndex)
|
|
{
|
|
if (ParticleModuleBaseClasses[BaseClassIndex]->GetFName() == BaseClassName)
|
|
{
|
|
BaseClass = ParticleModuleBaseClasses[BaseClassIndex];
|
|
break;
|
|
}
|
|
}
|
|
if (BaseClass)
|
|
{
|
|
for (int32 ClassIndex = 0; ClassIndex < ParticleModuleClasses.Num(); ++ClassIndex)
|
|
{
|
|
UClass* Class = ParticleModuleClasses[ClassIndex];
|
|
if (Class->IsChildOf(BaseClass))
|
|
{
|
|
Rejections.Add(Class->GetFName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 TypeDataIndex = 0; TypeDataIndex < ModuleMenu_TypeDataToSpecificModuleRejections.Num(); ++TypeDataIndex)
|
|
{
|
|
FModuleMenuMapper& MenuMapper = ModuleMenu_TypeDataToSpecificModuleRejections[TypeDataIndex];
|
|
FName TypeDataName = FName(*MenuMapper.ObjName);
|
|
TSet<FName>& Rejections = TypeDataModuleRejections.FindOrAdd(TypeDataName);
|
|
for (int32 ModuleIndex = 0; ModuleIndex < MenuMapper.InvalidObjNames.Num(); ++ModuleIndex)
|
|
{
|
|
Rejections.Add(FName(*MenuMapper.InvalidObjNames[ModuleIndex]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|