Files
UnrealEngineUWP/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp
ben ingram 0bb0a923a0 Merging Dev-LWCRendering into Main, this includes initial work to support rendering with LWC-scale position
Basic approach is to add HLSL types FLWCScalar, FLWCMatrix, FLWCVector, etc.  Inside shaders, absolute world space position values should be represented as FLWCVector3.  Matrices that transform *into* absolute world space become FLWCMatrix.  Matrices that transform *from* world space become FLWCInverseMatrix.  Generally LWC values work by extending the regular 'float' value with an additional tile coordinate.  Final tile size will be a trade-off between scale/accuracy; I'm using 256k for now, but may need to be adjusted.  Value represented by a FLWCVector thus becomes V.Tile * TileSize + V.Offset.  Most operations can be performed directly on LWC values.  There are HLSL functions like LWCAdd, LWCSub, LWCMultiply, LWCDivide (operator overloading would be really nice here).  The goal is to stay with LWC values for as long as needed, then convert to regular float values when possible.  One thing that comes up a lot is working in translated (rather than absolute) world space.  WorldSpace + View.PrevPreViewTranslation = TranslatedWorldspace.  Except 'View.PrevPreViewTranslation' is now a FLWCVector3, and WorldSpace quantities should be as well.  So that becomes LWCAdd(WorldSpace, View.PrevPreViewTranslation) = TranslatedWorldspace.  Assuming that we're talking about a position that's "reasonably close" to the camera, it should be safe to convert the translated WS value to float.  The 'tile' coordinate of the 2 LWC values should cancel out when added together in this case.  I've done some work throughout the shader code to do this.  Materials are fully supporting LWC-values as well.  Projective texturing and vertex animation materials that I've tested work correctly even when positioned "far away" from the origin.

Lots of work remains to fully convert all of our shader code.  There's a function LWCHackToFloat(), which is a simple wrapper for LWCToFloat().  The idea of HackToFloat is to mark places that need further attention, where I'm simply converting absolute WS positions to float, to get shaders to compile.  Shaders converted in this way should continue to work for all existing content (without LWC-scale values), but they will break if positions get too large.

General overview of changed files:
LargeWorldCoordinates.ush - This defines the FLWC types and operations
GPUScene.cpp, SceneData.ush - Primitives add an extra 'float3' tile coordinate.  Instance data is unchanged, so instances need to stay within single-precision range of the primitive origin.  Could potentially split instances behind the scenes (I think) if we don't want this limitation
HLSLMaterialDerivativeAutogen.cpp, HLSLMaterialTranslator.cpp, Preshader.cpp - Translated materials to use LWC values
SceneView.cpp, SceneRelativeViewMatrices.cpp, ShaderCompiler.cpp, InstancedStereo.ush - View uniform buffer includes LWC values where appropriate
#jira UE-117101
#rb arne.schober, Michael.Galetzka

#ROBOMERGE-AUTHOR: ben.ingram
#ROBOMERGE-SOURCE: CL 17787435 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v881-17767770)

[CL 17787478 by ben ingram in ue5-release-engine-test branch]
2021-10-12 13:31:00 -04:00

