Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Camera stuck after limiting zoom #3984

Closed
nikakhov opened this issue Jun 1, 2016 · 12 comments
Closed

Camera stuck after limiting zoom #3984

nikakhov opened this issue Jun 1, 2016 · 12 comments

Comments

@nikakhov
Copy link

nikakhov commented Jun 1, 2016

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.
@hpinkos
Copy link
Contributor

hpinkos commented Jun 3, 2016

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

@hpinkos
Copy link
Contributor

hpinkos commented Jan 26, 2017

@freder
Copy link

freder commented Mar 13, 2017

@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.

@hpinkos
Copy link
Contributor

hpinkos commented Mar 13, 2017

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

@emackey
Copy link
Contributor

emackey commented Mar 14, 2017

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.

@pjcozzi
Copy link
Contributor

pjcozzi commented Mar 18, 2017

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

@freder
Copy link

freder commented Mar 20, 2017

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);

@freder
Copy link

freder commented Mar 21, 2017

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.

@freder
Copy link

freder commented Apr 5, 2017

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; // ←

@Maplesog
Copy link

Maplesog commented Sep 7, 2017

m

@larvanitis
Copy link

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

@ggetz
Copy link
Contributor

ggetz commented Feb 7, 2022

I believe this has been fixed in #9932

@ggetz ggetz closed this as completed Feb 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants