Как получить 2D координаты в окне для 3D-объекта в javafx - PullRequest
0 голосов
/ 25 августа 2018

В javafx, если у нас есть 2D HUD (сделанный из Pane, а затем из него мы создаем объект SubScene для 2D Hud) и 3D SubScene, а внутри 3D сцены мы имеем некоторый объект с координатами (x, y, z) - как можно мы получаем 2D координаты в нашем HUD объекта, если он находится в поле зрения нашей перспективной камеры?

Я попытался получить первые координаты сцены объекта, а затем преобразовать его (sceneToScreen) координаты и то же самое для точки (0,0) панели, а затем вычесть первую точку из второй точки, но я не получил правильный результат , Извините из-за моего плохого английского. Может ли кто-нибудь помочь с этим?

1 Ответ

0 голосов
/ 25 августа 2018

Существует способ преобразовать трехмерные координаты объекта в подцеке в координаты двухмерной сцены, но, к сожалению, он использует закрытый API, поэтому рекомендуется не полагаться на него.

Идея основана на том, как работает проекция камеры, и основана на методе com.sun.javafx.scene.input.InputEventUtils.recomputeCoordinates(), который обычно используется для ввода событий из PickResult.

Допустим, у вас есть узел в дополнительной сцене. Для данной точки этого узла вы можете получить его координаты, такие как:

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

и вы можете узнать о дополнительной сцене узла:

SubScene subScene = NodeHelper.getSubScene(node);

Теперь вы можете использовать SceneUtils::subSceneToScene метод, который

Переводит точку из внутренних координат вспомогательной сцены в координаты сцены.

чтобы получить новый набор координат, привязанных к сцене:

coordinates = SceneUtils.subSceneToScene(subScene, coordinates);

Но это все еще трехмерные координаты.

Последний шаг для преобразования этих изображений в 2D с использованием CameraHelper::project:

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

В следующем примере 2D-метки размещаются на сцене точно в том же положении, что и 8 вершин 3D-блока в подцеке.

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

HUD

Библиотека FXyz3D имеет еще один аналогичный образец .

EDIT

Позднее редактирование этого ответа, но стоит упомянуть, что нет необходимости в частном API. В методах Node::localToScene есть общедоступный API-интерфейс, позволяющий обходить подсцену.

Так что это просто работает (обратите внимание на аргумент true):

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

Согласно JavaDoc для Node::localToScene:

Преобразует точку из локального координатного пространства этого узла в координатное пространство его сцены. Если узел не имеет никакой SubScene или rootScene имеет значение true, точка результата находится в координатах сцены узла, возвращаемого getScene (). В противном случае используются координаты подцены, что эквивалентно вызову 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);
            });
}
...