Bounding Camera to Content Javafx
Asked Answered
C

2

6

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.

Cathcart answered 17/8, 2017 at 9:45 Comment(2)
What are the possible transformations that you are allowing on the content?Reason
@JoseMartinez I am allowing most forms of transformations, but I am only looking for constraints to apply while moving along the X and Y axis with the camera while no other transformations other than Z axis transformations are appliedCathcart
D
1

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.

Dispensatory answered 26/8, 2017 at 14:10 Comment(2)
What is the method 'unionBounds' referenced in the example in the @Override method?Cathcart
It is a method that you have to implement, that's why I put some TODOs there. You should also validate, which dependencies you really need to compute scrollable bounds.Dispensatory
A
0

that's pretty common: just move and after you moved check if you're out of bounds... in that case go back into scene... this usually feels natural as when you try to pan an image on your phone.. it doesn't just block: it appears as it's making resistance and when you end your gesture it goes back... that's the simplest thing to do

Asco answered 24/8, 2017 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.