I’m trying to write in c++ a custom material expression that will output 3 results in 3 different output pins. In the attached image you can see the idea I’m trying to follow: basically convert a material function to a material expression in order to don’t see what is inside of it.
So far I managed to create the node with all the pins I need and in the .cpp file I already have the function and the 3 different results. I’m stuck because I can’t find a solution to distribute the 3 results in the 3 output pins.
Is there anyone who can help me?
Thank you!
This is the example code for the node in the attached image (.h and .cpp files):
“CustomNodeMultiOutput.h”
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Materials/MaterialExpression.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "CustomNodeMultiOutput.generated.h"
UCLASS(MinimalAPI)
class UMaterialExpressionCustomNodeMultiOutput : public UMaterialExpression
{
GENERATED_UCLASS_BODY()
UPROPERTY()
TArray<FString> ParamNames;
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Random"))
FExpressionInput Input1;
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Tiling"))
FExpressionInput Input2;
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Divisions"))
FExpressionInput Input3;
UPROPERTY(EditAnywhere, Category = MaterialExpressionEndless_UVs, meta = (OverridingInputProperty = "Random"))
float Input1Deafult;
UPROPERTY(EditAnywhere, Category = MaterialExpressionEndless_UVs, meta = (OverridingInputProperty = "Tiling"))
float Input2Deafult;
UPROPERTY(EditAnywhere, Category = MaterialExpressionEndless_UVs, meta = (OverridingInputProperty = "Divisions"))
int32 Input3Deafult;
//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual const TArray<FExpressionInput*> GetInputs() override;
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
virtual TArray<FExpressionOutput>& GetOutputs() override;
virtual FText GetKeywords() const override { return FText::FromString(TEXT("SOA")); }
#endif
//~ End UMaterialExpression Interface
};
“CustomNodeMultiOutput.cpp”
// Fill out your copyright notice in the Description page of Project Settings.
#include "CustomNodeMultiOutput.h"
#include "CoreMinimal.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "MaterialCompiler.h"
#include "Materials/MaterialExpressionVectorNoise.h"
#if WITH_EDITOR
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "MaterialGraph/MaterialGraphNode.h"
#endif //WITH_EDITOR
#define LOCTEXT_NAMESPACE "MaterialExpression"
UMaterialExpressionCustomNodeMultiOutput::UMaterialExpressionCustomNodeMultiOutput(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
FText NAME_Utility;
FText NAME_SOA;
FConstructorStatics()
: NAME_Utility(LOCTEXT("Utility", "Utility"))
, NAME_SOA(LOCTEXT("SOA", "SOA"))
{
}
};
static FConstructorStatics ConstructorStatics;
bShowOutputNameOnPin = true;
bHidePreviewWindow = true;
// Parameters Default values
Input1Deafult = 1.0f;
Input2Deafult = 4.0f;
Input3Deafult = 5.0f;
ParamNames.Add(TEXT("Result 1"));
ParamNames.Add(TEXT("Result 2"));
ParamNames.Add(TEXT("Result 3"));
#if WITH_EDITORONLY_DATA
MenuCategories.Add(ConstructorStatics.NAME_Utility);
MenuCategories.Add(ConstructorStatics.NAME_SOA);
Outputs.Reset();
Outputs.Add(FExpressionOutput(TEXT(""), 1, 1, 1, 1, 0));
Outputs.Add(FExpressionOutput(TEXT(""), 1, 1, 1, 1, 0));
Outputs.Add(FExpressionOutput(TEXT(""), 1, 1, 1, 1, 0));
#endif
}
TArray<FExpressionOutput>& UMaterialExpressionCustomNodeMultiOutput::GetOutputs()
{
Outputs[0].OutputName = *(ParamNames[0]);
Outputs[1].OutputName = *(ParamNames[1]);
Outputs[2].OutputName = *(ParamNames[2]);
return Outputs;
}
#if WITH_EDITOR
int32 UMaterialExpressionCustomNodeMultiOutput::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
// if the input is hooked up, use it, otherwise use the internal constant
float Input1Value = Input1.GetTracedInput().Expression ? Input1.Compile(Compiler) : Compiler->Constant(Input1Deafult);
// if the input is hooked up, use it, otherwise use the internal constant
float Input2Value = Input2.GetTracedInput().Expression ? Input2.Compile(Compiler) : Compiler->Constant(Input2Deafult);
// if the input is hooked up, use it, otherwise use the internal constant
int32 Input3Value = Input3.GetTracedInput().Expression ? Input3.Compile(Compiler) : Compiler->Constant(Input3Deafult);
//////////////////////////////////////////////
// FUNCTION example
//////////////////////////////////////////////
int32 value1 = Compiler->Mul(Input1Value, Input2Value);
int32 Result1 = Compiler->Add(value1, Input2Value); // Output 1
int32 Result2 = Compiler->AppendVector(value1, Input2Value); // Output 2
int32 Result3 = Compiler->Div(Result2, Input3Value); // Output 3
return 0; //right now I don't know how to return the 3 results and pass them to the 3 output pins
}
const TArray<FExpressionInput*> UMaterialExpressionCustomNodeMultiOutput::GetInputs()
{
TArray<FExpressionInput*> OutInputs;
int32 InputIndex = 0;
while (FExpressionInput * Ptr = GetInput(InputIndex++))
{
OutInputs.Add(Ptr);
}
return OutInputs;
}
// Material Expression Node Name
void UMaterialExpressionCustomNodeMultiOutput::GetCaption(TArray<FString>& OutCaptions) const
{
OutCaptions.Add(TEXT("Custom MultiOutput"));
}
#endif // WITH_EDITOR