neuray API Programmer's Manual

Example for Triangle meshes

[Previous] [Next] [Up]

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 red tetrahedron as a triangle mesh and constructs a blue subdivision surface from the tetrahedron.

New Topics

  • Creation of triangle meshes

  • Inclusion of geometry into the scene

  • Manipulation of triangle meshes

Detailed Description

Creation of triangle meshes

To create a triangle mesh you need to at least specify the points (the position of the vertices) of the triangle mesh and the triangles (as point indices). In create_tetrahedron() we create a tetrahedron with four points and four triangles.

Vertex normals are an attribute of the triangle mesh. In contrast to generic methods for attributes offered but INVALID_DOXYREFmi::IAttribute_set, meshes offer own methods to provide access to mesh-specific attributes. Here we specify one normal per point, hence, the mesh connectivity is used to create and to attach the attribute vector.

Inclusion of geometry into the scene

After geometry has been created and stored as database element it is necessary to include it in the scene graph (unless you do not want it to be part of the scene). The most common approach is to create an instance node that instantiates the geometry, and to include that instance in some group, for example the root group. The instance node allows you to share the geometry between several instances while having different settings per instance. For example, different instances typically have different transformation matrices, and might have different attributes, e.g., materials.

In setup_scene() we create an instance for each of both meshes and set the transformation matrix and the visible and material attribute. Both instances are then added to the root group.

Manipulation of triangle meshes

All triangle mesh data can be retrieved and changed via the API. The example demonstrates a Loop-subdivision scheme for triangle meshes.

It is possible to retrieve and/or change the number of points and triangles, as well as the point coordinates or triangle indices. To access the mesh-specific attributes you have to acquire the corresponding attribute vector. If you have obtained a non-const attribute vector you have to re-attach it to the mesh after you are done with it.

Example Source

Source Code Location: examples/example_triangle_mesh.cpp

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

// examples/example_triangle_mesh.cpp
// Creates and manipulates triangle meshes.

#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"

#include <iostream>
#include <map>
#include <vector>

// Create a simple tetrahedron with normal vectors.
INVALID_DOXYREFmi::ITriangle_mesh* create_tetrahedron( mi::neuraylib::ITransaction* transaction)
    // Some constants for the vertices, normals, and faces of the tetrahedron
    mi::Float32_3 tetra_points[4] = {
        mi::Float32_3( -0.5, -0.5, -0.5),
        mi::Float32_3(  0.5, -0.5, -0.5),
        mi::Float32_3( -0.5,  0.5, -0.5),
        mi::Float32_3( -0.5, -0.5,  0.5) };

    mi::Float32_3 tetra_normals[4] = {
        mi::Float32_3( -0.577f, -0.577f, -0.577f),
        mi::Float32_3(  0.89f,  -0.20f,  -0.20f),
        mi::Float32_3( -0.20f,   0.89f,  -0.20f),
        mi::Float32_3( -0.20f,  -0.20f,   0.89f) };

    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]);

    // Use the mesh connectivity for normal vectors
    mi::base::Handle< mi::ITriangle_connectivity> mesh_connectivity(

    // Create an attribute vector for the normals
    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());
    check_success( mesh_connectivity->attach_attribute_vector( normals.get()) == 0);
    check_success( !normals->is_valid_attribute());

    check_success( mesh->INVALID_DOXYREF( mesh_connectivity.get()) == 0);

    return mesh;

