JavaFX 3D:围绕Z轴旋转相机时标签定位错误

时间:2017-01-09 16:47:22

标签: javafx 3d rotation javafx-8

我目前正在JavaFX中编写一个程序,它以3D形式显示图形。用户应该能够翻译和旋转它 因为在3D中移动的常见规则是,移动相机而不是整个图形,我遵循这个规则。

我创建了一个 StackPane ,其中包含两个 Pane (bottomPane和topPane)。 bottomPane包含SubScene,其中显示图表并使用 PerspectiveCamera 。在topPane中,我为特定节点添加了包含 Label
我做了这个设置,以防止标签被图形隐藏,当它们的相应节点在其他节点或边缘后面时。

为了正确定位标签,我将一个函数绑定到摄像机的translateX / Y / Z属性,相应地更新标签的位置( rearrangeLabels())。登记/> 此函数计算节点的屏幕坐标,然后将这些坐标计算回包含标签的组的坐标。

这适用于翻译(例如,向上/向下/向左/向右移动图表)。如果我仅围绕x轴或y轴旋转相机,它也可以工作 但是当我想在两个轴上同时旋转(例如对角线)时,标签不会相应地移动。

我尝试了很多不同的方法并得出结论,这可能是JavaFX中的错误,或者更准确地说是 localToScreen() screenToLocal()功能。

我附上了一个演示程序,它描述了上述问题 只需按开始并使用主鼠标按钮旋转相机。

所以我的问题是,你是否有人知道,如果这是一个已知的错误?如果是,是否有解决方法?或者我做错了什么?

