Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Utilisation des forces pour mouvoir des objets, Partie II

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Stefbuet, le 14 février 2011

Introduction

Cet article est la suite de la première partie de l’utilisation de forces pour mouvoir des objets. Vous y découvrirez pourquoi la méthode employée précédemment n’est pas forcement la meilleure et une nouvelle méthode plus précise ayant l’avantage de pouvoir calculer des contraintes.

Problèmes et exemple par Euler

En programmation nous calculons l’état d’un système par intervalle de temps, le plus petit possible afin d’avoir des calculs précis. Avec la méthode d’Euler utilisée précédemment nous ajoutions lors de chaque pas de calcul à la vitesse du système son accélération, puis à sa position sa vitesse :

v(t+dt) = v(t) + a(t).dt
P(t+dt) = P(t) + v(t+dt).dt

C'est-à-dire

P(t+dt) = P(t) + v(t).dt + a.dt² pour une accélération constante a(t)=a


Seulement ici en fonction du pas d’intégration on va avoir une marge d’erreur sur la position plus ou moins grande par rapport aux vraies valeurs. Par exemple pour une accélération constante, après deux intégrations pour remonter à la position du système en fonction du temps on a :

P(t) = 0.5.a.t² + v0.t

Ou alors :

P(t+dt) = P(t) + 0.5.a.dt² + v(t).dt


La différence entre l’équation de Euler et l’équation exacte nous donne l’erreur relative des deux méthodes :

E = 0.5.a.dt²

L’erreur augmente à l’ordre 2 tandis que le pas de calcul augmente de façon linéaire, ce qui nous oblige à garder un pas d’intégration très faible pour avoir des résultats corrects, ces erreurs sont encore plus grandes pour des systèmes de type oscillateur (ressort). Le problème survient lors de l’utilisation de systèmes qui peuvent devenir instables, en particulier les ressorts qui ont une accélération non constante fonction de l’étirement de celui-ci, et si le système prend une certaine vitesse, même avec un très faible pas d’intégration l’intégrateur d’Euler ne suffira pas à garantir des résultats assez précis et le ressort va « partir à l’infini… » Nous allons utiliser un autre intégrateur que celui de Euler afin de gagner bien plus de précision en gardant le même pas d’intégration.

Intégration exacte, intégrateur de Verlet

L’intégrateur de Verlet est plus précis que celui d’Euler car d’ordre 4 alors que Euler est d’ordre 1 uniquement. Pour l’obtenir, on développe l’expression de la position d’un système pour un temps t+dt et t-dt avec un développement de Taylor puis on additionne les deux expressions, ce qui donne :

P(t+dt) = P(t) + v(t).dt + a(t).dt²/2 + k(t).dt³/6 + O(dt^4) (1)
P(t-dt) = P(t) - v(t).dt + a(t).dt²/2 - k(t).dt³/6 + O(dt^4) (2)
(1)+(2): P(t+dt) + P(t-dt) = 2*P(t) + a(t).dt² + O(dt^4)

Au final:

P(t+dt) = 2*P(t) - P(t-dt) + a(t).dt² + O(dt^4)


On remarque que la vitesse n’intervient plus directement dans cette expression, d’où une stabilité accrue du système. Ce modèle admet cependant de grandes pertes énergétiques (d’où sa stabilité) mais reste bien plus précis qu’une intégration numérique d’Euler.

Attention, les calculs pour obtenir cette formule utilisent des temps t+dt et t-dt, dt étant le même nous devons alors utiliser un pas de calcul dt constant. Cela est très important pour la véracité des calculs, dans le cas contraire vous allez récupérer des résultats très mauvais. Pour cela vous pouvez par exemple utiliser un Timer, ou alors gérer vous-même l’update de la physique de votre scène en fonction du temps écoulé depuis le dernier calcul Physique, mais attention au de ne pas tomber dans le piège d’un calcul physique par rendu graphique (Evénement ON_ENTERFRAME) car le FPS d’une animation n’est pas forcement constant !

Avec un pas d’intégration non constant :

