Funny moment underground

I have a tool implemented that makes a flyto of the camera to the position where the user clicks.

I think this was already solved in the last version, but the camera goes to the basement if the terrain (Cesium ION sample terrain) is not yet loaded.

or

See gif

ezgif.com-optimize

I have tried a few workarrounds but this is still happening.

I’ve tried the solutions discussed here: https://github.com/CesiumGS/cesium/issues/5837

And also forcing the rendering:

    scene.camera.flyTo({
      destination: point,
      orientation: initialOrientation,
      endTransform: Cesium.Matrix4.IDENTITY,
      complete: function() {
        scene.requestRender();
      }
    });

How can I update the terrain in the flyto or wait to terrain is loaded?Any idea how to solve this?

Cesium version 1.68.0

I solved it using this, but I don’t think it’s the right workarround:

      complete: function() {
        camera.moveUp(100);
        scene.requestRender();
      }

Perhaps the terrain is loaded, but the terrain clicked on is low LOD, and if it stayed at low LOD when the camera got there it would be above the terrain. However when the higher LOD terrain replaced the lower level terrain that point could then be underground. Looking at that GIF it would appear that they clicked somewhere in France or Spain while at ISS orbiting altitude, so very low terrain LOD.

I have a function that sampled a location at very high LOD for terrain height before taking the camera there, I haven’t used it lately though, not sure if it’ll work on newer versions of Cesium.

This is likely what’s happening here. @Fran_Raga you can confirm this by triggering another camera fly to, if that sends you to the right location then that it is indeed the problem.

One way I’ve worked around this is to do a two-pass camera fly. The first one to fly to the general area, to load the terrain, and the second one which runs on the complete of the first to fly to the location again to get to the correct height.

There’s a built-in function now to do that: https://cesium.com/docs/cesiumjs-ref-doc/global.html?classFilter=sampleTer#sampleTerrainMostDetailed

mm…Interesting, I’m already using this function to take out the sampleTerrainMostDetailed terrain.

But it’s a good idea, to do a general flight and then to that location. I think it’s a very good workarround.

I try it and comment on the result

I’m guessing what’s happening there is the camera is trying to go to the right location, but it collides with the ground mid-flight and stops. You could disable camera collision to determine if this is what’s happening: https://cesium.com/docs/cesiumjs-ref-doc/ScreenSpaceCameraController.html?classFilter=scree#enableCollisionDetection

In any case, let us know what approach ends up working for you.

That’s an even better function, the one I was using is just above it on the page you linked to. I was guessing 15 level of LOD, but that may or may not be the highest, perhaps even non existent!

I suppose you don’t need to know what the terrain provider is, just doing
var terrainProvider = Cesium.createWorldTerrain();
will identify it for you I presume? I don’t even know what provider I’m using, the one I’ve used before is

