2015-06-26 24 views
8

Sự cốLàm thế nào để tạo biểu đồ 3D/bề mặt bằng JavaFX?

Tôi đã cố gắng tạo biểu đồ 3D bằng JavaFX nhưng dường như khó khăn hơn so với mong đợi.

Cách làm hiện tại của tôi là tạo một TriangleMesh, nhưng điều đó khá đúng. Tất cả những gì tôi muốn làm là cung cấp List<Point3D> cho biểu đồ và sau đó biểu đồ sẽ được hiển thị dưới dạng bề mặt.

Tuy nhiên, thậm chí là một kim tự tháp đơn giản với 5 điểm dữ liệu hóa ra là khá phức tạp:

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

Câu hỏi

  • Có ai biết làm thế nào để tạo ra một biểu đồ 3d với JavaFX?
  • Tam giác có phải là cách thích hợp để làm điều đó không?
  • Làm cách nào để chuyển đổi List<Point3D> thành TriangleMesh?

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

} 

Biểu đồ nên e. g. một cái gì đó như thế này:

enter image description here

hay này:

enter image description here

Cuối cùng chúng ta có thể để hiển thị điện tử. g. the result of perlin noise, nhưng thay vì giá trị nhiễu perlin là giá trị màu, đó là giá trị độ cao.

Cảm ơn bạn rất nhiều vì đã giúp đỡ!

+0

Có, chắc chắn bạn có thể làm điều đó. Jose Pereda sẽ giỏi hơn tôi. Nó sẽ đưa tôi một thời gian dài để làm điều đó. Có lẽ các cơ sở trong thư viện [FXyz] (https://github.com/FXyz/FXyz) có thể làm cho nó dễ dàng hơn để thực hiện, tuy nhiên bạn có thể làm điều đó với một lưới thẳng mà bạn áp dụng một [bản đồ chuyển] (https: //en.wikipedia.org/wiki/Displacement_mapping). Thay vào đó, bạn có thể sử dụng [bump map] (https://en.wikipedia.org/wiki/Normal_mapping) bằng cách tính toán các normals và áp dụng điều đó (PhongMaterial hỗ trợ điều này), nhưng trực tiếp sửa đổi hình học lưới thông qua chuyển vị có lẽ là tốt nhất. – jewelsea

+0

Trên thực tế, [FXyz] (https://github.com/FXyz/FXyz) đã có triển khai 'SurfacePlotMesh', nơi bạn có thể thêm' Hàm 'để vẽ, thậm chí thêm một số bản đồ mật độ cho kết cấu, như trong [đây] (https://twitter.com/jdub1581/status/569603677419278336) hoặc [ở đây] (https://pbs.twimg.com/media/B-ehScMIcAA8dXc.png). Bạn có thể thử mẫu từ [ở đây] (https://github.com/FXyz/FXyz/releases). Chúng tôi cũng có một 'CubeViewer' như là một khung cho cốt truyện, do đó phần còn thiếu duy nhất là các nhãn. Nếu tôi có thời gian tôi có thể làm việc trên nó ... –

+0

[Đây là một cái gì đó thú vị] (https://github.com/miho/JavaOne2013) Tôi nhớ, nó thậm chí còn làm 3d video biểu đồ. – brian

Trả lời

8

Nhờ NwDx's answer Tôi đã tạo ra một thứ gì đó hữu ích. Nó không phải là một ứng dụng biểu đồ hoàn chỉnh và tôi hy vọng một ai đó với nhiều hơn biết làm thế nào có thể cung cấp một câu trả lời tốt hơn, nhưng tôi sẽ đăng kết quả nontheless.

Bạn có thể sử dụng tính năng kéo chuột để quay và bánh xe chuột để thu phóng. Ví dụ này cho thấy một đồ thị nhiễu perlin với một bản đồ khuếch tán được sử dụng trên lưới.

Cốt lõi không thực sự là nhiều mã. Nó chỉ là về biến một mảng 2d thành một lưới:

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

     } 
    } 

Nếu ai đó có thuật toán tốt hơn, hãy chia sẻ nó. Tôi không ngại nếu bạn sử dụng lại mã.

Các ví dụ hoàn làm việc:

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


} 

Ảnh chụp màn hình:

enter image description here

+0

Trông tuyệt vời. Bạn có định thêm giá trị trục không? – brian

+0

Có, tôi đã có (mã nhận xét chẳng hạn). Nhưng nó vì một số lý do làm méo mó các bức tường, vì thế họ không phải là người hèn nhát. Tôi sẽ phải tạo một nhóm riêng cho nó. Và tôi vẫn hy vọng rằng José đến với một cái gì đó tốt hơn :-) – Roland

1

Jzy3d có một cây cầu cho JavaFX, bạn có thể try it here.

Bạn có thể xây dựng bề mặt từ các hàm toán học hoặc từ các điểm bằng cách sử dụng tính năng delaunay tesselation.

Bạn có thể tìm thêm ví dụ herehere.

surface chart

Các vấn đề liên quan