Example Scripts

Simulation graph in Python

The following Python script runs a simple smoke simulation and caches the result to disk. It demonstrates how to create a node graph and evaluate it from within Python.

import eddy
from eddy import *


if __name__ == '__main__':
    eddy.startup()

    # Create all the nodes and set properties.

    sphere = create_node(type="SphereField")
    sphere.set_property_value(name="radius", value=0.5)

    iso_mask = create_node(type="IsoMaskField")
    iso_mask.set_property_value(name="scalar_inside_value", value=5.0)

    emitter = create_node(type="Emitter")
    emitter.set_property_value(name="mode", value="Add")

    element = create_node(type="SparseSmokeElement")
    element.set_property_value(name="dx", value=0.02)

    cache_writer = create_node(type="CacheWriter")
    cache_writer.set_property_value(name="overwrite", value=True)

    channel_list = ChannelList()
    channel_list.add_scalar_channel(channel_name="density")
    cache_writer.set_property_value(name="channel_list", value=channel_list)

    # Connect the nodes into a graph.

    connect(source_node=sphere, target_node=iso_mask)
    connect(source_node=iso_mask, target_node=emitter)
    connect(source_node=emitter, target_node=element, target_plug="density")
    connect(source_node=element, target_node=cache_writer)

    # Evaluate the graph for a range of frames.

    evaluator = Evaluator()

    prev_ticks = 0.0
    for frame in range(1, 101):
        print "Evaluating frame %d" % (frame)

        current_ticks = seconds_to_ticks(frame/24.0)
        context = EvaluationContext(start_ticks=prev_ticks, end_ticks=current_ticks, fps=24.0)

        cache_writer.set_property_value(name="file", value="cache."+format(frame, '04d')+".vdb")

        evaluator.evaluate(node=cache_writer, context=context)

        prev_ticks = current_ticks

Rendering graph in Python

The following Python script builds a node graph to create a pyroclastic sphere field, rasterize it to a DenseVolume, and then render it. It is also possible to do this using more direct API functions instead of building a node graph. See below for an equivalent example that doesn’t use the node graph.

import eddy
import math
from eddy import *


if __name__ == '__main__':
    eddy.startup()

    # Create a pyroclastic sphere field.
    sphere = create_node(type="SphereField")
    sphere.set_property_value(name="radius", value=0.9)

    iso_mask = create_node(type="IsoMaskField")
    iso_mask.set_property_value(name="scalar_inside_value", value=2.0)

    vector_noise = create_node(type="PerlinNoiseField")
    vector_noise.set_property_value(name="mode", value="vector")
    vector_noise.set_property_value(name="seed", value=4)
    vector_noise.set_property_value(name="frequency", value=V3f(3.0))
    vector_noise.set_property_value(name="vector_amplitude", value=V3f(1.4))
    vector_noise.set_property_value(name="octaves", value=5)
    vector_noise.set_property_value(name="lacunarity", value=2.0)
    vector_noise.set_property_value(name="gain", value=0.5)

    offset_lookup = create_node(type="OffsetLookupField")
    offset_lookup.set_property_value(name="mode", value="trace")
    offset_lookup.set_property_value(name="trace_steps", value=4)
    offset_lookup.set_property_value(name="scale", value=0.2)

    connect(source_node=sphere, target_node=iso_mask)
    connect(source_node=iso_mask, target_node=offset_lookup, target_plug="field")
    connect(source_node=vector_noise, target_node=offset_lookup, target_plug="offset_field")

    # Rasterize the field. Not strictly necessary, as we could render the implicit field directly, but it is usually faster
    # to render a rasterized version.
    rasterize = create_node(type="RasterizeField", name="my_rasterizer")
    rasterize.set_property_value(name="voxel_size", value=0.01)

    connect(source_node=offset_lookup, target_node=rasterize)

    # Create RenderVolume.
    render_volume = create_node(type="SmokeRenderVolume")
    render_volume.set_property_value(name="absorption_colour", value=C3f(0.36, 0.36, 0.4))
    render_volume.set_property_value(name="scattering_colour", value=C3f(0.45, 0.45, 0.5))

    connect(source_node=rasterize, target_node=render_volume, target_plug="density")

    # Create PointLight.
    light_transform = M44f()
    light_transform.set_translation(V3f(5.0, 5.0, 5.0))

    point_light = create_node(type="PointLight")
    point_light.set_property_value(name="transform", value=light_transform)
    point_light.set_property_value(name="intensity", value=V3f(20.0))

    # Create RenderScene.
    render_scene = create_node(type="RenderScene")

    connect(source_node=render_volume, target_node=render_scene)
    connect(source_node=point_light, target_node=render_scene)

    # Create camera.
    camera_transform = M44f()
    camera_transform.set_translation(V3f(0.0, 0.0, 4.0))
    horizontal_fov = math.pi / 3.0
    vertical_fov = 2.0 * math.atan(math.tan(0.5 * math.pi / 3.0) * 9.0 / 16.0)

    camera = create_node(type="Camera")
    camera.set_property_value(name="transform", value=camera_transform)
    camera.set_property_value(name="vertical_FOV", value=vertical_fov)
    camera.set_property_value(name="horizontal_FOV", value=horizontal_fov)

    # Create RenderData.
    render_data = create_node(type="RenderData")
    render_data.set_property_value(name="max_progressions", value=2000)

    connect(source_node=camera, target_node=render_data)
    connect(source_node=render_scene, target_node=render_data)

    # Create renderer node.
    render = create_node(type="Render")
    render.set_property_value(name="width", value=1280)
    render.set_property_value(name="height", value=720)

    connect(source_node=render_data, target_node=render)

    # Create ImageWriter to save the result.
    image_writer = create_node(type="ImageWriter")
    image_writer.set_property_value(name="filename", value="render_example.exr")

    connect(source_node=render, source_plug="image", target_node=image_writer)

    # Evaluate graph.
    evaluator = Evaluator()
    evaluator.evaluate(node=image_writer, context=EvaluationContext())

