Cesium: Camera stuck after limiting zoom

Created on 1 Jun 2016  ·  11Comments  ·  Source: CesiumGS/cesium

To reproduce

  1. Paste into sandcastle:

var viewer = new Cesium.Viewer('cesiumContainer');
viewer.scene.screenSpaceCameraController.minimumZoomDistance=2000000;

  1. Zoom in
  2. Once the camera stops zooming in, keep doing the zoom-in input (such as mouse wheel scroll) for a while.
  3. Try zooming out. Camera will be stuck for a while and will only move after a few moments of zoom-out input.
category - camera priority - high type - bug

Most helpful comment

I found a much better solution: simply setting scene.screenSpaceCameraController._minimumZoomRate to s.th. higher than the default value (20).

scene.screenSpaceCameraController.minimumZoomDistance = 250;
scene.screenSpaceCameraController.maximumZoomDistance = 20000000;
scene.screenSpaceCameraController._minimumZoomRate = 300; // ←

All 11 comments

Thanks @nikakhov! I can confirm that it does take a little bit of scrolling out to start zooming.

Also reported here: https://groups.google.com/forum/?hl=en#!topic/cesium-dev/7KZ9feIh1zE

@hpinkos, @pjcozzi
do you already have a hunch what the problem is, or what a possible fix might be? – maybe I can have a stab at it.

I'm not sure @freder. @bagnell, do you have any ideas here?

The first thing to understand is how zoom works: It's not linear. You don't move a fixed number of meters per mouse travel, because movements sizes only make sense when they're near objects of similar sizes.

So if you have Cesium zoomed into a compact car on the street, as you zoom out your zoom speed is about a meter per pixel of mouse travel, then when you see a whole city block the speed is several meters/pixel, then as the whole city comes into view it's hundreds of meters/pixel, then as nearby mountain ranges come into view it's kilometers/pixel, and then the limb of the Earth comes into view, and then the whole Earth is onscreen and you're moving hundreds or thousands of km/pixel, and then the Moon comes into view, and faster you go.

So when you set minimumZoomDistance=2000000, it's as if that city street with that small car parked on it was there at 2000 km altitude. As you approach that distance, zoom doesn't "stop", it slows down. If there were a physical object there for you to see, like a space station, you would see that although the Earth appears to stop moving in the background, the space station is getting closer and larger. In fact we can put the International Space Station model in space (at a much lower altitude of course) and there's an awkward "gap" in the perceived zoom speed, since there are no mountains or large cities near the space station which is only a couple city blocks wide, you can't "see" the zoom, from when the Earth in the background no longer has perceptible motion but the ISS is still contained within a single pixel. Eventually if you keep zooming, the ISS will get larger than a pixel and zoom will feel natural again. You can zoom in and out of the ISS, but the Earth won't budge, it's too big and too far away, like a mountain on the horizon.

But back to your case, you set a minimum zoom in space, but didn't put any object at that altitude of space. So, as you zoom into that altitude, the zoom slows down, and the motion of the Earth in the background becomes imperceptible. You can eventually zoom in to where the camera is only moving one meter per pixel, but with the Earth 2,000,000 meters away, you don't know that you're moving, there's no nearby object of the correct scale to show you that you're moving.

Of course, as you zoom out, the zoom speed slowly ramps up, just as it does when you zoom out from the ground. But unlike the ground, you can't see the ramp-up speed in empty space, so you feel stuck!

One possible fix here is that minimumZoomDistance should not move the zoom's "origin" closer, instead it should instead just put a hard-stop at that distance from the origin. Imagine if the zoom speed was still relative to the ground, not to 2000km, but there was a hard stop in the ramp at 2000km, like bumping into a wall. I think that would probably feel a lot more natural to users.

@emackey sounds like a good idea. If it is easy to test out, please go for it if you have the bandwidth.

here's my current workaround:

const minZoom = 250; // m
const maxZoom = 20000000;

// be notified of ALL camera change events
camera.percentageChanged = 0;

// make global camera event listener:
const listener = () => {
    const camHeight = camera.positionCartographic.height;

    let isOutsideZoomLimits = false;
    let destHeight;
    if (camHeight < minZoom) {
        isOutsideZoomLimits = true;
        destHeight = minZoom;
    } else if (camHeight > maxZoom) {
        isOutsideZoomLimits = true;
        destHeight = maxZoom;
    }

    if (isOutsideZoomLimits) {
        const dest = Cesium.Cartesian3.fromRadians(
            camera.positionCartographic.longitude,
            camera.positionCartographic.latitude,
            destHeight
        );
        camera.position = dest;
        // removeListener();
        // camera.flyTo({
        //  destination: dest,
        //  duration: 0.2, // seconds
        //  complete: () => camera.changed.addEventListener(listener)
        // });
    }
};

const removeListener = camera.changed.addEventListener(listener);

the workaround (above) is not optimal though, since the camera keeps on moving parallel to the surface, when the height limit hits. I tried to counteract that by using the previous lat/lng coordinates of the camera:

let lastCamPos = camera.positionCartographic.clone();
const listener = () => {
    const camHeight = camera.positionCartographic.height;
    let isOutsideZoomLimits = false;
    let destHeight;
    if (camHeight < constants.minZoom) {
        isOutsideZoomLimits = true;
        destHeight = constants.minZoom;
    } else if (camHeight > constants.maxZoom) {
        isOutsideZoomLimits = true;
        destHeight = constants.maxZoom;
    }
    if (isOutsideZoomLimits) {
        const dest = Cesium.Cartesian3.fromRadians(
            lastCamPos.longitude, // ← previous coordinates
            lastCamPos.latitude,
            destHeight
        );
        camera.position = dest;
    }
    lastCamPos = camera.positionCartographic.clone();
};

while that seems to work ok enough for the minZoom limit, it definitely does not for maxZoom.

I found a much better solution: simply setting scene.screenSpaceCameraController._minimumZoomRate to s.th. higher than the default value (20).

scene.screenSpaceCameraController.minimumZoomDistance = 250;
scene.screenSpaceCameraController.maximumZoomDistance = 20000000;
scene.screenSpaceCameraController._minimumZoomRate = 300; // ←

m

Please, expose _minimumZoomRate as public (probably the rest of those camera options).

Was this page helpful?
0 / 5 - 0 ratings