// Copyright Epic Games, Inc. All Rights Reserved. ///////////////////////////////////////////////////// // UMaterialGraph #include "MaterialGraph/MaterialGraph.h" #include "MaterialGraph/MaterialGraphNode_Comment.h" #include "MaterialGraph/MaterialGraphNode_Composite.h" #include "MaterialGraph/MaterialGraphNode_PinBase.h" #include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode_Root.h" #include "MaterialGraph/MaterialGraphSchema.h" #include "Materials/MaterialExpressionComment.h" #include "Materials/MaterialExpressionComposite.h" #include "Materials/MaterialExpressionPinBase.h" #include "Materials/MaterialExpressionFunctionOutput.h" #include "Materials/MaterialExpressionCustomOutput.h" #include "Materials/MaterialExpressionReroute.h" #include "Materials/MaterialExpressionNamedReroute.h" #include "Materials/MaterialExpressionExecBegin.h" #include "Materials/MaterialExpressionExecEnd.h" #include "MaterialCachedHLSLTree.h" #include "HLSLTree/HLSLTreeEmit.h" #include "MaterialHLSLTree.h" #include "MaterialGraphNode_Knot.h" #include "Kismet2/BlueprintEditorUtils.h" #define LOCTEXT_NAMESPACE "MaterialGraph" UMaterialGraph::UMaterialGraph(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } UMaterialGraph::UMaterialGraph() = default; UMaterialGraph::UMaterialGraph(FVTableHelper& Helper) { } UMaterialGraph::~UMaterialGraph() { } void UMaterialGraph::RebuildGraph() { check(Material); // Pre-group expressions & comments per subgraph to avoid unnecessary iteration over all material expressions TMap> SubgraphExpressionMap; TMap> SubgraphCommentMap; for (UMaterialExpression* Expression : Material->GetExpressions()) { SubgraphExpressionMap.FindOrAdd(Expression->SubgraphExpression).Add(Expression); } for (UMaterialExpressionComment* Comment : Material->GetEditorComments()) { if (Comment) { SubgraphCommentMap.FindOrAdd(Comment->SubgraphExpression).Add(Comment); } } RebuildGraphInternal(SubgraphExpressionMap, SubgraphCommentMap); } template static UMaterialGraphNode* InitExpressionNewNode(UMaterialGraph* Graph, UMaterialExpression* Expression, bool bUserInvoked) { UMaterialGraphNode* NewNode = nullptr; FGraphNodeCreator NodeCreator(*Graph); if (bUserInvoked) { NewNode = NodeCreator.CreateUserInvokedNode(); } else { NewNode = NodeCreator.CreateNode(false); } NewNode->MaterialExpression = Expression; NewNode->RealtimeDelegate = Graph->RealtimeDelegate; NewNode->MaterialDirtyDelegate = Graph->MaterialDirtyDelegate; Expression->GraphNode = NewNode; Expression->SubgraphExpression = Graph->SubgraphExpression; NodeCreator.Finalize(); return NewNode; } namespace Private { class FOwnedNodeMaterial : public UE::HLSLTree::FOwnedNode { public: FOwnedNodeMaterial(UObject* InMaterial) : Material(InMaterial) {} virtual TConstArrayView GetOwners() const final { return MakeArrayView(&Material, 1); } UObject* Material; }; void PrepareHLSLTree(UE::HLSLTree::FEmitContext& EmitContext, UMaterial* Material, const FMaterialCachedHLSLTree& CachedTree, EShaderFrequency ShaderFrequency) { using namespace UE::HLSLTree; using namespace UE::Shader; EmitContext.ShaderFrequency = ShaderFrequency; EmitContext.bUseAnalyticDerivatives = true; // We want to consider expressions used for analytic derivatives EmitContext.bMarkLiveValues = false; FEmitScope* EmitResultScope = EmitContext.PrepareScope(CachedTree.GetResultScope()); FRequestedType RequestedAttributesType(CachedTree.GetMaterialAttributesType(), false); CachedTree.SetRequestedFields(ShaderFrequency, RequestedAttributesType); FOwnedNodeMaterial MaterialOwner(Material); FEmitOwnerScope OwnerScope(EmitContext, &MaterialOwner); const FPreparedType& ResultType = EmitContext.PrepareExpression(CachedTree.GetResultExpression(), *EmitResultScope, RequestedAttributesType); } UMaterialGraphNode_Base* FindGraphNodeForObject(const UObject* Object) { if (const UMaterialExpression* MaterialExpression = Cast(Object)) { if (MaterialExpression->GraphNode) { return CastChecked(MaterialExpression->GraphNode); } } else if (const UMaterial* Material = Cast(Object)) { const UMaterialGraph* MaterialGraph = Material->MaterialGraph; if (MaterialGraph) { return MaterialGraph->RootNode; } } return nullptr; } } // namespace Private void UMaterialGraph::UpdatePinTypes() { using namespace UE::HLSLTree; if (!Material->IsUsingNewHLSLGenerator()) { return; } for (UEdGraphNode* Node : Nodes) { for (UEdGraphPin* Pin : Node->Pins) { if (Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec) { Pin->PinType.PinCategory = UMaterialGraphSchema::PC_Void; } } } FNullErrorHandler NullErrorHandler; FMemStackBase Allocator; const FMaterialCachedHLSLTree& CachedTree = Material->GetCachedHLSLTree(); FEmitContext EmitContext(Allocator, FTargetParameters(), NullErrorHandler, CachedTree.GetTypeRegistry()); Material::FEmitData& EmitMaterialData = EmitContext.AcquireData(); ::Private::PrepareHLSLTree(EmitContext, Material, CachedTree, SF_Pixel); ::Private::PrepareHLSLTree(EmitContext, Material, CachedTree, SF_Vertex); for (const auto& It : CachedTree.GetConnections()) { const FMaterialConnectionKey& Key = It.Key; const FExpression* OutputExpression = It.Value; const UE::Shader::FType OutputType = EmitContext.GetType(OutputExpression); if (!OutputType.IsVoid()) { const FConnectionKey InputKey(Key.InputObject, OutputExpression); const UE::Shader::FType* InputType = EmitContext.ConnectionMap.Find(InputKey); if (Cast(Key.InputObject) || Cast(Key.OutputObject)) { int a = 0; } if (InputType) { UMaterialGraphNode_Base* InputNode = ::Private::FindGraphNodeForObject(Key.InputObject); if (InputNode) { const int32 InputIndex = InputNode->GetSourceIndexForInputIndex(Key.InputIndex); UEdGraphPin* InputPin = InputNode->GetInputPin(InputIndex); if (InputPin) { const UE::Shader::FValueTypeDescription InputTypeDesc = UE::Shader::GetValueTypeDescription(*InputType); ensure(InputPin->PinType.PinCategory == UMaterialGraphSchema::PC_Void); InputPin->PinType.PinCategory = UMaterialGraphSchema::PC_ValueType; InputPin->PinType.PinSubCategory = InputTypeDesc.Name; } } } UMaterialGraphNode_Base* OutputNode = ::Private::FindGraphNodeForObject(Key.OutputObject); if (OutputNode) { UEdGraphPin* OutputPin = OutputNode->GetOutputPin(Key.OutputIndex); if (OutputPin) { // A single output pin may connect to multiple inputs, so it's possible to set the same value multiple times here const UE::Shader::FValueTypeDescription OutputTypeDesc = UE::Shader::GetValueTypeDescription(OutputType); OutputPin->PinType.PinCategory = UMaterialGraphSchema::PC_ValueType; OutputPin->PinType.PinSubCategory = OutputTypeDesc.Name; } } } } } void UMaterialGraph::RebuildGraphInternal(const TMap>& SubgraphExpressionMap, const TMap>& SubgraphCommentMap) { Modify(); RemoveAllNodes(); if (!MaterialFunction && !SubgraphExpression) { // This needs to be done before building the new material inputs to guarantee that the shading model field is up to date Material->RebuildShadingModelField(); // Initialize the material input list. MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_BaseColor, Material), MP_BaseColor, LOCTEXT("BaseColorToolTip", "Defines the overall color of the Material. Each channel is automatically clamped between 0 and 1"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Metallic, Material), MP_Metallic, LOCTEXT("MetallicToolTip", "Controls how \"metal-like\" your surface looks like"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Specular, Material), MP_Specular, LOCTEXT("SpecularToolTip", "Used to scale the current amount of specularity on non-metallic surfaces and is a value between 0 and 1, default at 0.5"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Roughness, Material), MP_Roughness, LOCTEXT("RoughnessToolTip", "Controls how rough the Material is. Roughness of 0 (smooth) is a mirror reflection and 1 (rough) is completely matte or diffuse"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Anisotropy, Material), MP_Anisotropy, LOCTEXT("AnisotropyToolTip", "Determines the extent the specular highlight is stretched along the tangent. Anisotropy from 0 to 1 results in a specular highlight that stretches from uniform to maximally stretched along the tangent direction."))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_EmissiveColor, Material), MP_EmissiveColor, LOCTEXT("EmissiveToolTip", "Controls which parts of your Material will appear to glow"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Opacity, Material), MP_Opacity, LOCTEXT("OpacityToolTip", "Controls the translucency of the Material"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_OpacityMask, Material), MP_OpacityMask, LOCTEXT("OpacityMaskToolTip", "When in Masked mode, a Material is either completely visible or completely invisible"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Normal, Material), MP_Normal, LOCTEXT("NormalToolTip", "Takes the input of a normal map"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Tangent, Material), MP_Tangent, LOCTEXT("TangentToolTip", "Takes the input of a tangent map. Useful for specifying anisotropy direction."))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_WorldPositionOffset, Material), MP_WorldPositionOffset, LOCTEXT("WorldPositionOffsetToolTip", "Allows for the vertices of a mesh to be manipulated in world space by the Material"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_SubsurfaceColor, Material), MP_SubsurfaceColor, LOCTEXT("SubsurfaceToolTip", "Allows you to add a color to your Material to simulate shifts in color when light passes through the surface"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData0, Material), MP_CustomData0, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData0, Material))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData1, Material), MP_CustomData1, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData1, Material))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_AmbientOcclusion, Material), MP_AmbientOcclusion, LOCTEXT("AmbientOcclusionToolTip", "Simulate the self-shadowing that happens within crevices of a surface, or of a volume for volumetric clouds only"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_Refraction, Material), MP_Refraction, LOCTEXT("RefractionToolTip", "Takes in a texture or value that simulates the index of refraction of the surface"))); for (int32 UVIndex = 0; UVIndex < UE_ARRAY_COUNT(Material->GetEditorOnlyData()->CustomizedUVs); UVIndex++) { //@todo - localize MaterialInputs.Add(FMaterialInputInfo(FText::FromString(FString::Printf(TEXT("Customized UV%u"), UVIndex)), (EMaterialProperty)(MP_CustomizedUVs0 + UVIndex), FText::FromString(FString::Printf(TEXT("CustomizedUV%uToolTip"), UVIndex)))); } MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_PixelDepthOffset, Material), MP_PixelDepthOffset, LOCTEXT("PixelDepthOffsetToolTip", "Pixel Depth Offset"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_ShadingModel, Material), MP_ShadingModel, LOCTEXT("ShadingModelToolTip", "Selects which shading model should be used per pixel"))); MaterialInputs.Add(FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_FrontMaterial, Material), MP_FrontMaterial, LOCTEXT("FrontMaterialToolTip", "Specify the front facing material"))); //^^^ New material properties go above here. ^^^^ MaterialInputs.Add(FMaterialInputInfo(LOCTEXT("MaterialAttributes", "Material Attributes"), MP_MaterialAttributes, LOCTEXT("MaterialAttributesToolTip", "Material Attributes"))); // Add Root Node { FGraphNodeCreator NodeCreator(*this); RootNode = NodeCreator.CreateNode(); RootNode->Material = Material; NodeCreator.Finalize(); } } if (Material->IsUsingControlFlow()) { if (ensure(Material->GetExpressionExecBegin())) { InitExpressionNewNode(this, Material->GetExpressionExecBegin(), false); } if (MaterialFunction) { check(MaterialFunction->GetExpressionExecBegin() == Material->GetExpressionExecBegin()); check(MaterialFunction->GetExpressionExecEnd() == Material->GetExpressionExecEnd()); if (ensure(Material->GetExpressionExecEnd())) { InitExpressionNewNode(this, Material->GetExpressionExecEnd(), false); } } } TArray ChildSubGraphExpressions; // Composite's use reroutes under the hood that we don't want to create nodes for, gather their expressions for checking TArray CompositeRerouteExpressions; if (UMaterialExpressionComposite* SubgraphParentComposite = Cast(SubgraphExpression)) { CompositeRerouteExpressions = SubgraphParentComposite->GetCurrentReroutes(); } if (const TArray* Expressions = SubgraphExpressionMap.Find(SubgraphExpression)) { for (UMaterialExpression* Expression : *Expressions) { if (!CompositeRerouteExpressions.Contains(Expression)) { AddExpression(Expression, false); //@TODO: Make a better way to check if an expression represents a subgraph than by type. if (Cast(Expression)) { ChildSubGraphExpressions.Add(Expression); } } } } if (const TArray* Comments = SubgraphCommentMap.Find(SubgraphExpression)) { for (UMaterialExpressionComment* Comment : *Comments) { AddComment(Comment); } } for (UMaterialExpression* ChildSubGraphExpression : ChildSubGraphExpressions) { UMaterialGraph* Subgraph = AddSubGraph(ChildSubGraphExpression); if (UMaterialGraphNode_Composite* CompositeNode = Cast(ChildSubGraphExpression->GraphNode)) { CompositeNode->BoundGraph = Subgraph; Subgraph->Rename(*CastChecked(CompositeNode->MaterialExpression)->SubgraphName); } Subgraph->RebuildGraphInternal(SubgraphExpressionMap, SubgraphCommentMap); } LinkGraphNodesFromMaterial(); } UMaterialGraphNode* UMaterialGraph::AddExpression(UMaterialExpression* Expression, bool bUserInvoked) { // Node for UMaterialExpressionExecBegin is explicitly placed if needed // We don't created any node for UMaterialExpressionExecEnd, it's handled as part of the root node UMaterialGraphNode* Node = nullptr; if (Expression && !Expression->IsA(UMaterialExpressionExecBegin::StaticClass()) && !Expression->IsA(UMaterialExpressionExecEnd::StaticClass())) { Modify(); if (Expression->IsA(UMaterialExpressionReroute::StaticClass())) { Node = InitExpressionNewNode(this, Expression, false); } else if (Expression->IsA(UMaterialExpressionComposite::StaticClass())) { Node = InitExpressionNewNode(this, Expression, false); } else if (Expression->IsA(UMaterialExpressionPinBase::StaticClass())) { Node = InitExpressionNewNode(this, Expression, false); } else { Node = InitExpressionNewNode(this, Expression, bUserInvoked); } } if (Node && bUserInvoked) { UpdatePinTypes(); } return Node; } UMaterialGraphNode_Comment* UMaterialGraph::AddComment(UMaterialExpressionComment* Comment, bool bIsUserInvoked) { UMaterialGraphNode_Comment* NewComment = NULL; if (Comment) { Modify(); FGraphNodeCreator NodeCreator(*this); if (bIsUserInvoked) { NewComment = NodeCreator.CreateUserInvokedNode(true); } else { NewComment = NodeCreator.CreateNode(false); } NewComment->MaterialExpressionComment = Comment; NewComment->MaterialDirtyDelegate = MaterialDirtyDelegate; Comment->GraphNode = NewComment; Comment->SubgraphExpression = SubgraphExpression; NodeCreator.Finalize(); } return NewComment; } UMaterialGraph* UMaterialGraph::AddSubGraph(UMaterialExpression* InSubgraphExpression) { UMaterialGraph* SubGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(InSubgraphExpression->GraphNode, NAME_None, UMaterialGraph::StaticClass(), Schema)); check(SubGraph); SubGraph->Material = Material; SubGraph->MaterialFunction = MaterialFunction; SubGraph->RealtimeDelegate = RealtimeDelegate; SubGraph->MaterialDirtyDelegate = MaterialDirtyDelegate; SubGraph->ToggleCollapsedDelegate = ToggleCollapsedDelegate; SubGraph->SubgraphExpression = InSubgraphExpression; SubGraphs.Add(SubGraph); // If we are a subgraph ourselves, mark that on the expression. InSubgraphExpression->SubgraphExpression = SubgraphExpression; return SubGraph; } void UMaterialGraph::LinkGraphNodesFromMaterial() { struct ExpressionMatchesPredicate { ExpressionMatchesPredicate(UMaterialExpressionReroute* InCompositeReroute) : CompositeReroute(InCompositeReroute) {} bool operator()(const FCompositeReroute& Reroute) { return Reroute.Expression == CompositeReroute; } UMaterialExpressionReroute* CompositeReroute; }; for (int32 Index = 0; Index < Nodes.Num(); ++Index) { Nodes[Index]->BreakAllNodeLinks(); } if (RootNode) { // Use Material Inputs to make GraphNode Connections for (int32 Index = 0; Index < MaterialInputs.Num(); ++Index) { UEdGraphPin* InputPin = RootNode->GetInputPin(Index); auto ExpressionInput = MaterialInputs[Index].GetExpressionInput(Material); if (ExpressionInput.Expression) { if (UMaterialGraphNode* GraphNode = Cast(ExpressionInput.Expression->GraphNode)) { InputPin->MakeLinkTo(GraphNode->GetOutputPin(GetValidOutputIndex(&ExpressionInput))); } else if (UMaterialExpressionReroute* CompositeReroute = CastChecked(ExpressionInput.Expression)) { // This is an unseen composite reroute expression, find the actual expression output to connect to. UMaterialExpressionComposite* OwningComposite = CastChecked(CompositeReroute->SubgraphExpression); UMaterialGraphNode* OutputGraphNode; int32 OutputPinIndex = OwningComposite->InputExpressions->ReroutePins.FindLastByPredicate(ExpressionMatchesPredicate(CompositeReroute)); if (OutputPinIndex != INDEX_NONE) { OutputGraphNode = CastChecked(OwningComposite->InputExpressions->GraphNode); } else { // Output pin base in the subgraph cannot have outputs, if this reroute isn't in the inputs connect to composite's outputs OutputPinIndex = OwningComposite->OutputExpressions->ReroutePins.FindLastByPredicate(ExpressionMatchesPredicate(CompositeReroute)); OutputGraphNode = CastChecked(OwningComposite->GraphNode); } InputPin->MakeLinkTo(OutputGraphNode->GetOutputPin(OutputPinIndex)); } } } } for (UMaterialExpression* Expression : Material->GetExpressions()) { if (!Expression) { continue; } UMaterialGraphNode* MaterialGraphNode = Cast(Expression->GraphNode); if (!MaterialGraphNode) { continue; } const TArray ExpressionInputs = Expression->GetInputs(); TArray ExecOutputs; Expression->GetExecOutputs(ExecOutputs); for (UEdGraphPin* Pin : MaterialGraphNode->Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec) { // Implicitly generated property inputs are not returned by GetInputs(), so check index is within valid range. if (ExpressionInputs.IsValidIndex(Pin->SourceIndex) && ExpressionInputs[Pin->SourceIndex]->Expression) { // Unclear why this is null sometimes outside of composite reroute, but this is safer than crashing if (UMaterialGraphNode* GraphNode = Cast(ExpressionInputs[Pin->SourceIndex]->Expression->GraphNode)) { // if GraphNode is a material function call for a missing material function, it may not have any output pins UEdGraphPin* OutputPin = GraphNode->GetOutputPin(GetValidOutputIndex(ExpressionInputs[Pin->SourceIndex])); if (LIKELY(OutputPin)) { Pin->MakeLinkTo(OutputPin); } } else if (UMaterialExpressionReroute* CompositeReroute = Cast(ExpressionInputs[Pin->SourceIndex]->Expression)) { // This is an unseen composite reroute expression, find the actual expression output to connect to. UMaterialExpressionComposite* OwningComposite = CastChecked(CompositeReroute->SubgraphExpression); UMaterialGraphNode* OutputGraphNode; int32 OutputPinIndex = OwningComposite->InputExpressions->ReroutePins.FindLastByPredicate(ExpressionMatchesPredicate(CompositeReroute)); if (OutputPinIndex != INDEX_NONE) { OutputGraphNode = CastChecked(OwningComposite->InputExpressions->GraphNode); } else { // Output pin base in the subgraph cannot have outputs, if this reroute isn't in the inputs connect to composite's outputs OutputPinIndex = OwningComposite->OutputExpressions->ReroutePins.FindLastByPredicate(ExpressionMatchesPredicate(CompositeReroute)); OutputGraphNode = CastChecked(OwningComposite->GraphNode); } Pin->MakeLinkTo(OutputGraphNode->GetOutputPin(OutputPinIndex)); } } } else if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == UMaterialGraphSchema::PC_Exec) { FExpressionExecOutput* ExecOutput = ExecOutputs[Pin->SourceIndex].Output; UMaterialExpression* ConnectedExpression = ExecOutput->GetExpression(); if (ConnectedExpression) { if (ConnectedExpression == Material->GetExpressionExecEnd() && RootNode) { // Exec end point is the root node (assuming there is a RootNode) // MaterialFunctions currently do not have a RootNode, and instead have the ExecEnd node as a stand-alone Pin->MakeLinkTo(RootNode->GetExecInputPin()); } else if (UMaterialGraphNode* GraphNode = Cast(ConnectedExpression->GraphNode)) { Pin->MakeLinkTo(GraphNode->GetExecInputPin()); } // TODO - UMaterialExpressionReroute? } } } } NotifyGraphChanged(); UpdatePinTypes(); } void UMaterialGraph::LinkMaterialExpressionsFromGraph() { // Use GraphNodes to make Material Expression Connections for (int32 NodeIndex = 0; NodeIndex < Nodes.Num(); ++NodeIndex) { if (RootNode && RootNode == Nodes[NodeIndex]) { // Setup Material's inputs from root node Material->Modify(); Material->EditorX = RootNode->NodePosX; Material->EditorY = RootNode->NodePosY; for (UEdGraphPin* Pin : RootNode->Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec) { FExpressionInput& MaterialInput = MaterialInputs[Pin->SourceIndex].GetExpressionInput(Material); if (Pin->LinkedTo.Num() > 0) { UEdGraphPin* ConnectedPin = Pin->LinkedTo[0]; UMaterialGraphNode* ConnectedNode = CastChecked(ConnectedPin->GetOwningNode()); check(ConnectedPin->SourceIndex != INDEX_NONE); if (!ConnectedNode->MaterialExpression->IsExpressionConnected(&MaterialInput, ConnectedPin->SourceIndex)) { ConnectedNode->MaterialExpression->Modify(); MaterialInput.Connect(ConnectedPin->SourceIndex, ConnectedNode->MaterialExpression); } } else if (MaterialInput.Expression) { MaterialInput.Expression = NULL; } } } } else { if (UMaterialGraphNode* GraphNode = Cast(Nodes[NodeIndex])) { // Need to be sure that we are changing the expression before calling modify - // triggers a rebuild of its preview when it is called UMaterialExpression* Expression = GraphNode->MaterialExpression; bool bModifiedExpression = false; if (Expression) { if (Expression->MaterialExpressionEditorX != GraphNode->NodePosX || Expression->MaterialExpressionEditorY != GraphNode->NodePosY || Expression->Desc != GraphNode->NodeComment) { bModifiedExpression = true; Expression->Modify(); // Update positions and comments Expression->MaterialExpressionEditorX = GraphNode->NodePosX; Expression->MaterialExpressionEditorY = GraphNode->NodePosY; Expression->Desc = GraphNode->NodeComment; } const TArray ExpressionInputs = Expression->GetInputs(); TArray ExecOutputs; Expression->GetExecOutputs(ExecOutputs); for (UEdGraphPin* Pin : GraphNode->Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec) { // Wire up non-execution input pins // Implicitly generated property inputs are not returned by GetInputs(), so check index is within valid range. FExpressionInput* ExpressionInput = ExpressionInputs.IsValidIndex(Pin->SourceIndex) ? ExpressionInputs[Pin->SourceIndex] : nullptr; if (Pin->LinkedTo.Num() > 0) { UEdGraphPin* ConnectedPin = Pin->LinkedTo[0]; UMaterialGraphNode* ConnectedNode = CastChecked(ConnectedPin->GetOwningNode()); if (ExpressionInput && !ConnectedNode->MaterialExpression->IsExpressionConnected(ExpressionInput, ConnectedPin->SourceIndex)) { if (!bModifiedExpression) { bModifiedExpression = true; Expression->Modify(); } ConnectedNode->MaterialExpression->Modify(); ExpressionInput->Connect(ConnectedPin->SourceIndex, ConnectedNode->MaterialExpression); } } else if (ExpressionInput && ExpressionInput->Expression) { if (!bModifiedExpression) { bModifiedExpression = true; Expression->Modify(); } ExpressionInput->Expression = NULL; } } else if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == UMaterialGraphSchema::PC_Exec) { // Wire up execution output pins FExpressionExecOutput* ExpressionOutput = ExecOutputs[Pin->SourceIndex].Output; if (Pin->LinkedTo.Num() > 0) { if (Pin->LinkedTo[0]->GetOwningNode() == RootNode) { if (!bModifiedExpression) { bModifiedExpression = true; Expression->Modify(); } ExpressionOutput->Connect(Material->GetExpressionExecEnd()); } else { UMaterialGraphNode* ConnectedNode = CastChecked(Pin->LinkedTo[0]->GetOwningNode()); if (ExpressionOutput && ExpressionOutput->GetExpression() != ConnectedNode->MaterialExpression && ConnectedNode->MaterialExpression->HasExecInput()) { if (!bModifiedExpression) { bModifiedExpression = true; Expression->Modify(); } ConnectedNode->MaterialExpression->Modify(); ExpressionOutput->Connect(ConnectedNode->MaterialExpression); } } } else if (ExpressionOutput && ExpressionOutput->GetExpression()) { if (!bModifiedExpression) { bModifiedExpression = true; Expression->Modify(); } ExpressionOutput->Connect(nullptr); } } } } } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Nodes[NodeIndex])) { UMaterialExpressionComment* Comment = CommentNode->MaterialExpressionComment; if (Comment) { if (Comment->MaterialExpressionEditorX != CommentNode->NodePosX || Comment->MaterialExpressionEditorY != CommentNode->NodePosY || Comment->Text != CommentNode->NodeComment || Comment->SizeX != CommentNode->NodeWidth || Comment->SizeY != CommentNode->NodeHeight || Comment->CommentColor != CommentNode->CommentColor) { Comment->Modify(); // Update positions and comments Comment->MaterialExpressionEditorX = CommentNode->NodePosX; Comment->MaterialExpressionEditorY = CommentNode->NodePosY; Comment->Text = CommentNode->NodeComment; Comment->SizeX = CommentNode->NodeWidth; Comment->SizeY = CommentNode->NodeHeight; Comment->CommentColor = CommentNode->CommentColor; } } } } } // Also link subgraphs? for (UEdGraph* SubGraph : SubGraphs) { CastChecked(SubGraph)->LinkMaterialExpressionsFromGraph(); } } bool UMaterialGraph::IsInputActive(UEdGraphPin* GraphPin) const { if (Material && RootNode && // No inputs without a root node GraphPin->Direction == EGPD_Input && GraphPin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec && GraphPin->SourceIndex < MaterialInputs.Num() && GraphPin->GetOwningNode() == RootNode // we only care about pins from the root node here ) { const EMaterialProperty Property = MaterialInputs[GraphPin->SourceIndex].GetProperty(); return Material->IsPropertyActiveInEditor(Property); } return true; } int32 UMaterialGraph::GetInputIndexForProperty(EMaterialProperty Property) const { for (int32 Index = 0; Index < MaterialInputs.Num(); ++Index) { if (MaterialInputs[Index].GetProperty() == Property) { return Index; } } return INDEX_NONE; } void UMaterialGraph::GetUnusedExpressions(TArray& UnusedNodes) const { UnusedNodes.Empty(); TArray NodesToCheck; if (RootNode) { for (UEdGraphPin* Pin : RootNode->Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec && MaterialInputs[Pin->SourceIndex].IsVisiblePin(Material) && Pin->LinkedTo.Num() > 0 && Pin->LinkedTo[0]) { NodesToCheck.Push(Pin->LinkedTo[0]->GetOwningNode()); } } for (int32 Index = 0; Index < Nodes.Num(); Index++) { UMaterialGraphNode* GraphNode = Cast(Nodes[Index]); if (GraphNode) { UMaterialExpressionCustomOutput* CustomOutput = Cast(GraphNode->MaterialExpression); if (CustomOutput) { NodesToCheck.Push(GraphNode); } } } } else if (MaterialFunction) { for (int32 Index = 0; Index < Nodes.Num(); Index++) { UMaterialGraphNode* GraphNode = Cast(Nodes[Index]); if (GraphNode) { UMaterialExpressionFunctionOutput* FunctionOutput = Cast(GraphNode->MaterialExpression); if (FunctionOutput) { NodesToCheck.Push(GraphNode); } } } } // Depth-first traverse the material expression graph. TArray UsedNodes; TMap ReachableNodes; while (NodesToCheck.Num() > 0) { UMaterialGraphNode* GraphNode = Cast(NodesToCheck.Pop()); if (GraphNode) { int32* AlreadyVisited = ReachableNodes.Find(GraphNode); if (!AlreadyVisited) { // Mark the expression as reachable. ReachableNodes.Add(GraphNode, 0); UsedNodes.Add(GraphNode); // Iterate over the expression's inputs and add them to the pending stack. for (UEdGraphPin* Pin : GraphNode->Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec && Pin->LinkedTo.Num() > 0 && Pin->LinkedTo[0]) { NodesToCheck.Push(Pin->LinkedTo[0]->GetOwningNode()); } } // Since named reroute nodes don't have any input pins, we manually push the declaration node here if (const UMaterialExpressionNamedRerouteUsage* NamedRerouteUsage = Cast(GraphNode->MaterialExpression)) { if (NamedRerouteUsage->Declaration && NamedRerouteUsage->Declaration->GraphNode) { NodesToCheck.Push(NamedRerouteUsage->Declaration->GraphNode); } } } } } for (int32 Index = 0; Index < Nodes.Num(); ++Index) { UMaterialGraphNode* GraphNode = Cast(Nodes[Index]); if (GraphNode && !UsedNodes.Contains(GraphNode)) { UnusedNodes.Add(GraphNode); } } } void UMaterialGraph::NotifyGraphChanged(const FEdGraphEditAction& Action) { Super::NotifyGraphChanged(Action); } void UMaterialGraph::NotifyGraphChanged() { Super::NotifyGraphChanged(); } void UMaterialGraph::RemoveAllNodes() { MaterialInputs.Empty(); RootNode = NULL; TArray NodesToRemove = Nodes; for (int32 NodeIndex = 0; NodeIndex < NodesToRemove.Num(); ++NodeIndex) { NodesToRemove[NodeIndex]->Modify(); RemoveNode(NodesToRemove[NodeIndex]); } } int32 UMaterialGraph::GetValidOutputIndex(FExpressionInput* Input) const { int32 OutputIndex = 0; if (Input->Expression) { TArray& Outputs = Input->Expression->GetOutputs(); if (Outputs.Num() > 0) { const bool bOutputIndexIsValid = Outputs.IsValidIndex(Input->OutputIndex) // Attempt to handle legacy connections before OutputIndex was used that had a mask && (Input->OutputIndex != 0 || Input->Mask == 0); for( ; OutputIndex < Outputs.Num() ; ++OutputIndex ) { const FExpressionOutput& Output = Outputs[OutputIndex]; if((bOutputIndexIsValid && OutputIndex == Input->OutputIndex) || (!bOutputIndexIsValid && Output.Mask == Input->Mask && Output.MaskR == Input->MaskR && Output.MaskG == Input->MaskG && Output.MaskB == Input->MaskB && Output.MaskA == Input->MaskA)) { break; } } if (OutputIndex >= Outputs.Num()) { // Work around for non-reproducible crash where OutputIndex would be out of bounds OutputIndex = Outputs.Num() - 1; } } } return OutputIndex; } #undef LOCTEXT_NAMESPACE