Appearance
Creating custom voxel graph nodes is really easy!
Quick start
First, add the VoxelGraph
module dependency to your build.cs
.
Then, create a class similar to this:
cpp
#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:
cpp
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 Min
and 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_COMPUTENODE
will 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:
cpp
// 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
cpp
SET_NODE_DEPENDENCIES(EVoxelAxisDependenciesFlags::XYZ) // Or X Y XY XZ YZ depending on what you're using
below GENERATED_VOXELNODE_BODY()
.