Iray Programmer's Manual

Creating triangle meshes

This topic introduces:

  1. Core concepts about creating triangle meshes, storing them as database elements, adding them to a scene graph, and retrieving and editing them:
  2. The program example_triangle_mesh.cpp, which serves as an example implementation of these concepts

Creating a triangle mesh

To create a triangle mesh, you need to specify at least the following:

  • The points (the position of the vertices) of the triangle mesh
  • The triangles (as point indices)

In example_triangle_mesh.cpp, create_tetrahedron() is used to 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 supported by mi::neuraylib::IAttribute_set, meshes provide their own methods to enable access to mesh-specific attributes. In example_triangle_mesh.cpp, one normal per point is specified; hence, the mesh connectivity is used to create and attach the attribute vector.

Adding geometry to a scene

After geometry has been created and stored as a 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, for example different materials.

In example_triangle_mesh.cpp, setup_scene() is used to create an instance of each mesh and to set the transformation matrix and the visible and material attribute. Both instances are then added to the root group.

Retrieving and editing triangle meshes

All triangle mesh data can be retrieved and changed by using the API. The example program example_triangle_mesh.cpp uses a Loop-subdivision scheme for triangle meshes to subdivide a copy of the tetrahedron created previously.

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

example_triangle_mesh.cpp

