neuray API Programmer's Manual

Example for Animated meshes

[Previous] [Next] [Up]

Again, this example imports a partial scene containing definitions for the camera, a light, and some geometry (a ground plane and a yellow cube). It then creates via the API a rotating tetrahedron as an example for an animated mesh,

Note that such a rotating mesh usually gets realized with a static mesh and a modification of the transformation matrix of the mesh instance. For demonstration purposes in this example we use a fixed transformation matrix and an animated mesh instead.

The examples also shows how to render different frames of the same scene (or better, slight modifications of the same scene).

New Topics

  • Creation of animated meshes

  • Rendering multiple frames

Detailed Description

Creation of animated meshes

Animated meshes are a special case of user-defined classes. Most notably there is a get_mesh() method and two more methods related to the bounding box of the animated mesh. For this simple example we ignore the methods related to user-defined classes. See the next example for details.

The implementation of INVALID_DOXYREF is the most important method in this example. It creates a regular tetrahedron, similar as in the previous example, but now the coordinates depend on the parameter time.

To assign different materials to each face of the tetrahedron we also create an attribute vector for material indices. Each face has its own material index. In the method setup_scene() we create a material attribute on the mesh instance as an array of four elements, and set each reference to a different shader.

Note that it is not necessary to create an animated mesh from scratch. It is also feasible to cache the meshes and to simply return a previously created mesh. However, you must not modify a mesh after you returned it in get_mesh().

Rendering multiple frames

In this example we render multiple frames which are slight variations of the scene. Each frame has a different value for the animation_time attribute of the global options. Displaying all frames in sequence gives then an animation of a rotating tetrahedron. On Linux you can view the actual animation using the animate command from the ImageMagick software as follows
‎ animate -delay 5 `\ls -1 *.png | sort -n` 

Note that it is necessary to commit the transaction after calling mi::neuraylib::IRender_context::render() or mi::neuraylib::IRender_context::render_async(). Hence, each frame has to be rendered in a separate transaction. Thus, for each frame we create a transaction, modify the animation_time attribute of the global options, access the scene, render it, and export the canvas to disk.

Example Source

Source Code Location: examples/example_animated_mesh.cpp

‎/******************************************************************************
 * Copyright 1986, 2011 NVIDIA Corporation. All rights reserved.
 *****************************************************************************/

// examples/example_animated_mesh.cpp
//
// Renders a sequence of frames with an animated mesh.

#include <iostream>
#include <sstream>

#include <mi/neuraylib.h>

// Include code shared by all examples.
#include "example_shared.h"
// Include an implementation of ITile, ICanvas, and IRender_target.
#include "example_render_target.h"

// Our implementation of the IAnimiated_mesh interface.
class My_animated_mesh : public
    mi::neuraylib::User_class<0xb75afb8a,0xc214,0x496e,0xb9,0x0f,0xd7,0xfa,0x43,0x6b,0x8d,0x14,
                              mi::neuraylib::IAnimated_mesh>
{
    // The methods of IAnimated_mesh
    const mi::base::IInterface* get_mesh(
        mi::neuraylib::ITransaction* transaction, mi::Float64 time) const;

    mi::Float32_3_struct get_bbox_min() const
    {
        return mi::Float32_3( -1, -1, -1);
    }
    mi::Float32_3_struct get_bbox_max() const
    {
        return mi::Float32_3(  1,  1,  1);
    }

    // The methods of IUser_class
    mi::neuraylib::IUser_class* copy() const { return new My_animated_mesh; }
    const char* get_class_name() const { return "My_animated_mesh"; }
    mi::IArray* get_references( mi::neuraylib::ITransaction* transaction) const { return 0; }

    // The methods of ISerializable
    void serialize( INVALID_DOXYREFmi::ISerializer* serializer) const {}
    void deserialize( INVALID_DOXYREFmi::IDeserializer* derserializer) {}
};

