Java3D tutoriel:

Chapitre 2: création de geométries élementaires

 

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

Comme vous ne pourrez pas toujours utiliser des formes primitives, il va falloir apprendre à créer vos propres objets geométriques à base de polygones de type: ligne, triangle, ... Ce n'est certainement pas la partie la plus distrayante. Cependant, c'est un passage obligatoire si vous voulez réellemnt incorporer à vos scènes des objets aux formes complexes et réalistes.

Ce chapitre se divise en deux sections. La première vous présentera les classes élementaires utiles à l'élaboration des formes. La seconde moins théorique, mettra en oeuvre vos nouvelles connaissances, pour la création d'une forme plus complexe.

Voici les principaux objets que nous manipulerons :

Leurs constructeurs sont tous présents et vous trouverez toutes les indications dans l'indispensable Api réference de Java3D. C'est pourquoi nous n'entrerons pas dans le détail ici. Dans le programme suivant, nous allons implémanter ces objets car, dans ce cas, rien ne sera plus clair que le code.


Exemple 1, les différentes formes élémentaires

Télécharger les codes complets

Extraits de codes

Extrait 1, création d'une ligne, l'axe horizontal :


//ligne des X
LineArray axisX=new LineArray(2,LineArray.COORDINATES|LineArray.COLOR_3);
axisX.setCoordinate(0,new Point3f(-1f,0f,0f));
axisX.setCoordinate(1,new Point3f(1f,0f,0f));
axisX.setColor(0,new Color3f(1f,0f,0f));
axisX.setColor(1,new Color3f(0f,0f,1f));

Extrait 2, création d'un triangle :


//triangle
TriangleArray triangle=new TriangleArray(3,TriangleArray.COORDINATES|TriangleArray.COLOR_3);
triangle.setCoordinate(0,new Point3f(-0.8f,0.2f,0f));
triangle.setCoordinate(1,new Point3f(-0.2f,0.2f,0f));
triangle.setCoordinate(2,new Point3f(-0.5f,0.8f,0f));
triangle.setColor(0,new Color3f(1f,0f,0f));
triangle.setColor(1,new Color3f(0f,1f,0f));
triangle.setColor(2,new Color3f(0f,0f,1f));

Extrait 3, création de deux quadrilatères :


//quadrilatère
QuadArray quad=new QuadArray(8,QuadArray.COORDINATES|QuadArray.COLOR_3);
quad.setCoordinate(0,new Point3f(0.2f,0.4f,0f));
quad.setCoordinate(1,new Point3f(0.6f,0.4f,0f));
quad.setCoordinate(2,new Point3f(0.6f,0.8f,0f));
quad.setCoordinate(3,new Point3f(0.2f,0.8f,0f));
quad.setCoordinate(4,new Point3f(0.6f,0.2f,0f));
quad.setCoordinate(5,new Point3f(0.8f,0.2f,0f));
quad.setCoordinate(6,new Point3f(0.8f,0.4f,0f));
quad.setCoordinate(7,new Point3f(0.6f,0.4f,0f));
for(int i=0;i<4;i++) quad.setColor(i,new Color3f(1f,1f,0f));
for(int i=4;i<8;i++) quad.setColor(i,new Color3f(0f,0f,1f));

Extrait 4, création d'un TriangleStripArray :


//triangles enchainés
int tab[]=new int[1];
tab[0]=4;
TriangleStripArray trStrip=new TriangleStripArray (4 ,TriangleStripArray.COORDINATES|TriangleStripArray.COLOR_3 ,tab);
trStrip.setCoordinate(0,new Point3f(-0.8f,-0.8f,0f));
trStrip.setCoordinate(1,new Point3f(-0.5f,-0.8f,0f));
trStrip.setCoordinate(2,new Point3f(-0.5f,-0.2f,0f));
trStrip.setCoordinate(3,new Point3f(-0.2f,-0.2f,0f));
for(int i=0;i<4;i++) trStrip.setColor(i,new Color3f(1f,0f,1f));

Extrait 5, création d'un TriangleFanArray :


