This commit is contained in:
2025-04-17 23:59:17 +08:00
commit 88536f22da
57 changed files with 8094 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
#include "DismembermentEditor.h"
#include "Widgets/Docking/SDockTab.h"
#include "PropertyEditorModule.h"
#include "Modules/ModuleManager.h"
#include "IDetailsView.h"
#include "Widgets/Layout/SBorder.h"
#include "EditorStyleSet.h"
#include "Engine/SkeletalMesh.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ToolMenus.h"
// Define tab names
const FName FDismembermentEditor::ViewportTabId(TEXT("DismembermentEditor_Viewport"));
const FName FDismembermentEditor::DetailsTabId(TEXT("DismembermentEditor_Details"));
const FName FDismembermentEditor::LayerSystemTabId(TEXT("DismembermentEditor_LayerSystem"));
const FName FDismembermentEditor::PhysicsSettingsTabId(TEXT("DismembermentEditor_PhysicsSettings"));
// Constructor
FDismembermentEditor::FDismembermentEditor()
: SkeletalMesh(nullptr)
{
}
// Destructor
FDismembermentEditor::~FDismembermentEditor()
{
}
// Initialize the editor
void FDismembermentEditor::InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh)
{
// Set the skeletal mesh being edited
SkeletalMesh = InSkeletalMesh;
// Create the layout
CreateEditorLayout();
// Create the toolbar
CreateEditorToolbar();
// Initialize the asset editor
InitAssetEditor(Mode, InitToolkitHost, FName("DismembermentEditorApp"),
FTabManager::FLayout::NullLayout,
/*bCreateDefaultStandaloneMenu=*/ true,
/*bCreateDefaultToolbar=*/ true,
InSkeletalMesh);
// Register for undo/redo
GEditor->RegisterForUndo(this);
}
// Get the toolkit name
FName FDismembermentEditor::GetToolkitFName() const
{
return FName("DismembermentEditor");
}
// Get the base toolkit name
FText FDismembermentEditor::GetBaseToolkitName() const
{
return FText::FromString("Dismemberment Editor");
}
// Get the world centric tab prefix
FString FDismembermentEditor::GetWorldCentricTabPrefix() const
{
return "DismembermentEditor";
}
// Get the world centric tab color scale
FLinearColor FDismembermentEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
}
// Post undo handler
void FDismembermentEditor::PostUndo(bool bSuccess)
{
// Refresh the views
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
// Post redo handler
void FDismembermentEditor::PostRedo(bool bSuccess)
{
// Refresh the views
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
// Create the editor layout
void FDismembermentEditor::CreateEditorLayout()
{
// Register tab spawners
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_DismembermentEditor_Layout_v1")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(GetToolbarTabId(), ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->AddTab(ViewportTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.3f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab(DetailsTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(LayerSystemTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab)
)
)
)
);
// Set the layout
FAssetEditorToolkit::InitAssetEditor(
EToolkitMode::Standalone,
TSharedPtr<IToolkitHost>(),
FName("DismembermentEditorApp"),
StandaloneDefaultLayout,
true,
true,
SkeletalMesh
);
}
// Create the editor toolbar
void FDismembermentEditor::CreateEditorToolbar()
{
// Create command list
TSharedPtr<FUICommandList> CommandList = MakeShareable(new FUICommandList);
// Get the toolbar builder
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.BeginSection("Dismemberment");
{
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PerformBooleanCut)),
NAME_None,
FText::FromString("Boolean Cut"),
FText::FromString("Perform a boolean cut operation"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.CurveBase"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::AddNewLayer)),
NAME_None,
FText::FromString("Add Layer"),
FText::FromString("Add a new anatomical layer"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Layer"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PreviewEffects)),
NAME_None,
FText::FromString("Preview"),
FText::FromString("Preview the dismemberment effects"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.ParticleSystem"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::SaveEdits)),
NAME_None,
FText::FromString("Save"),
FText::FromString("Save the dismemberment setup"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetEditor.SaveAsset"),
EUserInterfaceActionType::Button
);
}
ToolbarBuilder.EndSection();
})
);
// Add the extender
AddToolbarExtender(ToolbarExtender);
}
// Register tab spawners
void FDismembermentEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// Call the base implementation
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
// Register the tab spawners
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Viewport))
.SetDisplayName(FText::FromString("Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Details))
.SetDisplayName(FText::FromString("Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_LayerSystem))
.SetDisplayName(FText::FromString("Layer System"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Layer"));
InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_PhysicsSettings))
.SetDisplayName(FText::FromString("Physics Settings"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.PhysicsAsset"));
}
// Unregister tab spawners
void FDismembermentEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// Call the base implementation
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
// Unregister the tab spawners
InTabManager->UnregisterTabSpawner(ViewportTabId);
InTabManager->UnregisterTabSpawner(DetailsTabId);
InTabManager->UnregisterTabSpawner(LayerSystemTabId);
InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId);
}
// Spawn the viewport tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
// Create the viewport widget
ViewportWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Viewport will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Viewport"))
[
ViewportWidget.ToSharedRef()
];
}
// Spawn the details tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
{
// Create the details view
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = false;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
DetailsWidget->SetObject(SkeletalMesh);
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Details"))
[
DetailsWidget.ToSharedRef()
];
}
// Spawn the layer system tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_LayerSystem(const FSpawnTabArgs& Args)
{
// Create the layer system widget
LayerSystemWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Layer System will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Layer System"))
[
LayerSystemWidget.ToSharedRef()
];
}
// Spawn the physics settings tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args)
{
// Create the physics settings widget
PhysicsSettingsWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Physics Settings will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Physics Settings"))
[
PhysicsSettingsWidget.ToSharedRef()
];
}
// Perform a boolean cut operation
void FDismembermentEditor::PerformBooleanCut()
{
// To be implemented
}
// Add a new layer
void FDismembermentEditor::AddNewLayer()
{
// To be implemented
}
// Save the edits
void FDismembermentEditor::SaveEdits()
{
// To be implemented
}
// Preview the effects
void FDismembermentEditor::PreviewEffects()
{
// To be implemented
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "EditorUndoClient.h"
class USkeletalMesh;
class SDockTab;
class IDetailsView;
class SBorder;
/**
* Dismemberment System Editor
* Provides real-time boolean cutting and multi-layer system editing functionality
*/
class FDismembermentEditor : public FAssetEditorToolkit, public FEditorUndoClient
{
public:
FDismembermentEditor();
virtual ~FDismembermentEditor();
void InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh);
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
virtual void PostUndo(bool bSuccess) override;
virtual void PostRedo(bool bSuccess) override;
private:
void CreateEditorLayout();
void CreateEditorToolbar();
void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager);
void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager);
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_LayerSystem(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args);
void PerformBooleanCut();
void AddNewLayer();
void SaveEdits();
void PreviewEffects();
private:
USkeletalMesh* SkeletalMesh;
TSharedPtr<SBorder> ViewportWidget;
TSharedPtr<IDetailsView> DetailsWidget;
TSharedPtr<SBorder> LayerSystemWidget;
TSharedPtr<SBorder> PhysicsSettingsWidget;
static const FName ViewportTabId;
static const FName DetailsTabId;
static const FName LayerSystemTabId;
static const FName PhysicsSettingsTabId;
};

View File

@@ -0,0 +1,22 @@
#include "DismembermentGraph/DismembermentGraphEditorFactory.h"
#include "DismembermentGraph/DismembermentGraphAsset.h"
UDismembermentGraphEditorFactory::UDismembermentGraphEditorFactory()
{
// Factory configuration
SupportedClass = UDismembermentGraphAsset::StaticClass();
bCreateNew = true;
bEditAfterNew = true;
}
UObject* UDismembermentGraphEditorFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
// Create a new dismemberment graph asset
UDismembermentGraphAsset* NewAsset = NewObject<UDismembermentGraphAsset>(InParent, InClass, InName, Flags);
return NewAsset;
}
bool UDismembermentGraphEditorFactory::ShouldShowInNewMenu() const
{
return true;
}

View File

