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::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 }