//triangles avec sommet commun
tab[0]=5;
TriangleFanArray trFan=new TriangleFanArray (5 ,TriangleFanArray.COORDINATES|TriangleFanArray.COLOR_3 ,tab);
trFan.setCoordinate(0,new Point3f(0.2f,-0.2f,0f));
trFan.setCoordinate(1,new Point3f(0.2f,-0.8f,0f));
trFan.setCoordinate(2,new Point3f(0.4f,-0.75f,0f));
trFan.setCoordinate(3,new Point3f(0.6f,-0.6f,0f));
trFan.setCoordinate(4,new Point3f(0.75f,-0.3f,0f));
trFan.setColor(0,new Color3f(1f,0f,0f));
for(int i=1;i<5;i++) trFan.setColor(i,new Color3f(0f,1f,0f));

Commentaire des codes

Extrait 1, Line array:

Rien de bien difficile ici. On crée l'axe avec deux points indexés (0 et 1), puis à chacun d'eux on associe une couleur. On remarque que Java3D va alors automatiquement générer un beau dégradé de couleur.

"public LineArray(int vertexCount, int vertexFormat)" est le prototype du constructeur que nous avons utilisé. "vertexCount" définit le nombre de points que l'on utilise pour notre ligne. Dans notre cas, ce sera 2. "vertexFormat" permet de régler un certain nombre de paramètres pour notre géometrie. Nous les détaillons ci dessous :

Ces paramètres sont communs à tous les éléments géometriques que nous emploierons dans la suite du chapitre. Nous ne les redétaillerons pas de nouveau. Suivant les options choisies, vous pourrez ensuite utiliser certaines méthodes suffisamment explicites dans leurs intitulés pour que nous ne les développions pas. C'est le cas de setColor(), setCoordinate() ... Nous vous invitons à consulter l'Api réference pour plus de détails.

Extrait 2, Triangle Array :

"public TriangleArray(int vertexCount, int vertexFormat)" est le constructeur de ce type de triangle. De la même façon que pour la ligne, on fournit simplement le nombre de sommets (vertex) et des paramètres pour la génération de notre forme. Le nombre de sommets doit forcément être supérieur à 3 et être un multiple de 3. Par exemple, si vous créez une instance un TriangleArray à 6 sommets, vous définissez alors deux triangles: le premier constitué des 3 premiers points, le suivant des trois derniers. L'indexage des points commence bien sûr à 0. On obtient de nouveau un dégradé de couleurs en associant aux trois points des couleurs distinctes.

!! Attention !! : j'ai remarqué quelque chose d'étrange qui me paraît plus relevé du bug que d'un choix délibéré : il faut forcément définir les points dans le sens trigonométrique (sens inverse de celui des aiguilles d'une montre). Ceci m'a posé quelques difficultés au début car rien n'est spécifié par Sun à ce sujet dans l'Api référence Java3D. Cette remarque s'appliquera également à la suite du chapitre.

Extrait 3, Quad Array :

"public QuadArray(int vertexCount, int vertexFormat)" permet la création des deux carrés de l'exemple. Il est important de préciser que l'on aurait pu construire n'importe quel autre type de quadrilatère. On obtient ici deux quadrilatères car le nombre de sommets fournis au constructeur est 2*4=8.

Pour le reste, rien de bien nouveau.

Extrait 4, TriangleStripArray :

Constructeur : "public TriangleStripArray(int vertexCount, int vertexFormat, int[] stripVertexCounts)".

Nous attaquons maintenant une forme plus complexe mais également beaucoup plus utile et souple que les précédentes (voir l'exemple 2 de ce même chapitre). Le principe d'enchaîner les triangles permet ainsi de réutiliser pour chaque triangle deux sommets provenant du triangle précédent dans la figure. Les dessin suivant illustre le principe :

Ainsi on a défini une géométrie constituée de 4 trianlges avec seulement 6 sommets alors que si nous avions dû le faire avec un "TriangleArray " cela aurait demandé 12 sommets, soit le double. De plus, il est intéressant de savoir que tout calcul en 3D, au niveau d'un processeur, se fait sur des triangles élémentaires. Donc, toute forme à base de triangle est plus facile à calculer car il n'y a pas besoin de la décomposer d'abord en triangles.

Il reste un point à éclaircir : à quoi correspond ce tableau d'entiers figurant comme le dernier paramètre du constructeur ? En fait, il permet dans un seul objet ""TriangleStripArray" de définir plusieurs ensembles de triangles distincts. Ainsi, la dimension du tableau correspond au nombre de figures différentes et le contenu de chaque section du tableau donnera le nombre de sommets pour chaque figure. Si l'on passe en paramètre "int tab[3]={5,3,8}", Java3D génèrera alors trois sous-"TriangleStripArray" avec respectivement 5,3 et 8 sommets. Chose importante, le nombre "vertexCount" doit être égal à la somme de toutes les cases du tableau, sans quoi une exception sera générée. Ainsi, pour l'exemple précédent, "vertexCount" devrait valoir 5+3+8=16.

Extrait 5, TriangleFanArray :

Constructeur : "TriangleFanArray(int vertexCount, int vertexFormat, int[] stripVertexCounts)".

Proche dans le principe et dans l'utilisation de TriangleStripArray, cet objet partage pour tous les triangles un sommet commun (sur l'exemple ce sommet est représenté avec une couleur rouge).

