Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
            }
        });
   }
like image 540
Terran Avatar asked Mar 13 '15 13:03

Terran


1 Answers

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.

like image 75
José Pereda Avatar answered Oct 18 '22 03:10

José Pereda