Introduction
Nous allons maintenant essayer d'animer un peu nos scènes. Pour cela, BIFS propose deux façons de faire : soit en utilisant des interpolateurs et des générateurs d'évènements (à la VRML), soit de manière continue par ajouts, modifications, suppressions d'éléments au fur et à mesure de la lecture de la scène (par exemple envoyés par un serveur).
Une animation à base d'interpolateurs
Pour pouvoir générer une animation à base d'interpolateurs, nous avons besoin de quatre choses : un noeud, un propriété de ce noeud à animer, ici on utilisera un noeud de transformation (Transform2D), un noeud générateur d'événements temporels (TimeSensor), un noeud interpolateur (Interpolator) et une liaison entre les générateurs d'événements et les interpolateurs ainsi qu'une liaison entre les interpolateurs et les noeuds à animer (Route).
> Le noeud Transform2D
Le noeud Transform2D est LE noeud de transformations 2D d'objets graphiques. Voici la syntaxe associée :
<Transform2D center='0 0' rotationAngle='0.0' scale='1 1' scaleOrientation='0.0' translation='0 0'>
<children>... primitives graphiques ou autres noeuds ...</children>
</Transform2D>
Le fonctionnement de ce noeud est très simple et à la fois très puissant. Ce noeud applique des transformations à tous les noeuds visuels situés dans les sous-arbres de noeuds contenus dans le champ children. Ces transformations sont les suivantes :
- Translation de vecteur le champ translation (couple de réels),
- Mise à l'échelle bidimensionnelle suivant le champ scale (couple de facteurs de mise à l'échelle). Une éventuelle rotation d'angle scaleOrientation (réel exprimé en radians) peut-être appliquée avant mise à l'échelle et la rotation inverse est appliquée après mise à l'échelle.
- Rotation de centre le champ center (couple de réels) et d'angle le champ rotationAngle (réel exprimé en radians).
Ces opérations sont appliquées dans l'ordre suivant : mise à l'échelle, puis rotation, et enfin translation.
Reprenons l'exemple du rectangle, appliquons-lui quelques transformations et voyons le résultat.
|
Type de transformation |
Exemple |
Une simple translation |
rect4.xml, rect4.mp4 |
Une utilisation de la rotation |
rect5.xml, rect5.mp4 |
Une utilisation de la mise à l'échelle |
rect6.xml, rect6.mp4 |
Une utilisation combinée pour faire une transformation de type 'skew' (en français glissement) |
rect7.xml, rect7.mp4 |
Exercices
> Exercice 6
Créer une scène avec un cercle de 50 pixels de rayon centré en (0,0), un rectangle de 100x200 pixels, centré en (20,20) et un carré tourné de 45°, de 60 pixels de côté, centré en (-20,20).
> Le mécanisme de Route
Ce mécanisme représente la clé de toute animation à base d'interpolateurs. Il permet de transmettre des informations d'un noeud à l'autre. Plus précisément, une Route relie un champ émetteur d'évènements (eventOut) à un champ récepteur (eventIn). Pour bien comprendre ce mécanisme, il est nécessaire d'expliquer un peu plus la notion de noeud et de champ. Nous avons fait connaissance jusqu'à présent avec un certain nombre de noeuds (Rectangle, Transform2D, Shape, OrderedGroup ...). Chaque noeud possède un certain nombre de champs (order, emissiveColor, size ...). Chaque champ possède une propriété intrinsèque qui définit s'il est émetteur d'événements, récepteur, les deux ou ni l'un ni l'autre. On les qualifie alors respectivement de champ de type évènementiel eventOut, eventIn, exposedField ou field. A peu près tous les champs que nous avons vus jusqu'à présent sont de type évènementiel exposedField.
Une Route relie un exposedField ou eventOut à un eventIn ou exposedField. Pour qu'une Route puisse être établie entre 2 champs, il faut que ceux-ci contiennent des données de type compatible. Pour cela, BIFS définit 22 types de champ qui sont décrits dans la table ci-dessous :
Type de champ |
Description du contenu du champ |
SFBool |
Booléen (e.g. TimeSensor.enabled, IndexedLineSet2D.colorPerVertex) |
SFFloat |
Réel (e.g. LineProperties.lineWidth, Transform2D.rotationAngle) |
SFTime |
Réel destiné à contenir un temps |
SFInt32 |
Entier relatif (LineProperties.lineStyle) |
SFString |
Chaîne de charactères |
SFVec3f |
Triplet de réels destiné à contenir par exemple les coordonnées d'un point dans un espace 3D |
SFVec2f |
Couple de réels destiné à contenir par exemple les coordonnées d'un point ou d'un vecteur dans un espace 2D (e.g.Transform2D.scale) |
SFColor |
Triplet de réels représentant les 3 composantes RGB d'une couleur. Chaque réel est compris entre 0 et 1 (Material2D.emissiveColor) |
SFRotation |
Quadruplet de réels représentant un vecteur 3D directeur de la rotation et l'angle de cette rotation |
SFImage |
Tableau de pixels. Ce type de champ est très rarement utilisé. |
SFNode |
Contient un noeud qui à son tour pourra éventuellement contenir des noeuds (e.g. IndexedFaceSet2D.color, Shape.material) |
MFBool |
Liste de valeurs de type SFBool |
MFFloat |
Liste de valeurs de type SFFloat |
MFTime |
Liste de valeurs de type SFTime |
MFInt32 |
Liste de valeurs de type SFInt32 (e.g. IndexedFaceSet2D.colorIndex, OrderedGroup.order) |
MFString |
Liste de valeurs de type SFString |
MFVec3f |
Liste de valeurs de type SFVec3F |
MFVec2f |
Liste de valeurs de type SFVec2F (e.g. Coordinate2D.point) |
MFColor |
Liste de valeurs de type SFColor (e.g. Color.color) |
MFRotation |
Liste de valeurs de type SFRotation |
MFImage |
Liste de valeurs de type SFImage |
MFNode |
Liste de valeurs de type SFNode (e.g. OrderedGroup.children, Transform2D.children) |
Enfin, une Route établit une liaison particulière entre 2 champs de 2 instances de noeuds (éventuellement différents). Cela nécessite une manière d'identifier les noeuds dont on va utiliser les champs. On utilise alors le mécanisme DEF pour donner un identifiant à un noeud. Le mécanisme DEF peut être appliqué à tout noeud. La syntaxe est très simple, il suffit d'ajouter au noeud l'attribut XMT DEF. La valeur de cet attribut est une chaîne de caractères, qui constitue un identifiant unique de ce noeud.
Exemples :
<Transform2D scale='1 2' DEF='Y-Scaling'>...</Transform2D>
<Shape DEF='UnCarré'>...</Shape>
<IndexedLineSet2D DEF='Ligne1' colorPerVertex='false'>...</IndexedLineSet2D>
La syntaxe pour définir une Route en XMT est la suivante :
<Route fromNode='Noeud1' fromField='field1' toNode='Noeud2' toField='field2'/>
Dans cette syntaxe, 'Noeud1' est la même chaîne de caractères que celle de l'attribut DEF du noeud auquel appartient le champ émetteur. 'Noeud2' est la même chaîne de caractères que celle de l'attribut DEF du noeud auquel appartient le champ récepteur. 'field1' est le nom du champ émetteur et 'field2' celui du champ récepteur. Les noms 'Noeud1' et 'Noeud2' sont définis par l'auteur alors que les noms 'field1' et 'field2' sont ceux spécifiés par BIFS.
> Le noeud TimeSensor
Le noeud TimeSensor permet de générer périodiquement des événements temporels. La période est spécifiée en secondes par le champ cycleInterval. Le champ startTime spécifie quand doit commencer la génération des événements. On se contentera ici de laisser la valeur par défaut 0 qui signifie que ce noeud est actif dès le début de la scène. Le champ stopTime spécifie l'instant auquel la génération d'événements doit s'arrêter. De même, on se contentera d'utiliser la valeur par défaut -1 qui signifie que ce noeud restera actif durant toute la durée de la scène. Le champ loop indique si la génération d'événements doit continuer après un temps égal à cycleInterval, i.e. au-delà d'une période. Bien sûr tous ces champs ne sont significatifs que si le champ enabled indique 'true' (valeur par défaut). S'il indique 'false', le noeud n'est pas actif, i.e. aucun événement n'est généré.
Le noeud TimeSensor génère 4 eventOuts différents : cycleTime, isActive, fraction_changed et time. Nous allons nous intéresser seulement à fraction_changed. Le champ fraction_changed est un eventOut de type SFFloat, c'est-à-dire un réel. Ce réel est compris entre 0 et 1 et correspond à la fraction de période écoulée depuis le début de la période. Ainsi, si la période courante a commencé à l'instant t, que l'instant courant est t+dt et que cycleInterval vaut T alors fraction_changed vaut dt/T.
La syntaxe XML est la suivante :
<TimeSensor cycleInterval='1'/>
> Les interpolateurs :
BIFS dispose de nombreux interpolateurs : de coordonnées 3D, d'orientations 3D, de normales 3D, de positions 3D, de coordonnées 2D, de positions 2D, de scalaires et de couleurs. Les noeuds correspondants sont respectivement CoordinateInterpolator, OrientationInterpolator, NormalInterpolator, PositionInterpolator, CoordinateInterpolator2D, PositionInterpolator2D, ScalarInterpolator et ColorInterpolator. Nous ne nous intéresserons qu'aux 4 derniers puisqu'ils peuvent être utilisés dans un contexte 2D. Tous les interpolateurs possèdent 4 champs communs : 2 exposedfields nommés key, de type MFFloat, et keyValue, dont le type dépend de l'interpolateur lui-même; un eventIn nommé set_fraction toujours de type SFFloat et un eventOut nommé value_changed dont le type dépend aussi de l'interpolateur. Les différents types sont résumés dans la table ci-dessous.
Noeuds Interpolateurs |
Type du champ keyValue |
Type de valeurs interpolées |
|
PositionInterpolator2D, CoordinateInterpolator2D |
MFVec2F |
SFVec2F |
|
ColorInterpolator |
MFColor |
SFColor |
|
ScalarInterpolator |
MFFloat |
SFFloat |
|
Les interpolateurs réalisent une interpolation linéaire par morceaux sur l'ensemble des réels.
La fonction d'interpolation est définie par les n valeurs réelles du champ key,
appelées clés, et les n valeurs d'interpolations du champ keyValue.
Les clés doivent être croissantes mais ne sont pas restreintes à un intervalle particulier.
Un interpolateur utilise la valeur du temps t reçue dans le champ set_fraction
pour évaluer la fonction d'interpolation f(t). S'il y a n clés possibles (t0, t1, t2, ..., tn-1)
dans le champ key, l'ensemble des réels est partitionné
en n+1 intervalles : ]-infini, t0[, [t0, t1[, [t1, t2[, ... , [tn-1, +infini[. Si de plus, le champ contient les
n valeurs (v0, v1, v2, ..., vn-1) alors la valeur f(t) est obtenue comme suit :
f(t) = v0, si t <= t0,
f(t) = vn-1, si t >= tn-1,
f(t) = l'ordonnée du point de coordonnées (t,v) appartenant à
la droite définie par les point (ti,vi) et (ti+1,vi+1)
Deux clés consécutives peuvent être identiques, ce qui autorise les discontinuités de la fonction f(t). Cependant, la valeur à l'instant précis correspondant à la clé dupliquée est indéterminée.
La syntaxe est la suivante :
<PositionInterpolator2D key='0 1' keyValue='0 0 40 40'/>
Un tel interpolateur produira par exemple l'événement (30,30) dans son champ value_changed s'il reçoit dans son champ set_fraction la valeur 0.75.
> Une scène animée complète
Nous avons maintenant tous les éléments pour générer une scène complète animée à base d'interpolateurs. La scène suivante présente le déplacement du centre d'un rectangle de la position (0,0) à la position (40,40) en 10 secondes : anim1.xml, anim1.mp4.
<children>
<Transform2D DEF='Deplace'>
<children><Shape><geometry><Rectangle size='50 40'/></geometry></Shape></children>
</Transform2D>
<TimeSensor DEF='Timer' cycleInterval='10'/>
<PositionInterpolator2D DEF='Interp' key='0 1' keyValue='0 0 40 40'/>
</children>
</OrderedGroup>
<Route fromNode='Timer' fromField='fraction_changed' toNode='Interp' toField='set_fraction'/>
<Route fromNode='Interp' fromField='value_changed' toNode='Deplace' toField='translation'/>
Exercices
> Exercice 7
1. Changer la période du noeud TimeSensor et les ajouter des
clés pour obtenir une animation plus fluide et plus rapide.
2. Animer la rotation de ce rectangle autour de son centre.
3. Animer la couleur de remplissage du rectangle.
Une animation à base de mise à jour BIFS (update) :
Une des caractéristiques fondamentales d'une scène BIFS est qu'elle peut-être découpée en trames à peu près comme une séquence vidéo.
Une vidéo est formée une suite d'images auxquelles on associe une estampille temporelle. On parle de flux vidéo. La lecture du flux vidéo correspond à l'affichage des différentes images aux instants indiqués par leur estampilles. De plus, pour le codage d'un flux vidéo, certaines images sont codées sans aucune relation avec la précédente image (e.g. la première image) et d'autres sont codées par prédiction entre images.
Similairement, une scène BIFS est formée d'une suite de morceaux de scène auxquels on associe une estampille temporelle. On parle alors de flux BIFS. La lecture du flux BIFS consiste à l'affichage des différents morceaux de scènes aux instants indiqués par les estampilles. De plus, certains morceaux de scène peuvent correspondre à une scène complète, qui ne nécessite pas de connaissance a priori sur la scène (e.g. le premier morceau de scène), et d'autres peuvent correspondre à des mises à jour de la scène précédente. Dans tous les cas, on parle de mise à jour BIFS, (BIFS update). A partir de là, un flux BIFS diffère d'un flux vidéo car il existe 2 types de flux BIFS selon la nature des mises à jour. Il existe un type de flux pour effectuer des mise à jour allant de la simple modification de la valeur d'un champ d'un noeud à la suppression ou le remplacement d'un sous-arbre complet de la scène. Pour ce type de flux, on dit que les mises à jour BIFS sont des trames de commandes BIFS (BIFS command frames). Le second type de flux est utilisé pour mettre à jour un nombre réduit de valeur de champ de manière continue et plus compressée. On dit alors que les mises à jour BIFS sont des trames d'animation BIFS (BIFS animation frames). Nous allons uniquement nous consacrer à l'étude du premier type de flux.
> Structure d'un document XMT
Pour pouvoir ajouter des mises à jour BIFS dans un document XMT, il est nécessaire de commenter les parties inexpliquées du document vide.xml.
Un document XMT commence par l'élément XML XMT-A. Cet élément possède un élément header et un élément body. Nous expliquerons plus tard le contenu du header. Jusqu'à présent l'élément body a toujours contenu la syntaxe suivante :
<Replace>
<Scene>
<OrderedGroup> Le contenu de la scène </OrderedGroup>
... éventuellement une liste de Routes ...
</Scene>
</Replace>
</body>
L'élément body indique le début du flux BIFS proprement dit. L'estampille temporelle implicitement associée à cet élément est celle correspondant à T = 0. L'élément Replace indique qu'il s'agit d'une commande BIFS de remplacement. Son sous-élément Scene indique qu'on remplace toute la scène (qui était vide auparavant) avec le contenu de Scene (le sous-arbre de l'élément OrderedGroup et les éventuelles Routes). Cela définit implicitement un flux de trames de commandes BIFS dont la première trame contient une seul commande, celle du remplacement de scène, dont l'estampille temporelle est 0.
Pour ajouter une trame de commandes BIFS à ce flux à l'aide de la description XMT, il suffit d'ajouter la syntaxe suivante dans l'élément body, à la suite de l'élément Replace :
<par begin='1.5'> .... </par>
L'attribut begin décrit l'estampille temporelle en secondes associée à cette trame de commande BIFS. Le contenu de l'élément par est une suite de commandes BIFS qui seront appliquées, dans l'ordre du document, à l'instant décrit par begin.
> Les commandes BIFS
Il existe trois types de commandes BIFS : une commande de remplacement (Replace), une commande d'insertion (Insert) et une commande d'enlèvement (Delete).
> La commande Replace
Comme nous l'avons vu auparavant la commande Replace permet de remplacer toute la scène si son contenu est l'élément Scene. Mais elle permet aussi de remplacer un noeud ou une Route qui possède un identifiant (cf. commande DEF) ou la valeur d'un champ d'un noeud possédant aussi un identifiant.
Signification de la commande |
Syntaxe |
Pour remplacer un noeud |
<Replace atNode='Identifiant'> ... le noeud (voire sous-arbre) de remplacement ... </Replace> |
Pour remplacer une Route |
<Replace atRoute='Identifiant'> ... la Route de remplacement ... </Replace> |
Pour remplacer la valeur d'un champ de type différent de SFNodeou MFNode |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp' value='Valeur'/> |
Pour remplacer la valeur d'un champ de type SFNode |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp'> ... noeud de remplacement ... </Replace> |
Pour remplacer le premier, dernier ou n-ième noeud d'un champ de type MFNode |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp'
position='FIRST|END|n'> ... noeud de remplacement ... </Replace> |
Pour remplacer le premier, dernier ou n-ième noeud d'un champ de type différent de MF* |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp'
position='FIRST|END|n' value='ValeurARemplacer'/> |
Prenons notre premier exemple de scène (rect.xml) et remplaçons le rectangle, après 2 secondes, par le rectangle utilisé dans notre second exemple de scène (rect2.xml).
Le résultat obtenu est le suivant : update1.xml, update1.mp4.
NOTE : Bien que le noeud Shape ait été remplacé par un autre noeud Shape, le nouveau noeud Shape ne possède pas d'identifiant.
Exercices
> Exercice 8
A partir de l'exemple précédent, remplacer après 4 secondes la couleur du trait de contour (attention, il faudra donner un identifiant à un des objets apparus à la deuxième seconde).
> La commande Insert
La commande Insert permet d'insérer un noeud ou une route et également d'insérer une valeur de type SF dans un champ de type MF à une position précise.
Signification de la commande |
Syntaxe |
Pour insérer un noeud en première position, en dernière position ou à la n-ième position d'un champ de type MFNode : |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp'
position='FIRST|END|n'> ... noeud de remplacement ... </Replace> |
Pour insérer une valeur en première position, en dernière position ou à la n-ième position d'un champ de type MF différent de MFNode : |
<Replace atNode='IdentifiantDuNoeud' atField='NomDuChamp' position='FIRST|END|n' value='ValeurAInsérér'/> |
Pour insérer une Route : |
<Insert> ... la Route à insérer ... </Insert> |
Pour insérer une Route et lui donner un identifiant : |
<Insert atRoute='Identifiant'> ... la Route à insérer ... </Insert> |
Prenons l'exemple précédent (update1.xml) et insérons un cercle de rayon 10 pixels centré en (20, 0) à l'instant t=5 s. Les fichiers obtenus sont : update2.xml et update2.mp4 .
Exercices
> Exercice 9
Reprendre l'exemple de l'IndexedLineSet2D et ajouter des points et des couleurs à t = 3s.
> La commande Delete
La commande Delete permet de supprimer un noeud, une route ou une valeur de type SF d'un champ de type MF en donnant sa position.
Signification de la commande |
Syntaxe |
Pour supprimer un noeud : |
<Delete atNode='IdentifiantDuNoeud' /> |
Pour supprimer une valeur de type SF en première position, en dernière position ou à la n-ième position d'un champ de type MF: |
<Delete atNode='IdentifiantDuNoeud' atField='NomDuChamp' position='FIRST|END|n'/> |
Pour supprimer une Route : |
<Delete atRoute='IndetifiantDeRoute/> |
Pour insérer une Route et lui donner un identifiant : |
<Insert> ... la Route à insérer ... </Insert> |
Reprenons l'exemple précédent et supprimons le rectangle à la date t = 7s. Les fichiers obtenus sont : update3.xml et update3.mp4.
Exercices
> Exercice 10
Reprendre l'exemple d'animation à base d'interpolateurs et détruire une route au milieu de l'animation. Que se passe-t-il ?
Conclusion
Dans cette partie, nous avons vu comment appliquer des transformations à des objets graphiques. Ensuite, nous avons vu comment utiliser les mécanismes de DEF, de Routes et des interpolateurs pour animer ces transformations. Enfin nous avons abordé une des caractéristiques principales de BIFS : les commandes BIFS. Vous voilà encore plus au courant de ce que peux faire BIFS. Avec tous les outils que nous avons abordés ici et dans le chapitre précédent sur les primitives graphiques 2D, vous devriez être capable avec du temps et du courage ... de faire un dessin animé uniquement en BIFS.