Java3D tutoriel:

Chapitre 3: Les animations

Pré-requis: Chapitre 1: théorie et primitives
Nous contacter

Nous allons découvrir l'utilisation des différentes classes "interpolator" qui s'appliquent aux "TransformGroup" (TG ) et permettent d'animer tous nos objets. Nous n'utiliserons ici que le objets les plus simples possibles pour fixer notre attention sur l'animation. Vous pourrez bien sûr utiliser des objets plus complexes comme ceux des chapitres 1 et 2.

Tout d'abord, de manière générale, lorsque vous construisez chacun de vos TG vous devez spécifier s'il va ou non bouger dans votre monde. Si c'est le cas vous devez lui en donner la capacité avec la méthode "setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);". Puis vous avez besoin d'un fonction du temps, c'est là qu'intervient la classe Alpha dont les paramètres du constructeur utilisés sont le nombre de tours avant l'arrêt de la rotation, et la vitesse de rotation. Ici nous voulons que le cube tourne indéfiniment, il faut alors mettre -1 et nous réglons la vitesse de rotation à 4000 qui est une fréquence (augmenter ce nombre pour ralentir la rotation).
Nous allons faire connaissance avec l'animation la plus simple à mettre en oeuvre: la rotation.

Pour faire varier la rotation du TG en fonction du Alpha que nous avons défini, il convient d'utiliser la classe "RotationInterpolator" qui lie ( par référence) l'Apha à notre TG (d'où les arguments du constructeur ) et définit l'axe ainsi que les angles maximun et minimum de la rotation. Par défaut, la rotation se fait autour de l'axe Y, l'angle minimal est 0, l'angle maximal est 2PI. Ensuite il faut définir une zone d'infuence de cet "interpolator" pour notre TG au delà de laquelle la rotation n'agira plus. Cette zone d'influence correspond à la classe "BoundingSphere" définie par défaut par le contructeur par une sphère de rayon 1 (donc suffisant pour notre cube de 0.5). La méthode "setSchedulingBounds()" permet de lier cette zone avec l'interpolator. Enfin il faut lier parentalement le TG à l'interpolator comme d'habitude.

Dans les exemples de ce chapitre nous ne décrirons que les extraits de codes concernant la construction du BG principal. Vous pourrez bien évidemment télécharger les codes sources complets de tous ces exemples.
Télécharger la source


Organisation générale

// classes nécessaires à la rotation
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Alpha;
import javax.media.j3d.RotationInterpolator;

//crée un regroupement d'objets contenant un objet cube
public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//----------------------début de la création de la rotation--------------------------------

TransformGroup objSpin=new TransformGroup();

// permet de modifier l'objet pendant l'execution
objSpin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

// on crée un fonction de rotation au cours du temps
Alpha rotationAlpha=new Alpha(-1,4000);

// on crée un comportement qui va appliquer la rotation à l'objet voulu
RotationInterpolator rotator=new RotationInterpolator(rotationAlpha,objSpin);

// on définit la zone sur laquelle va s'appliquer la rotation
BoundingSphere bounds=new BoundingSphere();
rotator.setSchedulingBounds(bounds);
objSpin.addChild(rotator);

//----------------------début de la création de la rotation--------------------------------

objRoot.addChild(objSpin);

// on cree un cube qui hérite de la rotation
objSpin.addChild(new ColorCube(0.5));// de rayon 50 cm

return objRoot;

}


Nous allons maintenant découvrir un autre interpolator tres utile le "PositionPathInterpolator" qui permet à un TG de se déplacer au cours du temps en passant par différents points.
Pour cela on associe des positions ( un tableau de Point3f ) à l'échelle du temps representé par un tableau de float. Le constructeur du "PositionPathInterpolator" a besoin de ces 2 tableaux, d'une fontion du temps ( toujours la classe Alpha), du TG auquel l'interpolator sera relié (par référence), et e fin un Transform3D qui sera manipulé par la classe au cours du temps.
Télécharger la source
Organisation générale

 

// classes nécessaires à la translation
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Alpha;
import javax.media.j3d.PositionPathInterpolator;

//crée un regroupement d'objets contenant un objet cube
public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//----------------------début de la création de la translation--------------------------------

Transform3D trans=new Transform3D();
TransformGroup objSpin=new TransformGroup();

// permet de modifier l'objet pendant l'execution
objSpin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

