2015-06-26 18 views
8

ProblemWie erstellt man ein 3D/Surface-Diagramm mit JavaFX?

Ich habe versucht, ein 3D-Diagramm mit JavaFX zu schaffen, aber es scheint, als schwieriger zu sein, was man erwarten würde.

Mein aktueller Weg wäre es, ein TriangleMesh zu erstellen, aber das ist eher umständlich. Alles, was ich tun möchte, ist eine List<Point3D> für das Diagramm bereitzustellen und dann sollte das Diagramm als eine Oberfläche gerendert werden.

Aber auch eine einfache Pyramide mit fünf Datenpunkten erweist sich als kompliziert erwiesen:

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

Fragen

  • Wer weiß, wie man ein 3D-Diagramm mit JavaFX zu schaffen?
  • Ist ein TriangleMesh der richtige Weg, es zu tun?
  • Wie konvertiert man eine List<Point3D> in ein 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); 
     } 
    } 

} 

Die Grafik sollte e sein. G. so etwas wie dieses:

enter image description here

oder dies:

enter image description here

Am Ende sollte es möglich sein, E angezeigt werden soll. G. the result of perlin noise, aber anstatt dass der Perlin-Rauschwert ein Farbwert ist, ist es ein Höhenwert.

Vielen Dank für die Hilfe!

+0

Ja, sicher, dass Sie es tun könnte. Jose Pereda wäre besser als ich. Es würde lange dauern, es zu tun. Möglicherweise erleichtern die Einrichtungen in der [FXyz-Bibliothek] (https://github.com/FXyz/FXyz) die Ausführung, Sie können jedoch ein gerades Gitter verwenden, auf das Sie eine [Verschiebungskarte] anwenden (https: //en.wikipedia.org/wiki/Displacement_mapping). Sie könnten stattdessen eine [Bump-Map] (https://en.wikipedia.org/wiki/Normal_mapping) verwenden, indem Sie Normalen berechnen und diese anwenden (PhongMaterial unterstützt dies), aber es ist wahrscheinlich am besten, die Mesh-Geometrie durch Verschiebung direkt zu modifizieren. – jewelsea

+0

Tatsächlich hat [FXyz] (https://github.com/FXyz/FXyz) bereits eine 'SurfacePlotMesh'-Implementierung, in der Sie einfach eine' Function 'hinzufügen können, die geplottet werden soll, sogar um eine Dichtekarte hinzuzufügen für die Textur, wie in [hier] (https://twitter.com/jdub1581/status/569603677419278336) oder [hier] (https://pbs.twimg.com/media/B-ehScMIcAA8dXc.png). Sie können den Sampler von [hier] ausprobieren (https://github.com/FXyz/FXyz/releases). Wir haben auch einen 'CubeViewer' als Rahmen für das Plot, daher sind die Beschriftungen die einzigen fehlenden Teile. Wenn ich die Zeit habe, kann ich daran arbeiten ... –

+0

[Das war etwas Interessantes] (https://github.com/miho/JavaOne2013) Ich erinnere mich, es macht sogar 3D-Chart-Videos. – brian

Antwort

8

Dank NwDx's answer konnte ich etwas Nützliches erstellen. Es ist keine vollständige Chart-Anwendung und ich hoffe, dass jemand mit mehr Know-how eine bessere Antwort liefern kann, aber ich werde trotzdem das Ergebnis posten.

Sie können Maus zum Drehen und Mausrad zum Zoomen verwenden. Das Beispiel zeigt ein Perlin-Rauschdiagramm mit einer diffusen Abbildung, die auf dem Gitter verwendet wird.

Der Kern ist nicht wirklich viel Code. Es ist nur über einen 2d-Array in ein Gitterdreh:

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

     } 
    } 

Wenn jemand einen besseren Algorithmus hat, bitte teilen. Es macht mir nichts aus, wenn Sie den Code wiederverwenden.

Der vollständige Arbeitsbeispiel:

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

Sieht super aus. Planen Sie Achsenwerte hinzuzufügen? – brian

+0

Ja, ich habe bereits (Kommentar Code im Beispiel). Aber aus irgendeinem Grund verzerrt es die Wände, so dass sie nicht aneinandergereiht sind. Ich muss eine separate Gruppe dafür erstellen. Und ich hoffe immer noch, dass José sich etwas besseres einfallen lässt :-) – Roland

1

Jzy3d eine Brücke für JavaFX hat, können Sie try it here.

Sie können Oberflächen aus mathematischen Funktionen oder aus Punkten mithilfe von Delaunay-Tesselation erstellen.

Sie können weitere Beispiele here und here finden.

surface chart

Verwandte Themen