Baniere Saphir Control
http://www.saphir-control.fr/

bar


BACK

Scilab : l'environnement graphique (2/2)
Animation






Tous ces articles en ligne sur Scilab ont été écrits pour LINUX MAGAZINE FRANCE par les développeurs du Scilab Group. Les articles sont sous licence FDL (Free Documentation Licence)

On a vu dans le numéro précédent l'environnement graphique de Scilab et son utilisation pour développer des applications graphiques. Dans ce numéro, on présente les éléments nécessaires pour construire des animations.

On a déjà vu les principes de base de l'environnement graphique de Scilab et les fonctions qui permettent de modifier ses propriétés. On a aussi vu quelques fonctions pour dessiner des objets élémentaires. Dans cet article, on verra comment utiliser ces fonctions pour construire des animations. On fait cela à travers un exemple.

Contexte graphique

Pour construire des animations, il faut d'abord choisir les paramètres du contexte de la fenêtre graphique.
Driver Rec et driver X11
Dans Scilab, on peut créer du graphique en utilisant plusieurs drivers : Rec, X11, Postscritp, XFig et GIF. Les deux premiers seulement sont utilisés pour l'affichage à l'écran. La différence entre les drivers Rec et X11 est que le driver Rec enregistre toutes les commandes graphiques passées. Cela permet de rejouer automatiquement ces commandes si nécessaire, par exemple dans le cas d'un zoom, mais n'affectent en rien le résultat final. L'enregistrement (et par conséquence le graphique) est effacé avec la commande xbasc(). Dans le cas d'animation, il est préférable d'utiliser le driver X11 pour éviter les problèmes de mémoire. Cela peut se faire avec la fonction driver comme suit :
--> driver("X11")
Mode pixmap
Par défaut, les commandes graphiques Scilab dessinent dans une zone mémoire qu'on appelle la fenêtre virtuelle ; Scilab et le Window Manager font le nécessaire pour que le contenu de cette fenêtre soit affiché en permanence dans la fenêtre visible. En mode pixmap (la commande xset("pixmap",1)), les commandes graphiques sont dirigées vers une zone mémoire tampon qu'on appelle le pixmap. Le contenu du pixmap n'est pas affiché. Pour le visualiser, il faut utiliser la commande xset("wshow") qui le copie dans la fenêtre virtuelle. L'intêret du mode pixmap est que l'affichage à l'écran peut se faire aux moments choisis par l'utilisateur. Cela permet en particulier de construire des animations plus fluides. La contrepartie est une perte de la vitesse de l'animation car la commande xset("wshow") fait une recopie du contenu du pixmap dans la fenêtre virtuelle. Le temps de recopie dépend bien-entendu de la taille de la fenêtre.
Mode Xor
Une animation est une séquence d'images à afficher dans une boucle Scilab. On peut donc, à chaque itération, effacer l'ancienne image et dessiner une nouvelle. Pour effacer l'image on peut utiliser la commande xset("wwpc") si on est en mode pixmap, ou les commandes xbasc() (driver Rec) ou clear() (driver X11) sinon. Cela marche bien pour la plupart des animations. Dans certaines animations, en particulier quand seulement un petit nombre d'objets change de place d'une image à l'autre, il peut être plus efficace d'effacer et de ne redessiner que ces objets là. Pour cela, on peut utiliser la fonction xclea qui efface une région donnée de l'image, mais la méthode la plus efficace est l'utilisation du mode Xor. En mode Xor, quand on dessine dans une fenêtre, le dessin ne remplace pas le dessin précédent, mais il se combine avec. Par défaut, si on dessine une droite (xsegs) en noir, on a un certain nombre de pixels dans l'image, représentant la droite, qui deviennent noirs. Mais en mode Xor, les couleurs finales de ces pixels dépendent de leurs couleurs initiales. La couleur finale est en fait obtenue par l'opération xor bit-à-bit sur les codages binaires des deux couleurs. Donc dessiner en rouge sur bleu en mode Xor peut donner du jaune ou une autre couleur, cela dépend du codage des couleurs utilisé par le système. Mais ce qui est capital c'est que dans ce mode, pour effacer un dessin obtenu par une commande graphique, il suffit de répéter cette commande graphique. Un exemple d'utilisation du mode Xor est l'éditeur graphique de Scicos (boite à outils pour la modélisation et la simulation des sytèmes dynamiques). Les opérations d'édition ne font bouger en général qu'un petit nombre d'objets dans un schéma Scicos, le mode Xor est alors très efficace.

