JavaFX 在虚拟平面上用鼠标移动 3D 对象 [英] JavaFX Moving 3D objects with mouse on a virtual plane

查看:44
本文介绍了JavaFX 在虚拟平面上用鼠标移动 3D 对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在 JavaFX 中创建我的第一个 3D 游戏时 - 您可以在其中使用鼠标从零件组装船只.这会带来一个问题,因为 JAVAFX 似乎没有将 PerspectiveCamera 屏幕 2D 坐标转换为场景 3D 空间的原生方法.

As I was creating my first 3D game in JavaFX - where you would be able to assemble ships from parts using the mouse. This presents a problem since JAVAFX seems to have no native metods that work for converting PerspectiveCamera screen 2D coordinates into the scene's 3D space.

这是我正在努力实现的目标的代表.鼠标移动的块应该在一个假想平面上移动,该平面始终相对于相机旋转 90 度:我试图用三角学来解决这个问题,但没有取得多大成功.我没有附上代码片段,因为我正在寻找更通用的数学解决方案,但会在需要时提供.

Here is a representation of what I'm trying to acheive. A block moved by the mouse should move on an imaginary plane that is always rotated 90 in relation to the camera: I've tried to solve the problem with trigonometry without much success. I've not attached a code snippet as I'm looking for a more generic mathematical solution, but will provide it if requested.

感谢所有帮助!

想要的结果:

推荐答案

正如@jdub1581 指出的,Camera 是将鼠标移动与场景中的 3D 对象绑定的关键.

As @jdub1581 points out, the Camera is the key to bind mouse movement with your 3D objets on the scene.

对于初学者来说,我们知道公共 API PickResult,它允许我们基于从相机位置执行的一些光线追踪技术,用鼠标选择一个 3D 对象.

For starterts, we know about public API PickResult, that allows us to select a 3D object with the mouse, based on some ray tracing techniques performed from the camera position.

但是一旦我们有了一个对象,移动它就是另一回事了.

But once we have an object, moving it is a different problem.

前段时间在寻找这个问题的解决方案(在 3D 空间中用 2D 鼠标移动 3D 对象)时,我发现了 Camera3D 类.

Looking for a solution for this problem (moving 3D objects with a 2D mouse in 3D space) a while ago, I found the Camera3D class at the Toys project on the OpenJFX repository.

它有一个名为 unProjectDirection 的有前途的方法:

It has a promissing method called unProjectDirection:

/*
 * returns 3D direction from the Camera position to the mouse
 * in the Scene space 
 */

public Vec3d unProjectDirection(double sceneX, double sceneY, 
                                double sWidth, double sHeight) {
}

由于您要求提供数学解释,因此该方法使用了您正在寻找的三角函数.这将为您提供基于 (x,y) 鼠标坐标的 3D 矢量,使用私有 Vec3d 类(我们可以用公共 Point3D 替换):

Since you asked for mathematical explanation, this method uses the trigonometry you were looking for. This will give you a 3D vector based on (x,y) mouse coordinates, using a private Vec3d class (that we can replace with public Point3D):

double tanOfHalfFOV = Math.tan(Math.toRadians(camera.getFieldOfView()) * 0.5f);
Vec3d vMouse = new Vec3d(tanOfHalfFOV*(2*sceneX/sWidth-1), tanOfHalfFOV*(2*sceneY/sWidth-sHeight/sWidth), 1);

应用一些进一步的变换来获得场景坐标中的归一化向量.

Some further transformations are applied to get a normalized vector in scene coordinates.

下一步将在实坐标中转换这个归一化向量,只使用从相机到物体的距离,在拾取结果中给出,并转换物体位置.

The next step will transform this normalized vector in real coordinates, just using the distance from the camera to the object, given on the pick result, and transforming the object position.

基本上,这段代码概括了拖动对象的整个过程:

Basically, this snippet of code outlines the whole process of dragging an object:

    scene.setOnMousePressed((MouseEvent me) -> {
        vecIni = unProjectDirection(me.getSceneX(), me.getSceneY(), 
                     scene.getWidth(),scene.getHeight()); 
        distance=me.getPickResult().getIntersectedDistance();           
    });

    scene.setOnMouseDragged((MouseEvent me) -> {
        vecPos = unProjectDirection(mousePosX, mousePosY,
                 scene.getWidth(),scene.getHeight());
        Point3D p=vecPos.subtract(vecIni).multiply(distance);
        node.getTransforms().add(new Translate(p.getX(),p.getY(),p.getZ()));
        vecIni=vecPos;
        distance=me.getPickResult().getIntersectedDistance();
    });

