Newer
Older
# TP d'Animation de personnage (M1)
## TP partie 1 : affichage
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Vous allez créer un module Skeleton.h/.cpp (ce code n'est qu'indicatif, vous êtes libre de vos structures de données). Cette classe va stocker un tableau de toutes les articulations (SkeletonJoint) du squelette et pour chaque articulation stocke l'identifiant de l'articulation parent et la matrice de passage de l'articulation vers le monde.
Le fichier est déjà présent dans le code départ avec des TODO à compléter :
```
class Skeleton
{
public:
struct SkeletonJoint
{
int m_parentId; // Le numéro du père dans le tableau de CAJoint de CASkeleton
Transform m_l2w; // La matrice passant du repère de l'articulation vers le monde
};
Skeleton() {}
//! Créer un squelette ayant la même structure que définit dans le BVH c'est à dire
//! creer le tableau de SkeletonJoint à la bonne taille, avec les parentId initialsé pour chaque case
void init(const BVH& bvh);
//! Renvoie la position de l'articulation i en multipliant le m_l2w par le Point(0,0,0)
Point getJointPosition(int i) const;
//! Renvoie l'identifiant de l'articulation père de l'articulation numéro i
int getParentId(const int i) const;
//! Renvoie le nombre d'articulation
int numberOfJoint() const;
//! Positionne ce squelette dans la position n du BVH.
//! Assez proche de la fonction récursive (question 1), mais range la matrice (Transform)
//! dans la case du tableau. Pour obtenir la matrice allant de l'articulation local vers le monde,
//! il faut multiplier la matrice allant de l'articulation vers son père à la matrice du père allant de
//! l'articulation du père vers le monde.
void setPose(const BVH& bvh, int frameNumber);
protected:
//! L'ensemble des articulations.
//! Remarque : la notion de hiérarchie (arbre) n'est plus nécessaire ici,
//! pour tracer les os on utilise l'information "parentID" de la class CAJoint
std::vector<SkeletonJoint> m_joint;
};
```
Dans le Viewer vous devez :
* Déclarer un Skeleton en variable de la classe
* Ecrire une fonction qui fait l'affichage
```
void CharAnimViewer::skeletonDraw(const Skeleton& ske);
```
* Initaliser l'instance de Skeleton dans la fonction init
* Appeler setPose dans la fonction update
Remarques
* On sépare bien l'affichage de la gestion du squelette pour pouvoir réutiliser le code Skeleton avec une autre librairie d'affichage.
* On ne s'occupe pas du temps pour l'instant mais uniquement du numéro de la posture.
* Vous pouvez trouvez des BVH dans le répertoire data du code de départ. Notamment le fichier robot.bvh pour debuguer.
## TP partie 2 : Contrôleur d'animation
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
Ecrivez une class CharacterControler qui à partir des touches claviers contrôlera le déplacement d'un personnage. Dans une 1er temps faites juste déplacer une boule : accélérer, freiner, tourner à droite, tourner à gauche, sauter. Ce contrôleur comportera une position et une vitesse. La vitesse sera modifiée par les flèches (ou un pad) et la position sera mise à jour dans la fonction update du Viewer en utilisant le paramètre "delta" recu par la fonction update.
Une classe de Controller peut ressembler à ceci.
```
class CharacterController
{
public:
CharacterController() : ... {}
void update(const float dt);
void turnXZ(const float& rot_angle_v);
void accelerate(const float& speed_inc);
void setVelocityMax(const float vmax);
const Point position() const;
const Vector direction() const;
float velocity() const;
const Transform& controller2world() const { return m_ch2w; }
protected:
Transform m_ch2w; // matrice du character vers le monde
// le personnage se déplace vers X
// il tourne autour de Y
// Z est sa direction droite
float m_v; // le vecteur vitesse est m_v * m_ch2w * Vector(1,0,0)
float m_vMax; // ne peut pas accélérer plus que m_vMax
};
```
Dans un 2e temps, votre contrôleur comportera également une série d'animation bvh : attendre, marcher, courir, et donner un coup de pied.
En fonction de l'action que veut faire le joueur appuyant sur des touches vous changerez d'animation. Vous coderez la machine à états
finis (FiniteStateMachine) de l'image ci-dessous. Les cercles sont les états (l'animation en train d'être jouée), les rectangles rouges sont
les éventements et les carrés bleus sont les actions à effectuer (fonction de la classe). Ce changement se fera brutalement. Ne vous
occupez pas non plus des pieds qui glissent sur le sol. Un meilleur contrôle peut-être fait la construction d'un graphe d'animation.
## TP partie 3 : Transition et graphe d'animation
a) Pour améliorer le réalisme, il serait bon de faire les transitions entre deux animations en choisissant deux poses des animations qui sont
proches. Pour cela il faut calculer la distance entre deux poses d'animations (Voir les infos dans le sujet Graphe d'animation).
b) Pour aller encore plus loin, on peut construire un automate de manière complètement automatique. On appelle alors ceci un graphe d'animation. [Voir le sujet détaillé de cette 3e partie ici.](../tp_motiongraph)
a.bis) Indépendamment de la machine à état ou du graphe, si vous voulez gérer le temps de manière plus juste, il faudrait récupérer le temps
réellement écoulé depuis l'affichage précédent. Ceci vous fera ne vous fera pas tomber précisément sur une frame stocké dans le clip (BVH). Il faudra donc interpoler entre les 2 frames. Le résultat sera de l'ordre du détail lors de l'affichage mais si vous voulez que votre moteur
d'animation tourne sur toutes les machines indépendamment du CPU, il faut le faire. Cette interpolation peut également servir pour passer d'un clip à un autre.
## TP partie 4 : Interaction entre le personnage et des sphères physiques
Pour bien comprendre l'animation physique voir la partie de F. Zara.
Dans la fonction init de la class CharAnimViewer indiquez un nombre de particules non nul :
```
m_world.setParticlesCount( 10 );
```
Dans la fonction render, il faut afficher les particules en dé-commentant cette ligne :
```
m_world.draw();
```
Vous verrez alors les particules s'afficher, mais elles ne seront pas animées. Pour calculer la physique sur les particules, il y a deux
classes **PhysicalWorld** et **Particle**. Regardez le fichier Particles.h. Il faudra compléter les fonctions update, collision et groundCollision :
```
void update(const float dt = 0.1f)
void groundCollision()
void collision(const Point& p, const float radius)
```
Le code de update doit mettre à jour la vitesse avec l'équation `F=m.a` où `a = dv/dt`
Et mettre à jour la position avec l'équation habituelle `p = p + v.t`
Pour plus de détail, voir la partie de F. Zara.
Pour ajouter l'interaction entre votre personnage et des boules/sphères se trouvant dans l'environnement, il faut appeler `PhysicalWorld::collision` depuis `CharAnimViewer::update` en parcourant toutes les articulations du personnage. Dans un 1er temps, vous pouvez juste faire disparaitre les particules touchées en faisant passer le rayon de la particule à -1 et faire en sorte que les particules de rayon négatif ne soient pas affichées. Puis ajoutez dans `Particle::collision` du code pour déplacer les particules en collisions (résoudre les collisions) et changer leur vecteur vitesse.