You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Window menu is now sectioned and labeled based on the current editor. There's now a local workspace root member in FTabManager and a workspace category in FAssetEditorToolkit (both are FWorkspaceItem objects). Individual editors attach their local category to the tab manager's local root. Workflow app modes have their own category members that are swapped out when the mode changes.
- Finally, the AssetEditorCategory of FWorkspaceMenuStructure has been removed entirely.
- Replaced the AddMenuSeparator() call in FTabManager::PopulateSpawnerMenu_Helper() with a section of the same title as the workspace category.
- Tab spawner menu entries for the local editor now properly show the icon of the associated tab. To accomplish this it was necessary to change FWorkflowTabFactory::TabIcon to be an FSlateIcon instead of an FSlateBrush*. All factory instances have been updated accordingly.
- Added & updated lots of icons! (those missing will be TTP'd)
- The nomad tab spawner section (named "General" in the menu) has been largely compressed into the Developer Tools submenu, which has also been organized into sections for readability.
- Unreal frontend options were also moved into a context menu within the General section
- Moved all experimental tools to their own section of the Window menu. When they're no longer experimental they should register as nomads in the appropriate category
- Undo history now under Edit menu
[CL 2324285 by Dan Hertzka in Main branch]
1907 lines
57 KiB
C++
1907 lines
57 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "StaticMeshEditorModule.h"
|
|
#include "AssetRegistryModule.h"
|
|
|
|
#include "StaticMeshEditor.h"
|
|
#include "SStaticMeshEditorViewport.h"
|
|
#include "StaticMeshEditorViewportClient.h"
|
|
#include "StaticMeshEditorTools.h"
|
|
#include "StaticMeshEditorActions.h"
|
|
|
|
#include "UnrealEd.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "ISocketManager.h"
|
|
#include "PreviewScene.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "BusyCursor.h"
|
|
#include "FbxMeshUtils.h"
|
|
#include "../Private/GeomFitUtils.h"
|
|
#include "EditorViewportCommands.h"
|
|
#include "Editor/UnrealEd/Private/ConvexDecompTool.h"
|
|
#include "Editor/ContentBrowser/Public/ContentBrowserModule.h"
|
|
#include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h"
|
|
|
|
#include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
|
|
#include "EngineAnalytics.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "StaticMeshEditor"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogStaticMeshEditor, Log, All);
|
|
|
|
const FName FStaticMeshEditor::ViewportTabId( TEXT( "StaticMeshEditor_Viewport" ) );
|
|
const FName FStaticMeshEditor::PropertiesTabId( TEXT( "StaticMeshEditor_Properties" ) );
|
|
const FName FStaticMeshEditor::SocketManagerTabId( TEXT( "StaticMeshEditor_SocketManager" ) );
|
|
const FName FStaticMeshEditor::CollisionTabId( TEXT( "StaticMeshEditor_Collision" ) );
|
|
|
|
|
|
void FStaticMeshEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager)
|
|
{
|
|
WorkspaceMenuCategory = TabManager->GetLocalWorkspaceMenuRoot()->AddGroup(LOCTEXT("WorkspaceMenu_StaticMeshEditor", "Static Mesh Editor"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(TabManager);
|
|
|
|
TabManager->RegisterTabSpawner( ViewportTabId, FOnSpawnTab::CreateSP(this, &FStaticMeshEditor::SpawnTab_Viewport) )
|
|
.SetDisplayName( LOCTEXT("ViewportTab", "Viewport") )
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
TabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FStaticMeshEditor::SpawnTab_Properties) )
|
|
.SetDisplayName( LOCTEXT("PropertiesTab", "Details") )
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
TabManager->RegisterTabSpawner( SocketManagerTabId, FOnSpawnTab::CreateSP(this, &FStaticMeshEditor::SpawnTab_SocketManager) )
|
|
.SetDisplayName( LOCTEXT("SocketManagerTab", "Socket Manager") )
|
|
.SetGroup(WorkspaceMenuCategoryRef);
|
|
|
|
TabManager->RegisterTabSpawner( CollisionTabId, FOnSpawnTab::CreateSP(this, &FStaticMeshEditor::SpawnTab_Collision) )
|
|
.SetDisplayName( LOCTEXT("CollisionTab", "Convex Decomposition") )
|
|
.SetGroup(WorkspaceMenuCategoryRef);
|
|
}
|
|
|
|
void FStaticMeshEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& TabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(TabManager);
|
|
|
|
TabManager->UnregisterTabSpawner( ViewportTabId );
|
|
TabManager->UnregisterTabSpawner( PropertiesTabId );
|
|
TabManager->UnregisterTabSpawner( SocketManagerTabId );
|
|
TabManager->UnregisterTabSpawner( CollisionTabId );
|
|
}
|
|
|
|
|
|
FStaticMeshEditor::~FStaticMeshEditor()
|
|
{
|
|
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
|
|
|
|
GEditor->UnregisterForUndo( this );
|
|
GEditor->OnObjectReimported().RemoveAll(this);
|
|
}
|
|
|
|
void FStaticMeshEditor::InitStaticMeshEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UStaticMesh* ObjectToEdit )
|
|
{
|
|
FReimportManager::Instance()->OnPostReimport().AddRaw(this, &FStaticMeshEditor::OnPostReimport);
|
|
|
|
// Support undo/redo
|
|
ObjectToEdit->SetFlags( RF_Transactional );
|
|
|
|
GEditor->RegisterForUndo( this );
|
|
|
|
// Register our commands. This will only register them if not previously registered
|
|
FStaticMeshEditorCommands::Register();
|
|
|
|
// Register to be notified when an object is reimported.
|
|
GEditor->OnObjectReimported().AddSP(this, &FStaticMeshEditor::OnObjectReimported);
|
|
|
|
BindCommands();
|
|
|
|
Viewport = SNew(SStaticMeshEditorViewport)
|
|
.StaticMeshEditor(SharedThis(this))
|
|
.ObjectToEdit(ObjectToEdit);
|
|
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bAllowSearch = true;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.bUpdatesFromSelection = false;
|
|
DetailsViewArgs.bHideActorNameArea = true;
|
|
DetailsViewArgs.bObjectsUseNameArea = false;
|
|
DetailsViewArgs.NotifyHook = this;
|
|
|
|
StaticMeshDetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs );
|
|
|
|
FOnGetDetailCustomizationInstance LayoutCustomStaticMeshProperties = FOnGetDetailCustomizationInstance::CreateSP( this, &FStaticMeshEditor::MakeStaticMeshDetails );
|
|
StaticMeshDetailsView->RegisterInstancedCustomPropertyLayout( UStaticMesh::StaticClass(), LayoutCustomStaticMeshProperties );
|
|
|
|
SetEditorMesh(ObjectToEdit);
|
|
|
|
BuildSubTools();
|
|
|
|
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_StaticMeshEditor_Layout_v3" )
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.1f)
|
|
->SetHideTabWell( true )
|
|
->AddTab(GetToolbarTabId(), ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.9f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.6f)
|
|
->AddTab(ViewportTabId, ETabState::OpenedTab) ->SetHideTabWell( true )
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.2f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.5f)
|
|
->AddTab(PropertiesTabId, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.5f)
|
|
->AddTab(SocketManagerTabId, ETabState::ClosedTab)
|
|
->AddTab(CollisionTabId, ETabState::ClosedTab)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
const bool bCreateDefaultStandaloneMenu = true;
|
|
const bool bCreateDefaultToolbar = true;
|
|
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, StaticMeshEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultToolbar, bCreateDefaultStandaloneMenu, ObjectToEdit );
|
|
|
|
ExtendMenu();
|
|
ExtendToolBar();
|
|
RegenerateMenusAndToolbars();
|
|
}
|
|
|
|
TSharedRef<IDetailCustomization> FStaticMeshEditor::MakeStaticMeshDetails()
|
|
{
|
|
TSharedRef<FStaticMeshDetails> NewDetails = MakeShareable( new FStaticMeshDetails( *this ) );
|
|
StaticMeshDetails = NewDetails;
|
|
return NewDetails;
|
|
}
|
|
|
|
void FStaticMeshEditor::ExtendMenu()
|
|
{
|
|
struct Local
|
|
{
|
|
static void FillEditMenu( FMenuBuilder& InMenuBuilder )
|
|
{
|
|
InMenuBuilder.BeginSection("Sockets", LOCTEXT("EditStaticMeshSockets", "Sockets"));
|
|
{
|
|
InMenuBuilder.AddMenuEntry( FGenericCommands::Get().Delete, "DeleteSocket", LOCTEXT("DeleteSocket", "Delete Socket"), LOCTEXT("DeleteSocketToolTip", "Deletes the selected socket from the mesh.") );
|
|
InMenuBuilder.AddMenuEntry( FGenericCommands::Get().Duplicate, "DuplicateSocket", LOCTEXT("DuplicateSocket", "Duplicate Socket"), LOCTEXT("DuplicateSocketToolTip", "Duplicates the selected socket.") );
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
}
|
|
|
|
static void FillMeshMenu( FMenuBuilder& InMenuBuilder )
|
|
{
|
|
// @todo mainframe: These menus, and indeed all menus like them, should be updated with extension points, plus expose public module
|
|
// access to extending the menus. They may also need to extend the command list, or be able to PUSH a command list of their own.
|
|
// If we decide to only allow PUSHING, then nothing else should be needed (happens by extender automatically). But if we want to
|
|
// augment the asset editor's existing command list, then we need to think about how to expose support for that.
|
|
|
|
InMenuBuilder.BeginSection("MeshFindSource");
|
|
{
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().FindSource);
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
|
|
InMenuBuilder.BeginSection("MeshChange");
|
|
{
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().ChangeMesh);
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
}
|
|
|
|
static void FillCollisionMenu( FMenuBuilder& InMenuBuilder )
|
|
{
|
|
InMenuBuilder.BeginSection("CollisionEditCollision");
|
|
{
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateSphereCollision);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateSphylCollision);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateBoxCollision);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP10X);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP10Y);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP10Z);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP18);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP26);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().ConvertBoxesToConvex);
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().RemoveCollision);
|
|
InMenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete, "DeleteCollision", LOCTEXT("DeleteCollision", "Delete Selected Collision"), LOCTEXT("DeleteCollisionToolTip", "Deletes the selected Collision from the mesh."));
|
|
InMenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate, "DuplicateCollision", LOCTEXT("DuplicateCollision", "Duplicate Selected Collision"), LOCTEXT("DuplicateCollisionToolTip", "Duplicates the selected Collision."));
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
|
|
InMenuBuilder.BeginSection("CollisionAutoConvexCollision");
|
|
{
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateAutoConvexCollision);
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
|
|
InMenuBuilder.BeginSection("CollisionCopy");
|
|
{
|
|
InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CopyCollisionFromSelectedMesh);
|
|
}
|
|
InMenuBuilder.EndSection();
|
|
}
|
|
|
|
static void GenerateMeshAndCollisionMenuBars( FMenuBarBuilder& InMenuBarBuilder)
|
|
{
|
|
InMenuBarBuilder.AddPullDownMenu(
|
|
LOCTEXT("StaticMeshEditorMeshMenu", "Mesh"),
|
|
LOCTEXT("StaticMeshEditorMeshMenu_ToolTip", "Opens a menu with commands for altering this mesh"),
|
|
FNewMenuDelegate::CreateStatic(&Local::FillMeshMenu),
|
|
"Mesh");
|
|
|
|
InMenuBarBuilder.AddPullDownMenu(
|
|
LOCTEXT("StaticMeshEditorCollisionMenu", "Collision"),
|
|
LOCTEXT("StaticMeshEditorCollisionMenu_ToolTip", "Opens a menu with commands for editing this mesh's collision"),
|
|
FNewMenuDelegate::CreateStatic(&Local::FillCollisionMenu),
|
|
"Collision");
|
|
}
|
|
};
|
|
|
|
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
|
|
|
|
MenuExtender->AddMenuExtension(
|
|
"EditHistory",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FMenuExtensionDelegate::CreateStatic( &Local::FillEditMenu ) );
|
|
|
|
MenuExtender->AddMenuBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FMenuBarExtensionDelegate::CreateStatic( &Local::GenerateMeshAndCollisionMenuBars )
|
|
);
|
|
|
|
AddMenuExtender(MenuExtender);
|
|
|
|
IStaticMeshEditorModule* StaticMeshEditorModule = &FModuleManager::LoadModuleChecked<IStaticMeshEditorModule>( "StaticMeshEditor" );
|
|
AddMenuExtender(StaticMeshEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
void FStaticMeshEditor::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
Collector.AddReferencedObject( StaticMesh );
|
|
}
|
|
|
|
TSharedRef<SDockTab> FStaticMeshEditor::SpawnTab_Viewport( const FSpawnTabArgs& Args )
|
|
{
|
|
check( Args.GetTabId() == ViewportTabId );
|
|
|
|
TSharedRef<SDockTab> SpawnedTab =
|
|
SNew(SDockTab)
|
|
.Label( LOCTEXT("StaticMeshViewport_TabTitle", "Viewport") )
|
|
[
|
|
Viewport.ToSharedRef()
|
|
];
|
|
|
|
Viewport->SetParentTab( SpawnedTab );
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> FStaticMeshEditor::SpawnTab_Properties( const FSpawnTabArgs& Args )
|
|
{
|
|
check( Args.GetTabId() == PropertiesTabId );
|
|
|
|
return SNew(SDockTab)
|
|
.Icon( FEditorStyle::GetBrush("StaticMeshEditor.Tabs.Properties") )
|
|
.Label( LOCTEXT("StaticMeshProperties_TabTitle", "Details") )
|
|
[
|
|
StaticMeshDetailsView.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FStaticMeshEditor::SpawnTab_SocketManager( const FSpawnTabArgs& Args )
|
|
{
|
|
check( Args.GetTabId() == SocketManagerTabId );
|
|
|
|
return SNew(SDockTab)
|
|
.Label( LOCTEXT("StaticMeshSocketManager_TabTitle", "Socket Manager") )
|
|
[
|
|
SocketManager.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FStaticMeshEditor::SpawnTab_Collision( const FSpawnTabArgs& Args )
|
|
{
|
|
check( Args.GetTabId() == CollisionTabId );
|
|
|
|
return SNew(SDockTab)
|
|
.Label( LOCTEXT("StaticMeshConvexDecomp_TabTitle", "Convex Decomposition") )
|
|
[
|
|
ConvexDecomposition.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
void FStaticMeshEditor::BindCommands()
|
|
{
|
|
const FStaticMeshEditorCommands& Commands = FStaticMeshEditorCommands::Get();
|
|
|
|
const TSharedRef<FUICommandList>& UICommandList = GetToolkitCommands();
|
|
|
|
UICommandList->MapAction( FGenericCommands::Get().Delete,
|
|
FExecuteAction::CreateSP( this, &FStaticMeshEditor::DeleteSelected ),
|
|
FCanExecuteAction::CreateSP(this, &FStaticMeshEditor::CanDeleteSelected));
|
|
|
|
UICommandList->MapAction( FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP( this, &FStaticMeshEditor::UndoAction ) );
|
|
|
|
UICommandList->MapAction( FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP( this, &FStaticMeshEditor::RedoAction ) );
|
|
|
|
UICommandList->MapAction(
|
|
FGenericCommands::Get().Duplicate,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::DuplicateSelected),
|
|
FCanExecuteAction::CreateSP(this, &FStaticMeshEditor::CanDuplicateSelected));
|
|
|
|
UICommandList->MapAction(
|
|
FGenericCommands::Get().Rename,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::RequestRenameSelectedSocket),
|
|
FCanExecuteAction::CreateSP(this, &FStaticMeshEditor::CanRenameSelected));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateDOP10X,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::GenerateKDop, KDopDir10X, (uint32)10));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateDOP10Y,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::GenerateKDop, KDopDir10Y, (uint32)10));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateDOP10Z,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::GenerateKDop, KDopDir10Z, (uint32)10));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateDOP18,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::GenerateKDop, KDopDir18, (uint32)18));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateDOP26,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::GenerateKDop, KDopDir26, (uint32)26));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateBoxCollision,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnCollisionBox));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateSphereCollision,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnCollisionSphere));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CreateSphylCollision,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnCollisionSphyl));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.RemoveCollision,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnRemoveCollision));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.ConvertBoxesToConvex,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnConvertBoxToConvexCollision));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.CopyCollisionFromSelectedMesh,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnCopyCollisionFromSelectedStaticMesh));
|
|
|
|
// Mesh menu
|
|
UICommandList->MapAction(
|
|
Commands.FindSource,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::ExecuteFindInExplorer),
|
|
FCanExecuteAction::CreateSP(this, &FStaticMeshEditor::CanExecuteSourceCommands));
|
|
|
|
UICommandList->MapAction(
|
|
Commands.ChangeMesh,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnChangeMesh));
|
|
|
|
// Collision Menu
|
|
UICommandList->MapAction(
|
|
Commands.CreateAutoConvexCollision,
|
|
FExecuteAction::CreateSP(this, &FStaticMeshEditor::OnConvexDecomposition));
|
|
}
|
|
|
|
void FStaticMeshEditor::ExtendToolBar()
|
|
{
|
|
struct Local
|
|
{
|
|
static void FillToolbar(FToolBarBuilder& ToolbarBuilder, TSharedPtr< class STextComboBox > UVChannelCombo, TSharedPtr< class STextComboBox > LODLevelCombo)
|
|
{
|
|
ToolbarBuilder.BeginSection("Realtime");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FEditorViewportCommands::Get().ToggleRealTime);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Command");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowSockets);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowWireframe);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowVertexColor);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowGrid);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowBounds);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowCollision);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowPivot);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowNormals);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowTangents);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetShowBinormals);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetDrawUVs);
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().SetDrawAdditionalData);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("UV");
|
|
{
|
|
ToolbarBuilder.AddWidget(UVChannelCombo.ToSharedRef());
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Camera");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().ResetCamera);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("LOD");
|
|
{
|
|
ToolbarBuilder.AddWidget(LODLevelCombo.ToSharedRef());
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
}
|
|
};
|
|
|
|
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
Viewport->GetCommandList(),
|
|
FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, UVChannelCombo, LODLevelCombo )
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
IStaticMeshEditorModule* StaticMeshEditorModule = &FModuleManager::LoadModuleChecked<IStaticMeshEditorModule>( "StaticMeshEditor" );
|
|
AddToolbarExtender(StaticMeshEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
void FStaticMeshEditor::BuildSubTools()
|
|
{
|
|
FSimpleDelegate OnSocketSelectionChanged = FSimpleDelegate::CreateSP( SharedThis(this), &FStaticMeshEditor::OnSocketSelectionChanged );
|
|
|
|
SocketManager = ISocketManager::CreateSocketManager( SharedThis(this) , OnSocketSelectionChanged );
|
|
|
|
SAssignNew( ConvexDecomposition, SConvexDecomposition )
|
|
.StaticMeshEditorPtr(SharedThis(this));
|
|
|
|
// Build toolbar widgets
|
|
UVChannelCombo = SNew(STextComboBox)
|
|
.OptionsSource(&UVChannels)
|
|
.OnSelectionChanged(this, &FStaticMeshEditor::ComboBoxSelectionChanged)
|
|
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() );
|
|
|
|
if(UVChannels.IsValidIndex(0))
|
|
{
|
|
UVChannelCombo->SetSelectedItem(UVChannels[0]);
|
|
}
|
|
|
|
LODLevelCombo = SNew(STextComboBox)
|
|
.OptionsSource(&LODLevels)
|
|
.OnSelectionChanged(this, &FStaticMeshEditor::LODLevelsSelectionChanged)
|
|
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() );
|
|
|
|
if(LODLevels.IsValidIndex(0))
|
|
{
|
|
LODLevelCombo->SetSelectedItem(LODLevels[0]);
|
|
}
|
|
}
|
|
|
|
FName FStaticMeshEditor::GetToolkitFName() const
|
|
{
|
|
return FName("StaticMeshEditor");
|
|
}
|
|
|
|
FText FStaticMeshEditor::GetBaseToolkitName() const
|
|
{
|
|
return LOCTEXT("AppLabel", "StaticMesh Editor");
|
|
}
|
|
|
|
FString FStaticMeshEditor::GetWorldCentricTabPrefix() const
|
|
{
|
|
return LOCTEXT("WorldCentricTabPrefix", "StaticMesh ").ToString();
|
|
}
|
|
|
|
FLinearColor FStaticMeshEditor::GetWorldCentricTabColorScale() const
|
|
{
|
|
return FLinearColor( 0.3f, 0.2f, 0.5f, 0.5f );
|
|
}
|
|
|
|
UStaticMeshComponent* FStaticMeshEditor::GetStaticMeshComponent() const
|
|
{
|
|
return Viewport->GetStaticMeshComponent();
|
|
}
|
|
|
|
void FStaticMeshEditor::SetSelectedSocket(UStaticMeshSocket* InSelectedSocket)
|
|
{
|
|
SocketManager->SetSelectedSocket(InSelectedSocket);
|
|
}
|
|
|
|
UStaticMeshSocket* FStaticMeshEditor::GetSelectedSocket() const
|
|
{
|
|
check(SocketManager.IsValid());
|
|
|
|
return SocketManager->GetSelectedSocket();
|
|
}
|
|
|
|
void FStaticMeshEditor::DuplicateSelectedSocket()
|
|
{
|
|
SocketManager->DuplicateSelectedSocket();
|
|
}
|
|
|
|
void FStaticMeshEditor::RequestRenameSelectedSocket()
|
|
{
|
|
SocketManager->RequestRenameSelectedSocket();
|
|
}
|
|
|
|
bool FStaticMeshEditor::IsPrimValid(const FPrimData& InPrimData) const
|
|
{
|
|
if (StaticMesh->BodySetup)
|
|
{
|
|
const FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
switch (InPrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
return AggGeom->SphereElems.IsValidIndex(InPrimData.PrimIndex);
|
|
case KPT_Box:
|
|
return AggGeom->BoxElems.IsValidIndex(InPrimData.PrimIndex);
|
|
case KPT_Sphyl:
|
|
return AggGeom->SphylElems.IsValidIndex(InPrimData.PrimIndex);
|
|
case KPT_Convex:
|
|
return AggGeom->ConvexElems.IsValidIndex(InPrimData.PrimIndex);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FStaticMeshEditor::HasSelectedPrims() const
|
|
{
|
|
return (SelectedPrims.Num() > 0 ? true : false);
|
|
}
|
|
|
|
void FStaticMeshEditor::AddSelectedPrim(const FPrimData& InPrimData, bool bClearSelection)
|
|
{
|
|
check(IsPrimValid(InPrimData));
|
|
|
|
// Enable collision, if not already
|
|
if( !Viewport->GetViewportClient().IsSetShowWireframeCollisionChecked() )
|
|
{
|
|
Viewport->GetViewportClient().SetShowWireframeCollision();
|
|
}
|
|
|
|
if( bClearSelection )
|
|
{
|
|
ClearSelectedPrims();
|
|
}
|
|
SelectedPrims.Add(InPrimData);
|
|
}
|
|
|
|
void FStaticMeshEditor::RemoveSelectedPrim(const FPrimData& InPrimData)
|
|
{
|
|
SelectedPrims.Remove(InPrimData);
|
|
}
|
|
|
|
void FStaticMeshEditor::RemoveInvalidPrims()
|
|
{
|
|
for (int32 PrimIdx = SelectedPrims.Num() - 1; PrimIdx >= 0; PrimIdx--)
|
|
{
|
|
FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
if (!IsPrimValid(PrimData))
|
|
{
|
|
SelectedPrims.RemoveAt(PrimIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FStaticMeshEditor::IsSelectedPrim(const FPrimData& InPrimData) const
|
|
{
|
|
return SelectedPrims.Contains(InPrimData);
|
|
}
|
|
|
|
void FStaticMeshEditor::ClearSelectedPrims()
|
|
{
|
|
SelectedPrims.Empty();
|
|
}
|
|
|
|
void FStaticMeshEditor::DuplicateSelectedPrims(const FVector* InOffset)
|
|
{
|
|
if (SelectedPrims.Num() > 0)
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_DuplicateSelectedPrims", "Duplicate Collision"));
|
|
StaticMesh->BodySetup->Modify();
|
|
|
|
//Clear the cache (PIE may have created some data), create new GUID
|
|
StaticMesh->BodySetup->InvalidatePhysicsData();
|
|
|
|
for (int32 PrimIdx = 0; PrimIdx < SelectedPrims.Num(); PrimIdx++)
|
|
{
|
|
FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
check(IsPrimValid(PrimData));
|
|
switch (PrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
{
|
|
const FKSphereElem SphereElem = AggGeom->SphereElems[PrimData.PrimIndex];
|
|
PrimData.PrimIndex = AggGeom->SphereElems.Add(SphereElem);
|
|
}
|
|
break;
|
|
case KPT_Box:
|
|
{
|
|
const FKBoxElem BoxElem = AggGeom->BoxElems[PrimData.PrimIndex];
|
|
PrimData.PrimIndex = AggGeom->BoxElems.Add(BoxElem);
|
|
}
|
|
break;
|
|
case KPT_Sphyl:
|
|
{
|
|
const FKSphylElem SphylElem = AggGeom->SphylElems[PrimData.PrimIndex];
|
|
PrimData.PrimIndex = AggGeom->SphylElems.Add(SphylElem);
|
|
}
|
|
break;
|
|
case KPT_Convex:
|
|
{
|
|
const FKConvexElem ConvexElem = AggGeom->ConvexElems[PrimData.PrimIndex];
|
|
PrimData.PrimIndex = AggGeom->ConvexElems.Add(ConvexElem);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// If specified, offset the duplicate by a specific amount
|
|
if (InOffset)
|
|
{
|
|
FTransform PrimTransform = GetPrimTransform(PrimData);
|
|
FVector PrimLocation = PrimTransform.GetLocation();
|
|
PrimLocation += *InOffset;
|
|
PrimTransform.SetLocation(PrimLocation);
|
|
SetPrimTransform(PrimData, PrimTransform);
|
|
}
|
|
}
|
|
|
|
// refresh collision change back to staticmesh components
|
|
RefreshCollisionChange(StaticMesh);
|
|
|
|
GEditor->EndTransaction();
|
|
|
|
// Mark staticmesh as dirty, to help make sure it gets saved.
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Update views/property windows
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::TranslateSelectedPrims(const FVector& InDrag)
|
|
{
|
|
for (int32 PrimIdx = 0; PrimIdx < SelectedPrims.Num(); PrimIdx++)
|
|
{
|
|
const FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
FTransform PrimTransform = GetPrimTransform(PrimData);
|
|
|
|
FVector PrimLocation = PrimTransform.GetLocation();
|
|
PrimLocation += InDrag;
|
|
PrimTransform.SetLocation(PrimLocation);
|
|
|
|
SetPrimTransform(PrimData, PrimTransform);
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::RotateSelectedPrims(const FRotator& InRot)
|
|
{
|
|
const FQuat DeltaQ = InRot.Quaternion();
|
|
|
|
for (int32 PrimIdx = 0; PrimIdx < SelectedPrims.Num(); PrimIdx++)
|
|
{
|
|
const FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
FTransform PrimTransform = GetPrimTransform(PrimData);
|
|
|
|
FRotator ActorRotWind, ActorRotRem;
|
|
PrimTransform.Rotator().GetWindingAndRemainder(ActorRotWind, ActorRotRem);
|
|
|
|
const FQuat ActorQ = ActorRotRem.Quaternion();
|
|
FRotator NewActorRotRem = FRotator(DeltaQ * ActorQ);
|
|
NewActorRotRem.Normalize();
|
|
PrimTransform.SetRotation(NewActorRotRem.Quaternion());
|
|
|
|
SetPrimTransform(PrimData, PrimTransform);
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::ScaleSelectedPrims(const FVector& InScale)
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
FVector ModifiedScale = InScale;
|
|
if (GEditor->UsePercentageBasedScaling())
|
|
{
|
|
ModifiedScale = InScale * ((GEditor->GetScaleGridSize() / 100.0f) / GEditor->GetGridSize());
|
|
}
|
|
|
|
for (int32 PrimIdx = 0; PrimIdx < SelectedPrims.Num(); PrimIdx++)
|
|
{
|
|
const FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
check(IsPrimValid(PrimData));
|
|
switch (PrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
AggGeom->SphereElems[PrimData.PrimIndex].ScaleElem(InScale, MinPrimSize);
|
|
break;
|
|
case KPT_Box:
|
|
AggGeom->BoxElems[PrimData.PrimIndex].ScaleElem(InScale, MinPrimSize);
|
|
break;
|
|
case KPT_Sphyl:
|
|
AggGeom->SphylElems[PrimData.PrimIndex].ScaleElem(InScale, MinPrimSize);
|
|
break;
|
|
case KPT_Convex:
|
|
AggGeom->ConvexElems[PrimData.PrimIndex].ScaleElem(ModifiedScale, MinPrimSize);
|
|
break;
|
|
}
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
|
|
bool FStaticMeshEditor::CalcSelectedPrimsAABB(FBox &OutBox) const
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
for (int32 PrimIdx = 0; PrimIdx < SelectedPrims.Num(); PrimIdx++)
|
|
{
|
|
const FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
check(IsPrimValid(PrimData));
|
|
switch (PrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
OutBox += AggGeom->SphereElems[PrimData.PrimIndex].CalcAABB(FTransform::Identity, 1.f);
|
|
break;
|
|
case KPT_Box:
|
|
OutBox += AggGeom->BoxElems[PrimData.PrimIndex].CalcAABB(FTransform::Identity, 1.f);
|
|
break;
|
|
case KPT_Sphyl:
|
|
OutBox += AggGeom->SphylElems[PrimData.PrimIndex].CalcAABB(FTransform::Identity, 1.f);
|
|
break;
|
|
case KPT_Convex:
|
|
OutBox += AggGeom->ConvexElems[PrimData.PrimIndex].CalcAABB(FTransform::Identity, FVector(1.f));
|
|
break;
|
|
}
|
|
}
|
|
return HasSelectedPrims();
|
|
}
|
|
|
|
bool FStaticMeshEditor::GetLastSelectedPrimTransform(FTransform& OutTransform) const
|
|
{
|
|
if (SelectedPrims.Num() > 0)
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
const FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
const FPrimData& PrimData = SelectedPrims.Last();
|
|
|
|
check(IsPrimValid(PrimData));
|
|
switch (PrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
OutTransform = AggGeom->SphereElems[PrimData.PrimIndex].GetTransform();
|
|
break;
|
|
case KPT_Box:
|
|
OutTransform = AggGeom->BoxElems[PrimData.PrimIndex].GetTransform();
|
|
break;
|
|
case KPT_Sphyl:
|
|
OutTransform = AggGeom->SphylElems[PrimData.PrimIndex].GetTransform();
|
|
break;
|
|
case KPT_Convex:
|
|
OutTransform = AggGeom->ConvexElems[PrimData.PrimIndex].GetTransform();
|
|
break;
|
|
}
|
|
}
|
|
return HasSelectedPrims();
|
|
}
|
|
|
|
FTransform FStaticMeshEditor::GetPrimTransform(const FPrimData& InPrimData) const
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
const FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
check(IsPrimValid(InPrimData));
|
|
switch (InPrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
return AggGeom->SphereElems[InPrimData.PrimIndex].GetTransform();
|
|
case KPT_Box:
|
|
return AggGeom->BoxElems[InPrimData.PrimIndex].GetTransform();
|
|
case KPT_Sphyl:
|
|
return AggGeom->SphylElems[InPrimData.PrimIndex].GetTransform();
|
|
case KPT_Convex:
|
|
return AggGeom->ConvexElems[InPrimData.PrimIndex].GetTransform();
|
|
}
|
|
return FTransform::Identity;
|
|
}
|
|
|
|
void FStaticMeshEditor::SetPrimTransform(const FPrimData& InPrimData, const FTransform& InPrimTransform) const
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
check(IsPrimValid(InPrimData));
|
|
switch (InPrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
AggGeom->SphereElems[InPrimData.PrimIndex].SetTransform(InPrimTransform);
|
|
break;
|
|
case KPT_Box:
|
|
AggGeom->BoxElems[InPrimData.PrimIndex].SetTransform(InPrimTransform);
|
|
break;
|
|
case KPT_Sphyl:
|
|
AggGeom->SphylElems[InPrimData.PrimIndex].SetTransform(InPrimTransform);
|
|
break;
|
|
case KPT_Convex:
|
|
AggGeom->ConvexElems[InPrimData.PrimIndex].SetTransform(InPrimTransform);
|
|
break;
|
|
}
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
|
|
bool FStaticMeshEditor::OverlapsExistingPrim(const FPrimData& InPrimData) const
|
|
{
|
|
check(StaticMesh->BodySetup);
|
|
|
|
const FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
// Assume that if the transform of the prim is the same, then it overlaps (FKConvexElem doesn't have an operator==, and no shape takes tolerances into account)
|
|
check(IsPrimValid(InPrimData));
|
|
switch (InPrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
{
|
|
const FKSphereElem InSphereElem = AggGeom->SphereElems[InPrimData.PrimIndex];
|
|
const FTransform InElemTM = InSphereElem.GetTransform();
|
|
for (int32 i = 0; i < AggGeom->SphereElems.Num(); ++i)
|
|
{
|
|
if( i == InPrimData.PrimIndex )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FKSphereElem& SphereElem = AggGeom->SphereElems[i];
|
|
const FTransform ElemTM = SphereElem.GetTransform();
|
|
if( InElemTM.Equals(ElemTM) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case KPT_Box:
|
|
{
|
|
const FKBoxElem InBoxElem = AggGeom->BoxElems[InPrimData.PrimIndex];
|
|
const FTransform InElemTM = InBoxElem.GetTransform();
|
|
for (int32 i = 0; i < AggGeom->BoxElems.Num(); ++i)
|
|
{
|
|
if( i == InPrimData.PrimIndex )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FKBoxElem& BoxElem = AggGeom->BoxElems[i];
|
|
const FTransform ElemTM = BoxElem.GetTransform();
|
|
if( InElemTM.Equals(ElemTM) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case KPT_Sphyl:
|
|
{
|
|
const FKSphylElem InSphylElem = AggGeom->SphylElems[InPrimData.PrimIndex];
|
|
const FTransform InElemTM = InSphylElem.GetTransform();
|
|
for (int32 i = 0; i < AggGeom->SphylElems.Num(); ++i)
|
|
{
|
|
if( i == InPrimData.PrimIndex )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FKSphylElem& SphylElem = AggGeom->SphylElems[i];
|
|
const FTransform ElemTM = SphylElem.GetTransform();
|
|
if( InElemTM.Equals(ElemTM) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case KPT_Convex:
|
|
{
|
|
const FKConvexElem InConvexElem = AggGeom->ConvexElems[InPrimData.PrimIndex];
|
|
const FTransform InElemTM = InConvexElem.GetTransform();
|
|
for (int32 i = 0; i < AggGeom->ConvexElems.Num(); ++i)
|
|
{
|
|
if( i == InPrimData.PrimIndex )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FKConvexElem& ConvexElem = AggGeom->ConvexElems[i];
|
|
const FTransform ElemTM = ConvexElem.GetTransform();
|
|
if( InElemTM.Equals(ElemTM) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FStaticMeshEditor::RefreshTool()
|
|
{
|
|
int32 NumLODs = StaticMesh->GetNumLODs();
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
UpdateLODStats(LODIndex);
|
|
}
|
|
|
|
bool bForceRefresh = true;
|
|
StaticMeshDetailsView->SetObject( StaticMesh, bForceRefresh );
|
|
|
|
RegenerateLODComboList();
|
|
RegenerateUVChannelComboList();
|
|
RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::RefreshViewport()
|
|
{
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::RegenerateLODComboList()
|
|
{
|
|
if( StaticMesh->RenderData )
|
|
{
|
|
int32 OldLOD = GetCurrentLODLevel();
|
|
|
|
NumLODLevels = StaticMesh->RenderData->LODResources.Num();
|
|
|
|
// Fill out the LOD level combo.
|
|
LODLevels.Empty();
|
|
LODLevels.Add( MakeShareable( new FString( LOCTEXT("AutoLOD", "Auto LOD").ToString() ) ) );
|
|
LODLevels.Add( MakeShareable( new FString( LOCTEXT("BaseLOD", "Base LOD").ToString() ) ) );
|
|
for(int32 LODLevelID = 1; LODLevelID < NumLODLevels; ++LODLevelID)
|
|
{
|
|
LODLevels.Add( MakeShareable( new FString( FString::Printf(*LOCTEXT("LODLevel_ID", "LOD Level %d").ToString(), LODLevelID ) ) ) );
|
|
}
|
|
|
|
if( LODLevelCombo.IsValid() )
|
|
{
|
|
LODLevelCombo->RefreshOptions();
|
|
|
|
if( OldLOD < LODLevels.Num() )
|
|
{
|
|
LODLevelCombo->SetSelectedItem(LODLevels[OldLOD]);
|
|
}
|
|
else
|
|
{
|
|
LODLevelCombo->SetSelectedItem(LODLevels[0]);
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumLODLevels = 0;
|
|
LODLevels.Empty();
|
|
LODLevels.Add( MakeShareable( new FString( LOCTEXT("AutoLOD", "Auto LOD").ToString() ) ) );
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::RegenerateUVChannelComboList()
|
|
{
|
|
int32 OldUVChannel = GetCurrentUVChannel();
|
|
|
|
// Fill out the UV channels combo.
|
|
UVChannels.Empty();
|
|
int32 MaxUVChannels = FMath::Max<int32>(GetNumUVChannels(),1);
|
|
for(int32 UVChannelID = 0; UVChannelID < MaxUVChannels; ++UVChannelID)
|
|
{
|
|
UVChannels.Add( MakeShareable( new FString( FText::Format( LOCTEXT("UVChannel_ID", "UV Channel {0}"), FText::AsNumber( UVChannelID ) ).ToString() ) ) );
|
|
}
|
|
|
|
if(UVChannelCombo.IsValid())
|
|
{
|
|
UVChannelCombo->RefreshOptions();
|
|
if( OldUVChannel >= 0 && OldUVChannel < GetNumUVChannels() )
|
|
{
|
|
UVChannelCombo->SetSelectedItem(UVChannels[OldUVChannel]);
|
|
}
|
|
else
|
|
{
|
|
UVChannelCombo->SetSelectedItem(UVChannels[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FStaticMeshEditor::UpdateLODStats(int32 CurrentLOD)
|
|
{
|
|
NumTriangles[CurrentLOD] = 0;
|
|
NumVertices[CurrentLOD] = 0;
|
|
NumUVChannels[CurrentLOD] = 0;
|
|
NumLODLevels = 0;
|
|
|
|
if( StaticMesh->RenderData )
|
|
{
|
|
NumLODLevels = StaticMesh->RenderData->LODResources.Num();
|
|
if (CurrentLOD >= 0 && CurrentLOD < NumLODLevels)
|
|
{
|
|
FStaticMeshLODResources& LODModel = StaticMesh->RenderData->LODResources[CurrentLOD];
|
|
NumTriangles[CurrentLOD] = LODModel.GetNumTriangles();
|
|
NumVertices[CurrentLOD] = LODModel.GetNumVertices();
|
|
NumUVChannels[CurrentLOD] = LODModel.VertexBuffer.GetNumTexCoords();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::ComboBoxSelectionChanged( TSharedPtr<FString> NewSelection, ESelectInfo::Type /*SelectInfo*/ )
|
|
{
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::LODLevelsSelectionChanged( TSharedPtr<FString> NewSelection, ESelectInfo::Type /*SelectInfo*/ )
|
|
{
|
|
int32 CurrentLOD = GetCurrentLODLevel();
|
|
|
|
UpdateLODStats( CurrentLOD > 0? CurrentLOD - 1 : 0 );
|
|
|
|
Viewport->ForceLODLevel(CurrentLOD);
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetCurrentUVChannel()
|
|
{
|
|
int32 Index = 0;
|
|
UVChannels.Find(UVChannelCombo->GetSelectedItem(), Index);
|
|
|
|
return Index;
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetCurrentLODLevel()
|
|
{
|
|
int32 Index = 0;
|
|
LODLevels.Find(LODLevelCombo->GetSelectedItem(), Index);
|
|
|
|
return Index;
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetCurrentLODIndex()
|
|
{
|
|
int32 Index = LODLevels.Find(LODLevelCombo->GetSelectedItem());
|
|
|
|
return Index == 0? 0 : Index - 1;
|
|
}
|
|
|
|
void FStaticMeshEditor::GenerateKDop(const FVector* Directions, uint32 NumDirections)
|
|
{
|
|
TArray<FVector> DirArray;
|
|
for(uint32 DirectionIndex = 0;DirectionIndex < NumDirections;DirectionIndex++)
|
|
{
|
|
DirArray.Add(Directions[DirectionIndex]);
|
|
}
|
|
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_GenerateKDop", "Create Convex Collision"));
|
|
const int32 PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
GEditor->EndTransaction();
|
|
if (PrimIndex != INDEX_NONE)
|
|
{
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.Collision"), TEXT("Type"), TEXT("KDop Collision"));
|
|
}
|
|
const FPrimData PrimData = FPrimData(KPT_Convex, PrimIndex);
|
|
ClearSelectedPrims();
|
|
AddSelectedPrim(PrimData, true);
|
|
while( OverlapsExistingPrim(PrimData) )
|
|
{
|
|
TranslateSelectedPrims(OverlapNudge);
|
|
}
|
|
}
|
|
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnCollisionBox()
|
|
{
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_OnCollisionBox", "Create Box Collision"));
|
|
const int32 PrimIndex = GenerateBoxAsSimpleCollision(StaticMesh);
|
|
GEditor->EndTransaction();
|
|
if (PrimIndex != INDEX_NONE)
|
|
{
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.Collision"), TEXT("Type"), TEXT("Box Collision"));
|
|
}
|
|
const FPrimData PrimData = FPrimData(KPT_Box, PrimIndex);
|
|
ClearSelectedPrims();
|
|
AddSelectedPrim(PrimData, true);
|
|
while( OverlapsExistingPrim(PrimData) )
|
|
{
|
|
TranslateSelectedPrims(OverlapNudge);
|
|
}
|
|
}
|
|
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnCollisionSphere()
|
|
{
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_OnCollisionSphere", "Create Sphere Collision"));
|
|
const int32 PrimIndex = GenerateSphereAsSimpleCollision(StaticMesh);
|
|
GEditor->EndTransaction();
|
|
if (PrimIndex != INDEX_NONE)
|
|
{
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.Collision"), TEXT("Type"), TEXT("Sphere Collision"));
|
|
}
|
|
const FPrimData PrimData = FPrimData(KPT_Sphere, PrimIndex);
|
|
ClearSelectedPrims();
|
|
AddSelectedPrim(PrimData, true);
|
|
while( OverlapsExistingPrim(PrimData) )
|
|
{
|
|
TranslateSelectedPrims(OverlapNudge);
|
|
}
|
|
}
|
|
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnCollisionSphyl()
|
|
{
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_OnCollisionSphyl", "Create Capsule Collision"));
|
|
const int32 PrimIndex = GenerateSphylAsSimpleCollision(StaticMesh);
|
|
GEditor->EndTransaction();
|
|
if (PrimIndex != INDEX_NONE)
|
|
{
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.Collision"), TEXT("Type"), TEXT("Capsule Collision"));
|
|
}
|
|
const FPrimData PrimData = FPrimData(KPT_Sphyl, PrimIndex);
|
|
ClearSelectedPrims();
|
|
AddSelectedPrim(PrimData, true);
|
|
while( OverlapsExistingPrim(PrimData) )
|
|
{
|
|
TranslateSelectedPrims(OverlapNudge);
|
|
}
|
|
}
|
|
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnRemoveCollision(void)
|
|
{
|
|
// If we have a collision model for this staticmesh, ask if we want to replace it.
|
|
UBodySetup* BS = StaticMesh->BodySetup;
|
|
if (BS != NULL && (BS->AggGeom.GetElementCount() > 0))
|
|
{
|
|
int32 ShouldReplace = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("RemoveCollisionPrompt", "Are you sure you want to remove all the collision meshes?"));
|
|
if (ShouldReplace == EAppReturnType::Yes)
|
|
{
|
|
ClearSelectedPrims();
|
|
|
|
// Make sure rendering is done - so we are not changing data being used by collision drawing.
|
|
FlushRenderingCommands();
|
|
|
|
StaticMesh->BodySetup->RemoveSimpleCollision();
|
|
|
|
// refresh collision change back to staticmesh components
|
|
RefreshCollisionChange(StaticMesh);
|
|
|
|
// Mark staticmesh as dirty, to help make sure it gets saved.
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Update views/property windows
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Util for adding vertex to an array if it is not already present. */
|
|
static void AddVertexIfNotPresent(TArray<FVector>& Vertices, const FVector& NewVertex)
|
|
{
|
|
bool bIsPresent = false;
|
|
|
|
for(int32 i=0; i<Vertices.Num(); i++)
|
|
{
|
|
float diffSqr = (NewVertex - Vertices[i]).SizeSquared();
|
|
if(diffSqr < 0.01f * 0.01f)
|
|
{
|
|
bIsPresent = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!bIsPresent)
|
|
{
|
|
Vertices.Add(NewVertex);
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::CreateBoxVertsFromBoxCollision(const FKBoxElem& BoxElem, TArray<FVector>& Verts, float Scale)
|
|
{
|
|
FVector B[2], P, Q, Radii;
|
|
|
|
// X,Y,Z member variables are LENGTH not RADIUS
|
|
Radii.X = Scale*0.5f*BoxElem.X;
|
|
Radii.Y = Scale*0.5f*BoxElem.Y;
|
|
Radii.Z = Scale*0.5f*BoxElem.Z;
|
|
|
|
B[0] = Radii; // max
|
|
B[1] = -1.0f * Radii; // min
|
|
|
|
FTransform BoxElemTM = BoxElem.GetTransform();
|
|
|
|
for( int32 i=0; i<2; i++ )
|
|
{
|
|
for( int32 j=0; j<2; j++ )
|
|
{
|
|
P.X=B[i].X; Q.X=B[i].X;
|
|
P.Y=B[j].Y; Q.Y=B[j].Y;
|
|
P.Z=B[0].Z; Q.Z=B[1].Z;
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(P));
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(Q));
|
|
|
|
P.Y=B[i].Y; Q.Y=B[i].Y;
|
|
P.Z=B[j].Z; Q.Z=B[j].Z;
|
|
P.X=B[0].X; Q.X=B[1].X;
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(P));
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(Q));
|
|
|
|
P.Z=B[i].Z; Q.Z=B[i].Z;
|
|
P.X=B[j].X; Q.X=B[j].X;
|
|
P.Y=B[0].Y; Q.Y=B[1].Y;
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(P));
|
|
AddVertexIfNotPresent(Verts, BoxElemTM.TransformPosition(Q));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::OnConvertBoxToConvexCollision()
|
|
{
|
|
// If we have a collision model for this staticmesh, ask if we want to replace it.
|
|
if (StaticMesh->BodySetup != NULL)
|
|
{
|
|
int32 ShouldReplace = FMessageDialog::Open( EAppMsgType::YesNo, LOCTEXT("ConvertBoxCollisionPrompt", "Are you sure you want to convert all box collision?") );
|
|
if (ShouldReplace == EAppReturnType::Yes)
|
|
{
|
|
UBodySetup* BodySetup = StaticMesh->BodySetup;
|
|
|
|
int32 NumBoxElems = BodySetup->AggGeom.BoxElems.Num();
|
|
if (NumBoxElems > 0)
|
|
{
|
|
ClearSelectedPrims();
|
|
|
|
// Make sure rendering is done - so we are not changing data being used by collision drawing.
|
|
FlushRenderingCommands();
|
|
|
|
FKConvexElem* NewConvexColl = NULL;
|
|
|
|
//For each box elem, calculate the new convex collision representation
|
|
//Stored in a temp array so we can undo on failure.
|
|
TArray<FKConvexElem> TempArray;
|
|
|
|
for (int32 i=0; i<NumBoxElems; i++)
|
|
{
|
|
const FKBoxElem& BoxColl = BodySetup->AggGeom.BoxElems[i];
|
|
|
|
//Create a new convex collision element
|
|
NewConvexColl = new(TempArray) FKConvexElem();
|
|
NewConvexColl->Reset();
|
|
|
|
//Fill the convex verts from the box elem collision and generate the convex hull
|
|
CreateBoxVertsFromBoxCollision(BoxColl, NewConvexColl->VertexData, 1.0f);
|
|
NewConvexColl->UpdateElemBox();
|
|
}
|
|
|
|
//Clear the cache (PIE may have created some data), create new GUID
|
|
BodySetup->InvalidatePhysicsData();
|
|
|
|
//Copy the new data into the static mesh
|
|
BodySetup->AggGeom.ConvexElems.Append(TempArray);
|
|
|
|
//Clear out what we just replaced
|
|
BodySetup->AggGeom.BoxElems.Empty();
|
|
|
|
BodySetup->CreatePhysicsMeshes();
|
|
|
|
// Select the new prims
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
for (int32 i = 0; i < NumBoxElems; ++i)
|
|
{
|
|
AddSelectedPrim(FPrimData(KPT_Convex, (AggGeom->ConvexElems.Num() - (i+1))), false);
|
|
}
|
|
|
|
// Mark static mesh as dirty, to help make sure it gets saved.
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Update views/property windows
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::OnCopyCollisionFromSelectedStaticMesh()
|
|
{
|
|
// Find currently selected mesh from content browser
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
TArray<FAssetData> SelectedAssetData;
|
|
ContentBrowserModule.Get().GetSelectedAssets(SelectedAssetData);
|
|
UStaticMesh * SelectedMesh = NULL;
|
|
if ( SelectedAssetData.Num() > 0 )
|
|
{
|
|
// find the first staticmesh we can find
|
|
for ( auto AssetIt = SelectedAssetData.CreateConstIterator(); AssetIt; ++AssetIt )
|
|
{
|
|
const FAssetData& Asset = (*AssetIt);
|
|
// if staticmesh
|
|
if ( Asset.GetClass() == UStaticMesh::StaticClass() )
|
|
{
|
|
SelectedMesh = Cast<UStaticMesh>(Asset.GetAsset());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have a collision model for this staticmesh, ask if we want to replace it.
|
|
if (SelectedMesh && SelectedMesh != StaticMesh &&
|
|
SelectedMesh->BodySetup != NULL)
|
|
{
|
|
int32 ShouldReplace = FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( LOCTEXT("CopyCollisionFromMeshPrompt", "Are you sure you want to copy collision from {0}?"), FText::FromString( SelectedMesh->GetName() ) ) );
|
|
if (ShouldReplace == EAppReturnType::Yes)
|
|
{
|
|
UBodySetup* BodySetup = StaticMesh->BodySetup;
|
|
|
|
ClearSelectedPrims();
|
|
|
|
// Make sure rendering is done - so we are not changing data being used by collision drawing.
|
|
FlushRenderingCommands();
|
|
|
|
// Copy body properties from
|
|
BodySetup->CopyBodyPropertiesFrom(SelectedMesh->BodySetup);
|
|
|
|
// Enable collision, if not already
|
|
if( !Viewport->GetViewportClient().IsSetShowWireframeCollisionChecked() )
|
|
{
|
|
Viewport->GetViewportClient().SetShowWireframeCollision();
|
|
}
|
|
|
|
//Invalidate physics data
|
|
BodySetup->InvalidatePhysicsData();
|
|
|
|
// Mark static mesh as dirty, to help make sure it gets saved.
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Redraw level editor viewports, in case the asset's collision is visible in a viewport and the viewport isn't set to realtime.
|
|
// Note: This could be more intelligent and only trigger a redraw if the asset is referenced in the world.
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
|
|
// Update views/property windows
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo Info( LOCTEXT("InvalidMeshSelectionCollisionCopyFromPrompt", "Invalid asset to copy collision from. Select valid StaticMesh in Content Browser.") );
|
|
Info.ExpireDuration = 2.0f;
|
|
FSlateNotificationManager::Get().AddNotification( Info );
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::SetEditorMesh(UStaticMesh* InStaticMesh)
|
|
{
|
|
StaticMesh = InStaticMesh;
|
|
|
|
//Init stat arrays. A static mesh can have up to three level of details beyond the base mesh.
|
|
const int32 ArraySize = 4;
|
|
NumVertices.Empty(ArraySize);
|
|
NumVertices.AddZeroed(ArraySize);
|
|
NumTriangles.Empty(ArraySize);
|
|
NumTriangles.AddZeroed(ArraySize);
|
|
NumUVChannels.Empty(ArraySize);
|
|
NumUVChannels.AddZeroed(ArraySize);
|
|
|
|
// Always default the LOD to 0 when setting the mesh.
|
|
UpdateLODStats(0);
|
|
|
|
// Fill out the LOD level combo.
|
|
LODLevels.Empty();
|
|
LODLevels.Add( MakeShareable( new FString( LOCTEXT("AutoLOD", "Auto LOD").ToString() ) ) );
|
|
LODLevels.Add( MakeShareable( new FString( LOCTEXT("BaseLOD", "Base LOD").ToString() ) ) );
|
|
for(int32 LODLevelID = 1; LODLevelID < NumLODLevels; ++LODLevelID)
|
|
{
|
|
LODLevels.Add( MakeShareable( new FString( FString::Printf(*LOCTEXT("LODLevel_ID", "LOD Level %d").ToString(), LODLevelID ) ) ) );
|
|
|
|
//Update LOD stats for each level
|
|
UpdateLODStats(LODLevelID);
|
|
}
|
|
|
|
// Fill out the UV channels combo.
|
|
UVChannels.Empty();
|
|
for(int32 UVChannelID = 0; UVChannelID < GetNumUVChannels(0); ++UVChannelID)
|
|
{
|
|
UVChannels.Add( MakeShareable( new FString( FText::Format( LOCTEXT("UVChannel_ID", "UV Channel {0}"), FText::AsNumber( UVChannelID ) ).ToString() ) ) );
|
|
}
|
|
|
|
if( UVChannelCombo.IsValid() )
|
|
{
|
|
UVChannelCombo->RefreshOptions();
|
|
|
|
if(UVChannels.Num())
|
|
{
|
|
UVChannelCombo->SetSelectedItem(UVChannels[0]);
|
|
}
|
|
}
|
|
|
|
if( LODLevelCombo.IsValid() )
|
|
{
|
|
LODLevelCombo->RefreshOptions();
|
|
|
|
if(LODLevels.Num())
|
|
{
|
|
LODLevelCombo->SetSelectedItem(LODLevels[0]);
|
|
}
|
|
}
|
|
|
|
// Set the details view.
|
|
StaticMeshDetailsView->SetObject(StaticMesh);
|
|
|
|
Viewport->UpdatePreviewMesh(StaticMesh);
|
|
Viewport->RefreshViewport();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnChangeMesh()
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
UStaticMesh* SelectedMesh = GEditor->GetSelectedObjects()->GetTop<UStaticMesh>();
|
|
if(SelectedMesh && SelectedMesh != StaticMesh)
|
|
{
|
|
RemoveEditingObject(StaticMesh);
|
|
AddEditingObject(SelectedMesh);
|
|
|
|
SetEditorMesh(SelectedMesh);
|
|
|
|
if(SocketManager.IsValid())
|
|
{
|
|
SocketManager->UpdateStaticMesh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::DoDecomp(int32 InMaxHullCount, int32 InMaxHullVerts)
|
|
{
|
|
// Check we have a selected StaticMesh
|
|
if(StaticMesh && StaticMesh->RenderData)
|
|
{
|
|
FStaticMeshLODResources& LODModel = StaticMesh->RenderData->LODResources[0];
|
|
|
|
// Start a busy cursor so the user has feedback while waiting
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
// Make vertex buffer
|
|
int32 NumVerts = LODModel.VertexBuffer.GetNumVertices();
|
|
TArray<FVector> Verts;
|
|
for(int32 i=0; i<NumVerts; i++)
|
|
{
|
|
FVector Vert = LODModel.PositionVertexBuffer.VertexPosition(i);
|
|
Verts.Add(Vert);
|
|
}
|
|
|
|
// Make index buffer
|
|
TArray<uint32> Indices;
|
|
LODModel.IndexBuffer.GetCopy(Indices);
|
|
|
|
ClearSelectedPrims();
|
|
|
|
// Make sure rendering is done - so we are not changing data being used by collision drawing.
|
|
FlushRenderingCommands();
|
|
|
|
// Get the BodySetup we are going to put the collision into
|
|
UBodySetup* bs = StaticMesh->BodySetup;
|
|
if(bs)
|
|
{
|
|
bs->RemoveSimpleCollision();
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, create one here.
|
|
StaticMesh->CreateBodySetup();
|
|
bs = StaticMesh->BodySetup;
|
|
}
|
|
|
|
// Run actual util to do the work
|
|
DecomposeMeshToHulls(bs, Verts, Indices, InMaxHullCount, InMaxHullVerts);
|
|
|
|
// Enable collision, if not already
|
|
if( !Viewport->GetViewportClient().IsSetShowWireframeCollisionChecked() )
|
|
{
|
|
Viewport->GetViewportClient().SetShowWireframeCollision();
|
|
}
|
|
|
|
// refresh collision change back to staticmesh components
|
|
RefreshCollisionChange(StaticMesh);
|
|
|
|
// Mark mesh as dirty
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Update screen.
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::OnExportLightmapMesh( bool IsFBX )
|
|
{
|
|
check(StaticMesh); //must have a mesh to perform this operation
|
|
|
|
TArray <UStaticMesh*> StaticMeshArray;
|
|
StaticMeshArray.Add(StaticMesh);
|
|
|
|
FString Directory;
|
|
if (PromptUserForDirectory(Directory, *LOCTEXT("StaticMeshEditor_ExportToPromptTitle", "Export to...").ToString(), *FEditorDirectories::Get().GetLastDirectory(ELastDirectory::MESH_IMPORT_EXPORT)))
|
|
{
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::MESH_IMPORT_EXPORT, Directory); // Save path as default for next time.
|
|
|
|
bool bAnyErrorOccurred = FbxMeshUtils::ExportAllLightmapModels(StaticMeshArray, Directory, IsFBX);
|
|
if (bAnyErrorOccurred)
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("StaticMeshEditor_LightmapExportFailure", "Some static meshes failed to export or provided warnings. Please check the Log Window for details."));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::OnImportLightmapMesh( bool IsFBX )
|
|
{
|
|
check(StaticMesh); //must have a mesh to perform this operation
|
|
|
|
TArray <UStaticMesh*> StaticMeshArray;
|
|
StaticMeshArray.Add(StaticMesh);
|
|
|
|
FString Directory;
|
|
if (PromptUserForDirectory(Directory, *LOCTEXT("ImportFrom", "Import from...").ToString(), *FEditorDirectories::Get().GetLastDirectory(ELastDirectory::MESH_IMPORT_EXPORT)))
|
|
{
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::MESH_IMPORT_EXPORT, Directory); // Save path as default for next time.
|
|
|
|
bool bAnyErrorOccurred = FbxMeshUtils::ImportAllLightmapModels(StaticMeshArray, Directory, IsFBX);
|
|
if (bAnyErrorOccurred)
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("StaticMeshEditor_LightmapImportFailure", "Some static meshes failed to import or provided warnings. Please check the Log Window for details."));
|
|
}
|
|
Viewport->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
TSet< int32 >& FStaticMeshEditor::GetSelectedEdges()
|
|
{
|
|
return Viewport->GetSelectedEdges();
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetNumTriangles( int32 LODLevel ) const
|
|
{
|
|
return NumTriangles.IsValidIndex(LODLevel) ? NumTriangles[LODLevel] : 0;
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetNumVertices( int32 LODLevel ) const
|
|
{
|
|
return NumVertices.IsValidIndex(LODLevel) ? NumVertices[LODLevel] : 0;
|
|
}
|
|
|
|
int32 FStaticMeshEditor::GetNumUVChannels( int32 LODLevel ) const
|
|
{
|
|
return NumUVChannels.IsValidIndex(LODLevel) ? NumUVChannels[LODLevel] : 0;
|
|
}
|
|
|
|
void FStaticMeshEditor::DeleteSelected()
|
|
{
|
|
if (GetSelectedSocket())
|
|
{
|
|
DeleteSelectedSockets();
|
|
}
|
|
|
|
if (HasSelectedPrims())
|
|
{
|
|
DeleteSelectedPrims();
|
|
}
|
|
}
|
|
|
|
bool FStaticMeshEditor::CanDeleteSelected() const
|
|
{
|
|
return (GetSelectedSocket() != NULL || HasSelectedPrims());
|
|
}
|
|
|
|
void FStaticMeshEditor::DeleteSelectedSockets()
|
|
{
|
|
check(SocketManager.IsValid());
|
|
|
|
SocketManager->DeleteSelectedSocket();
|
|
}
|
|
|
|
void FStaticMeshEditor::DeleteSelectedPrims()
|
|
{
|
|
if (SelectedPrims.Num() > 0)
|
|
{
|
|
//int32 ShouldReplace = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("RemoveSelectedCollisionPrompt", "Are you sure you want to remove the selected collision meshes?"));
|
|
//if (ShouldReplace == EAppReturnType::Yes)
|
|
{
|
|
// Sort the selected prims by PrimIndex so when we're deleting them we don't mess up other prims indicies
|
|
struct FCompareFPrimDataPrimIndex
|
|
{
|
|
FORCEINLINE bool operator()(const FPrimData& A, const FPrimData& B) const
|
|
{
|
|
return A.PrimIndex < B.PrimIndex;
|
|
}
|
|
};
|
|
SelectedPrims.Sort(FCompareFPrimDataPrimIndex());
|
|
|
|
check(StaticMesh->BodySetup);
|
|
|
|
FKAggregateGeom* AggGeom = &StaticMesh->BodySetup->AggGeom;
|
|
|
|
GEditor->BeginTransaction(LOCTEXT("FStaticMeshEditor_DeleteSelectedPrims", "Delete Collision"));
|
|
StaticMesh->BodySetup->Modify();
|
|
|
|
for (int32 PrimIdx = SelectedPrims.Num() - 1; PrimIdx >= 0; PrimIdx--)
|
|
{
|
|
const FPrimData& PrimData = SelectedPrims[PrimIdx];
|
|
|
|
check(IsPrimValid(PrimData));
|
|
switch (PrimData.PrimType)
|
|
{
|
|
case KPT_Sphere:
|
|
AggGeom->SphereElems.RemoveAt(PrimData.PrimIndex);
|
|
break;
|
|
case KPT_Box:
|
|
AggGeom->BoxElems.RemoveAt(PrimData.PrimIndex);
|
|
break;
|
|
case KPT_Sphyl:
|
|
AggGeom->SphylElems.RemoveAt(PrimData.PrimIndex);
|
|
break;
|
|
case KPT_Convex:
|
|
AggGeom->ConvexElems.RemoveAt(PrimData.PrimIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
GEditor->EndTransaction();
|
|
|
|
ClearSelectedPrims();
|
|
|
|
// Make sure rendering is done - so we are not changing data being used by collision drawing.
|
|
FlushRenderingCommands();
|
|
|
|
// Make sure to invalidate cooked data
|
|
StaticMesh->BodySetup->InvalidatePhysicsData();
|
|
|
|
// refresh collision change back to staticmesh components
|
|
RefreshCollisionChange(StaticMesh);
|
|
|
|
// Mark staticmesh as dirty, to help make sure it gets saved.
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Update views/property windows
|
|
Viewport->RefreshViewport();
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::DuplicateSelected()
|
|
{
|
|
DuplicateSelectedSocket();
|
|
|
|
const FVector InitialOffset(20.f);
|
|
DuplicateSelectedPrims(&InitialOffset);
|
|
}
|
|
|
|
bool FStaticMeshEditor::CanDuplicateSelected() const
|
|
{
|
|
return (GetSelectedSocket() != NULL || HasSelectedPrims());
|
|
}
|
|
|
|
bool FStaticMeshEditor::CanRenameSelected() const
|
|
{
|
|
return (GetSelectedSocket() != NULL);
|
|
}
|
|
|
|
void FStaticMeshEditor::ExecuteFindInExplorer()
|
|
{
|
|
if ( ensure(StaticMesh->AssetImportData) )
|
|
{
|
|
const FString SourceFilePath = FReimportManager::ResolveImportFilename(StaticMesh->AssetImportData->SourceFilePath, StaticMesh);
|
|
if ( SourceFilePath.Len() && IFileManager::Get().FileSize( *SourceFilePath ) != INDEX_NONE )
|
|
{
|
|
FPlatformProcess::ExploreFolder( *FPaths::GetPath(SourceFilePath) );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FStaticMeshEditor::CanExecuteSourceCommands() const
|
|
{
|
|
if ( !StaticMesh->AssetImportData )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FString& SourceFilePath = FReimportManager::ResolveImportFilename(StaticMesh->AssetImportData->SourceFilePath, StaticMesh);
|
|
|
|
return SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE;
|
|
}
|
|
|
|
void FStaticMeshEditor::OnObjectReimported(UObject* InObject)
|
|
{
|
|
// Make sure we are using the object that is being reimported, otherwise a lot of needless work could occur.
|
|
if(StaticMesh == InObject)
|
|
{
|
|
SetEditorMesh(Cast<UStaticMesh>(InObject));
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::OnConvexDecomposition()
|
|
{
|
|
TabManager->InvokeTab(CollisionTabId);
|
|
}
|
|
|
|
bool FStaticMeshEditor::OnRequestClose()
|
|
{
|
|
bool bAllowClose = true;
|
|
if (StaticMeshDetails.IsValid() && StaticMeshDetails.Pin()->IsApplyNeeded())
|
|
{
|
|
// find out the user wants to do with this dirty material
|
|
EAppReturnType::Type YesNoCancelReply = FMessageDialog::Open(
|
|
EAppMsgType::YesNoCancel,
|
|
FText::Format( LOCTEXT("ShouldApplyLODChanges", "Would you like to apply level of detail changes to {0}?\n\n(No will lose all changes!)"), FText::FromString( StaticMesh->GetName() ) )
|
|
);
|
|
|
|
switch (YesNoCancelReply)
|
|
{
|
|
case EAppReturnType::Yes:
|
|
StaticMeshDetails.Pin()->ApplyChanges();
|
|
bAllowClose = true;
|
|
break;
|
|
case EAppReturnType::No:
|
|
// Do nothing, changes will be abandoned.
|
|
bAllowClose = true;
|
|
break;
|
|
case EAppReturnType::Cancel:
|
|
// Don't exit.
|
|
bAllowClose = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bAllowClose;
|
|
}
|
|
|
|
void FStaticMeshEditor::RegisterOnPostUndo( const FOnPostUndo& Delegate )
|
|
{
|
|
OnPostUndo.Add( Delegate );
|
|
}
|
|
|
|
void FStaticMeshEditor::UnregisterOnPostUndo( SWidget* Widget )
|
|
{
|
|
OnPostUndo.RemoveAll( Widget );
|
|
}
|
|
|
|
void FStaticMeshEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged )
|
|
{
|
|
if(StaticMesh && StaticMesh->BodySetup)
|
|
{
|
|
StaticMesh->BodySetup->CreatePhysicsMeshes();
|
|
}
|
|
}
|
|
|
|
void FStaticMeshEditor::UndoAction()
|
|
{
|
|
GEditor->UndoTransaction();
|
|
}
|
|
|
|
void FStaticMeshEditor::RedoAction()
|
|
{
|
|
GEditor->RedoTransaction();
|
|
}
|
|
|
|
void FStaticMeshEditor::PostUndo( bool bSuccess )
|
|
{
|
|
RemoveInvalidPrims();
|
|
|
|
OnPostUndo.Broadcast();
|
|
}
|
|
|
|
void FStaticMeshEditor::PostRedo( bool bSuccess )
|
|
{
|
|
RemoveInvalidPrims();
|
|
|
|
OnPostUndo.Broadcast();
|
|
}
|
|
|
|
void FStaticMeshEditor::OnSocketSelectionChanged()
|
|
{
|
|
UStaticMeshSocket* SelectedSocket = GetSelectedSocket();
|
|
if (SelectedSocket)
|
|
{
|
|
ClearSelectedPrims();
|
|
}
|
|
Viewport->GetViewportClient().OnSocketSelectionChanged( SelectedSocket );
|
|
}
|
|
|
|
void FStaticMeshEditor::OnPostReimport(UObject* InObject, bool bSuccess)
|
|
{
|
|
// Ignore if this is regarding a different object
|
|
if ( InObject != StaticMesh )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bSuccess)
|
|
{
|
|
RefreshTool();
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|