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