Iray SDK API nvidia_logo_transpbg.gif Up
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 mi::neuraylib::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 2023 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/example_triangle_mesh.cpp
//
// Creates and manipulates triangle meshes.
//
// The example expects the following command line arguments:
//
// example_triangle_mesh <mdl_path>
//
// mdl_path path to the MDL modules, e.g., iray-<version>/mdl
//
// The rendered image is written to a file named "example_triangle_mesh.png".
#include <mi/neuraylib.h>
// Include code shared by all examples.
#include "example_shared.h"
// Include an implementation of IRender_target.
#include "example_render_target_simple.h"
#include <iostream>
#include <map>
#include <vector>
// Create a simple tetrahedron with normal vectors.
{
// 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) };
// Create an empty triangle mesh
= transaction->create<mi::neuraylib::ITriangle_mesh>( "Triangle_mesh");
check_success( mesh);
// Create a tetrahedron
mesh->reserve_points( 4);
for( mi::Uint32 i = 0; i < 4; ++i)
mesh->append_point( tetra_points[i]);
mesh->reserve_triangles( 4);
for( mi::Uint32 i = 0; i < 4; ++i)
mesh->append_triangle( tetra_triangles[i]);
// Use the mesh connectivity for normal vectors
// Create an attribute vector for the normals
mesh_connectivity->create_attribute_vector( mi::neuraylib::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->attach_mesh_connectivity( 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::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->points_size(); // # points
mi::Uint32 t = mesh->triangles_size(); // # triangles
mi::Uint32 e = t * 3 / 2; // # edges
mesh->reserve_points( n + e);
mesh->reserve_triangles( 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) {
// 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.
mesh->point( triangle[0], p);
smoothed_point[ triangle[1]] += p;
mesh->point( triangle[1], p);
smoothed_point[ triangle[2]] += p;
mesh->point( 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->point( v0, p0); // point opposite of edge
mesh->point( 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->append_point( 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;
mesh->point( new_index[j], q);
mesh->set_point( new_index[j], q + new_point);
}
}
// 1:4 split, each triangle is split into 4 triangles
mi::neuraylib::Triangle_point_indices( triangle[0], new_index[2], new_index[1]));
mi::neuraylib::Triangle_point_indices( triangle[1], new_index[0], new_index[2]));
mi::neuraylib::Triangle_point_indices( triangle[2], new_index[1], new_index[0]));
mi::neuraylib::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) {
mesh->point( 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 * MI_PI / valence[i]);
w = 5.0/8.0 - w * w; // final weight: w for 1-ring, 1-w for old vertex
mesh->set_point( i,
(1 - w) * p + w * smoothed_point[i] / static_cast<mi::Float32>( valence[i]));
}
// Recompute the normals. They are stored per-point in this example, hence, retrieve them from
// the mesh connectivity.
mesh_connectivity->edit_attribute_vector( mi::neuraylib::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->points_size(); // # 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->triangles_size(); // # new triangles
for( mi::Uint32 i = 0; i < new_t; ++i) {
= mesh_connectivity->triangle_point_indices( mi::neuraylib::Triangle_handle( i));
mi::Float32_3 p0, p1, p2;
mesh->point( triangle[0], p0);
mesh->point( triangle[1], p1);
mesh->point( triangle[2], p2);
mi::Float32_3 v = cross( p1 - p0, p2 - p0);
v.normalize();
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);
v.normalize();
normals->set_vector3( i, v);
}
// Reattach the normal vector and the mesh connectivity
mesh_connectivity->attach_attribute_vector( normals.get());
mesh->attach_mesh_connectivity( 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::neuraylib::ITriangle_mesh> mesh_red( create_tetrahedron( transaction));
transaction->store( mesh_red.get(), "mesh_red");
// Create the instance for the red tetrahedron
transaction->create<mi::neuraylib::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.1, -0.5, 0.2);
matrix.rotate( 0.0, MI_PI_2, 0.0);
instance->set_matrix( matrix);
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( "red_material");
transaction->store( instance.get(), "instance_red");
// And attach the instance to the root group
transaction->edit<mi::neuraylib::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");
transaction->edit<mi::neuraylib::ITriangle_mesh>( "mesh_blue"));
loop_subdivision( mesh_blue.get());
loop_subdivision( mesh_blue.get());
loop_subdivision( mesh_blue.get());
loop_subdivision( mesh_blue.get());
// Create the instance for the blue object
instance = transaction->create<mi::neuraylib::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.4, -1.5, -1.6);
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 *= matrix_scale;
instance->set_matrix( matrix);
visible = instance->create_attribute<mi::IBoolean>( "visible", "Boolean");
visible->set_value( true);
material = instance->create_attribute<mi::IRef>( "material", "Ref");
material->set_reference( "blue_material");
transaction->store( instance.get(), "instance_blue");
// And attach the instance to the root group
group = transaction->edit<mi::neuraylib::IGroup>( rootgroup);
group->attach( "instance_blue");
}
void configuration( mi::neuraylib::INeuray* neuray, const char* mdl_path)
{
// Configure the neuray library. Here we set the search path for .mdl files.
check_success( rc->add_mdl_path( mdl_path) == 0);
check_success( rc->add_mdl_path( ".") == 0);
// Load the OpenImageIO, Iray Photoreal, and .mi importer plugins.
check_success( pc->load_plugin_library( "nv_openimageio" MI_BASE_DLL_FILE_EXT) == 0);
check_success( pc->load_plugin_library( "libiray" MI_BASE_DLL_FILE_EXT) == 0);
check_success( pc->load_plugin_library( "mi_importer" MI_BASE_DLL_FILE_EXT) == 0);
}
void rendering( mi::neuraylib::INeuray* neuray)
{
// Get the database, the global scope of the database, and create a transaction in the global
// scope for importing the scene file and storing the scene.
check_success( database.is_valid_interface());
database->get_global_scope());
scope->create_transaction());
check_success( transaction.is_valid_interface());
// Import the scene file
check_success( import_api.is_valid_interface());
import_api->import_elements( transaction.get(), "file: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
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 Iray Photoreal render mode
scene = transaction->edit<mi::neuraylib::IScene>( "the_scene");
scene->create_render_context( transaction.get(), "iray"));
check_success( render_context.is_valid_interface());
mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>());
scheduler_mode->set_c_str( "batch");
render_context->set_option( "scheduler_mode", scheduler_mode.get());
scene = 0;
// Create the render target and render the scene
new Render_target( image_api.get(), "Color", 512, 384));
check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0);
// Write the image to disk
check_success( export_api.is_valid_interface());
mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0));
export_api->export_canvas( "file:example_triangle_mesh.png", canvas.get());
transaction->commit();
}
int main( int argc, char* argv[])
{
// Collect command line parameters
if( argc != 2) {
std::cerr << "Usage: example_triangle_mesh <mdl_path>" << std::endl;
keep_console_open();
return EXIT_FAILURE;
}
const char* mdl_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.get(), mdl_path);
// Start the neuray library
mi::Sint32 result = neuray->start();
check_start_success( result);
// Do the actual rendering
rendering( neuray.get());
// Shut down the neuray library
check_success( neuray->shutdown() == 0);
neuray = 0;
// Unload the neuray library
check_success( unload());
keep_console_open();
return EXIT_SUCCESS;
}
This interface represents bool.
Definition: inumber.h:122
virtual void set_value(bool val)=0
Sets the value of the object via a parameter of type bool.
A reference is an object that acts as a pointer to other database elements.
Definition: iref.h:25
virtual Sint32 set_reference(const base::IInterface *db_element)=0
Sets the reference to db_element.
A simple string class.
Definition: istring.h:22
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
NxM-dimensional matrix class template of fixed dimensions.
Definition: matrix.h:367
Fixed-size math vector class template with generic operations.
Definition: vector.h:286
This interface is used to interact with the distributed database.
Definition: idatabase.h:293
This interface is used to export files.
Definition: iexport_api.h:38
A group is a container for other scene elements.
Definition: igroup.h:39
virtual Sint32 attach(const char *scene_element)=0
Attaches a scene element to the group.
This interface provides various utilities related to canvases and buffers.
Definition: iimage_api.h:49
This interface is used to import files.
Definition: iimport_api.h:100
An instance is a scene element that adds a transformation and attributes to another scene element.
Definition: iinstance.h:117
virtual Sint32 attach(const char *scene_element)=0
Attaches a scene element to the instance.
This is an object representing the Iray library.
Definition: ineuray.h:44
virtual Sint32 shutdown(bool blocking=true)=0
Shuts down the library.
virtual base::IInterface * get_api_component(const base::Uuid &uuid) const =0
Returns an API component from the Iray SDK API.
virtual Sint32 start(bool blocking=true)=0
Starts the operation of the Iray library.
This interface is used to load plugins and to query information about loaded plugins.
Definition: iplugin_configuration.h:24
This interface is used to query and change the rendering configuration.
Definition: irendering_configuration.h:109
The scene is the top-level element describing a subset of DB elements to be rendered.
Definition: iscene.h:44
A transaction provides a consistent view on the database.
Definition: itransaction.h:81
virtual Sint32 copy(const char *source, const char *target, Uint8 privacy=LOCAL_SCOPE)=0
Creates a copy of a database element.
virtual base::IInterface * create(const char *type_name, Uint32 argc=0, const base::IInterface *argv[]=0)=0
Creates an object of the type type_name.
virtual base::IInterface * edit(const char *name)=0
Retrieves an element from the database and returns it ready for editing.
virtual Sint32 commit()=0
Commits the transaction.
virtual Sint32 store(base::IInterface *db_element, const char *name, Uint8 privacy=LOCAL_SCOPE)=0
Stores the element db_element in the database under the name name and with the privacy level privacy.
Interface representing a triangle mesh.
Definition: itriangle_mesh.h:122
virtual Uint32 triangles_size() const =0
Returns the number of triangles.
virtual Sint32 attach_mesh_connectivity(ITriangle_connectivity *connectivity)=0
Attaches the mesh connectivity to the mesh.
virtual Uint32 points_size() const =0
Returns the number of points in the mesh.
virtual Uint32 append_point(const Float32_3_struct &p)=0
Adds a point p to the end of all points and returns the index of the new point.
virtual Triangle_handle_struct append_triangle(const Triangle_point_indices_struct &triangle)=0
Adds a triangle to the mesh.
virtual Triangle_point_indices_struct triangle_point_indices(Triangle_handle_struct hnd) const =0
Returns the triple of indices describing the vertices of the triangle of index i.
virtual ITriangle_connectivity * edit_mesh_connectivity()=0
Detaches and returns the mesh connectivity.
virtual Sint32 set_triangle(Triangle_handle_struct hnd, const Triangle_point_indices_struct &triangle)=0
Modifies a triangle of the mesh.
virtual void reserve_triangles(Uint32 n)=0
Reserves space for at least n triangles.
virtual void reserve_points(Uint32 n)=0
Reserves space for at least n points.
virtual Sint32 point(Uint32 index, Float32_3_struct &p) const =0
Returns the coordinates of the point of index index in the output parameter p.
virtual Sint32 set_point(Uint32 index, const Float32_3_struct &p)=0
Sets the point of index index to the new point p.
Handle class for type-safety.
Definition: identifier.h:66
A triangle defined by three point indices, starting at index 0.
Definition: itriangle_connectivity.h:33
#define MI_BASE_DLL_FILE_EXT
The operating system specific default filename extension for shared libraries (DLLs)
Definition: config.h:340
unsigned int Uint32
32-bit unsigned integer.
Definition: types.h:49
#define MI_PI_2
Value of Pi / 2.
Definition: types.h:194
double Float64
64-bit float.
Definition: types.h:52
float Float32
32-bit float.
Definition: types.h:51
#define MI_PI
Value of Pi.
Definition: types.h:192
signed int Sint32
32-bit signed integer.
Definition: types.h:46
Color cos(const Color &c)
Returns a color with the elementwise cosine of the color c.
Definition: color.h:561
T cross(const Vector_struct<T, 2> &lhs, const Vector_struct<T, 2> &rhs)
Returns the two-times-two determinant result for the two vectors lhs and rhs.
Definition: vector.h:1705
bool normalize()
Normalizes this vector to unit length.
Definition: vector.h:646
math::Vector<Float32, 3> Float32_3
Vector of three Float32.
Definition: vector_typedefs.h:90
math::Matrix<Float64, 4, 4> Float64_4_4
4 x 4 matrix of Float64.
Definition: matrix_typedefs.h:330
@ ATTR_NORMAL
Surface normals of type mi::Float32_3 per point or per vertex.
Definition: iattribute_vector.h:65
bool operator<(const Tag &lhs, const Tag &rhs)
Returns true if lhs is less than rhs.
Definition: iserializer.h:104
Iray SDK API.
[Previous] [Next] [Up]