Si vous avez compris l'extrait 4, il n'y a rien de plus à ajouter (le tableau, en dernier paramètre, joue le même rôle).

Avec cet exemple, vous avez maintenant une idée des formes élementaires utilisables pour l'élaboration de votre scène. Associées aux formes primitives définies par le Java3D (cf chapitre 1), elles vous premettront de créer toutes les formes que vous voulez avec plus ou moins de difficulté suivant leur complexité. Dans l'exemple suivant, vous comprendrez de façon plus pratique l'utilité de vos nouvelles connaissances.


Exemple 2, création d'un lampadaire

Télécharger les codes complets

L'exemple suivant se propose de rendre les choses plus pratiques et également plus intéressantes: nous allons créer le premier objet réel et ce à partir de triangles élementaires. Cependant, le code est, pour cet exemple, beaucoup plus difficile à comprendre que ceux des exemples précédents. Il va falloir vous armer de patience car le jeu en vaut la chandelle. Après cela vous pourrez enfin construire vos objets et les incorporer à vos scènes. De plus, la programmation est beaucoup plus soignée qu'auparavant : nous avons créé l'objet lampe de manière totalement indépendante (il se trouve dans un .java totalement à part). Ainsi, l'objet lampe est totalement réutilisable, c'est le principe de la modularité (le source est organisé en modules, portions de codes indépendantes les unes des autres); il constitue donc un bon exemple à suivre pour vos futures réalisations.

Extraits de codes

Extrait 1, notre propre Geometry :


private Geometry mkGeometry0(float radius1,float radius2,float height,int nbPane,Color3f color)
{
int tab[]=new int[1];
tab[0]=(nbPane*2)+2;
TriangleStripArray geom0=new TriangleStripArray((nbPane*2)+2, TriangleStripArray.COORDINATES|TriangleStripArray.COLOR_3|, tab);
Color3f blue=new Color3f(0f,0f,1f);
Color3f yellow=new Color3f(1f,1f,0f);

for(int i=0;i<(nbPane*2)+2;i++) geom0.setColor(i,color);

double angle=2*Math.PI/nbPane;

Point3f point=new Point3f();
for(int i=0;i<nbPane;i++)
{

if (i==0)
{
point.x=(float)(radius1*Math.cos(0*angle));
point.y=(float)height;
point.z=(float)(radius1*Math.sin(0*angle));
geom0.setCoordinate(0,point);

point.x=(float)(radius2*Math.cos(0*angle));
point.y=0f;
point.z=(float)(radius2*Math.sin(0*angle));
geom0.setCoordinate(1,point);

point.x=(float)(radius1*Math.cos(1*angle));
point.y=(float)height;
point.z=(float)(radius1*Math.sin(1*angle));
geom0.setCoordinate(2,point);

point.x=(float)(radius2*Math.cos(1*angle));
point.y=0f;
point.z=(float)(radius2*Math.sin(1*angle));
geom0.setCoordinate(3,point);

}
else

{
point.x=(float)(radius1*Math.cos((i+1)*angle));
point.y=(float)height;
point.z=(float)(radius1*Math.sin((i+1)*angle));
geom0.setCoordinate((i+1)*2,point);

point.x=(float)(radius2*Math.cos((i+1)*angle));
point.y=0f;
point.z=(float)(radius2*Math.sin((i+1)*angle));
geom0.setCoordinate((i+1)*2+1,point);
}

}

return geom0;
}


Extrait 2, notre propre Appearence :


private Appearance createApp(String str)
{
Appearance app=new Appearance();
PolygonAttributes polyAttrib = new PolygonAttributes();
polyAttrib.setCullFace(PolygonAttributes.CULL_NONE);
if (str.equals("LINE")) polyAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE);
app.setPolygonAttributes(polyAttrib);