// Creates a regular tetrahedron centered at the origin. The tetrahedron rotates around the
// axis though (0,0,0) and (1,1,1) according to the "time" parameter.
const mi::base::IInterface* My_animated_mesh::get_mesh(
    mi::neuraylib::ITransaction* transaction, mi::Float64 time) const
{
    mi::Float64 C = cos( time);
    mi::Float64 S = sin( time);

    mi::Float64_3 tetra_points[4] = {
        mi::Float64_3( -0.5                  , -0.5,                   -0.5                  ),
        mi::Float64_3(  0.167+0.333*C+0.577*S,  0.167+0.333*C-0.577*S,  0.167-0.667*C        ),
        mi::Float64_3(  0.167+0.333*C-0.577*S,  0.167-0.667*C        ,  0.167+0.333*C+0.577*S),
        mi::Float64_3(  0.167-0.667*C        ,  0.167+0.333*C+0.577*S,  0.167+0.333*C-0.577*S) };

    mi::Float32_3 tetra_normals[4] = {
        mi::Float32_3( 1.155 * tetra_points[0]),
        mi::Float32_3( 1.155 * tetra_points[1]),
        mi::Float32_3( 1.155 * tetra_points[2]),
        mi::Float32_3( 1.155 * tetra_points[3]) };

    INVALID_DOXYREFmi::Triangle_point_indices tetra_triangles[4] = {
        INVALID_DOXYREFmi::Triangle_point_indices( 0, 2, 1),
        INVALID_DOXYREFmi::Triangle_point_indices( 0, 1, 3),
        INVALID_DOXYREFmi::Triangle_point_indices( 0, 3, 2),
        INVALID_DOXYREFmi::Triangle_point_indices( 1, 2, 3) };

    // Create an empty triangle mesh
    INVALID_DOXYREFmi::ITriangle_mesh* mesh = transaction->create<INVALID_DOXYREFmi::ITriangle_mesh>( "Triangle_mesh");
    check_success( mesh);

    // Create a tetrahedron
    mesh->INVALID_DOXYREF( 4);
    for( mi::Uint32 i = 0; i < 4; ++i)
        mesh->INVALID_DOXYREF( tetra_points[i]);
    mesh->INVALID_DOXYREF( 4);
    for( mi::Uint32 i = 0; i < 4; ++i)
        mesh->INVALID_DOXYREF( tetra_triangles[i]);

    // Create corresponding normal vectors
    check_success( !mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::ATTR_NORMAL));
    mi::base::Handle< mi::ITriangle_connectivity> mesh_connectivity(
        mesh->INVALID_DOXYREF());
    mi::base::Handle< mi::IAttribute_vector> normals(
        mesh_connectivity->create_attribute_vector( INVALID_DOXYREFmi::ATTR_NORMAL));
    for( mi::Uint32 i = 0; i < 4; ++i)
        normals->append_vector3( tetra_normals[i]);
    check_success( normals->is_valid_attribute());
    mesh_connectivity->attach_attribute_vector( normals.get());
    check_success( !normals->is_valid_attribute());
    mesh->INVALID_DOXYREF( mesh_connectivity.get());
    check_success( mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::ATTR_NORMAL));

    // Create material indices
    check_success( !mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::ATTR_MATERIAL_INDEX));
    mi::base::Handle< mi::IAttribute_vector> material_indices(
        mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::ATTR_MATERIAL_INDEX));
    for( mi::Uint32 i = 0; i < 4; ++i)
        material_indices->append_uint32( i);
    check_success( material_indices->is_valid_attribute());
    mesh->INVALID_DOXYREF( material_indices.get());
    check_success( !material_indices->is_valid_attribute());
    check_success( mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::ATTR_MATERIAL_INDEX));

    return mesh;
}

// Add an animated tetrahedron with different materials referenced by each primitive
void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup)
{
    // Create the animated mesh
    mi::base::Handle< mi::neuraylib::IAnimated_mesh> animated_mesh(
        transaction->create<INVALID_DOXYREFmi::neuraylib::IAnimated_mesh>( "My_animated_mesh"));
    check_success( transaction->store( animated_mesh.get(), "animated_mesh") == 0);

    // Create the instance for the animated mesh
    mi::base::Handle< mi::IInstance> instance( transaction->create<INVALID_DOXYREFmi::IInstance>( "Instance"));
    instance->attach( "animated_mesh");

    // Set the transformation matrix and the visible attribute.
    mi::Float64_4_4 matrix( 1.0);
    matrix.translate( -0.1, 0.0, -0.2);
    matrix.rotate( 0.0, -1.5*MI_PI_2, 0.0);
    instance->set_world_to_obj( matrix);

    mi::base::Handle< mi::IBoolean> visible(
        instance->create_attribute<mi::IBoolean>( "visible", "Boolean"));
    visible->set_value( true);

    // Set the material attribute. In this example we assign four differently colored materials
    // to the four faces of the mesh.
    mi::base::Handle< mi::IRef> gold( transaction->create<mi::IRef>( "Ref"));
    gold->set_reference( "goldSG");
    mi::base::Handle< mi::IRef> red( transaction->create<mi::IRef>( "Ref"));
    red->set_reference( "redSG");
    mi::base::Handle< mi::IRef> green( transaction->create<mi::IRef>( "Ref"));
    green->set_reference( "greenSG");
    mi::base::Handle< mi::IRef> blue( transaction->create<mi::IRef>( "Ref"));
    blue->set_reference( "blueSG");
    mi::base::Handle< mi::IArray> array(
        instance->create_attribute<mi::IArray>( "material", "Ref[4]"));
    array->set_element( 0, gold.get());
    array->set_element( 1, red.get());
    array->set_element( 2, green.get());
    array->set_element( 3, blue.get());

    transaction->store( instance.get(), "instance_red");

    // And attach the instance to the root group
    mi::base::Handle< mi::IGroup> group( transaction->edit<INVALID_DOXYREFmi::IGroup>( rootgroup));
    group->attach( "instance_red");
}

void configuration( mi::base::Handle< mi::neuraylib::INeuray> neuray, const char* shader_path)
{
    // Configure the neuray library. Here we set some paths needed by the renderer.
    mi::base::Handle< mi::neuraylib::IRendering_configuration> rendering_configuration(
        neuray->get_api_component<mi::neuraylib::IRendering_configuration>());
    check_success( rendering_configuration.is_valid_interface());
    check_success( rendering_configuration->add_shader_path( shader_path) == 0);

    // Load the FreeImage image plugin and the LLVM backend for MetaSL.
    mi::base::Handle< mi::neuraylib::IPlugin_configuration> plugin_configuration(
        neuray->get_api_component<mi::neuraylib::IPlugin_configuration>());
#ifndef MI_PLATFORM_WINDOWS
    check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0);
    check_success( plugin_configuration->load_plugin_library( "gen_llvm.so") == 0);
#else
    check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0);
    check_success( plugin_configuration->load_plugin_library( "gen_llvm.dll") == 0);
#endif

    // Register the animated mesh class.
    mi::base::Handle< mi::neuraylib::IExtension_api> extension_api(
        neuray->get_api_component<mi::neuraylib::IExtension_api>());
    check_success( extension_api->register_class<My_animated_mesh>(
        "My_animated_mesh") == 0);
}

void rendering( mi::base::Handle< mi::neuraylib::INeuray> neuray)
{
    // Get the database, the global scope, which is the root for all transactions,
    // and create a transaction for importing the scene file and storing the scene.
    mi::base::Handle< mi::neuraylib::IDatabase> database(
        neuray->get_api_component<mi::neuraylib::IDatabase>());
    check_success( database.is_valid_interface());
    mi::base::Handle< mi::neuraylib::IScope> scope(
        database->get_global_scope());
    mi::base::Handle< mi::neuraylib::ITransaction> transaction(
        scope->create_transaction());
    check_success( transaction.is_valid_interface());

    // Open new block to ensure that all handles obtained from the transaction went out of scope
    // when the transaction gets committed.
    {
        // Import the scene file
        mi::base::Handle< mi::neuraylib::IImport_api> import_api(
            neuray->get_api_component<mi::neuraylib::IImport_api>());
        check_success( import_api.is_valid_interface());
        mi::base::Handle< const mi::IImport_result> import_result(
            import_api->import_elements( transaction.get(), "main.mi"));
        check_success( import_result->get_error_number() == 0);

        // Create the animation_time attribute on the global options.
        mi::base::Handle< mi::IOptions> options( transaction->edit<INVALID_DOXYREFmi::IOptions>(
            import_result->get_options()));
        mi::base::Handle< mi::IFloat64> animation_time( options->create_attribute<mi::IFloat64>(
            "animation_time", "Float64"));
        animation_time->set_value( 1.75);

        // Add the animated mesh to the scene
        setup_scene( transaction.get(), import_result->get_rootgroup());

        // Create the scene object
        mi::base::Handle< mi::neuraylib::IScene> scene(
            transaction->create<mi::neuraylib::IScene>( "Scene"));
        scene->set_rootgroup(       import_result->get_rootgroup());
        scene->set_options(         import_result->get_options());
        scene->set_camera_instance( import_result->get_camera_inst());
        transaction->store( scene.get(), "the_scene");
    }

    transaction->commit();

    // The number of iterations, i.e., frames of the animation.
    const mi::Uint32 N = 50;

    mi::base::Handle< mi::neuraylib::IExport_api> export_api(
        neuray->get_api_component<mi::neuraylib::IExport_api>());
    check_success( export_api.is_valid_interface());

    Render_target render_target( 512, 384);

    for( mi::Size i = 0; i < N; ++i) {

        // Create a new transaction for this frame. Note that there can be at most one render call
        // per transaction.
        mi::base::Handle< mi::neuraylib::ITransaction> transaction( scope->create_transaction());
        check_success( transaction.is_valid_interface());

        // Open new block to ensure that all handles obtained from the transaction went out of scope
        // when the transaction gets committed.
        {
            // Set the animation_time attribute for this frame.
            mi::base::Handle< mi::IOptions> options(
                transaction->edit<INVALID_DOXYREFmi::IOptions>( "miDefaultOptions"));
            check_success( options.is_valid_interface());
            mi::base::Handle< mi::IFloat64> animation_time( options->edit_attribute<mi::IFloat64>(
                "animation_time"));
            check_success( animation_time.is_valid_interface());
            animation_time->set_value( i * 2*MI_PI/N);
            animation_time = 0;
            options = 0;

            // Access the scene object, create a render context, and render the scene.
            mi::base::Handle< mi::neuraylib::IScene> scene(
               transaction->edit<mi::neuraylib::IScene>( "the_scene"));
            check_success( scene.is_valid_interface());
            mi::base::Handle< mi::neuraylib::IRender_context> render_context(
               scene->get_render_context( transaction.get(), "rt_bsp"));
            check_success( render_context.is_valid_interface());
            check_success(
                render_context->render( transaction.get(), &render_target, NULL) == 0);

            // Write the image to disk.
            mi::base::Handle< mi::neuraylib::ICanvas> canvas( render_target.get_canvas( 0));
            std::ostringstream str;
            str << i << ".png";
            export_api->export_canvas( str.str().c_str(), canvas.get(), 100);
        }

        transaction->commit();
    }
}

// The example takes the following command line arguments:
//
//   example_animated_mesh <shader_path>
//
// shader_path      path to the shaders, e.g., neuray-<version>/shaders
//
// The rendered images are written to files named "0.png", "1.png", etc.
//
int main( int argc, char* argv[])
{
    // Collect command line parameters
    if( argc != 2) {
        std::cerr << "Usage: example_animated_mesh <shader_path>" << std::endl;
        return EXIT_FAILURE;
    }
    const char* shader_path = argv[1];

    // Access the neuray library
    mi::base::Handle< mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
    check_success( neuray.is_valid_interface());

    // Configure the neuray library
    configuration ( neuray, shader_path);

    // Start the neuray library
    check_success( neuray->start( true) == 0);

    // Do the actual rendering
    rendering( neuray);

    // Shut down the neuray library
    check_success( neuray->shutdown() == 0);
    neuray = 0;

    // Unload the neuray library
    check_success( unload());

    return EXIT_SUCCESS;
}

[Previous] [Next] [Up]