// on crée un fonction de rotation au cour du temps
Alpha transAlpha=new Alpha(-1,6000);

// on crée notre chemin: une matrice de 3 Point3f dans ce cas simple juste 2 positions
// (la derniere permet de boucler): point de départ, point d'arrivée,
// puis retour au point de départ pour permettre de boucler
Point3f[] chemin=new Point3f[3];
chemin[0]=new Point3f(0.8f,0.0f,0.0f);t
chemin[1]=new Point3f(-0.8f,0.0f,0.0f);
chemin[2]=new Point3f(0.8f,0.0f,0.0f);

// on crée une matrice de float qui sert àfaire correspondre
// àchaque point de l'espace un point dans l'échelle du temps qui s'étend de 0 à 1
float[] timePosition={0.0f,0.50f,1.0f};

PositionPathInterpolator interpol=new PositionPathInterpolator(transAlpha,
objSpin,
trans,
timePosition,
chemin);

// on définit la zone sur laquelle va s'appliquer le chemin
BoundingSphere bounds=new BoundingSphere();
interpol.setSchedulingBounds(bounds);
objSpin.addChild( interpol);

//----------------------début de la création de la translation--------------------------------

objRoot.addChild(objSpin);

// on cree un cube qui hérite de la translation
objSpin.addChild(new ColorCube(0.5));// de rayon 50 cm

return objRoot;

}


Nous allons voir maintenant un autre interpolator qui sert à retailler les objets contenus dans un TG. Cet interpolator est représenté par la classe "ScaleInterpolator". Son emploi est très simple, il suffit de spécifier l'Alpha utilisé, le TG à lier, le Transform3D de travail, et enfin le pourcentage de taille initiale et celui de la taille finale en float ou 1.0f représente 100%.
Télécharger la source
Organisation générale

// classes nécessaires à la déformation
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Alpha;
import javax.media.j3d.ScaleInterpolator;

//crée un regroupement d'objets contenant un objet cube
public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//----------------------début de la création de la transformation--------------------------------

Transform3D trans=new Transform3D();
TransformGroup objSpin=new TransformGroup();

// permet de modifier l'objet pendant l'execution
objSpin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

// on crée un fonction de rotation au cour du temps
Alpha myAlpha=new Alpha(-1,6000);

ScaleInterpolator interpol=new ScaleInterpolator(myAlpha,objSpin,trans,0.5f,1.2f);

// on définit la zone sur laquelle va s'appliquer le chemin
BoundingSphere bounds=new BoundingSphere();
interpol.setSchedulingBounds(bounds);
objSpin.addChild( interpol);

//----------------------début de la création de la transformation--------------------------------

objRoot.addChild(objSpin);

// on cree un cube qui hérite de la rtranslation
objSpin.addChild(new ColorCube(0.5));// de rayon 50 cm

return objRoot;

}


Nous allons maintenant voir un exemple de mouvement plus complexe qui sera en fait composé de 3 TG, l'un associé à un interpolator de type "PositionPathInterpolator", et l'autre à un "RotationInterpolator", le dernier servant à définir un rayon de rotation pour notre cube. Le mouvement sera composé d'une rotation globale appliquée à un vecteur servant de rayon, enfin un mouvement d'oscillation sera associé au cube. Le plus simple pour comprendre est de regarder le schéma d'organisation générale.
Télécharger la source
Organisation générale

// classes nécessaires à la déformation
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Alpha;
import javax.media.j3d.PositionPathInterpolator;
import javax.media.j3d.RotationInterpolator;

// crée un regroupement d'objets contenant un objet cube qui va décrire un cercle sur le
// plan izo-y et un mouvement oscillatoire sur le plan izo-teta dans le repere cylindrique
// pour ceux qui connaissent
public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

// -----------début de création de la rotation--------------------------------------------------

TransformGroup objSpin=new TransformGroup();

// permet de modifier l'objet pendant l'execution
objSpin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

// on crée un fonction de rotation au cours du temps
Alpha rotationAlpha=new Alpha(-1,4000);

// on crée un comportement qui va appliquer la rotation à l'objet voulu
RotationInterpolator rotator=new RotationInterpolator(rotationAlpha,objSpin);

// on définit la zone sur laquelle va s'appliquer la rotation
BoundingSphere bounds=new BoundingSphere();
rotator.setSchedulingBounds(bounds);
objSpin.addChild(rotator);

