CesiumGS / cesium

An open-source JavaScript library for world-class 3D globes and maps :earth_americas:
https://cesium.com/cesiumjs/
Apache License 2.0
12.97k stars 3.5k forks source link

Camera produces unexpected transition at negative heights #8610

Open OmarShehata opened 4 years ago

OmarShehata commented 4 years ago

Here's a Sandcastle showing this issue around the Dead Sea, where the terrain has negative height. Try clicking "fly to position 2". Notice the camera will fly high up in the air, rotate slightly, then fly back down.

The expect transition here is for the camera to just rotate slightly and not do this extraneous transition.

This only seems to happen when doing camera flights at negative heights.

OmarShehata commented 4 years ago

This now becomes more common in underground scenes, and almost makes camera fly's unusable here. For example there's no way to smoothly go from one location in a tunnel to another since the camera will fly up above ground first, so the viewer loses context of where they were. CC @lilleyse

baothientran commented 4 years ago

@OmarShehata @lilleyse I'm currently working on the issue, but I think there is a workaround for it in the meantime.

To go from one location to the other, the camera is currently using two different math functions to interpolate the height between two of them during the flight. One is to linearly interpolate the start height and the destination height based on time. It will result in the flight path that is curved along the earth surface if the two points are far away, and a more straight path if they are close together. I think this one is the expected behavior in this case:

Screen Shot 2020-07-02 at 11 08 10 AM

The camera height is also interpolated based on an upside down parabolic function (Notice the upside down of the red curve of the link. The two lines that are parallel with the x-axis represent start height and end height)(https://www.desmos.com/calculator/ttzhuznwqp). This one is what currently happening:

Screen Shot 2020-07-02 at 11 14 26 AM

I think the function Camera.flyTo() has the option maximumHeight that is used to determine which method to be used. It is the maximum height the camera can go up before going back down to the destination if the parabolic function is used. If the option is not provided, the maximumHeight will be generated. The rule is if max(height(start), height(destination)) < maximumHeight (or altitude), the parabolic interpolation will be used; otherwise, it will use the linear interpolation. I think we can take advantage of that to set maximumHeight = min(height(start), height(destination)), so that only linear interpolation can be used in the underground. The downside of the workaround is that the camera will fly very close to the earth surface if the camera happens to be very far away from the destination. I modified the Sandcastle's example of the workaround

I'm still working on how to auto-generate the maximumHeight correctly. The function getAltitude() only generates a positive value currently. I'm not quite certain about the math behind it. If you have any info about it, it would be great.

I think the behavior of the camera should be that:

Please let me know if you have any information on the getAltitude() function above

lilleyse commented 4 years ago

@baothientran great notes. But I wonder if it can be simpler? In createHeightFunction if maxHeight is less than 0 just always use linear interpolation? Are there downsides to that? Maybe if both start and end points have a height less than 0 and are very far away from each other?

Let me know if you figure getAltitude() out, I wish there was a comment there.

baothientran commented 4 years ago

@lilleyse yup the case where both start and end points have a height less than 0 and are very far away is the edge case that I have a trouble with. Sometimes, those points are above the ground, very similar to the first example above.

Unfortunately, I haven't figured out the math behind getAltitude(). I will take a little bit more time this week to think about a better solution

dennisadams commented 4 years ago

The math behind getAltitude() is similar to the math in PerspectiveOffCenterFrustum.getPixelDimensions and in metersPerPixel.glsl. I think it calculates the camera's distance needed so that the visible area on screen has at least 2.0*dx width and 2.0*dy height. I also found this old comment in the commit where getAltitude() was introduced: // get minimum altitude from which the whole ellipsoid is visible The comment is about something a bit different (maximal possible required altitude) but was a useful hint. So my understanding is that getAltitude() is calculating the necessary altitude in the starting position so that the destination position will appear on screen. It seems like the calculation is based on the underlying assumption of a top-down view, because the calculated distance is (roughly) equivalent to the altitude only in a top-down view. Given that the result of getAltitude() is then mutiplied by 0.2, I assume the whole thing is just meant to find a meaningful way of calculating a height proportional to the flight, and minor inaccuracies are purposely neglected.

I'm not completely sure about this explanation, but thought it may help.