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

Fix sampleTerrain when using ArcGIS Terrain #9286

Merged

Conversation

DanielLeone
Copy link
Contributor

Hi all,

I've got a WIP fix an issue and I'm keen for some feedback/suggestions.
This PR fixes #8007

There's 2 bugs as mentioned in the issue.

  1. The availability property was not exposed from the ArcGISTiledElevationTerrainProvider.

    • I've fixed that in the same way the @OmarShehata suggested - buy exposing _tilesAvailable
  2. calling interpolateHeight on a HeightmapTerrainData instance from ArcGISTiledElevationTerrainProvider will return undefined.

    • The reason this is happening is because:
      1. the data returned from an ArcGIS server is encoded in LERC format
      2. by default the interpolateHeight method of HeightmapTerrainData will try to use that array buffer (returned from the server) directly like a 2D height map array lookup thing.
      3. this just does not work for the buffer since it's encoded in a different format
    • However we can avoid this code path by just calling createMesh() before calling interpolateHeight
      • We can see the first line of createVerticiesFromHeightmap (which is what createMesh calls) is where the LERC decoding happens.
        image
      • This then forces us to use the mesh for interpolateHeight rather than the LERC buffer. This code path works as expected.
        image

I've opted for this solution (just calling createMesh on every tile) inside sampleTerrain as it seemed to be the lowest friction change to me; but I don't think it's an optimal solution.

Another maybe better idea:

  • Make the ArcGIS Terrain Provider return a different HeightmapTerrainData class which already has it's mesh created beforehand; and then to meet the TerrainData interface createMesh would just be a no-op; and upsample seems to use the mesh anyway to build a new height map buffer. So MeshedHeightmapTerrainData.upsample -> HeightmapTerrainData or something.

Current issue with this solution:

  • all the sampleTerrain unit tests are failing and I'm not sure why. It appears when using Cesium World Terrain (as in the unit tests); interpolateHeight via the mesh code path isn't working for some reason 😢 . Any ideas on this one? I couldn't find any unit tests covering QuantizedMeshTerrainData.interpolateHeight when using the mesh code; nor do I see where it's ever really run in production (until now in this PR)... any help here is appreciated.

A less-than-ideal solution to this new issue would be

  • Only call createMesh for ArcGIS tiles.

Other changes:

  • Fixed the doc for TerrainData.createMesh (it was missing the terrain exaggeration param)
  • Added the IntelliJ shelf to the git ignore list
  • Updated the Terrain sandcastle so you can left and right click for sampleTerrain and sampleTerrainMostDetailed respectively and a few other changes - this is just temporary while testing though.

Any help is much appreciated; thanks!

* fixed the doc for `TerrainData.createMesh` (missing param)
* exposed `_tileAvailability` on ArcGISTiledElevationTerrainProvider as the `availability`; this fixes sampleTerrainMostDetailed which requires that property.
* made `sampleTerrain` call `createMesh` on every tile requested; this fixes ArcGIS terrain which currently requires the mesh to be built
  before interpolating height (because the request buffer is still encoded in LERC and the decoding happens during mesh building)
* increased the active task limit for now as it was getting in the way
@cesium-concierge
Copy link

Thank you so much for the pull request @DanielLeone! I noticed this is your first pull request and I wanted to say welcome to the Cesium community!

The Pull Request Guidelines is a handy reference for making sure your PR gets accepted quickly, so make sure to skim that.

  • ❌ Missing CONTRIBUTORS.md entry.
  • ✔️ Signed CLA found.
  • CHANGES.md was not updated.
    • If this change updates the public API in any way, please add a bullet point to CHANGES.md.
  • ❔ Unit tests were not updated.
    • Make sure you've updated tests to reflect your changes, added tests for any new code, and ran the code coverage tool.

Reviewers, don't forget to make sure that:

  • Cesium Viewer works.
  • Works in 2D/CV.
  • Works (or fails gracefully) in IE11.
@@ -650,6 +657,7 @@ function requestAvailability(that, level, x, y) {

// Mark whole area as having availability loaded
that._tilesAvailablityLoaded.addAvailableTileRange(
level,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just saw this - it looked like a missing parameter - I've got no tests or further context on this one! 😛 Will revert later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😮

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes me question _tilesAvailablityLoaded even more...

// Make sure to generate our mesh for each tile first
// because some tiles actually require the mesh to interpolate heights
// (eg: ArcGISTiledElevationTerrainProvider)
.then(createMeshCreatorFunction(tileRequest))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially the fix I've added. Call createMesh on each tile as it comes back. However; as mentioned, this causes CWT terrain interpolateHeight to return undefined - I haven't figured out why yet :)

@@ -182,7 +182,7 @@ Object.defineProperties(HeightmapTerrainData.prototype, {
},
});

var taskProcessor = new TaskProcessor("createVerticesFromHeightmap");
var taskProcessor = new TaskProcessor("createVerticesFromHeightmap", 5000);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh this previous limit of 5 was interfering with tests or the sand castle or something... I can't really remember 😛 I guess because every tile now has to go across the worker (during createMesh), there's a lot more traffic, and therefore chances for that limit of 5 to be reached pretty quick. Will revert / figure it out later.

);
}
//>>includeEnd('debug');
return this._tilesAvailable;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the other fix mentioned... I'm not really across how the TileAvailability class works and if this _tilesAvailable property is setup correctly. I did notice there's actually two properties _tilesAvailablityLoaded and _tilesAvailable in the ArcGIS Terrain Provider; not sure which one is correct in this case.

Either way, I think this makes sampleTerrainMostDetailed work as expected 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this seems like the right thing to return. I'm still wrapping my head around _tilesAvailablityLoaded. I think it only says whether availability has been loaded for tiles, not whether a tile is actually available. Not sure why _tilesAvailable can't do take care of this, but there's probably a good reason.

@DanielLeone
Copy link
Contributor Author

@IanLilleyT You might be interested in this one as #8481 is next on the chopping block 😄

…teMesh

* added some unit tests to sample terrain to test both code paths (ArcGIS and CWT)
* added a bunch of test-data files so it doesn't have to hit a real terrain sever
* maybe fixed eslint
@IanLilleyT
Copy link
Contributor

IanLilleyT commented Dec 18, 2020

@DanielLeone

I looked over the code and the PR write-up helped get me up to speed quickly. I came to a pretty similar conclusion to you, and here's my summary (just to make sure I have all the details right): interpolateHeights is supposed to work for a HeightmapTerrainData even if its triangle mesh hasn't been created yet, and it does this by accessing heights directly from the buffer that came from the server. But in this case the buffer from ArcGisTiledElevationTerrainProvider is LERC-encoded so it doesn't work until it has been decoded. And the current workaround is to call the createMesh function from inside sampleTerrain (which triggers the LERC-decode) in order to turn the heightmap into a mesh so it can be interpolated. I agree it's not the optimal solution because it adds some messiness to sampleTerrain.

Your idea of creating a new MeshedHeightmapTerrainData that forces the heightmap to be a mesh from the start feels like the right direction. But I think instead of creating a new TerrainData type, it could make sense to call the LERC-decode from inside ArcGisTiledElevationTerrainProvider's requestTileGeometry and then create a regular HeightmapTerrainData from the decoded buffer. The decode call would probably need to go in a worker otherwise the main thread will be stalled with all these LERC decodes.

I'm tempted to remove custom encodings from HeightmapTerrainData and createVerticesFromHeightmap entirely, but there could be custom TerrainProviders out there that use them, so it would be a breaking change. But the fact that TerrainData functions are supposed to work pre and post createMesh makes me think there is a problem with the HeightmapTerrainData API allowing these encodings.

@IanLilleyT
Copy link
Contributor

I was thinking about this a little more and started to change my mind from my previous comment. If a TerrainData is supposed to be a black box that converts arbitrary terrain formats to a TerrainMesh, then starting out in LERC format and decoding inside HeightmapTerrainData makes sense. So my earlier idea of moving the LERC decode into ArcGISTiledElevationTerrainProvider is probably not the right move.

So I'm liking the original approach of calling createMesh from sampleTerrain. But instead of having a special check for ArcGISTiledElevationTerrainProvider in sampleTerrain, what if we make interpolateHeight return undefined when the TerrainData is not ready to be interpolated. Then sampleTerrain would call createMesh followed by another interpolateHeight. And the nice part is it's not much code change from the original PR.

…is the mesh is not created and the encoding is LERC.

* sampleTerrain will now call createMesh if interpolateHeight first returns undefined.
@DanielLeone
Copy link
Contributor Author

@IanLilleyT Sounds good to me. Code has been updated! 👍 Let me know and I'll revert some of those other temporary changes.

… it returns a promise;

working around the fact you can only schedule 5 meshes to be created at once.
@DanielLeone
Copy link
Contributor Author

Okay, one more change. I reverted the 5000 limit on the task processor - so we're back to 5 as in master.
This broke sampling if more than 5 meshes needed to be sampled.
Fixed by putting in a tasty recursive promise ⚡