objRoot.addChild(objSpin);

// -------------fin de création de la rotation----------------------------------------------------
// -----------début de création du rayon-------------------------------------------------------

Transform3D rayonT=new Transform3D();
rayonT.set(new Vector3f(0.5f, 0.0f, 0.0f));
TransformGroup rayon=new TransformGroup(rayonT);
objSpin.addChild(rayon);

// --------------fin de création du rayon-------------------------------------------------------
// -----------début de création d'oscillation verticale----------------------------------------

Transform3D oscilT=new Transform3D();
TransformGroup oscil=new TransformGroup();

// permet de modifier l'objet pendant l'execution
oscil.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

// on crée un fonction de rotation au cours du temps
Alpha transAlpha=new Alpha(-1,2000);

// on crée notre chemin: une matrice de 3 Point3f: point de départ, point d'arrivée,
// puis retour au point de départ pour permettre de boucler
Point3f[] chemin=new Point3f[3];
chemin[0]=new Point3f(0.0f,0.05f,0.0f);
chemin[1]=new Point3f(0.0f,-0.05f,0.0f);
chemin[2]=new Point3f(0.0f,0.05f,0.0f);

// on crée une matrice de float qui sert à faire correspondre
// à chaque point de l'espace un point dans l'échelle du temps qui s'étend de 0 à 1
float[] timePosition={0.0f,0.5f,1.0f};

PositionPathInterpolator interpol=new PositionPathInterpolator(transAlpha,oscil,oscilT,timePosition,chemin);

// on définit la zone sur laquelle va s'appliquer le chemin
BoundingSphere bounds2=new BoundingSphere();
interpol.setSchedulingBounds(bounds2);
oscil.addChild( interpol);

rayon.addChild(oscil);

// --------------fin de création d'oscillation verticale------------------------------------------
// on crer un cube
oscil.addChild(new ColorCube(0.2));// de rayon 50 cm

return objRoot;

}


Nous allons maintenant vous permettre d'interagir avec votre environnement grâce à la classe "Behavior". C'est grâce à elle que vous allez pouvoir faire correspondre des évènements à des actions dans votre monde 3d. Il est pratique de définir vos interactions dans une classe dérivant de "Behavior". Cette nouvelle classe doit au moins avoir un constructeur contenant le TG à modifier, une méthode d'initialistion : "initialisatize" définissant les évenements à détecter, et enfin une méthode "processStimulus" décrivant les actions déclanchées par les stimulus.

Nous allons commencer par constuire une classe qui lors de la touche du clavier fera tourner un objet "box". L'évènement à détecter est donc "KeyEvent.KEY_PRESSED". On utilise la méthode "wakeupOn()" héritée de la classe "Behavior" avec un évènement pour spécifier quel évènement doit être détecté par notre classe.
Télécharger les sources behavior1.java et box.java.


Organisation générale

Création de l'interaction permettant la rotation d'un TG suite à la pression d'un touche/
Fichier behavior1.java

// classes Java 3d
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;

// classes nécessaires à l'interaction
import javax.media.j3d.Behavior;
import javax.media.j3d.WakeupOnAWTEvent;
import java.awt.event.KeyEvent;
import java.awt.AWTEvent;
import java.util.Enumeration;

// rotion du TG en entrée autour de l'axe Y
public class behavior1 extends Behavior
{

// variable de travail
private TransformGroup targetTG;
private Transform3D rotation = new Transform3D();
private double angle = 0.0;

// constructeur
behavior1(TransformGroup targetTG)
{
this.targetTG = targetTG;
}

// on définit les évenemenst à détecter
public void initialize()
{
this.wakeupOn(new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED));
}

// on définit l'action de ces évenements
public void processStimulus(Enumeration criteria)
{

// actions :rotation autour de l'axe Y
angle += 0.1;
rotation.rotY(angle);
targetTG.setTransform(rotation);
// on se remet en attente
this.wakeupOn(new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED));

}

}


Pour ajouter une interaction il faut donc définir le TG à manipuler comme pouvant être modifiable, puis céer une instance de votre classe behavior1 référençant (lien par référence) ce même TG, lui définir une zone d'influence, puis faire hériter le TG de ce behavior ( lien parental).
Extrait du fichier box.java

// classe créée pour l'interaction
import javax.media.j3d.BoundingSphere;
import behavior1;

