I. Le graphe, une façon de représenter un objet complexe▲
Code source du tutoriel
Imaginons que nous voulions modéliser une trottinette.
Nous aimerions que le guidon puisse tourner par rapport à la base.
Que les deux roues soient indépendantes.
Modélisé la trottinette avec une seule maille, ne rendrait pas les choses faciles pour animer le guidon et les roues.
En effet on serait obligé de la recalculée à chaque rotation d’une roue ou du guidon.
C’est pour cela que nous allons utiliser, quatre mailles différentes et indépendantes.
Nous allons aussi vouloir stocker d’autres informations que les mailles, par exemple leur couleur.
Pour cela, on crée un objet 3D, qui contiendra la maille ainsi que d’autres informations, comme la couleur.
Notre trottinette sera donc constituée de quatre objets 3D :
- Un objet 3D avec la maille modélisant la base.
- Un objet 3D avec la maille modélisant le guidon.
- Un objet 3D avec la maille modélisant la roue avant.
- Un objet 3D avec la maille modélisant la roue arrière.
Ainsi, il suffira de bouger l’un de ces objets sans modifier les mailles pour créer une animation.
À chaque fois que la base bouge, on veut que les trois autres objets la suivent du même mouvement.
Sinon, la trottinette se « casserait ».
Le plus simple est de placer relativement ces trois objets par rapport à la base.
Ainsi, à chaque mouvement de la base, toutes les parties de la trottinette bougent de manière cohérente.
Quand on tourne le guidon, on souhaite que la roue avant fasse de même.
En suivant le même raisonnement, la roue avant sera plutôt placée relativement au guidon.
On a donc la base. Le guidon et la roue arrière placés relativement à la base.
Et la roue avant placée relativement au guidon.
On peut voir les choses ainsi, la base est le parent.
Elle a pour enfant direct le guidon et la roue arrière.
Le guidon quant à lui a pour enfant direct la roue avant.
Ce qui peut se représenter en graphe
Ce raisonnement peut se reproduire pour tout un tas d’objets complexes
À chaque fois nous pouvons modéliser les parties indépendantes avec un graphe formé d’un ou plusieurs objets 3D indépendants.
Le fait de pour bouger les uns par rapport aux autres n’est pas le seul cas où l’on va découper en plusieurs objets.
Par exemples si le rendu est différent d’une partie à l’autre.
Par exemple une porte en bois vitrée.
On aura un objet pour la partie bois, et au moins un pour la partie vitre.
Ainsi la partie boiss pourra être opaque et la ou les parties vitre semi-transparentes.
II. Les nœuds▲
Il est parfois utile de pouvoir placer des objets 3D relativement à un point virtuel.
Ce point virtuel est un peu comme un objet 3D qui n’aurait pas de maille.
Ce point virtuelle s’appelle un nœud.
En général on dit qu’un objet 3D est un nœud possédant une maille. Qui donne, d'un point de vue conception objet :
III. Le graphe de scène▲
L’idée est de généraliser ce que nous venons de voir à toute la scène.
La scène dans son ensemble peut être perçue comme un objet très complexe.
Du coup on peut utiliser la représentation sous forme de graphe en lui donnant un nœud principal nommé racine.
Tous les objets de la scène seront relativement par rapport à ce nœud.
Ainsi pour bouger toute la scène, il suffit de déplacer ce nœud racine.
En décrivant ainsi la scène on obtient le graphe de scène.
Par exemple
IV. Mise en place du graphe de scène▲
Comme nous l’avons vu, le graphe de scène est composé de nœuds (les objets 3D héritant de nœud)
Avant de créer un nœud, parlons de comment décrire sa position dans la 3D.
IV-A. Les nœuds et leur description de position▲
On veut non seulement pouvoir les positionner avec (X, Y, Z), mais aussi pouvoir faire des rotations et changer les facteurs d’échelle.
Pour la position (X, Y, Z) un triplet de 3 floats feront l’affaire.
Pour la rotation, on pourrait spécifier l’axe de rotation et un angle. Mais dans la pratique ce n’est pas très simple à utiliser.
Le plus simple est de définir un angle de rotation autour de chacun des trois axes X, Y et Z.
Donc trois floats, un pour chaque angle, feront l’affaire.
Pour les facteurs d’échelles on a à un sur chaque axe, donc également 3 floats.
Il faut bien garder en tête que cette description est relative au nœud parent.
Sauf bien sûr pour le nœud racine pour qui elles sont absolues
On ajoute au nœud ces enfants dans une liste de nœuds.
Ce qui donne pour le moment :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNulltool.NonNull;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Objects;
/**
* Nœud virtuel de la scènescène
*/
public
class
Node3D
{
/**
* Coordonne X de la position dans la 3D
*/
public
float
x =
0
f;
/**
* Coordonne Y de la position dans la 3D
*/
public
float
y =
0
f;
/**
* Coordonne Z de la position dans la 3D
*/
public
float
z =
0
f;
/**
* Angle de la rotation autour de l'axe X
*/
public
float
angleX =
0
f;
/**
* Angle de la rotation autour de l'axe Y
*/
public
float
angleY =
0
f;
/**
* Angle de la rotation autour de l'axe Z
*/
public
float
angleZ =
0
f;
/**
* Facteur d'échelle le long de l'axe X
<
br/
>
* La valeur doit toujours être strictement positive
*/
public
float
scaleX =
1
f;
/**
* Facteur d'échelle le long de l'axe Y
<
br/
>
* La valeur doit toujours être strictement positive
*/
public
float
scaleY =
1
f;
/**
* Facteur d'échelle le long de l'axe Z
<
br/
>
* La valeur doit toujours être strictement positive
*/
public
float
scaleZ =
1
f;
/**
* Liste des nœuds placés relativement à celui-ci
*/
private
final
List<
Node3D>
children =
new
ArrayList<>(
);
public
Node3D
(
)
{
}
/**
* Ajoute un nœud enfant
*/
public
final
void
addChild
(
@NonNull
final
Node3D node)
{
Objects.requireNonNull
(
node, "node must not be null"
);
// On synchronize pour éviter l'accès concurrent
synchronized
(
this
.children)
{
this
.children.add
(
node);
}
}
}
IV-B. « Dessiner » le nœud avec OpenGL▲
On a les propriétés du nœud, maintenant il faut dire à OpenGL de les utiliser afin de placer notre nœud.
Le principe va être de placer le nœud, puis chacun de ces enfants relativement à celui-ci de manière récursive.
On peut toujours considérer que lors du placement d'un nœud la matrice actuelle est celle du parent.
Cela marche aussi pour le nœud racine en voyant le parent comme le repère de départ.
Pour ne pas perturber les éventuels autres enfants due son parent, un nœud va devoir restaurer la matrice du parent comme à l'origine à la fin.
Comme nous l'avons vu dans l'introduction, OpenGL possède une mécanisme pour cela : la pile des matrices.
C'est pour cela que le code du placement va ressembler à :
package
fr.developez.tutorial.java.dimension3.render;
// ...
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
org.lwjgl.opengl.GL11;
// ...
/**
* Nœud virtuel de la scène
*/
public
class
Node3D
{
// ...
/**
* Dessine le noeud
*/
@ThreadOpenGL
final
void
drawNode
(
)
{
// On sauvegarde la matrice actuelle
GL11.glPushMatrix
(
);
// TODO placer le nœud et ces enfants
// On restaure la matrice sauvegardée
GL11.glPopMatrix
(
);
}
}
Comme dit dans l'introduction, l'ordre des instructions de placements est important.
On veut que les rotations et les facteurs d'échelles soit relatifs à la position du nœud.
On va donc d'abord placer le nœud en (X, Y, Z)
On veut que les facteurs d'échelles n'influencent pas les rotations, ils seront donc après.
L'ordre des rotations des rotations entre elles n'a pas d'importance.
Tourner sur soi-même vers la droite, puis vers le haut, revient à tourner vers le haut, puis vers la droite, par exemple.
Par habitude, on mettra la rotation autour de X, puis Y, enfin Z.
Donc l'ordre sera
- Position (X,Y,Z)
- Rotation autour de X
- Rotation autour de Y
- Rotation autour de Z
- Facteurs d'échelles
package
fr.developez.tutorial.java.dimension3.render;
// ...
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
org.lwjgl.opengl.GL11;
// ...
/**
* Noeud virtuel de la scène
*/
public
class
Node3D
{
// ...
/**
* Dessine le noeud
*/
@ThreadOpenGL
final
void
drawNode
(
)
{
// On sauvegarde la matrice actuelle
GL11.glPushMatrix
(
);
// *** Position du noeud ***
// Position aux coordonnées X, Y, Z
GL11.glTranslatef
(
this
.x, this
.y, this
.z);
// Rotation autour de l'axe X
GL11.glRotatef
(
this
.angleX, 1
f, 0
f, 0
f);
// Rotation autour de l'axe Y
GL11.glRotatef
(
this
.angleY, 0
f, 1
f, 0
f);
// Rotation autour de l'axe Z
GL11.glRotatef
(
this
.angleZ, 0
f, 0
f, 1
f);
// Application des facteurs d'échelle
GL11.glScalef
(
this
.scaleX, this
.scaleY, this
.scaleZ);
// TODO placer les enfants
// On restaure la matrice sauvegardée
GL11.glPopMatrix
(
);
}
}
Avant de placer les enfants, nous allons préparer l'affichage spécifique. Pour un nœud générique, il n'y aura rien à y faire.
Mais, pour les objets 3D nous permettra de dessiner leur maille
package
fr.developez.tutorial.java.dimension3.render;
// ...
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
org.lwjgl.opengl.GL11;
// ...
/**
* Nœud virtuel de la scène
*/
public
class
Node3D
{
// ...
/**
* Dessine le noeud
*/
@ThreadOpenGL
final
void
drawNode
(
)
{
// On sauvegarde la matrice actuelle
GL11.glPushMatrix
(
);
// *** Position du nœud ***
// Position aux coordonnées X, Y, Z
GL11.glTranslatef
(
this
.x, this
.y, this
.z);
// Rotation autour de l'axe X
GL11.glRotatef
(
this
.angleX, 1
f, 0
f, 0
f);
// Rotation autour de l'axe Y
GL11.glRotatef
(
this
.angleY, 0
f, 1
f, 0
f);
// Rotation autour de l'axe Z
GL11.glRotatef
(
this
.angleZ, 0
f, 0
f, 1
f);
// Application des facteurs d'échelle
GL11.glScalef
(
this
.scaleX, this
.scaleY, this
.scaleZ);
// Dessin spécifique
this
.drawSpecific
(
);
// TODO placer les enfants
// On restaure la matrice sauvegardée
GL11.glPopMatrix
(
);
}
/**
* Dessin supplémentaire pour le nœud
*
<
p
>
* A surcharger par les classes enfants
*
<
p
>
* Ne fait rien par défaut
*/
@ThreadOpenGL
void
drawSpecific
(
)
{
// Rien à faire d'autres. Les classes enfants y mettront leur code spécifique
}
}
Et maintenant, par récursion ajoutons les enfants
package
fr.developez.tutorial.java.dimension3.render;
// ...
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
org.lwjgl.opengl.GL11;
// ...
/**
* Noeud virtuel de la scène
*/
public
class
Node3D
{
// ...
/**
* Dessine le noeud
*/
@ThreadOpenGL
final
void
drawNode
(
)
{
// On sauvegarde la matrice actuelle
GL11.glPushMatrix
(
);
// *** Position du noeud ***
// Position aux coordonnées X, Y, Z
GL11.glTranslatef
(
this
.x, this
.y, this
.z);
// Rotation autour de l'axe X
GL11.glRotatef
(
this
.angleX, 1
f, 0
f, 0
f);
// Rotation autour de l'axe Y
GL11.glRotatef
(
this
.angleY, 0
f, 1
f, 0
f);
// Rotation autour de l'axe Z
GL11.glRotatef
(
this
.angleZ, 0
f, 0
f, 1
f);
// Application des facteurs d'échelle
GL11.glScalef
(
this
.scaleX, this
.scaleY, this
.scaleZ);
// Dessin specific
this
.drawSpecific
(
);
// Placement des noeuds enfants relativement à la position de ce noeud
// On synchronize pour éviter l'accès concurrent
synchronized
(
this
.children)
{
for
(
final
Node3D child : this
.children)
{
child.drawNode
(
);
}
}
// On restaure la matrice sauvegardée
GL11.glPopMatrix
(
);
}
// …
}
Cet algorithme nous posera des soucis quand nous parleront de transparence dans un tutoriel dédié aux matériaux. Nous vous indiquerons à ce moment-là, la solution. Pour le moment, il nous suffit pour ce tutoriel.
IV-C. Les objets 3D et les mailles▲
Comme nous l'avons vu dans l'introduction, pour se dessiner, un objet 3D doit être constitué d'une maille.
Une maille est un ensemble de faces
Chaque face décrit un polygone de 3 ou 4 cotés en général.
On va définir les faces par les sommets du polygone.
Comme nous le verrons quand nous parlerons des textures ou des effets de lumière, le sommet possédera d'autre informations que ses coordonnées (X, Y, Z).
En infographie cela s'appelle un vertex.
Pour le moment il sera simplement, ce qui suit, il sera enrichit dans des prochains tutoriels.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
/**
* Vertex décrivant un sommet d'une face d'une maille.
*
<
br/
>
* Le vertex contient non seulement les coordonnées du point,
* mais également d'autre informations utiles pour les textures et la lumiere
*/
public
class
Vertex
{
public
float
x =
0
f;
public
float
y =
0
f;
public
float
z =
0
f;
public
Vertex
(
)
{
}
public
Vertex
(
float
x, float
y, float
z)
{
this
.x =
x;
this
.y =
y;
this
.z =
z;
}
}
Maintenant que nous avons notre vertex, on peut décrire une face
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
package
fr.developez.tutorial.java.dimension3.render.mesh;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
java.util.ArrayList;
import
java.util.Collections;
import
java.util.List;
import
java.util.Objects;
import
java.util.function.Consumer;
/**
* Décrit une face d'une maille
*/
public
final
class
Face
{
private
final
List<
Vertex>
vertexs =
new
ArrayList<>(
);
public
Face
(
Vertex... vertexs)
{
if
(
vertexs !=
null
)
{
Collections.addAll
(
this
.vertexs, vertexs);
}
}
public
void
addVertex
(
float
x, float
y, float
z)
{
this
.addVertex
(
new
Vertex
(
x, y, z));
}
public
void
addVertex
(
@NonNull
final
Vertex vertex)
{
Objects.requireNonNull
(
vertex, "vertex must not be null"
);
synchronized
(
this
.vertexs)
{
this
.vertexs.add
(
vertex);
}
}
public
void
forEachVertex
(
@NonNull
final
Consumer<
Vertex>
action)
{
Objects.requireNonNull
(
action, "action must not be null"
);
synchronized
(
this
.vertexs)
{
for
(
final
Vertex vertex : this
.vertexs)
{
action.accept
(
vertex);
}
}
}
}
Quant à la maille
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.render.mesh.Face;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Objects;
/**
* Maille d'un objet
*/
public
class
Mesh
{
private
final
List<
Face>
faces =
new
ArrayList<>(
);
public
Mesh
(
)
{
}
public
void
addFace
(
@NonNull
final
Face face)
{
Objects.requireNonNull
(
face, "face must not be null"
);
synchronized
(
this
.faces)
{
this
.faces.add
(
face);
}
}
}
Et l'objet 3D
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
java.util.Objects;
/**
* Objet 3D
*/
public
class
Object3D
extends
Node3D
{
@NonNull
public
final
Mesh mesh =
new
Mesh
(
);
public
Object3D
(
)
{
}
}
IV-D. Un peu de couleurs▲
On voudrait pouvoir appliquer une couleur à notre objet.
Au lieu de stocker les quatre informations d'une couleur, l'intensité de rouge, vert, bleu ainsi que le niveau d'opacité.
Nous allons créer un objet qui représente une couleur, ce qui nous permettra de créer quelques couleurs de bases.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.MathTools;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
org.lwjgl.opengl.GL11;
/**
* Représente une couleur
*/
public
class
Color4f
{
public
static
final
Color4f BLACK =
new
Color4f
(
0
f);
public
static
final
Color4f DARK_GREY =
new
Color4f
(
0.25
f);
public
static
final
Color4f GREY =
new
Color4f
(
0.5
f);
public
static
final
Color4f LIGHT_GREY =
new
Color4f
(
0.75
f);
public
static
final
Color4f WHITE =
new
Color4f
(
1
f);
public
static
final
Color4f DARK_RED =
new
Color4f
(
0.5
f, 0
f, 0
f);
public
static
final
Color4f RED =
new
Color4f
(
1
f, 0
f, 0
f);
public
static
final
Color4f LIGHT_RED =
new
Color4f
(
1
f, 0.5
f, 0.5
f);
public
static
final
Color4f DARK_GREEN =
new
Color4f
(
0
f, 0.5
f, 0
f);
public
static
final
Color4f GREEN =
new
Color4f
(
0
f, 1
f, 0
f);
public
static
final
Color4f LIGHT_GREEN =
new
Color4f
(
0.5
f, 1
f, 0.5
f);
public
static
final
Color4f DARK_BLUE =
new
Color4f
(
0
f, 0
f, 0.5
f);
public
static
final
Color4f BLUE =
new
Color4f
(
0
f, 0
f, 1
f);
public
static
final
Color4f LIGHT_BLUE =
new
Color4f
(
0.5
f, 0.5
f, 1
f);
/**
* Intensité de rouge
*/
public
final
float
red;
/**
* Intensité de vert
*/
public
final
float
green;
/**
* Intensité de bleu
*/
public
final
float
blue;
/**
* Niveau d'opacité
*/
public
final
float
alpha;
/**
* Construit la couleur à partir d'un description ARGB
*
*
@param
argb
Description ARGB
*/
public
Color4f
(
int
argb)
{
this
((
float
) ((
argb >>
16
) &
0xFF
) /
255
f,
(
float
) ((
argb >>
8
) &
0xFF
) /
255
f,
(
float
) (
argb &
0xFF
) /
255
f,
(
float
) ((
argb >>
24
) &
0xFF
) /
255
f);
}
/**
* Construit une couleur avec niveau de gris opaque
*
*
@param
grey
Niveau de gris
*/
public
Color4f
(
float
grey)
{
this
(
grey, grey, grey, 1
f);
}
/**
* Construit une couleur avec niveau de gris
*
*
@param
grey
Niveau de gris
*
@param
alpha
Niveau d'opacité
*/
public
Color4f
(
float
grey, float
alpha)
{
this
(
grey, grey, grey, alpha);
}
/**
* Construit une couleur opaque
*
*
@param
red
Intensité de rouge
*
@param
green
Intensité de vert
*
@param
blue
Intensité de bleu
*/
public
Color4f
(
float
red, float
green, float
blue)
{
this
(
red, green, blue, 1
f);
}
/**
* Construit une couleur
*
*
@param
red
Intensité de rouge
*
@param
green
Intensité de vert
*
@param
blue
Intensité de bleu
*
@param
alpha
Niveau d'opacité
*/
public
Color4f
(
float
red, float
green, float
blue, float
alpha)
{
this
.red =
MathTools.bounds
(
red, 0
f, 1
f);
this
.green =
MathTools.bounds
(
green, 0
f, 1
f);
this
.blue =
MathTools.bounds
(
blue, 0
f, 1
f);
this
.alpha =
MathTools.bounds
(
alpha, 0
f, 1
f);
}
/**
* Demande à OpenGL d'utilisé cette couleur
*/
@ThreadOpenGL
void
apply
(
)
{
GL11.glColor4f
(
this
.red, this
.green, this
.blue, this
.alpha);
}
}
La méthode `apply` permettra de demander à l'OpenGL d'utiliser la couleur.
On ajoute l'information de couleur à notre objet 3D
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
java.util.Objects;
/**
* Objet 3D
*/
public
class
Object3D
extends
Node3D
{
private
Color4f color =
Color4f.GREY;
@NonNull
public
final
Mesh mesh =
new
Mesh
(
);
public
Object3D
(
)
{
}
@NonNull
public
Color4f getColor
(
)
{
return
this
.color;
}
public
void
setColor
(
@NonNull
final
Color4f color)
{
this
.color =
Objects.requireNonNull
(
color, "color must not be null"
);
}
}
IV-E. Dessiner la maille avec OpenGL▲
Pour chaque face de la maille, on la dessine grâce à ses sommets.
On doit dire à OpenGL où commence et où finit le polygone.
Le nouveau code de la maille
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.render.mesh.Face;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Objects;
import
org.lwjgl.opengl.GL11;
/**
* Maille d'un objet
*/
public
class
Mesh
{
private
final
List<
Face>
faces =
new
ArrayList<>(
);
public
Mesh
(
)
{
}
public
void
addFace
(
@NonNull
final
Face face)
{
Objects.requireNonNull
(
face, "face must not be null"
);
synchronized
(
this
.faces)
{
this
.faces.add
(
face);
}
}
@ThreadOpenGL
void
drawMesh
(
)
{
synchronized
(
this
.faces)
{
for
(
final
Face face : this
.faces)
{
// Annonce à OpenGL le début du polygone
GL11.glBegin
(
GL11.GL_POLYGON);
face.forEachVertex
(
vertex ->
GL11.glVertex3f
(
vertex.x, vertex.y, vertex.z));
// Annonce à OpenGL la fin du polygone
GL11.glEnd
(
);
}
}
}
}
Comme pour le moment un vertex n'est composé que des coordonnées, on les précise à OpenGL. Quand nous auront plus d'informations, on mettra les instructions supplémentaires au sein de la lambda
Remarque
Devoir dessiner toutes les faces à chaque boucle de rendu n'est pas très optimisé.
Heureusement, comme nous le verrons un peu plus loin, OpenGL à un mécanisme pour nous aider.
Puis il nous reste plus qu'a complété l'objet pour que celui-ci puisse être dessiné par l'OpenGL
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.Objects;
/**
* Objet 3D
*/
public
class
Object3D
extends
Node3D
{
private
Color4f color =
Color4f.GREY;
@NonNull
public
final
Mesh mesh =
new
Mesh
(
);
public
Object3D
(
)
{
}
@NonNull
public
Color4f getColor
(
)
{
return
this
.color;
}
public
void
setColor
(
@NonNull
final
Color4f color)
{
this
.color =
Objects.requireNonNull
(
color, "color must not be null"
);
}
@ThreadOpenGL
@Override
void
drawSpecific
(
)
{
this
.color.apply
(
);
this
.mesh.drawMesh
(
);
}
}
IV-F. Un exemple d’objet 3D : la boîte▲
Afin d'avoir un objet de base à représenter créons une boîte
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
package
fr.developez.tutorial.java.dimension3.geometry;
import
fr.developez.tutorial.java.dimension3.render.Object3D;
import
fr.developez.tutorial.java.dimension3.render.mesh.Face;
/**
* Une boîte
*/
public
class
Box
extends
Object3D
{
public
Box
(
)
{
// Face de la boîte
Face face =
new
Face
(
);
face.addVertex
(-
0.5
f, 0.5
f, 0.5
f);
face.addVertex
(
0.5
f, 0.5
f, 0.5
f);
face.addVertex
(
0.5
f, -
0.5
f, 0.5
f);
face.addVertex
(-
0.5
f, -
0.5
f, 0.5
f);
this
.mesh.addFace
(
face);
// Dos de la boîte
face =
new
Face
(
);
face.addVertex
(-
0.5
f, -
0.5
f, -
0.5
f);
face.addVertex
(
0.5
f, -
0.5
f, -
0.5
f);
face.addVertex
(
0.5
f, 0.5
f, -
0.5
f);
face.addVertex
(-
0.5
f, 0.5
f, -
0.5
f);
this
.mesh.addFace
(
face);
// Haut de la boîte
face =
new
Face
(
);
face.addVertex
(-
0.5
f, 0.5
f, -
0.5
f);
face.addVertex
(
0.5
f, 0.5
f, -
0.5
f);
face.addVertex
(
0.5
f, 0.5
f, 0.5
f);
face.addVertex
(-
0.5
f, 0.5
f, 0.5
f);
this
.mesh.addFace
(
face);
// Bas de la boîte
face =
new
Face
(
);
face.addVertex
(-
0.5
f, -
0.5
f, 0.5
f);
face.addVertex
(
0.5
f, -
0.5
f, 0.5
f);
face.addVertex
(
0.5
f, -
0.5
f, -
0.5
f);
face.addVertex
(-
0.5
f, -
0.5
f, -
0.5
f);
this
.mesh.addFace
(
face);
// Coté droit de la boîte
face =
new
Face
(
);
face.addVertex
(
0.5
f, -
0.5
f, 0.5
f);
face.addVertex
(
0.5
f, 0.5
f, 0.5
f);
face.addVertex
(
0.5
f, 0.5
f, -
0.5
f);
face.addVertex
(
0.5
f, -
0.5
f, -
0.5
f);
this
.mesh.addFace
(
face);
// Coté gauche de la boîte
face =
new
Face
(
);
face.addVertex
(-
0.5
f, -
0.5
f, -
0.5
f);
face.addVertex
(-
0.5
f, 0.5
f, -
0.5
f);
face.addVertex
(-
0.5
f, 0.5
f, 0.5
f);
face.addVertex
(-
0.5
f, -
0.5
f, 0.5
f);
this
.mesh.addFace
(
face);
}
}
Comme on le voit, il suffit de créer chaque face de la boîte en faisant bien attention au sens des sommets pour que la partie extérieure soit visible.
IV-G. Le graphe de scène▲
Maintenant que tout est prêt, on peut créer le graphe de scène.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.Objects;
import
org.lwjgl.opengl.GL11;
/**
* Le graphe de scène
*/
public
class
SceneGraph
{
@NonNull
public
final
Node3D root =
new
Node3D
(
);
private
Color4f backgroundColor =
Color4f.WHITE;
public
SceneGraph
(
)
{
}
@NonNull
public
Color4f getBackground
(
)
{
return
this
.backgroundColor;
}
public
void
setBackground
(
@NonNull
final
Color4f backgroundColor)
{
this
.backgroundColor =
Objects.requireNonNull
(
backgroundColor, "backgroundColor must not be null"
);
}
@ThreadOpenGL
void
drawScene
(
)
{
GL11.glClearColor
(
this
.backgroundColor.red, this
.backgroundColor.green, this
.backgroundColor.blue, 1
f);
this
.root.drawNode
(
);
}
}
On remarque, que l'on a ajouté l'information de couleur de fond afin de pouvoir facilement la changer.
IV-H. Lier le graphe de scène avec la boucle de rendue▲
Maintenant, on peut ajouter notre graphe de scène à la boucle de rendu
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.GLU;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.Objects;
import
org.lwjgl.glfw.GLFW;
import
org.lwjgl.opengl.GL11;
import
org.lwjgl.opengl.GL12;
/**
* Boucle de redu
*/
class
Render3D
{
/**
* Graphe de scène associé
*/
@NonNull
final
SceneGraph sceneGraph =
new
SceneGraph
(
);
Render3D
(
)
{
}
// ...
/**
* Dessine la scène 3D
*/
@ThreadOpenGL
private
void
drawScene
(
)
{
this
.sceneGraph.drawScene
(
);
}
}
Exposons le graphe de scène afin de pouvoir décrire une scène.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.thread.ThreadManager;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.nio.IntBuffer;
import
java.util.Objects;
import
org.lwjgl.glfw.Callbacks;
import
org.lwjgl.glfw.GLFW;
import
org.lwjgl.glfw.GLFWErrorCallback;
import
org.lwjgl.glfw.GLFWVidMode;
import
org.lwjgl.opengl.GL;
import
org.lwjgl.system.MemoryStack;
import
org.lwjgl.system.MemoryUtil;
public
class
Window3D
{
// ...
@NonNull
public
SceneGraph getSceneGraph
(
)
{
return
this
.render3D.sceneGraph;
}
// ...
}
IV-I. Exemple de scène▲
Un petit exemple pour utiliser ce qu’on nous venons d’écrire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
package
fr.developez.tutorial.java.dimension3;
import
fr.developez.tutorial.java.dimension3.geometry.Box;
import
fr.developez.tutorial.java.dimension3.render.Color4f;
import
fr.developez.tutorial.java.dimension3.render.SceneGraph;
import
fr.developez.tutorial.java.dimension3.render.Window3D;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
public
class
MainBlueAndGreenCubes
{
public
static
void
main
(
String[] args)
{
final
Window3D window3D =
new
Window3D
(
800
, 600
, "Tutoriel 3D - Une boite blue et une verte"
);
MainBlueAndGreenCubes.drawScene
(
window3D.getSceneGraph
(
));
}
private
static
void
drawScene
(
@NonNull
final
SceneGraph sceneGraph)
{
final
Box blueBox =
new
Box
(
);
final
Box greenBox =
new
Box
(
);
sceneGraph.root.addChild
(
blueBox);
sceneGraph.root.addChild
(
greenBox);
blueBox.x =
-
1
f;
blueBox.z =
-
2
f;
blueBox.angleX =
22.5
f;
blueBox.angleY =
22.5
f;
blueBox.setColor
(
Color4f.BLUE);
greenBox.x =
1
f;
greenBox.z =
-
2
f;
greenBox.angleX =
22.5
f;
greenBox.angleY =
22.5
f;
greenBox.setColor
(
Color4f.GREEN);
}
}
Ce qui donne
On remarque au passage que la construction et la modification de la scène ne sont pas faite dans le thread OpenGL.
Ainsi l'utilisateur de la fenêtre n'a pas à s'en occuper
V. Optimiser le dessin de la maille▲
La maille ne va pas changer souvent.
Même, la plupart du temps, une fois définit, elle ne va pas changer du tout.
Pour cela nous allons voir deux optimisations au sein de la maille elle même.
L'une OpenGL, l'autre Java.
V-A. Optimisation OpenGL : La notion de liste▲
L'idée de cette optimisation, est de dire à la carte 3D de se rappelée l'ensemble des faces de notre maille.
Pour cela OpenGL introduit la notion de liste d'instructions.
Une liste d'instructions va permettre de sauvegarder dans la carte 3D les différentes instructions OpenGL pour construire notre maille.
Cela va même plus loin, en fait cela va permettre de lui dire qu'elles font parties d'un même bloc et donc pouvoir les pré-compiler et les optimiser.
Bref en plus de gagner du temps en n'envoyant plus systématiquement toutes les instructions OpenGL.
On va gagner du temps car la carte 3D va utiliser directement un format à elle et optimiser pour elle.
Nous allons avoir besoin de garder une référence sur la liste. Cette référence sera un entier positif ou nul. C'est la seule chose que l'on peut être sûr.
Comme le pointeur sur la fenêtre 3D, c'est une valeur opaque, c'est-à-dire que seule la carte 3D connaît sa vraie signification.
Mais tout ce que nous avons besoin de savoir c'est qu'elle est unique par liste et positive ou nulle.
Si la maille change, il faudra dire à la carte 3D d'oublier notre liste devenue obsolète et en créer une autre
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.render.mesh.Face;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Objects;
import
org.lwjgl.opengl.GL11;
/**
* Maille d'un objet
*/
public
class
Mesh
{
private
final
List<
Face>
faces =
new
ArrayList<>(
);
/**
* Identifiant OpenGL de la liste des polygones décrivant la maille
*/
private
int
idList =
-
1
;
/**
* Indique que la maille à été modifiée et donc a besoin d'être rafraîchie par OpenGL
*/
private
boolean
needReconstructTheList =
true
;
public
Mesh
(
)
{
}
public
void
addFace
(
@NonNull
final
Face face)
{
Objects.requireNonNull
(
face, "face must not be null"
);
synchronized
(
this
.faces)
{
this
.faces.add
(
face);
}
// On a modifié la maille, il faut donc la rafraîchir
this
.needReconstructTheList =
true
;
}
/**
* Indique qu'on a modifier l'une de faces ou l'un des vertex d'une des faces depuis l'extérieur.
*
* Dans ce cas on a besoin de rafraîchir la maille
*/
public
void
forceRefresh
(
)
{
this
.needReconstructTheList =
true
;
}
@ThreadOpenGL
void
drawMesh
(
)
{
// On va créer la liste des polygones la première fois et chaque fois que nécessaire
if
(
this
.idList <
0
||
this
.needReconstructTheList)
{
// On va construire la liste, donc on aura plus besoin de le faire au prochain tour
this
.needReconstructTheList =
false
;
// On efface l'ancienne liste de la mémoire de la carte graphique, celle-ci étant devenu obsolète
if
(
this
.idList >=
0
)
{
GL11.glDeleteLists
(
this
.idList, 1
);
}
// On demande une nouvelle référence de liste à la carte graphique
this
.idList =
GL11.glGenLists
(
1
);
// On indique le début de la liste
GL11.glNewList
(
this
.idList, GL11.GL_COMPILE);
synchronized
(
this
.faces)
{
for
(
final
Face face : this
.faces)
{
// Annonce à OpenGL le début du polygone
GL11.glBegin
(
GL11.GL_POLYGON);
face.forEachVertex
(
vertex ->
GL11.glVertex3f
(
vertex.x, vertex.y, vertex.z));
// Annonce à OpenGL la fin du polygone
GL11.glEnd
(
);
}
}
// On indique la fin de la liste
GL11.glEndList
(
);
}
// On dessine la liste mémorisé
GL11.glCallList
(
this
.idList);
}
}
Plus de détails :
- `GL11.glDeleteLists(this.idList, 1)` libère de la carte 3D la liste. Étant devenue obsolete, autant libéré la mémoire qui lui correspond.
- `GL11.glGenLists(1)` demande et réserve une nouvelle référence de liste. Cette référence ne sera plus attribuée tant qu'elle n'est pas détruite.
- `GL11.glNewList(this.idList, GL11.GL_COMPILE)` Indique le début de la description de la liste. On a choisi l'option `COMPILE` afin de demander de la compiler à la fin afin de profiter au maximum des optimisations coté carte 3D.
- `GL11.glEndList()` Indique la fin de la liste, et lance sa compilation. À noter que rien n'est dessiner à l'écran entre le `glNewList` et cette instruction.
- `GL11.glCallList(this.idList)` demande d'afficher le contenu de la liste. Ici notre maille.
V-B. Optimisation Java : La notion de scellé▲
Nous avons maintenant la description de la maille en mémoire sur la carte 3D.
Si on est sûr que la maille ne va plus changer, on peut effacer de la mémoire de la JVM la description de celle-ci.
Nous allons ajouter la possibilité de sceller la maille. C'est-à-dire, la figée pour de bon.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.render.mesh.Face;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Objects;
import
java.util.concurrent.atomic.AtomicBoolean;
import
org.lwjgl.opengl.GL11;
/**
* Maille d'un objet
*/
public
final
class
Mesh
{
private
final
List<
Face>
faces =
new
ArrayList<>(
);
/**
* Identifiant OpenGL de la liste des polygones décrivant la maille
*/
private
int
idList =
-
1
;
/**
* Indique que la maille à été modifiée et donc a besoin d'être rafraîchit par OpenGL
*/
private
boolean
needReconstructTheList =
true
;
/**
* Indique si la maille est scellée
*/
private
AtomicBoolean sealed =
new
AtomicBoolean
(
false
);
public
Mesh
(
)
{
}
/**
* Indique si la maille est scellée
*/
public
boolean
sealed
(
)
{
return
this
.sealed.get
(
);
}
/**
* Scelle la maille, elle ne pourra plus être modifiée
*/
public
void
seal
(
)
{
if
(
this
.sealed.compareAndSet
(
false
, true
))
{
// On s'assure de prendre les éventuelles dernières modifications
this
.needReconstructTheList =
true
;
}
}
public
void
addFace
(
@NonNull
final
Face face)
{
Objects.requireNonNull
(
face, "face must not be null"
);
// Si la maille est scellée, on n'ajoute pas la face
if
(
this
.sealed.get
(
))
{
return
;
}
synchronized
(
this
.faces)
{
this
.faces.add
(
face);
}
// On a modifié la mail, il faut donc la rafraîchir
this
.needReconstructTheList =
true
;
}
/**
* Indique qu'on a modifier l'une de faces ou l'un des vertex d'une des faces depuis l'extérieur.
*
<
p
>
* Dans ce cas on a besoin de rafraîchir la maille
*/
public
void
forceRefresh
(
)
{
// La maille ne peut changée que si elle n'est pas scellée
if
(!
this
.sealed.get
(
))
{
this
.needReconstructTheList =
true
;
}
}
@ThreadOpenGL
void
drawMesh
(
)
{
// On va créer la liste des polygones la première fois et chaque fois que nécessaire
if
(
this
.idList <
0
||
this
.needReconstructTheList)
{
// On va construire la liste, donc on aura plus besoin de le faire au prochain tour
this
.needReconstructTheList =
false
;
// On efface l'ancienne liste de la mémoire de la carte graphique, celle-ci étant devenu obsolète
if
(
this
.idList >=
0
)
{
GL11.glDeleteLists
(
this
.idList, 1
);
}
// On demande une nouvelle référence de liste à la carte graphique
this
.idList =
GL11.glGenLists
(
1
);
// On indique le début de la liste
GL11.glNewList
(
this
.idList, GL11.GL_COMPILE);
synchronized
(
this
.faces)
{
for
(
final
Face face : this
.faces)
{
// Annonce à OpenGL le début du polygone
GL11.glBegin
(
GL11.GL_POLYGON);
face.forEachVertex
(
vertex ->
GL11.glVertex3f
(
vertex.x, vertex.y, vertex.z));
// Annonce à OpenGL la fin du polygone
GL11.glEnd
(
);
}
}
// On indique la fin de la liste
GL11.glEndList
(
);
// Si la maille est scellée, il s'agit de la dernière construction de la liste, on peut libérer la description de la maille de la mémoire
if
(
this
.sealed.get
(
))
{
this
.faces.clear
(
);
}
}
// On dessine la liste mémorisée
GL11.glCallList
(
this
.idList);
}
}
Changeons un peu l'exemple de tout à l'heure pour illustrer notre propos
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package
fr.developez.tutorial.java.dimension3;
import
fr.developez.tutorial.java.dimension3.geometry.Box;
import
fr.developez.tutorial.java.dimension3.render.Color4f;
import
fr.developez.tutorial.java.dimension3.render.SceneGraph;
import
fr.developez.tutorial.java.dimension3.render.Window3D;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
public
class
MainBlueAndGreenCubes
{
public
static
void
main
(
String[] args)
{
final
Window3D window3D =
new
Window3D
(
800
, 600
, "Tutoriel 3D - Une boite bleue et une verte"
);
MainBlueAndGreenCubes.drawScene
(
window3D.getSceneGraph
(
));
}
private
static
void
drawScene
(
@NonNull
final
SceneGraph sceneGraph)
{
final
Box blueBox =
new
Box
(
);
blueBox.mesh.seal
(
);
final
Box greenBox =
new
Box
(
);
greenBox.mesh.seal
(
);
sceneGraph.root.addChild
(
blueBox);
sceneGraph.root.addChild
(
greenBox);
blueBox.x =
-
1
f;
blueBox.z =
-
2
f;
blueBox.angleX =
22.5
f;
blueBox.angleY =
22.5
f;
blueBox.setColor
(
Color4f.BLUE);
greenBox.x =
1
f;
greenBox.z =
-
2
f;
greenBox.angleX =
22.5
f;
greenBox.angleY =
22.5
f;
greenBox.setColor
(
Color4f.GREEN);
}
}
Bien entendu, cela ne change rien visuellement.
VI. Les clones▲
Si deux objets 3D ont exactement la même description de maille, il est interéssant de pouvoir partagée celle-ci.
Cela évite d'avoir en mémoire JVM deux fois la même description et d'avoir coté carte 3D deux listes qui contiennent exactement la même chose.
C'est l'idée derrière les clones.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
package
fr.developez.tutorial.java.dimension3.render;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
import
fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import
java.util.Objects;
/**
* Clone d'un objet 3D
*/
public
class
Clone3D
extends
Node3D
{
private
Color4f color =
Color4f.GREY;
/**
* Objet cloné
*/
private
final
Object3D cloned;
public
Clone3D
(
@NonNull
final
Object3D cloned)
{
this
.cloned =
Objects.requireNonNull
(
cloned, "cloned must not be null"
);
}
@NonNull
public
Color4f getColor
(
)
{
return
this
.color;
}
public
void
setColor
(
@NonNull
final
Color4f color)
{
this
.color =
Objects.requireNonNull
(
color, "color must not be null"
);
}
@ThreadOpenGL
@Override
void
drawSpecific
(
)
{
this
.color.apply
(
);
// Ici on utilise la maille de l'objet cloné
this
.cloned.mesh.drawMesh
(
);
}
}
Modifions notre exemple
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package
fr.developez.tutorial.java.dimension3;
import
fr.developez.tutorial.java.dimension3.geometry.Box;
import
fr.developez.tutorial.java.dimension3.render.Clone3D;
import
fr.developez.tutorial.java.dimension3.render.Color4f;
import
fr.developez.tutorial.java.dimension3.render.SceneGraph;
import
fr.developez.tutorial.java.dimension3.render.Window3D;
import
fr.developez.tutorial.java.dimension3.tool.NonNull;
public
class
MainBlueAndGreenCubes
{
public
static
void
main
(
String[] args)
{
final
Window3D window3D =
new
Window3D
(
800
, 600
, "Tutoriel 3D - Une boite blue et une verte"
);
MainBlueAndGreenCubes.drawScene
(
window3D.getSceneGraph
(
));
}
private
static
void
drawScene
(
@NonNull
final
SceneGraph sceneGraph)
{
final
Box blueBox =
new
Box
(
);
blueBox.mesh.seal
(
);
final
Clone3D greenBox =
new
Clone3D
(
blueBox);
sceneGraph.root.addChild
(
blueBox);
sceneGraph.root.addChild
(
greenBox);
blueBox.x =
-
1
f;
blueBox.z =
-
2
f;
blueBox.angleX =
22.5
f;
blueBox.angleY =
22.5
f;
blueBox.setColor
(
Color4f.BLUE);
greenBox.x =
1
f;
greenBox.z =
-
2
f;
greenBox.angleX =
22.5
f;
greenBox.angleY =
22.5
f;
greenBox.setColor
(
Color4f.GREEN);
}
}
Bien sûr l'aspect graphique n'a pas changé.
VII. Conclusion▲
Nous avons vu ce qu’est le graphe de scène, un exemple d’implémentation d’un et comment optimiser nos objets pour qu’ils soit plus rapidement dessiné et qu’ils prennent la mémoire juste nécessaire.
Dans le prochain tutoriel nous allons pouvoir jouer avec notre 3D en ajoutant de l’interaction avec l’utilisateur avec clavier, souris et manette de jeux. Nous verrons aussi comment capturer les objets sous la souris. Interaction avec l'utilisateurIntéraction avec l'utilisateur