@@ -0,0 +1,181 @@
#include "DismembermentGraph/DismembermentGraphEditor.h"
#include "DismembermentGraph/DismembermentGraphAsset.h"
#include "DismembermentGraph/DismembermentGraph.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "EdGraphUtilities.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "DismembermentGraphEditor"
/**
* Dismemberment graph schema actions factory
*/
class FDismembermentGraphSchemaActionMenuBuilder : public FGraphSchemaActionMenuBuilder
{
public:
// Constructor
FDismembermentGraphSchemaActionMenuBuilder(const UEdGraphSchema* InSchema)
: FGraphSchemaActionMenuBuilder(InSchema)
{
}
};
/**
* Dismemberment graph editor module
*/
class FDismembermentGraphEditorModule : public IModuleInterface
{
public:
// IModuleInterface interface
virtual void StartupModule() override
{
// Register asset type actions
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
// Register asset category
DismembermentGraphAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Dismemberment")), LOCTEXT("DismembermentGraphAssetCategory", "Dismemberment"));
// Register asset type actions
TSharedPtr<IAssetTypeActions> Action = MakeShareable(new FDismembermentGraphAssetTypeActions(DismembermentGraphAssetCategoryBit));
AssetTools.RegisterAssetTypeActions(Action);
RegisteredAssetTypeActions.Add(Action);
// Register graph editor factory
TSharedPtr<FGraphPanelNodeFactory> NodeFactory = MakeShareable(new FDismembermentGraphNodeFactory);
FEdGraphUtilities::RegisterVisualNodeFactory(NodeFactory);
// Register node factories
RegisterNodeFactory(UDismembermentGraphNodeCut::StaticClass(), LOCTEXT("CutNode", "Cut Node"), LOCTEXT("CutNodeTooltip", "Performs a cut operation"));
RegisterNodeFactory(UDismembermentGraphNodeBoneSelect::StaticClass(), LOCTEXT("BoneSelectNode", "Bone Select Node"), LOCTEXT("BoneSelectNodeTooltip", "Selects bones for operations"));
RegisterNodeFactory(UDismembermentGraphNodeBloodEffect::StaticClass(), LOCTEXT("BloodEffectNode", "Blood Effect Node"), LOCTEXT("BloodEffectNodeTooltip", "Creates blood effects"));
}
virtual void ShutdownModule() override
{
// Unregister asset type actions
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
for (TSharedPtr<IAssetTypeActions> Action : RegisteredAssetTypeActions)
{
AssetTools.UnregisterAssetTypeActions(Action.ToSharedRef());
}
}
RegisteredAssetTypeActions.Empty();
// Unregister node factories
for (const auto& Factory : RegisteredNodeFactories)
{
FEdGraphUtilities::UnregisterVisualNodeFactory(Factory);
}
RegisteredNodeFactories.Empty();
}
// End of IModuleInterface interface
private:
// Asset category bit
EAssetTypeCategories::Type DismembermentGraphAssetCategoryBit;
// Registered asset type actions
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
// Registered node factories
TArray<TSharedPtr<FGraphPanelNodeFactory>> RegisteredNodeFactories;
// Register a node factory
void RegisterNodeFactory(UClass* NodeClass, const FText& DisplayName, const FText& Tooltip)
{
TSharedPtr<FDismembermentGraphNodeFactory> NodeFactory = MakeShareable(new FDismembermentGraphNodeFactory(NodeClass, DisplayName, Tooltip));
FEdGraphUtilities::RegisterVisualNodeFactory(NodeFactory);
RegisteredNodeFactories.Add(NodeFactory);
}
};
/**
* Dismemberment graph asset type actions
*/
class FDismembermentGraphAssetTypeActions : public FAssetTypeActions_Base
{
public:
// Constructor
FDismembermentGraphAssetTypeActions(EAssetTypeCategories::Type InAssetCategory)
: AssetCategory(InAssetCategory)
{
}
// FAssetTypeActions_Base interface
virtual FText GetName() const override { return LOCTEXT("DismembermentGraphAssetName", "Dismemberment Graph"); }
virtual FColor GetTypeColor() const override { return FColor(255, 64, 64); }
virtual UClass* GetSupportedClass() const override { return UDismembermentGraphAsset::StaticClass(); }
virtual uint32 GetCategories() override { return AssetCategory; }
virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return false; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor) override
{
for (UObject* Object : InObjects)
{
if (UDismembermentGraphAsset* GraphAsset = Cast<UDismembermentGraphAsset>(Object))
{
TSharedRef<FDismembermentGraphEditor> NewEditor = MakeShareable(new FDismembermentGraphEditor());
NewEditor->InitDismembermentGraphEditor(EToolkitMode::Standalone, EditWithinLevelEditor, GraphAsset);
}
}
}
// End of FAssetTypeActions_Base interface
private:
// Asset category
EAssetTypeCategories::Type AssetCategory;
};
/**
* Dismemberment graph node factory
*/
class FDismembermentGraphNodeFactory : public FGraphPanelNodeFactory
{
public:
// Constructor
FDismembermentGraphNodeFactory()
: NodeClass(nullptr)
{
}
FDismembermentGraphNodeFactory(UClass* InNodeClass, const FText& InDisplayName, const FText& InTooltip)
: NodeClass(InNodeClass)
, DisplayName(InDisplayName)
, Tooltip(InTooltip)
{
}
// FGraphPanelNodeFactory interface
virtual TSharedPtr<SGraphNode> CreateNode(UEdGraphNode* Node) const override
{
if (NodeClass && Node->IsA(NodeClass))
{
return SNew(SDismembermentGraphNode, Node);
}
return nullptr;
}
// End of FGraphPanelNodeFactory interface
private:
// Node class
UClass* NodeClass;
// Display name
FText DisplayName;
// Tooltip
FText Tooltip;
};
IMPLEMENT_MODULE(FDismembermentGraphEditorModule, FLESHEditor)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,459 @@
#include "DismembermentGraph/DismembermentGraphSchema.h"
#include "DismembermentGraph/DismembermentGraph.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "DismembermentGraph/DismembermentGraphNodePhysics.h"
#include "DismembermentGraph/DismembermentGraphNodeOrgan.h"
#include "DismembermentGraph/DismembermentGraphNodeWound.h"
#include "EdGraphNode_Comment.h"
#include "Framework/Commands/GenericCommands.h"
#include "GraphEditorActions.h"
#include "ToolMenus.h"
#define LOCTEXT_NAMESPACE "DismembermentGraphSchema"
// Pin categories
const FName UDismembermentGraphSchema::PC_Exec("Exec");
const FName UDismembermentGraphSchema::PC_Bone("Bone");
const FName UDismembermentGraphSchema::PC_Cut("Cut");
const FName UDismembermentGraphSchema::PC_Blood("Blood");
const FName UDismembermentGraphSchema::PC_Physics("Physics");
const FName UDismembermentGraphSchema::PC_Organ("Organ");
const FName UDismembermentGraphSchema::PC_Wound("Wound");
void UDismembermentGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
// Add node actions
const FText ToolTip = LOCTEXT("NewDismembermentNodeTooltip", "Add node here");
const FText MenuDesc = LOCTEXT("NewDismembermentNodeMenuDesc", "Add Node");
TArray<TSubclassOf<UDismembermentGraphNode>> NodeClasses;
NodeClasses.Add(UDismembermentGraphNodeCut::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeBoneSelect::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeBloodEffect::StaticClass());
NodeClasses.Add(UDismembermentGraphNodePhysics::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeOrgan::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeWound::StaticClass());
for (TSubclassOf<UDismembermentGraphNode> NodeClass : NodeClasses)
{
UDismembermentGraphNode* NodeTemplate = NodeClass->GetDefaultObject<UDismembermentGraphNode>();
FText NodeCategory = NodeTemplate->NodeCategory;
FText NodeTitle = NodeTemplate->GetNodeTitle(ENodeTitleType::ListView);
FText NodeTooltip = NodeTemplate->GetTooltipText();
TSharedPtr<FDismembermentSchemaAction_NewNode> NewNodeAction(new FDismembermentSchemaAction_NewNode(
NodeCategory,
NodeTitle,
NodeTooltip,
0));
NewNodeAction->NodeClass = NodeClass;
ContextMenuBuilder.AddAction(NewNodeAction);
}
// Add comment node
TSharedPtr<FDismembermentSchemaAction_NewNode> NewCommentAction(new FDismembermentSchemaAction_NewNode(
FText::GetEmpty(),
LOCTEXT("NewComment", "Comment"),
LOCTEXT("NewCommentTooltip", "Add a comment node"),
0));
NewCommentAction->NodeClass = UEdGraphNode_Comment::StaticClass();
ContextMenuBuilder.AddAction(NewCommentAction);
}
void UDismembermentGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
if (Context && Context->Node)
{
FToolMenuSection& Section = Menu->AddSection("DismembermentGraphNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
// Add preview action
Section.AddMenuEntry(
"PreviewNode",
LOCTEXT("PreviewNode", "Preview Node"),
LOCTEXT("PreviewNodeTooltip", "Preview the effect of this node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Context]()
{
if (UDismembermentGraphNode* Node = Cast<UDismembermentGraphNode>(Context->Node))
{
// TODO: Implement node preview
}
}),
FCanExecuteAction::CreateLambda([Context]()
{
return Context->Node != nullptr;
})
)
);
}
// Add general graph actions
{
FToolMenuSection& Section = Menu->AddSection("DismembermentGraphActions", LOCTEXT("GraphActionsMenuHeader", "Graph Actions"));
Section.AddMenuEntry(FGraphEditorCommands::Get().SelectAllNodes);
Section.AddMenuEntry(
"ArrangeNodes",
LOCTEXT("ArrangeNodes", "Arrange Nodes"),
LOCTEXT("ArrangeNodesTooltip", "Arrange the nodes in the graph"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Context]()
{
if (Context && Context->Graph)
{
// TODO: Implement node arrangement
}
}),
FCanExecuteAction::CreateLambda([Context]()
{
return Context && Context->Graph;
})
)
);
}
}
const FPinConnectionResponse UDismembermentGraphSchema::CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
{
// Make sure the pins are valid
if (!A || !B)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Invalid pins"));
}
// Make sure the pins are not on the same node
if (A->GetOwningNode() == B->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("SameNode", "Can't connect pins on the same node"));
}
// Check pin directions
const UEdGraphPin* InputPin = nullptr;
const UEdGraphPin* OutputPin = nullptr;
if (A->Direction == EGPD_Input && B->Direction == EGPD_Output)
{
InputPin = A;
OutputPin = B;
}
else if (A->Direction == EGPD_Output && B->Direction == EGPD_Input)
{
InputPin = B;
OutputPin = A;
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("IncompatibleDirections", "Incompatible pin directions"));
}
// Check pin types
FDismembermentGraphPinType InputType = GetPinType(InputPin);
FDismembermentGraphPinType OutputType = GetPinType(OutputPin);
if (InputType != OutputType)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("IncompatibleTypes", "Incompatible pin types"));
}
// Check for cycles
if (WouldCreateCycle(OutputPin, InputPin))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("CycleDetected", "Connection would create a cycle"));
}
// Check if the pins are already connected
for (int32 i = 0; i < InputPin->LinkedTo.Num(); ++i)
{
if (InputPin->LinkedTo[i] == OutputPin)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("AlreadyConnected", "Pins are already connected"));
}
}
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, LOCTEXT("CreateConnection", "Create Connection"));
}
bool UDismembermentGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
{
// Check if the connection is valid
const FPinConnectionResponse Response = CanCreateConnection(A, B);
if (Response.Response != CONNECT_RESPONSE_MAKE)
{
return false;
}
// Break existing connections on input pins
if (A->Direction == EGPD_Input)
{
A->BreakAllPinLinks();
}
else if (B->Direction == EGPD_Input)
{
B->BreakAllPinLinks();
}
// Make the connection
A->MakeLinkTo(B);
return true;
}
bool UDismembermentGraphSchema::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const
{
return Pin && Pin->LinkedTo.Num() > 0;
}
FLinearColor UDismembermentGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
FDismembermentGraphPinType DismembermentPinType;
DismembermentPinType.PinCategory = PinType.PinCategory;
return GetPinTypeColor(DismembermentPinType);
}
void UDismembermentGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const
{
Super::BreakNodeLinks(TargetNode);
}
void UDismembermentGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const
{
Super::BreakPinLinks(TargetPin, bSendsNodeNotification);
}
void UDismembermentGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const
{
Super::BreakSinglePinLink(SourcePin, TargetPin);
}
void UDismembermentGraphSchema::DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
{
// TODO: Implement asset dropping
}
void UDismembermentGraphSchema::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const
{
// TODO: Implement asset dropping on nodes
}
void UDismembermentGraphSchema::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const
{
// TODO: Implement asset dropping on pins
}
void UDismembermentGraphSchema::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
{
// TODO: Implement asset hover message
OutTooltipText = TEXT("Drop to create a reference");
OutOkIcon = true;
}
void UDismembermentGraphSchema::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
{
// TODO: Implement asset hover message
OutTooltipText = TEXT("Drop to create a reference");
OutOkIcon = true;
}
bool UDismembermentGraphSchema::CanConnectPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, FDismembermentGraphConnectionResponse& OutResponse) const
{
// Check if the pins are valid
if (!PinA || !PinB)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("PinError", "Invalid pins");
return false;
}
// Check if the pins are on the same node
if (PinA->GetOwningNode() == PinB->GetOwningNode())
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_SELF_CONNECTION;
OutResponse.Message = LOCTEXT("SameNode", "Can't connect pins on the same node");
return false;
}
// Check pin directions
const UEdGraphPin* InputPin = nullptr;
const UEdGraphPin* OutputPin = nullptr;
if (PinA->Direction == EGPD_Input && PinB->Direction == EGPD_Output)
{
InputPin = PinA;
OutputPin = PinB;
}
else if (PinA->Direction == EGPD_Output && PinB->Direction == EGPD_Input)
{
InputPin = PinB;
OutputPin = PinA;
}
else
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("IncompatibleDirections", "Incompatible pin directions");
return false;
}
// Check pin types
FDismembermentGraphPinType InputType = GetPinType(InputPin);
FDismembermentGraphPinType OutputType = GetPinType(OutputPin);
if (InputType != OutputType)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("IncompatibleTypes", "Incompatible pin types");
return false;
}
// Check for cycles
if (WouldCreateCycle(OutputPin, InputPin))
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_CYCLE;
OutResponse.Message = LOCTEXT("CycleDetected", "Connection would create a cycle");
return false;
}
// Check if the pins are already connected
for (int32 i = 0; i < InputPin->LinkedTo.Num(); ++i)
{
if (InputPin->LinkedTo[i] == OutputPin)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_DISALLOWED;
OutResponse.Message = LOCTEXT("AlreadyConnected", "Pins are already connected");
return false;
}
}
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::OK;
return true;
}
bool UDismembermentGraphSchema::WouldCreateCycle(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
// Check if connecting these pins would create a cycle
const UEdGraphNode* NodeA = PinA->GetOwningNode();
const UEdGraphNode* NodeB = PinB->GetOwningNode();
// If the nodes are the same, there's a cycle
if (NodeA == NodeB)
{
return true;
}
// Depth-first search to check for cycles
TSet<const UEdGraphNode*> VisitedNodes;
TArray<const UEdGraphNode*> NodesToVisit;
NodesToVisit.Push(NodeB);
while (NodesToVisit.Num() > 0)
{
const UEdGraphNode* CurrentNode = NodesToVisit.Pop();
if (VisitedNodes.Contains(CurrentNode))
{
continue;
}
VisitedNodes.Add(CurrentNode);
// Check all output pins
for (int32 PinIndex = 0; PinIndex < CurrentNode->Pins.Num(); ++PinIndex)
{
const UEdGraphPin* Pin = CurrentNode->Pins[PinIndex];
if (Pin->Direction == EGPD_Output)
{
// Check all connections
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
const UEdGraphPin* LinkedPin = Pin->LinkedTo[LinkIndex];
const UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode();
// If we found NodeA, there's a cycle
if (LinkedNode == NodeA)
{
return true;
}
// Add the linked node to the nodes to visit
NodesToVisit.Push(LinkedNode);
}
}
}
}
return false;
}
FDismembermentGraphPinType UDismembermentGraphSchema::GetPinType(const UEdGraphPin* Pin)
{
return FDismembermentGraphPinType(Pin->PinType.PinCategory);
}
FLinearColor UDismembermentGraphSchema::GetPinTypeColor(const FDismembermentGraphPinType& PinType)
{
if (PinType.PinCategory == PC_Exec)
{
return FLinearColor::White;
}
else if (PinType.PinCategory == PC_Bone)
{
return FLinearColor(0.9f, 0.9f, 0.2f);
}
else if (PinType.PinCategory == PC_Cut)
{
return FLinearColor(1.0f, 0.0f, 0.0f);
}
else if (PinType.PinCategory == PC_Blood)
{
return FLinearColor(0.8f, 0.0f, 0.0f);
}
else if (PinType.PinCategory == PC_Physics)
{
return FLinearColor(0.0f, 0.8f, 0.8f);
}
else if (PinType.PinCategory == PC_Organ)
{
return FLinearColor(0.8f, 0.4f, 0.4f);
}
else if (PinType.PinCategory == PC_Wound)
{
return FLinearColor(0.6f, 0.0f, 0.0f);
}
return FLinearColor::Black;
}
UDismembermentGraphNode* UDismembermentGraphSchema::CreateNode(TSubclassOf<UDismembermentGraphNode> NodeClass, UEdGraph* ParentGraph, float NodePosX, float NodePosY, bool bSelectNewNode)
{
UDismembermentGraphNode* NewNode = NewObject<UDismembermentGraphNode>(ParentGraph, NodeClass);
NewNode->CreateNewGuid();
NewNode->PostPlacedNewNode();
NewNode->NodePosX = NodePosX;
NewNode->NodePosY = NodePosY;
NewNode->AllocateDefaultPins();
ParentGraph->AddNode(NewNode, true, bSelectNewNode);
return NewNode;
}
// Schema action for creating a new node
FDismembermentSchemaAction_NewNode::FDismembermentSchemaAction_NewNode(const FText& InNodeCategory, const FText& InMenuDesc, const FText& InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping)
{
}
UEdGraphNode* FDismembermentSchemaAction_NewNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
UDismembermentGraphNode* NewNode = UDismembermentGraphSchema::CreateNode(NodeClass, ParentGraph, Location.X, Location.Y, bSelectNewNode);
return NewNode;
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,787 @@
#include "DismembermentGraph/DismembermentPreviewManager.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "DismembermentGraph/DismembermentGraphNodePhysics.h"
#include "DismembermentGraph/DismembermentGraphNodeOrgan.h"
#include "DismembermentGraph/DismembermentGraphNodeWound.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/DecalComponent.h"
#include "NiagaraComponent.h"
#include "NiagaraSystem.h"
#include "Engine/StaticMesh.h"
#include "Materials/MaterialInterface.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
// Define log category
DEFINE_LOG_CATEGORY(LogFLESHPreview);
UDismembermentPreviewManager::UDismembermentPreviewManager()
: World(nullptr)
, TargetActor(nullptr)
, TargetSkeletalMesh(nullptr)
, PreviewedNode(nullptr)
, PreviewCutPlaneMesh(nullptr)
{
}
void UDismembermentPreviewManager::Initialize(TObjectPtr<UWorld> InWorld)
{
World = InWorld;
UE_LOG(LogFLESHPreview, Log, TEXT("Preview manager initialized"));
}
void UDismembermentPreviewManager::Cleanup()
{
UE_LOG(LogFLESHPreview, Log, TEXT("Cleaning up preview manager"));
ClearPreview();
World = nullptr;
TargetActor = nullptr;
TargetSkeletalMesh = nullptr;
}
void UDismembermentPreviewManager::SetTargetActor(TObjectPtr<AActor> InActor)
{
// Clear any existing preview
ClearPreview();
// Set the new target actor
TargetActor = InActor;
if (TargetActor)
{
UE_LOG(LogFLESHPreview, Log, TEXT("Set target actor: %s"), *TargetActor->GetName());
// Find the skeletal mesh component
FindTargetSkeletalMesh();
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Set empty target actor"));
}
}
bool UDismembermentPreviewManager::PreviewNode(TObjectPtr<UDismembermentGraphNode> Node)
{
// Clear any existing preview
ClearPreview();
// Set the new previewed node
PreviewedNode = Node;
// Check if we have a valid target
if (!TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Invalid target actor or skeletal mesh"));
return false;
}
if (!Node)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Node is null"));
return false;
}
UE_LOG(LogFLESHPreview, Log, TEXT("Previewing node: %s"), *Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
// Preview based on node type
if (TObjectPtr<UDismembermentGraphNodeCut> CutNode = Cast<UDismembermentGraphNodeCut>(Node))
{
return PreviewCutNode(CutNode);
}
else if (TObjectPtr<UDismembermentGraphNodeBoneSelect> BoneSelectNode = Cast<UDismembermentGraphNodeBoneSelect>(Node))
{
return PreviewBoneSelectNode(BoneSelectNode);
}
else if (TObjectPtr<UDismembermentGraphNodeBloodEffect> BloodEffectNode = Cast<UDismembermentGraphNodeBloodEffect>(Node))
{
return PreviewBloodEffectNode(BloodEffectNode);
}
else if (TObjectPtr<UDismembermentGraphNodePhysics> PhysicsNode = Cast<UDismembermentGraphNodePhysics>(Node))
{
return PreviewPhysicsNode(PhysicsNode);
}
else if (TObjectPtr<UDismembermentGraphNodeOrgan> OrganNode = Cast<UDismembermentGraphNodeOrgan>(Node))
{
return PreviewOrganNode(OrganNode);
}
else if (TObjectPtr<UDismembermentGraphNodeWound> WoundNode = Cast<UDismembermentGraphNodeWound>(Node))
{
return PreviewWoundNode(WoundNode);
}
UE_LOG(LogFLESHPreview, Warning, TEXT("Unknown node type"));
return false;
}
void UDismembermentPreviewManager::ClearPreview()
{
// Clear the previewed node
PreviewedNode = nullptr;
// Clear all preview components
ClearPreviewComponents();
// Clear preview data
PreviewBoneSelections.Empty();
PreviewCutTransforms.Empty();
PreviewBloodEffectTransforms.Empty();
PreviewOrganTransforms.Empty();
PreviewWoundTransforms.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Preview cleared"));
}
void UDismembermentPreviewManager::Tick(float DeltaTime)
{
// Update preview effects
if (PreviewedNode && TargetActor && TargetSkeletalMesh)
{
// Update based on node type
if (UDismembermentGraphNodeCut* CutNode = Cast<UDismembermentGraphNodeCut>(PreviewedNode))
{
// Update cut preview
}
else if (UDismembermentGraphNodeBloodEffect* BloodEffectNode = Cast<UDismembermentGraphNodeBloodEffect>(PreviewedNode))
{
// Update blood effect preview
}
else if (UDismembermentGraphNodePhysics* PhysicsNode = Cast<UDismembermentGraphNodePhysics>(PreviewedNode))
{
// Update physics preview
}
}
}
bool UDismembermentPreviewManager::FindTargetSkeletalMesh()
{
TargetSkeletalMesh = nullptr;
if (TargetActor)
{
// Find the first skeletal mesh component
TargetSkeletalMesh = TargetActor->FindComponentByClass<USkeletalMeshComponent>();
if (TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Log, TEXT("Found target skeletal mesh: %s"), *TargetSkeletalMesh->GetName());
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("No skeletal mesh component found on actor %s"), *TargetActor->GetName());
}
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot find skeletal mesh: Target actor is null"));
}
return TargetSkeletalMesh != nullptr;
}
bool UDismembermentPreviewManager::PreviewCutNode(TObjectPtr<UDismembermentGraphNodeCut> CutNode)
{
if (!CutNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview cut node: Invalid parameters"));
return false;
}
// Get the cut parameters
float CutWidth = CutNode->CutWidth;
float CutDepth = CutNode->CutDepth;
UMaterialInterface* CutMaterial = CutNode->bUseCustomMaterial ? CutNode->CustomCutMaterial : nullptr;
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default cut direction if not connected to an input
FVector CutLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
FVector CutDirection = FVector(1.0f, 0.0f, 0.0f);
// Create the preview cut plane mesh
PreviewCutPlaneMesh = CreatePreviewCutPlaneMesh(CutLocation, CutDirection, CutWidth, CutDepth, CutMaterial);
if (!PreviewCutPlaneMesh)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane mesh"));
return false;
}
// Store the cut transform for later use
FTransform CutTransform;
CutTransform.SetLocation(CutLocation);
CutTransform.SetRotation(FQuat::MakeFromX(CutDirection));
CutTransform.SetScale3D(FVector(CutWidth, CutDepth, 1.0f));
PreviewCutTransforms.Add(CutTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Cut node preview created successfully"));
return PreviewCutPlaneMesh != nullptr;
}
bool UDismembermentPreviewManager::PreviewBoneSelectNode(TObjectPtr<UDismembermentGraphNodeBoneSelect> BoneSelectNode)
{
if (!BoneSelectNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview bone select node: Invalid parameters"));
return false;
}
// Get the bone selection parameters
TArray<FName> BoneNames = BoneSelectNode->BoneNames;
bool bUseRegex = BoneSelectNode->bUseRegex;
FString BoneNamePattern = BoneSelectNode->BoneNamePattern;
bool bIncludeChildren = BoneSelectNode->bIncludeChildren;
// Store the bone selections for later use
PreviewBoneSelections.Append(BoneNames);
UE_LOG(LogFLESHPreview, Log, TEXT("Bone select node preview created successfully, selected %d bones"), BoneNames.Num());
// TODO: Handle regex and child bones
return true;
}
bool UDismembermentPreviewManager::PreviewBloodEffectNode(TObjectPtr<UDismembermentGraphNodeBloodEffect> BloodEffectNode)
{
if (!BloodEffectNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect node: Invalid parameters"));
return false;
}
// Get the blood effect parameters
UNiagaraSystem* BloodEffect = BloodEffectNode->BloodEffect;
float BloodAmount = BloodEffectNode->BloodAmount;
float BloodPressure = BloodEffectNode->BloodPressure;
bool bCreateBloodPool = BloodEffectNode->bCreateBloodPool;
float BloodPoolSize = BloodEffectNode->BloodPoolSize;
UMaterialInterface* BloodPoolMaterial = BloodEffectNode->BloodPoolMaterial;
if (!BloodEffect)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect: No blood effect system specified"));
return false;
}
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default blood effect location if not connected to an input
FVector BloodLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
// Create the preview blood effect
TObjectPtr<UNiagaraComponent> BloodEffectComponent = CreatePreviewBloodEffect(BloodLocation, BloodEffect, BloodAmount, BloodPressure);
if (!BloodEffectComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component"));
return false;
}
// Create the preview blood pool if needed
TObjectPtr<UDecalComponent> BloodPoolComponent = nullptr;
if (bCreateBloodPool)
{
BloodPoolComponent = CreatePreviewBloodPool(BloodLocation, BloodPoolSize, BloodPoolMaterial);
if (!BloodPoolComponent)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create blood pool component"));
}
}
// Store the blood effect transform for later use
FTransform BloodEffectTransform;
BloodEffectTransform.SetLocation(BloodLocation);
PreviewBloodEffectTransforms.Add(BloodEffectTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Blood effect node preview created successfully"));
return BloodEffectComponent != nullptr;
}
bool UDismembermentPreviewManager::PreviewPhysicsNode(TObjectPtr<UDismembermentGraphNodePhysics> PhysicsNode)
{
if (!PhysicsNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview physics node: Invalid parameters"));
return false;
}
// Get the physics parameters
float Mass = PhysicsNode->Mass;
float LinearDamping = PhysicsNode->LinearDamping;
float AngularDamping = PhysicsNode->AngularDamping;
bool bEnableGravity = PhysicsNode->bEnableGravity;
bool bSimulatePhysics = PhysicsNode->bSimulatePhysics;
bool bGenerateOverlapEvents = PhysicsNode->bGenerateOverlapEvents;
UPhysicalMaterial* PhysicalMaterial = PhysicsNode->PhysicalMaterial;
float ImpulseForce = PhysicsNode->ImpulseForce;
float ImpulseRadius = PhysicsNode->ImpulseRadius;
// Apply physics settings to the target skeletal mesh
if (TargetSkeletalMesh)
{
// Store original values to restore later
TargetSkeletalMesh->SetMassOverrideInKg(NAME_None, Mass, true);
TargetSkeletalMesh->SetLinearDamping(LinearDamping);
TargetSkeletalMesh->SetAngularDamping(AngularDamping);
TargetSkeletalMesh->SetEnableGravity(bEnableGravity);
TargetSkeletalMesh->SetSimulatePhysics(bSimulatePhysics);
TargetSkeletalMesh->SetGenerateOverlapEvents(bGenerateOverlapEvents);
if (PhysicalMaterial)
{
TargetSkeletalMesh->SetPhysMaterialOverride(PhysicalMaterial);
}
UE_LOG(LogFLESHPreview, Log, TEXT("Physics node preview applied successfully, mass: %.2f, linear damping: %.2f, angular damping: %.2f"),
Mass, LinearDamping, AngularDamping);
}
return true;
}
bool UDismembermentPreviewManager::PreviewOrganNode(TObjectPtr<UDismembermentGraphNodeOrgan> OrganNode)
{
if (!OrganNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ node: Invalid parameters"));
return false;
}
// Get the organ parameters
UStaticMesh* OrganMesh = OrganNode->OrganMesh;
UMaterialInterface* OrganMaterial = OrganNode->OrganMaterial;
FName AttachBoneName = OrganNode->AttachBoneName;
FVector RelativeLocation = OrganNode->RelativeLocation;
FRotator RelativeRotation = OrganNode->RelativeRotation;
FVector RelativeScale = OrganNode->RelativeScale;
bool bSimulatePhysics = OrganNode->bSimulatePhysics;
float DamageMultiplier = OrganNode->DamageMultiplier;
bool bIsCriticalOrgan = OrganNode->bIsCriticalOrgan;
float BloodAmount = OrganNode->BloodAmount;
if (!OrganMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ: No organ mesh specified"));
return false;
}
// Create the preview organ
TObjectPtr<UStaticMeshComponent> OrganComponent = CreatePreviewOrgan(OrganMesh, OrganMaterial, AttachBoneName, RelativeLocation, RelativeRotation, RelativeScale);
if (!OrganComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component"));
return false;
}
// Store the organ transform for later use
FTransform OrganTransform;
if (TargetSkeletalMesh && !AttachBoneName.IsNone())
{
FTransform BoneTransform = TargetSkeletalMesh->GetBoneTransform(TargetSkeletalMesh->GetBoneIndex(AttachBoneName));
OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * BoneTransform;
}
else
{
OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * TargetActor->GetActorTransform();
}
PreviewOrganTransforms.Add(OrganTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Organ node preview created successfully, attached to bone: %s"),
AttachBoneName.IsNone() ? TEXT("None") : *AttachBoneName.ToString());
return OrganComponent != nullptr;
}
bool UDismembermentPreviewManager::PreviewWoundNode(TObjectPtr<UDismembermentGraphNodeWound> WoundNode)
{
if (!WoundNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview wound node: Invalid parameters"));
return false;
}
// Get the wound parameters
float WoundSize = WoundNode->WoundSize;
float WoundDepth = WoundNode->WoundDepth;
UMaterialInterface* WoundMaterial = WoundNode->WoundMaterial;
UNiagaraSystem* WoundEffect = WoundNode->WoundEffect;
bool bCreateDecal = WoundNode->bCreateDecal;
UMaterialInterface* DecalMaterial = WoundNode->DecalMaterial;
float DecalSize = WoundNode->DecalSize;
float DecalLifetime = WoundNode->DecalLifetime;
bool bAffectBoneHealth = WoundNode->bAffectBoneHealth;
float BoneDamage = WoundNode->BoneDamage;
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default wound location if not connected to an input
FVector WoundLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
// Create the preview wound effect
TObjectPtr<UNiagaraComponent> WoundEffectComponent = nullptr;
if (WoundEffect)
{
WoundEffectComponent = UNiagaraComponent::SpawnSystemAttached(
WoundEffect,
TargetSkeletalMesh,
NAME_None,
WoundLocation,
FRotator::ZeroRotator,
EAttachLocation::KeepWorldPosition,
false);
if (WoundEffectComponent)
{
PreviewNiagaraComponents.Add(WoundEffectComponent);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound effect component"));
}
}
// Create the preview wound decal if needed
TObjectPtr<UDecalComponent> WoundDecalComponent = nullptr;
if (bCreateDecal && DecalMaterial)
{
WoundDecalComponent = CreatePreviewWound(WoundLocation, DecalSize, DecalMaterial);
if (!WoundDecalComponent)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound decal component"));
}
}
// Store the wound transform for later use
FTransform WoundTransform;
WoundTransform.SetLocation(WoundLocation);
PreviewWoundTransforms.Add(WoundTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Wound node preview created successfully, size: %.2f, depth: %.2f"), WoundSize, WoundDepth);
return WoundEffectComponent != nullptr || WoundDecalComponent != nullptr;
}
TObjectPtr<UStaticMeshComponent> UDismembermentPreviewManager::CreatePreviewCutPlaneMesh(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create cut plane: Invalid world or target actor"));
return nullptr;
}
// Create a static mesh component for the cut plane
TObjectPtr<UStaticMeshComponent> CutPlaneMeshComponent = NewObject<UStaticMeshComponent>(TargetActor);
if (!CutPlaneMeshComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane component"));
return nullptr;
}
// Register the component
CutPlaneMeshComponent->RegisterComponent();
// Set the mesh to a plane
UStaticMesh* PlaneMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Plane.Plane"));
if (PlaneMesh)
{
CutPlaneMeshComponent->SetStaticMesh(PlaneMesh);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load plane mesh"));
}
// Set the material
if (Material)
{
CutPlaneMeshComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
CutPlaneMeshComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
FRotator Rotation = FRotationMatrix::MakeFromX(Direction).Rotator();
CutPlaneMeshComponent->SetWorldLocationAndRotation(Location, Rotation);
CutPlaneMeshComponent->SetWorldScale3D(FVector(Width, Depth, 1.0f));
// Add to the preview components
PreviewStaticMeshComponents.Add(CutPlaneMeshComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created cut plane mesh, location: %s, direction: %s"),
*Location.ToString(), *Direction.ToString());
return CutPlaneMeshComponent;
}
TObjectPtr<UNiagaraComponent> UDismembermentPreviewManager::CreatePreviewBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure)
{
if (!World || !TargetActor || !BloodEffect)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood effect: Invalid parameters"));
return nullptr;
}
// Create a Niagara component for the blood effect
TObjectPtr<UNiagaraComponent> BloodEffectComponent = UNiagaraComponent::SpawnSystemAttached(
BloodEffect,
TargetSkeletalMesh,
NAME_None,
Location,
FRotator::ZeroRotator,
EAttachLocation::KeepWorldPosition,
false);
if (!BloodEffectComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component"));
return nullptr;
}
// Set parameters
BloodEffectComponent->SetFloatParameter(TEXT("BloodAmount"), BloodAmount);
BloodEffectComponent->SetFloatParameter(TEXT("BloodPressure"), BloodPressure);
// Add to the preview components
PreviewNiagaraComponents.Add(BloodEffectComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood effect, location: %s, blood amount: %.2f, blood pressure: %.2f"),
*Location.ToString(), BloodAmount, BloodPressure);
return BloodEffectComponent;
}
TObjectPtr<UDecalComponent> UDismembermentPreviewManager::CreatePreviewBloodPool(const FVector& Location, float Size, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood pool: Invalid world or target actor"));
return nullptr;
}
// Create a decal component for the blood pool
TObjectPtr<UDecalComponent> BloodPoolComponent = NewObject<UDecalComponent>(TargetActor);
if (!BloodPoolComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood pool component"));
return nullptr;
}
// Register the component
BloodPoolComponent->RegisterComponent();
// Set the material
if (Material)
{
BloodPoolComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
BloodPoolComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
BloodPoolComponent->SetWorldLocation(Location);
BloodPoolComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f));
BloodPoolComponent->DecalSize = FVector(Size, Size, 10.0f);
// Add to the preview components
PreviewDecalComponents.Add(BloodPoolComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood pool decal, location: %s, size: %.2f"),
*Location.ToString(), Size);
return BloodPoolComponent;
}
TObjectPtr<UStaticMeshComponent> UDismembermentPreviewManager::CreatePreviewOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale)
{
if (!World || !TargetActor || !OrganMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create organ: Invalid parameters"));
return nullptr;
}
// Create a static mesh component for the organ
TObjectPtr<UStaticMeshComponent> OrganComponent = NewObject<UStaticMeshComponent>(TargetActor);
if (!OrganComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component"));
return nullptr;
}
// Register the component
OrganComponent->RegisterComponent();
// Set the mesh
OrganComponent->SetStaticMesh(OrganMesh);
// Set the material
if (OrganMaterial)
{
OrganComponent->SetMaterial(0, OrganMaterial);
}
// Attach to bone if specified
if (TargetSkeletalMesh && !AttachBoneName.IsNone())
{
if (TargetSkeletalMesh->GetBoneIndex(AttachBoneName) != INDEX_NONE)
{
OrganComponent->AttachToComponent(TargetSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachBoneName);
OrganComponent->SetRelativeLocationAndRotation(RelativeLocation, RelativeRotation);
OrganComponent->SetRelativeScale3D(RelativeScale);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Organ attached to bone: %s"), *AttachBoneName.ToString());
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Bone not found: %s, attaching to actor root component"), *AttachBoneName.ToString());
// Set the transform
OrganComponent->SetWorldLocationAndRotation(
TargetActor->GetActorLocation() + RelativeLocation,
TargetActor->GetActorRotation() + RelativeRotation);
OrganComponent->SetWorldScale3D(RelativeScale);
}
}
else
{
// Set the transform
OrganComponent->SetWorldLocationAndRotation(
TargetActor->GetActorLocation() + RelativeLocation,
TargetActor->GetActorRotation() + RelativeRotation);
OrganComponent->SetWorldScale3D(RelativeScale);
}
// Add to the preview components
PreviewStaticMeshComponents.Add(OrganComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created organ component, relative location: %s"), *RelativeLocation.ToString());
return OrganComponent;
}
TObjectPtr<UDecalComponent> UDismembermentPreviewManager::CreatePreviewWound(const FVector& Location, float Size, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create wound: Invalid world or target actor"));
return nullptr;
}
// Create a decal component for the wound
TObjectPtr<UDecalComponent> WoundComponent = NewObject<UDecalComponent>(TargetActor);
if (!WoundComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create wound component"));
return nullptr;
}
// Register the component
WoundComponent->RegisterComponent();
// Set the material
if (Material)
{
WoundComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
WoundComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
WoundComponent->SetWorldLocation(Location);
WoundComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f));
WoundComponent->DecalSize = FVector(Size, Size, 10.0f);
// Add to the preview components
PreviewDecalComponents.Add(WoundComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created wound decal, location: %s, size: %.2f"),
*Location.ToString(), Size);
return WoundComponent;
}
void UDismembermentPreviewManager::ClearPreviewComponents()
{
// Destroy all preview components
for (TObjectPtr<UNiagaraComponent> Component : PreviewNiagaraComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewNiagaraComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d Niagara components"), PreviewNiagaraComponents.Num());
for (TObjectPtr<UDecalComponent> Component : PreviewDecalComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewDecalComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d decal components"), PreviewDecalComponents.Num());
for (TObjectPtr<UStaticMeshComponent> Component : PreviewStaticMeshComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewStaticMeshComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d static mesh components"), PreviewStaticMeshComponents.Num());
// Clear the cut plane mesh
if (PreviewCutPlaneMesh)
{
PreviewCutPlaneMesh->DestroyComponent();
PreviewCutPlaneMesh = nullptr;
}
}

View File

@@ -0,0 +1,377 @@
#include "DismembermentGraph/SDismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "GraphEditorSettings.h"
#include "SGraphPin.h"
#include "SlateOptMacros.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Images/SImage.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SDismembermentGraphNode::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
GraphNode = InNode;
UpdateGraphNode();
bIsHovered = false;
}
void SDismembermentGraphNode::UpdateGraphNode()
{
// Clear the widget
ContentScale.Bind(this, &SGraphNode::GetContentScale);
LeftNodeBox.Reset();
RightNodeBox.Reset();
OutputPinBox.Reset();
// Setup the node title
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
// Create the node body
this->ContentScale.Bind(this, &SGraphNode::GetContentScale);
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.Body"))
.BorderBackgroundColor(this, &SDismembermentGraphNode::GetNodeColor)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(0.0f)
[
SNew(SOverlay)
// Main node body
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SVerticalBox)
// Title bar
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.ColorSpill"))
.BorderBackgroundColor(this, &SDismembermentGraphNode::GetNodeTitleColor)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Center)
.Padding(FMargin(10.0f, 5.0f))
[
SNew(SHorizontalBox)
// Node title
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeTitle)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle")
.Margin(FMargin(0.0f, 0.0f, 4.0f, 0.0f))
]
// Node category
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeCategory)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle")
.ColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.8f))
.Margin(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
]
]
]
// Node content
+ SVerticalBox::Slot()
.Padding(0.0f)
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(FMargin(10.0f, 0.0f))
[
SNew(SVerticalBox)
// Node description
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 4.0f))
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeDescription)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
.WrapTextAt(200.0f)
]
// Node preview
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 4.0f))
[
GetNodePreviewWidget()
]
]
]
// Input pins
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(LeftNodeBox, SVerticalBox)
]
// Output pins
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(RightNodeBox, SVerticalBox)
]
]
]
];
// Create all the pins
CreatePinWidgets();
}
void SDismembermentGraphNode::CreatePinWidgets()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return;
}
// Create the input pins
for (int32 PinIndex = 0; PinIndex < DismembermentNode->Pins.Num(); PinIndex++)
{
UEdGraphPin* Pin = DismembermentNode->Pins[PinIndex];
if (!Pin->bHidden)
{
TSharedPtr<SGraphPin> NewPin = SNew(SGraphPin, Pin);
AddPin(NewPin.ToSharedRef());
}
}
}
void SDismembermentGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
if (PinToAdd->GetDirection() == EGPD_Input)
{
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(10.0f, 4.0f)
[
PinToAdd
];
InputPins.Add(PinToAdd);
}
else // EGPD_Output
{
RightNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(10.0f, 4.0f)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
TSharedPtr<SToolTip> SDismembermentGraphNode::GetComplexTooltip()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return nullptr;
}
return SNew(SToolTip)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 2.0f))
[
SNew(STextBlock)
.Text(DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 2.0f))
[
SNew(STextBlock)
.Text(DismembermentNode->GetTooltipText())
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
]
];
}
FReply SDismembermentGraphNode::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Handled().DetectDrag(SharedThis(this), EKeys::LeftMouseButton);
}
FReply SDismembermentGraphNode::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Handled();
}
FReply SDismembermentGraphNode::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Unhandled();
}
void SDismembermentGraphNode::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
bIsHovered = true;
SGraphNode::OnMouseEnter(MyGeometry, MouseEvent);
}
void SDismembermentGraphNode::OnMouseLeave(const FPointerEvent& MouseEvent)
{
bIsHovered = false;
SGraphNode::OnMouseLeave(MouseEvent);
}
UDismembermentGraphNode* SDismembermentGraphNode::GetDismembermentGraphNode() const
{
return Cast<UDismembermentGraphNode>(GraphNode);
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodeTitleWidget()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return SNew(STextBlock).Text(NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node"));
}
return SNew(STextBlock)
.Text(DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle))
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle");
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodeBodyWidget()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return SNew(STextBlock).Text(NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node"));
}
return SNew(STextBlock)
.Text(DismembermentNode->GetTooltipText())
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
.WrapTextAt(200.0f);
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodePreviewWidget()
{
// This can be customized for different node types to show a preview
return SNew(SBox)
.WidthOverride(100.0f)
.HeightOverride(100.0f)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.Body"))
.BorderBackgroundColor(FLinearColor(0.1f, 0.1f, 0.1f, 0.5f))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(NSLOCTEXT("SDismembermentGraphNode", "Preview", "Preview"))
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
]
];
}
FSlateColor SDismembermentGraphNode::GetNodeColor() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FLinearColor::Black;
}
FLinearColor NodeColor = DismembermentNode->NodeTitleColor;
if (IsNodeSelected())
{
NodeColor = FLinearColor(1.0f, 0.5f, 0.0f);
}
else if (IsNodeHovered())
{
NodeColor = NodeColor * 1.2f;
}
return NodeColor;
}
FSlateColor SDismembermentGraphNode::GetNodeTitleColor() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FLinearColor::Black;
}
return DismembermentNode->NodeTitleColor.LinearRGBToHSV();
}
FText SDismembermentGraphNode::GetNodeTitle() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node");
}
return DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle);
}
FText SDismembermentGraphNode::GetNodeCategory() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FText::GetEmpty();
}
return DismembermentNode->NodeCategory;
}
FText SDismembermentGraphNode::GetNodeDescription() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FText::GetEmpty();
}
return DismembermentNode->NodeDescription;
}
bool SDismembermentGraphNode::IsNodeSelected() const
{
return GraphNode && GraphNode->IsSelected();
}
bool SDismembermentGraphNode::IsNodeHovered() const
{
return bIsHovered;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@@ -0,0 +1,477 @@
#include "DismembermentGraph/SDismembermentPreviewViewport.h"
#include "DismembermentGraph/DismembermentPreviewManager.h"
#include "EditorViewportClient.h"
#include "SEditorViewport.h"
#include "PreviewScene.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/StaticMesh.h"
#include "AdvancedPreviewScene.h"
#include "AssetViewerSettings.h"
#include "EditorModeManager.h"
#include "EngineUtils.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
// Preview actor class
class ADismembermentPreviewActor : public AActor
{
public:
ADismembermentPreviewActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Create a skeletal mesh component
SkeletalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMeshComponent"));
RootComponent = SkeletalMeshComponent;
}
// Set the skeletal mesh
void SetSkeletalMesh(USkeletalMesh* InSkeletalMesh)
{
if (SkeletalMeshComponent)
{
SkeletalMeshComponent->SetSkeletalMesh(InSkeletalMesh);
}
}
// Get the skeletal mesh component
USkeletalMeshComponent* GetSkeletalMeshComponent() const
{
return SkeletalMeshComponent;
}
private:
// The skeletal mesh component
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent;
};
//////////////////////////////////////////////////////////////////////////
// SDismembermentPreviewViewport
void SDismembermentPreviewViewport::Construct(const FArguments& InArgs)
{
// Create the preview scene
PreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues()));
// Set up the preview scene
UAssetViewerSettings* Settings = UAssetViewerSettings::Get();
const int32 ProfileIndex = Settings->Profiles.IsValidIndex(0) ? 0 : INDEX_NONE;
if (ProfileIndex != INDEX_NONE)
{
Settings->Profiles[ProfileIndex].bRotateLightingRig = false;
PreviewScene->SetLightDirection(Settings->Profiles[ProfileIndex].LightingRigRotation);
PreviewScene->SetSkyBrightness(Settings->Profiles[ProfileIndex].EnvironmentCubeMapBrightness);
PreviewScene->SetLightColor(Settings->Profiles[ProfileIndex].LightColor);
PreviewScene->SetLightIntensity(Settings->Profiles[ProfileIndex].LightBrightness);
PreviewScene->SetEnvironmentCubeMap(Settings->Profiles[ProfileIndex].EnvironmentCubeMap);
}
// Create the preview actor
CreatePreviewActor();
// Call parent constructor
SEditorViewport::Construct(SEditorViewport::FArguments());
}
SDismembermentPreviewViewport::~SDismembermentPreviewViewport()
{
// Clean up the preview scene
if (PreviewScene.IsValid())
{
PreviewScene->RemoveComponent(PreviewActor);
PreviewScene.Reset();
}
// Clean up the preview actor
if (PreviewActor)
{
if (PreviewActor->GetWorld())
{
PreviewActor->Destroy();
}
PreviewActor = nullptr;
}
// Clean up the viewport client
ViewportClient.Reset();
}
void SDismembermentPreviewViewport::SetPreviewManager(UDismembermentPreviewManager* InPreviewManager)
{
PreviewManager = InPreviewManager;
// Update the viewport client
if (ViewportClient.IsValid())
{
ViewportClient->SetPreviewManager(PreviewManager);
}
// Update the preview actor
if (PreviewManager && PreviewActor)
{
PreviewManager->SetTargetActor(PreviewActor);
}
}
void SDismembermentPreviewViewport::SetPreviewSkeletalMesh(USkeletalMesh* InSkeletalMesh)
{
// Set the skeletal mesh on the preview actor
if (PreviewActor)
{
ADismembermentPreviewActor* PreviewActorCasted = Cast<ADismembermentPreviewActor>(PreviewActor);
if (PreviewActorCasted)
{
PreviewActorCasted->SetSkeletalMesh(InSkeletalMesh);
}
}
// Update the preview actor
UpdatePreviewActor();
}
AActor* SDismembermentPreviewViewport::GetPreviewActor() const
{
return PreviewActor;
}
void SDismembermentPreviewViewport::RefreshViewport()
{
// Invalidate the viewport
if (ViewportClient.IsValid())
{
ViewportClient->Invalidate();
}
}
TSharedRef<FEditorViewportClient> SDismembermentPreviewViewport::MakeEditorViewportClient()
{
// Create the viewport client
ViewportClient = MakeShareable(new SDismembermentPreviewViewportClient(PreviewScene.Get(), SharedThis(this)));
// Set the preview manager
ViewportClient->SetPreviewManager(PreviewManager);
// Set up the viewport client
ViewportClient->SetViewLocation(FVector(0.0f, 0.0f, 200.0f));
ViewportClient->SetViewRotation(FRotator(-20.0f, 0.0f, 0.0f));
ViewportClient->SetViewLocationForOrbiting(FVector(0.0f, 0.0f, 0.0f));
ViewportClient->bSetListenerPosition = false;
ViewportClient->EngineShowFlags.SetPostProcessing(true);
ViewportClient->EngineShowFlags.SetLighting(true);
ViewportClient->EngineShowFlags.SetIndirectLightingCache(true);
ViewportClient->EngineShowFlags.SetSeparateTranslucency(true);
ViewportClient->EngineShowFlags.SetTemporalAA(true);
ViewportClient->EngineShowFlags.SetGrid(false);
ViewportClient->EngineShowFlags.SetAtmosphere(true);
ViewportClient->EngineShowFlags.SetSkeletalMeshes(true);
ViewportClient->EngineShowFlags.SetDecals(true);
ViewportClient->EngineShowFlags.SetParticles(true);
ViewportClient->EngineShowFlags.SetVolumetricFog(true);
ViewportClient->EngineShowFlags.SetDynamicShadows(true);
ViewportClient->EngineShowFlags.SetSkyLighting(true);
ViewportClient->EngineShowFlags.SetAmbientOcclusion(true);
ViewportClient->EngineShowFlags.SetScreenSpaceReflections(true);
ViewportClient->EngineShowFlags.SetAntiAliasing(true);
ViewportClient->EngineShowFlags.SetMotionBlur(false);
ViewportClient->EngineShowFlags.SetBounds(false);
ViewportClient->EngineShowFlags.SetCollision(false);
ViewportClient->EngineShowFlags.SetBSP(false);
ViewportClient->EngineShowFlags.SetFog(true);
ViewportClient->EngineShowFlags.SetStaticMeshes(true);
ViewportClient->EngineShowFlags.SetLandscape(true);
ViewportClient->EngineShowFlags.SetTranslucency(true);
ViewportClient->EngineShowFlags.SetInstancedFoliage(true);
ViewportClient->EngineShowFlags.SetInstancedGrass(true);
ViewportClient->EngineShowFlags.SetInstancedStaticMeshes(true);
ViewportClient->EngineShowFlags.SetInstancedFoliage(true);
ViewportClient->EngineShowFlags.SetInstancedGrass(true);
ViewportClient->EngineShowFlags.SetInstancedStaticMeshes(true);
ViewportClient->EngineShowFlags.SetSplines(true);
ViewportClient->EngineShowFlags.SetSelectionOutline(true);
ViewportClient->EngineShowFlags.SetMeshEdges(false);
ViewportClient->EngineShowFlags.SetVertexColors(false);
ViewportClient->EngineShowFlags.SetLightComplexity(false);
ViewportClient->EngineShowFlags.SetShaderComplexity(false);
ViewportClient->EngineShowFlags.SetStaticMeshLODColoration(false);
ViewportClient->EngineShowFlags.SetLightMapDensity(false);
ViewportClient->EngineShowFlags.SetLightInfluences(false);
ViewportClient->EngineShowFlags.SetOptimizeVizibility(false);
ViewportClient->EngineShowFlags.SetTextRender(true);
ViewportClient->EngineShowFlags.SetTestImage(false);
ViewportClient->EngineShowFlags.SetVisualizeLightCulling(false);
ViewportClient->EngineShowFlags.SetPrecomputedVisibility(true);
ViewportClient->EngineShowFlags.SetPrecomputedVisibilityCells(false);
ViewportClient->EngineShowFlags.SetVolumes(false);
ViewportClient->EngineShowFlags.SetGame(false);
ViewportClient->EngineShowFlags.SetBSPTriangles(false);
ViewportClient->EngineShowFlags.SetHitProxies(false);
ViewportClient->EngineShowFlags.SetGBufferHints(false);
ViewportClient->EngineShowFlags.SetVisualizeShadingModels(false);
ViewportClient->EngineShowFlags.SetVisualizeAdaptiveDOF(false);
ViewportClient->EngineShowFlags.SetVisualizeSSR(false);
ViewportClient->EngineShowFlags.SetVisualizeSSS(false);
ViewportClient->EngineShowFlags.SetVolumetricLightmap(true);
ViewportClient->EngineShowFlags.SetVisualizeOutOfBoundsPixels(false);
ViewportClient->EngineShowFlags.SetHighResScreenshotMask(false);
ViewportClient->EngineShowFlags.SetHMDDistortion(false);
ViewportClient->EngineShowFlags.SetStereoRendering(false);
ViewportClient->EngineShowFlags.SetTonemapper(true);
ViewportClient->EngineShowFlags.SetLumenReflections(true);
ViewportClient->EngineShowFlags.SetLumenGlobalIllumination(true);
ViewportClient->EngineShowFlags.SetVirtualShadowMaps(true);
ViewportClient->EngineShowFlags.SetNanite(true);
return ViewportClient.ToSharedRef();
}
void SDismembermentPreviewViewport::OnFocusViewportToSelection()
{
// Focus the viewport on the preview actor
if (ViewportClient.IsValid() && PreviewActor)
{
ViewportClient->FocusViewportOnBox(PreviewActor->GetComponentsBoundingBox());
}
}
bool SDismembermentPreviewViewport::IsVisible() const
{
return SEditorViewport::IsVisible();
}
void SDismembermentPreviewViewport::CreatePreviewActor()
{
// Clean up any existing preview actor
if (PreviewActor)
{
if (PreviewActor->GetWorld())
{
PreviewActor->Destroy();
}
PreviewActor = nullptr;
}
// Create a new preview actor
if (PreviewScene.IsValid())
{
UWorld* World = PreviewScene->GetWorld();
if (World)
{
PreviewActor = World->SpawnActor<ADismembermentPreviewActor>();
if (PreviewActor)
{
// Add the actor to the preview scene
PreviewScene->AddComponent(PreviewActor->GetRootComponent(), FTransform::Identity);
// Update the preview manager
if (PreviewManager)
{
PreviewManager->SetTargetActor(PreviewActor);
}
}
}
}
}
void SDismembermentPreviewViewport::UpdatePreviewActor()
{
// Update the preview manager
if (PreviewManager && PreviewActor)
{
PreviewManager->SetTargetActor(PreviewActor);
}
// Refresh the viewport
RefreshViewport();
}
//////////////////////////////////////////////////////////////////////////
// SDismembermentPreviewViewportClient
SDismembermentPreviewViewportClient::SDismembermentPreviewViewportClient(FPreviewScene* InPreviewScene, const TWeakPtr<SDismembermentPreviewViewport>& InViewportWidget)
: FEditorViewportClient(nullptr, InPreviewScene, InViewportWidget)
, ViewportWidget(InViewportWidget)
, PreviewManager(nullptr)
{
// Set up the viewport client
SetRealtime(true);
bSetListenerPosition = false;
bShouldCheckHitProxy = true;
bShowGrid = false;
bDisableInput = false;
bAllowMatineePreview = false;
bUsingOrbitCamera = true;
bForceInitialFocus = true;
bShowBounds = false;
bShowBoundsActors = false;
bShowFloor = true;
bShowBoxes = false;
bShowWireframe = false;
bShowCollision = false;
bShowSockets = true;
bDrawAxes = false;
bShowNormals = false;
bShowTangents = false;
bShowBinormals = false;
bShowConstraints = false;
bShowCameras = false;
bShowLightRadius = false;
bShowLightVolumes = false;
bShowLightInfluences = false;
bShowLightFrustums = false;
bShowShadowFrustums = false;
bShowLightingOnlyOverlap = false;
bShowLightingVisualization = false;
bShowLightingStats = false;
bShowShadowDensity = false;
bShowPhysicalMaterialMasks = false;
bShowSpriteSockets = false;
bShowParticleSystemComponents = true;
bShowParticleSystems = true;
bShowLOD = false;
bShowHUD = false;
bShowDebugInfo = false;
bDrawPreviewShadowsInGame = false;
bEnableDirectLightMap = true;
bEnableIndirectLightMap = true;
bEnableColorizeDistancefieldLightingMask = false;
bEnableColorizeDistanceFieldLightingMaskGrayscale = false;
bEnableColorizeDistanceFieldLightingMaskColor = false;
bEnableColorizeDistanceFieldOcclusion = false;
bEnableColorizeDistanceFieldOcclusionGrayscale = false;
bEnableColorizeDistanceFieldOcclusionColor = false;
bEnableColorizeDistanceFieldLightingMaskGrayscale = false;
bEnableColorizeDistanceFieldLightingMaskColor = false;
bEnableColorizeDistanceFieldOcclusionGrayscale = false;
bEnableColorizeDistanceFieldOcclusionColor = false;
bEnableColorizeDistanceFieldLightingMask = false;
bEnableColorizeDistanceFieldOcclusion = false;
bEnableColorizeDistanceFieldGradient = false;
bEnableColorizeDistanceFieldGradientGrayscale = false;
bEnableColorizeDistanceFieldGradientColor = false;
bEnableColorizeDistanceFieldGradientGrayscale = false;
bEnableColorizeDistanceFieldGradientColor = false;
bEnableColorizeDistanceFieldGradient = false;
bEnableColorizeDistanceField = false;
bEnableColorizeDistanceFieldGrayscale = false;
bEnableColorizeDistanceFieldColor = false;
bEnableColorizeDistanceFieldGrayscale = false;
bEnableColorizeDistanceFieldColor = false;
bEnableColorizeDistanceField = false;
bEnableColorizeDistanceFieldMeshSDF = false;
bEnableColorizeDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeDistanceFieldMeshSDFColor = false;
bEnableColorizeDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeDistanceFieldMeshSDFColor = false;
bEnableColorizeDistanceFieldMeshSDF = false;
bEnableColorizeVolumetricLightmap = false;
bEnableColorizeVolumetricLightmapDensity = false;
bEnableColorizeVolumetricLightmapSamples = false;
bEnableColorizeGlobalDistanceField = false;
bEnableColorizeGlobalDistanceFieldGrayscale = false;
bEnableColorizeGlobalDistanceFieldColor = false;
bEnableColorizeGlobalDistanceFieldGrayscale = false;
bEnableColorizeGlobalDistanceFieldColor = false;
bEnableColorizeGlobalDistanceField = false;
bEnableColorizeGlobalDistanceFieldMeshSDF = false;
bEnableColorizeGlobalDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeGlobalDistanceFieldMeshSDFColor = false;
bEnableColorizeGlobalDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeGlobalDistanceFieldMeshSDFColor = false;
bEnableColorizeGlobalDistanceFieldMeshSDF = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusion = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionColor = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionColor = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusion = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensity = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensity = false;
bEnableColorizeVolumetricLightmapIndirectIntensity = false;
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapIndirectIntensity = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensity = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityColor = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityColor = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensity = false;
bEnableColorizeVolumetricLightmapSHBand0 = false;
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand0Color = false;
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand0Color = false;
bEnableColorizeVolumetricLightmapSHBand0 = false;
bEnableColorizeVolumetricLightmapSHBand1 = false;
bEnableColorizeVolumetricLightmapSHBand1Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand1Color = false;
bEnableColorizeVolumetricLightmapSHBand1Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand1Color = false;
bEnableColorizeVolumetricLightmapSHBand1 = false;
bEnableColorizeVolumetricLightmapSHBand2 = false;
bEnableColorizeVolumetricLightmapSHBand2Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand2Color = false;
bEnableColorizeVolumetricLightmapSHBand2Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand2Color = false;
bEnableColorizeVolumetricLightmapSHBand2 = false;
bEnableColorizeVolumetricLightmapSHBand3 = false;
bEnableColorizeVolumetricLightmapSHBand3Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand3Color = false;
bEnableColorizeVolumetricLightmapSHBand3Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand3Color = false;
bEnableColorizeVolumetricLightmapSHBand3 = false;
}
SDismembermentPreviewViewportClient::~SDismembermentPreviewViewportClient()
{
// Clean up
PreviewManager = nullptr;
}
void SDismembermentPreviewViewportClient::Tick(float DeltaSeconds)
{
// Call parent tick
FEditorViewportClient::Tick(DeltaSeconds);
// Tick the preview manager
if (PreviewManager)
{
PreviewManager->Tick(DeltaSeconds);
}
}
void SDismembermentPreviewViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
// Call parent draw
FEditorViewportClient::Draw(View, PDI);
// Draw preview elements
if (PreviewManager)
{
// TODO: Draw preview elements
}
}
void SDismembermentPreviewViewportClient::DrawCanvas(FViewport& InViewport, FSceneView& View, FCanvas& Canvas)
{
// Call parent draw canvas
FEditorViewportClient::DrawCanvas(InViewport, View, Canvas);
// Draw preview elements
if (PreviewManager)
{
// TODO: Draw preview elements
}
}
void SDismembermentPreviewViewportClient::SetPreviewManager(UDismembermentPreviewManager* InPreviewManager)
{
PreviewManager = InPreviewManager;
}

