Creating triangle meshes
This topic introduces:
- Core concepts about creating triangle meshes, storing them as database elements, adding them to a scene graph, and retrieving and editing them:
- 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 }