Restricting a 3D object mouse drag movement to a plane in JavaFX
Asked Answered
U

1

6

I'm using JavaFX to move 3D cubes around by mouse drag. The cube should stay on the plane spanned by x and z axis. My solution works fairly well, however if I move the cube too fast with my mouse or when it encounters an object with a certain depth (y-Axis), it is assumed, that the mouse is moving on the y-Axis and the cube starts jumping forward or backwards. Is there a way to restrict the mouse to the xz-plane? A more complicated solution would be projecting the y length back to the xz-plane, but I got no clue how. I looked at JavaFX Moving 3D Objects, but couldn't adapt it to my case.

My code so far:

    private volatile double relMousePosX;
    private volatile double relMousePosZ;

    public void enableDragMove(PaneBox paneBox) {
        Group paneBoxGroup = paneBox.get();

        paneBoxGroup.setOnMousePressed((MouseEvent me) -> {
            if(isSelected(paneBox) && MouseButton.PRIMARY.equals(me.getButton())) {
                relMousePosX = me.getX();
                relMousePosZ = me.getZ();
            }
        });

        paneBoxGroup.setOnMouseDragged((MouseEvent me) -> {
            if(paneBoxGroup.focusedProperty().get() && MouseButton.PRIMARY.equals(me.getButton())) {
                setDragInProgress(paneBox, true);
                System.out.println(me.getY()); // should stay small value, but jumps to higher values at times, creating the problem.
                paneBoxGroup.setCursor(Cursor.MOVE);
                paneBox.setTranslateX(paneBox.getTranslateX() + (me.getX() - relMousePosX));
                paneBox.setTranslateZ(paneBox.getTranslateZ() + (me.getZ() - relMousePosZ));
            }
        });

        paneBoxGroup.setOnMouseReleased((MouseEvent me) -> {
            if(paneBoxGroup.focusedProperty().get() && MouseButton.PRIMARY.equals(me.getButton())) {
                setDragInProgress(paneBox, false);
                paneBoxGroup.setCursor(Cursor.DEFAULT);
            }
        });
   }
Urger answered 13/3, 2015 at 13:55 Comment(1)
Thanks to the solution of José Pereda below I was able to implement it. See: github.com/Nurtak/ObjectGraphVisualization/blob/master/src/ch/… and the rest of the repository for context.Urger
Z
5

As you have mentioned, there two possible approachs for 3D dragging:

  • Pure drag & drop, like you propose
  • unprojecting directions, like in this question.

For drag and drop events, we can detect a drag event on the 3D shape, when the event starts and finishes.

The trick here is, instead of listening to the in between dragging events on the shape, listen to drag over events on a possible target.

By defining a target, you can restrict easily the shape movements, since you will only update its position when you are dragging it only over this target.

So if you want to restrict movements on a plane, you can use a Rectangle as the target.

This snippet shows how this approach works:

@Override 
public void start(Stage stage) {
    final PerspectiveCamera cam = new PerspectiveCamera();
    cam.setFieldOfView(20);
    cam.setFarClip(10000);
    cam.setNearClip(0.01);
    cam.getTransforms().addAll(new Rotate(60,Rotate.X_AXIS),new Translate(-200,-200,300));

    final Group root = new Group();

    final Box floor = new Box(500, 500, 1);
    floor.setTranslateX(200);
    floor.setTranslateY(200);
    floor.setTranslateZ(50);
    floor.setMaterial(new PhongMaterial(Color.YELLOW));
    root.getChildren().add(floor);

    final Box box = new Box(50, 50, 50);
    box.setMaterial(new PhongMaterial(Color.RED));
    root.getChildren().add(box);

    final Rectangle rectangle = new Rectangle(400, 400, Color.TRANSPARENT);
    rectangle.setMouseTransparent(true);
    rectangle.setDepthTest(DepthTest.DISABLE);
    root.getChildren().add(rectangle);

    // D&D starts
    box.setOnDragDetected((MouseEvent event)-> {
        box.setMouseTransparent(true);
        rectangle.setMouseTransparent(false);
        box.setCursor(Cursor.MOVE);
        box.startFullDrag();
    });

    // D&D ends
    box.setOnMouseReleased((MouseEvent event)-> {
        box.setMouseTransparent(false);
        rectangle.setMouseTransparent(true);
        box.setCursor(Cursor.DEFAULT);
    });

    // While D&D, only confined to the rectangle
    rectangle.setOnMouseDragOver((MouseDragEvent event)-> {
        Point3D coords = event.getPickResult().getIntersectedPoint();
        coords = rectangle.localToParent(coords);
        box.setTranslateX(coords.getX());
        box.setTranslateY(coords.getY());
        box.setTranslateZ(coords.getZ());
    });

    final Scene scene = new Scene(root, 800, 600, true);
    scene.setCamera(cam);

    stage.setScene(scene);
    stage.setTitle("JavaFX 3D Drag&Drop");
    stage.show();
}

If you run it, you will see that you can pick the box and drag it all over the floor.

In this picture, I've added some color and stroke to the rectangle to see the limits of dragging.

3D drag

Note also the changes in mouse transparent both in box and rectangle. The rectangle is not mouse transparent only during the dragging.

Zettazeugma answered 13/3, 2015 at 15:34 Comment(2)
This approach is near perfect! Only drawback is, that you need to make all Nodes in the scene mouse transparent (like iterating over all children of the subscene's world) and the size of the floor is limited (but could be bound to camera coordinates to follow along). Also subtract event.getX() from coords.getX() etc. so that the Box gets caught by where you click on it and its center doesn't jump to the mouse pointer on first drag.Urger
Well, the snippet has room for improvement, for sure, but you get the idea of how you can define planes at your convenience for allowing movement of 3D objects confined to those spaces.Athodyd

© 2022 - 2024 — McMap. All rights reserved.