return app;
}

Extrait 3, notre propore Shape3D :


public BranchGroup createLampe(String str0,String str1,String str2)
{
BranchGroup myScene=new BranchGroup();

//les geometries
Shape3D abatjour=new Shape3D(mkGeometry0(0.2f,0.3f,0.3f,25,new Color3f(1f,0f,0f)),createApp(str0));
Shape3D body=new Shape3D(mkGeometry0(0.03f,0.03f,1.2f,4,new Color3f(1f,1f,0f)),createApp(str1));
Shape3D socle=new Shape3D(mkGeometry0(0.025f,0.15f,0.1f,14,new Color3f(0f,1f,0f)),createApp(str2));

//les transformations
TransformGroup trTot=mkTgTr(vect);
TransformGroup trD=mkTgTr(new Vector3f(0f,-0.6f,0f));
trD.addChild(socle);
trD.addChild(body);
TransformGroup trU=mkTgTr(new Vector3f(0f,0.5f,0f));
trU.addChild(abatjour);

trTot.addChild(trD);
trTot.addChild(trU);

myScene.addChild(trTot);
myScene.compile();
return myScene;
}

Commentaire des codes

Un peu de théorie est nécessaire pour bien saisir le cheminement de l'application. Quand vous manipulez des formes géometriques en Java3D, celles-ci sont constituées d'une géométrie et d'une apparence respectivement gérées par un objet "Geometry et Appearance". Ces deux objets sont entièrement paramétrables et contiennent énormément d'informations sur l'objet que vous allez créer. Dans cet exemple, notre lampe est en fait constituée d'une seule forme élementaire, que nous avons créée grâce aux objets "Geometry et Appearance". En effet, l'abat-jour, le corps et le socle sont en fait 3 instances du même Shape3D mais avec des constructeurs différents.

Voici l'allure de ce Shape3D.

Vous pouvez télécharger ce petit exemple intermédiaire ici . Cela est même vivement conseillé car les extraits 1 et 2 de cette section sont exactement les mêmes dans ce petit exemple et dans celui de la lampe.

Extrait 1, la geométrie :

Constructeur : "private Geometry mkGeometry0(float radius1,float radius2,float height,int nbPane,Color3f color)".

Pour construire cette forme il faut fournir les paramètres suivants :

Pour bien comprendre le code de création de la géometrie, il vous faudra juste des bases de trigonométrie et un peu de patience car essayer de l'expliquer textuellement me semble quasi-impossible. Mais je suis sûr que vous y arriverez !!

Extrait 2, l'apparence :

C'est beaucoup plus simple pour cet exemple. Cependant, il faut savoir que l'objet apparence est l'un des plus importants pour gérer le rendu de vos Shape3D. Une bonne connaissance de cette classe vous permettra de créer des effets de transparence (avec setTransparencyAttribute()), plaquer des textures (nous y reviendrons dans les chapitres suivants). Dans notre cas, on l'utilise pour activer la représentation filaire (LINE) ou faces pleines (FILL, qui est le mode par défaut) de notre objet. On peut voir l'effet, de la modification de paramètre sur l'exemple de la lampe: à gauche les trois parties sont en mode filaire, au centre seul l'abat-jour est en mode filaire et tout à droite les trois Shape3D sont en mode faces-pleines.

Donc, la fonction de cet extrait crée une apparence, puis grâce à la méthode setPolygonMode() à laquelle on passe un objet PolygonAttributes (paramétré préalablement en mode filaire ou faces-pleines), on définit le type d'apparence puis on la retourne. Il serait instructif que vous consultiez l'Api reference pour l'objet Appearance, vous aurez ainsi une idée de l'ensemble des paramètres réglables.

Extrait 3, la forme finale :

Enfin, on instancie 3 Shape3D avec notre Geometry construite par la méthode mkGeometry0() et notre apparence construite par la méthode createApp(). On les positionne correctement grâce à des TransformGroup et des Transform3D et pour finir on renvoie un BranchGroup qui pourra être directement intégré à notre scène.

Pour finalement inclure notre objet, on utilise deux lignes de code de type :


lampe lp0=new lampe(this,new Vector3f(-0.9f,-0.1f,0f));
scene.addChild(lp0.createLampe("LINE","LINE","LINE"));

Maintenant vous pouvez aborder les chapitres suivants:
Chapitre 3: animation
Chapitre 4: texturation