如何在javafx中获取3D对象窗口上的2D坐标

Val*_*lis 4 3d javafx hud javafx-3d fxyz3d

在javafx中,如果我们有2D HUD(由Pane组成,然后我们为2D Hud创建SubScene对象)和3D SubScene,并且在3D场景中我们有一些带有坐标(x,y,z)的对象 - 我们如何获得2D如果该物体位于我们的透视相机的视野中,那么它在我们的 HUD 中的坐标是多少?

我尝试获取对象的第一个场景坐标,然后将其转换为 (sceneToScreen) 坐标,并且与窗格的点 (0,0) 相同,然后从第二点中减去第一个点,但我没有得到正确的结果。抱歉,我的英语不好。有人可以帮忙吗?

Jos*_*eda 5

有一种方法可以将子场景中对象的 3D 坐标转换为 2D 场景坐标,但不幸的是它使用私有 API,因此建议不要依赖它。

这个想法基于相机投影的工作原理,并且基于com.sun.javafx.scene.input.InputEventUtils.recomputeCoordinates()通常用于来自PickResult.

假设您在子场景中有一个节点。对于该节点的给定点,您可以获得其坐标,如下所示:

Point3D coordinates = node.localToScene(Point3D.ZERO);
Run Code Online (Sandbox Code Playgroud)

可以了解该节点的子场景:

SubScene subScene = NodeHelper.getSubScene(node);
Run Code Online (Sandbox Code Playgroud)

现在您可以使用SceneUtils::subSceneToScene该方法

将点从内部子场景坐标转换为场景坐标。

获取一组新的坐标,参考场景:

coordinates = SceneUtils.subSceneToScene(subScene, coordinates);
Run Code Online (Sandbox Code Playgroud)

但这些仍然是 3D 坐标。

将它们转换为 2D 的最后一步是使用CameraHelper::project

final Camera effectiveCamera = SceneHelper.getEffectiveCamera(node.getScene());        
Point2D p2 = CameraHelper.project(effectiveCamera, coordinates);
Run Code Online (Sandbox Code Playgroud)

以下示例将 2D 标签放置在场景中,子场景中 3D 框的 8 个顶点的位置完全相同。

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);
        });
}
Run Code Online (Sandbox Code Playgroud)

平视显示器

FXyz3D还有另一个类似的示例

编辑

这个答案的后期编辑,但值得一提的是,不需要私有 API。方法中有公共 APINode::localToScene允许遍历子场景。

所以这才有效(注意true参数):

Point3D p2 = box.localToScene(dot, true);
Run Code Online (Sandbox Code Playgroud)

根据 JavaDoc 的说法Node::localToScene

将点从该 Node 的局部坐标空间变换到其场景的坐标空间。如果 Node 没有任何 SubScene 或 rootScene 设置为 true,则结果点位于 getScene() 返回的 Node 的场景坐标中。否则,使用子场景坐标,相当于调用 localToScene(Point3D)。

没有true转换是在子场景内,但是有了它,转换是从当前子场景到场景。在这种情况下,这个方法调用SceneUtils::subSceneToScene,所以我们不需要再这样做了。

这样,updateLabels就可以简化为:

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);
            });
}
Run Code Online (Sandbox Code Playgroud)