Programmation JavaFX/Interactions 3D

Un livre de Wikilivres.

Ce chapitre explique comment ajouter de l'interaction à une scène 3D.

Le principe est le même avec les composants 2D d'une interface graphique : ajouter un listener à notifier de certains évènements.

Rotations et déplacements[modifier | modifier le wikicode]

Le code des exemples précédents contient déjà les transformations appliquées à la caméra :

private final Rotate rotate_x = new Rotate(0, Rotate.X_AXIS); // axe horizontal
private final Rotate rotate_y = new Rotate(0, Rotate.Y_AXIS); // axe vertical
private final Translate translate = new Translate(0, 0, -1000);

Il suffit d'ajouter des listeners pour modifier les paramètres de ces transformations selon les déplacements de la souris quand le bouton gauche est maintenu appuyé. La modification dépend des touches Shift et Ctrl appuyées lors du déplacement de la souris :

  • Si la touche Ctrl est appuyée; la caméra est déplacée dans le plan XY ;
  • Si la touche Shift est appuyée; la caméra est déplacée dans le plan ZY ;
  • Sinon, l'angle de rotation autour de l'axe horizontal est contrôlé par les mouvements verticaux de la souris et vice-versa.

Les listeners sont ajoutés à la scène.

scene.setOnMousePressed((MouseEvent me) -> {
    mouse_x = me.getSceneX();
    mouse_y = me.getSceneY();
    mouse_x_prev = mouse_x;
    mouse_y_prev = mouse_y;
});
scene.setOnMouseDragged((MouseEvent me) -> {
    mouse_x = me.getSceneX();
    mouse_y = me.getSceneY();
    if (me.isControlDown())
    {
        translate.setX(translate.getX() + (mouse_x - mouse_x_prev));
        translate.setY(translate.getY() + (mouse_y - mouse_y_prev));
    }
    else if (me.isShiftDown())
    {
        translate.setZ(translate.getZ() + (mouse_x - mouse_x_prev));
        translate.setY(translate.getY() + (mouse_y - mouse_y_prev));
    }
    else
    {
        rotate_x.setAngle(rotate_x.getAngle() - (mouse_y - mouse_y_prev));
        rotate_y.setAngle(rotate_y.getAngle() + (mouse_x - mouse_x_prev));
    }
    mouse_x_prev = mouse_x;
    mouse_y_prev = mouse_y;
});

La rotation est centrée sur la sphère, c'est à dire en (0,0,0), quel que soit la position de la caméra car il s'agit des deux premières transformations ajoutées à la caméra, avant le déplacement :

camera.getTransforms().addAll(rotate_x, rotate_y, translate);

Code complet[modifier | modifier le wikicode]

package org.wikibooks.fr.javafx;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.*;

public class TestCameraMove extends Application
{
    private double mouse_x;
    private double mouse_y;
    private double mouse_x_prev;
    private double mouse_y_prev;

    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)
    {
        Image tex_im = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Red-brick-wall-texture-1.jpg/640px-Red-brick-wall-texture-1.jpg");
        PhongMaterial mat_wall = new PhongMaterial();
        mat_wall.setDiffuseColor(Color.GRAY);
        mat_wall.setSpecularColor(Color.WHITE);
        mat_wall.setSpecularPower(8.0);
        mat_wall.setDiffuseMap(tex_im);

        PhongMaterial mat_blue = new PhongMaterial();
        mat_blue.setDiffuseColor(Color.BLUE);
        mat_blue.setSpecularColor(Color.LIGHTBLUE);
        mat_blue.setSpecularPower(10.0);

        PhongMaterial mat_red = new PhongMaterial();
        mat_red.setDiffuseColor(Color.RED);
        mat_red.setSpecularColor(Color.ORANGERED);
        mat_red.setSpecularPower(10.0);

        int dx = 0, dy = 0, dz = 0;
        Sphere sphere = new Sphere(100);
        sphere.setTranslateX(dx);
        sphere.setTranslateY(dy);
        sphere.setTranslateZ(dz);
        sphere.setCursor(Cursor.OPEN_HAND);
        sphere.setMaterial(mat_blue);

        Box box = new Box(100, 200, 100);
        box.setTranslateX(dx + 250);
        box.setTranslateY(dy + 220);
        box.setTranslateZ(dz);
        box.setCursor(Cursor.OPEN_HAND);
        box.setMaterial(mat_wall);

        Cylinder cyl = new Cylinder(40, 220);
        cyl.setTranslateX(dx);
        cyl.setTranslateY(dy + 220);
        cyl.setTranslateZ(dz);
        cyl.setCursor(Cursor.OPEN_HAND);
        cyl.setMaterial(mat_red);

        TriangleMesh pyramid_mesh = new TriangleMesh(); // VertexFormat.POINT_TEXCOORD => P,T

        // Texture : coordonées indépendantes de la taille de la texture
        // 0.5 = 50%, 1.0 = 100%, 2.0 = 200% (texture répétée 2 fois), ...
        pyramid_mesh.getTexCoords().addAll(0, 0); // --> T[]
        pyramid_mesh.getTexCoords().addAll(2.0F, 0); // --> T[]
        pyramid_mesh.getTexCoords().addAll(1.0F, 0.0F); // --> T[]
        pyramid_mesh.getTexCoords().addAll(2.0F, 2.0F); // --> T[]
        pyramid_mesh.getTexCoords().addAll(0, 2.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,1,  1,4,  2,0,          // Bottom rear face
            3,1,  4,3,  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.setMaterial(mat_wall);
        pyramid.setTranslateX(dx);
        pyramid.setTranslateY(dy-150-h);
        pyramid.setTranslateZ(dz);

        Node[] allnodes = { sphere, cyl, pyramid, box };
        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);

        Group root = new Group(allnodes);

        root.getChildren().addAll(light, light2);

        Scene scene = new Scene(root, 600, 600, true);

        scene.setFill(Color.BLACK);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.getTransforms().addAll(rotate_x, rotate_y, translate);
        scene.setCamera(camera);

        scene.setOnMousePressed((MouseEvent me) -> {
            mouse_x = me.getSceneX();
            mouse_y = me.getSceneY();
            mouse_x_prev = mouse_x;
            mouse_y_prev = mouse_y;
        });
        scene.setOnMouseDragged((MouseEvent me) -> {
            mouse_x = me.getSceneX();
            mouse_y = me.getSceneY();
            if (me.isControlDown())
            {
                translate.setX(translate.getX() + (mouse_x - mouse_x_prev));
                translate.setY(translate.getY() + (mouse_y - mouse_y_prev));
            }
            else if (me.isShiftDown())
            {
                translate.setZ(translate.getZ() + (mouse_x - mouse_x_prev));
                translate.setY(translate.getY() + (mouse_y - mouse_y_prev));
            }
            else
            {
                rotate_x.setAngle(rotate_x.getAngle() - (mouse_y - mouse_y_prev));
                rotate_y.setAngle(rotate_y.getAngle() + (mouse_x - mouse_x_prev));
            }
            mouse_x_prev = mouse_x;
            mouse_y_prev = mouse_y;
        });

        stage.setTitle("3D JavaFX");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args)
    {
        Application.launch(args);
    }
}