繁体   English   中英

JavaFX 3D 透视相机机芯

[英]JavaFX 3D perspective camera movement

所以现在,多亏了我之前的问题的帮助,以及用户“trashgod”(在我的例子中更恰当地发音为“javafxgod”)的非常有用的回答,我已经能够在我尝试的实际 model 上取得很大进展使用 JavaFX 进行构建,并对其 function 感到更加自在......然而,尽管现在能够大致移动我的 model,但我仍然对相机的行为感到非常困惑,尽管提供的代码很有帮助有效,我还无法修改导航以按我想要的方式工作。

令人遗憾的是,经过这么多年的 JavaFX,在我看来这是一个非常令人印象深刻的基础(他们混合支持 3D 的整个方式,尤其是 2D 看起来很酷),仍然没有基本的实用程序和库像标准轨道导航(例如,像这样,这大致是我在相机导航方面试图实现的目标,用 threejs 完成,它是OrbitNavigation类)。

我至少很好地组织了它(很好地捆绑到一个Group中),我想,所以当我最终把它做得很好和抛光时,其他人至少可以从中受益,因为这实际上是每个做任何 3D 建模的人的东西在 JFX 需求中,实际上,好的示例(并非一团糟)似乎并不容易找到。

我认为我最困惑的一点(尽管进行了大量研究并且非常透彻地阅读了Node文档)是何时使用局部坐标与父坐标。 例如,在我之前的问题中,提供的代码似乎使用了父坐标,当我使用setTranslateX(getTranslateX() + 10)或如图所示的类似方法时,它们似乎被解释为父坐标,因为当我旋转 我看到自己沿着轴标记移动。

但是,当我尝试将这些父坐标转换为本地坐标时,我会遇到无法理解的行为,并且通常会完全丢失我的 model。 请哦,请有人解释这里到底发生了什么,评论中显示了几种不同的尝试和问题

import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.input.*;
import javafx.scene.transform.Rotate;


public class NavigableCamera extends Group {
    private final Camera camera;
    private final Rotate xRotate = new Rotate(0, Rotate.X_AXIS);
    private final Rotate yRotate = new Rotate(0, Rotate.Y_AXIS);
    private final Rotate zRotate = new Rotate(0, Rotate.Z_AXIS);

    private double x, y, z, angleX, angleY;
    private Node pickedNode;

    public NavigableCamera(Scene scene)  {
        camera = new PerspectiveCamera(true);
        camera.setFarClip(6000);
        camera.setNearClip(0.01);
        getChildren().add(camera);

        //okay, sure this works and the camera ends up pointing at the origin, but "level" with it.
        setTranslateZ(-500);

        //but why oh why doesn't this work to end with the camera looking "down" at the origin from above?
//        setTranslateY(-500);
//        Point3D pivot = Point3D.ZERO;
//        Rotate pointDown = new Rotate(90, pivot.getX(), pivot.getY(), pivot.getZ(), Rotate.X_AXIS);
//        getTransforms().add(pointDown);

        getTransforms().addAll(xRotate, yRotate, zRotate);

        initKeyboardControl(scene);
        initMouseControl(scene);
        initTouchControls(scene);

        scene.setCamera(camera);
    }

    private void initMouseControl(Scene scene) {

        scene.setOnMousePressed(event -> {
            x = event.getSceneX();
            y = event.getSceneY();
            angleX = xRotate.getAngle();
            angleY = yRotate.getAngle();

            PickResult pickResult = event.getPickResult();
            pickedNode = pickResult.getIntersectedNode();
            double x, y, z;
            if(pickedNode != null) {
                x = parentToLocal(pickedNode.getBoundsInParent()).getCenterX();
                y = parentToLocal(pickedNode.getBoundsInParent()).getCenterY();
                z = parentToLocal(pickedNode.getBoundsInParent()).getCenterZ();
            } else {
                Point3D pivotPoint = pickResult.getIntersectedPoint();
                x = pivotPoint.getX();
                y = pivotPoint.getY();
                z = pivotPoint.getZ();
            }

            xRotate.setPivotX(x);
            xRotate.setPivotY(y);
            xRotate.setPivotZ(z);
            yRotate.setPivotX(x);
            yRotate.setPivotY(y);
            yRotate.setPivotZ(z);
        });

        scene.setOnMouseDragged(event -> {
            xRotate.setAngle(angleX - (x - event.getSceneY()));
            yRotate.setAngle(angleY + x - event.getSceneX());
        });

        scene.setOnMouseDragReleased(event -> {
            xRotate.setPivotX(0);
            xRotate.setPivotY(0);
            xRotate.setPivotZ(0);
        });

        scene.setOnScroll(event -> {
            xRotate.setAngle(xRotate.getAngle() + event.getDeltaY() / 10);
            yRotate.setAngle(yRotate.getAngle() - event.getDeltaX() / 10);
            setTranslateX(getTranslateX() + event.getDeltaX());
            setTranslateY(getTranslateY() + event.getDeltaY());
        });
    }

