Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bounding Camera to Content Javafx

I am trying to set restrictions on the movement of a camera in JavaFX such that when it moves, it does not allow the user to move in such a way that only the content of the subscene is visible. Currently, my movement code looks as follows and has no checks to prevent this, I have tried limiting the movement of the camera by checking its coordinates and approximating if it will or will not show the content of the subscene, but that has problems in that it is purely an approximation and when zooming. TLDR, the problem involves 1 detecting when the camera moves away from the content of it, and 2 preventing a transformation from occurring if it will result in the camera moving away from the content.

mapView.addEventFilter(MouseEvent.MOUSE_PRESSED, e->{
    startX = e.getX();
    startY = e.getY();
});
mapView.addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> {
    camera.setTranslateX(camera.getTranslateX() + (startX - e.getX()));
    camera.setTranslateY(camera.getTranslateY() + (startY - e.getY()));
});

mapView is a MeshView if that is relevant.

If you would like me to clarify anything or need further information I will provide it. Thanks for the help and good day.

like image 484
Politic Revolutionnaire Avatar asked Aug 17 '17 09:08

Politic Revolutionnaire


Video Answer


1 Answers

The camera has a viewport that you can imagine as a movable overlay above the contents (with some background being displayed in areas where no contents are placed). For the sake of simplicity, I would separate scrolling (i.e. moving the viewport) from content transformations (e.g. zooming).

enter image description here

Based on this mental model, you can define the scrollable bounds to be the bounds of your contents as well as a possibly empty portion of the current viewport (e.g. in case of contents smaller than viewport). The scrollable bounds needs to be recomputed after every scroll operation (increasing/reducing empty space within the current viewport) or content manipulation (transformations and bounds changes). If you restrict scrolling to the scrollable bounds, then you can ensure that empty space within the viewport is never increased by a scroll operation.

You can create an ObjectBinding scrollableBounds that is bound to the contents' bounds-in-local and local-to-parent-transform properties, as well the viewport-bounds. Then you can create a scollableBoundsProperty that is bound to the binding. That property can be accessed when scrolling to restrict the translation before applying it, thus preventing an increase of empty space within the viewport.

ObjectBinding<Bounds> scrollableBoundsBinding = new ObjectBinding<>() {
    {
        // TODO: bind to dependencies: viewport bounds and content bounds
        // TODO: (transformed to the same coordinate system)
        bind(camera.boundsInParentProperty(),
             contentPane.boundsInLocalProperty(),
             contentPane.localToParentTransformProperty());
    }
    @Override protected Bounds computeValue() {
        // TODO: compute union of viewport and content bounds
        return unionBounds(viewportBounds, contentBounds);
    }
};
ObjectProperty<Bounds> scrollableBoundsProperty = new SimpleObjectProperty<>(
    scrollableBoundsBinding);
// ...
// on mouse drag:
// dx, dy: relative mouse movement
// tx, ty: current scrolling
// mintx, maxtx, minty, maxty: translation range
// (taken from scrollable bounds and viewport size)
if (dx < 0) { tx = max(mintx, tx + dx); }
else        { tx = min(maxtx, tx + dx); }
if (dy < 0) { ty = max(minty, ty + dy); }
else        { ty = min(maxty, ty + dy); }

You might want to further restrict scrolling when the contents fully fit within the viewport, e.g. by placing the contents at the top left corner. You could also restrict the minimal zoom level in that case so that the contents are displayed as big as possible.

Note on usability: As already pointed out by another answer, you might want to consider allowing to drag over the contents by a bit, possibly with decreasing efficiency the further away one tries to scroll from the contents, comparable to the behavior of scrolling via touchpad in Safari. Then, when the interaction finishes, you could transition back instead of snapping in order to restrict the viewport to the contents again.

like image 91
Matthias Avatar answered Sep 21 '22 15:09

Matthias