Update
This commit is contained in:
@@ -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
|
Reference in New Issue
Block a user