Open Search
    Implementing Texture Nodes

    Table of Contents

    Describes how to define a texture node that is automatically created when an image file is dragged into the Node Editor.

    Resource Editor

    A texture node is a custom user node that has to provide at least the following ports:

    • A maxon::Color output port that represents the result.
    • A maxon::Url input port that defines the input image URL.
    • A maxon::Int input port that defines the index of the starting frame.
    • A maxon::Int input port that defines the index of the ending frame.

    See Implementing Custom User Nodes for more information.

    Code

    A texture node has to be registered within NodeRendererNodeSpaceImpl::Init(). Where IMAGENODEASSETID defines the node identifier and IMAGENODEPORTIDS defines the identifiers of the ports that will be handled automatically by Cinema 4D. The object ImageNodePortTuple() accepts node paths instead of identifiers.

    // Defining the texture node created on drag&drop
    spaceData.Set(maxon::nodes::NODESPACE::IMAGENODEASSETID, maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::GetId()) iferr_return;
    imageTuple.first = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_RESULT)) iferr_return;
    imageTuple.second = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_URL)) iferr_return;
    imageTuple.third = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_STARFRAME)) iferr_return;
    imageTuple.fourth = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_ENDFRAME)) iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::IMAGENODEPORTS, imageTuple) iferr_return;
    static auto Create(ARGS &&... args)
    Definition: apibase.h:2773
    HomogenousTupleType< 4, NodePath > ImageNodePortTuple
    Definition: node_spaces.h:153
    #define iferr_return
    Definition: resultbase.h:1519

    Final Code

    If all the steps have been followed, the code should look like the following:

    #include "maxon/module.h"
    #include "maxon/nodeslib.h"
    static maxon::nodes::NodeSystemClass g_nodeSystemClass;
    // This description processor has to be used for all nodes of the example namespace unless they register themselves at the BuiltinNodes registry (such as DynamicNode).
    MAXON_DECLARATION_REGISTER(maxon::DescriptionProcessors, "net.maxonexample.handbook.nodespace.noderendererprocessor")
    {
    return maxon::nodes::NodesLib::CreateNodeDescriptionProcessor([] (const maxon::Id& descriptionId, const maxon::DataDescription& dataDescription) -> maxon::Result<maxon::nodes::NodeTemplate>
    {
    {
    return maxon::nodes::NodesLib::BuildNodeFromDescription(descriptionId, g_nodeSystemClass);
    }, g_nodeSystemClass);
    });
    }
    static maxon::Result<void> CreateMaterialGraphDefaultSetup(const maxon::nodes::NodesGraphModelRef& graph)
    {
    // Instantiate a node that only has a description, but no process function.
    maxon::nodes::NodeTemplate userNodeSystem = maxon::nodes::NodesLib::LoadTemplate(maxon::AssetInterface::GetBuiltinRepository(), maxon::Id("net.maxonexample.handbook.noderenderer.usernode")) iferr_return;
    maxon::GraphNode userGraphNode = graph.AddChild(maxon::Id("user node instance"), userNodeSystem) iferr_return;
    maxon::nodes::NodeTemplate shaderNodeSystem = maxon::nodes::NodesLib::LoadTemplate(maxon::AssetInterface::GetBuiltinRepository(), maxon::Id("net.maxonexample.handbook.noderenderer.usernode_shade")) iferr_return;
    maxon::GraphNode shaderNode = graph.AddChild(maxon::Id("shader node instance"), shaderNodeSystem) iferr_return;
    // Connect the two instances.
    maxon::GraphNode outPort = userGraphNode.GetOutputs().FindChild(maxon::Id("net.maxonexample.handbook.node.usernode.out")) iferr_return;
    maxon::GraphNode inPort = shaderNode.GetInputs().FindChild(maxon::Id("usernode_shade.colorA")) iferr_return;
    outPort.Connect(inPort) iferr_return;
    // Replace the default value for the first instance.
    maxon::GraphNode valuePort = shaderNode.GetInputs().FindChild(maxon::Id("usernode_shade.colorB")) iferr_return;
    valuePort.SetDefaultValue(maxon::Data(maxon::ColorA(1.0, 1.0, 0.0, 1.0))) iferr_return;
    return maxon::OK;
    }
    static maxon::Result<void> ConfigurePreviewImageRequest(maxon::DataDictionaryObjectRef request)
    {
    request.Set(maxon::nodes::PREVIEWIMAGEREQUEST::PROVIDER, PreviewImageProviderExampleHandbook::GetClass()) iferr_return;
    return maxon::OK;
    }
    MAXON_METHOD maxon::Result<void> NodeRendererNodeSpaceImpl::Init(maxon::DataDictionary spaceData)
    {
    _class = NodeRendererNodeSystemClassImpl::GetClass().Create() iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::NODESYSTEMCLASS, _class) iferr_return;
    // Defines the End Node for this node systems
    maxon::BaseArray<maxon::Id> materialEndNodeIds;
    // Define the node we want to be defined as the end node.
    materialEndNodeIds.Append(maxon::Id("net.maxonexample.handbook.noderenderer.usernode_shade")) iferr_return;
    // Define the information in the DataDictionnary.
    spaceData.Set(maxon::nodes::NODESPACE::MATERIALENDNODEIDS, std::move(materialEndNodeIds)) iferr_return;
    // Defines the supported render
    // Create a BaseArray that store the supporter's engine ID.
    maxon::BaseArray<maxon::Int> supportedRenderers;
    // Here, g_nodeRendererId is the ID of a render engine.
    supportedRenderers.Append(g_nodeRendererId) iferr_return;
    supportedRenderers.Append(1023342) iferr_return;
    supportedRenderers.Append(0) iferr_return;
    // Add the array in the DataDictionnary.
    spaceData.Set(maxon::nodes::NODESPACE::RENDERERS, std::move(supportedRenderers)) iferr_return;
    // Defines the callback function that will create the default setup for the graph.
    spaceData.Set(maxon::nodes::NODESPACE::CREATEMATERIALGRAPHFUNC, maxon::nodes::NODESPACE::CreateMaterialGraphFunc(CreateMaterialGraphDefaultSetup)) iferr_return;
    // Defines the callback function that will configure the preview image request
    spaceData.Set(maxon::nodes::NODESPACE::CONFIGUREPREVIEWIMAGEREQUESTFUNC, maxon::nodes::NODESPACE::ConfigurePreviewImageRequestFunc(ConfigurePreviewImageRequest)) iferr_return;
    maxon::BaseArray<maxon::Id> materialPreviewNodes;
    materialPreviewNodes.Append(maxon::Id("net.maxonexample.handbook.noderenderer.usernode_shade")) iferr_return;
    materialPreviewNodes.Append(maxon::Id("net.maxonexample.handbook.noderenderer.usernode")) iferr_return;
    materialPreviewNodes.Append(maxon::Id("net.maxonexample.handbook.node.constantnode")) iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::MATERIALPREVIEWIDS, std::move(materialPreviewNodes)) iferr_return;
    maxon::BaseArray<maxon::Id> materialExchangeBundleIds;
    materialExchangeBundleIds.Append(maxon::NODESPACE::EXCHANGE::BUNDLE::VIEWPORTMATERIAL::GetId()) iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::MATERIALEXCHANGEBUNDLEIDS, std::move(materialExchangeBundleIds)) iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::MATERIALEXCHANGECLASS, NodeRendererViewPortMaterialHandbookImpl::GetClass()) iferr_return;
    // Defining the texture node created on drag&drop
    spaceData.Set(maxon::nodes::NODESPACE::IMAGENODEASSETID, maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::GetId()) iferr_return;
    imageTuple.first = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_RESULT)) iferr_return;
    imageTuple.second = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_URL)) iferr_return;
    imageTuple.third = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_STARFRAME)) iferr_return;
    imageTuple.fourth = maxon::NodePath::Create(maxon::ToSingletonBlock<maxon::InternedId>(maxonexample::HANDBOOK::NODERENDERER::TEXTURENODE::TEXTURENODE_ENDFRAME)) iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::IMAGENODEPORTS, imageTuple) iferr_return;
    super.Init(spaceData) iferr_return;
    return maxon::OK;
    }
    static maxon::Id g_nodeRendererDatabaseID = maxon::Id("net.maxonexample.handbook.nodes.registereddatabase");
    static maxon::GenericData g_exampleNodeSpace;
    static maxon::Result<void> LoadResources()
    {
    {
    err.CritStop();
    return err;
    };
    // Get plugin location
    const maxon::Url& binaryUrl = maxon::g_maxon.GetUrl();
    // Get plugin folder
    maxon::Url pluginDir = binaryUrl.GetDirectory();
    // Get resource folder (this folder must exist)
    const maxon::Url resourceUrl = pluginDir.Append("res"_s).Append("nodes"_s) iferr_return;
    // Register database
    // Register the node space
    // Use the same ID as in the resource files
    const maxon::Id spaceDescriptionId = maxon::Id("net.maxonexample.handbook.nodespace.node_renderer");
    // Load the description
    maxon::DataDictionary nodeRendererData = maxon::nodes::NodeSpaceHelpersInterface::LoadDescription(spaceDescriptionId) iferr_return;
    maxon::nodes::NodeSpaceRef nodeRendererSpace = NodeRendererNodeSpaceImpl::CreateInit(nodeRendererData) iferr_return;
    // Register the node space implementation
    g_exampleNodeSpace = maxon::nodes::MaterialNodeSpaces::Register(NodeRendererNodeSpaceImpl::GetDescriptor().GetId(), nodeRendererSpace) iferr_return;
    return maxon::OK;
    }
    static void FreeResources()
    {
    {
    err.CritStop();
    return;
    };
    // Unregister a database
    g_exampleNodeSpace = maxon::GenericData();
    g_nodeSystemClass = nullptr;
    }
    MAXON_INITIALIZATION(LoadResources, FreeResources);
    static MAXON_METHOD const AssetRepositoryRef & GetBuiltinRepository()
    MAXON_ATTRIBUTE_FORCE_INLINE ResultRef< T > Append(ARG &&x)
    Definition: basearray.h:677
    static MAXON_METHOD Result< void > RegisterDatabaseWithUrl(const Id &databaseId, const Url &url, const CString &version=CString::NullValue())
    static MAXON_METHOD Result< void > UnregisterDatabase(const Id &databaseId)
    Definition: delegate.h:240
    Definition: genericdata.h:20
    Definition: apibaseid.h:253
    Definition: resultbase.h:766
    Definition: url.h:952
    static MAXON_METHOD Result< DataDictionary > LoadDescription(const Id &spaceDescriptionId)
    static MAXON_METHOD Result< NodeTemplate > CreateLazyTemplate(const Id &nodeId, Delegate< Result< NodeTemplate >()> &&creator, Delegate< Result< Bool >(const NodeSystemClass &cls)> &&support={})
    static MAXON_METHOD Result< DescriptionProcessor > CreateNodeDescriptionProcessor(Delegate< Result< NodeTemplate >(const Id &descriptionId, const DataDescription &dataDescription)> &&delegate)
    static MAXON_METHOD Result< NodeTemplate > LoadTemplate(const AssetRepositoryRef &repo, const Id &assetId)
    static MAXON_METHOD Result< NodeSystemBasedNodeTemplate > BuildNodeFromDescription(const Id &id, const NodeSystemClass &nodeClass, Bool addDependencies=true, const Delegate< Result< void >(const MutableRoot &root, const TemplateArguments &args)> &finalizer={})
    OK
    Ok.
    Definition: ge_prepass.h:0
    return OK
    Definition: apibase.h:2690
    Col4< Float, 1 > ColorA
    Definition: vector4d.h:60
    #define MAXON_DECLARATION_REGISTER(...)
    Definition: module.h:933
    #define MAXON_INITIALIZATION(...)
    Definition: module.h:795
    #define MAXON_METHOD
    Definition: interfacebase.h:1001
    The maxon namespace contains all declarations of the MAXON API.
    Definition: autoweight.h:14
    Definition: animation_attributes.h:10
    #define iferr_scope_handler
    Definition: resultbase.h:1402
    #define iferr_scope
    Definition: resultbase.h:1384
    Definition: node.h:10