How to get 2D coordinates on window for 3D object in javafx

我的未来我决定 提交于 2019-11-29 15:19:44

There is a way to convert the 3D coordinates of an object in a subScene to a 2D scene coordinates, but unfortunately it uses private API, so it is advised not to rely on it.

The idea is based on how the camera projection works, and it is based on the com.sun.javafx.scene.input.InputEventUtils.recomputeCoordinates() method that is used typically for input events from a PickResult.

Let's say you have a node in a sub scene. For a given point of that node, you can obtain its coordinates like:

Point3D coordinates = node.localToScene(Point3D.ZERO);

and you can find out about the sub scene of the node:

SubScene subScene = NodeHelper.getSubScene(node);

Now you can use the SceneUtils::subSceneToScene method that

Translates point from inner subScene coordinates to scene coordinates.

to get a new set of coordinates, referenced to the scene:

coordinates = SceneUtils.subSceneToScene(subScene, coordinates);

But these are still 3D coordinates.

The final step to convert those to 2D is with the use of CameraHelper::project:

final Camera effectiveCamera = SceneHelper.getEffectiveCamera(node.getScene());        
Point2D p2 = CameraHelper.project(effectiveCamera, coordinates);

The following sample places 2D labels in the scene, at the exact same position of the 8 vertices of a 3D box in a subScene.

private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);

private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;

private Group root;

@Override
public void start(Stage primaryStage) {

    Box box = new Box(150, 100, 50);
    box.setDrawMode(DrawMode.LINE);
    box.setCullFace(CullFace.NONE);

    Group group = new Group(box);

    PerspectiveCamera camera = new PerspectiveCamera(true);
    camera.setNearClip(0.1);
    camera.setFarClip(10000.0);
    camera.setFieldOfView(20);
    camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -500));
    SubScene subScene = new SubScene(group, 500, 400, true, SceneAntialiasing.BALANCED);
    subScene.setCamera(camera);
    root = new Group(subScene);

    Scene scene = new Scene(root, 500, 400);

    primaryStage.setTitle("HUD: 2D Labels over 3D SubScene");
    primaryStage.setScene(scene);
    primaryStage.show();

    updateLabels(box);

    scene.setOnMousePressed(event -> {
        mousePosX = event.getSceneX();
        mousePosY = event.getSceneY();
    });

    scene.setOnMouseDragged(event -> {
        mousePosX = event.getSceneX();
        mousePosY = event.getSceneY();
        rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
        rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
        mouseOldX = mousePosX;
        mouseOldY = mousePosY;
        updateLabels(box);
    });
}

private List<Point3D> generateDots(Node box) {
    List<Point3D> vertices = new ArrayList<>();
    Bounds bounds = box.getBoundsInLocal();
    vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ())));
    vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ())));
    return vertices;
}

private void updateLabels(Node box) {
    root.getChildren().removeIf(Label.class::isInstance);
    SubScene oldSubScene = NodeHelper.getSubScene(box);
    AtomicInteger counter = new AtomicInteger(1);
    generateDots(box).stream()
        .forEach(dot -> {
            Point3D coordinates = SceneUtils.subSceneToScene(oldSubScene, dot);
            Point2D p2 = CameraHelper.project(SceneHelper.getEffectiveCamera(box.getScene()), coordinates);
            Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
            label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
            label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
            root.getChildren().add(label);
        });
}

The FXyz3D library has another similar sample.

EDIT

A late edit of this answer, but it is worthwhile mentioning that there is no need for private API. There is public API in the Node::localToScene methods that allows traversing the subScene.

So this just works (note the true argument):

Point3D p2 = box.localToScene(dot, true);

According to the JavaDoc for Node::localToScene:

Transforms a point from the local coordinate space of this Node into the coordinate space of its scene. If the Node does not have any SubScene or rootScene is set to true, the result point is in Scene coordinates of the Node returned by getScene(). Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Point3D).

Without true the conversion is within the subScene, but with it, the conversion goes from the current subScene to the scene. In this case, this methods calls SceneUtils::subSceneToScene, so we don't need to do it anymore.

With this, updateLabels gets simplified to:

private void updateLabels(Node box) {
    root.getChildren().removeIf(Label.class::isInstance);
    AtomicInteger counter = new AtomicInteger(1);
    generateDots(box).stream()
            .forEach(dot -> {
                Point3D p2 = box.localToScene(dot, true);
                Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
                label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
                label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
                root.getChildren().add(label);
            });
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!