Creating a viewer with SFE

08 Jun Creating a viewer with SFE

When trying to use SOFA in your own application, one of the important problems you have to face is the visualisation of your simulation.

SFE brings you two ways to solve that problem:
– using the SOFA internal code for OpenGL rendering;
– using your own render engine, after retrieving in SOFA all the necessary information.

The second method is a more complex but more versatile, as it allows you to use your own render engine rather than the default OpenGL pipeline of SOFA.

 

Using SOFA OpenGL rendering

 

The first method can be used each time you have an OpenGL context opened and current. Just call the draw() method on sfe::Simulation and the scene will be displayed exactly as it is done in runSofa. Of course, you will have to set-up yourself the position and characteristics of the camera and the lights, which are not included in the simulation scene.

1
2
3
4
5
...
// Place this call somewhere where the target OpenGL context is current
sfe::Simulation simulation = sfe::getSimulation();
simulation->draw();
...

You will also be able to render all the inner objects and interaction, given you have set correctly the corresponding flags in the scene context. Of course, if you place several VisualStyle objects in your graph, you will be able to control things in a finer way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
// Get the simulation root Place this code to set the display flags
sfe::Node root = sfe::getSimulation().root();
sfe::Object visualStyle = root.objectOfType( "VisualStyle" );
sfe::Data displayFlags = visualStyle.data( "displayFlags" );
if( displayFlags.isValid() )
{
  // the value of the data displayFlags is a combination of the following strings:
  // showAll, hideAll, showVisual, hideVisual, showVisualModels, hideVisualModels,
  // showBehavior, hideBehavior, showBehaviorModels, hideBehaviorModels, showForceFields, hideForceFields,
  // showInteractionForceFields, hideInteractionForceFields, showMapping, hideMapping, showMappings, hideMappings
  // showMechanicalMappings, hideMechanicalMappings, showCollision, hideCollision, showCollisionModels, hideCollisionModels
  // showBoundingCollisionModels, hideBoundingCollisionModels, showOptions, hideOptions, showRendering, hideRendering
  // showNormals, hideNormals, showWireframe hideWireframe
  // You can set it the following way:
  std::ostringstream oss;
  oss << ( showVisual ? "show" : "hide") << "VisualModels ";
  oss << ( showBehavior ? "show" : "hide") << "BehaviorModels ";
  oss << ( showForceFields ? "show" : "hide") << "ForceFields ";
  oss << ( showInteractions ? "show" : "hide") << "InteractionForceFields ";
  oss << ( showCollisions ? "show" : "hide") << "CollisionModels ";
...
 displayFlags.set( oss.str() );
}

 

Using your own render engine

 

The second method is a bit more complex but more versatile. The main idea is to use SFE to get the position and topology of all the visual models of the scene. We then transmit that information to the render engine.
One of its interests is that it allows you to use your own render engine: VTK, vgSDK, Horde3D, … You can even use it through the TCP server or on non OpenGL systems.

For every visual object in the scene, we will fill the following structure that contains all the information needed for the rendering (topology, normals, position, …).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ObjectInfo
{
  // Arrays to store the data extracted from SOFA
  std::vector< std::array< float, 3 > > positions;
  std::vector< std::array< float, 3 > > normals;
  std::vector< std::array< unsigned int, 3 > > triangles;
  std::vector< std::array< unsigned int, 4 > > quads;
  std::array< unsigned int, 4 > color;
  std::vector< std::array< float, 2 > > texCoords;
  std::string textureName;
 
  // Proxies to some SOFA data
  sfe::Data d_position, d_normal;
};

The first step is to retrieve handler for all the visual models in the scene:

1
2
3
4
...
sfe::Node root = sfe::getSimulation().root();
sfe::Node::ObjectList visualModels = root.findObjects( "VisualModelImpl" );
...

Now we fill the ObjectInfo structures:

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
...
std::vector< ObjectInfo > objectList;
for ( auto& visualModel : visualModels )
{
  ObjectInfo object;
 
  // Get the positions
  // Some objects use indices to construct the vertices and normals lists.
  // For those objects, vertices positions are stored in "vertices" and not in "positions"
  // To know in which case we are, we have to check whether the data "vertPosIdx" is empty or not
  std::vector< int > vertPosIdx;
  auto posIdData = visualModel.data( "vertPosIdx" );
  if ( posIdData )
    posIdData.get( vertPosIdx );
  if( vertPosIdx.empty() )
    object.d_position = visualModel.data( "position" );
  else
    object.d_vertices = visualModel.data( "vertices" );
 
  // Get the normals
  object.d_normal = visualModel.data( "normal" );
 
  // Get the list of triangles
  auto trianglesData = visualModel.data( "triangles" );
  if ( trianglesData )
    trianglesData.get( object.triangles );
 
  // Get the list of quads
  auto quadsData = visualModel.data( "quads" );
  if ( quadsData )
    quadsData.get( object.quads );
 
  // Get the textures coordinates
  auto textureData = visualModel.data( "texcoords" );
  if ( textureData )
    textureData.get( object.texCoords );
 
  // Get the name of the texture file
  // Note that getting the actual texture file name might be a bit more complex in some cases.
  auto textureNameData = visualModel.data( "texturename" );
  if ( textureNameDataData )
    textureNameData.get( object.textureName );
 
  // Get the diffuse color
  auto matData = visualModel.data( "material" );
  std::string material;
  matData.get( material );
  object.color = getColor( material );
 
  // If we have enough information, we can add the object it to the render list
  if ( object.d_position && object.d_normal
      && ( !object.triangles.empty() || !object.quads.empty() ) )
     objectList.push_back(object);
}
...

We now have all the information necessary to the render engine: positions, normals, topology, colour, texture, … If we do not have to deal with topological changes, the only things we have to update at each time step are the positions and normals of your objects:

1
2
3
4
5
6
7
8
void updatePositions(sfe::Node::ObjectList& objects)
{
  for ( auto& object : objects )
  {
    object.d_position.get( object.positions ); // Directly get the new value of the vertices
    object.d_normal.get(object.normals); // Directly get the new value of the normals
  }
}

With this method, displaying the internal objects of SOFA (collision models, interactions, …) is not straightforward. In case you want to display them, you will have to a code a similar process for each kind of object: find all the objects of the corresponding type in the scene graph, retrieve the topological and geometrical information and send them to the render engine.

No Comments

Post A Comment