Search code examples
javajavafxjavafx-3d

Most simple rotate camera via mouse not working


Okay, this is driving me crazy. The documentation is pretty weak, the example application from Oracle is very weird, with a huge convoluted helper class, and even the questions about it on here have no answers!

I've largely followed and simplified this tutorial, but instead of rotating the object, I'm trying to rotate the camera, so when you drag the mouse, it should orbit the camera.

However, though I have confirmed via console logs and debugging that the event handlers are being called, and everything seems to have the right values, my rotates just never happen! What am I missing?

Furthermore, I can't manage to move the camera at all, even the (commented out) translateX and the like don't work either, so I am quite stumped, but can't get the axis to look like anywhere but the upper left corner!

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;

public class RotateCameraExample extends Group {

    private double anchorX, anchorY;
    private double anchorAngleX = 0;
    private double anchorAngleY = 0;
    private DoubleProperty angleX = new SimpleDoubleProperty(0);
    private DoubleProperty angleY = new SimpleDoubleProperty(0);

    Camera camera;
    Group axes;

    public RotateCameraExample() {
        axes = buildAxes();
        getChildren().add(axes);

        camera = new PerspectiveCamera(true);
        camera.setFarClip(6000);
        camera.setNearClip(0.01);
        //camera.translateYProperty().set(300); // this doesn't do anything!  why?

        getChildren().add(camera);
        initMouseControl();
    }

    private void initMouseControl() {
        Rotate xRotate = new Rotate(0, Rotate.X_AXIS);
        Rotate yRotate = new Rotate(0, Rotate.Y_AXIS);
        camera.getTransforms().addAll(xRotate, yRotate);

        xRotate.angleProperty().bind(angleX);
        yRotate.angleProperty().bind(angleY);

        setOnMousePressed(event -> {
            anchorX = event.getSceneX();
            anchorY = event.getSceneY();
            anchorAngleX = angleX.get();
            anchorAngleY = angleY.get();
        });

        setOnMouseDragged(event -> {
            angleX.set(anchorAngleX - (anchorY - event.getSceneY()));
            angleY.set(anchorAngleY + anchorX - event.getSceneX());
        });
    }

    private Group 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);
        return axisGroup;
    }
}

Here can see that the axis is visible in the upper left, and I want it to remain at (0, 0, 0) while moving the camera around it.

image

Here is the Application launch code, which is clearly not the issue:

public class TestApp extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        RotateCameraExample g = new RotateCameraExample();
        Scene scene = new Scene(g, 800, 800, Color.BLACK);
        stage.setScene(scene);
        stage.show();
    }
    public static void main(String[] args) {
        launch();
    }
}

Solution

  • Instead of adding the camera to the children of the Group,

    getChildren().add(camera);
    

    You should set the scene's camera.

    scene.setCamera(g.camera);
    

    You will immediately see the axes at the center of the screen. Similarly, mouse handler(s) should be applied to the scene. You can then update the group's transforms in your scene's handler(s).

    As an example, the variation below alters the camera's rotation in response to mouse scroll events. Note how the vertical mouse scroll affects rotation about the X axis, while horizontal mouse scroll affects rotation about the Y axis. The same gestures also translate the group as a whole. An assortment of keyboard commands enable one to rotate the camera around the Z axis, dolly along the Z axis, and reset the scene.

    You can translate and rotate about points on a circle, as illustrated here; in contrast, this related example animates the rotation of an object about a pivot.

    imageImag

    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/69260181/230513
     */
    public class RotateCameraExample extends Application {
    
        private static class RotateCamera 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);
    
            public RotateCamera() {
                buildAxes();
                camera = new PerspectiveCamera(true);
                camera.setFarClip(6000);
                camera.setNearClip(0.01);
                camera.setTranslateZ(-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) -> {
                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.setTranslateZ(g.getTranslateZ() - 100);
                        break;
                    case DOWN:
                        g.setTranslateZ(g.getTranslateZ() + 100);
                        break;
                    case HOME:
                        g.xRotate.setAngle(0);
                        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();
        }
    }