    private void initKeyboardControl(Scene scene) {
        scene.setOnKeyPressed(event -> {
            KeyCode code = event.getCode();
            switch (code) {
                case SPACE:
                    moveLocalZ(+10);
                    break;
                case BACK_SPACE:
                    moveLocalZ(-10);
                    break;
                case RIGHT:
                      // this commented out setTranslate, as well as all the others seem to operate on a "PARENT" coordinate system, but when I try to get local coordinates instead, it doesn't work!  
//                    setTranslateX(getTranslateX() + 20);
                    moveLocalX(+10);
                    break;
                case LEFT:
//                    setTranslateX(getTranslateX() - 20);
                    moveLocalX(-10);
                    break;
                case UP:
                    moveLocalY(-10);
                    break;
                case DOWN:
                    moveLocalY(+10);
                    break;
                lateZ() - 30);
                    break;
            }
        });
    }

    private void moveLocalX(double amount) {
        Point3D p = localToParent(getTranslateX() + amount, getTranslateY(), getTranslateZ());
        moveToParentPoint(p);
    }

    private void moveLocalY(double amount) {
        Point3D p = localToParent(getTranslateX(), getTranslateY() + amount, getTranslateZ());
        moveToParentPoint(p);
    }

    private void moveLocalZ(double amount) {
        Point3D p = localToParent(getTranslateX(), getTranslateY(), getTranslateZ() + amount);
        moveToParentPoint(p);
    }

    private void moveToParentPoint(Point3D p) {
        setTranslateX(p.getX());
        setTranslateY(p.getY());
        setTranslateZ(p.getZ());
    }

    private void initTouchControls(Scene scene) {
        // on an entirely side note, I really don't get why this can't just be here, but it can be a member of the class? 🤨
        // double z;
        scene.setOnZoomStarted(event -> {
            z = event.getZ();
        });


        // frankly I can't even visualize what a "scale" means in the context of a "camera"
        // what I want for "zoom" is to move the camera "forwards" or "backwards" as in towards the
        // direction it's facing and away from it, but when I have tried this, I end up moving just
        // along the Z axis, wherever I'm facing!
        scene.setOnZoom(event -> {
            if (z > 0) {
                setScaleZ(getScaleZ() + event.getZoomFactor());
            } else {
                setScaleZ(getScaleZ() - event.getZoomFactor());
            }
        });
    }
}

我对这个可爱的小AxesMarker class 相当满意,起初我试图用 3D 块来做(基于一些在线示例),但这种 2D 方法更清晰,并且是 JavaFX 强大功能的一个很好的例子:

import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;

public class AxesMarker extends Group {
    Line x, y, z;

    public AxesMarker(double length) {
        x = new Line(0, 0, length, 0);
        y = new Line(0, 0, 0, length);
        z = new Line(0, 0, length, 0);
        z.getTransforms().add(new Rotate(90, 0, 0, 0, Rotate.Y_AXIS));
        x.setStroke(Color.RED);
        y.setStroke(Color.GREEN);
        z.setStroke(Color.BLUE);
        getChildren().addAll(x, y, z);
    }
}

这里只是足够的Application来演示NavigableCamera的用法:

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class CameraTest extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        Group root = new Group();
        root.getChildren().add(new AxesMarker(200));
        Scene scene = new Scene(root, 800, 800, Color.BLACK);
        NavigableCamera camera = new NavigableCamera(scene);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