1010 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MaterialGraphNode.cpp
=============================================================================*/
#include "MaterialGraph/MaterialGraphNode.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ToolMenus.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "Materials/MaterialExpressionComposite.h"
#include "Materials/MaterialExpressionPinBase.h"
#include "Materials/MaterialExpressionComponentMask.h"
#include "Materials/MaterialExpressionConstant.h"
#include "Materials/MaterialExpressionConstant2Vector.h"
#include "Materials/MaterialExpressionConstant3Vector.h"
#include "Materials/MaterialExpressionConstant4Vector.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "Materials/MaterialExpressionFontSample.h"
#include "Materials/MaterialExpressionFunctionInput.h"
#include "Materials/MaterialExpressionFunctionOutput.h"
#include "Materials/MaterialExpressionMaterialAttributeLayers.h"
#include "Materials/MaterialExpressionRuntimeVirtualTextureSample.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionStaticBool.h"
#include "Materials/MaterialExpressionStaticBoolParameter.h"
#include "Materials/MaterialExpressionTextureBase.h"
#include "Materials/MaterialExpressionTextureCoordinate.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "Materials/MaterialExpressionTextureObject.h"
#include "Materials/MaterialExpressionTextureProperty.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Materials/MaterialExpressionDoubleVectorParameter.h"
#include "Materials/MaterialExpressionViewProperty.h"
#include "Materials/MaterialExpressionMaterialLayerOutput.h"
#include "Materials/MaterialExpressionTextureObjectParameter.h"
#include "Materials/MaterialExpressionNamedReroute.h"
#include "Materials/MaterialExpressionReroute.h"
#include "Materials/MaterialExpressionCurveAtlasRowParameter.h"
#include "Materials/MaterialExpressionExecBegin.h"
#include "Materials/MaterialExpressionStrata.h"
#include "MaterialEditorUtilities.h"
#include "MaterialEditorActions.h"
#include "GraphEditorActions.h"
#include "GraphEditorSettings.h"
#include "Framework/Commands/GenericCommands.h"
#include "ScopedTransaction.h"
#define LOCTEXT_NAMESPACE "MaterialGraphNode"
static FText SpaceText = LOCTEXT("Space", " ");
/////////////////////////////////////////////////////
// UMaterialGraphNode
UMaterialGraphNode::UMaterialGraphNode(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bPreviewNeedsUpdate(false)
, bIsErrorExpression(false)
, bIsPreviewExpression(false)
{
}
void UMaterialGraphNode::PostCopyNode()
{
// Make sure the MaterialExpression goes back to being owned by the Material after copying.
ResetMaterialExpressionOwner();
}
FMaterialRenderProxy* UMaterialGraphNode::GetExpressionPreview()
{
return FMaterialEditorUtilities::GetExpressionPreview(GetGraph(), MaterialExpression);
}
void UMaterialGraphNode::RecreateAndLinkNode()
{
// Throw away the original pins
for (int32 PinIndex = 0; PinIndex < Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = Pins[PinIndex];
Pin->Modify();
Pin->BreakAllPinLinks();
UEdGraphNode::DestroyPin(Pin);
}
EmptyPins();
AllocateDefaultPins();
CastChecked<UMaterialGraph>(GetGraph())->LinkGraphNodesFromMaterial();
}
void UMaterialGraphNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
const FName PropertyName = PropertyChangedEvent.Property->GetFName();
if (PropertyName == FName(TEXT("NodeComment")))
{
if (MaterialExpression)
{
MaterialExpression->Modify();
// Note: This if prevents an infinite loop where the comment update triggers a node update that then triggers a comment update.
if (MaterialExpression->Desc != NodeComment)
{
MaterialExpression->Desc = NodeComment;
MaterialExpression->Material->PropagateExpressionParameterChanges(MaterialExpression);
}
}
}
}
}
void UMaterialGraphNode::PostEditImport()
{
// Make sure this MaterialExpression is owned by the Material it's being pasted into.
ResetMaterialExpressionOwner();
}
void UMaterialGraphNode::PostDuplicate(bool bDuplicateForPIE)
{
Super::PostDuplicate(bDuplicateForPIE);
if (!bDuplicateForPIE)
{
CreateNewGuid();
}
}
bool UMaterialGraphNode::CanPasteHere(const UEdGraph* TargetGraph) const
{
if (Super::CanPasteHere(TargetGraph))
{
const UMaterialGraph* MaterialGraph = Cast<const UMaterialGraph>(TargetGraph);
if (MaterialGraph)
{
// Check whether we're trying to paste a material function into a function that depends on it
UMaterialExpressionMaterialFunctionCall* FunctionExpression = Cast<UMaterialExpressionMaterialFunctionCall>(MaterialExpression);
bool bIsValidFunctionExpression = true;
if (MaterialGraph->MaterialFunction
&& FunctionExpression
&& FunctionExpression->MaterialFunction
&& FunctionExpression->MaterialFunction->IsDependent(MaterialGraph->MaterialFunction))
{
bIsValidFunctionExpression = false;
}
if (bIsValidFunctionExpression && MaterialExpression && IsAllowedExpressionType(MaterialExpression->GetClass(), MaterialGraph->MaterialFunction != NULL))
{
return true;
}
}
}
return false;
}
FText UMaterialGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
TArray<FString> Captions;
if (MaterialExpression)
{
MaterialExpression->GetCaption(Captions);
}
if (TitleType == ENodeTitleType::EditableTitle)
{
return FText::FromString(GetParameterName());
}
else if (MaterialExpression && (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle))
{
return FText::FromString(MaterialExpression->GetClass()->GetDescription());
}
else
{
// More useful to display multi line parameter captions in reverse order
// TODO: May have to choose order based on expression type if others need correct order
int32 CaptionIndex = Captions.Num() - 1;
FTextBuilder NodeTitle;
if (Captions.IsValidIndex(CaptionIndex))
{
NodeTitle.AppendLine(Captions[CaptionIndex]);
}
for (; CaptionIndex > 0; )
{
CaptionIndex--;
NodeTitle.AppendLine(Captions[CaptionIndex]);
}
if (MaterialExpression && MaterialExpression->bShaderInputData && (MaterialExpression->bHidePreviewWindow || MaterialExpression->bCollapsed))
{
if (MaterialExpression->IsA<UMaterialExpressionTextureProperty>())
{
NodeTitle.AppendLine(LOCTEXT("TextureProperty", "Texture Property"));
}
else if (MaterialExpression->IsA<UMaterialExpressionViewProperty>())
{
NodeTitle.AppendLine(LOCTEXT("ViewProperty", "View Property"));
}
else
{
NodeTitle.AppendLine(LOCTEXT("InputData", "Input Data"));
}
}
if (bIsPreviewExpression)
{
NodeTitle.AppendLine();
NodeTitle.AppendLine(LOCTEXT("PreviewExpression", "Previewing"));
}
return NodeTitle.ToText();
}
}
FLinearColor UMaterialGraphNode::GetNodeTitleColor() const
{
UMaterial* Material = CastChecked<UMaterialGraph>(GetGraph())->Material;
const UGraphEditorSettings* Settings = GetDefault<UGraphEditorSettings>();
if (bIsPreviewExpression)
{
// If we are currently previewing a node, its border should be the preview color.
return Settings->PreviewNodeTitleColor;
}
if (UsesBoolColour(MaterialExpression))
{
return Settings->BooleanPinTypeColor;
}
else if (UsesFloatColour(MaterialExpression))
{
return Settings->FloatPinTypeColor;
}
else if (UsesVectorColour(MaterialExpression))
{
return Settings->VectorPinTypeColor;
}
else if (UsesObjectColour(MaterialExpression))
{
return Settings->ObjectPinTypeColor;
}
else if (UsesEventColour(MaterialExpression))
{
return Settings->EventNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionMaterialFunctionCall::StaticClass()))
{
// Previously FColor(0, 116, 255);
return Settings->FunctionCallNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionMaterialAttributeLayers::StaticClass()))
{
return Settings->FunctionCallNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionFunctionInput::StaticClass()))
{
return Settings->FunctionCallNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionFunctionOutput::StaticClass()))
{
// Previously FColor(255, 155, 0);
return Settings->ResultNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionMaterialLayerOutput::StaticClass()))
{
// Previously FColor(255, 155, 0);
return Settings->ResultNodeTitleColor;
}
else if (MaterialExpression->IsA(UMaterialExpressionCustomOutput::StaticClass()))
{
// Previously FColor(255, 155, 0);
return Settings->ResultNodeTitleColor;
}
else if (MaterialExpression->HasExecInput())
{
return Settings->ExecutionPinTypeColor;
}
else if (const UMaterialExpressionNamedRerouteDeclaration* RerouteDeclaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(MaterialExpression))
{
// If it's a declaration node, we simply get the color from it
return RerouteDeclaration->NodeColor;
}
else if (const UMaterialExpressionNamedRerouteUsage* RerouteUsage = Cast<UMaterialExpressionNamedRerouteUsage>(MaterialExpression))
{
// Return the color of the declaration
if (RerouteUsage->Declaration)
{
return RerouteUsage->Declaration->NodeColor;
}
return FLinearColor::Black;
}
else if (UMaterial::IsParameter(MaterialExpression))
{
if (Material->HasDuplicateParameters(MaterialExpression))
{
return FColor( 0, 255, 255 );
}
else
{
return FColor( 0, 128, 128 );
}
}
else if (UMaterial::IsDynamicParameter(MaterialExpression))
{
if (Material->HasDuplicateDynamicParameters(MaterialExpression))
{
return FColor( 0, 255, 255 );
}
else
{
return FColor( 0, 128, 128 );
}
}
else if (const UMaterialExpressionStrataBSDF* StrataBSDF = Cast<UMaterialExpressionStrataBSDF>(MaterialExpression))
{
return FColor(181, 29, 230);
}
// Assume that most material expressions act like pure functions and don't affect anything else
return Settings->PureFunctionCallNodeTitleColor;
}
FText UMaterialGraphNode::GetTooltipText() const
{
if (MaterialExpression)
{
TArray<FString> ToolTips;
MaterialExpression->GetExpressionToolTip(ToolTips);
if (ToolTips.Num() > 0)
{
FString ToolTip = ToolTips[0];
for (int32 Index = 1; Index < ToolTips.Num(); ++Index)
{
ToolTip += TEXT("\n");
ToolTip += ToolTips[Index];
}
return FText::FromString(ToolTip);
}
}
return FText::GetEmpty();
}
void UMaterialGraphNode::PrepareForCopying()
{
if (MaterialExpression)
{
// Temporarily take ownership of the MaterialExpression, so that it is not deleted when cutting
MaterialExpression->Rename(NULL, this, REN_DontCreateRedirectors);
}
}
void UMaterialGraphNode::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(GetGraph());
if (Context->Node)
{
FToolMenuInsert MenuPosition = FToolMenuInsert(NAME_None, EToolMenuInsertType::First);
FToolMenuSection& NodeSection = Menu->AddSection("MaterialSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"), MenuPosition);
if (MaterialExpression)
{
if (MaterialExpression->IsA(UMaterialExpressionTextureBase::StaticClass()))
{
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().UseCurrentTexture);
}
// Add a 'Convert To Texture' option for convertible types
{
if ( MaterialExpression->IsA(UMaterialExpressionTextureSample::StaticClass()) && !MaterialExpression->HasAParameterName())
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertToTextureObjects);
}
else if ( MaterialExpression->IsA(UMaterialExpressionTextureObject::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertToTextureSamples);
}
}
}
// Add a 'Convert to Local Variables' option to reroute nodes
if (MaterialExpression->IsA(UMaterialExpressionReroute::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertRerouteToNamedReroute);
}
// Add local variables selection & conversion to reroute nodes
if (MaterialExpression->IsA(UMaterialExpressionNamedRerouteBase::StaticClass()))
{
if (MaterialExpression->IsA(UMaterialExpressionNamedRerouteDeclaration::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().SelectNamedRerouteUsages);
}
if (MaterialExpression->IsA(UMaterialExpressionNamedRerouteUsage::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().SelectNamedRerouteDeclaration);
}
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertNamedRerouteToReroute);
}
// Add a 'Convert To Parameter' option for convertible types
if (MaterialExpression->IsA(UMaterialExpressionConstant::StaticClass())
|| MaterialExpression->IsA(UMaterialExpressionConstant2Vector::StaticClass())
|| MaterialExpression->IsA(UMaterialExpressionConstant3Vector::StaticClass())
|| MaterialExpression->IsA(UMaterialExpressionConstant4Vector::StaticClass())
|| (MaterialExpression->IsA(UMaterialExpressionTextureSample::StaticClass()) && !MaterialExpression->HasAParameterName())
|| (MaterialExpression->IsA(UMaterialExpressionRuntimeVirtualTextureSample::StaticClass()) && !MaterialExpression->HasAParameterName())
|| MaterialExpression->IsA(UMaterialExpressionTextureObject::StaticClass())
|| MaterialExpression->IsA(UMaterialExpressionComponentMask::StaticClass()))
{
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertObjects);
}
}
// Constants cannot be converted back to a CurveAtlasRowParameter
const bool bIsAStandardScalarParam = MaterialExpression->IsA(UMaterialExpressionScalarParameter::StaticClass())
&& !MaterialExpression->IsA(UMaterialExpressionCurveAtlasRowParameter::StaticClass());
// Add a 'Convert To Constant' option for convertible types
if (bIsAStandardScalarParam
|| MaterialExpression->IsA(UMaterialExpressionVectorParameter::StaticClass())
|| MaterialExpression->IsA(UMaterialExpressionTextureObjectParameter::StaticClass()))
{
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().ConvertToConstant);
}
}
// Add 'Promote to Double' option for float types
if (MaterialExpression->IsA(UMaterialExpressionVectorParameter::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().PromoteToDouble);
}
// Add 'Promote to Float' option for double types
if (MaterialExpression->IsA(UMaterialExpressionDoubleVectorParameter::StaticClass()))
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().PromoteToFloat);
}
{
// Don't show preview option for bools
if (!MaterialExpression->IsA(UMaterialExpressionStaticBool::StaticClass())
&& !MaterialExpression->IsA(UMaterialExpressionStaticBoolParameter::StaticClass()))
{
// Add a preview node option if only one node is selected
if (bIsPreviewExpression)
{
// If we are already previewing the selected node, the menu option should tell the user that this will stop previewing
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().StopPreviewNode);
}
else
{
// The menu option should tell the user this node will be previewed.
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().StartPreviewNode);
}
}
if (MaterialExpression->bRealtimePreview)
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().DisableRealtimePreviewNode);
}
else
{
NodeSection.AddMenuEntry(FMaterialEditorCommands::Get().EnableRealtimePreviewNode);
}
}
}
// Break all links
{
NodeSection.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
// Separate the above frequently used options from the below less frequently used common options
{
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenu3", LOCTEXT("GeneralNodeActionsMenuHeader", "General"));
Section.AddMenuEntry( FGenericCommands::Get().Delete );
Section.AddMenuEntry( FGenericCommands::Get().Cut );
Section.AddMenuEntry( FGenericCommands::Get().Copy );
Section.AddMenuEntry( FGenericCommands::Get().Duplicate );
// Select upstream and downstream nodes
Section.AddMenuEntry(FMaterialEditorCommands::Get().SelectDownstreamNodes);
Section.AddMenuEntry(FMaterialEditorCommands::Get().SelectUpstreamNodes);
}
{
FToolMenuSection& Section = Menu->AddSection("EdGraphSchemaOrganization", LOCTEXT("OrganizationHeader", "Organization"));
Section.AddMenuEntry(FGraphEditorCommands::Get().CollapseNodes);
Section.AddMenuEntry(FGraphEditorCommands::Get().ExpandNodes);
Section.AddSubMenu(
"Alignment",
LOCTEXT("AlignmentHeader", "Alignment"),
FText(),
FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
{
FToolMenuSection& SubMenuSection = InMenu->AddSection("EdGraphSchemaAlignment", LOCTEXT("AlignHeader", "Align"));
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesTop);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesMiddle);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesBottom);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesLeft);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesCenter);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().AlignNodesRight);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().StraightenConnections);
}
{
FToolMenuSection& SubMenuSection = InMenu->AddSection("EdGraphSchemaDistribution", LOCTEXT("DistributionHeader", "Distribution"));
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().DistributeNodesHorizontally);
SubMenuSection.AddMenuEntry(FGraphEditorCommands::Get().DistributeNodesVertically);
}
}));
}
{
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenuDocumentation");
Section.AddMenuEntry(FGraphEditorCommands::Get().GoToDocumentation);
}
// Handle the favorites options
if (MaterialExpression)
{
{
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenuFavorites");
if (FMaterialEditorUtilities::IsMaterialExpressionInFavorites(MaterialExpression))
{
Section.AddMenuEntry(FMaterialEditorCommands::Get().RemoveFromFavorites);
}
else
{
Section.AddMenuEntry(FMaterialEditorCommands::Get().AddToFavorites);
}
}
}
}
}
namespace MaterialPinNames
{
static const FName Coordinates(TEXT("Coordinates"));
static const FName UVs(TEXT("UVs"));
static const FName TextureObject(TEXT("TextureObject"));
static const FName Tex(TEXT("Tex"));
static const FName Input(TEXT("Input"));
static const FName Exponent(TEXT("Exponent"));
static const FName Exp(TEXT("Exp"));
static const FName AGreaterThanB(TEXT("AGreaterThanB"));
static const FName CompactAGreaterThanB(TEXT("A > B"));
static const FName AEqualsB(TEXT("AEqualsB"));
static const FName CompactAEqualsB(TEXT("A == B"));
static const FName ALessThanB(TEXT("ALessThanB"));
static const FName CompactALessThanB(TEXT("A < B"));
static const FName MipLevel(TEXT("MipLevel"));
static const FName Level(TEXT("Level"));
static const FName MipBias(TEXT("MipBias"));
static const FName Bias(TEXT("Bias"));
}
FName UMaterialGraphNode::GetShortenPinName(const FName PinName)
{
FName InputName = PinName;
// Shorten long expression input names.
if (PinName == MaterialPinNames::Coordinates)
{
InputName = MaterialPinNames::UVs;
}
else if (PinName == MaterialPinNames::TextureObject)
{
InputName = MaterialPinNames::Tex;
}
else if (PinName == MaterialPinNames::Input)
{
InputName = NAME_None;
}
else if (PinName == MaterialPinNames::Exponent)
{
InputName = MaterialPinNames::Exp;
}
else if (PinName == MaterialPinNames::AGreaterThanB)
{
InputName = MaterialPinNames::CompactAGreaterThanB;
}
else if (PinName == MaterialPinNames::AEqualsB)
{
InputName = MaterialPinNames::CompactAEqualsB;
}
else if (PinName == MaterialPinNames::ALessThanB)
{
InputName = MaterialPinNames::CompactALessThanB;
}
else if (PinName == MaterialPinNames::MipLevel)
{
InputName = MaterialPinNames::Level;
}
else if (PinName == MaterialPinNames::MipBias)
{
InputName = MaterialPinNames::Bias;
}
return InputName;
}
uint32 UMaterialGraphNode::GetPinMaterialType(const UEdGraphPin* Pin) const
{
if (Pin->PinType.PinCategory == UMaterialGraphSchema::PC_Exec)
{
return MCT_Execution;
}
if (Pin->Direction == EGPD_Input)
{
return MaterialExpression->GetInputType(Pin->SourceIndex);
}
else
{
return MaterialExpression->GetOutputType(Pin->SourceIndex);
}
}
void UMaterialGraphNode::CreateInputPins()
{
if (MaterialExpression->HasExecInput())
{
UEdGraphPin* NewPin = CreatePin(EGPD_Input, UMaterialGraphSchema::PC_Exec, NAME_None, NAME_None);
NewPin->SourceIndex = 0;
// Makes sure pin has a name for lookup purposes but user will never see it
NewPin->PinName = CreateUniquePinName(TEXT("Input"));
NewPin->PinFriendlyName = SpaceText;
}
const TArray<FExpressionInput*> ExpressionInputs = MaterialExpression->GetInputs();
for (int32 Index = 0; Index < ExpressionInputs.Num(); ++Index)
{
FExpressionInput* Input = ExpressionInputs[Index];
FName PinCategory;
FName InputName = MaterialExpression->GetInputName(Index);
InputName = GetShortenPinName(InputName);
if (MaterialExpression->IsInputConnectionRequired(Index))
{
PinCategory = UMaterialGraphSchema::PC_Required;
}
else
{
PinCategory = UMaterialGraphSchema::PC_Optional;
}
UEdGraphPin* NewPin = CreatePin(EGPD_Input, PinCategory, InputName);
NewPin->SourceIndex = Index;
if (NewPin->PinName.IsNone())
{
// Makes sure pin has a name for lookup purposes but user will never see it
NewPin->PinName = CreateUniquePinName(TEXT("Input"));
NewPin->PinFriendlyName = SpaceText;
}
}
}
void UMaterialGraphNode::CreateOutputPins()
{
TArray<FExpressionOutput>& Outputs = MaterialExpression->GetOutputs();
for(int32 Index = 0; Index < Outputs.Num(); ++Index)
{
const FExpressionOutput& ExpressionOutput = Outputs[Index];
FName PinCategory;
FName PinSubCategory;
FName OutputName;
if (MaterialExpression->bShowOutputNameOnPin)
{
OutputName = ExpressionOutput.OutputName;
}
if (MaterialExpression->bShowMaskColorsOnPin && ExpressionOutput.Mask)
{
PinCategory = UMaterialGraphSchema::PC_Mask;
if (ExpressionOutput.MaskR && !ExpressionOutput.MaskG && !ExpressionOutput.MaskB && !ExpressionOutput.MaskA)
{
PinSubCategory = UMaterialGraphSchema::PSC_Red;
}
else if (!ExpressionOutput.MaskR && ExpressionOutput.MaskG && !ExpressionOutput.MaskB && !ExpressionOutput.MaskA)
{
PinSubCategory = UMaterialGraphSchema::PSC_Green;
}
else if (!ExpressionOutput.MaskR && !ExpressionOutput.MaskG && ExpressionOutput.MaskB && !ExpressionOutput.MaskA)
{
PinSubCategory = UMaterialGraphSchema::PSC_Blue;
}
else if (!ExpressionOutput.MaskR && !ExpressionOutput.MaskG && !ExpressionOutput.MaskB && ExpressionOutput.MaskA)
{
PinSubCategory = UMaterialGraphSchema::PSC_Alpha;
}
else if (ExpressionOutput.MaskR && ExpressionOutput.MaskG && ExpressionOutput.MaskB && ExpressionOutput.MaskA)
{
PinSubCategory = UMaterialGraphSchema::PSC_RGBA;
}
}
UEdGraphPin* NewPin = CreatePin(EGPD_Output, PinCategory, PinSubCategory, OutputName);
NewPin->SourceIndex = Index;
if (NewPin->PinName.IsNone())
{
// Makes sure pin has a name for lookup purposes but user will never see it
NewPin->PinName = CreateUniquePinName(TEXT("Output"));
NewPin->PinFriendlyName = SpaceText;
}
}
TArray<FExpressionExecOutputEntry> ExecOutputs;
MaterialExpression->GetExecOutputs(ExecOutputs);
for (int32 Index = 0; Index < ExecOutputs.Num(); ++Index)
{
const FExpressionExecOutputEntry& Entry = ExecOutputs[Index];
FName OutputName;
if (MaterialExpression->bShowOutputNameOnPin)
{
OutputName = Entry.Name;
}
UEdGraphPin* NewPin = CreatePin(EGPD_Output, UMaterialGraphSchema::PC_Exec, NAME_None, OutputName);
NewPin->SourceIndex = Index;
if (NewPin->PinName.IsNone())
{
// Makes sure pin has a name for lookup purposes but user will never see it
NewPin->PinName = CreateUniquePinName(TEXT("Output"));
NewPin->PinFriendlyName = SpaceText;
}
}
}
void UMaterialGraphNode::ResetMaterialExpressionOwner()
{
if (MaterialExpression)
{
// Ensures MaterialExpression is owned by the Material or Function
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(GetGraph());
UObject* ExpressionOuter = MaterialGraph->Material;
if (MaterialGraph->MaterialFunction)
{
ExpressionOuter = MaterialGraph->MaterialFunction;
}
MaterialExpression->Rename(NULL, ExpressionOuter, REN_DontCreateRedirectors);
// Set up the back pointer for newly created material nodes
MaterialExpression->GraphNode = this;
}
}
void UMaterialGraphNode::PostPlacedNewNode()
{
if (MaterialExpression)
{
NodeComment = MaterialExpression->Desc;
bCommentBubbleVisible = MaterialExpression->bCommentBubbleVisible;
NodePosX = MaterialExpression->MaterialExpressionEditorX;
NodePosY = MaterialExpression->MaterialExpressionEditorY;
bCanRenameNode = MaterialExpression->CanRenameNode();
}
}
void UMaterialGraphNode::NodeConnectionListChanged()
{
Super::NodeConnectionListChanged();
const UEdGraphSchema* Schema = GetSchema();
if (Schema != nullptr)
{
Schema->ForceVisualizationCacheClear();
}
}
void UMaterialGraphNode::OnRenameNode(const FString& NewName)
{
MaterialExpression->Modify();
SetParameterName(NewName);
MaterialExpression->MarkPackageDirty();
MaterialExpression->ValidateParameterName();
FProperty* NameProperty = nullptr;
if (Cast<UMaterialExpressionParameter>(MaterialExpression))
{
NameProperty = FindFieldChecked<FProperty>(UMaterialExpressionParameter::StaticClass(), GET_MEMBER_NAME_CHECKED(UMaterialExpressionParameter, ParameterName));
}
else if (Cast<UMaterialExpressionFontSampleParameter>(MaterialExpression))
{
NameProperty = FindFieldChecked<FProperty>(UMaterialExpressionFontSampleParameter::StaticClass(), GET_MEMBER_NAME_CHECKED(UMaterialExpressionFontSampleParameter, ParameterName));
}
else if (Cast<UMaterialExpressionTextureSampleParameter>(MaterialExpression))
{
NameProperty = FindFieldChecked<FProperty>(UMaterialExpressionTextureSampleParameter::StaticClass(), GET_MEMBER_NAME_CHECKED(UMaterialExpressionTextureSampleParameter, ParameterName));
}
if(NameProperty)
{
FPropertyChangedEvent PropertyChangeEvent(NameProperty, EPropertyChangeType::ValueSet);
MaterialExpression->PostEditChangeProperty(PropertyChangeEvent);
}
MaterialDirtyDelegate.ExecuteIfBound();
}
void UMaterialGraphNode::OnUpdateCommentText( const FString& NewComment )
{
const FScopedTransaction Transaction( LOCTEXT( "CommentCommitted", "Comment Changed" ) );
// Update the Node comment
Modify();
NodeComment = NewComment;
// Update the Material Expresssion desc to match the comment
if( MaterialExpression )
{
MaterialExpression->Modify();
// Note: This if prevents an infinite loop where the comment update triggers a node update that then triggers a comment update.
if (MaterialExpression->Desc != NewComment)
{
MaterialExpression->Desc = NewComment;
MaterialExpression->Material->PropagateExpressionParameterChanges(MaterialExpression);
}
MaterialDirtyDelegate.ExecuteIfBound();
}
}
void UMaterialGraphNode::OnCommentBubbleToggled( bool bInCommentBubbleVisible )
{
if ( MaterialExpression )
{
MaterialExpression->Modify();
MaterialExpression->bCommentBubbleVisible = bInCommentBubbleVisible;
MaterialDirtyDelegate.ExecuteIfBound();
}
}
void UMaterialGraphNode::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
{
Super::GetPinHoverText(Pin, HoverTextOut);
if (HoverTextOut.IsEmpty())
{
TArray<FString> ToolTips;
int32 DirIndex = -1;
int32 PinIndex = INDEX_NONE;
for (int32 Index = 0; Index < Pins.Num(); ++Index)
{
if (Pin.Direction == Pins[Index]->Direction)
{
++DirIndex;
if (Pins[Index] == &Pin)
{
PinIndex = DirIndex;
break;
}
}
}
if (Pin.Direction == EEdGraphPinDirection::EGPD_Input)
{
MaterialExpression->GetConnectorToolTip(PinIndex, INDEX_NONE, ToolTips);
}
else
{
MaterialExpression->GetConnectorToolTip(INDEX_NONE, PinIndex, ToolTips);
}
if (ToolTips.Num() > 0)
{
HoverTextOut = ToolTips[0];
for (int32 Index = 1; Index < ToolTips.Num(); ++Index)
{
HoverTextOut += TEXT("\n");
HoverTextOut += ToolTips[Index];
}
}
}
}
FString UMaterialGraphNode::GetParameterName() const
{
return MaterialExpression->GetEditableName();
}
void UMaterialGraphNode::SetParameterName(const FString& NewName)
{
MaterialExpression->SetEditableName(NewName);
//@TODO: Push into the SetEditableName interface
CastChecked<UMaterialGraph>(GetGraph())->Material->UpdateExpressionParameterName(MaterialExpression);
}
bool UMaterialGraphNode::UsesBoolColour(UMaterialExpression* Expression)
{
if (Expression->IsA<UMaterialExpressionStaticBool>())
{
return true;
}
// Explicitly check for bool param as switch params inherit from it
else if (Expression->GetClass() == UMaterialExpressionStaticBoolParameter::StaticClass())
{
return true;
}
return false;
}
bool UMaterialGraphNode::UsesFloatColour(UMaterialExpression* Expression)
{
if (Expression->IsA<UMaterialExpressionConstant>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionScalarParameter>())
{
return true;
}
return false;
}
bool UMaterialGraphNode::UsesVectorColour(UMaterialExpression* Expression)
{
if (Expression->IsA<UMaterialExpressionConstant2Vector>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionConstant3Vector>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionConstant4Vector>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionVectorParameter>())
{
return true;
}
return false;
}
bool UMaterialGraphNode::UsesObjectColour(UMaterialExpression* Expression)
{
if (Expression->IsA<UMaterialExpressionTextureBase>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionFontSample>())
{
return true;
}
return false;
}
bool UMaterialGraphNode::UsesEventColour(UMaterialExpression* Expression)
{
if (Expression->bShaderInputData && !Expression->IsA<UMaterialExpressionStaticBool>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionFunctionInput>())
{
return true;
}
else if (Expression->IsA<UMaterialExpressionTextureCoordinate>())
{
return true;
}
return false;
}
FString UMaterialGraphNode::GetDocumentationExcerptName() const
{
// Default the node to searching for an excerpt named for the C++ node class name, including the U prefix.
// This is done so that the excerpt name in the doc file can be found by find-in-files when searching for the full class name.
UClass* MyClass = (MaterialExpression != NULL) ? MaterialExpression->GetClass() : this->GetClass();
return FString::Printf(TEXT("%s%s"), MyClass->GetPrefixCPP(), *MyClass->GetName());
}
bool UMaterialGraphNode::CanUserDeleteNode() const
{
if (MaterialExpression != NULL)
{
return MaterialExpression->CanUserDeleteExpression();
}
return true;
}
#undef LOCTEXT_NAMESPACE