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?
Mã
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:
hay này:
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 đỡ!
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
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ó ... –
[Đâ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