Exemple

Pour bien comprendre comment une animation peut être programmée dans Scilab, on va construire ici un exemple simple mais complet. On considère l'animation du mouvement de n balles identiques dans une boite en 2D. Les balles sont lancées à partir des positions aléatoires à l'intérieur de la boite avec des vitesses intiales aléatoires. On supposera qu'il n'y a pas de frottement et que les chocs entre les balles, et les chocs contre le sol et les parois sont élastiques.
Modèle
L'équation du mouvement d'une balle (en dehors des chocs) est donnée par :
ÿ = g
¨x = 0
où (x,y) représente la position de la balle et g=-9.8m/sec2 (sur terre). On peut bien-entendu simuler ce système avec le solveur ode de Scilab, mais dans ce cas on peut aussi trouver explicitement la solution. En particulier, il est facile à voir que les solutions sont données par :
y(t) =
1
2
gt2 +bt +c
x(t) = dt + e
où (e,c) représente la position intiale (t=0) et (d,b) la vitesse intiale. Cette équation est valable, et donc peut être simulée, tant qu'il n'y a pas eu de choc. Il faut donc calculer le temps tc du premier choc. Celui-ci peut être un choc entre deux balles, ou entre une balle et la paroi de gauche, la paroi de droite ou le sol. Il est facile de calculer le temps du choc de la balle avec les parois. Soient xmin et xmax l'emplacement des deux parois verticales. Alors l'instant des prochains chocs potentiels avec ces deux parois sont obtenus par la résolution des équations
x
 
min
= dt + e
x
 
max
= dt + e
ce qui donne tc=(xmin-e)/d et (xmax-e)/d. De même, l'instant du choc avec le sol s'obtient par la résolution de l'équation suivante :
0=
1
2
gt2 +bt +c
Cela est un polynôme du second degré, on peut donc calculer ces racines explicitement. On ne prendra que la solution positive. En ce qui concerne les chocs entre deux balles, la situation est un peu plus complexe. Soient (xi,yi) et (xj,yj) les positions des deux balles. Alors un choc se produit quand
(xi(t)-xj(t))2+(yi(t)-yj(t))2-(2r)2=0
r représente le rayon des balles. En remplaçant xi(t), xj(t), yi(t) et yj(t) par les expressions explicites, on trouve encore une équation du second degré :
At2 + 2Bt + C=0
A = (di-dj)2+(bi-bj)2
B = (di-dj)(ei-ej)+(bi-bj)(ci-cj)
C = (ei-ej)2+(ci-cj)2-4r2
L'instant du choc donc s'obtient par la plus petite racine réelle et positive de ce polynome du second degré. Donc pour trouver l'instant du prochain choc, il faut calculer les temps de tous les chocs potentiels et choisir le plus petit.

L'effet du choc est de changer les vitesses des balles. Quand une balle rencontre une paroi, la composante de sa vitesse dans la direction x change de signe, quand elle rencontre le sol, la composante dans la direction y change de signe. Si l'instant du choc est t=0, cela revient respectivement à changer les signes de d et de b. Le choc entre deux balles est plus difficile à modéliser. Au moment du choc, on suppose que les deux balles subissent des impulsions, égales mais dans le sens opposé, en quantité du mouvement dans la direction normale au point du contact (figure 1).

bar



Figure 1: L'impulsion du choc.



