JavaFX 3D 透视相机运动

2024-03-01

现在,感谢我的帮助先前的问题 https://stackoverflow.com/questions/69249037/most-simple-rotate-camera-via-mouse-not-working,以及来自用户“trashgod”(在我的例子中更合适地发音为“javafxgod”)的非常有用的答案,我已经能够在我尝试使用 JavaFX 构建的实际模型上取得很大进展,并且获得了更多对其功能感到满意...但是,尽管现在能够在我的模型上大致移动,但我仍然对相机的行为感到完全困惑,尽管提供的代码有效,但我还无法修改导航以我想要的方式工作。

令人遗憾的是,JavaFX 经过这么多年的发展,在我看来已经有了令人印象深刻的基础(它们混合支持 3D 和 2D 的整个方式尤其看起来很酷),但仍然没有基本的实用程序和库像标准轨道导航(例如,像这样 https://codepen.io/pholmq/full/XGPrPd,这大致就是我在相机导航方面试图实现的目标,用 Threejs 完成,它是OrbitNavigation class).

我至少已经很好地组织了它(很好地捆绑到一个Group),我认为,这样,当我最终将其完美地完善和完善时,其他人至少可以从中受益,因为这实际上是每个在 JFX 中进行 3D 建模的人都需要的东西,而且确实是很好的例子(这些都不是)一切都混乱了)似乎不太容易找到。

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

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

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起初我试图用 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();
    }
}

找准方向,从这里开始example https://stackoverflow.com/a/69260181/230513。请注意,x, y and z轴分别为红、绿、蓝;如图所示here https://docs.oracle.com/javase/8/javafx/graphics-tutorial/camera.htm#CJAHFAHB, 积极的x指向右边,正数y点向下,点向上z点到屏幕上。相机已平移至-2000z轴,所以人们看到的是蓝色z轴在末端。

现在,围绕x轴 -90° 并沿y轴到-2000,所以人们是直视的down在绿色上y axis.

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

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

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

也可以看看在javaFX中围绕对象旋转透视相机 https://stackoverflow.com/q/43763551/230513,它说明了围绕一个pivot;不像this https://stackoverflow.com/a/69260181/230513,它两者sets现场的摄像机以及adds将相机添加到相机旋转的组中。

由于您的目标是对太阳仪进行建模,因此请考虑PathTransition https://docs.oracle.com/javase/8/javafx/api/javafx/animation/PathTransition.html沿着合适的Shape如图所示here https://stackoverflow.com/a/37370840/230513;只要你构造的黄道与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();
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaFX 3D 透视相机运动 的相关文章