new Cesium.CesiumTerrainProvider({url : ‘//cesiumjs.org/stk-terrain/tilesets/world/tiles’});

It fails (maybe because cesiumjs.org changed to cesium.com) and I think Cesium chooses some unknown url for terrain.

This is my full code

handler.setInputAction(async movement => {
  let position;
  let cartographic;
  let ray = camera.getPickRay(movement.position);
  if (ray) position = scene.globe.pick(ray, map.scene);
  if (position)
    cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);

  //World coordinates to geographic coordinates(radian)
  if (cartographic) {
    //Geographic coordinates(radians) to latitude and longitude coordinates

    let lon = Cesium.Math.toDegrees(cartographic.longitude);
    let lat = Cesium.Math.toDegrees(cartographic.latitude);

    let heading = turf.bearing(
      turf.point([lon, lat]),
      turf.point([midpoint.longitude, midpoint.latitude])
    );
    var positions = [cartographic];

    let res = await Cesium.sampleTerrainMostDetailed(terrainProvider, [
      positions
    ]);
    res[0][0].height += 30;
    let point = Cesium.Cartographic.toCartesian(res[0][0]);

    var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(
      Cesium.Math.toRadians(heading),
      Cesium.Math.toRadians(30),
      0.0
    );

    //change camera view to new location
    camera.flyTo({
      destination: point,
      orientation: initialOrientation,
      endTransform: Cesium.Matrix4.IDENTITY,
      complete: function() {
        camera.moveUp(100);
        scene.requestRender();
      }
    });
    scene.requestRender();
  }

  document.addEventListener(
    "keydown",
    async event => {
      const keyName = event.key;
      let viewer = this.context.viewer;
      if (
        keyName === "ArrowDown" ||
        keyName === "ArrowUp" ||
        keyName === "a" ||
        keyName === "q"
      ) {
        let cartographic = Cesium.Cartographic.fromCartesian(
          viewer.camera.position
        );

        let goBackAngle = keyName === "ArrowDown" ? 180 : 0;
        goBackAngle = keyName === "q" ? 180 : 0;

        let lon = Cesium.Math.toDegrees(cartographic.longitude);
        let lat = Cesium.Math.toDegrees(cartographic.latitude);

        let point = turf.point([lon, lat]);
        let distance = keyName === "ArrowUp" ? 0 : 0.01; //the step distance is 10 meters
        let bearing =
          Cesium.Math.toDegrees(scene.camera.heading) + goBackAngle;
        var options = { units: "kilometers" };
        let destination = turf.getCoord(
          turf.destination(point, distance, bearing, options)
        );

        cartographic = new Cesium.Cartographic(
          Cesium.Math.toRadians(destination[0]),
          Cesium.Math.toRadians(destination[1]),
          0.0
        );

        cartographic.height = scene.globe.getHeight(cartographic);

        let res = await Cesium.sampleTerrainMostDetailed(terrainProvider, [
          cartographic
        ]);

        res[0].height += 30;
        viewer.camera.position = Cesium.Cartographic.toCartesian(res[0]);

        scene.requestRender();
        return false;
      }
    },
    false
  );
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Where terrain is default Cesium terrain

let terrainProvider = new Cesium.CesiumTerrainProvider({
  url: Cesium.IonResource.fromAssetId(1)
});

I was thinking that doing the flight in two parts can solve the initial flight, the problem is that later as you see, the user can move using the keyboard (emulating that he walks on the ground) and there also happens the same, if he moves fast he goes under the ground.

What exactly does this property do?
https://cesium.com/docs/cesiumjs-ref-doc/ScreenSpaceCameraController.html?classFilter=scree#enableCollisionDetection

If I set it to false, I can move under the ground, that’s all

I never knew about await, I suppose if you don’t mind pausing your program that’ll be a good substitute for promise/when.

res[0][0].height ? I always use res[0].height. I don’t see another dimension to that array when I do console.log(res);

If enableCollisionDetection is true then when underground it’ll raise the camera over ground using the
function adjustHeightForTerrain(controller) in ScreenSpaceCameraController.js

However this function is activated only when camera movement is caused by one of Cesium’s movement functions. Since I have my own movement functions, I’ve created my own ‘unburrow’ function for when I don’t want to go underground, where I simply move the camera up however deep it is under the terrain. I now have a burrow variable for when I do want to go underground that bypasses this function.

mmm… Can you share your “unburrow” function?

and about the variable res, it does have another dimension

I just noticed that you’ve placed positions parameter (which is already an array) into an array
await Cesium.sampleTerrainMostDetailed(terrainProvider, [positions]);
does this not work (sans brackets?)
await Cesium.sampleTerrainMostDetailed(terrainProvider, positions);
where you just need to do res[0].height?

//determine camera height 
var cp = viewer.scene.camera.position;
camera_height = viewer.scene.globe.ellipsoid.cartesianToCartographic(cp).height;

//determine terrain_height (already did that)

//determine terrain relative height (edist)
var edist = camera_height-terrain_height;

//my unburrow code (modified to exclude stuff specific to my own program)
if(edist<0) //unbury
{
	var GD_transform = Cesium.Transforms.eastNorthUpToFixedFrame(cp, viewer.scene.globe.ellipsoid, new Cesium.Matrix4());
	var GD_ENU_U = new Cesium.Cartesian3(GD_transform[8],GD_transform[9],GD_transform[10]);
	var temp = new Cesium.Cartesian3();
	Cesium.Cartesian3.multiplyByScalar(GD_ENU_U, (terrain_height - camera_height), temp);
	viewer.scene.camera.position.x+=temp.x;
	viewer.scene.camera.position.y+=temp.y;
	viewer.scene.camera.position.z+=temp.z;
}

Hmm, code block windows in this discord forum are awfully narrow, perhaps for the sake of smarthphone displays. I suppose scrolling is better than word-wrap which can really make the formatting messy.

It doesn’t solve the problem in my case, but I think it’s a better way to find the solution.

my function:

  /**
   * Prevent go to underground
   * @param {*} terrain_height
   */
  unburrow = terrain_height => {
    let map = this.context.viewer;
    let cp = map.scene.camera.position;
    let camera_height = map.scene.globe.ellipsoid.cartesianToCartographic(cp)
      .height;

    //Determine terrain relative height (edist)
    let edist = camera_height - terrain_height;

    //Unburrow code
    if (edist < 0) {
      let GD_transform = Cesium.Transforms.eastNorthUpToFixedFrame(
        cp,
        map.scene.globe.ellipsoid,
        new Cesium.Matrix4()
      );
      let GD_ENU_U = new Cesium.Cartesian3(
        GD_transform[8],
        GD_transform[9],
        GD_transform[10]
      );
      let temp = new Cesium.Cartesian3();
      Cesium.Cartesian3.multiplyByScalar(
        GD_ENU_U,
        terrain_height - camera_height,
        temp
      );
      map.scene.requestRender();
      map.scene.camera.position.x += temp.x;
      map.scene.camera.position.y += temp.y;
      map.scene.camera.position.z += temp.z + 1;
      map.scene.requestRender();
    }
  };

Thank you very much.

You’re welcome. Just one thing, I noticed that you put a +1 onto temp.z. That would work fine on the north pole, but on the south pole it would put you into the ground. On the equator it would move you north since the coordinate system is worldwide not local. In the multiplyByScalar you could do this instead “terrain_height - camera_height + 1” which would rise you 1 meter from the ground in the local up direction.

Also I should have just used the add function instead of doing components
Cesium.Cartesian3.add (map.scene.camera.position, temp, map.scene.camera.position);
which adds cp and temp and puts the result into the 3rd parameter.

I think Cesium also provides a more direct path for getting the local up vector, not sure what it is though.

Great, I’ll try your suggestion.

Thank you very much again.