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

 

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

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 abstraite Mesh. La classe abstraite Mesh possède la sous-classe TriangleMesh 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

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

Sphere sphere = new Sphere(100);

Cylindre (classe Cylinder) modifier

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

Cylinder cyl = new Cylinder(40, 220);
cyl.setTranslateY(220);

Boîte (classe Box) modifier

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

Box box = new Box(100, 200, 100);
box.setTranslateX(250);
box.setTranslateY(220);

Forme triangulée modifier

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

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

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

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

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

La scène est construite en spécifiant le nœud racine regroupant les éléments de la scène.

Exemple modifier

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

 
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.