Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Création d'un portfolio avec interactions Physiques

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Stefbuet, le 26 février 2011
Prérequis Il est conseillé d'aller lire l'article sur l'utilisation de forces avant celui-ci. La notion de torseur mécanique peut être un plus mais n'est pas obligatoire, cependant vous devez savoir ce qu'est le Principe fondamental de la dynamique (notamment au niveau des moments)

Introduction

Nous allons voir dans cet article comment créer un portfolio original avec quelques calculs physiques. Le but de cet article sera donc d’afficher une série de photos à partir d’un fichier XML avec des interactions physique. Pour cela nous simulerons un pivot fixe au centre de la scène ou toutes les photos seraient attachées par une tige rigide. Les photos vont alors tourner autour de l’axe pivot sous l’action de la gravité avant de se stabiliser donnant un effet impressionnant.

Voici le résultat final que vous obtiendrez (Cliquez pour ajouter des images aléatoires) :

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

Fichier XML

Nous allons créer un fichier XML afin de pouvoir modifier les photos à afficher sans avoir à recompiler l’animation à chaque fois. Je vous propose la structure suivante, très simple :

 
<?xml version="1.0" encoding="iso-8859-1" ?> 
<album>
	<picture name="A130 plane" description="A310 Airbus plane" src="photos/a310.jpg" />
	<picture name="Cessna plane" description="A small cessna plane" src="photos/cessna.jpg" />
	<picture name="F16" description="American F16" src="photos/f16.jpg" />
	<picture name="F22 Raptor" description="Expensive F22 US raptors" src="photos/f22.jpg" />
	<picture name="F14 Tomcat" description="F14 tomcat, now outdated" src="photos/f14c.jpg" />
	<picture name="B52 bomber" description="One of the biggest..." src="photos/b52.jpg" />
	<picture name="Cessna 172" description="Cessna plane" src="photos/cessna-172.jpg" />
</album>

Commençons par charger ce fichier XML et traiter les données : On parcourt tous les éléments à la racine du fichier XML (à savoir les photos) et on rajoute l’URL de la photo courante dans une liste que l’on utilisera plus tard. Dans cet article je n’utiliserais que l’URL des images mais si vous voulez compléter votre portfolio vous pourrez toujours utiliser la description et le titre des images comme amélioration futur. A la fin de notre boucle, nous appelons une fonction pour charger la premier image, que nous détaillerons par la suite.

 
var imgList:Vector.<String>=new Vector.<String>();
 
var u:URLLoader=new URLLoader();
u.addEventListener(Event.COMPLETE, onXMLLoaded);
u.load(new URLRequest("/data.xml"));
 
function onXMLLoaded(e:Event):void {
	var xmlData:XMLList=XML(e.target.data).elements();
	for each(var picture:XML in xmlData) {
		imgList.push(picture.@src);
	}
	loadNextPicture();
}

Ensuite on va charger toutes ces images, en attendant que la précédente soit chargée avant de lancer le chargement de la suivante. Ainsi nous pourrons les rajouter sur la scène les unes après les autre avec la gestion de la physique ce qui rendra encore mieux. Pour le moment nous n’implémentons pas l’affichage graphique des images, juste leur chargement :

 
var currentIndex:uint=0;
var currentLoader:Loader;
var allImagesLoaded:Boolean=false;
 
function loadNextPicture():void {
	currentLoader=new Loader();
	currentLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onPictureLoaded);
	currentLoader.load(new URLRequest(imgList[currentIndex]));
}
 
function onPictureLoaded(e:Event):void {
 
	if((currentIndex!=imgList.length-1)&&(!allImagesLoaded)) {
	   currentIndex++;
	   loadNextPicture();
	}
	else {
		allImagesLoaded=true;
	}
 
}

L'utilisation de la variable allImagesLoaded se justifiera par la suite lorsque nous voudrons pouvoir rajouter des images manuellement à la souris après qu'elle aient toutes étés chargées : Nous voudrons en rajouter une seule, et cette variable permet d’empêcher de charger les suivantes automatiquement, du moment que les images on déjà été parcourues au moins une fois au départ.

Simulation physique

Nous allons travailler uniquement sur les couples créés par chaque photo au niveau de l’axe de rotation du système. Nous utiliserons donc le principe fondamental de la dynamique qui stipule que le torseur dynamique d’un système est égal a son torseur statique, mais nous n’utiliserons que le moment des torseurs, pour calculer l’accélération angulaire du système en fonction des couples exercées sur l’axe de rotation. Pour calculer la vitesse de rotation et la rotation du système nous utiliserons simplement une intégration d’Euler car ce type de système reste stable sans problème.

{D}={S}
⇔ I . d(omega)/dt = C

D’ou :

omegaV(t+dt)=omegaV(t)+dt*C(t+dt)/I
omega(t+dt)=omega(t)+omegaV(t+dt)*dt

Sachant que C=Somme(Ci) Avec omega l’angle de rotation autour du pivot du système, omegaV la vitesse angulaire, I l’inertie du système par rapport à cet axe et C le couple engendré par toutes les masses (photos) sur le système (Ci respectivement par la i-unième photo).

Affichage graphique

Nous allons maintenant gérer l’affichage graphique des photos, via une structure que nous pourrons modifier avec nos équations facilement. Pour faire simple, j’ai créé un clip sous Flash IDE en forme de rond coloré, exporté ce clip avec l’AS3 en modifiant ces propriété dans la bibliothèque (nom de classe : PhotoMC ) puis à l’intérieur j’ai créé un autre clip qui fera office de masque, même forme en disque mais un peu plus petit avec « photoMask » comme nom d’occurrence.

