Custom Graph Nodes

Creating custom voxel graph nodes is really easy!

Quick start

First, add the VoxelGraphmodule dependency to your build.cs.

Then, create a class similar to this:

#include "VoxelNodes/VoxelNodeHelpers.h"

// Return atan2(Y, X) <- tooltip
UCLASS(meta = (DisplayName = "Atan2"))
class YOURMODULE_API UVoxelNode_Atan2 : public UVoxelNodeHelper
{
    GENERATED_BODY()
    GENERATED_VOXELNODE_BODY()

public:
    UVoxelNode_Atan2()
    {
        SetInputs(EC::Float, "Y", EC::Float, "X");
        SetOutputs(EC::Float);
    }

    GENERATED_COMPUTENODE
    (
        DEFINE_INPUTS_REVERSED(float, float),
        DEFINE_OUTPUTS_REVERSED(float),
        _O0 = UVoxelNode_Atan2::Atan2(_I0, _I1);
    )

public:
    static float Atan2(float Y, float X)
    {
        return FMath::Atan2(Y, X);
    }
    static TVoxelRange<float> Atan2(const TVoxelRange<float>& Y, const TVoxelRange<float>& X)
    {
        return { -4, 4 };
    }
};

Atan2(float, float)is where the real computation happens.

Atan2(TVoxelRange, TVoxelRange)is for range analysis: here, the output will always be between -4 and 4.

Range analysis examples

Here are some examples of range analysis functions:

inline TVoxelRange<int> RoundToInt(const TVoxelRange<float>& Value)
{
    return { FMath::FloorToInt(Value.Min), FMath::CeilToInt(Value.Max) };
}
inline TVoxelRange<float> Lerp(const TVoxelRange<float>& A, const TVoxelRange<float>& B, const TVoxelRange<float>& Alpha)
{
    return A + Alpha * (B - A);
}
inline TVoxelRange<float> Tan(const TVoxelRange<float>& A)
{
    if (A.IsSingleValue())
    {
        return { FMath::Tan(A.Min), FMath::Tan(A.Max) };
    }
    else
    {
        return { MIN_flt, MAX_flt };
    }
}

You can use the Minand Max fields of voxel ranges. They also support basic arithmetic operators (* / + -). If you can't compute a range for those inputs, either call FVoxelRangeFailStatus::Fail() to show an error to the user or return min inf, max inf.

Supporting C++ translation

When compiling a voxel graph to C++, the code you put in GENERATED_COMPUTENODEwill be copied to the C++ file. However, if you are using functions (eg here UVoxelNode_Atan2::Atan2), those won't be included in the generated file.

To fix that, you need to add a custom include, like done here:

// Return atan2(Y, X) <- tooltip
UCLASS(meta = (DisplayName = "Atan2"))
class YOURMODULE_API UVoxelNode_Atan2 : public UVoxelNodeHelper
{
    GENERATED_BODY()
    GENERATED_VOXELNODE_BODY()

public:
    UVoxelNode_Atan2()
    {
        SetInputs(EC::Float, "Y", EC::Float, "X");
        SetOutputs(EC::Float);
    }

    class FLocalVoxelComputeNode : public FVoxelDataComputeNode
    {
    public:
        GENERATED_DATA_COMPUTE_NODE_BODY()

        using FVoxelDataComputeNode::FVoxelDataComputeNode;
        GENERATED_COMPUTE
        (
            DEFINE_INPUTS_REVERSED(float, float),
            DEFINE_OUTPUTS_REVERSED(float),
            _O0 = UVoxelNode_Atan2::Atan2(_I0, _I1);
        )
        void SetupCpp(FVoxelCppConfig& Config) const override
        {
            Config.AddInclude("MyInclude.h");
        }
    };

public:
    static float Atan2(float Y, float X)
    {
        return FMath::Atan2(Y, X);
    }
    static TVoxelRange<float> Atan2(const TVoxelRange<float>& Y, const TVoxelRange<float>& X)
    {
        return { -4, 4 };
    }
};

Compact nodes

By default, nodes look like this:

Sometimes you want to have a more compact look, like here:

To do that, add COMPACT_VOXELNODE("ATAN2") below GENERATED_VOXELNODE_BODY().

Node that uses X/Y/Z

You can access X/Y/Z in GENERATED_COMPUTE by using _C0: for instance, _C0.X + _C0.Y - _C0.Z.

However, if you do so, you need to tell the voxel graph compiler that you are using those coordinates. To do that, use the SET_NODE_DEPENDENCIES macro:

Add

SET_NODE_DEPENDENCIES(EVoxelAxisDependenciesFlags::XYZ) // Or X Y XY XZ YZ depending on what you're using

below GENERATED_VOXELNODE_BODY().

Last updated