neuray API Programmer's Manual

Example for Polygon 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 cylinder as a polygon mesh and tessellates a copy of it.

New Topics

  • Creation of polygon meshes

  • Using the tessellator

Detailed Description

Creation of polygon meshes

To create a polygon mesh you need to at least specify the points (the position of the vertices) of the polygon mesh and the polygons (as point indices). In create_tetrahedron() we create a cylinder with a regular N-gon as base. The actual data (point coordinates, normal directions, etc). is not hardcoded, but computed by a helper class Cylinder based on few input parameters (radius, height, parameter n of the N-gon).

In contrast to the previous example, vertex normals are not specified per point, but per vertex (to get a sharp edge for the top and base face). Hence, we cannot use the mesh connectivity, but have to use a custom connectivity. The vertices are mapped to entries in the attribute vector as follows: All vertices of the top face are mapped to the same normal, same for the vertices of the base face. Each two vertices on the edge between two adjacent side faces share one normal.

The cylinder is included in the scene similar as the tetrahedron in the previous example.

Using the tessellator

The tessellator is a functor which tessellates a polygon mesh and returns the tessellated mesh as a triangle mesh. In this example, the red cylinder is tessellated and the result is added to the scene (with a blue material). Currently, the tessellator does not support any options to control its behavior.

Example Source

Source Code Location: examples/example_polygon_mesh.cpp

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

// examples/example_polygon_mesh.cpp
//
// Creates and tessellates a polygon mesh.

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

// Helper class that represents a cylinder as a polygon mesh with a regular N-gon as base
class Cylinder
{
public:
    Cylinder( mi::Uint32 n, mi::Float32 radius, mi::Float32 height)
        : m_n( n), m_radius( radius), m_height( height)
    {
        m_mesh_connectivity = new mi::Uint32[ 2*n + 4*n];
        m_normal_connectivity = new mi::Uint32[ 2*n + 4*n];
        m_offsets = new mi::Uint32[ n + 2 + 1];

        // offsets to the first vertex index of each polygon
        m_offsets[0] = 0;                                     // base (n vertices)
        m_offsets[1] = n;                                     // top (n vertices)
        for( mi::Uint32 i = 0; i <= n; ++i)
            m_offsets[2+i] = 2*n + 4*i;                       // sides (4 vertices each)

        // the mesh connectivity
        mi::Uint32 index = 0;
        for( mi::Uint32 i = 0; i < n; ++i)                    // base (first n even indices)
            m_mesh_connectivity[index++] = 2*i;
        for( mi::Uint32 i = 0; i < n; ++i)                    // top (first n odd indices)
            m_mesh_connectivity[index++] = 2*i+1;
        for( mi::Uint32 i = 0; i < n; ++i) {                  // sides (four subsequent indices)
            m_mesh_connectivity[index++] =  2*i;
            m_mesh_connectivity[index++] = (2*i + 2) % (2*n);
            m_mesh_connectivity[index++] = (2*i + 3) % (2*n);
            m_mesh_connectivity[index++] = (2*i + 1) % (2*n);
        }

        // the custom connectivity for normals
        index = 0;
        for( mi::Uint32 i = 0; i < n; ++i)                    // base (one constant normal)
            m_normal_connectivity[index++] = 0;
        for( mi::Uint32 i = 0; i < n; ++i)                    // top (one constant normal)
            m_normal_connectivity[index++] = 1;
        for( mi::Uint32 i = 0; i < n; ++i) {                  // sides (two normals each, shared
            m_normal_connectivity[index++] = 2 + i;           // with adjacent side face)
            m_normal_connectivity[index++] = 2 + (i+1) % n;
            m_normal_connectivity[index++] = 2 + (i+1) % n;
            m_normal_connectivity[index++] = 2 + i;
        }
    }

    ~Cylinder()
    {
        delete[] m_mesh_connectivity;
        delete[] m_normal_connectivity;
        delete[] m_offsets;
    }

    mi::Uint32 num_points() const { return m_n * 2; }

    mi::Uint32 num_normals() const { return m_n + 2; }

    mi::Uint32 num_polys() const { return m_n + 2; }

    mi::Uint32 polygon_size( mi::Uint32 p) const { return m_offsets[p+1] - m_offsets[p]; }

    mi::Uint32* polygon_indices( mi::Uint32 p) const { return &m_mesh_connectivity[m_offsets[p]]; }

    mi::Uint32* normal_indices( mi::Uint32 p) const { return &m_normal_connectivity[m_offsets[p]]; }

    mi::Float32_3 point( mi::Uint32 index)
    {
        mi::Uint32 i = index / 2;
        mi::Float32 angle = static_cast<mi::Float32>( 2.0f * MI_PI * i / m_n);

        if( index % 2 == 0)
            return mi::Float32_3( -m_height/2.0f, m_radius * sin( angle), m_radius * cos( angle));
        else
            return mi::Float32_3(  m_height/2.0f, m_radius * sin( angle), m_radius * cos( angle));
    }