编辑:我将错误范围缩小为围绕Z轴的旋转。当仅围绕z轴旋转时,会发生奇怪的行为,而仅围绕X轴或Y轴旋转,一切都会按预期移动。

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class RotateCamWithLabel extends Application{
    Sphere node1;
    Sphere node2;
    Cylinder edge;

    Label nodeLabel1;
    Label nodeLabel2;

    Group labelGroup;
    Group graphGroup;

    Pane topPane;
    Pane bottomPane;

    PerspectiveCamera cam;

    double oldPosX;
    double oldPosY;

    Transform camTransform = new Rotate();

    @Override
    public void start(Stage primaryStage) throws Exception {
        // Setting up the root of the whole Scene
        topPane = new Pane();
        topPane.setPickOnBounds(false);
        bottomPane = new Pane();
        StackPane root = new StackPane();
        root.getChildren().addAll(bottomPane, topPane);

        Button startButton = new Button("Start");
        startButton.setOnAction(event -> {
            setUpSubSceneAndCam();
            addLabels();
        });
        topPane.getChildren().add(startButton);

        // Starting the Scene
        Scene scene = new Scene(root, 700, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void setUpSubSceneAndCam() {
        createGraph();
        SubScene subScene = new SubScene(graphGroup, 700, 500, true, SceneAntialiasing.BALANCED);
        subScene.setFill(Color.LIGHTGRAY);
        cam = new PerspectiveCamera(true);
        cam.setTranslateZ(-1000);
        cam.setNearClip(0.1);
        cam.setFarClip(Double.MAX_VALUE);
        subScene.setCamera(cam);
        bottomPane.getChildren().add(subScene);

        createCameraLabelBinding();

        subScene.setOnMousePressed(event -> {
            oldPosX = event.getSceneX();
            oldPosY = event.getSceneY();
        });

        subScene.setOnMouseDragged(event -> {
            double deltaX = event.getSceneX() - oldPosX;
            double deltaY = event.getSceneY() - oldPosY;

            if (event.isPrimaryButtonDown()) {
                rotateCamera(deltaX, deltaY);
            }

            oldPosX = event.getSceneX();
            oldPosY = event.getSceneY();
        });
    }

    // Rotation is done by calculating the local camera position in the subscene and out of these,
    // the rotation axis is calculated as well as the degree.
    // Then the camera is repositioned on the pivot point, turned based on the axis and degree, and put
    // back along its local backwards vector based on the starting ditance between the camera and the pivot point
    private void rotateCamera(double deltaX, double deltaY) {
        // Calculate rotation-axis
        Point3D leftVec = getCamLeftVector().multiply(deltaX);
        Point3D upVec = getCamUpVector().multiply(deltaY);
        Point3D dragVec = leftVec.add(upVec).multiply(-1);
        Point3D backVec = getCamBackwardVector();
        Point3D axis = dragVec.crossProduct(backVec);
        //Point3D axis = Rotate.Z_AXIS; //Does not work
        //Point3D axis = Rotate.Y_AXIS; //Works
        //Point3D axis = Rotate.X_AXIS; //Works
        Rotate r = new Rotate(dragVec.magnitude(), axis);

        // set camera on pivot point
        Point3D pivot = Point3D.ZERO;
        Point3D camCurrent = new Point3D(cam.getTranslateX(), cam.getTranslateY(), cam.getTranslateZ());
        Point3D camPivVec = pivot.subtract(camCurrent);
        setCameraPosition(pivot);

        // rotate camera
        camTransform = r.createConcatenation(camTransform);
        cam.getTransforms().setAll(camTransform);

        // put camera back along the local backwards vector
        double length = camPivVec.magnitude();
        setCameraPosition(getCamBackwardVector().multiply(length).add(pivot));
    }

    private void addLabels() {
        if (labelGroup != null) {
            labelGroup.getChildren().remove(nodeLabel1);
            labelGroup.getChildren().remove(nodeLabel2);
        }
        labelGroup = new Group();
        topPane.getChildren().add(labelGroup);
        nodeLabel1 = new Label("Hello");
        nodeLabel2 = new Label("Bye");
        labelGroup.getChildren().addAll(nodeLabel1, nodeLabel2);
        rearrangeLabels();
    }

    private void createCameraLabelBinding() {
        cam.translateXProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
        cam.translateYProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
        cam.translateZProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
    }

    // TODO: Here is probably a bug
    // I calculate the screen coordinates of the nodes, then turn them back to local positions
    // in the label-group
    private void rearrangeLabels() {
        Point2D screenCoord1 = node1.localToScreen(Point2D.ZERO);
        Point2D screenCoord2 = node2.localToScreen(Point2D.ZERO);

        Point2D groupCoord1 = labelGroup.screenToLocal(screenCoord1);
        Point2D groupCoord2 = labelGroup.screenToLocal(screenCoord2);

        nodeLabel1.setTranslateX(groupCoord1.getX());
        nodeLabel1.setTranslateY(groupCoord1.getY());
        nodeLabel2.setTranslateX(groupCoord2.getX());
        nodeLabel2.setTranslateY(groupCoord2.getY());
    }

    private void createGraph() {
        graphGroup = new Group();

        node1 = new Sphere(5);
        node2 = new Sphere(5);
        node1.setTranslateX(-50);
        node1.setTranslateY(-50);
        node2.setTranslateX(150);
        node2.setTranslateY(50);

        edge = new Cylinder(2, 10);
        connectNodesWithEdge(new Point3D(node1.getTranslateX(), node1.getTranslateY(), node1.getTranslateZ()),
                new Point3D(node2.getTranslateX(), node2.getTranslateY(), node2.getTranslateZ()));

        graphGroup.getChildren().addAll(node1, node2, edge);
    }

    private void connectNodesWithEdge(Point3D origin, Point3D target) {
        Point3D yAxis = new Point3D(0, 1, 0);
        Point3D diff = target.subtract(origin);
        double height = diff.magnitude();

        Point3D mid = target.midpoint(origin);
        Translate moveToMidpoint = new Translate(mid.getX(), mid.getY(), mid.getZ());

        Point3D axisOfRotation = diff.crossProduct(yAxis);
        double angle = Math.acos(diff.normalize().dotProduct(yAxis));
        Rotate rotateAroundCenter = new Rotate(-Math.toDegrees(angle), axisOfRotation);

        this.edge.setHeight(height);

        edge.getTransforms().addAll(moveToMidpoint, rotateAroundCenter);
    }

    private Point3D getCamLeftVector() {
        Point3D left = cam.localToScene(-1, 0, 0);
        Point3D current = cam.localToScene(0, 0, 0);
        return left.subtract(current);
    }

    private Point3D getCamUpVector() {
        Point3D up = cam.localToScene(0, -1, 0);
        Point3D current = cam.localToScene(0, 0, 0);
        return up.subtract(current);
    }

    private Point3D getCamBackwardVector() {
        Point3D backward = cam.localToScene(0, 0, -1);
        Point3D current = cam.localToScene(0, 0, 0);
        return backward.subtract(current);
    }

    private void setCameraPosition(Point3D position) {
        cam.setTranslateX(position.getX());
        cam.setTranslateY(position.getY());
        cam.setTranslateZ(position.getZ());
    }

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

0 个答案:

没有答案