2023-07-19 05:50:04 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MaterialEditorHelpers.h"
# include "MaterialEditor.h"
# include "AssetToolsModule.h"
# include "MaterialEditorUtilities.h"
# include "MaterialGraph/MaterialGraphNode.h"
# include "Factories/MaterialFunctionFactoryNew.h"
# include "Materials/MaterialExpressionConstant.h"
# include "Materials/MaterialExpressionConstant2Vector.h"
# include "Materials/MaterialExpressionConstant3Vector.h"
# include "Materials/MaterialExpressionConstant4Vector.h"
# include "Materials/MaterialExpressionFunctionInput.h"
# include "Materials/MaterialExpressionFunctionOutput.h"
# include "Materials/MaterialFunction.h"
# include "ScopedTransaction.h"
# define LOCTEXT_NAMESPACE "MaterialEditor"
void FMaterialEditorHelpers : : CollapseToFunction ( FMaterialEditor & MaterialEditor )
{
if ( ! ensure ( MaterialEditor . OriginalMaterialObject ) )
{
return ;
}
TSet < UEdGraphNode * > NodesToCollapse ;
for ( UObject * NodeObject : MaterialEditor . GetSelectedNodes ( ) )
{
UMaterialGraphNode * GraphNode = Cast < UMaterialGraphNode > ( NodeObject ) ;
if ( GraphNode & & GraphNode - > CanDuplicateNode ( ) )
{
NodesToCollapse . Add ( GraphNode ) ;
}
}
// Output pin outside the nodes to collapse -> all pins inside the nodes to collapse to connect to that new function input
TMap < UEdGraphPin * , TArray < UEdGraphPin * > > Inputs ;
// Output pin inside the nodes to collapse -> all pins outside the nodes to collapse to connect to that new function output
TMap < UEdGraphPin * , TArray < UEdGraphPin * > > Outputs ;
UEdGraph * OldGraph = nullptr ;
for ( UEdGraphNode * Node : NodesToCollapse )
{
OldGraph = Node - > GetGraph ( ) ;
for ( UEdGraphPin * Pin : Node - > Pins )
{
for ( UEdGraphPin * LinkedToPin : Pin - > LinkedTo )
{
if ( ! NodesToCollapse . Contains ( LinkedToPin - > GetOwningNode ( ) ) )
{
if ( Pin - > Direction = = EGPD_Input )
{
Inputs . FindOrAdd ( LinkedToPin ) . Add ( Pin ) ;
}
else
{
check ( Pin - > Direction = = EGPD_Output ) ;
Outputs . FindOrAdd ( Pin ) . Add ( LinkedToPin ) ;
}
}
}
}
}
// Expand the bounds to ensure the function input/output nodes don't overlap with the pasted nodes in the middle
FBox2D NodeBounds = GetNodesBounds ( MaterialEditor , NodesToCollapse ) ;
NodeBounds = NodeBounds . ExpandBy ( 300 ) ;
// Sort inputs & outputs by the average position of the pins inside the function
// This reduces the risk of having crossing links
{
const auto GetPinSortValue = [ ] ( UEdGraphPin * Pin )
{
return 100 * Pin - > GetOwningNode ( ) - > NodePosY + Pin - > GetOwningNode ( ) - > GetPinIndex ( Pin ) ;
} ;
Inputs . ValueSort ( [ & ] ( const TArray < UEdGraphPin * > & PinsA , const TArray < UEdGraphPin * > & PinsB )
{
float PositionA = 0 ;
for ( UEdGraphPin * Pin : PinsA )
{
PositionA + = GetPinSortValue ( Pin ) ;
}
PositionA / = PinsA . Num ( ) ;
float PositionB = 0 ;
for ( UEdGraphPin * Pin : PinsB )
{
PositionB + = GetPinSortValue ( Pin ) ;
}
PositionB / = PinsB . Num ( ) ;
return PositionA < PositionB ;
} ) ;
Outputs . KeySort ( [ & ] ( UEdGraphPin & PinA , UEdGraphPin & PinB )
{
return GetPinSortValue ( & PinA ) < GetPinSortValue ( & PinB ) ;
} ) ;
}
if ( Inputs . Num ( ) = = 0 & & Outputs . Num ( ) = = 0 )
{
// Avoids issues below
return ;
}
UMaterialFunction * NewMaterialFunction = nullptr ;
UMaterialGraph * NewGraph = nullptr ;
FMaterialEditor * NewMaterialEditor = nullptr ;
// Show prompt asking the user where the new asset should be placed
{
const FString DefaultSuffix = TEXT ( " _Func " ) ;
FString Name ;
FString PackageName ;
IAssetTools & AssetTools = FModuleManager : : GetModuleChecked < FAssetToolsModule > ( " AssetTools " ) . Get ( ) ;
AssetTools . CreateUniqueAssetName ( MaterialEditor . OriginalMaterialObject - > GetOutermost ( ) - > GetName ( ) , DefaultSuffix , PackageName , Name ) ;
UMaterialFunctionFactoryNew * Factory = NewObject < UMaterialFunctionFactoryNew > ( ) ;
UObject * FunctionObject = AssetTools . CreateAssetWithDialog ( Name , FPackageName : : GetLongPackagePath ( PackageName ) , UMaterialFunction : : StaticClass ( ) , Factory ) ;
if ( ! FunctionObject )
{
// Cancelled
return ;
}
NewMaterialFunction = Cast < UMaterialFunction > ( FunctionObject ) ;
if ( ! ensure ( NewMaterialFunction ) )
{
return ;
}
// Open the asset editor for the material function, so we can paste the nodes in it
// This is somewhat hacky, but much simpler than dealing with the material expressions themselves
// Needs to be done outside of the transaction, otherwise internal changes (like creating the graph schema) will be undone as well & will crash
NewMaterialEditor = OpenMaterialEditorForAsset ( NewMaterialFunction ) ;
if ( ! ensure ( NewMaterialEditor ) | |
! ensure ( NewMaterialEditor - > Material ) | |
! ensure ( NewMaterialEditor - > Material - > MaterialGraph ) )
{
return ;
}
NewGraph = NewMaterialEditor - > Material - > MaterialGraph ;
}
// Create the new function nodes
{
// Make sure to not put the asset creation or OpenMaterialEditorForAsset in the transaction - that won't undo properly
// We need two transactions here, as we need to call UpdateOriginalMaterial before spawning the new function call node
// and calling UpdateOriginalMaterial inside a transactions seems to create some asset corruption on undo
//
// Example of repro when UpdateOriginalMaterial is in a transaction:
// Collapse to function -> Ctrl Shift S -> Ctrl Z -> Ctrl Shift S -> the material object function is now deleted
// However the asset editor for the material function is still open, and references an invalid material function.
// The asset is also still visible in the content browser, but is nulled in memory (right clicking the asset will crash)
const FScopedTransaction Transaction ( LOCTEXT ( " OnCollapseToFunctionCreateNewFunction " , " Collapse to function - create new function " ) ) ;
TMap < FGuid , UMaterialGraphNode * > OldGuidToNewNode ;
// Paste the nodes inside the new function
{
// Delete all default nodes
// Need to take copy as DeleteNodes will remove them from the graph
TArray < UEdGraphNode * > NodesCopy = NewGraph - > Nodes ;
NewMaterialEditor - > DeleteNodes ( NodesCopy , false ) ;
ensure ( NewGraph - > Nodes . Num ( ) = = 0 ) ;
// Copy the selected nodes
const FString Clipboard = MaterialEditor . CopyNodesToBuffer ( NodesToCollapse ) ;
// Paste the nodes, keeping track of their new GUIDs
// Note that the pins GUIDs don't change on paste - their uniqueness is only guaranteed within their node
TMap < FGuid , FGuid > OldToNewGuids ;
NewMaterialEditor - > PasteNodesHereFromBuffer ( FVector2D : : ZeroVector , NewGraph , Clipboard , & OldToNewGuids ) ;
FindNewNodes ( * NewMaterialEditor , OldGuidToNewNode , OldToNewGuids ) ;
}
TSet < FName > UsedPinNames ;
const auto GetFunctionPinName = [ & ] ( UEdGraphPin * Pin )
{
// For function inputs/outputs, we try to give them nice names based on the pin they are linked to
FName Name = Pin - > PinName ;
if ( Name . IsNone ( ) | | Name = = " Output " )
{
// Pin doesn't have a name, fall back to the node title
UMaterialGraphNode * Node = Cast < UMaterialGraphNode > ( Pin - > GetOwningNode ( ) ) ;
if ( ensure ( Node ) & & ensure ( Node - > MaterialExpression ) )
{
TArray < FString > Captions ;
Node - > MaterialExpression - > GetCaption ( Captions ) ;
if ( Captions . Num ( ) > 0 )
{
Name = * Captions [ 0 ] ;
}
}
}
// Ensure the function parameters are unique
while ( UsedPinNames . Contains ( Name ) )
{
Name . SetNumber ( Name . GetNumber ( ) + 1 ) ;
}
UsedPinNames . Add ( Name ) ;
return Name ;
} ;
{
int32 InputIndex = 0 ;
for ( auto & It : Inputs )
{
// This pin is outside of the collapsed nodes
UEdGraphPin * PinLinkedToInput = It . Key ;
UMaterialGraphNode * GraphNodeLinkedToInput = CastChecked < UMaterialGraphNode > ( PinLinkedToInput - > GetOwningNode ( ) ) ;
EMaterialValueType PinMaterialType = EMaterialValueType ( GraphNodeLinkedToInput - > GetOutputType ( PinLinkedToInput ) ) ;
EFunctionInputType PinFunctionType = { } ;
// This could maybe be shared with UMaterialExpressionFunctionInput, but there are tricky details involved with MCT_Float1 vs MCT_Float
static_assert ( FunctionInput_MAX = = 13 , " Need to update " ) ;
switch ( PinMaterialType )
{
case MCT_Float1 : PinFunctionType = FunctionInput_Scalar ; break ;
case MCT_Float2 : PinFunctionType = FunctionInput_Vector2 ; break ;
case MCT_Float3 : PinFunctionType = FunctionInput_Vector3 ; break ;
case MCT_Float4 : PinFunctionType = FunctionInput_Vector4 ; break ;
case MCT_Texture2D : PinFunctionType = FunctionInput_Texture2D ; break ;
case MCT_TextureCube : PinFunctionType = FunctionInput_TextureCube ; break ;
case MCT_Texture2DArray : PinFunctionType = FunctionInput_Texture2DArray ; break ;
case MCT_VolumeTexture : PinFunctionType = FunctionInput_VolumeTexture ; break ;
case MCT_StaticBool : PinFunctionType = FunctionInput_StaticBool ; break ;
case MCT_MaterialAttributes : PinFunctionType = FunctionInput_MaterialAttributes ; break ;
case MCT_TextureExternal : PinFunctionType = FunctionInput_TextureExternal ; break ;
case MCT_Bool : PinFunctionType = FunctionInput_Bool ; break ;
2023-09-05 08:51:11 -04:00
case MCT_Substrate : PinFunctionType = FunctionInput_Substrate ; break ;
2023-07-19 05:50:04 -04:00
default :
// Will happen pretty often with MCT_Float, as the types are rarely fully resolved
// (eg Add nodes can take float1/2/3/4)
PinFunctionType = FunctionInput_Scalar ;
}
const FVector2D Location = FVector2D ( - NodeBounds . GetExtent ( ) . X , ( InputIndex - Inputs . Num ( ) / 2 ) * 100 ) ;
UMaterialExpressionFunctionInput * FunctionInput = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionFunctionInput > (
NewGraph ,
Location ,
false ) ;
FunctionInput - > bCollapsed = true ;
FunctionInput - > InputName = GetFunctionPinName ( PinLinkedToInput ) ;
FunctionInput - > Id = FGuid : : NewGuid ( ) ;
FunctionInput - > SortPriority = 10 * InputIndex ;
FunctionInput - > InputType = PinFunctionType ;
// For each of the pins inside of the collapsed nodes, make a link to the new function input
UMaterialGraphNode * FunctionInputGraphNode = CastChecked < UMaterialGraphNode > ( FunctionInput - > GraphNode ) ;
for ( UEdGraphPin * OldPin : It . Value )
{
UEdGraphPin * NewPin = FindNewPin ( OldPin , OldGuidToNewNode ) ;
if ( ! ensure ( NewPin ) )
{
continue ;
}
FunctionInputGraphNode - > GetOutputPin ( 0 ) - > MakeLinkTo ( NewPin ) ;
}
InputIndex + + ;
}
}
{
int32 OutputIndex = 0 ;
for ( auto & It : Outputs )
{
// This pin is inside the collapsed nodes
UEdGraphPin * OutputPin = It . Key ;
const FVector2D Location = FVector2D ( NodeBounds . GetExtent ( ) . X , ( OutputIndex - Outputs . Num ( ) / 2 ) * 100 ) ;
UMaterialExpressionFunctionOutput * FunctionOutput = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionFunctionOutput > (
NewGraph ,
Location ,
false ) ;
FunctionOutput - > bCollapsed = true ;
FunctionOutput - > OutputName = GetFunctionPinName ( OutputPin ) ;
FunctionOutput - > Id = FGuid : : NewGuid ( ) ;
FunctionOutput - > SortPriority = 10 * OutputIndex ;
UMaterialGraphNode * FunctionOutputGraphNode = CastChecked < UMaterialGraphNode > ( FunctionOutput - > GraphNode ) ;
UMaterialGraphNode * NewGraphNode = OldGuidToNewNode . FindRef ( OutputPin - > GetOwningNode ( ) - > NodeGuid ) ;
if ( ! ensure ( NewGraphNode ) )
{
continue ;
}
// Find OutputPin on the pasted nodes
UEdGraphPin * NewPin = FindNewPin ( OutputPin , OldGuidToNewNode ) ;
if ( ! ensure ( NewPin ) )
{
continue ;
}
FunctionOutputGraphNode - > GetInputPin ( 0 ) - > MakeLinkTo ( NewPin ) ;
OutputIndex + + ;
}
}
}
// Focus one of the newly spawned nodes - by default it'll focus the now deleted default Result output
NewMaterialEditor - > JumpToNode ( NewGraph - > Nodes [ 0 ] ) ;
// Update the material function to have the correct function pins
// This MUST NOT be in a transaction
NewMaterialEditor - > UpdateOriginalMaterial ( ) ;
const FScopedTransaction Transaction ( LOCTEXT ( " OnCollapseToFunction " , " Collapse to function " ) ) ;
UMaterialExpressionMaterialFunctionCall * FunctionCall = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionMaterialFunctionCall > (
OldGraph ,
NodeBounds . GetCenter ( ) ,
true ) ;
if ( ! ensure ( FunctionCall - > SetMaterialFunctionEx ( nullptr , NewMaterialFunction ) ) )
{
return ;
}
UMaterialGraphNode * FunctionCallGraphNode = CastChecked < UMaterialGraphNode > ( FunctionCall - > GraphNode ) ;
// Connect inputs
{
int32 InputIndex = 0 ;
for ( auto & It : Inputs )
{
UEdGraphPin * PinLinkedToInput = It . Key ;
UEdGraphPin * InputPin = FunctionCallGraphNode - > GetInputPin ( InputIndex ) ;
if ( ensure ( InputPin ) )
{
InputPin - > MakeLinkTo ( PinLinkedToInput ) ;
}
InputIndex + + ;
}
}
// Connect outputs
{
int32 OutputIndex = 0 ;
for ( auto & It : Outputs )
{
for ( UEdGraphPin * PinLinkedToOutput : It . Value )
{
UEdGraphPin * OutputPin = FunctionCallGraphNode - > GetOutputPin ( OutputIndex ) ;
if ( ensure ( OutputPin ) )
{
OutputPin - > MakeLinkTo ( PinLinkedToOutput ) ;
}
}
OutputIndex + + ;
}
}
// Remove the old nodes that are now collapsed
MaterialEditor . DeleteNodes ( NodesToCollapse . Array ( ) , false ) ;
// Make sure the active material editor still is the one focused
MaterialEditor . FocusWindow ( ) ;
}
void FMaterialEditorHelpers : : ExpandNode ( FMaterialEditor & MaterialEditor )
{
TMap < UMaterialGraphNode * , FMaterialEditor * > FunctionCalls ;
for ( UObject * NodeObject : MaterialEditor . GetSelectedNodes ( ) )
{
UMaterialGraphNode * Node = Cast < UMaterialGraphNode > ( NodeObject ) ;
if ( ! Node )
{
continue ;
}
UMaterialExpressionMaterialFunctionCall * FunctionCallExpression = Cast < UMaterialExpressionMaterialFunctionCall > ( Node - > MaterialExpression ) ;
if ( ! FunctionCallExpression )
{
continue ;
}
UMaterialFunctionInterface * MaterialFunction = FunctionCallExpression - > MaterialFunction ;
if ( ! MaterialFunction )
{
continue ;
}
FMaterialEditor * FunctionMaterialEditor = OpenMaterialEditorForAsset ( MaterialFunction ) ;
if ( ! ensure ( FunctionMaterialEditor ) )
{
continue ;
}
FunctionCalls . Add ( Node , FunctionMaterialEditor ) ;
}
// Do the transaction after all the OpenMaterialEditorForAsset are done
const FScopedTransaction Transaction ( LOCTEXT ( " ExpandNode " , " Expand node " ) ) ;
for ( auto & It : FunctionCalls )
{
ExpandNode ( MaterialEditor , * It . Value , It . Key ) ;
}
// Make the the active material editor still is the one focused
MaterialEditor . FocusWindow ( ) ;
}
void FMaterialEditorHelpers : : ExpandNode ( FMaterialEditor & MaterialEditor , FMaterialEditor & FunctionMaterialEditor , UMaterialGraphNode * FunctionCallNode )
{
if ( ! ensure ( MaterialEditor . Material ) | |
! ensure ( MaterialEditor . Material - > MaterialGraph ) )
{
return ;
}
UMaterialGraph * Graph = MaterialEditor . Material - > MaterialGraph ;
UMaterialExpressionMaterialFunctionCall * FunctionCallExpression = CastChecked < UMaterialExpressionMaterialFunctionCall > ( FunctionCallNode - > MaterialExpression ) ;
// Copy the function nodes into this graph
TMap < FGuid , UMaterialGraphNode * > OldGuidToNewNode ;
TMap < FGuid , UMaterialExpressionFunctionInput * > IdsToFunctionInputs ;
TMap < FGuid , UMaterialExpressionFunctionOutput * > IdsToFunctionOutputs ;
FVector2D PastePositionOffset ( ForceInit ) ;
{
if ( ! ensure ( FunctionMaterialEditor . OriginalMaterial ) | |
! ensure ( FunctionMaterialEditor . OriginalMaterial - > MaterialGraph ) )
{
return ;
}
TSet < UEdGraphNode * > NodesToCopy ( FunctionMaterialEditor . OriginalMaterial - > MaterialGraph - > Nodes ) ;
// Make sure to not copy the function input/outputs - these can't be copied into materials
for ( auto It = NodesToCopy . CreateIterator ( ) ; It ; + + It )
{
UMaterialGraphNode * MaterialNode = Cast < UMaterialGraphNode > ( * It ) ;
if ( ! MaterialNode )
{
continue ;
}
UMaterialExpressionFunctionInput * FunctionInput = Cast < UMaterialExpressionFunctionInput > ( MaterialNode - > MaterialExpression ) ;
if ( FunctionInput )
{
ensure ( ! IdsToFunctionInputs . Contains ( FunctionInput - > Id ) ) ;
IdsToFunctionInputs . Add ( FunctionInput - > Id , FunctionInput ) ;
It . RemoveCurrent ( ) ;
continue ;
}
UMaterialExpressionFunctionOutput * FunctionOutput = Cast < UMaterialExpressionFunctionOutput > ( MaterialNode - > MaterialExpression ) ;
if ( FunctionOutput )
{
ensure ( ! IdsToFunctionOutputs . Contains ( FunctionOutput - > Id ) ) ;
IdsToFunctionOutputs . Add ( FunctionOutput - > Id , FunctionOutput ) ;
It . RemoveCurrent ( ) ;
continue ;
}
}
const FString Clipboard = FunctionMaterialEditor . CopyNodesToBuffer ( NodesToCopy ) ;
const FVector2D PasteLocation ( FunctionCallNode - > NodePosX , FunctionCallNode - > NodePosY ) ;
// Compute the offset in a similar way PasteNodesHereFromBuffer does it, if we need to add new node for input previews
FVector2D AveragePosition = FVector2D : : ZeroVector ;
for ( UEdGraphNode * Node : NodesToCopy )
{
AveragePosition . X + = Node - > NodePosX ;
AveragePosition . Y + = Node - > NodePosY ;
}
AveragePosition / = NodesToCopy . Num ( ) ;
PastePositionOffset = PasteLocation - AveragePosition ;
TMap < FGuid , FGuid > OldToNewGuids ;
MaterialEditor . PasteNodesHereFromBuffer ( PasteLocation , Graph , Clipboard , & OldToNewGuids ) ;
FindNewNodes ( MaterialEditor , OldGuidToNewNode , OldToNewGuids ) ;
}
TArray < UEdGraphPin * > FunctionCallInputPins ;
TArray < UEdGraphPin * > FunctionCallOutputPins ;
TArray < UEdGraphPin * > Pins = FunctionCallNode - > Pins ;
for ( int32 PinIndex = 0 ; PinIndex < Pins . Num ( ) ; PinIndex + + )
{
if ( Pins [ PinIndex ] - > Direction = = EGPD_Input )
{
FunctionCallInputPins . Add ( Pins [ PinIndex ] ) ;
}
else
{
FunctionCallOutputPins . Add ( Pins [ PinIndex ] ) ;
}
}
if ( ! ensure ( FunctionCallInputPins . Num ( ) = = FunctionCallExpression - > FunctionInputs . Num ( ) ) | |
! ensure ( FunctionCallOutputPins . Num ( ) = = FunctionCallExpression - > FunctionOutputs . Num ( ) ) )
{
return ;
}
// Fixup inputs, making sure to handle preview values correctly
for ( int32 InputIndex = 0 ; InputIndex < FunctionCallInputPins . Num ( ) ; InputIndex + + )
{
UEdGraphPin * FunctionCallInputPin = FunctionCallInputPins [ InputIndex ] ;
const FFunctionExpressionInput & FunctionCallInput = FunctionCallExpression - > FunctionInputs [ InputIndex ] ;
UMaterialExpressionFunctionInput * FunctionInput = IdsToFunctionInputs . FindRef ( FunctionCallInput . ExpressionInputId ) ;
UMaterialGraphNode * FunctionInputNode = Cast < UMaterialGraphNode > ( FunctionInput - > GraphNode ) ;
if ( ! ensure ( FunctionInput ) | | ! ensure ( FunctionInputNode ) )
{
continue ;
}
UEdGraphPin * PinLinkedToInput = nullptr ;
if ( FunctionCallInputPin - > LinkedTo . Num ( ) > 0 )
{
// If the input is connected, just use that as input
ensure ( FunctionCallInputPin - > LinkedTo . Num ( ) = = 1 ) ;
PinLinkedToInput = FunctionCallInputPin - > LinkedTo [ 0 ] ;
}
else
{
// If no input connected, we need to figure out what to do with preview pins
// if bUsePreviewValueAsDefault is false this is technically a compilation error - but it's better if we just ignore that and use the preview value anyway
TArray < UEdGraphPin * > InputPins ;
for ( int32 PinIndex = 0 ; PinIndex < FunctionInputNode - > Pins . Num ( ) ; PinIndex + + )
{
if ( FunctionInputNode - > Pins [ PinIndex ] - > Direction = = EGPD_Input )
{
InputPins . Add ( FunctionInputNode - > Pins [ PinIndex ] ) ;
}
}
if ( ! ensure ( InputPins . Num ( ) = = 1 ) )
{
continue ;
}
if ( InputPins [ 0 ] - > LinkedTo . Num ( ) > 0 )
{
ensure ( InputPins [ 0 ] - > LinkedTo . Num ( ) = = 1 ) ;
PinLinkedToInput = FindNewPin ( InputPins [ 0 ] - > LinkedTo [ 0 ] , OldGuidToNewNode ) ;
}
else
{
// See UMaterialExpressionFunctionInput::CompilePreviewValue
const FVector2D NewNodePosition = FVector2D ( FunctionInputNode - > NodePosX , FunctionInputNode - > NodePosY ) + PastePositionOffset ;
switch ( FunctionInput - > InputType )
{
case FunctionInput_Scalar :
{
UMaterialExpressionConstant * Constant = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionConstant > (
Graph ,
NewNodePosition ,
false ) ;
Constant - > R = FunctionInput - > PreviewValue . X ;
PinLinkedToInput = CastChecked < UMaterialGraphNode > ( Constant - > GraphNode ) - > GetOutputPin ( 0 ) ;
break ;
}
case FunctionInput_Vector2 :
{
UMaterialExpressionConstant2Vector * Constant = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionConstant2Vector > (
Graph ,
NewNodePosition ,
false ) ;
Constant - > R = FunctionInput - > PreviewValue . X ;
Constant - > G = FunctionInput - > PreviewValue . Y ;
PinLinkedToInput = CastChecked < UMaterialGraphNode > ( Constant - > GraphNode ) - > GetOutputPin ( 0 ) ;
break ;
}
case FunctionInput_Vector3 :
{
UMaterialExpressionConstant3Vector * Constant = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionConstant3Vector > (
Graph ,
NewNodePosition ,
false ) ;
Constant - > Constant = FLinearColor ( FunctionInput - > PreviewValue ) ;
PinLinkedToInput = CastChecked < UMaterialGraphNode > ( Constant - > GraphNode ) - > GetOutputPin ( 0 ) ;
break ;
}
case FunctionInput_Vector4 :
{
UMaterialExpressionConstant4Vector * Constant = FMaterialEditorUtilities : : CreateNewMaterialExpression < UMaterialExpressionConstant4Vector > (
Graph ,
NewNodePosition ,
false ) ;
Constant - > Constant = FLinearColor ( FunctionInput - > PreviewValue ) ;
PinLinkedToInput = CastChecked < UMaterialGraphNode > ( Constant - > GraphNode ) - > GetOutputPin ( 0 ) ;
break ;
}
default : break ;
}
}
}
if ( ! PinLinkedToInput )
{
// Can happen if the preview value is not supported
continue ;
}
TArray < UEdGraphPin * > OutputPins ;
for ( int32 PinIndex = 0 ; PinIndex < FunctionInputNode - > Pins . Num ( ) ; PinIndex + + )
{
if ( FunctionInputNode - > Pins [ PinIndex ] - > Direction = = EGPD_Output )
{
OutputPins . Add ( FunctionInputNode - > Pins [ PinIndex ] ) ;
}
}
if ( ! ensure ( OutputPins . Num ( ) = = 1 ) )
{
continue ;
}
for ( UEdGraphPin * LinkedToInMaterialFunction : OutputPins [ 0 ] - > LinkedTo )
{
UEdGraphPin * LinkedToInPastedNodes = FindNewPin ( LinkedToInMaterialFunction , OldGuidToNewNode ) ;
if ( ensure ( LinkedToInPastedNodes ) )
{
PinLinkedToInput - > MakeLinkTo ( LinkedToInPastedNodes ) ;
}
}
}
// Fixup outputs
for ( int32 OutputIndex = 0 ; OutputIndex < FunctionCallOutputPins . Num ( ) ; OutputIndex + + )
{
UEdGraphPin * FunctionCallOutputPin = FunctionCallOutputPins [ OutputIndex ] ;
const FFunctionExpressionOutput & FunctionCallOutput = FunctionCallExpression - > FunctionOutputs [ OutputIndex ] ;
UMaterialExpressionFunctionOutput * FunctionOutput = IdsToFunctionOutputs . FindRef ( FunctionCallOutput . ExpressionOutputId ) ;
UMaterialGraphNode * FunctionOutputNode = Cast < UMaterialGraphNode > ( FunctionOutput - > GraphNode ) ;
if ( ! ensure ( FunctionOutput ) | | ! ensure ( FunctionOutputNode ) )
{
continue ;
}
TArray < UEdGraphPin * > InputPins ;
for ( int32 PinIndex = 0 ; PinIndex < FunctionOutputNode - > Pins . Num ( ) ; PinIndex + + )
{
if ( FunctionOutputNode - > Pins [ PinIndex ] - > Direction = = EGPD_Input )
{
InputPins . Add ( FunctionOutputNode - > Pins [ PinIndex ] ) ;
}
}
if ( ! ensure ( InputPins . Num ( ) = = 1 ) )
{
continue ;
}
if ( InputPins [ 0 ] - > LinkedTo . Num ( ) = = 0 )
{
// Function output is not connected
continue ;
}
ensure ( InputPins [ 0 ] - > LinkedTo . Num ( ) = = 1 ) ;
UEdGraphPin * LinkedToInPastedNodes = FindNewPin ( InputPins [ 0 ] - > LinkedTo [ 0 ] , OldGuidToNewNode ) ;
if ( ! ensure ( LinkedToInPastedNodes ) )
{
continue ;
}
for ( UEdGraphPin * PinLinkedToOutput : FunctionCallOutputPin - > LinkedTo )
{
PinLinkedToOutput - > MakeLinkTo ( LinkedToInPastedNodes ) ;
}
}
// Finally, delete the function call node
MaterialEditor . DeleteNodes ( { FunctionCallNode } , false ) ;
}
FBox2D FMaterialEditorHelpers : : GetNodesBounds ( FMaterialEditor & MaterialEditor , const TSet < UEdGraphNode * > & Nodes )
{
FBox2D Bounds ( ForceInit ) ;
for ( UEdGraphNode * Node : Nodes )
{
FSlateRect Rect ;
MaterialEditor . GetBoundsForNode ( Node , Rect , 0 ) ;
Bounds + = Rect . GetBottomLeft ( ) ;
Bounds + = Rect . GetTopRight ( ) ;
}
return Bounds ;
}
void FMaterialEditorHelpers : : FindNewNodes ( FMaterialEditor & MaterialEditor , TMap < FGuid , UMaterialGraphNode * > & OutOldGuidToNewNode , const TMap < FGuid , FGuid > & OldToNewGuids )
{
if ( ! ensure ( MaterialEditor . Material ) | |
! ensure ( MaterialEditor . Material - > MaterialGraph ) )
{
return ;
}
UMaterialGraph * Graph = MaterialEditor . Material - > MaterialGraph ;
TMap < FGuid , UMaterialGraphNode * > GuidToNode ;
for ( UEdGraphNode * Node : Graph - > Nodes )
{
GuidToNode . Add ( Node - > NodeGuid , Cast < UMaterialGraphNode > ( Node ) ) ;
}
for ( auto & It : OldToNewGuids )
{
OutOldGuidToNewNode . Add ( It . Key , GuidToNode . FindRef ( It . Value ) ) ;
}
}
UEdGraphPin * FMaterialEditorHelpers : : FindNewPin ( UEdGraphPin * OldPin , const TMap < FGuid , UMaterialGraphNode * > & OldGuidToNewNode )
{
if ( ! ensure ( OldPin ) | | ! ensure ( OldPin - > GetOwningNode ( ) ) )
{
return nullptr ;
}
UMaterialGraphNode * NewGraphNode = OldGuidToNewNode . FindRef ( OldPin - > GetOwningNode ( ) - > NodeGuid ) ;
if ( ! ensure ( NewGraphNode ) )
{
return nullptr ;
}
// Find OldPin on the pasted nodes
// Pin GUIDs aren't changed on paste
UEdGraphPin * NewPin = nullptr ;
for ( UEdGraphPin * Pin : NewGraphNode - > Pins )
{
if ( Pin - > PinId = = OldPin - > PinId )
{
NewPin = Pin ;
}
}
ensure ( NewPin ) ;
return NewPin ;
}
FMaterialEditor * FMaterialEditorHelpers : : OpenMaterialEditorForAsset ( UObject * Asset )
{
UAssetEditorSubsystem * AssetEditorSubsystem = GEditor - > GetEditorSubsystem < UAssetEditorSubsystem > ( ) ;
AssetEditorSubsystem - > OpenEditorForAsset ( Asset ) ;
IAssetEditorInstance * AssetEditor = AssetEditorSubsystem - > FindEditorForAsset ( Asset , true ) ;
if ( ! ensure ( AssetEditor ) | |
// Clumsy type safety check just in case - see FMaterialEditor::GetToolkitFName
! ensure ( AssetEditor - > GetEditorName ( ) = = " MaterialEditor " ) )
{
return nullptr ;
}
return static_cast < FMaterialEditor * > ( AssetEditor ) ;
}
# undef LOCTEXT_NAMESPACE