Notez que l’intégrateur de Verlet peut être modifié pour prendre en compte un pas de calcul non constant. Si vous êtes intéressé, je vous réfère à l’article très intéressent de GameDev ici : http://archive.gamedev.net/reference/programming/features/verlet/default.asp

Application de Verlet

Nous allons utiliser l’intégrateur de Verlet pour faire une petite simulation : Une balle chargée négativement placé aléatoirement sur l’écran, et on va considérer que les 4 coté de l’animation sont des plans chargés positivement et la souris une charge positive, pas de gravité, juste des forces électrostatiques !

Tout d’abord nous devons créer la classe du document que je nommerai Verlet. Dans le constructeur on va créer graphiquement notre petite balle, et on instancie 2 variables pour contenir la position de la balle pour les instants t et t-dt afin de calculer la position à t=t+dt via intégration de Verlet.

Nous ajoutons un Timer pour appeler une fonction de mise à jour de la physique toutes les 50ms. On va considérer que ces 50ms serons constantes même si les Timers ne respectent pas toujours ces temps la. Nous avons donc notre dt=25ms.

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.display.Shape;
	import flash.display.Graphics;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	public class Verlet extends Sprite {
 
		private var updateTimer:Timer;
		private var nextPosition:Point;
		private var previousPosition:Point;
 
		private var ball:Shape;
 
		public function Verlet() {
 
			ball=new Shape();
			var g:Graphics=ball.graphics;
			g.beginFill(0x990000);
			g.drawCircle(0,0,10);
			g.endFill();
			ball.x=10+Math.random()*(stage.stageWidth-20);
			ball.y=10+Math.random()*(stage.stageHeight-20);
			addChild(ball);
 
			//random pour donner une vitesse initiale à la boule
			previousPosition=new Point(ball.x+Math.random()*20-10, ball.y+Math.random()*20-10);
			nextPosition=new Point(ball.x, ball.y);
 
			updateTimer=new Timer(25); //dt=25ms
			updateTimer.addEventListener(TimerEvent.TIMER, updatePhysic);
			updateTimer.start();
 
 
		}
 
		private function updatePhysic(e:TimerEvent):void {
 
			//mise à jour physique de la scène...
 
		}
 
	}
}

Dans la fonction de mise à jour physique, on calcul l’accélération produite par les champs, pas de problème de ce coté la. Notez que je limite la fore produite par la souris pour éviter d’avoir des forces infinie en promenant le curseur exactement au même point que la boule.

Ensuite vient l’intégration de Verlet. On utilise simplement la formule d’intégration, puis on met l’ancienne position de la boule dans la variable previousPosition. Notez que je n’update par directement la position de la boule mais un objet de type Point, puis je donne comme valeur de position pour la boule les composantes de ce point. Pourquoi ? Un DisplayObject à des coordonnées de type entier alors que le point de type Number (nombre flottant), et si on utilisait directement les coordonnées du DisplayObject, les arrondis casseraient l’intégration de Verlet (par exemple un faible déplacement serait traduit par aucun déplacement). Pour finir je vérifie rapidement si la boule sort de la scène et si c'est la cas je la remet à l’intérieur, dans le cas ou sous l'action d'une trop grande force elle aurait réussie à sortir!

 
//force eletrostatique des 4 plan (bords) :
var a:Point=new Point(0,0);
var intencity:Number=10000.0;
 
a.x=intencity/(ball.x*ball.x); //electronic fields
a.x-=intencity/((stage.stageWidth-ball.x)*(stage.stageWidth-ball.x)); //+ rapide que d'utiliser pow(...,2)
a.y+=intencity/(ball.y*ball.y);
a.y-=intencity/((stage.stageHeight-ball.y)*(stage.stageHeight-ball.y)); //+ rapide que d'utiliser pow(...,2)
 