public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//------------ debut creation des apparences -------------
........................voir source........
//------------ fin creation des apparences -----------------
//------------ début de creation d'une porte ----------------

// on créer le TG qui servira à la rotation du behavior1
TransformGroup TG = new TransformGroup();
TG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
........................voir source........
objRoot.addChild(TG);

//------------ fin de creation d'une porte --------------------
//------- début de ajout de l'interaction ---------------------

behavior1 behav=new behavior1(TG);
behav.setSchedulingBounds(new BoundingSphere());
TG.addChild(behav);

//------- fin de ajout de l'interaction ------------------------

return objRoot;

}


Maintenant que vous connaissez les bases de l'interection, nous pouvons passer à un exemple plus conséquent: le contrôle complet de la rotation de notre porte grâce au pavé numérique.
Télécharger les sources behavior2.java et box2java.
Organisation générale

Pour cela, il y a très peu de modification à faire au fichier box.java. Il suffit juste de spécifier que le TG que l'on va manipuler sera disponible non seulement en écriture, mais également en lecture pour, à tout instant, pouvoir récupérer l'orientation de la porte et la modifier.
Extrait du fichier box2.java

// classe créée pour l'interaction
import javax.media.j3d.BoundingSphere;
import behavior2;

public BranchGroup createSceneGraph()
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//------------ debut creation des apparences -------------
........................voir source........
//------------ fin creation des apparences -----------------
//------------ début de creation d'une porte ----------------

// on créer le TG qui servira à la rotation du behavior2
TransformGroup TG = new TransformGroup();
TG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
TG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
........................voir source........
objRoot.addChild(TG);

//------------ fin de creation d'une porte --------------------
//------- début de ajout de l'interaction ---------------------

behavior2 behav=new behavior2(TG);
behav.setSchedulingBounds(new BoundingSphere());
TG.addChild(behav);

//------- fin de ajout de l'interaction ------------------------

return objRoot;

}


Les changements sont par contre plus importants concernant le fichier behavior2.java.
Les évènements détectées sont toujours "touche préssée ?" représentée par la constante "KeyEvent.KEY_PRESSED" et la méthode " initialize()" reste inchangée. La méthode " processStimulus" détermine les actions en réponse aux différents stimulis. La méthode "getAWTEvent()" permet de déterminer quel type de "KeyEvent.KEY_PRESSED", quelle touche à été enfoncée et nous stockons les évènements dans un tableau d'évènements "AWTEvent events[]". On initialise une matrice à la matrice d'identité "rot" ( la matrice identité est un facteur neutre pour la multiplication ). Puis on affecte une rotation de 0.1 selon l'axe voulu à cette matrice. La matrice "rot" est maintenant prête à être multipliée avec la matrice contenant l'orientation du TG. On récupère cette derniere à l'aide de la méthode "getTransform(rotation)" et on la stocke dans la matrice "rotation", puis on les multiplie et on réaffecte la matrice résultant: "rotation" à l'aide de la méthode "setTransform(rotation)" au TG.
Enfin il ne faut pas oublier de réactiver le mode d'attente d'évènements grâce à la méthode "wakeupOn(keyEvent)".
Extrait du fichier behavior2.java

// classes Java 3d
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;

// classes nécessaires à l'interaction
import javax.media.j3d.Behavior;
import javax.media.j3d.WakeupOnAWTEvent;
import java.awt.AWTEvent;
import java.util.Enumeration;
import java.awt.event.KeyEvent;

public class behavior2 extends Behavior
{

// variable de travail
private TransformGroup TG;
private Transform3D rot=new Transform3D();
private Transform3D rotation=new Transform3D();

// Type d'évenements à détecter
private WakeupOnAWTEvent keyEvent=new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);

// constructeur
behavior2(TransformGroup targetTG)
{
     this.targetTG = targetTG;
}

// on définit les évenemenst à détecter
public void initialize()
{
     this.wakeupOn(keyEvent);
}

