Philosophie de SOFA

Graphe de scène

Il faut savoir que la boucle d'animation de SOFA est basée sur l'utilisation d'un graphe de scène, chaque objet de la scène étant alors un noeud de ce graphe :

  • on crée tout d'abord le noeud racine (root) du graphe de scène (sofa::simulation::tree::GNode *root = …),
  • puis les autres noeuds sont rattachés à la racine (root→addChild(GNode *child-node), root→addObject(…)).

A chaque noeud du graphe, on peut y attacher :

  • soit des objets (addObject) représentés par des rectangles dans le graphe de scène,
  • soit d'autres noeuds/enfants (addChild) représentés par des losanges dans le graphe de scène.

Les objets attachés permettent de définir :

  • les propriétés globales à l'ensemble de la scène. Ces objets sont alors directement reliés à la racine (ex : gravité, solveur utilisé pour toute la scène, etc.).
  • les propriétés spécifiques à un noeud enfant du graphe de scène (ex : propriété mécanique, masse, etc.).

Tutoriaux de SOFA

Sur le site de SOFA vous pouvez trouver les explications de deux codes simples :

  • Exemple OneParticule : exemple très simple pour comprendre la structure en graphe de scène.
  • Exemple OneTetrahedron : exemple pour comprendre le mapping qui permet de faire coincider le comportement d'un objet (ici un foie (liver)), avec un modèle mécanique (ici un tétraédre en éléments finis) : l'animation du foie suit en fait l'animation du tétraédre.

Degrés de libertés (DOF) des objets de la scène

Un des composants les plus importants est le MechanicalObject. Ce composant permet de définir les degrès de liberté d'un objet (i. e. les coordonnées de positions, vitesses, accélérations, forces, etc.). A noter que toutes les coordonnées mises dans ce composant doivent avoir le même type (par exemple vecteur 3D). Les particules de cet élément sont dessinés en blanc dans l'application SOFA (visibles en cochant la case “Behavior Model”). Ci-dessous l'extrait du code (issu du tutoriel OneParticule) permettant de définir une particule :

Déclaration du composant MechanicalObject nommé particle
    typedef sofa::defaulttype::Vec3Types MyTypes;

    sofa::component::MechanicalObject<MyTypes>* particle = new 
    sofa::component::MechanicalObject<MyTypes>;
setName permet de définir le nom qui apparaîtra dans le graphe de scène
    particle->setName("particle");
Ce composant est rattaché au noeud "particule_node" du graphe de scène (cf. tutoriel)
    particule_node->addObject(particle);
Redéfinition de la taille de particle : 1 seul élément 
    particle->resize(1);
Initialisation des coordonnées de position et de vitesse de particle
    // The point
    (*particle->getX())[0] = Vec3(0,0,0);

    // The velocity
    (*particle->getV())[0] = Vec3(0,0,0);

Masse des éléments de la scène

La masse de l'objet est déclarée à l'aide d'un autre composant. En effet, la masse ne rentre pas dans le cadre de MechanicalObjet puisque tous les composants de cet élément doivent être de la même forme, donc souvent des vecteurs 3D. Voici quelques exemples de composants permettant de définir la masse de l'objet à simuler :

  • UniformMass< DataTypes, MassType > : masse uniformément répartie entre les particules composants l'objet. On peut définir la masse totale de l'objet (setTotalMass) et la masse de chacune des particules (setMass) (identique pour toutes les particules dans ce cas). Cette structure stocke ainsi une seule valeur.
  • DiagonalMass< DataTypes, MassType > : permet de définir les masses des particules composant l'objet à partir de la définition de sa densité et de sa topologie de maillage. La méthode load permet également d'initialiser les valeurs à partir d'un fichier. Cette structure permet ainsi le stockage de différentes valeurs de masses.

Solveurs utilisés dans la boucle d'animation

Pour animer les objets de la scène, nous devons, à chaque pas de temps, calculer les nouvelles positions des particules constituants les objets. Pour cela, à chaque pas de temps, les calculs suivants doivent être effectuer :

  • Calcul des forces appliquées aux particules constituant l'objet
  • Calcul des accélérations des particules
  • Calcul des vitesses et des positions des particules
  • Affichage des nouvelles positions

Les accélérations des particules sont calculées à partir des forces, en utilisant la loi fondamentale de la dynamique.

Les vitesses et les positions sont ensuite calculées par intégration successives des accélérations. Plusieurs méthodes d'intégrations existent, divisées en deux catégories : les méthodes explicites et les méthodes implicites (nécessitant la résolution d'un système linéaire). Vous pourrez trouver dans SOFA des solveurs déjà implantés permettant d'effectuer cette phase d'intégration :

  • EulerSolver : méthode d'intégration d'Euler explicite.
  • CGImpliciteSolver : méthode d'intégration d'Euler implicite utilisant la méthode du Gradient Conjugué (CG) pour résoudre le système linéaire.