// Data type to store edges in a std::map, needed in the loop subdivision algorithm below.
struct Edge {
    mi::Uint32 v1; // smaller index of the two vertex indices
    mi::Uint32 v2; // larger index of the two vertex indices
    Edge() : v1( 0), v2( 0) {}
    Edge( mi::Uint32 p, mi::Uint32 q) : v1( p<q ? p : q), v2( p<q ? q : p) {}
    bool operator< ( const Edge& e) const { return v1 < e.v1 || ( v1 == e.v1 && v2 < e.v2); }

// Loop subdivision scheme for oriented 2-manifold triangle meshes.
// For simplicity the code assumes that the mesh is an oriented 2-manifold without boundaries.
// It also assumes that the mesh has proper normal vector attributes.
void loop_subdivision( mi::neuraylib::ITransaction* transaction, INVALID_DOXYREFmi::ITriangle_mesh* mesh)
    // Keep the old mesh sizes in local variables. The old mesh will remain in its place as long as
    // needed, while new elements are appended or kept in temporary arrays.
    mi::Uint32 n = mesh->INVALID_DOXYREF();    // # points
    mi::Uint32 t = mesh->INVALID_DOXYREF(); // # triangles
    mi::Uint32 e = t * 3 / 2;              // # edges
    mesh->INVALID_DOXYREF( n + e);
    mesh->INVALID_DOXYREF( 4 * t);

    // Temporary space for smoothed points for the old existing vertices.
    std::vector< mi::Float32_3 > smoothed_point(
        n, mi::Float32_3( 0.0, 0.0, 0.0));

    // Valence (i.e., vertex degree) of the old existing vertices.
    std::vector< mi::Uint32> valence( n, 0);

    // Edge bisection introduces a single new point per edge, but we will in the course of the
    // algorithm see the edge twice, once per incident triangle. We store a mapping of edges to new
    // vertex indices for simplicity in the following STL map.
    std::map< Edge, mi::Uint32> split_vertex;

    // Compute, with a loop over all old triangles:
    //   - valence of the old vertices
    //   - contribution of 1-ring neighborhood to smoothed old vertices
    //     (weighting by valence follows later)
    //   - new vertices on split edges
    //   - 1:4 split, each triangle is split into 4 triangles
    for( mi::Uint32 i = 0; i < t; ++i) {
        INVALID_DOXYREFmi::Triangle_point_indices triangle
            = mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::Triangle_handle( i));

        // Increment valence for each vertex
        ++ valence[ triangle[0]];
        ++ valence[ triangle[1]];
        ++ valence[ triangle[2]];

        // Add neighbor vertices to smoothed vertex following triangle orientation. The opposite
        // contribution follows from the adjacent triangle.
        mi::Float32_3 p;
        mesh->INVALID_DOXYREF( triangle[0], p);
        smoothed_point[ triangle[1]] += p;
        mesh->INVALID_DOXYREF( triangle[1], p);
        smoothed_point[ triangle[2]] += p;
        mesh->INVALID_DOXYREF( triangle[2], p);
        smoothed_point[ triangle[0]] += p;