// on définit l'action de ces évenements
public void processStimulus(Enumeration criteria)
{

// on récupere le tableau d'evenements enregistres
AWTEvent events[]=keyEvent.getAWTEvent();
// on met la matrice à l'identité
rot.setIdentity();
// on recupere la matrice d'orientation actuelle du TG;
TG.getTransform(rotation);

//------ Rotation autour de l'axe Z sens positif assigné à la touche 1 du pavé numérique--------
if (((KeyEvent)events[0]).getKeyCode()==KeyEvent.VK_NUMPAD1)
            rot.rotZ(0.1d);// rotation d'1 degre autour de l'axe Z dans le sens poisitif
else
//------ Rotation autour de l'axe Z sens négatif assigné à la touche 3 du pavé numérique--------
if (((KeyEvent)events[0]).getKeyCode()==KeyEvent.VK_NUMPAD3)
            rot.rotZ(-0.1d);// rotation d'1 degre autour de l'axe Z dans le sens négatif
else
//------ Rotation autour de l'axe X sens positif assigné à la touche 2 du pavé numérique--------
........................... voir source .............................
else
//------ Rotation autour de l'axe X sens négatif assigné à la touche 8 du pavé numérique--------
........................... voir source .............................
else
//------ Rotation autour de l'axe Y sens positif assigné à la touche 6 du pavé numérique--------
........................... voir source .............................
else
//------ Rotation autour de l'axe Y sens négatif assigné à la touche 4 du pavé numérique--------
........................... voir source .............................

// on applique cette rotation a la matrice courante d'orientation du TG
rotation.mul(rot);
// on applique la nouvelle matrice au TG que l'on manipule
TG.setTransform(rotation);

// on se remet en mode d'attente des évenemenst de keyEvent : touche pressée
this.wakeupOn(keyEvent);

}

}


Maintenant que nous savons construire un comportement assez évolué nous pouvons construire un comportement nous permettant de déplacer la caméra dans notre monde virtuel. L'essentiel du travail est ici de récupérer le TG de la caméra. Pour cela, il convient de modifier légèrement la méthode qui construit la scène pour lui ajouter en argument l'univers dans lequel on travaille car c'est lui qui contient la caméra et le TG associé. Jusqu'à présent nous éludions la classe "simpleUniverse" du schéma d'organisation car elle servait peu dans la construction de notre monde et nous ne la configurions pas ( nous ommettons encore certains éléments de SimpleUniverse). On obtient la caméra grâce à la méthode "getViewingPlatform()" de l'univers utilisé, puis on récupere le TG de la caméra grâce à la méthode "getViewPlatformTransform()". Pour que vous puissiez aller partout dans votre monde il faut également que vous définissiez une zone d'influence le entièrement, c'est pour cela que nous n'utilisons plus le constructeur par défaut. La classe de comportement ne nécessite que peu de transformation, il faut juste remplacer certaines des rotations en translations.
Télécharger les sources
world.java et mouvbehav.java
Organisation générale

Extrait du fichier world.java

// classe créée pour l'interaction
import javax.media.j3d.BoundingSphere;
import mouvbehav;

public world()
{

super("- navigation : contrôle de la caméra -");
this.addWindowListener(this);
setLayout(new BorderLayout());
Canvas3D canvas3D = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
add("Center", canvas3D);

SimpleUniverse simpleU = new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().setNominalViewingTransform();

// on a modifier le constructeur pour inclure l'univers et pouvoir ainsi accéder
// à la caméra et interragir avec elle
BranchGroup scene = createSceneGraph(simpleU);
scene.compile();
simpleU.addBranchGraph(scene);

}

public BranchGroup createSceneGraph(SimpleUniverse simpleU)
{

//on crée le Bg principal
BranchGroup objRoot=new BranchGroup();

//------------ debut creation des apparences -------------
........................voir source........
//------------ fin creation des apparences -----------------
//------------ début de creation d'une box ----------------
........................voir source........
//------------ fin de creation d'une box --------------------
//------- début de ajout de l'interaction ---------------------

// On récupere le TG de la caméra
TransformGroup view=simpleU.getViewingPlatform().getViewPlatformTransform();

// l'intéraction aura lieu avec le TG de la caméra (lien par référence)
mouvbehav behav=new mouvbehav(view);

// On définit une zone d'influence très grande pour que l'interaction soit active sur une très grande zone
BoundingSphere influPartout=new BoundingSphere(new Point3d(),1000.0);
behav.setSchedulingBounds(influPartout);

// lien d'héritage
objRoot.addChild(behav);

//------- fin de ajout de l'interaction ------------------------

return objRoot;

}


Maintenant vous pouvez aborder les chapitres suivants:
Chapitre 2: création d'objets à partir d'objet élémentaire