这是一个完整的基本示例:

And this is a full working basic example:

    public class Drag3DObject extends Application {

    private final Group root = new Group();
    private PerspectiveCamera camera;
    private final double sceneWidth = 800;
    private final double sceneHeight = 600;

    private double mousePosX;
    private double mousePosY;
    private double mouseOldX;
    private double mouseOldY;
    private final Rotate rotateX = new Rotate(-20, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-20, Rotate.Y_AXIS);

    private volatile boolean isPicking=false;
    private Point3D vecIni, vecPos;
    private double distance;
    private Sphere s;

    @Override
    public void start(Stage stage) {
        Box floor = new Box(1500, 10, 1500);
        floor.setMaterial(new PhongMaterial(Color.GRAY));
        floor.setTranslateY(150);
        root.getChildren().add(floor);

        Sphere sphere = new Sphere(150);
        sphere.setMaterial(new PhongMaterial(Color.RED));
        sphere.setTranslateY(-5);
        root.getChildren().add(sphere);

        Scene scene = new Scene(root, sceneWidth, sceneHeight, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.web("3d3d3d"));

        camera = new PerspectiveCamera(true);
        camera.setVerticalFieldOfView(false);

        camera.setNearClip(0.1);
        camera.setFarClip(100000.0);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -3000));

        PointLight light = new PointLight(Color.GAINSBORO);
        root.getChildren().add(light);
        root.getChildren().add(new AmbientLight(Color.WHITE));
        scene.setCamera(camera);

        scene.setOnMousePressed((MouseEvent me) -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            PickResult pr = me.getPickResult();
            if(pr!=null && pr.getIntersectedNode() != null && pr.getIntersectedNode() instanceof Sphere){
                distance=pr.getIntersectedDistance();
                s = (Sphere) pr.getIntersectedNode();
                isPicking=true;
                vecIni = unProjectDirection(mousePosX, mousePosY, scene.getWidth(),scene.getHeight());
            }
        });
        scene.setOnMouseDragged((MouseEvent me) -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            if(isPicking){
                vecPos = unProjectDirection(mousePosX, mousePosY, scene.getWidth(),scene.getHeight());
                Point3D p=vecPos.subtract(vecIni).multiply(distance);
                s.getTransforms().add(new Translate(p.getX(),p.getY(),p.getZ()));
                vecIni=vecPos;
                PickResult pr = me.getPickResult();
                if(pr!=null && pr.getIntersectedNode() != null && pr.getIntersectedNode()==s){
                    distance=pr.getIntersectedDistance();
                } else {
                    isPicking=false;
                }
            } else {
                rotateX.setAngle(rotateX.getAngle()-(mousePosY - mouseOldY));
                rotateY.setAngle(rotateY.getAngle()+(mousePosX - mouseOldX));
                mouseOldX = mousePosX;
                mouseOldY = mousePosY;
            }
        });
        scene.setOnMouseReleased((MouseEvent me)->{
            if(isPicking){
                isPicking=false;
            }
        });

        stage.setTitle("3D Dragging");
        stage.setScene(scene);
        stage.show();
    }

    /*
     From fx83dfeatures.Camera3D
     http://hg.openjdk.java.net/openjfx/8u-dev/rt/file/5d371a34ddf1/apps/toys/FX8-3DFeatures/src/fx83dfeatures/Camera3D.java
    */
    public Point3D unProjectDirection(double sceneX, double sceneY, double sWidth, double sHeight) {
        double tanHFov = Math.tan(Math.toRadians(camera.getFieldOfView()) * 0.5f);
        Point3D vMouse = new Point3D(tanHFov*(2*sceneX/sWidth-1), tanHFov*(2*sceneY/sWidth-sHeight/sWidth), 1);

        Point3D result = localToSceneDirection(vMouse);
        return result.normalize();
    }

    public Point3D localToScene(Point3D pt) {
        Point3D res = camera.localToParentTransformProperty().get().transform(pt);
        if (camera.getParent() != null) {
            res = camera.getParent().localToSceneTransformProperty().get().transform(res);
        }
        return res;
    }

    public Point3D localToSceneDirection(Point3D dir) {
        Point3D res = localToScene(dir);
        return res.subtract(localToScene(new Point3D(0, 0, 0)));
    }

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

这将允许您在场景中选择和拖动球体:

That will allow you picking and dragging the sphere on the scene:

这篇关于JavaFX 在虚拟平面上用鼠标移动 3D 对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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