Rendering in Python

The following Python script creates a pyroclastic sphere field, rasterizes it to a DenseVolume, then renders it. It uses the direct functions of the API instead of creating a node graph. See above for a rendering example that uses the node graph.

import eddy
import math
from eddy import *


if __name__ == '__main__':
    eddy.startup()

    # Create a pyroclastic sphere field.
    sphere = SphereField(radius=0.9)
    iso_mask = IsoMaskField(field=sphere, inside_value=2.0)
    vector_noise = PerlinNoiseField(seed=4, frequency=V3f(3.0), amplitude=V3f(1.4), octaves=5, lacunarity=2.0, gain=0.5)
    offset_lookup = OffsetLookupField(field=iso_mask, offset_field=vector_noise, mode="trace", trace_steps=4, scale=0.2)

    # Rasterize the field. Not strictly necessary, as we could render the implicit field directly, but it is usually faster
    # to render a rasterized version.
    dense_volume = DenseVolume(dimensions=V3f(2.56), resolution=256)
    dense_volume.add_float_channel("density")

    rasterize_field(volume=dense_volume, channel_name="density", field=offset_lookup)

    density_field = DenseVolumeScalarField(volume=dense_volume, channel_name="density")

    # Load shader.
    shader_python_file = eddy.find_shader("smoke.py")
    with open(shader_python_file) as f:
        shader_python = f.read()

    # Create RenderVolume.
    render_volume = RenderVolume()
    render_volume.set_shader(language="Python", shader=shader_python)
    render_volume.set_parameter(name="density", value=density_field)
    render_volume.set_parameter(name="absorption_colour", value=V3f(0.36, 0.36, 0.4))
    render_volume.set_parameter(name="scattering_colour", value=V3f(0.45, 0.45, 0.5))

    # Create RenderScene with a PointLight.
    scene = RenderScene()
    scene.render_volume = render_volume
    scene.add_point_light(PointLight(position=V3f(5.0, 5.0, 5.0), intensity=V3f(20.0)))

    # Create RenderSettings.
    render_settings = RenderSettings()
    render_settings.min_progressions = 400;
    render_settings.max_progressions = 2000;
    render_settings.output_settings.append(RenderOutputSettings(error_bound=0.01))

    # Create camera.
    camera = CameraMotion()
    camera.position = V3f(0.0, 0.0, 4.0)
    camera.set_aspect_ratio_and_horizontal_fov(16.0/9.0, math.pi/3.0)

    # Create RenderData.
    render_data = RenderData(camera=camera, scene=scene, settings=render_settings)

    # Create RenderTarget.
    render_target = RenderTarget(1280, 720)

    # Create RenderState and execute the render.
    render_state = RenderState(render_data=render_data, render_target=render_target)
    render(render_state)

    # Save the result.
    render_target.write_exr_file(filename="render_example.exr")

Simulation and rendering in C

The following C++ program creates a node graph to both run a simulation and render the result, and save both the cache and the rendered images to disk.

#include <iostream>
#include <math.h>

#include "Eddy/eddy.h"


using eddy_api::ResourceHandle;


#define CHECK(result) do { checkEddyResult((result), __FILE__, __LINE__); } while (0,0)

void checkEddyResult(eddy_api::Result result, const char* file, int line)
{
    if (result != eddy_api::EDDY_RESULT_SUCCESS)
    {
        std::cerr << "Eddy function returned error: " << result << std::endl;
        std::cerr << file << ":" << line << std::endl;
        char errorStr[1024];
        eddy_api::GetLastErrorString(errorStr, sizeof(errorStr));
        std::cerr << errorStr << std::endl;

        exit(1);
    }
}


eddy_api::M44f createTranslationMatrix(float x, float y, float z)
{
    eddy_api::M44f result;
    result.v[0][0] = 1.0f; result.v[0][1] = 0.0f; result.v[0][2] = 0.0f; result.v[0][3] = 0.0f;
    result.v[1][0] = 0.0f; result.v[1][1] = 1.0f; result.v[1][2] = 0.0f; result.v[1][3] = 0.0f;
    result.v[2][0] = 0.0f; result.v[2][1] = 0.0f; result.v[2][2] = 1.0f; result.v[2][3] = 0.0f;
    result.v[3][0] = x;    result.v[3][1] = y;    result.v[3][2] = z;    result.v[3][3] = 1.0f;
    return result;
}

#define M_PI 3.14159265358979323846


int main(int argc, char *argv[])
{
    CHECK(eddy_api::Startup());

    // Create the simulation nodes.
    ResourceHandle sphereField;
    CHECK(eddy_api::CreateNode(&sphereField, "SphereField", "sphere"));
    CHECK(eddy_api::NodeSetPropertyFloat(sphereField, "radius", 0.5f));

    ResourceHandle isoMaskField;
    CHECK(eddy_api::CreateNode(&isoMaskField, "IsoMaskField", "isomask"));
    CHECK(eddy_api::NodeSetPropertyFloat(isoMaskField, "scalar_inside_value", 5.0f));

    ResourceHandle emitter;
    CHECK(eddy_api::CreateNode(&emitter, "Emitter", "emitter"));
    CHECK(eddy_api::NodeSetPropertyString(emitter, "mode", "Add"));

    ResourceHandle element;
    CHECK(eddy_api::CreateNode(&element, "SparseSmokeElement", "element"));
    CHECK(eddy_api::NodeSetPropertyFloat(element, "dx", 0.02f));

    ResourceHandle cacheWriter;
    CHECK(eddy_api::CreateNode(&cacheWriter, "CacheWriter", "cacheWriter"));
    CHECK(eddy_api::NodeSetPropertyBool(cacheWriter, "overwrite", true));

    ResourceHandle channelList;
    CHECK(eddy_api::CreateChannelList(&channelList));
    CHECK(eddy_api::ChannelListAddScalarChannel(channelList, "density", true, 0.0f, false, nullptr));

    CHECK(eddy_api::NodeSetProperty(cacheWriter, "channel_list", channelList));

    // Connect the simulation nodes.
    CHECK(eddy_api::NodeConnect(sphereField, nullptr, isoMaskField, nullptr));
    CHECK(eddy_api::NodeConnect(isoMaskField, nullptr, emitter, nullptr));
    CHECK(eddy_api::NodeConnect(emitter, nullptr, element, "density"));
    CHECK(eddy_api::NodeConnect(element, nullptr, cacheWriter, nullptr));

    // Create the rendering nodes.
    ResourceHandle densityChannel;
    CHECK(eddy_api::CreateNode(&densityChannel, "ChannelSetField", "densityChannel"));
    CHECK(eddy_api::NodeSetPropertyString(densityChannel, "channel", "density"));

    ResourceHandle renderVolume;
    CHECK(eddy_api::CreateNode(&renderVolume, "SmokeRenderVolume", "renderVolume"));
    CHECK(eddy_api::NodeSetPropertyColor3f(renderVolume, "absorption_colour", eddy_api::MakeV3f(0.36f, 0.36f, 0.4f)));
    CHECK(eddy_api::NodeSetPropertyColor3f(renderVolume, "scattering_colour", eddy_api::MakeV3f(0.45f, 0.45f, 0.5f)));

    ResourceHandle pointLight;
    CHECK(eddy_api::CreateNode(&pointLight, "PointLight", "pointLight"));
    CHECK(eddy_api::NodeSetPropertyM44f(pointLight, "transform", createTranslationMatrix(5.0f, 5.0f, 5.0f)));
    CHECK(eddy_api::NodeSetPropertyV3f(pointLight, "intensity", eddy_api::MakeV3f(20.0f, 20.0f, 20.0f)));

    ResourceHandle renderScene;
    CHECK(eddy_api::CreateNode(&renderScene, "RenderScene", "renderScene"));

    ResourceHandle camera;
    CHECK(eddy_api::CreateNode(&camera, "Camera", "camera"));
    CHECK(eddy_api::NodeSetPropertyM44f(camera, "transform", createTranslationMatrix(0.0f, 1.5f, 6.0f)));
    CHECK(eddy_api::NodeSetPropertyFloat(camera, "vertical_FOV", 2.0f * atanf(tanf(0.5f * M_PI / 3.0f) * 9.0f / 16.0f)));
    CHECK(eddy_api::NodeSetPropertyFloat(camera, "horizontal_FOV", M_PI / 3.0f));

    ResourceHandle renderData;
    CHECK(eddy_api::CreateNode(&renderData, "RenderData", "renderData"));
    CHECK(eddy_api::NodeSetPropertyInt(renderData, "max_progressions", 200));

    ResourceHandle renderer;
    CHECK(eddy_api::CreateNode(&renderer, "Render", "renderer"));
    CHECK(eddy_api::NodeSetPropertyInt(renderer, "width", 1280));
    CHECK(eddy_api::NodeSetPropertyInt(renderer, "height", 720));

    ResourceHandle imageWriter;
    CHECK(eddy_api::CreateNode(&imageWriter, "ImageWriter", "imageWriter"));

    // Connect the rendering nodes.
    CHECK(eddy_api::NodeConnect(cacheWriter, nullptr, densityChannel, nullptr));
    CHECK(eddy_api::NodeConnect(densityChannel, nullptr, renderVolume, "density"));
    CHECK(eddy_api::NodeConnect(renderVolume, nullptr, renderScene, nullptr));
    CHECK(eddy_api::NodeConnect(pointLight, nullptr, renderScene, nullptr));
    CHECK(eddy_api::NodeConnect(camera, nullptr, renderData, nullptr));
    CHECK(eddy_api::NodeConnect(renderScene, nullptr, renderData, nullptr));
    CHECK(eddy_api::NodeConnect(renderData, nullptr, renderer, nullptr));
    CHECK(eddy_api::NodeConnect(renderer, "image", imageWriter, nullptr));

    // Create the evaluator.
    ResourceHandle evaluator;
    eddy_api::CreateEvaluator(&evaluator);

    // Frame loop.
    double prevTicks = 0.0;
    for (int frame = 1; frame <= 100; ++frame)
    {
        std::cout << "Evaluating frame " << frame << std::endl;

        // Set the output filenames.
        char filename[256];
        sprintf(filename, "cache.%04d.vdb", frame);
        CHECK(eddy_api::NodeSetPropertyString(cacheWriter, "file", filename));
        sprintf(filename, "render.%04d.exr", frame);
        CHECK(eddy_api::NodeSetPropertyString(imageWriter, "filename", filename));

        // Evaluate graph for this frame.
        double currentTicks = frame / 24.0f * eddy_api::EDDY_TICKS_PER_SECOND;
        CHECK(eddy_api::Evaluate(evaluator, imageWriter, nullptr, prevTicks, currentTicks, 24.0f, nullptr));
        prevTicks = currentTicks;
    }

    // Release all resource handles.
    CHECK(eddy_api::ReleaseResource(sphereField));
    CHECK(eddy_api::ReleaseResource(isoMaskField));
    CHECK(eddy_api::ReleaseResource(emitter));
    CHECK(eddy_api::ReleaseResource(element));
    CHECK(eddy_api::ReleaseResource(cacheWriter));
    CHECK(eddy_api::ReleaseResource(channelList));
    CHECK(eddy_api::ReleaseResource(densityChannel));
    CHECK(eddy_api::ReleaseResource(renderVolume));
    CHECK(eddy_api::ReleaseResource(pointLight));
    CHECK(eddy_api::ReleaseResource(renderScene));
    CHECK(eddy_api::ReleaseResource(camera));
    CHECK(eddy_api::ReleaseResource(renderData));
    CHECK(eddy_api::ReleaseResource(renderer));
    CHECK(eddy_api::ReleaseResource(imageWriter));
    CHECK(eddy_api::ReleaseResource(evaluator));

    CHECK(eddy_api::Shutdown());

    return 0;
}