You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX相机绕X轴旋转异常问题求助

JavaFX 3D相机旋转异常问题解决

问题现象

尝试让相机绕X轴旋转时,相机未按预期绕自身轴转动,反而以全局原点(0,0,0)为中心旋转。初始状态下相机对准立方体,左移鼠标(X轴角度+2)时立方体沿大圆周移动,右移则反向;但将旋转轴应用到立方体时表现正常。期望实现现实转向逻辑:相机右转时立方体左移,左转时立方体右移。

原问题代码

public class javafx extends Application {
    int ze = 0;
    int ye = 0;
    int xe = 0;

    PerspectiveCamera cam = new PerspectiveCamera();
    //the rotation angles//
    Rotate rx = new Rotate();
    { rx.setAxis(Rotate.X_AXIS); }
    Rotate ry = new Rotate();
    { ry.setAxis(Rotate.Y_AXIS); }
    Rotate rz = new Rotate();
    { rz.setAxis(Rotate.Z_AXIS); }

    int xt = 0;
    int yt = 0;
    int one;
    int two;
    boolean flip = false;

    public static void addRotate(Node node, Point3D rotationAxis, double angle) {
        ObservableList<Transform> transforms = node.getTransforms();
        try {
            for (Transform t : transforms) {
                rotationAxis = t.inverseDeltaTransform(rotationAxis);
            }
        } catch (NonInvertibleTransformException ex) {
            throw new IllegalStateException(ex);
        }
        transforms.add(new Rotate(angle, rotationAxis));
    }

    public void start(Stage stage) {
        Box cube = new Box();

        cam.getTransforms().addAll(rx, rz, ry);

        cube.setDepth(100.0);
        cube.setHeight(100.0);
        cube.setWidth(200.0);
        cube.setCullFace(CullFace.BACK);
        cube.setDrawMode(DrawMode.FILL);
        PhongMaterial material = new PhongMaterial();
        material.setDiffuseColor(Color.BROWN);
        cube.setMaterial(material);
        cube.setTranslateX(1500.0);
        cube.setTranslateY(500.0);
        cube.setTranslateZ(0.0);

        cam.setTranslateX(0);
        cam.setTranslateY(0);
        cam.setTranslateZ(0);
        cam.setScaleX(2);
        cam.setScaleY(2);
        cam.setScaleZ(2);

        Group root = new Group(cube, cam);
        Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
        double screenHeight = dimension.getHeight();
        double screenWidth = dimension.getWidth();

        Scene scene = new Scene(root, 0,0);
        stage.setFullScreen(true);
        scene.setCamera(cam);
        stage.setTitle("3d space");
        stage.setScene(scene);
        stage.show();

        stage.setOnHiding( event -> {  System.exit(0);} );
        //to get the input from a mouse vvvv  //
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable toRun;
        toRun = new Runnable() {
            public void run() {
                if(!flip){
                    one = MouseInfo.getPointerInfo().getLocation().x;
                    two = MouseInfo.getPointerInfo().getLocation().x;
                    flip = true;
                }else{
                    flip = false;
                    if(one > MouseInfo.getPointerInfo().getLocation().x){
                        xt = xt +2;
                        ry.setAngle(xt);
                        System.out.println("left");
                    }else{
                        if(one < MouseInfo.getPointerInfo().getLocation().x){
                            System.out.println("right");
                            xt = xt -2;
                            ry.setAngle(xt);
                        }
                    }
                }
            }
        };
        ScheduledFuture<?> handle = scheduler.scheduleAtFixedRate(toRun, 1, 1, TimeUnit.MILLISECONDS);
    }
    public static void main(String args[]){
        launch(args);
    }
}

问题根源

JavaFX中直接给相机添加Rotate变换时,默认基于全局坐标系原点旋转,而非相机自身的局部坐标系。这导致相机绕原点转动,视觉上呈现为立方体在绕原点做圆周运动,而非相机自身转向。

修复方案

关键修改点

  1. 改用相机局部旋转属性:使用PerspectiveCamera的rotate和rotationAxis属性实现局部旋转,默认基于相机自身坐标系计算,无需额外设置旋转中心。
  2. 修正线程安全问题:将ScheduledExecutorService替换为JavaFX原生的AnimationTimer,确保所有UI操作在FX应用线程执行。
  3. 匹配现实转向逻辑:反转旋转角度的增减方向,实现“鼠标右移→相机右转→立方体左移”的自然体验。

修正后的完整代码

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import java.awt.MouseInfo;

public class JavaFX3DCameraFix extends Application {
    private PerspectiveCamera cam = new PerspectiveCamera();
    private double currentYAngle = 0;
    private double lastMouseX;
    private boolean firstMouseMove = true;

    @Override
    public void start(Stage stage) {
        // 创建立方体
        Box cube = new Box(200.0, 100.0, 100.0);
        cube.setCullFace(CullFace.BACK);
        cube.setDrawMode(DrawMode.FILL);
        PhongMaterial material = new PhongMaterial();
        material.setDiffuseColor(Color.BROWN);
        cube.setMaterial(material);
        cube.setTranslateX(1500.0);
        cube.setTranslateY(500.0);
        cube.setTranslateZ(0.0);

        // 配置相机
        cam.setTranslateX(0);
        cam.setTranslateY(0);
        cam.setTranslateZ(0);
        cam.setFieldOfView(60); // 设置合理视野角度

        Group root = new Group(cube, cam);
        Scene scene = new Scene(root, 0, 0, true); // 启用3D场景
        scene.setCamera(cam);

        stage.setFullScreen(true);
        stage.setTitle("3D Space");
        stage.setScene(scene);
        stage.show();

        // 使用AnimationTimer处理鼠标输入,确保线程安全
        new AnimationTimer() {
            @Override
            public void handle(long now) {
                double mouseX = MouseInfo.getPointerInfo().getLocation().getX();
                if (firstMouseMove) {
                    lastMouseX = mouseX;
                    firstMouseMove = false;
                    return;
                }

                // 计算鼠标移动增量,调整旋转灵敏度
                double deltaX = mouseX - lastMouseX;
                currentYAngle -= deltaX * 0.1;

                // 应用相机局部Y轴旋转
                cam.setRotationAxis(Rotate.Y_AXIS);
                cam.setRotate(currentYAngle);

                lastMouseX = mouseX;
            }
        }.start();
    }

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

效果说明

  • 相机绕自身Y轴旋转,转动时不会偏离初始观测方向,完全符合现实转向逻辑。
  • 鼠标右移时,相机右转,立方体在视野中向左移动;鼠标左移时则相反。
  • 使用AnimationTimer替代线程池,避免了跨线程操作UI的风险,提升了程序稳定性。

内容的提问来源于stack exchange,提问作者king cannibal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:20:23