Comment créer un graphique 3d / surface avec JavaFX?


Problème

J'ai essayé de créer un graphique 3d avec JavaFX mais cela semble plus difficile que ce à quoi on pourrait s'attendre.

Ma façon actuelle de le faire serait de créer un TriangleMesh, mais c'est plutôt circonstanciel. Tout ce que je voudrais faire est de fournir un List<Point3D> au graphique, puis le graphique doit être rendu sous forme de surface.

Cependant, même une simple pyramide avec 5 points de données s'avère plutôt compliqué:

    float h = 200;                    // Height
    float s = 200;                    // Side

    TriangleMesh pyramidMesh = new TriangleMesh();

    pyramidMesh.getTexCoords().addAll(0,0);
    pyramidMesh.getPoints().addAll(
            0,    0,    0,            // Point 0 - Top
            0,    h,    -s/2,         // Point 1 - Front
            -s/2, h,    0,            // Point 2 - Left
            s/2,  h,    0,            // Point 3 - Back
            0,    h,    s/2           // Point 4 - Right
        );

    pyramidMesh.getFaces().addAll(
      0,0,  2,0,  1,0,          // Front left face
      0,0,  1,0,  3,0,          // Front right face
      0,0,  3,0,  4,0,          // Back right face
      0,0,  4,0,  2,0,          // Back left face
      4,0,  1,0,  2,0,          // Bottom rear face
      4,0,  3,0,  1,0           // Bottom front face
  ); 

Questions

  • Quelqu'un sait-il comment créer un graphique 3d avec JavaFX?
  • Un TriangleMesh est-il la bonne façon de le faire?
  • Comment convertir un List<Point3D> en un TriangleMesh?

Code

import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Chart3dSampleApp extends Application {

    final Group root = new Group();
    final Group axisGroup = new Group();
    final Xform world = new Xform();
    final PerspectiveCamera camera = new PerspectiveCamera(true);
    final Xform cameraXform = new Xform();
    final Xform cameraXform2 = new Xform();
    final Xform cameraXform3 = new Xform();
    final double cameraDistance = 1450;
    final Xform moleculeGroup = new Xform();
    private Timeline timeline;
    boolean timelinePlaying = false;
    double ONE_FRAME = 1.0 / 24.0;
    double DELTA_MULTIPLIER = 200.0;
    double CONTROL_MULTIPLIER = 10.1;
    double SHIFT_MULTIPLIER = 0.1;
    double ALT_MULTIPLIER = 0.5;
    double mousePosX;
    double mousePosY;
    double mouseOldX;
    double mouseOldY;
    double mouseDeltaX;
    double mouseDeltaY;

    private void buildScene() {
        root.getChildren().add(world);
    }

    private void buildCamera() {
        root.getChildren().add(cameraXform);
        cameraXform.getChildren().add(cameraXform2);
        cameraXform2.getChildren().add(cameraXform3);
        cameraXform3.getChildren().add(camera);
        cameraXform3.setRotateZ(0);

        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.setTranslateZ(-cameraDistance);
        cameraXform.ry.setAngle(0);
        cameraXform.rx.setAngle(0);
    }

    private void buildAxes() {
        final PhongMaterial redMaterial = new PhongMaterial();
        redMaterial.setDiffuseColor(Color.DARKRED);
        redMaterial.setSpecularColor(Color.RED);

        final PhongMaterial greenMaterial = new PhongMaterial();
        greenMaterial.setDiffuseColor(Color.DARKGREEN);
        greenMaterial.setSpecularColor(Color.GREEN);

        final PhongMaterial blueMaterial = new PhongMaterial();
        blueMaterial.setDiffuseColor(Color.DARKBLUE);
        blueMaterial.setSpecularColor(Color.BLUE);

        final Box xAxis = new Box(300, 1, 300);
        final Box yAxis = new Box(1, 300, 300);
        final Box zAxis = new Box(300, 300, 1);

        yAxis.setTranslateY(-150);
        yAxis.setTranslateX(150);
        zAxis.setTranslateY(-150);
        zAxis.setTranslateZ(150);

        xAxis.setMaterial(redMaterial);
        yAxis.setMaterial(greenMaterial);
        zAxis.setMaterial(blueMaterial);

        axisGroup.getChildren().addAll(xAxis, yAxis, zAxis);
        world.getChildren().addAll(axisGroup);
    }

    private void buildChart() {

      final PhongMaterial whiteMaterial = new PhongMaterial();
      whiteMaterial.setDiffuseColor(Color.WHITE);
      whiteMaterial.setSpecularColor(Color.LIGHTBLUE);

        float h = 200;                    // Height
        float s = 200;                    // Side

        TriangleMesh pyramidMesh = new TriangleMesh();

        pyramidMesh.getTexCoords().addAll(0,0);
        pyramidMesh.getPoints().addAll(
                0,    0,    0,            // Point 0 - Top
                0,    h,    -s/2,         // Point 1 - Front
                -s/2, h,    0,            // Point 2 - Left
                s/2,  h,    0,            // Point 3 - Back
                0,    h,    s/2           // Point 4 - Right
            );

        pyramidMesh.getFaces().addAll(
          0,0,  2,0,  1,0,          // Front left face
          0,0,  1,0,  3,0,          // Front right face
          0,0,  3,0,  4,0,          // Back right face
          0,0,  4,0,  2,0,          // Back left face
          4,0,  1,0,  2,0,          // Bottom rear face
          4,0,  3,0,  1,0           // Bottom front face
      ); 


        MeshView pyramid = new MeshView(pyramidMesh);
        pyramid.setDrawMode(DrawMode.FILL);
        pyramid.setMaterial(whiteMaterial);
        pyramid.setTranslateY(-h);

        world.getChildren().addAll(pyramid);
    }


    private void handleMouse(Scene scene, final Node root) {
        scene.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent me) {
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseOldX = me.getSceneX();
                mouseOldY = me.getSceneY();
            }
        });
        scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent me) {
                mouseOldX = mousePosX;
                mouseOldY = mousePosY;
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseDeltaX = (mousePosX - mouseOldX);
                mouseDeltaY = (mousePosY - mouseOldY);

                double modifier = 1.0;
                double modifierFactor = 0.1;

                if (me.isControlDown()) {
                    modifier = 0.1;
                }
                if (me.isShiftDown()) {
                    modifier = 10.0;
                }
                if (me.isPrimaryButtonDown()) {
                    cameraXform.ry.setAngle(cameraXform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0);  // +
                    cameraXform.rx.setAngle(cameraXform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0);  // -
                } else if (me.isSecondaryButtonDown()) {
                    double z = camera.getTranslateZ();
                    double newZ = z + mouseDeltaX * modifierFactor * modifier;
                    camera.setTranslateZ(newZ);
                } else if (me.isMiddleButtonDown()) {
                    cameraXform2.t.setX(cameraXform2.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3);  // -
                    cameraXform2.t.setY(cameraXform2.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3);  // -
                }
            }
        });
    }

    private void handleKeyboard(Scene scene, final Node root) {
        final boolean moveCamera = true;
        scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                Duration currentTime;
                switch (event.getCode()) {
                    case Z:
                        if (event.isShiftDown()) {
                            cameraXform.ry.setAngle(0.0);
                            cameraXform.rx.setAngle(0.0);
                            camera.setTranslateZ(-300.0);
                        }
                        cameraXform2.t.setX(0.0);
                        cameraXform2.t.setY(0.0);
                        break;
                    case X:
                        if (event.isControlDown()) {
                            if (axisGroup.isVisible()) {
                                axisGroup.setVisible(false);
                            } else {
                                axisGroup.setVisible(true);
                            }
                        }
                        break;
                    case S:
                        if (event.isControlDown()) {
                            if (moleculeGroup.isVisible()) {
                                moleculeGroup.setVisible(false);
                            } else {
                                moleculeGroup.setVisible(true);
                            }
                        }
                        break;
                    case SPACE:
                        if (timelinePlaying) {
                            timeline.pause();
                            timelinePlaying = false;
                        } else {
                            timeline.play();
                            timelinePlaying = true;
                        }
                        break;
                    case UP:
                        if (event.isControlDown() && event.isShiftDown()) {
                            cameraXform2.t.setY(cameraXform2.t.getY() - 10.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown() && event.isShiftDown()) {
                            cameraXform.rx.setAngle(cameraXform.rx.getAngle() - 10.0 * ALT_MULTIPLIER);
                        } else if (event.isControlDown()) {
                            cameraXform2.t.setY(cameraXform2.t.getY() - 1.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown()) {
                            cameraXform.rx.setAngle(cameraXform.rx.getAngle() - 2.0 * ALT_MULTIPLIER);
                        } else if (event.isShiftDown()) {
                            double z = camera.getTranslateZ();
                            double newZ = z + 5.0 * SHIFT_MULTIPLIER;
                            camera.setTranslateZ(newZ);
                        }
                        break;
                    case DOWN:
                        if (event.isControlDown() && event.isShiftDown()) {
                            cameraXform2.t.setY(cameraXform2.t.getY() + 10.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown() && event.isShiftDown()) {
                            cameraXform.rx.setAngle(cameraXform.rx.getAngle() + 10.0 * ALT_MULTIPLIER);
                        } else if (event.isControlDown()) {
                            cameraXform2.t.setY(cameraXform2.t.getY() + 1.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown()) {
                            cameraXform.rx.setAngle(cameraXform.rx.getAngle() + 2.0 * ALT_MULTIPLIER);
                        } else if (event.isShiftDown()) {
                            double z = camera.getTranslateZ();
                            double newZ = z - 5.0 * SHIFT_MULTIPLIER;
                            camera.setTranslateZ(newZ);
                        }
                        break;
                    case RIGHT:
                        if (event.isControlDown() && event.isShiftDown()) {
                            cameraXform2.t.setX(cameraXform2.t.getX() + 10.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown() && event.isShiftDown()) {
                            cameraXform.ry.setAngle(cameraXform.ry.getAngle() - 10.0 * ALT_MULTIPLIER);
                        } else if (event.isControlDown()) {
                            cameraXform2.t.setX(cameraXform2.t.getX() + 1.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown()) {
                            cameraXform.ry.setAngle(cameraXform.ry.getAngle() - 2.0 * ALT_MULTIPLIER);
                        }
                        break;
                    case LEFT:
                        if (event.isControlDown() && event.isShiftDown()) {
                            cameraXform2.t.setX(cameraXform2.t.getX() - 10.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown() && event.isShiftDown()) {
                            cameraXform.ry.setAngle(cameraXform.ry.getAngle() + 10.0 * ALT_MULTIPLIER);  // -
                        } else if (event.isControlDown()) {
                            cameraXform2.t.setX(cameraXform2.t.getX() - 1.0 * CONTROL_MULTIPLIER);
                        } else if (event.isAltDown()) {
                            cameraXform.ry.setAngle(cameraXform.ry.getAngle() + 2.0 * ALT_MULTIPLIER);  // -
                        }
                        break;
                }
            }
        });
    }

        @Override
    public void start(Stage primaryStage) {
        buildScene();
        buildCamera();
        buildAxes();
        buildChart();

        Scene scene = new Scene(root, 1600, 900, true);
        scene.setFill(Color.GREY);
        handleKeyboard(scene, world);
        handleMouse(scene, world);

        primaryStage.setScene(scene);
        primaryStage.show();

        scene.setCamera(camera);

    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        System.setProperty("prism.dirtyopts", "false");
        launch(args);
    }

    public static class Xform extends Group {

      public enum RotateOrder {
          XYZ, XZY, YXZ, YZX, ZXY, ZYX
      }

      public Translate t  = new Translate(); 
      public Translate p  = new Translate(); 
      public Translate ip = new Translate(); 
      public Rotate rx = new Rotate();
      { rx.setAxis(Rotate.X_AXIS); }
      public Rotate ry = new Rotate();
      { ry.setAxis(Rotate.Y_AXIS); }
      public Rotate rz = new Rotate();
      { rz.setAxis(Rotate.Z_AXIS); }
      public Scale s = new Scale();

      public Xform() { 
          super(); 
          getTransforms().addAll(t, rz, ry, rx, s); 
      }

      public Xform(RotateOrder rotateOrder) { 
          super(); 
          // choose the order of rotations based on the rotateOrder
          switch (rotateOrder) {
          case XYZ:
              getTransforms().addAll(t, p, rz, ry, rx, s, ip); 
              break;
          case XZY:
              getTransforms().addAll(t, p, ry, rz, rx, s, ip); 
              break;
          case YXZ:
              getTransforms().addAll(t, p, rz, rx, ry, s, ip); 
              break;
          case YZX:
              getTransforms().addAll(t, p, rx, rz, ry, s, ip);  // For Camera
              break;
          case ZXY:
              getTransforms().addAll(t, p, ry, rx, rz, s, ip); 
              break;
          case ZYX:
              getTransforms().addAll(t, p, rx, ry, rz, s, ip); 
              break;
          }
      }

      public void setTranslate(double x, double y, double z) {
          t.setX(x);
          t.setY(y);
          t.setZ(z);
      }

      public void setTranslate(double x, double y) {
          t.setX(x);
          t.setY(y);
      }

      // Cannot override these methods as they are final:
      // public void setTranslateX(double x) { t.setX(x); }
      // public void setTranslateY(double y) { t.setY(y); }
      // public void setTranslateZ(double z) { t.setZ(z); }
      // Use these methods instead:
      public void setTx(double x) { t.setX(x); }
      public void setTy(double y) { t.setY(y); }
      public void setTz(double z) { t.setZ(z); }

      public void setRotate(double x, double y, double z) {
          rx.setAngle(x);
          ry.setAngle(y);
          rz.setAngle(z);
      }

      public void setRotateX(double x) { rx.setAngle(x); }
      public void setRotateY(double y) { ry.setAngle(y); }
      public void setRotateZ(double z) { rz.setAngle(z); }
      public void setRx(double x) { rx.setAngle(x); }
      public void setRy(double y) { ry.setAngle(y); }
      public void setRz(double z) { rz.setAngle(z); }

      public void setScale(double scaleFactor) {
          s.setX(scaleFactor);
          s.setY(scaleFactor);
          s.setZ(scaleFactor);
      }

      public void setScale(double x, double y, double z) {
          s.setX(x);
          s.setY(y);
          s.setZ(z);
      }

      // Cannot override these methods as they are final:
      // public void setScaleX(double x) { s.setX(x); }
      // public void setScaleY(double y) { s.setY(y); }
      // public void setScaleZ(double z) { s.setZ(z); }
      // Use these methods instead:
      public void setSx(double x) { s.setX(x); }
      public void setSy(double y) { s.setY(y); }
      public void setSz(double z) { s.setZ(z); }

      public void setPivot(double x, double y, double z) {
          p.setX(x);
          p.setY(y);
          p.setZ(z);
          ip.setX(-x);
          ip.setY(-y);
          ip.setZ(-z);
      }

      public void reset() {
          t.setX(0.0);
          t.setY(0.0);
          t.setZ(0.0);
          rx.setAngle(0.0);
          ry.setAngle(0.0);
          rz.setAngle(0.0);
          s.setX(1.0);
          s.setY(1.0);
          s.setZ(1.0);
          p.setX(0.0);
          p.setY(0.0);
          p.setZ(0.0);
          ip.setX(0.0);
          ip.setY(0.0);
          ip.setZ(0.0);
      }

      public void resetTSP() {
          t.setX(0.0);
          t.setY(0.0);
          t.setZ(0.0);
          s.setX(1.0);
          s.setY(1.0);
          s.setZ(1.0);
          p.setX(0.0);
          p.setY(0.0);
          p.setZ(0.0);
          ip.setX(0.0);
          ip.setY(0.0);
          ip.setZ(0.0);
      }
  }

}

Le graphique devrait être par exemple quelque chose comme ceci:

entrez la description de l'image ici

Ou ceci:

entrez la description de l'image ici

En fin de compte, il devrait être possible d'afficher par exemple le résultat du bruit de perlin , mais au lieu que la valeur de bruit perlin soit une valeur de couleur, c'est une valeur de hauteur.

Merci beaucoup pour l'aide!

Author: Community, 2015-06-26

2 answers

Grâce à la réponse de NwDx j'ai réussi à créer quelque chose d'utile. Ce n'est pas une application de graphique complète et j'espère que quelqu'un avec plus de savoir comment peut fournir une meilleure réponse, mais je vais poster le résultat nontheless.

Vous pouvez utiliser le glissement de la souris pour la rotation et la molette de la souris pour le zoom. L'exemple montre un graphique de bruit de perlin avec une carte diffuse qui est utilisée sur le maillage.

Le noyau n'est pas vraiment beaucoup de code. Il s'agit simplement de transformer un tableau 2d en un maille:

    // perlin noise
    float[][] noiseArray = createNoise( size);

    // mesh
    TriangleMesh mesh = new TriangleMesh();

    // create points for x/z
    float amplification = 100; // amplification of noise

    for (int x = 0; x < size; x++) {
        for (int z = 0; z < size; z++) {
            mesh.getPoints().addAll(x, noiseArray[x][z] * amplification, z);
        }
    }

    // texture
    int length = size;
    float total = length;

    for (float x = 0; x < length - 1; x++) {
        for (float y = 0; y < length - 1; y++) {

            float x0 = x / total;
            float y0 = y / total;
            float x1 = (x + 1) / total;
            float y1 = (y + 1) / total;

            mesh.getTexCoords().addAll( //
                    x0, y0, // 0, top-left
                    x0, y1, // 1, bottom-left
                    x1, y1, // 2, top-right
                    x1, y1 // 3, bottom-right
            );


        }
    }

    // faces
    for (int x = 0; x < length - 1; x++) {
        for (int z = 0; z < length - 1; z++) {

            int tl = x * length + z; // top-left
            int bl = x * length + z + 1; // bottom-left
            int tr = (x + 1) * length + z; // top-right
            int br = (x + 1) * length + z + 1; // bottom-right

            int offset = (x * (length - 1) + z ) * 8 / 2; // div 2 because we have u AND v in the list

            // working
            mesh.getFaces().addAll(bl, offset + 1, tl, offset + 0, tr, offset + 2);
            mesh.getFaces().addAll(tr, offset + 2, br, offset + 3, bl, offset + 1);

        }
    }

Si quelqu'un a un meilleur algorithme, veuillez le partager. Cela ne me dérange pas si vous réutilisez le code.

L'exemple de travail complet:

import java.util.ArrayList;
import java.util.List;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.paint.Stop;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Line;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class Chart3dDemo extends Application {

    // size of graph
    int size = 400;

    // variables for mouse interaction
    private double mousePosX, mousePosY;
    private double mouseOldX, mouseOldY;
    private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-45, Rotate.Y_AXIS);

    @Override
    public void start(Stage primaryStage) {

        // create axis walls
        Group cube = createCube(size);

        // initial cube rotation
        cube.getTransforms().addAll(rotateX, rotateY);

        // add objects to scene
        StackPane root = new StackPane();
        root.getChildren().add(cube);

        // perlin noise
        float[][] noiseArray = createNoise( size);

        // mesh
        TriangleMesh mesh = new TriangleMesh();

        // create points for x/z
        float amplification = 100; // amplification of noise

        for (int x = 0; x < size; x++) {
            for (int z = 0; z < size; z++) {
                mesh.getPoints().addAll(x, noiseArray[x][z] * amplification, z);
            }
        }

        // texture
        int length = size;
        float total = length;

        for (float x = 0; x < length - 1; x++) {
            for (float y = 0; y < length - 1; y++) {

                float x0 = x / total;
                float y0 = y / total;
                float x1 = (x + 1) / total;
                float y1 = (y + 1) / total;

                mesh.getTexCoords().addAll( //
                        x0, y0, // 0, top-left
                        x0, y1, // 1, bottom-left
                        x1, y1, // 2, top-right
                        x1, y1 // 3, bottom-right
                );


            }
        }

        // faces
        for (int x = 0; x < length - 1; x++) {
            for (int z = 0; z < length - 1; z++) {

                int tl = x * length + z; // top-left
                int bl = x * length + z + 1; // bottom-left
                int tr = (x + 1) * length + z; // top-right
                int br = (x + 1) * length + z + 1; // bottom-right

                int offset = (x * (length - 1) + z ) * 8 / 2; // div 2 because we have u AND v in the list

                // working
                mesh.getFaces().addAll(bl, offset + 1, tl, offset + 0, tr, offset + 2);
                mesh.getFaces().addAll(tr, offset + 2, br, offset + 3, bl, offset + 1);

            }
        }


        // material
        Image diffuseMap = createImage(size, noiseArray);

        PhongMaterial material = new PhongMaterial();
        material.setDiffuseMap(diffuseMap);
        material.setSpecularColor(Color.WHITE);

        // mesh view
        MeshView meshView = new MeshView(mesh);
        meshView.setTranslateX(-0.5 * size);
        meshView.setTranslateZ(-0.5 * size);
        meshView.setMaterial(material);
        meshView.setCullFace(CullFace.NONE);
        meshView.setDrawMode(DrawMode.FILL);
        meshView.setDepthTest(DepthTest.ENABLE);

        cube.getChildren().addAll(meshView);

        // testing / debugging stuff: show diffuse map on chart
        ImageView iv = new ImageView(diffuseMap);
        iv.setTranslateX(-0.5 * size);
        iv.setTranslateY(-0.10 * size);
        iv.setRotate(90);
        iv.setRotationAxis(new Point3D(1, 0, 0));
        cube.getChildren().add(iv);

        // scene
        Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
        scene.setCamera(new PerspectiveCamera());

        scene.setOnMousePressed(me -> {
            mouseOldX = me.getSceneX();
            mouseOldY = me.getSceneY();
        });
        scene.setOnMouseDragged(me -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;

        });

        makeZoomable(root);

        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();


    }

    /**
     * Create texture for uv mapping
     * @param size
     * @param noise
     * @return
     */
    public Image createImage(double size, float[][] noise) {

        int width = (int) size;
        int height = (int) size;

        WritableImage wr = new WritableImage(width, height);
        PixelWriter pw = wr.getPixelWriter();
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {

                float value = noise[x][y];

                double gray = normalizeValue(value, -.5, .5, 0., 1.);

                gray = clamp(gray, 0, 1);

                Color color = Color.RED.interpolate(Color.YELLOW, gray);

                pw.setColor(x, y, color);

            }
        }

        return wr;

    }

    /**
     * Axis wall
     */
    public static class Axis extends Pane {

        Rectangle wall;

        public Axis(double size) {

            // wall
            // first the wall, then the lines => overlapping of lines over walls
            // works
            wall = new Rectangle(size, size);
            getChildren().add(wall);

            // grid
            double zTranslate = 0;
            double lineWidth = 1.0;
            Color gridColor = Color.WHITE;

            for (int y = 0; y <= size; y += size / 10) {

                Line line = new Line(0, 0, size, 0);
                line.setStroke(gridColor);
                line.setFill(gridColor);
                line.setTranslateY(y);
                line.setTranslateZ(zTranslate);
                line.setStrokeWidth(lineWidth);

                getChildren().addAll(line);

            }

            for (int x = 0; x <= size; x += size / 10) {

                Line line = new Line(0, 0, 0, size);
                line.setStroke(gridColor);
                line.setFill(gridColor);
                line.setTranslateX(x);
                line.setTranslateZ(zTranslate);
                line.setStrokeWidth(lineWidth);

                getChildren().addAll(line);

            }

            // labels
            // TODO: for some reason the text makes the wall have an offset
            // for( int y=0; y <= size; y+=size/10) {
            //
            // Text text = new Text( ""+y);
            // text.setTranslateX(size + 10);
            //
            // text.setTranslateY(y);
            // text.setTranslateZ(zTranslate);
            //
            // getChildren().addAll(text);
            //
            // }

        }

        public void setFill(Paint paint) {
            wall.setFill(paint);
        }

    }

    public void makeZoomable(StackPane control) {

        final double MAX_SCALE = 20.0;
        final double MIN_SCALE = 0.1;

        control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {

            @Override
            public void handle(ScrollEvent event) {

                double delta = 1.2;

                double scale = control.getScaleX();

                if (event.getDeltaY() < 0) {
                    scale /= delta;
                } else {
                    scale *= delta;
                }

                scale = clamp(scale, MIN_SCALE, MAX_SCALE);

                control.setScaleX(scale);
                control.setScaleY(scale);

                event.consume();

            }

        });

    }

    /**
     * Create axis walls
     * @param size
     * @return
     */
    private Group createCube(int size) {

        Group cube = new Group();

        // size of the cube
        Color color = Color.DARKCYAN;

        List<Axis> cubeFaces = new ArrayList<>();
        Axis r;

        // back face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-0.5 * size);
        r.setTranslateZ(0.5 * size);

        cubeFaces.add(r);

        // bottom face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(0);
        r.setRotationAxis(Rotate.X_AXIS);
        r.setRotate(90);

        cubeFaces.add(r);

        // right face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
        r.setTranslateX(-1 * size);
        r.setTranslateY(-0.5 * size);
        r.setRotationAxis(Rotate.Y_AXIS);
        r.setRotate(90);

        // cubeFaces.add( r);

        // left face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
        r.setTranslateX(0);
        r.setTranslateY(-0.5 * size);
        r.setRotationAxis(Rotate.Y_AXIS);
        r.setRotate(90);

        cubeFaces.add(r);

        // top face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-1 * size);
        r.setRotationAxis(Rotate.X_AXIS);
        r.setRotate(90);

        // cubeFaces.add( r);

        // front face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-0.5 * size);
        r.setTranslateZ(-0.5 * size);

        // cubeFaces.add( r);

        cube.getChildren().addAll(cubeFaces);

        return cube;
    }

    /**
     * Create an array of the given size with values of perlin noise
     * @param size
     * @return
     */
    private float[][] createNoise( int size) {
        float[][] noiseArray = new float[(int) size][(int) size];

        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {

                double frequency = 10.0 / (double) size;

                double noise = ImprovedNoise.noise(x * frequency, y * frequency, 0);

                noiseArray[x][y] = (float) noise;
            }
        }

        return noiseArray;

    }

    public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {

        return (value - min) * (newMax - newMin) / (max - min) + newMin;

    }

    public static double clamp(double value, double min, double max) {

        if (Double.compare(value, min) < 0)
            return min;

        if (Double.compare(value, max) > 0)
            return max;

        return value;
    }   


    /**
     * Perlin noise generator
     * 
     * // JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
     * // http://mrl.nyu.edu/~perlin/paper445.pdf
     * // http://mrl.nyu.edu/~perlin/noise/
     */
    public final static class ImprovedNoise {
    static public double noise(double x, double y, double z) {
       int X = (int)Math.floor(x) & 255,                  // FIND UNIT CUBE THAT
           Y = (int)Math.floor(y) & 255,                  // CONTAINS POINT.
           Z = (int)Math.floor(z) & 255;
       x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
       y -= Math.floor(y);                                // OF POINT IN CUBE.
       z -= Math.floor(z);
       double u = fade(x),                                // COMPUTE FADE CURVES
              v = fade(y),                                // FOR EACH OF X,Y,Z.
              w = fade(z);
       int A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,      // HASH COORDINATES OF
           B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;      // THE 8 CUBE CORNERS,

       return lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                      grad(p[BA  ], x-1, y  , z   )), // BLENDED
                              lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                      grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                      lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                      grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                              lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                      grad(p[BB+1], x-1, y-1, z-1 ))));
    }
    static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); }
    static double lerp(double t, double a, double b) { return a + t * (b - a); }
    static double grad(int hash, double x, double y, double z) {
       int h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
       double u = h<8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
              v = h<4 ? y : h==12||h==14 ? x : z;
       return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
    }
    static final int p[] = new int[512], permutation[] = { 151,160,137,91,90,15,
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
    };
    static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; }
    }

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


}

Capture d'écran:

entrez la description de l'image ici

 9
Author: Roland, 2017-05-23 12:32:11

Jzy3d a un pont pour JavaFX, vous pouvez essayer ici.

Vous pouvez construire une surface à partir de fonctions mathématiques ou de points en utilisant la tesselation de delaunay.

Vous pouvez trouver plus d'exemples ici et ici.

graphique en surface

 1
Author: Martin, 2016-02-29 14:25:56