如何在javafx中为3D对象获取窗口上的2D坐标 [英] How to get 2D coordinates on window for 3D object in javafx

查看:59
本文介绍了如何在javafx中为3D对象获取窗口上的2D坐标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在javafx中,如果我们有2D HUD(由Pane组成,然后我们为2D Hud创建SubScene对象)和3D SubScene,在3D场景中我们有一些坐标(x,y,z)的对象 - 如何如果对象在我们的透视相机的视野中,我们会在对象的 HUD 中获得 2D 坐标吗?

In javafx if we have 2D HUD (made of Pane and then out of it we create SubScene object for 2D Hud) and 3D SubScene and inside 3D scene we have some object with coordinates (x,y,z) - how can we get 2D coordinates in our HUD of the object if it is in visual field of our perspective camera?

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

I tried to get first Scene coordinates of the object and then convert it (sceneToScreen) coordinates and the same for point (0,0) of Pane and then to subtract first point from second point but i don't get right result. Sorry because of my bad English.Can Someone help with this?

推荐答案

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

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.

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

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

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

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

但这些仍然是 3D 坐标.

But these are still 3D coordinates.

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

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

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

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

FXyz3D 图书馆 有另一个类似的sample.

The FXyz3D library has another similar sample.

编辑

此答案的后期编辑,但值得一提的是,不需要私有 API.Node::localToScene 方法中有公共 API,允许遍历 subScene.

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.

所以这只是工作(注意true参数):

So this just works (note the true argument):

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

根据Node::localToScene的JavaDoc:

According to the JavaDoc for Node::localToScene:

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

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

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

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.

这样,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);
            });
}

这篇关于如何在javafx中为3D对象获取窗口上的2D坐标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