Donc si on suppose que l'instant du choc est t=0, l'effet du choc est de modifier les vitesses di, bi, di et dj comme suit :
bi=bi+abj=bj-a,
di=di-bdj=dj+b,
a et b vérifient
a
b
=
ci-cj
ej-ei
Cette relation qui vient de l'hypothèse de l'absence de frottement, permet de calculer b en fonction de a, mais ne permet pas de calculer ce dernier. En effet, on n'a pas encore pris en compte la conservation de l'énergie ce qui implique que la somme des carrés des composantes des vitesses des deux balles, avant le choc et celle d'après, doivent être égales :
bi2+bj2+di2+dj2=(bi+a)2+ (bj-a)2+(di-b)2+(dj+b)2
Cela permet maintenant de calculer a (et donc b) de manière unique. On trouve :
a=-(bi-di/d-bj+dj/d)/(1+(1/d)2)
avec b=a/d
d=
ci-cj
ej-ei
.
Simulation
La simulation commence par l'initialisation des positions et des vitesses des balles :

function [b,c,d,e]=initialise(n)
rand("normal") //loi normale utilisee pour rand
b=rand(n,1);d=rand(n,1); //vitesses intiales
c=3*[1:n]';e=rand(n,1); //positions initiales
endfunction

Ici n représente le nombre de balles. Après, la simulation fonctionne dans une boucle infinie de la manière suivante :
  1. On est à t=0 et on calcule l'instant du prochain choc tc. On a vu que le calcul de tc nécessite la résolution de plusieurs équations du second degré, ce qui peut être vectorisé dans Scilab comme suit :
    
    function [tc,k]=prochain_choc(b,c,d,e)
    A=(P*b).^2+(P*d).^2;
    B=(P*b).*(P*c)+(P*d).*(P*e);
    C=(P*c).^2+(P*e).^2-4*r2;
    rac=[(-b-sqrt(b.^2-2*g*c))/g;
        -(10+e)./d;(10-e)./d;
        (-B-sqrt(B.^2-A.*C))./A]; //calcul des instants de choc
    rac(find(imag(rac)<>0))=%inf;//supprimer racines complexes
    rac=real(rac);
    rac(find(rac<=10*%eps))=%inf;//supprimer tc<0
    [tc,k]=min(rac);  //calcul de tc (premier choc)
    endfunction
    
    
    La matrice P contient un 1 et un -1 sur chaque ligne de telle façon que les composantes P*b soient b_i-b_j pour tout i différent de j, idem pour c, d et e. tc est le temps correspondant au prochain choc et k indique quel choc sera produit à tc (quelle balle contre quelle parois ou contre quelle autre balle).
  2. On calcule et mémorise les trajectoires des balles de 0 à tc. Le calcul de la trajectoire est facile car la solution explicite est disponible ; il suffit de discrétiser le temps :
    
    function [x,y]=trajectoire(b,c,d,e,t)
    // calcul de la trajectoire (x,y)
    // etant donne un vecteur de temps t
    y=.5*g*ones(n,1)*t.^2+b*t+c*ones(t); 
    x=d*t+e*ones(t);         
    endfunction
    
    
  3. On affiche successivement les positions des balles. On peut aussi bien utiliser xfpolys que xfarcs pour dessiner les balles. On dessine toutes les balles avec une seule commande graphique :
    
    function TT=anim(x,y,t,TT)
    I=size(t,"*");
    for i=1:I-1 
      xset("wwpc") //effacer 
      xfpolys((x(:,i)*s+cerclex)',(y(:,i)*s+cercley)',[1:n])
      //    TT=TT+DT;realtime(TT);  //pour mode temps reel
      xset("wshow")           //dessiner
    end  
    i=I;  //le cas i=I traiter a part pour le temps reel
    xset("wwpc")   //effacer
    xfpolys((x(:,i)*s+cerclex)',(y(:,i)*s+cercley)',[1:n])
    //  deux lignes suivantes pour mode temps reel
    //  if I>1 then TT=TT+t($)-t($-1);else TT=TT+t($);end
    //  realtime(TT);  
    xset("wshow")   //dessiner la derniere position (choc)
    endfunction
    
    
    La variable TT sera utilisée pour gérer l'animation temps-réel que nous verrons un peu plus tard. La gestion temps-réel est aussi la raison pour laquelle le cas i=I a été traité à part.
  4. On fait un changement de coordonnées pour remettre le temps courant (tc) à zéro. Pour cela il suffit de remplacer les ci par yi(tc)=1/2gtc2+bitc+ci, les ei par xi(tc)=ditc+ei et les bi par g*tc+bi.
    
    function [b,c,d,e]=nouv_coor(b,c,d,e,tc)
    // changement de coordonnees pour remettre le temps a 0
    c=.5*g*ones(n,1)*tc.^2+b*tc+c;
    e=d*tc+e;
    b=g*tc*ones(n,1)+b;
    endfunction
    
    
  5. On modifie les vitesses en fonction de type du choc. Tout est vectorisé dans ce cas pour éviter des boucles.
    
    function [b,c,d,e]=apres_choc(b,c,d,e)
    // gestion du choc
    if k<n+1 then  //choc avec le sol
      b(k)=abs(b(k));
    elseif k<2*n+1 then //choc avec la paroi gauche
      d(k-n)=abs(d(k-n));
    elseif k<3*n+1 //choc avec la paroi droite
      d(k-2*n)=-abs(d(k-2*n));
    else   // balle contre balle
      m=k-3*n;
      mm=find(P(m,:)<>0);m1=mm(1);m2=mm(2); //balles m1 et m2
      del=(c(m1)-c(m2))/(e(m2)-e(m1));
      alpha=-(b(m1)-d(m1)/del-b(m2)+d(m2)/del)/(1+(1/del)^2);
      b(m1)=b(m1)+alpha;    b(m2)=b(m2)-alpha;
      d(m1)=d(m1)-alpha/del;    d(m2)=d(m2)+alpha/del;
    end
    endfunction
    
    
  6. On recommence l'étape 1.
