Last active
May 29, 2020 23:26
-
-
Save Phyronnaz/17327303d340de7a1a32e45ad2a9cb43 to your computer and use it in GitHub Desktop.
Unreal Engine Material Graph Pack Node
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 Phyronnaz | |
#include "MaterialExpressionPack.h" | |
#include "MaterialCompiler.h" | |
#include "EdGraph/EdGraphNode.h" | |
#include "Materials/MaterialFunction.h" | |
#include "Materials/MaterialExpressionReroute.h" | |
#include "Materials/MaterialExpressionFunctionInput.h" | |
#include "Materials/MaterialExpressionFunctionOutput.h" | |
#include "Materials/MaterialExpressionMaterialFunctionCall.h" | |
UMaterialExpressionPack::UMaterialExpressionPack() | |
{ | |
#if WITH_EDITORONLY_DATA | |
MenuCategories.Add(INVTEXT("Packing")); | |
#endif | |
} | |
#if WITH_EDITOR | |
void UMaterialExpressionPack::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) | |
{ | |
Super::PostEditChangeProperty(PropertyChangedEvent); | |
if (PropertyChangedEvent.MemberProperty) // Else crash when calling reconstruct node | |
{ | |
// There's a bug when copy pasting arrays with default values at the end | |
for (auto& Input : Inputs) | |
{ | |
if (Input.InputName == "DEFAULT_DO_NOT_USE") | |
{ | |
Input.InputName = {}; | |
} | |
} | |
if (GraphNode) | |
{ | |
GraphNode->ReconstructNode(); | |
} | |
OnPostEditChangeProperty.Broadcast(); | |
} | |
} | |
int32 UMaterialExpressionPack::Compile(FMaterialCompiler* Compiler, int32 OutputIndex) | |
{ | |
if (!FMaterialExpressionUnpackScope::GetQueuedElement()) | |
{ | |
Compiler->Error(TEXT("Pack is not followed by an Unpack node!")); | |
return -1; | |
} | |
const auto Element = FMaterialExpressionUnpackScope::Pop(); | |
if (!Element.Unpack->MatchesPack(this)) | |
{ | |
Compiler->Error(TEXT("Unpack is out of sync! Click it and press Refresh")); | |
return -1; | |
} | |
if (!ensure(Inputs.IsValidIndex(Element.OutputIndex))) | |
{ | |
Compiler->Error(TEXT("Pack: Invalid OutputIndex! (INTERNAL ERROR)")); | |
return -1; | |
} | |
return Inputs[Element.OutputIndex].Input.Compile(Compiler); | |
} | |
void UMaterialExpressionPack::GetCaption(TArray<FString>& OutCaptions) const | |
{ | |
OutCaptions.Add(TEXT("Pack")); | |
} | |
const TArray<FExpressionInput*> UMaterialExpressionPack::GetInputs() | |
{ | |
TArray<FExpressionInput*> Result; | |
for (auto& Input : Inputs) | |
{ | |
Result.Add(&Input.Input); | |
} | |
return Result; | |
} | |
FExpressionInput* UMaterialExpressionPack::GetInput(int32 InputIndex) | |
{ | |
if (Inputs.IsValidIndex(InputIndex)) | |
{ | |
return &Inputs[InputIndex].Input; | |
} | |
return nullptr; | |
} | |
FName UMaterialExpressionPack::GetInputName(int32 InputIndex) const | |
{ | |
if (Inputs.IsValidIndex(InputIndex)) | |
{ | |
return Inputs[InputIndex].InputName; | |
} | |
return {}; | |
} | |
#endif // WITH_EDITOR | |
/////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
UMaterialExpressionUnpack::UMaterialExpressionUnpack() | |
{ | |
#if WITH_EDITORONLY_DATA | |
MenuCategories.Add(INVTEXT("Packing")); | |
Outputs.Reset(); | |
bShowOutputNameOnPin = true; | |
#endif | |
} | |
#if WITH_EDITOR | |
void UMaterialExpressionUnpack::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) | |
{ | |
Super::PostEditChangeProperty(PropertyChangedEvent); | |
if (PropertyChangedEvent.MemberProperty) // Else crash when calling reconstruct node | |
{ | |
RefreshPack(); | |
} | |
} | |
int32 UMaterialExpressionUnpack::Compile(FMaterialCompiler* Compiler, int32 OutputIndex) | |
{ | |
if (FMaterialExpressionUnpackScope::GetQueuedElement()) | |
{ | |
Compiler->Error(TEXT("Multiple unpacks are used with no pack in-between! This is not supported")); | |
return -1; | |
} | |
static TMap<int32, FGuid> OutputIndicesGUIDs; | |
FGuid& OutputGUID = OutputIndicesGUIDs.FindOrAdd(OutputIndex); | |
if (!OutputGUID.IsValid()) | |
{ | |
OutputGUID = FGuid::NewGuid(); | |
} | |
// If we don't do that, the output will be cached & the same thing will be used for all output indices | |
FScopedMaterialCompilerAttribute AttributeScope(Compiler, OutputGUID); | |
FMaterialExpressionUnpackScope UnpackScope(this, OutputIndex); | |
const int32 Ret = Input.Compile(Compiler); | |
if (Ret != -1) | |
{ | |
if (Compiler->GetType(Ret) == MCT_StaticBool) | |
{ | |
// Messes up the instances, due to the code in FMaterialEditorUtilities::GetStaticSwitchExpressionValue | |
Compiler->Error(TEXT("Pack/Unpack nodes cannot be used with static bools")); | |
return -1; | |
} | |
if (Input.Expression && !Input.Expression->IsResultMaterialAttributes(0)) | |
{ | |
// If Ret is -1, reroute nodes will return false | |
Compiler->Error(TEXT("Pack/Unpack nodes can only be connected to material attributes function input/output")); | |
} | |
} | |
return Ret; | |
} | |
void UMaterialExpressionUnpack::GetCaption(TArray<FString>& OutCaptions) const | |
{ | |
OutCaptions.Add(TEXT("Unpack")); | |
} | |
void UMaterialExpressionUnpack::RefreshPack() | |
{ | |
bRefresh = false; | |
Outputs.Reset(); | |
auto* Pack = GetPack_NotForCompile(); | |
if (Pack) | |
{ | |
for (const auto& PackInput : Pack->Inputs) | |
{ | |
Outputs.Add(FExpressionOutput(PackInput.InputName)); | |
} | |
if (!Pack->OnPostEditChangeProperty.IsBoundToObject(this)) | |
{ | |
Pack->OnPostEditChangeProperty.AddUObject(this, &UMaterialExpressionUnpack::RefreshPack); | |
} | |
} | |
if (GraphNode) | |
{ | |
GraphNode->ReconstructNode(); | |
} | |
} | |
UMaterialExpressionPack* UMaterialExpressionUnpack::GetPack_NotForCompile() const | |
{ | |
UMaterialExpression* Expression = Input.Expression; | |
TSet<UMaterialExpression*> VisitedExpressions; | |
while (!VisitedExpressions.Contains(Expression)) | |
{ | |
VisitedExpressions.Add(Expression); | |
if (auto* Pack = Cast<UMaterialExpressionPack>(Expression)) | |
{ | |
return Pack; | |
} | |
if (auto* Reroute = Cast<UMaterialExpressionReroute>(Expression)) | |
{ | |
Expression = Reroute->Input.Expression; | |
} | |
if (auto* FunctionInput = Cast<UMaterialExpressionFunctionInput>(Expression)) | |
{ | |
Expression = FunctionInput->Preview.Expression; | |
} | |
if (auto* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression)) | |
{ | |
Expression = FunctionOutput->A.Expression; | |
} | |
if (auto* FunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>(Expression)) | |
{ | |
if (ensure(FunctionCall->FunctionOutputs.IsValidIndex(Input.OutputIndex))) | |
{ | |
const auto& FunctionOutput = FunctionCall->FunctionOutputs[Input.OutputIndex]; | |
if (ensure(FunctionOutput.ExpressionOutput)) // If this gets raised, the ExpressionOutput is not cached and needs to be | |
{ | |
Expression = FunctionOutput.ExpressionOutput; | |
} | |
} | |
} | |
} | |
return nullptr; | |
} | |
bool UMaterialExpressionUnpack::MatchesPack(UMaterialExpressionPack* Pack) | |
{ | |
check(Pack); | |
if (Pack->Inputs.Num() != Outputs.Num()) | |
{ | |
return false; | |
} | |
for (int32 Index = 0; Index < Outputs.Num(); Index++) | |
{ | |
if (Pack->Inputs[Index].InputName != Outputs[Index].OutputName) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
#endif // WITH_EDITOR | |
/////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
FMaterialExpressionUnpackScope::FMaterialExpressionUnpackScope(UMaterialExpressionUnpack* Unpack, int32 OutputIndex) | |
{ | |
check(Unpack); | |
check(!QueuedElement.IsValid()); | |
QueuedElement = MakeUnique<FElement>(FElement{ Unpack, OutputIndex }); | |
} | |
FMaterialExpressionUnpackScope::~FMaterialExpressionUnpackScope() | |
{ | |
// Might already have been reset | |
QueuedElement.Reset(); | |
} | |
TUniquePtr<FMaterialExpressionUnpackScope::FElement> FMaterialExpressionUnpackScope::QueuedElement; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 Phyronnaz | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "Materials/MaterialExpression.h" | |
#include "MaterialExpressionPack.generated.h" | |
USTRUCT() | |
struct FMaterialPackInput | |
{ | |
GENERATED_BODY() | |
UPROPERTY(EditAnywhere, Category=PackInput) | |
FName InputName = "DEFAULT_DO_NOT_USE"; // Assign a default value to bypass copy paste bug when items are equal to default | |
UPROPERTY() | |
FExpressionInput Input; | |
}; | |
UCLASS(CollapseCategories, HideCategories = Object) | |
class UMaterialExpressionPack : public UMaterialExpression | |
{ | |
GENERATED_BODY() | |
public: | |
UMaterialExpressionPack(); | |
UPROPERTY(EditAnywhere, Category=MaterialExpressionPack) | |
TArray<FMaterialPackInput> Inputs; | |
FSimpleMulticastDelegate OnPostEditChangeProperty; | |
#if WITH_EDITOR | |
//~ Begin UObject Interface. | |
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; | |
//~ End UObject Interface. | |
//~ Begin UMaterialExpression Interface | |
virtual int32 Compile(FMaterialCompiler* Compiler, int32 OutputIndex) override; | |
virtual void GetCaption(TArray<FString>& OutCaptions) const override; | |
virtual const TArray<FExpressionInput*> GetInputs() override; | |
virtual FExpressionInput* GetInput(int32 InputIndex) override; | |
virtual FName GetInputName(int32 InputIndex) const override; | |
virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Unknown; } | |
virtual uint32 GetOutputType(int32 OutputIndex) override { return MCT_Unknown; } | |
virtual bool IsResultMaterialAttributes(int32 OutputIndex) override { return true; } | |
//~ End UMaterialExpression Interface | |
#endif | |
}; | |
UCLASS(CollapseCategories, HideCategories = Object) | |
class UMaterialExpressionUnpack : public UMaterialExpression | |
{ | |
GENERATED_BODY() | |
public: | |
UMaterialExpressionUnpack(); | |
UPROPERTY() | |
FExpressionInput Input; | |
UPROPERTY(EditAnywhere, Category=MaterialExpressionUnpack) | |
bool bRefresh = false; | |
#if WITH_EDITOR | |
//~ Begin UObject Interface. | |
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; | |
//~ End UObject Interface. | |
//~ Begin UMaterialExpression Interface | |
virtual int32 Compile(FMaterialCompiler* Compiler, int32 OutputIndex) override; | |
virtual void GetCaption(TArray<FString>& OutCaptions) const override; | |
virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Unknown; } | |
virtual uint32 GetOutputType(int32 OutputIndex) override { return MCT_Unknown; } | |
//~ End UMaterialExpression Interface | |
#endif | |
void RefreshPack(); | |
UMaterialExpressionPack* GetPack_NotForCompile() const; | |
bool MatchesPack(UMaterialExpressionPack* Pack); | |
}; | |
struct FMaterialExpressionUnpackScope | |
{ | |
struct FElement | |
{ | |
UMaterialExpressionUnpack* Unpack = nullptr; | |
int32 OutputIndex = 0; | |
}; | |
FMaterialExpressionUnpackScope(UMaterialExpressionUnpack* Unpack, int32 OutputIndex); | |
~FMaterialExpressionUnpackScope(); | |
static FElement* GetQueuedElement() | |
{ | |
return QueuedElement.Get(); | |
} | |
static FElement Pop() | |
{ | |
check(QueuedElement.IsValid()); | |
const auto Copy = *QueuedElement; | |
QueuedElement.Reset(); | |
return Copy; | |
} | |
private: | |
static TUniquePtr<FElement> QueuedElement; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment