2015-06-26 15 views
8

ProblemaCome creare un grafico 3d/superficie con JavaFX?

ho cercato di creare un grafico 3d con JavaFX, ma sembra essere più difficile di quanto ci si aspetterebbe.

Il mio attuale modo di farlo sarebbe creare un TriangleMesh, ma è piuttosto circostanziale. Tutto quello che mi piacerebbe fare è fornire uno List<Point3D> al grafico e quindi il grafico dovrebbe essere reso come una superficie.

Tuttavia, anche una semplice piramide con 5 punti di dati risulta essere piuttosto complicata:

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 
); 

Domande

  • Qualcuno sa come creare un grafico 3d con JavaFX?
  • TriangleMesh è il modo corretto per farlo?
  • Come si converte uno List<Point3D> in un TriangleMesh?

Codice

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); 
     } 
    } 

} 

Il grafico dovrebbe essere posta. g. qualcosa di simile:

enter image description here

o questo:

enter image description here

Alla fine dovrebbe essere possibile visualizzare e. g. the result of perlin noise, ma invece del valore del rumore perlin è un valore di colore, è un valore di altezza.

Grazie mille per l'aiuto!

+0

Sì, certo che potresti farlo. Jose Pereda sarebbe stato più bravo di me. Mi ci vorrà molto tempo per farlo. Forse le strutture nella [libreria FXyz] (https://github.com/FXyz/FXyz) potrebbero renderlo più facile da realizzare, tuttavia potresti farlo con una mesh diritta a cui applichi una [mappa di spostamento] (https: //en.wikipedia.org/wiki/Displacement_mapping). Potresti invece usare una [mappa d'urto] (https://en.wikipedia.org/wiki/Normal_mapping) calcolando le normali e applicandole (PhongMaterial supporta questo), ma la modifica diretta della geometria della mesh attraverso lo spostamento è probabilmente la migliore. – jewelsea

+0

In realtà, [FXyz] (https://github.com/FXyz/FXyz) ha già un'implementazione 'SurfacePlotMesh', dove puoi semplicemente aggiungere una' Funzione 'da tracciare, aggiungendo anche una mappa di densità per la trama, come in [here] (https://twitter.com/jdub1581/status/569603677419278336) o [qui] (https://pbs.twimg.com/media/B-ehScMIcAA8dXc.png). Puoi provare il campionatore da [qui] (https://github.com/FXyz/FXyz/releases). Abbiamo anche un 'CubeViewer' come cornice per la trama, quindi l'unica parte mancante sono le etichette. Se ho tempo posso lavorarci ... –

+0

[Questo era qualcosa di interessante] (https://github.com/miho/JavaOne2013) Ricordo che fa anche video di grafici in 3D. – brian

risposta

8

Grazie a NwDx's answer sono riuscito a creare qualcosa di utile. Non è un'applicazione grafica completa e spero che qualcuno con più know sappia fornire una risposta migliore, ma pubblicherò comunque il risultato.

È possibile utilizzare il trascinamento del mouse per la rotazione e la rotellina del mouse per lo zoom. L'esempio mostra un grafico rumore perlin con una mappa diffusa che viene utilizzata sulla mesh.

Il core non è in realtà molto codice. Si tratta solo di trasformare un 2d-array in una mesh:

// 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); 

     } 
    } 

Se qualcuno ha un algoritmo migliore, si prega di condividerlo. Non mi dispiace se riutilizzi il codice.

La piena esempio funzionante:

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); 
    } 


} 

Screenshot:

enter image description here

+0

Sembra fantastico. Hai intenzione di aggiungere valori asse? – brian

+0

Sì, ho già (codice commentato in esempio). Ma per qualche motivo distorce le pareti, quindi non sono aliganti. Dovrò creare un gruppo separato per questo. E spero ancora che José arrivi con qualcosa di meglio :-) – Roland

1

Jzy3d ha un ponte per JavaFX, è possibile try it here.

È possibile creare una superficie dalle funzioni matematiche o dai punti utilizzando la tesselazione delaunay.

È possibile trovare altri esempi here e here.

surface chart

Problemi correlati