L3IF Images
année 2005-2006
http://www710.univ-lyon1.fr/~jciehl/

TD4 - Modélisation Récursive



Mise en jambes

    Une technique courante, utilisée dans tous les jeux video, par exemple, consiste à décrire l'animation d'un personnage comme un squelette habillé. Seul le squelette est animé, son habillage (aussi bien le "volume" du corps que les vêtements) est ensuite déformé pour suivre les mouvements du squelette.

    Un squelette est une hiérarchie de repères : le bassin, le dos, l'épaule gauche, le bras gauche, l'avant bras gauche, le poignet gauche, la main gauche, les doigts de la main gauche, etc. Chaque élément est connecté à son suivant par une articulation représentée par une orientation. Par exemple, le coude permet d'orienter l'avant bras par rapport au bras, et comme la description est hiérarchique, le bras est lui aussi orienté par rapport à l'épaule, qui est elle même orientée et/ou située par rapport au dos, et enfin, au bassin.

modelisation recursive

    Définir une animation avec ce type de représentation revient donner à chaque instant l'orientation / position de chaque articulation du squelette.

    exercice
    Modélisez un bras, un avant bras et une main. Chaque élément pourra être "habillé" par un cube déformé.
    Vérifiez que vous pouvez orienter chaque articulation indépendament des autres et que l'orientation de l'articulation parente se propage bien le long de la hiérarchie.

    exercice
    Animez votre bras (en modifiant régulièrement les orientations des articulations)

    exercice
    Stockez toutes vos orientations dans un tableau pour quelques instants d'une animation.
    Comment interpoler des images intermédiaires (cf TD3) ?



Partie 1 : Motion Capture


    Il existe plusieurs manières de contrôler une animation, une méthode courament utilisée consiste à enregistrer des personnes et à retrouver l'orientation de chaque articulation à chaque instant (Motion capture).

    Un exemple de marche est visible
    Un millier d'exemples sont disponibles sur le site http://www.e-motek.com

    Les animations sont décrites par un fichier texte (format BVH) qui décrit le squelette (hiérarchie de segments orientés) ainsi que les valeurs des orientations pour un ensemble d'instants.

    Le format BVH est décrit dans le document "Motion Capture File Format Explained" (pdf)

    Voici un exemple de fichier walk.bvh. Cet exemple décrit un squelette sous la forme d'un arbre :

  joint 'Hips'
        joint 'LeftUpLeg'
            joint 'LeftLowLeg'
                joint 'LeftFoot'
                    joint 'ltoes'
                        joint 'Site'

        joint 'RightUpLeg'
            joint 'RightLowLeg'
                joint 'RightFoot'
                    joint 'rtoes'
                        joint 'Site'

        joint 'upperback'
            joint 'Chest'
                joint 'Neck'
                    joint 'Head'
                        joint 'Site'
                joint 'Chest'
                    joint 'LeftUpArm'
                        joint 'LeftLowArm'
                            joint 'LeftHand'
                                joint 'lfingers'
                                    joint 'Site'
                joint 'Chest'
                    joint 'RightUpArm'
                        joint 'RightLowArm'
                            joint 'RightHand'
                                joint 'rfingers'
                                    joint 'Site'


    Voici les premières lignes du fichier décrivant le bassin et la jambe gauche :

HIERARCHY
ROOT Hips
{
OFFSET 0.00 0.00 0.00
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
JOINT LeftUpLeg
{
OFFSET -0.0000 0.0000 -4.0122
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT LeftLowLeg
{
OFFSET 0.0000 -17.8667 -3.1504
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT LeftFoot
{
OFFSET 0.0000 -16.3749 -2.8873
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT ltoes
{
OFFSET 5.9227 -3.3675 -0.5938
CHANNELS 3 Zrotation Xrotation Yrotation
End Site
{
OFFSET 3.9580 0.0000 0.0000
}
}
}
}
}
}

    Chaque élément 'JOINT' indique le changement de repère liant deux articulations. La partie 'OFFSET' indique la translation alors que la partie 'CHANNELS' précise dans quel ordre composer les rotations. Les valeurs des rotations et des translations sont données dans la deuxième partie du fichier pour chaque instant de l'animation :

MOTION
Frames: 191
Frame Time: 0.033333

-64.2589 38.9445 65.3274 // hips Xp Yp Zp
6.1824 3.5077 40.8964 // hips Zr Xr Yr
1.3960 -15.6239 2.2344 // LeftUpLeg Zr Xr Yr
-37.5674 -2.1008 6.1715 // LeftLowLeg Zr Xr Yr
11.8681 0.5382 1.6109 // LeftFoot Zr Xr Yr
-3.8413 -0.0227 0.6768 // ltoes Zr Xr Yr

etc ...
    exercice :
    Utilisez la librairie bvh, décrite en annexe, pour charger les données d'animation et affichez le premier instant de l'animation.

    exercice :
    Affichez toutes les images de l'animation.

    exercice :
    Interpolez votre animation pour la rejouer à n'importe quelle vitesse.


      

Partie 2 : Habiller le squelette

    Vous avez maintenant une animation réaliste d'un ensemble de segments. Comment lui donner du volume ?

    exercice :
    Dessinez un cube déformé (cf. Mise en Jambes) ou un cylindre / ellipsoide à la place des segments du squelette.
    Le résultat est-il satisfaisant ?