View File

@@ -0,0 +1,546 @@
#include "FLESHEditor.h"
#include "FLESHEditorCommands.h"
#include "FLESHEditorStyle.h"
#include "MatrixInputWidget.h"
#include "DismembermentGraph/DismembermentGraphEditor.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 "PropertyEditorModule.h"
#include "EditorStyleSet.h"
#include "LevelEditor.h"
#include "ToolMenus.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()
{
}
FFLESHEditor::~FFLESHEditor()
{
}
void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost)
{
// Create command list
CreateCommandList();
// Create tab layout
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("FLESHEditorLayout_v1.0")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(ToolbarTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.7f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->AddTab(ViewportTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(MatrixEditorTabId, ETabState::OpenedTab)
)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.3f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.6f)
->AddTab(DetailsTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab(AssetBrowserTabId, ETabState::OpenedTab)
)
)
)
);
// Initialize toolkit
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, FName("FLESHEditorApp"), StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar);
}
void FFLESHEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
// Register tab spawners
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.TabIcon"));
InTabManager->RegisterTabSpawner(MatrixEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_MatrixEditor))
.SetDisplayName(LOCTEXT("MatrixEditorTab", "Matrix Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "PropertyEditor.Grid.TabIcon"));
InTabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Toolbar))
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Toolbar"));
}
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()
{
// Create new editor instance
TSharedRef<FFLESHEditor> NewEditor = MakeShareable(new FFLESHEditor());
NewEditor->InitFLESHEditor(EToolkitMode::Standalone, nullptr);
}
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)
{
return SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
CreateMatrixEditorWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
CreateGraphEditorWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("ToolbarTitle", "Toolbar"))
[
CreateToolbarWidget()
];
}
TSharedRef<SWidget> FFLESHEditor::CreateViewportWidget()
{
// Create viewport widget
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("ViewportHeader", "FLESH Viewport"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("ViewportPlaceholder", "Character model and dismemberment effects will be displayed here"))
]
]
];
}
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);
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("DetailsHeader", "Property Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
DetailsWidget.ToSharedRef()
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateAssetBrowserWidget()
{
// Create asset browser
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("AssetBrowserHeader", "Asset Browser"))
.Font(FEditorStyle::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(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportOrganModel", "Import Organ Model"))
.OnClicked_Lambda([this]() { OnImportOrganModel(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportSkeletonModel", "Import Skeleton Model"))
.OnClicked_Lambda([this]() { OnImportSkeletonModel(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportPhysicsAsset", "Import Physics Asset"))
.OnClicked_Lambda([this]() { OnImportPhysicsAsset(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateMatrixEditorWidget()
{
// Create matrix editor
MatrixEditorWidget = SNew(SMatrixInputWidget)
.Matrix(FMatrix::Identity)
.OnMatrixChanged_Lambda([this](const FMatrix& NewMatrix) {
// Handle matrix change
});
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorHeader", "Matrix Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
MatrixEditorWidget.ToSharedRef()
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("TestMatrix", "Test Matrix"))
.OnClicked_Lambda([this]() { OnTestMatrix(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateGraphEditorWidget()
{
// Create graph editor
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorHeader", "Graph Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FEditorStyle::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
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("DismembermentGraphEditor", "Dismemberment Graph Editor"))
.OnClicked_Lambda([this]() { OnOpenDismembermentGraphEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("AnatomicalLayerEditor", "Anatomical Layer Editor"))
.OnClicked_Lambda([this]() { OnOpenAnatomicalLayerEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("BooleanCutTool", "Boolean Cut Tool"))
.OnClicked_Lambda([this]() { OnOpenBooleanCutTool(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("BloodSystemEditor", "Blood System Editor"))
.OnClicked_Lambda([this]() { OnOpenBloodSystemEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
];
}
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
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportPhysicsAssetNotImplemented", "Import Physics Asset feature is not implemented yet"));
}
void FFLESHEditor::OnTestMatrix()
{
// Test matrix
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("TestMatrixNotImplemented", "Test Matrix feature is not implemented yet"));
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,14 @@
#include "FLESHEditorCommands.h"
#define LOCTEXT_NAMESPACE "FLESHEditorCommands"
void FFLESHEditorCommands::RegisterCommands()
{
UI_COMMAND(OpenFLESHEditor, "FLESH", "Open FLESH Dismemberment System Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenDismembermentGraphEditor, "Dismemberment Graph", "Open Dismemberment System Graph Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenAnatomicalLayerEditor, "Anatomical Layer", "Open Anatomical Layer Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenBooleanCutTool, "Boolean Cut", "Open Boolean Cut Tool", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenBloodSystemEditor, "Blood System", "Open Blood System Editor", EUserInterfaceActionType::Button, FInputChord());
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,74 @@
#include "FLESHEditorModule.h"
#include "Modules/ModuleManager.h"
#include "ToolMenus.h"
#include "FLESHEditorStyle.h"
#include "FLESHEditorCommands.h"
#include "FLESHEditor.h"
#include "LevelEditor.h"
#define LOCTEXT_NAMESPACE "FFLESHEditorModule"
void FFLESHEditorModule::StartupModule()
{
// This code will execute after your module is loaded into memory
// The exact timing is specified in the .uplugin file per-module
// Initialize style
FFLESHEditorStyle::Initialize();
FFLESHEditorStyle::ReloadTextures();
// Register commands
FFLESHEditorCommands::Register();
// Map commands
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(
FFLESHEditorCommands::Get().OpenFLESHEditor,
FExecuteAction::CreateRaw(this, &FFLESHEditorModule::OpenFLESHEditor),
FCanExecuteAction());
// Register editor menus
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([this]()
{
// Add menu items
FToolMenuOwnerScoped OwnerScoped(this);
// Add to main menu
{
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
Section.AddMenuEntryWithCommandList(FFLESHEditorCommands::Get().OpenFLESHEditor, PluginCommands);
}
// Add to toolbar
{
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFLESHEditorCommands::Get().OpenFLESHEditor));
Entry.SetCommandList(PluginCommands);
}
}));
}
void FFLESHEditorModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module
// For modules that support dynamic reloading, we call this function before unloading the module
// Unregister style
FFLESHEditorStyle::Shutdown();
// Unregister editor menus
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
void FFLESHEditorModule::OpenFLESHEditor()
{
// Open FLESH editor
FFLESHEditor::OpenEditor();
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FFLESHEditorModule, FLESHEditor)

View File

@@ -0,0 +1,92 @@
#include "FLESHEditorStyle.h"
#include "Styling/SlateStyleRegistry.h"
#include "Styling/SlateTypes.h"
#include "Styling/CoreStyle.h"
#include "Interfaces/IPluginManager.h"
#include "SlateOptMacros.h"
TSharedPtr<FSlateStyleSet> FFLESHEditorStyle::StyleInstance = nullptr;
void FFLESHEditorStyle::Initialize()
{
if (!StyleInstance.IsValid())
{
StyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
}
}
void FFLESHEditorStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
ensure(StyleInstance.IsUnique());
StyleInstance.Reset();
}
void FFLESHEditorStyle::ReloadTextures()
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
}
FName FFLESHEditorStyle::GetStyleSetName()
{
static FName StyleSetName(TEXT("FLESHEditorStyle"));
return StyleSetName;
}
const ISlateStyle& FFLESHEditorStyle::Get()
{
return *StyleInstance;
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<FSlateStyleSet> FFLESHEditorStyle::Create()
{
TSharedRef<FSlateStyleSet> StyleRef = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
StyleRef->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("FLESH"))->GetBaseDir() / TEXT("Resources"));
// Button styles
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon20x20(20.0f, 20.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
const FVector2D Icon64x64(64.0f, 64.0f);
const FVector2D Icon128x128(128.0f, 128.0f);
// Main FLESH editor button
StyleRef->Set("FLESHEditor.OpenFLESHEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenFLESHEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Dismemberment graph editor button
StyleRef->Set("FLESHEditor.OpenDismembermentGraphEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenDismembermentGraphEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Anatomical layer editor button
StyleRef->Set("FLESHEditor.OpenAnatomicalLayerEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenAnatomicalLayerEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Boolean cut tool button
StyleRef->Set("FLESHEditor.OpenBooleanCutTool", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenBooleanCutTool.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Blood system editor button
StyleRef->Set("FLESHEditor.OpenBloodSystemEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenBloodSystemEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Button style - rounded corners
const FButtonStyle RoundedButtonStyle = FButtonStyle()
.SetNormal(FSlateRoundedBoxBrush(FLinearColor(0.05f, 0.05f, 0.05f, 1.0f), 8.0f))
.SetHovered(FSlateRoundedBoxBrush(FLinearColor(0.1f, 0.1f, 0.1f, 1.0f), 8.0f))
.SetPressed(FSlateRoundedBoxBrush(FLinearColor(0.2f, 0.2f, 0.2f, 1.0f), 8.0f))
.SetNormalPadding(FMargin(4, 2, 4, 2))
.SetPressedPadding(FMargin(4, 3, 4, 1));
StyleRef->Set("FLESHEditor.RoundedButton", RoundedButtonStyle);
return StyleRef;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@@ -0,0 +1,223 @@
#include "MatrixInputWidget.h"
#include "SlateOptMacros.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "EditorStyleSet.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMatrixInputWidget::Construct(const FArguments& InArgs)
{
Matrix = InArgs._Matrix;
OnMatrixChanged = InArgs._OnMatrixChanged;
// Create a grid panel for the matrix elements
GridPanel = SNew(SGridPanel);
// Create numeric entry boxes for each matrix element
NumericEntryBoxes.SetNum(16); // 4x4 matrix
// Add labels for rows and columns
for (int32 Col = 0; Col < 4; ++Col)
{
GridPanel->AddSlot(Col + 1, 0)
[
SNew(STextBlock)
.Text(FText::Format(FText::FromString("Column {0}"), FText::AsNumber(Col)))
.Margin(FMargin(5.0f))
];
}
for (int32 Row = 0; Row < 4; ++Row)
{
GridPanel->AddSlot(0, Row + 1)
[
SNew(STextBlock)
.Text(FText::Format(FText::FromString("Row {0}"), FText::AsNumber(Row)))
.Margin(FMargin(5.0f))
];
}
// Add numeric entry boxes for each matrix element
for (int32 Row = 0; Row < 4; ++Row)
{
for (int32 Col = 0; Col < 4; ++Col)
{
TSharedPtr<SNumericEntryBox<float>> NumericEntryBox = CreateMatrixElementWidget(Row, Col);
NumericEntryBoxes[Row * 4 + Col] = NumericEntryBox;
GridPanel->AddSlot(Col + 1, Row + 1)
[
SNew(SBox)
.WidthOverride(80.0f)
.Padding(FMargin(2.0f))
[
NumericEntryBox.ToSharedRef()
]
];
}
}
// Create buttons for common operations
TSharedPtr<SUniformGridPanel> ButtonPanel = SNew(SUniformGridPanel)
.SlotPadding(FMargin(2.0f));
ButtonPanel->AddSlot(0, 0)
[
SNew(SButton)
.Text(FText::FromString("Reset to Identity"))
.ToolTipText(FText::FromString("Reset the matrix to identity"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
ResetToIdentity();
return FReply::Handled();
}))
];
ButtonPanel->AddSlot(1, 0)
[
SNew(SButton)
.Text(FText::FromString("Set from Rotation"))
.ToolTipText(FText::FromString("Set the matrix from rotation values (degrees)"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
SetFromEulerAngles(0.0f, 0.0f, 0.0f);
return FReply::Handled();
}))
];
ButtonPanel->AddSlot(2, 0)
[
SNew(SButton)
.Text(FText::FromString("Set from Translation"))
.ToolTipText(FText::FromString("Set the matrix from translation values"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
SetFromTranslation(FVector::ZeroVector);
return FReply::Handled();
}))
];
// Main widget layout
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5.0f)
[
SNew(STextBlock)
.Text(FText::FromString("Matrix Input"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(5.0f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
GridPanel.ToSharedRef()
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5.0f)
[
ButtonPanel.ToSharedRef()
]
];
// Update the UI with the initial matrix value
UpdateUI();
}
void SMatrixInputWidget::SetMatrix(const FMatrix& InMatrix)
{
Matrix = InMatrix;
UpdateUI();
// Notify listeners of the change
if (OnMatrixChanged.IsBound())
{
OnMatrixChanged.Execute(Matrix);
}
}
FMatrix SMatrixInputWidget::GetMatrix() const
{
return Matrix;
}
void SMatrixInputWidget::ResetToIdentity()
{
SetMatrix(FMatrix::Identity);
}
void SMatrixInputWidget::SetFromEulerAngles(float Roll, float Pitch, float Yaw)
{
FRotator Rotator(Pitch, Yaw, Roll);
FMatrix RotationMatrix = FRotationMatrix::Make(Rotator);
SetMatrix(RotationMatrix);
}
void SMatrixInputWidget::SetFromTranslation(const FVector& Translation)
{
FMatrix TranslationMatrix = FMatrix::Identity;
TranslationMatrix.SetOrigin(Translation);
SetMatrix(TranslationMatrix);
}
void SMatrixInputWidget::SetFromTransform(const FTransform& Transform)
{
SetMatrix(Transform.ToMatrixWithScale());
}
TSharedPtr<SNumericEntryBox<float>> SMatrixInputWidget::CreateMatrixElementWidget(int32 Row, int32 Col)
{
return SNew(SNumericEntryBox<float>)
.Value_Lambda([this, Row, Col]() -> float
{
return Matrix.M[Row][Col];
})
.OnValueChanged(SNumericEntryBox<float>::FOnValueChanged::CreateSP(this, &SMatrixInputWidget::OnMatrixElementChanged, Row, Col))
.AllowSpin(true)
.Delta(0.1f)
.MinValue(-10000.0f)
.MaxValue(10000.0f)
.MinSliderValue(-10.0f)
.MaxSliderValue(10.0f);
}
void SMatrixInputWidget::OnMatrixElementChanged(float NewValue, int32 Row, int32 Col)
{
// Update the matrix element
Matrix.M[Row][Col] = NewValue;
// Notify listeners of the change
if (OnMatrixChanged.IsBound())
{
OnMatrixChanged.Execute(Matrix);
}
}
void SMatrixInputWidget::UpdateUI()
{
// Update each numeric entry box with the current matrix values
for (int32 Row = 0; Row < 4; ++Row)
{
for (int32 Col = 0; Col < 4; ++Col)
{
int32 Index = Row * 4 + Col;
if (NumericEntryBoxes.IsValidIndex(Index) && NumericEntryBoxes[Index].IsValid())
{
NumericEntryBoxes[Index]->SetValue(Matrix.M[Row][Col]);
}
}
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION