diff --git a/changelog.d/1688-transit-terrain-height-cache.md b/changelog.d/1688-transit-terrain-height-cache.md index 27cef4638..2afecb0ae 100644 --- a/changelog.d/1688-transit-terrain-height-cache.md +++ b/changelog.d/1688-transit-terrain-height-cache.md @@ -2,5 +2,6 @@ globe, most visibly while zooming over a large fleet. Every reread asked the terrain for the height of every trail fix of every vehicle in view; heights are now kept per point, read again only while the camera moves or tiles - load, and read within a time budget that puts vehicle positions first - (mazy1998, #1688). + load, and read within a time budget that puts vehicle positions first. + Switching the terrain provider or globe starts the kept heights afresh + (mazy1998, review by kvnloo, #1688). diff --git a/src/layers/transit/height.js b/src/layers/transit/height.js index 6a7c7d8f4..3bc379f48 100644 --- a/src/layers/transit/height.js +++ b/src/layers/transit/height.js @@ -78,6 +78,9 @@ export function createHeight({ state, services, parts }) { cameraLat = NaN, cameraHeight = NaN; const terrainStats = { reads: 0, reused: 0, deferred: 0, borrowed: 0 }; + /** The globe and terrain provider the kept heights were read from. */ + let terrainGlobeRead = null, + terrainProviderRead = null; /** A number unique to a point on a `scale`-per-degree grid. */ function gridKey(lat, lon, scale) { @@ -219,11 +222,16 @@ export function createHeight({ state, services, parts }) { // On a terrain globe, use the loaded surface under the marker. This reads // tiles already loaded and does not launch a terrain request. const globe = state._viewer?.scene?.globe; + const provider = state._viewer?.terrainProvider; + // A height belongs to the terrain it was read from. Checked before the + // flat-globe answer, so terrain → flat → terrain starts afresh too. + if (globe !== terrainGlobeRead || provider !== terrainProviderRead) { + clearTerrainHeights(); + terrainGlobeRead = globe; + terrainProviderRead = provider; + } if (globe?.show === true) { - if ( - state._viewer?.terrainProvider instanceof - Cesium.EllipsoidTerrainProvider - ) + if (provider instanceof Cesium.EllipsoidTerrainProvider) return { cell, height: GROUND_FLOOR_LIFT_M, prior: null }; const rendered = globe.getHeight ? terrainHeight(globe, lat, lon, need) diff --git a/src/layers/transit/height.test.mjs b/src/layers/transit/height.test.mjs index c9bff2ef0..c4139e5a9 100644 --- a/src/layers/transit/height.test.mjs +++ b/src/layers/transit/height.test.mjs @@ -262,6 +262,40 @@ test('once the camera and the tiles have settled a height is kept until either c assert.equal(reads.length, 5); }); +test('another terrain provider or globe is read afresh, however settled the old heights were', (t) => { + const { height, state, globe, world, clock, reads } = terrain(t); + const viewer = state._viewer; + const at = () => height.requestHeight(42.36, -71.06).height; + // Read once the camera and tiles have long settled: kept indefinitely. + height.requestHeight(10, 10); + clock.now += TERRAIN_SETTLE_MS * 2; + reads.length = 0; + assert.equal(at(), 100 + LIFT); + clock.now += TERRAIN_SETTLE_MS * 10; + world.surface = () => 250; + assert.equal(at(), 100 + LIFT, 'same terrain: the kept height stands'); + assert.equal(reads.length, 1); + + viewer.terrainProvider = {}; + assert.equal(at(), 250 + LIFT, 'a new provider is a new surface'); + assert.equal(reads.length, 2); + + world.surface = () => 400; + viewer.scene.globe = { show: true, getHeight: globe.getHeight }; + assert.equal(at(), 400 + LIFT, 'and so is a new globe'); + assert.equal(reads.length, 3); + + // Terrain → flat → the same terrain provider: nothing asks the terrain + // while the globe is flat, yet the old heights must not come back. + const real = viewer.terrainProvider; + viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider(); + assert.equal(at(), LIFT); + world.surface = () => 30; + viewer.terrainProvider = real; + assert.equal(at(), 30 + LIFT); + assert.equal(reads.length, 4); +}); + test('a flat globe answers without reading terrain', (t) => { const { height, reads } = terrain(t, { provider: new Cesium.EllipsoidTerrainProvider(),