Calcul des forces des objets de la scène

Dans la boucle d'animation d'un objet, le calcul des forces est très important, puisque ce sont les forces appliquées aux différents éléments constituants l'objet qui vont déterminer son comportement au cours du temps. Par exemple dans un système masses-ressorts, l'objet est discrétisé en un ensemble de particules qui interragissent ensemble grâce à leurs connections par des ressorts. Ainsi, ce sont les forces engendrées par les ressorts qui vont déterminer le mouvement des différentes particules de l'objet.

Un certain nombre de classes ont été implantées dans le namespace sofa::component::forcefield. Vous y trouverez notamment les composants suivants :

  • TetrahedronFEMForceField : éléments finis tétraédriques.
  • BeamFEMForceField : éléments finis (poutre 6D).
  • SpringForceField : système masses-ressorts.

Selon le modèle de forces choisi, vous pourrez définir plus précisément les propriétés de votre modèle (raideur et amortissement des ressorts, propriétés élastiques (module de Young, de Poisson, etc.), …).


Topologie des maillages des objets de la scène

Le composant MeshToplogy permet de définir le maillage d'un objet de la scène. Ce maillage peut être composé de lignes, triangles, carrés, tétraédres, ou de cubes. Des méthodes permettent notamment de récupérer un élément particulier du maillage (par ex. getLine(index_type i)). Il est également possible de récupérer le nombre de chacun de ces éléments (par ex. getNbCubes ()), ou de modifier la topologie du maillage en y insérant de nouveaux éléments (par ex. addTriangle (int a, int b, int c)). Voici un exemple de code permettant de créer un simple tétraédre (issu du tutoriel OneTetrahedron) :

    sofa::component::topology::MeshTopology* topology 
     = new sofa::component::topology::MeshTopology;

    topology->addTetrahedron(0,1,2,3);

A noter que la méthode load() permet de récupérer les données mises dans un fichier .obj pour créer facilement le maillage d'un objet plus complexe.


Contraintes imposées

Le composant FixedConstraint permet d'ajouter des contraintes à un objet, contraintes qui seront fixes au cours du temps. Par exemple, voici le code permettant de fixer le point 0 d'un objet :

    sofa::component::constraint::FixedConstraint<MyTypes>* constraints = new  
      sofa::component::constraint::FixedConstraint<MyTypes>;

    node->addObject(constraints);
    constraints->addConstraint(0);

Visualisation des objets simulés

Le composant OglModel permet de créer la partie visuelle de l'objet. La méthode load() permet notamment de charger des données issues d'un fichier.obj par exemple.


Mapping

Il est parfois intéressant de décomposer un objet en deux : une partie servant au rendu de l'objet, et une autre servant aux calculs de l'animation. Il faut ensuite effectuer un “mapping” entre ces deux parties afin que le rendu suive le mouvement de la partie simulation. Plusieurs types de mapping sont disponibles dans SOFA :

  • BarycentringMapping : mapping basé sur le barycentre des deux objets.
  • SurfaceIdentityMapping : mapping entre un modèle surfacique et un modèle volumique.

Dans le tutoriel OneTetrahedron, le mapping permet de faire coincïder le mouvement du foie avec le mouvement simulé du tétraédre : le foie devient le tétraèdre.


Traitement des collisions entre les objets de la scène

Le traitement des collisions (détection et traitement) passe par plusieures étapes, chacune correspondant à un élément à ajouter à la scène (relié directement à la racine du graphe si identique pour tous les objets). Ces éléments font partie du namespace sofa::component::collision :

  • DefaultPipeline : pipeline pour le processus de détection des collisions. Il est décomposé en 4 étapes : (1) calcul de la hiérarchie de boîtes englobantes, (2) diffusion de la détection des collisions en partant de la boîte la plus grossière permettant d'obtenir au final des paires de modèles pour chaque collision détectée, (3), utilise les collisions détectées précédemment pour ???, (4) détection ???.
  • BruteForceDetection :
  • Intersection (ProximityIntersection, ContinuousIntersection, …) : permet de tester les intersections entre les éléments.
  • DefaultContactManager : gestion des contacts.
  • DefaultCollisionGroupManager :

Plusieurs modèles sont disponibles pour les collisions :

  • PointModel, LineModel, TriangleModel
  • CubeModel, SphereModel
  • SphereTreeModel, TriangleOctreeModel