Le but est, au moyen d'un exemple simple, le rebond d'une balle, nous semblant aujourd'hui évident, de comprendre en partie le déroulement mathématique, physique ou algorithmique à la base de l'animation procédurale.
“En synthèse d'image numérique, l'animation procédurale est une animation d'objets virtuels par génération en temps réel de mouvements, selon un ensemble de règles procédurales. L'animateur 3D spécifie les règles (par exemple des lois du monde physique décrites par des relations mathématiques), et les conditions initiales avant de lancer la simulation.” Cf. Wikipedia
Je rajouterais que l'intérêt majeur de l'animation procédurale est la génération de schémas complexes - voir très complexes - à partir d'outils et règles simples, voir très simples.
En animation par key frames nous définissons la valeur d'un attribut à différents temps, et le logiciel interpole cette valeur entre les clés.
En animation procédurale rien de tout cela. Nous définissons les conditions initiales en plaçant des objets dans l'espace et en définissant leurs propriétés, ainsi que les règles de la simulation en ajoutant des forces ou des conditions :
Dans l'illustration ci-dessus, les conditions initiales sont :
Les règles sont :
Lorsque nous lançons la simulation, la sphère chute et rebondis sur le cube.
De par les conditions et les règles nous comprenons pourquoi, toute la question est désormais : comment?
Pour comprendre comment cela fonctionne, nous allons programmer l'animation dynamique de notre balle en partant de zéro.
L'animation procédurale nécessite la maitrise d'un objet mathématique merveilleux : le vecteur.
Avant d'aller plus loin, je vous conseille donc fortement de lire l'article sur Les vecteurs.
Une fois les vecteurs assimilés, l'animation procédurale n'aura plus de secrets pour vous. Et cela tombe bien car on passe à la pratique, avec Maya et son Script Editor.
Les conditions initiales pour avoir une balle qui rebondis sont… suspense…
Normalement cela ne devrait pas poser de problèmes, c'est des chose que nous savons faire.
# on importe le module maya.cmds from maya.cmds import * # on définis une taille taille = 5 # et on définis que notre balle # est une sphère de cette taille balle = polySphere(radius=taille)
Maintenant que l'on a les vecteurs dans la poche, plus question de faire ça :
position_x = 0 position_y = 50 position_z = 0
Maintenant on fait :
position = [0, 50, 0] # position de départ
Notre variable position est donc un vecteur de coordonnées, indiquant une position dans l'espace.
Nous pourrions faire :
vitesse = 1
Et nous aurions une valeur de vitesse générale. Mais regardez ce qu'apportent ici les vecteurs :
vitesse = [1, 0, 0] # vitesse de départ
Notre variable vitesse est ici un vecteur directionnel indiquant à la fois la direction, le sens, et l'intensité de notre vitesse dans l'espace! En plus d'être d'une “intensité” de 1 (la longueur de notre vecteur), notre vitesse est ici dirigée vers la droite. C'est la représentation mathématique d'une force physique.
from maya.cmds import * # conditions initiales taille = 5 balle = polySphere(radius=taille) position = [0, 50, 0] vitesse = [1, 0, 0]
Très bien, mais si on lance notre code, à part créer une balle il ne se passe rien. Normal, il n'y a encore aucune notion de mouvement.
Le vecteur vitesse nous indique donc vers où doit se diriger notre balle. Il faut que notre balle se déplace de sa position à… sa position plus sa vitesse. Il nous suffit d'ajouter au vecteur position le vecteur vitesse. Pour cela on utilise la fonction précédemment citée.
from maya.cmds import * # fonctions def vectorAdd(a, b): return [a[0] b[0], a[1] b[1], a[2] b[2]] # conditions initiales taille = 5 balle = polySphere(radius=taille) position = [0, 50, 0] vitesse = [1, 0, 0] # mouvement print position # on affiche la position avant # on change la position selon la vitesse position = vectorAdd(position, vitesse) print position # on affiche la position après # et on déplace la balle move(position[0], position[1], position[2],balle)
Résultat :
[0, 50, 0] [1, 50, 0]
La balle a bien avancé sur la droite, comme indiqué par le vecteur vitesse, par contre elle ne l'a fait qu'une fois, quand on a lancé le script. Pour visualiser un mouvement, il faut répéter cette opération 20, 30 fois par secondes. Et pour cela nous n'avons qu'à mettre le calcul du mouvement dans une boucle.
A la suite des conditions initiales, faisons une boucle :
from maya.cmds import * # fonctions def vectorAdd(a, b): return [a[0]+b[0], a[1]+b[1], a[2]+b[2]] # conditions initiales taille = 5 balle = polySphere(radius=taille) position = [0, 50, 0] vitesse = [1, 0, 0] # animation time = 300 # Durée de l'animation en frame (nombre de tour de la boucle) # Pour chaque frame de l'animation... for frame in range(1, time): # On place la timeline à la frame actuelle (cela permet # d'actualiser le viewport et donc de voir l'animation) currentTime(frame) # mouvement position = vectorAdd(position, vitesse) move(position[0], position[1], position[2], balle)
Et voila une superbe simulation de balle dans le vide!
Nous avons la base fonctionnelle de notre simulation, nous pouvons désormais définir des règles permettant de décrire l'environnement de la simulation. Dans l'exemple de la balle qui rebondis nous allons mimer certaines lois physiques, mais il est possible de tout imaginer.
Que se passe-t-il concrètement quand une balle rebondis? Elle se dirige vers le sol, change de direction à son contact puis peu à peu s'y re-dirige, change de direction à son contact, etc…
Si la balle se dirige vers le sol, c'est que quelque chose l'y pousse : la gravité.
Qu'est-ce que la gravité? Cela a l'air d'une question idiote, mais la réponse peut ne pas l'être. Car si l'on observe en infiniment grand la théorie de la relativité générale nous parle d'une “manifestation de la déformation de la géométrie de l'espace-temps sous l'influence des objets qui l'occupent”, alors qu'en infiniment petit les théories quantiques de la gravitation nous disent que “l'équation des ondes gravitationnelles peut s'interpréter comme celle de la propagation d'une particule”. A notre échelle cela sera heureusement plus simple : c'est une force dirigée vers le bas.
Une force dirigée, comme notre vitesse, on sait que cela se représente simplement avec un vecteur directionnel. Pour définir une nouvelle force, après les conditions initiales nous n'avons donc qu'à faire :
# la gravité est seulement # une force dirigée vers le bas gravité = [0, -0.1, 0]
La direction de notre objet doit maintenant dépendre de plusieurs forces : sa direction préalable plus la gravité. Pour qu'une force affecte un objet, il suffit donc de l'ajouter au vecteur directionnel. On dit que le vecteur vitesse de l'objet est la résultante des forces qui lui sont appliquées.
from maya.cmds import * # fonctions def vectorAdd(a, b): return [a[0] b[0], a[1] b[1], a[2] b[2]] # conditions initiales taille = 5 balle = polySphere(radius=taille) position = [0, 50, 0] vitesse = [1, 0, 0] # environnement gravite = [0, -0.1, 0] # animation time = 100 for frame in range(1, time): currentTime(frame) # regles vitesse = vectorAdd(vitesse, gravite) # mouvement position = vectorAdd(position, vitesse) move(position[0], position[1], position[2], balle)
Rien de plus simple, non? En deux lignes, nous pouvons rajouter une force dirigée dans n'importe quel sens (une gravité vers le haut, pourquoi pas) et de n'importe quelle intensité (une gravité divisée par dix, et voila la lune).
Nous allons maintenant faire rebondir la balle lorsqu'elle touche le sol.
Disons que le sol est horizontal, à une certaine hauteur. La balle touche alors le sol si sa position en Y moins sa taille est inférieure ou égale à la hauteur du sol, et dans ce cas la balle ne chute pas. Faisons donc une condition pour nos règles :
# regles # si la position de la balle en Y moins sa taille # est inférieure ou égale à la hauteur du sol if position[1] - taille <= sol: # il y a contact, la balle ne chute pas vitesse = [0, 0, 0] # on stoppe sa vitesse else: # sinon la balle chute vitesse = vectorAdd(vitesse, gravite)
La balle chute, et une fois… dans le sol, s'arrête. Notre condition vérifie si la balle est déjà en contact ou dans le sol, il est donc un peu trop tard pour réagir. Pour palier à cela, nous allons vérifier si la balle serait dans le sol à la frame suivante si elle continuait sa course.
# regles # On calcule la prochaine position de la balle nextPosition = vectorAdd(position, vitesse) # si la prochaine position de la balle en Y moins sa taille # est inférieure ou égale à la hauteur du sol if nextPosition[1] - taille <= sol: # il y a contact, la balle ne chute pas vitesse = [0, 0, 0] # on stoppe sa vitesse else: # sinon la balle peut en effet chuter vitesse = vectorAdd(vitesse, gravite)
Lorsque la balle touche le sol, de par son élasticité et parce qu'elle ne peut traverser ce dernier, elle s'écrase. Une fraction de seconde plus tard, tentant de retrouver sa forme originale, elle est propulsée vers le haut. Ceci se représente par une force de réaction, perpendiculaire à la surface de contact.
Dans notre cas la force de réaction est verticale et correspond à :
[0, -vitesse[1] * 2, 0]
Dans notre condition, quand nous savons que la balle touche le sol, au lieu de la stopper nous allons calculer cette force de réaction et l'ajouter à la vitesse :
# regles nextPosition = vectorAdd(position, vitesse) if nextPosition[1] - taille <= sol: # il y a contact et se crée une force de réaction reaction = [0, -vitesse[1] * 2, 0] # qui s'ajoute au vecteur vitesse vitesse = vectorAdd(vitesse, reaction) else: # sinon la balle peut en effet chuter vitesse = vectorAdd(vitesse, gravite)
Et voila notre balle qui rebondis!
Vous pouvez remarquer que quel que soit le nombre de rebonds effectué par la balle, jamais elle ne s'arrête. Nous avons en effet créé ici une balle rebondissante parfaite, qui à chaque rebond remonte exactement aussi haut qu'au départ, sans jamais s'essouffler.
Pour changer cela il n'y a qu'à modifier l'intensité de la force de réaction.
L'efficience du rebond d'une matière étant un réel paramètre de description physique (en anglais “bounciness”), si l'on veut être plus précis cela correspond à :
# regles nextPosition = vectorAdd(position, vitesse) if nextPosition[1] - taille <= sol: # rebond bounciness = 0.75 reaction = [0, -vitesse[1] * (1 + bounciness), 0] vitesse = vectorAdd(vitesse, reaction) else: # chute vitesse = vectorAdd(vitesse, gravite)
Quelques indications :
Si maintenant la taille du rebond décroit avec le temps, le mouvement de la balle initié par la force de départ est lui toujours perpétuel : même sans rebondir, elle continue de glisser sur le sol. Il nous manque un paramètre de friction. L'effet de la friction est de freiner un objet lorsqu'il y a contact. Qui dit freiner dit diminuer sa vitesse.
Il nous faut donc réduire la longueur du vecteur vitesse quand il y a contact avec le sol. Comme vu dans le chapitre sur les vecteurs, il s'agit d'une simple multiplication du vecteur avec un nombre. Je récupère donc la fonction à cet effet :
def vectorMult(a, b): return [a[0]*b, a[1]*b, a[2]*b]
Dans notre condition, après avoir calculé la force de réaction et la nouvelle vitesse, nous n'avons plus qu'à multiplier cette dernière :
# regles nextPosition = vectorAdd(position, vitesse) if nextPosition[1] - taille <= sol: # rebond bounciness = 0.75 friction = 0.95 reaction = [0, -vitesse[1] * (1 + bounciness), 0] vitesse = vectorAdd(vitesse, reaction) vitesse = vectorMult(vitesse, friction) else: # chute vitesse = vectorAdd(vitesse, gravite)
Quelques indications :
from maya.cmds import polySphere, currentTime, move # fonctions def vectorAdd(a, b): '''Somme de deux vecteurs''' return [a[0] + b[0], a[1] + b[1], a[2] + b[2]] def vectorMult(a, b): '''Multiplication de vecteur par un nombre''' return [a[0] * b, a[1] * b, a[2] * b] # conditions initiales taille = 5 balle = polySphere(radius=taille) position = [0, 50, 0] vitesse = [1, 0, 0] bounciness = 0.75 friction = 0.95 # environnement gravite = [0, -0.1, 0] sol = 0 # animation time = 100 for frame in range(1, time): # regles positionGravite = vectorAdd(position, vitesse) if positionGravite[1] - taille <= sol: # rebond reaction = [0, -vitesse[1] * (1 + bounciness), 0] vitesse = vectorAdd(vitesse, reaction) vitesse = vectorMult(vitesse, friction) else: # chute vitesse = vectorAdd(vitesse, gravite) # mouvement position = vectorAdd(position, vitesse) currentTime(frame) move(position[0], position[1], position[2], balle)
Vous remarquerez que l'on utilise seulement trois fonctions de Maya : polySphere, currentTime et move, qui servent uniquement à visualiser le résultat de nos calculs. Cette procédure est donc transposable à tout langage, tout logiciel permettant de programmer (Flash, Processing, Blender…) et n'est en rien rattaché à Maya. Il s'avère par la même occasion que les temps de calculs sont infiniment plus rapides que si l'on avait usé et abusé des fonctions internes au logiciel.
Voila, j'espère vous avoir fait découvrir deux ou trois choses ;) .
Si vous avez des questions, des remarques, des suggestions, n'hésitez pas à m'en faire part.
~~DISCUSSION~~