    mi::Float32_3 normal( mi::Uint32 index)
    {
        if( index == 0) return mi::Float32_3( -1.0f, 0.0f, 0.0f);
        if( index == 1) return mi::Float32_3(  1.0f, 0.0f, 0.0f);

        mi::Float32 angle = static_cast<mi::Float32>( 2.0f * MI_PI * (index-2) / m_n);
        return mi::Float32_3( 0.0f, sin( angle), cos( angle));
    }

private:
    mi::Uint32 m_n;
    mi::Float32 m_radius, m_height;
    mi::Uint32* m_mesh_connectivity;
    mi::Uint32* m_normal_connectivity;
    mi::Uint32* m_offsets;
};

// Create a cylinder with a regular N-gon as base and normal vectors.
INVALID_DOXYREFmi::IPolygon_mesh* create_cylinder( mi::neuraylib::ITransaction* transaction,
    mi::Uint32 n, mi::Float32 radius, mi::Float32 height)
{
    Cylinder cylinder( n, radius, height);

    // Create an empty polygon mesh
    INVALID_DOXYREFmi::IPolygon_mesh* mesh = transaction->create<INVALID_DOXYREFmi::IPolygon_mesh>( "Polygon_mesh");
    check_success( mesh);

    // Create a cylinder (points and polygons)
    mesh->INVALID_DOXYREF( cylinder.num_points());
    for( mi::Uint32 i = 0; i < cylinder.num_points(); ++i)
        mesh->INVALID_DOXYREF( cylinder.point( i));
    for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i)
        mesh->INVALID_DOXYREF( cylinder.polygon_size( i));

    // Map vertices of the polygons to points
    mi::base::Handle< mi::IPolygon_connectivity> mesh_connectivity( mesh->INVALID_DOXYREF());
    for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i)
        mesh_connectivity->set_polygon_indices(
            INVALID_DOXYREFmi::Polygon_handle( i), cylinder.polygon_indices( i));
    check_success( mesh->INVALID_DOXYREF( mesh_connectivity.get()) == 0);

    // Create a custom connectivity for normal vectors and map vertices to entries in the
    // attribute vector
    mi::base::Handle< mi::IPolygon_connectivity> normal_connectivity(
        mesh->INVALID_DOXYREF());
    for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i)
        normal_connectivity->set_polygon_indices(
            INVALID_DOXYREFmi::Polygon_handle( i), cylinder.normal_indices( i));

    // Create an attribute vector for the normals
    mi::base::Handle< mi::IAttribute_vector> normals(
        normal_connectivity->create_attribute_vector( INVALID_DOXYREFmi::ATTR_NORMAL));
    for( mi::Uint32 i = 0; i < cylinder.num_normals(); ++i)
        normals->append_vector3( cylinder.normal( i));
    check_success( normals->is_valid_attribute());
    check_success( normal_connectivity->attach_attribute_vector( normals.get()) == 0);
    check_success( !normals->is_valid_attribute());

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

    return mesh;
}

// Add a red cylinder as polygon mesh and a blue cylinder as tessellation of the red cylinder
void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup)
{
    mi::Float32 cylinder_radius = 0.6f;
    mi::Float32 cylinder_height = 1.2f;
    
    // Create the red cylinder
    mi::base::Handle< mi::IPolygon_mesh> mesh_red( create_cylinder(
        transaction, 23, cylinder_radius, cylinder_height));
    transaction->store( mesh_red.get(), "mesh_red");

    // Create the instance for the red cylinder
    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.5-cylinder_radius, 0.8);
    matrix.rotate( 0.0, -0.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);

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

    // Tessellate the polygon mesh of the red cylinder.
    mi::base::Handle< mi::ITessellator> tessellator(
        transaction->create<INVALID_DOXYREFmi::ITessellator>( "Tessellator"));
    mi::base::Handle< const mi::IPolygon_mesh> c_mesh_red(
        transaction->access<INVALID_DOXYREFmi::IPolygon_mesh>( "mesh_red"));
    mi::base::Handle< mi::ITriangle_mesh> m_mesh_blue( tessellator->run( c_mesh_red.get()));
    transaction->store( m_mesh_blue.get(), "mesh_blue");

    // Create the instance for the blue cylinder
    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( -1.7, 0.5-cylinder_radius, -0.5);
    instance->set_world_to_obj( matrix);

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

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

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

    // And attach the instance to the root group
    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(
        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
}

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

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

    // Add a polygon mesh and a tessellated blue 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");

    // 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(), "rt_bsp"));
    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(
        neuray->get_api_component<mi::neuraylib::IExport_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_polygon_mesh.png", canvas.get(), 100);

    // All transactions need to get committed or aborted, not really important in this example.
    transaction->commit();
}

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