Nous allons créer une liste d’objets avec 3 propriétés : les movieClips qui correspondent chacun à une photo, l'angle initial de la photo et la distance au centre de l’écran (distance à l'axe en fin de compte) . A chaque nouvelle photo chargée nous allons rajouter un objet à cette liste qui aura été initialiser avec une position aléatoire, puis ajouter en enfant au clip la photo chargée avec comme masque celui créé précédemment pour faire un effet sympas. Ensuite on rajoute le clip aux enfants de la scene de base pour avoir un affichage graphique. L'angle et distance à l'axe initial seront nécessaires dans la suite pour faire les calculs physiques.

 
var mcList:Vector.<Object>=new Vector.<Object>();
 
function onPictureLoaded(e:Event):void {
 
	var mc:PhotoMC=new PhotoMC();
 
	var angle=Math.random()*2*3.1415;
	var amp:Number=100+Math.random()*200;
 
	mc.x=stage.stageWidth/2+Math.cos(angle)*amp;
	mc.y=stage.stageHeight/2+Math.sin(angle)*amp;
 
	currentLoader.width=currentLoader.height=150;
	currentLoader.x=currentLoader.y=-75;
	currentLoader.mask=mc.photoMask;
	mc.addChild(currentLoader);
 
	mcList.push({mc:mc, initAngle:angle, amp:amp});
 
	addChild(mc);
 
	if((currentIndex!=imgList.length-1)&&(!allImagesLoaded)) {
	   currentIndex++;
	   loadNextPicture();
	}
	else {
		allImagesLoaded=true;
	}
 
}

Mise à jour physique

Maintenant nous allons mettre à jour la position de toutes les photos en utilisant l’équation précédemment établie. Tout d’abord on calcule le couple total exercé par toutes les photos, sachant que :

Ci=d ^ F (produit vectoriel)

Nous calculons la nouvelle vitesse angulaire puis l’angle du système autour de l’axe par une intégration d’Euler. Ensuite, on transforme la position de toutes les photos avec une rotation égale la rotation du système plus la rotation initiale de la photo (en gardant la même amplitude). La rotation d’angle teta selon notre axe Z se représente par la matrice suivante :

Ce qui nous donne au final le code suivant pour mettre à jour la position des images. On appellera cette fonction toutes les 50ms grâce à un Timer :

 
var angularVelocity:Number=0;
var angle:Number=0;
 
var t:Timer=new Timer(50);
t.addEventListener(TimerEvent.TIMER, updatePhysic);
t.start();
 
var lastTime:Number=getTimer(), now:Number=0;
 
function updatePhysic(e:TimerEvent):void {
 
	var now=getTimer();
	var dt:Number=(now-lastTime)/1000;
	lastTime=now;
 
	var torqueTotal:Number=0;
 
	for(var i:int=0; i<mcList.length; i++) {
		torqueTotal+=(mcList[i].mc.x-stage.stageWidth/2)/1000;
	}
 
	angularVelocity+=torqueTotal*dt;
	angularVelocity*=0.95; //damping
 
	angle+=angularVelocity;
 
	for(i=0; i<mcList.length; i++) {
		mcList[i].mc.x=stage.stageWidth/2+Math.cos(angle+mcList[i].initAngle)*mcList[i].amp;
		mcList[i].mc.y=stage.stageHeight/2+Math.sin(angle+mcList[i].initAngle)*mcList[i].amp;
	}
 
 
}

Finalisation et conclusion

Afin de donner vraiment l’impression d’être en face d’un système mécanique réel, il faut relier les images au centre de l’axe de rotation, ici le milieu de l’écran. Pour cela nous allons simplement ajouter une fonction qui va utiliser la propriété Graphics d’une Shape toute bête afin de dessiner des lignes d’épaisseur 3px partant du centre de l’écran au centre de chaque photo. Cette fonction sera appelée à la fin de la fonction d’update de la physique.

 
var linesGraphic:Shape=new Shape();
addChild(linesGraphic);
 
function updateGraphics():void {
 
	var g:Graphics=linesGraphic.graphics;
	g.clear();
	g.lineStyle(7,0x816100,0.9);
	for(var i:Number=0; i<mcList.length; i++) {
		g.moveTo(stage.stageWidth/2, stage.stageHeight/2);
		g.lineTo(mcList[i].mc.x, mcList[i].mc.y);
	}
 
 
}

Pour conclure, nous avons ici réalisé un portfolio physique basique assez amusant. Pour le rendre encore mieux, vous pouvez y apporter quelques modifications :

-Interaction physique avec l’utilisateur : drag & drop des photos : lors du drag, juste donner une rotation au système pour que la souris reste au même point relatif, puis au drop, calculer vitesse souris par rapport à l’axe et changer la vitesse angulaire pour cette vitesse. Vous pourriez aussi utiliser des forces électromagnétiques par exemple.

-Zoom sur les photos, par exemple quand l’utilisateur clique sur une photo. On pourrait imaginer que plus la photo est grosse, plus son poids est grand, plus le couple exercé sur le système est grand…

Pour l’exemple, j’ai rajouté une petite fonction appelée à chaque clic sur l’animation qui va charger une image parmi celles déjà affichées au pif, cela vous a surement permis de jouer avec l’animation au début de cet article et de mieux voir comme elle fonctionnait :)

 
stage.addEventListener(MouseEvent.CLICK, addRandomImg);
 
function addRandomImg(e:MouseEvent):void {
	currentIndex=Math.floor(Math.random()*imgList.length);
	loadNextPicture();
 
 
}