Partie 3 : Simulation de mouvements


    Un système à base de ressorts permet de réaliser de nombreuses choses : quelques exemples et les explications associées sont disponibles dans "Exploring Spring Models"

    Est-il possible d'appliquer un système de ressorts à l'animation d'un squelette ?? Au volume du squelette ??
    Exemple : déformer le ventre d'un personnage en train de courir :
    "Dynamic Skining : Adding Real-Time Dynamic Effects to an Existing Character Animation"
    C. Larboulette, M.P. Cani, B. Arnaldi


    Est-il possible de déformer un tissu en utilisant un système de ressorts ?? Drapez votre squelette !



Annexe : utilisation de libbvh


    Télécharger libbvh.


    La première action consiste à charger un fichier BVH :
    MOTION *motion_new(char *filename);

    Cette fonction renvoie une structure MOTION, décrivant la totalité des données nécessaires à reproduire le mouvement capturé.

    Pour récupérer les données associées à une image particulière de l'animation (décrite par une structure MOTION) :
    int motion_get_frame(MFRAME *frame, MOTION *motion, int frame_number);

    Cette fonction remplit une structure MFRAME initialisée avec :
    MFRAME *motion_frame_init(MFRAME *frame, MOTION *motion);

    Plusieurs accesseurs permettent d'obtenir des renseignements sur l'image :
   
    son numéro de séquence :
    int motion_frame_get_id(MFRAME *frame);

    l'instant dans la séquence :
    float motion_frame_get_time(MFRAME *frame);

    la durée de l'animation :
    float motion_frame_get_motion_time(MFRAME *frame);

    Les valeurs décrivant chaque articulation du squelette sont fournies par les accesseurs get_offset, get_position et get_orientation.
    get_channel_id permet d'énumérer les articulations du squelette.

    int joint_get_channel_id(MFRAME *frame, int joint_id, int binding);
    int joint_get_offset(MFRAME *frame, int joint_id, VEC t);
    int joint_get_position(MFRAME *frame, int joint_id, VEC t);
    int joint_get_orientation(MFRAME *frame, int joint_id, VEC r, int bindings[3]);

   
    Voici un exemple montrant comment parcourir le squelette et afficher la totalité des données présentes dans l'animation.


void joint_print_binding(float v, int binding)
{
    printf("%f ", v);
    if(binding==JOINT_XROT)
        printf("X ");
    else if(binding==JOINT_YROT)
        printf("Y ");
    else if(binding==JOINT_ZROT)
        printf("Z ");
}

void joint_print(MFRAME *frame, int joint_id)
{
    VEC t;
    VEC r;
    int bindings[3];
    int i;
   
    while(joint_id!=-1)
    {
        if(frame->joints[joint_id].name!=NULL)
            printf("frame %d joint '%s'  ", frame->id, frame->joints[joint_id].name);
        else
            printf("frame %d joint 'noname' ( %d )  ", frame->id, joint_id);
   
        joint_get_offset(frame, joint_id, t);
        printf("  offset ( %f %f %f )\n", t[0], t[1], t[2]);
        joint_get_position(frame, joint_id, t);
        printf("    T ( %f %f %f )\n", t[0], t[1], t[2]);
        
        joint_get_orientation(frame, joint_id, r, bindings);
        printf("    R ( ");
            joint_print_binding(r[0], bindings[0]);
            joint_print_binding(r[1], bindings[1]);
            joint_print_binding(r[2], bindings[2]);
        printf(")");
        
        printf("\n");
        
        joint_print(frame, frame->joints[joint_id].child);
        joint_id= frame->joints[joint_id].next;
    }
}

void frame_print(MFRAME *frame)
{
    joint_print(frame, frame->root_id);
}


int main(int argc, char **argv)
{
    MOTION *motion;
    MFRAME frame;
    int i;
   
    if(argc!=2)
    {
        printf("usage : %s motion.bvh\n", argv[0]);
        return 0;
    }
   
    if(motion_load_bvh(&motion, argv[1]) < 0)
    {
        printf("failed.\n");
        return 1;
    }

    motion_print(motion);
    printf("time %f\n", motion_get_time(motion));

    //    
    motion_frame_init(&frame, motion);
   
    for(i= 0; motion_get_frame(&frame, motion, i)==0; i++)
    {
        printf("frame %d : time %f / %f\n",
            motion_frame_get_id(&frame),
            motion_frame_get_time(&frame),
            motion_frame_get_motion_time(&frame));
        
        frame_print(&frame);
    }
   
    printf("stop\n");
    motion_frame_free(&frame, 0);
    motion_free(motion, 1);
   
    return 0;
}
  

    Chaque articulation est un noeud dont les frères sont accessibles par ->next et les fils par ->child. La racine de l'animation est obtenue par ->root_id.
    Voici une manière d'écrire le parcours en profondeur :

void joint_print(MFRAME *frame, int joint_id)
{ 
    while(joint_id!=-1)
    {
        printf("frame %d joint '%s'  ", frame->id, frame->joints[joint_id].name);
  
        joint_print(frame, frame->joints[joint_id].child);

        joint_id= frame->joints[joint_id].next;
    }
}  

    avec le premier appel :

            joint_print(frame, frame->root_id);


    Les rotations sont décrites dans l'ordre précisé dans le fichier bvh (cf. Partie 1), leur "récupération" est un peu particulière :

        float valeurs[3];
        int axes[3];

        joint_get_orientation(frame, joint_id, valeurs, axes);
        joint_print_binding(valeurs[0], axes[0]);
        joint_print_binding(valeurs[1], axes[1]);
        joint_print_binding(valeurs[2], axes[2]);

    Chaque orientation et l'axe concerné sont placés dans les tableaux passés en paramètres à la fonction get_orientation.
    Les axes X, resp. Y et Z sont représentés par les constantes JOINT_XROT, JOINT_YROT, JOINT_ZROT.