要了解方向,请从这个示例开始。 请注意, xyz轴分别为红色、绿色和蓝色; 这里所示,正x指向右侧,正y指向下方,正z指向屏幕。 相机已沿z轴平移到 -2000,因此可以看到蓝色的z轴。

Z-2000.X-0

现在,将相机绕x轴旋转 -90° 并将其沿y轴平移至 -2000,这样可以直视绿色y轴。

private final Rotate xRotate = new Rotate(-90, Rotate.X_AXIS);
camera.setTranslateY(-2000);

Y-2000.X-90

最后,相应地调整键盘和鼠标处理程序。 例如,使用箭头键沿y轴移动轴组。

case UP:
    g.setTranslateY(g.getTranslateY() - 100);
    break;
case DOWN:
    g.setTranslateY(g.getTranslateY() + 100);
    break;
case HOME:
    g.xRotate.setAngle(-90);

另请参阅javaFX 中的围绕 object 旋转透视相机,它说明了围绕pivot旋转相机; 不同的是,它既相机设置在场景上,又将相机添加到相机旋转的组中。

由于您的目标是 model 一个太阳系仪,请考虑沿着合适ShapePathTransition ,如此处所示 只要您构建的黄道与xy平面重合,您就可以像往常一样旋转该组。

经过测试:

import javafx.application.Application;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/69339586/230513
 * @see https://stackoverflow.com/a/69260181/230513
 */
public class RotateCameraExample extends Application {

    private static class RotateCamera extends Group {

        private final Camera camera;
        private final Rotate xRotate = new Rotate(-90, Rotate.X_AXIS);
        private final Rotate yRotate = new Rotate(0, Rotate.Y_AXIS);
        private final Rotate zRotate = new Rotate(0, Rotate.Z_AXIS);

        public RotateCamera() {
            buildAxes();
            camera = new PerspectiveCamera(true);
            camera.setFarClip(6000);
            camera.setNearClip(0.01);
            camera.setTranslateY(-2000);
            camera.getTransforms().addAll(xRotate, yRotate, zRotate);
        }

        private void buildAxes() {
            final Box xAxis = new Box(1200, 10, 10);
            final Box yAxis = new Box(10, 1200, 10);
            final Box zAxis = new Box(10, 10, 1200);

            xAxis.setMaterial(new PhongMaterial(Color.RED));
            yAxis.setMaterial(new PhongMaterial(Color.GREEN));
            zAxis.setMaterial(new PhongMaterial(Color.BLUE));

            Group axisGroup = new Group();
            axisGroup.getChildren().addAll(xAxis, yAxis, zAxis);
            this.getChildren().add(axisGroup);
        }
    }

    @Override
    public void start(Stage stage) {
        RotateCamera g = new RotateCamera();
        Scene scene = new Scene(g, 800, 800, Color.BLACK);
        scene.setCamera(g.camera);
        stage.setScene(scene);
        stage.show();
        scene.setOnScroll((final ScrollEvent e) -> {
            g.xRotate.setAngle(g.xRotate.getAngle() + e.getDeltaY() / 10);
            g.yRotate.setAngle(g.yRotate.getAngle() - e.getDeltaX() / 10);
            g.setTranslateX(g.getTranslateX() + e.getDeltaX());
            g.setTranslateY(g.getTranslateY() + e.getDeltaY());
        });
        scene.setOnKeyPressed((KeyEvent e) -> {
            System.out.println(g.getTranslateY());
            KeyCode code = e.getCode();
            switch (code) {
                case LEFT:
                    g.zRotate.setAngle(g.zRotate.getAngle() + 10);
                    break;
                case RIGHT:
                    g.zRotate.setAngle(g.zRotate.getAngle() - 10);
                    break;
                case UP:
                    g.setTranslateY(g.getTranslateY() - 100);
                    break;
                case DOWN:
                    g.setTranslateY(g.getTranslateY() + 100);
                    break;
                case HOME:
                    g.xRotate.setAngle(-90);
                    g.yRotate.setAngle(0);
                    g.zRotate.setAngle(0);
                    g.setTranslateX(0);
                    g.setTranslateY(0);
                    g.setTranslateZ(0);
                    break;
                default:
                    break;
            }
        });
    }

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM