Files
FLESH/Source/FLESHEditor/Private/FLESHEditor.cpp

1703 lines
61 KiB
C++

#include "FLESHEditor.h"
#include "FLESHEditorCommands.h"
#include "FLESHEditorStyle.h"
#include "MatrixInputWidget.h"
#include "DismembermentGraph/DismembermentGraphEditor.h"
#include "DismembermentGraph/DismembermentGraphAsset.h"
#include "Framework/Docking/TabManager.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/Views/STreeView.h"
#include "Widgets/Views/STableViewBase.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Input/SCheckBox.h"
#include "PropertyEditorModule.h"
#include "Styling/AppStyle.h"
#include "LevelEditor.h"
#include "ToolMenus.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "FLESHViewportClient.h"
#include "VisceraNodeObject.h"
#include "BooleanCutTool.h"
#include "VisceraNodeFactory.h"
#define LOCTEXT_NAMESPACE "FLESHEditor"
const FName FFLESHEditor::ViewportTabId(TEXT("FLESHEditor_Viewport"));
const FName FFLESHEditor::DetailsTabId(TEXT("FLESHEditor_Details"));
const FName FFLESHEditor::AssetBrowserTabId(TEXT("FLESHEditor_AssetBrowser"));
const FName FFLESHEditor::MatrixEditorTabId(TEXT("FLESHEditor_MatrixEditor"));
const FName FFLESHEditor::GraphEditorTabId(TEXT("FLESHEditor_GraphEditor"));
const FName FFLESHEditor::ToolbarTabId(TEXT("FLESHEditor_Toolbar"));
FFLESHEditor::FFLESHEditor()
: EditingObject(nullptr)
, CommandList(MakeShareable(new FUICommandList))
{
// Initialize member variables
bIsEditorInitialized = false;
}
FFLESHEditor::~FFLESHEditor()
{
// Unregister from any events
// Comment out problematic code, as FFLESHEditor doesn't inherit from FEditorUndoClient
// if (GEditor)
// {
// GEditor->UnregisterForUndo(this);
// }
// Clear references
DetailsWidget = nullptr;
EditingObject = nullptr;
}
void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UObject* InObject)
{
// Store the object we're editing
EditingObject = InObject;
// Create command list
this->CreateCommandList();
// Build bone hierarchy
BuildBoneHierarchy();
// Create tab layout
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("FLESHEditorLayout_v3.0")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(ToolbarTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->SetSizeCoefficient(0.9f)
->Split
(
// Left side outline view panel - includes bone hierarchy
FTabManager::NewStack()
->SetSizeCoefficient(0.2f)
->AddTab(DetailsTabId, ETabState::OpenedTab)
->SetForegroundTab(DetailsTabId)
)
->Split
(
// Middle main view area
FTabManager::NewStack()
->SetSizeCoefficient(0.6f)
->AddTab(ViewportTabId, ETabState::OpenedTab)
->SetForegroundTab(ViewportTabId)
)
->Split
(
// Right side property panel
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.2f)
->Split
(
// Matrix editor
FTabManager::NewStack()
->SetSizeCoefficient(0.5f)
->AddTab(MatrixEditorTabId, ETabState::OpenedTab)
)
->Split
(
// Graph editor
FTabManager::NewStack()
->SetSizeCoefficient(0.5f)
->AddTab(GraphEditorTabId, ETabState::OpenedTab)
)
)
)
);
// Create a standalone toolkit
FAssetEditorToolkit::InitAssetEditor(
Mode,
InitToolkitHost,
FName("FLESHEditorApp"),
StandaloneDefaultLayout,
true, // bCreateDefaultStandaloneMenu
true, // bCreateDefaultToolbar
InObject);
// Register tab spawners
TabManager = GetTabManager();
if (TabManager.IsValid())
{
TabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
TabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
TabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.TabIcon"));
TabManager->RegisterTabSpawner(MatrixEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_MatrixEditor))
.SetDisplayName(LOCTEXT("MatrixEditorTab", "Matrix Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "PropertyEditor.Grid.TabIcon"));
TabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
TabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Toolbar))
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Toolbar"));
}
// Initialize the editor using the base class method, so it won't crash even if an empty object is passed in.
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = false;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsViewArgs.NotifyHook = nullptr;
DetailsViewArgs.bSearchInitialKeyFocus = false;
DetailsViewArgs.ViewIdentifier = NAME_None;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
DetailsViewArgs.bShowOptions = true;
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
DetailsWidget->SetObject(EditingObject);
}
void FFLESHEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FLESHEditor", "FLESH Editor"));
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.TabIcon"));
}
void FFLESHEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
// Unregister tab spawners
InTabManager->UnregisterTabSpawner(ViewportTabId);
InTabManager->UnregisterTabSpawner(DetailsTabId);
InTabManager->UnregisterTabSpawner(AssetBrowserTabId);
InTabManager->UnregisterTabSpawner(MatrixEditorTabId);
InTabManager->UnregisterTabSpawner(GraphEditorTabId);
InTabManager->UnregisterTabSpawner(ToolbarTabId);
}
FName FFLESHEditor::GetToolkitFName() const
{
return FName("FLESHEditor");
}
FText FFLESHEditor::GetBaseToolkitName() const
{
return LOCTEXT("FLESHEditorAppLabel", "FLESH Editor");
}
FString FFLESHEditor::GetWorldCentricTabPrefix() const
{
return TEXT("FLESH ");
}
FLinearColor FFLESHEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.7f, 0.0f, 0.0f, 0.5f);
}
void FFLESHEditor::OpenEditor()
{
try
{
// Create a DismembermentGraphAsset to avoid assertion failure and provide meaningful editing
UDismembermentGraphAsset* GraphAsset = NewObject<UDismembermentGraphAsset>(GetTransientPackage(), UDismembermentGraphAsset::StaticClass(), TEXT("TempDismembermentGraphAsset"));
// Create new editor instance
TSharedRef<FFLESHEditor> NewEditor = MakeShareable(new FFLESHEditor());
NewEditor->InitFLESHEditor(EToolkitMode::Standalone, nullptr, GraphAsset);
}
catch (const std::exception& e)
{
UE_LOG(LogTemp, Error, TEXT("Exception in FFLESHEditor::OpenEditor: %s"), UTF8_TO_TCHAR(e.what()));
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("EditorOpenError", "无法打开FLESH编辑器: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
}
catch (...)
{
UE_LOG(LogTemp, Error, TEXT("Unknown exception in FFLESHEditor::OpenEditor"));
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("EditorOpenUnknownError", "打开FLESH编辑器时发生未知错误"));
}
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("ViewportTitle", "Viewport"))
[
CreateViewportWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("DetailsTitle", "Details"))
[
CreateDetailsWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("AssetBrowserTitle", "Asset Browser"))
[
CreateAssetBrowserWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_MatrixEditor(const FSpawnTabArgs& Args)
{
try
{
return SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
CreateMatrixEditorWidget()
];
}
catch (const std::exception& e)
{
UE_LOG(LogTemp, Error, TEXT("Exception in SpawnTab_MatrixEditor: %s"), UTF8_TO_TCHAR(e.what()));
// Return a simple error widget to avoid crashing
return SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorError", "Error loading Matrix Editor"))
.ColorAndOpacity(FLinearColor::Red)
]
];
}
catch (...)
{
UE_LOG(LogTemp, Error, TEXT("Unknown exception in SpawnTab_MatrixEditor"));
// Return a simple error widget to avoid crashing
return SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorError", "Error loading Matrix Editor"))
.ColorAndOpacity(FLinearColor::Red)
]
];
}
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
{
try
{
return SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
CreateGraphEditorWidget()
];
}
catch (const std::exception& e)
{
UE_LOG(LogTemp, Error, TEXT("Exception in SpawnTab_GraphEditor: %s"), UTF8_TO_TCHAR(e.what()));
// Return a simple error widget to avoid crashing
return SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorError", "Error loading Graph Editor"))
.ColorAndOpacity(FLinearColor::Red)
]
];
}
catch (...)
{
UE_LOG(LogTemp, Error, TEXT("Unknown exception in SpawnTab_GraphEditor"));
// Return a simple error widget to avoid crashing
return SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorError", "Error loading Graph Editor"))
.ColorAndOpacity(FLinearColor::Red)
]
];
}
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("ToolbarTitle", "Toolbar"))
[
CreateToolbarWidget()
];
}
TSharedRef<SWidget> FFLESHEditor::CreateViewportWidget()
{
// Create viewport client
ViewportClient = MakeShared<FFLESHViewportClient>(this);
// Create viewport widget
ViewportWidget = SNew(SViewport)
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute())
.EnableGammaCorrection(false);
// Create scene viewport
Viewport = MakeShared<FSceneViewport>(ViewportClient.Get(), ViewportWidget);
// Set viewport in client
ViewportClient->Viewport = Viewport.Get();
// Associate scene viewport with widget
ViewportWidget->SetViewportInterface(StaticCastSharedRef<ISlateViewport>(Viewport.ToSharedRef()));
// Create viewport toolbar
TSharedRef<SWidget> ViewportToolbar = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
.OnClicked(this, &FFLESHEditor::OnResetCameraClicked)
.ToolTipText(LOCTEXT("ResetCamera", "Reset camera position"))
.ContentPadding(FMargin(2.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("ResetCameraButton", "Reset Camera"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
.OnClicked(this, &FFLESHEditor::OnToggleWireframeClicked)
.ToolTipText(LOCTEXT("ToggleWireframe", "Toggle wireframe mode"))
.ContentPadding(FMargin(2.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("ToggleWireframeButton", "Wireframe"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
.OnClicked(this, &FFLESHEditor::OnToggleBonesClicked)
.ToolTipText(LOCTEXT("ToggleBones", "Show/Hide bones"))
.ContentPadding(FMargin(2.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("ToggleBonesButton", "Show Bones"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
];
// Create viewport container
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
ViewportToolbar
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
ViewportWidget.ToSharedRef()
];
}
TSharedRef<SWidget> FFLESHEditor::CreateDetailsWidget()
{
// Create details panel
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = false;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsViewArgs.NotifyHook = nullptr;
DetailsViewArgs.bSearchInitialKeyFocus = false;
DetailsViewArgs.ViewIdentifier = NAME_None;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
DetailsViewArgs.bShowOptions = true;
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
// Create the node tree
BuildVisceraNodeTree();
// Create the outline view
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("NodeTreeHeader", "Node Tree"))
.Font(FAppStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSearchBox)
.HintText(LOCTEXT("SearchNodes", "Search nodes..."))
.OnTextChanged_Lambda([this](const FText& InFilterText)
{
// TODO: Implement search functionality
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4, 0, 0, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(1, 0))
.ToolTipText(LOCTEXT("AddNewSoftBodyTooltip", "Add a new SoftBody node"))
.OnClicked_Lambda([this]() -> FReply
{
// Add a new SoftBody node
AddNewVisceraNode(TEXT("SoftBody"));
return FReply::Handled();
})
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
]
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(0, 4, 0, 0)
[
SAssignNew(NodeTreeView, STreeView<TSharedPtr<FVisceraNodeItem>>)
.TreeItemsSource(&NodeItems)
.OnGenerateRow(this, &FFLESHEditor::OnGenerateNodeRow)
.OnGetChildren(this, &FFLESHEditor::OnGetNodeChildren)
.OnSelectionChanged(this, &FFLESHEditor::OnNodeSelectionChanged)
.SelectionMode(ESelectionMode::Single)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 8, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("PropertiesHeader", "Properties"))
.Font(FAppStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
DetailsWidget.ToSharedRef()
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateAssetBrowserWidget()
{
// Create asset browser
return SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("AssetBrowserHeader", "Asset Browser"))
.Font(FAppStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportCharacterModel", "Import Character Model"))
.OnClicked_Lambda([this]() { OnImportCharacterModel(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportOrganModel", "Import Organ Model"))
.OnClicked_Lambda([this]() { OnImportOrganModel(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportSkeletonModel", "Import Skeleton Model"))
.OnClicked_Lambda([this]() { OnImportSkeletonModel(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportPhysicsAsset", "Import Physics Asset"))
.OnClicked_Lambda([this]() { OnImportPhysicsAsset(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateMatrixEditorWidget()
{
// Create matrix editor widget
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorHeader", "切割参数"))
.Font(FAppStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(8.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(STextBlock)
.Text(LOCTEXT("CuttingPlaneMatrix", "切割平面矩阵"))
.Font(FAppStyle::GetFontStyle("NormalFont"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(MatrixEditorWidget, SMatrixInputWidget)
.Rows(4)
.Columns(3)
.Matrix(FMatrix::Identity)
.OnMatrixChanged(FOnMatrixChanged::CreateLambda([this](const FMatrix& NewMatrix)
{
// 处理矩阵变化
UE_LOG(LogTemp, Display, TEXT("Matrix changed"));
}))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 8, 0, 0)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
[
SNew(SButton)
.Text(LOCTEXT("ResetMatrix", "重置"))
.ToolTipText(LOCTEXT("ResetMatrixTooltip", "重置矩阵为单位矩阵"))
.OnClicked_Lambda([this]()
{
// 重置矩阵
if (MatrixEditorWidget.IsValid())
{
MatrixEditorWidget->ResetToIdentity();
}
return FReply::Handled();
})
]
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateGraphEditorWidget()
{
// Create graph editor
return SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorHeader", "Graph Editor"))
.Font(FAppStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorPlaceholder", "Dismemberment system logic graph will be displayed here"))
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateToolbarWidget()
{
// Create toolbar widget
return SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SHorizontalBox)
// Cutting tools group
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("CutTools", "Cutting Tools"))
.Font(FAppStyle::GetFontStyle("SmallFont"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SUniformGridPanel)
.SlotPadding(FMargin(2.0f))
.MinDesiredSlotWidth(64.0f)
.MinDesiredSlotHeight(64.0f)
// Plane cut
+ SUniformGridPanel::Slot(0, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
.ToolTipText(LOCTEXT("PlaneCutTooltip", "Plane cutting tool"))
.OnClicked_Lambda([this]()
{
// TODO: Activate plane cutting tool
return FReply::Handled();
})
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("PlaneCut", "Plane"))
]
]
// Curve cut
+ SUniformGridPanel::Slot(1, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
.ToolTipText(LOCTEXT("CurveCutTooltip", "Curve cutting tool"))
.OnClicked_Lambda([this]()
{
// TODO: Activate curve cutting tool
return FReply::Handled();
})
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("CurveCut", "Curve"))
]
]
// Freeform cut
+ SUniformGridPanel::Slot(0, 1)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
.ToolTipText(LOCTEXT("FreeformCutTooltip", "Freeform cutting tool"))
.OnClicked_Lambda([this]()
{
// TODO: Activate freeform cutting tool
return FReply::Handled();
})
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("FreeformCut", "Freeform"))
]
]
// Preset cut
+ SUniformGridPanel::Slot(1, 1)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
.ToolTipText(LOCTEXT("PresetCutTooltip", "Preset cutting tool"))
.OnClicked_Lambda([this]()
{
// TODO: Activate preset cutting tool
return FReply::Handled();
})
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("PresetCut", "Preset"))
]
]
]
]
// Cutting layer options
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("CutLayers", "Cutting Layers"))
.Font(FAppStyle::GetFontStyle("SmallFont"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("SkinLayer", "Skin"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("MuscleLayer", "Muscle"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("BoneLayer", "Bone"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("OrganLayer", "Organ"))
]
]
]
]
// Effects options
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("Effects", "Effects"))
.Font(FAppStyle::GetFontStyle("SmallFont"))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("BloodEffect", "Blood"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("PhysicsEffect", "Physics"))
]
]
]
]
// File operations
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.HAlign(HAlign_Right)
.Padding(8.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportModel", "Import Model"))
.OnClicked_Lambda([this]() { OnImportModel(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("SavePreset", "Save Preset"))
.OnClicked_Lambda([this]() { OnSavePreset(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("LoadPreset", "Load Preset"))
.OnClicked_Lambda([this]() { OnLoadPreset(); return FReply::Handled(); })
.ButtonStyle(FAppStyle::Get(), "FlatButton")
]
]
];
}
void FFLESHEditor::CreateCommandList()
{
// Create command list
CommandList = MakeShareable(new FUICommandList);
// Bind commands
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenDismembermentGraphEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenDismembermentGraphEditor),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenAnatomicalLayerEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenAnatomicalLayerEditor),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenBooleanCutTool,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBooleanCutTool),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenBloodSystemEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBloodSystemEditor),
FCanExecuteAction());
}
void FFLESHEditor::OnOpenDismembermentGraphEditor()
{
// Open dismemberment graph editor
FGlobalTabmanager::Get()->TryInvokeTab(GraphEditorTabId);
}
void FFLESHEditor::OnOpenAnatomicalLayerEditor()
{
// Open anatomical layer editor
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AnatomicalLayerEditorNotImplemented", "Anatomical Layer Editor is not implemented yet"));
}
void FFLESHEditor::OnOpenBooleanCutTool()
{
// Open boolean cut tool
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BooleanCutToolNotImplemented", "Boolean Cut Tool is not implemented yet"));
}
void FFLESHEditor::OnOpenBloodSystemEditor()
{
// Open blood system editor
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BloodSystemEditorNotImplemented", "Blood System Editor is not implemented yet"));
}
void FFLESHEditor::OnImportCharacterModel()
{
// Import character model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportCharacterModelNotImplemented", "Import Character Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportOrganModel()
{
// Import organ model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportOrganModelNotImplemented", "Import Organ Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportSkeletonModel()
{
// Import skeleton model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportSkeletonModelNotImplemented", "Import Skeleton Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportPhysicsAsset()
{
// Import physics asset
UE_LOG(LogTemp, Display, TEXT("Import Physics Asset"));
}
void FFLESHEditor::OnTestMatrix()
{
// Test matrix
UE_LOG(LogTemp, Display, TEXT("Test Matrix"));
}
void FFLESHEditor::OnImportModel()
{
// Import model
UE_LOG(LogTemp, Display, TEXT("Import Model"));
}
void FFLESHEditor::OnSavePreset()
{
// Save preset
UE_LOG(LogTemp, Display, TEXT("Save Preset"));
}
void FFLESHEditor::OnLoadPreset()
{
// Load preset
UE_LOG(LogTemp, Display, TEXT("Load Preset"));
}
FReply FFLESHEditor::OnResetCameraClicked()
{
// Reset camera
if (ViewportClient.IsValid())
{
ViewportClient->ResetCamera();
}
return FReply::Handled();
}
FReply FFLESHEditor::OnToggleWireframeClicked()
{
// Toggle wireframe
if (ViewportClient.IsValid())
{
ViewportClient->ToggleWireframe();
}
return FReply::Handled();
}
FReply FFLESHEditor::OnToggleBonesClicked()
{
// Toggle bones
if (ViewportClient.IsValid())
{
ViewportClient->ToggleBones();
}
return FReply::Handled();
}
TSharedRef<ITableRow> FFLESHEditor::OnGenerateBoneRow(TSharedPtr<FBoneTreeItem> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
// Create a row for the bone item
return SNew(STableRow<TSharedPtr<FBoneTreeItem>>, OwnerTable)
.Style(FAppStyle::Get(), "TableView.Row")
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(0.75f)
.VAlign(VAlign_Center)
.Padding(4.0f, 0.0f)
[
SNew(STextBlock)
.Text(FText::FromString(Item->DisplayName))
.Font(FAppStyle::Get().GetFontStyle("PropertyWindow.NormalFont"))
]
+ SHorizontalBox::Slot()
.FillWidth(0.25f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
.Padding(4.0f, 0.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton.Primary")
.ContentPadding(FMargin(2.0f, 1.0f))
.ToolTipText(LOCTEXT("CutBoneTooltip", "Apply cut to this bone"))
.OnClicked_Lambda([this, Item]()
{
// Apply cut to this bone
SelectedBoneItem = Item;
// TODO: Implement actual cut operation
return FReply::Handled();
})
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("CutBone", "Cut"))
]
]
];
}
void FFLESHEditor::OnGetBoneChildren(TSharedPtr<FBoneTreeItem> Item, TArray<TSharedPtr<FBoneTreeItem>>& OutChildren)
{
OutChildren = Item->Children;
}
void FFLESHEditor::OnBoneSelectionChanged(TSharedPtr<FBoneTreeItem> Item, ESelectInfo::Type SelectInfo)
{
SelectedBoneItem = Item;
// If a bone is selected, update the details panel
if (SelectedBoneItem.IsValid())
{
UE_LOG(LogTemp, Display, TEXT("Selected bone: %s"), *SelectedBoneItem->DisplayName);
// Add more bone selection handling logic here
}
else
{
UE_LOG(LogTemp, Display, TEXT("No bone selected"));
}
}
void FFLESHEditor::BuildBoneHierarchy()
{
// Clear existing items
BoneItems.Empty();
// Add some default cutting planes for testing
CuttingPlaneNames.Empty();
CuttingPlaneNames.Add(MakeShareable(new FName("Horizontal Plane")));
CuttingPlaneNames.Add(MakeShareable(new FName("Vertical Plane")));
CuttingPlaneNames.Add(MakeShareable(new FName("Custom Angle Plane")));
// Get skeletal mesh from editing object
USkeletalMesh* SkeletalMesh = nullptr;
if (EditingObject)
{
// Try to get skeletal mesh from the editing object
if (EditingObject->IsA<USkeletalMesh>())
{
SkeletalMesh = Cast<USkeletalMesh>(EditingObject);
}
else
{
// Try to find skeletal mesh property in the editing object
for (TFieldIterator<FProperty> PropIt(EditingObject->GetClass()); PropIt; ++PropIt)
{
FProperty* Property = *PropIt;
if (Property->IsA<FObjectProperty>())
{
FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property);
if (ObjectProperty->PropertyClass->IsChildOf(USkeletalMesh::StaticClass()))
{
UObject* PropValue = ObjectProperty->GetObjectPropertyValue_InContainer(EditingObject);
if (PropValue && PropValue->IsA<USkeletalMesh>())
{
SkeletalMesh = Cast<USkeletalMesh>(PropValue);
break;
}
}
}
}
}
}
if (!SkeletalMesh)
{
UE_LOG(LogTemp, Warning, TEXT("No valid skeletal mesh found, using default bone structure"));
TSharedPtr<FBoneTreeItem> RootItem = MakeShareable(new FBoneTreeItem(FName(TEXT("Root")), TEXT("Root")));
BoneItems.Add(RootItem);
}
else
{
// Get reference skeleton
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetRefSkeleton();
// Create bone items
TMap<int32, TSharedPtr<FBoneTreeItem>> BoneItemMap;
// Create root bones (bones with no parent)
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
{
const FName BoneName = RefSkeleton.GetBoneName(BoneIndex);
const FString DisplayName = BoneName.ToString();
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
TSharedPtr<FBoneTreeItem> BoneItem = MakeShareable(new FBoneTreeItem(BoneName, DisplayName));
BoneItemMap.Add(BoneIndex, BoneItem);
if (ParentIndex == INDEX_NONE)
{
// This is a root bone
BoneItems.Add(BoneItem);
}
else
{
// Add as child to parent
TSharedPtr<FBoneTreeItem>* ParentItem = BoneItemMap.Find(ParentIndex);
if (ParentItem)
{
(*ParentItem)->AddChild(BoneItem);
}
}
}
}
// Refresh the tree view
if (BoneTreeView.IsValid())
{
BoneTreeView->RequestTreeRefresh();
}
}
void FFLESHEditor::BuildVisceraNodeTree()
{
// Clear existing items
NodeItems.Empty();
SelectedNodeItem = nullptr;
// Create root node for viscera
TSharedPtr<FVisceraNodeItem> RootNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("SoftBody_Viscera"), TEXT("SoftBody_Viscera"));
NodeItems.Add(RootNode);
// Add default properties for advanced cutting
RootNode->BoolProperties.Add(TEXT("EnableMultiLayerCutting"), true);
RootNode->FloatProperties.Add(TEXT("CapMethod"), (float)ECapMeshMethod::TriangleFan);
// Add lungs with anchors and plane constraints
TSharedPtr<FVisceraNodeItem> LungsNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Lungs"), TEXT("Lungs"));
RootNode->AddChild(LungsNode);
// Add anchor points for lungs
LungsNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
FName("LeftLungAnchor"),
TEXT("LeftLungAnchor"),
FVector(-10.0f, 5.0f, 0.0f),
3.0f,
0.8f));
LungsNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
FName("RightLungAnchor"),
TEXT("RightLungAnchor"),
FVector(10.0f, 5.0f, 0.0f),
3.0f,
0.8f));
// Add plane constraints for lungs
LungsNode->AddChild(FVisceraNodeFactory::CreatePlaneNode(
FName("LungTopPlane"),
TEXT("LungTopPlane"),
FVector(0.0f, 10.0f, 0.0f),
FVector(0.0f, -1.0f, 0.0f),
0.7f));
// Add heart with tetrahedron structure
TSharedPtr<FVisceraNodeItem> HeartNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Heart"), TEXT("Heart"));
RootNode->AddChild(HeartNode);
// Add tetrahedron for heart
TArray<FVector> HeartTetraPoints;
HeartTetraPoints.Add(FVector(0.0f, 0.0f, 0.0f));
HeartTetraPoints.Add(FVector(5.0f, 0.0f, 0.0f));
HeartTetraPoints.Add(FVector(0.0f, 5.0f, 0.0f));
HeartTetraPoints.Add(FVector(0.0f, 0.0f, 5.0f));
HeartNode->AddChild(FVisceraNodeFactory::CreateTetraNode(
FName("HeartTetra"),
TEXT("HeartTetra"),
HeartTetraPoints,
0.9f));
// Add anchor for heart
HeartNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
FName("HeartAnchor"),
TEXT("HeartAnchor"),
FVector(0.0f, 0.0f, 5.0f),
2.0f,
1.0f));
// Add stomach with line chain structure
TSharedPtr<FVisceraNodeItem> StomachNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Stomach"), TEXT("Stomach"));
RootNode->AddChild(StomachNode);
// Add line chain for stomach
TArray<FVector> StomachLinePoints;
StomachLinePoints.Add(FVector(-5.0f, -5.0f, 0.0f));
StomachLinePoints.Add(FVector(0.0f, -8.0f, 0.0f));
StomachLinePoints.Add(FVector(5.0f, -5.0f, 0.0f));
StomachNode->AddChild(FVisceraNodeFactory::CreateLineChainNode(
FName("StomachLineChain"),
TEXT("StomachLineChain"),
StomachLinePoints,
0.6f,
1.5f));
// Add intestines with complex line chain
TSharedPtr<FVisceraNodeItem> IntestinesNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Intestines"), TEXT("Intestines"));
RootNode->AddChild(IntestinesNode);
// Add line chain for intestines
TArray<FVector> IntestinesLinePoints;
IntestinesLinePoints.Add(FVector(-8.0f, -10.0f, 0.0f));
IntestinesLinePoints.Add(FVector(-4.0f, -12.0f, 0.0f));
IntestinesLinePoints.Add(FVector(0.0f, -15.0f, 0.0f));
IntestinesLinePoints.Add(FVector(4.0f, -12.0f, 0.0f));
IntestinesLinePoints.Add(FVector(8.0f, -10.0f, 0.0f));
IntestinesNode->AddChild(FVisceraNodeFactory::CreateLineChainNode(
FName("IntestinesLineChain"),
TEXT("IntestinesLineChain"),
IntestinesLinePoints,
0.4f,
1.2f));
// Add group collisions node
RootNode->AddChild(FVisceraNodeFactory::CreateGroupCollisionsNode(
FName("GroupCollisions"),
TEXT("GroupCollisions")));
// Add time node with simulation parameters
RootNode->AddChild(FVisceraNodeFactory::CreateTimeNode(
FName("TimeNode"),
TEXT("TimeNode"),
0.01f,
2.0f));
// Refresh the tree view
if (NodeTreeView.IsValid())
{
NodeTreeView->RequestTreeRefresh();
}
}
TSharedRef<ITableRow> FFLESHEditor::OnGenerateNodeRow(TSharedPtr<FVisceraNodeItem> InItem, const TSharedRef<STableViewBase>& OwnerTable)
{
// Create the row
return SNew(STableRow<TSharedPtr<FVisceraNodeItem>>, OwnerTable)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image_Lambda([InItem]() -> const FSlateBrush*
{
if (InItem->NodeType == TEXT("Root"))
{
return FAppStyle::GetBrush("ClassIcon.Blueprint");
}
else if (InItem->NodeType == TEXT("SoftBody"))
{
return FAppStyle::GetBrush("ClassIcon.SkeletalMesh");
}
else if (InItem->NodeType == TEXT("AnchorSphere"))
{
return FAppStyle::GetBrush("ClassIcon.StaticMeshComponent");
}
else if (InItem->NodeType == TEXT("Plane"))
{
return FAppStyle::GetBrush("ClassIcon.Plane");
}
return FAppStyle::GetBrush("ClassIcon.Object");
})
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(InItem->DisplayName))
.ColorAndOpacity_Lambda([InItem]() -> FSlateColor
{
if (InItem->NodeType == TEXT("Root"))
{
return FLinearColor(0.9f, 0.9f, 0.9f);
}
else if (InItem->NodeType == TEXT("SoftBody"))
{
return FLinearColor(0.2f, 0.8f, 0.2f);
}
else if (InItem->NodeType == TEXT("AnchorSphere"))
{
return FLinearColor(0.8f, 0.2f, 0.2f);
}
else if (InItem->NodeType == TEXT("Plane"))
{
return FLinearColor(0.2f, 0.2f, 0.8f);
}
return FLinearColor::White;
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4, 0, 0, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(1, 0))
.Visibility_Lambda([InItem]() -> EVisibility
{
return InItem->NodeType != TEXT("Root") ? EVisibility::Visible : EVisibility::Collapsed;
})
.OnClicked_Lambda([this, InItem]() -> FReply
{
// Show context menu
const FVector2D CursorPos = FSlateApplication::Get().GetCursorPos();
FWidgetPath WidgetPath;
// Correctly call FindPathToWidget function
FSlateApplication::Get().FindPathToWidget(
FSlateApplication::Get().GetActiveTopLevelWindow().ToSharedRef(),
WidgetPath,
EVisibility::Visible
);
TSharedRef<SWidget> MenuContent = OnGetNodeContextMenu(InItem).ToSharedRef();
FSlateApplication::Get().PushMenu(
FSlateApplication::Get().GetActiveTopLevelWindow().ToSharedRef(),
WidgetPath,
MenuContent,
CursorPos,
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
);
return FReply::Handled();
})
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.Settings"))
]
]
];
}
void FFLESHEditor::OnGetNodeChildren(TSharedPtr<FVisceraNodeItem> InItem, TArray<TSharedPtr<FVisceraNodeItem>>& OutChildren)
{
OutChildren = InItem->Children;
}
void FFLESHEditor::OnNodeSelectionChanged(TSharedPtr<FVisceraNodeItem> InItem, ESelectInfo::Type SelectInfo)
{
SelectedNodeItem = InItem;
// Update details panel with the selected node's properties
if (DetailsWidget.IsValid() && InItem.IsValid())
{
// Create a UVisceraNodeObject and initialize it from the selected node
UVisceraNodeObject* NodeObject = NewObject<UVisceraNodeObject>(GetTransientPackage(), UVisceraNodeObject::StaticClass(), TEXT("VisceraNodeObject"));
NodeObject->InitFromNodeItem(InItem);
// Set the object in the details panel
DetailsWidget->SetObject(NodeObject);
// Register for property changes
DetailsWidget->OnFinishedChangingProperties().AddLambda([this, NodeObject](const FPropertyChangedEvent& PropertyChangedEvent) {
if (SelectedNodeItem.IsValid())
{
// Apply changes back to the node item
NodeObject->ApplyToNodeItem(SelectedNodeItem);
// If this is a SoftBody node, update the simulation settings
if (SelectedNodeItem->NodeType.Equals(TEXT("SoftBody")))
{
// In a real implementation, you would update the simulation settings
// For now, we'll just log a message
UE_LOG(LogTemp, Display, TEXT("Updated SoftBody simulation settings"));
}
}
});
}
}
TSharedPtr<SWidget> FFLESHEditor::OnGetNodeContextMenu(TSharedPtr<FVisceraNodeItem> InItem)
{
FMenuBuilder MenuBuilder(true, CommandList);
if (InItem->NodeType == TEXT("SoftBody"))
{
// Add options for SoftBody nodes
MenuBuilder.BeginSection("SoftBodyActions", FText::FromString("Add Node"));
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBodyAnchorSphereNode"),
FText::FromString("Add a new anchor sphere to this soft body"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
AddNewVisceraNode(TEXT("AnchorSphere"), InItem);
}))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBodyLineChainNode"),
FText::FromString("Add a new line chain to this soft body"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
AddNewVisceraNode(TEXT("LineChain"), InItem);
}))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBodyTetraNode"),
FText::FromString("Add a new tetra node to this soft body"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
AddNewVisceraNode(TEXT("Tetra"), InItem);
}))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBodyTimeNode"),
FText::FromString("Add a new time node to this soft body"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
AddNewVisceraNode(TEXT("Time"), InItem);
}))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBodyGroupCollisionsNode"),
FText::FromString("Add a new group collisions node to this soft body"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
AddNewVisceraNode(TEXT("GroupCollisions"), InItem);
}))
);
MenuBuilder.EndSection();
}
// Common actions for all nodes
MenuBuilder.BeginSection("CommonActions", FText::FromString("Actions"));
MenuBuilder.AddMenuEntry(
FText::FromString("Delete"),
FText::FromString("Delete this node"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
// Remove the node from its parent
TSharedPtr<FVisceraNodeItem> ParentItem = InItem->ParentItem.Pin();
if (ParentItem.IsValid())
{
ParentItem->Children.Remove(InItem);
NodeTreeView->RequestTreeRefresh();
}
}))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Rename"),
FText::FromString("Rename this node"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
// In a real implementation, you would show a rename dialog
// For now, we'll just log a message
UE_LOG(LogTemp, Display, TEXT("Rename node: %s"), *InItem->DisplayName);
}))
);
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void FFLESHEditor::AddNewVisceraNode(const FString& NodeType, TSharedPtr<FVisceraNodeItem> ParentItem)
{
if (!ParentItem.IsValid())
{
// If no parent is specified, use the root node
if (NodeItems.Num() > 0)
{
ParentItem = NodeItems[0];
}
else
{
return;
}
}
// Generate a unique name for the new node
FString BaseName = NodeType;
FString UniqueName = BaseName;
int32 Counter = 1;
bool bNameExists = true;
while (bNameExists)
{
bNameExists = false;
// Check if the name already exists in the parent's children
for (const TSharedPtr<FVisceraNodeItem>& Child : ParentItem->Children)
{
if (Child->DisplayName == UniqueName)
{
bNameExists = true;
UniqueName = FString::Printf(TEXT("%s_%d"), *BaseName, Counter++);
break;
}
}
}
// Create the new node
TSharedPtr<FVisceraNodeItem> NewNode = MakeShareable(new FVisceraNodeItem(
FName(*UniqueName),
UniqueName,
NodeType
));
// Add the new node to the parent
ParentItem->AddChild(NewNode);
// Refresh the tree view
NodeTreeView->RequestTreeRefresh();
// Select the new node
NodeTreeView->SetItemSelection(NewNode, true);
}
#undef LOCTEXT_NAMESPACE