// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Internationalization/Text.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundParamHelper.h" #include "MetasoundPrimitives.h" #include "MetasoundStandardNodesNames.h" #include "MetasoundTime.h" #include "MetasoundTrigger.h" #include "MetasoundStandardNodesCategories.h" #include "MetasoundVertex.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes" namespace Metasound { namespace RandomNodeNames { METASOUND_PARAM(InputNextTrigger, "Next", "Trigger to generate the next random integer.") METASOUND_PARAM(InputResetTrigger, "Reset", "Trigger to reset the random sequence with the supplied seed. Useful to get randomized repetition.") METASOUND_PARAM(InputSeed, "Seed", "The seed value to use for the random node. Set to -1 to use a random seed.") METASOUND_PARAM(InputMin, "Min", "Min random value.") METASOUND_PARAM(InputMax, "Max", "Max random value.") METASOUND_PARAM(OutputOnNextTrigger, "On Next", "Triggers when next is triggered.") METASOUND_PARAM(OutputOnResetTrigger, "On Reset", "Triggers when reset is triggered.") METASOUND_PARAM(OutputValue, "Value", "The randomly generated value.") } template struct TRandomNodeSpecialization { bool bSupported = false; }; template<> struct TRandomNodeSpecialization { static FName GetClassName() { return FName("RandomInt32"); } static FText GetDisplayName() { return METASOUND_LOCTEXT("RandomNode_Int32RandomValueDisplayName", "Random (Int)"); } static FText GetDescription() { return METASOUND_LOCTEXT("RandomNode_Int32RandomDescription", "Generates a seedable random integer in the given value range."); } static bool HasRange() { return true; } static int32 GetDefaultMin() { return 0; } static int32 GetDefaultMax() { return 100; } static int32 GetNextValue(FRandomStream& InStream, int32 InMin, int32 InMax) { return InStream.RandRange(InMin, InMax); } static TDataReadReference CreateMinValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMin), InOperatorSettings); } static TDataReadReference CreateMaxValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMax), InOperatorSettings); } }; template<> struct TRandomNodeSpecialization { static FName GetClassName() { return FName("RandomFloat"); } static FText GetDisplayName() { return METASOUND_LOCTEXT("RandomNode_FloatRandomValueDisplayName", "Random (Float)"); } static FText GetDescription() { return METASOUND_LOCTEXT("RandomNode_FloatRandomDescription", "Generates a seedable random float in the given value range."); } static bool HasRange() { return true; } static float GetDefaultMin() { return 0.0f; } static float GetDefaultMax() { return 1.0f; } static float GetNextValue(FRandomStream& InStream, float InMin, float InMax) { return InStream.FRandRange(InMin, InMax); } static TDataReadReference CreateMinValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMin), InOperatorSettings); } static TDataReadReference CreateMaxValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMax), InOperatorSettings); } }; template<> struct TRandomNodeSpecialization { static FName GetClassName() { return FName("RandomBool"); } static FText GetDisplayName() { return METASOUND_LOCTEXT("RandomNode_BoolRandomValueDisplayName", "Random (Bool)"); } static FText GetDescription() { return METASOUND_LOCTEXT("RandomNode_BoolRandomDiscription", "Generates a random bool value."); } static bool HasRange() { return false; } static bool GetDefaultMin() { return false; } static bool GetDefaultMax() { return true; } static bool GetNextValue(FRandomStream& InStream, bool, bool) { return (bool)InStream.RandRange(0, 1); } static TDataReadReference CreateMinValueRef(const FInputVertexInterfaceData&, const FOperatorSettings&) { return TDataReadReference::CreateNew(); } static TDataReadReference CreateMaxValueRef(const FInputVertexInterfaceData&, const FOperatorSettings&) { return TDataReadReference::CreateNew(); } }; template<> struct TRandomNodeSpecialization { static FName GetClassName() { return FName("RandomTime"); } static FText GetDisplayName() { return METASOUND_LOCTEXT("RandomNode_TimeRandomValueDisplayName", "Random (Time)"); } static FText GetDescription() { return METASOUND_LOCTEXT("RandomNode_TimeRandomDiscription", "Generates a random time value."); } static bool HasRange() { return true; } static float GetDefaultMin() { return 0.0f; } static float GetDefaultMax() { return 1.0f; } static FTime GetNextValue(FRandomStream& InStream, FTime InMin, FTime InMax) { float Seconds = InStream.FRandRange(InMin.GetSeconds(), InMax.GetSeconds()); return FTime(Seconds); } static TDataReadReference CreateMinValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMin), InOperatorSettings); } static TDataReadReference CreateMaxValueRef(const FInputVertexInterfaceData& InputData, const FOperatorSettings& InOperatorSettings) { using namespace RandomNodeNames; return InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputMax), InOperatorSettings); } }; template class TRandomNodeOperator : public TExecutableOperator> { public: static constexpr int32 DefaultSeed = -1; static const FVertexInterface& GetDefaultInterface() { using namespace RandomNodeNames; if (TRandomNodeSpecialization::HasRange()) { static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputNextTrigger)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputResetTrigger)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputSeed), DefaultSeed), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputMin), TRandomNodeSpecialization::GetDefaultMin()), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputMax), TRandomNodeSpecialization::GetDefaultMax()) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputOnNextTrigger)), TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputOnResetTrigger)), TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputValue)) ) ); return DefaultInterface; } else { static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputNextTrigger)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputResetTrigger)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputSeed), DefaultSeed) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputOnNextTrigger)), TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputOnResetTrigger)), TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputValue)) ) ); return DefaultInterface; } } static const FNodeClassMetadata& GetNodeInfo() { auto InitNodeInfo = []() -> FNodeClassMetadata { FNodeClassMetadata Info; Info.ClassName = { StandardNodes::Namespace, TRandomNodeSpecialization::GetClassName(), "" }; Info.MajorVersion = 1; Info.MinorVersion = 1; Info.DisplayName = TRandomNodeSpecialization::GetDisplayName(); Info.Description = METASOUND_LOCTEXT("RandomNode_Description", "Generates a random value."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = GetDefaultInterface(); Info.CategoryHierarchy.Emplace(NodeCategories::RandomUtils); return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } static TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) { const FInputVertexInterfaceData& InputData = InParams.InputData; using namespace RandomNodeNames; FTriggerReadRef InNextTrigger = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputNextTrigger), InParams.OperatorSettings); FTriggerReadRef InResetTrigger = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputResetTrigger), InParams.OperatorSettings); FInt32ReadRef InSeedValue = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputSeed), InParams.OperatorSettings); // note: random bool does not have a range if (TRandomNodeSpecialization::HasRange()) { TDataReadReference InMinValue = TRandomNodeSpecialization::CreateMinValueRef(InputData, InParams.OperatorSettings); TDataReadReference InMaxValue = TRandomNodeSpecialization::CreateMaxValueRef(InputData, InParams.OperatorSettings); return MakeUnique>(InParams.OperatorSettings, InNextTrigger, InResetTrigger, InSeedValue, InMinValue, InMaxValue); } else { return MakeUnique>(InParams.OperatorSettings, InNextTrigger, InResetTrigger, InSeedValue); } } TRandomNodeOperator(const FOperatorSettings& InSettings, TDataReadReference InNextTrigger, TDataReadReference InResetTrigger, TDataReadReference InSeedValue, TDataReadReference InMinValue, TDataReadReference InMaxValue) : NextTrigger(InNextTrigger) , ResetTrigger(InResetTrigger) , SeedValue(InSeedValue) , MinValue(InMinValue) , MaxValue(InMaxValue) , TriggerOutOnNext(FTriggerWriteRef::CreateNew(InSettings)) , TriggerOutOnReset(FTriggerWriteRef::CreateNew(InSettings)) , OutputValue(TDataWriteReferenceFactory::CreateAny(InSettings)) , bIsDefaultSeeded(*SeedValue == DefaultSeed) , bIsRandomStreamInitialized(false) { ResetInternal(); } TRandomNodeOperator(const FOperatorSettings& InSettings, TDataReadReference InNextTrigger, TDataReadReference InResetTrigger, TDataReadReference InSeedValue) : NextTrigger(InNextTrigger) , ResetTrigger(InResetTrigger) , SeedValue(InSeedValue) , MinValue(TDataReadReference::CreateNew()) // Create stub default for no range case , MaxValue(TDataReadReference::CreateNew()) , TriggerOutOnNext(FTriggerWriteRef::CreateNew(InSettings)) , TriggerOutOnReset(FTriggerWriteRef::CreateNew(InSettings)) , OutputValue(TDataWriteReferenceFactory::CreateAny(InSettings)) , bIsDefaultSeeded(*SeedValue == DefaultSeed) , bIsRandomStreamInitialized(false) { ResetInternal(); } virtual ~TRandomNodeOperator() = default; virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { using namespace RandomNodeNames; FDataReferenceCollection Inputs; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputNextTrigger), NextTrigger); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputResetTrigger), ResetTrigger); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputSeed), SeedValue); // If the type doesn't have a range no need for input pins to define it if (TRandomNodeSpecialization::HasRange()) { InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputMin), MinValue); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputMax), MaxValue); } } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { using namespace RandomNodeNames; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputOnNextTrigger), TriggerOutOnNext); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputOnResetTrigger), TriggerOutOnReset); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputValue), OutputValue); } void Execute() { TriggerOutOnReset->AdvanceBlock(); TriggerOutOnNext->AdvanceBlock(); ResetTrigger->ExecuteBlock( [&](int32 StartFrame, int32 EndFrame) { }, [this](int32 StartFrame, int32 EndFrame) { EvaluateSeedChanges(); RandomStream.Reset(); *OutputValue = TRandomNodeSpecialization::GetNextValue(RandomStream, *MinValue, *MaxValue); TriggerOutOnReset->TriggerFrame(StartFrame); } ); NextTrigger->ExecuteBlock( [&](int32 StartFrame, int32 EndFrame) { }, [this](int32 StartFrame, int32 EndFrame) { *OutputValue = TRandomNodeSpecialization::GetNextValue(RandomStream, *MinValue, *MaxValue); TriggerOutOnNext->TriggerFrame(StartFrame); } ); } // Externally visible initialize function void Reset(const IOperator::FResetParams& InParams) { ResetInternal(); } private: void ResetInternal() { TriggerOutOnNext->Reset(); TriggerOutOnReset->Reset(); bIsDefaultSeeded = (DefaultSeed == *SeedValue); bIsRandomStreamInitialized = false; EvaluateSeedChanges(); RandomStream.Reset(); // We need to initialize the output value to *something* *OutputValue = TRandomNodeSpecialization::GetNextValue(RandomStream, *MinValue, *MaxValue); } void EvaluateSeedChanges() { // if we have a non-zero seed if (*SeedValue != DefaultSeed) { // If we were previously zero-seeded OR our seed has changed if (bIsDefaultSeeded || !bIsRandomStreamInitialized || *SeedValue != RandomStream.GetInitialSeed()) { bIsRandomStreamInitialized = true; bIsDefaultSeeded = false; RandomStream.Initialize(*SeedValue); } } // If we are zero-seeded now BUT were previously not, we need to randomize our seed else if (!bIsDefaultSeeded || !bIsRandomStreamInitialized) { bIsRandomStreamInitialized = true; bIsDefaultSeeded = true; RandomStream.Initialize(FPlatformTime::Cycles()); } } FTriggerReadRef NextTrigger; FTriggerReadRef ResetTrigger; FInt32ReadRef SeedValue; TDataReadReference MinValue; TDataReadReference MaxValue; FTriggerWriteRef TriggerOutOnNext; FTriggerWriteRef TriggerOutOnReset; TDataWriteReference OutputValue; FRandomStream RandomStream; bool bIsDefaultSeeded = false; bool bIsRandomStreamInitialized = false; }; /** TRandomNode * * Generates a random float value when triggered. */ template using TRandomNode = TNodeFacade>; } // namespace Metasound #undef LOCTEXT_NAMESPACE