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 :
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 :
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
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 :
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
où 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
où
| 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).

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+a, bj=bj-a, |
| di=di-b, dj=dj+b, |
où a et b
vérifient
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 où
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 :
- 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).
- 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
- 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.
- 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
- 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
- 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

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ÿÿÿ