var d:Number=Math.sqrt(Math.pow(ball.x-mouseX,2)+Math.pow(ball.y-mouseY,2));
a.x+=0.5*intencity*(ball.x-mouseX)/(Math.max(10,d*d*Math.abs(ball.x-mouseX)));
a.y+=0.5*intencity*(ball.y-mouseY)/(Math.max(10,d*d*Math.abs(ball.y-mouseY)));
 
a.x-=(nextPosition.x-previousPosition.x)/20; //damping
a.y-=(nextPosition.y-previousPosition.y)/20;
 
var temp:Point=new Point(nextPosition.x,nextPosition.y);
 
nextPosition.x=2*nextPosition.x-previousPosition.x+a.x*0.1;//verlet integration
nextPosition.y=2*nextPosition.y-previousPosition.y+a.y*0.1;
 
previousPosition.x=temp.x;
previousPosition.y=temp.y;
 
ball.x=nextPosition.x;
ball.y=nextPosition.y;
 
if(ball.x>stage.stageWidth) { ball.x=nextPosition.x=stage.stageWidth-10; }
if(ball.y>stage.stageHeight) { ball.y=nextPosition.y=stage.stageHeight-10; }
if(ball.y<0) { ball.y=nextPosition.y=10; }
if(ball.x<0) { ball.x=nextPosition.x=10; }

Pour finir, juste afin de rendre le tout un peu plus joli j’ai rajouté une fonction appelé par un Timer toutes les 100ms pour tracer le chemin parcouru par cette petite boule. La couleur est d’autant plus rouge que la vitesse est forte, et le tracé n’est effectué que si la boule à une vitesse suffisante, en effet l’objet Graphics utilise beaucoup de CPU pendant du rendu vectoriel possédant beaucoup de composants. Le détail de cette fonction n’est pas plus expliqué car pas dans l’esprit principal de l’article.

Dans les déclarations de variables :

 
private var pathTimer:Timer;
private var lastPoint:Point;

A la fin du constructeur :

 
lastPoint=new Point(ball.x, ball.y);
 
pathTimer=new Timer(50);
pathTimer.addEventListener(TimerEvent.TIMER, drawPath);
pathTimer.start();

Dans la classe Verlet :

 
private function drawPath(e:TimerEvent):void {
 
			var g:Graphics=graphics;
			var force=Math.sqrt(Math.pow(stage.stageWidth/2-ball.x,2)+Math.pow(stage.stageHeight/2-ball.y,2));
			force=force/Math.sqrt(Math.pow(stage.stageWidth/2,2)+Math.pow(stage.stageHeight/2,2));
			g.lineStyle(1,Math.min(255,50*Math.abs(nextPosition.y-previousPosition.y))<<16);
			if(Math.abs(nextPosition.y-previousPosition.y)>0.01) {
				g.moveTo(lastPoint.x, lastPoint.y);
				g.lineTo(ball.x, ball.y);
			}
			lastPoint.x=ball.x;
			lastPoint.y=ball.y;
 
 
 
		}

Et voila notre simulation finale.
Si vous souhaitez faire une comparaison, codez donc la même simulation en utilisant une intégration d’Euler, vous verrez que la boule est vraiment plus instable avec un même pas d’intégration. Ici, même en « coinçant » la boule contre un coin avec la souris, là où le système est le plus potentiellement instable, on peut constater qu’il n’explose pas et reste plutôt stable grâce à l’intégration de Verlet.

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Extension de Verlet aux contraintes

Verlet est très intéressant car il peut aussi être utilisé pour faire des calculs de contrainte avec des particules. Cela peut être utile pour par exemple faire un moteur physique 2D ou 3D gérant des soft body constitué de points reliés par des contraintes. Ce type de contrainte est aussi beaucoup utilisé lors du calcul de tissus.

L’implémentation des contraintes est un très bon exercice complémentaire. Pour cela je vous conseil de lire la page suivante afin de bien cerner les concepts de l’intégration de Verlet : http://en.wikipedia.org/wiki/Verlet_integration puis jettez un oeil à cette video traitant du codage d’une simulation de tissu en AS3 via Verlet avec des contraintes de position : http://www.youtube.com/watch?v=8bAQbJzqa-0