Script Scilab
On commence par charger les fonctions Scilab présentées plus haut ; cela peut se faire en les plaçant dans un ou plusieurs fichiers et en utilisant la commande getf. Puis on exécute le script suivant (on peut aussi placer la défintion des fonctions au début de ce script) :

n=5;  // nombre de balles
g=-9.8; 
r=1.3;r2=r^2; //rayon des balles
// dessins des balles -> cercles approx par polylines 
s=[0:.2:2*%pi];
cerclex=ones(n,1)*r*cos(s);
cercley=ones(n,1)*r*sin(s);
s=ones(s);
//Matrice P telle que [P*x] = x_i - x_j
P=[];
for i=n-1:-1:1
  P=[P;[zeros(i,n-1-i),ones(i,1),-eye(i,i)]];
end
// contexte graphique pour l'animation
driver X11;xset("pixmap",1);xset("wpdim",300,430);
xsetech(wrect=[0,0,1,1],arect=[0,0,0,0]) 
// frameflag=4 correspond a isoview -> balles rondes
plot2d([-10-r,-10-r,10+r,10+r],[20,-r,-r,20],frameflag=4)
xset("colormap",rand(n,3,"uniform")) //couleur des balles 
xset("wwpc") // efface le contenu
//
TT=0;DT=.04; // discretisation en temps
//realtimeinit(1);realtime(TT); // pour mode temps reel

[b,c,d,e]=initialise(n);

while %t   //boucle infinie
  [tc,k]=prochain_choc(b,c,d,e);
  t=DT:DT:tc;t=[t,tc]; //disrectisation du temps
  [x,y]=trajectoire(b,c,d,e,t);
  TT=anim(x,y,t,TT);
  [b,c,d,e]=nouv_coor(b,c,d,e,tc);
  [b,c,d,e]=apres_choc(b,c,d,e);
end



bar



