Programmation JavaFX/Formes 3D
Une scène en 3D est composée de formes 3D, de source lumineuses et d'une caméra définissant le point de vue de la scène.
Coordonnées et orientation des axes 3D
[modifier | modifier le wikicode]Les coordonnées d'un point ou d'une forme est un ensemble de 3 valeurs de type double
, nommées X, Y et Z.
L'orientation par défaut des axes est le suivant :
- L'axe X est orienté de la gauche vers la droite : une valeur plus élevée de X donne une position relative plus à droite.
- L'axe Y est orienté du haut vers le bas.
- L'axe Z est orienté du plus proche au plus loin.
Classe de base
[modifier | modifier le wikicode]La classe Shape3D
est la classe de base des formes 3D.
Elle définit les méthodes communes à toutes les formes, permettant de les placer/déplacer et de les orienter.
La position initiale à la création d'une forme 3D est (0,0,0).
- setTranslateX(double)
- Incrémente la coordonnée X du nœud.
- setTranslateY(double)
- Incrémente la coordonnée Y du nœud.
- setTranslateZ(double)
- Incrémente la coordonnée Z du nœud.
La classe Shape3D
a 4 implémentations concrètes :
- La classe
Sphere
- La classe
Cylinder
- La classe
Box
représente une boîte ou parallélépipède rectangle ou pavé droit. - La classe
MeshView
permet de définir une forme particulière à partir d'une instance de la classe abstraiteMesh
. La classe abstraiteMesh
possède la sous-classeTriangleMesh
comme implémentation concrète, définissant la forme à partir de triangles.
Les sous-classes présentées ci-dessous ont des caractéristiques communes :
- Par défaut, les formes sont centrées sur le point d'origine (0,0,0).
- Si les dimensions ne sont pas spécifiées (constructeur sans arguments), les dimensions par défaut sont définies par une boîte 2x2x2.
Sphère (classe Sphere)
[modifier | modifier le wikicode]Les paramètres définissant une sphère sont les suivants (même ordre que les constructeurs de la classe) :
double radius
- (Optionnel) Rayon de la sphère. La valeur par défaut est
1.0
. int divisions
- (Optionnel) Nombre de divisions de la circonférence. La valeur par défaut est
64
.
Exemple
[modifier | modifier le wikicode]Sphere sphere = new Sphere(100);
Cylindre (classe Cylinder)
[modifier | modifier le wikicode]Les paramètres définissant un cylindre sont les suivants (même ordre que les constructeurs de la classe) :
double radius
- (Optionnel) Rayon du cylindre. La valeur par défaut est
1.0
. double height
- (Optionnel) Hauteur du cylindre. La valeur par défaut est
2.0
. int divisions
- (Optionnel) Nombre de divisions de la circonférence. La valeur par défaut est
64
.
L'orientation initiale du cylindre suit l'axe Y ; les faces circulaires sont donc perpendiculaire à l'axe Y.
Exemple
[modifier | modifier le wikicode]Cylinder cyl = new Cylinder(40, 220);
cyl.setTranslateY(220);
Boîte (classe Box)
[modifier | modifier le wikicode]Les paramètres définissant une boîte sont les suivants (même ordre que les constructeurs de la classe) :
double width
- (Optionnel) Largeur de la boîte (axe X). La valeur par défaut est
2.0
. double height
- (Optionnel) Hauteur de la boîte (axe Y). La valeur par défaut est
2.0
. double depth
- (Optionnel) Profondeur de la boîte (axe Z). La valeur par défaut est
2.0
.
Exemple
[modifier | modifier le wikicode]Box box = new Box(100, 200, 100);
box.setTranslateX(250);
box.setTranslateY(220);
Forme triangulée
[modifier | modifier le wikicode]Une forme triangulée est définie avec un objet de class TriangleMesh
.
Le seul constructeur à appeler n'a aucun paramètres.
Il faut ensuite remplir les listes de points, de faces triangulaires et les points de texture.
Méthodes de la classe TriangleMesh
:
getTexCoords()
- Retourne la liste modifiable (float) des points (U,V) pour les textures.
getPoints()
- Retourne la liste modifiable (float) de tous les sommets.
getFaces()
- Retourne la liste modifiable (int) des index des sommets de chaque face.
getFaceSmoothingGroups()
- Retourne la liste modifiable (int) des identifiants de face. La même valeur signifiant qu'il s'agit de la même face. Ceci est utilisé pour le rendu continu des faces.
Exemple
[modifier | modifier le wikicode]TriangleMesh pyramid_mesh = new TriangleMesh();
// Pas de texture dans cet exemple, mais un point est nécessaire => (0,0)
pyramid_mesh.getTexCoords().addAll(0, 0); // --> T[]
float h = 150; // Height
float s = 300; // Side
pyramid_mesh.getPoints().addAll( // --> P[]
// X Y Z
0, 0, 0, // Point 0 - Top
0, h, -s/2, // Point 1 - Front
-s/2, h, 0, // Point 2 - Left
0, h, s/2, // Point 3 - Back
s/2, h, 0 ); // Point 4 - Right
pyramid_mesh.getFaces().addAll( // --> F[] = index in P[],T[]
// P,T P,T P,T
0,2, 2,4, 1,3, // Front left face
0,2, 1,4, 4,3, // Front right face
0,2, 4,4, 3,3, // Back right face
0,2, 3,4, 2,3, // Back left face
3,2, 1,3, 2,0, // Bottom rear face
3,2, 4,1, 1,4 ); // Bottom front face
// Regrouper les deux derniers triangles constituant la base carrée de la pyramide
pyramid_mesh.getFaceSmoothingGroups().addAll(0,1,2,3,4,4);
Il faut ensuite encapsuler la triangulation dans une forme :
MeshView pyramid = new MeshView(pyramid_mesh);
pyramid.setDrawMode(DrawMode.FILL);
pyramid.setTranslateY(-150-h);
Sources de lumière
[modifier | modifier le wikicode]Par défaut, la scène n'a aucune source lumineuse. Le rendu serait donc de la couleur de fond de la scène.
Une source lumineuse a une couleur filtrant celle des objets, une intensité, et une position.
JavaFX a deux types de sources lumineuses :
- Lumière ambiante (
AmbientLight
) - Une source lumineuse sans position, se diffusant sur toute la scène avec la même intensité.
- Lumière ponctuelle (
PointLight
) - Une source lumineuse avec position, dont la luminosité décroit avec l'augmentation de la distance.
Il faut également définir pour chaque source lumineuse, la liste des objets éclairés par celle-ci.
Exemple
[modifier | modifier le wikicode]Node[] allnodes = { sphere, cyl, pyramid, box };
Color light_color = Color.LIGHTGREY;
AmbientLight light = new AmbientLight(light_color);
light.setTranslateX(-180);
light.setTranslateY(-90);
light.setTranslateZ(-120);
light.getScope().addAll(allnodes);
PointLight light2 = new PointLight(light_color);
light2.setTranslateX(+180);
light2.setTranslateY(+190);
light2.setTranslateZ(+180);
light2.getScope().addAll(allnodes);
Caméra
[modifier | modifier le wikicode]La caméra (classe Camera
) représente le point de vue d'affichage de la scène : position et orientation.
Pour une représentation en 3D, la sous-classe PerspectiveCamera
est utilisée.
setNearClip(float)
- Définit la distance minimale pour le rendu des objets.
setFarClip(float)
- Définit la distance maximale pour le rendu des objets.
getTransforms()
- Liste modifiable des transformations (translation, rotation).
// Rotation et relocalisation de la caméra
Rotate rotate_x = new Rotate(0, Rotate.X_AXIS);
Rotate rotate_y = new Rotate(0, Rotate.Y_AXIS);
Translate translate = new Translate(0, 0, -1000);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1); // Afficher pour une distance entre 0.1
camera.setFarClip(10000.0); // ... et 10000
camera.getTransforms().addAll(rotate_x, rotate_y, translate);
Construction de la scène
[modifier | modifier le wikicode]La scène est construite en spécifiant le nœud racine regroupant les éléments de la scène.
Exemple
[modifier | modifier le wikicode]Il ne manque que la scène constituée des éléments vus précédemment.
Group root = new Group(allnodes);
root.getChildren().addAll(light, light2);
Scene scene = new Scene(root, 600, 600, true); // true to use depth buffer
scene.setFill(Color.BLACK); // Couleur de fond par défaut
scene.setCamera(camera);
// Affichage de la scène dans la fenêtre de l'application
stage.setTitle("3D JavaFX");
stage.setScene(scene);
stage.show();
Code complet
[modifier | modifier le wikicode]package org.wikibooks.fr.javafx;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.*;
public class ExempleFormes3D extends Application
{
// Transformations pour la caméra
private final Rotate rotate_x = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotate_y = new Rotate(0, Rotate.Y_AXIS);
private final Translate translate = new Translate(0, 0, -1000);
@Override
public void start(Stage stage)
{
// Coordonnées de base
int dx = 0, dy = 0, dz = 0;
// Sphère
Sphere sphere = new Sphere(100);
sphere.setTranslateX(dx);
sphere.setTranslateY(dy);
sphere.setTranslateZ(dz);
sphere.setCursor(Cursor.OPEN_HAND);
// Boîte
Box box = new Box(100, 200, 100);
box.setTranslateX(dx + 250);
box.setTranslateY(dy + 220);
box.setTranslateZ(dz);
box.setCursor(Cursor.OPEN_HAND);
// Cylindre
Cylinder cyl = new Cylinder(40, 220);
cyl.setTranslateX(dx);
cyl.setTranslateY(dy + 220);
cyl.setTranslateZ(dz);
cyl.setCursor(Cursor.OPEN_HAND);
// Pyramide à base carrée : par triangulation
TriangleMesh pyramid_mesh = new TriangleMesh(); // VertexFormat.POINT_TEXCOORD => P,T
pyramid_mesh.getTexCoords().addAll(0, 0); // --> T[]
pyramid_mesh.getTexCoords().addAll(1.0F, 0); // --> T[]
pyramid_mesh.getTexCoords().addAll(0.515F, 0.02F); // --> T[]
pyramid_mesh.getTexCoords().addAll(1.0F, 1.0F); // --> T[]
pyramid_mesh.getTexCoords().addAll(0, 1.0F); // --> T[]
float h = 150; // Height
float s = 300; // Side
pyramid_mesh.getPoints().addAll( // --> P[]
// X Y Z
0, 0, 0, // Point 0 - Top
0, h, -s/2, // Point 1 - Front
-s/2, h, 0, // Point 2 - Left
0, h, s/2, // Point 3 - Back
s/2, h, 0 ); // Point 4 - Right
pyramid_mesh.getFaces().addAll( // --> F[] = index in P[],T[]
// P,T P,T P,T
0,2, 2,4, 1,3, // Front left face
0,2, 1,4, 4,3, // Front right face
0,2, 4,4, 3,3, // Back right face
0,2, 3,4, 2,3, // Back left face
3,2, 1,3, 2,0, // Bottom rear face
3,2, 4,1, 1,4 ); // Bottom front face
pyramid_mesh.getFaceSmoothingGroups().addAll(0,1,2,3,4,4);
MeshView pyramid = new MeshView(pyramid_mesh);
pyramid.setDrawMode(DrawMode.FILL);
pyramid.setTranslateX(dx);
pyramid.setTranslateY(dy-150-h);
pyramid.setTranslateZ(dz);
pyramid.setEffect(new Shadow());
// Tous les éléments à éclairer
Node[] allnodes = { sphere, cyl, pyramid, box };
// Sources lumineuses
Color light_color = Color.LIGHTGREY;
AmbientLight light = new AmbientLight(light_color);
light.setTranslateX(dx - 180);
light.setTranslateY(dy - 90);
light.setTranslateZ(dz - 120);
light.getScope().addAll(allnodes);
PointLight light2 = new PointLight(light_color);
light2.setTranslateX(dx + 180);
light2.setTranslateY(dy + 190);
light2.setTranslateZ(dz + 180);
light2.getScope().addAll(allnodes);
// Nœud racine regroupant tous les éléments
Group root = new Group(allnodes);
root.getChildren().addAll(light, light2); // et les sources lumineuses
Scene scene = new Scene(root, 600, 600, true); // true to use depth buffer
scene.setFill(Color.BLACK);
// Point de vue de la scène
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.getTransforms().addAll(rotate_x, rotate_y, translate);
scene.setCamera(camera);
stage.setTitle("3D JavaFX");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
L'apparence des objets sera améliorée dans le chapitre suivant.