Share

Create a complex operator - Arnold Developer Guide

Dynamic Creation of Spheres Using Child Operators

Let's build on the sphere Operator in Create Simple Operator and show a powerful way Operators can be used. An Operator can create another Operator , including itself. When doing so, the Operator can send private information to its children. A practical example would be an Operator authoring a cache such as Alembic, where the Operator can pass the Alembic iterator to child Operators in order to create sub-trees of the hierarchy. The benefit of doing this is twofold. Firstly, the work is split across more threads as each node, including Operators , is processed by a single thread so this effectively chunks the work up across Operators , and consequently threads. Secondly, any hierarchical information is expressed in the Operator graph which helps with maintaining and manipulating the data.

To keep things simple, let's create an Operator that:

  • Creates a single sphere and a single instance of itself
  • Each child instance keeps doing this until a certain criterion is met, namely:
    • Each op creates a random radius and passes it to its child
    • Stop if the radius received is below than some threshold or we have exceeded a given maximum number of instances, otherwise, keep going

Check the implementation in the source code for private_ryan below. The Operator doesn't set a transform on the spheres so they are subject to the same rules as described above.

The RyanData structure contains the information that's passed to the child Operators using AiOpSetChildData. As well as the data we must provide a cleanup function using AtOpCleanupChildData.

private_ryan.cpp

#include <ai.h>
#include <sstream>
#include <random>

AI_OPERATOR_NODE_EXPORT_METHODS(OpMethods);

struct RyanData
{
    RyanData(float radius = 0.5f, int max_instances = 50) : radius(radius), max_instances(max_instances), num_instances(0)
    {}

    float radius;
    int max_instances;
    int num_instances;
};

namespace
{
    std::uniform_real_distribution<float> dist(0.0, 1.0);
    std::mt19937 rand_generator;

    float random_value()
    {
        return dist(rand_generator);
    }
}

node_parameters
{
    AiParameterFlt("radius_scale", 1.0f);
    AiParameterFlt("stop_if_radius_below", 0.1f);
    AiParameterInt("initial_seed", 1000);
    AiParameterInt("max_instances", 50);
}

operator_init
{
    return true;
}

operator_cleanup
{
    return true;
}

bool cleanup_child_data(void* child_data)
{
    delete static_cast<RyanData*>(child_data);
    return true;
}

operator_cook
{
    RyanData* data = static_cast<RyanData*>(child_data);

    float radius_scale = AiNodeGetFlt(op, "radius_scale");
    float stop_if_below = AiNodeGetFlt(op, "stop_if_radius_below");
    int max_instances = AiNodeGetInt(op, "max_instances");

    std::stringstream child_name;
    child_name << AiNodeGetName(node) << "/";
    child_name << "ryan";
    std::string name = child_name.str();

    bool create_child = true;
    int num_instances = 1;

    if (data)
    {
        std::stringstream sphere_name;
        sphere_name << AiNodeGetName(node) << "/";
        sphere_name << "sphere";
        AtNode* node = AiNode("sphere", sphere_name.str().c_str());
        AiNodeSetFlt(node, AtString("radius"), data->radius);

        num_instances = data->num_instances + 1;
        if (data->radius < stop_if_below || data->max_instances == data->num_instances)
            create_child = false;
    }
    else
    {
        int initialSeed = AiNodeGetInt(op, AtString("initial_seed"));
        rand_generator.seed(initialSeed);
    }

    if (create_child)
    {
        float radius = random_value() * radius_scale;

        RyanData* child_data = new RyanData(radius, max_instances);
        child_data->num_instances = num_instances;
        AtNode* child_op = AiNode("private_ryan", name.c_str());
        AtOpCleanupChildData cleanup = &cleanup_child_data;
        AiOpSetChildData(child_op, child_data, cleanup);
    }

    return true;
}

operator_post_cook
{
    return true;
}

node_loader
{
    if (i > 0) return false;

    node->methods = OpMethods;
    node->output_type = AI_TYPE_NONE;
    node->name = AtString("private_ryan");
    node->node_type = AI_NODE_OPERATOR;
    strcpy(node->version, AI_VERSION);

    return true;
} 

private_ryan.ass

options
{
 AA_samples 9
 outputs "RGBA RGBA /out/arnold1:gaussian_filter /out/arnold1:jpeg"
 camera "camera"
 GI_diffuse_depth 1
 GI_specular_depth 1
 GI_diffuse_samples 3
 operator "make_spheres"
}

driver_jpeg
{
 name /out/arnold1:jpeg
 filename "simple.jpg"
}

gaussian_filter
{
 name /out/arnold1:gaussian_filter
}

persp_camera
{
 name camera
 matrix
  1 0 0 0
  0 1 0 0
  0 0 1 0
  0 0 30 1
}


private_ryan
{
    name make_spheres
    stop_if_radius_below 0.0001
    initial_seed 9999
    max_instances 50
}

complex operator render

Was this information helpful?