Projecting 2D mouse coordinates to 3D

  Kiến thức lập trình

I am looking to implement a simple 3D triangle creator in JavaFX, for that I need to be able to convert the 2D mouse position to 3D.

After searching for a while, it seems like the only way to do this is to use the internal CameraHelper class, specifically it’s pickProjectPlane(camera, x, y) method

This method however does not seem to work as I’d expect with my setup, where i have a SubScene for the 3D scene

Here is some example code, my goal is to have a box rendered at the click position

public class JavaFX3DPickingTest extends Application {

    private SubScene scene3D;
    private OrbitCamera orbitCamera;
    private Group sceneGroup = new Group();

    @Override
    public void start(Stage stage) {
        AnchorPane pane = new AnchorPane();
        create3DSubScene(800, 600);
        pane.getChildren().add(scene3D);
        // for reference
        Box referenceBox = new Box(1, 1, 1);
        sceneGroup.getChildren().add(referenceBox);

        Scene scene = new Scene(pane, 800, 600, true);
        // even if the listener is added to scene3D, it produces the same result
        scene.setOnMousePressed(event -> {
            // only pick when alt is down
            if (!event.isAltDown()) {
                return;
            }
            double x = event.getSceneX();
            double y = event.getSceneY();
            // these values are too huge, clearly they need to be scaled, but I don't know by what factor exactly
            Point3D pos = CameraHelper.pickProjectPlane(orbitCamera.camera, x, y);
            System.out.println("Pos: " + pos);
            Box box = new Box(1, 1, 1);
            box.setTranslateX(pos.getX());
            box.setTranslateY(pos.getY());
            box.setTranslateZ(pos.getZ());
            sceneGroup.getChildren().add(box);
        });
        stage.setScene(scene);
        stage.show();
    }

    private void create3DSubScene(double width, double height) {
        scene3D = new SubScene(sceneGroup, width, height, true, SceneAntialiasing.BALANCED);
        scene3D.setFill(Color.rgb(25, 25, 25));
        orbitCamera = new OrbitCamera(scene3D, sceneGroup);
    }

    private static class OrbitCamera {

        private final SubScene subScene;
        private final Group root3D;

        private final double MAX_ZOOM = 300.0;

        public OrbitCamera(SubScene subScene, Group root) {
            this.subScene = subScene;
            this.root3D = root;
            init();
        }

        private void init() {
            camera.setNearClip(0.1D);
            camera.setFarClip(MAX_ZOOM * 1.15D);
            root3D.getChildren().add(camera); // do we really need to do this

            updateCamera();

            subScene.setCamera(camera);
            subScene.setOnScroll(event -> {
                double zoomFactor = 1.05;
                double deltaY = event.getDeltaY();

                if (deltaY < 0) {
                    zoomFactor = 2.0 - zoomFactor;
                }
                double z = cameraDistance / zoomFactor;
                z = Math.max(1, Math.min(MAX_ZOOM, z));
                cameraDistance = z;
                updateCamera();
            });

            subScene.setOnMousePressed(event -> {
                if (event.isAltDown()) {
                    return;
                }
                mousePosX = event.getSceneX();
                mousePosY = event.getSceneY();
            });

            subScene.setOnMouseDragged(event -> {
                if (event.isAltDown()) {
                    return;
                }
                double modifier = 1.0;
                double modifierFactor = 0.3;

                if (event.isControlDown()) modifier = 0.1;
                if (event.isSecondaryButtonDown()) modifier = 0.035;

                double sceneX = event.getSceneX();
                double sceneY = event.getSceneY();
                double mouseDeltaX = sceneX - mousePosX;
                double mouseDeltaY = sceneY - mousePosY;
                mousePosX = sceneX;
                mousePosY = sceneY;

                if (event.isSecondaryButtonDown()) {
                    double dx = -mouseDeltaX * modifierFactor * modifier * 2.0;
                    double dy = -mouseDeltaY * modifierFactor * modifier * 2.0;
                    translation.setX(translation.getX() + dx);
                    translation.setY(translation.getY() + dy);
                } else if (event.isPrimaryButtonDown()) {
                    double dx = mouseDeltaX * modifierFactor * modifier * 2.0;
                    double dy = -mouseDeltaY * modifierFactor * modifier * 2.0;
                    yaw += dx;
                    pitch += dy;
                }

                updateCamera();
            });
        }

        public void updateCamera() {
            Affine affine = new Affine();
            Rotate rotateY = new Rotate(yaw, Rotate.Y_AXIS);
            Rotate rotateX = new Rotate(pitch, Rotate.X_AXIS);
            affine.append(rotateY);
            affine.append(rotateX);
            affine.append(new Translate(translation.getX(), translation.getY(), -cameraDistance));
            position = affine.transform(0, 0, 0);
            camera.getTransforms().setAll(affine);
        }


        private final PerspectiveCamera camera = new PerspectiveCamera(true);
        private double cameraDistance = 30;
        private Point3D position;
        private final Translate translation = new Translate(0.1889, -3.4019, -cameraDistance);
        private double yaw = 1.0;
        private double pitch = -19.4;
        private double mousePosX = 0;
        private double mousePosY = 0;
    }

}

LEAVE A COMMENT