La figure 2 montre le résultat d'une simulation ; pour pouvoir visualiser l'historique, les commandes xset("wwpc") ont été commentées.
Temps-réel
Il est souhaitable de pouvoir imposer la cadence d'affichage des images pour obtenir des animations plus fluides et réalistes. Dans le programme d'animation des balles, l'affichage de chaque image se fait le plus tôt possible, donc le rythme d'affichage dépend de la performance et de la charge de la machine, et de la complexité des calculs nécessaires pour construire une image. Cette complexité est plus importante par exemple pour une image suivant un choc. La fonction xpause permet de ralentir une animation en introduisant une pause, mais elle ne permet pas d'imposer une cadence régulière. En fait, pour le moment il n'existe aucune fonction interfacée dans Scilab pour faire du temps-réel. Mais Scicos qui est une boite à outils Scilab utilise des fonctions pour faire du temps-réel ; ces fonctions sont déjà dans Scilab et peuvent facilement être interfacées comme des primitives. Les deux fonctions qui nous intéressent sont : realtimeinit(t,scale) et realtime(t). Ceux qui ont une version source de Scilab peuvent examiner les codes source de ces fonctions qui se trouvent dans le fichier suivant :
SCI/routines/scicos/realtime.c

La fonction realtimeinit(t,scale) est utilisée pour définir l'échelle du temps (une unité du temps de simulation vaut alors scale secondes) et d'initialiser l'origine du temps réel en temps machine. Le premier argument (t) n'est pas utilisé Un premier appel à realtime(t) intialise l'origine du temps de simulation ; à ce point la transformation entre le temps réel et le temps de simulation est bien définie. Les appels realtime(t) suivants permettent de faire du temps-réel de la manière suivante : si t tranformé en temps réel est plus petit que le temps machine, la fonction rend la main immédiatement, dans le cas contraire, la fonction fait une pause et rend la main quand le temps machine (converti en temps de simulation) vaut t. On voit alors qu'en plaçant des appels à realtime(t) avant l'affichage de chaque image, on peut cadencer l'animation. Noter que l'utilisation de realtime(t) permet seulement de ralentir l'animation. Il faut donc s'assurer que la cadence imposée est compatible avec les performances de la machine.
Pour interfacer ses deux fonctions, il faut écrire une interface. Dans ce cas celle-ci peut est très simple :
#include "stack-c.h"

int intsrealtimeinit(fname)
   char *fname;
{
 int m1,n1,l1;
 double zer=0.0;
 CheckRhs(1,1);
 CheckLhs(1,1);
 /*  checking variable scale */
 GetRhsVar(1,"d",&m1,&n1,&l1);
 CheckScalar(1,m1,n1);
 /* cross variable size checking */
 C2F(realtimeinit)(&zer,stk(l1));
 LhsVar(1)=0;
 return 0;
}

int intsrealtime(fname)
   char *fname;
{
 int m1,n1,l1;
 CheckRhs(1,1);
 CheckLhs(1,1);
 /*  checking variable t */
 GetRhsVar(1,"d",&m1,&n1,&l1);
 CheckScalar(1,m1,n1);
 /* cross variable size checking */
 C2F(realtime)(stk(l1));
 LhsVar(1)=0;
 return 0;
}

On suppose que ce fichier (tempsreel.c) a été placé dans le répertoire courant. Pour compiler tempsreel.c on peut utiliser la fonction ilib_build de Scilab comme suit :

-->table =["realtimeinit","intsrealtimeinit";
-->        "realtime","intsrealtime"];

-->ilib_build("libtempsreel",table,"tempsreel.o",[])

Cela compile tempsreel.c, génére une librairie libtempsreel.so et un script loader.sce. Enfin, pour charger l'interface on fait :

-->exec loader.sce
 
Maintenant, les deux primitives realtime et realtimeinit sont bien définies dans Scilab et peuvent être utilisées. Noter que la primitive realtimeinit n'a qu'un seul argument scale. Le code de la simulation des balles contient déjà le mode temps-réel, il suffit de décommenter les quatres lignes indiquées par la commentaire ``pour mode temps réel''. On peut alors régler la cadence de l'animation en changenat l'argument de realtimeinit.

Ramine Nikoukhah
Scilab Group
scilab@inria.fr
http://www-rocq.inria.fr/scilab
This document was translated from LATEX by HEVEA.
de realtimeinit.

Ramine Nikoukhah
Scilab Group
scilab@inria.fr
http://www-rocq.inria.fr/scilab
This document was translated from LATEX by HEVEAÿÿÿ