When writing a custom node you need to think of it as a process that gets A data, does X thing, and returns B data.

All nodes inherit from InfiniteLandsNode, a base class that provides essential methods for storing nodes within the graph, managing connections, and handling cached data for use by other nodes.

To make a node creatable in the editor, you must use the CustomNode attribute, which has the following properties:

  • name: The node’s name.
  • customType: By default, nodes are placed inside a folder named Nodes, followed by their category. For example, operation nodes are stored in Nodes/HeightMaps/Operation. If you prefer a custom category but don’t want to set up the folder structure, you can use this field to define it.
  • docs: A URL pointing to the node’s documentation.
  • canCreate: If set to true, the node can be created via the menu.
  • startCollapse: If set to true, the node will be collapsed after generation.
  • singleInstance: Determines whether multiple instances of this node type can be created.

With this, the simplest functional node follows a specific structure. This is enough to make the node appear in the editor, allow creation, and enable movement—though, at this stage, it won’t perform any actual operations.

using sapra.InfiniteLands;
[CustomNode("Test")]
public class CustomNodeDoNothing : InfiniteLandsNode
{

}

Making it Work

To add functionality, you need to:

  • Define ports.
  • Define a Process method.

Ports

Ports are fields marked with the InputAttribute or OutputAttribute. By default, values are set via the SetInputValues method and cached using CacheOutputValues.

Infinite Lands currently has proper support for:

  • HeightData: A struct containing the necessary fields to represent and reference a HeightMap.
  • PointInstance: A class containing the different points that are either generated or need to be generated.

These types of ports will have custom previews. However, you can create any type of transferable object and implement its preview as needed.

Creating a Preview

A port’s preview is automatically determined by its type. Currently, previews are available for:

  • Asset Outputs: For objects of type IAsset.
  • Height Data: For objects of type HeightData.
  • Point Instance: For objects of type PointInstance.

You can create a custom preview by defining an editor class that inherits from OutputPreview.

Process

Once the ports are defined, you must implement a process that moves, creates, or transforms data from the input object to the output object. To do this, override the Process method.

Here’s an example of a node that moves data from input to output without modification:

using sapra.InfiniteLands;

[CustomNode("Transfer")]
public class TransferNode : InfiniteLandsNode
{
    [Input] public HeightData Input;
    [Output] public HeightData Output;
    protected override void Process(GenerationSettings settings, DataStore store)
    {
        Output = Input;
    }
}

Adding Visuals

To modify a node’s appearance, create a custom editor by defining an editor class that inherits from NodeView and applying the EditorForClass attribute.

Interfaces

Additional functionality can be implemented using interfaces. For simple nodes, this is generally not required. Below are the available interfaces you can use:

  • IHeightMapConnector: Controls how much data the node requires. By default, it automatically requests as much data as the output needs, but you can customize it to include additional padding.
  • IProcessPoints: Required for generating points. Unlike other data, points are generated globally rather than per chunk, so they follow a slightly different lifecycle.
  • ICreateGrid: Modifies the positions of vertices used during processing. This is used in the main Height Output Node and various warping nodes, such as the Warp Node.
  • ILoadAsset
  • ILoadPoints
  • IOutput: Marks the node as a final output, ensuring that mesh settings match the final output exactly.
  • ISetAsset