001 /******************************************************************************
002  * © 1986, 2016 NVIDIA Corporation. All rights reserved.
003  *****************************************************************************/
004 
005 // examples/example_triangle_mesh.cpp
006 //
007 // Creates and manipulates triangle meshes.
008 //
009 // The example expects the following command line arguments:
010 //
011 //   example_triangle_mesh <mdl_path>
012 //
013 // mdl_path         path to the MDL modules, e.g., iray-<version>/mdl
014 //
015 // The rendered image is written to a file named "example_triangle_mesh.png".
016 
017 #include <mi/neuraylib.h>
018 
019 // Include code shared by all examples.
020 #include "example_shared.h"
021 // Include an implementation of IRender_target.
022 #include "example_render_target_simple.h"
023 
024 #include <iostream>
025 #include <map>
026 #include <vector>
027 
028 // Create a simple tetrahedron with normal vectors.
029 mi::neuraylib::ITriangle_mesh* create_tetrahedron( mi::neuraylib::ITransaction* transaction)
030 {
031     // Some constants for the vertices, normals, and faces of the tetrahedron
032     mi::Float32_3 tetra_points[4] = {
033         mi::Float32_3( -0.5, -0.5, -0.5),
034         mi::Float32_3(  0.5, -0.5, -0.5),
035         mi::Float32_3( -0.5,  0.5, -0.5),
036         mi::Float32_3( -0.5, -0.5,  0.5) };
037 
038     mi::Float32_3 tetra_normals[4] = {
039         mi::Float32_3( -0.577f, -0.577f, -0.577f),
040         mi::Float32_3(  0.89f,  -0.20f,  -0.20f),
041         mi::Float32_3( -0.20f,   0.89f,  -0.20f),
042         mi::Float32_3( -0.20f,  -0.20f,   0.89f) };
043 
044     mi::neuraylib::Triangle_point_indices tetra_triangles[4] = {
045         mi::neuraylib::Triangle_point_indices( 0, 2, 1),
046         mi::neuraylib::Triangle_point_indices( 0, 1, 3),
047         mi::neuraylib::Triangle_point_indices( 0, 3, 2),
048         mi::neuraylib::Triangle_point_indices( 1, 2, 3) };
049 
050     // Create an empty triangle mesh
051     mi::neuraylib::ITriangle_mesh* mesh
052         = transaction->create<mi::neuraylib::ITriangle_mesh>( "Triangle_mesh");
053     check_success( mesh);
054 
055     // Create a tetrahedron
056     mesh->reserve_points( 4);
057     for( mi::Uint32 i = 0; i < 4; ++i)
058         mesh->append_point( tetra_points[i]);
059     mesh->reserve_triangles( 4);
060     for( mi::Uint32 i = 0; i < 4; ++i)
061         mesh->append_triangle( tetra_triangles[i]);
062 
063     // Use the mesh connectivity for normal vectors
064     mi::base::Handle<mi::neuraylib::ITriangle_connectivity> mesh_connectivity(
065         mesh->edit_mesh_connectivity());
066 
067     // Create an attribute vector for the normals
068     mi::base::Handle<mi::neuraylib::IAttribute_vector> normals(
069         mesh_connectivity->create_attribute_vector( mi::neuraylib::ATTR_NORMAL));
070     for( mi::Uint32 i = 0; i < 4; ++i)
071         normals->append_vector3( tetra_normals[i]);
072     check_success( normals->is_valid_attribute());
073     check_success( mesh_connectivity->attach_attribute_vector( normals.get()) == 0);
074     check_success( !normals->is_valid_attribute());
075 
076     check_success( mesh->attach_mesh_connectivity( mesh_connectivity.get()) == 0);
077 
078     return mesh;
079 }
080 
081 // Data type to store edges in a std::map, needed in the loop subdivision algorithm below.
082 struct Edge {
083     mi::Uint32 v1; // smaller index of the two vertex indices
084     mi::Uint32 v2; // larger index of the two vertex indices
085     Edge() : v1( 0), v2( 0) {}
086     Edge( mi::Uint32 p, mi::Uint32 q) : v1( p<q ? p : q), v2( p<q ? q : p) {}
087     bool operator< ( const Edge& e) const { return v1 < e.v1 || ( v1 == e.v1 && v2 < e.v2); }
088 };
089 
090 // Loop subdivision scheme for oriented 2-manifold triangle meshes.
091 //
092 // For simplicity the code assumes that the mesh is an oriented 2-manifold without boundaries.
093 // It also assumes that the mesh has proper normal vector attributes.
094 void loop_subdivision( mi::neuraylib::ITriangle_mesh* mesh)
095 {
096     // Keep the old mesh sizes in local variables. The old mesh will remain in its place as long as
097     // needed, while new elements are appended or kept in temporary arrays.
098     mi::Uint32 n = mesh->points_size();    // # points
099     mi::Uint32 t = mesh->triangles_size(); // # triangles
100     mi::Uint32 e = t * 3 / 2;              // # edges
101     mesh->reserve_points( n + e);
102     mesh->reserve_triangles( 4 * t);
103 
104     // Temporary space for smoothed points for the old existing vertices.
105     std::vector< mi::Float32_3 > smoothed_point(
106         n, mi::Float32_3( 0.0, 0.0, 0.0));
107 
108     // Valence (i.e., vertex degree) of the old existing vertices.
109     std::vector< mi::Uint32> valence( n, 0);
110 
111     // Edge bisection introduces a single new point per edge, but we will in the course of the
112     // algorithm see the edge twice, once per incident triangle. We store a mapping of edges to new
113     // vertex indices for simplicity in the following STL map.
114     std::map< Edge, mi::Uint32> split_vertex;
115 
116     // Compute, with a loop over all old triangles:
117     //   - valence of the old vertices
118     //   - contribution of 1-ring neighborhood to smoothed old vertices
119     //     (weighting by valence follows later)
120     //   - new vertices on split edges
121     //   - 1:4 split, each triangle is split into 4 triangles
122     for( mi::Uint32 i = 0; i < t; ++i) {
123         mi::neuraylib::Triangle_point_indices triangle
124             = mesh->triangle_point_indices( mi::neuraylib::Triangle_handle( i));
125 
126         // Increment valence for each vertex
127         ++ valence[ triangle[0]];
128         ++ valence[ triangle[1]];
129         ++ valence[ triangle[2]];
130 
131         // Add neighbor vertices to smoothed vertex following triangle orientation. The opposite
132         // contribution follows from the adjacent triangle.
133         mi::Float32_3 p;
134         mesh->point( triangle[0], p);
135         smoothed_point[ triangle[1]] += p;
136         mesh->point( triangle[1], p);
137         smoothed_point[ triangle[2]] += p;
138         mesh->point( triangle[2], p);
139         smoothed_point[ triangle[0]] += p;
140 
141         // Determine new vertices at split edges. Loop over all three edges.
142         mi::Uint32 new_index[3]; // indices of the three new vertices
143         for( mi::Uint32 j = 0; j != 3; ++j) {
144             // Consider the edge from v1 to v2.
145             mi::Uint32 v0 = triangle[ j     ]; // vertex opposite of edge
146             mi::Uint32 v1 = triangle[(j+1)%3]; // vertex that starts the edge
147             mi::Uint32 v2 = triangle[(j+2)%3]; // vertex that ends the edge
148             Edge edge( v1, v2);
149             // Create the new point (or the second half of the contribution) for the split vertex.
150             mi::Float32_3 p0, p1;
151             mesh->point( v0, p0); // point opposite of edge
152             mesh->point( v1, p1); // point that starts the edge
153             mi::Float32_3 new_point = ( p0 + p1 * 3.0) / 8.0;
154             // Is the split vertex on the edge defined?
155             std::map< Edge, mi::Uint32>::iterator split_vertex_pos = split_vertex.find( edge);
156             if ( split_vertex_pos == split_vertex.end()) {
157                 // If not yet defined, create it and a corresponding new vertex in the mesh.
158                 new_index[j] = mesh->append_point( new_point);
159                 split_vertex[ edge] = new_index[j];
160             } else {
161                 // If is defined, add the second half of the new vertex contribution
162                 new_index[j] = split_vertex_pos->second;
163                 mi::Float32_3 q;
164                 mesh->point( new_index[j], q);
165                 mesh->set_point( new_index[j], q + new_point);
166             }
167         }
168 
169         // 1:4 split, each triangle is split into 4 triangles
170         mesh->append_triangle(
171             mi::neuraylib::Triangle_point_indices( triangle[0], new_index[2], new_index[1]));
172         mesh->append_triangle(
173             mi::neuraylib::Triangle_point_indices( triangle[1], new_index[0], new_index[2]));
174         mesh->append_triangle(
175             mi::neuraylib::Triangle_point_indices( triangle[2], new_index[1], new_index[0]));
176         mesh->set_triangle( mi::neuraylib::Triangle_handle( i),
177             mi::neuraylib::Triangle_point_indices( new_index[0], new_index[1], new_index[2]));
178     }
179 
180     // One loop over all old vertices combines the 1-ring neighborhood of the old vertices stored in
181     // the smoothed vertices, weighted by valence, with the old vertices.
182     for( mi::Uint32 i = 0; i < n; ++i) {
183         mi::Float32_3 p;
184         mesh->point( i, p);
185         // Weight used to smooth the old vertices.
186         // (An improved implementation would store the weights in a lookup table.)
187         mi::Float64 w = 3.0/8.0 + 1.0/4.0 * cos( 2.0 * M_PI / valence[i]);
188         w = 5.0/8.0 - w * w; // final weight: w for 1-ring, 1-w for old vertex
189         mesh->set_point( i,
190             (1 - w) * p + w * smoothed_point[i] / static_cast<mi::Float32>( valence[i]));
191     }
192 
193     // Recompute the normals. They are stored per-point in this example, hence, retrieve them from
194     // the mesh connectivity.
195     mi::base::Handle<mi::neuraylib::ITriangle_connectivity> mesh_connectivity(
196         mesh->edit_mesh_connectivity());
197     mi::base::Handle<mi::neuraylib::IAttribute_vector> normals(
198         mesh_connectivity->edit_attribute_vector( mi::neuraylib::ATTR_NORMAL));
199     check_success( normals.is_valid_interface());
200     normals->reserve( n + e);
201 
202     // Compute smoothed normal vectors per vertex by averaging adjacent facet normals.
203     // First reset all old normals and add space for new normals.
204     mi::Uint32 new_n = mesh->points_size();    // # new points
205     for( mi::Uint32 i = 0; i < n; ++i)
206         normals->set_vector3( i, mi::Float32_3( 0.0, 0.0, 0.0));
207     for( mi::Uint32 i = n; i < new_n; ++i)
208         normals->append_vector3( mi::Float32_3( 0.0, 0.0, 0.0));
209 
210     // Compute, with a loop over all old and all new triangles the normal vectors for each triangle
211     // and add them to the per-vertex normals.
212     mi::Uint32 new_t = mesh->triangles_size(); // # new triangles
213     for( mi::Uint32 i = 0; i < new_t; ++i) {
214         mi::neuraylib::Triangle_point_indices triangle
215             = mesh_connectivity->triangle_point_indices( mi::neuraylib::Triangle_handle( i));
216         mi::Float32_3 p0, p1, p2;
217         mesh->point( triangle[0], p0);
218         mesh->point( triangle[1], p1);
219         mesh->point( triangle[2], p2);
220         mi::Float32_3 v = cross( p1 - p0, p2 - p0);
221         v.normalize();
222         normals->set_vector3( triangle[0],
223             v + mi::Float32_3( normals->get_vector3( triangle[0])));
224         normals->set_vector3( triangle[1],
225             v + mi::Float32_3( normals->get_vector3( triangle[1])));
226         normals->set_vector3( triangle[2],
227             v + mi::Float32_3( normals->get_vector3( triangle[2])));
228     }
229     // Renormalize all normals
230     for( mi::Uint32 i = 0; i < new_n; ++i) {
231         mi::Float32_3 v = normals->get_vector3( i);
232         v.normalize();
233         normals->set_vector3( i, v);
234     }
235 
236     // Reattach the normal vector and the mesh connectivity
237     mesh_connectivity->attach_attribute_vector( normals.get());
238     mesh->attach_mesh_connectivity( mesh_connectivity.get());
239 }
240 
241 // Add a red tetrahedron and a blue Loop-subdivision surface from the red tetrahedron
242 void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup)
243 {
244     // Create the red tetrahedron
245     mi::base::Handle<mi::neuraylib::ITriangle_mesh> mesh_red( create_tetrahedron( transaction));
246     transaction->store( mesh_red.get(), "mesh_red");
247 
248     // Create the instance for the red tetrahedron
249     mi::base::Handle<mi::neuraylib::IInstance> instance(
250         transaction->create<mi::neuraylib::IInstance>( "Instance"));
251     instance->attach( "mesh_red");
252 
253     // Set the transformation matrix, the visible attribute, and the material
254     mi::Float64_4_4 matrix( 1.0);
255     matrix.translate( -0.1, -0.5, 0.2);
256     matrix.rotate( 0.0, MI_PI_2, 0.0);
257     instance->set_matrix( matrix);
258 
259     mi::base::Handle<mi::IBoolean> visible(
260         instance->create_attribute<mi::IBoolean>( "visible", "Boolean"));
261     visible->set_value( true);
262 
263     mi::base::Handle<mi::IRef> material( instance->create_attribute<mi::IRef>( "material", "Ref"));
264     material->set_reference( "red_material");
265 
266     transaction->store( instance.get(), "instance_red");
267 
268     // And attach the instance to the root group
269     mi::base::Handle<mi::neuraylib::IGroup> group(
270         transaction->edit<mi::neuraylib::IGroup>( rootgroup));
271     group->attach( "instance_red");
272 
273     // Create the blue object as a Loop-subdivision surface based on the red tetrahedron
274     transaction->copy( "mesh_red", "mesh_blue");
275     mi::base::Handle<mi::neuraylib::ITriangle_mesh> mesh_blue(
276         transaction->edit<mi::neuraylib::ITriangle_mesh>( "mesh_blue"));
277     loop_subdivision( mesh_blue.get());
278     loop_subdivision( mesh_blue.get());
279     loop_subdivision( mesh_blue.get());
280     loop_subdivision( mesh_blue.get());
281 
282     // Create the instance for the blue object
283     instance = transaction->create<mi::neuraylib::IInstance>( "Instance");
284     instance->attach( "mesh_blue");
285 
286     // Set the transformation matrix, the visible attribute, and the material
287     matrix = mi::Float64_4_4( 1.0);
288     matrix.translate( 0.4, -1.5, -1.6);
289     matrix.rotate( 0.0, 1.25 * MI_PI_2, 0.0);
290     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);
291     matrix *= matrix_scale;
292     instance->set_matrix( matrix);
293 
294     visible = instance->create_attribute<mi::IBoolean>( "visible", "Boolean");
295     visible->set_value( true);
296 
297     material = instance->create_attribute<mi::IRef>( "material", "Ref");
298     material->set_reference( "blue_material");
299 
300     transaction->store( instance.get(), "instance_blue");
301 
302     // And attach the instance to the root group
303     group = transaction->edit<mi::neuraylib::IGroup>( rootgroup);
304     group->attach( "instance_blue");
305 }
306 
307 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path)
308 {
309     // Configure the neuray library. Here we set the search path for .mdl files.
310     mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration(
311         neuray->get_api_component<mi::neuraylib::IRendering_configuration>());
312     check_success( rendering_configuration.is_valid_interface());
313     check_success( rendering_configuration->add_mdl_path( mdl_path) == 0);
314 
315     // Load the FreeImage, Iray Photoreal, and .mi importer plugins.
316     mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration(
317         neuray->get_api_component<mi::neuraylib::IPlugin_configuration>());
318 #ifndef MI_PLATFORM_WINDOWS
319     check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0);
320     check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0);
321     check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0);
322 #else
323     check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0);
324     check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0);
325     check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0);
326 #endif
327 }
328 
329 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray)
330 {
331     // Get the database, the global scope of the database, and create a transaction in the global
332     // scope for importing the scene file and storing the scene.
333     mi::base::Handle<mi::neuraylib::IDatabase> database(
334         neuray->get_api_component<mi::neuraylib::IDatabase>());
335     check_success( database.is_valid_interface());
336     mi::base::Handle<mi::neuraylib::IScope> scope(
337         database->get_global_scope());
338     mi::base::Handle<mi::neuraylib::ITransaction> transaction(
339         scope->create_transaction());
340     check_success( transaction.is_valid_interface());
341 
342     // Import the scene file
343     mi::base::Handle<mi::neuraylib::IImport_api> import_api(
344         neuray->get_api_component<mi::neuraylib::IImport_api>());
345     check_success( import_api.is_valid_interface());
346     mi::base::Handle<const mi::neuraylib::IImport_result> import_result(
347         import_api->import_elements( transaction.get(), "file:main.mi"));
348     check_success( import_result->get_error_number() == 0);
349 
350     // Add two triangle meshes to the scene
351     setup_scene( transaction.get(), import_result->get_rootgroup());
352 
353     // Create the scene object
354     mi::base::Handle<mi::neuraylib::IScene> scene(
355         transaction->create<mi::neuraylib::IScene>( "Scene"));
356     scene->set_rootgroup(       import_result->get_rootgroup());
357     scene->set_options(         import_result->get_options());
358     scene->set_camera_instance( import_result->get_camera_inst());
359     transaction->store( scene.get(), "the_scene");
360 
361     // Create the render context using the Iray Photoreal render mode
362     scene = transaction->edit<mi::neuraylib::IScene>( "the_scene");
363     mi::base::Handle<mi::neuraylib::IRender_context> render_context(
364         scene->create_render_context( transaction.get(), "iray"));
365     check_success( render_context.is_valid_interface());
366     mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>());
367     scheduler_mode->set_c_str( "batch");
368     render_context->set_option( "scheduler_mode", scheduler_mode.get());
369     scene = 0;
370 
371     // Create the render target and render the scene
372     mi::base::Handle<mi::neuraylib::IImage_api> image_api(
373         neuray->get_api_component<mi::neuraylib::IImage_api>());
374     mi::base::Handle<mi::neuraylib::IRender_target> render_target(
375         new Render_target( image_api.get(), "Color", 512, 384));
376     check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0);
377 
378     // Write the image to disk
379     mi::base::Handle<mi::neuraylib::IExport_api> export_api(
380         neuray->get_api_component<mi::neuraylib::IExport_api>());
381     check_success( export_api.is_valid_interface());
382     mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0));
383     export_api->export_canvas( "file:example_triangle_mesh.png", canvas.get());
384 
385     transaction->commit();
386 }
387 
388 int main( int argc, char* argv[])
389 {
390     // Collect command line parameters
391     if( argc != 2) {
392         std::cerr << "Usage: example_triangle_mesh <mdl_path>" << std::endl;
393         keep_console_open();
394         return EXIT_FAILURE;
395     }
396     const char* mdl_path = argv[1];
397 
398     // Access the neuray library
399     mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
400     check_success( neuray.is_valid_interface());
401 
402     // Configure the neuray library
403     configuration( neuray, mdl_path);
404 
405     // Start the neuray library
406     mi::Sint32 result = neuray->start();
407     check_start_success( result);
408 
409     // Do the actual rendering
410     rendering( neuray);
411 
412     // Shut down the neuray library
413     check_success( neuray->shutdown() == 0);
414     neuray = 0;
415 
416     // Unload the neuray library
417     check_success( unload());
418 
419     keep_console_open();
420     return EXIT_SUCCESS;
421 }