From ffbc723134b2eee24f6b425de3bd36b145441eb0 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 8 Oct 2018 17:09:23 -0400 Subject: [PATCH 01/23] Initial cut of using the bvh terrain extension as the replacement for the layer.json available member. --- Source/Core/CesiumTerrainProvider.js | 93 ++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 3124e9b17d05..e58aaad19ab8 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -53,9 +53,12 @@ define([ this.version = layer.version; this.isHeightmap = layer.isHeightmap; this.tileUrlTemplates = layer.tileUrlTemplates; + this.hasAvailability = layer.hasAvailability; this.availability = layer.availability; this.hasVertexNormals = layer.hasVertexNormals; this.hasWaterMask = layer.hasWaterMask; + this.hasBvh = layer.hasBvh; + this.bvhLevels = layer.bvhLevels; this.littleEndianExtensionSize = layer.littleEndianExtensionSize; } @@ -145,6 +148,7 @@ define([ var layers = this._layers = []; var attribution = ''; var overallAvailability = []; + var overallMaxZoom = 0; when(options.url) .then(function(url) { var resource = Resource.createIfNeeded(url); @@ -191,6 +195,7 @@ define([ var hasVertexNormals = false; var hasWaterMask = false; + var hasBvh = false; var littleEndianExtensionSize = true; var isHeightmap = false; if (data.format === 'heightmap-1.0') { @@ -217,11 +222,14 @@ define([ var tileUrlTemplates = data.tiles; + var maxZoom = data.maxzoom; + overallMaxZoom = Math.max(overallMaxZoom, maxZoom); var availableTiles = data.available; var availability; + var hasAvailability = false; if (defined(availableTiles)) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); - + hasAvailability = true; for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level); @@ -237,6 +245,12 @@ define([ availability.addAvailableTileRange(level, range.startX, yStart, range.endX, yEnd); } } + } else { + availability = new TileAvailability(that._tilingScheme, maxZoom); + overallAvailability[0] = [ + [0, 0, 1, 0] + ]; + availability.addAvailableTileRange(0, 0, 0, 1, 0); } // The vertex normals defined in the 'octvertexnormals' extension is identical to the original @@ -254,9 +268,13 @@ define([ if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) { hasWaterMask = true; } + if (defined(data.extensions) && data.extensions.indexOf('bvh') !== -1) { + hasBvh = true; + } that._hasWaterMask = that._hasWaterMask || hasWaterMask; that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals; + that._hasBvh = that._hasBvh || hasBvh; if (defined(data.attribution)) { if (attribution.length > 0) { attribution += ' '; @@ -269,9 +287,12 @@ define([ version: data.version, isHeightmap: isHeightmap, tileUrlTemplates: tileUrlTemplates, + hasAvailability: hasAvailability, availability: availability, hasVertexNormals: hasVertexNormals, hasWaterMask: hasWaterMask, + hasBvh: hasBvh, + bvhLevels: data.bvhlevels, littleEndianExtensionSize: littleEndianExtensionSize })); @@ -308,14 +329,12 @@ define([ } var length = overallAvailability.length; - if (length > 0) { - var availability = that._availability = new TileAvailability(that._tilingScheme, length); - for (var level = 0; level < length; ++level) { - var levelRanges = overallAvailability[level]; - for (var i = 0; i < levelRanges.length; ++i) { - var range = levelRanges[i]; - availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); - } + var availability = that._availability = new TileAvailability(that._tilingScheme, overallMaxZoom); + for (var level = 0; level < length; ++level) { + var levelRanges = overallAvailability[level]; + for (var i = 0; i < levelRanges.length; ++i) { + var range = levelRanges[i]; + availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); } } @@ -382,7 +401,15 @@ define([ * @constant * @default 2 */ - WATER_MASK: 2 + WATER_MASK: 2, + /** + * A bounding-volume hierarchy is included as an extension to the tile mesh + * + * @type {Number} + * @constant + * @default 3 + */ + BVH: 3 }; function getRequestHeader(extensionsList) { @@ -410,7 +437,8 @@ define([ }); } - function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, littleEndianExtensionSize) { + function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer) { + var littleEndianExtensionSize = layer.littleEndianExtensionSize; var pos = 0; var cartesian3Elements = 3; var boundingSphereElements = cartesian3Elements + 1; @@ -512,6 +540,26 @@ define([ encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2); } else if (extensionId === QuantizedMeshExtensionIds.WATER_MASK && provider._requestWaterMask) { waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength); + } else if (extensionId === QuantizedMeshExtensionIds.BVH && provider._requestBvh) { + var extensionPos = pos; + // Align to 4 bytes. + if (extensionPos % 4 !== 0) { + extensionPos += (4 - (extensionPos % 4)); + } + + var numberOfHeights = view.getUint32(extensionPos, true); + extensionPos += Uint32Array.BYTES_PER_ELEMENT; + var bvh = new Float32Array(buffer, extensionPos, numberOfHeights); + extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; + + if (!layer.hasAvailability && defined(layer.availability)) { + debugger; + var maxLevel = layer.bvhLevels + level - 1; + var finalIndex = recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); + if (finalIndex !== numberOfHeights) { + console.log('Incorrect number of heights'); + } + } } pos += extensionLength; } @@ -556,6 +604,27 @@ define([ }); } + function recurseHeights(level, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability) { + if (level > maxLevel) { + return index; + } + + if (!isNaN(buffer[index])) { + // Minimum height isn't a Nan, so the tile exists + // TODO: Make this more efficient + providerAvailability.addAvailableTileRange(level, x, y, x, y); + layerAvailability.addAvailableTileRange(level, x, y, x, y); + } + index += 2; + + index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SW + index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SE + index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // NW + index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // NE + + return index; + } + /** * Requests the geometry for a given tile. This function should not be called before * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and @@ -654,7 +723,7 @@ define([ if (defined(that._heightmapStructure)) { return createHeightmapTerrainData(that, buffer, level, x, y, tmsY); } - return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY, layerToUse.littleEndianExtensionSize); + return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY, layerToUse); }); }; From a4646334fd541ab38382f78a3b2fb9d9e70745a4 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 9 Oct 2018 14:54:45 -0400 Subject: [PATCH 02/23] Tweaks to where it mostly works for non-cutout terrain. --- Source/Core/CesiumTerrainProvider.js | 40 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index e58aaad19ab8..a15bf5b0b203 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -125,6 +125,15 @@ define([ */ this._requestWaterMask = defaultValue(options.requestWaterMask, false); + /** + * Boolean flag that indicates if the client should request tile bounding-volume hierarchy information + * from the server. + * @type {Boolean} + * @default true + * @private + */ + this._requestBvh = defaultValue(options.requestBvh, true); + this._errorEvent = new Event(); var credit = options.credit; @@ -553,11 +562,12 @@ define([ extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; if (!layer.hasAvailability && defined(layer.availability)) { - debugger; - var maxLevel = layer.bvhLevels + level - 1; - var finalIndex = recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); - if (finalIndex !== numberOfHeights) { - console.log('Incorrect number of heights'); + var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); + if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { + var maxLevel = numberofIncludedLevels + level - 1; + recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); + } else { + console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); } } } @@ -604,6 +614,19 @@ define([ }); } + function findNumberOfLevels(value) { + // This finds the number of levels stored in a BVH extension tile. + // All levels need to be complete. + // See http://mikestoolbox.com/powersum.html + for (var i = 0, current = 1; i < 32; ++i, current *= 4) { + if (value === ((current-1) / 3)) { + return i; + } + } + + return undefined; + } + function recurseHeights(level, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability) { if (level > maxLevel) { return index; @@ -615,8 +638,10 @@ define([ providerAvailability.addAvailableTileRange(level, x, y, x, y); layerAvailability.addAvailableTileRange(level, x, y, x, y); } - index += 2; + index += 2; // Skip min and max + x *= 2; + y *= 2; index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SW index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SE index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // NW @@ -685,6 +710,9 @@ define([ if (this._requestWaterMask && layerToUse.hasWaterMask) { extensionList.push('watermask'); } + if (this._requestBvh && layerToUse.hasBvh) { + extensionList.push('bvh'); + } var headers; var query; From 2bec071b5a8a4234a4d51099821271a3cdf11b2f Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 9 Oct 2018 17:00:27 -0400 Subject: [PATCH 03/23] Started cutout terrain work. --- Source/Core/CesiumTerrainProvider.js | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index a15bf5b0b203..c32a644163fb 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -938,8 +938,46 @@ define([ return undefined; } - return this._availability.isTileAvailable(level, x, y); + var layers = this._layers; + var layerCount = layers.length; + if (this._availability.isTileAvailable(level, x, y)) { + // If the tile is listed as available, then we are done + return true; + } + if (layerCount === 1 || !this._hasBvh) { + // TODO: check Unavailable list?? + // If we don't have any ancestors or no ancestors have the bvh extension + // then we know this tile isn't available. + return false; + } + + // We need to load some tiles to figure out what is available + checkAncestors(x, y, level, layers, 0); + + // Return false for now + return false; }; + function checkAncestors(x, y, level, layers, index) { + var layer = layers[index]; + if (!layer.hasAvailability && layer.hasBvh) { + // We only need to check this layer if there wasn't + // an available list and it has the bvh extension + + // TODO: Check if tile that has bvh has been loaded + // Be careful because it's availability could've been loaded + // from the tile before (eg level 0 contains level 5, + // but level 5 must be loaded to check level 6) + } + + if (++index === layers.length) + { + // TODO: Unavailable list?? + return when.resolve(false); + } + + return checkAncestors(x, y, level, layers, index); + } + return CesiumTerrainProvider; }); From 9ccfa6428f388607e09380cc2be59b95c9adf23d Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 10 Oct 2018 15:52:09 -0400 Subject: [PATCH 04/23] Work on cutout terrain. --- Source/Core/CesiumTerrainProvider.js | 96 ++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index c32a644163fb..36d73f961c28 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -236,6 +236,7 @@ define([ var availableTiles = data.available; var availability; var hasAvailability = false; + var bvhLoaded; // Keeps track of which BVH tiles were loaded if (defined(availableTiles)) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); hasAvailability = true; @@ -279,6 +280,7 @@ define([ } if (defined(data.extensions) && data.extensions.indexOf('bvh') !== -1) { hasBvh = true; + bvhLoaded = new TileAvailability(that._tilingScheme, maxZoom); } that._hasWaterMask = that._hasWaterMask || hasWaterMask; @@ -302,6 +304,7 @@ define([ hasWaterMask: hasWaterMask, hasBvh: hasBvh, bvhLevels: data.bvhlevels, + bvhLoaded: bvhLoaded, littleEndianExtensionSize: littleEndianExtensionSize })); @@ -561,10 +564,11 @@ define([ var bvh = new Float32Array(buffer, extensionPos, numberOfHeights); extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; - if (!layer.hasAvailability && defined(layer.availability)) { + if (!layer.hasAvailability) { // No available list in layer.json var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { var maxLevel = numberofIncludedLevels + level - 1; + layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); } else { console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); @@ -938,45 +942,99 @@ define([ return undefined; } - var layers = this._layers; - var layerCount = layers.length; if (this._availability.isTileAvailable(level, x, y)) { // If the tile is listed as available, then we are done return true; } - if (layerCount === 1 || !this._hasBvh) { - // TODO: check Unavailable list?? - // If we don't have any ancestors or no ancestors have the bvh extension - // then we know this tile isn't available. + if (!this._hasBvh) { + // If we don't have any layers with the bvh extension then we don't have this tile return false; } - // We need to load some tiles to figure out what is available - checkAncestors(x, y, level, layers, 0); + // Load any tiles we need to figure out availability + checkAncestorsLayers(this, x, y, level, 0); // Return false for now return false; }; - function checkAncestors(x, y, level, layers, index) { + function checkAncestorsLayers(provider, x, y, level, index) { + var layers = provider._layers; var layer = layers[index]; + var promise; if (!layer.hasAvailability && layer.hasBvh) { // We only need to check this layer if there wasn't // an available list and it has the bvh extension + var bvhLevels = layer.bvhLevels - 1; + var bvhLevel = ((level / bvhLevels) | 0) * bvhLevels; + var divisor = 1 << (level - bvhLevel); + var bvhX = (x / divisor) | 0; + var bvhY = (y / divisor) | 0; + promise = checkBVHParentTiles(provider, bvhLevel, bvhX, bvhY, layer); + } - // TODO: Check if tile that has bvh has been loaded - // Be careful because it's availability could've been loaded - // from the tile before (eg level 0 contains level 5, - // but level 5 must be loaded to check level 6) + // Nothing to load, so this tile isn't available in this layer + if (!defined(promise)) { + // This layer doesn't have it so we can move on to check the next layer + if (++index === layers.length) + { + // No more layers left + return; + } + return checkAncestorsLayers(provider, x, y, level, index); } - if (++index === layers.length) - { - // TODO: Unavailable list?? - return when.resolve(false); + return promise + .then(function() { + if (layer.availability.isTileAvailable(level, x, y)) { + // We now know we have this tile, so we can stop here + return; + } + + // This layer doesn't have it so we can move on to check the next layer + if (++index === layers.length) + { + // No more layers left + return; + } + return checkAncestorsLayers(provider, x, y, level, index); + }); + } + + function checkBVHParentTiles(provider, x, y, level, layer) { + var isAvailable = layer.availability.isTileAvailable(level, x, y); + var isLoaded = layer.bvhLoaded.isTileAvailable(level, x, y); + if (isAvailable && isLoaded) { + // We are available and loaded, so just return + return; + } + + var promise = when.resolve(); + if (!isAvailable) { + var bvhLevels = layer.bvhLevels - 1; + var divisor = 1 << bvhLevels; + var parentLevel = level - bvhLevels; + var parentX = (x / divisor) | 0; + var parentY = (y / divisor) | 0; + + promise = checkBVHParentTiles(provider, parentLevel, parentX, parentY); + + // If all parent BVH tiles are already loaded, then this tile isn't available + if (!defined(promise)) { + return; + } } - return checkAncestors(x, y, level, layers, index); + return promise + .then(function() { + if (!layer.availability.isTileAvailable(level, x, y)) { + // All parents are loaded, so if this tile isn't available don't try to load it. + return; + } + + // Load the tile + return provider.requestTileGeometry(x, y, level); + }); } return CesiumTerrainProvider; From 76b1f0b982031ff01a9df21bfa0a267260613caf Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 16 Oct 2018 11:37:56 -0400 Subject: [PATCH 05/23] Fixes for BVH availability. --- Source/Core/CesiumTerrainProvider.js | 53 +++++++++++++++------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 36d73f961c28..db2b6ce4495f 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -60,6 +60,7 @@ define([ this.hasBvh = layer.hasBvh; this.bvhLevels = layer.bvhLevels; this.littleEndianExtensionSize = layer.littleEndianExtensionSize; + this.bvhLoaded = layer.bvhLoaded; } /** @@ -233,10 +234,31 @@ define([ var maxZoom = data.maxzoom; overallMaxZoom = Math.max(overallMaxZoom, maxZoom); + var bvhLoaded; // Keeps track of which BVH tiles were loaded + + // The vertex normals defined in the 'octvertexnormals' extension is identical to the original + // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now + // deprecated, as the extensionLength for this extension was incorrectly using big endian. + // We maintain backwards compatibility with the legacy 'vertexnormal' implementation + // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals' + // over 'vertexnormals' if both extensions are supported by the server. + if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) { + hasVertexNormals = true; + } else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) { + hasVertexNormals = true; + littleEndianExtensionSize = false; + } + if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) { + hasWaterMask = true; + } + if (defined(data.extensions) && data.extensions.indexOf('bvh') !== -1) { + hasBvh = true; + bvhLoaded = new TileAvailability(that._tilingScheme, maxZoom); + } + var availableTiles = data.available; var availability; var hasAvailability = false; - var bvhLoaded; // Keeps track of which BVH tiles were loaded if (defined(availableTiles)) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); hasAvailability = true; @@ -255,7 +277,7 @@ define([ availability.addAvailableTileRange(level, range.startX, yStart, range.endX, yEnd); } } - } else { + } else if (hasBvh) { availability = new TileAvailability(that._tilingScheme, maxZoom); overallAvailability[0] = [ [0, 0, 1, 0] @@ -263,26 +285,6 @@ define([ availability.addAvailableTileRange(0, 0, 0, 1, 0); } - // The vertex normals defined in the 'octvertexnormals' extension is identical to the original - // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now - // deprecated, as the extensionLength for this extension was incorrectly using big endian. - // We maintain backwards compatibility with the legacy 'vertexnormal' implementation - // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals' - // over 'vertexnormals' if both extensions are supported by the server. - if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) { - hasVertexNormals = true; - } else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) { - hasVertexNormals = true; - littleEndianExtensionSize = false; - } - if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) { - hasWaterMask = true; - } - if (defined(data.extensions) && data.extensions.indexOf('bvh') !== -1) { - hasBvh = true; - bvhLoaded = new TileAvailability(that._tilingScheme, maxZoom); - } - that._hasWaterMask = that._hasWaterMask || hasWaterMask; that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals; that._hasBvh = that._hasBvh || hasBvh; @@ -564,7 +566,8 @@ define([ var bvh = new Float32Array(buffer, extensionPos, numberOfHeights); extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; - if (!layer.hasAvailability) { // No available list in layer.json + // No available list in layer.json, so load availability from this tile + if (!layer.hasAvailability) { var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { var maxLevel = numberofIncludedLevels + level - 1; @@ -970,7 +973,7 @@ define([ var divisor = 1 << (level - bvhLevel); var bvhX = (x / divisor) | 0; var bvhY = (y / divisor) | 0; - promise = checkBVHParentTiles(provider, bvhLevel, bvhX, bvhY, layer); + promise = checkBVHParentTiles(provider, bvhX, bvhY, bvhLevel, layer); } // Nothing to load, so this tile isn't available in this layer @@ -1017,7 +1020,7 @@ define([ var parentX = (x / divisor) | 0; var parentY = (y / divisor) | 0; - promise = checkBVHParentTiles(provider, parentLevel, parentX, parentY); + promise = checkBVHParentTiles(provider, parentX, parentY, parentLevel, layer); // If all parent BVH tiles are already loaded, then this tile isn't available if (!defined(promise)) { From 49b94d2ead963d87e6aa18a64f43f3b5aa333c9e Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 16 Oct 2018 13:45:39 -0400 Subject: [PATCH 06/23] sampleTerrainMostDetailed now works correctly. --- Source/Core/CesiumTerrainProvider.js | 95 +++++++++++++++++++----- Source/Core/sampleTerrainMostDetailed.js | 49 +++++++----- 2 files changed, 108 insertions(+), 36 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index db2b6ce4495f..d3e1a23557bb 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -3,6 +3,7 @@ define([ '../ThirdParty/when', './AttributeCompression', './BoundingSphere', + './Cartesian2', './Cartesian3', './Credit', './defaultValue', @@ -27,6 +28,7 @@ define([ when, AttributeCompression, BoundingSphere, + Cartesian2, Cartesian3, Credit, defaultValue, @@ -343,12 +345,14 @@ define([ } var length = overallAvailability.length; - var availability = that._availability = new TileAvailability(that._tilingScheme, overallMaxZoom); - for (var level = 0; level < length; ++level) { - var levelRanges = overallAvailability[level]; - for (var i = 0; i < levelRanges.length; ++i) { - var range = levelRanges[i]; - availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); + if (length > 0) { + var availability = that._availability = new TileAvailability(that._tilingScheme, overallMaxZoom); + for (var level = 0; level < length; ++level) { + var levelRanges = overallAvailability[level]; + for (var i = 0; i < levelRanges.length; ++i) { + var range = levelRanges[i]; + availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); + } } } @@ -876,6 +880,26 @@ define([ } }, + /** + * Gets a value indicating whether or not the requested tiles include a BVH. + * This function should not be called before {@link CesiumTerrainProvider#ready} returns true. + * @memberof CesiumTerrainProvider.prototype + * @type {Boolean} + * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready} + */ + hasBvh : { + get : function() { + //>>includeStart('debug', pragmas.debug) + if (!this._ready) { + throw new DeveloperError('hasBvh must not be called before the terrain provider is ready.'); + } + //>>includeEnd('debug'); + + // returns true if we can request vertex normals from the server + return this._hasBvh && this._requestBvh; + } + }, + /** * Boolean flag that indicates if the client should request vertex normals from the server. * Vertex normals data is appended to the standard tile mesh data only if the client requests the vertex normals and @@ -902,6 +926,19 @@ define([ } }, + /** + * Boolean flag that indicates if the client should request a BVH from the server. + * BVH data is appended to the standard tile mesh data only if the client requests the BVH and + * if the server provides a BVH. + * @memberof CesiumTerrainProvider.prototype + * @type {Boolean} + */ + requestBvh : { + get : function() { + return this._requestBvh; + } + }, + /** * Gets an object that can be used to determine availability of terrain from this provider, such as * at points and in rectangles. This function should not be called before @@ -932,33 +969,57 @@ define([ return this._levelZeroMaximumGeometricError / (1 << level); }; + var scratchTileXY = new Cartesian2(); /** - * Determines whether data for a tile is available to be loaded. + * Loads the availability for the position if not already loaded. * - * @param {Number} x The X coordinate of the tile for which to request geometry. - * @param {Number} y The Y coordinate of the tile for which to request geometry. - * @param {Number} level The level of the tile for which to request geometry. - * @returns {Boolean} Undefined if not supported, otherwise true or false. + * @param {Cartographic} position The position to make sure availability is loaded for. + * @returns {undefined|Promise<>} A promise that resolves when the availability is loaded. Undefined if availability isn't supported. */ - CesiumTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + CesiumTerrainProvider.prototype.loadAvailability = function(position) { if (!defined(this._availability)) { return undefined; } - if (this._availability.isTileAvailable(level, x, y)) { + // Try and load the max level tile at the position. Even if its not there we will + // still end up loading all the BVH tiles below it and its max level of availability. + var level = this.availability._maximumLevel; + this._tilingScheme.positionToTileXY(position, level, scratchTileXY); + + return when(loadTileAvailability(this, scratchTileXY.x, scratchTileXY.y, level)); + }; + + function loadTileAvailability(provider, x, y, level) { + if (provider._availability.isTileAvailable(level, x, y)) { // If the tile is listed as available, then we are done return true; } - if (!this._hasBvh) { + if (!provider._hasBvh) { // If we don't have any layers with the bvh extension then we don't have this tile return false; } // Load any tiles we need to figure out availability - checkAncestorsLayers(this, x, y, level, 0); + return checkAncestorsLayers(provider, x, y, level, 0); + } + + /** + * Determines whether data for a tile is available to be loaded. + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {Boolean} Undefined if not supported, otherwise true or false. + */ + CesiumTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + if (!defined(this._availability)) { + return undefined; + } + + var result = loadTileAvailability(this, x, y, level); - // Return false for now - return false; + // If we got a result, return it. If it was a promise return false for now. + return (typeof result === 'boolean') ? result : false; }; function checkAncestorsLayers(provider, x, y, level, index) { diff --git a/Source/Core/sampleTerrainMostDetailed.js b/Source/Core/sampleTerrainMostDetailed.js index f2070ebd82b0..d0a15f5b8fc5 100644 --- a/Source/Core/sampleTerrainMostDetailed.js +++ b/Source/Core/sampleTerrainMostDetailed.js @@ -43,19 +43,39 @@ define([ } //>>includeEnd('debug'); - return terrainProvider.readyPromise.then(function() { - var byLevel = []; + return terrainProvider.readyPromise + .then(function() { + var byLevel = []; - var availability = terrainProvider.availability; + var availability = terrainProvider.availability; - //>>includeStart('debug', pragmas.debug); - if (!defined(availability)) { - throw new DeveloperError('sampleTerrainMostDetailed requires a terrain provider that has tile availability.'); - } - //>>includeEnd('debug'); + //>>includeStart('debug', pragmas.debug); + if (!defined(availability)) { + throw new DeveloperError('sampleTerrainMostDetailed requires a terrain provider that has tile availability.'); + } + //>>includeEnd('debug'); + + var promises = []; + for (var i = 0; i < positions.length; ++i) { + promises.push(loadAvailability(terrainProvider, positions[i], availability, byLevel)); + } - for (var i = 0; i < positions.length; ++i) { - var position = positions[i]; + return when.all(promises) + .then(function() { + return when.all(byLevel.map(function(positionsAtLevel, index) { + if (defined(positionsAtLevel)) { + return sampleTerrain(terrainProvider, index, positionsAtLevel); + } + })); + }).then(function() { + return positions; + }); + }); + } + + function loadAvailability(terrainProvider, position, availability, byLevel) { + return terrainProvider.loadAvailability(position) + .then(function() { var maxLevel = availability.computeMaximumLevelAtPosition(position); var atLevel = byLevel[maxLevel]; @@ -63,16 +83,7 @@ define([ byLevel[maxLevel] = atLevel = []; } atLevel.push(position); - } - - return when.all(byLevel.map(function(positionsAtLevel, index) { - if (defined(positionsAtLevel)) { - return sampleTerrain(terrainProvider, index, positionsAtLevel); - } - })).then(function() { - return positions; }); - }); } return sampleTerrainMostDetailed; From 689b886403b644e9c8e0425b95b2b0bed8f02628 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 22 Oct 2018 10:36:19 -0400 Subject: [PATCH 07/23] Doc fix. --- Source/Core/CesiumTerrainProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index d3e1a23557bb..5b6a2fd42ef8 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -974,7 +974,7 @@ define([ * Loads the availability for the position if not already loaded. * * @param {Cartographic} position The position to make sure availability is loaded for. - * @returns {undefined|Promise<>} A promise that resolves when the availability is loaded. Undefined if availability isn't supported. + * @returns {undefined|Promise} A promise that resolves when the availability is loaded. Undefined if availability isn't supported. */ CesiumTerrainProvider.prototype.loadAvailability = function(position) { if (!defined(this._availability)) { From 3d32849901babaf2b8fbd1577ca1047662d97047 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 22 Oct 2018 21:52:17 -0400 Subject: [PATCH 08/23] Tweaks to get cutout terrain almost working. --- Source/Core/CesiumTerrainProvider.js | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 5b6a2fd42ef8..cae28f7efd68 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -55,7 +55,6 @@ define([ this.version = layer.version; this.isHeightmap = layer.isHeightmap; this.tileUrlTemplates = layer.tileUrlTemplates; - this.hasAvailability = layer.hasAvailability; this.availability = layer.availability; this.hasVertexNormals = layer.hasVertexNormals; this.hasWaterMask = layer.hasWaterMask; @@ -260,10 +259,8 @@ define([ var availableTiles = data.available; var availability; - var hasAvailability = false; - if (defined(availableTiles)) { + if (defined(availableTiles) && !hasBvh) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); - hasAvailability = true; for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level); @@ -302,7 +299,6 @@ define([ version: data.version, isHeightmap: isHeightmap, tileUrlTemplates: tileUrlTemplates, - hasAvailability: hasAvailability, availability: availability, hasVertexNormals: hasVertexNormals, hasWaterMask: hasWaterMask, @@ -455,7 +451,7 @@ define([ }); } - function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer) { + function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer, availabilityOnly) { var littleEndianExtensionSize = layer.littleEndianExtensionSize; var pos = 0; var cartesian3Elements = 3; @@ -570,21 +566,22 @@ define([ var bvh = new Float32Array(buffer, extensionPos, numberOfHeights); extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; - // No available list in layer.json, so load availability from this tile - if (!layer.hasAvailability) { - var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); - if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { - var maxLevel = numberofIncludedLevels + level - 1; - layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); - recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); - } else { - console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); - } + var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); + if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { + var maxLevel = numberofIncludedLevels + level - 1; + layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); + recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); + } else { + console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); } } pos += extensionLength; } + if (availabilityOnly) { + return; + } + var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); @@ -701,6 +698,10 @@ define([ } } + return requestTileGeometry(this, x, y, level, request, layerToUse, false); + }; + + function requestTileGeometry(provider, x, y, level, request, layerToUse, availabilityOnly) { if (!defined(layerToUse)) { return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } @@ -710,18 +711,18 @@ define([ return undefined; } - var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level); + var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level); var tmsY = (yTiles - y - 1); var extensionList = []; - if (this._requestVertexNormals && layerToUse.hasVertexNormals) { + if (provider._requestVertexNormals && layerToUse.hasVertexNormals) { extensionList.push(layerToUse.littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals'); } - if (this._requestWaterMask && layerToUse.hasWaterMask) { + if (provider._requestWaterMask && layerToUse.hasWaterMask) { extensionList.push('watermask'); } - if (this._requestBvh && layerToUse.hasBvh) { + if (provider._requestBvh && layerToUse.hasBvh) { extensionList.push('bvh'); } @@ -757,14 +758,13 @@ define([ return undefined; } - var that = this; return promise.then(function (buffer) { - if (defined(that._heightmapStructure)) { - return createHeightmapTerrainData(that, buffer, level, x, y, tmsY); + if (defined(provider._heightmapStructure)) { + return createHeightmapTerrainData(provider, buffer, level, x, y, tmsY); } - return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY, layerToUse); + return createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layerToUse, availabilityOnly); }); - }; + } defineProperties(CesiumTerrainProvider.prototype, { /** @@ -1026,7 +1026,7 @@ define([ var layers = provider._layers; var layer = layers[index]; var promise; - if (!layer.hasAvailability && layer.hasBvh) { + if (layer.hasBvh) { // We only need to check this layer if there wasn't // an available list and it has the bvh extension var bvhLevels = layer.bvhLevels - 1; @@ -1097,7 +1097,7 @@ define([ } // Load the tile - return provider.requestTileGeometry(x, y, level); + return requestTileGeometry(provider, x, y, level, undefined, layer, true); }); } From fe2eeb89834751ceb58b315bf69663f1a79ccf32 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 23 Oct 2018 16:47:24 -0400 Subject: [PATCH 09/23] Fixed availability issue. --- Source/Core/CesiumTerrainProvider.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index cae28f7efd68..c85012023b36 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -570,7 +570,7 @@ define([ if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { var maxLevel = numberofIncludedLevels + level - 1; layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); - recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); + recurseHeights(level, x, y, bvh, 0, maxLevel, provider, layer.availability); } else { console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); } @@ -635,25 +635,32 @@ define([ return undefined; } - function recurseHeights(level, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability) { + function recurseHeights(level, x, y, buffer, index, maxLevel, provider, layerAvailability) { if (level > maxLevel) { return index; } + var providerAvailability = provider.availability; if (!isNaN(buffer[index])) { // Minimum height isn't a Nan, so the tile exists + + // The server stores everything in TMS, which has the reverse Y that our + // tiling scheme uses. Convert to the Cesium version to store in availability. + var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level); + var cesiumY = yTiles - y - 1; + // TODO: Make this more efficient - providerAvailability.addAvailableTileRange(level, x, y, x, y); - layerAvailability.addAvailableTileRange(level, x, y, x, y); + providerAvailability.addAvailableTileRange(level, x, cesiumY, x, cesiumY); + layerAvailability.addAvailableTileRange(level, x, cesiumY, x, cesiumY); } index += 2; // Skip min and max x *= 2; y *= 2; - index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SW - index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // SE - index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // NW - index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // NE + index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, provider, layerAvailability); // SW + index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, provider, layerAvailability); // SE + index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, provider, layerAvailability); // NW + index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, provider, layerAvailability); // NE return index; } From 163d3bea9efb39e9e71ccb3ae657c8f794d50d80 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 24 Oct 2018 16:31:52 -0400 Subject: [PATCH 10/23] Got cutout terrain working with BVH availability. --- Source/Core/CesiumTerrainProvider.js | 41 ++++++++++++---------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index c85012023b36..0f3824003c23 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -570,7 +570,7 @@ define([ if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { var maxLevel = numberofIncludedLevels + level - 1; layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); - recurseHeights(level, x, y, bvh, 0, maxLevel, provider, layer.availability); + recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); } else { console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); } @@ -635,32 +635,25 @@ define([ return undefined; } - function recurseHeights(level, x, y, buffer, index, maxLevel, provider, layerAvailability) { + function recurseHeights(level, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability) { if (level > maxLevel) { return index; } - var providerAvailability = provider.availability; if (!isNaN(buffer[index])) { // Minimum height isn't a Nan, so the tile exists - - // The server stores everything in TMS, which has the reverse Y that our - // tiling scheme uses. Convert to the Cesium version to store in availability. - var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level); - var cesiumY = yTiles - y - 1; - // TODO: Make this more efficient - providerAvailability.addAvailableTileRange(level, x, cesiumY, x, cesiumY); - layerAvailability.addAvailableTileRange(level, x, cesiumY, x, cesiumY); + providerAvailability.addAvailableTileRange(level, x, y, x, y); + layerAvailability.addAvailableTileRange(level, x, y, x, y); } index += 2; // Skip min and max x *= 2; y *= 2; - index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, provider, layerAvailability); // SW - index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, provider, layerAvailability); // SE - index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, provider, layerAvailability); // NW - index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, provider, layerAvailability); // NE + index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // SW + index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // SE + index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // NW + index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // NE return index; } @@ -1022,6 +1015,9 @@ define([ if (!defined(this._availability)) { return undefined; } + if (level > this._availability._maximumLevel) { + return false; + } var result = loadTileAvailability(this, x, y, level); @@ -1073,15 +1069,14 @@ define([ } function checkBVHParentTiles(provider, x, y, level, layer) { - var isAvailable = layer.availability.isTileAvailable(level, x, y); var isLoaded = layer.bvhLoaded.isTileAvailable(level, x, y); - if (isAvailable && isLoaded) { - // We are available and loaded, so just return + if (isLoaded) { + // Tile is loaded, so just return return; } var promise = when.resolve(); - if (!isAvailable) { + if (level !== 0) { var bvhLevels = layer.bvhLevels - 1; var divisor = 1 << bvhLevels; var parentLevel = level - bvhLevels; @@ -1089,11 +1084,11 @@ define([ var parentY = (y / divisor) | 0; promise = checkBVHParentTiles(provider, parentX, parentY, parentLevel, layer); + } - // If all parent BVH tiles are already loaded, then this tile isn't available - if (!defined(promise)) { - return; - } + // If all parent BVH tiles are already loaded, then this tile isn't available + if (!defined(promise)) { + return; } return promise From 041c744990efd0e0ff88dc53fc4e4d6fec93da67 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 5 Nov 2018 16:16:40 -0500 Subject: [PATCH 11/23] Got availability to work from metadata extension --- Source/Core/CesiumTerrainProvider.js | 243 +++++++++++++-------------- 1 file changed, 117 insertions(+), 126 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 0f3824003c23..7fd2980fd520 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -18,6 +18,8 @@ define([ './Math', './OrientedBoundingBox', './QuantizedMeshTerrainData', + './Request', + './RequestType', './Resource', './RuntimeError', './TerrainProvider', @@ -43,6 +45,8 @@ define([ CesiumMath, OrientedBoundingBox, QuantizedMeshTerrainData, + Request, + RequestType, Resource, RuntimeError, TerrainProvider, @@ -58,10 +62,11 @@ define([ this.availability = layer.availability; this.hasVertexNormals = layer.hasVertexNormals; this.hasWaterMask = layer.hasWaterMask; - this.hasBvh = layer.hasBvh; - this.bvhLevels = layer.bvhLevels; + this.hasMetadata = layer.hasMetadata; + this.availabilityLevels = layer.availabilityLevels; + this.availabilityTilesLoaded = layer.availabilityTilesLoaded; this.littleEndianExtensionSize = layer.littleEndianExtensionSize; - this.bvhLoaded = layer.bvhLoaded; + this.availabilityTilesLoaded = layer.availabilityTilesLoaded; } /** @@ -128,13 +133,13 @@ define([ this._requestWaterMask = defaultValue(options.requestWaterMask, false); /** - * Boolean flag that indicates if the client should request tile bounding-volume hierarchy information + * Boolean flag that indicates if the client should request tile metadata * from the server. * @type {Boolean} * @default true * @private */ - this._requestBvh = defaultValue(options.requestBvh, true); + this._requestMetadata = defaultValue(options.requestMetadata, true); this._errorEvent = new Event(); @@ -153,7 +158,7 @@ define([ var that = this; var lastResource; - var metadataResource; + var layerJsonResource; var metadataError; var layers = this._layers = []; @@ -165,11 +170,11 @@ define([ var resource = Resource.createIfNeeded(url); resource.appendForwardSlash(); lastResource = resource; - metadataResource = lastResource.getDerivedResource({ + layerJsonResource = lastResource.getDerivedResource({ url: 'layer.json' }); - var uri = new Uri(metadataResource.url); + var uri = new Uri(layerJsonResource.url); if (uri.authority === 'assets.agi.com') { var deprecationText = 'STK World Terrain at assets.agi.com was shut down on October 1, 2018.'; var deprecationLinkText = 'Check out the new high-resolution Cesium World Terrain for migration instructions.'; @@ -183,7 +188,7 @@ define([ that._tileCredits = resource.credits; } - requestMetadata(); + requestLayerJson(); }) .otherwise(function(e) { deferred.reject(e); @@ -194,19 +199,19 @@ define([ if (!data.format) { message = 'The tile format is not specified in the layer.json file.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson); return; } if (!data.tiles || data.tiles.length === 0) { message = 'The layer.json file does not specify any tile URL templates.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson); return; } var hasVertexNormals = false; var hasWaterMask = false; - var hasBvh = false; + var hasMetadata = false; var littleEndianExtensionSize = true; var isHeightmap = false; if (data.format === 'heightmap-1.0') { @@ -227,7 +232,7 @@ define([ that._requestWaterMask = true; } else if (data.format.indexOf('quantized-mesh-1.') !== 0) { message = 'The tile format "' + data.format + '" is invalid or not supported.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson); return; } @@ -235,7 +240,8 @@ define([ var maxZoom = data.maxzoom; overallMaxZoom = Math.max(overallMaxZoom, maxZoom); - var bvhLoaded; // Keeps track of which BVH tiles were loaded + // Keeps track of which of the availablity containing tiles have been loaded + var availabilityTilesLoaded; // The vertex normals defined in the 'octvertexnormals' extension is identical to the original // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now @@ -252,14 +258,14 @@ define([ if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) { hasWaterMask = true; } - if (defined(data.extensions) && data.extensions.indexOf('bvh') !== -1) { - hasBvh = true; - bvhLoaded = new TileAvailability(that._tilingScheme, maxZoom); + if (defined(data.extensions) && data.extensions.indexOf('metadata') !== -1) { + hasMetadata = true; } + var availabilityLevels = data.metadataAvailability; var availableTiles = data.available; var availability; - if (defined(availableTiles) && !hasBvh) { + if (defined(availableTiles) && !defined(availabilityLevels)) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; @@ -276,7 +282,8 @@ define([ availability.addAvailableTileRange(level, range.startX, yStart, range.endX, yEnd); } } - } else if (hasBvh) { + } else if (defined(availabilityLevels)) { + availabilityTilesLoaded = new TileAvailability(that._tilingScheme, maxZoom); availability = new TileAvailability(that._tilingScheme, maxZoom); overallAvailability[0] = [ [0, 0, 1, 0] @@ -286,7 +293,7 @@ define([ that._hasWaterMask = that._hasWaterMask || hasWaterMask; that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals; - that._hasBvh = that._hasBvh || hasBvh; + that._hasMetadata = that._hasMetadata || hasMetadata; if (defined(data.attribution)) { if (attribution.length > 0) { attribution += ' '; @@ -302,9 +309,9 @@ define([ availability: availability, hasVertexNormals: hasVertexNormals, hasWaterMask: hasWaterMask, - hasBvh: hasBvh, - bvhLevels: data.bvhlevels, - bvhLoaded: bvhLoaded, + hasMetadata: hasMetadata, + availabilityLevels: availabilityLevels, + availabilityTilesLoaded: availabilityTilesLoaded, littleEndianExtensionSize: littleEndianExtensionSize })); @@ -318,10 +325,10 @@ define([ url: parentUrl }); lastResource.appendForwardSlash(); // Terrain always expects a directory - metadataResource = lastResource.getDerivedResource({ + layerJsonResource = lastResource.getDerivedResource({ url: 'layer.json' }); - var parentMetadata = metadataResource.fetchJson(); + var parentMetadata = layerJsonResource.fetchJson(); return when(parentMetadata, parseMetadataSuccess, parseMetadataFailure); } @@ -329,8 +336,8 @@ define([ } function parseMetadataFailure(data) { - var message = 'An error occurred while accessing ' + metadataResource.url + '.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + var message = 'An error occurred while accessing ' + layerJsonResource.url + '.'; + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson); } function metadataSuccess(data) { @@ -384,8 +391,8 @@ define([ parseMetadataFailure(data); } - function requestMetadata() { - when(metadataResource.fetchJson()) + function requestLayerJson() { + when(layerJsonResource.fetchJson()) .then(metadataSuccess) .otherwise(metadataFailure); } @@ -417,13 +424,13 @@ define([ */ WATER_MASK: 2, /** - * A bounding-volume hierarchy is included as an extension to the tile mesh + * A json object contain metadata about the tile * * @type {Number} * @constant - * @default 3 + * @default 4 */ - BVH: 3 + METADATA: 4 }; function getRequestHeader(extensionsList) { @@ -451,7 +458,7 @@ define([ }); } - function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer, availabilityOnly) { + function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer) { var littleEndianExtensionSize = layer.littleEndianExtensionSize; var pos = 0; var cartesian3Elements = 3; @@ -554,34 +561,34 @@ define([ encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2); } else if (extensionId === QuantizedMeshExtensionIds.WATER_MASK && provider._requestWaterMask) { waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength); - } else if (extensionId === QuantizedMeshExtensionIds.BVH && provider._requestBvh) { - var extensionPos = pos; - // Align to 4 bytes. - if (extensionPos % 4 !== 0) { - extensionPos += (4 - (extensionPos % 4)); - } - - var numberOfHeights = view.getUint32(extensionPos, true); - extensionPos += Uint32Array.BYTES_PER_ELEMENT; - var bvh = new Float32Array(buffer, extensionPos, numberOfHeights); - extensionPos += Float32Array.BYTES_PER_ELEMENT * numberOfHeights; - - var numberofIncludedLevels = findNumberOfLevels(numberOfHeights/2); - if (defined(numberofIncludedLevels) && numberofIncludedLevels <= layer.bvhLevels) { - var maxLevel = numberofIncludedLevels + level - 1; - layer.bvhLoaded.addAvailableTileRange(level, x, y, x, y); - recurseHeights(level, x, y, bvh, 0, maxLevel, provider.availability, layer.availability); - } else { - console.log('Incorrect number of heights in tile Level: %d X: %d Y: %d', level, x, y); + } else if (extensionId === QuantizedMeshExtensionIds.METADATA && provider._requestMetadata) { + var stringLength = view.getUint32(pos, true); + if (stringLength > 0) { + var jsonString = String.fromCharCode.apply(null, + new Uint8Array(buffer, pos + Uint32Array.BYTES_PER_ELEMENT, stringLength)); + var metadata = JSON.parse(jsonString); + var availableTiles = metadata.available; + if (defined(availableTiles)) { + for (var offset = 0; offset < availableTiles.length; ++offset) { + var availableLevel = level + offset + 1; + var rangesAtLevel = availableTiles[offset]; + var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(availableLevel); + + for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) { + var range = rangesAtLevel[rangeIndex]; + var yStart = yTiles - range.endY - 1; + var yEnd = yTiles - range.startY - 1; + provider.availability.addAvailableTileRange(availableLevel, range.startX, yStart, range.endX, yEnd); + layer.availability.addAvailableTileRange(availableLevel, range.startX, yStart, range.endX, yEnd); + } + } + } } + layer.availabilityTilesLoaded.addAvailableTileRange(level, x, y, x, y); } pos += extensionLength; } - if (availabilityOnly) { - return; - } - var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); @@ -622,42 +629,6 @@ define([ }); } - function findNumberOfLevels(value) { - // This finds the number of levels stored in a BVH extension tile. - // All levels need to be complete. - // See http://mikestoolbox.com/powersum.html - for (var i = 0, current = 1; i < 32; ++i, current *= 4) { - if (value === ((current-1) / 3)) { - return i; - } - } - - return undefined; - } - - function recurseHeights(level, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability) { - if (level > maxLevel) { - return index; - } - - if (!isNaN(buffer[index])) { - // Minimum height isn't a Nan, so the tile exists - // TODO: Make this more efficient - providerAvailability.addAvailableTileRange(level, x, y, x, y); - layerAvailability.addAvailableTileRange(level, x, y, x, y); - } - index += 2; // Skip min and max - - x *= 2; - y *= 2; - index = recurseHeights(level + 1, x, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // SW - index = recurseHeights(level + 1, x + 1, y + 1, buffer, index, maxLevel, providerAvailability, layerAvailability); // SE - index = recurseHeights(level + 1, x, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // NW - index = recurseHeights(level + 1, x + 1, y, buffer, index, maxLevel, providerAvailability, layerAvailability); // NE - - return index; - } - /** * Requests the geometry for a given tile. This function should not be called before * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and @@ -698,14 +669,21 @@ define([ } } - return requestTileGeometry(this, x, y, level, request, layerToUse, false); + return requestTileGeometry(this, x, y, level, layerToUse, request); }; - function requestTileGeometry(provider, x, y, level, request, layerToUse, availabilityOnly) { + function requestTileGeometry(provider, x, y, level, layerToUse, request) { if (!defined(layerToUse)) { return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } + // Do we want a priorityFunction + request = defaultValue(request, new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TERRAIN + })); + var urlTemplates = layerToUse.tileUrlTemplates; if (urlTemplates.length === 0) { return undefined; @@ -722,8 +700,8 @@ define([ if (provider._requestWaterMask && layerToUse.hasWaterMask) { extensionList.push('watermask'); } - if (provider._requestBvh && layerToUse.hasBvh) { - extensionList.push('bvh'); + if (provider._requestMetadata && layerToUse.hasMetadata) { + extensionList.push('metadata'); } var headers; @@ -762,7 +740,7 @@ define([ if (defined(provider._heightmapStructure)) { return createHeightmapTerrainData(provider, buffer, level, x, y, tmsY); } - return createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layerToUse, availabilityOnly); + return createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layerToUse); }); } @@ -881,22 +859,22 @@ define([ }, /** - * Gets a value indicating whether or not the requested tiles include a BVH. + * Gets a value indicating whether or not the requested tiles include metadata. * This function should not be called before {@link CesiumTerrainProvider#ready} returns true. * @memberof CesiumTerrainProvider.prototype * @type {Boolean} * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready} */ - hasBvh : { + hasMetadata : { get : function() { //>>includeStart('debug', pragmas.debug) if (!this._ready) { - throw new DeveloperError('hasBvh must not be called before the terrain provider is ready.'); + throw new DeveloperError('hasMetadata must not be called before the terrain provider is ready.'); } //>>includeEnd('debug'); - // returns true if we can request vertex normals from the server - return this._hasBvh && this._requestBvh; + // returns true if we can request metadata from the server + return this._hasMetadata && this._requestMetadata; } }, @@ -927,15 +905,15 @@ define([ }, /** - * Boolean flag that indicates if the client should request a BVH from the server. - * BVH data is appended to the standard tile mesh data only if the client requests the BVH and - * if the server provides a BVH. + * Boolean flag that indicates if the client should request metadata from the server. + * Metadata is appended to the standard tile mesh data only if the client requests the metadata and + * if the server provides a metadata. * @memberof CesiumTerrainProvider.prototype * @type {Boolean} */ - requestBvh : { + requestMetadata : { get : function() { - return this._requestBvh; + return this._requestMetadata; } }, @@ -1025,19 +1003,37 @@ define([ return (typeof result === 'boolean') ? result : false; }; + function getAvailabilityTile(layer, x, y, level) { + var availabilityLevels = layer.availabilityLevels; + if (level % availabilityLevels === 0) { + return { + level: level, + x: x, + y: y + }; + } + + var parentLevel = ((level / availabilityLevels) | 0) * availabilityLevels; + var divisor = 1 << (level - parentLevel); + var parentX = (x / divisor) | 0; + var parentY = (y / divisor) | 0; + + return { + level: parentLevel, + x: parentX, + y: parentY + }; + } + function checkAncestorsLayers(provider, x, y, level, index) { var layers = provider._layers; var layer = layers[index]; var promise; - if (layer.hasBvh) { + if (defined(layer.availabilityLevels)) { // We only need to check this layer if there wasn't - // an available list and it has the bvh extension - var bvhLevels = layer.bvhLevels - 1; - var bvhLevel = ((level / bvhLevels) | 0) * bvhLevels; - var divisor = 1 << (level - bvhLevel); - var bvhX = (x / divisor) | 0; - var bvhY = (y / divisor) | 0; - promise = checkBVHParentTiles(provider, bvhX, bvhY, bvhLevel, layer); + // an available list and it has the metadata extension + var parent = getAvailabilityTile(layer, x, y, level); + promise = checkParentTiles(provider, parent.x, parent.y, parent.level, layer); } // Nothing to load, so this tile isn't available in this layer @@ -1068,8 +1064,8 @@ define([ }); } - function checkBVHParentTiles(provider, x, y, level, layer) { - var isLoaded = layer.bvhLoaded.isTileAvailable(level, x, y); + function checkParentTiles(provider, x, y, level, layer) { + var isLoaded = layer.availabilityTilesLoaded.isTileAvailable(level, x, y); if (isLoaded) { // Tile is loaded, so just return return; @@ -1077,16 +1073,11 @@ define([ var promise = when.resolve(); if (level !== 0) { - var bvhLevels = layer.bvhLevels - 1; - var divisor = 1 << bvhLevels; - var parentLevel = level - bvhLevels; - var parentX = (x / divisor) | 0; - var parentY = (y / divisor) | 0; - - promise = checkBVHParentTiles(provider, parentX, parentY, parentLevel, layer); + var parent = getAvailabilityTile(layer, x, y, level); + promise = checkParentTiles(provider, parent.x, parent.y, parent.level, layer); } - // If all parent BVH tiles are already loaded, then this tile isn't available + // If all parent tiles are already loaded, then this tile isn't available if (!defined(promise)) { return; } @@ -1099,7 +1090,7 @@ define([ } // Load the tile - return requestTileGeometry(provider, x, y, level, undefined, layer, true); + return requestTileGeometry(provider, x, y, level, layer); }); } From d55b88b1f119e26c9dc3f628a12ba2445a859791 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 9 Nov 2018 09:11:42 -0500 Subject: [PATCH 12/23] Final tweaks for cutout terrain. --- Source/Core/CesiumTerrainProvider.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 7fd2980fd520..809884e852f4 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -960,7 +960,7 @@ define([ } // Try and load the max level tile at the position. Even if its not there we will - // still end up loading all the BVH tiles below it and its max level of availability. + // still end up loading all the metadata availability tiles below it and its max level of availability. var level = this.availability._maximumLevel; this._tilingScheme.positionToTileXY(position, level, scratchTileXY); @@ -972,7 +972,7 @@ define([ // If the tile is listed as available, then we are done return true; } - if (!provider._hasBvh) { + if (!provider._hasMetadata) { // If we don't have any layers with the bvh extension then we don't have this tile return false; } @@ -1005,12 +1005,8 @@ define([ function getAvailabilityTile(layer, x, y, level) { var availabilityLevels = layer.availabilityLevels; - if (level % availabilityLevels === 0) { - return { - level: level, - x: x, - y: y - }; + if (level !== 0 && (level % availabilityLevels === 0)) { + level -= availabilityLevels; } var parentLevel = ((level / availabilityLevels) | 0) * availabilityLevels; From 710d227641c5d47f6549a5ab6783fbd71080c39a Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 9 Nov 2018 21:33:04 -0500 Subject: [PATCH 13/23] Cached promises when trying to load tiles. --- Source/Core/CesiumTerrainProvider.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 809884e852f4..e5a7953addc6 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -67,6 +67,7 @@ define([ this.availabilityTilesLoaded = layer.availabilityTilesLoaded; this.littleEndianExtensionSize = layer.littleEndianExtensionSize; this.availabilityTilesLoaded = layer.availabilityTilesLoaded; + this.availabilityPromiseCache = {}; } /** @@ -1085,8 +1086,23 @@ define([ return; } + // Check the cache + var key = level + '-' + x + '-' + y; + var promise = layer.availabilityPromiseCache[key]; + if (defined(promise)) { + return promise; + } + // Load the tile - return requestTileGeometry(provider, x, y, level, layer); + promise = requestTileGeometry(provider, x, y, level, layer); + if (defined(promise)) { + layer.availabilityPromiseCache[key] = promise; + } + + return promise + .then(function() { + delete layer.availabilityPromiseCache[key]; + }); }); } From a9da5b41b7daefb83c7bb450a41b43553785c1b2 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 9 Nov 2018 21:46:34 -0500 Subject: [PATCH 14/23] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 832b23c0bc59..f630d224d931 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Change Log * Added `Scene.clampToHeightMostDetailed`, an asynchronous version of `Scene.clampToHeight` that uses the maximum level of detail for 3D Tiles. * Added `Scene.invertClassificationSupported` for checking if invert classification is supported. * Added `computeLineSegmentLineSegmentIntersection` to `Intersections2D`. [#7228](https://github.com/AnalyticalGraphicsInc/Cesium/pull/7228) +* Added ability to load availability from quantized mesh tiles every few levels instead of upfront in layer.json. This should speed up the load time of large tilesets significantly. [#7196](https://github.com/AnalyticalGraphicsInc/cesium/pull/7196) ##### Fixes :wrench: * Fixed issue causing polyline to look wavy depending on the position of the camera [#7209](https://github.com/AnalyticalGraphicsInc/cesium/pull/7209) From 40e028c26df85b523d477b4599f4e51428442289 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 12 Nov 2018 14:20:41 -0500 Subject: [PATCH 15/23] Added tests for metadata availability. --- Source/Core/CesiumTerrainProvider.js | 16 +++-- Specs/Core/CesiumTerrainProviderSpec.js | 57 ++++++++++++++++++ .../MetadataAvailability.tile.json | 1 + .../tile.metadataavailability.terrain | Bin 0 -> 15472 bytes 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 Specs/Data/CesiumTerrainTileJson/MetadataAvailability.tile.json create mode 100644 Specs/Data/CesiumTerrainTileJson/tile.metadataavailability.terrain diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index e5a7953addc6..281d0e5c6e0b 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -678,13 +678,6 @@ define([ return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } - // Do we want a priorityFunction - request = defaultValue(request, new Request({ - throttle : true, - throttleByServer : true, - type : RequestType.TERRAIN - })); - var urlTemplates = layerToUse.tileUrlTemplates; if (urlTemplates.length === 0) { return undefined; @@ -974,7 +967,7 @@ define([ return true; } if (!provider._hasMetadata) { - // If we don't have any layers with the bvh extension then we don't have this tile + // If we don't have any layers with the metadata extension then we don't have this tile return false; } @@ -1094,7 +1087,12 @@ define([ } // Load the tile - promise = requestTileGeometry(provider, x, y, level, layer); + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TERRAIN + }); + promise = requestTileGeometry(provider, x, y, level, layer, request); if (defined(promise)) { layer.availabilityPromiseCache[key] = promise; } diff --git a/Specs/Core/CesiumTerrainProviderSpec.js b/Specs/Core/CesiumTerrainProviderSpec.js index 944f5e5fc1b9..4d0b7bead267 100644 --- a/Specs/Core/CesiumTerrainProviderSpec.js +++ b/Specs/Core/CesiumTerrainProviderSpec.js @@ -89,6 +89,10 @@ defineSuite([ }; } + function returnMetadataAvailabilityTileJson() { + return returnTileJson('Data/CesiumTerrainTileJson/MetadataAvailability.tile.json'); + } + function waitForTile(level, x, y, requestNormals, requestWaterMask, f) { var terrainProvider = new CesiumTerrainProvider({ url : 'made/up/url', @@ -652,6 +656,33 @@ defineSuite([ }); }); + it('provides QuantizedMeshTerrainData with Metadata availability', function() { + Resource._Implementations.loadWithXhr = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + Resource._DefaultImplementations.loadWithXhr('Data/CesiumTerrainTileJson/tile.metadataavailability.terrain', responseType, method, data, headers, deferred); + }; + + returnMetadataAvailabilityTileJson(); + + var terrainProvider = new CesiumTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.hasMetadata).toBe(true); + expect(terrainProvider._layers[0].availabilityLevels).toBe(10); + expect(terrainProvider.availability.isTileAvailable(0,0,0)).toBe(true); + expect(terrainProvider.availability.isTileAvailable(0,1,0)).toBe(true); + expect(terrainProvider.availability.isTileAvailable(1,0,0)).toBe(false); + + return terrainProvider.requestTileGeometry(0, 0, 0); + }).then(function(loadedData) { + expect(loadedData).toBeInstanceOf(QuantizedMeshTerrainData); + expect(terrainProvider.availability.isTileAvailable(1,0,0)).toBe(true); + }); + }); + it('returns undefined if too many requests are already in progress', function() { var baseUrl = 'made/up/url'; @@ -726,6 +757,32 @@ defineSuite([ }); }); + it('getTileDataAvailable() with Metadata availability', function() { + Resource._Implementations.loadWithXhr = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + Resource._DefaultImplementations.loadWithXhr('Data/CesiumTerrainTileJson/tile.metadataavailability.terrain', responseType, method, data, headers, deferred); + }; + + returnMetadataAvailabilityTileJson(); + + var terrainProvider = new CesiumTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.getTileDataAvailable(0,0,0)).toBe(true); + expect(terrainProvider.getTileDataAvailable(0,0,1)).toBe(false); + + RequestScheduler.update(); + + // The previous call should have kicked off the request for the 0,0,0 tile to get the availability + return pollToPromise(function() { + return terrainProvider.getTileDataAvailable(0,0,1); + }); + }); + }); + it('supports a query string in the base URL', function() { Resource._Implementations.loadWithXhr = function(url, responseType, method, data, headers, deferred, overrideMimeType) { // Just return any old file, as long as its big enough diff --git a/Specs/Data/CesiumTerrainTileJson/MetadataAvailability.tile.json b/Specs/Data/CesiumTerrainTileJson/MetadataAvailability.tile.json new file mode 100644 index 000000000000..d6e8206cacbf --- /dev/null +++ b/Specs/Data/CesiumTerrainTileJson/MetadataAvailability.tile.json @@ -0,0 +1 @@ +{"attribution":"","bounds":[-180,-90,180,90],"bvhlevels":6,"description":"","extensions":["bvh","metadata","octvertexnormals"],"format":"quantized-mesh-1.0","maxzoom":8,"metadataAvailability":10,"minzoom":0,"name":"","projection":"EPSG:4326","scheme":"tms","tiles":["{z}/{x}/{y}.terrain?v={version}"],"version":"1.33.0"} diff --git a/Specs/Data/CesiumTerrainTileJson/tile.metadataavailability.terrain b/Specs/Data/CesiumTerrainTileJson/tile.metadataavailability.terrain new file mode 100644 index 0000000000000000000000000000000000000000..d2be946768edfdb0f468b84ee320da2e95f84d50 GIT binary patch literal 15472 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%0A(H(NPlG6K$p zY^8(hK^?keD&L1UrmZVn=$G|FC~~NeEgBE^>ggP<)E0D1y+U1ypG0?oZBpeViX7^P z)N)jP_t?oTTiK}51HGibd)F{Tl&f!S)}F22%!*wvYI6#C?^HTNT%QLbOy`H3VxgdIpk}?hM z3xManc8Z;UGZDg8*vmnU@^!06n#tQ{HspGIZo&Qsa(pOa*pa77i9Asr-J=<+wd_au zyVp4;J;=xJ>CCMxSbkYy!twVAiS5|#Da}qfz!u$rzk7f_H6&fa`1@P6mQBx9u--pP z(r>4msky%_kygbuRj1}!$VNSmcgx){IbdHD$EAr86y=Ex&a<33A@Z^4K(%|Hwp_oy ze-k*@Bh~sBhD*-Z>^a8z)=@X_ejSPY-Nv!Xx$j!4AM@Ta^n36525ECvsM_^mb7_9w zEG{Fz(BgY8e;3qHt*F-#{x05^A=aJat_qn!`k6i}O*FKS`Pyc=jE6-ZW<+9RKO8Kx1_+E5MDV^y+v$Bs1 zb_1u=>P^QL^4fvMbvq}ZzI?}9_>NJB;~ysmuuHX9uw^?f1b>ra)+a1OK@Rn~ImZ=@ zkN88AIdsqBYXZuVhb#owRck16zs`6q;dj;_CRTCW8FqwYv%{F-|4dTcPVnH&m7}ME7~{Vn9}454e7X;_8oAa zhV9?Q`P$Z3XvuqD5!b+iCjB}3Cz*+*U+&WHSIFY&c4O(d*WTiL;835O>;z|wgFFv> zBceR~aF(Q7DT{*XFiD~P#E^ICVnD)ait~@?Gi5Z#*bb+q!0zv9;Jfuw>A?Fm_i-J0 z$j=TU>fg<964Cz4_kmjX_(-YE^#E=B#VE@d%mC$8ic`6%{rV6RLWM zg>PjM`8y?TMD^nsF?XhuIH-7ljJUXarj(fE$K@V_0>!^Cy36cjkSK?DlwJI^>ZyjI z93#IwDOJ}I)OdSWX`FKBp=MQV!2Se| zC?mhDZX-!DD`O{}7D?z|Kf5l+gseVdD)gVbWr_AiRR`5?rInQY*`MnZUbj%o!beGn z(`~$^74A0~S>0Ij*kr91t!gNJYIBvLzF^Ts_BN-5j5sc1BbN~;xUFV&XZDcBENa9a zjhrRr+-%CcJFbTHvO?MN)Rp6!1)sG??oXn%gYCqZM=S*7=T6v51IE@DpD)d0@GoaF zV*R4iQpnOq?4##M>BYgBobT>`QJAn{E4~*NFg{{Y&}&-#UZK^U>&jQ^XVL}nwlZ?4 zuSore&Ff!>WA-IGj*F_hO07QMU{-HbX^dBE)_biYZN2_hL49n|Io5lO8OKI9o=a-V zV)oqshP1y&b`e?*f*wjA?&UYAxGT%Dej9p_`WnahZ5I%ONM9OQ&UB)Z72{8v!2-byapx0%bxADLRn^*%0VIA*_@3Gb)w(LZF7 zV{XnA5#^W`?Zw|@eXcX@E0%@O5DA(tKJ^HPeIaVAPF%QnafnxFY z>Eg`6lQ~~%JyIO}7lC$TFn_St-S&rx}$sHXu@IX#|k{=*Qr+bF|0 zyv*IjEOwbbd*CKceSej58SJo)_QAbwQnL{FpW0UNN%I~uZOEvxLxzqW;oW}Vz%=g> z?VTXbX-1%eJl278?JTZBT2YF}D6M z>)>xg4uRHxwQg?A4lNAn{mXH1@YilUzyD(UZ@>RGZu9SAgn}1?>o)&eIlj3@i?3BS z))vYfYV7x)?uSaZu2|2h#3;su%pf75m#rg3!b;6s)7MdhOc$*OL6pnm7NO zd2sN*2NTp568!4F^}lLvO#44m4Mr1#HI~!pKB%p6X#cA@@A+TtLqlc{8r0l- Date: Tue, 13 Nov 2018 09:21:54 -0500 Subject: [PATCH 16/23] Handle throttled requests better. --- Source/Core/CesiumTerrainProvider.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 281d0e5c6e0b..ef2ed03c4ef4 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -1093,10 +1093,12 @@ define([ type : RequestType.TERRAIN }); promise = requestTileGeometry(provider, x, y, level, layer, request); - if (defined(promise)) { - layer.availabilityPromiseCache[key] = promise; + if (!defined(promise)) { + return; } + layer.availabilityPromiseCache[key] = promise; + return promise .then(function() { delete layer.availabilityPromiseCache[key]; From 6ce6a925652696c642012f09a06569621915a07e Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 15 Nov 2018 11:05:26 -0500 Subject: [PATCH 17/23] Doc fix --- Source/Core/CesiumTerrainProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index ef2ed03c4ef4..176f30333d87 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -81,6 +81,7 @@ define([ * @param {Resource|String|Promise|Promise} options.url The URL of the Cesium terrain server. * @param {Boolean} [options.requestVertexNormals=false] Flag that indicates if the client should request additional lighting information from the server, in the form of per vertex normals if available. * @param {Boolean} [options.requestWaterMask=false] Flag that indicates if the client should request per tile water masks from the server, if available. + * @param {Boolean} [options.requestMetadata=true] Flag that indicates if the client should request per tile metadata from the server, if available. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. * @@ -134,8 +135,7 @@ define([ this._requestWaterMask = defaultValue(options.requestWaterMask, false); /** - * Boolean flag that indicates if the client should request tile metadata - * from the server. + * Boolean flag that indicates if the client should request tile metadata from the server. * @type {Boolean} * @default true * @private From dc43ebf4e39979213883463c2fc751ec3c68db28 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 16 Nov 2018 12:06:20 -0500 Subject: [PATCH 18/23] Tweaks to sampleTerrainMostDetailed to simplify and speed up. Also use our built in helper to convert a buffer to a string. --- Source/Core/CesiumTerrainProvider.js | 67 ++++++++---------------- Source/Core/sampleTerrainMostDetailed.js | 51 ++++++++++-------- 2 files changed, 52 insertions(+), 66 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 176f30333d87..ea3be2920d8a 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -13,6 +13,7 @@ define([ './DeveloperError', './Event', './GeographicTilingScheme', + './getStringFromTypedArray', './HeightmapTerrainData', './IndexDatatype', './Math', @@ -40,6 +41,7 @@ define([ DeveloperError, Event, GeographicTilingScheme, + getStringFromTypedArray, HeightmapTerrainData, IndexDatatype, CesiumMath, @@ -565,8 +567,8 @@ define([ } else if (extensionId === QuantizedMeshExtensionIds.METADATA && provider._requestMetadata) { var stringLength = view.getUint32(pos, true); if (stringLength > 0) { - var jsonString = String.fromCharCode.apply(null, - new Uint8Array(buffer, pos + Uint32Array.BYTES_PER_ELEMENT, stringLength)); + var jsonString = + getStringFromTypedArray(new Uint8Array(buffer), pos + Uint32Array.BYTES_PER_ELEMENT, stringLength); var metadata = JSON.parse(jsonString); var availableTiles = metadata.available; if (defined(availableTiles)) { @@ -941,40 +943,6 @@ define([ return this._levelZeroMaximumGeometricError / (1 << level); }; - var scratchTileXY = new Cartesian2(); - /** - * Loads the availability for the position if not already loaded. - * - * @param {Cartographic} position The position to make sure availability is loaded for. - * @returns {undefined|Promise} A promise that resolves when the availability is loaded. Undefined if availability isn't supported. - */ - CesiumTerrainProvider.prototype.loadAvailability = function(position) { - if (!defined(this._availability)) { - return undefined; - } - - // Try and load the max level tile at the position. Even if its not there we will - // still end up loading all the metadata availability tiles below it and its max level of availability. - var level = this.availability._maximumLevel; - this._tilingScheme.positionToTileXY(position, level, scratchTileXY); - - return when(loadTileAvailability(this, scratchTileXY.x, scratchTileXY.y, level)); - }; - - function loadTileAvailability(provider, x, y, level) { - if (provider._availability.isTileAvailable(level, x, y)) { - // If the tile is listed as available, then we are done - return true; - } - if (!provider._hasMetadata) { - // If we don't have any layers with the metadata extension then we don't have this tile - return false; - } - - // Load any tiles we need to figure out availability - return checkAncestorsLayers(provider, x, y, level, 0); - } - /** * Determines whether data for a tile is available to be loaded. * @@ -991,7 +959,16 @@ define([ return false; } - var result = loadTileAvailability(this, x, y, level); + if (this._availability.isTileAvailable(level, x, y)) { + // If the tile is listed as available, then we are done + return true; + } + if (!this._hasMetadata) { + // If we don't have any layers with the metadata extension then we don't have this tile + return false; + } + + var result = checkAncestorsLayers(this, x, y, level, 0); // If we got a result, return it. If it was a promise return false for now. return (typeof result === 'boolean') ? result : false; @@ -1061,7 +1038,14 @@ define([ return; } - var promise = when.resolve(); + // Check the cache + var key = level + '-' + x + '-' + y; + var promise = layer.availabilityPromiseCache[key]; + if (defined(promise)) { + return promise; + } + + promise = when.resolve(); if (level !== 0) { var parent = getAvailabilityTile(layer, x, y, level); promise = checkParentTiles(provider, parent.x, parent.y, parent.level, layer); @@ -1079,13 +1063,6 @@ define([ return; } - // Check the cache - var key = level + '-' + x + '-' + y; - var promise = layer.availabilityPromiseCache[key]; - if (defined(promise)) { - return promise; - } - // Load the tile var request = new Request({ throttle : true, diff --git a/Source/Core/sampleTerrainMostDetailed.js b/Source/Core/sampleTerrainMostDetailed.js index d0a15f5b8fc5..6d551ee63aa4 100644 --- a/Source/Core/sampleTerrainMostDetailed.js +++ b/Source/Core/sampleTerrainMostDetailed.js @@ -46,6 +46,7 @@ define([ return terrainProvider.readyPromise .then(function() { var byLevel = []; + var maxLevels = []; var availability = terrainProvider.availability; @@ -55,34 +56,42 @@ define([ } //>>includeEnd('debug'); - var promises = []; for (var i = 0; i < positions.length; ++i) { - promises.push(loadAvailability(terrainProvider, positions[i], availability, byLevel)); + var position = positions[i]; + var maxLevel = availability.computeMaximumLevelAtPosition(position); + maxLevels[i] = maxLevel; + + var atLevel = byLevel[maxLevel]; + if (!defined(atLevel)) { + byLevel[maxLevel] = atLevel = []; + } + atLevel.push(position); } - return when.all(promises) + return when.all(byLevel.map(function(positionsAtLevel, index) { + if (defined(positionsAtLevel)) { + return sampleTerrain(terrainProvider, index, positionsAtLevel); + } + })) .then(function() { - return when.all(byLevel.map(function(positionsAtLevel, index) { - if (defined(positionsAtLevel)) { - return sampleTerrain(terrainProvider, index, positionsAtLevel); + var changedPositions = []; + for (var i = 0; i < positions.length; ++i) { + var position = positions[i]; + var maxLevel = availability.computeMaximumLevelAtPosition(position); + + if (maxLevel !== maxLevels[i]) { + // Now that we loaded the max availability, a higher level has become available + changedPositions.push(position); } - })); - }).then(function() { + } + + if (changedPositions.length > 0) { + return sampleTerrainMostDetailed(terrainProvider, changedPositions); + } + }) + .then(function() { return positions; }); - }); - } - - function loadAvailability(terrainProvider, position, availability, byLevel) { - return terrainProvider.loadAvailability(position) - .then(function() { - var maxLevel = availability.computeMaximumLevelAtPosition(position); - - var atLevel = byLevel[maxLevel]; - if (!defined(atLevel)) { - byLevel[maxLevel] = atLevel = []; - } - atLevel.push(position); }); } From 41577bbeb2aab4b1836e3351927ac2c8d3bd6b34 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 19 Nov 2018 12:16:22 -0500 Subject: [PATCH 19/23] Simplified the logic for getTileDataAvailable --- Source/Core/CesiumTerrainProvider.js | 126 +++++++++------------------ 1 file changed, 41 insertions(+), 85 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index ea3be2920d8a..813a7e6627fe 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -968,15 +968,26 @@ define([ return false; } - var result = checkAncestorsLayers(this, x, y, level, 0); + var layers = this._layers; + var count = layers.length; + for (var i = 0; i < count; ++i) { + var layerResult = checkLayer(this, x, y, level, layers[i], (i===0)); + if (layerResult) { + // There is a layer that may or may not have the tile + return undefined; + } + } - // If we got a result, return it. If it was a promise return false for now. - return (typeof result === 'boolean') ? result : false; + return false; }; function getAvailabilityTile(layer, x, y, level) { + if (level === 0) { + return; + } + var availabilityLevels = layer.availabilityLevels; - if (level !== 0 && (level % availabilityLevels === 0)) { + if (level % availabilityLevels === 0) { level -= availabilityLevels; } @@ -992,95 +1003,40 @@ define([ }; } - function checkAncestorsLayers(provider, x, y, level, index) { - var layers = provider._layers; - var layer = layers[index]; - var promise; - if (defined(layer.availabilityLevels)) { - // We only need to check this layer if there wasn't - // an available list and it has the metadata extension - var parent = getAvailabilityTile(layer, x, y, level); - promise = checkParentTiles(provider, parent.x, parent.y, parent.level, layer); - } - - // Nothing to load, so this tile isn't available in this layer - if (!defined(promise)) { - // This layer doesn't have it so we can move on to check the next layer - if (++index === layers.length) - { - // No more layers left - return; - } - return checkAncestorsLayers(provider, x, y, level, index); + function checkLayer(provider, x, y, level, layer, topLayer) { + if (!defined(layer.availabilityLevels)) { + // It's definitely not in this layer + return false; } - return promise - .then(function() { - if (layer.availability.isTileAvailable(level, x, y)) { - // We now know we have this tile, so we can stop here - return; - } + var availabilityTilesLoaded = layer.availabilityTilesLoaded; + var availability = layer.availability; - // This layer doesn't have it so we can move on to check the next layer - if (++index === layers.length) - { - // No more layers left - return; + var tile = getAvailabilityTile(layer, x, y, level); + while(defined(tile)) { + if (availability.isTileAvailable(tile.level, tile.x, tile.y) && + !availabilityTilesLoaded.isTileAvailable(tile.level, tile.x, tile.y)) + { + if (!topLayer) { + // For cutout terrain, if this isn't the top layer the availability tiles + // may never get loaded, so request it here. + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TERRAIN + }); + requestTileGeometry(provider, tile.x, tile.y, tile.level, layer, request); } - return checkAncestorsLayers(provider, x, y, level, index); - }); - } - function checkParentTiles(provider, x, y, level, layer) { - var isLoaded = layer.availabilityTilesLoaded.isTileAvailable(level, x, y); - if (isLoaded) { - // Tile is loaded, so just return - return; - } - - // Check the cache - var key = level + '-' + x + '-' + y; - var promise = layer.availabilityPromiseCache[key]; - if (defined(promise)) { - return promise; - } - - promise = when.resolve(); - if (level !== 0) { - var parent = getAvailabilityTile(layer, x, y, level); - promise = checkParentTiles(provider, parent.x, parent.y, parent.level, layer); - } + // The availability tile is available, but not loaded, so there + // is still a chance that it may become available at some point + return true; + } - // If all parent tiles are already loaded, then this tile isn't available - if (!defined(promise)) { - return; + tile = getAvailabilityTile(layer, tile.x, tile.y, tile.level); } - return promise - .then(function() { - if (!layer.availability.isTileAvailable(level, x, y)) { - // All parents are loaded, so if this tile isn't available don't try to load it. - return; - } - - // Load the tile - var request = new Request({ - throttle : true, - throttleByServer : true, - type : RequestType.TERRAIN - }); - promise = requestTileGeometry(provider, x, y, level, layer, request); - if (!defined(promise)) { - return; - } - - layer.availabilityPromiseCache[key] = promise; - - return promise - .then(function() { - delete layer.availabilityPromiseCache[key]; - }); - }); + return false; } return CesiumTerrainProvider; From bfa6c8c1f2f855abfb4572e71ca416d46c4af13b Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 19 Nov 2018 14:00:35 -0500 Subject: [PATCH 20/23] Fixed test. --- Specs/Core/CesiumTerrainProviderSpec.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Specs/Core/CesiumTerrainProviderSpec.js b/Specs/Core/CesiumTerrainProviderSpec.js index 4d0b7bead267..93113eac7cd4 100644 --- a/Specs/Core/CesiumTerrainProviderSpec.js +++ b/Specs/Core/CesiumTerrainProviderSpec.js @@ -772,14 +772,11 @@ defineSuite([ return terrainProvider.ready; }).then(function() { expect(terrainProvider.getTileDataAvailable(0,0,0)).toBe(true); - expect(terrainProvider.getTileDataAvailable(0,0,1)).toBe(false); + expect(terrainProvider.getTileDataAvailable(0,0,1)).toBeUndefined(); - RequestScheduler.update(); - - // The previous call should have kicked off the request for the 0,0,0 tile to get the availability - return pollToPromise(function() { - return terrainProvider.getTileDataAvailable(0,0,1); - }); + return terrainProvider.requestTileGeometry(0, 0, 0); + }).then(function() { + expect(terrainProvider.getTileDataAvailable(0,0,1)).toBe(true); }); }); From eb74652453b38650e4e52833f8025584ca6b6660 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 19 Nov 2018 15:43:30 -0500 Subject: [PATCH 21/23] Fixed sample terrain most detailed. --- Source/Core/CesiumTerrainProvider.js | 69 +++++++++++++++---- Source/Core/EllipsoidTerrainProvider.js | 12 ++++ .../GoogleEarthEnterpriseTerrainProvider.js | 12 ++++ Source/Core/TerrainProvider.js | 10 +++ Source/Core/VRTheWorldTerrainProvider.js | 12 ++++ Source/Core/sampleTerrainMostDetailed.js | 28 ++++++-- 6 files changed, 126 insertions(+), 17 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 813a7e6627fe..1033b7bd643e 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -972,7 +972,7 @@ define([ var count = layers.length; for (var i = 0; i < count; ++i) { var layerResult = checkLayer(this, x, y, level, layers[i], (i===0)); - if (layerResult) { + if (layerResult.result) { // There is a layer that may or may not have the tile return undefined; } @@ -981,6 +981,31 @@ define([ return false; }; + /** + * Makes sure we load availability data for a tile + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded + */ + CesiumTerrainProvider.prototype.loadTileDataAvailability = function(x, y, level) { + if (!defined(this._availability) || (level > this._availability._maximumLevel) || + (this._availability.isTileAvailable(level, x, y) || (!this._hasMetadata))) { + // We know the tile is either available or not available so nothing to wait on + return undefined; + } + + var layers = this._layers; + var count = layers.length; + for (var i = 0; i < count; ++i) { + var layerResult = checkLayer(this, x, y, level, layers[i], (i===0)); + if (defined(layerResult.promise)) { + return layerResult.promise; + } + } + }; + function getAvailabilityTile(layer, x, y, level) { if (level === 0) { return; @@ -1006,9 +1031,15 @@ define([ function checkLayer(provider, x, y, level, layer, topLayer) { if (!defined(layer.availabilityLevels)) { // It's definitely not in this layer - return false; + return { + result: false + }; } + var cacheKey; + var deleteFromCache = function () { + delete layer.availabilityPromiseCache[cacheKey]; + }; var availabilityTilesLoaded = layer.availabilityTilesLoaded; var availability = layer.availability; @@ -1017,26 +1048,40 @@ define([ if (availability.isTileAvailable(tile.level, tile.x, tile.y) && !availabilityTilesLoaded.isTileAvailable(tile.level, tile.x, tile.y)) { + var requestPromise; if (!topLayer) { - // For cutout terrain, if this isn't the top layer the availability tiles - // may never get loaded, so request it here. - var request = new Request({ - throttle : true, - throttleByServer : true, - type : RequestType.TERRAIN - }); - requestTileGeometry(provider, tile.x, tile.y, tile.level, layer, request); + cacheKey = tile.level + '-' + tile.x + '-' + tile.y; + requestPromise = layer.availabilityPromiseCache[cacheKey]; + if (!defined(requestPromise)) { + // For cutout terrain, if this isn't the top layer the availability tiles + // may never get loaded, so request it here. + var request = new Request({ + throttle: true, + throttleByServer: true, + type: RequestType.TERRAIN + }); + requestPromise = requestTileGeometry(provider, tile.x, tile.y, tile.level, layer, request); + if (defined(requestPromise)) { + layer.availabilityPromiseCache[cacheKey] = requestPromise; + requestPromise.then(deleteFromCache); + } + } } // The availability tile is available, but not loaded, so there // is still a chance that it may become available at some point - return true; + return { + result: true, + promise: requestPromise + }; } tile = getAvailabilityTile(layer, tile.x, tile.y, tile.level); } - return false; + return { + result: false + }; } return CesiumTerrainProvider; diff --git a/Source/Core/EllipsoidTerrainProvider.js b/Source/Core/EllipsoidTerrainProvider.js index 8ee50e2fc926..7266b9b11a00 100644 --- a/Source/Core/EllipsoidTerrainProvider.js +++ b/Source/Core/EllipsoidTerrainProvider.js @@ -189,5 +189,17 @@ define([ return undefined; }; + /** + * Makes sure we load availability data for a tile + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded + */ + EllipsoidTerrainProvider.prototype.loadTileDataAvailability = function(x, y, level) { + return undefined; + }; + return EllipsoidTerrainProvider; }); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index fe0b0c415eeb..3b8207872418 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -580,6 +580,18 @@ define([ return false; }; + /** + * Makes sure we load availability data for a tile + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded + */ + GoogleEarthEnterpriseTerrainProvider.prototype.loadTileDataAvailability = function(x, y, level) { + return undefined; + }; + // // Functions to handle imagery packets // diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 44a28042e1b1..122d3f0762a3 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -227,5 +227,15 @@ define([ */ TerrainProvider.prototype.getTileDataAvailable = DeveloperError.throwInstantiationError; + /** + * Makes sure we load availability data for a tile + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded + */ + TerrainProvider.prototype.loadTileDataAvailability = DeveloperError.throwInstantiationError; + return TerrainProvider; }); diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index 744a228afed8..4fe88acac15f 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -355,5 +355,17 @@ define([ return undefined; }; + /** + * Makes sure we load availability data for a tile + * + * @param {Number} x The X coordinate of the tile for which to request geometry. + * @param {Number} y The Y coordinate of the tile for which to request geometry. + * @param {Number} level The level of the tile for which to request geometry. + * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded + */ + VRTheWorldTerrainProvider.prototype.loadTileDataAvailability = function(x, y, level) { + return undefined; + }; + return VRTheWorldTerrainProvider; }); diff --git a/Source/Core/sampleTerrainMostDetailed.js b/Source/Core/sampleTerrainMostDetailed.js index 6d551ee63aa4..2b2a0cc5d300 100644 --- a/Source/Core/sampleTerrainMostDetailed.js +++ b/Source/Core/sampleTerrainMostDetailed.js @@ -1,15 +1,19 @@ define([ '../ThirdParty/when', + './Cartesian2', './defined', './DeveloperError', './sampleTerrain' ], function( when, + Cartesian2, defined, DeveloperError, sampleTerrain) { 'use strict'; + var scratchCartesian2 = new Cartesian2(); + /** * Initiates a sampleTerrain() request at the maximum available tile level for a terrain dataset. * @@ -56,10 +60,21 @@ define([ } //>>includeEnd('debug'); + var promises = []; for (var i = 0; i < positions.length; ++i) { var position = positions[i]; var maxLevel = availability.computeMaximumLevelAtPosition(position); maxLevels[i] = maxLevel; + if (maxLevel === 0) { + // This is a special case where we have a parent terrain and we are requesting + // heights from an area that isn't covered by the top level terrain at all. + // This will essentially trigger the loading of the parent terrains root tile + terrainProvider.tilingScheme.positionToTileXY(position, 1, scratchCartesian2); + var promise = terrainProvider.loadTileDataAvailability(scratchCartesian2.x, scratchCartesian2.y, 1); + if (defined(promise)) { + promises.push(promise); + } + } var atLevel = byLevel[maxLevel]; if (!defined(atLevel)) { @@ -68,11 +83,14 @@ define([ atLevel.push(position); } - return when.all(byLevel.map(function(positionsAtLevel, index) { - if (defined(positionsAtLevel)) { - return sampleTerrain(terrainProvider, index, positionsAtLevel); - } - })) + return when.all(promises) + .then(function() { + return when.all(byLevel.map(function(positionsAtLevel, index) { + if (defined(positionsAtLevel)) { + return sampleTerrain(terrainProvider, index, positionsAtLevel); + } + })); + }) .then(function() { var changedPositions = []; for (var i = 0; i < positions.length; ++i) { From b08bb23023d9cd323c379ac2a72f3cbee86bfac5 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 19 Nov 2018 16:25:02 -0500 Subject: [PATCH 22/23] Tweaked changes.md --- CHANGES.md | 5 ++++- Source/Core/TerrainProvider.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d7513ddce301..baa16dfc8fe5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ Change Log ### 1.52 - 2018-12-03 +##### Breaking Changes :mega: +* `TerrainProviders` must now implement the `loadTileDataAvailability` method. + ##### Additions :tada: * Added functions to get the most detailed height of 3D Tiles on-screen or off-screen. [#7115](https://github.com/AnalyticalGraphicsInc/cesium/pull/7115) * Added `Scene.sampleHeightMostDetailed`, an asynchronous version of `Scene.sampleHeight` that uses the maximum level of detail for 3D Tiles. @@ -10,7 +13,7 @@ Change Log * Added support for high dynamic range rendering. It is enabled by default when supported, but can be disabled with `Scene.highDynamicRange`. [#7017](https://github.com/AnalyticalGraphicsInc/cesium/pull/7017) * Added `Scene.invertClassificationSupported` for checking if invert classification is supported. * Added `computeLineSegmentLineSegmentIntersection` to `Intersections2D`. [#7228](https://github.com/AnalyticalGraphicsInc/Cesium/pull/7228) -* Added ability to load availability from quantized mesh tiles every few levels instead of upfront in layer.json. This should speed up the load time of large tilesets significantly. [#7196](https://github.com/AnalyticalGraphicsInc/cesium/pull/7196) +* Added ability to load availability progressively from a quantized mesh extension instead of upfront. This will speed up load time and reduce memory usage. [#7196](https://github.com/AnalyticalGraphicsInc/cesium/pull/7196) * Added the ability to apply styles to 3D Tilesets that don't contain features. [#7255](https://github.com/AnalyticalGraphicsInc/Cesium/pull/7255) ##### Fixes :wrench: diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 122d3f0762a3..efa99393e8de 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -227,7 +227,7 @@ define([ */ TerrainProvider.prototype.getTileDataAvailable = DeveloperError.throwInstantiationError; - /** + /** * Makes sure we load availability data for a tile * * @param {Number} x The X coordinate of the tile for which to request geometry. From a169f5455d6caf981f3c1a378c46124dd9a8323e Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 19 Nov 2018 17:19:52 -0500 Subject: [PATCH 23/23] Update CHANGES.md Co-Authored-By: tfili --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index baa16dfc8fe5..33a24e094fee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ Change Log ### 1.52 - 2018-12-03 ##### Breaking Changes :mega: -* `TerrainProviders` must now implement the `loadTileDataAvailability` method. +* `TerrainProviders` that implement `availability` must now also implement the `loadTileDataAvailability` method. ##### Additions :tada: * Added functions to get the most detailed height of 3D Tiles on-screen or off-screen. [#7115](https://github.com/AnalyticalGraphicsInc/cesium/pull/7115)