L3IF Images
année 2005-2006
http://www710.univ-lyon1.fr/~jciehl/
TD4 - Modélisation Récursive
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.
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 ??
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.