随机推荐

  • 这里不允许AllowOverride

    我已经设置了一个虚拟主机 如下所示
  • 如何在任务计划程序上执行PowerShell脚本?

    我正在尝试执行我的 Powershell 脚本 每 5 分钟运行一次 我尝试使用 Windows 7 的任务计划程序来运行它 但它在记事本上打开我的脚本 并且不会在我的数据库中插入任何内容 下面是我的 Powershell V 2 0 脚本
  • 如何通过 AutoCAD Plot API 在 Forge 设计自动化中使用用户定义的字体

    我们使用 Forge Plot API 将 DWG 绘制为 PDF JPG 一位客户提出了使用名为 ROBBI TTF 的特殊 TTF 字体的 DWG 我想这个字体必须上传到 Forge 服务器才能使用 是否可以上传字体一次 以便它可以用于
  • Jupyter:更改输出单元格大小

    我在 vscode 上使用 Jupyter 自上次更新以来 每次我都会看到整个输出窗口 当它很大时 有时会很烦人 我想减少它并且能够滚动它 我尝试修改 最大输出大小 设置 但它对我来说没有任何改变 有什么帮助吗 谢谢你 我是 VS Code
  • SmartGit 在 Ubuntu 上的安装和使用

    我已经下载了最新的 SmartGit 安装 每次我想使用它时 我都需要从 SmartGit bin 目录运行脚本 smartgit sh 此过程每次都需要相同的存储库设置 在 Ubuntu 上安装 SmartGit 的正确方法是什么 因此
  • Apache URL 重写,

    我正在尝试让 URL 重写在我的网站上运行 这是我的 htaccess 的内容 RewriteEngine On RewriteRule blog index php page blog L RewriteRule about index
  • 对“cvCreateKalman”的未定义引用[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我一直在开
  • 使用剪贴板 API 时 Chrome 中的构造函数非法?

    我目前正在构建一个网络应用程序 它是我很久以前用 Visual Basic 编写的另一个程序的翻译 在 Visual Basic 中 可以将数据直接放入操作系统的剪贴板中 Chrome 中有执行此操作的功能吗 我试过了 var magice
  • 在基于nx的nodejs应用程序中使用环境变量

    我已经在 nrwl nx 工作区中设置了一个包含多个 Nodejs 和 Angular 应用程序的项目 我正在尝试使用环境文件 在 Nodejs 应用程序内 我已经像这样设置了导入 import environment from envir
  • OLE DB 目标:转换规范的字符值无效

    我的表来源 num facture TYPE actif date 1 1 1 2010 01 31 00 00 00 000 2 2 1 2011 01 31 00 00 00 000 3 3 2 2012 01 31 00 00 00
  • 获取当前年份和月份会产生奇怪的结果

    我正在做一个与Android相关的学习项目 我试图使用下面的代码获取当前年份和月份 但它对我不起作用 GregorianCalendar gc new GregorianCalendar gc YEAR returning 1 gc MON
  • 在 WebSocketContext 中正确处置 WebSocket

    在 ASP NET 中 当您提供的处理程序HttpContext AcceptWebSocketRequest http msdn microsoft com en us library system web httpcontext acc
  • 将除一个目录之外的整个站点重定向到新站点 - apache .htaccess

    我正在尝试将当前站点移至新域 一个目录除外 Example 当前站点 oldsite olddomain example 新站点 新域名 example 所以我知道我可以创建一个 htaccess 重定向条目来执行此操作 并且它有效 但我想
  • 为什么要避免在 java 中使用 Runtime.exec() ?

    Process p Runtime getRuntime exec command is p getInputStream byte userbytes new byte 1024 is read userbytes 我想从 java 在
  • Teradata 优化器 SQL 中的 Equal 与 Like

    我目前正在尝试优化一些 bobj 报告 其中我们的后端是 Teradata 这Teradata优化器看起来很挑剔 我想知道是否有人想出了一个解决方案或解决方法让优化器以类似的方式对待喜欢的人 My issue is that we allo
  • VMWare vFabric tc Server 和 maven 找不到 org.springframework.web.servlet.DispatcherServlet

    我知道以前发布过一个与此非常相似的问题 但该解决方案不适用于我的问题 我正在尝试使用 Maven 启动一个基本的 Spring 项目 这是我的 pom xml
  • Pandas to_sql 不适用于 SQL Alchemy 连接

    我使用以下代码通过 SQL alchemy 连接到 MySQL 数据库 from sqlalchemy import create engine import pandas as pd query SELECT FROM hello eng
  • Java Swing - 本地化温度

    有没有办法在Java中本地化温度 就像温度格式将基于区域设置 例如 对于挪威语 温度格式应为 14 C 度数符号之前应该有一个空格 但其他语言应该是14 C 以下示例演示了温度本地化 包括按区域设置特定属性对十进制值进行可自定义的舍入和格式
  • 覆盖 PHPDoc 中的返回类型

    我有课Abcwith 方法 正文并不重要 return SomeBaseClass function getAll 在幼儿班Abc called AbcChild我想重新定义仅返回类的类型 以便在 Netbeans 中正确查看它 我可以在不
  • JavaFX 3D 透视相机运动

    现在 感谢我的帮助先前的问题 https stackoverflow com questions 69249037 most simple rotate camera via mouse not working 以及来自用户 trashgo