        // Determine new vertices at split edges. Loop over all three edges.
        mi::Uint32 new_index[3]; // indices of the three new vertices
        for( mi::Uint32 j = 0; j != 3; ++j) {
            // Consider the edge from v1 to v2.
            mi::Uint32 v0 = triangle[ j     ]; // vertex opposite of edge
            mi::Uint32 v1 = triangle[(j+1)%3]; // vertex that starts the edge
            mi::Uint32 v2 = triangle[(j+2)%3]; // vertex that ends the edge
            Edge edge( v1, v2);
            // Create the new point (or the second half of the contribution) for the split vertex.
            mi::Float32_3 p0, p1;
            mesh->INVALID_DOXYREF( v0, p0); // point opposite of edge
            mesh->INVALID_DOXYREF( v1, p1); // point that starts the edge
            mi::Float32_3 new_point = ( p0 + p1 * 3.0) / 8.0;
            // Is the split vertex on the edge defined?
            std::map< Edge, mi::Uint32>::iterator split_vertex_pos = split_vertex.find( edge);
            if ( split_vertex_pos == split_vertex.end()) {
                // If not yet defined, create it and a corresponding new vertex in the mesh.
                new_index[j] = mesh->INVALID_DOXYREF( new_point);
                split_vertex[ edge] = new_index[j];
            } else {
                // If is defined, add the second half of the new vertex contribution
                new_index[j] = split_vertex_pos->second;
                mi::Float32_3 q;
                mesh->INVALID_DOXYREF( new_index[j], q);
                mesh->INVALID_DOXYREF( new_index[j], q + new_point);

        // 1:4 split, each triangle is split into 4 triangles
            INVALID_DOXYREFmi::Triangle_point_indices( triangle[0], new_index[2], new_index[1]));
            INVALID_DOXYREFmi::Triangle_point_indices( triangle[1], new_index[0], new_index[2]));
            INVALID_DOXYREFmi::Triangle_point_indices( triangle[2], new_index[1], new_index[0]));
        mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::Triangle_handle( i),
            INVALID_DOXYREFmi::Triangle_point_indices( new_index[0], new_index[1], new_index[2]));

    // One loop over all old vertices combines the 1-ring neighborhood of the old vertices stored in
    // the smoothed vertices, weighted by valence, with the old vertices.
    for( mi::Uint32 i = 0; i < n; ++i) {
        mi::Float32_3 p;
        mesh->INVALID_DOXYREF( i, p);
        // Weight used to smooth the old vertices.
        // (An improved implementation would store the weights in a lookup table.)
        mi::Float64 w = 3.0/8.0 + 1.0/4.0 * cos( 2.0 * M_PI / valence[i]);
        w = 5.0/8.0 - w * w; // final weight: w for 1-ring, 1-w for old vertex
        mesh->INVALID_DOXYREF( i, (1 - w) * p + w * smoothed_point[i] / valence[i]);

    // Recompute the normals. They are stored per-point in this example, hence, retrieve them from
    // the mesh connectivity.
    mi::base::Handle< mi::ITriangle_connectivity> mesh_connectivity(
    mi::base::Handle< mi::IAttribute_vector> normals(
        mesh_connectivity->edit_attribute_vector( INVALID_DOXYREFmi::ATTR_NORMAL));
    check_success( normals.is_valid_interface());
    normals->reserve( n + e);

    // Compute smoothed normal vectors per vertex by averaging adjacent facet normals.
    // First reset all old normals and add space for new normals.
    mi::Uint32 new_n = mesh->INVALID_DOXYREF();    // # new points
    for( mi::Uint32 i = 0; i < n; ++i)
        normals->set_vector3( i, mi::Float32_3( 0.0, 0.0, 0.0));
    for( mi::Uint32 i = n; i < new_n; ++i)
        normals->append_vector3( mi::Float32_3( 0.0, 0.0, 0.0));

    // Compute, with a loop over all old and all new triangles the normal vectors for each triangle
    // and add them to the per-vertex normals.
    mi::Uint32 new_t = mesh->INVALID_DOXYREF(); // # new triangles
    for( mi::Uint32 i = 0; i < new_t; ++i) {
        INVALID_DOXYREFmi::Triangle_point_indices triangle
            = mesh_connectivity->triangle_point_indices( INVALID_DOXYREFmi::Triangle_handle( i));
        mi::Float32_3 p0, p1, p2;
        mesh->INVALID_DOXYREF( triangle[0], p0);
        mesh->INVALID_DOXYREF( triangle[1], p1);
        mesh->INVALID_DOXYREF( triangle[2], p2);
        mi::Float32_3 v = (p1 - p0).cross( p2 - p0);
        normals->set_vector3( triangle[0],
            v + mi::Float32_3( normals->get_vector3( triangle[0])));
        normals->set_vector3( triangle[1],
            v + mi::Float32_3( normals->get_vector3( triangle[1])));
        normals->set_vector3( triangle[2],
            v + mi::Float32_3( normals->get_vector3( triangle[2])));
    // Renormalize all normals
    for( mi::Uint32 i = 0; i < new_n; ++i) {
        mi::Float32_3 v = normals->get_vector3( i);
        normals->set_vector3( i, v);

    // Reattach the normal vector and the mesh connectivity
    mesh_connectivity->attach_attribute_vector( normals.get());
    mesh->INVALID_DOXYREF( mesh_connectivity.get());

// Add a red tetrahedron and a blue Loop-subdivision surface from the red tetrahedron
void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup)
    // Create the red tetrahedron
    mi::base::Handle< mi::ITriangle_mesh> mesh_red( create_tetrahedron( transaction));
    transaction->store( mesh_red.get(), "mesh_red");

    // Create the instance for the red tetrahedron
    mi::base::Handle< mi::IInstance> instance( transaction->create<INVALID_DOXYREFmi::IInstance>( "Instance"));
    instance->attach( "mesh_red");

    // Set the transformation matrix, the visible attribute, and the material
    mi::Float64_4_4 matrix( 1.0);
    matrix.translate( -0.7, 0.0, 0.8);
    matrix.rotate( 0.0, 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);

    mi::base::Handle< mi::IRef> material( instance->create_attribute<mi::IRef>( "material", "Ref"));
    material->set_reference( "redSG");

    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");

    // Create the blue object as a Loop-subdivision surface based on the red tetrahedron
    transaction->copy( "mesh_red", "mesh_blue");
    mi::base::Handle< mi::ITriangle_mesh> mesh_blue(
        transaction->edit<INVALID_DOXYREFmi::ITriangle_mesh>( "mesh_blue"));
    loop_subdivision( transaction, mesh_blue.get());
    loop_subdivision( transaction, mesh_blue.get());
    loop_subdivision( transaction, mesh_blue.get());
    loop_subdivision( transaction, mesh_blue.get());

    // Create the instance for the blue object
    instance = transaction->create<INVALID_DOXYREFmi::IInstance>( "Instance");
    instance->attach( "mesh_blue");

    // Set the transformation matrix, the visible attribute, and the material
    matrix = mi::Float64_4_4( 1.0);
    matrix.translate( -0.2, -1.0, -1.0);
    matrix.rotate( 0.0, 1.25 * MI_PI_2, 0.0);
    mi::Float64_4_4 matrix_scale(
        0.25, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 1);
    matrix.multiply( matrix_scale);
    instance->set_world_to_obj( matrix);

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

    material = instance->create_attribute<mi::IRef>( "material", "Ref");
    material->set_reference( "blueSG");

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

    // And attach the instance to the root group
    group = transaction->edit<INVALID_DOXYREFmi::IGroup>( rootgroup);
    group->attach( "instance_blue");

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(
    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(
    check_success( plugin_configuration->load_plugin_library( "") == 0);
    check_success( plugin_configuration->load_plugin_library( "") == 0);
    check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0);
    check_success( plugin_configuration->load_plugin_library( "gen_llvm.dll") == 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(
    check_success( database.is_valid_interface());
    mi::base::Handle< mi::neuraylib::IScope> scope(
    mi::base::Handle< mi::neuraylib::ITransaction> transaction(
    check_success( transaction.is_valid_interface());

    // Import the scene file
    mi::base::Handle< mi::neuraylib::IImport_api> import_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);

    // Add two triangle meshes 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");

    // Create the render context using the default renderer
    scene = transaction->edit<mi::neuraylib::IScene>( "the_scene");
    mi::base::Handle< mi::neuraylib::IRender_context> render_context(
        scene->get_render_context( transaction.get(), "default"));
    scene = 0;

    // Create the render target and render the scene
    Render_target render_target( 512, 384);
    check_success( render_context->render( transaction.get(), &render_target, NULL) == 0);

    // Write the image to disk
    mi::base::Handle< mi::neuraylib::IExport_api> export_api(
    check_success( export_api.is_valid_interface());
    mi::base::Handle< mi::neuraylib::ICanvas> canvas( render_target.get_canvas( 0));
    export_api->export_canvas( "example_triangle_mesh.png", canvas.get(), 100);

    // All transactions need to get committed or aborted, not really important in this example.

// The example takes the following command line arguments:
//   example_triangle_mesh <shader_path>
// shader_path      path to the shaders, e.g., neuray-<version>/shaders
// The rendered image is written to a file named "example_triangle_mesh.png".
int main( int argc, char* argv[])
    // Collect command line parameters
    if( argc != 2) {
        std::cerr << "Usage: example_triangle_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]