@IanLilleyT
Copy link
Contributor

Nice

My two issues right now are:

  1. I don't feel so good about the sleep. It's not really something the rest of the codebase does. We might be able to add a new option to createMesh like noThrottle to avoid needing an infinite loop in sampleTerrain. I hope to work on a PR for something like that today.
  2. The new test data is too big. Is it possible to make it a lot smaller?

Anyway, feel free to start removing any of the temp code. Once my PR is merged we can do code review. Thanks!

@DanielLeone
Copy link
Contributor Author

Okay no worries. Please let me know when the noThrottle option is in and I'll update all these things together. 👍

@IanLilleyT
Copy link
Contributor

@DanielLeone, I opened #9313. Once you have a chance to merge with that branch please set this PR's base branch to terrainDataThrottleControl.

Let me know if you have any questions!

…to fix-arcgis-sample-terrain-height

# Conflicts:
#	Source/Core/TerrainData.js
* shuffled and reduced the size of the test data
* bulked out the test assertions a little
@DanielLeone DanielLeone changed the base branch from master to terrainDataThrottleControl January 12, 2021 11:34
@DanielLeone
Copy link
Contributor Author

@IanLilleyT Done!

I got it down to 180KB of test data; I hope that's okay :)
90% of that size is the terrain tiles themselves - I've put in 1 CWT tile and 1 ArcGIS terrain tile.
I'd like to use these same tiles as part of the tests for my next PR.
Let me know if there's anything else :) 🌮

Copy link
Contributor

@IanLilleyT IanLilleyT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DanielLeone Overall this looks really good. In the end not a lot of code changed which I love to see! I tried it out with the Terrain.html you had in a previous commit and everything is working as expected. There's several little comments, mostly for the specs which need to be ES5 instead of ES6.

Also, please add an entry to the Fixes section for the Feburary release in CHANGES.md (the Fixes section doesn't exist yet but it will be underneath the Additions section. I made a mistake with the order in terrainDataThrottleControl branch so you'll have to merge first).

Source/Core/sampleTerrain.js Outdated Show resolved Hide resolved
Source/Core/sampleTerrain.js Outdated Show resolved Hide resolved
Source/Core/sampleTerrain.js Outdated Show resolved Hide resolved
Source/Core/sampleTerrain.js Outdated Show resolved Hide resolved
Specs/Core/sampleTerrainSpec.js Outdated Show resolved Hide resolved
Specs/Core/sampleTerrainSpec.js Outdated Show resolved Hide resolved
Specs/Core/sampleTerrainSpec.js Outdated Show resolved Hide resolved
Specs/Core/sampleTerrainSpec.js Outdated Show resolved Hide resolved
Specs/Core/sampleTerrainSpec.js Outdated Show resolved Hide resolved
Specs/Data/Terrain/assets_cesium_com/layer.json Outdated Show resolved Hide resolved
* moved the test data files around
* added a line to CHANGES.md
@DanielLeone
Copy link
Contributor Author

@IanLilleyT thanks for all the suggestions 👍 I think I got them all 🌮

sourceUrl.pathname + sourceUrl.search + sourceUrl.hash;
// find a key (source path) path in the spec which matches (ends with) the requested url
var availablePaths = Object.keys(proxySpec);
var proxiedUrl = null;
Copy link
Contributor

@IanLilleyT IanLilleyT Jan 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do var proxiedUrl; and down below if (!defined(proxiedUrl)) { (will need to include defined.js). That's the more standard way of handling null/undefined in the repo.

@IanLilleyT
Copy link
Contributor

@DanielLeone I posted one last code request

If all goes well #9313 and this will be in the February release!

@DanielLeone
Copy link
Contributor Author

@IanLilleyT Thanks for writing the throttling code and the quick PR reviews! 👍 🌮

@IanLilleyT
Copy link
Contributor

Unfortunately didn't get #9313 reviewed in time for the February release, so it will be out in the March release.

@lilleyse lilleyse closed this Feb 28, 2021
@lilleyse lilleyse deleted the branch CesiumGS:master February 28, 2021 19:55
@IanLilleyT IanLilleyT reopened this Feb 28, 2021
@IanLilleyT IanLilleyT changed the base branch from terrainDataThrottleControl to master February 28, 2021 19:59
@IanLilleyT IanLilleyT merged commit 368d9a4 into CesiumGS:master Feb 28, 2021
@IanLilleyT
Copy link
Contributor

Merged at last! Thanks @DanielLeone!

@IanLilleyT
Copy link
Contributor

Now time to peek at #9389

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
4 participants